import { exec } from 'child_process'; import { promisify } from 'util'; import { join } from 'path'; import type { Service } from '../services/types.js'; import { createDeployment, updateDeployment } from './repository.js'; import { productId } from '../../lib/config.js'; const execAsync = promisify(exec); export async function triggerDeployment(service: Service, triggeredBy: string): Promise { // Create deployment record const deployment = await createDeployment({ serviceId: service.id, version: 'pending', triggeredBy, productId, }); const deploymentId = deployment.id; // Trigger bash script asynchronously runDeploymentScript(service, deploymentId).catch(error => { console.error(`Deployment ${deploymentId} failed:`, error); }); return deploymentId; } async function runDeploymentScript(service: Service, deploymentId: string) { const scriptDir = join(process.cwd(), '../../'); // Go to bytelyst-devops-tools root const scriptPath = join(scriptDir, service.scriptPath); let finalStatus: 'success' | 'failed' = 'failed'; let logs = ''; let version: string | undefined; try { const { stdout, stderr } = await execAsync(`bash ${scriptPath}`, { cwd: scriptDir, timeout: 300000, // 5 minutes env: { ...process.env, // Add any deployment-specific env vars if needed }, }); logs = `STDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`; finalStatus = 'success'; version = extractVersion(stdout + stderr) || 'unknown'; // Update service status const { getServiceById, updateService } = await import('../services/repository.js'); const svc = await getServiceById(service.id); if (svc) { await updateService(service.id, { status: 'up', lastDeployedAt: new Date().toISOString(), version: version || svc.version, }); } } catch (error: any) { logs = error instanceof Error ? `ERROR: ${error.message}\n\n${(error as any).stdout ? `STDOUT:\n${(error as any).stdout}\n\n` : ''}${(error as any).stderr ? `STDERR:\n${(error as any).stderr}` : ''}` : String(error); // Update service status to down const { getServiceById, updateService } = await import('../services/repository.js'); const svc = await getServiceById(service.id); if (svc) { await updateService(service.id, { status: 'down', }); } } finally { // Always write final status — ensures the deployment never gets stuck in 'running' try { await updateDeployment(deploymentId, { status: finalStatus, logs, completedAt: new Date().toISOString(), ...(version ? { version } : {}), }); } catch (updateError) { console.error(`Failed to persist final deployment status for ${deploymentId}:`, updateError); } } } function extractVersion(logs: string): string | null { // Try multiple version patterns const patterns = [ /version[:\s]+([0-9.]+)/i, /v([0-9.]+\.[0-9.]+)/, /deployed.*?([0-9]+\.[0-9]+)/i, ]; for (const pattern of patterns) { const match = logs.match(pattern); if (match && match[1]) { return match[1]; } } return null; }