feat(api-docs): Redoc-rendered /api/docs + consolidate OpenAPI spec to docs/openapi.yaml (#4781)

Redoc /api/docs + OpenAPI spec consolidated to docs/openapi.yaml (canonical 201-path complete spec; old path → legacy fallback). All refs/gates/tests/CI updated. Integrated into release/v3.8.35.
This commit is contained in:
KooshaPari
2026-06-23 06:29:40 -07:00
committed by GitHub
parent 34a4645646
commit 463565c71a
94 changed files with 6923 additions and 110 deletions

View File

@@ -34,7 +34,7 @@ export const APP_STAGING_REMOVAL_PATHS: string[] = [
export const APP_STAGING_ALLOWED_EXACT_PATHS: string[] = [
".env.example",
"BUILD_SHA",
"docs/reference/openapi.yaml",
"docs/openapi.yaml",
"http-method-guard.cjs",
"open-sse/mcp-server/server.js",
// LLMLingua ONNX worker — esbuild'd standalone .js spawned via worker_threads

View File

@@ -5,7 +5,7 @@ import path from "node:path";
const cwd = process.cwd();
const packageJsonPath = path.resolve(cwd, "package.json");
const openApiPath = path.resolve(cwd, "docs/reference/openapi.yaml");
const openApiPath = path.resolve(cwd, "docs/openapi.yaml");
const changelogPath = path.resolve(cwd, "CHANGELOG.md");
const llmPath = path.resolve(cwd, "llm.txt");
const i18nDocsPath = path.resolve(cwd, "docs/i18n");
@@ -209,7 +209,7 @@ try {
const openApiVersion = extractOpenApiVersion(readText(openApiPath));
if (!openApiVersion) {
fail("could not extract docs/reference/openapi.yaml info.version");
fail("could not extract docs/openapi.yaml info.version");
} else if (openApiVersion !== packageVersion) {
fail(`OpenAPI version (${openApiVersion}) differs from package.json (${packageVersion})`);
} else {

View File

@@ -358,7 +358,7 @@ const ENDPOINT_ALLOWLIST = new Set([
/** Doc files to skip (auto-generated, vendored, or third-party). */
const SKIP_DOC_FILES = new Set([
"docs/reference/PROVIDER_REFERENCE.md", // auto-generated from providers.ts
"docs/reference/openapi.yaml",
"docs/openapi.yaml",
"docs/i18n", // translations — separate workflow
// Point-in-time documentation audit (v3.8.24): intentionally references drift,
// counts, and not-yet-existing files as part of documenting them — not living docs.

View File

@@ -2,7 +2,7 @@
// scripts/check/check-openapi-breaking.mjs
// Catraca de breaking-change na API pública (Fase 8 B.4 — backlog opcional).
//
// Diffa a spec do PR (docs/reference/openapi.yaml na working tree = HEAD) contra
// Diffa a spec do PR (docs/openapi.yaml na working tree = HEAD) contra
// a MESMA spec no branch base, via `oasdiff breaking`. Pega regressões de contrato:
// endpoint removido, parâmetro novo obrigatório, campo de resposta removido, enum
// estreitado, etc. — mudanças que quebram clientes existentes.
@@ -32,7 +32,7 @@
// • CI passa BASE_REF=${{ github.base_ref }} (ex.: "release/vX.Y.Z").
// • Local: default derivado da versão do package.json (releaseBranchForVersion),
// ex.: package 3.8.29 → "origin/release/v3.8.29" — nunca fica stale entre ciclos.
// A spec base é extraída com `git show <BASE_REF>:docs/reference/openapi.yaml`.
// A spec base é extraída com `git show <BASE_REF>:docs/openapi.yaml`.
//
// Uso:
// node scripts/check/check-openapi-breaking.mjs
@@ -52,8 +52,8 @@ const QUIET = process.argv.includes("--quiet");
const PRINT_JSON = process.argv.includes("--json");
const RATCHET = process.argv.includes("--ratchet");
const SPEC_REL = "docs/reference/openapi.yaml";
const SPEC_PATH = path.join(ROOT, "docs", "reference", "openapi.yaml");
const SPEC_REL = "docs/openapi.yaml";
const SPEC_PATH = path.join(ROOT, "docs", "openapi.yaml");
/**
* Deriva o branch base de release a partir de uma versão semver

View File

@@ -13,7 +13,7 @@ import * as yaml from "js-yaml";
const ROOT = process.cwd();
const API_ROOT = path.join(ROOT, "src", "app", "api");
const OPENAPI_PATH = path.join(ROOT, "docs", "reference", "openapi.yaml");
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,

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
// scripts/check/check-openapi-routes.mjs
// Gate anti-alucinação (docs): toda `path` documentada em docs/reference/openapi.yaml
// Gate anti-alucinação (docs): toda `path` documentada em docs/openapi.yaml
// deve resolver para um route.ts real em src/app/api/. Pega endpoint INVENTADO/obsoleto
// na spec (a IA escreve docs descrevendo rota que não existe). Complementa
// check-openapi-coverage.mjs (que mede a direção inversa: % de rotas documentadas).
@@ -14,7 +14,7 @@ import { assertNoStale } from "./lib/allowlist.mjs";
const ROOT = process.cwd();
const API_ROOT = path.join(ROOT, "src", "app", "api");
const OPENAPI_PATH = path.join(ROOT, "docs", "reference", "openapi.yaml");
const OPENAPI_PATH = path.join(ROOT, "docs", "openapi.yaml");
// Entradas da spec sem rota real, congeladas para triagem (catraca: bloqueia NOVAS).
export const KNOWN_STALE_SPEC = new Set([

View File

@@ -11,7 +11,7 @@ import path from "node:path";
import * as yaml from "js-yaml";
const ROOT = process.cwd();
const OPENAPI_PATH = path.join(ROOT, "docs", "reference", "openapi.yaml");
const OPENAPI_PATH = path.join(ROOT, "docs", "openapi.yaml");
const ROUTE_GUARD_PATH = path.join(ROOT, "src", "server", "authz", "routeGuard.ts");
function parseStringArray(match) {

View File

@@ -10,7 +10,7 @@ import * as yaml from "js-yaml";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, "..", "..");
const SPEC_PATH = process.env.OPENAPI_SPEC || join(ROOT, "docs/reference/openapi.yaml");
const SPEC_PATH = process.env.OPENAPI_SPEC || join(ROOT, "docs/openapi.yaml");
const OUT_DIR = join(ROOT, "bin/cli/api-commands");
// Operations already covered by hand-crafted commands — skip in generated output.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
/**
* gen-openapi-module.mjs — build helper that reads docs/reference/openapi.yaml,
* gen-openapi-module.mjs — build helper that reads docs/openapi.yaml,
* flattens the path/method matrix, and emits
* src/app/docs/lib/openapi.generated.ts so the Api Explorer client can
* iterate endpoints without parsing YAML at runtime.
@@ -23,7 +23,7 @@ import * as yaml from "js-yaml";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT = path.resolve(__dirname, "..", "..");
const OPENAPI_PATH = path.join(ROOT, "docs", "reference", "openapi.yaml");
const OPENAPI_PATH = path.join(ROOT, "docs", "openapi.yaml");
const OUT_PATH = path.join(ROOT, "src", "app", "docs", "lib", "openapi.generated.ts");
const HTTP_METHODS = ["get", "post", "put", "delete", "patch", "options", "head"];
@@ -108,7 +108,7 @@ async function main() {
const header = `// AUTO-GENERATED by scripts/docs/gen-openapi-module.mjs — DO NOT EDIT MANUALLY
// Regenerate with: node scripts/docs/gen-openapi-module.mjs
//
// Source of truth: docs/reference/openapi.yaml
// Source of truth: docs/openapi.yaml
//
// The Api Explorer consumes \`OPENAPI_ENDPOINTS\`; the spec metadata
// (\`OPENAPI_VERSION\`, \`OPENAPI_TITLE\`) is surfaced in the page header.

View File

@@ -119,7 +119,7 @@ function coverageByModule() {
// 4) OpenAPI coverage: percentage of implemented routes documented in openapi.yaml
function openapiCoverage() {
const API_ROOT = path.join(cwd, "src", "app", "api");
const OPENAPI_PATH = path.join(cwd, "docs", "reference", "openapi.yaml");
const OPENAPI_PATH = path.join(cwd, "docs", "openapi.yaml");
if (!fs.existsSync(API_ROOT) || !fs.existsSync(OPENAPI_PATH)) return;
function collectRoutePaths(dir) {