@kaynooo/cli-doctor 0.2.0 → 0.4.0
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/README.md +8 -0
- package/dist/cli-bin.js +532 -113
- package/dist/cli.js +468 -104
- package/package.json +2 -2
package/dist/cli-bin.js
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli-bin.ts
|
|
5
|
-
import
|
|
5
|
+
import process11 from "process";
|
|
6
6
|
|
|
7
7
|
// ../kit/src/args.ts
|
|
8
|
-
import
|
|
8
|
+
import process4 from "process";
|
|
9
9
|
import { parseArgs } from "util";
|
|
10
10
|
|
|
11
|
+
// ../kit/src/format.ts
|
|
12
|
+
import process2 from "process";
|
|
13
|
+
|
|
11
14
|
// ../kit/src/style.ts
|
|
12
15
|
import process from "process";
|
|
13
16
|
|
|
14
|
-
// ../../node_modules/.bun/@kaynooo+utils@2.31.
|
|
17
|
+
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
|
|
15
18
|
function x(s) {
|
|
16
19
|
let r = s.startsWith("#") ? s.slice(1).toLowerCase() : s.toLowerCase();
|
|
17
20
|
if (!/^[a-f0-9]+$/.test(r))
|
|
@@ -231,7 +234,7 @@ class e {
|
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
236
|
|
|
234
|
-
// ../../node_modules/.bun/@kaynooo+utils@2.31.
|
|
237
|
+
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-y1msbssb.js
|
|
235
238
|
var r = 39;
|
|
236
239
|
var o = 49;
|
|
237
240
|
var f2 = "\x1B[";
|
|
@@ -293,11 +296,102 @@ function upTo(text, separator) {
|
|
|
293
296
|
const end = text.indexOf(separator);
|
|
294
297
|
return end === -1 ? text : text.slice(0, end);
|
|
295
298
|
}
|
|
299
|
+
function truncate(text, width) {
|
|
300
|
+
return text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
301
|
+
}
|
|
302
|
+
var LINE_BREAKS = /\s*\n\s*/g;
|
|
296
303
|
var ESCAPE = String.fromCharCode(27);
|
|
304
|
+
function textWidth(text) {
|
|
305
|
+
return Bun.stringWidth(text);
|
|
306
|
+
}
|
|
307
|
+
function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
|
|
308
|
+
const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
|
|
309
|
+
const widths = [];
|
|
310
|
+
for (const row of cells) {
|
|
311
|
+
for (const [index, cell] of row.entries())
|
|
312
|
+
widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
|
|
313
|
+
}
|
|
314
|
+
const shown = fit === undefined ? cells : narrow(cells, widths, fit);
|
|
315
|
+
const colors = paint(process2.stdout);
|
|
316
|
+
const lines = shown.map((row) => row.map((cell, index) => {
|
|
317
|
+
const width = widths[index] ?? 0;
|
|
318
|
+
const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
|
|
319
|
+
if (align[index] === "right")
|
|
320
|
+
return `${gap}${cell}`;
|
|
321
|
+
return index === row.length - 1 ? cell : `${cell}${gap}`;
|
|
322
|
+
}).join(" ").trimEnd());
|
|
323
|
+
return lines.map((line, index) => header && index === 0 ? colors.bold(line) : line).join(`
|
|
324
|
+
`);
|
|
325
|
+
}
|
|
326
|
+
function narrow(rows, widths, limit) {
|
|
327
|
+
const used = () => widths.reduce((sum, width) => sum + width, 0) + 2 * Math.max(0, widths.length - 1);
|
|
328
|
+
const cuttable = (index) => rows.every((row) => !(row[index] ?? "").includes(ESCAPE));
|
|
329
|
+
const floor = 8;
|
|
330
|
+
while (used() > limit) {
|
|
331
|
+
let widest = -1;
|
|
332
|
+
for (const [index, width] of widths.entries()) {
|
|
333
|
+
if (cuttable(index) && width > floor && width > (widths[widest] ?? 0))
|
|
334
|
+
widest = index;
|
|
335
|
+
}
|
|
336
|
+
if (widest === -1)
|
|
337
|
+
break;
|
|
338
|
+
widths[widest] = Math.max(floor, (widths[widest] ?? 0) - (used() - limit));
|
|
339
|
+
}
|
|
340
|
+
return rows.map((row) => row.map((cell, index) => textWidth(cell) > (widths[index] ?? 0) ? truncate(cell, widths[index] ?? 0) : cell));
|
|
341
|
+
}
|
|
297
342
|
function printJson(value) {
|
|
298
343
|
console.log(JSON.stringify(value, undefined, 2));
|
|
299
344
|
}
|
|
300
345
|
|
|
346
|
+
// ../kit/src/help.ts
|
|
347
|
+
import process3 from "process";
|
|
348
|
+
var OPTIONAL = /\[[^\]]+\]/g;
|
|
349
|
+
var USAGE_PREFIX = /^usage:\s*/i;
|
|
350
|
+
var FULL_STOP = /\.$/;
|
|
351
|
+
function usageLine(usage, colors = paint(process3.stdout)) {
|
|
352
|
+
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
353
|
+
}
|
|
354
|
+
function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces } = {}) {
|
|
355
|
+
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
356
|
+
`);
|
|
357
|
+
}
|
|
358
|
+
function replacesRows(replaces, colors = paint(process3.stdout)) {
|
|
359
|
+
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
360
|
+
}
|
|
361
|
+
function wantsHelpJson(env = process3.env) {
|
|
362
|
+
return env.KIT_HELP === "json";
|
|
363
|
+
}
|
|
364
|
+
function description(usage, { examples = [], replaces } = {}) {
|
|
365
|
+
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
366
|
+
`);
|
|
367
|
+
const first = lines[0];
|
|
368
|
+
const colon = first.indexOf(": ");
|
|
369
|
+
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
370
|
+
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
371
|
+
return {
|
|
372
|
+
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
373
|
+
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
374
|
+
...replaces === undefined ? {} : { replaces },
|
|
375
|
+
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function exampleRows(examples, colors = paint(process3.stdout)) {
|
|
379
|
+
if (examples.length === 0)
|
|
380
|
+
return [];
|
|
381
|
+
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
382
|
+
}
|
|
383
|
+
function optionRows(options, colors = paint(process3.stdout)) {
|
|
384
|
+
const rows = Object.entries(options).map(([name, option]) => {
|
|
385
|
+
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
386
|
+
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
387
|
+
const placeholder = option.optional === undefined ? named : `[${named}]`;
|
|
388
|
+
const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
|
|
389
|
+
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
|
|
390
|
+
});
|
|
391
|
+
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
392
|
+
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
301
395
|
// ../kit/src/text.ts
|
|
302
396
|
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
|
|
303
397
|
function distance(a, b) {
|
|
@@ -317,8 +411,21 @@ function closest(name, names) {
|
|
|
317
411
|
const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
|
|
318
412
|
return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
|
|
319
413
|
}
|
|
414
|
+
// ../kit/src/input.ts
|
|
415
|
+
var DURATION = /^(\d+(?:\.\d+)?)(ms|[smhd])?$/;
|
|
416
|
+
var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
417
|
+
function duration(value, name, fallback, unit = "s") {
|
|
418
|
+
if (value === undefined)
|
|
419
|
+
return fallback;
|
|
420
|
+
const match = DURATION.exec(value.trim());
|
|
421
|
+
const ms = match === null ? 0 : Number(match[1]) * UNIT_MS[match[2] ?? unit];
|
|
422
|
+
if (ms < 1)
|
|
423
|
+
badUsage(`--${name} takes a duration such as 30s, 5m, 1h or 7d, not ${value}`);
|
|
424
|
+
return ms;
|
|
425
|
+
}
|
|
320
426
|
|
|
321
427
|
// ../kit/src/args.ts
|
|
428
|
+
var DASHES = /^-+/;
|
|
322
429
|
function jsonOption(holds) {
|
|
323
430
|
return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
|
|
324
431
|
}
|
|
@@ -349,61 +456,20 @@ class HelpShown extends Exit {
|
|
|
349
456
|
|
|
350
457
|
class Completing {
|
|
351
458
|
options;
|
|
352
|
-
|
|
459
|
+
positionals;
|
|
460
|
+
candidates;
|
|
461
|
+
constructor(options, positionals = [], candidates = {}) {
|
|
353
462
|
this.options = options;
|
|
463
|
+
this.positionals = positionals;
|
|
464
|
+
this.candidates = candidates;
|
|
354
465
|
}
|
|
355
466
|
}
|
|
356
467
|
var completion = { active: false };
|
|
357
|
-
var OPTIONAL = /\[[^\]]+\]/g;
|
|
358
|
-
var USAGE_PREFIX = /^usage:\s*/i;
|
|
359
|
-
var FULL_STOP = /\.$/;
|
|
360
|
-
var DASHES = /^-+/;
|
|
361
|
-
function usageLine(usage, colors = paint(process2.stdout)) {
|
|
362
|
-
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
363
|
-
}
|
|
364
|
-
function helpText(usage, options, colors = paint(process2.stdout), { examples = [], replaces } = {}) {
|
|
365
|
-
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
366
|
-
`);
|
|
367
|
-
}
|
|
368
|
-
function replacesRows(replaces, colors = paint(process2.stdout)) {
|
|
369
|
-
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
370
|
-
}
|
|
371
|
-
function wantsHelpJson(env = process2.env) {
|
|
372
|
-
return env.KIT_HELP === "json";
|
|
373
|
-
}
|
|
374
|
-
function description(usage, { examples = [], replaces } = {}) {
|
|
375
|
-
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
376
|
-
`);
|
|
377
|
-
const first = lines[0];
|
|
378
|
-
const colon = first.indexOf(": ");
|
|
379
|
-
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
380
|
-
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
381
|
-
return {
|
|
382
|
-
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
383
|
-
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
384
|
-
...replaces === undefined ? {} : { replaces },
|
|
385
|
-
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
function exampleRows(examples, colors = paint(process2.stdout)) {
|
|
389
|
-
if (examples.length === 0)
|
|
390
|
-
return [];
|
|
391
|
-
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
392
|
-
}
|
|
393
|
-
function optionRows(options, colors = paint(process2.stdout)) {
|
|
394
|
-
const rows = Object.entries(options).map(([name, option]) => {
|
|
395
|
-
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
396
|
-
const value = option.type === "string" ? ` ${colors.dim("<value>")}` : "";
|
|
397
|
-
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? " <value>".length : 0), description: option.description ?? "" };
|
|
398
|
-
});
|
|
399
|
-
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
400
|
-
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
401
|
-
}
|
|
402
468
|
var OUTPUT_OPTIONS = {
|
|
403
469
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
404
470
|
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
405
471
|
};
|
|
406
|
-
function applyOutput(color, plain, env =
|
|
472
|
+
function applyOutput(color, plain, env = process4.env) {
|
|
407
473
|
const when = plain === true ? "never" : color ?? "auto";
|
|
408
474
|
if (when === "auto")
|
|
409
475
|
return;
|
|
@@ -420,14 +486,13 @@ function applyOutput(color, plain, env = process2.env) {
|
|
|
420
486
|
function parse(args, usage, options, settings = {}) {
|
|
421
487
|
const output = Object.fromEntries(Object.entries(OUTPUT_OPTIONS).filter(([name]) => !(name in options)));
|
|
422
488
|
const all = { ...options, ...output, help: { type: "boolean", short: "h", description: "show this help" } };
|
|
489
|
+
const listing = listed(all, settings.allowNegative === true);
|
|
423
490
|
if (completion.active)
|
|
424
|
-
throw new Completing(
|
|
425
|
-
const
|
|
426
|
-
const own = separator === -1 ? args : args.slice(0, separator);
|
|
427
|
-
const rest = separator === -1 ? undefined : args.slice(separator + 1);
|
|
491
|
+
throw new Completing(listing, settings.positionals, settings.candidates);
|
|
492
|
+
const { own, rest } = settings.trailing === true ? splitTrailing(args, all, settings.positionals?.length ?? 0) : splitRest(args, settings.rest === true);
|
|
428
493
|
let parsed;
|
|
429
494
|
try {
|
|
430
|
-
parsed = parseArgs({ args: joinDashValues(own, all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
495
|
+
parsed = parseArgs({ args: joinDashValues(withBare(own, all), all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
431
496
|
} catch (error) {
|
|
432
497
|
const message = parseError(error);
|
|
433
498
|
throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
|
|
@@ -435,12 +500,84 @@ function parse(args, usage, options, settings = {}) {
|
|
|
435
500
|
const values = parsed.values;
|
|
436
501
|
applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
|
|
437
502
|
if (values.help === true) {
|
|
438
|
-
console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings)) : helpText(usage,
|
|
503
|
+
console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings)) : helpText(usage, listing, undefined, settings));
|
|
439
504
|
throw new HelpShown;
|
|
440
505
|
}
|
|
506
|
+
checkChoices(parsed.values, all);
|
|
507
|
+
checkConflicts(parsed.values, all);
|
|
441
508
|
if (settings.positionals !== undefined)
|
|
442
509
|
checkPositionals(parsed.positionals, settings.positionals);
|
|
443
|
-
return { ...parsed, positionals: parsed.positionals, rest };
|
|
510
|
+
return { ...parsed, values: parsed.values, positionals: parsed.positionals, rest };
|
|
511
|
+
}
|
|
512
|
+
function splitRest(args, passes) {
|
|
513
|
+
const separator = passes ? args.indexOf("--") : -1;
|
|
514
|
+
return { own: separator === -1 ? args : args.slice(0, separator), rest: separator === -1 ? undefined : args.slice(separator + 1) };
|
|
515
|
+
}
|
|
516
|
+
function splitTrailing(args, options, count) {
|
|
517
|
+
const takesValue = (word) => !word.includes("=") && Object.entries(options).some(([name, { short, type }]) => type === "string" && (word === `--${name}` || word === `-${short}`));
|
|
518
|
+
let seen = 0;
|
|
519
|
+
if (count === 0)
|
|
520
|
+
return { own: args, rest: undefined };
|
|
521
|
+
for (let at = 0;at < args.length; at++) {
|
|
522
|
+
const word = args[at];
|
|
523
|
+
if (word === "--")
|
|
524
|
+
break;
|
|
525
|
+
if (word.startsWith("-")) {
|
|
526
|
+
if (takesValue(word))
|
|
527
|
+
at++;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
seen++;
|
|
531
|
+
if (seen === count)
|
|
532
|
+
return { own: args.slice(0, at + 1), rest: args.slice(at + 1) };
|
|
533
|
+
}
|
|
534
|
+
return { own: args, rest: undefined };
|
|
535
|
+
}
|
|
536
|
+
function listed(options, allowNegative) {
|
|
537
|
+
if (!allowNegative)
|
|
538
|
+
return options;
|
|
539
|
+
return Object.fromEntries(Object.entries(options).flatMap(([name, option]) => {
|
|
540
|
+
if (option.type !== "boolean" || option.negative === undefined)
|
|
541
|
+
return [[name, option]];
|
|
542
|
+
return [...option.description === undefined ? [] : [[name, option]], [`no-${name}`, { type: "boolean", description: option.negative }]];
|
|
543
|
+
}));
|
|
544
|
+
}
|
|
545
|
+
function withBare(args, options) {
|
|
546
|
+
const bare = new Map(Object.entries(options).flatMap(([name, { short, optional }]) => optional === undefined ? [] : [[`--${name}`, optional], ...short === undefined ? [] : [[`-${short}`, optional]]]));
|
|
547
|
+
if (bare.size === 0)
|
|
548
|
+
return args;
|
|
549
|
+
const separator = args.indexOf("--");
|
|
550
|
+
const own = separator === -1 ? args : args.slice(0, separator);
|
|
551
|
+
return [...own.flatMap((arg, index) => {
|
|
552
|
+
const optional = bare.get(arg);
|
|
553
|
+
const next = own[index + 1];
|
|
554
|
+
const value = next !== undefined && (optional?.accepts === undefined ? !next.startsWith("-") : optional.accepts.test(next));
|
|
555
|
+
return optional === undefined || value ? [arg] : [arg, optional.bare];
|
|
556
|
+
}), ...separator === -1 ? [] : args.slice(separator)];
|
|
557
|
+
}
|
|
558
|
+
function checkConflicts(values, options) {
|
|
559
|
+
const given = (name) => values[name] !== undefined && values[name] !== false;
|
|
560
|
+
for (const [name, { conflicts = [] }] of Object.entries(options)) {
|
|
561
|
+
const other = given(name) ? conflicts.find(given) : undefined;
|
|
562
|
+
if (other !== undefined)
|
|
563
|
+
badUsage(`--${name} cannot be used with --${other}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function checkChoices(values, options) {
|
|
567
|
+
for (const [name, option] of Object.entries(options)) {
|
|
568
|
+
if (option.choices === undefined)
|
|
569
|
+
continue;
|
|
570
|
+
const given = values[name];
|
|
571
|
+
for (const value of Array.isArray(given) ? given : [given]) {
|
|
572
|
+
if (typeof value === "string")
|
|
573
|
+
oneOf(value, name, option.choices);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function oneOf(value, name, choices) {
|
|
578
|
+
if (!choices.includes(value))
|
|
579
|
+
badUsage(`${name.startsWith("<") ? name : `--${name}`} takes one of ${choices.join(", ")}, not ${value}`);
|
|
580
|
+
return value;
|
|
444
581
|
}
|
|
445
582
|
function checkPositionals(given, names) {
|
|
446
583
|
const variadic = names.at(-1)?.replace("]", "").endsWith("...") === true;
|
|
@@ -491,7 +628,7 @@ function nearestOption(message, options, allowNegative) {
|
|
|
491
628
|
}
|
|
492
629
|
// ../kit/src/process.ts
|
|
493
630
|
import { constants } from "os";
|
|
494
|
-
import
|
|
631
|
+
import process5 from "process";
|
|
495
632
|
var KILL_GRACE_MS = 2000;
|
|
496
633
|
function exitCodeOf(code, signal) {
|
|
497
634
|
if (code !== null)
|
|
@@ -511,7 +648,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
|
|
|
511
648
|
return;
|
|
512
649
|
try {
|
|
513
650
|
if (grouped)
|
|
514
|
-
|
|
651
|
+
process5.kill(-proc.pid, killing);
|
|
515
652
|
else
|
|
516
653
|
proc.kill(killing);
|
|
517
654
|
} catch {}
|
|
@@ -585,14 +722,103 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
|
|
|
585
722
|
var RESET_STYLE = `${ESC}[0m`;
|
|
586
723
|
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
587
724
|
// ../kit/src/cli.ts
|
|
588
|
-
import
|
|
725
|
+
import process7 from "process";
|
|
726
|
+
|
|
727
|
+
// ../kit/src/usage.ts
|
|
728
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
729
|
+
import { homedir } from "os";
|
|
730
|
+
import { dirname, isAbsolute, join } from "path";
|
|
731
|
+
import process6 from "process";
|
|
732
|
+
|
|
733
|
+
// ../kit/src/logs.ts
|
|
734
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
735
|
+
var CLI_FOLDER = "kaynooo-cli";
|
|
736
|
+
|
|
737
|
+
// ../kit/src/usage.ts
|
|
738
|
+
var UNKNOWN_OPTION2 = /unknown option '(--[a-z][a-z0-9-]*)[=']/;
|
|
739
|
+
var FLAG_NAME = /^(?:--[a-z][a-z0-9-]*|-[a-zA-Z])$/;
|
|
740
|
+
var LONGEST_NAME = 40;
|
|
741
|
+
var usageError;
|
|
742
|
+
function noteUsageError(message) {
|
|
743
|
+
usageError = message;
|
|
744
|
+
}
|
|
745
|
+
function stateHome(env = process6.env) {
|
|
746
|
+
const dir = env.XDG_STATE_HOME;
|
|
747
|
+
return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
|
|
748
|
+
}
|
|
749
|
+
function usagePath(env = process6.env) {
|
|
750
|
+
return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
751
|
+
}
|
|
752
|
+
function usageRecorded(env = process6.env) {
|
|
753
|
+
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
754
|
+
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
755
|
+
}
|
|
756
|
+
function flagNames(argv) {
|
|
757
|
+
const end = argv.indexOf("--");
|
|
758
|
+
const names = (end === -1 ? argv : argv.slice(0, end)).filter((word) => word.startsWith("-") && word !== "-").map((word) => word.includes("=") ? word.slice(0, word.indexOf("=")) : word);
|
|
759
|
+
return [...new Set(names)].filter((name) => name.length <= LONGEST_NAME && FLAG_NAME.test(name));
|
|
760
|
+
}
|
|
761
|
+
function usageEntry(bin, command, argv, code, now = new Date) {
|
|
762
|
+
const unknown = code === 2 && usageError !== undefined ? UNKNOWN_OPTION2.exec(usageError)?.[1] : undefined;
|
|
763
|
+
usageError = undefined;
|
|
764
|
+
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
765
|
+
}
|
|
766
|
+
function recordUsage(entry, env = process6.env) {
|
|
767
|
+
if (!usageRecorded(env))
|
|
768
|
+
return;
|
|
769
|
+
try {
|
|
770
|
+
const path = usagePath(env);
|
|
771
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
772
|
+
appendFileSync(path, `${JSON.stringify(entry)}
|
|
773
|
+
`, { mode: 384 });
|
|
774
|
+
} catch {}
|
|
775
|
+
}
|
|
776
|
+
async function readUsage(path = usagePath(), since = 0) {
|
|
777
|
+
const file = Bun.file(path);
|
|
778
|
+
if (!await file.exists())
|
|
779
|
+
return [];
|
|
780
|
+
return (await file.text()).split(`
|
|
781
|
+
`).flatMap((line) => {
|
|
782
|
+
try {
|
|
783
|
+
const entry = JSON.parse(line);
|
|
784
|
+
return Date.parse(entry.at) >= since ? [entry] : [];
|
|
785
|
+
} catch {
|
|
786
|
+
return [];
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
function summarizeUsage(entries) {
|
|
791
|
+
const bins = new Map;
|
|
792
|
+
const errors = new Map;
|
|
793
|
+
for (const entry of entries) {
|
|
794
|
+
const bin = bins.get(entry.bin) ?? { runs: 0, failed: 0, usageErrors: 0 };
|
|
795
|
+
bin.runs++;
|
|
796
|
+
bin.failed += entry.code === 0 ? 0 : 1;
|
|
797
|
+
bin.usageErrors += entry.code === 2 ? 1 : 0;
|
|
798
|
+
bins.set(entry.bin, bin);
|
|
799
|
+
if (entry.code === 2 && entry.unknown !== undefined) {
|
|
800
|
+
const key = `${entry.bin} ${entry.command ?? ""} ${entry.unknown}`;
|
|
801
|
+
const error = errors.get(key) ?? { bin: entry.bin, ...entry.command === undefined ? {} : { command: entry.command }, unknown: entry.unknown, count: 0 };
|
|
802
|
+
error.count++;
|
|
803
|
+
errors.set(key, error);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
runs: entries.length,
|
|
808
|
+
bins: [...bins].map(([bin, counts]) => ({ bin, ...counts })).toSorted((a, b) => b.runs - a.runs),
|
|
809
|
+
usageErrors: [...errors.values()].toSorted((a, b) => b.count - a.count)
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// ../kit/src/cli.ts
|
|
589
814
|
var LEVEL_OPTIONS = {
|
|
590
815
|
...OUTPUT_OPTIONS,
|
|
591
816
|
help: { type: "boolean", short: "h", description: "show this help" }
|
|
592
817
|
};
|
|
593
818
|
var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
|
|
594
|
-
async function commandRows(
|
|
595
|
-
const
|
|
819
|
+
async function commandRows(all, colors) {
|
|
820
|
+
const commands = shown(all);
|
|
821
|
+
const width = Math.max(0, ...Object.keys(commands).map((name) => name.length));
|
|
596
822
|
const groups = new Map([["Commands:", []]]);
|
|
597
823
|
const summaries = await Promise.all(Object.values(commands).map(async ({ summary }) => resolveSummary(summary)));
|
|
598
824
|
for (const [index, [name, { group }]] of Object.entries(commands).entries()) {
|
|
@@ -603,6 +829,9 @@ async function commandRows(commands, colors) {
|
|
|
603
829
|
}
|
|
604
830
|
return [...groups].filter(([, rows]) => rows.length > 0).flatMap(([heading, rows], index) => [...index === 0 ? [] : [""], colors.bold(heading), ...rows]);
|
|
605
831
|
}
|
|
832
|
+
function shown(commands) {
|
|
833
|
+
return Object.fromEntries(Object.entries(commands).filter(([, { hidden }]) => hidden !== true));
|
|
834
|
+
}
|
|
606
835
|
function takeOutputFlags(argv) {
|
|
607
836
|
let index = 0;
|
|
608
837
|
for (;index < argv.length; index++) {
|
|
@@ -634,7 +863,7 @@ async function expand(scope, words) {
|
|
|
634
863
|
}
|
|
635
864
|
return { ...scope, commands, lazy: [] };
|
|
636
865
|
}
|
|
637
|
-
async function
|
|
866
|
+
async function help2({ path, header, summary: lazySummary, commands, defaultCommand, examples, replaces }, stream) {
|
|
638
867
|
const colors = paint(stream);
|
|
639
868
|
const prefix = path.join(" ");
|
|
640
869
|
const summary = await resolveSummary(lazySummary);
|
|
@@ -661,8 +890,8 @@ async function help({ path, header, summary: lazySummary, commands, defaultComma
|
|
|
661
890
|
`);
|
|
662
891
|
}
|
|
663
892
|
function printError(prefix, message, hint, cause) {
|
|
664
|
-
const colors = paint(
|
|
665
|
-
const styled = takesControlCodes(
|
|
893
|
+
const colors = paint(process7.stderr);
|
|
894
|
+
const styled = takesControlCodes(process7.stderr);
|
|
666
895
|
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
667
896
|
const under = (label, text, tint) => text === undefined ? "" : `
|
|
668
897
|
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
@@ -681,6 +910,8 @@ function reportThrown(prefix, error, redact = (text) => text, json = false) {
|
|
|
681
910
|
const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
|
|
682
911
|
const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
|
|
683
912
|
const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
|
|
913
|
+
if (error instanceof UsageError)
|
|
914
|
+
noteUsageError(error.message);
|
|
684
915
|
const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
|
|
685
916
|
report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
|
|
686
917
|
return error instanceof Exit ? error.code : 1;
|
|
@@ -694,12 +925,20 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
694
925
|
return reportThrown(prefix, error);
|
|
695
926
|
}
|
|
696
927
|
const [name, ...args] = argv;
|
|
928
|
+
const fallback = level.defaultCommand === undefined ? undefined : level.commands[level.defaultCommand];
|
|
929
|
+
if (name === undefined && level.runBare && fallback !== undefined && "run" in fallback) {
|
|
930
|
+
try {
|
|
931
|
+
return await fallback.run([]);
|
|
932
|
+
} catch (error) {
|
|
933
|
+
return reportThrown(`${prefix} ${level.defaultCommand}`, error, redact, false);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
697
936
|
if (name === undefined && level.path.length > 1) {
|
|
698
|
-
console.error(await
|
|
937
|
+
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process7.stderr));
|
|
699
938
|
return 2;
|
|
700
939
|
}
|
|
701
940
|
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
702
|
-
console.log(await
|
|
941
|
+
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process7.stdout));
|
|
703
942
|
return 0;
|
|
704
943
|
}
|
|
705
944
|
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
@@ -708,6 +947,7 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
708
947
|
}
|
|
709
948
|
let command = level.commands[level.aliases?.[name] ?? name];
|
|
710
949
|
let rest = args;
|
|
950
|
+
let ran = name;
|
|
711
951
|
if (command === undefined && level.lazy.length > 0) {
|
|
712
952
|
level = await expand(level, [...level.path.slice(1), ...argv]);
|
|
713
953
|
command = level.commands[level.aliases?.[name] ?? name];
|
|
@@ -715,40 +955,77 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
715
955
|
if (command === undefined && level.defaultCommand !== undefined) {
|
|
716
956
|
command = level.commands[level.defaultCommand];
|
|
717
957
|
rest = argv;
|
|
958
|
+
ran = level.defaultCommand;
|
|
718
959
|
}
|
|
719
960
|
if (command === undefined) {
|
|
720
|
-
const meant = closest(name, Object.keys(level.commands));
|
|
961
|
+
const meant = closest(name, Object.keys(shown(level.commands)));
|
|
721
962
|
printError(prefix, `no command ${name}`, meant === undefined ? `run ${prefix} --help for its commands` : `did you mean ${meant}?`);
|
|
722
963
|
return 2;
|
|
723
964
|
}
|
|
724
965
|
if ("commands" in command) {
|
|
725
966
|
const { summary } = command;
|
|
726
|
-
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 });
|
|
967
|
+
return dispatch({ path: [...level.path, name], header: (colors, text) => `${colors.bold(`${prefix} ${name}`)} ${text}`, summary, ...scopeOf(command.commands, command.extraCommands, command.aliases), defaultCommand: undefined, runBare: false, examples: command.examples, replaces: undefined }, rest, { version, redact });
|
|
727
968
|
}
|
|
728
969
|
try {
|
|
729
970
|
return await command.run(rest);
|
|
730
971
|
} catch (error) {
|
|
731
|
-
return reportThrown(`${prefix} ${
|
|
972
|
+
return reportThrown(`${prefix} ${ran}`, error, redact, wantsJson(rest));
|
|
732
973
|
}
|
|
733
974
|
}
|
|
734
975
|
var COMPLETE = "__complete";
|
|
735
976
|
var FILES = "__files__";
|
|
736
|
-
async function
|
|
977
|
+
async function completingOf(run) {
|
|
737
978
|
completion.active = true;
|
|
738
979
|
try {
|
|
739
980
|
await run([]);
|
|
740
981
|
} catch (error) {
|
|
741
982
|
if (error instanceof Completing)
|
|
742
|
-
return error
|
|
983
|
+
return error;
|
|
743
984
|
} finally {
|
|
744
985
|
completion.active = false;
|
|
745
986
|
}
|
|
746
|
-
return {};
|
|
987
|
+
return new Completing({});
|
|
988
|
+
}
|
|
989
|
+
async function optionsOf(run) {
|
|
990
|
+
return (await completingOf(run)).options;
|
|
991
|
+
}
|
|
992
|
+
function candidateLine(candidate) {
|
|
993
|
+
return typeof candidate === "string" ? candidate : candidate.join("\t");
|
|
994
|
+
}
|
|
995
|
+
function positionalBeingTyped(before, { options, positionals }) {
|
|
996
|
+
const takesValue = (word) => !word.includes("=") && Object.entries(options).some(([name, { short, type }]) => type === "string" && (word === `--${name}` || word === `-${short}`));
|
|
997
|
+
let index = 0;
|
|
998
|
+
let at = 0;
|
|
999
|
+
while (at < before.length) {
|
|
1000
|
+
const word = before[at];
|
|
1001
|
+
if (word === "--") {
|
|
1002
|
+
index += before.length - at - 1;
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
if (!word.startsWith("-"))
|
|
1006
|
+
index++;
|
|
1007
|
+
else if (takesValue(word))
|
|
1008
|
+
at++;
|
|
1009
|
+
at++;
|
|
1010
|
+
}
|
|
1011
|
+
const last = positionals.at(-1);
|
|
1012
|
+
if (index < positionals.length)
|
|
1013
|
+
return positionals[index];
|
|
1014
|
+
return last?.replace("]", "").endsWith("...") === true ? last : undefined;
|
|
747
1015
|
}
|
|
748
1016
|
async function leafCandidates(run, words) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
1017
|
+
const completing = await completingOf(run);
|
|
1018
|
+
if ((words.at(-1) ?? "").startsWith("-"))
|
|
1019
|
+
return Object.entries(completing.options).map(([name, option]) => option.description === undefined ? `--${name}` : `--${name} ${option.description}`);
|
|
1020
|
+
const flag = words.at(-2);
|
|
1021
|
+
const option = flag?.startsWith("-") === true ? Object.entries(completing.options).find(([name, { short }]) => flag === `--${name}` || flag === `-${short}`)?.[1] : undefined;
|
|
1022
|
+
if (option?.type === "string") {
|
|
1023
|
+
const values = option.choices === undefined ? await option.candidates?.() : option.choices;
|
|
1024
|
+
return values === undefined ? [FILES] : values.map(candidateLine);
|
|
1025
|
+
}
|
|
1026
|
+
const name = positionalBeingTyped(words.slice(0, -1), completing);
|
|
1027
|
+
const values = name === undefined ? undefined : await completing.candidates[name]?.();
|
|
1028
|
+
return values === undefined ? [FILES] : values.map(candidateLine);
|
|
752
1029
|
}
|
|
753
1030
|
async function candidates(scope, defaultCommand, words, before = []) {
|
|
754
1031
|
const [word = "", ...rest] = words;
|
|
@@ -759,8 +1036,13 @@ async function candidates(scope, defaultCommand, words, before = []) {
|
|
|
759
1036
|
({ commands, aliases } = await expand(scope, [...before, ...words]));
|
|
760
1037
|
command = commands[aliases?.[word] ?? word];
|
|
761
1038
|
}
|
|
762
|
-
if (listing)
|
|
763
|
-
|
|
1039
|
+
if (listing) {
|
|
1040
|
+
const own = await Promise.all(Object.entries(shown(commands)).map(async ([name, { summary }]) => `${name} ${await resolveSummary(summary)}`));
|
|
1041
|
+
const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
|
|
1042
|
+
const values = fallback !== undefined && "run" in fallback ? await leafCandidates(fallback.run, [word]) : [];
|
|
1043
|
+
const valueOf = (line) => line.slice(0, line.includes("\t") ? line.indexOf("\t") : undefined);
|
|
1044
|
+
return [...own, ...values.filter((line) => line !== FILES && !(valueOf(line) in commands))];
|
|
1045
|
+
}
|
|
764
1046
|
if (command === undefined) {
|
|
765
1047
|
const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
|
|
766
1048
|
return fallback !== undefined && "run" in fallback ? leafCandidates(fallback.run, words) : [];
|
|
@@ -772,26 +1054,49 @@ async function printCandidates(found) {
|
|
|
772
1054
|
console.log(line);
|
|
773
1055
|
return 0;
|
|
774
1056
|
}
|
|
775
|
-
async function runCli(cli, argv =
|
|
1057
|
+
async function runCli(cli, argv = process7.argv.slice(2)) {
|
|
776
1058
|
if (argv[0] === COMPLETE)
|
|
777
1059
|
return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
|
|
778
|
-
|
|
1060
|
+
const code = await 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, runBare: cli.runBare === true, examples: cli.examples, replaces: cli.replaces }, argv, { version: cli.version, redact: cli.redact });
|
|
1061
|
+
if (cli.usage !== false) {
|
|
1062
|
+
try {
|
|
1063
|
+
recordUsage(usageEntry(cli.name, commandPath(scopeOf(cli.commands, cli.extraCommands, cli.aliases), argv), argv, code));
|
|
1064
|
+
} catch {}
|
|
1065
|
+
}
|
|
1066
|
+
return code;
|
|
1067
|
+
}
|
|
1068
|
+
function commandPath(scope, argv) {
|
|
1069
|
+
const path = [];
|
|
1070
|
+
let level = scope;
|
|
1071
|
+
for (const word of argv.filter((each) => !each.startsWith("-"))) {
|
|
1072
|
+
const name = level.aliases !== undefined && Object.hasOwn(level.aliases, word) ? level.aliases[word] ?? word : word;
|
|
1073
|
+
const command = Object.hasOwn(level.commands, name) ? level.commands[name] : undefined;
|
|
1074
|
+
if (command === undefined)
|
|
1075
|
+
break;
|
|
1076
|
+
path.push(word);
|
|
1077
|
+
if (!("commands" in command))
|
|
1078
|
+
break;
|
|
1079
|
+
level = scopeOf(command.commands, command.extraCommands, command.aliases);
|
|
1080
|
+
}
|
|
1081
|
+
return path.length === 0 ? undefined : path.join(" ");
|
|
779
1082
|
}
|
|
780
|
-
async function runMain({ name, version, run, redact }, argv =
|
|
1083
|
+
async function runMain({ name, version, run, redact, usage }, argv = process7.argv.slice(2)) {
|
|
781
1084
|
if (argv[0] === COMPLETE)
|
|
782
1085
|
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
783
1086
|
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
784
1087
|
console.log(version);
|
|
785
1088
|
return 0;
|
|
786
1089
|
}
|
|
1090
|
+
let code;
|
|
787
1091
|
try {
|
|
788
|
-
|
|
1092
|
+
code = await run(argv);
|
|
789
1093
|
} catch (error) {
|
|
790
|
-
|
|
1094
|
+
code = reportThrown(name, error, redact, wantsJson(argv));
|
|
791
1095
|
}
|
|
1096
|
+
if (usage !== false)
|
|
1097
|
+
recordUsage(usageEntry(name, undefined, argv, code));
|
|
1098
|
+
return code;
|
|
792
1099
|
}
|
|
793
|
-
// ../kit/src/logs.ts
|
|
794
|
-
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
795
1100
|
// ../kit/src/secrets.ts
|
|
796
1101
|
var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
|
|
797
1102
|
// ../kit/src/sessions.ts
|
|
@@ -799,29 +1104,12 @@ var {Glob } = globalThis.Bun;
|
|
|
799
1104
|
var SESSION_FILES = new Glob("*.json");
|
|
800
1105
|
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
801
1106
|
// package.json
|
|
802
|
-
var version = "0.
|
|
1107
|
+
var version = "0.4.0";
|
|
803
1108
|
|
|
804
|
-
// src/
|
|
805
|
-
import {
|
|
806
|
-
import { join } from "path";
|
|
807
|
-
import
|
|
808
|
-
var RUN_TIMEOUT_MS = 20000;
|
|
809
|
-
function hostHere() {
|
|
810
|
-
const home = homedir();
|
|
811
|
-
return {
|
|
812
|
-
home,
|
|
813
|
-
globalModules: join(process6.env.BUN_INSTALL ?? join(home, ".bun"), "install", "global", "node_modules"),
|
|
814
|
-
bunVersion: Bun.version,
|
|
815
|
-
which: (bin) => Bun.which(bin),
|
|
816
|
-
run: async (argv, env) => {
|
|
817
|
-
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process6.env, ...env } } });
|
|
818
|
-
if (ran.timedOut)
|
|
819
|
-
return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
|
|
820
|
-
const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
|
|
821
|
-
return { code: ran.code, stdout: of("stdout"), stderr: of("stderr") };
|
|
822
|
-
}
|
|
823
|
-
};
|
|
824
|
-
}
|
|
1109
|
+
// src/completions.ts
|
|
1110
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync } from "fs";
|
|
1111
|
+
import { join as join3 } from "path";
|
|
1112
|
+
import process9 from "process";
|
|
825
1113
|
|
|
826
1114
|
// src/checks.ts
|
|
827
1115
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -840,8 +1128,14 @@ var TOOLS = {
|
|
|
840
1128
|
"@kaynooo/cli-gitlab": ["gl"],
|
|
841
1129
|
"@kaynooo/cli-guard": ["guard"],
|
|
842
1130
|
"@kaynooo/cli-out": ["out"],
|
|
1131
|
+
"@kaynooo/cli-dev": ["dev"],
|
|
1132
|
+
"@kaynooo/cli-pick": ["pick"],
|
|
843
1133
|
"@kaynooo/cli-pkg": ["pkg"],
|
|
1134
|
+
"@kaynooo/cli-probe": ["probe"],
|
|
1135
|
+
"@kaynooo/cli-repos": ["repos"],
|
|
844
1136
|
"@kaynooo/cli-stack": ["stack"],
|
|
1137
|
+
"@kaynooo/cli-sweep": ["sweep"],
|
|
1138
|
+
"@kaynooo/cli-view": ["view"],
|
|
845
1139
|
"@kaynooo/cli-wait": ["waitfor", "slot"]
|
|
846
1140
|
};
|
|
847
1141
|
var FORGE = "git.kaynooo.fr";
|
|
@@ -927,12 +1221,82 @@ function checkBun(host) {
|
|
|
927
1221
|
return { check: "bun", ok: false, detail: `${host.bunVersion}, the tools ask for ${[...new Set(unmet)].join(" and ")}`, fix: "bun upgrade" };
|
|
928
1222
|
return { check: "bun", ok: true, detail: floors.length === 0 ? host.bunVersion : `${host.bunVersion}, the tools ask for ${[...new Set(floors)].join(" and ")}` };
|
|
929
1223
|
}
|
|
1224
|
+
function checkCompletions(host) {
|
|
1225
|
+
if (host.which("fish") === null)
|
|
1226
|
+
return { check: "completions", ok: true, detail: "fish is not installed" };
|
|
1227
|
+
const dir = completionDir(host.home);
|
|
1228
|
+
const missing = missingCompletions(host, dir);
|
|
1229
|
+
if (missing.length > 0)
|
|
1230
|
+
return { check: "completions", ok: false, detail: `no fish completion in ${dir} for ${missing.join(", ")}`, fix: "cli completions fish --install" };
|
|
1231
|
+
return { check: "completions", ok: true, detail: dir };
|
|
1232
|
+
}
|
|
930
1233
|
async function runChecks(host) {
|
|
931
1234
|
const entries = Object.entries(TOOLS);
|
|
932
1235
|
const tools = [];
|
|
933
1236
|
for (let at = 0;at < entries.length; at += INFO_AT_ONCE)
|
|
934
1237
|
tools.push(...await Promise.all(entries.slice(at, at + INFO_AT_ONCE).map(async ([name, bins]) => checkTool(host, name, bins))));
|
|
935
|
-
return [...tools, await checkTea(host), checkHooks(host), checkBun(host)];
|
|
1238
|
+
return [...tools, await checkTea(host), checkHooks(host), checkBun(host), checkCompletions(host)];
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// src/completions.ts
|
|
1242
|
+
var MARKER = "# Generated by `cli completions fish`";
|
|
1243
|
+
var FISH_FILE = /\.fish$/;
|
|
1244
|
+
var FUNCTION = `functions -q __kaynooo_cli_complete; or function __kaynooo_cli_complete --argument-names bin --description 'what a @kaynooo/cli-* bin offers for the word being typed'
|
|
1245
|
+
set -l tokens (commandline -xpc 2>/dev/null; or commandline -opc)
|
|
1246
|
+
set -l current (commandline -ct)
|
|
1247
|
+
for line in ($bin __complete $tokens[2..-1] $current 2>/dev/null)
|
|
1248
|
+
if test "$line" = __files__
|
|
1249
|
+
__fish_complete_path $current
|
|
1250
|
+
else
|
|
1251
|
+
echo $line
|
|
1252
|
+
end
|
|
1253
|
+
end
|
|
1254
|
+
end`;
|
|
1255
|
+
function completionDir(home, env = process9.env) {
|
|
1256
|
+
return join3(env.XDG_CACHE_HOME ?? join3(home, ".cache"), "fish", "kaynooo-cli");
|
|
1257
|
+
}
|
|
1258
|
+
function installedBins(host) {
|
|
1259
|
+
return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
|
|
1260
|
+
}
|
|
1261
|
+
function completionFile(bin) {
|
|
1262
|
+
return `${MARKER}
|
|
1263
|
+
${FUNCTION}
|
|
1264
|
+
complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
|
|
1265
|
+
`;
|
|
1266
|
+
}
|
|
1267
|
+
async function installCompletions(host, dir) {
|
|
1268
|
+
const bins = installedBins(host);
|
|
1269
|
+
mkdirSync2(dir, { recursive: true });
|
|
1270
|
+
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync2(join3(dir, name), "utf8").startsWith(MARKER));
|
|
1271
|
+
for (const name of removed)
|
|
1272
|
+
rmSync(join3(dir, name));
|
|
1273
|
+
await Promise.all(bins.map(async (bin) => Bun.write(join3(dir, `${bin}.fish`), completionFile(bin))));
|
|
1274
|
+
return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
|
|
1275
|
+
}
|
|
1276
|
+
function missingCompletions(host, dir) {
|
|
1277
|
+
return installedBins(host).filter((bin) => !existsSync2(join3(dir, `${bin}.fish`)));
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/host.ts
|
|
1281
|
+
import { homedir as homedir2 } from "os";
|
|
1282
|
+
import { join as join4 } from "path";
|
|
1283
|
+
import process10 from "process";
|
|
1284
|
+
var RUN_TIMEOUT_MS = 20000;
|
|
1285
|
+
function hostHere() {
|
|
1286
|
+
const home = homedir2();
|
|
1287
|
+
return {
|
|
1288
|
+
home,
|
|
1289
|
+
globalModules: join4(process10.env.BUN_INSTALL ?? join4(home, ".bun"), "install", "global", "node_modules"),
|
|
1290
|
+
bunVersion: Bun.version,
|
|
1291
|
+
which: (bin) => Bun.which(bin),
|
|
1292
|
+
run: async (argv, env) => {
|
|
1293
|
+
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process10.env, ...env } } });
|
|
1294
|
+
if (ran.timedOut)
|
|
1295
|
+
return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
|
|
1296
|
+
const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
|
|
1297
|
+
return { code: ran.code, stdout: of("stdout"), stderr: of("stderr") };
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
936
1300
|
}
|
|
937
1301
|
|
|
938
1302
|
// src/list.ts
|
|
@@ -960,7 +1324,7 @@ var LIST_OPTIONS = {
|
|
|
960
1324
|
json: jsonOption("[{bin, package, installed, synopsis?, summary?, replaces?, examples?}]")
|
|
961
1325
|
};
|
|
962
1326
|
function print(listed) {
|
|
963
|
-
const { cyan, dim } = paint(
|
|
1327
|
+
const { cyan, dim } = paint(process11.stdout);
|
|
964
1328
|
const width = Math.max(...listed.map((each) => each.bin.length));
|
|
965
1329
|
for (const each of listed) {
|
|
966
1330
|
const what = !each.installed ? dim(`not installed: bun install -g ${each.package}`) : `${each.summary ?? ""}${each.replaces === undefined ? "" : dim(`; instead of ${each.replaces}`)}`;
|
|
@@ -976,18 +1340,73 @@ async function list(args, host = hostHere()) {
|
|
|
976
1340
|
print(listed);
|
|
977
1341
|
return 0;
|
|
978
1342
|
}
|
|
1343
|
+
var COMPLETIONS_USAGE = `cli completions fish [--install] [--dir <folder>]: fish completions for every installed bin of the @kaynooo/cli-* packages
|
|
1344
|
+
|
|
1345
|
+
Each bin completes itself through \`<bin> __complete\`; this writes the one line fish needs per bin. Without --install the files print on stdout, with it they go to ~/.cache/fish/kaynooo-cli (or $XDG_CACHE_HOME), one <bin>.fish each, the stale ones this command wrote taken out. That folder has to be in fish_complete_path, first so a completion fish ships for the same name does not win: \`set -p fish_complete_path ~/.cache/fish/kaynooo-cli\`.
|
|
1346
|
+
Exits 0 when it printed or wrote them, 2 on a usage error.`;
|
|
1347
|
+
var COMPLETIONS_OPTIONS = {
|
|
1348
|
+
install: { type: "boolean", description: "write the files to the folder instead of printing them" },
|
|
1349
|
+
dir: { type: "string", short: "d", description: "the folder to write to, instead of ~/.cache/fish/kaynooo-cli" }
|
|
1350
|
+
};
|
|
1351
|
+
async function completions(args, host = hostHere()) {
|
|
1352
|
+
const { values, positionals } = parse(args, COMPLETIONS_USAGE, COMPLETIONS_OPTIONS, { positionals: ["<shell>"], candidates: { "<shell>": () => ["fish"] }, examples: ["cli completions fish", ["cli completions fish --install", "write one file per bin"]] });
|
|
1353
|
+
if (positionals[0] !== "fish")
|
|
1354
|
+
badUsage("only fish is supported: cli completions fish");
|
|
1355
|
+
if (values.install !== true) {
|
|
1356
|
+
console.log(installedBins(host).map(completionFile).join(`
|
|
1357
|
+
`));
|
|
1358
|
+
return 0;
|
|
1359
|
+
}
|
|
1360
|
+
const dir = values.dir ?? completionDir(host.home);
|
|
1361
|
+
const { written, removed } = await installCompletions(host, dir);
|
|
1362
|
+
console.log(`wrote ${written.length} completions to ${dir}${removed.length === 0 ? "" : `, took out ${removed.join(", ")}`}`);
|
|
1363
|
+
return 0;
|
|
1364
|
+
}
|
|
1365
|
+
var STATS_USAGE = `cli stats [--since 7d] [--json]: how often each bin ran, and the usage errors by the option it did not know
|
|
1366
|
+
|
|
1367
|
+
Every bin records each run in ~/.local/state/kaynooo-cli/usage.jsonl (or $XDG_STATE_HOME): the bin, its command, the names of the flags it saw (never a value or an argument), the exit code and, on a usage error, the unknown option. Set KAYNOOO_CLI_NO_USAGE=1 to stop recording; \`guard\` never records.
|
|
1368
|
+
Exits 0 when it printed the summary, 2 on a usage error.`;
|
|
1369
|
+
var STATS_OPTIONS = {
|
|
1370
|
+
since: { type: "string", description: "only the runs this recent (7d, 12h)" },
|
|
1371
|
+
json: jsonOption("{runs, bins: [{bin, runs, failed, usageErrors}], usageErrors: [{bin, command?, unknown, count}]}")
|
|
1372
|
+
};
|
|
1373
|
+
async function stats(args, path = usagePath()) {
|
|
1374
|
+
const { values } = parse(args, STATS_USAGE, STATS_OPTIONS, { positionals: [], examples: ["cli stats", ["cli stats --since 7d", "the last week"]] });
|
|
1375
|
+
const window = values.since === undefined ? undefined : duration(values.since, "since", 0, "h");
|
|
1376
|
+
const summary = summarizeUsage(await readUsage(path, window === undefined ? 0 : Date.now() - window));
|
|
1377
|
+
if (values.json === true) {
|
|
1378
|
+
printJson(summary);
|
|
1379
|
+
return 0;
|
|
1380
|
+
}
|
|
1381
|
+
if (summary.runs === 0) {
|
|
1382
|
+
console.log("no run recorded");
|
|
1383
|
+
return 0;
|
|
1384
|
+
}
|
|
1385
|
+
console.log(`${summary.runs} runs
|
|
1386
|
+
|
|
1387
|
+
${table(summary.bins.map((each) => [each.bin, String(each.runs), `${each.failed} failed`, `${each.usageErrors} usage errors`]))}`);
|
|
1388
|
+
if (summary.usageErrors.length > 0)
|
|
1389
|
+
console.log(`
|
|
1390
|
+
unknown options
|
|
1391
|
+
${table(summary.usageErrors.map((each) => [`${each.bin}${each.command === undefined ? "" : ` ${each.command}`}`, each.unknown, `${each.count}x`]))}`);
|
|
1392
|
+
return 0;
|
|
1393
|
+
}
|
|
979
1394
|
if (import.meta.main) {
|
|
980
|
-
|
|
1395
|
+
process11.exitCode = await runCli({
|
|
981
1396
|
name: "cli",
|
|
982
1397
|
version,
|
|
983
1398
|
about: "the bins of the @kaynooo/cli-* packages, described by their own help",
|
|
984
1399
|
replaces: "running each bin's --help to find the one that fits",
|
|
985
1400
|
examples: ["cli list", ["cli list --json", "with each bin's synopsis and examples"]],
|
|
986
1401
|
commands: {
|
|
987
|
-
list: { summary: "every bin in one line: what it does and what it is reached for instead of", run: async (args) => list(args) }
|
|
1402
|
+
list: { summary: "every bin in one line: what it does and what it is reached for instead of", run: async (args) => list(args) },
|
|
1403
|
+
stats: { summary: "how often each bin ran and the options usage errors did not know", run: async (args) => stats(args) },
|
|
1404
|
+
completions: { summary: "fish completions for every installed bin, printed or written to the folder fish reads", run: async (args) => completions(args) }
|
|
988
1405
|
}
|
|
989
1406
|
});
|
|
990
1407
|
}
|
|
991
1408
|
export {
|
|
992
|
-
|
|
1409
|
+
completions,
|
|
1410
|
+
list,
|
|
1411
|
+
stats
|
|
993
1412
|
};
|