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.

242 lines (214 loc) 6.4 kB
const fs = require('fs-extra'); const path = require('path'); const DEFAULT_CONFIG = { input: process.cwd(), output: './docs-output', framework: 'all', template: 'swagger', includePrivate: false, exclude: [ 'node_modules/**', 'dist/**', 'build/**', '.git/**', 'coverage/**', '**/*.test.*', '**/*.spec.*' ], server: { port: 3000, host: 'localhost' }, watch: { debounce: 1000, ignored: /(^|[\/\\])\../ }, generation: { includeTags: true, includeExamples: true, includeSchemas: true, sortEndpoints: true, groupByTags: true } }; async function loadConfig(configPath) { let config = { ...DEFAULT_CONFIG }; // Load from config file if provided if (configPath) { const resolvedPath = path.resolve(configPath); if (await fs.pathExists(resolvedPath)) { try { const fileConfig = await loadConfigFile(resolvedPath); config = mergeConfig(config, fileConfig); } catch (error) { console.warn(`⚠️ Failed to load config from ${configPath}:`, error.message); } } else { console.warn(`⚠️ Config file not found: ${configPath}`); } } else { // Look for default config files config = await loadDefaultConfig(config); } return validateConfig(config); } async function loadDefaultConfig(config) { const configFiles = [ 'scout.config.js', 'scout.config.json', 'api-scout.config.js', 'api-scout.config.json', '.scoutrc', '.scoutrc.json', '.scoutrc.js', '.api-scoutrc', '.api-scoutrc.json', '.api-scoutrc.js', // Legacy support 'apidocs.config.js', 'apidocs.config.json', '.apidocsrc', '.apidocsrc.json', '.apidocsrc.js' ]; for (const configFile of configFiles) { const configPath = path.resolve(configFile); if (await fs.pathExists(configPath)) { try { const fileConfig = await loadConfigFile(configPath); config = mergeConfig(config, fileConfig); console.log(`📝 Loaded config from: ${configFile}`); break; } catch (error) { console.warn(`⚠️ Failed to load ${configFile}:`, error.message); } } } return config; } async function loadConfigFile(configPath) { const ext = path.extname(configPath); if (ext === '.js') { // Load JavaScript config delete require.cache[require.resolve(configPath)]; const config = require(configPath); return typeof config === 'function' ? config() : config; } else { // Load JSON config return await fs.readJson(configPath); } } function mergeConfig(defaultConfig, userConfig) { const merged = { ...defaultConfig }; for (const [key, value] of Object.entries(userConfig)) { if (typeof value === 'object' && value !== null && !Array.isArray(value)) { merged[key] = { ...defaultConfig[key], ...value }; } else { merged[key] = value; } } return merged; } function validateConfig(config) { const errors = []; // Validate input path if (!config.input || typeof config.input !== 'string') { errors.push('Input path must be a valid string'); } // Validate output path if (!config.output || typeof config.output !== 'string') { errors.push('Output path must be a valid string'); } // Validate framework const validFrameworks = ['all', 'express', 'nestjs', 'fastapi', 'spring', 'next', 'koa', 'fastify', 'flask', 'django']; if (!validFrameworks.includes(config.framework)) { errors.push(`Framework must be one of: ${validFrameworks.join(', ')}`); } // Validate template const validTemplates = ['swagger', 'redoc', 'custom']; if (!validTemplates.includes(config.template)) { errors.push(`Template must be one of: ${validTemplates.join(', ')}`); } // Validate exclude patterns if (config.exclude && !Array.isArray(config.exclude)) { errors.push('Exclude must be an array of glob patterns'); } // Validate server config if (config.server) { if (config.server.port && (!Number.isInteger(config.server.port) || config.server.port < 1 || config.server.port > 65535)) { errors.push('Server port must be an integer between 1 and 65535'); } if (config.server.host && typeof config.server.host !== 'string') { errors.push('Server host must be a string'); } } // Validate watch config if (config.watch) { if (config.watch.debounce && (!Number.isInteger(config.watch.debounce) || config.watch.debounce < 0)) { errors.push('Watch debounce must be a non-negative integer'); } } if (errors.length > 0) { throw new Error(`Configuration validation failed:\n${errors.map(e => ` • ${e}`).join('\n')}`); } return config; } function createConfigFile(configPath, config = {}) { const fullConfig = mergeConfig(DEFAULT_CONFIG, config); const ext = path.extname(configPath); if (ext === '.js') { const jsConfig = `// API Scout Configuration module.exports = ${JSON.stringify(fullConfig, null, 2)};`; return fs.writeFile(configPath, jsConfig); } else { return fs.writeJson(configPath, fullConfig, { spaces: 2 }); } } function getConfigTemplate() { return { input: './src', output: './api-docs', framework: 'express', template: 'custom', includePrivate: false, exclude: [ 'node_modules/**', '**/*.test.*', 'dist/**' ], server: { port: 3000, host: 'localhost' }, watch: { debounce: 1000 }, generation: { includeTags: true, includeExamples: true, includeSchemas: true, sortEndpoints: true, groupByTags: true }, customization: { title: 'My API Documentation', description: 'Generated API documentation for my application', version: '1.0.0', contact: { name: 'API Support', email: 'api-support@example.com' } }, security: { enableAnalysis: true, reportLevel: 'detailed' } }; } module.exports = { loadConfig, validateConfig, createConfigFile, getConfigTemplate, DEFAULT_CONFIG };