release: a cancelled parent run is not a failure (a version-only child runs everything instead of stopping at the gate); a queued run gets 20 min before 'no ci run'; release.ts --resume continues a release that stopped after its push (the history rewrite re-pushed every old tag, their release.yml runs took the runners and cancelled 0.8.5's parent)
This commit is contained in:
parent
a64a7f198d
commit
77f141e223
3 changed files with 29 additions and 15 deletions
|
|
@ -86,7 +86,7 @@ export function versionOnlyProblems(changes: Change[]): { from: string | null; t
|
|||
|
||||
export type Status =
|
||||
| { state: 'green'; record: GreenRecord; release: Release }
|
||||
| { state: 'red'; run: string; failed: string[] }
|
||||
| { state: 'red'; run: string; failed: string[]; cancelledOnly?: boolean }
|
||||
| { state: 'pending'; run: string; running: string[] }
|
||||
| { state: 'none' };
|
||||
|
||||
|
|
@ -99,7 +99,8 @@ export function taskState(tasks: Task[], sha: string, runUrl: (n: number) => str
|
|||
const latest = Math.max(...mine.map(t => t.run_number));
|
||||
const jobs = mine.filter(t => t.run_number === latest);
|
||||
const failed = jobs.filter(t => ['failure', 'cancelled'].includes(t.status)).map(t => t.name);
|
||||
if (failed.length) return { state: 'red', run: runUrl(latest), failed };
|
||||
// a run that was only cancelled (superseded, runners freed) proves nothing either way
|
||||
if (failed.length) return { state: 'red', run: runUrl(latest), failed, cancelledOnly: !jobs.some(t => t.status === 'failure') };
|
||||
const running = jobs.filter(t => !['success', 'skipped'].includes(t.status)).map(t => `${t.name} (${t.status})`);
|
||||
// every job done and green but no green.json yet: the record job is still uploading, or ran in an older run
|
||||
return { state: 'pending', run: runUrl(latest), running };
|
||||
|
|
@ -158,16 +159,16 @@ export async function status(sha: string): Promise<Status> {
|
|||
}
|
||||
|
||||
/** Waits for a commit's candidate: green returns, red and none (after `graceMin`) fail at once. */
|
||||
export async function waitGreen(sha: string, timeoutMin: number, graceMin = 3): Promise<Extract<Status, { state: 'green' }>> {
|
||||
export async function waitGreen(sha: string, timeoutMin: number, graceMin = 20): Promise<Extract<Status, { state: 'green' }>> {
|
||||
const end = Date.now() + timeoutMin * 60_000;
|
||||
let last = 0;
|
||||
for (;;) {
|
||||
const s = await status(sha);
|
||||
if (s.state === 'green') { say(`candidate ${sha.slice(0, 10)} is green (run ${s.record.run}${s.record.reusedFrom ? `, CI and acceptance of ${s.record.reusedFrom.sha.slice(0, 10)}` : ''})`); return s; }
|
||||
if (s.state === 'red') fail(`commit ${sha.slice(0, 10)} is red: ${s.failed.join(', ')} failed in ${s.run}. Fix it and push; nothing is released from a red commit.`);
|
||||
if (s.state === 'none' && Date.now() - t0 > graceMin * 60_000) fail(`commit ${sha.slice(0, 10)} has no ci run on ${BASE}/${REPO}/actions: push it to main (ci.yml makes the candidate)`);
|
||||
if (s.state === 'none' && Date.now() - t0 > graceMin * 60_000) fail(`commit ${sha.slice(0, 10)} has no ci run on ${BASE}/${REPO}/actions after ${graceMin} min: push it to main (ci.yml makes the candidate), or free the runners (a queued run gets no task until one is free)`);
|
||||
if (Date.now() > end) fail(`timeout after ${timeoutMin} min waiting for the candidate of ${sha.slice(0, 10)}${s.state === 'pending' ? `: ${s.run} still has ${s.running.join(', ') || 'the record upload'}` : ''}`);
|
||||
if (Date.now() - last >= HEARTBEAT_MS) { last = Date.now(); say(`waiting for candidate ${sha.slice(0, 10)}: ${s.state === 'pending' ? `${s.run}: ${s.running.join(', ') || 'record upload'}` : 'no run yet'}`); }
|
||||
if (Date.now() - last >= HEARTBEAT_MS) { last = Date.now(); say(`waiting for candidate ${sha.slice(0, 10)}: ${s.state === 'pending' ? `${s.run}: ${s.running.join(', ') || 'record upload'}` : 'no run yet (queued runs get no task until a runner is free)'}`); }
|
||||
await Bun.sleep(10_000);
|
||||
}
|
||||
}
|
||||
|
|
@ -202,6 +203,7 @@ async function gate(sha: string, waitMin: number): Promise<void> {
|
|||
say(`version-only commit: ${from} -> ${to} (every copy exactly re-written, plus changelog / acceptance files); the parent ${parent.slice(0, 10)}'s CI and acceptance matrix count if it is green`);
|
||||
const first = await status(parent);
|
||||
if (first.state === 'none') { say(`the parent has no ci run (pushed together with this commit?): everything runs`); output({ reuse: 'false', from: '' }); return; }
|
||||
if (first.state === 'red' && first.cancelledOnly) { say(`the parent's run was cancelled (${first.run}), not failed: everything runs`); output({ reuse: 'false', from: '' }); return; }
|
||||
const s = await waitGreen(parent, waitMin);
|
||||
if (s.record.cliKey !== cliKey(ROOT)) { say(`the parent's CLI key ${s.record.cliKey} is not this commit's: everything runs`); output({ reuse: 'false', from: '' }); return; }
|
||||
output({ reuse: 'true', from: parent });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue