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.

172 lines (144 loc) 4.89 kB
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;