import { readFileSync, existsSync } from 'fs'; import { join } from 'path'; /** * Detect Node.js project type and configuration */ export function detectNodeJS(projectPath) { const packageJsonPath = join(projectPath, 'package.json'); if (!existsSync(packageJsonPath)) { return null; } let pkg; try { pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); } catch (error) { return null; } const deps = pkg.dependencies || {}; const devDeps = pkg.devDependencies || {}; const scripts = pkg.scripts || {}; // Check for Electron (not dockerizable) if (deps.electron || devDeps.electron) { return { type: 'nodejs-electron', dockerizable: false, reason: 'Electron apps are desktop applications and cannot be containerized', template: null }; } // Detect project subtype const hasVite = !!devDeps.vite; const hasReact = !!(deps.react || devDeps.react); const hasExpress = !!deps.express; const hasSocketIO = !!(deps['socket.io'] || deps['socket.io-client']); const hasConcurrently = !!devDeps.concurrently; // Check for server-side rendering setup (Vite + React + Express with concurrent dev) if (hasVite && hasReact && hasExpress && (hasConcurrently || scripts.dev?.includes('concurrently'))) { return { type: 'nodejs-vite-react-ssr', dockerizable: true, template: 'nodejs/vite-react-ssr', port: detectPort(pkg) || 3000, entryPoint: detectEntryPoint(pkg, projectPath, 'ssr'), buildCommand: 'npm run build', description: 'Vite + React with Express SSR' }; } // Vite + React SPA (no backend) if (hasVite && hasReact && !hasExpress) { return { type: 'nodejs-vite-react', dockerizable: true, template: 'nodejs/vite-react', port: 80, // Nginx serves static files on port 80 entryPoint: null, buildCommand: 'npm run build', description: 'Vite + React SPA (served by Nginx)' }; } // Express server (with or without Socket.io) if (hasExpress) { return { type: 'nodejs-express', dockerizable: true, template: 'nodejs/express', port: detectPort(pkg) || 3000, entryPoint: detectEntryPoint(pkg, projectPath, 'express'), buildCommand: detectBuildCommand(pkg), description: hasSocketIO ? 'Express + Socket.io server' : 'Express server' }; } // Generic Node.js project (has package.json but no clear type) if (pkg.main || scripts.start) { return { type: 'nodejs-generic', dockerizable: true, template: 'nodejs/express', port: detectPort(pkg) || 3000, entryPoint: detectEntryPoint(pkg, projectPath, 'generic'), buildCommand: detectBuildCommand(pkg), description: 'Generic Node.js application' }; } // Has package.json but nothing to run return { type: 'nodejs-unknown', dockerizable: false, reason: 'No start script or main entry point found', template: null }; } /** * Detect the default port from package.json or environment */ function detectPort(pkg) { const scripts = pkg.scripts || {}; // Check start script for port const startScript = scripts.start || ''; const portMatch = startScript.match(/PORT[=\s]+(\d+)/i) || startScript.match(/-p\s+(\d+)/) || startScript.match(/--port\s+(\d+)/); if (portMatch) { return parseInt(portMatch[1], 10); } // Check for common port patterns in dev script const devScript = scripts.dev || ''; const devPortMatch = devScript.match(/:(\d{4})/); if (devPortMatch) { return parseInt(devPortMatch[1], 10); } // Default based on common frameworks if (pkg.dependencies?.['next']) return 3000; if (pkg.devDependencies?.vite) return 5173; return null; } /** * Detect the entry point file */ function detectEntryPoint(pkg, projectPath, type) { // Check package.json main field if (pkg.main) { return pkg.main; } // Check for common entry points const commonEntries = [ 'server.js', 'server.mjs', 'src/server.js', 'src/server.mjs', 'src/index.js', 'src/index.mjs', 'index.js', 'app.js', 'src/app.js' ]; for (const entry of commonEntries) { if (existsSync(join(projectPath, entry))) { return entry; } } // For SSR projects, look for server files if (type === 'ssr') { const ssrEntries = ['server.mjs', 'server.js', 'src/server.mjs', 'src/server.js']; for (const entry of ssrEntries) { if (existsSync(join(projectPath, entry))) { return entry; } } } // Default return 'index.js'; } /** * Detect if project has a build command */ function detectBuildCommand(pkg) { const scripts = pkg.scripts || {}; if (scripts.build) { return 'npm run build'; } return null; } /** * Get additional info about the Node.js project */ export function getNodeJSInfo(projectPath) { const packageJsonPath = join(projectPath, 'package.json'); if (!existsSync(packageJsonPath)) { return null; } const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); return { name: pkg.name, version: pkg.version, hasLockFile: existsSync(join(projectPath, 'package-lock.json')), hasYarnLock: existsSync(join(projectPath, 'yarn.lock')), hasPnpmLock: existsSync(join(projectPath, 'pnpm-lock.yaml')), nodeVersion: pkg.engines?.node || null, scripts: Object.keys(pkg.scripts || {}), dependencies: Object.keys(pkg.dependencies || {}), devDependencies: Object.keys(pkg.devDependencies || {}) }; }