- 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>
106 lines
3.1 KiB
TypeScript
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;
|
|
}
|