Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Release

on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
id-token: write

steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
registry-url: https://registry.npmjs.org

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Test
run: npm test

- name: Release
run: npm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
38 changes: 38 additions & 0 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"branches": ["main"],
"tagFormat": "v${version}",
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
[
"@semantic-release/npm",
{
"npmPublish": true
}
],
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md", "package.json", "package-lock.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [2.0.1] - 2026-04-07

### Changed

- The `content-type` rule now allows other non-JSON content, but still demands all JSON requests/responses use `application/vnd.api+json`.

## [2.0.1] - 2026-03-15

### Added
- Created TypeScript version of the ruleset. YAML still available.
Expand Down
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,17 @@ It is intended to include rules that rely only on Spectral core functions. At th
- Prefer small, reviewable commits.
- Include a short summary of what changed and why.
- Mention any rule behavior changes that may affect downstream users.

## Automated Releases

Releases are automated with semantic-release via GitHub Actions on pushes to `main`.

- Commits should follow Conventional Commits (`feat:`, `fix:`, `feat!:`) so the next version can be calculated correctly.
- The workflow publishes to npm and creates a GitHub release and tag like `v2.1.0`.
- Configure repository secret `NPM_TOKEN` with publish access to `@apisyouwonthate/spectral-jsonapi`.

You can test release logic locally in dry-run mode:

```bash
npm run release -- --dry-run --no-ci
```
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Create a local ruleset file:
```yaml
# .spectral.yaml
extends:
- spectral:oas
- "@apisyouwonthate/spectral-jsonapi"
- spectral:oas
- "@apisyouwonthate/spectral-jsonapi"
```

Lint your OpenAPI description:
Expand All @@ -34,8 +34,8 @@ Use this when the project already installs dependencies with npm.
```yaml
# .spectral.yaml
extends:
- spectral:oas
- "@apisyouwonthate/spectral-jsonapi"
- spectral:oas
- "@apisyouwonthate/spectral-jsonapi"
```

### Use legacy YAML directly from GitHub
Expand All @@ -44,7 +44,7 @@ Use this when you want to consume the generated YAML ruleset without installing

```yaml
extends:
- "https://raw.githubusercontent.com/apisyouwonthate/spectral-jsonapi/refs/heads/main/.spectral.yml"
- "https://raw.githubusercontent.com/apisyouwonthate/spectral-jsonapi/refs/heads/main/.spectral.yml"
```

Once you have the ruleset set up, you can run Spectral in the same directory as your `.spectral.yml` ruleset, and it will include the JSON:API rules in its check
Expand All @@ -71,20 +71,20 @@ Set `x-jsonapi-virtual-resource: true` on the resource schema to skip the `resou

```yaml
components:
schemas:
AvailableSlotResource:
type: object
x-jsonapi-virtual-resource: true
required:
- type
- attributes
properties:
type:
type: string
enum:
- availableSlot
attributes:
$ref: "#/components/schemas/AvailableSlotAttributes"
schemas:
AvailableSlotResource:
type: object
x-jsonapi-virtual-resource: true
required:
- type
- attributes
properties:
type:
type: string
enum:
- availableSlot
attributes:
$ref: "#/components/schemas/AvailableSlotAttributes"
```

## 👥 Contributing
Expand Down
73 changes: 72 additions & 1 deletion __tests__/content-type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,34 @@ describe("Rule content-type", () => {
);
});

it("allows non-json content type", async () => {
await expectRuleErrors(
spectral,
"content-type",
{
openapi: "3.1.0",
info: { title: "Test", version: "1.0.0" },
paths: {
"/items": {
get: {
responses: {
"200": {
description: "ok",
content: {
"text/html": {
schema: { type: "string" },
},
},
},
},
},
},
},
},
[],
);
});

it("invalid content type", async () => {
await expectRuleErrors(
spectral,
Expand Down Expand Up @@ -91,7 +119,7 @@ describe("Rule content-type", () => {
[
{
message:
"Use application/vnd.api+json for all request and response bodies.",
"Use application/vnd.api+json for JSON request and response bodies.",
path: [
"paths",
"/items",
Expand All @@ -106,4 +134,47 @@ describe("Rule content-type", () => {
],
);
});

it("invalid +json content type", async () => {
await expectRuleErrors(
spectral,
"content-type",
{
openapi: "3.1.0",
info: { title: "Test", version: "1.0.0" },
paths: {
"/items": {
get: {
responses: {
"200": {
description: "ok",
content: {
"application/problem+json": {
schema: { type: "object" },
},
},
},
},
},
},
},
},
[
{
message:
"Use application/vnd.api+json for JSON request and response bodies.",
path: [
"paths",
"/items",
"get",
"responses",
"200",
"content",
"application/problem+json",
],
severity: DiagnosticSeverity.Error,
},
],
);
});
});
Loading
Loading