@mutmutco/cli 4.3.42 → 4.3.43
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 +66 -29
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -15710,10 +15710,10 @@ var rollout_plan_default = {
|
|
|
15710
15710
|
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)."
|
|
15711
15711
|
},
|
|
15712
15712
|
baseline: {
|
|
15713
|
-
version: "4.3.
|
|
15714
|
-
tag: "v4.3.
|
|
15715
|
-
commit: "
|
|
15716
|
-
npm: "@mutmutco/cli@4.3.
|
|
15713
|
+
version: "4.3.43",
|
|
15714
|
+
tag: "v4.3.43",
|
|
15715
|
+
commit: "120c58230cfc",
|
|
15716
|
+
npm: "@mutmutco/cli@4.3.43"
|
|
15717
15717
|
},
|
|
15718
15718
|
exitCriterion: "fleet-n-of-n",
|
|
15719
15719
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15730,14 +15730,14 @@ var rollout_plan_default = {
|
|
|
15730
15730
|
repo: "mutmutco/mmi-hub",
|
|
15731
15731
|
role: "canary",
|
|
15732
15732
|
schedule: "train",
|
|
15733
|
-
v3Target: "v4.3.
|
|
15733
|
+
v3Target: "v4.3.43"
|
|
15734
15734
|
}
|
|
15735
15735
|
],
|
|
15736
15736
|
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.",
|
|
15737
15737
|
rollback: {
|
|
15738
15738
|
independent: true,
|
|
15739
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15740
|
-
v3Target: "v4.3.
|
|
15739
|
+
mechanism: "npm dist-tag latest -> 4.3.43 and redeploy the Hub Lambda from tag v4.3.43 (120c58230cfc); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15740
|
+
v3Target: "v4.3.43 (@mutmutco/cli@4.3.43, tag commit 120c58230cfc \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15741
15741
|
}
|
|
15742
15742
|
},
|
|
15743
15743
|
{
|
|
@@ -42357,7 +42357,7 @@ var SSM_REGION = "eu-central-1";
|
|
|
42357
42357
|
var SSM_TOKEN_PARAM = "/mmi-future/_org/slack/SLACK_BOT_TOKEN";
|
|
42358
42358
|
var SSM_CHANNEL_PARAM = "/mmi-future/_org/slack/SLACK_ALERTS_CHANNEL";
|
|
42359
42359
|
var SLACK_TIMEOUT_MS = 1e4;
|
|
42360
|
-
async function
|
|
42360
|
+
async function readSsmParameter(deps, name, decrypt) {
|
|
42361
42361
|
const args = [
|
|
42362
42362
|
"ssm",
|
|
42363
42363
|
"get-parameter",
|
|
@@ -42375,23 +42375,30 @@ async function ssmParameter(deps, name, decrypt) {
|
|
|
42375
42375
|
if (!value || value === "None") throw new Error(`SSM parameter ${name} is empty`);
|
|
42376
42376
|
return value;
|
|
42377
42377
|
}
|
|
42378
|
-
async function
|
|
42379
|
-
const
|
|
42380
|
-
|
|
42378
|
+
async function postToChannel(deps, channel, text) {
|
|
42379
|
+
const resolved = channel.trim();
|
|
42380
|
+
if (!resolved || resolved === "None") throw new Error("no Slack channel resolved");
|
|
42381
|
+
const token = await readSsmParameter(deps, SSM_TOKEN_PARAM, true);
|
|
42381
42382
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
42382
42383
|
const res = await fetchImpl("https://slack.com/api/chat.postMessage", {
|
|
42383
42384
|
method: "POST",
|
|
42384
42385
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json; charset=utf-8" },
|
|
42385
|
-
body: JSON.stringify({ channel, text, unfurl_links: false }),
|
|
42386
|
+
body: JSON.stringify({ channel: resolved, text, unfurl_links: false }),
|
|
42386
42387
|
signal: AbortSignal.timeout(SLACK_TIMEOUT_MS)
|
|
42387
42388
|
});
|
|
42388
42389
|
const json = await res.json().catch(() => ({}));
|
|
42389
42390
|
if (!json.ok) throw new Error(`slack postMessage failed: ${json.error ?? `http ${res.status}`}`);
|
|
42391
|
+
return { status: "posted", note: "posted" };
|
|
42392
|
+
}
|
|
42393
|
+
async function postToAlertsChannel(deps, text) {
|
|
42394
|
+
const channel = await readSsmParameter(deps, SSM_CHANNEL_PARAM, false);
|
|
42395
|
+
await postToChannel(deps, channel, text);
|
|
42390
42396
|
return { status: "posted", note: "posted to the alerts channel" };
|
|
42391
42397
|
}
|
|
42392
42398
|
|
|
42393
42399
|
// src/release-announce.ts
|
|
42394
42400
|
var ANNOUNCE_REPO = "mutmutco/MMI-Hub";
|
|
42401
|
+
var RELEASE_CHANNEL_KEY = "SLACK_RELEASE_CHANNEL";
|
|
42395
42402
|
var MAX_BULLETS = 6;
|
|
42396
42403
|
var MAX_SUMMARY_LINES = 8;
|
|
42397
42404
|
function neutralizeHubProductBrands(text) {
|
|
@@ -42399,10 +42406,17 @@ function neutralizeHubProductBrands(text) {
|
|
|
42399
42406
|
s = s.replace(/\bdesign-system\s+design-system\b/gi, "design-system");
|
|
42400
42407
|
return s.replace(/\s{2,}/g, " ").trim();
|
|
42401
42408
|
}
|
|
42409
|
+
function releaseChannelParam(repo) {
|
|
42410
|
+
return `/mmi-future/${slugOf(repo)}/${RELEASE_CHANNEL_KEY}`;
|
|
42411
|
+
}
|
|
42412
|
+
function isAbsentParameter(e) {
|
|
42413
|
+
const message2 = e instanceof Error ? e.message : String(e);
|
|
42414
|
+
return /ParameterNotFound/.test(message2) || /is empty$/.test(message2);
|
|
42415
|
+
}
|
|
42402
42416
|
function escapeMrkdwn(text) {
|
|
42403
42417
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
42404
42418
|
}
|
|
42405
|
-
function bulletToLine(raw) {
|
|
42419
|
+
function bulletToLine(raw, neutralize) {
|
|
42406
42420
|
const m = /^[*-]\s+(.*)$/.exec(raw.trim());
|
|
42407
42421
|
if (!m) return null;
|
|
42408
42422
|
let text = m[1].trim();
|
|
@@ -42417,9 +42431,9 @@ function bulletToLine(raw) {
|
|
|
42417
42431
|
text = text.slice(0, tail.index).trim();
|
|
42418
42432
|
}
|
|
42419
42433
|
if (!text) return null;
|
|
42420
|
-
return `\u2022 ${escapeMrkdwn(neutralizeHubProductBrands(text))}${link}`;
|
|
42434
|
+
return `\u2022 ${escapeMrkdwn(neutralize ? neutralizeHubProductBrands(text) : text)}${link}`;
|
|
42421
42435
|
}
|
|
42422
|
-
function curateReleaseNotes(notesMd, maxBullets = MAX_BULLETS) {
|
|
42436
|
+
function curateReleaseNotes(notesMd, maxBullets = MAX_BULLETS, neutralize = true) {
|
|
42423
42437
|
const lines2 = [];
|
|
42424
42438
|
let bullets = 0;
|
|
42425
42439
|
let dropped = 0;
|
|
@@ -42437,7 +42451,7 @@ function curateReleaseNotes(notesMd, maxBullets = MAX_BULLETS) {
|
|
|
42437
42451
|
continue;
|
|
42438
42452
|
}
|
|
42439
42453
|
if (/new contributors/i.test(section)) continue;
|
|
42440
|
-
const line = bulletToLine(raw);
|
|
42454
|
+
const line = bulletToLine(raw, neutralize);
|
|
42441
42455
|
if (!line) continue;
|
|
42442
42456
|
if (bullets >= maxBullets) {
|
|
42443
42457
|
dropped += 1;
|
|
@@ -42463,21 +42477,39 @@ function formatAnnouncement(args) {
|
|
|
42463
42477
|
`<${args.releaseUrl}|Release notes>`
|
|
42464
42478
|
].join("\n");
|
|
42465
42479
|
}
|
|
42466
|
-
function summaryFileLines(content) {
|
|
42467
|
-
return content.split("\n").map((l) => l.trim()).filter(Boolean).slice(0, MAX_SUMMARY_LINES).map((l) =>
|
|
42480
|
+
function summaryFileLines(content, neutralize = true) {
|
|
42481
|
+
return content.split("\n").map((l) => l.trim()).filter(Boolean).slice(0, MAX_SUMMARY_LINES).map((l) => {
|
|
42482
|
+
const bare = l.replace(/^[•*-]\s*/, "");
|
|
42483
|
+
return escapeMrkdwn(neutralize ? neutralizeHubProductBrands(bare) : bare);
|
|
42484
|
+
}).filter(Boolean).map((l) => `\u2022 ${l}`);
|
|
42468
42485
|
}
|
|
42469
|
-
async function
|
|
42470
|
-
if (
|
|
42471
|
-
|
|
42486
|
+
async function resolveAnnounceTarget(deps, repo) {
|
|
42487
|
+
if (repo.toLowerCase() === ANNOUNCE_REPO.toLowerCase()) return { kind: "alerts" };
|
|
42488
|
+
const param = releaseChannelParam(repo);
|
|
42489
|
+
try {
|
|
42490
|
+
const channel = await readSsmParameter(deps, param, false);
|
|
42491
|
+
return { kind: "channel", channel };
|
|
42492
|
+
} catch (e) {
|
|
42493
|
+
if (isAbsentParameter(e)) return { kind: "none", param };
|
|
42494
|
+
throw e;
|
|
42472
42495
|
}
|
|
42496
|
+
}
|
|
42497
|
+
function missingChannelNote(repo) {
|
|
42498
|
+
const slug = slugOf(repo);
|
|
42499
|
+
return `no ${RELEASE_CHANNEL_KEY} declared for ${slug} \u2014 declare it with \`mmi-cli vault secrets declare ${RELEASE_CHANNEL_KEY} --group slack --consumers agent --purpose \u2026\` then \`mmi-cli vault secrets set ${RELEASE_CHANNEL_KEY}\``;
|
|
42500
|
+
}
|
|
42501
|
+
async function announceRelease(deps, args) {
|
|
42502
|
+
const neutralize = args.repo.toLowerCase() === ANNOUNCE_REPO.toLowerCase();
|
|
42473
42503
|
try {
|
|
42504
|
+
const target = await resolveAnnounceTarget(deps, args.repo);
|
|
42505
|
+
if (target.kind === "none") return { status: "skipped", note: missingChannelNote(args.repo) };
|
|
42474
42506
|
const viewRaw = await deps.run("gh", ["release", "view", args.tag, "--repo", args.repo, "--json", "body,url"]);
|
|
42475
42507
|
const view = JSON.parse(viewRaw);
|
|
42476
42508
|
const releaseUrl = view.url ?? `https://github.com/${args.repo}/releases/tag/${args.tag}`;
|
|
42477
42509
|
let lines2 = [];
|
|
42478
42510
|
if (args.summaryFile) {
|
|
42479
42511
|
if (!deps.readFile) throw new Error("summary file given but deps.readFile is missing");
|
|
42480
|
-
lines2 = summaryFileLines(await deps.readFile(args.summaryFile));
|
|
42512
|
+
lines2 = summaryFileLines(await deps.readFile(args.summaryFile), neutralize);
|
|
42481
42513
|
if (deps.removeFile) {
|
|
42482
42514
|
try {
|
|
42483
42515
|
await deps.removeFile(args.summaryFile);
|
|
@@ -42485,11 +42517,15 @@ async function announceRelease(deps, args) {
|
|
|
42485
42517
|
}
|
|
42486
42518
|
}
|
|
42487
42519
|
}
|
|
42488
|
-
if (lines2.length === 0) lines2 = curateReleaseNotes(view.body ?? "");
|
|
42520
|
+
if (lines2.length === 0) lines2 = curateReleaseNotes(view.body ?? "", MAX_BULLETS, neutralize);
|
|
42489
42521
|
if (lines2.length === 0) lines2 = ["\u2022 maintenance release \u2014 see the notes for details"];
|
|
42490
42522
|
const text = formatAnnouncement({ repo: args.repo, tag: args.tag, lines: lines2, releaseUrl });
|
|
42491
|
-
|
|
42492
|
-
|
|
42523
|
+
if (target.kind === "alerts") {
|
|
42524
|
+
await postToAlertsChannel(deps, text);
|
|
42525
|
+
return { status: "announced", note: `announced ${args.tag} to the alerts channel` };
|
|
42526
|
+
}
|
|
42527
|
+
await postToChannel(deps, target.channel, text);
|
|
42528
|
+
return { status: "announced", note: `announced ${args.tag} to the project release channel` };
|
|
42493
42529
|
} catch (e) {
|
|
42494
42530
|
return { status: "failed", note: `announce failed (release unaffected): ${e.message}` };
|
|
42495
42531
|
}
|
|
@@ -43904,7 +43940,8 @@ function trainApplyDeps() {
|
|
|
43904
43940
|
// train lands the back-merge itself instead of leaving a manual step. Same enqueue path `pr land` uses,
|
|
43905
43941
|
// but method `--merge` (NOT squash): the misalignment guard needs the merge parentage to survive.
|
|
43906
43942
|
mergeAuto: (prNumber, repo) => ghMergeAutoEnqueue(prNumber, repo, "--merge"),
|
|
43907
|
-
// Slack release announcement (#883):
|
|
43943
|
+
// Slack release announcement (#883/#6517): the org alerts channel for the Hub, the project's own
|
|
43944
|
+
// SLACK_RELEASE_CHANNEL for a product repo — best-effort inside announceRelease itself.
|
|
43908
43945
|
announce: (args) => announceRelease({
|
|
43909
43946
|
run: async (file, cmdArgs) => (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
|
|
43910
43947
|
readFile: (path2) => (0, import_promises10.readFile)(path2, "utf8"),
|
|
@@ -44168,7 +44205,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44168
44205
|
for (const commandName of ["rcand", "release"]) {
|
|
44169
44206
|
const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "write the terminal result (--apply, --resume, --abort, --retry-publish) to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)");
|
|
44170
44207
|
const RELEASE_ONLY_FLAGS = [
|
|
44171
|
-
{ flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line
|
|
44208
|
+
{ flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line release Slack summary; required for a new MMI-Hub --apply (#883/#3901), optional for a product repo announcing to its project release channel (#6517)" },
|
|
44172
44209
|
{ flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
|
|
44173
44210
|
{ flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
|
|
44174
44211
|
{ flags: "--abort", description: "with --apply, delete only a proven unpublished failed release candidate on any registered deploy model and restore local main for a same-version recut (#3944/#5650/#5657)" },
|
|
@@ -44194,7 +44231,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44194
44231
|
return fail(`${commandName}: --dev applies only to release \u2014 it ships development -> main skipping rc, which rcand cannot do. Run: mmi-cli devops release --dev`);
|
|
44195
44232
|
}
|
|
44196
44233
|
if (o.announceSummaryFile && commandName !== "release") {
|
|
44197
|
-
return fail(`${commandName}: --announce-summary-file applies only to release \u2014 rcand posts no
|
|
44234
|
+
return fail(`${commandName}: --announce-summary-file applies only to release \u2014 rcand posts no release announcement. Run: mmi-cli devops release --announce-summary-file <path>`);
|
|
44198
44235
|
}
|
|
44199
44236
|
if (o.abort && commandName !== "release") {
|
|
44200
44237
|
return fail(`${commandName}: --abort applies only to release \u2014 it rolls back a proven unpublished release tag. Run: mmi-cli devops release --abort --apply`);
|
|
@@ -44446,7 +44483,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44446
44483
|
console.log(o.json ? JSON.stringify({ command: "hotfix", steps }, null, 2) : renderSteps("mmi-cli devops hotfix: dry-run plan", steps));
|
|
44447
44484
|
});
|
|
44448
44485
|
hotfixCmd.command("start").description("cherry-pick one or more merged development PRs (or SHAs) onto hotfix/vX.Y.Z from origin/main, bump the distribution, open the main-base PR").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR number(s) or commit SHA(s) to cherry-pick, in pick order \u2014 one hotfix cycle carries as many fixes as you name (#4411)").option("--json", "machine-readable output").option("--out <path>", "write the result to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)").action(async (o) => runHotfixSub("start", () => runHotfixStart(trainApplyDeps(), { from: o.from }), o, renderHotfixStart));
|
|
44449
|
-
hotfixCmd.command("release <version>").description("after the hotfix PR is merged + checks green: tag, GitHub Release, watch deploy/publish, verify distribution (idempotent)").option("--json", "machine-readable output").option("--announce-summary-file <path>", "agent-curated 3-6 line
|
|
44486
|
+
hotfixCmd.command("release <version>").description("after the hotfix PR is merged + checks green: tag, GitHub Release, watch deploy/publish, verify distribution (idempotent)").option("--json", "machine-readable output").option("--announce-summary-file <path>", "agent-curated 3-6 line release Slack summary; required for a NEW MMI-Hub hotfix Release (#883/#3901/#6068), optional for a product repo announcing to its project release channel (#6517)").option("--carries <pr#|sha[,pr#|sha...]>", "declared fix target(s) this hotfix must carry; each must be proven present before tagging (#3056)").option("--out <path>", "write the result to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)").action(async (version, o) => runHotfixSub("release", async () => {
|
|
44450
44487
|
const result = await runHotfixRelease(trainApplyDeps(), version, { announceSummaryFile: o.announceSummaryFile, carries: o.carries ? [o.carries] : [] });
|
|
44451
44488
|
const postReleaseJervDoctor = await runPostReleaseJervDoctor(result.repo);
|
|
44452
44489
|
const projectInfoSync = await runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo);
|
package/package.json
CHANGED