- cardDirs: course/cards only from Module 5 (spec 6.2a); fixture modules 04/05 and tests - coupling-audit: noun words match as UPPERCASE, PascalCase or a whole string literal; lowercase identifiers like key/flag pass; card names keep every variant - isMain() argv guard replaces import.meta.main in coupling-audit and verify-references; engines.node >=22 - expansion-check: git diff --no-renames so moving engine code into content fails - start-module and other tools: clear error when .course.json is missing - check-links: malformed URLs reported as invalid URL instead of throwing Co-Authored-By: Claude Opus 5.5 <[email protected]>
66 lines
2.9 KiB
JavaScript
66 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
// For each module <= N with a reference/: copy reference + tests of modules 01..module into a temp dir, run tests.
|
|
// The card data (loaded by the references from Module 5) is staged as <dir>/cards: course/cards
|
|
// from Module 5 on (Modules 1-4 keep their cards in code), plus the cards/ of each module up to
|
|
// the one verified. Each tested module's levels/ (the puzzle levels its tests load, from
|
|
// Module 10) is staged as <dir>/levels.
|
|
import { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
import { tmpdir } from "node:os";
|
|
import { basename, join } from "node:path";
|
|
import { cardDirs, moduleDirs, testFiles, isMain } from "./lib.mjs";
|
|
|
|
// A staged reference has no node_modules, so TS installs the reference's own
|
|
// pinned devDependencies first (a bare `npx vitest` would fetch an unrelated version).
|
|
const DEFAULTS = {
|
|
rust: "cargo test",
|
|
ts: "npm install --no-audit --no-fund --loglevel=error && npx vitest run",
|
|
};
|
|
|
|
export function commandFor(lang, env = process.env) {
|
|
return env[`VERIFY_CMD_${lang.toUpperCase()}`] || DEFAULTS[lang];
|
|
}
|
|
|
|
export function planVerification(mods, upTo) {
|
|
return mods
|
|
.filter((m) => Number(m.num) >= 1 && Number(m.num) <= upTo)
|
|
.map((m) => ({
|
|
...m,
|
|
langs: ["rust", "ts"].filter((l) => existsSync(join(m.path, "reference", l))),
|
|
testModules: mods.filter((t) => Number(t.num) >= 1 && Number(t.num) <= Number(m.num)),
|
|
}))
|
|
.filter((p) => p.langs.length > 0);
|
|
}
|
|
|
|
export function stage(plan, lang) {
|
|
const dir = mkdtempSync(join(tmpdir(), `verify-${plan.num}-${lang}-`));
|
|
cpSync(join(plan.path, "reference", lang), dir, { recursive: true });
|
|
mkdirSync(join(dir, "tests"), { recursive: true });
|
|
for (const t of plan.testModules) {
|
|
for (const f of testFiles(t.path, lang)) cpSync(f, join(dir, "tests", basename(f)));
|
|
if (existsSync(join(t.path, "levels"))) cpSync(join(t.path, "levels"), join(dir, "levels"), { recursive: true });
|
|
}
|
|
for (const cards of cardDirs(plan.num)) cpSync(cards, join(dir, "cards"), { recursive: true });
|
|
return dir;
|
|
}
|
|
|
|
if (isMain(import.meta.url)) {
|
|
const all = moduleDirs();
|
|
const upTo = process.argv[2] ? Number(process.argv[2]) : Math.max(0, ...all.map((m) => Number(m.num)));
|
|
const plans = planVerification(all, upTo);
|
|
if (!plans.length) console.log("verify-references: no modules with reference/ up to " + upTo);
|
|
let failed = false;
|
|
for (const p of plans) {
|
|
for (const lang of p.langs) {
|
|
const dir = stage(p, lang);
|
|
const r = spawnSync(commandFor(lang), { cwd: dir, shell: true, encoding: "utf8" });
|
|
const ok = r.status === 0;
|
|
failed ||= !ok;
|
|
console.log(`module ${p.num}-${p.slug} [${lang}]: ${ok ? "ok" : "FAIL"}`);
|
|
if (!ok) console.log((r.stdout || "") + (r.stderr || ""));
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
if (failed) process.exit(1);
|
|
}
|