@mutmutco/cli 4.3.43 → 4.3.44

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 +117 -50
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -15710,10 +15710,10 @@ var rollout_plan_default = {
15710
15710
  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)."
15711
15711
  },
15712
15712
  baseline: {
15713
- version: "4.3.43",
15714
- tag: "v4.3.43",
15715
- commit: "120c58230cfc",
15716
- npm: "@mutmutco/cli@4.3.43"
15713
+ version: "4.3.44",
15714
+ tag: "v4.3.44",
15715
+ commit: "405d6e3283b9",
15716
+ npm: "@mutmutco/cli@4.3.44"
15717
15717
  },
15718
15718
  exitCriterion: "fleet-n-of-n",
15719
15719
  hubOnlyShortcut: "forbidden",
@@ -15730,14 +15730,14 @@ var rollout_plan_default = {
15730
15730
  repo: "mutmutco/mmi-hub",
15731
15731
  role: "canary",
15732
15732
  schedule: "train",
15733
- v3Target: "v4.3.43"
15733
+ v3Target: "v4.3.44"
15734
15734
  }
15735
15735
  ],
15736
15736
  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.",
15737
15737
  rollback: {
15738
15738
  independent: true,
15739
- mechanism: "npm dist-tag latest -> 4.3.43 and redeploy the Hub Lambda from tag v4.3.43 (120c58230cfc); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15740
- v3Target: "v4.3.43 (@mutmutco/cli@4.3.43, tag commit 120c58230cfc \u2014 last known-good release carrying the repo-index v4-only contract)"
15739
+ mechanism: "npm dist-tag latest -> 4.3.44 and redeploy the Hub Lambda from tag v4.3.44 (405d6e3283b9); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15740
+ v3Target: "v4.3.44 (@mutmutco/cli@4.3.44, tag commit 405d6e3283b9 \u2014 last known-good release carrying the repo-index v4-only contract)"
15741
15741
  }
15742
15742
  },
15743
15743
  {
@@ -29159,7 +29159,7 @@ function registerDeployCommands(program3) {
29159
29159
  }
29160
29160
 
29161
29161
  // src/project-set.ts
29162
- var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "dsManifestPath", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary"];
29162
+ var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary"];
29163
29163
  var UNSET_KEY_SET = new Set(UNSET_KEYS);
29164
29164
  var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
29165
29165
  var SECRET_CONSUMERS = ["runtime", "build", "lambda", "actions", "agent", "box"];
@@ -29443,6 +29443,20 @@ function parsePublishDirVar(raw) {
29443
29443
  }
29444
29444
  return v;
29445
29445
  }
29446
+ function parseReleaseChannelVar(raw) {
29447
+ const v = raw.trim();
29448
+ if (!/^[CG][A-Z0-9]{8,}$/.test(v)) {
29449
+ throw new Error("org project set: releaseChannel must be a Slack channel id (e.g. C0BE0BRGAT0); --unset releaseChannel disables release notes");
29450
+ }
29451
+ return v;
29452
+ }
29453
+ function parseReleaseLanguageVar(raw) {
29454
+ const v = raw.trim();
29455
+ if (!/^[a-z]{2}$/.test(v)) {
29456
+ throw new Error("org project set: releaseLanguage must be a 2-letter lowercase language code (e.g. tr); --unset releaseLanguage returns to en");
29457
+ }
29458
+ return v;
29459
+ }
29446
29460
  function parseDsManifestPathVar(raw) {
29447
29461
  const v = raw.trim();
29448
29462
  if (v === "" || !/^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/.test(v) || /(^|\/)\.\.(\/|$)/.test(v)) {
@@ -29559,6 +29573,8 @@ var SETTABLE_VAR_KEYS = [
29559
29573
  "oauth",
29560
29574
  "publishRequired",
29561
29575
  "publishDir",
29576
+ "releaseChannel",
29577
+ "releaseLanguage",
29562
29578
  "dsManifestPath",
29563
29579
  "fofuEnabled",
29564
29580
  "consumesDesignSystem",
@@ -29586,6 +29602,8 @@ var SETTABLE_VAR_HINTS = {
29586
29602
  projectNumber: "numeric",
29587
29603
  publishRequired: "true|false",
29588
29604
  publishDir: "relative subpath, e.g. packages/ui",
29605
+ releaseChannel: "Slack channel id, e.g. C0BE0BRGAT0",
29606
+ releaseLanguage: "2-letter language code for release notes, e.g. tr (default en)",
29589
29607
  dsManifestPath: "relative path to a package.json, e.g. web/package.json",
29590
29608
  fofuEnabled: "true|false",
29591
29609
  consumesDesignSystem: '"fofu"',
@@ -29698,6 +29716,10 @@ function buildProjectSetPatch(input) {
29698
29716
  patch[key] = parseConsumesDesignSystemVar(raw);
29699
29717
  } else if (key === "publishDir") {
29700
29718
  patch[key] = parsePublishDirVar(raw);
29719
+ } else if (key === "releaseChannel") {
29720
+ patch[key] = parseReleaseChannelVar(raw);
29721
+ } else if (key === "releaseLanguage") {
29722
+ patch[key] = parseReleaseLanguageVar(raw);
29701
29723
  } else if (key === "dsManifestPath") {
29702
29724
  patch[key] = parseDsManifestPathVar(raw);
29703
29725
  } else if (key === "ci") {
@@ -42398,7 +42420,6 @@ async function postToAlertsChannel(deps, text) {
42398
42420
 
42399
42421
  // src/release-announce.ts
42400
42422
  var ANNOUNCE_REPO = "mutmutco/MMI-Hub";
42401
- var RELEASE_CHANNEL_KEY = "SLACK_RELEASE_CHANNEL";
42402
42423
  var MAX_BULLETS = 6;
42403
42424
  var MAX_SUMMARY_LINES = 8;
42404
42425
  function neutralizeHubProductBrands(text) {
@@ -42406,13 +42427,6 @@ function neutralizeHubProductBrands(text) {
42406
42427
  s = s.replace(/\bdesign-system\s+design-system\b/gi, "design-system");
42407
42428
  return s.replace(/\s{2,}/g, " ").trim();
42408
42429
  }
42409
- function releaseChannelParam(repo) {
42410
- return `/mmi-future/${slugOf(repo)}/${RELEASE_CHANNEL_KEY}`;
42411
- }
42412
- function isAbsentParameter(e) {
42413
- const message2 = e instanceof Error ? e.message : String(e);
42414
- return /ParameterNotFound/.test(message2) || /is empty$/.test(message2);
42415
- }
42416
42430
  function escapeMrkdwn(text) {
42417
42431
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
42418
42432
  }
@@ -42467,14 +42481,20 @@ function curateReleaseNotes(notesMd, maxBullets = MAX_BULLETS, neutralize = true
42467
42481
  if (dropped > 0) lines2.push(`\u2026and ${dropped} more`);
42468
42482
  return lines2;
42469
42483
  }
42484
+ var ANNOUNCE_FRAMES = {
42485
+ en: { released: "released", notes: "Release notes" },
42486
+ tr: { released: "yay\u0131nda", notes: "S\xFCr\xFCm notlar\u0131" }
42487
+ };
42470
42488
  function formatAnnouncement(args) {
42471
42489
  const name = args.repo.split("/")[1] ?? args.repo;
42490
+ const code = args.language ?? "en";
42491
+ const frame = Object.hasOwn(ANNOUNCE_FRAMES, code) ? ANNOUNCE_FRAMES[code] : ANNOUNCE_FRAMES.en;
42472
42492
  return [
42473
- `:rocket: *${name} ${args.tag} released*`,
42493
+ `:rocket: *${name} ${args.tag} ${frame.released}*`,
42474
42494
  "",
42475
42495
  ...args.lines,
42476
42496
  "",
42477
- `<${args.releaseUrl}|Release notes>`
42497
+ `<${args.releaseUrl}|${frame.notes}>`
42478
42498
  ].join("\n");
42479
42499
  }
42480
42500
  function summaryFileLines(content, neutralize = true) {
@@ -42485,18 +42505,12 @@ function summaryFileLines(content, neutralize = true) {
42485
42505
  }
42486
42506
  async function resolveAnnounceTarget(deps, repo) {
42487
42507
  if (repo.toLowerCase() === ANNOUNCE_REPO.toLowerCase()) return { kind: "alerts" };
42488
- const param = releaseChannelParam(repo);
42489
- try {
42490
- const channel = await readSsmParameter(deps, param, false);
42491
- return { kind: "channel", channel };
42492
- } catch (e) {
42493
- if (isAbsentParameter(e)) return { kind: "none", param };
42494
- throw e;
42495
- }
42508
+ const target = deps.fetchReleaseTarget ? await deps.fetchReleaseTarget(slugOf(repo)) : { channel: null, language: "en" };
42509
+ return target.channel ? { kind: "channel", channel: target.channel, language: target.language } : { kind: "none" };
42496
42510
  }
42497
42511
  function missingChannelNote(repo) {
42498
42512
  const slug = slugOf(repo);
42499
- return `no ${RELEASE_CHANNEL_KEY} declared for ${slug} \u2014 declare it with \`mmi-cli vault secrets declare ${RELEASE_CHANNEL_KEY} --group slack --consumers agent --purpose \u2026\` then \`mmi-cli vault secrets set ${RELEASE_CHANNEL_KEY}\``;
42513
+ return `no releaseChannel configured for ${slug} \u2014 enable release notes with \`mmi-cli oracle org project set --var releaseChannel=<slack channel id> ${repo}\``;
42500
42514
  }
42501
42515
  async function announceRelease(deps, args) {
42502
42516
  const neutralize = args.repo.toLowerCase() === ANNOUNCE_REPO.toLowerCase();
@@ -42517,9 +42531,20 @@ async function announceRelease(deps, args) {
42517
42531
  }
42518
42532
  }
42519
42533
  }
42520
- if (lines2.length === 0) lines2 = curateReleaseNotes(view.body ?? "", MAX_BULLETS, neutralize);
42534
+ if (lines2.length === 0) {
42535
+ if (target.kind !== "alerts") {
42536
+ return { status: "skipped", note: "no --announce-summary-file given \u2014 raw release notes are never posted to a project channel" };
42537
+ }
42538
+ lines2 = curateReleaseNotes(view.body ?? "", MAX_BULLETS, neutralize);
42539
+ }
42521
42540
  if (lines2.length === 0) lines2 = ["\u2022 maintenance release \u2014 see the notes for details"];
42522
- const text = formatAnnouncement({ repo: args.repo, tag: args.tag, lines: lines2, releaseUrl });
42541
+ const text = formatAnnouncement({
42542
+ repo: args.repo,
42543
+ tag: args.tag,
42544
+ lines: lines2,
42545
+ releaseUrl,
42546
+ ...target.kind === "channel" ? { language: target.language } : {}
42547
+ });
42523
42548
  if (target.kind === "alerts") {
42524
42549
  await postToAlertsChannel(deps, text);
42525
42550
  return { status: "announced", note: `announced ${args.tag} to the alerts channel` };
@@ -43878,6 +43903,18 @@ var NODE_PREPARE_TIMEOUT_MS = 10 * 6e4;
43878
43903
  var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
43879
43904
  var NPM_TRAIN_TIMEOUT_MS = 6e4;
43880
43905
  var JERV_GATEWAY_DEPLOY_TIMEOUT_MS = 20 * 6e4;
43906
+ async function announceSummaryRefusal(path2) {
43907
+ let lines2;
43908
+ try {
43909
+ lines2 = summaryFileLines(await (0, import_promises10.readFile)(path2, "utf8"));
43910
+ } catch (e) {
43911
+ return `release: could not read --announce-summary-file ${path2}: ${e.message} \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`;
43912
+ }
43913
+ if (lines2.length < 3 || lines2.length > 6) {
43914
+ return `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`;
43915
+ }
43916
+ return null;
43917
+ }
43881
43918
  function trainApplyDeps() {
43882
43919
  return {
43883
43920
  run: async (file, args) => {
@@ -43940,12 +43977,25 @@ function trainApplyDeps() {
43940
43977
  // train lands the back-merge itself instead of leaving a manual step. Same enqueue path `pr land` uses,
43941
43978
  // but method `--merge` (NOT squash): the misalignment guard needs the merge parentage to survive.
43942
43979
  mergeAuto: (prNumber, repo) => ghMergeAutoEnqueue(prNumber, repo, "--merge"),
43943
- // Slack release announcement (#883/#6517): the org alerts channel for the Hub, the project's own
43944
- // SLACK_RELEASE_CHANNEL for a product repo — best-effort inside announceRelease itself.
43980
+ // Slack release announcement (#883/#6521/#6523): the org alerts channel for the Hub, the project's own
43981
+ // registry releaseChannel META for a product repo — best-effort inside announceRelease itself.
43945
43982
  announce: (args) => announceRelease({
43946
43983
  run: async (file, cmdArgs) => (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
43947
43984
  readFile: (path2) => (0, import_promises10.readFile)(path2, "utf8"),
43948
- removeFile: (path2) => (0, import_promises10.unlink)(path2)
43985
+ removeFile: (path2) => (0, import_promises10.unlink)(path2),
43986
+ // #6521/#6523: the project's own release channel AND releaseLanguage, read from registry META in ONE
43987
+ // checked read. Same shape as fetchEdgeDomains below: a FAILED read refuses by name so the announce
43988
+ // note reports a failed read, never a project that configured nothing.
43989
+ fetchReleaseTarget: async (slug) => {
43990
+ const read = await fetchProjectBySlugChecked(slug, registryClientDeps(await loadConfig()));
43991
+ if (!read.ok) {
43992
+ throw new Error(`release announcement: Hub registry read FAILED for ${slug} (${read.error}) \u2014 releaseChannel is UNREAD, not unset`);
43993
+ }
43994
+ return {
43995
+ channel: read.project?.releaseChannel?.trim() || null,
43996
+ language: read.project?.releaseLanguage?.trim() || "en"
43997
+ };
43998
+ }
43949
43999
  }, args),
43950
44000
  // #4713 (I/O-boundary census): `null` used to mean BOTH "this project configures no edge domains"
43951
44001
  // (a real answer) and "the registry read missed" — so a release verdict printed an environments block
@@ -44205,7 +44255,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44205
44255
  for (const commandName of ["rcand", "release"]) {
44206
44256
  const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "write the terminal result (--apply, --resume, --abort, --retry-publish) to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)");
44207
44257
  const RELEASE_ONLY_FLAGS = [
44208
- { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line release Slack summary; required for a new MMI-Hub --apply (#883/#3901), optional for a product repo announcing to its project release channel (#6517)" },
44258
+ { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line release Slack summary; required for a new MMI-Hub --apply (#883/#3901) and for a product repo whose registry META sets `releaseChannel` (#6523), in its `releaseLanguage`; nothing is posted to a project channel without it" },
44209
44259
  { flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
44210
44260
  { flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
44211
44261
  { flags: "--abort", description: "with --apply, delete only a proven unpublished failed release candidate on any registered deploy model and restore local main for a same-version recut (#3944/#5650/#5657)" },
@@ -44323,22 +44373,35 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44323
44373
  const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), rerun);
44324
44374
  if (!guard.ok) return fail(`${commandName}: ${guard.message}`);
44325
44375
  }
44326
- if (o.apply && commandName === "release" && (await resolveRepo())?.toLowerCase() === ANNOUNCE_REPO.toLowerCase()) {
44327
- if (!o.announceSummaryFile) {
44328
- return fail(
44329
- "release: a new MMI-Hub release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same release command \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing"
44330
- );
44331
- }
44332
- let summaryLines;
44333
- try {
44334
- summaryLines = summaryFileLines(await (0, import_promises10.readFile)(o.announceSummaryFile, "utf8"));
44335
- } catch (e) {
44336
- return fail(`release: could not read --announce-summary-file ${o.announceSummaryFile}: ${e.message} \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`);
44376
+ if (o.apply && commandName === "release") {
44377
+ const applyingRepo = await resolveRepo();
44378
+ const slug = applyingRepo ? slugOf(applyingRepo) : "";
44379
+ const isHub = applyingRepo?.toLowerCase() === ANNOUNCE_REPO.toLowerCase();
44380
+ let announcesToChannel = isHub;
44381
+ let releaseLanguage2 = "en";
44382
+ if (!isHub && slug) {
44383
+ let read;
44384
+ try {
44385
+ read = await fetchProjectBySlugChecked(slug, registryClientDeps(await loadConfig()));
44386
+ } catch (e) {
44387
+ read = { ok: false, error: e.message };
44388
+ }
44389
+ if (!read.ok) {
44390
+ return fail(
44391
+ `release: Hub registry read failed for ${slug} (${read.error}) \u2014 whether ${slug} announces to a project release channel is UNREAD, so the --announce-summary-file requirement is unproven. Likely transient (cold start, network, or auth blip); retry shortly.`
44392
+ );
44393
+ }
44394
+ announcesToChannel = Boolean(read.project?.releaseChannel?.trim());
44395
+ releaseLanguage2 = read.project?.releaseLanguage?.trim() || "en";
44337
44396
  }
44338
- if (summaryLines.length < 3 || summaryLines.length > 6) {
44339
- return fail(
44340
- `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${summaryLines.length}) \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`
44341
- );
44397
+ if (announcesToChannel) {
44398
+ if (!o.announceSummaryFile) {
44399
+ return fail(
44400
+ isHub ? "release: a new MMI-Hub release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same release command \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing" : `release: ${slug} announces to its project release channel, so a new release requires --announce-summary-file <path> with 3-6 short plain-language lines in the project's releaseLanguage (${releaseLanguage2}); create the summary file, then rerun the same release command \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`
44401
+ );
44402
+ }
44403
+ const refusal = await announceSummaryRefusal(o.announceSummaryFile);
44404
+ if (refusal) return fail(refusal);
44342
44405
  }
44343
44406
  }
44344
44407
  if (o.apply) {
@@ -44370,6 +44433,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44370
44433
  const repo = o.repo ?? await resolveRepo();
44371
44434
  const targets = commandName === "rcand" && !o.repo ? await resolveRcandPlanTargets() : void 0;
44372
44435
  let releaseTrack;
44436
+ let releaseChannel = null;
44437
+ let releaseLanguage = "en";
44373
44438
  if (repo) {
44374
44439
  let read;
44375
44440
  try {
@@ -44384,6 +44449,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44384
44449
  }
44385
44450
  const raw = read.project?.releaseTrack;
44386
44451
  if (isReleaseTrack(raw)) releaseTrack = raw;
44452
+ releaseChannel = read.project?.releaseChannel?.trim() || null;
44453
+ releaseLanguage = read.project?.releaseLanguage?.trim() || "en";
44387
44454
  }
44388
44455
  const steps = trainPlan(commandName, { ...targets ?? {}, repo, dev: o.dev, releaseTrack });
44389
44456
  let doctor;
@@ -44395,7 +44462,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44395
44462
  }
44396
44463
  }
44397
44464
  console.log(
44398
- o.json ? JSON.stringify({ command: commandName, ...targets ?? {}, repo, steps, ...doctor ? { doctor } : {} }, null, 2) : [renderSteps(`mmi-cli ${commandName}: dry-run plan`, steps), ...doctor ? ["", formatTrainDoctor(doctor)] : []].join("\n")
44465
+ o.json ? JSON.stringify({ command: commandName, ...targets ?? {}, repo, steps, ...commandName === "release" ? { releaseChannel, releaseLanguage } : {}, ...doctor ? { doctor } : {} }, null, 2) : [renderSteps(`mmi-cli ${commandName}: dry-run plan`, steps), ...doctor ? ["", formatTrainDoctor(doctor)] : []].join("\n")
44399
44466
  );
44400
44467
  });
44401
44468
  }
@@ -45315,7 +45382,7 @@ projectDeploy.command("doctor").description("read-only estate scan for duplicate
45315
45382
  if (report.error && report.status !== 403) return failGraceful(`org project deploy doctor: ${report.error}`);
45316
45383
  if (!report.ok && !report.error) process.exitCode = 1;
45317
45384
  });
45318
- project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").addOption(new Option("--class <class>", "deployable | content").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (clear all with --unset secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"|?], "provider":"<optional>"}').option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets, edgeDomains, requiredGcpApis, publishRequired").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
45385
+ project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").addOption(new Option("--class <class>", "deployable | content").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (clear all with --unset secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"|?], "provider":"<optional>"}').option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets, edgeDomains, requiredGcpApis, publishRequired, releaseChannel, releaseLanguage").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
45319
45386
  const cfg = await loadConfig();
45320
45387
  let target;
45321
45388
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.43",
3
+ "version": "4.3.44",
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",