Initial curated projectbrain source from 8a7b1e0651dbdab97d744cbd2e653ab3970ac6c5
This commit is contained in:
commit
5aa3e8d685
700 files changed
+132801
No files matched your search
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFile, readdir, stat } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { resolve, relative, extname, dirname } from "node:path";
|
||||
import process from "node:process";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const config = JSON.parse(await readFile(resolve(root, "config/architecture-budgets.json"), "utf8"));
|
||||
const normalized = (path) => relative(root, path).replaceAll("\\", "/");
|
||||
const sourceRoots = [resolve(root, "src"), resolve(root, "backend/src")];
|
||||
|
||||
const walk = async (directory) => {
|
||||
const entries = await readdir(directory, { withFileTypes: true });
|
||||
const nested = await Promise.all(entries.map((entry) => entry.isDirectory() ? walk(resolve(directory, entry.name)) : [resolve(directory, entry.name)]));
|
||||
return nested.flat();
|
||||
};
|
||||
|
||||
const failures = [];
|
||||
const sources = (await Promise.all(sourceRoots.map(walk))).flat().filter((path) => [".ts", ".tsx"].includes(extname(path)));
|
||||
const sourceSet = new Set(sources);
|
||||
const contentByPath = new Map();
|
||||
for (const path of sources) {
|
||||
const name = normalized(path);
|
||||
const content = await readFile(path, "utf8");
|
||||
contentByPath.set(path, content);
|
||||
const lines = content.split(/\r?\n/).length;
|
||||
const exception = config.temporaryFileExceptions[name];
|
||||
const maximum = exception?.maxLines ?? config.maximumNewSourceLines;
|
||||
if (lines > maximum) failures.push(`${name}: ${lines} lines exceeds ${maximum}${exception ? " temporary ceiling" : " general ceiling"}`);
|
||||
}
|
||||
|
||||
const resolveImport = (from, specifier) => {
|
||||
if (!specifier.startsWith(".") && !specifier.startsWith("@/")) return undefined;
|
||||
const base = specifier.startsWith("@/") ? resolve(root, "src", specifier.slice(2)) : resolve(dirname(from), specifier);
|
||||
return [base, `${base}.ts`, `${base}.tsx`, resolve(base, "index.ts"), resolve(base, "index.tsx")].find((candidate) => sourceSet.has(candidate));
|
||||
};
|
||||
const graph = new Map(sources.map((path) => {
|
||||
const imports = [...contentByPath.get(path).matchAll(/(?:from\s+|import\s*\(\s*|import\s+)["']([^"']+)["']/g)]
|
||||
.map((match) => resolveImport(path, match[1]))
|
||||
.filter(Boolean);
|
||||
return [path, [...new Set(imports)]];
|
||||
}));
|
||||
const visiting = new Set();
|
||||
const visited = new Set();
|
||||
const stack = [];
|
||||
const cycles = new Set();
|
||||
const visit = (path) => {
|
||||
if (visiting.has(path)) {
|
||||
const start = stack.indexOf(path);
|
||||
cycles.add([...stack.slice(start), path].map(normalized).join(" -> "));
|
||||
return;
|
||||
}
|
||||
if (visited.has(path)) return;
|
||||
visiting.add(path); stack.push(path);
|
||||
for (const dependency of graph.get(path) ?? []) visit(dependency);
|
||||
stack.pop(); visiting.delete(path); visited.add(path);
|
||||
};
|
||||
for (const path of sources) visit(path);
|
||||
for (const cycle of cycles) failures.push(`circular import: ${cycle}`);
|
||||
|
||||
const hashes = new Map();
|
||||
for (const [path, content] of contentByPath) {
|
||||
if (content.split(/\r?\n/).length < 20) continue;
|
||||
const hash = createHash("sha256").update(content.replaceAll(/\s+/g, " ").trim()).digest("hex");
|
||||
const duplicate = hashes.get(hash);
|
||||
if (duplicate) failures.push(`exact duplicate modules: ${normalized(duplicate)} and ${normalized(path)}`);
|
||||
else hashes.set(hash, path);
|
||||
}
|
||||
|
||||
const forbiddenRoots = [resolve(root, "src/shared/components/ui"), resolve(root, "src/shared/components/layout/AppLayout.tsx")];
|
||||
for (const path of forbiddenRoots) {
|
||||
try { await stat(path); failures.push(`${normalized(path)}: deprecated duplicate UI boundary still exists`); } catch (error) { if (error.code !== "ENOENT") throw error; }
|
||||
}
|
||||
|
||||
const distAssets = resolve(root, "dist/assets");
|
||||
try {
|
||||
const assets = (await readdir(distAssets)).filter((name) => name.endsWith(".js"));
|
||||
for (const asset of assets) {
|
||||
const bytes = (await stat(resolve(distAssets, asset))).size;
|
||||
if (/^index-/.test(asset) && bytes > config.bundle.maxEntryBytes) failures.push(`${asset}: ${bytes} bytes exceeds entry budget ${config.bundle.maxEntryBytes}`);
|
||||
if (!/^vendor-|^index-|^rolldown-runtime-/.test(asset)) {
|
||||
const exception = Object.entries(config.bundle.routeExceptions ?? {}).find(([prefix]) => asset.startsWith(prefix))?.[1];
|
||||
const maximum = exception?.maxBytes ?? config.bundle.maxRouteBytes;
|
||||
if (bytes > maximum) failures.push(`${asset}: ${bytes} bytes exceeds route budget ${maximum}${exception ? " temporary ceiling" : ""}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "ENOENT") throw error;
|
||||
failures.push("dist/assets is missing; run pnpm build before the architecture audit");
|
||||
}
|
||||
|
||||
if (failures.length) {
|
||||
console.error("Architecture budget failed:\n- " + failures.join("\n- "));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Architecture budget passed for ${sources.length} source files; ${Object.keys(config.temporaryFileExceptions).length} frozen exceptions, no import cycles/exact duplicate modules and production bundle ceilings verified.`);
|
||||
Reference in new issue
Block a user