@mutmutco/cli 4.5.36 → 4.5.37

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 +270 -57
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -863,6 +863,9 @@ var init_house_map = __esm({
863
863
  });
864
864
 
865
865
  // src/stdin-inject.ts
866
+ function setInjectedStdin(payload) {
867
+ injectedStdin = payload;
868
+ }
866
869
  function stdinHasPipedInput(statFd = () => (0, import_node_fs4.fstatSync)(0), getIsTTY = () => process.stdin.isTTY) {
867
870
  try {
868
871
  const stat4 = statFd();
@@ -922,6 +925,34 @@ var init_stdin_inject = __esm({
922
925
  });
923
926
 
924
927
  // src/cli-shared.ts
928
+ var cli_shared_exports = {};
929
+ __export(cli_shared_exports, {
930
+ DEFAULT_EXEC_TIMEOUT_MS: () => DEFAULT_EXEC_TIMEOUT_MS,
931
+ ExecDeadlineError: () => ExecDeadlineError,
932
+ GIT_TIMEOUT_MS: () => GIT_TIMEOUT_MS,
933
+ consoleIo: () => consoleIo,
934
+ execFileHard: () => execFileHard,
935
+ execFileP: () => execFileP,
936
+ fail: () => fail,
937
+ failGraceful: () => failGraceful,
938
+ failGracefulEnvelope: () => failGracefulEnvelope,
939
+ gitOut: () => gitOut,
940
+ githubLogin: () => githubLogin,
941
+ hubHeaders: () => hubHeaders,
942
+ installBrokenPipeGuard: () => installBrokenPipeGuard,
943
+ installProcessBackstop: () => installProcessBackstop,
944
+ isOrgRepoRoot: () => isOrgRepoRoot,
945
+ jsonParity: () => jsonParity,
946
+ loadConfig: () => loadConfig,
947
+ mutating: () => mutating,
948
+ planMutation: () => planMutation,
949
+ printLine: () => printLine,
950
+ rawFlag: () => rawFlag,
951
+ rawValue: () => rawValue,
952
+ readStdin: () => readStdin,
953
+ repoProject: () => repoProject,
954
+ setInjectedStdin: () => setInjectedStdin
955
+ });
925
956
  function killProcessTree(pid) {
926
957
  try {
927
958
  if (process.platform === "win32") {
@@ -11654,11 +11685,11 @@ function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb, declineW
11654
11685
  return `could NOT ${failedVerb} ${[...pins.keys()].join(", ")} \u2014 set it by hand`;
11655
11686
  }
11656
11687
  const verify = readKnownMarketplacesFile(path2);
11657
- const failed = [...pins].filter(([name, want]) => {
11688
+ const failed2 = [...pins].filter(([name, want]) => {
11658
11689
  const got = readKnownMarketplace(verify, name);
11659
11690
  return typeof want.autoUpdate === "boolean" && got.declared !== want.autoUpdate || want.ref !== void 0 && got.ref !== want.ref;
11660
11691
  });
11661
- return failed.length ? `${failedVerb} did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
11692
+ return failed2.length ? `${failedVerb} did NOT take for ${failed2.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
11662
11693
  });
11663
11694
  } catch (error) {
11664
11695
  if (error instanceof Error && error.message.startsWith("shared-document lock timeout:")) return error.message;
@@ -16840,10 +16871,10 @@ var rollout_plan_default = {
16840
16871
  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)."
16841
16872
  },
16842
16873
  baseline: {
16843
- version: "4.5.36",
16844
- tag: "v4.5.36",
16845
- commit: "dfc9e43e72eb",
16846
- npm: "@mutmutco/cli@4.5.36"
16874
+ version: "4.5.37",
16875
+ tag: "v4.5.37",
16876
+ commit: "75086128c3ba",
16877
+ npm: "@mutmutco/cli@4.5.37"
16847
16878
  },
16848
16879
  exitCriterion: "fleet-n-of-n",
16849
16880
  hubOnlyShortcut: "forbidden",
@@ -16860,14 +16891,14 @@ var rollout_plan_default = {
16860
16891
  repo: "mutmutco/mmi-hub",
16861
16892
  role: "canary",
16862
16893
  schedule: "train",
16863
- v3Target: "v4.5.36"
16894
+ v3Target: "v4.5.37"
16864
16895
  }
16865
16896
  ],
16866
16897
  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.",
16867
16898
  rollback: {
16868
16899
  independent: true,
16869
- mechanism: "npm dist-tag latest -> 4.5.36 and redeploy the Hub Lambda from tag v4.5.36 (dfc9e43e72eb); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16870
- v3Target: "v4.5.36 (@mutmutco/cli@4.5.36, tag commit dfc9e43e72eb \u2014 last known-good release carrying the repo-index v4-only contract)"
16900
+ mechanism: "npm dist-tag latest -> 4.5.37 and redeploy the Hub Lambda from tag v4.5.37 (75086128c3ba); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16901
+ v3Target: "v4.5.37 (@mutmutco/cli@4.5.37, tag commit 75086128c3ba \u2014 last known-good release carrying the repo-index v4-only contract)"
16871
16902
  }
16872
16903
  },
16873
16904
  {
@@ -19633,9 +19664,9 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince)
19633
19664
  });
19634
19665
  const satisfiedNote = autoSatisfied.size ? `, auto-satisfied (never runs on a tag, see #2404): ${[...autoSatisfied].join(", ")}` : "";
19635
19666
  lastStatus = `${states.map(([c, s]) => `${c}=${s}`).join(", ")}${satisfiedNote}`;
19636
- const failed = states.filter(([, s]) => s === "failed").map(([c]) => c);
19637
- if (failed.length > 0) {
19638
- throw new Error(`required train check failed: ${failed.join(", ")} (${lastStatus}${fleetNote})`);
19667
+ const failed2 = states.filter(([, s]) => s === "failed").map(([c]) => c);
19668
+ if (failed2.length > 0) {
19669
+ throw new Error(`required train check failed: ${failed2.join(", ")} (${lastStatus}${fleetNote})`);
19639
19670
  }
19640
19671
  if (states.every(([, s]) => s === "success")) {
19641
19672
  return `required checks passed: ${pending.join(", ") || "(none pending)"}${satisfiedNote}${fleetNote}`;
@@ -19937,9 +19968,9 @@ async function readVercelDeployment(api, repo, sha, target, since) {
19937
19968
  }
19938
19969
  if (newest.size === 0) return void 0;
19939
19970
  const reads = await Promise.all([...newest].map(async ([env, id]) => ({ env, id, ...await readOne(api, repo, id, since) })));
19940
- const failed = reads.find((r) => FAILED.has(r.state));
19971
+ const failed2 = reads.find((r) => FAILED.has(r.state));
19941
19972
  const unfinished = reads.find((r) => !isTerminalVercelState(r.state));
19942
- const pick = failed ?? unfinished ?? reads[0];
19973
+ const pick = failed2 ?? unfinished ?? reads[0];
19943
19974
  return {
19944
19975
  id: pick.id,
19945
19976
  environment: reads.map((r) => r.env).join(", "),
@@ -20332,11 +20363,11 @@ async function appendServedChannelProof(deps, input, dispatch) {
20332
20363
  const channel = servedChannelFor(input.repo, input.installer);
20333
20364
  if (!channel) return dispatch;
20334
20365
  const expected = input.tag.replace(/^v/, "");
20335
- const withProof = (conclusion, evidence, note, failed = false) => ({
20366
+ const withProof = (conclusion, evidence, note, failed2 = false) => ({
20336
20367
  ...dispatch,
20337
20368
  note: `${dispatch.note}; ${note}`,
20338
20369
  workflowRuns: [...dispatch.workflowRuns ?? [], servedChannelRow(channel.product, conclusion, evidence)],
20339
- ...failed ? { deployStatus: "failure" } : {}
20370
+ ...failed2 ? { deployStatus: "failure" } : {}
20340
20371
  });
20341
20372
  if (!input.shipped) {
20342
20373
  return withProof("pending", `${channel.url} not probed`, `served channel ${channel.url} not probed \u2014 deploy/publish are not proven green, so it is not expected to have moved`);
@@ -20611,8 +20642,8 @@ function shipLegsGreen(deploy, publish) {
20611
20642
  async function announceShippedRelease(deps, args, model, legs, deferred) {
20612
20643
  if (!deps.announce) return void 0;
20613
20644
  if (trainObservesShipment(model) && !shipLegsGreen(legs.deploy, legs.publish)) {
20614
- const failed = legs.deploy.state === "failed" || legs.publish.state === "failed";
20615
- return `not announced: deploy/publish ${failed ? "FAILED" : "not proven green"} \u2014 ${deferred}`;
20645
+ const failed2 = legs.deploy.state === "failed" || legs.publish.state === "failed";
20646
+ return `not announced: deploy/publish ${failed2 ? "FAILED" : "not proven green"} \u2014 ${deferred}`;
20616
20647
  }
20617
20648
  return (await deps.announce(args)).note;
20618
20649
  }
@@ -23378,8 +23409,8 @@ function ledgerFailedRemedy(ledger) {
23378
23409
  const openLegs = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
23379
23410
  const tails = formatFailedLegTails(ledger);
23380
23411
  if (hotfixRetryDeployShape(ledger)) {
23381
- const failed = openLegs.filter((l) => l.state === "failed").map((l) => l.phase);
23382
- return `the published ${ledger.tag} hotfix can retry the failed ${joinLegNames(failed)} of the same tag: \`mmi-cli devops hotfix release ${ledger.tag} --retry-deploy\` (the deploy helper is idempotent; alignment re-attempts the fold-port PR) (#6985)${tails}`;
23412
+ const failed2 = openLegs.filter((l) => l.state === "failed").map((l) => l.phase);
23413
+ return `the published ${ledger.tag} hotfix can retry the failed ${joinLegNames(failed2)} of the same tag: \`mmi-cli devops hotfix release ${ledger.tag} --retry-deploy\` (the deploy helper is idempotent; alignment re-attempts the fold-port PR) (#6985)${tails}`;
23383
23414
  }
23384
23415
  if (rcandRetryDeployShape(ledger)) {
23385
23416
  return `fix the rc deploy failure, then retry the failed deploy of the promoted ${ledger.tag} candidate (same tag and SHA, never re-cut): \`mmi-cli devops rcand --retry-deploy --watch\` (#7053)${tails}`;
@@ -23547,8 +23578,8 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
23547
23578
  `release --supersede-deploy: origin reports ${prior.tag} at ${liveSha.slice(0, 12) || "no tag"} but the ledger anchors ${prior.tagSha.slice(0, 12)} \u2014 an absent or divergent release is NOT supersedeable (release --abort --apply is the unpublished candidate's own exit); nothing was written (#6553)`
23548
23579
  );
23549
23580
  }
23550
- const failed = prior.phases.deploy.state === "failed";
23551
- const note = failed ? `superseded: content-caused deploy failure on the immutable promoted ${prior.tag} \u2014 the cause fix rides the next patch train (master-approved closure, #6553)` : `superseded: ${prior.tag} deploy outcome uncorrelatable from the release SHA (no failure recorded) \u2014 closed on the owner's evidence (master-approved closure, #6553, #7327)`;
23581
+ const failed2 = prior.phases.deploy.state === "failed";
23582
+ const note = failed2 ? `superseded: content-caused deploy failure on the immutable promoted ${prior.tag} \u2014 the cause fix rides the next patch train (master-approved closure, #6553)` : `superseded: ${prior.tag} deploy outcome uncorrelatable from the release SHA (no failure recorded) \u2014 closed on the owner's evidence (master-approved closure, #6553, #7327)`;
23552
23583
  await fence(() => atomicWriteJson(path2, {
23553
23584
  ...prior,
23554
23585
  phases: {
@@ -23562,7 +23593,7 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
23562
23593
  repo: prior.repo,
23563
23594
  tag: prior.tag,
23564
23595
  tagSha: prior.tagSha,
23565
- note: failed ? `closed the ${prior.tag} deploy leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm, then run the next patch train (hotfix or release) to carry the cause fix` : `closed the ${prior.tag} uncorrelatable deploy leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm`
23596
+ note: failed2 ? `closed the ${prior.tag} deploy leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm, then run the next patch train (hotfix or release) to carry the cause fix` : `closed the ${prior.tag} uncorrelatable deploy leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm`
23566
23597
  };
23567
23598
  });
23568
23599
  }
@@ -24537,17 +24568,17 @@ async function runTrainDoctor(input) {
24537
24568
  if (derived.phaseStatus !== "phases-green") {
24538
24569
  const openLegs = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
24539
24570
  const open2 = openLegs.map((l) => `${l.phase}=${l.state}`).join(", ");
24540
- const failed = derived.phaseStatus === "phases-failed";
24571
+ const failed2 = derived.phaseStatus === "phases-failed";
24541
24572
  const deployOnlyDeadlock = openLegs.length > 0 && openLegs.every((l) => l.phase === "deploy" && supersedeableDeployLegShape(l));
24542
24573
  const supersession = deployOnlyDeadlock ? `; when the deploy failure is content-caused on the already-published ${ledger.tag} (the immutable tag can never deploy green), the sanctioned exit is \`mmi-cli devops release --supersede-deploy --apply\` after explicit master approval \u2014 the next patch train carries the cause fix (#6553)` : "";
24543
24574
  add({
24544
- code: failed ? "ledger-failed" : "ledger-pending",
24575
+ code: failed2 ? "ledger-failed" : "ledger-pending",
24545
24576
  severity: "blocker",
24546
24577
  source: "local",
24547
24578
  // #6429: each lane finishes its own run (#6068) — an rcand-lane ledger named as a release
24548
24579
  // ledger, with `release --resume` as its remedy, sends the operator to a verb that refuses it.
24549
24580
  title: `a prior ${ledger.lane ?? "release"} ledger for ${ledger.tag} is ${derived.phaseStatus} (${open2}) \u2014 a new train must not stack on it (#5987)`,
24550
- remedy: failed ? ledgerFailedRemedy(ledger) : `finish it first: ${ledgerLaneResumeCommand(ledger)} (alignment-only pending legs close themselves with \`train doctor --heal\` once the PR merges, and a deploy/publish leg whose run already concluded folds under the same heal)${supersession}`
24581
+ remedy: failed2 ? ledgerFailedRemedy(ledger) : `finish it first: ${ledgerLaneResumeCommand(ledger)} (alignment-only pending legs close themselves with \`train doctor --heal\` once the PR merges, and a deploy/publish leg whose run already concluded folds under the same heal)${supersession}`
24551
24582
  });
24552
24583
  } else if (newestRemoteReleaseTag && newestRemoteReleaseTag !== ledger.tag) {
24553
24584
  add({
@@ -24594,21 +24625,21 @@ regenerate the projections (\`node scripts/check-skill-payload.mjs\`) and commit
24594
24625
  const installed = [];
24595
24626
  let pending = firstDir;
24596
24627
  let outstanding = failure;
24597
- let failed = null;
24628
+ let failed2 = null;
24598
24629
  while (pending && !installed.includes(pending)) {
24599
24630
  const dir = pending;
24600
24631
  try {
24601
24632
  await train.run("npm", ["--prefix", dir, "ci"]);
24602
24633
  } catch (e) {
24603
- failed = { dir, detail: `\`npm --prefix ${dir} ci\` failed: ${message(e)}` };
24634
+ failed2 = { dir, detail: `\`npm --prefix ${dir} ci\` failed: ${message(e)}` };
24604
24635
  break;
24605
24636
  }
24606
24637
  installed.push(dir);
24607
24638
  outstanding = await verifyDeps();
24608
24639
  pending = outstanding ? /in (cli|updater)\/node_modules/u.exec(outstanding)?.[1] ?? null : null;
24609
24640
  }
24610
- if (failed) {
24611
- add({ code: "hub-build-deps", severity: "blocker", source: "local", title: failed.detail, remedy: `repair ${failed.dir}/node_modules by hand, then rerun` });
24641
+ if (failed2) {
24642
+ add({ code: "hub-build-deps", severity: "blocker", source: "local", title: failed2.detail, remedy: `repair ${failed2.dir}/node_modules by hand, then rerun` });
24612
24643
  } else if (outstanding) {
24613
24644
  add({ code: "hub-build-deps", severity: "blocker", source: "local", title: `installing ${installed.join(", ")} from ${installed.length === 1 ? "its lockfile" : "their lockfiles"} did not resolve the build dependencies`, remedy: outstanding });
24614
24645
  } else {
@@ -24709,6 +24740,133 @@ function formatTrainDoctor(v) {
24709
24740
  return lines2.join("\n");
24710
24741
  }
24711
24742
 
24743
+ // src/vercel-version-env.ts
24744
+ var SKIPPED = { status: "skipped", note: "" };
24745
+ function vercelVersionEnvAheadNote(key) {
24746
+ return `a failed branch push can leave ${key} ahead of what is live`;
24747
+ }
24748
+ function safeReason(reason, token) {
24749
+ const compact = reason.replace(/\s+/g, " ").trim().slice(0, 180);
24750
+ const scrubbed = token && compact.includes(token) ? compact.split(token).join("[redacted]") : compact;
24751
+ return scrubbed || "unknown error";
24752
+ }
24753
+ function failed(deps, key, tag, reason) {
24754
+ const note = `vercel version env: FAILED to set ${key}=${tag} (${reason}) \u2014 /healthz will show the commit, not the tag; fix: set it and redeploy once`;
24755
+ deps.warn?.(note);
24756
+ return { status: "failed", note };
24757
+ }
24758
+ function targetLabel(stage) {
24759
+ return stage === "main" ? "production" : "preview";
24760
+ }
24761
+ function envCollectionUrl(project2, team, extra) {
24762
+ const url = new URL(`https://api.vercel.com/v10/projects/${encodeURIComponent(project2)}/env`);
24763
+ if (team) url.searchParams.set("teamId", team);
24764
+ for (const [key, value] of Object.entries(extra ?? {})) url.searchParams.set(key, value);
24765
+ return url.toString();
24766
+ }
24767
+ function envItemUrl(project2, id, team) {
24768
+ const url = new URL(`https://api.vercel.com/v9/projects/${encodeURIComponent(project2)}/env/${encodeURIComponent(id)}`);
24769
+ if (team) url.searchParams.set("teamId", team);
24770
+ return url.toString();
24771
+ }
24772
+ function isStageEnv(row, stage, key) {
24773
+ if (row.key !== key) return false;
24774
+ const target = Array.isArray(row.target) ? row.target.filter((item) => typeof item === "string") : [];
24775
+ if (stage === "main") return target.length === 1 && target[0] === "production";
24776
+ return target.length === 1 && target[0] === "preview" && row.gitBranch === "rc";
24777
+ }
24778
+ function envsOf(body) {
24779
+ if (Array.isArray(body)) return body;
24780
+ if (body && typeof body === "object" && Array.isArray(body.envs)) {
24781
+ return body.envs;
24782
+ }
24783
+ return null;
24784
+ }
24785
+ async function readBody(res, token) {
24786
+ let raw = "";
24787
+ try {
24788
+ raw = await res.text();
24789
+ } catch (e) {
24790
+ return { ok: false, reason: safeReason(e instanceof Error ? e.message : String(e), token) };
24791
+ }
24792
+ if (!res.ok) return { ok: false, reason: `HTTP ${res.status}${raw ? `: ${safeReason(raw, token)}` : ""}` };
24793
+ if (!raw.trim()) return { ok: true, body: {} };
24794
+ try {
24795
+ return { ok: true, body: JSON.parse(raw) };
24796
+ } catch {
24797
+ return { ok: false, reason: "response was not JSON" };
24798
+ }
24799
+ }
24800
+ async function setVercelVersionEnv(deps, meta, stage, tag) {
24801
+ if (meta?.deployModel !== "vercel" || !meta.vercelVersionEnv) return SKIPPED;
24802
+ const { project: project2, key, team } = meta.vercelVersionEnv;
24803
+ if (!project2 || !key) return failed(deps, key || "(missing key)", tag, "vercelVersionEnv is missing project or key");
24804
+ let token;
24805
+ try {
24806
+ token = await deps.readToken();
24807
+ } catch (e) {
24808
+ return failed(deps, key, tag, safeReason(e instanceof Error ? e.message : String(e), null));
24809
+ }
24810
+ if (!token) return failed(deps, key, tag, "no _org/vercel/API_TOKEN");
24811
+ const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
24812
+ let listed;
24813
+ try {
24814
+ const res = await deps.fetch(envCollectionUrl(project2, team, stage === "rc" ? { gitBranch: "rc" } : void 0), { method: "GET", headers });
24815
+ listed = await readBody(res, token);
24816
+ } catch (e) {
24817
+ return failed(deps, key, tag, safeReason(e instanceof Error ? e.message : String(e), token));
24818
+ }
24819
+ if (!listed.ok) return failed(deps, key, tag, listed.reason);
24820
+ const rows = envsOf(listed.body);
24821
+ if (!rows) return failed(deps, key, tag, "env list returned an unexpected shape");
24822
+ const existing = rows.find((row) => isStageEnv(row, stage, key) && typeof row.id === "string" && row.id.length > 0);
24823
+ const createBody = stage === "main" ? { key, value: tag, type: "plain", target: ["production"] } : { key, value: tag, type: "plain", target: ["preview"], gitBranch: "rc" };
24824
+ try {
24825
+ const res = existing ? await deps.fetch(envItemUrl(project2, existing.id, team), {
24826
+ method: "PATCH",
24827
+ headers,
24828
+ body: JSON.stringify({ value: tag })
24829
+ }) : await deps.fetch(envCollectionUrl(project2, team), {
24830
+ method: "POST",
24831
+ headers,
24832
+ body: JSON.stringify(createBody)
24833
+ });
24834
+ const written = await readBody(res, token);
24835
+ if (!written.ok) return failed(deps, key, tag, written.reason);
24836
+ } catch (e) {
24837
+ return failed(deps, key, tag, safeReason(e instanceof Error ? e.message : String(e), token));
24838
+ }
24839
+ const note = `vercel version env: ${key}=${tag} set on ${targetLabel(stage)}`;
24840
+ deps.warn?.(note);
24841
+ return { status: "set", note };
24842
+ }
24843
+ async function defaultVercelFetch(url, init) {
24844
+ const res = await fetch(url, { ...init, signal: AbortSignal.timeout(15e3) });
24845
+ return { ok: res.ok, status: res.status, text: () => res.text() };
24846
+ }
24847
+ async function readOrgVercelApiToken() {
24848
+ const { loadConfig: loadConfig2, hubHeaders: hubHeaders2, repoProject: repoProject2 } = await Promise.resolve().then(() => (init_cli_shared(), cli_shared_exports));
24849
+ const cfg = await loadConfig2();
24850
+ if (!cfg.sagaApiUrl) return null;
24851
+ const repo = `mutmutco/${(await repoProject2(cfg)).toLowerCase()}`;
24852
+ const res = await fetch(`${cfg.sagaApiUrl}/secrets/get`, {
24853
+ method: "POST",
24854
+ headers: await hubHeaders2({ "content-type": "application/json" }),
24855
+ body: JSON.stringify({ repo, key: "vercel/API_TOKEN", use: true, slug: "_org" }),
24856
+ signal: AbortSignal.timeout(8e3)
24857
+ });
24858
+ if (!res.ok) return null;
24859
+ const body = await res.json();
24860
+ return typeof body.value === "string" ? body.value : null;
24861
+ }
24862
+ function vercelVersionEnvDeps(deps) {
24863
+ return {
24864
+ fetch: deps.vercelFetch ?? defaultVercelFetch,
24865
+ readToken: deps.readVercelApiToken ?? readOrgVercelApiToken,
24866
+ warn: (message2) => deps.warn?.(message2)
24867
+ };
24868
+ }
24869
+
24712
24870
  // src/train-apply-phases.ts
24713
24871
  var TRAIN_BUMP_INTENTS = ["major", "minor", "patch"];
24714
24872
  function readLocalGateWorkflows() {
@@ -25313,14 +25471,14 @@ async function recoverFailedDispatchPhase(deps, ledger, anchors, phase, dispatch
25313
25471
  let fallbackRecovered = false;
25314
25472
  const failedRows = workflowRuns?.filter((run) => run.conclusion === "failure") ?? [];
25315
25473
  if (failedRows.length > 0) {
25316
- for (const failed of failedRows) {
25317
- const rowPhase = failed.workflow.includes("publish") ? "publish" : phase;
25474
+ for (const failed2 of failedRows) {
25475
+ const rowPhase = failed2.workflow.includes("publish") ? "publish" : phase;
25318
25476
  if (persisted?.phases[rowPhase]?.superseded === true) continue;
25319
25477
  const recovery = await recover(deps, ledger, anchors, rowPhase, {
25320
25478
  repo: input.repo,
25321
25479
  sha: input.sha,
25322
- failure: `watched ${rowPhase} run failed: ${failed.workflow} (${dispatch.note})`,
25323
- runs: [{ ...failed, repo: input.repo }],
25480
+ failure: `watched ${rowPhase} run failed: ${failed2.workflow} (${dispatch.note})`,
25481
+ runs: [{ ...failed2, repo: input.repo }],
25324
25482
  watched: input.watched
25325
25483
  });
25326
25484
  if (recovery.note) notes.push(recovery.note);
@@ -25386,6 +25544,27 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
25386
25544
  function releaseAnnounceDeferral(model, summaryFile) {
25387
25545
  return prodReleaseTargets(model).length > 0 || model === "vercel" ? `\`mmi-cli devops release --resume --watch${summaryFile ? ` --announce-summary-file ${summaryFile}` : ""}\` announces it once they are green` : "a central deploy is proven only under --watch, so this release stays unannounced";
25388
25546
  }
25547
+ function annotateVersionEnvDispatch(dispatch, result) {
25548
+ if (!result.note) return dispatch;
25549
+ return { ...dispatch, note: `${dispatch.note}; ${result.note}` };
25550
+ }
25551
+ function annotateVersionEnvAnnounce(note, result) {
25552
+ if (!result.note) return note;
25553
+ return note ? `${note}; ${result.note}` : result.note;
25554
+ }
25555
+ async function pushAfterVercelVersionEnv(deps, meta, stage, tag, pushArgs) {
25556
+ const result = await setVercelVersionEnv(vercelVersionEnvDeps(deps), meta, stage, tag);
25557
+ try {
25558
+ await runGitPush(deps, pushArgs);
25559
+ } catch (e) {
25560
+ const base = e instanceof Error ? e.message : String(e);
25561
+ const parts = [base];
25562
+ if (result.note) parts.push(result.note);
25563
+ if (result.status === "set" && meta.vercelVersionEnv?.key) parts.push(vercelVersionEnvAheadNote(meta.vercelVersionEnv.key));
25564
+ throw new Error(parts.join(" \u2014 "));
25565
+ }
25566
+ return result;
25567
+ }
25389
25568
  async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger, alignEarly) {
25390
25569
  const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
25391
25570
  const ledgerAnchors = { repo: ctx.repo, tag, tagSha: releaseSha };
@@ -25482,9 +25661,10 @@ ${recovery.note}`), recoveryInput);
25482
25661
  landed: "the immutable tag pushed at " + releaseSha.slice(0, 12)
25483
25662
  });
25484
25663
  if (trueMergeGateNote) checks = `${trueMergeGateNote}; ${checks}`;
25664
+ let versionEnv = { status: "skipped", note: "" };
25485
25665
  try {
25486
25666
  deps.warn?.("Release: Promoting the verified release");
25487
- await runGitPush(deps, ["push", "origin", "main"]);
25667
+ versionEnv = await pushAfterVercelVersionEnv(deps, meta, "main", tag, ["push", "origin", "main"]);
25488
25668
  } catch (e) {
25489
25669
  await recordPhase(ledger, deps, ledgerAnchors, "promotion", phaseEntry({ state: "failed", sha: releaseSha, error: `origin/main push failed after ${tag} was pushed: ${e instanceof Error ? e.message : String(e)}` }), { landed: "the immutable tag pushed at " + releaseSha.slice(0, 12) });
25490
25670
  throw partialTrainRecoveryError(e, recoveryInput);
@@ -25517,7 +25697,7 @@ ${recovery.note}`), recoveryInput);
25517
25697
  workflow: failedReleaseRun?.workflow ?? (isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow")
25518
25698
  }
25519
25699
  );
25520
- const deployDispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, recoveredDeployDispatch);
25700
+ const deployDispatch = annotateVersionEnvDispatch(await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, recoveredDeployDispatch), versionEnv);
25521
25701
  const deployLeg = phaseEntry(validSuiteCutContext(ctx.repo, deps.suiteCut) && ctx.slug !== "mmi-katip" ? { state: "skipped", sha: releaseSha, note: "suite bundle-only: live backend not deployed (#7263)" } : deployPhaseInput(deployModel, deployDispatch, { releaseSha }));
25522
25702
  await recordPhase(ledger, deps, ledgerAnchors, "deploy", deployLeg, {
25523
25703
  landed: "the immutable tag, the green required-check wall, the origin/main fast-forward, and the verified GitHub Release"
@@ -25551,7 +25731,7 @@ ${recovery.note}`), recoveryInput);
25551
25731
  appendPublishDispatch(deployDispatch, publishDispatch)
25552
25732
  );
25553
25733
  alignment = await aligning;
25554
- const announceNote = await announcing;
25734
+ const announceNote = annotateVersionEnvAnnounce(await announcing, versionEnv);
25555
25735
  return { checks, releaseUrl, announceNote, dispatch, ledgerAnchors, ...alignment ? { alignment } : {} };
25556
25736
  }
25557
25737
  function resumeStateOf(status) {
@@ -25754,10 +25934,10 @@ ${spent.note}`);
25754
25934
  await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified the required-check wall live on this resume" }), { strict, landed: "the immutable rc tag on origin at the resumed SHA" });
25755
25935
  const autoRunSince = (deps.now ?? Date.now)();
25756
25936
  deps.warn?.("Release: Promoting the verified release candidate");
25757
- await runGitPush(deps, ["push", "origin", `${tagSha}:refs/heads/rc`]);
25937
+ const versionEnv = await pushAfterVercelVersionEnv(deps, meta, "rc", tag, ["push", "origin", `${tagSha}:refs/heads/rc`]);
25758
25938
  await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/rc (tag preserved, never re-cut)` }), { strict, landed: "the immutable rc tag and the re-proven required-check wall" });
25759
25939
  await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "skipped", sha: tagSha, note: "rcand mints no GitHub Release \u2014 the rc tag is the candidate" }), { strict, landed: "the immutable rc tag, the green wall, and origin/rc" });
25760
- const dispatch0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, autoRunSince, tagSha, "report");
25940
+ const dispatch0 = annotateVersionEnvDispatch(await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, autoRunSince, tagSha, "report"), versionEnv);
25761
25941
  const dispatch = await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch0, { strict });
25762
25942
  const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
25763
25943
  return {
@@ -26037,7 +26217,7 @@ ${recovery.note}`), recoveryInput);
26037
26217
  }
26038
26218
  await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified the required-check wall live on this resume" }), { strict: ledgerMode === "strict", landed: "the immutable tag on origin at the resumed SHA" });
26039
26219
  deps.warn?.("Release: Promoting the verified release");
26040
- await runGitPush(deps, ["push", "origin", `${tagSha}:refs/heads/main`]);
26220
+ const versionEnv = await pushAfterVercelVersionEnv(deps, meta, "main", tag, ["push", "origin", `${tagSha}:refs/heads/main`]);
26041
26221
  steps.push(`pushed ${tagSha.slice(0, 12)} to origin/main`);
26042
26222
  await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/main (tag preserved, never re-cut)` }), { strict: ledgerMode === "strict", landed: "the immutable tag and the re-proven required-check wall" });
26043
26223
  deps.warn?.("Release: Creating the GitHub Release");
@@ -26067,7 +26247,7 @@ ${recovery.note}`), recoveryInput);
26067
26247
  workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow"
26068
26248
  }
26069
26249
  );
26070
- const deployDispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, recoveredDeployDispatch);
26250
+ const deployDispatch = annotateVersionEnvDispatch(await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, recoveredDeployDispatch), versionEnv);
26071
26251
  steps.push(`dispatched the ${deployModel} deploy path`);
26072
26252
  const deployLeg = persisted?.phases.deploy.superseded === true ? persisted.phases.deploy : phaseEntry(validSuiteCutContext(ctx.repo, deps.suiteCut) && ctx.slug !== "mmi-katip" ? { state: "skipped", sha: tagSha, note: "suite bundle-only: live backend not deployed (#7263)" } : deployPhaseInput(deployModel, deployDispatch, { releaseSha: tagSha }));
26073
26253
  if (persisted?.phases.deploy.superseded !== true) {
@@ -26108,7 +26288,7 @@ ${recovery.note}`), recoveryInput);
26108
26288
  devRollForward = landed.dev;
26109
26289
  rcAlignment = landed.rc;
26110
26290
  }
26111
- const announceNote = await announcing;
26291
+ const announceNote = annotateVersionEnvAnnounce(await announcing, versionEnv);
26112
26292
  steps.push(`development roll-forward: ${devRollForward.status}`);
26113
26293
  if (rcAlignment) steps.push(`rc alignment: ${rcAlignment.status}`);
26114
26294
  const followUp = deriveTrainFollowUpStatus({
@@ -26448,15 +26628,16 @@ ${spent.note}`), rcandRecovery(tag2));
26448
26628
  await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: rcSha, note: checks2 }), { landed: "the immutable rc tag pushed at " + rcSha.slice(0, 12) });
26449
26629
  const autoRunSince = (deps.now ?? Date.now)();
26450
26630
  deps.warn?.("Release: Promoting the verified release candidate");
26631
+ let versionEnv = { status: "skipped", note: "" };
26451
26632
  try {
26452
- await runGitPush(deps, ["push", "origin", "rc"]);
26633
+ versionEnv = await pushAfterVercelVersionEnv(deps, meta, "rc", tag2, ["push", "origin", "rc"]);
26453
26634
  } catch (e) {
26454
26635
  await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "failed", sha: rcSha, error: `origin/rc push failed after ${tag2} was pushed: ${e instanceof Error ? e.message : String(e)}` }), { landed: "the immutable rc tag pushed at " + rcSha.slice(0, 12) });
26455
26636
  throw partialTrainRecoveryError(e, rcandRecovery(tag2));
26456
26637
  }
26457
26638
  await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: rcSha, note: `immutable ${tag2} pushed at ${rcSha.slice(0, 12)}; origin/rc fast-forwarded to it` }), { landed: "the immutable rc tag and the green required-check wall" });
26458
26639
  await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "skipped", sha: rcSha, note: "rcand mints no GitHub Release \u2014 the rc tag is the candidate" }), { landed: "the immutable rc tag, the green wall, and origin/rc" });
26459
- const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha, "report");
26640
+ const d0 = annotateVersionEnvDispatch(await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha, "report"), versionEnv);
26460
26641
  const d2 = await recordRcandDeployLegs(ledger, deps, anchors, deployModel2, d0);
26461
26642
  const rcLedgerReport = releaseLedgerReport(await ledger?.load(anchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
26462
26643
  return { ...ctx, command, stage: "rc", ref: "rc", tag: tag2, bumpIntent, deployModel: deployModel2, promoted: true, checks: checks2, resumeNote, dispatch: d2.note, runId: d2.runId, runUrl: d2.runUrl, workflowRuns: d2.workflowRuns, deployStatus: d2.deployStatus, ...rcLedgerReport ? { ledger: rcLedgerReport } : {} };
@@ -28643,13 +28824,13 @@ function buildPluginCachePlan(home, running, deps, opts = {}) {
28643
28824
  }
28644
28825
  function applyPluginCachePlan(plan, remove2, stagingGuard) {
28645
28826
  const removed = [];
28646
- const failed = [];
28827
+ const failed2 = [];
28647
28828
  for (const version of plan.prune) {
28648
28829
  try {
28649
28830
  remove2(`${plan.cacheRoot}/${version}`);
28650
28831
  removed.push(version);
28651
28832
  } catch (e) {
28652
- failed.push({ version, error: e.message });
28833
+ failed2.push({ version, error: e.message });
28653
28834
  }
28654
28835
  }
28655
28836
  const removedStaging = [];
@@ -28682,7 +28863,7 @@ function applyPluginCachePlan(plan, remove2, stagingGuard) {
28682
28863
  }
28683
28864
  return {
28684
28865
  removed,
28685
- failed,
28866
+ failed: failed2,
28686
28867
  bytes: plan.bytes,
28687
28868
  removedStaging,
28688
28869
  failedStaging,
@@ -29694,7 +29875,7 @@ init_hub_auth();
29694
29875
  init_github_client();
29695
29876
 
29696
29877
  // src/project-set.ts
29697
- var UNSET_KEYS = ["projectAdmins", "installer", "oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary", "notifyOnDeploy"];
29878
+ var UNSET_KEYS = ["projectAdmins", "installer", "oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary", "notifyOnDeploy", "vercelVersionEnv"];
29698
29879
  var UNSET_KEY_SET = new Set(UNSET_KEYS);
29699
29880
  var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
29700
29881
  var SECRET_CONSUMERS = ["runtime", "build", "lambda", "actions", "agent", "box"];
@@ -30048,6 +30229,32 @@ function parseNotifyOnDeployVar(raw) {
30048
30229
  }
30049
30230
  return parsed;
30050
30231
  }
30232
+ var VERCEL_VERSION_ENV_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
30233
+ var VERCEL_VERSION_ENV_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
30234
+ function parseVercelVersionEnvVar(raw) {
30235
+ let parsed;
30236
+ try {
30237
+ parsed = JSON.parse(raw);
30238
+ } catch {
30239
+ throw new Error('org project set: vercelVersionEnv must be JSON, e.g. {"project":"mm-work","key":"MMW_VERSION","team":"team_xxx"}');
30240
+ }
30241
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
30242
+ throw new Error("org project set: vercelVersionEnv must be a JSON object { project, key, team? }");
30243
+ }
30244
+ const row = parsed;
30245
+ const unknown = Object.keys(row).filter((key) => key !== "project" && key !== "key" && key !== "team");
30246
+ if (unknown.length > 0) throw new Error(`org project set: vercelVersionEnv has unknown field(s): ${unknown.join(", ")}`);
30247
+ if (typeof row.project !== "string" || !VERCEL_VERSION_ENV_ID_RE.test(row.project)) {
30248
+ throw new Error("org project set: vercelVersionEnv.project must be a Vercel project id or name");
30249
+ }
30250
+ if (typeof row.key !== "string" || !VERCEL_VERSION_ENV_NAME_RE.test(row.key)) {
30251
+ throw new Error("org project set: vercelVersionEnv.key must be an env name (e.g. MMW_VERSION)");
30252
+ }
30253
+ if (row.team !== void 0 && (typeof row.team !== "string" || !VERCEL_VERSION_ENV_ID_RE.test(row.team))) {
30254
+ throw new Error("org project set: vercelVersionEnv.team must be a Vercel team id or slug");
30255
+ }
30256
+ return { project: row.project, key: row.key, ...typeof row.team === "string" ? { team: row.team } : {} };
30257
+ }
30051
30258
  function parseCiExemptReasonVar(raw) {
30052
30259
  const trimmed = raw.trim();
30053
30260
  if (!trimmed) throw new Error("org project set: ciExemptReason must be a non-empty reason (or use --unset ciExemptReason to clear it)");
@@ -30189,7 +30396,8 @@ var SETTABLE_VAR_KEYS = [
30189
30396
  "secrets",
30190
30397
  "seedCanary",
30191
30398
  "installer",
30192
- "notifyOnDeploy"
30399
+ "notifyOnDeploy",
30400
+ "vercelVersionEnv"
30193
30401
  ];
30194
30402
  var SETTABLE_VAR_KEY_SET = new Set(SETTABLE_VAR_KEYS);
30195
30403
  var SETTABLE_VAR_HINTS = {
@@ -30219,6 +30427,7 @@ var SETTABLE_VAR_HINTS = {
30219
30427
  requiredChecks: 'JSON array, e.g. ["gate"] or [] for no-ci',
30220
30428
  requiredCheckBranches: "non-empty JSON branch-name array; exact product-ruleset scope (#5210)",
30221
30429
  notifyOnDeploy: 'JSON stage array, e.g. ["dev"] \u2014 successful tenant-deploy sends <stage>-deployed to the project repo (#7358)',
30430
+ vercelVersionEnv: "JSON {project,key,team?} \u2014 train sets this Vercel env to the release tag before the main/rc push (#7362)",
30222
30431
  ciExemptReason: "non-empty string \u2014 declares this deployable repo has no CI surface (#4265)",
30223
30432
  gate: "JSON {runtime,cmd,affectedCmd,workdir,cacheDepPath,pyVersion}"
30224
30433
  };
@@ -30364,6 +30573,8 @@ function buildProjectSetPatch(input) {
30364
30573
  patch[key] = parseRequiredCheckBranchesVar(raw);
30365
30574
  } else if (key === "notifyOnDeploy") {
30366
30575
  patch[key] = parseNotifyOnDeployVar(raw);
30576
+ } else if (key === "vercelVersionEnv") {
30577
+ patch[key] = parseVercelVersionEnvVar(raw);
30367
30578
  } else if (key === "ciExemptReason") {
30368
30579
  patch[key] = parseCiExemptReasonVar(raw);
30369
30580
  } else if (key === "gate") {
@@ -35014,7 +35225,7 @@ async function runHotfixStartLocked(deps, options, doctor) {
35014
35225
  throw new Error(`working tree must be clean before hotfix start${describeBlockingPaths(status)}`);
35015
35226
  }
35016
35227
  await deps.run("git", ["fetch", "origin", "--tags"]);
35017
- const { deployModel } = await hotfixPreflight(deps, ctx, "start");
35228
+ const { deployModel, meta } = await hotfixPreflight(deps, ctx, "start");
35018
35229
  const bumpNotes = [];
35019
35230
  let resolved = await resolveHotfixVersion(deps);
35020
35231
  for (; ; ) {
@@ -35136,6 +35347,8 @@ async function runHotfixStartLocked(deps, options, doctor) {
35136
35347
  await assertHotfixFoldBomStamp(deps, tag);
35137
35348
  await deps.run("git", ["push", "-u", "origin", branch]);
35138
35349
  }
35350
+ const versionEnv = await setVercelVersionEnv(vercelVersionEnvDeps(deps), meta, "main", tag);
35351
+ if (versionEnv.note) notes.push(versionEnv.note);
35139
35352
  const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : foldPaths.length > 0 ? " with the package version bump" : "";
35140
35353
  const prUrl = clean3(await deps.run("gh", [
35141
35354
  "pr",
@@ -37387,12 +37600,12 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
37387
37600
  }
37388
37601
  function evaluatePrMergeHousekeeping(report, context, force = false) {
37389
37602
  const scratch = report.scratch;
37390
- const failed = scratch?.status === "failed";
37603
+ const failed2 = scratch?.status === "failed";
37391
37604
  const prunable = scratch?.safeAuto ?? 0;
37392
37605
  const unprunable = scratch?.skipped ?? 0;
37393
- if (!(failed || prunable > 0 || unprunable > 0)) return { blocked: false };
37606
+ if (!(failed2 || prunable > 0 || unprunable > 0)) return { blocked: false };
37394
37607
  if (force) return { blocked: false };
37395
- const detail = failed ? `scratch housekeeping failed${scratch?.error ? `: ${scratch.error}` : ""}` : `${prunable} prunable + ${unprunable} un-prunable scratch item(s) remain after cleanup`;
37608
+ const detail = failed2 ? `scratch housekeeping failed${scratch?.error ? `: ${scratch.error}` : ""}` : `${prunable} prunable + ${unprunable} un-prunable scratch item(s) remain after cleanup`;
37396
37609
  return {
37397
37610
  blocked: true,
37398
37611
  reason: `${context} housekeeping blocked merge: ${detail}. Run host-owned scratch cleanup, then retry \u2014 or re-run \`mmi-cli ${context} --force\` to acknowledge and land anyway. (Kept advisory plans/ scratch never blocks: it is gitignored and can never reach origin.)`
@@ -45137,15 +45350,15 @@ function renderReport(checks, opts) {
45137
45350
  }
45138
45351
  function renderTally(checks) {
45139
45352
  const total = checks.length;
45140
- const failed = checks.filter((c) => !c.ok).length;
45353
+ const failed2 = checks.filter((c) => !c.ok).length;
45141
45354
  const unverified = checks.filter((c) => c.ok && c.verified === false).length;
45142
45355
  const warned = checks.filter((c) => c.ok && c.verified !== false && c.warn).length;
45143
- const healthy = total - failed - unverified - warned;
45144
- if (failed === 0 && unverified === 0 && warned === 0) return `\u2713 all ${total} checks healthy`;
45356
+ const healthy = total - failed2 - unverified - warned;
45357
+ if (failed2 === 0 && unverified === 0 && warned === 0) return `\u2713 all ${total} checks healthy`;
45145
45358
  const parts = [`\u2713 ${healthy} verified healthy`];
45146
45359
  if (warned > 0) parts.push(`\u26A0 ${warned} warn`);
45147
45360
  if (unverified > 0) parts.push(`? ${unverified} unverified`);
45148
- if (failed > 0) parts.push(`\u2717 ${failed} failed`);
45361
+ if (failed2 > 0) parts.push(`\u2717 ${failed2} failed`);
45149
45362
  return `${parts.join(" \xB7 ")} \u2014 ${total} checks`;
45150
45363
  }
45151
45364
  function doctorReportExitCode(checks) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.36",
3
+ "version": "4.5.37",
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",