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