@kungfu-tech/buildchain 2.14.1 → 2.14.2-alpha.0
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/dist/site/buildchain-contract.json +5 -5
- package/dist/site/buildchain-site.json +13 -13
- package/dist/site/kfd-claims.json +8 -5
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/page-registry.json +6 -6
- package/dist/site/public-surface-audit.json +11 -6
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +7 -7
- package/dist/site/workflow-registry.json +9 -4
- package/docs/publish-transaction.md +14 -0
- package/docs/release-governance.md +27 -4
- package/docs/web-surface-deployments.md +12 -2
- package/package.json +1 -1
- package/scripts/buildchain-patrol.mjs +3 -1
- package/scripts/dev-merge-queue.mjs +64 -0
- package/scripts/dev-pr-auto-merge.mjs +362 -21
|
@@ -7,6 +7,15 @@ const DEFAULT_ALLOWED_HEAD_PREFIXES = ["feature/", "fix/", "chore/", "docs/", "c
|
|
|
7
7
|
const DEFAULT_REQUIRED_CHECKS = ["check"];
|
|
8
8
|
const SUCCESS_STATES = new Set(["success"]);
|
|
9
9
|
const SUCCESS_CONCLUSIONS = new Set(["success", "neutral", "skipped"]);
|
|
10
|
+
const VALID_LANDING_MODES = new Set(["auto", "direct", "queue"]);
|
|
11
|
+
const STATIC_SKIP_REASONS = new Set([
|
|
12
|
+
"draft",
|
|
13
|
+
"fork-or-cross-repository-head",
|
|
14
|
+
"head-prefix-not-allowed",
|
|
15
|
+
"missing-ready-label",
|
|
16
|
+
"blocked-label",
|
|
17
|
+
]);
|
|
18
|
+
const ADMISSION_CONTRACT = "kungfu-buildchain-dev-merge-queue-admission";
|
|
10
19
|
|
|
11
20
|
function splitList(value, fallback = []) {
|
|
12
21
|
if (Array.isArray(value)) return value.map((entry) => String(entry).trim()).filter(Boolean);
|
|
@@ -30,8 +39,16 @@ function intOption(value, fallback) {
|
|
|
30
39
|
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback;
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
function choiceOption(value, valid, fallback, field) {
|
|
43
|
+
const normalized = String(value || fallback).trim().toLowerCase();
|
|
44
|
+
if (!valid.has(normalized)) {
|
|
45
|
+
throw new Error(`${field} must be one of ${[...valid].join(", ")}, got: ${value || "<empty>"}`);
|
|
46
|
+
}
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
|
|
33
50
|
function normalizeRepo(value) {
|
|
34
|
-
const text = String(value || "").trim();
|
|
51
|
+
const text = String(value?.fullName || value || "").trim();
|
|
35
52
|
const match = text.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
36
53
|
if (!match) throw new Error(`repository must be owner/repo, got: ${text || "<empty>"}`);
|
|
37
54
|
return { owner: match[1], repo: match[2], fullName: `${match[1]}/${match[2]}` };
|
|
@@ -49,6 +66,7 @@ function normalizeOptions(options = {}) {
|
|
|
49
66
|
sameRepositoryOnly: boolOption(options.sameRepositoryOnly, true),
|
|
50
67
|
maxMerges: intOption(options.maxMerges, 1),
|
|
51
68
|
mergeMethod: String(options.mergeMethod || "merge").trim(),
|
|
69
|
+
landingMode: choiceOption(options.landingMode, VALID_LANDING_MODES, "auto", "landing mode"),
|
|
52
70
|
dryRun: boolOption(options.dryRun, true),
|
|
53
71
|
pollMergeableAttempts: intOption(options.pollMergeableAttempts, 3),
|
|
54
72
|
pollMergeableDelayMs: intOption(options.pollMergeableDelayMs, 1000),
|
|
@@ -165,6 +183,12 @@ export async function evaluatePullRequest(pr, options, client) {
|
|
|
165
183
|
attempts: options.pollMergeableAttempts,
|
|
166
184
|
delayMs: options.pollMergeableDelayMs,
|
|
167
185
|
});
|
|
186
|
+
if (detailed.base?.ref && detailed.base.ref !== options.targetBranch) {
|
|
187
|
+
return skip("base-branch-drift", {
|
|
188
|
+
expectedBaseRef: options.targetBranch,
|
|
189
|
+
observedBaseRef: detailed.base.ref,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
168
192
|
if (!mergeableAccepted(detailed)) {
|
|
169
193
|
return skip("not-mergeable", {
|
|
170
194
|
mergeable: detailed.mergeable,
|
|
@@ -172,19 +196,25 @@ export async function evaluatePullRequest(pr, options, client) {
|
|
|
172
196
|
});
|
|
173
197
|
}
|
|
174
198
|
|
|
199
|
+
const approval = { required: options.requireApproval, passed: true };
|
|
175
200
|
if (options.requireApproval) {
|
|
176
201
|
const reviews = await client.listReviews(pr.number);
|
|
177
|
-
|
|
202
|
+
approval.passed = hasRequiredApproval(reviews);
|
|
203
|
+
if (!approval.passed) return skip("missing-approval", { approval });
|
|
178
204
|
}
|
|
179
205
|
|
|
180
|
-
const
|
|
206
|
+
const observedHeadSha = detailed.head?.sha || pr.head?.sha || "";
|
|
207
|
+
const checks = await client.listCommitChecks(observedHeadSha);
|
|
181
208
|
const checkSummary = summarizeChecks(checks, options.requiredChecks);
|
|
182
|
-
if (!checkSummary.passed) return skip("required-checks-not-passing", { checks: checkSummary });
|
|
209
|
+
if (!checkSummary.passed) return skip("required-checks-not-passing", { checks: checkSummary, approval });
|
|
183
210
|
|
|
184
211
|
return {
|
|
185
212
|
action: options.dryRun ? "would-merge" : "merge",
|
|
186
213
|
reason: options.dryRun ? "dry-run" : "eligible",
|
|
187
214
|
checks: checkSummary,
|
|
215
|
+
approval,
|
|
216
|
+
pullRequestId: detailed.node_id || pr.node_id || "",
|
|
217
|
+
observedHeadSha,
|
|
188
218
|
};
|
|
189
219
|
}
|
|
190
220
|
|
|
@@ -302,6 +332,178 @@ export class GitHubClient {
|
|
|
302
332
|
const { data } = await this.request("GET", `/repos/${this.repository.owner}/${this.repository.repo}/git/ref/${ref}`);
|
|
303
333
|
return data.object?.sha || "";
|
|
304
334
|
}
|
|
335
|
+
|
|
336
|
+
async graphql(query, variables = {}) {
|
|
337
|
+
const { data } = await this.request("POST", "/graphql", {
|
|
338
|
+
body: { query, variables },
|
|
339
|
+
});
|
|
340
|
+
if (Array.isArray(data?.errors) && data.errors.length > 0) {
|
|
341
|
+
const error = new Error(data.errors.map((entry) => entry.message).filter(Boolean).join("; ") || "GitHub GraphQL request failed");
|
|
342
|
+
error.data = data;
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
return data?.data || {};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async getMergeQueueState(branch) {
|
|
349
|
+
const data = await this.graphql(
|
|
350
|
+
`query BuildchainMergeQueueState($owner: String!, $repo: String!, $branch: String!) {
|
|
351
|
+
repository(owner: $owner, name: $repo) {
|
|
352
|
+
mergeQueue(branch: $branch) {
|
|
353
|
+
id
|
|
354
|
+
entries(first: 100) {
|
|
355
|
+
nodes {
|
|
356
|
+
id
|
|
357
|
+
position
|
|
358
|
+
state
|
|
359
|
+
baseCommit { oid }
|
|
360
|
+
headCommit { oid }
|
|
361
|
+
pullRequest { number headRefOid }
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}`,
|
|
367
|
+
{
|
|
368
|
+
owner: this.repository.owner,
|
|
369
|
+
repo: this.repository.repo,
|
|
370
|
+
branch,
|
|
371
|
+
},
|
|
372
|
+
);
|
|
373
|
+
const queue = data.repository?.mergeQueue || null;
|
|
374
|
+
return {
|
|
375
|
+
enabled: Boolean(queue),
|
|
376
|
+
id: queue?.id || "",
|
|
377
|
+
entries: (queue?.entries?.nodes || []).map((entry) => ({
|
|
378
|
+
id: entry.id || "",
|
|
379
|
+
position: entry.position,
|
|
380
|
+
state: entry.state || "",
|
|
381
|
+
pullRequestNumber: entry.pullRequest?.number || null,
|
|
382
|
+
pullRequestHeadSha: entry.pullRequest?.headRefOid || "",
|
|
383
|
+
baseSha: entry.baseCommit?.oid || "",
|
|
384
|
+
headSha: entry.headCommit?.oid || "",
|
|
385
|
+
})),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async enqueuePullRequest({ pullRequestId, expectedHeadOid }) {
|
|
390
|
+
const data = await this.graphql(
|
|
391
|
+
`mutation BuildchainEnqueuePullRequest($input: EnqueuePullRequestInput!) {
|
|
392
|
+
enqueuePullRequest(input: $input) {
|
|
393
|
+
mergeQueueEntry {
|
|
394
|
+
id
|
|
395
|
+
position
|
|
396
|
+
state
|
|
397
|
+
baseCommit { oid }
|
|
398
|
+
headCommit { oid }
|
|
399
|
+
pullRequest { number headRefOid }
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}`,
|
|
403
|
+
{
|
|
404
|
+
input: {
|
|
405
|
+
pullRequestId,
|
|
406
|
+
expectedHeadOid,
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
);
|
|
410
|
+
const entry = data.enqueuePullRequest?.mergeQueueEntry;
|
|
411
|
+
if (!entry?.id) throw new Error("GitHub did not return a merge queue entry");
|
|
412
|
+
return {
|
|
413
|
+
id: entry.id,
|
|
414
|
+
position: entry.position,
|
|
415
|
+
state: entry.state || "",
|
|
416
|
+
pullRequestNumber: entry.pullRequest?.number || null,
|
|
417
|
+
pullRequestHeadSha: entry.pullRequest?.headRefOid || "",
|
|
418
|
+
baseSha: entry.baseCommit?.oid || "",
|
|
419
|
+
headSha: entry.headCommit?.oid || "",
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function queuePredecessor(queueState, fallback = {}) {
|
|
425
|
+
const entry = queueState?.entries?.[0];
|
|
426
|
+
if (entry) {
|
|
427
|
+
return {
|
|
428
|
+
queueEntryId: entry.id,
|
|
429
|
+
pullRequestNumber: entry.pullRequestNumber,
|
|
430
|
+
headSha: entry.pullRequestHeadSha || entry.headSha || "",
|
|
431
|
+
state: entry.state || "",
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
queueEntryId: fallback.queueEntryId || "",
|
|
436
|
+
pullRequestNumber: fallback.pullRequestNumber || null,
|
|
437
|
+
headSha: fallback.headSha || "",
|
|
438
|
+
state: fallback.state || "",
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function admissionReceipt({
|
|
443
|
+
options,
|
|
444
|
+
pr,
|
|
445
|
+
expectedBaseSha,
|
|
446
|
+
observedBaseSha,
|
|
447
|
+
expectedHeadSha,
|
|
448
|
+
observedHeadSha,
|
|
449
|
+
decision,
|
|
450
|
+
reason,
|
|
451
|
+
checks,
|
|
452
|
+
approval,
|
|
453
|
+
predecessor,
|
|
454
|
+
} = {}) {
|
|
455
|
+
return {
|
|
456
|
+
schemaVersion: 1,
|
|
457
|
+
contract: ADMISSION_CONTRACT,
|
|
458
|
+
repository: options.repository.fullName,
|
|
459
|
+
targetBranch: options.targetBranch,
|
|
460
|
+
pullRequestNumber: pr.number,
|
|
461
|
+
expectedBaseSha: expectedBaseSha || "",
|
|
462
|
+
observedBaseSha: observedBaseSha || "",
|
|
463
|
+
expectedHeadSha: expectedHeadSha || "",
|
|
464
|
+
observedHeadSha: observedHeadSha || "",
|
|
465
|
+
approvalRequired: options.requireApproval,
|
|
466
|
+
approval: approval || { required: options.requireApproval, passed: false },
|
|
467
|
+
checks: checks || { required: options.requiredChecks, entries: [], passed: false },
|
|
468
|
+
decision,
|
|
469
|
+
reason,
|
|
470
|
+
predecessor: predecessor || null,
|
|
471
|
+
finalSafetyBoundary: "github-merge-group",
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function evaluatedEntry(pr, decision) {
|
|
476
|
+
return {
|
|
477
|
+
number: pr.number,
|
|
478
|
+
title: pr.title || "",
|
|
479
|
+
headRef: pr.head?.ref || "",
|
|
480
|
+
headSha: pr.head?.sha || "",
|
|
481
|
+
action: decision.action,
|
|
482
|
+
reason: decision.reason,
|
|
483
|
+
checks: decision.checks,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function blockRemainingPullRequests(result, pullRequests, startIndex, options, expectedBaseSha, predecessor) {
|
|
488
|
+
for (const pr of pullRequests.slice(startIndex)) {
|
|
489
|
+
const entry = evaluatedEntry(pr, {
|
|
490
|
+
action: "skip",
|
|
491
|
+
reason: "blocked-by-predecessor",
|
|
492
|
+
});
|
|
493
|
+
entry.admissionReceipt = admissionReceipt({
|
|
494
|
+
options,
|
|
495
|
+
pr,
|
|
496
|
+
expectedBaseSha,
|
|
497
|
+
observedBaseSha: expectedBaseSha,
|
|
498
|
+
expectedHeadSha: pr.head?.sha || "",
|
|
499
|
+
observedHeadSha: pr.head?.sha || "",
|
|
500
|
+
decision: "blocked",
|
|
501
|
+
reason: "blocked-by-predecessor",
|
|
502
|
+
predecessor,
|
|
503
|
+
});
|
|
504
|
+
result.evaluated.push(entry);
|
|
505
|
+
result.skipped.push(entry);
|
|
506
|
+
}
|
|
305
507
|
}
|
|
306
508
|
|
|
307
509
|
export async function runDevPrAutoMerge(optionsInput = {}, clientInput) {
|
|
@@ -313,21 +515,51 @@ export async function runDevPrAutoMerge(optionsInput = {}, clientInput) {
|
|
|
313
515
|
apiUrl: optionsInput.apiUrl || process.env.GITHUB_API_URL || "https://api.github.com",
|
|
314
516
|
});
|
|
315
517
|
|
|
316
|
-
const pullRequests = await
|
|
518
|
+
const [pullRequests, initialBaseSha, initialQueueState] = await Promise.all([
|
|
519
|
+
client.listPullRequests(options.targetBranch),
|
|
520
|
+
client.getBranchSha(options.targetBranch).catch(() => ""),
|
|
521
|
+
client.getMergeQueueState(options.targetBranch),
|
|
522
|
+
]);
|
|
523
|
+
const landingMode = initialQueueState.enabled ? "queue" : options.landingMode === "queue" ? "queue" : "direct";
|
|
524
|
+
const orderedPullRequests = landingMode === "queue"
|
|
525
|
+
? [...pullRequests].sort((left, right) => Number(left.number) - Number(right.number))
|
|
526
|
+
: pullRequests;
|
|
317
527
|
const result = {
|
|
318
528
|
schemaVersion: 1,
|
|
319
529
|
contract: "kungfu-buildchain-dev-pr-auto-merge",
|
|
320
530
|
repository: options.repository.fullName,
|
|
321
531
|
targetBranch: options.targetBranch,
|
|
532
|
+
requestedLandingMode: options.landingMode,
|
|
533
|
+
landingMode,
|
|
322
534
|
dryRun: options.dryRun,
|
|
323
535
|
maxMerges: options.maxMerges,
|
|
324
536
|
evaluated: [],
|
|
537
|
+
actions: [],
|
|
325
538
|
merged: [],
|
|
539
|
+
enqueued: [],
|
|
326
540
|
skipped: [],
|
|
327
|
-
|
|
541
|
+
initialBaseSha,
|
|
542
|
+
finalBaseSha: initialBaseSha,
|
|
543
|
+
mergeQueue: initialQueueState,
|
|
328
544
|
};
|
|
329
545
|
|
|
330
|
-
|
|
546
|
+
if (landingMode === "queue" && !initialQueueState.enabled) {
|
|
547
|
+
blockRemainingPullRequests(result, orderedPullRequests, 0, options, initialBaseSha, null);
|
|
548
|
+
for (const entry of result.evaluated) {
|
|
549
|
+
entry.reason = "merge-queue-not-enabled";
|
|
550
|
+
entry.admissionReceipt.reason = "merge-queue-not-enabled";
|
|
551
|
+
entry.admissionReceipt.decision = "rejected";
|
|
552
|
+
}
|
|
553
|
+
return result;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (landingMode === "queue" && initialQueueState.entries.length > 0) {
|
|
557
|
+
blockRemainingPullRequests(result, orderedPullRequests, 0, options, initialBaseSha, queuePredecessor(initialQueueState));
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
for (let index = 0; index < orderedPullRequests.length; index += 1) {
|
|
562
|
+
const pr = orderedPullRequests[index];
|
|
331
563
|
if (result.merged.length >= options.maxMerges) {
|
|
332
564
|
const entry = { number: pr.number, title: pr.title || "", action: "skip", reason: "max-merges-reached" };
|
|
333
565
|
result.evaluated.push(entry);
|
|
@@ -336,29 +568,134 @@ export async function runDevPrAutoMerge(optionsInput = {}, clientInput) {
|
|
|
336
568
|
}
|
|
337
569
|
|
|
338
570
|
const decision = await evaluatePullRequest(pr, options, client);
|
|
339
|
-
const entry =
|
|
340
|
-
number: pr.number,
|
|
341
|
-
title: pr.title || "",
|
|
342
|
-
headRef: pr.head?.ref || "",
|
|
343
|
-
headSha: pr.head?.sha || "",
|
|
344
|
-
action: decision.action,
|
|
345
|
-
reason: decision.reason,
|
|
346
|
-
checks: decision.checks,
|
|
347
|
-
};
|
|
571
|
+
const entry = evaluatedEntry(pr, decision);
|
|
348
572
|
result.evaluated.push(entry);
|
|
349
573
|
|
|
350
|
-
if (decision.action === "merge") {
|
|
574
|
+
if (landingMode === "direct" && decision.action === "merge") {
|
|
351
575
|
const mergeResult = await client.mergePullRequest(pr.number, {
|
|
352
576
|
method: options.mergeMethod,
|
|
353
577
|
sha: pr.head?.sha,
|
|
354
578
|
});
|
|
355
579
|
const mergedEntry = { ...entry, mergeSha: mergeResult.sha || "" };
|
|
356
580
|
result.merged.push(mergedEntry);
|
|
581
|
+
result.actions.push(mergedEntry);
|
|
357
582
|
result.evaluated[result.evaluated.length - 1] = mergedEntry;
|
|
358
|
-
} else if (decision.action === "would-merge") {
|
|
583
|
+
} else if (landingMode === "direct" && decision.action === "would-merge") {
|
|
359
584
|
result.merged.push(entry);
|
|
360
|
-
|
|
585
|
+
result.actions.push(entry);
|
|
586
|
+
} else if (landingMode === "direct" || STATIC_SKIP_REASONS.has(decision.reason)) {
|
|
587
|
+
result.skipped.push(entry);
|
|
588
|
+
} else if (decision.action === "skip") {
|
|
589
|
+
entry.admissionReceipt = admissionReceipt({
|
|
590
|
+
options,
|
|
591
|
+
pr,
|
|
592
|
+
expectedBaseSha: initialBaseSha,
|
|
593
|
+
observedBaseSha: initialBaseSha,
|
|
594
|
+
expectedHeadSha: decision.observedHeadSha || pr.head?.sha || "",
|
|
595
|
+
observedHeadSha: decision.observedHeadSha || pr.head?.sha || "",
|
|
596
|
+
decision: "rejected",
|
|
597
|
+
reason: decision.reason,
|
|
598
|
+
checks: decision.checks,
|
|
599
|
+
approval: decision.approval,
|
|
600
|
+
});
|
|
361
601
|
result.skipped.push(entry);
|
|
602
|
+
blockRemainingPullRequests(result, orderedPullRequests, index + 1, options, initialBaseSha, queuePredecessor(null, {
|
|
603
|
+
pullRequestNumber: pr.number,
|
|
604
|
+
headSha: decision.observedHeadSha || pr.head?.sha || "",
|
|
605
|
+
state: "ADMISSION_REJECTED",
|
|
606
|
+
}));
|
|
607
|
+
break;
|
|
608
|
+
} else {
|
|
609
|
+
const expectedHeadSha = decision.observedHeadSha || pr.head?.sha || "";
|
|
610
|
+
const [observedPullRequest, observedBaseSha, observedQueueState] = await Promise.all([
|
|
611
|
+
client.getPullRequest(pr.number, {
|
|
612
|
+
attempts: options.pollMergeableAttempts,
|
|
613
|
+
delayMs: options.pollMergeableDelayMs,
|
|
614
|
+
}),
|
|
615
|
+
client.getBranchSha(options.targetBranch),
|
|
616
|
+
client.getMergeQueueState(options.targetBranch),
|
|
617
|
+
]);
|
|
618
|
+
const observedHeadSha = observedPullRequest.head?.sha || "";
|
|
619
|
+
let admissionDecision = options.dryRun ? "planned" : "accepted";
|
|
620
|
+
let admissionReason = options.dryRun ? "dry-run" : "eligible";
|
|
621
|
+
let predecessor = null;
|
|
622
|
+
if (observedQueueState.entries.length > 0) {
|
|
623
|
+
admissionDecision = "blocked";
|
|
624
|
+
admissionReason = "blocked-by-predecessor";
|
|
625
|
+
predecessor = queuePredecessor(observedQueueState);
|
|
626
|
+
} else if (observedBaseSha !== initialBaseSha) {
|
|
627
|
+
admissionDecision = "rejected";
|
|
628
|
+
admissionReason = "base-sha-drift";
|
|
629
|
+
} else if (observedHeadSha !== expectedHeadSha) {
|
|
630
|
+
admissionDecision = "rejected";
|
|
631
|
+
admissionReason = "head-sha-drift";
|
|
632
|
+
} else if (!mergeableAccepted(observedPullRequest)) {
|
|
633
|
+
admissionDecision = "rejected";
|
|
634
|
+
admissionReason = "not-mergeable-on-admission-recheck";
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
entry.action = admissionDecision === "planned" ? "would-enqueue" : admissionDecision === "accepted" ? "enqueue" : "skip";
|
|
638
|
+
entry.reason = admissionReason;
|
|
639
|
+
entry.headSha = expectedHeadSha;
|
|
640
|
+
entry.admissionReceipt = admissionReceipt({
|
|
641
|
+
options,
|
|
642
|
+
pr,
|
|
643
|
+
expectedBaseSha: initialBaseSha,
|
|
644
|
+
observedBaseSha,
|
|
645
|
+
expectedHeadSha,
|
|
646
|
+
observedHeadSha,
|
|
647
|
+
decision: admissionDecision,
|
|
648
|
+
reason: admissionReason,
|
|
649
|
+
checks: decision.checks,
|
|
650
|
+
approval: decision.approval,
|
|
651
|
+
predecessor,
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
if (admissionDecision === "planned") {
|
|
655
|
+
result.actions.push(entry);
|
|
656
|
+
} else if (admissionDecision === "accepted") {
|
|
657
|
+
if (!decision.pullRequestId) {
|
|
658
|
+
entry.action = "skip";
|
|
659
|
+
entry.reason = "missing-pull-request-node-id";
|
|
660
|
+
entry.admissionReceipt.decision = "rejected";
|
|
661
|
+
entry.admissionReceipt.reason = entry.reason;
|
|
662
|
+
result.skipped.push(entry);
|
|
663
|
+
} else {
|
|
664
|
+
try {
|
|
665
|
+
const queueEntry = await client.enqueuePullRequest({
|
|
666
|
+
pullRequestId: decision.pullRequestId,
|
|
667
|
+
expectedHeadOid: expectedHeadSha,
|
|
668
|
+
});
|
|
669
|
+
entry.action = "enqueued";
|
|
670
|
+
entry.reason = "enqueued-with-expected-head";
|
|
671
|
+
entry.queueEntry = queueEntry;
|
|
672
|
+
entry.admissionReceipt.reason = entry.reason;
|
|
673
|
+
result.actions.push(entry);
|
|
674
|
+
result.enqueued.push(entry);
|
|
675
|
+
} catch (error) {
|
|
676
|
+
entry.action = "skip";
|
|
677
|
+
entry.reason = "enqueue-rejected";
|
|
678
|
+
entry.enqueueError = {
|
|
679
|
+
status: error.status || null,
|
|
680
|
+
message: error.message || "GitHub rejected merge queue admission",
|
|
681
|
+
};
|
|
682
|
+
entry.admissionReceipt.decision = "rejected";
|
|
683
|
+
entry.admissionReceipt.reason = entry.reason;
|
|
684
|
+
result.skipped.push(entry);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
result.skipped.push(entry);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const activePredecessor = queuePredecessor(null, {
|
|
692
|
+
queueEntryId: entry.queueEntry?.id || predecessor?.queueEntryId || "",
|
|
693
|
+
pullRequestNumber: pr.number,
|
|
694
|
+
headSha: expectedHeadSha,
|
|
695
|
+
state: entry.queueEntry?.state || (admissionDecision === "planned" ? "ADMISSION_PLANNED" : "ADMISSION_REJECTED"),
|
|
696
|
+
});
|
|
697
|
+
blockRemainingPullRequests(result, orderedPullRequests, index + 1, options, observedBaseSha || initialBaseSha, activePredecessor);
|
|
698
|
+
break;
|
|
362
699
|
}
|
|
363
700
|
}
|
|
364
701
|
|
|
@@ -372,9 +709,10 @@ export function renderMarkdownSummary(result) {
|
|
|
372
709
|
"",
|
|
373
710
|
`Repository: \`${result.repository}\``,
|
|
374
711
|
`Target branch: \`${result.targetBranch}\``,
|
|
375
|
-
`
|
|
712
|
+
`Landing mode: \`${result.landingMode}\``,
|
|
713
|
+
`Execution: \`${result.dryRun ? "dry-run" : "apply"}\``,
|
|
376
714
|
`Evaluated PRs: ${result.evaluated.length}`,
|
|
377
|
-
`
|
|
715
|
+
`Actions ${result.dryRun ? "planned" : "taken"}: ${result.actions.length}`,
|
|
378
716
|
"",
|
|
379
717
|
"| PR | Action | Reason | Head |",
|
|
380
718
|
"| --- | --- | --- | --- |",
|
|
@@ -410,6 +748,7 @@ async function main() {
|
|
|
410
748
|
sameRepositoryOnly: process.env.BUILDCHAIN_DEV_PR_SAME_REPOSITORY_ONLY,
|
|
411
749
|
maxMerges: process.env.BUILDCHAIN_DEV_PR_MAX_MERGES,
|
|
412
750
|
mergeMethod: process.env.BUILDCHAIN_DEV_PR_MERGE_METHOD,
|
|
751
|
+
landingMode: process.env.BUILDCHAIN_DEV_PR_LANDING_MODE,
|
|
413
752
|
dryRun: process.env.BUILDCHAIN_DEV_PR_DRY_RUN,
|
|
414
753
|
outputPath: process.env.BUILDCHAIN_DEV_PR_OUTPUT_PATH,
|
|
415
754
|
});
|
|
@@ -422,6 +761,8 @@ async function main() {
|
|
|
422
761
|
writeGitHubOutputs({
|
|
423
762
|
"evaluated-count": result.evaluated.length,
|
|
424
763
|
"merged-count": result.merged.length,
|
|
764
|
+
"enqueued-count": result.enqueued.length,
|
|
765
|
+
"action-count": result.actions.length,
|
|
425
766
|
"skipped-count": result.skipped.length,
|
|
426
767
|
"final-base-sha": result.finalBaseSha,
|
|
427
768
|
"result-path": options.outputPath,
|