callumalpass_tasknotes/scripts/run-tasknotes-spec-conformance.mjs

152 lines
4.5 KiB
JavaScript
Raw Normal View History

import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const tasknotesRoot = resolve(scriptDir, "..");
const specRoot = resolve(tasknotesRoot, "../tasknotes-spec");
const adapterPath = resolve(specRoot, "conformance/adapters/tasknotes.adapter.mjs");
const defaultWaiverPath = resolve(tasknotesRoot, "conformance/waivers.json");
function requirePath(path, label) {
if (!existsSync(path)) {
console.error(`Missing ${label}: ${path}`);
process.exit(1);
}
}
function run(command, args, options = {}) {
return spawnSync(command, args, {
cwd: options.cwd,
env: options.env ? { ...process.env, ...options.env } : process.env,
stdio: "inherit",
});
}
function loadWaivers(path) {
if (!existsSync(path)) {
return [];
}
const parsed = JSON.parse(readFileSync(path, "utf8"));
const waivers = Array.isArray(parsed?.waivers) ? parsed.waivers : [];
return waivers.map((entry, index) => {
const id = typeof entry?.id === "string" ? entry.id.trim() : "";
const reason = typeof entry?.reason === "string" ? entry.reason.trim() : "";
const justification = typeof entry?.justification === "string" ? entry.justification.trim() : "";
const scope = typeof entry?.scope === "string" ? entry.scope.trim() : "unspecified";
if (!id) {
throw new Error(`Invalid waiver at index ${index}: missing id`);
}
if (!reason) {
throw new Error(`Invalid waiver ${id}: missing reason`);
}
if (!justification) {
throw new Error(`Invalid waiver ${id}: missing justification`);
}
return { id, reason, justification, scope };
});
}
function applyFixtureWaivers(root, waivers) {
if (waivers.length === 0) {
return { restore() {}, removedCount: 0 };
}
const fixturesDir = resolve(root, "conformance/fixtures");
const waiverIds = new Map(waivers.map((waiver) => [waiver.id, waiver]));
const unmatched = new Set(waiverIds.keys());
const backups = [];
let removedCount = 0;
for (const file of readdirSync(fixturesDir)) {
if (!file.endsWith(".json")) continue;
const path = resolve(fixturesDir, file);
const original = readFileSync(path, "utf8");
backups.push({ path, original });
const fixtures = JSON.parse(original);
if (!Array.isArray(fixtures)) {
continue;
}
const filtered = fixtures.filter((fixture) => {
const id = typeof fixture?.id === "string" ? fixture.id : "";
const shouldKeep = !waiverIds.has(id);
if (!shouldKeep) {
unmatched.delete(id);
removedCount += 1;
}
return shouldKeep;
});
if (filtered.length !== fixtures.length) {
writeFileSync(path, `${JSON.stringify(filtered, null, 2)}\n`);
}
}
if (unmatched.size > 0) {
for (const backup of backups) {
writeFileSync(backup.path, backup.original);
}
throw new Error(
`Waiver ids not found in generated fixtures: ${[...unmatched].sort().join(", ")}`,
);
}
return {
removedCount,
restore() {
for (const backup of backups) {
writeFileSync(backup.path, backup.original);
}
},
};
}
requirePath(specRoot, "tasknotes-spec repo");
requirePath(resolve(specRoot, "package.json"), "tasknotes-spec package.json");
const waiverPath = process.env.TASKNOTES_CONFORMANCE_WAIVERS
? resolve(tasknotesRoot, process.env.TASKNOTES_CONFORMANCE_WAIVERS)
: defaultWaiverPath;
const waivers = loadWaivers(waiverPath);
let result = run("npm", ["run", "conformance:generate"], { cwd: specRoot });
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
result = run("npm", ["run", "conformance:build:tasknotes-bridge"], { cwd: specRoot });
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
const waiverSession = applyFixtureWaivers(specRoot, waivers);
if (waivers.length > 0) {
console.warn(
`Applying ${waivers.length} TaskNotes conformance waiver(s) from ${waiverPath} (${waiverSession.removedCount} fixture(s) skipped):`,
);
for (const waiver of waivers) {
console.warn(`- ${waiver.id} [${waiver.scope}] ${waiver.reason}`);
console.warn(` justification: ${waiver.justification}`);
}
}
try {
result = run("npm", ["run", "conformance:test"], {
cwd: specRoot,
env: { TASKNOTES_ADAPTER: adapterPath },
});
} finally {
waiverSession.restore();
}
if (result.status !== 0) {
process.exit(result.status ?? 1);
}