| 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<void> { |
| 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<void> => { |
| 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<void> { |
| 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(); |
| }); |
| }); |
| } |
|
|