@mutmutco/cli 3.105.3 → 3.105.5

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 +79 -6
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -20341,6 +20341,51 @@ async function deriveHotfixVersion(deps) {
20341
20341
  function hotfixBranch(tag) {
20342
20342
  return `hotfix/${tag}`;
20343
20343
  }
20344
+ function pathIsTolerated(path2, tolerated) {
20345
+ return tolerated.some((entry) => {
20346
+ if (!entry) return false;
20347
+ if (path2 === entry) return true;
20348
+ const root = entry.endsWith("/") ? entry : `${entry}/`;
20349
+ return path2.startsWith(root);
20350
+ });
20351
+ }
20352
+ var HOTFIX_SKILL_TOLERATED_ROOTS = ["skills", ".kilo-plugin/skills"];
20353
+ async function cherryPickWithToleratedPaths(deps, sha, tolerated) {
20354
+ try {
20355
+ await deps.run("git", ["cherry-pick", "-x", sha]);
20356
+ return [];
20357
+ } catch (original) {
20358
+ const unmerged = (await deps.run("git", ["diff", "--name-only", "--diff-filter=U"])).split("\n").map((s) => s.trim()).filter(Boolean);
20359
+ const blocking = unmerged.filter((f) => !pathIsTolerated(f, tolerated));
20360
+ if (unmerged.length === 0 || blocking.length > 0) {
20361
+ try {
20362
+ await deps.run("git", ["cherry-pick", "--abort"]);
20363
+ } catch {
20364
+ }
20365
+ const detail = original.message ?? String(original);
20366
+ if (unmerged.length === 0) {
20367
+ throw new Error(`cherry-pick of ${sha.slice(0, 7)} failed without conflicted paths \u2014 ${detail}`);
20368
+ }
20369
+ throw new Error(
20370
+ `cherry-pick of ${sha.slice(0, 7)} conflicted on untolerated path(s): ${blocking.join(", ")}` + (tolerated.length > 0 ? ` (regenerable paths are auto-resolved: ${tolerated.join(", ")})` : "") + ` \u2014 ${detail}`
20371
+ );
20372
+ }
20373
+ await deps.run("git", ["checkout", "--theirs", "--", ...unmerged]);
20374
+ await deps.run("git", ["add", "--", ...unmerged]);
20375
+ try {
20376
+ await deps.run("git", ["-c", "core.editor=true", "cherry-pick", "--continue"]);
20377
+ } catch (contErr) {
20378
+ try {
20379
+ await deps.run("git", ["cherry-pick", "--abort"]);
20380
+ } catch {
20381
+ }
20382
+ throw new Error(
20383
+ `cherry-pick continue after regenerable-path resolve failed for ${unmerged.join(", ")} \u2014 ${contErr.message ?? contErr}`
20384
+ );
20385
+ }
20386
+ return unmerged;
20387
+ }
20388
+ }
20344
20389
  async function restoreAfterFailedPort(deps, opts) {
20345
20390
  const { startBranch, branch, created } = opts;
20346
20391
  if (!startBranch || startBranch === "HEAD" || startBranch === branch) {
@@ -20425,6 +20470,14 @@ async function hotfixBaseTagForRelease(deps, tag) {
20425
20470
  const current = normalizeHotfixVersion(tag).tag;
20426
20471
  return tags.find((t) => compareHotfixVersions(t, current) < 0) ?? tags.find((t) => t !== current) ?? "origin/main^";
20427
20472
  }
20473
+ async function hotfixReleaseExists(deps, ctx, tag) {
20474
+ try {
20475
+ await deps.run("gh", ["release", "view", tag, "--repo", ctx.repo, "--json", "tagName"]);
20476
+ return true;
20477
+ } catch {
20478
+ return false;
20479
+ }
20480
+ }
20428
20481
  async function runHotfixStart(deps, options) {
20429
20482
  const ctx = await buildTrainApplyContext(deps);
20430
20483
  const deployModel = await resolveHotfixDeployModel(deps, ctx);
@@ -20437,7 +20490,11 @@ async function runHotfixStart(deps, options) {
20437
20490
  const branch = hotfixBranch(tag);
20438
20491
  const notes = [];
20439
20492
  const existingPr = await findHotfixPr(deps, ctx, tag);
20440
- if (existingPr) {
20493
+ const existingState = (existingPr?.state ?? "").toUpperCase();
20494
+ const releaseExists = existingState === "MERGED" ? await hotfixReleaseExists(deps, ctx, tag) : false;
20495
+ const reusable = existingState === "OPEN" || existingState === "MERGED" && releaseExists;
20496
+ if (existingPr && reusable) {
20497
+ const next = existingState === "MERGED" ? `next: mmi-cli hotfix release ${tag}` : `next: merge it, then mmi-cli hotfix release ${tag}`;
20441
20498
  return {
20442
20499
  ...ctx,
20443
20500
  command: "hotfix-start",
@@ -20447,10 +20504,21 @@ async function runHotfixStart(deps, options) {
20447
20504
  source: options.from,
20448
20505
  prUrl: existingPr.url,
20449
20506
  reused: true,
20450
- notes: [`hotfix PR for ${tag} already exists (#${existingPr.number}, ${existingPr.state}) \u2014 reused; next: merge it, then mmi-cli hotfix release ${tag}`]
20507
+ notes: [`hotfix PR for ${tag} already exists (#${existingPr.number}, ${existingPr.state}) \u2014 reused; ${next}`]
20451
20508
  };
20452
20509
  }
20510
+ if (existingPr) {
20511
+ const reason = existingState === "MERGED" && !releaseExists ? `MERGED with no GitHub Release for ${tag} \u2014 recreating ${branch} from origin/main (#4381/#4383)` : `${existingPr.state} \u2014 recreating ${branch} from origin/main (#4369)`;
20512
+ notes.push(`prior hotfix PR #${existingPr.number} is ${reason}`);
20513
+ const staleRemote = clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
20514
+ if (staleRemote) {
20515
+ await deps.run("git", ["push", "origin", "--delete", branch]);
20516
+ notes.push(`deleted stale origin/${branch} left by the incomplete train`);
20517
+ }
20518
+ }
20453
20519
  const { sha, label } = await resolveHotfixSource(deps, ctx, options.from);
20520
+ const foldPaths = deployModel === "hub-serverless" || deployModel === "registry-publish" ? await resolveFoldPaths(deps, deployModel) : [];
20521
+ const pickTolerated = deployModel === "hub-serverless" ? [...foldPaths, ...HOTFIX_SKILL_TOLERATED_ROOTS] : foldPaths;
20454
20522
  if (deployModel === "hub-serverless") {
20455
20523
  await deps.run("node", ["scripts/release-distribution.mjs", "verify-deps"]);
20456
20524
  }
@@ -20464,9 +20532,11 @@ async function runHotfixStart(deps, options) {
20464
20532
  const preexistingLocal = clean3(await deps.run("git", ["branch", "--list", branch]));
20465
20533
  await deps.run("git", ["checkout", "-B", branch, "origin/main"]);
20466
20534
  try {
20467
- await deps.run("git", ["cherry-pick", "-x", sha]);
20535
+ const autoResolved = await cherryPickWithToleratedPaths(deps, sha, pickTolerated);
20536
+ if (autoResolved.length > 0) {
20537
+ notes.push(`auto-resolved regenerable cherry-pick conflict(s): ${autoResolved.join(", ")} (regenerated in bump step)`);
20538
+ }
20468
20539
  } catch (e) {
20469
- await deps.run("git", ["cherry-pick", "--abort"]);
20470
20540
  const recovery = await restoreAfterFailedPort(deps, { startBranch, branch, created: !preexistingLocal });
20471
20541
  throw new Error(
20472
20542
  `cherry-pick of ${label} onto ${branch} conflicted \u2014 aborted; nothing was pushed. Land the conflict resolution on development through a normal PR, then rerun \`mmi-cli hotfix start --from <that PR's merge sha>\` \u2014 never hand-resolve the port onto main. ${recovery} (${e.message ?? e})`
@@ -20485,12 +20555,15 @@ async function runHotfixStart(deps, options) {
20485
20555
  } else {
20486
20556
  notes.push("distribution prepare produced no changes \u2014 nothing extra committed");
20487
20557
  }
20558
+ } else if (deployModel === "registry-publish" && foldPaths.length > 0) {
20559
+ const foldNote = await foldReleaseVersion(deps, deployModel, tag, foldPaths);
20560
+ notes.push(foldNote);
20488
20561
  } else {
20489
- notes.push(`distribution bump skipped (deployModel=${deployModel}, Hub-only step)`);
20562
+ notes.push(`distribution bump skipped (deployModel=${deployModel})`);
20490
20563
  }
20491
20564
  await deps.run("git", ["push", "-u", "origin", branch]);
20492
20565
  }
20493
- const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : "";
20566
+ const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : deployModel === "registry-publish" ? " with the package version bump" : "";
20494
20567
  const prUrl = clean3(await deps.run("gh", [
20495
20568
  "pr",
20496
20569
  "create",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.105.3",
3
+ "version": "3.105.5",
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",