#!/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' }); try { runBundle(`redocly bundle "${tempFile}" -o "${outputFile}"`); } catch (e) { // Fallback: use npx if redocly isn't installed globally runBundle(`npx --yes @redocly/cli bundle "${tempFile}" -o "${outputFile}"`); } 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)}`); } catch (e) { console.error(`\n[ERROR] Failed to run Redocly CLI: ${e.message}`); console.error('Make sure Redocly CLI is installed (global or via npx): npm install -g @redocly/cli'); process.exit(1); }