@mutmutco/cli 4.5.58 → 4.5.59
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 +155 -34
- 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";
|
|
@@ -8637,7 +8666,7 @@ async function fetchSecretValue(deps, key, opts, diagnose = false) {
|
|
|
8637
8666
|
if (!res.ok) {
|
|
8638
8667
|
if (diagnose) {
|
|
8639
8668
|
const body = await readJsonBody(res);
|
|
8640
|
-
const reason = res.status === 403 ? "authority refused" : res.status === 429 ? "rate limited" : "Hub route refused";
|
|
8669
|
+
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";
|
|
8641
8670
|
throw new Error(`${reason} (HTTP ${res.status}${typeof body.error === "string" && /^(forbidden|unauthorized|rate limit|too many requests|master-admin only)$/i.test(body.error) ? `: ${body.error}` : ""})`);
|
|
8642
8671
|
}
|
|
8643
8672
|
return null;
|
|
@@ -9918,6 +9947,10 @@ async function secretsVerify(deps, key, opts) {
|
|
|
9918
9947
|
}
|
|
9919
9948
|
const hasProviderVerifier = Boolean(providerForSecretKey(key));
|
|
9920
9949
|
const value = await fetchSecretValue(deps, key, opts);
|
|
9950
|
+
if (value && value !== stripTrailingCRLF(value)) {
|
|
9951
|
+
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}\``);
|
|
9952
|
+
return false;
|
|
9953
|
+
}
|
|
9921
9954
|
if (!hasProviderVerifier) {
|
|
9922
9955
|
if (!value) {
|
|
9923
9956
|
deps.err(`${key}: ABSENT or unreachable at this coordinate \u2014 no value could be read`);
|
|
@@ -10105,6 +10138,40 @@ async function secretsDeclare(deps, key, opts) {
|
|
|
10105
10138
|
deps.log(`declared ${key} (project tier, ${slug}) \u2014 catalog metadata only; ask master (secrets request) to wire it into requiredRuntimeSecrets`);
|
|
10106
10139
|
return true;
|
|
10107
10140
|
}
|
|
10141
|
+
async function secretsUndeclare(deps, key, opts) {
|
|
10142
|
+
if (!DECLARE_KEY_RE.test(key.split("@")[0] ?? "")) {
|
|
10143
|
+
deps.err(`secrets undeclare: invalid key ${JSON.stringify(key)} \u2014 must be an env-style name (UPPER_SNAKE convention), optionally @dev|rc|main`);
|
|
10144
|
+
return false;
|
|
10145
|
+
}
|
|
10146
|
+
const repo = await targetRepo(deps, opts);
|
|
10147
|
+
const slug = await vaultSlug(deps, opts);
|
|
10148
|
+
let res;
|
|
10149
|
+
try {
|
|
10150
|
+
res = await deps.fetch(`${deps.apiUrl}/projects/${slug}/secrets/undeclare`, {
|
|
10151
|
+
method: "POST",
|
|
10152
|
+
headers: await deps.headers({ "content-type": "application/json" }),
|
|
10153
|
+
body: JSON.stringify({ repo, key }),
|
|
10154
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
10155
|
+
});
|
|
10156
|
+
} catch (e) {
|
|
10157
|
+
deps.err(`secrets undeclare: ${e.message}`);
|
|
10158
|
+
return false;
|
|
10159
|
+
}
|
|
10160
|
+
const body = await readJsonBody(res);
|
|
10161
|
+
if (!res.ok) {
|
|
10162
|
+
deps.err(await upgradeMessage(res, body) ?? `secrets undeclare failed: HTTP ${res.status}${errorDetail(body)}`);
|
|
10163
|
+
return false;
|
|
10164
|
+
}
|
|
10165
|
+
const undeclared = body?.undeclared !== false;
|
|
10166
|
+
if (opts.json) {
|
|
10167
|
+
deps.log(JSON.stringify({ ok: true, key, undeclared }));
|
|
10168
|
+
return true;
|
|
10169
|
+
}
|
|
10170
|
+
deps.log(
|
|
10171
|
+
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`
|
|
10172
|
+
);
|
|
10173
|
+
return true;
|
|
10174
|
+
}
|
|
10108
10175
|
function parseRailsCredentialMapping(raw, stage) {
|
|
10109
10176
|
const eq = raw.indexOf("=");
|
|
10110
10177
|
if (eq <= 0 || eq === raw.length - 1) return null;
|
|
@@ -16302,6 +16369,17 @@ function isHubControlRepo(repo) {
|
|
|
16302
16369
|
function isJervHubRepo(repo) {
|
|
16303
16370
|
return repo.toLowerCase() === JERV_HUB_REPO.toLowerCase();
|
|
16304
16371
|
}
|
|
16372
|
+
var WINDOWS_GIT_BASH_MSYSTEM = /^(?:MINGW(?:32|64)|UCRT64|CLANG(?:64|ARM64))$/u;
|
|
16373
|
+
function isWindowsGitBash(msystem) {
|
|
16374
|
+
return WINDOWS_GIT_BASH_MSYSTEM.test(msystem ?? "");
|
|
16375
|
+
}
|
|
16376
|
+
function assertGitBashOnWindowsForJervHubRelease(repo, platform2 = process.platform, env = process.env) {
|
|
16377
|
+
if (platform2 !== "win32" || !isJervHubRepo(repo)) return;
|
|
16378
|
+
if (isWindowsGitBash(env.MSYSTEM)) return;
|
|
16379
|
+
throw new Error(
|
|
16380
|
+
`${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"`
|
|
16381
|
+
);
|
|
16382
|
+
}
|
|
16305
16383
|
|
|
16306
16384
|
// src/train-apply-correlation.ts
|
|
16307
16385
|
var import_node_fs18 = require("node:fs");
|
|
@@ -17070,10 +17148,10 @@ var rollout_plan_default = {
|
|
|
17070
17148
|
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
17149
|
},
|
|
17072
17150
|
baseline: {
|
|
17073
|
-
version: "4.5.
|
|
17074
|
-
tag: "v4.5.
|
|
17075
|
-
commit: "
|
|
17076
|
-
npm: "@mutmutco/cli@4.5.
|
|
17151
|
+
version: "4.5.59",
|
|
17152
|
+
tag: "v4.5.59",
|
|
17153
|
+
commit: "ea079366d96e",
|
|
17154
|
+
npm: "@mutmutco/cli@4.5.59"
|
|
17077
17155
|
},
|
|
17078
17156
|
exitCriterion: "fleet-n-of-n",
|
|
17079
17157
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17090,14 +17168,14 @@ var rollout_plan_default = {
|
|
|
17090
17168
|
repo: "mutmutco/mmi-hub",
|
|
17091
17169
|
role: "canary",
|
|
17092
17170
|
schedule: "train",
|
|
17093
|
-
v3Target: "v4.5.
|
|
17171
|
+
v3Target: "v4.5.59"
|
|
17094
17172
|
}
|
|
17095
17173
|
],
|
|
17096
17174
|
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
17175
|
rollback: {
|
|
17098
17176
|
independent: true,
|
|
17099
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17100
|
-
v3Target: "v4.5.
|
|
17177
|
+
mechanism: "npm dist-tag latest -> 4.5.59 and redeploy the Hub Lambda from tag v4.5.59 (ea079366d96e); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17178
|
+
v3Target: "v4.5.59 (@mutmutco/cli@4.5.59, tag commit ea079366d96e \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17101
17179
|
}
|
|
17102
17180
|
},
|
|
17103
17181
|
{
|
|
@@ -25157,6 +25235,7 @@ function enforceGateBudget(deps, repo) {
|
|
|
25157
25235
|
}
|
|
25158
25236
|
}
|
|
25159
25237
|
async function preflight(deps, ctx, stage, meta, lane) {
|
|
25238
|
+
assertGitBashOnWindowsForJervHubRelease(ctx.repo);
|
|
25160
25239
|
deps.warn?.("Release: Checking release prerequisites");
|
|
25161
25240
|
if (deps.suiteCut && !authorisedSuiteCut(ctx.repo, meta, deps.suiteCut))
|
|
25162
25241
|
throw new Error(`${ctx.repo}: suite cut context does not match registry META; no deployment or tag was changed`);
|
|
@@ -27467,15 +27546,16 @@ async function branchPresence(deps, repo, branch) {
|
|
|
27467
27546
|
}
|
|
27468
27547
|
}
|
|
27469
27548
|
function collectRegistryRepos(projects) {
|
|
27470
|
-
const seen = /* @__PURE__ */ new
|
|
27549
|
+
const seen = /* @__PURE__ */ new Map();
|
|
27471
27550
|
for (const project2 of projects) {
|
|
27472
27551
|
for (const raw of project2.repos ?? []) {
|
|
27473
27552
|
const repo = raw.includes("/") ? raw : `mutmutco/${raw}`;
|
|
27474
|
-
|
|
27553
|
+
const key = repo.toLowerCase();
|
|
27554
|
+
if (!seen.has(key)) seen.set(key, repo);
|
|
27475
27555
|
}
|
|
27476
27556
|
}
|
|
27477
|
-
if (!
|
|
27478
|
-
return [...seen].sort((a, b) => a.localeCompare(b));
|
|
27557
|
+
if (!seen.has(HUB_REPO4.toLowerCase())) seen.set(HUB_REPO4.toLowerCase(), HUB_REPO4);
|
|
27558
|
+
return [...seen.values()].sort((a, b) => a.localeCompare(b));
|
|
27479
27559
|
}
|
|
27480
27560
|
async function prTriggeredWorkflowsOnRef(deps, repo, branch, repoClass) {
|
|
27481
27561
|
const hasGate = repoClass === "hub" ? true : await contentExists(deps, repo, branch, PRODUCT_GATE_PATH);
|
|
@@ -27522,7 +27602,29 @@ async function filterPullRequestTriggered(deps, repo, branch, paths) {
|
|
|
27522
27602
|
}
|
|
27523
27603
|
return kept;
|
|
27524
27604
|
}
|
|
27525
|
-
|
|
27605
|
+
function trackUnauditableReads(deps) {
|
|
27606
|
+
let reason;
|
|
27607
|
+
const record = (e) => {
|
|
27608
|
+
if (reason) return;
|
|
27609
|
+
const err = e;
|
|
27610
|
+
if (err?.rateLimited) reason = `GitHub App rate-limited: ${err.message ?? "rate limit exceeded"}`;
|
|
27611
|
+
else if (err?.status === 403 || err?.status === 429) reason = `HTTP ${err.status}${err.message ? `: ${err.message}` : ""}`;
|
|
27612
|
+
};
|
|
27613
|
+
const client = {
|
|
27614
|
+
...deps.client,
|
|
27615
|
+
rest: async (method, path2, init) => {
|
|
27616
|
+
try {
|
|
27617
|
+
return await deps.client.rest(method, path2, init);
|
|
27618
|
+
} catch (e) {
|
|
27619
|
+
record(e);
|
|
27620
|
+
throw e;
|
|
27621
|
+
}
|
|
27622
|
+
}
|
|
27623
|
+
};
|
|
27624
|
+
return { deps: { ...deps, client }, reason: () => reason };
|
|
27625
|
+
}
|
|
27626
|
+
async function auditRepoCi(repo, callerDeps) {
|
|
27627
|
+
const { deps, reason: unauditedReason } = trackUnauditableReads(callerDeps);
|
|
27526
27628
|
const meta = await deps.getProjectMeta(slugFromRepo(repo));
|
|
27527
27629
|
const repoClass = classifyRepo(repo, meta);
|
|
27528
27630
|
const checks = [];
|
|
@@ -27817,6 +27919,10 @@ async function auditRepoCi(repo, deps) {
|
|
|
27817
27919
|
const workflows = await getBranchWorkflows();
|
|
27818
27920
|
if (workflows) checks.push(...await auditSharedActionPins(workflows, deps));
|
|
27819
27921
|
}
|
|
27922
|
+
const failure = unauditedReason();
|
|
27923
|
+
if (failure) {
|
|
27924
|
+
return { repo, class: repoClass, mergePolicy: policy, ok: false, checks: [], explicitNoCi, unauditedReason: failure };
|
|
27925
|
+
}
|
|
27820
27926
|
const ok = checks.every((c) => c.ok);
|
|
27821
27927
|
return { repo, class: repoClass, mergePolicy: policy, ok, checks, explicitNoCi };
|
|
27822
27928
|
}
|
|
@@ -27858,8 +27964,8 @@ function renderCiAuditMarkdown(report) {
|
|
|
27858
27964
|
"|------|-------|--------|----|---------|"
|
|
27859
27965
|
];
|
|
27860
27966
|
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
|
|
27967
|
+
const gap = r.unauditedReason ? `CANNOT AUDIT \u2014 ${r.unauditedReason}` : r.checks.find((c) => !c.ok)?.label ?? "\u2014";
|
|
27968
|
+
lines2.push(`| ${r.repo} | ${r.class} | ${r.mergePolicy} | ${r.ok ? "yes" : "no"} | ${gap} |`);
|
|
27863
27969
|
}
|
|
27864
27970
|
return lines2.join("\n");
|
|
27865
27971
|
}
|
|
@@ -27869,8 +27975,13 @@ function renderCiAuditText(report) {
|
|
|
27869
27975
|
renderCiAuditScopeLine(report)
|
|
27870
27976
|
];
|
|
27871
27977
|
for (const r of report.repos) {
|
|
27978
|
+
const verdict = r.unauditedReason ? "CANNOT AUDIT" : r.ok ? "OK" : "GAP";
|
|
27872
27979
|
lines2.push(`
|
|
27873
|
-
${r.repo} (${r.class}, policy=${r.mergePolicy}) ${
|
|
27980
|
+
${r.repo} (${r.class}, policy=${r.mergePolicy}) ${verdict}`);
|
|
27981
|
+
if (r.unauditedReason) {
|
|
27982
|
+
lines2.push(` UNAUDITED \u2014 ${r.unauditedReason}`);
|
|
27983
|
+
continue;
|
|
27984
|
+
}
|
|
27874
27985
|
for (const c of r.checks) {
|
|
27875
27986
|
lines2.push(` ${c.ok ? "OK" : "FAIL"} ${c.label}${c.detail ? ` \u2014 ${c.detail}` : ""}`);
|
|
27876
27987
|
}
|
|
@@ -31356,7 +31467,7 @@ async function readSecretStdin() {
|
|
|
31356
31467
|
}
|
|
31357
31468
|
const chunks = [];
|
|
31358
31469
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
31359
|
-
return Buffer.concat(chunks).toString("utf8")
|
|
31470
|
+
return stripTrailingCRLF(Buffer.concat(chunks).toString("utf8"));
|
|
31360
31471
|
}
|
|
31361
31472
|
function makeSecretsDeps(cfg) {
|
|
31362
31473
|
return {
|
|
@@ -31508,6 +31619,10 @@ function registerSecretsCommands(program3) {
|
|
|
31508
31619
|
});
|
|
31509
31620
|
if (!ok) process.exitCode = 1;
|
|
31510
31621
|
}));
|
|
31622
|
+
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) => {
|
|
31623
|
+
const ok = await secretsUndeclare(d, key, { repo: o.repo, json: o.json });
|
|
31624
|
+
if (!ok) process.exitCode = 1;
|
|
31625
|
+
}));
|
|
31511
31626
|
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
31627
|
const ok = await secretsVerify(d, key, o);
|
|
31513
31628
|
if (!ok) process.exitCode = 1;
|
|
@@ -33370,9 +33485,13 @@ function applyWaivers(checks, waivers) {
|
|
|
33370
33485
|
const set = new Set(waivers);
|
|
33371
33486
|
return checks.map((c) => !c.ok && set.has(c.label) ? { ...c, waived: true } : c);
|
|
33372
33487
|
}
|
|
33488
|
+
var laneReasons = (doctor) => doctor.findings.filter((f) => (f.severity === "blocker" || f.code === "origin-unverified") && f.code !== SUITE_GUARD_CODE);
|
|
33489
|
+
function onlySuiteLeadOwned(doctor) {
|
|
33490
|
+
return !doctor.ready && laneReasons(doctor).length === 0 && doctor.findings.some((f) => f.code === SUITE_GUARD_CODE);
|
|
33491
|
+
}
|
|
33373
33492
|
function laneNotEvaluatedFromThisBranch(doctor) {
|
|
33374
33493
|
if (doctor.ready) return void 0;
|
|
33375
|
-
const reasons = doctor
|
|
33494
|
+
const reasons = laneReasons(doctor);
|
|
33376
33495
|
const only = reasons.length === 1 ? reasons[0] : void 0;
|
|
33377
33496
|
return only?.code === "branch-mismatch" ? only.title : void 0;
|
|
33378
33497
|
}
|
|
@@ -33383,7 +33502,7 @@ function attachTrainDoctorVerdicts(report, trainDoctors) {
|
|
|
33383
33502
|
});
|
|
33384
33503
|
return {
|
|
33385
33504
|
...report,
|
|
33386
|
-
ok: report.ok && marked.every((doctor) => doctor.ready || doctor.notEvaluatedFromThisBranch !== void 0),
|
|
33505
|
+
ok: report.ok && marked.every((doctor) => doctor.ready || doctor.notEvaluatedFromThisBranch !== void 0 || onlySuiteLeadOwned(doctor)),
|
|
33387
33506
|
...marked.length ? { trainDoctors: marked } : {}
|
|
33388
33507
|
};
|
|
33389
33508
|
}
|
|
@@ -33397,6 +33516,8 @@ function renderBootstrapVerifyReport(report) {
|
|
|
33397
33516
|
lines2.push("", formatTrainDoctor(doctor));
|
|
33398
33517
|
if (doctor.notEvaluatedFromThisBranch) {
|
|
33399
33518
|
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.`);
|
|
33519
|
+
} else if (onlySuiteLeadOwned(doctor)) {
|
|
33520
|
+
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
33521
|
}
|
|
33401
33522
|
}
|
|
33402
33523
|
return lines2.join("\n");
|
|
@@ -42112,6 +42233,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
42112
42233
|
try {
|
|
42113
42234
|
entries = (0, import_node_fs40.readdirSync)(dir, { withFileTypes: true });
|
|
42114
42235
|
} catch (e) {
|
|
42236
|
+
if (e.code === "ENOENT") continue;
|
|
42115
42237
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
42116
42238
|
}
|
|
42117
42239
|
for (const entry of entries) {
|
|
@@ -43930,7 +44052,7 @@ ${list}`);
|
|
|
43930
44052
|
console.error(line);
|
|
43931
44053
|
}
|
|
43932
44054
|
}
|
|
43933
|
-
const credential =
|
|
44055
|
+
const credential = "user";
|
|
43934
44056
|
if (o.json) printLine(JSON.stringify({ ...result, credential }));
|
|
43935
44057
|
else printLine(
|
|
43936
44058
|
`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""} (credential: ${credential})` + (result.branchFinality ? ` \u2014 ${result.branchFinality}` : "")
|
|
@@ -44445,9 +44567,8 @@ function trainApplyDeps() {
|
|
|
44445
44567
|
trainWriter: (verb, cwd) => acquireTrainWriterLock(cwd, `mmi-cli devops ${verb}`),
|
|
44446
44568
|
run: async (file, args) => {
|
|
44447
44569
|
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
44570
|
try {
|
|
44450
|
-
return isWin3 && file === "npm" ? (await execFileP("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP(file, file === "git" ? trainGitArgs(args) : args, { timeout
|
|
44571
|
+
return isWin3 && file === "npm" ? (await execFileP("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP(file, file === "git" ? trainGitArgs(args) : args, { timeout })).stdout;
|
|
44451
44572
|
} catch (e) {
|
|
44452
44573
|
throw surfaceTrainSubprocessFailure(e, file, args);
|
|
44453
44574
|
}
|
package/package.json
CHANGED