@mutmutco/cli 4.5.21 → 4.5.23

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 +85 -10
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -7803,6 +7803,28 @@ async function postJson(pathSuffix, payload, deps, method = "POST", opts = {}) {
7803
7803
  return { ok: false, status: 0, body: null, error: e.message, unreachable: "transport" };
7804
7804
  }
7805
7805
  }
7806
+ async function edgeCertificateForStage(slug, stage, deps, ensure = false) {
7807
+ if (!deps.baseUrl) throw new Error("Hub API URL unavailable");
7808
+ const token = await deps.token();
7809
+ if (!token) throw new Error("Hub session token unavailable");
7810
+ const path2 = `/projects/${encodeURIComponent(slug)}/edge-certificates/${stage}`;
7811
+ if (ensure) {
7812
+ const write = await postJson(path2, {}, deps, "POST", { noRetry: true });
7813
+ if (!write.ok) throw new Error(`Hub edge certificate ensure failed: ${write.error ?? `HTTP ${write.status}`}`);
7814
+ const body2 = write.body;
7815
+ if (typeof body2?.required !== "boolean") throw new Error("Hub edge certificate response invalid");
7816
+ return body2;
7817
+ }
7818
+ const res = await retriedFetch(
7819
+ deps,
7820
+ `${deps.baseUrl.replace(/\/$/, "")}${path2}`,
7821
+ { method: "GET", headers: { Authorization: `Bearer ${token}` } }
7822
+ );
7823
+ if (!res.ok) throw new Error(`Hub edge certificate ${ensure ? "ensure" : "read"} failed: HTTP ${res.status}`);
7824
+ const body = await res.json();
7825
+ if (typeof body?.required !== "boolean") throw new Error("Hub edge certificate response invalid");
7826
+ return body;
7827
+ }
7806
7828
  async function registerProject(payload, deps) {
7807
7829
  return postJson("/projects/register", payload, deps);
7808
7830
  }
@@ -8016,16 +8038,18 @@ function pickTenantDeployRun(rows, slug, stage) {
8016
8038
  function publicUrlFromDeployFact(fact) {
8017
8039
  return fact?.domain ? `https://${fact.domain}` : void 0;
8018
8040
  }
8019
- function tenantRuntimeHints(stage, fact, probe) {
8041
+ function tenantRuntimeHints(stage, fact, probe, edgeCertificate) {
8020
8042
  const hints = [];
8021
8043
  if (!fact) {
8022
8044
  hints.push(`DEPLOY#${stage} row missing or state-only; seed deploy coords before runtime audit can prove the endpoint.`);
8045
+ if (edgeCertificate?.missing && edgeCertificate.hint) hints.push(edgeCertificate.hint);
8023
8046
  return hints;
8024
8047
  }
8025
8048
  if (!fact.sshHostPresent && fact.substrate === "hetzner-ssh") hints.push(`DEPLOY#${stage} has hetzner-ssh substrate but no sshHost presence; tenant-deploy cannot reach the box.`);
8026
8049
  if (!fact.domain) hints.push(`DEPLOY#${stage} has no edgeVhost.domain; Cloudflare/Caddy public URL cannot be derived.`);
8027
8050
  if (typeof fact.port !== "number") hints.push(`DEPLOY#${stage} has no edgeVhost.port; Caddy upstream/healthUrl hints cannot be checked.`);
8028
8051
  if (probe?.url?.endsWith("/health") && probe.ok === false) hints.push(`Root is auth-walled (401/403) and ${probe.url} answered ${probe.status ?? probe.error}; expose a 2xx /health route or fix the app \u2014 the edge is fine.`);
8052
+ else if (edgeCertificate?.missing && edgeCertificate.hint) hints.push(edgeCertificate.hint);
8029
8053
  else if (probe?.ok === false || probe?.status != null && probe.status >= 500) hints.push("Public URL probe failed; for Cloudflare 525 check Caddy TLS/origin certificate and Cloudflare SSL mode before changing app code.");
8030
8054
  if (stage === "rc") hints.push("rc runtime is expected to be ephemeral: present between /rcand and /release, then retired after release.");
8031
8055
  return hints;
@@ -8040,9 +8064,13 @@ function buildTenantRuntimeStatus(input) {
8040
8064
  deploy: input.deploy ?? null,
8041
8065
  publicUrl,
8042
8066
  publicProbe: input.publicProbe,
8067
+ edgeCertificate: input.edgeCertificate,
8043
8068
  lastTenantDeployRun: input.lastTenantDeployRun,
8044
8069
  expectedEphemeralRc: input.stage === "rc",
8045
- hints: tenantRuntimeHints(input.stage, input.deploy, input.publicProbe)
8070
+ hints: [
8071
+ ...tenantRuntimeHints(input.stage, input.deploy, input.publicProbe, input.edgeCertificate),
8072
+ ...input.edgeCertificateError ? [`Cloudflare edge certificate coverage unreadable: ${input.edgeCertificateError}`] : []
8073
+ ]
8046
8074
  };
8047
8075
  }
8048
8076
  function buildFullTrackReadinessReport(input) {
@@ -16725,10 +16753,10 @@ var rollout_plan_default = {
16725
16753
  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)."
16726
16754
  },
16727
16755
  baseline: {
16728
- version: "4.5.21",
16729
- tag: "v4.5.21",
16730
- commit: "af03f80d9717",
16731
- npm: "@mutmutco/cli@4.5.21"
16756
+ version: "4.5.23",
16757
+ tag: "v4.5.23",
16758
+ commit: "cca49db9ef76",
16759
+ npm: "@mutmutco/cli@4.5.23"
16732
16760
  },
16733
16761
  exitCriterion: "fleet-n-of-n",
16734
16762
  hubOnlyShortcut: "forbidden",
@@ -16745,14 +16773,14 @@ var rollout_plan_default = {
16745
16773
  repo: "mutmutco/mmi-hub",
16746
16774
  role: "canary",
16747
16775
  schedule: "train",
16748
- v3Target: "v4.5.21"
16776
+ v3Target: "v4.5.23"
16749
16777
  }
16750
16778
  ],
16751
16779
  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.",
16752
16780
  rollback: {
16753
16781
  independent: true,
16754
- mechanism: "npm dist-tag latest -> 4.5.21 and redeploy the Hub Lambda from tag v4.5.21 (af03f80d9717); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16755
- v3Target: "v4.5.21 (@mutmutco/cli@4.5.21, tag commit af03f80d9717 \u2014 last known-good release carrying the repo-index v4-only contract)"
16782
+ mechanism: "npm dist-tag latest -> 4.5.23 and redeploy the Hub Lambda from tag v4.5.23 (cca49db9ef76); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16783
+ v3Target: "v4.5.23 (@mutmutco/cli@4.5.23, tag commit cca49db9ef76 \u2014 last known-good release carrying the repo-index v4-only contract)"
16756
16784
  }
16757
16785
  },
16758
16786
  {
@@ -28350,6 +28378,8 @@ var APP_POLL_PERMISSIONS = {
28350
28378
  metadata: "read",
28351
28379
  contents: "read",
28352
28380
  pull_requests: "read",
28381
+ // #7185: without issues:read GraphQL silently drops pr view's closingIssuesReferences.
28382
+ issues: "read",
28353
28383
  checks: "read",
28354
28384
  statuses: "read",
28355
28385
  actions: "read"
@@ -31744,6 +31774,17 @@ function deployRowChecks(repo, deployModel, releaseTrack, facts, factsRead) {
31744
31774
  };
31745
31775
  });
31746
31776
  }
31777
+ function edgeCertificateChecks(results) {
31778
+ return results.flatMap((result) => {
31779
+ if (!result) return [];
31780
+ const { stage, status, error } = result;
31781
+ return [{
31782
+ ok: !error && (status?.required === false || status?.required === true && status.missing === false),
31783
+ label: `Cloudflare edge certificate: ${stage}`,
31784
+ detail: error ? `certificate coverage unreadable (${error}); cannot verify TLS` : status?.hint
31785
+ }];
31786
+ });
31787
+ }
31747
31788
  async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
31748
31789
  const branchesWanted = expectedBranches(repoClass, releaseTrack);
31749
31790
  const baseBranch = releaseTrack === "trunk" || repoClass === "content" ? "main" : "development";
@@ -31865,6 +31906,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
31865
31906
  const check = npmrcGitHubPackagesAuthCheck(path2, await contentText(deps, repo, baseBranch, path2));
31866
31907
  if (check) checks.push(check);
31867
31908
  }
31909
+ checks.push(...edgeCertificateChecks(deps.edgeCertificates ?? []));
31868
31910
  const portRangeCheck = centralContainerPortRangeCheck(deps.deployModel, deps.projectMeta?.portRange, repo);
31869
31911
  if (portRangeCheck) checks.push(portRangeCheck);
31870
31912
  checks.push(...deployRowChecks(
@@ -32279,6 +32321,15 @@ function registerBootstrapCommands(program3) {
32279
32321
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
32280
32322
  deployFacts,
32281
32323
  deployFactsRead: deployFacts !== null,
32324
+ edgeCertificates: await Promise.all(["dev", "rc"].map(async (stage) => {
32325
+ const domain = meta?.edgeDomains?.[stage];
32326
+ if (!domain) return null;
32327
+ try {
32328
+ return { stage, status: await edgeCertificateForStage(slug, stage, reg) };
32329
+ } catch (e) {
32330
+ return { stage, error: e.message };
32331
+ }
32332
+ })),
32282
32333
  pushAllowlistOwners: verifyOwners,
32283
32334
  pushAllowlistOwnersRead: verifyOwnersRead,
32284
32335
  readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : null,
@@ -33003,6 +33054,15 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
33003
33054
  if (res.ok) {
33004
33055
  ddbWrites.push({ slug: registerPayload.slug, action: "register", record: registerPayload });
33005
33056
  applied.push(`ddb register ${registerPayload.slug}`);
33057
+ for (const stage of ["dev", "rc"]) {
33058
+ if (!registerPayload.edgeDomains?.[stage]) continue;
33059
+ try {
33060
+ const cert = await edgeCertificateForStage(slug, stage, reg, true);
33061
+ if (cert.required) applied.push(`edge certificate ${stage}: ${cert.action ?? "checked"}`);
33062
+ } catch (e) {
33063
+ return failGraceful(`bootstrap apply: edge certificate ${stage} could not be ensured after registry write (${e.message}); rerun apply to reconcile \u2014 do not treat this bootstrap as complete`);
33064
+ }
33065
+ }
33006
33066
  const deployModel = typeof registerPayload.deployModel === "string" ? registerPayload.deployModel : void 0;
33007
33067
  if (deployModel === "tenant-container" || deployModel === "solo-container") {
33008
33068
  const shortName = typeof registerPayload.name === "string" ? registerPayload.name : typeof registerPayload.slug === "string" ? registerPayload.slug : repo.split("/")[1] || repo;
@@ -48656,7 +48716,7 @@ tenant.command("status <owner/repo> <stage>").description("read tenant runtime r
48656
48716
  const cfg = await loadConfig();
48657
48717
  const result = await buildTenantRuntimeStatusFor(repo, stage, cfg);
48658
48718
  console.log(JSON.stringify(result, null, 2));
48659
- if (result.publicProbe?.ok === false) process.exitCode = 1;
48719
+ if (result.publicProbe?.ok === false || result.edgeCertificate?.missing) process.exitCode = 1;
48660
48720
  });
48661
48721
  tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the central tenant-deploy.yml for an already-promoted ref (no re-tag/merge); train-authority gated").option("--ref <ref>", "ref to deploy (defaults to the stage branch rc/main, or `development` for dev \u2014 the promoted/staging ref)").option("--watch", "block on the dispatched run and report its outcome (gh run watch --exit-status)").option("--json", "machine-readable output").action(async (repo, stage, o) => {
48662
48722
  if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("runtime tenant redeploy: <stage> must be dev, rc, or main");
@@ -48700,12 +48760,27 @@ async function buildTenantRuntimeStatusFor(target, stage, cfg) {
48700
48760
  const deploy = facts?.stages[stage] ?? null;
48701
48761
  const publicUrl = publicUrlFromDeployFact(deploy);
48702
48762
  const publicProbe = publicUrl ? await probePublicHealth(publicUrl) : void 0;
48763
+ let edgeCertificate;
48764
+ let edgeCertificateError;
48765
+ if (stage === "dev" || stage === "rc") {
48766
+ try {
48767
+ edgeCertificate = await edgeCertificateForStage(slug, stage, reg);
48768
+ if (deploy?.domain && edgeCertificate.host !== deploy.domain.toLowerCase()) {
48769
+ edgeCertificateError = `registered edge host ${edgeCertificate.host} differs from DEPLOY#${stage} ${deploy.domain}`;
48770
+ edgeCertificate = void 0;
48771
+ }
48772
+ } catch (e) {
48773
+ edgeCertificateError = e.message;
48774
+ }
48775
+ }
48703
48776
  return buildTenantRuntimeStatus({
48704
48777
  repo: target,
48705
48778
  slug,
48706
48779
  stage,
48707
48780
  deploy,
48708
48781
  publicProbe,
48782
+ edgeCertificate,
48783
+ edgeCertificateError,
48709
48784
  // Filter the central run list to THIS tenant + stage; rows[0] is whichever tenant deployed last (#2909).
48710
48785
  lastTenantDeployRun: (await fetchLastTenantDeployRun(slug, stage)).run
48711
48786
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.21",
3
+ "version": "4.5.23",
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",