import fs from 'fs'; import fsp from 'fs/promises'; import path from 'path'; import { exec } from 'child_process'; export interface TemplateJson { name: string; description: string; language: string; runtime: string; run: string; port: number; install: boolean; } export interface InstallResult { status: 'installing' | 'done' | 'error'; logs: string[]; } const TEMPLATES_DIR = path.join(process.cwd(), 'templates'); const PROJECTS_DIR = path.join(process.cwd(), 'projects'); export function getTemplateList(): { id: string; meta: TemplateJson }[] { if (!fs.existsSync(TEMPLATES_DIR)) return []; const entries = fs.readdirSync(TEMPLATES_DIR, { withFileTypes: true }); return entries .filter((e) => e.isDirectory()) .map((e) => { const metaPath = path.join(TEMPLATES_DIR, e.name, 'template.json'); if (!fs.existsSync(metaPath)) return null; try { const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8')) as TemplateJson; return { id: e.name, meta }; } catch { return null; } }) .filter((t): t is { id: string; meta: TemplateJson } => t !== null); } export async function copyTemplateToProject( templateId: string, projectId: string, ): Promise { const sanitized = templateId.replace(/[^a-zA-Z0-9_-]/g, ''); const templatePath = path.join(TEMPLATES_DIR, sanitized); const projectPath = path.join(PROJECTS_DIR, projectId); if (!fs.existsSync(templatePath)) { throw new Error(`Template "${sanitized}" not found`); } await fsp.mkdir(projectPath, { recursive: true }); const copyDir = async (src: string, dest: string): Promise => { const entries = await fsp.readdir(src, { withFileTypes: true }); for (const entry of entries) { if (entry.name === 'template.json') continue; const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { await fsp.mkdir(destPath, { recursive: true }); await copyDir(srcPath, destPath); } else { await fsp.copyFile(srcPath, destPath); } } }; await copyDir(templatePath, projectPath); } export function runInstallScript( projectId: string, onLog: (line: string) => void, ): Promise { return new Promise((resolve, reject) => { const projectPath = path.join(PROJECTS_DIR, projectId); const installScript = path.join(projectPath, 'install.sh'); if (!fs.existsSync(installScript)) { onLog('[Exocore] No install script found — skipping.\n'); resolve(); return; } const child = exec( `bash install.sh`, { cwd: projectPath, env: { ...process.env, FORCE_COLOR: '1' }, timeout: 300_000, }, ); child.stdout?.on('data', (d: Buffer) => onLog(d.toString())); child.stderr?.on('data', (d: Buffer) => onLog(d.toString())); child.on('error', (err) => { onLog(`[Exocore] Install error: ${err.message}\n`); reject(err); }); child.on('close', (code) => { if (code === 0 || code === null) { onLog('[Exocore] Installation complete!\n'); } else { onLog(`[Exocore] Install script exited with code ${code} — project was still created.\n`); } resolve(); }); }); }