Add support for CSV and TSV input formats in table command
All checks were successful
build / build (push) Successful in 12s
All checks were successful
build / build (push) Successful in 12s
This commit is contained in:
71
package-lock.json
generated
71
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@slawek/sk-az-tools",
|
"name": "@slawek/sk-az-tools",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@slawek/sk-az-tools",
|
"name": "@slawek/sk-az-tools",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/identity": "^4.13.0",
|
"@azure/identity": "^4.13.0",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"@azure/msal-node-extensions": "^1.2.0",
|
"@azure/msal-node-extensions": "^1.2.0",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
"azure-devops-node-api": "^15.1.2",
|
"azure-devops-node-api": "^15.1.2",
|
||||||
|
"d3-dsv": "^3.0.1",
|
||||||
"jmespath": "^0.16.0",
|
"jmespath": "^0.16.0",
|
||||||
"minimatch": "^10.1.2",
|
"minimatch": "^10.1.2",
|
||||||
"open": "^10.1.0"
|
"open": "^10.1.0"
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
"sk-az-tools": "dist/cli.js"
|
"sk-az-tools": "dist/cli.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/d3-dsv": "^3.0.7",
|
||||||
"@types/jmespath": "^0.15.2",
|
"@types/jmespath": "^0.15.2",
|
||||||
"@types/node": "^24.0.0",
|
"@types/node": "^24.0.0",
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.8.2"
|
||||||
@@ -269,6 +271,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-dsv": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/jmespath": {
|
"node_modules/@types/jmespath": {
|
||||||
"version": "0.15.2",
|
"version": "0.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz",
|
||||||
@@ -454,6 +463,40 @@
|
|||||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"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/d3-dsv": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "7",
|
||||||
|
"iconv-lite": "0.6",
|
||||||
|
"rw": "1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"csv2json": "bin/dsv2json.js",
|
||||||
|
"csv2tsv": "bin/dsv2dsv.js",
|
||||||
|
"dsv2dsv": "bin/dsv2dsv.js",
|
||||||
|
"dsv2json": "bin/dsv2json.js",
|
||||||
|
"json2csv": "bin/json2dsv.js",
|
||||||
|
"json2dsv": "bin/json2dsv.js",
|
||||||
|
"json2tsv": "bin/json2dsv.js",
|
||||||
|
"tsv2csv": "bin/dsv2dsv.js",
|
||||||
|
"tsv2json": "bin/dsv2json.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"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",
|
||||||
@@ -745,6 +788,18 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
@@ -1155,6 +1210,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rw": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@@ -1175,6 +1236,12 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@slawek/sk-az-tools",
|
"name": "@slawek/sk-az-tools",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@@ -24,11 +24,13 @@
|
|||||||
"@azure/msal-node-extensions": "^1.2.0",
|
"@azure/msal-node-extensions": "^1.2.0",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
"azure-devops-node-api": "^15.1.2",
|
"azure-devops-node-api": "^15.1.2",
|
||||||
|
"d3-dsv": "^3.0.1",
|
||||||
"jmespath": "^0.16.0",
|
"jmespath": "^0.16.0",
|
||||||
"minimatch": "^10.1.2",
|
"minimatch": "^10.1.2",
|
||||||
"open": "^10.1.0"
|
"open": "^10.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/d3-dsv": "^3.0.7",
|
||||||
"@types/jmespath": "^0.15.2",
|
"@types/jmespath": "^0.15.2",
|
||||||
"@types/node": "^24.0.0",
|
"@types/node": "^24.0.0",
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.8.2"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
type CliValues = {
|
type CliValues = {
|
||||||
help?: boolean;
|
help?: boolean;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
from?: string;
|
||||||
method?: string;
|
method?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
"display-name"?: string;
|
"display-name"?: string;
|
||||||
@@ -109,6 +110,7 @@ async function main(): Promise<void> {
|
|||||||
options: {
|
options: {
|
||||||
help: { type: "boolean", short: "h" },
|
help: { type: "boolean", short: "h" },
|
||||||
type: { type: "string", short: "t" },
|
type: { type: "string", short: "t" },
|
||||||
|
from: { type: "string", short: "F" },
|
||||||
method: { type: "string" },
|
method: { type: "string" },
|
||||||
url: { type: "string" },
|
url: { type: "string" },
|
||||||
"display-name": { type: "string", short: "n" },
|
"display-name": { type: "string", short: "n" },
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export async function runCommand(command: string, values: CommandValues): Promis
|
|||||||
case "logout":
|
case "logout":
|
||||||
return runLogoutCommand(values);
|
return runLogoutCommand(values);
|
||||||
case "table":
|
case "table":
|
||||||
return runTableCommand();
|
return runTableCommand(values);
|
||||||
case "list-apps":
|
case "list-apps":
|
||||||
return runListAppsCommand(values);
|
return runListAppsCommand(values);
|
||||||
case "list-app-permissions":
|
case "list-app-permissions":
|
||||||
|
|||||||
@@ -1,14 +1,28 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { readJsonFromStdin } from "../utils.ts";
|
import { readCsvFromStdin, readJsonFromStdin } from "../utils.ts";
|
||||||
|
|
||||||
|
import type { CommandValues } from "./types.ts";
|
||||||
|
|
||||||
export function usageTable(): string {
|
export function usageTable(): string {
|
||||||
return `Usage: sk-az-tools table [--header|-H <definition|auto|a|original|o>] [global options]
|
return `Usage: sk-az-tools table [--from|-F <json|csv|tsv>] [--header|-H <definition|auto|a|original|o>] [global options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-H, --header <value> Header mode: auto|a (default), original|o, or "col1, col2" or "key1: Label 1, key2: Label 2"`;
|
--from, -F <json|csv|tsv> Input format on stdin (default: json)
|
||||||
|
--header, -H <value> Header mode: auto|a (default), original|o, or "col1, col2" or "key1: Label 1, key2: Label 2"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runTableCommand(): Promise<unknown> {
|
export async function runTableCommand(values: CommandValues): Promise<unknown> {
|
||||||
return readJsonFromStdin();
|
const from = (values.from ?? "json").toString().trim().toLowerCase();
|
||||||
|
if (from === "json") {
|
||||||
|
return readJsonFromStdin();
|
||||||
|
}
|
||||||
|
if (from === "csv") {
|
||||||
|
return readCsvFromStdin(",");
|
||||||
|
}
|
||||||
|
if (from === "tsv") {
|
||||||
|
return readCsvFromStdin("\t");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Invalid --from '${values.from}'. Allowed: json, csv, tsv`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
export type CommandValues = {
|
export type CommandValues = {
|
||||||
[key: string]: string | boolean | undefined;
|
[key: string]: string | boolean | undefined;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
from?: string;
|
||||||
method?: string;
|
method?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
header?: string;
|
header?: string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import jmespath from "jmespath";
|
import jmespath from "jmespath";
|
||||||
|
import { dsvFormat } from "d3-dsv";
|
||||||
|
|
||||||
import { toMarkdownTable } from "../markdown.ts";
|
import { toMarkdownTable } from "../markdown.ts";
|
||||||
|
|
||||||
@@ -175,6 +176,43 @@ export async function readJsonFromStdin(): Promise<unknown> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function readCsvFromStdin(separator: string): Promise<unknown> {
|
||||||
|
if (!separator) {
|
||||||
|
throw new Error("separator is required");
|
||||||
|
}
|
||||||
|
if (separator.length !== 1) {
|
||||||
|
throw new Error("separator must be a single character");
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = await new Promise<string>((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 separated values input provided on stdin");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parser = dsvFormat(separator);
|
||||||
|
const rows = parser.parse(input);
|
||||||
|
if (rows.columns.some((header: string) => header.trim() === "")) {
|
||||||
|
throw new Error("header row contains empty column name");
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Invalid separated input on stdin: ${(err as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function renderOutput(
|
export function renderOutput(
|
||||||
command: string,
|
command: string,
|
||||||
output: unknown,
|
output: unknown,
|
||||||
|
|||||||
Reference in New Issue
Block a user