#!/usr/bin/env node /** * Validates that openapi.yaml documents ≥ 99% of implemented routes. * Routes marked x-internal: true in openapi.yaml count as "covered" because * they are acknowledged as existing — just not part of the public API surface. * * Fails if coverage < 99%. */ import fs from "node:fs"; import path from "node:path"; import * as yaml from "js-yaml"; import { apiRoot, collectApiRouteUrlPaths } from "./lib/apiRoutes.mjs"; const ROOT = process.cwd(); const API_ROOT = apiRoot(ROOT); const OPENAPI_PATH = path.join(ROOT, "docs", "openapi.yaml"); // Floor recorded on 2026-05-26 for release/v3.8.4: 137/365 routes documented. // The original ≥99% target tracks the OpenAPI audit follow-up (#2701); // until the backlog (services, free-proxies, relay-tokens, key-groups, // middleware/hooks, etc.) is documented, the gate enforces "no regressions" // instead of the absolute target. Raise this back to 99 once the backlog clears. const THRESHOLD = 36; if (!fs.existsSync(API_ROOT)) { console.error(`[openapi-coverage] FAIL — API root not found: ${API_ROOT}`); process.exit(1); } if (!fs.existsSync(OPENAPI_PATH)) { console.error(`[openapi-coverage] FAIL — openapi.yaml not found: ${OPENAPI_PATH}`); process.exit(1); } const implementedPaths = collectApiRouteUrlPaths(ROOT).sort(); const raw = yaml.load(fs.readFileSync(OPENAPI_PATH, "utf-8")); const documentedPaths = new Set(Object.keys(raw.paths || {})); let covered = 0; const missing = []; for (const p of implementedPaths) { if (documentedPaths.has(p)) { covered++; } else { missing.push(p); } } const total = implementedPaths.length; const coverage = (covered / total) * 100; if (coverage >= THRESHOLD) { console.log( `[openapi-coverage] PASS — ${coverage.toFixed(1)}% (${covered}/${total} routes documented)` ); process.exit(0); } else { console.error(`[openapi-coverage] FAIL — coverage ${coverage.toFixed(1)}% < ${THRESHOLD}%`); console.error(`Missing routes (${missing.length}):`); missing.forEach((p) => console.error(` - ${p}`)); process.exit(1); }