@mutmutco/cli 4.5.77 → 4.5.79
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 +204 -46
- 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"}`;
|
|
@@ -14348,7 +14366,7 @@ var PRODUCT_PENDING_RULESET_PREFIX = "mmi-product-required-checks-pending-";
|
|
|
14348
14366
|
var PRODUCT_GATE_CONTEXT = "gate";
|
|
14349
14367
|
var PRODUCT_RULESET_PATH = ".github/rulesets/mmi-product-required-checks.json";
|
|
14350
14368
|
function reseedProductRulesetStrictness(current, seed, declared = {}) {
|
|
14351
|
-
const
|
|
14369
|
+
const parse2 = (raw) => {
|
|
14352
14370
|
const policy2 = JSON.parse(raw);
|
|
14353
14371
|
if (!policy2 || typeof policy2 !== "object" || Array.isArray(policy2) || policy2.name !== PRODUCT_RULESET_NAME || policy2.target !== "branch" || !Array.isArray(policy2.rules) || policy2.rules.some((r) => !r || typeof r !== "object" || Array.isArray(r) || typeof r.type !== "string")) {
|
|
14354
14372
|
throw new Error("product ruleset must be a branch ruleset object with well-formed rules");
|
|
@@ -14360,11 +14378,11 @@ function reseedProductRulesetStrictness(current, seed, declared = {}) {
|
|
|
14360
14378
|
}
|
|
14361
14379
|
return { policy: policy2, parameters: parameters2 };
|
|
14362
14380
|
};
|
|
14363
|
-
const canonical =
|
|
14381
|
+
const canonical = parse2(seed);
|
|
14364
14382
|
const strict = canonical.parameters.strict_required_status_checks_policy;
|
|
14365
14383
|
if (typeof strict !== "boolean") throw new Error("canonical product ruleset must declare boolean strictness");
|
|
14366
14384
|
if (current === null) return seed;
|
|
14367
|
-
const { policy, parameters } =
|
|
14385
|
+
const { policy, parameters } = parse2(current);
|
|
14368
14386
|
let changed = parameters.strict_required_status_checks_policy !== strict;
|
|
14369
14387
|
parameters.strict_required_status_checks_policy = strict;
|
|
14370
14388
|
if (declared.requiredChecks) {
|
|
@@ -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.79",
|
|
17253
|
+
tag: "v4.5.79",
|
|
17254
|
+
commit: "542d3fde121b",
|
|
17255
|
+
npm: "@mutmutco/cli@4.5.79"
|
|
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.79"
|
|
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.79 and redeploy the Hub Lambda from tag v4.5.79 (542d3fde121b); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17279
|
+
v3Target: "v4.5.79 (@mutmutco/cli@4.5.79, tag commit 542d3fde121b \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17262
17280
|
}
|
|
17263
17281
|
},
|
|
17264
17282
|
{
|
|
@@ -20053,12 +20071,12 @@ async function isStrayUnreleasedTag(deps, tag, remoteSha, repo) {
|
|
|
20053
20071
|
return deps.run("gh", ["release", "view", tag, "--repo", repo, "--json", "tagName"]).then(() => false).catch((e) => /not found|HTTP 404/i.test(e instanceof Error ? e.message : String(e)));
|
|
20054
20072
|
}
|
|
20055
20073
|
function cmpReleaseTag(a, b) {
|
|
20056
|
-
const
|
|
20074
|
+
const parse2 = (t) => {
|
|
20057
20075
|
const m = /^v(\d+)\.(\d+)\.(\d+)$/.exec(t.trim());
|
|
20058
20076
|
return m ? { major: +m[1], minor: +m[2], patch: +m[3] } : null;
|
|
20059
20077
|
};
|
|
20060
|
-
const left =
|
|
20061
|
-
const right =
|
|
20078
|
+
const left = parse2(a);
|
|
20079
|
+
const right = parse2(b);
|
|
20062
20080
|
if (!left || !right) throw new Error(`cmpReleaseTag expects vX.Y.Z tags, got ${a} vs ${b}`);
|
|
20063
20081
|
return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
|
|
20064
20082
|
}
|
|
@@ -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
|
}
|
|
@@ -26200,12 +26229,12 @@ function resumeFollowUpOf(state) {
|
|
|
26200
26229
|
return state === "aligned" ? "complete" : state;
|
|
26201
26230
|
}
|
|
26202
26231
|
function compareRcTagsNewestFirst(a, b) {
|
|
26203
|
-
const
|
|
26232
|
+
const parse2 = (tag) => {
|
|
26204
26233
|
const match = /^v(\d+)\.(\d+)\.(\d+)-rc\.(\d+)$/.exec(tag);
|
|
26205
26234
|
return match ? match.slice(1).map(Number) : [];
|
|
26206
26235
|
};
|
|
26207
|
-
const left =
|
|
26208
|
-
const right =
|
|
26236
|
+
const left = parse2(a);
|
|
26237
|
+
const right = parse2(b);
|
|
26209
26238
|
for (let i = 0; i < 4; i++) {
|
|
26210
26239
|
const delta = (right[i] ?? 0) - (left[i] ?? 0);
|
|
26211
26240
|
if (delta !== 0) return delta;
|
|
@@ -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 };
|
|
@@ -32604,6 +32638,7 @@ function assignWaves(repos, canarySlug) {
|
|
|
32604
32638
|
function statusFor(read, currentSha) {
|
|
32605
32639
|
if (!read) return { status: "pending", record: {} };
|
|
32606
32640
|
if (read.drift === "match") return { status: "match", record: { mergeSha: read.pr?.mergeSha } };
|
|
32641
|
+
if (read.prUnread) return { status: "red", record: {} };
|
|
32607
32642
|
const pr = read.pr;
|
|
32608
32643
|
if (!pr) return { status: "pending", record: {} };
|
|
32609
32644
|
if (pr.sourceSha && pr.sourceSha !== currentSha) return { status: "pending", record: {} };
|
|
@@ -35056,9 +35091,10 @@ LIVE apply to ${repo}:
|
|
|
35056
35091
|
desiredByRepo.set(r.repo, desired);
|
|
35057
35092
|
const drift = compareSeedBytes(desired, content);
|
|
35058
35093
|
let pr;
|
|
35094
|
+
let prUnread = false;
|
|
35059
35095
|
try {
|
|
35060
35096
|
const listBranchPrs = async (branch) => {
|
|
35061
|
-
const listed = await gh2(["pr", "list", "--repo", r.repo, "--head", branch, "--base", baseBranch, "--state", "all", "--json", "number,url,state,statusCheckRollup,mergeCommit,body,files", "--limit", "100"]);
|
|
35097
|
+
const listed = await gh2(["pr", "list", "--repo", r.repo, "--head", branch, "--base", baseBranch, "--state", "all", "--json", "number,url,state,statusCheckRollup,mergeCommit,body,files,headRefName", "--limit", "100"]);
|
|
35062
35098
|
return JSON.parse(listed.stdout || "[]");
|
|
35063
35099
|
};
|
|
35064
35100
|
const targetSpecific = (await listBranchPrs(propagationBranch(r.repo, seed.target))).map((p2) => ({ ...p2, files: p2.files?.map((f) => f.path) }));
|
|
@@ -35073,11 +35109,19 @@ LIVE apply to ${repo}:
|
|
|
35073
35109
|
const checks = rollup.length === 0 ? "none" : rollup.some((c) => c.conclusion === "FAILURE" || c.state === "FAILURE") ? "red" : rollup.every((c) => c.conclusion === "SUCCESS" || c.state === "SUCCESS") ? "success" : "pending";
|
|
35074
35110
|
const sourceSha = p.body?.match(/Propagates MMI-Hub@([0-9a-fA-F]{7,40})/)?.[1];
|
|
35075
35111
|
pr = { number: p.number, url: p.url, state: p.state === "MERGED" ? "merged" : p.state === "CLOSED" ? "closed" : "open", checks, mergeSha: p.mergeCommit?.oid, sourceSha };
|
|
35112
|
+
if (pr.state === "open" && sourceSha && sourceSha !== seedSource.sha && p.headRefName) {
|
|
35113
|
+
const head = await gh2(["api", `repos/${r.repo}/contents/${enc(seed.target)}?ref=${encodeURIComponent(p.headRefName)}`]);
|
|
35114
|
+
const parsedHead = JSON.parse(head.stdout);
|
|
35115
|
+
const headContent = parsedHead.encoding === "base64" && typeof parsedHead.content === "string" ? decodeGitHubContents(parsedHead.content) : null;
|
|
35116
|
+
if (compareSeedBytes(desired, headContent) === "match") pr.sourceSha = seedSource.sha;
|
|
35117
|
+
}
|
|
35076
35118
|
}
|
|
35077
|
-
} catch {
|
|
35078
|
-
|
|
35119
|
+
} catch (e) {
|
|
35120
|
+
prUnread = true;
|
|
35121
|
+
process.stderr.write(`bootstrap propagate: could not read ${r.repo}'s propagation PRs for ${seed.target}: ${e.message}
|
|
35122
|
+
`);
|
|
35079
35123
|
}
|
|
35080
|
-
reads.push({ repo: r.repo, drift: drift === "waived" ? "match" : drift, pr });
|
|
35124
|
+
reads.push({ repo: r.repo, drift: drift === "waived" ? "match" : drift, pr, prUnread });
|
|
35081
35125
|
}
|
|
35082
35126
|
let functionGateSatisfied = !isWorkflowSeed;
|
|
35083
35127
|
if (isWorkflowSeed && canarySlug) {
|
|
@@ -35593,10 +35637,41 @@ function formatSuiteStatus(report) {
|
|
|
35593
35637
|
var import_node_fs29 = require("node:fs");
|
|
35594
35638
|
var import_node_path29 = require("node:path");
|
|
35595
35639
|
var import_node_crypto13 = require("node:crypto");
|
|
35640
|
+
|
|
35641
|
+
// src/kit-version.ts
|
|
35642
|
+
var parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(typeof v === "string" ? v.trim() : "");
|
|
35643
|
+
function kitSatisfies(shell2, module2) {
|
|
35644
|
+
const s = parse(shell2);
|
|
35645
|
+
const m = parse(module2);
|
|
35646
|
+
if (!s || !m) return true;
|
|
35647
|
+
for (let i = 1; i <= 3; i++) if (Number(s[i]) !== Number(m[i])) return Number(s[i]) > Number(m[i]);
|
|
35648
|
+
return !s[4] || Boolean(m[4]);
|
|
35649
|
+
}
|
|
35650
|
+
function kitFromBundle(js) {
|
|
35651
|
+
const direct = /export\s+(?:const|let|var)\s+__kit\s*=\s*["']([^"']+)["']/.exec(js);
|
|
35652
|
+
if (direct) return direct[1];
|
|
35653
|
+
const local = /[{,]\s*([\w$]+)\s+as\s+__kit\b/.exec(js)?.[1];
|
|
35654
|
+
if (!local) return void 0;
|
|
35655
|
+
const name = local.replace(/[$]/g, "\\$&");
|
|
35656
|
+
return new RegExp(`(?:^|[\\s;,{}])(?:const|let|var)?\\s*${name}\\s*=\\s*["']([^"']+)["']`).exec(js)?.[1];
|
|
35657
|
+
}
|
|
35658
|
+
function kitRefusals(shell2, modules) {
|
|
35659
|
+
return modules.filter((m) => !kitSatisfies(shell2, m.kit)).map((m) => `${m.id}: built on kit ${m.kit}, newer than the MM-Work shell's kit ${shell2}`);
|
|
35660
|
+
}
|
|
35661
|
+
|
|
35662
|
+
// src/suite-train.ts
|
|
35596
35663
|
var SCHEMA = "mmi-suite-cut/v1";
|
|
35597
35664
|
var SHA2 = /^[a-f0-9]{40}$/i;
|
|
35598
35665
|
var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
|
|
35599
35666
|
var RELEASE_TAG = /^v\d+\.\d+\.\d+$/;
|
|
35667
|
+
async function kitCheck(deps, shellSha, bundles, where) {
|
|
35668
|
+
const shell2 = shellSha ? await deps.shellKit(shellSha) : void 0;
|
|
35669
|
+
const kits = await Promise.all(bundles.map(async (b) => ({ id: b.id, kit: b.url ? await deps.bundleKit(b.url) : void 0 })));
|
|
35670
|
+
const refused = kitRefusals(shell2, kits);
|
|
35671
|
+
if (refused.length) throw new Error(`kit check (${where}) refused; a module built on a newer kit crashes the older shell. Release the newer kit to MM-Work first (or rebuild the module on the shell's kit):
|
|
35672
|
+
${refused.join("\n")}`);
|
|
35673
|
+
return `kit check (${where}): shell kit ${shell2 ?? "unknown"}; ${kits.map((k) => `${k.id} __kit ${k.kit ?? "none"}`).join(", ") || "no modules"}`;
|
|
35674
|
+
}
|
|
35600
35675
|
function suitePins(record, bundles) {
|
|
35601
35676
|
return record.members.map((m) => m.carried ? { module: m.id, rc: m.rcPin, main: m.mainPin } : {
|
|
35602
35677
|
module: m.id,
|
|
@@ -35708,6 +35783,8 @@ async function suiteRcand(deps, opts) {
|
|
|
35708
35783
|
`${SUITE_REPO}: one PR for both rc/main pins, wait for merge`,
|
|
35709
35784
|
`${SUITE_REPO}: rcand`
|
|
35710
35785
|
];
|
|
35786
|
+
const lead = await deps.head(SUITE_REPO, "development");
|
|
35787
|
+
plan.push(await kitCheck(deps, lead, active.map((m) => ({ id: m.id, url: m.bundles.dev })), "dev bundles"));
|
|
35711
35788
|
if (opts.dryRun) return { plan };
|
|
35712
35789
|
let record = readSuiteCut(deps.recordPath);
|
|
35713
35790
|
if (record?.leadReleased && record.announcement === "pending") {
|
|
@@ -35829,6 +35906,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
|
|
|
35829
35906
|
throw new Error(`${member.repo}: live backend or development changed during the cut; pins unchanged`);
|
|
35830
35907
|
}
|
|
35831
35908
|
const pins = suitePins(record, bundlesOf(modules));
|
|
35909
|
+
await kitCheck(deps, record.leadDevelopment, pins.map((p) => ({ id: p.module, url: p.rc })), "rc bundles");
|
|
35832
35910
|
await deps.verifyLeadCut(record.leadDevelopment);
|
|
35833
35911
|
const pr = await deps.pin({ cut: record.leadDevelopment, pins, heldIds: record.heldMembers?.map((h) => h.id) });
|
|
35834
35912
|
await deps.verifyLeadCut(record.leadDevelopment);
|
|
@@ -36610,7 +36688,7 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
36610
36688
|
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
36611
36689
|
const { deployModel, meta } = await hotfixPreflight(deps, ctx, "start");
|
|
36612
36690
|
const bumpNotes = [];
|
|
36613
|
-
let resolved = await resolveHotfixVersion(deps);
|
|
36691
|
+
let resolved = await resolveHotfixVersion(deps, process.env, preOneZeroInForce(meta));
|
|
36614
36692
|
for (; ; ) {
|
|
36615
36693
|
const priorPr = await findHotfixPr(deps, ctx, resolved.tag);
|
|
36616
36694
|
if ((priorPr?.state ?? "").toUpperCase() !== "MERGED") break;
|
|
@@ -37793,7 +37871,6 @@ async function advanceClosedIssuesToDone(deps) {
|
|
|
37793
37871
|
error: `expected an array of closing issue references, got ${closing === null ? "null" : typeof closing}`
|
|
37794
37872
|
};
|
|
37795
37873
|
}
|
|
37796
|
-
if (closing.length === 0) return { status: "none", entries: [] };
|
|
37797
37874
|
const refs = [];
|
|
37798
37875
|
for (const [i, candidate] of closing.entries()) {
|
|
37799
37876
|
const repo = repoOf2(candidate);
|
|
@@ -37807,6 +37884,29 @@ async function advanceClosedIssuesToDone(deps) {
|
|
|
37807
37884
|
refs.push({ ref: candidate, repo });
|
|
37808
37885
|
}
|
|
37809
37886
|
const entries = [];
|
|
37887
|
+
const fallbackNumbers = /* @__PURE__ */ new Set();
|
|
37888
|
+
let fallbackError;
|
|
37889
|
+
if (deps.fallback) {
|
|
37890
|
+
try {
|
|
37891
|
+
const registered = new Set(refs.filter((r) => r.repo.toLowerCase() === deps.repo.toLowerCase()).map((r) => r.ref.number));
|
|
37892
|
+
for (const n of [...new Set(await deps.fallback.fetchClaimed())].filter((n2) => !registered.has(n2))) {
|
|
37893
|
+
try {
|
|
37894
|
+
const closed = await deps.fallback.closeIssue(n);
|
|
37895
|
+
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})` });
|
|
37896
|
+
else if (closed.state === "closed") {
|
|
37897
|
+
fallbackNumbers.add(n);
|
|
37898
|
+
const [owner, name] = deps.repo.split("/");
|
|
37899
|
+
refs.push({ ref: { number: n, repository: { name: name ?? "", owner: { login: owner ?? "" } } }, repo: deps.repo });
|
|
37900
|
+
}
|
|
37901
|
+
} catch (e) {
|
|
37902
|
+
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})` });
|
|
37903
|
+
}
|
|
37904
|
+
}
|
|
37905
|
+
} catch (e) {
|
|
37906
|
+
fallbackError = e.message;
|
|
37907
|
+
}
|
|
37908
|
+
}
|
|
37909
|
+
if (refs.length === 0 && entries.length === 0) return { status: "none", entries: [], ...fallbackError ? { fallbackError } : {} };
|
|
37810
37910
|
for (const { ref, repo } of refs) {
|
|
37811
37911
|
if (repo.toLowerCase() !== deps.repo.toLowerCase()) {
|
|
37812
37912
|
entries.push({
|
|
@@ -37847,14 +37947,15 @@ async function advanceClosedIssuesToDone(deps) {
|
|
|
37847
37947
|
entries.push({ issue, moved: false, status: "failed", error: e.message });
|
|
37848
37948
|
}
|
|
37849
37949
|
}
|
|
37850
|
-
|
|
37950
|
+
for (const e of entries) if (fallbackNumbers.has(Number(e.issue.slice(1)))) e.closedByFallback = true;
|
|
37951
|
+
return { status: entries.every((e) => e.moved) && !fallbackError ? "ok" : "partial", entries, ...fallbackError ? { fallbackError } : {} };
|
|
37851
37952
|
}
|
|
37852
37953
|
function boardAdvanceFailures(result) {
|
|
37853
37954
|
return (result?.entries ?? []).filter((e) => !e.moved);
|
|
37854
37955
|
}
|
|
37855
37956
|
function boardAdvanceExitCode(result) {
|
|
37856
37957
|
if (!result) return void 0;
|
|
37857
|
-
if (result.status === "fetch-failed") return 1;
|
|
37958
|
+
if (result.status === "fetch-failed" || result.fallbackError) return 1;
|
|
37858
37959
|
return boardAdvanceFailures(result).length > 0 ? 1 : void 0;
|
|
37859
37960
|
}
|
|
37860
37961
|
function boardAdvanceFailureMessage(result) {
|
|
@@ -37863,6 +37964,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
37863
37964
|
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
37965
|
}
|
|
37865
37966
|
const failures = boardAdvanceFailures(result);
|
|
37967
|
+
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
37968
|
if (!failures.length) return void 0;
|
|
37867
37969
|
const named = failures.map((f) => f.error ? `${f.issue} (${f.error})` : f.issue).join(", ");
|
|
37868
37970
|
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 +39057,41 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
38955
39057
|
}
|
|
38956
39058
|
return advanceClosedIssuesToDone({
|
|
38957
39059
|
repo,
|
|
39060
|
+
// #7691: GitHub sometimes fails to register a body `Closes #N`, so the merge closes nothing. Close the
|
|
39061
|
+
// still-OPEN issues the body claims on a deliberate line-start closing line (same repo only). GitHub closes
|
|
39062
|
+
// only for PRs into the default branch, so a PR into rc/main (a train or hotfix PR) never falls back.
|
|
39063
|
+
fallback: {
|
|
39064
|
+
fetchClaimed: async () => {
|
|
39065
|
+
const { stdout } = await execFileP("gh", ["pr", "view", prNumber, ...repoArgs, "--json", "body,baseRefName"], { timeout: GC_GH_TIMEOUT_MS });
|
|
39066
|
+
const { body, baseRefName } = JSON.parse(stdout || "null") ?? {};
|
|
39067
|
+
if (typeof body !== "string" || typeof baseRefName !== "string") throw new Error("gh returned no PR body or base branch");
|
|
39068
|
+
const repoView = await execFileP("gh", ["repo", "view", repo, "--json", "defaultBranchRef"], { timeout: GC_GH_TIMEOUT_MS });
|
|
39069
|
+
const defaultBranch = JSON.parse(repoView.stdout || "null")?.defaultBranchRef?.name;
|
|
39070
|
+
if (typeof defaultBranch !== "string" || !defaultBranch) throw new Error("gh returned no default branch");
|
|
39071
|
+
if (baseRefName !== defaultBranch) return [];
|
|
39072
|
+
return findLineStartClosings(body, repo);
|
|
39073
|
+
},
|
|
39074
|
+
closeIssue: async (n) => {
|
|
39075
|
+
try {
|
|
39076
|
+
const view = await execFileP("gh", ["issue", "view", String(n), "--repo", repo, "--json", "state"], { timeout: GC_GH_TIMEOUT_MS });
|
|
39077
|
+
if (JSON.parse(view.stdout || "null")?.state !== "OPEN") return { state: "already-closed" };
|
|
39078
|
+
await execFileP("gh", [
|
|
39079
|
+
"issue",
|
|
39080
|
+
"close",
|
|
39081
|
+
String(n),
|
|
39082
|
+
"--repo",
|
|
39083
|
+
repo,
|
|
39084
|
+
"--reason",
|
|
39085
|
+
"completed",
|
|
39086
|
+
"--comment",
|
|
39087
|
+
`Closed by the CLI after PR #${prNumber} merged: its body says it closes this issue, but GitHub did not register the closing reference (#7691).`
|
|
39088
|
+
], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
39089
|
+
return { state: "closed" };
|
|
39090
|
+
} catch (e) {
|
|
39091
|
+
return { error: e.message };
|
|
39092
|
+
}
|
|
39093
|
+
}
|
|
39094
|
+
},
|
|
38958
39095
|
fetchClosingIssues: async () => {
|
|
38959
39096
|
const { stdout } = await execFileP("gh", ["pr", "view", prNumber, ...repoArgs, "--json", "closingIssuesReferences"], { timeout: GC_GH_TIMEOUT_MS });
|
|
38960
39097
|
const parsed = JSON.parse(stdout || "null");
|
|
@@ -44224,6 +44361,11 @@ ${list}`);
|
|
|
44224
44361
|
`
|
|
44225
44362
|
);
|
|
44226
44363
|
}
|
|
44364
|
+
const safeTitle = rewriteUnregisteredClosingKeywords(title, findClosingMentions(body).map((m) => m.issue));
|
|
44365
|
+
if (safeTitle !== title) {
|
|
44366
|
+
title = safeTitle;
|
|
44367
|
+
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');
|
|
44368
|
+
}
|
|
44227
44369
|
const docsCheck = await checkDocsIndexAtHead({ repo: o.repo, head: o.head }, createPrCreateDocsIndexDeps());
|
|
44228
44370
|
if (docsCheck && !docsCheck.ok) {
|
|
44229
44371
|
return fail(`pr create: ${docsCheck.detail} \u2014 ${docsCheck.fix}`);
|
|
@@ -45171,7 +45313,7 @@ async function requireFreshTrainCli(commandName) {
|
|
|
45171
45313
|
process.exit(converge.reexecCode);
|
|
45172
45314
|
}
|
|
45173
45315
|
var GH_TRAIN_TIMEOUT_MS = 3e4;
|
|
45174
|
-
var GH_RUN_WATCH_TIMEOUT_MS =
|
|
45316
|
+
var GH_RUN_WATCH_TIMEOUT_MS = 90 * 6e4;
|
|
45175
45317
|
var NODE_PREPARE_TIMEOUT_MS = 10 * 6e4;
|
|
45176
45318
|
var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
|
|
45177
45319
|
var NPM_TRAIN_TIMEOUT_MS = 6e4;
|
|
@@ -46102,9 +46244,9 @@ function isMissingRef(message2) {
|
|
|
46102
46244
|
return /404|Not Found|No commit found for SHA/.test(message2);
|
|
46103
46245
|
}
|
|
46104
46246
|
function releaseTagsFrom(out) {
|
|
46105
|
-
const
|
|
46247
|
+
const parse2 = (t) => t.slice(1).split(".").map(Number);
|
|
46106
46248
|
return out.split(/\s+/).filter((t) => /^v\d+\.\d+\.\d+$/.test(t)).sort((a, b) => {
|
|
46107
|
-
const [x, y] = [
|
|
46249
|
+
const [x, y] = [parse2(a), parse2(b)];
|
|
46108
46250
|
return x[0] - y[0] || x[1] - y[1] || x[2] - y[2];
|
|
46109
46251
|
});
|
|
46110
46252
|
}
|
|
@@ -46395,6 +46537,22 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
|
|
|
46395
46537
|
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`);
|
|
46396
46538
|
}
|
|
46397
46539
|
},
|
|
46540
|
+
shellKit: async (sha) => {
|
|
46541
|
+
await exec("git", ["fetch", "--quiet", "origin", "--tags"], root, WATCH_TIMEOUT);
|
|
46542
|
+
try {
|
|
46543
|
+
return (await exec("git", ["describe", "--tags", "--match", "ds-v[0-9]*", "--abbrev=0", sha], root)).replace(/^ds-v/, "") || void 0;
|
|
46544
|
+
} catch {
|
|
46545
|
+
return void 0;
|
|
46546
|
+
}
|
|
46547
|
+
},
|
|
46548
|
+
bundleKit: async (url) => {
|
|
46549
|
+
try {
|
|
46550
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(GH_TIMEOUT) });
|
|
46551
|
+
return res.ok ? kitFromBundle(await res.text()) : void 0;
|
|
46552
|
+
} catch {
|
|
46553
|
+
return void 0;
|
|
46554
|
+
}
|
|
46555
|
+
},
|
|
46398
46556
|
release,
|
|
46399
46557
|
shippedTag,
|
|
46400
46558
|
sameContent: sameContent2,
|
package/package.json
CHANGED