import { spawnSync } from 'node:child_process'; import { parseCapabilityAllowlist, releaseIdIsImmutable } from '../src/lib/server/release-gate'; type ReleaseCheckInput = { mode: string; releaseId: string; releaseEnvironment: string; dashboardImage: string; capabilityAllowlist: string | undefined; trackedFiles: readonly string[]; }; export function releaseSafetyErrors(input: ReleaseCheckInput): string[] { const errors: string[] = []; try { parseCapabilityAllowlist(input.capabilityAllowlist); } catch (error) { errors.push(error instanceof Error ? error.message : 'Invalid capability allowlist'); } const strictEnvironment = input.releaseEnvironment === 'production' || input.releaseEnvironment === 'staging'; if (!releaseIdIsImmutable(input.releaseId, strictEnvironment ? 'production' : 'development')) { errors.push('APP_RELEASE_ID must be a non-placeholder immutable identifier for this release environment'); } if (strictEnvironment && !input.dashboardImage.includes('@sha256:')) { errors.push('CONVEX_DASHBOARD_IMAGE must be digest-pinned for staging/production'); } if (input.mode === 'demo' && strictEnvironment) { errors.push('APP_MODE=demo is not permitted for a staging/production release'); } for (const file of input.trackedFiles) { if (/(^|\/)(?:\.env(?:\.|$)|.*\.pem$|.*\.key$)/i.test(file) && file !== '.env.example') { errors.push(`secret-like file is tracked: ${file}`); } } return errors; } function trackedFiles(): string[] { const result = spawnSync('git', ['ls-files'], { encoding: 'utf8' }); if (result.status !== 0) throw new Error('Unable to inspect tracked files'); return result.stdout.split('\n').filter(Boolean); } if (import.meta.main) { const releaseEnvironment = process.env.RELEASE_ENV ?? (process.env.APP_MODE === 'demo' ? 'fixture' : 'development'); const errors = releaseSafetyErrors({ mode: process.env.APP_MODE ?? 'live', releaseId: process.env.APP_RELEASE_ID ?? 'local', releaseEnvironment, dashboardImage: process.env.CONVEX_DASHBOARD_IMAGE ?? '', capabilityAllowlist: process.env.APP_CAPABILITY_ALLOWLIST, trackedFiles: trackedFiles(), }); if (errors.length) { console.error(`Release safety check failed (${errors.length} issue${errors.length === 1 ? '' : 's'})`); for (const message of errors) console.error(`- ${message}`); process.exitCode = 1; } else { const capabilities = parseCapabilityAllowlist(process.env.APP_CAPABILITY_ALLOWLIST).join(',') || 'none'; console.log(`Release safety check passed: environment=${releaseEnvironment}, mode=${process.env.APP_MODE ?? 'live'}, capabilities=${capabilities}`); } }