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.

340 lines (292 loc) 9.47 kB
const fs = require('fs-extra'); const path = require('path'); const { glob } = require('glob'); async function detectFramework(inputPath) { const frameworks = []; try { // Check package.json for framework dependencies const packageFrameworks = await detectFromPackageJson(inputPath); frameworks.push(...packageFrameworks); // Check source files for framework patterns const sourceFrameworks = await detectFromSourceFiles(inputPath); frameworks.push(...sourceFrameworks); // Remove duplicates return [...new Set(frameworks)]; } catch (error) { console.warn('⚠️ Framework detection failed:', error.message); return []; } } async function detectFromPackageJson(inputPath) { const frameworks = []; const packageJsonPath = path.join(inputPath, 'package.json'); if (await fs.pathExists(packageJsonPath)) { try { const packageJson = await fs.readJson(packageJsonPath); const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Express.js detection if (allDeps.express) { frameworks.push('express'); } // Next.js detection if (allDeps.next) { frameworks.push('next'); } // NestJS detection if (allDeps['@nestjs/core'] || allDeps['@nestjs/common'] || allDeps['@nestjs/platform-express']) { frameworks.push('nestjs'); } // Koa detection if (allDeps.koa) { frameworks.push('koa'); } // Fastify detection if (allDeps.fastify) { frameworks.push('fastify'); } } catch (error) { console.warn('⚠️ Failed to read package.json:', error.message); } } // Check for Python requirements const requirementsPath = path.join(inputPath, 'requirements.txt'); if (await fs.pathExists(requirementsPath)) { try { const requirements = await fs.readFile(requirementsPath, 'utf8'); if (requirements.includes('fastapi') || requirements.includes('FastAPI')) { frameworks.push('fastapi'); } if (requirements.includes('flask') || requirements.includes('Flask')) { frameworks.push('flask'); } if (requirements.includes('django') || requirements.includes('Django')) { frameworks.push('django'); } } catch (error) { console.warn('⚠️ Failed to read requirements.txt:', error.message); } } // Check for Java Maven/Gradle const pomPath = path.join(inputPath, 'pom.xml'); const gradlePath = path.join(inputPath, 'build.gradle'); if (await fs.pathExists(pomPath)) { try { const pom = await fs.readFile(pomPath, 'utf8'); if (pom.includes('spring-boot-starter-web') || pom.includes('spring-web')) { frameworks.push('spring'); } } catch (error) { console.warn('⚠️ Failed to read pom.xml:', error.message); } } if (await fs.pathExists(gradlePath)) { try { const gradle = await fs.readFile(gradlePath, 'utf8'); if (gradle.includes('spring-boot-starter-web') || gradle.includes('spring-web')) { frameworks.push('spring'); } } catch (error) { console.warn('⚠️ Failed to read build.gradle:', error.message); } } return frameworks; } async function detectFromSourceFiles(inputPath) { const frameworks = []; // Sample a few files to avoid scanning everything const sampleFiles = await getSampleFiles(inputPath, 20); for (const file of sampleFiles) { try { const content = await fs.readFile(file, 'utf8'); const detectedFrameworks = analyzeFileContent(content, file); frameworks.push(...detectedFrameworks); } catch (error) { // Skip files that can't be read continue; } } return [...new Set(frameworks)]; } async function getSampleFiles(inputPath, maxFiles = 20) { const patterns = [ '**/*.js', '**/*.ts', '**/*.py', '**/*.java' ]; const excludePatterns = [ 'node_modules/**', 'dist/**', 'build/**', '.git/**', 'coverage/**', '**/*.test.*', '**/*.spec.*' ]; const allFiles = []; for (const pattern of patterns) { const files = await glob(pattern, { cwd: inputPath, absolute: true, ignore: excludePatterns }); allFiles.push(...files); } // Return a sample of files const shuffled = allFiles.sort(() => 0.5 - Math.random()); return shuffled.slice(0, maxFiles); } function analyzeFileContent(content, filePath) { const frameworks = []; const ext = path.extname(filePath); // JavaScript/TypeScript frameworks if (['.js', '.ts', '.jsx', '.tsx'].includes(ext)) { // Express.js patterns if (content.includes('express()') || content.includes('app.get(') || content.includes('app.post(') || content.includes('router.get(') || content.includes('router.post(')) { frameworks.push('express'); } // Next.js patterns if (content.includes('export default function') && (content.includes('getServerSideProps') || content.includes('getStaticProps') || filePath.includes('pages/'))) { frameworks.push('next'); } // NestJS patterns if (content.includes('@Controller') || content.includes('@Get(') || content.includes('@Post(') || content.includes('@Injectable()') || content.includes('@Module') || content.includes('NestFactory')) { frameworks.push('nestjs'); } // Koa patterns if (content.includes('new Koa()') || content.includes('ctx.body') || content.includes('ctx.request')) { frameworks.push('koa'); } // Fastify patterns if (content.includes('fastify()') || content.includes('fastify.get(') || content.includes('fastify.post(')) { frameworks.push('fastify'); } } // Python frameworks if (ext === '.py') { // FastAPI patterns if (content.includes('from fastapi') || content.includes('FastAPI()') || content.includes('@app.get') || content.includes('@app.post')) { frameworks.push('fastapi'); } // Flask patterns if (content.includes('from flask') || content.includes('Flask(__name__)') || content.includes('@app.route')) { frameworks.push('flask'); } // Django patterns if (content.includes('from django') || content.includes('django.http') || content.includes('class.*View') || filePath.includes('views.py') || filePath.includes('urls.py')) { frameworks.push('django'); } } // Java frameworks if (ext === '.java') { // Spring Boot patterns if (content.includes('@RestController') || content.includes('@RequestMapping') || content.includes('@GetMapping') || content.includes('@PostMapping') || content.includes('ResponseEntity') || content.includes('@SpringBootApplication')) { frameworks.push('spring'); } } return frameworks; } function getFrameworkInfo(framework) { const frameworkData = { express: { name: 'Express.js', language: 'JavaScript/TypeScript', description: 'Fast, unopinionated, minimalist web framework for Node.js', color: '#000000' }, next: { name: 'Next.js', language: 'JavaScript/TypeScript', description: 'React framework with server-side rendering', color: '#000000' }, nestjs: { name: 'NestJS', language: 'TypeScript', description: 'Progressive Node.js framework for building efficient server-side applications', color: '#e0234e' }, koa: { name: 'Koa', language: 'JavaScript', description: 'Expressive middleware for node.js', color: '#33333d' }, fastify: { name: 'Fastify', language: 'JavaScript/TypeScript', description: 'Fast and low overhead web framework for Node.js', color: '#000000' }, fastapi: { name: 'FastAPI', language: 'Python', description: 'Modern, fast web framework for building APIs with Python', color: '#009688' }, flask: { name: 'Flask', language: 'Python', description: 'Lightweight WSGI web application framework', color: '#000000' }, django: { name: 'Django', language: 'Python', description: 'High-level Python web framework', color: '#092e20' }, spring: { name: 'Spring Boot', language: 'Java', description: 'Spring-based applications with minimal configuration', color: '#6db33f' } }; return frameworkData[framework] || { name: framework, language: 'Unknown', description: 'Framework detected in codebase', color: '#666666' }; } module.exports = { detectFramework, detectFromPackageJson, detectFromSourceFiles, analyzeFileContent, getFrameworkInfo };