Migrated to TypeScript.

This commit is contained in:
2026-03-05 21:29:58 +01:00
parent 5aacde4f67
commit cb41e7dec1
26 changed files with 659 additions and 430 deletions

View File

@@ -2,32 +2,55 @@
import { minimatch } from "minimatch";
import { loadPublicConfig } from "../index.js";
import { getGraphClient } from "../graph/auth.js";
import { login, logout } from "../azure/index.js";
import { loadPublicConfig } from "../index.ts";
import { getGraphClient } from "../graph/auth.ts";
import { login, logout } from "../azure/index.ts";
import {
listApps,
listAppPermissions,
listAppPermissionsResolved,
listAppGrants,
listResourcePermissions,
} from "../graph/app.js";
import { readJsonFromStdin } from "./utils.js";
} from "../graph/app.ts";
import { readJsonFromStdin } from "./utils.ts";
function filterByPermissionName(rows, pattern) {
type CommandValues = {
[key: string]: string | boolean | undefined;
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 filterByPermissionName<T extends PermissionRow>(rows: T[], pattern: string): T[] {
return rows.filter((item) =>
minimatch(item.permissionValue ?? "", pattern, { nocase: true })
|| minimatch(item.permissionDisplayName ?? "", pattern, { nocase: true })
|| minimatch(item.permissionDisplayName ?? "", pattern, { nocase: true }),
);
}
function filterByDisplayName(rows, pattern) {
function filterByDisplayName<T extends DisplayNameRow>(rows: T[], pattern: string): T[] {
return rows.filter((item) =>
minimatch(item.displayName ?? "", pattern, { nocase: true })
minimatch(item.displayName ?? "", pattern, { nocase: true }),
);
}
async function getGraphClientFromPublicConfig() {
async function getGraphClientFromPublicConfig(): Promise<{ client: any }> {
const config = await loadPublicConfig();
return getGraphClient({
tenantId: config.tenantId,
@@ -35,11 +58,11 @@ async function getGraphClientFromPublicConfig() {
});
}
async function runTableCommand() {
async function runTableCommand(): Promise<unknown> {
return readJsonFromStdin();
}
async function runLoginCommand(values) {
async function runLoginCommand(values: CommandValues): Promise<unknown> {
const config = await loadPublicConfig();
return login({
tenantId: config.tenantId,
@@ -52,7 +75,7 @@ async function runLoginCommand(values) {
});
}
async function runLogoutCommand(values) {
async function runLogoutCommand(values: CommandValues): Promise<unknown> {
const config = await loadPublicConfig();
return logout({
tenantId: config.tenantId,
@@ -61,7 +84,7 @@ async function runLogoutCommand(values) {
});
}
async function runListAppsCommand(values) {
async function runListAppsCommand(values: CommandValues): Promise<unknown> {
const { client } = await getGraphClientFromPublicConfig();
let result = await listApps(client, {
displayName: values["display-name"],
@@ -76,7 +99,7 @@ async function runListAppsCommand(values) {
return result;
}
async function runListAppPermissionsCommand(values) {
async function runListAppPermissionsCommand(values: CommandValues): Promise<unknown> {
if (!values["app-id"]) {
throw new Error("--app-id is required for list-app-permissions");
}
@@ -91,7 +114,7 @@ async function runListAppPermissionsCommand(values) {
return result;
}
async function runListAppGrantsCommand(values) {
async function runListAppGrantsCommand(values: CommandValues): Promise<unknown> {
if (!values["app-id"]) {
throw new Error("--app-id is required for list-app-grants");
}
@@ -100,7 +123,7 @@ async function runListAppGrantsCommand(values) {
return listAppGrants(client, values["app-id"]);
}
async function runListResourcePermissionsCommand(values) {
async function runListResourcePermissionsCommand(values: CommandValues): Promise<unknown> {
if (!values["app-id"] && !values["display-name"]) {
throw new Error("--app-id or --display-name is required for list-resource-permissions");
}
@@ -119,7 +142,7 @@ async function runListResourcePermissionsCommand(values) {
return result;
}
export async function runCommand(command, values) {
export async function runCommand(command: string, values: CommandValues): Promise<unknown> {
switch (command) {
case "login":
return runLoginCommand(values);

View File

@@ -2,15 +2,26 @@
import jmespath from "jmespath";
import { toMarkdownTable } from "../markdown.js";
import { toMarkdownTable } from "../markdown.ts";
export function outputFiltered(object, query) {
type HeaderSpec =
| { mode: "auto" }
| { mode: "original" }
| { mode: "list"; labels: string[] }
| { mode: "map"; map: Record<string, string> };
type OutputFormat = "json" | "table" | "alignedtable" | "prettytable" | "tsv";
type Scalar = string | number | boolean | null | undefined;
type ScalarRow = Record<string, Scalar>;
export function outputFiltered(object: unknown, query?: string): unknown {
return query
? jmespath.search(object, query)
: object;
}
export function parseHeaderSpec(headerValue) {
export function parseHeaderSpec(headerValue?: string): HeaderSpec {
if (!headerValue) {
return { mode: "auto" };
}
@@ -30,7 +41,7 @@ export function parseHeaderSpec(headerValue) {
return { mode: "list", labels: parts };
}
const map = {};
const map: Record<string, string> = {};
for (const part of parts) {
const idx = part.indexOf(":");
if (idx < 0) {
@@ -47,7 +58,7 @@ export function parseHeaderSpec(headerValue) {
return { mode: "map", map };
}
export function normalizeOutputFormat(outputValue) {
export function normalizeOutputFormat(outputValue?: string): OutputFormat {
if (outputValue == null) {
return "json";
}
@@ -66,16 +77,20 @@ export function normalizeOutputFormat(outputValue) {
throw new Error("--output must be one of: table|t, alignedtable|at, prettytable|pt, tsv");
}
function getScalarRowsAndHeaders(value) {
let rows;
function isScalar(value: unknown): value is Scalar {
return value == null || typeof value !== "object";
}
function getScalarRowsAndHeaders(value: unknown): { headers: string[]; rows: ScalarRow[] } {
let rows: Array<Record<string, unknown>>;
if (Array.isArray(value)) {
rows = value.map((item) =>
item && typeof item === "object" && !Array.isArray(item)
? item
? item as Record<string, unknown>
: { value: item },
);
} else if (value && typeof value === "object") {
rows = [value];
rows = [value as Record<string, unknown>];
} else {
rows = [{ value }];
}
@@ -89,10 +104,7 @@ function getScalarRowsAndHeaders(value) {
const headers = [...new Set(rows.flatMap((row) => Object.keys(row)))]
.filter((key) =>
rows.every((row) => {
const v = row[key];
return v == null || typeof v !== "object";
}),
rows.every((row) => isScalar(row[key])),
);
if (headers.length === 0) {
@@ -102,10 +114,20 @@ function getScalarRowsAndHeaders(value) {
};
}
return { headers, rows };
const scalarRows: ScalarRow[] = rows.map((row) => {
const result: ScalarRow = {};
for (const [key, rowValue] of Object.entries(row)) {
if (isScalar(rowValue)) {
result[key] = rowValue;
}
}
return result;
});
return { headers, rows: scalarRows };
}
function toTsv(value) {
function toTsv(value: unknown): string {
const { headers, rows } = getScalarRowsAndHeaders(value);
const lines = rows.map((row) =>
headers
@@ -115,22 +137,24 @@ function toTsv(value) {
return lines.join("\n");
}
export function omitPermissionGuidColumns(value) {
export function omitPermissionGuidColumns(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map((item) => omitPermissionGuidColumns(item));
}
if (!value || typeof value !== "object") {
return value;
}
const { resourceAppId, permissionId, ...rest } = value;
const { resourceAppId, permissionId, ...rest } = value as Record<string, unknown>;
void resourceAppId;
void permissionId;
return rest;
}
export async function readJsonFromStdin() {
const input = await new Promise((resolve, reject) => {
export async function readJsonFromStdin(): Promise<unknown> {
const input = await new Promise<string>((resolve, reject) => {
let data = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
process.stdin.on("data", (chunk: string) => {
data += chunk;
});
process.stdin.on("end", () => {
@@ -145,13 +169,18 @@ export async function readJsonFromStdin() {
}
try {
return JSON.parse(input);
return JSON.parse(input) as unknown;
} catch (err) {
throw new Error(`Invalid JSON input on stdin: ${err.message}`);
throw new Error(`Invalid JSON input on stdin: ${(err as Error).message}`);
}
}
export function renderOutput(command, output, outputFormat, headerSpec) {
export function renderOutput(
command: string,
output: unknown,
outputFormat: OutputFormat,
headerSpec: HeaderSpec,
): void {
if (outputFormat === "tsv") {
console.log(toTsv(output));
return;