80 lines
2.2 KiB
JavaScript
80 lines
2.2 KiB
JavaScript
#!/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.`);
|