@mutmutco/cli 3.7.0 → 3.9.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 +181 -38
- 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 {
|
|
@@ -9750,6 +9750,19 @@ function requireValue(value, label) {
|
|
|
9750
9750
|
if (!value) throw new Error(`${label} could not be resolved`);
|
|
9751
9751
|
return value;
|
|
9752
9752
|
}
|
|
9753
|
+
function surfaceTrainSubprocessFailure(e, file, args) {
|
|
9754
|
+
if (!(e instanceof Error)) return e;
|
|
9755
|
+
const err = e;
|
|
9756
|
+
const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
|
|
9757
|
+
const stdout = typeof err.stdout === "string" ? err.stdout.trim() : "";
|
|
9758
|
+
const cmd = `${file} ${args.join(" ")}`.trim();
|
|
9759
|
+
if (!stderr && !stdout) {
|
|
9760
|
+
err.message = `${cmd} failed with no stderr output (exit ${err.code ?? "?"}) \u2014 likely a transient subprocess/network error. [${err.message}]`;
|
|
9761
|
+
} else if (/^Command failed/i.test(err.message) && stderr) {
|
|
9762
|
+
err.message = `${cmd} failed: ${stderr}`;
|
|
9763
|
+
}
|
|
9764
|
+
return err;
|
|
9765
|
+
}
|
|
9753
9766
|
function planTrainApplyRepoGuard(applyRepo, cwdRepo, rerun) {
|
|
9754
9767
|
if (cwdRepo && cwdRepo.toLowerCase() === applyRepo.toLowerCase()) return { ok: true };
|
|
9755
9768
|
const where = cwdRepo ? `this checkout is ${cwdRepo}` : "this checkout could not be identified";
|
|
@@ -9817,14 +9830,14 @@ async function verifyPublishedRelease(deps, repo, tag, expectedTarget, expectedS
|
|
|
9817
9830
|
if (tagSha !== expectedSha) {
|
|
9818
9831
|
throw new Error(`Release ${tag} tag points at ${tagSha}, expected ${expectedSha}`);
|
|
9819
9832
|
}
|
|
9820
|
-
const branchOut = clean(await deps
|
|
9833
|
+
const branchOut = clean(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${expectedTarget}`]));
|
|
9821
9834
|
const branchSha = requireValue(branchOut.split(/\s+/)[0] ?? "", `origin/${expectedTarget} sha`);
|
|
9822
9835
|
if (branchSha !== expectedSha) {
|
|
9823
9836
|
throw new Error(`origin/${expectedTarget} points at ${branchSha}, expected ${expectedSha}`);
|
|
9824
9837
|
}
|
|
9825
9838
|
}
|
|
9826
9839
|
async function ffOnlyPull(deps, branch) {
|
|
9827
|
-
await deps
|
|
9840
|
+
await runGitRemoteRead(deps, ["pull", "--ff-only", "origin", branch]);
|
|
9828
9841
|
}
|
|
9829
9842
|
var RELEASE_TOLERATED_PATHS = [".gitignore"];
|
|
9830
9843
|
var MERGE_TREE_PREFLIGHT_ATTEMPTS = 3;
|
|
@@ -9878,7 +9891,7 @@ function ensurePositiveCount(out, emptyMessage) {
|
|
|
9878
9891
|
if (count <= 0) throw new Error(emptyMessage);
|
|
9879
9892
|
}
|
|
9880
9893
|
async function remoteBranchExists(deps, branch) {
|
|
9881
|
-
const out = clean(await deps
|
|
9894
|
+
const out = clean(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${branch}`]));
|
|
9882
9895
|
return out.length > 0;
|
|
9883
9896
|
}
|
|
9884
9897
|
async function loadReleaseTrackBranchHints(deps) {
|
|
@@ -10038,7 +10051,33 @@ var defaultSleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
|
10038
10051
|
function resolveSleep(deps) {
|
|
10039
10052
|
return deps.sleep ?? defaultSleep2;
|
|
10040
10053
|
}
|
|
10041
|
-
var
|
|
10054
|
+
var GIT_NETWORK_ATTEMPTS = 3;
|
|
10055
|
+
var GIT_NETWORK_RETRY_DELAY_MS = 1e3;
|
|
10056
|
+
function isPersistentGitPushFailure(e) {
|
|
10057
|
+
const msg = `${e instanceof Error ? e.message : String(e)} ${String(e.stderr ?? "")}`;
|
|
10058
|
+
return /non-fast-forward|\[rejected\]|failed to push|protected branch|hook declined|permission denied|could not read Username|authentication failed|Authentication failed|403 Forbidden|GH006|GH013/i.test(msg);
|
|
10059
|
+
}
|
|
10060
|
+
async function runGitWithRetry(deps, args, shouldRetry) {
|
|
10061
|
+
const sleep = resolveSleep(deps);
|
|
10062
|
+
let lastError;
|
|
10063
|
+
for (let attempt = 1; attempt <= GIT_NETWORK_ATTEMPTS; attempt++) {
|
|
10064
|
+
try {
|
|
10065
|
+
return await deps.run("git", args);
|
|
10066
|
+
} catch (e) {
|
|
10067
|
+
lastError = e;
|
|
10068
|
+
if (attempt >= GIT_NETWORK_ATTEMPTS || !shouldRetry(e)) throw e;
|
|
10069
|
+
await sleep(GIT_NETWORK_RETRY_DELAY_MS * attempt);
|
|
10070
|
+
}
|
|
10071
|
+
}
|
|
10072
|
+
throw lastError;
|
|
10073
|
+
}
|
|
10074
|
+
function runGitRemoteRead(deps, args) {
|
|
10075
|
+
return runGitWithRetry(deps, args, () => true);
|
|
10076
|
+
}
|
|
10077
|
+
function runGitPush(deps, args) {
|
|
10078
|
+
return runGitWithRetry(deps, args, (e) => !isPersistentGitPushFailure(e));
|
|
10079
|
+
}
|
|
10080
|
+
var TRAIN_CHECK_RUNS_JQ = "[.check_runs[]|{name:.name,status:.status,conclusion:.conclusion,startedAt:.started_at}]";
|
|
10042
10081
|
var TRAIN_COMMIT_STATUS_JQ = "[.statuses[]|{context:.context,state:.state}]";
|
|
10043
10082
|
var TRAIN_PROTECTION_CONTEXTS_JQ = "[.contexts[]]";
|
|
10044
10083
|
var TRAIN_RULES_CONTEXTS_JQ = '[.[]|select(.type=="required_status_checks")|.parameters.required_status_checks[].context]';
|
|
@@ -10046,6 +10085,7 @@ var TRAIN_CHECK_ATTEMPTS = 40;
|
|
|
10046
10085
|
var TRAIN_CHECK_DELAY_MS = 15e3;
|
|
10047
10086
|
var TRAIN_PR_ONLY_AUTOMATION_CONTEXTS = /* @__PURE__ */ new Set(["add-to-project", "mark-merged-pr-done"]);
|
|
10048
10087
|
var TRAIN_PR_AUTOMATION_GRACE_ATTEMPTS = 3;
|
|
10088
|
+
var TRAIN_FRESH_RUN_GRACE_ATTEMPTS = 4;
|
|
10049
10089
|
async function correlateRun(deps, args) {
|
|
10050
10090
|
const sleep = resolveSleep(deps);
|
|
10051
10091
|
const threshold = args.since - CORRELATE_SKEW_SLACK_MS;
|
|
@@ -10103,12 +10143,19 @@ async function correlateWorkflowRun(deps, args) {
|
|
|
10103
10143
|
}
|
|
10104
10144
|
async function watchTenantRun(deps, runId, repo = HUB_REPO3) {
|
|
10105
10145
|
if (runId == null) return "pending";
|
|
10146
|
+
let watchSaidSuccess = true;
|
|
10106
10147
|
try {
|
|
10107
10148
|
await deps.run("gh", ["run", "watch", String(runId), "--repo", repo, "--exit-status"]);
|
|
10108
|
-
return "success";
|
|
10109
10149
|
} catch {
|
|
10110
|
-
|
|
10150
|
+
watchSaidSuccess = false;
|
|
10151
|
+
}
|
|
10152
|
+
try {
|
|
10153
|
+
const out = await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--json", "status,conclusion"]);
|
|
10154
|
+
const parsed = JSON.parse(out);
|
|
10155
|
+
if (parsed.status === "completed") return parsed.conclusion === "success" ? "success" : "failure";
|
|
10156
|
+
} catch {
|
|
10111
10157
|
}
|
|
10158
|
+
return watchSaidSuccess ? "success" : "failure";
|
|
10112
10159
|
}
|
|
10113
10160
|
async function fetchControlRunLog(deps, runId) {
|
|
10114
10161
|
try {
|
|
@@ -10220,10 +10267,16 @@ function resolveContextState(context, checkRuns, statuses) {
|
|
|
10220
10267
|
if (sawSuccess) return "success";
|
|
10221
10268
|
return sawFailure ? "failed" : "pending";
|
|
10222
10269
|
}
|
|
10223
|
-
async function waitForRequiredTrainChecks(deps, ctx, sha, required) {
|
|
10270
|
+
async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince) {
|
|
10224
10271
|
if (required.length === 0) {
|
|
10225
10272
|
return "no required status checks configured on the target branch \u2014 check wait skipped (GitHub push gate is the backstop)";
|
|
10226
10273
|
}
|
|
10274
|
+
const freshThreshold = freshSince !== void 0 ? freshSince - CORRELATE_SKEW_SLACK_MS : void 0;
|
|
10275
|
+
const isFreshRun = (r) => {
|
|
10276
|
+
if (freshThreshold === void 0) return true;
|
|
10277
|
+
const t = Date.parse(String(r.startedAt ?? ""));
|
|
10278
|
+
return !Number.isFinite(t) || t >= freshThreshold;
|
|
10279
|
+
};
|
|
10227
10280
|
const sleep = resolveSleep(deps);
|
|
10228
10281
|
let lastStatus = "not checked";
|
|
10229
10282
|
let lastError;
|
|
@@ -10256,7 +10309,14 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required) {
|
|
|
10256
10309
|
}
|
|
10257
10310
|
}
|
|
10258
10311
|
const pending = required.filter((c) => !autoSatisfied.has(c));
|
|
10259
|
-
const
|
|
10312
|
+
const holdStale = freshThreshold !== void 0 && attempt < TRAIN_FRESH_RUN_GRACE_ATTEMPTS;
|
|
10313
|
+
const states = pending.map((c) => {
|
|
10314
|
+
if (holdStale) {
|
|
10315
|
+
const runsFor = checkRuns.filter((r) => r.name === c);
|
|
10316
|
+
if (runsFor.length > 0 && !runsFor.some(isFreshRun)) return [c, "pending"];
|
|
10317
|
+
}
|
|
10318
|
+
return [c, resolveContextState(c, checkRuns, statuses)];
|
|
10319
|
+
});
|
|
10260
10320
|
const satisfiedNote = autoSatisfied.size ? `, auto-satisfied (never runs on a tag, see #2404): ${[...autoSatisfied].join(", ")}` : "";
|
|
10261
10321
|
lastStatus = `${states.map(([c, s]) => `${c}=${s}`).join(", ")}${satisfiedNote}`;
|
|
10262
10322
|
const failed = states.filter(([, s]) => s === "failed").map(([c]) => c);
|
|
@@ -10291,9 +10351,12 @@ Recovery sequence:
|
|
|
10291
10351
|
Do not delete or force-move the pushed tag; rerun the train only after confirming the branch, release, and deploy states above.`
|
|
10292
10352
|
);
|
|
10293
10353
|
}
|
|
10294
|
-
async function
|
|
10295
|
-
const remoteOut = await deps
|
|
10296
|
-
|
|
10354
|
+
async function probeRemoteTag(deps, tag) {
|
|
10355
|
+
const remoteOut = await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/tags/${tag}`]);
|
|
10356
|
+
return clean(remoteOut).split(/\s+/)[0] || "";
|
|
10357
|
+
}
|
|
10358
|
+
async function ensureTagPushed(deps, tag, sha, probed) {
|
|
10359
|
+
const remoteSha = probed ? probed.remoteSha : await probeRemoteTag(deps, tag);
|
|
10297
10360
|
let localSha = "";
|
|
10298
10361
|
try {
|
|
10299
10362
|
localSha = clean(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
|
|
@@ -10308,17 +10371,20 @@ async function ensureTagPushed(deps, tag, sha) {
|
|
|
10308
10371
|
if (localSha && localSha !== sha) {
|
|
10309
10372
|
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
10373
|
}
|
|
10311
|
-
return `tag ${tag} already on origin at ${sha.slice(0, 7)} \u2014 resumed without re-pushing
|
|
10374
|
+
return { note: `tag ${tag} already on origin at ${sha.slice(0, 7)} \u2014 resumed without re-pushing`, pushed: false };
|
|
10312
10375
|
}
|
|
10313
10376
|
if (localSha && localSha !== sha) {
|
|
10314
10377
|
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
10378
|
}
|
|
10316
10379
|
if (!localSha) await deps.run("git", ["tag", tag, sha]);
|
|
10317
|
-
await deps
|
|
10318
|
-
return `tag ${tag} pushed at ${sha.slice(0, 7)}
|
|
10380
|
+
await runGitPush(deps, ["push", "origin", tag]);
|
|
10381
|
+
return { note: `tag ${tag} pushed at ${sha.slice(0, 7)}`, pushed: true };
|
|
10319
10382
|
}
|
|
10320
|
-
|
|
10321
|
-
|
|
10383
|
+
function probeRcResumeTags(deps, base) {
|
|
10384
|
+
return runGitRemoteRead(deps, ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
|
|
10385
|
+
}
|
|
10386
|
+
async function resolveRcResumeTag(deps, base, sha, probed) {
|
|
10387
|
+
const out = probed ?? await runGitRemoteRead(deps, ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
|
|
10322
10388
|
const onSha = clean(out).split("\n").map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)).filter(([refSha]) => refSha === sha).map(([, ref]) => ref.replace(/^refs\/tags\//, "").replace(/\^\{\}$/, "")).filter((ref) => new RegExp(`^${base.replace(/\./g, "\\.")}-rc\\.\\d+$`).test(ref));
|
|
10323
10389
|
const unique = [...new Set(onSha)];
|
|
10324
10390
|
if (unique.length === 0) return { tag: null };
|
|
@@ -10569,6 +10635,49 @@ Recovery sequence:
|
|
|
10569
10635
|
${steps.join("\n")}`
|
|
10570
10636
|
);
|
|
10571
10637
|
}
|
|
10638
|
+
async function recoverFailedRcand(deps, cause, preRcSha) {
|
|
10639
|
+
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
10640
|
+
const branch = await currentBranch(deps).catch(() => "");
|
|
10641
|
+
const mergeInProgress = await deps.run("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).then(() => true).catch(() => false);
|
|
10642
|
+
if (mergeInProgress) {
|
|
10643
|
+
try {
|
|
10644
|
+
await deps.run("git", ["merge", "--abort"]);
|
|
10645
|
+
} catch (e) {
|
|
10646
|
+
return new Error(
|
|
10647
|
+
`${causeMessage}
|
|
10648
|
+
|
|
10649
|
+
rcand failed with an in-progress merge on ${branch || "(unknown branch)"}; automatic \`git merge --abort\` failed: ${e instanceof Error ? e.message : String(e)}. Nothing was pushed to origin.
|
|
10650
|
+
Recovery sequence:
|
|
10651
|
+
1. git merge --abort
|
|
10652
|
+
2. git checkout development
|
|
10653
|
+
3. git branch -f rc origin/rc`
|
|
10654
|
+
);
|
|
10655
|
+
}
|
|
10656
|
+
}
|
|
10657
|
+
const rcSha = await deps.run("git", ["rev-parse", "rc"]).then(clean).catch(() => "");
|
|
10658
|
+
const rcDescends = Boolean(preRcSha) && Boolean(rcSha) ? await deps.run("git", ["merge-base", "--is-ancestor", preRcSha, "rc"]).then(() => true).catch(() => false) : false;
|
|
10659
|
+
if (branch === "rc" && rcDescends) {
|
|
10660
|
+
try {
|
|
10661
|
+
await deps.run("git", ["reset", "--hard", preRcSha]);
|
|
10662
|
+
await deps.run("git", ["checkout", "development"]);
|
|
10663
|
+
return new Error(
|
|
10664
|
+
`${causeMessage}
|
|
10665
|
+
|
|
10666
|
+
rcand failed after \`git checkout rc\`; local rc was reset to its pre-merge state (${shaLabel(preRcSha)}) and the checkout returned to development. Nothing was pushed to origin. Rerun \`mmi-cli rcand --apply\`.`
|
|
10667
|
+
);
|
|
10668
|
+
} catch {
|
|
10669
|
+
}
|
|
10670
|
+
}
|
|
10671
|
+
return new Error(
|
|
10672
|
+
`${causeMessage}
|
|
10673
|
+
|
|
10674
|
+
rcand failed leaving the checkout on ${branch || "(unknown)"} (pre-merge rc=${preRcSha ? shaLabel(preRcSha) : "not captured"}). Nothing was pushed to origin.
|
|
10675
|
+
Recovery sequence:
|
|
10676
|
+
1. git checkout development
|
|
10677
|
+
2. git branch -f rc origin/rc # discard the unpushed local rc merge
|
|
10678
|
+
3. mmi-cli rcand --apply`
|
|
10679
|
+
);
|
|
10680
|
+
}
|
|
10572
10681
|
async function recoverFailedFold(deps, cause, startBranch, preFoldMainSha) {
|
|
10573
10682
|
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
10574
10683
|
const probe = await probeFoldFailureState(deps);
|
|
@@ -10620,16 +10729,22 @@ async function runFoldStage(deps, startBranch, preFold, fn) {
|
|
|
10620
10729
|
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
10621
10730
|
}
|
|
10622
10731
|
}
|
|
10623
|
-
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha) {
|
|
10624
|
-
|
|
10732
|
+
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe) {
|
|
10733
|
+
let tagPush;
|
|
10734
|
+
try {
|
|
10735
|
+
tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe);
|
|
10736
|
+
} catch (e) {
|
|
10737
|
+
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
10738
|
+
}
|
|
10739
|
+
const tagPushSince = (deps.now ?? Date.now)();
|
|
10625
10740
|
const requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "main");
|
|
10626
10741
|
let checks;
|
|
10627
10742
|
try {
|
|
10628
|
-
checks = await waitForRequiredTrainChecks(deps, ctx, releaseSha, requiredChecks);
|
|
10743
|
+
checks = await waitForRequiredTrainChecks(deps, ctx, releaseSha, requiredChecks, tagPush.pushed ? tagPushSince : void 0);
|
|
10629
10744
|
} catch (e) {
|
|
10630
10745
|
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag, stage: "main" });
|
|
10631
10746
|
}
|
|
10632
|
-
await deps
|
|
10747
|
+
await runGitPush(deps, ["push", "origin", "main"]);
|
|
10633
10748
|
const releaseUrl = clean(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
10634
10749
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
|
|
10635
10750
|
const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
@@ -10670,23 +10785,34 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10670
10785
|
const deployModel2 = await preflight(deps, ctx, "rc", meta);
|
|
10671
10786
|
const releaseBase = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release base");
|
|
10672
10787
|
await releaseRcBranchLock(deps);
|
|
10788
|
+
const rcTagProbe = await probeRcResumeTags(deps, releaseBase);
|
|
10673
10789
|
await deps.run("git", ["checkout", "rc"]);
|
|
10674
10790
|
await ffOnlyPull(deps, "rc");
|
|
10675
|
-
await deps.run("git", ["
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10791
|
+
const preRcSha = requireValue(clean(await deps.run("git", ["rev-parse", "rc"])), "pre-merge rc sha");
|
|
10792
|
+
let rcSha;
|
|
10793
|
+
let resume;
|
|
10794
|
+
let tag2;
|
|
10795
|
+
let tagPush;
|
|
10796
|
+
try {
|
|
10797
|
+
await deps.run("git", ["merge", "development", "--no-edit"]);
|
|
10798
|
+
rcSha = requireValue(clean(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
|
|
10799
|
+
resume = await resolveRcResumeTag(deps, releaseBase, rcSha, rcTagProbe);
|
|
10800
|
+
tag2 = resume.tag ?? requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
|
|
10801
|
+
tagPush = await ensureTagPushed(deps, tag2, rcSha);
|
|
10802
|
+
} catch (e) {
|
|
10803
|
+
throw await recoverFailedRcand(deps, e, preRcSha);
|
|
10804
|
+
}
|
|
10679
10805
|
const resumeNote = resume.tag ? resume.note : void 0;
|
|
10680
|
-
|
|
10806
|
+
const tagPushSince = (deps.now ?? Date.now)();
|
|
10681
10807
|
const requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "rc");
|
|
10682
10808
|
let checks2;
|
|
10683
10809
|
try {
|
|
10684
|
-
checks2 = await waitForRequiredTrainChecks(deps, ctx, rcSha, requiredChecks);
|
|
10810
|
+
checks2 = await waitForRequiredTrainChecks(deps, ctx, rcSha, requiredChecks, tagPush.pushed ? tagPushSince : void 0);
|
|
10685
10811
|
} catch (e) {
|
|
10686
10812
|
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag: tag2, stage: "rc" });
|
|
10687
10813
|
}
|
|
10688
10814
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
10689
|
-
await deps
|
|
10815
|
+
await runGitPush(deps, ["push", "origin", "rc"]);
|
|
10690
10816
|
const d2 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha);
|
|
10691
10817
|
return { ...ctx, command, stage: "rc", ref: "rc", tag: tag2, deployModel: deployModel2, promoted: true, checks: checks2, resumeNote, dispatch: d2.note, runId: d2.runId, runUrl: d2.runUrl, workflowRuns: d2.workflowRuns, deployStatus: d2.deployStatus };
|
|
10692
10818
|
}
|
|
@@ -10718,6 +10844,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10718
10844
|
"development -> main merge would conflict on untolerated path(s)",
|
|
10719
10845
|
"The train is misaligned: reconcile main and development via an approved alignment PR, then rerun release."
|
|
10720
10846
|
);
|
|
10847
|
+
const tagProbe2 = { remoteSha: await probeRemoteTag(deps, tag2) };
|
|
10721
10848
|
const preFold2 = {};
|
|
10722
10849
|
const { versionFold: versionFold2, releaseSha: releaseSha2 } = await runFoldStage(deps, "development", preFold2, async () => {
|
|
10723
10850
|
await executeMergeToMain(deps, "development", "development -> main", tolerated2, predicted2, preFold2);
|
|
@@ -10725,7 +10852,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10725
10852
|
const releaseSha3 = requireValue(clean(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
10726
10853
|
return { versionFold: versionFold3, releaseSha: releaseSha3 };
|
|
10727
10854
|
});
|
|
10728
|
-
const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2);
|
|
10855
|
+
const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2);
|
|
10729
10856
|
const devRollForward2 = await rollDevelopmentForward(deps, ctx, tag2);
|
|
10730
10857
|
if (directTrack) {
|
|
10731
10858
|
return {
|
|
@@ -10801,15 +10928,16 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10801
10928
|
);
|
|
10802
10929
|
}
|
|
10803
10930
|
const releasedRcSha = clean(await deps.run("git", ["rev-parse", "origin/rc"]));
|
|
10931
|
+
const tag = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
|
|
10932
|
+
const tagProbe = { remoteSha: await probeRemoteTag(deps, tag) };
|
|
10804
10933
|
const preFold = {};
|
|
10805
|
-
const {
|
|
10934
|
+
const { versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, async () => {
|
|
10806
10935
|
await executeMergeToMain(deps, "rc", "rc -> main", tolerated, predicted, preFold);
|
|
10807
|
-
const
|
|
10808
|
-
const versionFold2 = await foldReleaseVersion(deps, deployModel, tag2, foldPaths);
|
|
10936
|
+
const versionFold2 = await foldReleaseVersion(deps, deployModel, tag, foldPaths);
|
|
10809
10937
|
const releaseSha2 = requireValue(clean(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
10810
|
-
return {
|
|
10938
|
+
return { versionFold: versionFold2, releaseSha: releaseSha2 };
|
|
10811
10939
|
});
|
|
10812
|
-
const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha);
|
|
10940
|
+
const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe);
|
|
10813
10941
|
const retirement = await retireRcRuntime(deps, ctx, deployModel, d.deployStatus, releasedRcSha);
|
|
10814
10942
|
const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
|
|
10815
10943
|
const rcAlignment = await pushRcAlignment(deps);
|
|
@@ -10843,7 +10971,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
10843
10971
|
const watch = options.watch ?? false;
|
|
10844
10972
|
const ctx = await buildTrainApplyContext(deps);
|
|
10845
10973
|
await requireCleanTree(deps);
|
|
10846
|
-
await deps
|
|
10974
|
+
await runGitRemoteRead(deps, ["fetch", "origin"]);
|
|
10847
10975
|
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
10848
10976
|
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
10849
10977
|
const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
|
|
@@ -11383,9 +11511,11 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
11383
11511
|
await deps.run("git", ["merge-base", "--is-ancestor", mergedSha, "origin/main"]).catch(() => {
|
|
11384
11512
|
throw new Error(`merged hotfix SHA ${mergedSha.slice(0, 7)} is not on origin/main \u2014 refusing to tag`);
|
|
11385
11513
|
});
|
|
11386
|
-
const
|
|
11514
|
+
const tagPush = await ensureTagPushed(deps, tag, mergedSha);
|
|
11515
|
+
const tagPushSince = Date.now();
|
|
11516
|
+
const tagNote = tagPush.note;
|
|
11387
11517
|
const required = await discoverRequiredCheckContexts(deps, ctx, "main");
|
|
11388
|
-
const checks = await waitForRequiredTrainChecks(deps, ctx, mergedSha, required);
|
|
11518
|
+
const checks = await waitForRequiredTrainChecks(deps, ctx, mergedSha, required, tagPush.pushed ? tagPushSince : void 0);
|
|
11389
11519
|
let releaseNote;
|
|
11390
11520
|
let releaseExists = false;
|
|
11391
11521
|
try {
|
|
@@ -13634,6 +13764,11 @@ function parseFofuEnabledVar(raw) {
|
|
|
13634
13764
|
if (raw === "false") return false;
|
|
13635
13765
|
throw new Error("project set: fofuEnabled must be true or false");
|
|
13636
13766
|
}
|
|
13767
|
+
function parseRuntimeVaultOnlyVar(raw) {
|
|
13768
|
+
if (raw === "true") return true;
|
|
13769
|
+
if (raw === "false") return false;
|
|
13770
|
+
throw new Error("project set: runtimeVaultOnly must be true or false");
|
|
13771
|
+
}
|
|
13637
13772
|
function parseConsumesDesignSystemVar(raw) {
|
|
13638
13773
|
if (raw === "fofu") return raw;
|
|
13639
13774
|
throw new Error('project set: consumesDesignSystem must be "fofu"');
|
|
@@ -13711,6 +13846,7 @@ var SETTABLE_VAR_KEYS = [
|
|
|
13711
13846
|
"dsManifestPath",
|
|
13712
13847
|
"fofuEnabled",
|
|
13713
13848
|
"consumesDesignSystem",
|
|
13849
|
+
"runtimeVaultOnly",
|
|
13714
13850
|
"requiredGcpApis",
|
|
13715
13851
|
"requiredRuntimeSecrets",
|
|
13716
13852
|
"requiredBuildSecrets",
|
|
@@ -13733,6 +13869,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
13733
13869
|
dsManifestPath: "relative path to a package.json, e.g. web/package.json",
|
|
13734
13870
|
fofuEnabled: "true|false",
|
|
13735
13871
|
consumesDesignSystem: '"fofu"',
|
|
13872
|
+
runtimeVaultOnly: "true|false",
|
|
13736
13873
|
repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
|
|
13737
13874
|
oauth: "JSON {subdomains,domains,callbackPath,fofuSubdomain}",
|
|
13738
13875
|
requiredGcpApis: "comma-string",
|
|
@@ -13814,6 +13951,8 @@ function buildProjectSetPatch(input) {
|
|
|
13814
13951
|
patch[key] = parsePublishRequiredVar(raw);
|
|
13815
13952
|
} else if (key === "fofuEnabled") {
|
|
13816
13953
|
patch[key] = parseFofuEnabledVar(raw);
|
|
13954
|
+
} else if (key === "runtimeVaultOnly") {
|
|
13955
|
+
patch[key] = parseRuntimeVaultOnlyVar(raw);
|
|
13817
13956
|
} else if (key === "consumesDesignSystem") {
|
|
13818
13957
|
patch[key] = parseConsumesDesignSystemVar(raw);
|
|
13819
13958
|
} else if (key === "publishDir") {
|
|
@@ -20862,7 +21001,11 @@ function trainApplyDeps() {
|
|
|
20862
21001
|
return {
|
|
20863
21002
|
run: async (file, args) => {
|
|
20864
21003
|
const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
|
|
20865
|
-
|
|
21004
|
+
try {
|
|
21005
|
+
return isWin2 && file === "npm" ? (await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP2(file, args, { timeout })).stdout;
|
|
21006
|
+
} catch (e) {
|
|
21007
|
+
throw surfaceTrainSubprocessFailure(e, file, args);
|
|
21008
|
+
}
|
|
20866
21009
|
},
|
|
20867
21010
|
runSelf: async (args) => (await execFileP2(process.execPath, [process.argv[1], ...args], { timeout: 3e4 })).stdout,
|
|
20868
21011
|
trainAuthority: async (repo) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.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",
|