Files

80 lines
2.5 KiB
JavaScript

#!/usr/bin/env node
/**
* Validate a question JSON file against the bundled schema.
*
* Usage:
* node validate.mjs <json_file>
* echo '<json>' | node validate.mjs -
*
* Exit codes: 0 = valid, 1 = errors found, 2 = usage error.
*/
import { readFileSync, existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const SCHEMA_PATH = resolve(__dirname, "../schema/question.schema.json");
const VALID_TYPES = [
"single_choice", "multiple_choice", "true_false",
"fill_blank", "short_answer", "code_reading", "scenario",
];
const ID_RE = /^(sc|mc|tf|fb|sa|cr|sn)-\d{3}$/;
function loadJson(path) {
const raw = path === "-" ? readFileSync(0, "utf-8") : readFileSync(path, "utf-8");
return JSON.parse(raw);
}
function validate(data) {
const errors = [];
const requiredTop = ["topic", "type", "schema_version", "questions"];
for (const field of requiredTop) {
if (!(field in data)) errors.push(`Missing required field: ${field}`);
}
if (!VALID_TYPES.includes(data.type)) errors.push(`Invalid type: ${data.type}`);
if (data.schema_version !== "1.0.0")
errors.push(`schema_version must be '1.0.0', got '${data.schema_version}'`);
for (const [i, q] of (data.questions || []).entries()) {
const p = `questions[${i}]`;
for (const field of ["id", "type", "difficulty", "tags", "question", "explanation"]) {
if (!(field in q)) errors.push(`${p}: missing '${field}'`);
}
if (!VALID_TYPES.includes(q.type)) errors.push(`${p}: invalid type '${q.type}'`);
if (q.difficulty !== undefined && !(q.difficulty >= 1 && q.difficulty <= 5))
errors.push(`${p}: difficulty must be 1-5`);
if (q.id !== undefined && !ID_RE.test(q.id))
errors.push(`${p}: id '${q.id}' doesn't match pattern {type}-{000}`);
}
return errors;
}
function main() {
if (process.argv.length !== 3) {
console.error(`Usage: node ${process.argv[1]} <json_file | ->`);
process.exit(2);
}
if (!existsSync(SCHEMA_PATH)) {
console.error(`❌ Schema not found: ${SCHEMA_PATH}`);
process.exit(2);
}
const data = loadJson(process.argv[2]);
const errors = validate(data);
if (errors.length) {
console.log(`❌ ${errors.length} error(s):`);
for (const e of errors) console.log(` • ${e}`);
process.exit(1);
} else {
console.log(`✅ Valid — ${(data.questions || []).length} question(s)`);
process.exit(0);
}
}
main();