@mutmutco/cli 4.1.11 → 4.1.13

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 +141 -17
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -13152,10 +13152,10 @@ var rollout_plan_default = {
13152
13152
  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)."
13153
13153
  },
13154
13154
  baseline: {
13155
- version: "4.1.11",
13156
- tag: "v4.1.11",
13157
- commit: "97f4b2119c1d",
13158
- npm: "@mutmutco/cli@4.1.11"
13155
+ version: "4.1.13",
13156
+ tag: "v4.1.13",
13157
+ commit: "91a8a172eda6",
13158
+ npm: "@mutmutco/cli@4.1.13"
13159
13159
  },
13160
13160
  exitCriterion: "fleet-n-of-n",
13161
13161
  hubOnlyShortcut: "forbidden",
@@ -13172,14 +13172,14 @@ var rollout_plan_default = {
13172
13172
  repo: "mutmutco/mmi-hub",
13173
13173
  role: "canary",
13174
13174
  schedule: "train",
13175
- v3Target: "v4.1.11"
13175
+ v3Target: "v4.1.13"
13176
13176
  }
13177
13177
  ],
13178
13178
  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.",
13179
13179
  rollback: {
13180
13180
  independent: true,
13181
- mechanism: "npm dist-tag latest -> 4.1.11 and redeploy the Hub Lambda from tag v4.1.11 (97f4b2119c1d); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13182
- v3Target: "v4.1.11 (@mutmutco/cli@4.1.11, tag commit 97f4b2119c1d \u2014 last known-good release carrying the repo-index v4-only contract)"
13181
+ mechanism: "npm dist-tag latest -> 4.1.13 and redeploy the Hub Lambda from tag v4.1.13 (91a8a172eda6); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13182
+ v3Target: "v4.1.13 (@mutmutco/cli@4.1.13, tag commit 91a8a172eda6 \u2014 last known-good release carrying the repo-index v4-only contract)"
13183
13183
  }
13184
13184
  },
13185
13185
  {
@@ -15938,9 +15938,13 @@ async function withCheckoutRestoredAfterRelease(deps, result, startBranch) {
15938
15938
  return { ...result, checkout, ...localSync ? { localSync } : {} };
15939
15939
  }
15940
15940
  var HUB_REPO3 = "mutmutco/MMI-Hub";
15941
+ var JERV_HUB_REPO = "mutmutco/Jerv-Hub";
15941
15942
  function isHubControlRepo(repo) {
15942
15943
  return repo.toLowerCase() === HUB_REPO3.toLowerCase();
15943
15944
  }
15945
+ function isJervHubRepo(repo) {
15946
+ return repo.toLowerCase() === JERV_HUB_REPO.toLowerCase();
15947
+ }
15944
15948
  function supportsReleaseAbort(_repo, meta, _branchHints) {
15945
15949
  return DEPLOY_MODELS.includes(meta.deployModel);
15946
15950
  }
@@ -16653,6 +16657,42 @@ function appendPublishDispatch(deploy, publish) {
16653
16657
  deployStatus: deploy.deployStatus === "failure" || publish.deployStatus === "failure" ? "failure" : deploy.deployStatus === "pending" || publish.deployStatus === "pending" ? "pending" : "success"
16654
16658
  };
16655
16659
  }
16660
+ async function appendJervGatewayReleaseDeploy(deps, repo, tag, tagSha, dispatch) {
16661
+ if (!isJervHubRepo(repo)) return dispatch;
16662
+ if (dispatch.deployStatus !== "success") {
16663
+ return {
16664
+ ...dispatch,
16665
+ note: `${dispatch.note}; Jerv Gateway deploy deferred until package publication is proven green`
16666
+ };
16667
+ }
16668
+ try {
16669
+ const output = await deps.run("bash", ["scripts/jerv-gateway-release-deploy.sh", "--tag", tag]);
16670
+ const receipt = output.trim().split(/\r?\n/).reverse().map((line) => {
16671
+ try {
16672
+ return JSON.parse(line);
16673
+ } catch {
16674
+ return null;
16675
+ }
16676
+ }).find((value) => value?.schema === "jerv-gateway/release-deploy-v1");
16677
+ if (!receipt || receipt.tag !== tag || receipt.commit !== tagSha || receipt.status !== "healthy") {
16678
+ throw new Error("helper receipt did not match the released tag and commit");
16679
+ }
16680
+ return {
16681
+ ...dispatch,
16682
+ note: `${dispatch.note}; Jerv Gateway deployed ${tagSha.slice(0, 12)} and passed health checks`,
16683
+ workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "jerv-gateway", conclusion: "success" }],
16684
+ deployStatus: "success"
16685
+ };
16686
+ } catch (error) {
16687
+ const detail = (error instanceof Error ? error.message : String(error)).split(/\r?\n/, 1)[0].slice(0, 240);
16688
+ return {
16689
+ ...dispatch,
16690
+ note: `${dispatch.note}; Jerv Gateway deploy FAILED (${detail})`,
16691
+ workflowRuns: [...dispatch.workflowRuns ?? [], { workflow: "jerv-gateway", conclusion: "failure" }],
16692
+ deployStatus: "failure"
16693
+ };
16694
+ }
16695
+ }
16656
16696
  async function watchOwnWorkflowRuns(deps, repo, targets, since, headSha, enumerateSha = false) {
16657
16697
  const workflowRuns = [];
16658
16698
  for (const target of targets) {
@@ -17338,6 +17378,9 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
17338
17378
  let tagPush;
17339
17379
  try {
17340
17380
  await assertComputedReleaseTagStillNext(deps, tag);
17381
+ if (deployModel === "hub-serverless") {
17382
+ await deps.run("node", ["scripts/release-distribution.mjs", "assert-release-lineage", tag.replace(/^v/, ""), "--release-commit", releaseSha]);
17383
+ }
17341
17384
  tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe, ctx.repo);
17342
17385
  } catch (e) {
17343
17386
  throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
@@ -17363,6 +17406,7 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
17363
17406
  const deployDispatch = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report", meta.publishDir);
17364
17407
  const publishDispatch = deployDispatch.deployStatus === "success" ? await dispatchPublishIfRequired(deps, ctx, meta, deployModel, "main", tag, watch, "report") : null;
17365
17408
  let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
17409
+ dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, dispatch);
17366
17410
  if (!publishDispatch && deployDispatch.deployStatus !== "success" && meta.publishRequired && (deployModel === "tenant-container" || deployModel === "solo-container")) {
17367
17411
  const reason = deployDispatch.deployStatus === "failure" ? "box deploy failed \u2014 redeploy the box before publishing" : "box deploy not confirmed (run with --watch) \u2014 publish after the box deploy lands";
17368
17412
  dispatch = {
@@ -17501,9 +17545,14 @@ async function runReleaseResume(deps, options = {}) {
17501
17545
  const rcAlignment2 = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
17502
17546
  if (rcAlignment2) steps2.push(`rc alignment: ${rcAlignment2.status}`);
17503
17547
  const historicalDeploy = await deriveHistoricalDeployStatus(deps, ctx, deployModel, tagSha);
17548
+ const dispatch2 = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, {
17549
+ note: `historical ${deployModel} deploy/publish status: ${historicalDeploy}`,
17550
+ deployStatus: historicalDeploy
17551
+ });
17552
+ if (isJervHubRepo(ctx.repo)) steps2.push(dispatch2.note);
17504
17553
  const state2 = resumeStateOf(deriveTrainFollowUpStatus({
17505
17554
  projectInfo: "ok",
17506
- deploy: deployFollowUpOutcome(historicalDeploy),
17555
+ deploy: deployFollowUpOutcome(dispatch2.deployStatus),
17507
17556
  rcRetirement: "ok",
17508
17557
  alignment: alignmentFollowUpOutcome(devRollForward2, rcAlignment2),
17509
17558
  foldPort: "ok"
@@ -17516,6 +17565,7 @@ async function runReleaseResume(deps, options = {}) {
17516
17565
  steps: steps2,
17517
17566
  devRollForward: devRollForward2,
17518
17567
  rcAlignment: rcAlignment2,
17568
+ dispatch: dispatch2,
17519
17569
  note: state2 === "failed" ? `release ${tag} was already published, but its deploy/publish on the release SHA FAILED \u2014 re-run the failed job (do NOT re-tag); the protected alignment PRs were reused` : state2 === "pending" ? `release ${tag} was already published; reused its protected alignment PRs, but a deploy/publish or alignment follow-up is unresolved \u2014 watch it to completion` : `release ${tag} was already published; alignment is complete and the deploy on the release SHA is proven green`
17520
17570
  };
17521
17571
  }
@@ -17548,7 +17598,8 @@ async function runReleaseResume(deps, options = {}) {
17548
17598
  const autoRunSince = (deps.now ?? Date.now)();
17549
17599
  const deployDispatch = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, tagSha, "report", meta.publishDir);
17550
17600
  const publishDispatch = deployDispatch.deployStatus === "success" ? await dispatchPublishIfRequired(deps, ctx, meta, deployModel, "main", tag, watch, "report") : null;
17551
- const dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
17601
+ let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
17602
+ dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, dispatch);
17552
17603
  steps.push(`dispatched the ${deployModel} deploy path`);
17553
17604
  const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
17554
17605
  steps.push(`development roll-forward: ${devRollForward.status}`);
@@ -24450,6 +24501,9 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
24450
24501
  }
24451
24502
  const required = await discoverRequiredCheckContexts(deps, ctx, "main");
24452
24503
  assertTagAddressableRequiredContexts(deps, required, ctx.repo);
24504
+ if (deployModel === "hub-serverless") {
24505
+ await deps.run("node", ["scripts/release-distribution.mjs", "assert-release-lineage", version, "--release-commit", mergedSha]);
24506
+ }
24453
24507
  const tagPush = await ensureTagPushed(deps, tag, mergedSha);
24454
24508
  const tagPushSince = Date.now();
24455
24509
  const tagNote = tagPush.note;
@@ -33658,6 +33712,27 @@ function selectSafeWorktreeCwd(worktrees, targetPath, options) {
33658
33712
  const exists = options?.pathExists ?? (() => true);
33659
33713
  return worktrees.find((w) => !samePath(w.path, targetPath) && exists(w.path))?.path;
33660
33714
  }
33715
+ function formatGitCommandError(e) {
33716
+ if (!(e instanceof Error)) return String(e);
33717
+ const err = e;
33718
+ const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
33719
+ const stdout = typeof err.stdout === "string" ? err.stdout.trim() : "";
33720
+ const message = err.message.trim();
33721
+ if (stderr) return stderr;
33722
+ if (message && !/^Command failed:/i.test(message)) return message;
33723
+ if (stdout) return stdout;
33724
+ return message || "git command failed";
33725
+ }
33726
+ function isWorktreePathRegistered(worktrees, wtPath) {
33727
+ return worktrees.some((w) => samePath(w.path, wtPath));
33728
+ }
33729
+ function isNotAWorkingTreeMessage(error) {
33730
+ return /is not a working tree/i.test(error);
33731
+ }
33732
+ function formatWorktreeRemovalFailureDetail(options) {
33733
+ const detail = options.removeError.trim() || "git worktree remove exited nonzero";
33734
+ return `${detail}; registered=${options.registered ? "yes" : "no"}; pathExists=${options.pathExists ? "yes" : "no"}; path=${options.wtPath}`;
33735
+ }
33661
33736
  async function resolveMergeBaseTimestampMs(git3, baseRef, branch) {
33662
33737
  try {
33663
33738
  const mergeBase = (await git3(["merge-base", baseRef, branch])).trim();
@@ -33685,14 +33760,52 @@ async function teardownWorktreeStage(worktreePath) {
33685
33760
  return { status: "failed", error: e instanceof Error ? e.message : String(e) };
33686
33761
  }
33687
33762
  }
33688
- async function removeWorktree(wtPath, git3) {
33689
- try {
33690
- await git3(["worktree", "remove", "--force", wtPath]);
33763
+ async function removeWorktreeWithReconcile(wtPath, git3, listWorktrees, pathExists) {
33764
+ const attemptRemove = async () => {
33765
+ try {
33766
+ await git3(["worktree", "remove", "--force", wtPath]);
33767
+ return void 0;
33768
+ } catch (e) {
33769
+ return formatGitCommandError(e);
33770
+ }
33771
+ };
33772
+ const firstError = await attemptRemove();
33773
+ if (!firstError) {
33691
33774
  await git3(["worktree", "prune"]).catch(() => "");
33692
33775
  return { status: "removed" };
33693
- } catch (e) {
33694
- return { status: "failed", error: e instanceof Error ? e.message : String(e) };
33695
33776
  }
33777
+ let worktrees = await listWorktrees().catch(() => []);
33778
+ if (!isWorktreePathRegistered(worktrees, wtPath)) {
33779
+ await git3(["worktree", "prune"]).catch(() => "");
33780
+ return { status: "removed", reason: "reconciled-unregistered" };
33781
+ }
33782
+ const retryError = await attemptRemove();
33783
+ if (!retryError) {
33784
+ await git3(["worktree", "prune"]).catch(() => "");
33785
+ return { status: "removed", reason: "reconciled-after-retry" };
33786
+ }
33787
+ if (isNotAWorkingTreeMessage(retryError) || isNotAWorkingTreeMessage(firstError)) {
33788
+ worktrees = await listWorktrees().catch(() => worktrees);
33789
+ if (!isWorktreePathRegistered(worktrees, wtPath)) {
33790
+ await git3(["worktree", "prune"]).catch(() => "");
33791
+ return { status: "removed", reason: "reconciled-not-a-working-tree" };
33792
+ }
33793
+ }
33794
+ worktrees = await listWorktrees().catch(() => worktrees);
33795
+ const registered = isWorktreePathRegistered(worktrees, wtPath);
33796
+ if (!registered) {
33797
+ await git3(["worktree", "prune"]).catch(() => "");
33798
+ return { status: "removed", reason: "reconciled-unregistered" };
33799
+ }
33800
+ return {
33801
+ status: "failed",
33802
+ error: formatWorktreeRemovalFailureDetail({
33803
+ removeError: retryError || firstError,
33804
+ registered,
33805
+ pathExists: pathExists(wtPath),
33806
+ wtPath
33807
+ })
33808
+ };
33696
33809
  }
33697
33810
  async function cleanupPrMergeLocalBranch(branch, options) {
33698
33811
  const report = { branch };
@@ -33797,7 +33910,12 @@ async function cleanupPrMergeLocalBranch(branch, options) {
33797
33910
  report.localBranch = { name: branch, status: "not-attempted", reason: "archive-failed" };
33798
33911
  return report;
33799
33912
  }
33800
- const removal = await removeWorktree(wtPath, git3);
33913
+ const removal = await removeWorktreeWithReconcile(
33914
+ wtPath,
33915
+ git3,
33916
+ async () => parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"])),
33917
+ pathExists
33918
+ );
33801
33919
  if (removal.status === "failed") {
33802
33920
  report.worktree = {
33803
33921
  path: wtPath,
@@ -33813,6 +33931,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
33813
33931
  report.worktree = {
33814
33932
  path: wtPath,
33815
33933
  status: "removed",
33934
+ ...removal.reason ? { reason: removal.reason } : {},
33816
33935
  artifactsArchive,
33817
33936
  stageTeardown,
33818
33937
  tmpEvidenceCount: tmpEvidence.length
@@ -33831,7 +33950,11 @@ function renderPrMergeCleanupLines(cleanup) {
33831
33950
  if (!wt?.path) return [];
33832
33951
  const lines = [];
33833
33952
  if (wt.status === "removed") {
33834
- lines.push(`pr merge: removed worktree ${wt.path} \u2014 that directory is gone; cd elsewhere before your next command`);
33953
+ if (wt.reason?.startsWith("reconciled")) {
33954
+ lines.push(`pr merge: worktree ${wt.path} remove reported failure but git no longer registers it \u2014 deleted local branch`);
33955
+ } else {
33956
+ lines.push(`pr merge: removed worktree ${wt.path} \u2014 that directory is gone; cd elsewhere before your next command`);
33957
+ }
33835
33958
  } else if (wt.status === "refused") {
33836
33959
  lines.push(`pr merge: worktree ${wt.path} kept \u2014 ${wt.reason ?? "refused"}${wt.error ? `: ${wt.error}` : ""}`);
33837
33960
  if (wt.tmpEvidenceCount) lines.push(`pr merge: ${wt.tmpEvidenceCount} tmp path(s) newer than branch base would have been destroyed`);
@@ -43087,10 +43210,11 @@ var GH_RUN_WATCH_TIMEOUT_MS = 20 * 6e4;
43087
43210
  var NODE_PREPARE_TIMEOUT_MS = 10 * 6e4;
43088
43211
  var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
43089
43212
  var NPM_TRAIN_TIMEOUT_MS = 6e4;
43213
+ var JERV_GATEWAY_DEPLOY_TIMEOUT_MS = 20 * 6e4;
43090
43214
  function trainApplyDeps() {
43091
43215
  return {
43092
43216
  run: async (file, args) => {
43093
- const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
43217
+ const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file === "bash" && args[0] === "scripts/jerv-gateway-release-deploy.sh" ? JERV_GATEWAY_DEPLOY_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
43094
43218
  try {
43095
43219
  return isWin2 && file === "npm" ? (await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP2(file, args, { timeout })).stdout;
43096
43220
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.1.11",
3
+ "version": "4.1.13",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",