|
| 1 | +import { spawn } from 'node:child_process'; |
| 2 | +import { performance } from 'node:perf_hooks'; |
| 3 | + |
| 4 | +const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; |
| 5 | +const startedAt = performance.now(); |
| 6 | + |
| 7 | +function seconds(ms) { |
| 8 | + return `${(ms / 1000).toFixed(2)}s`; |
| 9 | +} |
| 10 | + |
| 11 | +function runScript(script) { |
| 12 | + const start = performance.now(); |
| 13 | + process.stdout.write(`\n[build] ▶ ${script}\n`); |
| 14 | + |
| 15 | + return new Promise((resolve, reject) => { |
| 16 | + const child = spawn(npm, ['run', script], { |
| 17 | + cwd: process.cwd(), |
| 18 | + env: process.env, |
| 19 | + stdio: 'inherit', |
| 20 | + }); |
| 21 | + |
| 22 | + child.once('error', reject); |
| 23 | + child.once('exit', (code, signal) => { |
| 24 | + const elapsed = seconds(performance.now() - start); |
| 25 | + if (code === 0) { |
| 26 | + process.stdout.write(`[build] ✓ ${script} (${elapsed})\n`); |
| 27 | + resolve(); |
| 28 | + return; |
| 29 | + } |
| 30 | + |
| 31 | + reject( |
| 32 | + new Error( |
| 33 | + `${script} failed after ${elapsed}` + |
| 34 | + (signal ? ` (signal ${signal})` : ` (exit ${code ?? 'unknown'})`), |
| 35 | + ), |
| 36 | + ); |
| 37 | + }); |
| 38 | + }); |
| 39 | +} |
| 40 | + |
| 41 | +async function runParallel(label, scripts) { |
| 42 | + const start = performance.now(); |
| 43 | + process.stdout.write(`\n[build] ${label}: ${scripts.join(', ')}\n`); |
| 44 | + const results = await Promise.allSettled(scripts.map(runScript)); |
| 45 | + const failures = results.filter((result) => result.status === 'rejected'); |
| 46 | + |
| 47 | + if (failures.length > 0) { |
| 48 | + throw new AggregateError( |
| 49 | + failures.map((failure) => failure.reason), |
| 50 | + `${label} failed`, |
| 51 | + ); |
| 52 | + } |
| 53 | + |
| 54 | + process.stdout.write(`[build] ${label} complete (${seconds(performance.now() - start)})\n`); |
| 55 | +} |
| 56 | + |
| 57 | +async function frontendPipeline() { |
| 58 | + await runParallel('frontend preflight', [ |
| 59 | + 'orbit:test:content', |
| 60 | + 'orbit:test:clients', |
| 61 | + 'orbit:validate', |
| 62 | + 'production:config:check', |
| 63 | + 'og:generate', |
| 64 | + ]); |
| 65 | + |
| 66 | + await runScript('astro:build'); |
| 67 | + await runParallel('built artifact verification', ['site:test', 'browser:test']); |
| 68 | +} |
| 69 | + |
| 70 | +try { |
| 71 | + await frontendPipeline(); |
| 72 | + process.stdout.write(`\n[build] ✓ production frontend build (${seconds(performance.now() - startedAt)})\n`); |
| 73 | +} catch (error) { |
| 74 | + console.error('\n[build] ✗ production frontend build failed'); |
| 75 | + if (error instanceof AggregateError) { |
| 76 | + for (const failure of error.errors) console.error(failure); |
| 77 | + } else { |
| 78 | + console.error(error); |
| 79 | + } |
| 80 | + process.exitCode = 1; |
| 81 | +} |
0 commit comments