#!/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 /cards: course/cards // 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 /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, fail } 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 (import.meta.main) { 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); }