chore: make OpenAPI export atomic and add read-only check (WealthLedger/backend#3) (#97)
This commit was merged in pull request #97.
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
const { randomBytes } = require('node:crypto');
|
||||
const { writeFile } = require('node:fs/promises');
|
||||
const { readFile, rename, rm, writeFile } = require('node:fs/promises');
|
||||
const { resolve } = require('node:path');
|
||||
|
||||
// Relative to the working directory, as before: the contract test runs the script in a temporary directory.
|
||||
const target = resolve(process.cwd(), 'openapi.json');
|
||||
const checkOnly = process.argv.includes('--check');
|
||||
|
||||
// Schema generation needs no deployment configuration or real credentials.
|
||||
Object.assign(process.env, {
|
||||
@@ -19,23 +24,48 @@ const { NestFactory } = require('@nestjs/core');
|
||||
const { AppModule } = require('../dist/src/app.module.js');
|
||||
const { API_PREFIX, createOpenApiDocument } = require('../dist/src/openapi.js');
|
||||
|
||||
async function generate() {
|
||||
async function render() {
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
preview: true,
|
||||
logger: false,
|
||||
});
|
||||
try {
|
||||
app.setGlobalPrefix(API_PREFIX);
|
||||
await writeFile(
|
||||
'openapi.json',
|
||||
JSON.stringify(createOpenApiDocument(app), null, 2) + '\n',
|
||||
);
|
||||
return JSON.stringify(createOpenApiDocument(app), null, 2) + '\n';
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
}
|
||||
|
||||
generate().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
// Read-only: compares the runtime document with the committed artifact and never writes.
|
||||
async function check(content) {
|
||||
const current = await readFile(target, 'utf8').catch(() => null);
|
||||
if (current !== content) {
|
||||
console.error(
|
||||
'openapi.json is out of date with the runtime schema. Run `npm run openapi:generate`.',
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
console.log('openapi.json matches the runtime schema.');
|
||||
}
|
||||
|
||||
// Atomic update: write a sibling temp file, then rename over the target, so a failed run never leaves partial output.
|
||||
async function update(content) {
|
||||
const temp = `${target}.${process.pid}.tmp`;
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Operator-selected working directory and fixed artifact name; no request input.
|
||||
await writeFile(temp, content);
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Operator-selected working directory and fixed artifact name; no request input.
|
||||
await rename(temp, target);
|
||||
} finally {
|
||||
await rm(temp, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
render()
|
||||
.then((content) => (checkOnly ? check(content) : update(content)))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user