@mutmutco/cli 3.7.0 → 3.8.0
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 +39 -14
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -9270,7 +9270,7 @@ function parseNpmVersion(stdout) {
|
|
|
9270
9270
|
return /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(v) ? v : void 0;
|
|
9271
9271
|
}
|
|
9272
9272
|
function staleTrainCliMessage(report, commandName) {
|
|
9273
|
-
return `running mmi-cli ${report.currentVersion} is stale against released ${report.releasedVersion}; this is recoverable \u2014 run \`mmi-cli doctor --apply --no-repo-writes\` to update to ${report.releasedVersion}, then rerun ${commandName} so it uses the current train path`;
|
|
9273
|
+
return `running mmi-cli ${report.currentVersion} is stale against released ${report.releasedVersion}; this is recoverable \u2014 run \`mmi-cli doctor --apply --no-repo-writes\` to update to ${report.releasedVersion}, then rerun ${commandName} so it uses the current train path. This gate runs before any train mutation, so this invocation changed nothing; any partial train state (local merge, pushed tag) is from an earlier run and the rerun resumes it safely`;
|
|
9274
9274
|
}
|
|
9275
9275
|
async function resolveReleasedVersion(runners) {
|
|
9276
9276
|
try {
|
|
@@ -10038,7 +10038,7 @@ var defaultSleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
|
10038
10038
|
function resolveSleep(deps) {
|
|
10039
10039
|
return deps.sleep ?? defaultSleep2;
|
|
10040
10040
|
}
|
|
10041
|
-
var TRAIN_CHECK_RUNS_JQ = "[.check_runs[]|{name:.name,status:.status,conclusion:.conclusion}]";
|
|
10041
|
+
var TRAIN_CHECK_RUNS_JQ = "[.check_runs[]|{name:.name,status:.status,conclusion:.conclusion,startedAt:.started_at}]";
|
|
10042
10042
|
var TRAIN_COMMIT_STATUS_JQ = "[.statuses[]|{context:.context,state:.state}]";
|
|
10043
10043
|
var TRAIN_PROTECTION_CONTEXTS_JQ = "[.contexts[]]";
|
|
10044
10044
|
var TRAIN_RULES_CONTEXTS_JQ = '[.[]|select(.type=="required_status_checks")|.parameters.required_status_checks[].context]';
|
|
@@ -10046,6 +10046,7 @@ var TRAIN_CHECK_ATTEMPTS = 40;
|
|
|
10046
10046
|
var TRAIN_CHECK_DELAY_MS = 15e3;
|
|
10047
10047
|
var TRAIN_PR_ONLY_AUTOMATION_CONTEXTS = /* @__PURE__ */ new Set(["add-to-project", "mark-merged-pr-done"]);
|
|
10048
10048
|
var TRAIN_PR_AUTOMATION_GRACE_ATTEMPTS = 3;
|
|
10049
|
+
var TRAIN_FRESH_RUN_GRACE_ATTEMPTS = 4;
|
|
10049
10050
|
async function correlateRun(deps, args) {
|
|
10050
10051
|
const sleep = resolveSleep(deps);
|
|
10051
10052
|
const threshold = args.since - CORRELATE_SKEW_SLACK_MS;
|
|
@@ -10103,12 +10104,19 @@ async function correlateWorkflowRun(deps, args) {
|
|
|
10103
10104
|
}
|
|
10104
10105
|
async function watchTenantRun(deps, runId, repo = HUB_REPO3) {
|
|
10105
10106
|
if (runId == null) return "pending";
|
|
10107
|
+
let watchSaidSuccess = true;
|
|
10106
10108
|
try {
|
|
10107
10109
|
await deps.run("gh", ["run", "watch", String(runId), "--repo", repo, "--exit-status"]);
|
|
10108
|
-
return "success";
|
|
10109
10110
|
} catch {
|
|
10110
|
-
|
|
10111
|
+
watchSaidSuccess = false;
|
|
10111
10112
|
}
|
|
10113
|
+
try {
|
|
10114
|
+
const out = await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--json", "status,conclusion"]);
|
|
10115
|
+
const parsed = JSON.parse(out);
|
|
10116
|
+
if (parsed.status === "completed") return parsed.conclusion === "success" ? "success" : "failure";
|
|
10117
|
+
} catch {
|
|
10118
|
+
}
|
|
10119
|
+
return watchSaidSuccess ? "success" : "failure";
|
|
10112
10120
|
}
|
|
10113
10121
|
async function fetchControlRunLog(deps, runId) {
|
|
10114
10122
|
try {
|
|
@@ -10220,10 +10228,16 @@ function resolveContextState(context, checkRuns, statuses) {
|
|
|
10220
10228
|
if (sawSuccess) return "success";
|
|
10221
10229
|
return sawFailure ? "failed" : "pending";
|
|
10222
10230
|
}
|
|
10223
|
-
async function waitForRequiredTrainChecks(deps, ctx, sha, required) {
|
|
10231
|
+
async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince) {
|
|
10224
10232
|
if (required.length === 0) {
|
|
10225
10233
|
return "no required status checks configured on the target branch \u2014 check wait skipped (GitHub push gate is the backstop)";
|
|
10226
10234
|
}
|
|
10235
|
+
const freshThreshold = freshSince !== void 0 ? freshSince - CORRELATE_SKEW_SLACK_MS : void 0;
|
|
10236
|
+
const isFreshRun = (r) => {
|
|
10237
|
+
if (freshThreshold === void 0) return true;
|
|
10238
|
+
const t = Date.parse(String(r.startedAt ?? ""));
|
|
10239
|
+
return !Number.isFinite(t) || t >= freshThreshold;
|
|
10240
|
+
};
|
|
10227
10241
|
const sleep = resolveSleep(deps);
|
|
10228
10242
|
let lastStatus = "not checked";
|
|
10229
10243
|
let lastError;
|
|
@@ -10256,7 +10270,14 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required) {
|
|
|
10256
10270
|
}
|
|
10257
10271
|
}
|
|
10258
10272
|
const pending = required.filter((c) => !autoSatisfied.has(c));
|
|
10259
|
-
const
|
|
10273
|
+
const holdStale = freshThreshold !== void 0 && attempt < TRAIN_FRESH_RUN_GRACE_ATTEMPTS;
|
|
10274
|
+
const states = pending.map((c) => {
|
|
10275
|
+
if (holdStale) {
|
|
10276
|
+
const runsFor = checkRuns.filter((r) => r.name === c);
|
|
10277
|
+
if (runsFor.length > 0 && !runsFor.some(isFreshRun)) return [c, "pending"];
|
|
10278
|
+
}
|
|
10279
|
+
return [c, resolveContextState(c, checkRuns, statuses)];
|
|
10280
|
+
});
|
|
10260
10281
|
const satisfiedNote = autoSatisfied.size ? `, auto-satisfied (never runs on a tag, see #2404): ${[...autoSatisfied].join(", ")}` : "";
|
|
10261
10282
|
lastStatus = `${states.map(([c, s]) => `${c}=${s}`).join(", ")}${satisfiedNote}`;
|
|
10262
10283
|
const failed = states.filter(([, s]) => s === "failed").map(([c]) => c);
|
|
@@ -10308,14 +10329,14 @@ async function ensureTagPushed(deps, tag, sha) {
|
|
|
10308
10329
|
if (localSha && localSha !== sha) {
|
|
10309
10330
|
throw new Error(`local tag ${tag} points at ${localSha} but origin has it at ${sha} \u2014 delete the stale local tag (git tag -d ${tag}) and rerun`);
|
|
10310
10331
|
}
|
|
10311
|
-
return `tag ${tag} already on origin at ${sha.slice(0, 7)} \u2014 resumed without re-pushing
|
|
10332
|
+
return { note: `tag ${tag} already on origin at ${sha.slice(0, 7)} \u2014 resumed without re-pushing`, pushed: false };
|
|
10312
10333
|
}
|
|
10313
10334
|
if (localSha && localSha !== sha) {
|
|
10314
10335
|
throw new Error(`local tag ${tag} already points at ${localSha}, not the intended ${sha} \u2014 delete the stale local tag (git tag -d ${tag}) and rerun`);
|
|
10315
10336
|
}
|
|
10316
10337
|
if (!localSha) await deps.run("git", ["tag", tag, sha]);
|
|
10317
10338
|
await deps.run("git", ["push", "origin", tag]);
|
|
10318
|
-
return `tag ${tag} pushed at ${sha.slice(0, 7)}
|
|
10339
|
+
return { note: `tag ${tag} pushed at ${sha.slice(0, 7)}`, pushed: true };
|
|
10319
10340
|
}
|
|
10320
10341
|
async function resolveRcResumeTag(deps, base, sha) {
|
|
10321
10342
|
const out = await deps.run("git", ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
|
|
@@ -10621,11 +10642,12 @@ async function runFoldStage(deps, startBranch, preFold, fn) {
|
|
|
10621
10642
|
}
|
|
10622
10643
|
}
|
|
10623
10644
|
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha) {
|
|
10624
|
-
await ensureTagPushed(deps, tag, releaseSha);
|
|
10645
|
+
const tagPush = await ensureTagPushed(deps, tag, releaseSha);
|
|
10646
|
+
const tagPushSince = (deps.now ?? Date.now)();
|
|
10625
10647
|
const requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "main");
|
|
10626
10648
|
let checks;
|
|
10627
10649
|
try {
|
|
10628
|
-
checks = await waitForRequiredTrainChecks(deps, ctx, releaseSha, requiredChecks);
|
|
10650
|
+
checks = await waitForRequiredTrainChecks(deps, ctx, releaseSha, requiredChecks, tagPush.pushed ? tagPushSince : void 0);
|
|
10629
10651
|
} catch (e) {
|
|
10630
10652
|
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag, stage: "main" });
|
|
10631
10653
|
}
|
|
@@ -10677,11 +10699,12 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10677
10699
|
const resume = await resolveRcResumeTag(deps, releaseBase, rcSha);
|
|
10678
10700
|
const tag2 = resume.tag ?? requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
|
|
10679
10701
|
const resumeNote = resume.tag ? resume.note : void 0;
|
|
10680
|
-
await ensureTagPushed(deps, tag2, rcSha);
|
|
10702
|
+
const tagPush = await ensureTagPushed(deps, tag2, rcSha);
|
|
10703
|
+
const tagPushSince = (deps.now ?? Date.now)();
|
|
10681
10704
|
const requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "rc");
|
|
10682
10705
|
let checks2;
|
|
10683
10706
|
try {
|
|
10684
|
-
checks2 = await waitForRequiredTrainChecks(deps, ctx, rcSha, requiredChecks);
|
|
10707
|
+
checks2 = await waitForRequiredTrainChecks(deps, ctx, rcSha, requiredChecks, tagPush.pushed ? tagPushSince : void 0);
|
|
10685
10708
|
} catch (e) {
|
|
10686
10709
|
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag: tag2, stage: "rc" });
|
|
10687
10710
|
}
|
|
@@ -11383,9 +11406,11 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
11383
11406
|
await deps.run("git", ["merge-base", "--is-ancestor", mergedSha, "origin/main"]).catch(() => {
|
|
11384
11407
|
throw new Error(`merged hotfix SHA ${mergedSha.slice(0, 7)} is not on origin/main \u2014 refusing to tag`);
|
|
11385
11408
|
});
|
|
11386
|
-
const
|
|
11409
|
+
const tagPush = await ensureTagPushed(deps, tag, mergedSha);
|
|
11410
|
+
const tagPushSince = Date.now();
|
|
11411
|
+
const tagNote = tagPush.note;
|
|
11387
11412
|
const required = await discoverRequiredCheckContexts(deps, ctx, "main");
|
|
11388
|
-
const checks = await waitForRequiredTrainChecks(deps, ctx, mergedSha, required);
|
|
11413
|
+
const checks = await waitForRequiredTrainChecks(deps, ctx, mergedSha, required, tagPush.pushed ? tagPushSince : void 0);
|
|
11389
11414
|
let releaseNote;
|
|
11390
11415
|
let releaseExists = false;
|
|
11391
11416
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|