2026-03-16 07:24:50 +07:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* OpenAPI Documentation Bundler - Node.js Version
|
|
|
|
|
*
|
|
|
|
|
* This script merges the modular OpenAPI specification files into a single
|
|
|
|
|
* api-docs.bundled.yaml file that can be served to Swagger UI.
|
|
|
|
|
*
|
|
|
|
|
* It merges paths from the paths/ directory and then uses Redocly CLI to resolve
|
|
|
|
|
* all $ref references into inline definitions.
|
|
|
|
|
*
|
|
|
|
|
* Usage: node bundle-api-docs.js
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
const path = require('path');
|
|
|
|
|
const { execSync } = require('child_process');
|
|
|
|
|
|
|
|
|
|
const publicDir = __dirname;
|
|
|
|
|
const pathsDir = path.join(publicDir, 'paths');
|
|
|
|
|
const tempFile = path.join(publicDir, 'api-docs.merged.yaml');
|
|
|
|
|
const outputFile = path.join(publicDir, 'api-docs.bundled.yaml');
|
|
|
|
|
|
|
|
|
|
// Read the base api-docs.yaml
|
|
|
|
|
const apiDocsPath = path.join(publicDir, 'api-docs.yaml');
|
|
|
|
|
let apiDocsContent = fs.readFileSync(apiDocsPath, 'utf8');
|
|
|
|
|
|
|
|
|
|
// Parse YAML manually (simple approach - looking for paths: {})
|
|
|
|
|
const pathsMatch = apiDocsContent.match(/^paths:\s*\{\}/m);
|
|
|
|
|
if (!pathsMatch) {
|
|
|
|
|
console.error('Could not find empty paths section in api-docs.yaml');
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read all path files
|
|
|
|
|
const pathFiles = fs.readdirSync(pathsDir)
|
|
|
|
|
.filter(f => f.endsWith('.yaml'))
|
|
|
|
|
.map(f => path.join(pathsDir, f));
|
|
|
|
|
|
|
|
|
|
console.log(`Found ${pathFiles.length} path files to merge...`);
|
|
|
|
|
|
|
|
|
|
// Merge all paths content
|
|
|
|
|
let mergedPaths = [];
|
|
|
|
|
for (const filepath of pathFiles) {
|
|
|
|
|
const filename = path.basename(filepath);
|
|
|
|
|
try {
|
|
|
|
|
let content = fs.readFileSync(filepath, 'utf8');
|
|
|
|
|
// Fix relative paths: ../components/ -> ./components/
|
|
|
|
|
// because paths are now at the root level after merging
|
|
|
|
|
content = content.replace(/\.\.\/components\//g, './components/');
|
|
|
|
|
mergedPaths.push(`# From: ${filename}\n${content}`);
|
|
|
|
|
console.log(` [OK] Merged paths from ${filename}`);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log(` [WARN] Failed to read ${filename}: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace empty paths with merged paths
|
|
|
|
|
const pathsYaml = mergedPaths.join('\n\n');
|
|
|
|
|
const mergedContent = apiDocsContent.replace(
|
|
|
|
|
/^paths:\s*\{\}/m,
|
|
|
|
|
`paths:\n${pathsYaml.split('\n').map(line => ' ' + line).join('\n')}`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Write merged file (still has $refs)
|
|
|
|
|
fs.writeFileSync(tempFile, mergedContent, 'utf8');
|
|
|
|
|
console.log(`\n[SUCCESS] Merged paths into: ${tempFile}`);
|
|
|
|
|
|
|
|
|
|
// Now use Redocly CLI to resolve all $ref references
|
|
|
|
|
console.log('\nResolving $ref references with Redocly CLI...');
|
|
|
|
|
try {
|
|
|
|
|
const runBundle = (cmd) => execSync(cmd, { cwd: publicDir, stdio: 'inherit' });
|
2026-02-16 14:20:52 +07:00
|
|
|
|
|
|
|
|
try {
|
2026-03-16 07:24:50 +07:00
|
|
|
runBundle(`redocly bundle "${tempFile}" -o "${outputFile}"`);
|
2026-02-16 14:20:52 +07:00
|
|
|
} catch (e) {
|
2026-03-16 07:24:50 +07:00
|
|
|
// Fallback: use npx if redocly isn't installed globally
|
|
|
|
|
runBundle(`npx --yes @redocly/cli bundle "${tempFile}" -o "${outputFile}"`);
|
2026-02-16 14:20:52 +07:00
|
|
|
}
|
2026-03-16 07:24:50 +07:00
|
|
|
|
|
|
|
|
console.log(`\n[SUCCESS] Resolved all $ref references to: ${outputFile}`);
|
|
|
|
|
|
|
|
|
|
// Clean up temp file
|
|
|
|
|
fs.unlinkSync(tempFile);
|
|
|
|
|
console.log(`[CLEANUP] Removed temp file: ${tempFile}`);
|
|
|
|
|
|
|
|
|
|
// Show stats
|
|
|
|
|
const stats = fs.statSync(outputFile);
|
|
|
|
|
console.log(`\n${'='.repeat(60)}`);
|
|
|
|
|
console.log('Bundling complete!');
|
|
|
|
|
console.log(` Output: ${outputFile}`);
|
|
|
|
|
console.log(` Size: ${(stats.size / 1024).toFixed(1)} KB`);
|
|
|
|
|
console.log(`\nYou can now serve this file to Swagger UI.`);
|
|
|
|
|
console.log(`${'='.repeat(60)}`);
|
|
|
|
|
|
2026-02-16 14:20:52 +07:00
|
|
|
} catch (e) {
|
|
|
|
|
console.error(`\n[ERROR] Failed to run Redocly CLI: ${e.message}`);
|
2026-03-16 07:24:50 +07:00
|
|
|
console.error('Make sure Redocly CLI is installed (global or via npx): npm install -g @redocly/cli');
|
2026-02-16 14:20:52 +07:00
|
|
|
process.exit(1);
|
|
|
|
|
}
|