@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/repos-cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // @bun
3
3
 
4
4
  // src/repos-cli.ts
5
- import process14 from "process";
5
+ import process16 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: {}
333
+ };
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 }
292
342
  };
293
- function takesControlCodes(stream, env = process2.env) {
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
 
333
517
  // ../kit/src/format.ts
334
518
  var UNITS = [["d", 86400], ["h", 3600], ["m", 60], ["s", 1]];
@@ -367,7 +551,7 @@ var ESCAPE = String.fromCharCode(27);
367
551
  function textWidth(text) {
368
552
  return Bun.stringWidth(text);
369
553
  }
370
- function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
554
+ function table(rows, { cellWidth, header = false, align = [], fit, stream = process6.stdout } = {}) {
371
555
  const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
372
556
  const widths = [];
373
557
  for (const row of cells) {
@@ -375,7 +559,7 @@ function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
375
559
  widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
376
560
  }
377
561
  const shown = fit === undefined ? cells : narrow(cells, widths, fit);
378
- const colors = paint(process3.stdout);
562
+ const colors = paint(stream);
379
563
  const lines = shown.map((row) => row.map((cell, index) => {
380
564
  const width = widths[index] ?? 0;
381
565
  const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
@@ -383,7 +567,8 @@ function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
383
567
  return `${gap}${cell}`;
384
568
  return index === row.length - 1 ? cell : `${cell}${gap}`;
385
569
  }).join(" ").trimEnd());
386
- return lines.map((line, index) => header && index === 0 ? colors.bold(line) : line).join(`
570
+ const rule = header && takesControlCodes(stream) && lines.length > 1 ? [colors.dim("\u2500".repeat(Math.max(...lines.map(textWidth))))] : [];
571
+ return lines.flatMap((line, index) => header && index === 0 ? [colors.bold(line), ...rule] : [line]).join(`
387
572
  `);
388
573
  }
389
574
  function narrow(rows, widths, limit) {
@@ -407,27 +592,27 @@ function printJson(value) {
407
592
  }
408
593
 
409
594
  // ../kit/src/help.ts
410
- import process4 from "process";
595
+ import process7 from "process";
411
596
  var OPTIONAL = /\[[^\]]+\]/g;
412
597
  var USAGE_PREFIX = /^usage:\s*/i;
413
598
  var FULL_STOP = /\.$/;
414
- function usageLine(usage, colors = paint(process4.stdout)) {
599
+ function usageLine(usage, colors = paint(process7.stdout)) {
415
600
  return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
416
601
  }
417
- function helpText(usage, options, colors = paint(process4.stdout), { examples = [], replaces, exits = [] } = {}) {
602
+ function helpText(usage, options, colors = paint(process7.stdout), { examples = [], replaces, exits = [] } = {}) {
418
603
  return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
419
604
  `);
420
605
  }
421
- function exitRows(exits, colors = paint(process4.stdout)) {
606
+ function exitRows(exits, colors = paint(process7.stdout)) {
422
607
  if (exits.length === 0)
423
608
  return [];
424
609
  const width = Math.max(...exits.map(([code]) => String(code).length));
425
610
  return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
426
611
  }
427
- function replacesRows(replaces, colors = paint(process4.stdout)) {
612
+ function replacesRows(replaces, colors = paint(process7.stdout)) {
428
613
  return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
429
614
  }
430
- function wantsHelpJson(env = process4.env) {
615
+ function wantsHelpJson(env = process7.env) {
431
616
  return env.KIT_HELP === "json";
432
617
  }
433
618
  function description(usage, { examples = [], replaces, exits } = {}, options) {
@@ -446,12 +631,12 @@ function description(usage, { examples = [], replaces, exits } = {}, options) {
446
631
  ...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
447
632
  };
448
633
  }
449
- function exampleRows(examples, colors = paint(process4.stdout)) {
634
+ function exampleRows(examples, colors = paint(process7.stdout)) {
450
635
  if (examples.length === 0)
451
636
  return [];
452
637
  return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
453
638
  }
454
- function optionRows(options, colors = paint(process4.stdout)) {
639
+ function optionRows(options, colors = paint(process7.stdout)) {
455
640
  const rows = Object.entries(options).map(([name, option]) => {
456
641
  const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
457
642
  const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
@@ -544,7 +729,7 @@ var OUTPUT_OPTIONS = {
544
729
  color: { type: "string", description: "auto (on a terminal alone), always or never" },
545
730
  plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
546
731
  };
547
- function applyOutput(color, plain, env = process5.env) {
732
+ function applyOutput(color, plain, env = process8.env) {
548
733
  const when = plain === true ? "never" : color ?? "auto";
549
734
  if (when === "auto")
550
735
  return;
@@ -715,7 +900,7 @@ function nearestOption(message, options, allowNegative) {
715
900
  }
716
901
  // ../kit/src/process.ts
717
902
  import { constants } from "os";
718
- import process6 from "process";
903
+ import process9 from "process";
719
904
  var KILL_GRACE_MS = 2000;
720
905
  function exitCodeOf(code, signal) {
721
906
  if (code !== null)
@@ -735,7 +920,7 @@ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal,
735
920
  return;
736
921
  try {
737
922
  if (grouped)
738
- process6.kill(-proc.pid, killing);
923
+ process9.kill(-proc.pid, killing);
739
924
  else
740
925
  proc.kill(killing);
741
926
  } catch {}
@@ -810,10 +995,10 @@ var RESET_STYLE = `${ESC}[0m`;
810
995
  var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
811
996
 
812
997
  // ../kit/src/tool.ts
813
- import process7 from "process";
998
+ import process10 from "process";
814
999
  function cwdGone() {
815
1000
  try {
816
- process7.cwd();
1001
+ process10.cwd();
817
1002
  return false;
818
1003
  } catch {
819
1004
  return true;
@@ -835,7 +1020,7 @@ function spawnFailure(command, error) {
835
1020
  async function runTool(label, argv, { timeoutMs, cwd, onStart } = {}) {
836
1021
  let ran;
837
1022
  try {
838
- ran = await runCommand(argv, { env: process7.env, ...timeoutMs === undefined ? {} : { timeoutMs }, ...cwd === undefined ? {} : { cwd }, ...onStart === undefined ? {} : { onStart } });
1023
+ ran = await runCommand(argv, { env: process10.env, ...timeoutMs === undefined ? {} : { timeoutMs }, ...cwd === undefined ? {} : { cwd }, ...onStart === undefined ? {} : { onStart } });
839
1024
  } catch (error) {
840
1025
  throw spawnFailure(label, error);
841
1026
  }
@@ -850,15 +1035,15 @@ async function runTool(label, argv, { timeoutMs, cwd, onStart } = {}) {
850
1035
  return { code: ran.code, ...streams };
851
1036
  }
852
1037
  // ../kit/src/cli.ts
853
- import { readFileSync, realpathSync } from "fs";
854
- import { dirname as dirname2, join as join2 } from "path";
855
- import process9 from "process";
1038
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
1039
+ import { dirname as dirname3, join as join5 } from "path";
1040
+ import process12 from "process";
856
1041
 
857
1042
  // ../kit/src/usage.ts
858
1043
  import { appendFileSync, mkdirSync } from "fs";
859
- import { homedir } from "os";
860
- import { dirname, isAbsolute, join } from "path";
861
- import process8 from "process";
1044
+ import { homedir as homedir3 } from "os";
1045
+ import { dirname as dirname2, isAbsolute, join as join4 } from "path";
1046
+ import process11 from "process";
862
1047
 
863
1048
  // ../kit/src/logs.ts
864
1049
  var DAY_MS = 24 * 60 * 60 * 1000;
@@ -872,14 +1057,14 @@ var usageError;
872
1057
  function noteUsageError(message) {
873
1058
  usageError = message;
874
1059
  }
875
- function stateHome(env = process8.env) {
1060
+ function stateHome(env = process11.env) {
876
1061
  const dir = env.XDG_STATE_HOME;
877
- return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
1062
+ return dir !== undefined && isAbsolute(dir) ? dir : join4(homedir3(), ".local", "state");
878
1063
  }
879
- function usagePath(env = process8.env) {
880
- return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
1064
+ function usagePath(env = process11.env) {
1065
+ return join4(stateHome(env), CLI_FOLDER, "usage.jsonl");
881
1066
  }
882
- function usageRecorded(env = process8.env) {
1067
+ function usageRecorded(env = process11.env) {
883
1068
  const off = env.KAYNOOO_CLI_NO_USAGE;
884
1069
  return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
885
1070
  }
@@ -893,12 +1078,12 @@ function usageEntry(bin, command, argv, code, now = new Date) {
893
1078
  usageError = undefined;
894
1079
  return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
895
1080
  }
896
- function recordUsage(entry, env = process8.env) {
1081
+ function recordUsage(entry, env = process11.env) {
897
1082
  if (!usageRecorded(env))
898
1083
  return;
899
1084
  try {
900
1085
  const path = usagePath(env);
901
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
1086
+ mkdirSync(dirname2(path), { recursive: true, mode: 448 });
902
1087
  appendFileSync(path, `${JSON.stringify(entry)}
903
1088
  `, { mode: 384 });
904
1089
  } catch {}
@@ -984,8 +1169,8 @@ async function help2({ path, header, summary: lazySummary, commands, defaultComm
984
1169
  `);
985
1170
  }
986
1171
  function printError(prefix, message, hint, cause) {
987
- const colors = paint(process9.stderr);
988
- const styled = takesControlCodes(process9.stderr);
1172
+ const colors = paint(process12.stderr);
1173
+ const styled = takesControlCodes(process12.stderr);
989
1174
  const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
990
1175
  const under = (label, text, tint) => text === undefined ? "" : `
991
1176
  ${styled ? tint(label) : `${label}:`} ${text}`;
@@ -1028,11 +1213,11 @@ async function dispatch(level, typed, { version, redact }) {
1028
1213
  }
1029
1214
  }
1030
1215
  if (name === undefined && level.path.length > 1) {
1031
- console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process9.stderr));
1216
+ console.error(await help2(await expand(level, [...level.path.slice(1), ...argv]), process12.stderr));
1032
1217
  return 2;
1033
1218
  }
1034
1219
  if (name === undefined || name === "help" || name === "--help" || name === "-h") {
1035
- console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process9.stdout));
1220
+ console.log(await help2(await expand(level, [...level.path.slice(1), ...argv]), process12.stdout));
1036
1221
  return 0;
1037
1222
  }
1038
1223
  if (level.path.length === 1 && (name === "--version" || name === "-v")) {
@@ -1149,17 +1334,17 @@ async function printCandidates(found) {
1149
1334
  return 0;
1150
1335
  }
1151
1336
  function packageVersion(entry = Bun.main) {
1152
- for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
1337
+ for (let dir = dirname3(realpathSync(entry));; dir = dirname3(dir)) {
1153
1338
  try {
1154
- const { version } = JSON.parse(readFileSync(join2(dir, "package.json"), "utf8"));
1339
+ const { version } = JSON.parse(readFileSync2(join5(dir, "package.json"), "utf8"));
1155
1340
  if (typeof version === "string")
1156
1341
  return version;
1157
1342
  } catch {}
1158
- if (dirname2(dir) === dir)
1343
+ if (dirname3(dir) === dir)
1159
1344
  return "0.0.0";
1160
1345
  }
1161
1346
  }
1162
- async function runCli(cli, argv = process9.argv.slice(2)) {
1347
+ async function runCli(cli, argv = process12.argv.slice(2)) {
1163
1348
  const version = cli.version ?? packageVersion();
1164
1349
  if (argv[0] === COMPLETE)
1165
1350
  return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
@@ -1186,7 +1371,7 @@ function commandPath(scope, argv) {
1186
1371
  }
1187
1372
  return path.length === 0 ? undefined : path.join(" ");
1188
1373
  }
1189
- async function runMain({ name, version, run, redact, usage }, argv = process9.argv.slice(2)) {
1374
+ async function runMain({ name, version, run, redact, usage }, argv = process12.argv.slice(2)) {
1190
1375
  if (argv[0] === COMPLETE)
1191
1376
  return printCandidates(leafCandidates(run, argv.slice(1)));
1192
1377
  if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
@@ -1222,36 +1407,17 @@ function parseSize(text, bareUnit = 1) {
1222
1407
  const suffix = match[2].toLowerCase();
1223
1408
  return Math.round(Number(match[1]) * (suffix === "" ? bareUnit : 1024 ** POWERS[suffix]));
1224
1409
  }
1225
- // ../kit/src/user-config.ts
1226
- import { existsSync } from "fs";
1227
- import { homedir as homedir2 } from "os";
1228
- import { join as join3 } from "path";
1229
- import process10 from "process";
1230
- function configBase(where = {}) {
1231
- const xdg = where.xdg ?? (where.home === undefined ? process10.env.XDG_CONFIG_HOME : undefined);
1232
- return xdg !== undefined && xdg !== "" ? xdg : join3(where.home ?? homedir2(), ".config");
1233
- }
1234
- function userConfigPath(bin, where = {}) {
1235
- return join3(configBase(where), "kaynooo-cli", `${bin}.json`);
1236
- }
1237
- function userConfigReadPath(bin, legacy = [], where = {}) {
1238
- const current = userConfigPath(bin, where);
1239
- if (existsSync(current))
1240
- return current;
1241
- const base = configBase(where);
1242
- return legacy.map((parts) => join3(base, ...parts)).find((path) => existsSync(path)) ?? current;
1243
- }
1244
1410
  // src/repos.ts
1245
- import { existsSync as existsSync3, statSync } from "fs";
1246
- import { homedir as homedir3 } from "os";
1247
- import { join as join5, relative, resolve } from "path";
1248
- import process13 from "process";
1411
+ import { existsSync as existsSync5, statSync as statSync2 } from "fs";
1412
+ import { homedir as homedir4 } from "os";
1413
+ import { join as join7, relative, resolve as resolve3 } from "path";
1414
+ import process15 from "process";
1249
1415
 
1250
1416
  // src/repos-scan.ts
1251
- import { existsSync as existsSync2 } from "fs";
1417
+ import { existsSync as existsSync4 } from "fs";
1252
1418
  import { readdir } from "fs/promises";
1253
- import { basename, join as join4 } from "path";
1254
- import process12 from "process";
1419
+ import { basename, join as join6 } from "path";
1420
+ import process14 from "process";
1255
1421
  var MAX_DEPTH = 3;
1256
1422
  var CONCURRENCY = 8;
1257
1423
  var GIT_MS = 1e4;
@@ -1272,7 +1438,7 @@ function reasonsOf(repo) {
1272
1438
  return reasons;
1273
1439
  }
1274
1440
  async function findRepos(root, skip) {
1275
- if (existsSync2(join4(root, ".git")))
1441
+ if (existsSync4(join6(root, ".git")))
1276
1442
  return [root];
1277
1443
  const found = [];
1278
1444
  async function walk(dir, depth) {
@@ -1285,8 +1451,8 @@ async function findRepos(root, skip) {
1285
1451
  for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
1286
1452
  if (!entry.isDirectory() || entry.name === "node_modules" || entry.name.startsWith(".") || skip.has(entry.name))
1287
1453
  continue;
1288
- const path = join4(dir, entry.name);
1289
- if (existsSync2(join4(path, ".git")))
1454
+ const path = join6(dir, entry.name);
1455
+ if (existsSync4(join6(path, ".git")))
1290
1456
  found.push(path);
1291
1457
  else if (depth < MAX_DEPTH)
1292
1458
  await walk(path, depth + 1);
@@ -1297,7 +1463,7 @@ async function findRepos(root, skip) {
1297
1463
  }
1298
1464
  async function read(path, args, timeoutMs = GIT_MS) {
1299
1465
  try {
1300
- const ran = await runCommand(["git", "--no-optional-locks", ...args], { cwd: path, env: { ...process12.env, GIT_TERMINAL_PROMPT: "0" }, timeoutMs });
1466
+ const ran = await runCommand(["git", "--no-optional-locks", ...args], { cwd: path, env: { ...process14.env, GIT_TERMINAL_PROMPT: "0" }, timeoutMs });
1301
1467
  return ran.code === 0 && !ran.timedOut ? ran.text.trim() : undefined;
1302
1468
  } catch {
1303
1469
  return;
@@ -1317,7 +1483,7 @@ async function packageReleases(path) {
1317
1483
  }
1318
1484
  const releases = [];
1319
1485
  for (const name of [...names].toSorted()) {
1320
- const folder = [join4("packages", name), name].find((candidate) => existsSync2(join4(path, candidate)));
1486
+ const folder = [join6("packages", name), name].find((candidate) => existsSync4(join6(path, candidate)));
1321
1487
  const tag = folder === undefined ? undefined : await read(path, ["describe", "--tags", "--abbrev=0", "--match", `${name}-v*`]);
1322
1488
  if (folder !== undefined && tag !== undefined && tag !== "")
1323
1489
  releases.push({ name, tag, unreleased: await commitsSince(path, tag, folder) });
@@ -1376,7 +1542,7 @@ function firstLine(text) {
1376
1542
  }
1377
1543
  async function fastForward(path) {
1378
1544
  try {
1379
- const ran = await runCommand(["git", "--no-optional-locks", "merge", "--ff-only", "--quiet", "@{upstream}"], { cwd: path, env: { ...process12.env, GIT_TERMINAL_PROMPT: "0" }, timeoutMs: GIT_MS });
1545
+ const ran = await runCommand(["git", "--no-optional-locks", "merge", "--ff-only", "--quiet", "@{upstream}"], { cwd: path, env: { ...process14.env, GIT_TERMINAL_PROMPT: "0" }, timeoutMs: GIT_MS });
1380
1546
  if (ran.timedOut)
1381
1547
  return "timed out";
1382
1548
  return ran.code === 0 ? undefined : firstLine(ran.text.trim()) || `exit ${ran.code}`;
@@ -1416,7 +1582,7 @@ var OPTIONS = {
1416
1582
  };
1417
1583
  async function skipped() {
1418
1584
  const file = userConfigReadPath("repos");
1419
- if (!existsSync3(file))
1585
+ if (!existsSync5(file))
1420
1586
  return new Set;
1421
1587
  try {
1422
1588
  const config = await Bun.file(file).json();
@@ -1430,17 +1596,39 @@ function releasedCell(repo) {
1430
1596
  return repo.packages.filter((release) => release.unreleased > 0).map((release) => `${release.name} ${release.unreleased}`).join(", ") || "-";
1431
1597
  return repo.tag === null || repo.unreleased === 0 ? "-" : `${repo.unreleased} since ${repo.tag}`;
1432
1598
  }
1433
- function row(repo, shown) {
1434
- const tint = role("path", process13.stdout);
1599
+ function syncCell(repo, stream = process15.stdout) {
1600
+ const colors = paint(stream);
1435
1601
  const ahead = repo.ahead ?? 0;
1436
1602
  const behind = repo.behind ?? 0;
1437
- const sync = repo.upstream === null ? "no upstream" : [ahead > 0 ? `+${ahead}` : "", behind > 0 ? `-${behind}` : ""].filter(Boolean).join(" ") || "-";
1438
- return [tint(shown), repo.unreadable ? "? (unreadable)" : repo.branch, repo.dirty === 0 ? "-" : String(repo.dirty), sync, repo.worktrees === 0 ? "-" : String(repo.worktrees), releasedCell(repo)];
1603
+ if (repo.upstream === null)
1604
+ return colors.dim("no upstream");
1605
+ const parts = [ahead > 0 ? colors.green(`+${ahead}`) : "", behind > 0 ? colors.red(`-${behind}`) : ""].filter(Boolean);
1606
+ const diverged = takesControlCodes(stream) && ahead > 0 && behind > 0;
1607
+ return parts.length === 0 ? colors.dim("-") : `${parts.join(" ")}${diverged ? colors.yellow(" diverged") : ""}`;
1608
+ }
1609
+ function branchCell(repo) {
1610
+ const colors = paint(process15.stdout);
1611
+ if (repo.unreadable)
1612
+ return colors.red("? (unreadable)");
1613
+ return repo.branch === "(detached)" ? colors.yellow(repo.branch) : repo.branch;
1614
+ }
1615
+ function row(repo, shown) {
1616
+ const colors = paint(process15.stdout);
1617
+ const tint = role("path", process15.stdout);
1618
+ const unreleased = releasedCell(repo);
1619
+ return [
1620
+ tint(shown),
1621
+ branchCell(repo),
1622
+ repo.dirty === 0 ? colors.dim("-") : colors.yellow(String(repo.dirty)),
1623
+ syncCell(repo),
1624
+ repo.worktrees === 0 ? colors.dim("-") : String(repo.worktrees),
1625
+ unreleased === "-" ? colors.dim("-") : colors.cyan(unreleased)
1626
+ ];
1439
1627
  }
1440
1628
  async function repos(args) {
1441
1629
  const { values, positionals } = parse(args, USAGE, OPTIONS, { positionals: ["[root]"], exits: EXITS, 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"]] });
1442
- const root = resolve(positionals[0] ?? join5(homedir3(), "Workspace"));
1443
- if (!existsSync3(root) || !statSync(root).isDirectory())
1630
+ const root = resolve3(positionals[0] ?? join7(homedir4(), "Workspace"));
1631
+ if (!existsSync5(root) || !statSync2(root).isDirectory())
1444
1632
  fail(`no folder ${root}`);
1445
1633
  const only = values.only === undefined ? undefined : oneOf(values.only, "only", REASONS);
1446
1634
  const paths = await findRepos(root, await skipped());
@@ -1458,12 +1646,12 @@ async function repos(args) {
1458
1646
  const blocker = pullBlocker(repo);
1459
1647
  const refused = blocker === undefined ? await fastForward(repo.path) : undefined;
1460
1648
  if (blocker !== undefined) {
1461
- log(marked("note", `${repo.name} skipped`, process13.stdout, blocker));
1649
+ log(marked("note", `${repo.name} skipped`, process15.stdout, blocker));
1462
1650
  } else if (refused !== undefined) {
1463
1651
  failedPulls++;
1464
- log(marked("problem", `${repo.name} not pulled`, process13.stdout, refused));
1652
+ log(marked("problem", `${repo.name} not pulled`, process15.stdout, refused));
1465
1653
  } else {
1466
- log(marked("ok", `${repo.name} pulled`, process13.stdout, `${repo.behind} ${repo.behind === 1 ? "commit" : "commits"}`));
1654
+ log(marked("ok", `${repo.name} pulled`, process15.stdout, `${repo.behind} ${repo.behind === 1 ? "commit" : "commits"}`));
1467
1655
  states[index] = await inspect(repo.path, false);
1468
1656
  }
1469
1657
  }
@@ -1478,7 +1666,7 @@ async function repos(args) {
1478
1666
  printJson(kept.map((repo) => ({ ...repo, reasons: reasonsOf(repo) })));
1479
1667
  return failedPulls > 0 ? 1 : 0;
1480
1668
  }
1481
- const label = role("label", process13.stdout);
1669
+ const label = role("label", process15.stdout);
1482
1670
  if (kept.length === 0) {
1483
1671
  console.log(label(`nothing needs attention in ${states.length} repositories`));
1484
1672
  return failedPulls > 0 ? 1 : 0;
@@ -1493,4 +1681,4 @@ async function repos(args) {
1493
1681
  }
1494
1682
 
1495
1683
  // src/repos-cli.ts
1496
- process14.exitCode = await runMain({ name: "repos", run: repos });
1684
+ process16.exitCode = await runMain({ name: "repos", run: repos });