@mutmutco/cli 3.105.6 → 3.105.8
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 +177 -43
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -11392,11 +11392,11 @@ async function installAppFoldDeps(deps) {
|
|
|
11392
11392
|
const hasLock = await deps.run("git", ["cat-file", "-e", "HEAD:package-lock.json"]).then(() => true).catch(() => false);
|
|
11393
11393
|
await deps.run("npm", hasLock ? ["ci"] : ["install"]);
|
|
11394
11394
|
}
|
|
11395
|
-
async function foldReleaseVersion(deps, model, tag, foldPaths) {
|
|
11395
|
+
async function foldReleaseVersion(deps, model, tag, foldPaths, sourceCommit = "HEAD") {
|
|
11396
11396
|
if (foldPaths.length === 0) return "no version manifest to fold \u2014 the tag is the version";
|
|
11397
11397
|
const version = tag.replace(/^v/, "");
|
|
11398
11398
|
if (model === "hub-serverless") {
|
|
11399
|
-
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit",
|
|
11399
|
+
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", sourceCommit]);
|
|
11400
11400
|
} else {
|
|
11401
11401
|
await installAppFoldDeps(deps);
|
|
11402
11402
|
await deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]);
|
|
@@ -20516,7 +20516,14 @@ async function runHotfixStart(deps, options) {
|
|
|
20516
20516
|
notes.push(`deleted stale origin/${branch} left by the incomplete train`);
|
|
20517
20517
|
}
|
|
20518
20518
|
}
|
|
20519
|
-
const
|
|
20519
|
+
const specs = splitCarrySpecs([options.from]);
|
|
20520
|
+
if (specs.length === 0) throw new Error("hotfix start: --from named no PR or SHA");
|
|
20521
|
+
const sources = [];
|
|
20522
|
+
for (const spec of specs) {
|
|
20523
|
+
const resolved = await resolveHotfixSource(deps, ctx, spec);
|
|
20524
|
+
if (!sources.some((s) => s.sha === resolved.sha)) sources.push(resolved);
|
|
20525
|
+
}
|
|
20526
|
+
const label = sources.map((s) => s.label).join(", ");
|
|
20520
20527
|
const foldPaths = deployModel === "hub-serverless" || deployModel === "registry-publish" ? await resolveFoldPaths(deps, deployModel) : [];
|
|
20521
20528
|
const pickTolerated = deployModel === "hub-serverless" ? [...foldPaths, ...HOTFIX_SKILL_TOLERATED_ROOTS] : foldPaths;
|
|
20522
20529
|
if (deployModel === "hub-serverless") {
|
|
@@ -20532,9 +20539,11 @@ async function runHotfixStart(deps, options) {
|
|
|
20532
20539
|
const preexistingLocal = clean3(await deps.run("git", ["branch", "--list", branch]));
|
|
20533
20540
|
await deps.run("git", ["checkout", "-B", branch, "origin/main"]);
|
|
20534
20541
|
try {
|
|
20535
|
-
const
|
|
20536
|
-
|
|
20537
|
-
|
|
20542
|
+
for (const source of sources) {
|
|
20543
|
+
const autoResolved = await cherryPickWithToleratedPaths(deps, source.sha, pickTolerated);
|
|
20544
|
+
if (autoResolved.length > 0) {
|
|
20545
|
+
notes.push(`auto-resolved regenerable cherry-pick conflict(s) for ${source.label}: ${autoResolved.join(", ")} (regenerated in bump step)`);
|
|
20546
|
+
}
|
|
20538
20547
|
}
|
|
20539
20548
|
} catch (e) {
|
|
20540
20549
|
const recovery = await restoreAfterFailedPort(deps, { startBranch, branch, created: !preexistingLocal });
|
|
@@ -20575,10 +20584,12 @@ async function runHotfixStart(deps, options) {
|
|
|
20575
20584
|
branch,
|
|
20576
20585
|
"--title",
|
|
20577
20586
|
`[hotfix] ${tag}`,
|
|
20587
|
+
// Every picked sha goes in the marker (#4411) — `hotfix release` reads it when --carries is omitted,
|
|
20588
|
+
// so a multi-fix cycle proves ALL of its targets present before tagging, not just the first.
|
|
20578
20589
|
"--body",
|
|
20579
20590
|
`Hotfix ${tag}: cherry-pick of ${label} onto origin/main${bumpNote}.
|
|
20580
20591
|
|
|
20581
|
-
<!-- mmi-hotfix-carries: ${sha} -->
|
|
20592
|
+
<!-- mmi-hotfix-carries: ${sources.map((s) => s.sha).join(",")} -->
|
|
20582
20593
|
|
|
20583
20594
|
Merge this PR (human-initiated), then run \`mmi-cli hotfix release ${tag}\`.`
|
|
20584
20595
|
]));
|
|
@@ -20621,6 +20632,61 @@ async function watchReleaseRun(deps, ctx, workflow, sha) {
|
|
|
20621
20632
|
}
|
|
20622
20633
|
return { workflow, conclusion: "not-found" };
|
|
20623
20634
|
}
|
|
20635
|
+
function devFoldBranch(tag) {
|
|
20636
|
+
return `hotfix-fold/${tag}`;
|
|
20637
|
+
}
|
|
20638
|
+
async function portFoldToDevelopment(deps, ctx, deployModel, tag) {
|
|
20639
|
+
const foldPaths = deployModel === "hub-serverless" || deployModel === "registry-publish" ? await resolveFoldPaths(deps, deployModel) : [];
|
|
20640
|
+
if (foldPaths.length === 0) return `fold port skipped (deployModel=${deployModel} folds no version manifest)`;
|
|
20641
|
+
const branch = devFoldBranch(tag);
|
|
20642
|
+
const listed = await deps.run("gh", [
|
|
20643
|
+
"pr",
|
|
20644
|
+
"list",
|
|
20645
|
+
"--repo",
|
|
20646
|
+
ctx.repo,
|
|
20647
|
+
"--head",
|
|
20648
|
+
branch,
|
|
20649
|
+
"--base",
|
|
20650
|
+
"development",
|
|
20651
|
+
"--state",
|
|
20652
|
+
"all",
|
|
20653
|
+
"--limit",
|
|
20654
|
+
"10",
|
|
20655
|
+
"--json",
|
|
20656
|
+
"number,state,url"
|
|
20657
|
+
]);
|
|
20658
|
+
const existing = JSON.parse(listed || "[]").filter((r) => r.state === "OPEN" || r.state === "MERGED").sort((a, b) => (b.number ?? 0) - (a.number ?? 0))[0];
|
|
20659
|
+
if (existing) return `development fold PR #${existing.number} for ${tag} is ${existing.state} \u2014 reused`;
|
|
20660
|
+
const previousRef = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
20661
|
+
try {
|
|
20662
|
+
await deps.run("git", ["fetch", "origin", "development"]);
|
|
20663
|
+
await deps.run("git", ["checkout", "-B", branch, "origin/development"]);
|
|
20664
|
+
const durableSource = clean3(await deps.run("git", ["merge-base", "HEAD", "origin/development"]));
|
|
20665
|
+
const foldNote = await foldReleaseVersion(deps, deployModel, tag, foldPaths, durableSource);
|
|
20666
|
+
const committed = clean3(await deps.run("git", ["rev-list", "--count", "origin/development..HEAD"]));
|
|
20667
|
+
if (committed === "0") return `development already carries ${tag} \u2014 no fold PR needed (${foldNote})`;
|
|
20668
|
+
await deps.run("git", ["push", "-u", "origin", branch]);
|
|
20669
|
+
const prUrl = clean3(await deps.run("gh", [
|
|
20670
|
+
"pr",
|
|
20671
|
+
"create",
|
|
20672
|
+
"--repo",
|
|
20673
|
+
ctx.repo,
|
|
20674
|
+
"--base",
|
|
20675
|
+
"development",
|
|
20676
|
+
"--head",
|
|
20677
|
+
branch,
|
|
20678
|
+
"--title",
|
|
20679
|
+
`chore(release): port the ${tag} version fold to development`,
|
|
20680
|
+
"--body",
|
|
20681
|
+
`Regenerated version fold for ${tag} (#4410). \`main\` carries the released version; without this development declares the previous one and every PR into it fails catalog-lockstep.
|
|
20682
|
+
|
|
20683
|
+
Generated by \`mmi-cli hotfix release\` \u2014 no main-parented commit is merged in, so the squash parents stay clean (#4365/#4371).`
|
|
20684
|
+
]));
|
|
20685
|
+
return `opened development fold PR ${prUrl} (${foldNote})`;
|
|
20686
|
+
} finally {
|
|
20687
|
+
if (previousRef && previousRef !== "HEAD") await deps.run("git", ["checkout", previousRef]).catch(() => void 0);
|
|
20688
|
+
}
|
|
20689
|
+
}
|
|
20624
20690
|
async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
20625
20691
|
const ctx = await buildTrainApplyContext(deps);
|
|
20626
20692
|
const deployModel = await resolveHotfixDeployModel(deps, ctx);
|
|
@@ -20746,6 +20812,12 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
20746
20812
|
} else {
|
|
20747
20813
|
verifyNote = `distribution verify skipped (deployModel=${deployModel}, Hub-only step)`;
|
|
20748
20814
|
}
|
|
20815
|
+
let foldNote;
|
|
20816
|
+
try {
|
|
20817
|
+
foldNote = await portFoldToDevelopment(deps, ctx, deployModel, tag);
|
|
20818
|
+
} catch (e) {
|
|
20819
|
+
foldNote = `development fold port FAILED: ${e.message ?? e} \u2014 the release stands; port it by hand: git checkout -B ${devFoldBranch(tag)} origin/development && node scripts/release-distribution.mjs prepare ${version}, then open a development-base PR`;
|
|
20820
|
+
}
|
|
20749
20821
|
return {
|
|
20750
20822
|
...ctx,
|
|
20751
20823
|
command: "hotfix-release",
|
|
@@ -20758,6 +20830,7 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
20758
20830
|
runs,
|
|
20759
20831
|
deployNote,
|
|
20760
20832
|
verifyNote,
|
|
20833
|
+
foldNote,
|
|
20761
20834
|
announceNote
|
|
20762
20835
|
};
|
|
20763
20836
|
}
|
|
@@ -29109,6 +29182,21 @@ function derivePollState(buckets) {
|
|
|
29109
29182
|
if (anyFailure) return anyPending ? "failing" : "failure";
|
|
29110
29183
|
return anyPending ? "pending" : "success";
|
|
29111
29184
|
}
|
|
29185
|
+
function partitionByRequired(entries, requiredContexts) {
|
|
29186
|
+
if (!requiredContexts || requiredContexts.size === 0) {
|
|
29187
|
+
return { relevant: entries.map((e) => e.bucket), ignoredFailures: [] };
|
|
29188
|
+
}
|
|
29189
|
+
const relevant = [];
|
|
29190
|
+
const ignoredFailures = [];
|
|
29191
|
+
for (const entry of entries) {
|
|
29192
|
+
if (requiredContexts.has(entry.name)) {
|
|
29193
|
+
relevant.push(entry.bucket);
|
|
29194
|
+
} else if (entry.bucket === "fail") {
|
|
29195
|
+
ignoredFailures.push(entry.name);
|
|
29196
|
+
}
|
|
29197
|
+
}
|
|
29198
|
+
return { relevant, ignoredFailures };
|
|
29199
|
+
}
|
|
29112
29200
|
function parseNdjsonLines(stdout) {
|
|
29113
29201
|
return stdout.split(/\r?\n/).filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
29114
29202
|
}
|
|
@@ -29147,17 +29235,22 @@ async function fetchHeadCheckRuns(headSha, repo, gh) {
|
|
|
29147
29235
|
const runsOut = await gh(["--paginate", `repos/${repo}/commits/${headSha}/check-runs?per_page=100`, "--jq", ".check_runs[] | {id, name, status, conclusion, app_id: .app.id}"]);
|
|
29148
29236
|
return dedupeLatestCheckRuns(parseNdjsonLines(runsOut));
|
|
29149
29237
|
}
|
|
29150
|
-
async function
|
|
29238
|
+
async function fetchHeadCheckEntries(headSha, repo, gh) {
|
|
29151
29239
|
const [runs, statusesOut] = await Promise.all([
|
|
29152
29240
|
fetchHeadCheckRuns(headSha, repo, gh),
|
|
29153
29241
|
gh(["--paginate", `repos/${repo}/commits/${headSha}/status?per_page=100`, "--jq", ".statuses[] | {context, state}"])
|
|
29154
29242
|
]);
|
|
29155
29243
|
const statuses = parseNdjsonLines(statusesOut);
|
|
29156
|
-
return [
|
|
29244
|
+
return [
|
|
29245
|
+
...runs.map((run) => ({ name: run.name ?? `check-run ${run.id ?? "unknown"}`, bucket: classifyCheckRun(run) })),
|
|
29246
|
+
...statuses.map((s) => ({ name: s.context ?? "unknown-status", bucket: classifyCommitStatus(s.state) }))
|
|
29247
|
+
];
|
|
29157
29248
|
}
|
|
29158
|
-
async function pollRestPrChecks(prNumber, repo, gh = defaultGhApi) {
|
|
29249
|
+
async function pollRestPrChecks(prNumber, repo, gh = defaultGhApi, requiredContexts) {
|
|
29159
29250
|
const snapshot = await fetchRestPrSnapshot(prNumber, repo, gh);
|
|
29160
|
-
|
|
29251
|
+
const entries = await fetchHeadCheckEntries(snapshot.headSha, repo, gh);
|
|
29252
|
+
const { relevant } = partitionByRequired(entries, requiredContexts);
|
|
29253
|
+
return derivePollState(relevant);
|
|
29161
29254
|
}
|
|
29162
29255
|
async function pollRestPrMergeable(prNumber, repo, gh = defaultGhApi) {
|
|
29163
29256
|
try {
|
|
@@ -29258,6 +29351,37 @@ async function diagnoseFailedRestChecks(prNumber, repo, gh = defaultGhApi) {
|
|
|
29258
29351
|
return null;
|
|
29259
29352
|
}
|
|
29260
29353
|
}
|
|
29354
|
+
function isNotFoundError2(e) {
|
|
29355
|
+
const msg = `${e?.message ?? e} ${String(e?.stderr ?? "")}`;
|
|
29356
|
+
return /HTTP 404|Not Found|\(404\)/i.test(msg);
|
|
29357
|
+
}
|
|
29358
|
+
async function fetchRequiredCheckContexts(repo, branch, gh = defaultGhApi) {
|
|
29359
|
+
const contexts = /* @__PURE__ */ new Set();
|
|
29360
|
+
try {
|
|
29361
|
+
const raw = await gh([`repos/${repo}/branches/${encodeURIComponent(branch)}/protection/required_status_checks`]);
|
|
29362
|
+
const parsed = JSON.parse(raw);
|
|
29363
|
+
if (Array.isArray(parsed.contexts)) {
|
|
29364
|
+
for (const c of parsed.contexts) if (typeof c === "string") contexts.add(c);
|
|
29365
|
+
}
|
|
29366
|
+
} catch (e) {
|
|
29367
|
+
if (!isNotFoundError2(e)) return null;
|
|
29368
|
+
}
|
|
29369
|
+
try {
|
|
29370
|
+
const raw = await gh([`repos/${repo}/rules/branches/${encodeURIComponent(branch)}`]);
|
|
29371
|
+
const parsed = JSON.parse(raw);
|
|
29372
|
+
if (Array.isArray(parsed)) {
|
|
29373
|
+
for (const rule of parsed) {
|
|
29374
|
+
if (rule.type !== "required_status_checks") continue;
|
|
29375
|
+
for (const check of rule.parameters?.required_status_checks ?? []) {
|
|
29376
|
+
if (check.context) contexts.add(check.context);
|
|
29377
|
+
}
|
|
29378
|
+
}
|
|
29379
|
+
}
|
|
29380
|
+
} catch (e) {
|
|
29381
|
+
if (!isNotFoundError2(e)) return null;
|
|
29382
|
+
}
|
|
29383
|
+
return contexts;
|
|
29384
|
+
}
|
|
29261
29385
|
async function fetchRestCorePool(gh = defaultGhApi) {
|
|
29262
29386
|
try {
|
|
29263
29387
|
const parsed = JSON.parse(await gh(["rate_limit"]));
|
|
@@ -31048,8 +31172,8 @@ var LOOP_PLAYBOOKS = {
|
|
|
31048
31172
|
{ label: "Read the next board item", command: "mmi-cli board read" },
|
|
31049
31173
|
{ label: "Claim it and create an isolated worktree", command: "mmi-cli worktree create <issue-number> --claim --from origin/development" },
|
|
31050
31174
|
{ label: "Build and test in the touched package", command: "npm test && npm run build" },
|
|
31051
|
-
{ label: "Publish the branch", command: "git push
|
|
31052
|
-
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file PR_BODY.md --base development' },
|
|
31175
|
+
{ label: "Publish the branch", command: "git push origin <branch>:<branch>" },
|
|
31176
|
+
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
|
|
31053
31177
|
{ label: "Wait for checks and land to development", command: "mmi-cli pr checks-wait <PR-number> && mmi-cli pr land <PR-number>" },
|
|
31054
31178
|
{ label: "Release only after the gated train is authorized", command: "mmi-cli release --apply" }
|
|
31055
31179
|
]
|
|
@@ -31066,8 +31190,8 @@ var LOOP_PLAYBOOKS = {
|
|
|
31066
31190
|
"ship-pr": {
|
|
31067
31191
|
title: "Ship PR",
|
|
31068
31192
|
steps: [
|
|
31069
|
-
{ label: "Publish the branch", command: "git push
|
|
31070
|
-
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file PR_BODY.md --base development' },
|
|
31193
|
+
{ label: "Publish the branch", command: "git push origin <branch>:<branch>" },
|
|
31194
|
+
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
|
|
31071
31195
|
{ label: "Wait for CI checks", command: "mmi-cli pr checks-wait <PR-number>" },
|
|
31072
31196
|
{ label: "Land the PR (merge to development)", command: "mmi-cli pr land <PR-number>" }
|
|
31073
31197
|
]
|
|
@@ -31075,9 +31199,9 @@ var LOOP_PLAYBOOKS = {
|
|
|
31075
31199
|
"hotfix": {
|
|
31076
31200
|
title: "Hotfix",
|
|
31077
31201
|
steps: [
|
|
31078
|
-
{ label: "Create the main-base hotfix PR from
|
|
31202
|
+
{ label: "Create the main-base hotfix PR from every already-merged fix this cycle carries", command: "mmi-cli hotfix start --from <pr#|sha>[,<pr#|sha>...]" },
|
|
31079
31203
|
{ label: "Wait for the hotfix PR checks", command: "mmi-cli pr checks-wait <PR-number>" },
|
|
31080
|
-
{ label: "After the PR is merged, run the gated release", command: "mmi-cli hotfix release <vX.Y.Z> --carries <pr#|sha>" }
|
|
31204
|
+
{ label: "After the PR is merged, run the gated release", command: "mmi-cli hotfix release <vX.Y.Z> --carries <pr#|sha>[,<pr#|sha>...]" }
|
|
31081
31205
|
]
|
|
31082
31206
|
}
|
|
31083
31207
|
};
|
|
@@ -36696,10 +36820,11 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
36696
36820
|
console.log(JSON.stringify(created));
|
|
36697
36821
|
}), [
|
|
36698
36822
|
'mmi-cli pr create --title "Add the schema" --body "Closes #2680"',
|
|
36699
|
-
'mmi-cli pr create --title "Add the schema" --body-file PR_BODY.md --draft'
|
|
36823
|
+
'mmi-cli pr create --title "Add the schema" --body-file .jerv/PR_BODY.md --draft'
|
|
36700
36824
|
], [
|
|
36701
36825
|
"--head and --base default to the current branch and the repo default; only pass them to override.",
|
|
36702
|
-
"Use --body-file for multiline PR bodies instead of shell-escaped inline markdown."
|
|
36826
|
+
"Use --body-file for multiline PR bodies instead of shell-escaped inline markdown.",
|
|
36827
|
+
"Write that file under .jerv/ inside a worktree (#4405): any other untracked path makes `worktree land` refuse cleanup as untracked-files."
|
|
36703
36828
|
]);
|
|
36704
36829
|
pr.command("view <number>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) ? the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags ? in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) ? read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").action(async (number, o) => {
|
|
36705
36830
|
const n = Number(number);
|
|
@@ -36786,9 +36911,10 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
|
|
|
36786
36911
|
const snapshot = await fetchRestPrSnapshot(number, repo).catch(() => null);
|
|
36787
36912
|
const baseBranch = snapshot?.baseRef ?? "development";
|
|
36788
36913
|
const ciHeadRef = snapshot && !snapshot.headIsFork && snapshot.headRef ? snapshot.headRef : void 0;
|
|
36914
|
+
const requiredContexts = await fetchRequiredCheckContexts(repo, baseBranch).catch(() => null);
|
|
36789
36915
|
const result = await waitForPrChecks({
|
|
36790
36916
|
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
|
|
36791
|
-
pollChecks: () => pollRestPrChecks(number, repo),
|
|
36917
|
+
pollChecks: () => pollRestPrChecks(number, repo, void 0, requiredContexts),
|
|
36792
36918
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
36793
36919
|
pollRateLimit: () => fetchRestCorePool(),
|
|
36794
36920
|
// #3388: on a confirmed red, read the failing runs' annotations so a wall-clock budget kill stops
|
|
@@ -36854,31 +36980,37 @@ pr.command("land <number>").description("agent merge path (#1440): train probe ?
|
|
|
36854
36980
|
},
|
|
36855
36981
|
fetchTrainAuthority: async (repo) => fetchTrainAuthority(repo, registryClientDeps(await loadConfig())),
|
|
36856
36982
|
resolveCiPolicy: (repo) => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
|
|
36857
|
-
waitForChecks: (prNumber, repo) =>
|
|
36858
|
-
|
|
36859
|
-
|
|
36860
|
-
|
|
36861
|
-
|
|
36862
|
-
|
|
36863
|
-
|
|
36864
|
-
|
|
36865
|
-
|
|
36866
|
-
|
|
36867
|
-
|
|
36868
|
-
|
|
36869
|
-
|
|
36870
|
-
|
|
36871
|
-
|
|
36872
|
-
|
|
36873
|
-
|
|
36874
|
-
|
|
36983
|
+
waitForChecks: async (prNumber, repo) => {
|
|
36984
|
+
const requiredContexts = await fetchRequiredCheckContexts(repo, "development").catch(() => null);
|
|
36985
|
+
return waitForPrChecks({
|
|
36986
|
+
resolvePolicy: () => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
|
|
36987
|
+
// #3024: REST-only wait loop ? runPrLand's resolveRepo already guarantees `repo` is set.
|
|
36988
|
+
pollChecks: () => pollRestPrChecks(prNumber, repo, void 0, requiredContexts),
|
|
36989
|
+
// #2970: `pr land` only ever lands PRs based on development (resolveRepo above already rejects any
|
|
36990
|
+
// other base), so the fast-fail message's base branch is always 'development' here.
|
|
36991
|
+
pollMergeable: () => pollRestPrMergeable(prNumber, repo),
|
|
36992
|
+
pollRateLimit: () => fetchRestCorePool(),
|
|
36993
|
+
// #3388: `pr land` is the batch path ? the one most likely to self-DOS the shared runner and
|
|
36994
|
+
// then read its own wall-clock kill as a broken diff.
|
|
36995
|
+
diagnoseFailure: () => diagnoseFailedRestChecks(prNumber, repo),
|
|
36996
|
+
baseBranch: "development",
|
|
36997
|
+
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
36998
|
+
log: (message) => console.warn(message),
|
|
36999
|
+
// `pr land` inherits the same (raised) checks budget, so it needs the same liveness ? otherwise the
|
|
37000
|
+
// 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
|
|
37001
|
+
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr land: waiting on checks ? ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
37002
|
+
});
|
|
37003
|
+
},
|
|
36875
37004
|
// #2425: re-probe used ONLY to decide whether a failed `gh pr merge --auto` is worth retrying ? a
|
|
36876
37005
|
// fresh read of state/mergeable/checks, independent of the merge call's own (possibly stale) error.
|
|
37006
|
+
// #4396: same required-status scoping as the wait loop above ? a non-required red must not read
|
|
37007
|
+
// as "not ready to retry" any more than it should have blocked the wait itself.
|
|
36877
37008
|
probeMergeReady: async (prNumber, repo) => {
|
|
36878
|
-
const [snapshot,
|
|
37009
|
+
const [snapshot, requiredContexts] = await Promise.all([
|
|
36879
37010
|
fetchRestPrSnapshot(prNumber, repo).catch(() => null),
|
|
36880
|
-
|
|
37011
|
+
fetchRequiredCheckContexts(repo, "development").catch(() => null)
|
|
36881
37012
|
]);
|
|
37013
|
+
const checks = await pollRestPrChecks(prNumber, repo, void 0, requiredContexts).catch(() => "error");
|
|
36882
37014
|
return {
|
|
36883
37015
|
open: snapshot?.state === "open",
|
|
36884
37016
|
mergeable: snapshot?.mergeable === "MERGEABLE",
|
|
@@ -36961,9 +37093,10 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
36961
37093
|
if (o.wait) {
|
|
36962
37094
|
const repo = await requireRepo(o.repo);
|
|
36963
37095
|
const baseBranch = await fetchRestPrSnapshot(number, repo).then((s) => s.baseRef).catch(() => "development");
|
|
37096
|
+
const requiredContexts = await fetchRequiredCheckContexts(repo, baseBranch).catch(() => null);
|
|
36964
37097
|
const wait = await waitForPrChecks({
|
|
36965
37098
|
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
|
|
36966
|
-
pollChecks: () => pollRestPrChecks(number, repo),
|
|
37099
|
+
pollChecks: () => pollRestPrChecks(number, repo, void 0, requiredContexts),
|
|
36967
37100
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
36968
37101
|
pollRateLimit: () => fetchRestCorePool(),
|
|
36969
37102
|
diagnoseFailure: () => diagnoseFailedRestChecks(number, repo),
|
|
@@ -37456,7 +37589,8 @@ function renderHotfixRelease(r) {
|
|
|
37456
37589
|
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.url ? ` (${run.url})` : ""}`),
|
|
37457
37590
|
` - ${r.verifyNote}`,
|
|
37458
37591
|
...r.announceNote ? [` - announce: ${r.announceNote}`] : [],
|
|
37459
|
-
` -
|
|
37592
|
+
` - fold: ${r.foldNote}`,
|
|
37593
|
+
` - next: mmi-cli hotfix status ${r.tag} (no back-merge of the FIX ? development already has it; the version fold above is ported for you, #4410)`
|
|
37460
37594
|
].join("\n");
|
|
37461
37595
|
}
|
|
37462
37596
|
function renderHotfixStatus(r) {
|
|
@@ -37488,7 +37622,7 @@ var hotfixCmd = program2.command("hotfix").description("stepwise hotfix orchestr
|
|
|
37488
37622
|
const steps = trainPlan("hotfix");
|
|
37489
37623
|
console.log(o.json ? JSON.stringify({ command: "hotfix", steps }, null, 2) : renderSteps("mmi-cli hotfix: dry-run plan", steps));
|
|
37490
37624
|
});
|
|
37491
|
-
hotfixCmd.command("start").description("cherry-pick
|
|
37625
|
+
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").action(async (o) => runHotfixSub("start", () => runHotfixStart(trainApplyDeps(), { from: o.from }), o.json, renderHotfixStart));
|
|
37492
37626
|
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 summary lines for the Hub Slack announcement (#883)").option("--carries <pr#|sha[,pr#|sha...]>", "declared fix target(s) this hotfix must carry; each must be proven present before tagging (#3056)").action(async (version, o) => runHotfixSub("release", () => runHotfixRelease(trainApplyDeps(), version, { announceSummaryFile: o.announceSummaryFile, carries: o.carries ? [o.carries] : [] }), o.json, renderHotfixRelease));
|
|
37493
37627
|
hotfixCmd.command("status [version]").description("derive the full hotfix pipeline state from live git/gh reads and name the exact next subcommand").option("--json", "machine-readable output").action(async (version, o) => runHotfixSub("status", () => runHotfixStatus(trainApplyDeps(), version), o.json, renderHotfixStatus));
|
|
37494
37628
|
var ci = program2.command("ci").description("org CI + merge-readiness audit and reconcile");
|
package/package.json
CHANGED