@kaynooo/pkg-tools 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -7
  2. package/dist/cli.js +664 -304
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -418,6 +418,29 @@ var init_format = __esm(() => {
418
418
  ESCAPE = String.fromCharCode(27);
419
419
  });
420
420
 
421
+ // ../kit/src/text.ts
422
+ function distance(a, b) {
423
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
424
+ for (const [i, left] of [...a].entries()) {
425
+ const current = [i + 1];
426
+ for (const [j, right] of [...b].entries())
427
+ current.push(Math.min((previous[j + 1] ?? 0) + 1, (current[j] ?? 0) + 1, (previous[j] ?? 0) + (left === right ? 0 : 1)));
428
+ previous = current;
429
+ }
430
+ return previous[b.length] ?? 0;
431
+ }
432
+ function closest(name, names) {
433
+ const started = names.find((known) => known.startsWith(name));
434
+ if (started !== undefined)
435
+ return started;
436
+ const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
437
+ return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
438
+ }
439
+ var ANSI;
440
+ var init_text = __esm(() => {
441
+ ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
442
+ });
443
+
421
444
  // ../kit/src/args.ts
422
445
  import process3 from "process";
423
446
  import { parseArgs } from "util";
@@ -473,10 +496,10 @@ function optionRows(options, colors = paint(process3.stdout)) {
473
496
  const rows = Object.entries(options).map(([name, option]) => {
474
497
  const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
475
498
  const value = option.type === "string" ? ` ${colors.dim("<value>")}` : "";
476
- return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? " <value>".length : 0), description: option.description };
499
+ return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? " <value>".length : 0), description: option.description ?? "" };
477
500
  });
478
501
  const width = Math.max(0, ...rows.map((row) => row.width));
479
- return rows.map((row) => ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
502
+ return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
480
503
  }
481
504
  function applyOutput(color, plain, env = process3.env) {
482
505
  const when = plain === true ? "never" : color ?? "auto";
@@ -504,7 +527,8 @@ function parse(args, usage, options, settings = {}) {
504
527
  try {
505
528
  parsed = parseArgs({ args: joinDashValues(own, all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
506
529
  } catch (error) {
507
- badUsage(parseError(error));
530
+ const message = parseError(error);
531
+ throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
508
532
  }
509
533
  const values = parsed.values;
510
534
  applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
@@ -547,12 +571,27 @@ function parseError(error) {
547
571
  const message = upTo(error instanceof Error ? error.message : String(error), ". ");
548
572
  return `${message.charAt(0).toLowerCase()}${message.slice(1)}`;
549
573
  }
574
+ function nearestOption(message, options, allowNegative) {
575
+ const typed = UNKNOWN_OPTION.exec(message)?.[1];
576
+ if (typed === undefined)
577
+ return;
578
+ const word = typed.replace(DASHES, "");
579
+ if (word.length < 2 && !typed.startsWith("--"))
580
+ return;
581
+ const known = Object.entries(options).flatMap(([name, option]) => [
582
+ ...option.short === undefined ? [] : [`-${option.short}`],
583
+ `--${name}`,
584
+ ...allowNegative && option.type === "boolean" ? [`--no-${name}`] : []
585
+ ]);
586
+ const meant = closest(word, known.map((flag) => flag.replace(DASHES, "")));
587
+ return meant === undefined ? undefined : `did you mean ${known.find((flag) => flag.replace(DASHES, "") === meant)}?`;
588
+ }
550
589
  function wholeNumber(value, name, fallback, min = 1) {
551
590
  if (value === undefined)
552
591
  return fallback;
553
592
  const number = Number(value);
554
- if (!Number.isInteger(number) || number < min)
555
- badUsage(`--${name} takes a whole number from ${min}, not ${value}`);
593
+ if (!WHOLE.test(value) || !Number.isSafeInteger(number) || number < min)
594
+ badUsage(`${name.startsWith("<") ? name : `--${name}`} takes a whole number from ${min}, not ${value}`);
556
595
  return number;
557
596
  }
558
597
  function duration(value, name, fallback, unit = "s") {
@@ -564,10 +603,11 @@ function duration(value, name, fallback, unit = "s") {
564
603
  badUsage(`--${name} takes a duration such as 30s, 5m or 1h, not ${value}`);
565
604
  return ms;
566
605
  }
567
- var Exit, UsageError, HelpShown, completion, OPTIONAL, USAGE_PREFIX, FULL_STOP, OUTPUT_OPTIONS, DURATION, UNIT_MS;
606
+ var Exit, UsageError, HelpShown, completion, OPTIONAL, USAGE_PREFIX, FULL_STOP, DASHES, OUTPUT_OPTIONS, UNKNOWN_OPTION, WHOLE, DURATION, UNIT_MS;
568
607
  var init_args = __esm(() => {
569
608
  init_format();
570
609
  init_style();
610
+ init_text();
571
611
  Exit = class Exit extends Error {
572
612
  code;
573
613
  hint;
@@ -578,8 +618,8 @@ var init_args = __esm(() => {
578
618
  }
579
619
  };
580
620
  UsageError = class UsageError extends Exit {
581
- constructor(message) {
582
- super(message, 2);
621
+ constructor(message, hint) {
622
+ super(message, 2, hint);
583
623
  }
584
624
  };
585
625
  HelpShown = class HelpShown extends Exit {
@@ -591,10 +631,13 @@ var init_args = __esm(() => {
591
631
  OPTIONAL = /\[[^\]]+\]/g;
592
632
  USAGE_PREFIX = /^usage:\s*/i;
593
633
  FULL_STOP = /\.$/;
634
+ DASHES = /^-+/;
594
635
  OUTPUT_OPTIONS = {
595
636
  color: { type: "string", description: "auto (on a terminal alone), always or never" },
596
637
  plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
597
638
  };
639
+ UNKNOWN_OPTION = /^unknown option '(-[^']+)'/;
640
+ WHOLE = /^\d+$/;
598
641
  DURATION = /^(\d+(?:\.\d+)?)(ms|[smh])?$/;
599
642
  UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000 };
600
643
  });
@@ -605,31 +648,265 @@ var init_chart = __esm(() => {
605
648
  init_style();
606
649
  });
607
650
 
608
- // ../kit/src/text.ts
609
- function distance(a, b) {
610
- let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
611
- for (const [i, left] of [...a].entries()) {
612
- const current = [i + 1];
613
- for (const [j, right] of [...b].entries())
614
- current.push(Math.min((previous[j + 1] ?? 0) + 1, (current[j] ?? 0) + 1, (previous[j] ?? 0) + (left === right ? 0 : 1)));
615
- previous = current;
651
+ // ../kit/src/process.ts
652
+ import { constants } from "os";
653
+ import process4 from "process";
654
+ function exitCodeOf(code, signal) {
655
+ if (code !== null)
656
+ return code;
657
+ if (typeof signal === "number")
658
+ return 128 + signal;
659
+ return signal === null ? 129 : 128 + constants.signals[signal];
660
+ }
661
+ function holdSignals(cleanup) {
662
+ let received;
663
+ let child;
664
+ const held = {
665
+ get signal() {
666
+ return received;
667
+ },
668
+ forward: (to) => {
669
+ child = to;
670
+ },
671
+ release: () => {
672
+ for (const signal of SIGNALS)
673
+ process4.off(signal, onSignal);
674
+ }
675
+ };
676
+ function onSignal(signal) {
677
+ received = signal;
678
+ if (child !== undefined) {
679
+ child.kill(signal);
680
+ return;
681
+ }
682
+ if (cleanup === undefined)
683
+ return;
684
+ held.release();
685
+ cleanup(signal);
686
+ process4.kill(process4.pid, signal);
616
687
  }
617
- return previous[b.length] ?? 0;
688
+ for (const signal of SIGNALS)
689
+ process4.on(signal, onSignal);
690
+ return held;
618
691
  }
619
- var ANSI;
620
- var init_text = __esm(() => {
621
- ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
692
+ async function runCommand(argv, { cwd, env, stdin = "ignore", timeoutMs, signal, maxBytes = Infinity, onChunk, onStart } = {}) {
693
+ const chunks = [];
694
+ const grouped = timeoutMs !== undefined || signal !== undefined || onStart !== undefined || onChunk !== undefined;
695
+ const proc = Bun.spawn(argv, { ...cwd === undefined ? {} : { cwd }, ...env === undefined ? {} : { env }, stdin, stdout: "pipe", stderr: "pipe", ...grouped ? { detached: true } : {} });
696
+ const state = { kept: 0, truncated: false, timedOut: false, failure: undefined, done: false };
697
+ const timers = [];
698
+ const killGroup = (killing) => {
699
+ if (state.done)
700
+ return;
701
+ try {
702
+ if (grouped)
703
+ process4.kill(-proc.pid, killing);
704
+ else
705
+ proc.kill(killing);
706
+ } catch {}
707
+ };
708
+ const stop = () => {
709
+ killGroup("SIGTERM");
710
+ timers.push(setTimeout(killGroup, KILL_GRACE_MS, "SIGKILL"));
711
+ };
712
+ const onAbort = () => stop();
713
+ if (timeoutMs !== undefined) {
714
+ timers.push(setTimeout(() => {
715
+ state.timedOut = true;
716
+ stop();
717
+ }, timeoutMs));
718
+ }
719
+ if (signal?.aborted === true)
720
+ onAbort();
721
+ else
722
+ signal?.addEventListener("abort", onAbort, { once: true });
723
+ onStart?.({ kill: (killing) => killGroup(killing) });
724
+ const drain = async (stream, name) => {
725
+ for await (const data of stream) {
726
+ if (state.failure !== undefined)
727
+ continue;
728
+ try {
729
+ onChunk?.({ stream: name, data });
730
+ } catch (error) {
731
+ state.failure = error;
732
+ stop();
733
+ continue;
734
+ }
735
+ if (state.kept + data.length > maxBytes) {
736
+ state.truncated = true;
737
+ continue;
738
+ }
739
+ state.kept += data.length;
740
+ chunks.push({ stream: name, data });
741
+ }
742
+ };
743
+ try {
744
+ await Promise.all([drain(proc.stdout, "stdout"), drain(proc.stderr, "stderr")]);
745
+ await proc.exited;
746
+ } finally {
747
+ state.done = true;
748
+ for (const timer of timers)
749
+ clearTimeout(timer);
750
+ signal?.removeEventListener("abort", onAbort);
751
+ }
752
+ if (state.failure !== undefined)
753
+ throw state.failure;
754
+ const joined = () => Buffer.concat(chunks.map(({ data }) => data));
755
+ let output;
756
+ let text;
757
+ return {
758
+ code: exitCodeOf(proc.exitCode, proc.signalCode),
759
+ chunks,
760
+ timedOut: state.timedOut,
761
+ truncated: state.truncated,
762
+ get output() {
763
+ return output ??= joined();
764
+ },
765
+ get text() {
766
+ return text ??= (output ??= joined()).toString();
767
+ }
768
+ };
769
+ }
770
+ var KILL_GRACE_MS = 2000, SIGNALS;
771
+ var init_process = __esm(() => {
772
+ SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
773
+ });
774
+
775
+ // ../kit/src/progress.ts
776
+ import process5 from "process";
777
+ function liveRegion(render, stream = process5.stderr, env = process5.env) {
778
+ if (!takesControlCodes(stream, env))
779
+ return { redraw: () => {
780
+ return;
781
+ }, print: (text) => void stream.write(`${text}
782
+ `), stop: () => {
783
+ return;
784
+ } };
785
+ const write = stream.write.bind(stream);
786
+ let frame = 0;
787
+ let drawn = 0;
788
+ const clear = () => {
789
+ if (drawn > 0)
790
+ write(`${CLEAR_LINE}${LINE_UP.repeat(drawn - 1)}`);
791
+ drawn = 0;
792
+ };
793
+ const draw = () => {
794
+ const width = (stream.columns === undefined || stream.columns === 0 ? 80 : stream.columns) - 1;
795
+ const height = stream.rows === undefined || stream.rows === 0 ? 24 : stream.rows;
796
+ const lines = render(FRAMES[frame++ % FRAMES.length]).slice(0, Math.max(1, height - 1)).map((line) => cutToWidth(line, width));
797
+ if (lines.length === 0) {
798
+ clear();
799
+ return;
800
+ }
801
+ write(`${drawn > 0 ? `${CLEAR_LINE}${LINE_UP.repeat(drawn - 1)}` : CLEAR_LINE}${lines.join(`
802
+ `)}`);
803
+ drawn = lines.length;
804
+ };
805
+ const timer = setInterval(draw, FRAME_MS);
806
+ const restore = interceptOutput(clear);
807
+ const onExit = () => clear();
808
+ const signals = holdSignals(stop);
809
+ function stop() {
810
+ clearInterval(timer);
811
+ clear();
812
+ restore();
813
+ process5.off("exit", onExit);
814
+ signals.release();
815
+ }
816
+ timer.unref();
817
+ process5.on("exit", onExit);
818
+ return {
819
+ redraw: draw,
820
+ print: (text) => {
821
+ clear();
822
+ write(`${text}
823
+ `);
824
+ draw();
825
+ },
826
+ stop
827
+ };
828
+ }
829
+ function cutToWidth(line, width) {
830
+ if (Bun.stringWidth(line) <= width)
831
+ return line;
832
+ let room = width;
833
+ let kept = "";
834
+ let index = 0;
835
+ let linked = false;
836
+ while (index < line.length) {
837
+ ESCAPE_CODE.lastIndex = index;
838
+ const escape = ESCAPE_CODE.exec(line);
839
+ if (escape !== null) {
840
+ kept += escape[0];
841
+ linked ||= escape[0].startsWith(`${ESC}]`);
842
+ index = ESCAPE_CODE.lastIndex;
843
+ continue;
844
+ }
845
+ const char = String.fromCodePoint(line.codePointAt(index));
846
+ const columns = Bun.stringWidth(char);
847
+ if (columns > room)
848
+ break;
849
+ kept += char;
850
+ room -= columns;
851
+ index += char.length;
852
+ }
853
+ return `${kept}${RESET_STYLE}${linked ? CLOSE_LINK : ""}`;
854
+ }
855
+ function interceptOutput(clear) {
856
+ const targets = [...CONSOLE_METHODS.map((name) => [console, name]), [process5.stdout, "write"], [process5.stderr, "write"]];
857
+ const wrapped = targets.map(([target, name]) => {
858
+ const original = Reflect.get(target, name);
859
+ const wrapper = (...args) => {
860
+ clear();
861
+ return original.apply(target, args);
862
+ };
863
+ Reflect.set(target, name, wrapper);
864
+ return { target, name, original, wrapper };
865
+ });
866
+ return () => {
867
+ for (const { target, name, original, wrapper } of wrapped) {
868
+ if (Reflect.get(target, name) === wrapper)
869
+ Reflect.set(target, name, original);
870
+ }
871
+ };
872
+ }
873
+ var FRAMES, FRAME_MS = 80, CLEAR_LINE = "\r\x1B[2K", LINE_UP = "\x1B[1A\x1B[2K", ESC, ESCAPE_CODE, RESET_STYLE, CLOSE_LINK, CONSOLE_METHODS;
874
+ var init_progress = __esm(() => {
875
+ init_format();
876
+ init_process();
877
+ init_style();
878
+ FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
879
+ ESC = String.fromCharCode(27);
880
+ ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x7E]|\\][^\\u0007${ESC}]*(?:\\u0007|${ESC}\\\\))`, "y");
881
+ RESET_STYLE = `${ESC}[0m`;
882
+ CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
883
+ CONSOLE_METHODS = ["log", "info", "warn", "error", "debug", "dir", "dirxml", "table", "trace", "group", "groupCollapsed", "count", "timeEnd", "timeLog", "assert"];
884
+ });
885
+
886
+ // ../kit/src/tool.ts
887
+ var init_tool = __esm(() => {
888
+ init_format();
889
+ init_process();
890
+ });
891
+
892
+ // ../kit/src/ci.ts
893
+ var init_ci = __esm(() => {
894
+ init_args();
895
+ init_format();
896
+ init_progress();
897
+ init_tool();
622
898
  });
623
899
 
624
900
  // ../kit/src/cli.ts
625
- import process4 from "process";
626
- function commandRows(commands, colors) {
901
+ import process6 from "process";
902
+ async function commandRows(commands, colors) {
627
903
  const width = Math.max(...Object.keys(commands).map((name) => name.length));
628
904
  const groups = new Map([["Commands:", []]]);
629
- for (const [name, { summary, group }] of Object.entries(commands)) {
905
+ const summaries = await Promise.all(Object.values(commands).map(async ({ summary }) => resolveSummary(summary)));
906
+ for (const [index, [name, { group }]] of Object.entries(commands).entries()) {
630
907
  const heading = group === undefined ? "Commands:" : `${group}:`;
631
908
  const rows = groups.get(heading) ?? [];
632
- rows.push(` ${colors.cyan(name.padEnd(width))} ${colors.dim(summary)}`);
909
+ rows.push(` ${colors.cyan(name.padEnd(width))} ${colors.dim(summaries[index])}`);
633
910
  groups.set(heading, rows);
634
911
  }
635
912
  return [...groups].filter(([, rows]) => rows.length > 0).flatMap(([heading, rows], index) => [...index === 0 ? [] : [""], colors.bold(heading), ...rows]);
@@ -649,28 +926,38 @@ function takeOutputFlags(argv) {
649
926
  }
650
927
  return argv.slice(index);
651
928
  }
652
- function closest(name, names) {
653
- const started = names.find((known) => known.startsWith(name));
654
- if (started !== undefined)
655
- return started;
656
- const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
657
- return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
929
+ async function resolveSummary(summary) {
930
+ return typeof summary === "string" ? summary : summary();
658
931
  }
659
- async function help({ path, header, summary, commands, defaultCommand, examples, replaces }, stream) {
932
+ function scopeOf(commands, extra, aliases) {
933
+ return { commands: typeof commands === "function" ? {} : commands, lazy: [...typeof commands === "function" ? [commands] : [], ...extra === undefined ? [] : [extra]], aliases };
934
+ }
935
+ async function expand(scope, words) {
936
+ if (scope.lazy.length === 0)
937
+ return scope;
938
+ const commands = { ...scope.commands };
939
+ for (const added of await Promise.all(scope.lazy.map(async (lazy) => lazy(words)))) {
940
+ for (const [name, command] of Object.entries(added))
941
+ commands[name] ??= command;
942
+ }
943
+ return { ...scope, commands, lazy: [] };
944
+ }
945
+ async function help({ path, header, summary: lazySummary, commands, defaultCommand, examples, replaces }, stream) {
660
946
  const colors = paint(stream);
661
947
  const prefix = path.join(" ");
948
+ const summary = await resolveSummary(lazySummary);
662
949
  if (wantsHelpJson())
663
950
  return JSON.stringify(description(`${prefix} <command> [options]: ${summary}`, { examples, replaces }));
664
951
  const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
665
952
  const options = fallback !== undefined && "run" in fallback ? await optionsOf(fallback.run) : undefined;
666
953
  const own = Object.entries(options ?? {}).filter(([name]) => !(name in TOP_OPTIONS));
667
954
  return [
668
- header(colors),
955
+ header(colors, summary),
669
956
  "",
670
957
  usageLine(`${prefix} ${defaultCommand === undefined ? colors.cyan("<command>") : `[${colors.cyan("<command>")}]`} [options]`, colors),
671
958
  ...replacesRows(replaces, colors),
672
959
  "",
673
- ...commandRows(commands, colors),
960
+ ...await commandRows(commands, colors),
674
961
  ...own.length === 0 ? [] : ["", `${colors.bold(`Options of ${defaultCommand}:`)} ${colors.dim("the command run when none is named")}`, ...optionRows(Object.fromEntries(own), colors)],
675
962
  "",
676
963
  colors.bold("Options:"),
@@ -682,8 +969,8 @@ async function help({ path, header, summary, commands, defaultCommand, examples,
682
969
  `);
683
970
  }
684
971
  function printError(prefix, message, hint, cause) {
685
- const colors = paint(process4.stderr);
686
- const styled = takesControlCodes(process4.stderr);
972
+ const colors = paint(process6.stderr);
973
+ const styled = takesControlCodes(process6.stderr);
687
974
  const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
688
975
  const under = (label, text, tint) => text === undefined ? "" : `
689
976
  ${styled ? tint(label) : `${label}:`} ${text}`;
@@ -716,11 +1003,11 @@ async function dispatch(level, typed, { version, redact }) {
716
1003
  }
717
1004
  const [name, ...args] = argv;
718
1005
  if (name === undefined && level.path.length > 1) {
719
- console.error(await help(level, process4.stderr));
1006
+ console.error(await help(await expand(level, [...level.path.slice(1), ...argv]), process6.stderr));
720
1007
  return 2;
721
1008
  }
722
1009
  if (name === undefined || name === "help" || name === "--help" || name === "-h") {
723
- console.log(await help(level, process4.stdout));
1010
+ console.log(await help(await expand(level, [...level.path.slice(1), ...argv]), process6.stdout));
724
1011
  return 0;
725
1012
  }
726
1013
  if (level.path.length === 1 && (name === "--version" || name === "-v")) {
@@ -729,6 +1016,10 @@ async function dispatch(level, typed, { version, redact }) {
729
1016
  }
730
1017
  let command = level.commands[level.aliases?.[name] ?? name];
731
1018
  let rest = args;
1019
+ if (command === undefined && level.lazy.length > 0) {
1020
+ level = await expand(level, [...level.path.slice(1), ...argv]);
1021
+ command = level.commands[level.aliases?.[name] ?? name];
1022
+ }
732
1023
  if (command === undefined && level.defaultCommand !== undefined) {
733
1024
  command = level.commands[level.defaultCommand];
734
1025
  rest = argv;
@@ -740,7 +1031,7 @@ async function dispatch(level, typed, { version, redact }) {
740
1031
  }
741
1032
  if ("commands" in command) {
742
1033
  const { summary } = command;
743
- return dispatch({ path: [...level.path, name], header: (colors) => `${colors.bold(`${prefix} ${name}`)} ${summary}`, summary, commands: command.commands, aliases: command.aliases, defaultCommand: undefined, examples: command.examples, replaces: undefined }, rest, { version, redact });
1034
+ return dispatch({ path: [...level.path, name], header: (colors, text) => `${colors.bold(`${prefix} ${name}`)} ${text}`, summary, ...scopeOf(command.commands, command.extraCommands, command.aliases), defaultCommand: undefined, examples: command.examples, replaces: undefined }, rest, { version, redact });
744
1035
  }
745
1036
  try {
746
1037
  return await command.run(rest);
@@ -763,28 +1054,34 @@ async function optionsOf(run) {
763
1054
  async function leafCandidates(run, words) {
764
1055
  if (!(words.at(-1) ?? "").startsWith("-"))
765
1056
  return [FILES];
766
- return Object.entries(await optionsOf(run)).map(([name, option]) => `--${name} ${option.description}`);
1057
+ return Object.entries(await optionsOf(run)).map(([name, option]) => option.description === undefined ? `--${name}` : `--${name} ${option.description}`);
767
1058
  }
768
- async function candidates(commands, aliases, defaultCommand, words) {
1059
+ async function candidates(scope, defaultCommand, words, before = []) {
769
1060
  const [word = "", ...rest] = words;
770
- const command = commands[aliases?.[word] ?? word];
771
- if (rest.length === 0 && !word.startsWith("-"))
772
- return Object.entries(commands).map(([name, { summary }]) => `${name} ${summary}`);
1061
+ const listing = rest.length === 0 && !word.startsWith("-");
1062
+ let { commands, aliases } = scope;
1063
+ let command = commands[aliases?.[word] ?? word];
1064
+ if (listing || command === undefined) {
1065
+ ({ commands, aliases } = await expand(scope, [...before, ...words]));
1066
+ command = commands[aliases?.[word] ?? word];
1067
+ }
1068
+ if (listing)
1069
+ return Promise.all(Object.entries(commands).map(async ([name, { summary }]) => `${name} ${await resolveSummary(summary)}`));
773
1070
  if (command === undefined) {
774
1071
  const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
775
1072
  return fallback !== undefined && "run" in fallback ? leafCandidates(fallback.run, words) : [];
776
1073
  }
777
- return "commands" in command ? candidates(command.commands, command.aliases, undefined, rest) : leafCandidates(command.run, rest);
1074
+ return "commands" in command ? candidates(scopeOf(command.commands, command.extraCommands, command.aliases), undefined, rest, [...before, word]) : leafCandidates(command.run, rest);
778
1075
  }
779
1076
  async function printCandidates(found) {
780
1077
  for (const line of await found)
781
1078
  console.log(line);
782
1079
  return 0;
783
1080
  }
784
- async function runCli(cli, argv = process4.argv.slice(2)) {
1081
+ async function runCli(cli, argv = process6.argv.slice(2)) {
785
1082
  if (argv[0] === COMPLETE)
786
- return printCandidates(candidates(cli.commands, cli.aliases, cli.defaultCommand, argv.slice(1)));
787
- return dispatch({ path: [cli.name], header: (colors) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${cli.about}`, summary: cli.about, commands: cli.commands, aliases: cli.aliases, defaultCommand: cli.defaultCommand, examples: cli.examples, replaces: cli.replaces }, argv, { version: cli.version, redact: cli.redact });
1083
+ return printCandidates(candidates(scopeOf(cli.commands, cli.extraCommands, cli.aliases), cli.defaultCommand, argv.slice(1)));
1084
+ return dispatch({ path: [cli.name], header: (colors, about) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${about}`, summary: cli.about, ...scopeOf(cli.commands, cli.extraCommands, cli.aliases), defaultCommand: cli.defaultCommand, examples: cli.examples, replaces: cli.replaces }, argv, { version: cli.version, redact: cli.redact });
788
1085
  }
789
1086
  var LEVEL_OPTIONS, TOP_OPTIONS, COMPLETE = "__complete", FILES = "__files__";
790
1087
  var init_cli = __esm(() => {
@@ -803,6 +1100,12 @@ var init_config = __esm(() => {
803
1100
  init_args();
804
1101
  });
805
1102
 
1103
+ // ../kit/src/git.ts
1104
+ var init_git = __esm(() => {
1105
+ init_args();
1106
+ init_tool();
1107
+ });
1108
+
806
1109
  // ../kit/src/limit.ts
807
1110
  function limitOptions(fallback) {
808
1111
  return {
@@ -828,114 +1131,96 @@ var init_limit = __esm(() => {
828
1131
  init_args();
829
1132
  });
830
1133
 
831
- // ../kit/src/process.ts
832
- import { constants } from "os";
833
- import process5 from "process";
834
- async function collect(stream, name, chunks) {
835
- for await (const data of stream)
836
- chunks.push({ stream: name, data });
837
- }
838
- function exitCodeOf(code, signal) {
839
- if (code !== null)
840
- return code;
841
- if (typeof signal === "number")
842
- return 128 + signal;
843
- return signal === null ? 129 : 128 + constants.signals[signal];
844
- }
845
- function holdSignals(cleanup) {
846
- let received;
847
- let child;
848
- const held = {
849
- get signal() {
850
- return received;
851
- },
852
- forward: (to) => {
853
- child = to;
854
- },
855
- release: () => {
856
- for (const signal of SIGNALS)
857
- process5.off(signal, onSignal);
858
- }
859
- };
860
- function onSignal(signal) {
861
- received = signal;
862
- if (child !== undefined) {
863
- child.kill(signal);
864
- return;
865
- }
866
- if (cleanup === undefined)
867
- return;
868
- held.release();
869
- cleanup(signal);
870
- process5.kill(process5.pid, signal);
871
- }
872
- for (const signal of SIGNALS)
873
- process5.on(signal, onSignal);
874
- return held;
875
- }
876
- async function runCommand(argv, { cwd, env, stdin = "ignore" } = {}) {
877
- const chunks = [];
878
- const proc = Bun.spawn(argv, { ...cwd === undefined ? {} : { cwd }, ...env === undefined ? {} : { env }, stdin, stdout: "pipe", stderr: "pipe" });
879
- await Promise.all([collect(proc.stdout, "stdout", chunks), collect(proc.stderr, "stderr", chunks)]);
880
- await proc.exited;
881
- const output = Buffer.concat(chunks.map(({ data }) => data));
882
- return { code: exitCodeOf(proc.exitCode, proc.signalCode), chunks, output, text: output.toString() };
883
- }
884
- var SIGNALS;
885
- var init_process = __esm(() => {
886
- SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
1134
+ // ../kit/src/logs.ts
1135
+ var DAY_MS;
1136
+ var init_logs = __esm(() => {
1137
+ DAY_MS = 24 * 60 * 60 * 1000;
1138
+ });
1139
+
1140
+ // ../kit/src/secrets.ts
1141
+ var SECRET_WORDS;
1142
+ var init_secrets = __esm(() => {
1143
+ SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
887
1144
  });
888
1145
 
889
1146
  // ../kit/src/index.ts
890
1147
  var init_src = __esm(() => {
891
1148
  init_args();
892
1149
  init_chart();
1150
+ init_ci();
893
1151
  init_cli();
894
1152
  init_config();
895
1153
  init_format();
1154
+ init_git();
896
1155
  init_limit();
1156
+ init_logs();
897
1157
  init_process();
1158
+ init_secrets();
898
1159
  init_style();
899
1160
  init_text();
1161
+ init_tool();
900
1162
  });
901
1163
 
902
1164
  // src/registry.ts
903
1165
  function isPublic(registry) {
904
1166
  return registry.url.replace(TRAILING_SLASHES, "") === PUBLIC_REGISTRY;
905
1167
  }
1168
+ function unreachable(message) {
1169
+ return fail(`could not check: ${message}`, 2);
1170
+ }
1171
+ function requestInit(token) {
1172
+ return { headers: { accept: "application/json", ...token === undefined ? {} : { authorization: `Bearer ${token}` } }, signal: AbortSignal.timeout(REQUEST_TIMEOUT) };
1173
+ }
906
1174
  async function get(fetcher, url, token) {
907
1175
  try {
908
- return await fetcher(url, { headers: { accept: "application/json", ...token === undefined ? {} : { authorization: `Bearer ${token}` } } });
1176
+ return await fetcher(url, requestInit(token));
909
1177
  } catch (error) {
910
- return fail(`cannot reach ${new URL(url).host}: ${error instanceof Error ? error.message : String(error)}`, 1);
1178
+ return unreachable(`cannot reach ${new URL(url).host}: ${error instanceof Error ? error.message : String(error)}`);
911
1179
  }
912
1180
  }
1181
+ function isValidName(name) {
1182
+ return PACKAGE_NAME.test(name) && name.length <= 214;
1183
+ }
913
1184
  async function fetchPackument(name, registry, fetcher = fetch) {
914
- const response = await get(fetcher, `${registry.url.replace(TRAILING_SLASHES, "")}/${name.replace("/", "%2f")}`, registry.token);
1185
+ if (!isValidName(name))
1186
+ fail(`${name} is not a valid npm package name`);
1187
+ const response = await get(fetcher, `${registry.url.replace(TRAILING_SLASHES, "")}/${encodeURIComponent(name).replace("%40", "@").replace("%2F", "%2f")}`, registry.token);
915
1188
  if (response.status === 404)
916
1189
  return;
917
1190
  if (!response.ok)
918
- fail(`${new URL(registry.url).host} answered ${response.status} for ${name}`, 1);
919
- return await response.json();
1191
+ unreachable(`${new URL(registry.url).host} answered ${response.status} for ${name}`);
1192
+ let packument;
1193
+ try {
1194
+ packument = await response.json();
1195
+ } catch {
1196
+ return unreachable(`${new URL(registry.url).host} answered ${name} with something that is not JSON`);
1197
+ }
1198
+ if (packument?.name !== name)
1199
+ unreachable(`${new URL(registry.url).host} answered ${name} with the metadata of ${String(packument?.name)}`);
1200
+ return packument;
920
1201
  }
921
1202
  async function fetchDownloads(name, fetcher = fetch) {
922
- const response = await get(fetcher, `${DOWNLOADS_API}/${name}`);
1203
+ const response = await get(fetcher, `${DOWNLOADS_API}/${encodeURIComponent(name)}`);
923
1204
  if (!response.ok)
924
1205
  return;
925
- const body = await response.json();
926
- return body.downloads;
1206
+ try {
1207
+ return (await response.json()).downloads;
1208
+ } catch {
1209
+ return;
1210
+ }
927
1211
  }
928
- var PUBLIC_REGISTRY = "https://registry.npmjs.org", DOWNLOADS_API = "https://api.npmjs.org/downloads/point/last-week", TRAILING_SLASHES;
1212
+ var PUBLIC_REGISTRY = "https://registry.npmjs.org", DOWNLOADS_API = "https://api.npmjs.org/downloads/point/last-week", TRAILING_SLASHES, REQUEST_TIMEOUT = 1e4, PACKAGE_NAME;
929
1213
  var init_registry = __esm(() => {
930
1214
  init_src();
931
1215
  TRAILING_SLASHES = /\/+$/;
1216
+ PACKAGE_NAME = /^(?:@[\w~-][\w.~-]*\/)?[\w~-][\w.~-]*$/;
932
1217
  });
933
1218
 
934
1219
  // src/config.ts
935
1220
  import { homedir } from "os";
936
1221
  import { dirname, join } from "path";
937
- import process7 from "process";
938
- function expand(value, env) {
1222
+ import process8 from "process";
1223
+ function expand2(value, env) {
939
1224
  let missing = false;
940
1225
  const expanded = value.replace(ENV_REFERENCE, (_, braced, bare) => {
941
1226
  const found = env[braced ?? bare ?? ""];
@@ -944,6 +1229,9 @@ function expand(value, env) {
944
1229
  });
945
1230
  return missing ? undefined : expanded;
946
1231
  }
1232
+ function literal(value) {
1233
+ return expand2(value, {}) === value ? value : undefined;
1234
+ }
947
1235
  function parseNpmrc(text, env) {
948
1236
  const config = { scopes: {}, tokens: {}, scopeTokens: {} };
949
1237
  for (const line of text.split(`
@@ -952,11 +1240,12 @@ function parseNpmrc(text, env) {
952
1240
  if (at === -1 || COMMENT.test(line))
953
1241
  continue;
954
1242
  const key = line.slice(0, at).trim();
955
- const value = expand(line.slice(at + 1).trim().replace(QUOTES, "$2"), env);
956
- if (value === undefined || value === "")
957
- continue;
958
1243
  const scope = SCOPE_REGISTRY.exec(key)?.[1];
959
1244
  const tokenFor = AUTH_TOKEN.exec(key)?.[1];
1245
+ const raw = line.slice(at + 1).trim().replace(QUOTES, "$2");
1246
+ const value = tokenFor === undefined ? literal(raw) : expand2(raw, env);
1247
+ if (value === undefined || value === "")
1248
+ continue;
960
1249
  if (scope !== undefined)
961
1250
  config.scopes[scope] = value;
962
1251
  else if (tokenFor !== undefined)
@@ -968,8 +1257,8 @@ function parseNpmrc(text, env) {
968
1257
  }
969
1258
  function bunEntry(entry, env) {
970
1259
  const url = typeof entry === "string" ? entry : entry.url;
971
- const token = typeof entry === "string" || entry.token === undefined ? undefined : expand(entry.token, env);
972
- return { ...url === undefined ? {} : { url: expand(url, env) ?? url }, ...token === undefined ? {} : { token } };
1260
+ const token = typeof entry === "string" || entry.token === undefined ? undefined : expand2(entry.token, env);
1261
+ return { ...url === undefined || literal(url) === undefined ? {} : { url }, ...token === undefined ? {} : { token } };
973
1262
  }
974
1263
  function parseBunfig(text, env) {
975
1264
  const config = { scopes: {}, tokens: {}, scopeTokens: {} };
@@ -991,26 +1280,35 @@ function parseBunfig(text, env) {
991
1280
  return config;
992
1281
  }
993
1282
  function tokenFor(config, url) {
994
- const address = url.replace(PROTOCOL, "");
995
- const best = Object.keys(config.tokens).filter((key) => address.startsWith(key)).sort((a, b) => b.length - a.length)[0];
1283
+ const address = `${url.replace(PROTOCOL, "").replace(TRAILING_SLASHES2, "")}/`;
1284
+ const best = Object.keys(config.tokens).filter((key) => address.startsWith(`${key.replace(TRAILING_SLASHES2, "")}/`)).sort((a, b) => b.length - a.length)[0];
996
1285
  return best === undefined ? undefined : config.tokens[best];
997
1286
  }
1287
+ function mayCarryToken(url) {
1288
+ const { protocol, hostname } = new URL(url);
1289
+ return protocol === "https:" || LOCAL_HOSTS.has(hostname);
1290
+ }
998
1291
  function registryFor(name, configs) {
999
1292
  const scope = name.startsWith("@") ? name.slice(0, name.indexOf("/")) : undefined;
1000
- for (const config of configs) {
1001
- const url = scope === undefined ? config.registry : config.scopes[scope] ?? config.registry;
1002
- if (url === undefined)
1003
- continue;
1004
- const token = (scope === undefined ? undefined : config.scopeTokens[scope]) ?? tokenFor(config, url) ?? configs.map((other) => tokenFor(other, url)).find((found) => found !== undefined);
1005
- return { url, token };
1293
+ const scoped = scope === undefined ? undefined : configs.find((config) => config.scopes[scope] !== undefined);
1294
+ const url = (scope === undefined ? undefined : scoped?.scopes[scope]) ?? configs.find((config) => config.registry !== undefined)?.registry;
1295
+ if (url === undefined)
1296
+ return { url: PUBLIC_REGISTRY };
1297
+ const token = (scope === undefined ? undefined : scoped?.scopeTokens[scope]) ?? configs.map((other) => tokenFor(other, url)).find((found) => found !== undefined);
1298
+ return { url, token: mayCarryToken(url) ? token : undefined };
1299
+ }
1300
+ function readable(path, parse) {
1301
+ try {
1302
+ return parse();
1303
+ } catch (error) {
1304
+ return unreachable(`${path} cannot be read: ${error instanceof Error ? error.message : String(error)}`);
1006
1305
  }
1007
- return { url: PUBLIC_REGISTRY };
1008
1306
  }
1009
1307
  async function readIfThere(path) {
1010
1308
  const file = Bun.file(path);
1011
1309
  return await file.exists() ? file.text() : undefined;
1012
1310
  }
1013
- async function loadConfigs(cwd = process7.cwd(), env = process7.env, home = homedir()) {
1311
+ async function loadConfigs(cwd = process8.cwd(), env = process8.env, home = homedir()) {
1014
1312
  const directories = [];
1015
1313
  for (let dir = cwd;!directories.includes(dir); dir = dirname(dir))
1016
1314
  directories.push(dir);
@@ -1021,7 +1319,7 @@ async function loadConfigs(cwd = process7.cwd(), env = process7.env, home = home
1021
1319
  const bunfig = await readIfThere(join(dir, "bunfig.toml"));
1022
1320
  const npmrc = await readIfThere(join(dir, ".npmrc"));
1023
1321
  if (bunfig !== undefined)
1024
- configs.push(parseBunfig(bunfig, env));
1322
+ configs.push(readable(join(dir, "bunfig.toml"), () => parseBunfig(bunfig, env)));
1025
1323
  if (npmrc !== undefined)
1026
1324
  configs.push(parseNpmrc(npmrc, env));
1027
1325
  }
@@ -1039,7 +1337,7 @@ function parseSettings(text) {
1039
1337
  settings.blockSeverity = raw.blockSeverity;
1040
1338
  return settings;
1041
1339
  }
1042
- async function loadSettings(cwd = process7.cwd(), home = homedir()) {
1340
+ async function loadSettings(cwd = process8.cwd(), home = homedir()) {
1043
1341
  const files = [];
1044
1342
  for (let dir = cwd;!files.includes(join(dir, SETTINGS_FILE)); dir = dirname(dir))
1045
1343
  files.push(join(dir, SETTINGS_FILE));
@@ -1049,13 +1347,13 @@ async function loadSettings(cwd = process7.cwd(), home = homedir()) {
1049
1347
  const text = await readIfThere(file);
1050
1348
  if (text === undefined)
1051
1349
  continue;
1052
- const found = parseSettings(text);
1350
+ const found = readable(file, () => parseSettings(text));
1053
1351
  settings.licenses ??= found.licenses;
1054
1352
  settings.blockSeverity ??= found.blockSeverity;
1055
1353
  }
1056
1354
  return settings;
1057
1355
  }
1058
- var ENV_REFERENCE, COMMENT, SCOPE_REGISTRY, AUTH_TOKEN, PROTOCOL, QUOTES, SEVERITIES, SETTINGS_FILE = ".pkgrc.json";
1356
+ var ENV_REFERENCE, COMMENT, SCOPE_REGISTRY, AUTH_TOKEN, PROTOCOL, TRAILING_SLASHES2, LOCAL_HOSTS, QUOTES, SEVERITIES, DEFAULT_BLOCK_SEVERITY = "high", SETTINGS_FILE = ".pkgrc.json";
1059
1357
  var init_config2 = __esm(() => {
1060
1358
  init_registry();
1061
1359
  ENV_REFERENCE = /\$\{(\w+)\}|\$(\w+)/g;
@@ -1063,35 +1361,89 @@ var init_config2 = __esm(() => {
1063
1361
  SCOPE_REGISTRY = /^(@[^:]+):registry$/;
1064
1362
  AUTH_TOKEN = /^(\/\/.+?):_authToken$/;
1065
1363
  PROTOCOL = /^\w+:/;
1364
+ TRAILING_SLASHES2 = /\/+$/;
1365
+ LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
1066
1366
  QUOTES = /^(["'])(.*)\1$/;
1067
1367
  SEVERITIES = ["low", "moderate", "high", "critical"];
1068
1368
  });
1069
1369
 
1370
+ // src/semver.ts
1371
+ function isRange(text) {
1372
+ if (text.trim() === "")
1373
+ return true;
1374
+ return text.split(OR).every((alternative) => {
1375
+ const words = alternative.trim().split(WHITESPACE);
1376
+ return words.every((word, index) => word === "-" ? index > 0 && index < words.length - 1 : COMPARATOR.test(word));
1377
+ });
1378
+ }
1379
+ function compare(version, word) {
1380
+ const bound = BOUND.exec(word);
1381
+ if (bound === null)
1382
+ return;
1383
+ const order = Bun.semver.order(version, bound[2] ?? "");
1384
+ const operator = bound[1] ?? "=";
1385
+ if (operator === "<")
1386
+ return order < 0;
1387
+ if (operator === "<=")
1388
+ return order <= 0;
1389
+ if (operator === ">")
1390
+ return order > 0;
1391
+ return operator === ">=" ? order >= 0 : order === 0;
1392
+ }
1393
+ function inRange(version, range) {
1394
+ if (Bun.semver.satisfies(version, range))
1395
+ return true;
1396
+ if (!PRERELEASE.test(version))
1397
+ return false;
1398
+ return range.split(OR).some((alternative) => alternative.trim().split(WHITESPACE).every((word) => compare(version, word) ?? Bun.semver.satisfies(version, word)));
1399
+ }
1400
+ var OR, WHITESPACE, PRERELEASE, PART, COMPARATOR, BOUND, EXACT_VERSION;
1401
+ var init_semver = __esm(() => {
1402
+ OR = /\s*\|\|\s*/;
1403
+ WHITESPACE = /\s+/;
1404
+ PRERELEASE = /-/;
1405
+ PART = String.raw`(?:\d+|[xX*])`;
1406
+ COMPARATOR = new RegExp(String.raw`^(?:[<>]=?|=|\^|~>?)?v?${PART}(?:\.${PART}){0,2}(?:-[\w.-]+)?(?:\+[\w.-]+)?$`);
1407
+ BOUND = /^(<=|>=|[<>=])?v?(\d+\.\d+\.\d+(?:-[\w.-]+)?)$/;
1408
+ EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
1409
+ });
1410
+
1070
1411
  // src/advisories.ts
1071
1412
  function severityRank(severity) {
1072
1413
  return SEVERITIES.indexOf(severity);
1073
1414
  }
1074
1415
  function fixedVersion(vulnerable, version) {
1075
- const range = vulnerable.split("||").map((each) => each.trim()).find((each) => Bun.semver.satisfies(version, each));
1416
+ const range = vulnerable.split("||").map((each) => each.trim()).find((each) => inRange(version, each));
1076
1417
  return range === undefined ? undefined : UPPER_BOUND.exec(range)?.[1];
1077
1418
  }
1078
1419
  async function fetchAdvisories(versions, registry, fetcher = fetch) {
1079
- const url = `${registry.url.replace(TRAILING_SLASHES2, "")}${BULK_PATH}`;
1420
+ const url = `${registry.url.replace(TRAILING_SLASHES3, "")}${BULK_PATH}`;
1421
+ const host = new URL(registry.url).host;
1080
1422
  let response;
1081
1423
  try {
1082
1424
  response = await fetcher(url, {
1083
1425
  method: "POST",
1084
1426
  headers: { accept: "application/json", "content-type": "application/json", ...registry.token === undefined ? {} : { authorization: `Bearer ${registry.token}` } },
1085
- body: JSON.stringify(versions)
1427
+ body: JSON.stringify(versions),
1428
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT2)
1086
1429
  });
1087
- } catch {
1088
- return {};
1430
+ } catch (error) {
1431
+ return { found: {}, unavailable: `cannot reach ${host} for advisories: ${error instanceof Error ? error.message : String(error)}` };
1089
1432
  }
1433
+ if (NO_ENDPOINT.has(response.status))
1434
+ return { found: {}, isUnsupported: true, unavailable: `${host} has no advisory endpoint` };
1090
1435
  if (!response.ok)
1091
- return {};
1092
- const body = await response.json();
1436
+ return { found: {}, unavailable: `${host} answered ${response.status} for advisories` };
1437
+ let body;
1438
+ try {
1439
+ body = await response.json();
1440
+ } catch {
1441
+ return { found: {}, unavailable: `${host} answered advisories with something that is not JSON` };
1442
+ }
1093
1443
  const found = {};
1094
1444
  for (const [name, raws] of Object.entries(body)) {
1445
+ if (!Array.isArray(raws))
1446
+ continue;
1095
1447
  found[name] = raws.filter((raw) => SEVERITIES.includes(raw.severity)).map((raw) => ({
1096
1448
  id: raw.id,
1097
1449
  ghsa: GHSA.exec(raw.url)?.[0],
@@ -1101,20 +1453,22 @@ async function fetchAdvisories(versions, registry, fetcher = fetch) {
1101
1453
  vulnerable: raw.vulnerable_versions
1102
1454
  }));
1103
1455
  }
1104
- return found;
1456
+ return { found };
1105
1457
  }
1106
1458
  function advisoriesFor(advisories, version) {
1107
- return (advisories ?? []).filter((each) => Bun.semver.satisfies(version, each.vulnerable)).map((each) => ({ ...each, fixed: fixedVersion(each.vulnerable, version) }));
1459
+ return (advisories ?? []).filter((each) => inRange(version, each.vulnerable)).map((each) => ({ ...each, fixed: fixedVersion(each.vulnerable, version) }));
1108
1460
  }
1109
1461
  function filterAdvisories(advisories, level, ignore = []) {
1110
1462
  return advisories.filter((each) => severityRank(each.severity) >= severityRank(level) && !ignore.includes(each.ghsa ?? "") && !ignore.includes(String(each.id)));
1111
1463
  }
1112
- var BULK_PATH = "/-/npm/v1/security/advisories/bulk", TRAILING_SLASHES2, GHSA, UPPER_BOUND;
1464
+ var BULK_PATH = "/-/npm/v1/security/advisories/bulk", TRAILING_SLASHES3, REQUEST_TIMEOUT2 = 1e4, GHSA, UPPER_BOUND, NO_ENDPOINT;
1113
1465
  var init_advisories = __esm(() => {
1114
1466
  init_config2();
1115
- TRAILING_SLASHES2 = /\/+$/;
1467
+ init_semver();
1468
+ TRAILING_SLASHES3 = /\/+$/;
1116
1469
  GHSA = /GHSA-[\w-]+$/;
1117
1470
  UPPER_BOUND = /(?:^|\s)<([\w.+-]+)/;
1471
+ NO_ENDPOINT = new Set([404, 405, 501]);
1118
1472
  });
1119
1473
 
1120
1474
  // src/licenses.ts
@@ -1641,20 +1995,23 @@ var init_popular = __esm(() => {
1641
1995
  function baseName(name) {
1642
1996
  return name.slice(name.indexOf("/") + 1);
1643
1997
  }
1644
- function scopeOf(name) {
1998
+ function scopeOf2(name) {
1645
1999
  return name.startsWith("@") ? name.slice(0, name.indexOf("/")) : "";
1646
2000
  }
1647
2001
  function squash(name) {
1648
2002
  return name.toLowerCase().replace(SEPARATORS, "");
1649
2003
  }
1650
2004
  function resemblance(name, popular) {
1651
- if (scopeOf(name) !== scopeOf(popular) && baseName(name) === baseName(popular) && baseName(name).length >= MIN_LENGTH)
2005
+ if (scopeOf2(name) !== scopeOf2(popular) && baseName(name) === baseName(popular) && baseName(name).length >= MIN_LENGTH)
1652
2006
  return `${popular} under another scope`;
1653
2007
  if (squash(name) === squash(popular))
1654
2008
  return `${popular} spelled with other separators or case`;
2009
+ const shortest = Math.min(name.length, popular.length);
2010
+ const allowed = shortest >= MIN_LENGTH * 2 ? 2 : 1;
2011
+ if (shortest < MIN_LENGTH || Math.abs(name.length - popular.length) > allowed)
2012
+ return;
1655
2013
  const edits = distance(name, popular);
1656
- const allowed = Math.min(name.length, popular.length) >= MIN_LENGTH * 2 ? 2 : 1;
1657
- return Math.min(name.length, popular.length) >= MIN_LENGTH && edits <= allowed ? `${popular}, ${edits} ${edits === 1 ? "edit" : "edits"} away` : undefined;
2014
+ return edits <= allowed ? `${popular}, ${edits} ${edits === 1 ? "edit" : "edits"} away` : undefined;
1658
2015
  }
1659
2016
  function nearMisses(name, popular = popular_default) {
1660
2017
  if (popular.includes(name))
@@ -1690,6 +2047,8 @@ function resolve(packument, request) {
1690
2047
  return tags[wanted];
1691
2048
  if (versions.includes(wanted))
1692
2049
  return wanted;
2050
+ if (!isRange(wanted))
2051
+ return;
1693
2052
  return versions.filter((version) => Bun.semver.satisfies(version, wanted)).sort(Bun.semver.order).at(-1);
1694
2053
  }
1695
2054
  function licenseOf(license) {
@@ -1740,7 +2099,9 @@ function analyze(input) {
1740
2099
  }
1741
2100
  const age = ageDays(report.published, now);
1742
2101
  if (age === undefined) {
1743
- report.warnings.push(`${registry} gives no publish date for ${version}, its age cannot be checked`);
2102
+ const isBlocking = minAgeDays > 0 && registry === PUBLIC_HOST;
2103
+ report.blocked ||= isBlocking;
2104
+ report.warnings.push(`${registry} gives no publish date for ${version}, its age cannot be checked${isBlocking ? " and it is refused" : ""}`);
1744
2105
  } else if (age < minAgeDays) {
1745
2106
  report.blocked = true;
1746
2107
  report.warnings.push(`${version} was published ${age < 1 ? "less than a day" : `${Math.floor(age)} day${Math.floor(age) === 1 ? "" : "s"}`} ago, under ${minAgeDays} days${report.safe === undefined ? ", and no older stable version exists" : `, use ${spec.name}@${report.safe.version}`}`);
@@ -1769,11 +2130,14 @@ function analyze(input) {
1769
2130
  }
1770
2131
  return report;
1771
2132
  }
1772
- var DEFAULT_MIN_AGE_DAYS = 14, DAY = 86400000, INSTALL_SCRIPTS, GIT_PREFIX, VERSION_SEPARATOR;
2133
+ var DEFAULT_MIN_AGE_DAYS = 14, DAY = 86400000, PUBLIC_HOST, INSTALL_SCRIPTS, GIT_PREFIX, VERSION_SEPARATOR;
1773
2134
  var init_analyze = __esm(() => {
1774
2135
  init_advisories();
1775
2136
  init_licenses();
1776
2137
  init_nearmiss();
2138
+ init_registry();
2139
+ init_semver();
2140
+ PUBLIC_HOST = new URL(PUBLIC_REGISTRY).host;
1777
2141
  INSTALL_SCRIPTS = ["preinstall", "install", "postinstall"];
1778
2142
  GIT_PREFIX = /^git\+/;
1779
2143
  VERSION_SEPARATOR = /(?<=.)@/;
@@ -1782,7 +2146,8 @@ var init_analyze = __esm(() => {
1782
2146
  // src/commands/check.ts
1783
2147
  var exports_check = {};
1784
2148
  __export(exports_check, {
1785
- check: () => check
2149
+ check: () => check,
2150
+ checkPackage: () => checkPackage
1786
2151
  });
1787
2152
  async function checkPackage(text, context) {
1788
2153
  const spec = parseSpec(text);
@@ -1791,11 +2156,16 @@ async function checkPackage(text, context) {
1791
2156
  const downloads = packument !== undefined && isPublic(registry) ? await fetchDownloads(spec.name, context.fetcher) : undefined;
1792
2157
  const input = { spec, packument, downloads, registry: new URL(registry.url).host, now: context.now, minAgeDays: context.minAgeDays, licenses: context.settings?.licenses };
1793
2158
  const report = analyze(input);
1794
- const { blockSeverity } = context.settings ?? {};
1795
- if (blockSeverity === undefined || report.version === undefined)
2159
+ const blockSeverity = context.settings?.blockSeverity ?? DEFAULT_BLOCK_SEVERITY;
2160
+ if (report.version === undefined)
1796
2161
  return report;
1797
- const found = await fetchAdvisories({ [spec.name]: [report.version] }, registry, context.fetcher);
1798
- return analyze({ ...input, blockSeverity, advisories: advisoriesFor(found[spec.name], report.version) });
2162
+ const { found, unavailable, isUnsupported } = await fetchAdvisories({ [spec.name]: [report.version] }, registry, context.fetcher);
2163
+ if (unavailable !== undefined && isUnsupported !== true)
2164
+ return unreachable(unavailable);
2165
+ const checked = analyze({ ...input, blockSeverity, advisories: advisoriesFor(found[spec.name], report.version) });
2166
+ if (unavailable !== undefined)
2167
+ checked.warnings.push(`${unavailable}, so its known advisories are not checked`);
2168
+ return checked;
1799
2169
  }
1800
2170
  function when(release, now) {
1801
2171
  return release?.published === undefined ? "" : `, published ${formatAge(release.published, now)} (${release.published.slice(0, 10)})`;
@@ -1842,9 +2212,9 @@ var usage = `pkg [check] <name>[@version]...
1842
2212
 
1843
2213
  Checks npm packages before they are added: whether the name exists, the latest and the newest version at least two weeks old, weekly downloads, licence, repository, install scripts and a near miss of a popular name.
1844
2214
 
1845
- A \`.pkgrc.json\` (or ~/.config/pkg/config.json) with \`licenses\` (SPDX ids) refuses a licence outside the list or a missing one, and \`blockSeverity\` (low, moderate, high, critical) refuses a version with an advisory at that level or above.
2215
+ A \`.pkgrc.json\` (or ~/.config/pkg/config.json) with \`licenses\` (SPDX ids) refuses a licence outside the list or a missing one, and \`blockSeverity\` (low, moderate, high, critical; high when unset) refuses a version with an advisory at that level or above.
1846
2216
 
1847
- Exits 0 when every package passes, 1 when one is missing, the version asked for is under the minimum age, its licence is outside the allowlist or an advisory reaches the threshold, 2 on a usage error.`, options;
2217
+ Exits 0 when every package passes, 1 when one is blocked (missing, the version asked for is under the minimum age or has no date, its licence is outside the allowlist or an advisory reaches the threshold), 2 when it could not check (the registry unreachable, slow or answering badly, a broken .pkgrc.json, bunfig.toml or .npmrc) or on a usage error.`, options;
1848
2218
  var init_check = __esm(() => {
1849
2219
  init_src();
1850
2220
  init_advisories();
@@ -1858,6 +2228,15 @@ var init_check = __esm(() => {
1858
2228
  });
1859
2229
 
1860
2230
  // src/lockfile.ts
2231
+ function split(resolution) {
2232
+ const at = resolution.search(VERSION_SEPARATOR2);
2233
+ if (at === -1)
2234
+ return;
2235
+ const target = resolution.slice(at + 1);
2236
+ if (target.startsWith(NPM_ALIAS))
2237
+ return split(target.slice(NPM_ALIAS.length));
2238
+ return NOT_REGISTRY.test(target) ? undefined : { name: resolution.slice(0, at), version: target };
2239
+ }
1861
2240
  function parseBunLock(text) {
1862
2241
  const lock = Bun.JSONC.parse(text);
1863
2242
  const seen = new Map;
@@ -1865,17 +2244,15 @@ function parseBunLock(text) {
1865
2244
  const resolution = entry[0];
1866
2245
  if (typeof resolution !== "string")
1867
2246
  continue;
1868
- const at = resolution.search(VERSION_SEPARATOR2);
1869
- const name = resolution.slice(0, at);
1870
- const version = resolution.slice(at + 1);
1871
- if (at !== -1 && !NOT_REGISTRY.test(version))
1872
- seen.set(`${name}@${version}`, { name, version });
2247
+ const locked = split(resolution);
2248
+ if (locked !== undefined)
2249
+ seen.set(`${locked.name}@${locked.version}`, locked);
1873
2250
  }
1874
2251
  return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name) || Bun.semver.order(a.version, b.version));
1875
2252
  }
1876
- var NOT_REGISTRY, VERSION_SEPARATOR2;
2253
+ var NOT_REGISTRY, VERSION_SEPARATOR2, NPM_ALIAS = "npm:";
1877
2254
  var init_lockfile = __esm(() => {
1878
- NOT_REGISTRY = /^(?:workspace|link|file|github|git|http|https|npm):|^[\w.-]+\/[\w.-]+$/;
2255
+ NOT_REGISTRY = /^(?:workspace|link|file|github|git|http|https):|^[\w.-]+\/[\w.-]+$/;
1879
2256
  VERSION_SEPARATOR2 = /(?<=.)@/;
1880
2257
  });
1881
2258
 
@@ -1885,7 +2262,7 @@ __export(exports_audit, {
1885
2262
  audit: () => audit
1886
2263
  });
1887
2264
  import { join as join2, resolve as resolve2 } from "path";
1888
- import process8 from "process";
2265
+ import process9 from "process";
1889
2266
  function hasFinding(audited) {
1890
2267
  return audited.blocked || audited.warnings.length > 0 || audited.advisories.length > 0;
1891
2268
  }
@@ -1905,8 +2282,13 @@ async function auditPackages(packages, context) {
1905
2282
  registries.set(registry.url, group);
1906
2283
  }
1907
2284
  const advisories = {};
1908
- for (const { registry, versions } of registries.values())
1909
- Object.assign(advisories, await fetchAdvisories(versions, registry, context.fetcher));
2285
+ const unavailable = [];
2286
+ for (const { registry, versions } of registries.values()) {
2287
+ const fetched = await fetchAdvisories(versions, registry, context.fetcher);
2288
+ Object.assign(advisories, fetched.found);
2289
+ if (fetched.unavailable !== undefined)
2290
+ unavailable.push(fetched.unavailable);
2291
+ }
1910
2292
  const audited = [];
1911
2293
  for (let start = 0;start < packages.length; start += BATCH) {
1912
2294
  audited.push(...await Promise.all(packages.slice(start, start + BATCH).map(async ({ name, version }) => {
@@ -1916,7 +2298,7 @@ async function auditPackages(packages, context) {
1916
2298
  return { name, version, license: report.license, blocked: report.blocked || found.length > 0, warnings: report.warnings, advisories: found };
1917
2299
  })));
1918
2300
  }
1919
- return audited;
2301
+ return { audited, unavailable };
1920
2302
  }
1921
2303
  function renderAudit(findings, total) {
1922
2304
  const blocks = findings.map(({ name, version, warnings, advisories }) => [
@@ -1934,7 +2316,7 @@ async function audit(args) {
1934
2316
  const level = values["audit-level"] ?? "low";
1935
2317
  if (!isSeverity(level))
1936
2318
  fail(`--audit-level takes ${SEVERITIES.join(", ")}, not ${level}`);
1937
- const dir = resolve2(asked ?? process8.cwd());
2319
+ const dir = resolve2(asked ?? process9.cwd());
1938
2320
  const lock = Bun.file(join2(dir, "bun.lock"));
1939
2321
  if (!await lock.exists())
1940
2322
  fail(`no bun.lock in ${dir}`);
@@ -1946,19 +2328,24 @@ async function audit(args) {
1946
2328
  level,
1947
2329
  ignore: values.ignore ?? []
1948
2330
  };
1949
- const findings = (await auditPackages(packages, context)).filter(hasFinding);
2331
+ const { audited, unavailable } = await auditPackages(packages, context);
2332
+ const findings = audited.filter(hasFinding);
1950
2333
  if (values.json === true)
1951
2334
  printJson(findings);
1952
2335
  else
1953
2336
  console.log(renderAudit(findings, packages.length));
1954
- return findings.some((each) => each.blocked) ? 1 : 0;
2337
+ for (const reason of unavailable)
2338
+ warn(`${reason}, so its advisories are not checked`);
2339
+ if (findings.some((each) => each.blocked))
2340
+ return 1;
2341
+ return unavailable.length > 0 ? 2 : 0;
1955
2342
  }
1956
2343
  var usage2 = `pkg audit [dir]
1957
2344
 
1958
2345
  Reads the bun.lock in the folder (the current one by default) and runs on every registry package it pins the checks \`pkg check\` runs before an add: age, install scripts, a deprecated version, a near miss of a popular name, and the licence allowlist of the pkg config. It also lists the known advisories of each version, with the GHSA, the severity and the fixed version, from the registry's bulk advisory endpoint, the one \`bun audit\` reads.
1959
2346
  Findings are grouped per package, each registry package looked up once per run.
1960
2347
 
1961
- Exits 0 when nothing blocks, 1 when a package is missing, too fresh or outside the licence allowlist, or an advisory reaches --audit-level, 2 on a usage error or without a bun.lock.`, options2, BATCH = 8;
2348
+ Exits 0 when nothing blocks, 1 when a package is missing, too fresh or outside the licence allowlist, or an advisory reaches --audit-level, 2 when the registry could not be reached or answered badly (a warning says which, and nothing is reported as clean), on a usage error or without a bun.lock.`, options2, BATCH = 8;
1962
2349
  var init_audit = __esm(() => {
1963
2350
  init_src();
1964
2351
  init_advisories();
@@ -1974,125 +2361,42 @@ var init_audit = __esm(() => {
1974
2361
  };
1975
2362
  });
1976
2363
 
1977
- // ../kit/src/progress.ts
1978
- import process9 from "process";
1979
- function liveRegion(render, stream = process9.stderr, env = process9.env) {
1980
- if (!takesControlCodes(stream, env))
1981
- return { redraw: () => {
1982
- return;
1983
- }, print: (text) => void stream.write(`${text}
1984
- `), stop: () => {
1985
- return;
1986
- } };
1987
- const write = stream.write.bind(stream);
1988
- let frame = 0;
1989
- let drawn = 0;
1990
- const clear = () => {
1991
- if (drawn > 0)
1992
- write(`${CLEAR_LINE}${LINE_UP.repeat(drawn - 1)}`);
1993
- drawn = 0;
1994
- };
1995
- const draw = () => {
1996
- const width = (stream.columns === undefined || stream.columns === 0 ? 80 : stream.columns) - 1;
1997
- const height = stream.rows === undefined || stream.rows === 0 ? 24 : stream.rows;
1998
- const lines = render(FRAMES[frame++ % FRAMES.length]).slice(0, Math.max(1, height - 1)).map((line) => cutToWidth(line, width));
1999
- if (lines.length === 0) {
2000
- clear();
2001
- return;
2002
- }
2003
- write(`${drawn > 0 ? `${CLEAR_LINE}${LINE_UP.repeat(drawn - 1)}` : CLEAR_LINE}${lines.join(`
2004
- `)}`);
2005
- drawn = lines.length;
2006
- };
2007
- const timer = setInterval(draw, FRAME_MS);
2008
- const restore = interceptOutput(clear);
2009
- const onExit = () => clear();
2010
- const signals = holdSignals(stop);
2011
- function stop() {
2012
- clearInterval(timer);
2013
- clear();
2014
- restore();
2015
- process9.off("exit", onExit);
2016
- signals.release();
2017
- }
2018
- timer.unref();
2019
- process9.on("exit", onExit);
2020
- return {
2021
- redraw: draw,
2022
- print: (text) => {
2023
- clear();
2024
- write(`${text}
2025
- `);
2026
- draw();
2027
- },
2028
- stop
2029
- };
2030
- }
2031
- function cutToWidth(line, width) {
2032
- if (Bun.stringWidth(line) <= width)
2033
- return line;
2034
- let room = width;
2035
- let kept = "";
2036
- let index = 0;
2037
- let linked = false;
2038
- while (index < line.length) {
2039
- ESCAPE_CODE.lastIndex = index;
2040
- const escape = ESCAPE_CODE.exec(line);
2041
- if (escape !== null) {
2042
- kept += escape[0];
2043
- linked ||= escape[0].startsWith(`${ESC}]`);
2044
- index = ESCAPE_CODE.lastIndex;
2045
- continue;
2046
- }
2047
- const char = String.fromCodePoint(line.codePointAt(index));
2048
- const columns = Bun.stringWidth(char);
2049
- if (columns > room)
2050
- break;
2051
- kept += char;
2052
- room -= columns;
2053
- index += char.length;
2054
- }
2055
- return `${kept}${RESET_STYLE}${linked ? CLOSE_LINK : ""}`;
2056
- }
2057
- function interceptOutput(clear) {
2058
- const targets = [...CONSOLE_METHODS.map((name) => [console, name]), [process9.stdout, "write"], [process9.stderr, "write"]];
2059
- const wrapped = targets.map(([target, name]) => {
2060
- const original = Reflect.get(target, name);
2061
- const wrapper = (...args) => {
2062
- clear();
2063
- return original.apply(target, args);
2064
- };
2065
- Reflect.set(target, name, wrapper);
2066
- return { target, name, original, wrapper };
2067
- });
2068
- return () => {
2069
- for (const { target, name, original, wrapper } of wrapped) {
2070
- if (Reflect.get(target, name) === wrapper)
2071
- Reflect.set(target, name, original);
2072
- }
2073
- };
2074
- }
2075
- var FRAMES, FRAME_MS = 80, CLEAR_LINE = "\r\x1B[2K", LINE_UP = "\x1B[1A\x1B[2K", ESC, ESCAPE_CODE, RESET_STYLE, CLOSE_LINK, CONSOLE_METHODS;
2076
- var init_progress = __esm(() => {
2077
- init_format();
2078
- init_process();
2079
- init_style();
2080
- FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2081
- ESC = String.fromCharCode(27);
2082
- ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x7E]|\\][^\\u0007${ESC}]*(?:\\u0007|${ESC}\\\\))`, "y");
2083
- RESET_STYLE = `${ESC}[0m`;
2084
- CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
2085
- CONSOLE_METHODS = ["log", "info", "warn", "error", "debug", "dir", "dirxml", "table", "trace", "group", "groupCollapsed", "count", "timeEnd", "timeLog", "assert"];
2086
- });
2087
-
2088
2364
  // ../kit/src/shell.ts
2089
- var SEPARATORS2, PLAIN_BREAKERS, DOUBLE_QUOTED_ESCAPES, WRAPPERS;
2365
+ function wrapper(short = "", long = [], operands = 0) {
2366
+ return { short, long, operands };
2367
+ }
2368
+ var SEPARATORS2, PLAIN_BREAKERS, GLOB_CHARS, COMMENT_PREFIXES, DOUBLE_QUOTED_ESCAPES, KEYWORDS, SHELLS, COMMAND_STRING_PROGRAMS, SHELL_VALUE_OPTIONS, PRINTERS, FIND_EXEC, WRAPPERS;
2090
2369
  var init_shell = __esm(() => {
2091
2370
  SEPARATORS2 = new Set([`
2092
2371
  `, ";", "|"]);
2093
2372
  PLAIN_BREAKERS = new Set(["<", ">", "(", ")", "&", "|"]);
2373
+ GLOB_CHARS = new Set(["*", "?", "["]);
2374
+ COMMENT_PREFIXES = new Set([";", "&", "|", "(", ")"]);
2094
2375
  DOUBLE_QUOTED_ESCAPES = new Set(["$", "`", '"', "\\"]);
2095
- WRAPPERS = new Set(["command", "env", "sudo", "exec", "nohup", "time"]);
2376
+ KEYWORDS = new Set(["then", "do", "else", "if", "elif", "while", "until", "!", "{", "}"]);
2377
+ SHELLS = new Set(["sh", "bash", "zsh", "dash", "ash", "ksh"]);
2378
+ COMMAND_STRING_PROGRAMS = new Set([...SHELLS, "su", "runuser"]);
2379
+ SHELL_VALUE_OPTIONS = new Set(["-o", "-O", "--rcfile", "--init-file"]);
2380
+ PRINTERS = new Set(["echo", "printf"]);
2381
+ FIND_EXEC = new Set(["-exec", "-execdir", "-ok", "-okdir"]);
2382
+ WRAPPERS = new Map([
2383
+ ["command", wrapper()],
2384
+ ["builtin", wrapper()],
2385
+ ["nohup", wrapper()],
2386
+ ["setsid", wrapper()],
2387
+ ["busybox", wrapper()],
2388
+ ["exec", wrapper("a")],
2389
+ ["time", wrapper("fo", ["--format", "--output"])],
2390
+ ["env", wrapper("uCS", ["--unset", "--chdir", "--split-string", "--argv0"])],
2391
+ ["sudo", wrapper("ughpCTrtUDR", ["--user", "--group", "--host", "--prompt", "--close-from", "--command-timeout", "--role", "--type", "--other-user", "--chdir", "--chroot"])],
2392
+ ["doas", wrapper("uC")],
2393
+ ["timeout", wrapper("sk", ["--signal", "--kill-after"], 1)],
2394
+ ["nice", wrapper("n", ["--adjustment"])],
2395
+ ["ionice", wrapper("cnpPu", ["--class", "--classdata", "--pid", "--pgid", "--uid"])],
2396
+ ["stdbuf", wrapper("ioe", ["--input", "--output", "--error"])],
2397
+ ["chroot", wrapper("", ["--userspec", "--groups"], 1)],
2398
+ ["xargs", wrapper("IndsEaPL", ["--max-args", "--max-procs", "--max-chars", "--delimiter", "--arg-file", "--eof", "--max-lines", "--process-slot-var"])]
2399
+ ]);
2096
2400
  });
2097
2401
 
2098
2402
  // src/pin.ts
@@ -2187,7 +2491,7 @@ var init_consumers = __esm(() => {
2187
2491
  init_pin();
2188
2492
  KINDS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
2189
2493
  SKIPPED = new Set(["node_modules", "dist", "build", "vendor", "target", "coverage"]);
2190
- LOCAL_SPEC = /^(?:workspace|link|file|portal):/;
2494
+ LOCAL_SPEC = /[:/]/;
2191
2495
  KIND_FLAGS = { dependencies: [], devDependencies: ["--dev"], optionalDependencies: ["--optional"], peerDependencies: ["--peer"] };
2192
2496
  });
2193
2497
 
@@ -2200,23 +2504,43 @@ __export(exports_bump, {
2200
2504
  import { existsSync as existsSync2 } from "fs";
2201
2505
  import { homedir as homedir2 } from "os";
2202
2506
  import { dirname as dirname3, join as join4, relative as relative2 } from "path";
2507
+ async function lookup(name, registry, fetcher) {
2508
+ try {
2509
+ return { packument: await fetchPackument(name, registry, fetcher) };
2510
+ } catch (error) {
2511
+ if (error instanceof Exit && error.code === 2)
2512
+ return { error: error.message };
2513
+ throw error;
2514
+ }
2515
+ }
2203
2516
  async function servedVersion(name, version, settings) {
2517
+ if (!isValidName(name))
2518
+ fail(`${name} is not a valid npm package name`);
2519
+ if (version !== undefined && !EXACT_VERSION.test(version))
2520
+ fail(`bump takes an exact version, not ${version}: leave it out for the latest`);
2204
2521
  const registry = registryFor(name, settings.configs);
2205
2522
  const host = new URL(registry.url).host;
2206
2523
  const started = Date.now();
2207
2524
  const region = liveRegion((spinner) => [`${spinner} waiting for ${host} to serve ${name}@${version} ${formatDuration(Date.now() - started)}`]);
2525
+ let lastError = "";
2208
2526
  try {
2209
2527
  for (;; ) {
2210
- const packument = await fetchPackument(name, registry, settings.fetcher);
2211
- const latest = packument?.["dist-tags"]?.latest;
2212
- if (version === undefined && latest !== undefined)
2213
- return latest;
2214
- if (version === undefined)
2215
- fail(`${host} has no ${name}`, 1);
2216
- if (packument?.versions?.[version] !== undefined)
2217
- return version;
2528
+ const looked = await lookup(name, registry, settings.fetcher);
2529
+ if ("error" in looked) {
2530
+ lastError = looked.error;
2531
+ } else {
2532
+ const latest = looked.packument?.["dist-tags"]?.latest;
2533
+ if (looked.packument === undefined)
2534
+ fail(`${host} has no ${name}`, 1);
2535
+ if (version === undefined && latest === undefined)
2536
+ fail(`${host} has no ${name}`, 1);
2537
+ if (version === undefined && latest !== undefined)
2538
+ return latest;
2539
+ if (version !== undefined && looked.packument.versions?.[version] !== undefined)
2540
+ return version;
2541
+ }
2218
2542
  if (Date.now() - started >= settings.timeout)
2219
- fail(`${host} does not serve ${name}@${version} after ${formatDuration(settings.timeout)}`);
2543
+ fail(`${host} does not serve ${name}@${version} after ${formatDuration(settings.timeout)}${lastError === "" ? "" : `, last error: ${lastError}`}`);
2220
2544
  region.redraw();
2221
2545
  await Bun.sleep(settings.poll ?? POLL);
2222
2546
  }
@@ -2224,6 +2548,20 @@ async function servedVersion(name, version, settings) {
2224
2548
  region.stop();
2225
2549
  }
2226
2550
  }
2551
+ async function vet(name, version, settings) {
2552
+ if (settings.isForced === true)
2553
+ return true;
2554
+ const report = await checkPackage(`${name}@${version}`, { configs: settings.configs, fetcher: settings.fetcher, minAgeDays: settings.minAgeDays ?? 0, settings: settings.pkgSettings });
2555
+ if (!report.blocked) {
2556
+ for (const warning of report.warnings)
2557
+ warn(`${name}@${version}`, warning);
2558
+ return true;
2559
+ }
2560
+ for (const warning of report.warnings)
2561
+ problem(`${name}@${version} is refused`, warning);
2562
+ problem(`${name}@${version} is refused`, "no consumer was touched, --force bumps it anyway");
2563
+ return false;
2564
+ }
2227
2565
  function lockfileOf(dir, root) {
2228
2566
  for (let at = dir;; at = dirname3(at)) {
2229
2567
  const found = ["bun.lock", "bun.lockb"].find((file) => existsSync2(join4(at, file)));
@@ -2233,6 +2571,11 @@ function lockfileOf(dir, root) {
2233
2571
  return;
2234
2572
  }
2235
2573
  }
2574
+ async function hasUncommittedChanges(where, root, run) {
2575
+ const lockfile = lockfileOf(where, root);
2576
+ const status = await run(["git", "status", "--porcelain", "--", "package.json", ...lockfile === undefined ? [] : [lockfile]], where);
2577
+ return status.code === 0 && status.text.trim() !== "";
2578
+ }
2236
2579
  async function checkAndCommit(name, version, where, root, run) {
2237
2580
  const checked = await run(["check"], where);
2238
2581
  if (checked.code !== 0)
@@ -2255,11 +2598,14 @@ async function bumpConsumers(name, version, settings) {
2255
2598
  if (found.length === 0)
2256
2599
  note(`nothing under ${settings.root} depends on ${name}`);
2257
2600
  if (local > 0)
2258
- note(`${local} workspace:, link: or file: dependenc${local === 1 ? "y" : "ies"}`, "left as they are");
2601
+ note(`${local} non-registry dependenc${local === 1 ? "y" : "ies"} (workspace:, file:, git, URL, alias, catalog:)`, "left as they are");
2259
2602
  for (const consumer of consumers) {
2260
2603
  const argv = addCommand(consumer, name, version);
2261
2604
  const where = join4(settings.root, consumer.dir);
2262
- if (consumer.spec === version) {
2605
+ if (consumer.kind === "peerDependencies") {
2606
+ skipped.push(consumer.dir);
2607
+ note(consumer.dir, `peer range ${consumer.spec}, left as it is`);
2608
+ } else if (consumer.spec === version) {
2263
2609
  skipped.push(consumer.dir);
2264
2610
  note(consumer.dir, `already on ${version}`);
2265
2611
  } else if (consumer.manager !== "bun") {
@@ -2267,6 +2613,10 @@ async function bumpConsumers(name, version, settings) {
2267
2613
  warn(consumer.dir, `${consumer.spec}, its lockfile pins ${consumer.manager}: put ${version} in by hand`);
2268
2614
  } else if (settings.isDryRun === true) {
2269
2615
  note(consumer.dir, `would run ${[argv.join(" "), ...settings.isAll === true ? ["check", `gc 'chore(deps): bump ${name} to ${version}'`] : []].join(", then ")}`);
2616
+ } else if (settings.isAll === true && await hasUncommittedChanges(where, settings.root, run)) {
2617
+ isClean = false;
2618
+ failing.push(consumer.dir);
2619
+ problem(consumer.dir, "package.json or its lockfile has uncommitted changes, left alone so that gc does not commit them");
2270
2620
  } else {
2271
2621
  const ran = await run(argv, where);
2272
2622
  if (ran.code !== 0) {
@@ -2300,30 +2650,40 @@ async function bump(args) {
2300
2650
  configs: await loadConfigs(),
2301
2651
  timeout: duration(values.timeout, "timeout", 10 * 60000),
2302
2652
  isDryRun: values["dry-run"] === true,
2303
- isAll: values["all-consumers"] === true
2653
+ isAll: values["all-consumers"] === true,
2654
+ isForced: values.force === true,
2655
+ pkgSettings: await loadSettings(),
2656
+ minAgeDays: wholeNumber(values["min-age"], "min-age", 0, 0)
2304
2657
  };
2305
2658
  const version = await servedVersion(name, asked, settings);
2659
+ if (!await vet(name, version, settings))
2660
+ return 1;
2306
2661
  return await bumpConsumers(name, version, settings) ? 0 : 1;
2307
2662
  }
2308
2663
  var usage3 = `pkg bump <name> [version]
2309
2664
 
2310
- Waits until the registry serves the version (the latest when none is given), then runs \`bun add --exact --no-cache\` in each folder under --root whose package.json depends on the package, the kind of dependency kept, a line per folder.
2311
- A workspace:, link: or file: dependency, a folder already on the version and one whose lockfile pins another manager are left as they are, each said so.
2312
- With --all-consumers each folder whose install went through then runs its own \`check\` (the check bin) and is committed with \`gc 'chore(deps): bump <name> to <version>' -- package.json <lockfile>\` only when that passes; a failing one is left uncommitted and listed in the summary with the skipped ones, and nothing is ever pushed.
2665
+ Waits until the registry serves the version (the latest when none is given, an exact version otherwise), vets it as \`pkg check\` does (advisories, licence allowlist, and a minimum age only when --min-age asks for one), then runs \`bun add --exact --no-cache\` in each folder under --root whose package.json depends on the package, the kind of dependency kept, a line per folder.
2666
+ A version the vetting blocks is refused before any folder is touched, unless --force is given.
2667
+ A dependency that is not a registry version (workspace:, link:, file:, git, a URL, an npm: alias, catalog:), a peer range, a folder already on the version and one whose lockfile pins another manager are left as they are, each said so.
2668
+ With --all-consumers a folder whose package.json or lockfile already has uncommitted changes is left alone, and each folder whose install went through then runs its own \`check\` (the check bin) and is committed with \`gc 'chore(deps): bump <name> to <version>' -- package.json <lockfile>\` only when that passes; a failing one is left uncommitted and listed in the summary with the skipped ones, and nothing is ever pushed.
2313
2669
 
2314
- Exits 0 when every install ran or was left as it is (with --all-consumers, and every check passed and every commit landed), 1 when an install, a check or a commit fails or the registry has no such package, 2 when the version never shows within the timeout or on a usage error.`, ROOT_OPTION, options3, POLL = 5000;
2670
+ Exits 0 when every install ran or was left as it is (with --all-consumers, and every check passed and every commit landed), 1 when the vetting refuses the version, an install, a check or a commit fails or the registry has no such package, 2 when the version never shows within the timeout (a registry that cannot be reached is retried until then) or on a usage error.`, ROOT_OPTION, options3, POLL = 5000;
2315
2671
  var init_bump = __esm(() => {
2316
2672
  init_src();
2317
2673
  init_progress();
2318
2674
  init_config2();
2319
2675
  init_consumers();
2320
2676
  init_registry();
2677
+ init_semver();
2678
+ init_check();
2321
2679
  ROOT_OPTION = { type: "string", description: "the folder holding the repositories (~/Workspace)" };
2322
2680
  options3 = {
2323
2681
  root: ROOT_OPTION,
2324
2682
  timeout: { type: "string", short: "t", description: "how long to wait for the registry (10m): 30s, 5m, 1h" },
2325
2683
  "all-consumers": { type: "boolean", description: "after each install run the folder's check and commit it through gc when it passes" },
2326
- "dry-run": { type: "boolean", description: "print what would run, changing nothing" }
2684
+ "dry-run": { type: "boolean", description: "print what would run, changing nothing" },
2685
+ force: { type: "boolean", description: "bump even when the version is blocked by the vetting" },
2686
+ "min-age": { type: "string", description: "days a version must be old (0: a bump adopts a release on purpose, often one just made)" }
2327
2687
  };
2328
2688
  });
2329
2689
 
@@ -2367,7 +2727,7 @@ var init_consumers2 = __esm(() => {
2367
2727
  init_src();
2368
2728
  import process10 from "process";
2369
2729
  // package.json
2370
- var version = "0.4.0";
2730
+ var version = "0.5.0";
2371
2731
 
2372
2732
  // src/cli.ts
2373
2733
  process10.exitCode = await runCli({