formalconf 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -207
- package/dist/formalconf.js +575 -151
- package/package.json +1 -1
package/dist/formalconf.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// src/cli/formalconf.tsx
|
|
3
|
-
import { useState as
|
|
4
|
-
import { render, Box as
|
|
3
|
+
import { useState as useState5, useEffect as useEffect4, useMemo as useMemo2, useCallback, useRef } from "react";
|
|
4
|
+
import { render, Box as Box12, Text as Text11, useApp, useInput as useInput5 } from "ink";
|
|
5
5
|
import { Spinner } from "@inkjs/ui";
|
|
6
6
|
|
|
7
7
|
// src/components/ui/VimSelect.tsx
|
|
@@ -170,6 +170,115 @@ async function execLive(command, cwd) {
|
|
|
170
170
|
});
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
|
+
async function execStreaming(command, onLine, cwd) {
|
|
174
|
+
if (isBun) {
|
|
175
|
+
const proc = Bun.spawn(command, {
|
|
176
|
+
stdout: "pipe",
|
|
177
|
+
stderr: "pipe",
|
|
178
|
+
cwd
|
|
179
|
+
});
|
|
180
|
+
const processStream = async (stream) => {
|
|
181
|
+
const reader = stream.getReader();
|
|
182
|
+
const decoder = new TextDecoder;
|
|
183
|
+
let buffer = "";
|
|
184
|
+
while (true) {
|
|
185
|
+
const { done, value } = await reader.read();
|
|
186
|
+
if (done)
|
|
187
|
+
break;
|
|
188
|
+
buffer += decoder.decode(value, { stream: true });
|
|
189
|
+
const lines = buffer.split(`
|
|
190
|
+
`);
|
|
191
|
+
buffer = lines.pop() || "";
|
|
192
|
+
for (const line of lines) {
|
|
193
|
+
onLine(line);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (buffer) {
|
|
197
|
+
onLine(buffer);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
await Promise.all([
|
|
201
|
+
processStream(proc.stdout),
|
|
202
|
+
processStream(proc.stderr)
|
|
203
|
+
]);
|
|
204
|
+
return await proc.exited;
|
|
205
|
+
}
|
|
206
|
+
return new Promise((resolve) => {
|
|
207
|
+
const [cmd, ...args] = command;
|
|
208
|
+
const proc = nodeSpawn(cmd, args, { cwd, shell: false });
|
|
209
|
+
const processData = (data) => {
|
|
210
|
+
const text = data.toString();
|
|
211
|
+
const lines = text.split(`
|
|
212
|
+
`);
|
|
213
|
+
for (const line of lines) {
|
|
214
|
+
if (line)
|
|
215
|
+
onLine(line);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
proc.stdout?.on("data", processData);
|
|
219
|
+
proc.stderr?.on("data", processData);
|
|
220
|
+
proc.on("close", (code) => {
|
|
221
|
+
resolve(code ?? 1);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
async function execStreamingWithTTY(command, onLine, cwd) {
|
|
226
|
+
if (isBun) {
|
|
227
|
+
const proc = Bun.spawn(command, {
|
|
228
|
+
stdout: "pipe",
|
|
229
|
+
stderr: "pipe",
|
|
230
|
+
stdin: "inherit",
|
|
231
|
+
cwd
|
|
232
|
+
});
|
|
233
|
+
const processStream = async (stream) => {
|
|
234
|
+
const reader = stream.getReader();
|
|
235
|
+
const decoder = new TextDecoder;
|
|
236
|
+
let buffer = "";
|
|
237
|
+
while (true) {
|
|
238
|
+
const { done, value } = await reader.read();
|
|
239
|
+
if (done)
|
|
240
|
+
break;
|
|
241
|
+
buffer += decoder.decode(value, { stream: true });
|
|
242
|
+
const lines = buffer.split(`
|
|
243
|
+
`);
|
|
244
|
+
buffer = lines.pop() || "";
|
|
245
|
+
for (const line of lines) {
|
|
246
|
+
onLine(line);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (buffer) {
|
|
250
|
+
onLine(buffer);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
await Promise.all([
|
|
254
|
+
processStream(proc.stdout),
|
|
255
|
+
processStream(proc.stderr)
|
|
256
|
+
]);
|
|
257
|
+
return await proc.exited;
|
|
258
|
+
}
|
|
259
|
+
return new Promise((resolve) => {
|
|
260
|
+
const [cmd, ...args] = command;
|
|
261
|
+
const proc = nodeSpawn(cmd, args, {
|
|
262
|
+
cwd,
|
|
263
|
+
shell: false,
|
|
264
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
265
|
+
});
|
|
266
|
+
const processData = (data) => {
|
|
267
|
+
const text = data.toString();
|
|
268
|
+
const lines = text.split(`
|
|
269
|
+
`);
|
|
270
|
+
for (const line of lines) {
|
|
271
|
+
if (line)
|
|
272
|
+
onLine(line);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
proc.stdout?.on("data", processData);
|
|
276
|
+
proc.stderr?.on("data", processData);
|
|
277
|
+
proc.on("close", (code) => {
|
|
278
|
+
resolve(code ?? 1);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
}
|
|
173
282
|
async function readJson(path) {
|
|
174
283
|
if (isBun) {
|
|
175
284
|
return Bun.file(path).json();
|
|
@@ -210,6 +319,19 @@ async function commandExists(cmd) {
|
|
|
210
319
|
const result = await exec(["which", cmd]);
|
|
211
320
|
return result.success;
|
|
212
321
|
}
|
|
322
|
+
async function checkPrerequisites() {
|
|
323
|
+
const required = [
|
|
324
|
+
{ name: "stow", install: "brew install stow" },
|
|
325
|
+
{ name: "brew", install: "https://brew.sh" }
|
|
326
|
+
];
|
|
327
|
+
const missing = [];
|
|
328
|
+
for (const dep of required) {
|
|
329
|
+
if (!await commandExists(dep.name)) {
|
|
330
|
+
missing.push(dep);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return { ok: missing.length === 0, missing };
|
|
334
|
+
}
|
|
213
335
|
|
|
214
336
|
// src/lib/paths.ts
|
|
215
337
|
var HOME_DIR = homedir();
|
|
@@ -316,7 +438,7 @@ function StatusIndicator({
|
|
|
316
438
|
// package.json
|
|
317
439
|
var package_default = {
|
|
318
440
|
name: "formalconf",
|
|
319
|
-
version: "2.0.
|
|
441
|
+
version: "2.0.1",
|
|
320
442
|
description: "Dotfiles management TUI for macOS - config management, package sync, and theme switching",
|
|
321
443
|
type: "module",
|
|
322
444
|
main: "./dist/formalconf.js",
|
|
@@ -625,6 +747,130 @@ function ThemeCard({ theme, isSelected, width }) {
|
|
|
625
747
|
}, undefined, false, undefined, this);
|
|
626
748
|
}
|
|
627
749
|
|
|
750
|
+
// src/components/ScrollableLog.tsx
|
|
751
|
+
import { useState as useState4, useEffect as useEffect3, useMemo } from "react";
|
|
752
|
+
import { Box as Box10, Text as Text9, useInput as useInput3 } from "ink";
|
|
753
|
+
import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
|
|
754
|
+
function ScrollableLog({
|
|
755
|
+
lines,
|
|
756
|
+
maxHeight,
|
|
757
|
+
autoScroll = true,
|
|
758
|
+
showScrollHint = true
|
|
759
|
+
}) {
|
|
760
|
+
const { rows } = useTerminalSize();
|
|
761
|
+
const visibleLines = maxHeight || Math.max(5, rows - 12);
|
|
762
|
+
const [scrollOffset, setScrollOffset] = useState4(0);
|
|
763
|
+
const [isAutoScrolling, setIsAutoScrolling] = useState4(autoScroll);
|
|
764
|
+
const totalLines = lines.length;
|
|
765
|
+
const maxOffset = Math.max(0, totalLines - visibleLines);
|
|
766
|
+
useEffect3(() => {
|
|
767
|
+
if (isAutoScrolling) {
|
|
768
|
+
setScrollOffset(maxOffset);
|
|
769
|
+
}
|
|
770
|
+
}, [totalLines, maxOffset, isAutoScrolling]);
|
|
771
|
+
useInput3((input, key) => {
|
|
772
|
+
if (key.downArrow || input === "j") {
|
|
773
|
+
setIsAutoScrolling(false);
|
|
774
|
+
setScrollOffset((prev) => Math.min(prev + 1, maxOffset));
|
|
775
|
+
}
|
|
776
|
+
if (key.upArrow || input === "k") {
|
|
777
|
+
setIsAutoScrolling(false);
|
|
778
|
+
setScrollOffset((prev) => Math.max(prev - 1, 0));
|
|
779
|
+
}
|
|
780
|
+
if (input === "G") {
|
|
781
|
+
setIsAutoScrolling(true);
|
|
782
|
+
setScrollOffset(maxOffset);
|
|
783
|
+
}
|
|
784
|
+
if (input === "g") {
|
|
785
|
+
setIsAutoScrolling(false);
|
|
786
|
+
setScrollOffset(0);
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
const visibleContent = useMemo(() => {
|
|
790
|
+
return lines.slice(scrollOffset, scrollOffset + visibleLines);
|
|
791
|
+
}, [lines, scrollOffset, visibleLines]);
|
|
792
|
+
const showScrollUp = scrollOffset > 0;
|
|
793
|
+
const showScrollDown = scrollOffset < maxOffset;
|
|
794
|
+
return /* @__PURE__ */ jsxDEV10(Box10, {
|
|
795
|
+
flexDirection: "column",
|
|
796
|
+
children: [
|
|
797
|
+
showScrollHint && showScrollUp && /* @__PURE__ */ jsxDEV10(Text9, {
|
|
798
|
+
dimColor: true,
|
|
799
|
+
children: [
|
|
800
|
+
" ↑ ",
|
|
801
|
+
scrollOffset,
|
|
802
|
+
" more line",
|
|
803
|
+
scrollOffset !== 1 ? "s" : ""
|
|
804
|
+
]
|
|
805
|
+
}, undefined, true, undefined, this),
|
|
806
|
+
/* @__PURE__ */ jsxDEV10(Box10, {
|
|
807
|
+
flexDirection: "column",
|
|
808
|
+
height: visibleLines,
|
|
809
|
+
overflow: "hidden",
|
|
810
|
+
children: visibleContent.map((line, i) => /* @__PURE__ */ jsxDEV10(Text9, {
|
|
811
|
+
children: line
|
|
812
|
+
}, scrollOffset + i, false, undefined, this))
|
|
813
|
+
}, undefined, false, undefined, this),
|
|
814
|
+
showScrollHint && showScrollDown && /* @__PURE__ */ jsxDEV10(Text9, {
|
|
815
|
+
dimColor: true,
|
|
816
|
+
children: [
|
|
817
|
+
" ↓ ",
|
|
818
|
+
maxOffset - scrollOffset,
|
|
819
|
+
" more line",
|
|
820
|
+
maxOffset - scrollOffset !== 1 ? "s" : ""
|
|
821
|
+
]
|
|
822
|
+
}, undefined, true, undefined, this),
|
|
823
|
+
showScrollHint && totalLines > visibleLines && /* @__PURE__ */ jsxDEV10(Text9, {
|
|
824
|
+
dimColor: true,
|
|
825
|
+
children: [
|
|
826
|
+
"j/k scroll • g top • G bottom ",
|
|
827
|
+
isAutoScrolling ? "(auto-scroll)" : ""
|
|
828
|
+
]
|
|
829
|
+
}, undefined, true, undefined, this)
|
|
830
|
+
]
|
|
831
|
+
}, undefined, true, undefined, this);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/components/PromptInput.tsx
|
|
835
|
+
import { Box as Box11, Text as Text10, useInput as useInput4 } from "ink";
|
|
836
|
+
import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
|
|
837
|
+
function PromptInput({
|
|
838
|
+
question,
|
|
839
|
+
options = ["y", "n"],
|
|
840
|
+
onAnswer
|
|
841
|
+
}) {
|
|
842
|
+
useInput4((input) => {
|
|
843
|
+
const lower = input.toLowerCase();
|
|
844
|
+
if (options.includes(lower)) {
|
|
845
|
+
onAnswer(lower);
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
return /* @__PURE__ */ jsxDEV11(Box11, {
|
|
849
|
+
marginTop: 1,
|
|
850
|
+
borderStyle: "single",
|
|
851
|
+
borderColor: colors.accent,
|
|
852
|
+
paddingX: 1,
|
|
853
|
+
children: /* @__PURE__ */ jsxDEV11(Text10, {
|
|
854
|
+
children: [
|
|
855
|
+
question,
|
|
856
|
+
" ",
|
|
857
|
+
/* @__PURE__ */ jsxDEV11(Text10, {
|
|
858
|
+
color: colors.accent,
|
|
859
|
+
children: [
|
|
860
|
+
"[",
|
|
861
|
+
options.join("/"),
|
|
862
|
+
"]"
|
|
863
|
+
]
|
|
864
|
+
}, undefined, true, undefined, this),
|
|
865
|
+
/* @__PURE__ */ jsxDEV11(Text10, {
|
|
866
|
+
dimColor: true,
|
|
867
|
+
children: ": "
|
|
868
|
+
}, undefined, false, undefined, this)
|
|
869
|
+
]
|
|
870
|
+
}, undefined, true, undefined, this)
|
|
871
|
+
}, undefined, false, undefined, this);
|
|
872
|
+
}
|
|
873
|
+
|
|
628
874
|
// src/lib/theme-parser.ts
|
|
629
875
|
import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
|
|
630
876
|
import { join as join3 } from "path";
|
|
@@ -1203,6 +1449,15 @@ var colors3 = {
|
|
|
1203
1449
|
bold: "\x1B[1m",
|
|
1204
1450
|
reset: "\x1B[0m"
|
|
1205
1451
|
};
|
|
1452
|
+
async function runCommand(command, callbacks, cwd, needsTTY = false) {
|
|
1453
|
+
if (callbacks) {
|
|
1454
|
+
if (needsTTY) {
|
|
1455
|
+
return execStreamingWithTTY(command, callbacks.onLog, cwd);
|
|
1456
|
+
}
|
|
1457
|
+
return execStreaming(command, callbacks.onLog, cwd);
|
|
1458
|
+
}
|
|
1459
|
+
return execLive(command, cwd);
|
|
1460
|
+
}
|
|
1206
1461
|
async function checkDependencies() {
|
|
1207
1462
|
if (!await commandExists("brew")) {
|
|
1208
1463
|
console.error(`${colors3.red}Error: Homebrew not installed${colors3.reset}`);
|
|
@@ -1238,34 +1493,35 @@ async function getOutdatedMas() {
|
|
|
1238
1493
|
return null;
|
|
1239
1494
|
}).filter((app) => app !== null);
|
|
1240
1495
|
}
|
|
1241
|
-
async function upgradeWithVerification() {
|
|
1496
|
+
async function upgradeWithVerification(cb = null) {
|
|
1497
|
+
const log = cb?.onLog ?? console.log;
|
|
1242
1498
|
const result = {
|
|
1243
1499
|
attempted: [],
|
|
1244
1500
|
succeeded: [],
|
|
1245
1501
|
failed: [],
|
|
1246
1502
|
stillOutdated: []
|
|
1247
1503
|
};
|
|
1248
|
-
|
|
1504
|
+
log(`
|
|
1249
1505
|
${colors3.cyan}=== Checking for updates ===${colors3.reset}
|
|
1250
1506
|
`);
|
|
1251
|
-
await
|
|
1507
|
+
await runCommand(["brew", "update"], cb);
|
|
1252
1508
|
const beforeUpgrade = await getOutdatedPackages();
|
|
1253
1509
|
result.attempted = beforeUpgrade.map((p) => p.name);
|
|
1254
1510
|
if (beforeUpgrade.length === 0) {
|
|
1255
|
-
|
|
1511
|
+
log(`
|
|
1256
1512
|
${colors3.green}All brew packages are up to date${colors3.reset}`);
|
|
1257
1513
|
} else {
|
|
1258
|
-
|
|
1514
|
+
log(`
|
|
1259
1515
|
${colors3.yellow}Found ${beforeUpgrade.length} outdated packages${colors3.reset}
|
|
1260
1516
|
`);
|
|
1261
|
-
|
|
1517
|
+
log(`${colors3.cyan}=== Upgrading formulas ===${colors3.reset}
|
|
1262
1518
|
`);
|
|
1263
|
-
await
|
|
1264
|
-
|
|
1519
|
+
await runCommand(["brew", "upgrade", "--formula"], cb);
|
|
1520
|
+
log(`
|
|
1265
1521
|
${colors3.cyan}=== Upgrading casks ===${colors3.reset}
|
|
1266
1522
|
`);
|
|
1267
|
-
await
|
|
1268
|
-
|
|
1523
|
+
await runCommand(["brew", "upgrade", "--cask", "--greedy"], cb);
|
|
1524
|
+
log(`
|
|
1269
1525
|
${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
|
|
1270
1526
|
`);
|
|
1271
1527
|
const afterUpgrade = await getOutdatedPackages();
|
|
@@ -1278,13 +1534,13 @@ ${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
|
|
|
1278
1534
|
}
|
|
1279
1535
|
}
|
|
1280
1536
|
if (result.stillOutdated.length > 0) {
|
|
1281
|
-
|
|
1537
|
+
log(`${colors3.yellow}${result.stillOutdated.length} packages still outdated, retrying individually...${colors3.reset}
|
|
1282
1538
|
`);
|
|
1283
1539
|
for (const pkgName of [...result.stillOutdated]) {
|
|
1284
1540
|
const pkg = afterUpgrade.find((p) => p.name === pkgName);
|
|
1285
1541
|
if (!pkg)
|
|
1286
1542
|
continue;
|
|
1287
|
-
|
|
1543
|
+
log(` Retrying ${colors3.blue}${pkgName}${colors3.reset}...`);
|
|
1288
1544
|
const upgradeCmd = pkg.type === "cask" ? ["brew", "upgrade", "--cask", pkgName] : ["brew", "upgrade", pkgName];
|
|
1289
1545
|
const retryResult = await exec(upgradeCmd);
|
|
1290
1546
|
const checkResult = await exec([
|
|
@@ -1298,11 +1554,11 @@ ${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
|
|
|
1298
1554
|
if (!stillOutdatedNow.includes(pkgName)) {
|
|
1299
1555
|
result.succeeded.push(pkgName);
|
|
1300
1556
|
result.stillOutdated = result.stillOutdated.filter((n) => n !== pkgName);
|
|
1301
|
-
|
|
1557
|
+
log(` ${colors3.green}✓ Success${colors3.reset}`);
|
|
1302
1558
|
} else {
|
|
1303
1559
|
result.failed.push(pkgName);
|
|
1304
1560
|
result.stillOutdated = result.stillOutdated.filter((n) => n !== pkgName);
|
|
1305
|
-
|
|
1561
|
+
log(` ${colors3.red}✗ Failed${colors3.reset} ${retryResult.stderr ? `(${retryResult.stderr.split(`
|
|
1306
1562
|
`)[0]})` : ""}`);
|
|
1307
1563
|
}
|
|
1308
1564
|
}
|
|
@@ -1311,74 +1567,77 @@ ${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
|
|
|
1311
1567
|
if (await commandExists("mas")) {
|
|
1312
1568
|
const masOutdated = await getOutdatedMas();
|
|
1313
1569
|
if (masOutdated.length > 0) {
|
|
1314
|
-
|
|
1570
|
+
log(`
|
|
1315
1571
|
${colors3.cyan}=== Upgrading Mac App Store apps ===${colors3.reset}
|
|
1316
1572
|
`);
|
|
1317
|
-
await
|
|
1573
|
+
await runCommand(["mas", "upgrade"], cb, undefined, true);
|
|
1318
1574
|
}
|
|
1319
1575
|
}
|
|
1320
|
-
|
|
1576
|
+
log(`
|
|
1321
1577
|
${colors3.cyan}=== Cleanup ===${colors3.reset}
|
|
1322
1578
|
`);
|
|
1323
|
-
await
|
|
1324
|
-
|
|
1579
|
+
await runCommand(["brew", "cleanup"], cb);
|
|
1580
|
+
log(`
|
|
1325
1581
|
${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
1326
1582
|
`);
|
|
1327
1583
|
const lock = await updateLockfile();
|
|
1328
1584
|
const lockTotal = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
1329
|
-
|
|
1585
|
+
log(` Locked ${lockTotal} packages`);
|
|
1330
1586
|
return result;
|
|
1331
1587
|
}
|
|
1332
|
-
async function upgradeInteractive() {
|
|
1333
|
-
console.log
|
|
1588
|
+
async function upgradeInteractive(cb = null) {
|
|
1589
|
+
const log = cb?.onLog ?? console.log;
|
|
1590
|
+
const askPrompt = cb?.onPrompt ?? (async (q) => (prompt(q) || "").trim().toLowerCase());
|
|
1591
|
+
log(`
|
|
1334
1592
|
${colors3.cyan}=== Checking for updates ===${colors3.reset}
|
|
1335
1593
|
`);
|
|
1336
|
-
await
|
|
1594
|
+
await runCommand(["brew", "update"], cb);
|
|
1337
1595
|
const outdated = await getOutdatedPackages();
|
|
1338
1596
|
if (outdated.length === 0) {
|
|
1339
|
-
|
|
1597
|
+
log(`
|
|
1340
1598
|
${colors3.green}All packages are up to date${colors3.reset}
|
|
1341
1599
|
`);
|
|
1342
1600
|
return;
|
|
1343
1601
|
}
|
|
1344
|
-
|
|
1602
|
+
log(`
|
|
1345
1603
|
${colors3.yellow}Found ${outdated.length} outdated packages${colors3.reset}
|
|
1346
1604
|
`);
|
|
1347
1605
|
for (const pkg of outdated) {
|
|
1348
|
-
const question = `Upgrade ${colors3.blue}${pkg.name}${colors3.reset} (${pkg.type})
|
|
1349
|
-
const answer = (
|
|
1606
|
+
const question = `Upgrade ${colors3.blue}${pkg.name}${colors3.reset} (${pkg.type})?`;
|
|
1607
|
+
const answer = await askPrompt(question, ["y", "n", "q"]);
|
|
1350
1608
|
if (answer === "q") {
|
|
1351
|
-
|
|
1609
|
+
log(`
|
|
1352
1610
|
${colors3.yellow}Upgrade cancelled${colors3.reset}`);
|
|
1353
1611
|
return;
|
|
1354
1612
|
}
|
|
1355
1613
|
if (answer === "y" || answer === "yes") {
|
|
1356
1614
|
const cmd = pkg.type === "cask" ? ["brew", "upgrade", "--cask", pkg.name] : ["brew", "upgrade", pkg.name];
|
|
1357
|
-
await
|
|
1615
|
+
await runCommand(cmd, cb);
|
|
1358
1616
|
}
|
|
1359
1617
|
}
|
|
1360
1618
|
const stillOutdated = await getOutdatedPackages();
|
|
1361
1619
|
if (stillOutdated.length > 0) {
|
|
1362
|
-
|
|
1620
|
+
log(`
|
|
1363
1621
|
${colors3.yellow}Still outdated: ${stillOutdated.map((p) => p.name).join(", ")}${colors3.reset}`);
|
|
1364
1622
|
} else {
|
|
1365
|
-
|
|
1623
|
+
log(`
|
|
1366
1624
|
${colors3.green}All selected packages upgraded successfully${colors3.reset}`);
|
|
1367
1625
|
}
|
|
1368
|
-
await
|
|
1369
|
-
|
|
1626
|
+
await runCommand(["brew", "cleanup"], cb);
|
|
1627
|
+
log(`
|
|
1370
1628
|
${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
1371
1629
|
`);
|
|
1372
1630
|
await updateLockfile();
|
|
1373
1631
|
}
|
|
1374
|
-
async function syncPackages(config) {
|
|
1632
|
+
async function syncPackages(config, cb = null) {
|
|
1633
|
+
const log = cb?.onLog ?? console.log;
|
|
1375
1634
|
if (config.config.autoUpdate) {
|
|
1376
|
-
|
|
1635
|
+
log(`
|
|
1377
1636
|
${colors3.cyan}=== Updating Homebrew ===${colors3.reset}
|
|
1378
1637
|
`);
|
|
1379
|
-
await
|
|
1638
|
+
await runCommand(["brew", "update"], cb);
|
|
1380
1639
|
}
|
|
1381
|
-
|
|
1640
|
+
log(`
|
|
1382
1641
|
${colors3.cyan}=== Installing taps ===${colors3.reset}
|
|
1383
1642
|
`);
|
|
1384
1643
|
const tappedResult = await exec(["brew", "tap"]);
|
|
@@ -1386,34 +1645,34 @@ ${colors3.cyan}=== Installing taps ===${colors3.reset}
|
|
|
1386
1645
|
`).filter(Boolean);
|
|
1387
1646
|
for (const tap of config.taps) {
|
|
1388
1647
|
if (!tapped.includes(tap)) {
|
|
1389
|
-
|
|
1390
|
-
await
|
|
1648
|
+
log(` Adding tap: ${colors3.blue}${tap}${colors3.reset}`);
|
|
1649
|
+
await runCommand(["brew", "tap", tap], cb);
|
|
1391
1650
|
}
|
|
1392
1651
|
}
|
|
1393
|
-
|
|
1652
|
+
log(`
|
|
1394
1653
|
${colors3.cyan}=== Installing packages ===${colors3.reset}
|
|
1395
1654
|
`);
|
|
1396
1655
|
const installedFormulas = (await exec(["brew", "list", "--formula"])).stdout.split(`
|
|
1397
1656
|
`).filter(Boolean);
|
|
1398
1657
|
for (const pkg of config.packages) {
|
|
1399
1658
|
if (!installedFormulas.includes(pkg)) {
|
|
1400
|
-
|
|
1401
|
-
await
|
|
1659
|
+
log(` Installing: ${colors3.blue}${pkg}${colors3.reset}`);
|
|
1660
|
+
await runCommand(["brew", "install", pkg], cb);
|
|
1402
1661
|
}
|
|
1403
1662
|
}
|
|
1404
|
-
|
|
1663
|
+
log(`
|
|
1405
1664
|
${colors3.cyan}=== Installing casks ===${colors3.reset}
|
|
1406
1665
|
`);
|
|
1407
1666
|
const installedCasks = (await exec(["brew", "list", "--cask"])).stdout.split(`
|
|
1408
1667
|
`).filter(Boolean);
|
|
1409
1668
|
for (const cask of config.casks) {
|
|
1410
1669
|
if (!installedCasks.includes(cask)) {
|
|
1411
|
-
|
|
1412
|
-
await
|
|
1670
|
+
log(` Installing: ${colors3.blue}${cask}${colors3.reset}`);
|
|
1671
|
+
await runCommand(["brew", "install", "--cask", cask], cb);
|
|
1413
1672
|
}
|
|
1414
1673
|
}
|
|
1415
1674
|
if (await commandExists("mas")) {
|
|
1416
|
-
|
|
1675
|
+
log(`
|
|
1417
1676
|
${colors3.cyan}=== Installing Mac App Store apps ===${colors3.reset}
|
|
1418
1677
|
`);
|
|
1419
1678
|
const masResult = await exec(["mas", "list"]);
|
|
@@ -1424,26 +1683,28 @@ ${colors3.cyan}=== Installing Mac App Store apps ===${colors3.reset}
|
|
|
1424
1683
|
});
|
|
1425
1684
|
for (const [name, id] of Object.entries(config.mas)) {
|
|
1426
1685
|
if (!installedMas.includes(id)) {
|
|
1427
|
-
|
|
1428
|
-
await
|
|
1686
|
+
log(` Installing: ${colors3.blue}${name}${colors3.reset}`);
|
|
1687
|
+
await runCommand(["mas", "install", String(id)], cb, undefined, true);
|
|
1429
1688
|
}
|
|
1430
1689
|
}
|
|
1431
1690
|
}
|
|
1432
1691
|
if (config.config.purge) {
|
|
1433
|
-
await purgeUnlisted(config, config.config.purgeInteractive);
|
|
1692
|
+
await purgeUnlisted(config, config.config.purgeInteractive, cb);
|
|
1434
1693
|
}
|
|
1435
|
-
|
|
1694
|
+
log(`
|
|
1436
1695
|
${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
1437
1696
|
`);
|
|
1438
1697
|
const lock = await updateLockfile();
|
|
1439
1698
|
const lockTotal = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
1440
|
-
|
|
1441
|
-
|
|
1699
|
+
log(` Locked ${lockTotal} packages`);
|
|
1700
|
+
log(`
|
|
1442
1701
|
${colors3.green}=== Sync complete ===${colors3.reset}
|
|
1443
1702
|
`);
|
|
1444
1703
|
}
|
|
1445
|
-
async function purgeUnlisted(config, interactive) {
|
|
1446
|
-
console.log
|
|
1704
|
+
async function purgeUnlisted(config, interactive, cb = null) {
|
|
1705
|
+
const log = cb?.onLog ?? console.log;
|
|
1706
|
+
const askPrompt = cb?.onPrompt ?? (async (q) => (prompt(q) || "").trim().toLowerCase());
|
|
1707
|
+
log(`
|
|
1447
1708
|
${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
|
|
1448
1709
|
`);
|
|
1449
1710
|
const installedFormulas = (await exec(["brew", "list", "--formula"])).stdout.split(`
|
|
@@ -1452,17 +1713,17 @@ ${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
|
|
|
1452
1713
|
if (!config.packages.includes(pkg)) {
|
|
1453
1714
|
const usesResult = await exec(["brew", "uses", "--installed", pkg]);
|
|
1454
1715
|
if (usesResult.stdout.trim()) {
|
|
1455
|
-
|
|
1716
|
+
log(` ${colors3.yellow}Skipping ${pkg} (has dependents)${colors3.reset}`);
|
|
1456
1717
|
continue;
|
|
1457
1718
|
}
|
|
1458
1719
|
if (interactive) {
|
|
1459
|
-
const answer = (
|
|
1720
|
+
const answer = await askPrompt(`Remove ${colors3.red}${pkg}${colors3.reset}?`, ["y", "n"]);
|
|
1460
1721
|
if (answer === "y") {
|
|
1461
|
-
await
|
|
1722
|
+
await runCommand(["brew", "uninstall", pkg], cb);
|
|
1462
1723
|
}
|
|
1463
1724
|
} else {
|
|
1464
|
-
|
|
1465
|
-
await
|
|
1725
|
+
log(` Removing: ${colors3.red}${pkg}${colors3.reset}`);
|
|
1726
|
+
await runCommand(["brew", "uninstall", pkg], cb);
|
|
1466
1727
|
}
|
|
1467
1728
|
}
|
|
1468
1729
|
}
|
|
@@ -1471,13 +1732,13 @@ ${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
|
|
|
1471
1732
|
for (const cask of installedCasks) {
|
|
1472
1733
|
if (!config.casks.includes(cask)) {
|
|
1473
1734
|
if (interactive) {
|
|
1474
|
-
const answer = (
|
|
1735
|
+
const answer = await askPrompt(`Remove cask ${colors3.red}${cask}${colors3.reset}?`, ["y", "n"]);
|
|
1475
1736
|
if (answer === "y") {
|
|
1476
|
-
await
|
|
1737
|
+
await runCommand(["brew", "uninstall", "--cask", cask], cb);
|
|
1477
1738
|
}
|
|
1478
1739
|
} else {
|
|
1479
|
-
|
|
1480
|
-
await
|
|
1740
|
+
log(` Removing cask: ${colors3.red}${cask}${colors3.reset}`);
|
|
1741
|
+
await runCommand(["brew", "uninstall", "--cask", cask], cb);
|
|
1481
1742
|
}
|
|
1482
1743
|
}
|
|
1483
1744
|
}
|
|
@@ -1495,22 +1756,22 @@ ${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
|
|
|
1495
1756
|
}
|
|
1496
1757
|
if (!configMasIds.includes(app.id)) {
|
|
1497
1758
|
if (interactive) {
|
|
1498
|
-
const answer = (
|
|
1759
|
+
const answer = await askPrompt(`Remove app ${colors3.red}${app.name}${colors3.reset}?`, ["y", "n"]);
|
|
1499
1760
|
if (answer === "y") {
|
|
1500
|
-
await
|
|
1761
|
+
await runCommand(["mas", "uninstall", String(app.id)], cb, undefined, true);
|
|
1501
1762
|
}
|
|
1502
1763
|
} else {
|
|
1503
|
-
|
|
1504
|
-
await
|
|
1764
|
+
log(` Removing app: ${colors3.red}${app.name}${colors3.reset}`);
|
|
1765
|
+
await runCommand(["mas", "uninstall", String(app.id)], cb, undefined, true);
|
|
1505
1766
|
}
|
|
1506
1767
|
}
|
|
1507
1768
|
}
|
|
1508
1769
|
}
|
|
1509
|
-
|
|
1770
|
+
log(`
|
|
1510
1771
|
${colors3.cyan}=== Cleaning up ===${colors3.reset}
|
|
1511
1772
|
`);
|
|
1512
|
-
await
|
|
1513
|
-
await
|
|
1773
|
+
await runCommand(["brew", "autoremove"], cb);
|
|
1774
|
+
await runCommand(["brew", "cleanup"], cb);
|
|
1514
1775
|
}
|
|
1515
1776
|
function printUsage2() {
|
|
1516
1777
|
console.log(`
|
|
@@ -1527,7 +1788,7 @@ Examples:
|
|
|
1527
1788
|
bun run pkg-sync --purge Sync and remove unlisted packages
|
|
1528
1789
|
`);
|
|
1529
1790
|
}
|
|
1530
|
-
async function
|
|
1791
|
+
async function runPkgSyncWithCallbacks(args, callbacks) {
|
|
1531
1792
|
const { values, positionals } = parseArgs2({
|
|
1532
1793
|
args,
|
|
1533
1794
|
options: {
|
|
@@ -1539,12 +1800,19 @@ async function runPkgSync(args) {
|
|
|
1539
1800
|
allowPositionals: true
|
|
1540
1801
|
});
|
|
1541
1802
|
try {
|
|
1542
|
-
await
|
|
1803
|
+
if (!await commandExists("brew")) {
|
|
1804
|
+
callbacks.onLog(`${colors3.red}Error: Homebrew not installed${colors3.reset}`);
|
|
1805
|
+
return { output: "Homebrew not installed", success: false };
|
|
1806
|
+
}
|
|
1543
1807
|
} catch {
|
|
1544
1808
|
return { output: "Homebrew not installed", success: false };
|
|
1545
1809
|
}
|
|
1810
|
+
if (values["upgrade-interactive"]) {
|
|
1811
|
+
await upgradeInteractive(callbacks);
|
|
1812
|
+
return { output: "Interactive upgrade complete", success: true };
|
|
1813
|
+
}
|
|
1546
1814
|
if (values["upgrade-only"]) {
|
|
1547
|
-
const result = await upgradeWithVerification();
|
|
1815
|
+
const result = await upgradeWithVerification(callbacks);
|
|
1548
1816
|
let output = `Upgrade complete
|
|
1549
1817
|
`;
|
|
1550
1818
|
if (result.succeeded.length > 0) {
|
|
@@ -1561,7 +1829,7 @@ async function runPkgSync(args) {
|
|
|
1561
1829
|
if (values.purge) {
|
|
1562
1830
|
config.config.purge = true;
|
|
1563
1831
|
}
|
|
1564
|
-
await syncPackages(config);
|
|
1832
|
+
await syncPackages(config, callbacks);
|
|
1565
1833
|
return { output: "Sync complete", success: true };
|
|
1566
1834
|
}
|
|
1567
1835
|
async function main2() {
|
|
@@ -1957,12 +2225,12 @@ if (isMainModule4) {
|
|
|
1957
2225
|
}
|
|
1958
2226
|
|
|
1959
2227
|
// src/cli/formalconf.tsx
|
|
1960
|
-
import { jsxDEV as
|
|
2228
|
+
import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime";
|
|
1961
2229
|
function MainMenu({ onSelect }) {
|
|
1962
2230
|
const { exit } = useApp();
|
|
1963
|
-
return /* @__PURE__ */
|
|
2231
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
1964
2232
|
title: "Main Menu",
|
|
1965
|
-
children: /* @__PURE__ */
|
|
2233
|
+
children: /* @__PURE__ */ jsxDEV12(VimSelect, {
|
|
1966
2234
|
options: [
|
|
1967
2235
|
{ label: "Config Manager", value: "config" },
|
|
1968
2236
|
{ label: "Package Sync", value: "packages" },
|
|
@@ -1980,10 +2248,10 @@ function MainMenu({ onSelect }) {
|
|
|
1980
2248
|
}, undefined, false, undefined, this);
|
|
1981
2249
|
}
|
|
1982
2250
|
function ConfigMenu({ onBack }) {
|
|
1983
|
-
const [state, setState] =
|
|
1984
|
-
const [output, setOutput] =
|
|
1985
|
-
const [success, setSuccess] =
|
|
1986
|
-
|
|
2251
|
+
const [state, setState] = useState5("menu");
|
|
2252
|
+
const [output, setOutput] = useState5("");
|
|
2253
|
+
const [success, setSuccess] = useState5(true);
|
|
2254
|
+
useInput5((input, key) => {
|
|
1987
2255
|
if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
|
|
1988
2256
|
onBack();
|
|
1989
2257
|
}
|
|
@@ -2000,24 +2268,24 @@ function ConfigMenu({ onBack }) {
|
|
|
2000
2268
|
setState("result");
|
|
2001
2269
|
};
|
|
2002
2270
|
if (state === "running") {
|
|
2003
|
-
return /* @__PURE__ */
|
|
2271
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2004
2272
|
title: "Config Manager",
|
|
2005
|
-
children: /* @__PURE__ */
|
|
2273
|
+
children: /* @__PURE__ */ jsxDEV12(Spinner, {
|
|
2006
2274
|
label: "Processing..."
|
|
2007
2275
|
}, undefined, false, undefined, this)
|
|
2008
2276
|
}, undefined, false, undefined, this);
|
|
2009
2277
|
}
|
|
2010
2278
|
if (state === "result") {
|
|
2011
|
-
return /* @__PURE__ */
|
|
2279
|
+
return /* @__PURE__ */ jsxDEV12(CommandOutput, {
|
|
2012
2280
|
title: "Config Manager",
|
|
2013
2281
|
output,
|
|
2014
2282
|
success,
|
|
2015
2283
|
onDismiss: () => setState("menu")
|
|
2016
2284
|
}, undefined, false, undefined, this);
|
|
2017
2285
|
}
|
|
2018
|
-
return /* @__PURE__ */
|
|
2286
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2019
2287
|
title: "Config Manager",
|
|
2020
|
-
children: /* @__PURE__ */
|
|
2288
|
+
children: /* @__PURE__ */ jsxDEV12(VimSelect, {
|
|
2021
2289
|
options: [
|
|
2022
2290
|
{ label: "Stow all packages", value: "stow-all" },
|
|
2023
2291
|
{ label: "Unstow all packages", value: "unstow-all" },
|
|
@@ -2030,66 +2298,145 @@ function ConfigMenu({ onBack }) {
|
|
|
2030
2298
|
}, undefined, false, undefined, this);
|
|
2031
2299
|
}
|
|
2032
2300
|
function PackageMenu({ onBack }) {
|
|
2033
|
-
const [state, setState] =
|
|
2034
|
-
const [
|
|
2035
|
-
const [
|
|
2036
|
-
|
|
2301
|
+
const [state, setState] = useState5("menu");
|
|
2302
|
+
const [lines, setLines] = useState5([]);
|
|
2303
|
+
const [output, setOutput] = useState5("");
|
|
2304
|
+
const [isStreamingOp, setIsStreamingOp] = useState5(true);
|
|
2305
|
+
const [pendingPrompt, setPendingPrompt] = useState5(null);
|
|
2306
|
+
const [success, setSuccess] = useState5(true);
|
|
2307
|
+
const isRunningRef = useRef(false);
|
|
2308
|
+
useInput5((input, key) => {
|
|
2037
2309
|
if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
|
|
2038
2310
|
onBack();
|
|
2039
2311
|
}
|
|
2312
|
+
if (state === "result") {
|
|
2313
|
+
setState("menu");
|
|
2314
|
+
setLines([]);
|
|
2315
|
+
}
|
|
2040
2316
|
});
|
|
2317
|
+
const callbacks = useMemo2(() => ({
|
|
2318
|
+
onLog: (line) => {
|
|
2319
|
+
setLines((prev) => [...prev, line]);
|
|
2320
|
+
},
|
|
2321
|
+
onPrompt: (question, options) => {
|
|
2322
|
+
return new Promise((resolve) => {
|
|
2323
|
+
setPendingPrompt({ question, options, resolve });
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
}), []);
|
|
2327
|
+
const handlePromptAnswer = useCallback((answer) => {
|
|
2328
|
+
if (pendingPrompt) {
|
|
2329
|
+
setLines((prev) => [...prev, `> ${answer}`]);
|
|
2330
|
+
pendingPrompt.resolve(answer);
|
|
2331
|
+
setPendingPrompt(null);
|
|
2332
|
+
}
|
|
2333
|
+
}, [pendingPrompt]);
|
|
2041
2334
|
const handleAction = async (action) => {
|
|
2042
2335
|
if (action === "back") {
|
|
2043
2336
|
onBack();
|
|
2044
2337
|
return;
|
|
2045
2338
|
}
|
|
2339
|
+
if (isRunningRef.current)
|
|
2340
|
+
return;
|
|
2341
|
+
isRunningRef.current = true;
|
|
2046
2342
|
setState("running");
|
|
2343
|
+
setLines([]);
|
|
2344
|
+
setOutput("");
|
|
2345
|
+
setPendingPrompt(null);
|
|
2047
2346
|
let result;
|
|
2048
2347
|
switch (action) {
|
|
2049
2348
|
case "sync":
|
|
2050
|
-
|
|
2349
|
+
setIsStreamingOp(true);
|
|
2350
|
+
result = await runPkgSyncWithCallbacks([], callbacks);
|
|
2051
2351
|
break;
|
|
2052
2352
|
case "sync-purge":
|
|
2053
|
-
|
|
2353
|
+
setIsStreamingOp(true);
|
|
2354
|
+
result = await runPkgSyncWithCallbacks(["--purge"], callbacks);
|
|
2054
2355
|
break;
|
|
2055
2356
|
case "upgrade":
|
|
2056
|
-
|
|
2357
|
+
setIsStreamingOp(true);
|
|
2358
|
+
result = await runPkgSyncWithCallbacks(["--upgrade-only"], callbacks);
|
|
2057
2359
|
break;
|
|
2058
2360
|
case "upgrade-interactive":
|
|
2059
|
-
|
|
2361
|
+
setIsStreamingOp(true);
|
|
2362
|
+
result = await runPkgSyncWithCallbacks(["--upgrade-interactive"], callbacks);
|
|
2060
2363
|
break;
|
|
2061
2364
|
case "lock-update":
|
|
2365
|
+
setIsStreamingOp(false);
|
|
2062
2366
|
result = await runPkgLock(["update"]);
|
|
2367
|
+
setOutput(result.output);
|
|
2063
2368
|
break;
|
|
2064
2369
|
case "lock-status":
|
|
2370
|
+
setIsStreamingOp(false);
|
|
2065
2371
|
result = await runPkgLock(["status"]);
|
|
2372
|
+
setOutput(result.output);
|
|
2066
2373
|
break;
|
|
2067
2374
|
default:
|
|
2375
|
+
setIsStreamingOp(false);
|
|
2068
2376
|
result = { output: "Unknown action", success: false };
|
|
2377
|
+
setOutput(result.output);
|
|
2069
2378
|
}
|
|
2070
|
-
setOutput(result.output);
|
|
2071
2379
|
setSuccess(result.success);
|
|
2072
2380
|
setState("result");
|
|
2381
|
+
isRunningRef.current = false;
|
|
2073
2382
|
};
|
|
2074
2383
|
if (state === "running") {
|
|
2075
|
-
|
|
2384
|
+
if (!isStreamingOp) {
|
|
2385
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2386
|
+
title: "Package Sync",
|
|
2387
|
+
children: /* @__PURE__ */ jsxDEV12(Spinner, {
|
|
2388
|
+
label: "Processing..."
|
|
2389
|
+
}, undefined, false, undefined, this)
|
|
2390
|
+
}, undefined, false, undefined, this);
|
|
2391
|
+
}
|
|
2392
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2076
2393
|
title: "Package Sync",
|
|
2077
|
-
children:
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2394
|
+
children: [
|
|
2395
|
+
/* @__PURE__ */ jsxDEV12(ScrollableLog, {
|
|
2396
|
+
lines
|
|
2397
|
+
}, undefined, false, undefined, this),
|
|
2398
|
+
pendingPrompt && /* @__PURE__ */ jsxDEV12(PromptInput, {
|
|
2399
|
+
question: pendingPrompt.question,
|
|
2400
|
+
options: pendingPrompt.options,
|
|
2401
|
+
onAnswer: handlePromptAnswer
|
|
2402
|
+
}, undefined, false, undefined, this)
|
|
2403
|
+
]
|
|
2404
|
+
}, undefined, true, undefined, this);
|
|
2081
2405
|
}
|
|
2082
2406
|
if (state === "result") {
|
|
2083
|
-
|
|
2407
|
+
if (!isStreamingOp) {
|
|
2408
|
+
return /* @__PURE__ */ jsxDEV12(CommandOutput, {
|
|
2409
|
+
title: "Package Sync",
|
|
2410
|
+
output,
|
|
2411
|
+
success,
|
|
2412
|
+
onDismiss: () => setState("menu")
|
|
2413
|
+
}, undefined, false, undefined, this);
|
|
2414
|
+
}
|
|
2415
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2084
2416
|
title: "Package Sync",
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2417
|
+
borderColor: success ? colors.success : colors.error,
|
|
2418
|
+
children: [
|
|
2419
|
+
/* @__PURE__ */ jsxDEV12(ScrollableLog, {
|
|
2420
|
+
lines,
|
|
2421
|
+
autoScroll: false
|
|
2422
|
+
}, undefined, false, undefined, this),
|
|
2423
|
+
/* @__PURE__ */ jsxDEV12(Box12, {
|
|
2424
|
+
marginTop: 1,
|
|
2425
|
+
children: /* @__PURE__ */ jsxDEV12(Text11, {
|
|
2426
|
+
color: success ? colors.success : colors.error,
|
|
2427
|
+
children: success ? "Done" : "Failed"
|
|
2428
|
+
}, undefined, false, undefined, this)
|
|
2429
|
+
}, undefined, false, undefined, this),
|
|
2430
|
+
/* @__PURE__ */ jsxDEV12(Text11, {
|
|
2431
|
+
dimColor: true,
|
|
2432
|
+
children: "Press any key to continue..."
|
|
2433
|
+
}, undefined, false, undefined, this)
|
|
2434
|
+
]
|
|
2435
|
+
}, undefined, true, undefined, this);
|
|
2089
2436
|
}
|
|
2090
|
-
return /* @__PURE__ */
|
|
2437
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2091
2438
|
title: "Package Sync",
|
|
2092
|
-
children: /* @__PURE__ */
|
|
2439
|
+
children: /* @__PURE__ */ jsxDEV12(VimSelect, {
|
|
2093
2440
|
options: [
|
|
2094
2441
|
{ label: "Sync packages", value: "sync" },
|
|
2095
2442
|
{ label: "Sync with purge", value: "sync-purge" },
|
|
@@ -2104,45 +2451,45 @@ function PackageMenu({ onBack }) {
|
|
|
2104
2451
|
}, undefined, false, undefined, this);
|
|
2105
2452
|
}
|
|
2106
2453
|
function ThemeMenu({ onBack }) {
|
|
2107
|
-
const [themes, setThemes] =
|
|
2108
|
-
const [loading, setLoading] =
|
|
2109
|
-
const [selectedIndex, setSelectedIndex] =
|
|
2110
|
-
const [state, setState] =
|
|
2111
|
-
const [output, setOutput] =
|
|
2112
|
-
const [success, setSuccess] =
|
|
2454
|
+
const [themes, setThemes] = useState5([]);
|
|
2455
|
+
const [loading, setLoading] = useState5(true);
|
|
2456
|
+
const [selectedIndex, setSelectedIndex] = useState5(0);
|
|
2457
|
+
const [state, setState] = useState5("menu");
|
|
2458
|
+
const [output, setOutput] = useState5("");
|
|
2459
|
+
const [success, setSuccess] = useState5(true);
|
|
2113
2460
|
const { columns, rows } = useTerminalSize();
|
|
2114
2461
|
const CARD_HEIGHT = 3;
|
|
2115
2462
|
const LAYOUT_OVERHEAD = 20;
|
|
2116
|
-
const cardWidth =
|
|
2463
|
+
const cardWidth = useMemo2(() => {
|
|
2117
2464
|
const availableWidth = columns - 6;
|
|
2118
2465
|
const cardsPerRow2 = Math.max(1, Math.floor(availableWidth / 28));
|
|
2119
2466
|
return Math.floor(availableWidth / cardsPerRow2);
|
|
2120
2467
|
}, [columns]);
|
|
2121
|
-
const cardsPerRow =
|
|
2468
|
+
const cardsPerRow = useMemo2(() => {
|
|
2122
2469
|
const availableWidth = columns - 6;
|
|
2123
2470
|
return Math.max(1, Math.floor(availableWidth / 28));
|
|
2124
2471
|
}, [columns]);
|
|
2125
|
-
const visibleRows =
|
|
2472
|
+
const visibleRows = useMemo2(() => {
|
|
2126
2473
|
const availableHeight = rows - LAYOUT_OVERHEAD;
|
|
2127
2474
|
return Math.max(1, Math.floor(availableHeight / CARD_HEIGHT));
|
|
2128
2475
|
}, [rows]);
|
|
2129
2476
|
const selectedRow = Math.floor(selectedIndex / cardsPerRow);
|
|
2130
2477
|
const totalRows = Math.ceil(themes.length / cardsPerRow);
|
|
2131
|
-
const [scrollOffset, setScrollOffset] =
|
|
2132
|
-
|
|
2478
|
+
const [scrollOffset, setScrollOffset] = useState5(0);
|
|
2479
|
+
useEffect4(() => {
|
|
2133
2480
|
if (selectedRow < scrollOffset) {
|
|
2134
2481
|
setScrollOffset(selectedRow);
|
|
2135
2482
|
} else if (selectedRow >= scrollOffset + visibleRows) {
|
|
2136
2483
|
setScrollOffset(selectedRow - visibleRows + 1);
|
|
2137
2484
|
}
|
|
2138
2485
|
}, [selectedRow, scrollOffset, visibleRows]);
|
|
2139
|
-
const visibleThemes =
|
|
2486
|
+
const visibleThemes = useMemo2(() => {
|
|
2140
2487
|
const startIdx = scrollOffset * cardsPerRow;
|
|
2141
2488
|
const endIdx = (scrollOffset + visibleRows) * cardsPerRow;
|
|
2142
2489
|
return themes.slice(startIdx, endIdx);
|
|
2143
2490
|
}, [themes, scrollOffset, visibleRows, cardsPerRow]);
|
|
2144
2491
|
const visibleStartIndex = scrollOffset * cardsPerRow;
|
|
2145
|
-
|
|
2492
|
+
useInput5((input, key) => {
|
|
2146
2493
|
if (state !== "menu" || loading)
|
|
2147
2494
|
return;
|
|
2148
2495
|
if (key.escape) {
|
|
@@ -2175,7 +2522,7 @@ function ThemeMenu({ onBack }) {
|
|
|
2175
2522
|
applyTheme2(themes[selectedIndex]);
|
|
2176
2523
|
}
|
|
2177
2524
|
});
|
|
2178
|
-
|
|
2525
|
+
useEffect4(() => {
|
|
2179
2526
|
async function loadThemes() {
|
|
2180
2527
|
if (!existsSync6(THEMES_DIR)) {
|
|
2181
2528
|
setThemes([]);
|
|
@@ -2205,15 +2552,15 @@ function ThemeMenu({ onBack }) {
|
|
|
2205
2552
|
setState("result");
|
|
2206
2553
|
};
|
|
2207
2554
|
if (loading || state === "running") {
|
|
2208
|
-
return /* @__PURE__ */
|
|
2555
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2209
2556
|
title: "Select Theme",
|
|
2210
|
-
children: /* @__PURE__ */
|
|
2557
|
+
children: /* @__PURE__ */ jsxDEV12(Spinner, {
|
|
2211
2558
|
label: loading ? "Loading themes..." : "Applying theme..."
|
|
2212
2559
|
}, undefined, false, undefined, this)
|
|
2213
2560
|
}, undefined, false, undefined, this);
|
|
2214
2561
|
}
|
|
2215
2562
|
if (state === "result") {
|
|
2216
|
-
return /* @__PURE__ */
|
|
2563
|
+
return /* @__PURE__ */ jsxDEV12(CommandOutput, {
|
|
2217
2564
|
title: "Select Theme",
|
|
2218
2565
|
output,
|
|
2219
2566
|
success,
|
|
@@ -2221,28 +2568,28 @@ function ThemeMenu({ onBack }) {
|
|
|
2221
2568
|
}, undefined, false, undefined, this);
|
|
2222
2569
|
}
|
|
2223
2570
|
if (themes.length === 0) {
|
|
2224
|
-
return /* @__PURE__ */
|
|
2571
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2225
2572
|
title: "Select Theme",
|
|
2226
2573
|
children: [
|
|
2227
|
-
/* @__PURE__ */
|
|
2574
|
+
/* @__PURE__ */ jsxDEV12(Box12, {
|
|
2228
2575
|
flexDirection: "column",
|
|
2229
2576
|
children: [
|
|
2230
|
-
/* @__PURE__ */
|
|
2577
|
+
/* @__PURE__ */ jsxDEV12(Text11, {
|
|
2231
2578
|
color: colors.warning,
|
|
2232
2579
|
children: "No themes available."
|
|
2233
2580
|
}, undefined, false, undefined, this),
|
|
2234
|
-
/* @__PURE__ */
|
|
2581
|
+
/* @__PURE__ */ jsxDEV12(Text11, {
|
|
2235
2582
|
children: "This system is compatible with omarchy themes."
|
|
2236
2583
|
}, undefined, false, undefined, this),
|
|
2237
|
-
/* @__PURE__ */
|
|
2584
|
+
/* @__PURE__ */ jsxDEV12(Text11, {
|
|
2238
2585
|
dimColor: true,
|
|
2239
2586
|
children: "Add themes to ~/.config/formalconf/themes/"
|
|
2240
2587
|
}, undefined, false, undefined, this)
|
|
2241
2588
|
]
|
|
2242
2589
|
}, undefined, true, undefined, this),
|
|
2243
|
-
/* @__PURE__ */
|
|
2590
|
+
/* @__PURE__ */ jsxDEV12(Box12, {
|
|
2244
2591
|
marginTop: 1,
|
|
2245
|
-
children: /* @__PURE__ */
|
|
2592
|
+
children: /* @__PURE__ */ jsxDEV12(VimSelect, {
|
|
2246
2593
|
options: [{ label: "Back", value: "back" }],
|
|
2247
2594
|
onChange: () => onBack()
|
|
2248
2595
|
}, undefined, false, undefined, this)
|
|
@@ -2253,10 +2600,10 @@ function ThemeMenu({ onBack }) {
|
|
|
2253
2600
|
const showScrollUp = scrollOffset > 0;
|
|
2254
2601
|
const showScrollDown = scrollOffset + visibleRows < totalRows;
|
|
2255
2602
|
const gridHeight = visibleRows * CARD_HEIGHT;
|
|
2256
|
-
return /* @__PURE__ */
|
|
2603
|
+
return /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2257
2604
|
title: "Select Theme",
|
|
2258
2605
|
children: [
|
|
2259
|
-
showScrollUp && /* @__PURE__ */
|
|
2606
|
+
showScrollUp && /* @__PURE__ */ jsxDEV12(Text11, {
|
|
2260
2607
|
dimColor: true,
|
|
2261
2608
|
children: [
|
|
2262
2609
|
" ↑ ",
|
|
@@ -2265,18 +2612,18 @@ function ThemeMenu({ onBack }) {
|
|
|
2265
2612
|
scrollOffset > 1 ? "s" : ""
|
|
2266
2613
|
]
|
|
2267
2614
|
}, undefined, true, undefined, this),
|
|
2268
|
-
/* @__PURE__ */
|
|
2615
|
+
/* @__PURE__ */ jsxDEV12(Box12, {
|
|
2269
2616
|
flexDirection: "row",
|
|
2270
2617
|
flexWrap: "wrap",
|
|
2271
2618
|
height: gridHeight,
|
|
2272
2619
|
overflow: "hidden",
|
|
2273
|
-
children: visibleThemes.map((theme, index) => /* @__PURE__ */
|
|
2620
|
+
children: visibleThemes.map((theme, index) => /* @__PURE__ */ jsxDEV12(ThemeCard, {
|
|
2274
2621
|
theme,
|
|
2275
2622
|
isSelected: visibleStartIndex + index === selectedIndex,
|
|
2276
2623
|
width: cardWidth
|
|
2277
2624
|
}, theme.path, false, undefined, this))
|
|
2278
2625
|
}, undefined, false, undefined, this),
|
|
2279
|
-
showScrollDown && /* @__PURE__ */
|
|
2626
|
+
showScrollDown && /* @__PURE__ */ jsxDEV12(Text11, {
|
|
2280
2627
|
dimColor: true,
|
|
2281
2628
|
children: [
|
|
2282
2629
|
" ↓ ",
|
|
@@ -2285,9 +2632,9 @@ function ThemeMenu({ onBack }) {
|
|
|
2285
2632
|
totalRows - scrollOffset - visibleRows > 1 ? "s" : ""
|
|
2286
2633
|
]
|
|
2287
2634
|
}, undefined, true, undefined, this),
|
|
2288
|
-
/* @__PURE__ */
|
|
2635
|
+
/* @__PURE__ */ jsxDEV12(Box12, {
|
|
2289
2636
|
marginTop: 1,
|
|
2290
|
-
children: /* @__PURE__ */
|
|
2637
|
+
children: /* @__PURE__ */ jsxDEV12(Text11, {
|
|
2291
2638
|
dimColor: true,
|
|
2292
2639
|
children: "←→↑↓/hjkl navigate • Enter select • Esc back"
|
|
2293
2640
|
}, undefined, false, undefined, this)
|
|
@@ -2295,16 +2642,76 @@ function ThemeMenu({ onBack }) {
|
|
|
2295
2642
|
]
|
|
2296
2643
|
}, undefined, true, undefined, this);
|
|
2297
2644
|
}
|
|
2645
|
+
function PrerequisiteError({
|
|
2646
|
+
missing,
|
|
2647
|
+
onExit
|
|
2648
|
+
}) {
|
|
2649
|
+
useInput5(() => onExit());
|
|
2650
|
+
return /* @__PURE__ */ jsxDEV12(Layout, {
|
|
2651
|
+
breadcrumb: ["Error"],
|
|
2652
|
+
children: /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2653
|
+
title: "Missing Prerequisites",
|
|
2654
|
+
borderColor: colors.error,
|
|
2655
|
+
children: [
|
|
2656
|
+
/* @__PURE__ */ jsxDEV12(Text11, {
|
|
2657
|
+
color: colors.error,
|
|
2658
|
+
children: "Required tools are not installed:"
|
|
2659
|
+
}, undefined, false, undefined, this),
|
|
2660
|
+
/* @__PURE__ */ jsxDEV12(Box12, {
|
|
2661
|
+
flexDirection: "column",
|
|
2662
|
+
marginTop: 1,
|
|
2663
|
+
children: missing.map((dep) => /* @__PURE__ */ jsxDEV12(Box12, {
|
|
2664
|
+
children: [
|
|
2665
|
+
/* @__PURE__ */ jsxDEV12(Text11, {
|
|
2666
|
+
color: colors.warning,
|
|
2667
|
+
children: [
|
|
2668
|
+
"• ",
|
|
2669
|
+
dep.name
|
|
2670
|
+
]
|
|
2671
|
+
}, undefined, true, undefined, this),
|
|
2672
|
+
/* @__PURE__ */ jsxDEV12(Text11, {
|
|
2673
|
+
dimColor: true,
|
|
2674
|
+
children: [
|
|
2675
|
+
" — Install: ",
|
|
2676
|
+
dep.install
|
|
2677
|
+
]
|
|
2678
|
+
}, undefined, true, undefined, this)
|
|
2679
|
+
]
|
|
2680
|
+
}, dep.name, true, undefined, this))
|
|
2681
|
+
}, undefined, false, undefined, this),
|
|
2682
|
+
/* @__PURE__ */ jsxDEV12(Box12, {
|
|
2683
|
+
marginTop: 1,
|
|
2684
|
+
children: /* @__PURE__ */ jsxDEV12(Text11, {
|
|
2685
|
+
dimColor: true,
|
|
2686
|
+
children: "Press any key to exit..."
|
|
2687
|
+
}, undefined, false, undefined, this)
|
|
2688
|
+
}, undefined, false, undefined, this)
|
|
2689
|
+
]
|
|
2690
|
+
}, undefined, true, undefined, this)
|
|
2691
|
+
}, undefined, false, undefined, this);
|
|
2692
|
+
}
|
|
2298
2693
|
function App() {
|
|
2299
|
-
const [
|
|
2694
|
+
const [appState, setAppState] = useState5("loading");
|
|
2695
|
+
const [missingDeps, setMissingDeps] = useState5([]);
|
|
2696
|
+
const [screen, setScreen] = useState5("main");
|
|
2300
2697
|
const { exit } = useApp();
|
|
2301
|
-
|
|
2698
|
+
useInput5((input) => {
|
|
2302
2699
|
if (input === "q") {
|
|
2303
2700
|
exit();
|
|
2304
2701
|
}
|
|
2305
2702
|
});
|
|
2306
|
-
|
|
2307
|
-
|
|
2703
|
+
useEffect4(() => {
|
|
2704
|
+
async function init() {
|
|
2705
|
+
ensureConfigDir();
|
|
2706
|
+
const result = await checkPrerequisites();
|
|
2707
|
+
if (!result.ok) {
|
|
2708
|
+
setMissingDeps(result.missing);
|
|
2709
|
+
setAppState("error");
|
|
2710
|
+
} else {
|
|
2711
|
+
setAppState("ready");
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
init();
|
|
2308
2715
|
}, []);
|
|
2309
2716
|
const getBreadcrumb = () => {
|
|
2310
2717
|
switch (screen) {
|
|
@@ -2318,22 +2725,39 @@ function App() {
|
|
|
2318
2725
|
return ["Main"];
|
|
2319
2726
|
}
|
|
2320
2727
|
};
|
|
2321
|
-
|
|
2728
|
+
if (appState === "loading") {
|
|
2729
|
+
return /* @__PURE__ */ jsxDEV12(Layout, {
|
|
2730
|
+
breadcrumb: ["Loading"],
|
|
2731
|
+
children: /* @__PURE__ */ jsxDEV12(Panel, {
|
|
2732
|
+
title: "FormalConf",
|
|
2733
|
+
children: /* @__PURE__ */ jsxDEV12(Spinner, {
|
|
2734
|
+
label: "Checking prerequisites..."
|
|
2735
|
+
}, undefined, false, undefined, this)
|
|
2736
|
+
}, undefined, false, undefined, this)
|
|
2737
|
+
}, undefined, false, undefined, this);
|
|
2738
|
+
}
|
|
2739
|
+
if (appState === "error") {
|
|
2740
|
+
return /* @__PURE__ */ jsxDEV12(PrerequisiteError, {
|
|
2741
|
+
missing: missingDeps,
|
|
2742
|
+
onExit: exit
|
|
2743
|
+
}, undefined, false, undefined, this);
|
|
2744
|
+
}
|
|
2745
|
+
return /* @__PURE__ */ jsxDEV12(Layout, {
|
|
2322
2746
|
breadcrumb: getBreadcrumb(),
|
|
2323
2747
|
children: [
|
|
2324
|
-
screen === "main" && /* @__PURE__ */
|
|
2748
|
+
screen === "main" && /* @__PURE__ */ jsxDEV12(MainMenu, {
|
|
2325
2749
|
onSelect: setScreen
|
|
2326
2750
|
}, undefined, false, undefined, this),
|
|
2327
|
-
screen === "config" && /* @__PURE__ */
|
|
2751
|
+
screen === "config" && /* @__PURE__ */ jsxDEV12(ConfigMenu, {
|
|
2328
2752
|
onBack: () => setScreen("main")
|
|
2329
2753
|
}, undefined, false, undefined, this),
|
|
2330
|
-
screen === "packages" && /* @__PURE__ */
|
|
2754
|
+
screen === "packages" && /* @__PURE__ */ jsxDEV12(PackageMenu, {
|
|
2331
2755
|
onBack: () => setScreen("main")
|
|
2332
2756
|
}, undefined, false, undefined, this),
|
|
2333
|
-
screen === "themes" && /* @__PURE__ */
|
|
2757
|
+
screen === "themes" && /* @__PURE__ */ jsxDEV12(ThemeMenu, {
|
|
2334
2758
|
onBack: () => setScreen("main")
|
|
2335
2759
|
}, undefined, false, undefined, this)
|
|
2336
2760
|
]
|
|
2337
2761
|
}, undefined, true, undefined, this);
|
|
2338
2762
|
}
|
|
2339
|
-
render(/* @__PURE__ */
|
|
2763
|
+
render(/* @__PURE__ */ jsxDEV12(App, {}, undefined, false, undefined, this));
|