@kaynooo/cli-docker 0.2.0 → 0.4.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 +7 -1
- package/dist/dc-cli.js +14 -3
- package/dist/dlog-cli.js +1164 -0
- package/dist/dx-cli.js +103 -57
- package/dist/sf-cli.js +1339 -0
- package/package.json +10 -5
- package/src/dlog-cli.ts +8 -0
- package/src/dlog.ts +179 -0
- package/src/dx.ts +5 -5
- package/src/picker.ts +15 -45
- package/src/sf-cli.ts +8 -0
- package/src/sf.ts +368 -0
- package/src/stack.ts +19 -5
- package/src/history.ts +0 -48
package/dist/dlog-cli.js
ADDED
|
@@ -0,0 +1,1164 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/dlog-cli.ts
|
|
5
|
+
import process9 from "process";
|
|
6
|
+
|
|
7
|
+
// ../kit/src/args.ts
|
|
8
|
+
import process4 from "process";
|
|
9
|
+
import { parseArgs } from "util";
|
|
10
|
+
|
|
11
|
+
// ../kit/src/style.ts
|
|
12
|
+
import process2 from "process";
|
|
13
|
+
|
|
14
|
+
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
|
|
15
|
+
function x(s) {
|
|
16
|
+
let r = s.startsWith("#") ? s.slice(1).toLowerCase() : s.toLowerCase();
|
|
17
|
+
if (!/^[a-f0-9]+$/.test(r))
|
|
18
|
+
return [0, 0, 0];
|
|
19
|
+
let t = r.length, n = (c) => Number.parseInt(c, 16);
|
|
20
|
+
if (t === 3 || t === 4) {
|
|
21
|
+
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;
|
|
22
|
+
return g !== undefined ? [c, u, h, g] : [c, u, h];
|
|
23
|
+
}
|
|
24
|
+
if (t !== 6 && t !== 8)
|
|
25
|
+
throw Error(`Invalid hex color length: ${t}`);
|
|
26
|
+
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;
|
|
27
|
+
if ([a, b, o].some(Number.isNaN) || y !== undefined && Number.isNaN(y))
|
|
28
|
+
throw Error(`Invalid hex color value: ${s}`);
|
|
29
|
+
return y !== undefined ? [a, b, o, y] : [a, b, o];
|
|
30
|
+
}
|
|
31
|
+
function A(s) {
|
|
32
|
+
let [r, t, n, a] = s;
|
|
33
|
+
if (a)
|
|
34
|
+
return `#${(r << 24 | t << 16 | n << 8 | Math.round((a ?? 1) * 255)).toString(16).padStart(8, "0").toUpperCase()}`;
|
|
35
|
+
return `#${(r << 16 | t << 8 | n).toString(16).padStart(6, "0").toUpperCase()}`;
|
|
36
|
+
}
|
|
37
|
+
function R(s, r, t) {
|
|
38
|
+
s = (s % 360 + 360) % 360, t /= 100;
|
|
39
|
+
let n = r * Math.min(t, 1 - t) / 100, a = (b) => {
|
|
40
|
+
let o = (b + s / 30) % 12, y = t - n * Math.max(Math.min(o - 3, 9 - o, 1), -1);
|
|
41
|
+
return Math.round(255 * y);
|
|
42
|
+
};
|
|
43
|
+
return [a(0), a(8), a(4)];
|
|
44
|
+
}
|
|
45
|
+
function k(s) {
|
|
46
|
+
let [r, t, n] = s;
|
|
47
|
+
r /= 255, t /= 255, n /= 255;
|
|
48
|
+
let a = Math.max(r, t, n), b = Math.min(r, t, n), o = 0, y = 0, c = (a + b) / 2;
|
|
49
|
+
if (a !== b) {
|
|
50
|
+
let u = a - b;
|
|
51
|
+
if (y = c > 0 && c < 1 ? u / (1 - Math.abs(2 * c - 1)) : 0, a === r)
|
|
52
|
+
o = (t - n) / u + (t < n ? 6 : 0);
|
|
53
|
+
else if (a === t)
|
|
54
|
+
o = (n - r) / u + 2;
|
|
55
|
+
else
|
|
56
|
+
o = (r - t) / u + 4;
|
|
57
|
+
o /= 6;
|
|
58
|
+
}
|
|
59
|
+
return [Math.round(o * 360), Math.round(y * 100), Math.round(c * 100)];
|
|
60
|
+
}
|
|
61
|
+
function H(s, r, t) {
|
|
62
|
+
s = (s % 360 + 360) % 360, r /= 100, t /= 100;
|
|
63
|
+
let n = (a) => {
|
|
64
|
+
let b = (a + s / 60) % 6, o = t - t * r * Math.max(Math.min(b, 4 - b, 1), 0);
|
|
65
|
+
return Math.round(255 * o);
|
|
66
|
+
};
|
|
67
|
+
return [n(5), n(3), n(1)];
|
|
68
|
+
}
|
|
69
|
+
function f(s) {
|
|
70
|
+
let [r, t, n] = s;
|
|
71
|
+
r /= 255, t /= 255, n /= 255;
|
|
72
|
+
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;
|
|
73
|
+
if (o !== 0) {
|
|
74
|
+
if (a === r)
|
|
75
|
+
y = (t - n) / o + (t < n ? 6 : 0);
|
|
76
|
+
else if (a === t)
|
|
77
|
+
y = (n - r) / o + 2;
|
|
78
|
+
else
|
|
79
|
+
y = (r - t) / o + 4;
|
|
80
|
+
y /= 6;
|
|
81
|
+
}
|
|
82
|
+
return [Math.round(y * 360), Math.round(c * 100), Math.round(u * 100)];
|
|
83
|
+
}
|
|
84
|
+
function O(s) {
|
|
85
|
+
let r = s[0] / 255, t = s[1] / 255, n = s[2] / 255, a = 1 - Math.max(r, t, n);
|
|
86
|
+
if (a === 1)
|
|
87
|
+
return [0, 0, 0, 100];
|
|
88
|
+
let b = (1 - r - a) / (1 - a), o = (1 - t - a) / (1 - a), y = (1 - n - a) / (1 - a);
|
|
89
|
+
return [Math.round(b * 100), Math.round(o * 100), Math.round(y * 100), Math.round(a * 100)];
|
|
90
|
+
}
|
|
91
|
+
function v(s, r, t, n) {
|
|
92
|
+
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))];
|
|
93
|
+
}
|
|
94
|
+
var m = (s) => s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
95
|
+
var p = (s) => s <= 0.0031308 ? 12.92 * s : 1.055 * s ** 0.4166666666666667 - 0.055;
|
|
96
|
+
var i = (s, r) => Math.round(s * 10 ** r) / 10 ** r;
|
|
97
|
+
function T(s) {
|
|
98
|
+
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);
|
|
99
|
+
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)];
|
|
100
|
+
}
|
|
101
|
+
function M(s, r, t) {
|
|
102
|
+
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)));
|
|
103
|
+
return [u(o), u(y), u(c)];
|
|
104
|
+
}
|
|
105
|
+
function d(s) {
|
|
106
|
+
let [r, t, n] = T(s), a = Math.sqrt(t * t + n * n), b = (Math.atan2(n, t) * 180 / Math.PI + 360) % 360;
|
|
107
|
+
return [i(r, 4), i(a, 4), i(b, 2)];
|
|
108
|
+
}
|
|
109
|
+
function C(s, r, t) {
|
|
110
|
+
let n = t * Math.PI / 180;
|
|
111
|
+
return M(s, r * Math.cos(n), r * Math.sin(n));
|
|
112
|
+
}
|
|
113
|
+
function l(s) {
|
|
114
|
+
let r = s / 255;
|
|
115
|
+
return r <= 0.03928 ? r / 12.92 : ((r + 0.055) / 1.055) ** 2.4;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
class e {
|
|
119
|
+
r;
|
|
120
|
+
g;
|
|
121
|
+
b;
|
|
122
|
+
a;
|
|
123
|
+
constructor(s, r, t, n = 1) {
|
|
124
|
+
this.r = s, this.g = r, this.b = t, this.a = n;
|
|
125
|
+
}
|
|
126
|
+
static from(s, r) {
|
|
127
|
+
if (s === "hex") {
|
|
128
|
+
let [t, n, a, b] = x(r);
|
|
129
|
+
return new e(t, n, a, b);
|
|
130
|
+
}
|
|
131
|
+
if (s === "rgb") {
|
|
132
|
+
if (Array.isArray(r)) {
|
|
133
|
+
let [o, y, c, u] = r;
|
|
134
|
+
return new e(o, y, c, u);
|
|
135
|
+
}
|
|
136
|
+
let { r: t, g: n, b: a, a: b } = r;
|
|
137
|
+
return new e(t, n, a, b);
|
|
138
|
+
}
|
|
139
|
+
if (s === "hsl") {
|
|
140
|
+
let t, n, a, b;
|
|
141
|
+
if (Array.isArray(r))
|
|
142
|
+
[t, n, a, b] = r;
|
|
143
|
+
else
|
|
144
|
+
({ h: t, s: n, l: a, a: b } = r);
|
|
145
|
+
let [o, y, c] = R(t, n, a);
|
|
146
|
+
return new e(o, y, c, b);
|
|
147
|
+
}
|
|
148
|
+
if (s === "hsv") {
|
|
149
|
+
let t, n, a, b;
|
|
150
|
+
if (Array.isArray(r))
|
|
151
|
+
[t, n, a, b] = r;
|
|
152
|
+
else
|
|
153
|
+
({ h: t, s: n, v: a, a: b } = r);
|
|
154
|
+
let [o, y, c] = H(t, n, a);
|
|
155
|
+
return new e(o, y, c, b);
|
|
156
|
+
}
|
|
157
|
+
if (s === "cmyk") {
|
|
158
|
+
let t, n, a, b, o;
|
|
159
|
+
if (Array.isArray(r))
|
|
160
|
+
[t, n, a, b, o] = r;
|
|
161
|
+
else
|
|
162
|
+
({ c: t, m: n, y: a, k: b, a: o } = r);
|
|
163
|
+
let [y, c, u] = v(t, n, a, b);
|
|
164
|
+
return new e(y, c, u, o);
|
|
165
|
+
}
|
|
166
|
+
if (s === "oklch") {
|
|
167
|
+
let t, n, a, b;
|
|
168
|
+
if (Array.isArray(r))
|
|
169
|
+
[t, n, a, b] = r;
|
|
170
|
+
else
|
|
171
|
+
({ l: t, c: n, h: a, a: b } = r);
|
|
172
|
+
let [o, y, c] = C(t, n, a);
|
|
173
|
+
return new e(o, y, c, b);
|
|
174
|
+
}
|
|
175
|
+
if (s === "oklab") {
|
|
176
|
+
let t, n, a, b;
|
|
177
|
+
if (Array.isArray(r))
|
|
178
|
+
[t, n, a, b] = r;
|
|
179
|
+
else
|
|
180
|
+
({ l: t, a: n, b: a, alpha: b } = r);
|
|
181
|
+
let [o, y, c] = M(t, n, a);
|
|
182
|
+
return new e(o, y, c, b);
|
|
183
|
+
}
|
|
184
|
+
throw Error("Invalid input");
|
|
185
|
+
}
|
|
186
|
+
to(s) {
|
|
187
|
+
switch (s) {
|
|
188
|
+
case "rgb":
|
|
189
|
+
return [this.r, this.g, this.b];
|
|
190
|
+
case "rgba":
|
|
191
|
+
return [this.r, this.g, this.b, this.a];
|
|
192
|
+
case "hex":
|
|
193
|
+
return A([this.r, this.g, this.b]);
|
|
194
|
+
case "hexa":
|
|
195
|
+
return A([this.r, this.g, this.b, this.a]);
|
|
196
|
+
case "hsl":
|
|
197
|
+
return k([this.r, this.g, this.b]);
|
|
198
|
+
case "hsla":
|
|
199
|
+
return [...k([this.r, this.g, this.b]), this.a];
|
|
200
|
+
case "hsv":
|
|
201
|
+
return f([this.r, this.g, this.b]);
|
|
202
|
+
case "hsva":
|
|
203
|
+
return [...f([this.r, this.g, this.b]), this.a];
|
|
204
|
+
case "cmyk":
|
|
205
|
+
return O([this.r, this.g, this.b]);
|
|
206
|
+
case "cmyka":
|
|
207
|
+
return [...O([this.r, this.g, this.b]), this.a];
|
|
208
|
+
case "oklch":
|
|
209
|
+
return d([this.r, this.g, this.b]);
|
|
210
|
+
case "oklcha":
|
|
211
|
+
return [...d([this.r, this.g, this.b]), this.a];
|
|
212
|
+
case "oklab":
|
|
213
|
+
return T([this.r, this.g, this.b]);
|
|
214
|
+
case "oklaba":
|
|
215
|
+
return [...T([this.r, this.g, this.b]), this.a];
|
|
216
|
+
default:
|
|
217
|
+
return [this.r, this.g, this.b];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
getLuminance() {
|
|
221
|
+
let s = l(this.r), r = l(this.g), t = l(this.b);
|
|
222
|
+
return 0.2126 * s + 0.7152 * r + 0.0722 * t;
|
|
223
|
+
}
|
|
224
|
+
contrastRatio(s) {
|
|
225
|
+
let r = this.getLuminance(), t = s.getLuminance(), [n, a] = r >= t ? [r, t] : [t, r];
|
|
226
|
+
return (n + 0.05) / (a + 0.05);
|
|
227
|
+
}
|
|
228
|
+
bestContrastForeground() {
|
|
229
|
+
let s = new e(255, 255, 255, 1), r = new e(0, 0, 0, 1), t = this.contrastRatio(s), n = this.contrastRatio(r);
|
|
230
|
+
return t >= n ? s : r;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ../../node_modules/.bun/@kaynooo+utils@2.31.2+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-y1msbssb.js
|
|
235
|
+
var r = 39;
|
|
236
|
+
var o = 49;
|
|
237
|
+
var f2 = "\x1B[";
|
|
238
|
+
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") => {
|
|
239
|
+
let [a, l, s] = e.from("hex", i).to("rgb");
|
|
240
|
+
return [`${n === "fg" ? 38 : 48};2;${a};${l};${s}`, n === "fg" ? r : o];
|
|
241
|
+
}, ansi256: (i, n = "fg") => [`${n === "fg" ? 38 : 48};5;${i}`, n === "fg" ? r : o] };
|
|
242
|
+
var b = new Set(["", "1", "2", "3", "true"]);
|
|
243
|
+
function h(i, n = globalThis.process?.env ?? {}) {
|
|
244
|
+
if (n.FORCE_COLOR !== undefined)
|
|
245
|
+
return b.has(n.FORCE_COLOR);
|
|
246
|
+
if (n.NO_COLOR !== undefined || n.NODE_DISABLE_COLORS !== undefined || n.TERM === "dumb")
|
|
247
|
+
return false;
|
|
248
|
+
return i?.isTTY === true;
|
|
249
|
+
}
|
|
250
|
+
function d2(i, n, a) {
|
|
251
|
+
if (!h(a.stream ?? globalThis.process?.stdout, a.env))
|
|
252
|
+
return n;
|
|
253
|
+
let l = "", s = "";
|
|
254
|
+
for (let g = 0, y = i.length;g < y; g++) {
|
|
255
|
+
let [c, p] = i[g];
|
|
256
|
+
l += `${f2}${c}m`, s = `${f2}${p}m${s}`;
|
|
257
|
+
}
|
|
258
|
+
return l + n + s;
|
|
259
|
+
}
|
|
260
|
+
function u(i, n = []) {
|
|
261
|
+
return new Proxy((l) => d2(n, l, i), { get(l, s) {
|
|
262
|
+
let g = C2[s];
|
|
263
|
+
if (Array.isArray(g))
|
|
264
|
+
return u(i, [...n, g]);
|
|
265
|
+
if (typeof g === "function")
|
|
266
|
+
return (...y) => u(i, [...n, g(...y)]);
|
|
267
|
+
throw Error(`Unknown style: ${String(s)}`);
|
|
268
|
+
} });
|
|
269
|
+
}
|
|
270
|
+
function le(i = {}) {
|
|
271
|
+
return u(i);
|
|
272
|
+
}
|
|
273
|
+
var t = le();
|
|
274
|
+
|
|
275
|
+
// ../kit/src/style.ts
|
|
276
|
+
function takesControlCodes(stream, env = process2.env) {
|
|
277
|
+
return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
|
|
278
|
+
}
|
|
279
|
+
function paint(stream, env = process2.env) {
|
|
280
|
+
return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
|
|
281
|
+
}
|
|
282
|
+
var STATUSES = {
|
|
283
|
+
passed: { symbol: "\u2713", words: ["passed", "success", "succeeded", "ok"], paint: (colors) => colors.green },
|
|
284
|
+
failed: { symbol: "\u2717", words: ["failed", "failure", "error", "errored"], paint: (colors) => colors.red },
|
|
285
|
+
running: { symbol: "\u25CF", words: ["running", "in_progress", "started"], paint: (colors) => colors.cyan },
|
|
286
|
+
queued: { symbol: "\u25CB", words: ["queued", "waiting", "pending", "created", "blocked", "scheduled"], paint: (colors) => colors.yellow },
|
|
287
|
+
skipped: { symbol: "-", words: ["skipped", "cancelled", "canceled", "manual"], paint: (colors) => colors.dim }
|
|
288
|
+
};
|
|
289
|
+
var STATUS_OF = new Map(Object.entries(STATUSES).flatMap(([status, { words }]) => words.map((word) => [word, status])));
|
|
290
|
+
|
|
291
|
+
// ../kit/src/format.ts
|
|
292
|
+
function upTo(text, separator) {
|
|
293
|
+
const end = text.indexOf(separator);
|
|
294
|
+
return end === -1 ? text : text.slice(0, end);
|
|
295
|
+
}
|
|
296
|
+
var ESCAPE = String.fromCharCode(27);
|
|
297
|
+
function printJson(value) {
|
|
298
|
+
console.log(JSON.stringify(value, undefined, 2));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ../kit/src/help.ts
|
|
302
|
+
import process3 from "process";
|
|
303
|
+
var OPTIONAL = /\[[^\]]+\]/g;
|
|
304
|
+
var USAGE_PREFIX = /^usage:\s*/i;
|
|
305
|
+
var FULL_STOP = /\.$/;
|
|
306
|
+
function usageLine(usage, colors = paint(process3.stdout)) {
|
|
307
|
+
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
308
|
+
}
|
|
309
|
+
function helpText(usage, options, colors = paint(process3.stdout), { examples = [], replaces } = {}) {
|
|
310
|
+
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
311
|
+
`);
|
|
312
|
+
}
|
|
313
|
+
function replacesRows(replaces, colors = paint(process3.stdout)) {
|
|
314
|
+
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
315
|
+
}
|
|
316
|
+
function wantsHelpJson(env = process3.env) {
|
|
317
|
+
return env.KIT_HELP === "json";
|
|
318
|
+
}
|
|
319
|
+
function description(usage, { examples = [], replaces } = {}) {
|
|
320
|
+
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
321
|
+
`);
|
|
322
|
+
const first = lines[0];
|
|
323
|
+
const colon = first.indexOf(": ");
|
|
324
|
+
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
325
|
+
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
326
|
+
return {
|
|
327
|
+
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
328
|
+
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
329
|
+
...replaces === undefined ? {} : { replaces },
|
|
330
|
+
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function exampleRows(examples, colors = paint(process3.stdout)) {
|
|
334
|
+
if (examples.length === 0)
|
|
335
|
+
return [];
|
|
336
|
+
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
337
|
+
}
|
|
338
|
+
function optionRows(options, colors = paint(process3.stdout)) {
|
|
339
|
+
const rows = Object.entries(options).map(([name, option]) => {
|
|
340
|
+
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
341
|
+
const named = option.choices === undefined ? "<value>" : `<${option.choices.join("|")}>`;
|
|
342
|
+
const placeholder = option.optional === undefined ? named : `[${named}]`;
|
|
343
|
+
const value = option.type === "string" ? ` ${colors.dim(placeholder)}` : "";
|
|
344
|
+
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? placeholder.length + 1 : 0), description: option.description ?? "" };
|
|
345
|
+
});
|
|
346
|
+
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
347
|
+
return rows.map((row) => row.description === "" ? ` ${row.flag}` : ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ../kit/src/text.ts
|
|
351
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
|
|
352
|
+
function stripAnsi(text) {
|
|
353
|
+
return text.replace(ANSI, "");
|
|
354
|
+
}
|
|
355
|
+
function distance(a, b) {
|
|
356
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
357
|
+
for (const [i, left] of [...a].entries()) {
|
|
358
|
+
const current = [i + 1];
|
|
359
|
+
for (const [j, right] of [...b].entries())
|
|
360
|
+
current.push(Math.min((previous[j + 1] ?? 0) + 1, (current[j] ?? 0) + 1, (previous[j] ?? 0) + (left === right ? 0 : 1)));
|
|
361
|
+
previous = current;
|
|
362
|
+
}
|
|
363
|
+
return previous[b.length] ?? 0;
|
|
364
|
+
}
|
|
365
|
+
function closest(name, names) {
|
|
366
|
+
const started = names.find((known) => known.startsWith(name));
|
|
367
|
+
if (started !== undefined)
|
|
368
|
+
return started;
|
|
369
|
+
const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
|
|
370
|
+
return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ../kit/src/args.ts
|
|
374
|
+
var DASHES = /^-+/;
|
|
375
|
+
function jsonOption(holds) {
|
|
376
|
+
return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
class Exit extends Error {
|
|
380
|
+
code;
|
|
381
|
+
hint;
|
|
382
|
+
constructor(message, code, hint) {
|
|
383
|
+
super(message);
|
|
384
|
+
this.code = code;
|
|
385
|
+
this.hint = hint;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function fail(message, code = 2, hint) {
|
|
389
|
+
throw new Exit(message, code, hint);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
class UsageError extends Exit {
|
|
393
|
+
constructor(message, hint) {
|
|
394
|
+
super(message, 2, hint);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function badUsage(message) {
|
|
398
|
+
throw new UsageError(message);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
class HelpShown extends Exit {
|
|
402
|
+
constructor() {
|
|
403
|
+
super("help shown", 0);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
class Completing {
|
|
408
|
+
options;
|
|
409
|
+
positionals;
|
|
410
|
+
candidates;
|
|
411
|
+
constructor(options, positionals = [], candidates = {}) {
|
|
412
|
+
this.options = options;
|
|
413
|
+
this.positionals = positionals;
|
|
414
|
+
this.candidates = candidates;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
var completion = { active: false };
|
|
418
|
+
var OUTPUT_OPTIONS = {
|
|
419
|
+
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
420
|
+
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
421
|
+
};
|
|
422
|
+
function applyOutput(color, plain, env = process4.env) {
|
|
423
|
+
const when = plain === true ? "never" : color ?? "auto";
|
|
424
|
+
if (when === "auto")
|
|
425
|
+
return;
|
|
426
|
+
if (when !== "always" && when !== "never")
|
|
427
|
+
badUsage(`--color takes auto, always or never, not ${when}`);
|
|
428
|
+
if (when === "always") {
|
|
429
|
+
env.FORCE_COLOR = "1";
|
|
430
|
+
delete env.NO_COLOR;
|
|
431
|
+
} else {
|
|
432
|
+
env.NO_COLOR = "1";
|
|
433
|
+
delete env.FORCE_COLOR;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function parse(args, usage, options, settings = {}) {
|
|
437
|
+
const output = Object.fromEntries(Object.entries(OUTPUT_OPTIONS).filter(([name]) => !(name in options)));
|
|
438
|
+
const all = { ...options, ...output, help: { type: "boolean", short: "h", description: "show this help" } };
|
|
439
|
+
const listing = listed(all, settings.allowNegative === true);
|
|
440
|
+
if (completion.active)
|
|
441
|
+
throw new Completing(listing, settings.positionals, settings.candidates);
|
|
442
|
+
const { own, rest } = settings.trailing === true ? splitTrailing(args, all, settings.positionals?.length ?? 0) : splitRest(args, settings.rest === true);
|
|
443
|
+
let parsed;
|
|
444
|
+
try {
|
|
445
|
+
parsed = parseArgs({ args: joinDashValues(withBare(own, all), all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
446
|
+
} catch (error) {
|
|
447
|
+
const message = parseError(error);
|
|
448
|
+
throw new UsageError(message, nearestOption(message, all, settings.allowNegative === true));
|
|
449
|
+
}
|
|
450
|
+
const values = parsed.values;
|
|
451
|
+
applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
|
|
452
|
+
if (values.help === true) {
|
|
453
|
+
console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings)) : helpText(usage, listing, undefined, settings));
|
|
454
|
+
throw new HelpShown;
|
|
455
|
+
}
|
|
456
|
+
checkChoices(parsed.values, all);
|
|
457
|
+
checkConflicts(parsed.values, all);
|
|
458
|
+
if (settings.positionals !== undefined)
|
|
459
|
+
checkPositionals(parsed.positionals, settings.positionals);
|
|
460
|
+
return { ...parsed, values: parsed.values, positionals: parsed.positionals, rest };
|
|
461
|
+
}
|
|
462
|
+
function splitRest(args, passes) {
|
|
463
|
+
const separator = passes ? args.indexOf("--") : -1;
|
|
464
|
+
return { own: separator === -1 ? args : args.slice(0, separator), rest: separator === -1 ? undefined : args.slice(separator + 1) };
|
|
465
|
+
}
|
|
466
|
+
function splitTrailing(args, options, count) {
|
|
467
|
+
const takesValue = (word) => !word.includes("=") && Object.entries(options).some(([name, { short, type }]) => type === "string" && (word === `--${name}` || word === `-${short}`));
|
|
468
|
+
let seen = 0;
|
|
469
|
+
if (count === 0)
|
|
470
|
+
return { own: args, rest: undefined };
|
|
471
|
+
for (let at = 0;at < args.length; at++) {
|
|
472
|
+
const word = args[at];
|
|
473
|
+
if (word === "--")
|
|
474
|
+
break;
|
|
475
|
+
if (word.startsWith("-")) {
|
|
476
|
+
if (takesValue(word))
|
|
477
|
+
at++;
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
seen++;
|
|
481
|
+
if (seen === count)
|
|
482
|
+
return { own: args.slice(0, at + 1), rest: args.slice(at + 1) };
|
|
483
|
+
}
|
|
484
|
+
return { own: args, rest: undefined };
|
|
485
|
+
}
|
|
486
|
+
function listed(options, allowNegative) {
|
|
487
|
+
if (!allowNegative)
|
|
488
|
+
return options;
|
|
489
|
+
return Object.fromEntries(Object.entries(options).flatMap(([name, option]) => {
|
|
490
|
+
if (option.type !== "boolean" || option.negative === undefined)
|
|
491
|
+
return [[name, option]];
|
|
492
|
+
return [...option.description === undefined ? [] : [[name, option]], [`no-${name}`, { type: "boolean", description: option.negative }]];
|
|
493
|
+
}));
|
|
494
|
+
}
|
|
495
|
+
function withBare(args, options) {
|
|
496
|
+
const bare = new Map(Object.entries(options).flatMap(([name, { short, optional }]) => optional === undefined ? [] : [[`--${name}`, optional], ...short === undefined ? [] : [[`-${short}`, optional]]]));
|
|
497
|
+
if (bare.size === 0)
|
|
498
|
+
return args;
|
|
499
|
+
const separator = args.indexOf("--");
|
|
500
|
+
const own = separator === -1 ? args : args.slice(0, separator);
|
|
501
|
+
return [...own.flatMap((arg, index) => {
|
|
502
|
+
const optional = bare.get(arg);
|
|
503
|
+
const next = own[index + 1];
|
|
504
|
+
const value = next !== undefined && (optional?.accepts === undefined ? !next.startsWith("-") : optional.accepts.test(next));
|
|
505
|
+
return optional === undefined || value ? [arg] : [arg, optional.bare];
|
|
506
|
+
}), ...separator === -1 ? [] : args.slice(separator)];
|
|
507
|
+
}
|
|
508
|
+
function checkConflicts(values, options) {
|
|
509
|
+
const given = (name) => values[name] !== undefined && values[name] !== false;
|
|
510
|
+
for (const [name, { conflicts = [] }] of Object.entries(options)) {
|
|
511
|
+
const other = given(name) ? conflicts.find(given) : undefined;
|
|
512
|
+
if (other !== undefined)
|
|
513
|
+
badUsage(`--${name} cannot be used with --${other}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function checkChoices(values, options) {
|
|
517
|
+
for (const [name, option] of Object.entries(options)) {
|
|
518
|
+
if (option.choices === undefined)
|
|
519
|
+
continue;
|
|
520
|
+
const given = values[name];
|
|
521
|
+
for (const value of Array.isArray(given) ? given : [given]) {
|
|
522
|
+
if (typeof value === "string")
|
|
523
|
+
oneOf(value, name, option.choices);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function oneOf(value, name, choices) {
|
|
528
|
+
if (!choices.includes(value))
|
|
529
|
+
badUsage(`${name.startsWith("<") ? name : `--${name}`} takes one of ${choices.join(", ")}, not ${value}`);
|
|
530
|
+
return value;
|
|
531
|
+
}
|
|
532
|
+
function checkPositionals(given, names) {
|
|
533
|
+
const variadic = names.at(-1)?.replace("]", "").endsWith("...") === true;
|
|
534
|
+
const required = names.filter((name) => !name.startsWith("[")).length;
|
|
535
|
+
if (given.length < required)
|
|
536
|
+
badUsage(`missing ${names[given.length]}`);
|
|
537
|
+
if (!variadic && given.length > names.length)
|
|
538
|
+
badUsage(`unexpected argument ${given[names.length]}`);
|
|
539
|
+
}
|
|
540
|
+
function joinDashValues(args, options) {
|
|
541
|
+
const longOf = new Map(Object.entries(options).flatMap(([name, option]) => option.short === undefined ? [[`--${name}`, name]] : [[`--${name}`, name], [`-${option.short}`, name]]));
|
|
542
|
+
const isOption = (arg) => arg === "--" || longOf.has(arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg);
|
|
543
|
+
const joined = [];
|
|
544
|
+
for (let i = 0;i < args.length; i++) {
|
|
545
|
+
const arg = args[i] ?? "";
|
|
546
|
+
if (arg === "--")
|
|
547
|
+
return [...joined, ...args.slice(i)];
|
|
548
|
+
const name = longOf.get(arg);
|
|
549
|
+
const next = args[i + 1];
|
|
550
|
+
if (name !== undefined && options[name]?.type === "string" && next !== undefined && next.startsWith("-") && next !== "-" && !isOption(next)) {
|
|
551
|
+
joined.push(`--${name}=${next}`);
|
|
552
|
+
i++;
|
|
553
|
+
} else {
|
|
554
|
+
joined.push(arg);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return joined;
|
|
558
|
+
}
|
|
559
|
+
function parseError(error) {
|
|
560
|
+
const message = upTo(error instanceof Error ? error.message : String(error), ". ");
|
|
561
|
+
return `${message.charAt(0).toLowerCase()}${message.slice(1)}`;
|
|
562
|
+
}
|
|
563
|
+
var UNKNOWN_OPTION = /^unknown option '(-[^']+)'/;
|
|
564
|
+
function nearestOption(message, options, allowNegative) {
|
|
565
|
+
const typed = UNKNOWN_OPTION.exec(message)?.[1];
|
|
566
|
+
if (typed === undefined)
|
|
567
|
+
return;
|
|
568
|
+
const word = typed.replace(DASHES, "");
|
|
569
|
+
if (word.length < 2 && !typed.startsWith("--"))
|
|
570
|
+
return;
|
|
571
|
+
const known = Object.entries(options).flatMap(([name, option]) => [
|
|
572
|
+
...option.short === undefined ? [] : [`-${option.short}`],
|
|
573
|
+
`--${name}`,
|
|
574
|
+
...allowNegative && option.type === "boolean" ? [`--no-${name}`] : []
|
|
575
|
+
]);
|
|
576
|
+
const meant = closest(word, known.map((flag) => flag.replace(DASHES, "")));
|
|
577
|
+
return meant === undefined ? undefined : `did you mean ${known.find((flag) => flag.replace(DASHES, "") === meant)}?`;
|
|
578
|
+
}
|
|
579
|
+
// ../kit/src/progress.ts
|
|
580
|
+
var ESC = String.fromCharCode(27);
|
|
581
|
+
var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x7E]|\\][^\\u0007${ESC}]*(?:\\u0007|${ESC}\\\\))`, "y");
|
|
582
|
+
var RESET_STYLE = `${ESC}[0m`;
|
|
583
|
+
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
584
|
+
// ../kit/src/cli.ts
|
|
585
|
+
import process6 from "process";
|
|
586
|
+
|
|
587
|
+
// ../kit/src/usage.ts
|
|
588
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
589
|
+
import { homedir } from "os";
|
|
590
|
+
import { dirname, isAbsolute, join } from "path";
|
|
591
|
+
import process5 from "process";
|
|
592
|
+
|
|
593
|
+
// ../kit/src/logs.ts
|
|
594
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
595
|
+
var CLI_FOLDER = "kaynooo-cli";
|
|
596
|
+
|
|
597
|
+
// ../kit/src/usage.ts
|
|
598
|
+
var UNKNOWN_OPTION2 = /unknown option '(--[a-z][a-z0-9-]*)[=']/;
|
|
599
|
+
var FLAG_NAME = /^(?:--[a-z][a-z0-9-]*|-[a-zA-Z])$/;
|
|
600
|
+
var LONGEST_NAME = 40;
|
|
601
|
+
var usageError;
|
|
602
|
+
function noteUsageError(message) {
|
|
603
|
+
usageError = message;
|
|
604
|
+
}
|
|
605
|
+
function stateHome(env = process5.env) {
|
|
606
|
+
const dir = env.XDG_STATE_HOME;
|
|
607
|
+
return dir !== undefined && isAbsolute(dir) ? dir : join(homedir(), ".local", "state");
|
|
608
|
+
}
|
|
609
|
+
function usagePath(env = process5.env) {
|
|
610
|
+
return join(stateHome(env), CLI_FOLDER, "usage.jsonl");
|
|
611
|
+
}
|
|
612
|
+
function usageRecorded(env = process5.env) {
|
|
613
|
+
const off = env.KAYNOOO_CLI_NO_USAGE;
|
|
614
|
+
return env.NODE_ENV !== "test" && (off === undefined || off === "" || off === "0");
|
|
615
|
+
}
|
|
616
|
+
function flagNames(argv) {
|
|
617
|
+
const end = argv.indexOf("--");
|
|
618
|
+
const names = (end === -1 ? argv : argv.slice(0, end)).filter((word) => word.startsWith("-") && word !== "-").map((word) => word.includes("=") ? word.slice(0, word.indexOf("=")) : word);
|
|
619
|
+
return [...new Set(names)].filter((name) => name.length <= LONGEST_NAME && FLAG_NAME.test(name));
|
|
620
|
+
}
|
|
621
|
+
function usageEntry(bin, command, argv, code, now = new Date) {
|
|
622
|
+
const unknown = code === 2 && usageError !== undefined ? UNKNOWN_OPTION2.exec(usageError)?.[1] : undefined;
|
|
623
|
+
usageError = undefined;
|
|
624
|
+
return { at: now.toISOString(), bin, ...command === undefined ? {} : { command }, flags: flagNames(argv), code, ...unknown === undefined ? {} : { unknown } };
|
|
625
|
+
}
|
|
626
|
+
function recordUsage(entry, env = process5.env) {
|
|
627
|
+
if (!usageRecorded(env))
|
|
628
|
+
return;
|
|
629
|
+
try {
|
|
630
|
+
const path = usagePath(env);
|
|
631
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
632
|
+
appendFileSync(path, `${JSON.stringify(entry)}
|
|
633
|
+
`, { mode: 384 });
|
|
634
|
+
} catch {}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ../kit/src/cli.ts
|
|
638
|
+
var LEVEL_OPTIONS = {
|
|
639
|
+
...OUTPUT_OPTIONS,
|
|
640
|
+
help: { type: "boolean", short: "h", description: "show this help" }
|
|
641
|
+
};
|
|
642
|
+
var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
|
|
643
|
+
function printError(prefix, message, hint, cause) {
|
|
644
|
+
const colors = paint(process6.stderr);
|
|
645
|
+
const styled = takesControlCodes(process6.stderr);
|
|
646
|
+
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
647
|
+
const under = (label, text, tint) => text === undefined ? "" : `
|
|
648
|
+
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
649
|
+
console.error(`${head} ${message}${under("cause", cause, colors.dim)}${under("hint", hint, colors.yellow)}`);
|
|
650
|
+
}
|
|
651
|
+
function printJsonError(message, hint, cause) {
|
|
652
|
+
console.error(JSON.stringify({ error: message, ...hint === undefined ? {} : { hint }, ...cause === undefined ? {} : { cause } }));
|
|
653
|
+
}
|
|
654
|
+
function wantsJson(argv) {
|
|
655
|
+
const end = argv.indexOf("--");
|
|
656
|
+
return (end === -1 ? argv : argv.slice(0, end)).includes("--json");
|
|
657
|
+
}
|
|
658
|
+
function reportThrown(prefix, error, redact = (text) => text, json = false) {
|
|
659
|
+
if (error instanceof HelpShown)
|
|
660
|
+
return 0;
|
|
661
|
+
const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
|
|
662
|
+
const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
|
|
663
|
+
const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
|
|
664
|
+
if (error instanceof UsageError)
|
|
665
|
+
noteUsageError(error.message);
|
|
666
|
+
const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
|
|
667
|
+
report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
|
|
668
|
+
return error instanceof Exit ? error.code : 1;
|
|
669
|
+
}
|
|
670
|
+
var COMPLETE = "__complete";
|
|
671
|
+
var FILES = "__files__";
|
|
672
|
+
async function completingOf(run) {
|
|
673
|
+
completion.active = true;
|
|
674
|
+
try {
|
|
675
|
+
await run([]);
|
|
676
|
+
} catch (error) {
|
|
677
|
+
if (error instanceof Completing)
|
|
678
|
+
return error;
|
|
679
|
+
} finally {
|
|
680
|
+
completion.active = false;
|
|
681
|
+
}
|
|
682
|
+
return new Completing({});
|
|
683
|
+
}
|
|
684
|
+
function candidateLine(candidate) {
|
|
685
|
+
return typeof candidate === "string" ? candidate : candidate.join("\t");
|
|
686
|
+
}
|
|
687
|
+
function positionalBeingTyped(before, { options, positionals }) {
|
|
688
|
+
const takesValue = (word) => !word.includes("=") && Object.entries(options).some(([name, { short, type }]) => type === "string" && (word === `--${name}` || word === `-${short}`));
|
|
689
|
+
let index = 0;
|
|
690
|
+
let at = 0;
|
|
691
|
+
while (at < before.length) {
|
|
692
|
+
const word = before[at];
|
|
693
|
+
if (word === "--") {
|
|
694
|
+
index += before.length - at - 1;
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
if (!word.startsWith("-"))
|
|
698
|
+
index++;
|
|
699
|
+
else if (takesValue(word))
|
|
700
|
+
at++;
|
|
701
|
+
at++;
|
|
702
|
+
}
|
|
703
|
+
const last = positionals.at(-1);
|
|
704
|
+
if (index < positionals.length)
|
|
705
|
+
return positionals[index];
|
|
706
|
+
return last?.replace("]", "").endsWith("...") === true ? last : undefined;
|
|
707
|
+
}
|
|
708
|
+
async function leafCandidates(run, words) {
|
|
709
|
+
const completing = await completingOf(run);
|
|
710
|
+
if ((words.at(-1) ?? "").startsWith("-"))
|
|
711
|
+
return Object.entries(completing.options).map(([name, option]) => option.description === undefined ? `--${name}` : `--${name} ${option.description}`);
|
|
712
|
+
const flag = words.at(-2);
|
|
713
|
+
const option = flag?.startsWith("-") === true ? Object.entries(completing.options).find(([name, { short }]) => flag === `--${name}` || flag === `-${short}`)?.[1] : undefined;
|
|
714
|
+
if (option?.type === "string") {
|
|
715
|
+
const values = option.choices === undefined ? await option.candidates?.() : option.choices;
|
|
716
|
+
return values === undefined ? [FILES] : values.map(candidateLine);
|
|
717
|
+
}
|
|
718
|
+
const name = positionalBeingTyped(words.slice(0, -1), completing);
|
|
719
|
+
const values = name === undefined ? undefined : await completing.candidates[name]?.();
|
|
720
|
+
return values === undefined ? [FILES] : values.map(candidateLine);
|
|
721
|
+
}
|
|
722
|
+
async function printCandidates(found) {
|
|
723
|
+
for (const line of await found)
|
|
724
|
+
console.log(line);
|
|
725
|
+
return 0;
|
|
726
|
+
}
|
|
727
|
+
async function runMain({ name, version, run, redact, usage }, argv = process6.argv.slice(2)) {
|
|
728
|
+
if (argv[0] === COMPLETE)
|
|
729
|
+
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
730
|
+
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
731
|
+
console.log(version);
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
let code;
|
|
735
|
+
try {
|
|
736
|
+
code = await run(argv);
|
|
737
|
+
} catch (error) {
|
|
738
|
+
code = reportThrown(name, error, redact, wantsJson(argv));
|
|
739
|
+
}
|
|
740
|
+
if (usage !== false)
|
|
741
|
+
recordUsage(usageEntry(name, undefined, argv, code));
|
|
742
|
+
return code;
|
|
743
|
+
}
|
|
744
|
+
// ../kit/src/secrets.ts
|
|
745
|
+
var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
|
|
746
|
+
// ../kit/src/sessions.ts
|
|
747
|
+
var {Glob } = globalThis.Bun;
|
|
748
|
+
var SESSION_FILES = new Glob("*.json");
|
|
749
|
+
var ACCOUNT_DIRS = new Glob("*/sessions");
|
|
750
|
+
// package.json
|
|
751
|
+
var version = "0.4.0";
|
|
752
|
+
|
|
753
|
+
// src/dlog.ts
|
|
754
|
+
import process8 from "process";
|
|
755
|
+
|
|
756
|
+
// ../env/src/mask.ts
|
|
757
|
+
var KEY_MASK = "*** private key redacted ***";
|
|
758
|
+
var PLAIN_VALUE = /^(?:true|false|null|none|nil|yes|no|on|off|undefined)$/i;
|
|
759
|
+
var PLACEHOLDER = /^[$%{<(*/]/;
|
|
760
|
+
var URL_PASSWORD = /(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,31}:\/\/[^\s:/@]*:([^\s'"]{1,1024})@/dgi;
|
|
761
|
+
var PORT = /^\d{1,5}$/;
|
|
762
|
+
function isPlaceholder(value) {
|
|
763
|
+
return PLACEHOLDER.test(value) || PLAIN_VALUE.test(value);
|
|
764
|
+
}
|
|
765
|
+
var TOKEN_FORMATS = [
|
|
766
|
+
{ kind: "GitHub token", pattern: /\b(?:gh[pousr]_|github_pat_)\w{20,}/dg },
|
|
767
|
+
{ kind: "GitLab token", pattern: /\bglpat-[\w-]{20,}/dg },
|
|
768
|
+
{ kind: "API key", pattern: /\bsk-[\w-]{20,}/dg },
|
|
769
|
+
{ kind: "Slack token", pattern: /\bx(?:ox[abprs]|app)-[\w-]{10,}/dg },
|
|
770
|
+
{ kind: "JWT", pattern: /\beyJ[\w-]+\.[\w-]+\.[\w-]+/dg },
|
|
771
|
+
{ kind: "AWS access key", pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/dg },
|
|
772
|
+
{ kind: "npm token", pattern: /\bnpm_[A-Za-z0-9]{30,}/dg },
|
|
773
|
+
{ kind: "Stripe key", pattern: /\b[sr]k_(?:live|test)_[A-Za-z0-9]{10,}/dg },
|
|
774
|
+
{ kind: "Google API key", pattern: /\bAIza[\w-]{35}/dg },
|
|
775
|
+
{ kind: "SendGrid key", pattern: /\bSG\.[\w-]{16,}\.[\w-]{16,}/dg },
|
|
776
|
+
{ kind: "Authorization header", pattern: /\bAuthorization["']?\s*[:=]\s*["']?(?:Bearer|Basic|Token|Digest)\s+([^\s"',;]{8,})/dgi, skip: isPlaceholder },
|
|
777
|
+
{ kind: "Bearer token", pattern: /\bBearer\s+((?=[\w.~+/=-]*\d)[\w.~+/=-]{20,})/dg },
|
|
778
|
+
{ kind: "password", pattern: /(?<![\w-])(?:[\w-]{0,30}[_-])?(?:password|passwd|secret|api[_-]?key)(?:[_-][\w-]{0,30})?=([^\s"'&;,]{4,})/dgi, skip: isPlaceholder },
|
|
779
|
+
{ kind: "password", pattern: /(?<![\w-])["']?(?:[\w-]{0,30}[_-])?(?:password|passwd|secret|api[_-]?key)(?:[_-][\w-]{0,30})?["']?\s*[:=]\s*(["'])((?:\\.|(?!\1)[^\\\n]){4,})\1/dgi, skip: isPlaceholder }
|
|
780
|
+
];
|
|
781
|
+
var PEM_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----/;
|
|
782
|
+
var PEM_END = /-----END [A-Z ]*PRIVATE KEY(?: BLOCK)?-----/;
|
|
783
|
+
var PEM_BODY = /(?<=-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----)(?:(?!-----END [A-Z ]*PRIVATE KEY(?: BLOCK)?-----)[\s\S])+/dg;
|
|
784
|
+
var URL_PARTS = /(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,31}:\/\/([^\s/?#'"<>`]*)([^\s?#'"<>`]*)(?:\?([^\s#'"<>`]*))?/dgi;
|
|
785
|
+
var PATH_SEGMENT = /[^/]+/g;
|
|
786
|
+
var SECRET_PARAM = /^(?:token|[a-z]+_token|key|api[_-]?key|access[_-]?key|secret|[a-z]+_secret|password|passwd|pwd|sig|signature|auth|authorization)$/i;
|
|
787
|
+
var WEBHOOK_FORMATS = [
|
|
788
|
+
["Discord webhook", /(?:^|\.)discord(?:app)?\.com\/api\/(?:v\d+\/)?webhooks\/\d+\/([\w-]+)/di],
|
|
789
|
+
["Slack webhook", /^hooks\.slack\.com\/(?:services|workflows)\/(?:[\w-]+\/)*([\w-]+)/di],
|
|
790
|
+
["Uptime Kuma push URL", /\/api\/push\/([\w-]+)/di],
|
|
791
|
+
["healthchecks.io ping URL", /^hc-ping\.com\/([\w-]+)/di]
|
|
792
|
+
];
|
|
793
|
+
var OPAQUE_SEGMENT = /^(?:(?=[\w-]{20,}$)(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\w-]+|(?=[A-Za-z\d]{32,}$)(?=.*[A-Za-z])(?=.*\d).+)$/;
|
|
794
|
+
function hint(value) {
|
|
795
|
+
const shown = Math.min(3, Math.floor(value.length / 4));
|
|
796
|
+
return `${value.slice(0, shown)}***(${value.length})`;
|
|
797
|
+
}
|
|
798
|
+
function passwordSpans(text) {
|
|
799
|
+
const spans = [];
|
|
800
|
+
for (const url of text.matchAll(URL_PASSWORD)) {
|
|
801
|
+
const [start, end] = url.indices?.[1] ?? [];
|
|
802
|
+
const password = url[1] ?? "";
|
|
803
|
+
const slash = password.indexOf("/");
|
|
804
|
+
if (start !== undefined && end !== undefined && !(slash !== -1 && PORT.test(password.slice(0, slash))))
|
|
805
|
+
spans.push([start, end]);
|
|
806
|
+
}
|
|
807
|
+
return spans;
|
|
808
|
+
}
|
|
809
|
+
function urlTokens(line) {
|
|
810
|
+
const found = [];
|
|
811
|
+
const add = (kind, start, value) => {
|
|
812
|
+
found.push({ start, end: start + value.length, kind, mask: hint(value) });
|
|
813
|
+
};
|
|
814
|
+
for (const url of line.matchAll(URL_PARTS)) {
|
|
815
|
+
const [authority, path = "", query] = [url[1] ?? "", url[2] ?? "", url[3]];
|
|
816
|
+
const authorityStart = url.indices?.[1]?.[0];
|
|
817
|
+
const pathStart = url.indices?.[2]?.[0];
|
|
818
|
+
const queryStart = url.indices?.[3]?.[0];
|
|
819
|
+
if (authorityStart === undefined || pathStart === undefined)
|
|
820
|
+
continue;
|
|
821
|
+
const hostAt = authority.lastIndexOf("@") + 1;
|
|
822
|
+
const target = authority.slice(hostAt) + path;
|
|
823
|
+
for (const [kind, pattern] of WEBHOOK_FORMATS) {
|
|
824
|
+
const [start, end] = pattern.exec(target)?.indices?.[1] ?? [];
|
|
825
|
+
if (start !== undefined && end !== undefined)
|
|
826
|
+
add(kind, authorityStart + hostAt + start, target.slice(start, end));
|
|
827
|
+
}
|
|
828
|
+
for (const segment of path.matchAll(PATH_SEGMENT)) {
|
|
829
|
+
if (OPAQUE_SEGMENT.test(segment[0]))
|
|
830
|
+
add("URL token", pathStart + segment.index, segment[0]);
|
|
831
|
+
}
|
|
832
|
+
if (query === undefined || queryStart === undefined)
|
|
833
|
+
continue;
|
|
834
|
+
let offset = 0;
|
|
835
|
+
for (const param of query.split("&")) {
|
|
836
|
+
const equals = param.indexOf("=");
|
|
837
|
+
if (equals > 0 && equals < param.length - 1 && SECRET_PARAM.test(param.slice(0, equals)))
|
|
838
|
+
add("URL token", queryStart + offset + equals + 1, param.slice(equals + 1));
|
|
839
|
+
offset += param.length + 1;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return found.filter((match) => match.end > match.start);
|
|
843
|
+
}
|
|
844
|
+
function findSecrets(line, known) {
|
|
845
|
+
const found = [];
|
|
846
|
+
for (const [start, end] of passwordSpans(line))
|
|
847
|
+
found.push({ start, end, kind: "URL password", mask: "***" });
|
|
848
|
+
for (const body of line.matchAll(PEM_BODY)) {
|
|
849
|
+
const [start, end] = body.indices?.[0] ?? [];
|
|
850
|
+
if (start !== undefined && end !== undefined)
|
|
851
|
+
found.push({ start, end, kind: "private key", mask: KEY_MASK });
|
|
852
|
+
}
|
|
853
|
+
for (const { kind, pattern, skip } of TOKEN_FORMATS) {
|
|
854
|
+
for (const token of line.matchAll(pattern)) {
|
|
855
|
+
const [start, end] = token.indices?.at(-1) ?? [];
|
|
856
|
+
if (start === undefined || end === undefined)
|
|
857
|
+
continue;
|
|
858
|
+
const value = line.slice(start, end);
|
|
859
|
+
if (!skip?.(value))
|
|
860
|
+
found.push({ start, end, kind, mask: hint(value) });
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
found.push(...urlTokens(line));
|
|
864
|
+
if (known?.pattern !== undefined) {
|
|
865
|
+
for (const value of line.matchAll(known.pattern))
|
|
866
|
+
found.push({ start: value.index, end: value.index + value[0].length, kind: `value of ${known.keys.get(value[0])}`, mask: hint(value[0]) });
|
|
867
|
+
}
|
|
868
|
+
found.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
869
|
+
const kept = [];
|
|
870
|
+
for (const match of found) {
|
|
871
|
+
if (match.start >= (kept.at(-1)?.end ?? 0))
|
|
872
|
+
kept.push(match);
|
|
873
|
+
}
|
|
874
|
+
return kept;
|
|
875
|
+
}
|
|
876
|
+
function maskLine(line, known) {
|
|
877
|
+
let out = "";
|
|
878
|
+
let at = 0;
|
|
879
|
+
for (const match of findSecrets(line, known)) {
|
|
880
|
+
out += line.slice(at, match.start) + match.mask;
|
|
881
|
+
at = match.end;
|
|
882
|
+
}
|
|
883
|
+
return out + line.slice(at);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// src/stack.ts
|
|
887
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
888
|
+
import { dirname as dirname2, join as join2, relative, resolve } from "path";
|
|
889
|
+
var LABEL_FORMAT = ["project", "project.config_files", "project.environment_file", "project.working_dir"].map((label) => `{{.Label "com.docker.compose.${label}"}}`).join("\t");
|
|
890
|
+
var COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m;
|
|
891
|
+
var COMPOSE_FILES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
|
|
892
|
+
var SKIPPED = new Set(["node_modules", "vendor", "dist", "build", "target", "venv", "__pycache__"]);
|
|
893
|
+
var ACTIONS = ["shell", "logs", "restart", "up", "stop", "build"];
|
|
894
|
+
function from(dir, command) {
|
|
895
|
+
return dir === "." ? command : ["env", "-C", dir, ...command];
|
|
896
|
+
}
|
|
897
|
+
function children(dir = ".") {
|
|
898
|
+
return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED.has(child.name)).map((child) => child.name).sort();
|
|
899
|
+
}
|
|
900
|
+
function up(dir) {
|
|
901
|
+
const dirs = [];
|
|
902
|
+
for (let current = dir;; current = join2(current, "..")) {
|
|
903
|
+
dirs.push(current);
|
|
904
|
+
const absolute = resolve(current);
|
|
905
|
+
if (existsSync(join2(current, ".git")) || dirname2(absolute) === absolute)
|
|
906
|
+
return dirs;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function stackAt(dir) {
|
|
910
|
+
return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd: resolve(dir), env: process.env } };
|
|
911
|
+
}
|
|
912
|
+
function holdsStack(dir) {
|
|
913
|
+
if (COMPOSE_FILES.some((name) => existsSync(join2(dir, name))))
|
|
914
|
+
return true;
|
|
915
|
+
try {
|
|
916
|
+
return COMPOSE_FILE_VAR.test(readFileSync(join2(dir, ".env"), "utf-8"));
|
|
917
|
+
} catch {
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function nearestStack(dir) {
|
|
922
|
+
const found = up(dir).find(holdsStack);
|
|
923
|
+
return found === undefined ? undefined : stackAt(found);
|
|
924
|
+
}
|
|
925
|
+
function stacks() {
|
|
926
|
+
const here = nearestStack(".");
|
|
927
|
+
const below = children().filter(holdsStack);
|
|
928
|
+
return [...here ? [here] : [], ...below.filter((dir) => dir !== here?.dir).map(stackAt)];
|
|
929
|
+
}
|
|
930
|
+
function labelled(text) {
|
|
931
|
+
const projects = new Map;
|
|
932
|
+
for (const line of text.split(`
|
|
933
|
+
`)) {
|
|
934
|
+
const [project, files, envFiles = "", workdir = ""] = line.split("\t");
|
|
935
|
+
if (project !== undefined && project !== "" && files !== undefined && files !== "" && workdir !== "" && !projects.has(project))
|
|
936
|
+
projects.set(project, { project, files: files.split(","), envFiles: envFiles.split(",").filter(Boolean), workdir });
|
|
937
|
+
}
|
|
938
|
+
return [...projects.values()];
|
|
939
|
+
}
|
|
940
|
+
async function labels(project) {
|
|
941
|
+
const filter = project === undefined ? [] : ["--filter", `label=com.docker.compose.project=${project}`];
|
|
942
|
+
const proc = Bun.spawn(["docker", "ps", "--all", ...filter, "--format", LABEL_FORMAT], { stdout: "pipe", stderr: "ignore" });
|
|
943
|
+
if (await proc.exited !== 0)
|
|
944
|
+
return [];
|
|
945
|
+
return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) => existsSync(file)));
|
|
946
|
+
}
|
|
947
|
+
function labelledStack(dir, { project, files, envFiles, workdir }) {
|
|
948
|
+
const options = (paths) => ["-p", project, ...files.flatMap((file) => ["-f", paths(file)]), ...envFiles.flatMap((file) => ["--env-file", paths(file)])];
|
|
949
|
+
return {
|
|
950
|
+
dir,
|
|
951
|
+
project,
|
|
952
|
+
run: from(dir, ["docker", "compose", ...options((file) => relative(workdir, file))]),
|
|
953
|
+
list: { argv: ["docker", "compose", ...options((file) => file)], cwd: workdir, env: process.env }
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function stackOf(recorded) {
|
|
957
|
+
return labelledStack(relative(".", recorded.workdir) || ".", recorded);
|
|
958
|
+
}
|
|
959
|
+
async function recordedNearby() {
|
|
960
|
+
const dirs = new Set([...up("."), ...children()].map((dir) => resolve(dir)));
|
|
961
|
+
return (await labels()).filter((stack) => dirs.has(stack.workdir));
|
|
962
|
+
}
|
|
963
|
+
async function attempt(stack, args) {
|
|
964
|
+
const proc = Bun.spawn([...stack.list.argv, ...args], { cwd: stack.list.cwd, env: stack.list.env, stdout: "pipe", stderr: "pipe" });
|
|
965
|
+
const [text, stderr, code] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
|
|
966
|
+
if (code === 0)
|
|
967
|
+
return { text, error: undefined };
|
|
968
|
+
const last = stderr.trim().split(`
|
|
969
|
+
`).at(-1) ?? "";
|
|
970
|
+
return { text: "", error: last === "" ? `exit ${code}` : last };
|
|
971
|
+
}
|
|
972
|
+
async function output(stack, args) {
|
|
973
|
+
return (await attempt(stack, args)).text;
|
|
974
|
+
}
|
|
975
|
+
function containers(text) {
|
|
976
|
+
const trimmed = text.trim();
|
|
977
|
+
if (!trimmed)
|
|
978
|
+
return [];
|
|
979
|
+
return trimmed.startsWith("[") ? JSON.parse(trimmed) : trimmed.split(`
|
|
980
|
+
`).map((line) => JSON.parse(line));
|
|
981
|
+
}
|
|
982
|
+
async function services(stack) {
|
|
983
|
+
const [config, ps] = await Promise.all([output(stack, ["config", "--format", "json"]), output(stack, ["ps", "--all", "--format", "json"])]);
|
|
984
|
+
const parsed = config ? JSON.parse(config) : {};
|
|
985
|
+
const declared = parsed.services ?? {};
|
|
986
|
+
const project = stack.project ?? parsed.name ?? "";
|
|
987
|
+
const running = new Map(containers(ps).map((container) => [container.Service, container]));
|
|
988
|
+
return Object.entries(declared).map(([name, service]) => ({
|
|
989
|
+
stack,
|
|
990
|
+
project,
|
|
991
|
+
name,
|
|
992
|
+
state: running.get(name)?.State ?? "none",
|
|
993
|
+
status: running.get(name)?.Status ?? "",
|
|
994
|
+
ports: (service.ports ?? []).filter((port) => (port.published ?? "") !== "").map((port) => `${port.published}->${port.target}`).join(" "),
|
|
995
|
+
image: service.image ?? "built"
|
|
996
|
+
}));
|
|
997
|
+
}
|
|
998
|
+
async function allServices() {
|
|
999
|
+
const [listed, recorded] = await Promise.all([Promise.all(stacks().map(services)), recordedNearby()]);
|
|
1000
|
+
const found = listed.flat();
|
|
1001
|
+
const known = new Set(found.map((service) => service.project));
|
|
1002
|
+
const others = recorded.filter((stack) => !known.has(stack.project));
|
|
1003
|
+
return [...found, ...(await Promise.all(others.map(async (stack) => services(stackOf(stack))))).flat()];
|
|
1004
|
+
}
|
|
1005
|
+
function named(service) {
|
|
1006
|
+
return service.stack.project === undefined ? service.name : `${service.stack.project}:${service.name}`;
|
|
1007
|
+
}
|
|
1008
|
+
function key(service) {
|
|
1009
|
+
return service.stack.dir === "." ? named(service) : `${service.stack.dir}/${named(service)}`;
|
|
1010
|
+
}
|
|
1011
|
+
async function shellOf(service) {
|
|
1012
|
+
return await output(service.stack, ["exec", "-T", service.name, "sh", "-c", "command -v bash"]) ? "bash" : "sh";
|
|
1013
|
+
}
|
|
1014
|
+
async function commandFor(service, action) {
|
|
1015
|
+
const run = service.stack.run;
|
|
1016
|
+
switch (action) {
|
|
1017
|
+
case "shell":
|
|
1018
|
+
return [...run, "exec", service.name, await shellOf(service)];
|
|
1019
|
+
case "logs":
|
|
1020
|
+
return [...run, "logs", "-f", "--tail=200", service.name];
|
|
1021
|
+
case "restart":
|
|
1022
|
+
return [...run, "restart", service.name];
|
|
1023
|
+
case "up":
|
|
1024
|
+
return [...run, "up", "-d", service.name];
|
|
1025
|
+
case "stop":
|
|
1026
|
+
return [...run, "stop", service.name];
|
|
1027
|
+
case "build":
|
|
1028
|
+
return [...run, "up", "-d", "--build", service.name];
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
function defaultAction(service) {
|
|
1032
|
+
return service.state === "running" ? "shell" : "up";
|
|
1033
|
+
}
|
|
1034
|
+
function stateColor(state) {
|
|
1035
|
+
if (state === "running")
|
|
1036
|
+
return "green";
|
|
1037
|
+
if (state === "exited" || state === "dead")
|
|
1038
|
+
return "red";
|
|
1039
|
+
return state === "none" ? "dim" : "yellow";
|
|
1040
|
+
}
|
|
1041
|
+
async function serviceNamed(dir, name, project) {
|
|
1042
|
+
const [recorded] = project === "" ? [] : await labels(project);
|
|
1043
|
+
const stack = recorded === undefined ? stackAt(dir) : labelledStack(dir, recorded);
|
|
1044
|
+
return (await services(stack)).find((candidate) => candidate.name === name);
|
|
1045
|
+
}
|
|
1046
|
+
async function logsOf(stack, name) {
|
|
1047
|
+
return output(stack, ["logs", "--tail=30", "--no-log-prefix", name]);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// src/dlog.ts
|
|
1051
|
+
var usage2 = `dlog [service...]
|
|
1052
|
+
|
|
1053
|
+
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.
|
|
1054
|
+
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.
|
|
1055
|
+
|
|
1056
|
+
Exits 0 when every service is up and quiet, 1 when one is down, unhealthy or still starting, logged an error or has logs compose could not read, so it can gate a script, 1 too when there is no compose service here. A service that exited with code 0 (a migration, an init) is not down.`;
|
|
1057
|
+
var options = {
|
|
1058
|
+
lines: { type: "string", short: "n", default: "5", description: "error lines kept per service" },
|
|
1059
|
+
since: { type: "string", default: "1h", description: "how far back the logs are read (compose --since: 30m, 2h, a timestamp)" },
|
|
1060
|
+
all: { type: "boolean", short: "a", description: "the last log lines of a service with no error too" },
|
|
1061
|
+
json: jsonOption("[{ key, state, health, status, errors: [{ line, count }], tail }]")
|
|
1062
|
+
};
|
|
1063
|
+
var ERROR = /\b(?:error|err|fatal|exception|panic|critical|crit|traceback|failed|failure|refused|denied|timeout|timed out|oom|killed)\b/i;
|
|
1064
|
+
var TAIL = 500;
|
|
1065
|
+
var MAX_WIDTH = 220;
|
|
1066
|
+
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;
|
|
1067
|
+
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;
|
|
1068
|
+
var NUMBER = /\d+/g;
|
|
1069
|
+
var STATUS_HEALTH = /\((healthy|unhealthy|health: starting)\)/;
|
|
1070
|
+
var LEADING_BRACKETS = /^(?:\[\s*\]|\s)+/;
|
|
1071
|
+
function shape(line) {
|
|
1072
|
+
return line.replace(TIMESTAMP, "").replace(IDENTIFIER, "#").replace(NUMBER, "#").replace(LEADING_BRACKETS, "").trim();
|
|
1073
|
+
}
|
|
1074
|
+
function clip(line) {
|
|
1075
|
+
return line.length > MAX_WIDTH ? `${line.slice(0, MAX_WIDTH - 1)}\u2026` : line;
|
|
1076
|
+
}
|
|
1077
|
+
function errorLines(log, limit) {
|
|
1078
|
+
const seen = new Map;
|
|
1079
|
+
for (const raw of log.split(`
|
|
1080
|
+
`)) {
|
|
1081
|
+
const line = stripAnsi(raw).trim();
|
|
1082
|
+
if (line === "" || !ERROR.test(line))
|
|
1083
|
+
continue;
|
|
1084
|
+
const shared = shape(line);
|
|
1085
|
+
const before = seen.get(shared);
|
|
1086
|
+
seen.delete(shared);
|
|
1087
|
+
seen.set(shared, { line: clip(maskLine(line)), count: (before?.count ?? 0) + 1 });
|
|
1088
|
+
}
|
|
1089
|
+
return [...seen.values()].slice(-limit);
|
|
1090
|
+
}
|
|
1091
|
+
function healthOf(state, container) {
|
|
1092
|
+
const health = (container?.Health ?? "") || (STATUS_HEALTH.exec(container?.Status ?? "")?.[1]?.replace("health: ", "") ?? "");
|
|
1093
|
+
const finished = state === "exited" && container?.ExitCode === 0;
|
|
1094
|
+
return { health, fine: (state === "running" || finished) && (health === "" || health === "healthy") };
|
|
1095
|
+
}
|
|
1096
|
+
async function read(service, since) {
|
|
1097
|
+
const [ps, logs] = await Promise.all([
|
|
1098
|
+
attempt(service.stack, ["ps", "--all", "--format", "json", service.name]),
|
|
1099
|
+
attempt(service.stack, ["logs", "--no-color", "--no-log-prefix", `--tail=${TAIL}`, `--since=${since}`, service.name])
|
|
1100
|
+
]);
|
|
1101
|
+
return { log: logs.text, container: containers(ps.text)[0], unreadable: ps.error ?? logs.error };
|
|
1102
|
+
}
|
|
1103
|
+
function withoutKeys(lines) {
|
|
1104
|
+
let inKey = false;
|
|
1105
|
+
return lines.filter((text) => {
|
|
1106
|
+
const opens = PEM_BEGIN.test(text);
|
|
1107
|
+
const hidden = inKey || opens;
|
|
1108
|
+
inKey = (inKey || opens) && !PEM_END.test(text);
|
|
1109
|
+
return !hidden;
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
function line(report) {
|
|
1113
|
+
const colors = paint(process8.stdout);
|
|
1114
|
+
const status = report.status.replace(STATUS_HEALTH, "").trim();
|
|
1115
|
+
const detail = [report.health, status].filter(Boolean).join(" \xB7 ");
|
|
1116
|
+
return `${report.key.padEnd(24)} ${colors[stateColor(report.state)](report.state.padEnd(9))} ${colors.dim(detail)}`.trimEnd();
|
|
1117
|
+
}
|
|
1118
|
+
function show(reports) {
|
|
1119
|
+
const colors = paint(process8.stdout);
|
|
1120
|
+
for (const report of reports) {
|
|
1121
|
+
console.log(line(report));
|
|
1122
|
+
if (report.unreadable !== undefined)
|
|
1123
|
+
console.log(` ${colors.red(`logs unavailable: ${report.unreadable}`)}`);
|
|
1124
|
+
for (const { line: text, count } of report.errors)
|
|
1125
|
+
console.log(` ${colors.red(text)}${count > 1 ? colors.dim(` \xD7${count}`) : ""}`);
|
|
1126
|
+
for (const text of report.tail)
|
|
1127
|
+
console.log(` ${colors.dim(text)}`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
async function dlog(args) {
|
|
1131
|
+
const parsed = parse(args, usage2, options, { 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 --json", "the report as JSON"]] });
|
|
1132
|
+
const limit = Number(parsed.values.lines);
|
|
1133
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
1134
|
+
fail("--lines takes a positive whole number");
|
|
1135
|
+
const services = await allServices();
|
|
1136
|
+
if (services.length === 0)
|
|
1137
|
+
fail("no compose service here", 1);
|
|
1138
|
+
const wanted = parsed.positionals;
|
|
1139
|
+
const unknown = wanted.filter((name) => !services.some((service) => key(service) === name || service.name === name));
|
|
1140
|
+
if (unknown.length > 0)
|
|
1141
|
+
fail(`no service named ${unknown.join(", ")}`, 1);
|
|
1142
|
+
const chosen = wanted.length === 0 ? services : services.filter((service) => wanted.includes(key(service)) || wanted.includes(service.name));
|
|
1143
|
+
const reports = await Promise.all(chosen.map(async (service) => {
|
|
1144
|
+
const { log, container, unreadable } = await read(service, parsed.values.since);
|
|
1145
|
+
const { health, fine } = healthOf(service.state, container);
|
|
1146
|
+
const lines = withoutKeys(log.split(`
|
|
1147
|
+
`));
|
|
1148
|
+
const errors = errorLines(lines.join(`
|
|
1149
|
+
`), limit);
|
|
1150
|
+
const tail = parsed.values.all === true && errors.length === 0 ? lines.map((text) => clip(maskLine(stripAnsi(text).trim()))).filter(Boolean).slice(-limit) : [];
|
|
1151
|
+
return { key: key(service), state: service.state, health, status: service.status, errors, tail, unreadable, fine };
|
|
1152
|
+
}));
|
|
1153
|
+
if (parsed.values.json === true)
|
|
1154
|
+
printJson(reports);
|
|
1155
|
+
else
|
|
1156
|
+
show(reports);
|
|
1157
|
+
const down = reports.filter((report) => !report.fine || report.unreadable !== undefined || report.errors.length > 0);
|
|
1158
|
+
if (parsed.values.json !== true && down.length > 0)
|
|
1159
|
+
console.error(paint(process8.stderr).dim(`${down.length} of ${reports.length} services need a look: ${down.map((report) => report.key).join(", ")}`));
|
|
1160
|
+
return down.length > 0 ? 1 : 0;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/dlog-cli.ts
|
|
1164
|
+
process9.exitCode = await runMain({ name: "dlog", version, run: dlog });
|