Generate explicit OpenAPI contracts for API routes

This commit is contained in:
Mike Cao committed 2026-09-01 15:09:10 -07:00
1 parent cc5d71d39f
commit e1a4934038
177 files changed
+50955 -1460

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
import { execFile } from 'node:child_process';
import { rm, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { promisify } from 'node:util';
import { loadApiContracts } from '@/openapi/contracts';
import { discoverApiOperations, discoverContractFiles } from '@/openapi/discover';
import { buildOpenApiDocument } from '@/openapi/document';
import { inferApiContracts } from '@/openapi/infer';
import type { LoadedApiOperationContract } from '@/openapi/operation';
const projectRoot = process.cwd();
const generatedFileName = 'contract.generated.ts';
const generatedHeader = '// Generated by `pnpm openapi:contracts`. Do not edit directly.';
const execFileAsync = promisify(execFile);
const existingGeneratedFiles = (await discoverContractFiles(projectRoot)).filter(
file => path.basename(file) === generatedFileName,
);
await Promise.all(existingGeneratedFiles.map(file => rm(file)));
const [explicitContracts, discovered] = await Promise.all([
loadApiContracts(projectRoot),
discoverApiOperations(projectRoot),
]);
const inferredContracts = inferApiContracts(discovered, explicitContracts);
const { document } = await buildOpenApiDocument('all', projectRoot);
const documentPaths = document.paths ?? {};
const byRoute = new Map<string, LoadedApiOperationContract[]>();
const generatedFiles: string[] = [];
inferredContracts.forEach(contract => {
byRoute.set(contract.source, [...(byRoute.get(contract.source) ?? []), contract]);
});
function serialize(value: unknown) {
return JSON.stringify(value, null, 2);
}
for (const [routeSource, contracts] of byRoute) {
const routeFile = path.join(projectRoot, ...routeSource.split('/'));
const outputFile = path.join(path.dirname(routeFile), generatedFileName);
const operationDefinitions = contracts.map((contract, index) => {
const generatedOperation = documentPaths[contract.path]?.[contract.method];
if (!generatedOperation || typeof generatedOperation !== 'object') {
throw new Error(
`Generated document is missing ${contract.method.toUpperCase()} ${contract.path}.`,
);
}
const operation = structuredClone(generatedOperation) as Record<string, unknown>;
delete operation.security;
delete operation['x-umami-audience'];
delete operation['x-umami-contract'];
delete operation['x-umami-source'];
if (
typeof operation.description === 'string' &&
operation.description.startsWith('Generated from')
) {
operation.description = `${operation.summary}. The request and response models are derived from the validated route contract.`;
}
return `const operation${index + 1} = defineOperation({\n method: ${serialize(contract.method)},\n path: ${serialize(contract.path)},\n audience: ${serialize(contract.audience)},\n auth: ${serialize(contract.auth)},\n operation: ${serialize(operation)} as ZodOpenApiOperationObject,\n});`;
});
const exports = contracts.map((_, index) => `operation${index + 1}`).join(', ');
const content = `${generatedHeader}\n\nimport type { ZodOpenApiOperationObject } from 'zod-openapi';\nimport { defineOperation } from '@/openapi/operation';\n\n${operationDefinitions.join('\n\n')}\n\nexport const operations = [${exports}] as const;\n`;
await writeFile(outputFile, content, 'utf8');
generatedFiles.push(outputFile);
}
const formatter = path.join(projectRoot, 'node_modules', '@biomejs', 'biome', 'bin', 'biome');
for (let index = 0; index < generatedFiles.length; index += 20) {
await execFileAsync(process.execPath, [
formatter,
'format',
'--write',
...generatedFiles.slice(index, index + 20),
]);
}
const generatedCount = [...byRoute.values()].reduce(
(total, contracts) => total + contracts.length,
0,
);
console.log(`Generated ${generatedCount} explicit contracts in ${byRoute.size} route modules.`);