@kaynooo/pkg-tools 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +30 -4
- package/dist/cli.js +1130 -406
- package/package.json +6 -6
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";
|
|
@@ -440,9 +463,29 @@ class Completing {
|
|
|
440
463
|
function usageLine(usage, colors = paint(process3.stdout)) {
|
|
441
464
|
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
442
465
|
}
|
|
443
|
-
function helpText(usage, options, colors = paint(process3.stdout), examples = []) {
|
|
444
|
-
return [usageLine(usage, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
466
|
+
function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces } = {}) {
|
|
467
|
+
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
468
|
+
`);
|
|
469
|
+
}
|
|
470
|
+
function replacesRows(replaces, colors = paint(process3.stdout)) {
|
|
471
|
+
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
472
|
+
}
|
|
473
|
+
function wantsHelpJson(env = process3.env) {
|
|
474
|
+
return env.KIT_HELP === "json";
|
|
475
|
+
}
|
|
476
|
+
function description(usage, { examples = [], replaces } = {}) {
|
|
477
|
+
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
445
478
|
`);
|
|
479
|
+
const first = lines[0];
|
|
480
|
+
const colon = first.indexOf(": ");
|
|
481
|
+
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
482
|
+
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
483
|
+
return {
|
|
484
|
+
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
485
|
+
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
486
|
+
...replaces === undefined ? {} : { replaces },
|
|
487
|
+
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
488
|
+
};
|
|
446
489
|
}
|
|
447
490
|
function exampleRows(examples, colors = paint(process3.stdout)) {
|
|
448
491
|
if (examples.length === 0)
|
|
@@ -453,10 +496,10 @@ function optionRows(options, colors = paint(process3.stdout)) {
|
|
|
453
496
|
const rows = Object.entries(options).map(([name, option]) => {
|
|
454
497
|
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
455
498
|
const value = option.type === "string" ? ` ${colors.dim("<value>")}` : "";
|
|
456
|
-
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 ?? "" };
|
|
457
500
|
});
|
|
458
501
|
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
459
|
-
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)}`);
|
|
460
503
|
}
|
|
461
504
|
function applyOutput(color, plain, env = process3.env) {
|
|
462
505
|
const when = plain === true ? "never" : color ?? "auto";
|
|
@@ -482,14 +525,15 @@ function parse(args, usage, options, settings = {}) {
|
|
|
482
525
|
const rest = separator === -1 ? undefined : args.slice(separator + 1);
|
|
483
526
|
let parsed;
|
|
484
527
|
try {
|
|
485
|
-
parsed = parseArgs({ args: own, options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
528
|
+
parsed = parseArgs({ args: joinDashValues(own, all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
486
529
|
} catch (error) {
|
|
487
|
-
|
|
530
|
+
const message = parseError(error);
|
|
531
|
+
throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
|
|
488
532
|
}
|
|
489
533
|
const values = parsed.values;
|
|
490
534
|
applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
|
|
491
535
|
if (values.help === true) {
|
|
492
|
-
console.log(helpText(usage, all, undefined, settings
|
|
536
|
+
console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings)) : helpText(usage, all, undefined, settings));
|
|
493
537
|
throw new HelpShown;
|
|
494
538
|
}
|
|
495
539
|
if (settings.positionals !== undefined)
|
|
@@ -504,16 +548,50 @@ function checkPositionals(given, names) {
|
|
|
504
548
|
if (!variadic && given.length > names.length)
|
|
505
549
|
badUsage(`unexpected argument ${given[names.length]}`);
|
|
506
550
|
}
|
|
551
|
+
function joinDashValues(args, options) {
|
|
552
|
+
const longOf = new Map(Object.entries(options).flatMap(([name, option]) => option.short === undefined ? [[`--${name}`, name]] : [[`--${name}`, name], [`-${option.short}`, name]]));
|
|
553
|
+
const isOption = (arg) => arg === "--" || longOf.has(arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg);
|
|
554
|
+
const joined = [];
|
|
555
|
+
for (let i = 0;i < args.length; i++) {
|
|
556
|
+
const arg = args[i] ?? "";
|
|
557
|
+
if (arg === "--")
|
|
558
|
+
return [...joined, ...args.slice(i)];
|
|
559
|
+
const name = longOf.get(arg);
|
|
560
|
+
const next = args[i + 1];
|
|
561
|
+
if (name !== undefined && options[name]?.type === "string" && next !== undefined && next.startsWith("-") && next !== "-" && !isOption(next)) {
|
|
562
|
+
joined.push(`--${name}=${next}`);
|
|
563
|
+
i++;
|
|
564
|
+
} else {
|
|
565
|
+
joined.push(arg);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return joined;
|
|
569
|
+
}
|
|
507
570
|
function parseError(error) {
|
|
508
571
|
const message = upTo(error instanceof Error ? error.message : String(error), ". ");
|
|
509
572
|
return `${message.charAt(0).toLowerCase()}${message.slice(1)}`;
|
|
510
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
|
+
}
|
|
511
589
|
function wholeNumber(value, name, fallback, min = 1) {
|
|
512
590
|
if (value === undefined)
|
|
513
591
|
return fallback;
|
|
514
592
|
const number = Number(value);
|
|
515
|
-
if (!Number.
|
|
516
|
-
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}`);
|
|
517
595
|
return number;
|
|
518
596
|
}
|
|
519
597
|
function duration(value, name, fallback, unit = "s") {
|
|
@@ -525,10 +603,11 @@ function duration(value, name, fallback, unit = "s") {
|
|
|
525
603
|
badUsage(`--${name} takes a duration such as 30s, 5m or 1h, not ${value}`);
|
|
526
604
|
return ms;
|
|
527
605
|
}
|
|
528
|
-
var Exit, UsageError, HelpShown, completion, OPTIONAL, USAGE_PREFIX, 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;
|
|
529
607
|
var init_args = __esm(() => {
|
|
530
608
|
init_format();
|
|
531
609
|
init_style();
|
|
610
|
+
init_text();
|
|
532
611
|
Exit = class Exit extends Error {
|
|
533
612
|
code;
|
|
534
613
|
hint;
|
|
@@ -539,8 +618,8 @@ var init_args = __esm(() => {
|
|
|
539
618
|
}
|
|
540
619
|
};
|
|
541
620
|
UsageError = class UsageError extends Exit {
|
|
542
|
-
constructor(message) {
|
|
543
|
-
super(message, 2);
|
|
621
|
+
constructor(message, hint) {
|
|
622
|
+
super(message, 2, hint);
|
|
544
623
|
}
|
|
545
624
|
};
|
|
546
625
|
HelpShown = class HelpShown extends Exit {
|
|
@@ -551,10 +630,14 @@ var init_args = __esm(() => {
|
|
|
551
630
|
completion = { active: false };
|
|
552
631
|
OPTIONAL = /\[[^\]]+\]/g;
|
|
553
632
|
USAGE_PREFIX = /^usage:\s*/i;
|
|
633
|
+
FULL_STOP = /\.$/;
|
|
634
|
+
DASHES = /^-+/;
|
|
554
635
|
OUTPUT_OPTIONS = {
|
|
555
636
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
556
637
|
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
557
638
|
};
|
|
639
|
+
UNKNOWN_OPTION = /^unknown option '(-[^']+)'/;
|
|
640
|
+
WHOLE = /^\d+$/;
|
|
558
641
|
DURATION = /^(\d+(?:\.\d+)?)(ms|[smh])?$/;
|
|
559
642
|
UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000 };
|
|
560
643
|
});
|
|
@@ -565,31 +648,265 @@ var init_chart = __esm(() => {
|
|
|
565
648
|
init_style();
|
|
566
649
|
});
|
|
567
650
|
|
|
568
|
-
// ../kit/src/
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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);
|
|
576
687
|
}
|
|
577
|
-
|
|
688
|
+
for (const signal of SIGNALS)
|
|
689
|
+
process4.on(signal, onSignal);
|
|
690
|
+
return held;
|
|
578
691
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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();
|
|
582
898
|
});
|
|
583
899
|
|
|
584
900
|
// ../kit/src/cli.ts
|
|
585
|
-
import
|
|
586
|
-
function commandRows(commands, colors) {
|
|
901
|
+
import process6 from "process";
|
|
902
|
+
async function commandRows(commands, colors) {
|
|
587
903
|
const width = Math.max(...Object.keys(commands).map((name) => name.length));
|
|
588
904
|
const groups = new Map([["Commands:", []]]);
|
|
589
|
-
|
|
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()) {
|
|
590
907
|
const heading = group === undefined ? "Commands:" : `${group}:`;
|
|
591
908
|
const rows = groups.get(heading) ?? [];
|
|
592
|
-
rows.push(` ${colors.cyan(name.padEnd(width))} ${colors.dim(
|
|
909
|
+
rows.push(` ${colors.cyan(name.padEnd(width))} ${colors.dim(summaries[index])}`);
|
|
593
910
|
groups.set(heading, rows);
|
|
594
911
|
}
|
|
595
912
|
return [...groups].filter(([, rows]) => rows.length > 0).flatMap(([heading, rows], index) => [...index === 0 ? [] : [""], colors.bold(heading), ...rows]);
|
|
@@ -609,25 +926,38 @@ function takeOutputFlags(argv) {
|
|
|
609
926
|
}
|
|
610
927
|
return argv.slice(index);
|
|
611
928
|
}
|
|
612
|
-
function
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
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();
|
|
931
|
+
}
|
|
932
|
+
function scopeOf(commands, extra, aliases) {
|
|
933
|
+
return { commands: typeof commands === "function" ? {} : commands, lazy: [...typeof commands === "function" ? [commands] : [], ...extra === undefined ? [] : [extra]], aliases };
|
|
618
934
|
}
|
|
619
|
-
async function
|
|
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) {
|
|
620
946
|
const colors = paint(stream);
|
|
621
947
|
const prefix = path.join(" ");
|
|
948
|
+
const summary = await resolveSummary(lazySummary);
|
|
949
|
+
if (wantsHelpJson())
|
|
950
|
+
return JSON.stringify(description(`${prefix} <command> [options]: ${summary}`, { examples, replaces }));
|
|
622
951
|
const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
|
|
623
952
|
const options = fallback !== undefined && "run" in fallback ? await optionsOf(fallback.run) : undefined;
|
|
624
953
|
const own = Object.entries(options ?? {}).filter(([name]) => !(name in TOP_OPTIONS));
|
|
625
954
|
return [
|
|
626
|
-
header(colors),
|
|
955
|
+
header(colors, summary),
|
|
627
956
|
"",
|
|
628
957
|
usageLine(`${prefix} ${defaultCommand === undefined ? colors.cyan("<command>") : `[${colors.cyan("<command>")}]`} [options]`, colors),
|
|
958
|
+
...replacesRows(replaces, colors),
|
|
629
959
|
"",
|
|
630
|
-
...commandRows(commands, colors),
|
|
960
|
+
...await commandRows(commands, colors),
|
|
631
961
|
...own.length === 0 ? [] : ["", `${colors.bold(`Options of ${defaultCommand}:`)} ${colors.dim("the command run when none is named")}`, ...optionRows(Object.fromEntries(own), colors)],
|
|
632
962
|
"",
|
|
633
963
|
colors.bold("Options:"),
|
|
@@ -639,20 +969,28 @@ async function help({ path, header, commands, defaultCommand, examples }, stream
|
|
|
639
969
|
`);
|
|
640
970
|
}
|
|
641
971
|
function printError(prefix, message, hint, cause) {
|
|
642
|
-
const colors = paint(
|
|
643
|
-
const styled = takesControlCodes(
|
|
972
|
+
const colors = paint(process6.stderr);
|
|
973
|
+
const styled = takesControlCodes(process6.stderr);
|
|
644
974
|
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
645
975
|
const under = (label, text, tint) => text === undefined ? "" : `
|
|
646
976
|
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
647
977
|
console.error(`${head} ${message}${under("cause", cause, colors.dim)}${under("hint", hint, colors.yellow)}`);
|
|
648
978
|
}
|
|
649
|
-
function
|
|
979
|
+
function printJsonError(message, hint, cause) {
|
|
980
|
+
console.error(JSON.stringify({ error: message, ...hint === undefined ? {} : { hint }, ...cause === undefined ? {} : { cause } }));
|
|
981
|
+
}
|
|
982
|
+
function wantsJson(argv) {
|
|
983
|
+
const end = argv.indexOf("--");
|
|
984
|
+
return (end === -1 ? argv : argv.slice(0, end)).includes("--json");
|
|
985
|
+
}
|
|
986
|
+
function reportThrown(prefix, error, redact = (text) => text, json = false) {
|
|
650
987
|
if (error instanceof HelpShown)
|
|
651
988
|
return 0;
|
|
652
989
|
const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
|
|
653
990
|
const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
|
|
654
991
|
const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
|
|
655
|
-
|
|
992
|
+
const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
|
|
993
|
+
report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
|
|
656
994
|
return error instanceof Exit ? error.code : 1;
|
|
657
995
|
}
|
|
658
996
|
async function dispatch(level, typed, { version, redact }) {
|
|
@@ -665,11 +1003,11 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
665
1003
|
}
|
|
666
1004
|
const [name, ...args] = argv;
|
|
667
1005
|
if (name === undefined && level.path.length > 1) {
|
|
668
|
-
console.error(await help(level,
|
|
1006
|
+
console.error(await help(await expand(level, [...level.path.slice(1), ...argv]), process6.stderr));
|
|
669
1007
|
return 2;
|
|
670
1008
|
}
|
|
671
1009
|
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
672
|
-
console.log(await help(level,
|
|
1010
|
+
console.log(await help(await expand(level, [...level.path.slice(1), ...argv]), process6.stdout));
|
|
673
1011
|
return 0;
|
|
674
1012
|
}
|
|
675
1013
|
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
@@ -678,6 +1016,10 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
678
1016
|
}
|
|
679
1017
|
let command = level.commands[level.aliases?.[name] ?? name];
|
|
680
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
|
+
}
|
|
681
1023
|
if (command === undefined && level.defaultCommand !== undefined) {
|
|
682
1024
|
command = level.commands[level.defaultCommand];
|
|
683
1025
|
rest = argv;
|
|
@@ -689,12 +1031,12 @@ async function dispatch(level, typed, { version, redact }) {
|
|
|
689
1031
|
}
|
|
690
1032
|
if ("commands" in command) {
|
|
691
1033
|
const { summary } = command;
|
|
692
|
-
return dispatch({ path: [...level.path, name], header: (colors) => `${colors.bold(`${prefix} ${name}`)} ${
|
|
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 });
|
|
693
1035
|
}
|
|
694
1036
|
try {
|
|
695
1037
|
return await command.run(rest);
|
|
696
1038
|
} catch (error) {
|
|
697
|
-
return reportThrown(`${prefix} ${name}`, error, redact);
|
|
1039
|
+
return reportThrown(`${prefix} ${name}`, error, redact, wantsJson(rest));
|
|
698
1040
|
}
|
|
699
1041
|
}
|
|
700
1042
|
async function optionsOf(run) {
|
|
@@ -712,28 +1054,34 @@ async function optionsOf(run) {
|
|
|
712
1054
|
async function leafCandidates(run, words) {
|
|
713
1055
|
if (!(words.at(-1) ?? "").startsWith("-"))
|
|
714
1056
|
return [FILES];
|
|
715
|
-
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}`);
|
|
716
1058
|
}
|
|
717
|
-
async function candidates(
|
|
1059
|
+
async function candidates(scope, defaultCommand, words, before = []) {
|
|
718
1060
|
const [word = "", ...rest] = words;
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
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)}`));
|
|
722
1070
|
if (command === undefined) {
|
|
723
1071
|
const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
|
|
724
1072
|
return fallback !== undefined && "run" in fallback ? leafCandidates(fallback.run, words) : [];
|
|
725
1073
|
}
|
|
726
|
-
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);
|
|
727
1075
|
}
|
|
728
1076
|
async function printCandidates(found) {
|
|
729
1077
|
for (const line of await found)
|
|
730
1078
|
console.log(line);
|
|
731
1079
|
return 0;
|
|
732
1080
|
}
|
|
733
|
-
async function runCli(cli, argv =
|
|
1081
|
+
async function runCli(cli, argv = process6.argv.slice(2)) {
|
|
734
1082
|
if (argv[0] === COMPLETE)
|
|
735
|
-
return printCandidates(candidates(cli.commands, cli.aliases, cli.defaultCommand, argv.slice(1)));
|
|
736
|
-
return dispatch({ path: [cli.name], header: (colors) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${
|
|
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 });
|
|
737
1085
|
}
|
|
738
1086
|
var LEVEL_OPTIONS, TOP_OPTIONS, COMPLETE = "__complete", FILES = "__files__";
|
|
739
1087
|
var init_cli = __esm(() => {
|
|
@@ -752,74 +1100,388 @@ var init_config = __esm(() => {
|
|
|
752
1100
|
init_args();
|
|
753
1101
|
});
|
|
754
1102
|
|
|
755
|
-
// ../kit/src/
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1103
|
+
// ../kit/src/git.ts
|
|
1104
|
+
var init_git = __esm(() => {
|
|
1105
|
+
init_args();
|
|
1106
|
+
init_tool();
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
// ../kit/src/limit.ts
|
|
1110
|
+
function limitOptions(fallback) {
|
|
1111
|
+
return {
|
|
1112
|
+
limit: { type: "string", description: `show at most this many rows, the rest counted (${fallback})` },
|
|
1113
|
+
full: { type: "boolean", description: "show every row" }
|
|
1114
|
+
};
|
|
761
1115
|
}
|
|
762
|
-
function
|
|
763
|
-
if (
|
|
764
|
-
|
|
765
|
-
if (
|
|
766
|
-
return
|
|
767
|
-
return
|
|
1116
|
+
function limitOf(values, fallback) {
|
|
1117
|
+
if (values.full === true && values.limit !== undefined)
|
|
1118
|
+
badUsage("--limit and --full exclude each other");
|
|
1119
|
+
if (values.full === true || values.json === true && values.limit === undefined)
|
|
1120
|
+
return Number.POSITIVE_INFINITY;
|
|
1121
|
+
return wholeNumber(values.limit, "limit", fallback);
|
|
768
1122
|
}
|
|
769
|
-
function
|
|
770
|
-
|
|
771
|
-
let child;
|
|
772
|
-
const held = {
|
|
773
|
-
get signal() {
|
|
774
|
-
return received;
|
|
775
|
-
},
|
|
776
|
-
forward: (to) => {
|
|
777
|
-
child = to;
|
|
778
|
-
},
|
|
779
|
-
release: () => {
|
|
780
|
-
for (const signal of SIGNALS)
|
|
781
|
-
process5.off(signal, onSignal);
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
function onSignal(signal) {
|
|
785
|
-
received = signal;
|
|
786
|
-
if (child !== undefined) {
|
|
787
|
-
child.kill(signal);
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
if (cleanup === undefined)
|
|
791
|
-
return;
|
|
792
|
-
held.release();
|
|
793
|
-
cleanup(signal);
|
|
794
|
-
process5.kill(process5.pid, signal);
|
|
795
|
-
}
|
|
796
|
-
for (const signal of SIGNALS)
|
|
797
|
-
process5.on(signal, onSignal);
|
|
798
|
-
return held;
|
|
1123
|
+
function moreFooter(more) {
|
|
1124
|
+
return `${more} more, rerun with --full`;
|
|
799
1125
|
}
|
|
800
|
-
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
await Promise.all([collect(proc.stdout, "stdout", chunks), collect(proc.stderr, "stderr", chunks)]);
|
|
804
|
-
await proc.exited;
|
|
805
|
-
const output = Buffer.concat(chunks.map(({ data }) => data));
|
|
806
|
-
return { code: exitCodeOf(proc.exitCode, proc.signalCode), chunks, output, text: output.toString() };
|
|
1126
|
+
function capped(rows, limit) {
|
|
1127
|
+
const more = rows.length - limit;
|
|
1128
|
+
return more > 0 ? { shown: rows.slice(0, limit), footer: moreFooter(more) } : { shown: [...rows], footer: undefined };
|
|
807
1129
|
}
|
|
808
|
-
var
|
|
809
|
-
|
|
810
|
-
|
|
1130
|
+
var init_limit = __esm(() => {
|
|
1131
|
+
init_args();
|
|
1132
|
+
});
|
|
1133
|
+
|
|
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"]);
|
|
811
1144
|
});
|
|
812
1145
|
|
|
813
1146
|
// ../kit/src/index.ts
|
|
814
1147
|
var init_src = __esm(() => {
|
|
815
1148
|
init_args();
|
|
816
1149
|
init_chart();
|
|
1150
|
+
init_ci();
|
|
817
1151
|
init_cli();
|
|
818
1152
|
init_config();
|
|
819
1153
|
init_format();
|
|
1154
|
+
init_git();
|
|
1155
|
+
init_limit();
|
|
1156
|
+
init_logs();
|
|
820
1157
|
init_process();
|
|
1158
|
+
init_secrets();
|
|
821
1159
|
init_style();
|
|
822
1160
|
init_text();
|
|
1161
|
+
init_tool();
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
// src/registry.ts
|
|
1165
|
+
function isPublic(registry) {
|
|
1166
|
+
return registry.url.replace(TRAILING_SLASHES, "") === PUBLIC_REGISTRY;
|
|
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
|
+
}
|
|
1174
|
+
async function get(fetcher, url, token) {
|
|
1175
|
+
try {
|
|
1176
|
+
return await fetcher(url, requestInit(token));
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
return unreachable(`cannot reach ${new URL(url).host}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
function isValidName(name) {
|
|
1182
|
+
return PACKAGE_NAME.test(name) && name.length <= 214;
|
|
1183
|
+
}
|
|
1184
|
+
async function fetchPackument(name, registry, fetcher = fetch) {
|
|
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);
|
|
1188
|
+
if (response.status === 404)
|
|
1189
|
+
return;
|
|
1190
|
+
if (!response.ok)
|
|
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;
|
|
1201
|
+
}
|
|
1202
|
+
async function fetchDownloads(name, fetcher = fetch) {
|
|
1203
|
+
const response = await get(fetcher, `${DOWNLOADS_API}/${encodeURIComponent(name)}`);
|
|
1204
|
+
if (!response.ok)
|
|
1205
|
+
return;
|
|
1206
|
+
try {
|
|
1207
|
+
return (await response.json()).downloads;
|
|
1208
|
+
} catch {
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
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;
|
|
1213
|
+
var init_registry = __esm(() => {
|
|
1214
|
+
init_src();
|
|
1215
|
+
TRAILING_SLASHES = /\/+$/;
|
|
1216
|
+
PACKAGE_NAME = /^(?:@[\w~-][\w.~-]*\/)?[\w~-][\w.~-]*$/;
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
// src/config.ts
|
|
1220
|
+
import { homedir } from "os";
|
|
1221
|
+
import { dirname, join } from "path";
|
|
1222
|
+
import process8 from "process";
|
|
1223
|
+
function expand2(value, env) {
|
|
1224
|
+
let missing = false;
|
|
1225
|
+
const expanded = value.replace(ENV_REFERENCE, (_, braced, bare) => {
|
|
1226
|
+
const found = env[braced ?? bare ?? ""];
|
|
1227
|
+
missing ||= found === undefined;
|
|
1228
|
+
return found ?? "";
|
|
1229
|
+
});
|
|
1230
|
+
return missing ? undefined : expanded;
|
|
1231
|
+
}
|
|
1232
|
+
function literal(value) {
|
|
1233
|
+
return expand2(value, {}) === value ? value : undefined;
|
|
1234
|
+
}
|
|
1235
|
+
function parseNpmrc(text, env) {
|
|
1236
|
+
const config = { scopes: {}, tokens: {}, scopeTokens: {} };
|
|
1237
|
+
for (const line of text.split(`
|
|
1238
|
+
`)) {
|
|
1239
|
+
const at = line.indexOf("=");
|
|
1240
|
+
if (at === -1 || COMMENT.test(line))
|
|
1241
|
+
continue;
|
|
1242
|
+
const key = line.slice(0, at).trim();
|
|
1243
|
+
const scope = SCOPE_REGISTRY.exec(key)?.[1];
|
|
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;
|
|
1249
|
+
if (scope !== undefined)
|
|
1250
|
+
config.scopes[scope] = value;
|
|
1251
|
+
else if (tokenFor !== undefined)
|
|
1252
|
+
config.tokens[tokenFor] = value;
|
|
1253
|
+
else if (key === "registry")
|
|
1254
|
+
config.registry = value;
|
|
1255
|
+
}
|
|
1256
|
+
return config;
|
|
1257
|
+
}
|
|
1258
|
+
function bunEntry(entry, env) {
|
|
1259
|
+
const url = typeof entry === "string" ? entry : entry.url;
|
|
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 } };
|
|
1262
|
+
}
|
|
1263
|
+
function parseBunfig(text, env) {
|
|
1264
|
+
const config = { scopes: {}, tokens: {}, scopeTokens: {} };
|
|
1265
|
+
const install = Bun.TOML.parse(text).install;
|
|
1266
|
+
if (install?.registry !== undefined) {
|
|
1267
|
+
const { url, token } = bunEntry(install.registry, env);
|
|
1268
|
+
if (url !== undefined)
|
|
1269
|
+
config.registry = url;
|
|
1270
|
+
if (url !== undefined && token !== undefined)
|
|
1271
|
+
config.tokens[url.replace(PROTOCOL, "")] = token;
|
|
1272
|
+
}
|
|
1273
|
+
for (const [scope, entry] of Object.entries(install?.scopes ?? {})) {
|
|
1274
|
+
const { url, token } = bunEntry(entry, env);
|
|
1275
|
+
if (url !== undefined)
|
|
1276
|
+
config.scopes[scope.startsWith("@") ? scope : `@${scope}`] = url;
|
|
1277
|
+
if (token !== undefined)
|
|
1278
|
+
config.scopeTokens[scope.startsWith("@") ? scope : `@${scope}`] = token;
|
|
1279
|
+
}
|
|
1280
|
+
return config;
|
|
1281
|
+
}
|
|
1282
|
+
function tokenFor(config, url) {
|
|
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];
|
|
1285
|
+
return best === undefined ? undefined : config.tokens[best];
|
|
1286
|
+
}
|
|
1287
|
+
function mayCarryToken(url) {
|
|
1288
|
+
const { protocol, hostname } = new URL(url);
|
|
1289
|
+
return protocol === "https:" || LOCAL_HOSTS.has(hostname);
|
|
1290
|
+
}
|
|
1291
|
+
function registryFor(name, configs) {
|
|
1292
|
+
const scope = name.startsWith("@") ? name.slice(0, name.indexOf("/")) : undefined;
|
|
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)}`);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
async function readIfThere(path) {
|
|
1308
|
+
const file = Bun.file(path);
|
|
1309
|
+
return await file.exists() ? file.text() : undefined;
|
|
1310
|
+
}
|
|
1311
|
+
async function loadConfigs(cwd = process8.cwd(), env = process8.env, home = homedir()) {
|
|
1312
|
+
const directories = [];
|
|
1313
|
+
for (let dir = cwd;!directories.includes(dir); dir = dirname(dir))
|
|
1314
|
+
directories.push(dir);
|
|
1315
|
+
if (!directories.includes(home))
|
|
1316
|
+
directories.push(home);
|
|
1317
|
+
const configs = [];
|
|
1318
|
+
for (const dir of directories) {
|
|
1319
|
+
const bunfig = await readIfThere(join(dir, "bunfig.toml"));
|
|
1320
|
+
const npmrc = await readIfThere(join(dir, ".npmrc"));
|
|
1321
|
+
if (bunfig !== undefined)
|
|
1322
|
+
configs.push(readable(join(dir, "bunfig.toml"), () => parseBunfig(bunfig, env)));
|
|
1323
|
+
if (npmrc !== undefined)
|
|
1324
|
+
configs.push(parseNpmrc(npmrc, env));
|
|
1325
|
+
}
|
|
1326
|
+
return configs;
|
|
1327
|
+
}
|
|
1328
|
+
function isSeverity(value) {
|
|
1329
|
+
return SEVERITIES.includes(value);
|
|
1330
|
+
}
|
|
1331
|
+
function parseSettings(text) {
|
|
1332
|
+
const raw = Bun.JSONC.parse(text);
|
|
1333
|
+
const settings = {};
|
|
1334
|
+
if (Array.isArray(raw?.licenses) && raw.licenses.every((each) => typeof each === "string"))
|
|
1335
|
+
settings.licenses = raw.licenses;
|
|
1336
|
+
if (isSeverity(raw?.blockSeverity))
|
|
1337
|
+
settings.blockSeverity = raw.blockSeverity;
|
|
1338
|
+
return settings;
|
|
1339
|
+
}
|
|
1340
|
+
async function loadSettings(cwd = process8.cwd(), home = homedir()) {
|
|
1341
|
+
const files = [];
|
|
1342
|
+
for (let dir = cwd;!files.includes(join(dir, SETTINGS_FILE)); dir = dirname(dir))
|
|
1343
|
+
files.push(join(dir, SETTINGS_FILE));
|
|
1344
|
+
files.push(join(home, ".config", "pkg", "config.json"));
|
|
1345
|
+
const settings = {};
|
|
1346
|
+
for (const file of files) {
|
|
1347
|
+
const text = await readIfThere(file);
|
|
1348
|
+
if (text === undefined)
|
|
1349
|
+
continue;
|
|
1350
|
+
const found = readable(file, () => parseSettings(text));
|
|
1351
|
+
settings.licenses ??= found.licenses;
|
|
1352
|
+
settings.blockSeverity ??= found.blockSeverity;
|
|
1353
|
+
}
|
|
1354
|
+
return settings;
|
|
1355
|
+
}
|
|
1356
|
+
var ENV_REFERENCE, COMMENT, SCOPE_REGISTRY, AUTH_TOKEN, PROTOCOL, TRAILING_SLASHES2, LOCAL_HOSTS, QUOTES, SEVERITIES, DEFAULT_BLOCK_SEVERITY = "high", SETTINGS_FILE = ".pkgrc.json";
|
|
1357
|
+
var init_config2 = __esm(() => {
|
|
1358
|
+
init_registry();
|
|
1359
|
+
ENV_REFERENCE = /\$\{(\w+)\}|\$(\w+)/g;
|
|
1360
|
+
COMMENT = /^\s*[#;]/;
|
|
1361
|
+
SCOPE_REGISTRY = /^(@[^:]+):registry$/;
|
|
1362
|
+
AUTH_TOKEN = /^(\/\/.+?):_authToken$/;
|
|
1363
|
+
PROTOCOL = /^\w+:/;
|
|
1364
|
+
TRAILING_SLASHES2 = /\/+$/;
|
|
1365
|
+
LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1366
|
+
QUOTES = /^(["'])(.*)\1$/;
|
|
1367
|
+
SEVERITIES = ["low", "moderate", "high", "critical"];
|
|
1368
|
+
});
|
|
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
|
+
|
|
1411
|
+
// src/advisories.ts
|
|
1412
|
+
function severityRank(severity) {
|
|
1413
|
+
return SEVERITIES.indexOf(severity);
|
|
1414
|
+
}
|
|
1415
|
+
function fixedVersion(vulnerable, version) {
|
|
1416
|
+
const range = vulnerable.split("||").map((each) => each.trim()).find((each) => inRange(version, each));
|
|
1417
|
+
return range === undefined ? undefined : UPPER_BOUND.exec(range)?.[1];
|
|
1418
|
+
}
|
|
1419
|
+
async function fetchAdvisories(versions, registry, fetcher = fetch) {
|
|
1420
|
+
const url = `${registry.url.replace(TRAILING_SLASHES3, "")}${BULK_PATH}`;
|
|
1421
|
+
const host = new URL(registry.url).host;
|
|
1422
|
+
let response;
|
|
1423
|
+
try {
|
|
1424
|
+
response = await fetcher(url, {
|
|
1425
|
+
method: "POST",
|
|
1426
|
+
headers: { accept: "application/json", "content-type": "application/json", ...registry.token === undefined ? {} : { authorization: `Bearer ${registry.token}` } },
|
|
1427
|
+
body: JSON.stringify(versions),
|
|
1428
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT2)
|
|
1429
|
+
});
|
|
1430
|
+
} catch (error) {
|
|
1431
|
+
return { found: {}, unavailable: `cannot reach ${host} for advisories: ${error instanceof Error ? error.message : String(error)}` };
|
|
1432
|
+
}
|
|
1433
|
+
if (NO_ENDPOINT.has(response.status))
|
|
1434
|
+
return { found: {}, isUnsupported: true, unavailable: `${host} has no advisory endpoint` };
|
|
1435
|
+
if (!response.ok)
|
|
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
|
+
}
|
|
1443
|
+
const found = {};
|
|
1444
|
+
for (const [name, raws] of Object.entries(body)) {
|
|
1445
|
+
if (!Array.isArray(raws))
|
|
1446
|
+
continue;
|
|
1447
|
+
found[name] = raws.filter((raw) => SEVERITIES.includes(raw.severity)).map((raw) => ({
|
|
1448
|
+
id: raw.id,
|
|
1449
|
+
ghsa: GHSA.exec(raw.url)?.[0],
|
|
1450
|
+
url: raw.url,
|
|
1451
|
+
title: raw.title,
|
|
1452
|
+
severity: raw.severity,
|
|
1453
|
+
vulnerable: raw.vulnerable_versions
|
|
1454
|
+
}));
|
|
1455
|
+
}
|
|
1456
|
+
return { found };
|
|
1457
|
+
}
|
|
1458
|
+
function advisoriesFor(advisories, version) {
|
|
1459
|
+
return (advisories ?? []).filter((each) => inRange(version, each.vulnerable)).map((each) => ({ ...each, fixed: fixedVersion(each.vulnerable, version) }));
|
|
1460
|
+
}
|
|
1461
|
+
function filterAdvisories(advisories, level, ignore = []) {
|
|
1462
|
+
return advisories.filter((each) => severityRank(each.severity) >= severityRank(level) && !ignore.includes(each.ghsa ?? "") && !ignore.includes(String(each.id)));
|
|
1463
|
+
}
|
|
1464
|
+
var BULK_PATH = "/-/npm/v1/security/advisories/bulk", TRAILING_SLASHES3, REQUEST_TIMEOUT2 = 1e4, GHSA, UPPER_BOUND, NO_ENDPOINT;
|
|
1465
|
+
var init_advisories = __esm(() => {
|
|
1466
|
+
init_config2();
|
|
1467
|
+
init_semver();
|
|
1468
|
+
TRAILING_SLASHES3 = /\/+$/;
|
|
1469
|
+
GHSA = /GHSA-[\w-]+$/;
|
|
1470
|
+
UPPER_BOUND = /(?:^|\s)<([\w.+-]+)/;
|
|
1471
|
+
NO_ENDPOINT = new Set([404, 405, 501]);
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
// src/licenses.ts
|
|
1475
|
+
function isLicenseAllowed(expression, allowed) {
|
|
1476
|
+
const known = new Set(allowed.map((each) => each.toLowerCase()));
|
|
1477
|
+
return expression.replace(PARENS, " ").trim().split(OR_SPLIT).some((alternative) => alternative.split(AND_SPLIT).every((term) => known.has(term.replace(WITH_EXCEPTION, "").trim().toLowerCase())));
|
|
1478
|
+
}
|
|
1479
|
+
var OR_SPLIT, AND_SPLIT, WITH_EXCEPTION, PARENS;
|
|
1480
|
+
var init_licenses = __esm(() => {
|
|
1481
|
+
OR_SPLIT = /\s+OR\s+/i;
|
|
1482
|
+
AND_SPLIT = /\s+AND\s+/i;
|
|
1483
|
+
WITH_EXCEPTION = /\s+WITH\s+\S+$/i;
|
|
1484
|
+
PARENS = /[()]/g;
|
|
823
1485
|
});
|
|
824
1486
|
|
|
825
1487
|
// src/popular.json
|
|
@@ -1333,20 +1995,23 @@ var init_popular = __esm(() => {
|
|
|
1333
1995
|
function baseName(name) {
|
|
1334
1996
|
return name.slice(name.indexOf("/") + 1);
|
|
1335
1997
|
}
|
|
1336
|
-
function
|
|
1998
|
+
function scopeOf2(name) {
|
|
1337
1999
|
return name.startsWith("@") ? name.slice(0, name.indexOf("/")) : "";
|
|
1338
2000
|
}
|
|
1339
2001
|
function squash(name) {
|
|
1340
2002
|
return name.toLowerCase().replace(SEPARATORS, "");
|
|
1341
2003
|
}
|
|
1342
2004
|
function resemblance(name, popular) {
|
|
1343
|
-
if (
|
|
2005
|
+
if (scopeOf2(name) !== scopeOf2(popular) && baseName(name) === baseName(popular) && baseName(name).length >= MIN_LENGTH)
|
|
1344
2006
|
return `${popular} under another scope`;
|
|
1345
2007
|
if (squash(name) === squash(popular))
|
|
1346
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;
|
|
1347
2013
|
const edits = distance(name, popular);
|
|
1348
|
-
|
|
1349
|
-
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;
|
|
1350
2015
|
}
|
|
1351
2016
|
function nearMisses(name, popular = popular_default) {
|
|
1352
2017
|
if (popular.includes(name))
|
|
@@ -1382,6 +2047,8 @@ function resolve(packument, request) {
|
|
|
1382
2047
|
return tags[wanted];
|
|
1383
2048
|
if (versions.includes(wanted))
|
|
1384
2049
|
return wanted;
|
|
2050
|
+
if (!isRange(wanted))
|
|
2051
|
+
return;
|
|
1385
2052
|
return versions.filter((version) => Bun.semver.satisfies(version, wanted)).sort(Bun.semver.order).at(-1);
|
|
1386
2053
|
}
|
|
1387
2054
|
function licenseOf(license) {
|
|
@@ -1405,7 +2072,7 @@ function safeRelease(packument, now, minAgeDays) {
|
|
|
1405
2072
|
}
|
|
1406
2073
|
function analyze(input) {
|
|
1407
2074
|
const { spec, packument, registry, now = Date.now(), minAgeDays = DEFAULT_MIN_AGE_DAYS } = input;
|
|
1408
|
-
const report = { name: spec.name, registry, exists: packument !== undefined, requested: spec.request, installScripts: {}, nearMisses: nearMisses(spec.name), warnings: [], blocked: false, downloads: input.downloads };
|
|
2075
|
+
const report = { name: spec.name, registry, exists: packument !== undefined, requested: spec.request, installScripts: {}, nearMisses: nearMisses(spec.name), advisories: input.advisories ?? [], warnings: [], blocked: false, downloads: input.downloads };
|
|
1409
2076
|
if (report.nearMisses.length > 0)
|
|
1410
2077
|
report.warnings.push(`name looks like ${report.nearMisses.join("; ")}, check it is the package meant`);
|
|
1411
2078
|
if (packument === undefined) {
|
|
@@ -1432,7 +2099,9 @@ function analyze(input) {
|
|
|
1432
2099
|
}
|
|
1433
2100
|
const age = ageDays(report.published, now);
|
|
1434
2101
|
if (age === undefined) {
|
|
1435
|
-
|
|
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" : ""}`);
|
|
1436
2105
|
} else if (age < minAgeDays) {
|
|
1437
2106
|
report.blocked = true;
|
|
1438
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}`}`);
|
|
@@ -1442,166 +2111,61 @@ function analyze(input) {
|
|
|
1442
2111
|
report.warnings.push(`${version} runs ${scripts.join(", ")} at install`);
|
|
1443
2112
|
if (report.deprecated !== undefined)
|
|
1444
2113
|
report.warnings.push(`${version} is deprecated: ${report.deprecated}`);
|
|
2114
|
+
if (input.licenses !== undefined) {
|
|
2115
|
+
const isMissing = report.license === undefined || report.license === "";
|
|
2116
|
+
if (isMissing) {
|
|
2117
|
+
report.blocked = true;
|
|
2118
|
+
report.warnings.push(`${version} declares no licence, and the allowlist needs one`);
|
|
2119
|
+
} else if (!isLicenseAllowed(report.license ?? "", input.licenses)) {
|
|
2120
|
+
report.blocked = true;
|
|
2121
|
+
report.warnings.push(`licence ${report.license} is not in the allowlist (${input.licenses.join(", ")})`);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
if (input.blockSeverity !== undefined) {
|
|
2125
|
+
const blocking = filterAdvisories(report.advisories, input.blockSeverity);
|
|
2126
|
+
if (blocking.length > 0) {
|
|
2127
|
+
report.blocked = true;
|
|
2128
|
+
report.warnings.push(`${version} has ${blocking.length} advisor${blocking.length === 1 ? "y" : "ies"} at ${input.blockSeverity} or above: ${blocking.map((each) => each.ghsa ?? each.id).join(", ")}`);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
1445
2131
|
return report;
|
|
1446
2132
|
}
|
|
1447
|
-
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;
|
|
1448
2134
|
var init_analyze = __esm(() => {
|
|
2135
|
+
init_advisories();
|
|
2136
|
+
init_licenses();
|
|
1449
2137
|
init_nearmiss();
|
|
2138
|
+
init_registry();
|
|
2139
|
+
init_semver();
|
|
2140
|
+
PUBLIC_HOST = new URL(PUBLIC_REGISTRY).host;
|
|
1450
2141
|
INSTALL_SCRIPTS = ["preinstall", "install", "postinstall"];
|
|
1451
2142
|
GIT_PREFIX = /^git\+/;
|
|
1452
2143
|
VERSION_SEPARATOR = /(?<=.)@/;
|
|
1453
2144
|
});
|
|
1454
2145
|
|
|
1455
|
-
// src/registry.ts
|
|
1456
|
-
function isPublic(registry) {
|
|
1457
|
-
return registry.url.replace(TRAILING_SLASHES, "") === PUBLIC_REGISTRY;
|
|
1458
|
-
}
|
|
1459
|
-
async function get(fetcher, url, token) {
|
|
1460
|
-
try {
|
|
1461
|
-
return await fetcher(url, { headers: { accept: "application/json", ...token === undefined ? {} : { authorization: `Bearer ${token}` } } });
|
|
1462
|
-
} catch (error) {
|
|
1463
|
-
return fail(`cannot reach ${new URL(url).host}: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
async function fetchPackument(name, registry, fetcher = fetch) {
|
|
1467
|
-
const response = await get(fetcher, `${registry.url.replace(TRAILING_SLASHES, "")}/${name.replace("/", "%2f")}`, registry.token);
|
|
1468
|
-
if (response.status === 404)
|
|
1469
|
-
return;
|
|
1470
|
-
if (!response.ok)
|
|
1471
|
-
fail(`${new URL(registry.url).host} answered ${response.status} for ${name}`, 1);
|
|
1472
|
-
return await response.json();
|
|
1473
|
-
}
|
|
1474
|
-
async function fetchDownloads(name, fetcher = fetch) {
|
|
1475
|
-
const response = await get(fetcher, `${DOWNLOADS_API}/${name}`);
|
|
1476
|
-
if (!response.ok)
|
|
1477
|
-
return;
|
|
1478
|
-
const body = await response.json();
|
|
1479
|
-
return body.downloads;
|
|
1480
|
-
}
|
|
1481
|
-
var PUBLIC_REGISTRY = "https://registry.npmjs.org", DOWNLOADS_API = "https://api.npmjs.org/downloads/point/last-week", TRAILING_SLASHES;
|
|
1482
|
-
var init_registry = __esm(() => {
|
|
1483
|
-
init_src();
|
|
1484
|
-
TRAILING_SLASHES = /\/+$/;
|
|
1485
|
-
});
|
|
1486
|
-
|
|
1487
|
-
// src/config.ts
|
|
1488
|
-
import { homedir } from "os";
|
|
1489
|
-
import { dirname, join } from "path";
|
|
1490
|
-
import process7 from "process";
|
|
1491
|
-
function expand(value, env) {
|
|
1492
|
-
let missing = false;
|
|
1493
|
-
const expanded = value.replace(ENV_REFERENCE, (_, braced, bare) => {
|
|
1494
|
-
const found = env[braced ?? bare ?? ""];
|
|
1495
|
-
missing ||= found === undefined;
|
|
1496
|
-
return found ?? "";
|
|
1497
|
-
});
|
|
1498
|
-
return missing ? undefined : expanded;
|
|
1499
|
-
}
|
|
1500
|
-
function parseNpmrc(text, env) {
|
|
1501
|
-
const config = { scopes: {}, tokens: {}, scopeTokens: {} };
|
|
1502
|
-
for (const line of text.split(`
|
|
1503
|
-
`)) {
|
|
1504
|
-
const at = line.indexOf("=");
|
|
1505
|
-
if (at === -1 || COMMENT.test(line))
|
|
1506
|
-
continue;
|
|
1507
|
-
const key = line.slice(0, at).trim();
|
|
1508
|
-
const value = expand(line.slice(at + 1).trim().replace(QUOTES, "$2"), env);
|
|
1509
|
-
if (value === undefined || value === "")
|
|
1510
|
-
continue;
|
|
1511
|
-
const scope = SCOPE_REGISTRY.exec(key)?.[1];
|
|
1512
|
-
const tokenFor = AUTH_TOKEN.exec(key)?.[1];
|
|
1513
|
-
if (scope !== undefined)
|
|
1514
|
-
config.scopes[scope] = value;
|
|
1515
|
-
else if (tokenFor !== undefined)
|
|
1516
|
-
config.tokens[tokenFor] = value;
|
|
1517
|
-
else if (key === "registry")
|
|
1518
|
-
config.registry = value;
|
|
1519
|
-
}
|
|
1520
|
-
return config;
|
|
1521
|
-
}
|
|
1522
|
-
function bunEntry(entry, env) {
|
|
1523
|
-
const url = typeof entry === "string" ? entry : entry.url;
|
|
1524
|
-
const token = typeof entry === "string" || entry.token === undefined ? undefined : expand(entry.token, env);
|
|
1525
|
-
return { ...url === undefined ? {} : { url: expand(url, env) ?? url }, ...token === undefined ? {} : { token } };
|
|
1526
|
-
}
|
|
1527
|
-
function parseBunfig(text, env) {
|
|
1528
|
-
const config = { scopes: {}, tokens: {}, scopeTokens: {} };
|
|
1529
|
-
const install = Bun.TOML.parse(text).install;
|
|
1530
|
-
if (install?.registry !== undefined) {
|
|
1531
|
-
const { url, token } = bunEntry(install.registry, env);
|
|
1532
|
-
if (url !== undefined)
|
|
1533
|
-
config.registry = url;
|
|
1534
|
-
if (url !== undefined && token !== undefined)
|
|
1535
|
-
config.tokens[url.replace(PROTOCOL, "")] = token;
|
|
1536
|
-
}
|
|
1537
|
-
for (const [scope, entry] of Object.entries(install?.scopes ?? {})) {
|
|
1538
|
-
const { url, token } = bunEntry(entry, env);
|
|
1539
|
-
if (url !== undefined)
|
|
1540
|
-
config.scopes[scope.startsWith("@") ? scope : `@${scope}`] = url;
|
|
1541
|
-
if (token !== undefined)
|
|
1542
|
-
config.scopeTokens[scope.startsWith("@") ? scope : `@${scope}`] = token;
|
|
1543
|
-
}
|
|
1544
|
-
return config;
|
|
1545
|
-
}
|
|
1546
|
-
function tokenFor(config, url) {
|
|
1547
|
-
const address = url.replace(PROTOCOL, "");
|
|
1548
|
-
const best = Object.keys(config.tokens).filter((key) => address.startsWith(key)).sort((a, b) => b.length - a.length)[0];
|
|
1549
|
-
return best === undefined ? undefined : config.tokens[best];
|
|
1550
|
-
}
|
|
1551
|
-
function registryFor(name, configs) {
|
|
1552
|
-
const scope = name.startsWith("@") ? name.slice(0, name.indexOf("/")) : undefined;
|
|
1553
|
-
for (const config of configs) {
|
|
1554
|
-
const url = scope === undefined ? config.registry : config.scopes[scope] ?? config.registry;
|
|
1555
|
-
if (url === undefined)
|
|
1556
|
-
continue;
|
|
1557
|
-
const token = (scope === undefined ? undefined : config.scopeTokens[scope]) ?? tokenFor(config, url) ?? configs.map((other) => tokenFor(other, url)).find((found) => found !== undefined);
|
|
1558
|
-
return { url, token };
|
|
1559
|
-
}
|
|
1560
|
-
return { url: PUBLIC_REGISTRY };
|
|
1561
|
-
}
|
|
1562
|
-
async function readIfThere(path) {
|
|
1563
|
-
const file = Bun.file(path);
|
|
1564
|
-
return await file.exists() ? file.text() : undefined;
|
|
1565
|
-
}
|
|
1566
|
-
async function loadConfigs(cwd = process7.cwd(), env = process7.env, home = homedir()) {
|
|
1567
|
-
const directories = [];
|
|
1568
|
-
for (let dir = cwd;!directories.includes(dir); dir = dirname(dir))
|
|
1569
|
-
directories.push(dir);
|
|
1570
|
-
if (!directories.includes(home))
|
|
1571
|
-
directories.push(home);
|
|
1572
|
-
const configs = [];
|
|
1573
|
-
for (const dir of directories) {
|
|
1574
|
-
const bunfig = await readIfThere(join(dir, "bunfig.toml"));
|
|
1575
|
-
const npmrc = await readIfThere(join(dir, ".npmrc"));
|
|
1576
|
-
if (bunfig !== undefined)
|
|
1577
|
-
configs.push(parseBunfig(bunfig, env));
|
|
1578
|
-
if (npmrc !== undefined)
|
|
1579
|
-
configs.push(parseNpmrc(npmrc, env));
|
|
1580
|
-
}
|
|
1581
|
-
return configs;
|
|
1582
|
-
}
|
|
1583
|
-
var ENV_REFERENCE, COMMENT, SCOPE_REGISTRY, AUTH_TOKEN, PROTOCOL, QUOTES;
|
|
1584
|
-
var init_config2 = __esm(() => {
|
|
1585
|
-
init_registry();
|
|
1586
|
-
ENV_REFERENCE = /\$\{(\w+)\}|\$(\w+)/g;
|
|
1587
|
-
COMMENT = /^\s*[#;]/;
|
|
1588
|
-
SCOPE_REGISTRY = /^(@[^:]+):registry$/;
|
|
1589
|
-
AUTH_TOKEN = /^(\/\/.+?):_authToken$/;
|
|
1590
|
-
PROTOCOL = /^\w+:/;
|
|
1591
|
-
QUOTES = /^(["'])(.*)\1$/;
|
|
1592
|
-
});
|
|
1593
|
-
|
|
1594
2146
|
// src/commands/check.ts
|
|
1595
2147
|
var exports_check = {};
|
|
1596
2148
|
__export(exports_check, {
|
|
1597
|
-
check: () => check
|
|
2149
|
+
check: () => check,
|
|
2150
|
+
checkPackage: () => checkPackage
|
|
1598
2151
|
});
|
|
1599
2152
|
async function checkPackage(text, context) {
|
|
1600
2153
|
const spec = parseSpec(text);
|
|
1601
2154
|
const registry = registryFor(spec.name, context.configs);
|
|
1602
2155
|
const packument = await fetchPackument(spec.name, registry, context.fetcher);
|
|
1603
2156
|
const downloads = packument !== undefined && isPublic(registry) ? await fetchDownloads(spec.name, context.fetcher) : undefined;
|
|
1604
|
-
|
|
2157
|
+
const input = { spec, packument, downloads, registry: new URL(registry.url).host, now: context.now, minAgeDays: context.minAgeDays, licenses: context.settings?.licenses };
|
|
2158
|
+
const report = analyze(input);
|
|
2159
|
+
const blockSeverity = context.settings?.blockSeverity ?? DEFAULT_BLOCK_SEVERITY;
|
|
2160
|
+
if (report.version === undefined)
|
|
2161
|
+
return report;
|
|
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;
|
|
1605
2169
|
}
|
|
1606
2170
|
function when(release, now) {
|
|
1607
2171
|
return release?.published === undefined ? "" : `, published ${formatAge(release.published, now)} (${release.published.slice(0, 10)})`;
|
|
@@ -1634,7 +2198,7 @@ async function check(args) {
|
|
|
1634
2198
|
fail(`a package name is needed
|
|
1635
2199
|
|
|
1636
2200
|
${usage}`);
|
|
1637
|
-
const context = { configs: await loadConfigs(), minAgeDays: wholeNumber(values["min-age"], "min-age", DEFAULT_MIN_AGE_DAYS, 0) };
|
|
2201
|
+
const context = { configs: await loadConfigs(), settings: await loadSettings(), minAgeDays: wholeNumber(values["min-age"], "min-age", DEFAULT_MIN_AGE_DAYS, 0) };
|
|
1638
2202
|
const reports = await Promise.all(positionals.map(async (text) => checkPackage(text, context)));
|
|
1639
2203
|
if (values.json === true)
|
|
1640
2204
|
printJson(reports.length === 1 ? reports[0] : reports);
|
|
@@ -1647,9 +2211,13 @@ ${usage}`);
|
|
|
1647
2211
|
var usage = `pkg [check] <name>[@version]...
|
|
1648
2212
|
|
|
1649
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.
|
|
1650
|
-
|
|
2214
|
+
|
|
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.
|
|
2216
|
+
|
|
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;
|
|
1651
2218
|
var init_check = __esm(() => {
|
|
1652
2219
|
init_src();
|
|
2220
|
+
init_advisories();
|
|
1653
2221
|
init_analyze();
|
|
1654
2222
|
init_config2();
|
|
1655
2223
|
init_registry();
|
|
@@ -1659,125 +2227,176 @@ var init_check = __esm(() => {
|
|
|
1659
2227
|
};
|
|
1660
2228
|
});
|
|
1661
2229
|
|
|
1662
|
-
//
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
if (
|
|
1666
|
-
return
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
drawn = 0;
|
|
1679
|
-
};
|
|
1680
|
-
const draw = () => {
|
|
1681
|
-
const width = (stream.columns === undefined || stream.columns === 0 ? 80 : stream.columns) - 1;
|
|
1682
|
-
const height = stream.rows === undefined || stream.rows === 0 ? 24 : stream.rows;
|
|
1683
|
-
const lines = render(FRAMES[frame++ % FRAMES.length]).slice(0, Math.max(1, height - 1)).map((line) => cutToWidth(line, width));
|
|
1684
|
-
if (lines.length === 0) {
|
|
1685
|
-
clear();
|
|
1686
|
-
return;
|
|
1687
|
-
}
|
|
1688
|
-
write(`${drawn > 0 ? `${CLEAR_LINE}${LINE_UP.repeat(drawn - 1)}` : CLEAR_LINE}${lines.join(`
|
|
1689
|
-
`)}`);
|
|
1690
|
-
drawn = lines.length;
|
|
1691
|
-
};
|
|
1692
|
-
const timer = setInterval(draw, FRAME_MS);
|
|
1693
|
-
const restore = interceptOutput(clear);
|
|
1694
|
-
const onExit = () => clear();
|
|
1695
|
-
const signals = holdSignals(stop);
|
|
1696
|
-
function stop() {
|
|
1697
|
-
clearInterval(timer);
|
|
1698
|
-
clear();
|
|
1699
|
-
restore();
|
|
1700
|
-
process8.off("exit", onExit);
|
|
1701
|
-
signals.release();
|
|
1702
|
-
}
|
|
1703
|
-
timer.unref();
|
|
1704
|
-
process8.on("exit", onExit);
|
|
1705
|
-
return {
|
|
1706
|
-
redraw: draw,
|
|
1707
|
-
print: (text) => {
|
|
1708
|
-
clear();
|
|
1709
|
-
write(`${text}
|
|
1710
|
-
`);
|
|
1711
|
-
draw();
|
|
1712
|
-
},
|
|
1713
|
-
stop
|
|
1714
|
-
};
|
|
1715
|
-
}
|
|
1716
|
-
function cutToWidth(line, width) {
|
|
1717
|
-
if (Bun.stringWidth(line) <= width)
|
|
1718
|
-
return line;
|
|
1719
|
-
let room = width;
|
|
1720
|
-
let kept = "";
|
|
1721
|
-
let index = 0;
|
|
1722
|
-
let linked = false;
|
|
1723
|
-
while (index < line.length) {
|
|
1724
|
-
ESCAPE_CODE.lastIndex = index;
|
|
1725
|
-
const escape = ESCAPE_CODE.exec(line);
|
|
1726
|
-
if (escape !== null) {
|
|
1727
|
-
kept += escape[0];
|
|
1728
|
-
linked ||= escape[0].startsWith(`${ESC}]`);
|
|
1729
|
-
index = ESCAPE_CODE.lastIndex;
|
|
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
|
+
}
|
|
2240
|
+
function parseBunLock(text) {
|
|
2241
|
+
const lock = Bun.JSONC.parse(text);
|
|
2242
|
+
const seen = new Map;
|
|
2243
|
+
for (const entry of Object.values(lock?.packages ?? {})) {
|
|
2244
|
+
const resolution = entry[0];
|
|
2245
|
+
if (typeof resolution !== "string")
|
|
1730
2246
|
continue;
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
if (columns > room)
|
|
1735
|
-
break;
|
|
1736
|
-
kept += char;
|
|
1737
|
-
room -= columns;
|
|
1738
|
-
index += char.length;
|
|
2247
|
+
const locked = split(resolution);
|
|
2248
|
+
if (locked !== undefined)
|
|
2249
|
+
seen.set(`${locked.name}@${locked.version}`, locked);
|
|
1739
2250
|
}
|
|
1740
|
-
return
|
|
2251
|
+
return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name) || Bun.semver.order(a.version, b.version));
|
|
1741
2252
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
2253
|
+
var NOT_REGISTRY, VERSION_SEPARATOR2, NPM_ALIAS = "npm:";
|
|
2254
|
+
var init_lockfile = __esm(() => {
|
|
2255
|
+
NOT_REGISTRY = /^(?:workspace|link|file|github|git|http|https):|^[\w.-]+\/[\w.-]+$/;
|
|
2256
|
+
VERSION_SEPARATOR2 = /(?<=.)@/;
|
|
2257
|
+
});
|
|
2258
|
+
|
|
2259
|
+
// src/commands/audit.ts
|
|
2260
|
+
var exports_audit = {};
|
|
2261
|
+
__export(exports_audit, {
|
|
2262
|
+
audit: () => audit
|
|
2263
|
+
});
|
|
2264
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
2265
|
+
import process9 from "process";
|
|
2266
|
+
function hasFinding(audited) {
|
|
2267
|
+
return audited.blocked || audited.warnings.length > 0 || audited.advisories.length > 0;
|
|
2268
|
+
}
|
|
2269
|
+
async function auditPackages(packages, context) {
|
|
2270
|
+
const packuments = new Map;
|
|
2271
|
+
const lookup = async (name, registry) => {
|
|
2272
|
+
const key = `${registry.url}|${name}`;
|
|
2273
|
+
if (!packuments.has(key))
|
|
2274
|
+
packuments.set(key, fetchPackument(name, registry, context.fetcher));
|
|
2275
|
+
return packuments.get(key);
|
|
1758
2276
|
};
|
|
2277
|
+
const registries = new Map;
|
|
2278
|
+
for (const { name, version } of packages) {
|
|
2279
|
+
const registry = registryFor(name, context.configs);
|
|
2280
|
+
const group = registries.get(registry.url) ?? { registry, versions: {} };
|
|
2281
|
+
group.versions[name] = [...group.versions[name] ?? [], version];
|
|
2282
|
+
registries.set(registry.url, group);
|
|
2283
|
+
}
|
|
2284
|
+
const advisories = {};
|
|
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
|
+
}
|
|
2292
|
+
const audited = [];
|
|
2293
|
+
for (let start = 0;start < packages.length; start += BATCH) {
|
|
2294
|
+
audited.push(...await Promise.all(packages.slice(start, start + BATCH).map(async ({ name, version }) => {
|
|
2295
|
+
const registry = registryFor(name, context.configs);
|
|
2296
|
+
const found = filterAdvisories(advisoriesFor(advisories[name], version), context.level, context.ignore);
|
|
2297
|
+
const report = analyze({ spec: { name, request: version }, packument: await lookup(name, registry), registry: new URL(registry.url).host, now: context.now, minAgeDays: context.minAgeDays, licenses: context.settings.licenses });
|
|
2298
|
+
return { name, version, license: report.license, blocked: report.blocked || found.length > 0, warnings: report.warnings, advisories: found };
|
|
2299
|
+
})));
|
|
2300
|
+
}
|
|
2301
|
+
return { audited, unavailable };
|
|
1759
2302
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
2303
|
+
function renderAudit(findings, total) {
|
|
2304
|
+
const blocks = findings.map(({ name, version, warnings, advisories }) => [
|
|
2305
|
+
`${name}@${version}`,
|
|
2306
|
+
...warnings.map((warning) => ` warning: ${warning}`),
|
|
2307
|
+
...advisories.map((each) => ` ${each.ghsa ?? each.id} ${each.severity}: ${each.title}${each.fixed === undefined ? "" : ` (fixed in ${each.fixed})`}`)
|
|
2308
|
+
].join(`
|
|
2309
|
+
`));
|
|
2310
|
+
return [...blocks, `${findings.length} of ${total} packages with a finding, ${findings.filter((each) => each.blocked).length} blocking`].join(`
|
|
2311
|
+
|
|
2312
|
+
`);
|
|
2313
|
+
}
|
|
2314
|
+
async function audit(args) {
|
|
2315
|
+
const { values, positionals: [asked] } = parse(args, usage2, options2, { positionals: ["[dir]"] });
|
|
2316
|
+
const level = values["audit-level"] ?? "low";
|
|
2317
|
+
if (!isSeverity(level))
|
|
2318
|
+
fail(`--audit-level takes ${SEVERITIES.join(", ")}, not ${level}`);
|
|
2319
|
+
const dir = resolve2(asked ?? process9.cwd());
|
|
2320
|
+
const lock = Bun.file(join2(dir, "bun.lock"));
|
|
2321
|
+
if (!await lock.exists())
|
|
2322
|
+
fail(`no bun.lock in ${dir}`);
|
|
2323
|
+
const packages = parseBunLock(await lock.text());
|
|
2324
|
+
const context = {
|
|
2325
|
+
configs: await loadConfigs(dir),
|
|
2326
|
+
settings: await loadSettings(dir),
|
|
2327
|
+
minAgeDays: wholeNumber(values["min-age"], "min-age", DEFAULT_MIN_AGE_DAYS, 0),
|
|
2328
|
+
level,
|
|
2329
|
+
ignore: values.ignore ?? []
|
|
2330
|
+
};
|
|
2331
|
+
const { audited, unavailable } = await auditPackages(packages, context);
|
|
2332
|
+
const findings = audited.filter(hasFinding);
|
|
2333
|
+
if (values.json === true)
|
|
2334
|
+
printJson(findings);
|
|
2335
|
+
else
|
|
2336
|
+
console.log(renderAudit(findings, packages.length));
|
|
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;
|
|
2342
|
+
}
|
|
2343
|
+
var usage2 = `pkg audit [dir]
|
|
2344
|
+
|
|
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.
|
|
2346
|
+
Findings are grouped per package, each registry package looked up once per run.
|
|
2347
|
+
|
|
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;
|
|
2349
|
+
var init_audit = __esm(() => {
|
|
2350
|
+
init_src();
|
|
2351
|
+
init_advisories();
|
|
2352
|
+
init_analyze();
|
|
2353
|
+
init_config2();
|
|
2354
|
+
init_lockfile();
|
|
2355
|
+
init_registry();
|
|
2356
|
+
options2 = {
|
|
2357
|
+
json: jsonOption("[{name, version, license, blocked, warnings, advisories}] for the packages with a finding"),
|
|
2358
|
+
"min-age": { type: "string", description: `days a version must be old (${DEFAULT_MIN_AGE_DAYS})` },
|
|
2359
|
+
"audit-level": { type: "string", description: "lowest advisory severity listed and failing: low, moderate, high, critical (low)" },
|
|
2360
|
+
ignore: { type: "string", multiple: true, description: "an advisory skipped by GHSA or numeric id, repeatable" }
|
|
2361
|
+
};
|
|
1771
2362
|
});
|
|
1772
2363
|
|
|
1773
2364
|
// ../kit/src/shell.ts
|
|
1774
|
-
|
|
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;
|
|
1775
2369
|
var init_shell = __esm(() => {
|
|
1776
2370
|
SEPARATORS2 = new Set([`
|
|
1777
2371
|
`, ";", "|"]);
|
|
1778
2372
|
PLAIN_BREAKERS = new Set(["<", ">", "(", ")", "&", "|"]);
|
|
2373
|
+
GLOB_CHARS = new Set(["*", "?", "["]);
|
|
2374
|
+
COMMENT_PREFIXES = new Set([";", "&", "|", "(", ")"]);
|
|
1779
2375
|
DOUBLE_QUOTED_ESCAPES = new Set(["$", "`", '"', "\\"]);
|
|
1780
|
-
|
|
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
|
+
]);
|
|
1781
2400
|
});
|
|
1782
2401
|
|
|
1783
2402
|
// src/pin.ts
|
|
@@ -1820,7 +2439,7 @@ var init_pin = __esm(() => {
|
|
|
1820
2439
|
|
|
1821
2440
|
// src/consumers.ts
|
|
1822
2441
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
1823
|
-
import { dirname as dirname2, join as
|
|
2442
|
+
import { dirname as dirname2, join as join3, relative } from "path";
|
|
1824
2443
|
function packageFiles(dir, depth, found) {
|
|
1825
2444
|
let entries;
|
|
1826
2445
|
try {
|
|
@@ -1830,15 +2449,15 @@ function packageFiles(dir, depth, found) {
|
|
|
1830
2449
|
}
|
|
1831
2450
|
for (const entry of entries) {
|
|
1832
2451
|
if (entry.isFile() && entry.name === "package.json")
|
|
1833
|
-
found.push(
|
|
2452
|
+
found.push(join3(dir, entry.name));
|
|
1834
2453
|
else if (entry.isDirectory() && depth < MAX_DEPTH && !entry.name.startsWith(".") && !SKIPPED.has(entry.name))
|
|
1835
|
-
packageFiles(
|
|
2454
|
+
packageFiles(join3(dir, entry.name), depth + 1, found);
|
|
1836
2455
|
}
|
|
1837
2456
|
return found;
|
|
1838
2457
|
}
|
|
1839
2458
|
function managerFor(dir, root) {
|
|
1840
2459
|
for (let at = dir;; at = dirname2(at)) {
|
|
1841
|
-
const lockfile = LOCKFILES.find(([file]) => existsSync(
|
|
2460
|
+
const lockfile = LOCKFILES.find(([file]) => existsSync(join3(at, file)));
|
|
1842
2461
|
if (lockfile !== undefined)
|
|
1843
2462
|
return lockfile[1];
|
|
1844
2463
|
if (at === root || at === dirname2(at))
|
|
@@ -1872,7 +2491,7 @@ var init_consumers = __esm(() => {
|
|
|
1872
2491
|
init_pin();
|
|
1873
2492
|
KINDS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
|
|
1874
2493
|
SKIPPED = new Set(["node_modules", "dist", "build", "vendor", "target", "coverage"]);
|
|
1875
|
-
LOCAL_SPEC =
|
|
2494
|
+
LOCAL_SPEC = /[:/]/;
|
|
1876
2495
|
KIND_FLAGS = { dependencies: [], devDependencies: ["--dev"], optionalDependencies: ["--optional"], peerDependencies: ["--peer"] };
|
|
1877
2496
|
});
|
|
1878
2497
|
|
|
@@ -1882,25 +2501,46 @@ __export(exports_bump, {
|
|
|
1882
2501
|
ROOT_OPTION: () => ROOT_OPTION,
|
|
1883
2502
|
bump: () => bump
|
|
1884
2503
|
});
|
|
2504
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1885
2505
|
import { homedir as homedir2 } from "os";
|
|
1886
|
-
import { join as
|
|
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
|
+
}
|
|
1887
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`);
|
|
1888
2521
|
const registry = registryFor(name, settings.configs);
|
|
1889
2522
|
const host = new URL(registry.url).host;
|
|
1890
2523
|
const started = Date.now();
|
|
1891
2524
|
const region = liveRegion((spinner) => [`${spinner} waiting for ${host} to serve ${name}@${version} ${formatDuration(Date.now() - started)}`]);
|
|
2525
|
+
let lastError = "";
|
|
1892
2526
|
try {
|
|
1893
2527
|
for (;; ) {
|
|
1894
|
-
const
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
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
|
+
}
|
|
1902
2542
|
if (Date.now() - started >= settings.timeout)
|
|
1903
|
-
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}`}`);
|
|
1904
2544
|
region.redraw();
|
|
1905
2545
|
await Bun.sleep(settings.poll ?? POLL);
|
|
1906
2546
|
}
|
|
@@ -1908,65 +2548,142 @@ async function servedVersion(name, version, settings) {
|
|
|
1908
2548
|
region.stop();
|
|
1909
2549
|
}
|
|
1910
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
|
+
}
|
|
2565
|
+
function lockfileOf(dir, root) {
|
|
2566
|
+
for (let at = dir;; at = dirname3(at)) {
|
|
2567
|
+
const found = ["bun.lock", "bun.lockb"].find((file) => existsSync2(join4(at, file)));
|
|
2568
|
+
if (found !== undefined)
|
|
2569
|
+
return relative2(dir, join4(at, found));
|
|
2570
|
+
if (at === root || at === dirname3(at))
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
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
|
+
}
|
|
2579
|
+
async function checkAndCommit(name, version, where, root, run) {
|
|
2580
|
+
const checked = await run(["check"], where);
|
|
2581
|
+
if (checked.code !== 0)
|
|
2582
|
+
return `check exited ${checked.code}: ${checked.text.trim().split(`
|
|
2583
|
+
`).at(-1) ?? ""}`;
|
|
2584
|
+
const lockfile = lockfileOf(where, root);
|
|
2585
|
+
const committed = await run(["gc", `chore(deps): bump ${name} to ${version}`, "--", "package.json", ...lockfile === undefined ? [] : [lockfile]], where);
|
|
2586
|
+
return committed.code === 0 ? undefined : `gc exited ${committed.code}: ${committed.text.trim().split(`
|
|
2587
|
+
`).at(-1) ?? ""}`;
|
|
2588
|
+
}
|
|
1911
2589
|
async function bumpConsumers(name, version, settings) {
|
|
1912
2590
|
const found = findConsumers(name, settings.root);
|
|
1913
2591
|
const consumers = found.filter((each) => !isLocal(each));
|
|
1914
2592
|
const local = found.length - consumers.length;
|
|
1915
2593
|
const run = settings.run ?? (async (argv, cwd) => runCommand(argv, { cwd }));
|
|
2594
|
+
const committed = [];
|
|
2595
|
+
const failing = [];
|
|
2596
|
+
const skipped = [];
|
|
1916
2597
|
let isClean = true;
|
|
1917
2598
|
if (found.length === 0)
|
|
1918
2599
|
note(`nothing under ${settings.root} depends on ${name}`);
|
|
1919
2600
|
if (local > 0)
|
|
1920
|
-
note(`${local}
|
|
2601
|
+
note(`${local} non-registry dependenc${local === 1 ? "y" : "ies"} (workspace:, file:, git, URL, alias, catalog:)`, "left as they are");
|
|
1921
2602
|
for (const consumer of consumers) {
|
|
1922
2603
|
const argv = addCommand(consumer, name, version);
|
|
1923
|
-
const where =
|
|
1924
|
-
if (consumer.
|
|
2604
|
+
const where = join4(settings.root, consumer.dir);
|
|
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) {
|
|
2609
|
+
skipped.push(consumer.dir);
|
|
1925
2610
|
note(consumer.dir, `already on ${version}`);
|
|
1926
2611
|
} else if (consumer.manager !== "bun") {
|
|
2612
|
+
skipped.push(consumer.dir);
|
|
1927
2613
|
warn(consumer.dir, `${consumer.spec}, its lockfile pins ${consumer.manager}: put ${version} in by hand`);
|
|
1928
2614
|
} else if (settings.isDryRun === true) {
|
|
1929
|
-
note(consumer.dir, `would run ${argv.join(" ")}`);
|
|
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");
|
|
1930
2620
|
} else {
|
|
1931
2621
|
const ran = await run(argv, where);
|
|
1932
|
-
if (ran.code
|
|
1933
|
-
ok(consumer.dir, `${consumer.spec} -> ${version}`);
|
|
1934
|
-
} else {
|
|
2622
|
+
if (ran.code !== 0) {
|
|
1935
2623
|
isClean = false;
|
|
2624
|
+
failing.push(consumer.dir);
|
|
1936
2625
|
problem(consumer.dir, `${argv.join(" ")} exited ${ran.code}: ${ran.text.trim().split(`
|
|
1937
2626
|
`).at(-1) ?? ""}`);
|
|
2627
|
+
} else if (settings.isAll !== true) {
|
|
2628
|
+
ok(consumer.dir, `${consumer.spec} -> ${version}`);
|
|
2629
|
+
} else {
|
|
2630
|
+
const failure = await checkAndCommit(name, version, where, settings.root, run);
|
|
2631
|
+
if (failure === undefined) {
|
|
2632
|
+
committed.push(consumer.dir);
|
|
2633
|
+
ok(consumer.dir, `${consumer.spec} -> ${version}, checked and committed`);
|
|
2634
|
+
} else {
|
|
2635
|
+
isClean = false;
|
|
2636
|
+
failing.push(consumer.dir);
|
|
2637
|
+
problem(consumer.dir, `${consumer.spec} -> ${version}, left uncommitted: ${failure}`);
|
|
2638
|
+
}
|
|
1938
2639
|
}
|
|
1939
2640
|
}
|
|
1940
2641
|
}
|
|
2642
|
+
if (settings.isAll === true && settings.isDryRun !== true)
|
|
2643
|
+
note("summary", `${committed.length} bumped and committed, ${failing.length} bumped and failing${failing.length > 0 ? ` (${failing.join(", ")})` : ""}, ${skipped.length + local} skipped`);
|
|
1941
2644
|
return isClean;
|
|
1942
2645
|
}
|
|
1943
2646
|
async function bump(args) {
|
|
1944
|
-
const { values, positionals: [name, asked] } = parse(args,
|
|
2647
|
+
const { values, positionals: [name, asked] } = parse(args, usage3, options3, { positionals: ["<name>", "[version]"] });
|
|
1945
2648
|
const settings = {
|
|
1946
|
-
root: values.root ??
|
|
2649
|
+
root: values.root ?? join4(homedir2(), "Workspace"),
|
|
1947
2650
|
configs: await loadConfigs(),
|
|
1948
2651
|
timeout: duration(values.timeout, "timeout", 10 * 60000),
|
|
1949
|
-
isDryRun: values["dry-run"] === true
|
|
2652
|
+
isDryRun: values["dry-run"] === 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)
|
|
1950
2657
|
};
|
|
1951
2658
|
const version = await servedVersion(name, asked, settings);
|
|
2659
|
+
if (!await vet(name, version, settings))
|
|
2660
|
+
return 1;
|
|
1952
2661
|
return await bumpConsumers(name, version, settings) ? 0 : 1;
|
|
1953
2662
|
}
|
|
1954
|
-
var
|
|
2663
|
+
var usage3 = `pkg bump <name> [version]
|
|
1955
2664
|
|
|
1956
|
-
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.
|
|
1957
|
-
A
|
|
1958
|
-
|
|
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.
|
|
2669
|
+
|
|
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;
|
|
1959
2671
|
var init_bump = __esm(() => {
|
|
1960
2672
|
init_src();
|
|
1961
2673
|
init_progress();
|
|
1962
2674
|
init_config2();
|
|
1963
2675
|
init_consumers();
|
|
1964
2676
|
init_registry();
|
|
2677
|
+
init_semver();
|
|
2678
|
+
init_check();
|
|
1965
2679
|
ROOT_OPTION = { type: "string", description: "the folder holding the repositories (~/Workspace)" };
|
|
1966
|
-
|
|
2680
|
+
options3 = {
|
|
1967
2681
|
root: ROOT_OPTION,
|
|
1968
2682
|
timeout: { type: "string", short: "t", description: "how long to wait for the registry (10m): 30s, 5m, 1h" },
|
|
1969
|
-
"
|
|
2683
|
+
"all-consumers": { type: "boolean", description: "after each install run the folder's check and commit it through gc when it passes" },
|
|
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)" }
|
|
1970
2687
|
};
|
|
1971
2688
|
});
|
|
1972
2689
|
|
|
@@ -1976,46 +2693,53 @@ __export(exports_consumers, {
|
|
|
1976
2693
|
consumers: () => consumers
|
|
1977
2694
|
});
|
|
1978
2695
|
import { homedir as homedir3 } from "os";
|
|
1979
|
-
import { join as
|
|
2696
|
+
import { join as join5 } from "path";
|
|
1980
2697
|
function consumers(args) {
|
|
1981
|
-
const { values, positionals: [name] } = parse(args,
|
|
1982
|
-
const root = values.root ??
|
|
1983
|
-
const
|
|
2698
|
+
const { values, positionals: [name] } = parse(args, usage4, options4, { positionals: ["<name>"] });
|
|
2699
|
+
const root = values.root ?? join5(homedir3(), "Workspace");
|
|
2700
|
+
const { shown, footer } = capped(findConsumers(name, root), limitOf(values, CONSUMERS_LIMIT));
|
|
1984
2701
|
if (values.json === true)
|
|
1985
|
-
printJson(
|
|
1986
|
-
else if (
|
|
2702
|
+
printJson(shown);
|
|
2703
|
+
else if (shown.length === 0)
|
|
1987
2704
|
console.log(`nothing under ${root} depends on ${name}`);
|
|
1988
2705
|
else
|
|
1989
|
-
console.log(table([["FOLDER", "KIND", "VERSION", "MANAGER"], ...
|
|
2706
|
+
console.log([table([["FOLDER", "KIND", "VERSION", "MANAGER"], ...shown.map((each) => [each.dir, each.kind, each.spec, each.manager])], { header: true }), ...footer === undefined ? [] : [footer]].join(`
|
|
2707
|
+
`));
|
|
1990
2708
|
return 0;
|
|
1991
2709
|
}
|
|
1992
|
-
var
|
|
2710
|
+
var usage4 = `pkg consumers <name>
|
|
1993
2711
|
|
|
1994
|
-
Lists the folders under --root whose package.json depends on the package: the folder, the kind of dependency, the version it asks for and the manager its lockfile pins
|
|
2712
|
+
Lists the folders under --root whose package.json depends on the package: the folder, the kind of dependency, the version it asks for and the manager its lockfile pins, 30 at most with the count of the rest after them (--limit <n>, --full for all; --json is never cut unless --limit is given).
|
|
2713
|
+
|
|
2714
|
+
Exits 0 when it listed them, 2 on a usage error.`, CONSUMERS_LIMIT = 30, options4;
|
|
1995
2715
|
var init_consumers2 = __esm(() => {
|
|
1996
2716
|
init_src();
|
|
1997
2717
|
init_consumers();
|
|
1998
2718
|
init_bump();
|
|
1999
|
-
|
|
2719
|
+
options4 = {
|
|
2000
2720
|
root: ROOT_OPTION,
|
|
2721
|
+
...limitOptions(CONSUMERS_LIMIT),
|
|
2001
2722
|
json: jsonOption("[{dir, path, kind, spec, manager}]")
|
|
2002
2723
|
};
|
|
2003
2724
|
});
|
|
2004
2725
|
|
|
2005
2726
|
// src/cli.ts
|
|
2006
2727
|
init_src();
|
|
2007
|
-
import
|
|
2728
|
+
import process10 from "process";
|
|
2008
2729
|
// package.json
|
|
2009
|
-
var version = "0.
|
|
2730
|
+
var version = "0.5.0";
|
|
2010
2731
|
|
|
2011
2732
|
// src/cli.ts
|
|
2012
|
-
|
|
2733
|
+
process10.exitCode = await runCli({
|
|
2013
2734
|
name: "pkg",
|
|
2014
2735
|
version,
|
|
2015
2736
|
about: "checks npm packages before they are added, and moves a lib's consumers onto a new release",
|
|
2737
|
+
replaces: "bun add on trust, and a bun info and sleep loop then a bun add per consumer",
|
|
2738
|
+
examples: [["pkg zod", "the name, the newest version two weeks old, its licence and scripts"], ["pkg bump @kaynooo/cli-kit --dry-run", "the consumers a release would move"]],
|
|
2016
2739
|
defaultCommand: "check",
|
|
2017
2740
|
commands: {
|
|
2018
2741
|
check: { summary: "check packages before they are added (`pkg <name>` alone runs it)", run: async (args) => (await Promise.resolve().then(() => (init_check(), exports_check))).check(args) },
|
|
2742
|
+
audit: { summary: "check every package a bun.lock pins, with its known advisories", run: async (args) => (await Promise.resolve().then(() => (init_audit(), exports_audit))).audit(args) },
|
|
2019
2743
|
bump: { summary: "wait for a release on the registry, then bun add it in every consumer", run: async (args) => (await Promise.resolve().then(() => (init_bump(), exports_bump))).bump(args) },
|
|
2020
2744
|
consumers: { summary: "list the folders depending on a package, with the version each asks for", run: async (args) => (await Promise.resolve().then(() => (init_consumers2(), exports_consumers))).consumers(args) }
|
|
2021
2745
|
}
|