bytelyst-devops-tools/dashboard/backend/src/modules/deployments/orchestrator.ts
Hermes VM 31b414d62b fix: systematic bug fixes — code-quality parser, env key, config warnings, auth cleanup, deployment safety
- code-quality/repository.ts: fix tsErrorMatch[3] → [4] for type field (group 3 is column, 4 is error|warning)
- code-quality/repository.ts: fix ESLint regex to make rule brackets optional (not all formatters include them)
- code-quality/repository.ts: fix Vitest test count — parse 'Tests' line (individual tests) instead of 'Test Files' (file count); improve Jest regex to capture pass/fail independently
- env/repository.ts: replace raw process.env.ENCRYPTION_KEY with config.ENCRYPTION_KEY so the validated default flows through a single source of truth
- config.ts: add startup console.warn when CSRF_SECRET or ENCRYPTION_KEY are using insecure defaults
- deployments/orchestrator.ts: refactor runDeploymentScript to use try/catch/finally — deployment record is now always written in the finally block, preventing zombie 'running' states if updateDeployment itself throws
- auth.tsx: remove dead 'user &&' guard (user is always truthy after the !user check above); remove debug console.log calls, keep console.error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:53:20 +00:00

106 lines
3.1 KiB
TypeScript

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<string> {
// 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;
}