@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.
- package/dist/main.cjs +85 -10
- 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:
|
|
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.
|
|
16729
|
-
tag: "v4.5.
|
|
16730
|
-
commit: "
|
|
16731
|
-
npm: "@mutmutco/cli@4.5.
|
|
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.
|
|
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.
|
|
16755
|
-
v3Target: "v4.5.
|
|
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