12const CEILING = 10_000;345const TS_AREAS: [string, string[], number][] = [6 ["bucket", ["src/bucket/"], 900],7 ["mirror", ["src/mirror/"], 1100],8 ["describe", ["src/describe/"], 500],9 ["pages", ["src/pages/"], 2600],10 ["releases", ["src/releases/"], 900],11 ["tarball", ["src/tarball/"], 300],12 ["issues", ["src/issues/"], 600],13 ["admin", ["src/admin/"], 1100],14 ["import-kepr2", ["scripts/import-kepr2.ts"], 100],15 ["server", ["src/server/", "src/main.ts", "scripts/budget.ts"], 700],16];17const ASSET_AREAS: [string, string, number][] = [18 ["public/js", "public/js/**/*.js", 1200],19 ["public/css", "public/css/**/*.css", 2000],20];2122async function lineCount(path: string): Promise<number> {23 const text = await Bun.file(path).text();24 return text.split("\n").length - (text.endsWith("\n") ? 1 : 0);25}2627async function countFiles(pattern: string): Promise<Map<string, number>> {28 const counts = new Map<string, number>();29 for await (const path of new Bun.Glob(pattern).scan(".")) counts.set(path, await lineCount(path));30 return counts;31}3233const tsFiles = await countFiles("{src,scripts}/**/*.ts");34const totals = new Map(TS_AREAS.map(([name]) => [name, 0]));35const unassigned: string[] = [];36for (const [path, lines] of tsFiles) {37 if (path.endsWith(".test.ts")) continue;38 const area = TS_AREAS.find(([, prefixes]) => prefixes.some((prefix) => path.startsWith(prefix)));39 if (!area) unassigned.push(path);40 else totals.set(area[0], (totals.get(area[0]) ?? 0) + lines);41}4243const rows: [string, number, number][] = TS_AREAS.map(([name, , budget]) => [44 name,45 totals.get(name) ?? 0,46 budget,47]);48const total = rows.reduce((sum, [, lines]) => sum + lines, 0);49for (const [name, pattern, budget] of ASSET_AREAS) {50 const files = await countFiles(pattern);51 rows.push([name, [...files.values()].reduce((a, b) => a + b, 0), budget]);52}5354let failed = false;55for (const [name, lines, budget] of rows) {56 const over = lines > budget;57 failed ||= over;58 console.log(`${name.padEnd(14)} ${String(lines).padStart(6)} / ${budget}${over ? " OVER" : ""}`);59}60console.log(`${"typescript".padEnd(14)} ${String(total).padStart(6)} / ${CEILING}`);61if (total > CEILING) failed = true;62for (const path of unassigned) console.error(`no budget area for ${path}`);63if (failed || unassigned.length > 0) process.exit(1);