#!/usr/bin/env node // Lightweight, dependency-free OpenAPI consistency check: valid YAML, every internal // $ref resolves, and every path/schema referenced by a $ref actually exists. This is // not a full OpenAPI 3.1 schema validator, but it catches the class of error that // actually breaks generated clients and docs sites: dangling references and typos. import fs from "node:fs"; import process from "node:process"; import yaml from "js-yaml"; const path = "docs/api/openapi.yaml"; const document = yaml.load(fs.readFileSync(path, "utf8")); const errors = []; function resolveRef(ref) { if (!ref.startsWith("#/")) { errors.push(`External or malformed $ref not supported: ${ref}`); return; } const segments = ref .slice(2) .split("/") .map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")); let cursor = document; for (const segment of segments) { if (cursor === undefined || cursor === null || !(segment in cursor)) { errors.push(`Dangling $ref: ${ref}`); return; } cursor = cursor[segment]; } } function walk(node) { if (Array.isArray(node)) { for (const item of node) { walk(item); } return; } if (node && typeof node === "object") { for (const [key, value] of Object.entries(node)) { if (key === "$ref" && typeof value === "string") { resolveRef(value); } else { walk(value); } } } } if (!document || typeof document !== "object") { errors.push("Document did not parse to an object."); } else { if (document.openapi === undefined) { errors.push("Missing top-level 'openapi' version field."); } if (!document.paths || Object.keys(document.paths).length === 0) { errors.push("No paths defined."); } walk(document); } if (errors.length > 0) { console.error("OpenAPI validation failed:"); for (const error of errors) { console.error(`- ${error}`); } process.exit(1); } const pathCount = Object.keys(document.paths ?? {}).length; const schemaCount = Object.keys(document.components?.schemas ?? {}).length; console.log(`OpenAPI document is well-formed: ${pathCount} paths, ${schemaCount} schemas, all $ref pointers resolve.`);