panel v0.9.1 — open-source game server manager
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
// 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);
|
||||
Reference in New Issue
Block a user