formalconf 2.0.6 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/formalconf.js +2235 -541
- package/package.json +1 -1
package/dist/formalconf.js
CHANGED
|
@@ -1,54 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { useState as useState11, useEffect as useEffect6 } from "react";
|
|
4
|
-
import { render, useApp as useApp2, useInput as useInput11 } from "ink";
|
|
5
|
-
import { Spinner as Spinner2 } from "@inkjs/ui";
|
|
6
|
-
|
|
7
|
-
// src/components/layout/Layout.tsx
|
|
8
|
-
import { Box as Box5 } from "ink";
|
|
9
|
-
|
|
10
|
-
// src/hooks/useTerminalSize.ts
|
|
11
|
-
import { useStdout } from "ink";
|
|
12
|
-
import { useState, useEffect } from "react";
|
|
13
|
-
function useTerminalSize() {
|
|
14
|
-
const { stdout } = useStdout();
|
|
15
|
-
const [size, setSize] = useState({
|
|
16
|
-
columns: stdout.columns || 80,
|
|
17
|
-
rows: stdout.rows || 24
|
|
18
|
-
});
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
const handleResize = () => {
|
|
21
|
-
setSize({
|
|
22
|
-
columns: stdout.columns || 80,
|
|
23
|
-
rows: stdout.rows || 24
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
stdout.on("resize", handleResize);
|
|
27
|
-
return () => {
|
|
28
|
-
stdout.off("resize", handleResize);
|
|
29
|
-
};
|
|
30
|
-
}, [stdout]);
|
|
31
|
-
return size;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// src/components/Header.tsx
|
|
35
|
-
import { Box as Box2, Text as Text2 } from "ink";
|
|
36
|
-
|
|
37
|
-
// src/hooks/useSystemStatus.ts
|
|
38
|
-
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
39
|
-
import { existsSync, readlinkSync, readdirSync, lstatSync } from "fs";
|
|
40
|
-
|
|
41
|
-
// src/lib/paths.ts
|
|
42
|
-
import { homedir } from "os";
|
|
43
|
-
import { join } from "path";
|
|
44
|
-
import { readdir } from "fs/promises";
|
|
2
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
45
3
|
|
|
46
4
|
// src/lib/runtime.ts
|
|
47
5
|
import { spawn as nodeSpawn } from "child_process";
|
|
48
6
|
import { readFile as nodeReadFile, writeFile as nodeWriteFile, mkdir } from "fs/promises";
|
|
49
7
|
import { dirname } from "path";
|
|
50
8
|
import { fileURLToPath } from "url";
|
|
51
|
-
var isBun = typeof Bun !== "undefined";
|
|
52
9
|
async function exec(command, cwd) {
|
|
53
10
|
if (isBun) {
|
|
54
11
|
const proc = Bun.spawn(command, {
|
|
@@ -261,20 +218,95 @@ async function commandExists(cmd) {
|
|
|
261
218
|
return result.success;
|
|
262
219
|
}
|
|
263
220
|
async function checkPrerequisites() {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
{
|
|
221
|
+
const isMacOS = process.platform === "darwin";
|
|
222
|
+
const common = [
|
|
223
|
+
{
|
|
224
|
+
name: "stow",
|
|
225
|
+
macInstall: "brew install stow",
|
|
226
|
+
linuxInstall: "Install via your package manager (pacman -S stow, apt install stow, etc.)"
|
|
227
|
+
}
|
|
267
228
|
];
|
|
229
|
+
const platformSpecific = isMacOS ? [{ name: "brew", install: "https://brew.sh" }] : [];
|
|
268
230
|
const missing = [];
|
|
269
|
-
for (const dep of
|
|
231
|
+
for (const dep of common) {
|
|
232
|
+
if (!await commandExists(dep.name)) {
|
|
233
|
+
missing.push({
|
|
234
|
+
name: dep.name,
|
|
235
|
+
install: isMacOS ? dep.macInstall : dep.linuxInstall
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const dep of platformSpecific) {
|
|
270
240
|
if (!await commandExists(dep.name)) {
|
|
271
241
|
missing.push(dep);
|
|
272
242
|
}
|
|
273
243
|
}
|
|
244
|
+
if (!isMacOS) {
|
|
245
|
+
const packageManagers = ["pacman", "apt", "dnf"];
|
|
246
|
+
const hasPackageManager = await Promise.all(packageManagers.map((pm) => commandExists(pm))).then((results) => results.some(Boolean));
|
|
247
|
+
if (!hasPackageManager) {
|
|
248
|
+
missing.push({
|
|
249
|
+
name: "package manager",
|
|
250
|
+
install: "No supported package manager found (pacman, apt, or dnf)"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
274
254
|
return { ok: missing.length === 0, missing };
|
|
275
255
|
}
|
|
256
|
+
var isBun;
|
|
257
|
+
var init_runtime = __esm(() => {
|
|
258
|
+
isBun = typeof Bun !== "undefined";
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// src/lib/shell.ts
|
|
262
|
+
var init_shell = __esm(() => {
|
|
263
|
+
init_runtime();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// src/cli/formalconf.tsx
|
|
267
|
+
import { useState as useState11, useEffect as useEffect7 } from "react";
|
|
268
|
+
import { render, useApp as useApp2, useInput as useInput11 } from "ink";
|
|
269
|
+
import { Spinner as Spinner2 } from "@inkjs/ui";
|
|
270
|
+
|
|
271
|
+
// src/components/layout/Layout.tsx
|
|
272
|
+
import { Box as Box5 } from "ink";
|
|
273
|
+
|
|
274
|
+
// src/hooks/useTerminalSize.ts
|
|
275
|
+
import { useStdout } from "ink";
|
|
276
|
+
import { useState, useEffect } from "react";
|
|
277
|
+
function useTerminalSize() {
|
|
278
|
+
const { stdout } = useStdout();
|
|
279
|
+
const [size, setSize] = useState({
|
|
280
|
+
columns: stdout.columns || 80,
|
|
281
|
+
rows: stdout.rows || 24
|
|
282
|
+
});
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
const handleResize = () => {
|
|
285
|
+
setSize({
|
|
286
|
+
columns: stdout.columns || 80,
|
|
287
|
+
rows: stdout.rows || 24
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
stdout.on("resize", handleResize);
|
|
291
|
+
return () => {
|
|
292
|
+
stdout.off("resize", handleResize);
|
|
293
|
+
};
|
|
294
|
+
}, [stdout]);
|
|
295
|
+
return size;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/components/Header.tsx
|
|
299
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
300
|
+
|
|
301
|
+
// src/hooks/useSystemStatus.ts
|
|
302
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
303
|
+
import { existsSync, readlinkSync, readdirSync, lstatSync } from "fs";
|
|
276
304
|
|
|
277
305
|
// src/lib/paths.ts
|
|
306
|
+
init_runtime();
|
|
307
|
+
import { homedir } from "os";
|
|
308
|
+
import { join } from "path";
|
|
309
|
+
import { readdir } from "fs/promises";
|
|
278
310
|
var HOME_DIR = homedir();
|
|
279
311
|
var CONFIG_DIR = join(HOME_DIR, ".config", "formalconf");
|
|
280
312
|
var THEME_TARGET_DIR = join(CONFIG_DIR, "current", "theme");
|
|
@@ -285,6 +317,7 @@ var CONFIGS_DIR = join(CONFIG_DIR, "configs");
|
|
|
285
317
|
var THEMES_DIR = join(CONFIG_DIR, "themes");
|
|
286
318
|
var PKG_CONFIG_PATH = join(CONFIG_DIR, "pkg-config.json");
|
|
287
319
|
var PKG_LOCK_PATH = join(CONFIG_DIR, "pkg-lock.json");
|
|
320
|
+
var THEME_CONFIG_PATH = join(CONFIG_DIR, "theme-config.json");
|
|
288
321
|
async function ensureDir2(path) {
|
|
289
322
|
await ensureDir(path);
|
|
290
323
|
}
|
|
@@ -414,7 +447,7 @@ function StatusIndicator({
|
|
|
414
447
|
// package.json
|
|
415
448
|
var package_default = {
|
|
416
449
|
name: "formalconf",
|
|
417
|
-
version: "2.0.
|
|
450
|
+
version: "2.0.8",
|
|
418
451
|
description: "Dotfiles management TUI for macOS - config management, package sync, and theme switching",
|
|
419
452
|
type: "module",
|
|
420
453
|
main: "./dist/formalconf.js",
|
|
@@ -739,6 +772,7 @@ function VimSelect({ options, onChange, isDisabled = false }) {
|
|
|
739
772
|
// src/lib/templates.ts
|
|
740
773
|
import { join as join3 } from "path";
|
|
741
774
|
import { existsSync as existsSync2 } from "fs";
|
|
775
|
+
init_runtime();
|
|
742
776
|
var EXAMPLE_CONFIG_README = `# Example Stow Config Package
|
|
743
777
|
|
|
744
778
|
This is an example dotfiles package for use with GNU Stow.
|
|
@@ -1337,6 +1371,7 @@ function useBackNavigation({
|
|
|
1337
1371
|
}
|
|
1338
1372
|
|
|
1339
1373
|
// src/cli/config-manager.ts
|
|
1374
|
+
init_shell();
|
|
1340
1375
|
import { parseArgs } from "util";
|
|
1341
1376
|
import { readdirSync as readdirSync2, existsSync as existsSync3, lstatSync as lstatSync2, readlinkSync as readlinkSync2 } from "fs";
|
|
1342
1377
|
var colors2 = {
|
|
@@ -1349,7 +1384,9 @@ var colors2 = {
|
|
|
1349
1384
|
};
|
|
1350
1385
|
async function checkStow() {
|
|
1351
1386
|
if (!await commandExists("stow")) {
|
|
1352
|
-
|
|
1387
|
+
const isMacOS = process.platform === "darwin";
|
|
1388
|
+
const installHint = isMacOS ? "brew install stow" : "Install via your package manager (pacman -S stow, apt install stow, dnf install stow)";
|
|
1389
|
+
console.error(`${colors2.red}Error: GNU Stow is not installed. ${installHint}${colors2.reset}`);
|
|
1353
1390
|
process.exit(1);
|
|
1354
1391
|
}
|
|
1355
1392
|
}
|
|
@@ -1674,7 +1711,7 @@ function ConfigMenu({ onBack }) {
|
|
|
1674
1711
|
}
|
|
1675
1712
|
|
|
1676
1713
|
// src/components/menus/PackageMenu.tsx
|
|
1677
|
-
import { useState as useState8, useCallback as useCallback2, useMemo as useMemo2, useRef } from "react";
|
|
1714
|
+
import { useState as useState8, useCallback as useCallback2, useMemo as useMemo2, useRef, useEffect as useEffect4 } from "react";
|
|
1678
1715
|
import { Box as Box14, Text as Text13, useInput as useInput9 } from "ink";
|
|
1679
1716
|
|
|
1680
1717
|
// src/components/ScrollableLog.tsx
|
|
@@ -1938,29 +1975,76 @@ function OrphanTable({ result, onAction, onDismiss }) {
|
|
|
1938
1975
|
}
|
|
1939
1976
|
|
|
1940
1977
|
// src/cli/pkg-sync.ts
|
|
1978
|
+
init_shell();
|
|
1979
|
+
init_runtime();
|
|
1941
1980
|
import { parseArgs as parseArgs2 } from "util";
|
|
1942
1981
|
|
|
1943
1982
|
// src/lib/config.ts
|
|
1983
|
+
init_runtime();
|
|
1944
1984
|
import { existsSync as existsSync4 } from "fs";
|
|
1945
|
-
var
|
|
1985
|
+
var DEFAULT_CONFIG_V2 = {
|
|
1986
|
+
version: 2,
|
|
1946
1987
|
config: {
|
|
1947
1988
|
purge: false,
|
|
1948
1989
|
purgeInteractive: true,
|
|
1949
1990
|
autoUpdate: true
|
|
1950
1991
|
},
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1992
|
+
global: {
|
|
1993
|
+
packages: []
|
|
1994
|
+
},
|
|
1995
|
+
macos: {
|
|
1996
|
+
taps: [],
|
|
1997
|
+
formulas: [],
|
|
1998
|
+
casks: [],
|
|
1999
|
+
mas: {}
|
|
2000
|
+
}
|
|
1955
2001
|
};
|
|
2002
|
+
function migrateV1toV2(v1Config) {
|
|
2003
|
+
return {
|
|
2004
|
+
version: 2,
|
|
2005
|
+
config: {
|
|
2006
|
+
purge: v1Config.config.purge,
|
|
2007
|
+
purgeInteractive: v1Config.config.purgeInteractive,
|
|
2008
|
+
autoUpdate: v1Config.config.autoUpdate
|
|
2009
|
+
},
|
|
2010
|
+
global: {
|
|
2011
|
+
packages: []
|
|
2012
|
+
},
|
|
2013
|
+
macos: {
|
|
2014
|
+
taps: v1Config.taps,
|
|
2015
|
+
formulas: v1Config.packages,
|
|
2016
|
+
casks: v1Config.casks,
|
|
2017
|
+
mas: v1Config.mas
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
function isV1Config(config) {
|
|
2022
|
+
if (!config || typeof config !== "object")
|
|
2023
|
+
return false;
|
|
2024
|
+
const c = config;
|
|
2025
|
+
return !("version" in c) && "config" in c && "taps" in c && "packages" in c && "casks" in c;
|
|
2026
|
+
}
|
|
2027
|
+
function configIsV2(config) {
|
|
2028
|
+
return "version" in config && config.version === 2;
|
|
2029
|
+
}
|
|
1956
2030
|
async function loadPkgConfig(path) {
|
|
1957
2031
|
await ensureConfigDir();
|
|
1958
2032
|
const configPath = path || PKG_CONFIG_PATH;
|
|
1959
2033
|
if (!existsSync4(configPath)) {
|
|
1960
|
-
await savePkgConfig(
|
|
1961
|
-
return
|
|
2034
|
+
await savePkgConfig(DEFAULT_CONFIG_V2, configPath);
|
|
2035
|
+
return DEFAULT_CONFIG_V2;
|
|
1962
2036
|
}
|
|
1963
|
-
|
|
2037
|
+
const rawConfig = await readJson(configPath);
|
|
2038
|
+
if (isV1Config(rawConfig)) {
|
|
2039
|
+
const v2Config = migrateV1toV2(rawConfig);
|
|
2040
|
+
await savePkgConfig(v2Config, configPath);
|
|
2041
|
+
return v2Config;
|
|
2042
|
+
}
|
|
2043
|
+
if (configIsV2(rawConfig)) {
|
|
2044
|
+
return rawConfig;
|
|
2045
|
+
}
|
|
2046
|
+
await savePkgConfig(DEFAULT_CONFIG_V2, configPath);
|
|
2047
|
+
return DEFAULT_CONFIG_V2;
|
|
1964
2048
|
}
|
|
1965
2049
|
async function savePkgConfig(config, path) {
|
|
1966
2050
|
await ensureConfigDir();
|
|
@@ -1979,249 +2063,1486 @@ async function savePkgLock(lock) {
|
|
|
1979
2063
|
}
|
|
1980
2064
|
|
|
1981
2065
|
// src/lib/lockfile.ts
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2066
|
+
init_shell();
|
|
2067
|
+
|
|
2068
|
+
// src/lib/platform.ts
|
|
2069
|
+
init_runtime();
|
|
2070
|
+
var cachedPlatformInfo = null;
|
|
2071
|
+
function getOS() {
|
|
2072
|
+
return process.platform === "darwin" ? "darwin" : "linux";
|
|
2073
|
+
}
|
|
2074
|
+
async function getLinuxDistro() {
|
|
2075
|
+
if (getOS() !== "linux") {
|
|
2076
|
+
return "unknown";
|
|
2077
|
+
}
|
|
2078
|
+
try {
|
|
2079
|
+
const result = await exec(["cat", "/etc/os-release"]);
|
|
2080
|
+
if (!result.success) {
|
|
2081
|
+
return "unknown";
|
|
2082
|
+
}
|
|
2083
|
+
const lines = result.stdout.split(`
|
|
2084
|
+
`);
|
|
2085
|
+
for (const line of lines) {
|
|
2086
|
+
if (line.startsWith("ID=")) {
|
|
2087
|
+
const id = line.slice(3).replace(/"/g, "").toLowerCase();
|
|
2088
|
+
switch (id) {
|
|
2089
|
+
case "arch":
|
|
2090
|
+
case "manjaro":
|
|
2091
|
+
case "endeavouros":
|
|
2092
|
+
case "artix":
|
|
2093
|
+
return "arch";
|
|
2094
|
+
case "debian":
|
|
2095
|
+
return "debian";
|
|
2096
|
+
case "ubuntu":
|
|
2097
|
+
case "linuxmint":
|
|
2098
|
+
case "pop":
|
|
2099
|
+
case "elementary":
|
|
2100
|
+
return "ubuntu";
|
|
2101
|
+
case "fedora":
|
|
2102
|
+
return "fedora";
|
|
2103
|
+
case "rhel":
|
|
2104
|
+
case "centos":
|
|
2105
|
+
case "rocky":
|
|
2106
|
+
case "almalinux":
|
|
2107
|
+
return "rhel";
|
|
2108
|
+
case "opensuse":
|
|
2109
|
+
case "opensuse-leap":
|
|
2110
|
+
case "opensuse-tumbleweed":
|
|
2111
|
+
return "opensuse";
|
|
2112
|
+
default:
|
|
2113
|
+
const idLikeLine = lines.find((l) => l.startsWith("ID_LIKE="));
|
|
2114
|
+
if (idLikeLine) {
|
|
2115
|
+
const idLike = idLikeLine.slice(8).replace(/"/g, "").toLowerCase();
|
|
2116
|
+
if (idLike.includes("arch"))
|
|
2117
|
+
return "arch";
|
|
2118
|
+
if (idLike.includes("debian") || idLike.includes("ubuntu"))
|
|
2119
|
+
return "debian";
|
|
2120
|
+
if (idLike.includes("fedora") || idLike.includes("rhel"))
|
|
2121
|
+
return "fedora";
|
|
2122
|
+
}
|
|
2123
|
+
return "unknown";
|
|
2003
2124
|
}
|
|
2004
2125
|
}
|
|
2005
2126
|
}
|
|
2127
|
+
} catch {
|
|
2128
|
+
return "unknown";
|
|
2006
2129
|
}
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2130
|
+
return "unknown";
|
|
2131
|
+
}
|
|
2132
|
+
async function detectAurHelper() {
|
|
2133
|
+
const helpers = ["yay", "paru", "trizen"];
|
|
2134
|
+
for (const helper of helpers) {
|
|
2135
|
+
if (await commandExists(helper)) {
|
|
2136
|
+
return helper;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
return "none";
|
|
2140
|
+
}
|
|
2141
|
+
async function detectAvailablePackageManagers() {
|
|
2142
|
+
const os = getOS();
|
|
2143
|
+
const managers = [];
|
|
2144
|
+
if (os === "darwin") {
|
|
2145
|
+
if (await commandExists("brew")) {
|
|
2146
|
+
managers.push("homebrew");
|
|
2147
|
+
}
|
|
2148
|
+
if (await commandExists("mas")) {
|
|
2149
|
+
managers.push("mas");
|
|
2150
|
+
}
|
|
2151
|
+
} else {
|
|
2152
|
+
if (await commandExists("pacman")) {
|
|
2153
|
+
managers.push("pacman");
|
|
2154
|
+
const aurHelper = await detectAurHelper();
|
|
2155
|
+
if (aurHelper !== "none") {
|
|
2156
|
+
managers.push("aur");
|
|
2024
2157
|
}
|
|
2025
2158
|
}
|
|
2159
|
+
if (await commandExists("apt")) {
|
|
2160
|
+
managers.push("apt");
|
|
2161
|
+
}
|
|
2162
|
+
if (await commandExists("dnf")) {
|
|
2163
|
+
managers.push("dnf");
|
|
2164
|
+
}
|
|
2165
|
+
if (await commandExists("flatpak")) {
|
|
2166
|
+
managers.push("flatpak");
|
|
2167
|
+
}
|
|
2026
2168
|
}
|
|
2027
|
-
return
|
|
2169
|
+
return managers;
|
|
2028
2170
|
}
|
|
2029
|
-
async function
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2171
|
+
async function getPlatformInfo() {
|
|
2172
|
+
if (cachedPlatformInfo) {
|
|
2173
|
+
return cachedPlatformInfo;
|
|
2174
|
+
}
|
|
2175
|
+
const os = getOS();
|
|
2176
|
+
const distro = os === "linux" ? await getLinuxDistro() : null;
|
|
2177
|
+
const aurHelper = distro === "arch" ? await detectAurHelper() : null;
|
|
2178
|
+
const availableManagers = await detectAvailablePackageManagers();
|
|
2179
|
+
cachedPlatformInfo = {
|
|
2180
|
+
os,
|
|
2181
|
+
distro,
|
|
2182
|
+
aurHelper,
|
|
2183
|
+
availableManagers
|
|
2036
2184
|
};
|
|
2185
|
+
return cachedPlatformInfo;
|
|
2037
2186
|
}
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
const mergedFormulas = {};
|
|
2042
|
-
for (const [name, info] of Object.entries(formulas)) {
|
|
2043
|
-
const prev = existing?.formulas[name];
|
|
2044
|
-
if (prev && prev.version === info.version) {
|
|
2045
|
-
mergedFormulas[name] = prev;
|
|
2046
|
-
} else {
|
|
2047
|
-
mergedFormulas[name] = info;
|
|
2048
|
-
}
|
|
2187
|
+
function getPlatformDisplayName(info) {
|
|
2188
|
+
if (info.os === "darwin") {
|
|
2189
|
+
return "macOS";
|
|
2049
2190
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2191
|
+
switch (info.distro) {
|
|
2192
|
+
case "arch":
|
|
2193
|
+
return "Arch Linux";
|
|
2194
|
+
case "debian":
|
|
2195
|
+
return "Debian";
|
|
2196
|
+
case "ubuntu":
|
|
2197
|
+
return "Ubuntu";
|
|
2198
|
+
case "fedora":
|
|
2199
|
+
return "Fedora";
|
|
2200
|
+
case "rhel":
|
|
2201
|
+
return "RHEL/CentOS";
|
|
2202
|
+
case "opensuse":
|
|
2203
|
+
return "openSUSE";
|
|
2204
|
+
default:
|
|
2205
|
+
return "Linux";
|
|
2058
2206
|
}
|
|
2059
|
-
const lock = {
|
|
2060
|
-
version: 1,
|
|
2061
|
-
lastUpdated: new Date().toISOString(),
|
|
2062
|
-
formulas: mergedFormulas,
|
|
2063
|
-
casks: mergedCasks
|
|
2064
|
-
};
|
|
2065
|
-
await savePkgLock(lock);
|
|
2066
|
-
return lock;
|
|
2067
2207
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
const
|
|
2074
|
-
if (
|
|
2075
|
-
|
|
2076
|
-
return
|
|
2208
|
+
|
|
2209
|
+
// src/lib/package-managers/homebrew.ts
|
|
2210
|
+
init_runtime();
|
|
2211
|
+
init_runtime();
|
|
2212
|
+
async function runBrewCommand(args, callbacks) {
|
|
2213
|
+
const cmd = ["brew", ...args];
|
|
2214
|
+
if (callbacks?.onLog) {
|
|
2215
|
+
const exitCode = await execStreaming(cmd, callbacks.onLog);
|
|
2216
|
+
return exitCode === 0;
|
|
2077
2217
|
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2218
|
+
const result = await exec(cmd);
|
|
2219
|
+
return result.success;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
class HomebrewFormulas {
|
|
2223
|
+
type = "homebrew";
|
|
2224
|
+
displayName = "Homebrew Formulas";
|
|
2225
|
+
async isAvailable() {
|
|
2226
|
+
return commandExists("brew");
|
|
2227
|
+
}
|
|
2228
|
+
async update(callbacks) {
|
|
2229
|
+
return runBrewCommand(["update"], callbacks);
|
|
2230
|
+
}
|
|
2231
|
+
async install(packages, callbacks) {
|
|
2232
|
+
if (packages.length === 0)
|
|
2233
|
+
return true;
|
|
2234
|
+
return runBrewCommand(["install", ...packages], callbacks);
|
|
2235
|
+
}
|
|
2236
|
+
async uninstall(packages, callbacks) {
|
|
2237
|
+
if (packages.length === 0)
|
|
2238
|
+
return true;
|
|
2239
|
+
return runBrewCommand(["uninstall", ...packages], callbacks);
|
|
2240
|
+
}
|
|
2241
|
+
async upgrade(packages, callbacks) {
|
|
2242
|
+
const args = ["upgrade", "--formula"];
|
|
2243
|
+
if (packages && packages.length > 0) {
|
|
2244
|
+
args.push(...packages);
|
|
2087
2245
|
}
|
|
2246
|
+
return runBrewCommand(args, callbacks);
|
|
2088
2247
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2248
|
+
async listInstalled() {
|
|
2249
|
+
const result = await exec(["brew", "info", "--json=v2", "--installed"]);
|
|
2250
|
+
if (!result.success)
|
|
2251
|
+
return [];
|
|
2252
|
+
try {
|
|
2253
|
+
const info = JSON.parse(result.stdout);
|
|
2254
|
+
return info.formulae.map((f) => ({
|
|
2255
|
+
name: f.name,
|
|
2256
|
+
version: f.installed[0]?.version || "unknown"
|
|
2257
|
+
}));
|
|
2258
|
+
} catch {
|
|
2259
|
+
return [];
|
|
2092
2260
|
}
|
|
2093
2261
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2262
|
+
async listOutdated() {
|
|
2263
|
+
const result = await exec(["brew", "outdated", "--formula", "--json"]);
|
|
2264
|
+
if (!result.success || !result.stdout)
|
|
2265
|
+
return [];
|
|
2266
|
+
try {
|
|
2267
|
+
const outdated = JSON.parse(result.stdout);
|
|
2268
|
+
return outdated.formulae?.map((f) => ({
|
|
2269
|
+
name: f.name,
|
|
2270
|
+
currentVersion: f.installed_versions?.[0] || "unknown",
|
|
2271
|
+
newVersion: f.current_version
|
|
2272
|
+
})) || [];
|
|
2273
|
+
} catch {
|
|
2274
|
+
const quietResult = await exec(["brew", "outdated", "--formula", "--quiet"]);
|
|
2275
|
+
if (!quietResult.success)
|
|
2276
|
+
return [];
|
|
2277
|
+
return quietResult.stdout.split(`
|
|
2278
|
+
`).filter(Boolean).map((name) => ({
|
|
2099
2279
|
name,
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
});
|
|
2280
|
+
currentVersion: "unknown",
|
|
2281
|
+
newVersion: "unknown"
|
|
2282
|
+
}));
|
|
2103
2283
|
}
|
|
2104
2284
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2285
|
+
async listLeaves() {
|
|
2286
|
+
const result = await exec(["brew", "leaves"]);
|
|
2287
|
+
if (!result.success)
|
|
2288
|
+
return [];
|
|
2289
|
+
return result.stdout.split(`
|
|
2290
|
+
`).filter(Boolean);
|
|
2291
|
+
}
|
|
2292
|
+
async cleanup(callbacks) {
|
|
2293
|
+
const autoremove = await runBrewCommand(["autoremove"], callbacks);
|
|
2294
|
+
const cleanup = await runBrewCommand(["cleanup"], callbacks);
|
|
2295
|
+
return autoremove && cleanup;
|
|
2296
|
+
}
|
|
2297
|
+
async addRepository(repo, callbacks) {
|
|
2298
|
+
return runBrewCommand(["tap", repo], callbacks);
|
|
2299
|
+
}
|
|
2300
|
+
async isInstalled(packages) {
|
|
2301
|
+
const result = new Map;
|
|
2302
|
+
const listResult = await exec(["brew", "list", "--formula"]);
|
|
2303
|
+
const installed = new Set(listResult.stdout.split(`
|
|
2304
|
+
`).filter(Boolean));
|
|
2305
|
+
for (const pkg of packages) {
|
|
2306
|
+
const shortName = pkg.split("/").pop() || pkg;
|
|
2307
|
+
result.set(pkg, installed.has(shortName) || installed.has(pkg));
|
|
2108
2308
|
}
|
|
2309
|
+
return result;
|
|
2310
|
+
}
|
|
2311
|
+
async getTappedRepos() {
|
|
2312
|
+
const result = await exec(["brew", "tap"]);
|
|
2313
|
+
if (!result.success)
|
|
2314
|
+
return [];
|
|
2315
|
+
return result.stdout.split(`
|
|
2316
|
+
`).filter(Boolean);
|
|
2317
|
+
}
|
|
2318
|
+
async hasDependents(pkg) {
|
|
2319
|
+
const result = await exec(["brew", "uses", "--installed", pkg]);
|
|
2320
|
+
return result.success && result.stdout.trim().length > 0;
|
|
2109
2321
|
}
|
|
2110
|
-
return { added, removed, upgraded };
|
|
2111
2322
|
}
|
|
2112
2323
|
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
408981434,
|
|
2119
|
-
682658836,
|
|
2120
|
-
424389933,
|
|
2121
|
-
424390742,
|
|
2122
|
-
413897608,
|
|
2123
|
-
1274495053,
|
|
2124
|
-
425424353,
|
|
2125
|
-
497799835,
|
|
2126
|
-
634148309,
|
|
2127
|
-
1480068668,
|
|
2128
|
-
803453959,
|
|
2129
|
-
1295203466,
|
|
2130
|
-
1444383602,
|
|
2131
|
-
640199958,
|
|
2132
|
-
899247664,
|
|
2133
|
-
1176895641,
|
|
2134
|
-
1451685025
|
|
2135
|
-
];
|
|
2136
|
-
|
|
2137
|
-
// src/cli/pkg-sync.ts
|
|
2138
|
-
var colors3 = {
|
|
2139
|
-
red: "\x1B[0;31m",
|
|
2140
|
-
green: "\x1B[0;32m",
|
|
2141
|
-
blue: "\x1B[0;34m",
|
|
2142
|
-
yellow: "\x1B[1;33m",
|
|
2143
|
-
cyan: "\x1B[0;36m",
|
|
2144
|
-
bold: "\x1B[1m",
|
|
2145
|
-
reset: "\x1B[0m"
|
|
2146
|
-
};
|
|
2147
|
-
async function runCommand(command, callbacks, cwd, needsTTY = false) {
|
|
2148
|
-
if (callbacks) {
|
|
2149
|
-
if (needsTTY) {
|
|
2150
|
-
return execStreamingWithTTY(command, callbacks.onLog, cwd);
|
|
2151
|
-
}
|
|
2152
|
-
return execStreaming(command, callbacks.onLog, cwd);
|
|
2324
|
+
class HomebrewCasks {
|
|
2325
|
+
type = "homebrew";
|
|
2326
|
+
displayName = "Homebrew Casks";
|
|
2327
|
+
async isAvailable() {
|
|
2328
|
+
return commandExists("brew");
|
|
2153
2329
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
async function checkDependencies() {
|
|
2157
|
-
if (!await commandExists("brew")) {
|
|
2158
|
-
console.error(`${colors3.red}Error: Homebrew not installed${colors3.reset}`);
|
|
2159
|
-
process.exit(1);
|
|
2330
|
+
async update(callbacks) {
|
|
2331
|
+
return runBrewCommand(["update"], callbacks);
|
|
2160
2332
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
exec(["brew", "outdated", "--cask", "--quiet"])
|
|
2166
|
-
]);
|
|
2167
|
-
const packages = [];
|
|
2168
|
-
if (formulas.stdout) {
|
|
2169
|
-
packages.push(...formulas.stdout.split(`
|
|
2170
|
-
`).filter(Boolean).map((name) => ({ name, type: "formula" })));
|
|
2333
|
+
async install(packages, callbacks) {
|
|
2334
|
+
if (packages.length === 0)
|
|
2335
|
+
return true;
|
|
2336
|
+
return runBrewCommand(["install", "--cask", ...packages], callbacks);
|
|
2171
2337
|
}
|
|
2172
|
-
|
|
2173
|
-
packages.
|
|
2174
|
-
|
|
2338
|
+
async uninstall(packages, callbacks) {
|
|
2339
|
+
if (packages.length === 0)
|
|
2340
|
+
return true;
|
|
2341
|
+
return runBrewCommand(["uninstall", "--cask", ...packages], callbacks);
|
|
2175
2342
|
}
|
|
2176
|
-
|
|
2343
|
+
async upgrade(packages, callbacks) {
|
|
2344
|
+
const args = ["upgrade", "--cask", "--greedy"];
|
|
2345
|
+
if (packages && packages.length > 0) {
|
|
2346
|
+
args.push(...packages);
|
|
2347
|
+
}
|
|
2348
|
+
return runBrewCommand(args, callbacks);
|
|
2349
|
+
}
|
|
2350
|
+
async listInstalled() {
|
|
2351
|
+
const result = await exec(["brew", "info", "--json=v2", "--cask", "--installed"]);
|
|
2352
|
+
if (!result.success)
|
|
2353
|
+
return [];
|
|
2354
|
+
try {
|
|
2355
|
+
const info = JSON.parse(result.stdout);
|
|
2356
|
+
return info.casks.map((c) => ({
|
|
2357
|
+
name: c.token,
|
|
2358
|
+
version: c.installed || "unknown"
|
|
2359
|
+
}));
|
|
2360
|
+
} catch {
|
|
2361
|
+
return [];
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
async listOutdated() {
|
|
2365
|
+
const result = await exec(["brew", "outdated", "--cask", "--quiet"]);
|
|
2366
|
+
if (!result.success)
|
|
2367
|
+
return [];
|
|
2368
|
+
return result.stdout.split(`
|
|
2369
|
+
`).filter(Boolean).map((name) => ({
|
|
2370
|
+
name,
|
|
2371
|
+
currentVersion: "unknown",
|
|
2372
|
+
newVersion: "unknown"
|
|
2373
|
+
}));
|
|
2374
|
+
}
|
|
2375
|
+
async cleanup(callbacks) {
|
|
2376
|
+
return runBrewCommand(["cleanup"], callbacks);
|
|
2377
|
+
}
|
|
2378
|
+
async isInstalled(packages) {
|
|
2379
|
+
const result = new Map;
|
|
2380
|
+
const listResult = await exec(["brew", "list", "--cask"]);
|
|
2381
|
+
const installed = new Set(listResult.stdout.split(`
|
|
2382
|
+
`).filter(Boolean));
|
|
2383
|
+
for (const pkg of packages) {
|
|
2384
|
+
result.set(pkg, installed.has(pkg));
|
|
2385
|
+
}
|
|
2386
|
+
return result;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
// src/lib/package-managers/mas.ts
|
|
2391
|
+
init_runtime();
|
|
2392
|
+
init_runtime();
|
|
2393
|
+
function parseMasOutput(output) {
|
|
2394
|
+
const results = [];
|
|
2395
|
+
for (const line of output.split(`
|
|
2396
|
+
`).filter(Boolean)) {
|
|
2397
|
+
const match = line.match(/^(\d+)\s+(.+?)(?:\s+\(([^)]+)\))?$/);
|
|
2398
|
+
if (match) {
|
|
2399
|
+
results.push({
|
|
2400
|
+
id: parseInt(match[1], 10),
|
|
2401
|
+
name: match[2].trim(),
|
|
2402
|
+
version: match[3]
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
return results;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
class MacAppStore {
|
|
2410
|
+
type = "mas";
|
|
2411
|
+
displayName = "Mac App Store";
|
|
2412
|
+
async isAvailable() {
|
|
2413
|
+
return commandExists("mas");
|
|
2414
|
+
}
|
|
2415
|
+
async update(_callbacks) {
|
|
2416
|
+
return true;
|
|
2417
|
+
}
|
|
2418
|
+
async install(packages, callbacks) {
|
|
2419
|
+
if (packages.length === 0)
|
|
2420
|
+
return true;
|
|
2421
|
+
for (const appId of packages) {
|
|
2422
|
+
const cmd = ["mas", "install", appId];
|
|
2423
|
+
if (callbacks?.onLog) {
|
|
2424
|
+
const exitCode = await execStreamingWithTTY(cmd, callbacks.onLog);
|
|
2425
|
+
if (exitCode !== 0)
|
|
2426
|
+
return false;
|
|
2427
|
+
} else {
|
|
2428
|
+
const result = await exec(cmd);
|
|
2429
|
+
if (!result.success)
|
|
2430
|
+
return false;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
return true;
|
|
2434
|
+
}
|
|
2435
|
+
async uninstall(packages, callbacks) {
|
|
2436
|
+
if (packages.length === 0)
|
|
2437
|
+
return true;
|
|
2438
|
+
for (const appId of packages) {
|
|
2439
|
+
const cmd = ["mas", "uninstall", appId];
|
|
2440
|
+
if (callbacks?.onLog) {
|
|
2441
|
+
const exitCode = await execStreamingWithTTY(cmd, callbacks.onLog);
|
|
2442
|
+
if (exitCode !== 0)
|
|
2443
|
+
return false;
|
|
2444
|
+
} else {
|
|
2445
|
+
const result = await exec(cmd);
|
|
2446
|
+
if (!result.success)
|
|
2447
|
+
return false;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return true;
|
|
2451
|
+
}
|
|
2452
|
+
async upgrade(_packages, callbacks) {
|
|
2453
|
+
const cmd = ["mas", "upgrade"];
|
|
2454
|
+
if (callbacks?.onLog) {
|
|
2455
|
+
const exitCode = await execStreamingWithTTY(cmd, callbacks.onLog);
|
|
2456
|
+
return exitCode === 0;
|
|
2457
|
+
}
|
|
2458
|
+
const result = await exec(cmd);
|
|
2459
|
+
return result.success;
|
|
2460
|
+
}
|
|
2461
|
+
async listInstalled() {
|
|
2462
|
+
const result = await exec(["mas", "list"]);
|
|
2463
|
+
if (!result.success)
|
|
2464
|
+
return [];
|
|
2465
|
+
const apps = parseMasOutput(result.stdout);
|
|
2466
|
+
return apps.map((app) => ({
|
|
2467
|
+
name: String(app.id),
|
|
2468
|
+
version: app.version || "unknown"
|
|
2469
|
+
}));
|
|
2470
|
+
}
|
|
2471
|
+
async listOutdated() {
|
|
2472
|
+
const result = await exec(["mas", "outdated"]);
|
|
2473
|
+
if (!result.success)
|
|
2474
|
+
return [];
|
|
2475
|
+
const apps = parseMasOutput(result.stdout);
|
|
2476
|
+
return apps.map((app) => ({
|
|
2477
|
+
name: String(app.id),
|
|
2478
|
+
currentVersion: app.version || "unknown",
|
|
2479
|
+
newVersion: "available"
|
|
2480
|
+
}));
|
|
2481
|
+
}
|
|
2482
|
+
async cleanup(_callbacks) {
|
|
2483
|
+
return true;
|
|
2484
|
+
}
|
|
2485
|
+
async isInstalled(packages) {
|
|
2486
|
+
const result = new Map;
|
|
2487
|
+
const listResult = await exec(["mas", "list"]);
|
|
2488
|
+
const apps = parseMasOutput(listResult.stdout);
|
|
2489
|
+
const installedIds = new Set(apps.map((app) => String(app.id)));
|
|
2490
|
+
for (const appId of packages) {
|
|
2491
|
+
result.set(appId, installedIds.has(appId));
|
|
2492
|
+
}
|
|
2493
|
+
return result;
|
|
2494
|
+
}
|
|
2495
|
+
async getInstalledApps() {
|
|
2496
|
+
const result = await exec(["mas", "list"]);
|
|
2497
|
+
if (!result.success)
|
|
2498
|
+
return [];
|
|
2499
|
+
return parseMasOutput(result.stdout);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
// src/lib/package-managers/pacman.ts
|
|
2504
|
+
init_runtime();
|
|
2505
|
+
init_runtime();
|
|
2506
|
+
async function runPacmanCommand(args, callbacks, sudo = false) {
|
|
2507
|
+
const cmd = sudo ? ["sudo", "pacman", ...args] : ["pacman", ...args];
|
|
2508
|
+
if (callbacks?.onLog) {
|
|
2509
|
+
const exitCode = await execStreaming(cmd, callbacks.onLog);
|
|
2510
|
+
return exitCode === 0;
|
|
2511
|
+
}
|
|
2512
|
+
const result = await exec(cmd);
|
|
2513
|
+
return result.success;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
class Pacman {
|
|
2517
|
+
type = "pacman";
|
|
2518
|
+
displayName = "Pacman";
|
|
2519
|
+
async isAvailable() {
|
|
2520
|
+
return commandExists("pacman");
|
|
2521
|
+
}
|
|
2522
|
+
async update(callbacks) {
|
|
2523
|
+
return runPacmanCommand(["-Sy", "--noconfirm"], callbacks, true);
|
|
2524
|
+
}
|
|
2525
|
+
async install(packages, callbacks) {
|
|
2526
|
+
if (packages.length === 0)
|
|
2527
|
+
return true;
|
|
2528
|
+
return runPacmanCommand(["-S", "--noconfirm", "--needed", ...packages], callbacks, true);
|
|
2529
|
+
}
|
|
2530
|
+
async uninstall(packages, callbacks) {
|
|
2531
|
+
if (packages.length === 0)
|
|
2532
|
+
return true;
|
|
2533
|
+
return runPacmanCommand(["-Rs", "--noconfirm", ...packages], callbacks, true);
|
|
2534
|
+
}
|
|
2535
|
+
async upgrade(packages, callbacks) {
|
|
2536
|
+
if (packages && packages.length > 0) {
|
|
2537
|
+
return runPacmanCommand(["-S", "--noconfirm", ...packages], callbacks, true);
|
|
2538
|
+
}
|
|
2539
|
+
return runPacmanCommand(["-Syu", "--noconfirm"], callbacks, true);
|
|
2540
|
+
}
|
|
2541
|
+
async listInstalled() {
|
|
2542
|
+
const result = await exec(["pacman", "-Qe"]);
|
|
2543
|
+
if (!result.success)
|
|
2544
|
+
return [];
|
|
2545
|
+
return result.stdout.split(`
|
|
2546
|
+
`).filter(Boolean).map((line) => {
|
|
2547
|
+
const [name, version] = line.split(" ");
|
|
2548
|
+
return { name, version: version || "unknown" };
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
async listOutdated() {
|
|
2552
|
+
const checkupdatesExists = await commandExists("checkupdates");
|
|
2553
|
+
if (checkupdatesExists) {
|
|
2554
|
+
const result2 = await exec(["checkupdates"]);
|
|
2555
|
+
if (!result2.stdout)
|
|
2556
|
+
return [];
|
|
2557
|
+
return result2.stdout.split(`
|
|
2558
|
+
`).filter(Boolean).map((line) => {
|
|
2559
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+->\s+(\S+)$/);
|
|
2560
|
+
if (match) {
|
|
2561
|
+
return {
|
|
2562
|
+
name: match[1],
|
|
2563
|
+
currentVersion: match[2],
|
|
2564
|
+
newVersion: match[3]
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
return { name: line, currentVersion: "unknown", newVersion: "unknown" };
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2570
|
+
await exec(["sudo", "pacman", "-Sy"]);
|
|
2571
|
+
const result = await exec(["pacman", "-Qu"]);
|
|
2572
|
+
if (!result.success || !result.stdout)
|
|
2573
|
+
return [];
|
|
2574
|
+
return result.stdout.split(`
|
|
2575
|
+
`).filter(Boolean).map((line) => {
|
|
2576
|
+
const [name, ...rest] = line.split(" ");
|
|
2577
|
+
const versionPart = rest.join(" ");
|
|
2578
|
+
const match = versionPart.match(/(\S+)\s+->\s+(\S+)/);
|
|
2579
|
+
return {
|
|
2580
|
+
name,
|
|
2581
|
+
currentVersion: match?.[1] || "unknown",
|
|
2582
|
+
newVersion: match?.[2] || "unknown"
|
|
2583
|
+
};
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
async listLeaves() {
|
|
2587
|
+
const result = await exec(["pacman", "-Qqe"]);
|
|
2588
|
+
if (!result.success)
|
|
2589
|
+
return [];
|
|
2590
|
+
return result.stdout.split(`
|
|
2591
|
+
`).filter(Boolean);
|
|
2592
|
+
}
|
|
2593
|
+
async cleanup(callbacks) {
|
|
2594
|
+
const orphansResult = await exec(["pacman", "-Qdtq"]);
|
|
2595
|
+
if (orphansResult.success && orphansResult.stdout.trim()) {
|
|
2596
|
+
const orphans = orphansResult.stdout.split(`
|
|
2597
|
+
`).filter(Boolean);
|
|
2598
|
+
if (orphans.length > 0) {
|
|
2599
|
+
await runPacmanCommand(["-Rs", "--noconfirm", ...orphans], callbacks, true);
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
return runPacmanCommand(["-Sc", "--noconfirm"], callbacks, true);
|
|
2603
|
+
}
|
|
2604
|
+
async isInstalled(packages) {
|
|
2605
|
+
const result = new Map;
|
|
2606
|
+
const listResult = await exec(["pacman", "-Qq"]);
|
|
2607
|
+
const installed = new Set(listResult.stdout.split(`
|
|
2608
|
+
`).filter(Boolean));
|
|
2609
|
+
for (const pkg of packages) {
|
|
2610
|
+
result.set(pkg, installed.has(pkg));
|
|
2611
|
+
}
|
|
2612
|
+
return result;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
// src/lib/package-managers/aur.ts
|
|
2617
|
+
init_runtime();
|
|
2618
|
+
init_runtime();
|
|
2619
|
+
async function runAurCommand(helper, args, callbacks) {
|
|
2620
|
+
if (helper === "none")
|
|
2621
|
+
return false;
|
|
2622
|
+
const cmd = [helper, ...args];
|
|
2623
|
+
if (callbacks?.onLog) {
|
|
2624
|
+
const exitCode = await execStreaming(cmd, callbacks.onLog);
|
|
2625
|
+
return exitCode === 0;
|
|
2626
|
+
}
|
|
2627
|
+
const result = await exec(cmd);
|
|
2628
|
+
return result.success;
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
class AurPackageManager {
|
|
2632
|
+
type = "aur";
|
|
2633
|
+
displayName = "AUR";
|
|
2634
|
+
helper = "none";
|
|
2635
|
+
helperDetected = false;
|
|
2636
|
+
async getHelper() {
|
|
2637
|
+
if (!this.helperDetected) {
|
|
2638
|
+
this.helper = await detectAurHelper();
|
|
2639
|
+
this.helperDetected = true;
|
|
2640
|
+
}
|
|
2641
|
+
return this.helper;
|
|
2642
|
+
}
|
|
2643
|
+
async isAvailable() {
|
|
2644
|
+
const helper = await this.getHelper();
|
|
2645
|
+
return helper !== "none";
|
|
2646
|
+
}
|
|
2647
|
+
async ensureAurHelper(callbacks) {
|
|
2648
|
+
const helper = await this.getHelper();
|
|
2649
|
+
if (helper !== "none")
|
|
2650
|
+
return true;
|
|
2651
|
+
const log = callbacks?.onLog || console.log;
|
|
2652
|
+
log("No AUR helper found. Installing yay...");
|
|
2653
|
+
const hasGit = await commandExists("git");
|
|
2654
|
+
const hasMakepkg = await commandExists("makepkg");
|
|
2655
|
+
const hasBaseDevel = await exec(["pacman", "-Qq", "base-devel"]);
|
|
2656
|
+
if (!hasGit) {
|
|
2657
|
+
log("Installing git...");
|
|
2658
|
+
const gitResult = await exec(["sudo", "pacman", "-S", "--noconfirm", "git"]);
|
|
2659
|
+
if (!gitResult.success) {
|
|
2660
|
+
log("Failed to install git");
|
|
2661
|
+
return false;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
if (!hasBaseDevel.success) {
|
|
2665
|
+
log("Installing base-devel...");
|
|
2666
|
+
const baseResult = await exec(["sudo", "pacman", "-S", "--noconfirm", "base-devel"]);
|
|
2667
|
+
if (!baseResult.success) {
|
|
2668
|
+
log("Failed to install base-devel");
|
|
2669
|
+
return false;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
const tmpDir = "/tmp/yay-install";
|
|
2673
|
+
await exec(["rm", "-rf", tmpDir]);
|
|
2674
|
+
log("Cloning yay from AUR...");
|
|
2675
|
+
const cloneResult = await exec(["git", "clone", "https://aur.archlinux.org/yay.git", tmpDir]);
|
|
2676
|
+
if (!cloneResult.success) {
|
|
2677
|
+
log(`Failed to clone yay: ${cloneResult.stderr}`);
|
|
2678
|
+
return false;
|
|
2679
|
+
}
|
|
2680
|
+
log("Building and installing yay...");
|
|
2681
|
+
const buildExitCode = await execLive(["makepkg", "-si", "--noconfirm"], tmpDir);
|
|
2682
|
+
if (buildExitCode !== 0) {
|
|
2683
|
+
log("Failed to build yay");
|
|
2684
|
+
return false;
|
|
2685
|
+
}
|
|
2686
|
+
await exec(["rm", "-rf", tmpDir]);
|
|
2687
|
+
this.helper = "yay";
|
|
2688
|
+
log("yay installed successfully!");
|
|
2689
|
+
return true;
|
|
2690
|
+
}
|
|
2691
|
+
async update(callbacks) {
|
|
2692
|
+
const helper = await this.getHelper();
|
|
2693
|
+
if (helper === "none")
|
|
2694
|
+
return false;
|
|
2695
|
+
return runAurCommand(helper, ["-Sy"], callbacks);
|
|
2696
|
+
}
|
|
2697
|
+
async install(packages, callbacks) {
|
|
2698
|
+
if (packages.length === 0)
|
|
2699
|
+
return true;
|
|
2700
|
+
const helper = await this.getHelper();
|
|
2701
|
+
if (helper === "none") {
|
|
2702
|
+
const installed = await this.ensureAurHelper(callbacks);
|
|
2703
|
+
if (!installed)
|
|
2704
|
+
return false;
|
|
2705
|
+
}
|
|
2706
|
+
const currentHelper = await this.getHelper();
|
|
2707
|
+
return runAurCommand(currentHelper, ["-S", "--noconfirm", "--needed", ...packages], callbacks);
|
|
2708
|
+
}
|
|
2709
|
+
async uninstall(packages, callbacks) {
|
|
2710
|
+
if (packages.length === 0)
|
|
2711
|
+
return true;
|
|
2712
|
+
const helper = await this.getHelper();
|
|
2713
|
+
if (helper === "none")
|
|
2714
|
+
return false;
|
|
2715
|
+
return runAurCommand(helper, ["-Rs", "--noconfirm", ...packages], callbacks);
|
|
2716
|
+
}
|
|
2717
|
+
async upgrade(packages, callbacks) {
|
|
2718
|
+
const helper = await this.getHelper();
|
|
2719
|
+
if (helper === "none")
|
|
2720
|
+
return false;
|
|
2721
|
+
if (packages && packages.length > 0) {
|
|
2722
|
+
return runAurCommand(helper, ["-S", "--noconfirm", ...packages], callbacks);
|
|
2723
|
+
}
|
|
2724
|
+
return runAurCommand(helper, ["-Syu", "--noconfirm"], callbacks);
|
|
2725
|
+
}
|
|
2726
|
+
async listInstalled() {
|
|
2727
|
+
const result = await exec(["pacman", "-Qm"]);
|
|
2728
|
+
if (!result.success)
|
|
2729
|
+
return [];
|
|
2730
|
+
return result.stdout.split(`
|
|
2731
|
+
`).filter(Boolean).map((line) => {
|
|
2732
|
+
const [name, version] = line.split(" ");
|
|
2733
|
+
return { name, version: version || "unknown" };
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
async listOutdated() {
|
|
2737
|
+
const helper = await this.getHelper();
|
|
2738
|
+
if (helper === "none")
|
|
2739
|
+
return [];
|
|
2740
|
+
const result = await exec([helper, "-Qua"]);
|
|
2741
|
+
if (!result.success || !result.stdout)
|
|
2742
|
+
return [];
|
|
2743
|
+
return result.stdout.split(`
|
|
2744
|
+
`).filter(Boolean).map((line) => {
|
|
2745
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+->\s+(\S+)$/);
|
|
2746
|
+
if (match) {
|
|
2747
|
+
return {
|
|
2748
|
+
name: match[1],
|
|
2749
|
+
currentVersion: match[2],
|
|
2750
|
+
newVersion: match[3]
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
return { name: line.split(" ")[0], currentVersion: "unknown", newVersion: "unknown" };
|
|
2754
|
+
});
|
|
2755
|
+
}
|
|
2756
|
+
async cleanup(callbacks) {
|
|
2757
|
+
const helper = await this.getHelper();
|
|
2758
|
+
if (helper === "none")
|
|
2759
|
+
return true;
|
|
2760
|
+
return runAurCommand(helper, ["-Sc", "--noconfirm"], callbacks);
|
|
2761
|
+
}
|
|
2762
|
+
async isInstalled(packages) {
|
|
2763
|
+
const result = new Map;
|
|
2764
|
+
const listResult = await exec(["pacman", "-Qmq"]);
|
|
2765
|
+
const installed = new Set(listResult.stdout.split(`
|
|
2766
|
+
`).filter(Boolean));
|
|
2767
|
+
for (const pkg of packages) {
|
|
2768
|
+
result.set(pkg, installed.has(pkg));
|
|
2769
|
+
}
|
|
2770
|
+
return result;
|
|
2771
|
+
}
|
|
2772
|
+
getHelperName() {
|
|
2773
|
+
return this.getHelper();
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
// src/lib/package-managers/apt.ts
|
|
2778
|
+
init_runtime();
|
|
2779
|
+
init_runtime();
|
|
2780
|
+
async function runAptCommand(args, callbacks, sudo = false) {
|
|
2781
|
+
const cmd = sudo ? ["sudo", "apt-get", ...args] : ["apt-get", ...args];
|
|
2782
|
+
if (callbacks?.onLog) {
|
|
2783
|
+
const exitCode = await execStreaming(cmd, callbacks.onLog);
|
|
2784
|
+
return exitCode === 0;
|
|
2785
|
+
}
|
|
2786
|
+
const result = await exec(cmd);
|
|
2787
|
+
return result.success;
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
class Apt {
|
|
2791
|
+
type = "apt";
|
|
2792
|
+
displayName = "APT";
|
|
2793
|
+
async isAvailable() {
|
|
2794
|
+
return commandExists("apt-get");
|
|
2795
|
+
}
|
|
2796
|
+
async update(callbacks) {
|
|
2797
|
+
return runAptCommand(["update", "-y"], callbacks, true);
|
|
2798
|
+
}
|
|
2799
|
+
async install(packages, callbacks) {
|
|
2800
|
+
if (packages.length === 0)
|
|
2801
|
+
return true;
|
|
2802
|
+
return runAptCommand(["install", "-y", ...packages], callbacks, true);
|
|
2803
|
+
}
|
|
2804
|
+
async uninstall(packages, callbacks) {
|
|
2805
|
+
if (packages.length === 0)
|
|
2806
|
+
return true;
|
|
2807
|
+
return runAptCommand(["remove", "-y", ...packages], callbacks, true);
|
|
2808
|
+
}
|
|
2809
|
+
async upgrade(packages, callbacks) {
|
|
2810
|
+
if (packages && packages.length > 0) {
|
|
2811
|
+
return runAptCommand(["install", "-y", "--only-upgrade", ...packages], callbacks, true);
|
|
2812
|
+
}
|
|
2813
|
+
await runAptCommand(["update", "-y"], callbacks, true);
|
|
2814
|
+
return runAptCommand(["upgrade", "-y"], callbacks, true);
|
|
2815
|
+
}
|
|
2816
|
+
async listInstalled() {
|
|
2817
|
+
const result = await exec(["dpkg-query", "-W", "-f=${Package} ${Version}\n"]);
|
|
2818
|
+
if (!result.success)
|
|
2819
|
+
return [];
|
|
2820
|
+
return result.stdout.split(`
|
|
2821
|
+
`).filter(Boolean).map((line) => {
|
|
2822
|
+
const [name, version] = line.split(" ");
|
|
2823
|
+
return { name, version: version || "unknown" };
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
async listOutdated() {
|
|
2827
|
+
await exec(["sudo", "apt-get", "update", "-y"]);
|
|
2828
|
+
const result = await exec(["apt", "list", "--upgradable"]);
|
|
2829
|
+
if (!result.success)
|
|
2830
|
+
return [];
|
|
2831
|
+
return result.stdout.split(`
|
|
2832
|
+
`).filter((line) => line.includes("[upgradable")).map((line) => {
|
|
2833
|
+
const match = line.match(/^(\S+)\/\S+\s+(\S+)\s+\S+\s+\[upgradable from:\s+(\S+)\]/);
|
|
2834
|
+
if (match) {
|
|
2835
|
+
return {
|
|
2836
|
+
name: match[1],
|
|
2837
|
+
currentVersion: match[3],
|
|
2838
|
+
newVersion: match[2]
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
return null;
|
|
2842
|
+
}).filter((pkg) => pkg !== null);
|
|
2843
|
+
}
|
|
2844
|
+
async listLeaves() {
|
|
2845
|
+
const result = await exec(["apt-mark", "showmanual"]);
|
|
2846
|
+
if (!result.success)
|
|
2847
|
+
return [];
|
|
2848
|
+
return result.stdout.split(`
|
|
2849
|
+
`).filter(Boolean);
|
|
2850
|
+
}
|
|
2851
|
+
async cleanup(callbacks) {
|
|
2852
|
+
const autoremove = await runAptCommand(["autoremove", "-y"], callbacks, true);
|
|
2853
|
+
const autoclean = await runAptCommand(["autoclean"], callbacks, true);
|
|
2854
|
+
return autoremove && autoclean;
|
|
2855
|
+
}
|
|
2856
|
+
async addRepository(repo, callbacks) {
|
|
2857
|
+
const cmd = ["sudo", "add-apt-repository", "-y", repo];
|
|
2858
|
+
if (callbacks?.onLog) {
|
|
2859
|
+
const exitCode = await execStreaming(cmd, callbacks.onLog);
|
|
2860
|
+
if (exitCode !== 0)
|
|
2861
|
+
return false;
|
|
2862
|
+
} else {
|
|
2863
|
+
const result = await exec(cmd);
|
|
2864
|
+
if (!result.success)
|
|
2865
|
+
return false;
|
|
2866
|
+
}
|
|
2867
|
+
return runAptCommand(["update", "-y"], callbacks, true);
|
|
2868
|
+
}
|
|
2869
|
+
async isInstalled(packages) {
|
|
2870
|
+
const result = new Map;
|
|
2871
|
+
for (const pkg of packages) {
|
|
2872
|
+
const checkResult = await exec(["dpkg", "-s", pkg]);
|
|
2873
|
+
result.set(pkg, checkResult.success && checkResult.stdout.includes("Status: install ok installed"));
|
|
2874
|
+
}
|
|
2875
|
+
return result;
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
// src/lib/package-managers/dnf.ts
|
|
2880
|
+
init_runtime();
|
|
2881
|
+
init_runtime();
|
|
2882
|
+
async function runDnfCommand(args, callbacks, sudo = false) {
|
|
2883
|
+
const cmd = sudo ? ["sudo", "dnf", ...args] : ["dnf", ...args];
|
|
2884
|
+
if (callbacks?.onLog) {
|
|
2885
|
+
const exitCode = await execStreaming(cmd, callbacks.onLog);
|
|
2886
|
+
return exitCode === 0;
|
|
2887
|
+
}
|
|
2888
|
+
const result = await exec(cmd);
|
|
2889
|
+
return result.success;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
class Dnf {
|
|
2893
|
+
type = "dnf";
|
|
2894
|
+
displayName = "DNF";
|
|
2895
|
+
async isAvailable() {
|
|
2896
|
+
return commandExists("dnf");
|
|
2897
|
+
}
|
|
2898
|
+
async update(callbacks) {
|
|
2899
|
+
return runDnfCommand(["check-update", "-y"], callbacks, true);
|
|
2900
|
+
}
|
|
2901
|
+
async install(packages, callbacks) {
|
|
2902
|
+
if (packages.length === 0)
|
|
2903
|
+
return true;
|
|
2904
|
+
return runDnfCommand(["install", "-y", ...packages], callbacks, true);
|
|
2905
|
+
}
|
|
2906
|
+
async uninstall(packages, callbacks) {
|
|
2907
|
+
if (packages.length === 0)
|
|
2908
|
+
return true;
|
|
2909
|
+
return runDnfCommand(["remove", "-y", ...packages], callbacks, true);
|
|
2910
|
+
}
|
|
2911
|
+
async upgrade(packages, callbacks) {
|
|
2912
|
+
if (packages && packages.length > 0) {
|
|
2913
|
+
return runDnfCommand(["upgrade", "-y", ...packages], callbacks, true);
|
|
2914
|
+
}
|
|
2915
|
+
return runDnfCommand(["upgrade", "-y"], callbacks, true);
|
|
2916
|
+
}
|
|
2917
|
+
async listInstalled() {
|
|
2918
|
+
const result = await exec(["dnf", "list", "installed", "-q"]);
|
|
2919
|
+
if (!result.success)
|
|
2920
|
+
return [];
|
|
2921
|
+
return result.stdout.split(`
|
|
2922
|
+
`).filter(Boolean).slice(1).map((line) => {
|
|
2923
|
+
const parts = line.trim().split(/\s+/);
|
|
2924
|
+
if (parts.length >= 2) {
|
|
2925
|
+
const fullName = parts[0];
|
|
2926
|
+
const name = fullName.replace(/\.\w+$/, "");
|
|
2927
|
+
return { name, version: parts[1] };
|
|
2928
|
+
}
|
|
2929
|
+
return null;
|
|
2930
|
+
}).filter((pkg) => pkg !== null);
|
|
2931
|
+
}
|
|
2932
|
+
async listOutdated() {
|
|
2933
|
+
const result = await exec(["dnf", "check-update", "-q"]);
|
|
2934
|
+
if (!result.stdout)
|
|
2935
|
+
return [];
|
|
2936
|
+
return result.stdout.split(`
|
|
2937
|
+
`).filter((line) => line.trim() && !line.startsWith("Last metadata")).map((line) => {
|
|
2938
|
+
const parts = line.trim().split(/\s+/);
|
|
2939
|
+
if (parts.length >= 2) {
|
|
2940
|
+
const fullName = parts[0];
|
|
2941
|
+
const name = fullName.replace(/\.\w+$/, "");
|
|
2942
|
+
return {
|
|
2943
|
+
name,
|
|
2944
|
+
currentVersion: "installed",
|
|
2945
|
+
newVersion: parts[1]
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
return null;
|
|
2949
|
+
}).filter((pkg) => pkg !== null);
|
|
2950
|
+
}
|
|
2951
|
+
async listLeaves() {
|
|
2952
|
+
const hasLeaves = await commandExists("dnf-leaves");
|
|
2953
|
+
if (hasLeaves) {
|
|
2954
|
+
const result2 = await exec(["dnf", "leaves"]);
|
|
2955
|
+
if (result2.success) {
|
|
2956
|
+
return result2.stdout.split(`
|
|
2957
|
+
`).filter(Boolean);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
const result = await exec(["dnf", "repoquery", "--userinstalled", "-q"]);
|
|
2961
|
+
if (!result.success)
|
|
2962
|
+
return [];
|
|
2963
|
+
return result.stdout.split(`
|
|
2964
|
+
`).filter(Boolean).map((line) => line.replace(/\.\w+$/, ""));
|
|
2965
|
+
}
|
|
2966
|
+
async cleanup(callbacks) {
|
|
2967
|
+
const autoremove = await runDnfCommand(["autoremove", "-y"], callbacks, true);
|
|
2968
|
+
const clean = await runDnfCommand(["clean", "all"], callbacks, true);
|
|
2969
|
+
return autoremove && clean;
|
|
2970
|
+
}
|
|
2971
|
+
async addRepository(repo, callbacks) {
|
|
2972
|
+
if (repo.startsWith("copr:")) {
|
|
2973
|
+
const coprRepo = repo.replace("copr:", "");
|
|
2974
|
+
return runDnfCommand(["copr", "enable", "-y", coprRepo], callbacks, true);
|
|
2975
|
+
}
|
|
2976
|
+
return runDnfCommand(["config-manager", "--add-repo", repo], callbacks, true);
|
|
2977
|
+
}
|
|
2978
|
+
async isInstalled(packages) {
|
|
2979
|
+
const result = new Map;
|
|
2980
|
+
for (const pkg of packages) {
|
|
2981
|
+
const checkResult = await exec(["rpm", "-q", pkg]);
|
|
2982
|
+
result.set(pkg, checkResult.success);
|
|
2983
|
+
}
|
|
2984
|
+
return result;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
// src/lib/package-managers/flatpak.ts
|
|
2989
|
+
init_runtime();
|
|
2990
|
+
init_runtime();
|
|
2991
|
+
async function runFlatpakCommand(args, callbacks) {
|
|
2992
|
+
const cmd = ["flatpak", ...args];
|
|
2993
|
+
if (callbacks?.onLog) {
|
|
2994
|
+
const exitCode = await execStreaming(cmd, callbacks.onLog);
|
|
2995
|
+
return exitCode === 0;
|
|
2996
|
+
}
|
|
2997
|
+
const result = await exec(cmd);
|
|
2998
|
+
return result.success;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
class Flatpak {
|
|
3002
|
+
type = "flatpak";
|
|
3003
|
+
displayName = "Flatpak";
|
|
3004
|
+
async isAvailable() {
|
|
3005
|
+
return commandExists("flatpak");
|
|
3006
|
+
}
|
|
3007
|
+
async update(callbacks) {
|
|
3008
|
+
return true;
|
|
3009
|
+
}
|
|
3010
|
+
async install(packages, callbacks) {
|
|
3011
|
+
if (packages.length === 0)
|
|
3012
|
+
return true;
|
|
3013
|
+
for (const appId of packages) {
|
|
3014
|
+
const success = await runFlatpakCommand(["install", "-y", "--noninteractive", "flathub", appId], callbacks);
|
|
3015
|
+
if (!success)
|
|
3016
|
+
return false;
|
|
3017
|
+
}
|
|
3018
|
+
return true;
|
|
3019
|
+
}
|
|
3020
|
+
async uninstall(packages, callbacks) {
|
|
3021
|
+
if (packages.length === 0)
|
|
3022
|
+
return true;
|
|
3023
|
+
for (const appId of packages) {
|
|
3024
|
+
const success = await runFlatpakCommand(["uninstall", "-y", "--noninteractive", appId], callbacks);
|
|
3025
|
+
if (!success)
|
|
3026
|
+
return false;
|
|
3027
|
+
}
|
|
3028
|
+
return true;
|
|
3029
|
+
}
|
|
3030
|
+
async upgrade(packages, callbacks) {
|
|
3031
|
+
if (packages && packages.length > 0) {
|
|
3032
|
+
for (const appId of packages) {
|
|
3033
|
+
const success = await runFlatpakCommand(["update", "-y", "--noninteractive", appId], callbacks);
|
|
3034
|
+
if (!success)
|
|
3035
|
+
return false;
|
|
3036
|
+
}
|
|
3037
|
+
return true;
|
|
3038
|
+
}
|
|
3039
|
+
return runFlatpakCommand(["update", "-y", "--noninteractive"], callbacks);
|
|
3040
|
+
}
|
|
3041
|
+
async listInstalled() {
|
|
3042
|
+
const result = await exec(["flatpak", "list", "--app", "--columns=application,version"]);
|
|
3043
|
+
if (!result.success)
|
|
3044
|
+
return [];
|
|
3045
|
+
return result.stdout.split(`
|
|
3046
|
+
`).filter(Boolean).map((line) => {
|
|
3047
|
+
const [name, version] = line.split("\t");
|
|
3048
|
+
return { name: name.trim(), version: version?.trim() || "unknown" };
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
async listOutdated() {
|
|
3052
|
+
const result = await exec(["flatpak", "remote-ls", "--updates", "--columns=application,version"]);
|
|
3053
|
+
if (!result.success)
|
|
3054
|
+
return [];
|
|
3055
|
+
return result.stdout.split(`
|
|
3056
|
+
`).filter(Boolean).map((line) => {
|
|
3057
|
+
const [name, newVersion] = line.split("\t");
|
|
3058
|
+
return {
|
|
3059
|
+
name: name.trim(),
|
|
3060
|
+
currentVersion: "installed",
|
|
3061
|
+
newVersion: newVersion?.trim() || "unknown"
|
|
3062
|
+
};
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
3065
|
+
async cleanup(callbacks) {
|
|
3066
|
+
return runFlatpakCommand(["uninstall", "-y", "--unused", "--noninteractive"], callbacks);
|
|
3067
|
+
}
|
|
3068
|
+
async addRepository(repo, callbacks) {
|
|
3069
|
+
if (repo === "flathub") {
|
|
3070
|
+
return runFlatpakCommand(["remote-add", "--if-not-exists", "flathub", "https://flathub.org/repo/flathub.flatpakrepo"], callbacks);
|
|
3071
|
+
}
|
|
3072
|
+
const [name, url] = repo.split(" ");
|
|
3073
|
+
if (!url)
|
|
3074
|
+
return false;
|
|
3075
|
+
return runFlatpakCommand(["remote-add", "--if-not-exists", name, url], callbacks);
|
|
3076
|
+
}
|
|
3077
|
+
async isInstalled(packages) {
|
|
3078
|
+
const result = new Map;
|
|
3079
|
+
const listResult = await exec(["flatpak", "list", "--app", "--columns=application"]);
|
|
3080
|
+
const installed = new Set(listResult.stdout.split(`
|
|
3081
|
+
`).filter(Boolean).map((l) => l.trim()));
|
|
3082
|
+
for (const appId of packages) {
|
|
3083
|
+
result.set(appId, installed.has(appId));
|
|
3084
|
+
}
|
|
3085
|
+
return result;
|
|
3086
|
+
}
|
|
3087
|
+
async ensureFlathub(callbacks) {
|
|
3088
|
+
const result = await exec(["flatpak", "remotes"]);
|
|
3089
|
+
if (result.success && result.stdout.includes("flathub")) {
|
|
3090
|
+
return true;
|
|
3091
|
+
}
|
|
3092
|
+
return this.addRepository("flathub", callbacks);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
// src/lib/package-managers/index.ts
|
|
3097
|
+
var managerInstances = new Map;
|
|
3098
|
+
function getPackageManager(type) {
|
|
3099
|
+
const existing = managerInstances.get(type);
|
|
3100
|
+
if (existing)
|
|
3101
|
+
return existing;
|
|
3102
|
+
let manager;
|
|
3103
|
+
switch (type) {
|
|
3104
|
+
case "homebrew":
|
|
3105
|
+
manager = new HomebrewFormulas;
|
|
3106
|
+
break;
|
|
3107
|
+
case "homebrew-casks":
|
|
3108
|
+
manager = new HomebrewCasks;
|
|
3109
|
+
break;
|
|
3110
|
+
case "mas":
|
|
3111
|
+
manager = new MacAppStore;
|
|
3112
|
+
break;
|
|
3113
|
+
case "pacman":
|
|
3114
|
+
manager = new Pacman;
|
|
3115
|
+
break;
|
|
3116
|
+
case "aur":
|
|
3117
|
+
manager = new AurPackageManager;
|
|
3118
|
+
break;
|
|
3119
|
+
case "apt":
|
|
3120
|
+
manager = new Apt;
|
|
3121
|
+
break;
|
|
3122
|
+
case "dnf":
|
|
3123
|
+
manager = new Dnf;
|
|
3124
|
+
break;
|
|
3125
|
+
case "flatpak":
|
|
3126
|
+
manager = new Flatpak;
|
|
3127
|
+
break;
|
|
3128
|
+
default:
|
|
3129
|
+
throw new Error(`Unknown package manager type: ${type}`);
|
|
3130
|
+
}
|
|
3131
|
+
managerInstances.set(type, manager);
|
|
3132
|
+
return manager;
|
|
3133
|
+
}
|
|
3134
|
+
async function getAvailableManagers() {
|
|
3135
|
+
const platformInfo = await getPlatformInfo();
|
|
3136
|
+
const managers = [];
|
|
3137
|
+
for (const type of platformInfo.availableManagers) {
|
|
3138
|
+
const manager = getPackageManager(type);
|
|
3139
|
+
if (await manager.isAvailable()) {
|
|
3140
|
+
managers.push(manager);
|
|
3141
|
+
if (type === "homebrew") {
|
|
3142
|
+
const casks = getPackageManager("homebrew-casks");
|
|
3143
|
+
if (await casks.isAvailable()) {
|
|
3144
|
+
managers.push(casks);
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
return managers;
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
// src/lib/lockfile.ts
|
|
3153
|
+
async function fetchInstalledVersionsV2() {
|
|
3154
|
+
const platform = await getPlatformInfo();
|
|
3155
|
+
const config = await loadPkgConfig();
|
|
3156
|
+
const now = new Date().toISOString();
|
|
3157
|
+
const packages = {};
|
|
3158
|
+
const managers = await getAvailableManagers();
|
|
3159
|
+
for (const manager of managers) {
|
|
3160
|
+
const installed = await manager.listInstalled();
|
|
3161
|
+
for (const pkg of installed) {
|
|
3162
|
+
const key = `${manager.type}:${pkg.name}`;
|
|
3163
|
+
packages[key] = {
|
|
3164
|
+
version: pkg.version,
|
|
3165
|
+
installedAt: pkg.installedAt || now,
|
|
3166
|
+
manager: manager.type
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
if (platform.os === "darwin") {
|
|
3171
|
+
const formulaPackages = config.macos?.formulas || [];
|
|
3172
|
+
const globalPackages = config.global?.packages || [];
|
|
3173
|
+
const allFormulas = [...globalPackages, ...formulaPackages];
|
|
3174
|
+
if (allFormulas.length > 0) {
|
|
3175
|
+
const result = await exec([
|
|
3176
|
+
"brew",
|
|
3177
|
+
"info",
|
|
3178
|
+
"--json=v2",
|
|
3179
|
+
...allFormulas
|
|
3180
|
+
]);
|
|
3181
|
+
if (result.success && result.stdout) {
|
|
3182
|
+
try {
|
|
3183
|
+
const info = JSON.parse(result.stdout);
|
|
3184
|
+
for (const formula of info.formulae) {
|
|
3185
|
+
if (formula.installed.length > 0) {
|
|
3186
|
+
const key = `homebrew:${formula.name}`;
|
|
3187
|
+
if (packages[key]) {
|
|
3188
|
+
packages[key].tap = formula.tap;
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
} catch {}
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
return packages;
|
|
3197
|
+
}
|
|
3198
|
+
async function updateLockfile() {
|
|
3199
|
+
const existing = await loadPkgLock();
|
|
3200
|
+
const packages = await fetchInstalledVersionsV2();
|
|
3201
|
+
const mergedPackages = {};
|
|
3202
|
+
for (const [key, info] of Object.entries(packages)) {
|
|
3203
|
+
if (existing?.version === 2) {
|
|
3204
|
+
const prev = existing.packages[key];
|
|
3205
|
+
if (prev && prev.version === info.version) {
|
|
3206
|
+
mergedPackages[key] = prev;
|
|
3207
|
+
} else {
|
|
3208
|
+
mergedPackages[key] = info;
|
|
3209
|
+
}
|
|
3210
|
+
} else if (existing?.version === 1) {
|
|
3211
|
+
const [manager, name] = key.split(":");
|
|
3212
|
+
let prev;
|
|
3213
|
+
if (manager === "homebrew") {
|
|
3214
|
+
prev = existing.formulas[name];
|
|
3215
|
+
} else if (manager === "homebrew-casks") {
|
|
3216
|
+
prev = existing.casks[name];
|
|
3217
|
+
}
|
|
3218
|
+
if (prev && prev.version === info.version) {
|
|
3219
|
+
mergedPackages[key] = {
|
|
3220
|
+
...info,
|
|
3221
|
+
installedAt: prev.installedAt
|
|
3222
|
+
};
|
|
3223
|
+
} else {
|
|
3224
|
+
mergedPackages[key] = info;
|
|
3225
|
+
}
|
|
3226
|
+
} else {
|
|
3227
|
+
mergedPackages[key] = info;
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
const lock = {
|
|
3231
|
+
version: 2,
|
|
3232
|
+
lastUpdated: new Date().toISOString(),
|
|
3233
|
+
packages: mergedPackages
|
|
3234
|
+
};
|
|
3235
|
+
await savePkgLock(lock);
|
|
3236
|
+
return lock;
|
|
3237
|
+
}
|
|
3238
|
+
async function fetchInstalledVersions() {
|
|
3239
|
+
const config = await loadPkgConfig();
|
|
3240
|
+
const now = new Date().toISOString();
|
|
3241
|
+
const formulas = {};
|
|
3242
|
+
const casks = {};
|
|
3243
|
+
const allFormulas = [
|
|
3244
|
+
...config.global?.packages || [],
|
|
3245
|
+
...config.macos?.formulas || []
|
|
3246
|
+
];
|
|
3247
|
+
if (allFormulas.length > 0) {
|
|
3248
|
+
const result = await exec([
|
|
3249
|
+
"brew",
|
|
3250
|
+
"info",
|
|
3251
|
+
"--json=v2",
|
|
3252
|
+
...allFormulas
|
|
3253
|
+
]);
|
|
3254
|
+
if (result.success && result.stdout) {
|
|
3255
|
+
const info = JSON.parse(result.stdout);
|
|
3256
|
+
for (const formula of info.formulae) {
|
|
3257
|
+
if (formula.installed.length > 0) {
|
|
3258
|
+
formulas[formula.name] = {
|
|
3259
|
+
version: formula.installed[0].version,
|
|
3260
|
+
tap: formula.tap,
|
|
3261
|
+
installedAt: now
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
const allCasks = config.macos?.casks || [];
|
|
3268
|
+
if (allCasks.length > 0) {
|
|
3269
|
+
const result = await exec([
|
|
3270
|
+
"brew",
|
|
3271
|
+
"info",
|
|
3272
|
+
"--json=v2",
|
|
3273
|
+
"--cask",
|
|
3274
|
+
...allCasks
|
|
3275
|
+
]);
|
|
3276
|
+
if (result.success && result.stdout) {
|
|
3277
|
+
const info = JSON.parse(result.stdout);
|
|
3278
|
+
for (const cask of info.casks) {
|
|
3279
|
+
if (cask.installed) {
|
|
3280
|
+
casks[cask.token] = {
|
|
3281
|
+
version: cask.installed,
|
|
3282
|
+
installedAt: now
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
return { formulas, casks };
|
|
3289
|
+
}
|
|
3290
|
+
async function generateLockfile() {
|
|
3291
|
+
const { formulas, casks } = await fetchInstalledVersions();
|
|
3292
|
+
return {
|
|
3293
|
+
version: 1,
|
|
3294
|
+
lastUpdated: new Date().toISOString(),
|
|
3295
|
+
formulas,
|
|
3296
|
+
casks
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
async function getChangedPackages() {
|
|
3300
|
+
const existing = await loadPkgLock();
|
|
3301
|
+
const packages = await fetchInstalledVersionsV2();
|
|
3302
|
+
const added = [];
|
|
3303
|
+
const removed = [];
|
|
3304
|
+
const upgraded = [];
|
|
3305
|
+
if (!existing) {
|
|
3306
|
+
added.push(...Object.keys(packages));
|
|
3307
|
+
return { added, removed, upgraded };
|
|
3308
|
+
}
|
|
3309
|
+
if (existing.version === 2) {
|
|
3310
|
+
for (const [key, info] of Object.entries(packages)) {
|
|
3311
|
+
if (!existing.packages[key]) {
|
|
3312
|
+
added.push(key);
|
|
3313
|
+
} else if (existing.packages[key].version !== info.version) {
|
|
3314
|
+
upgraded.push({
|
|
3315
|
+
name: key,
|
|
3316
|
+
from: existing.packages[key].version,
|
|
3317
|
+
to: info.version
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
for (const key of Object.keys(existing.packages)) {
|
|
3322
|
+
if (!packages[key]) {
|
|
3323
|
+
removed.push(key);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
} else {
|
|
3327
|
+
const { formulas, casks } = await fetchInstalledVersions();
|
|
3328
|
+
for (const [name, info] of Object.entries(formulas)) {
|
|
3329
|
+
if (!existing.formulas[name]) {
|
|
3330
|
+
added.push(name);
|
|
3331
|
+
} else if (existing.formulas[name].version !== info.version) {
|
|
3332
|
+
upgraded.push({
|
|
3333
|
+
name,
|
|
3334
|
+
from: existing.formulas[name].version,
|
|
3335
|
+
to: info.version
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
for (const name of Object.keys(existing.formulas)) {
|
|
3340
|
+
if (!formulas[name]) {
|
|
3341
|
+
removed.push(name);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
for (const [name, info] of Object.entries(casks)) {
|
|
3345
|
+
if (!existing.casks[name]) {
|
|
3346
|
+
added.push(name);
|
|
3347
|
+
} else if (existing.casks[name].version !== info.version) {
|
|
3348
|
+
upgraded.push({
|
|
3349
|
+
name,
|
|
3350
|
+
from: existing.casks[name].version,
|
|
3351
|
+
to: info.version
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
for (const name of Object.keys(existing.casks)) {
|
|
3356
|
+
if (!casks[name]) {
|
|
3357
|
+
removed.push(name);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
return { added, removed, upgraded };
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
// src/types/pkg-config.ts
|
|
3365
|
+
var SYSTEM_APP_IDS = [
|
|
3366
|
+
409183694,
|
|
3367
|
+
409203825,
|
|
3368
|
+
409201541,
|
|
3369
|
+
408981434,
|
|
3370
|
+
682658836,
|
|
3371
|
+
424389933,
|
|
3372
|
+
424390742,
|
|
3373
|
+
413897608,
|
|
3374
|
+
1274495053,
|
|
3375
|
+
425424353,
|
|
3376
|
+
497799835,
|
|
3377
|
+
634148309,
|
|
3378
|
+
1480068668,
|
|
3379
|
+
803453959,
|
|
3380
|
+
1295203466,
|
|
3381
|
+
1444383602,
|
|
3382
|
+
640199958,
|
|
3383
|
+
899247664,
|
|
3384
|
+
1176895641,
|
|
3385
|
+
1451685025
|
|
3386
|
+
];
|
|
3387
|
+
|
|
3388
|
+
// src/cli/pkg-sync.ts
|
|
3389
|
+
var colors3 = {
|
|
3390
|
+
red: "\x1B[0;31m",
|
|
3391
|
+
green: "\x1B[0;32m",
|
|
3392
|
+
blue: "\x1B[0;34m",
|
|
3393
|
+
yellow: "\x1B[1;33m",
|
|
3394
|
+
cyan: "\x1B[0;36m",
|
|
3395
|
+
bold: "\x1B[1m",
|
|
3396
|
+
reset: "\x1B[0m"
|
|
3397
|
+
};
|
|
3398
|
+
async function getPackageSetsForPlatform(config, platform) {
|
|
3399
|
+
const sets = [];
|
|
3400
|
+
if (platform.os === "darwin") {
|
|
3401
|
+
const formulas = getPackageManager("homebrew");
|
|
3402
|
+
const casks = getPackageManager("homebrew-casks");
|
|
3403
|
+
const mas = getPackageManager("mas");
|
|
3404
|
+
const globalPkgs = config.global?.packages || [];
|
|
3405
|
+
const macosFormulas = config.macos?.formulas || [];
|
|
3406
|
+
const allFormulas = [...globalPkgs, ...macosFormulas];
|
|
3407
|
+
if (allFormulas.length > 0 || (config.macos?.taps || []).length > 0) {
|
|
3408
|
+
sets.push({
|
|
3409
|
+
manager: formulas,
|
|
3410
|
+
packages: allFormulas,
|
|
3411
|
+
repositories: config.macos?.taps
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
const macosCasks = config.macos?.casks || [];
|
|
3415
|
+
if (macosCasks.length > 0) {
|
|
3416
|
+
sets.push({
|
|
3417
|
+
manager: casks,
|
|
3418
|
+
packages: macosCasks
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3421
|
+
const masMappings = config.macos?.mas || {};
|
|
3422
|
+
const masIds = Object.values(masMappings).map(String);
|
|
3423
|
+
if (masIds.length > 0 && await mas.isAvailable()) {
|
|
3424
|
+
sets.push({
|
|
3425
|
+
manager: mas,
|
|
3426
|
+
packages: masIds
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3429
|
+
} else {
|
|
3430
|
+
const distro = platform.distro;
|
|
3431
|
+
const globalPkgs = config.global?.packages || [];
|
|
3432
|
+
const linuxPkgs = config.linux?.packages || [];
|
|
3433
|
+
if (distro === "arch") {
|
|
3434
|
+
const pacman = getPackageManager("pacman");
|
|
3435
|
+
const aur = getPackageManager("aur");
|
|
3436
|
+
const archPkgs = config.arch?.packages || [];
|
|
3437
|
+
const allPacmanPkgs = [...globalPkgs, ...linuxPkgs, ...archPkgs];
|
|
3438
|
+
if (allPacmanPkgs.length > 0) {
|
|
3439
|
+
sets.push({
|
|
3440
|
+
manager: pacman,
|
|
3441
|
+
packages: allPacmanPkgs
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3444
|
+
const aurPkgs = config.arch?.aur || [];
|
|
3445
|
+
if (aurPkgs.length > 0 && await aur.isAvailable()) {
|
|
3446
|
+
sets.push({
|
|
3447
|
+
manager: aur,
|
|
3448
|
+
packages: aurPkgs
|
|
3449
|
+
});
|
|
3450
|
+
}
|
|
3451
|
+
} else if (distro === "debian" || distro === "ubuntu") {
|
|
3452
|
+
const apt = getPackageManager("apt");
|
|
3453
|
+
const debianPkgs = config.debian?.packages || [];
|
|
3454
|
+
const allAptPkgs = [...globalPkgs, ...linuxPkgs, ...debianPkgs];
|
|
3455
|
+
if (allAptPkgs.length > 0 || (config.debian?.ppas || []).length > 0) {
|
|
3456
|
+
sets.push({
|
|
3457
|
+
manager: apt,
|
|
3458
|
+
packages: allAptPkgs,
|
|
3459
|
+
repositories: config.debian?.ppas
|
|
3460
|
+
});
|
|
3461
|
+
}
|
|
3462
|
+
} else if (distro === "fedora" || distro === "rhel") {
|
|
3463
|
+
const dnf = getPackageManager("dnf");
|
|
3464
|
+
const fedoraPkgs = config.fedora?.packages || [];
|
|
3465
|
+
const allDnfPkgs = [...globalPkgs, ...linuxPkgs, ...fedoraPkgs];
|
|
3466
|
+
if (allDnfPkgs.length > 0 || (config.fedora?.copr || []).length > 0) {
|
|
3467
|
+
sets.push({
|
|
3468
|
+
manager: dnf,
|
|
3469
|
+
packages: allDnfPkgs,
|
|
3470
|
+
repositories: config.fedora?.copr?.map((r) => `copr:${r}`)
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
} else {
|
|
3474
|
+
const managers = await getAvailableManagers();
|
|
3475
|
+
const primaryManager = managers.find((m) => m.type === "pacman" || m.type === "apt" || m.type === "dnf");
|
|
3476
|
+
if (primaryManager) {
|
|
3477
|
+
const allPkgs = [...globalPkgs, ...linuxPkgs];
|
|
3478
|
+
if (allPkgs.length > 0) {
|
|
3479
|
+
sets.push({
|
|
3480
|
+
manager: primaryManager,
|
|
3481
|
+
packages: allPkgs
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
const flatpak = getPackageManager("flatpak");
|
|
3487
|
+
const flatpakApps = config.linux?.flatpak || [];
|
|
3488
|
+
if (flatpakApps.length > 0 && await flatpak.isAvailable()) {
|
|
3489
|
+
sets.push({
|
|
3490
|
+
manager: flatpak,
|
|
3491
|
+
packages: flatpakApps
|
|
3492
|
+
});
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
return sets;
|
|
2177
3496
|
}
|
|
2178
|
-
async function
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
`).filter(Boolean).map((line) => {
|
|
2184
|
-
const match = line.match(/^(\d+)\s+(.+?)(?:\s+\(|$)/);
|
|
2185
|
-
if (match) {
|
|
2186
|
-
return { id: parseInt(match[1], 10), name: match[2].trim() };
|
|
3497
|
+
async function checkDependencies(platform) {
|
|
3498
|
+
if (platform.os === "darwin") {
|
|
3499
|
+
if (!await commandExists("brew")) {
|
|
3500
|
+
console.error(`${colors3.red}Error: Homebrew not installed${colors3.reset}`);
|
|
3501
|
+
process.exit(1);
|
|
2187
3502
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
3503
|
+
} else {
|
|
3504
|
+
const hasPackageManager = await Promise.all([
|
|
3505
|
+
commandExists("pacman"),
|
|
3506
|
+
commandExists("apt"),
|
|
3507
|
+
commandExists("dnf")
|
|
3508
|
+
]).then((results) => results.some(Boolean));
|
|
3509
|
+
if (!hasPackageManager) {
|
|
3510
|
+
console.error(`${colors3.red}Error: No supported package manager found (pacman, apt, or dnf)${colors3.reset}`);
|
|
3511
|
+
process.exit(1);
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
2190
3514
|
}
|
|
2191
3515
|
async function upgradeWithVerification(cb = null) {
|
|
2192
3516
|
const log = cb?.onLog ?? console.log;
|
|
3517
|
+
const platform = await getPlatformInfo();
|
|
2193
3518
|
const result = {
|
|
2194
3519
|
attempted: [],
|
|
2195
3520
|
succeeded: [],
|
|
2196
3521
|
failed: [],
|
|
2197
3522
|
stillOutdated: []
|
|
2198
3523
|
};
|
|
3524
|
+
const callbacks = cb ? { onLog: cb.onLog } : undefined;
|
|
2199
3525
|
log(`
|
|
2200
3526
|
${colors3.cyan}=== Checking for updates ===${colors3.reset}
|
|
2201
3527
|
`);
|
|
2202
|
-
await
|
|
2203
|
-
const
|
|
2204
|
-
result.attempted = beforeUpgrade.map((p) => p.name);
|
|
2205
|
-
if (beforeUpgrade.length === 0) {
|
|
2206
|
-
log(`
|
|
2207
|
-
${colors3.green}All brew packages are up to date${colors3.reset}`);
|
|
2208
|
-
} else {
|
|
2209
|
-
log(`
|
|
2210
|
-
${colors3.yellow}Found ${beforeUpgrade.length} outdated packages${colors3.reset}
|
|
2211
|
-
`);
|
|
2212
|
-
log(`${colors3.cyan}=== Upgrading formulas ===${colors3.reset}
|
|
2213
|
-
`);
|
|
2214
|
-
await runCommand(["brew", "upgrade", "--formula"], cb);
|
|
3528
|
+
const managers = await getAvailableManagers();
|
|
3529
|
+
for (const manager of managers) {
|
|
2215
3530
|
log(`
|
|
2216
|
-
${colors3.cyan}
|
|
3531
|
+
${colors3.cyan}--- ${manager.displayName} ---${colors3.reset}
|
|
2217
3532
|
`);
|
|
2218
|
-
await
|
|
2219
|
-
|
|
2220
|
-
|
|
3533
|
+
await manager.update(callbacks);
|
|
3534
|
+
const outdated = await manager.listOutdated();
|
|
3535
|
+
if (outdated.length === 0) {
|
|
3536
|
+
log(`${colors3.green}All packages are up to date${colors3.reset}`);
|
|
3537
|
+
continue;
|
|
3538
|
+
}
|
|
3539
|
+
log(`${colors3.yellow}Found ${outdated.length} outdated packages${colors3.reset}
|
|
2221
3540
|
`);
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
3541
|
+
result.attempted.push(...outdated.map((p) => p.name));
|
|
3542
|
+
await manager.upgrade(undefined, callbacks);
|
|
3543
|
+
const stillOutdated = await manager.listOutdated();
|
|
3544
|
+
const stillOutdatedSet = new Set(stillOutdated.map((p) => p.name));
|
|
3545
|
+
for (const pkg of outdated) {
|
|
2225
3546
|
if (stillOutdatedSet.has(pkg.name)) {
|
|
2226
3547
|
result.stillOutdated.push(pkg.name);
|
|
2227
3548
|
} else {
|
|
@@ -2232,93 +3553,70 @@ ${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
|
|
|
2232
3553
|
log(`${colors3.yellow}${result.stillOutdated.length} packages still outdated, retrying individually...${colors3.reset}
|
|
2233
3554
|
`);
|
|
2234
3555
|
for (const pkgName of [...result.stillOutdated]) {
|
|
2235
|
-
const pkg = afterUpgrade.find((p) => p.name === pkgName);
|
|
2236
|
-
if (!pkg)
|
|
2237
|
-
continue;
|
|
2238
3556
|
log(` Retrying ${colors3.blue}${pkgName}${colors3.reset}...`);
|
|
2239
|
-
const
|
|
2240
|
-
const
|
|
2241
|
-
const
|
|
2242
|
-
|
|
2243
|
-
"outdated",
|
|
2244
|
-
pkg.type === "cask" ? "--cask" : "--formula",
|
|
2245
|
-
"--quiet"
|
|
2246
|
-
]);
|
|
2247
|
-
const stillOutdatedNow = checkResult.stdout.split(`
|
|
2248
|
-
`).filter(Boolean);
|
|
2249
|
-
if (!stillOutdatedNow.includes(pkgName)) {
|
|
3557
|
+
const upgradeSuccess = await manager.upgrade([pkgName], callbacks);
|
|
3558
|
+
const checkOutdated = await manager.listOutdated();
|
|
3559
|
+
const stillFailing = checkOutdated.some((p) => p.name === pkgName);
|
|
3560
|
+
if (!stillFailing) {
|
|
2250
3561
|
result.succeeded.push(pkgName);
|
|
2251
3562
|
result.stillOutdated = result.stillOutdated.filter((n) => n !== pkgName);
|
|
2252
3563
|
log(` ${colors3.green}✓ Success${colors3.reset}`);
|
|
2253
3564
|
} else {
|
|
2254
3565
|
result.failed.push(pkgName);
|
|
2255
3566
|
result.stillOutdated = result.stillOutdated.filter((n) => n !== pkgName);
|
|
2256
|
-
log(` ${colors3.red}✗ Failed${colors3.reset}
|
|
2257
|
-
`)[0]})` : ""}`);
|
|
3567
|
+
log(` ${colors3.red}✗ Failed${colors3.reset}`);
|
|
2258
3568
|
}
|
|
2259
3569
|
}
|
|
2260
3570
|
}
|
|
2261
3571
|
}
|
|
2262
|
-
if (await commandExists("mas")) {
|
|
2263
|
-
const masOutdated = await getOutdatedMas();
|
|
2264
|
-
if (masOutdated.length > 0) {
|
|
2265
|
-
log(`
|
|
2266
|
-
${colors3.cyan}=== Upgrading Mac App Store apps ===${colors3.reset}
|
|
2267
|
-
`);
|
|
2268
|
-
await runCommand(["mas", "upgrade"], cb, undefined, true);
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
3572
|
log(`
|
|
2272
3573
|
${colors3.cyan}=== Cleanup ===${colors3.reset}
|
|
2273
3574
|
`);
|
|
2274
|
-
|
|
3575
|
+
for (const manager of managers) {
|
|
3576
|
+
await manager.cleanup(callbacks);
|
|
3577
|
+
}
|
|
2275
3578
|
log(`
|
|
2276
3579
|
${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
2277
3580
|
`);
|
|
2278
3581
|
const lock = await updateLockfile();
|
|
2279
|
-
const lockTotal = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
3582
|
+
const lockTotal = lock.version === 2 ? Object.keys(lock.packages).length : Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
2280
3583
|
log(` Locked ${lockTotal} packages`);
|
|
2281
3584
|
return result;
|
|
2282
3585
|
}
|
|
2283
3586
|
async function upgradeInteractive(cb = null) {
|
|
2284
3587
|
const log = cb?.onLog ?? console.log;
|
|
2285
3588
|
const askPrompt = cb?.onPrompt ?? (async (q) => (prompt(q) || "").trim().toLowerCase());
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
await runCommand(["brew", "update"], cb);
|
|
2290
|
-
const outdated = await getOutdatedPackages();
|
|
2291
|
-
if (outdated.length === 0) {
|
|
3589
|
+
const callbacks = cb ? { onLog: cb.onLog } : undefined;
|
|
3590
|
+
const managers = await getAvailableManagers();
|
|
3591
|
+
for (const manager of managers) {
|
|
2292
3592
|
log(`
|
|
2293
|
-
${colors3.
|
|
3593
|
+
${colors3.cyan}=== ${manager.displayName} ===${colors3.reset}
|
|
2294
3594
|
`);
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
3595
|
+
await manager.update(callbacks);
|
|
3596
|
+
const outdated = await manager.listOutdated();
|
|
3597
|
+
if (outdated.length === 0) {
|
|
3598
|
+
log(`${colors3.green}All packages are up to date${colors3.reset}
|
|
2299
3599
|
`);
|
|
2300
|
-
|
|
2301
|
-
const question = `Upgrade ${colors3.blue}${pkg.name}${colors3.reset} (${pkg.type})?`;
|
|
2302
|
-
const answer = await askPrompt(question, ["y", "n", "q"]);
|
|
2303
|
-
if (answer === "q") {
|
|
2304
|
-
log(`
|
|
2305
|
-
${colors3.yellow}Upgrade cancelled${colors3.reset}`);
|
|
2306
|
-
return;
|
|
3600
|
+
continue;
|
|
2307
3601
|
}
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
3602
|
+
log(`${colors3.yellow}Found ${outdated.length} outdated packages${colors3.reset}
|
|
3603
|
+
`);
|
|
3604
|
+
for (const pkg of outdated) {
|
|
3605
|
+
const question = `Upgrade ${colors3.blue}${pkg.name}${colors3.reset} (${pkg.currentVersion} -> ${pkg.newVersion})?`;
|
|
3606
|
+
const answer = await askPrompt(question, ["y", "n", "q"]);
|
|
3607
|
+
if (answer === "q") {
|
|
3608
|
+
log(`
|
|
3609
|
+
${colors3.yellow}Upgrade cancelled${colors3.reset}`);
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
if (answer === "y" || answer === "yes") {
|
|
3613
|
+
await manager.upgrade([pkg.name], callbacks);
|
|
3614
|
+
}
|
|
2311
3615
|
}
|
|
2312
3616
|
}
|
|
2313
|
-
const
|
|
2314
|
-
|
|
2315
|
-
log(`
|
|
2316
|
-
${colors3.yellow}Still outdated: ${stillOutdated.map((p) => p.name).join(", ")}${colors3.reset}`);
|
|
2317
|
-
} else {
|
|
2318
|
-
log(`
|
|
2319
|
-
${colors3.green}All selected packages upgraded successfully${colors3.reset}`);
|
|
3617
|
+
for (const manager of managers) {
|
|
3618
|
+
await manager.cleanup(callbacks);
|
|
2320
3619
|
}
|
|
2321
|
-
await runCommand(["brew", "cleanup"], cb);
|
|
2322
3620
|
log(`
|
|
2323
3621
|
${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
2324
3622
|
`);
|
|
@@ -2326,61 +3624,50 @@ ${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
|
2326
3624
|
}
|
|
2327
3625
|
async function syncPackages(config, cb = null) {
|
|
2328
3626
|
const log = cb?.onLog ?? console.log;
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
${colors3.cyan}=== Updating Homebrew ===${colors3.reset}
|
|
2332
|
-
`);
|
|
2333
|
-
await runCommand(["brew", "update"], cb);
|
|
2334
|
-
}
|
|
2335
|
-
log(`
|
|
2336
|
-
${colors3.cyan}=== Installing taps ===${colors3.reset}
|
|
2337
|
-
`);
|
|
2338
|
-
const tappedResult = await exec(["brew", "tap"]);
|
|
2339
|
-
const tapped = tappedResult.stdout.split(`
|
|
2340
|
-
`).filter(Boolean);
|
|
2341
|
-
for (const tap of config.taps) {
|
|
2342
|
-
if (!tapped.includes(tap)) {
|
|
2343
|
-
log(` Adding tap: ${colors3.blue}${tap}${colors3.reset}`);
|
|
2344
|
-
await runCommand(["brew", "tap", tap], cb);
|
|
2345
|
-
}
|
|
2346
|
-
}
|
|
3627
|
+
const platform = await getPlatformInfo();
|
|
3628
|
+
const callbacks = cb ? { onLog: cb.onLog } : undefined;
|
|
2347
3629
|
log(`
|
|
2348
|
-
${colors3.cyan}
|
|
3630
|
+
${colors3.cyan}Platform: ${getPlatformDisplayName(platform)}${colors3.reset}
|
|
2349
3631
|
`);
|
|
2350
|
-
const
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
log(` Installing: ${colors3.blue}${pkg}${colors3.reset}`);
|
|
2355
|
-
await runCommand(["brew", "install", pkg], cb);
|
|
2356
|
-
}
|
|
3632
|
+
const packageSets = await getPackageSetsForPlatform(config, platform);
|
|
3633
|
+
if (packageSets.length === 0) {
|
|
3634
|
+
log(`${colors3.yellow}No packages configured for this platform${colors3.reset}`);
|
|
3635
|
+
return;
|
|
2357
3636
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
3637
|
+
if (config.config.autoUpdate) {
|
|
3638
|
+
log(`
|
|
3639
|
+
${colors3.cyan}=== Updating package managers ===${colors3.reset}
|
|
2360
3640
|
`);
|
|
2361
|
-
|
|
2362
|
-
`
|
|
2363
|
-
|
|
2364
|
-
if (!installedCasks.includes(cask)) {
|
|
2365
|
-
log(` Installing: ${colors3.blue}${cask}${colors3.reset}`);
|
|
2366
|
-
await runCommand(["brew", "install", "--cask", cask], cb);
|
|
3641
|
+
for (const set of packageSets) {
|
|
3642
|
+
log(` Updating ${set.manager.displayName}...`);
|
|
3643
|
+
await set.manager.update(callbacks);
|
|
2367
3644
|
}
|
|
2368
3645
|
}
|
|
2369
|
-
|
|
3646
|
+
for (const set of packageSets) {
|
|
2370
3647
|
log(`
|
|
2371
|
-
${colors3.cyan}===
|
|
3648
|
+
${colors3.cyan}=== ${set.manager.displayName} ===${colors3.reset}
|
|
2372
3649
|
`);
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
3650
|
+
if (set.repositories && set.repositories.length > 0 && set.manager.addRepository) {
|
|
3651
|
+
log(` Adding repositories...`);
|
|
3652
|
+
for (const repo of set.repositories) {
|
|
3653
|
+
log(` ${colors3.blue}${repo}${colors3.reset}`);
|
|
3654
|
+
await set.manager.addRepository(repo, callbacks);
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
if (set.packages.length === 0) {
|
|
3658
|
+
log(` No packages to install`);
|
|
3659
|
+
continue;
|
|
3660
|
+
}
|
|
3661
|
+
const installedMap = await set.manager.isInstalled(set.packages);
|
|
3662
|
+
const toInstall = set.packages.filter((pkg) => !installedMap.get(pkg));
|
|
3663
|
+
if (toInstall.length === 0) {
|
|
3664
|
+
log(` All ${set.packages.length} packages already installed`);
|
|
3665
|
+
} else {
|
|
3666
|
+
log(` Installing ${toInstall.length} packages...`);
|
|
3667
|
+
for (const pkg of toInstall) {
|
|
3668
|
+
log(` ${colors3.blue}${pkg}${colors3.reset}`);
|
|
2383
3669
|
}
|
|
3670
|
+
await set.manager.install(toInstall, callbacks);
|
|
2384
3671
|
}
|
|
2385
3672
|
}
|
|
2386
3673
|
if (config.config.purge) {
|
|
@@ -2390,7 +3677,7 @@ ${colors3.cyan}=== Installing Mac App Store apps ===${colors3.reset}
|
|
|
2390
3677
|
${colors3.cyan}=== Updating lockfile ===${colors3.reset}
|
|
2391
3678
|
`);
|
|
2392
3679
|
const lock = await updateLockfile();
|
|
2393
|
-
const lockTotal = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
3680
|
+
const lockTotal = lock.version === 2 ? Object.keys(lock.packages).length : Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
2394
3681
|
log(` Locked ${lockTotal} packages`);
|
|
2395
3682
|
log(`
|
|
2396
3683
|
${colors3.green}=== Sync complete ===${colors3.reset}
|
|
@@ -2399,74 +3686,53 @@ ${colors3.green}=== Sync complete ===${colors3.reset}
|
|
|
2399
3686
|
async function purgeUnlisted(config, interactive, cb = null) {
|
|
2400
3687
|
const log = cb?.onLog ?? console.log;
|
|
2401
3688
|
const askPrompt = cb?.onPrompt ?? (async (q) => (prompt(q) || "").trim().toLowerCase());
|
|
3689
|
+
const platform = await getPlatformInfo();
|
|
3690
|
+
const callbacks = cb ? { onLog: cb.onLog } : undefined;
|
|
2402
3691
|
log(`
|
|
2403
3692
|
${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
|
|
2404
3693
|
`);
|
|
2405
|
-
const
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
3694
|
+
const packageSets = await getPackageSetsForPlatform(config, platform);
|
|
3695
|
+
for (const set of packageSets) {
|
|
3696
|
+
const configuredSet = new Set(set.packages);
|
|
3697
|
+
let installedLeaves;
|
|
3698
|
+
if (set.manager.listLeaves) {
|
|
3699
|
+
installedLeaves = await set.manager.listLeaves();
|
|
3700
|
+
} else {
|
|
3701
|
+
const installed = await set.manager.listInstalled();
|
|
3702
|
+
installedLeaves = installed.map((p) => p.name);
|
|
3703
|
+
}
|
|
3704
|
+
for (const pkg of installedLeaves) {
|
|
3705
|
+
if (configuredSet.has(pkg))
|
|
2412
3706
|
continue;
|
|
3707
|
+
if (set.manager.type === "mas") {
|
|
3708
|
+
const appId = parseInt(pkg, 10);
|
|
3709
|
+
if (SYSTEM_APP_IDS.includes(appId))
|
|
3710
|
+
continue;
|
|
2413
3711
|
}
|
|
2414
|
-
if (
|
|
2415
|
-
const
|
|
2416
|
-
if (
|
|
2417
|
-
|
|
3712
|
+
if (set.manager.type === "homebrew") {
|
|
3713
|
+
const formulas = set.manager;
|
|
3714
|
+
if (await formulas.hasDependents(pkg)) {
|
|
3715
|
+
log(` ${colors3.yellow}Skipping ${pkg} (has dependents)${colors3.reset}`);
|
|
3716
|
+
continue;
|
|
2418
3717
|
}
|
|
2419
|
-
} else {
|
|
2420
|
-
log(` Removing: ${colors3.red}${pkg}${colors3.reset}`);
|
|
2421
|
-
await runCommand(["brew", "uninstall", pkg], cb);
|
|
2422
3718
|
}
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
const installedCasks = (await exec(["brew", "list", "--cask"])).stdout.split(`
|
|
2426
|
-
`).filter(Boolean);
|
|
2427
|
-
for (const cask of installedCasks) {
|
|
2428
|
-
if (!config.casks.includes(cask)) {
|
|
2429
3719
|
if (interactive) {
|
|
2430
|
-
const answer = await askPrompt(`Remove
|
|
3720
|
+
const answer = await askPrompt(`Remove ${colors3.red}${pkg}${colors3.reset} (${set.manager.displayName})?`, ["y", "n"]);
|
|
2431
3721
|
if (answer === "y") {
|
|
2432
|
-
await
|
|
3722
|
+
await set.manager.uninstall([pkg], callbacks);
|
|
2433
3723
|
}
|
|
2434
3724
|
} else {
|
|
2435
|
-
log(` Removing
|
|
2436
|
-
await
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
if (await commandExists("mas")) {
|
|
2441
|
-
const masResult = await exec(["mas", "list"]);
|
|
2442
|
-
const installedMas = masResult.stdout.split(`
|
|
2443
|
-
`).filter(Boolean).map((line) => {
|
|
2444
|
-
const match = line.match(/^(\d+)\s+(.+?)(?:\s+\(|$)/);
|
|
2445
|
-
return match ? { id: parseInt(match[1], 10), name: match[2].trim() } : null;
|
|
2446
|
-
}).filter((app) => app !== null);
|
|
2447
|
-
const configMasIds = Object.values(config.mas);
|
|
2448
|
-
for (const app of installedMas) {
|
|
2449
|
-
if (SYSTEM_APP_IDS.includes(app.id)) {
|
|
2450
|
-
continue;
|
|
2451
|
-
}
|
|
2452
|
-
if (!configMasIds.includes(app.id)) {
|
|
2453
|
-
if (interactive) {
|
|
2454
|
-
const answer = await askPrompt(`Remove app ${colors3.red}${app.name}${colors3.reset}?`, ["y", "n"]);
|
|
2455
|
-
if (answer === "y") {
|
|
2456
|
-
await runCommand(["mas", "uninstall", String(app.id)], cb, undefined, true);
|
|
2457
|
-
}
|
|
2458
|
-
} else {
|
|
2459
|
-
log(` Removing app: ${colors3.red}${app.name}${colors3.reset}`);
|
|
2460
|
-
await runCommand(["mas", "uninstall", String(app.id)], cb, undefined, true);
|
|
2461
|
-
}
|
|
3725
|
+
log(` Removing: ${colors3.red}${pkg}${colors3.reset}`);
|
|
3726
|
+
await set.manager.uninstall([pkg], callbacks);
|
|
2462
3727
|
}
|
|
2463
3728
|
}
|
|
2464
3729
|
}
|
|
2465
3730
|
log(`
|
|
2466
3731
|
${colors3.cyan}=== Cleaning up ===${colors3.reset}
|
|
2467
3732
|
`);
|
|
2468
|
-
|
|
2469
|
-
|
|
3733
|
+
for (const set of packageSets) {
|
|
3734
|
+
await set.manager.cleanup(callbacks);
|
|
3735
|
+
}
|
|
2470
3736
|
}
|
|
2471
3737
|
function printUsage2() {
|
|
2472
3738
|
console.log(`
|
|
@@ -2494,13 +3760,12 @@ async function runPkgSyncWithCallbacks(args, callbacks) {
|
|
|
2494
3760
|
},
|
|
2495
3761
|
allowPositionals: true
|
|
2496
3762
|
});
|
|
3763
|
+
const platform = await getPlatformInfo();
|
|
2497
3764
|
try {
|
|
2498
|
-
|
|
2499
|
-
callbacks.onLog(`${colors3.red}Error: Homebrew not installed${colors3.reset}`);
|
|
2500
|
-
return { output: "Homebrew not installed", success: false };
|
|
2501
|
-
}
|
|
3765
|
+
await checkDependencies(platform);
|
|
2502
3766
|
} catch {
|
|
2503
|
-
|
|
3767
|
+
callbacks.onLog(`${colors3.red}Error: Required dependencies not installed${colors3.reset}`);
|
|
3768
|
+
return { output: "Dependencies not installed", success: false };
|
|
2504
3769
|
}
|
|
2505
3770
|
if (values["upgrade-interactive"]) {
|
|
2506
3771
|
await upgradeInteractive(callbacks);
|
|
@@ -2542,7 +3807,8 @@ async function main2() {
|
|
|
2542
3807
|
printUsage2();
|
|
2543
3808
|
process.exit(0);
|
|
2544
3809
|
}
|
|
2545
|
-
await
|
|
3810
|
+
const platform = await getPlatformInfo();
|
|
3811
|
+
await checkDependencies(platform);
|
|
2546
3812
|
if (values["upgrade-interactive"]) {
|
|
2547
3813
|
await upgradeInteractive();
|
|
2548
3814
|
return;
|
|
@@ -2645,21 +3911,30 @@ async function showLockfile() {
|
|
|
2645
3911
|
console.log(`${colors4.bold}Package Lockfile${colors4.reset}`);
|
|
2646
3912
|
console.log(`Last updated: ${lock.lastUpdated}
|
|
2647
3913
|
`);
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
console.log(` ${name} ${colors4.blue}${version}${colors4.reset} (${tap})`);
|
|
3914
|
+
if (lock.version === 2) {
|
|
3915
|
+
const packages = Object.entries(lock.packages).sort(([a], [b]) => a.localeCompare(b));
|
|
3916
|
+
console.log(`${colors4.cyan}Packages (${packages.length}):${colors4.reset}`);
|
|
3917
|
+
for (const [key, info] of packages) {
|
|
3918
|
+
const [manager, name] = key.split(":");
|
|
3919
|
+
console.log(` ${name} ${colors4.blue}${info.version}${colors4.reset} (${manager}${info.tap ? `, ${info.tap}` : ""})`);
|
|
2655
3920
|
}
|
|
2656
|
-
}
|
|
2657
|
-
|
|
2658
|
-
|
|
3921
|
+
} else {
|
|
3922
|
+
const formulaNames = Object.keys(lock.formulas).sort();
|
|
3923
|
+
const caskNames = Object.keys(lock.casks).sort();
|
|
3924
|
+
if (formulaNames.length > 0) {
|
|
3925
|
+
console.log(`${colors4.cyan}Formulas (${formulaNames.length}):${colors4.reset}`);
|
|
3926
|
+
for (const name of formulaNames) {
|
|
3927
|
+
const { version, tap } = lock.formulas[name];
|
|
3928
|
+
console.log(` ${name} ${colors4.blue}${version}${colors4.reset} (${tap})`);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
if (caskNames.length > 0) {
|
|
3932
|
+
console.log(`
|
|
2659
3933
|
${colors4.cyan}Casks (${caskNames.length}):${colors4.reset}`);
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
3934
|
+
for (const name of caskNames) {
|
|
3935
|
+
const { version } = lock.casks[name];
|
|
3936
|
+
console.log(` ${name} ${colors4.blue}${version}${colors4.reset}`);
|
|
3937
|
+
}
|
|
2663
3938
|
}
|
|
2664
3939
|
}
|
|
2665
3940
|
}
|
|
@@ -2675,7 +3950,7 @@ async function runPkgLock(args) {
|
|
|
2675
3950
|
switch (command) {
|
|
2676
3951
|
case "update": {
|
|
2677
3952
|
const lock = await updateLockfile();
|
|
2678
|
-
const total = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
3953
|
+
const total = lock.version === 2 ? Object.keys(lock.packages).length : Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
2679
3954
|
return { output: `Lockfile updated with ${total} packages`, success: true };
|
|
2680
3955
|
}
|
|
2681
3956
|
case "status": {
|
|
@@ -2720,7 +3995,7 @@ async function main3() {
|
|
|
2720
3995
|
case "update": {
|
|
2721
3996
|
console.log(`${colors4.cyan}Updating lockfile...${colors4.reset}`);
|
|
2722
3997
|
const lock = await updateLockfile();
|
|
2723
|
-
const total = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
3998
|
+
const total = lock.version === 2 ? Object.keys(lock.packages).length : Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
|
|
2724
3999
|
console.log(`${colors4.green}Lockfile updated with ${total} packages.${colors4.reset}`);
|
|
2725
4000
|
break;
|
|
2726
4001
|
}
|
|
@@ -2754,64 +4029,248 @@ function getPackageName(fullName) {
|
|
|
2754
4029
|
const parts = fullName.split("/");
|
|
2755
4030
|
return parts[parts.length - 1];
|
|
2756
4031
|
}
|
|
4032
|
+
function getOrphanPackageType(managerType) {
|
|
4033
|
+
switch (managerType) {
|
|
4034
|
+
case "homebrew":
|
|
4035
|
+
return "formula";
|
|
4036
|
+
case "homebrew-casks":
|
|
4037
|
+
return "cask";
|
|
4038
|
+
case "pacman":
|
|
4039
|
+
return "pacman";
|
|
4040
|
+
case "aur":
|
|
4041
|
+
return "aur";
|
|
4042
|
+
case "apt":
|
|
4043
|
+
return "apt";
|
|
4044
|
+
case "dnf":
|
|
4045
|
+
return "dnf";
|
|
4046
|
+
case "flatpak":
|
|
4047
|
+
return "flatpak";
|
|
4048
|
+
default:
|
|
4049
|
+
return "formula";
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
async function getConfiguredPackagesForPlatform(config, platform) {
|
|
4053
|
+
const result = [];
|
|
4054
|
+
if (platform.os === "darwin") {
|
|
4055
|
+
const formulas = getPackageManager("homebrew");
|
|
4056
|
+
const casks = getPackageManager("homebrew-casks");
|
|
4057
|
+
const globalPkgs = config.global?.packages || [];
|
|
4058
|
+
const macosFormulas = config.macos?.formulas || [];
|
|
4059
|
+
const allFormulas = [...globalPkgs, ...macosFormulas];
|
|
4060
|
+
if (await formulas.isAvailable()) {
|
|
4061
|
+
result.push({
|
|
4062
|
+
manager: formulas,
|
|
4063
|
+
configuredPackages: new Set(allFormulas.map((p) => getPackageName(p)))
|
|
4064
|
+
});
|
|
4065
|
+
}
|
|
4066
|
+
const macosCasks = config.macos?.casks || [];
|
|
4067
|
+
if (await casks.isAvailable()) {
|
|
4068
|
+
result.push({
|
|
4069
|
+
manager: casks,
|
|
4070
|
+
configuredPackages: new Set(macosCasks)
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
const mas = getPackageManager("mas");
|
|
4074
|
+
const masMappings = config.macos?.mas || {};
|
|
4075
|
+
const masIds = Object.values(masMappings).map(String);
|
|
4076
|
+
if (await mas.isAvailable()) {
|
|
4077
|
+
result.push({
|
|
4078
|
+
manager: mas,
|
|
4079
|
+
configuredPackages: new Set(masIds)
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
} else {
|
|
4083
|
+
const distro = platform.distro;
|
|
4084
|
+
const globalPkgs = config.global?.packages || [];
|
|
4085
|
+
const linuxPkgs = config.linux?.packages || [];
|
|
4086
|
+
if (distro === "arch") {
|
|
4087
|
+
const pacman = getPackageManager("pacman");
|
|
4088
|
+
const aur = getPackageManager("aur");
|
|
4089
|
+
const archPkgs = config.arch?.packages || [];
|
|
4090
|
+
const allPacmanPkgs = [...globalPkgs, ...linuxPkgs, ...archPkgs];
|
|
4091
|
+
if (await pacman.isAvailable()) {
|
|
4092
|
+
result.push({
|
|
4093
|
+
manager: pacman,
|
|
4094
|
+
configuredPackages: new Set(allPacmanPkgs)
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
const aurPkgs = config.arch?.aur || [];
|
|
4098
|
+
if (await aur.isAvailable()) {
|
|
4099
|
+
result.push({
|
|
4100
|
+
manager: aur,
|
|
4101
|
+
configuredPackages: new Set(aurPkgs)
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
4104
|
+
} else if (distro === "debian" || distro === "ubuntu") {
|
|
4105
|
+
const apt = getPackageManager("apt");
|
|
4106
|
+
const debianPkgs = config.debian?.packages || [];
|
|
4107
|
+
const allAptPkgs = [...globalPkgs, ...linuxPkgs, ...debianPkgs];
|
|
4108
|
+
if (await apt.isAvailable()) {
|
|
4109
|
+
result.push({
|
|
4110
|
+
manager: apt,
|
|
4111
|
+
configuredPackages: new Set(allAptPkgs)
|
|
4112
|
+
});
|
|
4113
|
+
}
|
|
4114
|
+
} else if (distro === "fedora" || distro === "rhel") {
|
|
4115
|
+
const dnf = getPackageManager("dnf");
|
|
4116
|
+
const fedoraPkgs = config.fedora?.packages || [];
|
|
4117
|
+
const allDnfPkgs = [...globalPkgs, ...linuxPkgs, ...fedoraPkgs];
|
|
4118
|
+
if (await dnf.isAvailable()) {
|
|
4119
|
+
result.push({
|
|
4120
|
+
manager: dnf,
|
|
4121
|
+
configuredPackages: new Set(allDnfPkgs)
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
} else {
|
|
4125
|
+
const managers = await getAvailableManagers();
|
|
4126
|
+
const primaryManager = managers.find((m) => m.type === "pacman" || m.type === "apt" || m.type === "dnf");
|
|
4127
|
+
if (primaryManager) {
|
|
4128
|
+
const allPkgs = [...globalPkgs, ...linuxPkgs];
|
|
4129
|
+
result.push({
|
|
4130
|
+
manager: primaryManager,
|
|
4131
|
+
configuredPackages: new Set(allPkgs)
|
|
4132
|
+
});
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
const flatpak = getPackageManager("flatpak");
|
|
4136
|
+
const flatpakApps = config.linux?.flatpak || [];
|
|
4137
|
+
if (await flatpak.isAvailable()) {
|
|
4138
|
+
result.push({
|
|
4139
|
+
manager: flatpak,
|
|
4140
|
+
configuredPackages: new Set(flatpakApps)
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
return result;
|
|
4145
|
+
}
|
|
2757
4146
|
async function detectOrphanedPackages() {
|
|
2758
4147
|
const config = await loadPkgConfig();
|
|
2759
|
-
const
|
|
2760
|
-
const installedLeaves = leavesResult.success ? leavesResult.stdout.split(`
|
|
2761
|
-
`).filter(Boolean) : [];
|
|
2762
|
-
const casksResult = await exec(["brew", "list", "--cask"]);
|
|
2763
|
-
const installedCasks = casksResult.success ? casksResult.stdout.split(`
|
|
2764
|
-
`).filter(Boolean) : [];
|
|
4148
|
+
const platform = await getPlatformInfo();
|
|
2765
4149
|
const orphans = [];
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
})
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
4150
|
+
let totalConfigPackages = 0;
|
|
4151
|
+
let totalInstalledPackages = 0;
|
|
4152
|
+
const configuredPackagesPerManager = await getConfiguredPackagesForPlatform(config, platform);
|
|
4153
|
+
for (const { manager, configuredPackages } of configuredPackagesPerManager) {
|
|
4154
|
+
totalConfigPackages += configuredPackages.size;
|
|
4155
|
+
let installedLeaves2;
|
|
4156
|
+
if (manager.listLeaves) {
|
|
4157
|
+
installedLeaves2 = await manager.listLeaves();
|
|
4158
|
+
} else {
|
|
4159
|
+
const installed = await manager.listInstalled();
|
|
4160
|
+
installedLeaves2 = installed.map((p) => p.name);
|
|
2775
4161
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
4162
|
+
totalInstalledPackages += installedLeaves2.length;
|
|
4163
|
+
for (const installed of installedLeaves2) {
|
|
4164
|
+
const installedShort = getPackageName(installed);
|
|
4165
|
+
const isInConfig = configuredPackages.has(installed) || configuredPackages.has(installedShort) || Array.from(configuredPackages).some((cfg) => installed === cfg || installedShort === cfg || getPackageName(cfg) === installedShort || installed.endsWith(`/${getPackageName(cfg)}`));
|
|
4166
|
+
if (!isInConfig) {
|
|
4167
|
+
if (manager.type === "mas") {
|
|
4168
|
+
const appId = parseInt(installed, 10);
|
|
4169
|
+
if (SYSTEM_APP_IDS.includes(appId))
|
|
4170
|
+
continue;
|
|
4171
|
+
}
|
|
4172
|
+
if (manager.type === "homebrew") {
|
|
4173
|
+
const formulas = manager;
|
|
4174
|
+
if (await formulas.hasDependents(installed))
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4177
|
+
orphans.push({
|
|
4178
|
+
name: installed,
|
|
4179
|
+
type: getOrphanPackageType(manager.type),
|
|
4180
|
+
manager: manager.type
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
2781
4183
|
}
|
|
2782
4184
|
}
|
|
2783
4185
|
orphans.sort((a, b) => {
|
|
2784
4186
|
if (a.type !== b.type)
|
|
2785
|
-
return a.type
|
|
4187
|
+
return a.type.localeCompare(b.type);
|
|
2786
4188
|
return a.name.localeCompare(b.name);
|
|
2787
4189
|
});
|
|
4190
|
+
let configFormulas = 0;
|
|
4191
|
+
let configCasks = 0;
|
|
4192
|
+
let installedLeaves = 0;
|
|
4193
|
+
let installedCasks = 0;
|
|
4194
|
+
if (platform.os === "darwin") {
|
|
4195
|
+
configFormulas = (config.global?.packages?.length || 0) + (config.macos?.formulas?.length || 0);
|
|
4196
|
+
configCasks = config.macos?.casks?.length || 0;
|
|
4197
|
+
const formulas = getPackageManager("homebrew");
|
|
4198
|
+
const casks = getPackageManager("homebrew-casks");
|
|
4199
|
+
if (await formulas.isAvailable()) {
|
|
4200
|
+
const leaves = await formulas.listLeaves?.();
|
|
4201
|
+
installedLeaves = leaves?.length || 0;
|
|
4202
|
+
}
|
|
4203
|
+
if (await casks.isAvailable()) {
|
|
4204
|
+
const caskList = await casks.listInstalled();
|
|
4205
|
+
installedCasks = caskList.length;
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
2788
4208
|
return {
|
|
2789
4209
|
orphans,
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
4210
|
+
configPackages: totalConfigPackages,
|
|
4211
|
+
installedPackages: totalInstalledPackages,
|
|
4212
|
+
configFormulas,
|
|
4213
|
+
configCasks,
|
|
4214
|
+
installedLeaves,
|
|
4215
|
+
installedCasks
|
|
2794
4216
|
};
|
|
2795
4217
|
}
|
|
2796
4218
|
async function addToConfig(pkg) {
|
|
2797
4219
|
const config = await loadPkgConfig();
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
config.
|
|
4220
|
+
const platform = await getPlatformInfo();
|
|
4221
|
+
if (platform.os === "darwin") {
|
|
4222
|
+
if (pkg.type === "formula") {
|
|
4223
|
+
const formulas = config.macos?.formulas || [];
|
|
4224
|
+
if (!formulas.includes(pkg.name)) {
|
|
4225
|
+
config.macos = config.macos || {};
|
|
4226
|
+
config.macos.formulas = [...formulas, pkg.name].sort();
|
|
4227
|
+
}
|
|
4228
|
+
} else if (pkg.type === "cask") {
|
|
4229
|
+
const casks = config.macos?.casks || [];
|
|
4230
|
+
if (!casks.includes(pkg.name)) {
|
|
4231
|
+
config.macos = config.macos || {};
|
|
4232
|
+
config.macos.casks = [...casks, pkg.name].sort();
|
|
4233
|
+
}
|
|
2802
4234
|
}
|
|
2803
4235
|
} else {
|
|
2804
|
-
if (
|
|
2805
|
-
config.
|
|
2806
|
-
|
|
4236
|
+
if (pkg.type === "pacman") {
|
|
4237
|
+
const packages = config.arch?.packages || [];
|
|
4238
|
+
if (!packages.includes(pkg.name)) {
|
|
4239
|
+
config.arch = config.arch || {};
|
|
4240
|
+
config.arch.packages = [...packages, pkg.name].sort();
|
|
4241
|
+
}
|
|
4242
|
+
} else if (pkg.type === "aur") {
|
|
4243
|
+
const aurPkgs = config.arch?.aur || [];
|
|
4244
|
+
if (!aurPkgs.includes(pkg.name)) {
|
|
4245
|
+
config.arch = config.arch || {};
|
|
4246
|
+
config.arch.aur = [...aurPkgs, pkg.name].sort();
|
|
4247
|
+
}
|
|
4248
|
+
} else if (pkg.type === "apt") {
|
|
4249
|
+
const packages = config.debian?.packages || [];
|
|
4250
|
+
if (!packages.includes(pkg.name)) {
|
|
4251
|
+
config.debian = config.debian || {};
|
|
4252
|
+
config.debian.packages = [...packages, pkg.name].sort();
|
|
4253
|
+
}
|
|
4254
|
+
} else if (pkg.type === "dnf") {
|
|
4255
|
+
const packages = config.fedora?.packages || [];
|
|
4256
|
+
if (!packages.includes(pkg.name)) {
|
|
4257
|
+
config.fedora = config.fedora || {};
|
|
4258
|
+
config.fedora.packages = [...packages, pkg.name].sort();
|
|
4259
|
+
}
|
|
4260
|
+
} else if (pkg.type === "flatpak") {
|
|
4261
|
+
const flatpakApps = config.linux?.flatpak || [];
|
|
4262
|
+
if (!flatpakApps.includes(pkg.name)) {
|
|
4263
|
+
config.linux = config.linux || {};
|
|
4264
|
+
config.linux.flatpak = [...flatpakApps, pkg.name].sort();
|
|
4265
|
+
}
|
|
2807
4266
|
}
|
|
2808
4267
|
}
|
|
2809
4268
|
await savePkgConfig(config);
|
|
2810
4269
|
}
|
|
2811
4270
|
async function uninstallPackage(pkg) {
|
|
2812
|
-
const
|
|
2813
|
-
|
|
2814
|
-
return
|
|
4271
|
+
const manager = getPackageManager(pkg.manager);
|
|
4272
|
+
await manager.uninstall([pkg.name]);
|
|
4273
|
+
return true;
|
|
2815
4274
|
}
|
|
2816
4275
|
|
|
2817
4276
|
// src/components/menus/PackageMenu.tsx
|
|
@@ -2825,7 +4284,18 @@ function PackageMenu({ onBack }) {
|
|
|
2825
4284
|
const [success, setSuccess] = useState8(true);
|
|
2826
4285
|
const [orphanResult, setOrphanResult] = useState8(null);
|
|
2827
4286
|
const [isOrphanView, setIsOrphanView] = useState8(false);
|
|
4287
|
+
const [platformInfo, setPlatformInfo] = useState8(null);
|
|
4288
|
+
const [availableManagerNames, setAvailableManagerNames] = useState8([]);
|
|
2828
4289
|
const isRunningRef = useRef(false);
|
|
4290
|
+
useEffect4(() => {
|
|
4291
|
+
async function loadPlatformInfo() {
|
|
4292
|
+
const info = await getPlatformInfo();
|
|
4293
|
+
setPlatformInfo(info);
|
|
4294
|
+
const managers = await getAvailableManagers();
|
|
4295
|
+
setAvailableManagerNames(managers.map((m) => m.displayName));
|
|
4296
|
+
}
|
|
4297
|
+
loadPlatformInfo();
|
|
4298
|
+
}, []);
|
|
2829
4299
|
useInput9((input, key) => {
|
|
2830
4300
|
if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
|
|
2831
4301
|
onBack();
|
|
@@ -2978,37 +4448,73 @@ function PackageMenu({ onBack }) {
|
|
|
2978
4448
|
]
|
|
2979
4449
|
}, undefined, true, undefined, this);
|
|
2980
4450
|
}
|
|
4451
|
+
const platformDisplay = platformInfo ? getPlatformDisplayName(platformInfo) : "Detecting...";
|
|
4452
|
+
const managersDisplay = availableManagerNames.length > 0 ? availableManagerNames.join(", ") : "Detecting...";
|
|
2981
4453
|
return /* @__PURE__ */ jsxDEV17(Panel, {
|
|
2982
4454
|
title: "Package Sync",
|
|
2983
|
-
children:
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
4455
|
+
children: [
|
|
4456
|
+
/* @__PURE__ */ jsxDEV17(Box14, {
|
|
4457
|
+
marginBottom: 1,
|
|
4458
|
+
flexDirection: "column",
|
|
4459
|
+
children: [
|
|
4460
|
+
/* @__PURE__ */ jsxDEV17(Text13, {
|
|
4461
|
+
children: [
|
|
4462
|
+
/* @__PURE__ */ jsxDEV17(Text13, {
|
|
4463
|
+
dimColor: true,
|
|
4464
|
+
children: "Platform: "
|
|
4465
|
+
}, undefined, false, undefined, this),
|
|
4466
|
+
/* @__PURE__ */ jsxDEV17(Text13, {
|
|
4467
|
+
color: colors.info,
|
|
4468
|
+
children: platformDisplay
|
|
4469
|
+
}, undefined, false, undefined, this)
|
|
4470
|
+
]
|
|
4471
|
+
}, undefined, true, undefined, this),
|
|
4472
|
+
/* @__PURE__ */ jsxDEV17(Text13, {
|
|
4473
|
+
children: [
|
|
4474
|
+
/* @__PURE__ */ jsxDEV17(Text13, {
|
|
4475
|
+
dimColor: true,
|
|
4476
|
+
children: "Managers: "
|
|
4477
|
+
}, undefined, false, undefined, this),
|
|
4478
|
+
/* @__PURE__ */ jsxDEV17(Text13, {
|
|
4479
|
+
color: colors.info,
|
|
4480
|
+
children: managersDisplay
|
|
4481
|
+
}, undefined, false, undefined, this)
|
|
4482
|
+
]
|
|
4483
|
+
}, undefined, true, undefined, this)
|
|
4484
|
+
]
|
|
4485
|
+
}, undefined, true, undefined, this),
|
|
4486
|
+
/* @__PURE__ */ jsxDEV17(VimSelect, {
|
|
4487
|
+
options: [
|
|
4488
|
+
{ label: "Sync packages", value: "sync" },
|
|
4489
|
+
{ label: "Sync with purge", value: "sync-purge" },
|
|
4490
|
+
{ label: "Upgrade all (with verification)", value: "upgrade" },
|
|
4491
|
+
{ label: "Upgrade interactive", value: "upgrade-interactive" },
|
|
4492
|
+
{ label: "Update lockfile", value: "lock-update" },
|
|
4493
|
+
{ label: "Lockfile status", value: "lock-status" },
|
|
4494
|
+
{ label: "Find orphaned packages", value: "orphans" },
|
|
4495
|
+
{ label: "Back", value: "back" }
|
|
4496
|
+
],
|
|
4497
|
+
onChange: handleAction
|
|
4498
|
+
}, undefined, false, undefined, this)
|
|
4499
|
+
]
|
|
4500
|
+
}, undefined, true, undefined, this);
|
|
2997
4501
|
}
|
|
2998
4502
|
|
|
2999
4503
|
// src/components/menus/ThemeMenu.tsx
|
|
3000
|
-
import { useState as useState10, useEffect as
|
|
4504
|
+
import { useState as useState10, useEffect as useEffect6, useMemo as useMemo4 } from "react";
|
|
3001
4505
|
import { Box as Box16, Text as Text15 } from "ink";
|
|
3002
|
-
import { existsSync as
|
|
4506
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5 } from "fs";
|
|
3003
4507
|
import { join as join6 } from "path";
|
|
3004
4508
|
|
|
3005
4509
|
// src/components/ThemeCard.tsx
|
|
3006
4510
|
import { Box as Box15, Text as Text14 } from "ink";
|
|
3007
4511
|
import { jsxDEV as jsxDEV18 } from "react/jsx-dev-runtime";
|
|
3008
|
-
function ThemeCard({ theme, isSelected, width }) {
|
|
4512
|
+
function ThemeCard({ theme, isSelected, width, isDeviceTheme }) {
|
|
3009
4513
|
const borderColor = isSelected ? colors.accent : colors.border;
|
|
3010
4514
|
const nameColor = isSelected ? colors.primary : colors.text;
|
|
3011
4515
|
const indicators = [];
|
|
4516
|
+
if (isDeviceTheme)
|
|
4517
|
+
indicators.push("device");
|
|
3012
4518
|
if (theme.hasBackgrounds)
|
|
3013
4519
|
indicators.push("bg");
|
|
3014
4520
|
if (theme.isLightMode)
|
|
@@ -3042,7 +4548,7 @@ function ThemeCard({ theme, isSelected, width }) {
|
|
|
3042
4548
|
}
|
|
3043
4549
|
|
|
3044
4550
|
// src/hooks/useThemeGrid.ts
|
|
3045
|
-
import { useState as useState9, useEffect as
|
|
4551
|
+
import { useState as useState9, useEffect as useEffect5 } from "react";
|
|
3046
4552
|
import { useInput as useInput10 } from "ink";
|
|
3047
4553
|
function useThemeGrid({
|
|
3048
4554
|
itemCount,
|
|
@@ -3050,6 +4556,7 @@ function useThemeGrid({
|
|
|
3050
4556
|
layoutOverhead = 20,
|
|
3051
4557
|
minCardWidth = 28,
|
|
3052
4558
|
onSelect,
|
|
4559
|
+
onSelectAndSave,
|
|
3053
4560
|
onBack,
|
|
3054
4561
|
enabled = true
|
|
3055
4562
|
}) {
|
|
@@ -3063,7 +4570,7 @@ function useThemeGrid({
|
|
|
3063
4570
|
const visibleRows = Math.max(1, Math.floor(availableHeight / cardHeight));
|
|
3064
4571
|
const selectedRow = Math.floor(selectedIndex / cardsPerRow);
|
|
3065
4572
|
const totalRows = Math.ceil(itemCount / cardsPerRow);
|
|
3066
|
-
|
|
4573
|
+
useEffect5(() => {
|
|
3067
4574
|
if (selectedRow < scrollOffset) {
|
|
3068
4575
|
setScrollOffset(selectedRow);
|
|
3069
4576
|
} else if (selectedRow >= scrollOffset + visibleRows) {
|
|
@@ -3099,8 +4606,12 @@ function useThemeGrid({
|
|
|
3099
4606
|
setSelectedIndex(prevIndex);
|
|
3100
4607
|
}
|
|
3101
4608
|
}
|
|
3102
|
-
if (key.return
|
|
3103
|
-
|
|
4609
|
+
if (key.return) {
|
|
4610
|
+
if (key.shift && onSelectAndSave) {
|
|
4611
|
+
onSelectAndSave(selectedIndex);
|
|
4612
|
+
} else if (onSelect) {
|
|
4613
|
+
onSelect(selectedIndex);
|
|
4614
|
+
}
|
|
3104
4615
|
}
|
|
3105
4616
|
});
|
|
3106
4617
|
const visibleStartIndex = scrollOffset * cardsPerRow;
|
|
@@ -3121,6 +4632,7 @@ function useThemeGrid({
|
|
|
3121
4632
|
}
|
|
3122
4633
|
|
|
3123
4634
|
// src/lib/theme-parser.ts
|
|
4635
|
+
init_runtime();
|
|
3124
4636
|
import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
|
|
3125
4637
|
import { join as join4 } from "path";
|
|
3126
4638
|
function parseYaml(content) {
|
|
@@ -3196,9 +4708,86 @@ async function parseTheme(themePath, themeName) {
|
|
|
3196
4708
|
|
|
3197
4709
|
// src/cli/set-theme.ts
|
|
3198
4710
|
import { parseArgs as parseArgs4 } from "util";
|
|
3199
|
-
import { readdirSync as readdirSync4, existsSync as
|
|
3200
|
-
import { join as join5
|
|
3201
|
-
|
|
4711
|
+
import { readdirSync as readdirSync4, existsSync as existsSync7, rmSync, symlinkSync, unlinkSync } from "fs";
|
|
4712
|
+
import { join as join5 } from "path";
|
|
4713
|
+
|
|
4714
|
+
// src/lib/theme-config.ts
|
|
4715
|
+
import { hostname } from "os";
|
|
4716
|
+
import { existsSync as existsSync6, readFileSync, writeFileSync } from "fs";
|
|
4717
|
+
var DEFAULT_CONFIG = {
|
|
4718
|
+
version: 1,
|
|
4719
|
+
defaultTheme: null,
|
|
4720
|
+
devices: {}
|
|
4721
|
+
};
|
|
4722
|
+
function getDeviceHostname() {
|
|
4723
|
+
return hostname();
|
|
4724
|
+
}
|
|
4725
|
+
function loadThemeConfig() {
|
|
4726
|
+
if (!existsSync6(THEME_CONFIG_PATH)) {
|
|
4727
|
+
return { ...DEFAULT_CONFIG, devices: {} };
|
|
4728
|
+
}
|
|
4729
|
+
try {
|
|
4730
|
+
const content = readFileSync(THEME_CONFIG_PATH, "utf-8");
|
|
4731
|
+
const parsed = JSON.parse(content);
|
|
4732
|
+
return {
|
|
4733
|
+
version: parsed.version ?? 1,
|
|
4734
|
+
defaultTheme: parsed.defaultTheme ?? null,
|
|
4735
|
+
devices: parsed.devices ?? {}
|
|
4736
|
+
};
|
|
4737
|
+
} catch {
|
|
4738
|
+
return { ...DEFAULT_CONFIG, devices: {} };
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
async function saveThemeConfig(config) {
|
|
4742
|
+
await ensureConfigDir();
|
|
4743
|
+
writeFileSync(THEME_CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
4744
|
+
`);
|
|
4745
|
+
}
|
|
4746
|
+
function getDeviceTheme() {
|
|
4747
|
+
const config = loadThemeConfig();
|
|
4748
|
+
const device = getDeviceHostname();
|
|
4749
|
+
const mapping = config.devices[device];
|
|
4750
|
+
if (mapping) {
|
|
4751
|
+
return mapping.theme;
|
|
4752
|
+
}
|
|
4753
|
+
return config.defaultTheme;
|
|
4754
|
+
}
|
|
4755
|
+
async function setDeviceTheme(themeName) {
|
|
4756
|
+
const config = loadThemeConfig();
|
|
4757
|
+
const device = getDeviceHostname();
|
|
4758
|
+
config.devices[device] = {
|
|
4759
|
+
theme: themeName,
|
|
4760
|
+
setAt: new Date().toISOString()
|
|
4761
|
+
};
|
|
4762
|
+
await saveThemeConfig(config);
|
|
4763
|
+
}
|
|
4764
|
+
async function setDefaultTheme(themeName) {
|
|
4765
|
+
const config = loadThemeConfig();
|
|
4766
|
+
config.defaultTheme = themeName;
|
|
4767
|
+
await saveThemeConfig(config);
|
|
4768
|
+
}
|
|
4769
|
+
async function clearDeviceTheme() {
|
|
4770
|
+
const config = loadThemeConfig();
|
|
4771
|
+
const device = getDeviceHostname();
|
|
4772
|
+
delete config.devices[device];
|
|
4773
|
+
await saveThemeConfig(config);
|
|
4774
|
+
}
|
|
4775
|
+
function listDeviceMappings() {
|
|
4776
|
+
const config = loadThemeConfig();
|
|
4777
|
+
const currentDevice = getDeviceHostname();
|
|
4778
|
+
return Object.entries(config.devices).map(([device, mapping]) => ({
|
|
4779
|
+
device,
|
|
4780
|
+
theme: mapping.theme,
|
|
4781
|
+
setAt: mapping.setAt,
|
|
4782
|
+
isCurrent: device === currentDevice
|
|
4783
|
+
}));
|
|
4784
|
+
}
|
|
4785
|
+
function getDefaultTheme() {
|
|
4786
|
+
const config = loadThemeConfig();
|
|
4787
|
+
return config.defaultTheme;
|
|
4788
|
+
}
|
|
4789
|
+
|
|
4790
|
+
// src/cli/set-theme.ts
|
|
3202
4791
|
var colors5 = {
|
|
3203
4792
|
red: "\x1B[0;31m",
|
|
3204
4793
|
green: "\x1B[0;32m",
|
|
@@ -3210,7 +4799,7 @@ var colors5 = {
|
|
|
3210
4799
|
};
|
|
3211
4800
|
async function listThemes() {
|
|
3212
4801
|
await ensureConfigDir();
|
|
3213
|
-
if (!
|
|
4802
|
+
if (!existsSync7(THEMES_DIR)) {
|
|
3214
4803
|
return [];
|
|
3215
4804
|
}
|
|
3216
4805
|
const entries = readdirSync4(THEMES_DIR, { withFileTypes: true });
|
|
@@ -3225,7 +4814,7 @@ async function listThemes() {
|
|
|
3225
4814
|
return themes;
|
|
3226
4815
|
}
|
|
3227
4816
|
function clearDirectory(dir) {
|
|
3228
|
-
if (
|
|
4817
|
+
if (existsSync7(dir)) {
|
|
3229
4818
|
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
3230
4819
|
for (const entry of entries) {
|
|
3231
4820
|
const fullPath = join5(dir, entry.name);
|
|
@@ -3238,21 +4827,21 @@ function clearDirectory(dir) {
|
|
|
3238
4827
|
}
|
|
3239
4828
|
}
|
|
3240
4829
|
function createSymlink(source, target) {
|
|
3241
|
-
if (
|
|
4830
|
+
if (existsSync7(target)) {
|
|
3242
4831
|
unlinkSync(target);
|
|
3243
4832
|
}
|
|
3244
4833
|
symlinkSync(source, target);
|
|
3245
4834
|
}
|
|
3246
|
-
async function applyTheme(themeName) {
|
|
4835
|
+
async function applyTheme(themeName, saveMapping = false) {
|
|
3247
4836
|
const themeDir = join5(THEMES_DIR, themeName);
|
|
3248
|
-
if (!
|
|
4837
|
+
if (!existsSync7(themeDir)) {
|
|
3249
4838
|
return { output: `Theme '${themeName}' not found`, success: false };
|
|
3250
4839
|
}
|
|
3251
4840
|
await ensureConfigDir();
|
|
3252
4841
|
await ensureDir2(THEME_TARGET_DIR);
|
|
3253
4842
|
const theme = await parseTheme(themeDir, themeName);
|
|
3254
4843
|
clearDirectory(THEME_TARGET_DIR);
|
|
3255
|
-
if (
|
|
4844
|
+
if (existsSync7(BACKGROUNDS_TARGET_DIR)) {
|
|
3256
4845
|
rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
|
|
3257
4846
|
}
|
|
3258
4847
|
const entries = readdirSync4(themeDir, { withFileTypes: true });
|
|
@@ -3267,13 +4856,13 @@ async function applyTheme(themeName) {
|
|
|
3267
4856
|
const backgroundsSource = join5(themeDir, "backgrounds");
|
|
3268
4857
|
createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
|
|
3269
4858
|
}
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
const lynkBrowserDir = dirname3(LYNK_BROWSER_CSS);
|
|
3273
|
-
await ensureDir2(lynkBrowserDir);
|
|
3274
|
-
createSymlink(styleCssSource, LYNK_BROWSER_CSS);
|
|
4859
|
+
if (saveMapping) {
|
|
4860
|
+
await setDeviceTheme(themeName);
|
|
3275
4861
|
}
|
|
3276
4862
|
let output = `Theme '${theme.name}' applied successfully`;
|
|
4863
|
+
if (saveMapping) {
|
|
4864
|
+
output += ` (saved as device preference for '${getDeviceHostname()}')`;
|
|
4865
|
+
}
|
|
3277
4866
|
if (theme.metadata?.author) {
|
|
3278
4867
|
output += `
|
|
3279
4868
|
Author: ${theme.metadata.author}`;
|
|
@@ -3290,7 +4879,7 @@ Note: This is a light mode theme`;
|
|
|
3290
4879
|
}
|
|
3291
4880
|
async function showThemeInfo(themeName) {
|
|
3292
4881
|
const themeDir = join5(THEMES_DIR, themeName);
|
|
3293
|
-
if (!
|
|
4882
|
+
if (!existsSync7(themeDir)) {
|
|
3294
4883
|
console.error(`${colors5.red}Error: Theme '${themeName}' not found${colors5.reset}`);
|
|
3295
4884
|
process.exit(1);
|
|
3296
4885
|
}
|
|
@@ -3323,48 +4912,134 @@ ${colors5.green}Has wallpapers${colors5.reset}`);
|
|
|
3323
4912
|
console.log(`${colors5.yellow}Light mode theme${colors5.reset}`);
|
|
3324
4913
|
}
|
|
3325
4914
|
}
|
|
3326
|
-
async function runSetTheme(themeName) {
|
|
3327
|
-
return applyTheme(themeName);
|
|
4915
|
+
async function runSetTheme(themeName, saveMapping = false) {
|
|
4916
|
+
return applyTheme(themeName, saveMapping);
|
|
4917
|
+
}
|
|
4918
|
+
function showDeviceMappings() {
|
|
4919
|
+
const mappings = listDeviceMappings();
|
|
4920
|
+
const defaultTheme = getDefaultTheme();
|
|
4921
|
+
const currentDevice = getDeviceHostname();
|
|
4922
|
+
console.log(`${colors5.cyan}Device Theme Mappings${colors5.reset}`);
|
|
4923
|
+
console.log(`Current device: ${colors5.blue}${currentDevice}${colors5.reset}
|
|
4924
|
+
`);
|
|
4925
|
+
if (defaultTheme) {
|
|
4926
|
+
console.log(`Default theme: ${colors5.green}${defaultTheme}${colors5.reset}
|
|
4927
|
+
`);
|
|
4928
|
+
}
|
|
4929
|
+
if (mappings.length === 0) {
|
|
4930
|
+
console.log(`${colors5.dim}No device-specific themes configured.${colors5.reset}`);
|
|
4931
|
+
return;
|
|
4932
|
+
}
|
|
4933
|
+
console.log("Configured devices:");
|
|
4934
|
+
for (const mapping of mappings) {
|
|
4935
|
+
const marker = mapping.isCurrent ? ` ${colors5.green}(current)${colors5.reset}` : "";
|
|
4936
|
+
const date = new Date(mapping.setAt).toLocaleDateString();
|
|
4937
|
+
console.log(` ${colors5.blue}•${colors5.reset} ${mapping.device}${marker}: ${mapping.theme} ${colors5.dim}(set ${date})${colors5.reset}`);
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
async function showThemeList() {
|
|
4941
|
+
const themes = await listThemes();
|
|
4942
|
+
const deviceTheme = getDeviceTheme();
|
|
4943
|
+
if (themes.length === 0) {
|
|
4944
|
+
console.log(`${colors5.yellow}No themes available.${colors5.reset}`);
|
|
4945
|
+
console.log(`This system is compatible with omarchy themes.`);
|
|
4946
|
+
console.log(`
|
|
4947
|
+
Add themes to: ${colors5.cyan}~/.config/formalconf/themes/${colors5.reset}`);
|
|
4948
|
+
return;
|
|
4949
|
+
}
|
|
4950
|
+
console.log(`${colors5.cyan}Usage: formalconf theme <theme-name>${colors5.reset}`);
|
|
4951
|
+
console.log(` formalconf theme <theme-name> --save ${colors5.dim}(save as device preference)${colors5.reset}`);
|
|
4952
|
+
console.log(` formalconf theme --apply ${colors5.dim}(apply device's theme)${colors5.reset}`);
|
|
4953
|
+
console.log(` formalconf theme --list-devices ${colors5.dim}(show device mappings)${colors5.reset}`);
|
|
4954
|
+
console.log(` formalconf theme --default <name> ${colors5.dim}(set default theme)${colors5.reset}`);
|
|
4955
|
+
console.log(` formalconf theme --clear-default ${colors5.dim}(remove default theme)${colors5.reset}`);
|
|
4956
|
+
console.log(` formalconf theme --clear ${colors5.dim}(remove device mapping)${colors5.reset}`);
|
|
4957
|
+
console.log(` formalconf theme --info <theme-name> ${colors5.dim}(show theme details)${colors5.reset}
|
|
4958
|
+
`);
|
|
4959
|
+
console.log("Available themes:");
|
|
4960
|
+
for (const theme of themes) {
|
|
4961
|
+
const extras = [];
|
|
4962
|
+
if (theme.hasBackgrounds)
|
|
4963
|
+
extras.push("wallpapers");
|
|
4964
|
+
if (theme.isLightMode)
|
|
4965
|
+
extras.push("light");
|
|
4966
|
+
if (theme.name === deviceTheme)
|
|
4967
|
+
extras.push("device");
|
|
4968
|
+
const suffix = extras.length ? ` ${colors5.dim}(${extras.join(", ")})${colors5.reset}` : "";
|
|
4969
|
+
console.log(` ${colors5.blue}•${colors5.reset} ${theme.name}${suffix}`);
|
|
4970
|
+
}
|
|
3328
4971
|
}
|
|
3329
4972
|
async function main4() {
|
|
3330
4973
|
const { positionals, values } = parseArgs4({
|
|
3331
4974
|
args: process.argv.slice(2),
|
|
3332
4975
|
options: {
|
|
3333
|
-
info: { type: "boolean", short: "i" }
|
|
4976
|
+
info: { type: "boolean", short: "i" },
|
|
4977
|
+
save: { type: "boolean", short: "s" },
|
|
4978
|
+
apply: { type: "boolean", short: "a" },
|
|
4979
|
+
"list-devices": { type: "boolean", short: "l" },
|
|
4980
|
+
default: { type: "string", short: "d" },
|
|
4981
|
+
"clear-default": { type: "boolean" },
|
|
4982
|
+
clear: { type: "boolean", short: "c" }
|
|
3334
4983
|
},
|
|
3335
4984
|
allowPositionals: true
|
|
3336
4985
|
});
|
|
3337
4986
|
const [themeName] = positionals;
|
|
3338
|
-
if (
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
4987
|
+
if (values["list-devices"]) {
|
|
4988
|
+
showDeviceMappings();
|
|
4989
|
+
return;
|
|
4990
|
+
}
|
|
4991
|
+
if (values.clear) {
|
|
4992
|
+
const deviceTheme = getDeviceTheme();
|
|
4993
|
+
if (!deviceTheme) {
|
|
4994
|
+
console.log(`${colors5.yellow}No theme configured for this device.${colors5.reset}`);
|
|
4995
|
+
return;
|
|
3346
4996
|
}
|
|
3347
|
-
|
|
3348
|
-
console.log(
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
4997
|
+
await clearDeviceTheme();
|
|
4998
|
+
console.log(`${colors5.green}Removed theme mapping for '${getDeviceHostname()}'.${colors5.reset}`);
|
|
4999
|
+
return;
|
|
5000
|
+
}
|
|
5001
|
+
if (values["clear-default"]) {
|
|
5002
|
+
await setDefaultTheme(null);
|
|
5003
|
+
console.log(`${colors5.green}Default theme cleared.${colors5.reset}`);
|
|
5004
|
+
return;
|
|
5005
|
+
}
|
|
5006
|
+
if (values.default !== undefined) {
|
|
5007
|
+
const themeDir = join5(THEMES_DIR, values.default);
|
|
5008
|
+
if (!existsSync7(themeDir)) {
|
|
5009
|
+
console.error(`${colors5.red}Error: Theme '${values.default}' not found${colors5.reset}`);
|
|
5010
|
+
process.exit(1);
|
|
3359
5011
|
}
|
|
3360
|
-
|
|
5012
|
+
await setDefaultTheme(values.default);
|
|
5013
|
+
console.log(`${colors5.green}Default theme set to '${values.default}'.${colors5.reset}`);
|
|
5014
|
+
return;
|
|
5015
|
+
}
|
|
5016
|
+
if (values.apply) {
|
|
5017
|
+
const deviceTheme = getDeviceTheme();
|
|
5018
|
+
if (!deviceTheme) {
|
|
5019
|
+
console.log(`${colors5.yellow}No theme configured for device '${getDeviceHostname()}'.${colors5.reset}`);
|
|
5020
|
+
console.log(`Use 'formalconf theme <name> --save' to set a device preference.`);
|
|
5021
|
+
return;
|
|
5022
|
+
}
|
|
5023
|
+
const result2 = await applyTheme(deviceTheme);
|
|
5024
|
+
console.log(result2.success ? `${colors5.green}${result2.output}${colors5.reset}` : `${colors5.red}${result2.output}${colors5.reset}`);
|
|
5025
|
+
return;
|
|
5026
|
+
}
|
|
5027
|
+
if (!themeName) {
|
|
5028
|
+
const deviceTheme = getDeviceTheme();
|
|
5029
|
+
if (deviceTheme) {
|
|
5030
|
+
const result2 = await applyTheme(deviceTheme);
|
|
5031
|
+
console.log(result2.success ? `${colors5.green}${result2.output}${colors5.reset}` : `${colors5.red}${result2.output}${colors5.reset}`);
|
|
5032
|
+
} else {
|
|
5033
|
+
await showThemeList();
|
|
5034
|
+
}
|
|
5035
|
+
return;
|
|
3361
5036
|
}
|
|
3362
5037
|
if (values.info) {
|
|
3363
5038
|
await showThemeInfo(themeName);
|
|
3364
|
-
|
|
3365
|
-
const result = await applyTheme(themeName);
|
|
3366
|
-
console.log(result.success ? `${colors5.green}${result.output}${colors5.reset}` : `${colors5.red}${result.output}${colors5.reset}`);
|
|
5039
|
+
return;
|
|
3367
5040
|
}
|
|
5041
|
+
const result = await applyTheme(themeName, values.save ?? false);
|
|
5042
|
+
console.log(result.success ? `${colors5.green}${result.output}${colors5.reset}` : `${colors5.red}${result.output}${colors5.reset}`);
|
|
3368
5043
|
}
|
|
3369
5044
|
var isMainModule4 = process.argv[1]?.includes("set-theme");
|
|
3370
5045
|
if (isMainModule4) {
|
|
@@ -3376,16 +5051,19 @@ import { jsxDEV as jsxDEV19 } from "react/jsx-dev-runtime";
|
|
|
3376
5051
|
function ThemeMenu({ onBack }) {
|
|
3377
5052
|
const [themes, setThemes] = useState10([]);
|
|
3378
5053
|
const [loading, setLoading] = useState10(true);
|
|
5054
|
+
const [deviceTheme, setDeviceThemeName] = useState10(null);
|
|
3379
5055
|
const { state, output, success, isRunning, isResult, execute, reset } = useMenuAction();
|
|
5056
|
+
const hostname2 = getDeviceHostname();
|
|
3380
5057
|
const grid = useThemeGrid({
|
|
3381
5058
|
itemCount: themes.length,
|
|
3382
|
-
onSelect: (index) => applyTheme2(themes[index]),
|
|
5059
|
+
onSelect: (index) => applyTheme2(themes[index], false),
|
|
5060
|
+
onSelectAndSave: (index) => applyTheme2(themes[index], true),
|
|
3383
5061
|
onBack,
|
|
3384
5062
|
enabled: state === "menu" && !loading && themes.length > 0
|
|
3385
5063
|
});
|
|
3386
|
-
|
|
5064
|
+
useEffect6(() => {
|
|
3387
5065
|
async function loadThemes() {
|
|
3388
|
-
if (!
|
|
5066
|
+
if (!existsSync8(THEMES_DIR)) {
|
|
3389
5067
|
setThemes([]);
|
|
3390
5068
|
setLoading(false);
|
|
3391
5069
|
return;
|
|
@@ -3400,13 +5078,17 @@ function ThemeMenu({ onBack }) {
|
|
|
3400
5078
|
}
|
|
3401
5079
|
}
|
|
3402
5080
|
setThemes(loadedThemes);
|
|
5081
|
+
setDeviceThemeName(getDeviceTheme());
|
|
3403
5082
|
setLoading(false);
|
|
3404
5083
|
}
|
|
3405
5084
|
loadThemes();
|
|
3406
5085
|
}, []);
|
|
3407
|
-
const applyTheme2 = async (theme) => {
|
|
5086
|
+
const applyTheme2 = async (theme, saveAsDeviceDefault) => {
|
|
3408
5087
|
const themeName = theme.path.split("/").pop();
|
|
3409
|
-
await execute(() => runSetTheme(themeName));
|
|
5088
|
+
await execute(() => runSetTheme(themeName, saveAsDeviceDefault));
|
|
5089
|
+
if (saveAsDeviceDefault) {
|
|
5090
|
+
setDeviceThemeName(themeName);
|
|
5091
|
+
}
|
|
3410
5092
|
};
|
|
3411
5093
|
const visibleThemes = useMemo4(() => {
|
|
3412
5094
|
return themes.slice(grid.visibleStartIndex, grid.visibleEndIndex);
|
|
@@ -3476,7 +5158,8 @@ function ThemeMenu({ onBack }) {
|
|
|
3476
5158
|
children: visibleThemes.map((theme, index) => /* @__PURE__ */ jsxDEV19(ThemeCard, {
|
|
3477
5159
|
theme,
|
|
3478
5160
|
isSelected: grid.visibleStartIndex + index === grid.selectedIndex,
|
|
3479
|
-
width: grid.cardWidth
|
|
5161
|
+
width: grid.cardWidth,
|
|
5162
|
+
isDeviceTheme: theme.name === deviceTheme
|
|
3480
5163
|
}, theme.path, false, undefined, this))
|
|
3481
5164
|
}, undefined, false, undefined, this),
|
|
3482
5165
|
grid.showScrollDown && /* @__PURE__ */ jsxDEV19(Text15, {
|
|
@@ -3491,16 +5174,27 @@ function ThemeMenu({ onBack }) {
|
|
|
3491
5174
|
}, undefined, true, undefined, this),
|
|
3492
5175
|
/* @__PURE__ */ jsxDEV19(Box16, {
|
|
3493
5176
|
marginTop: 1,
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
5177
|
+
flexDirection: "column",
|
|
5178
|
+
children: [
|
|
5179
|
+
/* @__PURE__ */ jsxDEV19(Text15, {
|
|
5180
|
+
dimColor: true,
|
|
5181
|
+
children: "←→↑↓/hjkl navigate • Enter apply • Shift+Enter save as device default • Esc back"
|
|
5182
|
+
}, undefined, false, undefined, this),
|
|
5183
|
+
/* @__PURE__ */ jsxDEV19(Text15, {
|
|
5184
|
+
dimColor: true,
|
|
5185
|
+
children: [
|
|
5186
|
+
"Device: ",
|
|
5187
|
+
hostname2
|
|
5188
|
+
]
|
|
5189
|
+
}, undefined, true, undefined, this)
|
|
5190
|
+
]
|
|
5191
|
+
}, undefined, true, undefined, this)
|
|
3499
5192
|
]
|
|
3500
5193
|
}, undefined, true, undefined, this);
|
|
3501
5194
|
}
|
|
3502
5195
|
|
|
3503
5196
|
// src/cli/formalconf.tsx
|
|
5197
|
+
init_runtime();
|
|
3504
5198
|
import { jsxDEV as jsxDEV20 } from "react/jsx-dev-runtime";
|
|
3505
5199
|
var BREADCRUMBS = {
|
|
3506
5200
|
main: ["Main"],
|
|
@@ -3517,7 +5211,7 @@ function App() {
|
|
|
3517
5211
|
if (input === "q")
|
|
3518
5212
|
exit();
|
|
3519
5213
|
});
|
|
3520
|
-
|
|
5214
|
+
useEffect7(() => {
|
|
3521
5215
|
async function init() {
|
|
3522
5216
|
await ensureConfigDir();
|
|
3523
5217
|
const result = await checkPrerequisites();
|