diff --git a/src/cli.ts b/src/cli.ts index e106946..ee96cc5 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,6 +4,15 @@ import { parseArgs } from "node:util"; import { runCommand } from "./cli/commands.ts"; +import { usageGetToken } from "./cli/commands/get-token.ts"; +import { usageListAppGrants } from "./cli/commands/list-app-grants.ts"; +import { usageListAppPermissions } from "./cli/commands/list-app-permissions.ts"; +import { usageListApps } from "./cli/commands/list-apps.ts"; +import { usageListResourcePermissions } from "./cli/commands/list-resource-permissions.ts"; +import { usageLogin } from "./cli/commands/login.ts"; +import { usageLogout } from "./cli/commands/logout.ts"; +import { usageRest } from "./cli/commands/rest.ts"; +import { usageTable } from "./cli/commands/table.ts"; import { normalizeOutputFormat, omitPermissionGuidColumns, @@ -57,86 +66,6 @@ Use: sk-az-tools --help or: sk-az-tools --help`; } -function usageListApps(): string { - return `Usage: sk-az-tools list-apps [--display-name|-n ] [--app-id|-i ] [--filter|-f ] [global options] - -Options: - -n, --display-name Get app by name - -i, --app-id Get app by id - -f, --filter Filter by app display name glob`; -} - -function usageLogin(): string { - return `Usage: sk-az-tools login [--resources ] [--use-device-code] [--no-browser] [--browser ] [--browser-profile ] [global options] - -Options: - --resources Comma-separated resources: graph,devops,arm (default: all) - --use-device-code Use device code flow instead of interactive flow - --no-browser Do not launch browser; print interactive URL to stderr - --browser Browser keyword: brave|browser|browserPrivate|chrome|edge|firefox - --browser-profile Chromium profile name (e.g. Default, "Profile 1")`; -} - -function usageLogout(): string { - return `Usage: sk-az-tools logout [--all] [global options] - -Options: - --all Clear login state and remove all cached accounts`; -} - -function usageGetToken(): string { - return `Usage: sk-az-tools get-token --type|-t [global options] - -Options: - -t, --type Token type: azurerm|devops`; -} - -function usageRest(): string { - return `Usage: sk-az-tools rest [--method ] --url [--header ] [global options] - -Options: - --method HTTP method (default: GET; examples: GET, POST, PATCH, DELETE) - --url Full URL to call - --header 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 usageListAppPermissions(): string { - return `Usage: sk-az-tools list-app-permissions --app-id|-i [--resolve|-r] [--short|-s] [--filter|-f ] [global options] - -Options: - -i, --app-id Application (client) ID (required) - -r, --resolve Resolve permission GUIDs to human-readable values - -s, --short Makes output more compact - -f, --filter Filter by permission name glob`; -} - -function usageListAppGrants(): string { - return `Usage: sk-az-tools list-app-grants --app-id|-i [global options] - -Options: - -i, --app-id Application (client) ID (required)`; -} - -function usageListResourcePermissions(): string { - return `Usage: sk-az-tools list-resource-permissions [--app-id|-i | --display-name|-n ] [--filter|-f ] [global options] - -Options: - -i, --app-id Resource app ID - -n, --display-name Resource app display name - -f, --filter Filter by permission name glob`; -} - -function usageTable(): string { - return `Usage: sk-az-tools table [--header|-H ] [global options] - -Options: - -H, --header Header mode/spec: auto|a (default), original|o, OR "col1, col2" OR "key1: Label 1, key2: Label 2"`; -} - function usageCommand(command: string): string { switch (command) { case "login": diff --git a/src/cli/commands.ts b/src/cli/commands.ts index b44b92e..c6e9b3f 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -1,303 +1,16 @@ // SPDX-License-Identifier: MIT -import { minimatch } from "minimatch"; +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 { runTableCommand } from "./commands/table.ts"; -import { loadPublicConfig } from "../index.ts"; -import { getGraphClient } from "../graph/auth.ts"; -import { acquireResourceTokenFromLogin, login, logout } from "../azure/index.ts"; -import { getDevOpsApiToken } from "../devops/index.ts"; -import { - listApps, - listAppPermissions, - listAppPermissionsResolved, - listAppGrants, - listResourcePermissions, -} from "../graph/app.ts"; -import { readJsonFromStdin } from "./utils.ts"; - -type CommandValues = { - [key: string]: string | boolean | undefined; - type?: string; - method?: string; - url?: string; - header?: string; - resources?: string; - "use-device-code"?: boolean; - "no-browser"?: boolean; - browser?: string; - "browser-profile"?: string; - all?: boolean; - "display-name"?: string; - "app-id"?: string; - filter?: string; - resolve?: boolean; -}; - -type PermissionRow = { - permissionValue?: string | null; - permissionDisplayName?: string | null; -}; - -type DisplayNameRow = { - displayName?: string | null; -}; - -function parseHeaderLine(header?: string): { name: string; value: string } | null { - if (!header || header.trim() === "") { - return null; - } - - const separatorIndex = header.indexOf(":"); - if (separatorIndex < 1) { - throw new Error("--header must be in the format 'Name: Value'"); - } - - const name = header.slice(0, separatorIndex).trim(); - const value = header.slice(separatorIndex + 1).trim(); - if (!name || !value) { - throw new Error("--header must be in the format 'Name: Value'"); - } - - return { name, value }; -} - -function hasAuthorizationHeader(headers: Headers): boolean { - for (const headerName of headers.keys()) { - if (headerName.toLowerCase() === "authorization") { - return true; - } - } - - return false; -} - -async function getAutoAuthorizationHeader(url: URL): Promise { - const host = url.hostname.toLowerCase(); - if (host !== "management.azure.com" && host !== "dev.azure.com") { - return null; - } - - const config = await loadPublicConfig(); - if (!config.tenantId) { - throw new Error("tenantId is required"); - } - if (!config.clientId) { - throw new Error("clientId is required"); - } - - if (host === "management.azure.com") { - const result = await acquireResourceTokenFromLogin({ - tenantId: config.tenantId, - clientId: config.clientId, - resource: "arm", - }); - const accessToken = result?.accessToken; - if (!accessToken) { - throw new Error("Failed to obtain AzureRM token"); - } - return `Bearer ${accessToken}`; - } - - const accessToken = await getDevOpsApiToken(config.tenantId, config.clientId); - return `Bearer ${accessToken}`; -} - -function filterByPermissionName(rows: T[], pattern: string): T[] { - return rows.filter((item) => - minimatch(item.permissionValue ?? "", pattern, { nocase: true }) - || minimatch(item.permissionDisplayName ?? "", pattern, { nocase: true }), - ); -} - -function filterByDisplayName(rows: T[], pattern: string): T[] { - return rows.filter((item) => - minimatch(item.displayName ?? "", pattern, { nocase: true }), - ); -} - -async function getGraphClientFromPublicConfig(): Promise<{ client: any }> { - const config = await loadPublicConfig(); - return getGraphClient({ - tenantId: config.tenantId, - clientId: config.clientId, - }); -} - -async function runTableCommand(): Promise { - return readJsonFromStdin(); -} - -async function runLoginCommand(values: CommandValues): Promise { - const config = await loadPublicConfig(); - return login({ - tenantId: config.tenantId, - clientId: config.clientId, - resourcesCsv: values.resources, - useDeviceCode: Boolean(values["use-device-code"]), - noBrowser: Boolean(values["no-browser"]), - browser: values.browser, - browserProfile: values["browser-profile"], - }); -} - -async function runLogoutCommand(values: CommandValues): Promise { - const config = await loadPublicConfig(); - return logout({ - tenantId: config.tenantId, - clientId: config.clientId, - clearAll: Boolean(values.all), - }); -} - -async function runListAppsCommand(values: CommandValues): Promise { - const { client } = await getGraphClientFromPublicConfig(); - let result = await listApps(client, { - displayName: values["display-name"], - appId: values["app-id"], - }); - 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 (values.filter) { - result = filterByDisplayName(result, values.filter); - } - return result; -} - -async function runListAppPermissionsCommand(values: CommandValues): Promise { - if (!values["app-id"]) { - throw new Error("--app-id is required for list-app-permissions"); - } - - const { client } = await getGraphClientFromPublicConfig(); - let result = values.resolve || values.filter - ? await listAppPermissionsResolved(client, values["app-id"]) - : await listAppPermissions(client, values["app-id"]); - if (values.filter) { - result = filterByPermissionName(result, values.filter); - } - return result; -} - -async function runListAppGrantsCommand(values: CommandValues): Promise { - if (!values["app-id"]) { - throw new Error("--app-id is required for list-app-grants"); - } - - const { client } = await getGraphClientFromPublicConfig(); - return listAppGrants(client, values["app-id"]); -} - -async function runListResourcePermissionsCommand(values: CommandValues): Promise { - if (!values["app-id"] && !values["display-name"]) { - throw new Error("--app-id or --display-name is required for list-resource-permissions"); - } - if (values["app-id"] && values["display-name"]) { - throw new Error("Use either --app-id or --display-name for list-resource-permissions, not both"); - } - - const { client } = await getGraphClientFromPublicConfig(); - let result = await listResourcePermissions(client, { - appId: values["app-id"], - displayName: values["display-name"], - }); - if (values.filter) { - result = filterByPermissionName(result, values.filter); - } - return result; -} - -async function runGetTokenCommand(values: CommandValues): Promise { - const tokenType = (values.type ?? "").toString().trim().toLowerCase(); - if (!tokenType) { - throw new Error("--type is required for get-token (allowed: azurerm, devops)"); - } - - const config = await loadPublicConfig(); - if (!config.tenantId) { - throw new Error("tenantId is required"); - } - if (!config.clientId) { - throw new Error("clientId is required"); - } - - if (tokenType === "azurerm") { - const result = await acquireResourceTokenFromLogin({ - tenantId: config.tenantId, - clientId: config.clientId, - resource: "arm", - }); - - const accessToken = result?.accessToken; - if (!accessToken) { - throw new Error("Failed to obtain AzureRM token"); - } - - return { - tokenType, - accessToken, - }; - } - - if (tokenType === "devops") { - const accessToken = await getDevOpsApiToken(config.tenantId, config.clientId); - return { - tokenType, - accessToken, - }; - } - - throw new Error(`Invalid --type '${values.type}'. Allowed: azurerm, devops`); -} - -async function runRestCommand(values: CommandValues): Promise { - const method = (values.method ?? "GET").toString().trim().toUpperCase() || "GET"; - const urlValue = (values.url ?? "").toString().trim(); - - if (!urlValue) { - throw new Error("--url is required for rest"); - } - - let targetUrl: URL; - try { - targetUrl = new URL(urlValue); - } catch { - throw new Error(`Invalid --url '${urlValue}'`); - } - - const headers = new Headers(); - const customHeader = parseHeaderLine(values.header); - if (customHeader) { - headers.set(customHeader.name, customHeader.value); - } - - if (!hasAuthorizationHeader(headers)) { - const authorization = await getAutoAuthorizationHeader(targetUrl); - if (authorization) { - headers.set("Authorization", authorization); - } - } - - const response = await fetch(targetUrl, { - method, - headers, - }); - - const contentType = response.headers.get("content-type") ?? ""; - let body: unknown; - if (contentType.toLowerCase().includes("application/json")) { - body = await response.json(); - } else { - body = await response.text(); - } - - return { - ok: response.ok, - status: response.status, - statusText: response.statusText, - body, - }; -} +import type { CommandValues } from "./commands/types.ts"; export async function runCommand(command: string, values: CommandValues): Promise { switch (command) { diff --git a/src/cli/commands/get-token.ts b/src/cli/commands/get-token.ts new file mode 100644 index 0000000..817a8f8 --- /dev/null +++ b/src/cli/commands/get-token.ts @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT + +import { acquireResourceTokenFromLogin } from "../../azure/index.ts"; +import { getDevOpsApiToken } from "../../devops/index.ts"; +import { loadPublicConfig } from "../../index.ts"; + +import type { CommandValues } from "./types.ts"; + +export function usageGetToken(): string { + return `Usage: sk-az-tools get-token --type|-t [global options] + +Options: + -t, --type Token type: azurerm|devops`; +} + +export async function runGetTokenCommand(values: CommandValues): Promise { + const tokenType = (values.type ?? "").toString().trim().toLowerCase(); + if (!tokenType) { + throw new Error("--type is required for get-token (allowed: azurerm, devops)"); + } + + const config = await loadPublicConfig(); + if (!config.tenantId) { + throw new Error("tenantId is required"); + } + if (!config.clientId) { + throw new Error("clientId is required"); + } + + if (tokenType === "azurerm") { + const result = await acquireResourceTokenFromLogin({ + tenantId: config.tenantId, + clientId: config.clientId, + resource: "arm", + }); + + const accessToken = result?.accessToken; + if (!accessToken) { + throw new Error("Failed to obtain AzureRM token"); + } + + return { + tokenType, + accessToken, + }; + } + + if (tokenType === "devops") { + const accessToken = await getDevOpsApiToken(config.tenantId, config.clientId); + return { + tokenType, + accessToken, + }; + } + + throw new Error(`Invalid --type '${values.type}'. Allowed: azurerm, devops`); +} diff --git a/src/cli/commands/list-app-grants.ts b/src/cli/commands/list-app-grants.ts new file mode 100644 index 0000000..4cdda21 --- /dev/null +++ b/src/cli/commands/list-app-grants.ts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +import { listAppGrants } from "../../graph/app.ts"; + +import { getGraphClientFromPublicConfig } from "./shared.ts"; +import type { CommandValues } from "./types.ts"; + +export function usageListAppGrants(): string { + return `Usage: sk-az-tools list-app-grants --app-id|-i [global options] + +Options: + -i, --app-id Application (client) ID (required)`; +} + +export async function runListAppGrantsCommand(values: CommandValues): Promise { + if (!values["app-id"]) { + throw new Error("--app-id is required for list-app-grants"); + } + + const { client } = await getGraphClientFromPublicConfig(); + return listAppGrants(client, values["app-id"]); +} diff --git a/src/cli/commands/list-app-permissions.ts b/src/cli/commands/list-app-permissions.ts new file mode 100644 index 0000000..1d488dc --- /dev/null +++ b/src/cli/commands/list-app-permissions.ts @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +import { listAppPermissions, listAppPermissionsResolved } from "../../graph/app.ts"; + +import { filterByPermissionName, getGraphClientFromPublicConfig } from "./shared.ts"; +import type { CommandValues } from "./types.ts"; + +export function usageListAppPermissions(): string { + return `Usage: sk-az-tools list-app-permissions --app-id|-i [--resolve|-r] [--short|-s] [--filter|-f ] [global options] + +Options: + -i, --app-id Application (client) ID (required) + -r, --resolve Resolve permission GUIDs to human-readable values + -s, --short Makes output more compact + -f, --filter Filter by permission name glob`; +} + +export async function runListAppPermissionsCommand(values: CommandValues): Promise { + if (!values["app-id"]) { + throw new Error("--app-id is required for list-app-permissions"); + } + + const { client } = await getGraphClientFromPublicConfig(); + let result = values.resolve || values.filter + ? await listAppPermissionsResolved(client, values["app-id"]) + : await listAppPermissions(client, values["app-id"]); + if (values.filter) { + result = filterByPermissionName(result, values.filter); + } + return result; +} diff --git a/src/cli/commands/list-apps.ts b/src/cli/commands/list-apps.ts new file mode 100644 index 0000000..a710803 --- /dev/null +++ b/src/cli/commands/list-apps.ts @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +import { listApps } from "../../graph/app.ts"; + +import { filterByDisplayName, getGraphClientFromPublicConfig } from "./shared.ts"; +import type { CommandValues } from "./types.ts"; + +export function usageListApps(): string { + return `Usage: sk-az-tools list-apps [--display-name|-n ] [--app-id|-i ] [--filter|-f ] [global options] + +Options: + -n, --display-name Get app by name + -i, --app-id Get app by id + -f, --filter Filter by app display name glob`; +} + +export async function runListAppsCommand(values: CommandValues): Promise { + const { client } = await getGraphClientFromPublicConfig(); + let result = await listApps(client, { + displayName: values["display-name"], + appId: values["app-id"], + }); + 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 (values.filter) { + result = filterByDisplayName(result, values.filter); + } + return result; +} diff --git a/src/cli/commands/list-resource-permissions.ts b/src/cli/commands/list-resource-permissions.ts new file mode 100644 index 0000000..182de91 --- /dev/null +++ b/src/cli/commands/list-resource-permissions.ts @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +import { listResourcePermissions } from "../../graph/app.ts"; + +import { filterByPermissionName, getGraphClientFromPublicConfig } from "./shared.ts"; +import type { CommandValues } from "./types.ts"; + +export function usageListResourcePermissions(): string { + return `Usage: sk-az-tools list-resource-permissions [--app-id|-i | --display-name|-n ] [--filter|-f ] [global options] + +Options: + -i, --app-id Resource app ID + -n, --display-name Resource app display name + -f, --filter Filter by permission name glob`; +} + +export async function runListResourcePermissionsCommand(values: CommandValues): Promise { + if (!values["app-id"] && !values["display-name"]) { + throw new Error("--app-id or --display-name is required for list-resource-permissions"); + } + if (values["app-id"] && values["display-name"]) { + throw new Error("Use either --app-id or --display-name for list-resource-permissions, not both"); + } + + const { client } = await getGraphClientFromPublicConfig(); + let result = await listResourcePermissions(client, { + appId: values["app-id"], + displayName: values["display-name"], + }); + if (values.filter) { + result = filterByPermissionName(result, values.filter); + } + return result; +} diff --git a/src/cli/commands/login.ts b/src/cli/commands/login.ts new file mode 100644 index 0000000..c4635a7 --- /dev/null +++ b/src/cli/commands/login.ts @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +import { login } from "../../azure/index.ts"; +import { loadPublicConfig } from "../../index.ts"; + +import type { CommandValues } from "./types.ts"; + +export function usageLogin(): string { + return `Usage: sk-az-tools login [--resources ] [--use-device-code] [--no-browser] [--browser ] [--browser-profile ] [global options] + +Options: + --resources Comma-separated resources: graph,devops,arm (default: all) + --use-device-code Use device code flow instead of interactive flow + --no-browser Do not launch browser; print interactive URL to stderr + --browser Browser keyword: brave|browser|browserPrivate|chrome|edge|firefox + --browser-profile Chromium profile name (e.g. Default, "Profile 1")`; +} + +export async function runLoginCommand(values: CommandValues): Promise { + const config = await loadPublicConfig(); + return login({ + tenantId: config.tenantId, + clientId: config.clientId, + resourcesCsv: values.resources, + useDeviceCode: Boolean(values["use-device-code"]), + noBrowser: Boolean(values["no-browser"]), + browser: values.browser, + browserProfile: values["browser-profile"], + }); +} diff --git a/src/cli/commands/logout.ts b/src/cli/commands/logout.ts new file mode 100644 index 0000000..b980a76 --- /dev/null +++ b/src/cli/commands/logout.ts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +import { logout } from "../../azure/index.ts"; +import { loadPublicConfig } from "../../index.ts"; + +import type { CommandValues } from "./types.ts"; + +export function usageLogout(): string { + return `Usage: sk-az-tools logout [--all] [global options] + +Options: + --all Clear login state and remove all cached accounts`; +} + +export async function runLogoutCommand(values: CommandValues): Promise { + const config = await loadPublicConfig(); + return logout({ + tenantId: config.tenantId, + clientId: config.clientId, + clearAll: Boolean(values.all), + }); +} diff --git a/src/cli/commands/rest.ts b/src/cli/commands/rest.ts new file mode 100644 index 0000000..7bb4f90 --- /dev/null +++ b/src/cli/commands/rest.ts @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT + +import { acquireResourceTokenFromLogin } from "../../azure/index.ts"; +import { getDevOpsApiToken } from "../../devops/index.ts"; +import { loadPublicConfig } from "../../index.ts"; + +import type { CommandValues } from "./types.ts"; + +export function usageRest(): string { + return `Usage: sk-az-tools rest [--method ] --url [--header ] [global options] + +Options: + --method HTTP method (default: GET; examples: GET, POST, PATCH, DELETE) + --url Full URL to call + --header 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(header?: string): { name: string; value: string } | null { + if (!header || header.trim() === "") { + return null; + } + + const separatorIndex = header.indexOf(":"); + if (separatorIndex < 1) { + throw new Error("--header must be in the format 'Name: Value'"); + } + + const name = header.slice(0, separatorIndex).trim(); + const value = header.slice(separatorIndex + 1).trim(); + if (!name || !value) { + throw new Error("--header must be in the format 'Name: Value'"); + } + + return { name, value }; +} + +function hasAuthorizationHeader(headers: Headers): boolean { + for (const headerName of headers.keys()) { + if (headerName.toLowerCase() === "authorization") { + return true; + } + } + + return false; +} + +async function getAutoAuthorizationHeader(url: URL): Promise { + const host = url.hostname.toLowerCase(); + if (host !== "management.azure.com" && host !== "dev.azure.com") { + return null; + } + + const config = await loadPublicConfig(); + if (!config.tenantId) { + throw new Error("tenantId is required"); + } + if (!config.clientId) { + throw new Error("clientId is required"); + } + + if (host === "management.azure.com") { + const result = await acquireResourceTokenFromLogin({ + tenantId: config.tenantId, + clientId: config.clientId, + resource: "arm", + }); + const accessToken = result?.accessToken; + if (!accessToken) { + throw new Error("Failed to obtain AzureRM token"); + } + return `Bearer ${accessToken}`; + } + + const accessToken = await getDevOpsApiToken(config.tenantId, config.clientId); + return `Bearer ${accessToken}`; +} + +export async function runRestCommand(values: CommandValues): Promise { + const method = (values.method ?? "GET").toString().trim().toUpperCase() || "GET"; + const urlValue = (values.url ?? "").toString().trim(); + + if (!urlValue) { + throw new Error("--url is required for rest"); + } + + let targetUrl: URL; + try { + targetUrl = new URL(urlValue); + } catch { + throw new Error(`Invalid --url '${urlValue}'`); + } + + const headers = new Headers(); + const customHeader = parseHeaderLine(values.header); + if (customHeader) { + headers.set(customHeader.name, customHeader.value); + } + + if (!hasAuthorizationHeader(headers)) { + const authorization = await getAutoAuthorizationHeader(targetUrl); + if (authorization) { + headers.set("Authorization", authorization); + } + } + + const response = await fetch(targetUrl, { + method, + headers, + }); + + const contentType = response.headers.get("content-type") ?? ""; + let body: unknown; + if (contentType.toLowerCase().includes("application/json")) { + body = await response.json(); + } else { + body = await response.text(); + } + + return { + ok: response.ok, + status: response.status, + statusText: response.statusText, + body, + }; +} diff --git a/src/cli/commands/shared.ts b/src/cli/commands/shared.ts new file mode 100644 index 0000000..8573a55 --- /dev/null +++ b/src/cli/commands/shared.ts @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +import { minimatch } from "minimatch"; + +import { loadPublicConfig } from "../../index.ts"; +import { getGraphClient } from "../../graph/auth.ts"; + +type PermissionRow = { + permissionValue?: string | null; + permissionDisplayName?: string | null; +}; + +type DisplayNameRow = { + displayName?: string | null; +}; + +export function filterByPermissionName(rows: T[], pattern: string): T[] { + return rows.filter((item) => + minimatch(item.permissionValue ?? "", pattern, { nocase: true }) + || minimatch(item.permissionDisplayName ?? "", pattern, { nocase: true }), + ); +} + +export function filterByDisplayName(rows: T[], pattern: string): T[] { + return rows.filter((item) => + minimatch(item.displayName ?? "", pattern, { nocase: true }), + ); +} + +export async function getGraphClientFromPublicConfig(): Promise<{ client: any }> { + const config = await loadPublicConfig(); + return getGraphClient({ + tenantId: config.tenantId, + clientId: config.clientId, + }); +} diff --git a/src/cli/commands/table.ts b/src/cli/commands/table.ts new file mode 100644 index 0000000..3a7340a --- /dev/null +++ b/src/cli/commands/table.ts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +import { readJsonFromStdin } from "../utils.ts"; + +export function usageTable(): string { + return `Usage: sk-az-tools table [--header|-H ] [global options] + +Options: + -H, --header Header mode/spec: auto|a (default), original|o, OR "col1, col2" OR "key1: Label 1, key2: Label 2"`; +} + +export async function runTableCommand(): Promise { + return readJsonFromStdin(); +} diff --git a/src/cli/commands/types.ts b/src/cli/commands/types.ts new file mode 100644 index 0000000..6dabd1e --- /dev/null +++ b/src/cli/commands/types.ts @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +export type CommandValues = { + [key: string]: string | boolean | undefined; + type?: string; + method?: string; + url?: string; + header?: string; + resources?: string; + "use-device-code"?: boolean; + "no-browser"?: boolean; + browser?: string; + "browser-profile"?: string; + all?: boolean; + "display-name"?: string; + "app-id"?: string; + filter?: string; + resolve?: boolean; +};