UNPKG

api-scout

Version:

๐Ÿ” Automatically scout, discover and generate beautiful interactive API documentation from your codebase. Supports Express.js, NestJS, FastAPI, Spring Boot with interactive testing and security analysis.

786 lines (683 loc) โ€ข 30.2 kB
const fs = require('fs-extra'); const path = require('path'); const Handlebars = require('handlebars'); const InteractiveTester = require('./interactive-tester'); const AuthenticationAnalyzer = require('../security/auth-analyzer'); class Generator { constructor(options = {}) { this.options = options; this.templatesPath = path.join(__dirname, '../templates'); this.authAnalyzer = new AuthenticationAnalyzer(); } async generate(apiData) { const outputPath = path.resolve(this.options.output); // Ensure output directory exists await fs.ensureDir(outputPath); // Perform security analysis const securityAnalysis = await this.authAnalyzer.analyzeProject(this.options.input); apiData.security = this.authAnalyzer.generateSecurityReport(securityAnalysis); // Generate based on template type switch (this.options.template) { case 'swagger': await this.generateSwaggerDocs(apiData, outputPath); break; case 'redoc': await this.generateRedocDocs(apiData, outputPath); break; case 'custom': await this.generateCustomDocs(apiData, outputPath); break; default: await this.generateSwaggerDocs(apiData, outputPath); } console.log(`๐Ÿ“ Generated documentation in: ${outputPath}`); } async generateSwaggerDocs(apiData, outputPath) { // Generate OpenAPI specification const openApiSpec = this.generateOpenAPISpec(apiData); // Write OpenAPI JSON await fs.writeFile( path.join(outputPath, 'openapi.json'), JSON.stringify(openApiSpec, null, 2) ); // Generate Swagger UI HTML const swaggerHTML = await this.generateSwaggerHTML(openApiSpec); await fs.writeFile(path.join(outputPath, 'index.html'), swaggerHTML); // Copy Swagger UI assets await this.copySwaggerAssets(outputPath); // Generate additional files await this.generateSummaryReport(apiData, outputPath); } async generateRedocDocs(apiData, outputPath) { // Generate OpenAPI specification const openApiSpec = this.generateOpenAPISpec(apiData); // Write OpenAPI JSON await fs.writeFile( path.join(outputPath, 'openapi.json'), JSON.stringify(openApiSpec, null, 2) ); // Generate ReDoc HTML const redocHTML = await this.generateRedocHTML(openApiSpec); await fs.writeFile(path.join(outputPath, 'index.html'), redocHTML); } async generateCustomDocs(apiData, outputPath) { // Generate custom interactive documentation const customHTML = await this.generateCustomHTML(apiData); await fs.writeFile(path.join(outputPath, 'index.html'), customHTML); // Copy custom assets await this.copyCustomAssets(outputPath); // Generate JSON data file await fs.writeFile( path.join(outputPath, 'api-data.json'), JSON.stringify(apiData, null, 2) ); } generateOpenAPISpec(apiData) { const spec = { openapi: '3.0.3', info: { title: 'Generated API Documentation', description: 'Automatically generated API documentation from codebase analysis', version: '1.0.0', contact: { name: 'API Documentation Generator' } }, servers: [ { url: 'http://localhost:3000', description: 'Development server' } ], paths: {}, components: { schemas: {} }, tags: [] }; // Group endpoints by tags const tagSet = new Set(); // Process endpoints for (const endpoint of apiData.endpoints) { const pathKey = this.normalizePath(endpoint.path); if (!spec.paths[pathKey]) { spec.paths[pathKey] = {}; } const operation = { summary: endpoint.description || `${endpoint.method} ${endpoint.path}`, description: endpoint.description || `Generated from ${path.basename(endpoint.file)}`, operationId: this.generateOperationId(endpoint), tags: endpoint.tags || [this.inferTag(endpoint.file)], parameters: this.convertParameters(endpoint.parameters || []), responses: this.generateResponses(endpoint) }; // Add request body for POST/PUT/PATCH if (['POST', 'PUT', 'PATCH'].includes(endpoint.method) && endpoint.parameters) { const bodyParam = endpoint.parameters.find(p => p.in === 'body'); if (bodyParam) { operation.requestBody = { required: bodyParam.required, content: { 'application/json': { schema: { type: 'object' } } } }; } } spec.paths[pathKey][endpoint.method.toLowerCase()] = operation; // Collect tags if (endpoint.tags) { endpoint.tags.forEach(tag => tagSet.add(tag)); } else { tagSet.add(this.inferTag(endpoint.file)); } } // Process schemas for (const schema of apiData.schemas || []) { spec.components.schemas[schema.name] = { type: schema.type || 'object', properties: schema.properties || {}, description: `Generated from ${path.basename(schema.file)}` }; } // Add tags spec.tags = Array.from(tagSet).map(tag => ({ name: tag, description: `${tag} related endpoints` })); return spec; } normalizePath(apiPath) { // Convert path parameters to OpenAPI format return apiPath.replace(/:([^/]+)/g, '{$1}'); } generateOperationId(endpoint) { const pathParts = endpoint.path.split('/').filter(p => p && !p.startsWith(':')); const baseName = pathParts.join('_') || 'root'; return `${endpoint.method.toLowerCase()}_${baseName}`.replace(/[^a-zA-Z0-9_]/g, '_'); } inferTag(filePath) { const fileName = path.basename(filePath, path.extname(filePath)); return fileName.replace(/[^a-zA-Z0-9]/g, ''); } convertParameters(parameters) { return parameters .filter(p => p.in !== 'body') .map(param => ({ name: param.name, in: param.in, required: param.required, schema: { type: param.type || 'string' }, description: param.description })); } generateResponses(endpoint) { const responses = { '200': { description: 'Successful response', content: { 'application/json': { schema: { type: 'object' } } } } }; // Add error responses for different methods if (['POST', 'PUT', 'PATCH'].includes(endpoint.method)) { responses['400'] = { description: 'Bad Request' }; } if (endpoint.method === 'GET') { responses['404'] = { description: 'Not Found' }; } return responses; } async generateSwaggerHTML(openApiSpec) { const template = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>API Documentation</title> <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui.css" /> <style> html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; } *, *:before, *:after { box-sizing: inherit; } body { margin:0; background: #fafafa; } .swagger-ui .topbar { display: none; } </style> </head> <body> <div id="swagger-ui"></div> <script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-bundle.js"></script> <script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-standalone-preset.js"></script> <script> window.onload = function() { const ui = SwaggerUIBundle({ spec: ${JSON.stringify(openApiSpec, null, 2)}, dom_id: '#swagger-ui', deepLinking: true, presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], plugins: [ SwaggerUIBundle.plugins.DownloadUrl ], layout: "StandaloneLayout", tryItOutEnabled: true, requestInterceptor: function(request) { console.log('API Request:', request); return request; } }); } </script> </body> </html>`; return template; } async generateRedocHTML(openApiSpec) { return ` <!DOCTYPE html> <html> <head> <title>API Documentation</title> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet"> <style> body { margin: 0; padding: 0; } </style> </head> <body> <div id="redoc-container"></div> <script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0/bundles/redoc.standalone.js"></script> <script> Redoc.init(${JSON.stringify(openApiSpec, null, 2)}, { theme: { colors: { primary: { main: '#32329f' } }, typography: { fontSize: '14px', fontFamily: 'Roboto, sans-serif' } } }, document.getElementById('redoc-container')); </script> </body> </html>`; } async generateCustomHTML(apiData) { const tester = new InteractiveTester(); return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>๐Ÿš€ Interactive API Documentation</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 2rem 0; margin-bottom: 2rem; } .header h1 { text-align: center; font-size: 2.5rem; } .header p { text-align: center; font-size: 1.1rem; opacity: 0.9; } .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; } .stat-card { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; } .stat-number { font-size: 2rem; font-weight: bold; color: #667eea; } .stat-label { color: #666; margin-top: 0.5rem; } .endpoint-list { display: grid; gap: 1rem; } .endpoint { background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; } .endpoint-header { padding: 1rem; background: #f8f9fa; border-bottom: 1px solid #dee2e6; display: flex; align-items: center; gap: 1rem; justify-content: space-between; } .endpoint-left { display: flex; align-items: center; gap: 1rem; } .method { padding: 0.25rem 0.75rem; border-radius: 4px; font-weight: bold; font-size: 0.875rem; } .method.GET { background: #d4edda; color: #155724; } .method.POST { background: #cce5ff; color: #004085; } .method.PUT { background: #fff3cd; color: #856404; } .method.DELETE { background: #f8d7da; color: #721c24; } .method.PATCH { background: #e2e3e5; color: #383d41; } .endpoint-path { font-family: 'Courier New', monospace; font-weight: bold; } .endpoint-details { padding: 1rem; } .endpoint-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin-top: 1rem; } .meta-section { padding: 1rem; background: #f8f9fa; border-radius: 6px; } .meta-title { font-weight: bold; margin-bottom: 0.5rem; color: #495057; } .parameter { padding: 0.5rem 0; border-bottom: 1px solid #dee2e6; } .parameter:last-child { border-bottom: none; } .param-name { font-family: 'Courier New', monospace; font-weight: bold; } .param-type { color: #6c757d; font-size: 0.875rem; } .search-box { width: 100%; padding: 1rem; border: 2px solid #dee2e6; border-radius: 8px; font-size: 1rem; margin-bottom: 2rem; } .filter-buttons { display: flex; gap: 0.5rem; margin-bottom: 2rem; flex-wrap: wrap; } .filter-btn { padding: 0.5rem 1rem; border: 2px solid #dee2e6; background: white; border-radius: 6px; cursor: pointer; transition: all 0.2s; } .filter-btn.active { background: #667eea; color: white; border-color: #667eea; } .filter-btn:hover { border-color: #667eea; } .test-btn { background: #FF9800; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; font-size: 0.875rem; transition: background 0.2s; } .test-btn:hover { background: #F57C00; } .code-examples { margin-top: 1rem; } .example-tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; } .example-tab { padding: 0.5rem 1rem; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px 4px 0 0; cursor: pointer; font-size: 0.875rem; } .example-tab.active { background: white; border-bottom: 1px solid white; margin-bottom: -1px; } .example-content { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 0 6px 6px 6px; padding: 1rem; } .example-content pre { margin: 0; background: #2d3748; color: #e2e8f0; padding: 1rem; border-radius: 4px; overflow-x: auto; } .example-content code { font-family: 'Courier New', monospace; font-size: 0.875rem; } </style> ${tester.generateTesterHTML()} </head> <body> <div class="header"> <div class="container"> <h1>๐Ÿš€ API Documentation</h1> <p>Generated automatically from your codebase</p> </div> </div> <div class="container"> <div class="stats"> <div class="stat-card"> <div class="stat-number">${apiData.endpoints.length}</div> <div class="stat-label">Total Endpoints</div> </div> <div class="stat-card"> <div class="stat-number">${apiData.frameworks.length}</div> <div class="stat-label">Frameworks Detected</div> </div> <div class="stat-card"> <div class="stat-number">${apiData.schemas?.length || 0}</div> <div class="stat-label">Data Models</div> </div> <div class="stat-card"> <div class="stat-number">${apiData.metadata.totalFiles}</div> <div class="stat-label">Files Scanned</div> </div> </div> <input type="text" class="search-box" placeholder="๐Ÿ” Search endpoints..." id="searchBox"> <div class="filter-buttons"> <button class="filter-btn active" data-method="ALL">All Methods</button> <button class="filter-btn" data-method="GET">GET</button> <button class="filter-btn" data-method="POST">POST</button> <button class="filter-btn" data-method="PUT">PUT</button> <button class="filter-btn" data-method="DELETE">DELETE</button> <button class="filter-btn" data-method="PATCH">PATCH</button> </div> <div class="endpoint-list" id="endpointList"> ${apiData.endpoints.map(endpoint => ` <div class="endpoint" data-method="${endpoint.method}"> <div class="endpoint-header"> <div class="endpoint-left"> <span class="method ${endpoint.method}">${endpoint.method}</span> <span class="endpoint-path">${endpoint.path}</span> </div> <button class="test-btn" onclick="openTester('${endpoint.method}', '${endpoint.path}', ${JSON.stringify(endpoint.parameters || [])})">๐Ÿงช Test API</button> </div> <div class="endpoint-details"> ${endpoint.description ? `<p><strong>Description:</strong> ${endpoint.description}</p>` : ''} <p><strong>File:</strong> <code>${path.basename(endpoint.file)}</code> (Line ${endpoint.line || 'N/A'})</p> ${endpoint.parameters && endpoint.parameters.length > 0 ? ` <div class="endpoint-meta"> <div class="meta-section"> <div class="meta-title">Parameters</div> ${endpoint.parameters.map(param => ` <div class="parameter"> <div class="param-name">${param.name}</div> <div class="param-type">${param.type} โ€ข ${param.in} โ€ข ${param.required ? 'Required' : 'Optional'}</div> ${param.description ? `<div>${param.description}</div>` : ''} </div> `).join('')} </div> </div> ` : ''} <div class="code-examples"> <div class="example-tabs"> <div class="example-tab active" onclick="showExample('curl-${endpoint.method}-${endpoint.path.replace(/[^a-zA-Z0-9]/g, '')}')">cURL</div> <div class="example-tab" onclick="showExample('js-${endpoint.method}-${endpoint.path.replace(/[^a-zA-Z0-9]/g, '')}')">JavaScript</div> <div class="example-tab" onclick="showExample('python-${endpoint.method}-${endpoint.path.replace(/[^a-zA-Z0-9]/g, '')}')">Python</div> </div> <div class="example-content"> <div id="curl-${endpoint.method}-${endpoint.path.replace(/[^a-zA-Z0-9]/g, '')}" class="example-section active"> <pre><code>${this.generateCurlExample(endpoint)}</code></pre> </div> <div id="js-${endpoint.method}-${endpoint.path.replace(/[^a-zA-Z0-9]/g, '')}" class="example-section" style="display: none;"> <pre><code>${this.generateJavaScriptExample(endpoint)}</code></pre> </div> <div id="python-${endpoint.method}-${endpoint.path.replace(/[^a-zA-Z0-9]/g, '')}" class="example-section" style="display: none;"> <pre><code>${this.generatePythonExample(endpoint)}</code></pre> </div> </div> </div> </div> </div> `).join('')} </div> </div> <script> ${tester.generateTesterJS()} // Search functionality const searchBox = document.getElementById('searchBox'); const endpointList = document.getElementById('endpointList'); const filterButtons = document.querySelectorAll('.filter-btn'); let currentFilter = 'ALL'; function filterEndpoints() { const searchTerm = searchBox.value.toLowerCase(); const endpoints = endpointList.querySelectorAll('.endpoint'); endpoints.forEach(endpoint => { const method = endpoint.dataset.method; const text = endpoint.textContent.toLowerCase(); const matchesSearch = text.includes(searchTerm); const matchesFilter = currentFilter === 'ALL' || method === currentFilter; endpoint.style.display = matchesSearch && matchesFilter ? 'block' : 'none'; }); } searchBox.addEventListener('input', filterEndpoints); filterButtons.forEach(btn => { btn.addEventListener('click', () => { filterButtons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); currentFilter = btn.dataset.method; filterEndpoints(); }); }); // Example tab switching function showExample(exampleId) { // Hide all example sections in the same container const container = document.getElementById(exampleId).closest('.code-examples'); container.querySelectorAll('.example-section').forEach(section => { section.style.display = 'none'; }); container.querySelectorAll('.example-tab').forEach(tab => { tab.classList.remove('active'); }); // Show selected example document.getElementById(exampleId).style.display = 'block'; event.target.classList.add('active'); } </script> </body> </html>`; } async generateSummaryReport(apiData, outputPath) { const report = { summary: { totalEndpoints: apiData.endpoints.length, frameworks: apiData.frameworks, totalSchemas: apiData.schemas?.length || 0, filesScanned: apiData.metadata.totalFiles, generatedAt: new Date().toISOString() }, endpointsByMethod: this.groupEndpointsByMethod(apiData.endpoints), endpointsByFile: this.groupEndpointsByFile(apiData.endpoints), coverage: this.calculateCoverage(apiData) }; await fs.writeFile( path.join(outputPath, 'report.json'), JSON.stringify(report, null, 2) ); } groupEndpointsByMethod(endpoints) { const groups = {}; endpoints.forEach(endpoint => { if (!groups[endpoint.method]) { groups[endpoint.method] = []; } groups[endpoint.method].push(endpoint); }); return groups; } groupEndpointsByFile(endpoints) { const groups = {}; endpoints.forEach(endpoint => { const fileName = path.basename(endpoint.file); if (!groups[fileName]) { groups[fileName] = []; } groups[fileName].push(endpoint); }); return groups; } calculateCoverage(apiData) { return { filesWithEndpoints: new Set(apiData.endpoints.map(e => e.file)).size, totalFiles: apiData.metadata.totalFiles, coveragePercentage: Math.round((new Set(apiData.endpoints.map(e => e.file)).size / apiData.metadata.totalFiles) * 100) }; } async copySwaggerAssets(outputPath) { // In a real implementation, you might want to copy local Swagger UI assets // For now, we're using CDN links in the HTML } async copyCustomAssets(outputPath) { // Copy any custom CSS/JS assets if needed } generateCurlExample(endpoint) { const url = `http://localhost:3000${endpoint.path}`; const pathParams = endpoint.parameters?.filter(p => p.in === 'path') || []; const queryParams = endpoint.parameters?.filter(p => p.in === 'query') || []; const bodyParams = endpoint.parameters?.filter(p => p.in === 'body') || []; let finalUrl = url; // Replace path parameters pathParams.forEach(param => { finalUrl = finalUrl.replace(`:${param.name}`, `{${param.name}}`); }); // Add query parameters if (queryParams.length > 0) { const queryString = queryParams.map(p => `${p.name}={${p.name}}`).join('&'); finalUrl += `?${queryString}`; } let curlCommand = `curl -X ${endpoint.method} "${finalUrl}"`; // Add headers curlCommand += ` \\ -H "Content-Type: application/json"`; // Add authentication example if (endpoint.middleware?.includes('authenticateToken') || endpoint.middleware?.includes('authenticate')) { curlCommand += ` \\ -H "Authorization: Bearer YOUR_TOKEN"`; } // Add body for POST/PUT/PATCH if (['POST', 'PUT', 'PATCH'].includes(endpoint.method) && bodyParams.length > 0) { const bodyExample = this.generateBodyExample(bodyParams); curlCommand += ` \\ -d '${bodyExample}'`; } return curlCommand; } generateJavaScriptExample(endpoint) { const url = `http://localhost:3000${endpoint.path}`; const pathParams = endpoint.parameters?.filter(p => p.in === 'path') || []; const queryParams = endpoint.parameters?.filter(p => p.in === 'query') || []; const bodyParams = endpoint.parameters?.filter(p => p.in === 'body') || []; let finalUrl = url; // Replace path parameters pathParams.forEach(param => { finalUrl = finalUrl.replace(`:${param.name}`, `\${${param.name}}`); }); let jsCode = ''; // Define parameters if (pathParams.length > 0) { jsCode += `// Path parameters\n`; pathParams.forEach(param => { jsCode += `const ${param.name} = 'example_${param.name}';\n`; }); jsCode += '\n'; } if (queryParams.length > 0) { jsCode += `// Query parameters\n`; jsCode += `const params = new URLSearchParams({\n`; queryParams.forEach(param => { jsCode += ` ${param.name}: 'example_value',\n`; }); jsCode += `});\n\n`; } // Build fetch request jsCode += `const response = await fetch('${finalUrl}${queryParams.length > 0 ? '?' + '${params}' : ''}', {\n`; jsCode += ` method: '${endpoint.method}',\n`; jsCode += ` headers: {\n`; jsCode += ` 'Content-Type': 'application/json',\n`; if (endpoint.middleware?.includes('authenticateToken') || endpoint.middleware?.includes('authenticate')) { jsCode += ` 'Authorization': 'Bearer YOUR_TOKEN',\n`; } jsCode += ` },\n`; if (['POST', 'PUT', 'PATCH'].includes(endpoint.method)) { const bodyExample = this.generateBodyExample(bodyParams); jsCode += ` body: JSON.stringify(${bodyExample}),\n`; } jsCode += `});\n\n`; jsCode += `const data = await response.json();\n`; jsCode += `console.log(data);`; return jsCode; } generatePythonExample(endpoint) { const url = `http://localhost:3000${endpoint.path}`; const pathParams = endpoint.parameters?.filter(p => p.in === 'path') || []; const queryParams = endpoint.parameters?.filter(p => p.in === 'query') || []; const bodyParams = endpoint.parameters?.filter(p => p.in === 'body') || []; let pythonCode = `import requests\nimport json\n\n`; // Define parameters if (pathParams.length > 0) { pythonCode += `# Path parameters\n`; pathParams.forEach(param => { pythonCode += `${param.name} = 'example_${param.name}'\n`; }); pythonCode += '\n'; } // Build URL let finalUrl = url; pathParams.forEach(param => { finalUrl = finalUrl.replace(`:${param.name}`, `{${param.name}}`); }); pythonCode += `url = f"${finalUrl}"\n\n`; // Headers pythonCode += `headers = {\n`; pythonCode += ` 'Content-Type': 'application/json',\n`; if (endpoint.middleware?.includes('authenticateToken') || endpoint.middleware?.includes('authenticate')) { pythonCode += ` 'Authorization': 'Bearer YOUR_TOKEN',\n`; } pythonCode += `}\n\n`; // Query parameters if (queryParams.length > 0) { pythonCode += `params = {\n`; queryParams.forEach(param => { pythonCode += ` '${param.name}': 'example_value',\n`; }); pythonCode += `}\n\n`; } // Body data if (['POST', 'PUT', 'PATCH'].includes(endpoint.method)) { const bodyExample = this.generateBodyExample(bodyParams); pythonCode += `data = ${bodyExample.replace(/"/g, "'")}\n\n`; } // Make request pythonCode += `response = requests.${endpoint.method.toLowerCase()}(\n`; pythonCode += ` url,\n`; pythonCode += ` headers=headers,\n`; if (queryParams.length > 0) { pythonCode += ` params=params,\n`; } if (['POST', 'PUT', 'PATCH'].includes(endpoint.method)) { pythonCode += ` json=data,\n`; } pythonCode += `)\n\n`; pythonCode += `print(response.status_code)\n`; pythonCode += `print(response.json())`; return pythonCode; } generateBodyExample(bodyParams) { if (!bodyParams || bodyParams.length === 0) { // Generate a simple example based on common patterns return JSON.stringify({ name: "example_name", description: "example_description" }); } const example = {}; bodyParams.forEach(param => { switch (param.type) { case 'string': example[param.name] = `example_${param.name}`; break; case 'integer': case 'number': example[param.name] = 123; break; case 'boolean': example[param.name] = true; break; case 'array': example[param.name] = ['item1', 'item2']; break; default: example[param.name] = `example_${param.name}`; } }); return JSON.stringify(example, null, 2); } } module.exports = Generator;