UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

306 lines 11.8 kB
import { existsSync, readFileSync } from 'fs'; import { join } from 'path'; export class FrameworkDetector { projectPath; static FRAMEWORK_PATTERNS = { // Web Frameworks react: { name: 'React', category: 'web' }, vue: { name: 'Vue.js', category: 'web' }, angular: { name: 'Angular', category: 'web' }, '@angular/core': { name: 'Angular', category: 'web' }, svelte: { name: 'Svelte', category: 'web' }, next: { name: 'Next.js', category: 'web' }, nuxt: { name: 'Nuxt.js', category: 'web' }, express: { name: 'Express.js', category: 'backend' }, fastify: { name: 'Fastify', category: 'backend' }, koa: { name: 'Koa', category: 'backend' }, nestjs: { name: 'NestJS', category: 'backend' }, // Python Frameworks django: { name: 'Django', category: 'backend' }, flask: { name: 'Flask', category: 'backend' }, fastapi: { name: 'FastAPI', category: 'backend' }, // Build Tools webpack: { name: 'Webpack', category: 'build' }, vite: { name: 'Vite', category: 'build' }, rollup: { name: 'Rollup', category: 'build' }, parcel: { name: 'Parcel', category: 'build' }, esbuild: { name: 'esbuild', category: 'build' }, // Testing Frameworks jest: { name: 'Jest', category: 'testing' }, mocha: { name: 'Mocha', category: 'testing' }, chai: { name: 'Chai', category: 'testing' }, jasmine: { name: 'Jasmine', category: 'testing' }, cypress: { name: 'Cypress', category: 'testing' }, playwright: { name: 'Playwright', category: 'testing' }, vitest: { name: 'Vitest', category: 'testing' }, // Mobile 'react-native': { name: 'React Native', category: 'mobile' }, flutter: { name: 'Flutter', category: 'mobile' }, ionic: { name: 'Ionic', category: 'mobile' }, // Desktop electron: { name: 'Electron', category: 'desktop' }, tauri: { name: 'Tauri', category: 'desktop' }, }; constructor(projectPath) { this.projectPath = projectPath; } /** * Detect frameworks and dependencies in the project */ detectDependencies() { const result = { frameworks: [], buildTools: [], testingFrameworks: [], buildInfo: {}, }; // Check different dependency files this.checkPackageJson(result); this.checkRequirementsTxt(result); this.checkCargoToml(result); this.checkGoMod(result); // Deduplicate frameworks by name const uniqueFrameworks = new Map(); for (const framework of result.frameworks) { const existing = uniqueFrameworks.get(framework.name); if (!existing || framework.confidence === 'high') { uniqueFrameworks.set(framework.name, framework); } } result.frameworks = Array.from(uniqueFrameworks.values()); // Deduplicate build tools and testing frameworks result.buildTools = [...new Set(result.buildTools)]; result.testingFrameworks = [...new Set(result.testingFrameworks)]; return result; } /** * Check package.json for Node.js dependencies */ checkPackageJson(result) { const packageJsonPath = join(this.projectPath, 'package.json'); if (!existsSync(packageJsonPath)) { return; } try { const content = readFileSync(packageJsonPath, 'utf-8'); const packageJson = JSON.parse(content); // Extract build info if (packageJson.scripts) { result.buildInfo.scripts = packageJson.scripts; result.buildInfo.buildCommand = packageJson.scripts.build; result.buildInfo.testCommand = packageJson.scripts.test; result.buildInfo.devCommand = packageJson.scripts.dev || packageJson.scripts['dev:server']; result.buildInfo.startCommand = packageJson.scripts.start; } // Check dependencies and devDependencies const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, ...packageJson.peerDependencies, }; for (const [depName, version] of Object.entries(allDeps)) { const framework = this.matchFramework(depName, version); if (framework) { result.frameworks.push(framework); // Categorize if (framework.category === 'build') { result.buildTools.push(framework.name); } else if (framework.category === 'testing') { result.testingFrameworks.push(framework.name); } } } } catch { // Ignore parsing errors } } /** * Check requirements.txt for Python dependencies */ checkRequirementsTxt(result) { const reqPath = join(this.projectPath, 'requirements.txt'); if (!existsSync(reqPath)) { return; } try { const content = readFileSync(reqPath, 'utf-8'); const lines = content .split('\n') .filter(line => line.trim() && !line.startsWith('#')); for (const line of lines) { const depName = line.split(/[>=<]/)[0].trim(); const framework = this.matchFramework(depName, ''); if (framework) { result.frameworks.push(framework); } } } catch { // Ignore parsing errors } } /** * Check Cargo.toml for Rust dependencies */ checkCargoToml(result) { const cargoPath = join(this.projectPath, 'Cargo.toml'); if (!existsSync(cargoPath)) { return; } try { const content = readFileSync(cargoPath, 'utf-8'); // Simple TOML parsing for dependencies section const depsMatch = content.match(/\[dependencies\]([\s\S]*?)(?:\[|$)/); if (depsMatch) { const depsSection = depsMatch[1]; const lines = depsSection .split('\n') .filter(line => line.trim() && !line.startsWith('#')); for (const line of lines) { const match = line.match(/^([^=]+)\s*=/); if (match) { const depName = match[1].trim(); const framework = this.matchFramework(depName, ''); if (framework) { result.frameworks.push(framework); } } } } // Check for common Rust web frameworks if (content.includes('actix-web')) { result.frameworks.push({ name: 'Actix Web', category: 'backend', confidence: 'high', }); } if (content.includes('warp')) { result.frameworks.push({ name: 'Warp', category: 'backend', confidence: 'high', }); } if (content.includes('rocket')) { result.frameworks.push({ name: 'Rocket', category: 'backend', confidence: 'high', }); } } catch { // Ignore parsing errors } } /** * Check go.mod for Go dependencies */ checkGoMod(result) { const goModPath = join(this.projectPath, 'go.mod'); if (!existsSync(goModPath)) { return; } try { const content = readFileSync(goModPath, 'utf-8'); // Check for common Go frameworks if (content.includes('gin-gonic/gin')) { result.frameworks.push({ name: 'Gin', category: 'backend', confidence: 'high', }); } if (content.includes('gorilla/mux')) { result.frameworks.push({ name: 'Gorilla Mux', category: 'backend', confidence: 'high', }); } if (content.includes('echo')) { result.frameworks.push({ name: 'Echo', category: 'backend', confidence: 'high', }); } } catch { // Ignore parsing errors } } /** * Match a dependency name to a known framework */ matchFramework(depName, version) { const pattern = FrameworkDetector.FRAMEWORK_PATTERNS[depName]; if (pattern) { return { name: pattern.name, category: pattern.category, version: version || undefined, confidence: 'high', }; } // Check for partial matches with more precise logic for (const [key, pattern] of Object.entries(FrameworkDetector.FRAMEWORK_PATTERNS)) { // Avoid false positives by checking for word boundaries and common prefixes const isExactWordMatch = depName === key; const hasCommonPrefix = depName.startsWith(key + '/') || depName.startsWith(key + '-'); const isPackageVariant = key.startsWith('@') && depName.startsWith(key); // Only match if it's a clear variant of the framework, not just a substring if (isExactWordMatch || hasCommonPrefix || isPackageVariant) { return { name: pattern.name, category: pattern.category, version: version || undefined, confidence: 'medium', }; } } return null; } /** * Get build commands based on detected frameworks and package.json */ getBuildCommands() { const deps = this.detectDependencies(); const commands = {}; if (deps.buildInfo.scripts) { const scripts = deps.buildInfo.scripts; // Standard npm/yarn commands if (scripts.build) commands['Build'] = 'npm run build'; if (scripts.test) commands['Test'] = 'npm run test'; if (scripts.dev) commands['Development'] = 'npm run dev'; if (scripts.start) commands['Start'] = 'npm run start'; if (scripts.lint) commands['Lint'] = 'npm run lint'; } // Add language-specific commands if (existsSync(join(this.projectPath, 'Cargo.toml'))) { commands['Build'] = 'cargo build'; commands['Test'] = 'cargo test'; commands['Run'] = 'cargo run'; } if (existsSync(join(this.projectPath, 'go.mod'))) { commands['Build'] = 'go build'; commands['Test'] = 'go test ./...'; commands['Run'] = 'go run .'; } if (existsSync(join(this.projectPath, 'requirements.txt'))) { commands['Install'] = 'pip install -r requirements.txt'; commands['Test'] = 'python -m pytest'; } return commands; } } //# sourceMappingURL=framework-detector.js.map