41 lines
2.4 KiB
TypeScript
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);
|
|
}
|