@mutmutco/cli 4.3.68 → 4.4.0

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 +45 -47
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -6380,7 +6380,7 @@ function decorateAwsModel(entries, registry2, verdicts) {
6380
6380
  });
6381
6381
  }
6382
6382
  var DECLARED_ENTRIES = [
6383
- { name: "mmi-backup (mm-central, mmi-fofu, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli devops runtime box get <box> --ssh)" },
6383
+ { name: "mmi-backup (mm-central, mm-work, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli devops runtime box get <box> --ssh)" },
6384
6384
  // #3370/#6270: the one runner hygiene layer (runner-hygiene.yml was folded in and deleted). A workflow ran ON the runner and needed checkout +
6385
6385
  // setup-node before it can prune, so at 100% it dies at checkout and never prunes — the cleanup is
6386
6386
  // unavailable exactly when it is needed. This one is root cron on the box: no checkout, no node, and
@@ -9439,6 +9439,18 @@ function resolveUseEnvNames(keys, name) {
9439
9439
  }
9440
9440
  return { ok: true, entries };
9441
9441
  }
9442
+ function referencesDollarEnv(arg, name) {
9443
+ return arg.includes(`\${${name}}`) || new RegExp(`\\$${name}(?![A-Za-z0-9_])`).test(arg);
9444
+ }
9445
+ function secretsUseDollarHint(command, envNames) {
9446
+ for (const arg of command) {
9447
+ for (const name of envNames) {
9448
+ if (!referencesDollarEnv(arg, name)) continue;
9449
+ return `secrets use: $${name} is injected into the CHILD's env, not your shell \u2014 the caller expanded it (empty) before this ran. Wrap the consumer so expansion happens inside: bash -c 'your-command "$${name}"'`;
9450
+ }
9451
+ }
9452
+ return null;
9453
+ }
9442
9454
  var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
9443
9455
  async function drainHttpPoolBeforeSpawn() {
9444
9456
  const drained = await Promise.race([
@@ -9490,6 +9502,8 @@ async function secretsUse(deps, keys, opts) {
9490
9502
  deps.err(named.error);
9491
9503
  return false;
9492
9504
  }
9505
+ const dollarHint = secretsUseDollarHint(command, named.entries.map((e) => e.envName));
9506
+ if (dollarHint) deps.err(dollarHint);
9493
9507
  const repo = await targetRepo(deps, opts);
9494
9508
  const slug = opts.slug?.toLowerCase();
9495
9509
  const env = { ...process.env };
@@ -14238,9 +14252,6 @@ function expectedHosts(cfg) {
14238
14252
  for (const env of ENV_PREFIXES) out.push(env ? `${env}.${base}` : base);
14239
14253
  }
14240
14254
  }
14241
- if (cfg.fofuSubdomain !== void 0) {
14242
- out.push(cfg.fofuSubdomain ? `${cfg.fofuSubdomain}.fofu.ai` : "fofu.ai");
14243
- }
14244
14255
  return uniq([...out, ...cfg.edgeHosts ?? []]);
14245
14256
  }
14246
14257
  function expectedJsOrigins(cfg) {
@@ -14305,10 +14316,7 @@ function parseOauthConfig(mmiConfig, slug) {
14305
14316
  );
14306
14317
  }
14307
14318
  const extraCallbackPaths = parseExtraCallbackPaths(raw.extraCallbackPaths);
14308
- const meta = mmiConfig ?? {};
14309
- const rawFofuSub = raw.fofuSubdomain;
14310
- const fofuSubdomain = meta.fofuEnabled === true ? typeof rawFofuSub === "string" ? rawFofuSub : defaultSubdomain(slug) : void 0;
14311
- return { subdomains, domains, callbackPath, extraCallbackPaths, fofuSubdomain, edgeHosts: Object.values(edgeDomainsByStage(mmiConfig)) };
14319
+ return { subdomains, domains, callbackPath, extraCallbackPaths, edgeHosts: Object.values(edgeDomainsByStage(mmiConfig)) };
14312
14320
  }
14313
14321
  function probeRedirectUri(callbackPath, port = 9123) {
14314
14322
  return `http://localhost:${port}${callbackPath}`;
@@ -15862,10 +15870,10 @@ var rollout_plan_default = {
15862
15870
  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)."
15863
15871
  },
15864
15872
  baseline: {
15865
- version: "4.3.68",
15866
- tag: "v4.3.68",
15867
- commit: "e9edc6e353ec",
15868
- npm: "@mutmutco/cli@4.3.68"
15873
+ version: "4.4.0",
15874
+ tag: "v4.4.0",
15875
+ commit: "8e1fae5ff484",
15876
+ npm: "@mutmutco/cli@4.4.0"
15869
15877
  },
15870
15878
  exitCriterion: "fleet-n-of-n",
15871
15879
  hubOnlyShortcut: "forbidden",
@@ -15882,14 +15890,14 @@ var rollout_plan_default = {
15882
15890
  repo: "mutmutco/mmi-hub",
15883
15891
  role: "canary",
15884
15892
  schedule: "train",
15885
- v3Target: "v4.3.68"
15893
+ v3Target: "v4.4.0"
15886
15894
  }
15887
15895
  ],
15888
15896
  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.",
15889
15897
  rollback: {
15890
15898
  independent: true,
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)"
15899
+ mechanism: "npm dist-tag latest -> 4.4.0 and redeploy the Hub Lambda from tag v4.4.0 (8e1fae5ff484); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15900
+ v3Target: "v4.4.0 (@mutmutco/cli@4.4.0, tag commit 8e1fae5ff484 \u2014 last known-good release carrying the repo-index v4-only contract)"
15893
15901
  }
15894
15902
  },
15895
15903
  {
@@ -15986,12 +15994,6 @@ var rollout_plan_default = {
15986
15994
  schedule: "train",
15987
15995
  v3Target: "v0.3.0"
15988
15996
  },
15989
- {
15990
- repo: "mutmutco/mm-fofu",
15991
- role: "member",
15992
- schedule: "train",
15993
- v3Target: "v0.1.0"
15994
- },
15995
15997
  {
15996
15998
  repo: "mutmutco/mmi-junior",
15997
15999
  role: "member",
@@ -29667,12 +29669,12 @@ function registerDeployCommands(program3) {
29667
29669
  init_cli_shared();
29668
29670
 
29669
29671
  // src/project-set.ts
29670
- var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary"];
29672
+ var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary"];
29671
29673
  var UNSET_KEY_SET = new Set(UNSET_KEYS);
29672
29674
  var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
29673
29675
  var SECRET_CONSUMERS = ["runtime", "build", "lambda", "actions", "agent", "box"];
29674
29676
  var SECRET_ENV_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
29675
- var BUILD_SECRET_REF_RE = /^(?:[A-Z_][A-Z0-9_]*=)?(?:[A-Z_][A-Z0-9_]*|(?:mm-fofu|_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*|@github-packages-token)$/;
29677
+ var BUILD_SECRET_REF_RE = /^(?:[A-Z_][A-Z0-9_]*=)?(?:[A-Z_][A-Z0-9_]*|(?:_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*|@github-packages-token)$/;
29676
29678
  function previewRegistryMetaMerge(existing, patch) {
29677
29679
  const out = { ...existing ?? {} };
29678
29680
  for (const [key, value] of Object.entries(patch)) {
@@ -29868,7 +29870,7 @@ function parseOauthVar(raw) {
29868
29870
  throw new Error(`org project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}`);
29869
29871
  }
29870
29872
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
29871
- throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,extraCallbackPaths,fofuSubdomain} object");
29873
+ throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,extraCallbackPaths} object");
29872
29874
  }
29873
29875
  const map = parsed;
29874
29876
  const out = {};
@@ -29896,11 +29898,8 @@ function parseOauthVar(raw) {
29896
29898
  } catch (e) {
29897
29899
  throw new Error(`org project set: ${e.message}`);
29898
29900
  }
29899
- } else if (key === "fofuSubdomain") {
29900
- if (typeof value !== "string") throw new Error('org project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
29901
- out.fofuSubdomain = value.trim();
29902
29901
  } else {
29903
- throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/extraCallbackPaths/fofuSubdomain`);
29902
+ throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/extraCallbackPaths`);
29904
29903
  }
29905
29904
  }
29906
29905
  return out;
@@ -29922,11 +29921,6 @@ function parsePublishRequiredVar(raw) {
29922
29921
  if (raw === "false") return false;
29923
29922
  throw new Error("org project set: publishRequired must be true or false");
29924
29923
  }
29925
- function parseFofuEnabledVar(raw) {
29926
- if (raw === "true") return true;
29927
- if (raw === "false") return false;
29928
- throw new Error("org project set: fofuEnabled must be true or false");
29929
- }
29930
29924
  function parseRuntimeVaultOnlyVar(raw) {
29931
29925
  if (raw === "true") return true;
29932
29926
  if (raw === "false") return false;
@@ -30084,7 +30078,6 @@ var SETTABLE_VAR_KEYS = [
30084
30078
  "releaseChannel",
30085
30079
  "releaseLanguage",
30086
30080
  "dsManifestPath",
30087
- "fofuEnabled",
30088
30081
  "consumesDesignSystem",
30089
30082
  "runtimeVaultOnly",
30090
30083
  "requiredGcpApis",
@@ -30113,12 +30106,11 @@ var SETTABLE_VAR_HINTS = {
30113
30106
  releaseChannel: "Slack channel id, e.g. C0BE0BRGAT0",
30114
30107
  releaseLanguage: "2-letter language code for release notes, e.g. tr (default en)",
30115
30108
  dsManifestPath: "relative path to a package.json, e.g. web/package.json",
30116
- fofuEnabled: "true|false",
30117
30109
  consumesDesignSystem: '"fofu"',
30118
30110
  runtimeVaultOnly: "true|false",
30119
30111
  seedCanary: "true|false",
30120
30112
  repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
30121
- oauth: "JSON {subdomains,domains,callbackPath,extraCallbackPaths,fofuSubdomain}",
30113
+ oauth: "JSON {subdomains,domains,callbackPath,extraCallbackPaths}",
30122
30114
  requiredGcpApis: "comma-string",
30123
30115
  requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
30124
30116
  requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
@@ -30214,8 +30206,6 @@ function buildProjectSetPatch(input) {
30214
30206
  patch[key] = parseReposVar(raw);
30215
30207
  } else if (key === "publishRequired") {
30216
30208
  patch[key] = parsePublishRequiredVar(raw);
30217
- } else if (key === "fofuEnabled") {
30218
- patch[key] = parseFofuEnabledVar(raw);
30219
30209
  } else if (key === "runtimeVaultOnly") {
30220
30210
  patch[key] = parseRuntimeVaultOnlyVar(raw);
30221
30211
  } else if (key === "seedCanary") {
@@ -36752,7 +36742,7 @@ async function runStageLiveDown(deps, t) {
36752
36742
  var GITHUB_PACKAGES_TOKEN_REF = "@github-packages-token";
36753
36743
  var ENV_ID_RE = /^[A-Z_][A-Z0-9_]*$/;
36754
36744
  var BARE_VAULT_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
36755
- var CROSS_SLUG_REF_RE = /^(?:mm-fofu|_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*$/;
36745
+ var CROSS_SLUG_REF_RE = /^(?:_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*$/;
36756
36746
  function parseStageBuildSecretEntry(entry) {
36757
36747
  const trimmed = entry.trim();
36758
36748
  if (!trimmed) return null;
@@ -36824,7 +36814,6 @@ async function fetchVaultRef(fetchVault, ref) {
36824
36814
  if (colon === -1) return fetchVault(ref);
36825
36815
  const prefix = ref.slice(0, colon);
36826
36816
  const key = ref.slice(colon + 1);
36827
- if (prefix === "mm-fofu") return fetchVault(key, { slug: "mm-fofu" });
36828
36817
  if (prefix.startsWith("_org/")) {
36829
36818
  const provider = prefix.slice("_org/".length);
36830
36819
  return fetchVault(`${provider}/${key}`, { slug: "_org" });
@@ -42014,7 +42003,7 @@ function formatBoxTable(boxes) {
42014
42003
  }
42015
42004
  return lines2.join("\n");
42016
42005
  }
42017
- function formatSshRecipe(box) {
42006
+ function formatSshRecipe(box, remoteCommand) {
42018
42007
  if (!box.address) {
42019
42008
  return `box ${box.name} has no public IPv4 \u2014 it is not directly reachable over ssh.`;
42020
42009
  }
@@ -42028,13 +42017,21 @@ function formatSshRecipe(box) {
42028
42017
  " --with-decryption --query Parameter.Value --output text --region eu-central-1 \\",
42029
42018
  ` | tr -d '\\r' > "$kf"`,
42030
42019
  'test -s "$kf" # never hand ssh an empty key file',
42031
- `ssh -i "$kf" -o StrictHostKeyChecking=accept-new root@${box.address} 'hostname; uptime'`
42020
+ `ssh -i "$kf" -o StrictHostKeyChecking=accept-new root@${box.address} ${shellSingleQuote(sshRemoteCommand(remoteCommand))}`
42032
42021
  ].join("\n");
42033
42022
  }
42023
+ var SSH_DEFAULT_PROBE = "hostname; uptime";
42024
+ function sshRemoteCommand(remoteCommand) {
42025
+ const trimmed = remoteCommand?.trim();
42026
+ return trimmed ? trimmed : SSH_DEFAULT_PROBE;
42027
+ }
42028
+ function shellSingleQuote(value) {
42029
+ return `'${value.replace(/'/g, `'\\''`)}'`;
42030
+ }
42034
42031
  var SSH_RECIPE_AGENT_NOTE = "# agent callers: this is multi-line, so running it inline is denied (#1473/#2125).\n# Use `mmi-cli devops runtime box get <name> --ssh --script <path>` to write it to a file, then `bash <path>`.";
42035
- function sshRecipeScript(box) {
42032
+ function sshRecipeScript(box, remoteCommand) {
42036
42033
  return `#!/usr/bin/env bash
42037
- ${formatSshRecipe(box)}
42034
+ ${formatSshRecipe(box, remoteCommand)}
42038
42035
  `;
42039
42036
  }
42040
42037
 
@@ -42109,7 +42106,7 @@ function registerBoxCommands(program3) {
42109
42106
  await failGraceful(e.message);
42110
42107
  }
42111
42108
  });
42112
- box.command("get <name>").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (materializes the key to a temp file; never echoes it)").option("--script <path>", "with --ssh: write the recipe to <path> and print the one line to run \u2014 the runnable form for agent callers, whose transport denies a multi-line inline body (#3385)").action(async (name, o) => {
42109
+ box.command("get <name> [command...]").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (materializes the key to a temp file; never echoes it)").option("--script <path>", "with --ssh: write the recipe to <path> and print the one line to run \u2014 the runnable form for agent callers, whose transport denies a multi-line inline body (#3385)").action(async (name, command, o) => {
42113
42110
  try {
42114
42111
  const { boxes, incomplete } = await fetchInventory();
42115
42112
  const result = lookupBox(name, boxes, incomplete);
@@ -42129,8 +42126,9 @@ function registerBoxCommands(program3) {
42129
42126
  await failGraceful("runtime box get: --script requires --ssh (it writes the ssh connect recipe)");
42130
42127
  return;
42131
42128
  }
42129
+ const remote = command && command.length ? command.join(" ") : void 0;
42132
42130
  const wroteScript = o.ssh && o.script ? o.script : null;
42133
- if (wroteScript) (0, import_node_fs42.writeFileSync)(wroteScript, sshRecipeScript(found), "utf8");
42131
+ if (wroteScript) (0, import_node_fs42.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
42134
42132
  if (o.json) {
42135
42133
  console.log(JSON.stringify({
42136
42134
  box: found,
@@ -42138,7 +42136,7 @@ function registerBoxCommands(program3) {
42138
42136
  ...wroteScript ? { script: { path: wroteScript, run: `bash "${wroteScript}"` } } : {}
42139
42137
  }, null, 2));
42140
42138
  } else if (wroteScript) console.log(`wrote ${wroteScript} \u2014 run: bash "${wroteScript}"`);
42141
- else if (o.ssh) console.log(`${formatSshRecipe(found)}
42139
+ else if (o.ssh) console.log(`${formatSshRecipe(found, remote)}
42142
42140
  ${SSH_RECIPE_AGENT_NOTE}`);
42143
42141
  else console.log(formatBoxTable([found]));
42144
42142
  if (!o.json) warnIncomplete2(incomplete);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.68",
3
+ "version": "4.4.0",
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",