@mutmutco/cli 4.5.145 → 4.5.147
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/main.cjs +128 -59
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -18277,10 +18277,10 @@ var rollout_plan_default = {
|
|
|
18277
18277
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
18278
18278
|
},
|
|
18279
18279
|
baseline: {
|
|
18280
|
-
version: "4.5.
|
|
18281
|
-
tag: "v4.5.
|
|
18282
|
-
commit: "
|
|
18283
|
-
npm: "@mutmutco/cli@4.5.
|
|
18280
|
+
version: "4.5.147",
|
|
18281
|
+
tag: "v4.5.147",
|
|
18282
|
+
commit: "c21970738b82",
|
|
18283
|
+
npm: "@mutmutco/cli@4.5.147"
|
|
18284
18284
|
},
|
|
18285
18285
|
exitCriterion: "fleet-n-of-n",
|
|
18286
18286
|
hubOnlyShortcut: "forbidden",
|
|
@@ -18297,14 +18297,14 @@ var rollout_plan_default = {
|
|
|
18297
18297
|
repo: "mutmutco/mmi-hub",
|
|
18298
18298
|
role: "canary",
|
|
18299
18299
|
schedule: "train",
|
|
18300
|
-
v3Target: "v4.5.
|
|
18300
|
+
v3Target: "v4.5.147"
|
|
18301
18301
|
}
|
|
18302
18302
|
],
|
|
18303
18303
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
18304
18304
|
rollback: {
|
|
18305
18305
|
independent: true,
|
|
18306
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
18307
|
-
v3Target: "v4.5.
|
|
18306
|
+
mechanism: "npm dist-tag latest -> 4.5.147 and redeploy the Hub Lambda from tag v4.5.147 (c21970738b82); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
18307
|
+
v3Target: "v4.5.147 (@mutmutco/cli@4.5.147, tag commit c21970738b82 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
18308
18308
|
}
|
|
18309
18309
|
},
|
|
18310
18310
|
{
|
|
@@ -39981,6 +39981,7 @@ async function updatePrHeadFromBase(input) {
|
|
|
39981
39981
|
throw new Error(`GitHub accepted the update of ${input.head} from ${input.base} but the head did not move \u2014 check the PR, then rerun`);
|
|
39982
39982
|
}
|
|
39983
39983
|
var PR_LAND_POLL_MS = 6e4;
|
|
39984
|
+
var PR_LAND_EJECT_CONFIRM_MS = 2e4;
|
|
39984
39985
|
var PR_LAND_ENQUEUE_TIMEOUT_MS = 10 * 6e4;
|
|
39985
39986
|
var PR_LAND_STATE_READ_RETRIES = 3;
|
|
39986
39987
|
var PR_LAND_STATE_READ_DELAY_MS = 15e3;
|
|
@@ -40155,6 +40156,13 @@ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
|
|
|
40155
40156
|
deps.queueProgress?.(base);
|
|
40156
40157
|
if (options.enqueueOnly) return { ...base, status: "queued" };
|
|
40157
40158
|
} else if (read.state === "OPEN" && !read.autoMergeRequest) {
|
|
40159
|
+
await sleep2(PR_LAND_EJECT_CONFIRM_MS);
|
|
40160
|
+
const again = await deps.readMergeQueue(pr, base.repo);
|
|
40161
|
+
if (again.state === "MERGED") return queueMerged(base, deps);
|
|
40162
|
+
if (again.isInMergeQueue || again.autoMergeRequest) {
|
|
40163
|
+
snapshot = again;
|
|
40164
|
+
continue;
|
|
40165
|
+
}
|
|
40158
40166
|
const run = await deps.findMergeGroupRun(pr, base.repo, base.base);
|
|
40159
40167
|
const result2 = {
|
|
40160
40168
|
...base,
|
|
@@ -41226,6 +41234,32 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh2, opts) {
|
|
|
41226
41234
|
return void 0;
|
|
41227
41235
|
}
|
|
41228
41236
|
}
|
|
41237
|
+
async function ghAdminTrueMergeWhenClean(prNumber, repo, opts = {}) {
|
|
41238
|
+
const io = opts.io ?? defaultGhMergeAutoIo;
|
|
41239
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
41240
|
+
const pollMs = opts.pollMs ?? 15e3;
|
|
41241
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 20 * 6e4);
|
|
41242
|
+
for (; ; ) {
|
|
41243
|
+
const raw = await io.gh(["pr", "view", prNumber, "--repo", repo, "--json", "state,mergeStateStatus"], GC_GH_TIMEOUT_MS).catch(() => "");
|
|
41244
|
+
let view = {};
|
|
41245
|
+
try {
|
|
41246
|
+
view = JSON.parse(raw);
|
|
41247
|
+
} catch {
|
|
41248
|
+
}
|
|
41249
|
+
if (view.state === "MERGED") return { mergeStatus: "merged" };
|
|
41250
|
+
if (view.state === "CLOSED") return { mergeStatus: "failed", error: `PR #${prNumber} was closed without a merge` };
|
|
41251
|
+
if (view.mergeStateStatus === "CLEAN") break;
|
|
41252
|
+
if (Date.now() >= deadline) return { mergeStatus: "failed", error: `PR #${prNumber} not CLEAN before the deadline (${view.mergeStateStatus ?? "unread"})` };
|
|
41253
|
+
await sleep2(pollMs);
|
|
41254
|
+
}
|
|
41255
|
+
try {
|
|
41256
|
+
await io.gh(["pr", "merge", prNumber, "--repo", repo, "--merge", "--admin"], GH_MUTATION_TIMEOUT_MS);
|
|
41257
|
+
} catch (e) {
|
|
41258
|
+
const message2 = String(e.message || "");
|
|
41259
|
+
if (!/already been merged/i.test(message2)) return { mergeStatus: "failed", error: ghFailureReason(e) };
|
|
41260
|
+
}
|
|
41261
|
+
return { mergeStatus: "merged" };
|
|
41262
|
+
}
|
|
41229
41263
|
async function ghMergeAutoEnqueue(prNumber, repo, method, extra) {
|
|
41230
41264
|
const io = extra?.io ?? defaultGhMergeAutoIo;
|
|
41231
41265
|
const args = repo ? ["--repo", repo] : [];
|
|
@@ -41414,6 +41448,69 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
41414
41448
|
};
|
|
41415
41449
|
}
|
|
41416
41450
|
|
|
41451
|
+
// src/pr-merge-queue.ts
|
|
41452
|
+
init_cli_shared();
|
|
41453
|
+
var readApi = async (args) => {
|
|
41454
|
+
const env = await ghReadEnv();
|
|
41455
|
+
return (await execFileP("gh", ["api", ...args], {
|
|
41456
|
+
timeout: 2e4,
|
|
41457
|
+
...env ? { env } : {}
|
|
41458
|
+
})).stdout;
|
|
41459
|
+
};
|
|
41460
|
+
async function queueGraphql(repo, fields, gh2) {
|
|
41461
|
+
const [owner, name] = repo.split("/");
|
|
41462
|
+
const response = JSON.parse(await gh2(["graphql", "-f", `query=query { repository(owner:${JSON.stringify(owner)},name:${JSON.stringify(name)}) { ${fields} } }`]));
|
|
41463
|
+
if (response.errors?.length) throw new Error(response.errors.map((e) => e.message).join("; "));
|
|
41464
|
+
if (!response.data?.repository) throw new Error("merge queue repository read failed");
|
|
41465
|
+
return response.data.repository;
|
|
41466
|
+
}
|
|
41467
|
+
async function hasPrMergeQueue(repo, base, gh2 = readApi) {
|
|
41468
|
+
const result = await queueGraphql(repo, `mergeQueue(branch:${JSON.stringify(base)}) { id }`, gh2);
|
|
41469
|
+
return result.mergeQueue !== null;
|
|
41470
|
+
}
|
|
41471
|
+
async function readPrMergeQueueMethod(repo, base, gh2 = readApi) {
|
|
41472
|
+
const result = await queueGraphql(
|
|
41473
|
+
repo,
|
|
41474
|
+
`mergeQueue(branch:${JSON.stringify(base)}) { configuration { mergeMethod } }`,
|
|
41475
|
+
gh2
|
|
41476
|
+
);
|
|
41477
|
+
return result.mergeQueue ? result.mergeQueue.configuration?.mergeMethod ?? "UNKNOWN" : void 0;
|
|
41478
|
+
}
|
|
41479
|
+
function decideQueueMergeRoute(queueMethod, method, headRef) {
|
|
41480
|
+
if (!queueMethod || `--${queueMethod.toLowerCase()}` === method) return "normal";
|
|
41481
|
+
return method === "--merge" && /^(release-align|hotfix-fold)\//.test(headRef) ? "admin-true-merge" : "refuse";
|
|
41482
|
+
}
|
|
41483
|
+
async function readPrMergeQueue(pr, repo, gh2 = readApi) {
|
|
41484
|
+
if (!/^\d+$/.test(pr)) throw new Error("merge queue read requires a PR number");
|
|
41485
|
+
const result = await queueGraphql(
|
|
41486
|
+
repo,
|
|
41487
|
+
`pullRequest(number:${pr}) { state isInMergeQueue mergeQueueEntry { state position } autoMergeRequest { enabledAt } }`,
|
|
41488
|
+
gh2
|
|
41489
|
+
);
|
|
41490
|
+
if (!result.pullRequest) throw new Error(`merge queue PR #${pr} not found`);
|
|
41491
|
+
return result.pullRequest;
|
|
41492
|
+
}
|
|
41493
|
+
async function findPrMergeGroupRun(pr, repo, base, gh2 = readApi) {
|
|
41494
|
+
const pages = JSON.parse(await gh2([`repos/${repo}/actions/runs?event=merge_group&per_page=100`, "--paginate", "--slurp"]));
|
|
41495
|
+
const prefix = `gh-readonly-queue/${base}/pr-${pr}-`;
|
|
41496
|
+
const run = pages.flatMap((page) => page.workflow_runs).filter((run2) => run2.head_branch?.startsWith(prefix)).sort((a, b) => b.created_at.localeCompare(a.created_at) || b.id - a.id)[0];
|
|
41497
|
+
return run ? { id: run.id, url: run.html_url, conclusion: run.conclusion } : void 0;
|
|
41498
|
+
}
|
|
41499
|
+
function prMergeQueueLine(result) {
|
|
41500
|
+
const queue = result.mergeQueue;
|
|
41501
|
+
if (!queue) return void 0;
|
|
41502
|
+
switch (queue.state) {
|
|
41503
|
+
case "queued":
|
|
41504
|
+
return `pr land: PR #${result.pr} queued on ${queue.base}${queue.position === void 0 ? "" : ` (position ${queue.position})`}`;
|
|
41505
|
+
case "merged":
|
|
41506
|
+
return `pr land: PR #${result.pr} merged via merge queue`;
|
|
41507
|
+
case "ejected":
|
|
41508
|
+
return `pr land: PR #${result.pr} ejected from merge queue \u2014 ${queue.ejectedRun ? `merge_group run ${queue.ejectedRun.id} ${queue.ejectedRun.conclusion} ${queue.ejectedRun.url}` : "merge_group run not found"}`;
|
|
41509
|
+
case "timeout":
|
|
41510
|
+
return `pr land: PR #${result.pr} merge queue timeout on ${queue.base}`;
|
|
41511
|
+
}
|
|
41512
|
+
}
|
|
41513
|
+
|
|
41417
41514
|
// src/project-info-sync.ts
|
|
41418
41515
|
var import_node_fs34 = require("node:fs");
|
|
41419
41516
|
var import_node_path33 = require("node:path");
|
|
@@ -42022,57 +42119,6 @@ function applyChecklistCheck(body, query, checked) {
|
|
|
42022
42119
|
return { ok: true, edit: setChecklistMarker(body, sel.item, checked), item: sel.item };
|
|
42023
42120
|
}
|
|
42024
42121
|
|
|
42025
|
-
// src/pr-merge-queue.ts
|
|
42026
|
-
init_cli_shared();
|
|
42027
|
-
var readApi = async (args) => {
|
|
42028
|
-
const env = await ghReadEnv();
|
|
42029
|
-
return (await execFileP("gh", ["api", ...args], {
|
|
42030
|
-
timeout: 2e4,
|
|
42031
|
-
...env ? { env } : {}
|
|
42032
|
-
})).stdout;
|
|
42033
|
-
};
|
|
42034
|
-
async function queueGraphql(repo, fields, gh2) {
|
|
42035
|
-
const [owner, name] = repo.split("/");
|
|
42036
|
-
const response = JSON.parse(await gh2(["graphql", "-f", `query=query { repository(owner:${JSON.stringify(owner)},name:${JSON.stringify(name)}) { ${fields} } }`]));
|
|
42037
|
-
if (response.errors?.length) throw new Error(response.errors.map((e) => e.message).join("; "));
|
|
42038
|
-
if (!response.data?.repository) throw new Error("merge queue repository read failed");
|
|
42039
|
-
return response.data.repository;
|
|
42040
|
-
}
|
|
42041
|
-
async function hasPrMergeQueue(repo, base, gh2 = readApi) {
|
|
42042
|
-
const result = await queueGraphql(repo, `mergeQueue(branch:${JSON.stringify(base)}) { id }`, gh2);
|
|
42043
|
-
return result.mergeQueue !== null;
|
|
42044
|
-
}
|
|
42045
|
-
async function readPrMergeQueue(pr, repo, gh2 = readApi) {
|
|
42046
|
-
if (!/^\d+$/.test(pr)) throw new Error("merge queue read requires a PR number");
|
|
42047
|
-
const result = await queueGraphql(
|
|
42048
|
-
repo,
|
|
42049
|
-
`pullRequest(number:${pr}) { state isInMergeQueue mergeQueueEntry { state position } autoMergeRequest { enabledAt } }`,
|
|
42050
|
-
gh2
|
|
42051
|
-
);
|
|
42052
|
-
if (!result.pullRequest) throw new Error(`merge queue PR #${pr} not found`);
|
|
42053
|
-
return result.pullRequest;
|
|
42054
|
-
}
|
|
42055
|
-
async function findPrMergeGroupRun(pr, repo, base, gh2 = readApi) {
|
|
42056
|
-
const pages = JSON.parse(await gh2([`repos/${repo}/actions/runs?event=merge_group&per_page=100`, "--paginate", "--slurp"]));
|
|
42057
|
-
const prefix = `gh-readonly-queue/${base}/pr-${pr}-`;
|
|
42058
|
-
const run = pages.flatMap((page) => page.workflow_runs).filter((run2) => run2.head_branch?.startsWith(prefix)).sort((a, b) => b.created_at.localeCompare(a.created_at) || b.id - a.id)[0];
|
|
42059
|
-
return run ? { id: run.id, url: run.html_url, conclusion: run.conclusion } : void 0;
|
|
42060
|
-
}
|
|
42061
|
-
function prMergeQueueLine(result) {
|
|
42062
|
-
const queue = result.mergeQueue;
|
|
42063
|
-
if (!queue) return void 0;
|
|
42064
|
-
switch (queue.state) {
|
|
42065
|
-
case "queued":
|
|
42066
|
-
return `pr land: PR #${result.pr} queued on ${queue.base}${queue.position === void 0 ? "" : ` (position ${queue.position})`}`;
|
|
42067
|
-
case "merged":
|
|
42068
|
-
return `pr land: PR #${result.pr} merged via merge queue`;
|
|
42069
|
-
case "ejected":
|
|
42070
|
-
return `pr land: PR #${result.pr} ejected from merge queue \u2014 ${queue.ejectedRun ? `merge_group run ${queue.ejectedRun.id} ${queue.ejectedRun.conclusion} ${queue.ejectedRun.url}` : "merge_group run not found"}`;
|
|
42071
|
-
case "timeout":
|
|
42072
|
-
return `pr land: PR #${result.pr} merge queue timeout on ${queue.base}`;
|
|
42073
|
-
}
|
|
42074
|
-
}
|
|
42075
|
-
|
|
42076
42122
|
// src/command-register-collaboration.ts
|
|
42077
42123
|
init_github_client();
|
|
42078
42124
|
init_github_quota();
|
|
@@ -47351,6 +47397,25 @@ ${list}`);
|
|
|
47351
47397
|
return true;
|
|
47352
47398
|
};
|
|
47353
47399
|
if (o.wait && !await runWaitGate()) return;
|
|
47400
|
+
if (prMeta.state === "OPEN") {
|
|
47401
|
+
const queueMethod = await readPrMergeQueueMethod(repoForPostCleanup, baseRef);
|
|
47402
|
+
const route = decideQueueMergeRoute(queueMethod, method, headRef);
|
|
47403
|
+
if (route === "refuse") {
|
|
47404
|
+
throw new Error(`pr merge: ${baseRef} in ${repoForPostCleanup} merges through a ${queueMethod} merge queue, which would land ${method} as ${queueMethod}. Use --squash (or pr land); only release-align/ and hotfix-fold/ train PRs true-merge outside the queue (#8304).`);
|
|
47405
|
+
}
|
|
47406
|
+
if (route === "admin-true-merge") {
|
|
47407
|
+
console.warn(`pr merge: ${baseRef} has a ${queueMethod} merge queue \u2014 landing train PR #${number} as a true merge through the ruleset bypass once it is CLEAN (#8296).`);
|
|
47408
|
+
const res = await ghAdminTrueMergeWhenClean(number, repoForPostCleanup);
|
|
47409
|
+
if (res.mergeStatus !== "merged") {
|
|
47410
|
+
console.log(JSON.stringify({ mergeStatus: "not-merged", reason: "admin-true-merge-failed", pr: number, branch: headRef, error: res.error }));
|
|
47411
|
+
console.error(`pr merge: train PR #${number} not merged \u2014 ${res.error}`);
|
|
47412
|
+
process.exitCode = 1;
|
|
47413
|
+
return;
|
|
47414
|
+
}
|
|
47415
|
+
console.log(JSON.stringify({ mergeStatus: "merged", method: "merge", via: "ruleset-bypass", pr: number, branch: headRef }));
|
|
47416
|
+
return;
|
|
47417
|
+
}
|
|
47418
|
+
}
|
|
47354
47419
|
if (ciPolicy.policy === "no-ci") {
|
|
47355
47420
|
const guard = decidePrMergeNoCiGuard(await pollGhPrChecks(number, repoArgs), ciPolicy.reason);
|
|
47356
47421
|
if (guard.action === "refuse") throw new Error(`gh pr merge ${number}: ${guard.message}`);
|
|
@@ -47825,7 +47890,11 @@ function trainApplyDeps() {
|
|
|
47825
47890
|
// #2737: enqueue GitHub auto-merge on the `main -> development` alignment PR the release opens, so the
|
|
47826
47891
|
// train lands the back-merge itself instead of leaving a manual step. Same enqueue path `pr land` uses,
|
|
47827
47892
|
// but method `--merge` (NOT squash): the misalignment guard needs the merge parentage to survive.
|
|
47828
|
-
|
|
47893
|
+
// #8296: a merge-queue base squashes, so there the train waits for green and true-merges via admin bypass.
|
|
47894
|
+
mergeAuto: async (prNumber, repo) => {
|
|
47895
|
+
const base = (await execFileP("gh", ["pr", "view", prNumber, "--repo", repo, "--json", "baseRefName", "--jq", ".baseRefName"], { timeout: GH_TRAIN_TIMEOUT_MS })).stdout.trim();
|
|
47896
|
+
return base && await hasPrMergeQueue(repo, base) ? ghAdminTrueMergeWhenClean(prNumber, repo) : ghMergeAutoEnqueue(prNumber, repo, "--merge");
|
|
47897
|
+
},
|
|
47829
47898
|
// Slack release announcement (#883/#6521/#6523): the org alerts channel for the Hub, the project's own
|
|
47830
47899
|
// registry releaseChannel META for a product repo — best-effort inside announceRelease itself.
|
|
47831
47900
|
announce: (args) => announceRelease({
|
package/package.json
CHANGED