@mutmutco/cli 2.3.0 → 2.5.1

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/index.cjs +44 -13
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -4255,6 +4255,26 @@ function parseWorktreePorcelain(stdout) {
4255
4255
  }
4256
4256
  return out;
4257
4257
  }
4258
+ function samePath(a, b) {
4259
+ return a.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase() === b.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
4260
+ }
4261
+ function selectPrMergeCleanupWorktree(branch, before, after, startingPath) {
4262
+ if (!branch) return void 0;
4263
+ const current = after.find((w) => w.branch === branch)?.path;
4264
+ if (current) return current;
4265
+ const previous = before.find((w) => w.branch === branch)?.path;
4266
+ if (previous) return previous;
4267
+ if (startingPath && before.some((w) => w.branch === branch && samePath(w.path, startingPath))) return startingPath;
4268
+ return void 0;
4269
+ }
4270
+ function selectSafeWorktreeCwd(worktrees, targetPath) {
4271
+ if (!targetPath) return void 0;
4272
+ return worktrees.find((w) => !samePath(w.path, targetPath))?.path;
4273
+ }
4274
+ function branchMissingFromList(branch, stdout) {
4275
+ const names = stdout.split(/\r?\n/).map((line) => line.replace(/^\*\s*/, "").trim()).filter(Boolean);
4276
+ return !names.includes(branch);
4277
+ }
4258
4278
  function formatGcPlan(plan2, apply) {
4259
4279
  const lines = [`mmi-cli gc: ${apply ? "apply" : "dry-run"}`];
4260
4280
  if (!plan2.branches.length && !plan2.trackingRefs.length) lines.push("nothing to clean");
@@ -4705,9 +4725,9 @@ function requireValue(value, label) {
4705
4725
  function releaseTagFromRcTag(tag) {
4706
4726
  return tag.replace(/-rc\.\d+$/, "");
4707
4727
  }
4708
- async function verifyHubDistributionIfChanged(deps, model, releaseTag) {
4728
+ async function verifyHubDistributionVersion(deps, model, releaseTag) {
4709
4729
  if (model !== "hub-serverless") return;
4710
- await deps.run("node", ["scripts/release-distribution.mjs", "verify-if-changed", releaseTag, "--skip-npm-view"]);
4730
+ await deps.run("node", ["scripts/release-distribution.mjs", "verify", releaseTag, "--skip-npm-view"]);
4711
4731
  }
4712
4732
  function ensurePositiveCount(out, emptyMessage) {
4713
4733
  const count = Number.parseInt(out.trim(), 10);
@@ -4792,7 +4812,7 @@ async function runTrainApply(command, deps) {
4792
4812
  );
4793
4813
  const deployModel2 = await preflight(deps, ctx, "rc");
4794
4814
  const tag2 = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
4795
- await verifyHubDistributionIfChanged(deps, deployModel2, releaseTagFromRcTag(tag2));
4815
+ await verifyHubDistributionVersion(deps, deployModel2, releaseTagFromRcTag(tag2));
4796
4816
  await deps.run("git", ["checkout", "rc"]);
4797
4817
  await deps.run("git", ["pull", "--ff-only", "origin", "rc"]);
4798
4818
  await deps.run("git", ["merge", "development", "--no-edit"]);
@@ -4812,7 +4832,7 @@ async function runTrainApply(command, deps) {
4812
4832
  await deps.run("git", ["pull", "--ff-only", "origin", "main"]);
4813
4833
  await deps.run("git", ["merge", "rc", "--no-edit"]);
4814
4834
  const tag = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
4815
- await verifyHubDistributionIfChanged(deps, deployModel, tag);
4835
+ await verifyHubDistributionVersion(deps, deployModel, tag);
4816
4836
  await deps.run("git", ["tag", tag]);
4817
4837
  await deps.run("git", ["push", "origin", "main"]);
4818
4838
  await deps.run("git", ["push", "origin", tag]);
@@ -6312,24 +6332,31 @@ async function applyGcPlan(plan2, remote) {
6312
6332
  await execFileP3("git", ["worktree", "prune"], { timeout: GIT_TIMEOUT_MS });
6313
6333
  }
6314
6334
  }
6315
- async function cleanupLocalBranch(branch) {
6335
+ async function cleanupLocalBranch(branch, before = {}) {
6316
6336
  const result = { branchDeleted: false };
6317
6337
  if (!branch) return result;
6318
6338
  const { stdout } = await execFileP3("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
6319
- const wt = parseWorktreePorcelain(stdout).find((w) => w.branch === branch);
6320
- if (wt) {
6321
- await execFileP3("git", ["worktree", "remove", "--force", wt.path], { timeout: GIT_TIMEOUT_MS }).catch(() => {
6339
+ const afterWorktrees = parseWorktreePorcelain(stdout);
6340
+ const wtPath = selectPrMergeCleanupWorktree(branch, before.worktrees ?? [], afterWorktrees, before.startingPath);
6341
+ const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...before.worktrees ?? []], wtPath);
6342
+ const git = (args) => safeCwd ? execFileP3("git", ["-C", safeCwd, ...args], { timeout: GIT_TIMEOUT_MS }) : execFileP3("git", args, { timeout: GIT_TIMEOUT_MS });
6343
+ if (wtPath) {
6344
+ await git(["worktree", "remove", "--force", wtPath]).catch(() => {
6322
6345
  });
6323
- result.worktreeRemoved = wt.path;
6346
+ result.worktreeRemoved = wtPath;
6324
6347
  }
6325
- const current = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]) || "";
6348
+ const current = safeCwd ? ((await git(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => ({ stdout: "" }))).stdout || "").trim() : await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]) || "";
6326
6349
  if (branch !== current) {
6327
- await execFileP3("git", ["branch", "-D", branch], { timeout: GIT_TIMEOUT_MS }).then(() => {
6350
+ await git(["branch", "-D", branch]).then(() => {
6328
6351
  result.branchDeleted = true;
6329
6352
  }).catch(() => {
6330
6353
  });
6354
+ if (!result.branchDeleted) {
6355
+ const remaining = await git(["branch", "--list", branch]).catch(() => ({ stdout: "" }));
6356
+ result.branchDeleted = branchMissingFromList(branch, remaining.stdout || "");
6357
+ }
6331
6358
  }
6332
- if (wt) await execFileP3("git", ["worktree", "prune"], { timeout: GIT_TIMEOUT_MS }).catch(() => {
6359
+ if (wtPath) await git(["worktree", "prune"]).catch(() => {
6333
6360
  });
6334
6361
  return result;
6335
6362
  }
@@ -7015,10 +7042,14 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
7015
7042
  const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
7016
7043
  const repoArgs = o.repo ? ["--repo", o.repo] : [];
7017
7044
  const headRef = (await execFileP3("gh", ["pr", "view", number, ...repoArgs, "--json", "headRefName", "--jq", ".headRefName"], { timeout: GC_GH_TIMEOUT_MS })).stdout.trim();
7045
+ const startingPath = (await execFileP3("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
7046
+ const beforeWorktrees = parseWorktreePorcelain(
7047
+ (await execFileP3("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout
7048
+ );
7018
7049
  await execFileP3("gh", ["pr", "merge", number, ...repoArgs, method, "--delete-branch"], { timeout: GC_GH_TIMEOUT_MS }).catch((e) => {
7019
7050
  if (!/used by worktree|cannot delete branch|already been merged/i.test(String(e.message || ""))) throw e;
7020
7051
  });
7021
- const cleaned = repoArgs.length ? { branchDeleted: false } : await cleanupLocalBranch(headRef);
7052
+ const cleaned = repoArgs.length ? { branchDeleted: false } : await cleanupLocalBranch(headRef, { worktrees: beforeWorktrees, startingPath });
7022
7053
  console.log(JSON.stringify({ merged: number, branch: headRef, method: method.slice(2), ...cleaned }));
7023
7054
  });
7024
7055
  async function runBoardRead(o) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.3.0",
3
+ "version": "2.5.1",
4
4
  "description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",