background tests: ci.yml runs the CI and the acceptance matrix on every push again but nothing waits for them (the gate no longer waits for the parent's run, which a release's own bump push cancels; the record job needs only the installers); tools/ci-status.ts reports main's newest test run and release.ts prints a red one as a warning
This commit is contained in:
parent
b84538e4cb
commit
9059b2b32a
5 changed files with 69 additions and 10 deletions
41
tools/ci-status.ts
Normal file
41
tools/ci-status.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#!/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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue