@kaynooo/cli-workspace 0.2.0 → 0.3.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/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli.ts
5
- import process16 from "process";
5
+ import process18 from "process";
6
6
 
7
7
  // ../kit/src/advisory.ts
8
8
  var SEVERITIES = ["low", "moderate", "high", "critical"];
@@ -13,14 +13,14 @@ function isIgnored(advisory, ignore) {
13
13
  return ignore.includes(advisory.ghsa ?? "") || ignore.includes(String(advisory.id));
14
14
  }
15
15
  // ../kit/src/args.ts
16
- import process5 from "process";
16
+ import process8 from "process";
17
17
  import { parseArgs } from "util";
18
18
 
19
19
  // ../kit/src/format.ts
20
- import process3 from "process";
20
+ import process6 from "process";
21
21
 
22
22
  // ../kit/src/style.ts
23
- import process2 from "process";
23
+ import process5 from "process";
24
24
 
25
25
  // ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
26
26
  function x(s) {
@@ -283,52 +283,236 @@ function le(i = {}) {
283
283
  }
284
284
  var t = le();
285
285
 
286
- // ../kit/src/style.ts
287
- var MARKS = {
288
- ok: { symbol: "\u2713", word: "ok:", paint: (colors) => colors.green },
289
- warn: { symbol: "!", word: "warning:", paint: (colors) => colors.yellow },
290
- problem: { symbol: "\u2717", word: "error:", paint: (colors) => colors.red },
291
- note: { symbol: "\xB7", word: "note:", paint: (colors) => colors.dim }
286
+ // ../kit/src/theme.ts
287
+ import { existsSync as existsSync3, readFileSync, statSync } from "fs";
288
+ import { homedir as homedir2 } from "os";
289
+ import { join as join3, resolve as resolve2 } from "path";
290
+ import process4 from "process";
291
+
292
+ // ../kit/src/config.ts
293
+ import { existsSync } from "fs";
294
+ import { dirname, join, resolve } from "path";
295
+ import process2 from "process";
296
+ function findUp(names, from = process2.cwd(), stop = () => false) {
297
+ const wanted = typeof names === "string" ? [names] : names;
298
+ for (let dir = resolve(from);; dir = dirname(dir)) {
299
+ const found = wanted.map((name) => join(dir, name)).find((path) => existsSync(path));
300
+ if (found !== undefined)
301
+ return found;
302
+ if (stop(dir) || dirname(dir) === dir)
303
+ return;
304
+ }
305
+ }
306
+
307
+ // ../kit/src/user-config.ts
308
+ import { existsSync as existsSync2 } from "fs";
309
+ import { homedir } from "os";
310
+ import { join as join2 } from "path";
311
+ import process3 from "process";
312
+ function configBase(where = {}) {
313
+ const xdg = where.xdg ?? (where.home === undefined ? process3.env.XDG_CONFIG_HOME : undefined);
314
+ return xdg !== undefined && xdg !== "" ? xdg : join2(where.home ?? homedir(), ".config");
315
+ }
316
+ function userConfigPath(bin, where = {}) {
317
+ return join2(configBase(where), "kaynooo-cli", `${bin}.json`);
318
+ }
319
+ function userConfigReadPath(bin, legacy = [], where = {}) {
320
+ const current = userConfigPath(bin, where);
321
+ if (existsSync2(current))
322
+ return current;
323
+ const base = configBase(where);
324
+ return legacy.map((parts) => join2(base, ...parts)).find((path) => existsSync2(path)) ?? current;
325
+ }
326
+
327
+ // ../kit/src/theme.ts
328
+ var DEFAULT = {
329
+ colors: { accent: "cyan", id: "yellow", path: "blue", label: "dim", value: "bold", ok: "green", warn: "yellow", problem: "red", note: "dim", passed: "green", failed: "red", running: "cyan", queued: "yellow", skipped: "dim" },
330
+ glyphs: { ok: "\u2713", warn: "\u25B2", problem: "\u2717", note: "\u203A", passed: "\u2713", failed: "\u2717", running: "\u25CF", queued: "\u25CB", skipped: "-", bar: "\u2501" },
331
+ accents: ["cyan", "magenta", "green", "blue", "yellow", "redBright"],
332
+ bins: {}
292
333
  };
293
- function takesControlCodes(stream, env = process2.env) {
334
+ var BRIGHT = { accent: "cyanBright", id: "yellowBright", path: "blueBright", label: "white", value: "bold whiteBright", ok: "greenBright", warn: "yellowBright", problem: "redBright", note: "white", passed: "greenBright", failed: "redBright", running: "cyanBright", queued: "yellowBright", skipped: "white" };
335
+ var MONO = { accent: "bold", id: "bold", path: "underline", label: "dim", value: "bold", ok: "bold", warn: "bold", problem: "bold", note: "dim", passed: "bold", failed: "bold", running: "bold", queued: "dim", skipped: "dim" };
336
+ var ASCII = { ok: "+", warn: "!", problem: "x", note: ">", passed: "+", failed: "x", running: "*", queued: "o", skipped: "-", bar: "#" };
337
+ var PRESETS = {
338
+ default: {},
339
+ "high-contrast": { colors: BRIGHT, accents: ["cyanBright", "magentaBright", "greenBright", "blueBright", "yellowBright", "redBright"] },
340
+ mono: { colors: MONO, accents: ["bold"] },
341
+ ascii: { glyphs: ASCII }
342
+ };
343
+ var STYLES = new Set(["bold", "dim", "italic", "underline", "inverse", "strikethrough", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "gray", "grey", "blackBright", "redBright", "greenBright", "yellowBright", "blueBright", "magentaBright", "cyanBright", "whiteBright"]);
344
+ var HEX = /^#[0-9a-f]{6}$/i;
345
+ var CONTROL = /\p{Cc}/u;
346
+ var MAX_FILE_BYTES = 64 * 1024;
347
+ var SPACES = /\s+/;
348
+ function isColorSpec(spec) {
349
+ if (typeof spec === "number")
350
+ return Number.isInteger(spec) && spec >= 0 && spec <= 255;
351
+ return typeof spec === "string" && spec.trim() !== "" && spec.trim().split(SPACES).every((token) => STYLES.has(token) || HEX.test(token));
352
+ }
353
+ function tinted(colors, spec) {
354
+ if (typeof spec === "number")
355
+ return colors.ansi256(spec);
356
+ return spec.trim().split(SPACES).reduce((chain, token) => HEX.test(token) ? chain.hex(token) : chain[token] ?? chain, colors);
357
+ }
358
+ function isRecord(value) {
359
+ return typeof value === "object" && value !== null && !Array.isArray(value);
360
+ }
361
+ function readThemeFile(path, warn) {
362
+ try {
363
+ const stat = statSync(path, { throwIfNoEntry: false });
364
+ if (stat === undefined)
365
+ return;
366
+ if (!stat.isFile() || stat.size > MAX_FILE_BYTES) {
367
+ warn(`${path} is not a regular file under ${MAX_FILE_BYTES / 1024} KiB, theme ignored`);
368
+ return;
369
+ }
370
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
371
+ if (isRecord(parsed))
372
+ return parsed;
373
+ warn(`${path} is not a JSON object, theme ignored`);
374
+ } catch (error) {
375
+ warn(`${path} does not parse (${error instanceof Error ? error.message : String(error)}), theme ignored`);
376
+ }
377
+ return;
378
+ }
379
+ function applyPreset(theme, name, warn, where) {
380
+ if (name === undefined)
381
+ return;
382
+ if (typeof name !== "string" || !Object.hasOwn(PRESETS, name)) {
383
+ warn(`${where}: no preset ${String(name)}, one of ${Object.keys(PRESETS).join(", ")}`);
384
+ return;
385
+ }
386
+ const preset = PRESETS[name];
387
+ Object.assign(theme.colors, preset.colors);
388
+ Object.assign(theme.glyphs, preset.glyphs);
389
+ if (preset.accents !== undefined)
390
+ theme.accents = [...preset.accents];
391
+ return name;
392
+ }
393
+ function applyFile(theme, file, where, warn) {
394
+ if (isRecord(file.colors)) {
395
+ for (const [key, value] of Object.entries(file.colors)) {
396
+ if (!Object.hasOwn(DEFAULT.colors, key))
397
+ warn(`${where}: no colour ${key}, one of ${Object.keys(DEFAULT.colors).join(", ")}`);
398
+ else if (!isColorSpec(value))
399
+ warn(`${where}: colour ${key} is not an ANSI name, \`#rrggbb\` or a number up to 255`);
400
+ else
401
+ theme.colors[key] = value;
402
+ }
403
+ }
404
+ if (isRecord(file.glyphs)) {
405
+ for (const [key, value] of Object.entries(file.glyphs)) {
406
+ if (!Object.hasOwn(DEFAULT.glyphs, key))
407
+ warn(`${where}: no glyph ${key}, one of ${Object.keys(DEFAULT.glyphs).join(", ")}`);
408
+ else if (typeof value !== "string" || CONTROL.test(value) || Bun.stringWidth(value) !== 1)
409
+ warn(`${where}: glyph ${key} must be one visible character wide, with no control character`);
410
+ else
411
+ theme.glyphs[key] = value;
412
+ }
413
+ }
414
+ if (Array.isArray(file.accents) && file.accents.length > 0 && file.accents.every(isColorSpec))
415
+ theme.accents = [...file.accents];
416
+ else if (file.accents !== undefined)
417
+ warn(`${where}: accents must be a non-empty list of colours`);
418
+ if (isRecord(file.bins)) {
419
+ for (const [bin, value] of Object.entries(file.bins)) {
420
+ if (isColorSpec(value))
421
+ theme.bins[bin] = value;
422
+ else
423
+ warn(`${where}: bin ${bin} has no valid colour`);
424
+ }
425
+ }
426
+ }
427
+ function endsWalk(home) {
428
+ return (dir) => dir === resolve2(home) || existsSync3(join3(dir, ".git"));
429
+ }
430
+ var warned = new Set;
431
+ function warnOnce(message) {
432
+ if (warned.has(message))
433
+ return;
434
+ warned.add(message);
435
+ console.error(`warning: theme ${message}`);
436
+ }
437
+ function isPath(chosen) {
438
+ return chosen.includes("/") || chosen.endsWith(".json");
439
+ }
440
+ function loadTheme({ env = process4.env, from = process4.cwd(), home = homedir2(), warn = warnOnce } = {}) {
441
+ const theme = { colors: { ...DEFAULT.colors }, glyphs: { ...DEFAULT.glyphs }, accents: [...DEFAULT.accents], bins: Object.create(null) };
442
+ const sources = [];
443
+ const chosen = env.KIT_THEME ?? "";
444
+ const wanted = chosen === "" ? [userConfigPath("theme", { home, xdg: env.XDG_CONFIG_HOME }), findUp(join3(".kaynooo", "theme.json"), from, endsWalk(home))] : [];
445
+ if (isPath(chosen))
446
+ wanted.push(resolve2(from, chosen));
447
+ const files = [];
448
+ for (const path of wanted) {
449
+ const file = path === undefined ? undefined : readThemeFile(path, warn);
450
+ if (path !== undefined && file !== undefined)
451
+ files.push({ path, file });
452
+ }
453
+ let preset = "default";
454
+ if (chosen !== "" && !isPath(chosen)) {
455
+ preset = applyPreset(theme, chosen, warn, "KIT_THEME") ?? preset;
456
+ } else {
457
+ const named = files.findLast(({ file }) => file.preset !== undefined);
458
+ if (named !== undefined)
459
+ preset = applyPreset(theme, named.file.preset, warn, named.path) ?? preset;
460
+ }
461
+ for (const { path, file } of files) {
462
+ applyFile(theme, file, path, warn);
463
+ sources.push(path);
464
+ }
465
+ return { theme, sources, preset };
466
+ }
467
+ var current;
468
+ var currentKey = "";
469
+ function activeTheme() {
470
+ const key = `${process4.cwd()}\x00${process4.env.KIT_THEME ?? ""}\x00${process4.env.XDG_CONFIG_HOME ?? ""}`;
471
+ if (current === undefined || key !== currentKey) {
472
+ current = loadTheme();
473
+ currentKey = key;
474
+ }
475
+ return current;
476
+ }
477
+
478
+ // ../kit/src/style.ts
479
+ var MARK_WORDS = { ok: "ok:", warn: "warning:", problem: "error:", note: "note:" };
480
+ function takesControlCodes(stream, env = process5.env) {
294
481
  return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
295
482
  }
296
- function paint(stream, env = process2.env) {
483
+ function paint(stream, env = process5.env) {
297
484
  return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
298
485
  }
299
- function marked(mark, text, stream, detail, env = process2.env) {
486
+ function marked(mark, text, stream, detail, env = process5.env) {
300
487
  const colors = paint(stream, env);
301
- const { symbol, word, paint: tint } = MARKS[mark];
302
- const head = takesControlCodes(stream, env) ? tint(colors)(symbol) : word;
488
+ let head = MARK_WORDS[mark];
489
+ if (takesControlCodes(stream, env)) {
490
+ const { theme } = activeTheme();
491
+ head = tinted(colors.bold, theme.colors[mark])(theme.glyphs[mark]);
492
+ }
303
493
  return `${head} ${text}${detail === undefined ? "" : ` ${colors.dim(detail)}`}`;
304
494
  }
305
495
  function ok(text, detail) {
306
- console.log(marked("ok", text, process2.stdout, detail));
496
+ console.log(marked("ok", text, process5.stdout, detail));
307
497
  }
308
498
  function note(text, detail) {
309
- console.log(marked("note", text, process2.stdout, detail));
499
+ console.log(marked("note", text, process5.stdout, detail));
310
500
  }
311
501
  function problem(text, detail) {
312
- console.error(marked("problem", text, process2.stderr, detail));
313
- }
314
- var ROLES = {
315
- accent: (colors) => colors.cyan,
316
- id: (colors) => colors.yellow,
317
- path: (colors) => colors.blue,
318
- label: (colors) => colors.dim,
319
- value: (colors) => colors.bold
320
- };
321
- function role(name, stream, env = process2.env) {
322
- return ROLES[name](paint(stream, env));
323
- }
324
- var STATUSES = {
325
- passed: { symbol: "\u2713", words: ["passed", "success", "succeeded", "ok"], paint: (colors) => colors.green },
326
- failed: { symbol: "\u2717", words: ["failed", "failure", "error", "errored"], paint: (colors) => colors.red },
327
- running: { symbol: "\u25CF", words: ["running", "in_progress", "started"], paint: (colors) => colors.cyan },
328
- queued: { symbol: "\u25CB", words: ["queued", "waiting", "pending", "created", "blocked", "scheduled"], paint: (colors) => colors.yellow },
329
- skipped: { symbol: "-", words: ["skipped", "cancelled", "canceled", "manual"], paint: (colors) => colors.dim }
502
+ console.error(marked("problem", text, process5.stderr, detail));
503
+ }
504
+ function role(name, stream, env = process5.env) {
505
+ const colors = paint(stream, env);
506
+ return takesControlCodes(stream, env) ? tinted(colors, activeTheme().theme.colors[name]) : colors;
507
+ }
508
+ var STATUS_WORDS = {
509
+ passed: ["passed", "success", "succeeded", "ok"],
510
+ failed: ["failed", "failure", "error", "errored"],
511
+ running: ["running", "in_progress", "started"],
512
+ queued: ["queued", "waiting", "pending", "created", "blocked", "scheduled"],
513
+ skipped: ["skipped", "cancelled", "canceled", "manual"]
330
514
  };
331
- var STATUS_OF = new Map(Object.entries(STATUSES).flatMap(([status, { words }]) => words.map((word) => [word, status])));
515
+ var STATUS_OF = new Map(Object.entries(STATUS_WORDS).flatMap(([status, words]) => words.map((word) => [word, status])));
332
516
  // ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-hrsswv1w.js
333
517
  function $e(t, e = false, i = 1) {
334
518
  let n = typeof e === "object" ? e : { units: e ? "si" : "iec", dp: i }, { units: s = "iec", dp: u = 1 } = n, r = s === "si" ? 1000 : 1024;
@@ -378,7 +562,7 @@ var ESCAPE = String.fromCharCode(27);
378
562
  function textWidth(text) {
379
563
  return Bun.stringWidth(text);
380
564
  }
381
- function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
565
+ function table(rows, { cellWidth, header = false, align = [], fit, stream = process6.stdout } = {}) {
382
566
  const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
383
567
  const widths = [];
384
568
  for (const row of cells) {
@@ -386,7 +570,7 @@ function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
386
570
  widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
387
571
  }
388
572
  const shown = fit === undefined ? cells : narrow(cells, widths, fit);
389
- const colors = paint(process3.stdout);
573
+ const colors = paint(stream);
390
574
  const lines = shown.map((row) => row.map((cell, index) => {
391
575
  const width = widths[index] ?? 0;
392
576
  const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
@@ -394,7 +578,8 @@ function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
394
578
  return `${gap}${cell}`;
395
579
  return index === row.length - 1 ? cell : `${cell}${gap}`;
396
580
  }).join(" ").trimEnd());
397
- return lines.map((line, index) => header && index === 0 ? colors.bold(line) : line).join(`
581
+ const rule = header && takesControlCodes(stream) && lines.length > 1 ? [colors.dim("\u2500".repeat(Math.max(...lines.map(textWidth))))] : [];
582
+ return lines.flatMap((line, index) => header && index === 0 ? [colors.bold(line), ...rule] : [line]).join(`
398
583
  `);
399
584
  }
400
585
  function narrow(rows, widths, limit) {
@@ -418,27 +603,27 @@ function printJson(value) {
418
603
  }
419
604
 
420
605
  // ../kit/src/help.ts
421
- import process4 from "process";
606
+ import process7 from "process";
422
607
  var OPTIONAL = /\[[^\]]+\]/g;
423
608
  var USAGE_PREFIX = /^usage:\s*/i;
424
609
  var FULL_STOP = /\.$/;
425
- function usageLine(usage, colors = paint(process4.stdout)) {
610
+ function usageLine(usage, colors = paint(process7.stdout)) {
426
611
  return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
427
612
  }
428
- function helpText(usage, options, colors = paint(process4.stdout), { examples = [], replaces, exits = [] } = {}) {
613
+ function helpText(usage, options, colors = paint(process7.stdout), { examples = [], replaces, exits = [] } = {}) {
429
614
  return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
430
615
  `);
431
616
  }
432
- function exitRows(exits, colors = paint(process4.stdout)) {
617
+ function exitRows(exits, colors = paint(process7.stdout)) {
433
618
  if (exits.length === 0)
434
619
  return [];
435
620
  const width = Math.max(...exits.map(([code]) => String(code).length));
436
621
  return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
437
622
  }
438
- function replacesRows(replaces, colors = paint(process4.stdout)) {
623
+ function replacesRows(replaces, colors = paint(process7.stdout)) {
439
624
  return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
440
625
  }
441
- function wantsHelpJson(env = process4.env) {
626
+ function wantsHelpJson(env = process7.env) {
442
627
  return env.KIT_HELP === "json";
443
628
  }
444
629
  function description(usage, { examples = [], replaces, exits } = {}, options) {
@@ -457,12 +642,12 @@ function description(usage, { examples = [], replaces, exits } = {}, options) {
457
642
  ...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
458
643
  };
459
644
  }
460
- function exampleRows(examples, colors = paint(process4.stdout)) {
645
+ function exampleRows(examples, colors = paint(process7.stdout)) {
461
646
  if (examples.length === 0)
462
647
  return [];
463
648
  return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
464
649
  }
465
- function optionRows(options, colors = paint(process4.stdout)) {
650
+ function optionRows(options, colors = paint(process7.stdout)) {
466
651
  const rows = Object.entries(options).map(([name, option]) => {
467
652
  const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
468
653
  const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
@@ -555,7 +740,7 @@ var OUTPUT_OPTIONS = {
555
740
  color: { type: "string", description: "auto (on a terminal alone), always or never" },
556
741
  plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
557
742
  };
558
- function applyOutput(color, plain, env = process5.env) {
743
+ function applyOutput(color, plain, env = process8.env) {
559
744
  const when = plain === true ? "never" : color ?? "auto";
560
745
  if (when === "auto")
561
746
  return;
@@ -726,7 +911,7 @@ function nearestOption(message, options, allowNegative) {
726
911
  }
727
912
  // ../kit/src/process.ts
728
913
  import { constants } from "os";
729
- import process6 from "process";
914
+ import process9 from "process";
730
915
  var KILL_GRACE_MS = 2000;
731
916
  function exitCodeOf(code, signal) {
732
917
  if (code !== null)
@@ -746,7 +931,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
746
931
  return;
747
932
  try {
748
933
  if (grouped)
749
- process6.kill(-proc.pid, killing);
934
+ process9.kill(-proc.pid, killing);
750
935
  else
751
936
  proc.kill(killing);
752
937
  } catch {}
@@ -821,10 +1006,10 @@ var RESET_STYLE = `${ESC}[0m`;
821
1006
  var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
822
1007
 
823
1008
  // ../kit/src/tool.ts
824
- import process7 from "process";
1009
+ import process10 from "process";
825
1010
  function cwdGone() {
826
1011
  try {
827
- process7.cwd();
1012
+ process10.cwd();
828
1013
  return false;
829
1014
  } catch {
830
1015
  return true;
@@ -846,7 +1031,7 @@ function spawnFailure(command, error) {
846
1031
  async function runTool(label, argv, { timeoutMs, cwd, onStart } = {}) {
847
1032
  let ran;
848
1033
  try {
849
- ran = await runCommand(argv, { env: process7.env, ...timeoutMs === undefined ? {} : { timeoutMs }, ...cwd === undefined ? {} : { cwd }, ...onStart === undefined ? {} : { onStart } });
1034
+ ran = await runCommand(argv, { env: process10.env, ...timeoutMs === undefined ? {} : { timeoutMs }, ...cwd === undefined ? {} : { cwd }, ...onStart === undefined ? {} : { onStart } });
850
1035
  } catch (error) {
851
1036
  throw spawnFailure(label, error);
852
1037
  }
@@ -861,15 +1046,15 @@ async function runTool(label, argv, { timeoutMs, cwd, onStart } = {}) {
861
1046
  return { code: ran.code, ...streams };
862
1047
  }
863
1048
  // ../kit/src/cli.ts
864
- import { readFileSync, realpathSync } from "fs";
865
- import { dirname as dirname2, join as join2 } from "path";
866
- import process9 from "process";
1049
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
1050
+ import { dirname as dirname3, join as join5 } from "path";
1051
+ import process12 from "process";
867
1052
 
868
1053
  // ../kit/src/usage.ts
869
1054
  import { appendFileSync, mkdirSync } from "fs";
870
- import { homedir } from "os";
871
- import { dirname, isAbsolute, join } from "path";
872
- import process8 from "process";
1055
+ import { homedir as homedir3 } from "os";
1056
+ import { dirname as dirname2, isAbsolute, join as join4 } from "path";
1057
+ import process11 from "process";
873
1058
 
874
1059
  // ../kit/src/logs.ts
875
1060
  var DAY_MS = 24 * 60 * 60 * 1000;
@@ -883,14 +1068,14 @@ var usageError;
883
1068
  function noteUsageError(message) {
884
1069
  usageError = message;
885
1070
  }
886
- function stateHome(env = process8.env) {
1071
+ function stateHome(env = process11.env) {
887
1072
  const dir = env.XDG_STATE_HOME;
888
- return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
1073
+ return dir !== undefined && isAbsolute(dir) ? dir : join4(homedir3(), ".local", "state");
889
1074
  }
890
- function usagePath(env = process8.env) {
891
- return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
1075
+ function usagePath(env = process11.env) {
1076
+ return join4(stateHome(env), CLI_FOLDER, "usage.jsonl");
892
1077
  }
893
- function usageRecorded(env = process8.env) {
1078
+ function usageRecorded(env = process11.env) {
894
1079
  const off = env.KAYNOOO_CLI_NO_USAGE;
895
1080
  return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
896
1081
  }
@@ -904,12 +1089,12 @@ function usageEntry(bin, command, argv, code, now = new Date) {
904
1089
  usageError = undefined;
905
1090
  return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
906
1091
  }
907
- function recordUsage(entry, env = process8.env) {
1092
+ function recordUsage(entry, env = process11.env) {
908
1093
  if (!usageRecorded(env))
909
1094
  return;
910
1095
  try {
911
1096
  const path = usagePath(env);
912
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
1097
+ mkdirSync(dirname2(path), { recursive: true, mode: 448 });
913
1098
  appendFileSync(path, `${JSON.stringify(entry)}
914
1099
  `, { mode: 384 });
915
1100
  } catch {}
@@ -995,8 +1180,8 @@ async function help2({ path, header, summary: lazySummary, commands, defaultComm
995
1180
  `);
996
1181
  }
997
1182
  function printError(prefix, message, hint, cause) {
998
- const colors = paint(process9.stderr);
999
- const styled = takesControlCodes(process9.stderr);
1183
+ const colors = paint(process12.stderr);
1184
+ const styled = takesControlCodes(process12.stderr);
1000
1185
  const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
1001
1186
  const under = (label, text, tint) => text === undefined ? "" : `
1002
1187
  ${styled ? tint(label) : `${label}:`} ${text}`;
@@ -1039,11 +1224,11 @@ async function dispatch(level, typed, { version, redact }) {
1039
1224
  }
1040
1225
  }
1041
1226
  if (name === undefined && level.path.length > 1) {
1042
- console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process9.stderr));
1227
+ console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process12.stderr));
1043
1228
  return 2;
1044
1229
  }
1045
1230
  if (name === undefined || name === "help" || name === "--help" || name === "-h") {
1046
- console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process9.stdout));
1231
+ console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process12.stdout));
1047
1232
  return 0;
1048
1233
  }
1049
1234
  if (level.path.length === 1 && (name === "--version" || name === "-v")) {
@@ -1160,17 +1345,17 @@ async function printCandidates(found) {
1160
1345
  return 0;
1161
1346
  }
1162
1347
  function packageVersion(entry = Bun.main) {
1163
- for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
1348
+ for (let dir = dirname3(realpathSync(entry));; dir = dirname3(dir)) {
1164
1349
  try {
1165
- const { version } = JSON.parse(readFileSync(join2(dir, "package.json"), "utf8"));
1350
+ const { version } = JSON.parse(readFileSync2(join5(dir, "package.json"), "utf8"));
1166
1351
  if (typeof version === "string")
1167
1352
  return version;
1168
1353
  } catch {}
1169
- if (dirname2(dir) === dir)
1354
+ if (dirname3(dir) === dir)
1170
1355
  return "0.0.0";
1171
1356
  }
1172
1357
  }
1173
- async function runCli(cli, argv = process9.argv.slice(2)) {
1358
+ async function runCli(cli, argv = process12.argv.slice(2)) {
1174
1359
  const version = cli.version ?? packageVersion();
1175
1360
  if (argv[0] === COMPLETE)
1176
1361
  return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
@@ -1197,7 +1382,7 @@ function commandPath(scope, argv) {
1197
1382
  }
1198
1383
  return path.length === 0 ? undefined : path.join(" ");
1199
1384
  }
1200
- async function runMain({ name, version, run, redact, usage }, argv = process9.argv.slice(2)) {
1385
+ async function runMain({ name, version, run, redact, usage }, argv = process12.argv.slice(2)) {
1201
1386
  if (argv[0] === COMPLETE)
1202
1387
  return printCandidates(leafCandidates(run, argv.slice(1)));
1203
1388
  if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
@@ -1233,34 +1418,15 @@ function parseSize(text, bareUnit = 1) {
1233
1418
  const suffix = match[2].toLowerCase();
1234
1419
  return Math.round(Number(match[1]) * (suffix === "" ? bareUnit : 1024 ** POWERS[suffix]));
1235
1420
  }
1236
- // ../kit/src/user-config.ts
1237
- import { existsSync } from "fs";
1238
- import { homedir as homedir2 } from "os";
1239
- import { join as join3 } from "path";
1240
- import process10 from "process";
1241
- function configBase(where = {}) {
1242
- const xdg = where.xdg ?? (where.home === undefined ? process10.env.XDG_CONFIG_HOME : undefined);
1243
- return xdg !== undefined && xdg !== "" ? xdg : join3(where.home ?? homedir2(), ".config");
1244
- }
1245
- function userConfigPath(bin, where = {}) {
1246
- return join3(configBase(where), "kaynooo-cli", `${bin}.json`);
1247
- }
1248
- function userConfigReadPath(bin, legacy = [], where = {}) {
1249
- const current = userConfigPath(bin, where);
1250
- if (existsSync(current))
1251
- return current;
1252
- const base = configBase(where);
1253
- return legacy.map((parts) => join3(base, ...parts)).find((path) => existsSync(path)) ?? current;
1254
- }
1255
1421
  // src/audit.ts
1256
- import { existsSync as existsSync3, readdirSync, statSync } from "fs";
1422
+ import { existsSync as existsSync5, readdirSync, statSync as statSync2 } from "fs";
1257
1423
  import { readdir } from "fs/promises";
1258
- import { join as join5, relative, resolve } from "path";
1259
- import process12 from "process";
1424
+ import { join as join7, relative, resolve as resolve3 } from "path";
1425
+ import process14 from "process";
1260
1426
 
1261
1427
  // src/report.ts
1262
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1263
- import { join as join4 } from "path";
1428
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1429
+ import { join as join6 } from "path";
1264
1430
  var UPPER_BOUND = /<\s*(\d[\w.+-]*)/;
1265
1431
  var DEPENDENCY_KINDS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
1266
1432
  var GHSA = /GHSA-[\w-]+$/;
@@ -1304,7 +1470,7 @@ function findingsOf(audit, reaching, direct, level, ignore = []) {
1304
1470
  }
1305
1471
  function readManifest(path) {
1306
1472
  try {
1307
- return existsSync2(path) ? JSON.parse(readFileSync2(path, "utf8")) : {};
1473
+ return existsSync4(path) ? JSON.parse(readFileSync3(path, "utf8")) : {};
1308
1474
  } catch {
1309
1475
  return {};
1310
1476
  }
@@ -1316,9 +1482,9 @@ function namesIn(manifest) {
1316
1482
  });
1317
1483
  }
1318
1484
  function directDependencies(root, workspaces) {
1319
- const names = new Set(namesIn(readManifest(join4(root, "package.json"))));
1485
+ const names = new Set(namesIn(readManifest(join6(root, "package.json"))));
1320
1486
  for (const workspace of workspaces) {
1321
- for (const name of namesIn(readManifest(join4(root, workspace, "package.json"))))
1487
+ for (const name of namesIn(readManifest(join6(root, workspace, "package.json"))))
1322
1488
  names.add(name);
1323
1489
  }
1324
1490
  return names;
@@ -1330,8 +1496,8 @@ function workspacePatterns(field) {
1330
1496
  return Array.isArray(packages) ? packages : [];
1331
1497
  }
1332
1498
  function workspaceFolders(root, children) {
1333
- const patterns = workspacePatterns(readManifest(join4(root, "package.json")).workspaces);
1334
- return patterns.filter((pattern) => typeof pattern === "string").flatMap((pattern) => pattern.endsWith("/*") ? children(pattern.slice(0, -2)).map((name) => join4(pattern.slice(0, -2), name)) : [pattern]);
1499
+ const patterns = workspacePatterns(readManifest(join6(root, "package.json")).workspaces);
1500
+ return patterns.filter((pattern) => typeof pattern === "string").flatMap((pattern) => pattern.endsWith("/*") ? children(pattern.slice(0, -2)).map((name) => join6(pattern.slice(0, -2), name)) : [pattern]);
1335
1501
  }
1336
1502
 
1337
1503
  // src/audit.ts
@@ -1352,7 +1518,7 @@ var MAX_DEPTH = 3;
1352
1518
  var CONCURRENCY = 4;
1353
1519
  var AUDIT_MS = 60000;
1354
1520
  function holdsLock(dir) {
1355
- return existsSync3(join5(dir, "bun.lock")) || existsSync3(join5(dir, "bun.lockb"));
1521
+ return existsSync5(join7(dir, "bun.lock")) || existsSync5(join7(dir, "bun.lockb"));
1356
1522
  }
1357
1523
  async function findRepos(root) {
1358
1524
  if (holdsLock(root))
@@ -1368,7 +1534,7 @@ async function findRepos(root) {
1368
1534
  for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
1369
1535
  if (!entry.isDirectory() || entry.name === "node_modules" || entry.name.startsWith("."))
1370
1536
  continue;
1371
- const path = join5(dir, entry.name);
1537
+ const path = join7(dir, entry.name);
1372
1538
  if (holdsLock(path))
1373
1539
  found.push(path);
1374
1540
  else if (depth < MAX_DEPTH)
@@ -1393,7 +1559,7 @@ async function pool(items, limit, task) {
1393
1559
  function children(root) {
1394
1560
  return (folder) => {
1395
1561
  try {
1396
- return readdirSync(join5(root, folder), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
1562
+ return readdirSync(join7(root, folder), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
1397
1563
  } catch {
1398
1564
  return [];
1399
1565
  }
@@ -1420,7 +1586,7 @@ function failure(ran) {
1420
1586
  `).at(-1) ?? `exit ${ran.code}`;
1421
1587
  }
1422
1588
  function tint(severity) {
1423
- const colors = paint(process12.stdout);
1589
+ const colors = paint(process14.stdout);
1424
1590
  if (severity === "critical" || severity === "high")
1425
1591
  return colors.red;
1426
1592
  return severity === "moderate" ? colors.yellow : colors.dim;
@@ -1437,8 +1603,8 @@ function rows(findings) {
1437
1603
  }
1438
1604
  async function audit(args) {
1439
1605
  const { values, positionals } = parse(args, USAGE, OPTIONS, { positionals: ["[root]"], exits: EXITS, replaces: "a bun audit run in each repository and a read of every advisory to find what is worth fixing", examples: ["audit", "audit ~/Workspace --level high --prod"] });
1440
- const root = resolve(positionals[0] ?? ".");
1441
- if (!existsSync3(root) || !statSync(root).isDirectory())
1606
+ const root = resolve3(positionals[0] ?? ".");
1607
+ if (!existsSync5(root) || !statSync2(root).isDirectory())
1442
1608
  fail(`no folder ${root}`);
1443
1609
  const level = oneOf(values.level, "level", SEVERITIES);
1444
1610
  const paths = await findRepos(root);
@@ -1455,8 +1621,8 @@ async function audit(args) {
1455
1621
  return kept.some((each) => each.findings.length > 0) ? 1 : 0;
1456
1622
  }
1457
1623
  function show(audits, total) {
1458
- const label = role("label", process12.stdout);
1459
- const path = role("path", process12.stdout);
1624
+ const label = role("label", process14.stdout);
1625
+ const path = role("path", process14.stdout);
1460
1626
  const flagged = audits.filter((each) => each.error !== undefined || each.findings.length > 0);
1461
1627
  if (flagged.length === 0) {
1462
1628
  console.log(label(`no advisory in ${total} ${total === 1 ? "repository" : "repositories"}`));
@@ -1470,16 +1636,16 @@ function show(audits, total) {
1470
1636
  }
1471
1637
 
1472
1638
  // src/repos.ts
1473
- import { existsSync as existsSync5, statSync as statSync2 } from "fs";
1474
- import { homedir as homedir3 } from "os";
1475
- import { join as join7, relative as relative2, resolve as resolve2 } from "path";
1476
- import process14 from "process";
1639
+ import { existsSync as existsSync7, statSync as statSync3 } from "fs";
1640
+ import { homedir as homedir4 } from "os";
1641
+ import { join as join9, relative as relative2, resolve as resolve4 } from "path";
1642
+ import process16 from "process";
1477
1643
 
1478
1644
  // src/repos-scan.ts
1479
- import { existsSync as existsSync4 } from "fs";
1645
+ import { existsSync as existsSync6 } from "fs";
1480
1646
  import { readdir as readdir2 } from "fs/promises";
1481
- import { basename, join as join6 } from "path";
1482
- import process13 from "process";
1647
+ import { basename, join as join8 } from "path";
1648
+ import process15 from "process";
1483
1649
  var MAX_DEPTH2 = 3;
1484
1650
  var CONCURRENCY2 = 8;
1485
1651
  var GIT_MS = 1e4;
@@ -1500,7 +1666,7 @@ function reasonsOf(repo) {
1500
1666
  return reasons;
1501
1667
  }
1502
1668
  async function findRepos2(root, skip) {
1503
- if (existsSync4(join6(root, ".git")))
1669
+ if (existsSync6(join8(root, ".git")))
1504
1670
  return [root];
1505
1671
  const found = [];
1506
1672
  async function walk(dir, depth) {
@@ -1513,8 +1679,8 @@ async function findRepos2(root, skip) {
1513
1679
  for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
1514
1680
  if (!entry.isDirectory() || entry.name === "node_modules" || entry.name.startsWith(".") || skip.has(entry.name))
1515
1681
  continue;
1516
- const path = join6(dir, entry.name);
1517
- if (existsSync4(join6(path, ".git")))
1682
+ const path = join8(dir, entry.name);
1683
+ if (existsSync6(join8(path, ".git")))
1518
1684
  found.push(path);
1519
1685
  else if (depth < MAX_DEPTH2)
1520
1686
  await walk(path, depth + 1);
@@ -1525,7 +1691,7 @@ async function findRepos2(root, skip) {
1525
1691
  }
1526
1692
  async function read(path, args, timeoutMs = GIT_MS) {
1527
1693
  try {
1528
- const ran = await runCommand(["git", "--no-optional-locks", ...args], { cwd: path, env: { ...process13.env, GIT_TERMINAL_PROMPT: "0" }, timeoutMs });
1694
+ const ran = await runCommand(["git", "--no-optional-locks", ...args], { cwd: path, env: { ...process15.env, GIT_TERMINAL_PROMPT: "0" }, timeoutMs });
1529
1695
  return ran.code === 0 && !ran.timedOut ? ran.text.trim() : undefined;
1530
1696
  } catch {
1531
1697
  return;
@@ -1545,7 +1711,7 @@ async function packageReleases(path) {
1545
1711
  }
1546
1712
  const releases = [];
1547
1713
  for (const name of [...names].toSorted()) {
1548
- const folder = [join6("packages", name), name].find((candidate) => existsSync4(join6(path, candidate)));
1714
+ const folder = [join8("packages", name), name].find((candidate) => existsSync6(join8(path, candidate)));
1549
1715
  const tag = folder === undefined ? undefined : await read(path, ["describe", "--tags", "--abbrev=0", "--match", `${name}-v*`]);
1550
1716
  if (folder !== undefined && tag !== undefined && tag !== "")
1551
1717
  releases.push({ name, tag, unreleased: await commitsSince(path, tag, folder) });
@@ -1604,7 +1770,7 @@ function firstLine(text) {
1604
1770
  }
1605
1771
  async function fastForward(path) {
1606
1772
  try {
1607
- const ran = await runCommand(["git", "--no-optional-locks", "merge", "--ff-only", "--quiet", "@{upstream}"], { cwd: path, env: { ...process13.env, GIT_TERMINAL_PROMPT: "0" }, timeoutMs: GIT_MS });
1773
+ const ran = await runCommand(["git", "--no-optional-locks", "merge", "--ff-only", "--quiet", "@{upstream}"], { cwd: path, env: { ...process15.env, GIT_TERMINAL_PROMPT: "0" }, timeoutMs: GIT_MS });
1608
1774
  if (ran.timedOut)
1609
1775
  return "timed out";
1610
1776
  return ran.code === 0 ? undefined : firstLine(ran.text.trim()) || `exit ${ran.code}`;
@@ -1644,7 +1810,7 @@ var OPTIONS2 = {
1644
1810
  };
1645
1811
  async function skipped() {
1646
1812
  const file = userConfigReadPath("repos");
1647
- if (!existsSync5(file))
1813
+ if (!existsSync7(file))
1648
1814
  return new Set;
1649
1815
  try {
1650
1816
  const config = await Bun.file(file).json();
@@ -1658,17 +1824,39 @@ function releasedCell(repo) {
1658
1824
  return repo.packages.filter((release) => release.unreleased > 0).map((release) => `${release.name} ${release.unreleased}`).join(", ") || "-";
1659
1825
  return repo.tag === null || repo.unreleased === 0 ? "-" : `${repo.unreleased} since ${repo.tag}`;
1660
1826
  }
1661
- function row(repo, shown) {
1662
- const tint = role("path", process14.stdout);
1827
+ function syncCell(repo, stream = process16.stdout) {
1828
+ const colors = paint(stream);
1663
1829
  const ahead = repo.ahead ?? 0;
1664
1830
  const behind = repo.behind ?? 0;
1665
- const sync = repo.upstream === null ? "no upstream" : [ahead > 0 ? `+${ahead}` : "", behind > 0 ? `-${behind}` : ""].filter(Boolean).join(" ") || "-";
1666
- return [tint(shown), repo.unreadable ? "? (unreadable)" : repo.branch, repo.dirty === 0 ? "-" : String(repo.dirty), sync, repo.worktrees === 0 ? "-" : String(repo.worktrees), releasedCell(repo)];
1831
+ if (repo.upstream === null)
1832
+ return colors.dim("no upstream");
1833
+ const parts = [ahead > 0 ? colors.green(`+${ahead}`) : "", behind > 0 ? colors.red(`-${behind}`) : ""].filter(Boolean);
1834
+ const diverged = takesControlCodes(stream) && ahead > 0 && behind > 0;
1835
+ return parts.length === 0 ? colors.dim("-") : `${parts.join(" ")}${diverged ? colors.yellow(" diverged") : ""}`;
1836
+ }
1837
+ function branchCell(repo) {
1838
+ const colors = paint(process16.stdout);
1839
+ if (repo.unreadable)
1840
+ return colors.red("? (unreadable)");
1841
+ return repo.branch === "(detached)" ? colors.yellow(repo.branch) : repo.branch;
1842
+ }
1843
+ function row(repo, shown) {
1844
+ const colors = paint(process16.stdout);
1845
+ const tint = role("path", process16.stdout);
1846
+ const unreleased = releasedCell(repo);
1847
+ return [
1848
+ tint(shown),
1849
+ branchCell(repo),
1850
+ repo.dirty === 0 ? colors.dim("-") : colors.yellow(String(repo.dirty)),
1851
+ syncCell(repo),
1852
+ repo.worktrees === 0 ? colors.dim("-") : String(repo.worktrees),
1853
+ unreleased === "-" ? colors.dim("-") : colors.cyan(unreleased)
1854
+ ];
1667
1855
  }
1668
1856
  async function repos(args) {
1669
1857
  const { values, positionals } = parse(args, USAGE2, OPTIONS2, { positionals: ["[root]"], exits: EXITS2, replaces: "a cd, git status, git log and git tag loop over each repository of the workspace", examples: ["repos", "repos ~/Workspace --only unreleased --fetch", ["repos --only behind --pull", "fast-forward what is behind and clean"], ["repos --only dirty --path", "the paths, for xargs"]] });
1670
- const root = resolve2(positionals[0] ?? join7(homedir3(), "Workspace"));
1671
- if (!existsSync5(root) || !statSync2(root).isDirectory())
1858
+ const root = resolve4(positionals[0] ?? join9(homedir4(), "Workspace"));
1859
+ if (!existsSync7(root) || !statSync3(root).isDirectory())
1672
1860
  fail(`no folder ${root}`);
1673
1861
  const only = values.only === undefined ? undefined : oneOf(values.only, "only", REASONS);
1674
1862
  const paths = await findRepos2(root, await skipped());
@@ -1686,12 +1874,12 @@ async function repos(args) {
1686
1874
  const blocker = pullBlocker(repo);
1687
1875
  const refused = blocker === undefined ? await fastForward(repo.path) : undefined;
1688
1876
  if (blocker !== undefined) {
1689
- log(marked("note", `${repo.name} skipped`, process14.stdout, blocker));
1877
+ log(marked("note", `${repo.name} skipped`, process16.stdout, blocker));
1690
1878
  } else if (refused !== undefined) {
1691
1879
  failedPulls++;
1692
- log(marked("problem", `${repo.name} not pulled`, process14.stdout, refused));
1880
+ log(marked("problem", `${repo.name} not pulled`, process16.stdout, refused));
1693
1881
  } else {
1694
- log(marked("ok", `${repo.name} pulled`, process14.stdout, `${repo.behind} ${repo.behind === 1 ? "commit" : "commits"}`));
1882
+ log(marked("ok", `${repo.name} pulled`, process16.stdout, `${repo.behind} ${repo.behind === 1 ? "commit" : "commits"}`));
1695
1883
  states[index] = await inspect(repo.path, false);
1696
1884
  }
1697
1885
  }
@@ -1706,7 +1894,7 @@ async function repos(args) {
1706
1894
  printJson(kept.map((repo) => ({ ...repo, reasons: reasonsOf(repo) })));
1707
1895
  return failedPulls > 0 ? 1 : 0;
1708
1896
  }
1709
- const label = role("label", process14.stdout);
1897
+ const label = role("label", process16.stdout);
1710
1898
  if (kept.length === 0) {
1711
1899
  console.log(label(`nothing needs attention in ${states.length} repositories`));
1712
1900
  return failedPulls > 0 ? 1 : 0;
@@ -1721,17 +1909,17 @@ async function repos(args) {
1721
1909
  }
1722
1910
 
1723
1911
  // src/sweep.ts
1724
- import { existsSync as existsSync8, statSync as statSync3 } from "fs";
1725
- import { homedir as homedir5 } from "os";
1726
- import { join as join11, relative as relative3, resolve as resolve3 } from "path";
1912
+ import { existsSync as existsSync10, statSync as statSync4 } from "fs";
1913
+ import { homedir as homedir6 } from "os";
1914
+ import { join as join13, relative as relative3, resolve as resolve5 } from "path";
1727
1915
 
1728
1916
  // ../kit/src/prompt.ts
1729
- import process15 from "process";
1917
+ import process17 from "process";
1730
1918
  var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|[\s\S]/giu;
1731
- var CONTROL = /^[\x00-\x1F\x7F]/;
1919
+ var CONTROL2 = /^[\x00-\x1F\x7F]/;
1732
1920
  var KEYS = { "\x1B[A": "up", "\x1BOA": "up", "\x1B[B": "down", "\x1BOB": "down", "\r": "enter", "\n": "enter", "\x03": "cancel", "\x1B": "cancel", "\x7F": "erase", "\b": "erase" };
1733
1921
  function readKeys(input) {
1734
- return (input.match(KEY_SEQUENCES) ?? []).flatMap((sequence) => KEYS[sequence] ?? (CONTROL.test(sequence) ? [] : [{ text: sequence }]));
1922
+ return (input.match(KEY_SEQUENCES) ?? []).flatMap((sequence) => KEYS[sequence] ?? (CONTROL2.test(sequence) ? [] : [{ text: sequence }]));
1735
1923
  }
1736
1924
  async function confirm(question, { default: fallback, ...settings } = {}) {
1737
1925
  let answered = fallback;
@@ -1755,7 +1943,7 @@ function asking(colors, question, typed, keys) {
1755
1943
  function answer(colors, question, state, picked) {
1756
1944
  return state === "enter" ? `${colors.green("\u2713")} ${question} ${colors.cyan(picked)}` : `${colors.red("\u2717")} ${question} ${colors.dim("cancelled")}`;
1757
1945
  }
1758
- async function ask(question, { hint, terminal = process15 }, render, press) {
1946
+ async function ask(question, { hint, terminal = process17 }, render, press) {
1759
1947
  const { stdin, stderr } = terminal;
1760
1948
  if (stdin.isTTY !== true || stderr.isTTY !== true)
1761
1949
  fail(`cannot ask "${question}" off a terminal`, 2, hint);
@@ -1806,19 +1994,19 @@ async function ask(question, { hint, terminal = process15 }, render, press) {
1806
1994
  }
1807
1995
 
1808
1996
  // src/apply.ts
1809
- import { existsSync as existsSync7 } from "fs";
1997
+ import { existsSync as existsSync9 } from "fs";
1810
1998
  import { lstat as lstat3, rm } from "fs/promises";
1811
- import { basename as basename3, join as join10 } from "path";
1999
+ import { basename as basename3, join as join12 } from "path";
1812
2000
 
1813
2001
  // src/sweep-scan.ts
1814
- import { existsSync as existsSync6 } from "fs";
2002
+ import { existsSync as existsSync8 } from "fs";
1815
2003
  import { lstat as lstat2, readdir as readdir4, realpath } from "fs/promises";
1816
- import { homedir as homedir4 } from "os";
1817
- import { basename as basename2, dirname as dirname3, join as join9 } from "path";
2004
+ import { homedir as homedir5 } from "os";
2005
+ import { basename as basename2, dirname as dirname4, join as join11 } from "path";
1818
2006
 
1819
2007
  // src/size.ts
1820
2008
  import { lstat, readdir as readdir3 } from "fs/promises";
1821
- import { join as join8 } from "path";
2009
+ import { join as join10 } from "path";
1822
2010
  async function pooled(items, limit, work) {
1823
2011
  const results = Array.from({ length: items.length });
1824
2012
  let next = 0;
@@ -1855,7 +2043,7 @@ async function sizeOf(folder) {
1855
2043
  return;
1856
2044
  }
1857
2045
  await Promise.all(entries.map(async (entry) => {
1858
- const path = join8(dir, entry.name);
2046
+ const path = join10(dir, entry.name);
1859
2047
  if (entry.isDirectory())
1860
2048
  return visit(path);
1861
2049
  try {
@@ -1879,8 +2067,8 @@ var FOLDER_NAMES = new Set(["node_modules", ".nuxt", ".next", ".output", "dist",
1879
2067
  var MAX_DEPTH3 = 3;
1880
2068
  var PROJECT_CACHE_NAMES = new Set([".turbo", ".nuxt", ".next", ".svelte-kit"]);
1881
2069
  var GLOBAL_CACHES = [".bun/install/cache", ".npm/_cacache", ".cache/pip", ".cache/uv", ".cache/ms-playwright", ".cache/puppeteer", ".cache/go-build", ".cache/typescript", ".cargo/registry"];
1882
- function isCachePath(path, home = homedir4()) {
1883
- return GLOBAL_CACHES.some((cache) => join9(home, cache) === path) || basename2(path) === ".cache" && basename2(dirname3(path)) === "node_modules";
2070
+ function isCachePath(path, home = homedir5()) {
2071
+ return GLOBAL_CACHES.some((cache) => join11(home, cache) === path) || basename2(path) === ".cache" && basename2(dirname4(path)) === "node_modules";
1884
2072
  }
1885
2073
  async function listing(dir) {
1886
2074
  try {
@@ -1891,7 +2079,7 @@ async function listing(dir) {
1891
2079
  }
1892
2080
  async function addGlobalCaches(found, home) {
1893
2081
  for (const cache of GLOBAL_CACHES) {
1894
- const path = join9(home, cache);
2082
+ const path = join11(home, cache);
1895
2083
  try {
1896
2084
  const stat = await lstat2(path);
1897
2085
  if (stat.isSymbolicLink())
@@ -1901,12 +2089,12 @@ async function addGlobalCaches(found, home) {
1901
2089
  } catch {}
1902
2090
  }
1903
2091
  }
1904
- async function findCandidates(root, home = homedir4(), withGlobal = true) {
2092
+ async function findCandidates(root, home = homedir5(), withGlobal = true) {
1905
2093
  const found = { candidates: [], skipped: [] };
1906
2094
  const visit = async (dir, depth) => {
1907
2095
  const entries = await listing(dir);
1908
2096
  for (const entry of entries) {
1909
- const path = join9(dir, entry.name);
2097
+ const path = join11(dir, entry.name);
1910
2098
  if (entry.name === ".git")
1911
2099
  continue;
1912
2100
  if (entry.name === ".worktrees" || entry.name === "worktrees" && basename2(dir) === ".claude") {
@@ -1916,13 +2104,13 @@ async function findCandidates(root, home = homedir4(), withGlobal = true) {
1916
2104
  }
1917
2105
  for (const worktree of await listing(path)) {
1918
2106
  if (worktree.isDirectory())
1919
- found.candidates.push({ path: join9(path, worktree.name), kind: "worktree" });
2107
+ found.candidates.push({ path: join11(path, worktree.name), kind: "worktree" });
1920
2108
  else if (worktree.isSymbolicLink())
1921
- found.skipped.push({ path: join9(path, worktree.name), reason: "symlink" });
2109
+ found.skipped.push({ path: join11(path, worktree.name), reason: "symlink" });
1922
2110
  }
1923
2111
  continue;
1924
2112
  }
1925
- if (FOLDER_NAMES.has(entry.name) && (entry.name !== "target" || existsSync6(join9(dir, "Cargo.toml")))) {
2113
+ if (FOLDER_NAMES.has(entry.name) && (entry.name !== "target" || existsSync8(join11(dir, "Cargo.toml")))) {
1926
2114
  if (entry.isSymbolicLink()) {
1927
2115
  found.skipped.push({ path, reason: "symlink" });
1928
2116
  } else if (entry.isDirectory()) {
@@ -1930,9 +2118,9 @@ async function findCandidates(root, home = homedir4(), withGlobal = true) {
1930
2118
  if (entry.name === "node_modules") {
1931
2119
  const cache = (await listing(path)).find((inner) => inner.name === ".cache");
1932
2120
  if (cache?.isSymbolicLink())
1933
- found.skipped.push({ path: join9(path, ".cache"), reason: "symlink" });
2121
+ found.skipped.push({ path: join11(path, ".cache"), reason: "symlink" });
1934
2122
  else if (cache?.isDirectory())
1935
- found.candidates.push({ path: join9(path, ".cache"), kind: "cache", inside: path });
2123
+ found.candidates.push({ path: join11(path, ".cache"), kind: "cache", inside: path });
1936
2124
  }
1937
2125
  }
1938
2126
  continue;
@@ -2003,12 +2191,12 @@ var worktreesOf = memo(async (repo) => {
2003
2191
  return branches;
2004
2192
  });
2005
2193
  async function judgeFolder(path, kind, inside) {
2006
- const parent = dirname3(path);
2194
+ const parent = dirname4(path);
2007
2195
  const name = basename2(path);
2008
2196
  const located = await toplevelOf(parent);
2009
2197
  if (located === "failed")
2010
2198
  return { skip: "git failed" };
2011
- if (existsSync6(join9(path, ".git")))
2199
+ if (existsSync8(join11(path, ".git")))
2012
2200
  return { skip: "holds a git repository" };
2013
2201
  if (located === "none")
2014
2202
  return name === "node_modules" || inside !== undefined ? { entry: { path, kind, committedAt: undefined, action: "remove", inside } } : { skip: "not in a git repository" };
@@ -2020,7 +2208,7 @@ async function judgeFolder(path, kind, inside) {
2020
2208
  return { entry: { path, kind, committedAt: await lastCommitOf(located.top), action: "remove", inside } };
2021
2209
  }
2022
2210
  async function judgeWorktree(path) {
2023
- const located = await toplevelOf(dirname3(path));
2211
+ const located = await toplevelOf(dirname4(path));
2024
2212
  if (located === "failed")
2025
2213
  return { skip: "git failed" };
2026
2214
  if (located === "none")
@@ -2076,7 +2264,7 @@ async function removeFolder(entry) {
2076
2264
  const stat = await lstat3(entry.path);
2077
2265
  if (stat.isSymbolicLink() || !stat.isDirectory() || !(FOLDER_NAMES.has(basename3(entry.path)) || entry.kind === "cache" && isCachePath(entry.path)))
2078
2266
  return { path: entry.path, removed: false, error: "no longer a plain folder" };
2079
- if (existsSync7(join10(entry.path, ".git")))
2267
+ if (existsSync9(join12(entry.path, ".git")))
2080
2268
  return { path: entry.path, removed: false, error: "holds a git repository" };
2081
2269
  await rm(entry.path, { recursive: true, force: true });
2082
2270
  return { path: entry.path, removed: true };
@@ -2107,6 +2295,8 @@ async function applyAll(entries, done) {
2107
2295
  }
2108
2296
 
2109
2297
  // src/sweep.ts
2298
+ var LARGE = 100 * 1024 ** 2;
2299
+ var BIG = 1024 ** 3;
2110
2300
  var USAGE3 = `sweep [options] [root]: lists the regenerable folders (node_modules, .output, dist, build, target beside a Cargo.toml), the caches (.nuxt, .next, .turbo, .svelte-kit and node_modules/.cache per project; ~/.bun/install/cache, ~/.npm/_cacache, ~/.cache/{pip,uv,ms-playwright,puppeteer,go-build,typescript} and ~/.cargo/registry, exact paths only, listed only when no root is given or with --only caches) and the .worktrees/* and .claude/worktrees/* entries under root (~/Workspace by default, ${MAX_DEPTH3} levels down), largest first with the age of the repo's last commit; --only worktrees, folders or caches keeps one kind
2111
2301
 
2112
2302
  Dry unless --apply, which deletes after a confirm showing the total (read from the terminal; off one, pass --yes). A folder is deleted for good, not trashed. Never touched: a symlink, a folder git tracks files in, a build folder git does not ignore. A worktree is removed with git worktree remove, never forced, and only when it is clean and every commit of its branch is on another branch or a remote; the others stay listed as kept and count for nothing in the total. With --docker the Docker usage comes after the list, left to dclean.
@@ -2136,8 +2326,8 @@ async function sweep(args) {
2136
2326
  const { values, positionals } = parse(args, USAGE3, OPTIONS3, { positionals: ["[root]"], replaces: "find ~/Workspace -name node_modules, du -sh and rm -rf by hand", examples: ["sweep", "sweep ~/Workspace --older 30d --min 100M --apply", "sweep --only worktrees --apply"] });
2137
2327
  if (positionals.length > 1)
2138
2328
  badUsage("give one root");
2139
- const root = resolve3(positionals[0] ?? join11(homedir5(), "Workspace"));
2140
- if (!existsSync8(root) || !statSync3(root).isDirectory())
2329
+ const root = resolve5(positionals[0] ?? join13(homedir6(), "Workspace"));
2330
+ if (!existsSync10(root) || !statSync4(root).isDirectory())
2141
2331
  fail(`${root} is not a folder`);
2142
2332
  if (values.only !== undefined && !Object.hasOwn(ONLY_KINDS, values.only))
2143
2333
  badUsage(`--only takes worktrees, folders or caches, not ${values.only}`);
@@ -2146,7 +2336,7 @@ async function sweep(args) {
2146
2336
  if (minBytes === undefined)
2147
2337
  badUsage(`--min takes a size such as 500K, 10M or 1G, not ${values.min}`);
2148
2338
  const now = Date.now();
2149
- const found = await findCandidates(root, homedir5(), positionals.length === 0 || values.only === "caches");
2339
+ const found = await findCandidates(root, homedir6(), positionals.length === 0 || values.only === "caches");
2150
2340
  const wanted = values.only === undefined ? found.candidates : found.candidates.filter((candidate) => candidate.kind === ONLY_KINDS[values.only]);
2151
2341
  const { entries: all, skipped } = await inspect2({ candidates: wanted, skipped: found.skipped });
2152
2342
  const entries = all.filter((entry) => entry.bytes >= minBytes && (olderMs === undefined || entry.committedAt !== undefined && now - entry.committedAt >= olderMs)).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path));
@@ -2198,13 +2388,20 @@ function print(root, entries, skipped, reclaimable, now) {
2198
2388
  console.log("nothing to sweep");
2199
2389
  return;
2200
2390
  }
2201
- console.log(table([["size", "age", "what", "path", "note"], ...entries.map((entry) => [$e(entry.bytes), entry.committedAt === undefined ? "-" : formatAge(entry.committedAt, now), entry.kind, relative3(root, entry.path), entry.action === "keep" ? `kept: ${entry.reason}` : ""])], { header: true, align: ["right", "right", "left", "left", "left"] }));
2391
+ const colors = paint(process.stdout);
2392
+ const path = role("path", process.stdout);
2393
+ const sized = (bytes) => {
2394
+ if (bytes >= BIG)
2395
+ return colors.red($e(bytes));
2396
+ return bytes >= LARGE ? colors.yellow($e(bytes)) : $e(bytes);
2397
+ };
2398
+ console.log(table([["size", "age", "what", "path", "note"], ...entries.map((entry) => [sized(entry.bytes), entry.committedAt === undefined ? colors.dim("-") : colors.dim(formatAge(entry.committedAt, now)), entry.kind, path(relative3(root, entry.path)), entry.action === "keep" ? colors.dim(`kept: ${entry.reason}`) : ""])], { header: true, align: ["right", "right", "left", "left", "left"] }));
2202
2399
  console.log(`
2203
2400
  ${$e(reclaimable)} reclaimable${skipped === 0 ? "" : `, ${skipped} skipped (symlinks, tracked or not ignored by git)`}`);
2204
2401
  }
2205
2402
 
2206
2403
  // src/cli.ts
2207
- process16.exitCode = await runCli({
2404
+ process18.exitCode = await runCli({
2208
2405
  name: "ws",
2209
2406
  about: "the repositories under a root: the ones needing attention, the folders worth reclaiming and their known vulnerabilities",
2210
2407
  replaces: "a cd, git status and du loop over each repository of the workspace, and a bun audit run in each",