@mutmutco/cli 4.3.21 → 4.3.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 +88 -24
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -12242,7 +12242,10 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
12242
12242
|
}
|
|
12243
12243
|
return;
|
|
12244
12244
|
}
|
|
12245
|
-
if (!options.force)
|
|
12245
|
+
if (!options.force) {
|
|
12246
|
+
const refusal = laneContestMessage(item.ref, contest, "claim");
|
|
12247
|
+
throw new Error(options.bulk ? refusal : withPowerShellChainHint(refusal));
|
|
12248
|
+
}
|
|
12246
12249
|
previousHolder = displaced();
|
|
12247
12250
|
};
|
|
12248
12251
|
await refuseIfContested();
|
|
@@ -12313,7 +12316,7 @@ async function claimBoardIssues(options, deps = {}) {
|
|
|
12313
12316
|
const selector = selectors[index];
|
|
12314
12317
|
const ref = `${selector.repo}#${selector.number}`;
|
|
12315
12318
|
try {
|
|
12316
|
-
const result = await claimOneBoardItem(ctx, selector, options);
|
|
12319
|
+
const result = await claimOneBoardItem(ctx, selector, { ...options, bulk: true });
|
|
12317
12320
|
results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, outcome: result.outcome, holder: result.holder, previousHolder: result.previousHolder, resumeEvidence: result.resumeEvidence, alreadyClaimed: result.alreadyClaimed, checked: result.checked };
|
|
12318
12321
|
} catch (e) {
|
|
12319
12322
|
results[index] = { ref, claimed: false, reason: e.message };
|
|
@@ -15516,10 +15519,10 @@ var rollout_plan_default = {
|
|
|
15516
15519
|
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)."
|
|
15517
15520
|
},
|
|
15518
15521
|
baseline: {
|
|
15519
|
-
version: "4.3.
|
|
15520
|
-
tag: "v4.3.
|
|
15521
|
-
commit: "
|
|
15522
|
-
npm: "@mutmutco/cli@4.3.
|
|
15522
|
+
version: "4.3.23",
|
|
15523
|
+
tag: "v4.3.23",
|
|
15524
|
+
commit: "64b06ac32d90",
|
|
15525
|
+
npm: "@mutmutco/cli@4.3.23"
|
|
15523
15526
|
},
|
|
15524
15527
|
exitCriterion: "fleet-n-of-n",
|
|
15525
15528
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15536,14 +15539,14 @@ var rollout_plan_default = {
|
|
|
15536
15539
|
repo: "mutmutco/mmi-hub",
|
|
15537
15540
|
role: "canary",
|
|
15538
15541
|
schedule: "train",
|
|
15539
|
-
v3Target: "v4.3.
|
|
15542
|
+
v3Target: "v4.3.23"
|
|
15540
15543
|
}
|
|
15541
15544
|
],
|
|
15542
15545
|
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.",
|
|
15543
15546
|
rollback: {
|
|
15544
15547
|
independent: true,
|
|
15545
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15546
|
-
v3Target: "v4.3.
|
|
15548
|
+
mechanism: "npm dist-tag latest -> 4.3.23 and redeploy the Hub Lambda from tag v4.3.23 (64b06ac32d90); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15549
|
+
v3Target: "v4.3.23 (@mutmutco/cli@4.3.23, tag commit 64b06ac32d90 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15547
15550
|
}
|
|
15548
15551
|
},
|
|
15549
15552
|
{
|
|
@@ -17911,8 +17914,29 @@ async function discoverRequiredCheckContexts(deps, ctx, branch) {
|
|
|
17911
17914
|
}
|
|
17912
17915
|
return [...contexts];
|
|
17913
17916
|
}
|
|
17914
|
-
|
|
17915
|
-
|
|
17917
|
+
function alignmentBranchName(target, tag) {
|
|
17918
|
+
return `release-align/${target}/${tag}`;
|
|
17919
|
+
}
|
|
17920
|
+
async function pushAlignmentBranch(deps, target, tag) {
|
|
17921
|
+
const branch = alignmentBranchName(target, tag);
|
|
17922
|
+
let tree;
|
|
17923
|
+
try {
|
|
17924
|
+
tree = clean2(await deps.run("git", ["merge-tree", "--write-tree", "--no-messages", `origin/${target}`, "origin/main"])).split("\n")[0] ?? "";
|
|
17925
|
+
} catch {
|
|
17926
|
+
const files = await runMergeTreePreflight(deps, `origin/${target}`, "origin/main");
|
|
17927
|
+
throw new Error(
|
|
17928
|
+
`the ${target} alignment of ${tag} could not merge origin/main${files.length ? ` \u2014 conflicts on ${files.join(", ")}` : ""} (#6288). Reconcile it on a branch cut from origin/${target} that merges origin/main, open that against ${target}, and land it with a true merge (\`mmi-cli devops pr merge <n> --auto --merge\`) \u2014 never hand-resolve on main, and never merge ${target} into main.`
|
|
17929
|
+
);
|
|
17930
|
+
}
|
|
17931
|
+
if (!tree) throw new Error(`git merge-tree wrote no tree for the ${target} alignment of ${tag} (#6288)`);
|
|
17932
|
+
const commit = clean2(await deps.run("git", ["commit-tree", tree, "-p", `origin/${target}`, "-p", "origin/main", "-m", `chore(release): align ${target} to ${tag}`]));
|
|
17933
|
+
if (!commit) throw new Error(`git commit-tree wrote no commit for the ${target} alignment of ${tag} (#6288)`);
|
|
17934
|
+
await deps.run("git", ["push", "origin", "--delete", branch]).catch(() => void 0);
|
|
17935
|
+
await runGitPush(deps, ["push", "origin", `${commit}:refs/heads/${branch}`]);
|
|
17936
|
+
return branch;
|
|
17937
|
+
}
|
|
17938
|
+
async function findAlignmentPr(deps, ctx, target, head) {
|
|
17939
|
+
const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", target, "--head", head, "--state", "all", "--json", "number,url,state"]));
|
|
17916
17940
|
if (!out) return void 0;
|
|
17917
17941
|
const rows = JSON.parse(out);
|
|
17918
17942
|
const row = rows.filter((r) => typeof r.number === "number" && typeof r.url === "string" && (r.state === "OPEN" || r.state === "MERGED")).sort((a, b) => b.number - a.number)[0];
|
|
@@ -17933,23 +17957,28 @@ async function rollDevelopmentForward(deps, ctx, tag) {
|
|
|
17933
17957
|
return { status: "pushed", note: "development rolled forward to the released main (development has no required checks)" };
|
|
17934
17958
|
}
|
|
17935
17959
|
const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/development..origin/main"]));
|
|
17936
|
-
const
|
|
17960
|
+
const branch = alignmentBranchName("development", tag);
|
|
17961
|
+
const existing = await findAlignmentPr(deps, ctx, "development", branch);
|
|
17937
17962
|
if (ahead === "0") {
|
|
17963
|
+
const merged = existing ?? await findAlignmentPr(deps, ctx, "development", "main");
|
|
17938
17964
|
return {
|
|
17939
17965
|
status: "aligned",
|
|
17940
17966
|
alignment: "already-merged",
|
|
17941
|
-
note:
|
|
17967
|
+
note: merged?.state === "MERGED" ? `alignment: already-merged \u2014 development already contains main via alignment PR #${merged.number}` : "alignment: already-merged \u2014 development already contains the released main; nothing to roll forward"
|
|
17942
17968
|
};
|
|
17943
17969
|
}
|
|
17944
17970
|
if (existing?.state === "OPEN") {
|
|
17945
17971
|
return enqueueAlignmentAutoMerge(deps, ctx, existing.number, existing.url, `alignment PR already open: ${existing.url}`);
|
|
17946
17972
|
}
|
|
17973
|
+
const superseded = await findAlignmentPr(deps, ctx, "development", "main");
|
|
17974
|
+
const supersededNote = superseded?.state === "OPEN" ? ` (supersedes the main-headed alignment PR #${superseded.number}, which GitHub closes as merged once this lands \u2014 #6288)` : "";
|
|
17975
|
+
await pushAlignmentBranch(deps, "development", tag);
|
|
17947
17976
|
const body = `Carries the ${tag} release (including the version fold) from \`main\` back to \`development\`.
|
|
17948
17977
|
|
|
17949
|
-
\`development\` requires status checks, so the release train opens this alignment PR instead of a direct push of the un-checked merge commit (#1143). Land it with a **true merge** \u2014 \`mmi-cli devops pr merge <n> --auto --merge\` (not squash) so the merge parentage survives and the misalignment guard stays satisfied. \`--auto\` waits out the checks this PR triggers, which otherwise block an immediate merge right after the release.`;
|
|
17950
|
-
const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head",
|
|
17978
|
+
\`development\` requires status checks, so the release train opens this alignment PR instead of a direct push of the un-checked merge commit (#1143). The head is \`development\` with \`main\` merged into it, never \`main\` itself: a strict \`require branches to be up to date\` ruleset can never be satisfied by a main-headed PR, and satisfying it literally would promote every unreleased commit to \`main\` (#6288). Land it with a **true merge** \u2014 \`mmi-cli devops pr merge <n> --auto --merge\` (not squash) so the merge parentage survives and the misalignment guard stays satisfied. \`--auto\` waits out the checks this PR triggers, which otherwise block an immediate merge right after the release.`;
|
|
17979
|
+
const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head", branch, "--title", `chore(release): align development to ${tag}`, "--body", body]));
|
|
17951
17980
|
const number = parsePrNumber(url);
|
|
17952
|
-
return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `development requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}`);
|
|
17981
|
+
return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `development requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}${supersededNote}`);
|
|
17953
17982
|
}
|
|
17954
17983
|
var ALIGNMENT_ARM_ATTEMPTS = 3;
|
|
17955
17984
|
var ALIGNMENT_ARM_BACKOFF_MS = 2e3;
|
|
@@ -17990,23 +18019,28 @@ async function alignRcForward(deps, ctx, tag) {
|
|
|
17990
18019
|
return { status: "pushed", note: "rc aligned to the released main (rc has no required checks)" };
|
|
17991
18020
|
}
|
|
17992
18021
|
const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/rc..origin/main"]));
|
|
17993
|
-
const
|
|
18022
|
+
const branch = alignmentBranchName("rc", tag);
|
|
18023
|
+
const existing = await findAlignmentPr(deps, ctx, "rc", branch);
|
|
17994
18024
|
if (ahead === "0") {
|
|
18025
|
+
const merged = existing ?? await findAlignmentPr(deps, ctx, "rc", "main");
|
|
17995
18026
|
return {
|
|
17996
18027
|
status: "aligned",
|
|
17997
18028
|
alignment: "already-merged",
|
|
17998
|
-
note:
|
|
18029
|
+
note: merged?.state === "MERGED" ? `alignment: already-merged \u2014 rc already contains main via alignment PR #${merged.number}` : "alignment: already-merged \u2014 rc already contains the released main; nothing to align"
|
|
17999
18030
|
};
|
|
18000
18031
|
}
|
|
18001
18032
|
if (existing?.state === "OPEN") {
|
|
18002
18033
|
return enqueueAlignmentAutoMerge(deps, ctx, existing.number, existing.url, `rc alignment PR already open: ${existing.url}`);
|
|
18003
18034
|
}
|
|
18035
|
+
const superseded = await findAlignmentPr(deps, ctx, "rc", "main");
|
|
18036
|
+
const supersededNote = superseded?.state === "OPEN" ? ` (supersedes the main-headed alignment PR #${superseded.number}, which GitHub closes as merged once this lands \u2014 #6288)` : "";
|
|
18037
|
+
await pushAlignmentBranch(deps, "rc", tag);
|
|
18004
18038
|
const body = `Carries the ${tag} release from \`main\` back to \`rc\`.
|
|
18005
18039
|
|
|
18006
|
-
\`rc\` requires status checks, so the release train opens this alignment PR instead of bypassing branch protection with a direct push. Land it with a **true merge** \u2014 \`mmi-cli devops pr merge <n> --auto --merge\` (not squash) so the merge parentage survives.`;
|
|
18007
|
-
const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "rc", "--head",
|
|
18040
|
+
\`rc\` requires status checks, so the release train opens this alignment PR instead of bypassing branch protection with a direct push. The head is \`rc\` with \`main\` merged into it, never \`main\` itself \u2014 a strict \`require branches to be up to date\` ruleset can never be satisfied by a main-headed PR (#6288). Land it with a **true merge** \u2014 \`mmi-cli devops pr merge <n> --auto --merge\` (not squash) so the merge parentage survives.`;
|
|
18041
|
+
const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "rc", "--head", branch, "--title", `chore(release): align rc to ${tag}`, "--body", body]));
|
|
18008
18042
|
const number = parsePrNumber(url);
|
|
18009
|
-
return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `rc requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}`);
|
|
18043
|
+
return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `rc requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}${supersededNote}`);
|
|
18010
18044
|
}
|
|
18011
18045
|
var FLEET_FOREIGN_VERDICT_GRACE_ATTEMPTS = 3;
|
|
18012
18046
|
function resolveContextState(context, checkRuns, statuses) {
|
|
@@ -21549,6 +21583,23 @@ async function runTrainDoctor(input) {
|
|
|
21549
21583
|
derived = deriveReleasePhaseStatus(ledger);
|
|
21550
21584
|
}
|
|
21551
21585
|
}
|
|
21586
|
+
if (derived.legs.some((l) => l.phase === "alignment" && l.state === "pending")) {
|
|
21587
|
+
for (const target of ["development", "rc"].filter((t) => t !== "rc" || track === "full")) {
|
|
21588
|
+
try {
|
|
21589
|
+
const stale = await findAlignmentPr(train, ctx, target, "main");
|
|
21590
|
+
if (stale?.state !== "OPEN") continue;
|
|
21591
|
+
add({
|
|
21592
|
+
code: "alignment-pr-main-headed",
|
|
21593
|
+
severity: "blocker",
|
|
21594
|
+
source: "origin",
|
|
21595
|
+
title: `alignment PR #${stale.number} (\`main\` -> ${target}) is headed by \`main\` \u2014 under a strict "branches must be up to date" ruleset it can never merge (#6288)`,
|
|
21596
|
+
remedy: `rerun \`mmi-cli devops release --resume\`: the train rebuilds the alignment as \`release-align/${target}/<tag>\` (${target} with \`main\` merged into it), which is up to date by construction; GitHub closes #${stale.number} as merged once that lands. Never merge ${target} into \`main\`, never hand-merge on \`main\`, and never add a bypass actor to the strict rule`
|
|
21597
|
+
});
|
|
21598
|
+
} catch (e) {
|
|
21599
|
+
unverified(`the ${target} alignment PR head`, e);
|
|
21600
|
+
}
|
|
21601
|
+
}
|
|
21602
|
+
}
|
|
21552
21603
|
if (derived.phaseStatus !== "phases-green") {
|
|
21553
21604
|
const open2 = derived.legs.filter((l) => l.state === "pending" || l.state === "failed").map((l) => `${l.phase}=${l.state}`).join(", ");
|
|
21554
21605
|
const failed = derived.phaseStatus === "phases-failed";
|
|
@@ -33227,7 +33278,10 @@ function extendCreateCommand(issue, batchAttach) {
|
|
|
33227
33278
|
try {
|
|
33228
33279
|
const raw = (0, import_node_fs31.readFileSync)(opts.batch, "utf8");
|
|
33229
33280
|
specs = JSON.parse(raw);
|
|
33230
|
-
if (!Array.isArray(specs))
|
|
33281
|
+
if (!Array.isArray(specs)) {
|
|
33282
|
+
const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
|
|
33283
|
+
throw new Error(`batch file must contain a JSON array, but its top level is ${top}`);
|
|
33284
|
+
}
|
|
33231
33285
|
} catch (e) {
|
|
33232
33286
|
return fail(`issue create --batch: cannot read/parse ${opts.batch}: ${e.message}`);
|
|
33233
33287
|
}
|
|
@@ -36185,10 +36239,18 @@ function prHeadBehindBase(input) {
|
|
|
36185
36239
|
function prHeadUpdateRemedy(head, base) {
|
|
36186
36240
|
return `git fetch origin ${base} && git merge origin/${base} (on ${head}), resolve the conflicts, push, then rerun \u2014 docs/Guides/train-troubleshooting.md#head-behind-base`;
|
|
36187
36241
|
}
|
|
36242
|
+
var PROTECTED_PR_HEAD_BRANCHES = ["main", "master", "development", "rc"];
|
|
36243
|
+
function isProtectedPrHead(head) {
|
|
36244
|
+
return PROTECTED_PR_HEAD_BRANCHES.includes(head);
|
|
36245
|
+
}
|
|
36246
|
+
function protectedPrHeadRemedy(head, base) {
|
|
36247
|
+
return `PR head \`${head}\` is a train branch \u2014 updating it from \`${base}\` would merge \`${base}\` into \`${head}\`, promoting every unreleased commit. Never do that and never hand-merge on \`${head}\`. This is the pre-#6288 alignment shape: close this PR and let the train rebuild the alignment on a \`${base}\`-based branch that merges \`${head}\` into it (\`mmi-cli devops release --resume\`), then land THAT with a true merge \u2014 docs/Guides/train-troubleshooting.md#head-behind-base`;
|
|
36248
|
+
}
|
|
36188
36249
|
var PR_HEAD_UPDATE_API_READ_RETRIES = 5;
|
|
36189
36250
|
var PR_HEAD_UPDATE_API_READ_DELAY_MS = 2e3;
|
|
36190
36251
|
async function updatePrHeadFromBase(input) {
|
|
36191
36252
|
const remedy = prHeadUpdateRemedy(input.head, input.base);
|
|
36253
|
+
if (isProtectedPrHead(input.head)) throw new Error(protectedPrHeadRemedy(input.head, input.base));
|
|
36192
36254
|
if (input.localCheckedOut) {
|
|
36193
36255
|
const from2 = (await input.git(["rev-parse", "HEAD"])).trim();
|
|
36194
36256
|
await input.git(["fetch", "origin", input.base]);
|
|
@@ -36475,8 +36537,9 @@ var WAIVABLE_KINDS = [
|
|
|
36475
36537
|
var TEST_WORK_KIND = "unrequested-test-file";
|
|
36476
36538
|
var TEST_FILE_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
36477
36539
|
var PY_TEST_FILE_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
36540
|
+
var GO_TEST_FILE_RE = /_test\.go$/;
|
|
36478
36541
|
function isTestPath(path2) {
|
|
36479
|
-
return typeof path2 === "string" && (TEST_FILE_RE.test(path2) || PY_TEST_FILE_RE.test(path2));
|
|
36542
|
+
return typeof path2 === "string" && (TEST_FILE_RE.test(path2) || PY_TEST_FILE_RE.test(path2) || GO_TEST_FILE_RE.test(path2));
|
|
36480
36543
|
}
|
|
36481
36544
|
function editsExistingTest(paths, addedPaths = []) {
|
|
36482
36545
|
const added = new Set(Array.isArray(addedPaths) ? addedPaths : []);
|
|
@@ -36631,6 +36694,7 @@ var HOTFIX_POLICY_REF = "origin/development";
|
|
|
36631
36694
|
var HOTFIX_DIFF_BASE = "origin/main";
|
|
36632
36695
|
var TEST_RE = TEST_FILE_RE;
|
|
36633
36696
|
var PY_TEST_RE = PY_TEST_FILE_RE;
|
|
36697
|
+
var GO_TEST_RE = GO_TEST_FILE_RE;
|
|
36634
36698
|
function translate(glob) {
|
|
36635
36699
|
let out = "";
|
|
36636
36700
|
for (let i = 0; i < glob.length; i++) {
|
|
@@ -36666,7 +36730,7 @@ function globToRegExp2(glob) {
|
|
|
36666
36730
|
return new RegExp(`^${translate(glob)}$`);
|
|
36667
36731
|
}
|
|
36668
36732
|
function isTestPath2(path2) {
|
|
36669
|
-
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
36733
|
+
return TEST_RE.test(path2) || PY_TEST_RE.test(path2) || GO_TEST_RE.test(path2);
|
|
36670
36734
|
}
|
|
36671
36735
|
var SUPPORTED_SOURCE = /\.[cm]?[jt]s$/;
|
|
36672
36736
|
var MAX_ANALYZED_BYTES = 512 * 1024;
|
package/package.json
CHANGED