@kaynooo/env-tools 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +45 -2
- package/dist/ev.js +1542 -0
- package/dist/redact.js +1055 -0
- package/package.json +37 -4
- package/src/dotenv.ts +152 -0
package/dist/redact.js
ADDED
|
@@ -0,0 +1,1055 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/redact.ts
|
|
5
|
+
import process7 from "process";
|
|
6
|
+
|
|
7
|
+
// ../kit/src/args.ts
|
|
8
|
+
import process4 from "process";
|
|
9
|
+
import { parseArgs } from "util";
|
|
10
|
+
|
|
11
|
+
// ../kit/src/format.ts
|
|
12
|
+
import process3 from "process";
|
|
13
|
+
|
|
14
|
+
// ../kit/src/style.ts
|
|
15
|
+
import process2 from "process";
|
|
16
|
+
|
|
17
|
+
// ../../node_modules/.bun/@kaynooo+utils@2.31.0+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-mtetydvc.js
|
|
18
|
+
function x(s) {
|
|
19
|
+
let r = s.startsWith("#") ? s.slice(1).toLowerCase() : s.toLowerCase();
|
|
20
|
+
if (!/^[a-f0-9]+$/.test(r))
|
|
21
|
+
return [0, 0, 0];
|
|
22
|
+
let t = r.length, n = (c) => Number.parseInt(c, 16);
|
|
23
|
+
if (t === 3 || t === 4) {
|
|
24
|
+
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;
|
|
25
|
+
return g !== undefined ? [c, u, h, g] : [c, u, h];
|
|
26
|
+
}
|
|
27
|
+
if (t !== 6 && t !== 8)
|
|
28
|
+
throw Error(`Invalid hex color length: ${t}`);
|
|
29
|
+
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;
|
|
30
|
+
if ([a, b, o].some(Number.isNaN) || y !== undefined && Number.isNaN(y))
|
|
31
|
+
throw Error(`Invalid hex color value: ${s}`);
|
|
32
|
+
return y !== undefined ? [a, b, o, y] : [a, b, o];
|
|
33
|
+
}
|
|
34
|
+
function A(s) {
|
|
35
|
+
let [r, t, n, a] = s;
|
|
36
|
+
if (a)
|
|
37
|
+
return `#${(r << 24 | t << 16 | n << 8 | Math.round((a ?? 1) * 255)).toString(16).padStart(8, "0").toUpperCase()}`;
|
|
38
|
+
return `#${(r << 16 | t << 8 | n).toString(16).padStart(6, "0").toUpperCase()}`;
|
|
39
|
+
}
|
|
40
|
+
function R(s, r, t) {
|
|
41
|
+
s = (s % 360 + 360) % 360, t /= 100;
|
|
42
|
+
let n = r * Math.min(t, 1 - t) / 100, a = (b) => {
|
|
43
|
+
let o = (b + s / 30) % 12, y = t - n * Math.max(Math.min(o - 3, 9 - o, 1), -1);
|
|
44
|
+
return Math.round(255 * y);
|
|
45
|
+
};
|
|
46
|
+
return [a(0), a(8), a(4)];
|
|
47
|
+
}
|
|
48
|
+
function k(s) {
|
|
49
|
+
let [r, t, n] = s;
|
|
50
|
+
r /= 255, t /= 255, n /= 255;
|
|
51
|
+
let a = Math.max(r, t, n), b = Math.min(r, t, n), o = 0, y = 0, c = (a + b) / 2;
|
|
52
|
+
if (a !== b) {
|
|
53
|
+
let u = a - b;
|
|
54
|
+
if (y = c > 0 && c < 1 ? u / (1 - Math.abs(2 * c - 1)) : 0, a === r)
|
|
55
|
+
o = (t - n) / u + (t < n ? 6 : 0);
|
|
56
|
+
else if (a === t)
|
|
57
|
+
o = (n - r) / u + 2;
|
|
58
|
+
else
|
|
59
|
+
o = (r - t) / u + 4;
|
|
60
|
+
o /= 6;
|
|
61
|
+
}
|
|
62
|
+
return [Math.round(o * 360), Math.round(y * 100), Math.round(c * 100)];
|
|
63
|
+
}
|
|
64
|
+
function H(s, r, t) {
|
|
65
|
+
s = (s % 360 + 360) % 360, r /= 100, t /= 100;
|
|
66
|
+
let n = (a) => {
|
|
67
|
+
let b = (a + s / 60) % 6, o = t - t * r * Math.max(Math.min(b, 4 - b, 1), 0);
|
|
68
|
+
return Math.round(255 * o);
|
|
69
|
+
};
|
|
70
|
+
return [n(5), n(3), n(1)];
|
|
71
|
+
}
|
|
72
|
+
function f(s) {
|
|
73
|
+
let [r, t, n] = s;
|
|
74
|
+
r /= 255, t /= 255, n /= 255;
|
|
75
|
+
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;
|
|
76
|
+
if (o !== 0) {
|
|
77
|
+
if (a === r)
|
|
78
|
+
y = (t - n) / o + (t < n ? 6 : 0);
|
|
79
|
+
else if (a === t)
|
|
80
|
+
y = (n - r) / o + 2;
|
|
81
|
+
else
|
|
82
|
+
y = (r - t) / o + 4;
|
|
83
|
+
y /= 6;
|
|
84
|
+
}
|
|
85
|
+
return [Math.round(y * 360), Math.round(c * 100), Math.round(u * 100)];
|
|
86
|
+
}
|
|
87
|
+
function O(s) {
|
|
88
|
+
let r = s[0] / 255, t = s[1] / 255, n = s[2] / 255, a = 1 - Math.max(r, t, n);
|
|
89
|
+
if (a === 1)
|
|
90
|
+
return [0, 0, 0, 100];
|
|
91
|
+
let b = (1 - r - a) / (1 - a), o = (1 - t - a) / (1 - a), y = (1 - n - a) / (1 - a);
|
|
92
|
+
return [Math.round(b * 100), Math.round(o * 100), Math.round(y * 100), Math.round(a * 100)];
|
|
93
|
+
}
|
|
94
|
+
function v(s, r, t, n) {
|
|
95
|
+
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))];
|
|
96
|
+
}
|
|
97
|
+
var m = (s) => s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
98
|
+
var p = (s) => s <= 0.0031308 ? 12.92 * s : 1.055 * s ** 0.4166666666666667 - 0.055;
|
|
99
|
+
var i = (s, r) => Math.round(s * 10 ** r) / 10 ** r;
|
|
100
|
+
function T(s) {
|
|
101
|
+
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);
|
|
102
|
+
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)];
|
|
103
|
+
}
|
|
104
|
+
function M(s, r, t) {
|
|
105
|
+
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)));
|
|
106
|
+
return [u(o), u(y), u(c)];
|
|
107
|
+
}
|
|
108
|
+
function d(s) {
|
|
109
|
+
let [r, t, n] = T(s), a = Math.sqrt(t * t + n * n), b = (Math.atan2(n, t) * 180 / Math.PI + 360) % 360;
|
|
110
|
+
return [i(r, 4), i(a, 4), i(b, 2)];
|
|
111
|
+
}
|
|
112
|
+
function C(s, r, t) {
|
|
113
|
+
let n = t * Math.PI / 180;
|
|
114
|
+
return M(s, r * Math.cos(n), r * Math.sin(n));
|
|
115
|
+
}
|
|
116
|
+
function l(s) {
|
|
117
|
+
let r = s / 255;
|
|
118
|
+
return r <= 0.03928 ? r / 12.92 : ((r + 0.055) / 1.055) ** 2.4;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class e {
|
|
122
|
+
r;
|
|
123
|
+
g;
|
|
124
|
+
b;
|
|
125
|
+
a;
|
|
126
|
+
constructor(s, r, t, n = 1) {
|
|
127
|
+
this.r = s, this.g = r, this.b = t, this.a = n;
|
|
128
|
+
}
|
|
129
|
+
static from(s, r) {
|
|
130
|
+
if (s === "hex") {
|
|
131
|
+
let [t, n, a, b] = x(r);
|
|
132
|
+
return new e(t, n, a, b);
|
|
133
|
+
}
|
|
134
|
+
if (s === "rgb") {
|
|
135
|
+
if (Array.isArray(r)) {
|
|
136
|
+
let [o, y, c, u] = r;
|
|
137
|
+
return new e(o, y, c, u);
|
|
138
|
+
}
|
|
139
|
+
let { r: t, g: n, b: a, a: b } = r;
|
|
140
|
+
return new e(t, n, a, b);
|
|
141
|
+
}
|
|
142
|
+
if (s === "hsl") {
|
|
143
|
+
let t, n, a, b;
|
|
144
|
+
if (Array.isArray(r))
|
|
145
|
+
[t, n, a, b] = r;
|
|
146
|
+
else
|
|
147
|
+
({ h: t, s: n, l: a, a: b } = r);
|
|
148
|
+
let [o, y, c] = R(t, n, a);
|
|
149
|
+
return new e(o, y, c, b);
|
|
150
|
+
}
|
|
151
|
+
if (s === "hsv") {
|
|
152
|
+
let t, n, a, b;
|
|
153
|
+
if (Array.isArray(r))
|
|
154
|
+
[t, n, a, b] = r;
|
|
155
|
+
else
|
|
156
|
+
({ h: t, s: n, v: a, a: b } = r);
|
|
157
|
+
let [o, y, c] = H(t, n, a);
|
|
158
|
+
return new e(o, y, c, b);
|
|
159
|
+
}
|
|
160
|
+
if (s === "cmyk") {
|
|
161
|
+
let t, n, a, b, o;
|
|
162
|
+
if (Array.isArray(r))
|
|
163
|
+
[t, n, a, b, o] = r;
|
|
164
|
+
else
|
|
165
|
+
({ c: t, m: n, y: a, k: b, a: o } = r);
|
|
166
|
+
let [y, c, u] = v(t, n, a, b);
|
|
167
|
+
return new e(y, c, u, o);
|
|
168
|
+
}
|
|
169
|
+
if (s === "oklch") {
|
|
170
|
+
let t, n, a, b;
|
|
171
|
+
if (Array.isArray(r))
|
|
172
|
+
[t, n, a, b] = r;
|
|
173
|
+
else
|
|
174
|
+
({ l: t, c: n, h: a, a: b } = r);
|
|
175
|
+
let [o, y, c] = C(t, n, a);
|
|
176
|
+
return new e(o, y, c, b);
|
|
177
|
+
}
|
|
178
|
+
if (s === "oklab") {
|
|
179
|
+
let t, n, a, b;
|
|
180
|
+
if (Array.isArray(r))
|
|
181
|
+
[t, n, a, b] = r;
|
|
182
|
+
else
|
|
183
|
+
({ l: t, a: n, b: a, alpha: b } = r);
|
|
184
|
+
let [o, y, c] = M(t, n, a);
|
|
185
|
+
return new e(o, y, c, b);
|
|
186
|
+
}
|
|
187
|
+
throw Error("Invalid input");
|
|
188
|
+
}
|
|
189
|
+
to(s) {
|
|
190
|
+
switch (s) {
|
|
191
|
+
case "rgb":
|
|
192
|
+
return [this.r, this.g, this.b];
|
|
193
|
+
case "rgba":
|
|
194
|
+
return [this.r, this.g, this.b, this.a];
|
|
195
|
+
case "hex":
|
|
196
|
+
return A([this.r, this.g, this.b]);
|
|
197
|
+
case "hexa":
|
|
198
|
+
return A([this.r, this.g, this.b, this.a]);
|
|
199
|
+
case "hsl":
|
|
200
|
+
return k([this.r, this.g, this.b]);
|
|
201
|
+
case "hsla":
|
|
202
|
+
return [...k([this.r, this.g, this.b]), this.a];
|
|
203
|
+
case "hsv":
|
|
204
|
+
return f([this.r, this.g, this.b]);
|
|
205
|
+
case "hsva":
|
|
206
|
+
return [...f([this.r, this.g, this.b]), this.a];
|
|
207
|
+
case "cmyk":
|
|
208
|
+
return O([this.r, this.g, this.b]);
|
|
209
|
+
case "cmyka":
|
|
210
|
+
return [...O([this.r, this.g, this.b]), this.a];
|
|
211
|
+
case "oklch":
|
|
212
|
+
return d([this.r, this.g, this.b]);
|
|
213
|
+
case "oklcha":
|
|
214
|
+
return [...d([this.r, this.g, this.b]), this.a];
|
|
215
|
+
case "oklab":
|
|
216
|
+
return T([this.r, this.g, this.b]);
|
|
217
|
+
case "oklaba":
|
|
218
|
+
return [...T([this.r, this.g, this.b]), this.a];
|
|
219
|
+
default:
|
|
220
|
+
return [this.r, this.g, this.b];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
getLuminance() {
|
|
224
|
+
let s = l(this.r), r = l(this.g), t = l(this.b);
|
|
225
|
+
return 0.2126 * s + 0.7152 * r + 0.0722 * t;
|
|
226
|
+
}
|
|
227
|
+
contrastRatio(s) {
|
|
228
|
+
let r = this.getLuminance(), t = s.getLuminance(), [n, a] = r >= t ? [r, t] : [t, r];
|
|
229
|
+
return (n + 0.05) / (a + 0.05);
|
|
230
|
+
}
|
|
231
|
+
bestContrastForeground() {
|
|
232
|
+
let s = new e(255, 255, 255, 1), r = new e(0, 0, 0, 1), t = this.contrastRatio(s), n = this.contrastRatio(r);
|
|
233
|
+
return t >= n ? s : r;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ../../node_modules/.bun/@kaynooo+utils@2.31.0+d0769e28dc4dad16/node_modules/@kaynooo/utils/dist/chunk-y1msbssb.js
|
|
238
|
+
var r = 39;
|
|
239
|
+
var o = 49;
|
|
240
|
+
var f2 = "\x1B[";
|
|
241
|
+
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") => {
|
|
242
|
+
let [a, l, s] = e.from("hex", i).to("rgb");
|
|
243
|
+
return [`${n === "fg" ? 38 : 48};2;${a};${l};${s}`, n === "fg" ? r : o];
|
|
244
|
+
}, ansi256: (i, n = "fg") => [`${n === "fg" ? 38 : 48};5;${i}`, n === "fg" ? r : o] };
|
|
245
|
+
var b = new Set(["", "1", "2", "3", "true"]);
|
|
246
|
+
function h(i, n = globalThis.process?.env ?? {}) {
|
|
247
|
+
if (n.FORCE_COLOR !== undefined)
|
|
248
|
+
return b.has(n.FORCE_COLOR);
|
|
249
|
+
if (n.NO_COLOR !== undefined || n.NODE_DISABLE_COLORS !== undefined || n.TERM === "dumb")
|
|
250
|
+
return false;
|
|
251
|
+
return i?.isTTY === true;
|
|
252
|
+
}
|
|
253
|
+
function d2(i, n, a) {
|
|
254
|
+
if (!h(a.stream ?? globalThis.process?.stdout, a.env))
|
|
255
|
+
return n;
|
|
256
|
+
let l = "", s = "";
|
|
257
|
+
for (let g = 0, y = i.length;g < y; g++) {
|
|
258
|
+
let [c, p] = i[g];
|
|
259
|
+
l += `${f2}${c}m`, s = `${f2}${p}m${s}`;
|
|
260
|
+
}
|
|
261
|
+
return l + n + s;
|
|
262
|
+
}
|
|
263
|
+
function u(i, n = []) {
|
|
264
|
+
return new Proxy((l) => d2(n, l, i), { get(l, s) {
|
|
265
|
+
let g = C2[s];
|
|
266
|
+
if (Array.isArray(g))
|
|
267
|
+
return u(i, [...n, g]);
|
|
268
|
+
if (typeof g === "function")
|
|
269
|
+
return (...y) => u(i, [...n, g(...y)]);
|
|
270
|
+
throw Error(`Unknown style: ${String(s)}`);
|
|
271
|
+
} });
|
|
272
|
+
}
|
|
273
|
+
function le(i = {}) {
|
|
274
|
+
return u(i);
|
|
275
|
+
}
|
|
276
|
+
var t = le();
|
|
277
|
+
|
|
278
|
+
// ../kit/src/style.ts
|
|
279
|
+
function takesControlCodes(stream, env = process2.env) {
|
|
280
|
+
return stream.isTTY === true && env.NO_COLOR === undefined && env.NODE_DISABLE_COLORS === undefined && env.TERM !== "dumb";
|
|
281
|
+
}
|
|
282
|
+
function paint(stream, env = process2.env) {
|
|
283
|
+
return le({ stream: { isTTY: takesControlCodes(stream, env) }, env });
|
|
284
|
+
}
|
|
285
|
+
var STATUSES = {
|
|
286
|
+
passed: { symbol: "\u2713", words: ["passed", "success", "succeeded", "ok"], paint: (colors) => colors.green },
|
|
287
|
+
failed: { symbol: "\u2717", words: ["failed", "failure", "error", "errored"], paint: (colors) => colors.red },
|
|
288
|
+
running: { symbol: "\u25CF", words: ["running", "in_progress", "started"], paint: (colors) => colors.cyan },
|
|
289
|
+
queued: { symbol: "\u25CB", words: ["queued", "waiting", "pending", "created", "blocked", "scheduled"], paint: (colors) => colors.yellow },
|
|
290
|
+
skipped: { symbol: "-", words: ["skipped", "cancelled", "canceled", "manual"], paint: (colors) => colors.dim }
|
|
291
|
+
};
|
|
292
|
+
var STATUS_OF = new Map(Object.entries(STATUSES).flatMap(([status, { words }]) => words.map((word) => [word, status])));
|
|
293
|
+
|
|
294
|
+
// ../kit/src/format.ts
|
|
295
|
+
function upTo(text, separator) {
|
|
296
|
+
const end = text.indexOf(separator);
|
|
297
|
+
return end === -1 ? text : text.slice(0, end);
|
|
298
|
+
}
|
|
299
|
+
function truncate(text, width) {
|
|
300
|
+
return text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
301
|
+
}
|
|
302
|
+
var LINE_BREAKS = /\s*\n\s*/g;
|
|
303
|
+
var ESCAPE = String.fromCharCode(27);
|
|
304
|
+
function textWidth(text) {
|
|
305
|
+
return Bun.stringWidth(text);
|
|
306
|
+
}
|
|
307
|
+
function table(rows, { cellWidth, header = false, align = [], fit } = {}) {
|
|
308
|
+
const cells = cellWidth === undefined ? rows : rows.map((row) => row.map((cell) => truncate(cell.replaceAll(LINE_BREAKS, " "), cellWidth)));
|
|
309
|
+
const widths = [];
|
|
310
|
+
for (const row of cells) {
|
|
311
|
+
for (const [index, cell] of row.entries())
|
|
312
|
+
widths[index] = Math.max(widths[index] ?? 0, textWidth(cell));
|
|
313
|
+
}
|
|
314
|
+
const shown = fit === undefined ? cells : narrow(cells, widths, fit);
|
|
315
|
+
const colors = paint(process3.stdout);
|
|
316
|
+
const lines = shown.map((row) => row.map((cell, index) => {
|
|
317
|
+
const width = widths[index] ?? 0;
|
|
318
|
+
const gap = " ".repeat(Math.max(0, width - textWidth(cell)));
|
|
319
|
+
if (align[index] === "right")
|
|
320
|
+
return `${gap}${cell}`;
|
|
321
|
+
return index === row.length - 1 ? cell : `${cell}${gap}`;
|
|
322
|
+
}).join(" ").trimEnd());
|
|
323
|
+
return lines.map((line, index) => header && index === 0 ? colors.bold(line) : line).join(`
|
|
324
|
+
`);
|
|
325
|
+
}
|
|
326
|
+
function narrow(rows, widths, limit) {
|
|
327
|
+
const used = () => widths.reduce((sum, width) => sum + width, 0) + 2 * Math.max(0, widths.length - 1);
|
|
328
|
+
const cuttable = (index) => rows.every((row) => !(row[index] ?? "").includes(ESCAPE));
|
|
329
|
+
const floor = 8;
|
|
330
|
+
while (used() > limit) {
|
|
331
|
+
let widest = -1;
|
|
332
|
+
for (const [index, width] of widths.entries()) {
|
|
333
|
+
if (cuttable(index) && width > floor && width > (widths[widest] ?? 0))
|
|
334
|
+
widest = index;
|
|
335
|
+
}
|
|
336
|
+
if (widest === -1)
|
|
337
|
+
break;
|
|
338
|
+
widths[widest] = Math.max(floor, (widths[widest] ?? 0) - (used() - limit));
|
|
339
|
+
}
|
|
340
|
+
return rows.map((row) => row.map((cell, index) => textWidth(cell) > (widths[index] ?? 0) ? truncate(cell, widths[index] ?? 0) : cell));
|
|
341
|
+
}
|
|
342
|
+
function printJson(value) {
|
|
343
|
+
console.log(JSON.stringify(value, undefined, 2));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ../kit/src/args.ts
|
|
347
|
+
function jsonOption(holds) {
|
|
348
|
+
return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
class Exit extends Error {
|
|
352
|
+
code;
|
|
353
|
+
hint;
|
|
354
|
+
constructor(message, code, hint) {
|
|
355
|
+
super(message);
|
|
356
|
+
this.code = code;
|
|
357
|
+
this.hint = hint;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function fail(message, code = 2, hint) {
|
|
361
|
+
throw new Exit(message, code, hint);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
class UsageError extends Exit {
|
|
365
|
+
constructor(message) {
|
|
366
|
+
super(message, 2);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function badUsage(message) {
|
|
370
|
+
throw new UsageError(message);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
class HelpShown extends Exit {
|
|
374
|
+
constructor() {
|
|
375
|
+
super("help shown", 0);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
class Completing {
|
|
380
|
+
options;
|
|
381
|
+
constructor(options) {
|
|
382
|
+
this.options = options;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
var completion = { active: false };
|
|
386
|
+
var OPTIONAL = /\[[^\]]+\]/g;
|
|
387
|
+
var USAGE_PREFIX = /^usage:\s*/i;
|
|
388
|
+
var FULL_STOP = /\.$/;
|
|
389
|
+
function usageLine(usage, colors = paint(process4.stdout)) {
|
|
390
|
+
return `${colors.bold("Usage:")} ${usage.replace(USAGE_PREFIX, "").replaceAll(OPTIONAL, (part) => colors.dim(part))}`;
|
|
391
|
+
}
|
|
392
|
+
function helpText(usage, options, colors = paint(process4.stdout), { examples = [], replaces } = {}) {
|
|
393
|
+
return [usageLine(usage, colors), ...replacesRows(replaces, colors), "", colors.bold("Options:"), ...optionRows(options, colors), ...exampleRows(examples, colors)].join(`
|
|
394
|
+
`);
|
|
395
|
+
}
|
|
396
|
+
function replacesRows(replaces, colors = paint(process4.stdout)) {
|
|
397
|
+
return replaces === undefined ? [] : ["", `${colors.bold("Instead of:")} ${replaces}`];
|
|
398
|
+
}
|
|
399
|
+
function wantsHelpJson(env = process4.env) {
|
|
400
|
+
return env.KIT_HELP === "json";
|
|
401
|
+
}
|
|
402
|
+
function description(usage, { examples = [], replaces } = {}) {
|
|
403
|
+
const lines = usage.replace(USAGE_PREFIX, "").split(`
|
|
404
|
+
`);
|
|
405
|
+
const first = lines[0];
|
|
406
|
+
const colon = first.indexOf(": ");
|
|
407
|
+
const prose = lines[lines.indexOf("") + 1] ?? "";
|
|
408
|
+
const fallback = lines.includes("") ? `${prose.charAt(0).toLowerCase()}${prose.slice(1)}`.replace(FULL_STOP, "") : "";
|
|
409
|
+
return {
|
|
410
|
+
synopsis: colon === -1 ? first : first.slice(0, colon),
|
|
411
|
+
summary: colon === -1 ? fallback : first.slice(colon + 2),
|
|
412
|
+
...replaces === undefined ? {} : { replaces },
|
|
413
|
+
examples: examples.map((example) => typeof example === "string" ? { line: example } : { line: example[0], note: example[1] })
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function exampleRows(examples, colors = paint(process4.stdout)) {
|
|
417
|
+
if (examples.length === 0)
|
|
418
|
+
return [];
|
|
419
|
+
return ["", colors.bold("Examples:"), ...examples.map((example) => typeof example === "string" ? ` ${example}` : ` ${example[0]} ${colors.dim(`# ${example[1]}`)}`)];
|
|
420
|
+
}
|
|
421
|
+
function optionRows(options, colors = paint(process4.stdout)) {
|
|
422
|
+
const rows = Object.entries(options).map(([name, option]) => {
|
|
423
|
+
const short = option.short === undefined ? " " : `${colors.cyan(`-${option.short}`)}, `;
|
|
424
|
+
const value = option.type === "string" ? ` ${colors.dim("<value>")}` : "";
|
|
425
|
+
return { flag: `${short}${colors.cyan(`--${name}`)}${value}`, width: `-x, --${name}`.length + (option.type === "string" ? " <value>".length : 0), description: option.description };
|
|
426
|
+
});
|
|
427
|
+
const width = Math.max(0, ...rows.map((row) => row.width));
|
|
428
|
+
return rows.map((row) => ` ${row.flag}${" ".repeat(width - row.width)} ${colors.dim(row.description)}`);
|
|
429
|
+
}
|
|
430
|
+
var OUTPUT_OPTIONS = {
|
|
431
|
+
color: { type: "string", description: "auto (on a terminal alone), always or never" },
|
|
432
|
+
plain: { type: "boolean", description: "plain text, no colour or spinner: --color never" }
|
|
433
|
+
};
|
|
434
|
+
function applyOutput(color, plain, env = process4.env) {
|
|
435
|
+
const when = plain === true ? "never" : color ?? "auto";
|
|
436
|
+
if (when === "auto")
|
|
437
|
+
return;
|
|
438
|
+
if (when !== "always" && when !== "never")
|
|
439
|
+
badUsage(`--color takes auto, always or never, not ${when}`);
|
|
440
|
+
if (when === "always") {
|
|
441
|
+
env.FORCE_COLOR = "1";
|
|
442
|
+
delete env.NO_COLOR;
|
|
443
|
+
} else {
|
|
444
|
+
env.NO_COLOR = "1";
|
|
445
|
+
delete env.FORCE_COLOR;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function parse(args, usage, options, settings = {}) {
|
|
449
|
+
const output = Object.fromEntries(Object.entries(OUTPUT_OPTIONS).filter(([name]) => !(name in options)));
|
|
450
|
+
const all = { ...options, ...output, help: { type: "boolean", short: "h", description: "show this help" } };
|
|
451
|
+
if (completion.active)
|
|
452
|
+
throw new Completing(all);
|
|
453
|
+
const separator = settings.rest === true ? args.indexOf("--") : -1;
|
|
454
|
+
const own = separator === -1 ? args : args.slice(0, separator);
|
|
455
|
+
const rest = separator === -1 ? undefined : args.slice(separator + 1);
|
|
456
|
+
let parsed;
|
|
457
|
+
try {
|
|
458
|
+
parsed = parseArgs({ args: joinDashValues(own, all), options: all, allowPositionals: true, allowNegative: settings.allowNegative === true, strict: true });
|
|
459
|
+
} catch (error) {
|
|
460
|
+
badUsage(parseError(error));
|
|
461
|
+
}
|
|
462
|
+
const values = parsed.values;
|
|
463
|
+
applyOutput("color" in output ? values.color : undefined, "plain" in output ? values.plain : undefined);
|
|
464
|
+
if (values.help === true) {
|
|
465
|
+
console.log(wantsHelpJson() ? JSON.stringify(description(usage, settings)) : helpText(usage, all, undefined, settings));
|
|
466
|
+
throw new HelpShown;
|
|
467
|
+
}
|
|
468
|
+
if (settings.positionals !== undefined)
|
|
469
|
+
checkPositionals(parsed.positionals, settings.positionals);
|
|
470
|
+
return { ...parsed, positionals: parsed.positionals, rest };
|
|
471
|
+
}
|
|
472
|
+
function checkPositionals(given, names) {
|
|
473
|
+
const variadic = names.at(-1)?.replace("]", "").endsWith("...") === true;
|
|
474
|
+
const required = names.filter((name) => !name.startsWith("[")).length;
|
|
475
|
+
if (given.length < required)
|
|
476
|
+
badUsage(`missing ${names[given.length]}`);
|
|
477
|
+
if (!variadic && given.length > names.length)
|
|
478
|
+
badUsage(`unexpected argument ${given[names.length]}`);
|
|
479
|
+
}
|
|
480
|
+
function joinDashValues(args, options) {
|
|
481
|
+
const longOf = new Map(Object.entries(options).flatMap(([name, option]) => option.short === undefined ? [[`--${name}`, name]] : [[`--${name}`, name], [`-${option.short}`, name]]));
|
|
482
|
+
const isOption = (arg) => arg === "--" || longOf.has(arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg);
|
|
483
|
+
const joined = [];
|
|
484
|
+
for (let i = 0;i < args.length; i++) {
|
|
485
|
+
const arg = args[i] ?? "";
|
|
486
|
+
if (arg === "--")
|
|
487
|
+
return [...joined, ...args.slice(i)];
|
|
488
|
+
const name = longOf.get(arg);
|
|
489
|
+
const next = args[i + 1];
|
|
490
|
+
if (name !== undefined && options[name]?.type === "string" && next !== undefined && next.startsWith("-") && next !== "-" && !isOption(next)) {
|
|
491
|
+
joined.push(`--${name}=${next}`);
|
|
492
|
+
i++;
|
|
493
|
+
} else {
|
|
494
|
+
joined.push(arg);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return joined;
|
|
498
|
+
}
|
|
499
|
+
function parseError(error) {
|
|
500
|
+
const message = upTo(error instanceof Error ? error.message : String(error), ". ");
|
|
501
|
+
return `${message.charAt(0).toLowerCase()}${message.slice(1)}`;
|
|
502
|
+
}
|
|
503
|
+
async function readInput(path, { noStdin = "nothing is piped on stdin" } = {}) {
|
|
504
|
+
if (path === "-") {
|
|
505
|
+
if (process4.stdin.isTTY)
|
|
506
|
+
fail(noStdin);
|
|
507
|
+
return Bun.stdin.text();
|
|
508
|
+
}
|
|
509
|
+
const file = Bun.file(path);
|
|
510
|
+
if (!await file.exists())
|
|
511
|
+
fail(`no file ${path}`);
|
|
512
|
+
return file.text();
|
|
513
|
+
}
|
|
514
|
+
// ../kit/src/cli.ts
|
|
515
|
+
import process5 from "process";
|
|
516
|
+
|
|
517
|
+
// ../kit/src/text.ts
|
|
518
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[\\d;?]*[a-z]`, "gi");
|
|
519
|
+
function distance(a, b) {
|
|
520
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
521
|
+
for (const [i, left] of [...a].entries()) {
|
|
522
|
+
const current = [i + 1];
|
|
523
|
+
for (const [j, right] of [...b].entries())
|
|
524
|
+
current.push(Math.min((previous[j + 1] ?? 0) + 1, (current[j] ?? 0) + 1, (previous[j] ?? 0) + (left === right ? 0 : 1)));
|
|
525
|
+
previous = current;
|
|
526
|
+
}
|
|
527
|
+
return previous[b.length] ?? 0;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// ../kit/src/cli.ts
|
|
531
|
+
var LEVEL_OPTIONS = {
|
|
532
|
+
...OUTPUT_OPTIONS,
|
|
533
|
+
help: { type: "boolean", short: "h", description: "show this help" }
|
|
534
|
+
};
|
|
535
|
+
var TOP_OPTIONS = { ...LEVEL_OPTIONS, version: { type: "boolean", short: "v", description: "print the version" } };
|
|
536
|
+
function commandRows(commands, colors) {
|
|
537
|
+
const width = Math.max(...Object.keys(commands).map((name) => name.length));
|
|
538
|
+
const groups = new Map([["Commands:", []]]);
|
|
539
|
+
for (const [name, { summary, group }] of Object.entries(commands)) {
|
|
540
|
+
const heading = group === undefined ? "Commands:" : `${group}:`;
|
|
541
|
+
const rows = groups.get(heading) ?? [];
|
|
542
|
+
rows.push(` ${colors.cyan(name.padEnd(width))} ${colors.dim(summary)}`);
|
|
543
|
+
groups.set(heading, rows);
|
|
544
|
+
}
|
|
545
|
+
return [...groups].filter(([, rows]) => rows.length > 0).flatMap(([heading, rows], index) => [...index === 0 ? [] : [""], colors.bold(heading), ...rows]);
|
|
546
|
+
}
|
|
547
|
+
function takeOutputFlags(argv) {
|
|
548
|
+
let index = 0;
|
|
549
|
+
for (;index < argv.length; index++) {
|
|
550
|
+
const word = argv[index];
|
|
551
|
+
if (word === "--plain")
|
|
552
|
+
applyOutput(undefined, true);
|
|
553
|
+
else if (word.startsWith("--color="))
|
|
554
|
+
applyOutput(word.slice("--color=".length), undefined);
|
|
555
|
+
else if (word === "--color" && argv[index + 1] !== undefined)
|
|
556
|
+
applyOutput(argv[++index], undefined);
|
|
557
|
+
else
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
return argv.slice(index);
|
|
561
|
+
}
|
|
562
|
+
function closest(name, names) {
|
|
563
|
+
const started = names.find((known) => known.startsWith(name));
|
|
564
|
+
if (started !== undefined)
|
|
565
|
+
return started;
|
|
566
|
+
const [best] = names.map((known) => ({ known, far: distance(name, known) })).sort((a, b) => a.far - b.far);
|
|
567
|
+
return best !== undefined && best.far <= (name.length < 4 ? 1 : 2) ? best.known : undefined;
|
|
568
|
+
}
|
|
569
|
+
async function help({ path, header, summary, commands, defaultCommand, examples, replaces }, stream) {
|
|
570
|
+
const colors = paint(stream);
|
|
571
|
+
const prefix = path.join(" ");
|
|
572
|
+
if (wantsHelpJson())
|
|
573
|
+
return JSON.stringify(description(`${prefix} <command> [options]: ${summary}`, { examples, replaces }));
|
|
574
|
+
const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
|
|
575
|
+
const options = fallback !== undefined && "run" in fallback ? await optionsOf(fallback.run) : undefined;
|
|
576
|
+
const own = Object.entries(options ?? {}).filter(([name]) => !(name in TOP_OPTIONS));
|
|
577
|
+
return [
|
|
578
|
+
header(colors),
|
|
579
|
+
"",
|
|
580
|
+
usageLine(`${prefix} ${defaultCommand === undefined ? colors.cyan("<command>") : `[${colors.cyan("<command>")}]`} [options]`, colors),
|
|
581
|
+
...replacesRows(replaces, colors),
|
|
582
|
+
"",
|
|
583
|
+
...commandRows(commands, colors),
|
|
584
|
+
...own.length === 0 ? [] : ["", `${colors.bold(`Options of ${defaultCommand}:`)} ${colors.dim("the command run when none is named")}`, ...optionRows(Object.fromEntries(own), colors)],
|
|
585
|
+
"",
|
|
586
|
+
colors.bold("Options:"),
|
|
587
|
+
...optionRows(path.length === 1 ? TOP_OPTIONS : LEVEL_OPTIONS, colors),
|
|
588
|
+
...exampleRows(examples ?? [], colors),
|
|
589
|
+
"",
|
|
590
|
+
`${colors.dim("Run")} ${prefix} ${colors.cyan("<command>")} --help ${colors.dim("for its options.")}`
|
|
591
|
+
].join(`
|
|
592
|
+
`);
|
|
593
|
+
}
|
|
594
|
+
function printError(prefix, message, hint, cause) {
|
|
595
|
+
const colors = paint(process5.stderr);
|
|
596
|
+
const styled = takesControlCodes(process5.stderr);
|
|
597
|
+
const head = styled ? `${colors.red.bold("error")} ${colors.dim(`${prefix}:`)}` : `error: ${prefix}:`;
|
|
598
|
+
const under = (label, text, tint) => text === undefined ? "" : `
|
|
599
|
+
${styled ? tint(label) : `${label}:`} ${text}`;
|
|
600
|
+
console.error(`${head} ${message}${under("cause", cause, colors.dim)}${under("hint", hint, colors.yellow)}`);
|
|
601
|
+
}
|
|
602
|
+
function printJsonError(message, hint, cause) {
|
|
603
|
+
console.error(JSON.stringify({ error: message, ...hint === undefined ? {} : { hint }, ...cause === undefined ? {} : { cause } }));
|
|
604
|
+
}
|
|
605
|
+
function wantsJson(argv) {
|
|
606
|
+
const end = argv.indexOf("--");
|
|
607
|
+
return (end === -1 ? argv : argv.slice(0, end)).includes("--json");
|
|
608
|
+
}
|
|
609
|
+
function reportThrown(prefix, error, redact = (text) => text, json = false) {
|
|
610
|
+
if (error instanceof HelpShown)
|
|
611
|
+
return 0;
|
|
612
|
+
const help = error instanceof UsageError ? `run ${prefix} --help for its options` : undefined;
|
|
613
|
+
const named = error instanceof Error && !(error instanceof Exit) && error.name !== "Error" ? `${error.name}: ${error.message}` : undefined;
|
|
614
|
+
const cause = error instanceof Error && error.cause instanceof Error ? redact(error.cause.message) : undefined;
|
|
615
|
+
const report = json ? printJsonError : (message, hint, causeText) => printError(prefix, message, hint, causeText);
|
|
616
|
+
report(redact(named ?? (error instanceof Error ? error.message : String(error))), error instanceof Exit ? error.hint ?? help : undefined, cause);
|
|
617
|
+
return error instanceof Exit ? error.code : 1;
|
|
618
|
+
}
|
|
619
|
+
async function dispatch(level, typed, { version, redact }) {
|
|
620
|
+
const prefix = level.path.join(" ");
|
|
621
|
+
let argv;
|
|
622
|
+
try {
|
|
623
|
+
argv = takeOutputFlags(typed);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
return reportThrown(prefix, error);
|
|
626
|
+
}
|
|
627
|
+
const [name, ...args] = argv;
|
|
628
|
+
if (name === undefined && level.path.length > 1) {
|
|
629
|
+
console.error(await help(level, process5.stderr));
|
|
630
|
+
return 2;
|
|
631
|
+
}
|
|
632
|
+
if (name === undefined || name === "help" || name === "--help" || name === "-h") {
|
|
633
|
+
console.log(await help(level, process5.stdout));
|
|
634
|
+
return 0;
|
|
635
|
+
}
|
|
636
|
+
if (level.path.length === 1 && (name === "--version" || name === "-v")) {
|
|
637
|
+
console.log(version);
|
|
638
|
+
return 0;
|
|
639
|
+
}
|
|
640
|
+
let command = level.commands[level.aliases?.[name] ?? name];
|
|
641
|
+
let rest = args;
|
|
642
|
+
if (command === undefined && level.defaultCommand !== undefined) {
|
|
643
|
+
command = level.commands[level.defaultCommand];
|
|
644
|
+
rest = argv;
|
|
645
|
+
}
|
|
646
|
+
if (command === undefined) {
|
|
647
|
+
const meant = closest(name, Object.keys(level.commands));
|
|
648
|
+
printError(prefix, `no command ${name}`, meant === undefined ? `run ${prefix} --help for its commands` : `did you mean ${meant}?`);
|
|
649
|
+
return 2;
|
|
650
|
+
}
|
|
651
|
+
if ("commands" in command) {
|
|
652
|
+
const { summary } = command;
|
|
653
|
+
return dispatch({ path: [...level.path, name], header: (colors) => `${colors.bold(`${prefix} ${name}`)} ${summary}`, summary, commands: command.commands, aliases: command.aliases, defaultCommand: undefined, examples: command.examples, replaces: undefined }, rest, { version, redact });
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
return await command.run(rest);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
return reportThrown(`${prefix} ${name}`, error, redact, wantsJson(rest));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
var COMPLETE = "__complete";
|
|
662
|
+
var FILES = "__files__";
|
|
663
|
+
async function optionsOf(run) {
|
|
664
|
+
completion.active = true;
|
|
665
|
+
try {
|
|
666
|
+
await run([]);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
if (error instanceof Completing)
|
|
669
|
+
return error.options;
|
|
670
|
+
} finally {
|
|
671
|
+
completion.active = false;
|
|
672
|
+
}
|
|
673
|
+
return {};
|
|
674
|
+
}
|
|
675
|
+
async function leafCandidates(run, words) {
|
|
676
|
+
if (!(words.at(-1) ?? "").startsWith("-"))
|
|
677
|
+
return [FILES];
|
|
678
|
+
return Object.entries(await optionsOf(run)).map(([name, option]) => `--${name} ${option.description}`);
|
|
679
|
+
}
|
|
680
|
+
async function candidates(commands, aliases, defaultCommand, words) {
|
|
681
|
+
const [word = "", ...rest] = words;
|
|
682
|
+
const command = commands[aliases?.[word] ?? word];
|
|
683
|
+
if (rest.length === 0 && !word.startsWith("-"))
|
|
684
|
+
return Object.entries(commands).map(([name, { summary }]) => `${name} ${summary}`);
|
|
685
|
+
if (command === undefined) {
|
|
686
|
+
const fallback = defaultCommand === undefined ? undefined : commands[defaultCommand];
|
|
687
|
+
return fallback !== undefined && "run" in fallback ? leafCandidates(fallback.run, words) : [];
|
|
688
|
+
}
|
|
689
|
+
return "commands" in command ? candidates(command.commands, command.aliases, undefined, rest) : leafCandidates(command.run, rest);
|
|
690
|
+
}
|
|
691
|
+
async function printCandidates(found) {
|
|
692
|
+
for (const line of await found)
|
|
693
|
+
console.log(line);
|
|
694
|
+
return 0;
|
|
695
|
+
}
|
|
696
|
+
async function runCli(cli, argv = process5.argv.slice(2)) {
|
|
697
|
+
if (argv[0] === COMPLETE)
|
|
698
|
+
return printCandidates(candidates(cli.commands, cli.aliases, cli.defaultCommand, argv.slice(1)));
|
|
699
|
+
return dispatch({ path: [cli.name], header: (colors) => `${colors.bold(cli.name)} ${colors.dim(cli.version)} ${cli.about}`, summary: cli.about, commands: cli.commands, aliases: cli.aliases, defaultCommand: cli.defaultCommand, examples: cli.examples, replaces: cli.replaces }, argv, { version: cli.version, redact: cli.redact });
|
|
700
|
+
}
|
|
701
|
+
async function runMain({ name, version, run, redact }, argv = process5.argv.slice(2)) {
|
|
702
|
+
if (argv[0] === COMPLETE)
|
|
703
|
+
return printCandidates(leafCandidates(run, argv.slice(1)));
|
|
704
|
+
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
705
|
+
console.log(version);
|
|
706
|
+
return 0;
|
|
707
|
+
}
|
|
708
|
+
try {
|
|
709
|
+
return await run(argv);
|
|
710
|
+
} catch (error) {
|
|
711
|
+
return reportThrown(name, error, redact, wantsJson(argv));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
// package.json
|
|
715
|
+
var version = "0.5.0";
|
|
716
|
+
|
|
717
|
+
// src/files.ts
|
|
718
|
+
import { readdir } from "fs/promises";
|
|
719
|
+
|
|
720
|
+
// src/dotenv.ts
|
|
721
|
+
var ASSIGNMENT = /^\s*(?:export\s+)?([A-Z_][\w.-]*)\s*=(.*)$/i;
|
|
722
|
+
var EXPORTED = /^\s*export\s/;
|
|
723
|
+
var INLINE_COMMENT = /\s+#.*$/;
|
|
724
|
+
var CARRIAGE_RETURN = /\r$/;
|
|
725
|
+
var ESCAPES = { n: `
|
|
726
|
+
`, r: "\r", t: "\t", '"': '"', "\\": "\\" };
|
|
727
|
+
var NEEDS_QUOTES = /[\s#"'\\$`]/;
|
|
728
|
+
function closingQuote(text, quote) {
|
|
729
|
+
for (let index = 0;index < text.length; index++) {
|
|
730
|
+
if (quote === '"' && text[index] === "\\")
|
|
731
|
+
index++;
|
|
732
|
+
else if (text[index] === quote)
|
|
733
|
+
return index;
|
|
734
|
+
}
|
|
735
|
+
return -1;
|
|
736
|
+
}
|
|
737
|
+
function unescapeDouble(text) {
|
|
738
|
+
let out = "";
|
|
739
|
+
for (let index = 0;index < text.length; index++) {
|
|
740
|
+
const next = text[index + 1];
|
|
741
|
+
if (text[index] === "\\" && next !== undefined && next in ESCAPES) {
|
|
742
|
+
out += ESCAPES[next];
|
|
743
|
+
index++;
|
|
744
|
+
} else {
|
|
745
|
+
out += text[index];
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return out;
|
|
749
|
+
}
|
|
750
|
+
function parseDotenv(text) {
|
|
751
|
+
const physical = text.split(`
|
|
752
|
+
`);
|
|
753
|
+
const lines = [];
|
|
754
|
+
for (let index = 0;index < physical.length; index++) {
|
|
755
|
+
const line = physical[index] ?? "";
|
|
756
|
+
const match = ASSIGNMENT.exec(line.replace(CARRIAGE_RETURN, ""));
|
|
757
|
+
if (match === null) {
|
|
758
|
+
lines.push({ raw: line });
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
const [, key = "", rest = ""] = match;
|
|
762
|
+
const start = rest.trimStart();
|
|
763
|
+
const quote = start[0];
|
|
764
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") {
|
|
765
|
+
lines.push({ raw: line, key, value: start.replace(INLINE_COMMENT, "").trim() });
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
let body = start.slice(1);
|
|
769
|
+
let raw = line;
|
|
770
|
+
let end = closingQuote(body, quote);
|
|
771
|
+
while (end === -1 && index + 1 < physical.length) {
|
|
772
|
+
index++;
|
|
773
|
+
body += `
|
|
774
|
+
${(physical[index] ?? "").replace(CARRIAGE_RETURN, "")}`;
|
|
775
|
+
raw += `
|
|
776
|
+
${physical[index] ?? ""}`;
|
|
777
|
+
end = closingQuote(body, quote);
|
|
778
|
+
}
|
|
779
|
+
const inner = end === -1 ? body : body.slice(0, end);
|
|
780
|
+
lines.push({ raw, key, value: quote === '"' ? unescapeDouble(inner) : inner });
|
|
781
|
+
}
|
|
782
|
+
return { lines };
|
|
783
|
+
}
|
|
784
|
+
function serializeDotenv(file) {
|
|
785
|
+
return file.lines.map((line) => line.raw).join(`
|
|
786
|
+
`);
|
|
787
|
+
}
|
|
788
|
+
function entries(file) {
|
|
789
|
+
const map = new Map;
|
|
790
|
+
for (const line of file.lines) {
|
|
791
|
+
if (line.key !== undefined)
|
|
792
|
+
map.set(line.key, line.value ?? "");
|
|
793
|
+
}
|
|
794
|
+
return map;
|
|
795
|
+
}
|
|
796
|
+
function formatAssignment(key, value) {
|
|
797
|
+
if (value === "" || !NEEDS_QUOTES.test(value))
|
|
798
|
+
return `${key}=${value}`;
|
|
799
|
+
const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("\r", "\\r").replaceAll(`
|
|
800
|
+
`, "\\n");
|
|
801
|
+
return `${key}="${escaped}"`;
|
|
802
|
+
}
|
|
803
|
+
function setLine(file, key, value, raw) {
|
|
804
|
+
const index = file.lines.findLastIndex((line) => line.key === key);
|
|
805
|
+
const existing = file.lines[index];
|
|
806
|
+
if (existing !== undefined) {
|
|
807
|
+
const exported = EXPORTED.test(existing.raw) ? "export " : "";
|
|
808
|
+
file.lines[index] = { raw: raw ?? `${exported}${formatAssignment(key, value)}`, key, value };
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const line = { raw: raw ?? formatAssignment(key, value), key, value };
|
|
812
|
+
const last = file.lines.at(-1);
|
|
813
|
+
if (last === undefined)
|
|
814
|
+
file.lines.push(line, { raw: "" });
|
|
815
|
+
else if (last.raw === "")
|
|
816
|
+
file.lines.splice(-1, 0, line);
|
|
817
|
+
else
|
|
818
|
+
file.lines.push(line, { raw: "" });
|
|
819
|
+
}
|
|
820
|
+
function removeKey(file, key) {
|
|
821
|
+
const before = file.lines.length;
|
|
822
|
+
file.lines = file.lines.filter((line) => line.key !== key);
|
|
823
|
+
return before - file.lines.length;
|
|
824
|
+
}
|
|
825
|
+
function lastLine(file, key) {
|
|
826
|
+
return file.lines.findLast((line) => line.key === key);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/mask.ts
|
|
830
|
+
var MIN_KNOWN_LENGTH = 6;
|
|
831
|
+
var SECRET_KEY = /TOKEN|SECRET|PASSWORD|KEY|PRIVATE|DSN/i;
|
|
832
|
+
var URL_PASSWORD = /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]*:([^\s@/]+)@/dgi;
|
|
833
|
+
var ESCAPE2 = /[.*+?^${}()|[\]\\]/g;
|
|
834
|
+
var TOKEN_FORMATS = [
|
|
835
|
+
["GitHub token", /\b(?:gh[pousr]_|github_pat_)\w{20,}/g],
|
|
836
|
+
["GitLab token", /\bglpat-[\w-]{20,}/g],
|
|
837
|
+
["API key", /\bsk-[\w-]{20,}/g],
|
|
838
|
+
["Slack token", /\bxox[abprs]-[\w-]{10,}/g],
|
|
839
|
+
["JWT", /\beyJ[\w-]+\.[\w-]+\.[\w-]+/g]
|
|
840
|
+
];
|
|
841
|
+
var PEM_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/;
|
|
842
|
+
var PEM_END = /-----END [A-Z ]*PRIVATE KEY-----/;
|
|
843
|
+
function isSecretKey(key) {
|
|
844
|
+
return SECRET_KEY.test(key);
|
|
845
|
+
}
|
|
846
|
+
function hint(value) {
|
|
847
|
+
const shown = Math.min(3, Math.floor(value.length / 4));
|
|
848
|
+
return `${value.slice(0, shown)}***(${value.length})`;
|
|
849
|
+
}
|
|
850
|
+
function knownSecrets(pairs) {
|
|
851
|
+
const keys = new Map;
|
|
852
|
+
for (const [key, value] of pairs) {
|
|
853
|
+
if (value.length >= MIN_KNOWN_LENGTH && !keys.has(value))
|
|
854
|
+
keys.set(value, key);
|
|
855
|
+
}
|
|
856
|
+
if (keys.size === 0)
|
|
857
|
+
return { pattern: undefined, keys };
|
|
858
|
+
const alternatives = [...keys.keys()].sort((a, b) => b.length - a.length).map((value) => value.replace(ESCAPE2, "\\$&"));
|
|
859
|
+
return { pattern: new RegExp(`(?<!\\w)(?:${alternatives.join("|")})(?!\\w)`, "g"), keys };
|
|
860
|
+
}
|
|
861
|
+
function urlPasswords(text) {
|
|
862
|
+
return [...text.matchAll(URL_PASSWORD)].flatMap((url) => url[1] ?? []);
|
|
863
|
+
}
|
|
864
|
+
function findSecrets(line, known) {
|
|
865
|
+
const found = [];
|
|
866
|
+
for (const url of line.matchAll(URL_PASSWORD)) {
|
|
867
|
+
const [start, end] = url.indices?.[1] ?? [];
|
|
868
|
+
if (start !== undefined && end !== undefined)
|
|
869
|
+
found.push({ start, end, kind: "URL password", mask: "***" });
|
|
870
|
+
}
|
|
871
|
+
for (const [kind, pattern] of TOKEN_FORMATS) {
|
|
872
|
+
for (const token of line.matchAll(pattern))
|
|
873
|
+
found.push({ start: token.index, end: token.index + token[0].length, kind, mask: hint(token[0]) });
|
|
874
|
+
}
|
|
875
|
+
if (known?.pattern !== undefined) {
|
|
876
|
+
for (const value of line.matchAll(known.pattern))
|
|
877
|
+
found.push({ start: value.index, end: value.index + value[0].length, kind: `value of ${known.keys.get(value[0])}`, mask: hint(value[0]) });
|
|
878
|
+
}
|
|
879
|
+
found.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
880
|
+
const kept = [];
|
|
881
|
+
for (const match of found) {
|
|
882
|
+
if (match.start >= (kept.at(-1)?.end ?? 0))
|
|
883
|
+
kept.push(match);
|
|
884
|
+
}
|
|
885
|
+
return kept;
|
|
886
|
+
}
|
|
887
|
+
function maskLine(line, known) {
|
|
888
|
+
let out = "";
|
|
889
|
+
let at = 0;
|
|
890
|
+
for (const match of findSecrets(line, known)) {
|
|
891
|
+
out += line.slice(at, match.start) + match.mask;
|
|
892
|
+
at = match.end;
|
|
893
|
+
}
|
|
894
|
+
return out + line.slice(at);
|
|
895
|
+
}
|
|
896
|
+
function maskValue(key, value) {
|
|
897
|
+
if (value === "")
|
|
898
|
+
return value;
|
|
899
|
+
return isSecretKey(key) || PEM_BEGIN.test(value) ? hint(value) : maskLine(value);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// src/files.ts
|
|
903
|
+
var ENV_FILE = /^\.env(?:\..+)?$/;
|
|
904
|
+
var TEMPLATE = /\.(?:example|sample|template|dist|defaults)$/;
|
|
905
|
+
async function envFiles(folder = process.cwd()) {
|
|
906
|
+
const found = await readdir(folder, { withFileTypes: true });
|
|
907
|
+
return found.filter((entry) => entry.isFile() && ENV_FILE.test(entry.name)).map((entry) => entry.name).sort();
|
|
908
|
+
}
|
|
909
|
+
async function readEnv(path) {
|
|
910
|
+
const file = Bun.file(path);
|
|
911
|
+
if (!await file.exists())
|
|
912
|
+
fail(`${path} does not exist`);
|
|
913
|
+
return parseDotenv(await file.text());
|
|
914
|
+
}
|
|
915
|
+
async function writeEnv(path, file) {
|
|
916
|
+
await Bun.write(path, serializeDotenv(file));
|
|
917
|
+
}
|
|
918
|
+
async function loadKnown(folder = process.cwd()) {
|
|
919
|
+
const pairs = [];
|
|
920
|
+
for (const name of await envFiles(folder)) {
|
|
921
|
+
for (const [key, value] of entries(parseDotenv(await Bun.file(`${folder}/${name}`).text()))) {
|
|
922
|
+
if (isSecretKey(key))
|
|
923
|
+
pairs.push([key, value]);
|
|
924
|
+
else
|
|
925
|
+
pairs.push(...urlPasswords(value).map((password) => [key, password]));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return knownSecrets(pairs);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/redactor.ts
|
|
932
|
+
var DIFF_START = /^(?:diff --git |@@ )/;
|
|
933
|
+
var NEW_FILE = /^\+\+\+ (?:b\/)?(.+)$/;
|
|
934
|
+
var HUNK = /^@@ -\d+(?:,\d+)? \+(\d+)/;
|
|
935
|
+
|
|
936
|
+
class Redactor {
|
|
937
|
+
known;
|
|
938
|
+
pending = "";
|
|
939
|
+
inKey = false;
|
|
940
|
+
hidden = false;
|
|
941
|
+
constructor(known) {
|
|
942
|
+
this.known = known;
|
|
943
|
+
}
|
|
944
|
+
push(chunk) {
|
|
945
|
+
this.pending += chunk;
|
|
946
|
+
const end = this.pending.lastIndexOf(`
|
|
947
|
+
`);
|
|
948
|
+
if (end === -1)
|
|
949
|
+
return "";
|
|
950
|
+
const complete = this.pending.slice(0, end + 1);
|
|
951
|
+
this.pending = this.pending.slice(end + 1);
|
|
952
|
+
return complete.split(`
|
|
953
|
+
`).slice(0, -1).map((line) => this.line(line)).filter((line) => line !== undefined).map((line) => `${line}
|
|
954
|
+
`).join("");
|
|
955
|
+
}
|
|
956
|
+
end() {
|
|
957
|
+
const rest = this.pending;
|
|
958
|
+
this.pending = "";
|
|
959
|
+
return rest === "" ? "" : this.line(rest) ?? "";
|
|
960
|
+
}
|
|
961
|
+
line(line) {
|
|
962
|
+
if (this.inKey) {
|
|
963
|
+
if (PEM_END.test(line)) {
|
|
964
|
+
this.inKey = false;
|
|
965
|
+
return line;
|
|
966
|
+
}
|
|
967
|
+
if (this.hidden)
|
|
968
|
+
return;
|
|
969
|
+
this.hidden = true;
|
|
970
|
+
return "*** private key redacted ***";
|
|
971
|
+
}
|
|
972
|
+
if (PEM_BEGIN.test(line)) {
|
|
973
|
+
this.inKey = !PEM_END.test(line);
|
|
974
|
+
this.hidden = false;
|
|
975
|
+
return line;
|
|
976
|
+
}
|
|
977
|
+
return maskLine(line, this.known);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function check(text, known) {
|
|
981
|
+
const findings = [];
|
|
982
|
+
const isDiff = text.split(`
|
|
983
|
+
`).some((line) => DIFF_START.test(line));
|
|
984
|
+
let file = "stdin";
|
|
985
|
+
let number = 0;
|
|
986
|
+
let inKey = false;
|
|
987
|
+
for (const [index, raw] of text.split(`
|
|
988
|
+
`).entries()) {
|
|
989
|
+
let line = raw;
|
|
990
|
+
if (isDiff) {
|
|
991
|
+
const hunk = HUNK.exec(raw);
|
|
992
|
+
if (hunk !== null) {
|
|
993
|
+
number = Number(hunk[1]) - 1;
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
const named = NEW_FILE.exec(raw);
|
|
997
|
+
if (named !== null) {
|
|
998
|
+
file = named[1] ?? file;
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
if (raw.startsWith("diff --git ")) {
|
|
1002
|
+
file = "(unnamed)";
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
if (raw.startsWith("-") || raw.startsWith("\\"))
|
|
1006
|
+
continue;
|
|
1007
|
+
number++;
|
|
1008
|
+
if (!raw.startsWith("+"))
|
|
1009
|
+
continue;
|
|
1010
|
+
line = raw.slice(1);
|
|
1011
|
+
} else {
|
|
1012
|
+
number = index + 1;
|
|
1013
|
+
}
|
|
1014
|
+
if (inKey) {
|
|
1015
|
+
inKey = !PEM_END.test(line);
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
if (PEM_BEGIN.test(line)) {
|
|
1019
|
+
inKey = !PEM_END.test(line);
|
|
1020
|
+
findings.push({ file, line: number, kind: "private key" });
|
|
1021
|
+
}
|
|
1022
|
+
for (const match of findSecrets(line, known))
|
|
1023
|
+
findings.push({ file, line: number, kind: match.kind });
|
|
1024
|
+
}
|
|
1025
|
+
return findings;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/redact.ts
|
|
1029
|
+
var usage = `redact [--check]
|
|
1030
|
+
|
|
1031
|
+
Copies stdin to stdout line by line with secrets masked: known token formats (ghp_, github_pat_, glpat-, sk-, xox?-, JWTs), private key blocks, the password of any URL and the values of secret-looking keys in this folder's .env* files.
|
|
1032
|
+
--check prints nothing of the input and lists the file and line of each secret found, exiting 1 when there is any: \`git diff --cached | redact --check\`.
|
|
1033
|
+
|
|
1034
|
+
Exits 0 when it copied the input or found no secret, 1 when --check finds one, 2 on a usage error.`;
|
|
1035
|
+
var options = {
|
|
1036
|
+
check: { type: "boolean", description: "list where secrets are instead of masking them, exit 1 when found" },
|
|
1037
|
+
"no-env": { type: "boolean", description: "do not search for the values of the .env* files" }
|
|
1038
|
+
};
|
|
1039
|
+
async function run(args) {
|
|
1040
|
+
const parsed = parse(args, usage, options, { replaces: "printing a command's secrets into a log or a transcript", examples: [["docker compose config | redact", "the resolved compose file, secrets masked"], ["git diff --cached | redact --check", "refuse a commit carrying a secret"]] });
|
|
1041
|
+
const known = parsed.values["no-env"] === true ? undefined : await loadKnown();
|
|
1042
|
+
if (parsed.values.check === true) {
|
|
1043
|
+
const findings = check(await readInput("-"), known);
|
|
1044
|
+
for (const { file, line, kind } of findings)
|
|
1045
|
+
console.log(`${file}:${line}: ${kind}`);
|
|
1046
|
+
return findings.length === 0 ? 0 : 1;
|
|
1047
|
+
}
|
|
1048
|
+
const redactor = new Redactor(known);
|
|
1049
|
+
const decoder = new TextDecoder;
|
|
1050
|
+
for await (const chunk of Bun.stdin.stream())
|
|
1051
|
+
process7.stdout.write(redactor.push(decoder.decode(chunk, { stream: true })));
|
|
1052
|
+
process7.stdout.write(redactor.push(decoder.decode()) + redactor.end());
|
|
1053
|
+
return 0;
|
|
1054
|
+
}
|
|
1055
|
+
process7.exitCode = await runMain({ name: "redact", version, run });
|