50d4e457800f
add scripts/ci.ts — bun CI runner for typecheck, tests, chrome and firefox builds
- time
- 2026-05-04 23:50 UTC · 5 months ago
- machine
- koh2
- parent
- cc7196b4e72a
↓ peach-browser-50d4e457.tar.gzatlas at this save
files changed2 files
package.json+2 −1scripts/ci.ts+121 −0package.json+2 −1
@@ -13,7 +13,8 @@1313 "typecheck": "tsc --noEmit",1414 "test": "vitest run",1515 "test:watch": "vitest",16- "test:passkeys": "npm run build:e2e && playwright test tests/passkeys.spec.ts"16+ "test:passkeys": "npm run build:e2e && playwright test tests/passkeys.spec.ts",17+ "ci": "bun run scripts/ci.ts"1718 },1819 "dependencies": {1920 "@bokuweb/zstd-wasm": "^0.0.27",scripts/ci.ts+121 −0
@@ -0,0 +1,121 @@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.ts5+6+import { spawnSync } from 'bun';7+import { existsSync } from 'fs';8+9+const RESET = '\x1b[0m';10+const GREEN = '\x1b[32m';11+const RED = '\x1b[31m';12+const YELLOW = '\x1b[33m';13+const BOLD = '\x1b[1m';14+const DIM = '\x1b[2m';15+16+interface Step {17+ name: string;18+ cmd: string[];19+ cwd?: string;20+}21+22+const steps: Step[] = [23+ {24+ name: 'TypeScript',25+ cmd: ['npx', 'tsc', '--noEmit'],26+ },27+ {28+ name: 'Tests',29+ cmd: ['npm', 'test', '--', '--reporter=verbose'],30+ },31+ {32+ name: 'Chrome build',33+ cmd: ['npm', 'run', 'build:chrome'],34+ },35+ {36+ name: 'Firefox build',37+ cmd: ['npm', 'run', 'build:firefox'],38+ },39+];40+41+function run(step: Step): { ok: boolean; output: string; duration: number } {42+ const start = Date.now();43+ const result = spawnSync(step.cmd, {44+ cwd: step.cwd ?? process.cwd(),45+ env: process.env,46+ stdout: 'pipe',47+ stderr: 'pipe',48+ });49+ const duration = Date.now() - start;50+ const ok = result.exitCode === 0;51+ const output = [52+ result.stdout?.toString().trim(),53+ result.stderr?.toString().trim(),54+ ].filter(Boolean).join('\n');55+56+ return { ok, output, duration };57+}58+59+function formatDuration(ms: number): string {60+ if (ms < 1000) return `${ms}ms`;61+ return `${(ms / 1000).toFixed(1)}s`;62+}63+64+function printStep(name: string, ok: boolean, duration: number) {65+ const icon = ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;66+ const time = `${DIM}${formatDuration(duration)}${RESET}`;67+ console.log(` ${icon} ${name.padEnd(20)} ${time}`);68+}69+70+async function main() {71+ console.log();72+ console.log(`${BOLD}peach-browser CI${RESET}`);73+ console.log(`${DIM}${new Date().toLocaleTimeString()}${RESET}`);74+ console.log();75+76+ let failed = false;77+ const failures: { name: string; output: string }[] = [];78+79+ for (const step of steps) {80+ process.stdout.write(` ${DIM}…${RESET} ${step.name.padEnd(20)}\r`);81+ const result = run(step);82+ printStep(step.name, result.ok, result.duration);83+84+ if (!result.ok) {85+ failed = true;86+ failures.push({ name: step.name, output: result.output });87+ break; // fail fast88+ }89+ }90+91+ console.log();92+93+ if (!failed) {94+ console.log(`${GREEN}${BOLD}All checks passed.${RESET}`);95+ console.log();96+97+ // Show build artifact sizes98+ const chromeManifest = 'dist-chrome/manifest.json';99+ if (existsSync(chromeManifest)) {100+ const manifest = JSON.parse(await Bun.file(chromeManifest).text());101+ console.log(`${DIM}Version: ${manifest.version}${RESET}`);102+ }103+ } else {104+ console.log(`${RED}${BOLD}Failed: ${failures[0].name}${RESET}`);105+ console.log();106+107+ // Print the relevant failure output108+ const output = failures[0].output;109+ if (output) {110+ // Show last 30 lines of output — most errors are at the end111+ const lines = output.split('\n');112+ const relevant = lines.slice(-30).join('\n');113+ console.log(`${DIM}${relevant}${RESET}`);114+ }115+116+ console.log();117+ process.exit(1);118+ }119+}120+121+main();