@mutmutco/cli 4.5.57 → 4.5.58
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 +143 -71
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -15697,11 +15697,12 @@ function planBootstrapLabels(labels, live) {
|
|
|
15697
15697
|
...labelsToPrune(labels.map((label) => label.name)).filter((name) => existing.has(name)).map((name) => ({ name, action: "DELETE" }))
|
|
15698
15698
|
];
|
|
15699
15699
|
}
|
|
15700
|
-
function renderBootstrapAncillaryPlan(labels, environments) {
|
|
15700
|
+
function renderBootstrapAncillaryPlan(labels, environments, exceptions = []) {
|
|
15701
15701
|
return [
|
|
15702
15702
|
"bootstrap apply \u2014 labels and environments (dry-run; no mutations):",
|
|
15703
15703
|
...labels.map(({ action, name }) => ` ${action} label ${name}`),
|
|
15704
|
-
...environments.map(({ action, environment, reason }) => ` ${action} environment ${environment} (${reason})`)
|
|
15704
|
+
...environments.map(({ action, environment, reason }) => ` ${action} environment ${environment} (${reason})`),
|
|
15705
|
+
...exceptions.map(({ name, detail }) => ` OK environment ${name} (${detail})`)
|
|
15705
15706
|
].join("\n");
|
|
15706
15707
|
}
|
|
15707
15708
|
function resolveSeedContent(seed, vars, readFile9) {
|
|
@@ -17069,10 +17070,10 @@ var rollout_plan_default = {
|
|
|
17069
17070
|
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)."
|
|
17070
17071
|
},
|
|
17071
17072
|
baseline: {
|
|
17072
|
-
version: "4.5.
|
|
17073
|
-
tag: "v4.5.
|
|
17074
|
-
commit: "
|
|
17075
|
-
npm: "@mutmutco/cli@4.5.
|
|
17073
|
+
version: "4.5.58",
|
|
17074
|
+
tag: "v4.5.58",
|
|
17075
|
+
commit: "05d087d1fcb7",
|
|
17076
|
+
npm: "@mutmutco/cli@4.5.58"
|
|
17076
17077
|
},
|
|
17077
17078
|
exitCriterion: "fleet-n-of-n",
|
|
17078
17079
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17089,14 +17090,14 @@ var rollout_plan_default = {
|
|
|
17089
17090
|
repo: "mutmutco/mmi-hub",
|
|
17090
17091
|
role: "canary",
|
|
17091
17092
|
schedule: "train",
|
|
17092
|
-
v3Target: "v4.5.
|
|
17093
|
+
v3Target: "v4.5.58"
|
|
17093
17094
|
}
|
|
17094
17095
|
],
|
|
17095
17096
|
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.",
|
|
17096
17097
|
rollback: {
|
|
17097
17098
|
independent: true,
|
|
17098
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17099
|
-
v3Target: "v4.5.
|
|
17099
|
+
mechanism: "npm dist-tag latest -> 4.5.58 and redeploy the Hub Lambda from tag v4.5.58 (05d087d1fcb7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17100
|
+
v3Target: "v4.5.58 (@mutmutco/cli@4.5.58, tag commit 05d087d1fcb7 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17100
17101
|
}
|
|
17101
17102
|
},
|
|
17102
17103
|
{
|
|
@@ -20094,9 +20095,14 @@ function validSuiteCutContext(repo, context) {
|
|
|
20094
20095
|
function authorisedSuiteCut(repo, meta, context) {
|
|
20095
20096
|
return validSuiteCutContext(repo, context) && !!meta?.suite && context.lead.toLowerCase() === suiteLeadRepo(repo, meta.suite).toLowerCase();
|
|
20096
20097
|
}
|
|
20098
|
+
var BUNDLE_ONLY_RC_EXCEPTION = "suite member: rc is bundle-only (no rc runtime)";
|
|
20099
|
+
function isBundleOnlySuiteMember(repo, suite) {
|
|
20100
|
+
if (!suite) return false;
|
|
20101
|
+
return repo.split("/")[1]?.toLowerCase() !== suite.toLowerCase();
|
|
20102
|
+
}
|
|
20097
20103
|
function suiteMemberRefusal(repo, meta, lane, solo = false, cut) {
|
|
20098
20104
|
const suite = meta?.suite;
|
|
20099
|
-
if (!suite || repo
|
|
20105
|
+
if (!suite || !isBundleOnlySuiteMember(repo, suite)) return null;
|
|
20100
20106
|
if (authorisedSuiteCut(repo, meta, cut)) return null;
|
|
20101
20107
|
if (lane !== "rcand" && solo) return null;
|
|
20102
20108
|
return `${repo} is part of the ${suite} suite; run the suite cut/release from ${suiteLeadRepo(repo, suite)} (mmi-cli devops suite rcand|release). A solo backend release needs --suite-solo --owner-go.`;
|
|
@@ -20701,7 +20707,7 @@ async function appendServedChannelLegs(deps, input, dispatch) {
|
|
|
20701
20707
|
const publishStatus = input.publish?.state === "complete" ? "success" : "failure";
|
|
20702
20708
|
const prepared = await (input.prepared ?? prepareServedChannel(deps, input));
|
|
20703
20709
|
if (!isHubControlRepo(input.repo)) {
|
|
20704
|
-
const bundleOnly = validSuiteCutContext(input.repo, deps.suiteCut)
|
|
20710
|
+
const bundleOnly = validSuiteCutContext(input.repo, deps.suiteCut);
|
|
20705
20711
|
const [home2, proof2] = await Promise.all([
|
|
20706
20712
|
appendInstallHomePublish(deps, { repo: input.repo, installer: input.installer, publishStatus, watch: input.watch }, dispatch),
|
|
20707
20713
|
bundleOnly && servedChannelFor(input.repo, input.installer) ? Promise.resolve({ ...dispatch, note: `${dispatch.note}; served channel not probed: a suite bundle-only release does not deploy the backend that serves it (#7468)` }) : appendServedChannelProof(deps, { repo: input.repo, tag: input.tag, shipped: shipLegsGreen(input.deploy, input.publish), installer: input.installer }, dispatch)
|
|
@@ -20971,7 +20977,7 @@ async function discoverShaWorkflowRuns(deps, repo, headSha, seenRunIds) {
|
|
|
20971
20977
|
return extra;
|
|
20972
20978
|
}
|
|
20973
20979
|
async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince, autoRunHeadSha, dispatchFailure = "throw", publishDir) {
|
|
20974
|
-
if (validSuiteCutContext(ctx.repo, deps.suiteCut) &&
|
|
20980
|
+
if (validSuiteCutContext(ctx.repo, deps.suiteCut) && stage !== "dev") {
|
|
20975
20981
|
return { note: "suite bundle-only: live backend not deployed", deployStatus: "success" };
|
|
20976
20982
|
}
|
|
20977
20983
|
if (isCentralDispatchModel(model)) {
|
|
@@ -21098,7 +21104,7 @@ async function attemptRetire(deps, ctx) {
|
|
|
21098
21104
|
return { status: "failed", category: r.category ?? "transport-failed", note: r.note };
|
|
21099
21105
|
}
|
|
21100
21106
|
async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
|
|
21101
|
-
if (validSuiteCutContext(ctx.repo, deps.suiteCut)
|
|
21107
|
+
if (validSuiteCutContext(ctx.repo, deps.suiteCut))
|
|
21102
21108
|
return { status: "not-applicable", note: "bundle-only suite member has no rc runtime to retire" };
|
|
21103
21109
|
if (model !== "tenant-container") {
|
|
21104
21110
|
return { status: "not-applicable", note: `${model} has no co-resident rc runtime to retire` };
|
|
@@ -24718,7 +24724,7 @@ async function runTrainDoctor(input) {
|
|
|
24718
24724
|
}
|
|
24719
24725
|
}
|
|
24720
24726
|
let preflightTimeouts = 0;
|
|
24721
|
-
if (!
|
|
24727
|
+
if (!authorisedSuiteCut(repo, meta, input.suiteCut)) for (; ; ) {
|
|
24722
24728
|
try {
|
|
24723
24729
|
await train.runSelf(["secrets", "preflight", "--stage", stage, "--repo", repo, ...lane === "hotfix" ? ["--lane", "hotfix"] : []]);
|
|
24724
24730
|
break;
|
|
@@ -25161,7 +25167,7 @@ async function preflight(deps, ctx, stage, meta, lane) {
|
|
|
25161
25167
|
if (model === "none") {
|
|
25162
25168
|
throw new Error(`${ctx.repo} is not Hub-deployed (deployModel=none) \u2014 the release train does not apply; use the project's own release path`);
|
|
25163
25169
|
}
|
|
25164
|
-
if (!
|
|
25170
|
+
if (!authorisedSuiteCut(ctx.repo, meta, deps.suiteCut))
|
|
25165
25171
|
await deps.runSelf(["secrets", "preflight", "--stage", stage, "--repo", ctx.repo, ...lane === "hotfix" ? ["--lane", "hotfix"] : []]);
|
|
25166
25172
|
await assertNpmMajorPreflightFromWorkflows(deps, ctx.repo);
|
|
25167
25173
|
await assertActionsJobsCanStart(deps, ctx.repo);
|
|
@@ -25760,7 +25766,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
|
|
|
25760
25766
|
);
|
|
25761
25767
|
const dispatch = recovered.deployStatus === "failure" && !recovered.note.includes(RCAND_RETRY_DEPLOY_COMMAND) ? { ...recovered, note: `once the cause is fixed, retry this rc deploy with \`${RCAND_RETRY_DEPLOY_COMMAND}\` (same tag and SHA, never re-cut): ${recovered.note}` } : recovered;
|
|
25762
25768
|
const unverifiable = !isCentralDispatchModel(deployModel) && dispatch.deployStatus === "pending" && dispatch.runId == null && !dispatch.workflowRuns?.length;
|
|
25763
|
-
const deployEntry = validSuiteCutContext(anchors.repo, deps.suiteCut)
|
|
25769
|
+
const deployEntry = validSuiteCutContext(anchors.repo, deps.suiteCut) ? { state: "skipped", sha, note: "suite bundle-only: rc backend not deployed (#7262)" } : unverifiable ? { state: "skipped", sha, note: `${dispatch.note} \u2014 no run to verify on rc; recorded skipped, never pending (#6068)` } : deployPhaseInput(deployModel, dispatch, { releaseSha: sha });
|
|
25764
25770
|
await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(deployEntry), { ...opts, landed: "the immutable rc tag, the green wall, and origin/rc" });
|
|
25765
25771
|
await recordPhase(ledger, deps, anchors, "publish", phaseEntry({ state: "skipped", sha, note: "rcand publishes nothing \u2014 the publish leg rides the release" }), { ...opts, landed });
|
|
25766
25772
|
await recordPhase(ledger, deps, anchors, "alignment", phaseEntry({ state: "skipped", sha, note: "rcand aligns no branch \u2014 alignment rides the release" }), { ...opts, landed });
|
|
@@ -25923,7 +25929,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
25923
25929
|
}
|
|
25924
25930
|
);
|
|
25925
25931
|
const deployDispatch = annotateVersionEnvDispatch(await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, recoveredDeployDispatch), versionEnv);
|
|
25926
|
-
const deployLeg = phaseEntry(validSuiteCutContext(ctx.repo, deps.suiteCut)
|
|
25932
|
+
const deployLeg = phaseEntry(validSuiteCutContext(ctx.repo, deps.suiteCut) ? { state: "skipped", sha: releaseSha, note: "suite bundle-only: live backend not deployed (#7263)" } : deployPhaseInput(deployModel, deployDispatch, { releaseSha }));
|
|
25927
25933
|
await recordPhase(ledger, deps, ledgerAnchors, "deploy", deployLeg, {
|
|
25928
25934
|
landed: "the immutable tag, the green required-check wall, the origin/main fast-forward, and the verified GitHub Release"
|
|
25929
25935
|
});
|
|
@@ -26346,7 +26352,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
26346
26352
|
dispatch2.deployStatus = "success";
|
|
26347
26353
|
dispatch2.note = `historical ${deployModel} deploy/publish status: success (re-verified by the recorded run)`;
|
|
26348
26354
|
}
|
|
26349
|
-
if (validSuiteCutContext(ctx.repo, deps.suiteCut)
|
|
26355
|
+
if (validSuiteCutContext(ctx.repo, deps.suiteCut)) {
|
|
26350
26356
|
phaseInputs.deploy = { state: "skipped", sha: tagSha, note: "suite bundle-only: live backend not deployed (#7263)" };
|
|
26351
26357
|
if (dispatch2.deployStatus !== "failure" && (phaseInputs.publish.state === "complete" || phaseInputs.publish.state === "skipped")) {
|
|
26352
26358
|
dispatch2.deployStatus = "success";
|
|
@@ -26481,7 +26487,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
26481
26487
|
);
|
|
26482
26488
|
const deployDispatch = annotateVersionEnvDispatch(await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, recoveredDeployDispatch), versionEnv);
|
|
26483
26489
|
steps.push(`dispatched the ${deployModel} deploy path`);
|
|
26484
|
-
const deployLeg = persisted?.phases.deploy.superseded === true ? persisted.phases.deploy : phaseEntry(validSuiteCutContext(ctx.repo, deps.suiteCut)
|
|
26490
|
+
const deployLeg = persisted?.phases.deploy.superseded === true ? persisted.phases.deploy : phaseEntry(validSuiteCutContext(ctx.repo, deps.suiteCut) ? { state: "skipped", sha: tagSha, note: "suite bundle-only: live backend not deployed (#7263)" } : deployPhaseInput(deployModel, deployDispatch, { releaseSha: tagSha }));
|
|
26485
26491
|
if (persisted?.phases.deploy.superseded !== true) {
|
|
26486
26492
|
await recordPhase(ledger, deps, anchors, "deploy", deployLeg, {
|
|
26487
26493
|
strict: ledgerMode === "strict",
|
|
@@ -32356,11 +32362,12 @@ function environmentNamesForTrack(track) {
|
|
|
32356
32362
|
const branches = new Set(branchesForTrack(track));
|
|
32357
32363
|
return REPO_ENVIRONMENTS.filter((environment) => branches.has(ENVIRONMENT_BRANCHES[environment]));
|
|
32358
32364
|
}
|
|
32359
|
-
function repoEnvironmentSpecs(
|
|
32365
|
+
function repoEnvironmentSpecs(repo, releaseTrack = "full", suite) {
|
|
32360
32366
|
const included = new Set(environmentNamesForTrack(releaseTrack));
|
|
32361
32367
|
const trackExceptions = releaseTrack === "full" ? void 0 : TRACK_ENVIRONMENT_EXCEPTIONS[releaseTrack];
|
|
32368
|
+
const bundleOnlyRc = isBundleOnlySuiteMember(repo, suite);
|
|
32362
32369
|
return REPO_ENVIRONMENTS.map((name) => {
|
|
32363
|
-
const exception = name
|
|
32370
|
+
const exception = !included.has(name) ? trackExceptions?.[name] : name === "rc" && bundleOnlyRc ? BUNDLE_ONLY_RC_EXCEPTION : void 0;
|
|
32364
32371
|
return {
|
|
32365
32372
|
name,
|
|
32366
32373
|
branch: ENVIRONMENT_BRANCHES[name],
|
|
@@ -32371,8 +32378,8 @@ function repoEnvironmentSpecs(_repo, releaseTrack = "full") {
|
|
|
32371
32378
|
};
|
|
32372
32379
|
});
|
|
32373
32380
|
}
|
|
32374
|
-
function provisionedEnvironmentSpecs(repo, releaseTrack = "full") {
|
|
32375
|
-
return repoEnvironmentSpecs(repo, releaseTrack).filter((spec) => spec.exception == null);
|
|
32381
|
+
function provisionedEnvironmentSpecs(repo, releaseTrack = "full", suite) {
|
|
32382
|
+
return repoEnvironmentSpecs(repo, releaseTrack, suite).filter((spec) => spec.exception == null);
|
|
32376
32383
|
}
|
|
32377
32384
|
function asEnvironmentList(value) {
|
|
32378
32385
|
if (Array.isArray(value)) return value;
|
|
@@ -32412,10 +32419,10 @@ var PLAN_GATED_PROTECTION_RE = /billing plan/i;
|
|
|
32412
32419
|
function isPlanGatedProtectionError(error) {
|
|
32413
32420
|
return PLAN_GATED_PROTECTION_RE.test(String(error?.message ?? error));
|
|
32414
32421
|
}
|
|
32415
|
-
async function reconcileRepoEnvironments(repo, releaseTrack, client) {
|
|
32422
|
+
async function reconcileRepoEnvironments(repo, releaseTrack, client, suite) {
|
|
32416
32423
|
const raw = await client.rest("GET", `repos/${repo}/environments`);
|
|
32417
32424
|
const live = new Map(asEnvironmentList(raw).flatMap((entry) => entry.name ? [[entry.name, entry]] : []));
|
|
32418
|
-
const planned = provisionedEnvironmentSpecs(repo, releaseTrack);
|
|
32425
|
+
const planned = provisionedEnvironmentSpecs(repo, releaseTrack, suite);
|
|
32419
32426
|
const created = [];
|
|
32420
32427
|
const updated = [];
|
|
32421
32428
|
const skipped = [];
|
|
@@ -32448,10 +32455,10 @@ async function reconcileRepoEnvironments(repo, releaseTrack, client) {
|
|
|
32448
32455
|
}
|
|
32449
32456
|
return { repo, planned, created, updated, skipped, planGated };
|
|
32450
32457
|
}
|
|
32451
|
-
async function verifyRepoEnvironments(repo, releaseTrack, client) {
|
|
32458
|
+
async function verifyRepoEnvironments(repo, releaseTrack, client, suite) {
|
|
32452
32459
|
const raw = await client.rest("GET", `repos/${repo}/environments`);
|
|
32453
32460
|
const live = new Map(asEnvironmentList(raw).flatMap((entry) => entry.name ? [[entry.name, entry]] : []));
|
|
32454
|
-
return provisionedEnvironmentSpecs(repo, releaseTrack).map((spec) => {
|
|
32461
|
+
return provisionedEnvironmentSpecs(repo, releaseTrack, suite).map((spec) => {
|
|
32455
32462
|
const drift = environmentDrift(live.get(spec.name), spec);
|
|
32456
32463
|
return { environment: spec.name, missing: !live.has(spec.name), problems: drift.problems, notes: drift.notes };
|
|
32457
32464
|
});
|
|
@@ -32569,7 +32576,17 @@ function planBootstrapControlPlane(input) {
|
|
|
32569
32576
|
}
|
|
32570
32577
|
}
|
|
32571
32578
|
const deployStages = deployStagesForBootstrap(input.deployModel, input.releaseTrack);
|
|
32579
|
+
const bundleOnlyRc = isBundleOnlySuiteMember(input.repo, input.suite);
|
|
32572
32580
|
for (const stage of deployStages) {
|
|
32581
|
+
if (stage === "rc" && bundleOnlyRc) {
|
|
32582
|
+
rows.push({
|
|
32583
|
+
kind: "deploy-row",
|
|
32584
|
+
target: "DEPLOY#rc",
|
|
32585
|
+
action: "skip",
|
|
32586
|
+
detail: BUNDLE_ONLY_RC_EXCEPTION
|
|
32587
|
+
});
|
|
32588
|
+
continue;
|
|
32589
|
+
}
|
|
32573
32590
|
const present = input.deployFacts?.stages?.[stage]?.present === true;
|
|
32574
32591
|
const command = bootstrapSetDeployCommand(input.repo, stage, input.deployModel);
|
|
32575
32592
|
rows.push(present && stage === "main" ? {
|
|
@@ -32959,10 +32976,13 @@ function centralContainerPortRangeCheck(deployModel, portRange, repo) {
|
|
|
32959
32976
|
detail: ok ? void 0 : `${deployModel} needs PROJECT# META.portRange for mmi-cli stage \u2014 assign with: mmi-cli stage port-range ${repo}`
|
|
32960
32977
|
};
|
|
32961
32978
|
}
|
|
32962
|
-
function deployRowChecks(repo, deployModel, releaseTrack, facts, factsRead) {
|
|
32979
|
+
function deployRowChecks(repo, deployModel, releaseTrack, facts, factsRead, suite) {
|
|
32963
32980
|
const stages = deployStagesForBootstrap(deployModel, releaseTrack);
|
|
32964
32981
|
if (!stages.length) return [];
|
|
32965
32982
|
return stages.map((stage) => {
|
|
32983
|
+
if (stage === "rc" && isBundleOnlySuiteMember(repo, suite)) {
|
|
32984
|
+
return { ok: true, label: "DEPLOY#rc row exception", detail: BUNDLE_ONLY_RC_EXCEPTION };
|
|
32985
|
+
}
|
|
32966
32986
|
const ok = factsRead && facts?.stages?.[stage]?.present === true;
|
|
32967
32987
|
return {
|
|
32968
32988
|
ok,
|
|
@@ -33023,7 +33043,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
33023
33043
|
}
|
|
33024
33044
|
let environmentDrift2 = [];
|
|
33025
33045
|
try {
|
|
33026
|
-
environmentDrift2 = await verifyRepoEnvironments(repo, effectiveTrack, deps.client);
|
|
33046
|
+
environmentDrift2 = await verifyRepoEnvironments(repo, effectiveTrack, deps.client, deps.projectMeta?.suite);
|
|
33027
33047
|
} catch (e) {
|
|
33028
33048
|
checks.push({
|
|
33029
33049
|
ok: false,
|
|
@@ -33038,7 +33058,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
33038
33058
|
detail: drift.problems.length ? drift.problems.join("; ") : drift.notes.length ? `advisory: ${drift.notes.join("; ")}` : void 0
|
|
33039
33059
|
});
|
|
33040
33060
|
}
|
|
33041
|
-
for (const spec of repoEnvironmentSpecs(repo, releaseTrack ?? (repoClass === "content" ? "trunk" : "full")).filter((entry) => entry.exception)) {
|
|
33061
|
+
for (const spec of repoEnvironmentSpecs(repo, releaseTrack ?? (repoClass === "content" ? "trunk" : "full"), deps.projectMeta?.suite).filter((entry) => entry.exception)) {
|
|
33042
33062
|
checks.push({ ok: true, label: `repository environment exception: ${spec.name}`, detail: spec.exception });
|
|
33043
33063
|
}
|
|
33044
33064
|
const strayTrainBranches = TRAIN_BRANCHES2.filter((b) => !branchesWanted.includes(b) && branchNames.has(b));
|
|
@@ -33112,7 +33132,8 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
33112
33132
|
deps.deployModel,
|
|
33113
33133
|
effectiveTrack,
|
|
33114
33134
|
deps.deployFacts,
|
|
33115
|
-
deps.deployFactsRead === true
|
|
33135
|
+
deps.deployFactsRead === true,
|
|
33136
|
+
deps.projectMeta?.suite
|
|
33116
33137
|
));
|
|
33117
33138
|
const readme = await contentText(deps, repo, baseBranch, "README.md");
|
|
33118
33139
|
checks.push({
|
|
@@ -33707,6 +33728,7 @@ function registerBootstrapCommands(program3) {
|
|
|
33707
33728
|
requiredCheckBranches: resolveRequiredCheckBranches(metaWithDeclaredClass(meta, cls), repo),
|
|
33708
33729
|
requiredChecks: meta?.requiredChecks ?? void 0,
|
|
33709
33730
|
deployModel: meta?.deployModel,
|
|
33731
|
+
suite: meta?.suite,
|
|
33710
33732
|
owners: controlOwners,
|
|
33711
33733
|
github,
|
|
33712
33734
|
deployFacts,
|
|
@@ -33773,31 +33795,39 @@ function registerBootstrapCommands(program3) {
|
|
|
33773
33795
|
return fail(`bootstrap apply: ${e.message}`);
|
|
33774
33796
|
}
|
|
33775
33797
|
let effectiveTrack = bootstrapReleaseTrack;
|
|
33798
|
+
let suiteSlug;
|
|
33776
33799
|
if (!o.releaseTrack) {
|
|
33777
33800
|
const read = await fetchProjectBySlugChecked(parsedRepo.slug, registryClientDeps(await loadConfig()));
|
|
33778
33801
|
if (read.ok) {
|
|
33779
33802
|
effectiveTrack = resolveReleaseTrack(metaWithDeclaredClass(read.project, o.class), void 0, repo);
|
|
33803
|
+
suiteSlug = read.project?.suite;
|
|
33780
33804
|
} else if (o.execute) {
|
|
33781
33805
|
return failGraceful(`bootstrap apply: registry META unreadable (${read.error}) and no --release-track given \u2014 refusing to assume the ${bootstrapReleaseTrack} track for live writes`);
|
|
33782
33806
|
} else {
|
|
33783
33807
|
console.error(`bootstrap apply: registry META unreadable (${read.error}) \u2014 release track ${bootstrapReleaseTrack} is ASSUMED from the class default for this dry-run; --execute will refuse`);
|
|
33784
33808
|
}
|
|
33809
|
+
} else {
|
|
33810
|
+
const read = await fetchProjectBySlugChecked(parsedRepo.slug, registryClientDeps(await loadConfig()));
|
|
33811
|
+
if (read.ok) suiteSlug = read.project?.suite;
|
|
33785
33812
|
}
|
|
33786
33813
|
if (o.only.trim() === "environments") {
|
|
33787
33814
|
const client = defaultGitHubClient();
|
|
33788
33815
|
const envJson = { repo, track: effectiveTrack };
|
|
33789
33816
|
if (!o.execute) {
|
|
33790
|
-
const drift = await verifyRepoEnvironments(repo, effectiveTrack, client);
|
|
33817
|
+
const drift = await verifyRepoEnvironments(repo, effectiveTrack, client, suiteSlug);
|
|
33791
33818
|
const classified = drift.map(classifyEnvironmentDrift);
|
|
33792
33819
|
envJson.plan = classified;
|
|
33793
33820
|
if (o.json) console.log(JSON.stringify(envJson, null, 2));
|
|
33794
33821
|
else {
|
|
33795
33822
|
console.log(`bootstrap apply --only environments \u2014 dry-run (${repo}, track ${effectiveTrack}); no writes:`);
|
|
33796
33823
|
for (const c of classified) console.log(` ${c.action} ${c.environment} \u2014 ${c.reason}`);
|
|
33824
|
+
for (const spec of repoEnvironmentSpecs(repo, effectiveTrack, suiteSlug)) {
|
|
33825
|
+
if (spec.exception) console.log(` OK environment ${spec.name} (${spec.exception})`);
|
|
33826
|
+
}
|
|
33797
33827
|
console.log(" (plan-gated protections \u2014 wait_timer/reviewers the org plan rejects \u2014 are reported at --execute)");
|
|
33798
33828
|
}
|
|
33799
33829
|
} else {
|
|
33800
|
-
const envResult = await reconcileRepoEnvironments(repo, effectiveTrack, client);
|
|
33830
|
+
const envResult = await reconcileRepoEnvironments(repo, effectiveTrack, client, suiteSlug);
|
|
33801
33831
|
const lines2 = [];
|
|
33802
33832
|
if (envResult.created.length) lines2.push(`environments created: ${envResult.created.join(", ")}`);
|
|
33803
33833
|
if (envResult.updated.length) lines2.push(`environments updated: ${envResult.updated.join(", ")}`);
|
|
@@ -33805,6 +33835,9 @@ function registerBootstrapCommands(program3) {
|
|
|
33805
33835
|
for (const gated of envResult.planGated) {
|
|
33806
33836
|
lines2.push(`environment ${gated.environment}: plan-gated protections (${gated.protections.join(", ")} not settable on this org plan \u2014 policy-only provisioned)`);
|
|
33807
33837
|
}
|
|
33838
|
+
for (const spec of repoEnvironmentSpecs(repo, effectiveTrack, suiteSlug)) {
|
|
33839
|
+
if (spec.exception) lines2.push(`OK environment ${spec.name} (${spec.exception})`);
|
|
33840
|
+
}
|
|
33808
33841
|
envJson.result = { created: envResult.created, updated: envResult.updated, skipped: envResult.skipped, planGated: envResult.planGated };
|
|
33809
33842
|
if (o.json) console.log(JSON.stringify(envJson, null, 2));
|
|
33810
33843
|
else {
|
|
@@ -33926,6 +33959,7 @@ function registerBootstrapCommands(program3) {
|
|
|
33926
33959
|
requiredCheckBranches: resolveRequiredCheckBranches(metaWithDeclaredClass(registryMeta, o.class), repo),
|
|
33927
33960
|
requiredChecks: registryMeta?.requiredChecks ?? void 0,
|
|
33928
33961
|
deployModel: applyDeployModel,
|
|
33962
|
+
suite: suiteSlug,
|
|
33929
33963
|
owners: controlOwners,
|
|
33930
33964
|
github,
|
|
33931
33965
|
deployFacts: controlDeployFacts,
|
|
@@ -34270,7 +34304,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
34270
34304
|
}
|
|
34271
34305
|
if (o.execute && !onlyTarget) {
|
|
34272
34306
|
try {
|
|
34273
|
-
const envResult = await reconcileRepoEnvironments(repo, effectiveTrack, defaultGitHubClient());
|
|
34307
|
+
const envResult = await reconcileRepoEnvironments(repo, effectiveTrack, defaultGitHubClient(), suiteSlug);
|
|
34274
34308
|
if (envResult.created.length) applied.push(`environments created: ${envResult.created.join(", ")}`);
|
|
34275
34309
|
if (envResult.updated.length) applied.push(`environments updated: ${envResult.updated.join(", ")}`);
|
|
34276
34310
|
if (envResult.skipped.length) applied.push(`environments current: ${envResult.skipped.join(", ")}`);
|
|
@@ -34367,6 +34401,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
34367
34401
|
requiredCheckBranches: resolveRequiredCheckBranches(metaWithDeclaredClass(registryMeta, o.class), repo),
|
|
34368
34402
|
requiredChecks: registryMeta?.requiredChecks ?? void 0,
|
|
34369
34403
|
deployModel: applyDeployModel,
|
|
34404
|
+
suite: suiteSlug,
|
|
34370
34405
|
owners: controlOwners,
|
|
34371
34406
|
github,
|
|
34372
34407
|
deployFacts: controlDeployFacts,
|
|
@@ -34410,7 +34445,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
34410
34445
|
const liveLabels = JSON.parse((await gh2(["label", "list", "-R", repo, "--limit", "1000", "--json", "name"])).stdout);
|
|
34411
34446
|
if (!Array.isArray(liveLabels)) throw new Error("label list is not an array");
|
|
34412
34447
|
ancillaryPlan = planBootstrapLabels(manifest.labels, liveLabels);
|
|
34413
|
-
environmentPlan = (await verifyRepoEnvironments(repo, effectiveTrack, controlClient)).map(classifyEnvironmentDrift);
|
|
34448
|
+
environmentPlan = (await verifyRepoEnvironments(repo, effectiveTrack, controlClient, suiteSlug)).map(classifyEnvironmentDrift);
|
|
34414
34449
|
} catch (e) {
|
|
34415
34450
|
return failGraceful(`bootstrap apply: cannot plan labels/environments; no writes made: ${e.message}`);
|
|
34416
34451
|
}
|
|
@@ -34421,7 +34456,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
34421
34456
|
if (controlRows.length) console.log(`
|
|
34422
34457
|
${renderBootstrapControlPlan(controlRows)}`);
|
|
34423
34458
|
if (ancillaryPlan && environmentPlan) console.log(`
|
|
34424
|
-
${renderBootstrapAncillaryPlan(ancillaryPlan, environmentPlan)}`);
|
|
34459
|
+
${renderBootstrapAncillaryPlan(ancillaryPlan, environmentPlan, repoEnvironmentSpecs(repo, effectiveTrack, suiteSlug).filter((spec) => spec.exception).map((spec) => ({ name: spec.name, detail: spec.exception })))}`);
|
|
34425
34460
|
if (o.execute) console.log(`
|
|
34426
34461
|
LIVE apply to ${repo}:
|
|
34427
34462
|
${applied.join("\n ")}`);
|
|
@@ -35086,13 +35121,15 @@ var RELEASE_TAG = /^v\d+\.\d+\.\d+$/;
|
|
|
35086
35121
|
function suitePins(record, bundles) {
|
|
35087
35122
|
return record.members.map((m) => m.carried ? { module: m.id, rc: m.rcPin, main: m.mainPin } : {
|
|
35088
35123
|
module: m.id,
|
|
35089
|
-
rc: `https://${m.
|
|
35124
|
+
rc: `https://${m.host}/rc/module.js?v=${m.tag}`,
|
|
35090
35125
|
main: mainPinFor(m.host, bundles[m.id], suiteReleaseTag(m.tag))
|
|
35091
35126
|
});
|
|
35092
35127
|
}
|
|
35093
35128
|
function mainPinFor(host, bundles, tag) {
|
|
35094
|
-
|
|
35095
|
-
|
|
35129
|
+
return `https://${host}${mainBundlePath(bundles)}?v=${tag}`;
|
|
35130
|
+
}
|
|
35131
|
+
function mainBundlePath(bundles) {
|
|
35132
|
+
return [bundles?.dev, bundles?.main].map(pathOf).find(Boolean) ?? "/module.js";
|
|
35096
35133
|
}
|
|
35097
35134
|
function pathOf(url) {
|
|
35098
35135
|
try {
|
|
@@ -35120,7 +35157,7 @@ function readSuiteCut(path2) {
|
|
|
35120
35157
|
} catch {
|
|
35121
35158
|
throw new Error(`suite cut record unreadable at ${path2}; repair it, never recut`);
|
|
35122
35159
|
}
|
|
35123
|
-
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent"].includes(record.announcement) || !record.leadReleased) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0)))
|
|
35160
|
+
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent", "skipped"].includes(record.announcement) || !record.leadReleased) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.mainPublished !== void 0 && (m.mainPublished !== true || !m.released || !!m.carried) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0)))
|
|
35124
35161
|
throw new Error(`suite cut record has unknown or invalid state at ${path2}; repair it, never recut`);
|
|
35125
35162
|
return record;
|
|
35126
35163
|
}
|
|
@@ -35176,13 +35213,13 @@ async function suiteRcand(deps, opts) {
|
|
|
35176
35213
|
const members = suiteMembers(await deps.registry());
|
|
35177
35214
|
const modules = members.filter((m) => m.repo !== SUITE_REPO);
|
|
35178
35215
|
const plan = [
|
|
35179
|
-
...modules.map((m) => `${m.repo}: normal rcand
|
|
35216
|
+
...modules.map((m) => `${m.repo}: normal rcand + module-rc-publish.yml (once)`),
|
|
35180
35217
|
`${SUITE_REPO}: one PR for both rc/main pins, wait for merge`,
|
|
35181
35218
|
`${SUITE_REPO}: rcand`
|
|
35182
35219
|
];
|
|
35183
35220
|
if (opts.dryRun) return { plan };
|
|
35184
35221
|
let record = readSuiteCut(deps.recordPath);
|
|
35185
|
-
if (record?.announcement === "sent" && record.leadReleased) {
|
|
35222
|
+
if ((record?.announcement === "sent" || record?.announcement === "skipped") && record.leadReleased) {
|
|
35186
35223
|
const archive = `${deps.recordPath}.${record.leadRcTag}.json`;
|
|
35187
35224
|
if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
|
|
35188
35225
|
(0, import_node_fs29.renameSync)(deps.recordPath, archive);
|
|
@@ -35200,7 +35237,7 @@ async function suiteRcand(deps, opts) {
|
|
|
35200
35237
|
const [backend, development, rcHead] = await Promise.all([deps.backend(m), deps.head(m.repo, "development"), deps.head(m.repo, "rc")]);
|
|
35201
35238
|
const previousRc = rcHead ?? await deps.head(m.repo, "main");
|
|
35202
35239
|
if (!backend.version || !backend.host || !development || !SHA2.test(development) || !previousRc || !SHA2.test(previousRc)) throw new Error(`${m.repo}: live backend or branch snapshot is unproven; no cut started`);
|
|
35203
|
-
return { repo: m.repo, id: m.id, backend: backend.version, host: backend.host,
|
|
35240
|
+
return { repo: m.repo, id: m.id, backend: backend.version, host: backend.host, development, previousRc, ...rcHead === null ? { rcWasAbsent: true } : {} };
|
|
35204
35241
|
}))
|
|
35205
35242
|
]);
|
|
35206
35243
|
if (!leadDevelopment || !leadPreviousRc || !SHA2.test(leadDevelopment) || !SHA2.test(leadPreviousRc)) throw new Error("MM-Work development/rc SHA unproven; no cut started");
|
|
@@ -35233,7 +35270,7 @@ async function suiteRcand(deps, opts) {
|
|
|
35233
35270
|
}
|
|
35234
35271
|
const refusals = [];
|
|
35235
35272
|
for (const member of record.members) {
|
|
35236
|
-
if (member.published
|
|
35273
|
+
if (member.published) continue;
|
|
35237
35274
|
try {
|
|
35238
35275
|
await deps.preflight(member.repo);
|
|
35239
35276
|
} catch (e) {
|
|
@@ -35251,7 +35288,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
|
|
|
35251
35288
|
if ((await deps.head(member.repo, "development"))?.toLowerCase() !== member.development.toLowerCase())
|
|
35252
35289
|
throw new Error(`${member.repo}: development moved after the cut snapshot; refuse to add untested code`);
|
|
35253
35290
|
if (member.carried) {
|
|
35254
|
-
|
|
35291
|
+
await deps.publish(member.repo, pinVersion(member.rcPin));
|
|
35255
35292
|
continue;
|
|
35256
35293
|
}
|
|
35257
35294
|
if (!member.tag) {
|
|
@@ -35264,7 +35301,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
|
|
|
35264
35301
|
}
|
|
35265
35302
|
if ((await deps.tagSha(member.repo, member.tag))?.toLowerCase() !== member.sha.toLowerCase()) throw new Error(`${member.repo}: rc tag ${member.tag} moved or is unreadable; stop`);
|
|
35266
35303
|
if (!member.published) {
|
|
35267
|
-
|
|
35304
|
+
await deps.publish(member.repo, member.tag);
|
|
35268
35305
|
member.published = true;
|
|
35269
35306
|
writeSuiteCut(deps.recordPath, record);
|
|
35270
35307
|
}
|
|
@@ -35323,43 +35360,59 @@ async function suiteRelease(deps, ownerGo) {
|
|
|
35323
35360
|
if (hotfix) member.hotfixedTo = hotfix;
|
|
35324
35361
|
}
|
|
35325
35362
|
if (member.hotfixedTo && !member.released) continue;
|
|
35326
|
-
|
|
35327
|
-
|
|
35328
|
-
if (live.version !== expectedBackend && !(member.id === "katip" && !member.released && katipReleaseSha && live.version === katipReleaseSha))
|
|
35329
|
-
throw new Error(`${member.repo}: live backend changed after the cut (${expectedBackend} \u2192 ${live.version}); release refused`);
|
|
35363
|
+
if (live.version !== member.backend && !(member.hotfixedTo && await deps.hotfixOver(member.repo, planned, live.version) === member.hotfixedTo))
|
|
35364
|
+
throw new Error(`${member.repo}: live backend changed after the cut (${member.backend} \u2192 ${live.version}); release refused`);
|
|
35330
35365
|
}
|
|
35331
35366
|
if ((await deps.tagSha(SUITE_REPO, record.leadRcTag))?.toLowerCase() !== record.leadRcSha?.toLowerCase()) throw new Error("MM-Work rc tag changed after the cut; release refused");
|
|
35367
|
+
const publishesMain = !record.leadReleased;
|
|
35368
|
+
const refusals = [];
|
|
35369
|
+
for (const member of record.members) {
|
|
35370
|
+
if (!publishesMain || member.carried || member.released) continue;
|
|
35371
|
+
try {
|
|
35372
|
+
await deps.preflightMain(member.repo, mainBundlePath(members.find((m) => m.repo === member.repo)?.bundles));
|
|
35373
|
+
} catch (e) {
|
|
35374
|
+
refusals.push(`${member.repo}: ${e.message}`);
|
|
35375
|
+
}
|
|
35376
|
+
}
|
|
35377
|
+
if (refusals.length) throw new Error(`module main serving is not ready; no member was released:
|
|
35378
|
+
${refusals.join("\n")}
|
|
35379
|
+
Each member's live release must serve <data>/main/module.js at its main pin path when <data>/main exists.`);
|
|
35332
35380
|
for (const member of record.members) {
|
|
35333
|
-
if (member.
|
|
35334
|
-
if (member.
|
|
35335
|
-
|
|
35336
|
-
|
|
35381
|
+
if (member.carried) continue;
|
|
35382
|
+
if (!member.released) {
|
|
35383
|
+
if (member.hotfixedTo) {
|
|
35384
|
+
console.warn(`${member.repo}: released as ${member.releasedAs ?? suiteReleaseTag(member.tag)} and hotfixed to ${member.hotfixedTo}, which is live; recorded as released (#7482)`);
|
|
35385
|
+
} else {
|
|
35386
|
+
if (member.releasedAs && member.releasedAs !== suiteReleaseTag(member.tag)) console.warn(`${member.repo}: its rc already shipped as ${member.releasedAs}, not ${suiteReleaseTag(member.tag)}; finishing that release (#7464)`);
|
|
35387
|
+
await deps.release(member.repo, member.releasedAs ?? suiteReleaseTag(member.tag), member.sha);
|
|
35388
|
+
}
|
|
35337
35389
|
member.released = true;
|
|
35338
35390
|
writeSuiteCut(deps.recordPath, record);
|
|
35339
|
-
continue;
|
|
35340
35391
|
}
|
|
35341
|
-
if (
|
|
35342
|
-
|
|
35343
|
-
|
|
35344
|
-
|
|
35345
|
-
const live = await deps.backend({ repo: member.repo, id: member.id, bundles: {} });
|
|
35346
|
-
if (!expected || live.version !== expected) throw new Error(`${member.repo}: normal main deploy not stamped at released tag SHA; resume after verifying live backend`);
|
|
35347
|
-
member.backendAfterRelease = live.version;
|
|
35392
|
+
if (publishesMain && !member.mainPublished) {
|
|
35393
|
+
await deps.publishMain(member.repo, member.hotfixedTo ?? member.releasedAs ?? suiteReleaseTag(member.tag), mainBundlePath(members.find((m) => m.repo === member.repo)?.bundles));
|
|
35394
|
+
member.mainPublished = true;
|
|
35395
|
+
writeSuiteCut(deps.recordPath, record);
|
|
35348
35396
|
}
|
|
35349
|
-
member.released = true;
|
|
35350
|
-
writeSuiteCut(deps.recordPath, record);
|
|
35351
35397
|
}
|
|
35352
35398
|
if (!record.leadReleased) {
|
|
35353
35399
|
await deps.release(SUITE_REPO, suiteReleaseTag(record.leadRcTag), record.leadRcSha);
|
|
35354
35400
|
record.leadReleased = true;
|
|
35355
35401
|
writeSuiteCut(deps.recordPath, record);
|
|
35356
35402
|
}
|
|
35357
|
-
if (record.announcement === "pending")
|
|
35403
|
+
if (record.announcement === "pending") {
|
|
35404
|
+
const reason = await deps.announceSkipReason(record);
|
|
35405
|
+
if (!reason) throw new Error("suite announcement outcome ambiguous; verify it before retrying (never post twice)");
|
|
35406
|
+
record.announcement = "skipped";
|
|
35407
|
+
record.announcementNote = reason;
|
|
35408
|
+
writeSuiteCut(deps.recordPath, record);
|
|
35409
|
+
}
|
|
35358
35410
|
if (!record.announcement) {
|
|
35359
35411
|
record.announcement = "pending";
|
|
35360
35412
|
writeSuiteCut(deps.recordPath, record);
|
|
35361
|
-
await deps.announce(record);
|
|
35362
|
-
record.announcement = "sent";
|
|
35413
|
+
const { skipped } = await deps.announce(record);
|
|
35414
|
+
record.announcement = skipped ? "skipped" : "sent";
|
|
35415
|
+
if (skipped) record.announcementNote = skipped;
|
|
35363
35416
|
writeSuiteCut(deps.recordPath, record);
|
|
35364
35417
|
}
|
|
35365
35418
|
return record;
|
|
@@ -45331,10 +45384,7 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45331
45384
|
throw new Error(`${member.repo}: healthy live backend stamp and edgeDomains.main must agree with DEPLOY#main; stop`);
|
|
45332
45385
|
const health = await probePublicHealth(url);
|
|
45333
45386
|
if (!health.ok) throw new Error(`${member.repo}: live backend health could not be proved at ${url}; stop`);
|
|
45334
|
-
|
|
45335
|
-
if (member.id === "katip" && (!rcHost || facts?.stages.rc?.domain !== rcHost))
|
|
45336
|
-
throw new Error(`${member.repo}: normal Katip rc host must agree with DEPLOY#rc; stop`);
|
|
45337
|
-
return { version: live.deployedTag, host, ...rcHost ? { rcHost } : {} };
|
|
45387
|
+
return { version: live.deployedTag, host };
|
|
45338
45388
|
};
|
|
45339
45389
|
const tagSha = async (repo, tag) => {
|
|
45340
45390
|
try {
|
|
@@ -45449,10 +45499,22 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45449
45499
|
const run = await watchRcRun(title, [`repo=${repo}`, `tag=${tag}`], existing[0]);
|
|
45450
45500
|
if (run.conclusion !== "success") throw new Error(`${repo}: module rc publish run ${run.id} not green${await rcRunCause(run.id)}`);
|
|
45451
45501
|
};
|
|
45502
|
+
const publishMain = async (repo, tag, path2) => {
|
|
45503
|
+
const title = `module-main-publish ${repo} @ ${tag}`;
|
|
45504
|
+
const existing = (await rcRuns()).filter((r) => r.displayTitle === title).sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
|
|
45505
|
+
if (existing[0]?.conclusion === "success") return;
|
|
45506
|
+
if (existing[0]?.conclusion === "failure") throw new Error(`${repo}: prior module-main-publish ${tag} failed; inspect the run and retry it explicitly before continuing`);
|
|
45507
|
+
const run = await watchRcRun(title, [`repo=${repo}`, `tag=${tag}`, "channel=main", `path=${path2}`], existing[0]);
|
|
45508
|
+
if (run.conclusion !== "success") throw new Error(`${repo}: module main publish run ${run.id} not green${await rcRunCause(run.id)}`);
|
|
45509
|
+
};
|
|
45452
45510
|
const preflight2 = async (repo) => {
|
|
45453
45511
|
const run = await watchRcRun(`module-rc-preflight ${repo}`, [`repo=${repo}`, "preflight=true"], void 0);
|
|
45454
45512
|
if (run.conclusion !== "success") throw new Error(`rc serving preflight run ${run.id} failed${await rcRunCause(run.id)}`);
|
|
45455
45513
|
};
|
|
45514
|
+
const preflightMain = async (repo, path2) => {
|
|
45515
|
+
const run = await watchRcRun(`module-main-preflight ${repo}`, [`repo=${repo}`, "channel=main", `path=${path2}`, "preflight=true"], void 0);
|
|
45516
|
+
if (run.conclusion !== "success") throw new Error(`main serving preflight run ${run.id} failed${await rcRunCause(run.id)}`);
|
|
45517
|
+
};
|
|
45456
45518
|
const rcRunCause = async (id) => {
|
|
45457
45519
|
try {
|
|
45458
45520
|
return rcRunCauseFromLog(await exec("gh", ["run", "view", String(id), "--repo", "mutmutco/MMI-Hub", "--log-failed"]));
|
|
@@ -45493,6 +45555,8 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45493
45555
|
rcand: runRcand,
|
|
45494
45556
|
publish,
|
|
45495
45557
|
preflight: preflight2,
|
|
45558
|
+
publishMain,
|
|
45559
|
+
preflightMain,
|
|
45496
45560
|
pin: (input) => openSuiteCutPinPr({ root, run: (file, args, cwd) => exec(file, args, cwd, GIT_TIMEOUT_MS) }, input),
|
|
45497
45561
|
verifyLeadCut: async (sha) => {
|
|
45498
45562
|
const current = await gh(["api", `repos/${SUITE_REPO}/git/ref/heads/development`, "--jq", ".object.sha"]);
|
|
@@ -45516,7 +45580,15 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45516
45580
|
announce: async (record) => {
|
|
45517
45581
|
if (!summaryFile) throw new Error("suite announcement requires --announce-summary-file; outcome was not posted");
|
|
45518
45582
|
const result = await trainApplyDeps().announce?.({ repo: SUITE_REPO, tag: record.leadRcTag.replace(/-rc\.\d+$/, ""), summaryFile });
|
|
45583
|
+
if (result?.status === "skipped") return { skipped: result.note };
|
|
45519
45584
|
if (result?.status !== "announced") throw new Error(`suite announcement unverified: ${result?.note ?? "no announcement seam"}`);
|
|
45585
|
+
return {};
|
|
45586
|
+
},
|
|
45587
|
+
announceSkipReason: async () => {
|
|
45588
|
+
const slug = slugOf(SUITE_REPO);
|
|
45589
|
+
const read = await fetchProjectBySlugChecked(slug, registryClientDeps(await loadConfig()));
|
|
45590
|
+
if (!read.ok) throw new Error(`suite announcement: registry read failed for ${slug} (${read.error}); outcome stays ambiguous`);
|
|
45591
|
+
return read.project?.releaseChannel?.trim() ? null : `no releaseChannel configured for ${slug}; nothing could have been posted`;
|
|
45520
45592
|
}
|
|
45521
45593
|
};
|
|
45522
45594
|
}
|
package/package.json
CHANGED