123456import { spawnSync } from 'bun';7import { existsSync } from 'node:fs';8import { resolveBin } from './release-lib';910const RESET = '\x1b[0m';11const GREEN = '\x1b[32m';12const RED = '\x1b[31m';13const YELLOW = '\x1b[33m';14const BOLD = '\x1b[1m';15const DIM = '\x1b[2m';16const RUN_FIREFOX_BROWSER_CHECKS = process.env.CI === 'true' || process.env.PEACH_E2E_FIREFOX === '1';17181920const RUN_BROWSER_GATE = process.env.CI === 'true' || process.env.PEACH_RELEASE_BROWSER_GATE === '1';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 name: 'Tests',46 cmd: ['npm', 'test', '--', '--reporter=verbose'],47 },48 {49 50 51 name: 'Noise WASM provenance',52 cmd: ['node', 'scripts/check-noise-core.mjs'],53 },54 {55 name: 'Chrome build',56 cmd: ['npm', 'run', 'build:chrome'],57 },58 {59 name: 'Firefox build',60 cmd: ['npm', 'run', 'build:firefox'],61 },62 ...(RUN_FIREFOX_BROWSER_CHECKS63 ? [{64 name: 'Firefox sender context',65 cmd: ['npm', 'run', 'test:firefox:sender'],66 }]67 : []),68 69 70 ...(RUN_BROWSER_GATE71 ? [{72 name: 'Chrome release browser gate',73 cmd: () => [resolveBin('playwright'), 'test', '--project=chrome-release'],74 }]75 : []),76 77 78 79 ...(RUN_BROWSER_GATE80 ? [{81 name: 'Firefox release browser gate',82 cmd: ['node', 'scripts/test-firefox-passkeys.mjs'],83 }]84 : []),85];8687function run(step: Step): { ok: boolean; output: string; duration: number } {88 const start = Date.now();89 const cmd = typeof step.cmd === 'function' ? step.cmd() : step.cmd;90 const result = spawnSync(cmd, {91 cwd: step.cwd ?? process.cwd(),92 env: process.env,93 stdout: 'pipe',94 stderr: 'pipe',95 });96 const duration = Date.now() - start;97 const ok = result.exitCode === 0;98 const output = [99 result.stdout?.toString().trim(),100 result.stderr?.toString().trim(),101 ].filter(Boolean).join('\n');102103 return { ok, output, duration };104}105106function formatDuration(ms: number): string {107 if (ms < 1000) return `${ms}ms`;108 return `${(ms / 1000).toFixed(1)}s`;109}110111function printStep(name: string, ok: boolean, duration: number) {112 const icon = ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;113 const time = `${DIM}${formatDuration(duration)}${RESET}`;114 console.log(` ${icon} ${name.padEnd(20)} ${time}`);115}116117async function main() {118 console.log();119 console.log(`${BOLD}peach-browser CI${RESET}`);120 console.log(`${DIM}${new Date().toLocaleTimeString()}${RESET}`);121 console.log();122123 let failed = false;124 const failures: { name: string; output: string }[] = [];125126 127 128 if (RUN_BROWSER_GATE) {129 console.log(`${DIM}Browser gate enabled — reporting runtime versions:${RESET}`);130 const chromedriver = spawnSync({131 cmd: [resolveBin('playwright'), '--version'],132 stdout: 'pipe',133 stderr: 'pipe',134 });135 if (chromedriver.stdout) console.log(`${DIM} Playwright: ${chromedriver.stdout.toString().trim()}${RESET}`);136 if (process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH) {137 console.log(`${DIM} Chrome: ${process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH}${RESET}`);138 }139 console.log();140 }141142 for (const step of steps) {143 process.stdout.write(` ${DIM}…${RESET} ${step.name.padEnd(20)}\r`);144 const result = run(step);145 printStep(step.name, result.ok, result.duration);146147 if (!result.ok) {148 failed = true;149 failures.push({ name: step.name, output: result.output });150 break; 151 }152 }153154 console.log();155156 if (!failed) {157 console.log(`${GREEN}${BOLD}All checks passed.${RESET}`);158 console.log();159160 161 const chromeManifest = 'dist-chrome/manifest.json';162 if (existsSync(chromeManifest)) {163 const manifest = JSON.parse(await Bun.file(chromeManifest).text());164 console.log(`${DIM}Version: ${manifest.version}${RESET}`);165 }166 } else {167 console.log(`${RED}${BOLD}Failed: ${failures[0].name}${RESET}`);168 console.log();169170 171 const output = failures[0].output;172 if (output) {173 174 const lines = output.split('\n');175 const relevant = lines.slice(-30).join('\n');176 console.log(`${DIM}${relevant}${RESET}`);177 }178179 console.log();180 process.exit(1);181 }182}183184main();