diff --git a/.github/workflows/jsonschema-lint.yaml b/.github/workflows/jsonschema-lint.yaml new file mode 100644 index 00000000..db0017b5 --- /dev/null +++ b/.github/workflows/jsonschema-lint.yaml @@ -0,0 +1,45 @@ +name: JSON Schema Lint + +on: + push: + branches: + - main + paths: + - 'rulesets/schemas/**/*.json' + - '.github/workflows/jsonschema-lint.yaml' + pull_request: + branches: + - main + paths: + - 'rulesets/schemas/**/*.json' + - '.github/workflows/jsonschema-lint.yaml' + +jobs: + lint-jsonschema: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + - name: Install JSON Schema CLI + run: npm install @sourcemeta/jsonschema@11.5.1 + - name: Lint JSON Schemas + run: npx jsonschema lint rulesets/schemas + continue-on-error: true + - name: Fix JSON Schema issues + run: npx jsonschema lint rulesets/schemas --fix + - name: Commit fixes + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add rulesets/schemas/ + if ! git diff --staged --quiet; then + git commit -m "fix(schema): auto-fix JSON schema linting issues" + git push origin HEAD:${{ github.head_ref || github.ref_name }} + fi diff --git a/package-lock.json b/package-lock.json index ec2cc2bc..6e417934 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@sourcemeta/jsonschema": "^11.5.1", "node-fetch": "^3.1.0", "tar": "^6.1.11" }, @@ -20,6 +21,30 @@ "node": ">=16.0.0" } }, + "node_modules/@sourcemeta/jsonschema": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/@sourcemeta/jsonschema/-/jsonschema-11.5.1.tgz", + "integrity": "sha512-ScaIt6xL7dQxsr3aIzZD+MvGYtELvXD8Z5p7gEZxFJr0rja71csZpHJNRdhU3q4IiUSRPFa1IjXdXHrcIIRdSg==", + "cpu": [ + "x64", + "arm64" + ], + "license": "AGPL-3.0", + "os": [ + "darwin", + "linux", + "win32" + ], + "bin": { + "jsonschema": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sourcemeta" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -180,6 +205,11 @@ } }, "dependencies": { + "@sourcemeta/jsonschema": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/@sourcemeta/jsonschema/-/jsonschema-11.5.1.tgz", + "integrity": "sha512-ScaIt6xL7dQxsr3aIzZD+MvGYtELvXD8Z5p7gEZxFJr0rja71csZpHJNRdhU3q4IiUSRPFa1IjXdXHrcIIRdSg==" + }, "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", diff --git a/package.json b/package.json index 0cf2e5cf..a8574443 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "npm-install" ], "dependencies": { + "@sourcemeta/jsonschema": "^11.5.1", "node-fetch": "^3.2.10", "tar": "^6.1.11" }, diff --git a/rulesets/schemas/rule.schema.json b/rulesets/schemas/rule.schema.json index ee91dc0e..a8ac7933 100644 --- a/rulesets/schemas/rule.schema.json +++ b/rulesets/schemas/rule.schema.json @@ -12,7 +12,7 @@ }, "type": { "enum": [ "style", "validation" ], - "errorMessage": "allowed types are \"style\" and \"validation\"" + "x-errorMessage": "allowed types are \"style\" and \"validation\"" }, "then": { "anyOf": [ @@ -51,7 +51,7 @@ "documentationUrl": { "type": "string", "format": "url", - "errorMessage": "must be a valid URL" + "x-errorMessage": "must be a valid URL" }, "formats": { "$ref": "#/definitions/Formats" @@ -97,20 +97,20 @@ "enum": [ -1, 0, 1, 2, 3 ] }, "Format": { - "$anchor": "format", - "errorMessage": "must be a valid format", - "spectral-runtime": "spectral-format" + "x-$anchor": "format", + "x-errorMessage": "must be a valid format", + "x-spectral-runtime": "spectral-format" }, "Formats": { - "$anchor": "formats", + "x-$anchor": "formats", "type": "array", "items": { "$ref": "#/definitions/Format" }, - "errorMessage": "must be an array of formats" + "x-errorMessage": "must be an array of formats" }, "Functions": { - "$anchor": "functions", + "x-$anchor": "functions", "type": "array", "items": { "type": "string" @@ -120,7 +120,7 @@ "enum": [ "error", "warn", "info", "hint", "off" ] }, "Severity": { - "$anchor": "severity", + "x-$anchor": "severity", "oneOf": [ { "$ref": "#/definitions/DiagnosticSeverity" @@ -129,22 +129,20 @@ "$ref": "#/definitions/HumanReadableSeverity" } ], - "errorMessage": "the value has to be one of: 0, 1, 2, 3 or \"error\", \"warn\", \"info\", \"hint\", \"off\"" + "x-errorMessage": "the value has to be one of: 0, 1, 2, 3 or \"error\", \"warn\", \"info\", \"hint\", \"off\"" }, "Then": { "type": "object", + "properties": { + "field": { + "type": "string" + } + }, + "required": [ "function" ], + "x-spectral-runtime": "ruleset-function", "allOf": [ { - "properties": { - "field": { - "type": "string" - } - } - }, - { - "type": "object", - "required": [ "function" ], - "spectral-runtime": "ruleset-function" + "type": "object" } ] } diff --git a/rulesets/schemas/ruleset.schema.json b/rulesets/schemas/ruleset.schema.json index f6dd2d0b..6bbe810f 100644 --- a/rulesets/schemas/ruleset.schema.json +++ b/rulesets/schemas/ruleset.schema.json @@ -13,7 +13,7 @@ "type": "object", "propertyNames": { "pattern": "^[A-Za-z][A-Za-z\\d_-]*$", - "errorMessage": { + "x-errorMessage": { "pattern": "to avoid confusion the name must match /^[A-Za-z][A-Za-z\\d_-]*$/ regular expression", "minLength": "the name of an alias must not be empty" } @@ -43,14 +43,14 @@ "$ref": "#/definitions/Given" } }, - "errorMessage": "a valid target must contain given and non-empty formats" + "x-errorMessage": "a valid target must contain given and non-empty formats" }, - "errorMessage": { + "x-errorMessage": { "minItems": "targets must have at least a single alias definition" } } }, - "errorMessage": { + "x-errorMessage": { "required": "targets must be present and have at least a single alias definition" } }, @@ -62,7 +62,7 @@ "documentationUrl": { "type": "string", "format": "url", - "errorMessage": "must be a valid URL" + "x-errorMessage": "must be a valid URL" }, "formats": { "$ref": "#/definitions/Formats" @@ -89,7 +89,7 @@ "pattern": "^[^#]+#", "minLength": 1 }, - "errorMessage": "must be a non-empty array of glob patterns" + "x-errorMessage": "must be a non-empty array of glob patterns" } } }, @@ -103,48 +103,46 @@ "additionalProperties": { "$ref": "#/definitions/Severity" }, - "errorMessage": { + "x-errorMessage": { "enum": "must be a valid severity level" } } }, "additionalProperties": false, - "errorMessage": { + "x-errorMessage": { "required": "must contain rules when JSON Pointers are defined", "additionalProperties": "must not override any other property than rules when JSON Pointers are defined" } }, "else": { - "allOf": [ - { - "type": "object", - "required": [ "files" ], - "properties": { - "files": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "pattern": "[^#]", - "minLength": 1 - }, - "errorMessage": "must be a non-empty array of glob patterns" - } + "type": "object", + "required": [ "files" ], + "properties": { + "files": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "[^#]", + "minLength": 1 }, - "errorMessage": { - "type": "must be an override, i.e. { \"files\": [\"v2/**/*.json\"], \"rules\": {} }" - } + "x-errorMessage": "must be a non-empty array of glob patterns" + } + }, + "x-errorMessage": { + "type": "must be an override, i.e. { \"files\": [\"v2/**/*.json\"], \"rules\": {} }" + }, + "anyOf": [ + { + "required": [ "extends" ] }, + { + "required": [ "rules" ] + } + ], + "allOf": [ { "type": "object", - "anyOf": [ - { - "required": [ "extends" ] - }, - { - "required": [ "rules" ] - } - ], "properties": { "extends": { "$ref": "#/properties/extends" @@ -166,7 +164,7 @@ ] } }, - "errorMessage": { + "x-errorMessage": { "minItems": "must not be empty" } }, @@ -198,7 +196,7 @@ }, "type": { "enum": [ "style", "validation" ], - "errorMessage": "allowed types are \"style\" and \"validation\"" + "x-errorMessage": "allowed types are \"style\" and \"validation\"" }, "then": { "anyOf": [ @@ -237,7 +235,7 @@ "documentationUrl": { "type": "string", "format": "url", - "errorMessage": "must be a valid URL" + "x-errorMessage": "must be a valid URL" }, "formats": { "$ref": "#/definitions/Formats" @@ -278,7 +276,7 @@ } }, "additionalProperties": false, - "errorMessage": { + "x-errorMessage": { "required": "the rule must have at least \"given\" and \"then\" properties" } }, @@ -298,7 +296,7 @@ "enum": [ -1, 0, 1, 2, 3 ] }, "Extends": { - "$anchor": "extends", + "x-$anchor": "extends", "oneOf": [ { "type": "string" @@ -319,7 +317,7 @@ }, { "enum": [ "all", "recommended", "off" ], - "errorMessage": "allowed types are \"off\", \"recommended\" and \"all\"" + "x-errorMessage": "allowed types are \"off\", \"recommended\" and \"all\"" } ], "additionalItems": false @@ -330,20 +328,20 @@ ] }, "Format": { - "$anchor": "format", - "errorMessage": "must be a valid format", - "spectral-runtime": "spectral-format" + "x-$anchor": "format", + "x-errorMessage": "must be a valid format", + "x-spectral-runtime": "spectral-format" }, "Formats": { - "$anchor": "formats", + "x-$anchor": "formats", "type": "array", "items": { "$ref": "#/definitions/Format" }, - "errorMessage": "must be an array of formats" + "x-errorMessage": "must be an array of formats" }, "Function": { - "$anchor": "function", + "x-$anchor": "function", "type": "object", "required": [ "function" ], "properties": { @@ -353,29 +351,29 @@ } }, "Functions": { - "$anchor": "functions", + "x-$anchor": "functions", "type": "array", "items": { "type": "string" } }, "FunctionsDir": { - "$anchor": "functionsDir", + "x-$anchor": "functionsDir", "type": "string" }, "Given": { - "$anchor": "given", + "x-$anchor": "given", "if": { "type": "array" }, "then": { - "$anchor": "arrayish-given", + "x-$anchor": "arrayish-given", "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/PathExpression" }, - "errorMessage": { + "x-errorMessage": { "minItems": "must be a non-empty array of expressions" } }, @@ -390,10 +388,10 @@ "$id": "path-expression", "type": "string", "pattern": "^[$#]", - "errorMessage": "must be a valid JSON Path expression or a reference to the existing Alias optionally paired with a JSON Path expression subset" + "x-errorMessage": "must be a valid JSON Path expression or a reference to the existing Alias optionally paired with a JSON Path expression subset" }, "Severity": { - "$anchor": "severity", + "x-$anchor": "severity", "oneOf": [ { "$ref": "#/definitions/DiagnosticSeverity" @@ -402,22 +400,20 @@ "$ref": "#/definitions/HumanReadableSeverity" } ], - "errorMessage": "the value has to be one of: 0, 1, 2, 3 or \"error\", \"warn\", \"info\", \"hint\", \"off\"" + "x-errorMessage": "the value has to be one of: 0, 1, 2, 3 or \"error\", \"warn\", \"info\", \"hint\", \"off\"" }, "Then": { "type": "object", + "properties": { + "field": { + "type": "string" + } + }, + "required": [ "function" ], + "x-spectral-runtime": "ruleset-function", "allOf": [ { - "properties": { - "field": { - "type": "string" - } - } - }, - { - "type": "object", - "required": [ "function" ], - "spectral-runtime": "ruleset-function" + "type": "object" } ] }