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.
172 lines (144 loc) • 4.89 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const { glob } = require('glob');
const ExpressParser = require('../parsers/express');
const FastAPIParser = require('../parsers/fastapi');
const SpringParser = require('../parsers/spring');
const NestJSParser = require('../parsers/nestjs');
const { detectFramework } = require('../utils/framework-detector');
class Scanner {
constructor(options = {}) {
this.options = options;
this.parsers = {
express: new ExpressParser(options),
fastapi: new FastAPIParser(options),
spring: new SpringParser(options),
nestjs: new NestJSParser(options)
};
}
async scan() {
const inputPath = path.resolve(this.options.input);
if (!await fs.pathExists(inputPath)) {
throw new Error(`Input path does not exist: ${inputPath}`);
}
// Detect frameworks
const frameworks = await detectFramework(inputPath);
// Find relevant files
const files = await this.findSourceFiles(inputPath);
console.log(`🔍 Scanning ${files.length} files...`);
const results = {
frameworks,
endpoints: [],
schemas: [],
metadata: {
scannedAt: new Date().toISOString(),
totalFiles: files.length,
inputPath: inputPath
}
};
// Parse files with appropriate parsers
for (const file of files) {
const fileFrameworks = await this.detectFileFramework(file);
for (const framework of fileFrameworks) {
if (this.parsers[framework] && this.shouldScanFramework(framework)) {
try {
const parseResult = await this.parsers[framework].parseFile(file);
if (parseResult.endpoints) {
results.endpoints.push(...parseResult.endpoints);
}
if (parseResult.schemas) {
results.schemas.push(...parseResult.schemas);
}
} catch (error) {
console.warn(`⚠️ Failed to parse ${file} with ${framework} parser:`, error.message);
}
}
}
}
// Remove duplicates and sort
results.endpoints = this.deduplicateEndpoints(results.endpoints);
results.schemas = this.deduplicateSchemas(results.schemas);
return results;
}
async findSourceFiles(inputPath) {
const patterns = [
'**/*.js',
'**/*.ts',
'**/*.py',
'**/*.java',
'**/*.cs'
];
const excludePatterns = [
'node_modules/**',
'dist/**',
'build/**',
'.git/**',
'coverage/**',
'**/*.test.*',
'**/*.spec.*',
...(this.options.exclude || [])
];
const allFiles = [];
for (const pattern of patterns) {
const files = await glob(pattern, {
cwd: inputPath,
absolute: true,
ignore: excludePatterns
});
allFiles.push(...files);
}
return [...new Set(allFiles)]; // Remove duplicates
}
async detectFileFramework(filePath) {
const ext = path.extname(filePath);
const content = await fs.readFile(filePath, 'utf8');
const frameworks = [];
// JavaScript/TypeScript - Express.js
if (['.js', '.ts'].includes(ext)) {
if (content.includes('express') || content.includes('app.get') || content.includes('router.')) {
frameworks.push('express');
}
// NestJS detection
if (content.includes('@Controller') || content.includes('@Get(') || content.includes('@Post(') ||
content.includes('@nestjs/') || content.includes('NestFactory')) {
frameworks.push('nestjs');
}
}
// Python - FastAPI
if (ext === '.py') {
if (content.includes('fastapi') || content.includes('@app.') || content.includes('FastAPI')) {
frameworks.push('fastapi');
}
}
// Java - Spring Boot
if (ext === '.java') {
if (content.includes('@RestController') || content.includes('@RequestMapping') || content.includes('Spring')) {
frameworks.push('spring');
}
}
return frameworks;
}
shouldScanFramework(framework) {
if (this.options.framework === 'all') return true;
return this.options.framework === framework;
}
deduplicateEndpoints(endpoints) {
const seen = new Set();
return endpoints.filter(endpoint => {
const key = `${endpoint.method}:${endpoint.path}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
deduplicateSchemas(schemas) {
const seen = new Set();
return schemas.filter(schema => {
const key = schema.name;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
}
module.exports = Scanner;