@kaynooo/guard-tools 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +89 -45
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -298,6 +298,26 @@ function upTo(text, separator) {
|
|
|
298
298
|
}
|
|
299
299
|
var ESCAPE = String.fromCharCode(27);
|
|
300
300
|
|
|
301
|
+
// ../kit/src/text.ts
|
|
302
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
|
|
303
|
+
function distance(a, b) {
|
|
304
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
305
|
+
for (const [i, left] of [...a].entries()) {
|
|
306
|
+
const current = [i + 1];
|
|
307
|
+
for (const [j, right] of [...b].entries())
|
|
308
|
+
current.push(Math.min((previous[j + 1] ?? 0) + 1, (current[j] ?? 0) + 1, (previous[j] ?? 0) + (left === right ? 0 : 1)));
|
|
309
|
+
previous = current;
|
|
310
|
+
}
|
|
311
|
+
return previous[b.length] ?? 0;
|
|
312
|
+
}
|
|
313
|
+
function closest(name, names) {
|
|
314
|
+
const started = names.find((known) => known.startsWith(name));
|
|
315
|
+
if (started !== undefined)
|
|
316
|
+
return started;
|
|
317
|
+
const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
|
|
318
|
+
return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
301
321
|
// ../kit/src/args.ts
|
|
302
322
|
function jsonOption(holds) {
|
|
303
323
|
return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
|
|
@@ -317,8 +337,8 @@ function fail(message, code = 2, hint) {
|
|
|
317
337
|
}
|
|
318
338
|
|
|
319
339
|
class UsageError extends Exit {
|
|
320
|
-
constructor(message) {
|
|
321
|
-
super(message, 2);
|
|
340
|
+
constructor(message, hint) {
|
|
341
|
+
super(message, 2, hint);
|
|
322
342
|
}
|
|
323
343
|
}
|
|
324
344
|
function badUsage(message) {
|
|
@@ -341,6 +361,7 @@ var completion = { active: false };
|
|
|
341
361
|
var OPTIONAL = /\[[^\]]+\]/g;
|
|
342
362
|
var USAGE_PREFIX = /^usage:\s*/i;
|
|
343
363
|
var FULL_STOP = /\.$/;
|
|
364
|
+
var DASHES = /^-+/;
|
|
344
365
|
function usageLine(usage, colors = paint(process3.stdout)) {
|
|
345
366
|
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
346
367
|
}
|
|
@@ -377,10 +398,10 @@ function optionRows(options, colors = paint(process3.stdout)) {
|
|
|
377
398
|
const rows = Object.entries(options).map(([name, option]) => {
|
|
378
399
|
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
379
400
|
const value = option.type === "string" ? ` ${colors.dim("<value>")}` : "";
|
|
380
|
-
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? " <value>".length : 0), description: option.description };
|
|
401
|
+
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? " <value>".length : 0), description: option.description ?? "" };
|
|
381
402
|
});
|
|
382
403
|
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
383
|
-
return rows.map((row) => ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
404
|
+
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
384
405
|
}
|
|
385
406
|
var OUTPUT_OPTIONS = {
|
|
386
407
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
@@ -412,7 +433,8 @@ function parse(args, usage, options, settings = {}) {
|
|
|
412
433
|
try {
|
|
413
434
|
parsed = parseArgs({ args: joinDashValues(own, all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
414
435
|
} catch (error) {
|
|
415
|
-
|
|
436
|
+
const message = parseError(error);
|
|
437
|
+
throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
|
|
416
438
|
}
|
|
417
439
|
const values = parsed.values;
|
|
418
440
|
applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
|
|
@@ -455,6 +477,22 @@ function parseError(error) {
|
|
|
455
477
|
const message = upTo(error instanceof Error ? error.message : String(error), ". ");
|
|
456
478
|
return `${message.charAt(0).toLowerCase()}${message.slice(1)}`;
|
|
457
479
|
}
|
|
480
|
+
var UNKNOWN_OPTION = /^unknown option '(-[^']+)'/;
|
|
481
|
+
function nearestOption(message, options, allowNegative) {
|
|
482
|
+
const typed = UNKNOWN_OPTION.exec(message)?.[1];
|
|
483
|
+
if (typed === undefined)
|
|
484
|
+
return;
|
|
485
|
+
const word = typed.replace(DASHES, "");
|
|
486
|
+
if (word.length < 2 && !typed.startsWith("--"))
|
|
487
|
+
return;
|
|
488
|
+
const known = Object.entries(options).flatMap(([name, option]) => [
|
|
489
|
+
...option.short === undefined ? [] : [`-${option.short}`],
|
|
490
|
+
`--${name}`,
|
|
491
|
+
...allowNegative && option.type === "boolean" ? [`--no-${name}`] : []
|
|
492
|
+
]);
|
|
493
|
+
const meant = closest(word, known.map((flag) => flag.replace(DASHES, "")));
|
|
494
|
+
return meant === undefined ? undefined : `did you mean ${known.find((flag) => flag.replace(DASHES, "") === meant)}?`;
|
|
495
|
+
}
|
|
458
496
|
async function readInput(path, { noStdin = "nothing is piped on stdin" } = {}) {
|
|
459
497
|
if (path === "-") {
|
|
460
498
|
if (process3.stdin.isTTY)
|
|
@@ -468,33 +506,19 @@ async function readInput(path, { noStdin = "nothing is piped on stdin" } = {}) {
|
|
|
468
506
|
}
|
|
469
507
|
// ../kit/src/cli.ts
|
|
470
508
|
import process4 from "process";
|
|
471
|
-
|
|
472
|
-
// ../kit/src/text.ts
|
|
473
|
-
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
|
|
474
|
-
function distance(a, b) {
|
|
475
|
-
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
476
|
-
for (const [i, left] of [...a].entries()) {
|
|
477
|
-
const current = [i + 1];
|
|
478
|
-
for (const [j, right] of [...b].entries())
|
|
479
|
-
current.push(Math.min((previous[j + 1] ?? 0) + 1, (current[j] ?? 0) + 1, (previous[j] ?? 0) + (left === right ? 0 : 1)));
|
|
480
|
-
previous = current;
|
|
481
|
-
}
|
|
482
|
-
return previous[b.length] ?? 0;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// ../kit/src/cli.ts
|
|
486
509
|
var LEVEL_OPTIONS = {
|
|
487
510
|
...OUTPUT_OPTIONS,
|
|
488
511
|
help: { type: "boolean", short: "h", description: "show this help" }
|
|
489
512
|
};
|
|
490
513
|
var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
|
|
491
|
-
function commandRows(commands, colors) {
|
|
514
|
+
async function commandRows(commands, colors) {
|
|
492
515
|
const width = Math.max(...Object.keys(commands).map((name) => name.length));
|
|
493
516
|
const groups = new Map([["Commands:", []]]);
|
|
494
|
-
|
|
517
|
+
const summaries = await Promise.all(Object.values(commands).map(async ({ summary }) => resolveSummary(summary)));
|
|
518
|
+
for (const [index, [name, { group }]] of Object.entries(commands).entries()) {
|
|
495
519
|
const heading = group === undefined ? "Commands:" : `${group}:`;
|
|
496
520
|
const rows = groups.get(heading) ?? [];
|
|
497
|
-
rows.push(` ${colors.cyan(name.padEnd(width))} ${colors.dim(
|
|
521
|
+
rows.push(` ${colors.cyan(name.padEnd(width))} ${colors.dim(summaries[index])}`);
|
|
498
522
|
groups.set(heading, rows);
|
|
499
523
|
}
|
|
500
524
|
return [...groups].filter(([, rows]) => rows.length > 0).flatMap(([heading, rows], index) => [...index === 0 ? [] : [""], colors.bold(heading), ...rows]);
|
|
@@ -514,28 +538,38 @@ function takeOutputFlags(argv) {
|
|
|
514
538
|
}
|
|
515
539
|
return argv.slice(index);
|
|
516
540
|
}
|
|
517
|
-
function
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
|
|
541
|
+
async function resolveSummary(summary) {
|
|
542
|
+
return typeof summary === "string" ? summary : summary();
|
|
543
|
+
}
|
|
544
|
+
function scopeOf(commands, extra, aliases) {
|
|
545
|
+
return { commands: typeof commands === "function" ? {} : commands, lazy: [...typeof commands === "function" ? [commands] : [], ...extra === undefined ? [] : [extra]], aliases };
|
|
523
546
|
}
|
|
524
|
-
async function
|
|
547
|
+
async function expand(scope, words) {
|
|
548
|
+
if (scope.lazy.length === 0)
|
|
549
|
+
return scope;
|
|
550
|
+
const commands = { ...scope.commands };
|
|
551
|
+
for (const added of await Promise.all(scope.lazy.map(async (lazy) => lazy(words)))) {
|
|
552
|
+
for (const [name, command] of Object.entries(added))
|
|
553
|
+
commands[name] ??= command;
|
|
554
|
+
}
|
|
555
|
+
return { ...scope, commands, lazy: [] };
|
|
556
|
+
}
|
|
557
|
+
async function help({ path, header, summary: lazySummary, commands, defaultCommand, examples, replaces }, stream) {
|
|
525
558
|
const colors = paint(stream);
|
|
526
559
|
const prefix = path.join(" ");
|
|
560
|
+
const summary = await resolveSummary(lazySummary);
|
|
527
561
|
if (wantsHelpJson())
|
|
528
562
|
return JSON.stringify(description(`${prefix} <command> [options]: ${summary}`, { examples, replaces }));
|
|
529
563
|
const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
|
|
530
564
|
const options = fallback !== undefined && "run" in fallback ? await optionsOf(fallback.run) : undefined;
|
|
531
565
|
const own = Object.entries(options ?? {}).filter(([name]) => !(name in TOP_OPTIONS));
|
|
532
566
|
return [
|
|
533
|
-
header(colors),
|
|
567
|
+
header(colors, summary),
|
|
534
568
|
"",
|
|
535
569
|
usageLine(`${prefix} ${defaultCommand === undefined ? colors.cyan("<command>") : `[${colors.cyan("<command>")}]`} [options]`, colors),
|
|
536
570
|
...replacesRows(replaces, colors),
|
|
537
571
|
"",
|
|
538
|
-
...commandRows(commands, colors),
|
|
572
|
+
...await commandRows(commands, colors),
|
|
539
573
|
...own.length === 0 ? [] : ["", `${colors.bold(`Options of ${defaultCommand}:`)} ${colors.dim("the command run when none is named")}`, ...optionRows(Object.fromEntries(own), colors)],
|
|
540
574
|
"",
|
|
541
575
|
colors.bold("Options:"),
|
|
@@ -581,11 +615,11 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
581
615
|
}
|
|
582
616
|
const [name, ...args] = argv;
|
|
583
617
|
if (name === undefined && level.path.length > 1) {
|
|
584
|
-
console.error(await help(level, process4.stderr));
|
|
618
|
+
console.error(await help(await expand(level, [...level.path.slice(1), ...argv]), process4.stderr));
|
|
585
619
|
return 2;
|
|
586
620
|
}
|
|
587
621
|
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
588
|
-
console.log(await help(level, process4.stdout));
|
|
622
|
+
console.log(await help(await expand(level, [...level.path.slice(1), ...argv]), process4.stdout));
|
|
589
623
|
return 0;
|
|
590
624
|
}
|
|
591
625
|
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
@@ -594,6 +628,10 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
594
628
|
}
|
|
595
629
|
let command = level.commands[level.aliases?.[name] ?? name];
|
|
596
630
|
let rest = args;
|
|
631
|
+
if (command === undefined && level.lazy.length > 0) {
|
|
632
|
+
level = await expand(level, [...level.path.slice(1), ...argv]);
|
|
633
|
+
command = level.commands[level.aliases?.[name] ?? name];
|
|
634
|
+
}
|
|
597
635
|
if (command === undefined && level.defaultCommand !== undefined) {
|
|
598
636
|
command = level.commands[level.defaultCommand];
|
|
599
637
|
rest = argv;
|
|
@@ -605,7 +643,7 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
605
643
|
}
|
|
606
644
|
if ("commands" in command) {
|
|
607
645
|
const { summary } = command;
|
|
608
|
-
return dispatch({ path: [...level.path, name], header: (colors) => `${colors.bold(`${prefix} ${name}`)} ${
|
|
646
|
+
return dispatch({ path: [...level.path, name], header: (colors, text) => `${colors.bold(`${prefix} ${name}`)} ${text}`, summary, ...scopeOf(command.commands, command.extraCommands, command.aliases), defaultCommand: undefined, examples: command.examples, replaces: undefined }, rest, { version, redact });
|
|
609
647
|
}
|
|
610
648
|
try {
|
|
611
649
|
return await command.run(rest);
|
|
@@ -630,18 +668,24 @@ async function optionsOf(run) {
|
|
|
630
668
|
async function leafCandidates(run, words) {
|
|
631
669
|
if (!(words.at(-1) ?? "").startsWith("-"))
|
|
632
670
|
return [FILES];
|
|
633
|
-
return Object.entries(await optionsOf(run)).map(([name, option]) => `--${name} ${option.description}`);
|
|
671
|
+
return Object.entries(await optionsOf(run)).map(([name, option]) => option.description === undefined ? `--${name}` : `--${name} ${option.description}`);
|
|
634
672
|
}
|
|
635
|
-
async function candidates(
|
|
673
|
+
async function candidates(scope, defaultCommand, words, before = []) {
|
|
636
674
|
const [word = "", ...rest] = words;
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
675
|
+
const listing = rest.length === 0 && !word.startsWith("-");
|
|
676
|
+
let { commands, aliases } = scope;
|
|
677
|
+
let command = commands[aliases?.[word] ?? word];
|
|
678
|
+
if (listing || command === undefined) {
|
|
679
|
+
({ commands, aliases } = await expand(scope, [...before, ...words]));
|
|
680
|
+
command = commands[aliases?.[word] ?? word];
|
|
681
|
+
}
|
|
682
|
+
if (listing)
|
|
683
|
+
return Promise.all(Object.entries(commands).map(async ([name, { summary }]) => `${name} ${await resolveSummary(summary)}`));
|
|
640
684
|
if (command === undefined) {
|
|
641
685
|
const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
|
|
642
686
|
return fallback !== undefined && "run" in fallback ? leafCandidates(fallback.run, words) : [];
|
|
643
687
|
}
|
|
644
|
-
return "commands" in command ? candidates(command.commands, command.aliases, undefined, rest) : leafCandidates(command.run, rest);
|
|
688
|
+
return "commands" in command ? candidates(scopeOf(command.commands, command.extraCommands, command.aliases), undefined, rest, [...before, word]) : leafCandidates(command.run, rest);
|
|
645
689
|
}
|
|
646
690
|
async function printCandidates(found) {
|
|
647
691
|
for (const line of await found)
|
|
@@ -650,8 +694,8 @@ async function printCandidates(found) {
|
|
|
650
694
|
}
|
|
651
695
|
async function runCli(cli, argv = process4.argv.slice(2)) {
|
|
652
696
|
if (argv[0] === COMPLETE)
|
|
653
|
-
return printCandidates(candidates(cli.commands, cli.aliases, cli.defaultCommand, argv.slice(1)));
|
|
654
|
-
return dispatch({ path: [cli.name], header: (colors) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${
|
|
697
|
+
return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
|
|
698
|
+
return dispatch({ path: [cli.name], header: (colors, about) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${about}`, summary: cli.about, ...scopeOf(cli.commands, cli.extraCommands, cli.aliases), defaultCommand: cli.defaultCommand, examples: cli.examples, replaces: cli.replaces }, argv, { version: cli.version, redact: cli.redact });
|
|
655
699
|
}
|
|
656
700
|
// ../kit/src/process.ts
|
|
657
701
|
import { constants } from "os";
|
|
@@ -675,7 +719,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore" } = {}) {
|
|
|
675
719
|
return { code: exitCodeOf(proc.exitCode, proc.signalCode), chunks, output, text: output.toString() };
|
|
676
720
|
}
|
|
677
721
|
// package.json
|
|
678
|
-
var version = "0.4.
|
|
722
|
+
var version = "0.4.1";
|
|
679
723
|
|
|
680
724
|
// ../kit/src/shell.ts
|
|
681
725
|
var SEPARATORS = new Set([`
|
|
@@ -1171,7 +1215,7 @@ var EXEC_VALUE_OPTIONS = new Set(["-e", "--env", "--env-file", "-u", "--user", "
|
|
|
1171
1215
|
var CONFIG_LISTS = new Set(["--services", "--volumes", "--profiles", "--images", "--networks", "--hash", "-q", "--quiet"]);
|
|
1172
1216
|
var FORMAT_OPTIONS = new Set(["-f", "--format"]);
|
|
1173
1217
|
var WHOLE_FORMAT = /Env|\{\{\s*json\s+\.\s*\}\}|^json$/;
|
|
1174
|
-
var USE_EV = '.env files hold secrets that must never reach the transcript, so they are not read, edited or printed directly. Use `ev` instead: `ev list`, `ev get KEY` (masked), `ev grep <regex>` for the keys matching it, `ev show <rev>:<file>` for one at a git revision, `printf %s "$value" | ev set KEY [file]` (`--empty` for an empty value), `ev unset KEY`, `ev mv KEY --from .env --to .env.local`, `ev diff a b`, `ev missing`; and pipe output that may carry a secret through `redact`, as `docker compose exec <svc> printenv | redact` or `docker compose config | redact`. If `ev` is missing: `bun install -g @kaynooo/env-tools`.';
|
|
1218
|
+
var USE_EV = '.env files hold secrets that must never reach the transcript, so they are not read, edited or printed directly. Use `ev` instead: `ev list`, `ev get KEY` (masked), `ev grep <regex>` for the keys matching it, `ev show <rev>:<file>` for one at a git revision, `printf %s "$value" | ev set KEY [file]` (`--empty` for an empty value), `ev unset KEY`, `ev cp KEY --from .env --to .env.local`, `ev mv KEY --from .env --to .env.local`, `ev diff a b`, `ev missing`; and pipe output that may carry a secret through `redact`, as `docker compose exec <svc> printenv | redact` or `docker compose config | redact`. If `ev` is missing: `bun install -g @kaynooo/env-tools`.';
|
|
1175
1219
|
function isEnvFile(path) {
|
|
1176
1220
|
const name = path.slice(Math.max(path.lastIndexOf("/"), path.lastIndexOf(":")) + 1);
|
|
1177
1221
|
return (ENV_FILE.test(name) || name === ".env*") && !TEMPLATE.test(name);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaynooo/guard-tools",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"description": "guard: the safety rules a Claude Code Bash call is held to (git, remote machines, SQL writes, env files, package managers, commit messages), each refusal naming the route.",
|
|
6
6
|
"author": "Kaynooo <contact@kaynooo.fr>",
|
|
7
7
|
"license": "MIT",
|