@arjunkhera/atlas 0.4.11 → 0.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/door/lib/gate.mjs +31 -30
- package/package.json +1 -1
package/door/lib/gate.mjs
CHANGED
|
@@ -218,36 +218,48 @@ export const gateRunTitle = (pr, sha) => `gate #${pr} at ${sha}`;
|
|
|
218
218
|
// Why one workflow run cannot carry a required check of this pull request,
|
|
219
219
|
// or null when it can. The run must come from a base file that defines the
|
|
220
220
|
// check, from a pull request event, on this head, for this pull request into
|
|
221
|
-
// its base branch.
|
|
222
|
-
|
|
221
|
+
// its base branch. The gate runs on pull_request_target, and its title must
|
|
222
|
+
// name this pull request and this head. The probe of 26 October (done line
|
|
223
|
+
// 15, atlas#211) showed that its run and its check run sit on the head commit,
|
|
224
|
+
// like the others.
|
|
225
|
+
export function refusal(run, { name, pr, sha, baseRef, homes, sides = () => [] }) {
|
|
223
226
|
if (!run) return 'no workflow run';
|
|
224
227
|
if (!homes.get(name)?.has(run.path)) return `from ${run.path ?? 'no file'}, not a base file that defines the check`;
|
|
225
|
-
|
|
228
|
+
const event = name === GATE_CHECK ? 'pull_request_target' : 'pull_request';
|
|
229
|
+
if (run.event !== event) return `from the event ${run.event}`;
|
|
226
230
|
if (run.head_sha !== sha) return 'of another head commit';
|
|
231
|
+
if (name === GATE_CHECK && run.display_title !== gateRunTitle(pr, sha)) return 'of another pull request or head';
|
|
227
232
|
const pulls = run.pull_requests ?? [];
|
|
228
233
|
if (!pulls.some((each) => Number(each.number) === Number(pr) && each.base?.ref === baseRef)) return `not of #${pr} into ${baseRef}`;
|
|
229
234
|
// GitHub lists every open pull request with this head, so a run made for a
|
|
230
235
|
// pull request into a side branch can also name this one. Such a run used
|
|
231
236
|
// the side branch's file, so it does not count.
|
|
232
237
|
if (pulls.some((each) => each.base?.ref !== baseRef)) return `also of a pull request into ${pulls.find((each) => each.base?.ref !== baseRef).base?.ref ?? 'another branch'}`;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (run.display_title !== gateRunTitle(pr, sha)) return 'of another pull request or head';
|
|
243
|
-
if (run.head_branch !== baseRef) return `from the branch ${run.head_branch}, not ${baseRef}`;
|
|
244
|
-
if (!onBase(run.head_sha)) return `from a commit that is not on ${baseRef}`;
|
|
238
|
+
// GitHub builds that list when it is read, from the open pull requests of
|
|
239
|
+
// the head branch. Once a side pull request closes, its gate run names only
|
|
240
|
+
// this one, and its title can be set to match. So the gate also refuses a
|
|
241
|
+
// head branch that ever had a pull request into another branch.
|
|
242
|
+
if (name === GATE_CHECK) {
|
|
243
|
+
const side = sides();
|
|
244
|
+
if (side === null) return 'no head branch to check for side pull requests';
|
|
245
|
+
if (side.length) return `the head branch also had #${side[0].number} into ${side[0].base?.ref ?? 'another branch'}`;
|
|
246
|
+
}
|
|
245
247
|
return null;
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
// The check runs that may count for this pull request, and the ones refused.
|
|
249
|
-
function pinnedRuns(gh, repo, { pr, sha, baseRef, checks }) {
|
|
251
|
+
function pinnedRuns(gh, repo, { pr, sha, baseRef, head, checks }) {
|
|
250
252
|
const homes = homesAt(gh, repo, baseRef, checks);
|
|
253
|
+
// Every pull request, open or closed, from the head branch into another
|
|
254
|
+
// branch. Read once, and only when a gate run needs it.
|
|
255
|
+
let sideList;
|
|
256
|
+
const sides = () => {
|
|
257
|
+
if (sideList !== undefined) return sideList;
|
|
258
|
+
const owner = head?.repo?.owner?.login ?? head?.user?.login;
|
|
259
|
+
if (!head?.ref || !owner) return (sideList = null);
|
|
260
|
+
const all = gh('GET', `repos/${repo}/pulls?head=${encodeURIComponent(`${owner}:${head.ref}`)}&state=all&per_page=100`) ?? [];
|
|
261
|
+
return (sideList = all.filter((each) => each.base?.ref !== baseRef));
|
|
262
|
+
};
|
|
251
263
|
const suites = new Map();
|
|
252
264
|
const runOf = (id) => {
|
|
253
265
|
if (!suites.has(id)) suites.set(id, id ? (gh('GET', `repos/${repo}/actions/runs?check_suite_id=${id}`)?.workflow_runs ?? [])[0] ?? null : null);
|
|
@@ -256,21 +268,10 @@ function pinnedRuns(gh, repo, { pr, sha, baseRef, checks }) {
|
|
|
256
268
|
const kept = [];
|
|
257
269
|
const refused = [];
|
|
258
270
|
for (const check of checkRuns(gh, repo, sha)) {
|
|
259
|
-
if (!checks.includes(check.name) || check.
|
|
260
|
-
const why = refusal(runOf(check.check_suite?.id), { name: check.name, pr, sha, baseRef, homes });
|
|
271
|
+
if (!checks.includes(check.name) || check.app?.id !== ACTIONS_APP_ID) continue;
|
|
272
|
+
const why = refusal(runOf(check.check_suite?.id), { name: check.name, pr, sha, baseRef, homes, sides });
|
|
261
273
|
if (why) refused.push({ name: check.name, why }); else kept.push(check);
|
|
262
274
|
}
|
|
263
|
-
if (checks.includes(GATE_CHECK)) {
|
|
264
|
-
const onBase = (commit) => ['ahead', 'identical'].includes(gh('GET', `repos/${repo}/compare/${commit}...${encodeURIComponent(baseRef)}`)?.status);
|
|
265
|
-
const runs = gh('GET', `repos/${repo}/actions/workflows/${GATE_WORKFLOW.split('/').pop()}/runs?event=pull_request_target&per_page=100`)?.workflow_runs ?? [];
|
|
266
|
-
for (const run of runs.filter((each) => each.display_title === gateRunTitle(pr, sha))) {
|
|
267
|
-
const why = gateRefusal(run, { pr, sha, baseRef, onBase });
|
|
268
|
-
if (why) { refused.push({ name: GATE_CHECK, why }); continue; }
|
|
269
|
-
for (const job of gh('GET', `repos/${repo}/actions/runs/${run.id}/jobs`)?.jobs ?? []) {
|
|
270
|
-
if (job.name === GATE_CHECK) kept.push({ ...job, app: { id: ACTIONS_APP_ID } });
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
275
|
return { kept, refused };
|
|
275
276
|
}
|
|
276
277
|
|
|
@@ -283,7 +284,7 @@ export function readyCommand({ repo, pr, answer }, line, gh = ghApi) {
|
|
|
283
284
|
if (pull?.state !== 'open') { line(`atlas gate ready: pull request #${pr} is ${pull?.merged ? 'merged' : pull?.state ?? 'not found'}. NOT READY.`); return 1; }
|
|
284
285
|
const sha = pull.head.sha;
|
|
285
286
|
const checks = checksAt(gh, repo, pull.base.ref);
|
|
286
|
-
const { kept, refused } = pinnedRuns(gh, repo, { pr, sha, baseRef: pull.base.ref, checks });
|
|
287
|
+
const { kept, refused } = pinnedRuns(gh, repo, { pr, sha, baseRef: pull.base.ref, head: pull.head, checks });
|
|
287
288
|
const rows = checkStates(checks, kept);
|
|
288
289
|
line(`atlas gate ready: #${pr} at ${sha.slice(0, 7)}, ${checks.length} required check(s) from atlas.yaml on ${pull.base.ref}.`);
|
|
289
290
|
for (const row of rows) line(checkLine(row));
|
package/package.json
CHANGED