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 YELLOW = '\x1b[33m';15const BOLD = '\x1b[1m';16const DIM = '\x1b[2m';17const RUN_FIREFOX_BROWSER_CHECKS = process.env.CI === 'true' || process.env.PEACH_E2E_FIREFOX === '1';18192021const RUN_BROWSER_GATE = process.env.CI === 'true' || process.env.PEACH_RELEASE_BROWSER_GATE === '1';2223interface Step {24 name: string;25 cmd: string[] | (() => string[]);26 cwd?: string;27}2829const steps: Step[] = [30 {31 32 33 34 name: 'Lockfile freshness',35 cmd: ['bun', 'install', '--frozen-lockfile'],36 },37 {38 name: 'TypeScript',39 cmd: () => [resolveBin('tsc'), '--noEmit'],40 },41 {42 name: 'Lint',43 cmd: ['npm', 'run', 'lint'],44 },45 {46 name: 'Tests',47 cmd: ['npm', 'test', '--', '--reporter=verbose'],48 },49 {50 51 52 name: 'Noise WASM provenance',53 cmd: ['node', 'scripts/check-noise-core.mjs'],54 },55 {56 name: 'Chrome build',57 cmd: ['npm', 'run', 'build:chrome'],58 },59 {60 name: 'Firefox build',61 cmd: ['npm', 'run', 'build:firefox'],62 },63 ...(RUN_FIREFOX_BROWSER_CHECKS64 ? [{65 name: 'Firefox sender context',66 cmd: ['npm', 'run', 'test:firefox:sender'],67 }]68 : []),69 70 71 ...(RUN_BROWSER_GATE72 ? [{73 name: 'Chrome release browser gate',74 cmd: ['bun', 'run', 'scripts/run-release-browser-gate.ts'],75 }]76 : []),77 78 79 80 ...(RUN_BROWSER_GATE81 ? [{82 name: 'Firefox release browser gate',83 cmd: ['node', 'scripts/test-firefox-passkeys.mjs'],84 }]85 : []),86];8788function run(step: Step): { ok: boolean; output: string; duration: number } {89 const start = Date.now();90 const cmd = typeof step.cmd === 'function' ? step.cmd() : step.cmd;91 const result = spawnSync(cmd, {92 cwd: step.cwd ?? process.cwd(),93 env: process.env,94 stdout: 'pipe',95 stderr: 'pipe',96 });97 const duration = Date.now() - start;98 const ok = result.exitCode === 0;99 const output = [100 result.stdout?.toString().trim(),101 result.stderr?.toString().trim(),102 ].filter(Boolean).join('\n');103104 return { ok, output, duration };105}106107function formatDuration(ms: number): string {108 if (ms < 1000) return `${ms}ms`;109 return `${(ms / 1000).toFixed(1)}s`;110}111112function printStep(name: string, ok: boolean, duration: number) {113 const icon = ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;114 const time = `${DIM}${formatDuration(duration)}${RESET}`;115 console.log(` ${icon} ${name.padEnd(20)} ${time}`);116}117118async function main() {119 console.log();120 console.log(`${BOLD}peach-browser CI${RESET}`);121 console.log(`${DIM}${new Date().toLocaleTimeString()}${RESET}`);122 console.log();123124 let failed = false;125 const failures: { name: string; output: string }[] = [];126127 128 129 if (RUN_BROWSER_GATE) {130 console.log(`${DIM}Browser gate enabled — reporting runtime versions:${RESET}`);131 const chromedriver = spawnSync({132 cmd: [resolveBin('playwright'), '--version'],133 stdout: 'pipe',134 stderr: 'pipe',135 });136 if (chromedriver.stdout) console.log(`${DIM} Playwright: ${chromedriver.stdout.toString().trim()}${RESET}`);137 if (process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH) {138 console.log(`${DIM} Chrome: ${process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH}${RESET}`);139 }140 console.log();141 }142143 for (const step of steps) {144 process.stdout.write(` ${DIM}…${RESET} ${step.name.padEnd(20)}\r`);145 const result = run(step);146 printStep(step.name, result.ok, result.duration);147148 if (!result.ok) {149 failed = true;150 failures.push({ name: step.name, output: result.output });151 break; 152 }153 }154155 console.log();156157 if (!failed) {158 console.log(`${GREEN}${BOLD}All checks passed.${RESET}`);159 console.log();160161 162 const chromeManifest = 'dist-chrome/manifest.json';163 if (existsSync(chromeManifest)) {164 const manifest = JSON.parse(await Bun.file(chromeManifest).text());165 console.log(`${DIM}Version: ${manifest.version}${RESET}`);166 }167 } else {168 console.log(`${RED}${BOLD}Failed: ${failures[0].name}${RESET}`);169 console.log();170171 172 const output = failures[0].output;173 if (output) {174 175 const lines = output.split('\n');176 const relevant = lines.slice(-30).join('\n');177 console.log(`${DIM}${relevant}${RESET}`);178 }179180 console.log();181 process.exit(1);182 }183}184185main();