@kaynooo/cli-docker 0.5.0 → 0.6.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/README.md +2 -2
- package/dist/dc-cli.js +247 -59
- package/dist/dlog-cli.js +247 -59
- package/dist/dx-cli.js +904 -129
- package/dist/sf-cli.js +911 -137
- package/package.json +3 -3
- package/src/dc-cli.ts +1 -1
- package/src/dlog-cli.ts +1 -1
- package/src/dx-cli.ts +1 -1
- package/src/dx.ts +5 -8
- package/src/picker.ts +5 -5
- package/src/sf-cli.ts +1 -1
- package/src/sf.ts +13 -17
package/dist/dx-cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
1
|
+
#!/usr/bin/env -S bun --no-env-file
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/dx-cli.ts
|
|
5
|
-
import
|
|
5
|
+
import process16 from "process";
|
|
6
6
|
// ../kit/src/args.ts
|
|
7
|
-
import
|
|
7
|
+
import process7 from "process";
|
|
8
8
|
import { parseArgs } from "util";
|
|
9
9
|
|
|
10
10
|
// ../kit/src/style.ts
|
|
11
|
-
import
|
|
11
|
+
import process5 from "process";
|
|
12
12
|
|
|
13
13
|
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
|
|
14
14
|
function x(s) {
|
|
@@ -271,21 +271,209 @@ function le(i = {}) {
|
|
|
271
271
|
}
|
|
272
272
|
var t = le();
|
|
273
273
|
|
|
274
|
+
// ../kit/src/theme.ts
|
|
275
|
+
import { existsSync as existsSync2, readFileSync, statSync } from "fs";
|
|
276
|
+
import { homedir as homedir2 } from "os";
|
|
277
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
278
|
+
import process4 from "process";
|
|
279
|
+
|
|
280
|
+
// ../kit/src/config.ts
|
|
281
|
+
import { existsSync } from "fs";
|
|
282
|
+
import { dirname, join, resolve } from "path";
|
|
283
|
+
import process2 from "process";
|
|
284
|
+
function findUp(names, from = process2.cwd(), stop = () => false) {
|
|
285
|
+
const wanted = typeof names === "string" ? [names] : names;
|
|
286
|
+
for (let dir = resolve(from);; dir = dirname(dir)) {
|
|
287
|
+
const found = wanted.map((name) => join(dir, name)).find((path) => existsSync(path));
|
|
288
|
+
if (found !== undefined)
|
|
289
|
+
return found;
|
|
290
|
+
if (stop(dir) || dirname(dir) === dir)
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ../kit/src/user-config.ts
|
|
296
|
+
import { homedir } from "os";
|
|
297
|
+
import { join as join2 } from "path";
|
|
298
|
+
import process3 from "process";
|
|
299
|
+
function configBase(where = {}) {
|
|
300
|
+
const xdg = where.xdg ?? (where.home === undefined ? process3.env.XDG_CONFIG_HOME : undefined);
|
|
301
|
+
return xdg !== undefined && xdg !== "" ? xdg : join2(where.home ?? homedir(), ".config");
|
|
302
|
+
}
|
|
303
|
+
function userConfigPath(bin, where = {}) {
|
|
304
|
+
return join2(configBase(where), "kaynooo-cli", `${bin}.json`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ../kit/src/theme.ts
|
|
308
|
+
var DEFAULT = {
|
|
309
|
+
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" },
|
|
310
|
+
glyphs: { ok: "\u2713", warn: "\u25B2", problem: "\u2717", note: "\u203A", passed: "\u2713", failed: "\u2717", running: "\u25CF", queued: "\u25CB", skipped: "-", bar: "\u2501" },
|
|
311
|
+
accents: ["cyan", "magenta", "green", "blue", "yellow", "redBright"],
|
|
312
|
+
bins: {}
|
|
313
|
+
};
|
|
314
|
+
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" };
|
|
315
|
+
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" };
|
|
316
|
+
var ASCII = { ok: "+", warn: "!", problem: "x", note: ">", passed: "+", failed: "x", running: "*", queued: "o", skipped: "-", bar: "#" };
|
|
317
|
+
var PRESETS = {
|
|
318
|
+
default: {},
|
|
319
|
+
"high-contrast": { colors: BRIGHT, accents: ["cyanBright", "magentaBright", "greenBright", "blueBright", "yellowBright", "redBright"] },
|
|
320
|
+
mono: { colors: MONO, accents: ["bold"] },
|
|
321
|
+
ascii: { glyphs: ASCII }
|
|
322
|
+
};
|
|
323
|
+
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"]);
|
|
324
|
+
var HEX = /^#[0-9a-f]{6}$/i;
|
|
325
|
+
var CONTROL = /\p{Cc}/u;
|
|
326
|
+
var MAX_FILE_BYTES = 64 * 1024;
|
|
327
|
+
var SPACES = /\s+/;
|
|
328
|
+
function isColorSpec(spec) {
|
|
329
|
+
if (typeof spec === "number")
|
|
330
|
+
return Number.isInteger(spec) && spec >= 0 && spec <= 255;
|
|
331
|
+
return typeof spec === "string" && spec.trim() !== "" && spec.trim().split(SPACES).every((token) => STYLES.has(token) || HEX.test(token));
|
|
332
|
+
}
|
|
333
|
+
function tinted(colors, spec) {
|
|
334
|
+
if (typeof spec === "number")
|
|
335
|
+
return colors.ansi256(spec);
|
|
336
|
+
return spec.trim().split(SPACES).reduce((chain, token) => HEX.test(token) ? chain.hex(token) : chain[token] ?? chain, colors);
|
|
337
|
+
}
|
|
338
|
+
function isRecord(value) {
|
|
339
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
340
|
+
}
|
|
341
|
+
function readThemeFile(path, warn) {
|
|
342
|
+
try {
|
|
343
|
+
const stat = statSync(path, { throwIfNoEntry: false });
|
|
344
|
+
if (stat === undefined)
|
|
345
|
+
return;
|
|
346
|
+
if (!stat.isFile() || stat.size > MAX_FILE_BYTES) {
|
|
347
|
+
warn(`${path} is not a regular file under ${MAX_FILE_BYTES / 1024} KiB, theme ignored`);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
351
|
+
if (isRecord(parsed))
|
|
352
|
+
return parsed;
|
|
353
|
+
warn(`${path} is not a JSON object, theme ignored`);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
warn(`${path} does not parse (${error instanceof Error ? error.message : String(error)}), theme ignored`);
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
function applyPreset(theme, name, warn, where) {
|
|
360
|
+
if (name === undefined)
|
|
361
|
+
return;
|
|
362
|
+
if (typeof name !== "string" || !Object.hasOwn(PRESETS, name)) {
|
|
363
|
+
warn(`${where}: no preset ${String(name)}, one of ${Object.keys(PRESETS).join(", ")}`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const preset = PRESETS[name];
|
|
367
|
+
Object.assign(theme.colors, preset.colors);
|
|
368
|
+
Object.assign(theme.glyphs, preset.glyphs);
|
|
369
|
+
if (preset.accents !== undefined)
|
|
370
|
+
theme.accents = [...preset.accents];
|
|
371
|
+
return name;
|
|
372
|
+
}
|
|
373
|
+
function applyFile(theme, file, where, warn) {
|
|
374
|
+
if (isRecord(file.colors)) {
|
|
375
|
+
for (const [key, value] of Object.entries(file.colors)) {
|
|
376
|
+
if (!Object.hasOwn(DEFAULT.colors, key))
|
|
377
|
+
warn(`${where}: no colour ${key}, one of ${Object.keys(DEFAULT.colors).join(", ")}`);
|
|
378
|
+
else if (!isColorSpec(value))
|
|
379
|
+
warn(`${where}: colour ${key} is not an ANSI name, \`#rrggbb\` or a number up to 255`);
|
|
380
|
+
else
|
|
381
|
+
theme.colors[key] = value;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (isRecord(file.glyphs)) {
|
|
385
|
+
for (const [key, value] of Object.entries(file.glyphs)) {
|
|
386
|
+
if (!Object.hasOwn(DEFAULT.glyphs, key))
|
|
387
|
+
warn(`${where}: no glyph ${key}, one of ${Object.keys(DEFAULT.glyphs).join(", ")}`);
|
|
388
|
+
else if (typeof value !== "string" || CONTROL.test(value) || Bun.stringWidth(value) !== 1)
|
|
389
|
+
warn(`${where}: glyph ${key} must be one visible character wide, with no control character`);
|
|
390
|
+
else
|
|
391
|
+
theme.glyphs[key] = value;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (Array.isArray(file.accents) && file.accents.length > 0 && file.accents.every(isColorSpec))
|
|
395
|
+
theme.accents = [...file.accents];
|
|
396
|
+
else if (file.accents !== undefined)
|
|
397
|
+
warn(`${where}: accents must be a non-empty list of colours`);
|
|
398
|
+
if (isRecord(file.bins)) {
|
|
399
|
+
for (const [bin, value] of Object.entries(file.bins)) {
|
|
400
|
+
if (isColorSpec(value))
|
|
401
|
+
theme.bins[bin] = value;
|
|
402
|
+
else
|
|
403
|
+
warn(`${where}: bin ${bin} has no valid colour`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function endsWalk(home) {
|
|
408
|
+
return (dir) => dir === resolve2(home) || existsSync2(join3(dir, ".git"));
|
|
409
|
+
}
|
|
410
|
+
var warned = new Set;
|
|
411
|
+
function warnOnce(message) {
|
|
412
|
+
if (warned.has(message))
|
|
413
|
+
return;
|
|
414
|
+
warned.add(message);
|
|
415
|
+
console.error(`warning: theme ${message}`);
|
|
416
|
+
}
|
|
417
|
+
function isPath(chosen) {
|
|
418
|
+
return chosen.includes("/") || chosen.endsWith(".json");
|
|
419
|
+
}
|
|
420
|
+
function loadTheme({ env = process4.env, from = process4.cwd(), home = homedir2(), warn = warnOnce } = {}) {
|
|
421
|
+
const theme = { colors: { ...DEFAULT.colors }, glyphs: { ...DEFAULT.glyphs }, accents: [...DEFAULT.accents], bins: Object.create(null) };
|
|
422
|
+
const sources = [];
|
|
423
|
+
const chosen = env.KIT_THEME ?? "";
|
|
424
|
+
const wanted = chosen === "" ? [userConfigPath("theme", { home, xdg: env.XDG_CONFIG_HOME }), findUp(join3(".kaynooo", "theme.json"), from, endsWalk(home))] : [];
|
|
425
|
+
if (isPath(chosen))
|
|
426
|
+
wanted.push(resolve2(from, chosen));
|
|
427
|
+
const files = [];
|
|
428
|
+
for (const path of wanted) {
|
|
429
|
+
const file = path === undefined ? undefined : readThemeFile(path, warn);
|
|
430
|
+
if (path !== undefined && file !== undefined)
|
|
431
|
+
files.push({ path, file });
|
|
432
|
+
}
|
|
433
|
+
let preset = "default";
|
|
434
|
+
if (chosen !== "" && !isPath(chosen)) {
|
|
435
|
+
preset = applyPreset(theme, chosen, warn, "KIT_THEME") ?? preset;
|
|
436
|
+
} else {
|
|
437
|
+
const named = files.findLast(({ file }) => file.preset !== undefined);
|
|
438
|
+
if (named !== undefined)
|
|
439
|
+
preset = applyPreset(theme, named.file.preset, warn, named.path) ?? preset;
|
|
440
|
+
}
|
|
441
|
+
for (const { path, file } of files) {
|
|
442
|
+
applyFile(theme, file, path, warn);
|
|
443
|
+
sources.push(path);
|
|
444
|
+
}
|
|
445
|
+
return { theme, sources, preset };
|
|
446
|
+
}
|
|
447
|
+
var current;
|
|
448
|
+
var currentKey = "";
|
|
449
|
+
function activeTheme() {
|
|
450
|
+
const key = `${process4.cwd()}\x00${process4.env.KIT_THEME ?? ""}\x00${process4.env.XDG_CONFIG_HOME ?? ""}`;
|
|
451
|
+
if (current === undefined || key !== currentKey) {
|
|
452
|
+
current = loadTheme();
|
|
453
|
+
currentKey = key;
|
|
454
|
+
}
|
|
455
|
+
return current;
|
|
456
|
+
}
|
|
457
|
+
|
|
274
458
|
// ../kit/src/style.ts
|
|
275
|
-
function takesControlCodes(stream, env =
|
|
459
|
+
function takesControlCodes(stream, env = process5.env) {
|
|
276
460
|
return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
|
|
277
461
|
}
|
|
278
|
-
function paint(stream, env =
|
|
462
|
+
function paint(stream, env = process5.env) {
|
|
279
463
|
return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
|
|
280
464
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
465
|
+
function role(name, stream, env = process5.env) {
|
|
466
|
+
const colors = paint(stream, env);
|
|
467
|
+
return takesControlCodes(stream, env) ? tinted(colors, activeTheme().theme.colors[name]) : colors;
|
|
468
|
+
}
|
|
469
|
+
var STATUS_WORDS = {
|
|
470
|
+
passed: ["passed", "success", "succeeded", "ok"],
|
|
471
|
+
failed: ["failed", "failure", "error", "errored"],
|
|
472
|
+
running: ["running", "in_progress", "started"],
|
|
473
|
+
queued: ["queued", "waiting", "pending", "created", "blocked", "scheduled"],
|
|
474
|
+
skipped: ["skipped", "cancelled", "canceled", "manual"]
|
|
287
475
|
};
|
|
288
|
-
var STATUS_OF = new Map(Object.entries(
|
|
476
|
+
var STATUS_OF = new Map(Object.entries(STATUS_WORDS).flatMap(([status, words]) => words.map((word) => [word, status])));
|
|
289
477
|
|
|
290
478
|
// ../kit/src/format.ts
|
|
291
479
|
function upTo(text, separator) {
|
|
@@ -298,27 +486,27 @@ function printJson(value) {
|
|
|
298
486
|
}
|
|
299
487
|
|
|
300
488
|
// ../kit/src/help.ts
|
|
301
|
-
import
|
|
489
|
+
import process6 from "process";
|
|
302
490
|
var OPTIONAL = /\[[^\]]+\]/g;
|
|
303
491
|
var USAGE_PREFIX = /^usage:\s*/i;
|
|
304
492
|
var FULL_STOP = /\.$/;
|
|
305
|
-
function usageLine(usage, colors = paint(
|
|
493
|
+
function usageLine(usage, colors = paint(process6.stdout)) {
|
|
306
494
|
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
307
495
|
}
|
|
308
|
-
function helpText(usage, options, colors = paint(
|
|
496
|
+
function helpText(usage, options, colors = paint(process6.stdout), { examples = [], replaces, exits = [] } = {}) {
|
|
309
497
|
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
|
|
310
498
|
`);
|
|
311
499
|
}
|
|
312
|
-
function exitRows(exits, colors = paint(
|
|
500
|
+
function exitRows(exits, colors = paint(process6.stdout)) {
|
|
313
501
|
if (exits.length === 0)
|
|
314
502
|
return [];
|
|
315
503
|
const width = Math.max(...exits.map(([code]) => String(code).length));
|
|
316
504
|
return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
|
|
317
505
|
}
|
|
318
|
-
function replacesRows(replaces, colors = paint(
|
|
506
|
+
function replacesRows(replaces, colors = paint(process6.stdout)) {
|
|
319
507
|
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
320
508
|
}
|
|
321
|
-
function wantsHelpJson(env =
|
|
509
|
+
function wantsHelpJson(env = process6.env) {
|
|
322
510
|
return env.KIT_HELP === "json";
|
|
323
511
|
}
|
|
324
512
|
function description(usage, { examples = [], replaces, exits } = {}, options) {
|
|
@@ -337,12 +525,12 @@ function description(usage, { examples = [], replaces, exits } = {}, options) {
|
|
|
337
525
|
...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
|
|
338
526
|
};
|
|
339
527
|
}
|
|
340
|
-
function exampleRows(examples, colors = paint(
|
|
528
|
+
function exampleRows(examples, colors = paint(process6.stdout)) {
|
|
341
529
|
if (examples.length === 0)
|
|
342
530
|
return [];
|
|
343
531
|
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
344
532
|
}
|
|
345
|
-
function optionRows(options, colors = paint(
|
|
533
|
+
function optionRows(options, colors = paint(process6.stdout)) {
|
|
346
534
|
const rows = Object.entries(options).map(([name, option]) => {
|
|
347
535
|
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
348
536
|
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
@@ -436,7 +624,7 @@ var OUTPUT_OPTIONS = {
|
|
|
436
624
|
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
437
625
|
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
438
626
|
};
|
|
439
|
-
function applyOutput(color, plain, env =
|
|
627
|
+
function applyOutput(color, plain, env = process7.env) {
|
|
440
628
|
const when = plain === true ? "never" : color ?? "auto";
|
|
441
629
|
if (when === "auto")
|
|
442
630
|
return;
|
|
@@ -611,15 +799,15 @@ var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x
|
|
|
611
799
|
var RESET_STYLE = `${ESC}[0m`;
|
|
612
800
|
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
613
801
|
// ../kit/src/cli.ts
|
|
614
|
-
import { readFileSync, realpathSync } from "fs";
|
|
615
|
-
import { dirname as
|
|
616
|
-
import
|
|
802
|
+
import { readFileSync as readFileSync2, realpathSync } from "fs";
|
|
803
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
804
|
+
import process9 from "process";
|
|
617
805
|
|
|
618
806
|
// ../kit/src/usage.ts
|
|
619
807
|
import { appendFileSync, mkdirSync } from "fs";
|
|
620
|
-
import { homedir } from "os";
|
|
621
|
-
import { dirname, isAbsolute, join } from "path";
|
|
622
|
-
import
|
|
808
|
+
import { homedir as homedir3 } from "os";
|
|
809
|
+
import { dirname as dirname2, isAbsolute, join as join4 } from "path";
|
|
810
|
+
import process8 from "process";
|
|
623
811
|
|
|
624
812
|
// ../kit/src/logs.ts
|
|
625
813
|
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -633,14 +821,14 @@ var usageError;
|
|
|
633
821
|
function noteUsageError(message) {
|
|
634
822
|
usageError = message;
|
|
635
823
|
}
|
|
636
|
-
function stateHome(env =
|
|
824
|
+
function stateHome(env = process8.env) {
|
|
637
825
|
const dir = env.XDG_STATE_HOME;
|
|
638
|
-
return dir !== undefined && isAbsolute(dir) ? dir :
|
|
826
|
+
return dir !== undefined && isAbsolute(dir) ? dir : join4(homedir3(), ".local", "state");
|
|
639
827
|
}
|
|
640
|
-
function usagePath(env =
|
|
641
|
-
return
|
|
828
|
+
function usagePath(env = process8.env) {
|
|
829
|
+
return join4(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
642
830
|
}
|
|
643
|
-
function usageRecorded(env =
|
|
831
|
+
function usageRecorded(env = process8.env) {
|
|
644
832
|
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
645
833
|
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
646
834
|
}
|
|
@@ -654,12 +842,12 @@ function usageEntry(bin, command, argv, code, now = new Date) {
|
|
|
654
842
|
usageError = undefined;
|
|
655
843
|
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
656
844
|
}
|
|
657
|
-
function recordUsage(entry, env =
|
|
845
|
+
function recordUsage(entry, env = process8.env) {
|
|
658
846
|
if (!usageRecorded(env))
|
|
659
847
|
return;
|
|
660
848
|
try {
|
|
661
849
|
const path = usagePath(env);
|
|
662
|
-
mkdirSync(
|
|
850
|
+
mkdirSync(dirname2(path), { recursive: true, mode: 448 });
|
|
663
851
|
appendFileSync(path, `${JSON.stringify(entry)}
|
|
664
852
|
`, { mode: 384 });
|
|
665
853
|
} catch {}
|
|
@@ -672,8 +860,8 @@ var LEVEL_OPTIONS = {
|
|
|
672
860
|
};
|
|
673
861
|
var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
|
|
674
862
|
function printError(prefix, message, hint, cause) {
|
|
675
|
-
const colors = paint(
|
|
676
|
-
const styled = takesControlCodes(
|
|
863
|
+
const colors = paint(process9.stderr);
|
|
864
|
+
const styled = takesControlCodes(process9.stderr);
|
|
677
865
|
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
678
866
|
const under = (label, text, tint) => text === undefined ? "" : `
|
|
679
867
|
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
@@ -692,7 +880,7 @@ function reportThrown(prefix, error, redact = (text) => text, json = false) {
|
|
|
692
880
|
const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
|
|
693
881
|
const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
|
|
694
882
|
const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
|
|
695
|
-
if (error instanceof
|
|
883
|
+
if (error instanceof Exit && error.code === 2)
|
|
696
884
|
noteUsageError(error.message);
|
|
697
885
|
const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
|
|
698
886
|
report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
|
|
@@ -756,17 +944,17 @@ async function printCandidates(found) {
|
|
|
756
944
|
return 0;
|
|
757
945
|
}
|
|
758
946
|
function packageVersion(entry = Bun.main) {
|
|
759
|
-
for (let dir =
|
|
947
|
+
for (let dir = dirname3(realpathSync(entry));; dir = dirname3(dir)) {
|
|
760
948
|
try {
|
|
761
|
-
const { version } = JSON.parse(
|
|
949
|
+
const { version } = JSON.parse(readFileSync2(join5(dir, "package.json"), "utf8"));
|
|
762
950
|
if (typeof version === "string")
|
|
763
951
|
return version;
|
|
764
952
|
} catch {}
|
|
765
|
-
if (
|
|
953
|
+
if (dirname3(dir) === dir)
|
|
766
954
|
return "0.0.0";
|
|
767
955
|
}
|
|
768
956
|
}
|
|
769
|
-
async function runMain({ name, version, run, redact, usage }, argv =
|
|
957
|
+
async function runMain({ name, version, run, redact, usage }, argv = process9.argv.slice(2)) {
|
|
770
958
|
if (argv[0] === COMPLETE)
|
|
771
959
|
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
772
960
|
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
@@ -809,11 +997,11 @@ var {Glob } = globalThis.Bun;
|
|
|
809
997
|
var SESSION_FILES = new Glob("*.json");
|
|
810
998
|
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
811
999
|
// src/dx.ts
|
|
812
|
-
import
|
|
1000
|
+
import process15 from "process";
|
|
813
1001
|
|
|
814
1002
|
// ../env/src/files.ts
|
|
815
1003
|
import { chmod, chown, readdir, realpath, rename, rm, stat, writeFile } from "fs/promises";
|
|
816
|
-
import { basename, dirname as
|
|
1004
|
+
import { basename, dirname as dirname4, join as join6 } from "path";
|
|
817
1005
|
|
|
818
1006
|
// ../env/src/dotenv.ts
|
|
819
1007
|
var ASSIGNMENT = /^\s*(?:export\s+)?([A-Z_][\w.-]*)\s*=(.*)$/i;
|
|
@@ -1084,7 +1272,7 @@ async function envFilesBelow(folder, depth = 0) {
|
|
|
1084
1272
|
return found;
|
|
1085
1273
|
}
|
|
1086
1274
|
for (const entry of children) {
|
|
1087
|
-
const path =
|
|
1275
|
+
const path = join6(folder, entry.name);
|
|
1088
1276
|
if (entry.isDirectory() && depth < KNOWN_DEPTH && !SKIPPED.has(entry.name))
|
|
1089
1277
|
found.push(...await envFilesBelow(path, depth + 1));
|
|
1090
1278
|
else if (ENV_FILE.test(entry.name) && await isFileEntry(entry, path))
|
|
@@ -1161,11 +1349,630 @@ class Redactor {
|
|
|
1161
1349
|
}
|
|
1162
1350
|
}
|
|
1163
1351
|
|
|
1164
|
-
// ../kit/src/
|
|
1165
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync as
|
|
1166
|
-
import { homedir as
|
|
1167
|
-
import { dirname as
|
|
1168
|
-
import
|
|
1352
|
+
// ../kit/src/picker.ts
|
|
1353
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1354
|
+
import { homedir as homedir4 } from "os";
|
|
1355
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1356
|
+
import process13 from "process";
|
|
1357
|
+
|
|
1358
|
+
// ../kit/src/picker/score.ts
|
|
1359
|
+
var MATCH = 16;
|
|
1360
|
+
var CONSECUTIVE = 12;
|
|
1361
|
+
var BOUNDARY = 10;
|
|
1362
|
+
var PREFIX = 8;
|
|
1363
|
+
var GAP = 1;
|
|
1364
|
+
var BOUNDARY_AFTER = new Set([" ", "/", "-", "_", ".", ":", "\\", "(", "[", ",", "\t"]);
|
|
1365
|
+
var UPPER = /\p{Lu}/u;
|
|
1366
|
+
var LOWER = /\p{Ll}/u;
|
|
1367
|
+
var NONE = Number.NEGATIVE_INFINITY;
|
|
1368
|
+
function startsWord(text, index) {
|
|
1369
|
+
if (index === 0)
|
|
1370
|
+
return true;
|
|
1371
|
+
const before = text[index - 1];
|
|
1372
|
+
return BOUNDARY_AFTER.has(before) || LOWER.test(before) && UPPER.test(text[index]);
|
|
1373
|
+
}
|
|
1374
|
+
function fuzzy(query, text) {
|
|
1375
|
+
if (query === "")
|
|
1376
|
+
return { score: 0, positions: [] };
|
|
1377
|
+
const exact = query !== query.toLowerCase();
|
|
1378
|
+
const needle = exact ? query : query.toLowerCase();
|
|
1379
|
+
const haystack = exact ? text : text.toLowerCase();
|
|
1380
|
+
if (haystack.length !== text.length || needle.length > haystack.length)
|
|
1381
|
+
return;
|
|
1382
|
+
const width = haystack.length;
|
|
1383
|
+
const best = Array.from({ length: needle.length }, () => new Array(width).fill(NONE));
|
|
1384
|
+
const from = Array.from({ length: needle.length }, () => new Array(width).fill(-1));
|
|
1385
|
+
for (let j = 0;j < needle.length; j++) {
|
|
1386
|
+
let run = NONE;
|
|
1387
|
+
let runAt = -1;
|
|
1388
|
+
for (let i = j;i < width; i++) {
|
|
1389
|
+
if (j > 0 && i - 2 >= j - 1) {
|
|
1390
|
+
const candidate = best[j - 1][i - 2] + (i - 2) * GAP;
|
|
1391
|
+
if (candidate > run) {
|
|
1392
|
+
run = candidate;
|
|
1393
|
+
runAt = i - 2;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
if (haystack[i] !== needle[j])
|
|
1397
|
+
continue;
|
|
1398
|
+
const here = MATCH + (startsWord(text, i) ? BOUNDARY : 0) + (i === 0 ? PREFIX : 0);
|
|
1399
|
+
if (j === 0) {
|
|
1400
|
+
best[0][i] = here - i * GAP;
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
const joined = best[j - 1][i - 1] + CONSECUTIVE;
|
|
1404
|
+
const skipped = run - (i - 1) * GAP;
|
|
1405
|
+
if (joined > NONE && joined >= skipped) {
|
|
1406
|
+
best[j][i] = joined + here;
|
|
1407
|
+
from[j][i] = i - 1;
|
|
1408
|
+
} else if (skipped > NONE) {
|
|
1409
|
+
best[j][i] = skipped + here;
|
|
1410
|
+
from[j][i] = runAt;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
const last = best[needle.length - 1];
|
|
1415
|
+
let end = -1;
|
|
1416
|
+
for (let i = 0;i < width; i++) {
|
|
1417
|
+
if (last[i] > NONE && (end === -1 || last[i] > last[end]))
|
|
1418
|
+
end = i;
|
|
1419
|
+
}
|
|
1420
|
+
if (end === -1)
|
|
1421
|
+
return;
|
|
1422
|
+
const positions = [];
|
|
1423
|
+
for (let j = needle.length - 1, i = end;j >= 0; j--) {
|
|
1424
|
+
positions.unshift(i);
|
|
1425
|
+
i = from[j][i];
|
|
1426
|
+
}
|
|
1427
|
+
return { score: last[end], positions };
|
|
1428
|
+
}
|
|
1429
|
+
function rank(query, lines) {
|
|
1430
|
+
if (query === "")
|
|
1431
|
+
return lines.map((_, index) => ({ index, positions: [] }));
|
|
1432
|
+
const found = [];
|
|
1433
|
+
for (const [index, line] of lines.entries()) {
|
|
1434
|
+
const match = fuzzy(query, line);
|
|
1435
|
+
if (match !== undefined)
|
|
1436
|
+
found.push({ index, positions: match.positions, score: match.score });
|
|
1437
|
+
}
|
|
1438
|
+
return found.sort((a, b) => b.score - a.score || lines[a.index].length - lines[b.index].length || a.index - b.index);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// ../kit/src/picker/state.ts
|
|
1442
|
+
var ANSI2 = /\x1B(?:\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\][^\x07\x1B]*(?:\x07|\x1B\\)|[\x40-\x5A\x5C-\x5F])/g;
|
|
1443
|
+
var CONTROL2 = /[\x00-\x1F\x7F-\x9F]/g;
|
|
1444
|
+
var IS_CONTROL = /[\x00-\x1F\x7F-\x9F]/;
|
|
1445
|
+
var SGR = /^\x1B\[[0-9;]*m$/;
|
|
1446
|
+
var RESET = /^\x1B\[0?m$/;
|
|
1447
|
+
var COLUMN_GAP = 2;
|
|
1448
|
+
var PASTE_START = "\x1B[200~";
|
|
1449
|
+
var PASTE_END = "\x1B[201~";
|
|
1450
|
+
var LAST_WORD = /\S*\s*$/;
|
|
1451
|
+
var KEY_SEQUENCES = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|\x1B[\s\S]|[\s\S]/gu;
|
|
1452
|
+
var KEYS = {
|
|
1453
|
+
"\x1B[A": "up",
|
|
1454
|
+
"\x1BOA": "up",
|
|
1455
|
+
"\x10": "up",
|
|
1456
|
+
"\x1B[B": "down",
|
|
1457
|
+
"\x1BOB": "down",
|
|
1458
|
+
"\x0E": "down",
|
|
1459
|
+
"\r": "enter",
|
|
1460
|
+
"\n": "enter",
|
|
1461
|
+
"\x03": "cancel",
|
|
1462
|
+
"\x1B": "cancel",
|
|
1463
|
+
"\x7F": "erase",
|
|
1464
|
+
"\b": "erase",
|
|
1465
|
+
"\x15": "clear",
|
|
1466
|
+
"\x17": "word",
|
|
1467
|
+
"\t": "tab"
|
|
1468
|
+
};
|
|
1469
|
+
var CTRL_KEY = /^ctrl-[abd-gkln-z]$/;
|
|
1470
|
+
function repeat(value, count) {
|
|
1471
|
+
return Array.from({ length: count }).fill(value);
|
|
1472
|
+
}
|
|
1473
|
+
function styled(line) {
|
|
1474
|
+
let text = "";
|
|
1475
|
+
const styles = [];
|
|
1476
|
+
let active = "";
|
|
1477
|
+
let colored = false;
|
|
1478
|
+
let last = 0;
|
|
1479
|
+
function add(chunk) {
|
|
1480
|
+
const flat = chunk.replace(CONTROL2, " ");
|
|
1481
|
+
text += flat;
|
|
1482
|
+
colored ||= active !== "" && flat !== "";
|
|
1483
|
+
styles.push(...repeat(active, flat.length));
|
|
1484
|
+
}
|
|
1485
|
+
for (const found of line.matchAll(ANSI2)) {
|
|
1486
|
+
add(line.slice(last, found.index));
|
|
1487
|
+
last = found.index + found[0].length;
|
|
1488
|
+
if (SGR.test(found[0]))
|
|
1489
|
+
active = RESET.test(found[0]) ? "" : active + found[0];
|
|
1490
|
+
}
|
|
1491
|
+
add(line.slice(last));
|
|
1492
|
+
return { text, styles: colored ? styles : undefined };
|
|
1493
|
+
}
|
|
1494
|
+
function lay(rows, searched) {
|
|
1495
|
+
const parsed = rows.map((row) => row.cells.map(styled));
|
|
1496
|
+
const widths = (parsed[0] ?? []).map((_, column) => Math.max(...parsed.map((cells) => Bun.stringWidth(cells[column]?.text ?? ""))));
|
|
1497
|
+
return parsed.map((cells) => {
|
|
1498
|
+
let text = "";
|
|
1499
|
+
const styles = [];
|
|
1500
|
+
let colored = false;
|
|
1501
|
+
let searchEnd = 0;
|
|
1502
|
+
for (const [column, cell] of cells.entries()) {
|
|
1503
|
+
const last = column === cells.length - 1;
|
|
1504
|
+
const shown = cells.length > 1 && last ? cell.text.trimEnd() : cell.text;
|
|
1505
|
+
text += shown;
|
|
1506
|
+
colored ||= cell.styles !== undefined;
|
|
1507
|
+
styles.push(...cell.styles?.slice(0, shown.length) ?? repeat("", shown.length));
|
|
1508
|
+
if (column < searched)
|
|
1509
|
+
searchEnd = cells.length > 1 ? text.trimEnd().length : text.length;
|
|
1510
|
+
if (!last) {
|
|
1511
|
+
const pad = widths[column] - Bun.stringWidth(cell.text) + COLUMN_GAP;
|
|
1512
|
+
text += " ".repeat(pad);
|
|
1513
|
+
styles.push(...repeat("", pad));
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return { text, styles: colored ? styles : undefined, searchable: text.slice(0, searchEnd) };
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
function parseKeys(input, paste = { active: false }, ctrls = []) {
|
|
1520
|
+
const codes = new Map(ctrls.map((name) => [String.fromCharCode(name.codePointAt(name.length - 1) - 96), name]));
|
|
1521
|
+
return (input.match(KEY_SEQUENCES) ?? []).flatMap((sequence) => {
|
|
1522
|
+
const named = codes.get(sequence);
|
|
1523
|
+
const key = named === undefined ? KEYS[sequence] : { ctrl: named };
|
|
1524
|
+
if (sequence === PASTE_START || sequence === PASTE_END) {
|
|
1525
|
+
paste.active = sequence === PASTE_START;
|
|
1526
|
+
return [];
|
|
1527
|
+
}
|
|
1528
|
+
if (paste.active && (sequence === "\r" || sequence === `
|
|
1529
|
+
`))
|
|
1530
|
+
return [];
|
|
1531
|
+
if (key !== undefined)
|
|
1532
|
+
return [key];
|
|
1533
|
+
return IS_CONTROL.test(sequence) || sequence.startsWith("\x1B") ? [] : [{ text: sequence }];
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
function createState(rows, { multi = false, prompt = "> ", query = "", header = "", expect = [], searched = Number.POSITIVE_INFINITY } = {}) {
|
|
1537
|
+
const state = { rows: [], items: [], styles: [], searchable: [], searched, header, expect, key: "", status: "", query, matches: [], cursor: 0, marked: new Set, multi, prompt, done: undefined };
|
|
1538
|
+
return withRows(state, rows);
|
|
1539
|
+
}
|
|
1540
|
+
function withRows(state, rows, header = state.header) {
|
|
1541
|
+
const cells = rows.map((row) => typeof row === "string" ? { hidden: [], cells: [row] } : row);
|
|
1542
|
+
const laid = lay(cells, state.searched);
|
|
1543
|
+
const searchable = laid.map((row) => row.searchable);
|
|
1544
|
+
const matches = rank(state.query, searchable);
|
|
1545
|
+
return { ...state, rows: cells, items: laid.map((row) => row.text), styles: laid.map((row) => row.styles), searchable, header, matches, cursor: Math.max(0, Math.min(matches.length - 1, state.cursor)), marked: new Set };
|
|
1546
|
+
}
|
|
1547
|
+
function withQuery(state, query) {
|
|
1548
|
+
return { ...state, query, matches: rank(query, state.searchable), cursor: 0 };
|
|
1549
|
+
}
|
|
1550
|
+
function move(state, by) {
|
|
1551
|
+
return { ...state, cursor: Math.max(0, Math.min(state.matches.length - 1, state.cursor + by)) };
|
|
1552
|
+
}
|
|
1553
|
+
function highlighted(state) {
|
|
1554
|
+
return state.matches[state.cursor]?.index;
|
|
1555
|
+
}
|
|
1556
|
+
function press(state, key) {
|
|
1557
|
+
if (state.done !== undefined)
|
|
1558
|
+
return state;
|
|
1559
|
+
if (typeof key === "object") {
|
|
1560
|
+
if ("text" in key)
|
|
1561
|
+
return withQuery(state, state.query + key.text);
|
|
1562
|
+
return state.expect.includes(key.ctrl) ? { ...state, done: "accept", key: key.ctrl } : state;
|
|
1563
|
+
}
|
|
1564
|
+
switch (key) {
|
|
1565
|
+
case "up":
|
|
1566
|
+
return move(state, -1);
|
|
1567
|
+
case "down":
|
|
1568
|
+
return move(state, 1);
|
|
1569
|
+
case "cancel":
|
|
1570
|
+
return { ...state, done: "cancel" };
|
|
1571
|
+
case "erase":
|
|
1572
|
+
return withQuery(state, [...state.query].slice(0, -1).join(""));
|
|
1573
|
+
case "clear":
|
|
1574
|
+
return withQuery(state, "");
|
|
1575
|
+
case "word":
|
|
1576
|
+
return withQuery(state, state.query.replace(LAST_WORD, ""));
|
|
1577
|
+
case "enter":
|
|
1578
|
+
return state.matches.length === 0 && state.marked.size === 0 ? state : { ...state, done: "accept" };
|
|
1579
|
+
case "tab": {
|
|
1580
|
+
const at = highlighted(state);
|
|
1581
|
+
if (!state.multi || at === undefined)
|
|
1582
|
+
return state;
|
|
1583
|
+
const marked = new Set(state.marked);
|
|
1584
|
+
if (!marked.delete(at))
|
|
1585
|
+
marked.add(at);
|
|
1586
|
+
return move({ ...state, marked }, 1);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
function chosen(state) {
|
|
1591
|
+
if (state.marked.size > 0)
|
|
1592
|
+
return state.rows.filter((_, index) => state.marked.has(index));
|
|
1593
|
+
const at = highlighted(state);
|
|
1594
|
+
return at === undefined ? [] : [state.rows[at]];
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// ../kit/src/picker/terminal.ts
|
|
1598
|
+
import { openSync } from "fs";
|
|
1599
|
+
import process12 from "process";
|
|
1600
|
+
import tty from "tty";
|
|
1601
|
+
|
|
1602
|
+
// ../kit/src/picker/preview.ts
|
|
1603
|
+
import process11 from "process";
|
|
1604
|
+
|
|
1605
|
+
// ../kit/src/shell.ts
|
|
1606
|
+
var SEPARATORS = new Set([`
|
|
1607
|
+
`, ";", "|"]);
|
|
1608
|
+
var PLAIN_BREAKERS = new Set(["<", ">", "(", ")", "&", "|"]);
|
|
1609
|
+
var GLOB_CHARS = new Set(["*", "?", "["]);
|
|
1610
|
+
var COMMENT_PREFIXES = new Set([";", "&", "|", "(", ")"]);
|
|
1611
|
+
var SAFE_WORD = /^[\w@%+=:,./-]+$/;
|
|
1612
|
+
var DOUBLE_QUOTED_ESCAPES = new Set(["$", "`", '"', "\\"]);
|
|
1613
|
+
var KEYWORDS = new Set(["then", "do", "else", "if", "elif", "while", "until", "!", "{", "}"]);
|
|
1614
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ash", "ksh", "fish", "csh", "tcsh", "nu", "xonsh", "elvish", "pwsh", "powershell"]);
|
|
1615
|
+
var SCANNED_PROGRAMS = new Set(["su", "runuser", "flock"]);
|
|
1616
|
+
var COMMAND_STRING_PROGRAMS = new Set([...SHELLS, ...SCANNED_PROGRAMS]);
|
|
1617
|
+
var POWERSHELL = new Set(["pwsh", "powershell"]);
|
|
1618
|
+
var SHELL_VALUE_OPTIONS = new Set(["-o", "-O", "--rcfile", "--init-file"]);
|
|
1619
|
+
var FISH_VALUE_OPTIONS = new Set(["-C", "--init-command"]);
|
|
1620
|
+
var PRINTERS = new Set(["echo", "printf"]);
|
|
1621
|
+
var FIND_EXEC = new Set(["-exec", "-execdir", "-ok", "-okdir"]);
|
|
1622
|
+
function wrapper(short = "", long = [], operands = 0, runsString = false) {
|
|
1623
|
+
return { short, long, operands, runsString };
|
|
1624
|
+
}
|
|
1625
|
+
var WRAPPERS = new Map([
|
|
1626
|
+
["command", wrapper()],
|
|
1627
|
+
["builtin", wrapper()],
|
|
1628
|
+
["nohup", wrapper()],
|
|
1629
|
+
["setsid", wrapper()],
|
|
1630
|
+
["busybox", wrapper()],
|
|
1631
|
+
["exec", wrapper("a")],
|
|
1632
|
+
["time", wrapper("fo", ["--format", "--output"])],
|
|
1633
|
+
["env", wrapper("uCS", ["--unset", "--chdir", "--split-string", "--argv0"])],
|
|
1634
|
+
["sudo", wrapper("ughpCTrtUDR", ["--user", "--group", "--host", "--prompt", "--close-from", "--command-timeout", "--role", "--type", "--other-user", "--chdir", "--chroot"])],
|
|
1635
|
+
["doas", wrapper("uC")],
|
|
1636
|
+
["timeout", wrapper("sk", ["--signal", "--kill-after"], 1)],
|
|
1637
|
+
["nice", wrapper("n", ["--adjustment"])],
|
|
1638
|
+
["ionice", wrapper("cnpPu", ["--class", "--classdata", "--pid", "--pgid", "--uid"])],
|
|
1639
|
+
["stdbuf", wrapper("ioe", ["--input", "--output", "--error"])],
|
|
1640
|
+
["chroot", wrapper("", ["--userspec", "--groups"], 1)],
|
|
1641
|
+
["watch", wrapper("n", ["--interval"], 0, true)],
|
|
1642
|
+
["flock", wrapper("wE", ["--timeout", "--conflict-exit-code"], 1)],
|
|
1643
|
+
["parallel", wrapper("jnNSaIPlLdTsC", ["--jobs", "--max-args", "--max-replace-args", "--sshlogin", "--arg-file", "--delimiter", "--colsep"], 0, true)],
|
|
1644
|
+
["unbuffer", wrapper()],
|
|
1645
|
+
["systemd-run", wrapper("puEMH", ["--property", "--unit", "--description", "--slice", "--setenv", "--machine", "--host", "--uid", "--gid", "--working-directory", "--nice", "--service-type"])],
|
|
1646
|
+
["xargs", wrapper("IndsEaPL", ["--max-args", "--max-procs", "--max-chars", "--delimiter", "--arg-file", "--eof", "--max-lines", "--process-slot-var"])]
|
|
1647
|
+
]);
|
|
1648
|
+
function shellQuote(word) {
|
|
1649
|
+
return SAFE_WORD.test(word) && !word.startsWith("=") ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// ../kit/src/picker/preview.ts
|
|
1653
|
+
var PREVIEW_BYTES = 64 * 1024;
|
|
1654
|
+
var PREVIEW_LINES = 200;
|
|
1655
|
+
var PREVIEW_TIMEOUT_MS = 3000;
|
|
1656
|
+
var PLACEHOLDER2 = /\{([1-9]\d*)?\}/g;
|
|
1657
|
+
function previewCommand(template, line, hidden = []) {
|
|
1658
|
+
return template.replace(PLACEHOLDER2, (_, field) => shellQuote(field === undefined ? line : hidden[Number(field) - 1] ?? ""));
|
|
1659
|
+
}
|
|
1660
|
+
function previewLines(output) {
|
|
1661
|
+
const lines = output.split(`
|
|
1662
|
+
`);
|
|
1663
|
+
if (lines.at(-1) === "")
|
|
1664
|
+
lines.pop();
|
|
1665
|
+
return lines.slice(0, PREVIEW_LINES);
|
|
1666
|
+
}
|
|
1667
|
+
async function runPreview(template, line, signal, hidden = []) {
|
|
1668
|
+
const proc = Bun.spawn(["sh", "-c", `exec 2>&1
|
|
1669
|
+
${previewCommand(template, line, hidden)}`], { stdin: "ignore", stdout: "pipe", stderr: "ignore", detached: true });
|
|
1670
|
+
const stop = () => {
|
|
1671
|
+
try {
|
|
1672
|
+
process11.kill(-proc.pid, "SIGKILL");
|
|
1673
|
+
} catch {
|
|
1674
|
+
proc.kill();
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
const timer = setTimeout(stop, PREVIEW_TIMEOUT_MS);
|
|
1678
|
+
const chunks = [];
|
|
1679
|
+
let size = 0;
|
|
1680
|
+
signal.addEventListener("abort", stop);
|
|
1681
|
+
try {
|
|
1682
|
+
for await (const chunk of proc.stdout) {
|
|
1683
|
+
chunks.push(chunk);
|
|
1684
|
+
size += chunk.length;
|
|
1685
|
+
if (size >= PREVIEW_BYTES) {
|
|
1686
|
+
stop();
|
|
1687
|
+
break;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
} finally {
|
|
1691
|
+
clearTimeout(timer);
|
|
1692
|
+
signal.removeEventListener("abort", stop);
|
|
1693
|
+
}
|
|
1694
|
+
return signal.aborted ? [] : previewLines(Buffer.concat(chunks).subarray(0, PREVIEW_BYTES).toString());
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// ../kit/src/picker/view.ts
|
|
1698
|
+
var MIN_HEIGHT = 4;
|
|
1699
|
+
var PREVIEW_MIN_COLUMNS = 80;
|
|
1700
|
+
var PREVIEW_MIN_ROWS = 10;
|
|
1701
|
+
var PREVIEW_SHARE = 0.4;
|
|
1702
|
+
var RESET2 = "\x1B[0m";
|
|
1703
|
+
var SEPARATOR = " \u2502 ";
|
|
1704
|
+
var HEIGHT = /^(\d+)(%?)$/;
|
|
1705
|
+
function parseHeight(text) {
|
|
1706
|
+
const found = HEIGHT.exec(text);
|
|
1707
|
+
if (found === null || Number(found[1]) < 1)
|
|
1708
|
+
return;
|
|
1709
|
+
return found[2] === "%" ? { percent: Math.min(100, Number(found[1])) } : { lines: Number(found[1]) };
|
|
1710
|
+
}
|
|
1711
|
+
function layoutFor(size, height, previewing, position = "right") {
|
|
1712
|
+
const wanted = "lines" in height ? height.lines : Math.round(size.rows * height.percent / 100);
|
|
1713
|
+
const rows = Math.min(size.rows, Math.max(MIN_HEIGHT, wanted));
|
|
1714
|
+
if (position === "down") {
|
|
1715
|
+
const previewHeight = previewing && rows >= PREVIEW_MIN_ROWS ? Math.round(rows * PREVIEW_SHARE) : 0;
|
|
1716
|
+
return { height: rows, listHeight: rows - previewHeight, listWidth: size.columns, previewWidth: 0, previewHeight };
|
|
1717
|
+
}
|
|
1718
|
+
const split = previewing && size.columns >= PREVIEW_MIN_COLUMNS;
|
|
1719
|
+
const listWidth = split ? Math.floor(size.columns / 2) : size.columns;
|
|
1720
|
+
return { height: rows, listHeight: rows, listWidth, previewWidth: split ? size.columns - listWidth - SEPARATOR.length : 0, previewHeight: 0 };
|
|
1721
|
+
}
|
|
1722
|
+
function fit(text, width, plain, mark = plain, marked = new Set, styles) {
|
|
1723
|
+
const room = Bun.stringWidth(text) > width ? width - 1 : width;
|
|
1724
|
+
let out = "";
|
|
1725
|
+
let used = 0;
|
|
1726
|
+
let at = 0;
|
|
1727
|
+
for (const char of text) {
|
|
1728
|
+
const size = Bun.stringWidth(char);
|
|
1729
|
+
if (used + size > room)
|
|
1730
|
+
break;
|
|
1731
|
+
const style = marked.has(at) ? "" : styles?.[at] ?? "";
|
|
1732
|
+
out += style === "" ? (marked.has(at) ? mark : plain)(char) : `${style}${char}${RESET2}`;
|
|
1733
|
+
used += size;
|
|
1734
|
+
at += char.length;
|
|
1735
|
+
}
|
|
1736
|
+
if (room < width) {
|
|
1737
|
+
out += plain("\u2026");
|
|
1738
|
+
used += 1;
|
|
1739
|
+
}
|
|
1740
|
+
return { text: out, width: used };
|
|
1741
|
+
}
|
|
1742
|
+
var identity = (text) => text;
|
|
1743
|
+
function fitStyled(line, width, plain = identity) {
|
|
1744
|
+
const { text, styles } = styled(line);
|
|
1745
|
+
return fit(text, width, plain, plain, new Set, styles);
|
|
1746
|
+
}
|
|
1747
|
+
function caretColumn(state, layout) {
|
|
1748
|
+
return Math.min(layout.listWidth, Bun.stringWidth(state.prompt) + Bun.stringWidth(state.query) + 1);
|
|
1749
|
+
}
|
|
1750
|
+
function listLines(state, layout, theme) {
|
|
1751
|
+
const header = state.header === "" ? [] : state.header.split(`
|
|
1752
|
+
`).map((line) => fitStyled(line, layout.listWidth, theme.label));
|
|
1753
|
+
const rows = Math.max(1, layout.listHeight - 2 - header.length);
|
|
1754
|
+
const prefixWidth = state.multi ? 4 : 2;
|
|
1755
|
+
const top = Math.max(0, Math.min(state.cursor - Math.floor(rows / 2), state.matches.length - rows));
|
|
1756
|
+
const marks = (state.marked.size > 0 ? ` (${state.marked.size} marked)` : "") + (state.status === "" ? "" : ` ${state.status}`);
|
|
1757
|
+
const promptWidth = Bun.stringWidth(state.prompt);
|
|
1758
|
+
const query = fit(state.query, Math.max(1, layout.listWidth - promptWidth), identity);
|
|
1759
|
+
const count = fit(` ${state.matches.length}/${state.items.length}${marks}`, layout.listWidth, theme.label);
|
|
1760
|
+
const lines = [{ text: theme.accent(state.prompt) + query.text, width: promptWidth + query.width }, count, ...header];
|
|
1761
|
+
if (state.matches.length === 0)
|
|
1762
|
+
lines.push(fit(" no match", layout.listWidth, theme.label));
|
|
1763
|
+
for (const [offset, match] of state.matches.slice(top, top + rows).entries()) {
|
|
1764
|
+
const current = top + offset === state.cursor;
|
|
1765
|
+
const pointer = current ? theme.accent("\u276F") : " ";
|
|
1766
|
+
const mark = state.multi ? `${state.marked.has(match.index) ? theme.id("\u25CF") : " "} ` : "";
|
|
1767
|
+
const text = fit(state.items[match.index], layout.listWidth - prefixWidth, current ? theme.value : identity, theme.accent, new Set(match.positions), state.styles[match.index]);
|
|
1768
|
+
lines.push({ text: `${pointer} ${mark}${text.text}`, width: prefixWidth + text.width });
|
|
1769
|
+
}
|
|
1770
|
+
return lines;
|
|
1771
|
+
}
|
|
1772
|
+
function render(state, layout, theme, preview = []) {
|
|
1773
|
+
const left = listLines(state, layout, theme);
|
|
1774
|
+
return Array.from({ length: layout.height }, (_, row) => {
|
|
1775
|
+
if (row >= layout.listHeight)
|
|
1776
|
+
return row === layout.listHeight ? theme.label("\u2500".repeat(layout.listWidth)) : fitStyled(preview[row - layout.listHeight - 1] ?? "", layout.listWidth).text;
|
|
1777
|
+
const list = left[row] ?? { text: "", width: 0 };
|
|
1778
|
+
if (layout.previewWidth === 0)
|
|
1779
|
+
return list.text;
|
|
1780
|
+
return `${list.text}${" ".repeat(Math.max(0, layout.listWidth - list.width))}${theme.label(SEPARATOR)}${fitStyled(preview[row] ?? "", layout.previewWidth).text}`;
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// ../kit/src/picker/terminal.ts
|
|
1785
|
+
var PREVIEW_DELAY_MS = 120;
|
|
1786
|
+
var OPEN = { ctrl: "open" };
|
|
1787
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
1788
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
1789
|
+
var PASTE_ON = "\x1B[?2004h";
|
|
1790
|
+
var PASTE_OFF = "\x1B[?2004l";
|
|
1791
|
+
var CLEAR_BELOW = "\r\x1B[J";
|
|
1792
|
+
var SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
1793
|
+
function up(lines) {
|
|
1794
|
+
return lines > 0 ? `\x1B[${lines}A` : "";
|
|
1795
|
+
}
|
|
1796
|
+
function openTerminal() {
|
|
1797
|
+
let input;
|
|
1798
|
+
try {
|
|
1799
|
+
input = new tty.ReadStream(openSync("/dev/tty", "r"));
|
|
1800
|
+
} catch {
|
|
1801
|
+
return fail("no terminal to read keys from (/dev/tty)");
|
|
1802
|
+
}
|
|
1803
|
+
const out = process12.stderr;
|
|
1804
|
+
let resize;
|
|
1805
|
+
return {
|
|
1806
|
+
get columns() {
|
|
1807
|
+
return out.columns || 80;
|
|
1808
|
+
},
|
|
1809
|
+
get rows() {
|
|
1810
|
+
return out.rows || 24;
|
|
1811
|
+
},
|
|
1812
|
+
theme: { accent: role("accent", out), label: role("label", out), value: role("value", out), id: role("id", out) },
|
|
1813
|
+
write: (text) => void out.write(text),
|
|
1814
|
+
listen(onChunk, onResize) {
|
|
1815
|
+
resize = onResize;
|
|
1816
|
+
input.setRawMode(true);
|
|
1817
|
+
out.on("resize", onResize);
|
|
1818
|
+
input.on("data", (chunk) => onChunk(chunk.toString()));
|
|
1819
|
+
},
|
|
1820
|
+
close() {
|
|
1821
|
+
if (resize !== undefined)
|
|
1822
|
+
out.off("resize", resize);
|
|
1823
|
+
try {
|
|
1824
|
+
input.setRawMode(false);
|
|
1825
|
+
} finally {
|
|
1826
|
+
input.destroy();
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
async function select(rows, settings, terminal = openTerminal()) {
|
|
1832
|
+
const { theme } = terminal;
|
|
1833
|
+
let state = createState(rows, settings);
|
|
1834
|
+
let preview = [];
|
|
1835
|
+
let reserved = 0;
|
|
1836
|
+
let previewTimer;
|
|
1837
|
+
let previewing = new AbortController;
|
|
1838
|
+
let shownLine;
|
|
1839
|
+
let finish;
|
|
1840
|
+
let restored = false;
|
|
1841
|
+
function draw(clear = false) {
|
|
1842
|
+
if (restored)
|
|
1843
|
+
return;
|
|
1844
|
+
const layout = layoutFor({ columns: terminal.columns, rows: terminal.rows }, settings.height, settings.preview !== undefined, settings.previewPosition);
|
|
1845
|
+
let frame = HIDE_CURSOR;
|
|
1846
|
+
if (reserved !== layout.height || clear) {
|
|
1847
|
+
frame += CLEAR_BELOW + `
|
|
1848
|
+
`.repeat(layout.height - 1) + up(layout.height - 1);
|
|
1849
|
+
reserved = layout.height;
|
|
1850
|
+
}
|
|
1851
|
+
frame += `\r${render(state, layout, theme, preview).map((line) => `\x1B[2K${line}`).join(`
|
|
1852
|
+
`)}${up(layout.height - 1)}\r\x1B[${caretColumn(state, layout)}G${SHOW_CURSOR}`;
|
|
1853
|
+
terminal.write(frame);
|
|
1854
|
+
}
|
|
1855
|
+
function refreshPreview() {
|
|
1856
|
+
const at = highlighted(state);
|
|
1857
|
+
if (settings.preview === undefined || at === shownLine)
|
|
1858
|
+
return;
|
|
1859
|
+
shownLine = at;
|
|
1860
|
+
clearTimeout(previewTimer);
|
|
1861
|
+
previewing.abort();
|
|
1862
|
+
previewing = new AbortController;
|
|
1863
|
+
if (at === undefined) {
|
|
1864
|
+
preview = [];
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
const { signal } = previewing;
|
|
1868
|
+
const template = settings.preview;
|
|
1869
|
+
const row = state.rows[at];
|
|
1870
|
+
async function show() {
|
|
1871
|
+
const shown = typeof template === "string" ? await runPreview(template, row.cells.join(" "), signal, row.hidden) : await previewOf(template, row.hidden, signal);
|
|
1872
|
+
if (!signal.aborted) {
|
|
1873
|
+
preview = shown;
|
|
1874
|
+
draw();
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
previewTimer = setTimeout(() => void show(), PREVIEW_DELAY_MS);
|
|
1878
|
+
}
|
|
1879
|
+
function restore() {
|
|
1880
|
+
if (restored)
|
|
1881
|
+
return;
|
|
1882
|
+
restored = true;
|
|
1883
|
+
clearTimeout(previewTimer);
|
|
1884
|
+
previewing.abort();
|
|
1885
|
+
terminal.write(`${CLEAR_BELOW}${PASTE_OFF}${SHOW_CURSOR}`);
|
|
1886
|
+
terminal.close();
|
|
1887
|
+
}
|
|
1888
|
+
const stop = () => {
|
|
1889
|
+
state = { ...state, done: "cancel" };
|
|
1890
|
+
finish?.();
|
|
1891
|
+
};
|
|
1892
|
+
const paste = { active: false };
|
|
1893
|
+
const { reload } = settings;
|
|
1894
|
+
const pending = [];
|
|
1895
|
+
let busy = false;
|
|
1896
|
+
let draining = false;
|
|
1897
|
+
async function reloadRows() {
|
|
1898
|
+
if (reload === undefined)
|
|
1899
|
+
return;
|
|
1900
|
+
busy = true;
|
|
1901
|
+
state = { ...state, status: "reloading" };
|
|
1902
|
+
draw();
|
|
1903
|
+
try {
|
|
1904
|
+
const next = await reload.run();
|
|
1905
|
+
state = { ...withRows(state, next.rows, next.header), status: "" };
|
|
1906
|
+
shownLine = undefined;
|
|
1907
|
+
} catch (error) {
|
|
1908
|
+
state = { ...state, status: `reload failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
1909
|
+
} finally {
|
|
1910
|
+
busy = false;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
async function drain() {
|
|
1914
|
+
if (draining)
|
|
1915
|
+
return;
|
|
1916
|
+
draining = true;
|
|
1917
|
+
try {
|
|
1918
|
+
for (let key = pending.shift();key !== undefined && state.done === undefined; key = pending.shift()) {
|
|
1919
|
+
if (key === OPEN || typeof key === "object" && "ctrl" in key && key.ctrl === reload?.key)
|
|
1920
|
+
await reloadRows();
|
|
1921
|
+
else
|
|
1922
|
+
state = press(state, key);
|
|
1923
|
+
}
|
|
1924
|
+
} finally {
|
|
1925
|
+
draining = false;
|
|
1926
|
+
}
|
|
1927
|
+
if (state.done !== undefined) {
|
|
1928
|
+
finish?.();
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
refreshPreview();
|
|
1932
|
+
draw();
|
|
1933
|
+
}
|
|
1934
|
+
function onChunk(chunk) {
|
|
1935
|
+
const keys = parseKeys(chunk, paste, [...settings.expect ?? [], ...reload?.key === undefined ? [] : [reload.key]]);
|
|
1936
|
+
if (busy && keys.includes("cancel")) {
|
|
1937
|
+
stop();
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
pending.push(...keys);
|
|
1941
|
+
drain();
|
|
1942
|
+
}
|
|
1943
|
+
const ended = new Promise((resolve) => {
|
|
1944
|
+
finish = resolve;
|
|
1945
|
+
});
|
|
1946
|
+
process12.once("exit", restore);
|
|
1947
|
+
for (const signal of SIGNALS)
|
|
1948
|
+
process12.once(signal, stop);
|
|
1949
|
+
try {
|
|
1950
|
+
terminal.write(PASTE_ON);
|
|
1951
|
+
terminal.listen(onChunk, () => draw(true));
|
|
1952
|
+
refreshPreview();
|
|
1953
|
+
draw(true);
|
|
1954
|
+
if (reload?.onOpen === true) {
|
|
1955
|
+
pending.push(OPEN);
|
|
1956
|
+
drain();
|
|
1957
|
+
}
|
|
1958
|
+
await ended;
|
|
1959
|
+
} finally {
|
|
1960
|
+
for (const signal of SIGNALS)
|
|
1961
|
+
process12.off(signal, stop);
|
|
1962
|
+
process12.off("exit", restore);
|
|
1963
|
+
restore();
|
|
1964
|
+
}
|
|
1965
|
+
return state.done === "accept" ? { rows: chosen(state), key: state.key, query: state.query } : undefined;
|
|
1966
|
+
}
|
|
1967
|
+
async function previewOf(preview, hidden, signal) {
|
|
1968
|
+
try {
|
|
1969
|
+
return previewLines(await preview(hidden, signal));
|
|
1970
|
+
} catch (error) {
|
|
1971
|
+
return [error instanceof Error ? error.message : String(error)];
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// ../kit/src/picker.ts
|
|
1169
1976
|
var REMEMBERED = 20;
|
|
1170
1977
|
function table(rows) {
|
|
1171
1978
|
const widths = rows[0]?.cells.map((_, index) => Math.max(...rows.map((row) => Bun.stringWidth(row.cells[index])))) ?? [];
|
|
@@ -1174,70 +1981,40 @@ function table(rows) {
|
|
|
1174
1981
|
...row.cells.map((cell, index) => index === row.cells.length - 1 ? cell.trimEnd() : `${cell}${" ".repeat(widths[index] + 1 - Bun.stringWidth(cell))}`)
|
|
1175
1982
|
].join("\t"));
|
|
1176
1983
|
}
|
|
1177
|
-
function
|
|
1178
|
-
const
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
options.height ?? "100%",
|
|
1189
|
-
"--delimiter",
|
|
1190
|
-
"\t",
|
|
1191
|
-
"--with-nth",
|
|
1192
|
-
`${hidden + 1}..`,
|
|
1193
|
-
"--nth",
|
|
1194
|
-
`1..${options.searched}`,
|
|
1195
|
-
"--tabstop",
|
|
1196
|
-
"1",
|
|
1197
|
-
"--tiebreak",
|
|
1198
|
-
"chunk,index",
|
|
1199
|
-
...options.preview === undefined ? [] : ["--preview", options.preview, "--preview-window", "down,40%"],
|
|
1200
|
-
...(options.bind ?? []).flatMap((bind) => ["--bind", bind]),
|
|
1201
|
-
...options.query === true ? ["--print-query"] : []
|
|
1202
|
-
], {
|
|
1203
|
-
stdin: new TextEncoder().encode(table(rows).join(`
|
|
1204
|
-
`)),
|
|
1205
|
-
stdout: "pipe",
|
|
1206
|
-
stderr: "inherit"
|
|
1207
|
-
});
|
|
1208
|
-
return picked(proc.stdout.toString(), hidden, options);
|
|
1209
|
-
}
|
|
1210
|
-
function picked(output, hidden, options) {
|
|
1211
|
-
const lines = output.split(`
|
|
1212
|
-
`);
|
|
1213
|
-
const query = options.query === true ? lines.shift() : "";
|
|
1214
|
-
const [key, line] = (options.expect ?? []).length > 0 ? [lines[0], lines[1]] : ["", lines[0]];
|
|
1215
|
-
if (line !== undefined && line !== "")
|
|
1216
|
-
return { key, hidden: line.split("\t").slice(0, hidden), query };
|
|
1217
|
-
return options.query === true && key !== "" ? { key, hidden: [], query } : undefined;
|
|
1984
|
+
async function pick(rows, options) {
|
|
1985
|
+
const height = parseHeight(options.height ?? "100%");
|
|
1986
|
+
const keys = [...options.expect ?? [], ...options.reload?.key === undefined ? [] : [options.reload.key]];
|
|
1987
|
+
if (height === undefined)
|
|
1988
|
+
throw new Error(`pick: the height is lines or a percentage, not ${options.height}`);
|
|
1989
|
+
for (const key of keys) {
|
|
1990
|
+
if (!CTRL_KEY.test(key))
|
|
1991
|
+
throw new Error(`pick: ${key} is not a key it can report, one of ctrl-a to ctrl-z but ctrl-c, h, i, j and m`);
|
|
1992
|
+
}
|
|
1993
|
+
const found = await select(rows, { multi: false, prompt: options.prompt, query: "", height, preview: options.preview, previewPosition: "down", header: options.header, expect: options.expect, searched: options.searched, reload: options.reload });
|
|
1994
|
+
return found === undefined ? undefined : { key: found.key, hidden: found.rows[0]?.hidden ?? [], query: found.query };
|
|
1218
1995
|
}
|
|
1219
1996
|
function heightFor(count) {
|
|
1220
1997
|
return String(Math.max(15, Math.ceil((count + 4) / 0.6)));
|
|
1221
1998
|
}
|
|
1222
1999
|
function historyFile(name) {
|
|
1223
|
-
return
|
|
2000
|
+
return join7(process13.env.XDG_CACHE_HOME ?? join7(homedir4(), ".cache"), name, "history.json");
|
|
1224
2001
|
}
|
|
1225
2002
|
function history(name) {
|
|
1226
2003
|
try {
|
|
1227
2004
|
const file = historyFile(name);
|
|
1228
|
-
return
|
|
2005
|
+
return existsSync3(file) ? JSON.parse(readFileSync3(file, "utf-8")) : {};
|
|
1229
2006
|
} catch {
|
|
1230
2007
|
return {};
|
|
1231
2008
|
}
|
|
1232
2009
|
}
|
|
1233
2010
|
function runs(name) {
|
|
1234
|
-
return history(name)[
|
|
2011
|
+
return history(name)[process13.cwd()] ?? [];
|
|
1235
2012
|
}
|
|
1236
2013
|
function record(name, run) {
|
|
1237
2014
|
const all = history(name);
|
|
1238
2015
|
const file = historyFile(name);
|
|
1239
|
-
all[
|
|
1240
|
-
mkdirSync2(
|
|
2016
|
+
all[process13.cwd()] = [run, ...(all[process13.cwd()] ?? []).filter((earlier) => earlier.id !== run.id)].slice(0, REMEMBERED);
|
|
2017
|
+
mkdirSync2(dirname5(file), { recursive: true });
|
|
1241
2018
|
writeFileSync(file, JSON.stringify(all));
|
|
1242
2019
|
}
|
|
1243
2020
|
function byRecency(items, previous, idOf) {
|
|
@@ -1260,11 +2037,11 @@ function list(rows) {
|
|
|
1260
2037
|
}
|
|
1261
2038
|
|
|
1262
2039
|
// src/picker.ts
|
|
1263
|
-
import
|
|
2040
|
+
import process14 from "process";
|
|
1264
2041
|
|
|
1265
2042
|
// src/stack.ts
|
|
1266
|
-
import { existsSync as
|
|
1267
|
-
import { dirname as
|
|
2043
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4 } from "fs";
|
|
2044
|
+
import { dirname as dirname6, join as join8, relative, resolve as resolve3 } from "path";
|
|
1268
2045
|
var LABEL_FORMAT = ["project", "project.config_files", "project.environment_file", "project.working_dir"].map((label) => `{{.Label "com.docker.compose.${label}"}}`).join("\t");
|
|
1269
2046
|
var COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m;
|
|
1270
2047
|
var COMPOSE_FILES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
|
|
@@ -1276,29 +2053,29 @@ function from(dir, command) {
|
|
|
1276
2053
|
function children(dir = ".") {
|
|
1277
2054
|
return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED2.has(child.name)).map((child) => child.name).sort();
|
|
1278
2055
|
}
|
|
1279
|
-
function
|
|
2056
|
+
function up2(dir) {
|
|
1280
2057
|
const dirs = [];
|
|
1281
|
-
for (let current = dir;; current =
|
|
2058
|
+
for (let current = dir;; current = join8(current, "..")) {
|
|
1282
2059
|
dirs.push(current);
|
|
1283
|
-
const absolute =
|
|
1284
|
-
if (
|
|
2060
|
+
const absolute = resolve3(current);
|
|
2061
|
+
if (existsSync4(join8(current, ".git")) || dirname6(absolute) === absolute)
|
|
1285
2062
|
return dirs;
|
|
1286
2063
|
}
|
|
1287
2064
|
}
|
|
1288
2065
|
function stackAt(dir) {
|
|
1289
|
-
return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd:
|
|
2066
|
+
return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd: resolve3(dir), env: process.env } };
|
|
1290
2067
|
}
|
|
1291
2068
|
function holdsStack(dir) {
|
|
1292
|
-
if (COMPOSE_FILES.some((name) =>
|
|
2069
|
+
if (COMPOSE_FILES.some((name) => existsSync4(join8(dir, name))))
|
|
1293
2070
|
return true;
|
|
1294
2071
|
try {
|
|
1295
|
-
return COMPOSE_FILE_VAR.test(
|
|
2072
|
+
return COMPOSE_FILE_VAR.test(readFileSync4(join8(dir, ".env"), "utf-8"));
|
|
1296
2073
|
} catch {
|
|
1297
2074
|
return false;
|
|
1298
2075
|
}
|
|
1299
2076
|
}
|
|
1300
2077
|
function nearestStack(dir) {
|
|
1301
|
-
const found =
|
|
2078
|
+
const found = up2(dir).find(holdsStack);
|
|
1302
2079
|
return found === undefined ? undefined : stackAt(found);
|
|
1303
2080
|
}
|
|
1304
2081
|
function stacks() {
|
|
@@ -1321,7 +2098,7 @@ async function labels(project) {
|
|
|
1321
2098
|
const proc = Bun.spawn(["docker", "ps", "--all", ...filter, "--format", LABEL_FORMAT], { stdout: "pipe", stderr: "ignore" });
|
|
1322
2099
|
if (await proc.exited !== 0)
|
|
1323
2100
|
return [];
|
|
1324
|
-
return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) =>
|
|
2101
|
+
return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) => existsSync4(file)));
|
|
1325
2102
|
}
|
|
1326
2103
|
function labelledStack(dir, { project, files, envFiles, workdir }) {
|
|
1327
2104
|
const options = (paths) => ["-p", project, ...files.flatMap((file) => ["-f", paths(file)]), ...envFiles.flatMap((file) => ["--env-file", paths(file)])];
|
|
@@ -1336,7 +2113,7 @@ function stackOf(recorded) {
|
|
|
1336
2113
|
return labelledStack(relative(".", recorded.workdir) || ".", recorded);
|
|
1337
2114
|
}
|
|
1338
2115
|
async function recordedNearby() {
|
|
1339
|
-
const dirs = new Set([...
|
|
2116
|
+
const dirs = new Set([...up2("."), ...children()].map((dir) => resolve3(dir)));
|
|
1340
2117
|
return (await labels()).filter((stack) => dirs.has(stack.workdir));
|
|
1341
2118
|
}
|
|
1342
2119
|
async function attempt(stack, args) {
|
|
@@ -1427,10 +2204,10 @@ async function logsOf(stack, name) {
|
|
|
1427
2204
|
}
|
|
1428
2205
|
|
|
1429
2206
|
// src/picker.ts
|
|
1430
|
-
var
|
|
2207
|
+
var KEYS2 = { "ctrl-l": "logs", "ctrl-r": "restart", "ctrl-x": "stop", "ctrl-b": "build" };
|
|
1431
2208
|
var KEY_LABELS = { "ctrl-l": "Logs", "ctrl-r": "Restart", "ctrl-x": "Stop", "ctrl-b": "Rebuild" };
|
|
1432
2209
|
function row(service) {
|
|
1433
|
-
const colors = paint(
|
|
2210
|
+
const colors = paint(process14.stderr);
|
|
1434
2211
|
const dir = service.stack.dir;
|
|
1435
2212
|
const project = service.stack.project;
|
|
1436
2213
|
const name = project === undefined ? service.name : `${colors.dim(`${project}:`)}${service.name}`;
|
|
@@ -1439,14 +2216,14 @@ function row(service) {
|
|
|
1439
2216
|
cells: [dir === "." ? name : `${colors.dim(`${dir}/`)}${name}`, colors[stateColor(service.state)](service.state), colors.cyan(service.ports), colors.dim(service.image)]
|
|
1440
2217
|
};
|
|
1441
2218
|
}
|
|
1442
|
-
function pickService(services) {
|
|
1443
|
-
const picked =
|
|
2219
|
+
async function pickService(services) {
|
|
2220
|
+
const picked = await pick(services.map(row), {
|
|
1444
2221
|
prompt: "dx> ",
|
|
1445
2222
|
header: "enter shell or up, ctrl-l logs, ctrl-r restart, ctrl-x stop, ctrl-b rebuild",
|
|
1446
|
-
expect: Object.keys(
|
|
2223
|
+
expect: Object.keys(KEYS2),
|
|
1447
2224
|
searched: 1,
|
|
1448
2225
|
height: heightFor(services.length),
|
|
1449
|
-
preview: `${
|
|
2226
|
+
preview: `${process14.execPath} ${process14.argv[1]} --show {1} {2} {4}`
|
|
1450
2227
|
});
|
|
1451
2228
|
const service = services.find((candidate) => key(candidate) === picked?.hidden[2]);
|
|
1452
2229
|
return picked === undefined || service === undefined ? undefined : { service, key: picked.key };
|
|
@@ -1456,7 +2233,7 @@ function pickService(services) {
|
|
|
1456
2233
|
var usage2 = `dx [service] [action]
|
|
1457
2234
|
dx -
|
|
1458
2235
|
|
|
1459
|
-
Without a terminal
|
|
2236
|
+
Without a terminal (a script, a pipe, an agent) \`dx\` lists the services with their state, ports and image: that listing is the form to use there, \`--json\` giving the full record. On a terminal it picks one instead: enter opens its shell (or brings a stopped service up), ctrl-l follows its logs, ctrl-r restarts, ctrl-x stops and ctrl-b rebuilds it. The services last acted on come first, per folder.
|
|
1460
2237
|
\`dx -\` does the last action again, \`dx <service> [action]\` acts without the picker: \`shell\` (bash when the container has it), \`logs\` (follows the last ${LOGS_TAIL} lines; \`--tail N\` changes the count and \`--no-follow\` prints them once and exits, for a script), \`restart\`, \`up\`, \`stop\` or \`build\` (up with a rebuild).
|
|
1461
2238
|
The services are the stack of this folder or its nearest parent, the ones of the child folders (keyed \`api/php\`), and stacks found only through their containers (keyed \`<project>:php\`).`;
|
|
1462
2239
|
var EXITS = [["the code of the command it ran", "a listing, a shell, logs, restart, up, stop or build"], [0, "it listed the services"], [1, "no compose service here or none by that name"], [2, "a usage error"]];
|
|
@@ -1466,7 +2243,7 @@ var options = {
|
|
|
1466
2243
|
follow: { type: "boolean", negative: "print the logs once and exit instead of following them" }
|
|
1467
2244
|
};
|
|
1468
2245
|
function table2(services) {
|
|
1469
|
-
const colors = paint(
|
|
2246
|
+
const colors = paint(process15.stdout);
|
|
1470
2247
|
const keys = services.map(key);
|
|
1471
2248
|
const width = Math.max(...keys.map((name) => name.length));
|
|
1472
2249
|
const states = Math.max(...services.map((service) => service.state.length));
|
|
@@ -1491,7 +2268,7 @@ function listed2(services) {
|
|
|
1491
2268
|
hint: [service.state, service.ports, service.image].filter(Boolean).join(" \xB7 "),
|
|
1492
2269
|
typed: key(service),
|
|
1493
2270
|
preview: ["--show", service.stack.dir, service.name, service.stack.project ?? ""],
|
|
1494
|
-
actions: Object.entries(
|
|
2271
|
+
actions: Object.entries(KEYS2).map(([pressed, action]) => ({ label: KEY_LABELS[pressed] ?? action, key: pressed, typed: `${key(service)} ${action}` }))
|
|
1495
2272
|
}));
|
|
1496
2273
|
}
|
|
1497
2274
|
async function hidden(flag, rest, services) {
|
|
@@ -1509,7 +2286,7 @@ async function hidden(flag, rest, services) {
|
|
|
1509
2286
|
const service = await serviceNamed(dir, name, project);
|
|
1510
2287
|
if (service === undefined)
|
|
1511
2288
|
return 0;
|
|
1512
|
-
const colors = paint(
|
|
2289
|
+
const colors = paint(process15.stdout);
|
|
1513
2290
|
console.log(`${colors[stateColor(service.state)](service.status || service.state)} ${colors.dim(service.image)}${service.ports ? ` ${colors.cyan(service.ports)}` : ""}
|
|
1514
2291
|
`);
|
|
1515
2292
|
console.log(await logsOf(service.stack, name));
|
|
@@ -1527,8 +2304,8 @@ async function followMasked(command) {
|
|
|
1527
2304
|
const decoder = new TextDecoder;
|
|
1528
2305
|
const proc = Bun.spawn(command, { stdio: ["inherit", "pipe", "inherit"] });
|
|
1529
2306
|
for await (const chunk of proc.stdout)
|
|
1530
|
-
|
|
1531
|
-
|
|
2307
|
+
process15.stdout.write(redactor.push(decoder.decode(chunk, { stream: true })));
|
|
2308
|
+
process15.stdout.write(redactor.end());
|
|
1532
2309
|
return proc.exited;
|
|
1533
2310
|
}
|
|
1534
2311
|
async function dx(args) {
|
|
@@ -1548,23 +2325,21 @@ async function dx(args) {
|
|
|
1548
2325
|
let service;
|
|
1549
2326
|
let action;
|
|
1550
2327
|
if (named === undefined) {
|
|
1551
|
-
const interactive =
|
|
1552
|
-
if (!interactive
|
|
2328
|
+
const interactive = process15.stdin.isTTY && process15.stderr.isTTY && parsed.values.json !== true;
|
|
2329
|
+
if (!interactive) {
|
|
1553
2330
|
if (emit)
|
|
1554
|
-
fail("the picker needs a terminal
|
|
1555
|
-
if (interactive)
|
|
1556
|
-
console.error("fzf is not installed, listing the services instead");
|
|
2331
|
+
fail("the picker needs a terminal", 1);
|
|
1557
2332
|
if (parsed.values.json === true)
|
|
1558
2333
|
printJson(services.map((each) => ({ key: key(each), project: each.project, name: each.name, state: each.state, status: each.status, ports: each.ports, image: each.image })));
|
|
1559
2334
|
else
|
|
1560
2335
|
console.log(table2(services));
|
|
1561
2336
|
return 0;
|
|
1562
2337
|
}
|
|
1563
|
-
const picked = pickService(byRecency(services, runs("dx"), key));
|
|
2338
|
+
const picked = await pickService(byRecency(services, runs("dx"), key));
|
|
1564
2339
|
if (picked === undefined)
|
|
1565
2340
|
return 1;
|
|
1566
2341
|
service = picked.service;
|
|
1567
|
-
action =
|
|
2342
|
+
action = KEYS2[picked.key];
|
|
1568
2343
|
} else if (named === "-") {
|
|
1569
2344
|
const last = runs("dx")[0];
|
|
1570
2345
|
service = last && services.find((candidate) => key(candidate) === last.id);
|
|
@@ -1592,4 +2367,4 @@ async function dx(args) {
|
|
|
1592
2367
|
}
|
|
1593
2368
|
|
|
1594
2369
|
// src/dx-cli.ts
|
|
1595
|
-
|
|
2370
|
+
process16.exitCode = await runMain({ name: "dx", run: dx });
|