const fs = require('fs'); const path = require('path'); class ProjectScanner { constructor(rootPath) { this.rootPath = rootPath; } async scan() { const projects = []; try { const entries = fs.readdirSync(this.rootPath, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory()) continue; const projectPath = path.join(this.rootPath, entry.name); const projectInfo = this.analyzeProject(projectPath, entry.name); if (projectInfo) { projects.push(projectInfo); } } } catch (err) { console.error('Failed to scan projects:', err); } return projects; } analyzeProject(projectPath, name) { const hasDockerfile = fs.existsSync(path.join(projectPath, 'Dockerfile')); const hasDockerCompose = fs.existsSync(path.join(projectPath, 'docker-compose.yml')); const hasBuildScript = fs.existsSync(path.join(projectPath, 'build-image-tar.ps1')); const hasDeployScript = fs.existsSync(path.join(projectPath, 'deploy-docker-auto.ps1')); const hasDeploymentConfig = fs.existsSync(path.join(projectPath, 'docker-deployment.json')); const hasDockerIgnore = fs.existsSync(path.join(projectPath, '.dockerignore')); // Find tar files let tarFile = null; try { const files = fs.readdirSync(projectPath); tarFile = files.find(f => f.endsWith('.tar')); } catch (err) { // ignore } // Read deployment config if exists let deploymentConfig = null; if (hasDeploymentConfig) { try { deploymentConfig = JSON.parse(fs.readFileSync(path.join(projectPath, 'docker-deployment.json'), 'utf-8')); } catch (err) { // ignore } } // Determine docker status let dockerStatus = 'none'; if (hasDockerfile && hasDockerCompose) { dockerStatus = 'configured'; } else if (hasDockerfile || hasDockerCompose) { dockerStatus = 'partial'; } return { name, path: projectPath, hasDockerfile, hasDockerCompose, hasBuildScript, hasDeployScript, hasDeploymentConfig, hasDockerIgnore, tarFile, deploymentConfig, dockerStatus, serverId: deploymentConfig?.deployment?.serverId || null, remotePath: deploymentConfig?.deployment?.targetPath || `~/containers/${name}` }; } // Scan subdirectories too (for monorepos like AudioSphere) async scanDeep(maxDepth = 2) { const projects = []; await this._scanRecursive(this.rootPath, projects, 0, maxDepth); return projects; } async _scanRecursive(currentPath, projects, depth, maxDepth) { if (depth > maxDepth) return; try { const entries = fs.readdirSync(currentPath, { withFileTypes: true }); // Check if this directory has a Dockerfile const hasDockerfile = entries.some(e => e.name === 'Dockerfile'); if (hasDockerfile) { const name = path.relative(this.rootPath, currentPath).replace(/\\/g, '/'); const projectInfo = this.analyzeProject(currentPath, name); if (projectInfo) { projects.push(projectInfo); } } // Recurse into subdirectories for (const entry of entries) { if (!entry.isDirectory()) continue; if (entry.name.startsWith('.')) continue; if (entry.name === 'node_modules') continue; if (entry.name === 'dist') continue; if (entry.name === 'build') continue; await this._scanRecursive(path.join(currentPath, entry.name), projects, depth + 1, maxDepth); } } catch (err) { // ignore permission errors etc } } } module.exports = { ProjectScanner };