@mutmutco/cli 4.5.77 → 4.5.78
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 +129 -31
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -6986,6 +6986,24 @@ function findClaimedButUnregisteredClosings(text, closing, alreadyClosed = []) {
|
|
|
6986
6986
|
}
|
|
6987
6987
|
return [...claimed].filter((n) => !registered.has(n) && !closed.has(n));
|
|
6988
6988
|
}
|
|
6989
|
+
function findLineStartClosings(text, repo) {
|
|
6990
|
+
const found = /* @__PURE__ */ new Set();
|
|
6991
|
+
let inFence = false;
|
|
6992
|
+
for (const line of text.split(/\r?\n/)) {
|
|
6993
|
+
if (FENCE_LINE_RE.test(line)) {
|
|
6994
|
+
inFence = !inFence;
|
|
6995
|
+
continue;
|
|
6996
|
+
}
|
|
6997
|
+
if (inFence || !LINE_START_CLOSING_RE.test(line)) continue;
|
|
6998
|
+
const m = findClosingMentions(line)[0];
|
|
6999
|
+
if (!m || m.negated || !Number.isInteger(m.issue) || m.issue <= 0) continue;
|
|
7000
|
+
const link = /\[#\d+\]\(([^)\s]+)\)/.exec(line);
|
|
7001
|
+
if (link && !(link[1] ?? "").toLowerCase().includes(`github.com/${repo.toLowerCase()}/issues/${m.issue}`)) continue;
|
|
7002
|
+
found.add(m.issue);
|
|
7003
|
+
}
|
|
7004
|
+
const ambiguous = new Set(findCrossRepoAmbiguousClosings(text, [...found], repo));
|
|
7005
|
+
return [...found].filter((n) => !ambiguous.has(n));
|
|
7006
|
+
}
|
|
6989
7007
|
function claimedClosingWarning(unregistered, context = "pr merge") {
|
|
6990
7008
|
const named = unregistered.map((n) => `#${n}`).join(", ");
|
|
6991
7009
|
const first = `#${unregistered[0] ?? "N"}`;
|
|
@@ -17231,10 +17249,10 @@ var rollout_plan_default = {
|
|
|
17231
17249
|
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)."
|
|
17232
17250
|
},
|
|
17233
17251
|
baseline: {
|
|
17234
|
-
version: "4.5.
|
|
17235
|
-
tag: "v4.5.
|
|
17236
|
-
commit: "
|
|
17237
|
-
npm: "@mutmutco/cli@4.5.
|
|
17252
|
+
version: "4.5.78",
|
|
17253
|
+
tag: "v4.5.78",
|
|
17254
|
+
commit: "55782d939329",
|
|
17255
|
+
npm: "@mutmutco/cli@4.5.78"
|
|
17238
17256
|
},
|
|
17239
17257
|
exitCriterion: "fleet-n-of-n",
|
|
17240
17258
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17251,14 +17269,14 @@ var rollout_plan_default = {
|
|
|
17251
17269
|
repo: "mutmutco/mmi-hub",
|
|
17252
17270
|
role: "canary",
|
|
17253
17271
|
schedule: "train",
|
|
17254
|
-
v3Target: "v4.5.
|
|
17272
|
+
v3Target: "v4.5.78"
|
|
17255
17273
|
}
|
|
17256
17274
|
],
|
|
17257
17275
|
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.",
|
|
17258
17276
|
rollback: {
|
|
17259
17277
|
independent: true,
|
|
17260
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17261
|
-
v3Target: "v4.5.
|
|
17278
|
+
mechanism: "npm dist-tag latest -> 4.5.78 and redeploy the Hub Lambda from tag v4.5.78 (55782d939329); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17279
|
+
v3Target: "v4.5.78 (@mutmutco/cli@4.5.78, tag commit 55782d939329 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17262
17280
|
}
|
|
17263
17281
|
},
|
|
17264
17282
|
{
|
|
@@ -20072,10 +20090,10 @@ async function listRemoteReleaseTags(deps) {
|
|
|
20072
20090
|
}
|
|
20073
20091
|
return [...tags].sort(cmpReleaseTag);
|
|
20074
20092
|
}
|
|
20075
|
-
async function assertComputedReleaseTagStillNext(deps, tag) {
|
|
20093
|
+
async function assertComputedReleaseTagStillNext(deps, tag, preOneZero = false) {
|
|
20076
20094
|
if (!/^v\d+\.\d+\.\d+$/.test(tag)) return;
|
|
20077
20095
|
const remote = await listRemoteReleaseTags(deps);
|
|
20078
|
-
const newer = remote.filter((t) => cmpReleaseTag(t, tag) > 0);
|
|
20096
|
+
const newer = remote.filter((t) => cmpReleaseTag(t, tag) > 0 && !(preOneZero && !t.startsWith("v0.")));
|
|
20079
20097
|
if (newer.length === 0) return;
|
|
20080
20098
|
const latest = newer[newer.length - 1];
|
|
20081
20099
|
throw new Error(
|
|
@@ -21697,6 +21715,17 @@ Resume it: ${resumeCommand}
|
|
|
21697
21715
|
);
|
|
21698
21716
|
}
|
|
21699
21717
|
|
|
21718
|
+
// src/pre-one-zero.ts
|
|
21719
|
+
function preOneZeroInForce(meta) {
|
|
21720
|
+
return !!meta?.suite && meta.preOneZero !== false;
|
|
21721
|
+
}
|
|
21722
|
+
function assertPreOneZero(repo, meta, tag) {
|
|
21723
|
+
if (preOneZeroInForce(meta) && /^v?[1-9]\d*\./.test(tag)) {
|
|
21724
|
+
throw new Error(`${repo} is a ${meta.suite} suite member held on 0.x.y; ${tag} would be major >= 1. Only the owner lifts this: set registry META preOneZero to false. If the tag came from a stale scripts/next-version.mjs, propagate the current seed first (#7699).`);
|
|
21725
|
+
}
|
|
21726
|
+
return tag;
|
|
21727
|
+
}
|
|
21728
|
+
|
|
21700
21729
|
// src/train-doctor.ts
|
|
21701
21730
|
var import_node_child_process9 = require("node:child_process");
|
|
21702
21731
|
var import_node_fs23 = require("node:fs");
|
|
@@ -24833,7 +24862,7 @@ async function runTrainDoctor(input) {
|
|
|
24833
24862
|
let newestRemoteReleaseTag;
|
|
24834
24863
|
try {
|
|
24835
24864
|
remoteTags = tagNames(await runGitRemoteRead(train, ["ls-remote", "--tags", "origin", "refs/tags/v*"]));
|
|
24836
|
-
const releaseTags = await listRemoteReleaseTags(train);
|
|
24865
|
+
const releaseTags = (await listRemoteReleaseTags(train)).filter((t) => !preOneZeroInForce(meta) || t.startsWith("v0."));
|
|
24837
24866
|
const latest = releaseTags[releaseTags.length - 1];
|
|
24838
24867
|
newestRemoteReleaseTag = latest;
|
|
24839
24868
|
if (latest) {
|
|
@@ -26055,7 +26084,7 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
|
|
|
26055
26084
|
let tagLedgerStarted = false;
|
|
26056
26085
|
const recoveryInput = { repo: ctx.repo, tag, stage: "main", deployModel, meta, branchHints };
|
|
26057
26086
|
try {
|
|
26058
|
-
await assertComputedReleaseTagStillNext(deps, tag);
|
|
26087
|
+
await assertComputedReleaseTagStillNext(deps, tag, preOneZeroInForce(meta));
|
|
26059
26088
|
if (deployModel === "hub-serverless") {
|
|
26060
26089
|
await deps.run("node", ["scripts/release-distribution.mjs", "assert-release-lineage", tag.replace(/^v/, ""), "--release-commit", releaseSha]);
|
|
26061
26090
|
}
|
|
@@ -26479,7 +26508,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
|
|
|
26479
26508
|
async function runReleaseResumePipeline(deps, options, prepared) {
|
|
26480
26509
|
const watch = options.watch ?? false;
|
|
26481
26510
|
const { ctx, meta, branchHints, directTrack, deployModel } = prepared;
|
|
26482
|
-
const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((t) => t.trim()).filter((t) => /^v\d+\.\d+\.\d
|
|
26511
|
+
const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((t) => t.trim()).filter((t) => (preOneZeroInForce(meta) ? /^v0\.\d+\.\d+$/ : /^v\d+\.\d+\.\d+$/).test(t));
|
|
26483
26512
|
const tag = tags[0];
|
|
26484
26513
|
if (!tag) throw new Error("release --resume: no final v* release tag found \u2014 there is no release to resume");
|
|
26485
26514
|
const tagSha = await probeRemoteTag(deps, tag);
|
|
@@ -26839,7 +26868,7 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
26839
26868
|
throw new Error("release --abort must run from the primary checkout left on main by the failed train; nothing was written");
|
|
26840
26869
|
}
|
|
26841
26870
|
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
26842
|
-
const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((tag2) => tag2.trim()).filter((tag2) => /^v\d+\.\d+\.\d
|
|
26871
|
+
const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((tag2) => tag2.trim()).filter((tag2) => (preOneZeroInForce(meta) ? /^v0\.\d+\.\d+$/ : /^v\d+\.\d+\.\d+$/).test(tag2));
|
|
26843
26872
|
const tag = tags[0];
|
|
26844
26873
|
if (!tag) throw new Error("release --abort found no final v* tag; nothing was written");
|
|
26845
26874
|
const tagSha = await probeRemoteTag(deps, tag);
|
|
@@ -27024,7 +27053,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
27024
27053
|
"nothing to promote: origin/development is not ahead of origin/rc"
|
|
27025
27054
|
);
|
|
27026
27055
|
const deployModel2 = await preflight(deps, ctx, "rc", meta);
|
|
27027
|
-
const releaseBase = requireValue(clean2(await deps
|
|
27056
|
+
const releaseBase = requireValue(clean2(await nextVersion(deps, ctx, meta, "cycle")), "release base");
|
|
27028
27057
|
const { tolerated: tolerated2, predicted: predicted2 } = await preflightMergeToMain(
|
|
27029
27058
|
deps,
|
|
27030
27059
|
deployModel2,
|
|
@@ -27049,7 +27078,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
27049
27078
|
else await mergeWithToleratedResolution(deps, "development", "development -> rc", "theirs", tolerated2);
|
|
27050
27079
|
rcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
|
|
27051
27080
|
resume = await resolveRcResumeTag(deps, releaseBase, rcSha, rcTagProbe);
|
|
27052
|
-
tag2 = resume.tag ?? requireValue(clean2(await deps
|
|
27081
|
+
tag2 = resume.tag ?? requireValue(clean2(await nextVersion(deps, ctx, meta, "rc")), "rc tag");
|
|
27053
27082
|
requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "rc");
|
|
27054
27083
|
assertTagAddressableRequiredContexts(deps, requiredChecks, ctx.repo);
|
|
27055
27084
|
const anchors2 = { repo: ctx.repo, tag: tag2, tagSha: rcSha };
|
|
@@ -27129,7 +27158,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
27129
27158
|
"nothing to release: origin/development is not ahead of origin/main"
|
|
27130
27159
|
);
|
|
27131
27160
|
const deployModel2 = await preflight(deps, ctx, "main", meta);
|
|
27132
|
-
const tag2 = requireValue(clean2(await deps
|
|
27161
|
+
const tag2 = requireValue(clean2(await nextVersion(deps, ctx, meta, "cycle")), "release tag");
|
|
27133
27162
|
const rcShaAtRelease = !directTrack && hasRcBranch ? clean2(await deps.run("git", ["rev-parse", "origin/rc"])) : "";
|
|
27134
27163
|
const { foldPaths: foldPaths2, tolerated: tolerated2, predicted: predicted2 } = await preflightMergeToMain(
|
|
27135
27164
|
deps,
|
|
@@ -27247,7 +27276,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
27247
27276
|
);
|
|
27248
27277
|
}
|
|
27249
27278
|
const releasedRcSha = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
|
|
27250
|
-
const tag = requireValue(clean2(await deps
|
|
27279
|
+
const tag = requireValue(clean2(await nextVersion(deps, ctx, meta, "release")), "release tag");
|
|
27251
27280
|
const tagProbe = { remoteSha: await probeRemoteTag(deps, tag) };
|
|
27252
27281
|
const preFold = {};
|
|
27253
27282
|
const { versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, "mmi-cli devops release --apply", async () => {
|
|
@@ -27306,19 +27335,23 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
27306
27335
|
...ledgerReport ? { cutBy: ledgerReport.cutBy, ledger: ledgerReport } : {}
|
|
27307
27336
|
};
|
|
27308
27337
|
}
|
|
27309
|
-
async function
|
|
27338
|
+
async function nextVersion(deps, ctx, meta, mode) {
|
|
27339
|
+
const held = preOneZeroInForce(meta);
|
|
27340
|
+
return assertPreOneZero(ctx.repo, meta, clean2(await deps.run("node", ["scripts/next-version.mjs", mode, ...held ? ["--pre-one-zero"] : []])));
|
|
27341
|
+
}
|
|
27342
|
+
async function latestLocalReleaseTag(deps, mergedInto, preOneZero = false) {
|
|
27310
27343
|
const tags = clean2(await deps.run("git", ["tag", "--list", "v*", ...mergedInto ? ["--merged", mergedInto] : []])).split("\n");
|
|
27311
27344
|
let latest = { major: 0, minor: 0, patch: 0 };
|
|
27312
27345
|
for (const tag of tags) {
|
|
27313
27346
|
const m = /^v(\d+)\.(\d+)\.(\d+)$/.exec(tag.trim());
|
|
27314
|
-
if (!m) continue;
|
|
27347
|
+
if (!m || preOneZero && m[1] !== "0") continue;
|
|
27315
27348
|
const v = { major: +m[1], minor: +m[2], patch: +m[3] };
|
|
27316
27349
|
const delta = v.major - latest.major || v.minor - latest.minor || v.patch - latest.patch;
|
|
27317
27350
|
if (delta > 0) latest = v;
|
|
27318
27351
|
}
|
|
27319
27352
|
return latest;
|
|
27320
27353
|
}
|
|
27321
|
-
async function resolveTrainBumpIntent(deps, env = process.env, latestRelease) {
|
|
27354
|
+
async function resolveTrainBumpIntent(deps, env = process.env, latestRelease, preOneZero = false) {
|
|
27322
27355
|
const explicitIntent = (env.MMI_BUMP_INTENT ?? "").trim();
|
|
27323
27356
|
if (explicitIntent) {
|
|
27324
27357
|
if (!TRAIN_BUMP_INTENTS.includes(explicitIntent)) {
|
|
@@ -27331,7 +27364,8 @@ async function resolveTrainBumpIntent(deps, env = process.env, latestRelease) {
|
|
|
27331
27364
|
const m = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(explicitVersion);
|
|
27332
27365
|
if (!m) throw new Error(`MMI_RELEASE_VERSION must be X.Y.Z (got ${explicitVersion})`);
|
|
27333
27366
|
const explicit = { major: +m[1], minor: +m[2], patch: +m[3] };
|
|
27334
|
-
|
|
27367
|
+
if (preOneZero && explicit.major >= 1) throw new Error(`MMI_RELEASE_VERSION ${explicitVersion} is major >= 1 but this suite member is held on 0.x.y; only the owner lifts it (registry META preOneZero=false) (#7699)`);
|
|
27368
|
+
const latest = latestRelease ?? await latestLocalReleaseTag(deps, void 0, preOneZero);
|
|
27335
27369
|
const delta = explicit.major - latest.major || explicit.minor - latest.minor || explicit.patch - latest.patch;
|
|
27336
27370
|
if (delta <= 0) {
|
|
27337
27371
|
throw new Error(
|
|
@@ -27342,8 +27376,8 @@ async function resolveTrainBumpIntent(deps, env = process.env, latestRelease) {
|
|
|
27342
27376
|
}
|
|
27343
27377
|
return "patch";
|
|
27344
27378
|
}
|
|
27345
|
-
async function resolveHotfixVersion(deps, env = process.env) {
|
|
27346
|
-
const latest = await latestLocalReleaseTag(deps, "origin/main");
|
|
27379
|
+
async function resolveHotfixVersion(deps, env = process.env, preOneZero = false) {
|
|
27380
|
+
const latest = await latestLocalReleaseTag(deps, "origin/main", preOneZero);
|
|
27347
27381
|
if (latest.major === 0 && latest.minor === 0 && latest.patch === 0) {
|
|
27348
27382
|
throw new Error("no vX.Y.Z release tag found on origin/main \u2014 cannot derive the hotfix PATCH version");
|
|
27349
27383
|
}
|
|
@@ -27355,14 +27389,14 @@ async function resolveHotfixVersion(deps, env = process.env) {
|
|
|
27355
27389
|
declared.push({
|
|
27356
27390
|
name: "MMI_BUMP_INTENT",
|
|
27357
27391
|
value: explicitIntent,
|
|
27358
|
-
intent: await resolveTrainBumpIntent(deps, { MMI_BUMP_INTENT: explicitIntent }, latest)
|
|
27392
|
+
intent: await resolveTrainBumpIntent(deps, { MMI_BUMP_INTENT: explicitIntent }, latest, preOneZero)
|
|
27359
27393
|
});
|
|
27360
27394
|
}
|
|
27361
27395
|
if (explicitVersion) {
|
|
27362
27396
|
declared.push({
|
|
27363
27397
|
name: "MMI_RELEASE_VERSION",
|
|
27364
27398
|
value: explicitVersion,
|
|
27365
|
-
intent: await resolveTrainBumpIntent(deps, { MMI_RELEASE_VERSION: explicitVersion }, latest)
|
|
27399
|
+
intent: await resolveTrainBumpIntent(deps, { MMI_RELEASE_VERSION: explicitVersion }, latest, preOneZero)
|
|
27366
27400
|
});
|
|
27367
27401
|
}
|
|
27368
27402
|
const nonPatch = declared.find((d) => d.intent !== "patch");
|
|
@@ -27386,8 +27420,8 @@ async function runTrainApplyLocked(command, deps, options = {}) {
|
|
|
27386
27420
|
const ctx = await buildTrainApplyContext(deps);
|
|
27387
27421
|
await requireCleanTree(deps);
|
|
27388
27422
|
await runGitRemoteRead(deps, ["fetch", "origin"]);
|
|
27389
|
-
const bumpIntent = await resolveTrainBumpIntent(deps);
|
|
27390
27423
|
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
27424
|
+
const bumpIntent = await resolveTrainBumpIntent(deps, process.env, void 0, preOneZeroInForce(meta));
|
|
27391
27425
|
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
27392
27426
|
const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
|
|
27393
27427
|
const pipelineInput = { deps, ctx, command, meta, branchHints, watch, options, bumpIntent };
|
|
@@ -36610,7 +36644,7 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
36610
36644
|
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
36611
36645
|
const { deployModel, meta } = await hotfixPreflight(deps, ctx, "start");
|
|
36612
36646
|
const bumpNotes = [];
|
|
36613
|
-
let resolved = await resolveHotfixVersion(deps);
|
|
36647
|
+
let resolved = await resolveHotfixVersion(deps, process.env, preOneZeroInForce(meta));
|
|
36614
36648
|
for (; ; ) {
|
|
36615
36649
|
const priorPr = await findHotfixPr(deps, ctx, resolved.tag);
|
|
36616
36650
|
if ((priorPr?.state ?? "").toUpperCase() !== "MERGED") break;
|
|
@@ -37793,7 +37827,6 @@ async function advanceClosedIssuesToDone(deps) {
|
|
|
37793
37827
|
error: `expected an array of closing issue references, got ${closing === null ? "null" : typeof closing}`
|
|
37794
37828
|
};
|
|
37795
37829
|
}
|
|
37796
|
-
if (closing.length === 0) return { status: "none", entries: [] };
|
|
37797
37830
|
const refs = [];
|
|
37798
37831
|
for (const [i, candidate] of closing.entries()) {
|
|
37799
37832
|
const repo = repoOf2(candidate);
|
|
@@ -37807,6 +37840,29 @@ async function advanceClosedIssuesToDone(deps) {
|
|
|
37807
37840
|
refs.push({ ref: candidate, repo });
|
|
37808
37841
|
}
|
|
37809
37842
|
const entries = [];
|
|
37843
|
+
const fallbackNumbers = /* @__PURE__ */ new Set();
|
|
37844
|
+
let fallbackError;
|
|
37845
|
+
if (deps.fallback) {
|
|
37846
|
+
try {
|
|
37847
|
+
const registered = new Set(refs.filter((r) => r.repo.toLowerCase() === deps.repo.toLowerCase()).map((r) => r.ref.number));
|
|
37848
|
+
for (const n of [...new Set(await deps.fallback.fetchClaimed())].filter((n2) => !registered.has(n2))) {
|
|
37849
|
+
try {
|
|
37850
|
+
const closed = await deps.fallback.closeIssue(n);
|
|
37851
|
+
if ("error" in closed) entries.push({ issue: `#${n}`, moved: false, status: "failed", error: `GitHub did not register the closing reference and the CLI could not close it (${closed.error})` });
|
|
37852
|
+
else if (closed.state === "closed") {
|
|
37853
|
+
fallbackNumbers.add(n);
|
|
37854
|
+
const [owner, name] = deps.repo.split("/");
|
|
37855
|
+
refs.push({ ref: { number: n, repository: { name: name ?? "", owner: { login: owner ?? "" } } }, repo: deps.repo });
|
|
37856
|
+
}
|
|
37857
|
+
} catch (e) {
|
|
37858
|
+
entries.push({ issue: `#${n}`, moved: false, status: "failed", error: `GitHub did not register the closing reference and the CLI could not close it (${e.message})` });
|
|
37859
|
+
}
|
|
37860
|
+
}
|
|
37861
|
+
} catch (e) {
|
|
37862
|
+
fallbackError = e.message;
|
|
37863
|
+
}
|
|
37864
|
+
}
|
|
37865
|
+
if (refs.length === 0 && entries.length === 0) return { status: "none", entries: [], ...fallbackError ? { fallbackError } : {} };
|
|
37810
37866
|
for (const { ref, repo } of refs) {
|
|
37811
37867
|
if (repo.toLowerCase() !== deps.repo.toLowerCase()) {
|
|
37812
37868
|
entries.push({
|
|
@@ -37847,14 +37903,15 @@ async function advanceClosedIssuesToDone(deps) {
|
|
|
37847
37903
|
entries.push({ issue, moved: false, status: "failed", error: e.message });
|
|
37848
37904
|
}
|
|
37849
37905
|
}
|
|
37850
|
-
|
|
37906
|
+
for (const e of entries) if (fallbackNumbers.has(Number(e.issue.slice(1)))) e.closedByFallback = true;
|
|
37907
|
+
return { status: entries.every((e) => e.moved) && !fallbackError ? "ok" : "partial", entries, ...fallbackError ? { fallbackError } : {} };
|
|
37851
37908
|
}
|
|
37852
37909
|
function boardAdvanceFailures(result) {
|
|
37853
37910
|
return (result?.entries ?? []).filter((e) => !e.moved);
|
|
37854
37911
|
}
|
|
37855
37912
|
function boardAdvanceExitCode(result) {
|
|
37856
37913
|
if (!result) return void 0;
|
|
37857
|
-
if (result.status === "fetch-failed") return 1;
|
|
37914
|
+
if (result.status === "fetch-failed" || result.fallbackError) return 1;
|
|
37858
37915
|
return boardAdvanceFailures(result).length > 0 ? 1 : void 0;
|
|
37859
37916
|
}
|
|
37860
37917
|
function boardAdvanceFailureMessage(result) {
|
|
@@ -37863,6 +37920,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
37863
37920
|
return `pr merge: could not read the PR's closing issues (${result.error ?? "unknown error"}) \u2014 the PR MERGED, but no board item was advanced. Check the board by hand.`;
|
|
37864
37921
|
}
|
|
37865
37922
|
const failures = boardAdvanceFailures(result);
|
|
37923
|
+
if (result.fallbackError) return `pr merge: could not read the PR body to close unregistered closing lines (${result.fallbackError}) \u2014 the PR MERGED; check by hand whether its "Closes #N" issues are still open.`;
|
|
37866
37924
|
if (!failures.length) return void 0;
|
|
37867
37925
|
const named = failures.map((f) => f.error ? `${f.issue} (${f.error})` : f.issue).join(", ");
|
|
37868
37926
|
return `pr merge: board advance incomplete for ${named} \u2014 the PR MERGED, but ${failures.length === 1 ? "this issue" : "these issues"} did not reach Done on the board.`;
|
|
@@ -38955,6 +39013,41 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
38955
39013
|
}
|
|
38956
39014
|
return advanceClosedIssuesToDone({
|
|
38957
39015
|
repo,
|
|
39016
|
+
// #7691: GitHub sometimes fails to register a body `Closes #N`, so the merge closes nothing. Close the
|
|
39017
|
+
// still-OPEN issues the body claims on a deliberate line-start closing line (same repo only). GitHub closes
|
|
39018
|
+
// only for PRs into the default branch, so a PR into rc/main (a train or hotfix PR) never falls back.
|
|
39019
|
+
fallback: {
|
|
39020
|
+
fetchClaimed: async () => {
|
|
39021
|
+
const { stdout } = await execFileP("gh", ["pr", "view", prNumber, ...repoArgs, "--json", "body,baseRefName"], { timeout: GC_GH_TIMEOUT_MS });
|
|
39022
|
+
const { body, baseRefName } = JSON.parse(stdout || "null") ?? {};
|
|
39023
|
+
if (typeof body !== "string" || typeof baseRefName !== "string") throw new Error("gh returned no PR body or base branch");
|
|
39024
|
+
const repoView = await execFileP("gh", ["repo", "view", repo, "--json", "defaultBranchRef"], { timeout: GC_GH_TIMEOUT_MS });
|
|
39025
|
+
const defaultBranch = JSON.parse(repoView.stdout || "null")?.defaultBranchRef?.name;
|
|
39026
|
+
if (typeof defaultBranch !== "string" || !defaultBranch) throw new Error("gh returned no default branch");
|
|
39027
|
+
if (baseRefName !== defaultBranch) return [];
|
|
39028
|
+
return findLineStartClosings(body, repo);
|
|
39029
|
+
},
|
|
39030
|
+
closeIssue: async (n) => {
|
|
39031
|
+
try {
|
|
39032
|
+
const view = await execFileP("gh", ["issue", "view", String(n), "--repo", repo, "--json", "state"], { timeout: GC_GH_TIMEOUT_MS });
|
|
39033
|
+
if (JSON.parse(view.stdout || "null")?.state !== "OPEN") return { state: "already-closed" };
|
|
39034
|
+
await execFileP("gh", [
|
|
39035
|
+
"issue",
|
|
39036
|
+
"close",
|
|
39037
|
+
String(n),
|
|
39038
|
+
"--repo",
|
|
39039
|
+
repo,
|
|
39040
|
+
"--reason",
|
|
39041
|
+
"completed",
|
|
39042
|
+
"--comment",
|
|
39043
|
+
`Closed by the CLI after PR #${prNumber} merged: its body says it closes this issue, but GitHub did not register the closing reference (#7691).`
|
|
39044
|
+
], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
39045
|
+
return { state: "closed" };
|
|
39046
|
+
} catch (e) {
|
|
39047
|
+
return { error: e.message };
|
|
39048
|
+
}
|
|
39049
|
+
}
|
|
39050
|
+
},
|
|
38958
39051
|
fetchClosingIssues: async () => {
|
|
38959
39052
|
const { stdout } = await execFileP("gh", ["pr", "view", prNumber, ...repoArgs, "--json", "closingIssuesReferences"], { timeout: GC_GH_TIMEOUT_MS });
|
|
38960
39053
|
const parsed = JSON.parse(stdout || "null");
|
|
@@ -44224,6 +44317,11 @@ ${list}`);
|
|
|
44224
44317
|
`
|
|
44225
44318
|
);
|
|
44226
44319
|
}
|
|
44320
|
+
const safeTitle = rewriteUnregisteredClosingKeywords(title, findClosingMentions(body).map((m) => m.issue));
|
|
44321
|
+
if (safeTitle !== title) {
|
|
44322
|
+
title = safeTitle;
|
|
44323
|
+
process.stderr.write('pr create: WARNING \u2014 rewrote close-keyword text in the title to "part of #N": the squash commit subject would close it (#7688). Put closing keywords on a "Closes #N" body line.\n');
|
|
44324
|
+
}
|
|
44227
44325
|
const docsCheck = await checkDocsIndexAtHead({ repo: o.repo, head: o.head }, createPrCreateDocsIndexDeps());
|
|
44228
44326
|
if (docsCheck && !docsCheck.ok) {
|
|
44229
44327
|
return fail(`pr create: ${docsCheck.detail} \u2014 ${docsCheck.fix}`);
|
|
@@ -45171,7 +45269,7 @@ async function requireFreshTrainCli(commandName) {
|
|
|
45171
45269
|
process.exit(converge.reexecCode);
|
|
45172
45270
|
}
|
|
45173
45271
|
var GH_TRAIN_TIMEOUT_MS = 3e4;
|
|
45174
|
-
var GH_RUN_WATCH_TIMEOUT_MS =
|
|
45272
|
+
var GH_RUN_WATCH_TIMEOUT_MS = 90 * 6e4;
|
|
45175
45273
|
var NODE_PREPARE_TIMEOUT_MS = 10 * 6e4;
|
|
45176
45274
|
var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
|
|
45177
45275
|
var NPM_TRAIN_TIMEOUT_MS = 6e4;
|
package/package.json
CHANGED