// SPDX-License-Identifier: MIT import jmespath from "jmespath"; import { toMarkdownTable } from "../markdown.ts"; type HeaderSpec = | { mode: "auto" } | { mode: "original" } | { mode: "list"; labels: string[] } | { mode: "map"; map: Record }; type OutputFormat = "json" | "table" | "alignedtable" | "prettytable" | "tsv"; type Scalar = string | number | boolean | null | undefined; type ScalarRow = Record; export function outputFiltered(object: unknown, query?: string): unknown { return query ? jmespath.search(object, query) : object; } export function parseHeaderSpec(headerValue?: string): HeaderSpec { if (!headerValue) { return { mode: "auto" }; } const raw = headerValue.trim(); if (raw === "" || raw.toLowerCase() === "auto" || raw.toLowerCase() === "a") { return { mode: "auto" }; } if (raw.toLowerCase() === "original" || raw.toLowerCase() === "o") { return { mode: "original" }; } const parts = raw.split(",").map((p) => p.trim()).filter(Boolean); const isMap = parts.some((p) => p.includes(":")); if (!isMap) { return { mode: "list", labels: parts }; } const map: Record = {}; for (const part of parts) { const idx = part.indexOf(":"); if (idx < 0) { throw new Error(`Invalid --header mapping segment: '${part}'`); } const key = part.slice(0, idx).trim(); const label = part.slice(idx + 1).trim(); if (!key || !label) { throw new Error(`Invalid --header mapping segment: '${part}'`); } map[key] = label; } return { mode: "map", map }; } export function normalizeOutputFormat(outputValue?: string): OutputFormat { if (outputValue == null) { return "json"; } const raw = outputValue.toLowerCase(); if (raw === "json") { throw new Error("JSON is the default output. Omit --output to use it."); } if (raw === "j") { throw new Error("JSON is the default output. Omit --output to use it."); } if (raw === "table" || raw === "t") return "table"; if (raw === "alignedtable" || raw === "at") return "alignedtable"; if (raw === "prettytable" || raw === "pt") return "prettytable"; if (raw === "tsv") return "tsv"; throw new Error("--output must be one of: table|t, alignedtable|at, prettytable|pt, tsv"); } function isScalar(value: unknown): value is Scalar { return value == null || typeof value !== "object"; } function getScalarRowsAndHeaders(value: unknown): { headers: string[]; rows: ScalarRow[] } { let rows: Array>; if (Array.isArray(value)) { rows = value.map((item) => item && typeof item === "object" && !Array.isArray(item) ? item as Record : { value: item }, ); } else if (value && typeof value === "object") { rows = [value as Record]; } else { rows = [{ value }]; } if (rows.length === 0) { return { headers: ["result"], rows: [{ result: "" }], }; } const headers = [...new Set(rows.flatMap((row) => Object.keys(row)))] .filter((key) => rows.every((row) => isScalar(row[key])), ); if (headers.length === 0) { return { headers: ["result"], rows: [{ result: "" }], }; } 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: unknown): string { const { headers, rows } = getScalarRowsAndHeaders(value); const lines = rows.map((row) => headers .map((header) => (row[header] == null ? "" : String(row[header]).replaceAll("\t", " ").replaceAll("\n", " "))) .join("\t"), ); return lines.join("\n"); } 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 as Record; void resourceAppId; void permissionId; return rest; } export async function readJsonFromStdin(): Promise { const input = await new Promise((resolve, reject) => { let data = ""; process.stdin.setEncoding("utf8"); process.stdin.on("data", (chunk: string) => { data += chunk; }); process.stdin.on("end", () => { resolve(data); }); process.stdin.on("error", (err) => { reject(err); }); }); if (!input.trim()) { throw new Error("No JSON input provided on stdin"); } try { return JSON.parse(input) as unknown; } catch (err) { throw new Error(`Invalid JSON input on stdin: ${(err as Error).message}`); } } export function renderOutput( command: string, output: unknown, outputFormat: OutputFormat, headerSpec: HeaderSpec, ): void { if (outputFormat === "tsv") { console.log(toTsv(output)); return; } if (command === "table") { console.log(toMarkdownTable( output, outputFormat === "alignedtable" || outputFormat === "prettytable", outputFormat === "prettytable", headerSpec, )); } else if (outputFormat === "alignedtable") { console.log(toMarkdownTable(output, true, false, headerSpec)); } else if (outputFormat === "prettytable") { console.log(toMarkdownTable(output, true, true, headerSpec)); } else if (outputFormat === "table") { console.log(toMarkdownTable(output, false, false, headerSpec)); } else { console.log(JSON.stringify(output, null, 2)); } }