@mutmutco/cli 4.5.78 → 4.5.79

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 +82 -22
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -14366,7 +14366,7 @@ var PRODUCT_PENDING_RULESET_PREFIX = "mmi-product-required-checks-pending-";
14366
14366
  var PRODUCT_GATE_CONTEXT = "gate";
14367
14367
  var PRODUCT_RULESET_PATH = ".github/rulesets/mmi-product-required-checks.json";
14368
14368
  function reseedProductRulesetStrictness(current, seed, declared = {}) {
14369
- const parse = (raw) => {
14369
+ const parse2 = (raw) => {
14370
14370
  const policy2 = JSON.parse(raw);
14371
14371
  if (!policy2 || typeof policy2 !== "object" || Array.isArray(policy2) || policy2.name !== PRODUCT_RULESET_NAME || policy2.target !== "branch" || !Array.isArray(policy2.rules) || policy2.rules.some((r) => !r || typeof r !== "object" || Array.isArray(r) || typeof r.type !== "string")) {
14372
14372
  throw new Error("product ruleset must be a branch ruleset object with well-formed rules");
@@ -14378,11 +14378,11 @@ function reseedProductRulesetStrictness(current, seed, declared = {}) {
14378
14378
  }
14379
14379
  return { policy: policy2, parameters: parameters2 };
14380
14380
  };
14381
- const canonical = parse(seed);
14381
+ const canonical = parse2(seed);
14382
14382
  const strict = canonical.parameters.strict_required_status_checks_policy;
14383
14383
  if (typeof strict !== "boolean") throw new Error("canonical product ruleset must declare boolean strictness");
14384
14384
  if (current === null) return seed;
14385
- const { policy, parameters } = parse(current);
14385
+ const { policy, parameters } = parse2(current);
14386
14386
  let changed = parameters.strict_required_status_checks_policy !== strict;
14387
14387
  parameters.strict_required_status_checks_policy = strict;
14388
14388
  if (declared.requiredChecks) {
@@ -17249,10 +17249,10 @@ var rollout_plan_default = {
17249
17249
  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)."
17250
17250
  },
17251
17251
  baseline: {
17252
- version: "4.5.78",
17253
- tag: "v4.5.78",
17254
- commit: "55782d939329",
17255
- npm: "@mutmutco/cli@4.5.78"
17252
+ version: "4.5.79",
17253
+ tag: "v4.5.79",
17254
+ commit: "542d3fde121b",
17255
+ npm: "@mutmutco/cli@4.5.79"
17256
17256
  },
17257
17257
  exitCriterion: "fleet-n-of-n",
17258
17258
  hubOnlyShortcut: "forbidden",
@@ -17269,14 +17269,14 @@ var rollout_plan_default = {
17269
17269
  repo: "mutmutco/mmi-hub",
17270
17270
  role: "canary",
17271
17271
  schedule: "train",
17272
- v3Target: "v4.5.78"
17272
+ v3Target: "v4.5.79"
17273
17273
  }
17274
17274
  ],
17275
17275
  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.",
17276
17276
  rollback: {
17277
17277
  independent: true,
17278
- mechanism: "npm dist-tag latest -> 4.5.78 and redeploy the Hub Lambda from tag v4.5.78 (55782d939329); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17279
- v3Target: "v4.5.78 (@mutmutco/cli@4.5.78, tag commit 55782d939329 \u2014 last known-good release carrying the repo-index v4-only contract)"
17278
+ mechanism: "npm dist-tag latest -> 4.5.79 and redeploy the Hub Lambda from tag v4.5.79 (542d3fde121b); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
17279
+ v3Target: "v4.5.79 (@mutmutco/cli@4.5.79, tag commit 542d3fde121b \u2014 last known-good release carrying the repo-index v4-only contract)"
17280
17280
  }
17281
17281
  },
17282
17282
  {
@@ -20071,12 +20071,12 @@ async function isStrayUnreleasedTag(deps, tag, remoteSha, repo) {
20071
20071
  return deps.run("gh", ["release", "view", tag, "--repo", repo, "--json", "tagName"]).then(() => false).catch((e) => /not found|HTTP 404/i.test(e instanceof Error ? e.message : String(e)));
20072
20072
  }
20073
20073
  function cmpReleaseTag(a, b) {
20074
- const parse = (t) => {
20074
+ const parse2 = (t) => {
20075
20075
  const m = /^v(\d+)\.(\d+)\.(\d+)$/.exec(t.trim());
20076
20076
  return m ? { major: +m[1], minor: +m[2], patch: +m[3] } : null;
20077
20077
  };
20078
- const left = parse(a);
20079
- const right = parse(b);
20078
+ const left = parse2(a);
20079
+ const right = parse2(b);
20080
20080
  if (!left || !right) throw new Error(`cmpReleaseTag expects vX.Y.Z tags, got ${a} vs ${b}`);
20081
20081
  return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
20082
20082
  }
@@ -26229,12 +26229,12 @@ function resumeFollowUpOf(state) {
26229
26229
  return state === "aligned" ? "complete" : state;
26230
26230
  }
26231
26231
  function compareRcTagsNewestFirst(a, b) {
26232
- const parse = (tag) => {
26232
+ const parse2 = (tag) => {
26233
26233
  const match = /^v(\d+)\.(\d+)\.(\d+)-rc\.(\d+)$/.exec(tag);
26234
26234
  return match ? match.slice(1).map(Number) : [];
26235
26235
  };
26236
- const left = parse(a);
26237
- const right = parse(b);
26236
+ const left = parse2(a);
26237
+ const right = parse2(b);
26238
26238
  for (let i = 0; i < 4; i++) {
26239
26239
  const delta = (right[i] ?? 0) - (left[i] ?? 0);
26240
26240
  if (delta !== 0) return delta;
@@ -32638,6 +32638,7 @@ function assignWaves(repos, canarySlug) {
32638
32638
  function statusFor(read, currentSha) {
32639
32639
  if (!read) return { status: "pending", record: {} };
32640
32640
  if (read.drift === "match") return { status: "match", record: { mergeSha: read.pr?.mergeSha } };
32641
+ if (read.prUnread) return { status: "red", record: {} };
32641
32642
  const pr = read.pr;
32642
32643
  if (!pr) return { status: "pending", record: {} };
32643
32644
  if (pr.sourceSha && pr.sourceSha !== currentSha) return { status: "pending", record: {} };
@@ -35090,9 +35091,10 @@ LIVE apply to ${repo}:
35090
35091
  desiredByRepo.set(r.repo, desired);
35091
35092
  const drift = compareSeedBytes(desired, content);
35092
35093
  let pr;
35094
+ let prUnread = false;
35093
35095
  try {
35094
35096
  const listBranchPrs = async (branch) => {
35095
- const listed = await gh2(["pr", "list", "--repo", r.repo, "--head", branch, "--base", baseBranch, "--state", "all", "--json", "number,url,state,statusCheckRollup,mergeCommit,body,files", "--limit", "100"]);
35097
+ const listed = await gh2(["pr", "list", "--repo", r.repo, "--head", branch, "--base", baseBranch, "--state", "all", "--json", "number,url,state,statusCheckRollup,mergeCommit,body,files,headRefName", "--limit", "100"]);
35096
35098
  return JSON.parse(listed.stdout || "[]");
35097
35099
  };
35098
35100
  const targetSpecific = (await listBranchPrs(propagationBranch(r.repo, seed.target))).map((p2) => ({ ...p2, files: p2.files?.map((f) => f.path) }));
@@ -35107,11 +35109,19 @@ LIVE apply to ${repo}:
35107
35109
  const checks = rollup.length === 0 ? "none" : rollup.some((c) => c.conclusion === "FAILURE" || c.state === "FAILURE") ? "red" : rollup.every((c) => c.conclusion === "SUCCESS" || c.state === "SUCCESS") ? "success" : "pending";
35108
35110
  const sourceSha = p.body?.match(/Propagates MMI-Hub@([0-9a-fA-F]{7,40})/)?.[1];
35109
35111
  pr = { number: p.number, url: p.url, state: p.state === "MERGED" ? "merged" : p.state === "CLOSED" ? "closed" : "open", checks, mergeSha: p.mergeCommit?.oid, sourceSha };
35112
+ if (pr.state === "open" && sourceSha && sourceSha !== seedSource.sha && p.headRefName) {
35113
+ const head = await gh2(["api", `repos/${r.repo}/contents/${enc(seed.target)}?ref=${encodeURIComponent(p.headRefName)}`]);
35114
+ const parsedHead = JSON.parse(head.stdout);
35115
+ const headContent = parsedHead.encoding === "base64" && typeof parsedHead.content === "string" ? decodeGitHubContents(parsedHead.content) : null;
35116
+ if (compareSeedBytes(desired, headContent) === "match") pr.sourceSha = seedSource.sha;
35117
+ }
35110
35118
  }
35111
- } catch {
35112
- pr = void 0;
35119
+ } catch (e) {
35120
+ prUnread = true;
35121
+ process.stderr.write(`bootstrap propagate: could not read ${r.repo}'s propagation PRs for ${seed.target}: ${e.message}
35122
+ `);
35113
35123
  }
35114
- reads.push({ repo: r.repo, drift: drift === "waived" ? "match" : drift, pr });
35124
+ reads.push({ repo: r.repo, drift: drift === "waived" ? "match" : drift, pr, prUnread });
35115
35125
  }
35116
35126
  let functionGateSatisfied = !isWorkflowSeed;
35117
35127
  if (isWorkflowSeed && canarySlug) {
@@ -35627,10 +35637,41 @@ function formatSuiteStatus(report) {
35627
35637
  var import_node_fs29 = require("node:fs");
35628
35638
  var import_node_path29 = require("node:path");
35629
35639
  var import_node_crypto13 = require("node:crypto");
35640
+
35641
+ // src/kit-version.ts
35642
+ var parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(typeof v === "string" ? v.trim() : "");
35643
+ function kitSatisfies(shell2, module2) {
35644
+ const s = parse(shell2);
35645
+ const m = parse(module2);
35646
+ if (!s || !m) return true;
35647
+ for (let i = 1; i <= 3; i++) if (Number(s[i]) !== Number(m[i])) return Number(s[i]) > Number(m[i]);
35648
+ return !s[4] || Boolean(m[4]);
35649
+ }
35650
+ function kitFromBundle(js) {
35651
+ const direct = /export\s+(?:const|let|var)\s+__kit\s*=\s*["']([^"']+)["']/.exec(js);
35652
+ if (direct) return direct[1];
35653
+ const local = /[{,]\s*([\w$]+)\s+as\s+__kit\b/.exec(js)?.[1];
35654
+ if (!local) return void 0;
35655
+ const name = local.replace(/[$]/g, "\\$&");
35656
+ return new RegExp(`(?:^|[\\s;,{}])(?:const|let|var)?\\s*${name}\\s*=\\s*["']([^"']+)["']`).exec(js)?.[1];
35657
+ }
35658
+ function kitRefusals(shell2, modules) {
35659
+ return modules.filter((m) => !kitSatisfies(shell2, m.kit)).map((m) => `${m.id}: built on kit ${m.kit}, newer than the MM-Work shell's kit ${shell2}`);
35660
+ }
35661
+
35662
+ // src/suite-train.ts
35630
35663
  var SCHEMA = "mmi-suite-cut/v1";
35631
35664
  var SHA2 = /^[a-f0-9]{40}$/i;
35632
35665
  var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
35633
35666
  var RELEASE_TAG = /^v\d+\.\d+\.\d+$/;
35667
+ async function kitCheck(deps, shellSha, bundles, where) {
35668
+ const shell2 = shellSha ? await deps.shellKit(shellSha) : void 0;
35669
+ const kits = await Promise.all(bundles.map(async (b) => ({ id: b.id, kit: b.url ? await deps.bundleKit(b.url) : void 0 })));
35670
+ const refused = kitRefusals(shell2, kits);
35671
+ if (refused.length) throw new Error(`kit check (${where}) refused; a module built on a newer kit crashes the older shell. Release the newer kit to MM-Work first (or rebuild the module on the shell's kit):
35672
+ ${refused.join("\n")}`);
35673
+ return `kit check (${where}): shell kit ${shell2 ?? "unknown"}; ${kits.map((k) => `${k.id} __kit ${k.kit ?? "none"}`).join(", ") || "no modules"}`;
35674
+ }
35634
35675
  function suitePins(record, bundles) {
35635
35676
  return record.members.map((m) => m.carried ? { module: m.id, rc: m.rcPin, main: m.mainPin } : {
35636
35677
  module: m.id,
@@ -35742,6 +35783,8 @@ async function suiteRcand(deps, opts) {
35742
35783
  `${SUITE_REPO}: one PR for both rc/main pins, wait for merge`,
35743
35784
  `${SUITE_REPO}: rcand`
35744
35785
  ];
35786
+ const lead = await deps.head(SUITE_REPO, "development");
35787
+ plan.push(await kitCheck(deps, lead, active.map((m) => ({ id: m.id, url: m.bundles.dev })), "dev bundles"));
35745
35788
  if (opts.dryRun) return { plan };
35746
35789
  let record = readSuiteCut(deps.recordPath);
35747
35790
  if (record?.leadReleased && record.announcement === "pending") {
@@ -35863,6 +35906,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
35863
35906
  throw new Error(`${member.repo}: live backend or development changed during the cut; pins unchanged`);
35864
35907
  }
35865
35908
  const pins = suitePins(record, bundlesOf(modules));
35909
+ await kitCheck(deps, record.leadDevelopment, pins.map((p) => ({ id: p.module, url: p.rc })), "rc bundles");
35866
35910
  await deps.verifyLeadCut(record.leadDevelopment);
35867
35911
  const pr = await deps.pin({ cut: record.leadDevelopment, pins, heldIds: record.heldMembers?.map((h) => h.id) });
35868
35912
  await deps.verifyLeadCut(record.leadDevelopment);
@@ -46200,9 +46244,9 @@ function isMissingRef(message2) {
46200
46244
  return /404|Not Found|No commit found for SHA/.test(message2);
46201
46245
  }
46202
46246
  function releaseTagsFrom(out) {
46203
- const parse = (t) => t.slice(1).split(".").map(Number);
46247
+ const parse2 = (t) => t.slice(1).split(".").map(Number);
46204
46248
  return out.split(/\s+/).filter((t) => /^v\d+\.\d+\.\d+$/.test(t)).sort((a, b) => {
46205
- const [x, y] = [parse(a), parse(b)];
46249
+ const [x, y] = [parse2(a), parse2(b)];
46206
46250
  return x[0] - y[0] || x[1] - y[1] || x[2] - y[2];
46207
46251
  });
46208
46252
  }
@@ -46493,6 +46537,22 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
46493
46537
  throw new Error(`${pin.module}: MM-Work ${ref} pins do not equal the tested cut (${row?.bundles.main ?? "no main pin"} vs ${pin.main}); release refused`);
46494
46538
  }
46495
46539
  },
46540
+ shellKit: async (sha) => {
46541
+ await exec("git", ["fetch", "--quiet", "origin", "--tags"], root, WATCH_TIMEOUT);
46542
+ try {
46543
+ return (await exec("git", ["describe", "--tags", "--match", "ds-v[0-9]*", "--abbrev=0", sha], root)).replace(/^ds-v/, "") || void 0;
46544
+ } catch {
46545
+ return void 0;
46546
+ }
46547
+ },
46548
+ bundleKit: async (url) => {
46549
+ try {
46550
+ const res = await fetch(url, { signal: AbortSignal.timeout(GH_TIMEOUT) });
46551
+ return res.ok ? kitFromBundle(await res.text()) : void 0;
46552
+ } catch {
46553
+ return void 0;
46554
+ }
46555
+ },
46496
46556
  release,
46497
46557
  shippedTag,
46498
46558
  sameContent: sameContent2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.78",
3
+ "version": "4.5.79",
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",