File size: 5,423 Bytes
d4f7e1d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | #!/usr/bin/env node
/**
* Entry points are cost contracts.
*
* A package's `exports` map is the only place that says which modules are public, and one stray
* `export *` can silently make a narrow entry drag an entire barrel: importing a 1-file pure
* function through a barrel costs ~37 MB of evaluated module graph, and nothing fails until someone
* measures a process. This walks the value-import graph of every declared entry point and enforces a
* budget per entry, so that regression fails at commit time instead.
*
* Only value imports count. `import type` / `export type` are erased before Node sees them.
*/
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { dirname, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
/** Workspace package name -> its source root, so cross-package imports are followed. */
const WORKSPACE = {
"@earendil-works/chord": "packages/chord/src",
"@earendil-works/pi-ai": "packages/ai/src",
"@earendil-works/pi-agent-core": "packages/agent/src",
"@earendil-works/pi-telemetry": "packages/telemetry/src",
"@earendil-works/pi-tui": "packages/tui/src",
};
/**
* Budgets are deliberate. `.` and `./node` are batteries-included entries and stay unbounded; every
* narrow entry states the graph it is allowed to reach.
*/
const BUDGETS = {
"packages/ai": {
"./utils/*": { maxFiles: 3, forbid: ["providers/", "api/", "index.ts"] },
},
"packages/agent": {
"./harness/runtime/reducer": { maxFiles: 1 },
"./harness/context": { maxFiles: 6, forbid: ["harness/runtime/", "harness/execution/", "packages/ai/"] },
"./harness/env/nodejs": { maxFiles: 5, forbid: ["packages/ai/", "harness/runtime/"] },
"./harness/session": { maxFiles: 25, forbid: ["harness/runtime/", "harness/execution/", "packages/ai/src/index.ts"] },
},
};
const SPEC = /(?:^|\n)\s*(?:import|export)\s+(?!type\s)([^;]*?\sfrom\s*)?["']([^"']+)["']/g;
function resolveSpec(spec, fromFile) {
if (spec.startsWith("node:")) return null;
if (spec.startsWith(".")) {
const base = resolve(dirname(fromFile), spec);
for (const candidate of [base, `${base}.ts`, `${base}/index.ts`]) {
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
}
return null;
}
for (const [name, src] of Object.entries(WORKSPACE)) {
if (spec === name) return resolve(ROOT, src, "index.ts");
if (!spec.startsWith(`${name}/`)) continue;
const tail = spec.slice(name.length + 1);
for (const candidate of [`${tail}.ts`, `${tail}/index.ts`, tail]) {
const file = resolve(ROOT, src, candidate);
if (existsSync(file) && statSync(file).isFile()) return file;
}
}
return null; // external dependency: not part of the workspace graph
}
function walk(entryFile) {
const seen = new Set();
const queue = [entryFile];
while (queue.length > 0) {
const file = queue.pop();
if (seen.has(file) || file.endsWith(".json")) continue;
seen.add(file);
for (const match of readFileSync(file, "utf8").matchAll(SPEC)) {
const target = resolveSpec(match[2], file);
if (target) queue.push(target);
}
}
return seen;
}
/** `./dist/harness/context.js` in the exports map is `src/harness/context.ts` on disk. */
function sourceFor(pkgDir, distPath) {
const rel = distPath.replace(/^\.\/dist\//, "").replace(/\.js$/, ".ts");
const file = resolve(ROOT, pkgDir, "src", rel);
return existsSync(file) ? file : undefined;
}
function expand(pkgDir, entry, target) {
if (!entry.includes("*")) return [[entry, target]];
const dir = resolve(ROOT, pkgDir, "src", dirname(target.replace(/^\.\/dist\//, "")));
if (!existsSync(dir)) return [];
return readdirSync(dir)
.filter((name) => name.endsWith(".ts"))
.map((name) => [entry.replace("*", name.replace(/\.ts$/, "")), target.replace("*", name.replace(/\.ts$/, ""))]);
}
let failures = 0;
for (const [pkgDir, budgets] of Object.entries(BUDGETS)) {
const manifest = JSON.parse(readFileSync(resolve(ROOT, pkgDir, "package.json"), "utf8"));
for (const [entry, budget] of Object.entries(budgets)) {
const declared = manifest.exports?.[entry];
if (!declared) {
console.error(`${pkgDir} declares no export "${entry}" but a budget exists for it`);
failures += 1;
continue;
}
const target = typeof declared === "string" ? declared : declared.import;
for (const [name, distPath] of expand(pkgDir, entry, target)) {
const source = sourceFor(pkgDir, distPath);
if (!source) {
console.error(`${pkgDir} export "${name}" points at ${distPath}, which has no source file`);
failures += 1;
continue;
}
const graph = [...walk(source)].map((file) => relative(ROOT, file));
if (graph.length > budget.maxFiles) {
console.error(
`${pkgDir} export "${name}" reaches ${graph.length} files, budget ${budget.maxFiles}\n` +
graph.map((file) => ` ${file}`).join("\n"),
);
failures += 1;
}
for (const pattern of budget.forbid ?? []) {
const hit = graph.filter((file) => file.includes(pattern));
if (hit.length > 0) {
console.error(`${pkgDir} export "${name}" must not reach ${pattern}:\n${hit.map((f) => ` ${f}`).join("\n")}`);
failures += 1;
}
}
}
}
}
if (failures > 0) {
console.error(`\n${failures} entry-point budget violation(s).`);
process.exit(1);
}
console.log("Entry point graphs are within budget.");
|