54 lines
4.0 KiB
JavaScript
54 lines
4.0 KiB
JavaScript
import { readdir, readFile, writeFile, mkdir } from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
const root = path.resolve(import.meta.dirname, "..");
|
|
const sourceRoots = ["main.cjs", "preload.cjs", "src/main", "src/renderer", "src/shared"];
|
|
const extensions = new Set([".js", ".cjs", ".mjs"]);
|
|
|
|
async function filesBelow(entry) {
|
|
const absolute = path.join(root, entry);
|
|
const stat = await import("node:fs/promises").then(({ stat }) => stat(absolute));
|
|
if (stat.isFile()) return [entry];
|
|
const result = [];
|
|
for (const child of await readdir(absolute, { withFileTypes: true })) {
|
|
const relative = path.join(entry, child.name);
|
|
if (child.isDirectory()) result.push(...await filesBelow(relative));
|
|
else if (extensions.has(path.extname(child.name))) result.push(relative);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function analyze(relative, source) {
|
|
const lines = source.split(/\r?\n/).length;
|
|
const branches = (source.match(/\b(?:if|else if|for|while|case|catch)\b|\?\?/g) || []).length;
|
|
const functions = (source.match(/\b(?:async\s+)?function\b|=>|\b(?:async\s+)?[A-Za-z_$][\w$]*\s*\([^)]*\)\s*\{/g) || []).length;
|
|
const ipcHandlers = (source.match(/\bregister\(\s*["']/g) || []).length;
|
|
const responsibilities = [
|
|
["inventory", /inventory|workload/i], ["deployment", /deploy|rollback|activation/i],
|
|
["git", /\bgit|repository/i], ["ipc", /ipc|register\(/i], ["renderer", /render|modal|document\./i],
|
|
["security", /key|credential|signature|checksum/i], ["updates", /update|release|artifact/i],
|
|
].filter(([, pattern]) => pattern.test(source)).map(([name]) => name);
|
|
return { file: relative.replaceAll("\\", "/"), lines, branches, functions, ipcHandlers, responsibilities, hotspotScore: branches + Math.max(0, responsibilities.length - 2) * 10 };
|
|
}
|
|
|
|
const files = (await Promise.all(sourceRoots.map(filesBelow))).flat();
|
|
const results = [];
|
|
for (const file of files) results.push(analyze(file, await readFile(path.join(root, file), "utf8")));
|
|
results.sort((a, b) => b.hotspotScore - a.hotspotScore || b.lines - a.lines);
|
|
const report = {
|
|
generatedAt: new Date().toISOString(),
|
|
thresholds: { preferredMaximumLines: 750, justificationRequiredLines: 1000 },
|
|
over750: results.filter((item) => item.lines > 750),
|
|
over1000: results.filter((item) => item.lines > 1000),
|
|
cyclomaticHotspots: results.filter((item) => item.branches >= 75).slice(0, 20),
|
|
mixedResponsibilityModules: results.filter((item) => item.responsibilities.length >= 4),
|
|
ipcHotspots: results.filter((item) => item.ipcHandlers >= 10),
|
|
};
|
|
const reportDir = path.join(root, "reports");
|
|
await mkdir(reportDir, { recursive: true });
|
|
await writeFile(path.join(reportDir, "architecture-audit.json"), `${JSON.stringify(report, null, 2)}\n`);
|
|
const table = (items) => items.length ? items.map((item) => `| \`${item.file}\` | ${item.lines} | ${item.branches} | ${item.functions} | ${item.ipcHandlers} | ${item.responsibilities.join(", ")} |`).join("\n") : "No findings.";
|
|
const markdown = `# ForgeFlow architecture audit\n\nGenerated ${report.generatedAt}. Complexity is a deterministic decision-point count used for hotspot ranking, not a claim of exact McCabe complexity.\n\n## Files above 750 lines\n\n| File | Lines | Decisions | Functions | IPC handlers | Responsibilities |\n|---|---:|---:|---:|---:|---|\n${table(report.over750)}\n\n## Files above 1,000 lines\n\n| File | Lines | Decisions | Functions | IPC handlers | Responsibilities |\n|---|---:|---:|---:|---:|---|\n${table(report.over1000)}\n\n## Cyclomatic hotspots\n\n| File | Lines | Decisions | Functions | IPC handlers | Responsibilities |\n|---|---:|---:|---:|---:|---|\n${table(report.cyclomaticHotspots)}\n\n## Interpretation\n\nFiles above 750 lines require decomposition. Files above 1,000 lines are release blockers unless a concrete technical exception is documented. Mixed responsibility and IPC hotspot lists are available in the JSON report.\n`;
|
|
await writeFile(path.join(reportDir, "architecture-audit.md"), markdown);
|
|
console.log(`Audited ${results.length} source files; ${report.over750.length} exceed 750 lines and ${report.over1000.length} exceed 1,000 lines.`);
|