123456import { spawnSync } from 'bun';7import { existsSync } from 'node:fs';8import { resolveBin } from './release-lib';91011const RESET = '\x1b[0m';12const GREEN = '\x1b[32m';13const RED = '\x1b[31m';14const BOLD = '\x1b[1m';15const DIM = '\x1b[2m';16171819const RUN_BROWSER_GATE = process.env.CI === 'true' || process.env.PEACH_RELEASE_BROWSER_GATE === '1';20const RUN_FIREFOX_SENDER_ONLY = process.env.PEACH_E2E_FIREFOX === '1' && !RUN_BROWSER_GATE;2122interface Step {23 name: string;24 cmd: string[] | (() => string[]);25 cwd?: string;26}2728const steps: Step[] = [29 {30 31 32 33 name: 'Lockfile freshness',34 cmd: ['bun', 'install', '--frozen-lockfile'],35 },36 {37 name: 'TypeScript',38 cmd: () => [resolveBin('tsc'), '--noEmit'],39 },40 {41 name: 'Lint',42 cmd: ['npm', 'run', 'lint'],43 },44 {45 46 47 name: 'Noise WASM provenance',48 cmd: ['node', 'scripts/check-noise-core.mjs'],49 },50 {51 name: 'Chrome build',52 cmd: ['npm', 'run', 'build:chrome'],53 },54 {55 name: 'Firefox build',56 cmd: ['npm', 'run', 'build:firefox'],57 },58 {59 60 61 62 name: 'Tests',63 cmd: ['npm', 'test', '--', '--reporter=verbose'],64 },65 ...(RUN_FIREFOX_SENDER_ONLY66 ? [{67 name: 'Firefox sender context',68 cmd: ['npm', 'run', 'test:firefox:sender'],69 }]70 : []),71 72 73 ...(RUN_BROWSER_GATE74 ? [{75 name: 'Release browser gate',76 cmd: ['bun', 'run', 'scripts/run-release-browser-gate.ts'],77 }]78 : []),79];8081function run(step: Step): { ok: boolean; output: string; duration: number } {82 const start = Date.now();83 const cmd = typeof step.cmd === 'function' ? step.cmd() : step.cmd;84 const result = spawnSync(cmd, {85 cwd: step.cwd ?? process.cwd(),86 env: process.env,87 stdout: 'pipe',88 stderr: 'pipe',89 });90 const duration = Date.now() - start;91 const ok = result.exitCode === 0;92 const output = [93 result.stdout?.toString().trim(),94 result.stderr?.toString().trim(),95 ].filter(Boolean).join('\n');9697 return { ok, output, duration };98}99100function formatDuration(ms: number): string {101 if (ms < 1000) return `${ms}ms`;102 return `${(ms / 1000).toFixed(1)}s`;103}104105function printStep(name: string, ok: boolean, duration: number) {106 const icon = ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;107 const time = `${DIM}${formatDuration(duration)}${RESET}`;108 console.log(` ${icon} ${name.padEnd(20)} ${time}`);109}110111async function main() {112 console.log();113 console.log(`${BOLD}peach-browser CI${RESET}`);114 console.log(`${DIM}${new Date().toLocaleTimeString()}${RESET}`);115 console.log();116117 let failed = false;118 const failures: { name: string; output: string }[] = [];119120 121 122 if (RUN_BROWSER_GATE) {123 console.log(`${DIM}Browser gate enabled — reporting runtime versions:${RESET}`);124 const chromedriver = spawnSync({125 cmd: [resolveBin('playwright'), '--version'],126 stdout: 'pipe',127 stderr: 'pipe',128 });129 if (chromedriver.stdout) console.log(`${DIM} Playwright: ${chromedriver.stdout.toString().trim()}${RESET}`);130 if (process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH) {131 console.log(`${DIM} Chrome: ${process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH}${RESET}`);132 }133 console.log();134 }135136 for (const step of steps) {137 process.stdout.write(` ${DIM}…${RESET} ${step.name.padEnd(20)}\r`);138 const result = run(step);139 printStep(step.name, result.ok, result.duration);140141 if (!result.ok) {142 failed = true;143 failures.push({ name: step.name, output: result.output });144 break; 145 }146 }147148 console.log();149150 if (!failed) {151 console.log(`${GREEN}${BOLD}All checks passed.${RESET}`);152 console.log();153154 155 const chromeManifest = 'dist-chrome/manifest.json';156 if (existsSync(chromeManifest)) {157 const manifest = JSON.parse(await Bun.file(chromeManifest).text());158 console.log(`${DIM}Version: ${manifest.version}${RESET}`);159 }160 } else {161 console.log(`${RED}${BOLD}Failed: ${failures[0].name}${RESET}`);162 console.log();163164 165 const output = failures[0].output;166 if (output) {167 168 const lines = output.split('\n');169 const relevant = lines.slice(-30).join('\n');170 console.log(`${DIM}${relevant}${RESET}`);171 }172173 console.log();174 process.exit(1);175 }176}177178main();