dotenvx-ui 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-GSBFG44K.js +456 -0
- package/dist/cli.js +719 -919
- package/dist/server-DDA3TXEA.js +141 -0
- package/dist/ui/assets/index-CrW6jA2b.js +11 -0
- package/dist/ui/assets/index-vQpShLEP.css +2 -0
- package/dist/ui/index.html +16 -0
- package/package.json +18 -4
package/dist/cli.js
CHANGED
|
@@ -1,141 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
addKey,
|
|
4
|
+
decryptAllValues,
|
|
5
|
+
decryptFile,
|
|
6
|
+
decryptValue,
|
|
7
|
+
encryptFile,
|
|
8
|
+
encryptKey,
|
|
9
|
+
isEncryptedValue,
|
|
10
|
+
readEnvFile,
|
|
11
|
+
removeKey,
|
|
12
|
+
scan,
|
|
13
|
+
updateKey
|
|
14
|
+
} from "./chunk-GSBFG44K.js";
|
|
2
15
|
|
|
3
16
|
// src/cli.tsx
|
|
17
|
+
import { createRequire } from "module";
|
|
4
18
|
import { render } from "ink";
|
|
5
19
|
|
|
6
|
-
// src/core/scanner.ts
|
|
7
|
-
import { readdirSync, readFileSync, statSync } from "fs";
|
|
8
|
-
import { join, relative, basename, dirname } from "path";
|
|
9
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10
|
-
"node_modules",
|
|
11
|
-
".git",
|
|
12
|
-
"dist",
|
|
13
|
-
".next",
|
|
14
|
-
".turbo",
|
|
15
|
-
"build",
|
|
16
|
-
".cache"
|
|
17
|
-
]);
|
|
18
|
-
function detectRoot(cwd) {
|
|
19
|
-
let dir = cwd;
|
|
20
|
-
while (true) {
|
|
21
|
-
try {
|
|
22
|
-
statSync(join(dir, ".git"));
|
|
23
|
-
return dir;
|
|
24
|
-
} catch {
|
|
25
|
-
}
|
|
26
|
-
const parent = dirname(dir);
|
|
27
|
-
if (parent === dir) break;
|
|
28
|
-
dir = parent;
|
|
29
|
-
}
|
|
30
|
-
return cwd;
|
|
31
|
-
}
|
|
32
|
-
function scanForEnvFiles(root) {
|
|
33
|
-
const results = [];
|
|
34
|
-
function walk(dir) {
|
|
35
|
-
let entries;
|
|
36
|
-
try {
|
|
37
|
-
entries = readdirSync(dir);
|
|
38
|
-
} catch {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
if (SKIP_DIRS.has(entry)) continue;
|
|
43
|
-
const full = join(dir, entry);
|
|
44
|
-
try {
|
|
45
|
-
const stat = statSync(full);
|
|
46
|
-
if (stat.isDirectory()) {
|
|
47
|
-
walk(full);
|
|
48
|
-
} else if (isEnvFile(entry)) {
|
|
49
|
-
results.push(full);
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
walk(root);
|
|
56
|
-
return results;
|
|
57
|
-
}
|
|
58
|
-
function isEnvFile(name) {
|
|
59
|
-
if (name === ".env.keys") return false;
|
|
60
|
-
return name === ".env" || name.startsWith(".env.");
|
|
61
|
-
}
|
|
62
|
-
function parseEnvironmentFromFilename(filename) {
|
|
63
|
-
const name = basename(filename);
|
|
64
|
-
if (name === ".env") return "default";
|
|
65
|
-
const suffix = name.slice(".env.".length);
|
|
66
|
-
return suffix || "default";
|
|
67
|
-
}
|
|
68
|
-
function scan(cwd) {
|
|
69
|
-
const root = detectRoot(cwd);
|
|
70
|
-
const paths = scanForEnvFiles(root);
|
|
71
|
-
return paths.map((filePath) => {
|
|
72
|
-
const rel = relative(root, filePath);
|
|
73
|
-
const pkg = relative(root, dirname(filePath)) || ".";
|
|
74
|
-
const environment = parseEnvironmentFromFilename(basename(filePath));
|
|
75
|
-
let content = "";
|
|
76
|
-
try {
|
|
77
|
-
content = readFileSync(filePath, "utf8");
|
|
78
|
-
} catch {
|
|
79
|
-
}
|
|
80
|
-
const hasPublicKey = content.includes("DOTENV_PUBLIC_KEY=");
|
|
81
|
-
const encrypted = /encrypted:/.test(content);
|
|
82
|
-
return {
|
|
83
|
-
path: filePath,
|
|
84
|
-
relativePath: rel,
|
|
85
|
-
package: pkg,
|
|
86
|
-
environment,
|
|
87
|
-
encrypted,
|
|
88
|
-
hasPublicKey,
|
|
89
|
-
keys: []
|
|
90
|
-
};
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
20
|
// src/tui/App.tsx
|
|
95
|
-
import { useState as useState4 } from "react";
|
|
96
|
-
import { Box as Box7, Text as Text7, useApp, useInput as useInput6, useStdin as useStdin6 } from "ink";
|
|
97
21
|
import clipboard from "clipboardy";
|
|
22
|
+
import { Box as Box7, Text as Text7, useApp, useInput as useInput6, useStdin as useStdin6 } from "ink";
|
|
23
|
+
import { useState as useState4 } from "react";
|
|
98
24
|
|
|
99
|
-
// src/tui/
|
|
25
|
+
// src/tui/DiffView.tsx
|
|
100
26
|
import { Box, Text, useInput, useStdin } from "ink";
|
|
101
|
-
import {
|
|
102
|
-
function FileList({ files, selectedIndex, focused, interactive, onSelect }) {
|
|
103
|
-
const { isRawModeSupported } = useStdin();
|
|
104
|
-
useInput((_, key) => {
|
|
105
|
-
if (!focused) return;
|
|
106
|
-
if (key.upArrow) onSelect(Math.max(0, selectedIndex - 1));
|
|
107
|
-
if (key.downArrow) onSelect(Math.min(files.length - 1, selectedIndex + 1));
|
|
108
|
-
}, { isActive: isRawModeSupported && interactive });
|
|
109
|
-
const byPkg = Map.groupBy(files, (f) => f.package);
|
|
110
|
-
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: 24, borderStyle: "single", borderRight: true, borderTop: false, borderBottom: false, borderLeft: false, children: Array.from(byPkg.entries()).map(([pkg, pkgFiles]) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
111
|
-
/* @__PURE__ */ jsxs(Text, { bold: true, dimColor: true, children: [
|
|
112
|
-
" ",
|
|
113
|
-
pkg
|
|
114
|
-
] }),
|
|
115
|
-
pkgFiles.map((f) => {
|
|
116
|
-
const idx = files.indexOf(f);
|
|
117
|
-
const selected = idx === selectedIndex;
|
|
118
|
-
return /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(
|
|
119
|
-
Text,
|
|
120
|
-
{
|
|
121
|
-
backgroundColor: selected && focused ? "blue" : void 0,
|
|
122
|
-
color: selected && focused ? "white" : selected ? "cyan" : void 0,
|
|
123
|
-
children: [
|
|
124
|
-
f.encrypted ? "\u{1F512} " : " ",
|
|
125
|
-
f.environment
|
|
126
|
-
]
|
|
127
|
-
}
|
|
128
|
-
) }, f.path);
|
|
129
|
-
})
|
|
130
|
-
] }, pkg)) });
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// src/tui/KeyTable.tsx
|
|
134
|
-
import { Box as Box2, Text as Text2, useInput as useInput2, useStdin as useStdin2 } from "ink";
|
|
27
|
+
import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
135
28
|
|
|
136
29
|
// src/tui/useTerminalRows.ts
|
|
137
|
-
import { useEffect, useState } from "react";
|
|
138
30
|
import { useStdout } from "ink";
|
|
31
|
+
import { useEffect, useState } from "react";
|
|
139
32
|
function useTerminalRows() {
|
|
140
33
|
const { stdout } = useStdout();
|
|
141
34
|
const [rows, setRows] = useState(stdout?.rows ?? 24);
|
|
@@ -163,7 +56,8 @@ function useTerminalCols() {
|
|
|
163
56
|
return cols;
|
|
164
57
|
}
|
|
165
58
|
function scrollWindow(length, selectedIndex, maxVisible) {
|
|
166
|
-
if (length <= maxVisible)
|
|
59
|
+
if (length <= maxVisible)
|
|
60
|
+
return { start: 0, end: length, above: 0, below: 0 };
|
|
167
61
|
const start = Math.min(
|
|
168
62
|
Math.max(0, selectedIndex - Math.floor(maxVisible / 2)),
|
|
169
63
|
length - maxVisible
|
|
@@ -172,444 +66,8 @@ function scrollWindow(length, selectedIndex, maxVisible) {
|
|
|
172
66
|
return { start, end, above: start, below: length - end };
|
|
173
67
|
}
|
|
174
68
|
|
|
175
|
-
// src/tui/KeyTable.tsx
|
|
176
|
-
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
177
|
-
var SECRET_PATTERN = /secret|password|token|key|private|api_?key/i;
|
|
178
|
-
var MAX_INLINE = 48;
|
|
179
|
-
function truncate(s) {
|
|
180
|
-
const first = s.split("\n")[0];
|
|
181
|
-
return first.length > MAX_INLINE ? first.slice(0, MAX_INLINE - 1) + "\u2026" : first;
|
|
182
|
-
}
|
|
183
|
-
function maskValue(k, revealed) {
|
|
184
|
-
if (revealed.has(k.key)) return truncate(revealed.get(k.key));
|
|
185
|
-
if (k.encrypted) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
186
|
-
if (SECRET_PATTERN.test(k.key)) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
187
|
-
return truncate(k.value);
|
|
188
|
-
}
|
|
189
|
-
function KeyTable({ file, keys, selectedIndex, focused, interactive, revealed, onSelect, maxRows }) {
|
|
190
|
-
const { isRawModeSupported } = useStdin2();
|
|
191
|
-
useInput2((_, key) => {
|
|
192
|
-
if (!focused) return;
|
|
193
|
-
if (key.upArrow) onSelect(Math.max(0, selectedIndex - 1));
|
|
194
|
-
if (key.downArrow) onSelect(Math.min(keys.length - 1, selectedIndex + 1));
|
|
195
|
-
}, { isActive: isRawModeSupported && interactive });
|
|
196
|
-
const maxVisible = Math.max(3, maxRows - 3);
|
|
197
|
-
const { start, end, above, below } = scrollWindow(keys.length, selectedIndex, maxVisible);
|
|
198
|
-
const visibleKeys = keys.slice(start, end);
|
|
199
|
-
const keyColWidth = Math.min(48, Math.max(16, ...keys.map((k) => k.key.length))) + 2;
|
|
200
|
-
const encBadge = file.encrypted ? /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: " encrypted" }) : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " plain" });
|
|
201
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 1, children: [
|
|
202
|
-
/* @__PURE__ */ jsxs2(Box2, { paddingX: 1, children: [
|
|
203
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, children: file.relativePath }),
|
|
204
|
-
encBadge,
|
|
205
|
-
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
206
|
-
" ",
|
|
207
|
-
keys.length,
|
|
208
|
-
" key",
|
|
209
|
-
keys.length === 1 ? "" : "s"
|
|
210
|
-
] })
|
|
211
|
-
] }),
|
|
212
|
-
keys.length === 0 ? /* @__PURE__ */ jsx2(Box2, { paddingX: 2, marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
213
|
-
"No keys found. Press ",
|
|
214
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, children: "a" }),
|
|
215
|
-
" to add one."
|
|
216
|
-
] }) }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
217
|
-
above > 0 && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
218
|
-
"\u2191 ",
|
|
219
|
-
above,
|
|
220
|
-
" more"
|
|
221
|
-
] }) }),
|
|
222
|
-
visibleKeys.map((k, i) => {
|
|
223
|
-
const idx = start + i;
|
|
224
|
-
const selected = idx === selectedIndex;
|
|
225
|
-
const value = maskValue(k, revealed);
|
|
226
|
-
const lockIcon = k.encrypted && !revealed.has(k.key) ? " \u{1F512}" : "";
|
|
227
|
-
return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsxs2(
|
|
228
|
-
Text2,
|
|
229
|
-
{
|
|
230
|
-
backgroundColor: selected && focused ? "blue" : void 0,
|
|
231
|
-
color: selected && focused ? "white" : selected ? "cyan" : void 0,
|
|
232
|
-
children: [
|
|
233
|
-
k.key.padEnd(keyColWidth),
|
|
234
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: !selected, children: value }),
|
|
235
|
-
lockIcon
|
|
236
|
-
]
|
|
237
|
-
}
|
|
238
|
-
) }, k.key);
|
|
239
|
-
}),
|
|
240
|
-
below > 0 && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
241
|
-
"\u2193 ",
|
|
242
|
-
below,
|
|
243
|
-
" more"
|
|
244
|
-
] }) })
|
|
245
|
-
] })
|
|
246
|
-
] });
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// src/tui/StatusBar.tsx
|
|
250
|
-
import { Box as Box3, Text as Text3 } from "ink";
|
|
251
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
252
|
-
var FILE_HINTS = [
|
|
253
|
-
{ key: "\u2191\u2193", action: "navigate" },
|
|
254
|
-
{ key: "tab", action: "switch panel" },
|
|
255
|
-
{ key: "?", action: "help" },
|
|
256
|
-
{ key: "q", action: "quit" }
|
|
257
|
-
];
|
|
258
|
-
var KEY_HINTS = [
|
|
259
|
-
{ key: "\u2191\u2193", action: "navigate" },
|
|
260
|
-
{ key: "tab", action: "switch" },
|
|
261
|
-
{ key: "enter", action: "edit" },
|
|
262
|
-
{ key: "y", action: "copy" },
|
|
263
|
-
{ key: "r", action: "reveal" },
|
|
264
|
-
{ key: "a", action: "add" },
|
|
265
|
-
{ key: "D", action: "delete" },
|
|
266
|
-
{ key: "d", action: "diff" },
|
|
267
|
-
{ key: "?", action: "help" },
|
|
268
|
-
{ key: "q", action: "quit" }
|
|
269
|
-
];
|
|
270
|
-
function Hints({ hints }) {
|
|
271
|
-
return /* @__PURE__ */ jsx3(Box3, { gap: 2, children: hints.map(({ key, action }) => /* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
|
|
272
|
-
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: key }),
|
|
273
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: action })
|
|
274
|
-
] }, key)) });
|
|
275
|
-
}
|
|
276
|
-
function StatusBar({ message, focus }) {
|
|
277
|
-
return /* @__PURE__ */ jsx3(Box3, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, children: message ? /* @__PURE__ */ jsx3(Text3, { children: message }) : /* @__PURE__ */ jsx3(Hints, { hints: focus === "files" ? FILE_HINTS : KEY_HINTS }) });
|
|
278
|
-
}
|
|
279
|
-
|
|
280
69
|
// src/tui/DiffView.tsx
|
|
281
|
-
import {
|
|
282
|
-
import { Box as Box4, Text as Text4, useInput as useInput3, useStdin as useStdin3 } from "ink";
|
|
283
|
-
|
|
284
|
-
// src/core/parser/io.ts
|
|
285
|
-
import { readFileSync as readFileSync2, writeFileSync, renameSync } from "fs";
|
|
286
|
-
import { join as join2, dirname as dirname2 } from "path";
|
|
287
|
-
import { randomBytes } from "crypto";
|
|
288
|
-
|
|
289
|
-
// src/core/parser/values.ts
|
|
290
|
-
function isEncryptedValue(value) {
|
|
291
|
-
return value.startsWith("encrypted:");
|
|
292
|
-
}
|
|
293
|
-
function parseValue(rawValue, allLines, nextLineIdx) {
|
|
294
|
-
const trimmed = rawValue.trim();
|
|
295
|
-
if (trimmed.startsWith('"')) {
|
|
296
|
-
const inner = trimmed.slice(1);
|
|
297
|
-
const closeIdx = findClosingQuote(inner);
|
|
298
|
-
if (closeIdx !== -1) {
|
|
299
|
-
return { value: unescape(inner.slice(0, closeIdx)), extraLines: [] };
|
|
300
|
-
}
|
|
301
|
-
const valueLines = [inner];
|
|
302
|
-
let idx = nextLineIdx;
|
|
303
|
-
while (idx < allLines.length) {
|
|
304
|
-
const continuation = allLines[idx];
|
|
305
|
-
const close = findClosingQuote(continuation);
|
|
306
|
-
if (close !== -1) {
|
|
307
|
-
valueLines.push(continuation.slice(0, close));
|
|
308
|
-
return {
|
|
309
|
-
value: valueLines.join("\n"),
|
|
310
|
-
extraLines: allLines.slice(nextLineIdx, idx + 1)
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
valueLines.push(continuation);
|
|
314
|
-
idx++;
|
|
315
|
-
}
|
|
316
|
-
return { value: valueLines.join("\n"), extraLines: allLines.slice(nextLineIdx, idx) };
|
|
317
|
-
}
|
|
318
|
-
if (trimmed.startsWith("'")) {
|
|
319
|
-
const inner = trimmed.slice(1);
|
|
320
|
-
const closeIdx = inner.indexOf("'");
|
|
321
|
-
return {
|
|
322
|
-
value: closeIdx !== -1 ? inner.slice(0, closeIdx) : inner,
|
|
323
|
-
extraLines: []
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
const commentIdx = trimmed.indexOf(" #");
|
|
327
|
-
const bare = commentIdx !== -1 ? trimmed.slice(0, commentIdx) : trimmed;
|
|
328
|
-
return { value: bare, extraLines: [] };
|
|
329
|
-
}
|
|
330
|
-
function serializeKeyValue(key, value) {
|
|
331
|
-
if (value.includes("\n")) {
|
|
332
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r");
|
|
333
|
-
return `${key}="${escaped}"`;
|
|
334
|
-
}
|
|
335
|
-
if (value === "" || /[\s#"'`]/.test(value)) {
|
|
336
|
-
return `${key}="${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
337
|
-
}
|
|
338
|
-
return `${key}=${value}`;
|
|
339
|
-
}
|
|
340
|
-
function findClosingQuote(s) {
|
|
341
|
-
for (let i = 0; i < s.length; i++) {
|
|
342
|
-
if (s[i] === "\\") {
|
|
343
|
-
i++;
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
if (s[i] === '"') return i;
|
|
347
|
-
}
|
|
348
|
-
return -1;
|
|
349
|
-
}
|
|
350
|
-
function unescape(s) {
|
|
351
|
-
return s.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\\\/g, "\\").replace(/\\"/g, '"');
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// src/core/parser/io.ts
|
|
355
|
-
function readEnvFile(filePath) {
|
|
356
|
-
const content = readFileSync2(filePath, "utf8");
|
|
357
|
-
return parse(content).filter((e) => e.type === "key").map((e) => ({
|
|
358
|
-
key: e.key,
|
|
359
|
-
value: e.value,
|
|
360
|
-
encrypted: isEncryptedValue(e.value),
|
|
361
|
-
comment: extractLeadingComment(e.lines)
|
|
362
|
-
}));
|
|
363
|
-
}
|
|
364
|
-
function writeEnvFile(filePath, keys) {
|
|
365
|
-
const content = readFileSync2(filePath, "utf8");
|
|
366
|
-
const entries = parse(content);
|
|
367
|
-
const updates = new Map(keys.map((k) => [k.key, k]));
|
|
368
|
-
const outLines = [];
|
|
369
|
-
const written = /* @__PURE__ */ new Set();
|
|
370
|
-
for (const entry of entries) {
|
|
371
|
-
if (entry.type === "raw") {
|
|
372
|
-
outLines.push(entry.text);
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
const update = updates.get(entry.key);
|
|
376
|
-
if (!update) continue;
|
|
377
|
-
written.add(entry.key);
|
|
378
|
-
const leadingComments = getLeadingCommentLines(entry.lines);
|
|
379
|
-
outLines.push(...leadingComments);
|
|
380
|
-
if (update.value === entry.value) {
|
|
381
|
-
const keyLines = entry.lines.filter((l) => !l.trimStart().startsWith("#"));
|
|
382
|
-
outLines.push(...keyLines);
|
|
383
|
-
} else {
|
|
384
|
-
outLines.push(serializeKeyValue(entry.key, update.value));
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
for (const k of keys) {
|
|
388
|
-
if (!written.has(k.key)) {
|
|
389
|
-
if (k.comment) outLines.push(`# ${k.comment}`);
|
|
390
|
-
outLines.push(serializeKeyValue(k.key, k.value));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
const output = outLines.join("\n") + (content.endsWith("\n") ? "\n" : "");
|
|
394
|
-
atomicWrite(filePath, output);
|
|
395
|
-
}
|
|
396
|
-
function addKey(filePath, key, value) {
|
|
397
|
-
const keys = readEnvFile(filePath);
|
|
398
|
-
if (keys.some((k) => k.key === key)) {
|
|
399
|
-
throw new Error(`Key "${key}" already exists in ${filePath}`);
|
|
400
|
-
}
|
|
401
|
-
keys.push({ key, value, encrypted: isEncryptedValue(value) });
|
|
402
|
-
writeEnvFile(filePath, keys);
|
|
403
|
-
}
|
|
404
|
-
function updateKey(filePath, key, value) {
|
|
405
|
-
const keys = readEnvFile(filePath);
|
|
406
|
-
const idx = keys.findIndex((k) => k.key === key);
|
|
407
|
-
if (idx === -1) throw new Error(`Key "${key}" not found in ${filePath}`);
|
|
408
|
-
keys[idx] = { ...keys[idx], key, value, encrypted: isEncryptedValue(value) };
|
|
409
|
-
writeEnvFile(filePath, keys);
|
|
410
|
-
}
|
|
411
|
-
function removeKey(filePath, key) {
|
|
412
|
-
const keys = readEnvFile(filePath).filter((k) => k.key !== key);
|
|
413
|
-
writeEnvFile(filePath, keys);
|
|
414
|
-
}
|
|
415
|
-
function parse(content) {
|
|
416
|
-
const entries = [];
|
|
417
|
-
const lines = content.split("\n");
|
|
418
|
-
if (lines[lines.length - 1] === "") lines.pop();
|
|
419
|
-
let i = 0;
|
|
420
|
-
let pendingComments = [];
|
|
421
|
-
while (i < lines.length) {
|
|
422
|
-
const line = lines[i];
|
|
423
|
-
if (line.trim() === "") {
|
|
424
|
-
for (const c of pendingComments) entries.push({ type: "raw", text: c });
|
|
425
|
-
pendingComments = [];
|
|
426
|
-
entries.push({ type: "raw", text: line });
|
|
427
|
-
i++;
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
if (line.trimStart().startsWith("#")) {
|
|
431
|
-
pendingComments.push(line);
|
|
432
|
-
i++;
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
const eqIdx = line.indexOf("=");
|
|
436
|
-
if (eqIdx === -1) {
|
|
437
|
-
for (const c of pendingComments) entries.push({ type: "raw", text: c });
|
|
438
|
-
pendingComments = [];
|
|
439
|
-
entries.push({ type: "raw", text: line });
|
|
440
|
-
i++;
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
const key = line.slice(0, eqIdx).trim();
|
|
444
|
-
const rawValue = line.slice(eqIdx + 1);
|
|
445
|
-
const { value, extraLines } = parseValue(rawValue, lines, i + 1);
|
|
446
|
-
entries.push({ type: "key", key, value, lines: [...pendingComments, line, ...extraLines] });
|
|
447
|
-
pendingComments = [];
|
|
448
|
-
i += 1 + extraLines.length;
|
|
449
|
-
}
|
|
450
|
-
for (const c of pendingComments) entries.push({ type: "raw", text: c });
|
|
451
|
-
return entries;
|
|
452
|
-
}
|
|
453
|
-
function getLeadingCommentLines(lines) {
|
|
454
|
-
const result = [];
|
|
455
|
-
for (const l of lines) {
|
|
456
|
-
if (l.trimStart().startsWith("#")) result.push(l);
|
|
457
|
-
else break;
|
|
458
|
-
}
|
|
459
|
-
return result;
|
|
460
|
-
}
|
|
461
|
-
function extractLeadingComment(lines) {
|
|
462
|
-
const comments = getLeadingCommentLines(lines).map(
|
|
463
|
-
(l) => l.trimStart().slice(1).trim()
|
|
464
|
-
);
|
|
465
|
-
return comments.length > 0 ? comments.join("\n") : void 0;
|
|
466
|
-
}
|
|
467
|
-
function atomicWrite(filePath, content) {
|
|
468
|
-
const tmp = join2(
|
|
469
|
-
dirname2(filePath),
|
|
470
|
-
`.dotenvx-ui-tmp-${randomBytes(6).toString("hex")}`
|
|
471
|
-
);
|
|
472
|
-
try {
|
|
473
|
-
writeFileSync(tmp, content, { encoding: "utf8", flag: "wx" });
|
|
474
|
-
renameSync(tmp, filePath);
|
|
475
|
-
} catch (err) {
|
|
476
|
-
try {
|
|
477
|
-
writeFileSync(tmp, "");
|
|
478
|
-
} catch {
|
|
479
|
-
}
|
|
480
|
-
throw new Error(`Failed to write ${filePath}: ${err.message}`);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// src/core/dotenvx.ts
|
|
485
|
-
import { createRequire } from "module";
|
|
486
|
-
import { existsSync, readFileSync as readFileSync3 } from "fs";
|
|
487
|
-
import { join as join3, dirname as dirname3 } from "path";
|
|
488
|
-
var dotenvx = createRequire(import.meta.url)("@dotenvx/dotenvx");
|
|
489
|
-
function decryptValue(encryptedValue, envFilePath) {
|
|
490
|
-
if (!isEncryptedValue(encryptedValue)) return encryptedValue;
|
|
491
|
-
const keyName = findKeyForValue(encryptedValue, envFilePath);
|
|
492
|
-
if (!keyName) return null;
|
|
493
|
-
const keysFile = findKeysFile(envFilePath);
|
|
494
|
-
return silenced(() => {
|
|
495
|
-
const result = dotenvx.get(keyName, {
|
|
496
|
-
path: envFilePath,
|
|
497
|
-
...keysFile ? { envKeysFile: keysFile } : {},
|
|
498
|
-
logLevel: "error"
|
|
499
|
-
});
|
|
500
|
-
return result ?? null;
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
function decryptAllValues(envFilePath) {
|
|
504
|
-
let raw;
|
|
505
|
-
try {
|
|
506
|
-
raw = readFileSync3(envFilePath, "utf8");
|
|
507
|
-
} catch {
|
|
508
|
-
return {};
|
|
509
|
-
}
|
|
510
|
-
let privateKey = process.env["DOTENV_PRIVATE_KEY"] ?? null;
|
|
511
|
-
if (!privateKey) {
|
|
512
|
-
const keysFile = findKeysFile(envFilePath);
|
|
513
|
-
if (keysFile) {
|
|
514
|
-
try {
|
|
515
|
-
const keypairs = dotenvx.keypair(envFilePath, void 0, keysFile);
|
|
516
|
-
for (const [name, value] of Object.entries(keypairs)) {
|
|
517
|
-
if (name.startsWith("DOTENV_PRIVATE_KEY") && value) {
|
|
518
|
-
privateKey = value;
|
|
519
|
-
break;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
} catch {
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
return silenced(() => {
|
|
527
|
-
try {
|
|
528
|
-
return dotenvx.parse(raw, {
|
|
529
|
-
...privateKey ? { privateKey } : {},
|
|
530
|
-
processEnv: {}
|
|
531
|
-
});
|
|
532
|
-
} catch {
|
|
533
|
-
return {};
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
var DOTENVX_INTERNAL_KEYS = /* @__PURE__ */ new Set(["DOTENV_PUBLIC_KEY", "DOTENV_PRIVATE_KEY"]);
|
|
538
|
-
function encryptFile(envFilePath) {
|
|
539
|
-
const keys = readEnvFile(envFilePath);
|
|
540
|
-
for (const k of keys) {
|
|
541
|
-
if (!isEncryptedValue(k.value) && !DOTENVX_INTERNAL_KEYS.has(k.key)) {
|
|
542
|
-
dotenvx.set(k.key, k.value, {
|
|
543
|
-
path: envFilePath,
|
|
544
|
-
encrypt: true,
|
|
545
|
-
logLevel: "error"
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
function encryptKey(envFilePath, keyName, plainValue) {
|
|
551
|
-
dotenvx.set(keyName, plainValue, {
|
|
552
|
-
path: envFilePath,
|
|
553
|
-
encrypt: true,
|
|
554
|
-
logLevel: "error"
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
function decryptFile(envFilePath) {
|
|
558
|
-
const keys = readEnvFile(envFilePath);
|
|
559
|
-
const decrypted = decryptAllValues(envFilePath);
|
|
560
|
-
for (const k of keys) {
|
|
561
|
-
if (isEncryptedValue(k.value)) {
|
|
562
|
-
const plain = decrypted[k.key];
|
|
563
|
-
if (plain !== void 0 && !isEncryptedValue(plain)) updateKey(envFilePath, k.key, plain);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
function silenced(fn) {
|
|
568
|
-
const origWrite = process.stderr.write.bind(process.stderr);
|
|
569
|
-
const origOut = process.stdout.write.bind(process.stdout);
|
|
570
|
-
const mute = (chunk) => {
|
|
571
|
-
const s = String(chunk);
|
|
572
|
-
if (s.includes("[MISSING_PRIVATE_KEY]") || s.includes("could not decrypt") || s.includes("\u2620")) return true;
|
|
573
|
-
return false;
|
|
574
|
-
};
|
|
575
|
-
process.stderr.write = ((chunk, ...args) => mute(chunk) ? true : origWrite(chunk, ...args));
|
|
576
|
-
process.stdout.write = ((chunk, ...args) => mute(chunk) ? true : origOut(chunk, ...args));
|
|
577
|
-
try {
|
|
578
|
-
return fn();
|
|
579
|
-
} finally {
|
|
580
|
-
process.stderr.write = origWrite;
|
|
581
|
-
process.stdout.write = origOut;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
function findKeysFile(envFilePath) {
|
|
585
|
-
let dir = dirname3(envFilePath);
|
|
586
|
-
while (true) {
|
|
587
|
-
const candidate = join3(dir, ".env.keys");
|
|
588
|
-
if (existsSync(candidate)) return candidate;
|
|
589
|
-
const parent = dirname3(dir);
|
|
590
|
-
if (parent === dir) break;
|
|
591
|
-
dir = parent;
|
|
592
|
-
}
|
|
593
|
-
return null;
|
|
594
|
-
}
|
|
595
|
-
function findKeyForValue(encryptedValue, envFilePath) {
|
|
596
|
-
let raw;
|
|
597
|
-
try {
|
|
598
|
-
raw = readFileSync3(envFilePath, "utf8");
|
|
599
|
-
} catch {
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
|
-
for (const line of raw.split("\n")) {
|
|
603
|
-
const eqIdx = line.indexOf("=");
|
|
604
|
-
if (eqIdx === -1) continue;
|
|
605
|
-
const lineValue = line.slice(eqIdx + 1).trim();
|
|
606
|
-
if (lineValue === encryptedValue) return line.slice(0, eqIdx).trim();
|
|
607
|
-
}
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// src/tui/DiffView.tsx
|
|
612
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
70
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
613
71
|
var COL_VAL = 26;
|
|
614
72
|
var PICKER_MAX = 5;
|
|
615
73
|
function buildDisplayMap(keys, filePath) {
|
|
@@ -618,7 +76,10 @@ function buildDisplayMap(keys, filePath) {
|
|
|
618
76
|
for (const k of keys) {
|
|
619
77
|
if (k.encrypted) {
|
|
620
78
|
const plain = decrypted[k.key];
|
|
621
|
-
out.set(
|
|
79
|
+
out.set(
|
|
80
|
+
k.key,
|
|
81
|
+
plain !== void 0 && !isEncryptedValue(plain) ? plain : "\u{1F512}"
|
|
82
|
+
);
|
|
622
83
|
} else {
|
|
623
84
|
out.set(k.key, k.value);
|
|
624
85
|
}
|
|
@@ -645,7 +106,7 @@ function buildRows(leftMap, rightMap) {
|
|
|
645
106
|
}
|
|
646
107
|
function trunc(s, max) {
|
|
647
108
|
const first = s.split("\n")[0];
|
|
648
|
-
return first.length > max ? first.slice(0, max - 1)
|
|
109
|
+
return first.length > max ? `${first.slice(0, max - 1)}\u2026` : first;
|
|
649
110
|
}
|
|
650
111
|
function safeRead(file) {
|
|
651
112
|
try {
|
|
@@ -655,40 +116,43 @@ function safeRead(file) {
|
|
|
655
116
|
}
|
|
656
117
|
}
|
|
657
118
|
function DiffView({ left, files, onClose }) {
|
|
658
|
-
const { isRawModeSupported } =
|
|
119
|
+
const { isRawModeSupported } = useStdin();
|
|
659
120
|
const termRows = useTerminalRows();
|
|
660
121
|
const others = files.filter((f) => f.path !== left.path);
|
|
661
122
|
const [pickerIndex, setPickerIndex] = useState2(0);
|
|
662
123
|
const [rowScroll, setRowScroll] = useState2(0);
|
|
663
124
|
const rightFile = others[pickerIndex] ?? null;
|
|
664
125
|
const mapsCache = useRef(/* @__PURE__ */ new Map());
|
|
665
|
-
const [
|
|
126
|
+
const [_cacheVersion, setCacheVersion] = useState2(0);
|
|
666
127
|
function getMap(file) {
|
|
667
128
|
return mapsCache.current.get(file.path) ?? null;
|
|
668
129
|
}
|
|
669
130
|
function buildAndCache(file) {
|
|
670
131
|
if (!mapsCache.current.has(file.path)) {
|
|
671
|
-
mapsCache.current.set(
|
|
132
|
+
mapsCache.current.set(
|
|
133
|
+
file.path,
|
|
134
|
+
buildDisplayMap(safeRead(file), file.path)
|
|
135
|
+
);
|
|
672
136
|
setCacheVersion((v) => v + 1);
|
|
673
137
|
}
|
|
674
138
|
}
|
|
675
139
|
useEffect2(() => {
|
|
676
140
|
buildAndCache(left);
|
|
677
141
|
if (others[0]) buildAndCache(others[0]);
|
|
678
|
-
}, []);
|
|
142
|
+
}, [left, others[0], buildAndCache]);
|
|
679
143
|
useEffect2(() => {
|
|
680
144
|
const next = others[pickerIndex + 1];
|
|
681
145
|
const prev = others[pickerIndex - 1];
|
|
682
146
|
if (next) buildAndCache(next);
|
|
683
147
|
if (prev) buildAndCache(prev);
|
|
684
|
-
}, [pickerIndex]);
|
|
148
|
+
}, [pickerIndex, others, buildAndCache]);
|
|
685
149
|
const rows = useMemo(() => {
|
|
686
150
|
if (!rightFile) return [];
|
|
687
151
|
const leftMap = getMap(left);
|
|
688
152
|
const rightMap = getMap(rightFile);
|
|
689
153
|
if (!leftMap || !rightMap) return [];
|
|
690
154
|
return buildRows(leftMap, rightMap);
|
|
691
|
-
}, [left.path, rightFile?.path,
|
|
155
|
+
}, [left.path, rightFile?.path, getMap, left, rightFile]);
|
|
692
156
|
const pickerVisible = Math.min(others.length, PICKER_MAX);
|
|
693
157
|
const picker = scrollWindow(others.length, pickerIndex, PICKER_MAX);
|
|
694
158
|
const chrome = 9 + pickerVisible + (picker.above > 0 || picker.below > 0 ? 2 : 0);
|
|
@@ -698,41 +162,47 @@ function DiffView({ left, files, onClose }) {
|
|
|
698
162
|
const visibleRows = rows.slice(scroll, scroll + maxRows);
|
|
699
163
|
const rowsAbove = scroll;
|
|
700
164
|
const rowsBelow = rows.length - (scroll + visibleRows.length);
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
165
|
+
useInput(
|
|
166
|
+
(input, key) => {
|
|
167
|
+
if (key.escape || input === "q") {
|
|
168
|
+
onClose();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (key.upArrow) {
|
|
172
|
+
setPickerIndex((i) => Math.max(0, i - 1));
|
|
173
|
+
setRowScroll(0);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (key.downArrow) {
|
|
177
|
+
setPickerIndex((i) => Math.min(others.length - 1, i + 1));
|
|
178
|
+
setRowScroll(0);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (input === "k") setRowScroll(Math.max(0, scroll - 1));
|
|
182
|
+
if (input === "j") setRowScroll(Math.min(maxScroll, scroll + 1));
|
|
183
|
+
if (key.pageUp) setRowScroll(Math.max(0, scroll - maxRows));
|
|
184
|
+
if (key.pageDown) setRowScroll(Math.min(maxScroll, scroll + maxRows));
|
|
185
|
+
},
|
|
186
|
+
{ isActive: isRawModeSupported }
|
|
187
|
+
);
|
|
721
188
|
const keyColWidth = Math.min(48, Math.max(16, ...rows.map((r) => r.key.length))) + 2;
|
|
722
189
|
const leftName = trunc(left.relativePath, COL_VAL);
|
|
723
190
|
const rightName = rightFile ? trunc(rightFile.relativePath, COL_VAL) : "\u2014";
|
|
724
|
-
return /* @__PURE__ */
|
|
725
|
-
/* @__PURE__ */
|
|
726
|
-
/* @__PURE__ */
|
|
727
|
-
|
|
728
|
-
"
|
|
191
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
192
|
+
/* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
|
|
193
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
|
|
194
|
+
"dotenvx-ui",
|
|
195
|
+
" "
|
|
196
|
+
] }),
|
|
197
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
198
|
+
"diff ",
|
|
729
199
|
left.relativePath,
|
|
730
|
-
"
|
|
200
|
+
" \u2194 ",
|
|
731
201
|
rightFile?.relativePath ?? "\u2014"
|
|
732
202
|
] })
|
|
733
203
|
] }),
|
|
734
|
-
/* @__PURE__ */
|
|
735
|
-
|
|
204
|
+
/* @__PURE__ */ jsxs(
|
|
205
|
+
Box,
|
|
736
206
|
{
|
|
737
207
|
flexDirection: "column",
|
|
738
208
|
paddingX: 1,
|
|
@@ -742,16 +212,16 @@ function DiffView({ left, files, onClose }) {
|
|
|
742
212
|
borderLeft: false,
|
|
743
213
|
borderRight: false,
|
|
744
214
|
children: [
|
|
745
|
-
/* @__PURE__ */
|
|
746
|
-
picker.above > 0 && /* @__PURE__ */
|
|
747
|
-
"
|
|
215
|
+
/* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, children: "compare with" }),
|
|
216
|
+
picker.above > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
217
|
+
" \u2191 ",
|
|
748
218
|
picker.above,
|
|
749
219
|
" more"
|
|
750
220
|
] }),
|
|
751
221
|
others.slice(picker.start, picker.end).map((f, i) => {
|
|
752
222
|
const selected = picker.start + i === pickerIndex;
|
|
753
|
-
return /* @__PURE__ */
|
|
754
|
-
|
|
223
|
+
return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(
|
|
224
|
+
Text,
|
|
755
225
|
{
|
|
756
226
|
backgroundColor: selected ? "blue" : void 0,
|
|
757
227
|
color: selected ? "white" : void 0,
|
|
@@ -762,59 +232,136 @@ function DiffView({ left, files, onClose }) {
|
|
|
762
232
|
}
|
|
763
233
|
) }, f.path);
|
|
764
234
|
}),
|
|
765
|
-
picker.below > 0 && /* @__PURE__ */
|
|
766
|
-
"
|
|
235
|
+
picker.below > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
236
|
+
" \u2193 ",
|
|
767
237
|
picker.below,
|
|
768
238
|
" more"
|
|
769
239
|
] }),
|
|
770
|
-
others.length === 0 && /* @__PURE__ */
|
|
240
|
+
others.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " no other files" })
|
|
771
241
|
]
|
|
772
242
|
}
|
|
773
243
|
),
|
|
774
|
-
/* @__PURE__ */
|
|
775
|
-
/* @__PURE__ */
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
"
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
244
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
|
|
245
|
+
/* @__PURE__ */ jsxs(
|
|
246
|
+
Box,
|
|
247
|
+
{
|
|
248
|
+
borderStyle: "single",
|
|
249
|
+
borderBottom: true,
|
|
250
|
+
borderTop: false,
|
|
251
|
+
borderLeft: false,
|
|
252
|
+
borderRight: false,
|
|
253
|
+
children: [
|
|
254
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "KEY".padEnd(keyColWidth) }),
|
|
255
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, children: [
|
|
256
|
+
" ",
|
|
257
|
+
leftName.padEnd(COL_VAL + 2)
|
|
258
|
+
] }),
|
|
259
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: rightName })
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
),
|
|
263
|
+
rowsAbove > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
784
264
|
"\u2191 ",
|
|
785
265
|
rowsAbove,
|
|
786
266
|
" more"
|
|
787
267
|
] }),
|
|
788
|
-
visibleRows.map((row) => /* @__PURE__ */
|
|
789
|
-
rowsBelow > 0 && /* @__PURE__ */
|
|
268
|
+
visibleRows.map((row) => /* @__PURE__ */ jsx(DiffRow, { row, keyColWidth }, row.key)),
|
|
269
|
+
rowsBelow > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
790
270
|
"\u2193 ",
|
|
791
271
|
rowsBelow,
|
|
792
272
|
" more"
|
|
793
273
|
] }),
|
|
794
|
-
rows.length === 0 && /* @__PURE__ */
|
|
274
|
+
rows.length === 0 && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Select a file to compare." }) })
|
|
795
275
|
] }),
|
|
796
|
-
/* @__PURE__ */
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
276
|
+
/* @__PURE__ */ jsxs(
|
|
277
|
+
Box,
|
|
278
|
+
{
|
|
279
|
+
borderStyle: "single",
|
|
280
|
+
borderTop: true,
|
|
281
|
+
borderBottom: false,
|
|
282
|
+
borderLeft: false,
|
|
283
|
+
borderRight: false,
|
|
284
|
+
paddingX: 1,
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 pick file j/k scroll esc close " }),
|
|
287
|
+
/* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF same" })
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
)
|
|
800
291
|
] });
|
|
801
292
|
}
|
|
802
293
|
function DiffRow({ row, keyColWidth }) {
|
|
803
294
|
const { key, leftDisplay, rightDisplay, status } = row;
|
|
804
295
|
const color = status === "same" ? "green" : void 0;
|
|
805
|
-
return /* @__PURE__ */
|
|
806
|
-
/* @__PURE__ */
|
|
807
|
-
/* @__PURE__ */
|
|
296
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
297
|
+
/* @__PURE__ */ jsx(Text, { color, children: key.padEnd(keyColWidth) }),
|
|
298
|
+
/* @__PURE__ */ jsxs(Text, { color, children: [
|
|
808
299
|
" ",
|
|
809
300
|
(leftDisplay || "\u2014").padEnd(COL_VAL + 2)
|
|
810
301
|
] }),
|
|
811
|
-
/* @__PURE__ */
|
|
302
|
+
/* @__PURE__ */ jsx(Text, { color, children: rightDisplay || "\u2014" })
|
|
812
303
|
] });
|
|
813
304
|
}
|
|
814
305
|
|
|
306
|
+
// src/tui/FileList.tsx
|
|
307
|
+
import { Box as Box2, Text as Text2, useInput as useInput2, useStdin as useStdin2 } from "ink";
|
|
308
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
309
|
+
function FileList({
|
|
310
|
+
files,
|
|
311
|
+
selectedIndex,
|
|
312
|
+
focused,
|
|
313
|
+
interactive,
|
|
314
|
+
onSelect
|
|
315
|
+
}) {
|
|
316
|
+
const { isRawModeSupported } = useStdin2();
|
|
317
|
+
useInput2(
|
|
318
|
+
(_, key) => {
|
|
319
|
+
if (!focused) return;
|
|
320
|
+
if (key.upArrow) onSelect(Math.max(0, selectedIndex - 1));
|
|
321
|
+
if (key.downArrow)
|
|
322
|
+
onSelect(Math.min(files.length - 1, selectedIndex + 1));
|
|
323
|
+
},
|
|
324
|
+
{ isActive: isRawModeSupported && interactive }
|
|
325
|
+
);
|
|
326
|
+
const byPkg = Map.groupBy(files, (f) => f.package);
|
|
327
|
+
return /* @__PURE__ */ jsx2(
|
|
328
|
+
Box2,
|
|
329
|
+
{
|
|
330
|
+
flexDirection: "column",
|
|
331
|
+
width: 24,
|
|
332
|
+
borderStyle: "single",
|
|
333
|
+
borderRight: true,
|
|
334
|
+
borderTop: false,
|
|
335
|
+
borderBottom: false,
|
|
336
|
+
borderLeft: false,
|
|
337
|
+
children: Array.from(byPkg.entries()).map(([pkg, pkgFiles]) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
338
|
+
/* @__PURE__ */ jsxs2(Text2, { bold: true, dimColor: true, children: [
|
|
339
|
+
" ",
|
|
340
|
+
pkg
|
|
341
|
+
] }),
|
|
342
|
+
pkgFiles.map((f) => {
|
|
343
|
+
const idx = files.indexOf(f);
|
|
344
|
+
const selected = idx === selectedIndex;
|
|
345
|
+
return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(
|
|
346
|
+
Text2,
|
|
347
|
+
{
|
|
348
|
+
backgroundColor: selected && focused ? "blue" : void 0,
|
|
349
|
+
color: selected && focused ? "white" : selected ? "cyan" : void 0,
|
|
350
|
+
children: [
|
|
351
|
+
f.encrypted ? "\u{1F512} " : " ",
|
|
352
|
+
f.environment
|
|
353
|
+
]
|
|
354
|
+
}
|
|
355
|
+
) }, f.path);
|
|
356
|
+
})
|
|
357
|
+
] }, pkg))
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
815
362
|
// src/tui/HelpOverlay.tsx
|
|
816
|
-
import { Box as
|
|
817
|
-
import { jsx as
|
|
363
|
+
import { Box as Box3, Text as Text3, useInput as useInput3, useStdin as useStdin3 } from "ink";
|
|
364
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
818
365
|
var SECTIONS = [
|
|
819
366
|
{
|
|
820
367
|
title: "Navigation",
|
|
@@ -852,30 +399,36 @@ var SECTIONS = [
|
|
|
852
399
|
];
|
|
853
400
|
var KEY_WIDTH = 10;
|
|
854
401
|
function HelpOverlay({ onClose }) {
|
|
855
|
-
const { isRawModeSupported } =
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
402
|
+
const { isRawModeSupported } = useStdin3();
|
|
403
|
+
useInput3(
|
|
404
|
+
(input, key) => {
|
|
405
|
+
if (input === "?" || input === "q" || key.escape) onClose();
|
|
406
|
+
},
|
|
407
|
+
{ isActive: isRawModeSupported }
|
|
408
|
+
);
|
|
409
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
410
|
+
/* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
|
|
411
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, color: "cyan", children: [
|
|
412
|
+
"dotenvx-ui",
|
|
413
|
+
" "
|
|
414
|
+
] }),
|
|
415
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "keyboard shortcuts" })
|
|
863
416
|
] }),
|
|
864
|
-
SECTIONS.map((section) => /* @__PURE__ */
|
|
865
|
-
/* @__PURE__ */
|
|
866
|
-
section.rows.map(([key, desc]) => /* @__PURE__ */
|
|
867
|
-
/* @__PURE__ */
|
|
868
|
-
/* @__PURE__ */
|
|
417
|
+
SECTIONS.map((section) => /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
|
|
418
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: section.title }),
|
|
419
|
+
section.rows.map(([key, desc]) => /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
420
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: key.padEnd(KEY_WIDTH) }),
|
|
421
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: desc })
|
|
869
422
|
] }, key))
|
|
870
423
|
] }, section.title)),
|
|
871
|
-
/* @__PURE__ */
|
|
424
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "? / q / esc close help" }) })
|
|
872
425
|
] });
|
|
873
426
|
}
|
|
874
427
|
|
|
875
428
|
// src/tui/InlineForm.tsx
|
|
876
|
-
import {
|
|
877
|
-
import {
|
|
878
|
-
import { jsx as
|
|
429
|
+
import { Box as Box4, Text as Text4, useInput as useInput4, useStdin as useStdin4, useStdout as useStdout2 } from "ink";
|
|
430
|
+
import { useRef as useRef2, useState as useState3 } from "react";
|
|
431
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
879
432
|
function makeInitialState(value) {
|
|
880
433
|
const lines = value.split("\n");
|
|
881
434
|
return {
|
|
@@ -885,153 +438,332 @@ function makeInitialState(value) {
|
|
|
885
438
|
};
|
|
886
439
|
}
|
|
887
440
|
var CHROME = 5;
|
|
888
|
-
function InlineForm({
|
|
889
|
-
|
|
441
|
+
function InlineForm({
|
|
442
|
+
label,
|
|
443
|
+
initialValue = "",
|
|
444
|
+
onSubmit,
|
|
445
|
+
onCancel
|
|
446
|
+
}) {
|
|
447
|
+
const { isRawModeSupported } = useStdin4();
|
|
890
448
|
const { stdout } = useStdout2();
|
|
891
|
-
const [editor, setEditor] = useState3(
|
|
449
|
+
const [editor, setEditor] = useState3(
|
|
450
|
+
() => makeInitialState(initialValue)
|
|
451
|
+
);
|
|
892
452
|
const editorRef = useRef2(editor);
|
|
893
453
|
editorRef.current = editor;
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
const { lines: lines2, row: row2, col: col2 } = editorRef.current;
|
|
900
|
-
const lineWidth = Math.max(1, (stdout?.columns ?? 80) - CHROME);
|
|
901
|
-
if (key.return) {
|
|
902
|
-
onSubmit(lines2.join("\n"));
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
if (key.upArrow) {
|
|
906
|
-
const line = lines2[row2];
|
|
907
|
-
const visualRow = Math.floor(col2 / lineWidth);
|
|
908
|
-
if (visualRow > 0) {
|
|
909
|
-
const targetVisualRow = visualRow - 1;
|
|
910
|
-
const colInVisualRow = col2 % lineWidth;
|
|
911
|
-
const newCol = Math.min(targetVisualRow * lineWidth + colInVisualRow, line.length);
|
|
912
|
-
setEditor({ lines: lines2, row: row2, col: newCol });
|
|
913
|
-
} else if (row2 > 0) {
|
|
914
|
-
const prevLine = lines2[row2 - 1];
|
|
915
|
-
const prevVisualRows = Math.floor(prevLine.length / lineWidth);
|
|
916
|
-
const colInVisualRow = col2 % lineWidth;
|
|
917
|
-
const newCol = Math.min(prevVisualRows * lineWidth + colInVisualRow, prevLine.length);
|
|
918
|
-
setEditor({ lines: lines2, row: row2 - 1, col: newCol });
|
|
454
|
+
useInput4(
|
|
455
|
+
(input, key) => {
|
|
456
|
+
if (key.escape) {
|
|
457
|
+
onCancel();
|
|
458
|
+
return;
|
|
919
459
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const lastVisualRow = Math.floor(line.length / lineWidth);
|
|
926
|
-
if (visualRow < lastVisualRow) {
|
|
927
|
-
const colInVisualRow = col2 % lineWidth;
|
|
928
|
-
const newCol = Math.min((visualRow + 1) * lineWidth + colInVisualRow, line.length);
|
|
929
|
-
setEditor({ lines: lines2, row: row2, col: newCol });
|
|
930
|
-
} else if (row2 < lines2.length - 1) {
|
|
931
|
-
const colInVisualRow = col2 % lineWidth;
|
|
932
|
-
const newCol = Math.min(colInVisualRow, lines2[row2 + 1].length);
|
|
933
|
-
setEditor({ lines: lines2, row: row2 + 1, col: newCol });
|
|
460
|
+
const { lines: lines2, row: row2, col: col2 } = editorRef.current;
|
|
461
|
+
const lineWidth = Math.max(1, (stdout?.columns ?? 80) - CHROME);
|
|
462
|
+
if (key.return) {
|
|
463
|
+
onSubmit(lines2.join("\n"));
|
|
464
|
+
return;
|
|
934
465
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
466
|
+
if (key.upArrow) {
|
|
467
|
+
const line = lines2[row2];
|
|
468
|
+
const visualRow = Math.floor(col2 / lineWidth);
|
|
469
|
+
if (visualRow > 0) {
|
|
470
|
+
const targetVisualRow = visualRow - 1;
|
|
471
|
+
const colInVisualRow = col2 % lineWidth;
|
|
472
|
+
const newCol = Math.min(
|
|
473
|
+
targetVisualRow * lineWidth + colInVisualRow,
|
|
474
|
+
line.length
|
|
475
|
+
);
|
|
476
|
+
setEditor({ lines: lines2, row: row2, col: newCol });
|
|
477
|
+
} else if (row2 > 0) {
|
|
478
|
+
const prevLine = lines2[row2 - 1];
|
|
479
|
+
const prevVisualRows = Math.floor(prevLine.length / lineWidth);
|
|
480
|
+
const colInVisualRow = col2 % lineWidth;
|
|
481
|
+
const newCol = Math.min(
|
|
482
|
+
prevVisualRows * lineWidth + colInVisualRow,
|
|
483
|
+
prevLine.length
|
|
484
|
+
);
|
|
485
|
+
setEditor({ lines: lines2, row: row2 - 1, col: newCol });
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
943
488
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
489
|
+
if (key.downArrow) {
|
|
490
|
+
const line = lines2[row2];
|
|
491
|
+
const visualRow = Math.floor(col2 / lineWidth);
|
|
492
|
+
const lastVisualRow = Math.floor(line.length / lineWidth);
|
|
493
|
+
if (visualRow < lastVisualRow) {
|
|
494
|
+
const colInVisualRow = col2 % lineWidth;
|
|
495
|
+
const newCol = Math.min(
|
|
496
|
+
(visualRow + 1) * lineWidth + colInVisualRow,
|
|
497
|
+
line.length
|
|
498
|
+
);
|
|
499
|
+
setEditor({ lines: lines2, row: row2, col: newCol });
|
|
500
|
+
} else if (row2 < lines2.length - 1) {
|
|
501
|
+
const colInVisualRow = col2 % lineWidth;
|
|
502
|
+
const newCol = Math.min(colInVisualRow, lines2[row2 + 1].length);
|
|
503
|
+
setEditor({ lines: lines2, row: row2 + 1, col: newCol });
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
951
506
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
if (key.ctrl && input === "k") {
|
|
963
|
-
const newLines = lines2.map((l, i) => i === row2 ? l.slice(0, col2) : l);
|
|
964
|
-
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
if (key.ctrl && input === "u") {
|
|
968
|
-
const newLines = lines2.map((l, i) => i === row2 ? l.slice(col2) : l);
|
|
969
|
-
setEditor({ lines: newLines, row: row2, col: 0 });
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
if (key.backspace) {
|
|
973
|
-
if (col2 > 0) {
|
|
974
|
-
const cur = lines2[row2];
|
|
975
|
-
const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2 - 1) + cur.slice(col2) : l);
|
|
976
|
-
setEditor({ lines: newLines, row: row2, col: col2 - 1 });
|
|
977
|
-
} else if (row2 > 0) {
|
|
978
|
-
const prevLen = lines2[row2 - 1].length;
|
|
979
|
-
const merged = lines2[row2 - 1] + lines2[row2];
|
|
980
|
-
const newLines = [...lines2.slice(0, row2 - 1), merged, ...lines2.slice(row2 + 1)];
|
|
981
|
-
setEditor({ lines: newLines, row: row2 - 1, col: prevLen });
|
|
507
|
+
if (key.leftArrow) {
|
|
508
|
+
if (col2 > 0) {
|
|
509
|
+
setEditor({ lines: lines2, row: row2, col: col2 - 1 });
|
|
510
|
+
} else if (row2 > 0) {
|
|
511
|
+
const newRow = row2 - 1;
|
|
512
|
+
setEditor({ lines: lines2, row: newRow, col: lines2[newRow].length });
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
982
515
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
|
|
516
|
+
if (key.rightArrow) {
|
|
517
|
+
if (col2 < lines2[row2].length) {
|
|
518
|
+
setEditor({ lines: lines2, row: row2, col: col2 + 1 });
|
|
519
|
+
} else if (row2 < lines2.length - 1) {
|
|
520
|
+
setEditor({ lines: lines2, row: row2 + 1, col: 0 });
|
|
521
|
+
}
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (key.ctrl && input === "a" || key.home) {
|
|
525
|
+
setEditor({ lines: lines2, row: row2, col: 0 });
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (key.ctrl && input === "e" || key.end) {
|
|
529
|
+
setEditor({ lines: lines2, row: row2, col: lines2[row2].length });
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (key.ctrl && input === "k") {
|
|
533
|
+
const newLines = lines2.map((l, i) => i === row2 ? l.slice(0, col2) : l);
|
|
993
534
|
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
535
|
+
return;
|
|
994
536
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
537
|
+
if (key.ctrl && input === "u") {
|
|
538
|
+
const newLines = lines2.map((l, i) => i === row2 ? l.slice(col2) : l);
|
|
539
|
+
setEditor({ lines: newLines, row: row2, col: 0 });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (key.backspace) {
|
|
543
|
+
if (col2 > 0) {
|
|
544
|
+
const cur = lines2[row2];
|
|
545
|
+
const newLines = lines2.map(
|
|
546
|
+
(l, i) => i === row2 ? cur.slice(0, col2 - 1) + cur.slice(col2) : l
|
|
547
|
+
);
|
|
548
|
+
setEditor({ lines: newLines, row: row2, col: col2 - 1 });
|
|
549
|
+
} else if (row2 > 0) {
|
|
550
|
+
const prevLen = lines2[row2 - 1].length;
|
|
551
|
+
const merged = lines2[row2 - 1] + lines2[row2];
|
|
552
|
+
const newLines = [
|
|
553
|
+
...lines2.slice(0, row2 - 1),
|
|
554
|
+
merged,
|
|
555
|
+
...lines2.slice(row2 + 1)
|
|
556
|
+
];
|
|
557
|
+
setEditor({ lines: newLines, row: row2 - 1, col: prevLen });
|
|
558
|
+
}
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (key.delete) {
|
|
562
|
+
const cur = lines2[row2];
|
|
563
|
+
if (col2 < cur.length) {
|
|
564
|
+
const newLines = lines2.map(
|
|
565
|
+
(l, i) => i === row2 ? cur.slice(0, col2) + cur.slice(col2 + 1) : l
|
|
566
|
+
);
|
|
567
|
+
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
568
|
+
} else if (row2 < lines2.length - 1) {
|
|
569
|
+
const merged = cur + lines2[row2 + 1];
|
|
570
|
+
const newLines = [
|
|
571
|
+
...lines2.slice(0, row2),
|
|
572
|
+
merged,
|
|
573
|
+
...lines2.slice(row2 + 2)
|
|
574
|
+
];
|
|
575
|
+
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
576
|
+
}
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (input && !key.ctrl && !key.meta) {
|
|
580
|
+
const cur = lines2[row2];
|
|
581
|
+
const newLines = lines2.map(
|
|
582
|
+
(l, i) => i === row2 ? cur.slice(0, col2) + input + cur.slice(col2) : l
|
|
583
|
+
);
|
|
584
|
+
setEditor({ lines: newLines, row: row2, col: col2 + input.length });
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
{ isActive: isRawModeSupported }
|
|
588
|
+
);
|
|
1003
589
|
const { lines, row, col } = editor;
|
|
1004
|
-
return /* @__PURE__ */
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
590
|
+
return /* @__PURE__ */ jsxs4(
|
|
591
|
+
Box4,
|
|
592
|
+
{
|
|
593
|
+
flexDirection: "column",
|
|
594
|
+
borderStyle: "single",
|
|
595
|
+
borderTop: true,
|
|
596
|
+
borderBottom: false,
|
|
597
|
+
borderLeft: false,
|
|
598
|
+
borderRight: false,
|
|
599
|
+
paddingX: 1,
|
|
600
|
+
paddingTop: 0,
|
|
601
|
+
children: [
|
|
602
|
+
/* @__PURE__ */ jsxs4(Box4, { children: [
|
|
603
|
+
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: "cyan", children: [
|
|
604
|
+
label,
|
|
605
|
+
" "
|
|
606
|
+
] }),
|
|
607
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u21B5 confirm esc cancel" })
|
|
608
|
+
] }),
|
|
609
|
+
lines.map((line, r) => {
|
|
610
|
+
const isActive = r === row;
|
|
611
|
+
if (!isActive) {
|
|
612
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
613
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " " }),
|
|
614
|
+
/* @__PURE__ */ jsx4(Text4, { children: line || " " })
|
|
615
|
+
] }, r);
|
|
616
|
+
}
|
|
617
|
+
const before = line.slice(0, col);
|
|
618
|
+
const cursor = line[col] ?? " ";
|
|
619
|
+
const after = line.slice(col + 1);
|
|
620
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
621
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " " }),
|
|
622
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
623
|
+
before,
|
|
624
|
+
/* @__PURE__ */ jsx4(Text4, { inverse: true, children: cursor }),
|
|
625
|
+
after
|
|
626
|
+
] })
|
|
627
|
+
] }, r);
|
|
628
|
+
})
|
|
629
|
+
]
|
|
630
|
+
}
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/tui/KeyTable.tsx
|
|
635
|
+
import { Box as Box5, Text as Text5, useInput as useInput5, useStdin as useStdin5 } from "ink";
|
|
636
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
637
|
+
var SECRET_PATTERN = /secret|password|token|key|private|api_?key/i;
|
|
638
|
+
var MAX_INLINE = 48;
|
|
639
|
+
function truncate(s) {
|
|
640
|
+
const first = s.split("\n")[0];
|
|
641
|
+
return first.length > MAX_INLINE ? `${first.slice(0, MAX_INLINE - 1)}\u2026` : first;
|
|
642
|
+
}
|
|
643
|
+
function maskValue(k, revealed) {
|
|
644
|
+
if (revealed.has(k.key)) return truncate(revealed.get(k.key));
|
|
645
|
+
if (k.encrypted) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
646
|
+
if (SECRET_PATTERN.test(k.key)) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
647
|
+
return truncate(k.value);
|
|
648
|
+
}
|
|
649
|
+
function KeyTable({
|
|
650
|
+
file,
|
|
651
|
+
keys,
|
|
652
|
+
selectedIndex,
|
|
653
|
+
focused,
|
|
654
|
+
interactive,
|
|
655
|
+
revealed,
|
|
656
|
+
onSelect,
|
|
657
|
+
maxRows
|
|
658
|
+
}) {
|
|
659
|
+
const { isRawModeSupported } = useStdin5();
|
|
660
|
+
useInput5(
|
|
661
|
+
(_, key) => {
|
|
662
|
+
if (!focused) return;
|
|
663
|
+
if (key.upArrow) onSelect(Math.max(0, selectedIndex - 1));
|
|
664
|
+
if (key.downArrow) onSelect(Math.min(keys.length - 1, selectedIndex + 1));
|
|
665
|
+
},
|
|
666
|
+
{ isActive: isRawModeSupported && interactive }
|
|
667
|
+
);
|
|
668
|
+
const maxVisible = Math.max(3, maxRows - 3);
|
|
669
|
+
const { start, end, above, below } = scrollWindow(
|
|
670
|
+
keys.length,
|
|
671
|
+
selectedIndex,
|
|
672
|
+
maxVisible
|
|
673
|
+
);
|
|
674
|
+
const visibleKeys = keys.slice(start, end);
|
|
675
|
+
const keyColWidth = Math.min(48, Math.max(16, ...keys.map((k) => k.key.length))) + 2;
|
|
676
|
+
const encBadge = file.encrypted ? /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: " encrypted" }) : /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " plain" });
|
|
677
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", flexGrow: 1, children: [
|
|
678
|
+
/* @__PURE__ */ jsxs5(Box5, { paddingX: 1, children: [
|
|
679
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: file.relativePath }),
|
|
680
|
+
encBadge,
|
|
681
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
682
|
+
" ",
|
|
683
|
+
keys.length,
|
|
684
|
+
" key",
|
|
685
|
+
keys.length === 1 ? "" : "s"
|
|
686
|
+
] })
|
|
1011
687
|
] }),
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
688
|
+
keys.length === 0 ? /* @__PURE__ */ jsx5(Box5, { paddingX: 2, marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
689
|
+
"No keys found. Press ",
|
|
690
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "a" }),
|
|
691
|
+
" to add one."
|
|
692
|
+
] }) }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
693
|
+
above > 0 && /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
694
|
+
"\u2191 ",
|
|
695
|
+
above,
|
|
696
|
+
" more"
|
|
697
|
+
] }) }),
|
|
698
|
+
visibleKeys.map((k, i) => {
|
|
699
|
+
const idx = start + i;
|
|
700
|
+
const selected = idx === selectedIndex;
|
|
701
|
+
const value = maskValue(k, revealed);
|
|
702
|
+
const lockIcon = k.encrypted && !revealed.has(k.key) ? " \u{1F512}" : "";
|
|
703
|
+
return /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsxs5(
|
|
704
|
+
Text5,
|
|
705
|
+
{
|
|
706
|
+
backgroundColor: selected && focused ? "blue" : void 0,
|
|
707
|
+
color: selected && focused ? "white" : selected ? "cyan" : void 0,
|
|
708
|
+
children: [
|
|
709
|
+
k.key.padEnd(keyColWidth),
|
|
710
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: !selected, children: value }),
|
|
711
|
+
lockIcon
|
|
712
|
+
]
|
|
713
|
+
}
|
|
714
|
+
) }, k.key);
|
|
715
|
+
}),
|
|
716
|
+
below > 0 && /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
717
|
+
"\u2193 ",
|
|
718
|
+
below,
|
|
719
|
+
" more"
|
|
720
|
+
] }) })
|
|
721
|
+
] })
|
|
1032
722
|
] });
|
|
1033
723
|
}
|
|
1034
724
|
|
|
725
|
+
// src/tui/StatusBar.tsx
|
|
726
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
727
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
728
|
+
var FILE_HINTS = [
|
|
729
|
+
{ key: "\u2191\u2193", action: "navigate" },
|
|
730
|
+
{ key: "tab", action: "switch panel" },
|
|
731
|
+
{ key: "?", action: "help" },
|
|
732
|
+
{ key: "q", action: "quit" }
|
|
733
|
+
];
|
|
734
|
+
var KEY_HINTS = [
|
|
735
|
+
{ key: "\u2191\u2193", action: "navigate" },
|
|
736
|
+
{ key: "tab", action: "switch" },
|
|
737
|
+
{ key: "enter", action: "edit" },
|
|
738
|
+
{ key: "y", action: "copy" },
|
|
739
|
+
{ key: "r", action: "reveal" },
|
|
740
|
+
{ key: "a", action: "add" },
|
|
741
|
+
{ key: "D", action: "delete" },
|
|
742
|
+
{ key: "d", action: "diff" },
|
|
743
|
+
{ key: "?", action: "help" },
|
|
744
|
+
{ key: "q", action: "quit" }
|
|
745
|
+
];
|
|
746
|
+
function Hints({ hints }) {
|
|
747
|
+
return /* @__PURE__ */ jsx6(Box6, { gap: 2, children: hints.map(({ key, action }) => /* @__PURE__ */ jsxs6(Box6, { gap: 1, children: [
|
|
748
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: key }),
|
|
749
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: action })
|
|
750
|
+
] }, key)) });
|
|
751
|
+
}
|
|
752
|
+
function StatusBar({ message, focus }) {
|
|
753
|
+
return /* @__PURE__ */ jsx6(
|
|
754
|
+
Box6,
|
|
755
|
+
{
|
|
756
|
+
borderStyle: "single",
|
|
757
|
+
borderTop: true,
|
|
758
|
+
borderBottom: false,
|
|
759
|
+
borderLeft: false,
|
|
760
|
+
borderRight: false,
|
|
761
|
+
paddingX: 1,
|
|
762
|
+
children: message ? /* @__PURE__ */ jsx6(Text6, { children: message }) : /* @__PURE__ */ jsx6(Hints, { hints: focus === "files" ? FILE_HINTS : KEY_HINTS })
|
|
763
|
+
}
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
1035
767
|
// src/tui/App.tsx
|
|
1036
768
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1037
769
|
function App({ files }) {
|
|
@@ -1065,108 +797,111 @@ function App({ files }) {
|
|
|
1065
797
|
setStatusMsg(msg);
|
|
1066
798
|
setTimeout(() => setStatusMsg(void 0), ms);
|
|
1067
799
|
}
|
|
1068
|
-
useInput6(
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
if (input === "?") {
|
|
1074
|
-
setMode({ type: "help" });
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
if (key.tab) {
|
|
1078
|
-
setFocus((f) => f === "files" ? "keys" : "files");
|
|
1079
|
-
return;
|
|
1080
|
-
}
|
|
1081
|
-
if (focus !== "keys") return;
|
|
1082
|
-
const k = keys[keyIndex];
|
|
1083
|
-
if (input === "r") {
|
|
1084
|
-
if (!k) return;
|
|
1085
|
-
if (revealed.has(k.key)) {
|
|
1086
|
-
setRevealed((prev) => {
|
|
1087
|
-
const next = new Map(prev);
|
|
1088
|
-
next.delete(k.key);
|
|
1089
|
-
return next;
|
|
1090
|
-
});
|
|
800
|
+
useInput6(
|
|
801
|
+
(input, key) => {
|
|
802
|
+
if (input === "q" || key.escape) {
|
|
803
|
+
exit();
|
|
1091
804
|
return;
|
|
1092
805
|
}
|
|
1093
|
-
if (
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
flash("\u{1F512} Private key not found in environment");
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
setRevealed((prev) => new Map(prev).set(k.key, plain));
|
|
1100
|
-
} else {
|
|
1101
|
-
setRevealed((prev) => new Map(prev).set(k.key, k.value));
|
|
806
|
+
if (input === "?") {
|
|
807
|
+
setMode({ type: "help" });
|
|
808
|
+
return;
|
|
1102
809
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
if (input === "R") {
|
|
1106
|
-
if (revealed.size > 0) {
|
|
1107
|
-
setRevealed(/* @__PURE__ */ new Map());
|
|
810
|
+
if (key.tab) {
|
|
811
|
+
setFocus((f) => f === "files" ? "keys" : "files");
|
|
1108
812
|
return;
|
|
1109
813
|
}
|
|
1110
|
-
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
if (
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
814
|
+
if (focus !== "keys") return;
|
|
815
|
+
const k = keys[keyIndex];
|
|
816
|
+
if (input === "r") {
|
|
817
|
+
if (!k) return;
|
|
818
|
+
if (revealed.has(k.key)) {
|
|
819
|
+
setRevealed((prev) => {
|
|
820
|
+
const next = new Map(prev);
|
|
821
|
+
next.delete(k.key);
|
|
822
|
+
return next;
|
|
823
|
+
});
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (k.encrypted) {
|
|
827
|
+
const plain = decryptValue(k.value, selectedFile.path);
|
|
828
|
+
if (plain === null) {
|
|
829
|
+
flash("\u{1F512} Private key not found in environment");
|
|
1117
830
|
return;
|
|
1118
831
|
}
|
|
1119
|
-
|
|
832
|
+
setRevealed((prev) => new Map(prev).set(k.key, plain));
|
|
1120
833
|
} else {
|
|
1121
|
-
|
|
834
|
+
setRevealed((prev) => new Map(prev).set(k.key, k.value));
|
|
1122
835
|
}
|
|
836
|
+
return;
|
|
1123
837
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
838
|
+
if (input === "R") {
|
|
839
|
+
if (revealed.size > 0) {
|
|
840
|
+
setRevealed(/* @__PURE__ */ new Map());
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const decrypted = keys.some((e) => e.encrypted) ? decryptAllValues(selectedFile.path) : {};
|
|
844
|
+
const next = /* @__PURE__ */ new Map();
|
|
845
|
+
for (const entry of keys) {
|
|
846
|
+
if (entry.encrypted) {
|
|
847
|
+
const plain = decrypted[entry.key];
|
|
848
|
+
if (plain === void 0 || isEncryptedValue(plain)) {
|
|
849
|
+
flash("\u{1F512} Private key not found \u2014 cannot reveal all");
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
next.set(entry.key, plain);
|
|
853
|
+
} else {
|
|
854
|
+
next.set(entry.key, entry.value);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
setRevealed(next);
|
|
1132
858
|
return;
|
|
1133
859
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
if (!k) return;
|
|
1140
|
-
if (k.encrypted) {
|
|
1141
|
-
const plain = decryptValue(k.value, selectedFile.path);
|
|
1142
|
-
if (plain === null) {
|
|
1143
|
-
flash("\u{1F512} Private key not found \u2014 cannot edit");
|
|
860
|
+
if (input === "y") {
|
|
861
|
+
if (!k) return;
|
|
862
|
+
const value = k.encrypted ? decryptValue(k.value, selectedFile.path) : k.value;
|
|
863
|
+
if (value === null) {
|
|
864
|
+
flash("\u{1F512} Private key not found \u2014 cannot copy");
|
|
1144
865
|
return;
|
|
1145
866
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
867
|
+
clipboard.writeSync(value);
|
|
868
|
+
flash(`Copied ${k.key}`);
|
|
869
|
+
return;
|
|
1149
870
|
}
|
|
1150
|
-
return
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
871
|
+
if (key.return) {
|
|
872
|
+
if (!k) return;
|
|
873
|
+
if (k.encrypted) {
|
|
874
|
+
const plain = decryptValue(k.value, selectedFile.path);
|
|
875
|
+
if (plain === null) {
|
|
876
|
+
flash("\u{1F512} Private key not found \u2014 cannot edit");
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
setMode({ type: "edit", key: { ...k, value: plain } });
|
|
880
|
+
} else {
|
|
881
|
+
setMode({ type: "edit", key: k });
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (input === "a") {
|
|
886
|
+
setMode({ type: "add-key" });
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (input === "D") {
|
|
890
|
+
if (!k) return;
|
|
891
|
+
setMode({ type: "confirm-delete", key: k });
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
if (input === "d") {
|
|
895
|
+
setMode({ type: "diff" });
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
if (input === "e") {
|
|
899
|
+
setMode({ type: "confirm-encrypt" });
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
{ isActive: isRawModeSupported && mode.type === "normal" }
|
|
904
|
+
);
|
|
1170
905
|
if (mode.type === "edit") {
|
|
1171
906
|
const editing = mode.key;
|
|
1172
907
|
return /* @__PURE__ */ jsx7(
|
|
@@ -1304,7 +1039,15 @@ function App({ files }) {
|
|
|
1304
1039
|
statusMsg,
|
|
1305
1040
|
focus2: focus,
|
|
1306
1041
|
interactive: false,
|
|
1307
|
-
extra: /* @__PURE__ */ jsx7(
|
|
1042
|
+
extra: /* @__PURE__ */ jsx7(
|
|
1043
|
+
ConfirmAddEncrypt,
|
|
1044
|
+
{
|
|
1045
|
+
keyName,
|
|
1046
|
+
onEncrypt: () => commit(true),
|
|
1047
|
+
onPlain: () => commit(false),
|
|
1048
|
+
onCancel: () => setMode({ type: "normal" })
|
|
1049
|
+
}
|
|
1050
|
+
)
|
|
1308
1051
|
}
|
|
1309
1052
|
);
|
|
1310
1053
|
}
|
|
@@ -1372,7 +1115,9 @@ function App({ files }) {
|
|
|
1372
1115
|
flash(`Encrypted ${selectedFile.relativePath}`);
|
|
1373
1116
|
}
|
|
1374
1117
|
} catch (err) {
|
|
1375
|
-
flash(
|
|
1118
|
+
flash(
|
|
1119
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
1120
|
+
);
|
|
1376
1121
|
}
|
|
1377
1122
|
refreshKeys();
|
|
1378
1123
|
setRevealed(/* @__PURE__ */ new Map());
|
|
@@ -1436,17 +1181,26 @@ function Layout({
|
|
|
1436
1181
|
/* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1437
1182
|
/* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "dotenvx-ui" }),
|
|
1438
1183
|
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1439
|
-
"
|
|
1184
|
+
" ",
|
|
1440
1185
|
selectedFile.relativePath,
|
|
1441
|
-
"
|
|
1186
|
+
" \xB7 ",
|
|
1442
1187
|
files.length,
|
|
1443
|
-
" files
|
|
1188
|
+
" files \xB7 ",
|
|
1444
1189
|
encCount,
|
|
1445
1190
|
" enc"
|
|
1446
1191
|
] })
|
|
1447
1192
|
] }),
|
|
1448
1193
|
/* @__PURE__ */ jsxs7(Box7, { height: listRows, children: [
|
|
1449
|
-
/* @__PURE__ */ jsx7(
|
|
1194
|
+
/* @__PURE__ */ jsx7(
|
|
1195
|
+
FileList,
|
|
1196
|
+
{
|
|
1197
|
+
files,
|
|
1198
|
+
selectedIndex: fileIndex,
|
|
1199
|
+
focused: focus === "files",
|
|
1200
|
+
interactive,
|
|
1201
|
+
onSelect: onSelectFile
|
|
1202
|
+
}
|
|
1203
|
+
),
|
|
1450
1204
|
/* @__PURE__ */ jsx7(
|
|
1451
1205
|
KeyTable,
|
|
1452
1206
|
{
|
|
@@ -1462,11 +1216,26 @@ function Layout({
|
|
|
1462
1216
|
)
|
|
1463
1217
|
] }),
|
|
1464
1218
|
extra,
|
|
1465
|
-
!extra && /* @__PURE__ */ jsx7(
|
|
1219
|
+
!extra && /* @__PURE__ */ jsx7(
|
|
1220
|
+
ValuePreview,
|
|
1221
|
+
{
|
|
1222
|
+
keys,
|
|
1223
|
+
keyIndex,
|
|
1224
|
+
focus,
|
|
1225
|
+
revealed,
|
|
1226
|
+
width: termCols
|
|
1227
|
+
}
|
|
1228
|
+
),
|
|
1466
1229
|
/* @__PURE__ */ jsx7(StatusBar, { focus, message: statusMsg })
|
|
1467
1230
|
] });
|
|
1468
1231
|
}
|
|
1469
|
-
function ValuePreview({
|
|
1232
|
+
function ValuePreview({
|
|
1233
|
+
keys,
|
|
1234
|
+
keyIndex,
|
|
1235
|
+
focus,
|
|
1236
|
+
revealed,
|
|
1237
|
+
width
|
|
1238
|
+
}) {
|
|
1470
1239
|
if (focus !== "keys") return null;
|
|
1471
1240
|
const k = keys[keyIndex];
|
|
1472
1241
|
if (!k) return null;
|
|
@@ -1490,35 +1259,47 @@ function ValuePreview({ keys, keyIndex, focus, revealed, width }) {
|
|
|
1490
1259
|
children: [
|
|
1491
1260
|
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: "cyan", children: [
|
|
1492
1261
|
k.key,
|
|
1493
|
-
"
|
|
1262
|
+
" "
|
|
1494
1263
|
] }),
|
|
1495
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\xB7
|
|
1496
|
-
/* @__PURE__ */ jsx7(
|
|
1264
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\xB7 " }),
|
|
1265
|
+
/* @__PURE__ */ jsx7(Box7, { overflow: "hidden", children: /* @__PURE__ */ jsx7(Text7, { children: flat }) })
|
|
1497
1266
|
]
|
|
1498
1267
|
}
|
|
1499
1268
|
);
|
|
1500
1269
|
}
|
|
1501
1270
|
function ConfirmDelete({ keyName, onConfirm, onCancel }) {
|
|
1502
1271
|
const { isRawModeSupported } = useStdin6();
|
|
1503
|
-
useInput6(
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1272
|
+
useInput6(
|
|
1273
|
+
(input) => {
|
|
1274
|
+
if (input === "y" || input === "Y") onConfirm();
|
|
1275
|
+
else onCancel();
|
|
1276
|
+
},
|
|
1277
|
+
{ isActive: isRawModeSupported }
|
|
1278
|
+
);
|
|
1507
1279
|
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1508
1280
|
/* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
|
|
1509
1281
|
"Delete ",
|
|
1510
1282
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
|
|
1511
|
-
"?
|
|
1283
|
+
"?",
|
|
1284
|
+
" "
|
|
1512
1285
|
] }),
|
|
1513
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y confirm
|
|
1286
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y confirm any other key cancel" })
|
|
1514
1287
|
] });
|
|
1515
1288
|
}
|
|
1516
|
-
function ConfirmEncrypt({
|
|
1289
|
+
function ConfirmEncrypt({
|
|
1290
|
+
decrypt,
|
|
1291
|
+
fileName,
|
|
1292
|
+
onConfirm,
|
|
1293
|
+
onCancel
|
|
1294
|
+
}) {
|
|
1517
1295
|
const { isRawModeSupported } = useStdin6();
|
|
1518
|
-
useInput6(
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1296
|
+
useInput6(
|
|
1297
|
+
(input) => {
|
|
1298
|
+
if (input === "y" || input === "Y") onConfirm();
|
|
1299
|
+
else onCancel();
|
|
1300
|
+
},
|
|
1301
|
+
{ isActive: isRawModeSupported }
|
|
1302
|
+
);
|
|
1522
1303
|
const action = decrypt ? "Decrypt" : "Encrypt";
|
|
1523
1304
|
const color = decrypt ? "yellow" : "green";
|
|
1524
1305
|
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
@@ -1526,42 +1307,52 @@ function ConfirmEncrypt({ decrypt, fileName, onConfirm, onCancel }) {
|
|
|
1526
1307
|
action,
|
|
1527
1308
|
" ",
|
|
1528
1309
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: fileName }),
|
|
1529
|
-
"?
|
|
1310
|
+
"?",
|
|
1311
|
+
" "
|
|
1530
1312
|
] }),
|
|
1531
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y confirm
|
|
1313
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y confirm any other key cancel" })
|
|
1532
1314
|
] });
|
|
1533
1315
|
}
|
|
1534
|
-
function ConfirmAddEncrypt({
|
|
1316
|
+
function ConfirmAddEncrypt({
|
|
1317
|
+
keyName,
|
|
1318
|
+
onEncrypt,
|
|
1319
|
+
onPlain,
|
|
1320
|
+
onCancel
|
|
1321
|
+
}) {
|
|
1535
1322
|
const { isRawModeSupported } = useStdin6();
|
|
1536
|
-
useInput6(
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1323
|
+
useInput6(
|
|
1324
|
+
(input, key) => {
|
|
1325
|
+
if (key.escape) {
|
|
1326
|
+
onCancel();
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
if (input === "y" || input === "Y") {
|
|
1330
|
+
onEncrypt();
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (input === "n" || input === "N" || key.return) {
|
|
1334
|
+
onPlain();
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
{ isActive: isRawModeSupported }
|
|
1339
|
+
);
|
|
1550
1340
|
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1551
1341
|
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
1552
1342
|
"Encrypt ",
|
|
1553
1343
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
|
|
1554
|
-
"?
|
|
1344
|
+
"?",
|
|
1345
|
+
" "
|
|
1555
1346
|
] }),
|
|
1556
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y encrypt
|
|
1347
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y encrypt n plain esc cancel" })
|
|
1557
1348
|
] });
|
|
1558
1349
|
}
|
|
1559
1350
|
|
|
1560
1351
|
// src/tui/ErrorBoundary.tsx
|
|
1561
|
-
import React4 from "react";
|
|
1562
1352
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
1353
|
+
import React from "react";
|
|
1563
1354
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1564
|
-
var ErrorBoundary = class extends
|
|
1355
|
+
var ErrorBoundary = class extends React.Component {
|
|
1565
1356
|
state = { error: null };
|
|
1566
1357
|
static getDerivedStateFromError(error) {
|
|
1567
1358
|
return { error };
|
|
@@ -1579,9 +1370,8 @@ var ErrorBoundary = class extends React4.Component {
|
|
|
1579
1370
|
};
|
|
1580
1371
|
|
|
1581
1372
|
// src/cli.tsx
|
|
1582
|
-
import { createRequire as createRequire2 } from "module";
|
|
1583
1373
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1584
|
-
var { version } =
|
|
1374
|
+
var { version } = createRequire(import.meta.url)("../package.json");
|
|
1585
1375
|
var HELP = `
|
|
1586
1376
|
dotenvx-ui \u2014 terminal and web UI for dotenvx environment files
|
|
1587
1377
|
|
|
@@ -1610,12 +1400,14 @@ var commands = {
|
|
|
1610
1400
|
console.log(HELP);
|
|
1611
1401
|
process.exit(0);
|
|
1612
1402
|
},
|
|
1613
|
-
|
|
1403
|
+
ui: runWebUI
|
|
1614
1404
|
};
|
|
1615
1405
|
var [, , command] = process.argv;
|
|
1616
1406
|
if (command !== void 0 && !(command in commands)) {
|
|
1617
|
-
console.error(
|
|
1618
|
-
|
|
1407
|
+
console.error(
|
|
1408
|
+
`Unknown command: ${command}
|
|
1409
|
+
Run dotenvx-ui --help for usage.`
|
|
1410
|
+
);
|
|
1619
1411
|
process.exit(1);
|
|
1620
1412
|
}
|
|
1621
1413
|
commands[command ?? ""]?.() ?? runTUI();
|
|
@@ -1625,9 +1417,17 @@ function runTUI() {
|
|
|
1625
1417
|
console.error("No .env files found in this directory.");
|
|
1626
1418
|
process.exit(1);
|
|
1627
1419
|
}
|
|
1628
|
-
render(
|
|
1420
|
+
render(
|
|
1421
|
+
/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }),
|
|
1422
|
+
{ alternateScreen: true }
|
|
1423
|
+
);
|
|
1629
1424
|
}
|
|
1630
|
-
function runWebUI() {
|
|
1631
|
-
|
|
1632
|
-
process.
|
|
1425
|
+
async function runWebUI() {
|
|
1426
|
+
const { startServer } = await import("./server-DDA3TXEA.js");
|
|
1427
|
+
const files = scan(process.cwd());
|
|
1428
|
+
if (files.length === 0) {
|
|
1429
|
+
console.error("No .env files found in this directory.");
|
|
1430
|
+
process.exit(1);
|
|
1431
|
+
}
|
|
1432
|
+
await startServer(process.cwd());
|
|
1633
1433
|
}
|