multi-projects-runner / lib /detector.js
Karan6933's picture
Upload 35 files
05d91af verified
import { readdir, access, constants, readFile, writeFile } from 'node:fs/promises';
import { join, extname } from 'node:path';
async function checkFileExists(filepath) {
try {
await access(filepath, constants.F_OK);
return true;
} catch {
return false;
}
}
async function hasInstallLock(projectPath, type) {
try {
const lockPath = join(projectPath, '.project-lock');
const content = await readFile(lockPath, 'utf-8');
const locks = JSON.parse(content);
return locks[type] === true;
} catch {
return false;
}
}
async function setInstallLock(projectPath, type) {
const lockPath = join(projectPath, '.project-lock');
let locks = {};
try {
const content = await readFile(lockPath, 'utf-8');
locks = JSON.parse(content);
} catch {}
locks[type] = true;
await writeFile(lockPath, JSON.stringify(locks, null, 2));
}
async function clearInstallLock(projectPath, type) {
const lockPath = join(projectPath, '.project-lock');
let locks = {};
try {
const content = await readFile(lockPath, 'utf-8');
locks = JSON.parse(content);
} catch {}
if (locks[type]) {
delete locks[type];
await writeFile(lockPath, JSON.stringify(locks, null, 2));
}
}
async function hasStreamlit(projectPath) {
try {
const reqPath = join(projectPath, 'requirements.txt');
const content = await readFile(reqPath, 'utf-8');
return content.includes('streamlit');
} catch {
return false;
}
}
function detectPort(output) {
const patterns = [
/Local:\s+(?:https?:\/\/)?(?:[^:\s]+):(\d{4,5})/i,
/Local:\s+.*?(https?:\/\/)?[^:\s]+:(\d{4,5})/i,
/(?:localhost|127\.0\.0\.1|0\.0\.0\.0):(\d{4,5})/i,
/(?:port|listening on|running on)\s*[:=]?\s*(?:https?:\/\/)?(?:[^:\s]+:)?(\d{4,5})/i,
/\*\s+Running on.*:(\d{4,5})/i,
/You can now view your.*at.*:(\d{4,5})/i,
/Local URL:.*:(\d{4,5})/i,
/port\s*(\d{4,5})/i,
/PORT\s*(\d{4,5})/i,
/server.*(?:port|addr|address).*?(\d{4,5})/i
];
for (const pattern of patterns) {
const match = output.match(pattern);
if (match) {
return match[1] || match[2] || match[3];
}
}
return null;
}
const LANGUAGE_CONFIGS = {
nodejs: {
files: ['package.json'],
setup: async (projectPath, execAsync) => {
const nodeModulesPath = join(projectPath, 'node_modules');
const viteBin = join(projectPath, 'node_modules', '.bin', 'vite');
const hasNodeModules = await checkFileExists(nodeModulesPath);
const hasViteBin = await checkFileExists(viteBin);
if (!hasNodeModules || !hasViteBin) {
console.log(`[nodejs] Installing dependencies in ${projectPath}...`);
try {
await execAsync('npm install --include=dev', { cwd: projectPath });
console.log(`[nodejs] Dependencies installed successfully`);
} catch (error) {
console.error(`[nodejs] npm install failed:`, error.message);
throw error;
}
}
},
start: (projectPath, port) => {
const viteBin = join(projectPath, 'node_modules', '.bin', 'vite');
return {
command: 'node',
args: [viteBin, '--port', port, '--host', '0.0.0.0'],
cwd: projectPath,
env: { ...process.env, PORT: port, HOST: '0.0.0.0' }
};
},
detectPort
},
python: {
files: ['main.py', 'app.py', 'server.py', 'requirements.txt'],
setup: async (projectPath, execAsync, isStreamlit = false) => {
const venvPath = join(projectPath, 'venv');
const venvActivate = join(projectPath, 'venv', 'bin', 'activate');
const venvPython = join(projectPath, 'venv', 'bin', 'python');
const venvStreamlit = join(projectPath, 'venv', 'bin', 'streamlit');
const hasVenv = await checkFileExists(venvPath);
const hasVenvPython = await checkFileExists(venvPython);
const hasVenvStreamlit = await checkFileExists(venvStreamlit);
const reqPath = join(projectPath, 'requirements.txt');
const hasReq = await checkFileExists(reqPath);
const hasLock = await hasInstallLock(projectPath, 'pip');
const shouldDetectStreamlit = isStreamlit || await hasStreamlit(projectPath);
const needsSetup = !hasVenv || !hasVenvPython || (shouldDetectStreamlit && !hasVenvStreamlit) || !hasLock;
if (needsSetup) {
if (!hasVenv || !hasVenvPython) {
console.log(`[python] Creating virtual environment...`);
await execAsync('python3 -m venv venv', { cwd: projectPath });
}
if (hasReq) {
console.log(`[python] Installing requirements...`);
await execAsync('source venv/bin/activate && pip install --break-system-packages -r requirements.txt', {
cwd: projectPath,
shell: '/bin/bash'
});
} else if (shouldDetectStreamlit && !hasVenvStreamlit) {
console.log(`[python] Installing streamlit...`);
await execAsync('source venv/bin/activate && pip install --break-system-packages streamlit flask fastapi', {
cwd: projectPath,
shell: '/bin/bash'
});
}
await setInstallLock(projectPath, 'pip');
console.log(`[python] Python environment ready`);
}
},
start: (projectPath, port, isStreamlit = false) => {
const shouldDetectStreamlit = isStreamlit;
let cmd;
if (shouldDetectStreamlit) {
cmd = `source venv/bin/activate && streamlit run main.py --server.port ${port} --server.address 0.0.0.0 --server.headless true`;
} else {
cmd = `source venv/bin/activate && python main.py`;
}
return {
command: 'bash',
args: ['-c', cmd],
cwd: projectPath,
env: { ...process.env, PORT: port }
};
},
detectPort
},
go: {
files: ['go.mod'],
setup: async (projectPath, execAsync) => {
console.log(`[go] Downloading dependencies...`);
await execAsync('go mod download', { cwd: projectPath });
},
start: (projectPath, port) => ({
command: 'go',
args: ['run', 'main.go'],
cwd: projectPath,
env: { ...process.env, PORT: port }
}),
detectPort
},
rust: {
files: ['Cargo.toml'],
setup: async (projectPath, execAsync) => {
console.log(`[rust] Building dependencies...`);
await execAsync('cargo build', { cwd: projectPath });
},
start: (projectPath, port) => ({
command: 'cargo',
args: ['run'],
cwd: projectPath,
env: { ...process.env, PORT: port }
}),
detectPort
},
deno: {
files: ['deno.json', 'deno.jsonc'],
setup: async () => {},
start: (projectPath, port) => ({
command: 'deno',
args: ['run', '--allow-net', 'main.ts'],
cwd: projectPath,
env: { ...process.env, PORT: port }
}),
detectPort
},
static: {
files: ['index.html'],
setup: async () => {},
start: (projectPath, port) => ({
command: 'npx',
args: ['serve', '-p', port, '-s', projectPath],
cwd: projectPath
}),
detectPort
}
};
export async function detectLanguage(projectPath) {
const entries = await readdir(projectPath, { withFileTypes: true });
const files = entries.map(e => e.name);
for (const [lang, config] of Object.entries(LANGUAGE_CONFIGS)) {
for (const file of config.files) {
if (files.includes(file)) {
if (lang === 'python') {
const isStreamlit = await hasStreamlit(projectPath);
return {
language: lang,
config: config,
detectedFile: file,
isStreamlit
};
}
return {
language: lang,
config: config,
detectedFile: file
};
}
}
}
const extMap = {
'.js': 'nodejs',
'.ts': 'nodejs',
'.py': 'python',
'.go': 'go',
'.rs': 'rust',
'.html': 'static',
'.htm': 'static'
};
for (const file of files) {
const ext = extname(file).toLowerCase();
if (extMap[ext]) {
return {
language: extMap[ext],
config: LANGUAGE_CONFIGS[extMap[ext]],
detectedFile: file
};
}
}
return null;
}
export { checkFileExists, LANGUAGE_CONFIGS };