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