diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 879fbfe7..6242ecfc 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -57,6 +57,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/install-pinned-toolchains - run: make setup + - run: make test-json-schemas - run: make lint-js build-x86_64-apple-darwin: diff --git a/Makefile b/Makefile index b17b4a5f..7c30b084 100644 --- a/Makefile +++ b/Makefile @@ -13,11 +13,16 @@ setup: setup-js setup-gitignore-dirs setup-rust .PHONY: test test: \ test-warning \ + test-json-schemas \ test-rust \ test-rust-ffi \ benchmark-rust \ test-scripts +.PHONY: test-json-schemas +test-json-schemas: setup-js + bun run -- 'script/validate-json-schemas.ts' + .PHONY: test-warning test-warning: @echo "Warning: tests are slow to run right now." diff --git a/script/validate-json-schemas.ts b/script/validate-json-schemas.ts new file mode 100644 index 00000000..088aedce --- /dev/null +++ b/script/validate-json-schemas.ts @@ -0,0 +1,54 @@ +import { spawnSync } from "node:child_process"; +import { readdir } from "node:fs/promises"; +import path from "node:path"; + +const schemaRoot = "node_modules/cubing/experimental-json-schema/kpuzzle"; +const sources = ["samples", "src/lib/scramble/puzzles/definitions"]; + +async function jsonFiles(directory: string): Promise { + const entries = await readdir(directory, { withFileTypes: true }); + const nested = await Promise.all( + entries.map(async (entry) => { + const entryPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + return jsonFiles(entryPath); + } + return entry.isFile() && entry.name.endsWith(".json") ? [entryPath] : []; + }), + ); + return nested.flat(); +} + +const files = (await Promise.all(sources.map(jsonFiles))).flat().sort(); +if (files.length === 0) { + throw new Error("No sample or puzzle definition JSON files were found."); +} + +const schemas = new Map(); +for (const file of files) { + const schemaName = + file.endsWith(".def.json") || file.endsWith(".kpuzzle.json") + ? "KPuzzleDefinition.schema.json" + : "KPatternData.schema.json"; + const schema = path.posix.join(schemaRoot, schemaName); + const group = schemas.get(schema) ?? []; + group.push(file.replaceAll(path.sep, "/")); + schemas.set(schema, group); +} + +for (const [schema, dataFiles] of schemas) { + const args = ["x", "ajv-cli@5.0.0", "validate", "-s", schema]; + for (const file of dataFiles) { + args.push("-d", file); + } + + const result = spawnSync(process.execPath, args, { stdio: "inherit" }); + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} + +console.log(`Validated ${files.length} sample and puzzle JSON files.`);