@mutmutco/cli 4.5.47 → 4.5.49
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 +75 -37
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -17043,10 +17043,10 @@ var rollout_plan_default = {
|
|
|
17043
17043
|
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)."
|
|
17044
17044
|
},
|
|
17045
17045
|
baseline: {
|
|
17046
|
-
version: "4.5.
|
|
17047
|
-
tag: "v4.5.
|
|
17048
|
-
commit: "
|
|
17049
|
-
npm: "@mutmutco/cli@4.5.
|
|
17046
|
+
version: "4.5.49",
|
|
17047
|
+
tag: "v4.5.49",
|
|
17048
|
+
commit: "b572aadbcfd3",
|
|
17049
|
+
npm: "@mutmutco/cli@4.5.49"
|
|
17050
17050
|
},
|
|
17051
17051
|
exitCriterion: "fleet-n-of-n",
|
|
17052
17052
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17063,14 +17063,14 @@ var rollout_plan_default = {
|
|
|
17063
17063
|
repo: "mutmutco/mmi-hub",
|
|
17064
17064
|
role: "canary",
|
|
17065
17065
|
schedule: "train",
|
|
17066
|
-
v3Target: "v4.5.
|
|
17066
|
+
v3Target: "v4.5.49"
|
|
17067
17067
|
}
|
|
17068
17068
|
],
|
|
17069
17069
|
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.",
|
|
17070
17070
|
rollback: {
|
|
17071
17071
|
independent: true,
|
|
17072
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17073
|
-
v3Target: "v4.5.
|
|
17072
|
+
mechanism: "npm dist-tag latest -> 4.5.49 and redeploy the Hub Lambda from tag v4.5.49 (b572aadbcfd3); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17073
|
+
v3Target: "v4.5.49 (@mutmutco/cli@4.5.49, tag commit b572aadbcfd3 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17074
17074
|
}
|
|
17075
17075
|
},
|
|
17076
17076
|
{
|
|
@@ -35039,13 +35039,25 @@ var SCHEMA = "mmi-suite-cut/v1";
|
|
|
35039
35039
|
var SHA2 = /^[a-f0-9]{40}$/i;
|
|
35040
35040
|
var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
|
|
35041
35041
|
var RELEASE_TAG = /^v\d+\.\d+\.\d+$/;
|
|
35042
|
-
function suitePins(record) {
|
|
35042
|
+
function suitePins(record, bundles) {
|
|
35043
35043
|
return record.members.map((m) => m.carried ? { module: m.id, rc: m.rcPin, main: m.mainPin } : {
|
|
35044
35044
|
module: m.id,
|
|
35045
35045
|
rc: `https://${m.id === "katip" ? m.rcHost ?? m.host : m.host}/${m.id === "katip" ? "" : "rc/"}module.js?v=${m.tag}`,
|
|
35046
|
-
main:
|
|
35046
|
+
main: mainPinFor(m.host, bundles[m.id], suiteReleaseTag(m.tag))
|
|
35047
35047
|
});
|
|
35048
35048
|
}
|
|
35049
|
+
function mainPinFor(host, bundles, tag) {
|
|
35050
|
+
const path2 = [bundles?.dev, bundles?.main].map(pathOf).find(Boolean) ?? "/module.js";
|
|
35051
|
+
return `https://${host}${path2}?v=${tag}`;
|
|
35052
|
+
}
|
|
35053
|
+
function pathOf(url) {
|
|
35054
|
+
try {
|
|
35055
|
+
return url ? new URL(url).pathname : void 0;
|
|
35056
|
+
} catch {
|
|
35057
|
+
return void 0;
|
|
35058
|
+
}
|
|
35059
|
+
}
|
|
35060
|
+
var bundlesOf = (members) => Object.fromEntries(members.map((m) => [m.id, m.bundles]));
|
|
35049
35061
|
function suiteReleaseTag(rc) {
|
|
35050
35062
|
if (!RC_TAG.test(rc)) throw new Error(`invalid rc tag ${rc}`);
|
|
35051
35063
|
return rc.replace(/-rc\.\d+$/, "");
|
|
@@ -35091,19 +35103,24 @@ function pinVersion(url) {
|
|
|
35091
35103
|
async function carriedPins(deps, member, bundles) {
|
|
35092
35104
|
const main = bundles.main;
|
|
35093
35105
|
if (!main || !RELEASE_TAG.test(pinVersion(main))) throw new Error(`${member.repo}: nothing new since rc, but its MM-Work main pin ${main ?? "(none)"} has no ?v=vX.Y.Z release; pin it before the suite cut`);
|
|
35106
|
+
if (!await deps.tagSha(member.repo, pinVersion(main))) throw new Error(`${member.repo}: nothing new since rc, but its MM-Work main pin names ${pinVersion(main)}, which was never released; release it or pin its live release before the suite cut`);
|
|
35107
|
+
const mainPin = mainPinFor(new URL(main).host, bundles, pinVersion(main));
|
|
35094
35108
|
for (const url of [bundles.rc, main]) {
|
|
35095
35109
|
const tag = pinVersion(url);
|
|
35096
35110
|
if (!RC_TAG.test(tag) && !RELEASE_TAG.test(tag)) continue;
|
|
35097
35111
|
const sha = await deps.tagSha(member.repo, tag);
|
|
35098
|
-
if (sha && await deps.contains(member.repo, member.development, sha)) return { rcPin: url
|
|
35112
|
+
if (sha && await deps.contains(member.repo, member.development, sha)) return { rcPin: url === main ? mainPin : url, mainPin };
|
|
35099
35113
|
}
|
|
35100
35114
|
throw new Error(`${member.repo}: nothing new since rc, but no pinned MM-Work bundle carries development ${member.development.slice(0, 12)}; pin it before the suite cut`);
|
|
35101
35115
|
}
|
|
35102
|
-
function suiteAbandon(recordPath) {
|
|
35116
|
+
function suiteAbandon(recordPath, ownerGo = false) {
|
|
35103
35117
|
const record = readSuiteCut(recordPath);
|
|
35104
35118
|
if (!record) throw new Error("no suite cut record to abandon");
|
|
35105
|
-
if (record.
|
|
35106
|
-
throw new Error(
|
|
35119
|
+
if (record.leadReleased || record.members.some((m) => m.released)) {
|
|
35120
|
+
throw new Error("the cut already released a member or the lead; finish it with suite release --owner-go, never abandon it");
|
|
35121
|
+
}
|
|
35122
|
+
if (record.pinPr && !ownerGo) {
|
|
35123
|
+
throw new Error(`the cut already opened pin PR ${record.pinPr}; its pins may be visible, so resume it (suite rcand --owner-go), release it (suite release), or retire it with suite abandon --owner-go`);
|
|
35107
35124
|
}
|
|
35108
35125
|
const archive = `${recordPath}.abandoned-${record.leadDevelopment.slice(0, 12)}.json`;
|
|
35109
35126
|
if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
|
|
@@ -35152,7 +35169,15 @@ async function suiteRcand(deps, opts) {
|
|
|
35152
35169
|
throw new Error("suite roster changed since cut began; refuse to omit or add a member");
|
|
35153
35170
|
const quiet = [];
|
|
35154
35171
|
for (const member of record.members) {
|
|
35155
|
-
if (
|
|
35172
|
+
if (member.tag || member.carried || !await deps.contains(member.repo, member.development, member.previousRc)) continue;
|
|
35173
|
+
const rcTag = pinVersion(modules.find((m) => m.repo === member.repo).bundles.rc);
|
|
35174
|
+
if (RC_TAG.test(rcTag) && (await deps.tagSha(member.repo, rcTag))?.toLowerCase() === member.previousRc.toLowerCase() && !await deps.tagSha(member.repo, suiteReleaseTag(rcTag))) {
|
|
35175
|
+
member.tag = rcTag;
|
|
35176
|
+
member.sha = member.previousRc;
|
|
35177
|
+
writeSuiteCut(deps.recordPath, record);
|
|
35178
|
+
continue;
|
|
35179
|
+
}
|
|
35180
|
+
quiet.push(member);
|
|
35156
35181
|
}
|
|
35157
35182
|
if (record.members.every((m) => m.carried || quiet.includes(m))) {
|
|
35158
35183
|
(0, import_node_fs29.rmSync)(deps.recordPath, { force: true });
|
|
@@ -35164,7 +35189,7 @@ async function suiteRcand(deps, opts) {
|
|
|
35164
35189
|
}
|
|
35165
35190
|
const refusals = [];
|
|
35166
35191
|
for (const member of record.members) {
|
|
35167
|
-
if (member.
|
|
35192
|
+
if (member.published || member.id === "katip") continue;
|
|
35168
35193
|
try {
|
|
35169
35194
|
await deps.preflight(member.repo);
|
|
35170
35195
|
} catch (e) {
|
|
@@ -35181,7 +35206,10 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
|
|
|
35181
35206
|
for (const member of record.members) {
|
|
35182
35207
|
if ((await deps.head(member.repo, "development"))?.toLowerCase() !== member.development.toLowerCase())
|
|
35183
35208
|
throw new Error(`${member.repo}: development moved after the cut snapshot; refuse to add untested code`);
|
|
35184
|
-
if (member.carried)
|
|
35209
|
+
if (member.carried) {
|
|
35210
|
+
if (member.id !== "katip") await deps.publish(member.repo, pinVersion(member.rcPin));
|
|
35211
|
+
continue;
|
|
35212
|
+
}
|
|
35185
35213
|
if (!member.tag) {
|
|
35186
35214
|
const cut = await deps.rcand(member.repo, member.previousRc, member.rcWasAbsent);
|
|
35187
35215
|
if (!RC_TAG.test(cut.tag) || !SHA2.test(cut.sha) || cut.sha.toLowerCase() === member.previousRc.toLowerCase()) throw new Error(`${member.repo}: rcand did not return a new immutable rc tag/SHA`);
|
|
@@ -35205,7 +35233,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
|
|
|
35205
35233
|
if (live.version !== member.backend || devHead?.toLowerCase() !== member.development.toLowerCase())
|
|
35206
35234
|
throw new Error(`${member.repo}: live backend or development changed during the cut; pins unchanged`);
|
|
35207
35235
|
}
|
|
35208
|
-
const pins = suitePins(record);
|
|
35236
|
+
const pins = suitePins(record, bundlesOf(modules));
|
|
35209
35237
|
await deps.verifyLeadCut(record.leadDevelopment);
|
|
35210
35238
|
const pr = await deps.pin({ cut: record.leadDevelopment, pins });
|
|
35211
35239
|
await deps.verifyLeadCut(record.leadDevelopment);
|
|
@@ -35230,7 +35258,7 @@ async function suiteRelease(deps, ownerGo) {
|
|
|
35230
35258
|
if (!record?.pinMerged || !record.leadRcTag) throw new Error("no completed suite rc cut and merged pin PR; release refused");
|
|
35231
35259
|
const members = suiteMembers(await deps.registry()).filter((m) => m.repo !== SUITE_REPO);
|
|
35232
35260
|
if (members.length !== record.members.length || members.some((m) => !record.members.some((r) => r.repo === m.repo && r.id === m.id))) throw new Error("suite roster changed after the cut; release refused");
|
|
35233
|
-
await deps.verifyPins(suitePins(record));
|
|
35261
|
+
await deps.verifyPins(suitePins(record, bundlesOf(members)), record.leadRcTag);
|
|
35234
35262
|
for (const member of record.members) {
|
|
35235
35263
|
if (member.carried) {
|
|
35236
35264
|
const live2 = await deps.backend({ repo: member.repo, id: member.id, bundles: {} });
|
|
@@ -38586,8 +38614,8 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
38586
38614
|
const ids = input.pins.map((p) => p.module);
|
|
38587
38615
|
if (new Set(ids).size !== ids.length) throw new Error("suite pin PR contains a duplicate module");
|
|
38588
38616
|
const branch = `suite-pin/cut-${input.cut.slice(0, 16)}`;
|
|
38589
|
-
const registry2 = async () => {
|
|
38590
|
-
const get = JSON.parse(await deps.run("gh", ["api", `repos/${SUITE_REPO}/contents/seed/modules.json?ref
|
|
38617
|
+
const registry2 = async (ref = "development") => {
|
|
38618
|
+
const get = JSON.parse(await deps.run("gh", ["api", `repos/${SUITE_REPO}/contents/seed/modules.json?ref=${encodeURIComponent(ref)}`]));
|
|
38591
38619
|
if (!get.content) throw new Error("MM-Work seed/modules.json is unreadable");
|
|
38592
38620
|
return JSON.parse(Buffer.from(get.content, "base64").toString("utf8"));
|
|
38593
38621
|
};
|
|
@@ -38611,25 +38639,35 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
38611
38639
|
})) throw new Error(`${branch} merged but development pins differ from this cut`);
|
|
38612
38640
|
return { status: "merged", pr: prs[0].url };
|
|
38613
38641
|
}
|
|
38614
|
-
|
|
38615
|
-
|
|
38616
|
-
const row = rows.find((r) => r.id === p.module);
|
|
38642
|
+
const matches = (r) => input.pins.every((p) => {
|
|
38643
|
+
const row = r.find((x) => x.id === p.module);
|
|
38617
38644
|
return row?.bundles.rc === p.rc && row?.bundles.main === p.main;
|
|
38618
|
-
})
|
|
38645
|
+
});
|
|
38646
|
+
if (prs.length && matches(await registry2(branch))) return { status: "pending", pr: prs[0].url };
|
|
38647
|
+
if (!prs.length && matches(rows)) throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
|
|
38619
38648
|
await (0, import_promises5.mkdir)((0, import_node_path34.join)(deps.root, "tmp"), { recursive: true });
|
|
38620
38649
|
scratch = await (0, import_promises5.mkdtemp)((0, import_node_path34.join)(deps.root, "tmp", "suite-pin-"));
|
|
38621
38650
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
38622
|
-
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
38623
|
-
if (
|
|
38624
|
-
|
|
38625
|
-
|
|
38651
|
+
await run("git", ["clone", "--quiet", "--branch", prs.length ? branch : "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
38652
|
+
if (prs.length) {
|
|
38653
|
+
if ((await run("git", ["merge-base", "HEAD", `${input.cut}`]).catch(() => "")).trim().toLowerCase() !== input.cut.toLowerCase())
|
|
38654
|
+
throw new Error(`${branch} no longer builds on the cut ${input.cut.slice(0, 12)}; pin PR left unchanged`);
|
|
38655
|
+
} else {
|
|
38656
|
+
if ((await run("git", ["rev-parse", "HEAD"])).trim().toLowerCase() !== input.cut.toLowerCase())
|
|
38657
|
+
throw new Error("MM-Work development moved after the suite snapshot; no pin PR opened");
|
|
38658
|
+
await run("git", ["switch", "-c", branch]);
|
|
38659
|
+
}
|
|
38626
38660
|
for (const stage of ["rc", "main"]) {
|
|
38627
38661
|
const path2 = (0, import_node_path34.join)(scratch, `${stage}-pins.json`);
|
|
38628
38662
|
await (0, import_promises5.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
|
|
38629
38663
|
await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
|
|
38630
38664
|
}
|
|
38631
38665
|
await run("git", ["add", "seed/modules.json"]);
|
|
38632
|
-
await run("git", ["commit", "-m", `Pin MM-Work suite candidate ${input.cut.slice(0, 12)} (#7262)`]);
|
|
38666
|
+
await run("git", ["commit", "-m", prs.length ? `Correct MM-Work suite candidate pins ${input.cut.slice(0, 12)} (#7450)` : `Pin MM-Work suite candidate ${input.cut.slice(0, 12)} (#7262)`]);
|
|
38667
|
+
if (prs.length) {
|
|
38668
|
+
await run("git", ["push", "origin", branch]);
|
|
38669
|
+
return { status: "pending", pr: prs[0].url };
|
|
38670
|
+
}
|
|
38633
38671
|
await run("git", ["push", "origin", branch]);
|
|
38634
38672
|
const pr = (await run("gh", [
|
|
38635
38673
|
"pr",
|
|
@@ -45303,8 +45341,8 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45303
45341
|
return "";
|
|
45304
45342
|
}
|
|
45305
45343
|
};
|
|
45306
|
-
const registry2 = async () => {
|
|
45307
|
-
const file = await defaultGitHubClient().rest("GET", `repos/${SUITE_REPO}/contents/seed/modules.json?ref
|
|
45344
|
+
const registry2 = async (ref = "development") => {
|
|
45345
|
+
const file = await defaultGitHubClient().rest("GET", `repos/${SUITE_REPO}/contents/seed/modules.json?ref=${encodeURIComponent(ref)}`);
|
|
45308
45346
|
if (!file?.content) throw new Error("MM-Work registry unreadable");
|
|
45309
45347
|
const parsed = JSON.parse(decodeGitHubContents(file.content));
|
|
45310
45348
|
suiteMembers(parsed);
|
|
@@ -45344,12 +45382,12 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45344
45382
|
if (delta.ahead_by !== 1 || delta.behind_by !== 0 || delta.files?.length !== 1 || delta.files[0] !== "seed/modules.json")
|
|
45345
45383
|
throw new Error("MM-Work development changed outside the one suite pin commit after the cut snapshot; refuse to cut an untested lead");
|
|
45346
45384
|
},
|
|
45347
|
-
verifyPins: async (pins) => {
|
|
45348
|
-
const rows = await registry2();
|
|
45385
|
+
verifyPins: async (pins, ref) => {
|
|
45386
|
+
const rows = await registry2(ref);
|
|
45349
45387
|
for (const pin of pins) {
|
|
45350
45388
|
const row = rows.find((r) => r.id === pin.module);
|
|
45351
45389
|
if (row?.bundles.rc !== pin.rc || row?.bundles.main !== pin.main)
|
|
45352
|
-
throw new Error(`${pin.module}: MM-Work
|
|
45390
|
+
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`);
|
|
45353
45391
|
}
|
|
45354
45392
|
},
|
|
45355
45393
|
release,
|
|
@@ -45392,19 +45430,19 @@ function registerSuiteCommands(program3) {
|
|
|
45392
45430
|
return fail(`suite rcand: ${e.message}`);
|
|
45393
45431
|
}
|
|
45394
45432
|
});
|
|
45395
|
-
suite.command("abandon").description("retire an unfinished cut
|
|
45433
|
+
suite.command("abandon").description("retire an unfinished cut: archive the record; member rc tags stay (#7396). A cut whose pin PR landed but released nothing needs --owner-go (#7450)").option("--owner-go", "explicit owner approval to retire a cut whose pin PR already landed").option("--json", "structured receipt").action(async (o) => {
|
|
45396
45434
|
try {
|
|
45397
45435
|
const root = await leadRoot();
|
|
45398
45436
|
const unlock = await acquireTrainWriterLock(root, "suite abandon");
|
|
45399
45437
|
let result;
|
|
45400
45438
|
try {
|
|
45401
|
-
result = suiteAbandon(suiteTrainIo(root).recordPath);
|
|
45439
|
+
result = suiteAbandon(suiteTrainIo(root).recordPath, o.ownerGo === true);
|
|
45402
45440
|
} finally {
|
|
45403
45441
|
await unlock();
|
|
45404
45442
|
}
|
|
45405
45443
|
console.log(o.json ? JSON.stringify(result, null, 2) : [
|
|
45406
45444
|
`suite abandon: archived the cut record to ${result.archive}`,
|
|
45407
|
-
...result.orphanTags.length ? [" member rc tags left in place (immutable; the next rc
|
|
45445
|
+
...result.orphanTags.length ? [" member rc tags left in place (immutable; the next cut reuses one still unreleased on its rc head, #7450):", ...result.orphanTags.map((t) => ` - ${t}`)] : [" no member rc tag was minted"],
|
|
45408
45446
|
" the next suite rcand --owner-go re-snapshots every member"
|
|
45409
45447
|
].join("\n"));
|
|
45410
45448
|
} catch (e) {
|
package/package.json
CHANGED