dotenvx-ui 0.1.0 → 0.1.1
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 +460 -123
- 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,81 @@ 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 });
|
|
538
721
|
const leftName = trunc(left.relativePath, COL_VAL);
|
|
539
722
|
const rightName = rightFile ? trunc(rightFile.relativePath, COL_VAL) : "\u2014";
|
|
540
|
-
return /* @__PURE__ */
|
|
541
|
-
/* @__PURE__ */
|
|
723
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
724
|
+
/* @__PURE__ */ jsxs4(Box4, { paddingX: 1, children: [
|
|
542
725
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "dotenvx-ui " }),
|
|
543
|
-
/* @__PURE__ */
|
|
726
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
544
727
|
"diff ",
|
|
545
728
|
left.relativePath,
|
|
546
729
|
" \u2194 ",
|
|
547
730
|
rightFile?.relativePath ?? "\u2014"
|
|
548
731
|
] })
|
|
549
732
|
] }),
|
|
550
|
-
/* @__PURE__ */
|
|
733
|
+
/* @__PURE__ */ jsxs4(
|
|
551
734
|
Box4,
|
|
552
735
|
{
|
|
553
736
|
flexDirection: "column",
|
|
@@ -559,9 +742,14 @@ function DiffView({ left, files, onClose }) {
|
|
|
559
742
|
borderRight: false,
|
|
560
743
|
children: [
|
|
561
744
|
/* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "compare with" }),
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
745
|
+
picker.above > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
746
|
+
" \u2191 ",
|
|
747
|
+
picker.above,
|
|
748
|
+
" more"
|
|
749
|
+
] }),
|
|
750
|
+
others.slice(picker.start, picker.end).map((f, i) => {
|
|
751
|
+
const selected = picker.start + i === pickerIndex;
|
|
752
|
+
return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(
|
|
565
753
|
Text4,
|
|
566
754
|
{
|
|
567
755
|
backgroundColor: selected ? "blue" : void 0,
|
|
@@ -573,24 +761,39 @@ function DiffView({ left, files, onClose }) {
|
|
|
573
761
|
}
|
|
574
762
|
) }, f.path);
|
|
575
763
|
}),
|
|
764
|
+
picker.below > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
765
|
+
" \u2193 ",
|
|
766
|
+
picker.below,
|
|
767
|
+
" more"
|
|
768
|
+
] }),
|
|
576
769
|
others.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " no other files" })
|
|
577
770
|
]
|
|
578
771
|
}
|
|
579
772
|
),
|
|
580
|
-
/* @__PURE__ */
|
|
581
|
-
/* @__PURE__ */
|
|
773
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
|
|
774
|
+
/* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, children: [
|
|
582
775
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "KEY".padEnd(22) }),
|
|
583
|
-
/* @__PURE__ */
|
|
776
|
+
/* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
|
|
584
777
|
" ",
|
|
585
778
|
leftName.padEnd(COL_VAL + 2)
|
|
586
779
|
] }),
|
|
587
780
|
/* @__PURE__ */ jsx4(Text4, { bold: true, children: rightName })
|
|
588
781
|
] }),
|
|
589
|
-
|
|
782
|
+
rowsAbove > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
783
|
+
"\u2191 ",
|
|
784
|
+
rowsAbove,
|
|
785
|
+
" more"
|
|
786
|
+
] }),
|
|
787
|
+
visibleRows.map((row) => /* @__PURE__ */ jsx4(DiffRow, { row }, row.key)),
|
|
788
|
+
rowsBelow > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
789
|
+
"\u2193 ",
|
|
790
|
+
rowsBelow,
|
|
791
|
+
" more"
|
|
792
|
+
] }),
|
|
590
793
|
rows.length === 0 && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Select a file to compare." }) })
|
|
591
794
|
] }),
|
|
592
|
-
/* @__PURE__ */
|
|
593
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 pick file esc close " }),
|
|
795
|
+
/* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, children: [
|
|
796
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 pick file j/k scroll esc close " }),
|
|
594
797
|
/* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u25CF same" })
|
|
595
798
|
] })
|
|
596
799
|
] });
|
|
@@ -598,9 +801,9 @@ function DiffView({ left, files, onClose }) {
|
|
|
598
801
|
function DiffRow({ row }) {
|
|
599
802
|
const { key, leftDisplay, rightDisplay, status } = row;
|
|
600
803
|
const color = status === "same" ? "green" : void 0;
|
|
601
|
-
return /* @__PURE__ */
|
|
804
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
602
805
|
/* @__PURE__ */ jsx4(Text4, { color, children: key.padEnd(22) }),
|
|
603
|
-
/* @__PURE__ */
|
|
806
|
+
/* @__PURE__ */ jsxs4(Text4, { color, children: [
|
|
604
807
|
" ",
|
|
605
808
|
(leftDisplay || "\u2014").padEnd(COL_VAL + 2)
|
|
606
809
|
] }),
|
|
@@ -610,7 +813,7 @@ function DiffRow({ row }) {
|
|
|
610
813
|
|
|
611
814
|
// src/tui/HelpOverlay.tsx
|
|
612
815
|
import { Box as Box5, Text as Text5, useInput as useInput4, useStdin as useStdin4 } from "ink";
|
|
613
|
-
import { jsx as jsx5, jsxs as
|
|
816
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
614
817
|
var SECTIONS = [
|
|
615
818
|
{
|
|
616
819
|
title: "Navigation",
|
|
@@ -652,14 +855,14 @@ function HelpOverlay({ onClose }) {
|
|
|
652
855
|
useInput4((input, key) => {
|
|
653
856
|
if (input === "?" || input === "q" || key.escape) onClose();
|
|
654
857
|
}, { isActive: isRawModeSupported });
|
|
655
|
-
return /* @__PURE__ */
|
|
656
|
-
/* @__PURE__ */
|
|
858
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
859
|
+
/* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, children: [
|
|
657
860
|
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "dotenvx-ui " }),
|
|
658
861
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "keyboard shortcuts" })
|
|
659
862
|
] }),
|
|
660
|
-
SECTIONS.map((section) => /* @__PURE__ */
|
|
863
|
+
SECTIONS.map((section) => /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
|
|
661
864
|
/* @__PURE__ */ jsx5(Text5, { bold: true, children: section.title }),
|
|
662
|
-
section.rows.map(([key, desc]) => /* @__PURE__ */
|
|
865
|
+
section.rows.map(([key, desc]) => /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
663
866
|
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: key.padEnd(KEY_WIDTH) }),
|
|
664
867
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: desc })
|
|
665
868
|
] }, key))
|
|
@@ -669,89 +872,177 @@ function HelpOverlay({ onClose }) {
|
|
|
669
872
|
}
|
|
670
873
|
|
|
671
874
|
// 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
|
|
875
|
+
import { useState as useState3, useRef as useRef2 } from "react";
|
|
876
|
+
import { Box as Box6, Text as Text6, useInput as useInput5, useStdin as useStdin5, useStdout as useStdout2 } from "ink";
|
|
877
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
878
|
+
function makeInitialState(value) {
|
|
879
|
+
const lines = value.split("\n");
|
|
880
|
+
return {
|
|
881
|
+
lines,
|
|
882
|
+
row: lines.length - 1,
|
|
883
|
+
col: lines[lines.length - 1].length
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
var CHROME = 5;
|
|
675
887
|
function InlineForm({ label, initialValue = "", onSubmit, onCancel }) {
|
|
676
888
|
const { isRawModeSupported } = useStdin5();
|
|
677
|
-
const
|
|
678
|
-
const [
|
|
889
|
+
const { stdout } = useStdout2();
|
|
890
|
+
const [editor, setEditor] = useState3(() => makeInitialState(initialValue));
|
|
891
|
+
const editorRef = useRef2(editor);
|
|
892
|
+
editorRef.current = editor;
|
|
679
893
|
useInput5((input, key) => {
|
|
680
894
|
if (key.escape) {
|
|
681
895
|
onCancel();
|
|
682
896
|
return;
|
|
683
897
|
}
|
|
898
|
+
const { lines: lines2, row: row2, col: col2 } = editorRef.current;
|
|
899
|
+
const lineWidth = Math.max(1, (stdout?.columns ?? 80) - CHROME);
|
|
684
900
|
if (key.return) {
|
|
685
|
-
onSubmit(
|
|
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 });
|
|
918
|
+
}
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
if (key.downArrow) {
|
|
922
|
+
const line = lines2[row2];
|
|
923
|
+
const visualRow = Math.floor(col2 / lineWidth);
|
|
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 });
|
|
933
|
+
}
|
|
686
934
|
return;
|
|
687
935
|
}
|
|
688
936
|
if (key.leftArrow) {
|
|
689
|
-
|
|
937
|
+
if (col2 > 0) {
|
|
938
|
+
setEditor({ lines: lines2, row: row2, col: col2 - 1 });
|
|
939
|
+
} else if (row2 > 0) {
|
|
940
|
+
const newRow = row2 - 1;
|
|
941
|
+
setEditor({ lines: lines2, row: newRow, col: lines2[newRow].length });
|
|
942
|
+
}
|
|
690
943
|
return;
|
|
691
944
|
}
|
|
692
945
|
if (key.rightArrow) {
|
|
693
|
-
|
|
946
|
+
if (col2 < lines2[row2].length) {
|
|
947
|
+
setEditor({ lines: lines2, row: row2, col: col2 + 1 });
|
|
948
|
+
} else if (row2 < lines2.length - 1) {
|
|
949
|
+
setEditor({ lines: lines2, row: row2 + 1, col: 0 });
|
|
950
|
+
}
|
|
694
951
|
return;
|
|
695
952
|
}
|
|
696
953
|
if (key.ctrl && input === "a" || key.home) {
|
|
697
|
-
|
|
954
|
+
setEditor({ lines: lines2, row: row2, col: 0 });
|
|
698
955
|
return;
|
|
699
956
|
}
|
|
700
957
|
if (key.ctrl && input === "e" || key.end) {
|
|
701
|
-
|
|
958
|
+
setEditor({ lines: lines2, row: row2, col: lines2[row2].length });
|
|
702
959
|
return;
|
|
703
960
|
}
|
|
704
961
|
if (key.ctrl && input === "k") {
|
|
705
|
-
|
|
962
|
+
const newLines = lines2.map((l, i) => i === row2 ? l.slice(0, col2) : l);
|
|
963
|
+
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
706
964
|
return;
|
|
707
965
|
}
|
|
708
966
|
if (key.ctrl && input === "u") {
|
|
709
|
-
|
|
710
|
-
|
|
967
|
+
const newLines = lines2.map((l, i) => i === row2 ? l.slice(col2) : l);
|
|
968
|
+
setEditor({ lines: newLines, row: row2, col: 0 });
|
|
711
969
|
return;
|
|
712
970
|
}
|
|
713
971
|
if (key.backspace) {
|
|
714
|
-
if (
|
|
715
|
-
|
|
716
|
-
|
|
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 });
|
|
981
|
+
}
|
|
717
982
|
return;
|
|
718
983
|
}
|
|
719
984
|
if (key.delete) {
|
|
720
|
-
|
|
985
|
+
const cur = lines2[row2];
|
|
986
|
+
if (col2 < cur.length) {
|
|
987
|
+
const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2) + cur.slice(col2 + 1) : l);
|
|
988
|
+
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
989
|
+
} else if (row2 < lines2.length - 1) {
|
|
990
|
+
const merged = cur + lines2[row2 + 1];
|
|
991
|
+
const newLines = [...lines2.slice(0, row2), merged, ...lines2.slice(row2 + 2)];
|
|
992
|
+
setEditor({ lines: newLines, row: row2, col: col2 });
|
|
993
|
+
}
|
|
721
994
|
return;
|
|
722
995
|
}
|
|
723
996
|
if (input && !key.ctrl && !key.meta) {
|
|
724
|
-
|
|
725
|
-
|
|
997
|
+
const cur = lines2[row2];
|
|
998
|
+
const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2) + input + cur.slice(col2) : l);
|
|
999
|
+
setEditor({ lines: newLines, row: row2, col: col2 + input.length });
|
|
726
1000
|
}
|
|
727
1001
|
}, { isActive: isRawModeSupported });
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1002
|
+
const { lines, row, col } = editor;
|
|
1003
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, paddingTop: 0, children: [
|
|
1004
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1005
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, color: "cyan", children: [
|
|
1006
|
+
label,
|
|
1007
|
+
" "
|
|
1008
|
+
] }),
|
|
1009
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u21B5 confirm esc cancel" })
|
|
735
1010
|
] }),
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1011
|
+
lines.map((line, r) => {
|
|
1012
|
+
const isActive = r === row;
|
|
1013
|
+
if (!isActive) {
|
|
1014
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1015
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
|
|
1016
|
+
/* @__PURE__ */ jsx6(Text6, { children: line || " " })
|
|
1017
|
+
] }, r);
|
|
1018
|
+
}
|
|
1019
|
+
const before = line.slice(0, col);
|
|
1020
|
+
const cursor = line[col] ?? " ";
|
|
1021
|
+
const after = line.slice(col + 1);
|
|
1022
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1023
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
|
|
1024
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1025
|
+
before,
|
|
1026
|
+
/* @__PURE__ */ jsx6(Text6, { inverse: true, children: cursor }),
|
|
1027
|
+
after
|
|
1028
|
+
] })
|
|
1029
|
+
] }, r);
|
|
1030
|
+
})
|
|
1031
|
+
] });
|
|
741
1032
|
}
|
|
742
1033
|
|
|
743
1034
|
// src/tui/App.tsx
|
|
744
|
-
import { jsx as jsx7, jsxs as
|
|
1035
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
745
1036
|
function App({ files }) {
|
|
746
1037
|
const { exit } = useApp();
|
|
747
1038
|
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] =
|
|
1039
|
+
const [fileIndex, setFileIndex] = useState4(0);
|
|
1040
|
+
const [keyIndex, setKeyIndex] = useState4(0);
|
|
1041
|
+
const [focus, setFocus] = useState4("files");
|
|
1042
|
+
const [revealed, setRevealed] = useState4(/* @__PURE__ */ new Map());
|
|
1043
|
+
const [mode, setMode] = useState4({ type: "normal" });
|
|
1044
|
+
const [statusMsg, setStatusMsg] = useState4();
|
|
1045
|
+
const [keys, setKeys] = useState4(() => loadKeys(files[0]));
|
|
755
1046
|
const selectedFile = files[fileIndex];
|
|
756
1047
|
function loadKeys(file) {
|
|
757
1048
|
try {
|
|
@@ -774,7 +1065,6 @@ function App({ files }) {
|
|
|
774
1065
|
setTimeout(() => setStatusMsg(void 0), ms);
|
|
775
1066
|
}
|
|
776
1067
|
useInput6((input, key) => {
|
|
777
|
-
if (mode.type !== "normal") return;
|
|
778
1068
|
if (input === "q" || key.escape) {
|
|
779
1069
|
exit();
|
|
780
1070
|
return;
|
|
@@ -816,11 +1106,12 @@ function App({ files }) {
|
|
|
816
1106
|
setRevealed(/* @__PURE__ */ new Map());
|
|
817
1107
|
return;
|
|
818
1108
|
}
|
|
1109
|
+
const decrypted = keys.some((e) => e.encrypted) ? decryptAllValues(selectedFile.path) : {};
|
|
819
1110
|
const next = /* @__PURE__ */ new Map();
|
|
820
1111
|
for (const entry of keys) {
|
|
821
1112
|
if (entry.encrypted) {
|
|
822
|
-
const plain =
|
|
823
|
-
if (plain ===
|
|
1113
|
+
const plain = decrypted[entry.key];
|
|
1114
|
+
if (plain === void 0 || isEncryptedValue(plain)) {
|
|
824
1115
|
flash("\u{1F512} Private key not found \u2014 cannot reveal all");
|
|
825
1116
|
return;
|
|
826
1117
|
}
|
|
@@ -890,6 +1181,7 @@ function App({ files }) {
|
|
|
890
1181
|
onSelectKey: setKeyIndex,
|
|
891
1182
|
statusMsg,
|
|
892
1183
|
focus2: focus,
|
|
1184
|
+
interactive: false,
|
|
893
1185
|
extra: /* @__PURE__ */ jsx7(
|
|
894
1186
|
InlineForm,
|
|
895
1187
|
{
|
|
@@ -930,6 +1222,7 @@ function App({ files }) {
|
|
|
930
1222
|
onSelectKey: setKeyIndex,
|
|
931
1223
|
statusMsg,
|
|
932
1224
|
focus2: focus,
|
|
1225
|
+
interactive: false,
|
|
933
1226
|
extra: /* @__PURE__ */ jsx7(
|
|
934
1227
|
InlineForm,
|
|
935
1228
|
{
|
|
@@ -963,6 +1256,7 @@ function App({ files }) {
|
|
|
963
1256
|
onSelectKey: setKeyIndex,
|
|
964
1257
|
statusMsg,
|
|
965
1258
|
focus2: focus,
|
|
1259
|
+
interactive: false,
|
|
966
1260
|
extra: /* @__PURE__ */ jsx7(
|
|
967
1261
|
InlineForm,
|
|
968
1262
|
{
|
|
@@ -1008,6 +1302,7 @@ function App({ files }) {
|
|
|
1008
1302
|
onSelectKey: setKeyIndex,
|
|
1009
1303
|
statusMsg,
|
|
1010
1304
|
focus2: focus,
|
|
1305
|
+
interactive: false,
|
|
1011
1306
|
extra: /* @__PURE__ */ jsx7(ConfirmAddEncrypt, { keyName, onEncrypt: () => commit(true), onPlain: () => commit(false), onCancel: () => setMode({ type: "normal" }) })
|
|
1012
1307
|
}
|
|
1013
1308
|
);
|
|
@@ -1027,6 +1322,7 @@ function App({ files }) {
|
|
|
1027
1322
|
onSelectKey: setKeyIndex,
|
|
1028
1323
|
statusMsg,
|
|
1029
1324
|
focus2: focus,
|
|
1325
|
+
interactive: false,
|
|
1030
1326
|
extra: /* @__PURE__ */ jsx7(
|
|
1031
1327
|
ConfirmDelete,
|
|
1032
1328
|
{
|
|
@@ -1059,6 +1355,7 @@ function App({ files }) {
|
|
|
1059
1355
|
onSelectKey: setKeyIndex,
|
|
1060
1356
|
statusMsg,
|
|
1061
1357
|
focus2: focus,
|
|
1358
|
+
interactive: false,
|
|
1062
1359
|
extra: /* @__PURE__ */ jsx7(
|
|
1063
1360
|
ConfirmEncrypt,
|
|
1064
1361
|
{
|
|
@@ -1111,7 +1408,8 @@ function App({ files }) {
|
|
|
1111
1408
|
onSelectFile: selectFile,
|
|
1112
1409
|
onSelectKey: setKeyIndex,
|
|
1113
1410
|
statusMsg,
|
|
1114
|
-
focus2: focus
|
|
1411
|
+
focus2: focus,
|
|
1412
|
+
interactive: true
|
|
1115
1413
|
}
|
|
1116
1414
|
);
|
|
1117
1415
|
}
|
|
@@ -1121,6 +1419,7 @@ function Layout({
|
|
|
1121
1419
|
keys,
|
|
1122
1420
|
keyIndex,
|
|
1123
1421
|
focus,
|
|
1422
|
+
interactive,
|
|
1124
1423
|
revealed,
|
|
1125
1424
|
onSelectFile,
|
|
1126
1425
|
onSelectKey,
|
|
@@ -1129,10 +1428,13 @@ function Layout({
|
|
|
1129
1428
|
}) {
|
|
1130
1429
|
const selectedFile = files[fileIndex];
|
|
1131
1430
|
const encCount = files.filter((f) => f.encrypted).length;
|
|
1132
|
-
|
|
1133
|
-
|
|
1431
|
+
const termRows = useTerminalRows();
|
|
1432
|
+
const termCols = useTerminalCols();
|
|
1433
|
+
const listRows = Math.max(3, termRows - (extra ? 3 : 5));
|
|
1434
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", height: termRows, children: [
|
|
1435
|
+
/* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1134
1436
|
/* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "dotenvx-ui" }),
|
|
1135
|
-
/* @__PURE__ */
|
|
1437
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1136
1438
|
" ",
|
|
1137
1439
|
selectedFile.relativePath,
|
|
1138
1440
|
" \xB7 ",
|
|
@@ -1142,8 +1444,8 @@ function Layout({
|
|
|
1142
1444
|
" enc"
|
|
1143
1445
|
] })
|
|
1144
1446
|
] }),
|
|
1145
|
-
/* @__PURE__ */
|
|
1146
|
-
/* @__PURE__ */ jsx7(FileList, { files, selectedIndex: fileIndex, focused: focus === "files", onSelect: onSelectFile }),
|
|
1447
|
+
/* @__PURE__ */ jsxs7(Box7, { height: listRows, children: [
|
|
1448
|
+
/* @__PURE__ */ jsx7(FileList, { files, selectedIndex: fileIndex, focused: focus === "files", interactive, onSelect: onSelectFile }),
|
|
1147
1449
|
/* @__PURE__ */ jsx7(
|
|
1148
1450
|
KeyTable,
|
|
1149
1451
|
{
|
|
@@ -1151,23 +1453,58 @@ function Layout({
|
|
|
1151
1453
|
keys,
|
|
1152
1454
|
selectedIndex: keyIndex,
|
|
1153
1455
|
focused: focus === "keys",
|
|
1456
|
+
interactive,
|
|
1154
1457
|
revealed,
|
|
1155
|
-
onSelect: onSelectKey
|
|
1458
|
+
onSelect: onSelectKey,
|
|
1459
|
+
maxRows: listRows
|
|
1156
1460
|
}
|
|
1157
1461
|
)
|
|
1158
1462
|
] }),
|
|
1159
1463
|
extra,
|
|
1464
|
+
!extra && /* @__PURE__ */ jsx7(ValuePreview, { keys, keyIndex, focus, revealed, width: termCols }),
|
|
1160
1465
|
/* @__PURE__ */ jsx7(StatusBar, { focus, message: statusMsg })
|
|
1161
1466
|
] });
|
|
1162
1467
|
}
|
|
1468
|
+
function ValuePreview({ keys, keyIndex, focus, revealed, width }) {
|
|
1469
|
+
if (focus !== "keys") return null;
|
|
1470
|
+
const k = keys[keyIndex];
|
|
1471
|
+
if (!k) return null;
|
|
1472
|
+
let value;
|
|
1473
|
+
if (k.encrypted) {
|
|
1474
|
+
value = revealed.has(k.key) ? revealed.get(k.key) : "\u2022\u2022\u2022\u2022 (press r to reveal)";
|
|
1475
|
+
} else {
|
|
1476
|
+
value = revealed.has(k.key) ? revealed.get(k.key) : k.value;
|
|
1477
|
+
}
|
|
1478
|
+
const flat = value.replace(/\n/g, "\u21B5 ");
|
|
1479
|
+
return /* @__PURE__ */ jsxs7(
|
|
1480
|
+
Box7,
|
|
1481
|
+
{
|
|
1482
|
+
borderStyle: "single",
|
|
1483
|
+
borderTop: true,
|
|
1484
|
+
borderBottom: false,
|
|
1485
|
+
borderLeft: false,
|
|
1486
|
+
borderRight: false,
|
|
1487
|
+
paddingX: 1,
|
|
1488
|
+
width,
|
|
1489
|
+
children: [
|
|
1490
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: "cyan", children: [
|
|
1491
|
+
k.key,
|
|
1492
|
+
" "
|
|
1493
|
+
] }),
|
|
1494
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\xB7 " }),
|
|
1495
|
+
/* @__PURE__ */ jsx7(Text7, { truncate: true, children: flat })
|
|
1496
|
+
]
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1163
1500
|
function ConfirmDelete({ keyName, onConfirm, onCancel }) {
|
|
1164
1501
|
const { isRawModeSupported } = useStdin6();
|
|
1165
1502
|
useInput6((input) => {
|
|
1166
1503
|
if (input === "y" || input === "Y") onConfirm();
|
|
1167
1504
|
else onCancel();
|
|
1168
1505
|
}, { isActive: isRawModeSupported });
|
|
1169
|
-
return /* @__PURE__ */
|
|
1170
|
-
/* @__PURE__ */
|
|
1506
|
+
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1507
|
+
/* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
|
|
1171
1508
|
"Delete ",
|
|
1172
1509
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
|
|
1173
1510
|
"? "
|
|
@@ -1183,8 +1520,8 @@ function ConfirmEncrypt({ decrypt, fileName, onConfirm, onCancel }) {
|
|
|
1183
1520
|
}, { isActive: isRawModeSupported });
|
|
1184
1521
|
const action = decrypt ? "Decrypt" : "Encrypt";
|
|
1185
1522
|
const color = decrypt ? "yellow" : "green";
|
|
1186
|
-
return /* @__PURE__ */
|
|
1187
|
-
/* @__PURE__ */
|
|
1523
|
+
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1524
|
+
/* @__PURE__ */ jsxs7(Text7, { color, children: [
|
|
1188
1525
|
action,
|
|
1189
1526
|
" ",
|
|
1190
1527
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: fileName }),
|
|
@@ -1209,8 +1546,8 @@ function ConfirmAddEncrypt({ keyName, onEncrypt, onPlain, onCancel }) {
|
|
|
1209
1546
|
return;
|
|
1210
1547
|
}
|
|
1211
1548
|
}, { isActive: isRawModeSupported });
|
|
1212
|
-
return /* @__PURE__ */
|
|
1213
|
-
/* @__PURE__ */
|
|
1549
|
+
return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
|
|
1550
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
1214
1551
|
"Encrypt ",
|
|
1215
1552
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
|
|
1216
1553
|
"? "
|
|
@@ -1222,7 +1559,7 @@ function ConfirmAddEncrypt({ keyName, onEncrypt, onPlain, onCancel }) {
|
|
|
1222
1559
|
// src/tui/ErrorBoundary.tsx
|
|
1223
1560
|
import React4 from "react";
|
|
1224
1561
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
1225
|
-
import { jsx as jsx8, jsxs as
|
|
1562
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1226
1563
|
var ErrorBoundary = class extends React4.Component {
|
|
1227
1564
|
state = { error: null };
|
|
1228
1565
|
static getDerivedStateFromError(error) {
|
|
@@ -1230,7 +1567,7 @@ var ErrorBoundary = class extends React4.Component {
|
|
|
1230
1567
|
}
|
|
1231
1568
|
render() {
|
|
1232
1569
|
if (this.state.error) {
|
|
1233
|
-
return /* @__PURE__ */
|
|
1570
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
1234
1571
|
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "red", children: "dotenvx-ui crashed" }),
|
|
1235
1572
|
/* @__PURE__ */ jsx8(Text8, { children: this.state.error.message }),
|
|
1236
1573
|
/* @__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 +1624,7 @@ function runTUI() {
|
|
|
1287
1624
|
console.error("No .env files found in this directory.");
|
|
1288
1625
|
process.exit(1);
|
|
1289
1626
|
}
|
|
1290
|
-
render(/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }));
|
|
1627
|
+
render(/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }), { alternateScreen: true });
|
|
1291
1628
|
}
|
|
1292
1629
|
function runWebUI() {
|
|
1293
1630
|
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.1",
|
|
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
|
+
}
|