singularity-forge/scripts/check-circular-deps.mjs
Mikael Hugo ea360f6ad2 feat: add circular dep detection tool + fix duplicate milestone dirs + fix metrics NULL
- Add scripts/check-circular-deps.mjs using madge; npm run check:circular
  and check:circular:ext scan src/ and the SF extension respectively
- findMilestoneIds() is now DB-first: reads from milestones table when DB is
  open so stale/duplicate filesystem dirs (M001/ and M001-6377a4/) are never
  returned; falls back to fs scan only during early bootstrap
- milestone-id-utils.js was a stale duplicate; replaced with re-exports from
  canonical milestone-ids.js
- metrics-central.js: guard null/undefined counter/gauge/histogram values
  with ?? 0 to prevent NOT NULL constraint failure on metrics.value

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-10 01:56:08 +02:00

62 lines
1.7 KiB
JavaScript

#!/usr/bin/env node
/**
* check-circular-deps.mjs — detect circular imports across the SF codebase.
*
* Usage:
* npm run check:circular # scan src/ + packages/
* npm run check:circular -- --ext # scan extension source only
* node scripts/check-circular-deps.mjs [--ext] [--json]
*
* Exit 0 = no cycles found. Exit 1 = cycles detected (or scan error).
*/
import madge from "madge";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "..");
const args = process.argv.slice(2);
const extOnly = args.includes("--ext");
const jsonOut = args.includes("--json");
const entries = extOnly
? [resolve(root, "src/resources/extensions/sf")]
: [resolve(root, "src"), resolve(root, "packages")];
console.error(`Scanning: ${entries.map((e) => e.replace(root + "/", "")).join(", ")}`);
let result;
try {
result = await madge(entries, {
fileExtensions: ["js", "mjs", "ts"],
excludeRegExp: [
/node_modules/,
/\.test\.(js|mjs|ts)$/,
/\/dist\//,
/\/tests?\//,
],
detectiveOptions: {
es6: { mixedImports: true },
},
});
} catch (err) {
console.error(`Scan failed: ${err.message}`);
process.exit(1);
}
const cycles = result.circular();
if (jsonOut) {
console.log(JSON.stringify({ cycles, count: cycles.length }, null, 2));
} else if (cycles.length === 0) {
console.log("✅ No circular dependencies found.");
} else {
console.log(`${cycles.length} circular dependency chain(s) found:\n`);
for (const [i, chain] of cycles.entries()) {
console.log(` ${i + 1}. ${chain.join(" → ")}${chain[0]}`);
}
}
process.exit(cycles.length > 0 ? 1 : 0);