@mutmutco/cli 4.5.58 → 4.5.60
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 +413 -157
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5825,11 +5825,14 @@ function waitReadFailureMessage(e) {
|
|
|
5825
5825
|
return (stderr || err?.message || String(e)).replace(/\s+/g, " ").slice(0, 300);
|
|
5826
5826
|
}
|
|
5827
5827
|
var PR_CHECKS_POLL_MS = 3e4;
|
|
5828
|
+
function pollIntervalMs(elapsedMs) {
|
|
5829
|
+
return Math.min(4 * PR_CHECKS_POLL_MS, PR_CHECKS_POLL_MS * 2 ** Math.floor(Math.max(0, elapsedMs) / 3e5));
|
|
5830
|
+
}
|
|
5828
5831
|
var PR_CHECKS_TIMEOUT_MS = 30 * 6e4;
|
|
5829
5832
|
var PR_CHECKS_SUCCESS_CONFIRMATIONS = 2;
|
|
5830
5833
|
var PR_CHECKS_CONFIRM_MS = 5e3;
|
|
5831
5834
|
var PR_CHECKS_FAILURE_CONFIRMATIONS = 2;
|
|
5832
|
-
var PR_CHECKS_RATELIMIT_FLOOR =
|
|
5835
|
+
var PR_CHECKS_RATELIMIT_FLOOR = 1e3;
|
|
5833
5836
|
var PR_CHECKS_RATELIMIT_JITTER_MAX_MS = 5e3;
|
|
5834
5837
|
var NO_CI_LIVE_CHECKS_CONTRADICTION_REASON = "ci:none contradicted by live check runs on PR head";
|
|
5835
5838
|
var PR_CHECKS_ZERO_RUNS_GRACE_MS = 2 * 6e4;
|
|
@@ -5965,7 +5968,7 @@ async function waitForPrChecks(deps) {
|
|
|
5965
5968
|
}
|
|
5966
5969
|
if (state === "failing") {
|
|
5967
5970
|
lastDetail = "failing (a check failed; others still running)";
|
|
5968
|
-
await deps.sleep(
|
|
5971
|
+
await deps.sleep(pollIntervalMs(now() - started));
|
|
5969
5972
|
continue;
|
|
5970
5973
|
}
|
|
5971
5974
|
if (state === "success") {
|
|
@@ -6003,11 +6006,11 @@ async function waitForPrChecks(deps) {
|
|
|
6003
6006
|
} else {
|
|
6004
6007
|
zeroRunsStreak = 0;
|
|
6005
6008
|
}
|
|
6006
|
-
await deps.sleep(
|
|
6009
|
+
await deps.sleep(pollIntervalMs(now() - started));
|
|
6007
6010
|
continue;
|
|
6008
6011
|
}
|
|
6009
6012
|
lastDetail = state;
|
|
6010
|
-
await deps.sleep(
|
|
6013
|
+
await deps.sleep(pollIntervalMs(now() - started));
|
|
6011
6014
|
}
|
|
6012
6015
|
return { policy, status: "timeout", reason, detail: lastDetail, waitedMs: now() - started };
|
|
6013
6016
|
}
|
|
@@ -6044,15 +6047,38 @@ async function pollToken() {
|
|
|
6044
6047
|
pollTokenOnce ??= pollTokenProvider().catch(() => void 0);
|
|
6045
6048
|
return pollTokenOnce;
|
|
6046
6049
|
}
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6050
|
+
var INCLUDED_HEADER_BLOCK = /^(?:\r?\n)?HTTP\/\d(?:\.\d)? (\d{3})[^\n]*\n(?:[^\n]+\n)*?\r?\n/gm;
|
|
6051
|
+
function parseIncludedOutput(out) {
|
|
6052
|
+
let status;
|
|
6053
|
+
let etag;
|
|
6054
|
+
let pages = 0;
|
|
6055
|
+
const body = out.replace(INCLUDED_HEADER_BLOCK, (block, code) => {
|
|
6056
|
+
if (pages++ === 0) {
|
|
6057
|
+
status = Number(code);
|
|
6058
|
+
etag = /^etag:\s*(.+?)\s*$/im.exec(block)?.[1];
|
|
6059
|
+
}
|
|
6060
|
+
return "";
|
|
6054
6061
|
});
|
|
6055
|
-
return
|
|
6062
|
+
return { body, status, etag, pages };
|
|
6063
|
+
}
|
|
6064
|
+
var etagCache = /* @__PURE__ */ new Map();
|
|
6065
|
+
async function defaultGhApi(args) {
|
|
6066
|
+
const key = args.join("\0");
|
|
6067
|
+
const cached = etagCache.get(key);
|
|
6068
|
+
let stdout;
|
|
6069
|
+
try {
|
|
6070
|
+
({ stdout } = await execFileP("gh", ["api", "--include", ...cached ? ["-H", `If-None-Match: ${cached.etag}`] : [], ...args], {
|
|
6071
|
+
timeout: REST_GH_TIMEOUT_MS
|
|
6072
|
+
}));
|
|
6073
|
+
} catch (e) {
|
|
6074
|
+
const out = e.stdout;
|
|
6075
|
+
if (cached && typeof out === "string" && parseIncludedOutput(out).status === 304) return cached.body;
|
|
6076
|
+
throw e;
|
|
6077
|
+
}
|
|
6078
|
+
const { body, etag, pages } = parseIncludedOutput(stdout);
|
|
6079
|
+
if (etag && pages === 1) etagCache.set(key, { etag, body });
|
|
6080
|
+
else etagCache.delete(key);
|
|
6081
|
+
return body;
|
|
6056
6082
|
}
|
|
6057
6083
|
function classifyCheckRun(run) {
|
|
6058
6084
|
if (run.status !== "completed") return "pending";
|
|
@@ -8475,6 +8501,9 @@ function isValidSecretKey(key) {
|
|
|
8475
8501
|
if (key.includes("..") || key.startsWith("/") || key.includes("*")) return false;
|
|
8476
8502
|
return KEY_RE.test(key);
|
|
8477
8503
|
}
|
|
8504
|
+
function stripTrailingCRLF(value) {
|
|
8505
|
+
return value.replace(/\r\n$|\r$|\n$/, "");
|
|
8506
|
+
}
|
|
8478
8507
|
function classifyTier(_slug, key) {
|
|
8479
8508
|
const slash2 = key.indexOf("/");
|
|
8480
8509
|
if (slash2 === -1) return "project";
|
|
@@ -8624,6 +8653,9 @@ async function upgradeMessage(res, body) {
|
|
|
8624
8653
|
if (res.status !== 426) return null;
|
|
8625
8654
|
return upgradeRequiredError(res, body ?? await readJsonBody(res));
|
|
8626
8655
|
}
|
|
8656
|
+
function safeServerReason(error) {
|
|
8657
|
+
return typeof error === "string" && error.length > 0 && error.length <= 200 && /^[\x20-\x7e]+$/.test(error) && error.includes(" ") && !/[A-Za-z0-9+/=_-]{32,}/.test(error);
|
|
8658
|
+
}
|
|
8627
8659
|
async function fetchSecretValue(deps, key, opts, diagnose = false) {
|
|
8628
8660
|
if (!isValidSecretKey(key)) return null;
|
|
8629
8661
|
const repo = await targetRepo(deps, opts);
|
|
@@ -8637,8 +8669,8 @@ async function fetchSecretValue(deps, key, opts, diagnose = false) {
|
|
|
8637
8669
|
if (!res.ok) {
|
|
8638
8670
|
if (diagnose) {
|
|
8639
8671
|
const body = await readJsonBody(res);
|
|
8640
|
-
const reason = res.status === 403 ? "authority refused" : res.status === 429 ? "rate limited" : "Hub route refused";
|
|
8641
|
-
throw new Error(`${reason} (HTTP ${res.status}${
|
|
8672
|
+
const reason = res.status === 403 ? "authority refused" : res.status === 429 ? "rate limited" : body.code === "github-rate-limited" ? "Hub GitHub App allowance used up, not a denial" : "Hub route refused";
|
|
8673
|
+
throw new Error(`${reason} (HTTP ${res.status}${safeServerReason(body.error) ? `: ${body.error}` : ""})`);
|
|
8642
8674
|
}
|
|
8643
8675
|
return null;
|
|
8644
8676
|
}
|
|
@@ -9918,6 +9950,10 @@ async function secretsVerify(deps, key, opts) {
|
|
|
9918
9950
|
}
|
|
9919
9951
|
const hasProviderVerifier = Boolean(providerForSecretKey(key));
|
|
9920
9952
|
const value = await fetchSecretValue(deps, key, opts);
|
|
9953
|
+
if (value && value !== stripTrailingCRLF(value)) {
|
|
9954
|
+
deps.err(`${key}: stored value ends in a CR or LF character \u2014 likely corrupted by a piped write; re-set it with \`mmi-cli vault secrets set ${key}\``);
|
|
9955
|
+
return false;
|
|
9956
|
+
}
|
|
9921
9957
|
if (!hasProviderVerifier) {
|
|
9922
9958
|
if (!value) {
|
|
9923
9959
|
deps.err(`${key}: ABSENT or unreachable at this coordinate \u2014 no value could be read`);
|
|
@@ -10105,6 +10141,40 @@ async function secretsDeclare(deps, key, opts) {
|
|
|
10105
10141
|
deps.log(`declared ${key} (project tier, ${slug}) \u2014 catalog metadata only; ask master (secrets request) to wire it into requiredRuntimeSecrets`);
|
|
10106
10142
|
return true;
|
|
10107
10143
|
}
|
|
10144
|
+
async function secretsUndeclare(deps, key, opts) {
|
|
10145
|
+
if (!DECLARE_KEY_RE.test(key.split("@")[0] ?? "")) {
|
|
10146
|
+
deps.err(`secrets undeclare: invalid key ${JSON.stringify(key)} \u2014 must be an env-style name (UPPER_SNAKE convention), optionally @dev|rc|main`);
|
|
10147
|
+
return false;
|
|
10148
|
+
}
|
|
10149
|
+
const repo = await targetRepo(deps, opts);
|
|
10150
|
+
const slug = await vaultSlug(deps, opts);
|
|
10151
|
+
let res;
|
|
10152
|
+
try {
|
|
10153
|
+
res = await deps.fetch(`${deps.apiUrl}/projects/${slug}/secrets/undeclare`, {
|
|
10154
|
+
method: "POST",
|
|
10155
|
+
headers: await deps.headers({ "content-type": "application/json" }),
|
|
10156
|
+
body: JSON.stringify({ repo, key }),
|
|
10157
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
10158
|
+
});
|
|
10159
|
+
} catch (e) {
|
|
10160
|
+
deps.err(`secrets undeclare: ${e.message}`);
|
|
10161
|
+
return false;
|
|
10162
|
+
}
|
|
10163
|
+
const body = await readJsonBody(res);
|
|
10164
|
+
if (!res.ok) {
|
|
10165
|
+
deps.err(await upgradeMessage(res, body) ?? `secrets undeclare failed: HTTP ${res.status}${errorDetail(body)}`);
|
|
10166
|
+
return false;
|
|
10167
|
+
}
|
|
10168
|
+
const undeclared = body?.undeclared !== false;
|
|
10169
|
+
if (opts.json) {
|
|
10170
|
+
deps.log(JSON.stringify({ ok: true, key, undeclared }));
|
|
10171
|
+
return true;
|
|
10172
|
+
}
|
|
10173
|
+
deps.log(
|
|
10174
|
+
undeclared ? `undeclared ${key} (project tier, ${slug}) \u2014 catalog entry removed; the vault value was already absent` : `${key} was not declared in ${slug}'s catalog \u2014 nothing to undeclare`
|
|
10175
|
+
);
|
|
10176
|
+
return true;
|
|
10177
|
+
}
|
|
10108
10178
|
function parseRailsCredentialMapping(raw, stage) {
|
|
10109
10179
|
const eq = raw.indexOf("=");
|
|
10110
10180
|
if (eq <= 0 || eq === raw.length - 1) return null;
|
|
@@ -14688,7 +14758,7 @@ function extractForwardRefs(markdown) {
|
|
|
14688
14758
|
});
|
|
14689
14759
|
return refs;
|
|
14690
14760
|
}
|
|
14691
|
-
function checkPins(root,
|
|
14761
|
+
function checkPins(root, readFile10, docs) {
|
|
14692
14762
|
const findings = [];
|
|
14693
14763
|
for (const [doc, markdown] of Object.entries(docs)) {
|
|
14694
14764
|
for (const pin of extractPins(markdown)) {
|
|
@@ -14696,7 +14766,7 @@ function checkPins(root, readFile9, docs) {
|
|
|
14696
14766
|
findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
|
|
14697
14767
|
continue;
|
|
14698
14768
|
}
|
|
14699
|
-
const source =
|
|
14769
|
+
const source = readFile10((0, import_node_path12.join)(root, pin.file));
|
|
14700
14770
|
if (source == null) {
|
|
14701
14771
|
findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
|
|
14702
14772
|
continue;
|
|
@@ -14913,7 +14983,7 @@ function defaultTrackedFirstSegments(root, firstSegments, exec2 = import_node_ch
|
|
|
14913
14983
|
}
|
|
14914
14984
|
}
|
|
14915
14985
|
function runDocRefs(root, deps = {}) {
|
|
14916
|
-
const
|
|
14986
|
+
const readFile10 = deps.readFile ?? readFileOrNull;
|
|
14917
14987
|
const exists = deps.exists ?? import_node_fs14.existsSync;
|
|
14918
14988
|
const listDocs = deps.listDocs ?? defaultListDocs;
|
|
14919
14989
|
const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
|
|
@@ -14922,11 +14992,11 @@ function runDocRefs(root, deps = {}) {
|
|
|
14922
14992
|
const walked = listDocs(root);
|
|
14923
14993
|
const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
|
|
14924
14994
|
const docs = Object.fromEntries(
|
|
14925
|
-
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel,
|
|
14995
|
+
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile10((0, import_node_path12.join)(root, rel))]).filter(([, body]) => body != null)
|
|
14926
14996
|
);
|
|
14927
14997
|
const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs);
|
|
14928
14998
|
const findings = [
|
|
14929
|
-
...checkPins(root,
|
|
14999
|
+
...checkPins(root, readFile10, docs).findings,
|
|
14930
15000
|
...refResult.findings
|
|
14931
15001
|
];
|
|
14932
15002
|
if (commandPaths == null) {
|
|
@@ -15705,22 +15775,22 @@ function renderBootstrapAncillaryPlan(labels, environments, exceptions = []) {
|
|
|
15705
15775
|
...exceptions.map(({ name, detail }) => ` OK environment ${name} (${detail})`)
|
|
15706
15776
|
].join("\n");
|
|
15707
15777
|
}
|
|
15708
|
-
function resolveSeedContent(seed, vars,
|
|
15709
|
-
if (seed.source === "self") return
|
|
15778
|
+
function resolveSeedContent(seed, vars, readFile10) {
|
|
15779
|
+
if (seed.source === "self") return readFile10(seed.target);
|
|
15710
15780
|
if (seed.source.startsWith("seed:")) {
|
|
15711
|
-
const tmpl =
|
|
15781
|
+
const tmpl = readFile10(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
|
|
15712
15782
|
return tmpl == null ? null : renderSeed(tmpl, vars);
|
|
15713
15783
|
}
|
|
15714
15784
|
return null;
|
|
15715
15785
|
}
|
|
15716
|
-
function resolveSeedWriteContent(seed, vars,
|
|
15786
|
+
function resolveSeedWriteContent(seed, vars, readFile10, remoteContent) {
|
|
15717
15787
|
if (!seed.managedBlock) {
|
|
15718
|
-
return { ok: true, content: resolveSeedContent(seed, vars,
|
|
15788
|
+
return { ok: true, content: resolveSeedContent(seed, vars, readFile10), managed: seed.source === "managed-block" };
|
|
15719
15789
|
}
|
|
15720
|
-
const base = remoteContent ?? resolveSeedContent(seed, vars,
|
|
15790
|
+
const base = remoteContent ?? resolveSeedContent(seed, vars, readFile10);
|
|
15721
15791
|
if (base == null) return { ok: true, content: null, managed: true };
|
|
15722
15792
|
const blockSeed = { ...seed, source: seed.managedBlock.source, managedBlock: void 0 };
|
|
15723
|
-
const desired = resolveSeedContent(blockSeed, vars,
|
|
15793
|
+
const desired = resolveSeedContent(blockSeed, vars, readFile10);
|
|
15724
15794
|
if (desired == null) return { ok: true, content: null, managed: true };
|
|
15725
15795
|
const result = upsertManagedSeedBlock(base, desired, seed.managedBlock.begin, seed.managedBlock.end);
|
|
15726
15796
|
return result.ok ? { ok: true, content: result.content, managed: true } : { ok: false, reason: result.reason, managed: true };
|
|
@@ -16302,6 +16372,17 @@ function isHubControlRepo(repo) {
|
|
|
16302
16372
|
function isJervHubRepo(repo) {
|
|
16303
16373
|
return repo.toLowerCase() === JERV_HUB_REPO.toLowerCase();
|
|
16304
16374
|
}
|
|
16375
|
+
var WINDOWS_GIT_BASH_MSYSTEM = /^(?:MINGW(?:32|64)|UCRT64|CLANG(?:64|ARM64))$/u;
|
|
16376
|
+
function isWindowsGitBash(msystem) {
|
|
16377
|
+
return WINDOWS_GIT_BASH_MSYSTEM.test(msystem ?? "");
|
|
16378
|
+
}
|
|
16379
|
+
function assertGitBashOnWindowsForJervHubRelease(repo, platform2 = process.platform, env = process.env) {
|
|
16380
|
+
if (platform2 !== "win32" || !isJervHubRepo(repo)) return;
|
|
16381
|
+
if (isWindowsGitBash(env.MSYSTEM)) return;
|
|
16382
|
+
throw new Error(
|
|
16383
|
+
`${repo}: Windows Git Bash required \u2014 the release train's Jerv Gateway deploy leg refuses outside it, and the current shell is not Git Bash (MSYSTEM=${env.MSYSTEM ?? "(unset)"}). Run the release from Git Bash instead, e.g.: "C:/Program Files/Git/bin/bash.exe" -lc "mmi-cli devops release --apply"`
|
|
16384
|
+
);
|
|
16385
|
+
}
|
|
16305
16386
|
|
|
16306
16387
|
// src/train-apply-correlation.ts
|
|
16307
16388
|
var import_node_fs18 = require("node:fs");
|
|
@@ -17070,10 +17151,10 @@ var rollout_plan_default = {
|
|
|
17070
17151
|
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)."
|
|
17071
17152
|
},
|
|
17072
17153
|
baseline: {
|
|
17073
|
-
version: "4.5.
|
|
17074
|
-
tag: "v4.5.
|
|
17075
|
-
commit: "
|
|
17076
|
-
npm: "@mutmutco/cli@4.5.
|
|
17154
|
+
version: "4.5.60",
|
|
17155
|
+
tag: "v4.5.60",
|
|
17156
|
+
commit: "18fb882893af",
|
|
17157
|
+
npm: "@mutmutco/cli@4.5.60"
|
|
17077
17158
|
},
|
|
17078
17159
|
exitCriterion: "fleet-n-of-n",
|
|
17079
17160
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17090,14 +17171,14 @@ var rollout_plan_default = {
|
|
|
17090
17171
|
repo: "mutmutco/mmi-hub",
|
|
17091
17172
|
role: "canary",
|
|
17092
17173
|
schedule: "train",
|
|
17093
|
-
v3Target: "v4.5.
|
|
17174
|
+
v3Target: "v4.5.60"
|
|
17094
17175
|
}
|
|
17095
17176
|
],
|
|
17096
17177
|
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.",
|
|
17097
17178
|
rollback: {
|
|
17098
17179
|
independent: true,
|
|
17099
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17100
|
-
v3Target: "v4.5.
|
|
17180
|
+
mechanism: "npm dist-tag latest -> 4.5.60 and redeploy the Hub Lambda from tag v4.5.60 (18fb882893af); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17181
|
+
v3Target: "v4.5.60 (@mutmutco/cli@4.5.60, tag commit 18fb882893af \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17101
17182
|
}
|
|
17102
17183
|
},
|
|
17103
17184
|
{
|
|
@@ -21284,7 +21365,7 @@ function renderTenantControl(r) {
|
|
|
21284
21365
|
|
|
21285
21366
|
// src/train-apply-phases.ts
|
|
21286
21367
|
var import_node_fs24 = require("node:fs");
|
|
21287
|
-
var
|
|
21368
|
+
var import_promises4 = require("node:fs/promises");
|
|
21288
21369
|
var import_node_path24 = require("node:path");
|
|
21289
21370
|
|
|
21290
21371
|
// src/actions-billing-preflight.ts
|
|
@@ -24971,6 +25052,59 @@ function formatTrainDoctor(v) {
|
|
|
24971
25052
|
return lines2.join("\n");
|
|
24972
25053
|
}
|
|
24973
25054
|
|
|
25055
|
+
// src/release-plain-words.ts
|
|
25056
|
+
var import_promises3 = require("node:fs/promises");
|
|
25057
|
+
var PLAIN_WORDS_HEADING = "## In plain words";
|
|
25058
|
+
var PLAIN_WORDS_MARKER = "<!-- mmi:big -->";
|
|
25059
|
+
function buildPlainWordsSection(lines2, big) {
|
|
25060
|
+
const [title, ...notes] = lines2;
|
|
25061
|
+
const body = [PLAIN_WORDS_HEADING, title ?? "", ...notes.slice(0, 5).map((n) => `- ${n}`)];
|
|
25062
|
+
if (big) body.push(PLAIN_WORDS_MARKER);
|
|
25063
|
+
return body.join("\n");
|
|
25064
|
+
}
|
|
25065
|
+
function noChangePlainWordsLine(suiteTag) {
|
|
25066
|
+
return `No changes to this module in this release; ships with the MM-Work suite ${suiteTag}.`;
|
|
25067
|
+
}
|
|
25068
|
+
function upsertPlainWordsSection(body, section) {
|
|
25069
|
+
const lines2 = body.split(/\r?\n/);
|
|
25070
|
+
const at = lines2.findIndex((l) => l.trim() === PLAIN_WORDS_HEADING);
|
|
25071
|
+
let rest = body;
|
|
25072
|
+
if (at >= 0) {
|
|
25073
|
+
let end = lines2.length;
|
|
25074
|
+
for (let i = at + 1; i < lines2.length; i++) {
|
|
25075
|
+
if (lines2[i].trim().startsWith("## ")) {
|
|
25076
|
+
end = i;
|
|
25077
|
+
break;
|
|
25078
|
+
}
|
|
25079
|
+
}
|
|
25080
|
+
rest = [...lines2.slice(0, at), ...lines2.slice(end)].join("\n");
|
|
25081
|
+
}
|
|
25082
|
+
const trimmedRest = rest.trim();
|
|
25083
|
+
return trimmedRest ? `${section}
|
|
25084
|
+
|
|
25085
|
+
${trimmedRest}` : section;
|
|
25086
|
+
}
|
|
25087
|
+
function plainWordsSummaryLines(content) {
|
|
25088
|
+
return content.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).slice(0, 6);
|
|
25089
|
+
}
|
|
25090
|
+
async function writePlainWordsIfSuiteMember(deps, args) {
|
|
25091
|
+
if (args.meta?.suite !== "mm-work") return;
|
|
25092
|
+
try {
|
|
25093
|
+
const lines2 = args.summaryLines && args.summaryLines.length > 0 ? args.summaryLines : [noChangePlainWordsLine(args.suiteTag ?? args.tag)];
|
|
25094
|
+
const section = buildPlainWordsSection(lines2, args.big === true);
|
|
25095
|
+
const body = clean2(await deps.run("gh", ["release", "view", args.tag, "--repo", args.repo, "--json", "body", "-q", ".body"]));
|
|
25096
|
+
await deps.run("gh", ["release", "edit", args.tag, "--repo", args.repo, "--notes", upsertPlainWordsSection(body, section)]);
|
|
25097
|
+
} catch {
|
|
25098
|
+
}
|
|
25099
|
+
}
|
|
25100
|
+
async function writePlainWordsSection(deps, options, repo, meta, tag) {
|
|
25101
|
+
try {
|
|
25102
|
+
const summaryLines = options.announceSummaryFile ? plainWordsSummaryLines(await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(options.announceSummaryFile)) : void 0;
|
|
25103
|
+
await writePlainWordsIfSuiteMember(deps, { repo, tag, meta, summaryLines, big: options.big, suiteTag: options.suiteTag });
|
|
25104
|
+
} catch {
|
|
25105
|
+
}
|
|
25106
|
+
}
|
|
25107
|
+
|
|
24974
25108
|
// src/vercel-version-env.ts
|
|
24975
25109
|
var SKIPPED = { status: "skipped", note: "" };
|
|
24976
25110
|
function vercelVersionEnvAheadNote(key) {
|
|
@@ -25157,6 +25291,7 @@ function enforceGateBudget(deps, repo) {
|
|
|
25157
25291
|
}
|
|
25158
25292
|
}
|
|
25159
25293
|
async function preflight(deps, ctx, stage, meta, lane) {
|
|
25294
|
+
assertGitBashOnWindowsForJervHubRelease(ctx.repo);
|
|
25160
25295
|
deps.warn?.("Release: Checking release prerequisites");
|
|
25161
25296
|
if (deps.suiteCut && !authorisedSuiteCut(ctx.repo, meta, deps.suiteCut))
|
|
25162
25297
|
throw new Error(`${ctx.repo}: suite cut context does not match registry META; no deployment or tag was changed`);
|
|
@@ -25270,7 +25405,7 @@ async function waitForFoldIndexLock(deps, startBranch, resumeCommand) {
|
|
|
25270
25405
|
const deadline = now() + GIT_INDEX_LOCK_WAIT_MS;
|
|
25271
25406
|
for (; ; ) {
|
|
25272
25407
|
try {
|
|
25273
|
-
await (0,
|
|
25408
|
+
await (0, import_promises4.stat)(lockPath);
|
|
25274
25409
|
} catch (e) {
|
|
25275
25410
|
if (e.code === "ENOENT") return;
|
|
25276
25411
|
throw foldFailureGuidance(
|
|
@@ -25306,7 +25441,7 @@ async function clearFoldChildIndexLock(deps) {
|
|
|
25306
25441
|
"git index lock path"
|
|
25307
25442
|
);
|
|
25308
25443
|
try {
|
|
25309
|
-
await (0,
|
|
25444
|
+
await (0, import_promises4.unlink)(lockPath);
|
|
25310
25445
|
} catch (e) {
|
|
25311
25446
|
if (e.code === "ENOENT") return void 0;
|
|
25312
25447
|
throw e;
|
|
@@ -25905,6 +26040,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
25905
26040
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
25906
26041
|
const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
25907
26042
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
|
|
26043
|
+
await writePlainWordsSection(deps, options, ctx.repo, meta, tag);
|
|
25908
26044
|
await recordPhase(ledger, deps, ledgerAnchors, "githubRelease", phaseEntry({ state: "complete", sha: releaseSha, runUrl: releaseUrl, note: `verified GitHub Release ${tag} against ${releaseSha.slice(0, 12)}` }), { landed: "the immutable tag, the green required-check wall, and the origin/main fast-forward" });
|
|
25909
26045
|
let alignment = alignEarly ? await alignEarly() : void 0;
|
|
25910
26046
|
const prepared = prepareServedChannel(deps, { repo: ctx.repo, releaseSha, watch });
|
|
@@ -26464,6 +26600,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
26464
26600
|
steps.push(`created the GitHub Release for ${tag}`);
|
|
26465
26601
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
|
|
26466
26602
|
steps.push("verified the Release published against the tagged commit");
|
|
26603
|
+
await writePlainWordsSection(deps, options, ctx.repo, meta, tag);
|
|
26467
26604
|
await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "complete", sha: tagSha, runUrl: releaseUrl, note: "verified the Release published against the tagged commit" }), { strict: ledgerMode === "strict", landed: "the immutable tag, the re-proven wall, and the origin/main push" });
|
|
26468
26605
|
let devRollForward = await rollDevelopmentForward(deps, ctx, tag);
|
|
26469
26606
|
let rcAlignment = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
|
|
@@ -27201,6 +27338,45 @@ var REQUIRED_CONTEXTS_EMITTED_LABEL = "required check contexts match PR workflow
|
|
|
27201
27338
|
var UNBLOCKING_GATE_JOBS_LABEL = "every gate job can block a merge (#7012)";
|
|
27202
27339
|
var TAG_ADDRESSABLE_CONTEXTS_LABEL = "rc/main required contexts are tag-addressable (#3880)";
|
|
27203
27340
|
var RELEASE_BRANCH_REACHABLE_LABEL = "rc/main required contexts are reachable from a release-base head (#5193)";
|
|
27341
|
+
var DEV_STAGE_FRESH_LABEL = "tenant dev stage runs the development tip (#7536)";
|
|
27342
|
+
var DEV_STAGE_STALE_TOLERANCE_MS = 30 * 60 * 1e3;
|
|
27343
|
+
function devStageFreshnessCheck(input) {
|
|
27344
|
+
const { repo, deployedTag, tipSha, tipCommittedAt, nowMs } = input;
|
|
27345
|
+
const label = DEV_STAGE_FRESH_LABEL;
|
|
27346
|
+
const cannotAudit = (why) => ({ ok: true, label, detail: `cannot audit: ${why}` });
|
|
27347
|
+
if (!tipSha) return cannotAudit("development tip unreadable");
|
|
27348
|
+
if (!deployedTag) return cannotAudit("deployed dev SHA unreadable or never stamped");
|
|
27349
|
+
if (deployedTag.toLowerCase() === tipSha.toLowerCase()) return { ok: true, label };
|
|
27350
|
+
const ageMs = nowMs - Date.parse(tipCommittedAt ?? "");
|
|
27351
|
+
if (!Number.isFinite(ageMs)) return cannotAudit("development tip commit time unreadable");
|
|
27352
|
+
const minutes = Math.max(0, Math.round(ageMs / 6e4));
|
|
27353
|
+
const behind = `dev runs ${deployedTag.slice(0, 7)}, development is at ${tipSha.slice(0, 7)} (${minutes} min old)`;
|
|
27354
|
+
if (ageMs <= DEV_STAGE_STALE_TOLERANCE_MS) return { ok: true, label, detail: `deploy pending: ${behind}` };
|
|
27355
|
+
return {
|
|
27356
|
+
ok: false,
|
|
27357
|
+
label,
|
|
27358
|
+
detail: behind,
|
|
27359
|
+
remediation: `mmi-cli devops runtime tenant redeploy ${repo} dev --watch \u2014 then read the Hub's "dev-auto-deploy ${repo}" log lines for why the merge did not deploy`
|
|
27360
|
+
};
|
|
27361
|
+
}
|
|
27362
|
+
async function auditDevStageFreshness(repo, deps) {
|
|
27363
|
+
const facts = await deps.getDeployFacts(slugFromRepo(repo)).catch(() => null);
|
|
27364
|
+
const dev = facts?.stages.dev;
|
|
27365
|
+
if (facts && !dev?.present) return void 0;
|
|
27366
|
+
let tip;
|
|
27367
|
+
try {
|
|
27368
|
+
tip = await deps.client.rest("GET", `repos/${repo}/commits/development`);
|
|
27369
|
+
} catch {
|
|
27370
|
+
tip = void 0;
|
|
27371
|
+
}
|
|
27372
|
+
return devStageFreshnessCheck({
|
|
27373
|
+
repo,
|
|
27374
|
+
deployedTag: dev?.deployedTag,
|
|
27375
|
+
tipSha: tip?.sha,
|
|
27376
|
+
tipCommittedAt: tip?.commit?.committer?.date,
|
|
27377
|
+
nowMs: Date.now()
|
|
27378
|
+
});
|
|
27379
|
+
}
|
|
27204
27380
|
var RULESET_TRACK_SCOPE_LABEL = "product ruleset branch scope covers the release track (#6327)";
|
|
27205
27381
|
var RECONCILE_SEED_BRANCH_PREFIX = "bootstrap-seed-ruleset-ref";
|
|
27206
27382
|
var NPM_PUBLISH_SAFE_FLOOR_SHA = "b5632c8b940d8025fdb5630e576502e1397c4552";
|
|
@@ -27467,15 +27643,16 @@ async function branchPresence(deps, repo, branch) {
|
|
|
27467
27643
|
}
|
|
27468
27644
|
}
|
|
27469
27645
|
function collectRegistryRepos(projects) {
|
|
27470
|
-
const seen = /* @__PURE__ */ new
|
|
27646
|
+
const seen = /* @__PURE__ */ new Map();
|
|
27471
27647
|
for (const project2 of projects) {
|
|
27472
27648
|
for (const raw of project2.repos ?? []) {
|
|
27473
27649
|
const repo = raw.includes("/") ? raw : `mutmutco/${raw}`;
|
|
27474
|
-
|
|
27650
|
+
const key = repo.toLowerCase();
|
|
27651
|
+
if (!seen.has(key)) seen.set(key, repo);
|
|
27475
27652
|
}
|
|
27476
27653
|
}
|
|
27477
|
-
if (!
|
|
27478
|
-
return [...seen].sort((a, b) => a.localeCompare(b));
|
|
27654
|
+
if (!seen.has(HUB_REPO4.toLowerCase())) seen.set(HUB_REPO4.toLowerCase(), HUB_REPO4);
|
|
27655
|
+
return [...seen.values()].sort((a, b) => a.localeCompare(b));
|
|
27479
27656
|
}
|
|
27480
27657
|
async function prTriggeredWorkflowsOnRef(deps, repo, branch, repoClass) {
|
|
27481
27658
|
const hasGate = repoClass === "hub" ? true : await contentExists(deps, repo, branch, PRODUCT_GATE_PATH);
|
|
@@ -27522,7 +27699,29 @@ async function filterPullRequestTriggered(deps, repo, branch, paths) {
|
|
|
27522
27699
|
}
|
|
27523
27700
|
return kept;
|
|
27524
27701
|
}
|
|
27525
|
-
|
|
27702
|
+
function trackUnauditableReads(deps) {
|
|
27703
|
+
let reason;
|
|
27704
|
+
const record = (e) => {
|
|
27705
|
+
if (reason) return;
|
|
27706
|
+
const err = e;
|
|
27707
|
+
if (err?.rateLimited) reason = `GitHub App rate-limited: ${err.message ?? "rate limit exceeded"}`;
|
|
27708
|
+
else if (err?.status === 403 || err?.status === 429) reason = `HTTP ${err.status}${err.message ? `: ${err.message}` : ""}`;
|
|
27709
|
+
};
|
|
27710
|
+
const client = {
|
|
27711
|
+
...deps.client,
|
|
27712
|
+
rest: async (method, path2, init) => {
|
|
27713
|
+
try {
|
|
27714
|
+
return await deps.client.rest(method, path2, init);
|
|
27715
|
+
} catch (e) {
|
|
27716
|
+
record(e);
|
|
27717
|
+
throw e;
|
|
27718
|
+
}
|
|
27719
|
+
}
|
|
27720
|
+
};
|
|
27721
|
+
return { deps: { ...deps, client }, reason: () => reason };
|
|
27722
|
+
}
|
|
27723
|
+
async function auditRepoCi(repo, callerDeps) {
|
|
27724
|
+
const { deps, reason: unauditedReason } = trackUnauditableReads(callerDeps);
|
|
27526
27725
|
const meta = await deps.getProjectMeta(slugFromRepo(repo));
|
|
27527
27726
|
const repoClass = classifyRepo(repo, meta);
|
|
27528
27727
|
const checks = [];
|
|
@@ -27817,6 +28016,14 @@ async function auditRepoCi(repo, deps) {
|
|
|
27817
28016
|
const workflows = await getBranchWorkflows();
|
|
27818
28017
|
if (workflows) checks.push(...await auditSharedActionPins(workflows, deps));
|
|
27819
28018
|
}
|
|
28019
|
+
if (meta?.deployModel === "tenant-container" && deps.getDeployFacts) {
|
|
28020
|
+
const fresh = await auditDevStageFreshness(repo, deps);
|
|
28021
|
+
if (fresh) checks.push(fresh);
|
|
28022
|
+
}
|
|
28023
|
+
const failure = unauditedReason();
|
|
28024
|
+
if (failure) {
|
|
28025
|
+
return { repo, class: repoClass, mergePolicy: policy, ok: false, checks: [], explicitNoCi, unauditedReason: failure };
|
|
28026
|
+
}
|
|
27820
28027
|
const ok = checks.every((c) => c.ok);
|
|
27821
28028
|
return { repo, class: repoClass, mergePolicy: policy, ok, checks, explicitNoCi };
|
|
27822
28029
|
}
|
|
@@ -27858,8 +28065,8 @@ function renderCiAuditMarkdown(report) {
|
|
|
27858
28065
|
"|------|-------|--------|----|---------|"
|
|
27859
28066
|
];
|
|
27860
28067
|
for (const r of report.repos) {
|
|
27861
|
-
const gap = r.checks.find((c) => !c.ok);
|
|
27862
|
-
lines2.push(`| ${r.repo} | ${r.class} | ${r.mergePolicy} | ${r.ok ? "yes" : "no"} | ${gap
|
|
28068
|
+
const gap = r.unauditedReason ? `CANNOT AUDIT \u2014 ${r.unauditedReason}` : r.checks.find((c) => !c.ok)?.label ?? "\u2014";
|
|
28069
|
+
lines2.push(`| ${r.repo} | ${r.class} | ${r.mergePolicy} | ${r.ok ? "yes" : "no"} | ${gap} |`);
|
|
27863
28070
|
}
|
|
27864
28071
|
return lines2.join("\n");
|
|
27865
28072
|
}
|
|
@@ -27869,8 +28076,13 @@ function renderCiAuditText(report) {
|
|
|
27869
28076
|
renderCiAuditScopeLine(report)
|
|
27870
28077
|
];
|
|
27871
28078
|
for (const r of report.repos) {
|
|
28079
|
+
const verdict = r.unauditedReason ? "CANNOT AUDIT" : r.ok ? "OK" : "GAP";
|
|
27872
28080
|
lines2.push(`
|
|
27873
|
-
${r.repo} (${r.class}, policy=${r.mergePolicy}) ${
|
|
28081
|
+
${r.repo} (${r.class}, policy=${r.mergePolicy}) ${verdict}`);
|
|
28082
|
+
if (r.unauditedReason) {
|
|
28083
|
+
lines2.push(` UNAUDITED \u2014 ${r.unauditedReason}`);
|
|
28084
|
+
continue;
|
|
28085
|
+
}
|
|
27874
28086
|
for (const c of r.checks) {
|
|
27875
28087
|
lines2.push(` ${c.ok ? "OK" : "FAIL"} ${c.label}${c.detail ? ` \u2014 ${c.detail}` : ""}`);
|
|
27876
28088
|
}
|
|
@@ -30258,7 +30470,7 @@ init_hub_auth();
|
|
|
30258
30470
|
init_github_client();
|
|
30259
30471
|
|
|
30260
30472
|
// src/project-set.ts
|
|
30261
|
-
var UNSET_KEYS = ["projectAdmins", "installer", "oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary", "notifyOnDeploy", "vercelVersionEnv"];
|
|
30473
|
+
var UNSET_KEYS = ["projectAdmins", "installer", "oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary", "notifyOnDeploy", "vercelVersionEnv", "suiteHold"];
|
|
30262
30474
|
var UNSET_KEY_SET = new Set(UNSET_KEYS);
|
|
30263
30475
|
var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
|
|
30264
30476
|
var SECRET_CONSUMERS = ["runtime", "build", "lambda", "actions", "agent", "box"];
|
|
@@ -30758,6 +30970,7 @@ var SETTABLE_VAR_KEYS = [
|
|
|
30758
30970
|
"branch",
|
|
30759
30971
|
"vaultPath",
|
|
30760
30972
|
"suite",
|
|
30973
|
+
"suiteHold",
|
|
30761
30974
|
"repos",
|
|
30762
30975
|
"oauth",
|
|
30763
30976
|
"publishRequired",
|
|
@@ -30792,6 +31005,7 @@ var SETTABLE_VAR_KEY_SET = new Set(SETTABLE_VAR_KEYS);
|
|
|
30792
31005
|
var SETTABLE_VAR_HINTS = {
|
|
30793
31006
|
projectNumber: "numeric",
|
|
30794
31007
|
suite: "lowercase kebab slug, e.g. mm-work",
|
|
31008
|
+
suiteHold: 'JSON {reason, since?} \u2014 cuts the member out of the MM-Work suite train (e.g. {"reason":"frozen pending go-live proof"}); clear with --unset suiteHold (#7547)',
|
|
30795
31009
|
publishRequired: "true|false",
|
|
30796
31010
|
publishDir: "relative subpath, e.g. packages/ui",
|
|
30797
31011
|
releaseChannel: "Slack channel id, e.g. C0BE0BRGAT0",
|
|
@@ -30870,6 +31084,20 @@ function clearSecretsConfirmationError(unsets, existing, confirmed) {
|
|
|
30870
31084
|
if (confirmed) return null;
|
|
30871
31085
|
return `org project set: --unset secrets clears the WHOLE catalog for ${existing?.slug ?? "this project"} (${count} declared entr${count === 1 ? "y" : "ies"} would be removed). To drop ONE entry use --unset-secret <KEY>; re-run with --confirm-clear-secrets to wipe all ${count}.`;
|
|
30872
31086
|
}
|
|
31087
|
+
function parseSuiteHoldVar(raw) {
|
|
31088
|
+
let parsed;
|
|
31089
|
+
try {
|
|
31090
|
+
parsed = JSON.parse(raw);
|
|
31091
|
+
} catch {
|
|
31092
|
+
throw new Error('org project set: suiteHold must be a JSON object {"reason":"...","since":"..."}');
|
|
31093
|
+
}
|
|
31094
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error('org project set: suiteHold must be a JSON object {"reason":"...","since":"..."}');
|
|
31095
|
+
const hold = parsed;
|
|
31096
|
+
if (typeof hold.reason !== "string" || !hold.reason.trim()) throw new Error("org project set: suiteHold.reason must be a non-empty string naming the freeze");
|
|
31097
|
+
if (hold.since !== void 0 && (typeof hold.since !== "string" || !hold.since.trim())) throw new Error('org project set: suiteHold.since must be a non-empty string (e.g. "2026-09-28")');
|
|
31098
|
+
if (Object.keys(hold).some((k) => k !== "reason" && k !== "since")) throw new Error("org project set: suiteHold accepts only reason and since");
|
|
31099
|
+
return { reason: hold.reason, ...typeof hold.since === "string" ? { since: hold.since } : {} };
|
|
31100
|
+
}
|
|
30873
31101
|
function buildProjectSetPatch(input) {
|
|
30874
31102
|
const patch = {};
|
|
30875
31103
|
if (input.class) {
|
|
@@ -30947,6 +31175,8 @@ function buildProjectSetPatch(input) {
|
|
|
30947
31175
|
} else if (key === "suite") {
|
|
30948
31176
|
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(raw)) throw new Error("org project set: suite must be a lowercase kebab slug (e.g. mm-work)");
|
|
30949
31177
|
patch[key] = raw;
|
|
31178
|
+
} else if (key === "suiteHold") {
|
|
31179
|
+
patch[key] = parseSuiteHoldVar(raw);
|
|
30950
31180
|
} else if (key === "releaseChannel") {
|
|
30951
31181
|
patch[key] = parseReleaseChannelVar(raw);
|
|
30952
31182
|
} else if (key === "releaseLanguage") {
|
|
@@ -31356,7 +31586,7 @@ async function readSecretStdin() {
|
|
|
31356
31586
|
}
|
|
31357
31587
|
const chunks = [];
|
|
31358
31588
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
31359
|
-
return Buffer.concat(chunks).toString("utf8")
|
|
31589
|
+
return stripTrailingCRLF(Buffer.concat(chunks).toString("utf8"));
|
|
31360
31590
|
}
|
|
31361
31591
|
function makeSecretsDeps(cfg) {
|
|
31362
31592
|
return {
|
|
@@ -31508,6 +31738,10 @@ function registerSecretsCommands(program3) {
|
|
|
31508
31738
|
});
|
|
31509
31739
|
if (!ok) process.exitCode = 1;
|
|
31510
31740
|
}));
|
|
31741
|
+
secrets.command("undeclare <key>").description("project-admin self-undeclare: retire a secrets-catalog entry for your own repo (catalog metadata only \u2014 never a value); declare's exact inverse (MMI-Hub#7524). Refuses (409) while a live vault value still exists for the key at any declared stage \u2014 delete the value first (`secrets rm`), then undeclare. A key that is not declared is a no-op, not an error.").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((key, o) => withSecrets(async (d) => {
|
|
31742
|
+
const ok = await secretsUndeclare(d, key, { repo: o.repo, json: o.json });
|
|
31743
|
+
if (!ok) process.exitCode = 1;
|
|
31744
|
+
}));
|
|
31511
31745
|
secrets.command("verify <key>").description("validate a known provider secret without printing its value; a key with no configured provider verifier falls back to a value-free PRESENT/ABSENT reachability check (#3436)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--slug <slug>", "org-infra namespace (e.g. _org) for a granted org secret").action((key, o) => withSecrets(async (d) => {
|
|
31512
31746
|
const ok = await secretsVerify(d, key, o);
|
|
31513
31747
|
if (!ok) process.exitCode = 1;
|
|
@@ -33370,9 +33604,13 @@ function applyWaivers(checks, waivers) {
|
|
|
33370
33604
|
const set = new Set(waivers);
|
|
33371
33605
|
return checks.map((c) => !c.ok && set.has(c.label) ? { ...c, waived: true } : c);
|
|
33372
33606
|
}
|
|
33607
|
+
var laneReasons = (doctor) => doctor.findings.filter((f) => (f.severity === "blocker" || f.code === "origin-unverified") && f.code !== SUITE_GUARD_CODE);
|
|
33608
|
+
function onlySuiteLeadOwned(doctor) {
|
|
33609
|
+
return !doctor.ready && laneReasons(doctor).length === 0 && doctor.findings.some((f) => f.code === SUITE_GUARD_CODE);
|
|
33610
|
+
}
|
|
33373
33611
|
function laneNotEvaluatedFromThisBranch(doctor) {
|
|
33374
33612
|
if (doctor.ready) return void 0;
|
|
33375
|
-
const reasons = doctor
|
|
33613
|
+
const reasons = laneReasons(doctor);
|
|
33376
33614
|
const only = reasons.length === 1 ? reasons[0] : void 0;
|
|
33377
33615
|
return only?.code === "branch-mismatch" ? only.title : void 0;
|
|
33378
33616
|
}
|
|
@@ -33383,7 +33621,7 @@ function attachTrainDoctorVerdicts(report, trainDoctors) {
|
|
|
33383
33621
|
});
|
|
33384
33622
|
return {
|
|
33385
33623
|
...report,
|
|
33386
|
-
ok: report.ok && marked.every((doctor) => doctor.ready || doctor.notEvaluatedFromThisBranch !== void 0),
|
|
33624
|
+
ok: report.ok && marked.every((doctor) => doctor.ready || doctor.notEvaluatedFromThisBranch !== void 0 || onlySuiteLeadOwned(doctor)),
|
|
33387
33625
|
...marked.length ? { trainDoctors: marked } : {}
|
|
33388
33626
|
};
|
|
33389
33627
|
}
|
|
@@ -33397,6 +33635,8 @@ function renderBootstrapVerifyReport(report) {
|
|
|
33397
33635
|
lines2.push("", formatTrainDoctor(doctor));
|
|
33398
33636
|
if (doctor.notEvaluatedFromThisBranch) {
|
|
33399
33637
|
lines2.push(` NOT EVALUATED from this branch \u2014 ${doctor.notEvaluatedFromThisBranch}; one checkout stands on one branch, so this lane does not fail the verify (#6677). Run \`mmi-cli devops train doctor --lane ${doctor.lane}\` from that branch for its own verdict.`);
|
|
33638
|
+
} else if (onlySuiteLeadOwned(doctor)) {
|
|
33639
|
+
lines2.push(" SUITE MEMBER: this lane is run from the suite lead (mmi-cli devops suite rcand|release), so suite-member-solo does not fail the verify (#7511).");
|
|
33400
33640
|
}
|
|
33401
33641
|
}
|
|
33402
33642
|
return lines2.join("\n");
|
|
@@ -33863,7 +34103,7 @@ function registerBootstrapCommands(program3) {
|
|
|
33863
34103
|
}
|
|
33864
34104
|
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
33865
34105
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
33866
|
-
const
|
|
34106
|
+
const readFile10 = (p) => (0, import_node_fs28.existsSync)(p) ? (0, import_node_fs28.readFileSync)(p, "utf8") : null;
|
|
33867
34107
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
33868
34108
|
const putSeed = async (target, content, ref, sha) => {
|
|
33869
34109
|
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
@@ -34019,7 +34259,7 @@ function registerBootstrapCommands(program3) {
|
|
|
34019
34259
|
registryMeta?.requiredCheckBranches,
|
|
34020
34260
|
registryMeta?.requiredChecks
|
|
34021
34261
|
);
|
|
34022
|
-
const rendered = resolveSeedContent(gateSeed, gateVars,
|
|
34262
|
+
const rendered = resolveSeedContent(gateSeed, gateVars, readFile10);
|
|
34023
34263
|
if (rendered === null || missingPlaceholders(rendered).length) return failGraceful("bootstrap apply: registry gate template is unreadable or has unfilled placeholders");
|
|
34024
34264
|
const base = (await readTargetedSeedFiles(repo, scopedPlan, onlyTarget, scopedPlan.baseSha, controlClient)).base?.content ?? null;
|
|
34025
34265
|
configGateAction = planGateFromConfig(base, rendered);
|
|
@@ -34123,7 +34363,7 @@ function registerBootstrapCommands(program3) {
|
|
|
34123
34363
|
...vars,
|
|
34124
34364
|
...registryMeta?.requiredChecks?.length ? { GATE_RULESET_CONTEXTS_JSON: rulesetVars.GATE_RULESET_CONTEXTS_JSON } : {},
|
|
34125
34365
|
...registryMeta?.requiredCheckBranches?.length ? { GATE_RULESET_BRANCH_REFS_JSON: rulesetVars.GATE_RULESET_BRANCH_REFS_JSON } : {}
|
|
34126
|
-
} : vars,
|
|
34366
|
+
} : vars, readFile10, preservedContent);
|
|
34127
34367
|
if (!writeContent.ok) {
|
|
34128
34368
|
return fail(`bootstrap apply: ${resolved.target}: ${writeContent.reason} \u2014 refusing to overwrite repo-owned content`);
|
|
34129
34369
|
}
|
|
@@ -34278,7 +34518,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
34278
34518
|
}
|
|
34279
34519
|
const rulesetSeed = repo.toLowerCase() === "mutmutco/mmi-hub" ? void 0 : manifest.seeds.find((s) => s.target === ".github/rulesets/mmi-product-required-checks.json");
|
|
34280
34520
|
if (rulesetSeed) {
|
|
34281
|
-
const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars,
|
|
34521
|
+
const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile10);
|
|
34282
34522
|
if (rulesetContent) {
|
|
34283
34523
|
try {
|
|
34284
34524
|
const client = controlClient;
|
|
@@ -35139,6 +35379,16 @@ function pathOf(url) {
|
|
|
35139
35379
|
}
|
|
35140
35380
|
}
|
|
35141
35381
|
var bundlesOf = (members) => Object.fromEntries(members.map((m) => [m.id, m.bundles]));
|
|
35382
|
+
async function holdSplit(deps, modules) {
|
|
35383
|
+
const pairs = await Promise.all(modules.map(async (m) => [m, await deps.hold(m)]));
|
|
35384
|
+
return {
|
|
35385
|
+
active: pairs.filter(([, hold]) => !hold).map(([m]) => m),
|
|
35386
|
+
held: pairs.flatMap(([m, hold]) => hold ? [{ id: m.id, repo: m.repo, reason: hold.reason, ...hold.since ? { since: hold.since } : {} }] : [])
|
|
35387
|
+
};
|
|
35388
|
+
}
|
|
35389
|
+
function heldMatches(recorded, now) {
|
|
35390
|
+
return recorded.length === now.length && now.every((h) => recorded.some((r) => r.id === h.id && r.repo === h.repo));
|
|
35391
|
+
}
|
|
35142
35392
|
function suiteReleaseTag(rc) {
|
|
35143
35393
|
if (!RC_TAG.test(rc)) throw new Error(`invalid rc tag ${rc}`);
|
|
35144
35394
|
return rc.replace(/-rc\.\d+$/, "");
|
|
@@ -35157,7 +35407,7 @@ function readSuiteCut(path2) {
|
|
|
35157
35407
|
} catch {
|
|
35158
35408
|
throw new Error(`suite cut record unreadable at ${path2}; repair it, never recut`);
|
|
35159
35409
|
}
|
|
35160
|
-
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent", "skipped"].includes(record.announcement) || !record.leadReleased) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.mainPublished !== void 0 && (m.mainPublished !== true || !m.released || !!m.carried) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0)))
|
|
35410
|
+
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent", "skipped"].includes(record.announcement) || !record.leadReleased) || record.heldMembers !== void 0 && (!Array.isArray(record.heldMembers) || record.heldMembers.some((h) => !h.id || !h.repo || typeof h.reason !== "string" || !h.reason.trim() || h.since !== void 0 && typeof h.since !== "string" || record.members.some((m) => m.id === h.id))) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.mainPublished !== void 0 && (m.mainPublished !== true || !m.released || !!m.carried) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0)))
|
|
35161
35411
|
throw new Error(`suite cut record has unknown or invalid state at ${path2}; repair it, never recut`);
|
|
35162
35412
|
return record;
|
|
35163
35413
|
}
|
|
@@ -35212,8 +35462,10 @@ async function suiteRcand(deps, opts) {
|
|
|
35212
35462
|
if (!opts.ownerGo) throw new Error("suite rcand requires --owner-go (rc uses live data); nothing was written");
|
|
35213
35463
|
const members = suiteMembers(await deps.registry());
|
|
35214
35464
|
const modules = members.filter((m) => m.repo !== SUITE_REPO);
|
|
35465
|
+
const { active, held } = await holdSplit(deps, modules);
|
|
35466
|
+
const heldById = new Map(held.map((h) => [h.id, h]));
|
|
35215
35467
|
const plan = [
|
|
35216
|
-
...modules.map((m) => `${m.repo}: normal rcand + module-rc-publish.yml (once)`),
|
|
35468
|
+
...modules.map((m) => heldById.has(m.id) ? `${m.repo}: HELD: ${heldById.get(m.id).reason}` : `${m.repo}: normal rcand + module-rc-publish.yml (once)`),
|
|
35217
35469
|
`${SUITE_REPO}: one PR for both rc/main pins, wait for merge`,
|
|
35218
35470
|
`${SUITE_REPO}: rcand`
|
|
35219
35471
|
];
|
|
@@ -35233,7 +35485,7 @@ async function suiteRcand(deps, opts) {
|
|
|
35233
35485
|
const [leadDevelopment, leadPreviousRc, snapshots] = await Promise.all([
|
|
35234
35486
|
deps.head(SUITE_REPO, "development"),
|
|
35235
35487
|
deps.head(SUITE_REPO, "rc"),
|
|
35236
|
-
Promise.all(
|
|
35488
|
+
Promise.all(active.map(async (m) => {
|
|
35237
35489
|
const [backend, development, rcHead] = await Promise.all([deps.backend(m), deps.head(m.repo, "development"), deps.head(m.repo, "rc")]);
|
|
35238
35490
|
const previousRc = rcHead ?? await deps.head(m.repo, "main");
|
|
35239
35491
|
if (!backend.version || !backend.host || !development || !SHA2.test(development) || !previousRc || !SHA2.test(previousRc)) throw new Error(`${m.repo}: live backend or branch snapshot is unproven; no cut started`);
|
|
@@ -35241,12 +35493,14 @@ async function suiteRcand(deps, opts) {
|
|
|
35241
35493
|
}))
|
|
35242
35494
|
]);
|
|
35243
35495
|
if (!leadDevelopment || !leadPreviousRc || !SHA2.test(leadDevelopment) || !SHA2.test(leadPreviousRc)) throw new Error("MM-Work development/rc SHA unproven; no cut started");
|
|
35244
|
-
const created = { schema: SCHEMA, lead: SUITE_REPO, leadDevelopment, leadPreviousRc, members: snapshots };
|
|
35496
|
+
const created = { schema: SCHEMA, lead: SUITE_REPO, leadDevelopment, leadPreviousRc, members: snapshots, ...held.length ? { heldMembers: held } : {} };
|
|
35245
35497
|
writeSuiteCut(deps.recordPath, created);
|
|
35246
35498
|
record = created;
|
|
35247
35499
|
}
|
|
35248
35500
|
if (!record) throw new Error("suite cut record unavailable after initialization");
|
|
35249
|
-
if (
|
|
35501
|
+
if (!heldMatches(record.heldMembers ?? [], held))
|
|
35502
|
+
throw new Error("suite hold changed since cut began; finish or abandon this cut, then re-cut with the new holds");
|
|
35503
|
+
if (record.members.length !== active.length || active.some((m) => !record.members.some((r) => r.repo === m.repo && r.id === m.id)))
|
|
35250
35504
|
throw new Error("suite roster changed since cut began; refuse to omit or add a member");
|
|
35251
35505
|
const quiet = [];
|
|
35252
35506
|
for (const member of record.members) {
|
|
@@ -35316,7 +35570,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
|
|
|
35316
35570
|
}
|
|
35317
35571
|
const pins = suitePins(record, bundlesOf(modules));
|
|
35318
35572
|
await deps.verifyLeadCut(record.leadDevelopment);
|
|
35319
|
-
const pr = await deps.pin({ cut: record.leadDevelopment, pins });
|
|
35573
|
+
const pr = await deps.pin({ cut: record.leadDevelopment, pins, heldIds: record.heldMembers?.map((h) => h.id) });
|
|
35320
35574
|
await deps.verifyLeadCut(record.leadDevelopment);
|
|
35321
35575
|
if (record.pinPr && record.pinPr !== pr.pr) throw new Error(`pin PR changed from ${record.pinPr} to ${pr.pr}; stop`);
|
|
35322
35576
|
record.pinPr = pr.pr;
|
|
@@ -35333,12 +35587,14 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
|
|
|
35333
35587
|
}
|
|
35334
35588
|
return { plan, record };
|
|
35335
35589
|
}
|
|
35336
|
-
async function suiteRelease(deps, ownerGo) {
|
|
35590
|
+
async function suiteRelease(deps, ownerGo, big = false) {
|
|
35337
35591
|
if (!ownerGo) throw new Error("suite release requires --owner-go; nothing was written");
|
|
35338
35592
|
const record = readSuiteCut(deps.recordPath);
|
|
35339
35593
|
if (!record?.pinMerged || !record.leadRcTag) throw new Error("no completed suite rc cut and merged pin PR; release refused");
|
|
35340
35594
|
const members = suiteMembers(await deps.registry()).filter((m) => m.repo !== SUITE_REPO);
|
|
35341
|
-
|
|
35595
|
+
const { active, held } = await holdSplit(deps, members);
|
|
35596
|
+
if (!heldMatches(record.heldMembers ?? [], held)) throw new Error("suite hold changed after the cut; release refused");
|
|
35597
|
+
if (active.length !== record.members.length || active.some((m) => !record.members.some((r) => r.repo === m.repo && r.id === m.id))) throw new Error("suite roster changed after the cut; release refused");
|
|
35342
35598
|
await deps.verifyPins(suitePins(record, bundlesOf(members)), record.leadRcTag);
|
|
35343
35599
|
for (const member of record.members) {
|
|
35344
35600
|
if (member.carried) {
|
|
@@ -35384,7 +35640,7 @@ Each member's live release must serve <data>/main/module.js at its main pin path
|
|
|
35384
35640
|
console.warn(`${member.repo}: released as ${member.releasedAs ?? suiteReleaseTag(member.tag)} and hotfixed to ${member.hotfixedTo}, which is live; recorded as released (#7482)`);
|
|
35385
35641
|
} else {
|
|
35386
35642
|
if (member.releasedAs && member.releasedAs !== suiteReleaseTag(member.tag)) console.warn(`${member.repo}: its rc already shipped as ${member.releasedAs}, not ${suiteReleaseTag(member.tag)}; finishing that release (#7464)`);
|
|
35387
|
-
await deps.release(member.repo, member.releasedAs ?? suiteReleaseTag(member.tag), member.sha);
|
|
35643
|
+
await deps.release(member.repo, member.releasedAs ?? suiteReleaseTag(member.tag), member.sha, { suiteTag: suiteReleaseTag(record.leadRcTag) });
|
|
35388
35644
|
}
|
|
35389
35645
|
member.released = true;
|
|
35390
35646
|
writeSuiteCut(deps.recordPath, record);
|
|
@@ -35396,7 +35652,7 @@ Each member's live release must serve <data>/main/module.js at its main pin path
|
|
|
35396
35652
|
}
|
|
35397
35653
|
}
|
|
35398
35654
|
if (!record.leadReleased) {
|
|
35399
|
-
await deps.release(SUITE_REPO, suiteReleaseTag(record.leadRcTag), record.leadRcSha);
|
|
35655
|
+
await deps.release(SUITE_REPO, suiteReleaseTag(record.leadRcTag), record.leadRcSha, { big });
|
|
35400
35656
|
record.leadReleased = true;
|
|
35401
35657
|
writeSuiteCut(deps.recordPath, record);
|
|
35402
35658
|
}
|
|
@@ -35424,14 +35680,14 @@ var import_node_path43 = require("node:path");
|
|
|
35424
35680
|
init_cli_shared();
|
|
35425
35681
|
|
|
35426
35682
|
// src/command-register-train-operations.ts
|
|
35427
|
-
var
|
|
35683
|
+
var import_promises9 = require("node:fs/promises");
|
|
35428
35684
|
init_clean_exit();
|
|
35429
35685
|
init_cli_shared();
|
|
35430
35686
|
init_client_version();
|
|
35431
35687
|
init_house_map();
|
|
35432
35688
|
|
|
35433
35689
|
// src/hotfix-apply.ts
|
|
35434
|
-
var
|
|
35690
|
+
var import_promises5 = require("node:fs/promises");
|
|
35435
35691
|
|
|
35436
35692
|
// src/release-announce.ts
|
|
35437
35693
|
var ANNOUNCE_REPO = "mutmutco/MMI-Hub";
|
|
@@ -36400,7 +36656,7 @@ async function runHotfixReleaseLocked(deps, versionInput, options, doctor) {
|
|
|
36400
36656
|
}
|
|
36401
36657
|
let lines2;
|
|
36402
36658
|
try {
|
|
36403
|
-
lines2 = summaryFileLines(await (deps.readFile ?? ((p) => (0,
|
|
36659
|
+
lines2 = summaryFileLines(await (deps.readFile ?? ((p) => (0, import_promises5.readFile)(p, "utf8")))(options.announceSummaryFile));
|
|
36404
36660
|
} catch (e) {
|
|
36405
36661
|
throw new Error(`could not read --announce-summary-file ${options.announceSummaryFile}: ${e.message} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`);
|
|
36406
36662
|
}
|
|
@@ -36451,6 +36707,7 @@ ${decision.note}`);
|
|
|
36451
36707
|
await deps.run("gh", ["release", "create", tag, "--repo", ctx.repo, "--target", "main", "--generate-notes", "--latest"]);
|
|
36452
36708
|
releaseNote = `Release ${tag} created (target main)`;
|
|
36453
36709
|
}
|
|
36710
|
+
await writePlainWordsSection(deps, options, ctx.repo, meta, tag);
|
|
36454
36711
|
await recordHotfixPhase(ledger, anchors, "githubRelease", { state: "complete", sha: mergedSha, note: releaseNote }, "the immutable tag, the green wall, and origin/main");
|
|
36455
36712
|
const prepared = prepareServedChannel(deps, { repo: ctx.repo, releaseSha: mergedSha, watch: true });
|
|
36456
36713
|
const runs = [];
|
|
@@ -37775,8 +38032,8 @@ function annotateChangeMeaning(changed, policy, read) {
|
|
|
37775
38032
|
function isMeaningfulRow(file) {
|
|
37776
38033
|
return file.meaningful !== false;
|
|
37777
38034
|
}
|
|
37778
|
-
function loadPolicy(root,
|
|
37779
|
-
const raw =
|
|
38035
|
+
function loadPolicy(root, readFile10 = readFileOrNull2) {
|
|
38036
|
+
const raw = readFile10((0, import_node_path30.join)(root, POLICY_FILE));
|
|
37780
38037
|
if (raw == null) return { mandatory: [], declared: false };
|
|
37781
38038
|
return parsePolicy(raw, POLICY_FILE);
|
|
37782
38039
|
}
|
|
@@ -38724,12 +38981,16 @@ function releaseProgressVerdict(status, subject = "Release") {
|
|
|
38724
38981
|
}
|
|
38725
38982
|
|
|
38726
38983
|
// src/suite-pin.ts
|
|
38727
|
-
var
|
|
38984
|
+
var import_promises6 = require("node:fs/promises");
|
|
38728
38985
|
var import_node_path34 = require("node:path");
|
|
38729
38986
|
async function openSuiteCutPinPr(deps, input) {
|
|
38730
38987
|
if (!/^[a-f0-9]{40}$/.test(input.cut) || !input.pins.length) throw new Error("suite pin PR needs an anchored cut and at least one module");
|
|
38731
38988
|
const ids = input.pins.map((p) => p.module);
|
|
38732
38989
|
if (new Set(ids).size !== ids.length) throw new Error("suite pin PR contains a duplicate module");
|
|
38990
|
+
const heldIds = input.heldIds ?? [];
|
|
38991
|
+
if (new Set(heldIds).size !== heldIds.length) throw new Error("suite pin PR contains a duplicate held member");
|
|
38992
|
+
const covered = [...ids, ...heldIds];
|
|
38993
|
+
if (new Set(covered).size !== covered.length) throw new Error("suite pin PR holds a member it also pins");
|
|
38733
38994
|
const branch = `suite-pin/cut-${input.cut.slice(0, 16)}`;
|
|
38734
38995
|
const registry2 = async (ref = "development") => {
|
|
38735
38996
|
const get = JSON.parse(await deps.run("gh", ["api", `repos/${SUITE_REPO}/contents/seed/modules.json?ref=${encodeURIComponent(ref)}`]));
|
|
@@ -38740,7 +39001,7 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
38740
39001
|
try {
|
|
38741
39002
|
const rows = await registry2();
|
|
38742
39003
|
const members = suiteMembers(rows).filter((m) => m.repo !== SUITE_REPO);
|
|
38743
|
-
if (members.length !==
|
|
39004
|
+
if (members.length !== covered.length || members.some((m) => !covered.includes(m.id))) throw new Error("cut pins do not match the complete MM-Work roster");
|
|
38744
39005
|
for (const pin of input.pins) {
|
|
38745
39006
|
for (const url of [pin.rc, pin.main]) {
|
|
38746
39007
|
const parsed = new URL(url);
|
|
@@ -38762,8 +39023,8 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
38762
39023
|
});
|
|
38763
39024
|
if (prs.length && matches(await registry2(branch))) return { status: "pending", pr: prs[0].url };
|
|
38764
39025
|
if (!prs.length && matches(rows)) throw new Error("pins already match without a merged cut PR \u2014 provenance unverified");
|
|
38765
|
-
await (0,
|
|
38766
|
-
scratch = await (0,
|
|
39026
|
+
await (0, import_promises6.mkdir)((0, import_node_path34.join)(deps.root, "tmp"), { recursive: true });
|
|
39027
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path34.join)(deps.root, "tmp", "suite-pin-"));
|
|
38767
39028
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
38768
39029
|
await run("git", ["clone", "--quiet", "--branch", prs.length ? branch : "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
38769
39030
|
if (prs.length) {
|
|
@@ -38776,7 +39037,7 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
38776
39037
|
}
|
|
38777
39038
|
for (const stage of ["rc", "main"]) {
|
|
38778
39039
|
const path2 = (0, import_node_path34.join)(scratch, `${stage}-pins.json`);
|
|
38779
|
-
await (0,
|
|
39040
|
+
await (0, import_promises6.writeFile)(path2, JSON.stringify(input.pins.map((p) => ({ module: p.module, url: p[stage] }))));
|
|
38780
39041
|
await run("node", ["scripts/pin-modules.mjs", "--stage", stage, "--pins", path2]);
|
|
38781
39042
|
}
|
|
38782
39043
|
await run("git", ["add", "seed/modules.json"]);
|
|
@@ -38806,7 +39067,7 @@ async function openSuiteCutPinPr(deps, input) {
|
|
|
38806
39067
|
} catch (e) {
|
|
38807
39068
|
throw new Error(`suite cut pin PR FAILED: ${e.message}; merge/verify the single ${SUITE_REPO} development pin PR, then rerun the cut (never recut member tags)`);
|
|
38808
39069
|
} finally {
|
|
38809
|
-
if (scratch) await (0,
|
|
39070
|
+
if (scratch) await (0, import_promises6.rm)(scratch, { recursive: true, force: true });
|
|
38810
39071
|
}
|
|
38811
39072
|
}
|
|
38812
39073
|
async function openSuitePinPr(deps, input) {
|
|
@@ -38825,8 +39086,8 @@ async function openSuitePinPr(deps, input) {
|
|
|
38825
39086
|
url.searchParams.set("v", tag);
|
|
38826
39087
|
const pin = url.toString();
|
|
38827
39088
|
const branch = `suite-pin/${stage}-${member.id}-${tag}`;
|
|
38828
|
-
await (0,
|
|
38829
|
-
scratch = await (0,
|
|
39089
|
+
await (0, import_promises6.mkdir)((0, import_node_path34.join)(deps.root, "tmp"), { recursive: true });
|
|
39090
|
+
scratch = await (0, import_promises6.mkdtemp)((0, import_node_path34.join)(deps.root, "tmp", "suite-pin-"));
|
|
38830
39091
|
const run = (file, args) => deps.run(file, args, scratch);
|
|
38831
39092
|
await run("git", ["clone", "--quiet", "--branch", "development", "--single-branch", `https://github.com/${SUITE_REPO}.git`, "."]);
|
|
38832
39093
|
const existing = JSON.parse(await run("gh", ["pr", "list", "--repo", SUITE_REPO, "--head", branch, "--state", "all", "--json", "url,state"]));
|
|
@@ -38864,14 +39125,14 @@ async function openSuitePinPr(deps, input) {
|
|
|
38864
39125
|
} catch (e) {
|
|
38865
39126
|
throw new Error(`suite pin PR FAILED for ${repo} ${tag}: ${e.message}; remedy: ${remedy}`);
|
|
38866
39127
|
} finally {
|
|
38867
|
-
if (scratch) await (0,
|
|
39128
|
+
if (scratch) await (0, import_promises6.rm)(scratch, { recursive: true, force: true });
|
|
38868
39129
|
}
|
|
38869
39130
|
}
|
|
38870
39131
|
|
|
38871
39132
|
// src/command-register-collaboration.ts
|
|
38872
39133
|
var import_node_child_process17 = require("node:child_process");
|
|
38873
39134
|
var import_node_fs42 = require("node:fs");
|
|
38874
|
-
var
|
|
39135
|
+
var import_promises8 = require("node:fs/promises");
|
|
38875
39136
|
init_clean_exit();
|
|
38876
39137
|
init_cli_shared();
|
|
38877
39138
|
init_error_codes();
|
|
@@ -40144,7 +40405,7 @@ function postMergeReconWarnings(input) {
|
|
|
40144
40405
|
}
|
|
40145
40406
|
|
|
40146
40407
|
// src/pr-commands.ts
|
|
40147
|
-
var
|
|
40408
|
+
var import_promises7 = require("node:fs/promises");
|
|
40148
40409
|
init_cli_shared();
|
|
40149
40410
|
init_error_codes();
|
|
40150
40411
|
var GC_GH_TIMEOUT_MS2 = 2e4;
|
|
@@ -40298,10 +40559,10 @@ function registerPrLifecycleCommands(program3) {
|
|
|
40298
40559
|
let body;
|
|
40299
40560
|
try {
|
|
40300
40561
|
if (o.title || o.titleFile) {
|
|
40301
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
40562
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
40302
40563
|
}
|
|
40303
40564
|
if (o.body || o.bodyFile) {
|
|
40304
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
40565
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
40305
40566
|
}
|
|
40306
40567
|
} catch (e) {
|
|
40307
40568
|
return fail(`pr edit: ${e.message}`);
|
|
@@ -40331,7 +40592,7 @@ function registerPrLifecycleCommands(program3) {
|
|
|
40331
40592
|
}
|
|
40332
40593
|
let body;
|
|
40333
40594
|
try {
|
|
40334
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
40595
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
40335
40596
|
} catch (e) {
|
|
40336
40597
|
return fail(`pr comment: ${e.message}`);
|
|
40337
40598
|
}
|
|
@@ -42112,6 +42373,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
42112
42373
|
try {
|
|
42113
42374
|
entries = (0, import_node_fs40.readdirSync)(dir, { withFileTypes: true });
|
|
42114
42375
|
} catch (e) {
|
|
42376
|
+
if (e.code === "ENOENT") continue;
|
|
42115
42377
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
42116
42378
|
}
|
|
42117
42379
|
for (const entry of entries) {
|
|
@@ -42691,34 +42953,6 @@ function renderResidueSweepLines(report) {
|
|
|
42691
42953
|
return lines2;
|
|
42692
42954
|
}
|
|
42693
42955
|
|
|
42694
|
-
// src/pr-deploy-dev.ts
|
|
42695
|
-
function slugOfRepo(repo) {
|
|
42696
|
-
return repo.split("/")[1]?.toLowerCase() ?? "";
|
|
42697
|
-
}
|
|
42698
|
-
async function planDevDeployOnDevelopmentMerge(repo, baseRef, deps) {
|
|
42699
|
-
if (baseRef !== "development") return { applicable: false, reason: "not-development-base" };
|
|
42700
|
-
const slug = slugOfRepo(repo);
|
|
42701
|
-
if (!slug) return { applicable: false, reason: "unreadable" };
|
|
42702
|
-
let project2;
|
|
42703
|
-
try {
|
|
42704
|
-
project2 = await fetchProjectBySlug(slug, deps);
|
|
42705
|
-
} catch {
|
|
42706
|
-
return { applicable: false, reason: "unreadable" };
|
|
42707
|
-
}
|
|
42708
|
-
const model = project2?.deployModel;
|
|
42709
|
-
if (!model || !isCentralDispatchModel(model)) return { applicable: false, reason: "not-tenant" };
|
|
42710
|
-
return {
|
|
42711
|
-
applicable: true,
|
|
42712
|
-
// The issue's fallback ask, verbatim-shaped: the gap must be visible and actionable at the moment it opens.
|
|
42713
|
-
manualPointer: `dev not redeployed \u2014 run \`mmi-cli devops runtime tenant redeploy ${repo} dev\``
|
|
42714
|
-
};
|
|
42715
|
-
}
|
|
42716
|
-
async function dispatchDevDeploy(repo, deps) {
|
|
42717
|
-
const res = await tenantDeploy({ repo, stage: "dev" }, deps);
|
|
42718
|
-
if (res.ok) return { ok: true };
|
|
42719
|
-
return { ok: false, detail: res.body?.error ?? res.error ?? `HTTP ${res.status}` };
|
|
42720
|
-
}
|
|
42721
|
-
|
|
42722
42956
|
// src/command-register-collaboration.ts
|
|
42723
42957
|
var DEFAULT_PRIORITY = "medium";
|
|
42724
42958
|
function argvWantsJson2() {
|
|
@@ -42916,7 +43150,7 @@ function registerCollaborationCommands(program3) {
|
|
|
42916
43150
|
try {
|
|
42917
43151
|
title = await resolveIssueTitle(
|
|
42918
43152
|
{ title: opts.title, titleFile: opts.titleFile },
|
|
42919
|
-
{ readFile:
|
|
43153
|
+
{ readFile: import_promises8.readFile, readStdin }
|
|
42920
43154
|
);
|
|
42921
43155
|
} catch (e) {
|
|
42922
43156
|
return fail(
|
|
@@ -42970,8 +43204,8 @@ function registerCollaborationCommands(program3) {
|
|
|
42970
43204
|
let surfaceInferred;
|
|
42971
43205
|
try {
|
|
42972
43206
|
issueType = resolveCreateType(o.type, "issue create", o.label);
|
|
42973
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
42974
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
43207
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises8.readFile, readStdin });
|
|
43208
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
|
|
42975
43209
|
if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
|
|
42976
43210
|
priority = resolveCreatePriority(o.priority, "issue create");
|
|
42977
43211
|
extraLabels = [...o.label ?? []];
|
|
@@ -43247,7 +43481,7 @@ function registerCollaborationCommands(program3) {
|
|
|
43247
43481
|
}
|
|
43248
43482
|
let body;
|
|
43249
43483
|
try {
|
|
43250
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
43484
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
|
|
43251
43485
|
} catch (e) {
|
|
43252
43486
|
return fail(`issue comment: ${e.message}`);
|
|
43253
43487
|
}
|
|
@@ -43307,8 +43541,8 @@ ${list}`);
|
|
|
43307
43541
|
let title;
|
|
43308
43542
|
const sourceRepo = o.repo ?? await resolveRepo(void 0);
|
|
43309
43543
|
try {
|
|
43310
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
43311
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
43544
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises8.readFile, readStdin });
|
|
43545
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
|
|
43312
43546
|
priority = resolveCreatePriority(o.priority, "report");
|
|
43313
43547
|
if (!ISSUE_TYPES.includes(o.type)) {
|
|
43314
43548
|
throw new Error(`unknown issue type "${o.type}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`);
|
|
@@ -43362,8 +43596,8 @@ ${list}`);
|
|
|
43362
43596
|
let args;
|
|
43363
43597
|
try {
|
|
43364
43598
|
skill = assertSkillName(o.skill);
|
|
43365
|
-
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
43366
|
-
const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
43599
|
+
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
|
|
43600
|
+
const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises8.readFile, readStdin });
|
|
43367
43601
|
title = buildSkillLessonTitle(skill, rawTitle);
|
|
43368
43602
|
priority = resolveCreatePriority(o.priority, "skill-lesson");
|
|
43369
43603
|
body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
|
|
@@ -43424,8 +43658,8 @@ ${list}`);
|
|
|
43424
43658
|
let body;
|
|
43425
43659
|
let title;
|
|
43426
43660
|
try {
|
|
43427
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
43428
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
43661
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises8.readFile, readStdin });
|
|
43662
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
|
|
43429
43663
|
} catch (e) {
|
|
43430
43664
|
return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
43431
43665
|
}
|
|
@@ -43930,7 +44164,7 @@ ${list}`);
|
|
|
43930
44164
|
console.error(line);
|
|
43931
44165
|
}
|
|
43932
44166
|
}
|
|
43933
|
-
const credential =
|
|
44167
|
+
const credential = "user";
|
|
43934
44168
|
if (o.json) printLine(JSON.stringify({ ...result, credential }));
|
|
43935
44169
|
else printLine(
|
|
43936
44170
|
`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""} (credential: ${credential})` + (result.branchFinality ? ` \u2014 ${result.branchFinality}` : "")
|
|
@@ -43960,8 +44194,6 @@ ${list}`);
|
|
|
43960
44194
|
const baseRef = prMeta.base;
|
|
43961
44195
|
const headRefOid = (prMeta.oid ?? "").trim() || void 0;
|
|
43962
44196
|
if (!repoForPostCleanup) throw new Error("pr merge: cannot resolve target repository");
|
|
43963
|
-
const devDeployDeps = repoForPostCleanup ? registryClientDeps(await loadConfig()) : void 0;
|
|
43964
|
-
const devDeployPlan = repoForPostCleanup && devDeployDeps ? await planDevDeployOnDevelopmentMerge(repoForPostCleanup, baseRef, devDeployDeps).catch(() => ({ applicable: false, reason: "unreadable" })) : { applicable: false, reason: "unreadable" };
|
|
43965
44197
|
const closingGuardInput = await withAlreadyClosedCommitTargets(
|
|
43966
44198
|
await readClosingGuardInput(number, repoArgs, repoForPostCleanup, "pr merge"),
|
|
43967
44199
|
async (n) => {
|
|
@@ -44191,7 +44423,6 @@ ${list}`);
|
|
|
44191
44423
|
const enqueued = describePrMergeEnqueuedReason(await pollGhPrChecks(number, repoArgs).catch(() => void 0));
|
|
44192
44424
|
console.log(JSON.stringify({ mergeStatus: "auto-merge-enqueued", enqueuedReason: enqueued.reason, pr: number, branch: headRef, state: state || "unknown", upgradedToAuto: upgradedToAuto || void 0 }));
|
|
44193
44425
|
console.warn(`pr merge: PR #${number} is ENQUEUED, not merged \u2014 ${enqueued.message}.`);
|
|
44194
|
-
if (devDeployPlan.applicable) console.warn(`pr merge: ${devDeployPlan.manualPointer} once the merge lands (auto-merge does not redeploy the dev stage).`);
|
|
44195
44426
|
if (upgradedToAuto && !o.auto) {
|
|
44196
44427
|
console.warn(`pr merge: exiting ${PR_MERGE_ENQUEUED_EXIT_CODE} so a chained command does not treat this as a completed merge.`);
|
|
44197
44428
|
process.exitCode = PR_MERGE_ENQUEUED_EXIT_CODE;
|
|
@@ -44301,8 +44532,6 @@ ${list}`);
|
|
|
44301
44532
|
} else if (repoForPostCleanup) {
|
|
44302
44533
|
clearPostMergeReconRecovery(process.cwd(), repoForPostCleanup, number);
|
|
44303
44534
|
}
|
|
44304
|
-
const devDeploy = devDeployPlan.applicable && devDeployDeps && remoteNotAttemptedReason !== "pr-already-merged" ? await dispatchDevDeploy(repoForPostCleanup, devDeployDeps) : void 0;
|
|
44305
|
-
const devDeployManual = devDeployPlan.applicable ? devDeployPlan.manualPointer : void 0;
|
|
44306
44535
|
let observedMethod;
|
|
44307
44536
|
if (remoteNotAttemptedReason === "pr-already-merged" && repoForPostCleanup) {
|
|
44308
44537
|
try {
|
|
@@ -44337,14 +44566,10 @@ ${list}`);
|
|
|
44337
44566
|
...boardAdvance.entries.length ? { boardAdvance: boardAdvance.entries } : {},
|
|
44338
44567
|
boardAdvanceStatus: boardAdvance.status,
|
|
44339
44568
|
...crossRepoFilingIssue.status !== "not-applicable" ? { crossRepoFilingIssue } : {},
|
|
44340
|
-
...recovery ? { postMergeReconRecovery: recovery } : {}
|
|
44341
|
-
...devDeploy ? { devDeploy: devDeploy.ok ? { dispatched: true } : { dispatched: false, manual: devDeployManual } } : {}
|
|
44569
|
+
...recovery ? { postMergeReconRecovery: recovery } : {}
|
|
44342
44570
|
}));
|
|
44343
44571
|
for (const line of renderPrMergeCleanupLines(localCleanup)) console.warn(line);
|
|
44344
44572
|
for (const line of renderResidueSweepLines(residueSweep)) console.warn(line);
|
|
44345
|
-
if (devDeploy && !devDeploy.ok) {
|
|
44346
|
-
console.error(`pr merge: ${devDeployManual} (${devDeploy.detail})`);
|
|
44347
|
-
}
|
|
44348
44573
|
for (const line of postMergeReconWarnings({
|
|
44349
44574
|
context: "pr merge",
|
|
44350
44575
|
boardAdvance,
|
|
@@ -44408,7 +44633,7 @@ function trainGitArgs(args) {
|
|
|
44408
44633
|
async function announceSummaryRefusal(path2, isHub = false) {
|
|
44409
44634
|
let lines2;
|
|
44410
44635
|
try {
|
|
44411
|
-
lines2 = summaryFileLines(await (0,
|
|
44636
|
+
lines2 = summaryFileLines(await (0, import_promises9.readFile)(path2, "utf8"));
|
|
44412
44637
|
} catch (e) {
|
|
44413
44638
|
return `release: could not read --announce-summary-file ${path2}: ${e.message} \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing`;
|
|
44414
44639
|
}
|
|
@@ -44445,9 +44670,8 @@ function trainApplyDeps() {
|
|
|
44445
44670
|
trainWriter: (verb, cwd) => acquireTrainWriterLock(cwd, `mmi-cli devops ${verb}`),
|
|
44446
44671
|
run: async (file, args) => {
|
|
44447
44672
|
const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" && (args[0] === "version" || args[0] === "ci" || args[0] === "install") ? NPM_FOLD_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file === "bash" && /jerv-gateway-release-deploy\.sh$/.test(args[0] ?? "") ? JERV_GATEWAY_DEPLOY_TIMEOUT_MS : file === "git" && args[0] === "commit" ? GIT_COMMIT_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
|
|
44448
|
-
const watchToken = file === "gh" && args[0] === "run" && args[1] === "watch" ? await pollToken() : void 0;
|
|
44449
44673
|
try {
|
|
44450
|
-
return isWin3 && file === "npm" ? (await execFileP("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP(file, file === "git" ? trainGitArgs(args) : args, { timeout
|
|
44674
|
+
return isWin3 && file === "npm" ? (await execFileP("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP(file, file === "git" ? trainGitArgs(args) : args, { timeout })).stdout;
|
|
44451
44675
|
} catch (e) {
|
|
44452
44676
|
throw surfaceTrainSubprocessFailure(e, file, args);
|
|
44453
44677
|
}
|
|
@@ -44517,8 +44741,8 @@ function trainApplyDeps() {
|
|
|
44517
44741
|
}
|
|
44518
44742
|
return body.status;
|
|
44519
44743
|
},
|
|
44520
|
-
readFile: (path2) => (0,
|
|
44521
|
-
removeFile: (path2) => (0,
|
|
44744
|
+
readFile: (path2) => (0, import_promises9.readFile)(path2, "utf8"),
|
|
44745
|
+
removeFile: (path2) => (0, import_promises9.unlink)(path2),
|
|
44522
44746
|
// #6521/#6523: the project's own release channel AND releaseLanguage, read from registry META in ONE
|
|
44523
44747
|
// checked read. Same shape as fetchEdgeDomains below: a FAILED read refuses by name so the announce
|
|
44524
44748
|
// note reports a failed read, never a project that configured nothing.
|
|
@@ -44846,7 +45070,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44846
45070
|
{ flags: "--abort", description: "with --apply, delete only a proven unpublished failed release candidate on any registered deploy model and restore local main for a same-version recut (#3944/#5650/#5657)" },
|
|
44847
45071
|
{ flags: "--supersede-deploy", description: "with --apply, master-approved closure of a content-caused failed deploy leg on a PUBLISHED release whose immutable tag can never deploy green \u2014 the next patch train then carries the fix (#6553); combined with --supersede-publish it closes BOTH content-caused legs in one approved closure (#6752)" },
|
|
44848
45072
|
{ flags: "--supersede-publish", description: "with --apply, master-approved closure of a content-caused failed publish leg on a fully PROMOTED release whose immutable tag can never publish green \u2014 a deferred Jerv-Hub operator-host Gateway deploy leg is run and verified as part of the closure \u2014 the next patch train then carries the fix (#6638/#6742); combined with --supersede-deploy it closes BOTH content-caused legs in one approved closure (#6752)" },
|
|
44849
|
-
{ flags: "--retry-publish <run-id>", description: "with --apply, retry one proven failed or cancelled Hub publish release run (#3949/#5797)" }
|
|
45073
|
+
{ flags: "--retry-publish <run-id>", description: "with --apply, retry one proven failed or cancelled Hub publish release run (#3949/#5797)" },
|
|
45074
|
+
{ flags: "--big", description: "marks this release big for the mm-work suite `## In plain words` section even though its patch tag alone would not read as one (#7510); automatic for a minor/major bump \u2014 this is only for a patch a project-admin wants surfaced as big" }
|
|
44850
45075
|
];
|
|
44851
45076
|
for (const f of RELEASE_ONLY_FLAGS) {
|
|
44852
45077
|
if (commandName === "release") {
|
|
@@ -44874,6 +45099,9 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44874
45099
|
if (o.announceSummaryFile && commandName !== "release") {
|
|
44875
45100
|
return fail(`${commandName}: --announce-summary-file applies only to release \u2014 rcand posts no release announcement. Run: mmi-cli devops release --announce-summary-file <path>`);
|
|
44876
45101
|
}
|
|
45102
|
+
if (o.big && commandName !== "release") {
|
|
45103
|
+
return fail(`${commandName}: --big applies only to release \u2014 it marks the mm-work suite plain-words section, which only a Release carries. Run: mmi-cli devops release --big`);
|
|
45104
|
+
}
|
|
44877
45105
|
if (o.abort && commandName !== "release") {
|
|
44878
45106
|
return fail(`${commandName}: --abort applies only to release \u2014 it rolls back a proven unpublished release tag. Run: mmi-cli devops release --abort --apply`);
|
|
44879
45107
|
}
|
|
@@ -44916,7 +45144,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44916
45144
|
if (o.abort) return fail("release: --supersede-deploy --supersede-publish and --abort are mutually exclusive \u2014 the closure is for a PUBLISHED release, abort only ever deletes a proven unpublished candidate");
|
|
44917
45145
|
if (o.retryPublish) return fail("release: --supersede-deploy --supersede-publish cannot be combined with --retry-publish \u2014 a content-caused failure on the immutable tag cannot be retried green");
|
|
44918
45146
|
if (!o.apply) return fail("release: --supersede-deploy --supersede-publish requires --apply after explicit master approval; nothing was written");
|
|
44919
|
-
if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
|
|
45147
|
+
if (o.watch || o.announceSummaryFile || o.ack || o.dev || o.big) {
|
|
44920
45148
|
return fail("release: --supersede-deploy --supersede-publish accepts only --apply, --repo, --json and --out; it closes ledger legs and dispatches nothing");
|
|
44921
45149
|
}
|
|
44922
45150
|
if (o.repo) {
|
|
@@ -44933,7 +45161,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44933
45161
|
if (o.retryPublish) {
|
|
44934
45162
|
if (o.resume || o.abort) return fail("release: --retry-publish cannot be combined with --resume or --abort");
|
|
44935
45163
|
if (!o.apply) return fail("release: --retry-publish requires --apply after explicit approval; nothing was written");
|
|
44936
|
-
if (o.announceSummaryFile || o.ack || o.dev) return fail("release: --retry-publish accepts only --apply, --watch, --repo, --json and --out");
|
|
45164
|
+
if (o.announceSummaryFile || o.ack || o.dev || o.big) return fail("release: --retry-publish accepts only --apply, --watch, --repo, --json and --out");
|
|
44937
45165
|
if (o.repo) {
|
|
44938
45166
|
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli devops release --retry-publish ${o.retryPublish} --apply${o.watch ? " --watch" : ""}`);
|
|
44939
45167
|
if (!guard.ok) return fail(`release: ${guard.message}`);
|
|
@@ -44957,7 +45185,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44957
45185
|
if (o.abort) {
|
|
44958
45186
|
if (o.resume) return fail("release: --abort and --resume are mutually exclusive \u2014 abort removes an unpublished tag while resume preserves and promotes it");
|
|
44959
45187
|
if (!o.apply) return fail("release: --abort requires --apply after explicit approval; nothing was written");
|
|
44960
|
-
if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
|
|
45188
|
+
if (o.watch || o.announceSummaryFile || o.ack || o.dev || o.big) {
|
|
44961
45189
|
return fail("release: --abort accepts only --apply, --repo, --json and --out; promotion flags cannot be combined with rollback");
|
|
44962
45190
|
}
|
|
44963
45191
|
if (o.repo) {
|
|
@@ -44976,7 +45204,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44976
45204
|
if (o.abort) return fail("release: --supersede-deploy and --abort are mutually exclusive \u2014 the closure is for a PUBLISHED release, abort only ever deletes a proven unpublished candidate");
|
|
44977
45205
|
if (o.retryPublish) return fail("release: --supersede-deploy cannot be combined with --retry-publish");
|
|
44978
45206
|
if (!o.apply) return fail("release: --supersede-deploy requires --apply after explicit master approval; nothing was written");
|
|
44979
|
-
if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
|
|
45207
|
+
if (o.watch || o.announceSummaryFile || o.ack || o.dev || o.big) {
|
|
44980
45208
|
return fail("release: --supersede-deploy accepts only --apply, --repo, --json and --out; it closes a ledger leg and dispatches nothing");
|
|
44981
45209
|
}
|
|
44982
45210
|
if (o.repo) {
|
|
@@ -44995,7 +45223,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
44995
45223
|
if (o.abort) return fail("release: --supersede-publish and --abort are mutually exclusive \u2014 the closure is for a PROMOTED release, abort only ever deletes a proven unpublished candidate");
|
|
44996
45224
|
if (o.retryPublish) return fail("release: --supersede-publish cannot be combined with --retry-publish \u2014 a content-caused failure on the immutable tag cannot be retried green");
|
|
44997
45225
|
if (!o.apply) return fail("release: --supersede-publish requires --apply after explicit master approval; nothing was written");
|
|
44998
|
-
if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
|
|
45226
|
+
if (o.watch || o.announceSummaryFile || o.ack || o.dev || o.big) {
|
|
44999
45227
|
return fail("release: --supersede-publish accepts only --apply, --repo, --json and --out; it closes a ledger leg and dispatches nothing");
|
|
45000
45228
|
}
|
|
45001
45229
|
if (o.repo) {
|
|
@@ -45023,7 +45251,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
45023
45251
|
const raw = await withReleaseProgress(
|
|
45024
45252
|
trainApplyDeps(),
|
|
45025
45253
|
"Checking the existing release",
|
|
45026
|
-
(deps) => runReleaseResume(deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, suiteSolo: o.suiteSolo })
|
|
45254
|
+
(deps) => runReleaseResume(deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, suiteSolo: o.suiteSolo, big: o.big })
|
|
45027
45255
|
);
|
|
45028
45256
|
const result = raw.dispatch?.workflowRuns ? { ...raw, dispatch: { ...raw.dispatch, workflowRuns: raw.dispatch.workflowRuns.map(workflowRunWithEvidence) } } : raw;
|
|
45029
45257
|
const projectInfoSync = await withReleaseProgress(
|
|
@@ -45087,7 +45315,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
45087
45315
|
if (o.apply) {
|
|
45088
45316
|
try {
|
|
45089
45317
|
const ack = (o.ack ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
45090
|
-
const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, ack, dev: o.dev, suiteSolo: o.suiteSolo });
|
|
45318
|
+
const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, ack, dev: o.dev, suiteSolo: o.suiteSolo, big: o.big });
|
|
45091
45319
|
const raw = await withReleaseProgress(trainApplyDeps(), commandName === "release" ? "Preparing release" : "Preparing release candidate", apply);
|
|
45092
45320
|
const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
|
|
45093
45321
|
let projectInfoSync;
|
|
@@ -45338,7 +45566,10 @@ function rcRunCauseFromLog(log) {
|
|
|
45338
45566
|
const causes = [...new Set([...log.matchAll(/##\[error\](?!Process completed with exit code)([^\r\n]+)/g)].map((m) => m[1].trim()).filter(Boolean))];
|
|
45339
45567
|
return causes.length ? `: ${causes.join("; ")}` : "";
|
|
45340
45568
|
}
|
|
45341
|
-
function
|
|
45569
|
+
function memberAnnounceSummaryFile(repo, summaryFile, memberSummaryFiles) {
|
|
45570
|
+
return repo === SUITE_REPO ? summaryFile : memberSummaryFiles[repo];
|
|
45571
|
+
}
|
|
45572
|
+
function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
|
|
45342
45573
|
const recordPath = repoRuntimeStatePath(root, "suite", "cut.json");
|
|
45343
45574
|
const base = (0, import_node_path43.join)(repoRuntimeStatePath(root, "suite"), "checkouts");
|
|
45344
45575
|
const checkout = async (repo) => {
|
|
@@ -45386,6 +45617,17 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45386
45617
|
if (!health.ok) throw new Error(`${member.repo}: live backend health could not be proved at ${url}; stop`);
|
|
45387
45618
|
return { version: live.deployedTag, host };
|
|
45388
45619
|
};
|
|
45620
|
+
const hold = async (member) => {
|
|
45621
|
+
const slug = member.repo.split("/")[1].toLowerCase();
|
|
45622
|
+
const read = await fetchProjectBySlugChecked(slug, registryClientDeps(await loadConfig()));
|
|
45623
|
+
if (!read.ok) throw new Error(`${member.repo}: registry META unreadable for suiteHold (${read.error}); stop`);
|
|
45624
|
+
const value = read.project?.suiteHold;
|
|
45625
|
+
if (value === void 0 || value === null) return null;
|
|
45626
|
+
const raw = value;
|
|
45627
|
+
if (typeof raw.reason !== "string" || !raw.reason.trim())
|
|
45628
|
+
throw new Error(`${member.repo}: registry META suiteHold is malformed; set it as {"reason":"...","since":"..."} or clear it with --unset suiteHold; stop`);
|
|
45629
|
+
return { reason: raw.reason, ...typeof raw.since === "string" && raw.since ? { since: raw.since } : {} };
|
|
45630
|
+
};
|
|
45389
45631
|
const tagSha = async (repo, tag) => {
|
|
45390
45632
|
try {
|
|
45391
45633
|
return await gh(["api", `repos/${repo}/commits/${encodeURIComponent(tag)}`, "--jq", ".sha"]);
|
|
@@ -45432,8 +45674,9 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45432
45674
|
return { tag: result.tag, sha: result.tagSha };
|
|
45433
45675
|
}
|
|
45434
45676
|
});
|
|
45435
|
-
const release = async (repo, expected, rcSha) => inTrain(repo, async (deps) => {
|
|
45677
|
+
const release = async (repo, expected, rcSha, opts) => inTrain(repo, async (deps) => {
|
|
45436
45678
|
await prepareRcCheckout((args) => deps.run("git", args), repo);
|
|
45679
|
+
const plainWords = { announceSummaryFile: memberAnnounceSummaryFile(repo, summaryFile, memberSummaryFiles), big: opts?.big, suiteTag: opts?.suiteTag };
|
|
45437
45680
|
const current = await tagSha(repo, expected);
|
|
45438
45681
|
if (current) {
|
|
45439
45682
|
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
@@ -45442,10 +45685,10 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45442
45685
|
} catch {
|
|
45443
45686
|
throw new Error(`${repo}: existing ${expected} does not carry this suite's rc SHA ${rcSha}; refuse stale release`);
|
|
45444
45687
|
}
|
|
45445
|
-
const result = await runReleaseResume(deps, { watch: true });
|
|
45688
|
+
const result = await runReleaseResume(deps, { watch: true, ...plainWords });
|
|
45446
45689
|
if (result.tag !== expected || result.state !== "aligned") throw new Error(`${repo}: release resume not complete at ${expected} (${result.tag}/${result.state})`);
|
|
45447
45690
|
} else {
|
|
45448
|
-
const result = await withEnv("MMI_RELEASE_VERSION", expected.replace(/^v/, ""), () => runTrainApply("release", deps, { watch: true }));
|
|
45691
|
+
const result = await withEnv("MMI_RELEASE_VERSION", expected.replace(/^v/, ""), () => runTrainApply("release", deps, { watch: true, ...plainWords }));
|
|
45449
45692
|
if (result.tag !== expected || result.deployStatus !== "success") throw new Error(`${repo}: release is not green at expected ${expected} (${result.tag}/${result.deployStatus})`);
|
|
45450
45693
|
}
|
|
45451
45694
|
});
|
|
@@ -45533,6 +45776,7 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45533
45776
|
recordPath,
|
|
45534
45777
|
registry: registry2,
|
|
45535
45778
|
backend,
|
|
45779
|
+
hold,
|
|
45536
45780
|
head: async (repo, branch) => {
|
|
45537
45781
|
try {
|
|
45538
45782
|
return await gh(["api", `repos/${repo}/git/ref/heads/${branch}`, "--jq", ".object.sha"]);
|
|
@@ -45596,6 +45840,15 @@ function suiteTrainIo(root, summaryFile) {
|
|
|
45596
45840
|
// src/suite-commands.ts
|
|
45597
45841
|
init_cli_shared();
|
|
45598
45842
|
var import_node_fs44 = require("node:fs");
|
|
45843
|
+
function parseMemberSummaryFiles(entries) {
|
|
45844
|
+
const memberSummaryFiles = {};
|
|
45845
|
+
for (const entry of entries) {
|
|
45846
|
+
const eq = entry.indexOf("=");
|
|
45847
|
+
if (eq < 0) throw new Error(`--member-summary-file expects <owner/repo>=<path>, got ${entry}`);
|
|
45848
|
+
memberSummaryFiles[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
45849
|
+
}
|
|
45850
|
+
return memberSummaryFiles;
|
|
45851
|
+
}
|
|
45599
45852
|
function registerSuiteCommands(program3) {
|
|
45600
45853
|
const suite = program3.command("suite").description("MM-Work suite status, candidate cut and release (#7262/#7263)");
|
|
45601
45854
|
const leadRoot = async () => {
|
|
@@ -45643,23 +45896,25 @@ function registerSuiteCommands(program3) {
|
|
|
45643
45896
|
return fail(`suite abandon: ${e.message}`);
|
|
45644
45897
|
}
|
|
45645
45898
|
});
|
|
45646
|
-
suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train").option("--json", "structured receipt").action(async (o) => {
|
|
45899
|
+
suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train; also this cut's plain-words summary for MM-Work itself").option("--member-summary-file <owner/repo=path>", "repeatable: a member's OWN plain-words summary (#7510) \u2014 never the suite lead's summary reused; a member with no entry still ships, with the coordinator-ruled no-change line", (val, prev = []) => [...prev, val], []).option("--big", "marks MM-Work's own release big for the plain-words section (#7510); a member is never marked big through this flag \u2014 it is big only via the read route's automatic minor/major detection").option("--json", "structured receipt").action(async (o) => {
|
|
45647
45900
|
try {
|
|
45648
45901
|
if (!o.ownerGo) throw new Error("suite release requires --owner-go; nothing was written");
|
|
45649
45902
|
if (!o.announceSummaryFile) throw new Error("suite release requires --announce-summary-file before promoting any member");
|
|
45650
45903
|
const lines2 = (0, import_node_fs44.readFileSync)(o.announceSummaryFile, "utf8").split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
45651
45904
|
if (lines2.length < 3 || lines2.length > 6) throw new Error("suite release announcement needs 3\u20136 curated lines before promoting any member");
|
|
45905
|
+
const memberSummaryFiles = parseMemberSummaryFiles(o.memberSummaryFile ?? []);
|
|
45652
45906
|
const root = await leadRoot();
|
|
45653
45907
|
const unlock = await acquireTrainWriterLock(root, "suite release");
|
|
45654
45908
|
let result;
|
|
45655
45909
|
try {
|
|
45656
|
-
result = await suiteRelease(suiteTrainIo(root, o.announceSummaryFile), true);
|
|
45910
|
+
result = await suiteRelease(suiteTrainIo(root, o.announceSummaryFile, memberSummaryFiles), true, o.big === true);
|
|
45657
45911
|
} finally {
|
|
45658
45912
|
await unlock();
|
|
45659
45913
|
}
|
|
45660
45914
|
console.log(o.json ? JSON.stringify(result, null, 2) : [
|
|
45661
45915
|
`suite release: ${result.leadRcTag} \u2192 main`,
|
|
45662
45916
|
...result.members.map((m) => m.carried ? ` - ${m.repo}: carried, untouched` : ` - ${m.repo}: ${m.tag} \u2192 main ${m.released ? "released" : "pending"}`),
|
|
45917
|
+
...result.heldMembers?.length ? ["held (frozen live release, untouched):", ...result.heldMembers.map((h) => ` - ${h.repo}: HELD: ${h.reason}`)] : [],
|
|
45663
45918
|
` - MM-Work: ${result.leadReleased ? "released" : "pending"}; announcement: ${result.announcement}`
|
|
45664
45919
|
].join("\n"));
|
|
45665
45920
|
} catch (e) {
|
|
@@ -48691,7 +48946,7 @@ function renderTrackToDirectResult(result) {
|
|
|
48691
48946
|
}
|
|
48692
48947
|
|
|
48693
48948
|
// src/schedules-commands.ts
|
|
48694
|
-
var
|
|
48949
|
+
var import_promises10 = require("node:fs/promises");
|
|
48695
48950
|
init_clean_exit();
|
|
48696
48951
|
init_github_client();
|
|
48697
48952
|
init_cli_shared();
|
|
@@ -48885,9 +49140,9 @@ function registerSchedulesCommands(program3) {
|
|
|
48885
49140
|
await failGraceful("org schedules --doc: refusing to regenerate the doc from an incomplete read (see warnings above).");
|
|
48886
49141
|
return;
|
|
48887
49142
|
}
|
|
48888
|
-
const docText = await (0,
|
|
49143
|
+
const docText = await (0, import_promises10.readFile)(o.doc, "utf8");
|
|
48889
49144
|
const spliced = spliceDoc(docText, renderDocSection(entries, now.toISOString()));
|
|
48890
|
-
await (0,
|
|
49145
|
+
await (0, import_promises10.writeFile)(o.doc, spliced, "utf8");
|
|
48891
49146
|
reportParked(parkedLines);
|
|
48892
49147
|
reportDrift(drift);
|
|
48893
49148
|
console.log(`org schedules: wrote ${entries.length} entries into ${o.doc}`);
|
|
@@ -49625,7 +49880,7 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
49625
49880
|
// src/tenant-artifact.ts
|
|
49626
49881
|
var import_node_crypto15 = require("node:crypto");
|
|
49627
49882
|
var import_node_fs48 = require("node:fs");
|
|
49628
|
-
var
|
|
49883
|
+
var import_promises11 = require("node:fs/promises");
|
|
49629
49884
|
var import_node_path46 = require("node:path");
|
|
49630
49885
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
49631
49886
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
@@ -49637,7 +49892,7 @@ async function sha256File(path2) {
|
|
|
49637
49892
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
49638
49893
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
49639
49894
|
const path2 = (0, import_node_path46.resolve)(inputPath);
|
|
49640
|
-
const info = await (0,
|
|
49895
|
+
const info = await (0, import_promises11.stat)(path2);
|
|
49641
49896
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
49642
49897
|
if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
|
|
49643
49898
|
const sha2562 = await sha256File(path2);
|
|
@@ -50119,7 +50374,7 @@ init_github_client();
|
|
|
50119
50374
|
init_hub_auth();
|
|
50120
50375
|
|
|
50121
50376
|
// src/schedules-lift-command.ts
|
|
50122
|
-
var
|
|
50377
|
+
var import_promises12 = require("node:fs/promises");
|
|
50123
50378
|
var import_node_path49 = require("node:path");
|
|
50124
50379
|
init_clean_exit();
|
|
50125
50380
|
init_cli_shared();
|
|
@@ -50142,14 +50397,14 @@ var RegistryUnreachableError = class extends Error {
|
|
|
50142
50397
|
async function readWorkflowFiles(dir) {
|
|
50143
50398
|
let names;
|
|
50144
50399
|
try {
|
|
50145
|
-
names = await (0,
|
|
50400
|
+
names = await (0, import_promises12.readdir)(dir);
|
|
50146
50401
|
} catch {
|
|
50147
50402
|
return [];
|
|
50148
50403
|
}
|
|
50149
50404
|
const files = [];
|
|
50150
50405
|
for (const name of names.sort()) {
|
|
50151
50406
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
50152
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0,
|
|
50407
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0, import_node_path49.join)(dir, name), "utf8") });
|
|
50153
50408
|
}
|
|
50154
50409
|
return files;
|
|
50155
50410
|
}
|
|
@@ -51876,6 +52131,7 @@ function ciAuditDeps2() {
|
|
|
51876
52131
|
client: defaultGitHubClient(),
|
|
51877
52132
|
listProjects: async () => fetchProjectsList(registryClientDeps(await cfgPromise)),
|
|
51878
52133
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
52134
|
+
getDeployFacts: async (slug) => fetchDeployFactsBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
51879
52135
|
// Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates.
|
|
51880
52136
|
// Resolve from the installed package root so the reconcile works from any org repo checkout; when the
|
|
51881
52137
|
// Hub root is not locatable (e.g. a global npm install without a local checkout), return null so the
|