const fs = require('fs'); const path = require('path'); /** * Repo Profiler * Analyzes repository files and dependencies to determine its Archetype deterministically. */ const ARCHETYPES = { INFRASTRUCTURE: 'Infrastructure', FRONTEND: 'Frontend SPA', BACKEND: 'Backend API', PIPELINE: 'Data Pipeline', LIBRARY: 'Library', MONOREPO: 'Monorepo', UNKNOWN: 'Unknown' }; function readJsonFile(filePath) { try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch (e) { return null; } } function analyzePackageJson(dir) { const pkg = readJsonFile(path.join(dir, 'package.json')); if (!pkg) return null; const signals = []; const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }; const depsKeys = Object.keys(deps); // Frontend if (depsKeys.includes('react') || depsKeys.includes('vue') || depsKeys.includes('angular') || depsKeys.includes('next') || depsKeys.includes('vite') || depsKeys.includes('webpack')) { signals.push('frontend_framework'); } // Backend if (depsKeys.includes('express') || depsKeys.includes('fastify') || depsKeys.includes('nestjs') || depsKeys.includes('koa')) { signals.push('backend_framework'); } // Library if (!pkg.scripts?.start && (pkg.main || pkg.exports) && !depsKeys.includes('express') && !depsKeys.includes('react') && !depsKeys.includes('vue') && !depsKeys.includes('angular')) { signals.push('library_exports'); } // Monorepo workspaces if (pkg.workspaces) { signals.push('workspaces'); } return signals; } function analyzeFiles(dir, maxDepth = 3) { const rootFiles = []; try { rootFiles.push(...fs.readdirSync(dir)); } catch (e) { // directory doesn't exist } const signals = []; // Check root level first if (rootFiles.includes('Chart.yaml') || rootFiles.some(f => f.endsWith('.tf')) || rootFiles.includes('terraform')) { signals.push('infra_files'); } if (rootFiles.some(f => f.endsWith('.hcl') || f === 'helm' || f === 'kubernetes' || f === 'k8s')) { signals.push('infra_files'); } if (rootFiles.includes('go.mod')) signals.push('go_backend'); if (rootFiles.includes('Cargo.toml')) signals.push('rust_app'); if (rootFiles.includes('requirements.txt') || rootFiles.includes('Pipfile') || rootFiles.includes('pyproject.toml')) signals.push('python_app'); if (rootFiles.includes('lerna.json') || rootFiles.includes('turbo.json') || rootFiles.includes('nx.json')) signals.push('monorepo_tools'); // Recurse into subdirectories to find infra patterns (Helm charts, TF files) if (!signals.includes('infra_files')) { const infraFound = findInfraRecursive(dir, maxDepth, 0); if (infraFound) signals.push('infra_files'); } return signals; } function findInfraRecursive(dir, maxDepth, currentDepth) { if (currentDepth >= maxDepth) return false; try { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === '.terraform') continue; if (entry.isFile()) { if (entry.name === 'Chart.yaml' || entry.name.endsWith('.tf') || entry.name === 'Dockerfile' || entry.name === 'crossplane.yaml') { return true; } } else if (entry.isDirectory()) { if (['charts', 'helm', 'terraform', 'modules', 'k8s', 'kubernetes'].includes(entry.name)) return true; if (findInfraRecursive(path.join(dir, entry.name), maxDepth, currentDepth + 1)) return true; } } } catch (e) { /* skip unreadable dirs */ } return false; } function profileRepo(repoPath, graph = null) { const signals = new Set(); const fileSignals = analyzeFiles(repoPath); fileSignals.forEach(s => signals.add(s)); const pkgSignals = analyzePackageJson(repoPath); if (pkgSignals) { pkgSignals.forEach(s => signals.add(s)); } if (graph && graph.nodes) { let hasRoutes = false; let hasComponents = false; let hasInfraNodes = false; for (const [id, node] of Object.entries(graph.nodes)) { if (node.type === 'route' || node.type === 'controller') hasRoutes = true; if (node.type === 'component') hasComponents = true; if (node.type === 'resource' || node.type === 'chart' || node.type === 'module') hasInfraNodes = true; } if (hasRoutes) signals.add('graph_routes'); if (hasComponents) signals.add('graph_components'); if (hasInfraNodes) signals.add('graph_infra'); } let archetype = ARCHETYPES.UNKNOWN; let confidence = 0.0; if (signals.has('workspaces') || signals.has('monorepo_tools')) { archetype = ARCHETYPES.MONOREPO; confidence = 0.9; } else if (signals.has('infra_files') || signals.has('graph_infra')) { archetype = ARCHETYPES.INFRASTRUCTURE; confidence = 0.9; } else if (signals.has('frontend_framework') || signals.has('graph_components')) { archetype = ARCHETYPES.FRONTEND; confidence = 0.85; } else if (signals.has('backend_framework') || signals.has('graph_routes') || signals.has('go_backend')) { archetype = ARCHETYPES.BACKEND; confidence = 0.85; } else if (signals.has('library_exports')) { archetype = ARCHETYPES.LIBRARY; confidence = 0.7; } else if (signals.has('python_app') || signals.has('rust_app')) { archetype = ARCHETYPES.BACKEND; confidence = 0.6; } return { archetype, confidence, signals: Array.from(signals) }; } module.exports = { profileRepo, ARCHETYPES }; if (require.main === module) { const targetDir = process.argv[2] || process.cwd(); const profile = profileRepo(targetDir); console.log(JSON.stringify(profile, null, 2)); }