#!/usr/bin/env node // Anti-regression gate: asserts a minimum test-file-per-source-file density // per workspace. Catches "route added without test" without requiring full // coverage tooling. Floors are deliberate snapshots of the current ratio; // they must only go down via deliberate commits (= more tests added). import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const repoRoot = path.resolve(fileURLToPath(import.meta.url), "..", ".."); const TARGETS = [ { name: "apps/api", root: "apps/api/src", minRatio: 0.18 }, { name: "apps/worker", root: "apps/worker/src", minRatio: 0.18 }, { name: "apps/web app routes", root: "apps/web/app/routes", minRatio: 0.05 }, { name: "apps/web app modules", root: "apps/web/app/modules", minRatio: 0.04 }, { name: "apps/web app utils", root: "apps/web/app/utils", minRatio: 0.10 }, { name: "packages/foundation", root: "packages/foundation/src", minRatio: 0.10 }, { name: "packages/db", root: "packages/db/src", minRatio: 0.05 }, { name: "packages/mail", root: "packages/mail/src", minRatio: 0.30 }, { name: "packages/queue", root: "packages/queue/src", minRatio: 0.10 }, { name: "packages/i18n", root: "packages/i18n/src", minRatio: 0.30 }, { name: "packages/brand", root: "packages/brand/src", minRatio: 0.50 }, { name: "packages/config", root: "packages/config/src", minRatio: 0.50 }, ]; const ignoredDirs = new Set(["node_modules", "dist", "build", "coverage", ".react-router", "generated", "__generated__"]); const sourcePattern = /\.(?:ts|tsx)$/; const testPattern = /\.(?:test|spec)\.(?:ts|tsx)$/; const declarationPattern = /\.d\.ts$/; export function walk(dir, acc = { source: 0, test: 0 }) { if (!fs.existsSync(dir)) return acc; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { if (ignoredDirs.has(entry.name) || entry.name.startsWith(".")) continue; const full = path.join(dir, entry.name); if (entry.isDirectory()) { walk(full, acc); continue; } if (!entry.isFile()) continue; if (declarationPattern.test(entry.name)) continue; if (testPattern.test(entry.name)) { acc.test += 1; } else if (sourcePattern.test(entry.name)) { acc.source += 1; } } return acc; } export function evaluateTargets(targets, rootDir = repoRoot) { const failures = []; const passes = []; for (const target of targets) { const counts = walk(path.join(rootDir, target.root)); if (counts.source === 0) { passes.push(`SKIP ${target.name} no source files`); continue; } const ratio = counts.test / counts.source; const line = `${target.name} source=${counts.source} tests=${counts.test} ratio=${ratio.toFixed(3)} min=${target.minRatio.toFixed(3)}`; if (ratio >= target.minRatio) { passes.push(`PASS ${line}`); } else { failures.push(`FAIL ${line} (drift=${(ratio - target.minRatio).toFixed(3)})`); } } return { failures, passes }; } export function formatSummary(result) { return `Summary: ${result.passes.length} passed, 0 observed, ${result.failures.length} failed`; } function main() { const result = evaluateTargets(TARGETS); for (const line of result.passes) console.log(line); for (const line of result.failures) console.error(line); console.log(formatSummary(result)); if (result.failures.length > 0) process.exit(1); } if (import.meta.url === `file://${process.argv[1]}`) { main(); }