# Swagger/OpenAPI Runner Guide for generating backend API documentation from OpenAPI/Swagger specifications. ## Overview This script extracts and converts OpenAPI/Swagger specifications to Markdown format for inclusion in the software manual. ## Detection Strategy ### Check for Existing Specification ```javascript async function detectOpenAPISpec() { // Check for existing spec files const specPatterns = [ 'openapi.json', 'openapi.yaml', 'openapi.yml', 'swagger.json', 'swagger.yaml', 'swagger.yml', '**/openapi*.json', '**/swagger*.json' ]; for (const pattern of specPatterns) { const files = Glob(pattern); if (files.length > 0) { return { found: true, type: 'file', path: files[0] }; } } // Check for swagger-jsdoc in dependencies const packageJson = JSON.parse(Read('package.json')); if (packageJson.dependencies?.['swagger-jsdoc'] || packageJson.devDependencies?.['swagger-jsdoc']) { return { found: true, type: 'jsdoc' }; } // Check for NestJS Swagger if (packageJson.dependencies?.['@nestjs/swagger']) { return { found: true, type: 'nestjs' }; } // Check for runtime endpoint return { found: false, suggestion: 'runtime' }; } ``` ## Extraction Methods ### Method A: From Existing Spec File ```javascript async function extractFromFile(specPath, workDir) { const outputDir = `${workDir}/api-docs/backend`; Bash({ command: `mkdir -p "${outputDir}"` }); // Copy spec to output Bash({ command: `cp "${specPath}" "${outputDir}/openapi.json"` }); // Convert to Markdown using widdershins const result = Bash({ command: `npx widdershins "${specPath}" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'python:Python' 'bash:cURL'`, timeout: 60000 }); return { success: result.exitCode === 0, outputDir }; } ``` ### Method B: From swagger-jsdoc ```javascript async function extractFromJsDoc(workDir) { const outputDir = `${workDir}/api-docs/backend`; // Look for swagger definition file const defFiles = Glob('**/swagger*.js').concat(Glob('**/openapi*.js')); if (defFiles.length === 0) { return { success: false, error: 'No swagger definition found' }; } // Generate spec const result = Bash({ command: `npx swagger-jsdoc -d "${defFiles[0]}" -o "${outputDir}/openapi.json"`, timeout: 60000 }); if (result.exitCode !== 0) { return { success: false, error: result.stderr }; } // Convert to Markdown Bash({ command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'bash:cURL'` }); return { success: true, outputDir }; } ``` ### Method C: From NestJS Swagger ```javascript async function extractFromNestJS(workDir) { const outputDir = `${workDir}/api-docs/backend`; // NestJS typically exposes /api-docs-json at runtime // We need to start the server temporarily // Start server in background const server = Bash({ command: 'npm run start:dev', run_in_background: true, timeout: 30000 }); // Wait for server to be ready await waitForServer('http://localhost:3000', 30000); // Fetch OpenAPI spec const spec = await fetch('http://localhost:3000/api-docs-json'); const specJson = await spec.json(); // Save spec Write(`${outputDir}/openapi.json`, JSON.stringify(specJson, null, 2)); // Stop server KillShell({ shell_id: server.task_id }); // Convert to Markdown Bash({ command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'bash:cURL'` }); return { success: true, outputDir }; } ``` ### Method D: From Runtime Endpoint ```javascript async function extractFromRuntime(workDir, serverUrl = 'http://localhost:3000') { const outputDir = `${workDir}/api-docs/backend`; // Common OpenAPI endpoint paths const endpointPaths = [ '/api-docs-json', '/swagger.json', '/openapi.json', '/docs/json', '/api/v1/docs.json' ]; let specJson = null; for (const path of endpointPaths) { try { const response = await fetch(`${serverUrl}${path}`); if (response.ok) { specJson = await response.json(); break; } } catch (e) { continue; } } if (!specJson) { return { success: false, error: 'Could not fetch OpenAPI spec from server' }; } // Save and convert Write(`${outputDir}/openapi.json`, JSON.stringify(specJson, null, 2)); Bash({ command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md"` }); return { success: true, outputDir }; } ``` ## Installation ### Required Tools ```bash # For OpenAPI to Markdown conversion npm install -g widdershins # Or as dev dependency npm install --save-dev widdershins # For generating from JSDoc comments npm install --save-dev swagger-jsdoc ``` ## Configuration ### widdershins Options ```bash npx widdershins openapi.json \ -o api-reference.md \ --language_tabs 'javascript:JavaScript' 'python:Python' 'bash:cURL' \ --summary \ --omitHeader \ --resolve \ --expandBody ``` | Option | Description | |--------|-------------| | `--language_tabs` | Code example languages | | `--summary` | Use summary as operation heading | | `--omitHeader` | Don't include title header | | `--resolve` | Resolve $ref references | | `--expandBody` | Show full request body | ### swagger-jsdoc Definition Example `swagger-def.js`: ```javascript module.exports = { definition: { openapi: '3.0.0', info: { title: 'MyApp API', version: '1.0.0', description: 'API documentation for MyApp' }, servers: [ { url: 'http://localhost:3000/api/v1' } ] }, apis: ['./src/routes/*.js', './src/controllers/*.js'] }; ``` ## Output Format ### Generated Markdown Structure ```markdown # MyApp API ## Overview Base URL: `http://localhost:3000/api/v1` ## Authentication This API uses Bearer token authentication. --- ## Projects ### List Projects `GET /projects` Returns a list of all projects. **Parameters** | Name | In | Type | Required | Description | |------|-----|------|----------|-------------| | status | query | string | false | Filter by status | | page | query | integer | false | Page number | **Responses** | Status | Description | |--------|-------------| | 200 | Successful response | | 401 | Unauthorized | **Example Request** ```javascript fetch('/api/v1/projects?status=active') .then(res => res.json()) .then(data => console.log(data)); ``` **Example Response** ```json { "data": [ { "id": "1", "name": "Project 1" } ], "pagination": { "page": 1, "total": 10 } } ``` ``` ## Integration ### Main Runner ```javascript async function runSwaggerExtraction(workDir) { const detection = await detectOpenAPISpec(); if (!detection.found) { console.log('No OpenAPI spec detected. Skipping backend API docs.'); return { success: false, skipped: true }; } let result; switch (detection.type) { case 'file': result = await extractFromFile(detection.path, workDir); break; case 'jsdoc': result = await extractFromJsDoc(workDir); break; case 'nestjs': result = await extractFromNestJS(workDir); break; default: result = await extractFromRuntime(workDir); } if (result.success) { // Post-process the Markdown await postProcessApiDocs(result.outputDir); } return result; } async function postProcessApiDocs(outputDir) { const mdFile = `${outputDir}/api-reference.md`; let content = Read(mdFile); // Remove widdershins header content = content.replace(/^---[\s\S]*?---\n/, ''); // Add custom styling hints content = content.replace(/^(#{1,3} .+)$/gm, '$1\n'); Write(mdFile, content); } ``` ## Troubleshooting ### Common Issues #### "widdershins: command not found" ```bash npm install -g widdershins # Or use npx npx widdershins openapi.json -o api.md ``` #### "Error parsing OpenAPI spec" ```bash # Validate spec first npx @redocly/cli lint openapi.json # Fix common issues npx @redocly/cli bundle openapi.json -o fixed.json ``` #### "Server not responding" Ensure the development server is running and accessible: ```bash # Check if server is running curl http://localhost:3000/health # Check OpenAPI endpoint curl http://localhost:3000/api-docs-json ``` ### Manual Fallback If automatic extraction fails, document APIs manually: 1. List all route files: `Glob('**/routes/*.js')` 2. Extract route definitions using regex 3. Build documentation structure manually ```javascript async function manualApiExtraction(workDir) { const routeFiles = Glob('src/routes/*.js').concat(Glob('src/routes/*.ts')); const endpoints = []; for (const file of routeFiles) { const content = Read(file); const routes = content.matchAll(/router\.(get|post|put|delete|patch)\(['"]([^'"]+)['"]/g); for (const match of routes) { endpoints.push({ method: match[1].toUpperCase(), path: match[2], file: file }); } } return endpoints; } ```