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
JavaScript
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
};