#!/usr/bin/env bun // The background tests of main (ci.yml's ci and acceptance jobs): they never gate a release (the owner's decision, // 2026-10-16), so this reports them. Check it at the start of a session and fix a red result in the next patch. // bun tools/ci-status.ts the newest main run that has test jobs: green, red (which jobs) or running // bun tools/ci-status.ts --json import { spawnSync } from 'node:child_process'; import { runUrl, tasks, type Task } from './release-candidate'; const TEST = /^(ci|acceptance\b)/; export interface TestState { sha: string; run: string; state: 'green' | 'red' | 'running' | 'none'; failed: string[]; running: string[] } /** The state of the newest run (of the given commits, newest first) that has test jobs. Pure. */ export function testState(all: Task[], shas: string[], url: (n: number) => string): TestState { for (const sha of shas) { const mine = all.filter(t => t.head_sha === sha && t.workflow_id === 'ci.yml' && TEST.test(t.name)); if (!mine.length) continue; const latest = Math.max(...mine.map(t => t.run_number)); const jobs = mine.filter(t => t.run_number === latest); // a run cancelled by a newer push proves nothing: look at the older commit if (jobs.every(t => t.status === 'cancelled')) continue; const failed = jobs.filter(t => t.status === 'failure').map(t => t.name); const running = jobs.filter(t => !['success', 'skipped', 'failure', 'cancelled'].includes(t.status)).map(t => t.name); return { sha, run: url(latest), state: failed.length ? 'red' : running.length ? 'running' : 'green', failed, running }; } return { sha: shas[0] ?? '', run: '', state: 'none', failed: [], running: [] }; } export async function mainTests(): Promise { spawnSync('git', ['fetch', '-q', 'origin', 'main']); const shas = (spawnSync('git', ['log', '-20', '--format=%H', 'origin/main'], { encoding: 'utf8' }).stdout ?? '').split('\n').filter(Boolean); return testState(await tasks(), shas, runUrl); } if (import.meta.main) { const s = await mainTests(); if (process.argv.includes('--json')) console.log(JSON.stringify(s, null, 2)); else console.log(s.state === 'none' ? 'no test run on the last 20 commits of main' : `${s.state}: ${s.sha.slice(0, 10)} ${s.run}${s.failed.length ? ` (failed: ${s.failed.join(', ')})` : ''}${s.running.length ? ` (running: ${s.running.join(', ')})` : ''}`); process.exit(s.state === 'red' ? 1 : 0); }