@kaynooo/cli-docker 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/dc-cli.js +448 -27
- package/dist/dlog-cli.js +1465 -0
- package/dist/dx-cli.js +479 -42
- package/dist/sf-cli.js +494 -50
- package/package.json +6 -3
- package/src/dc-cli.ts +1 -2
- package/src/dlog-cli.ts +7 -0
- package/src/dlog.ts +219 -0
- package/src/dx-cli.ts +1 -2
- package/src/dx.ts +41 -10
- package/src/sf-cli.ts +1 -2
- package/src/sf.ts +41 -7
- package/src/stack.ts +33 -7
package/dist/dlog-cli.js
ADDED
|
@@ -0,0 +1,1465 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/dlog-cli.ts
|
|
5
|
+
import process9 from "process";
|
|
6
|
+
// ../kit/src/args.ts
|
|
7
|
+
import process4 from "process";
|
|
8
|
+
import { parseArgs } from "util";
|
|
9
|
+
|
|
10
|
+
// ../kit/src/style.ts
|
|
11
|
+
import process2 from "process";
|
|
12
|
+
|
|
13
|
+
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
|
|
14
|
+
function x(s) {
|
|
15
|
+
let r = s.startsWith("#") ? s.slice(1).toLowerCase() : s.toLowerCase();
|
|
16
|
+
if (!/^[a-f0-9]+$/.test(r))
|
|
17
|
+
return [0, 0, 0];
|
|
18
|
+
let t = r.length, n = (c) => Number.parseInt(c, 16);
|
|
19
|
+
if (t === 3 || t === 4) {
|
|
20
|
+
let c = n(r[0] + r[0]), u = n(r[1] + r[1]), h = n(r[2] + r[2]), g = t === 4 ? n(r[3] + r[3]) : undefined;
|
|
21
|
+
return g !== undefined ? [c, u, h, g] : [c, u, h];
|
|
22
|
+
}
|
|
23
|
+
if (t !== 6 && t !== 8)
|
|
24
|
+
throw Error(`Invalid hex color length: ${t}`);
|
|
25
|
+
let a = n(r.slice(0, 2)), b = n(r.slice(2, 4)), o = n(r.slice(4, 6)), y = t === 8 ? n(r.slice(6, 8)) : undefined;
|
|
26
|
+
if ([a, b, o].some(Number.isNaN) || y !== undefined && Number.isNaN(y))
|
|
27
|
+
throw Error(`Invalid hex color value: ${s}`);
|
|
28
|
+
return y !== undefined ? [a, b, o, y] : [a, b, o];
|
|
29
|
+
}
|
|
30
|
+
function A(s) {
|
|
31
|
+
let [r, t, n, a] = s;
|
|
32
|
+
if (a)
|
|
33
|
+
return `#${(r << 24 | t << 16 | n << 8 | Math.round((a ?? 1) * 255)).toString(16).padStart(8, "0").toUpperCase()}`;
|
|
34
|
+
return `#${(r << 16 | t << 8 | n).toString(16).padStart(6, "0").toUpperCase()}`;
|
|
35
|
+
}
|
|
36
|
+
function R(s, r, t) {
|
|
37
|
+
s = (s % 360 + 360) % 360, t /= 100;
|
|
38
|
+
let n = r * Math.min(t, 1 - t) / 100, a = (b) => {
|
|
39
|
+
let o = (b + s / 30) % 12, y = t - n * Math.max(Math.min(o - 3, 9 - o, 1), -1);
|
|
40
|
+
return Math.round(255 * y);
|
|
41
|
+
};
|
|
42
|
+
return [a(0), a(8), a(4)];
|
|
43
|
+
}
|
|
44
|
+
function k(s) {
|
|
45
|
+
let [r, t, n] = s;
|
|
46
|
+
r /= 255, t /= 255, n /= 255;
|
|
47
|
+
let a = Math.max(r, t, n), b = Math.min(r, t, n), o = 0, y = 0, c = (a + b) / 2;
|
|
48
|
+
if (a !== b) {
|
|
49
|
+
let u = a - b;
|
|
50
|
+
if (y = c > 0 && c < 1 ? u / (1 - Math.abs(2 * c - 1)) : 0, a === r)
|
|
51
|
+
o = (t - n) / u + (t < n ? 6 : 0);
|
|
52
|
+
else if (a === t)
|
|
53
|
+
o = (n - r) / u + 2;
|
|
54
|
+
else
|
|
55
|
+
o = (r - t) / u + 4;
|
|
56
|
+
o /= 6;
|
|
57
|
+
}
|
|
58
|
+
return [Math.round(o * 360), Math.round(y * 100), Math.round(c * 100)];
|
|
59
|
+
}
|
|
60
|
+
function H(s, r, t) {
|
|
61
|
+
s = (s % 360 + 360) % 360, r /= 100, t /= 100;
|
|
62
|
+
let n = (a) => {
|
|
63
|
+
let b = (a + s / 60) % 6, o = t - t * r * Math.max(Math.min(b, 4 - b, 1), 0);
|
|
64
|
+
return Math.round(255 * o);
|
|
65
|
+
};
|
|
66
|
+
return [n(5), n(3), n(1)];
|
|
67
|
+
}
|
|
68
|
+
function f(s) {
|
|
69
|
+
let [r, t, n] = s;
|
|
70
|
+
r /= 255, t /= 255, n /= 255;
|
|
71
|
+
let a = Math.max(r, t, n), b = Math.min(r, t, n), o = a - b, y = 0, c = a === 0 ? 0 : o / a, u = a;
|
|
72
|
+
if (o !== 0) {
|
|
73
|
+
if (a === r)
|
|
74
|
+
y = (t - n) / o + (t < n ? 6 : 0);
|
|
75
|
+
else if (a === t)
|
|
76
|
+
y = (n - r) / o + 2;
|
|
77
|
+
else
|
|
78
|
+
y = (r - t) / o + 4;
|
|
79
|
+
y /= 6;
|
|
80
|
+
}
|
|
81
|
+
return [Math.round(y * 360), Math.round(c * 100), Math.round(u * 100)];
|
|
82
|
+
}
|
|
83
|
+
function O(s) {
|
|
84
|
+
let r = s[0] / 255, t = s[1] / 255, n = s[2] / 255, a = 1 - Math.max(r, t, n);
|
|
85
|
+
if (a === 1)
|
|
86
|
+
return [0, 0, 0, 100];
|
|
87
|
+
let b = (1 - r - a) / (1 - a), o = (1 - t - a) / (1 - a), y = (1 - n - a) / (1 - a);
|
|
88
|
+
return [Math.round(b * 100), Math.round(o * 100), Math.round(y * 100), Math.round(a * 100)];
|
|
89
|
+
}
|
|
90
|
+
function v(s, r, t, n) {
|
|
91
|
+
return s /= 100, r /= 100, t /= 100, n /= 100, [Math.round(255 * (1 - s) * (1 - n)), Math.round(255 * (1 - r) * (1 - n)), Math.round(255 * (1 - t) * (1 - n))];
|
|
92
|
+
}
|
|
93
|
+
var m = (s) => s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
94
|
+
var p = (s) => s <= 0.0031308 ? 12.92 * s : 1.055 * s ** 0.4166666666666667 - 0.055;
|
|
95
|
+
var i = (s, r) => Math.round(s * 10 ** r) / 10 ** r;
|
|
96
|
+
function T(s) {
|
|
97
|
+
let r = m(s[0] / 255), t = m(s[1] / 255), n = m(s[2] / 255), a = Math.cbrt(0.4122214708 * r + 0.5363325363 * t + 0.0514459929 * n), b = Math.cbrt(0.2119034982 * r + 0.6806995451 * t + 0.1073969566 * n), o = Math.cbrt(0.0883024619 * r + 0.2817188376 * t + 0.6299787005 * n);
|
|
98
|
+
return [i(0.2104542553 * a + 0.793617785 * b - 0.0040720468 * o, 4), i(1.9779984951 * a - 2.428592205 * b + 0.4505937099 * o, 4), i(0.0259040371 * a + 0.7827717662 * b - 0.808675766 * o, 4)];
|
|
99
|
+
}
|
|
100
|
+
function M(s, r, t) {
|
|
101
|
+
let n = (s + 0.3963377774 * r + 0.2158037573 * t) ** 3, a = (s - 0.1055613458 * r - 0.0638541728 * t) ** 3, b = (s - 0.0894841775 * r - 1.291485548 * t) ** 3, o = p(4.0767416621 * n - 3.3077115913 * a + 0.2309699292 * b), y = p(-1.2684380046 * n + 2.6097574011 * a - 0.3413193965 * b), c = p(-0.0041960863 * n - 0.7034186147 * a + 1.707614701 * b), u = (h) => Math.min(255, Math.max(0, Math.round(h * 255)));
|
|
102
|
+
return [u(o), u(y), u(c)];
|
|
103
|
+
}
|
|
104
|
+
function d(s) {
|
|
105
|
+
let [r, t, n] = T(s), a = Math.sqrt(t * t + n * n), b = (Math.atan2(n, t) * 180 / Math.PI + 360) % 360;
|
|
106
|
+
return [i(r, 4), i(a, 4), i(b, 2)];
|
|
107
|
+
}
|
|
108
|
+
function C(s, r, t) {
|
|
109
|
+
let n = t * Math.PI / 180;
|
|
110
|
+
return M(s, r * Math.cos(n), r * Math.sin(n));
|
|
111
|
+
}
|
|
112
|
+
function l(s) {
|
|
113
|
+
let r = s / 255;
|
|
114
|
+
return r <= 0.03928 ? r / 12.92 : ((r + 0.055) / 1.055) ** 2.4;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class e {
|
|
118
|
+
r;
|
|
119
|
+
g;
|
|
120
|
+
b;
|
|
121
|
+
a;
|
|
122
|
+
constructor(s, r, t, n = 1) {
|
|
123
|
+
this.r = s, this.g = r, this.b = t, this.a = n;
|
|
124
|
+
}
|
|
125
|
+
static from(s, r) {
|
|
126
|
+
if (s === "hex") {
|
|
127
|
+
let [t, n, a, b] = x(r);
|
|
128
|
+
return new e(t, n, a, b);
|
|
129
|
+
}
|
|
130
|
+
if (s === "rgb") {
|
|
131
|
+
if (Array.isArray(r)) {
|
|
132
|
+
let [o, y, c, u] = r;
|
|
133
|
+
return new e(o, y, c, u);
|
|
134
|
+
}
|
|
135
|
+
let { r: t, g: n, b: a, a: b } = r;
|
|
136
|
+
return new e(t, n, a, b);
|
|
137
|
+
}
|
|
138
|
+
if (s === "hsl") {
|
|
139
|
+
let t, n, a, b;
|
|
140
|
+
if (Array.isArray(r))
|
|
141
|
+
[t, n, a, b] = r;
|
|
142
|
+
else
|
|
143
|
+
({ h: t, s: n, l: a, a: b } = r);
|
|
144
|
+
let [o, y, c] = R(t, n, a);
|
|
145
|
+
return new e(o, y, c, b);
|
|
146
|
+
}
|
|
147
|
+
if (s === "hsv") {
|
|
148
|
+
let t, n, a, b;
|
|
149
|
+
if (Array.isArray(r))
|
|
150
|
+
[t, n, a, b] = r;
|
|
151
|
+
else
|
|
152
|
+
({ h: t, s: n, v: a, a: b } = r);
|
|
153
|
+
let [o, y, c] = H(t, n, a);
|
|
154
|
+
return new e(o, y, c, b);
|
|
155
|
+
}
|
|
156
|
+
if (s === "cmyk") {
|
|
157
|
+
let t, n, a, b, o;
|
|
158
|
+
if (Array.isArray(r))
|
|
159
|
+
[t, n, a, b, o] = r;
|
|
160
|
+
else
|
|
161
|
+
({ c: t, m: n, y: a, k: b, a: o } = r);
|
|
162
|
+
let [y, c, u] = v(t, n, a, b);
|
|
163
|
+
return new e(y, c, u, o);
|
|
164
|
+
}
|
|
165
|
+
if (s === "oklch") {
|
|
166
|
+
let t, n, a, b;
|
|
167
|
+
if (Array.isArray(r))
|
|
168
|
+
[t, n, a, b] = r;
|
|
169
|
+
else
|
|
170
|
+
({ l: t, c: n, h: a, a: b } = r);
|
|
171
|
+
let [o, y, c] = C(t, n, a);
|
|
172
|
+
return new e(o, y, c, b);
|
|
173
|
+
}
|
|
174
|
+
if (s === "oklab") {
|
|
175
|
+
let t, n, a, b;
|
|
176
|
+
if (Array.isArray(r))
|
|
177
|
+
[t, n, a, b] = r;
|
|
178
|
+
else
|
|
179
|
+
({ l: t, a: n, b: a, alpha: b } = r);
|
|
180
|
+
let [o, y, c] = M(t, n, a);
|
|
181
|
+
return new e(o, y, c, b);
|
|
182
|
+
}
|
|
183
|
+
throw Error("Invalid input");
|
|
184
|
+
}
|
|
185
|
+
to(s) {
|
|
186
|
+
switch (s) {
|
|
187
|
+
case "rgb":
|
|
188
|
+
return [this.r, this.g, this.b];
|
|
189
|
+
case "rgba":
|
|
190
|
+
return [this.r, this.g, this.b, this.a];
|
|
191
|
+
case "hex":
|
|
192
|
+
return A([this.r, this.g, this.b]);
|
|
193
|
+
case "hexa":
|
|
194
|
+
return A([this.r, this.g, this.b, this.a]);
|
|
195
|
+
case "hsl":
|
|
196
|
+
return k([this.r, this.g, this.b]);
|
|
197
|
+
case "hsla":
|
|
198
|
+
return [...k([this.r, this.g, this.b]), this.a];
|
|
199
|
+
case "hsv":
|
|
200
|
+
return f([this.r, this.g, this.b]);
|
|
201
|
+
case "hsva":
|
|
202
|
+
return [...f([this.r, this.g, this.b]), this.a];
|
|
203
|
+
case "cmyk":
|
|
204
|
+
return O([this.r, this.g, this.b]);
|
|
205
|
+
case "cmyka":
|
|
206
|
+
return [...O([this.r, this.g, this.b]), this.a];
|
|
207
|
+
case "oklch":
|
|
208
|
+
return d([this.r, this.g, this.b]);
|
|
209
|
+
case "oklcha":
|
|
210
|
+
return [...d([this.r, this.g, this.b]), this.a];
|
|
211
|
+
case "oklab":
|
|
212
|
+
return T([this.r, this.g, this.b]);
|
|
213
|
+
case "oklaba":
|
|
214
|
+
return [...T([this.r, this.g, this.b]), this.a];
|
|
215
|
+
default:
|
|
216
|
+
return [this.r, this.g, this.b];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
getLuminance() {
|
|
220
|
+
let s = l(this.r), r = l(this.g), t = l(this.b);
|
|
221
|
+
return 0.2126 * s + 0.7152 * r + 0.0722 * t;
|
|
222
|
+
}
|
|
223
|
+
contrastRatio(s) {
|
|
224
|
+
let r = this.getLuminance(), t = s.getLuminance(), [n, a] = r >= t ? [r, t] : [t, r];
|
|
225
|
+
return (n + 0.05) / (a + 0.05);
|
|
226
|
+
}
|
|
227
|
+
bestContrastForeground() {
|
|
228
|
+
let s = new e(255, 255, 255, 1), r = new e(0, 0, 0, 1), t = this.contrastRatio(s), n = this.contrastRatio(r);
|
|
229
|
+
return t >= n ? s : r;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-y1msbssb.js
|
|
234
|
+
var r = 39;
|
|
235
|
+
var o = 49;
|
|
236
|
+
var f2 = "\x1B[";
|
|
237
|
+
var C2 = { reset: [0, 0], bold: [1, 22], dim: [2, 22], italic: [3, 23], underline: [4, 24], inverse: [7, 27], hidden: [8, 28], strikethrough: [9, 29], black: [30, r], red: [31, r], green: [32, r], yellow: [33, r], blue: [34, r], magenta: [35, r], cyan: [36, r], white: [37, r], gray: [90, r], grey: [90, r], blackBright: [90, r], redBright: [91, r], greenBright: [92, r], yellowBright: [93, r], blueBright: [94, r], magentaBright: [95, r], cyanBright: [96, r], whiteBright: [97, r], bgBlack: [40, o], bgRed: [41, o], bgGreen: [42, o], bgYellow: [43, o], bgBlue: [44, o], bgMagenta: [45, o], bgCyan: [46, o], bgWhite: [47, o], bgBlackBright: [100, o], bgRedBright: [101, o], bgGreenBright: [102, o], bgYellowBright: [103, o], bgBlueBright: [104, o], bgMagentaBright: [105, o], bgCyanBright: [106, o], bgWhiteBright: [107, o], rgb: (i, n, a, l = "fg") => [`${l === "fg" ? 38 : 48};2;${i};${n};${a}`, l === "fg" ? r : o], hex: (i, n = "fg") => {
|
|
238
|
+
let [a, l, s] = e.from("hex", i).to("rgb");
|
|
239
|
+
return [`${n === "fg" ? 38 : 48};2;${a};${l};${s}`, n === "fg" ? r : o];
|
|
240
|
+
}, ansi256: (i, n = "fg") => [`${n === "fg" ? 38 : 48};5;${i}`, n === "fg" ? r : o] };
|
|
241
|
+
var b = new Set(["", "1", "2", "3", "true"]);
|
|
242
|
+
function h(i, n = globalThis.process?.env ?? {}) {
|
|
243
|
+
if (n.FORCE_COLOR !== undefined)
|
|
244
|
+
return b.has(n.FORCE_COLOR);
|
|
245
|
+
if (n.NO_COLOR !== undefined || n.NODE_DISABLE_COLORS !== undefined || n.TERM === "dumb")
|
|
246
|
+
return false;
|
|
247
|
+
return i?.isTTY === true;
|
|
248
|
+
}
|
|
249
|
+
function d2(i, n, a) {
|
|
250
|
+
if (!h(a.stream ?? globalThis.process?.stdout, a.env))
|
|
251
|
+
return n;
|
|
252
|
+
let l = "", s = "";
|
|
253
|
+
for (let g = 0, y = i.length;g < y; g++) {
|
|
254
|
+
let [c, p] = i[g];
|
|
255
|
+
l += `${f2}${c}m`, s = `${f2}${p}m${s}`;
|
|
256
|
+
}
|
|
257
|
+
return l + n + s;
|
|
258
|
+
}
|
|
259
|
+
function u(i, n = []) {
|
|
260
|
+
return new Proxy((l) => d2(n, l, i), { get(l, s) {
|
|
261
|
+
let g = C2[s];
|
|
262
|
+
if (Array.isArray(g))
|
|
263
|
+
return u(i, [...n, g]);
|
|
264
|
+
if (typeof g === "function")
|
|
265
|
+
return (...y) => u(i, [...n, g(...y)]);
|
|
266
|
+
throw Error(`Unknown style: ${String(s)}`);
|
|
267
|
+
} });
|
|
268
|
+
}
|
|
269
|
+
function le(i = {}) {
|
|
270
|
+
return u(i);
|
|
271
|
+
}
|
|
272
|
+
var t = le();
|
|
273
|
+
|
|
274
|
+
// ../kit/src/style.ts
|
|
275
|
+
function takesControlCodes(stream, env = process2.env) {
|
|
276
|
+
return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
|
|
277
|
+
}
|
|
278
|
+
function paint(stream, env = process2.env) {
|
|
279
|
+
return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
|
|
280
|
+
}
|
|
281
|
+
var STATUSES = {
|
|
282
|
+
passed: { symbol: "\u2713", words: ["passed", "success", "succeeded", "ok"], paint: (colors) => colors.green },
|
|
283
|
+
failed: { symbol: "\u2717", words: ["failed", "failure", "error", "errored"], paint: (colors) => colors.red },
|
|
284
|
+
running: { symbol: "\u25CF", words: ["running", "in_progress", "started"], paint: (colors) => colors.cyan },
|
|
285
|
+
queued: { symbol: "\u25CB", words: ["queued", "waiting", "pending", "created", "blocked", "scheduled"], paint: (colors) => colors.yellow },
|
|
286
|
+
skipped: { symbol: "-", words: ["skipped", "cancelled", "canceled", "manual"], paint: (colors) => colors.dim }
|
|
287
|
+
};
|
|
288
|
+
var STATUS_OF = new Map(Object.entries(STATUSES).flatMap(([status, { words }]) => words.map((word) => [word, status])));
|
|
289
|
+
|
|
290
|
+
// ../kit/src/format.ts
|
|
291
|
+
function upTo(text, separator) {
|
|
292
|
+
const end = text.indexOf(separator);
|
|
293
|
+
return end === -1 ? text : text.slice(0, end);
|
|
294
|
+
}
|
|
295
|
+
var ESCAPE = String.fromCharCode(27);
|
|
296
|
+
function printJson(value) {
|
|
297
|
+
console.log(JSON.stringify(value, undefined, 2));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ../kit/src/help.ts
|
|
301
|
+
import process3 from "process";
|
|
302
|
+
var OPTIONAL = /\[[^\]]+\]/g;
|
|
303
|
+
var USAGE_PREFIX = /^usage:\s*/i;
|
|
304
|
+
var FULL_STOP = /\.$/;
|
|
305
|
+
function usageLine(usage, colors = paint(process3.stdout)) {
|
|
306
|
+
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
307
|
+
}
|
|
308
|
+
function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces, exits = [] } = {}) {
|
|
309
|
+
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exitRows(exits, colors), ...exampleRows(examples, colors)].join(`
|
|
310
|
+
`);
|
|
311
|
+
}
|
|
312
|
+
function exitRows(exits, colors = paint(process3.stdout)) {
|
|
313
|
+
if (exits.length === 0)
|
|
314
|
+
return [];
|
|
315
|
+
const width = Math.max(...exits.map(([code]) => String(code).length));
|
|
316
|
+
return ["", colors.bold("Exits:"), ...exits.map(([code, meaning]) => ` ${colors.cyan(String(code).padEnd(width))} ${meaning}`)];
|
|
317
|
+
}
|
|
318
|
+
function replacesRows(replaces, colors = paint(process3.stdout)) {
|
|
319
|
+
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
320
|
+
}
|
|
321
|
+
function wantsHelpJson(env = process3.env) {
|
|
322
|
+
return env.KIT_HELP === "json";
|
|
323
|
+
}
|
|
324
|
+
function description(usage, { examples = [], replaces, exits } = {}, options) {
|
|
325
|
+
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
326
|
+
`);
|
|
327
|
+
const first = lines[0];
|
|
328
|
+
const colon = first.indexOf(": ");
|
|
329
|
+
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
330
|
+
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
331
|
+
return {
|
|
332
|
+
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
333
|
+
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
334
|
+
...replaces === undefined ? {} : { replaces },
|
|
335
|
+
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] }),
|
|
336
|
+
...exits === undefined ? {} : { exits: exits.map(([code, meaning]) => ({ code, meaning })) },
|
|
337
|
+
...options === undefined ? {} : { options: Object.entries(options).map(([name, { short, description }]) => ({ name, ...short === undefined ? {} : { short }, ...description === undefined ? {} : { description } })) }
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function exampleRows(examples, colors = paint(process3.stdout)) {
|
|
341
|
+
if (examples.length === 0)
|
|
342
|
+
return [];
|
|
343
|
+
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
344
|
+
}
|
|
345
|
+
function optionRows(options, colors = paint(process3.stdout)) {
|
|
346
|
+
const rows = Object.entries(options).map(([name, option]) => {
|
|
347
|
+
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
348
|
+
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
349
|
+
const placeholder = option.optional === undefined ? named : `[${named}]`;
|
|
350
|
+
const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
|
|
351
|
+
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
|
|
352
|
+
});
|
|
353
|
+
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
354
|
+
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ../kit/src/text.ts
|
|
358
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
|
|
359
|
+
function stripAnsi(text) {
|
|
360
|
+
return text.replace(ANSI, "");
|
|
361
|
+
}
|
|
362
|
+
function distance(a, b) {
|
|
363
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
364
|
+
for (const [i, left] of [...a].entries()) {
|
|
365
|
+
const current = [i + 1];
|
|
366
|
+
for (const [j, right] of [...b].entries())
|
|
367
|
+
current.push(Math.min((previous[j + 1] ?? 0) + 1, (current[j] ?? 0) + 1, (previous[j] ?? 0) + (left === right ? 0 : 1)));
|
|
368
|
+
previous = current;
|
|
369
|
+
}
|
|
370
|
+
return previous[b.length] ?? 0;
|
|
371
|
+
}
|
|
372
|
+
function closest(name, names) {
|
|
373
|
+
const started = names.find((known) => known.startsWith(name));
|
|
374
|
+
if (started !== undefined)
|
|
375
|
+
return started;
|
|
376
|
+
const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
|
|
377
|
+
return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
|
|
378
|
+
}
|
|
379
|
+
// ../kit/src/input.ts
|
|
380
|
+
var WHOLE = /^\d+$/;
|
|
381
|
+
function wholeNumber(value, name, fallback, min = 1) {
|
|
382
|
+
if (value === undefined)
|
|
383
|
+
return fallback;
|
|
384
|
+
const number = Number(value);
|
|
385
|
+
if (!WHOLE.test(value) || !Number.isSafeInteger(number) || number < min)
|
|
386
|
+
badUsage(`${name.startsWith("<") ? name : `--${name}`} takes a whole number from ${min}, not ${value}`);
|
|
387
|
+
return number;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ../kit/src/args.ts
|
|
391
|
+
var DASHES = /^-+/;
|
|
392
|
+
function jsonOption(holds) {
|
|
393
|
+
return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
class Exit extends Error {
|
|
397
|
+
code;
|
|
398
|
+
hint;
|
|
399
|
+
constructor(message, code, hint) {
|
|
400
|
+
super(message);
|
|
401
|
+
this.code = code;
|
|
402
|
+
this.hint = hint;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function fail(message, code = 2, hint) {
|
|
406
|
+
throw new Exit(message, code, hint);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
class UsageError extends Exit {
|
|
410
|
+
constructor(message, hint) {
|
|
411
|
+
super(message, 2, hint);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function badUsage(message) {
|
|
415
|
+
throw new UsageError(message);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
class HelpShown extends Exit {
|
|
419
|
+
constructor() {
|
|
420
|
+
super("help shown", 0);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
class Completing {
|
|
425
|
+
options;
|
|
426
|
+
positionals;
|
|
427
|
+
candidates;
|
|
428
|
+
constructor(options, positionals = [], candidates = {}) {
|
|
429
|
+
this.options = options;
|
|
430
|
+
this.positionals = positionals;
|
|
431
|
+
this.candidates = candidates;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
var completion = { active: false };
|
|
435
|
+
var OUTPUT_OPTIONS = {
|
|
436
|
+
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
437
|
+
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
438
|
+
};
|
|
439
|
+
function applyOutput(color, plain, env = process4.env) {
|
|
440
|
+
const when = plain === true ? "never" : color ?? "auto";
|
|
441
|
+
if (when === "auto")
|
|
442
|
+
return;
|
|
443
|
+
if (when !== "always" && when !== "never")
|
|
444
|
+
badUsage(`--color takes auto, always or never, not ${when}`);
|
|
445
|
+
if (when === "always") {
|
|
446
|
+
env.FORCE_COLOR = "1";
|
|
447
|
+
delete env.NO_COLOR;
|
|
448
|
+
} else {
|
|
449
|
+
env.NO_COLOR = "1";
|
|
450
|
+
delete env.FORCE_COLOR;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function parse(args, usage, options, settings = {}) {
|
|
454
|
+
const output = Object.fromEntries(Object.entries(OUTPUT_OPTIONS).filter(([name]) => !(name in options)));
|
|
455
|
+
const all = { ...options, ...output, help: { type: "boolean", short: "h", description: "show this help" } };
|
|
456
|
+
const listing = listed(all, settings.allowNegative === true);
|
|
457
|
+
if (completion.active)
|
|
458
|
+
throw new Completing(listing, settings.positionals, settings.candidates);
|
|
459
|
+
const { own, rest } = settings.trailing === true ? splitTrailing(args, all, settings.positionals?.length ?? 0) : splitRest(args, settings.rest === true);
|
|
460
|
+
let parsed;
|
|
461
|
+
try {
|
|
462
|
+
parsed = parseArgs({ args: joinDashValues(withBare(withAliases(own, all), all), all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
463
|
+
} catch (error) {
|
|
464
|
+
const message = parseError(error);
|
|
465
|
+
throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
|
|
466
|
+
}
|
|
467
|
+
const values = parsed.values;
|
|
468
|
+
applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
|
|
469
|
+
if (values.help === true) {
|
|
470
|
+
console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings, listing)) : helpText(usage, listing, undefined, settings));
|
|
471
|
+
throw new HelpShown;
|
|
472
|
+
}
|
|
473
|
+
checkChoices(parsed.values, all);
|
|
474
|
+
checkConflicts(parsed.values, all);
|
|
475
|
+
if (settings.positionals !== undefined)
|
|
476
|
+
checkPositionals(parsed.positionals, settings.positionals);
|
|
477
|
+
return { ...parsed, values: parsed.values, positionals: parsed.positionals, rest };
|
|
478
|
+
}
|
|
479
|
+
function splitRest(args, passes) {
|
|
480
|
+
const separator = passes ? args.indexOf("--") : -1;
|
|
481
|
+
return { own: separator === -1 ? args : args.slice(0, separator), rest: separator === -1 ? undefined : args.slice(separator + 1) };
|
|
482
|
+
}
|
|
483
|
+
function splitTrailing(args, options, count) {
|
|
484
|
+
const takesValue = (word) => !word.includes("=") && Object.entries(options).some(([name, { short, type }]) => type === "string" && (word === `--${name}` || word === `-${short}`));
|
|
485
|
+
let seen = 0;
|
|
486
|
+
if (count === 0)
|
|
487
|
+
return { own: args, rest: undefined };
|
|
488
|
+
for (let at = 0;at < args.length; at++) {
|
|
489
|
+
const word = args[at];
|
|
490
|
+
if (word === "--")
|
|
491
|
+
break;
|
|
492
|
+
if (word.startsWith("-")) {
|
|
493
|
+
if (takesValue(word))
|
|
494
|
+
at++;
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
seen++;
|
|
498
|
+
if (seen === count)
|
|
499
|
+
return { own: args.slice(0, at + 1), rest: args.slice(at + 1) };
|
|
500
|
+
}
|
|
501
|
+
return { own: args, rest: undefined };
|
|
502
|
+
}
|
|
503
|
+
function listed(options, allowNegative) {
|
|
504
|
+
if (!allowNegative)
|
|
505
|
+
return options;
|
|
506
|
+
return Object.fromEntries(Object.entries(options).flatMap(([name, option]) => {
|
|
507
|
+
if (option.type !== "boolean" || option.negative === undefined)
|
|
508
|
+
return [[name, option]];
|
|
509
|
+
return [...option.description === undefined ? [] : [[name, option]], [`no-${name}`, { type: "boolean", description: option.negative }]];
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
512
|
+
function withAliases(args, options) {
|
|
513
|
+
const named = new Map(Object.entries(options).flatMap(([name, { aliases = [] }]) => aliases.map((alias) => [`--${alias}`, `--${name}`])));
|
|
514
|
+
if (named.size === 0)
|
|
515
|
+
return args;
|
|
516
|
+
const separator = args.indexOf("--");
|
|
517
|
+
const own = separator === -1 ? args : args.slice(0, separator);
|
|
518
|
+
return [...own.map((arg) => {
|
|
519
|
+
const at = arg.indexOf("=");
|
|
520
|
+
const current = named.get(at === -1 ? arg : arg.slice(0, at));
|
|
521
|
+
return current === undefined ? arg : `${current}${at === -1 ? "" : arg.slice(at)}`;
|
|
522
|
+
}), ...separator === -1 ? [] : args.slice(separator)];
|
|
523
|
+
}
|
|
524
|
+
function withBare(args, options) {
|
|
525
|
+
const bare = new Map(Object.entries(options).flatMap(([name, { short, optional }]) => optional === undefined ? [] : [[`--${name}`, optional], ...short === undefined ? [] : [[`-${short}`, optional]]]));
|
|
526
|
+
if (bare.size === 0)
|
|
527
|
+
return args;
|
|
528
|
+
const separator = args.indexOf("--");
|
|
529
|
+
const own = separator === -1 ? args : args.slice(0, separator);
|
|
530
|
+
return [...own.flatMap((arg, index) => {
|
|
531
|
+
const optional = bare.get(arg);
|
|
532
|
+
const next = own[index + 1];
|
|
533
|
+
const value = next !== undefined && (optional?.accepts === undefined ? !next.startsWith("-") : optional.accepts.test(next));
|
|
534
|
+
return optional === undefined || value ? [arg] : [arg, optional.bare];
|
|
535
|
+
}), ...separator === -1 ? [] : args.slice(separator)];
|
|
536
|
+
}
|
|
537
|
+
function checkConflicts(values, options) {
|
|
538
|
+
const given = (name) => values[name] !== undefined && values[name] !== false;
|
|
539
|
+
for (const [name, { conflicts = [] }] of Object.entries(options)) {
|
|
540
|
+
const other = given(name) ? conflicts.find(given) : undefined;
|
|
541
|
+
if (other !== undefined)
|
|
542
|
+
badUsage(`--${name} cannot be used with --${other}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function checkChoices(values, options) {
|
|
546
|
+
for (const [name, option] of Object.entries(options)) {
|
|
547
|
+
if (option.choices === undefined)
|
|
548
|
+
continue;
|
|
549
|
+
const given = values[name];
|
|
550
|
+
for (const value of Array.isArray(given) ? given : [given]) {
|
|
551
|
+
if (typeof value === "string")
|
|
552
|
+
oneOf(value, name, option.choices);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function oneOf(value, name, choices) {
|
|
557
|
+
if (!choices.includes(value))
|
|
558
|
+
badUsage(`${name.startsWith("<") ? name : `--${name}`} takes one of ${choices.join(", ")}, not ${value}`);
|
|
559
|
+
return value;
|
|
560
|
+
}
|
|
561
|
+
function checkPositionals(given, names) {
|
|
562
|
+
const variadic = names.at(-1)?.replace("]", "").endsWith("...") === true;
|
|
563
|
+
const required = names.filter((name) => !name.startsWith("[")).length;
|
|
564
|
+
if (given.length < required)
|
|
565
|
+
badUsage(`missing ${names[given.length]}`);
|
|
566
|
+
if (!variadic && given.length > names.length)
|
|
567
|
+
badUsage(`unexpected argument ${given[names.length]}`);
|
|
568
|
+
}
|
|
569
|
+
function joinDashValues(args, options) {
|
|
570
|
+
const longOf = new Map(Object.entries(options).flatMap(([name, option]) => option.short === undefined ? [[`--${name}`, name]] : [[`--${name}`, name], [`-${option.short}`, name]]));
|
|
571
|
+
const isOption = (arg) => arg === "--" || longOf.has(arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg);
|
|
572
|
+
const joined = [];
|
|
573
|
+
for (let i = 0;i < args.length; i++) {
|
|
574
|
+
const arg = args[i] ?? "";
|
|
575
|
+
if (arg === "--")
|
|
576
|
+
return [...joined, ...args.slice(i)];
|
|
577
|
+
const name = longOf.get(arg);
|
|
578
|
+
const next = args[i + 1];
|
|
579
|
+
if (name !== undefined && options[name]?.type === "string" && next !== undefined && next.startsWith("-") && next !== "-" && !isOption(next)) {
|
|
580
|
+
joined.push(`--${name}=${next}`);
|
|
581
|
+
i++;
|
|
582
|
+
} else {
|
|
583
|
+
joined.push(arg);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return joined;
|
|
587
|
+
}
|
|
588
|
+
function parseError(error) {
|
|
589
|
+
const message = upTo(error instanceof Error ? error.message : String(error), ". ");
|
|
590
|
+
return `${message.charAt(0).toLowerCase()}${message.slice(1)}`;
|
|
591
|
+
}
|
|
592
|
+
var UNKNOWN_OPTION = /^unknown option '(-[^']+)'/;
|
|
593
|
+
function nearestOption(message, options, allowNegative) {
|
|
594
|
+
const typed = UNKNOWN_OPTION.exec(message)?.[1];
|
|
595
|
+
if (typed === undefined)
|
|
596
|
+
return;
|
|
597
|
+
const word = typed.replace(DASHES, "");
|
|
598
|
+
if (word.length < 2 && !typed.startsWith("--"))
|
|
599
|
+
return;
|
|
600
|
+
const known = Object.entries(options).flatMap(([name, option]) => [
|
|
601
|
+
...option.short === undefined ? [] : [`-${option.short}`],
|
|
602
|
+
`--${name}`,
|
|
603
|
+
...allowNegative && option.type === "boolean" ? [`--no-${name}`] : []
|
|
604
|
+
]);
|
|
605
|
+
const meant = closest(word, known.map((flag) => flag.replace(DASHES, "")));
|
|
606
|
+
return meant === undefined ? undefined : `did you mean ${known.find((flag) => flag.replace(DASHES, "") === meant)}?`;
|
|
607
|
+
}
|
|
608
|
+
// ../kit/src/progress.ts
|
|
609
|
+
var ESC = String.fromCharCode(27);
|
|
610
|
+
var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x7E]|\\][^\\u0007${ESC}]*(?:\\u0007|${ESC}\\\\))`, "y");
|
|
611
|
+
var RESET_STYLE = `${ESC}[0m`;
|
|
612
|
+
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
613
|
+
// ../kit/src/cli.ts
|
|
614
|
+
import { readFileSync, realpathSync } from "fs";
|
|
615
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
616
|
+
import process6 from "process";
|
|
617
|
+
|
|
618
|
+
// ../kit/src/usage.ts
|
|
619
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
620
|
+
import { homedir } from "os";
|
|
621
|
+
import { dirname, isAbsolute, join } from "path";
|
|
622
|
+
import process5 from "process";
|
|
623
|
+
|
|
624
|
+
// ../kit/src/logs.ts
|
|
625
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
626
|
+
var CLI_FOLDER = "kaynooo-cli";
|
|
627
|
+
|
|
628
|
+
// ../kit/src/usage.ts
|
|
629
|
+
var UNKNOWN_OPTION2 = /unknown option '(--[a-z][a-z0-9-]*)[=']/;
|
|
630
|
+
var FLAG_NAME = /^(?:--[a-z][a-z0-9-]*|-[a-zA-Z])$/;
|
|
631
|
+
var LONGEST_NAME = 40;
|
|
632
|
+
var usageError;
|
|
633
|
+
function noteUsageError(message) {
|
|
634
|
+
usageError = message;
|
|
635
|
+
}
|
|
636
|
+
function stateHome(env = process5.env) {
|
|
637
|
+
const dir = env.XDG_STATE_HOME;
|
|
638
|
+
return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
|
|
639
|
+
}
|
|
640
|
+
function usagePath(env = process5.env) {
|
|
641
|
+
return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
642
|
+
}
|
|
643
|
+
function usageRecorded(env = process5.env) {
|
|
644
|
+
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
645
|
+
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
646
|
+
}
|
|
647
|
+
function flagNames(argv) {
|
|
648
|
+
const end = argv.indexOf("--");
|
|
649
|
+
const names = (end === -1 ? argv : argv.slice(0, end)).filter((word) => word.startsWith("-") && word !== "-").map((word) => word.includes("=") ? word.slice(0, word.indexOf("=")) : word);
|
|
650
|
+
return [...new Set(names)].filter((name) => name.length <= LONGEST_NAME && FLAG_NAME.test(name));
|
|
651
|
+
}
|
|
652
|
+
function usageEntry(bin, command, argv, code, now = new Date) {
|
|
653
|
+
const unknown = code === 2 && usageError !== undefined ? UNKNOWN_OPTION2.exec(usageError)?.[1] : undefined;
|
|
654
|
+
usageError = undefined;
|
|
655
|
+
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
656
|
+
}
|
|
657
|
+
function recordUsage(entry, env = process5.env) {
|
|
658
|
+
if (!usageRecorded(env))
|
|
659
|
+
return;
|
|
660
|
+
try {
|
|
661
|
+
const path = usagePath(env);
|
|
662
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
663
|
+
appendFileSync(path, `${JSON.stringify(entry)}
|
|
664
|
+
`, { mode: 384 });
|
|
665
|
+
} catch {}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ../kit/src/cli.ts
|
|
669
|
+
var LEVEL_OPTIONS = {
|
|
670
|
+
...OUTPUT_OPTIONS,
|
|
671
|
+
help: { type: "boolean", short: "h", description: "show this help" }
|
|
672
|
+
};
|
|
673
|
+
var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
|
|
674
|
+
function printError(prefix, message, hint, cause) {
|
|
675
|
+
const colors = paint(process6.stderr);
|
|
676
|
+
const styled = takesControlCodes(process6.stderr);
|
|
677
|
+
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
678
|
+
const under = (label, text, tint) => text === undefined ? "" : `
|
|
679
|
+
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
680
|
+
console.error(`${head} ${message}${under("cause", cause, colors.dim)}${under("hint", hint, colors.yellow)}`);
|
|
681
|
+
}
|
|
682
|
+
function printJsonError(message, hint, cause) {
|
|
683
|
+
console.error(JSON.stringify({ error: message, ...hint === undefined ? {} : { hint }, ...cause === undefined ? {} : { cause } }));
|
|
684
|
+
}
|
|
685
|
+
function wantsJson(argv) {
|
|
686
|
+
const end = argv.indexOf("--");
|
|
687
|
+
return (end === -1 ? argv : argv.slice(0, end)).includes("--json");
|
|
688
|
+
}
|
|
689
|
+
function reportThrown(prefix, error, redact = (text) => text, json = false) {
|
|
690
|
+
if (error instanceof HelpShown)
|
|
691
|
+
return 0;
|
|
692
|
+
const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
|
|
693
|
+
const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
|
|
694
|
+
const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
|
|
695
|
+
if (error instanceof UsageError)
|
|
696
|
+
noteUsageError(error.message);
|
|
697
|
+
const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
|
|
698
|
+
report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
|
|
699
|
+
return error instanceof Exit ? error.code : 1;
|
|
700
|
+
}
|
|
701
|
+
var COMPLETE = "__complete";
|
|
702
|
+
var FILES = "__files__";
|
|
703
|
+
async function completingOf(run) {
|
|
704
|
+
completion.active = true;
|
|
705
|
+
try {
|
|
706
|
+
await run([]);
|
|
707
|
+
} catch (error) {
|
|
708
|
+
if (error instanceof Completing)
|
|
709
|
+
return error;
|
|
710
|
+
} finally {
|
|
711
|
+
completion.active = false;
|
|
712
|
+
}
|
|
713
|
+
return new Completing({});
|
|
714
|
+
}
|
|
715
|
+
function candidateLine(candidate) {
|
|
716
|
+
return typeof candidate === "string" ? candidate : candidate.join("\t");
|
|
717
|
+
}
|
|
718
|
+
function positionalBeingTyped(before, { options, positionals }) {
|
|
719
|
+
const takesValue = (word) => !word.includes("=") && Object.entries(options).some(([name, { short, type }]) => type === "string" && (word === `--${name}` || word === `-${short}`));
|
|
720
|
+
let index = 0;
|
|
721
|
+
let at = 0;
|
|
722
|
+
while (at < before.length) {
|
|
723
|
+
const word = before[at];
|
|
724
|
+
if (word === "--") {
|
|
725
|
+
index += before.length - at - 1;
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
if (!word.startsWith("-"))
|
|
729
|
+
index++;
|
|
730
|
+
else if (takesValue(word))
|
|
731
|
+
at++;
|
|
732
|
+
at++;
|
|
733
|
+
}
|
|
734
|
+
const last = positionals.at(-1);
|
|
735
|
+
if (index < positionals.length)
|
|
736
|
+
return positionals[index];
|
|
737
|
+
return last?.replace("]", "").endsWith("...") === true ? last : undefined;
|
|
738
|
+
}
|
|
739
|
+
async function leafCandidates(run, words) {
|
|
740
|
+
const completing = await completingOf(run);
|
|
741
|
+
if ((words.at(-1) ?? "").startsWith("-"))
|
|
742
|
+
return Object.entries(completing.options).map(([name, option]) => option.description === undefined ? `--${name}` : `--${name} ${option.description}`);
|
|
743
|
+
const flag = words.at(-2);
|
|
744
|
+
const option = flag?.startsWith("-") === true ? Object.entries(completing.options).find(([name, { short }]) => flag === `--${name}` || flag === `-${short}`)?.[1] : undefined;
|
|
745
|
+
if (option?.type === "string") {
|
|
746
|
+
const values = option.choices === undefined ? await option.candidates?.() : option.choices;
|
|
747
|
+
return values === undefined ? [FILES] : values.map(candidateLine);
|
|
748
|
+
}
|
|
749
|
+
const name = positionalBeingTyped(words.slice(0, -1), completing);
|
|
750
|
+
const values = name === undefined ? undefined : await completing.candidates[name]?.();
|
|
751
|
+
return values === undefined ? [FILES] : values.map(candidateLine);
|
|
752
|
+
}
|
|
753
|
+
async function printCandidates(found) {
|
|
754
|
+
for (const line of await found)
|
|
755
|
+
console.log(line);
|
|
756
|
+
return 0;
|
|
757
|
+
}
|
|
758
|
+
function packageVersion(entry = Bun.main) {
|
|
759
|
+
for (let dir = dirname2(realpathSync(entry));; dir = dirname2(dir)) {
|
|
760
|
+
try {
|
|
761
|
+
const { version } = JSON.parse(readFileSync(join2(dir, "package.json"), "utf8"));
|
|
762
|
+
if (typeof version === "string")
|
|
763
|
+
return version;
|
|
764
|
+
} catch {}
|
|
765
|
+
if (dirname2(dir) === dir)
|
|
766
|
+
return "0.0.0";
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async function runMain({ name, version, run, redact, usage }, argv = process6.argv.slice(2)) {
|
|
770
|
+
if (argv[0] === COMPLETE)
|
|
771
|
+
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
772
|
+
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
773
|
+
console.log(version ?? packageVersion());
|
|
774
|
+
return 0;
|
|
775
|
+
}
|
|
776
|
+
let code;
|
|
777
|
+
try {
|
|
778
|
+
code = await run(argv);
|
|
779
|
+
} catch (error) {
|
|
780
|
+
code = reportThrown(name, error, redact, wantsJson(argv));
|
|
781
|
+
}
|
|
782
|
+
if (usage !== false) {
|
|
783
|
+
try {
|
|
784
|
+
recordUsage(usageEntry(name, undefined, argv, code));
|
|
785
|
+
} catch {}
|
|
786
|
+
}
|
|
787
|
+
return code;
|
|
788
|
+
}
|
|
789
|
+
// ../kit/src/compose.ts
|
|
790
|
+
async function captureOutput(argv, { cwd, env } = {}) {
|
|
791
|
+
const run = Bun.spawn(argv, { ...cwd === undefined ? {} : { cwd }, env: { ...process.env, ...env }, stdin: "ignore", stdout: "pipe", stderr: "pipe" });
|
|
792
|
+
const [stdout, stderr, code] = await Promise.all([run.stdout.text(), run.stderr.text(), run.exited]);
|
|
793
|
+
return { code, stdout, stderr };
|
|
794
|
+
}
|
|
795
|
+
function composeError({ code, stderr }) {
|
|
796
|
+
const last = stderr.trim().split(`
|
|
797
|
+
`).at(-1) ?? "";
|
|
798
|
+
return last === "" ? `exit ${code}` : last;
|
|
799
|
+
}
|
|
800
|
+
// ../kit/src/secrets.ts
|
|
801
|
+
var SECRET_PART = /token|secret|passw|credential|private|dsn|apikey|accesskey|key$/i;
|
|
802
|
+
var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
|
|
803
|
+
var WORD_BREAKS = /[^a-zA-Z\d]+|(?<=[a-z\d])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/;
|
|
804
|
+
function isSecretName(name) {
|
|
805
|
+
return SECRET_PART.test(name) || name.split(WORD_BREAKS).some((word) => SECRET_WORDS.has(word.toLowerCase()));
|
|
806
|
+
}
|
|
807
|
+
// ../kit/src/sessions.ts
|
|
808
|
+
var {Glob } = globalThis.Bun;
|
|
809
|
+
var SESSION_FILES = new Glob("*.json");
|
|
810
|
+
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
811
|
+
// src/dlog.ts
|
|
812
|
+
import process8 from "process";
|
|
813
|
+
|
|
814
|
+
// ../env/src/files.ts
|
|
815
|
+
import { chmod, chown, readdir, realpath, rename, rm, stat, writeFile } from "fs/promises";
|
|
816
|
+
import { basename, dirname as dirname3, join as join3 } from "path";
|
|
817
|
+
|
|
818
|
+
// ../env/src/dotenv.ts
|
|
819
|
+
var ASSIGNMENT = /^\s*(?:export\s+)?([A-Z_][\w.-]*)\s*=(.*)$/i;
|
|
820
|
+
var INLINE_COMMENT = /\s+#.*$/;
|
|
821
|
+
var CARRIAGE_RETURN = /\r$/;
|
|
822
|
+
var ESCAPES = { n: `
|
|
823
|
+
`, r: "\r", t: "\t", '"': '"', "\\": "\\", $: "$" };
|
|
824
|
+
function closingQuote(text, quote) {
|
|
825
|
+
for (let index = 0;index < text.length; index++) {
|
|
826
|
+
if (quote === '"' && text[index] === "\\")
|
|
827
|
+
index++;
|
|
828
|
+
else if (text[index] === quote)
|
|
829
|
+
return index;
|
|
830
|
+
}
|
|
831
|
+
return -1;
|
|
832
|
+
}
|
|
833
|
+
function unescapeDollar(text) {
|
|
834
|
+
return text.replaceAll("\\$", "$");
|
|
835
|
+
}
|
|
836
|
+
function unescapeDouble(text) {
|
|
837
|
+
let out = "";
|
|
838
|
+
for (let index = 0;index < text.length; index++) {
|
|
839
|
+
const next = text[index + 1];
|
|
840
|
+
if (text[index] === "\\" && next !== undefined && next in ESCAPES) {
|
|
841
|
+
out += ESCAPES[next];
|
|
842
|
+
index++;
|
|
843
|
+
} else {
|
|
844
|
+
out += text[index];
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return out;
|
|
848
|
+
}
|
|
849
|
+
function parseDotenv(text) {
|
|
850
|
+
const physical = text.split(`
|
|
851
|
+
`);
|
|
852
|
+
const lines = [];
|
|
853
|
+
for (let index = 0;index < physical.length; index++) {
|
|
854
|
+
const line = physical[index] ?? "";
|
|
855
|
+
const match = ASSIGNMENT.exec(line.replace(CARRIAGE_RETURN, ""));
|
|
856
|
+
if (match === null) {
|
|
857
|
+
lines.push({ raw: line });
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
const [, key = "", rest = ""] = match;
|
|
861
|
+
const start = rest.trimStart();
|
|
862
|
+
const quote = start[0];
|
|
863
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") {
|
|
864
|
+
lines.push({ raw: line, key, value: unescapeDollar(start.replace(INLINE_COMMENT, "").trim()) });
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
let body = start.slice(1);
|
|
868
|
+
let raw = line;
|
|
869
|
+
let end = closingQuote(body, quote);
|
|
870
|
+
while (end === -1 && index + 1 < physical.length) {
|
|
871
|
+
index++;
|
|
872
|
+
body += `
|
|
873
|
+
${(physical[index] ?? "").replace(CARRIAGE_RETURN, "")}`;
|
|
874
|
+
raw += `
|
|
875
|
+
${physical[index] ?? ""}`;
|
|
876
|
+
end = closingQuote(body, quote);
|
|
877
|
+
}
|
|
878
|
+
const inner = end === -1 ? body : body.slice(0, end);
|
|
879
|
+
lines.push({ raw, key, value: quote === '"' ? unescapeDouble(inner) : unescapeDollar(inner) });
|
|
880
|
+
}
|
|
881
|
+
return { lines };
|
|
882
|
+
}
|
|
883
|
+
function entries(file) {
|
|
884
|
+
const map = new Map;
|
|
885
|
+
for (const line of file.lines) {
|
|
886
|
+
if (line.key !== undefined)
|
|
887
|
+
map.set(line.key, line.value ?? "");
|
|
888
|
+
}
|
|
889
|
+
return map;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// ../env/src/mask.ts
|
|
893
|
+
var MIN_KNOWN_LENGTH = 4;
|
|
894
|
+
var KEY_MASK = "*** private key redacted ***";
|
|
895
|
+
var PLAIN_VALUE = /^(?:true|false|null|none|nil|yes|no|on|off|undefined)$/i;
|
|
896
|
+
var PLACEHOLDER = /^[$%{<(*/]/;
|
|
897
|
+
var URL_PASSWORD = /(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,31}:\/\/[^\s:/@]*:([^\s'"]{1,1024})@/dgi;
|
|
898
|
+
var PORT = /^\d{1,5}$/;
|
|
899
|
+
var ESCAPE2 = /[.*+?^${}()|[\]\\]/g;
|
|
900
|
+
function isPlaceholder(value) {
|
|
901
|
+
return PLACEHOLDER.test(value) || PLAIN_VALUE.test(value);
|
|
902
|
+
}
|
|
903
|
+
var TOKEN_FORMATS = [
|
|
904
|
+
{ kind: "GitHub token", pattern: /\b(?:gh[pousr]_|github_pat_)\w{20,}/dg },
|
|
905
|
+
{ kind: "GitLab token", pattern: /\bglpat-[\w-]{20,}/dg },
|
|
906
|
+
{ kind: "API key", pattern: /\bsk-[\w-]{20,}/dg },
|
|
907
|
+
{ kind: "Slack token", pattern: /\bx(?:ox[abprs]|app)-[\w-]{10,}/dg },
|
|
908
|
+
{ kind: "JWT", pattern: /\beyJ[\w-]+\.[\w-]+\.[\w-]+/dg },
|
|
909
|
+
{ kind: "AWS access key", pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/dg },
|
|
910
|
+
{ kind: "npm token", pattern: /\bnpm_[A-Za-z0-9]{30,}/dg },
|
|
911
|
+
{ kind: "Stripe key", pattern: /\b[sr]k_(?:live|test)_[A-Za-z0-9]{10,}/dg },
|
|
912
|
+
{ kind: "Google API key", pattern: /\bAIza[\w-]{35}/dg },
|
|
913
|
+
{ kind: "SendGrid key", pattern: /\bSG\.[\w-]{16,}\.[\w-]{16,}/dg },
|
|
914
|
+
{ kind: "Authorization header", pattern: /\bAuthorization["']?\s*[:=]\s*["']?(?:Bearer|Basic|Token|Digest)\s+([^\s"',;]{8,})/dgi, skip: isPlaceholder },
|
|
915
|
+
{ kind: "Bearer token", pattern: /\bBearer\s+((?=[\w.~+/=-]*\d)[\w.~+/=-]{20,})/dg },
|
|
916
|
+
{ kind: "password", pattern: /(?<![\w-])(?:[\w-]{0,30}[_-])?(?:password|passwd|secret|api[_-]?key)(?:[_-][\w-]{0,30})?=([^\s"'&;,]{4,})/dgi, skip: isPlaceholder },
|
|
917
|
+
{ kind: "password", pattern: /(?<![\w-])["']?(?:[\w-]{0,30}[_-])?(?:password|passwd|secret|api[_-]?key)(?:[_-][\w-]{0,30})?["']?\s*[:=]\s*(["'])((?:\\.|(?!\1)[^\\\n]){4,})\1/dgi, skip: isPlaceholder }
|
|
918
|
+
];
|
|
919
|
+
var PEM_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----/;
|
|
920
|
+
var PEM_END = /-----END [A-Z ]*PRIVATE KEY(?: BLOCK)?-----/;
|
|
921
|
+
var PEM_BODY = /(?<=-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----)(?:(?!-----END [A-Z ]*PRIVATE KEY(?: BLOCK)?-----)[\s\S])+/dg;
|
|
922
|
+
var PEM_BEGIN_ALL = /-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----/g;
|
|
923
|
+
var URL_PARTS = /(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,31}:\/\/([^\s/?#'"<>`]*)([^\s?#'"<>`]*)(?:\?([^\s#'"<>`]*))?/dgi;
|
|
924
|
+
var PATH_SEGMENT = /[^/]+/g;
|
|
925
|
+
var SECRET_PARAM = /^(?:token|[a-z]+_token|key|api[_-]?key|access[_-]?key|secret|[a-z]+_secret|password|passwd|pwd|sig|signature|auth|authorization)$/i;
|
|
926
|
+
var WEBHOOK_FORMATS = [
|
|
927
|
+
["Discord webhook", /(?:^|\.)discord(?:app)?\.com\/api\/(?:v\d+\/)?webhooks\/\d+\/([\w-]+)/di],
|
|
928
|
+
["Slack webhook", /^hooks\.slack\.com\/(?:services|workflows)\/(?:[\w-]+\/)*([\w-]+)/di],
|
|
929
|
+
["Uptime Kuma push URL", /\/api\/push\/([\w-]+)/di],
|
|
930
|
+
["healthchecks.io ping URL", /^hc-ping\.com\/([\w-]+)/di]
|
|
931
|
+
];
|
|
932
|
+
var OPAQUE_SEGMENT = /^(?:(?=[\w-]{20,}$)(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\w-]+|(?=[A-Za-z\d]{32,}$)(?=.*[A-Za-z])(?=.*\d).+)$/;
|
|
933
|
+
function hint(value) {
|
|
934
|
+
const shown = Math.min(3, Math.floor(value.length / 4));
|
|
935
|
+
return `${value.slice(0, shown)}***(${value.length})`;
|
|
936
|
+
}
|
|
937
|
+
function forms(value) {
|
|
938
|
+
return [...new Set([value, encodeURIComponent(value), JSON.stringify(value).slice(1, -1)])];
|
|
939
|
+
}
|
|
940
|
+
function knownSecrets(pairs) {
|
|
941
|
+
const keys = new Map;
|
|
942
|
+
for (const [key, value] of pairs) {
|
|
943
|
+
if (value.length < MIN_KNOWN_LENGTH || PLAIN_VALUE.test(value))
|
|
944
|
+
continue;
|
|
945
|
+
for (const form of forms(value)) {
|
|
946
|
+
if (!keys.has(form))
|
|
947
|
+
keys.set(form, key);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (keys.size === 0)
|
|
951
|
+
return { pattern: undefined, keys };
|
|
952
|
+
const alternatives = [...keys.keys()].sort((a, b) => b.length - a.length).map((value) => value.replace(ESCAPE2, "\\$&"));
|
|
953
|
+
return { pattern: new RegExp(`(?<!\\w)(?:${alternatives.join("|")})(?!\\w)`, "g"), keys };
|
|
954
|
+
}
|
|
955
|
+
function passwordSpans(text) {
|
|
956
|
+
const spans = [];
|
|
957
|
+
for (const url of text.matchAll(URL_PASSWORD)) {
|
|
958
|
+
const [start, end] = url.indices?.[1] ?? [];
|
|
959
|
+
const password = url[1] ?? "";
|
|
960
|
+
const slash = password.indexOf("/");
|
|
961
|
+
if (start !== undefined && end !== undefined && !(slash !== -1 && PORT.test(password.slice(0, slash))))
|
|
962
|
+
spans.push([start, end]);
|
|
963
|
+
}
|
|
964
|
+
return spans;
|
|
965
|
+
}
|
|
966
|
+
function urlPasswords(text) {
|
|
967
|
+
return passwordSpans(text).map(([start, end]) => text.slice(start, end));
|
|
968
|
+
}
|
|
969
|
+
function urlTokens(line) {
|
|
970
|
+
const found = [];
|
|
971
|
+
const add = (kind, start, value) => {
|
|
972
|
+
found.push({ start, end: start + value.length, kind, mask: hint(value) });
|
|
973
|
+
};
|
|
974
|
+
for (const url of line.matchAll(URL_PARTS)) {
|
|
975
|
+
const [authority, path = "", query] = [url[1] ?? "", url[2] ?? "", url[3]];
|
|
976
|
+
const authorityStart = url.indices?.[1]?.[0];
|
|
977
|
+
const pathStart = url.indices?.[2]?.[0];
|
|
978
|
+
const queryStart = url.indices?.[3]?.[0];
|
|
979
|
+
if (authorityStart === undefined || pathStart === undefined)
|
|
980
|
+
continue;
|
|
981
|
+
const hostAt = authority.lastIndexOf("@") + 1;
|
|
982
|
+
const target = authority.slice(hostAt) + path;
|
|
983
|
+
for (const [kind, pattern] of WEBHOOK_FORMATS) {
|
|
984
|
+
const [start, end] = pattern.exec(target)?.indices?.[1] ?? [];
|
|
985
|
+
if (start !== undefined && end !== undefined)
|
|
986
|
+
add(kind, authorityStart + hostAt + start, target.slice(start, end));
|
|
987
|
+
}
|
|
988
|
+
for (const segment of path.matchAll(PATH_SEGMENT)) {
|
|
989
|
+
if (OPAQUE_SEGMENT.test(segment[0]))
|
|
990
|
+
add("URL token", pathStart + segment.index, segment[0]);
|
|
991
|
+
}
|
|
992
|
+
if (query === undefined || queryStart === undefined)
|
|
993
|
+
continue;
|
|
994
|
+
let offset = 0;
|
|
995
|
+
for (const param of query.split("&")) {
|
|
996
|
+
const equals = param.indexOf("=");
|
|
997
|
+
if (equals > 0 && equals < param.length - 1 && SECRET_PARAM.test(param.slice(0, equals)))
|
|
998
|
+
add("URL token", queryStart + offset + equals + 1, param.slice(equals + 1));
|
|
999
|
+
offset += param.length + 1;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return found.filter((match) => match.end > match.start);
|
|
1003
|
+
}
|
|
1004
|
+
function findSecrets(line, known) {
|
|
1005
|
+
const found = [];
|
|
1006
|
+
for (const [start, end] of passwordSpans(line))
|
|
1007
|
+
found.push({ start, end, kind: "URL password", mask: "***" });
|
|
1008
|
+
for (const body of line.matchAll(PEM_BODY)) {
|
|
1009
|
+
const [start, end] = body.indices?.[0] ?? [];
|
|
1010
|
+
if (start !== undefined && end !== undefined)
|
|
1011
|
+
found.push({ start, end, kind: "private key", mask: KEY_MASK });
|
|
1012
|
+
}
|
|
1013
|
+
for (const { kind, pattern, skip } of TOKEN_FORMATS) {
|
|
1014
|
+
for (const token of line.matchAll(pattern)) {
|
|
1015
|
+
const [start, end] = token.indices?.at(-1) ?? [];
|
|
1016
|
+
if (start === undefined || end === undefined)
|
|
1017
|
+
continue;
|
|
1018
|
+
const value = line.slice(start, end);
|
|
1019
|
+
if (!skip?.(value))
|
|
1020
|
+
found.push({ start, end, kind, mask: hint(value) });
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
found.push(...urlTokens(line));
|
|
1024
|
+
if (known?.pattern !== undefined) {
|
|
1025
|
+
for (const value of line.matchAll(known.pattern))
|
|
1026
|
+
found.push({ start: value.index, end: value.index + value[0].length, kind: `value of ${known.keys.get(value[0])}`, mask: hint(value[0]) });
|
|
1027
|
+
}
|
|
1028
|
+
found.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
1029
|
+
const kept = [];
|
|
1030
|
+
for (const match of found) {
|
|
1031
|
+
if (match.start >= (kept.at(-1)?.end ?? 0))
|
|
1032
|
+
kept.push(match);
|
|
1033
|
+
}
|
|
1034
|
+
return kept;
|
|
1035
|
+
}
|
|
1036
|
+
function openKeyBody(line) {
|
|
1037
|
+
const last = [...line.matchAll(PEM_BEGIN_ALL)].at(-1);
|
|
1038
|
+
if (last === undefined)
|
|
1039
|
+
return;
|
|
1040
|
+
const after = line.slice(last.index + last[0].length);
|
|
1041
|
+
return PEM_END.test(after) ? undefined : after.trim();
|
|
1042
|
+
}
|
|
1043
|
+
function maskLine(line, known) {
|
|
1044
|
+
let out = "";
|
|
1045
|
+
let at = 0;
|
|
1046
|
+
for (const match of findSecrets(line, known)) {
|
|
1047
|
+
out += line.slice(at, match.start) + match.mask;
|
|
1048
|
+
at = match.end;
|
|
1049
|
+
}
|
|
1050
|
+
return out + line.slice(at);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// ../env/src/files.ts
|
|
1054
|
+
var ENV_FILE = /^(?:\.env(?:\..+)?|.+\.env|\.envrc)$/;
|
|
1055
|
+
var KNOWN_DEPTH = 3;
|
|
1056
|
+
var SKIPPED = new Set(["node_modules", ".git", "vendor"]);
|
|
1057
|
+
async function isFileEntry(entry, path) {
|
|
1058
|
+
if (entry.isFile())
|
|
1059
|
+
return true;
|
|
1060
|
+
if (!entry.isSymbolicLink())
|
|
1061
|
+
return false;
|
|
1062
|
+
try {
|
|
1063
|
+
return (await stat(path)).isFile();
|
|
1064
|
+
} catch {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
function secretPairs(file) {
|
|
1069
|
+
const pairs = [];
|
|
1070
|
+
for (const [key, value] of entries(file)) {
|
|
1071
|
+
if (isSecretName(key))
|
|
1072
|
+
pairs.push([key, value]);
|
|
1073
|
+
else
|
|
1074
|
+
pairs.push(...urlPasswords(value).map((password) => [key, password]));
|
|
1075
|
+
}
|
|
1076
|
+
return pairs;
|
|
1077
|
+
}
|
|
1078
|
+
async function envFilesBelow(folder, depth = 0) {
|
|
1079
|
+
const found = [];
|
|
1080
|
+
let children;
|
|
1081
|
+
try {
|
|
1082
|
+
children = await readdir(folder, { withFileTypes: true });
|
|
1083
|
+
} catch {
|
|
1084
|
+
return found;
|
|
1085
|
+
}
|
|
1086
|
+
for (const entry of children) {
|
|
1087
|
+
const path = join3(folder, entry.name);
|
|
1088
|
+
if (entry.isDirectory() && depth < KNOWN_DEPTH && !SKIPPED.has(entry.name))
|
|
1089
|
+
found.push(...await envFilesBelow(path, depth + 1));
|
|
1090
|
+
else if (ENV_FILE.test(entry.name) && await isFileEntry(entry, path))
|
|
1091
|
+
found.push(path);
|
|
1092
|
+
}
|
|
1093
|
+
return found;
|
|
1094
|
+
}
|
|
1095
|
+
async function loadKnown(folder = process.cwd(), extra = []) {
|
|
1096
|
+
const pairs = extra.flatMap(secretPairs);
|
|
1097
|
+
for (const path of await envFilesBelow(folder)) {
|
|
1098
|
+
try {
|
|
1099
|
+
pairs.push(...secretPairs(parseDotenv(await Bun.file(path).text())));
|
|
1100
|
+
} catch {}
|
|
1101
|
+
}
|
|
1102
|
+
return knownSecrets(pairs);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/stack.ts
|
|
1106
|
+
import { existsSync, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
1107
|
+
import { dirname as dirname4, join as join4, relative, resolve } from "path";
|
|
1108
|
+
|
|
1109
|
+
// ../env/src/redactor.ts
|
|
1110
|
+
var MAX_PENDING = 1 << 18;
|
|
1111
|
+
var OVERLAP = 1 << 13;
|
|
1112
|
+
|
|
1113
|
+
class Redactor {
|
|
1114
|
+
known;
|
|
1115
|
+
pending = "";
|
|
1116
|
+
inKey = false;
|
|
1117
|
+
hidden = false;
|
|
1118
|
+
constructor(known) {
|
|
1119
|
+
this.known = known;
|
|
1120
|
+
}
|
|
1121
|
+
push(chunk) {
|
|
1122
|
+
const end = chunk.lastIndexOf(`
|
|
1123
|
+
`);
|
|
1124
|
+
this.pending += chunk;
|
|
1125
|
+
if (end === -1)
|
|
1126
|
+
return this.pending.length > MAX_PENDING ? this.cut() : "";
|
|
1127
|
+
const split = this.pending.length - chunk.length + end + 1;
|
|
1128
|
+
const complete = this.pending.slice(0, split);
|
|
1129
|
+
this.pending = this.pending.slice(split);
|
|
1130
|
+
return complete.split(`
|
|
1131
|
+
`).slice(0, -1).map((line) => this.line(line)).filter((line) => line !== undefined).map((line) => `${line}
|
|
1132
|
+
`).join("");
|
|
1133
|
+
}
|
|
1134
|
+
cut() {
|
|
1135
|
+
const limit = this.pending.length - OVERLAP;
|
|
1136
|
+
const space = this.pending.lastIndexOf(" ", limit);
|
|
1137
|
+
const at = space > 0 ? space + 1 : limit;
|
|
1138
|
+
const head = this.pending.slice(0, at);
|
|
1139
|
+
this.pending = this.pending.slice(at);
|
|
1140
|
+
return this.line(head) ?? "";
|
|
1141
|
+
}
|
|
1142
|
+
end() {
|
|
1143
|
+
const rest = this.pending;
|
|
1144
|
+
this.pending = "";
|
|
1145
|
+
return rest === "" ? "" : this.line(rest) ?? "";
|
|
1146
|
+
}
|
|
1147
|
+
line(line) {
|
|
1148
|
+
if (this.inKey) {
|
|
1149
|
+
const end = PEM_END.exec(line);
|
|
1150
|
+
if (end === null) {
|
|
1151
|
+
if (this.hidden)
|
|
1152
|
+
return;
|
|
1153
|
+
this.hidden = true;
|
|
1154
|
+
return KEY_MASK;
|
|
1155
|
+
}
|
|
1156
|
+
this.inKey = false;
|
|
1157
|
+
return (end.index > 0 && !this.hidden ? KEY_MASK : "") + this.line(line.slice(end.index));
|
|
1158
|
+
}
|
|
1159
|
+
const body = openKeyBody(line);
|
|
1160
|
+
if (body !== undefined) {
|
|
1161
|
+
this.inKey = true;
|
|
1162
|
+
this.hidden = body !== "";
|
|
1163
|
+
}
|
|
1164
|
+
return maskLine(line, this.known);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/stack.ts
|
|
1169
|
+
var LABEL_FORMAT = ["project", "project.config_files", "project.environment_file", "project.working_dir"].map((label) => `{{.Label "com.docker.compose.${label}"}}`).join("\t");
|
|
1170
|
+
var COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m;
|
|
1171
|
+
var COMPOSE_FILES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
|
|
1172
|
+
var SKIPPED2 = new Set(["node_modules", "vendor", "dist", "build", "target", "venv", "__pycache__"]);
|
|
1173
|
+
var ACTIONS = ["shell", "logs", "restart", "up", "stop", "build"];
|
|
1174
|
+
function from(dir, command) {
|
|
1175
|
+
return dir === "." ? command : ["env", "-C", dir, ...command];
|
|
1176
|
+
}
|
|
1177
|
+
function children(dir = ".") {
|
|
1178
|
+
return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED2.has(child.name)).map((child) => child.name).sort();
|
|
1179
|
+
}
|
|
1180
|
+
function up(dir) {
|
|
1181
|
+
const dirs = [];
|
|
1182
|
+
for (let current = dir;; current = join4(current, "..")) {
|
|
1183
|
+
dirs.push(current);
|
|
1184
|
+
const absolute = resolve(current);
|
|
1185
|
+
if (existsSync(join4(current, ".git")) || dirname4(absolute) === absolute)
|
|
1186
|
+
return dirs;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
function stackAt(dir) {
|
|
1190
|
+
return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd: resolve(dir), env: process.env } };
|
|
1191
|
+
}
|
|
1192
|
+
function holdsStack(dir) {
|
|
1193
|
+
if (COMPOSE_FILES.some((name) => existsSync(join4(dir, name))))
|
|
1194
|
+
return true;
|
|
1195
|
+
try {
|
|
1196
|
+
return COMPOSE_FILE_VAR.test(readFileSync2(join4(dir, ".env"), "utf-8"));
|
|
1197
|
+
} catch {
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function nearestStack(dir) {
|
|
1202
|
+
const found = up(dir).find(holdsStack);
|
|
1203
|
+
return found === undefined ? undefined : stackAt(found);
|
|
1204
|
+
}
|
|
1205
|
+
function stacks() {
|
|
1206
|
+
const here = nearestStack(".");
|
|
1207
|
+
const below = children().filter(holdsStack);
|
|
1208
|
+
return [...here ? [here] : [], ...below.filter((dir) => dir !== here?.dir).map(stackAt)];
|
|
1209
|
+
}
|
|
1210
|
+
function labelled(text) {
|
|
1211
|
+
const projects = new Map;
|
|
1212
|
+
for (const line of text.split(`
|
|
1213
|
+
`)) {
|
|
1214
|
+
const [project, files, envFiles = "", workdir = ""] = line.split("\t");
|
|
1215
|
+
if (project !== undefined && project !== "" && files !== undefined && files !== "" && workdir !== "" && !projects.has(project))
|
|
1216
|
+
projects.set(project, { project, files: files.split(","), envFiles: envFiles.split(",").filter(Boolean), workdir });
|
|
1217
|
+
}
|
|
1218
|
+
return [...projects.values()];
|
|
1219
|
+
}
|
|
1220
|
+
async function labels(project) {
|
|
1221
|
+
const filter = project === undefined ? [] : ["--filter", `label=com.docker.compose.project=${project}`];
|
|
1222
|
+
const proc = Bun.spawn(["docker", "ps", "--all", ...filter, "--format", LABEL_FORMAT], { stdout: "pipe", stderr: "ignore" });
|
|
1223
|
+
if (await proc.exited !== 0)
|
|
1224
|
+
return [];
|
|
1225
|
+
return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) => existsSync(file)));
|
|
1226
|
+
}
|
|
1227
|
+
function labelledStack(dir, { project, files, envFiles, workdir }) {
|
|
1228
|
+
const options = (paths) => ["-p", project, ...files.flatMap((file) => ["-f", paths(file)]), ...envFiles.flatMap((file) => ["--env-file", paths(file)])];
|
|
1229
|
+
return {
|
|
1230
|
+
dir,
|
|
1231
|
+
project,
|
|
1232
|
+
run: from(dir, ["docker", "compose", ...options((file) => relative(workdir, file))]),
|
|
1233
|
+
list: { argv: ["docker", "compose", ...options((file) => file)], cwd: workdir, env: process.env }
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
function stackOf(recorded) {
|
|
1237
|
+
return labelledStack(relative(".", recorded.workdir) || ".", recorded);
|
|
1238
|
+
}
|
|
1239
|
+
async function recordedNearby() {
|
|
1240
|
+
const dirs = new Set([...up("."), ...children()].map((dir) => resolve(dir)));
|
|
1241
|
+
return (await labels()).filter((stack) => dirs.has(stack.workdir));
|
|
1242
|
+
}
|
|
1243
|
+
async function attempt(stack, args) {
|
|
1244
|
+
const result = await captureOutput([...stack.list.argv, ...args], { cwd: stack.list.cwd, env: stack.list.env });
|
|
1245
|
+
return result.code === 0 ? { text: result.stdout, error: undefined } : { text: "", error: composeError(result) };
|
|
1246
|
+
}
|
|
1247
|
+
async function output(stack, args) {
|
|
1248
|
+
return (await attempt(stack, args)).text;
|
|
1249
|
+
}
|
|
1250
|
+
function containers(text) {
|
|
1251
|
+
const trimmed = text.trim();
|
|
1252
|
+
if (!trimmed)
|
|
1253
|
+
return [];
|
|
1254
|
+
return trimmed.startsWith("[") ? JSON.parse(trimmed) : trimmed.split(`
|
|
1255
|
+
`).map((line) => JSON.parse(line));
|
|
1256
|
+
}
|
|
1257
|
+
async function services(stack) {
|
|
1258
|
+
const [config, ps] = await Promise.all([output(stack, ["config", "--format", "json"]), output(stack, ["ps", "--all", "--format", "json"])]);
|
|
1259
|
+
const parsed = config ? JSON.parse(config) : {};
|
|
1260
|
+
const declared = parsed.services ?? {};
|
|
1261
|
+
const project = stack.project ?? parsed.name ?? "";
|
|
1262
|
+
const running = new Map(containers(ps).map((container) => [container.Service, container]));
|
|
1263
|
+
return Object.entries(declared).map(([name, service]) => ({
|
|
1264
|
+
stack,
|
|
1265
|
+
project,
|
|
1266
|
+
name,
|
|
1267
|
+
state: running.get(name)?.State ?? "none",
|
|
1268
|
+
status: running.get(name)?.Status ?? "",
|
|
1269
|
+
ports: (service.ports ?? []).filter((port) => (port.published ?? "") !== "").map((port) => `${port.published}->${port.target}`).join(" "),
|
|
1270
|
+
image: service.image ?? "built"
|
|
1271
|
+
}));
|
|
1272
|
+
}
|
|
1273
|
+
async function allServices() {
|
|
1274
|
+
const [listed, recorded] = await Promise.all([Promise.all(stacks().map(services)), recordedNearby()]);
|
|
1275
|
+
const found = listed.flat();
|
|
1276
|
+
const known = new Set(found.map((service) => service.project));
|
|
1277
|
+
const others = recorded.filter((stack) => !known.has(stack.project));
|
|
1278
|
+
return [...found, ...(await Promise.all(others.map(async (stack) => services(stackOf(stack))))).flat()];
|
|
1279
|
+
}
|
|
1280
|
+
function named(service) {
|
|
1281
|
+
return service.stack.project === undefined ? service.name : `${service.stack.project}:${service.name}`;
|
|
1282
|
+
}
|
|
1283
|
+
function key(service) {
|
|
1284
|
+
return service.stack.dir === "." ? named(service) : `${service.stack.dir}/${named(service)}`;
|
|
1285
|
+
}
|
|
1286
|
+
async function shellOf(service) {
|
|
1287
|
+
return await output(service.stack, ["exec", "-T", service.name, "sh", "-c", "command -v bash"]) ? "bash" : "sh";
|
|
1288
|
+
}
|
|
1289
|
+
var LOGS_TAIL = 200;
|
|
1290
|
+
async function commandFor(service, action, logs = {}) {
|
|
1291
|
+
const run = service.stack.run;
|
|
1292
|
+
switch (action) {
|
|
1293
|
+
case "shell":
|
|
1294
|
+
return [...run, "exec", service.name, await shellOf(service)];
|
|
1295
|
+
case "logs":
|
|
1296
|
+
return [...run, "logs", ...logs.follow === false ? [] : ["-f"], `--tail=${logs.tail ?? LOGS_TAIL}`, service.name];
|
|
1297
|
+
case "restart":
|
|
1298
|
+
return [...run, "restart", service.name];
|
|
1299
|
+
case "up":
|
|
1300
|
+
return [...run, "up", "-d", service.name];
|
|
1301
|
+
case "stop":
|
|
1302
|
+
return [...run, "stop", service.name];
|
|
1303
|
+
case "build":
|
|
1304
|
+
return [...run, "up", "-d", "--build", service.name];
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function defaultAction(service) {
|
|
1308
|
+
return service.state === "running" ? "shell" : "up";
|
|
1309
|
+
}
|
|
1310
|
+
function stateColor(state) {
|
|
1311
|
+
if (state === "running")
|
|
1312
|
+
return "green";
|
|
1313
|
+
if (state === "exited" || state === "dead")
|
|
1314
|
+
return "red";
|
|
1315
|
+
return state === "none" ? "dim" : "yellow";
|
|
1316
|
+
}
|
|
1317
|
+
async function serviceNamed(dir, name, project) {
|
|
1318
|
+
const [recorded] = project === "" ? [] : await labels(project);
|
|
1319
|
+
const stack = recorded === undefined ? stackAt(dir) : labelledStack(dir, recorded);
|
|
1320
|
+
return (await services(stack)).find((candidate) => candidate.name === name);
|
|
1321
|
+
}
|
|
1322
|
+
function maskLog(text, known) {
|
|
1323
|
+
const redactor = new Redactor(known);
|
|
1324
|
+
return redactor.push(text) + redactor.end();
|
|
1325
|
+
}
|
|
1326
|
+
async function logsOf(stack, name) {
|
|
1327
|
+
return maskLog(await output(stack, ["logs", "--tail=30", "--no-log-prefix", name]), await loadKnown());
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// src/dlog.ts
|
|
1331
|
+
var usage2 = `dlog [service...]
|
|
1332
|
+
|
|
1333
|
+
One line per compose service with its state and health, then the last distinct error lines of the services that log some or are not healthy, repeats collapsed to one line with a count and secrets masked as redact does.
|
|
1334
|
+
With service names, only those are read. The stacks are the ones dx lists: this folder's, its child folders' and the ones only their containers tell of.
|
|
1335
|
+
\`--level warn\` keeps warnings too, \`--grep <regex>\` keeps the lines matching a case insensitive regex instead of the error words. What fails the exit is \`--fail-on\`: by default a service down, unhealthy or still starting, an error line or logs compose could not read, so it can gate a script; \`--fail-on down\` leaves out an old error line that \`--since\` has not aged out yet. A service that exited with code 0 (a migration, an init) is not down.`;
|
|
1336
|
+
var EXITS = [[0, "nothing the --fail-on list names: every service is up and quiet"], [1, "a service is down, unhealthy or still starting, logged a matching line or has logs compose could not read (as --fail-on keeps them), or there is no compose service here"], [2, "a usage error"]];
|
|
1337
|
+
var FAIL_ON = ["down", "unhealthy", "errors"];
|
|
1338
|
+
var options = {
|
|
1339
|
+
lines: { type: "string", short: "n", default: "5", description: "error lines kept per service" },
|
|
1340
|
+
since: { type: "string", default: "1h", description: "how far back the logs are read (compose --since: 30m, 2h, a timestamp)" },
|
|
1341
|
+
level: { type: "string", choices: ["error", "warn"], default: "error", description: "lowest level of the lines kept: error (the default) or warn, warnings too" },
|
|
1342
|
+
grep: { type: "string", description: "keep the lines matching this regex, case insensitive, instead of the --level words" },
|
|
1343
|
+
"fail-on": { type: "string", multiple: true, choices: FAIL_ON, description: "what makes the exit 1, repeatable: down, unhealthy or errors (all three)" },
|
|
1344
|
+
all: { type: "boolean", short: "a", description: "the last log lines of a service with no error too" },
|
|
1345
|
+
json: jsonOption("[{ key, state, health, status, errors: [{ line, count }], tail, down, unhealthy }]")
|
|
1346
|
+
};
|
|
1347
|
+
var ERROR = /\b(?:error|err|fatal|exception|panic|critical|crit|traceback|failed|failure|refused|denied|timeout|timed out|oom|killed)\b/i;
|
|
1348
|
+
var WARNING = /\b(?:warn|warning|deprecated|deprecation)\b/i;
|
|
1349
|
+
var LEVELS = { error: ERROR, warn: new RegExp(`${ERROR.source}|${WARNING.source}`, "i") };
|
|
1350
|
+
var TAIL = 500;
|
|
1351
|
+
var MAX_WIDTH = 220;
|
|
1352
|
+
var TIMESTAMP = /\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[.,]\d+)?(?:Z|[+-]\d{2}:?\d{2})?|\d{2}[/-][A-Za-z]{3}[/-]\d{4}:\d{2}:\d{2}:\d{2}(?: [+-]\d{4})?|\d{2}:\d{2}:\d{2}(?:[.,]\d+)?/g;
|
|
1353
|
+
var IDENTIFIER = /\b(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|0x[0-9a-f]+|[0-9a-f]{12,})\b/gi;
|
|
1354
|
+
var NUMBER = /\d+/g;
|
|
1355
|
+
var STATUS_HEALTH = /\((healthy|unhealthy|health: starting)\)/;
|
|
1356
|
+
var LEADING_BRACKETS = /^(?:\[\s*\]|\s)+/;
|
|
1357
|
+
function shape(line) {
|
|
1358
|
+
return line.replace(TIMESTAMP, "").replace(IDENTIFIER, "#").replace(NUMBER, "#").replace(LEADING_BRACKETS, "").trim();
|
|
1359
|
+
}
|
|
1360
|
+
function clip(line) {
|
|
1361
|
+
return line.length > MAX_WIDTH ? `${line.slice(0, MAX_WIDTH - 1)}\u2026` : line;
|
|
1362
|
+
}
|
|
1363
|
+
function errorLines(log, limit, known, matches = ERROR) {
|
|
1364
|
+
const seen = new Map;
|
|
1365
|
+
for (const raw of log.split(`
|
|
1366
|
+
`)) {
|
|
1367
|
+
const line = stripAnsi(raw).trim();
|
|
1368
|
+
if (line === "" || !matches.test(line))
|
|
1369
|
+
continue;
|
|
1370
|
+
const shared = shape(line);
|
|
1371
|
+
const before = seen.get(shared);
|
|
1372
|
+
seen.delete(shared);
|
|
1373
|
+
seen.set(shared, { line: clip(maskLine(line, known)), count: (before?.count ?? 0) + 1 });
|
|
1374
|
+
}
|
|
1375
|
+
return [...seen.values()].slice(-limit);
|
|
1376
|
+
}
|
|
1377
|
+
function isUp(state, container) {
|
|
1378
|
+
return state === "running" || state === "exited" && container?.ExitCode === 0;
|
|
1379
|
+
}
|
|
1380
|
+
function healthOf(state, container) {
|
|
1381
|
+
const health = (container?.Health ?? "") || (STATUS_HEALTH.exec(container?.Status ?? "")?.[1]?.replace("health: ", "") ?? "");
|
|
1382
|
+
return { health, fine: isUp(state, container) && (health === "" || health === "healthy") };
|
|
1383
|
+
}
|
|
1384
|
+
function needsLook(report, failOn) {
|
|
1385
|
+
return failOn.has("down") && (report.down || report.unreadable !== undefined) || failOn.has("unhealthy") && report.unhealthy || failOn.has("errors") && (report.errors.length > 0 || report.unreadable !== undefined);
|
|
1386
|
+
}
|
|
1387
|
+
function matcherOf(level, grep) {
|
|
1388
|
+
if (grep === undefined)
|
|
1389
|
+
return LEVELS[level];
|
|
1390
|
+
try {
|
|
1391
|
+
return new RegExp(grep, "i");
|
|
1392
|
+
} catch {
|
|
1393
|
+
return badUsage(`--grep takes a regex, ${grep} is not one`);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
async function read(service, since) {
|
|
1397
|
+
const [ps, logs] = await Promise.all([
|
|
1398
|
+
attempt(service.stack, ["ps", "--all", "--format", "json", service.name]),
|
|
1399
|
+
attempt(service.stack, ["logs", "--no-color", "--no-log-prefix", `--tail=${TAIL}`, `--since=${since}`, service.name])
|
|
1400
|
+
]);
|
|
1401
|
+
return { log: logs.text, container: containers(ps.text)[0], unreadable: ps.error ?? logs.error };
|
|
1402
|
+
}
|
|
1403
|
+
function withoutKeys(lines) {
|
|
1404
|
+
let inKey = false;
|
|
1405
|
+
return lines.filter((text) => {
|
|
1406
|
+
const opens = PEM_BEGIN.test(text);
|
|
1407
|
+
const hidden = inKey || opens;
|
|
1408
|
+
inKey = (inKey || opens) && !PEM_END.test(text);
|
|
1409
|
+
return !hidden;
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
function line(report) {
|
|
1413
|
+
const colors = paint(process8.stdout);
|
|
1414
|
+
const status = report.status.replace(STATUS_HEALTH, "").trim();
|
|
1415
|
+
const detail = [report.health, status].filter(Boolean).join(" \xB7 ");
|
|
1416
|
+
return `${report.key.padEnd(24)} ${colors[stateColor(report.state)](report.state.padEnd(9))} ${colors.dim(detail)}`.trimEnd();
|
|
1417
|
+
}
|
|
1418
|
+
function show(reports) {
|
|
1419
|
+
const colors = paint(process8.stdout);
|
|
1420
|
+
for (const report of reports) {
|
|
1421
|
+
console.log(line(report));
|
|
1422
|
+
if (report.unreadable !== undefined)
|
|
1423
|
+
console.log(` ${colors.red(`logs unavailable: ${report.unreadable}`)}`);
|
|
1424
|
+
for (const { line: text, count } of report.errors)
|
|
1425
|
+
console.log(` ${colors.red(text)}${count > 1 ? colors.dim(` \xD7${count}`) : ""}`);
|
|
1426
|
+
for (const text of report.tail)
|
|
1427
|
+
console.log(` ${colors.dim(text)}`);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
async function dlog(args) {
|
|
1431
|
+
const parsed = parse(args, usage2, options, { exits: EXITS, replaces: "docker compose ps, logs --tail | grep -i error and docker inspect for health", examples: ["dlog", ["dlog php db", "only those services"], ["dlog -n 10 --since 15m", "ten error lines of the last quarter of an hour"], ["dlog --level warn", "warnings too"], ['dlog --grep "timeout|5\\d\\d"', "the lines matching a regex"], ["dlog --fail-on down --fail-on unhealthy", "a gate that ignores old errors"], ["dlog --json", "the report as JSON"]] });
|
|
1432
|
+
const limit = wholeNumber(parsed.values.lines, "lines", 5);
|
|
1433
|
+
const matches = matcherOf(parsed.values.level === "warn" ? "warn" : "error", parsed.values.grep);
|
|
1434
|
+
const failOn = new Set((parsed.values["fail-on"] ?? []).length > 0 ? parsed.values["fail-on"] ?? FAIL_ON : FAIL_ON);
|
|
1435
|
+
const known = await loadKnown();
|
|
1436
|
+
const services = await allServices();
|
|
1437
|
+
if (services.length === 0)
|
|
1438
|
+
fail("no compose service here", 1);
|
|
1439
|
+
const wanted = parsed.positionals;
|
|
1440
|
+
const unknown = wanted.filter((name) => !services.some((service) => key(service) === name || service.name === name));
|
|
1441
|
+
if (unknown.length > 0)
|
|
1442
|
+
fail(`no service named ${unknown.join(", ")}`, 1);
|
|
1443
|
+
const chosen = wanted.length === 0 ? services : services.filter((service) => wanted.includes(key(service)) || wanted.includes(service.name));
|
|
1444
|
+
const reports = await Promise.all(chosen.map(async (service) => {
|
|
1445
|
+
const { log, container, unreadable } = await read(service, parsed.values.since);
|
|
1446
|
+
const { health, fine } = healthOf(service.state, container);
|
|
1447
|
+
const lines = withoutKeys(log.split(`
|
|
1448
|
+
`));
|
|
1449
|
+
const errors = errorLines(lines.join(`
|
|
1450
|
+
`), limit, known, matches);
|
|
1451
|
+
const tail = parsed.values.all === true && errors.length === 0 ? lines.map((text) => clip(maskLine(stripAnsi(text).trim(), known))).filter(Boolean).slice(-limit) : [];
|
|
1452
|
+
return { key: key(service), state: service.state, health, status: service.status, errors, tail, unreadable, fine, down: !isUp(service.state, container), unhealthy: isUp(service.state, container) && !fine };
|
|
1453
|
+
}));
|
|
1454
|
+
if (parsed.values.json === true)
|
|
1455
|
+
printJson(reports);
|
|
1456
|
+
else
|
|
1457
|
+
show(reports);
|
|
1458
|
+
const down = reports.filter((report) => needsLook(report, failOn));
|
|
1459
|
+
if (parsed.values.json !== true && down.length > 0)
|
|
1460
|
+
console.error(paint(process8.stderr).dim(`${down.length} of ${reports.length} services need a look: ${down.map((report) => report.key).join(", ")}`));
|
|
1461
|
+
return down.length > 0 ? 1 : 0;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// src/dlog-cli.ts
|
|
1465
|
+
process9.exitCode = await runMain({ name: "dlog", run: dlog });
|