@kaynooo/cli-doctor 0.3.0 → 0.5.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 +27 -1
- package/dist/cli-bin.js +398 -114
- package/dist/cli.js +431 -118
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import process12 from "process";
|
|
7
6
|
// ../kit/src/args.ts
|
|
8
|
-
import
|
|
7
|
+
import process5 from "process";
|
|
9
8
|
import { parseArgs } from "util";
|
|
10
9
|
|
|
10
|
+
// ../kit/src/format.ts
|
|
11
|
+
import process3 from "process";
|
|
12
|
+
|
|
11
13
|
// ../kit/src/style.ts
|
|
12
|
-
import
|
|
14
|
+
import process2 from "process";
|
|
13
15
|
|
|
14
16
|
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
|
|
15
17
|
function x(s) {
|
|
@@ -273,10 +275,10 @@ function le(i = {}) {
|
|
|
273
275
|
var t = le();
|
|
274
276
|
|
|
275
277
|
// ../kit/src/style.ts
|
|
276
|
-
function takesControlCodes(stream, env =
|
|
278
|
+
function takesControlCodes(stream, env = process2.env) {
|
|
277
279
|
return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
|
|
278
280
|
}
|
|
279
|
-
function paint(stream, env =
|
|
281
|
+
function paint(stream, env = process2.env) {
|
|
280
282
|
return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
|
|
281
283
|
}
|
|
282
284
|
var STATUSES = {
|
|
@@ -293,11 +295,110 @@ function upTo(text, separator) {
|
|
|
293
295
|
const end = text.indexOf(separator);
|
|
294
296
|
return end === -1 ? text : text.slice(0, end);
|
|
295
297
|
}
|
|
298
|
+
function truncate(text, width) {
|
|
299
|
+
return text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
300
|
+
}
|
|
301
|
+
var LINE_BREAKS = /\s*\n\s*/g;
|
|
296
302
|
var ESCAPE = String.fromCharCode(27);
|
|
303
|
+
function textWidth(text) {
|
|
304
|
+
return Bun.stringWidth(text);
|
|
305
|
+
}
|
|
306
|
+
function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
|
|
307
|
+
const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
|
|
308
|
+
const widths = [];
|
|
309
|
+
for (const row of cells) {
|
|
310
|
+
for (const [index, cell] of row.entries())
|
|
311
|
+
widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
|
|
312
|
+
}
|
|
313
|
+
const shown = fit === undefined ? cells : narrow(cells, widths, fit);
|
|
314
|
+
const colors = paint(process3.stdout);
|
|
315
|
+
const lines = shown.map((row) => row.map((cell, index) => {
|
|
316
|
+
const width = widths[index] ?? 0;
|
|
317
|
+
const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
|
|
318
|
+
if (align[index] === "right")
|
|
319
|
+
return `${gap}${cell}`;
|
|
320
|
+
return index === row.length - 1 ? cell : `${cell}${gap}`;
|
|
321
|
+
}).join(" ").trimEnd());
|
|
322
|
+
return lines.map((line, index) => header && index === 0 ? colors.bold(line) : line).join(`
|
|
323
|
+
`);
|
|
324
|
+
}
|
|
325
|
+
function narrow(rows, widths, limit) {
|
|
326
|
+
const used = () => widths.reduce((sum, width) => sum + width, 0) + 2 * Math.max(0, widths.length - 1);
|
|
327
|
+
const cuttable = (index) => rows.every((row) => !(row[index] ?? "").includes(ESCAPE));
|
|
328
|
+
const floor = 8;
|
|
329
|
+
while (used() > limit) {
|
|
330
|
+
let widest = -1;
|
|
331
|
+
for (const [index, width] of widths.entries()) {
|
|
332
|
+
if (cuttable(index) && width > floor && width > (widths[widest] ?? 0))
|
|
333
|
+
widest = index;
|
|
334
|
+
}
|
|
335
|
+
if (widest === -1)
|
|
336
|
+
break;
|
|
337
|
+
widths[widest] = Math.max(floor, (widths[widest] ?? 0) - (used() - limit));
|
|
338
|
+
}
|
|
339
|
+
return rows.map((row) => row.map((cell, index) => textWidth(cell) > (widths[index] ?? 0) ? truncate(cell, widths[index] ?? 0) : cell));
|
|
340
|
+
}
|
|
297
341
|
function printJson(value) {
|
|
298
342
|
console.log(JSON.stringify(value, undefined, 2));
|
|
299
343
|
}
|
|
300
344
|
|
|
345
|
+
// ../kit/src/help.ts
|
|
346
|
+
import process4 from "process";
|
|
347
|
+
var OPTIONAL = /\[[^\]]+\]/g;
|
|
348
|
+
var USAGE_PREFIX = /^usage:\s*/i;
|
|
349
|
+
var FULL_STOP = /\.$/;
|
|
350
|
+
function usageLine(usage, colors = paint(process4.stdout)) {
|
|
351
|
+
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
352
|
+
}
|
|
353
|
+
function helpText(usage, options, colors = paint(process4.stdout), { examples = [], replaces, exits = [] } = {}) {
|
|
354
|
+
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
|
|
355
|
+
`);
|
|
356
|
+
}
|
|
357
|
+
function exitRows(exits, colors = paint(process4.stdout)) {
|
|
358
|
+
if (exits.length === 0)
|
|
359
|
+
return [];
|
|
360
|
+
const width = Math.max(...exits.map(([code]) => String(code).length));
|
|
361
|
+
return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
|
|
362
|
+
}
|
|
363
|
+
function replacesRows(replaces, colors = paint(process4.stdout)) {
|
|
364
|
+
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
365
|
+
}
|
|
366
|
+
function wantsHelpJson(env = process4.env) {
|
|
367
|
+
return env.KIT_HELP === "json";
|
|
368
|
+
}
|
|
369
|
+
function description(usage, { examples = [], replaces, exits } = {}, options) {
|
|
370
|
+
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
371
|
+
`);
|
|
372
|
+
const first = lines[0];
|
|
373
|
+
const colon = first.indexOf(": ");
|
|
374
|
+
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
375
|
+
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
376
|
+
return {
|
|
377
|
+
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
378
|
+
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
379
|
+
...replaces === undefined ? {} : { replaces },
|
|
380
|
+
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] }),
|
|
381
|
+
...exits === undefined ? {} : { exits: exits.map(([code, meaning]) => ({ code, meaning })) },
|
|
382
|
+
...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function exampleRows(examples, colors = paint(process4.stdout)) {
|
|
386
|
+
if (examples.length === 0)
|
|
387
|
+
return [];
|
|
388
|
+
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
389
|
+
}
|
|
390
|
+
function optionRows(options, colors = paint(process4.stdout)) {
|
|
391
|
+
const rows = Object.entries(options).map(([name, option]) => {
|
|
392
|
+
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
393
|
+
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
394
|
+
const placeholder = option.optional === undefined ? named : `[${named}]`;
|
|
395
|
+
const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
|
|
396
|
+
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
|
|
397
|
+
});
|
|
398
|
+
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
399
|
+
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
400
|
+
}
|
|
401
|
+
|
|
301
402
|
// ../kit/src/text.ts
|
|
302
403
|
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
|
|
303
404
|
function distance(a, b) {
|
|
@@ -317,8 +418,21 @@ function closest(name, names) {
|
|
|
317
418
|
const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
|
|
318
419
|
return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
|
|
319
420
|
}
|
|
421
|
+
// ../kit/src/input.ts
|
|
422
|
+
var DURATION = /^(\d+(?:\.\d+)?)(ms|[smhd])?$/;
|
|
423
|
+
var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
424
|
+
function duration(value, name, fallback, unit = "s") {
|
|
425
|
+
if (value === undefined)
|
|
426
|
+
return fallback;
|
|
427
|
+
const match = DURATION.exec(value.trim());
|
|
428
|
+
const ms = match === null ? 0 : Number(match[1]) * UNIT_MS[match[2] ?? unit];
|
|
429
|
+
if (ms < 1)
|
|
430
|
+
badUsage(`--${name} takes a duration such as 30s, 5m, 1h or 7d, not ${value}`);
|
|
431
|
+
return ms;
|
|
432
|
+
}
|
|
320
433
|
|
|
321
434
|
// ../kit/src/args.ts
|
|
435
|
+
var DASHES = /^-+/;
|
|
322
436
|
function jsonOption(holds) {
|
|
323
437
|
return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
|
|
324
438
|
}
|
|
@@ -358,58 +472,11 @@ class Completing {
|
|
|
358
472
|
}
|
|
359
473
|
}
|
|
360
474
|
var completion = { active: false };
|
|
361
|
-
var OPTIONAL = /\[[^\]]+\]/g;
|
|
362
|
-
var USAGE_PREFIX = /^usage:\s*/i;
|
|
363
|
-
var FULL_STOP = /\.$/;
|
|
364
|
-
var DASHES = /^-+/;
|
|
365
|
-
function usageLine(usage, colors = paint(process2.stdout)) {
|
|
366
|
-
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
367
|
-
}
|
|
368
|
-
function helpText(usage, options, colors = paint(process2.stdout), { examples = [], replaces } = {}) {
|
|
369
|
-
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
370
|
-
`);
|
|
371
|
-
}
|
|
372
|
-
function replacesRows(replaces, colors = paint(process2.stdout)) {
|
|
373
|
-
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
374
|
-
}
|
|
375
|
-
function wantsHelpJson(env = process2.env) {
|
|
376
|
-
return env.KIT_HELP === "json";
|
|
377
|
-
}
|
|
378
|
-
function description(usage, { examples = [], replaces } = {}) {
|
|
379
|
-
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
380
|
-
`);
|
|
381
|
-
const first = lines[0];
|
|
382
|
-
const colon = first.indexOf(": ");
|
|
383
|
-
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
384
|
-
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
385
|
-
return {
|
|
386
|
-
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
387
|
-
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
388
|
-
...replaces === undefined ? {} : { replaces },
|
|
389
|
-
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
function exampleRows(examples, colors = paint(process2.stdout)) {
|
|
393
|
-
if (examples.length === 0)
|
|
394
|
-
return [];
|
|
395
|
-
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
396
|
-
}
|
|
397
|
-
function optionRows(options, colors = paint(process2.stdout)) {
|
|
398
|
-
const rows = Object.entries(options).map(([name, option]) => {
|
|
399
|
-
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
400
|
-
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
401
|
-
const placeholder = option.optional === undefined ? named : `[${named}]`;
|
|
402
|
-
const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
|
|
403
|
-
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
|
|
404
|
-
});
|
|
405
|
-
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
406
|
-
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
407
|
-
}
|
|
408
475
|
var OUTPUT_OPTIONS = {
|
|
409
476
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
410
477
|
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
411
478
|
};
|
|
412
|
-
function applyOutput(color, plain, env =
|
|
479
|
+
function applyOutput(color, plain, env = process5.env) {
|
|
413
480
|
const when = plain === true ? "never" : color ?? "auto";
|
|
414
481
|
if (when === "auto")
|
|
415
482
|
return;
|
|
@@ -432,7 +499,7 @@ function parse(args, usage, options, settings = {}) {
|
|
|
432
499
|
const { own, rest } = settings.trailing === true ? splitTrailing(args, all, settings.positionals?.length ?? 0) : splitRest(args, settings.rest === true);
|
|
433
500
|
let parsed;
|
|
434
501
|
try {
|
|
435
|
-
parsed = parseArgs({ args: joinDashValues(withBare(own, all), all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
502
|
+
parsed = parseArgs({ args: joinDashValues(withBare(withAliases(own, all), all), all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
436
503
|
} catch (error) {
|
|
437
504
|
const message = parseError(error);
|
|
438
505
|
throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
|
|
@@ -440,7 +507,7 @@ function parse(args, usage, options, settings = {}) {
|
|
|
440
507
|
const values = parsed.values;
|
|
441
508
|
applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
|
|
442
509
|
if (values.help === true) {
|
|
443
|
-
console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings)) : helpText(usage, listing, undefined, settings));
|
|
510
|
+
console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings, listing)) : helpText(usage, listing, undefined, settings));
|
|
444
511
|
throw new HelpShown;
|
|
445
512
|
}
|
|
446
513
|
checkChoices(parsed.values, all);
|
|
@@ -482,6 +549,18 @@ function listed(options, allowNegative) {
|
|
|
482
549
|
return [...option.description === undefined ? [] : [[name, option]], [`no-${name}`, { type: "boolean", description: option.negative }]];
|
|
483
550
|
}));
|
|
484
551
|
}
|
|
552
|
+
function withAliases(args, options) {
|
|
553
|
+
const named = new Map(Object.entries(options).flatMap(([name, { aliases = [] }]) => aliases.map((alias) => [`--${alias}`, `--${name}`])));
|
|
554
|
+
if (named.size === 0)
|
|
555
|
+
return args;
|
|
556
|
+
const separator = args.indexOf("--");
|
|
557
|
+
const own = separator === -1 ? args : args.slice(0, separator);
|
|
558
|
+
return [...own.map((arg) => {
|
|
559
|
+
const at = arg.indexOf("=");
|
|
560
|
+
const current = named.get(at === -1 ? arg : arg.slice(0, at));
|
|
561
|
+
return current === undefined ? arg : `${current}${at === -1 ? "" : arg.slice(at)}`;
|
|
562
|
+
}), ...separator === -1 ? [] : args.slice(separator)];
|
|
563
|
+
}
|
|
485
564
|
function withBare(args, options) {
|
|
486
565
|
const bare = new Map(Object.entries(options).flatMap(([name, { short, optional }]) => optional === undefined ? [] : [[`--${name}`, optional], ...short === undefined ? [] : [[`-${short}`, optional]]]));
|
|
487
566
|
if (bare.size === 0)
|
|
@@ -568,7 +647,7 @@ function nearestOption(message, options, allowNegative) {
|
|
|
568
647
|
}
|
|
569
648
|
// ../kit/src/process.ts
|
|
570
649
|
import { constants } from "os";
|
|
571
|
-
import
|
|
650
|
+
import process6 from "process";
|
|
572
651
|
var KILL_GRACE_MS = 2000;
|
|
573
652
|
function exitCodeOf(code, signal) {
|
|
574
653
|
if (code !== null)
|
|
@@ -588,7 +667,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
|
|
|
588
667
|
return;
|
|
589
668
|
try {
|
|
590
669
|
if (grouped)
|
|
591
|
-
|
|
670
|
+
process6.kill(-proc.pid, killing);
|
|
592
671
|
else
|
|
593
672
|
proc.kill(killing);
|
|
594
673
|
} catch {}
|
|
@@ -662,7 +741,97 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
|
|
|
662
741
|
var RESET_STYLE = `${ESC}[0m`;
|
|
663
742
|
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
664
743
|
// ../kit/src/cli.ts
|
|
665
|
-
import
|
|
744
|
+
import { readFileSync, realpathSync } from "fs";
|
|
745
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
746
|
+
import process8 from "process";
|
|
747
|
+
|
|
748
|
+
// ../kit/src/usage.ts
|
|
749
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
750
|
+
import { homedir } from "os";
|
|
751
|
+
import { dirname, isAbsolute, join } from "path";
|
|
752
|
+
import process7 from "process";
|
|
753
|
+
|
|
754
|
+
// ../kit/src/logs.ts
|
|
755
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
756
|
+
var CLI_FOLDER = "kaynooo-cli";
|
|
757
|
+
|
|
758
|
+
// ../kit/src/usage.ts
|
|
759
|
+
var UNKNOWN_OPTION2 = /unknown option '(--[a-z][a-z0-9-]*)[=']/;
|
|
760
|
+
var FLAG_NAME = /^(?:--[a-z][a-z0-9-]*|-[a-zA-Z])$/;
|
|
761
|
+
var LONGEST_NAME = 40;
|
|
762
|
+
var usageError;
|
|
763
|
+
function noteUsageError(message) {
|
|
764
|
+
usageError = message;
|
|
765
|
+
}
|
|
766
|
+
function stateHome(env = process7.env) {
|
|
767
|
+
const dir = env.XDG_STATE_HOME;
|
|
768
|
+
return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
|
|
769
|
+
}
|
|
770
|
+
function usagePath(env = process7.env) {
|
|
771
|
+
return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
772
|
+
}
|
|
773
|
+
function usageRecorded(env = process7.env) {
|
|
774
|
+
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
775
|
+
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
776
|
+
}
|
|
777
|
+
function flagNames(argv) {
|
|
778
|
+
const end = argv.indexOf("--");
|
|
779
|
+
const names = (end === -1 ? argv : argv.slice(0, end)).filter((word) => word.startsWith("-") && word !== "-").map((word) => word.includes("=") ? word.slice(0, word.indexOf("=")) : word);
|
|
780
|
+
return [...new Set(names)].filter((name) => name.length <= LONGEST_NAME && FLAG_NAME.test(name));
|
|
781
|
+
}
|
|
782
|
+
function usageEntry(bin, command, argv, code, now = new Date) {
|
|
783
|
+
const unknown = code === 2 && usageError !== undefined ? UNKNOWN_OPTION2.exec(usageError)?.[1] : undefined;
|
|
784
|
+
usageError = undefined;
|
|
785
|
+
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
786
|
+
}
|
|
787
|
+
function recordUsage(entry, env = process7.env) {
|
|
788
|
+
if (!usageRecorded(env))
|
|
789
|
+
return;
|
|
790
|
+
try {
|
|
791
|
+
const path = usagePath(env);
|
|
792
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
793
|
+
appendFileSync(path, `${JSON.stringify(entry)}
|
|
794
|
+
`, { mode: 384 });
|
|
795
|
+
} catch {}
|
|
796
|
+
}
|
|
797
|
+
async function readUsage(path = usagePath(), since = 0) {
|
|
798
|
+
const file = Bun.file(path);
|
|
799
|
+
if (!await file.exists())
|
|
800
|
+
return [];
|
|
801
|
+
return (await file.text()).split(`
|
|
802
|
+
`).flatMap((line) => {
|
|
803
|
+
try {
|
|
804
|
+
const entry = JSON.parse(line);
|
|
805
|
+
return Date.parse(entry.at) >= since ? [entry] : [];
|
|
806
|
+
} catch {
|
|
807
|
+
return [];
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
function summarizeUsage(entries) {
|
|
812
|
+
const bins = new Map;
|
|
813
|
+
const errors = new Map;
|
|
814
|
+
for (const entry of entries) {
|
|
815
|
+
const bin = bins.get(entry.bin) ?? { runs: 0, failed: 0, usageErrors: 0 };
|
|
816
|
+
bin.runs++;
|
|
817
|
+
bin.failed += entry.code === 0 ? 0 : 1;
|
|
818
|
+
bin.usageErrors += entry.code === 2 ? 1 : 0;
|
|
819
|
+
bins.set(entry.bin, bin);
|
|
820
|
+
if (entry.code === 2 && entry.unknown !== undefined) {
|
|
821
|
+
const key = `${entry.bin} ${entry.command ?? ""} ${entry.unknown}`;
|
|
822
|
+
const error = errors.get(key) ?? { bin: entry.bin, ...entry.command === undefined ? {} : { command: entry.command }, unknown: entry.unknown, count: 0 };
|
|
823
|
+
error.count++;
|
|
824
|
+
errors.set(key, error);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
runs: entries.length,
|
|
829
|
+
bins: [...bins].map(([bin, counts]) => ({ bin, ...counts })).toSorted((a, b) => b.runs - a.runs),
|
|
830
|
+
usageErrors: [...errors.values()].toSorted((a, b) => b.count - a.count)
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// ../kit/src/cli.ts
|
|
666
835
|
var LEVEL_OPTIONS = {
|
|
667
836
|
...OUTPUT_OPTIONS,
|
|
668
837
|
help: { type: "boolean", short: "h", description: "show this help" }
|
|
@@ -715,7 +884,7 @@ async function expand(scope, words) {
|
|
|
715
884
|
}
|
|
716
885
|
return { ...scope, commands, lazy: [] };
|
|
717
886
|
}
|
|
718
|
-
async function
|
|
887
|
+
async function help2({ path, header, summary: lazySummary, commands, defaultCommand, examples, replaces }, stream) {
|
|
719
888
|
const colors = paint(stream);
|
|
720
889
|
const prefix = path.join(" ");
|
|
721
890
|
const summary = await resolveSummary(lazySummary);
|
|
@@ -742,8 +911,8 @@ async function help({ path, header, summary: lazySummary, commands, defaultComma
|
|
|
742
911
|
`);
|
|
743
912
|
}
|
|
744
913
|
function printError(prefix, message, hint, cause) {
|
|
745
|
-
const colors = paint(
|
|
746
|
-
const styled = takesControlCodes(
|
|
914
|
+
const colors = paint(process8.stderr);
|
|
915
|
+
const styled = takesControlCodes(process8.stderr);
|
|
747
916
|
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
748
917
|
const under = (label, text, tint) => text === undefined ? "" : `
|
|
749
918
|
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
@@ -762,6 +931,8 @@ function reportThrown(prefix, error, redact = (text) => text, json = false) {
|
|
|
762
931
|
const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
|
|
763
932
|
const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
|
|
764
933
|
const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
|
|
934
|
+
if (error instanceof UsageError)
|
|
935
|
+
noteUsageError(error.message);
|
|
765
936
|
const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
|
|
766
937
|
report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
|
|
767
938
|
return error instanceof Exit ? error.code : 1;
|
|
@@ -784,11 +955,11 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
784
955
|
}
|
|
785
956
|
}
|
|
786
957
|
if (name === undefined && level.path.length > 1) {
|
|
787
|
-
console.error(await
|
|
958
|
+
console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stderr));
|
|
788
959
|
return 2;
|
|
789
960
|
}
|
|
790
961
|
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
791
|
-
console.log(await
|
|
962
|
+
console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process8.stdout));
|
|
792
963
|
return 0;
|
|
793
964
|
}
|
|
794
965
|
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
@@ -904,43 +1075,78 @@ async function printCandidates(found) {
|
|
|
904
1075
|
console.log(line);
|
|
905
1076
|
return 0;
|
|
906
1077
|
}
|
|
907
|
-
|
|
1078
|
+
function packageVersion(entry = Bun.main) {
|
|
1079
|
+
for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
|
|
1080
|
+
try {
|
|
1081
|
+
const { version } = JSON.parse(readFileSync(join2(dir, "package.json"), "utf8"));
|
|
1082
|
+
if (typeof version === "string")
|
|
1083
|
+
return version;
|
|
1084
|
+
} catch {}
|
|
1085
|
+
if (dirname2(dir) === dir)
|
|
1086
|
+
return "0.0.0";
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
async function runCli(cli, argv = process8.argv.slice(2)) {
|
|
1090
|
+
const version = cli.version ?? packageVersion();
|
|
908
1091
|
if (argv[0] === COMPLETE)
|
|
909
1092
|
return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
|
|
910
|
-
|
|
1093
|
+
const code = await dispatch({ path: [cli.name], header: (colors, about) => `${colors.bold(cli.name)} ${colors.dim(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, redact: cli.redact });
|
|
1094
|
+
if (cli.usage !== false) {
|
|
1095
|
+
try {
|
|
1096
|
+
recordUsage(usageEntry(cli.name, commandPath(scopeOf(cli.commands, cli.extraCommands, cli.aliases), argv), argv, code));
|
|
1097
|
+
} catch {}
|
|
1098
|
+
}
|
|
1099
|
+
return code;
|
|
1100
|
+
}
|
|
1101
|
+
function commandPath(scope, argv) {
|
|
1102
|
+
const path = [];
|
|
1103
|
+
let level = scope;
|
|
1104
|
+
for (const word of argv.filter((each) => !each.startsWith("-"))) {
|
|
1105
|
+
const name = level.aliases !== undefined && Object.hasOwn(level.aliases, word) ? level.aliases[word] ?? word : word;
|
|
1106
|
+
const command = Object.hasOwn(level.commands, name) ? level.commands[name] : undefined;
|
|
1107
|
+
if (command === undefined)
|
|
1108
|
+
break;
|
|
1109
|
+
path.push(word);
|
|
1110
|
+
if (!("commands" in command))
|
|
1111
|
+
break;
|
|
1112
|
+
level = scopeOf(command.commands, command.extraCommands, command.aliases);
|
|
1113
|
+
}
|
|
1114
|
+
return path.length === 0 ? undefined : path.join(" ");
|
|
911
1115
|
}
|
|
912
|
-
async function runMain({ name, version, run, redact }, argv =
|
|
1116
|
+
async function runMain({ name, version, run, redact, usage }, argv = process8.argv.slice(2)) {
|
|
913
1117
|
if (argv[0] === COMPLETE)
|
|
914
1118
|
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
915
1119
|
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
916
|
-
console.log(version);
|
|
1120
|
+
console.log(version ?? packageVersion());
|
|
917
1121
|
return 0;
|
|
918
1122
|
}
|
|
1123
|
+
let code;
|
|
919
1124
|
try {
|
|
920
|
-
|
|
1125
|
+
code = await run(argv);
|
|
921
1126
|
} catch (error) {
|
|
922
|
-
|
|
1127
|
+
code = reportThrown(name, error, redact, wantsJson(argv));
|
|
1128
|
+
}
|
|
1129
|
+
if (usage !== false) {
|
|
1130
|
+
try {
|
|
1131
|
+
recordUsage(usageEntry(name, undefined, argv, code));
|
|
1132
|
+
} catch {}
|
|
923
1133
|
}
|
|
1134
|
+
return code;
|
|
924
1135
|
}
|
|
925
|
-
// ../kit/src/logs.ts
|
|
926
|
-
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
927
1136
|
// ../kit/src/secrets.ts
|
|
928
1137
|
var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
|
|
929
1138
|
// ../kit/src/sessions.ts
|
|
930
1139
|
var {Glob } = globalThis.Bun;
|
|
931
1140
|
var SESSION_FILES = new Glob("*.json");
|
|
932
1141
|
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
933
|
-
// package.json
|
|
934
|
-
var version = "0.3.0";
|
|
935
|
-
|
|
936
1142
|
// src/checks.ts
|
|
937
|
-
import { existsSync as existsSync2, readFileSync as
|
|
938
|
-
import { basename, join as
|
|
1143
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
1144
|
+
import { basename, join as join4 } from "path";
|
|
939
1145
|
|
|
940
1146
|
// src/completions.ts
|
|
941
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from "fs";
|
|
942
|
-
import { join } from "path";
|
|
943
|
-
import
|
|
1147
|
+
import { existsSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync } from "fs";
|
|
1148
|
+
import { join as join3 } from "path";
|
|
1149
|
+
import process10 from "process";
|
|
944
1150
|
var MARKER = "# Generated by `cli completions fish`";
|
|
945
1151
|
var FISH_FILE = /\.fish$/;
|
|
946
1152
|
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'
|
|
@@ -954,8 +1160,28 @@ var FUNCTION = `functions -q __kaynooo_cli_complete; or function __kaynooo_cli_c
|
|
|
954
1160
|
end
|
|
955
1161
|
end
|
|
956
1162
|
end`;
|
|
957
|
-
|
|
958
|
-
|
|
1163
|
+
var BASH_FUNCTION = `__kaynooo_cli_complete() {
|
|
1164
|
+
local IFS=$'\\n' cur=\${COMP_WORDS[COMP_CWORD]} line words=
|
|
1165
|
+
local -a lines
|
|
1166
|
+
COMPREPLY=()
|
|
1167
|
+
lines=($("\${COMP_WORDS[0]}" __complete "\${COMP_WORDS[@]:1:COMP_CWORD}" 2>/dev/null))
|
|
1168
|
+
for line in "\${lines[@]}"; do
|
|
1169
|
+
if [[ $line == __files__ ]]; then
|
|
1170
|
+
COMPREPLY+=($(compgen -f -- "$cur"))
|
|
1171
|
+
else
|
|
1172
|
+
words+="\${line%%$'\\t'*}"$'\\n'
|
|
1173
|
+
fi
|
|
1174
|
+
done
|
|
1175
|
+
COMPREPLY+=($(compgen -W "$words" -- "$cur"))
|
|
1176
|
+
}`;
|
|
1177
|
+
function bashScript(bins) {
|
|
1178
|
+
return `# Generated by \`cli completions bash\`
|
|
1179
|
+
${BASH_FUNCTION}
|
|
1180
|
+
complete -F __kaynooo_cli_complete ${bins.join(" ")}
|
|
1181
|
+
`;
|
|
1182
|
+
}
|
|
1183
|
+
function completionDir(home, env = process10.env) {
|
|
1184
|
+
return join3(env.XDG_CACHE_HOME ?? join3(home, ".cache"), "fish", "kaynooo-cli");
|
|
959
1185
|
}
|
|
960
1186
|
function installedBins(host) {
|
|
961
1187
|
return Object.values(TOOLS).flat().filter((bin) => host.which(bin) !== null);
|
|
@@ -968,34 +1194,42 @@ complete -c ${bin} -f -a '(__kaynooo_cli_complete ${bin})'
|
|
|
968
1194
|
}
|
|
969
1195
|
async function installCompletions(host, dir) {
|
|
970
1196
|
const bins = installedBins(host);
|
|
971
|
-
|
|
972
|
-
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) &&
|
|
1197
|
+
mkdirSync2(dir, { recursive: true });
|
|
1198
|
+
const removed = readdirSync(dir).filter((name) => FISH_FILE.test(name) && !bins.includes(name.replace(FISH_FILE, "")) && readFileSync2(join3(dir, name), "utf8").startsWith(MARKER));
|
|
973
1199
|
for (const name of removed)
|
|
974
|
-
rmSync(
|
|
975
|
-
await Promise.all(bins.map(async (bin) => Bun.write(
|
|
1200
|
+
rmSync(join3(dir, name));
|
|
1201
|
+
await Promise.all(bins.map(async (bin) => Bun.write(join3(dir, `${bin}.fish`), completionFile(bin))));
|
|
976
1202
|
return { written: bins, removed: removed.map((name) => name.replace(FISH_FILE, "")) };
|
|
977
1203
|
}
|
|
978
1204
|
function missingCompletions(host, dir) {
|
|
979
|
-
return installedBins(host).filter((bin) => !existsSync(
|
|
1205
|
+
return installedBins(host).filter((bin) => !existsSync(join3(dir, `${bin}.fish`)));
|
|
980
1206
|
}
|
|
981
1207
|
|
|
982
1208
|
// src/checks.ts
|
|
983
1209
|
var TOOLS = {
|
|
1210
|
+
"@kaynooo/cli-audit": ["audit"],
|
|
984
1211
|
"@kaynooo/cli-base": ["base", "proof"],
|
|
985
1212
|
"@kaynooo/cli-changelog": ["changelog"],
|
|
986
|
-
"@kaynooo/cli-check": ["check", "fix", "errs"],
|
|
1213
|
+
"@kaynooo/cli-check": ["check", "fix", "errs", "j"],
|
|
987
1214
|
"@kaynooo/cli-code": ["peek", "todo-scan", "hotspots"],
|
|
988
1215
|
"@kaynooo/cli-db": ["db"],
|
|
1216
|
+
"@kaynooo/cli-docker": ["dc", "dlog", "dx", "sf"],
|
|
989
1217
|
"@kaynooo/cli-doctor": ["doctor", "cli"],
|
|
990
1218
|
"@kaynooo/cli-edit": ["sub"],
|
|
991
1219
|
"@kaynooo/cli-env": ["ev", "redact"],
|
|
992
1220
|
"@kaynooo/cli-forgejo": ["ft"],
|
|
993
|
-
"@kaynooo/cli-git": ["gst", "gc"],
|
|
1221
|
+
"@kaynooo/cli-git": ["gst", "gc", "gb"],
|
|
994
1222
|
"@kaynooo/cli-gitlab": ["gl"],
|
|
995
1223
|
"@kaynooo/cli-guard": ["guard"],
|
|
996
1224
|
"@kaynooo/cli-out": ["out"],
|
|
1225
|
+
"@kaynooo/cli-dev": ["dev"],
|
|
1226
|
+
"@kaynooo/cli-pick": ["pick"],
|
|
997
1227
|
"@kaynooo/cli-pkg": ["pkg"],
|
|
1228
|
+
"@kaynooo/cli-probe": ["probe"],
|
|
1229
|
+
"@kaynooo/cli-proc": ["pid", "ports", "procs"],
|
|
1230
|
+
"@kaynooo/cli-repos": ["repos"],
|
|
998
1231
|
"@kaynooo/cli-stack": ["stack"],
|
|
1232
|
+
"@kaynooo/cli-sweep": ["sweep"],
|
|
999
1233
|
"@kaynooo/cli-view": ["view"],
|
|
1000
1234
|
"@kaynooo/cli-wait": ["waitfor", "slot"]
|
|
1001
1235
|
};
|
|
@@ -1014,7 +1248,7 @@ function readJsonText(text) {
|
|
|
1014
1248
|
}
|
|
1015
1249
|
}
|
|
1016
1250
|
function readJson(path) {
|
|
1017
|
-
return existsSync2(path) ? readJsonText(
|
|
1251
|
+
return existsSync2(path) ? readJsonText(readFileSync3(path, "utf8")) : undefined;
|
|
1018
1252
|
}
|
|
1019
1253
|
function firstLine(text) {
|
|
1020
1254
|
const trimmed = text.trim();
|
|
@@ -1023,12 +1257,12 @@ function firstLine(text) {
|
|
|
1023
1257
|
return end === -1 ? trimmed : trimmed.slice(0, end);
|
|
1024
1258
|
}
|
|
1025
1259
|
function installed(host, name) {
|
|
1026
|
-
return readJson(
|
|
1260
|
+
return readJson(join4(host.globalModules, name, "package.json"));
|
|
1027
1261
|
}
|
|
1028
1262
|
function ownBin(host, name, bin) {
|
|
1029
1263
|
const declared = installed(host, name)?.bin;
|
|
1030
1264
|
const file = typeof declared === "string" ? declared : declared?.[bin];
|
|
1031
|
-
return file === undefined ? undefined :
|
|
1265
|
+
return file === undefined ? undefined : join4(host.globalModules, name, file);
|
|
1032
1266
|
}
|
|
1033
1267
|
async function checkTool(host, name, bins) {
|
|
1034
1268
|
const check = name.slice(name.indexOf("/") + 1);
|
|
@@ -1037,7 +1271,7 @@ async function checkTool(host, name, bins) {
|
|
|
1037
1271
|
return { check, ok: false, detail: "not installed", fix: `bun install -g ${name}` };
|
|
1038
1272
|
const off = bins.filter((bin) => host.which(bin) === null);
|
|
1039
1273
|
if (off.length > 0)
|
|
1040
|
-
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${
|
|
1274
|
+
return { check, ok: false, detail: `${version}, ${off.join(", ")} not on the PATH`, fix: `add ${join4(host.globalModules, "..", "..", "..", "bin")} to the PATH` };
|
|
1041
1275
|
const info = await host.run(["bun", "info", name, "version", "--no-cache"]);
|
|
1042
1276
|
const answered = info.code === 0 ? firstLine(info.stdout.trim().split(`
|
|
1043
1277
|
`).at(-1) ?? "") : "";
|
|
@@ -1064,9 +1298,9 @@ async function checkTea(host) {
|
|
|
1064
1298
|
return { check: "tea", ok: true, detail: `logged in to ${forge} as ${login.name}, Forgejo ${served}` };
|
|
1065
1299
|
}
|
|
1066
1300
|
function checkHooks(host) {
|
|
1067
|
-
const settings = readJson(
|
|
1301
|
+
const settings = readJson(join4(host.home, ".claude", "settings.json")) ?? {};
|
|
1068
1302
|
const plugin = settings.enabledPlugins?.[PLUGIN] === true;
|
|
1069
|
-
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync2(
|
|
1303
|
+
const mod = (settings.env?.CLAUDE_CODE_PLUGIN_DIRS ?? "").split(":").map((dir) => dir.replace(LEADING_TILDE, host.home)).find((dir) => basename(dir) === "cli" && existsSync2(join4(dir, "hooks", "hooks.json")));
|
|
1070
1304
|
if (plugin && mod !== undefined)
|
|
1071
1305
|
return { check: "hooks", ok: false, detail: `both the ${PLUGIN} plugin and the mod in ${mod} run guard, so every call is checked twice`, fix: `claude plugin disable ${PLUGIN}` };
|
|
1072
1306
|
if (plugin)
|
|
@@ -1091,28 +1325,75 @@ function checkCompletions(host) {
|
|
|
1091
1325
|
return { check: "completions", ok: false, detail: `no fish completion in ${dir} for ${missing.join(", ")}`, fix: "cli completions fish --install" };
|
|
1092
1326
|
return { check: "completions", ok: true, detail: dir };
|
|
1093
1327
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1328
|
+
var toolCheck = (name) => name.slice(name.indexOf("/") + 1);
|
|
1329
|
+
var OTHER_CHECKS = ["tea", "hooks", "bun", "completions"];
|
|
1330
|
+
var CHECK_NAMES = [...Object.keys(TOOLS).map(toolCheck), ...OTHER_CHECKS];
|
|
1331
|
+
async function runChecks(host, only = []) {
|
|
1332
|
+
const wanted = (check) => only.length === 0 || only.includes(check);
|
|
1333
|
+
const entries = Object.entries(TOOLS).filter(([name]) => wanted(toolCheck(name)));
|
|
1096
1334
|
const tools = [];
|
|
1097
1335
|
for (let at = 0;at < entries.length; at += INFO_AT_ONCE)
|
|
1098
1336
|
tools.push(...await Promise.all(entries.slice(at, at + INFO_AT_ONCE).map(async ([name, bins]) => checkTool(host, name, bins))));
|
|
1099
|
-
return [
|
|
1337
|
+
return [
|
|
1338
|
+
...tools,
|
|
1339
|
+
...wanted("tea") ? [await checkTea(host)] : [],
|
|
1340
|
+
...wanted("hooks") ? [checkHooks(host)] : [],
|
|
1341
|
+
...wanted("bun") ? [checkBun(host)] : [],
|
|
1342
|
+
...wanted("completions") ? [checkCompletions(host)] : []
|
|
1343
|
+
];
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// src/fix.ts
|
|
1347
|
+
var INSTALL = /^bun install -g (\S+)$/;
|
|
1348
|
+
var RUNNABLE = /^(?:cli completions fish --install|claude plugin (?:install \S+ --marketplace \S+|disable \S+))$/;
|
|
1349
|
+
function planFixes(results) {
|
|
1350
|
+
const steps = [];
|
|
1351
|
+
const installs = [];
|
|
1352
|
+
for (const { ok, fix } of results) {
|
|
1353
|
+
if (ok || fix === undefined)
|
|
1354
|
+
continue;
|
|
1355
|
+
const install = INSTALL.exec(fix);
|
|
1356
|
+
if (install !== null)
|
|
1357
|
+
installs.push(install[1] ?? "");
|
|
1358
|
+
else
|
|
1359
|
+
steps.push({ command: fix, argv: RUNNABLE.test(fix) ? fix.split(" ") : undefined });
|
|
1360
|
+
}
|
|
1361
|
+
if (installs.length === 0)
|
|
1362
|
+
return steps;
|
|
1363
|
+
const command = `bun install -g ${installs.join(" ")}`;
|
|
1364
|
+
return [{ command, argv: command.split(" ") }, ...steps];
|
|
1365
|
+
}
|
|
1366
|
+
async function applyFixes(host, steps, dry) {
|
|
1367
|
+
const outcomes = [];
|
|
1368
|
+
for (const { command, argv } of steps) {
|
|
1369
|
+
if (argv === undefined) {
|
|
1370
|
+
outcomes.push({ command, status: "not run", detail: "not a command doctor runs: do it by hand" });
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
if (dry) {
|
|
1374
|
+
outcomes.push({ command, status: "dry-run" });
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
const ran = await host.run(argv);
|
|
1378
|
+
outcomes.push(ran.code === 0 ? { command, status: "ran" } : { command, status: "failed", detail: `exit ${ran.code}: ${firstLine(ran.stderr || ran.stdout)}` });
|
|
1379
|
+
}
|
|
1380
|
+
return outcomes;
|
|
1100
1381
|
}
|
|
1101
1382
|
|
|
1102
1383
|
// src/host.ts
|
|
1103
|
-
import { homedir } from "os";
|
|
1104
|
-
import { join as
|
|
1105
|
-
import
|
|
1384
|
+
import { homedir as homedir2 } from "os";
|
|
1385
|
+
import { join as join5 } from "path";
|
|
1386
|
+
import process11 from "process";
|
|
1106
1387
|
var RUN_TIMEOUT_MS = 20000;
|
|
1107
1388
|
function hostHere() {
|
|
1108
|
-
const home =
|
|
1389
|
+
const home = homedir2();
|
|
1109
1390
|
return {
|
|
1110
1391
|
home,
|
|
1111
|
-
globalModules:
|
|
1392
|
+
globalModules: join5(process11.env.BUN_INSTALL ?? join5(home, ".bun"), "install", "global", "node_modules"),
|
|
1112
1393
|
bunVersion: Bun.version,
|
|
1113
1394
|
which: (bin) => Bun.which(bin),
|
|
1114
1395
|
run: async (argv, env) => {
|
|
1115
|
-
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...
|
|
1396
|
+
const ran = await runCommand(argv, { timeoutMs: RUN_TIMEOUT_MS, ...env === undefined ? {} : { env: { ...process11.env, ...env } } });
|
|
1116
1397
|
if (ran.timedOut)
|
|
1117
1398
|
return { code: 1, stdout: "", stderr: `timed out after ${RUN_TIMEOUT_MS / 1000}s` };
|
|
1118
1399
|
const of = (stream) => Buffer.concat(ran.chunks.filter((chunk) => chunk.stream === stream).map((chunk) => chunk.data)).toString();
|
|
@@ -1122,35 +1403,67 @@ function hostHere() {
|
|
|
1122
1403
|
}
|
|
1123
1404
|
|
|
1124
1405
|
// src/cli.ts
|
|
1125
|
-
var USAGE = `doctor [--json]: checks this host's @kaynooo/cli-* install, one line per check, the command that fixes a failing one beside it
|
|
1406
|
+
var USAGE = `doctor [--only <check>] [--quiet] [--fix [--dry-run]] [--json]: checks this host's @kaynooo/cli-* install, one line per check, the command that fixes a failing one beside it
|
|
1126
1407
|
|
|
1127
|
-
Each @kaynooo/cli-* package installed globally with its bins on the PATH and no older than the registry's latest; tea logged in to git.kaynooo.fr and reaching it; the Claude Code hooks wired through the cli-hooks plugin or the cli mod, not both; Bun at least the version the tools ask for.
|
|
1128
|
-
|
|
1129
|
-
|
|
1408
|
+
Each @kaynooo/cli-* package installed globally with its bins on the PATH and no older than the registry's latest; tea logged in to git.kaynooo.fr and reaching it; the Claude Code hooks wired through the cli-hooks plugin or the cli mod, not both; Bun at least the version the tools ask for; fish completions for every bin.
|
|
1409
|
+
--fix runs the printed fix: lines it may (global installs of the packages, the completions, the hooks plugin), the installs in one call, then checks again; a line that installs a system package or only describes a step is printed as not run. --dry-run shows what --fix would run.
|
|
1410
|
+
--quiet prints only the failing checks, nothing when all pass. --only limits the run to the checks named, e.g. --only tea --only bun.`;
|
|
1411
|
+
var EXITS = [[0, "every check passes (after --fix, the ones it fixed too)"], [1, "a check fails"], [2, "a usage error"]];
|
|
1412
|
+
var INSTALL2 = "bun install -g ";
|
|
1130
1413
|
var OPTIONS = {
|
|
1131
|
-
json: jsonOption("[{check, ok, detail, fix?}]")
|
|
1414
|
+
json: jsonOption("[{check, ok, detail, fix?}], as the checks stand after --fix; the fixes it ran go to stderr"),
|
|
1415
|
+
only: { type: "string", multiple: true, choices: CHECK_NAMES, description: "run only this check (a package such as cli-forgejo, tea, hooks, bun or completions), repeatable" },
|
|
1416
|
+
quiet: { type: "boolean", short: "q", description: "print only the failing checks" },
|
|
1417
|
+
fix: { type: "boolean", description: "run the fix: lines doctor may run, then check again" },
|
|
1418
|
+
"dry-run": { type: "boolean", description: "with --fix, show what would run without running it (alone, it implies --fix)" }
|
|
1132
1419
|
};
|
|
1133
1420
|
function print(results) {
|
|
1134
|
-
|
|
1421
|
+
if (results.length === 0)
|
|
1422
|
+
return;
|
|
1423
|
+
const { green, red, dim } = paint(process12.stdout);
|
|
1135
1424
|
const width = Math.max(...results.map((result) => result.check.length));
|
|
1136
1425
|
for (const { check, ok, detail, fix } of results)
|
|
1137
1426
|
console.log(`${ok ? green("ok ") : red("FAIL")} ${check.padEnd(width)} ${detail}${fix === undefined ? "" : ` ${dim("fix:")} ${fix}`}`);
|
|
1138
|
-
const installs = results.flatMap((result) => result.fix?.startsWith(
|
|
1427
|
+
const installs = results.flatMap((result) => result.fix?.startsWith(INSTALL2) === true ? [result.fix.slice(INSTALL2.length)] : []);
|
|
1139
1428
|
if (installs.length > 1)
|
|
1140
1429
|
console.log(`
|
|
1141
|
-
${dim("fix them all:")} ${
|
|
1430
|
+
${dim("fix them all:")} ${INSTALL2}${installs.join(" ")}`);
|
|
1431
|
+
}
|
|
1432
|
+
function printOutcomes(outcomes, log) {
|
|
1433
|
+
const { green, red, dim } = paint(process12.stdout);
|
|
1434
|
+
const marks = { ran: green("ran "), failed: red("failed "), "dry-run": dim("would "), "not run": dim("not run") };
|
|
1435
|
+
for (const { command, status, detail } of outcomes)
|
|
1436
|
+
log(`${marks[status]} ${command}${detail === undefined ? "" : ` ${dim(detail)}`}`);
|
|
1142
1437
|
}
|
|
1143
1438
|
async function main(args, host = hostHere()) {
|
|
1144
|
-
const { values } = parse(args, USAGE, OPTIONS, { positionals: [], replaces: "checking each bin's version, tea's login and the hooks by hand", examples: ["doctor", ["doctor --json", "the checks for a script"]] });
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1439
|
+
const { values } = parse(args, USAGE, OPTIONS, { positionals: [], exits: EXITS, replaces: "checking each bin's version, tea's login and the hooks by hand", examples: ["doctor", ["doctor --json", "the checks for a script"], ["doctor -q", "silent unless something fails"], ["doctor --fix --dry-run", "what --fix would run"]] });
|
|
1440
|
+
const only = values.only ?? [];
|
|
1441
|
+
const dry = values["dry-run"] === true;
|
|
1442
|
+
const json = values.json === true;
|
|
1443
|
+
let results = await runChecks(host, only);
|
|
1444
|
+
let fixed = false;
|
|
1445
|
+
if ((values.fix === true || dry) && results.some((result) => !result.ok)) {
|
|
1446
|
+
if (!json)
|
|
1447
|
+
print(values.quiet === true ? results.filter((result) => !result.ok) : results);
|
|
1448
|
+
const steps = planFixes(results);
|
|
1449
|
+
printOutcomes(await applyFixes(host, steps, dry), json ? console.error : console.log);
|
|
1450
|
+
if (dry)
|
|
1451
|
+
return 1;
|
|
1452
|
+
results = await runChecks(host, only);
|
|
1453
|
+
fixed = true;
|
|
1454
|
+
}
|
|
1455
|
+
const shown = values.quiet === true ? results.filter((result) => !result.ok) : results;
|
|
1456
|
+
if (fixed && !json && shown.length > 0)
|
|
1457
|
+
console.log(`
|
|
1458
|
+
after the fixes:`);
|
|
1459
|
+
if (json)
|
|
1460
|
+
printJson(shown);
|
|
1148
1461
|
else
|
|
1149
|
-
print(
|
|
1462
|
+
print(shown);
|
|
1150
1463
|
return results.every((result) => result.ok) ? 0 : 1;
|
|
1151
1464
|
}
|
|
1152
1465
|
if (import.meta.main)
|
|
1153
|
-
|
|
1466
|
+
process12.exitCode = await runMain({ name: "doctor", run: async (args) => main(args) });
|
|
1154
1467
|
export {
|
|
1155
1468
|
main
|
|
1156
1469
|
};
|