import { spawn } from 'node:child_process'; import http from 'node:http'; import path from 'node:path'; const port = Number(process.env.RELEASE_BUILDER_PORT ?? 15100); const allowedRoots = [ '/srv/core/repository', '/srv/core/profile-worktrees', '/srv/core/release-worktrees', '/workspace/core2026', ].map((root) => path.resolve(root)); const allowedEnvName = /^(?:CI|NODE_OPTIONS|PROFILE_FRONTEND_BUILD_NODE_OPTIONS|RAYON_NUM_THREADS|RELEASE_TURBO_CONCURRENCY|TURBO_CACHE_DIR|TZ|VITE_[A-Z0-9_]+)$/; const maxOutput = 64 * 1024; let queue = Promise.resolve(); const appendTail = (current, chunk) => `${current}${String(chunk)}`.slice(-maxOutput); const isWithin = (candidate, root) => { const relative = path.relative(root, candidate); return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative)); }; const validateCommand = (raw) => { if (!raw || typeof raw !== 'object' || !Array.isArray(raw.args) || typeof raw.cwd !== 'string') { throw new Error('Each release build command must provide command, args, and cwd.'); } const cwd = path.resolve(raw.cwd); if (!allowedRoots.some((root) => isWithin(cwd, root))) { throw new Error(`Release build cwd is outside the shared Core workspaces: ${cwd}`); } const args = raw.args.map((value) => String(value)); if (raw.command === 'pnpm') { const install = args[0] === 'install' && args.includes('--frozen-lockfile'); const turbo = args[0] === 'exec' && args[1] === 'turbo' && args[2] === 'run'; const packageTask = args[0] === '--filter' && args.length >= 3 && ['build', 'prisma:generate'].includes(args[2]); if (!install && !turbo && !packageTask) throw new Error('Unsupported pnpm release build command.'); } else if (raw.command === 'node') { if (args[0] !== 'tools/build-scripts/materialize-profile-frontend.mjs' || args.length !== 2) { throw new Error('Unsupported Node release build command.'); } } else { throw new Error(`Unsupported release build executable: ${raw.command}`); } const overrides = Object.fromEntries( Object.entries(raw.env ?? {}).filter( ([name, value]) => allowedEnvName.test(name) && typeof value === 'string', ), ); return { command: raw.command, args, cwd, env: { ...process.env, ...overrides }, eventCommand: { command: raw.command, args, cwd, env: overrides }, }; }; const terminate = (child, signal) => { if (!child.pid) return; try { process.kill(-child.pid, signal); } catch { try { child.kill(signal); } catch { // The build already exited. } } }; const runCommand = (command, emit, signal) => new Promise((resolve) => { emit({ event: { type: 'COMMAND_START', command: command.eventCommand } }); const child = spawn(command.command, command.args, { cwd: command.cwd, env: command.env, stdio: ['ignore', 'pipe', 'pipe'], detached: true, }); let output = ''; let aborted = false; let spawnError = null; const abort = () => { if (aborted) return; aborted = true; terminate(child, 'SIGTERM'); const killTimer = setTimeout(() => terminate(child, 'SIGKILL'), 5000); killTimer.unref(); }; signal.addEventListener('abort', abort, { once: true }); for (const stream of ['stdout', 'stderr']) { child[stream].on('data', (chunk) => { output = appendTail(output, chunk); for (const message of String(chunk).split(/\r?\n/).filter(Boolean)) { emit({ event: { type: 'OUTPUT', stream, message: message.slice(0, 2000) } }); } }); } child.on('error', (error) => { spawnError = error; output = appendTail(output, error.message); }); child.on('close', (exitCode) => { signal.removeEventListener('abort', abort); emit({ event: { type: 'COMMAND_END', command: command.eventCommand, exitCode: spawnError ? null : exitCode } }); resolve({ ok: !aborted && !spawnError && exitCode === 0, exitCode: spawnError ? null : exitCode, output, ...(aborted ? { aborted: true } : {}), }); }); }); const runJob = async (commands, emit, signal) => { let output = ''; for (const command of commands) { if (signal.aborted) return { ok: false, exitCode: null, output, aborted: true }; const result = await runCommand(command, emit, signal); output = appendTail(output, result.output); if (!result.ok) return { ...result, output }; } return { ok: true, exitCode: 0, output }; }; const readJson = async (request) => { let body = ''; for await (const chunk of request) { body += chunk; if (body.length > 256 * 1024) throw new Error('Release build request is too large.'); } return JSON.parse(body); }; const server = http.createServer(async (request, response) => { if (request.method === 'GET' && request.url === '/healthz') { response.writeHead(200, { 'content-type': 'text/plain' }); response.end('ok'); return; } if (request.method !== 'POST' || request.url !== '/v1/builds') { response.writeHead(404).end(); return; } try { const payload = await readJson(request); if (!Array.isArray(payload.commands) || payload.commands.length === 0 || payload.commands.length > 12) { throw new Error('Release build request must contain 1-12 commands.'); } const commands = payload.commands.map(validateCommand); response.writeHead(200, { 'content-type': 'application/x-ndjson', 'cache-control': 'no-store', 'x-content-type-options': 'nosniff', }); response.flushHeaders(); const abortController = new AbortController(); request.once('aborted', () => abortController.abort()); response.once('close', () => { if (!response.writableEnded) abortController.abort(); }); const emit = (message) => { if (!response.writableEnded) response.write(`${JSON.stringify(message)}\n`); }; const execution = queue.then(() => runJob(commands, emit, abortController.signal)); queue = execution.then(() => undefined, () => undefined); const result = await execution; emit({ result }); response.end(); } catch (error) { if (!response.headersSent) response.writeHead(400, { 'content-type': 'text/plain' }); response.end(error instanceof Error ? error.message : String(error)); } }); server.listen(port, '0.0.0.0', () => { console.log(`[release-builder] listening on ${port}`); }); const shutdown = () => server.close(() => process.exit(0)); process.once('SIGINT', shutdown); process.once('SIGTERM', shutdown);