import { spawn } from 'node:child_process'; import process from 'node:process'; import { REBUILD_EXIT_CODES, getRebuildExitSymbol } from './rebuild-codes.ts'; type RebuildStage = 'sync' | 'build' | 'deploy' | 'bootstrap'; type StepResult = { ok: boolean; exitCode: number; stage: RebuildStage; durationMs: number; tail: string[]; }; async function main() { const startedAt = new Date(); console.log(`[rebuild] start ${startedAt.toISOString()}`); const syncResult = await runNpmStep({ stage: 'sync', script: 'content:sync', failureCode: REBUILD_EXIT_CODES.SYNC_FAILED, }); if (!syncResult.ok) { return finish(syncResult, startedAt); } const buildResult = await runNpmStep({ stage: 'build', script: 'build', failureCode: REBUILD_EXIT_CODES.BUILD_FAILED, }); return finish(buildResult, startedAt); } async function runNpmStep(input: { stage: RebuildStage; script: string; failureCode: number; }): Promise { console.log(`[rebuild] stage=${input.stage} command="npm run ${input.script}"`); const command = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const startedAt = Date.now(); const tailBuffer = createTailBuffer(40); return new Promise((resolve) => { const child = spawn(command, ['run', input.script], { cwd: process.cwd(), env: process.env, stdio: ['ignore', 'pipe', 'pipe'], }); child.stdout.on('data', (chunk) => { const text = chunk.toString(); process.stdout.write(text); tailBuffer.push(text); }); child.stderr.on('data', (chunk) => { const text = chunk.toString(); process.stderr.write(text); tailBuffer.push(text); }); child.on('error', (error) => { const durationMs = Date.now() - startedAt; const exitCode = classifySpawnError(error); tailBuffer.push(`[rebuild] failed to spawn command: ${error.message}\n`); resolve({ ok: false, exitCode, stage: input.stage, durationMs, tail: tailBuffer.read(), }); }); child.on('close', (code) => { const durationMs = Date.now() - startedAt; const normalizedExitCode = code === 0 ? REBUILD_EXIT_CODES.SUCCESS : normalizeStageExitCode(input.failureCode, code); resolve({ ok: normalizedExitCode === REBUILD_EXIT_CODES.SUCCESS, exitCode: normalizedExitCode, stage: input.stage, durationMs, tail: tailBuffer.read(), }); }); }); } function finish(result: StepResult, startedAt: Date) { const finishedAt = new Date(); const payload = { ok: result.ok, stage: result.stage, code: result.exitCode, symbol: getRebuildExitSymbol(result.exitCode), startedAt: startedAt.toISOString(), finishedAt: finishedAt.toISOString(), durationMs: finishedAt.getTime() - startedAt.getTime(), failedStepDurationMs: result.durationMs, logTail: result.tail, }; console.log(`REBUILD_RESULT ${JSON.stringify(payload)}`); if (!result.ok) { console.error( `[rebuild] failed stage=${result.stage} code=${result.exitCode} symbol=${payload.symbol}`, ); } else { console.log(`[rebuild] success code=0 symbol=${payload.symbol}`); } process.exitCode = result.exitCode; } function normalizeStageExitCode(fallbackCode: number, childCode: number | null) { if (childCode == null) { return fallbackCode; } if (Object.values(REBUILD_EXIT_CODES).includes(childCode as never)) { return childCode; } return fallbackCode; } function classifySpawnError(error: NodeJS.ErrnoException) { if (error.code === 'ENOENT') { return REBUILD_EXIT_CODES.CONFIG_INVALID; } return REBUILD_EXIT_CODES.UNKNOWN_ERROR; } function createTailBuffer(maxLines: number) { const lines: string[] = []; return { push(chunk: string) { const normalized = chunk.replace(/\r\n/g, '\n'); for (const line of normalized.split('\n')) { if (!line.trim()) { continue; } lines.push(line); } while (lines.length > maxLines) { lines.shift(); } }, read() { return [...lines]; }, }; } main().catch((error) => { const message = error instanceof Error ? error.message : String(error); console.error('[rebuild] unhandled failure'); console.error(message); console.log( `REBUILD_RESULT ${JSON.stringify({ ok: false, stage: 'bootstrap', code: REBUILD_EXIT_CODES.UNKNOWN_ERROR, symbol: getRebuildExitSymbol(REBUILD_EXIT_CODES.UNKNOWN_ERROR), startedAt: new Date().toISOString(), finishedAt: new Date().toISOString(), durationMs: 0, failedStepDurationMs: 0, logTail: [message], })}`, ); process.exitCode = REBUILD_EXIT_CODES.UNKNOWN_ERROR; });