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 name: 'TypeScript',25 cmd: ['npx', 'tsc', '--noEmit'],26 },27 {28 name: 'Tests',29 cmd: ['npm', 'test', '--', '--reporter=verbose'],30 },31 {32 name: 'Chrome build',33 cmd: ['npm', 'run', 'build:chrome'],34 },35 {36 name: 'Firefox build',37 cmd: ['npm', 'run', 'build:firefox'],38 },39 {40 name: 'Mobile build',41 cmd: ['npm', 'run', 'build:mobile'],42 },43];4445function run(step: Step): { ok: boolean; output: string; duration: number } {46 const start = Date.now();47 const result = spawnSync(step.cmd, {48 cwd: step.cwd ?? process.cwd(),49 env: process.env,50 stdout: 'pipe',51 stderr: 'pipe',52 });53 const duration = Date.now() - start;54 const ok = result.exitCode === 0;55 const output = [56 result.stdout?.toString().trim(),57 result.stderr?.toString().trim(),58 ].filter(Boolean).join('\n');5960 return { ok, output, duration };61}6263function formatDuration(ms: number): string {64 if (ms < 1000) return `${ms}ms`;65 return `${(ms / 1000).toFixed(1)}s`;66}6768function printStep(name: string, ok: boolean, duration: number) {69 const icon = ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;70 const time = `${DIM}${formatDuration(duration)}${RESET}`;71 console.log(` ${icon} ${name.padEnd(20)} ${time}`);72}7374async function main() {75 console.log();76 console.log(`${BOLD}peach-browser CI${RESET}`);77 console.log(`${DIM}${new Date().toLocaleTimeString()}${RESET}`);78 console.log();7980 let failed = false;81 const failures: { name: string; output: string }[] = [];8283 for (const step of steps) {84 process.stdout.write(` ${DIM}…${RESET} ${step.name.padEnd(20)}\r`);85 const result = run(step);86 printStep(step.name, result.ok, result.duration);8788 if (!result.ok) {89 failed = true;90 failures.push({ name: step.name, output: result.output });91 break; 92 }93 }9495 console.log();9697 if (!failed) {98 console.log(`${GREEN}${BOLD}All checks passed.${RESET}`);99 console.log();100101 102 const chromeManifest = 'dist-chrome/manifest.json';103 if (existsSync(chromeManifest)) {104 const manifest = JSON.parse(await Bun.file(chromeManifest).text());105 console.log(`${DIM}Version: ${manifest.version}${RESET}`);106 }107 } else {108 console.log(`${RED}${BOLD}Failed: ${failures[0].name}${RESET}`);109 console.log();110111 112 const output = failures[0].output;113 if (output) {114 115 const lines = output.split('\n');116 const relevant = lines.slice(-30).join('\n');117 console.log(`${DIM}${relevant}${RESET}`);118 }119120 console.log();121 process.exit(1);122 }123}124125main();