diff --git a/docs/Commands.md b/docs/Commands.md index 8a8e2f0..502632e 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -6,6 +6,22 @@ The `sk-az-tools` package may act as a CLI tool that provides various commands f - Azure Resource Manager - Azure DevOps Services +## Global Options + +These options apply to all commands unless stated otherwise: + +- `--query`, `-q` - Apply JMESPath filter before output rendering. +- `--output`, `-o` - Output format: `table|t|alignedtable|at|prettytable|pt|tsv`. +- `--columns`, `-C` - Column selection for table outputs: + - `col1` - Select column (case-insensitive match), keep raw header label. + - `col1:` - Select column (case-insensitive match), use auto-generated header label. + - `col1: Label 1` - Select column (case-insensitive match), use custom header label. + - Prefix token with `=` for exact column-name match: `=col1`, `=col1:`, `=col1:Label`. + - Tokens are comma-separated and rendered in the specified order. +- `--help`, `-h` - Show command help. + +Note: `rest --header` is a command-specific HTTP header option and is unrelated to `--columns`. + ## Login **Command name:** `login` diff --git a/package-lock.json b/package-lock.json index 84f5aa8..52e8dfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@slawek/sk-az-tools", - "version": "0.4.5", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index 01e3dcc..5bc63d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@slawek/sk-az-tools", - "version": "0.4.5", + "version": "0.5.0", "type": "module", "files": [ "dist", diff --git a/src/cli.ts b/src/cli.ts index 85cb439..4cf99c9 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,10 +13,7 @@ import { usageLogin } from "./cli/commands/login.ts"; import { usageLogout } from "./cli/commands/logout.ts"; import { usageRest } from "./cli/commands/rest.ts"; import { - normalizeOutputFormat, - outputFiltered, - parseHeaderSpec, - renderOutput, + renderCliOutput, } from "@slawek/sk-tools"; type CliValues = { @@ -36,6 +33,7 @@ type CliValues = { short?: boolean; filter?: string; query?: string; + columns?: string; header?: string; output?: string; [key: string]: string | boolean | undefined; @@ -56,6 +54,7 @@ Commands: Global options (all commands): -q, --query + -C, --columns Column tokens: col (raw), col: (auto), col:Label (custom), exact via = prefix -o, --output table|t|alignedtable|at|prettytable|pt|tsv -h, --help @@ -86,16 +85,6 @@ function usageCommand(command: string): string { } } -function omitRecords(record: Record, names: Set): Record { - return Object.fromEntries( - Object.entries(record).filter(([key]) => !names.has(key)), - ); -} - -function isRecord(value: unknown): value is Record { - return value !== null && typeof value === "object" && !Array.isArray(value); -} - async function main(): Promise { const argv = process.argv.slice(2); const command = argv[0]; @@ -128,7 +117,8 @@ async function main(): Promise { short: { type: "boolean", short: "s" }, filter: { type: "string", short: "f" }, query: { type: "string", short: "q" }, - header: { type: "string", short: "H" }, + columns: { type: "string", short: "C" }, + header: { type: "string" }, output: { type: "string", short: "o" }, }, strict: true, @@ -142,19 +132,8 @@ async function main(): Promise { process.exit(0); } - const outputFormat = normalizeOutputFormat(typedValues.output); - const result = await runCommand(command, typedValues); - const filtered = outputFiltered(result, typedValues.query); - let output: unknown = filtered; - if (command === "list-app-permissions" && typedValues.short && Array.isArray(filtered) && filtered.every(isRecord)) { - const names = new Set(["resourceAppId", "permissionId"]); - output = filtered.map((item) => omitRecords(item, names)); - } - const headerSpec = command === "rest" - ? parseHeaderSpec(undefined) - : parseHeaderSpec(typedValues.header); - - renderOutput(outputFormat, headerSpec, output); + const output = await runCommand(command, typedValues); + renderCliOutput(output, typedValues.output, typedValues.query, typedValues.columns); } main().catch((err: unknown) => { diff --git a/src/cli/commands/list-app-permissions.ts b/src/cli/commands/list-app-permissions.ts index a277c02..d83c231 100644 --- a/src/cli/commands/list-app-permissions.ts +++ b/src/cli/commands/list-app-permissions.ts @@ -5,6 +5,23 @@ import { listAppPermissions, listAppPermissionsResolved } from "../../graph/app. import { filterByPermissionName, getGraphClientFromPublicConfig } from "./shared.ts"; import type { CommandValues } from "./types.ts"; +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function omitColumns(input: unknown, names: string[]): unknown { + if (!Array.isArray(input) || !input.every(isRecord)) { + return input; + } + + const namesSet = new Set(names); + return input.map((record) => + Object.fromEntries( + Object.entries(record).filter(([key]) => !namesSet.has(key)), + ) + ); +} + export function usageListAppPermissions(): string { return `Usage: sk-az-tools list-app-permissions --app-id|-i [--resolve|-r] [--short|-s] [--filter|-f ] [global options] @@ -21,11 +38,14 @@ export async function runListAppPermissionsCommand(values: CommandValues): Promi } const { client } = await getGraphClientFromPublicConfig(); - let result = values.resolve || values.filter + let result: unknown = values.resolve || values.filter ? await listAppPermissionsResolved(client, values["app-id"]) : await listAppPermissions(client, values["app-id"]); + if (values.short) { + result = omitColumns(result, ["resourceAppId", "permissionId"]); + } if (values.filter) { - result = filterByPermissionName(result, values.filter); + result = filterByPermissionName(result as Array>, values.filter); } return result; }