@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
JavaScript
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