@mutmutco/cli 3.44.0 → 3.46.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.
Files changed (2) hide show
  1. package/dist/main.cjs +71 -33
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -15432,11 +15432,15 @@ function renderDocsIndex(entries) {
15432
15432
  }
15433
15433
  return lines.join("\n") + "\n";
15434
15434
  }
15435
+ function sameContent(a, b) {
15436
+ const normalize = (text) => text.replace(/\r\n/g, "\n");
15437
+ return normalize(a) === normalize(b);
15438
+ }
15435
15439
  function docsIndex(deps, opts = {}) {
15436
15440
  const entries = deps.listDocs().slice().sort().map((relPath) => extractDocMeta(relPath, deps.readDoc(relPath)));
15437
15441
  const content = renderDocsIndex(entries);
15438
15442
  const current = deps.readIndex();
15439
- const drift = current !== content;
15443
+ const drift = current === null || !sameContent(current, content);
15440
15444
  let wrote = false;
15441
15445
  if (!opts.check && drift) {
15442
15446
  deps.writeIndex(content);
@@ -20424,13 +20428,46 @@ async function pollGhPrChecks(prNumber, repoArgs) {
20424
20428
  }
20425
20429
  return state;
20426
20430
  }
20427
- async function ghMergeAutoEnqueue(prNumber, repo, method) {
20431
+ function ghFailureReason(e) {
20432
+ const err = e;
20433
+ const head = String(err?.message ?? "").split("\n")[0]?.trim() ?? "";
20434
+ const detail = `${err?.stderr ?? ""}
20435
+ ${err?.stdout ?? ""}`.split("\n").map((line) => line.trim()).find((line) => line.length > 0);
20436
+ if (detail && head) return `${head} \u2014 ${detail}`;
20437
+ return detail || head || "unknown error";
20438
+ }
20439
+ var defaultGhMergeAutoIo = {
20440
+ gh: async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout
20441
+ };
20442
+ async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAutoIo) {
20428
20443
  const args = repo ? ["--repo", repo] : [];
20429
- const headRef = (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "headRefName", "--jq", ".headRefName"], { timeout: GC_GH_TIMEOUT_MS2 }).catch(() => ({ stdout: "" }))).stdout.trim();
20444
+ const headRef = (await io.gh(["pr", "view", prNumber, ...args, "--json", "headRefName", "--jq", ".headRefName"], GC_GH_TIMEOUT_MS2).catch(() => "")).trim();
20430
20445
  const deleteBranch = !isProtectedBranch(headRef);
20431
- const readMergeState = () => readGhPrStateWithRetry(async () => (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "state", "--jq", ".state"], { timeout: GC_GH_TIMEOUT_MS2 })).stdout);
20446
+ const readMergeState = () => readGhPrStateWithRetry(
20447
+ () => io.gh(["pr", "view", prNumber, ...args, "--json", "state", "--jq", ".state"], GC_GH_TIMEOUT_MS2)
20448
+ );
20449
+ const confirmEnqueueOutcome = async () => {
20450
+ const stateRead = await readMergeState();
20451
+ if (!stateRead.ok) {
20452
+ return { mergeStatus: "failed", error: `could not read PR state after merge: ${stateRead.error}` };
20453
+ }
20454
+ if (stateRead.state === "MERGED") return { mergeStatus: "merged" };
20455
+ const confirmation = await confirmAutoMergeEnqueued({
20456
+ readAutoMergeRequest: async () => io.gh(["pr", "view", prNumber, ...args, "--json", "autoMergeRequest", "--jq", '.autoMergeRequest // ""'], GC_GH_TIMEOUT_MS2),
20457
+ readMerged: async () => {
20458
+ const s = await readMergeState();
20459
+ return s.ok && s.state === "MERGED";
20460
+ },
20461
+ reEnqueue: async () => {
20462
+ await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
20463
+ }
20464
+ });
20465
+ if (confirmation === "merged") return { mergeStatus: "merged" };
20466
+ if (confirmation === "enqueued") return { mergeStatus: "auto-merge-enqueued" };
20467
+ return { mergeStatus: "failed", error: "auto-merge did not stick \u2014 GitHub reported no autoMergeRequest after enqueue; retry once the PR has a pending check" };
20468
+ };
20432
20469
  try {
20433
- await execFileP2("gh", buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), { timeout: GH_MUTATION_TIMEOUT_MS });
20470
+ await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
20434
20471
  } catch (e) {
20435
20472
  const message = String(e.message || "");
20436
20473
  if (/already been merged/i.test(message)) return { mergeStatus: "merged" };
@@ -20438,50 +20475,51 @@ async function ghMergeAutoEnqueue(prNumber, repo, method) {
20438
20475
  if (note) return { mergeStatus: "failed", error: note };
20439
20476
  if (mergeAutoRejectedPrAlreadyClean(message)) {
20440
20477
  try {
20441
- await execFileP2("gh", buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: false, deleteBranch }), { timeout: GH_MUTATION_TIMEOUT_MS });
20478
+ await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: false, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
20442
20479
  } catch (e2) {
20443
20480
  const m2 = String(e2.message || "");
20444
20481
  if (/already been merged/i.test(m2)) return { mergeStatus: "merged" };
20445
- if (!ghPrMergeLocalBranchDeleteWarning(m2)) return { mergeStatus: "failed", error: m2.split("\n")[0] };
20482
+ if (!ghPrMergeLocalBranchDeleteWarning(m2)) {
20483
+ const afterDirect = await readMergeState();
20484
+ if (afterDirect.ok && afterDirect.state === "MERGED") return { mergeStatus: "merged" };
20485
+ if (!afterDirect.ok) {
20486
+ return { mergeStatus: "failed", error: `could not confirm PR state after clean-status fallback: ${afterDirect.error}` };
20487
+ }
20488
+ try {
20489
+ await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
20490
+ } catch (e3) {
20491
+ const m3 = String(e3.message || "");
20492
+ if (/already been merged/i.test(m3)) return { mergeStatus: "merged" };
20493
+ const afterRetry = await readMergeState();
20494
+ if (afterRetry.ok && afterRetry.state === "MERGED") return { mergeStatus: "merged" };
20495
+ const stuck = (await io.gh(["pr", "view", prNumber, ...args, "--json", "autoMergeRequest", "--jq", '.autoMergeRequest // ""'], GC_GH_TIMEOUT_MS2).catch(() => "")).trim();
20496
+ if (stuck) return { mergeStatus: "auto-merge-enqueued" };
20497
+ return { mergeStatus: "failed", error: `${ghFailureReason(e2)} (re-enqueue also failed: ${ghFailureReason(e3)})` };
20498
+ }
20499
+ return confirmEnqueueOutcome();
20500
+ }
20446
20501
  }
20447
- const stateRead2 = await readMergeState();
20448
- if (stateRead2.ok && stateRead2.state === "MERGED") return { mergeStatus: "merged" };
20502
+ const stateRead = await readMergeState();
20503
+ if (stateRead.ok && stateRead.state === "MERGED") return { mergeStatus: "merged" };
20449
20504
  return {
20450
20505
  mergeStatus: "failed",
20451
- error: stateRead2.ok ? "direct merge did not land after clean-status fallback" : `could not confirm PR state after clean-status fallback: ${stateRead2.error}`
20506
+ error: stateRead.ok ? "direct merge did not land after clean-status fallback" : `could not confirm PR state after clean-status fallback: ${stateRead.error}`
20452
20507
  };
20453
20508
  }
20454
20509
  if (ghPrMergeLocalBranchDeleteWarning(message)) {
20455
- const stateRead2 = await readMergeState();
20456
- if (stateRead2.ok && stateRead2.state === "MERGED") return { mergeStatus: "merged" };
20510
+ const stateRead = await readMergeState();
20511
+ if (stateRead.ok && stateRead.state === "MERGED") return { mergeStatus: "merged" };
20457
20512
  return {
20458
20513
  mergeStatus: "failed",
20459
- error: stateRead2.ok ? message.split("\n")[0] : `could not confirm PR state after local branch cleanup warning: ${stateRead2.error}`
20514
+ error: stateRead.ok ? message.split("\n")[0] : `could not confirm PR state after local branch cleanup warning: ${stateRead.error}`
20460
20515
  };
20461
20516
  }
20462
20517
  if (!basePolicyBlocksImmediateMerge(message)) {
20463
- return { mergeStatus: "failed", error: message.split("\n")[0] };
20518
+ return { mergeStatus: "failed", error: ghFailureReason(e) };
20464
20519
  }
20465
- return { mergeStatus: "failed", error: `merge blocked: ${message.split("\n")[0]} \u2014 ensure checks are green` };
20520
+ return { mergeStatus: "failed", error: `merge blocked: ${ghFailureReason(e)} \u2014 ensure checks are green` };
20466
20521
  }
20467
- const stateRead = await readMergeState();
20468
- if (!stateRead.ok) {
20469
- return { mergeStatus: "failed", error: `could not read PR state after merge: ${stateRead.error}` };
20470
- }
20471
- if (stateRead.state === "MERGED") return { mergeStatus: "merged" };
20472
- const confirmation = await confirmAutoMergeEnqueued({
20473
- readAutoMergeRequest: async () => (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "autoMergeRequest", "--jq", '.autoMergeRequest // ""'], { timeout: GC_GH_TIMEOUT_MS2 })).stdout,
20474
- readMerged: async () => {
20475
- const s = await readMergeState();
20476
- return s.ok && s.state === "MERGED";
20477
- },
20478
- reEnqueue: async () => {
20479
- await execFileP2("gh", buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), { timeout: GH_MUTATION_TIMEOUT_MS });
20480
- }
20481
- });
20482
- if (confirmation === "merged") return { mergeStatus: "merged" };
20483
- if (confirmation === "enqueued") return { mergeStatus: "auto-merge-enqueued" };
20484
- return { mergeStatus: "failed", error: "auto-merge did not stick \u2014 GitHub reported no autoMergeRequest after enqueue; retry once the PR has a pending check" };
20522
+ return confirmEnqueueOutcome();
20485
20523
  }
20486
20524
  async function remoteBranchExists2(branch, options = {}) {
20487
20525
  return checkRemoteBranchExists(branch, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.44.0",
3
+ "version": "3.46.0",
4
4
  "description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",