658bda1d24
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
107 lines
5.0 KiB
JavaScript
107 lines
5.0 KiB
JavaScript
// WI-07 verification: diff the dashboard's old inline per-module maps
|
|
// (READY_PATTERNS / moduleAppearance / moduleBrowseableRoots /
|
|
// guessPortsForModule — kept in rcc.js as fallbacks) against the values
|
|
// the manifests now serve via GET /api/modules (as loaded by
|
|
// tools/module-meta-dump, which uses the controller's own LoadDir).
|
|
//
|
|
// ready_pattern + appearance were MOVED (must be value-identical → any
|
|
// mismatch exits 1). ports + browseable_roots were already declared in
|
|
// module.yaml; drift there is reported informationally (manifest wins).
|
|
//
|
|
// Run from repo root: node controller/.test/wi07-diff-maps.mjs
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { execFileSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
const rcc = fs.readFileSync(path.join(root, "controller/cmd/controller/static/rcc.js"), "utf8");
|
|
|
|
// Extract a brace-balanced object literal following `marker`.
|
|
function extractObject(marker, from = 0) {
|
|
const i = rcc.indexOf(marker, from);
|
|
if (i < 0) throw new Error("marker not found: " + marker);
|
|
const start = rcc.indexOf("{", i);
|
|
let depth = 0, j = start;
|
|
for (; j < rcc.length; j++) {
|
|
const c = rcc[j];
|
|
if (c === "{") depth++;
|
|
else if (c === "}") { depth--; if (depth === 0) break; }
|
|
}
|
|
return eval("(" + rcc.slice(start, j + 1) + ")");
|
|
}
|
|
|
|
const READY = extractObject("const READY_PATTERNS = ");
|
|
const APPEAR = extractObject("const moduleAppearance = ");
|
|
const ROOTS = extractObject("const moduleBrowseableRoots = ");
|
|
// ports map lives inside guessPortsForModule()
|
|
const fnIdx = rcc.indexOf("function guessPortsForModule(id)");
|
|
if (fnIdx < 0) throw new Error("guessPortsForModule not found");
|
|
const PORTS = extractObject("const m = {", fnIdx);
|
|
|
|
// Manifest side.
|
|
const goExe = process.env.GO_EXE || "C:\\Program Files\\Go\\bin\\go.exe";
|
|
const manifest = JSON.parse(execFileSync(goExe, ["run", "./tools/module-meta-dump"], { cwd: root, encoding: "utf8" }));
|
|
|
|
// Same compile rule the dashboard uses for manifest ready_pattern strings.
|
|
function compileReady(s) {
|
|
if (!s) return null;
|
|
const m = /^\/([\s\S]+)\/([a-z]*)$/.exec(s);
|
|
return m ? new RegExp(m[1], m[2]) : new RegExp(s);
|
|
}
|
|
|
|
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
let hardFail = 0;
|
|
const ids = [...new Set([...Object.keys(manifest), ...Object.keys(READY), ...Object.keys(APPEAR), ...Object.keys(ROOTS), ...Object.keys(PORTS)])].sort();
|
|
|
|
for (const id of ids) {
|
|
const man = manifest[id];
|
|
const lines = [];
|
|
|
|
// ready_pattern — moved: must be identical where both sides have it.
|
|
const oldRe = READY[id];
|
|
const newRe = man && compileReady(man.ready_pattern);
|
|
if (oldRe && !man) lines.push(" ready: JS-only (no manifest dir) — fallback covers it");
|
|
else if (oldRe && !newRe) { lines.push(" ready: MISSING in manifest"); hardFail++; }
|
|
else if (oldRe && newRe && (oldRe.source !== newRe.source || oldRe.flags !== newRe.flags)) {
|
|
lines.push(` ready: MISMATCH\n old ${oldRe}\n new ${newRe}`); hardFail++;
|
|
}
|
|
|
|
// appearance — moved: compare emoji/grad/art/steam.
|
|
const oa = APPEAR[id], na = man && man.appearance;
|
|
if (oa && !man) lines.push(" appearance: JS-only (no manifest dir) — fallback covers it");
|
|
else if (oa && !na) { lines.push(" appearance: MISSING in manifest"); hardFail++; }
|
|
else if (oa && na) {
|
|
for (const k of ["emoji", "grad", "art", "steam"]) {
|
|
if ((oa[k] || "") !== (na[k] || "")) { lines.push(` appearance.${k}: MISMATCH old=${JSON.stringify(oa[k] || "")} new=${JSON.stringify(na[k] || "")}`); hardFail++; }
|
|
}
|
|
}
|
|
|
|
// browseable_roots — pre-existing in manifests; report drift.
|
|
const orr = ROOTS[id], nrr = man && man.browseable_roots;
|
|
if (orr && man && !eq(orr, nrr)) lines.push(` roots drift (manifest wins):\n old ${JSON.stringify(orr)}\n new ${JSON.stringify(nrr)}`);
|
|
if (orr && !man) lines.push(" roots: JS-only (no manifest dir) — fallback covers it");
|
|
|
|
// ports — pre-existing in manifests; compare ignoring the additive env field.
|
|
const op = PORTS[id];
|
|
const np = man && man.ports && man.ports.map(p => {
|
|
const o = { name: p.name, proto: p.proto, default: p.default };
|
|
if (p.required) o.required = true;
|
|
if (p.internal) o.internal = true;
|
|
return o;
|
|
});
|
|
const opNorm = op && op.map(p => {
|
|
const o = { name: p.name, proto: p.proto, default: p.default };
|
|
if (p.required) o.required = true;
|
|
if (p.internal) o.internal = true;
|
|
return o;
|
|
});
|
|
if (op && man && !eq(opNorm, np)) lines.push(` ports drift (manifest wins):\n old ${JSON.stringify(opNorm)}\n new ${JSON.stringify(np)}`);
|
|
if (op && !man) lines.push(" ports: JS-only (no manifest dir) — fallback covers it");
|
|
|
|
if (lines.length) console.log(id + "\n" + lines.join("\n"));
|
|
else console.log(id + "\n OK (identical)");
|
|
}
|
|
console.log(hardFail ? `\nFAIL: ${hardFail} moved-field mismatch(es)` : "\nPASS: all moved fields (ready_pattern, appearance) value-identical");
|
|
process.exit(hardFail ? 1 : 0);
|