dotenvx-ui 0.1.1 → 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 +721 -920
- 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,40 +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
|
+
);
|
|
188
|
+
const keyColWidth = Math.min(48, Math.max(16, ...rows.map((r) => r.key.length))) + 2;
|
|
721
189
|
const leftName = trunc(left.relativePath, COL_VAL);
|
|
722
190
|
const rightName = rightFile ? trunc(rightFile.relativePath, COL_VAL) : "\u2014";
|
|
723
|
-
return /* @__PURE__ */
|
|
724
|
-
/* @__PURE__ */
|
|
725
|
-
/* @__PURE__ */
|
|
726
|
-
|
|
727
|
-
"
|
|
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 ",
|
|
728
199
|
left.relativePath,
|
|
729
|
-
"
|
|
200
|
+
" \u2194 ",
|
|
730
201
|
rightFile?.relativePath ?? "\u2014"
|
|
731
202
|
] })
|
|
732
203
|
] }),
|
|
733
|
-
/* @__PURE__ */
|
|
734
|
-
|
|
204
|
+
/* @__PURE__ */ jsxs(
|
|
205
|
+
Box,
|
|
735
206
|
{
|
|
736
207
|
flexDirection: "column",
|
|
737
208
|
paddingX: 1,
|
|
@@ -741,16 +212,16 @@ function DiffView({ left, files, onClose }) {
|
|
|
741
212
|
borderLeft: false,
|
|
742
213
|
borderRight: false,
|
|
743
214
|
children: [
|
|
744
|
-
/* @__PURE__ */
|
|
745
|
-
picker.above > 0 && /* @__PURE__ */
|
|
746
|
-
"
|
|
215
|
+
/* @__PURE__ */ jsx(Text, { bold: true, dimColor: true, children: "compare with" }),
|
|
216
|
+
picker.above > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
217
|
+
" \u2191 ",
|
|
747
218
|
picker.above,
|
|
748
219
|
" more"
|
|
749
220
|
] }),
|
|
750
221
|
others.slice(picker.start, picker.end).map((f, i) => {
|
|
751
222
|
const selected = picker.start + i === pickerIndex;
|
|
752
|
-
return /* @__PURE__ */
|
|
753
|
-
|
|
223
|
+
return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(
|
|
224
|
+
Text,
|
|
754
225
|
{
|
|
755
226
|
backgroundColor: selected ? "blue" : void 0,
|
|
756
227
|
color: selected ? "white" : void 0,
|
|
@@ -761,59 +232,136 @@ function DiffView({ left, files, onClose }) {
|
|
|
761
232
|
}
|
|
762
233
|
) }, f.path);
|
|
763
234
|
}),
|
|
764
|
-
picker.below > 0 && /* @__PURE__ */
|
|
765
|
-
"
|
|
235
|
+
picker.below > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
236
|
+
" \u2193 ",
|
|
766
237
|
picker.below,
|
|
767
238
|
" more"
|
|
768
239
|
] }),
|
|
769
|
-
others.length === 0 && /* @__PURE__ */
|
|
240
|
+
others.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: " no other files" })
|
|
770
241
|
]
|
|
771
242
|
}
|
|
772
243
|
),
|
|
773
|
-
/* @__PURE__ */
|
|
774
|
-
/* @__PURE__ */
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
"
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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: [
|
|
783
264
|
"\u2191 ",
|
|
784
265
|
rowsAbove,
|
|
785
266
|
" more"
|
|
786
267
|
] }),
|
|
787
|
-
visibleRows.map((row) => /* @__PURE__ */
|
|
788
|
-
rowsBelow > 0 && /* @__PURE__ */
|
|
268
|
+
visibleRows.map((row) => /* @__PURE__ */ jsx(DiffRow, { row, keyColWidth }, row.key)),
|
|
269
|
+
rowsBelow > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
789
270
|
"\u2193 ",
|
|
790
271
|
rowsBelow,
|
|
791
272
|
" more"
|
|
792
273
|
] }),
|
|
793
|
-
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." }) })
|
|
794
275
|
] }),
|
|
795
|
-
/* @__PURE__ */
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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
|
+
)
|
|
799
291
|
] });
|
|
800
292
|
}
|
|
801
|
-
function DiffRow({ row }) {
|
|
293
|
+
function DiffRow({ row, keyColWidth }) {
|
|
802
294
|
const { key, leftDisplay, rightDisplay, status } = row;
|
|
803
295
|
const color = status === "same" ? "green" : void 0;
|
|
804
|
-
return /* @__PURE__ */
|
|
805
|
-
/* @__PURE__ */
|
|
806
|
-
/* @__PURE__ */
|
|
296
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
297
|
+
/* @__PURE__ */ jsx(Text, { color, children: key.padEnd(keyColWidth) }),
|
|
298
|
+
/* @__PURE__ */ jsxs(Text, { color, children: [
|
|
807
299
|
" ",
|
|
808
300
|
(leftDisplay || "\u2014").padEnd(COL_VAL + 2)
|
|
809
301
|
] }),
|
|
810
|
-
/* @__PURE__ */
|
|
302
|
+
/* @__PURE__ */ jsx(Text, { color, children: rightDisplay || "\u2014" })
|
|
811
303
|
] });
|
|
812
304
|
}
|
|
813
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
|
+
|
|
814
362
|
// src/tui/HelpOverlay.tsx
|
|
815
|
-
import { Box as
|
|
816
|
-
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";
|
|
817
365
|
var SECTIONS = [
|
|
818
366
|
{
|
|
819
367
|
title: "Navigation",
|
|
@@ -851,30 +399,36 @@ var SECTIONS = [
|
|
|
851
399
|
];
|
|
852
400
|
var KEY_WIDTH = 10;
|
|
853
401
|
function HelpOverlay({ onClose }) {
|
|
854
|
-
const { isRawModeSupported } =
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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" })
|
|
862
416
|
] }),
|
|
863
|
-
SECTIONS.map((section) => /* @__PURE__ */
|
|
864
|
-
/* @__PURE__ */
|
|
865
|
-
section.rows.map(([key, desc]) => /* @__PURE__ */
|
|
866
|
-
/* @__PURE__ */
|
|
867
|
-
/* @__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 })
|
|
868
422
|
] }, key))
|
|
869
423
|
] }, section.title)),
|
|
870
|
-
/* @__PURE__ */
|
|
424
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "? / q / esc close help" }) })
|
|
871
425
|
] });
|
|
872
426
|
}
|
|
873
427
|
|
|
874
428
|
// src/tui/InlineForm.tsx
|
|
875
|
-
import {
|
|
876
|
-
import {
|
|
877
|
-
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";
|
|
878
432
|
function makeInitialState(value) {
|
|
879
433
|
const lines = value.split("\n");
|
|
880
434
|
return {
|
|
@@ -884,153 +438,332 @@ function makeInitialState(value) {
|
|
|
884
438
|
};
|
|
885
439
|
}
|
|
886
440
|
var CHROME = 5;
|
|
887
|
-
function InlineForm({
|
|
888
|
-
|
|
441
|
+
function InlineForm({
|
|
442
|
+
label,
|
|
443
|
+
initialValue = "",
|
|
444
|
+
onSubmit,
|
|
445
|
+
onCancel
|
|
446
|
+
}) {
|
|
447
|
+
const { isRawModeSupported } = useStdin4();
|
|
889
448
|
const { stdout } = useStdout2();
|
|
890
|
-
const [editor, setEditor] = useState3(
|
|
449
|
+
const [editor, setEditor] = useState3(
|
|
450
|
+
() => makeInitialState(initialValue)
|
|
451
|
+
);
|
|
891
452
|
const editorRef = useRef2(editor);
|
|
892
453
|
editorRef.current = editor;
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
const { lines: lines2, row: row2, col: col2 } = editorRef.current;
|
|
899
|
-
const lineWidth = Math.max(1, (stdout?.columns ?? 80) - CHROME);
|
|
900
|
-
if (key.return) {
|
|
901
|
-
onSubmit(lines2.join("\n"));
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
if (key.upArrow) {
|
|
905
|
-
const line = lines2[row2];
|
|
906
|
-
const visualRow = Math.floor(col2 / lineWidth);
|
|
907
|
-
if (visualRow > 0) {
|
|
908
|
-
const targetVisualRow = visualRow - 1;
|
|
909
|
-
const colInVisualRow = col2 % lineWidth;
|
|
910
|
-
const newCol = Math.min(targetVisualRow * lineWidth + colInVisualRow, line.length);
|
|
911
|
-
setEditor({ lines: lines2, row: row2, col: newCol });
|
|
912
|
-
} else if (row2 > 0) {
|
|
913
|
-
const prevLine = lines2[row2 - 1];
|
|
914
|
-
const prevVisualRows = Math.floor(prevLine.length / lineWidth);
|
|
915
|
-
const colInVisualRow = col2 % lineWidth;
|
|
916
|
-
const newCol = Math.min(prevVisualRows * lineWidth + colInVisualRow, prevLine.length);
|
|
917
|
-
setEditor({ lines: lines2, row: row2 - 1, col: newCol });
|
|
454
|
+
useInput4(
|
|
455
|
+
(input, key) => {
|
|
456
|
+
if (key.escape) {
|
|
457
|
+
onCancel();
|
|
458
|
+
return;
|
|
918
459
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
const lastVisualRow = Math.floor(line.length / lineWidth);
|
|
925
|
-
if (visualRow < lastVisualRow) {
|
|
926
|
-
const colInVisualRow = col2 % lineWidth;
|
|
927
|
-
const newCol = Math.min((visualRow + 1) * lineWidth + colInVisualRow, line.length);
|
|
928
|
-
setEditor({ lines: lines2, row: row2, col: newCol });
|
|
929
|
-
} else if (row2 < lines2.length - 1) {
|
|
930
|
-
const colInVisualRow = col2 % lineWidth;
|
|
931
|
-
const newCol = Math.min(colInVisualRow, lines2[row2 + 1].length);
|
|
932
|
-
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;
|
|
933
465
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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;
|
|
942
488
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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;
|
|
950
506
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
return;
|
|
960
|
-
}
|
|
961
|
-
if (key.ctrl && input === "k") {
|
|
962
|
-
const newLines = lines2.map((l, i) => i === row2 ? l.slice(0, col2) : l);
|
|
963
|
-
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
964
|
-
return;
|
|
965
|
-
}
|
|
966
|
-
if (key.ctrl && input === "u") {
|
|
967
|
-
const newLines = lines2.map((l, i) => i === row2 ? l.slice(col2) : l);
|
|
968
|
-
setEditor({ lines: newLines, row: row2, col: 0 });
|
|
969
|
-
return;
|
|
970
|
-
}
|
|
971
|
-
if (key.backspace) {
|
|
972
|
-
if (col2 > 0) {
|
|
973
|
-
const cur = lines2[row2];
|
|
974
|
-
const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2 - 1) + cur.slice(col2) : l);
|
|
975
|
-
setEditor({ lines: newLines, row: row2, col: col2 - 1 });
|
|
976
|
-
} else if (row2 > 0) {
|
|
977
|
-
const prevLen = lines2[row2 - 1].length;
|
|
978
|
-
const merged = lines2[row2 - 1] + lines2[row2];
|
|
979
|
-
const newLines = [...lines2.slice(0, row2 - 1), merged, ...lines2.slice(row2 + 1)];
|
|
980
|
-
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;
|
|
981
515
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
|
|
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);
|
|
992
534
|
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
535
|
+
return;
|
|
993
536
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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
|
+
);
|
|
1002
589
|
const { lines, row, col } = editor;
|
|
1003
|
-
return /* @__PURE__ */
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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
|
+
] })
|
|
1010
687
|
] }),
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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
|
+
] })
|
|
1031
722
|
] });
|
|
1032
723
|
}
|
|
1033
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
|
+
|
|
1034
767
|
// src/tui/App.tsx
|
|
1035
768
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1036
769
|
function App({ files }) {
|
|
@@ -1064,108 +797,111 @@ function App({ files }) {
|
|
|
1064
797
|
setStatusMsg(msg);
|
|
1065
798
|
setTimeout(() => setStatusMsg(void 0), ms);
|
|
1066
799
|
}
|
|
1067
|
-
useInput6(
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
}
|
|
1072
|
-
if (input === "?") {
|
|
1073
|
-
setMode({ type: "help" });
|
|
1074
|
-
return;
|
|
1075
|
-
}
|
|
1076
|
-
if (key.tab) {
|
|
1077
|
-
setFocus((f) => f === "files" ? "keys" : "files");
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
if (focus !== "keys") return;
|
|
1081
|
-
const k = keys[keyIndex];
|
|
1082
|
-
if (input === "r") {
|
|
1083
|
-
if (!k) return;
|
|
1084
|
-
if (revealed.has(k.key)) {
|
|
1085
|
-
setRevealed((prev) => {
|
|
1086
|
-
const next = new Map(prev);
|
|
1087
|
-
next.delete(k.key);
|
|
1088
|
-
return next;
|
|
1089
|
-
});
|
|
800
|
+
useInput6(
|
|
801
|
+
(input, key) => {
|
|
802
|
+
if (input === "q" || key.escape) {
|
|
803
|
+
exit();
|
|
1090
804
|
return;
|
|
1091
805
|
}
|
|
1092
|
-
if (
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
flash("\u{1F512} Private key not found in environment");
|
|
1096
|
-
return;
|
|
1097
|
-
}
|
|
1098
|
-
setRevealed((prev) => new Map(prev).set(k.key, plain));
|
|
1099
|
-
} else {
|
|
1100
|
-
setRevealed((prev) => new Map(prev).set(k.key, k.value));
|
|
806
|
+
if (input === "?") {
|
|
807
|
+
setMode({ type: "help" });
|
|
808
|
+
return;
|
|
1101
809
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
if (input === "R") {
|
|
1105
|
-
if (revealed.size > 0) {
|
|
1106
|
-
setRevealed(/* @__PURE__ */ new Map());
|
|
810
|
+
if (key.tab) {
|
|
811
|
+
setFocus((f) => f === "files" ? "keys" : "files");
|
|
1107
812
|
return;
|
|
1108
813
|
}
|
|
1109
|
-
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
if (
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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");
|
|
1116
830
|
return;
|
|
1117
831
|
}
|
|
1118
|
-
|
|
832
|
+
setRevealed((prev) => new Map(prev).set(k.key, plain));
|
|
1119
833
|
} else {
|
|
1120
|
-
|
|
834
|
+
setRevealed((prev) => new Map(prev).set(k.key, k.value));
|
|
1121
835
|
}
|
|
836
|
+
return;
|
|
1122
837
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
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);
|
|
1131
858
|
return;
|
|
1132
859
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
if (!k) return;
|
|
1139
|
-
if (k.encrypted) {
|
|
1140
|
-
const plain = decryptValue(k.value, selectedFile.path);
|
|
1141
|
-
if (plain === null) {
|
|
1142
|
-
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");
|
|
1143
865
|
return;
|
|
1144
866
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
867
|
+
clipboard.writeSync(value);
|
|
868
|
+
flash(`Copied ${k.key}`);
|
|
869
|
+
return;
|
|
1148
870
|
}
|
|
1149
|
-
return
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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
|
+
);
|
|
1169
905
|
if (mode.type === "edit") {
|
|
1170
906
|
const editing = mode.key;
|
|
1171
907
|
return /* @__PURE__ */ jsx7(
|
|
@@ -1303,7 +1039,15 @@ function App({ files }) {
|
|
|
1303
1039
|
statusMsg,
|
|
1304
1040
|
focus2: focus,
|
|
1305
1041
|
interactive: false,
|
|
1306
|
-
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
|
+
)
|
|
1307
1051
|
}
|
|
1308
1052
|
);
|
|
1309
1053
|
}
|
|
@@ -1371,7 +1115,9 @@ function App({ files }) {
|
|
|
1371
1115
|
flash(`Encrypted ${selectedFile.relativePath}`);
|
|
1372
1116
|
}
|
|
1373
1117
|
} catch (err) {
|
|
1374
|
-
flash(
|
|
1118
|
+
flash(
|
|
1119
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
1120
|
+
);
|
|
1375
1121
|
}
|
|
1376
1122
|
refreshKeys();
|
|
1377
1123
|
setRevealed(/* @__PURE__ */ new Map());
|
|
@@ -1435,17 +1181,26 @@ function Layout({
|
|
|
1435
1181
|
/* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1436
1182
|
/* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "dotenvx-ui" }),
|
|
1437
1183
|
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1438
|
-
"
|
|
1184
|
+
" ",
|
|
1439
1185
|
selectedFile.relativePath,
|
|
1440
|
-
"
|
|
1186
|
+
" \xB7 ",
|
|
1441
1187
|
files.length,
|
|
1442
|
-
" files
|
|
1188
|
+
" files \xB7 ",
|
|
1443
1189
|
encCount,
|
|
1444
1190
|
" enc"
|
|
1445
1191
|
] })
|
|
1446
1192
|
] }),
|
|
1447
1193
|
/* @__PURE__ */ jsxs7(Box7, { height: listRows, children: [
|
|
1448
|
-
/* @__PURE__ */ jsx7(
|
|
1194
|
+
/* @__PURE__ */ jsx7(
|
|
1195
|
+
FileList,
|
|
1196
|
+
{
|
|
1197
|
+
files,
|
|
1198
|
+
selectedIndex: fileIndex,
|
|
1199
|
+
focused: focus === "files",
|
|
1200
|
+
interactive,
|
|
1201
|
+
onSelect: onSelectFile
|
|
1202
|
+
}
|
|
1203
|
+
),
|
|
1449
1204
|
/* @__PURE__ */ jsx7(
|
|
1450
1205
|
KeyTable,
|
|
1451
1206
|
{
|
|
@@ -1461,11 +1216,26 @@ function Layout({
|
|
|
1461
1216
|
)
|
|
1462
1217
|
] }),
|
|
1463
1218
|
extra,
|
|
1464
|
-
!extra && /* @__PURE__ */ jsx7(
|
|
1219
|
+
!extra && /* @__PURE__ */ jsx7(
|
|
1220
|
+
ValuePreview,
|
|
1221
|
+
{
|
|
1222
|
+
keys,
|
|
1223
|
+
keyIndex,
|
|
1224
|
+
focus,
|
|
1225
|
+
revealed,
|
|
1226
|
+
width: termCols
|
|
1227
|
+
}
|
|
1228
|
+
),
|
|
1465
1229
|
/* @__PURE__ */ jsx7(StatusBar, { focus, message: statusMsg })
|
|
1466
1230
|
] });
|
|
1467
1231
|
}
|
|
1468
|
-
function ValuePreview({
|
|
1232
|
+
function ValuePreview({
|
|
1233
|
+
keys,
|
|
1234
|
+
keyIndex,
|
|
1235
|
+
focus,
|
|
1236
|
+
revealed,
|
|
1237
|
+
width
|
|
1238
|
+
}) {
|
|
1469
1239
|
if (focus !== "keys") return null;
|
|
1470
1240
|
const k = keys[keyIndex];
|
|
1471
1241
|
if (!k) return null;
|
|
@@ -1489,35 +1259,47 @@ function ValuePreview({ keys, keyIndex, focus, revealed, width }) {
|
|
|
1489
1259
|
children: [
|
|
1490
1260
|
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: "cyan", children: [
|
|
1491
1261
|
k.key,
|
|
1492
|
-
"
|
|
1262
|
+
" "
|
|
1493
1263
|
] }),
|
|
1494
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\xB7
|
|
1495
|
-
/* @__PURE__ */ jsx7(
|
|
1264
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\xB7 " }),
|
|
1265
|
+
/* @__PURE__ */ jsx7(Box7, { overflow: "hidden", children: /* @__PURE__ */ jsx7(Text7, { children: flat }) })
|
|
1496
1266
|
]
|
|
1497
1267
|
}
|
|
1498
1268
|
);
|
|
1499
1269
|
}
|
|
1500
1270
|
function ConfirmDelete({ keyName, onConfirm, onCancel }) {
|
|
1501
1271
|
const { isRawModeSupported } = useStdin6();
|
|
1502
|
-
useInput6(
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1272
|
+
useInput6(
|
|
1273
|
+
(input) => {
|
|
1274
|
+
if (input === "y" || input === "Y") onConfirm();
|
|
1275
|
+
else onCancel();
|
|
1276
|
+
},
|
|
1277
|
+
{ isActive: isRawModeSupported }
|
|
1278
|
+
);
|
|
1506
1279
|
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1507
1280
|
/* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
|
|
1508
1281
|
"Delete ",
|
|
1509
1282
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
|
|
1510
|
-
"?
|
|
1283
|
+
"?",
|
|
1284
|
+
" "
|
|
1511
1285
|
] }),
|
|
1512
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y confirm
|
|
1286
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y confirm any other key cancel" })
|
|
1513
1287
|
] });
|
|
1514
1288
|
}
|
|
1515
|
-
function ConfirmEncrypt({
|
|
1289
|
+
function ConfirmEncrypt({
|
|
1290
|
+
decrypt,
|
|
1291
|
+
fileName,
|
|
1292
|
+
onConfirm,
|
|
1293
|
+
onCancel
|
|
1294
|
+
}) {
|
|
1516
1295
|
const { isRawModeSupported } = useStdin6();
|
|
1517
|
-
useInput6(
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1296
|
+
useInput6(
|
|
1297
|
+
(input) => {
|
|
1298
|
+
if (input === "y" || input === "Y") onConfirm();
|
|
1299
|
+
else onCancel();
|
|
1300
|
+
},
|
|
1301
|
+
{ isActive: isRawModeSupported }
|
|
1302
|
+
);
|
|
1521
1303
|
const action = decrypt ? "Decrypt" : "Encrypt";
|
|
1522
1304
|
const color = decrypt ? "yellow" : "green";
|
|
1523
1305
|
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
@@ -1525,42 +1307,52 @@ function ConfirmEncrypt({ decrypt, fileName, onConfirm, onCancel }) {
|
|
|
1525
1307
|
action,
|
|
1526
1308
|
" ",
|
|
1527
1309
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: fileName }),
|
|
1528
|
-
"?
|
|
1310
|
+
"?",
|
|
1311
|
+
" "
|
|
1529
1312
|
] }),
|
|
1530
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y confirm
|
|
1313
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y confirm any other key cancel" })
|
|
1531
1314
|
] });
|
|
1532
1315
|
}
|
|
1533
|
-
function ConfirmAddEncrypt({
|
|
1316
|
+
function ConfirmAddEncrypt({
|
|
1317
|
+
keyName,
|
|
1318
|
+
onEncrypt,
|
|
1319
|
+
onPlain,
|
|
1320
|
+
onCancel
|
|
1321
|
+
}) {
|
|
1534
1322
|
const { isRawModeSupported } = useStdin6();
|
|
1535
|
-
useInput6(
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
+
);
|
|
1549
1340
|
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1550
1341
|
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
1551
1342
|
"Encrypt ",
|
|
1552
1343
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
|
|
1553
|
-
"?
|
|
1344
|
+
"?",
|
|
1345
|
+
" "
|
|
1554
1346
|
] }),
|
|
1555
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y encrypt
|
|
1347
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "y encrypt n plain esc cancel" })
|
|
1556
1348
|
] });
|
|
1557
1349
|
}
|
|
1558
1350
|
|
|
1559
1351
|
// src/tui/ErrorBoundary.tsx
|
|
1560
|
-
import React4 from "react";
|
|
1561
1352
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
1353
|
+
import React from "react";
|
|
1562
1354
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1563
|
-
var ErrorBoundary = class extends
|
|
1355
|
+
var ErrorBoundary = class extends React.Component {
|
|
1564
1356
|
state = { error: null };
|
|
1565
1357
|
static getDerivedStateFromError(error) {
|
|
1566
1358
|
return { error };
|
|
@@ -1578,9 +1370,8 @@ var ErrorBoundary = class extends React4.Component {
|
|
|
1578
1370
|
};
|
|
1579
1371
|
|
|
1580
1372
|
// src/cli.tsx
|
|
1581
|
-
import { createRequire as createRequire2 } from "module";
|
|
1582
1373
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1583
|
-
var { version } =
|
|
1374
|
+
var { version } = createRequire(import.meta.url)("../package.json");
|
|
1584
1375
|
var HELP = `
|
|
1585
1376
|
dotenvx-ui \u2014 terminal and web UI for dotenvx environment files
|
|
1586
1377
|
|
|
@@ -1609,12 +1400,14 @@ var commands = {
|
|
|
1609
1400
|
console.log(HELP);
|
|
1610
1401
|
process.exit(0);
|
|
1611
1402
|
},
|
|
1612
|
-
|
|
1403
|
+
ui: runWebUI
|
|
1613
1404
|
};
|
|
1614
1405
|
var [, , command] = process.argv;
|
|
1615
1406
|
if (command !== void 0 && !(command in commands)) {
|
|
1616
|
-
console.error(
|
|
1617
|
-
|
|
1407
|
+
console.error(
|
|
1408
|
+
`Unknown command: ${command}
|
|
1409
|
+
Run dotenvx-ui --help for usage.`
|
|
1410
|
+
);
|
|
1618
1411
|
process.exit(1);
|
|
1619
1412
|
}
|
|
1620
1413
|
commands[command ?? ""]?.() ?? runTUI();
|
|
@@ -1624,9 +1417,17 @@ function runTUI() {
|
|
|
1624
1417
|
console.error("No .env files found in this directory.");
|
|
1625
1418
|
process.exit(1);
|
|
1626
1419
|
}
|
|
1627
|
-
render(
|
|
1420
|
+
render(
|
|
1421
|
+
/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }),
|
|
1422
|
+
{ alternateScreen: true }
|
|
1423
|
+
);
|
|
1628
1424
|
}
|
|
1629
|
-
function runWebUI() {
|
|
1630
|
-
|
|
1631
|
-
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());
|
|
1632
1433
|
}
|