#!/usr/bin/env node /* eslint-disable no-console */ import { createHash } from "node:crypto"; import { mkdir, readdir, readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { markFailedBaselineGates } from "./baseline-gate-exit.mjs"; import { spawn } from "node:child_process"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(__dirname, ".."); export const reportPath = path.join(repoRoot, ".tc/baselines/billing-boundary/baseline-health.md"); const billingInventoryPatterns = [ { id: "plan-equality", pattern: /\bplan\s*===/g }, { id: "trial-days", pattern: /\btrialDays\b/g }, { id: "trial-free-autosubscribe", pattern: /\bautosubscribeToTrialOrFreePlan\b/g }, { id: "stripe-price-product-lookup", pattern: /\b(?:stripePriceId|stripeProductId|getSubscriptionPriceByStripeId|getSubscriptionProductByStripeId|price\.stripeId|product\.stripeId|stripe\.prices|stripe\.products)\b/g }, { id: "direct-subscription-model-access", pattern: /\bdb\.(?:subscriptionProduct|subscriptionPrice|subscriptionFeature|subscriptionUsageBasedPrice|subscriptionUsageBasedTier|tenantSubscription|tenantSubscriptionProduct|tenantSubscriptionProductPrice|tenantSubscriptionUsageRecord|checkoutSessionStatus)\b/g }, ]; export const gates = [ { id: "typecheck", command: "pnpm", args: ["run", "typecheck"] }, { id: "lint", command: "pnpm", args: ["run", "lint"] }, { id: "vitest-billing-boundary", command: "pnpm", args: [ "exec", "vitest", "run", "packages/db/src/subscriptions.server.test.ts", "packages/db/src/subscriptions-boundary.test.ts", "apps/web/app/utils/services/.server/subscriptionService.test.ts", ], }, { id: "billing-inventory", type: "billing-inventory" }, ]; function shellCommand(gate) { if (gate.type === "billing-inventory") return "read-only billing inventory"; return [gate.command, ...gate.args].join(" "); } function outputFingerprint(output) { const normalized = output .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean) .slice(-40) .join("\n"); const hash = createHash("sha256").update(output).digest("hex").slice(0, 12); return { hash, excerpt: normalized.slice(0, 1200) || "(no output)", }; } function parseNumber(value) { const parsed = Number.parseInt(value, 10); return Number.isFinite(parsed) ? parsed : 0; } function countTypeScriptFailures(output) { return (output.match(/error TS\d+/g) ?? []).length; } function countEslintFailures(output) { const problemsMatch = output.match(/✖\s+(\d+)\s+problems?\s+\((\d+)\s+errors?/i); if (problemsMatch) return parseNumber(problemsMatch[2]); const compactMatch = output.match(/(\d+)\s+errors?/i); if (compactMatch) return parseNumber(compactMatch[1]); return 0; } function countVitestFailures(output) { const failedTestMatch = output.match(/Tests\s+(\d+)\s+failed/i); if (failedTestMatch) return parseNumber(failedTestMatch[1]); const failedFilesMatch = output.match(/Test Files\s+(\d+)\s+failed/i); if (failedFilesMatch) return parseNumber(failedFilesMatch[1]); return 0; } function conservativeFailureCount(count, output) { return count > 0 ? count : output.trim() ? 1 : 0; } export function failureCount(gateId, stdout, stderr, exitCode = 1) { if (exitCode === 0) return 0; const output = `${stdout}\n${stderr}`; if (gateId === "typecheck") return conservativeFailureCount(countTypeScriptFailures(output), output); if (gateId === "lint") return conservativeFailureCount(countEslintFailures(output), output); if (gateId === "vitest-billing-boundary") return conservativeFailureCount(countVitestFailures(output), output); if (gateId === "billing-inventory") return 0; return output.trim() ? 1 : 0; } function runShellGate(gate) { return new Promise((resolve) => { const startedAt = Date.now(); const child = spawn(gate.command, gate.args, { cwd: repoRoot, env: { ...process.env, CI: "1" }, shell: false, }); let stdout = ""; let stderr = ""; child.stdout.on("data", (chunk) => { stdout += chunk; }); child.stderr.on("data", (chunk) => { stderr += chunk; }); child.on("error", (error) => { stderr += `\n${error.message}`; }); child.on("close", (code) => { const output = `${stdout}\n${stderr}`; resolve({ id: gate.id, command: shellCommand(gate), exitCode: code ?? 1, durationMs: Date.now() - startedAt, failureCount: failureCount(gate.id, stdout, stderr, code ?? 1), fingerprint: outputFingerprint(output), }); }); }); } async function collectFiles(rootRelativePath) { const absoluteRoot = path.join(repoRoot, rootRelativePath); const files = []; async function walk(directory) { const entries = await readdir(directory, { withFileTypes: true }); for (const entry of entries) { if (entry.name === "node_modules" || entry.name === "build" || entry.name === "dist" || entry.name === "coverage") continue; const absolutePath = path.join(directory, entry.name); if (entry.isDirectory()) { await walk(absolutePath); continue; } if (!entry.isFile() || !/\.(?:ts|tsx|js|jsx|mjs|cjs|prisma)$/.test(entry.name)) continue; files.push(absolutePath); } } await walk(absoluteRoot); return files; } async function runBillingInventoryGate(gate) { const startedAt = Date.now(); const scanRoots = ["apps/web/app", "packages/db/src"]; const files = [ ...(await collectFiles(scanRoots[0])), ...(await collectFiles(scanRoots[1])), path.join(repoRoot, "prisma/schema/subscriptions.prisma"), ]; const counts = new Map(billingInventoryPatterns.map(({ id }) => [id, 0])); const matches = []; for (const file of files) { const relativeFile = path.relative(repoRoot, file).split(path.sep).join("/"); const source = await readFile(file, "utf8"); const lines = source.split(/\r?\n/); lines.forEach((line, index) => { for (const { id, pattern } of billingInventoryPatterns) { pattern.lastIndex = 0; const found = line.match(pattern); if (!found) continue; counts.set(id, (counts.get(id) ?? 0) + found.length); matches.push(`${relativeFile}:${index + 1}: [${id}] ${line.trim()}`); } }); } const outputLines = [ `Billing inventory scanned roots: ${scanRoots.join(", ")}, prisma/schema/subscriptions.prisma`, ...Array.from(counts.entries()).map(([id, count]) => `${id}: ${count}`), `total matches: ${matches.length}`, ...matches.slice(0, 240), ]; if (matches.length > 240) outputLines.push(`... ${matches.length - 240} additional matches omitted`); const output = outputLines.join("\n"); return { id: gate.id, command: shellCommand(gate), exitCode: 0, durationMs: Date.now() - startedAt, failureCount: 0, fingerprint: outputFingerprint(output), }; } async function runGate(gate) { if (gate.type === "billing-inventory") return runBillingInventoryGate(gate); return runShellGate(gate); } export function renderReport(results) { const lines = [ "# Billing Boundary Baseline Health", "", `Generated: ${new Date().toISOString()}`, "", "This report captures non-E2E baseline gates for Billing Boundary baseline. The runner exits 0 when every gate was executed and this report was written, even if individual gates fail.", "The billing inventory gate is read-only and fingerprints known plan, trial, Stripe lookup, and direct subscription model access hot spots without failing on legacy findings.", "", ]; for (const result of results) { lines.push( `## ${result.id}`, "", `- Command: \`${result.command}\``, `- Exitcode: ${result.exitCode}`, `- Failure Count: ${result.failureCount}`, `- Duration: ${result.durationMs}ms`, `- Output Fingerprint: ${result.fingerprint.hash}`, "", "```text", result.fingerprint.excerpt, "```", "", ); } return `${lines.join("\n")}\n`; } async function writeFailureReport(error) { await mkdir(path.dirname(reportPath), { recursive: true }); const output = error?.stack ?? String(error); const result = { id: "runner-error", command: "node scripts/verify-billing-boundary-baseline.mjs", exitCode: 1, durationMs: 0, failureCount: 1, fingerprint: outputFingerprint(output), }; await writeFile(reportPath, renderReport([result]), "utf8"); } async function main() { const results = []; for (const gate of gates) { console.log(`Running ${gate.id}: ${shellCommand(gate)}`); results.push(await runGate(gate)); } await mkdir(path.dirname(reportPath), { recursive: true }); await writeFile(reportPath, renderReport(results), "utf8"); console.log(`Wrote ${path.relative(repoRoot, reportPath)}`); markFailedBaselineGates(results); } if (process.argv[1] === fileURLToPath(import.meta.url)) { main().catch(async (error) => { console.error(error); try { await writeFailureReport(error); } finally { process.exit(1); } }); }