123456import { spawnSync } from 'bun';7import { existsSync } from 'node:fs';8import { resolveBin } from './release-lib';910const RESET = '\x1b[0m';11const GREEN = '\x1b[32m';12const RED = '\x1b[31m';13const BOLD = '\x1b[1m';14const DIM = '\x1b[2m';15const RUN_FIREFOX_SENDER_ONLY = process.env.PEACH_E2E_FIREFOX === '1';1617interface Step {18 name: string;19 cmd: string[] | (() => string[]);20 cwd?: string;21}2223const steps: Step[] = [24 {25 26 27 28 name: 'Lockfile freshness',29 cmd: ['bun', 'install', '--frozen-lockfile'],30 },31 {32 name: 'TypeScript',33 cmd: () => [resolveBin('tsc'), '--noEmit'],34 },35 {36 name: 'Lint',37 cmd: ['npm', 'run', 'lint'],38 },39 {40 41 42 name: 'Noise WASM provenance',43 cmd: ['node', 'scripts/check-noise-core.mjs'],44 },45 {46 name: 'Chrome build',47 cmd: ['npm', 'run', 'build:chrome'],48 },49 {50 name: 'Firefox build',51 cmd: ['npm', 'run', 'build:firefox'],52 },53 {54 55 name: 'Release browser gate',56 cmd: ['bun', 'run', 'scripts/run-release-browser-gate.ts'],57 },58 {59 name: 'Tests',60 cmd: ['npm', 'test', '--', '--reporter=verbose'],61 },62 ...(RUN_FIREFOX_SENDER_ONLY63 ? [{64 name: 'Firefox sender context',65 cmd: ['npm', 'run', 'test:firefox:sender'],66 }]67 : []),68];6970function run(step: Step): { ok: boolean; output: string; duration: number } {71 const start = Date.now();72 const cmd = typeof step.cmd === 'function' ? step.cmd() : step.cmd;73 const result = spawnSync(cmd, {74 cwd: step.cwd ?? process.cwd(),75 env: process.env,76 stdout: 'pipe',77 stderr: 'pipe',78 });79 const duration = Date.now() - start;80 const ok = result.exitCode === 0;81 const output = [82 result.stdout?.toString().trim(),83 result.stderr?.toString().trim(),84 ].filter(Boolean).join('\n');8586 return { ok, output, duration };87}8889function formatDuration(ms: number): string {90 if (ms < 1000) return `${ms}ms`;91 return `${(ms / 1000).toFixed(1)}s`;92}9394function printStep(name: string, ok: boolean, duration: number) {95 const icon = ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;96 const time = `${DIM}${formatDuration(duration)}${RESET}`;97 console.log(` ${icon} ${name.padEnd(20)} ${time}`);98}99100async function main() {101 console.log();102 console.log(`${BOLD}peach-browser CI${RESET}`);103 console.log(`${DIM}${new Date().toLocaleTimeString()}${RESET}`);104 console.log();105106 let failed = false;107 const failures: { name: string; output: string }[] = [];108109 for (const step of steps) {110 process.stdout.write(` ${DIM}…${RESET} ${step.name.padEnd(20)}\r`);111 const result = run(step);112 printStep(step.name, result.ok, result.duration);113114 if (!result.ok) {115 failed = true;116 failures.push({ name: step.name, output: result.output });117 break; 118 }119 }120121 console.log();122123 if (!failed) {124 console.log(`${GREEN}${BOLD}All checks passed.${RESET}`);125 console.log();126127 128 const chromeManifest = 'dist-chrome/manifest.json';129 if (existsSync(chromeManifest)) {130 const manifest = JSON.parse(await Bun.file(chromeManifest).text());131 console.log(`${DIM}Version: ${manifest.version}${RESET}`);132 }133 } else {134 console.log(`${RED}${BOLD}Failed: ${failures[0].name}${RESET}`);135 console.log();136137 138 const output = failures[0].output;139 if (output) {140 141 const lines = output.split('\n');142 const relevant = lines.slice(-30).join('\n');143 console.log(`${DIM}${relevant}${RESET}`);144 }145146 console.log();147 process.exit(1);148 }149}150151main();