Finished migration to commander.js, updated table rendering engine to use code blocks for resource Guids.
All checks were successful
build / build (push) Successful in 8s

This commit is contained in:
2026-03-10 07:41:47 +01:00
parent 47a8b19748
commit 047e342842
9 changed files with 90 additions and 138 deletions

View File

@@ -2,26 +2,41 @@
The `sk-tools` package provides generic CLI utilities not tied to a specific cloud provider.
## Global Options
These options apply to all commands unless stated otherwise:
- `--query`, `-q` <jmespath> - Apply JMESPath filter before output rendering.
- `--output`, `-o` <format> - Output format: `table|t|alignedtable|at|prettytable|pt|tsv`.
- `--columns`, `-C` <definition> - 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 help.
## Table
**Command name:** `table`
**Command name:** `table` (alias: `t`)
**Usage:** `sk-tools table [--from|-F <json|csv|tsv>] [--columns|-C <definition>] [global options]`
**Options:**
- `--from`, `-F` <json|csv|tsv> - Input format read from stdin. Default: `json`.
- `--columns`, `-C` <definition> - Column definition. Possible values:
- `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.
**Global options:**
- `--query`, `-q` <jmespath> - JMESPath filter applied before rendering.
- `--output`, `-o` <format> - Output format: `table|t|alignedtable|at|prettytable|pt|tsv`.
- `--help`, `-h` - Show help.
- `--columns`, `-C` <definition> - Column definition (see global options).
**Description:** The `table` command transforms stdin data into tabular output. It accepts JSON, CSV, or TSV input and renders either Markdown table variants or TSV output.
## Table Info
**Command name:** `table-info` (alias: `ti`)
**Usage:** `sk-tools table-info [--from|-F <json|csv|tsv>]`
**Options:**
- `--from`, `-F` <json|csv|tsv> - Input format read from stdin. Default: `json`.
**Description:** The `table-info` command reads stdin data and prints basic diagnostics: number of rows, number of columns, and inferred type for each column.

18
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "0.3.0",
"license": "MIT",
"dependencies": {
"commander": "^14.0.3",
"d3-dsv": "^3.0.1",
"jmespath": "^0.16.0",
"semver": "^7.7.4",
@@ -52,12 +53,12 @@
}
},
"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==",
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">=20"
}
},
"node_modules/d3-dsv": {
@@ -85,6 +86,15 @@
"node": ">=12"
}
},
"node_modules/d3-dsv/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/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@slawek/sk-tools",
"version": "0.3.0",
"version": "0.4.0",
"type": "module",
"files": [
"dist",
@@ -17,6 +17,7 @@
},
"description": "A set of generic NodeJS utilities shared by Slawek tools.",
"dependencies": {
"commander": "^14.0.3",
"d3-dsv": "^3.0.1",
"jmespath": "^0.16.0",
"semver": "^7.7.4",

View File

@@ -36,8 +36,9 @@ const program = new Command();
program
.name('bump-patch')
.description('Bump the version in package.json')
.addOption(new Option('-r, --release-type <type>', 'Release type (major, minor, patch)', 'patch')
.choices('release-type', ['major', 'minor', 'patch'])
.addOption(new Option('-r, --release-type <type>', 'Release type (major, minor, patch)')
.choices(['major', 'minor', 'patch'])
.default('patch')
)
.parse(process.argv);

View File

@@ -1,98 +1,53 @@
#!/usr/bin/env node
// SPDX-License-Identifier: MIT
import { parseArgs } from "node:util";
import { Command, Option } from "commander";
import { runCommand } from "./cli/commands.ts";
import { usageTableInfo } from "./cli/commands/table-info.ts";
import { usageTable } from "./cli/commands/table.ts";
import { runTableInfoCommand } from "./cli/commands/table-info.ts";
import { runTableCommand } from "./cli/commands/table.ts";
import { renderCliOutput } from "./cli/utils.ts";
type CliValues = {
help?: boolean;
from?: string;
query?: string;
columns?: string;
output?: string;
[key: string]: string | boolean | undefined;
};
function usage(): string {
return `Usage: sk-tools <command> [options]
Commands:
table, t Render stdin data as Markdown table
table-info, ti Print row/column stats and inferred column types
Global options (all commands):
--query, -q <jmespath>
--output, -o <format> table|t|alignedtable|at|prettytable|pt|tsv
--help, -h
Use: sk-tools --help <command>
or: sk-tools <command> --help`;
}
function usageCommand(command: string): string {
switch (command) {
case "table":
case "t":
return usageTable();
case "table-info":
case "ti":
return usageTableInfo();
default:
return `Unknown command: ${command}\n\n${usage()}`;
}
}
async function main(): Promise<void> {
const argv = process.argv.slice(2);
const command = argv[0];
const skTools = new Command();
if (!command) {
console.log(usage());
process.exit(0);
}
skTools
.name("sk-tools")
.description("A set of generic NodeJS utilities shared by Slawek tools.")
.option("-q, --query <jmespath>", "JMESPath query to filter output")
.option("-C, --columns <definition>", "Column tokens: col (raw), col: (auto), col:Label (custom), exact via = prefix")
.addOption(new Option("-o, --output <format>", "Output format: table|t|alignedtable|at|prettytable|pt|tsv")
.choices(["table", "t", "alignedtable", "at", "prettytable", "pt", "tsv"]));
if (command === "-h" || command === "--help") {
const helpCommand = argv[1];
console.log(helpCommand ? usageCommand(helpCommand) : usage());
process.exit(0);
}
skTools
.command("table")
.alias("t")
.description("Render stdin data as Markdown table")
.addOption(new Option("-F, --from <json|csv|tsv>", "Input format on stdin")
.choices(["json", "csv", "tsv"])
.default("json"))
.action(async (options, command) => {
const allOptions = command.optsWithGlobals();
const output = await runTableCommand(options);
renderCliOutput(output, allOptions.output ?? "alignedtable", allOptions.query, allOptions.columns);
});
const { values } = parseArgs({
args: argv.slice(1),
options: {
help: { type: "boolean", short: "h" },
from: { type: "string", short: "F" },
query: { type: "string", short: "q" },
columns: { type: "string", short: "C" },
output: { type: "string", short: "o" },
},
strict: true,
allowPositionals: false,
});
skTools
.command("table-info")
.alias("ti")
.description("Print row/column stats and inferred column types")
.addOption(new Option("-F, --from <json|csv|tsv>", "Input format on stdin")
.choices(["json", "csv", "tsv"])
.default("json"))
.action(async (options) => {
const output = await runTableInfoCommand(options);
console.log(output);
});
const typedValues = values as CliValues;
if (typedValues.help) {
console.log(usageCommand(command));
process.exit(0);
}
const output = await runCommand(command, typedValues);
if (typeof output === "string") {
console.log(output);
return;
}
renderCliOutput(output, typedValues.output, typedValues.query, typedValues.columns);
await skTools.parseAsync(process.argv);
}
main().catch((err: unknown) => {
const error = err as Error;
console.error(`Error: ${error.message}`);
console.error(usage());
process.exit(1);
});

View File

@@ -1,21 +0,0 @@
// SPDX-License-Identifier: MIT
import { runTableCommand } from "./commands/table.ts";
import { runTableInfoCommand } from "./commands/table-info.ts";
type CommandValues = {
[key: string]: string | boolean | undefined;
};
export async function runCommand(command: string, values: CommandValues): Promise<unknown> {
switch (command) {
case "table":
case "t":
return runTableCommand(values);
case "table-info":
case "ti":
return runTableInfoCommand(values);
default:
throw new Error(`Unknown command: ${command}`);
}
}

View File

@@ -7,7 +7,6 @@ import {
type TableInfoCommandValues = {
from?: string;
[key: string]: string | boolean | undefined;
};
type RowObject = Record<string, unknown>;
@@ -102,13 +101,6 @@ function buildInfo(input: unknown): string {
return lines.join("\n");
}
export function usageTableInfo(): string {
return `Usage: sk-tools table-info [--from|-F <json|csv|tsv>]
Options:
--from, -F <json|csv|tsv> Input format on stdin (default: json)`;
}
export async function runTableInfoCommand(values: TableInfoCommandValues): Promise<string> {
const from = (values.from ?? "json").toString().trim().toLowerCase();

View File

@@ -7,17 +7,8 @@ import {
type TableCommandValues = {
from?: string;
[key: string]: string | boolean | undefined;
};
export function usageTable(): string {
return `Usage: sk-tools table [--from|-F <json|csv|tsv>] [--columns|-C <columns>] [global options]
Options:
--from, -F <json|csv|tsv> Input format on stdin (default: json)
--columns, -C <value> Column tokens: col (raw), col: (auto), col:Label (custom), with exact match via = prefix (e.g. =col:)`;
}
export async function runTableCommand(values: TableCommandValues): Promise<unknown> {
const from = (values.from ?? "json").toString().trim().toLowerCase();

View File

@@ -27,6 +27,14 @@ function formatCell(value: unknown): string {
const inlineCodePredicates = [
(value: Scalar): boolean => typeof value === "string" && validateUuid(value),
(value: Scalar): boolean => {
if (typeof value !== "string" || !value.toLowerCase().startsWith("/subscriptions/")) {
return false;
}
const rest = value.slice("/subscriptions/".length);
const subscriptionId = rest.split("/", 1)[0];
return validateUuid(subscriptionId);
},
(value: Scalar): boolean =>
typeof value === "string"
&& /^(?:\d{1,3}\.){3}\d{1,3}(?:\/(?:\d{1,2}|(?:\d{1,3}\.){3}\d{1,3}))?$/.test(value),