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';15const RUN_FIREFOX_BROWSER_CHECKS = process.env.CI === 'true' || process.env.PEACH_E2E_FIREFOX === '1';16171819const RUN_BROWSER_GATE = process.env.CI === 'true' || process.env.PEACH_RELEASE_BROWSER_GATE === '1';2021interface Step {22 name: string;23 cmd: string[];24 cwd?: string;25}2627const steps: Step[] = [28 {29 30 31 32 name: 'Lockfile freshness',33 cmd: ['bun', 'install', '--frozen-lockfile'],34 },35 {36 name: 'TypeScript',37 cmd: ['npx', 'tsc', '--noEmit'],38 },39 {40 name: 'Lint',41 cmd: ['npm', 'run', 'lint'],42 },43 {44 name: 'Tests',45 cmd: ['npm', 'test', '--', '--reporter=verbose'],46 },47 {48 49 50 name: 'Noise WASM provenance',51 cmd: ['node', 'scripts/check-noise-core.mjs'],52 },53 {54 name: 'Chrome build',55 cmd: ['npm', 'run', 'build:chrome'],56 },57 {58 name: 'Firefox build',59 cmd: ['npm', 'run', 'build:firefox'],60 },61 ...(RUN_FIREFOX_BROWSER_CHECKS62 ? [{63 name: 'Firefox sender context',64 cmd: ['npm', 'run', 'test:firefox:sender'],65 }]66 : []),67 68 69 ...(RUN_BROWSER_GATE70 ? [{71 name: 'Chrome release browser gate',72 cmd: ['npx', 'playwright', 'test', '--project=chrome-release'],73 }]74 : []),75 76 77 78 ...(RUN_BROWSER_GATE79 ? [{80 name: 'Firefox release browser gate',81 cmd: ['node', 'scripts/test-firefox-passkeys.mjs'],82 }]83 : []),84];8586function run(step: Step): { ok: boolean; output: string; duration: number } {87 const start = Date.now();88 const result = spawnSync(step.cmd, {89 cwd: step.cwd ?? process.cwd(),90 env: process.env,91 stdout: 'pipe',92 stderr: 'pipe',93 });94 const duration = Date.now() - start;95 const ok = result.exitCode === 0;96 const output = [97 result.stdout?.toString().trim(),98 result.stderr?.toString().trim(),99 ].filter(Boolean).join('\n');100101 return { ok, output, duration };102}103104function formatDuration(ms: number): string {105 if (ms < 1000) return `${ms}ms`;106 return `${(ms / 1000).toFixed(1)}s`;107}108109function printStep(name: string, ok: boolean, duration: number) {110 const icon = ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;111 const time = `${DIM}${formatDuration(duration)}${RESET}`;112 console.log(` ${icon} ${name.padEnd(20)} ${time}`);113}114115async function main() {116 console.log();117 console.log(`${BOLD}peach-browser CI${RESET}`);118 console.log(`${DIM}${new Date().toLocaleTimeString()}${RESET}`);119 console.log();120121 let failed = false;122 const failures: { name: string; output: string }[] = [];123124 125 126 if (RUN_BROWSER_GATE) {127 console.log(`${DIM}Browser gate enabled — reporting runtime versions:${RESET}`);128 const chromedriver = spawnSync({129 cmd: ['npx', 'playwright', '--version'],130 stdout: 'pipe',131 stderr: 'pipe',132 });133 if (chromedriver.stdout) console.log(`${DIM} Playwright: ${chromedriver.stdout.toString().trim()}${RESET}`);134 if (process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH) {135 console.log(`${DIM} Chrome: ${process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH}${RESET}`);136 }137 console.log();138 }139140 for (const step of steps) {141 process.stdout.write(` ${DIM}…${RESET} ${step.name.padEnd(20)}\r`);142 const result = run(step);143 printStep(step.name, result.ok, result.duration);144145 if (!result.ok) {146 failed = true;147 failures.push({ name: step.name, output: result.output });148 break; 149 }150 }151152 console.log();153154 if (!failed) {155 console.log(`${GREEN}${BOLD}All checks passed.${RESET}`);156 console.log();157158 159 const chromeManifest = 'dist-chrome/manifest.json';160 if (existsSync(chromeManifest)) {161 const manifest = JSON.parse(await Bun.file(chromeManifest).text());162 console.log(`${DIM}Version: ${manifest.version}${RESET}`);163 }164 } else {165 console.log(`${RED}${BOLD}Failed: ${failures[0].name}${RESET}`);166 console.log();167168 169 const output = failures[0].output;170 if (output) {171 172 const lines = output.split('\n');173 const relevant = lines.slice(-30).join('\n');174 console.log(`${DIM}${relevant}${RESET}`);175 }176177 console.log();178 process.exit(1);179 }180}181182main();