123456import { spawnSync } from 'bun';7import { existsSync } from 'fs';89const RESET = '\x1b[0m';10const GREEN = '\x1b[32m';11const RED = '\x1b[31m';12const YELLOW = '\x1b[33m';13const BOLD = '\x1b[1m';14const DIM = '\x1b[2m';1516interface Step {17 name: string;18 cmd: string[];19 cwd?: string;20}2122const steps: Step[] = [23 {24 25 26 27 name: 'Lockfile freshness',28 cmd: ['bun', 'install', '--frozen-lockfile'],29 },30 {31 name: 'TypeScript',32 cmd: ['npx', 'tsc', '--noEmit'],33 },34 {35 name: 'Lint',36 cmd: ['npm', 'run', 'lint'],37 },38 {39 name: 'Tests',40 cmd: ['npm', 'test', '--', '--reporter=verbose'],41 },42 {43 44 45 name: 'Noise WASM provenance',46 cmd: ['node', 'scripts/check-noise-core.mjs'],47 },48 {49 name: 'Chrome build',50 cmd: ['npm', 'run', 'build:chrome'],51 },52 {53 name: 'Firefox build',54 cmd: ['npm', 'run', 'build:firefox'],55 },56];5758function run(step: Step): { ok: boolean; output: string; duration: number } {59 const start = Date.now();60 const result = spawnSync(step.cmd, {61 cwd: step.cwd ?? process.cwd(),62 env: process.env,63 stdout: 'pipe',64 stderr: 'pipe',65 });66 const duration = Date.now() - start;67 const ok = result.exitCode === 0;68 const output = [69 result.stdout?.toString().trim(),70 result.stderr?.toString().trim(),71 ].filter(Boolean).join('\n');7273 return { ok, output, duration };74}7576function formatDuration(ms: number): string {77 if (ms < 1000) return `${ms}ms`;78 return `${(ms / 1000).toFixed(1)}s`;79}8081function printStep(name: string, ok: boolean, duration: number) {82 const icon = ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;83 const time = `${DIM}${formatDuration(duration)}${RESET}`;84 console.log(` ${icon} ${name.padEnd(20)} ${time}`);85}8687async function main() {88 console.log();89 console.log(`${BOLD}peach-browser CI${RESET}`);90 console.log(`${DIM}${new Date().toLocaleTimeString()}${RESET}`);91 console.log();9293 let failed = false;94 const failures: { name: string; output: string }[] = [];9596 for (const step of steps) {97 process.stdout.write(` ${DIM}…${RESET} ${step.name.padEnd(20)}\r`);98 const result = run(step);99 printStep(step.name, result.ok, result.duration);100101 if (!result.ok) {102 failed = true;103 failures.push({ name: step.name, output: result.output });104 break; 105 }106 }107108 console.log();109110 if (!failed) {111 console.log(`${GREEN}${BOLD}All checks passed.${RESET}`);112 console.log();113114 115 const chromeManifest = 'dist-chrome/manifest.json';116 if (existsSync(chromeManifest)) {117 const manifest = JSON.parse(await Bun.file(chromeManifest).text());118 console.log(`${DIM}Version: ${manifest.version}${RESET}`);119 }120 } else {121 console.log(`${RED}${BOLD}Failed: ${failures[0].name}${RESET}`);122 console.log();123124 125 const output = failures[0].output;126 if (output) {127 128 const lines = output.split('\n');129 const relevant = lines.slice(-30).join('\n');130 console.log(`${DIM}${relevant}${RESET}`);131 }132133 console.log();134 process.exit(1);135 }136}137138main();