@mutmutco/codex-plugin 4.5.95 → 4.5.97
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/.codex-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +275 -53
- package/cli/package.json +1 -1
- package/package.json +1 -1
package/cli/dist/main.cjs
CHANGED
|
@@ -5927,7 +5927,7 @@ async function waitForPrChecks(deps) {
|
|
|
5927
5927
|
reason = NO_CI_LIVE_CHECKS_CONTRADICTION_REASON;
|
|
5928
5928
|
deps.log?.(`merge CI policy contradiction: ${reason}; waiting for live PR checks`);
|
|
5929
5929
|
policy = "wait-for-checks";
|
|
5930
|
-
queuedStates.push(firstState);
|
|
5930
|
+
if (!deps.pollChecksWithEvidence) queuedStates.push(firstState);
|
|
5931
5931
|
} catch (e) {
|
|
5932
5932
|
if (!isRetryableGitHubWaitReadError(e)) {
|
|
5933
5933
|
return { policy, status: "failure", reason: waitReadFailureMessage(e), detail: "github-read-failed" };
|
|
@@ -5955,6 +5955,8 @@ async function waitForPrChecks(deps) {
|
|
|
5955
5955
|
let successStreak = 0;
|
|
5956
5956
|
let failureStreak = 0;
|
|
5957
5957
|
let zeroRunsStreak = 0;
|
|
5958
|
+
let evidenceHeadSha;
|
|
5959
|
+
let lastEvidence;
|
|
5958
5960
|
report("starting");
|
|
5959
5961
|
while (now() < deadline) {
|
|
5960
5962
|
if (deps.pollRateLimit) {
|
|
@@ -5986,7 +5988,17 @@ async function waitForPrChecks(deps) {
|
|
|
5986
5988
|
}
|
|
5987
5989
|
let state;
|
|
5988
5990
|
try {
|
|
5989
|
-
|
|
5991
|
+
if (queuedStates.length) state = queuedStates.shift();
|
|
5992
|
+
else if (deps.pollChecksWithEvidence) {
|
|
5993
|
+
lastEvidence = await deps.pollChecksWithEvidence();
|
|
5994
|
+
if (evidenceHeadSha !== void 0 && evidenceHeadSha !== lastEvidence.headSha) {
|
|
5995
|
+
successStreak = 0;
|
|
5996
|
+
failureStreak = 0;
|
|
5997
|
+
zeroRunsStreak = 0;
|
|
5998
|
+
}
|
|
5999
|
+
evidenceHeadSha = lastEvidence.headSha;
|
|
6000
|
+
state = lastEvidence.state;
|
|
6001
|
+
} else state = await deps.pollChecks();
|
|
5990
6002
|
} catch (e) {
|
|
5991
6003
|
if (!isRetryableGitHubWaitReadError(e)) {
|
|
5992
6004
|
return { policy, status: "failure", reason: waitReadFailureMessage(e), detail: "github-read-failed", waitedMs: now() - started };
|
|
@@ -6032,7 +6044,13 @@ async function waitForPrChecks(deps) {
|
|
|
6032
6044
|
if (state === "success") {
|
|
6033
6045
|
successStreak += 1;
|
|
6034
6046
|
if (successStreak >= PR_CHECKS_SUCCESS_CONFIRMATIONS) {
|
|
6035
|
-
return {
|
|
6047
|
+
return {
|
|
6048
|
+
policy,
|
|
6049
|
+
status: "success",
|
|
6050
|
+
reason,
|
|
6051
|
+
detail: "checks-success",
|
|
6052
|
+
...lastEvidence ? { headSha: lastEvidence.headSha, checks: lastEvidence.checks, workflowRuns: lastEvidence.workflows } : {}
|
|
6053
|
+
};
|
|
6036
6054
|
}
|
|
6037
6055
|
lastDetail = "confirming-success";
|
|
6038
6056
|
await deps.sleep(PR_CHECKS_CONFIRM_MS);
|
|
@@ -6347,6 +6365,36 @@ async function pollRestPrChecks(prNumber, repo, gh2 = defaultGhApi, requiredCont
|
|
|
6347
6365
|
const { relevant } = partitionByRequired(entries, requiredContexts);
|
|
6348
6366
|
return derivePollState(relevant);
|
|
6349
6367
|
}
|
|
6368
|
+
async function pollRestPrChecksWithWorkflowEvidence(prNumber, repo, gh2 = defaultGhApi) {
|
|
6369
|
+
const snapshot = await fetchRestPrSnapshot(prNumber, repo, gh2);
|
|
6370
|
+
const [entries, rawRuns] = await Promise.all([
|
|
6371
|
+
fetchHeadCheckEntries(snapshot.headSha, repo, gh2),
|
|
6372
|
+
gh2(["--paginate", `repos/${repo}/actions/runs?head_sha=${encodeURIComponent(snapshot.headSha)}&per_page=100`, "--jq", ".workflow_runs[] | {id, name, head_sha, status, conclusion, html_url}"])
|
|
6373
|
+
]);
|
|
6374
|
+
const checks = entries.filter((entry) => !AUXILIARY_RELEASE_CHECK_CONTEXTS.has(entry.name)).map((entry) => ({ name: entry.name, state: entry.bucket }));
|
|
6375
|
+
const workflows = parseNdjsonLines(rawRuns).flatMap((run) => {
|
|
6376
|
+
if (run.head_sha !== snapshot.headSha || typeof run.id !== "number" || typeof run.name !== "string" || typeof run.status !== "string") return [];
|
|
6377
|
+
if (AUXILIARY_RELEASE_CHECK_CONTEXTS.has(run.name)) return [];
|
|
6378
|
+
return [{
|
|
6379
|
+
id: run.id,
|
|
6380
|
+
name: run.name,
|
|
6381
|
+
headSha: snapshot.headSha,
|
|
6382
|
+
status: run.status,
|
|
6383
|
+
conclusion: typeof run.conclusion === "string" ? run.conclusion : null,
|
|
6384
|
+
...typeof run.html_url === "string" ? { url: run.html_url } : {}
|
|
6385
|
+
}];
|
|
6386
|
+
});
|
|
6387
|
+
const workflowBuckets = workflows.map((run) => {
|
|
6388
|
+
if (run.status !== "completed") return "pending";
|
|
6389
|
+
return run.conclusion === "success" || run.conclusion === "neutral" || run.conclusion === "skipped" ? "pass" : "fail";
|
|
6390
|
+
});
|
|
6391
|
+
return {
|
|
6392
|
+
state: derivePollState([...checks.map((check) => check.state), ...workflowBuckets]),
|
|
6393
|
+
headSha: snapshot.headSha,
|
|
6394
|
+
checks,
|
|
6395
|
+
workflows
|
|
6396
|
+
};
|
|
6397
|
+
}
|
|
6350
6398
|
async function pollRestCommitChecks(sha, repo, gh2 = defaultGhApi) {
|
|
6351
6399
|
return derivePollState(partitionByRequired(await fetchHeadCheckEntries(sha, repo, gh2), null).relevant);
|
|
6352
6400
|
}
|
|
@@ -7460,11 +7508,11 @@ function workflowEntry(repo, workflowPath, yamlText) {
|
|
|
7460
7508
|
};
|
|
7461
7509
|
}
|
|
7462
7510
|
var DECLARED_ENTRIES = [
|
|
7463
|
-
{ name: "mmi-backup (mm-central, mm-work
|
|
7511
|
+
{ name: "mmi-backup (mm-central, mm-work)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh; every Docker volume is tarred and every running Postgres container is pg_dumpall'd beside it (#7147; source scripts/nightly-backup.sh); owner-approved mmi-oguz rebuild as CI-only mmi-runner2 with full erasure and no backups (#7859); zuber (46.225.58.31) is gone: deleted 2026-09-18 by the owner-approved ZuberShade decommission (MMC-ZuberShade#1838, snapshot kept); Hetzner has since reassigned that IP to another machine (#7351)" },
|
|
7464
7512
|
// #7148: a backup nobody restored is a hope. Monthly, restore the latest artifact of every
|
|
7465
7513
|
// archived stage (including a stage whose containers are down) into scratch Postgres and compare
|
|
7466
7514
|
// row counts, and read-verify every storage archive against its listing; per-stage PASS/FAIL.
|
|
7467
|
-
{ name: "mmi-backup-restore-check (mm-central, mm-work
|
|
7515
|
+
{ name: "mmi-backup-restore-check (mm-central, mm-work)", cadence: "0 3 1 * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup-restore-check \u2192 /opt/mmi-control/nightly-backup-restore-check.sh; monthly restore proof for every archived stage (#7148; source scripts/nightly-backup-restore-check.sh); owner-approved mmi-oguz rebuild as CI-only mmi-runner2 with full erasure and no backups (#7859); zuber (46.225.58.31) is gone: deleted 2026-09-18 by the owner-approved ZuberShade decommission (MMC-ZuberShade#1838, snapshot kept); Hetzner has since reassigned that IP to another machine (#7351)" },
|
|
7468
7516
|
// #7662 (MM-Work#1520): Vercel Cron's 15-minute central mail call, moved to the box with MM-Work. The app
|
|
7469
7517
|
// calls its own endpoint with its own CRON_SECRET via docker exec, so the secret stays in the container.
|
|
7470
7518
|
{ name: "mm-work-mail-tick (mm-work)", cadence: "*/15 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mm-work-mail-tick \u2192 /opt/mmi-control/mm-work-mail-tick.sh; GET /api/internal/mail per stage listed in MM_WORK_MAIL_STAGES (source scripts/mm-work-mail-tick.sh; log /var/log/mm-work-mail-tick.log)" },
|
|
@@ -7472,10 +7520,11 @@ var DECLARED_ENTRIES = [
|
|
|
7472
7520
|
// setup-node before it can prune, so at 100% it dies at checkout and never prunes — the cleanup is
|
|
7473
7521
|
// unavailable exactly when it is needed. This one is root cron on the box: no checkout, no node, and
|
|
7474
7522
|
// a no-op below 90% disk, which is why 15 minutes is affordable.
|
|
7475
|
-
{ name: "mmi-runner-hygiene-oob (mmi-runner)", cadence: "*/15 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-hygiene \u2192 /opt/mmi-control/runner-hygiene-oob.sh; routine docker/cache prunes every
|
|
7523
|
+
{ name: "mmi-runner-hygiene-oob (mmi-runner, mmi-runner2)", cadence: "*/15 * * * * + 5-59/15,10-59/15 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-hygiene \u2192 /opt/mmi-control/runner-hygiene-oob.sh; routine docker/cache prunes every 15 min, relief-only pressure checks between, _work relief at >=90% disk or <8 GiB free; second-host rollout #7859 (verify each box via mmi-cli devops runtime box get <box> --ssh)" },
|
|
7476
7524
|
// #4118: Listening + systemd-active ≠ GitHub online after acquirejob 503. restart.conf cannot see
|
|
7477
7525
|
// it; an Actions workflow on mmi-live cannot heal it when half the fleet is offline. Root cron.
|
|
7478
7526
|
{ name: "mmi-runner-online-reconcile (mmi-runner)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; API-offline + _diag acquirejob, max 2 restarts/tick (verify: mmi-cli devops runtime box get mmi-runner --ssh)" },
|
|
7527
|
+
{ name: "mmi-runner-online-reconcile (mmi-runner2)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; scripts/runner2-online-reconcile.cron selects lanes 13-16, disables credential files, _diag acquirejob only, max 2 restarts/tick; rollout #7859 (verify: mmi-cli devops runtime box get mmi-runner2 --ssh)" },
|
|
7479
7528
|
// #5292: a lane that holds a dispatched JOB but produces zero output past the budget honestly
|
|
7480
7529
|
// reports "busy", so the online reconcile never fires and GitHub will not re-dispatch while the
|
|
7481
7530
|
// run holds the assignment. This tick cancels + re-triggers those runs (max 1/tick, debounced).
|
|
@@ -7483,7 +7532,7 @@ var DECLARED_ENTRIES = [
|
|
|
7483
7532
|
// #3852: the runner /tmp sweep — daily inode hygiene (03:41 UTC), deliberately the one clutter path the
|
|
7484
7533
|
// out-of-band disk-relief script must never touch. Verified installed 2026-08-18: the cron.d file and
|
|
7485
7534
|
// flock line match scripts/runner-tmp-sweep.cron exactly.
|
|
7486
|
-
{ name: "mmi-runner-tmp-sweep (mmi-runner)", cadence: "41 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-tmp-sweep \u2192 /opt/mmi-control/runner-tmp-sweep.sh; hourly inode hygiene, 6h age (#6274), flock -n (verify
|
|
7535
|
+
{ name: "mmi-runner-tmp-sweep (mmi-runner, mmi-runner2)", cadence: "41 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-tmp-sweep \u2192 /opt/mmi-control/runner-tmp-sweep.sh; hourly inode hygiene, 6h age (#6274), flock -n; second-host rollout #7859 (verify each box via mmi-cli devops runtime box get <box> --ssh)" }
|
|
7487
7536
|
// RETIRED 2026-08-18 (MMI-Hub#5234): `zuber-bake` (every 30 min) and `zuber-bake-full` (daily 00:30 UTC)
|
|
7488
7537
|
// were declared as zuber systemd timers. The ZuberShade full park (#1774, 2026-08-06) stopped them: on
|
|
7489
7538
|
// the box (46.225.58.31) zuber-bake.timer / zuber-bake-full.timer answer not-found and
|
|
@@ -13142,6 +13191,7 @@ function deriveLoopStaleness(item, loopState, now = Date.now()) {
|
|
|
13142
13191
|
};
|
|
13143
13192
|
}
|
|
13144
13193
|
var CLAIM_MARKER_PATTERN = /<!--\s*mmi-claim:\s*(\{[\s\S]*?\})\s*-->/g;
|
|
13194
|
+
var CLAIM_RELEASE_PATTERN = /<!--\s*mmi-claim-release:\s*(\{[\s\S]*?\})\s*-->/g;
|
|
13145
13195
|
var CLAIM_MARKER_LIVE_MS = 24 * 60 * 6e4;
|
|
13146
13196
|
var CLAIM_STAMP_IDLE_MS = 4 * 60 * 6e4;
|
|
13147
13197
|
function isValidClaimMarker(parsed) {
|
|
@@ -13160,10 +13210,36 @@ function parseClaimMarkers(body) {
|
|
|
13160
13210
|
}
|
|
13161
13211
|
return out;
|
|
13162
13212
|
}
|
|
13213
|
+
function isValidClaimRelease(parsed) {
|
|
13214
|
+
if (!parsed || typeof parsed !== "object") return false;
|
|
13215
|
+
const release = parsed;
|
|
13216
|
+
return typeof release.v === "number" && typeof release.ts === "string" && Number.isFinite(Date.parse(release.ts)) && (release.session === void 0 || typeof release.session === "string") && (release.surface === void 0 || typeof release.surface === "string") && (release.host === void 0 || typeof release.host === "string");
|
|
13217
|
+
}
|
|
13218
|
+
function parseClaimReleases(body) {
|
|
13219
|
+
const out = [];
|
|
13220
|
+
for (const match of body.matchAll(CLAIM_RELEASE_PATTERN)) {
|
|
13221
|
+
try {
|
|
13222
|
+
const parsed = JSON.parse(match[1]);
|
|
13223
|
+
if (isValidClaimRelease(parsed)) out.push(parsed);
|
|
13224
|
+
} catch {
|
|
13225
|
+
}
|
|
13226
|
+
}
|
|
13227
|
+
return out;
|
|
13228
|
+
}
|
|
13229
|
+
function renderClaimReleaseBody(release) {
|
|
13230
|
+
return `Released by \`${formatSessionIdentity(release)}\` at ${release.ts}.
|
|
13231
|
+
|
|
13232
|
+
<!-- mmi-claim-release: ${JSON.stringify(release)} -->`;
|
|
13233
|
+
}
|
|
13163
13234
|
function latestClaimMarker(comments) {
|
|
13164
13235
|
for (let i = comments.length - 1; i >= 0; i -= 1) {
|
|
13165
|
-
const
|
|
13166
|
-
|
|
13236
|
+
const body = comments[i].body;
|
|
13237
|
+
const markers = parseClaimMarkers(body);
|
|
13238
|
+
const releases = parseClaimReleases(body);
|
|
13239
|
+
if (!markers.length && !releases.length) continue;
|
|
13240
|
+
if (!releases.length) return markers[markers.length - 1];
|
|
13241
|
+
if (!markers.length) return void 0;
|
|
13242
|
+
return body.lastIndexOf("mmi-claim-release:") > body.lastIndexOf("mmi-claim:") ? void 0 : markers[markers.length - 1];
|
|
13167
13243
|
}
|
|
13168
13244
|
return void 0;
|
|
13169
13245
|
}
|
|
@@ -13197,7 +13273,7 @@ function claimFromMarker(item, marker, linked = {}, now = Date.now()) {
|
|
|
13197
13273
|
holder: describeClaimMarker(marker),
|
|
13198
13274
|
ageMs: now - Date.parse(marker.ts),
|
|
13199
13275
|
currentlyClaimed: item.assignees.length > 0 && item.status === "In Progress",
|
|
13200
|
-
...claimStampStaleness(marker, { activityAt: [
|
|
13276
|
+
...claimStampStaleness(marker, { activityAt: [...linked.activityAt ?? []], linkedWork: linked.linkedWork }, now)
|
|
13201
13277
|
};
|
|
13202
13278
|
}
|
|
13203
13279
|
function classifyClaimableMarker(comments, actor, now = Date.now(), activityAt = []) {
|
|
@@ -13279,7 +13355,7 @@ async function demoteLiveClaimedClaimables(scope, client, actor = describeSessio
|
|
|
13279
13355
|
warnings.push(`${item.ref} left claimable: claim-marker evidence could not be read`);
|
|
13280
13356
|
continue;
|
|
13281
13357
|
}
|
|
13282
|
-
const verdict = classifyClaimableMarker(comments, actor, deps.now
|
|
13358
|
+
const verdict = classifyClaimableMarker(comments, actor, deps.now);
|
|
13283
13359
|
if (verdict.verdict === "claimable") {
|
|
13284
13360
|
stillClaimable.push(item);
|
|
13285
13361
|
} else if (verdict.verdict === "owned") {
|
|
@@ -14200,6 +14276,20 @@ async function unclaimBoardIssue(options, deps = {}) {
|
|
|
14200
14276
|
if (!options.allowPartial) throw new Error(warning);
|
|
14201
14277
|
return { item, viewer, repo: currentRepo, status: item.status, partial: true, warning };
|
|
14202
14278
|
}
|
|
14279
|
+
try {
|
|
14280
|
+
const actor = deps.session ?? describeSessionIdentity();
|
|
14281
|
+
const release = {
|
|
14282
|
+
v: 1,
|
|
14283
|
+
session: actor.session,
|
|
14284
|
+
surface: actor.surface,
|
|
14285
|
+
host: actor.host,
|
|
14286
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
14287
|
+
};
|
|
14288
|
+
await postIssueComment(client, { ref: item.ref, body: renderClaimReleaseBody(release) });
|
|
14289
|
+
} catch (e) {
|
|
14290
|
+
process.stderr.write(`warning: ${item.ref} was unclaimed, but recording the release marker comment failed: ${ghError(e)}
|
|
14291
|
+
`);
|
|
14292
|
+
}
|
|
14203
14293
|
const optionId = cfg.statusOptions[toStatus];
|
|
14204
14294
|
let unclaimCredential = "user";
|
|
14205
14295
|
try {
|
|
@@ -15223,7 +15313,7 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
15223
15313
|
|
|
15224
15314
|
// src/gate-budget.ts
|
|
15225
15315
|
var BLESSED_RUN_WITH_BUDGET_SHA = "5ce541add569e54e7ca96de67a768577d3d604e4";
|
|
15226
|
-
var BLESSED_RUNNER_GATE_SHA = "
|
|
15316
|
+
var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
15227
15317
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
15228
15318
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
15229
15319
|
var FULL_SHA = /^[0-9a-f]{40}$/;
|
|
@@ -17255,10 +17345,10 @@ var rollout_plan_default = {
|
|
|
17255
17345
|
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)."
|
|
17256
17346
|
},
|
|
17257
17347
|
baseline: {
|
|
17258
|
-
version: "4.5.
|
|
17259
|
-
tag: "v4.5.
|
|
17260
|
-
commit: "
|
|
17261
|
-
npm: "@mutmutco/cli@4.5.
|
|
17348
|
+
version: "4.5.97",
|
|
17349
|
+
tag: "v4.5.97",
|
|
17350
|
+
commit: "72af9cdfc1b7",
|
|
17351
|
+
npm: "@mutmutco/cli@4.5.97"
|
|
17262
17352
|
},
|
|
17263
17353
|
exitCriterion: "fleet-n-of-n",
|
|
17264
17354
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17275,14 +17365,14 @@ var rollout_plan_default = {
|
|
|
17275
17365
|
repo: "mutmutco/mmi-hub",
|
|
17276
17366
|
role: "canary",
|
|
17277
17367
|
schedule: "train",
|
|
17278
|
-
v3Target: "v4.5.
|
|
17368
|
+
v3Target: "v4.5.97"
|
|
17279
17369
|
}
|
|
17280
17370
|
],
|
|
17281
17371
|
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.",
|
|
17282
17372
|
rollback: {
|
|
17283
17373
|
independent: true,
|
|
17284
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17285
|
-
v3Target: "v4.5.
|
|
17374
|
+
mechanism: "npm dist-tag latest -> 4.5.97 and redeploy the Hub Lambda from tag v4.5.97 (72af9cdfc1b7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17375
|
+
v3Target: "v4.5.97 (@mutmutco/cli@4.5.97, tag commit 72af9cdfc1b7 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17286
17376
|
}
|
|
17287
17377
|
},
|
|
17288
17378
|
{
|
|
@@ -30464,8 +30554,10 @@ function trainPlanTargetsFromTags(tags, explicitRaw) {
|
|
|
30464
30554
|
}
|
|
30465
30555
|
function derivedStagePlan(derived, shell2, stops = true) {
|
|
30466
30556
|
const { port } = derived;
|
|
30467
|
-
const
|
|
30468
|
-
const
|
|
30557
|
+
const baseOrder = ["MMI_STAGE", "MMI_PORT", "COMPOSE_PROFILES"];
|
|
30558
|
+
const projectName = derived.config.env?.COMPOSE_PROJECT_NAME?.trim();
|
|
30559
|
+
const envOrder = projectName ? [...baseOrder, "COMPOSE_PROJECT_NAME"] : baseOrder;
|
|
30560
|
+
const resolved = { MMI_STAGE: "dev", MMI_PORT: String(port), COMPOSE_PROFILES: "local", ...projectName ? { COMPOSE_PROJECT_NAME: projectName } : {} };
|
|
30469
30561
|
const ensureEnv = shell2 === "powershell" ? `if (-not (Test-Path -LiteralPath '.env')) { Copy-Item -LiteralPath '.env.example' -Destination '.env' }` : `[ -f .env ] || cp .env.example .env`;
|
|
30470
30562
|
const envPrefix = shell2 === "powershell" ? envOrder.map((k) => `$env:${k}='${resolved[k]}'`).join("; ") : envOrder.map((k) => `${k}=${resolved[k]}`).join(" ");
|
|
30471
30563
|
const build = shell2 === "powershell" ? `${envPrefix}; ${derived.config.build ?? "docker compose build --no-cache"}` : `${envPrefix} ${derived.config.build ?? "docker compose build --no-cache"}`;
|
|
@@ -37015,8 +37107,8 @@ function bothSidesText(portFailures, portLaneShas) {
|
|
|
37015
37107
|
if (rows.length === 0) return "";
|
|
37016
37108
|
return `BEFORE that --ack: ${rows.map((r) => `${r.label} also conflicts on ${r.paths.join(", ")}`).join("; ")}, which exist on both branches \u2014 the port resolves them by hand for main, so land the SAME resolution on development first (the --ack covers the whole commit, and the next release would otherwise overwrite or clash with the main-only content). `;
|
|
37017
37109
|
}
|
|
37018
|
-
function formatHotfixBatchPreflightRefusal(failures, fromSpecs, portLaneShas = /* @__PURE__ */ new Map()) {
|
|
37019
|
-
const lines2 = failures.map((f) => ` - ${f.label}: ${f.blocking.join(", ")}`);
|
|
37110
|
+
function formatHotfixBatchPreflightRefusal(failures, fromSpecs, portLaneShas = /* @__PURE__ */ new Map(), context = { sources: failures, ackedShas: [] }) {
|
|
37111
|
+
const lines2 = failures.map((f) => ` - ${f.label}: ${f.blocking.join(", ")}` + (f.afterAbortedPicks?.length ? ` (observed after aborted pick(s): ${f.afterAbortedPicks.join(", ")}; may depend on those missing changes)` : ""));
|
|
37020
37112
|
const portFailures = failures.filter((f) => portLaneShas.has(f.sha));
|
|
37021
37113
|
const synthFailures = failures.filter((f) => !portLaneShas.has(f.sha));
|
|
37022
37114
|
const head = `hotfix start batch preflight refused: ${failures.length} pick(s) would hard-stop on origin/main:
|
|
@@ -37024,15 +37116,15 @@ ${lines2.join("\n")}
|
|
|
37024
37116
|
`;
|
|
37025
37117
|
const batchFrom = fromSpecs.join(",");
|
|
37026
37118
|
const guide = "https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#hotfix-pick-refused";
|
|
37027
|
-
|
|
37028
|
-
|
|
37029
|
-
|
|
37030
|
-
|
|
37031
|
-
|
|
37032
|
-
|
|
37033
|
-
|
|
37034
|
-
${
|
|
37035
|
-
|
|
37119
|
+
const replaceNow = failures.filter((f) => !f.afterAbortedPicks?.length);
|
|
37120
|
+
const replacements = new Map(replaceNow.map((f, i) => [f.sha, `<replacementSHA${i + 1}>`]));
|
|
37121
|
+
const picks = context.sources.map((s) => replacements.get(s.sha) ?? s.sha).join(",");
|
|
37122
|
+
const exclusions = [.../* @__PURE__ */ new Set([...context.ackedShas, ...replaceNow.map((f) => f.sha)])].join(",");
|
|
37123
|
+
const mapping = replaceNow.map((f) => `${replacements.get(f.sha)} replaces ${f.label} (${f.sha})`).join("; ");
|
|
37124
|
+
const dependencyNote = failures.some((f) => f.afterAbortedPicks?.length) ? "Later failures were observed after earlier picks were aborted. Keep those original SHAs in the retry. Re-preflight the complete repaired batch before treating a later path absent on static origin/main as an independent port requirement. If a later pick still fails, replacing it requires separate approval for its exact original SHA. " : "";
|
|
37125
|
+
const portText = portFailures.length > 0 ? `Pick(s) ${portFailures.map((f) => f.label).join(", ")} have path(s) ABSENT on origin/main but present on origin/development. If they still fail after prerequisite repairs, use the main-based PORT-COMMIT lane: cut a branch FROM origin/main, add the missing paths and fix, push it and keep it pushed until the release \u2014 never merge it into development. Record the port SHA in the hotfix PR body. The NEXT rc\u2192main release must pass \`--ack <the hotfix's main commit>\` (hotfix-coverage). ` + bothSidesText(portFailures, portLaneShas) : "";
|
|
37126
|
+
const repairText = synthFailures.length > 0 ? `Pick(s) ${synthFailures.map((f) => f.label).join(", ")} need a development repair PR: cut FROM origin/development and land the conflict correction through normal CI. Its picked commit must carry the required main-clean delta; merely citing the original fixes carries no content and grants no coverage exemption. If that delta cannot be produced on development, stop and ask the owner for a scoped port plan. ` : "";
|
|
37127
|
+
return `${head}${portFailures.length > 0 && synthFailures.length > 0 ? "Mixed batch. " : ""}${dependencyNote}${portText}${repairText}Original ordered --from: ${batchFrom}. Replacement mapping: ${mapping}. Keep every passing pick in its original position and retain existing approved exclusions. Additional owner approval required for replacing/excluding original development commit(s): ${replaceNow.map((f) => f.sha).join(",")}. No cite-only marker is implemented; mmi-hotfix-carries is release verification only. Only after that explicit approval, replace the placeholders and rerun \`mmi-cli devops hotfix start --from ${picks} --ack ${exclusions}\`. Re-preflight the whole batch. Never hand-resolve onto main or merge a main-parented branch into development; see ${guide}`;
|
|
37036
37128
|
}
|
|
37037
37129
|
var CHERRY_PICKED_FROM = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
37038
37130
|
async function scanHotfixPickGaps(deps, sources, tolerated, rcRef) {
|
|
@@ -37122,7 +37214,8 @@ async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
|
37122
37214
|
failures.push({
|
|
37123
37215
|
label: source.label,
|
|
37124
37216
|
sha: source.sha,
|
|
37125
|
-
blocking: blocking.length > 0 ? blocking : [message2.split("\n")[0] ?? message2]
|
|
37217
|
+
blocking: blocking.length > 0 ? blocking : [message2.split("\n")[0] ?? message2],
|
|
37218
|
+
...failures.length > 0 ? { afterAbortedPicks: failures.map((f) => f.label) } : {}
|
|
37126
37219
|
});
|
|
37127
37220
|
}
|
|
37128
37221
|
}
|
|
@@ -37337,7 +37430,7 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
37337
37430
|
const preflight2 = await preflightHotfixBatchPicks(deps, sources, pickTolerated);
|
|
37338
37431
|
if (preflight2.failures.length > 0) {
|
|
37339
37432
|
const portLaneShas = await classifyHotfixPortLanePicks(deps, preflight2.failures);
|
|
37340
|
-
throw new Error(formatHotfixBatchPreflightRefusal(preflight2.failures, specs, portLaneShas));
|
|
37433
|
+
throw new Error(formatHotfixBatchPreflightRefusal(preflight2.failures, specs, portLaneShas, { sources, ackedShas }));
|
|
37341
37434
|
}
|
|
37342
37435
|
if (preflight2.skipped.length > 0) {
|
|
37343
37436
|
const skippedShas = new Set(preflight2.skipped.map((s) => s.sha));
|
|
@@ -39570,11 +39663,6 @@ var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["development", "main", "master
|
|
|
39570
39663
|
function isProtectedBranch(branch) {
|
|
39571
39664
|
return PROTECTED_BRANCHES.has(branch.trim());
|
|
39572
39665
|
}
|
|
39573
|
-
function deriveComposeProjectName(worktreePath) {
|
|
39574
|
-
const base = worktreePath.replace(/\\/g, "/").replace(/\/+$/, "").split("/").pop()?.toLowerCase() ?? "";
|
|
39575
|
-
const name = base.replace(/[^a-z0-9_-]+/g, "_").replace(/^[^a-z0-9]+/, "");
|
|
39576
|
-
return name || void 0;
|
|
39577
|
-
}
|
|
39578
39666
|
|
|
39579
39667
|
// src/pr-merge-helpers.ts
|
|
39580
39668
|
function buildPrMergeArgs(input) {
|
|
@@ -41787,6 +41875,53 @@ function parsePrChecksTable(stdout) {
|
|
|
41787
41875
|
}
|
|
41788
41876
|
return rows;
|
|
41789
41877
|
}
|
|
41878
|
+
function includeMissingRequiredChecks(rows, required) {
|
|
41879
|
+
const reported = new Set(rows.map((row) => row.name));
|
|
41880
|
+
const missing = [...new Set(required)].filter((name) => !reported.has(name)).map((name) => ({
|
|
41881
|
+
name,
|
|
41882
|
+
status: "missing",
|
|
41883
|
+
details: "expected required check has not reported"
|
|
41884
|
+
}));
|
|
41885
|
+
return [...rows, ...missing];
|
|
41886
|
+
}
|
|
41887
|
+
async function requiredCheckContexts(repo, prNumber) {
|
|
41888
|
+
const { stdout: prOutput } = await execFileP("gh", ["pr", "view", String(prNumber), "--repo", repo, "--json", "baseRefName"], { timeout: GC_GH_TIMEOUT_MS2 });
|
|
41889
|
+
const baseRef = JSON.parse(prOutput).baseRefName;
|
|
41890
|
+
if (typeof baseRef !== "string" || !baseRef) throw new Error("pull request response had no base branch");
|
|
41891
|
+
const read = async (path2) => {
|
|
41892
|
+
try {
|
|
41893
|
+
const { stdout } = await execFileP("gh", ["api", path2], { timeout: GC_GH_TIMEOUT_MS2 });
|
|
41894
|
+
return JSON.parse(stdout);
|
|
41895
|
+
} catch (e) {
|
|
41896
|
+
const err = e;
|
|
41897
|
+
if (/\b404\b|Not Found/i.test(`${err.stderr ?? ""} ${err.message ?? ""}`)) return null;
|
|
41898
|
+
throw e;
|
|
41899
|
+
}
|
|
41900
|
+
};
|
|
41901
|
+
const [classic, rules] = await Promise.all([
|
|
41902
|
+
read(`repos/${repo}/branches/${encodeURIComponent(baseRef)}/protection/required_status_checks`),
|
|
41903
|
+
read(`repos/${repo}/rules/branches/${encodeURIComponent(baseRef)}`)
|
|
41904
|
+
]);
|
|
41905
|
+
const contexts = /* @__PURE__ */ new Set();
|
|
41906
|
+
const oldContexts = classic?.contexts;
|
|
41907
|
+
if (Array.isArray(oldContexts)) {
|
|
41908
|
+
for (const name of oldContexts) if (typeof name === "string") contexts.add(name);
|
|
41909
|
+
}
|
|
41910
|
+
if (Array.isArray(rules)) {
|
|
41911
|
+
for (const rule of rules) {
|
|
41912
|
+
const required = rule.type === "required_status_checks" ? rule.parameters?.required_status_checks : void 0;
|
|
41913
|
+
if (Array.isArray(required)) for (const check of required) {
|
|
41914
|
+
const name = check?.context;
|
|
41915
|
+
if (typeof name === "string") contexts.add(name);
|
|
41916
|
+
}
|
|
41917
|
+
}
|
|
41918
|
+
}
|
|
41919
|
+
return [...contexts];
|
|
41920
|
+
}
|
|
41921
|
+
function checksStateWithExpected(state, rows, required) {
|
|
41922
|
+
const checks = includeMissingRequiredChecks(rows, required);
|
|
41923
|
+
return checks.some((row) => row.status === "missing") && (state === "success" || state === "no-checks-reported") ? { state: "incomplete", rows: checks } : { state, rows: checks };
|
|
41924
|
+
}
|
|
41790
41925
|
function parseLinkedIssues(body) {
|
|
41791
41926
|
const refs = /* @__PURE__ */ new Set();
|
|
41792
41927
|
const re = /\b(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved|ref|refs|re|references)\s+#(\d+)\b/gi;
|
|
@@ -42047,6 +42182,7 @@ function registerPrLifecycleCommands(program3) {
|
|
|
42047
42182
|
return stdout;
|
|
42048
42183
|
};
|
|
42049
42184
|
const withLogs = async (rows) => o.logs ? attachFailureLogs(rows, fetchLog) : rows;
|
|
42185
|
+
let expected = [];
|
|
42050
42186
|
const snap = async () => {
|
|
42051
42187
|
let stdout = "";
|
|
42052
42188
|
let stderr = "";
|
|
@@ -42061,14 +42197,15 @@ function registerPrLifecycleCommands(program3) {
|
|
|
42061
42197
|
const parsed = parseGhPrChecksResult(stdout, stderr);
|
|
42062
42198
|
const state = pollStateFromParsed(parsed);
|
|
42063
42199
|
const rows = parsePrChecksTable(stdout);
|
|
42064
|
-
return
|
|
42200
|
+
return checksStateWithExpected(state, rows, expected);
|
|
42065
42201
|
};
|
|
42066
42202
|
try {
|
|
42203
|
+
expected = await requiredCheckContexts(repo, n);
|
|
42067
42204
|
if (!o.watch) {
|
|
42068
42205
|
const { state, rows } = await snap();
|
|
42069
42206
|
const output = { state, checks: await withLogs(rows) };
|
|
42070
42207
|
console.log(JSON.stringify(output));
|
|
42071
|
-
if (state === "failure" || state === "failing") process.exitCode = 1;
|
|
42208
|
+
if (state === "failure" || state === "failing" || state === "incomplete") process.exitCode = 1;
|
|
42072
42209
|
return;
|
|
42073
42210
|
}
|
|
42074
42211
|
const deadline = Date.now() + CHECKS_WATCH_TIMEOUT_MS;
|
|
@@ -42799,11 +42936,18 @@ function parseDockerContainerLs(stdout) {
|
|
|
42799
42936
|
function containersPublishingPort(containers, port) {
|
|
42800
42937
|
return containers.filter((c) => c.publishedHostPorts.includes(port));
|
|
42801
42938
|
}
|
|
42939
|
+
function stageComposeProjectName(cwd) {
|
|
42940
|
+
const parts = cwd.replace(/\\/g, "/").replace(/\/+$/, "").split("/").filter(Boolean);
|
|
42941
|
+
const base = parts.pop() ?? "";
|
|
42942
|
+
const parent = parts.pop();
|
|
42943
|
+
const raw = parent ? `${parent}-${base}` : base;
|
|
42944
|
+
const name = raw.toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^[^a-z0-9]+/, "");
|
|
42945
|
+
return name || "mmi-stage";
|
|
42946
|
+
}
|
|
42802
42947
|
function isOwnComposeContainer(container, cwd) {
|
|
42803
42948
|
if (container.composeWorkingDir && normPath(container.composeWorkingDir) === normPath(cwd)) return true;
|
|
42804
42949
|
if (container.composeWorkingDir && pathUnder(container.composeWorkingDir, cwd)) return true;
|
|
42805
|
-
|
|
42806
|
-
return Boolean(derived && container.composeProject === derived);
|
|
42950
|
+
return container.composeProject === stageComposeProjectName(cwd);
|
|
42807
42951
|
}
|
|
42808
42952
|
function planStagePortTeardown(containers, port, cwd) {
|
|
42809
42953
|
const ownProjects = [];
|
|
@@ -43045,11 +43189,13 @@ function composeResolvesPort(cwd) {
|
|
|
43045
43189
|
return (0, import_node_fs38.existsSync)(envFile) && envFileKeys((0, import_node_fs38.readFileSync)(envFile, "utf8")).has("PORT");
|
|
43046
43190
|
}
|
|
43047
43191
|
function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
43192
|
+
const composeProject = config.env?.COMPOSE_PROJECT_NAME ?? vaultEnvMerge?.COMPOSE_PROJECT_NAME ?? stageComposeProjectName(cwd);
|
|
43048
43193
|
return {
|
|
43049
43194
|
// Vault secrets first so the stage-selection contract (MMI_STAGE/MMI_PORT/…) always wins on any collision.
|
|
43050
43195
|
...vaultEnvMerge ?? {},
|
|
43051
43196
|
...stageProcessEnv(stagePort, stageExtraEnv(config, stagePort)),
|
|
43052
|
-
...stagePort != null && !composeResolvesPort(cwd) ? { PORT: String(stagePort) } : {}
|
|
43197
|
+
...stagePort != null && !composeResolvesPort(cwd) ? { PORT: String(stagePort) } : {},
|
|
43198
|
+
COMPOSE_PROJECT_NAME: composeProject
|
|
43053
43199
|
};
|
|
43054
43200
|
}
|
|
43055
43201
|
function stageComposeFiles(cwd) {
|
|
@@ -43199,6 +43345,58 @@ async function killTree(pid) {
|
|
|
43199
43345
|
}
|
|
43200
43346
|
}
|
|
43201
43347
|
}
|
|
43348
|
+
async function describeExitedComposeServices(cwd, env) {
|
|
43349
|
+
const dockerEnv = { ...process.env, ...env };
|
|
43350
|
+
const run = (args) => execFileP3("docker", args, {
|
|
43351
|
+
cwd,
|
|
43352
|
+
windowsHide: true,
|
|
43353
|
+
timeout: DOCKER_TIMEOUT_MS,
|
|
43354
|
+
maxBuffer: 1024 * 1024 * 4,
|
|
43355
|
+
env: dockerEnv
|
|
43356
|
+
}).then(({ stdout }) => stdout);
|
|
43357
|
+
let rows;
|
|
43358
|
+
try {
|
|
43359
|
+
const text = (await run(["compose", "ps", "--all", "--format", "json"])).trim();
|
|
43360
|
+
if (!text) return void 0;
|
|
43361
|
+
try {
|
|
43362
|
+
const parsed = JSON.parse(text);
|
|
43363
|
+
rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
43364
|
+
} catch {
|
|
43365
|
+
rows = text.split(/\r?\n/).flatMap((line) => {
|
|
43366
|
+
try {
|
|
43367
|
+
return [JSON.parse(line)];
|
|
43368
|
+
} catch {
|
|
43369
|
+
return [];
|
|
43370
|
+
}
|
|
43371
|
+
});
|
|
43372
|
+
}
|
|
43373
|
+
} catch {
|
|
43374
|
+
return void 0;
|
|
43375
|
+
}
|
|
43376
|
+
const exited = rows.filter((row) => typeof row.State === "string" && /^(exited|dead)$/i.test(row.State));
|
|
43377
|
+
if (!exited.length) return void 0;
|
|
43378
|
+
const parts = [];
|
|
43379
|
+
for (const row of exited.slice(0, 3)) {
|
|
43380
|
+
const service = String(row.Service ?? row.Name ?? "unknown");
|
|
43381
|
+
let exit = row.ExitCode ?? row.exitCode;
|
|
43382
|
+
if (exit == null && row.ID) {
|
|
43383
|
+
try {
|
|
43384
|
+
exit = (await run(["inspect", "--format", "{{.State.ExitCode}}", String(row.ID)])).trim() || void 0;
|
|
43385
|
+
} catch {
|
|
43386
|
+
}
|
|
43387
|
+
}
|
|
43388
|
+
let tail = "";
|
|
43389
|
+
try {
|
|
43390
|
+
tail = (await run(["compose", "logs", "--no-color", "--tail", "50", service])).trim();
|
|
43391
|
+
} catch {
|
|
43392
|
+
}
|
|
43393
|
+
parts.push(`service '${service}' exited before health (exit ${exit ?? "unknown"})${tail ? ` \u2014 log tail:
|
|
43394
|
+
${tail}` : ""}`);
|
|
43395
|
+
}
|
|
43396
|
+
return `
|
|
43397
|
+
compose services exited before health:
|
|
43398
|
+
${parts.join("\n")}`;
|
|
43399
|
+
}
|
|
43202
43400
|
async function waitForHealth(url, timeoutMs, anyStatus = false) {
|
|
43203
43401
|
const deadline = Date.now() + timeoutMs;
|
|
43204
43402
|
let last = "";
|
|
@@ -43299,7 +43497,15 @@ async function startStage(config = {}, opts = {}) {
|
|
|
43299
43497
|
if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
|
|
43300
43498
|
else await waitForProcessStability(child2);
|
|
43301
43499
|
} catch (e) {
|
|
43500
|
+
let detail;
|
|
43501
|
+
if (state.healthUrl) {
|
|
43502
|
+
try {
|
|
43503
|
+
detail = await describeExitedComposeServices(cwd, upEnv);
|
|
43504
|
+
} catch {
|
|
43505
|
+
}
|
|
43506
|
+
}
|
|
43302
43507
|
await cleanupStageState(state, [statePath, globalStatePath], opts.timeoutMs ?? 6e4, cwd, upEnv);
|
|
43508
|
+
if (detail) throw new Error(`${e.message}${detail}`, { cause: e });
|
|
43303
43509
|
throw e;
|
|
43304
43510
|
}
|
|
43305
43511
|
const result = {
|
|
@@ -45240,6 +45446,11 @@ ${list}`);
|
|
|
45240
45446
|
} else {
|
|
45241
45447
|
printLine(`${label}: ${result.status}${result.reason ? ` \u2014 ${result.reason}` : ""}${result.detail ? ` (${result.detail})` : ""}`);
|
|
45242
45448
|
for (const line of failedChecksReceiptLines(label, result)) printLine(line);
|
|
45449
|
+
if (result.status === "success" && result.headSha) {
|
|
45450
|
+
const checks = result.checks?.map((check) => `${check.name}=${check.state}`).join(", ") || "none";
|
|
45451
|
+
const workflows = result.workflowRuns?.map((run) => `${run.name}#${run.id}=${run.status}/${run.conclusion ?? "pending"}`).join(", ") || "none";
|
|
45452
|
+
printLine(`${label}: verified head ${result.headSha}; checks: ${checks}; workflows: ${workflows}`);
|
|
45453
|
+
}
|
|
45243
45454
|
}
|
|
45244
45455
|
if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
|
|
45245
45456
|
if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
@@ -45292,6 +45503,7 @@ ${list}`);
|
|
|
45292
45503
|
// pending must hold the wait open, and a non-required red must fail it; #4396's scoping is the
|
|
45293
45504
|
// merge paths' contract (`pr land`, `pr merge --wait`), which mirror the GitHub merge button.
|
|
45294
45505
|
pollChecks: () => pollRestPrChecks(number, repo),
|
|
45506
|
+
pollChecksWithEvidence: () => pollRestPrChecksWithWorkflowEvidence(number, repo),
|
|
45295
45507
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
45296
45508
|
pollRateLimit: () => waitLoopCorePool("pr checks-wait"),
|
|
45297
45509
|
// #3388: on a confirmed red, read the failing runs' annotations so a wall-clock budget kill stops
|
|
@@ -51810,10 +52022,13 @@ function deriveStage(inputs) {
|
|
|
51810
52022
|
// #2655: only bootstrap a `.env` when the repo ships `.env.example` (legacy). Without it, the stage is
|
|
51811
52023
|
// vault-native — the runner injects the declared dev secrets into the compose process env (no disk file).
|
|
51812
52024
|
...inputs.hasEnvExample ? { ensureEnv: { example: ".env.example", target: ".env" } } : {},
|
|
51813
|
-
//
|
|
51814
|
-
//
|
|
52025
|
+
// MMI_STAGE is the local dev stage name the estate compose contract selects on (`${MMI_STAGE:-dev}`
|
|
52026
|
+
// in the seeded compose template): `development` never matched it, so a derived stage booted with
|
|
52027
|
+
// the wrong stage identity (#7870). MMI_PORT is the host-published port (the free STAGE_PORT); PORT
|
|
52028
|
+
// is intentionally absent so compose reads the container's bind port from .env rather than this
|
|
52029
|
+
// overriding it to the publish port (#1505).
|
|
51815
52030
|
env: {
|
|
51816
|
-
MMI_STAGE: "
|
|
52031
|
+
MMI_STAGE: "dev",
|
|
51817
52032
|
MMI_PORT: "$STAGE_PORT",
|
|
51818
52033
|
COMPOSE_PROFILES: "local"
|
|
51819
52034
|
}
|
|
@@ -52012,13 +52227,20 @@ function registerStageCommands(program3) {
|
|
|
52012
52227
|
const portRangeMeta = project2?.portRange ?? void 0;
|
|
52013
52228
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
52014
52229
|
const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
|
|
52230
|
+
const resolution = decideStage({
|
|
52231
|
+
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
52232
|
+
hasCompose: (0, import_node_fs48.existsSync)((0, import_node_path46.join)(process.cwd(), "docker-compose.yml")),
|
|
52233
|
+
hasEnvExample: (0, import_node_fs48.existsSync)((0, import_node_path46.join)(process.cwd(), ".env.example")),
|
|
52234
|
+
repo
|
|
52235
|
+
});
|
|
52236
|
+
if (resolution.source === "derived" && resolution.derived) {
|
|
52237
|
+
resolution.derived.config.env = {
|
|
52238
|
+
...resolution.derived.config.env,
|
|
52239
|
+
COMPOSE_PROJECT_NAME: stageComposeProjectName(process.cwd())
|
|
52240
|
+
};
|
|
52241
|
+
}
|
|
52015
52242
|
return {
|
|
52016
|
-
resolution
|
|
52017
|
-
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
52018
|
-
hasCompose: (0, import_node_fs48.existsSync)((0, import_node_path46.join)(process.cwd(), "docker-compose.yml")),
|
|
52019
|
-
hasEnvExample: (0, import_node_fs48.existsSync)((0, import_node_path46.join)(process.cwd(), ".env.example")),
|
|
52020
|
-
repo
|
|
52021
|
-
}),
|
|
52243
|
+
resolution,
|
|
52022
52244
|
project: project2,
|
|
52023
52245
|
cfg
|
|
52024
52246
|
};
|
package/cli/package.json
CHANGED