dotenvx-ui 0.1.0 → 0.1.2
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/cli.js +464 -126
- package/package.json +15 -8
package/dist/cli.js
CHANGED
|
@@ -92,20 +92,20 @@ function scan(cwd) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// src/tui/App.tsx
|
|
95
|
-
import { useState as
|
|
95
|
+
import { useState as useState4 } from "react";
|
|
96
96
|
import { Box as Box7, Text as Text7, useApp, useInput as useInput6, useStdin as useStdin6 } from "ink";
|
|
97
97
|
import clipboard from "clipboardy";
|
|
98
98
|
|
|
99
99
|
// src/tui/FileList.tsx
|
|
100
100
|
import { Box, Text, useInput, useStdin } from "ink";
|
|
101
101
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
102
|
-
function FileList({ files, selectedIndex, focused, onSelect }) {
|
|
102
|
+
function FileList({ files, selectedIndex, focused, interactive, onSelect }) {
|
|
103
103
|
const { isRawModeSupported } = useStdin();
|
|
104
104
|
useInput((_, key) => {
|
|
105
105
|
if (!focused) return;
|
|
106
106
|
if (key.upArrow) onSelect(Math.max(0, selectedIndex - 1));
|
|
107
107
|
if (key.downArrow) onSelect(Math.min(files.length - 1, selectedIndex + 1));
|
|
108
|
-
}, { isActive: isRawModeSupported });
|
|
108
|
+
}, { isActive: isRawModeSupported && interactive });
|
|
109
109
|
const byPkg = Map.groupBy(files, (f) => f.package);
|
|
110
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
111
|
/* @__PURE__ */ jsxs(Text, { bold: true, dimColor: true, children: [
|
|
@@ -132,21 +132,71 @@ function FileList({ files, selectedIndex, focused, onSelect }) {
|
|
|
132
132
|
|
|
133
133
|
// src/tui/KeyTable.tsx
|
|
134
134
|
import { Box as Box2, Text as Text2, useInput as useInput2, useStdin as useStdin2 } from "ink";
|
|
135
|
-
|
|
135
|
+
|
|
136
|
+
// src/tui/useTerminalRows.ts
|
|
137
|
+
import { useEffect, useState } from "react";
|
|
138
|
+
import { useStdout } from "ink";
|
|
139
|
+
function useTerminalRows() {
|
|
140
|
+
const { stdout } = useStdout();
|
|
141
|
+
const [rows, setRows] = useState(stdout?.rows ?? 24);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!stdout) return;
|
|
144
|
+
const onResize = () => setRows(stdout.rows);
|
|
145
|
+
stdout.on("resize", onResize);
|
|
146
|
+
return () => {
|
|
147
|
+
stdout.off("resize", onResize);
|
|
148
|
+
};
|
|
149
|
+
}, [stdout]);
|
|
150
|
+
return rows;
|
|
151
|
+
}
|
|
152
|
+
function useTerminalCols() {
|
|
153
|
+
const { stdout } = useStdout();
|
|
154
|
+
const [cols, setCols] = useState(stdout?.columns ?? 80);
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!stdout) return;
|
|
157
|
+
const onResize = () => setCols(stdout.columns);
|
|
158
|
+
stdout.on("resize", onResize);
|
|
159
|
+
return () => {
|
|
160
|
+
stdout.off("resize", onResize);
|
|
161
|
+
};
|
|
162
|
+
}, [stdout]);
|
|
163
|
+
return cols;
|
|
164
|
+
}
|
|
165
|
+
function scrollWindow(length, selectedIndex, maxVisible) {
|
|
166
|
+
if (length <= maxVisible) return { start: 0, end: length, above: 0, below: 0 };
|
|
167
|
+
const start = Math.min(
|
|
168
|
+
Math.max(0, selectedIndex - Math.floor(maxVisible / 2)),
|
|
169
|
+
length - maxVisible
|
|
170
|
+
);
|
|
171
|
+
const end = start + maxVisible;
|
|
172
|
+
return { start, end, above: start, below: length - end };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/tui/KeyTable.tsx
|
|
176
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
136
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
|
+
}
|
|
137
183
|
function maskValue(k, revealed) {
|
|
138
|
-
if (revealed.has(k.key)) return revealed.get(k.key);
|
|
184
|
+
if (revealed.has(k.key)) return truncate(revealed.get(k.key));
|
|
139
185
|
if (k.encrypted) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
140
186
|
if (SECRET_PATTERN.test(k.key)) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
141
|
-
return k.value
|
|
187
|
+
return truncate(k.value);
|
|
142
188
|
}
|
|
143
|
-
function KeyTable({ file, keys, selectedIndex, focused, revealed, onSelect }) {
|
|
189
|
+
function KeyTable({ file, keys, selectedIndex, focused, interactive, revealed, onSelect, maxRows }) {
|
|
144
190
|
const { isRawModeSupported } = useStdin2();
|
|
145
191
|
useInput2((_, key) => {
|
|
146
192
|
if (!focused) return;
|
|
147
193
|
if (key.upArrow) onSelect(Math.max(0, selectedIndex - 1));
|
|
148
194
|
if (key.downArrow) onSelect(Math.min(keys.length - 1, selectedIndex + 1));
|
|
149
|
-
}, { isActive: isRawModeSupported });
|
|
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;
|
|
150
200
|
const encBadge = file.encrypted ? /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: " encrypted" }) : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " plain" });
|
|
151
201
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 1, children: [
|
|
152
202
|
/* @__PURE__ */ jsxs2(Box2, { paddingX: 1, children: [
|
|
@@ -163,37 +213,72 @@ function KeyTable({ file, keys, selectedIndex, focused, revealed, onSelect }) {
|
|
|
163
213
|
"No keys found. Press ",
|
|
164
214
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: "a" }),
|
|
165
215
|
" to add one."
|
|
166
|
-
] }) }) :
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
] })
|
|
183
246
|
] });
|
|
184
247
|
}
|
|
185
248
|
|
|
186
249
|
// src/tui/StatusBar.tsx
|
|
187
250
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
188
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
189
|
-
var FILE_HINTS =
|
|
190
|
-
|
|
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
|
+
}
|
|
191
276
|
function StatusBar({ message, focus }) {
|
|
192
|
-
return /* @__PURE__ */ jsx3(Box3, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, children: /* @__PURE__ */ jsx3(Text3, {
|
|
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 }) });
|
|
193
278
|
}
|
|
194
279
|
|
|
195
280
|
// src/tui/DiffView.tsx
|
|
196
|
-
import { useState } from "react";
|
|
281
|
+
import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
197
282
|
import { Box as Box4, Text as Text4, useInput as useInput3, useStdin as useStdin3 } from "ink";
|
|
198
283
|
|
|
199
284
|
// src/core/parser/io.ts
|
|
@@ -406,16 +491,48 @@ function decryptValue(encryptedValue, envFilePath) {
|
|
|
406
491
|
const keyName = findKeyForValue(encryptedValue, envFilePath);
|
|
407
492
|
if (!keyName) return null;
|
|
408
493
|
const keysFile = findKeysFile(envFilePath);
|
|
409
|
-
|
|
494
|
+
return silenced(() => {
|
|
410
495
|
const result = dotenvx.get(keyName, {
|
|
411
496
|
path: envFilePath,
|
|
412
497
|
...keysFile ? { envKeysFile: keysFile } : {},
|
|
413
498
|
logLevel: "error"
|
|
414
499
|
});
|
|
415
500
|
return result ?? null;
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
function decryptAllValues(envFilePath) {
|
|
504
|
+
let raw;
|
|
505
|
+
try {
|
|
506
|
+
raw = readFileSync3(envFilePath, "utf8");
|
|
416
507
|
} catch {
|
|
417
|
-
return
|
|
508
|
+
return {};
|
|
418
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
|
+
});
|
|
419
536
|
}
|
|
420
537
|
var DOTENVX_INTERNAL_KEYS = /* @__PURE__ */ new Set(["DOTENV_PUBLIC_KEY", "DOTENV_PRIVATE_KEY"]);
|
|
421
538
|
function encryptFile(envFilePath) {
|
|
@@ -439,13 +556,31 @@ function encryptKey(envFilePath, keyName, plainValue) {
|
|
|
439
556
|
}
|
|
440
557
|
function decryptFile(envFilePath) {
|
|
441
558
|
const keys = readEnvFile(envFilePath);
|
|
559
|
+
const decrypted = decryptAllValues(envFilePath);
|
|
442
560
|
for (const k of keys) {
|
|
443
561
|
if (isEncryptedValue(k.value)) {
|
|
444
|
-
const plain =
|
|
445
|
-
if (plain !==
|
|
562
|
+
const plain = decrypted[k.key];
|
|
563
|
+
if (plain !== void 0 && !isEncryptedValue(plain)) updateKey(envFilePath, k.key, plain);
|
|
446
564
|
}
|
|
447
565
|
}
|
|
448
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
|
+
}
|
|
449
584
|
function findKeysFile(envFilePath) {
|
|
450
585
|
let dir = dirname3(envFilePath);
|
|
451
586
|
while (true) {
|
|
@@ -474,14 +609,16 @@ function findKeyForValue(encryptedValue, envFilePath) {
|
|
|
474
609
|
}
|
|
475
610
|
|
|
476
611
|
// src/tui/DiffView.tsx
|
|
477
|
-
import { jsx as jsx4, jsxs as
|
|
612
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
478
613
|
var COL_VAL = 26;
|
|
614
|
+
var PICKER_MAX = 5;
|
|
479
615
|
function buildDisplayMap(keys, filePath) {
|
|
616
|
+
const decrypted = keys.some((k) => k.encrypted) ? decryptAllValues(filePath) : {};
|
|
480
617
|
const out = /* @__PURE__ */ new Map();
|
|
481
618
|
for (const k of keys) {
|
|
482
619
|
if (k.encrypted) {
|
|
483
|
-
const plain =
|
|
484
|
-
out.set(k.key, plain !==
|
|
620
|
+
const plain = decrypted[k.key];
|
|
621
|
+
out.set(k.key, plain !== void 0 && !isEncryptedValue(plain) ? plain : "\u{1F512}");
|
|
485
622
|
} else {
|
|
486
623
|
out.set(k.key, k.value);
|
|
487
624
|
}
|
|
@@ -519,35 +656,82 @@ function safeRead(file) {
|
|
|
519
656
|
}
|
|
520
657
|
function DiffView({ left, files, onClose }) {
|
|
521
658
|
const { isRawModeSupported } = useStdin3();
|
|
659
|
+
const termRows = useTerminalRows();
|
|
522
660
|
const others = files.filter((f) => f.path !== left.path);
|
|
523
|
-
const [pickerIndex, setPickerIndex] =
|
|
661
|
+
const [pickerIndex, setPickerIndex] = useState2(0);
|
|
662
|
+
const [rowScroll, setRowScroll] = useState2(0);
|
|
524
663
|
const rightFile = others[pickerIndex] ?? null;
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
664
|
+
const mapsCache = useRef(/* @__PURE__ */ new Map());
|
|
665
|
+
const [cacheVersion, setCacheVersion] = useState2(0);
|
|
666
|
+
function getMap(file) {
|
|
667
|
+
return mapsCache.current.get(file.path) ?? null;
|
|
668
|
+
}
|
|
669
|
+
function buildAndCache(file) {
|
|
670
|
+
if (!mapsCache.current.has(file.path)) {
|
|
671
|
+
mapsCache.current.set(file.path, buildDisplayMap(safeRead(file), file.path));
|
|
672
|
+
setCacheVersion((v) => v + 1);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
useEffect2(() => {
|
|
676
|
+
buildAndCache(left);
|
|
677
|
+
if (others[0]) buildAndCache(others[0]);
|
|
678
|
+
}, []);
|
|
679
|
+
useEffect2(() => {
|
|
680
|
+
const next = others[pickerIndex + 1];
|
|
681
|
+
const prev = others[pickerIndex - 1];
|
|
682
|
+
if (next) buildAndCache(next);
|
|
683
|
+
if (prev) buildAndCache(prev);
|
|
684
|
+
}, [pickerIndex]);
|
|
685
|
+
const rows = useMemo(() => {
|
|
686
|
+
if (!rightFile) return [];
|
|
687
|
+
const leftMap = getMap(left);
|
|
688
|
+
const rightMap = getMap(rightFile);
|
|
689
|
+
if (!leftMap || !rightMap) return [];
|
|
690
|
+
return buildRows(leftMap, rightMap);
|
|
691
|
+
}, [left.path, rightFile?.path, cacheVersion]);
|
|
692
|
+
const pickerVisible = Math.min(others.length, PICKER_MAX);
|
|
693
|
+
const picker = scrollWindow(others.length, pickerIndex, PICKER_MAX);
|
|
694
|
+
const chrome = 9 + pickerVisible + (picker.above > 0 || picker.below > 0 ? 2 : 0);
|
|
695
|
+
const maxRows = Math.max(3, termRows - chrome);
|
|
696
|
+
const maxScroll = Math.max(0, rows.length - maxRows);
|
|
697
|
+
const scroll = Math.min(rowScroll, maxScroll);
|
|
698
|
+
const visibleRows = rows.slice(scroll, scroll + maxRows);
|
|
699
|
+
const rowsAbove = scroll;
|
|
700
|
+
const rowsBelow = rows.length - (scroll + visibleRows.length);
|
|
530
701
|
useInput3((input, key) => {
|
|
531
702
|
if (key.escape || input === "q") {
|
|
532
703
|
onClose();
|
|
533
704
|
return;
|
|
534
705
|
}
|
|
535
|
-
if (key.upArrow)
|
|
536
|
-
|
|
706
|
+
if (key.upArrow) {
|
|
707
|
+
setPickerIndex((i) => Math.max(0, i - 1));
|
|
708
|
+
setRowScroll(0);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (key.downArrow) {
|
|
712
|
+
setPickerIndex((i) => Math.min(others.length - 1, i + 1));
|
|
713
|
+
setRowScroll(0);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (input === "k") setRowScroll(Math.max(0, scroll - 1));
|
|
717
|
+
if (input === "j") setRowScroll(Math.min(maxScroll, scroll + 1));
|
|
718
|
+
if (key.pageUp) setRowScroll(Math.max(0, scroll - maxRows));
|
|
719
|
+
if (key.pageDown) setRowScroll(Math.min(maxScroll, scroll + maxRows));
|
|
537
720
|
}, { isActive: isRawModeSupported });
|
|
721
|
+
const keyColWidth = Math.min(48, Math.max(16, ...rows.map((r) => r.key.length))) + 2;
|
|
538
722
|
const leftName = trunc(left.relativePath, COL_VAL);
|
|
539
723
|
const rightName = rightFile ? trunc(rightFile.relativePath, COL_VAL) : "\u2014";
|
|
540
|
-
return /* @__PURE__ */
|
|
541
|
-
/* @__PURE__ */
|
|
724
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
725
|
+
/* @__PURE__ */ jsxs4(Box4, { paddingX: 1, children: [
|
|
542
726
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "dotenvx-ui " }),
|
|
543
|
-
/* @__PURE__ */
|
|
727
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
544
728
|
"diff ",
|
|
545
729
|
left.relativePath,
|
|
546
730
|
" \u2194 ",
|
|
547
731
|
rightFile?.relativePath ?? "\u2014"
|
|
548
732
|
] })
|
|
549
733
|
] }),
|
|
550
|
-
/* @__PURE__ */
|
|
734
|
+
/* @__PURE__ */ jsxs4(
|
|
551
735
|
Box4,
|
|
552
736
|
{
|
|
553
737
|
flexDirection: "column",
|
|
@@ -559,9 +743,14 @@ function DiffView({ left, files, onClose }) {
|
|
|
559
743
|
borderRight: false,
|
|
560
744
|
children: [
|
|
561
745
|
/* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "compare with" }),
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
746
|
+
picker.above > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
747
|
+
" \u2191 ",
|
|
748
|
+
picker.above,
|
|
749
|
+
" more"
|
|
750
|
+
] }),
|
|
751
|
+
others.slice(picker.start, picker.end).map((f, i) => {
|
|
752
|
+
const selected = picker.start + i === pickerIndex;
|
|
753
|
+
return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(
|
|
565
754
|
Text4,
|
|
566
755
|
{
|
|
567
756
|
backgroundColor: selected ? "blue" : void 0,
|
|
@@ -573,34 +762,49 @@ function DiffView({ left, files, onClose }) {
|
|
|
573
762
|
}
|
|
574
763
|
) }, f.path);
|
|
575
764
|
}),
|
|
765
|
+
picker.below > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
766
|
+
" \u2193 ",
|
|
767
|
+
picker.below,
|
|
768
|
+
" more"
|
|
769
|
+
] }),
|
|
576
770
|
others.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " no other files" })
|
|
577
771
|
]
|
|
578
772
|
}
|
|
579
773
|
),
|
|
580
|
-
/* @__PURE__ */
|
|
581
|
-
/* @__PURE__ */
|
|
582
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "KEY".padEnd(
|
|
583
|
-
/* @__PURE__ */
|
|
774
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
|
|
775
|
+
/* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, children: [
|
|
776
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "KEY".padEnd(keyColWidth) }),
|
|
777
|
+
/* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
|
|
584
778
|
" ",
|
|
585
779
|
leftName.padEnd(COL_VAL + 2)
|
|
586
780
|
] }),
|
|
587
781
|
/* @__PURE__ */ jsx4(Text4, { bold: true, children: rightName })
|
|
588
782
|
] }),
|
|
589
|
-
|
|
783
|
+
rowsAbove > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
784
|
+
"\u2191 ",
|
|
785
|
+
rowsAbove,
|
|
786
|
+
" more"
|
|
787
|
+
] }),
|
|
788
|
+
visibleRows.map((row) => /* @__PURE__ */ jsx4(DiffRow, { row, keyColWidth }, row.key)),
|
|
789
|
+
rowsBelow > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
790
|
+
"\u2193 ",
|
|
791
|
+
rowsBelow,
|
|
792
|
+
" more"
|
|
793
|
+
] }),
|
|
590
794
|
rows.length === 0 && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Select a file to compare." }) })
|
|
591
795
|
] }),
|
|
592
|
-
/* @__PURE__ */
|
|
593
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 pick file esc close " }),
|
|
796
|
+
/* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, children: [
|
|
797
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 pick file j/k scroll esc close " }),
|
|
594
798
|
/* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u25CF same" })
|
|
595
799
|
] })
|
|
596
800
|
] });
|
|
597
801
|
}
|
|
598
|
-
function DiffRow({ row }) {
|
|
802
|
+
function DiffRow({ row, keyColWidth }) {
|
|
599
803
|
const { key, leftDisplay, rightDisplay, status } = row;
|
|
600
804
|
const color = status === "same" ? "green" : void 0;
|
|
601
|
-
return /* @__PURE__ */
|
|
602
|
-
/* @__PURE__ */ jsx4(Text4, { color, children: key.padEnd(
|
|
603
|
-
/* @__PURE__ */
|
|
805
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
806
|
+
/* @__PURE__ */ jsx4(Text4, { color, children: key.padEnd(keyColWidth) }),
|
|
807
|
+
/* @__PURE__ */ jsxs4(Text4, { color, children: [
|
|
604
808
|
" ",
|
|
605
809
|
(leftDisplay || "\u2014").padEnd(COL_VAL + 2)
|
|
606
810
|
] }),
|
|
@@ -610,7 +814,7 @@ function DiffRow({ row }) {
|
|
|
610
814
|
|
|
611
815
|
// src/tui/HelpOverlay.tsx
|
|
612
816
|
import { Box as Box5, Text as Text5, useInput as useInput4, useStdin as useStdin4 } from "ink";
|
|
613
|
-
import { jsx as jsx5, jsxs as
|
|
817
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
614
818
|
var SECTIONS = [
|
|
615
819
|
{
|
|
616
820
|
title: "Navigation",
|
|
@@ -652,14 +856,14 @@ function HelpOverlay({ onClose }) {
|
|
|
652
856
|
useInput4((input, key) => {
|
|
653
857
|
if (input === "?" || input === "q" || key.escape) onClose();
|
|
654
858
|
}, { isActive: isRawModeSupported });
|
|
655
|
-
return /* @__PURE__ */
|
|
656
|
-
/* @__PURE__ */
|
|
859
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
860
|
+
/* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, children: [
|
|
657
861
|
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "dotenvx-ui " }),
|
|
658
862
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "keyboard shortcuts" })
|
|
659
863
|
] }),
|
|
660
|
-
SECTIONS.map((section) => /* @__PURE__ */
|
|
864
|
+
SECTIONS.map((section) => /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
661
865
|
/* @__PURE__ */ jsx5(Text5, { bold: true, children: section.title }),
|
|
662
|
-
section.rows.map(([key, desc]) => /* @__PURE__ */
|
|
866
|
+
section.rows.map(([key, desc]) => /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
663
867
|
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: key.padEnd(KEY_WIDTH) }),
|
|
664
868
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: desc })
|
|
665
869
|
] }, key))
|
|
@@ -669,89 +873,177 @@ function HelpOverlay({ onClose }) {
|
|
|
669
873
|
}
|
|
670
874
|
|
|
671
875
|
// src/tui/InlineForm.tsx
|
|
672
|
-
import { useState as
|
|
673
|
-
import { Box as Box6, Text as Text6, useInput as useInput5, useStdin as useStdin5 } from "ink";
|
|
674
|
-
import { jsx as jsx6, jsxs as
|
|
876
|
+
import { useState as useState3, useRef as useRef2 } from "react";
|
|
877
|
+
import { Box as Box6, Text as Text6, useInput as useInput5, useStdin as useStdin5, useStdout as useStdout2 } from "ink";
|
|
878
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
879
|
+
function makeInitialState(value) {
|
|
880
|
+
const lines = value.split("\n");
|
|
881
|
+
return {
|
|
882
|
+
lines,
|
|
883
|
+
row: lines.length - 1,
|
|
884
|
+
col: lines[lines.length - 1].length
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
var CHROME = 5;
|
|
675
888
|
function InlineForm({ label, initialValue = "", onSubmit, onCancel }) {
|
|
676
889
|
const { isRawModeSupported } = useStdin5();
|
|
677
|
-
const
|
|
678
|
-
const [
|
|
890
|
+
const { stdout } = useStdout2();
|
|
891
|
+
const [editor, setEditor] = useState3(() => makeInitialState(initialValue));
|
|
892
|
+
const editorRef = useRef2(editor);
|
|
893
|
+
editorRef.current = editor;
|
|
679
894
|
useInput5((input, key) => {
|
|
680
895
|
if (key.escape) {
|
|
681
896
|
onCancel();
|
|
682
897
|
return;
|
|
683
898
|
}
|
|
899
|
+
const { lines: lines2, row: row2, col: col2 } = editorRef.current;
|
|
900
|
+
const lineWidth = Math.max(1, (stdout?.columns ?? 80) - CHROME);
|
|
684
901
|
if (key.return) {
|
|
685
|
-
onSubmit(
|
|
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 });
|
|
919
|
+
}
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
if (key.downArrow) {
|
|
923
|
+
const line = lines2[row2];
|
|
924
|
+
const visualRow = Math.floor(col2 / lineWidth);
|
|
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 });
|
|
934
|
+
}
|
|
686
935
|
return;
|
|
687
936
|
}
|
|
688
937
|
if (key.leftArrow) {
|
|
689
|
-
|
|
938
|
+
if (col2 > 0) {
|
|
939
|
+
setEditor({ lines: lines2, row: row2, col: col2 - 1 });
|
|
940
|
+
} else if (row2 > 0) {
|
|
941
|
+
const newRow = row2 - 1;
|
|
942
|
+
setEditor({ lines: lines2, row: newRow, col: lines2[newRow].length });
|
|
943
|
+
}
|
|
690
944
|
return;
|
|
691
945
|
}
|
|
692
946
|
if (key.rightArrow) {
|
|
693
|
-
|
|
947
|
+
if (col2 < lines2[row2].length) {
|
|
948
|
+
setEditor({ lines: lines2, row: row2, col: col2 + 1 });
|
|
949
|
+
} else if (row2 < lines2.length - 1) {
|
|
950
|
+
setEditor({ lines: lines2, row: row2 + 1, col: 0 });
|
|
951
|
+
}
|
|
694
952
|
return;
|
|
695
953
|
}
|
|
696
954
|
if (key.ctrl && input === "a" || key.home) {
|
|
697
|
-
|
|
955
|
+
setEditor({ lines: lines2, row: row2, col: 0 });
|
|
698
956
|
return;
|
|
699
957
|
}
|
|
700
958
|
if (key.ctrl && input === "e" || key.end) {
|
|
701
|
-
|
|
959
|
+
setEditor({ lines: lines2, row: row2, col: lines2[row2].length });
|
|
702
960
|
return;
|
|
703
961
|
}
|
|
704
962
|
if (key.ctrl && input === "k") {
|
|
705
|
-
|
|
963
|
+
const newLines = lines2.map((l, i) => i === row2 ? l.slice(0, col2) : l);
|
|
964
|
+
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
706
965
|
return;
|
|
707
966
|
}
|
|
708
967
|
if (key.ctrl && input === "u") {
|
|
709
|
-
|
|
710
|
-
|
|
968
|
+
const newLines = lines2.map((l, i) => i === row2 ? l.slice(col2) : l);
|
|
969
|
+
setEditor({ lines: newLines, row: row2, col: 0 });
|
|
711
970
|
return;
|
|
712
971
|
}
|
|
713
972
|
if (key.backspace) {
|
|
714
|
-
if (
|
|
715
|
-
|
|
716
|
-
|
|
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 });
|
|
982
|
+
}
|
|
717
983
|
return;
|
|
718
984
|
}
|
|
719
985
|
if (key.delete) {
|
|
720
|
-
|
|
986
|
+
const cur = lines2[row2];
|
|
987
|
+
if (col2 < cur.length) {
|
|
988
|
+
const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2) + cur.slice(col2 + 1) : l);
|
|
989
|
+
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
990
|
+
} else if (row2 < lines2.length - 1) {
|
|
991
|
+
const merged = cur + lines2[row2 + 1];
|
|
992
|
+
const newLines = [...lines2.slice(0, row2), merged, ...lines2.slice(row2 + 2)];
|
|
993
|
+
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
994
|
+
}
|
|
721
995
|
return;
|
|
722
996
|
}
|
|
723
997
|
if (input && !key.ctrl && !key.meta) {
|
|
724
|
-
|
|
725
|
-
|
|
998
|
+
const cur = lines2[row2];
|
|
999
|
+
const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2) + input + cur.slice(col2) : l);
|
|
1000
|
+
setEditor({ lines: newLines, row: row2, col: col2 + input.length });
|
|
726
1001
|
}
|
|
727
1002
|
}, { isActive: isRawModeSupported });
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1003
|
+
const { lines, row, col } = editor;
|
|
1004
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, paddingTop: 0, children: [
|
|
1005
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1006
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, color: "cyan", children: [
|
|
1007
|
+
label,
|
|
1008
|
+
" "
|
|
1009
|
+
] }),
|
|
1010
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u21B5 confirm esc cancel" })
|
|
735
1011
|
] }),
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1012
|
+
lines.map((line, r) => {
|
|
1013
|
+
const isActive = r === row;
|
|
1014
|
+
if (!isActive) {
|
|
1015
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1016
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
|
|
1017
|
+
/* @__PURE__ */ jsx6(Text6, { children: line || " " })
|
|
1018
|
+
] }, r);
|
|
1019
|
+
}
|
|
1020
|
+
const before = line.slice(0, col);
|
|
1021
|
+
const cursor = line[col] ?? " ";
|
|
1022
|
+
const after = line.slice(col + 1);
|
|
1023
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1024
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
|
|
1025
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1026
|
+
before,
|
|
1027
|
+
/* @__PURE__ */ jsx6(Text6, { inverse: true, children: cursor }),
|
|
1028
|
+
after
|
|
1029
|
+
] })
|
|
1030
|
+
] }, r);
|
|
1031
|
+
})
|
|
1032
|
+
] });
|
|
741
1033
|
}
|
|
742
1034
|
|
|
743
1035
|
// src/tui/App.tsx
|
|
744
|
-
import { jsx as jsx7, jsxs as
|
|
1036
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
745
1037
|
function App({ files }) {
|
|
746
1038
|
const { exit } = useApp();
|
|
747
1039
|
const { isRawModeSupported } = useStdin6();
|
|
748
|
-
const [fileIndex, setFileIndex] =
|
|
749
|
-
const [keyIndex, setKeyIndex] =
|
|
750
|
-
const [focus, setFocus] =
|
|
751
|
-
const [revealed, setRevealed] =
|
|
752
|
-
const [mode, setMode] =
|
|
753
|
-
const [statusMsg, setStatusMsg] =
|
|
754
|
-
const [keys, setKeys] =
|
|
1040
|
+
const [fileIndex, setFileIndex] = useState4(0);
|
|
1041
|
+
const [keyIndex, setKeyIndex] = useState4(0);
|
|
1042
|
+
const [focus, setFocus] = useState4("files");
|
|
1043
|
+
const [revealed, setRevealed] = useState4(/* @__PURE__ */ new Map());
|
|
1044
|
+
const [mode, setMode] = useState4({ type: "normal" });
|
|
1045
|
+
const [statusMsg, setStatusMsg] = useState4();
|
|
1046
|
+
const [keys, setKeys] = useState4(() => loadKeys(files[0]));
|
|
755
1047
|
const selectedFile = files[fileIndex];
|
|
756
1048
|
function loadKeys(file) {
|
|
757
1049
|
try {
|
|
@@ -774,7 +1066,6 @@ function App({ files }) {
|
|
|
774
1066
|
setTimeout(() => setStatusMsg(void 0), ms);
|
|
775
1067
|
}
|
|
776
1068
|
useInput6((input, key) => {
|
|
777
|
-
if (mode.type !== "normal") return;
|
|
778
1069
|
if (input === "q" || key.escape) {
|
|
779
1070
|
exit();
|
|
780
1071
|
return;
|
|
@@ -816,11 +1107,12 @@ function App({ files }) {
|
|
|
816
1107
|
setRevealed(/* @__PURE__ */ new Map());
|
|
817
1108
|
return;
|
|
818
1109
|
}
|
|
1110
|
+
const decrypted = keys.some((e) => e.encrypted) ? decryptAllValues(selectedFile.path) : {};
|
|
819
1111
|
const next = /* @__PURE__ */ new Map();
|
|
820
1112
|
for (const entry of keys) {
|
|
821
1113
|
if (entry.encrypted) {
|
|
822
|
-
const plain =
|
|
823
|
-
if (plain ===
|
|
1114
|
+
const plain = decrypted[entry.key];
|
|
1115
|
+
if (plain === void 0 || isEncryptedValue(plain)) {
|
|
824
1116
|
flash("\u{1F512} Private key not found \u2014 cannot reveal all");
|
|
825
1117
|
return;
|
|
826
1118
|
}
|
|
@@ -890,6 +1182,7 @@ function App({ files }) {
|
|
|
890
1182
|
onSelectKey: setKeyIndex,
|
|
891
1183
|
statusMsg,
|
|
892
1184
|
focus2: focus,
|
|
1185
|
+
interactive: false,
|
|
893
1186
|
extra: /* @__PURE__ */ jsx7(
|
|
894
1187
|
InlineForm,
|
|
895
1188
|
{
|
|
@@ -930,6 +1223,7 @@ function App({ files }) {
|
|
|
930
1223
|
onSelectKey: setKeyIndex,
|
|
931
1224
|
statusMsg,
|
|
932
1225
|
focus2: focus,
|
|
1226
|
+
interactive: false,
|
|
933
1227
|
extra: /* @__PURE__ */ jsx7(
|
|
934
1228
|
InlineForm,
|
|
935
1229
|
{
|
|
@@ -963,6 +1257,7 @@ function App({ files }) {
|
|
|
963
1257
|
onSelectKey: setKeyIndex,
|
|
964
1258
|
statusMsg,
|
|
965
1259
|
focus2: focus,
|
|
1260
|
+
interactive: false,
|
|
966
1261
|
extra: /* @__PURE__ */ jsx7(
|
|
967
1262
|
InlineForm,
|
|
968
1263
|
{
|
|
@@ -1008,6 +1303,7 @@ function App({ files }) {
|
|
|
1008
1303
|
onSelectKey: setKeyIndex,
|
|
1009
1304
|
statusMsg,
|
|
1010
1305
|
focus2: focus,
|
|
1306
|
+
interactive: false,
|
|
1011
1307
|
extra: /* @__PURE__ */ jsx7(ConfirmAddEncrypt, { keyName, onEncrypt: () => commit(true), onPlain: () => commit(false), onCancel: () => setMode({ type: "normal" }) })
|
|
1012
1308
|
}
|
|
1013
1309
|
);
|
|
@@ -1027,6 +1323,7 @@ function App({ files }) {
|
|
|
1027
1323
|
onSelectKey: setKeyIndex,
|
|
1028
1324
|
statusMsg,
|
|
1029
1325
|
focus2: focus,
|
|
1326
|
+
interactive: false,
|
|
1030
1327
|
extra: /* @__PURE__ */ jsx7(
|
|
1031
1328
|
ConfirmDelete,
|
|
1032
1329
|
{
|
|
@@ -1059,6 +1356,7 @@ function App({ files }) {
|
|
|
1059
1356
|
onSelectKey: setKeyIndex,
|
|
1060
1357
|
statusMsg,
|
|
1061
1358
|
focus2: focus,
|
|
1359
|
+
interactive: false,
|
|
1062
1360
|
extra: /* @__PURE__ */ jsx7(
|
|
1063
1361
|
ConfirmEncrypt,
|
|
1064
1362
|
{
|
|
@@ -1111,7 +1409,8 @@ function App({ files }) {
|
|
|
1111
1409
|
onSelectFile: selectFile,
|
|
1112
1410
|
onSelectKey: setKeyIndex,
|
|
1113
1411
|
statusMsg,
|
|
1114
|
-
focus2: focus
|
|
1412
|
+
focus2: focus,
|
|
1413
|
+
interactive: true
|
|
1115
1414
|
}
|
|
1116
1415
|
);
|
|
1117
1416
|
}
|
|
@@ -1121,6 +1420,7 @@ function Layout({
|
|
|
1121
1420
|
keys,
|
|
1122
1421
|
keyIndex,
|
|
1123
1422
|
focus,
|
|
1423
|
+
interactive,
|
|
1124
1424
|
revealed,
|
|
1125
1425
|
onSelectFile,
|
|
1126
1426
|
onSelectKey,
|
|
@@ -1129,10 +1429,13 @@ function Layout({
|
|
|
1129
1429
|
}) {
|
|
1130
1430
|
const selectedFile = files[fileIndex];
|
|
1131
1431
|
const encCount = files.filter((f) => f.encrypted).length;
|
|
1132
|
-
|
|
1133
|
-
|
|
1432
|
+
const termRows = useTerminalRows();
|
|
1433
|
+
const termCols = useTerminalCols();
|
|
1434
|
+
const listRows = Math.max(3, termRows - (extra ? 3 : 5));
|
|
1435
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", height: termRows, children: [
|
|
1436
|
+
/* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1134
1437
|
/* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "dotenvx-ui" }),
|
|
1135
|
-
/* @__PURE__ */
|
|
1438
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1136
1439
|
" ",
|
|
1137
1440
|
selectedFile.relativePath,
|
|
1138
1441
|
" \xB7 ",
|
|
@@ -1142,8 +1445,8 @@ function Layout({
|
|
|
1142
1445
|
" enc"
|
|
1143
1446
|
] })
|
|
1144
1447
|
] }),
|
|
1145
|
-
/* @__PURE__ */
|
|
1146
|
-
/* @__PURE__ */ jsx7(FileList, { files, selectedIndex: fileIndex, focused: focus === "files", onSelect: onSelectFile }),
|
|
1448
|
+
/* @__PURE__ */ jsxs7(Box7, { height: listRows, children: [
|
|
1449
|
+
/* @__PURE__ */ jsx7(FileList, { files, selectedIndex: fileIndex, focused: focus === "files", interactive, onSelect: onSelectFile }),
|
|
1147
1450
|
/* @__PURE__ */ jsx7(
|
|
1148
1451
|
KeyTable,
|
|
1149
1452
|
{
|
|
@@ -1151,23 +1454,58 @@ function Layout({
|
|
|
1151
1454
|
keys,
|
|
1152
1455
|
selectedIndex: keyIndex,
|
|
1153
1456
|
focused: focus === "keys",
|
|
1457
|
+
interactive,
|
|
1154
1458
|
revealed,
|
|
1155
|
-
onSelect: onSelectKey
|
|
1459
|
+
onSelect: onSelectKey,
|
|
1460
|
+
maxRows: listRows
|
|
1156
1461
|
}
|
|
1157
1462
|
)
|
|
1158
1463
|
] }),
|
|
1159
1464
|
extra,
|
|
1465
|
+
!extra && /* @__PURE__ */ jsx7(ValuePreview, { keys, keyIndex, focus, revealed, width: termCols }),
|
|
1160
1466
|
/* @__PURE__ */ jsx7(StatusBar, { focus, message: statusMsg })
|
|
1161
1467
|
] });
|
|
1162
1468
|
}
|
|
1469
|
+
function ValuePreview({ keys, keyIndex, focus, revealed, width }) {
|
|
1470
|
+
if (focus !== "keys") return null;
|
|
1471
|
+
const k = keys[keyIndex];
|
|
1472
|
+
if (!k) return null;
|
|
1473
|
+
let value;
|
|
1474
|
+
if (k.encrypted) {
|
|
1475
|
+
value = revealed.has(k.key) ? revealed.get(k.key) : "\u2022\u2022\u2022\u2022 (press r to reveal)";
|
|
1476
|
+
} else {
|
|
1477
|
+
value = revealed.has(k.key) ? revealed.get(k.key) : k.value;
|
|
1478
|
+
}
|
|
1479
|
+
const flat = value.replace(/\n/g, "\u21B5 ");
|
|
1480
|
+
return /* @__PURE__ */ jsxs7(
|
|
1481
|
+
Box7,
|
|
1482
|
+
{
|
|
1483
|
+
borderStyle: "single",
|
|
1484
|
+
borderTop: true,
|
|
1485
|
+
borderBottom: false,
|
|
1486
|
+
borderLeft: false,
|
|
1487
|
+
borderRight: false,
|
|
1488
|
+
paddingX: 1,
|
|
1489
|
+
width,
|
|
1490
|
+
children: [
|
|
1491
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: "cyan", children: [
|
|
1492
|
+
k.key,
|
|
1493
|
+
" "
|
|
1494
|
+
] }),
|
|
1495
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\xB7 " }),
|
|
1496
|
+
/* @__PURE__ */ jsx7(Text7, { truncate: true, children: flat })
|
|
1497
|
+
]
|
|
1498
|
+
}
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1163
1501
|
function ConfirmDelete({ keyName, onConfirm, onCancel }) {
|
|
1164
1502
|
const { isRawModeSupported } = useStdin6();
|
|
1165
1503
|
useInput6((input) => {
|
|
1166
1504
|
if (input === "y" || input === "Y") onConfirm();
|
|
1167
1505
|
else onCancel();
|
|
1168
1506
|
}, { isActive: isRawModeSupported });
|
|
1169
|
-
return /* @__PURE__ */
|
|
1170
|
-
/* @__PURE__ */
|
|
1507
|
+
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1508
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
|
|
1171
1509
|
"Delete ",
|
|
1172
1510
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
|
|
1173
1511
|
"? "
|
|
@@ -1183,8 +1521,8 @@ function ConfirmEncrypt({ decrypt, fileName, onConfirm, onCancel }) {
|
|
|
1183
1521
|
}, { isActive: isRawModeSupported });
|
|
1184
1522
|
const action = decrypt ? "Decrypt" : "Encrypt";
|
|
1185
1523
|
const color = decrypt ? "yellow" : "green";
|
|
1186
|
-
return /* @__PURE__ */
|
|
1187
|
-
/* @__PURE__ */
|
|
1524
|
+
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1525
|
+
/* @__PURE__ */ jsxs7(Text7, { color, children: [
|
|
1188
1526
|
action,
|
|
1189
1527
|
" ",
|
|
1190
1528
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: fileName }),
|
|
@@ -1209,8 +1547,8 @@ function ConfirmAddEncrypt({ keyName, onEncrypt, onPlain, onCancel }) {
|
|
|
1209
1547
|
return;
|
|
1210
1548
|
}
|
|
1211
1549
|
}, { isActive: isRawModeSupported });
|
|
1212
|
-
return /* @__PURE__ */
|
|
1213
|
-
/* @__PURE__ */
|
|
1550
|
+
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1551
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
1214
1552
|
"Encrypt ",
|
|
1215
1553
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
|
|
1216
1554
|
"? "
|
|
@@ -1222,7 +1560,7 @@ function ConfirmAddEncrypt({ keyName, onEncrypt, onPlain, onCancel }) {
|
|
|
1222
1560
|
// src/tui/ErrorBoundary.tsx
|
|
1223
1561
|
import React4 from "react";
|
|
1224
1562
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
1225
|
-
import { jsx as jsx8, jsxs as
|
|
1563
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1226
1564
|
var ErrorBoundary = class extends React4.Component {
|
|
1227
1565
|
state = { error: null };
|
|
1228
1566
|
static getDerivedStateFromError(error) {
|
|
@@ -1230,7 +1568,7 @@ var ErrorBoundary = class extends React4.Component {
|
|
|
1230
1568
|
}
|
|
1231
1569
|
render() {
|
|
1232
1570
|
if (this.state.error) {
|
|
1233
|
-
return /* @__PURE__ */
|
|
1571
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
1234
1572
|
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "red", children: "dotenvx-ui crashed" }),
|
|
1235
1573
|
/* @__PURE__ */ jsx8(Text8, { children: this.state.error.message }),
|
|
1236
1574
|
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Please report this at https://github.com/alxbrla/dotenvx-ui/issues" }) })
|
|
@@ -1287,7 +1625,7 @@ function runTUI() {
|
|
|
1287
1625
|
console.error("No .env files found in this directory.");
|
|
1288
1626
|
process.exit(1);
|
|
1289
1627
|
}
|
|
1290
|
-
render(/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }));
|
|
1628
|
+
render(/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }), { alternateScreen: true });
|
|
1291
1629
|
}
|
|
1292
1630
|
function runWebUI() {
|
|
1293
1631
|
console.log("Web UI \u2014 coming soon");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dotenvx-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Terminal and web UI for managing dotenvx environment files",
|
|
5
5
|
"author": "Alexandru Burla <alexandru.burla@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"test": "pnpm build && tsc --noEmit && node --import tsx/esm --test 'src/test/**/*.test.ts'",
|
|
26
|
+
"prepublishOnly": "pnpm build"
|
|
27
|
+
},
|
|
21
28
|
"keywords": [
|
|
22
29
|
"dotenvx",
|
|
23
30
|
"env",
|
|
@@ -25,6 +32,7 @@
|
|
|
25
32
|
"cli",
|
|
26
33
|
"dotenv"
|
|
27
34
|
],
|
|
35
|
+
"packageManager": "pnpm@10.30.0",
|
|
28
36
|
"engines": {
|
|
29
37
|
"node": ">=22"
|
|
30
38
|
},
|
|
@@ -35,6 +43,11 @@
|
|
|
35
43
|
"ink": "^7.0.5",
|
|
36
44
|
"react": "^19.2.7"
|
|
37
45
|
},
|
|
46
|
+
"pnpm": {
|
|
47
|
+
"onlyBuiltDependencies": [
|
|
48
|
+
"esbuild"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
38
51
|
"devDependencies": {
|
|
39
52
|
"@types/node": "^25.9.3",
|
|
40
53
|
"@types/react": "^19.2.17",
|
|
@@ -42,11 +55,5 @@
|
|
|
42
55
|
"tsup": "^8.5.1",
|
|
43
56
|
"tsx": "^4.22.4",
|
|
44
57
|
"typescript": "^6.0.3"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "tsup",
|
|
48
|
-
"dev": "tsup --watch",
|
|
49
|
-
"typecheck": "tsc --noEmit",
|
|
50
|
-
"test": "pnpm build && tsc --noEmit && node --import tsx/esm --test 'src/test/**/*.test.ts'"
|
|
51
58
|
}
|
|
52
|
-
}
|
|
59
|
+
}
|