@m8t-stack/cli 0.2.23 → 0.2.24

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/cli.js CHANGED
@@ -1231,7 +1231,7 @@ var init_enable_hosted_brain = __esm({
1231
1231
  import { Builtins, Cli } from "clipanion";
1232
1232
 
1233
1233
  // src/lib/package-version.ts
1234
- var CLI_VERSION = "0.2.23";
1234
+ var CLI_VERSION = "0.2.24";
1235
1235
 
1236
1236
  // src/lib/render-error.ts
1237
1237
  init_errors();
@@ -14297,6 +14297,14 @@ function run(cmd, cmdArgs, cwd, env) {
14297
14297
  c.stderr.on("data", (d) => {
14298
14298
  err += d.toString();
14299
14299
  });
14300
+ c.on("error", (e) => {
14301
+ reject(
14302
+ new LocalCliError({
14303
+ code: "GH_APP_PUSH_FAILED",
14304
+ message: `${cmd} ${cmdArgs.join(" ")} could not run: ${e.message}`
14305
+ })
14306
+ );
14307
+ });
14300
14308
  c.on("close", (code) => {
14301
14309
  if (code === 0) {
14302
14310
  resolve3();
@@ -14627,6 +14635,19 @@ async function promptCollision(ctx, info) {
14627
14635
  rl.close();
14628
14636
  }
14629
14637
  }
14638
+ function resolveScaffoldCommit(repoRoot, env) {
14639
+ const baked = env.M8T_BUILD_SHA?.trim();
14640
+ if (baked) return baked;
14641
+ try {
14642
+ return execFileSync("git", ["rev-parse", "HEAD"], {
14643
+ cwd: repoRoot,
14644
+ encoding: "utf8",
14645
+ stdio: ["ignore", "pipe", "ignore"]
14646
+ }).trim() || "(scaffold)";
14647
+ } catch {
14648
+ return "(scaffold)";
14649
+ }
14650
+ }
14630
14651
  function stageAsGitRepo(dir) {
14631
14652
  const steps = [
14632
14653
  { args: ["init", "-b", "main"], label: "git init" },
@@ -14770,7 +14791,7 @@ var BrainCreateCommand = class extends M8tCommand {
14770
14791
  if (needsPush) {
14771
14792
  materializeBrainTree({ templateSrc, destDir: tmpDir, seedDir });
14772
14793
  if (seedName && seedDir) {
14773
- const headSha = execFileSync("git", ["rev-parse", "HEAD"], { cwd: repoRoot, encoding: "utf8" }).trim();
14794
+ const headSha = resolveScaffoldCommit(repoRoot, env);
14774
14795
  writeSeedMarker({ destDir: tmpDir, seedName, seedDir, refreshedToCommit: headSha, version: "(scaffold)" });
14775
14796
  }
14776
14797
  this.context.stdout.write(
@@ -25388,6 +25409,7 @@ init_errors();
25388
25409
 
25389
25410
  // src/lib/bootstrap-mi.ts
25390
25411
  var INSTALLER_GRANT_DESCRIPTION = "m8t-installer auto-reap (bootstrap)";
25412
+ var GATEWAY_SUBSCOPE_GRANT_DESCRIPTION = "m8t-gateway auto-reap (bootstrap)";
25391
25413
  async function ensureResourceGroup2(opts) {
25392
25414
  const existing = (await runAz(["group", "show", "--name", opts.name, "--subscription", opts.subscriptionId, "-o", "json"]).catch(() => "")).trim();
25393
25415
  if (existing) return;
@@ -25631,7 +25653,7 @@ async function writeBootstrapState(state, home = os13.homedir()) {
25631
25653
  // src/commands/bootstrap/launch.ts
25632
25654
  var DEFAULT_RG = "rg-m8t-stack";
25633
25655
  var DEFAULT_INSTALLER = "ghcr.io/m8t-labs/m8t-installer";
25634
- var DEFAULT_INSTALLER_TAG = "v0.1.30";
25656
+ var DEFAULT_INSTALLER_TAG = "v0.1.31";
25635
25657
  var ACI_NAME = "m8t-installer";
25636
25658
  var MI_NAME = "m8t-installer-mi";
25637
25659
  var BootstrapLaunchCommand = class extends M8tCommand {
@@ -25642,8 +25664,8 @@ var BootstrapLaunchCommand = class extends M8tCommand {
25642
25664
  examples: [
25643
25665
  ["Launch in eastus2", "$0 bootstrap launch --location eastus2"],
25644
25666
  ["BYO app registration", "$0 bootstrap launch --location eastus2 --client-id <appId>"],
25645
- ["Pin a specific installer tag", "$0 bootstrap launch --location eastus2 --installer-tag v0.1.30"],
25646
- ["Override the full installer image ref", "$0 bootstrap launch --location eastus2 --installer-image ghcr.io/m8t-labs/m8t-installer:v0.1.30"]
25667
+ ["Pin a specific installer tag", "$0 bootstrap launch --location eastus2 --installer-tag v0.1.31"],
25668
+ ["Override the full installer image ref", "$0 bootstrap launch --location eastus2 --installer-image ghcr.io/m8t-labs/m8t-installer:v0.1.31"]
25647
25669
  ]
25648
25670
  });
25649
25671
  location = Option46.String("--location");
@@ -25869,35 +25891,53 @@ init_errors();
25869
25891
 
25870
25892
  // src/lib/bootstrap-reap.ts
25871
25893
  var ROLE_ASSIGNMENT_API = "2022-04-01";
25872
- async function sweepOrphanOwnerAssignments(opts) {
25873
- const subScope = `/subscriptions/${opts.subscriptionId}`;
25874
- const all = JSON.parse(
25894
+ var GATEWAY_SUBSCOPE_ROLES = ["Cost Management Reader", "Monitoring Reader"];
25895
+ async function listSubscriptionAssignments(subscriptionId) {
25896
+ return JSON.parse(
25875
25897
  await runAz([
25876
25898
  "role",
25877
25899
  "assignment",
25878
25900
  "list",
25879
25901
  "--all",
25880
25902
  "--subscription",
25881
- opts.subscriptionId,
25903
+ subscriptionId,
25882
25904
  "-o",
25883
25905
  "json"
25884
25906
  ]).catch(() => "[]") || "[]"
25885
25907
  );
25908
+ }
25909
+ async function deleteAssignmentsById(ids) {
25910
+ for (const id of ids) {
25911
+ await runAz([
25912
+ "rest",
25913
+ "--method",
25914
+ "delete",
25915
+ "--url",
25916
+ `https://management.azure.com${id}?api-version=${ROLE_ASSIGNMENT_API}`
25917
+ ]).catch(() => void 0);
25918
+ }
25919
+ }
25920
+ async function sweepOrphanGatewaySubRoles(opts) {
25921
+ const subScope = `/subscriptions/${opts.subscriptionId}`;
25922
+ const all = await listSubscriptionAssignments(opts.subscriptionId);
25923
+ const matched = all.filter((r) => {
25924
+ if (r.principalName) return false;
25925
+ if (r.principalType !== "ServicePrincipal") return false;
25926
+ if (r.scope !== subScope) return false;
25927
+ if (r.description === GATEWAY_SUBSCOPE_GRANT_DESCRIPTION) return true;
25928
+ return GATEWAY_SUBSCOPE_ROLES.includes(r.roleDefinitionName ?? "");
25929
+ });
25930
+ if (opts.execute) await deleteAssignmentsById(matched.map((r) => r.id));
25931
+ return matched.map((r) => ({ id: r.id, description: r.description, roleDefinitionName: r.roleDefinitionName, createdOn: r.createdOn }));
25932
+ }
25933
+ async function sweepOrphanOwnerAssignments(opts) {
25934
+ const subScope = `/subscriptions/${opts.subscriptionId}`;
25935
+ const all = await listSubscriptionAssignments(opts.subscriptionId);
25886
25936
  const matched = all.filter((r) => {
25887
25937
  if (r.description === INSTALLER_GRANT_DESCRIPTION) return true;
25888
25938
  return !r.principalName && r.principalType === "ServicePrincipal" && r.roleDefinitionName === "Owner" && r.scope === subScope;
25889
25939
  });
25890
- if (opts.execute) {
25891
- for (const r of matched) {
25892
- await runAz([
25893
- "rest",
25894
- "--method",
25895
- "delete",
25896
- "--url",
25897
- `https://management.azure.com${r.id}?api-version=${ROLE_ASSIGNMENT_API}`
25898
- ]).catch(() => void 0);
25899
- }
25900
- }
25940
+ if (opts.execute) await deleteAssignmentsById(matched.map((r) => r.id));
25901
25941
  return matched.map((r) => ({ id: r.id, description: r.description, createdOn: r.createdOn }));
25902
25942
  }
25903
25943
  async function reapInstaller(opts) {
@@ -25954,22 +25994,30 @@ var BootstrapReapCommand = class extends M8tCommand {
25954
25994
  if (!sub) {
25955
25995
  throw new LocalCliError({ code: "BOOTSTRAP_SWEEP_NO_SUB", message: "No active subscription.", hint: "Run 'az account set --subscription <id>'." });
25956
25996
  }
25957
- const found = await sweepOrphanOwnerAssignments({ subscriptionId: sub, execute: this.yes === true });
25958
- if (found.length === 0) {
25959
- this.context.stdout.write(`${colors.success("\u2713")} No orphaned installer Owner@sub assignments found.
25997
+ const execute = this.yes === true;
25998
+ const owner = await sweepOrphanOwnerAssignments({ subscriptionId: sub, execute });
25999
+ const gateway = await sweepOrphanGatewaySubRoles({ subscriptionId: sub, execute });
26000
+ const total = owner.length + gateway.length;
26001
+ if (total === 0) {
26002
+ this.context.stdout.write(`${colors.success("\u2713")} No orphaned installer Owner@sub or gateway sub-scope assignments found.
25960
26003
  `);
25961
26004
  return 0;
25962
26005
  }
25963
- for (const f of found) {
25964
- this.context.stdout.write(` ${f.id}${f.createdOn ? colors.dim(` (created ${f.createdOn})`) : ""}${f.description ? colors.dim(" [tagged]") : ""}
26006
+ for (const f of owner) {
26007
+ this.context.stdout.write(` ${colors.dim("Owner@sub")} ${f.id}${f.createdOn ? colors.dim(` (created ${f.createdOn})`) : ""}${f.description ? colors.dim(" [tagged]") : ""}
26008
+ `);
26009
+ }
26010
+ for (const f of gateway) {
26011
+ this.context.stdout.write(` ${colors.dim(f.roleDefinitionName ?? "gateway sub-scope")} ${f.id}${f.createdOn ? colors.dim(` (created ${f.createdOn})`) : ""}${f.description ? colors.dim(" [tagged]") : ""}
25965
26012
  `);
25966
26013
  }
25967
- if (this.yes === true) {
25968
- this.context.stdout.write(`${colors.success("\u2713")} Removed ${String(found.length)} orphaned Owner@sub assignment(s).
26014
+ const breakdown = `${String(owner.length)} Owner@sub, ${String(gateway.length)} gateway sub-scope`;
26015
+ if (execute) {
26016
+ this.context.stdout.write(`${colors.success("\u2713")} Removed ${String(total)} orphaned assignment(s) (${breakdown}).
25969
26017
  `);
25970
26018
  } else {
25971
26019
  this.context.stdout.write(`
25972
- Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colors.hint("Re-run with --sweep-orphans --yes to delete them.")}
26020
+ Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors.hint("Re-run with --sweep-orphans --yes to delete them.")}
25973
26021
  `);
25974
26022
  }
25975
26023
  return 0;
@@ -25981,10 +26029,12 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
25981
26029
  if (!sub) {
25982
26030
  throw new LocalCliError({ code: "BOOTSTRAP_SWEEP_NO_SUB", message: "No active subscription.", hint: "Run 'az account set --subscription <id>'." });
25983
26031
  }
25984
- const found = await sweepOrphanOwnerAssignments({ subscriptionId: sub, execute: true });
26032
+ const owner = await sweepOrphanOwnerAssignments({ subscriptionId: sub, execute: true });
26033
+ const gateway = await sweepOrphanGatewaySubRoles({ subscriptionId: sub, execute: true });
26034
+ const total = owner.length + gateway.length;
25985
26035
  this.context.stdout.write(
25986
- found.length === 0 ? `${colors.success("\u2713")} No bootstrap state and no orphaned installer Owner@sub assignments. Nothing to reap.
25987
- ` : `${colors.success("\u2713")} No bootstrap state found; swept ${String(found.length)} orphaned installer Owner@sub assignment(s).
26036
+ total === 0 ? `${colors.success("\u2713")} No bootstrap state and no orphaned installer Owner@sub or gateway sub-scope assignments. Nothing to reap.
26037
+ ` : `${colors.success("\u2713")} No bootstrap state found; swept ${String(total)} orphaned assignment(s) (${String(owner.length)} Owner@sub, ${String(gateway.length)} gateway sub-scope).
25988
26038
  `
25989
26039
  );
25990
26040
  return 0;
@@ -25992,7 +26042,7 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
25992
26042
  throw new LocalCliError({
25993
26043
  code: "BOOTSTRAP_NO_STATE",
25994
26044
  message: "No bootstrap state to reap.",
25995
- hint: "Nothing to do. If a launch failed early it may have orphaned an installer Owner@sub grant \u2014 run 'm8t bootstrap reap --force' (or 'm8t bootstrap reap --sweep-orphans --yes') to clean it."
26045
+ hint: "Nothing to do. If a launch failed and its RG was deleted it may have orphaned an installer Owner@sub grant or the gateway's sub-scope reader roles \u2014 run 'm8t bootstrap reap --force' (or 'm8t bootstrap reap --sweep-orphans --yes') to clean them."
25996
26046
  });
25997
26047
  }
25998
26048
  if (this.force !== true) {