ci.ts

3.6 KB · raw

1#!/usr/bin/env bun2// peach-browser/scripts/ci.ts3// Run at the end of a work session to verify everything is solid.4// Usage: bun run scripts/ci.ts56import { 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    // LV-6: standardize on bun + a frozen lockfile. --frozen-lockfile exits25    // non-zero if bun.lock is stale relative to package.json, so a forgotten26    // lockfile update fails CI instead of drifting silently.27    name: 'Lockfile freshness',28    cmd: ['bun', 'install', '--frozen-lockfile'],29  },30  {31    name: 'TypeScript',32    cmd: ['npx', 'tsc', '--noEmit'],33  },34  {35    name: 'Lint',36    cmd: ['npm', 'run', 'lint'],37  },38  {39    name: 'Tests',40    cmd: ['npm', 'test', '--', '--reporter=verbose'],41  },42  {43    // PB-26-003: rebuild the Noise WASM from crypto-core and fail if the44    // committed blob drifts — "reviewed source → shipped binary" enforced.45    name: 'Noise WASM provenance',46    cmd: ['node', 'scripts/check-noise-core.mjs'],47  },48  {49    name: 'Chrome build',50    cmd: ['npm', 'run', 'build:chrome'],51  },52  {53    name: 'Firefox build',54    cmd: ['npm', 'run', 'build:firefox'],55  },56];5758function run(step: Step): { ok: boolean; output: string; duration: number } {59  const start = Date.now();60  const result = spawnSync(step.cmd, {61    cwd: step.cwd ?? process.cwd(),62    env: process.env,63    stdout: 'pipe',64    stderr: 'pipe',65  });66  const duration = Date.now() - start;67  const ok = result.exitCode === 0;68  const output = [69    result.stdout?.toString().trim(),70    result.stderr?.toString().trim(),71  ].filter(Boolean).join('\n');7273  return { ok, output, duration };74}7576function formatDuration(ms: number): string {77  if (ms < 1000) return `${ms}ms`;78  return `${(ms / 1000).toFixed(1)}s`;79}8081function printStep(name: string, ok: boolean, duration: number) {82  const icon = ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;83  const time = `${DIM}${formatDuration(duration)}${RESET}`;84  console.log(`  ${icon}  ${name.padEnd(20)} ${time}`);85}8687async function main() {88  console.log();89  console.log(`${BOLD}peach-browser CI${RESET}`);90  console.log(`${DIM}${new Date().toLocaleTimeString()}${RESET}`);91  console.log();9293  let failed = false;94  const failures: { name: string; output: string }[] = [];9596  for (const step of steps) {97    process.stdout.write(`  ${DIM}…${RESET}  ${step.name.padEnd(20)}\r`);98    const result = run(step);99    printStep(step.name, result.ok, result.duration);100101    if (!result.ok) {102      failed = true;103      failures.push({ name: step.name, output: result.output });104      break; // fail fast105    }106  }107108  console.log();109110  if (!failed) {111    console.log(`${GREEN}${BOLD}All checks passed.${RESET}`);112    console.log();113114    // Show build artifact sizes115    const chromeManifest = 'dist-chrome/manifest.json';116    if (existsSync(chromeManifest)) {117      const manifest = JSON.parse(await Bun.file(chromeManifest).text());118      console.log(`${DIM}Version: ${manifest.version}${RESET}`);119    }120  } else {121    console.log(`${RED}${BOLD}Failed: ${failures[0].name}${RESET}`);122    console.log();123124    // Print the relevant failure output125    const output = failures[0].output;126    if (output) {127      // Show last 30 lines of output — most errors are at the end128      const lines = output.split('\n');129      const relevant = lines.slice(-30).join('\n');130      console.log(`${DIM}${relevant}${RESET}`);131    }132133    console.log();134    process.exit(1);135  }136}137138main();