Files
umami/scripts/check-openapi.ts

53 lines
1.6 KiB
TypeScript

import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { formatCoverageSummary } from '@/openapi/coverage';
import { buildOpenApiDocument } from '@/openapi/document';
import { getOperationKey } from '@/openapi/operation';
import { serializeOpenApiDocument } from '@/openapi/serialize';
const projectRoot = process.cwd();
const outputFile = path.join(projectRoot, 'public', 'openapi.json');
const requireExplicit =
process.argv.includes('--explicit') || process.env.OPENAPI_REQUIRE_EXPLICIT === '1';
const verbose = process.argv.includes('--verbose');
const { document, coverage } = await buildOpenApiDocument('all', projectRoot);
const expected = serializeOpenApiDocument(document);
let actual = '';
try {
actual = await readFile(outputFile, 'utf8');
} catch {
// The comparison below reports the missing generated artifact.
}
let failed = false;
if (actual !== expected) {
console.error(
`${path.relative(projectRoot, outputFile)} is stale or missing. Run \`pnpm openapi:generate\`.`,
);
failed = true;
}
console.log(formatCoverageSummary(coverage));
if (verbose && coverage.missing.length) {
coverage.missing.forEach(operation => {
console.log(` ${getOperationKey(operation)} (${operation.source})`);
});
}
if (coverage.missingGenerated.length) {
console.error('OpenAPI generation must include every discovered API operation.');
failed = true;
}
if (requireExplicit && coverage.missing.length) {
console.error('Explicit OpenAPI coverage requires a hand-authored contract for every operation.');
failed = true;
}
if (failed) {
process.exitCode = 1;
}