@mutmutco/cli 2.9.0 → 2.10.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.
Files changed (2) hide show
  1. package/dist/index.cjs +51 -4
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -5566,9 +5566,10 @@ async function buildTrainApplyContext(deps) {
5566
5566
  const [owner, name] = repo.split("/");
5567
5567
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
5568
5568
  const login = requireValue(clean(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
5569
- const role = clean(await deps.run("gh", ["api", `orgs/${owner}/memberships/${login}`, "--jq", ".role"]));
5570
- if (role !== "admin") {
5571
- throw new Error(`${commandAuthorityLabel(owner)} is master-admin only; @${login} is ${role || "not an org admin"}`);
5569
+ const verdict = await deps.trainAuthority(repo);
5570
+ if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
5571
+ if (!verdict.train) {
5572
+ throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
5572
5573
  }
5573
5574
  return {
5574
5575
  repo,
@@ -6391,6 +6392,25 @@ var PROJECTS_ENVELOPE_KEY = "projects";
6391
6392
 
6392
6393
  // src/registry-client.ts
6393
6394
  var DEFAULT_TIMEOUT_MS = 8e3;
6395
+ async function fetchTrainAuthority(repo, deps) {
6396
+ if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
6397
+ const token = await deps.token();
6398
+ if (!token) return { ok: false, error: "no GitHub token (gh auth login)" };
6399
+ const doFetch = deps.fetch ?? fetch;
6400
+ try {
6401
+ const res = await doFetch(`${deps.baseUrl.replace(/\/$/, "")}/train-authority?repo=${encodeURIComponent(repo)}`, {
6402
+ method: "GET",
6403
+ headers: { Authorization: `Bearer ${token}` },
6404
+ signal: AbortSignal.timeout(deps.timeoutMs ?? DEFAULT_TIMEOUT_MS)
6405
+ });
6406
+ if (!res.ok) return { ok: false, error: `train-authority HTTP ${res.status}` };
6407
+ const body = await res.json();
6408
+ if (typeof body?.train !== "boolean" || !body.role) return { ok: false, error: "malformed train-authority response" };
6409
+ return { ok: true, authority: body };
6410
+ } catch (e) {
6411
+ return { ok: false, error: e.message };
6412
+ }
6413
+ }
6394
6414
  async function fetchProjectsList(deps) {
6395
6415
  if (!deps.baseUrl) return null;
6396
6416
  const token = await deps.token();
@@ -8845,7 +8865,11 @@ for (const commandName of ["rcand", "release", "hotfix"]) {
8845
8865
  try {
8846
8866
  const result = await runTrainApply(commandName, {
8847
8867
  run: async (file, args) => (await execFileP3(file, args, { timeout: file === "gh" ? 3e4 : GIT_TIMEOUT_MS })).stdout,
8848
- runSelf: async (args) => (await execFileP3(process.execPath, [process.argv[1], ...args], { timeout: 3e4 })).stdout
8868
+ runSelf: async (args) => (await execFileP3(process.execPath, [process.argv[1], ...args], { timeout: 3e4 })).stdout,
8869
+ trainAuthority: async (repo) => {
8870
+ const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
8871
+ return verdict.ok ? { ok: true, role: verdict.authority.role, train: verdict.authority.train } : verdict;
8872
+ }
8849
8873
  });
8850
8874
  const message = `mmi-cli ${commandName}: applied ${result.repo} ${result.stage} train at ${result.tag} [${result.deployModel}]; ${result.dispatch}`;
8851
8875
  return printLine(o.json ? JSON.stringify(result, null, 2) : message);
@@ -9011,6 +9035,29 @@ LIVE apply to ${repo}:
9011
9035
  }
9012
9036
  });
9013
9037
  var access = program2.command("access").description("org access audit (read-only)");
9038
+ access.command("role [repo]").description("D14 train authority for a repo (server-side Hub check): master | project-admin | developer").option("--json", "machine-readable output").action(async (repoArg) => {
9039
+ const o = { json: rawFlag("--json") };
9040
+ const repo = repoArg ?? await resolveRepo();
9041
+ if (!repo) return fail("access role: pass <owner/repo> or run inside a repo checkout");
9042
+ const cfg = await loadConfig();
9043
+ const verdict = await fetchTrainAuthority(repo, registryClientDeps(cfg));
9044
+ if (!verdict.ok) {
9045
+ if (o.json) {
9046
+ console.log(JSON.stringify({ repo, train: false, error: verdict.error }));
9047
+ process.exitCode = 1;
9048
+ return;
9049
+ }
9050
+ return fail(`access role: ${verdict.error}`);
9051
+ }
9052
+ const a = verdict.authority;
9053
+ if (o.json) {
9054
+ console.log(JSON.stringify(a));
9055
+ if (!a.train) process.exitCode = 1;
9056
+ return;
9057
+ }
9058
+ console.log(`${a.repo}: @${a.login} is ${a.role} \u2014 train ${a.train ? "AUTHORIZED" : "not authorized"}${a.hubTrainMasterOnly ? " (Hub train is master-only)" : ""}`);
9059
+ if (!a.train) process.exitCode = 1;
9060
+ });
9014
9061
  access.command("audit").description("audit collaborator roles + train-branch push allowlists vs the locked state; read-only, emits gh remediation, never applies").option("--json", "machine-readable output").option("--repo <owner/repo>", "audit a single repo instead of the whole org").option("--class <class>", "repo class for --repo (deployable | content)", "deployable").action(async () => {
9015
9062
  const o = { json: rawFlag("--json"), repo: rawValue("--repo", ""), class: rawValue("--class", "deployable") };
9016
9063
  const deps = { gh: async (args) => execFileP3("gh", args, { timeout: 2e4 }) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",