@m8t-stack/cli 0.2.23 → 0.2.25
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 +108 -46
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -604,6 +604,22 @@ async function authedJsonResult(args) {
|
|
|
604
604
|
async function authedJson(args) {
|
|
605
605
|
return (await authedJsonResult(args)).data;
|
|
606
606
|
}
|
|
607
|
+
async function authedJsonPagedValues(args) {
|
|
608
|
+
const maxPages = args.maxPages ?? 100;
|
|
609
|
+
const items = [];
|
|
610
|
+
let url = args.url;
|
|
611
|
+
for (let page = 0; url && page < maxPages; page++) {
|
|
612
|
+
const body = await authedJson({
|
|
613
|
+
credential: args.credential,
|
|
614
|
+
scope: args.scope,
|
|
615
|
+
method: "GET",
|
|
616
|
+
url
|
|
617
|
+
});
|
|
618
|
+
items.push(...body?.value ?? []);
|
|
619
|
+
url = body?.nextLink;
|
|
620
|
+
}
|
|
621
|
+
return items;
|
|
622
|
+
}
|
|
607
623
|
var init_http = __esm({
|
|
608
624
|
"src/lib/http.ts"() {
|
|
609
625
|
"use strict";
|
|
@@ -1231,7 +1247,7 @@ var init_enable_hosted_brain = __esm({
|
|
|
1231
1247
|
import { Builtins, Cli } from "clipanion";
|
|
1232
1248
|
|
|
1233
1249
|
// src/lib/package-version.ts
|
|
1234
|
-
var CLI_VERSION = "0.2.
|
|
1250
|
+
var CLI_VERSION = "0.2.25";
|
|
1235
1251
|
|
|
1236
1252
|
// src/lib/render-error.ts
|
|
1237
1253
|
init_errors();
|
|
@@ -13663,23 +13679,22 @@ var ACCOUNTS_API = "2024-10-01";
|
|
|
13663
13679
|
var PROJECTS_API = "2025-04-01-preview";
|
|
13664
13680
|
async function listProjectsForAccount(credential2, accountId) {
|
|
13665
13681
|
const url = `${ARM}${accountId}/projects?api-version=${PROJECTS_API}`;
|
|
13666
|
-
return await
|
|
13682
|
+
return await authedJsonPagedValues({ credential: credential2, scope: ARM_SCOPE2, url });
|
|
13667
13683
|
}
|
|
13668
13684
|
function formatProjectCandidate(c) {
|
|
13669
13685
|
return `${c.projectName} (account=${c.accountName}, ${c.region})`;
|
|
13670
13686
|
}
|
|
13671
13687
|
async function resolveFoundryProject(opts) {
|
|
13672
13688
|
const candidates = [];
|
|
13673
|
-
const accounts = await
|
|
13689
|
+
const accounts = await authedJsonPagedValues({
|
|
13674
13690
|
credential: opts.credential,
|
|
13675
13691
|
scope: ARM_SCOPE2,
|
|
13676
|
-
method: "GET",
|
|
13677
13692
|
url: `${ARM}/subscriptions/${opts.subscriptionId}/providers/Microsoft.CognitiveServices/accounts?api-version=${ACCOUNTS_API}`
|
|
13678
|
-
})
|
|
13679
|
-
for (const acc of accounts
|
|
13693
|
+
});
|
|
13694
|
+
for (const acc of accounts) {
|
|
13680
13695
|
if (acc.kind !== "AIServices" || !acc.name || !acc.id) continue;
|
|
13681
13696
|
const projects = await listProjectsForAccount(opts.credential, acc.id);
|
|
13682
|
-
for (const proj of projects
|
|
13697
|
+
for (const proj of projects) {
|
|
13683
13698
|
if (!proj.name) continue;
|
|
13684
13699
|
const projectName = proj.name.includes("/") ? proj.name.slice(proj.name.lastIndexOf("/") + 1) : proj.name;
|
|
13685
13700
|
candidates.push({
|
|
@@ -14297,6 +14312,14 @@ function run(cmd, cmdArgs, cwd, env) {
|
|
|
14297
14312
|
c.stderr.on("data", (d) => {
|
|
14298
14313
|
err += d.toString();
|
|
14299
14314
|
});
|
|
14315
|
+
c.on("error", (e) => {
|
|
14316
|
+
reject(
|
|
14317
|
+
new LocalCliError({
|
|
14318
|
+
code: "GH_APP_PUSH_FAILED",
|
|
14319
|
+
message: `${cmd} ${cmdArgs.join(" ")} could not run: ${e.message}`
|
|
14320
|
+
})
|
|
14321
|
+
);
|
|
14322
|
+
});
|
|
14300
14323
|
c.on("close", (code) => {
|
|
14301
14324
|
if (code === 0) {
|
|
14302
14325
|
resolve3();
|
|
@@ -14627,6 +14650,19 @@ async function promptCollision(ctx, info) {
|
|
|
14627
14650
|
rl.close();
|
|
14628
14651
|
}
|
|
14629
14652
|
}
|
|
14653
|
+
function resolveScaffoldCommit(repoRoot, env) {
|
|
14654
|
+
const baked = env.M8T_BUILD_SHA?.trim();
|
|
14655
|
+
if (baked) return baked;
|
|
14656
|
+
try {
|
|
14657
|
+
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
14658
|
+
cwd: repoRoot,
|
|
14659
|
+
encoding: "utf8",
|
|
14660
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
14661
|
+
}).trim() || "(scaffold)";
|
|
14662
|
+
} catch {
|
|
14663
|
+
return "(scaffold)";
|
|
14664
|
+
}
|
|
14665
|
+
}
|
|
14630
14666
|
function stageAsGitRepo(dir) {
|
|
14631
14667
|
const steps = [
|
|
14632
14668
|
{ args: ["init", "-b", "main"], label: "git init" },
|
|
@@ -14770,7 +14806,7 @@ var BrainCreateCommand = class extends M8tCommand {
|
|
|
14770
14806
|
if (needsPush) {
|
|
14771
14807
|
materializeBrainTree({ templateSrc, destDir: tmpDir, seedDir });
|
|
14772
14808
|
if (seedName && seedDir) {
|
|
14773
|
-
const headSha =
|
|
14809
|
+
const headSha = resolveScaffoldCommit(repoRoot, env);
|
|
14774
14810
|
writeSeedMarker({ destDir: tmpDir, seedName, seedDir, refreshedToCommit: headSha, version: "(scaffold)" });
|
|
14775
14811
|
}
|
|
14776
14812
|
this.context.stdout.write(
|
|
@@ -24397,13 +24433,12 @@ var STORAGE_API2 = "2023-05-01";
|
|
|
24397
24433
|
var LA_API = "2023-09-01";
|
|
24398
24434
|
async function discoverLedgerResources(opts) {
|
|
24399
24435
|
const { credential: credential2, subscriptionId, resourceGroup } = opts;
|
|
24400
|
-
const storage = await
|
|
24436
|
+
const storage = await authedJsonPagedValues({
|
|
24401
24437
|
credential: credential2,
|
|
24402
24438
|
scope: ARM_SCOPE8,
|
|
24403
|
-
method: "GET",
|
|
24404
24439
|
url: `${ARM4}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Storage/storageAccounts?api-version=${STORAGE_API2}`
|
|
24405
|
-
})
|
|
24406
|
-
const accounts =
|
|
24440
|
+
});
|
|
24441
|
+
const accounts = storage.filter((a) => a.properties?.primaryEndpoints?.table);
|
|
24407
24442
|
const ledgerAcct = accounts.find((a) => (a.tags?.["m8t-stack:role"] ?? a.properties?.tags?.["m8t-stack:role"]) === "ledger") ?? (accounts.length === 1 ? accounts[0] : void 0);
|
|
24408
24443
|
const ledgerTableEndpoint = ledgerAcct?.properties?.primaryEndpoints?.table?.replace(/\/$/, "");
|
|
24409
24444
|
if (!ledgerTableEndpoint) {
|
|
@@ -24413,13 +24448,11 @@ async function discoverLedgerResources(opts) {
|
|
|
24413
24448
|
hint: `Found: ${accounts.map((a) => a.name).join(", ") || "(none)"}. Tag the ledger account 'm8t-stack:role=ledger'.`
|
|
24414
24449
|
});
|
|
24415
24450
|
}
|
|
24416
|
-
const
|
|
24451
|
+
const ws = await authedJsonPagedValues({
|
|
24417
24452
|
credential: credential2,
|
|
24418
24453
|
scope: ARM_SCOPE8,
|
|
24419
|
-
method: "GET",
|
|
24420
24454
|
url: `${ARM4}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.OperationalInsights/workspaces?api-version=${LA_API}`
|
|
24421
|
-
})
|
|
24422
|
-
const ws = laList.value ?? [];
|
|
24455
|
+
});
|
|
24423
24456
|
const workspaceId = (ws.length === 1 ? ws[0] : ws.find((w) => w.properties?.customerId))?.properties?.customerId;
|
|
24424
24457
|
if (!workspaceId) {
|
|
24425
24458
|
throw new LocalCliError({
|
|
@@ -25388,6 +25421,7 @@ init_errors();
|
|
|
25388
25421
|
|
|
25389
25422
|
// src/lib/bootstrap-mi.ts
|
|
25390
25423
|
var INSTALLER_GRANT_DESCRIPTION = "m8t-installer auto-reap (bootstrap)";
|
|
25424
|
+
var GATEWAY_SUBSCOPE_GRANT_DESCRIPTION = "m8t-gateway auto-reap (bootstrap)";
|
|
25391
25425
|
async function ensureResourceGroup2(opts) {
|
|
25392
25426
|
const existing = (await runAz(["group", "show", "--name", opts.name, "--subscription", opts.subscriptionId, "-o", "json"]).catch(() => "")).trim();
|
|
25393
25427
|
if (existing) return;
|
|
@@ -25631,7 +25665,7 @@ async function writeBootstrapState(state, home = os13.homedir()) {
|
|
|
25631
25665
|
// src/commands/bootstrap/launch.ts
|
|
25632
25666
|
var DEFAULT_RG = "rg-m8t-stack";
|
|
25633
25667
|
var DEFAULT_INSTALLER = "ghcr.io/m8t-labs/m8t-installer";
|
|
25634
|
-
var DEFAULT_INSTALLER_TAG = "v0.1.
|
|
25668
|
+
var DEFAULT_INSTALLER_TAG = "v0.1.32";
|
|
25635
25669
|
var ACI_NAME = "m8t-installer";
|
|
25636
25670
|
var MI_NAME = "m8t-installer-mi";
|
|
25637
25671
|
var BootstrapLaunchCommand = class extends M8tCommand {
|
|
@@ -25642,8 +25676,8 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
25642
25676
|
examples: [
|
|
25643
25677
|
["Launch in eastus2", "$0 bootstrap launch --location eastus2"],
|
|
25644
25678
|
["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.
|
|
25646
|
-
["Override the full installer image ref", "$0 bootstrap launch --location eastus2 --installer-image ghcr.io/m8t-labs/m8t-installer:v0.1.
|
|
25679
|
+
["Pin a specific installer tag", "$0 bootstrap launch --location eastus2 --installer-tag v0.1.32"],
|
|
25680
|
+
["Override the full installer image ref", "$0 bootstrap launch --location eastus2 --installer-image ghcr.io/m8t-labs/m8t-installer:v0.1.32"]
|
|
25647
25681
|
]
|
|
25648
25682
|
});
|
|
25649
25683
|
location = Option46.String("--location");
|
|
@@ -25869,35 +25903,53 @@ init_errors();
|
|
|
25869
25903
|
|
|
25870
25904
|
// src/lib/bootstrap-reap.ts
|
|
25871
25905
|
var ROLE_ASSIGNMENT_API = "2022-04-01";
|
|
25872
|
-
|
|
25873
|
-
|
|
25874
|
-
|
|
25906
|
+
var GATEWAY_SUBSCOPE_ROLES = ["Cost Management Reader", "Monitoring Reader"];
|
|
25907
|
+
async function listSubscriptionAssignments(subscriptionId) {
|
|
25908
|
+
return JSON.parse(
|
|
25875
25909
|
await runAz([
|
|
25876
25910
|
"role",
|
|
25877
25911
|
"assignment",
|
|
25878
25912
|
"list",
|
|
25879
25913
|
"--all",
|
|
25880
25914
|
"--subscription",
|
|
25881
|
-
|
|
25915
|
+
subscriptionId,
|
|
25882
25916
|
"-o",
|
|
25883
25917
|
"json"
|
|
25884
25918
|
]).catch(() => "[]") || "[]"
|
|
25885
25919
|
);
|
|
25920
|
+
}
|
|
25921
|
+
async function deleteAssignmentsById(ids) {
|
|
25922
|
+
for (const id of ids) {
|
|
25923
|
+
await runAz([
|
|
25924
|
+
"rest",
|
|
25925
|
+
"--method",
|
|
25926
|
+
"delete",
|
|
25927
|
+
"--url",
|
|
25928
|
+
`https://management.azure.com${id}?api-version=${ROLE_ASSIGNMENT_API}`
|
|
25929
|
+
]).catch(() => void 0);
|
|
25930
|
+
}
|
|
25931
|
+
}
|
|
25932
|
+
async function sweepOrphanGatewaySubRoles(opts) {
|
|
25933
|
+
const subScope = `/subscriptions/${opts.subscriptionId}`;
|
|
25934
|
+
const all = await listSubscriptionAssignments(opts.subscriptionId);
|
|
25935
|
+
const matched = all.filter((r) => {
|
|
25936
|
+
if (r.principalName) return false;
|
|
25937
|
+
if (r.principalType !== "ServicePrincipal") return false;
|
|
25938
|
+
if (r.scope !== subScope) return false;
|
|
25939
|
+
if (r.description === GATEWAY_SUBSCOPE_GRANT_DESCRIPTION) return true;
|
|
25940
|
+
return GATEWAY_SUBSCOPE_ROLES.includes(r.roleDefinitionName ?? "");
|
|
25941
|
+
});
|
|
25942
|
+
if (opts.execute) await deleteAssignmentsById(matched.map((r) => r.id));
|
|
25943
|
+
return matched.map((r) => ({ id: r.id, description: r.description, roleDefinitionName: r.roleDefinitionName, createdOn: r.createdOn }));
|
|
25944
|
+
}
|
|
25945
|
+
async function sweepOrphanOwnerAssignments(opts) {
|
|
25946
|
+
const subScope = `/subscriptions/${opts.subscriptionId}`;
|
|
25947
|
+
const all = await listSubscriptionAssignments(opts.subscriptionId);
|
|
25886
25948
|
const matched = all.filter((r) => {
|
|
25887
25949
|
if (r.description === INSTALLER_GRANT_DESCRIPTION) return true;
|
|
25888
25950
|
return !r.principalName && r.principalType === "ServicePrincipal" && r.roleDefinitionName === "Owner" && r.scope === subScope;
|
|
25889
25951
|
});
|
|
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
|
-
}
|
|
25952
|
+
if (opts.execute) await deleteAssignmentsById(matched.map((r) => r.id));
|
|
25901
25953
|
return matched.map((r) => ({ id: r.id, description: r.description, createdOn: r.createdOn }));
|
|
25902
25954
|
}
|
|
25903
25955
|
async function reapInstaller(opts) {
|
|
@@ -25954,22 +26006,30 @@ var BootstrapReapCommand = class extends M8tCommand {
|
|
|
25954
26006
|
if (!sub) {
|
|
25955
26007
|
throw new LocalCliError({ code: "BOOTSTRAP_SWEEP_NO_SUB", message: "No active subscription.", hint: "Run 'az account set --subscription <id>'." });
|
|
25956
26008
|
}
|
|
25957
|
-
const
|
|
25958
|
-
|
|
25959
|
-
|
|
26009
|
+
const execute = this.yes === true;
|
|
26010
|
+
const owner = await sweepOrphanOwnerAssignments({ subscriptionId: sub, execute });
|
|
26011
|
+
const gateway = await sweepOrphanGatewaySubRoles({ subscriptionId: sub, execute });
|
|
26012
|
+
const total = owner.length + gateway.length;
|
|
26013
|
+
if (total === 0) {
|
|
26014
|
+
this.context.stdout.write(`${colors.success("\u2713")} No orphaned installer Owner@sub or gateway sub-scope assignments found.
|
|
25960
26015
|
`);
|
|
25961
26016
|
return 0;
|
|
25962
26017
|
}
|
|
25963
|
-
for (const f of
|
|
25964
|
-
this.context.stdout.write(` ${f.id}${f.createdOn ? colors.dim(` (created ${f.createdOn})`) : ""}${f.description ? colors.dim(" [tagged]") : ""}
|
|
26018
|
+
for (const f of owner) {
|
|
26019
|
+
this.context.stdout.write(` ${colors.dim("Owner@sub")} ${f.id}${f.createdOn ? colors.dim(` (created ${f.createdOn})`) : ""}${f.description ? colors.dim(" [tagged]") : ""}
|
|
26020
|
+
`);
|
|
26021
|
+
}
|
|
26022
|
+
for (const f of gateway) {
|
|
26023
|
+
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
26024
|
`);
|
|
25966
26025
|
}
|
|
25967
|
-
|
|
25968
|
-
|
|
26026
|
+
const breakdown = `${String(owner.length)} Owner@sub, ${String(gateway.length)} gateway sub-scope`;
|
|
26027
|
+
if (execute) {
|
|
26028
|
+
this.context.stdout.write(`${colors.success("\u2713")} Removed ${String(total)} orphaned assignment(s) (${breakdown}).
|
|
25969
26029
|
`);
|
|
25970
26030
|
} else {
|
|
25971
26031
|
this.context.stdout.write(`
|
|
25972
|
-
Found ${String(
|
|
26032
|
+
Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors.hint("Re-run with --sweep-orphans --yes to delete them.")}
|
|
25973
26033
|
`);
|
|
25974
26034
|
}
|
|
25975
26035
|
return 0;
|
|
@@ -25981,10 +26041,12 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
|
|
|
25981
26041
|
if (!sub) {
|
|
25982
26042
|
throw new LocalCliError({ code: "BOOTSTRAP_SWEEP_NO_SUB", message: "No active subscription.", hint: "Run 'az account set --subscription <id>'." });
|
|
25983
26043
|
}
|
|
25984
|
-
const
|
|
26044
|
+
const owner = await sweepOrphanOwnerAssignments({ subscriptionId: sub, execute: true });
|
|
26045
|
+
const gateway = await sweepOrphanGatewaySubRoles({ subscriptionId: sub, execute: true });
|
|
26046
|
+
const total = owner.length + gateway.length;
|
|
25985
26047
|
this.context.stdout.write(
|
|
25986
|
-
|
|
25987
|
-
` : `${colors.success("\u2713")} No bootstrap state found; swept ${String(
|
|
26048
|
+
total === 0 ? `${colors.success("\u2713")} No bootstrap state and no orphaned installer Owner@sub or gateway sub-scope assignments. Nothing to reap.
|
|
26049
|
+
` : `${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
26050
|
`
|
|
25989
26051
|
);
|
|
25990
26052
|
return 0;
|
|
@@ -25992,7 +26054,7 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
|
|
|
25992
26054
|
throw new LocalCliError({
|
|
25993
26055
|
code: "BOOTSTRAP_NO_STATE",
|
|
25994
26056
|
message: "No bootstrap state to reap.",
|
|
25995
|
-
hint: "Nothing to do. If a launch failed
|
|
26057
|
+
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
26058
|
});
|
|
25997
26059
|
}
|
|
25998
26060
|
if (this.force !== true) {
|