@mutmutco/cli 3.137.0 → 3.138.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 +124 -44
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -14487,10 +14487,10 @@ var rollout_plan_default = {
14487
14487
  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)."
14488
14488
  },
14489
14489
  baseline: {
14490
- version: "3.137.0",
14491
- tag: "v3.137.0",
14492
- commit: "48d8161cd121",
14493
- npm: "@mutmutco/cli@3.137.0"
14490
+ version: "3.138.0",
14491
+ tag: "v3.138.0",
14492
+ commit: "8735687f4693",
14493
+ npm: "@mutmutco/cli@3.138.0"
14494
14494
  },
14495
14495
  exitCriterion: "fleet-n-of-n",
14496
14496
  hubOnlyShortcut: "forbidden",
@@ -14507,14 +14507,14 @@ var rollout_plan_default = {
14507
14507
  repo: "mutmutco/mmi-hub",
14508
14508
  role: "canary",
14509
14509
  schedule: "train",
14510
- v3Target: "v3.137.0"
14510
+ v3Target: "v3.138.0"
14511
14511
  }
14512
14512
  ],
14513
14513
  rollbackTrigger: "Any red inside the post-cut soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a v3 client refused while the compat window must still admit it (SUPPORTED_MINOR_WINDOW=2, MIN_CLIENT_VERSION 0.0.0 \u2014 D6a), or npm consumer install/doctor failure on the v4 dist.",
14514
14514
  rollback: {
14515
14515
  independent: true,
14516
- mechanism: "npm dist-tag latest -> 3.137.0 and redeploy the Hub Lambda from tag v3.137.0 (48d8161cd121); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
14517
- v3Target: "v3.137.0 (@mutmutco/cli@3.137.0, tag commit 48d8161cd121 \u2014 the preserved latest-v3 distribution, D6b)"
14516
+ mechanism: "npm dist-tag latest -> 3.138.0 and redeploy the Hub Lambda from tag v3.138.0 (8735687f4693); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
14517
+ v3Target: "v3.138.0 (@mutmutco/cli@3.138.0, tag commit 8735687f4693 \u2014 the preserved latest-v3 distribution, D6b)"
14518
14518
  }
14519
14519
  },
14520
14520
  {
@@ -29012,6 +29012,11 @@ var ENVIRONMENT_BRANCHES = {
29012
29012
  rc: "rc",
29013
29013
  main: "main"
29014
29014
  };
29015
+ function classifyEnvironmentDrift(drift) {
29016
+ if (drift.missing) return { environment: drift.environment, action: "CREATE", reason: "environment is missing" };
29017
+ const why = [...drift.problems, ...drift.notes].join("; ");
29018
+ return why ? { environment: drift.environment, action: "UPDATE", reason: why } : { environment: drift.environment, action: "SKIP", reason: "already current" };
29019
+ }
29015
29020
  var TRACK_ENVIRONMENT_EXCEPTIONS = {
29016
29021
  direct: { rc: "direct release track has no rc stage" },
29017
29022
  trunk: {
@@ -29050,9 +29055,9 @@ function reviewerKey(reviewer) {
29050
29055
  return `${reviewer.type ?? ""}:${reviewer.id ?? ""}`;
29051
29056
  }
29052
29057
  function environmentDrift(live, desired) {
29053
- if (!live) return ["environment is missing"];
29058
+ if (!live) return { environment: desired.name, missing: true, problems: ["environment is missing"], notes: [] };
29054
29059
  const problems = [];
29055
- if (live.wait_timer !== desired.waitTimer) problems.push(`wait_timer=${live.wait_timer ?? "unset"} (want ${desired.waitTimer})`);
29060
+ const notes = [];
29056
29061
  const policy = live.deployment_branch_policy;
29057
29062
  if (policy?.protected_branches !== desired.deploymentBranchPolicy.protectedBranches) {
29058
29063
  problems.push(`protected_branches=${String(policy?.protected_branches)} (want true)`);
@@ -29060,16 +29065,67 @@ function environmentDrift(live, desired) {
29060
29065
  if (policy?.custom_branch_policies !== desired.deploymentBranchPolicy.customBranchPolicies) {
29061
29066
  problems.push(`custom_branch_policies=${String(policy?.custom_branch_policies)} (want false)`);
29062
29067
  }
29068
+ if (live.wait_timer !== desired.waitTimer) notes.push(`wait_timer=${live.wait_timer ?? "unset"} (want ${desired.waitTimer})`);
29063
29069
  const reviewers = (live.reviewers ?? []).map(reviewerKey).sort();
29064
- if (reviewers.length !== 0) problems.push(`reviewers=${reviewers.join(", ")} (want none)`);
29065
- return problems;
29070
+ if (reviewers.length !== 0) notes.push(`reviewers=${reviewers.join(", ")} (want none)`);
29071
+ return { environment: desired.name, missing: false, problems, notes };
29072
+ }
29073
+ function environmentBody(spec) {
29074
+ return {
29075
+ wait_timer: spec.waitTimer,
29076
+ reviewers: spec.reviewers,
29077
+ deployment_branch_policy: {
29078
+ protected_branches: spec.deploymentBranchPolicy.protectedBranches,
29079
+ custom_branch_policies: spec.deploymentBranchPolicy.customBranchPolicies
29080
+ }
29081
+ };
29082
+ }
29083
+ var PLAN_GATED_PROTECTION_RE = /billing plan/i;
29084
+ function isPlanGatedProtectionError(error) {
29085
+ return PLAN_GATED_PROTECTION_RE.test(String(error?.message ?? error));
29086
+ }
29087
+ async function reconcileRepoEnvironments(repo, releaseTrack, client) {
29088
+ const raw = await client.rest("GET", `repos/${repo}/environments`);
29089
+ const live = new Map(asEnvironmentList(raw).flatMap((entry) => entry.name ? [[entry.name, entry]] : []));
29090
+ const planned = provisionedEnvironmentSpecs(repo, releaseTrack);
29091
+ const created = [];
29092
+ const updated = [];
29093
+ const skipped = [];
29094
+ const planGated = [];
29095
+ for (const spec of planned) {
29096
+ const current = live.get(spec.name);
29097
+ const drift = environmentDrift(current, spec);
29098
+ if (drift.problems.length === 0 && drift.notes.length === 0) {
29099
+ skipped.push(spec.name);
29100
+ continue;
29101
+ }
29102
+ const path2 = `repos/${repo}/environments/${encodeURIComponent(spec.name)}`;
29103
+ let gatedProtections = [];
29104
+ try {
29105
+ await client.rest("PUT", path2, { body: environmentBody(spec) });
29106
+ } catch (error) {
29107
+ if (!isPlanGatedProtectionError(error)) throw error;
29108
+ await client.rest("PUT", path2, {
29109
+ body: {
29110
+ deployment_branch_policy: {
29111
+ protected_branches: spec.deploymentBranchPolicy.protectedBranches,
29112
+ custom_branch_policies: spec.deploymentBranchPolicy.customBranchPolicies
29113
+ }
29114
+ }
29115
+ });
29116
+ gatedProtections = ["wait_timer", "reviewers"];
29117
+ }
29118
+ if (gatedProtections.length) planGated.push({ environment: spec.name, protections: gatedProtections });
29119
+ (current ? updated : created).push(spec.name);
29120
+ }
29121
+ return { repo, planned, created, updated, skipped, planGated };
29066
29122
  }
29067
29123
  async function verifyRepoEnvironments(repo, releaseTrack, client) {
29068
29124
  const raw = await client.rest("GET", `repos/${repo}/environments`);
29069
29125
  const live = new Map(asEnvironmentList(raw).flatMap((entry) => entry.name ? [[entry.name, entry]] : []));
29070
29126
  return provisionedEnvironmentSpecs(repo, releaseTrack).map((spec) => {
29071
- const problems = environmentDrift(live.get(spec.name), spec);
29072
- return { environment: spec.name, missing: !live.has(spec.name), problems };
29127
+ const drift = environmentDrift(live.get(spec.name), spec);
29128
+ return { environment: spec.name, missing: !live.has(spec.name), problems: drift.problems, notes: drift.notes };
29073
29129
  });
29074
29130
  }
29075
29131
 
@@ -29320,7 +29376,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
29320
29376
  checks.push({
29321
29377
  ok: drift.problems.length === 0,
29322
29378
  label: `repository environment protected: ${drift.environment}`,
29323
- detail: drift.problems.length ? drift.problems.join("; ") : void 0
29379
+ detail: drift.problems.length ? drift.problems.join("; ") : drift.notes.length ? `advisory: ${drift.notes.join("; ")}` : void 0
29324
29380
  });
29325
29381
  }
29326
29382
  for (const spec of repoEnvironmentSpecs(repo, releaseTrack ?? (repoClass === "content" ? "trunk" : "full")).filter((entry) => entry.exception)) {
@@ -29767,7 +29823,7 @@ function registerBootstrapCommands(program3) {
29767
29823
  }
29768
29824
  if (findings.some((f) => f.state !== "waived")) process.exitCode = 1;
29769
29825
  });
29770
- bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: idempotent seed apply from skills/bootstrap/seeds/manifest.json; dry-run unless --execute (live, master-gated)").addOption(new Option("--class <class>", "deployable | content").default("deployable").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)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--execute", "LIVE apply via gh (master-gated) \u2014 stamps seed files + labels into the repo").option("--only <target>", "deliver ONLY this manifest target (#3818) \u2014 one file, no labels/ruleset/registry writes").option("--var <KEY=VALUE...>", "placeholder values for repo-owned templates (repeatable)").option("--json", "machine-readable output").action(async (repo, cmdOpts) => {
29826
+ bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: idempotent seed apply from skills/bootstrap/seeds/manifest.json; dry-run unless --execute (live, master-gated)").addOption(new Option("--class <class>", "deployable | content").default("deployable").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)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--execute", "LIVE apply via gh (master-gated) \u2014 stamps seed files + labels into the repo").option("--only <target>", "deliver ONLY this manifest target (#3818) \u2014 one file, no labels/ruleset/registry writes; or the special target environments (#5035) to run the deployment-environment reconcile alone (dry-run classifies, --execute writes)").option("--var <KEY=VALUE...>", "placeholder values for repo-owned templates (repeatable)").option("--json", "machine-readable output").action(async (repo, cmdOpts) => {
29771
29827
  const o = {
29772
29828
  class: rawValue("--class", "deployable"),
29773
29829
  projectType: rawValue("--project-type", ""),
@@ -29788,6 +29844,45 @@ function registerBootstrapCommands(program3) {
29788
29844
  } catch (e) {
29789
29845
  return fail(`bootstrap apply: ${e.message}`);
29790
29846
  }
29847
+ let effectiveTrack = bootstrapReleaseTrack;
29848
+ if (!o.releaseTrack) {
29849
+ try {
29850
+ const meta = await fetchProjectBySlug(parsedRepo.slug, registryClientDeps(await loadConfig()));
29851
+ if (meta?.releaseTrack && isReleaseTrack(meta.releaseTrack)) effectiveTrack = meta.releaseTrack;
29852
+ } catch {
29853
+ }
29854
+ }
29855
+ if (o.only.trim() === "environments") {
29856
+ const client = defaultGitHubClient();
29857
+ const envJson = { repo, track: effectiveTrack };
29858
+ if (!o.execute) {
29859
+ const drift = await verifyRepoEnvironments(repo, effectiveTrack, client);
29860
+ const classified = drift.map(classifyEnvironmentDrift);
29861
+ envJson.plan = classified;
29862
+ if (o.json) console.log(JSON.stringify(envJson, null, 2));
29863
+ else {
29864
+ console.log(`bootstrap apply --only environments \u2014 dry-run (${repo}, track ${effectiveTrack}); no writes:`);
29865
+ for (const c of classified) console.log(` ${c.action} ${c.environment} \u2014 ${c.reason}`);
29866
+ console.log(" (plan-gated protections \u2014 wait_timer/reviewers the org plan rejects \u2014 are reported at --execute)");
29867
+ }
29868
+ } else {
29869
+ const envResult = await reconcileRepoEnvironments(repo, effectiveTrack, client);
29870
+ const lines = [];
29871
+ if (envResult.created.length) lines.push(`environments created: ${envResult.created.join(", ")}`);
29872
+ if (envResult.updated.length) lines.push(`environments updated: ${envResult.updated.join(", ")}`);
29873
+ if (envResult.skipped.length) lines.push(`environments current: ${envResult.skipped.join(", ")}`);
29874
+ for (const gated of envResult.planGated) {
29875
+ lines.push(`environment ${gated.environment}: plan-gated protections (${gated.protections.join(", ")} not settable on this org plan \u2014 policy-only provisioned)`);
29876
+ }
29877
+ envJson.result = { created: envResult.created, updated: envResult.updated, skipped: envResult.skipped, planGated: envResult.planGated };
29878
+ if (o.json) console.log(JSON.stringify(envJson, null, 2));
29879
+ else {
29880
+ console.log(`LIVE environments apply to ${repo}:`);
29881
+ lines.forEach((l) => console.log(` ${l}`));
29882
+ }
29883
+ }
29884
+ return;
29885
+ }
29791
29886
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
29792
29887
  if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
29793
29888
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
@@ -30037,6 +30132,19 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
30037
30132
  }
30038
30133
  }
30039
30134
  }
30135
+ if (o.execute && !onlyTarget) {
30136
+ try {
30137
+ const envResult = await reconcileRepoEnvironments(repo, effectiveTrack, defaultGitHubClient());
30138
+ if (envResult.created.length) applied.push(`environments created: ${envResult.created.join(", ")}`);
30139
+ if (envResult.updated.length) applied.push(`environments updated: ${envResult.updated.join(", ")}`);
30140
+ if (envResult.skipped.length) applied.push(`environments current: ${envResult.skipped.join(", ")}`);
30141
+ for (const gated of envResult.planGated) {
30142
+ applied.push(`environment ${gated.environment}: plan-gated protections (${gated.protections.join(", ")} not settable on this org plan \u2014 policy-only provisioned)`);
30143
+ }
30144
+ } catch (e) {
30145
+ applied.push(`environments (failed: ${e.message})`);
30146
+ }
30147
+ }
30040
30148
  if (o.execute && !onlyTarget) {
30041
30149
  for (const l of manifest.labels) {
30042
30150
  try {
@@ -36730,34 +36838,6 @@ var surfaces_default = {
36730
36838
  },
36731
36839
  publishVisibility: "public"
36732
36840
  },
36733
- {
36734
- id: "mmi-updater-compat",
36735
- classification: "packaging",
36736
- kind: "cli",
36737
- ownerPath: "packages/updater-compat/package.json",
36738
- deliveryPath: "packages/updater-compat/package.json",
36739
- delivery: "npm",
36740
- applicability: "one-window deprecated @mutmutco/updater / mmi-updater wrapper delegating to the exact coordinated @mutmutco/hub version",
36741
- versionCoordinated: true,
36742
- versionPaths: [
36743
- {
36744
- path: "packages/updater-compat/package.json",
36745
- pointer: "version"
36746
- },
36747
- {
36748
- path: "packages/updater-compat/package.json",
36749
- pointer: "dependencies.@mutmutco/hub"
36750
- }
36751
- ],
36752
- additionalPaths: [
36753
- "packages/updater-compat/dist"
36754
- ],
36755
- artifactIdentity: {
36756
- kind: "npm-pack",
36757
- packagePath: "packages/updater-compat"
36758
- },
36759
- publishVisibility: "public"
36760
- },
36761
36841
  {
36762
36842
  id: "mmi-cli-lock",
36763
36843
  classification: "packaging",
@@ -39519,7 +39599,7 @@ withExamples(mutating(
39519
39599
  return fail("worktree create: --claim/--for need an issue-ref argument (e.g. worktree create 2687 --claim)");
39520
39600
  }
39521
39601
  const repoRoot2 = await primaryCheckoutRoot(process.cwd()) ?? process.cwd();
39522
- const wtPath = o.path ?? defaultWorktreePath(repoRoot2, branch);
39602
+ const wtPath = o.path ? (0, import_node_path46.resolve)(o.path) : defaultWorktreePath(repoRoot2, branch);
39523
39603
  const { base: fallbackBase, fetchBranch, preferRemote } = resolveWorktreeBase(fromRef, o.remote);
39524
39604
  let base = fallbackBase;
39525
39605
  step = `fetch the base ref ${fromRef}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.137.0",
3
+ "version": "3.138.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",