Migrated from parseArgs from node:util to commander.js.
All checks were successful
build / build (push) Successful in 14s
All checks were successful
build / build (push) Successful in 14s
This commit is contained in:
22
package-lock.json
generated
22
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@slawek/sk-az-tools",
|
"name": "@slawek/sk-az-tools",
|
||||||
"version": "0.5.1",
|
"version": "0.6.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@slawek/sk-az-tools",
|
"name": "@slawek/sk-az-tools",
|
||||||
"version": "0.5.1",
|
"version": "0.6.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/identity": "^4.13.0",
|
"@azure/identity": "^4.13.0",
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
"@slawek/sk-tools": ">=0.2.0",
|
"@slawek/sk-tools": ">=0.2.0",
|
||||||
"azure-devops-node-api": "^15.1.2",
|
"azure-devops-node-api": "^15.1.2",
|
||||||
|
"commander": "^14.0.3",
|
||||||
"minimatch": "^10.1.2",
|
"minimatch": "^10.1.2",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
@@ -502,12 +503,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "7.2.0",
|
"version": "14.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
|
||||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/d3-dsv": {
|
"node_modules/d3-dsv": {
|
||||||
@@ -535,6 +536,15 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-dsv/node_modules/commander": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@slawek/sk-az-tools",
|
"name": "@slawek/sk-az-tools",
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf dist && tsc && chmod +x dist/cli.js",
|
"build": "rm -rf dist && tsc && chmod +x dist/cli.js",
|
||||||
|
"build:watch": "tsc --watch",
|
||||||
"create-pca": "node dist/create-pca.js",
|
"create-pca": "node dist/create-pca.js",
|
||||||
"bump-patch": "node scripts/bump-patch.mjs",
|
"bump-patch": "node scripts/bump-patch.mjs",
|
||||||
"make-deps": "node scripts/make-mermaid-func-deps.mjs",
|
"make-deps": "node scripts/make-mermaid-func-deps.mjs",
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
"@slawek/sk-tools": ">=0.2.0",
|
"@slawek/sk-tools": ">=0.2.0",
|
||||||
"azure-devops-node-api": "^15.1.2",
|
"azure-devops-node-api": "^15.1.2",
|
||||||
|
"commander": "^14.0.3",
|
||||||
"minimatch": "^10.1.2",
|
"minimatch": "^10.1.2",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
|
|||||||
239
src/cli.ts
239
src/cli.ts
@@ -1,144 +1,133 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { parseArgs } from "node:util";
|
import { Command, Option } from "commander";
|
||||||
|
import { renderCliOutput} from "@slawek/sk-tools";
|
||||||
|
|
||||||
import { runCommand } from "./cli/commands.ts";
|
// Commands
|
||||||
import { usageGetToken } from "./cli/commands/get-token.ts";
|
import { runGetTokenCommand } from "./cli/commands/get-token.ts";
|
||||||
import { usageListAppGrants } from "./cli/commands/list-app-grants.ts";
|
import { runListAppGrantsCommand } from "./cli/commands/list-app-grants.ts";
|
||||||
import { usageListAppPermissions } from "./cli/commands/list-app-permissions.ts";
|
import { runListAppPermissionsCommand } from "./cli/commands/list-app-permissions.ts";
|
||||||
import { usageListApps } from "./cli/commands/list-apps.ts";
|
import { runListAppsCommand } from "./cli/commands/list-apps.ts";
|
||||||
import { usageListResourcePermissions } from "./cli/commands/list-resource-permissions.ts";
|
import { runListResourcePermissionsCommand } from "./cli/commands/list-resource-permissions.ts";
|
||||||
import { usageLogin } from "./cli/commands/login.ts";
|
import { runLoginCommand } from "./cli/commands/login.ts";
|
||||||
import { usageLogout } from "./cli/commands/logout.ts";
|
import { runLogoutCommand } from "./cli/commands/logout.ts";
|
||||||
import { usageRest } from "./cli/commands/rest.ts";
|
import { runRestCommand } from "./cli/commands/rest.ts";
|
||||||
import {
|
|
||||||
renderCliOutput,
|
|
||||||
} from "@slawek/sk-tools";
|
|
||||||
|
|
||||||
type CliValues = {
|
import pkg from "../package.json" with { type: "json" };
|
||||||
help?: boolean;
|
const { version: packageVersion } = pkg;
|
||||||
type?: string;
|
|
||||||
method?: string;
|
|
||||||
url?: string;
|
|
||||||
"display-name"?: string;
|
|
||||||
"app-id"?: string;
|
|
||||||
resources?: string;
|
|
||||||
"use-device-code"?: boolean;
|
|
||||||
"no-browser"?: boolean;
|
|
||||||
browser?: string;
|
|
||||||
"browser-profile"?: string;
|
|
||||||
all?: boolean;
|
|
||||||
resolve?: boolean;
|
|
||||||
short?: boolean;
|
|
||||||
filter?: string;
|
|
||||||
query?: string;
|
|
||||||
columns?: string;
|
|
||||||
header?: string;
|
|
||||||
output?: string;
|
|
||||||
[key: string]: string | boolean | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
function usage(): string {
|
|
||||||
return `Usage: sk-az-tools <command> [options]
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
login Authenticate selected resources
|
|
||||||
logout Sign out and clear login state
|
|
||||||
get-token Get access token (azurerm|devops)
|
|
||||||
rest Call REST API endpoint
|
|
||||||
list-apps List Entra applications
|
|
||||||
list-app-permissions List required permissions for an app
|
|
||||||
list-app-grants List OAuth2 grants for an app
|
|
||||||
list-resource-permissions List available permissions for a resource app
|
|
||||||
|
|
||||||
Global options (all commands):
|
|
||||||
-q, --query <jmespath>
|
|
||||||
-C, --columns <definition> Column tokens: col (raw), col: (auto), col:Label (custom), exact via = prefix
|
|
||||||
-o, --output <format> table|t|alignedtable|at|prettytable|pt|tsv
|
|
||||||
-h, --help
|
|
||||||
|
|
||||||
Use: sk-az-tools --help <command>
|
|
||||||
or: sk-az-tools <command> --help`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function usageCommand(command: string): string {
|
|
||||||
switch (command) {
|
|
||||||
case "login":
|
|
||||||
return usageLogin();
|
|
||||||
case "list-apps":
|
|
||||||
return usageListApps();
|
|
||||||
case "logout":
|
|
||||||
return usageLogout();
|
|
||||||
case "get-token":
|
|
||||||
return usageGetToken();
|
|
||||||
case "rest":
|
|
||||||
return usageRest();
|
|
||||||
case "list-app-permissions":
|
|
||||||
return usageListAppPermissions();
|
|
||||||
case "list-app-grants":
|
|
||||||
return usageListAppGrants();
|
|
||||||
case "list-resource-permissions":
|
|
||||||
return usageListResourcePermissions();
|
|
||||||
default:
|
|
||||||
return `Unknown command: ${command}\n\n${usage()}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const argv = process.argv.slice(2);
|
const skAzTools = new Command();
|
||||||
const command = argv[0];
|
|
||||||
if (!command) {
|
|
||||||
console.log(usage());
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
if (command === "-h" || command === "--help") {
|
|
||||||
const helpCommand = argv[1];
|
|
||||||
console.log(helpCommand ? usageCommand(helpCommand) : usage());
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { values } = parseArgs({
|
skAzTools
|
||||||
args: argv.slice(1),
|
.name("sk-az-tools")
|
||||||
options: {
|
.description("A collection of tools for Azure and Microsoft Entra management")
|
||||||
help: { type: "boolean", short: "h" },
|
.version(packageVersion)
|
||||||
type: { type: "string", short: "t" },
|
.option("-q, --query <jmespath>", "JMESPath query to filter output")
|
||||||
method: { type: "string" },
|
.option("-C, --columns <definition>", "Column tokens: col (raw), col: (auto), col:Label (custom), exact via = prefix")
|
||||||
url: { type: "string" },
|
.addOption(new Option("-o, --output <format>", "Output format: table|t|alignedtable|at|prettytable|pt|tsv")
|
||||||
"display-name": { type: "string", short: "n" },
|
.choices(["table", "t", "alignedtable", "at", "prettytable", "pt", "tsv"])
|
||||||
"app-id": { type: "string", short: "i" },
|
);
|
||||||
resources: { type: "string" },
|
|
||||||
"use-device-code": { type: "boolean" },
|
skAzTools
|
||||||
"no-browser": { type: "boolean" },
|
.command("login")
|
||||||
browser: { type: "string" },
|
.description("Authenticate selected resources")
|
||||||
"browser-profile": { type: "string" },
|
.option("--resources <csv>", "Comma-separated resources: graph,devops,arm")
|
||||||
all: { type: "boolean" },
|
.option("--use-device-code", "Use device code flow")
|
||||||
resolve: { type: "boolean", short: "r" },
|
.option("--no-browser", "Do not launch browser")
|
||||||
short: { type: "boolean", short: "s" },
|
.option("--browser <name>", "Browser keyword: brave|browser|browserPrivate|chrome|edge|firefox")
|
||||||
filter: { type: "string", short: "f" },
|
.option("--browser-profile <name>", "Chromium profile name")
|
||||||
query: { type: "string", short: "q" },
|
.action(async (options) => {
|
||||||
columns: { type: "string", short: "C" },
|
const output = await runLoginCommand(options);
|
||||||
header: { type: "string" },
|
renderCliOutput(output, skAzTools.opts().output, skAzTools.opts().query, skAzTools.opts().columns);
|
||||||
output: { type: "string", short: "o" },
|
|
||||||
},
|
|
||||||
strict: true,
|
|
||||||
allowPositionals: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const typedValues = values as CliValues;
|
skAzTools
|
||||||
|
.command("logout")
|
||||||
|
.description("Sign out and clear login state")
|
||||||
|
.option("--all", "Clear login state and remove all cached accounts")
|
||||||
|
.action(async (options) => {
|
||||||
|
const output = await runLogoutCommand(options);
|
||||||
|
renderCliOutput(output, skAzTools.opts().output, skAzTools.opts().query, skAzTools.opts().columns);
|
||||||
|
});
|
||||||
|
|
||||||
if (typedValues.help) {
|
skAzTools
|
||||||
console.log(usageCommand(command));
|
.command("get-token")
|
||||||
process.exit(0);
|
.description("Get access token (azurerm|devops)")
|
||||||
}
|
.addOption(new Option("-t, --type <value>", "Token type").choices(["azurerm", "devops"]))
|
||||||
|
.action(async (options) => {
|
||||||
|
const output = await runGetTokenCommand(options);
|
||||||
|
renderCliOutput(output, skAzTools.opts().output, skAzTools.opts().query, skAzTools.opts().columns);
|
||||||
|
});
|
||||||
|
|
||||||
const output = await runCommand(command, typedValues);
|
skAzTools
|
||||||
renderCliOutput(output, typedValues.output, typedValues.query, typedValues.columns);
|
.command("rest")
|
||||||
|
.description("Call REST API endpoint")
|
||||||
|
.argument("<url>", "Full URL to call")
|
||||||
|
.addOption(new Option("-X, --method <httpMethod>", "HTTP method")
|
||||||
|
.choices(["GET", "POST", "PUT", "PATCH", "DELETE"]))
|
||||||
|
.option("-H, --header <name: value>", "Extra request header")
|
||||||
|
.addHelpText("after", `
|
||||||
|
Authorization is added automatically for:
|
||||||
|
management.azure.com Uses azurerm token
|
||||||
|
dev.azure.com Uses devops token`)
|
||||||
|
.action(async (url, options) => {
|
||||||
|
const output = await runRestCommand(url, options);
|
||||||
|
renderCliOutput(output, skAzTools.opts().output, skAzTools.opts().query, skAzTools.opts().columns);
|
||||||
|
});
|
||||||
|
|
||||||
|
skAzTools
|
||||||
|
.command("list-apps")
|
||||||
|
.description("List Entra applications")
|
||||||
|
.option("-n, --display-name <name>", "Get app by display name")
|
||||||
|
.option("-i, --app-id <id>", "Get app by id")
|
||||||
|
.option("-f, --filter <pattern>", "Filter display name glob")
|
||||||
|
.action(async (options) => {
|
||||||
|
const output = await runListAppsCommand(options);
|
||||||
|
renderCliOutput(output, skAzTools.opts().output, skAzTools.opts().query, skAzTools.opts().columns);
|
||||||
|
});
|
||||||
|
|
||||||
|
skAzTools
|
||||||
|
.command("list-app-permissions")
|
||||||
|
.description("List required permissions for an app")
|
||||||
|
.option("-i, --app-id <appId>", "Application (client) ID")
|
||||||
|
.option("-r, --resolve", "Resolve permission GUIDs to human-readable values")
|
||||||
|
.option("-s, --short", "Makes output more compact")
|
||||||
|
.option("-f, --filter <glob>", "Filter by permission name glob")
|
||||||
|
.action(async (options) => {
|
||||||
|
const output = await runListAppPermissionsCommand(options);
|
||||||
|
renderCliOutput(output, skAzTools.opts().output, skAzTools.opts().query, skAzTools.opts().columns);
|
||||||
|
});
|
||||||
|
|
||||||
|
skAzTools
|
||||||
|
.command("list-app-grants")
|
||||||
|
.description("List OAuth2 grants for an app")
|
||||||
|
.option("-i, --app-id <appId>", "Application (client) ID")
|
||||||
|
.action(async (options) => {
|
||||||
|
const output = await runListAppGrantsCommand(options);
|
||||||
|
renderCliOutput(output, skAzTools.opts().output, skAzTools.opts().query, skAzTools.opts().columns);
|
||||||
|
});
|
||||||
|
|
||||||
|
skAzTools
|
||||||
|
.command("list-resource-permissions")
|
||||||
|
.description("List available permissions for a resource app")
|
||||||
|
.option("-i, --app-id <appId>", "Resource app ID")
|
||||||
|
.option("-n, --display-name <name>", "Resource app display name")
|
||||||
|
.option("-f, --filter <glob>", "Filter by permission name glob")
|
||||||
|
.action(async (options) => {
|
||||||
|
const output = await runListResourcePermissionsCommand(options);
|
||||||
|
renderCliOutput(output, skAzTools.opts().output, skAzTools.opts().query, skAzTools.opts().columns);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Parse arguments
|
||||||
|
await skAzTools.parseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err: unknown) => {
|
main().catch((err: unknown) => {
|
||||||
const error = err as Error;
|
const error = err as Error;
|
||||||
console.error(`Error: ${error.message}`);
|
console.error(`Error: ${error.message}`);
|
||||||
console.error(usage());
|
//console.error(usage());
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
import { runGetTokenCommand } from "./commands/get-token.ts";
|
|
||||||
import { runListAppGrantsCommand } from "./commands/list-app-grants.ts";
|
|
||||||
import { runListAppPermissionsCommand } from "./commands/list-app-permissions.ts";
|
|
||||||
import { runListAppsCommand } from "./commands/list-apps.ts";
|
|
||||||
import { runListResourcePermissionsCommand } from "./commands/list-resource-permissions.ts";
|
|
||||||
import { runLoginCommand } from "./commands/login.ts";
|
|
||||||
import { runLogoutCommand } from "./commands/logout.ts";
|
|
||||||
import { runRestCommand } from "./commands/rest.ts";
|
|
||||||
|
|
||||||
import type { CommandValues } from "./commands/types.ts";
|
|
||||||
|
|
||||||
export async function runCommand(command: string, values: CommandValues): Promise<unknown> {
|
|
||||||
switch (command) {
|
|
||||||
case "login":
|
|
||||||
return runLoginCommand(values);
|
|
||||||
case "logout":
|
|
||||||
return runLogoutCommand(values);
|
|
||||||
case "list-apps":
|
|
||||||
return runListAppsCommand(values);
|
|
||||||
case "list-app-permissions":
|
|
||||||
return runListAppPermissionsCommand(values);
|
|
||||||
case "list-app-grants":
|
|
||||||
return runListAppGrantsCommand(values);
|
|
||||||
case "list-resource-permissions":
|
|
||||||
return runListResourcePermissionsCommand(values);
|
|
||||||
case "get-token":
|
|
||||||
return runGetTokenCommand(values);
|
|
||||||
case "rest":
|
|
||||||
return runRestCommand(values);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown command: ${command}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,9 @@ import { getAccessToken } from "../../azure/index.ts";
|
|||||||
import { getDevOpsApiToken } from "../../devops/index.ts";
|
import { getDevOpsApiToken } from "../../devops/index.ts";
|
||||||
import { loadAuthConfig } from "../../index.ts";
|
import { loadAuthConfig } from "../../index.ts";
|
||||||
|
|
||||||
import type { CommandValues } from "./types.ts";
|
type GetTokenOptions = {
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function usageGetToken(): string {
|
export function usageGetToken(): string {
|
||||||
return `Usage: sk-az-tools get-token --type|-t <azurerm|devops> [global options]
|
return `Usage: sk-az-tools get-token --type|-t <azurerm|devops> [global options]
|
||||||
@@ -14,9 +16,9 @@ Options:
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function runGetTokenCommand(
|
export async function runGetTokenCommand(
|
||||||
values: CommandValues,
|
options: GetTokenOptions,
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
const tokenType = (values.type ?? "").toString().trim().toLowerCase();
|
const tokenType = (options.type ?? "").toString().trim().toLowerCase();
|
||||||
if (!tokenType) {
|
if (!tokenType) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"--type is required for get-token (allowed: azurerm, devops)",
|
"--type is required for get-token (allowed: azurerm, devops)",
|
||||||
@@ -49,5 +51,5 @@ export async function runGetTokenCommand(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Invalid --type '${values.type}'. Allowed: azurerm, devops`);
|
throw new Error(`Invalid --type '${options.type}'. Allowed: azurerm, devops`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
import { listAppGrants } from "../../graph/app.ts";
|
import { listAppGrants } from "../../graph/app.ts";
|
||||||
import { getGraphClient } from "../../graph/index.ts";
|
import { getGraphClient } from "../../graph/index.ts";
|
||||||
import type { CommandValues } from "./types.ts";
|
|
||||||
|
type ListAppGrantsOptions = {
|
||||||
|
appId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function usageListAppGrants(): string {
|
export function usageListAppGrants(): string {
|
||||||
return `Usage: sk-az-tools list-app-grants --app-id|-i <appId> [global options]
|
return `Usage: sk-az-tools list-app-grants --app-id|-i <appId> [global options]
|
||||||
@@ -11,11 +14,11 @@ Options:
|
|||||||
--app-id, -i <appId> Application (client) ID (required)`;
|
--app-id, -i <appId> Application (client) ID (required)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runListAppGrantsCommand(values: CommandValues): Promise<unknown> {
|
export async function runListAppGrantsCommand(options: ListAppGrantsOptions): Promise<unknown> {
|
||||||
if (!values["app-id"]) {
|
if (!options.appId) {
|
||||||
throw new Error("--app-id is required for list-app-grants");
|
throw new Error("--app-id is required for list-app-grants");
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = await getGraphClient();
|
const client = await getGraphClient();
|
||||||
return listAppGrants(client, values["app-id"]);
|
return listAppGrants(client, options.appId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import { listAppPermissions, listAppPermissionsResolved } from "../../graph/app.
|
|||||||
|
|
||||||
import { filterByPermissionName } from "./shared.ts";
|
import { filterByPermissionName } from "./shared.ts";
|
||||||
import { getGraphClient } from "../../graph/index.ts";
|
import { getGraphClient } from "../../graph/index.ts";
|
||||||
import type { CommandValues } from "./types.ts";
|
|
||||||
|
type ListAppPermissionsOptions = {
|
||||||
|
appId?: string;
|
||||||
|
resolve?: boolean;
|
||||||
|
short?: boolean;
|
||||||
|
filter?: string;
|
||||||
|
};
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||||
@@ -33,20 +39,20 @@ Options:
|
|||||||
--filter, -f <glob> Filter by permission name glob`;
|
--filter, -f <glob> Filter by permission name glob`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runListAppPermissionsCommand(values: CommandValues): Promise<unknown> {
|
export async function runListAppPermissionsCommand(options: ListAppPermissionsOptions): Promise<unknown> {
|
||||||
if (!values["app-id"]) {
|
if (!options.appId) {
|
||||||
throw new Error("--app-id is required for list-app-permissions");
|
throw new Error("--app-id is required for list-app-permissions");
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = await getGraphClient();
|
const client = await getGraphClient();
|
||||||
let result: unknown = values.resolve || values.filter
|
let result: unknown = options.resolve || options.filter
|
||||||
? await listAppPermissionsResolved(client, values["app-id"])
|
? await listAppPermissionsResolved(client, options.appId)
|
||||||
: await listAppPermissions(client, values["app-id"]);
|
: await listAppPermissions(client, options.appId);
|
||||||
if (values.short) {
|
if (options.short) {
|
||||||
result = omitColumns(result, ["resourceAppId", "permissionId"]);
|
result = omitColumns(result, ["resourceAppId", "permissionId"]);
|
||||||
}
|
}
|
||||||
if (values.filter) {
|
if (options.filter) {
|
||||||
result = filterByPermissionName(result as Array<Record<string, unknown>>, values.filter);
|
result = filterByPermissionName(result as Array<Record<string, unknown>>, options.filter);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,23 @@
|
|||||||
import { listApps } from "../../graph/app.ts";
|
import { listApps } from "../../graph/app.ts";
|
||||||
import { filterByDisplayName } from "./shared.ts";
|
import { filterByDisplayName } from "./shared.ts";
|
||||||
import { getGraphClient } from "../../graph/index.ts";
|
import { getGraphClient } from "../../graph/index.ts";
|
||||||
import type { CommandValues } from "./types.ts";
|
|
||||||
|
|
||||||
export function usageListApps(): string {
|
type ListAppsOptions = {
|
||||||
return `Usage: sk-az-tools list-apps [--display-name|-n <name>] [--app-id|-i <appId>] [--filter|-f <glob>] [global options]
|
displayName?: string;
|
||||||
|
appId?: string;
|
||||||
|
filter?: string;
|
||||||
|
};
|
||||||
|
|
||||||
Options:
|
export async function runListAppsCommand(options: ListAppsOptions): Promise<unknown> {
|
||||||
--display-name, -n <name> Get app by name
|
|
||||||
--app-id, -i <appId> Get app by id
|
|
||||||
--filter, -f <glob> Filter by app display name glob`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runListAppsCommand(values: CommandValues): Promise<unknown> {
|
|
||||||
const client = await getGraphClient();
|
const client = await getGraphClient();
|
||||||
|
|
||||||
let result = await listApps(client, values["display-name"], values["app-id"]);
|
let result = await listApps(client, options.displayName, options.appId);
|
||||||
if (values["app-id"] && result.length > 1) {
|
|
||||||
throw new Error(`Expected a single app for --app-id ${values["app-id"]}, but got ${result.length}`);
|
if (options.appId && result.length > 1) {
|
||||||
|
throw new Error(`Expected a single app for --app-id ${options.appId}, but got ${result.length}`);
|
||||||
}
|
}
|
||||||
if (values.filter) {
|
if (options.filter) {
|
||||||
result = filterByDisplayName(result, values.filter);
|
result = filterByDisplayName(result, options.filter);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,12 @@
|
|||||||
import { listResourcePermissions } from "../../graph/app.ts";
|
import { listResourcePermissions } from "../../graph/app.ts";
|
||||||
import { getGraphClient } from "../../graph/index.ts";
|
import { getGraphClient } from "../../graph/index.ts";
|
||||||
import { filterByPermissionName } from "./shared.ts";
|
import { filterByPermissionName } from "./shared.ts";
|
||||||
import type { CommandValues } from "./types.ts";
|
|
||||||
|
type ListResourcePermissionsOptions = {
|
||||||
|
appId?: string;
|
||||||
|
displayName?: string;
|
||||||
|
filter?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function usageListResourcePermissions(): string {
|
export function usageListResourcePermissions(): string {
|
||||||
return `Usage: sk-az-tools list-resource-permissions [--app-id|-i <appId> | --display-name|-n <name>] [--filter|-f <glob>] [global options]
|
return `Usage: sk-az-tools list-resource-permissions [--app-id|-i <appId> | --display-name|-n <name>] [--filter|-f <glob>] [global options]
|
||||||
@@ -14,22 +19,22 @@ Options:
|
|||||||
--filter, -f <glob> Filter by permission name glob`;
|
--filter, -f <glob> Filter by permission name glob`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runListResourcePermissionsCommand(values: CommandValues): Promise<unknown> {
|
export async function runListResourcePermissionsCommand(options: ListResourcePermissionsOptions): Promise<unknown> {
|
||||||
if (!values["app-id"] && !values["display-name"]) {
|
if (!options.appId && !options.displayName) {
|
||||||
throw new Error("--app-id or --display-name is required for list-resource-permissions");
|
throw new Error("--app-id or --display-name is required for list-resource-permissions");
|
||||||
}
|
}
|
||||||
if (values["app-id"] && values["display-name"]) {
|
if (options.appId && options.displayName) {
|
||||||
throw new Error("Use either --app-id or --display-name for list-resource-permissions, not both");
|
throw new Error("Use either --app-id or --display-name for list-resource-permissions, not both");
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = await getGraphClient();
|
const client = await getGraphClient();
|
||||||
let result = await listResourcePermissions(
|
let result = await listResourcePermissions(
|
||||||
client,
|
client,
|
||||||
values["app-id"],
|
options.appId,
|
||||||
values["display-name"],
|
options.displayName,
|
||||||
);
|
);
|
||||||
if (values.filter) {
|
if (options.filter) {
|
||||||
result = filterByPermissionName(result, values.filter);
|
result = filterByPermissionName(result, options.filter);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,13 @@
|
|||||||
import { login } from "../../azure/index.ts";
|
import { login } from "../../azure/index.ts";
|
||||||
import { loadAuthConfig } from "../../index.ts";
|
import { loadAuthConfig } from "../../index.ts";
|
||||||
|
|
||||||
import type { CommandValues } from "./types.ts";
|
type LoginOptions = {
|
||||||
|
resources?: string;
|
||||||
|
useDeviceCode?: boolean;
|
||||||
|
noBrowser?: boolean;
|
||||||
|
browser?: string;
|
||||||
|
browserProfile?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function usageLogin(): string {
|
export function usageLogin(): string {
|
||||||
return `Usage: sk-az-tools login [--resources <csv>] [--use-device-code] [--no-browser] [--browser <name>] [--browser-profile <profile>] [global options]
|
return `Usage: sk-az-tools login [--resources <csv>] [--use-device-code] [--no-browser] [--browser <name>] [--browser-profile <profile>] [global options]
|
||||||
@@ -16,15 +22,15 @@ Options:
|
|||||||
--browser-profile <name> Chromium profile name (e.g. Default, "Profile 1")`;
|
--browser-profile <name> Chromium profile name (e.g. Default, "Profile 1")`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runLoginCommand(values: CommandValues): Promise<unknown> {
|
export async function runLoginCommand(options: LoginOptions): Promise<unknown> {
|
||||||
const config = await loadAuthConfig("public-config");
|
const config = await loadAuthConfig("public-config");
|
||||||
return login(
|
return login(
|
||||||
config.tenantId,
|
config.tenantId,
|
||||||
config.clientId,
|
config.clientId,
|
||||||
values.resources,
|
options.resources,
|
||||||
Boolean(values["use-device-code"]),
|
Boolean(options.useDeviceCode),
|
||||||
Boolean(values["no-browser"]),
|
Boolean(options.noBrowser),
|
||||||
values.browser,
|
options.browser,
|
||||||
values["browser-profile"],
|
options.browserProfile,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
import { logout } from "../../azure/index.ts";
|
import { logout } from "../../azure/index.ts";
|
||||||
import { loadAuthConfig } from "../../index.ts";
|
import { loadAuthConfig } from "../../index.ts";
|
||||||
|
|
||||||
import type { CommandValues } from "./types.ts";
|
type LogoutOptions = {
|
||||||
|
all?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export function usageLogout(): string {
|
export function usageLogout(): string {
|
||||||
return `Usage: sk-az-tools logout [--all] [global options]
|
return `Usage: sk-az-tools logout [--all] [global options]
|
||||||
@@ -12,7 +14,7 @@ Options:
|
|||||||
--all Clear login state and remove all cached accounts`;
|
--all Clear login state and remove all cached accounts`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runLogoutCommand(values: CommandValues): Promise<unknown> {
|
export async function runLogoutCommand(options: LogoutOptions): Promise<unknown> {
|
||||||
const config = await loadAuthConfig("public-config");
|
const config = await loadAuthConfig("public-config");
|
||||||
return logout(config.tenantId, config.clientId, Boolean(values.all));
|
return logout(config.tenantId, config.clientId, Boolean(options.all));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,6 @@ import { getAccessToken } from "../../azure/index.ts";
|
|||||||
import { getDevOpsApiToken } from "../../devops/index.ts";
|
import { getDevOpsApiToken } from "../../devops/index.ts";
|
||||||
import { loadAuthConfig } from "../../index.ts";
|
import { loadAuthConfig } from "../../index.ts";
|
||||||
|
|
||||||
import type { CommandValues } from "./types.ts";
|
|
||||||
|
|
||||||
export function usageRest(): string {
|
|
||||||
return `Usage: sk-az-tools rest [--method <httpMethod>] --url <url> [--header <name: value>] [global options]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--method <httpMethod> HTTP method (default: GET; examples: GET, POST, PATCH, DELETE)
|
|
||||||
--url <url> Full URL to call
|
|
||||||
--header <name: value> Extra request header; example: "Content-Type: application/json"
|
|
||||||
|
|
||||||
Authorization is added automatically for:
|
|
||||||
management.azure.com Uses azurerm token
|
|
||||||
dev.azure.com Uses devops token`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseHeaderLine(
|
function parseHeaderLine(
|
||||||
header?: string,
|
header?: string,
|
||||||
): { name: string; value: string } | null {
|
): { name: string; value: string } | null {
|
||||||
@@ -70,24 +55,30 @@ async function getAutoAuthorizationHeader(url: URL): Promise<string | null> {
|
|||||||
return `Bearer ${accessToken}`;
|
return `Bearer ${accessToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runRestCommand(values: CommandValues): Promise<unknown> {
|
type httpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||||||
const method =
|
|
||||||
(values.method ?? "GET").toString().trim().toUpperCase() || "GET";
|
type restOptions = {
|
||||||
const urlValue = (values.url ?? "").toString().trim();
|
method?: httpMethod;
|
||||||
|
header?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function runRestCommand(url: string, options: restOptions): Promise<unknown> {
|
||||||
|
const method = options.method || "GET";
|
||||||
|
const urlValue = (url ?? "").toString().trim();
|
||||||
|
|
||||||
if (!urlValue) {
|
if (!urlValue) {
|
||||||
throw new Error("--url is required for rest");
|
throw new Error("URL is required for rest");
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetUrl: URL;
|
let targetUrl: URL;
|
||||||
try {
|
try {
|
||||||
targetUrl = new URL(urlValue);
|
targetUrl = new URL(urlValue);
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(`Invalid --url '${urlValue}'`);
|
throw new Error(`Invalid URL '${urlValue}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
const customHeader = parseHeaderLine(values.header);
|
const customHeader = parseHeaderLine(options.header);
|
||||||
if (customHeader) {
|
if (customHeader) {
|
||||||
headers.set(customHeader.name, customHeader.value);
|
headers.set(customHeader.name, customHeader.value);
|
||||||
}
|
}
|
||||||
@@ -105,9 +96,9 @@ export async function runRestCommand(values: CommandValues): Promise<unknown> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const contentType = response.headers.get("content-type") ?? "";
|
const contentType = response.headers.get("content-type") ?? "";
|
||||||
let body: unknown;
|
let body: string;
|
||||||
if (contentType.toLowerCase().includes("application/json")) {
|
if (contentType.toLowerCase().includes("application/json")) {
|
||||||
body = await response.json();
|
body = JSON.stringify(await response.json());
|
||||||
} else {
|
} else {
|
||||||
body = await response.text();
|
body = await response.text();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user