formalconf 2.0.0 → 2.0.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/README.md +167 -207
- package/dist/formalconf.js +1132 -643
- package/package.json +1 -1
package/dist/formalconf.js
CHANGED
|
@@ -1,78 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// src/cli/formalconf.tsx
|
|
3
|
-
import { useState as
|
|
4
|
-
import { render,
|
|
5
|
-
import { Spinner } from "@inkjs/ui";
|
|
6
|
-
|
|
7
|
-
// src/components/ui/VimSelect.tsx
|
|
8
|
-
import { useState } from "react";
|
|
9
|
-
import { Box, Text, useInput } from "ink";
|
|
10
|
-
|
|
11
|
-
// src/lib/theme.ts
|
|
12
|
-
var colors = {
|
|
13
|
-
primary: "#5eead4",
|
|
14
|
-
primaryDim: "#2dd4bf",
|
|
15
|
-
accent: "#06b6d4",
|
|
16
|
-
success: "#22c55e",
|
|
17
|
-
error: "#ef4444",
|
|
18
|
-
warning: "#f59e0b",
|
|
19
|
-
info: "#3b82f6",
|
|
20
|
-
text: "white",
|
|
21
|
-
textDim: "gray",
|
|
22
|
-
border: "#374151",
|
|
23
|
-
borderLight: "#4b5563"
|
|
24
|
-
};
|
|
25
|
-
var borderStyles = {
|
|
26
|
-
panel: "round",
|
|
27
|
-
header: "round",
|
|
28
|
-
footer: "single"
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// src/components/ui/VimSelect.tsx
|
|
32
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
33
|
-
function VimSelect({ options, onChange, isDisabled = false }) {
|
|
34
|
-
const [index, setIndex] = useState(0);
|
|
35
|
-
useInput((input, key) => {
|
|
36
|
-
if (isDisabled)
|
|
37
|
-
return;
|
|
38
|
-
if (input === "j" || key.downArrow) {
|
|
39
|
-
setIndex((i) => i < options.length - 1 ? i + 1 : i);
|
|
40
|
-
}
|
|
41
|
-
if (input === "k" || key.upArrow) {
|
|
42
|
-
setIndex((i) => i > 0 ? i - 1 : i);
|
|
43
|
-
}
|
|
44
|
-
if (input === "l" || key.return) {
|
|
45
|
-
onChange(options[index].value);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
49
|
-
flexDirection: "column",
|
|
50
|
-
children: options.map((opt, i) => /* @__PURE__ */ jsxDEV(Box, {
|
|
51
|
-
children: /* @__PURE__ */ jsxDEV(Text, {
|
|
52
|
-
color: i === index ? colors.primary : undefined,
|
|
53
|
-
children: [
|
|
54
|
-
i === index ? "❯" : " ",
|
|
55
|
-
" ",
|
|
56
|
-
opt.label
|
|
57
|
-
]
|
|
58
|
-
}, undefined, true, undefined, this)
|
|
59
|
-
}, opt.value, false, undefined, this))
|
|
60
|
-
}, undefined, false, undefined, this);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// src/cli/formalconf.tsx
|
|
64
|
-
import { readdirSync as readdirSync5, existsSync as existsSync6 } from "fs";
|
|
65
|
-
import { join as join5 } from "path";
|
|
3
|
+
import { useState as useState9, useEffect as useEffect6 } from "react";
|
|
4
|
+
import { render, useApp as useApp2, useInput as useInput9 } from "ink";
|
|
5
|
+
import { Spinner as Spinner2 } from "@inkjs/ui";
|
|
66
6
|
|
|
67
7
|
// src/components/layout/Layout.tsx
|
|
68
|
-
import { Box as
|
|
8
|
+
import { Box as Box5 } from "ink";
|
|
69
9
|
|
|
70
10
|
// src/hooks/useTerminalSize.ts
|
|
71
11
|
import { useStdout } from "ink";
|
|
72
|
-
import { useState
|
|
12
|
+
import { useState, useEffect } from "react";
|
|
73
13
|
function useTerminalSize() {
|
|
74
14
|
const { stdout } = useStdout();
|
|
75
|
-
const [size, setSize] =
|
|
15
|
+
const [size, setSize] = useState({
|
|
76
16
|
columns: stdout.columns || 80,
|
|
77
17
|
rows: stdout.rows || 24
|
|
78
18
|
});
|
|
@@ -92,10 +32,10 @@ function useTerminalSize() {
|
|
|
92
32
|
}
|
|
93
33
|
|
|
94
34
|
// src/components/Header.tsx
|
|
95
|
-
import { Box as
|
|
35
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
96
36
|
|
|
97
37
|
// src/hooks/useSystemStatus.ts
|
|
98
|
-
import { useState as
|
|
38
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
99
39
|
import { existsSync, readlinkSync, readdirSync, lstatSync } from "fs";
|
|
100
40
|
|
|
101
41
|
// src/lib/paths.ts
|
|
@@ -170,6 +110,115 @@ async function execLive(command, cwd) {
|
|
|
170
110
|
});
|
|
171
111
|
});
|
|
172
112
|
}
|
|
113
|
+
async function execStreaming(command, onLine, cwd) {
|
|
114
|
+
if (isBun) {
|
|
115
|
+
const proc = Bun.spawn(command, {
|
|
116
|
+
stdout: "pipe",
|
|
117
|
+
stderr: "pipe",
|
|
118
|
+
cwd
|
|
119
|
+
});
|
|
120
|
+
const processStream = async (stream) => {
|
|
121
|
+
const reader = stream.getReader();
|
|
122
|
+
const decoder = new TextDecoder;
|
|
123
|
+
let buffer = "";
|
|
124
|
+
while (true) {
|
|
125
|
+
const { done, value } = await reader.read();
|
|
126
|
+
if (done)
|
|
127
|
+
break;
|
|
128
|
+
buffer += decoder.decode(value, { stream: true });
|
|
129
|
+
const lines = buffer.split(`
|
|
130
|
+
`);
|
|
131
|
+
buffer = lines.pop() || "";
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
onLine(line);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (buffer) {
|
|
137
|
+
onLine(buffer);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
await Promise.all([
|
|
141
|
+
processStream(proc.stdout),
|
|
142
|
+
processStream(proc.stderr)
|
|
143
|
+
]);
|
|
144
|
+
return await proc.exited;
|
|
145
|
+
}
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
const [cmd, ...args] = command;
|
|
148
|
+
const proc = nodeSpawn(cmd, args, { cwd, shell: false });
|
|
149
|
+
const processData = (data) => {
|
|
150
|
+
const text = data.toString();
|
|
151
|
+
const lines = text.split(`
|
|
152
|
+
`);
|
|
153
|
+
for (const line of lines) {
|
|
154
|
+
if (line)
|
|
155
|
+
onLine(line);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
proc.stdout?.on("data", processData);
|
|
159
|
+
proc.stderr?.on("data", processData);
|
|
160
|
+
proc.on("close", (code) => {
|
|
161
|
+
resolve(code ?? 1);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async function execStreamingWithTTY(command, onLine, cwd) {
|
|
166
|
+
if (isBun) {
|
|
167
|
+
const proc = Bun.spawn(command, {
|
|
168
|
+
stdout: "pipe",
|
|
169
|
+
stderr: "pipe",
|
|
170
|
+
stdin: "inherit",
|
|
171
|
+
cwd
|
|
172
|
+
});
|
|
173
|
+
const processStream = async (stream) => {
|
|
174
|
+
const reader = stream.getReader();
|
|
175
|
+
const decoder = new TextDecoder;
|
|
176
|
+
let buffer = "";
|
|
177
|
+
while (true) {
|
|
178
|
+
const { done, value } = await reader.read();
|
|
179
|
+
if (done)
|
|
180
|
+
break;
|
|
181
|
+
buffer += decoder.decode(value, { stream: true });
|
|
182
|
+
const lines = buffer.split(`
|
|
183
|
+
`);
|
|
184
|
+
buffer = lines.pop() || "";
|
|
185
|
+
for (const line of lines) {
|
|
186
|
+
onLine(line);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (buffer) {
|
|
190
|
+
onLine(buffer);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
await Promise.all([
|
|
194
|
+
processStream(proc.stdout),
|
|
195
|
+
processStream(proc.stderr)
|
|
196
|
+
]);
|
|
197
|
+
return await proc.exited;
|
|
198
|
+
}
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
const [cmd, ...args] = command;
|
|
201
|
+
const proc = nodeSpawn(cmd, args, {
|
|
202
|
+
cwd,
|
|
203
|
+
shell: false,
|
|
204
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
205
|
+
});
|
|
206
|
+
const processData = (data) => {
|
|
207
|
+
const text = data.toString();
|
|
208
|
+
const lines = text.split(`
|
|
209
|
+
`);
|
|
210
|
+
for (const line of lines) {
|
|
211
|
+
if (line)
|
|
212
|
+
onLine(line);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
proc.stdout?.on("data", processData);
|
|
216
|
+
proc.stderr?.on("data", processData);
|
|
217
|
+
proc.on("close", (code) => {
|
|
218
|
+
resolve(code ?? 1);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
173
222
|
async function readJson(path) {
|
|
174
223
|
if (isBun) {
|
|
175
224
|
return Bun.file(path).json();
|
|
@@ -210,6 +259,19 @@ async function commandExists(cmd) {
|
|
|
210
259
|
const result = await exec(["which", cmd]);
|
|
211
260
|
return result.success;
|
|
212
261
|
}
|
|
262
|
+
async function checkPrerequisites() {
|
|
263
|
+
const required = [
|
|
264
|
+
{ name: "stow", install: "brew install stow" },
|
|
265
|
+
{ name: "brew", install: "https://brew.sh" }
|
|
266
|
+
];
|
|
267
|
+
const missing = [];
|
|
268
|
+
for (const dep of required) {
|
|
269
|
+
if (!await commandExists(dep.name)) {
|
|
270
|
+
missing.push(dep);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { ok: missing.length === 0, missing };
|
|
274
|
+
}
|
|
213
275
|
|
|
214
276
|
// src/lib/paths.ts
|
|
215
277
|
var HOME_DIR = homedir();
|
|
@@ -236,7 +298,7 @@ async function ensureConfigDir() {
|
|
|
236
298
|
// src/hooks/useSystemStatus.ts
|
|
237
299
|
import { basename, dirname as dirname2, join as join2 } from "path";
|
|
238
300
|
function useSystemStatus() {
|
|
239
|
-
const [status, setStatus] =
|
|
301
|
+
const [status, setStatus] = useState2({
|
|
240
302
|
currentTheme: null,
|
|
241
303
|
configsLinked: false,
|
|
242
304
|
loading: true
|
|
@@ -273,8 +335,30 @@ function useSystemStatus() {
|
|
|
273
335
|
}
|
|
274
336
|
|
|
275
337
|
// src/components/ui/StatusIndicator.tsx
|
|
276
|
-
import { Box
|
|
277
|
-
|
|
338
|
+
import { Box, Text } from "ink";
|
|
339
|
+
|
|
340
|
+
// src/lib/theme.ts
|
|
341
|
+
var colors = {
|
|
342
|
+
primary: "#5eead4",
|
|
343
|
+
primaryDim: "#2dd4bf",
|
|
344
|
+
accent: "#06b6d4",
|
|
345
|
+
success: "#22c55e",
|
|
346
|
+
error: "#ef4444",
|
|
347
|
+
warning: "#f59e0b",
|
|
348
|
+
info: "#3b82f6",
|
|
349
|
+
text: "white",
|
|
350
|
+
textDim: "gray",
|
|
351
|
+
border: "#374151",
|
|
352
|
+
borderLight: "#4b5563"
|
|
353
|
+
};
|
|
354
|
+
var borderStyles = {
|
|
355
|
+
panel: "round",
|
|
356
|
+
header: "round",
|
|
357
|
+
footer: "single"
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// src/components/ui/StatusIndicator.tsx
|
|
361
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
278
362
|
function StatusIndicator({
|
|
279
363
|
label,
|
|
280
364
|
value,
|
|
@@ -292,17 +376,17 @@ function StatusIndicator({
|
|
|
292
376
|
error: "●",
|
|
293
377
|
neutral: "○"
|
|
294
378
|
};
|
|
295
|
-
return /* @__PURE__ */
|
|
379
|
+
return /* @__PURE__ */ jsxDEV(Box, {
|
|
296
380
|
gap: 1,
|
|
297
381
|
children: [
|
|
298
|
-
/* @__PURE__ */
|
|
382
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
299
383
|
dimColor: true,
|
|
300
384
|
children: [
|
|
301
385
|
label,
|
|
302
386
|
":"
|
|
303
387
|
]
|
|
304
388
|
}, undefined, true, undefined, this),
|
|
305
|
-
/* @__PURE__ */
|
|
389
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
306
390
|
color: statusColors[status],
|
|
307
391
|
children: [
|
|
308
392
|
icon[status],
|
|
@@ -316,7 +400,7 @@ function StatusIndicator({
|
|
|
316
400
|
// package.json
|
|
317
401
|
var package_default = {
|
|
318
402
|
name: "formalconf",
|
|
319
|
-
version: "2.0.
|
|
403
|
+
version: "2.0.2",
|
|
320
404
|
description: "Dotfiles management TUI for macOS - config management, package sync, and theme switching",
|
|
321
405
|
type: "module",
|
|
322
406
|
main: "./dist/formalconf.js",
|
|
@@ -369,11 +453,11 @@ var package_default = {
|
|
|
369
453
|
};
|
|
370
454
|
|
|
371
455
|
// src/components/Header.tsx
|
|
372
|
-
import { jsxDEV as
|
|
456
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
373
457
|
function Header() {
|
|
374
458
|
const { columns } = useTerminalSize();
|
|
375
459
|
const { currentTheme, configsLinked, loading } = useSystemStatus();
|
|
376
|
-
return /* @__PURE__ */
|
|
460
|
+
return /* @__PURE__ */ jsxDEV2(Box2, {
|
|
377
461
|
flexDirection: "column",
|
|
378
462
|
width: columns - 2,
|
|
379
463
|
borderStyle: borderStyles.header,
|
|
@@ -381,24 +465,24 @@ function Header() {
|
|
|
381
465
|
paddingX: 2,
|
|
382
466
|
marginBottom: 1,
|
|
383
467
|
children: [
|
|
384
|
-
/* @__PURE__ */
|
|
468
|
+
/* @__PURE__ */ jsxDEV2(Box2, {
|
|
385
469
|
justifyContent: "space-between",
|
|
386
470
|
width: "100%",
|
|
387
471
|
children: [
|
|
388
|
-
/* @__PURE__ */
|
|
472
|
+
/* @__PURE__ */ jsxDEV2(Box2, {
|
|
389
473
|
children: [
|
|
390
|
-
/* @__PURE__ */
|
|
474
|
+
/* @__PURE__ */ jsxDEV2(Text2, {
|
|
391
475
|
bold: true,
|
|
392
476
|
color: colors.primary,
|
|
393
477
|
children: "FormalConf"
|
|
394
478
|
}, undefined, false, undefined, this),
|
|
395
|
-
/* @__PURE__ */
|
|
479
|
+
/* @__PURE__ */ jsxDEV2(Text2, {
|
|
396
480
|
dimColor: true,
|
|
397
481
|
children: " - Dotfiles Manager"
|
|
398
482
|
}, undefined, false, undefined, this)
|
|
399
483
|
]
|
|
400
484
|
}, undefined, true, undefined, this),
|
|
401
|
-
/* @__PURE__ */
|
|
485
|
+
/* @__PURE__ */ jsxDEV2(Text2, {
|
|
402
486
|
dimColor: true,
|
|
403
487
|
children: [
|
|
404
488
|
"v",
|
|
@@ -407,16 +491,16 @@ function Header() {
|
|
|
407
491
|
}, undefined, true, undefined, this)
|
|
408
492
|
]
|
|
409
493
|
}, undefined, true, undefined, this),
|
|
410
|
-
!loading && /* @__PURE__ */
|
|
494
|
+
!loading && /* @__PURE__ */ jsxDEV2(Box2, {
|
|
411
495
|
marginTop: 1,
|
|
412
496
|
gap: 4,
|
|
413
497
|
children: [
|
|
414
|
-
/* @__PURE__ */
|
|
498
|
+
/* @__PURE__ */ jsxDEV2(StatusIndicator, {
|
|
415
499
|
label: "Theme",
|
|
416
500
|
value: currentTheme,
|
|
417
501
|
status: currentTheme ? "success" : "neutral"
|
|
418
502
|
}, undefined, false, undefined, this),
|
|
419
|
-
/* @__PURE__ */
|
|
503
|
+
/* @__PURE__ */ jsxDEV2(StatusIndicator, {
|
|
420
504
|
label: "Configs",
|
|
421
505
|
value: configsLinked ? "Linked" : "Not linked",
|
|
422
506
|
status: configsLinked ? "success" : "warning"
|
|
@@ -428,8 +512,8 @@ function Header() {
|
|
|
428
512
|
}
|
|
429
513
|
|
|
430
514
|
// src/components/layout/Footer.tsx
|
|
431
|
-
import { Box as
|
|
432
|
-
import { jsxDEV as
|
|
515
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
516
|
+
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
433
517
|
var defaultShortcuts = [
|
|
434
518
|
{ key: "↑↓/jk", label: "Navigate" },
|
|
435
519
|
{ key: "Enter/l", label: "Select" },
|
|
@@ -438,7 +522,7 @@ var defaultShortcuts = [
|
|
|
438
522
|
];
|
|
439
523
|
function Footer({ shortcuts = defaultShortcuts }) {
|
|
440
524
|
const { columns } = useTerminalSize();
|
|
441
|
-
return /* @__PURE__ */
|
|
525
|
+
return /* @__PURE__ */ jsxDEV3(Box3, {
|
|
442
526
|
width: columns - 2,
|
|
443
527
|
borderStyle: borderStyles.footer,
|
|
444
528
|
borderColor: colors.border,
|
|
@@ -446,15 +530,15 @@ function Footer({ shortcuts = defaultShortcuts }) {
|
|
|
446
530
|
marginTop: 1,
|
|
447
531
|
justifyContent: "center",
|
|
448
532
|
gap: 2,
|
|
449
|
-
children: shortcuts.map((shortcut, index) => /* @__PURE__ */
|
|
533
|
+
children: shortcuts.map((shortcut, index) => /* @__PURE__ */ jsxDEV3(Box3, {
|
|
450
534
|
gap: 1,
|
|
451
535
|
children: [
|
|
452
|
-
/* @__PURE__ */
|
|
536
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
453
537
|
color: colors.primary,
|
|
454
538
|
bold: true,
|
|
455
539
|
children: shortcut.key
|
|
456
540
|
}, undefined, false, undefined, this),
|
|
457
|
-
/* @__PURE__ */
|
|
541
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
458
542
|
dimColor: true,
|
|
459
543
|
children: shortcut.label
|
|
460
544
|
}, undefined, false, undefined, this)
|
|
@@ -464,14 +548,14 @@ function Footer({ shortcuts = defaultShortcuts }) {
|
|
|
464
548
|
}
|
|
465
549
|
|
|
466
550
|
// src/components/layout/Breadcrumb.tsx
|
|
467
|
-
import
|
|
468
|
-
import { Box as
|
|
469
|
-
import { jsxDEV as
|
|
551
|
+
import React from "react";
|
|
552
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
553
|
+
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
470
554
|
function Breadcrumb({ path }) {
|
|
471
|
-
return /* @__PURE__ */
|
|
472
|
-
children: path.map((segment, index) => /* @__PURE__ */
|
|
555
|
+
return /* @__PURE__ */ jsxDEV4(Box4, {
|
|
556
|
+
children: path.map((segment, index) => /* @__PURE__ */ jsxDEV4(React.Fragment, {
|
|
473
557
|
children: [
|
|
474
|
-
index > 0 && /* @__PURE__ */
|
|
558
|
+
index > 0 && /* @__PURE__ */ jsxDEV4(Text4, {
|
|
475
559
|
color: colors.textDim,
|
|
476
560
|
children: [
|
|
477
561
|
" ",
|
|
@@ -479,7 +563,7 @@ function Breadcrumb({ path }) {
|
|
|
479
563
|
" "
|
|
480
564
|
]
|
|
481
565
|
}, undefined, true, undefined, this),
|
|
482
|
-
/* @__PURE__ */
|
|
566
|
+
/* @__PURE__ */ jsxDEV4(Text4, {
|
|
483
567
|
color: index === path.length - 1 ? colors.primary : colors.textDim,
|
|
484
568
|
bold: index === path.length - 1,
|
|
485
569
|
children: segment
|
|
@@ -490,39 +574,39 @@ function Breadcrumb({ path }) {
|
|
|
490
574
|
}
|
|
491
575
|
|
|
492
576
|
// src/components/layout/Layout.tsx
|
|
493
|
-
import { jsxDEV as
|
|
577
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
494
578
|
function Layout({
|
|
495
579
|
children,
|
|
496
580
|
breadcrumb = ["Main"],
|
|
497
581
|
showFooter = true
|
|
498
582
|
}) {
|
|
499
583
|
const { columns } = useTerminalSize();
|
|
500
|
-
return /* @__PURE__ */
|
|
584
|
+
return /* @__PURE__ */ jsxDEV5(Box5, {
|
|
501
585
|
flexDirection: "column",
|
|
502
586
|
width: columns,
|
|
503
587
|
padding: 1,
|
|
504
588
|
children: [
|
|
505
|
-
/* @__PURE__ */
|
|
506
|
-
breadcrumb.length > 1 && /* @__PURE__ */
|
|
589
|
+
/* @__PURE__ */ jsxDEV5(Header, {}, undefined, false, undefined, this),
|
|
590
|
+
breadcrumb.length > 1 && /* @__PURE__ */ jsxDEV5(Box5, {
|
|
507
591
|
marginBottom: 1,
|
|
508
592
|
marginLeft: 1,
|
|
509
|
-
children: /* @__PURE__ */
|
|
593
|
+
children: /* @__PURE__ */ jsxDEV5(Breadcrumb, {
|
|
510
594
|
path: breadcrumb
|
|
511
595
|
}, undefined, false, undefined, this)
|
|
512
596
|
}, undefined, false, undefined, this),
|
|
513
|
-
/* @__PURE__ */
|
|
597
|
+
/* @__PURE__ */ jsxDEV5(Box5, {
|
|
514
598
|
flexDirection: "column",
|
|
515
599
|
flexGrow: 1,
|
|
516
600
|
children
|
|
517
601
|
}, undefined, false, undefined, this),
|
|
518
|
-
showFooter && /* @__PURE__ */
|
|
602
|
+
showFooter && /* @__PURE__ */ jsxDEV5(Footer, {}, undefined, false, undefined, this)
|
|
519
603
|
]
|
|
520
604
|
}, undefined, true, undefined, this);
|
|
521
605
|
}
|
|
522
606
|
|
|
523
607
|
// src/components/layout/Panel.tsx
|
|
524
|
-
import { Box as
|
|
525
|
-
import { jsxDEV as
|
|
608
|
+
import { Box as Box6, Text as Text5 } from "ink";
|
|
609
|
+
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
526
610
|
function Panel({
|
|
527
611
|
title,
|
|
528
612
|
children,
|
|
@@ -530,7 +614,7 @@ function Panel({
|
|
|
530
614
|
flexGrow,
|
|
531
615
|
borderColor = colors.border
|
|
532
616
|
}) {
|
|
533
|
-
return /* @__PURE__ */
|
|
617
|
+
return /* @__PURE__ */ jsxDEV6(Box6, {
|
|
534
618
|
flexDirection: "column",
|
|
535
619
|
width,
|
|
536
620
|
flexGrow,
|
|
@@ -538,9 +622,9 @@ function Panel({
|
|
|
538
622
|
borderColor,
|
|
539
623
|
paddingX: 1,
|
|
540
624
|
children: [
|
|
541
|
-
title && /* @__PURE__ */
|
|
625
|
+
title && /* @__PURE__ */ jsxDEV6(Box6, {
|
|
542
626
|
marginBottom: 1,
|
|
543
|
-
children: /* @__PURE__ */
|
|
627
|
+
children: /* @__PURE__ */ jsxDEV6(Text5, {
|
|
544
628
|
bold: true,
|
|
545
629
|
color: colors.primary,
|
|
546
630
|
children: title
|
|
@@ -551,34 +635,144 @@ function Panel({
|
|
|
551
635
|
}, undefined, true, undefined, this);
|
|
552
636
|
}
|
|
553
637
|
|
|
554
|
-
// src/components/
|
|
638
|
+
// src/components/PrerequisiteError.tsx
|
|
639
|
+
import { Box as Box7, Text as Text6, useInput } from "ink";
|
|
640
|
+
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
641
|
+
function PrerequisiteError({ missing, onExit }) {
|
|
642
|
+
useInput(() => onExit());
|
|
643
|
+
return /* @__PURE__ */ jsxDEV7(Layout, {
|
|
644
|
+
breadcrumb: ["Error"],
|
|
645
|
+
children: /* @__PURE__ */ jsxDEV7(Panel, {
|
|
646
|
+
title: "Missing Prerequisites",
|
|
647
|
+
borderColor: colors.error,
|
|
648
|
+
children: [
|
|
649
|
+
/* @__PURE__ */ jsxDEV7(Text6, {
|
|
650
|
+
color: colors.error,
|
|
651
|
+
children: "Required tools are not installed:"
|
|
652
|
+
}, undefined, false, undefined, this),
|
|
653
|
+
/* @__PURE__ */ jsxDEV7(Box7, {
|
|
654
|
+
flexDirection: "column",
|
|
655
|
+
marginTop: 1,
|
|
656
|
+
children: missing.map((dep) => /* @__PURE__ */ jsxDEV7(Box7, {
|
|
657
|
+
children: [
|
|
658
|
+
/* @__PURE__ */ jsxDEV7(Text6, {
|
|
659
|
+
color: colors.warning,
|
|
660
|
+
children: [
|
|
661
|
+
"• ",
|
|
662
|
+
dep.name
|
|
663
|
+
]
|
|
664
|
+
}, undefined, true, undefined, this),
|
|
665
|
+
/* @__PURE__ */ jsxDEV7(Text6, {
|
|
666
|
+
dimColor: true,
|
|
667
|
+
children: [
|
|
668
|
+
" — Install: ",
|
|
669
|
+
dep.install
|
|
670
|
+
]
|
|
671
|
+
}, undefined, true, undefined, this)
|
|
672
|
+
]
|
|
673
|
+
}, dep.name, true, undefined, this))
|
|
674
|
+
}, undefined, false, undefined, this),
|
|
675
|
+
/* @__PURE__ */ jsxDEV7(Box7, {
|
|
676
|
+
marginTop: 1,
|
|
677
|
+
children: /* @__PURE__ */ jsxDEV7(Text6, {
|
|
678
|
+
dimColor: true,
|
|
679
|
+
children: "Press any key to exit..."
|
|
680
|
+
}, undefined, false, undefined, this)
|
|
681
|
+
}, undefined, false, undefined, this)
|
|
682
|
+
]
|
|
683
|
+
}, undefined, true, undefined, this)
|
|
684
|
+
}, undefined, false, undefined, this);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/components/menus/MainMenu.tsx
|
|
688
|
+
import { useApp } from "ink";
|
|
689
|
+
|
|
690
|
+
// src/components/ui/VimSelect.tsx
|
|
691
|
+
import { useState as useState3 } from "react";
|
|
555
692
|
import { Box as Box8, Text as Text7, useInput as useInput2 } from "ink";
|
|
556
693
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
694
|
+
function VimSelect({ options, onChange, isDisabled = false }) {
|
|
695
|
+
const [index, setIndex] = useState3(0);
|
|
696
|
+
useInput2((input, key) => {
|
|
697
|
+
if (isDisabled)
|
|
698
|
+
return;
|
|
699
|
+
if (input === "j" || key.downArrow) {
|
|
700
|
+
setIndex((i) => i < options.length - 1 ? i + 1 : i);
|
|
701
|
+
}
|
|
702
|
+
if (input === "k" || key.upArrow) {
|
|
703
|
+
setIndex((i) => i > 0 ? i - 1 : i);
|
|
704
|
+
}
|
|
705
|
+
if (input === "l" || key.return) {
|
|
706
|
+
onChange(options[index].value);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
return /* @__PURE__ */ jsxDEV8(Box8, {
|
|
710
|
+
flexDirection: "column",
|
|
711
|
+
children: options.map((opt, i) => /* @__PURE__ */ jsxDEV8(Box8, {
|
|
712
|
+
children: /* @__PURE__ */ jsxDEV8(Text7, {
|
|
713
|
+
color: i === index ? colors.primary : undefined,
|
|
714
|
+
children: [
|
|
715
|
+
i === index ? "❯" : " ",
|
|
716
|
+
" ",
|
|
717
|
+
opt.label
|
|
718
|
+
]
|
|
719
|
+
}, undefined, true, undefined, this)
|
|
720
|
+
}, opt.value, false, undefined, this))
|
|
721
|
+
}, undefined, false, undefined, this);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/components/menus/MainMenu.tsx
|
|
725
|
+
import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
|
|
726
|
+
function MainMenu({ onSelect }) {
|
|
727
|
+
const { exit } = useApp();
|
|
728
|
+
return /* @__PURE__ */ jsxDEV9(Panel, {
|
|
729
|
+
title: "Main Menu",
|
|
730
|
+
children: /* @__PURE__ */ jsxDEV9(VimSelect, {
|
|
731
|
+
options: [
|
|
732
|
+
{ label: "Config Manager", value: "config" },
|
|
733
|
+
{ label: "Package Sync", value: "packages" },
|
|
734
|
+
{ label: "Set Theme", value: "themes" },
|
|
735
|
+
{ label: "Exit", value: "exit" }
|
|
736
|
+
],
|
|
737
|
+
onChange: (value) => {
|
|
738
|
+
if (value === "exit") {
|
|
739
|
+
exit();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
onSelect(value);
|
|
743
|
+
}
|
|
744
|
+
}, undefined, false, undefined, this)
|
|
745
|
+
}, undefined, false, undefined, this);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/components/CommandOutput.tsx
|
|
749
|
+
import { Box as Box9, Text as Text8, useInput as useInput3 } from "ink";
|
|
750
|
+
import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
|
|
557
751
|
function CommandOutput({
|
|
558
752
|
title,
|
|
559
753
|
output,
|
|
560
754
|
success = true,
|
|
561
755
|
onDismiss
|
|
562
756
|
}) {
|
|
563
|
-
|
|
757
|
+
useInput3(() => {
|
|
564
758
|
onDismiss();
|
|
565
759
|
});
|
|
566
|
-
return /* @__PURE__ */
|
|
760
|
+
return /* @__PURE__ */ jsxDEV10(Panel, {
|
|
567
761
|
title,
|
|
568
762
|
borderColor: success ? colors.success : colors.error,
|
|
569
763
|
children: [
|
|
570
|
-
output && /* @__PURE__ */
|
|
764
|
+
output && /* @__PURE__ */ jsxDEV10(Box9, {
|
|
571
765
|
flexDirection: "column",
|
|
572
766
|
marginBottom: 1,
|
|
573
|
-
children: /* @__PURE__ */
|
|
767
|
+
children: /* @__PURE__ */ jsxDEV10(Text8, {
|
|
574
768
|
children: output
|
|
575
769
|
}, undefined, false, undefined, this)
|
|
576
770
|
}, undefined, false, undefined, this),
|
|
577
|
-
/* @__PURE__ */
|
|
771
|
+
/* @__PURE__ */ jsxDEV10(Text8, {
|
|
578
772
|
color: success ? colors.success : colors.error,
|
|
579
773
|
children: success ? "Done" : "Failed"
|
|
580
774
|
}, undefined, false, undefined, this),
|
|
581
|
-
/* @__PURE__ */
|
|
775
|
+
/* @__PURE__ */ jsxDEV10(Text8, {
|
|
582
776
|
dimColor: true,
|
|
583
777
|
children: "Press any key to continue..."
|
|
584
778
|
}, undefined, false, undefined, this)
|
|
@@ -586,122 +780,61 @@ function CommandOutput({
|
|
|
586
780
|
}, undefined, true, undefined, this);
|
|
587
781
|
}
|
|
588
782
|
|
|
589
|
-
// src/components/
|
|
590
|
-
import {
|
|
591
|
-
import { jsxDEV as
|
|
592
|
-
function
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
if (theme.isLightMode)
|
|
599
|
-
indicators.push("light");
|
|
600
|
-
const indicatorText = indicators.length > 0 ? ` [${indicators.join(" ")}]` : "";
|
|
601
|
-
return /* @__PURE__ */ jsxDEV9(Box9, {
|
|
602
|
-
flexDirection: "column",
|
|
603
|
-
width,
|
|
604
|
-
borderStyle: borderStyles.panel,
|
|
605
|
-
borderColor,
|
|
606
|
-
paddingX: 1,
|
|
607
|
-
children: /* @__PURE__ */ jsxDEV9(Box9, {
|
|
608
|
-
children: [
|
|
609
|
-
/* @__PURE__ */ jsxDEV9(Text8, {
|
|
610
|
-
color: isSelected ? colors.accent : colors.primaryDim,
|
|
611
|
-
children: isSelected ? "● " : " "
|
|
612
|
-
}, undefined, false, undefined, this),
|
|
613
|
-
/* @__PURE__ */ jsxDEV9(Text8, {
|
|
614
|
-
color: nameColor,
|
|
615
|
-
bold: true,
|
|
616
|
-
wrap: "truncate",
|
|
617
|
-
children: theme.name
|
|
618
|
-
}, undefined, false, undefined, this),
|
|
619
|
-
/* @__PURE__ */ jsxDEV9(Text8, {
|
|
620
|
-
color: colors.primaryDim,
|
|
621
|
-
children: indicatorText
|
|
622
|
-
}, undefined, false, undefined, this)
|
|
623
|
-
]
|
|
624
|
-
}, undefined, true, undefined, this)
|
|
783
|
+
// src/components/LoadingPanel.tsx
|
|
784
|
+
import { Spinner } from "@inkjs/ui";
|
|
785
|
+
import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
|
|
786
|
+
function LoadingPanel({ title, label = "Processing..." }) {
|
|
787
|
+
return /* @__PURE__ */ jsxDEV11(Panel, {
|
|
788
|
+
title,
|
|
789
|
+
children: /* @__PURE__ */ jsxDEV11(Spinner, {
|
|
790
|
+
label
|
|
791
|
+
}, undefined, false, undefined, this)
|
|
625
792
|
}, undefined, false, undefined, this);
|
|
626
793
|
}
|
|
627
794
|
|
|
628
|
-
// src/
|
|
629
|
-
import {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const [, key, value] = match;
|
|
645
|
-
if (indentLevel === 0) {
|
|
646
|
-
if (value) {
|
|
647
|
-
result[key] = value.replace(/^["']|["']$/g, "");
|
|
648
|
-
} else {
|
|
649
|
-
currentKey = key;
|
|
650
|
-
currentSection = {};
|
|
651
|
-
result[key] = currentSection;
|
|
652
|
-
}
|
|
653
|
-
} else if (currentSection) {
|
|
654
|
-
currentSection[key] = value.replace(/^["']|["']$/g, "");
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
return result;
|
|
659
|
-
}
|
|
660
|
-
async function parseThemeMetadata(themePath) {
|
|
661
|
-
const yamlPath = join3(themePath, "theme.yaml");
|
|
662
|
-
if (!existsSync2(yamlPath)) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
try {
|
|
666
|
-
const content = await readText(yamlPath);
|
|
667
|
-
const parsed = parseYaml(content);
|
|
668
|
-
return {
|
|
669
|
-
name: parsed.name || "",
|
|
670
|
-
author: parsed.author,
|
|
671
|
-
description: parsed.description,
|
|
672
|
-
version: parsed.version,
|
|
673
|
-
source: parsed.source,
|
|
674
|
-
colors: parsed.colors
|
|
675
|
-
};
|
|
676
|
-
} catch {
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
function parseThemeFiles(themePath) {
|
|
681
|
-
const entries = readdirSync2(themePath, { withFileTypes: true });
|
|
682
|
-
return entries.filter((e) => e.isFile() && !e.name.startsWith(".") && e.name !== "theme.yaml" && e.name !== "light.mode").map((e) => ({
|
|
683
|
-
name: e.name,
|
|
684
|
-
path: join3(themePath, e.name),
|
|
685
|
-
application: e.name.replace(/\.(conf|theme|lua|toml|css|json|ini)$/, "")
|
|
686
|
-
}));
|
|
687
|
-
}
|
|
688
|
-
async function parseTheme(themePath, themeName) {
|
|
689
|
-
const files = parseThemeFiles(themePath);
|
|
690
|
-
const metadata = await parseThemeMetadata(themePath);
|
|
795
|
+
// src/hooks/useMenuAction.ts
|
|
796
|
+
import { useState as useState4, useCallback } from "react";
|
|
797
|
+
function useMenuAction() {
|
|
798
|
+
const [state, setState] = useState4("menu");
|
|
799
|
+
const [output, setOutput] = useState4("");
|
|
800
|
+
const [success, setSuccess] = useState4(true);
|
|
801
|
+
const execute = useCallback(async (action) => {
|
|
802
|
+
setState("running");
|
|
803
|
+
const result = await action();
|
|
804
|
+
setOutput(result.output);
|
|
805
|
+
setSuccess(result.success);
|
|
806
|
+
setState("result");
|
|
807
|
+
}, []);
|
|
808
|
+
const reset = useCallback(() => {
|
|
809
|
+
setState("menu");
|
|
810
|
+
}, []);
|
|
691
811
|
return {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
812
|
+
state,
|
|
813
|
+
output,
|
|
814
|
+
success,
|
|
815
|
+
isRunning: state === "running",
|
|
816
|
+
isResult: state === "result",
|
|
817
|
+
execute,
|
|
818
|
+
reset
|
|
699
819
|
};
|
|
700
820
|
}
|
|
701
821
|
|
|
822
|
+
// src/hooks/useBackNavigation.ts
|
|
823
|
+
import { useInput as useInput4 } from "ink";
|
|
824
|
+
function useBackNavigation({
|
|
825
|
+
enabled = true,
|
|
826
|
+
onBack
|
|
827
|
+
}) {
|
|
828
|
+
useInput4((input, key) => {
|
|
829
|
+
if (enabled && (key.escape || key.leftArrow || input === "h")) {
|
|
830
|
+
onBack();
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
702
835
|
// src/cli/config-manager.ts
|
|
703
836
|
import { parseArgs } from "util";
|
|
704
|
-
import { readdirSync as
|
|
837
|
+
import { readdirSync as readdirSync2, existsSync as existsSync2, lstatSync as lstatSync2, readlinkSync as readlinkSync2 } from "fs";
|
|
705
838
|
var colors2 = {
|
|
706
839
|
red: "\x1B[0;31m",
|
|
707
840
|
green: "\x1B[0;32m",
|
|
@@ -717,7 +850,7 @@ async function checkStow() {
|
|
|
717
850
|
}
|
|
718
851
|
}
|
|
719
852
|
function listPackages() {
|
|
720
|
-
const entries =
|
|
853
|
+
const entries = readdirSync2(CONFIGS_DIR, { withFileTypes: true });
|
|
721
854
|
return entries.filter((e) => e.isDirectory()).map((e) => ({
|
|
722
855
|
name: e.name,
|
|
723
856
|
path: `${CONFIGS_DIR}/${e.name}`,
|
|
@@ -726,12 +859,12 @@ function listPackages() {
|
|
|
726
859
|
}
|
|
727
860
|
function checkPackageStowed(packageName) {
|
|
728
861
|
const packageDir = `${CONFIGS_DIR}/${packageName}`;
|
|
729
|
-
if (!
|
|
862
|
+
if (!existsSync2(packageDir))
|
|
730
863
|
return false;
|
|
731
|
-
const entries =
|
|
864
|
+
const entries = readdirSync2(packageDir, { withFileTypes: true });
|
|
732
865
|
for (const entry of entries) {
|
|
733
866
|
const targetPath = `${HOME_DIR}/${entry.name}`;
|
|
734
|
-
if (!
|
|
867
|
+
if (!existsSync2(targetPath))
|
|
735
868
|
return false;
|
|
736
869
|
try {
|
|
737
870
|
const stat = lstatSync2(targetPath);
|
|
@@ -996,11 +1129,179 @@ if (isMainModule) {
|
|
|
996
1129
|
main().catch(console.error);
|
|
997
1130
|
}
|
|
998
1131
|
|
|
1132
|
+
// src/components/menus/ConfigMenu.tsx
|
|
1133
|
+
import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
|
|
1134
|
+
function ConfigMenu({ onBack }) {
|
|
1135
|
+
const { state, output, success, isRunning, isResult, execute, reset } = useMenuAction();
|
|
1136
|
+
useBackNavigation({ enabled: state === "menu", onBack });
|
|
1137
|
+
const handleAction = async (action) => {
|
|
1138
|
+
if (action === "back") {
|
|
1139
|
+
onBack();
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
await execute(() => runConfigManager([action]));
|
|
1143
|
+
};
|
|
1144
|
+
if (isRunning) {
|
|
1145
|
+
return /* @__PURE__ */ jsxDEV12(LoadingPanel, {
|
|
1146
|
+
title: "Config Manager"
|
|
1147
|
+
}, undefined, false, undefined, this);
|
|
1148
|
+
}
|
|
1149
|
+
if (isResult) {
|
|
1150
|
+
return /* @__PURE__ */ jsxDEV12(CommandOutput, {
|
|
1151
|
+
title: "Config Manager",
|
|
1152
|
+
output,
|
|
1153
|
+
success,
|
|
1154
|
+
onDismiss: reset
|
|
1155
|
+
}, undefined, false, undefined, this);
|
|
1156
|
+
}
|
|
1157
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
1158
|
+
title: "Config Manager",
|
|
1159
|
+
children: /* @__PURE__ */ jsxDEV12(VimSelect, {
|
|
1160
|
+
options: [
|
|
1161
|
+
{ label: "Stow all packages", value: "stow-all" },
|
|
1162
|
+
{ label: "Unstow all packages", value: "unstow-all" },
|
|
1163
|
+
{ label: "Check status", value: "status" },
|
|
1164
|
+
{ label: "List packages", value: "list" },
|
|
1165
|
+
{ label: "Back", value: "back" }
|
|
1166
|
+
],
|
|
1167
|
+
onChange: handleAction
|
|
1168
|
+
}, undefined, false, undefined, this)
|
|
1169
|
+
}, undefined, false, undefined, this);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// src/components/menus/PackageMenu.tsx
|
|
1173
|
+
import { useState as useState6, useCallback as useCallback2, useMemo as useMemo2, useRef } from "react";
|
|
1174
|
+
import { Box as Box12, Text as Text11, useInput as useInput7 } from "ink";
|
|
1175
|
+
|
|
1176
|
+
// src/components/ScrollableLog.tsx
|
|
1177
|
+
import { useState as useState5, useEffect as useEffect3, useMemo } from "react";
|
|
1178
|
+
import { Box as Box10, Text as Text9, useInput as useInput5 } from "ink";
|
|
1179
|
+
import { jsxDEV as jsxDEV13 } from "react/jsx-dev-runtime";
|
|
1180
|
+
function ScrollableLog({
|
|
1181
|
+
lines,
|
|
1182
|
+
maxHeight,
|
|
1183
|
+
autoScroll = true,
|
|
1184
|
+
showScrollHint = true
|
|
1185
|
+
}) {
|
|
1186
|
+
const { rows } = useTerminalSize();
|
|
1187
|
+
const visibleLines = maxHeight || Math.max(5, rows - 12);
|
|
1188
|
+
const [scrollOffset, setScrollOffset] = useState5(0);
|
|
1189
|
+
const [isAutoScrolling, setIsAutoScrolling] = useState5(autoScroll);
|
|
1190
|
+
const totalLines = lines.length;
|
|
1191
|
+
const maxOffset = Math.max(0, totalLines - visibleLines);
|
|
1192
|
+
useEffect3(() => {
|
|
1193
|
+
if (isAutoScrolling) {
|
|
1194
|
+
setScrollOffset(maxOffset);
|
|
1195
|
+
}
|
|
1196
|
+
}, [totalLines, maxOffset, isAutoScrolling]);
|
|
1197
|
+
useInput5((input, key) => {
|
|
1198
|
+
if (key.downArrow || input === "j") {
|
|
1199
|
+
setIsAutoScrolling(false);
|
|
1200
|
+
setScrollOffset((prev) => Math.min(prev + 1, maxOffset));
|
|
1201
|
+
}
|
|
1202
|
+
if (key.upArrow || input === "k") {
|
|
1203
|
+
setIsAutoScrolling(false);
|
|
1204
|
+
setScrollOffset((prev) => Math.max(prev - 1, 0));
|
|
1205
|
+
}
|
|
1206
|
+
if (input === "G") {
|
|
1207
|
+
setIsAutoScrolling(true);
|
|
1208
|
+
setScrollOffset(maxOffset);
|
|
1209
|
+
}
|
|
1210
|
+
if (input === "g") {
|
|
1211
|
+
setIsAutoScrolling(false);
|
|
1212
|
+
setScrollOffset(0);
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
const visibleContent = useMemo(() => {
|
|
1216
|
+
return lines.slice(scrollOffset, scrollOffset + visibleLines);
|
|
1217
|
+
}, [lines, scrollOffset, visibleLines]);
|
|
1218
|
+
const showScrollUp = scrollOffset > 0;
|
|
1219
|
+
const showScrollDown = scrollOffset < maxOffset;
|
|
1220
|
+
return /* @__PURE__ */ jsxDEV13(Box10, {
|
|
1221
|
+
flexDirection: "column",
|
|
1222
|
+
children: [
|
|
1223
|
+
showScrollHint && showScrollUp && /* @__PURE__ */ jsxDEV13(Text9, {
|
|
1224
|
+
dimColor: true,
|
|
1225
|
+
children: [
|
|
1226
|
+
" ↑ ",
|
|
1227
|
+
scrollOffset,
|
|
1228
|
+
" more line",
|
|
1229
|
+
scrollOffset !== 1 ? "s" : ""
|
|
1230
|
+
]
|
|
1231
|
+
}, undefined, true, undefined, this),
|
|
1232
|
+
/* @__PURE__ */ jsxDEV13(Box10, {
|
|
1233
|
+
flexDirection: "column",
|
|
1234
|
+
height: visibleLines,
|
|
1235
|
+
overflow: "hidden",
|
|
1236
|
+
children: visibleContent.map((line, i) => /* @__PURE__ */ jsxDEV13(Text9, {
|
|
1237
|
+
children: line
|
|
1238
|
+
}, scrollOffset + i, false, undefined, this))
|
|
1239
|
+
}, undefined, false, undefined, this),
|
|
1240
|
+
showScrollHint && showScrollDown && /* @__PURE__ */ jsxDEV13(Text9, {
|
|
1241
|
+
dimColor: true,
|
|
1242
|
+
children: [
|
|
1243
|
+
" ↓ ",
|
|
1244
|
+
maxOffset - scrollOffset,
|
|
1245
|
+
" more line",
|
|
1246
|
+
maxOffset - scrollOffset !== 1 ? "s" : ""
|
|
1247
|
+
]
|
|
1248
|
+
}, undefined, true, undefined, this),
|
|
1249
|
+
showScrollHint && totalLines > visibleLines && /* @__PURE__ */ jsxDEV13(Text9, {
|
|
1250
|
+
dimColor: true,
|
|
1251
|
+
children: [
|
|
1252
|
+
"j/k scroll • g top • G bottom ",
|
|
1253
|
+
isAutoScrolling ? "(auto-scroll)" : ""
|
|
1254
|
+
]
|
|
1255
|
+
}, undefined, true, undefined, this)
|
|
1256
|
+
]
|
|
1257
|
+
}, undefined, true, undefined, this);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/components/PromptInput.tsx
|
|
1261
|
+
import { Box as Box11, Text as Text10, useInput as useInput6 } from "ink";
|
|
1262
|
+
import { jsxDEV as jsxDEV14 } from "react/jsx-dev-runtime";
|
|
1263
|
+
function PromptInput({
|
|
1264
|
+
question,
|
|
1265
|
+
options = ["y", "n"],
|
|
1266
|
+
onAnswer
|
|
1267
|
+
}) {
|
|
1268
|
+
useInput6((input) => {
|
|
1269
|
+
const lower = input.toLowerCase();
|
|
1270
|
+
if (options.includes(lower)) {
|
|
1271
|
+
onAnswer(lower);
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
return /* @__PURE__ */ jsxDEV14(Box11, {
|
|
1275
|
+
marginTop: 1,
|
|
1276
|
+
borderStyle: "single",
|
|
1277
|
+
borderColor: colors.accent,
|
|
1278
|
+
paddingX: 1,
|
|
1279
|
+
children: /* @__PURE__ */ jsxDEV14(Text10, {
|
|
1280
|
+
children: [
|
|
1281
|
+
question,
|
|
1282
|
+
" ",
|
|
1283
|
+
/* @__PURE__ */ jsxDEV14(Text10, {
|
|
1284
|
+
color: colors.accent,
|
|
1285
|
+
children: [
|
|
1286
|
+
"[",
|
|
1287
|
+
options.join("/"),
|
|
1288
|
+
"]"
|
|
1289
|
+
]
|
|
1290
|
+
}, undefined, true, undefined, this),
|
|
1291
|
+
/* @__PURE__ */ jsxDEV14(Text10, {
|
|
1292
|
+
dimColor: true,
|
|
1293
|
+
children: ": "
|
|
1294
|
+
}, undefined, false, undefined, this)
|
|
1295
|
+
]
|
|
1296
|
+
}, undefined, true, undefined, this)
|
|
1297
|
+
}, undefined, false, undefined, this);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
999
1300
|
// src/cli/pkg-sync.ts
|
|
1000
1301
|
import { parseArgs as parseArgs2 } from "util";
|
|
1001
1302
|
|
|
1002
1303
|
// src/lib/config.ts
|
|
1003
|
-
import { existsSync as
|
|
1304
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1004
1305
|
var DEFAULT_CONFIG = {
|
|
1005
1306
|
config: {
|
|
1006
1307
|
purge: false,
|
|
@@ -1015,7 +1316,7 @@ var DEFAULT_CONFIG = {
|
|
|
1015
1316
|
async function loadPkgConfig(path) {
|
|
1016
1317
|
await ensureConfigDir();
|
|
1017
1318
|
const configPath = path || PKG_CONFIG_PATH;
|
|
1018
|
-
if (!
|
|
1319
|
+
if (!existsSync3(configPath)) {
|
|
1019
1320
|
await savePkgConfig(DEFAULT_CONFIG, configPath);
|
|
1020
1321
|
return DEFAULT_CONFIG;
|
|
1021
1322
|
}
|
|
@@ -1027,7 +1328,7 @@ async function savePkgConfig(config, path) {
|
|
|
1027
1328
|
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
1028
1329
|
}
|
|
1029
1330
|
async function loadPkgLock() {
|
|
1030
|
-
if (!
|
|
1331
|
+
if (!existsSync3(PKG_LOCK_PATH)) {
|
|
1031
1332
|
return null;
|
|
1032
1333
|
}
|
|
1033
1334
|
return readJson(PKG_LOCK_PATH);
|
|
@@ -1203,6 +1504,15 @@ var colors3 = {
|
|
|
1203
1504
|
bold: "\x1B[1m",
|
|
1204
1505
|
reset: "\x1B[0m"
|
|
1205
1506
|
};
|
|
1507
|
+
async function runCommand(command, callbacks, cwd, needsTTY = false) {
|
|
1508
|
+
if (callbacks) {
|
|
1509
|
+
if (needsTTY) {
|
|
1510
|
+
return execStreamingWithTTY(command, callbacks.onLog, cwd);
|
|
1511
|
+
}
|
|
1512
|
+
return execStreaming(command, callbacks.onLog, cwd);
|
|
1513
|
+
}
|
|
1514
|
+
return execLive(command, cwd);
|
|
1515
|
+
}
|
|
1206
1516
|
async function checkDependencies() {
|
|
1207
1517
|
if (!await commandExists("brew")) {
|
|
1208
1518
|
console.error(`${colors3.red}Error: Homebrew not installed${colors3.reset}`);
|
|
@@ -1238,34 +1548,35 @@ async function getOutdatedMas() {
|
|
|
1238
1548
|
return null;
|
|
1239
1549
|
}).filter((app) => app !== null);
|
|
1240
1550
|
}
|
|
1241
|
-
async function upgradeWithVerification() {
|
|
1551
|
+
async function upgradeWithVerification(cb = null) {
|
|
1552
|
+
const log = cb?.onLog ?? console.log;
|
|
1242
1553
|
const result = {
|
|
1243
1554
|
attempted: [],
|
|
1244
1555
|
succeeded: [],
|
|
1245
1556
|
failed: [],
|
|
1246
1557
|
stillOutdated: []
|
|
1247
1558
|
};
|
|
1248
|
-
|
|
1559
|
+
log(`
|
|
1249
1560
|
${colors3.cyan}=== Checking for updates ===${colors3.reset}
|
|
1250
1561
|
`);
|
|
1251
|
-
await
|
|
1562
|
+
await runCommand(["brew", "update"], cb);
|
|
1252
1563
|
const beforeUpgrade = await getOutdatedPackages();
|
|
1253
1564
|
result.attempted = beforeUpgrade.map((p) => p.name);
|
|
1254
1565
|
if (beforeUpgrade.length === 0) {
|
|
1255
|
-
|
|
1566
|
+
log(`
|
|
1256
1567
|
${colors3.green}All brew packages are up to date${colors3.reset}`);
|
|
1257
1568
|
} else {
|
|
1258
|
-
|
|
1569
|
+
log(`
|
|
1259
1570
|
${colors3.yellow}Found ${beforeUpgrade.length} outdated packages${colors3.reset}
|
|
1260
1571
|
`);
|
|
1261
|
-
|
|
1572
|
+
log(`${colors3.cyan}=== Upgrading formulas ===${colors3.reset}
|
|
1262
1573
|
`);
|
|
1263
|
-
await
|
|
1264
|
-
|
|
1574
|
+
await runCommand(["brew", "upgrade", "--formula"], cb);
|
|
1575
|
+
log(`
|
|
1265
1576
|
${colors3.cyan}=== Upgrading casks ===${colors3.reset}
|
|
1266
1577
|
`);
|
|
1267
|
-
await
|
|
1268
|
-
|
|
1578
|
+
await runCommand(["brew", "upgrade", "--cask", "--greedy"], cb);
|
|
1579
|
+
log(`
|
|
1269
1580
|
${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
|
|
1270
1581
|
`);
|
|
1271
1582
|
const afterUpgrade = await getOutdatedPackages();
|
|
@@ -1278,13 +1589,13 @@ ${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
|
|
|
1278
1589
|
}
|
|
1279
1590
|
}
|
|
1280
1591
|
if (result.stillOutdated.length > 0) {
|
|
1281
|
-
|
|
1592
|
+
log(`${colors3.yellow}${result.stillOutdated.length} packages still outdated, retrying individually...${colors3.reset}
|
|
1282
1593
|
`);
|
|
1283
1594
|
for (const pkgName of [...result.stillOutdated]) {
|
|
1284
1595
|
const pkg = afterUpgrade.find((p) => p.name === pkgName);
|
|
1285
1596
|
if (!pkg)
|
|
1286
1597
|
continue;
|
|
1287
|
-
|
|
1598
|
+
log(` Retrying ${colors3.blue}${pkgName}${colors3.reset}...`);
|
|
1288
1599
|
const upgradeCmd = pkg.type === "cask" ? ["brew", "upgrade", "--cask", pkgName] : ["brew", "upgrade", pkgName];
|
|
1289
1600
|
const retryResult = await exec(upgradeCmd);
|
|
1290
1601
|
const checkResult = await exec([
|
|
@@ -1298,11 +1609,11 @@ ${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
|
|
|
1298
1609
|
if (!stillOutdatedNow.includes(pkgName)) {
|
|
1299
1610
|
result.succeeded.push(pkgName);
|
|
1300
1611
|
result.stillOutdated = result.stillOutdated.filter((n) => n !== pkgName);
|
|
1301
|
-
|
|
1612
|
+
log(` ${colors3.green}✓ Success${colors3.reset}`);
|
|
1302
1613
|
} else {
|
|
1303
1614
|
result.failed.push(pkgName);
|
|
1304
1615
|
result.stillOutdated = result.stillOutdated.filter((n) => n !== pkgName);
|
|
1305
|
-
|
|
1616
|
+
log(` ${colors3.red}✗ Failed${colors3.reset} ${retryResult.stderr ? `(${retryResult.stderr.split(`
|
|
1306
1617
|
`)[0]})` : ""}`);
|
|
1307
1618
|
}
|
|
1308
1619
|
}
|
|
@@ -1311,74 +1622,77 @@ ${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
|
|
|
1311
1622
|
if (await commandExists("mas")) {
|
|
1312
1623
|
const masOutdated = await getOutdatedMas();
|
|
1313
1624
|
if (masOutdated.length > 0) {
|
|
1314
|
-
|
|
1625
|
+
log(`
|
|
1315
1626
|
${colors3.cyan}=== Upgrading Mac App Store apps ===${colors3.reset}
|
|
1316
1627
|
`);
|
|
1317
|
-
await
|
|
1628
|
+
await runCommand(["mas", "upgrade"], cb, undefined, true);
|
|
1318
1629
|
}
|
|
1319
1630
|
}
|
|
1320
|
-
|
|
1631
|
+
log(`
|
|
1321
1632
|
${colors3.cyan}=== Cleanup ===${colors3.reset}
|
|
1322
1633
|
`);
|
|
1323
|
-
await
|
|
1324
|
-
|
|
1634
|
+
await runCommand(["brew", "cleanup"], cb);
|
|
1635
|
+
log(`
|
|
1325
1636
|
${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
1326
1637
|
`);
|
|
1327
1638
|
const lock = await updateLockfile();
|
|
1328
1639
|
const lockTotal = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
1329
|
-
|
|
1640
|
+
log(` Locked ${lockTotal} packages`);
|
|
1330
1641
|
return result;
|
|
1331
1642
|
}
|
|
1332
|
-
async function upgradeInteractive() {
|
|
1333
|
-
console.log
|
|
1643
|
+
async function upgradeInteractive(cb = null) {
|
|
1644
|
+
const log = cb?.onLog ?? console.log;
|
|
1645
|
+
const askPrompt = cb?.onPrompt ?? (async (q) => (prompt(q) || "").trim().toLowerCase());
|
|
1646
|
+
log(`
|
|
1334
1647
|
${colors3.cyan}=== Checking for updates ===${colors3.reset}
|
|
1335
1648
|
`);
|
|
1336
|
-
await
|
|
1649
|
+
await runCommand(["brew", "update"], cb);
|
|
1337
1650
|
const outdated = await getOutdatedPackages();
|
|
1338
1651
|
if (outdated.length === 0) {
|
|
1339
|
-
|
|
1652
|
+
log(`
|
|
1340
1653
|
${colors3.green}All packages are up to date${colors3.reset}
|
|
1341
1654
|
`);
|
|
1342
1655
|
return;
|
|
1343
1656
|
}
|
|
1344
|
-
|
|
1657
|
+
log(`
|
|
1345
1658
|
${colors3.yellow}Found ${outdated.length} outdated packages${colors3.reset}
|
|
1346
1659
|
`);
|
|
1347
1660
|
for (const pkg of outdated) {
|
|
1348
|
-
const question = `Upgrade ${colors3.blue}${pkg.name}${colors3.reset} (${pkg.type})
|
|
1349
|
-
const answer = (
|
|
1661
|
+
const question = `Upgrade ${colors3.blue}${pkg.name}${colors3.reset} (${pkg.type})?`;
|
|
1662
|
+
const answer = await askPrompt(question, ["y", "n", "q"]);
|
|
1350
1663
|
if (answer === "q") {
|
|
1351
|
-
|
|
1664
|
+
log(`
|
|
1352
1665
|
${colors3.yellow}Upgrade cancelled${colors3.reset}`);
|
|
1353
1666
|
return;
|
|
1354
1667
|
}
|
|
1355
1668
|
if (answer === "y" || answer === "yes") {
|
|
1356
1669
|
const cmd = pkg.type === "cask" ? ["brew", "upgrade", "--cask", pkg.name] : ["brew", "upgrade", pkg.name];
|
|
1357
|
-
await
|
|
1670
|
+
await runCommand(cmd, cb);
|
|
1358
1671
|
}
|
|
1359
1672
|
}
|
|
1360
1673
|
const stillOutdated = await getOutdatedPackages();
|
|
1361
1674
|
if (stillOutdated.length > 0) {
|
|
1362
|
-
|
|
1675
|
+
log(`
|
|
1363
1676
|
${colors3.yellow}Still outdated: ${stillOutdated.map((p) => p.name).join(", ")}${colors3.reset}`);
|
|
1364
1677
|
} else {
|
|
1365
|
-
|
|
1678
|
+
log(`
|
|
1366
1679
|
${colors3.green}All selected packages upgraded successfully${colors3.reset}`);
|
|
1367
1680
|
}
|
|
1368
|
-
await
|
|
1369
|
-
|
|
1681
|
+
await runCommand(["brew", "cleanup"], cb);
|
|
1682
|
+
log(`
|
|
1370
1683
|
${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
1371
1684
|
`);
|
|
1372
1685
|
await updateLockfile();
|
|
1373
1686
|
}
|
|
1374
|
-
async function syncPackages(config) {
|
|
1687
|
+
async function syncPackages(config, cb = null) {
|
|
1688
|
+
const log = cb?.onLog ?? console.log;
|
|
1375
1689
|
if (config.config.autoUpdate) {
|
|
1376
|
-
|
|
1690
|
+
log(`
|
|
1377
1691
|
${colors3.cyan}=== Updating Homebrew ===${colors3.reset}
|
|
1378
1692
|
`);
|
|
1379
|
-
await
|
|
1693
|
+
await runCommand(["brew", "update"], cb);
|
|
1380
1694
|
}
|
|
1381
|
-
|
|
1695
|
+
log(`
|
|
1382
1696
|
${colors3.cyan}=== Installing taps ===${colors3.reset}
|
|
1383
1697
|
`);
|
|
1384
1698
|
const tappedResult = await exec(["brew", "tap"]);
|
|
@@ -1386,34 +1700,34 @@ ${colors3.cyan}=== Installing taps ===${colors3.reset}
|
|
|
1386
1700
|
`).filter(Boolean);
|
|
1387
1701
|
for (const tap of config.taps) {
|
|
1388
1702
|
if (!tapped.includes(tap)) {
|
|
1389
|
-
|
|
1390
|
-
await
|
|
1703
|
+
log(` Adding tap: ${colors3.blue}${tap}${colors3.reset}`);
|
|
1704
|
+
await runCommand(["brew", "tap", tap], cb);
|
|
1391
1705
|
}
|
|
1392
1706
|
}
|
|
1393
|
-
|
|
1707
|
+
log(`
|
|
1394
1708
|
${colors3.cyan}=== Installing packages ===${colors3.reset}
|
|
1395
1709
|
`);
|
|
1396
1710
|
const installedFormulas = (await exec(["brew", "list", "--formula"])).stdout.split(`
|
|
1397
1711
|
`).filter(Boolean);
|
|
1398
1712
|
for (const pkg of config.packages) {
|
|
1399
1713
|
if (!installedFormulas.includes(pkg)) {
|
|
1400
|
-
|
|
1401
|
-
await
|
|
1714
|
+
log(` Installing: ${colors3.blue}${pkg}${colors3.reset}`);
|
|
1715
|
+
await runCommand(["brew", "install", pkg], cb);
|
|
1402
1716
|
}
|
|
1403
1717
|
}
|
|
1404
|
-
|
|
1718
|
+
log(`
|
|
1405
1719
|
${colors3.cyan}=== Installing casks ===${colors3.reset}
|
|
1406
1720
|
`);
|
|
1407
1721
|
const installedCasks = (await exec(["brew", "list", "--cask"])).stdout.split(`
|
|
1408
1722
|
`).filter(Boolean);
|
|
1409
1723
|
for (const cask of config.casks) {
|
|
1410
1724
|
if (!installedCasks.includes(cask)) {
|
|
1411
|
-
|
|
1412
|
-
await
|
|
1725
|
+
log(` Installing: ${colors3.blue}${cask}${colors3.reset}`);
|
|
1726
|
+
await runCommand(["brew", "install", "--cask", cask], cb);
|
|
1413
1727
|
}
|
|
1414
1728
|
}
|
|
1415
1729
|
if (await commandExists("mas")) {
|
|
1416
|
-
|
|
1730
|
+
log(`
|
|
1417
1731
|
${colors3.cyan}=== Installing Mac App Store apps ===${colors3.reset}
|
|
1418
1732
|
`);
|
|
1419
1733
|
const masResult = await exec(["mas", "list"]);
|
|
@@ -1424,26 +1738,28 @@ ${colors3.cyan}=== Installing Mac App Store apps ===${colors3.reset}
|
|
|
1424
1738
|
});
|
|
1425
1739
|
for (const [name, id] of Object.entries(config.mas)) {
|
|
1426
1740
|
if (!installedMas.includes(id)) {
|
|
1427
|
-
|
|
1428
|
-
await
|
|
1741
|
+
log(` Installing: ${colors3.blue}${name}${colors3.reset}`);
|
|
1742
|
+
await runCommand(["mas", "install", String(id)], cb, undefined, true);
|
|
1429
1743
|
}
|
|
1430
1744
|
}
|
|
1431
1745
|
}
|
|
1432
1746
|
if (config.config.purge) {
|
|
1433
|
-
await purgeUnlisted(config, config.config.purgeInteractive);
|
|
1747
|
+
await purgeUnlisted(config, config.config.purgeInteractive, cb);
|
|
1434
1748
|
}
|
|
1435
|
-
|
|
1749
|
+
log(`
|
|
1436
1750
|
${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
1437
1751
|
`);
|
|
1438
1752
|
const lock = await updateLockfile();
|
|
1439
1753
|
const lockTotal = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
1440
|
-
|
|
1441
|
-
|
|
1754
|
+
log(` Locked ${lockTotal} packages`);
|
|
1755
|
+
log(`
|
|
1442
1756
|
${colors3.green}=== Sync complete ===${colors3.reset}
|
|
1443
1757
|
`);
|
|
1444
1758
|
}
|
|
1445
|
-
async function purgeUnlisted(config, interactive) {
|
|
1446
|
-
console.log
|
|
1759
|
+
async function purgeUnlisted(config, interactive, cb = null) {
|
|
1760
|
+
const log = cb?.onLog ?? console.log;
|
|
1761
|
+
const askPrompt = cb?.onPrompt ?? (async (q) => (prompt(q) || "").trim().toLowerCase());
|
|
1762
|
+
log(`
|
|
1447
1763
|
${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
|
|
1448
1764
|
`);
|
|
1449
1765
|
const installedFormulas = (await exec(["brew", "list", "--formula"])).stdout.split(`
|
|
@@ -1452,17 +1768,17 @@ ${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
|
|
|
1452
1768
|
if (!config.packages.includes(pkg)) {
|
|
1453
1769
|
const usesResult = await exec(["brew", "uses", "--installed", pkg]);
|
|
1454
1770
|
if (usesResult.stdout.trim()) {
|
|
1455
|
-
|
|
1771
|
+
log(` ${colors3.yellow}Skipping ${pkg} (has dependents)${colors3.reset}`);
|
|
1456
1772
|
continue;
|
|
1457
1773
|
}
|
|
1458
1774
|
if (interactive) {
|
|
1459
|
-
const answer = (
|
|
1775
|
+
const answer = await askPrompt(`Remove ${colors3.red}${pkg}${colors3.reset}?`, ["y", "n"]);
|
|
1460
1776
|
if (answer === "y") {
|
|
1461
|
-
await
|
|
1777
|
+
await runCommand(["brew", "uninstall", pkg], cb);
|
|
1462
1778
|
}
|
|
1463
1779
|
} else {
|
|
1464
|
-
|
|
1465
|
-
await
|
|
1780
|
+
log(` Removing: ${colors3.red}${pkg}${colors3.reset}`);
|
|
1781
|
+
await runCommand(["brew", "uninstall", pkg], cb);
|
|
1466
1782
|
}
|
|
1467
1783
|
}
|
|
1468
1784
|
}
|
|
@@ -1471,13 +1787,13 @@ ${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
|
|
|
1471
1787
|
for (const cask of installedCasks) {
|
|
1472
1788
|
if (!config.casks.includes(cask)) {
|
|
1473
1789
|
if (interactive) {
|
|
1474
|
-
const answer = (
|
|
1790
|
+
const answer = await askPrompt(`Remove cask ${colors3.red}${cask}${colors3.reset}?`, ["y", "n"]);
|
|
1475
1791
|
if (answer === "y") {
|
|
1476
|
-
await
|
|
1792
|
+
await runCommand(["brew", "uninstall", "--cask", cask], cb);
|
|
1477
1793
|
}
|
|
1478
1794
|
} else {
|
|
1479
|
-
|
|
1480
|
-
await
|
|
1795
|
+
log(` Removing cask: ${colors3.red}${cask}${colors3.reset}`);
|
|
1796
|
+
await runCommand(["brew", "uninstall", "--cask", cask], cb);
|
|
1481
1797
|
}
|
|
1482
1798
|
}
|
|
1483
1799
|
}
|
|
@@ -1495,22 +1811,22 @@ ${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
|
|
|
1495
1811
|
}
|
|
1496
1812
|
if (!configMasIds.includes(app.id)) {
|
|
1497
1813
|
if (interactive) {
|
|
1498
|
-
const answer = (
|
|
1814
|
+
const answer = await askPrompt(`Remove app ${colors3.red}${app.name}${colors3.reset}?`, ["y", "n"]);
|
|
1499
1815
|
if (answer === "y") {
|
|
1500
|
-
await
|
|
1816
|
+
await runCommand(["mas", "uninstall", String(app.id)], cb, undefined, true);
|
|
1501
1817
|
}
|
|
1502
1818
|
} else {
|
|
1503
|
-
|
|
1504
|
-
await
|
|
1819
|
+
log(` Removing app: ${colors3.red}${app.name}${colors3.reset}`);
|
|
1820
|
+
await runCommand(["mas", "uninstall", String(app.id)], cb, undefined, true);
|
|
1505
1821
|
}
|
|
1506
1822
|
}
|
|
1507
1823
|
}
|
|
1508
1824
|
}
|
|
1509
|
-
|
|
1825
|
+
log(`
|
|
1510
1826
|
${colors3.cyan}=== Cleaning up ===${colors3.reset}
|
|
1511
1827
|
`);
|
|
1512
|
-
await
|
|
1513
|
-
await
|
|
1828
|
+
await runCommand(["brew", "autoremove"], cb);
|
|
1829
|
+
await runCommand(["brew", "cleanup"], cb);
|
|
1514
1830
|
}
|
|
1515
1831
|
function printUsage2() {
|
|
1516
1832
|
console.log(`
|
|
@@ -1527,7 +1843,7 @@ Examples:
|
|
|
1527
1843
|
bun run pkg-sync --purge Sync and remove unlisted packages
|
|
1528
1844
|
`);
|
|
1529
1845
|
}
|
|
1530
|
-
async function
|
|
1846
|
+
async function runPkgSyncWithCallbacks(args, callbacks) {
|
|
1531
1847
|
const { values, positionals } = parseArgs2({
|
|
1532
1848
|
args,
|
|
1533
1849
|
options: {
|
|
@@ -1539,12 +1855,19 @@ async function runPkgSync(args) {
|
|
|
1539
1855
|
allowPositionals: true
|
|
1540
1856
|
});
|
|
1541
1857
|
try {
|
|
1542
|
-
await
|
|
1858
|
+
if (!await commandExists("brew")) {
|
|
1859
|
+
callbacks.onLog(`${colors3.red}Error: Homebrew not installed${colors3.reset}`);
|
|
1860
|
+
return { output: "Homebrew not installed", success: false };
|
|
1861
|
+
}
|
|
1543
1862
|
} catch {
|
|
1544
1863
|
return { output: "Homebrew not installed", success: false };
|
|
1545
1864
|
}
|
|
1865
|
+
if (values["upgrade-interactive"]) {
|
|
1866
|
+
await upgradeInteractive(callbacks);
|
|
1867
|
+
return { output: "Interactive upgrade complete", success: true };
|
|
1868
|
+
}
|
|
1546
1869
|
if (values["upgrade-only"]) {
|
|
1547
|
-
const result = await upgradeWithVerification();
|
|
1870
|
+
const result = await upgradeWithVerification(callbacks);
|
|
1548
1871
|
let output = `Upgrade complete
|
|
1549
1872
|
`;
|
|
1550
1873
|
if (result.succeeded.length > 0) {
|
|
@@ -1561,7 +1884,7 @@ async function runPkgSync(args) {
|
|
|
1561
1884
|
if (values.purge) {
|
|
1562
1885
|
config.config.purge = true;
|
|
1563
1886
|
}
|
|
1564
|
-
await syncPackages(config);
|
|
1887
|
+
await syncPackages(config, callbacks);
|
|
1565
1888
|
return { output: "Sync complete", success: true };
|
|
1566
1889
|
}
|
|
1567
1890
|
async function main2() {
|
|
@@ -1786,60 +2109,411 @@ if (isMainModule3) {
|
|
|
1786
2109
|
main3().catch(console.error);
|
|
1787
2110
|
}
|
|
1788
2111
|
|
|
1789
|
-
// src/
|
|
1790
|
-
import {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
async function listThemes() {
|
|
1803
|
-
await ensureConfigDir();
|
|
1804
|
-
if (!existsSync5(THEMES_DIR)) {
|
|
1805
|
-
return [];
|
|
1806
|
-
}
|
|
1807
|
-
const entries = readdirSync4(THEMES_DIR, { withFileTypes: true });
|
|
1808
|
-
const themes = [];
|
|
1809
|
-
for (const entry of entries) {
|
|
1810
|
-
if (entry.isDirectory()) {
|
|
1811
|
-
const themePath = join4(THEMES_DIR, entry.name);
|
|
1812
|
-
const theme = await parseTheme(themePath, entry.name);
|
|
1813
|
-
themes.push(theme);
|
|
2112
|
+
// src/components/menus/PackageMenu.tsx
|
|
2113
|
+
import { jsxDEV as jsxDEV15 } from "react/jsx-dev-runtime";
|
|
2114
|
+
function PackageMenu({ onBack }) {
|
|
2115
|
+
const [state, setState] = useState6("menu");
|
|
2116
|
+
const [lines, setLines] = useState6([]);
|
|
2117
|
+
const [output, setOutput] = useState6("");
|
|
2118
|
+
const [isStreamingOp, setIsStreamingOp] = useState6(true);
|
|
2119
|
+
const [pendingPrompt, setPendingPrompt] = useState6(null);
|
|
2120
|
+
const [success, setSuccess] = useState6(true);
|
|
2121
|
+
const isRunningRef = useRef(false);
|
|
2122
|
+
useInput7((input, key) => {
|
|
2123
|
+
if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
|
|
2124
|
+
onBack();
|
|
1814
2125
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
function clearDirectory(dir) {
|
|
1819
|
-
if (existsSync5(dir)) {
|
|
1820
|
-
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
1821
|
-
for (const entry of entries) {
|
|
1822
|
-
const fullPath = join4(dir, entry.name);
|
|
1823
|
-
if (entry.isSymbolicLink() || entry.isFile()) {
|
|
1824
|
-
unlinkSync(fullPath);
|
|
1825
|
-
} else if (entry.isDirectory()) {
|
|
1826
|
-
rmSync(fullPath, { recursive: true, force: true });
|
|
1827
|
-
}
|
|
2126
|
+
if (state === "result") {
|
|
2127
|
+
setState("menu");
|
|
2128
|
+
setLines([]);
|
|
1828
2129
|
}
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2130
|
+
});
|
|
2131
|
+
const callbacks = useMemo2(() => ({
|
|
2132
|
+
onLog: (line) => {
|
|
2133
|
+
setLines((prev) => [...prev, line]);
|
|
2134
|
+
},
|
|
2135
|
+
onPrompt: (question, options) => {
|
|
2136
|
+
return new Promise((resolve) => {
|
|
2137
|
+
setPendingPrompt({ question, options, resolve });
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
}), []);
|
|
2141
|
+
const handlePromptAnswer = useCallback2((answer) => {
|
|
2142
|
+
if (pendingPrompt) {
|
|
2143
|
+
setLines((prev) => [...prev, `> ${answer}`]);
|
|
2144
|
+
pendingPrompt.resolve(answer);
|
|
2145
|
+
setPendingPrompt(null);
|
|
2146
|
+
}
|
|
2147
|
+
}, [pendingPrompt]);
|
|
2148
|
+
const handleAction = async (action) => {
|
|
2149
|
+
if (action === "back") {
|
|
2150
|
+
onBack();
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
if (isRunningRef.current)
|
|
2154
|
+
return;
|
|
2155
|
+
isRunningRef.current = true;
|
|
2156
|
+
setState("running");
|
|
2157
|
+
setLines([]);
|
|
2158
|
+
setOutput("");
|
|
2159
|
+
setPendingPrompt(null);
|
|
2160
|
+
let result;
|
|
2161
|
+
switch (action) {
|
|
2162
|
+
case "sync":
|
|
2163
|
+
setIsStreamingOp(true);
|
|
2164
|
+
result = await runPkgSyncWithCallbacks([], callbacks);
|
|
2165
|
+
break;
|
|
2166
|
+
case "sync-purge":
|
|
2167
|
+
setIsStreamingOp(true);
|
|
2168
|
+
result = await runPkgSyncWithCallbacks(["--purge"], callbacks);
|
|
2169
|
+
break;
|
|
2170
|
+
case "upgrade":
|
|
2171
|
+
setIsStreamingOp(true);
|
|
2172
|
+
result = await runPkgSyncWithCallbacks(["--upgrade-only"], callbacks);
|
|
2173
|
+
break;
|
|
2174
|
+
case "upgrade-interactive":
|
|
2175
|
+
setIsStreamingOp(true);
|
|
2176
|
+
result = await runPkgSyncWithCallbacks(["--upgrade-interactive"], callbacks);
|
|
2177
|
+
break;
|
|
2178
|
+
case "lock-update":
|
|
2179
|
+
setIsStreamingOp(false);
|
|
2180
|
+
result = await runPkgLock(["update"]);
|
|
2181
|
+
setOutput(result.output);
|
|
2182
|
+
break;
|
|
2183
|
+
case "lock-status":
|
|
2184
|
+
setIsStreamingOp(false);
|
|
2185
|
+
result = await runPkgLock(["status"]);
|
|
2186
|
+
setOutput(result.output);
|
|
2187
|
+
break;
|
|
2188
|
+
default:
|
|
2189
|
+
setIsStreamingOp(false);
|
|
2190
|
+
result = { output: "Unknown action", success: false };
|
|
2191
|
+
setOutput(result.output);
|
|
2192
|
+
}
|
|
2193
|
+
setSuccess(result.success);
|
|
2194
|
+
setState("result");
|
|
2195
|
+
isRunningRef.current = false;
|
|
2196
|
+
};
|
|
2197
|
+
if (state === "running") {
|
|
2198
|
+
if (!isStreamingOp) {
|
|
2199
|
+
return /* @__PURE__ */ jsxDEV15(LoadingPanel, {
|
|
2200
|
+
title: "Package Sync"
|
|
2201
|
+
}, undefined, false, undefined, this);
|
|
2202
|
+
}
|
|
2203
|
+
return /* @__PURE__ */ jsxDEV15(Panel, {
|
|
2204
|
+
title: "Package Sync",
|
|
2205
|
+
children: [
|
|
2206
|
+
/* @__PURE__ */ jsxDEV15(ScrollableLog, {
|
|
2207
|
+
lines
|
|
2208
|
+
}, undefined, false, undefined, this),
|
|
2209
|
+
pendingPrompt && /* @__PURE__ */ jsxDEV15(PromptInput, {
|
|
2210
|
+
question: pendingPrompt.question,
|
|
2211
|
+
options: pendingPrompt.options,
|
|
2212
|
+
onAnswer: handlePromptAnswer
|
|
2213
|
+
}, undefined, false, undefined, this)
|
|
2214
|
+
]
|
|
2215
|
+
}, undefined, true, undefined, this);
|
|
2216
|
+
}
|
|
2217
|
+
if (state === "result") {
|
|
2218
|
+
if (!isStreamingOp) {
|
|
2219
|
+
return /* @__PURE__ */ jsxDEV15(CommandOutput, {
|
|
2220
|
+
title: "Package Sync",
|
|
2221
|
+
output,
|
|
2222
|
+
success,
|
|
2223
|
+
onDismiss: () => setState("menu")
|
|
2224
|
+
}, undefined, false, undefined, this);
|
|
2225
|
+
}
|
|
2226
|
+
return /* @__PURE__ */ jsxDEV15(Panel, {
|
|
2227
|
+
title: "Package Sync",
|
|
2228
|
+
borderColor: success ? colors.success : colors.error,
|
|
2229
|
+
children: [
|
|
2230
|
+
/* @__PURE__ */ jsxDEV15(ScrollableLog, {
|
|
2231
|
+
lines,
|
|
2232
|
+
autoScroll: false
|
|
2233
|
+
}, undefined, false, undefined, this),
|
|
2234
|
+
/* @__PURE__ */ jsxDEV15(Box12, {
|
|
2235
|
+
marginTop: 1,
|
|
2236
|
+
children: /* @__PURE__ */ jsxDEV15(Text11, {
|
|
2237
|
+
color: success ? colors.success : colors.error,
|
|
2238
|
+
children: success ? "Done" : "Failed"
|
|
2239
|
+
}, undefined, false, undefined, this)
|
|
2240
|
+
}, undefined, false, undefined, this),
|
|
2241
|
+
/* @__PURE__ */ jsxDEV15(Text11, {
|
|
2242
|
+
dimColor: true,
|
|
2243
|
+
children: "Press any key to continue..."
|
|
2244
|
+
}, undefined, false, undefined, this)
|
|
2245
|
+
]
|
|
2246
|
+
}, undefined, true, undefined, this);
|
|
2247
|
+
}
|
|
2248
|
+
return /* @__PURE__ */ jsxDEV15(Panel, {
|
|
2249
|
+
title: "Package Sync",
|
|
2250
|
+
children: /* @__PURE__ */ jsxDEV15(VimSelect, {
|
|
2251
|
+
options: [
|
|
2252
|
+
{ label: "Sync packages", value: "sync" },
|
|
2253
|
+
{ label: "Sync with purge", value: "sync-purge" },
|
|
2254
|
+
{ label: "Upgrade all (with verification)", value: "upgrade" },
|
|
2255
|
+
{ label: "Upgrade interactive", value: "upgrade-interactive" },
|
|
2256
|
+
{ label: "Update lockfile", value: "lock-update" },
|
|
2257
|
+
{ label: "Lockfile status", value: "lock-status" },
|
|
2258
|
+
{ label: "Back", value: "back" }
|
|
2259
|
+
],
|
|
2260
|
+
onChange: handleAction
|
|
2261
|
+
}, undefined, false, undefined, this)
|
|
2262
|
+
}, undefined, false, undefined, this);
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
// src/components/menus/ThemeMenu.tsx
|
|
2266
|
+
import { useState as useState8, useEffect as useEffect5, useMemo as useMemo4 } from "react";
|
|
2267
|
+
import { Box as Box14, Text as Text13 } from "ink";
|
|
2268
|
+
import { existsSync as existsSync6, readdirSync as readdirSync5 } from "fs";
|
|
2269
|
+
import { join as join5 } from "path";
|
|
2270
|
+
|
|
2271
|
+
// src/components/ThemeCard.tsx
|
|
2272
|
+
import { Box as Box13, Text as Text12 } from "ink";
|
|
2273
|
+
import { jsxDEV as jsxDEV16 } from "react/jsx-dev-runtime";
|
|
2274
|
+
function ThemeCard({ theme, isSelected, width }) {
|
|
2275
|
+
const borderColor = isSelected ? colors.accent : colors.border;
|
|
2276
|
+
const nameColor = isSelected ? colors.primary : colors.text;
|
|
2277
|
+
const indicators = [];
|
|
2278
|
+
if (theme.hasBackgrounds)
|
|
2279
|
+
indicators.push("bg");
|
|
2280
|
+
if (theme.isLightMode)
|
|
2281
|
+
indicators.push("light");
|
|
2282
|
+
const indicatorText = indicators.length > 0 ? ` [${indicators.join(" ")}]` : "";
|
|
2283
|
+
return /* @__PURE__ */ jsxDEV16(Box13, {
|
|
2284
|
+
flexDirection: "column",
|
|
2285
|
+
width,
|
|
2286
|
+
borderStyle: borderStyles.panel,
|
|
2287
|
+
borderColor,
|
|
2288
|
+
paddingX: 1,
|
|
2289
|
+
children: /* @__PURE__ */ jsxDEV16(Box13, {
|
|
2290
|
+
children: [
|
|
2291
|
+
/* @__PURE__ */ jsxDEV16(Text12, {
|
|
2292
|
+
color: isSelected ? colors.accent : colors.primaryDim,
|
|
2293
|
+
children: isSelected ? "● " : " "
|
|
2294
|
+
}, undefined, false, undefined, this),
|
|
2295
|
+
/* @__PURE__ */ jsxDEV16(Text12, {
|
|
2296
|
+
color: nameColor,
|
|
2297
|
+
bold: true,
|
|
2298
|
+
wrap: "truncate",
|
|
2299
|
+
children: theme.name
|
|
2300
|
+
}, undefined, false, undefined, this),
|
|
2301
|
+
/* @__PURE__ */ jsxDEV16(Text12, {
|
|
2302
|
+
color: colors.primaryDim,
|
|
2303
|
+
children: indicatorText
|
|
2304
|
+
}, undefined, false, undefined, this)
|
|
2305
|
+
]
|
|
2306
|
+
}, undefined, true, undefined, this)
|
|
2307
|
+
}, undefined, false, undefined, this);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
// src/hooks/useThemeGrid.ts
|
|
2311
|
+
import { useState as useState7, useEffect as useEffect4 } from "react";
|
|
2312
|
+
import { useInput as useInput8 } from "ink";
|
|
2313
|
+
function useThemeGrid({
|
|
2314
|
+
itemCount,
|
|
2315
|
+
cardHeight = 3,
|
|
2316
|
+
layoutOverhead = 20,
|
|
2317
|
+
minCardWidth = 28,
|
|
2318
|
+
onSelect,
|
|
2319
|
+
onBack,
|
|
2320
|
+
enabled = true
|
|
2321
|
+
}) {
|
|
2322
|
+
const { columns, rows } = useTerminalSize();
|
|
2323
|
+
const [selectedIndex, setSelectedIndex] = useState7(0);
|
|
2324
|
+
const [scrollOffset, setScrollOffset] = useState7(0);
|
|
2325
|
+
const availableWidth = columns - 6;
|
|
2326
|
+
const cardsPerRow = Math.max(1, Math.floor(availableWidth / minCardWidth));
|
|
2327
|
+
const cardWidth = Math.floor(availableWidth / cardsPerRow);
|
|
2328
|
+
const availableHeight = rows - layoutOverhead;
|
|
2329
|
+
const visibleRows = Math.max(1, Math.floor(availableHeight / cardHeight));
|
|
2330
|
+
const selectedRow = Math.floor(selectedIndex / cardsPerRow);
|
|
2331
|
+
const totalRows = Math.ceil(itemCount / cardsPerRow);
|
|
2332
|
+
useEffect4(() => {
|
|
2333
|
+
if (selectedRow < scrollOffset) {
|
|
2334
|
+
setScrollOffset(selectedRow);
|
|
2335
|
+
} else if (selectedRow >= scrollOffset + visibleRows) {
|
|
2336
|
+
setScrollOffset(selectedRow - visibleRows + 1);
|
|
2337
|
+
}
|
|
2338
|
+
}, [selectedRow, scrollOffset, visibleRows]);
|
|
2339
|
+
useInput8((input, key) => {
|
|
2340
|
+
if (!enabled)
|
|
2341
|
+
return;
|
|
2342
|
+
if (key.escape && onBack) {
|
|
2343
|
+
onBack();
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
if (key.rightArrow || input === "l") {
|
|
2347
|
+
if (selectedIndex < itemCount - 1) {
|
|
2348
|
+
setSelectedIndex((i) => i + 1);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
if (key.leftArrow || input === "h") {
|
|
2352
|
+
if (selectedIndex > 0) {
|
|
2353
|
+
setSelectedIndex((i) => i - 1);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
if (key.downArrow || input === "j") {
|
|
2357
|
+
const nextIndex = selectedIndex + cardsPerRow;
|
|
2358
|
+
if (nextIndex < itemCount) {
|
|
2359
|
+
setSelectedIndex(nextIndex);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
if (key.upArrow || input === "k") {
|
|
2363
|
+
const prevIndex = selectedIndex - cardsPerRow;
|
|
2364
|
+
if (prevIndex >= 0) {
|
|
2365
|
+
setSelectedIndex(prevIndex);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
if (key.return && onSelect) {
|
|
2369
|
+
onSelect(selectedIndex);
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
const visibleStartIndex = scrollOffset * cardsPerRow;
|
|
2373
|
+
const visibleEndIndex = (scrollOffset + visibleRows) * cardsPerRow;
|
|
2374
|
+
return {
|
|
2375
|
+
cardsPerRow,
|
|
2376
|
+
cardWidth,
|
|
2377
|
+
visibleRows,
|
|
2378
|
+
scrollOffset,
|
|
2379
|
+
selectedIndex,
|
|
2380
|
+
visibleStartIndex,
|
|
2381
|
+
visibleEndIndex,
|
|
2382
|
+
showScrollUp: scrollOffset > 0,
|
|
2383
|
+
showScrollDown: scrollOffset + visibleRows < totalRows,
|
|
2384
|
+
gridHeight: visibleRows * cardHeight,
|
|
2385
|
+
totalRows
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
// src/lib/theme-parser.ts
|
|
2390
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3 } from "fs";
|
|
2391
|
+
import { join as join3 } from "path";
|
|
2392
|
+
function parseYaml(content) {
|
|
2393
|
+
const result = {};
|
|
2394
|
+
const lines = content.split(`
|
|
2395
|
+
`);
|
|
2396
|
+
let currentSection = null;
|
|
2397
|
+
let currentKey = "";
|
|
2398
|
+
for (const line of lines) {
|
|
2399
|
+
const trimmed = line.trim();
|
|
2400
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
2401
|
+
continue;
|
|
2402
|
+
const indentLevel = line.search(/\S/);
|
|
2403
|
+
const match = trimmed.match(/^([\w-]+):\s*(.*)$/);
|
|
2404
|
+
if (match) {
|
|
2405
|
+
const [, key, value] = match;
|
|
2406
|
+
if (indentLevel === 0) {
|
|
2407
|
+
if (value) {
|
|
2408
|
+
result[key] = value.replace(/^["']|["']$/g, "");
|
|
2409
|
+
} else {
|
|
2410
|
+
currentKey = key;
|
|
2411
|
+
currentSection = {};
|
|
2412
|
+
result[key] = currentSection;
|
|
2413
|
+
}
|
|
2414
|
+
} else if (currentSection) {
|
|
2415
|
+
currentSection[key] = value.replace(/^["']|["']$/g, "");
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
return result;
|
|
2420
|
+
}
|
|
2421
|
+
async function parseThemeMetadata(themePath) {
|
|
2422
|
+
const yamlPath = join3(themePath, "theme.yaml");
|
|
2423
|
+
if (!existsSync4(yamlPath)) {
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
try {
|
|
2427
|
+
const content = await readText(yamlPath);
|
|
2428
|
+
const parsed = parseYaml(content);
|
|
2429
|
+
return {
|
|
2430
|
+
name: parsed.name || "",
|
|
2431
|
+
author: parsed.author,
|
|
2432
|
+
description: parsed.description,
|
|
2433
|
+
version: parsed.version,
|
|
2434
|
+
source: parsed.source,
|
|
2435
|
+
colors: parsed.colors
|
|
2436
|
+
};
|
|
2437
|
+
} catch {
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
function parseThemeFiles(themePath) {
|
|
2442
|
+
const entries = readdirSync3(themePath, { withFileTypes: true });
|
|
2443
|
+
return entries.filter((e) => e.isFile() && !e.name.startsWith(".") && e.name !== "theme.yaml" && e.name !== "light.mode").map((e) => ({
|
|
2444
|
+
name: e.name,
|
|
2445
|
+
path: join3(themePath, e.name),
|
|
2446
|
+
application: e.name.replace(/\.(conf|theme|lua|toml|css|json|ini)$/, "")
|
|
2447
|
+
}));
|
|
2448
|
+
}
|
|
2449
|
+
async function parseTheme(themePath, themeName) {
|
|
2450
|
+
const files = parseThemeFiles(themePath);
|
|
2451
|
+
const metadata = await parseThemeMetadata(themePath);
|
|
2452
|
+
return {
|
|
2453
|
+
name: metadata?.name || themeName,
|
|
2454
|
+
path: themePath,
|
|
2455
|
+
files,
|
|
2456
|
+
metadata,
|
|
2457
|
+
hasBackgrounds: existsSync4(join3(themePath, "backgrounds")),
|
|
2458
|
+
hasPreview: existsSync4(join3(themePath, "preview.png")),
|
|
2459
|
+
isLightMode: existsSync4(join3(themePath, "light.mode"))
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// src/cli/set-theme.ts
|
|
2464
|
+
import { parseArgs as parseArgs4 } from "util";
|
|
2465
|
+
import { readdirSync as readdirSync4, existsSync as existsSync5, rmSync, symlinkSync, unlinkSync } from "fs";
|
|
2466
|
+
import { join as join4 } from "path";
|
|
2467
|
+
var colors5 = {
|
|
2468
|
+
red: "\x1B[0;31m",
|
|
2469
|
+
green: "\x1B[0;32m",
|
|
2470
|
+
blue: "\x1B[0;34m",
|
|
2471
|
+
yellow: "\x1B[1;33m",
|
|
2472
|
+
cyan: "\x1B[0;36m",
|
|
2473
|
+
dim: "\x1B[2m",
|
|
2474
|
+
reset: "\x1B[0m"
|
|
2475
|
+
};
|
|
2476
|
+
async function listThemes() {
|
|
2477
|
+
await ensureConfigDir();
|
|
2478
|
+
if (!existsSync5(THEMES_DIR)) {
|
|
2479
|
+
return [];
|
|
2480
|
+
}
|
|
2481
|
+
const entries = readdirSync4(THEMES_DIR, { withFileTypes: true });
|
|
2482
|
+
const themes = [];
|
|
2483
|
+
for (const entry of entries) {
|
|
2484
|
+
if (entry.isDirectory()) {
|
|
2485
|
+
const themePath = join4(THEMES_DIR, entry.name);
|
|
2486
|
+
const theme = await parseTheme(themePath, entry.name);
|
|
2487
|
+
themes.push(theme);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
return themes;
|
|
2491
|
+
}
|
|
2492
|
+
function clearDirectory(dir) {
|
|
2493
|
+
if (existsSync5(dir)) {
|
|
2494
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
2495
|
+
for (const entry of entries) {
|
|
2496
|
+
const fullPath = join4(dir, entry.name);
|
|
2497
|
+
if (entry.isSymbolicLink() || entry.isFile()) {
|
|
2498
|
+
unlinkSync(fullPath);
|
|
2499
|
+
} else if (entry.isDirectory()) {
|
|
2500
|
+
rmSync(fullPath, { recursive: true, force: true });
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
function createSymlink(source, target) {
|
|
2506
|
+
if (existsSync5(target)) {
|
|
2507
|
+
unlinkSync(target);
|
|
2508
|
+
}
|
|
2509
|
+
symlinkSync(source, target);
|
|
2510
|
+
}
|
|
2511
|
+
async function applyTheme(themeName) {
|
|
2512
|
+
const themeDir = join4(THEMES_DIR, themeName);
|
|
2513
|
+
if (!existsSync5(themeDir)) {
|
|
2514
|
+
return { output: `Theme '${themeName}' not found`, success: false };
|
|
2515
|
+
}
|
|
2516
|
+
await ensureConfigDir();
|
|
1843
2517
|
await ensureDir2(THEME_TARGET_DIR);
|
|
1844
2518
|
const theme = await parseTheme(themeDir, themeName);
|
|
1845
2519
|
clearDirectory(THEME_TARGET_DIR);
|
|
@@ -1956,226 +2630,19 @@ if (isMainModule4) {
|
|
|
1956
2630
|
main4().catch(console.error);
|
|
1957
2631
|
}
|
|
1958
2632
|
|
|
1959
|
-
// src/
|
|
1960
|
-
import { jsxDEV as
|
|
1961
|
-
function MainMenu({ onSelect }) {
|
|
1962
|
-
const { exit } = useApp();
|
|
1963
|
-
return /* @__PURE__ */ jsxDEV10(Panel, {
|
|
1964
|
-
title: "Main Menu",
|
|
1965
|
-
children: /* @__PURE__ */ jsxDEV10(VimSelect, {
|
|
1966
|
-
options: [
|
|
1967
|
-
{ label: "Config Manager", value: "config" },
|
|
1968
|
-
{ label: "Package Sync", value: "packages" },
|
|
1969
|
-
{ label: "Set Theme", value: "themes" },
|
|
1970
|
-
{ label: "Exit", value: "exit" }
|
|
1971
|
-
],
|
|
1972
|
-
onChange: (value) => {
|
|
1973
|
-
if (value === "exit") {
|
|
1974
|
-
exit();
|
|
1975
|
-
return;
|
|
1976
|
-
}
|
|
1977
|
-
onSelect(value);
|
|
1978
|
-
}
|
|
1979
|
-
}, undefined, false, undefined, this)
|
|
1980
|
-
}, undefined, false, undefined, this);
|
|
1981
|
-
}
|
|
1982
|
-
function ConfigMenu({ onBack }) {
|
|
1983
|
-
const [state, setState] = useState4("menu");
|
|
1984
|
-
const [output, setOutput] = useState4("");
|
|
1985
|
-
const [success, setSuccess] = useState4(true);
|
|
1986
|
-
useInput3((input, key) => {
|
|
1987
|
-
if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
|
|
1988
|
-
onBack();
|
|
1989
|
-
}
|
|
1990
|
-
});
|
|
1991
|
-
const handleAction = async (action) => {
|
|
1992
|
-
if (action === "back") {
|
|
1993
|
-
onBack();
|
|
1994
|
-
return;
|
|
1995
|
-
}
|
|
1996
|
-
setState("running");
|
|
1997
|
-
const result = await runConfigManager([action]);
|
|
1998
|
-
setOutput(result.output);
|
|
1999
|
-
setSuccess(result.success);
|
|
2000
|
-
setState("result");
|
|
2001
|
-
};
|
|
2002
|
-
if (state === "running") {
|
|
2003
|
-
return /* @__PURE__ */ jsxDEV10(Panel, {
|
|
2004
|
-
title: "Config Manager",
|
|
2005
|
-
children: /* @__PURE__ */ jsxDEV10(Spinner, {
|
|
2006
|
-
label: "Processing..."
|
|
2007
|
-
}, undefined, false, undefined, this)
|
|
2008
|
-
}, undefined, false, undefined, this);
|
|
2009
|
-
}
|
|
2010
|
-
if (state === "result") {
|
|
2011
|
-
return /* @__PURE__ */ jsxDEV10(CommandOutput, {
|
|
2012
|
-
title: "Config Manager",
|
|
2013
|
-
output,
|
|
2014
|
-
success,
|
|
2015
|
-
onDismiss: () => setState("menu")
|
|
2016
|
-
}, undefined, false, undefined, this);
|
|
2017
|
-
}
|
|
2018
|
-
return /* @__PURE__ */ jsxDEV10(Panel, {
|
|
2019
|
-
title: "Config Manager",
|
|
2020
|
-
children: /* @__PURE__ */ jsxDEV10(VimSelect, {
|
|
2021
|
-
options: [
|
|
2022
|
-
{ label: "Stow all packages", value: "stow-all" },
|
|
2023
|
-
{ label: "Unstow all packages", value: "unstow-all" },
|
|
2024
|
-
{ label: "Check status", value: "status" },
|
|
2025
|
-
{ label: "List packages", value: "list" },
|
|
2026
|
-
{ label: "Back", value: "back" }
|
|
2027
|
-
],
|
|
2028
|
-
onChange: handleAction
|
|
2029
|
-
}, undefined, false, undefined, this)
|
|
2030
|
-
}, undefined, false, undefined, this);
|
|
2031
|
-
}
|
|
2032
|
-
function PackageMenu({ onBack }) {
|
|
2033
|
-
const [state, setState] = useState4("menu");
|
|
2034
|
-
const [output, setOutput] = useState4("");
|
|
2035
|
-
const [success, setSuccess] = useState4(true);
|
|
2036
|
-
useInput3((input, key) => {
|
|
2037
|
-
if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
|
|
2038
|
-
onBack();
|
|
2039
|
-
}
|
|
2040
|
-
});
|
|
2041
|
-
const handleAction = async (action) => {
|
|
2042
|
-
if (action === "back") {
|
|
2043
|
-
onBack();
|
|
2044
|
-
return;
|
|
2045
|
-
}
|
|
2046
|
-
setState("running");
|
|
2047
|
-
let result;
|
|
2048
|
-
switch (action) {
|
|
2049
|
-
case "sync":
|
|
2050
|
-
result = await runPkgSync([]);
|
|
2051
|
-
break;
|
|
2052
|
-
case "sync-purge":
|
|
2053
|
-
result = await runPkgSync(["--purge"]);
|
|
2054
|
-
break;
|
|
2055
|
-
case "upgrade":
|
|
2056
|
-
result = await runPkgSync(["--upgrade-only"]);
|
|
2057
|
-
break;
|
|
2058
|
-
case "upgrade-interactive":
|
|
2059
|
-
result = await runPkgSync(["--upgrade-interactive"]);
|
|
2060
|
-
break;
|
|
2061
|
-
case "lock-update":
|
|
2062
|
-
result = await runPkgLock(["update"]);
|
|
2063
|
-
break;
|
|
2064
|
-
case "lock-status":
|
|
2065
|
-
result = await runPkgLock(["status"]);
|
|
2066
|
-
break;
|
|
2067
|
-
default:
|
|
2068
|
-
result = { output: "Unknown action", success: false };
|
|
2069
|
-
}
|
|
2070
|
-
setOutput(result.output);
|
|
2071
|
-
setSuccess(result.success);
|
|
2072
|
-
setState("result");
|
|
2073
|
-
};
|
|
2074
|
-
if (state === "running") {
|
|
2075
|
-
return /* @__PURE__ */ jsxDEV10(Panel, {
|
|
2076
|
-
title: "Package Sync",
|
|
2077
|
-
children: /* @__PURE__ */ jsxDEV10(Spinner, {
|
|
2078
|
-
label: "Syncing packages..."
|
|
2079
|
-
}, undefined, false, undefined, this)
|
|
2080
|
-
}, undefined, false, undefined, this);
|
|
2081
|
-
}
|
|
2082
|
-
if (state === "result") {
|
|
2083
|
-
return /* @__PURE__ */ jsxDEV10(CommandOutput, {
|
|
2084
|
-
title: "Package Sync",
|
|
2085
|
-
output,
|
|
2086
|
-
success,
|
|
2087
|
-
onDismiss: () => setState("menu")
|
|
2088
|
-
}, undefined, false, undefined, this);
|
|
2089
|
-
}
|
|
2090
|
-
return /* @__PURE__ */ jsxDEV10(Panel, {
|
|
2091
|
-
title: "Package Sync",
|
|
2092
|
-
children: /* @__PURE__ */ jsxDEV10(VimSelect, {
|
|
2093
|
-
options: [
|
|
2094
|
-
{ label: "Sync packages", value: "sync" },
|
|
2095
|
-
{ label: "Sync with purge", value: "sync-purge" },
|
|
2096
|
-
{ label: "Upgrade all (with verification)", value: "upgrade" },
|
|
2097
|
-
{ label: "Upgrade interactive", value: "upgrade-interactive" },
|
|
2098
|
-
{ label: "Update lockfile", value: "lock-update" },
|
|
2099
|
-
{ label: "Lockfile status", value: "lock-status" },
|
|
2100
|
-
{ label: "Back", value: "back" }
|
|
2101
|
-
],
|
|
2102
|
-
onChange: handleAction
|
|
2103
|
-
}, undefined, false, undefined, this)
|
|
2104
|
-
}, undefined, false, undefined, this);
|
|
2105
|
-
}
|
|
2633
|
+
// src/components/menus/ThemeMenu.tsx
|
|
2634
|
+
import { jsxDEV as jsxDEV17 } from "react/jsx-dev-runtime";
|
|
2106
2635
|
function ThemeMenu({ onBack }) {
|
|
2107
|
-
const [themes, setThemes] =
|
|
2108
|
-
const [loading, setLoading] =
|
|
2109
|
-
const
|
|
2110
|
-
const
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
const LAYOUT_OVERHEAD = 20;
|
|
2116
|
-
const cardWidth = useMemo(() => {
|
|
2117
|
-
const availableWidth = columns - 6;
|
|
2118
|
-
const cardsPerRow2 = Math.max(1, Math.floor(availableWidth / 28));
|
|
2119
|
-
return Math.floor(availableWidth / cardsPerRow2);
|
|
2120
|
-
}, [columns]);
|
|
2121
|
-
const cardsPerRow = useMemo(() => {
|
|
2122
|
-
const availableWidth = columns - 6;
|
|
2123
|
-
return Math.max(1, Math.floor(availableWidth / 28));
|
|
2124
|
-
}, [columns]);
|
|
2125
|
-
const visibleRows = useMemo(() => {
|
|
2126
|
-
const availableHeight = rows - LAYOUT_OVERHEAD;
|
|
2127
|
-
return Math.max(1, Math.floor(availableHeight / CARD_HEIGHT));
|
|
2128
|
-
}, [rows]);
|
|
2129
|
-
const selectedRow = Math.floor(selectedIndex / cardsPerRow);
|
|
2130
|
-
const totalRows = Math.ceil(themes.length / cardsPerRow);
|
|
2131
|
-
const [scrollOffset, setScrollOffset] = useState4(0);
|
|
2132
|
-
useEffect3(() => {
|
|
2133
|
-
if (selectedRow < scrollOffset) {
|
|
2134
|
-
setScrollOffset(selectedRow);
|
|
2135
|
-
} else if (selectedRow >= scrollOffset + visibleRows) {
|
|
2136
|
-
setScrollOffset(selectedRow - visibleRows + 1);
|
|
2137
|
-
}
|
|
2138
|
-
}, [selectedRow, scrollOffset, visibleRows]);
|
|
2139
|
-
const visibleThemes = useMemo(() => {
|
|
2140
|
-
const startIdx = scrollOffset * cardsPerRow;
|
|
2141
|
-
const endIdx = (scrollOffset + visibleRows) * cardsPerRow;
|
|
2142
|
-
return themes.slice(startIdx, endIdx);
|
|
2143
|
-
}, [themes, scrollOffset, visibleRows, cardsPerRow]);
|
|
2144
|
-
const visibleStartIndex = scrollOffset * cardsPerRow;
|
|
2145
|
-
useInput3((input, key) => {
|
|
2146
|
-
if (state !== "menu" || loading)
|
|
2147
|
-
return;
|
|
2148
|
-
if (key.escape) {
|
|
2149
|
-
onBack();
|
|
2150
|
-
return;
|
|
2151
|
-
}
|
|
2152
|
-
if (key.rightArrow || input === "l") {
|
|
2153
|
-
if (selectedIndex < themes.length - 1) {
|
|
2154
|
-
setSelectedIndex((i) => i + 1);
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
if (key.leftArrow || input === "h") {
|
|
2158
|
-
if (selectedIndex > 0) {
|
|
2159
|
-
setSelectedIndex((i) => i - 1);
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
if (key.downArrow || input === "j") {
|
|
2163
|
-
const nextIndex = selectedIndex + cardsPerRow;
|
|
2164
|
-
if (nextIndex < themes.length) {
|
|
2165
|
-
setSelectedIndex(nextIndex);
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
if (key.upArrow || input === "k") {
|
|
2169
|
-
const prevIndex = selectedIndex - cardsPerRow;
|
|
2170
|
-
if (prevIndex >= 0) {
|
|
2171
|
-
setSelectedIndex(prevIndex);
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
if (key.return) {
|
|
2175
|
-
applyTheme2(themes[selectedIndex]);
|
|
2176
|
-
}
|
|
2636
|
+
const [themes, setThemes] = useState8([]);
|
|
2637
|
+
const [loading, setLoading] = useState8(true);
|
|
2638
|
+
const { state, output, success, isRunning, isResult, execute, reset } = useMenuAction();
|
|
2639
|
+
const grid = useThemeGrid({
|
|
2640
|
+
itemCount: themes.length,
|
|
2641
|
+
onSelect: (index) => applyTheme2(themes[index]),
|
|
2642
|
+
onBack,
|
|
2643
|
+
enabled: state === "menu" && !loading && themes.length > 0
|
|
2177
2644
|
});
|
|
2178
|
-
|
|
2645
|
+
useEffect5(() => {
|
|
2179
2646
|
async function loadThemes() {
|
|
2180
2647
|
if (!existsSync6(THEMES_DIR)) {
|
|
2181
2648
|
setThemes([]);
|
|
@@ -2197,52 +2664,49 @@ function ThemeMenu({ onBack }) {
|
|
|
2197
2664
|
loadThemes();
|
|
2198
2665
|
}, []);
|
|
2199
2666
|
const applyTheme2 = async (theme) => {
|
|
2200
|
-
setState("running");
|
|
2201
2667
|
const themeName = theme.path.split("/").pop();
|
|
2202
|
-
|
|
2203
|
-
setOutput(result.output);
|
|
2204
|
-
setSuccess(result.success);
|
|
2205
|
-
setState("result");
|
|
2668
|
+
await execute(() => runSetTheme(themeName));
|
|
2206
2669
|
};
|
|
2207
|
-
|
|
2208
|
-
return
|
|
2670
|
+
const visibleThemes = useMemo4(() => {
|
|
2671
|
+
return themes.slice(grid.visibleStartIndex, grid.visibleEndIndex);
|
|
2672
|
+
}, [themes, grid.visibleStartIndex, grid.visibleEndIndex]);
|
|
2673
|
+
if (loading || isRunning) {
|
|
2674
|
+
return /* @__PURE__ */ jsxDEV17(LoadingPanel, {
|
|
2209
2675
|
title: "Select Theme",
|
|
2210
|
-
|
|
2211
|
-
label: loading ? "Loading themes..." : "Applying theme..."
|
|
2212
|
-
}, undefined, false, undefined, this)
|
|
2676
|
+
label: loading ? "Loading themes..." : "Applying theme..."
|
|
2213
2677
|
}, undefined, false, undefined, this);
|
|
2214
2678
|
}
|
|
2215
|
-
if (
|
|
2216
|
-
return /* @__PURE__ */
|
|
2679
|
+
if (isResult) {
|
|
2680
|
+
return /* @__PURE__ */ jsxDEV17(CommandOutput, {
|
|
2217
2681
|
title: "Select Theme",
|
|
2218
2682
|
output,
|
|
2219
2683
|
success,
|
|
2220
|
-
onDismiss:
|
|
2684
|
+
onDismiss: reset
|
|
2221
2685
|
}, undefined, false, undefined, this);
|
|
2222
2686
|
}
|
|
2223
2687
|
if (themes.length === 0) {
|
|
2224
|
-
return /* @__PURE__ */
|
|
2688
|
+
return /* @__PURE__ */ jsxDEV17(Panel, {
|
|
2225
2689
|
title: "Select Theme",
|
|
2226
2690
|
children: [
|
|
2227
|
-
/* @__PURE__ */
|
|
2691
|
+
/* @__PURE__ */ jsxDEV17(Box14, {
|
|
2228
2692
|
flexDirection: "column",
|
|
2229
2693
|
children: [
|
|
2230
|
-
/* @__PURE__ */
|
|
2694
|
+
/* @__PURE__ */ jsxDEV17(Text13, {
|
|
2231
2695
|
color: colors.warning,
|
|
2232
2696
|
children: "No themes available."
|
|
2233
2697
|
}, undefined, false, undefined, this),
|
|
2234
|
-
/* @__PURE__ */
|
|
2698
|
+
/* @__PURE__ */ jsxDEV17(Text13, {
|
|
2235
2699
|
children: "This system is compatible with omarchy themes."
|
|
2236
2700
|
}, undefined, false, undefined, this),
|
|
2237
|
-
/* @__PURE__ */
|
|
2701
|
+
/* @__PURE__ */ jsxDEV17(Text13, {
|
|
2238
2702
|
dimColor: true,
|
|
2239
2703
|
children: "Add themes to ~/.config/formalconf/themes/"
|
|
2240
2704
|
}, undefined, false, undefined, this)
|
|
2241
2705
|
]
|
|
2242
2706
|
}, undefined, true, undefined, this),
|
|
2243
|
-
/* @__PURE__ */
|
|
2707
|
+
/* @__PURE__ */ jsxDEV17(Box14, {
|
|
2244
2708
|
marginTop: 1,
|
|
2245
|
-
children: /* @__PURE__ */
|
|
2709
|
+
children: /* @__PURE__ */ jsxDEV17(VimSelect, {
|
|
2246
2710
|
options: [{ label: "Back", value: "back" }],
|
|
2247
2711
|
onChange: () => onBack()
|
|
2248
2712
|
}, undefined, false, undefined, this)
|
|
@@ -2250,44 +2714,43 @@ function ThemeMenu({ onBack }) {
|
|
|
2250
2714
|
]
|
|
2251
2715
|
}, undefined, true, undefined, this);
|
|
2252
2716
|
}
|
|
2253
|
-
|
|
2254
|
-
const showScrollDown = scrollOffset + visibleRows < totalRows;
|
|
2255
|
-
const gridHeight = visibleRows * CARD_HEIGHT;
|
|
2256
|
-
return /* @__PURE__ */ jsxDEV10(Panel, {
|
|
2717
|
+
return /* @__PURE__ */ jsxDEV17(Panel, {
|
|
2257
2718
|
title: "Select Theme",
|
|
2258
2719
|
children: [
|
|
2259
|
-
showScrollUp && /* @__PURE__ */
|
|
2720
|
+
grid.showScrollUp && /* @__PURE__ */ jsxDEV17(Text13, {
|
|
2260
2721
|
dimColor: true,
|
|
2261
2722
|
children: [
|
|
2262
|
-
"
|
|
2263
|
-
|
|
2723
|
+
" ",
|
|
2724
|
+
"↑ ",
|
|
2725
|
+
grid.scrollOffset,
|
|
2264
2726
|
" more row",
|
|
2265
|
-
scrollOffset > 1 ? "s" : ""
|
|
2727
|
+
grid.scrollOffset > 1 ? "s" : ""
|
|
2266
2728
|
]
|
|
2267
2729
|
}, undefined, true, undefined, this),
|
|
2268
|
-
/* @__PURE__ */
|
|
2730
|
+
/* @__PURE__ */ jsxDEV17(Box14, {
|
|
2269
2731
|
flexDirection: "row",
|
|
2270
2732
|
flexWrap: "wrap",
|
|
2271
|
-
height: gridHeight,
|
|
2733
|
+
height: grid.gridHeight,
|
|
2272
2734
|
overflow: "hidden",
|
|
2273
|
-
children: visibleThemes.map((theme, index) => /* @__PURE__ */
|
|
2735
|
+
children: visibleThemes.map((theme, index) => /* @__PURE__ */ jsxDEV17(ThemeCard, {
|
|
2274
2736
|
theme,
|
|
2275
|
-
isSelected: visibleStartIndex + index === selectedIndex,
|
|
2276
|
-
width: cardWidth
|
|
2737
|
+
isSelected: grid.visibleStartIndex + index === grid.selectedIndex,
|
|
2738
|
+
width: grid.cardWidth
|
|
2277
2739
|
}, theme.path, false, undefined, this))
|
|
2278
2740
|
}, undefined, false, undefined, this),
|
|
2279
|
-
showScrollDown && /* @__PURE__ */
|
|
2741
|
+
grid.showScrollDown && /* @__PURE__ */ jsxDEV17(Text13, {
|
|
2280
2742
|
dimColor: true,
|
|
2281
2743
|
children: [
|
|
2282
|
-
"
|
|
2283
|
-
|
|
2744
|
+
" ",
|
|
2745
|
+
"↓ ",
|
|
2746
|
+
grid.totalRows - grid.scrollOffset - grid.visibleRows,
|
|
2284
2747
|
" more row",
|
|
2285
|
-
totalRows - scrollOffset - visibleRows > 1 ? "s" : ""
|
|
2748
|
+
grid.totalRows - grid.scrollOffset - grid.visibleRows > 1 ? "s" : ""
|
|
2286
2749
|
]
|
|
2287
2750
|
}, undefined, true, undefined, this),
|
|
2288
|
-
/* @__PURE__ */
|
|
2751
|
+
/* @__PURE__ */ jsxDEV17(Box14, {
|
|
2289
2752
|
marginTop: 1,
|
|
2290
|
-
children: /* @__PURE__ */
|
|
2753
|
+
children: /* @__PURE__ */ jsxDEV17(Text13, {
|
|
2291
2754
|
dimColor: true,
|
|
2292
2755
|
children: "←→↑↓/hjkl navigate • Enter select • Esc back"
|
|
2293
2756
|
}, undefined, false, undefined, this)
|
|
@@ -2295,45 +2758,71 @@ function ThemeMenu({ onBack }) {
|
|
|
2295
2758
|
]
|
|
2296
2759
|
}, undefined, true, undefined, this);
|
|
2297
2760
|
}
|
|
2761
|
+
|
|
2762
|
+
// src/cli/formalconf.tsx
|
|
2763
|
+
import { jsxDEV as jsxDEV18 } from "react/jsx-dev-runtime";
|
|
2764
|
+
var BREADCRUMBS = {
|
|
2765
|
+
main: ["Main"],
|
|
2766
|
+
config: ["Main", "Config Manager"],
|
|
2767
|
+
packages: ["Main", "Package Sync"],
|
|
2768
|
+
themes: ["Main", "Themes"]
|
|
2769
|
+
};
|
|
2298
2770
|
function App() {
|
|
2299
|
-
const [
|
|
2300
|
-
const
|
|
2301
|
-
|
|
2302
|
-
|
|
2771
|
+
const [appState, setAppState] = useState9("loading");
|
|
2772
|
+
const [missingDeps, setMissingDeps] = useState9([]);
|
|
2773
|
+
const [screen, setScreen] = useState9("main");
|
|
2774
|
+
const { exit } = useApp2();
|
|
2775
|
+
useInput9((input) => {
|
|
2776
|
+
if (input === "q")
|
|
2303
2777
|
exit();
|
|
2304
|
-
}
|
|
2305
2778
|
});
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
return ["Main", "Themes"];
|
|
2317
|
-
default:
|
|
2318
|
-
return ["Main"];
|
|
2779
|
+
useEffect6(() => {
|
|
2780
|
+
async function init() {
|
|
2781
|
+
ensureConfigDir();
|
|
2782
|
+
const result = await checkPrerequisites();
|
|
2783
|
+
if (!result.ok) {
|
|
2784
|
+
setMissingDeps(result.missing);
|
|
2785
|
+
setAppState("error");
|
|
2786
|
+
} else {
|
|
2787
|
+
setAppState("ready");
|
|
2788
|
+
}
|
|
2319
2789
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2790
|
+
init();
|
|
2791
|
+
}, []);
|
|
2792
|
+
if (appState === "loading") {
|
|
2793
|
+
return /* @__PURE__ */ jsxDEV18(Layout, {
|
|
2794
|
+
breadcrumb: ["Loading"],
|
|
2795
|
+
children: /* @__PURE__ */ jsxDEV18(Panel, {
|
|
2796
|
+
title: "FormalConf",
|
|
2797
|
+
children: /* @__PURE__ */ jsxDEV18(Spinner2, {
|
|
2798
|
+
label: "Checking prerequisites..."
|
|
2799
|
+
}, undefined, false, undefined, this)
|
|
2800
|
+
}, undefined, false, undefined, this)
|
|
2801
|
+
}, undefined, false, undefined, this);
|
|
2802
|
+
}
|
|
2803
|
+
if (appState === "error") {
|
|
2804
|
+
return /* @__PURE__ */ jsxDEV18(PrerequisiteError, {
|
|
2805
|
+
missing: missingDeps,
|
|
2806
|
+
onExit: exit
|
|
2807
|
+
}, undefined, false, undefined, this);
|
|
2808
|
+
}
|
|
2809
|
+
const goBack = () => setScreen("main");
|
|
2810
|
+
return /* @__PURE__ */ jsxDEV18(Layout, {
|
|
2811
|
+
breadcrumb: BREADCRUMBS[screen],
|
|
2323
2812
|
children: [
|
|
2324
|
-
screen === "main" && /* @__PURE__ */
|
|
2813
|
+
screen === "main" && /* @__PURE__ */ jsxDEV18(MainMenu, {
|
|
2325
2814
|
onSelect: setScreen
|
|
2326
2815
|
}, undefined, false, undefined, this),
|
|
2327
|
-
screen === "config" && /* @__PURE__ */
|
|
2328
|
-
onBack:
|
|
2816
|
+
screen === "config" && /* @__PURE__ */ jsxDEV18(ConfigMenu, {
|
|
2817
|
+
onBack: goBack
|
|
2329
2818
|
}, undefined, false, undefined, this),
|
|
2330
|
-
screen === "packages" && /* @__PURE__ */
|
|
2331
|
-
onBack:
|
|
2819
|
+
screen === "packages" && /* @__PURE__ */ jsxDEV18(PackageMenu, {
|
|
2820
|
+
onBack: goBack
|
|
2332
2821
|
}, undefined, false, undefined, this),
|
|
2333
|
-
screen === "themes" && /* @__PURE__ */
|
|
2334
|
-
onBack:
|
|
2822
|
+
screen === "themes" && /* @__PURE__ */ jsxDEV18(ThemeMenu, {
|
|
2823
|
+
onBack: goBack
|
|
2335
2824
|
}, undefined, false, undefined, this)
|
|
2336
2825
|
]
|
|
2337
2826
|
}, undefined, true, undefined, this);
|
|
2338
2827
|
}
|
|
2339
|
-
render(/* @__PURE__ */
|
|
2828
|
+
render(/* @__PURE__ */ jsxDEV18(App, {}, undefined, false, undefined, this));
|