@mutmutco/cli 3.69.0 → 3.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +133 -45
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -33,9 +33,11 @@ __export(index_exports, {
|
|
|
33
33
|
DEFAULT_PRIORITY: () => DEFAULT_PRIORITY,
|
|
34
34
|
awsCallerArn: () => awsCallerArn,
|
|
35
35
|
classifyParseError: () => classifyParseError,
|
|
36
|
+
commandOwnLongFlags: () => commandOwnLongFlags,
|
|
36
37
|
envHealLockPath: () => envHealLockPath,
|
|
37
38
|
gcPlan: () => gcPlan,
|
|
38
39
|
isOrgRegisteredRepo: () => isOrgRegisteredRepo,
|
|
40
|
+
positionalTargetForm: () => positionalTargetForm,
|
|
39
41
|
registryClientDeps: () => registryClientDeps,
|
|
40
42
|
repoSlug: () => repoSlug,
|
|
41
43
|
shouldMarkWorktreeActivity: () => shouldMarkWorktreeActivity,
|
|
@@ -7752,7 +7754,13 @@ function collectRegistryRepos(projects) {
|
|
|
7752
7754
|
if (![...seen].some((r) => r.toLowerCase() === HUB_REPO.toLowerCase())) seen.add(HUB_REPO);
|
|
7753
7755
|
return [...seen].sort((a, b) => a.localeCompare(b));
|
|
7754
7756
|
}
|
|
7755
|
-
async function
|
|
7757
|
+
async function prTriggeredWorkflowsOnRef(deps, repo, branch, repoClass) {
|
|
7758
|
+
const hasGate = repoClass === "hub" ? true : await contentExists(deps, repo, branch, PRODUCT_GATE_PATH);
|
|
7759
|
+
const workflowPaths = repoClass === "hub" ? [".github/workflows/gate.yml"] : hasGate ? [PRODUCT_GATE_PATH] : await listWorkflowPaths(deps, repo, branch);
|
|
7760
|
+
if (workflowPaths === void 0) return void 0;
|
|
7761
|
+
return filterPullRequestTriggered(deps, repo, branch, workflowPaths);
|
|
7762
|
+
}
|
|
7763
|
+
async function resolveRepoMergeCiPolicy(repo, deps, headRef) {
|
|
7756
7764
|
const meta = await deps.getProjectMeta(slugFromRepo(repo));
|
|
7757
7765
|
const repoClass = classifyRepo(repo, meta);
|
|
7758
7766
|
if (repoClass === "content") {
|
|
@@ -7763,17 +7771,25 @@ async function resolveRepoMergeCiPolicy(repo, deps) {
|
|
|
7763
7771
|
});
|
|
7764
7772
|
}
|
|
7765
7773
|
const baseBranch = "development";
|
|
7766
|
-
const
|
|
7767
|
-
|
|
7768
|
-
if (workflowPaths === void 0) {
|
|
7774
|
+
const prTriggered = await prTriggeredWorkflowsOnRef(deps, repo, baseBranch, repoClass);
|
|
7775
|
+
if (prTriggered === void 0) {
|
|
7769
7776
|
return { policy: "wait-for-checks", reason: "workflows listing unreadable (API error) \u2014 failing safe to wait-for-checks" };
|
|
7770
7777
|
}
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7778
|
+
let effective = prTriggered;
|
|
7779
|
+
let headReason = "";
|
|
7780
|
+
if (!effective.length && headRef && headRef !== baseBranch) {
|
|
7781
|
+
const onHead = await prTriggeredWorkflowsOnRef(deps, repo, headRef, repoClass);
|
|
7782
|
+
if (onHead?.length) {
|
|
7783
|
+
effective = onHead;
|
|
7784
|
+
headReason = ` (on PR head ${headRef}; absent on ${baseBranch})`;
|
|
7785
|
+
}
|
|
7786
|
+
}
|
|
7787
|
+
const resolved = resolveMergeCiPolicy({
|
|
7788
|
+
workflowPaths: effective,
|
|
7774
7789
|
registryCi: meta?.ci,
|
|
7775
7790
|
registryRequiredChecks: meta?.requiredChecks
|
|
7776
7791
|
});
|
|
7792
|
+
return headReason ? { ...resolved, reason: `${resolved.reason}${headReason}` } : resolved;
|
|
7777
7793
|
}
|
|
7778
7794
|
async function filterPullRequestTriggered(deps, repo, branch, paths) {
|
|
7779
7795
|
const kept = [];
|
|
@@ -9465,7 +9481,8 @@ var OWNER = "mutmutco";
|
|
|
9465
9481
|
var SSM_ROOT = "/mmi-future";
|
|
9466
9482
|
var PROJECT_TIER_SEGMENT = "dev";
|
|
9467
9483
|
var ORG_INFRA_SLUG = "_org";
|
|
9468
|
-
var
|
|
9484
|
+
var KEY_MAX_PATH_SEGMENTS = 4;
|
|
9485
|
+
var KEY_RE = new RegExp(`^(?:[a-z0-9][a-z0-9-]*/){0,${KEY_MAX_PATH_SEGMENTS}}[A-Za-z][A-Za-z0-9_]*$`);
|
|
9469
9486
|
function isValidSecretKey(key) {
|
|
9470
9487
|
if (!key || key.length > 256) return false;
|
|
9471
9488
|
if (key.includes("..") || key.startsWith("/") || key.includes("*")) return false;
|
|
@@ -9664,7 +9681,7 @@ function formatCapabilities(r) {
|
|
|
9664
9681
|
no vault credentials visible`;
|
|
9665
9682
|
const width = Math.max(...items.map((i) => i.scope.length));
|
|
9666
9683
|
const lines = items.map(
|
|
9667
|
-
(i) => `${i.accessible ? "+" : " "} ${i.scope.padEnd(width)} ${i.tier.padEnd(7)} ${i.reason}`
|
|
9684
|
+
(i) => `${i.accessible ? "+" : " "} ${i.scope.padEnd(width)} ${i.tier.padEnd(7)} ${i.grantScope ? `${i.reason} (${i.grantScope})` : i.reason}`
|
|
9668
9685
|
);
|
|
9669
9686
|
const reachable = items.filter((i) => i.accessible).length;
|
|
9670
9687
|
return [
|
|
@@ -9675,6 +9692,7 @@ no vault credentials visible`;
|
|
|
9675
9692
|
...lines,
|
|
9676
9693
|
"",
|
|
9677
9694
|
"+ = usable now. Consume keyless: `mmi-cli secrets use <KEY> -- <cmd>` (inject, never printed); validate: `mmi-cli secrets verify <KEY>`. No command prints a raw value (#2844).",
|
|
9695
|
+
" `grant (read)` = keyless use only \u2014 rotating or removing that key still needs an rw grant (#3652).",
|
|
9678
9696
|
` Org-infra (#3463): \`--slug\` is ALWAYS \`${ORG_INFRA_SLUG}\`; the provider is the key's FIRST SEGMENT \u2014 \`mmi-cli secrets use cloudflare/GLOBAL_API_KEY --slug ${ORG_INFRA_SLUG} -- <cmd>\` (master-gated).`
|
|
9679
9697
|
].join("\n");
|
|
9680
9698
|
}
|
|
@@ -10339,11 +10357,29 @@ async function secretsRemove(deps, key, opts) {
|
|
|
10339
10357
|
}
|
|
10340
10358
|
deps.log(`removed ${key}`);
|
|
10341
10359
|
}
|
|
10342
|
-
|
|
10360
|
+
var WILDCARD_GRANT_KEY_RE = /^(?:[a-z][a-z0-9-]*\/)?\*$/;
|
|
10361
|
+
function isWildcardGrantKey(key) {
|
|
10362
|
+
return WILDCARD_GRANT_KEY_RE.test(key);
|
|
10363
|
+
}
|
|
10364
|
+
function isValidGrantKey(key) {
|
|
10365
|
+
if (!key || key.length > 256) return false;
|
|
10366
|
+
if (key.includes("..") || key.startsWith("/")) return false;
|
|
10367
|
+
return isWildcardGrantKey(key) || isValidSecretKey(key);
|
|
10368
|
+
}
|
|
10369
|
+
async function secretsGrant(deps, repo, login, key, opts) {
|
|
10370
|
+
if (!isValidGrantKey(key)) {
|
|
10371
|
+
deps.err(`invalid grant key ${JSON.stringify(key)} \u2014 expected <provider>/<KEY>, or a read-only wildcard \`*\` / \`<provider>/*\``);
|
|
10372
|
+
return;
|
|
10373
|
+
}
|
|
10374
|
+
if (isWildcardGrantKey(key) && !opts.read) {
|
|
10375
|
+
deps.err(`secrets grant: ${key} is a wildcard \u2014 pass --read. A wildcard never confers write, so granting it rw is refused rather than silently narrowed.`);
|
|
10376
|
+
return;
|
|
10377
|
+
}
|
|
10378
|
+
const scope = opts.read ? "read" : "rw";
|
|
10343
10379
|
const res = await deps.fetch(`${deps.apiUrl}/secrets/grant`, {
|
|
10344
10380
|
method: "POST",
|
|
10345
10381
|
headers: await deps.headers({ "content-type": "application/json" }),
|
|
10346
|
-
body: JSON.stringify({ repo, login, key }),
|
|
10382
|
+
body: JSON.stringify({ repo, login, key, scope }),
|
|
10347
10383
|
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
10348
10384
|
});
|
|
10349
10385
|
if (!res.ok) {
|
|
@@ -10352,7 +10388,7 @@ async function secretsGrant(deps, repo, login, key, _opts) {
|
|
|
10352
10388
|
);
|
|
10353
10389
|
return;
|
|
10354
10390
|
}
|
|
10355
|
-
deps.log(`granted @${login} access to ${key} in ${repo}`);
|
|
10391
|
+
deps.log(`granted @${login} ${scope === "read" ? "read-only" : "read/write"} access to ${key} in ${repo}`);
|
|
10356
10392
|
}
|
|
10357
10393
|
async function secretsRevoke(deps, repo, login, key, _opts) {
|
|
10358
10394
|
const res = await deps.fetch(`${deps.apiUrl}/secrets/revoke`, {
|
|
@@ -15944,12 +15980,15 @@ async function resolveOwners(deps) {
|
|
|
15944
15980
|
function collaboratorRole(c) {
|
|
15945
15981
|
return c.role_name ?? (c.permissions?.admin ? "admin" : c.permissions?.maintain ? "maintain" : "write");
|
|
15946
15982
|
}
|
|
15947
|
-
async function auditRepoCollaborators(repo, owners, deps) {
|
|
15983
|
+
async function auditRepoCollaborators(repo, owners, deps, projectAdmins = /* @__PURE__ */ new Set(), sanctionedAdmins = /* @__PURE__ */ new Set()) {
|
|
15948
15984
|
const collabs = await restPagedJson(deps, `repos/${repo}/collaborators?affiliation=direct`, []);
|
|
15949
15985
|
const findings = [];
|
|
15986
|
+
const writeOrBetter = /* @__PURE__ */ new Set();
|
|
15950
15987
|
for (const c of collabs) {
|
|
15951
15988
|
if (owners.has(c.login)) continue;
|
|
15952
15989
|
const role = collaboratorRole(c);
|
|
15990
|
+
writeOrBetter.add(c.login);
|
|
15991
|
+
if (OVERGRANT_ROLES.has(role) && sanctionedAdmins.has(c.login)) continue;
|
|
15953
15992
|
if (OVERGRANT_ROLES.has(role)) {
|
|
15954
15993
|
findings.push({
|
|
15955
15994
|
repo,
|
|
@@ -15961,6 +16000,17 @@ async function auditRepoCollaborators(repo, owners, deps) {
|
|
|
15961
16000
|
});
|
|
15962
16001
|
}
|
|
15963
16002
|
}
|
|
16003
|
+
for (const login of projectAdmins) {
|
|
16004
|
+
if (owners.has(login) || writeOrBetter.has(login)) continue;
|
|
16005
|
+
findings.push({
|
|
16006
|
+
repo,
|
|
16007
|
+
kind: "collaborator-undergrant",
|
|
16008
|
+
severity: "medium",
|
|
16009
|
+
actor: login,
|
|
16010
|
+
detail: `@${login} is a declared project-admin of ${repo} in the registry but holds no direct collaborator role; secrets self-serve and train authority fail closed for them`,
|
|
16011
|
+
remediation: `gh api -X PUT repos/${repo}/collaborators/${login} -f permission=push`
|
|
16012
|
+
});
|
|
16013
|
+
}
|
|
15964
16014
|
return findings;
|
|
15965
16015
|
}
|
|
15966
16016
|
async function auditTrainBranch(repo, branch, owners, deps, projectAdmins = /* @__PURE__ */ new Set()) {
|
|
@@ -16008,6 +16058,18 @@ async function auditTrainBranch(repo, branch, owners, deps, projectAdmins = /* @
|
|
|
16008
16058
|
});
|
|
16009
16059
|
}
|
|
16010
16060
|
}
|
|
16061
|
+
for (const login of projectAdmins) {
|
|
16062
|
+
if (owners.has(login) || users.includes(login)) continue;
|
|
16063
|
+
findings.push({
|
|
16064
|
+
repo,
|
|
16065
|
+
branch,
|
|
16066
|
+
kind: "train-allowlist-missing",
|
|
16067
|
+
severity: "medium",
|
|
16068
|
+
actor: login,
|
|
16069
|
+
detail: `declared project-admin @${login} is missing from the ${branch} push allowlist \u2014 the registry grants them train authority the branch lock denies`,
|
|
16070
|
+
remediation: `gh api -X POST repos/${repo}/branches/${branch}/protection/restrictions/users -f users[]="${login}"`
|
|
16071
|
+
});
|
|
16072
|
+
}
|
|
16011
16073
|
const apps = (restrictions.apps ?? []).map((a) => a.slug);
|
|
16012
16074
|
if (!apps.includes(LOCKED_APP)) {
|
|
16013
16075
|
findings.push({
|
|
@@ -16040,9 +16102,9 @@ function auditDataAccessContracts(repo, contracts = { consumers: {} }) {
|
|
|
16040
16102
|
}
|
|
16041
16103
|
return findings;
|
|
16042
16104
|
}
|
|
16043
|
-
async function auditRepoAccess(repo, repoClass, owners, deps, projectAdmins = /* @__PURE__ */ new Set(), dataAccess, releaseTrack) {
|
|
16105
|
+
async function auditRepoAccess(repo, repoClass, owners, deps, projectAdmins = /* @__PURE__ */ new Set(), dataAccess, releaseTrack, sanctionedAdmins = /* @__PURE__ */ new Set()) {
|
|
16044
16106
|
const findings = [];
|
|
16045
|
-
findings.push(...await auditRepoCollaborators(repo, owners, deps));
|
|
16107
|
+
findings.push(...await auditRepoCollaborators(repo, owners, deps, projectAdmins, sanctionedAdmins));
|
|
16046
16108
|
if (dataAccess) findings.push(...auditDataAccessContracts(repo, dataAccess));
|
|
16047
16109
|
const track = releaseTrack ?? (repoClass === "content" ? "trunk" : void 0);
|
|
16048
16110
|
for (const branch of lockedBranches(repoClass, track)) {
|
|
@@ -16082,7 +16144,7 @@ async function auditPluginReadAccess(owners, projectAdmins, deps) {
|
|
|
16082
16144
|
}
|
|
16083
16145
|
return findings;
|
|
16084
16146
|
}
|
|
16085
|
-
async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
|
|
16147
|
+
async function auditOrgAccess(targets, deps, matrix = {}, dataAccess, sanctioned = {}) {
|
|
16086
16148
|
if (targets.length === 0) {
|
|
16087
16149
|
return {
|
|
16088
16150
|
ok: false,
|
|
@@ -16119,7 +16181,16 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
|
|
|
16119
16181
|
orgFindings.push(...await auditPluginReadAccess(owners, allProjectAdmins, deps));
|
|
16120
16182
|
const repos = [];
|
|
16121
16183
|
for (const target of targets) {
|
|
16122
|
-
repos.push(await auditRepoAccess(
|
|
16184
|
+
repos.push(await auditRepoAccess(
|
|
16185
|
+
target.repo,
|
|
16186
|
+
target.class,
|
|
16187
|
+
owners,
|
|
16188
|
+
deps,
|
|
16189
|
+
new Set(entriesValueByCanonicalRepo(matrix, target.repo) ?? []),
|
|
16190
|
+
dataAccess,
|
|
16191
|
+
target.releaseTrack,
|
|
16192
|
+
new Set(entriesValueByCanonicalRepo(sanctioned, target.repo) ?? [])
|
|
16193
|
+
));
|
|
16123
16194
|
}
|
|
16124
16195
|
const ok = orgFindings.every((f) => f.severity !== "high") && repos.every((r) => r.ok);
|
|
16125
16196
|
return { ok, owners: [...owners], orgFindings, repos };
|
|
@@ -16158,6 +16229,10 @@ function loadAccessMatrix(matrixJson) {
|
|
|
16158
16229
|
if (!matrixJson) return {};
|
|
16159
16230
|
return safeJson(matrixJson, {}).projectAdmins ?? {};
|
|
16160
16231
|
}
|
|
16232
|
+
function loadSanctionedAdmins(matrixJson) {
|
|
16233
|
+
if (!matrixJson) return {};
|
|
16234
|
+
return safeJson(matrixJson, {}).sanctionedAdmins ?? {};
|
|
16235
|
+
}
|
|
16161
16236
|
function loadDataAccessContracts(dataAccessJson) {
|
|
16162
16237
|
if (!dataAccessJson) return { consumers: {} };
|
|
16163
16238
|
const parsed = safeJson(dataAccessJson, { consumers: {} });
|
|
@@ -18073,8 +18148,8 @@ function registerSecretsCommands(program3) {
|
|
|
18073
18148
|
if (ok === false) process.exitCode = 1;
|
|
18074
18149
|
});
|
|
18075
18150
|
});
|
|
18076
|
-
secrets.command("grant <repo> <login> <key>").description("MASTER-ONLY: grant a project-admin standing access to
|
|
18077
|
-
secrets.command("revoke <repo> <login> <key>").description("MASTER-ONLY: withdraw a previously granted org-infra secret access").action((repo, login, key) => withSecrets((d) => secretsRevoke(d, repo, login, key, {})));
|
|
18151
|
+
secrets.command("grant <repo> <login> <key>").description("MASTER-ONLY: grant a project-admin standing access to an org-infra secret. Default is read/write on one exact key; --read grants keyless USE only, and only --read accepts a wildcard key (`*` = the whole namespace, `<provider>/*` = one provider group) (#3652)").option("--read", "read-only: permits keyless `secrets use`, never `set`/`rm`. Required for a wildcard key.").action((repo, login, key, o) => withSecrets((d) => secretsGrant(d, repo, login, key, { read: o.read })));
|
|
18152
|
+
secrets.command("revoke <repo> <login> <key>").description("MASTER-ONLY: withdraw a previously granted org-infra secret access (pass the grant key exactly as granted, wildcard included)").action((repo, login, key) => withSecrets((d) => secretsRevoke(d, repo, login, key, {})));
|
|
18078
18153
|
}
|
|
18079
18154
|
|
|
18080
18155
|
// src/app-actor.ts
|
|
@@ -25801,23 +25876,6 @@ async function requireFreshTrainCli(commandName) {
|
|
|
25801
25876
|
if (report.ok) return;
|
|
25802
25877
|
throw new Error(staleTrainCliMessage(report, commandName));
|
|
25803
25878
|
}
|
|
25804
|
-
var cachedLongFlags;
|
|
25805
|
-
function allLongFlags() {
|
|
25806
|
-
if (cachedLongFlags) return cachedLongFlags;
|
|
25807
|
-
const acc = /* @__PURE__ */ new Set();
|
|
25808
|
-
const walk2 = (node) => {
|
|
25809
|
-
if (!isCanonicalSuggestion(node)) return;
|
|
25810
|
-
for (const opt of node.options) {
|
|
25811
|
-
if (opt.discovery) continue;
|
|
25812
|
-
const m = /--[\w-]+/.exec(opt.flags);
|
|
25813
|
-
if (m) acc.add(m[0]);
|
|
25814
|
-
}
|
|
25815
|
-
for (const child2 of node.subcommands) walk2(child2);
|
|
25816
|
-
};
|
|
25817
|
-
walk2(buildCommandManifest(program2).tree);
|
|
25818
|
-
cachedLongFlags = [...acc];
|
|
25819
|
-
return cachedLongFlags;
|
|
25820
|
-
}
|
|
25821
25879
|
var cachedCommandPaths;
|
|
25822
25880
|
function allCommandPaths() {
|
|
25823
25881
|
if (cachedCommandPaths) return cachedCommandPaths;
|
|
@@ -25884,6 +25942,22 @@ function commandPath2(cmd) {
|
|
|
25884
25942
|
}
|
|
25885
25943
|
return parts.join(" ");
|
|
25886
25944
|
}
|
|
25945
|
+
function commandOwnLongFlags(cmd) {
|
|
25946
|
+
if (!cmd) return [];
|
|
25947
|
+
const flags = /* @__PURE__ */ new Set();
|
|
25948
|
+
for (const opt of cmd.options) {
|
|
25949
|
+
if (opt.long) flags.add(opt.long);
|
|
25950
|
+
}
|
|
25951
|
+
return [...flags];
|
|
25952
|
+
}
|
|
25953
|
+
var TARGET_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["--repo", "--ref", "--target", "--project"]);
|
|
25954
|
+
function positionalTargetForm(cmd) {
|
|
25955
|
+
if (!cmd) return void 0;
|
|
25956
|
+
const args = cmd.registeredArguments ?? [];
|
|
25957
|
+
const first = args[0];
|
|
25958
|
+
if (!first) return void 0;
|
|
25959
|
+
return `mmi-cli ${commandPath2(cmd)} <${first.name()}>`;
|
|
25960
|
+
}
|
|
25887
25961
|
function resolveParseHint() {
|
|
25888
25962
|
if (lastParseErrorKind === "unknown-command") {
|
|
25889
25963
|
const path3 = lastUnknownCommand ? suggestCommandPath(lastUnknownCommand, allCommandPaths()) : void 0;
|
|
@@ -25912,10 +25986,13 @@ function envelopeAwareWriteErr(str) {
|
|
|
25912
25986
|
if (match) {
|
|
25913
25987
|
const flag = match[1];
|
|
25914
25988
|
if (argvWantsJson2()) {
|
|
25915
|
-
const
|
|
25989
|
+
const invoked = resolveCommandFromArgv(program2, process.argv.slice(2));
|
|
25990
|
+
const suggestion = didYouMean(flag, commandOwnLongFlags(invoked));
|
|
25916
25991
|
const corrected = suggestion ? `mmi-cli ${process.argv.slice(2).map((a) => a === flag ? suggestion : a).join(" ")}` : void 0;
|
|
25992
|
+
const positional = !suggestion && TARGET_SELECTOR_FLAGS.has(flag) ? positionalTargetForm(invoked) : void 0;
|
|
25993
|
+
const message = positional ? `unknown option '${flag}' \u2014 this command takes its target as a positional: ${positional}` : `unknown option '${flag}'`;
|
|
25917
25994
|
process.stderr.write(
|
|
25918
|
-
formatErrorEnvelope(
|
|
25995
|
+
formatErrorEnvelope(message, {
|
|
25919
25996
|
code: ERROR_CODES.ERR_UNKNOWN_FLAG,
|
|
25920
25997
|
offending_flag: flag,
|
|
25921
25998
|
...suggestion ? { did_you_mean: suggestion } : {},
|
|
@@ -27371,14 +27448,19 @@ async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
|
27371
27448
|
}
|
|
27372
27449
|
}).map((name) => `.github/workflows/${name}`);
|
|
27373
27450
|
}
|
|
27374
|
-
async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
27451
|
+
async function resolveMergeCiPolicyForCheckout(repoOpt, headRef) {
|
|
27375
27452
|
const repo = repoOpt ?? await resolveRepo();
|
|
27376
27453
|
if (repo) {
|
|
27377
|
-
return resolveRepoMergeCiPolicy(repo, ciAuditDeps());
|
|
27454
|
+
return resolveRepoMergeCiPolicy(repo, ciAuditDeps(), headRef);
|
|
27378
27455
|
}
|
|
27379
27456
|
const workflowPaths = await listCiWorkflowPaths();
|
|
27380
27457
|
return resolveMergeCiPolicy({ workflowPaths });
|
|
27381
27458
|
}
|
|
27459
|
+
async function prHeadRefForCiProbe(prNumber, repo) {
|
|
27460
|
+
const snapshot = await fetchRestPrSnapshot(prNumber, repo).catch(() => null);
|
|
27461
|
+
if (!snapshot || snapshot.headIsFork || !snapshot.headRef) return void 0;
|
|
27462
|
+
return snapshot.headRef;
|
|
27463
|
+
}
|
|
27382
27464
|
function ciAuditDeps() {
|
|
27383
27465
|
const cfgPromise = loadConfig();
|
|
27384
27466
|
const root = hubRoot();
|
|
@@ -27417,9 +27499,11 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
|
|
|
27417
27499
|
timeoutMs = Math.round(minutes * 6e4);
|
|
27418
27500
|
}
|
|
27419
27501
|
const repo = await requireRepo(o.repo);
|
|
27420
|
-
const
|
|
27502
|
+
const snapshot = await fetchRestPrSnapshot(number, repo).catch(() => null);
|
|
27503
|
+
const baseBranch = snapshot?.baseRef ?? "development";
|
|
27504
|
+
const ciHeadRef = snapshot && !snapshot.headIsFork && snapshot.headRef ? snapshot.headRef : void 0;
|
|
27421
27505
|
const result = await waitForPrChecks({
|
|
27422
|
-
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo),
|
|
27506
|
+
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
|
|
27423
27507
|
pollChecks: () => pollRestPrChecks(number, repo),
|
|
27424
27508
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
27425
27509
|
pollRateLimit: () => fetchRestCorePool(),
|
|
@@ -27564,12 +27648,13 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
27564
27648
|
const beforeWorktrees = parseWorktreePorcelain(
|
|
27565
27649
|
(await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout
|
|
27566
27650
|
);
|
|
27567
|
-
const
|
|
27651
|
+
const ciHeadRef = repoForPostCleanup ? await prHeadRefForCiProbe(number, repoForPostCleanup) : void 0;
|
|
27652
|
+
const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
|
|
27568
27653
|
if (o.wait) {
|
|
27569
27654
|
const repo = await requireRepo(o.repo);
|
|
27570
27655
|
const baseBranch = await fetchRestPrSnapshot(number, repo).then((s) => s.baseRef).catch(() => "development");
|
|
27571
27656
|
const wait = await waitForPrChecks({
|
|
27572
|
-
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo),
|
|
27657
|
+
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
|
|
27573
27658
|
pollChecks: () => pollRestPrChecks(number, repo),
|
|
27574
27659
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
27575
27660
|
pollRateLimit: () => fetchRestCorePool(),
|
|
@@ -28031,7 +28116,8 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
28031
28116
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
28032
28117
|
const fileContracts = (0, import_node_fs33.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs33.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
28033
28118
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
28034
|
-
const
|
|
28119
|
+
const sanctioned = (0, import_node_fs33.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs33.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
28120
|
+
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
28035
28121
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
28036
28122
|
if (!report.ok) process.exitCode = 1;
|
|
28037
28123
|
});
|
|
@@ -28214,9 +28300,11 @@ program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGr
|
|
|
28214
28300
|
DEFAULT_PRIORITY,
|
|
28215
28301
|
awsCallerArn,
|
|
28216
28302
|
classifyParseError,
|
|
28303
|
+
commandOwnLongFlags,
|
|
28217
28304
|
envHealLockPath,
|
|
28218
28305
|
gcPlan,
|
|
28219
28306
|
isOrgRegisteredRepo,
|
|
28307
|
+
positionalTargetForm,
|
|
28220
28308
|
registryClientDeps,
|
|
28221
28309
|
repoSlug,
|
|
28222
28310
|
shouldMarkWorktreeActivity,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.70.0",
|
|
4
4
|
"description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|