@mutmutco/cli 4.3.67 → 4.3.68

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 +285 -88
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -1741,8 +1741,8 @@ var Help = class {
1741
1741
  * @param {string} str
1742
1742
  * @returns {number}
1743
1743
  */
1744
- displayWidth(str) {
1745
- return (0, import_node_util.stripVTControlCharacters)(str).length;
1744
+ displayWidth(str2) {
1745
+ return (0, import_node_util.stripVTControlCharacters)(str2).length;
1746
1746
  }
1747
1747
  /**
1748
1748
  * Style the title for displaying in the help. Called with 'Usage:', 'Options:', etc.
@@ -1750,11 +1750,11 @@ var Help = class {
1750
1750
  * @param {string} str
1751
1751
  * @returns {string}
1752
1752
  */
1753
- styleTitle(str) {
1754
- return str;
1753
+ styleTitle(str2) {
1754
+ return str2;
1755
1755
  }
1756
- styleUsage(str) {
1757
- return str.split(" ").map((word) => {
1756
+ styleUsage(str2) {
1757
+ return str2.split(" ").map((word) => {
1758
1758
  if (word === "[options]") return this.styleOptionText(word);
1759
1759
  if (word === "[command]") return this.styleSubcommandText(word);
1760
1760
  if (word[0] === "[" || word[0] === "<")
@@ -1762,46 +1762,46 @@ var Help = class {
1762
1762
  return this.styleCommandText(word);
1763
1763
  }).join(" ");
1764
1764
  }
1765
- styleCommandDescription(str) {
1766
- return this.styleDescriptionText(str);
1765
+ styleCommandDescription(str2) {
1766
+ return this.styleDescriptionText(str2);
1767
1767
  }
1768
- styleOptionDescription(str) {
1769
- return this.styleDescriptionText(str);
1768
+ styleOptionDescription(str2) {
1769
+ return this.styleDescriptionText(str2);
1770
1770
  }
1771
- styleSubcommandDescription(str) {
1772
- return this.styleDescriptionText(str);
1771
+ styleSubcommandDescription(str2) {
1772
+ return this.styleDescriptionText(str2);
1773
1773
  }
1774
- styleArgumentDescription(str) {
1775
- return this.styleDescriptionText(str);
1774
+ styleArgumentDescription(str2) {
1775
+ return this.styleDescriptionText(str2);
1776
1776
  }
1777
- styleDescriptionText(str) {
1778
- return str;
1777
+ styleDescriptionText(str2) {
1778
+ return str2;
1779
1779
  }
1780
- styleOptionTerm(str) {
1781
- return this.styleOptionText(str);
1780
+ styleOptionTerm(str2) {
1781
+ return this.styleOptionText(str2);
1782
1782
  }
1783
- styleSubcommandTerm(str) {
1784
- return str.split(" ").map((word) => {
1783
+ styleSubcommandTerm(str2) {
1784
+ return str2.split(" ").map((word) => {
1785
1785
  if (word === "[options]") return this.styleOptionText(word);
1786
1786
  if (word[0] === "[" || word[0] === "<")
1787
1787
  return this.styleArgumentText(word);
1788
1788
  return this.styleSubcommandText(word);
1789
1789
  }).join(" ");
1790
1790
  }
1791
- styleArgumentTerm(str) {
1792
- return this.styleArgumentText(str);
1791
+ styleArgumentTerm(str2) {
1792
+ return this.styleArgumentText(str2);
1793
1793
  }
1794
- styleOptionText(str) {
1795
- return str;
1794
+ styleOptionText(str2) {
1795
+ return str2;
1796
1796
  }
1797
- styleArgumentText(str) {
1798
- return str;
1797
+ styleArgumentText(str2) {
1798
+ return str2;
1799
1799
  }
1800
- styleSubcommandText(str) {
1801
- return str;
1800
+ styleSubcommandText(str2) {
1801
+ return str2;
1802
1802
  }
1803
- styleCommandText(str) {
1804
- return str;
1803
+ styleCommandText(str2) {
1804
+ return str2;
1805
1805
  }
1806
1806
  /**
1807
1807
  * Calculate the pad width from the maximum term length.
@@ -1824,8 +1824,8 @@ var Help = class {
1824
1824
  * @param {string} str
1825
1825
  * @returns {boolean}
1826
1826
  */
1827
- preformatted(str) {
1828
- return /\n[^\S\r\n]/.test(str);
1827
+ preformatted(str2) {
1828
+ return /\n[^\S\r\n]/.test(str2);
1829
1829
  }
1830
1830
  /**
1831
1831
  * Format the "item", which consists of a term and description. Pad the term and wrap the description, indenting the following lines.
@@ -1871,9 +1871,9 @@ ${itemIndentStr}`);
1871
1871
  * @param {number} width
1872
1872
  * @returns {string}
1873
1873
  */
1874
- boxWrap(str, width) {
1875
- if (width < this.minWidthToWrap) return str;
1876
- const rawLines = str.split(/\r\n|\n/);
1874
+ boxWrap(str2, width) {
1875
+ if (width < this.minWidthToWrap) return str2;
1876
+ const rawLines = str2.split(/\r\n|\n/);
1877
1877
  const chunkPattern = /[\s]*[^\s]+/g;
1878
1878
  const wrappedLines = [];
1879
1879
  rawLines.forEach((line) => {
@@ -2163,9 +2163,9 @@ var DualOptions = class {
2163
2163
  return option.negate === (negativeValue === value);
2164
2164
  }
2165
2165
  };
2166
- function camelcase(str) {
2167
- return str.split("-").reduce((str2, word) => {
2168
- return str2 + word[0].toUpperCase() + word.slice(1);
2166
+ function camelcase(str2) {
2167
+ return str2.split("-").reduce((str3, word) => {
2168
+ return str3 + word[0].toUpperCase() + word.slice(1);
2169
2169
  });
2170
2170
  }
2171
2171
  function splitOptionFlags(flags) {
@@ -2325,14 +2325,14 @@ var Command = class _Command extends import_node_events.EventEmitter {
2325
2325
  this._showSuggestionAfterError = true;
2326
2326
  this._savedState = null;
2327
2327
  this._outputConfiguration = {
2328
- writeOut: (str) => import_node_process.default.stdout.write(str),
2329
- writeErr: (str) => import_node_process.default.stderr.write(str),
2330
- outputError: (str, write) => write(str),
2328
+ writeOut: (str2) => import_node_process.default.stdout.write(str2),
2329
+ writeErr: (str2) => import_node_process.default.stderr.write(str2),
2330
+ outputError: (str2, write) => write(str2),
2331
2331
  getOutHelpWidth: () => import_node_process.default.stdout.isTTY ? import_node_process.default.stdout.columns : void 0,
2332
2332
  getErrHelpWidth: () => import_node_process.default.stderr.isTTY ? import_node_process.default.stderr.columns : void 0,
2333
2333
  getOutHasColors: () => useColor() ?? (import_node_process.default.stdout.isTTY && import_node_process.default.stdout.hasColors?.()),
2334
2334
  getErrHasColors: () => useColor() ?? (import_node_process.default.stderr.isTTY && import_node_process.default.stderr.hasColors?.()),
2335
- stripColor: (str) => (0, import_node_util2.stripVTControlCharacters)(str)
2335
+ stripColor: (str2) => (0, import_node_util2.stripVTControlCharacters)(str2)
2336
2336
  };
2337
2337
  this._hidden = false;
2338
2338
  this._helpOption = void 0;
@@ -4060,18 +4060,18 @@ Expecting one of '${allowedValues.join("', '")}'`);
4060
4060
  * @param {string} [description]
4061
4061
  * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments
4062
4062
  */
4063
- version(str, flags, description) {
4064
- if (str === void 0) return this._version;
4065
- this._version = str;
4063
+ version(str2, flags, description) {
4064
+ if (str2 === void 0) return this._version;
4065
+ this._version = str2;
4066
4066
  flags = flags || "-V, --version";
4067
4067
  description = description || "output the version number";
4068
4068
  const versionOption = this.createOption(flags, description);
4069
4069
  this._versionOptionName = versionOption.attributeName();
4070
4070
  this._registerOption(versionOption);
4071
4071
  this.on("option:" + versionOption.name(), () => {
4072
- this._outputConfiguration.writeOut(`${str}
4072
+ this._outputConfiguration.writeOut(`${str2}
4073
4073
  `);
4074
- this._exit(0, "commander.version", str);
4074
+ this._exit(0, "commander.version", str2);
4075
4075
  });
4076
4076
  return this;
4077
4077
  }
@@ -4082,10 +4082,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
4082
4082
  * @param {object} [argsDescription]
4083
4083
  * @return {(string|Command)}
4084
4084
  */
4085
- description(str, argsDescription) {
4086
- if (str === void 0 && argsDescription === void 0)
4085
+ description(str2, argsDescription) {
4086
+ if (str2 === void 0 && argsDescription === void 0)
4087
4087
  return this._description;
4088
- this._description = str;
4088
+ this._description = str2;
4089
4089
  if (argsDescription) {
4090
4090
  this._argsDescription = argsDescription;
4091
4091
  }
@@ -4097,9 +4097,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
4097
4097
  * @param {string} [str]
4098
4098
  * @return {(string|Command)}
4099
4099
  */
4100
- summary(str) {
4101
- if (str === void 0) return this._summary;
4102
- this._summary = str;
4100
+ summary(str2) {
4101
+ if (str2 === void 0) return this._summary;
4102
+ this._summary = str2;
4103
4103
  return this;
4104
4104
  }
4105
4105
  /**
@@ -4147,8 +4147,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
4147
4147
  * @param {string} [str]
4148
4148
  * @return {(string|Command)}
4149
4149
  */
4150
- usage(str) {
4151
- if (str === void 0) {
4150
+ usage(str2) {
4151
+ if (str2 === void 0) {
4152
4152
  if (this._usage) return this._usage;
4153
4153
  const args = this.registeredArguments.map((arg) => {
4154
4154
  return humanReadableArgName(arg);
@@ -4159,7 +4159,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
4159
4159
  this.registeredArguments.length ? args : []
4160
4160
  ).join(" ");
4161
4161
  }
4162
- this._usage = str;
4162
+ this._usage = str2;
4163
4163
  return this;
4164
4164
  }
4165
4165
  /**
@@ -4168,9 +4168,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
4168
4168
  * @param {string} [str]
4169
4169
  * @return {(string|Command)}
4170
4170
  */
4171
- name(str) {
4172
- if (str === void 0) return this._name;
4173
- this._name = str;
4171
+ name(str2) {
4172
+ if (str2 === void 0) return this._name;
4173
+ this._name = str2;
4174
4174
  return this;
4175
4175
  }
4176
4176
  /**
@@ -4304,17 +4304,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
4304
4304
  let hasColors;
4305
4305
  let helpWidth;
4306
4306
  if (error) {
4307
- baseWrite = (str) => this._outputConfiguration.writeErr(str);
4307
+ baseWrite = (str2) => this._outputConfiguration.writeErr(str2);
4308
4308
  hasColors = this._outputConfiguration.getErrHasColors();
4309
4309
  helpWidth = this._outputConfiguration.getErrHelpWidth();
4310
4310
  } else {
4311
- baseWrite = (str) => this._outputConfiguration.writeOut(str);
4311
+ baseWrite = (str2) => this._outputConfiguration.writeOut(str2);
4312
4312
  hasColors = this._outputConfiguration.getOutHasColors();
4313
4313
  helpWidth = this._outputConfiguration.getOutHelpWidth();
4314
4314
  }
4315
- const write = (str) => {
4316
- if (!hasColors) str = this._outputConfiguration.stripColor(str);
4317
- return baseWrite(str);
4315
+ const write = (str2) => {
4316
+ if (!hasColors) str2 = this._outputConfiguration.stripColor(str2);
4317
+ return baseWrite(str2);
4318
4318
  };
4319
4319
  return { error, write, hasColors, helpWidth };
4320
4320
  }
@@ -7004,7 +7004,7 @@ init_github_client();
7004
7004
 
7005
7005
  // src/project-model.ts
7006
7006
  var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "mobile-app", "non-deployable", "cli-tool", "worker"];
7007
- var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "static-cdn", "registry-publish", "content", "none"];
7007
+ var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "static-cdn", "registry-publish", "content", "none", "vercel"];
7008
7008
  var RELEASE_TRACKS = ["full", "direct", "trunk"];
7009
7009
  var REQUIRED_CHECK_BRANCHES = ["development", "rc", "main"];
7010
7010
  var PROJECT_TYPE_SET = new Set(PROJECT_TYPES);
@@ -9209,6 +9209,66 @@ async function secretsCopy(deps, opts) {
9209
9209
  }
9210
9210
  return true;
9211
9211
  }
9212
+ function transferKey(leaf, stage) {
9213
+ return stage ? `${stage}/${leaf}` : leaf;
9214
+ }
9215
+ async function secretsTransfer(deps, opts) {
9216
+ const fromRepo = opts.fromRepo.trim();
9217
+ const toRepo = opts.toRepo.trim();
9218
+ if (!fromRepo || !toRepo) {
9219
+ deps.err("secrets transfer: --from-repo and --to-repo are required (owner/repo)");
9220
+ return false;
9221
+ }
9222
+ if (fromRepo.toLowerCase() === toRepo.toLowerCase() && opts.fromStage === opts.toStage) {
9223
+ deps.err("secrets transfer: source and destination are the same coordinate \u2014 use `secrets copy` to move between stages of one project");
9224
+ return false;
9225
+ }
9226
+ const report = await probeCapabilities(deps, fromRepo);
9227
+ if (report?.role !== "master") {
9228
+ deps.err(report === void 0 ? "secrets transfer: could not verify your role (capabilities probe unavailable) \u2014 cross-project transfer is master-only and refuses on an unverified role" : `secrets transfer: master-admin only \u2014 your role on ${fromRepo} is ${report.role}. Cross-project value movement is master-gated; ask the master to run the transfer.`);
9229
+ return false;
9230
+ }
9231
+ const keys = [...new Set(opts.keys.flatMap((k) => k.split(/[,\s]+/)).filter(Boolean))];
9232
+ if (!keys.length) {
9233
+ deps.err("secrets transfer: --keys required (comma- or space-separated allowlist)");
9234
+ return false;
9235
+ }
9236
+ for (const key of keys) {
9237
+ if (!isValidSecretKey(key)) {
9238
+ deps.err(`invalid secret key ${JSON.stringify(key)}`);
9239
+ return false;
9240
+ }
9241
+ if (isSecretCopyBlocked(key)) {
9242
+ deps.err(`secrets transfer: ${key} is not transferable \u2014 generate encryption/stage-distinct keys per project`);
9243
+ return false;
9244
+ }
9245
+ }
9246
+ for (const key of keys) {
9247
+ const leaf = secretLeafName(key);
9248
+ const srcKey = transferKey(leaf, opts.fromStage);
9249
+ const dstKey = transferKey(leaf, opts.toStage);
9250
+ const srcPath = `${fromRepo} ${srcKey}`;
9251
+ const dstPath = `${toRepo} ${dstKey}`;
9252
+ if (opts.dryRun) {
9253
+ const probe = await fetchSecretValue(deps, srcKey, { repo: fromRepo });
9254
+ if (!probe) {
9255
+ deps.err(`secrets transfer: could not read source ${srcPath}`);
9256
+ return false;
9257
+ }
9258
+ deps.log(`would transfer ${srcPath} \u2192 ${dstPath}`);
9259
+ continue;
9260
+ }
9261
+ const value = await fetchSecretValue(deps, srcKey, { repo: fromRepo });
9262
+ if (!value) {
9263
+ deps.err(`secrets transfer: could not read source ${srcPath}`);
9264
+ return false;
9265
+ }
9266
+ const ok = await putSecret(deps, dstKey, value, { repo: toRepo });
9267
+ if (!ok) return false;
9268
+ deps.log(`transferred ${srcPath} \u2192 ${dstPath}`);
9269
+ }
9270
+ return true;
9271
+ }
9212
9272
  var CMD_METACHAR_RE = /[&|^%<>()!"]/;
9213
9273
  var CMD_METACHAR_G = /[&|^%<>()!"]/g;
9214
9274
  function argvQuote(token, force) {
@@ -13772,7 +13832,7 @@ function walk(dir, root, out) {
13772
13832
  return out;
13773
13833
  }
13774
13834
  function defaultListDocs(root) {
13775
- const docs = ["docs", "skills/org-guide", "skills/mmi-help", "skills/brand"].flatMap((path2) => {
13835
+ const docs = ["docs", "skills/org-guide", "skills/mmi-help"].flatMap((path2) => {
13776
13836
  const directory = (0, import_node_path12.join)(root, path2);
13777
13837
  return (0, import_node_fs14.existsSync)(directory) ? walk(directory, root, []) : [];
13778
13838
  }).filter(
@@ -14755,6 +14815,7 @@ function planTrainApplyRepoGuard(applyRepo, cwdRepo, rerun, what = "--apply") {
14755
14815
  };
14756
14816
  }
14757
14817
  async function resolveFoldPaths(deps, model) {
14818
+ if (model === "vercel") return [];
14758
14819
  const helper = "scripts/release-distribution.mjs";
14759
14820
  if (model === "hub-serverless" || model === "registry-publish") {
14760
14821
  if ((0, import_node_fs16.existsSync)(helper)) {
@@ -15801,10 +15862,10 @@ var rollout_plan_default = {
15801
15862
  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)."
15802
15863
  },
15803
15864
  baseline: {
15804
- version: "4.3.67",
15805
- tag: "v4.3.67",
15806
- commit: "61dc785d3abb",
15807
- npm: "@mutmutco/cli@4.3.67"
15865
+ version: "4.3.68",
15866
+ tag: "v4.3.68",
15867
+ commit: "e9edc6e353ec",
15868
+ npm: "@mutmutco/cli@4.3.68"
15808
15869
  },
15809
15870
  exitCriterion: "fleet-n-of-n",
15810
15871
  hubOnlyShortcut: "forbidden",
@@ -15821,14 +15882,14 @@ var rollout_plan_default = {
15821
15882
  repo: "mutmutco/mmi-hub",
15822
15883
  role: "canary",
15823
15884
  schedule: "train",
15824
- v3Target: "v4.3.67"
15885
+ v3Target: "v4.3.68"
15825
15886
  }
15826
15887
  ],
15827
15888
  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.",
15828
15889
  rollback: {
15829
15890
  independent: true,
15830
- mechanism: "npm dist-tag latest -> 4.3.67 and redeploy the Hub Lambda from tag v4.3.67 (61dc785d3abb); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15831
- v3Target: "v4.3.67 (@mutmutco/cli@4.3.67, tag commit 61dc785d3abb \u2014 last known-good release carrying the repo-index v4-only contract)"
15891
+ mechanism: "npm dist-tag latest -> 4.3.68 and redeploy the Hub Lambda from tag v4.3.68 (e9edc6e353ec); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15892
+ v3Target: "v4.3.68 (@mutmutco/cli@4.3.68, tag commit e9edc6e353ec \u2014 last known-good release carrying the repo-index v4-only contract)"
15832
15893
  }
15833
15894
  },
15834
15895
  {
@@ -18670,6 +18731,56 @@ function isReleaseShaDeployEnumerationCandidate(row, nonDeployEvents) {
18670
18731
  return true;
18671
18732
  }
18672
18733
 
18734
+ // src/vercel-deployments.ts
18735
+ var TERMINAL = /* @__PURE__ */ new Set(["success", "failure", "error", "inactive"]);
18736
+ function isTerminalVercelState(state) {
18737
+ return TERMINAL.has(state);
18738
+ }
18739
+ function str(v) {
18740
+ return typeof v === "string" && v.length > 0 ? v : void 0;
18741
+ }
18742
+ async function readVercelDeployment(api, repo, target, environment) {
18743
+ const query = target.sha ? `sha=${encodeURIComponent(target.sha)}` : `ref=${encodeURIComponent(target.ref ?? "")}`;
18744
+ const listed = await api(`repos/${repo}/deployments?${query}&per_page=30`);
18745
+ const rows = Array.isArray(listed) ? listed : [];
18746
+ const prefix = environment.toLowerCase();
18747
+ const hit = rows.find((d) => typeof d.id === "number" && (str(d.environment) ?? "").toLowerCase().startsWith(prefix));
18748
+ if (!hit) return void 0;
18749
+ const statuses = await api(`repos/${repo}/deployments/${hit.id}/statuses?per_page=1`);
18750
+ const latest = Array.isArray(statuses) ? statuses[0] : void 0;
18751
+ const state = str(latest?.state) ?? "none";
18752
+ const url = str(latest?.environment_url) ?? str(latest?.target_url);
18753
+ return {
18754
+ id: hit.id,
18755
+ environment: str(hit.environment) ?? environment,
18756
+ sha: str(hit.sha) ?? target.sha ?? "",
18757
+ ref: str(hit.ref) ?? target.ref ?? "",
18758
+ state,
18759
+ ...url ? { url } : {},
18760
+ ...str(latest?.log_url) ? { logUrl: str(latest?.log_url) } : {}
18761
+ };
18762
+ }
18763
+ var VERCEL_WATCH_ATTEMPTS = 120;
18764
+ var VERCEL_WATCH_DELAY_MS = 1e4;
18765
+ async function watchVercelProduction(api, repo, sha, sleep2, onTick) {
18766
+ let last;
18767
+ for (let attempt = 0; attempt < VERCEL_WATCH_ATTEMPTS; attempt++) {
18768
+ if (attempt > 0) await sleep2(VERCEL_WATCH_DELAY_MS);
18769
+ last = await readVercelDeployment(api, repo, { sha }, "Production");
18770
+ onTick?.(attempt, last);
18771
+ if (last && isTerminalVercelState(last.state)) return last;
18772
+ }
18773
+ return last;
18774
+ }
18775
+ async function probeVercelUrl(url, fetchImpl = fetch) {
18776
+ try {
18777
+ const res = await fetchImpl(url, { method: "GET", redirect: "follow", signal: AbortSignal.timeout(15e3) });
18778
+ return { ok: res.status < 500, status: res.status };
18779
+ } catch (e) {
18780
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
18781
+ }
18782
+ }
18783
+
18673
18784
  // src/train-apply-deploy.ts
18674
18785
  var DISPATCH_ATTEMPTS = 3;
18675
18786
  var DISPATCH_RETRY_DELAY_MS = 2e3;
@@ -18841,7 +18952,45 @@ function prodReleaseTargets(model) {
18841
18952
  return [];
18842
18953
  }
18843
18954
  function trainObservesShipment(model) {
18844
- return isCentralDispatchModel(model) || prodReleaseTargets(model).length > 0;
18955
+ return isCentralDispatchModel(model) || prodReleaseTargets(model).length > 0 || model === "vercel";
18956
+ }
18957
+ async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
18958
+ const note = "no dispatch: Vercel deploys main on push (Production) \u2014 observed via the Vercel GitHub app Deployment";
18959
+ if (ref !== "main") return { note: "no dispatch on rc: a vercel repo is direct-track and has no rc stage", deployStatus: "pending" };
18960
+ if (!headSha) return { note, deployStatus: "pending" };
18961
+ const api = async (path2) => JSON.parse(await deps.run("gh", ["api", path2]));
18962
+ const row = (d, conclusion) => ({
18963
+ workflow: "vercel",
18964
+ conclusion,
18965
+ ...d?.logUrl ? { runUrl: d.logUrl } : { runUrlNote: "no Vercel Deployment posted for this SHA yet" },
18966
+ event: "push"
18967
+ });
18968
+ const verdict = (d) => d?.state === "success" ? "success" : d && isTerminalVercelState(d.state) ? "failure" : "pending";
18969
+ let read;
18970
+ try {
18971
+ if (!watch) {
18972
+ deps.warn?.("Release: Checking Vercel deployment status");
18973
+ read = await readVercelDeployment(api, ctx.repo, { sha: headSha }, "Production");
18974
+ } else {
18975
+ deps.warn?.("Release: Waiting for Vercel to deploy production");
18976
+ read = await watchVercelProduction(api, ctx.repo, headSha, resolveSleep(deps), (attempt, d) => {
18977
+ if (attempt > 0 && attempt % 6 === 0) deps.warn?.(`Vercel deployment ${d ? d.state : "not posted yet"} \u2014 ${attempt / 6}m elapsed`);
18978
+ });
18979
+ }
18980
+ } catch (e) {
18981
+ return { note: `${note}; the Deployment read FAILED (${describeReadError(e)}) \u2014 verify on Vercel; never re-tag`, workflowRuns: [row(void 0, "failure")], deployStatus: "failure" };
18982
+ }
18983
+ let deployStatus = verdict(read);
18984
+ let detail = read ? `Vercel Production deployment ${read.state}${read.url ? ` at ${read.url}` : ""}` : "no Vercel Deployment posted for this SHA (is the Vercel GitHub app connected?)";
18985
+ if (watch && deployStatus === "success" && read?.url) {
18986
+ const probe = await (deps.probeUrl ?? probeVercelUrl)(read.url);
18987
+ if (probe.ok) detail = `${detail}; URL answered HTTP ${probe.status}`;
18988
+ else {
18989
+ deployStatus = "failure";
18990
+ detail = `${detail}; URL probe FAILED (${probe.error ?? `HTTP ${probe.status}`})`;
18991
+ }
18992
+ }
18993
+ return { note: `${note}; ${detail}`, ...read?.logUrl ? { runUrl: read.logUrl } : {}, workflowRuns: [row(read, deployStatus)], deployStatus };
18845
18994
  }
18846
18995
  function shipLegsGreen(deploy, publish) {
18847
18996
  const green = (s) => s === "complete" || s === "skipped";
@@ -19023,6 +19172,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
19023
19172
  deployStatus: aggregateWorkflowRuns(workflowRuns)
19024
19173
  };
19025
19174
  }
19175
+ if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha);
19026
19176
  return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
19027
19177
  }
19028
19178
  async function readEdgeDomains(deps, slug) {
@@ -36269,6 +36419,30 @@ function registerSecretsCommands(program3) {
36269
36419
  });
36270
36420
  if (!ok) process.exitCode = 1;
36271
36421
  }));
36422
+ secrets.command("transfer").description("(master-only) copy secret VALUES from one project vault to another \u2014 the succession path `copy` cannot serve (#6687). Values never printed, never in argv; encryption/stage-distinct keys blocked; declare-first enforced on the destination").requiredOption("--from-repo <owner/repo>", "source repo whose vault holds the values").requiredOption("--to-repo <owner/repo>", "destination repo receiving the values").requiredOption("--keys <names>", "comma- or space-separated secret names (encryption keys blocked)").option("--from-stage <stage>", "source tier: dev, rc, or main (omit for the stageless canonical)").option("--to-stage <stage>", "destination tier: dev, rc, or main (omit for the stageless canonical)").option("--dry-run", "report the transfers, and verify each source is readable, without writing").action((o) => withSecrets(async (d) => {
36423
+ const stages = ["dev", "rc", "main"];
36424
+ const parseStage = (v, flag) => {
36425
+ if (v === void 0) return void 0;
36426
+ if (!stages.includes(v)) {
36427
+ fail(`secrets transfer: ${flag} must be dev, rc, or main`);
36428
+ return null;
36429
+ }
36430
+ return v;
36431
+ };
36432
+ const fromStage = parseStage(o.fromStage, "--from-stage");
36433
+ if (fromStage === null) return;
36434
+ const toStage = parseStage(o.toStage, "--to-stage");
36435
+ if (toStage === null) return;
36436
+ const ok = await secretsTransfer(d, {
36437
+ fromRepo: o.fromRepo,
36438
+ toRepo: o.toRepo,
36439
+ keys: o.keys.split(","),
36440
+ ...fromStage !== void 0 ? { fromStage } : {},
36441
+ ...toStage !== void 0 ? { toStage } : {},
36442
+ ...o.dryRun !== void 0 ? { dryRun: o.dryRun } : {}
36443
+ });
36444
+ if (!ok) process.exitCode = 1;
36445
+ }));
36272
36446
  secrets.command("import-rails-credentials").description("decrypt Rails credentials and import explicit mappings into the vault (values never printed)").requiredOption("--stage <dev|rc|main>", "target vault stage").option("--map <credential.path=ENV_KEY>", "explicit credential-to-env mapping; repeat for each key", collectMap, []).option("--app-dir <path>", "Rails app directory (defaults to cwd)").option("--credentials-file <path>", `encrypted credentials path relative to --app-dir (default: ${DEFAULT_RAILS_CREDENTIALS_FILE})`).option("--master-key-file <path>", `master key path relative to --app-dir (default: ${DEFAULT_RAILS_MASTER_KEY_FILE})`).option("--dry-run", "decrypt and show mapped key names without writing values").option("--remove-files", "delete credentials/master key files after every mapped value imports successfully").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((o) => withSecrets(async (d) => {
36273
36447
  const stages = ["dev", "rc", "main"];
36274
36448
  if (!stages.includes(o.stage)) {
@@ -36699,6 +36873,28 @@ function registerStageCommands(program3) {
36699
36873
  cfg
36700
36874
  };
36701
36875
  }
36876
+ async function stagePreview(project2) {
36877
+ if (project2?.deployModel !== "vercel") return null;
36878
+ const branch = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]);
36879
+ const repo = Array.isArray(project2.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : await repoSlug();
36880
+ try {
36881
+ const gh = defaultGitHubClient();
36882
+ const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo, { ref: branch }, "Preview");
36883
+ return { branch, ...deployment ? { deployment } : {} };
36884
+ } catch (e) {
36885
+ return { branch, error: e.message };
36886
+ }
36887
+ }
36888
+ function previewLine(preview) {
36889
+ if (!preview) return "";
36890
+ if (preview.error) return `
36891
+ vercel preview (${preview.branch}): read failed \u2014 ${preview.error}`;
36892
+ const d = preview.deployment;
36893
+ if (!d) return `
36894
+ vercel preview (${preview.branch}): no Preview deployment posted yet \u2014 push the branch, or connect the Vercel GitHub app`;
36895
+ return `
36896
+ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}`;
36897
+ }
36702
36898
  async function fetchStageVaultEnvMerge(project2, cfg) {
36703
36899
  if (!cfg.sagaApiUrl || !project2) return void 0;
36704
36900
  const names = requiredRuntimeSecretNames("dev", project2.requiredRuntimeSecrets, { includeGoogleOAuth: false });
@@ -36839,6 +37035,7 @@ function registerStageCommands(program3) {
36839
37035
  if (o.down && !o.live) return fail("stage: --down applies to --live only (local teardown is `mmi-cli stage stop --apply`)");
36840
37036
  if (o.live) return runStageLiveCommand(o);
36841
37037
  const { resolution: res, project: project2, cfg: stageCfg } = await resolveStage();
37038
+ const preview = await stagePreview(project2);
36842
37039
  if (o.apply) {
36843
37040
  if (res.source === "none") return failGraceful(`stage: ${res.gap}`);
36844
37041
  const cfg = res.derived.config;
@@ -36848,7 +37045,7 @@ function registerStageCommands(program3) {
36848
37045
  const result = await runStage(cfg, { ...stageScopedRunOpts({ timeoutMs: o.timeoutMs }), ...vaultOpts });
36849
37046
  const reportUrl = reportedStageUrl(res, result);
36850
37047
  const url = reportUrl ? ` \u2014 ${reportUrl}` : "";
36851
- return printLine(o.json ? JSON.stringify({ ...result, source: res.source, url: reportUrl }) : `mmi-cli stage: ${result.message}${url}`);
37048
+ return printLine(o.json ? JSON.stringify({ ...result, source: res.source, url: reportUrl, ...preview ? { preview } : {} }) : `mmi-cli stage: ${result.message}${url}${previewLine(preview)}`);
36852
37049
  } catch (e) {
36853
37050
  return failGraceful(`stage: ${e.message}`);
36854
37051
  } finally {
@@ -36856,8 +37053,8 @@ function registerStageCommands(program3) {
36856
37053
  }
36857
37054
  }
36858
37055
  const steps = stageStepsFor(res);
36859
- if (o.json) return console.log(JSON.stringify({ command: "stage", ...stageReceiptFields(res), steps }, null, 2));
36860
- console.log(renderSteps("mmi-cli stage: dry-run plan", steps));
37056
+ if (o.json) return console.log(JSON.stringify({ command: "stage", ...stageReceiptFields(res), steps, ...preview ? { preview } : {} }, null, 2));
37057
+ console.log(renderSteps("mmi-cli stage: dry-run plan", steps) + previewLine(preview));
36861
37058
  });
36862
37059
  stage.command("stop").description("stop the previous local stage process recorded in tmp/stage/state.json").option("--json", "machine-readable output").option("--apply", "kill the recorded process tree and remove the state file").action(async () => {
36863
37060
  const o = { json: rawFlag("--json"), apply: rawFlag("--apply") };
@@ -45464,17 +45661,17 @@ function excessArgumentCorrection(extra, argv) {
45464
45661
  return sibling ? correctedPositionalCommand(sibling, extra[0], argv) : void 0;
45465
45662
  }
45466
45663
  var pendingHumanParseError;
45467
- function writeHumanParseError(str) {
45468
- pendingHumanParseError = str;
45664
+ function writeHumanParseError(str2) {
45665
+ pendingHumanParseError = str2;
45469
45666
  }
45470
- function envelopeAwareWriteErr(str) {
45471
- const plain = str.replace(/\[[0-9;]*m/g, "");
45667
+ function envelopeAwareWriteErr(str2) {
45668
+ const plain = str2.replace(/\[[0-9;]*m/g, "");
45472
45669
  if (plain.includes(PARSE_HINT_SENTINEL)) {
45473
45670
  if (unknownFlagJsonHandled && argvWantsJson3()) {
45474
45671
  pendingHumanParseError = void 0;
45475
45672
  return;
45476
45673
  }
45477
- const hint = str.replace(PARSE_HINT_SENTINEL, resolveParseHint());
45674
+ const hint = str2.replace(PARSE_HINT_SENTINEL, resolveParseHint());
45478
45675
  if (lastParseErrorKind === "bad-arguments") {
45479
45676
  process.stderr.write(hint);
45480
45677
  if (pendingHumanParseError) process.stderr.write(pendingHumanParseError);
@@ -45490,7 +45687,7 @@ function envelopeAwareWriteErr(str) {
45490
45687
  const badChoice = parseInvalidChoiceError(plain);
45491
45688
  if (badChoice) {
45492
45689
  if (!argvWantsJson3()) {
45493
- writeHumanParseError(str);
45690
+ writeHumanParseError(str2);
45494
45691
  return;
45495
45692
  }
45496
45693
  process.stderr.write(
@@ -45518,7 +45715,7 @@ function envelopeAwareWriteErr(str) {
45518
45715
  unknownFlagJsonHandled = true;
45519
45716
  return;
45520
45717
  }
45521
- writeHumanParseError(str.replace(/error: too many arguments[^\n]*/, `error: ${message2}`));
45718
+ writeHumanParseError(str2.replace(/error: too many arguments[^\n]*/, `error: ${message2}`));
45522
45719
  return;
45523
45720
  }
45524
45721
  const match = /unknown option '([^']+)'/.exec(plain);
@@ -45544,18 +45741,18 @@ function envelopeAwareWriteErr(str) {
45544
45741
  return;
45545
45742
  }
45546
45743
  if (positional) {
45547
- writeHumanParseError(str.replace(/unknown option '[^']+'/, unknownTargetFlagMessage(flag, positional)));
45744
+ writeHumanParseError(str2.replace(/unknown option '[^']+'/, unknownTargetFlagMessage(flag, positional)));
45548
45745
  return;
45549
45746
  }
45550
- writeHumanParseError(str);
45747
+ writeHumanParseError(str2);
45551
45748
  return;
45552
45749
  }
45553
45750
  if (unknownFlagJsonHandled && argvWantsJson3()) return;
45554
45751
  if (argvWantsJson3()) {
45555
- process.stderr.write(str);
45752
+ process.stderr.write(str2);
45556
45753
  return;
45557
45754
  }
45558
- writeHumanParseError(str);
45755
+ writeHumanParseError(str2);
45559
45756
  }
45560
45757
  var INVOKED_ARGV3 = process.argv.slice(2);
45561
45758
  var program2 = new Command();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.67",
3
+ "version": "4.3.68",
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",