Move business logic out of App Router into module packages, add boundary validation scripts, and keep all routes as thin re-exports without changing URLs or API behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
76 lines
2.2 KiB
JavaScript
76 lines
2.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Detect circular imports within src/modules/accounting. */
|
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
import { join, relative, sep, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "..");
|
|
const MOD = join(ROOT, "src", "modules", "accounting");
|
|
const EXT = new Set([".ts", ".tsx"]);
|
|
const importRe = /from\s+["'](@\/src\/modules\/accounting\/[^"']+)["']/g;
|
|
|
|
function walk(dir, files = []) {
|
|
for (const entry of readdirSync(dir)) {
|
|
const full = join(dir, entry);
|
|
if (statSync(full).isDirectory()) walk(full, files);
|
|
else if (EXT.has(entry.slice(entry.lastIndexOf(".")))) files.push(full);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
/** @type {Map<string, string[]>} */
|
|
const graph = new Map();
|
|
|
|
for (const file of walk(MOD)) {
|
|
const rel = relative(MOD, file).split(sep).join("/").replace(/\.tsx?$/, "");
|
|
const content = readFileSync(file, "utf8");
|
|
const deps = [];
|
|
let m;
|
|
importRe.lastIndex = 0;
|
|
while ((m = importRe.exec(content)) !== null) {
|
|
const p = m[1].replace("@/src/modules/accounting/", "");
|
|
deps.push(p);
|
|
}
|
|
graph.set(rel, deps);
|
|
}
|
|
|
|
function findCycles() {
|
|
/** @type {string[][]} */
|
|
const cycles = [];
|
|
const visiting = new Set();
|
|
const visited = new Set();
|
|
/** @type {string[]} */
|
|
const stack = [];
|
|
|
|
function dfs(node) {
|
|
if (visiting.has(node)) {
|
|
const idx = stack.indexOf(node);
|
|
cycles.push(stack.slice(idx).concat(node));
|
|
return;
|
|
}
|
|
if (visited.has(node)) return;
|
|
visiting.add(node);
|
|
stack.push(node);
|
|
for (const dep of graph.get(node) ?? []) {
|
|
const key = dep.replace(/\/index$/, "");
|
|
if (graph.has(key)) dfs(key);
|
|
}
|
|
stack.pop();
|
|
visiting.delete(node);
|
|
visited.add(node);
|
|
}
|
|
|
|
for (const node of graph.keys()) dfs(node);
|
|
return cycles;
|
|
}
|
|
|
|
const cycles = findCycles();
|
|
console.log("Circular Dependency Check (accounting module)\n");
|
|
if (cycles.length) {
|
|
console.error(`❌ ${cycles.length} cycle(s):`);
|
|
for (const c of cycles) console.error(` ${c.join(" → ")}`);
|
|
process.exit(1);
|
|
}
|
|
console.log(`✅ No cycles in ${graph.size} accounting module files`);
|
|
process.exit(0);
|