storebound/tools/ci-status.ts

41 lines
2.4 KiB
TypeScript

#!/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<TestState> {
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);
}