Files
fusion/scripts/check-routes-modular.mjs
gsxdsm e0e395a715 FN-8365: enforce dashboard route registrar mount order
Keep dashboard API registration modular while preserving Express route precedence.

- Route all top-level dashboard registrars through a runtime-checked canonical mount sequence
- Add mount-order and inline-route-ratchet coverage with CI enforcement
- Document registrar ownership and mount-order conventions

Files changed:
.github/workflows/pr-checks.yml                    |   3 +
AGENTS.md                                          |   2 +
package.json                                       |   5 +-
packages/dashboard/src/routes.ts                   | 136 +++++-----
packages/dashboard/src/routes/README.md            | 276 ++++++++++-----------
packages/dashboard/src/routes/__tests__/create-api-routes-mount-order.test.ts |  66 +++++
packages/dashboard/src/routes/create-api-routes-mount-sequence.ts |  54 ++++
scripts/__tests__/check-routes-modular.test.mjs    |  28 +++
scripts/check-routes-modular.mjs                   |  65 +++++
scripts/lib/routes-modular-baseline.json           |   3 +
10 files changed, 433 insertions(+), 205 deletions(-)

Fusion-Task-Id: FN-8365

Fusion-Task-Lineage: 9c36a263-ed5e-4524-8ea5-71ed3f3e34d9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-19 15:53:07 -07:00

66 lines
3.3 KiB
JavaScript

#!/usr/bin/env node
/*
FNXC:RouteModularity 2026-07-19-12:00:
New dashboard endpoints live in packages/dashboard/src/routes registrars and
createApiRoutes is an orchestrator. Grandfathered inline registrations may shrink
but must not grow; registrar mount order is separately enforced at runtime by the
mount-sequence seam and its contract tests.
*/
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath, URL } from "node:url";
const ROUTES_PATH = fileURLToPath(new URL("../packages/dashboard/src/routes.ts", import.meta.url));
const BASELINE_PATH = fileURLToPath(new URL("./lib/routes-modular-baseline.json", import.meta.url));
const ROUTE_REGISTRATION = /\brouter\.(?:get|post|put|delete|patch|use|all)\s*\(/g;
/** Replace comments and string literals without counting illustrative route calls. */
export function stripCommentsAndStrings(source) {
let result = "";
let index = 0;
let state = "code";
while (index < source.length) {
const char = source[index];
const next = source[index + 1];
if (state === "code") {
if (char === "/" && next === "/") { state = "line-comment"; result += " "; index += 2; continue; }
if (char === "/" && next === "*") { state = "block-comment"; result += " "; index += 2; continue; }
if (char === "'" || char === '"' || char === "`") { state = char; result += " "; index += 1; continue; }
result += char; index += 1; continue;
}
if (state === "line-comment") { if (char === "\n") { state = "code"; result += "\n"; } else result += " "; index += 1; continue; }
if (state === "block-comment") { if (char === "*" && next === "/") { state = "code"; result += " "; index += 2; } else { result += char === "\n" ? "\n" : " "; index += 1; } continue; }
if (char === "\\") { result += " "; index += 2; continue; }
if (char === state) state = "code";
result += " "; index += 1;
}
return result;
}
export function countInlineRouteRegistrations(source) {
return (stripCommentsAndStrings(source).match(ROUTE_REGISTRATION) ?? []).length;
}
export function evaluate(count, baseline) {
return { count, baseline, passes: count <= baseline.inlineRouteRegistrations };
}
export function formatFailureMessage({ count, baseline }) {
return `[check-routes-modular] routes.ts has ${count} inline router registrations, above its pinned baseline of ${baseline.inlineRouteRegistrations}. Add new endpoints under packages/dashboard/src/routes/ registrars, not inline in routes.ts. Extract existing handlers to lower the ratchet; only use --update to record that decrease.`;
}
export function main(argv = process.argv.slice(2), { routesPath = ROUTES_PATH, baselinePath = BASELINE_PATH } = {}) {
const count = countInlineRouteRegistrations(readFileSync(routesPath, "utf8"));
if (argv.includes("--update")) {
writeFileSync(baselinePath, `${JSON.stringify({ inlineRouteRegistrations: count }, null, 2)}\n`);
console.error(`[check-routes-modular] baseline rewritten at ${count} inline route registrations.`);
return 0;
}
const baseline = JSON.parse(readFileSync(baselinePath, "utf8"));
const result = evaluate(count, baseline);
if (result.passes) return 0;
console.error(formatFailureMessage(result));
return 1;
}
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) process.exitCode = main();