formalconf 2.0.6 → 2.0.7

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.
Files changed (2) hide show
  1. package/dist/formalconf.js +1991 -493
  2. package/package.json +1 -1
@@ -1,54 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // src/cli/formalconf.tsx
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 required = [
265
- { name: "stow", install: "brew install stow" },
266
- { name: "brew", install: "https://brew.sh" }
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 required) {
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");
@@ -414,7 +446,7 @@ function StatusIndicator({
414
446
  // package.json
415
447
  var package_default = {
416
448
  name: "formalconf",
417
- version: "2.0.6",
449
+ version: "2.0.7",
418
450
  description: "Dotfiles management TUI for macOS - config management, package sync, and theme switching",
419
451
  type: "module",
420
452
  main: "./dist/formalconf.js",
@@ -739,6 +771,7 @@ function VimSelect({ options, onChange, isDisabled = false }) {
739
771
  // src/lib/templates.ts
740
772
  import { join as join3 } from "path";
741
773
  import { existsSync as existsSync2 } from "fs";
774
+ init_runtime();
742
775
  var EXAMPLE_CONFIG_README = `# Example Stow Config Package
743
776
 
744
777
  This is an example dotfiles package for use with GNU Stow.
@@ -1337,6 +1370,7 @@ function useBackNavigation({
1337
1370
  }
1338
1371
 
1339
1372
  // src/cli/config-manager.ts
1373
+ init_shell();
1340
1374
  import { parseArgs } from "util";
1341
1375
  import { readdirSync as readdirSync2, existsSync as existsSync3, lstatSync as lstatSync2, readlinkSync as readlinkSync2 } from "fs";
1342
1376
  var colors2 = {
@@ -1349,7 +1383,9 @@ var colors2 = {
1349
1383
  };
1350
1384
  async function checkStow() {
1351
1385
  if (!await commandExists("stow")) {
1352
- console.error(`${colors2.red}Error: GNU Stow is not installed. Install with: brew install stow${colors2.reset}`);
1386
+ const isMacOS = process.platform === "darwin";
1387
+ const installHint = isMacOS ? "brew install stow" : "Install via your package manager (pacman -S stow, apt install stow, dnf install stow)";
1388
+ console.error(`${colors2.red}Error: GNU Stow is not installed. ${installHint}${colors2.reset}`);
1353
1389
  process.exit(1);
1354
1390
  }
1355
1391
  }
@@ -1674,7 +1710,7 @@ function ConfigMenu({ onBack }) {
1674
1710
  }
1675
1711
 
1676
1712
  // src/components/menus/PackageMenu.tsx
1677
- import { useState as useState8, useCallback as useCallback2, useMemo as useMemo2, useRef } from "react";
1713
+ import { useState as useState8, useCallback as useCallback2, useMemo as useMemo2, useRef, useEffect as useEffect4 } from "react";
1678
1714
  import { Box as Box14, Text as Text13, useInput as useInput9 } from "ink";
1679
1715
 
1680
1716
  // src/components/ScrollableLog.tsx
@@ -1938,29 +1974,76 @@ function OrphanTable({ result, onAction, onDismiss }) {
1938
1974
  }
1939
1975
 
1940
1976
  // src/cli/pkg-sync.ts
1977
+ init_shell();
1978
+ init_runtime();
1941
1979
  import { parseArgs as parseArgs2 } from "util";
1942
1980
 
1943
1981
  // src/lib/config.ts
1982
+ init_runtime();
1944
1983
  import { existsSync as existsSync4 } from "fs";
1945
- var DEFAULT_CONFIG = {
1984
+ var DEFAULT_CONFIG_V2 = {
1985
+ version: 2,
1946
1986
  config: {
1947
1987
  purge: false,
1948
1988
  purgeInteractive: true,
1949
1989
  autoUpdate: true
1950
1990
  },
1951
- taps: [],
1952
- packages: [],
1953
- casks: [],
1954
- mas: {}
1991
+ global: {
1992
+ packages: []
1993
+ },
1994
+ macos: {
1995
+ taps: [],
1996
+ formulas: [],
1997
+ casks: [],
1998
+ mas: {}
1999
+ }
1955
2000
  };
2001
+ function migrateV1toV2(v1Config) {
2002
+ return {
2003
+ version: 2,
2004
+ config: {
2005
+ purge: v1Config.config.purge,
2006
+ purgeInteractive: v1Config.config.purgeInteractive,
2007
+ autoUpdate: v1Config.config.autoUpdate
2008
+ },
2009
+ global: {
2010
+ packages: []
2011
+ },
2012
+ macos: {
2013
+ taps: v1Config.taps,
2014
+ formulas: v1Config.packages,
2015
+ casks: v1Config.casks,
2016
+ mas: v1Config.mas
2017
+ }
2018
+ };
2019
+ }
2020
+ function isV1Config(config) {
2021
+ if (!config || typeof config !== "object")
2022
+ return false;
2023
+ const c = config;
2024
+ return !("version" in c) && "config" in c && "taps" in c && "packages" in c && "casks" in c;
2025
+ }
2026
+ function configIsV2(config) {
2027
+ return "version" in config && config.version === 2;
2028
+ }
1956
2029
  async function loadPkgConfig(path) {
1957
2030
  await ensureConfigDir();
1958
2031
  const configPath = path || PKG_CONFIG_PATH;
1959
2032
  if (!existsSync4(configPath)) {
1960
- await savePkgConfig(DEFAULT_CONFIG, configPath);
1961
- return DEFAULT_CONFIG;
2033
+ await savePkgConfig(DEFAULT_CONFIG_V2, configPath);
2034
+ return DEFAULT_CONFIG_V2;
2035
+ }
2036
+ const rawConfig = await readJson(configPath);
2037
+ if (isV1Config(rawConfig)) {
2038
+ const v2Config = migrateV1toV2(rawConfig);
2039
+ await savePkgConfig(v2Config, configPath);
2040
+ return v2Config;
1962
2041
  }
1963
- return readJson(configPath);
2042
+ if (configIsV2(rawConfig)) {
2043
+ return rawConfig;
2044
+ }
2045
+ await savePkgConfig(DEFAULT_CONFIG_V2, configPath);
2046
+ return DEFAULT_CONFIG_V2;
1964
2047
  }
1965
2048
  async function savePkgConfig(config, path) {
1966
2049
  await ensureConfigDir();
@@ -1979,249 +2062,1486 @@ async function savePkgLock(lock) {
1979
2062
  }
1980
2063
 
1981
2064
  // src/lib/lockfile.ts
1982
- async function fetchInstalledVersions() {
1983
- const config = await loadPkgConfig();
1984
- const now = new Date().toISOString();
1985
- const formulas = {};
1986
- const casks = {};
1987
- if (config.packages.length > 0) {
1988
- const result = await exec([
1989
- "brew",
1990
- "info",
1991
- "--json=v2",
1992
- ...config.packages
1993
- ]);
1994
- if (result.success && result.stdout) {
1995
- const info = JSON.parse(result.stdout);
1996
- for (const formula of info.formulae) {
1997
- if (formula.installed.length > 0) {
1998
- formulas[formula.name] = {
1999
- version: formula.installed[0].version,
2000
- tap: formula.tap,
2001
- installedAt: now
2002
- };
2065
+ init_shell();
2066
+
2067
+ // src/lib/platform.ts
2068
+ init_runtime();
2069
+ var cachedPlatformInfo = null;
2070
+ function getOS() {
2071
+ return process.platform === "darwin" ? "darwin" : "linux";
2072
+ }
2073
+ async function getLinuxDistro() {
2074
+ if (getOS() !== "linux") {
2075
+ return "unknown";
2076
+ }
2077
+ try {
2078
+ const result = await exec(["cat", "/etc/os-release"]);
2079
+ if (!result.success) {
2080
+ return "unknown";
2081
+ }
2082
+ const lines = result.stdout.split(`
2083
+ `);
2084
+ for (const line of lines) {
2085
+ if (line.startsWith("ID=")) {
2086
+ const id = line.slice(3).replace(/"/g, "").toLowerCase();
2087
+ switch (id) {
2088
+ case "arch":
2089
+ case "manjaro":
2090
+ case "endeavouros":
2091
+ case "artix":
2092
+ return "arch";
2093
+ case "debian":
2094
+ return "debian";
2095
+ case "ubuntu":
2096
+ case "linuxmint":
2097
+ case "pop":
2098
+ case "elementary":
2099
+ return "ubuntu";
2100
+ case "fedora":
2101
+ return "fedora";
2102
+ case "rhel":
2103
+ case "centos":
2104
+ case "rocky":
2105
+ case "almalinux":
2106
+ return "rhel";
2107
+ case "opensuse":
2108
+ case "opensuse-leap":
2109
+ case "opensuse-tumbleweed":
2110
+ return "opensuse";
2111
+ default:
2112
+ const idLikeLine = lines.find((l) => l.startsWith("ID_LIKE="));
2113
+ if (idLikeLine) {
2114
+ const idLike = idLikeLine.slice(8).replace(/"/g, "").toLowerCase();
2115
+ if (idLike.includes("arch"))
2116
+ return "arch";
2117
+ if (idLike.includes("debian") || idLike.includes("ubuntu"))
2118
+ return "debian";
2119
+ if (idLike.includes("fedora") || idLike.includes("rhel"))
2120
+ return "fedora";
2121
+ }
2122
+ return "unknown";
2003
2123
  }
2004
2124
  }
2005
2125
  }
2126
+ } catch {
2127
+ return "unknown";
2006
2128
  }
2007
- if (config.casks.length > 0) {
2008
- const result = await exec([
2009
- "brew",
2010
- "info",
2011
- "--json=v2",
2012
- "--cask",
2013
- ...config.casks
2014
- ]);
2015
- if (result.success && result.stdout) {
2016
- const info = JSON.parse(result.stdout);
2017
- for (const cask of info.casks) {
2018
- if (cask.installed) {
2019
- casks[cask.token] = {
2020
- version: cask.installed,
2021
- installedAt: now
2022
- };
2023
- }
2129
+ return "unknown";
2130
+ }
2131
+ async function detectAurHelper() {
2132
+ const helpers = ["yay", "paru", "trizen"];
2133
+ for (const helper of helpers) {
2134
+ if (await commandExists(helper)) {
2135
+ return helper;
2136
+ }
2137
+ }
2138
+ return "none";
2139
+ }
2140
+ async function detectAvailablePackageManagers() {
2141
+ const os = getOS();
2142
+ const managers = [];
2143
+ if (os === "darwin") {
2144
+ if (await commandExists("brew")) {
2145
+ managers.push("homebrew");
2146
+ }
2147
+ if (await commandExists("mas")) {
2148
+ managers.push("mas");
2149
+ }
2150
+ } else {
2151
+ if (await commandExists("pacman")) {
2152
+ managers.push("pacman");
2153
+ const aurHelper = await detectAurHelper();
2154
+ if (aurHelper !== "none") {
2155
+ managers.push("aur");
2024
2156
  }
2025
2157
  }
2158
+ if (await commandExists("apt")) {
2159
+ managers.push("apt");
2160
+ }
2161
+ if (await commandExists("dnf")) {
2162
+ managers.push("dnf");
2163
+ }
2164
+ if (await commandExists("flatpak")) {
2165
+ managers.push("flatpak");
2166
+ }
2026
2167
  }
2027
- return { formulas, casks };
2168
+ return managers;
2028
2169
  }
2029
- async function generateLockfile() {
2030
- const { formulas, casks } = await fetchInstalledVersions();
2031
- return {
2032
- version: 1,
2033
- lastUpdated: new Date().toISOString(),
2034
- formulas,
2035
- casks
2170
+ async function getPlatformInfo() {
2171
+ if (cachedPlatformInfo) {
2172
+ return cachedPlatformInfo;
2173
+ }
2174
+ const os = getOS();
2175
+ const distro = os === "linux" ? await getLinuxDistro() : null;
2176
+ const aurHelper = distro === "arch" ? await detectAurHelper() : null;
2177
+ const availableManagers = await detectAvailablePackageManagers();
2178
+ cachedPlatformInfo = {
2179
+ os,
2180
+ distro,
2181
+ aurHelper,
2182
+ availableManagers
2036
2183
  };
2184
+ return cachedPlatformInfo;
2037
2185
  }
2038
- async function updateLockfile() {
2039
- const existing = await loadPkgLock();
2040
- const { formulas, casks } = await fetchInstalledVersions();
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
- }
2186
+ function getPlatformDisplayName(info) {
2187
+ if (info.os === "darwin") {
2188
+ return "macOS";
2049
2189
  }
2050
- const mergedCasks = {};
2051
- for (const [name, info] of Object.entries(casks)) {
2052
- const prev = existing?.casks[name];
2053
- if (prev && prev.version === info.version) {
2054
- mergedCasks[name] = prev;
2055
- } else {
2056
- mergedCasks[name] = info;
2057
- }
2190
+ switch (info.distro) {
2191
+ case "arch":
2192
+ return "Arch Linux";
2193
+ case "debian":
2194
+ return "Debian";
2195
+ case "ubuntu":
2196
+ return "Ubuntu";
2197
+ case "fedora":
2198
+ return "Fedora";
2199
+ case "rhel":
2200
+ return "RHEL/CentOS";
2201
+ case "opensuse":
2202
+ return "openSUSE";
2203
+ default:
2204
+ return "Linux";
2058
2205
  }
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
2206
  }
2068
- async function getChangedPackages() {
2069
- const existing = await loadPkgLock();
2070
- const { formulas, casks } = await fetchInstalledVersions();
2071
- const added = [];
2072
- const removed = [];
2073
- const upgraded = [];
2074
- if (!existing) {
2075
- added.push(...Object.keys(formulas), ...Object.keys(casks));
2076
- return { added, removed, upgraded };
2207
+
2208
+ // src/lib/package-managers/homebrew.ts
2209
+ init_runtime();
2210
+ init_runtime();
2211
+ async function runBrewCommand(args, callbacks) {
2212
+ const cmd = ["brew", ...args];
2213
+ if (callbacks?.onLog) {
2214
+ const exitCode = await execStreaming(cmd, callbacks.onLog);
2215
+ return exitCode === 0;
2077
2216
  }
2078
- for (const [name, info] of Object.entries(formulas)) {
2079
- if (!existing.formulas[name]) {
2080
- added.push(name);
2081
- } else if (existing.formulas[name].version !== info.version) {
2082
- upgraded.push({
2083
- name,
2084
- from: existing.formulas[name].version,
2085
- to: info.version
2086
- });
2217
+ const result = await exec(cmd);
2218
+ return result.success;
2219
+ }
2220
+
2221
+ class HomebrewFormulas {
2222
+ type = "homebrew";
2223
+ displayName = "Homebrew Formulas";
2224
+ async isAvailable() {
2225
+ return commandExists("brew");
2226
+ }
2227
+ async update(callbacks) {
2228
+ return runBrewCommand(["update"], callbacks);
2229
+ }
2230
+ async install(packages, callbacks) {
2231
+ if (packages.length === 0)
2232
+ return true;
2233
+ return runBrewCommand(["install", ...packages], callbacks);
2234
+ }
2235
+ async uninstall(packages, callbacks) {
2236
+ if (packages.length === 0)
2237
+ return true;
2238
+ return runBrewCommand(["uninstall", ...packages], callbacks);
2239
+ }
2240
+ async upgrade(packages, callbacks) {
2241
+ const args = ["upgrade", "--formula"];
2242
+ if (packages && packages.length > 0) {
2243
+ args.push(...packages);
2087
2244
  }
2245
+ return runBrewCommand(args, callbacks);
2088
2246
  }
2089
- for (const name of Object.keys(existing.formulas)) {
2090
- if (!formulas[name]) {
2091
- removed.push(name);
2247
+ async listInstalled() {
2248
+ const result = await exec(["brew", "info", "--json=v2", "--installed"]);
2249
+ if (!result.success)
2250
+ return [];
2251
+ try {
2252
+ const info = JSON.parse(result.stdout);
2253
+ return info.formulae.map((f) => ({
2254
+ name: f.name,
2255
+ version: f.installed[0]?.version || "unknown"
2256
+ }));
2257
+ } catch {
2258
+ return [];
2092
2259
  }
2093
2260
  }
2094
- for (const [name, info] of Object.entries(casks)) {
2095
- if (!existing.casks[name]) {
2096
- added.push(name);
2097
- } else if (existing.casks[name].version !== info.version) {
2098
- upgraded.push({
2261
+ async listOutdated() {
2262
+ const result = await exec(["brew", "outdated", "--formula", "--json"]);
2263
+ if (!result.success || !result.stdout)
2264
+ return [];
2265
+ try {
2266
+ const outdated = JSON.parse(result.stdout);
2267
+ return outdated.formulae?.map((f) => ({
2268
+ name: f.name,
2269
+ currentVersion: f.installed_versions?.[0] || "unknown",
2270
+ newVersion: f.current_version
2271
+ })) || [];
2272
+ } catch {
2273
+ const quietResult = await exec(["brew", "outdated", "--formula", "--quiet"]);
2274
+ if (!quietResult.success)
2275
+ return [];
2276
+ return quietResult.stdout.split(`
2277
+ `).filter(Boolean).map((name) => ({
2099
2278
  name,
2100
- from: existing.casks[name].version,
2101
- to: info.version
2102
- });
2279
+ currentVersion: "unknown",
2280
+ newVersion: "unknown"
2281
+ }));
2103
2282
  }
2104
2283
  }
2105
- for (const name of Object.keys(existing.casks)) {
2106
- if (!casks[name]) {
2107
- removed.push(name);
2284
+ async listLeaves() {
2285
+ const result = await exec(["brew", "leaves"]);
2286
+ if (!result.success)
2287
+ return [];
2288
+ return result.stdout.split(`
2289
+ `).filter(Boolean);
2290
+ }
2291
+ async cleanup(callbacks) {
2292
+ const autoremove = await runBrewCommand(["autoremove"], callbacks);
2293
+ const cleanup = await runBrewCommand(["cleanup"], callbacks);
2294
+ return autoremove && cleanup;
2295
+ }
2296
+ async addRepository(repo, callbacks) {
2297
+ return runBrewCommand(["tap", repo], callbacks);
2298
+ }
2299
+ async isInstalled(packages) {
2300
+ const result = new Map;
2301
+ const listResult = await exec(["brew", "list", "--formula"]);
2302
+ const installed = new Set(listResult.stdout.split(`
2303
+ `).filter(Boolean));
2304
+ for (const pkg of packages) {
2305
+ const shortName = pkg.split("/").pop() || pkg;
2306
+ result.set(pkg, installed.has(shortName) || installed.has(pkg));
2108
2307
  }
2308
+ return result;
2309
+ }
2310
+ async getTappedRepos() {
2311
+ const result = await exec(["brew", "tap"]);
2312
+ if (!result.success)
2313
+ return [];
2314
+ return result.stdout.split(`
2315
+ `).filter(Boolean);
2316
+ }
2317
+ async hasDependents(pkg) {
2318
+ const result = await exec(["brew", "uses", "--installed", pkg]);
2319
+ return result.success && result.stdout.trim().length > 0;
2109
2320
  }
2110
- return { added, removed, upgraded };
2111
2321
  }
2112
2322
 
2113
- // src/types/pkg-config.ts
2114
- var SYSTEM_APP_IDS = [
2115
- 409183694,
2116
- 409203825,
2117
- 409201541,
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);
2323
+ class HomebrewCasks {
2324
+ type = "homebrew";
2325
+ displayName = "Homebrew Casks";
2326
+ async isAvailable() {
2327
+ return commandExists("brew");
2153
2328
  }
2154
- return execLive(command, cwd);
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);
2329
+ async update(callbacks) {
2330
+ return runBrewCommand(["update"], callbacks);
2160
2331
  }
2161
- }
2162
- async function getOutdatedPackages() {
2163
- const [formulas, casks] = await Promise.all([
2164
- exec(["brew", "outdated", "--formula", "--quiet"]),
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" })));
2332
+ async install(packages, callbacks) {
2333
+ if (packages.length === 0)
2334
+ return true;
2335
+ return runBrewCommand(["install", "--cask", ...packages], callbacks);
2171
2336
  }
2172
- if (casks.stdout) {
2173
- packages.push(...casks.stdout.split(`
2174
- `).filter(Boolean).map((name) => ({ name, type: "cask" })));
2337
+ async uninstall(packages, callbacks) {
2338
+ if (packages.length === 0)
2339
+ return true;
2340
+ return runBrewCommand(["uninstall", "--cask", ...packages], callbacks);
2175
2341
  }
2176
- return packages;
2177
- }
2178
- async function getOutdatedMas() {
2179
- const result = await exec(["mas", "outdated"]);
2180
- if (!result.success || !result.stdout)
2181
- return [];
2182
- return result.stdout.split(`
2183
- `).filter(Boolean).map((line) => {
2184
- const match = line.match(/^(\d+)\s+(.+?)(?:\s+\(|$)/);
2342
+ async upgrade(packages, callbacks) {
2343
+ const args = ["upgrade", "--cask", "--greedy"];
2344
+ if (packages && packages.length > 0) {
2345
+ args.push(...packages);
2346
+ }
2347
+ return runBrewCommand(args, callbacks);
2348
+ }
2349
+ async listInstalled() {
2350
+ const result = await exec(["brew", "info", "--json=v2", "--cask", "--installed"]);
2351
+ if (!result.success)
2352
+ return [];
2353
+ try {
2354
+ const info = JSON.parse(result.stdout);
2355
+ return info.casks.map((c) => ({
2356
+ name: c.token,
2357
+ version: c.installed || "unknown"
2358
+ }));
2359
+ } catch {
2360
+ return [];
2361
+ }
2362
+ }
2363
+ async listOutdated() {
2364
+ const result = await exec(["brew", "outdated", "--cask", "--quiet"]);
2365
+ if (!result.success)
2366
+ return [];
2367
+ return result.stdout.split(`
2368
+ `).filter(Boolean).map((name) => ({
2369
+ name,
2370
+ currentVersion: "unknown",
2371
+ newVersion: "unknown"
2372
+ }));
2373
+ }
2374
+ async cleanup(callbacks) {
2375
+ return runBrewCommand(["cleanup"], callbacks);
2376
+ }
2377
+ async isInstalled(packages) {
2378
+ const result = new Map;
2379
+ const listResult = await exec(["brew", "list", "--cask"]);
2380
+ const installed = new Set(listResult.stdout.split(`
2381
+ `).filter(Boolean));
2382
+ for (const pkg of packages) {
2383
+ result.set(pkg, installed.has(pkg));
2384
+ }
2385
+ return result;
2386
+ }
2387
+ }
2388
+
2389
+ // src/lib/package-managers/mas.ts
2390
+ init_runtime();
2391
+ init_runtime();
2392
+ function parseMasOutput(output) {
2393
+ const results = [];
2394
+ for (const line of output.split(`
2395
+ `).filter(Boolean)) {
2396
+ const match = line.match(/^(\d+)\s+(.+?)(?:\s+\(([^)]+)\))?$/);
2185
2397
  if (match) {
2186
- return { id: parseInt(match[1], 10), name: match[2].trim() };
2398
+ results.push({
2399
+ id: parseInt(match[1], 10),
2400
+ name: match[2].trim(),
2401
+ version: match[3]
2402
+ });
2403
+ }
2404
+ }
2405
+ return results;
2406
+ }
2407
+
2408
+ class MacAppStore {
2409
+ type = "mas";
2410
+ displayName = "Mac App Store";
2411
+ async isAvailable() {
2412
+ return commandExists("mas");
2413
+ }
2414
+ async update(_callbacks) {
2415
+ return true;
2416
+ }
2417
+ async install(packages, callbacks) {
2418
+ if (packages.length === 0)
2419
+ return true;
2420
+ for (const appId of packages) {
2421
+ const cmd = ["mas", "install", appId];
2422
+ if (callbacks?.onLog) {
2423
+ const exitCode = await execStreamingWithTTY(cmd, callbacks.onLog);
2424
+ if (exitCode !== 0)
2425
+ return false;
2426
+ } else {
2427
+ const result = await exec(cmd);
2428
+ if (!result.success)
2429
+ return false;
2430
+ }
2431
+ }
2432
+ return true;
2433
+ }
2434
+ async uninstall(packages, callbacks) {
2435
+ if (packages.length === 0)
2436
+ return true;
2437
+ for (const appId of packages) {
2438
+ const cmd = ["mas", "uninstall", appId];
2439
+ if (callbacks?.onLog) {
2440
+ const exitCode = await execStreamingWithTTY(cmd, callbacks.onLog);
2441
+ if (exitCode !== 0)
2442
+ return false;
2443
+ } else {
2444
+ const result = await exec(cmd);
2445
+ if (!result.success)
2446
+ return false;
2447
+ }
2448
+ }
2449
+ return true;
2450
+ }
2451
+ async upgrade(_packages, callbacks) {
2452
+ const cmd = ["mas", "upgrade"];
2453
+ if (callbacks?.onLog) {
2454
+ const exitCode = await execStreamingWithTTY(cmd, callbacks.onLog);
2455
+ return exitCode === 0;
2456
+ }
2457
+ const result = await exec(cmd);
2458
+ return result.success;
2459
+ }
2460
+ async listInstalled() {
2461
+ const result = await exec(["mas", "list"]);
2462
+ if (!result.success)
2463
+ return [];
2464
+ const apps = parseMasOutput(result.stdout);
2465
+ return apps.map((app) => ({
2466
+ name: String(app.id),
2467
+ version: app.version || "unknown"
2468
+ }));
2469
+ }
2470
+ async listOutdated() {
2471
+ const result = await exec(["mas", "outdated"]);
2472
+ if (!result.success)
2473
+ return [];
2474
+ const apps = parseMasOutput(result.stdout);
2475
+ return apps.map((app) => ({
2476
+ name: String(app.id),
2477
+ currentVersion: app.version || "unknown",
2478
+ newVersion: "available"
2479
+ }));
2480
+ }
2481
+ async cleanup(_callbacks) {
2482
+ return true;
2483
+ }
2484
+ async isInstalled(packages) {
2485
+ const result = new Map;
2486
+ const listResult = await exec(["mas", "list"]);
2487
+ const apps = parseMasOutput(listResult.stdout);
2488
+ const installedIds = new Set(apps.map((app) => String(app.id)));
2489
+ for (const appId of packages) {
2490
+ result.set(appId, installedIds.has(appId));
2491
+ }
2492
+ return result;
2493
+ }
2494
+ async getInstalledApps() {
2495
+ const result = await exec(["mas", "list"]);
2496
+ if (!result.success)
2497
+ return [];
2498
+ return parseMasOutput(result.stdout);
2499
+ }
2500
+ }
2501
+
2502
+ // src/lib/package-managers/pacman.ts
2503
+ init_runtime();
2504
+ init_runtime();
2505
+ async function runPacmanCommand(args, callbacks, sudo = false) {
2506
+ const cmd = sudo ? ["sudo", "pacman", ...args] : ["pacman", ...args];
2507
+ if (callbacks?.onLog) {
2508
+ const exitCode = await execStreaming(cmd, callbacks.onLog);
2509
+ return exitCode === 0;
2510
+ }
2511
+ const result = await exec(cmd);
2512
+ return result.success;
2513
+ }
2514
+
2515
+ class Pacman {
2516
+ type = "pacman";
2517
+ displayName = "Pacman";
2518
+ async isAvailable() {
2519
+ return commandExists("pacman");
2520
+ }
2521
+ async update(callbacks) {
2522
+ return runPacmanCommand(["-Sy", "--noconfirm"], callbacks, true);
2523
+ }
2524
+ async install(packages, callbacks) {
2525
+ if (packages.length === 0)
2526
+ return true;
2527
+ return runPacmanCommand(["-S", "--noconfirm", "--needed", ...packages], callbacks, true);
2528
+ }
2529
+ async uninstall(packages, callbacks) {
2530
+ if (packages.length === 0)
2531
+ return true;
2532
+ return runPacmanCommand(["-Rs", "--noconfirm", ...packages], callbacks, true);
2533
+ }
2534
+ async upgrade(packages, callbacks) {
2535
+ if (packages && packages.length > 0) {
2536
+ return runPacmanCommand(["-S", "--noconfirm", ...packages], callbacks, true);
2537
+ }
2538
+ return runPacmanCommand(["-Syu", "--noconfirm"], callbacks, true);
2539
+ }
2540
+ async listInstalled() {
2541
+ const result = await exec(["pacman", "-Qe"]);
2542
+ if (!result.success)
2543
+ return [];
2544
+ return result.stdout.split(`
2545
+ `).filter(Boolean).map((line) => {
2546
+ const [name, version] = line.split(" ");
2547
+ return { name, version: version || "unknown" };
2548
+ });
2549
+ }
2550
+ async listOutdated() {
2551
+ const checkupdatesExists = await commandExists("checkupdates");
2552
+ if (checkupdatesExists) {
2553
+ const result2 = await exec(["checkupdates"]);
2554
+ if (!result2.stdout)
2555
+ return [];
2556
+ return result2.stdout.split(`
2557
+ `).filter(Boolean).map((line) => {
2558
+ const match = line.match(/^(\S+)\s+(\S+)\s+->\s+(\S+)$/);
2559
+ if (match) {
2560
+ return {
2561
+ name: match[1],
2562
+ currentVersion: match[2],
2563
+ newVersion: match[3]
2564
+ };
2565
+ }
2566
+ return { name: line, currentVersion: "unknown", newVersion: "unknown" };
2567
+ });
2568
+ }
2569
+ await exec(["sudo", "pacman", "-Sy"]);
2570
+ const result = await exec(["pacman", "-Qu"]);
2571
+ if (!result.success || !result.stdout)
2572
+ return [];
2573
+ return result.stdout.split(`
2574
+ `).filter(Boolean).map((line) => {
2575
+ const [name, ...rest] = line.split(" ");
2576
+ const versionPart = rest.join(" ");
2577
+ const match = versionPart.match(/(\S+)\s+->\s+(\S+)/);
2578
+ return {
2579
+ name,
2580
+ currentVersion: match?.[1] || "unknown",
2581
+ newVersion: match?.[2] || "unknown"
2582
+ };
2583
+ });
2584
+ }
2585
+ async listLeaves() {
2586
+ const result = await exec(["pacman", "-Qqe"]);
2587
+ if (!result.success)
2588
+ return [];
2589
+ return result.stdout.split(`
2590
+ `).filter(Boolean);
2591
+ }
2592
+ async cleanup(callbacks) {
2593
+ const orphansResult = await exec(["pacman", "-Qdtq"]);
2594
+ if (orphansResult.success && orphansResult.stdout.trim()) {
2595
+ const orphans = orphansResult.stdout.split(`
2596
+ `).filter(Boolean);
2597
+ if (orphans.length > 0) {
2598
+ await runPacmanCommand(["-Rs", "--noconfirm", ...orphans], callbacks, true);
2599
+ }
2600
+ }
2601
+ return runPacmanCommand(["-Sc", "--noconfirm"], callbacks, true);
2602
+ }
2603
+ async isInstalled(packages) {
2604
+ const result = new Map;
2605
+ const listResult = await exec(["pacman", "-Qq"]);
2606
+ const installed = new Set(listResult.stdout.split(`
2607
+ `).filter(Boolean));
2608
+ for (const pkg of packages) {
2609
+ result.set(pkg, installed.has(pkg));
2610
+ }
2611
+ return result;
2612
+ }
2613
+ }
2614
+
2615
+ // src/lib/package-managers/aur.ts
2616
+ init_runtime();
2617
+ init_runtime();
2618
+ async function runAurCommand(helper, args, callbacks) {
2619
+ if (helper === "none")
2620
+ return false;
2621
+ const cmd = [helper, ...args];
2622
+ if (callbacks?.onLog) {
2623
+ const exitCode = await execStreaming(cmd, callbacks.onLog);
2624
+ return exitCode === 0;
2625
+ }
2626
+ const result = await exec(cmd);
2627
+ return result.success;
2628
+ }
2629
+
2630
+ class AurPackageManager {
2631
+ type = "aur";
2632
+ displayName = "AUR";
2633
+ helper = "none";
2634
+ helperDetected = false;
2635
+ async getHelper() {
2636
+ if (!this.helperDetected) {
2637
+ this.helper = await detectAurHelper();
2638
+ this.helperDetected = true;
2639
+ }
2640
+ return this.helper;
2641
+ }
2642
+ async isAvailable() {
2643
+ const helper = await this.getHelper();
2644
+ return helper !== "none";
2645
+ }
2646
+ async ensureAurHelper(callbacks) {
2647
+ const helper = await this.getHelper();
2648
+ if (helper !== "none")
2649
+ return true;
2650
+ const log = callbacks?.onLog || console.log;
2651
+ log("No AUR helper found. Installing yay...");
2652
+ const hasGit = await commandExists("git");
2653
+ const hasMakepkg = await commandExists("makepkg");
2654
+ const hasBaseDevel = await exec(["pacman", "-Qq", "base-devel"]);
2655
+ if (!hasGit) {
2656
+ log("Installing git...");
2657
+ const gitResult = await exec(["sudo", "pacman", "-S", "--noconfirm", "git"]);
2658
+ if (!gitResult.success) {
2659
+ log("Failed to install git");
2660
+ return false;
2661
+ }
2662
+ }
2663
+ if (!hasBaseDevel.success) {
2664
+ log("Installing base-devel...");
2665
+ const baseResult = await exec(["sudo", "pacman", "-S", "--noconfirm", "base-devel"]);
2666
+ if (!baseResult.success) {
2667
+ log("Failed to install base-devel");
2668
+ return false;
2669
+ }
2670
+ }
2671
+ const tmpDir = "/tmp/yay-install";
2672
+ await exec(["rm", "-rf", tmpDir]);
2673
+ log("Cloning yay from AUR...");
2674
+ const cloneResult = await exec(["git", "clone", "https://aur.archlinux.org/yay.git", tmpDir]);
2675
+ if (!cloneResult.success) {
2676
+ log(`Failed to clone yay: ${cloneResult.stderr}`);
2677
+ return false;
2678
+ }
2679
+ log("Building and installing yay...");
2680
+ const buildExitCode = await execLive(["makepkg", "-si", "--noconfirm"], tmpDir);
2681
+ if (buildExitCode !== 0) {
2682
+ log("Failed to build yay");
2683
+ return false;
2684
+ }
2685
+ await exec(["rm", "-rf", tmpDir]);
2686
+ this.helper = "yay";
2687
+ log("yay installed successfully!");
2688
+ return true;
2689
+ }
2690
+ async update(callbacks) {
2691
+ const helper = await this.getHelper();
2692
+ if (helper === "none")
2693
+ return false;
2694
+ return runAurCommand(helper, ["-Sy"], callbacks);
2695
+ }
2696
+ async install(packages, callbacks) {
2697
+ if (packages.length === 0)
2698
+ return true;
2699
+ const helper = await this.getHelper();
2700
+ if (helper === "none") {
2701
+ const installed = await this.ensureAurHelper(callbacks);
2702
+ if (!installed)
2703
+ return false;
2704
+ }
2705
+ const currentHelper = await this.getHelper();
2706
+ return runAurCommand(currentHelper, ["-S", "--noconfirm", "--needed", ...packages], callbacks);
2707
+ }
2708
+ async uninstall(packages, callbacks) {
2709
+ if (packages.length === 0)
2710
+ return true;
2711
+ const helper = await this.getHelper();
2712
+ if (helper === "none")
2713
+ return false;
2714
+ return runAurCommand(helper, ["-Rs", "--noconfirm", ...packages], callbacks);
2715
+ }
2716
+ async upgrade(packages, callbacks) {
2717
+ const helper = await this.getHelper();
2718
+ if (helper === "none")
2719
+ return false;
2720
+ if (packages && packages.length > 0) {
2721
+ return runAurCommand(helper, ["-S", "--noconfirm", ...packages], callbacks);
2722
+ }
2723
+ return runAurCommand(helper, ["-Syu", "--noconfirm"], callbacks);
2724
+ }
2725
+ async listInstalled() {
2726
+ const result = await exec(["pacman", "-Qm"]);
2727
+ if (!result.success)
2728
+ return [];
2729
+ return result.stdout.split(`
2730
+ `).filter(Boolean).map((line) => {
2731
+ const [name, version] = line.split(" ");
2732
+ return { name, version: version || "unknown" };
2733
+ });
2734
+ }
2735
+ async listOutdated() {
2736
+ const helper = await this.getHelper();
2737
+ if (helper === "none")
2738
+ return [];
2739
+ const result = await exec([helper, "-Qua"]);
2740
+ if (!result.success || !result.stdout)
2741
+ return [];
2742
+ return result.stdout.split(`
2743
+ `).filter(Boolean).map((line) => {
2744
+ const match = line.match(/^(\S+)\s+(\S+)\s+->\s+(\S+)$/);
2745
+ if (match) {
2746
+ return {
2747
+ name: match[1],
2748
+ currentVersion: match[2],
2749
+ newVersion: match[3]
2750
+ };
2751
+ }
2752
+ return { name: line.split(" ")[0], currentVersion: "unknown", newVersion: "unknown" };
2753
+ });
2754
+ }
2755
+ async cleanup(callbacks) {
2756
+ const helper = await this.getHelper();
2757
+ if (helper === "none")
2758
+ return true;
2759
+ return runAurCommand(helper, ["-Sc", "--noconfirm"], callbacks);
2760
+ }
2761
+ async isInstalled(packages) {
2762
+ const result = new Map;
2763
+ const listResult = await exec(["pacman", "-Qmq"]);
2764
+ const installed = new Set(listResult.stdout.split(`
2765
+ `).filter(Boolean));
2766
+ for (const pkg of packages) {
2767
+ result.set(pkg, installed.has(pkg));
2768
+ }
2769
+ return result;
2770
+ }
2771
+ getHelperName() {
2772
+ return this.getHelper();
2773
+ }
2774
+ }
2775
+
2776
+ // src/lib/package-managers/apt.ts
2777
+ init_runtime();
2778
+ init_runtime();
2779
+ async function runAptCommand(args, callbacks, sudo = false) {
2780
+ const cmd = sudo ? ["sudo", "apt-get", ...args] : ["apt-get", ...args];
2781
+ if (callbacks?.onLog) {
2782
+ const exitCode = await execStreaming(cmd, callbacks.onLog);
2783
+ return exitCode === 0;
2784
+ }
2785
+ const result = await exec(cmd);
2786
+ return result.success;
2787
+ }
2788
+
2789
+ class Apt {
2790
+ type = "apt";
2791
+ displayName = "APT";
2792
+ async isAvailable() {
2793
+ return commandExists("apt-get");
2794
+ }
2795
+ async update(callbacks) {
2796
+ return runAptCommand(["update", "-y"], callbacks, true);
2797
+ }
2798
+ async install(packages, callbacks) {
2799
+ if (packages.length === 0)
2800
+ return true;
2801
+ return runAptCommand(["install", "-y", ...packages], callbacks, true);
2802
+ }
2803
+ async uninstall(packages, callbacks) {
2804
+ if (packages.length === 0)
2805
+ return true;
2806
+ return runAptCommand(["remove", "-y", ...packages], callbacks, true);
2807
+ }
2808
+ async upgrade(packages, callbacks) {
2809
+ if (packages && packages.length > 0) {
2810
+ return runAptCommand(["install", "-y", "--only-upgrade", ...packages], callbacks, true);
2811
+ }
2812
+ await runAptCommand(["update", "-y"], callbacks, true);
2813
+ return runAptCommand(["upgrade", "-y"], callbacks, true);
2814
+ }
2815
+ async listInstalled() {
2816
+ const result = await exec(["dpkg-query", "-W", "-f=${Package} ${Version}\n"]);
2817
+ if (!result.success)
2818
+ return [];
2819
+ return result.stdout.split(`
2820
+ `).filter(Boolean).map((line) => {
2821
+ const [name, version] = line.split(" ");
2822
+ return { name, version: version || "unknown" };
2823
+ });
2824
+ }
2825
+ async listOutdated() {
2826
+ await exec(["sudo", "apt-get", "update", "-y"]);
2827
+ const result = await exec(["apt", "list", "--upgradable"]);
2828
+ if (!result.success)
2829
+ return [];
2830
+ return result.stdout.split(`
2831
+ `).filter((line) => line.includes("[upgradable")).map((line) => {
2832
+ const match = line.match(/^(\S+)\/\S+\s+(\S+)\s+\S+\s+\[upgradable from:\s+(\S+)\]/);
2833
+ if (match) {
2834
+ return {
2835
+ name: match[1],
2836
+ currentVersion: match[3],
2837
+ newVersion: match[2]
2838
+ };
2839
+ }
2840
+ return null;
2841
+ }).filter((pkg) => pkg !== null);
2842
+ }
2843
+ async listLeaves() {
2844
+ const result = await exec(["apt-mark", "showmanual"]);
2845
+ if (!result.success)
2846
+ return [];
2847
+ return result.stdout.split(`
2848
+ `).filter(Boolean);
2849
+ }
2850
+ async cleanup(callbacks) {
2851
+ const autoremove = await runAptCommand(["autoremove", "-y"], callbacks, true);
2852
+ const autoclean = await runAptCommand(["autoclean"], callbacks, true);
2853
+ return autoremove && autoclean;
2854
+ }
2855
+ async addRepository(repo, callbacks) {
2856
+ const cmd = ["sudo", "add-apt-repository", "-y", repo];
2857
+ if (callbacks?.onLog) {
2858
+ const exitCode = await execStreaming(cmd, callbacks.onLog);
2859
+ if (exitCode !== 0)
2860
+ return false;
2861
+ } else {
2862
+ const result = await exec(cmd);
2863
+ if (!result.success)
2864
+ return false;
2865
+ }
2866
+ return runAptCommand(["update", "-y"], callbacks, true);
2867
+ }
2868
+ async isInstalled(packages) {
2869
+ const result = new Map;
2870
+ for (const pkg of packages) {
2871
+ const checkResult = await exec(["dpkg", "-s", pkg]);
2872
+ result.set(pkg, checkResult.success && checkResult.stdout.includes("Status: install ok installed"));
2873
+ }
2874
+ return result;
2875
+ }
2876
+ }
2877
+
2878
+ // src/lib/package-managers/dnf.ts
2879
+ init_runtime();
2880
+ init_runtime();
2881
+ async function runDnfCommand(args, callbacks, sudo = false) {
2882
+ const cmd = sudo ? ["sudo", "dnf", ...args] : ["dnf", ...args];
2883
+ if (callbacks?.onLog) {
2884
+ const exitCode = await execStreaming(cmd, callbacks.onLog);
2885
+ return exitCode === 0;
2886
+ }
2887
+ const result = await exec(cmd);
2888
+ return result.success;
2889
+ }
2890
+
2891
+ class Dnf {
2892
+ type = "dnf";
2893
+ displayName = "DNF";
2894
+ async isAvailable() {
2895
+ return commandExists("dnf");
2896
+ }
2897
+ async update(callbacks) {
2898
+ return runDnfCommand(["check-update", "-y"], callbacks, true);
2899
+ }
2900
+ async install(packages, callbacks) {
2901
+ if (packages.length === 0)
2902
+ return true;
2903
+ return runDnfCommand(["install", "-y", ...packages], callbacks, true);
2904
+ }
2905
+ async uninstall(packages, callbacks) {
2906
+ if (packages.length === 0)
2907
+ return true;
2908
+ return runDnfCommand(["remove", "-y", ...packages], callbacks, true);
2909
+ }
2910
+ async upgrade(packages, callbacks) {
2911
+ if (packages && packages.length > 0) {
2912
+ return runDnfCommand(["upgrade", "-y", ...packages], callbacks, true);
2913
+ }
2914
+ return runDnfCommand(["upgrade", "-y"], callbacks, true);
2915
+ }
2916
+ async listInstalled() {
2917
+ const result = await exec(["dnf", "list", "installed", "-q"]);
2918
+ if (!result.success)
2919
+ return [];
2920
+ return result.stdout.split(`
2921
+ `).filter(Boolean).slice(1).map((line) => {
2922
+ const parts = line.trim().split(/\s+/);
2923
+ if (parts.length >= 2) {
2924
+ const fullName = parts[0];
2925
+ const name = fullName.replace(/\.\w+$/, "");
2926
+ return { name, version: parts[1] };
2927
+ }
2928
+ return null;
2929
+ }).filter((pkg) => pkg !== null);
2930
+ }
2931
+ async listOutdated() {
2932
+ const result = await exec(["dnf", "check-update", "-q"]);
2933
+ if (!result.stdout)
2934
+ return [];
2935
+ return result.stdout.split(`
2936
+ `).filter((line) => line.trim() && !line.startsWith("Last metadata")).map((line) => {
2937
+ const parts = line.trim().split(/\s+/);
2938
+ if (parts.length >= 2) {
2939
+ const fullName = parts[0];
2940
+ const name = fullName.replace(/\.\w+$/, "");
2941
+ return {
2942
+ name,
2943
+ currentVersion: "installed",
2944
+ newVersion: parts[1]
2945
+ };
2946
+ }
2947
+ return null;
2948
+ }).filter((pkg) => pkg !== null);
2949
+ }
2950
+ async listLeaves() {
2951
+ const hasLeaves = await commandExists("dnf-leaves");
2952
+ if (hasLeaves) {
2953
+ const result2 = await exec(["dnf", "leaves"]);
2954
+ if (result2.success) {
2955
+ return result2.stdout.split(`
2956
+ `).filter(Boolean);
2957
+ }
2958
+ }
2959
+ const result = await exec(["dnf", "repoquery", "--userinstalled", "-q"]);
2960
+ if (!result.success)
2961
+ return [];
2962
+ return result.stdout.split(`
2963
+ `).filter(Boolean).map((line) => line.replace(/\.\w+$/, ""));
2964
+ }
2965
+ async cleanup(callbacks) {
2966
+ const autoremove = await runDnfCommand(["autoremove", "-y"], callbacks, true);
2967
+ const clean = await runDnfCommand(["clean", "all"], callbacks, true);
2968
+ return autoremove && clean;
2969
+ }
2970
+ async addRepository(repo, callbacks) {
2971
+ if (repo.startsWith("copr:")) {
2972
+ const coprRepo = repo.replace("copr:", "");
2973
+ return runDnfCommand(["copr", "enable", "-y", coprRepo], callbacks, true);
2974
+ }
2975
+ return runDnfCommand(["config-manager", "--add-repo", repo], callbacks, true);
2976
+ }
2977
+ async isInstalled(packages) {
2978
+ const result = new Map;
2979
+ for (const pkg of packages) {
2980
+ const checkResult = await exec(["rpm", "-q", pkg]);
2981
+ result.set(pkg, checkResult.success);
2982
+ }
2983
+ return result;
2984
+ }
2985
+ }
2986
+
2987
+ // src/lib/package-managers/flatpak.ts
2988
+ init_runtime();
2989
+ init_runtime();
2990
+ async function runFlatpakCommand(args, callbacks) {
2991
+ const cmd = ["flatpak", ...args];
2992
+ if (callbacks?.onLog) {
2993
+ const exitCode = await execStreaming(cmd, callbacks.onLog);
2994
+ return exitCode === 0;
2995
+ }
2996
+ const result = await exec(cmd);
2997
+ return result.success;
2998
+ }
2999
+
3000
+ class Flatpak {
3001
+ type = "flatpak";
3002
+ displayName = "Flatpak";
3003
+ async isAvailable() {
3004
+ return commandExists("flatpak");
3005
+ }
3006
+ async update(callbacks) {
3007
+ return true;
3008
+ }
3009
+ async install(packages, callbacks) {
3010
+ if (packages.length === 0)
3011
+ return true;
3012
+ for (const appId of packages) {
3013
+ const success = await runFlatpakCommand(["install", "-y", "--noninteractive", "flathub", appId], callbacks);
3014
+ if (!success)
3015
+ return false;
3016
+ }
3017
+ return true;
3018
+ }
3019
+ async uninstall(packages, callbacks) {
3020
+ if (packages.length === 0)
3021
+ return true;
3022
+ for (const appId of packages) {
3023
+ const success = await runFlatpakCommand(["uninstall", "-y", "--noninteractive", appId], callbacks);
3024
+ if (!success)
3025
+ return false;
3026
+ }
3027
+ return true;
3028
+ }
3029
+ async upgrade(packages, callbacks) {
3030
+ if (packages && packages.length > 0) {
3031
+ for (const appId of packages) {
3032
+ const success = await runFlatpakCommand(["update", "-y", "--noninteractive", appId], callbacks);
3033
+ if (!success)
3034
+ return false;
3035
+ }
3036
+ return true;
3037
+ }
3038
+ return runFlatpakCommand(["update", "-y", "--noninteractive"], callbacks);
3039
+ }
3040
+ async listInstalled() {
3041
+ const result = await exec(["flatpak", "list", "--app", "--columns=application,version"]);
3042
+ if (!result.success)
3043
+ return [];
3044
+ return result.stdout.split(`
3045
+ `).filter(Boolean).map((line) => {
3046
+ const [name, version] = line.split("\t");
3047
+ return { name: name.trim(), version: version?.trim() || "unknown" };
3048
+ });
3049
+ }
3050
+ async listOutdated() {
3051
+ const result = await exec(["flatpak", "remote-ls", "--updates", "--columns=application,version"]);
3052
+ if (!result.success)
3053
+ return [];
3054
+ return result.stdout.split(`
3055
+ `).filter(Boolean).map((line) => {
3056
+ const [name, newVersion] = line.split("\t");
3057
+ return {
3058
+ name: name.trim(),
3059
+ currentVersion: "installed",
3060
+ newVersion: newVersion?.trim() || "unknown"
3061
+ };
3062
+ });
3063
+ }
3064
+ async cleanup(callbacks) {
3065
+ return runFlatpakCommand(["uninstall", "-y", "--unused", "--noninteractive"], callbacks);
3066
+ }
3067
+ async addRepository(repo, callbacks) {
3068
+ if (repo === "flathub") {
3069
+ return runFlatpakCommand(["remote-add", "--if-not-exists", "flathub", "https://flathub.org/repo/flathub.flatpakrepo"], callbacks);
3070
+ }
3071
+ const [name, url] = repo.split(" ");
3072
+ if (!url)
3073
+ return false;
3074
+ return runFlatpakCommand(["remote-add", "--if-not-exists", name, url], callbacks);
3075
+ }
3076
+ async isInstalled(packages) {
3077
+ const result = new Map;
3078
+ const listResult = await exec(["flatpak", "list", "--app", "--columns=application"]);
3079
+ const installed = new Set(listResult.stdout.split(`
3080
+ `).filter(Boolean).map((l) => l.trim()));
3081
+ for (const appId of packages) {
3082
+ result.set(appId, installed.has(appId));
3083
+ }
3084
+ return result;
3085
+ }
3086
+ async ensureFlathub(callbacks) {
3087
+ const result = await exec(["flatpak", "remotes"]);
3088
+ if (result.success && result.stdout.includes("flathub")) {
3089
+ return true;
3090
+ }
3091
+ return this.addRepository("flathub", callbacks);
3092
+ }
3093
+ }
3094
+
3095
+ // src/lib/package-managers/index.ts
3096
+ var managerInstances = new Map;
3097
+ function getPackageManager(type) {
3098
+ const existing = managerInstances.get(type);
3099
+ if (existing)
3100
+ return existing;
3101
+ let manager;
3102
+ switch (type) {
3103
+ case "homebrew":
3104
+ manager = new HomebrewFormulas;
3105
+ break;
3106
+ case "homebrew-casks":
3107
+ manager = new HomebrewCasks;
3108
+ break;
3109
+ case "mas":
3110
+ manager = new MacAppStore;
3111
+ break;
3112
+ case "pacman":
3113
+ manager = new Pacman;
3114
+ break;
3115
+ case "aur":
3116
+ manager = new AurPackageManager;
3117
+ break;
3118
+ case "apt":
3119
+ manager = new Apt;
3120
+ break;
3121
+ case "dnf":
3122
+ manager = new Dnf;
3123
+ break;
3124
+ case "flatpak":
3125
+ manager = new Flatpak;
3126
+ break;
3127
+ default:
3128
+ throw new Error(`Unknown package manager type: ${type}`);
3129
+ }
3130
+ managerInstances.set(type, manager);
3131
+ return manager;
3132
+ }
3133
+ async function getAvailableManagers() {
3134
+ const platformInfo = await getPlatformInfo();
3135
+ const managers = [];
3136
+ for (const type of platformInfo.availableManagers) {
3137
+ const manager = getPackageManager(type);
3138
+ if (await manager.isAvailable()) {
3139
+ managers.push(manager);
3140
+ if (type === "homebrew") {
3141
+ const casks = getPackageManager("homebrew-casks");
3142
+ if (await casks.isAvailable()) {
3143
+ managers.push(casks);
3144
+ }
3145
+ }
3146
+ }
3147
+ }
3148
+ return managers;
3149
+ }
3150
+
3151
+ // src/lib/lockfile.ts
3152
+ async function fetchInstalledVersionsV2() {
3153
+ const platform = await getPlatformInfo();
3154
+ const config = await loadPkgConfig();
3155
+ const now = new Date().toISOString();
3156
+ const packages = {};
3157
+ const managers = await getAvailableManagers();
3158
+ for (const manager of managers) {
3159
+ const installed = await manager.listInstalled();
3160
+ for (const pkg of installed) {
3161
+ const key = `${manager.type}:${pkg.name}`;
3162
+ packages[key] = {
3163
+ version: pkg.version,
3164
+ installedAt: pkg.installedAt || now,
3165
+ manager: manager.type
3166
+ };
3167
+ }
3168
+ }
3169
+ if (platform.os === "darwin") {
3170
+ const formulaPackages = config.macos?.formulas || [];
3171
+ const globalPackages = config.global?.packages || [];
3172
+ const allFormulas = [...globalPackages, ...formulaPackages];
3173
+ if (allFormulas.length > 0) {
3174
+ const result = await exec([
3175
+ "brew",
3176
+ "info",
3177
+ "--json=v2",
3178
+ ...allFormulas
3179
+ ]);
3180
+ if (result.success && result.stdout) {
3181
+ try {
3182
+ const info = JSON.parse(result.stdout);
3183
+ for (const formula of info.formulae) {
3184
+ if (formula.installed.length > 0) {
3185
+ const key = `homebrew:${formula.name}`;
3186
+ if (packages[key]) {
3187
+ packages[key].tap = formula.tap;
3188
+ }
3189
+ }
3190
+ }
3191
+ } catch {}
3192
+ }
3193
+ }
3194
+ }
3195
+ return packages;
3196
+ }
3197
+ async function updateLockfile() {
3198
+ const existing = await loadPkgLock();
3199
+ const packages = await fetchInstalledVersionsV2();
3200
+ const mergedPackages = {};
3201
+ for (const [key, info] of Object.entries(packages)) {
3202
+ if (existing?.version === 2) {
3203
+ const prev = existing.packages[key];
3204
+ if (prev && prev.version === info.version) {
3205
+ mergedPackages[key] = prev;
3206
+ } else {
3207
+ mergedPackages[key] = info;
3208
+ }
3209
+ } else if (existing?.version === 1) {
3210
+ const [manager, name] = key.split(":");
3211
+ let prev;
3212
+ if (manager === "homebrew") {
3213
+ prev = existing.formulas[name];
3214
+ } else if (manager === "homebrew-casks") {
3215
+ prev = existing.casks[name];
3216
+ }
3217
+ if (prev && prev.version === info.version) {
3218
+ mergedPackages[key] = {
3219
+ ...info,
3220
+ installedAt: prev.installedAt
3221
+ };
3222
+ } else {
3223
+ mergedPackages[key] = info;
3224
+ }
3225
+ } else {
3226
+ mergedPackages[key] = info;
3227
+ }
3228
+ }
3229
+ const lock = {
3230
+ version: 2,
3231
+ lastUpdated: new Date().toISOString(),
3232
+ packages: mergedPackages
3233
+ };
3234
+ await savePkgLock(lock);
3235
+ return lock;
3236
+ }
3237
+ async function fetchInstalledVersions() {
3238
+ const config = await loadPkgConfig();
3239
+ const now = new Date().toISOString();
3240
+ const formulas = {};
3241
+ const casks = {};
3242
+ const allFormulas = [
3243
+ ...config.global?.packages || [],
3244
+ ...config.macos?.formulas || []
3245
+ ];
3246
+ if (allFormulas.length > 0) {
3247
+ const result = await exec([
3248
+ "brew",
3249
+ "info",
3250
+ "--json=v2",
3251
+ ...allFormulas
3252
+ ]);
3253
+ if (result.success && result.stdout) {
3254
+ const info = JSON.parse(result.stdout);
3255
+ for (const formula of info.formulae) {
3256
+ if (formula.installed.length > 0) {
3257
+ formulas[formula.name] = {
3258
+ version: formula.installed[0].version,
3259
+ tap: formula.tap,
3260
+ installedAt: now
3261
+ };
3262
+ }
3263
+ }
3264
+ }
3265
+ }
3266
+ const allCasks = config.macos?.casks || [];
3267
+ if (allCasks.length > 0) {
3268
+ const result = await exec([
3269
+ "brew",
3270
+ "info",
3271
+ "--json=v2",
3272
+ "--cask",
3273
+ ...allCasks
3274
+ ]);
3275
+ if (result.success && result.stdout) {
3276
+ const info = JSON.parse(result.stdout);
3277
+ for (const cask of info.casks) {
3278
+ if (cask.installed) {
3279
+ casks[cask.token] = {
3280
+ version: cask.installed,
3281
+ installedAt: now
3282
+ };
3283
+ }
3284
+ }
3285
+ }
3286
+ }
3287
+ return { formulas, casks };
3288
+ }
3289
+ async function generateLockfile() {
3290
+ const { formulas, casks } = await fetchInstalledVersions();
3291
+ return {
3292
+ version: 1,
3293
+ lastUpdated: new Date().toISOString(),
3294
+ formulas,
3295
+ casks
3296
+ };
3297
+ }
3298
+ async function getChangedPackages() {
3299
+ const existing = await loadPkgLock();
3300
+ const packages = await fetchInstalledVersionsV2();
3301
+ const added = [];
3302
+ const removed = [];
3303
+ const upgraded = [];
3304
+ if (!existing) {
3305
+ added.push(...Object.keys(packages));
3306
+ return { added, removed, upgraded };
3307
+ }
3308
+ if (existing.version === 2) {
3309
+ for (const [key, info] of Object.entries(packages)) {
3310
+ if (!existing.packages[key]) {
3311
+ added.push(key);
3312
+ } else if (existing.packages[key].version !== info.version) {
3313
+ upgraded.push({
3314
+ name: key,
3315
+ from: existing.packages[key].version,
3316
+ to: info.version
3317
+ });
3318
+ }
2187
3319
  }
2188
- return null;
2189
- }).filter((app) => app !== null);
3320
+ for (const key of Object.keys(existing.packages)) {
3321
+ if (!packages[key]) {
3322
+ removed.push(key);
3323
+ }
3324
+ }
3325
+ } else {
3326
+ const { formulas, casks } = await fetchInstalledVersions();
3327
+ for (const [name, info] of Object.entries(formulas)) {
3328
+ if (!existing.formulas[name]) {
3329
+ added.push(name);
3330
+ } else if (existing.formulas[name].version !== info.version) {
3331
+ upgraded.push({
3332
+ name,
3333
+ from: existing.formulas[name].version,
3334
+ to: info.version
3335
+ });
3336
+ }
3337
+ }
3338
+ for (const name of Object.keys(existing.formulas)) {
3339
+ if (!formulas[name]) {
3340
+ removed.push(name);
3341
+ }
3342
+ }
3343
+ for (const [name, info] of Object.entries(casks)) {
3344
+ if (!existing.casks[name]) {
3345
+ added.push(name);
3346
+ } else if (existing.casks[name].version !== info.version) {
3347
+ upgraded.push({
3348
+ name,
3349
+ from: existing.casks[name].version,
3350
+ to: info.version
3351
+ });
3352
+ }
3353
+ }
3354
+ for (const name of Object.keys(existing.casks)) {
3355
+ if (!casks[name]) {
3356
+ removed.push(name);
3357
+ }
3358
+ }
3359
+ }
3360
+ return { added, removed, upgraded };
3361
+ }
3362
+
3363
+ // src/types/pkg-config.ts
3364
+ var SYSTEM_APP_IDS = [
3365
+ 409183694,
3366
+ 409203825,
3367
+ 409201541,
3368
+ 408981434,
3369
+ 682658836,
3370
+ 424389933,
3371
+ 424390742,
3372
+ 413897608,
3373
+ 1274495053,
3374
+ 425424353,
3375
+ 497799835,
3376
+ 634148309,
3377
+ 1480068668,
3378
+ 803453959,
3379
+ 1295203466,
3380
+ 1444383602,
3381
+ 640199958,
3382
+ 899247664,
3383
+ 1176895641,
3384
+ 1451685025
3385
+ ];
3386
+
3387
+ // src/cli/pkg-sync.ts
3388
+ var colors3 = {
3389
+ red: "\x1B[0;31m",
3390
+ green: "\x1B[0;32m",
3391
+ blue: "\x1B[0;34m",
3392
+ yellow: "\x1B[1;33m",
3393
+ cyan: "\x1B[0;36m",
3394
+ bold: "\x1B[1m",
3395
+ reset: "\x1B[0m"
3396
+ };
3397
+ async function getPackageSetsForPlatform(config, platform) {
3398
+ const sets = [];
3399
+ if (platform.os === "darwin") {
3400
+ const formulas = getPackageManager("homebrew");
3401
+ const casks = getPackageManager("homebrew-casks");
3402
+ const mas = getPackageManager("mas");
3403
+ const globalPkgs = config.global?.packages || [];
3404
+ const macosFormulas = config.macos?.formulas || [];
3405
+ const allFormulas = [...globalPkgs, ...macosFormulas];
3406
+ if (allFormulas.length > 0 || (config.macos?.taps || []).length > 0) {
3407
+ sets.push({
3408
+ manager: formulas,
3409
+ packages: allFormulas,
3410
+ repositories: config.macos?.taps
3411
+ });
3412
+ }
3413
+ const macosCasks = config.macos?.casks || [];
3414
+ if (macosCasks.length > 0) {
3415
+ sets.push({
3416
+ manager: casks,
3417
+ packages: macosCasks
3418
+ });
3419
+ }
3420
+ const masMappings = config.macos?.mas || {};
3421
+ const masIds = Object.values(masMappings).map(String);
3422
+ if (masIds.length > 0 && await mas.isAvailable()) {
3423
+ sets.push({
3424
+ manager: mas,
3425
+ packages: masIds
3426
+ });
3427
+ }
3428
+ } else {
3429
+ const distro = platform.distro;
3430
+ const globalPkgs = config.global?.packages || [];
3431
+ const linuxPkgs = config.linux?.packages || [];
3432
+ if (distro === "arch") {
3433
+ const pacman = getPackageManager("pacman");
3434
+ const aur = getPackageManager("aur");
3435
+ const archPkgs = config.arch?.packages || [];
3436
+ const allPacmanPkgs = [...globalPkgs, ...linuxPkgs, ...archPkgs];
3437
+ if (allPacmanPkgs.length > 0) {
3438
+ sets.push({
3439
+ manager: pacman,
3440
+ packages: allPacmanPkgs
3441
+ });
3442
+ }
3443
+ const aurPkgs = config.arch?.aur || [];
3444
+ if (aurPkgs.length > 0 && await aur.isAvailable()) {
3445
+ sets.push({
3446
+ manager: aur,
3447
+ packages: aurPkgs
3448
+ });
3449
+ }
3450
+ } else if (distro === "debian" || distro === "ubuntu") {
3451
+ const apt = getPackageManager("apt");
3452
+ const debianPkgs = config.debian?.packages || [];
3453
+ const allAptPkgs = [...globalPkgs, ...linuxPkgs, ...debianPkgs];
3454
+ if (allAptPkgs.length > 0 || (config.debian?.ppas || []).length > 0) {
3455
+ sets.push({
3456
+ manager: apt,
3457
+ packages: allAptPkgs,
3458
+ repositories: config.debian?.ppas
3459
+ });
3460
+ }
3461
+ } else if (distro === "fedora" || distro === "rhel") {
3462
+ const dnf = getPackageManager("dnf");
3463
+ const fedoraPkgs = config.fedora?.packages || [];
3464
+ const allDnfPkgs = [...globalPkgs, ...linuxPkgs, ...fedoraPkgs];
3465
+ if (allDnfPkgs.length > 0 || (config.fedora?.copr || []).length > 0) {
3466
+ sets.push({
3467
+ manager: dnf,
3468
+ packages: allDnfPkgs,
3469
+ repositories: config.fedora?.copr?.map((r) => `copr:${r}`)
3470
+ });
3471
+ }
3472
+ } else {
3473
+ const managers = await getAvailableManagers();
3474
+ const primaryManager = managers.find((m) => m.type === "pacman" || m.type === "apt" || m.type === "dnf");
3475
+ if (primaryManager) {
3476
+ const allPkgs = [...globalPkgs, ...linuxPkgs];
3477
+ if (allPkgs.length > 0) {
3478
+ sets.push({
3479
+ manager: primaryManager,
3480
+ packages: allPkgs
3481
+ });
3482
+ }
3483
+ }
3484
+ }
3485
+ const flatpak = getPackageManager("flatpak");
3486
+ const flatpakApps = config.linux?.flatpak || [];
3487
+ if (flatpakApps.length > 0 && await flatpak.isAvailable()) {
3488
+ sets.push({
3489
+ manager: flatpak,
3490
+ packages: flatpakApps
3491
+ });
3492
+ }
3493
+ }
3494
+ return sets;
3495
+ }
3496
+ async function checkDependencies(platform) {
3497
+ if (platform.os === "darwin") {
3498
+ if (!await commandExists("brew")) {
3499
+ console.error(`${colors3.red}Error: Homebrew not installed${colors3.reset}`);
3500
+ process.exit(1);
3501
+ }
3502
+ } else {
3503
+ const hasPackageManager = await Promise.all([
3504
+ commandExists("pacman"),
3505
+ commandExists("apt"),
3506
+ commandExists("dnf")
3507
+ ]).then((results) => results.some(Boolean));
3508
+ if (!hasPackageManager) {
3509
+ console.error(`${colors3.red}Error: No supported package manager found (pacman, apt, or dnf)${colors3.reset}`);
3510
+ process.exit(1);
3511
+ }
3512
+ }
2190
3513
  }
2191
3514
  async function upgradeWithVerification(cb = null) {
2192
3515
  const log = cb?.onLog ?? console.log;
3516
+ const platform = await getPlatformInfo();
2193
3517
  const result = {
2194
3518
  attempted: [],
2195
3519
  succeeded: [],
2196
3520
  failed: [],
2197
3521
  stillOutdated: []
2198
3522
  };
3523
+ const callbacks = cb ? { onLog: cb.onLog } : undefined;
2199
3524
  log(`
2200
3525
  ${colors3.cyan}=== Checking for updates ===${colors3.reset}
2201
3526
  `);
2202
- await runCommand(["brew", "update"], cb);
2203
- const beforeUpgrade = await getOutdatedPackages();
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);
3527
+ const managers = await getAvailableManagers();
3528
+ for (const manager of managers) {
2215
3529
  log(`
2216
- ${colors3.cyan}=== Upgrading casks ===${colors3.reset}
3530
+ ${colors3.cyan}--- ${manager.displayName} ---${colors3.reset}
2217
3531
  `);
2218
- await runCommand(["brew", "upgrade", "--cask", "--greedy"], cb);
2219
- log(`
2220
- ${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
3532
+ await manager.update(callbacks);
3533
+ const outdated = await manager.listOutdated();
3534
+ if (outdated.length === 0) {
3535
+ log(`${colors3.green}All packages are up to date${colors3.reset}`);
3536
+ continue;
3537
+ }
3538
+ log(`${colors3.yellow}Found ${outdated.length} outdated packages${colors3.reset}
2221
3539
  `);
2222
- const afterUpgrade = await getOutdatedPackages();
2223
- const stillOutdatedSet = new Set(afterUpgrade.map((p) => p.name));
2224
- for (const pkg of beforeUpgrade) {
3540
+ result.attempted.push(...outdated.map((p) => p.name));
3541
+ await manager.upgrade(undefined, callbacks);
3542
+ const stillOutdated = await manager.listOutdated();
3543
+ const stillOutdatedSet = new Set(stillOutdated.map((p) => p.name));
3544
+ for (const pkg of outdated) {
2225
3545
  if (stillOutdatedSet.has(pkg.name)) {
2226
3546
  result.stillOutdated.push(pkg.name);
2227
3547
  } else {
@@ -2232,93 +3552,70 @@ ${colors3.cyan}=== Verifying upgrades ===${colors3.reset}
2232
3552
  log(`${colors3.yellow}${result.stillOutdated.length} packages still outdated, retrying individually...${colors3.reset}
2233
3553
  `);
2234
3554
  for (const pkgName of [...result.stillOutdated]) {
2235
- const pkg = afterUpgrade.find((p) => p.name === pkgName);
2236
- if (!pkg)
2237
- continue;
2238
3555
  log(` Retrying ${colors3.blue}${pkgName}${colors3.reset}...`);
2239
- const upgradeCmd = pkg.type === "cask" ? ["brew", "upgrade", "--cask", pkgName] : ["brew", "upgrade", pkgName];
2240
- const retryResult = await exec(upgradeCmd);
2241
- const checkResult = await exec([
2242
- "brew",
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)) {
3556
+ const upgradeSuccess = await manager.upgrade([pkgName], callbacks);
3557
+ const checkOutdated = await manager.listOutdated();
3558
+ const stillFailing = checkOutdated.some((p) => p.name === pkgName);
3559
+ if (!stillFailing) {
2250
3560
  result.succeeded.push(pkgName);
2251
3561
  result.stillOutdated = result.stillOutdated.filter((n) => n !== pkgName);
2252
3562
  log(` ${colors3.green}✓ Success${colors3.reset}`);
2253
3563
  } else {
2254
3564
  result.failed.push(pkgName);
2255
3565
  result.stillOutdated = result.stillOutdated.filter((n) => n !== pkgName);
2256
- log(` ${colors3.red}✗ Failed${colors3.reset} ${retryResult.stderr ? `(${retryResult.stderr.split(`
2257
- `)[0]})` : ""}`);
3566
+ log(` ${colors3.red}✗ Failed${colors3.reset}`);
2258
3567
  }
2259
3568
  }
2260
3569
  }
2261
3570
  }
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
3571
  log(`
2272
3572
  ${colors3.cyan}=== Cleanup ===${colors3.reset}
2273
3573
  `);
2274
- await runCommand(["brew", "cleanup"], cb);
3574
+ for (const manager of managers) {
3575
+ await manager.cleanup(callbacks);
3576
+ }
2275
3577
  log(`
2276
3578
  ${colors3.cyan}=== Updating lockfile ===${colors3.reset}
2277
3579
  `);
2278
3580
  const lock = await updateLockfile();
2279
- const lockTotal = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
3581
+ const lockTotal = lock.version === 2 ? Object.keys(lock.packages).length : Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
2280
3582
  log(` Locked ${lockTotal} packages`);
2281
3583
  return result;
2282
3584
  }
2283
3585
  async function upgradeInteractive(cb = null) {
2284
3586
  const log = cb?.onLog ?? console.log;
2285
3587
  const askPrompt = cb?.onPrompt ?? (async (q) => (prompt(q) || "").trim().toLowerCase());
2286
- log(`
2287
- ${colors3.cyan}=== Checking for updates ===${colors3.reset}
2288
- `);
2289
- await runCommand(["brew", "update"], cb);
2290
- const outdated = await getOutdatedPackages();
2291
- if (outdated.length === 0) {
3588
+ const callbacks = cb ? { onLog: cb.onLog } : undefined;
3589
+ const managers = await getAvailableManagers();
3590
+ for (const manager of managers) {
2292
3591
  log(`
2293
- ${colors3.green}All packages are up to date${colors3.reset}
3592
+ ${colors3.cyan}=== ${manager.displayName} ===${colors3.reset}
2294
3593
  `);
2295
- return;
2296
- }
2297
- log(`
2298
- ${colors3.yellow}Found ${outdated.length} outdated packages${colors3.reset}
3594
+ await manager.update(callbacks);
3595
+ const outdated = await manager.listOutdated();
3596
+ if (outdated.length === 0) {
3597
+ log(`${colors3.green}All packages are up to date${colors3.reset}
2299
3598
  `);
2300
- for (const pkg of outdated) {
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;
3599
+ continue;
2307
3600
  }
2308
- if (answer === "y" || answer === "yes") {
2309
- const cmd = pkg.type === "cask" ? ["brew", "upgrade", "--cask", pkg.name] : ["brew", "upgrade", pkg.name];
2310
- await runCommand(cmd, cb);
3601
+ log(`${colors3.yellow}Found ${outdated.length} outdated packages${colors3.reset}
3602
+ `);
3603
+ for (const pkg of outdated) {
3604
+ const question = `Upgrade ${colors3.blue}${pkg.name}${colors3.reset} (${pkg.currentVersion} -> ${pkg.newVersion})?`;
3605
+ const answer = await askPrompt(question, ["y", "n", "q"]);
3606
+ if (answer === "q") {
3607
+ log(`
3608
+ ${colors3.yellow}Upgrade cancelled${colors3.reset}`);
3609
+ return;
3610
+ }
3611
+ if (answer === "y" || answer === "yes") {
3612
+ await manager.upgrade([pkg.name], callbacks);
3613
+ }
2311
3614
  }
2312
3615
  }
2313
- const stillOutdated = await getOutdatedPackages();
2314
- if (stillOutdated.length > 0) {
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}`);
3616
+ for (const manager of managers) {
3617
+ await manager.cleanup(callbacks);
2320
3618
  }
2321
- await runCommand(["brew", "cleanup"], cb);
2322
3619
  log(`
2323
3620
  ${colors3.cyan}=== Updating lockfile ===${colors3.reset}
2324
3621
  `);
@@ -2326,61 +3623,50 @@ ${colors3.cyan}=== Updating lockfile ===${colors3.reset}
2326
3623
  }
2327
3624
  async function syncPackages(config, cb = null) {
2328
3625
  const log = cb?.onLog ?? console.log;
2329
- if (config.config.autoUpdate) {
2330
- log(`
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
- }
3626
+ const platform = await getPlatformInfo();
3627
+ const callbacks = cb ? { onLog: cb.onLog } : undefined;
2347
3628
  log(`
2348
- ${colors3.cyan}=== Installing packages ===${colors3.reset}
3629
+ ${colors3.cyan}Platform: ${getPlatformDisplayName(platform)}${colors3.reset}
2349
3630
  `);
2350
- const installedFormulas = (await exec(["brew", "list", "--formula"])).stdout.split(`
2351
- `).filter(Boolean);
2352
- for (const pkg of config.packages) {
2353
- if (!installedFormulas.includes(pkg)) {
2354
- log(` Installing: ${colors3.blue}${pkg}${colors3.reset}`);
2355
- await runCommand(["brew", "install", pkg], cb);
2356
- }
3631
+ const packageSets = await getPackageSetsForPlatform(config, platform);
3632
+ if (packageSets.length === 0) {
3633
+ log(`${colors3.yellow}No packages configured for this platform${colors3.reset}`);
3634
+ return;
2357
3635
  }
2358
- log(`
2359
- ${colors3.cyan}=== Installing casks ===${colors3.reset}
3636
+ if (config.config.autoUpdate) {
3637
+ log(`
3638
+ ${colors3.cyan}=== Updating package managers ===${colors3.reset}
2360
3639
  `);
2361
- const installedCasks = (await exec(["brew", "list", "--cask"])).stdout.split(`
2362
- `).filter(Boolean);
2363
- for (const cask of config.casks) {
2364
- if (!installedCasks.includes(cask)) {
2365
- log(` Installing: ${colors3.blue}${cask}${colors3.reset}`);
2366
- await runCommand(["brew", "install", "--cask", cask], cb);
3640
+ for (const set of packageSets) {
3641
+ log(` Updating ${set.manager.displayName}...`);
3642
+ await set.manager.update(callbacks);
2367
3643
  }
2368
3644
  }
2369
- if (await commandExists("mas")) {
3645
+ for (const set of packageSets) {
2370
3646
  log(`
2371
- ${colors3.cyan}=== Installing Mac App Store apps ===${colors3.reset}
3647
+ ${colors3.cyan}=== ${set.manager.displayName} ===${colors3.reset}
2372
3648
  `);
2373
- const masResult = await exec(["mas", "list"]);
2374
- const installedMas = masResult.stdout.split(`
2375
- `).filter(Boolean).map((line) => {
2376
- const match = line.match(/^(\d+)/);
2377
- return match ? parseInt(match[1], 10) : 0;
2378
- });
2379
- for (const [name, id] of Object.entries(config.mas)) {
2380
- if (!installedMas.includes(id)) {
2381
- log(` Installing: ${colors3.blue}${name}${colors3.reset}`);
2382
- await runCommand(["mas", "install", String(id)], cb, undefined, true);
3649
+ if (set.repositories && set.repositories.length > 0 && set.manager.addRepository) {
3650
+ log(` Adding repositories...`);
3651
+ for (const repo of set.repositories) {
3652
+ log(` ${colors3.blue}${repo}${colors3.reset}`);
3653
+ await set.manager.addRepository(repo, callbacks);
3654
+ }
3655
+ }
3656
+ if (set.packages.length === 0) {
3657
+ log(` No packages to install`);
3658
+ continue;
3659
+ }
3660
+ const installedMap = await set.manager.isInstalled(set.packages);
3661
+ const toInstall = set.packages.filter((pkg) => !installedMap.get(pkg));
3662
+ if (toInstall.length === 0) {
3663
+ log(` All ${set.packages.length} packages already installed`);
3664
+ } else {
3665
+ log(` Installing ${toInstall.length} packages...`);
3666
+ for (const pkg of toInstall) {
3667
+ log(` ${colors3.blue}${pkg}${colors3.reset}`);
2383
3668
  }
3669
+ await set.manager.install(toInstall, callbacks);
2384
3670
  }
2385
3671
  }
2386
3672
  if (config.config.purge) {
@@ -2390,7 +3676,7 @@ ${colors3.cyan}=== Installing Mac App Store apps ===${colors3.reset}
2390
3676
  ${colors3.cyan}=== Updating lockfile ===${colors3.reset}
2391
3677
  `);
2392
3678
  const lock = await updateLockfile();
2393
- const lockTotal = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
3679
+ const lockTotal = lock.version === 2 ? Object.keys(lock.packages).length : Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
2394
3680
  log(` Locked ${lockTotal} packages`);
2395
3681
  log(`
2396
3682
  ${colors3.green}=== Sync complete ===${colors3.reset}
@@ -2399,74 +3685,53 @@ ${colors3.green}=== Sync complete ===${colors3.reset}
2399
3685
  async function purgeUnlisted(config, interactive, cb = null) {
2400
3686
  const log = cb?.onLog ?? console.log;
2401
3687
  const askPrompt = cb?.onPrompt ?? (async (q) => (prompt(q) || "").trim().toLowerCase());
3688
+ const platform = await getPlatformInfo();
3689
+ const callbacks = cb ? { onLog: cb.onLog } : undefined;
2402
3690
  log(`
2403
3691
  ${colors3.cyan}=== Checking for unlisted packages ===${colors3.reset}
2404
3692
  `);
2405
- const installedFormulas = (await exec(["brew", "list", "--formula"])).stdout.split(`
2406
- `).filter(Boolean);
2407
- for (const pkg of installedFormulas) {
2408
- if (!config.packages.includes(pkg)) {
2409
- const usesResult = await exec(["brew", "uses", "--installed", pkg]);
2410
- if (usesResult.stdout.trim()) {
2411
- log(` ${colors3.yellow}Skipping ${pkg} (has dependents)${colors3.reset}`);
3693
+ const packageSets = await getPackageSetsForPlatform(config, platform);
3694
+ for (const set of packageSets) {
3695
+ const configuredSet = new Set(set.packages);
3696
+ let installedLeaves;
3697
+ if (set.manager.listLeaves) {
3698
+ installedLeaves = await set.manager.listLeaves();
3699
+ } else {
3700
+ const installed = await set.manager.listInstalled();
3701
+ installedLeaves = installed.map((p) => p.name);
3702
+ }
3703
+ for (const pkg of installedLeaves) {
3704
+ if (configuredSet.has(pkg))
2412
3705
  continue;
3706
+ if (set.manager.type === "mas") {
3707
+ const appId = parseInt(pkg, 10);
3708
+ if (SYSTEM_APP_IDS.includes(appId))
3709
+ continue;
2413
3710
  }
2414
- if (interactive) {
2415
- const answer = await askPrompt(`Remove ${colors3.red}${pkg}${colors3.reset}?`, ["y", "n"]);
2416
- if (answer === "y") {
2417
- await runCommand(["brew", "uninstall", pkg], cb);
3711
+ if (set.manager.type === "homebrew") {
3712
+ const formulas = set.manager;
3713
+ if (await formulas.hasDependents(pkg)) {
3714
+ log(` ${colors3.yellow}Skipping ${pkg} (has dependents)${colors3.reset}`);
3715
+ continue;
2418
3716
  }
2419
- } else {
2420
- log(` Removing: ${colors3.red}${pkg}${colors3.reset}`);
2421
- await runCommand(["brew", "uninstall", pkg], cb);
2422
3717
  }
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
3718
  if (interactive) {
2430
- const answer = await askPrompt(`Remove cask ${colors3.red}${cask}${colors3.reset}?`, ["y", "n"]);
3719
+ const answer = await askPrompt(`Remove ${colors3.red}${pkg}${colors3.reset} (${set.manager.displayName})?`, ["y", "n"]);
2431
3720
  if (answer === "y") {
2432
- await runCommand(["brew", "uninstall", "--cask", cask], cb);
3721
+ await set.manager.uninstall([pkg], callbacks);
2433
3722
  }
2434
3723
  } else {
2435
- log(` Removing cask: ${colors3.red}${cask}${colors3.reset}`);
2436
- await runCommand(["brew", "uninstall", "--cask", cask], cb);
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
- }
3724
+ log(` Removing: ${colors3.red}${pkg}${colors3.reset}`);
3725
+ await set.manager.uninstall([pkg], callbacks);
2462
3726
  }
2463
3727
  }
2464
3728
  }
2465
3729
  log(`
2466
3730
  ${colors3.cyan}=== Cleaning up ===${colors3.reset}
2467
3731
  `);
2468
- await runCommand(["brew", "autoremove"], cb);
2469
- await runCommand(["brew", "cleanup"], cb);
3732
+ for (const set of packageSets) {
3733
+ await set.manager.cleanup(callbacks);
3734
+ }
2470
3735
  }
2471
3736
  function printUsage2() {
2472
3737
  console.log(`
@@ -2494,13 +3759,12 @@ async function runPkgSyncWithCallbacks(args, callbacks) {
2494
3759
  },
2495
3760
  allowPositionals: true
2496
3761
  });
3762
+ const platform = await getPlatformInfo();
2497
3763
  try {
2498
- if (!await commandExists("brew")) {
2499
- callbacks.onLog(`${colors3.red}Error: Homebrew not installed${colors3.reset}`);
2500
- return { output: "Homebrew not installed", success: false };
2501
- }
3764
+ await checkDependencies(platform);
2502
3765
  } catch {
2503
- return { output: "Homebrew not installed", success: false };
3766
+ callbacks.onLog(`${colors3.red}Error: Required dependencies not installed${colors3.reset}`);
3767
+ return { output: "Dependencies not installed", success: false };
2504
3768
  }
2505
3769
  if (values["upgrade-interactive"]) {
2506
3770
  await upgradeInteractive(callbacks);
@@ -2542,7 +3806,8 @@ async function main2() {
2542
3806
  printUsage2();
2543
3807
  process.exit(0);
2544
3808
  }
2545
- await checkDependencies();
3809
+ const platform = await getPlatformInfo();
3810
+ await checkDependencies(platform);
2546
3811
  if (values["upgrade-interactive"]) {
2547
3812
  await upgradeInteractive();
2548
3813
  return;
@@ -2645,21 +3910,30 @@ async function showLockfile() {
2645
3910
  console.log(`${colors4.bold}Package Lockfile${colors4.reset}`);
2646
3911
  console.log(`Last updated: ${lock.lastUpdated}
2647
3912
  `);
2648
- const formulaNames = Object.keys(lock.formulas).sort();
2649
- const caskNames = Object.keys(lock.casks).sort();
2650
- if (formulaNames.length > 0) {
2651
- console.log(`${colors4.cyan}Formulas (${formulaNames.length}):${colors4.reset}`);
2652
- for (const name of formulaNames) {
2653
- const { version, tap } = lock.formulas[name];
2654
- console.log(` ${name} ${colors4.blue}${version}${colors4.reset} (${tap})`);
3913
+ if (lock.version === 2) {
3914
+ const packages = Object.entries(lock.packages).sort(([a], [b]) => a.localeCompare(b));
3915
+ console.log(`${colors4.cyan}Packages (${packages.length}):${colors4.reset}`);
3916
+ for (const [key, info] of packages) {
3917
+ const [manager, name] = key.split(":");
3918
+ console.log(` ${name} ${colors4.blue}${info.version}${colors4.reset} (${manager}${info.tap ? `, ${info.tap}` : ""})`);
2655
3919
  }
2656
- }
2657
- if (caskNames.length > 0) {
2658
- console.log(`
3920
+ } else {
3921
+ const formulaNames = Object.keys(lock.formulas).sort();
3922
+ const caskNames = Object.keys(lock.casks).sort();
3923
+ if (formulaNames.length > 0) {
3924
+ console.log(`${colors4.cyan}Formulas (${formulaNames.length}):${colors4.reset}`);
3925
+ for (const name of formulaNames) {
3926
+ const { version, tap } = lock.formulas[name];
3927
+ console.log(` ${name} ${colors4.blue}${version}${colors4.reset} (${tap})`);
3928
+ }
3929
+ }
3930
+ if (caskNames.length > 0) {
3931
+ console.log(`
2659
3932
  ${colors4.cyan}Casks (${caskNames.length}):${colors4.reset}`);
2660
- for (const name of caskNames) {
2661
- const { version } = lock.casks[name];
2662
- console.log(` ${name} ${colors4.blue}${version}${colors4.reset}`);
3933
+ for (const name of caskNames) {
3934
+ const { version } = lock.casks[name];
3935
+ console.log(` ${name} ${colors4.blue}${version}${colors4.reset}`);
3936
+ }
2663
3937
  }
2664
3938
  }
2665
3939
  }
@@ -2675,7 +3949,7 @@ async function runPkgLock(args) {
2675
3949
  switch (command) {
2676
3950
  case "update": {
2677
3951
  const lock = await updateLockfile();
2678
- const total = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
3952
+ const total = lock.version === 2 ? Object.keys(lock.packages).length : Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
2679
3953
  return { output: `Lockfile updated with ${total} packages`, success: true };
2680
3954
  }
2681
3955
  case "status": {
@@ -2720,7 +3994,7 @@ async function main3() {
2720
3994
  case "update": {
2721
3995
  console.log(`${colors4.cyan}Updating lockfile...${colors4.reset}`);
2722
3996
  const lock = await updateLockfile();
2723
- const total = Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
3997
+ const total = lock.version === 2 ? Object.keys(lock.packages).length : Object.keys(lock.formulas).length + Object.keys(lock.casks).length;
2724
3998
  console.log(`${colors4.green}Lockfile updated with ${total} packages.${colors4.reset}`);
2725
3999
  break;
2726
4000
  }
@@ -2754,64 +4028,248 @@ function getPackageName(fullName) {
2754
4028
  const parts = fullName.split("/");
2755
4029
  return parts[parts.length - 1];
2756
4030
  }
4031
+ function getOrphanPackageType(managerType) {
4032
+ switch (managerType) {
4033
+ case "homebrew":
4034
+ return "formula";
4035
+ case "homebrew-casks":
4036
+ return "cask";
4037
+ case "pacman":
4038
+ return "pacman";
4039
+ case "aur":
4040
+ return "aur";
4041
+ case "apt":
4042
+ return "apt";
4043
+ case "dnf":
4044
+ return "dnf";
4045
+ case "flatpak":
4046
+ return "flatpak";
4047
+ default:
4048
+ return "formula";
4049
+ }
4050
+ }
4051
+ async function getConfiguredPackagesForPlatform(config, platform) {
4052
+ const result = [];
4053
+ if (platform.os === "darwin") {
4054
+ const formulas = getPackageManager("homebrew");
4055
+ const casks = getPackageManager("homebrew-casks");
4056
+ const globalPkgs = config.global?.packages || [];
4057
+ const macosFormulas = config.macos?.formulas || [];
4058
+ const allFormulas = [...globalPkgs, ...macosFormulas];
4059
+ if (await formulas.isAvailable()) {
4060
+ result.push({
4061
+ manager: formulas,
4062
+ configuredPackages: new Set(allFormulas.map((p) => getPackageName(p)))
4063
+ });
4064
+ }
4065
+ const macosCasks = config.macos?.casks || [];
4066
+ if (await casks.isAvailable()) {
4067
+ result.push({
4068
+ manager: casks,
4069
+ configuredPackages: new Set(macosCasks)
4070
+ });
4071
+ }
4072
+ const mas = getPackageManager("mas");
4073
+ const masMappings = config.macos?.mas || {};
4074
+ const masIds = Object.values(masMappings).map(String);
4075
+ if (await mas.isAvailable()) {
4076
+ result.push({
4077
+ manager: mas,
4078
+ configuredPackages: new Set(masIds)
4079
+ });
4080
+ }
4081
+ } else {
4082
+ const distro = platform.distro;
4083
+ const globalPkgs = config.global?.packages || [];
4084
+ const linuxPkgs = config.linux?.packages || [];
4085
+ if (distro === "arch") {
4086
+ const pacman = getPackageManager("pacman");
4087
+ const aur = getPackageManager("aur");
4088
+ const archPkgs = config.arch?.packages || [];
4089
+ const allPacmanPkgs = [...globalPkgs, ...linuxPkgs, ...archPkgs];
4090
+ if (await pacman.isAvailable()) {
4091
+ result.push({
4092
+ manager: pacman,
4093
+ configuredPackages: new Set(allPacmanPkgs)
4094
+ });
4095
+ }
4096
+ const aurPkgs = config.arch?.aur || [];
4097
+ if (await aur.isAvailable()) {
4098
+ result.push({
4099
+ manager: aur,
4100
+ configuredPackages: new Set(aurPkgs)
4101
+ });
4102
+ }
4103
+ } else if (distro === "debian" || distro === "ubuntu") {
4104
+ const apt = getPackageManager("apt");
4105
+ const debianPkgs = config.debian?.packages || [];
4106
+ const allAptPkgs = [...globalPkgs, ...linuxPkgs, ...debianPkgs];
4107
+ if (await apt.isAvailable()) {
4108
+ result.push({
4109
+ manager: apt,
4110
+ configuredPackages: new Set(allAptPkgs)
4111
+ });
4112
+ }
4113
+ } else if (distro === "fedora" || distro === "rhel") {
4114
+ const dnf = getPackageManager("dnf");
4115
+ const fedoraPkgs = config.fedora?.packages || [];
4116
+ const allDnfPkgs = [...globalPkgs, ...linuxPkgs, ...fedoraPkgs];
4117
+ if (await dnf.isAvailable()) {
4118
+ result.push({
4119
+ manager: dnf,
4120
+ configuredPackages: new Set(allDnfPkgs)
4121
+ });
4122
+ }
4123
+ } else {
4124
+ const managers = await getAvailableManagers();
4125
+ const primaryManager = managers.find((m) => m.type === "pacman" || m.type === "apt" || m.type === "dnf");
4126
+ if (primaryManager) {
4127
+ const allPkgs = [...globalPkgs, ...linuxPkgs];
4128
+ result.push({
4129
+ manager: primaryManager,
4130
+ configuredPackages: new Set(allPkgs)
4131
+ });
4132
+ }
4133
+ }
4134
+ const flatpak = getPackageManager("flatpak");
4135
+ const flatpakApps = config.linux?.flatpak || [];
4136
+ if (await flatpak.isAvailable()) {
4137
+ result.push({
4138
+ manager: flatpak,
4139
+ configuredPackages: new Set(flatpakApps)
4140
+ });
4141
+ }
4142
+ }
4143
+ return result;
4144
+ }
2757
4145
  async function detectOrphanedPackages() {
2758
4146
  const config = await loadPkgConfig();
2759
- const leavesResult = await exec(["brew", "leaves"]);
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) : [];
4147
+ const platform = await getPlatformInfo();
2765
4148
  const orphans = [];
2766
- const configPackages = config.packages.map((pkg) => ({
2767
- full: pkg,
2768
- short: getPackageName(pkg)
2769
- }));
2770
- for (const installed of installedLeaves) {
2771
- const installedShort = getPackageName(installed);
2772
- const isInConfig = configPackages.some((cfg) => installed === cfg.full || installed === cfg.short || installedShort === cfg.full || installedShort === cfg.short || installed.endsWith(`/${cfg.short}`));
2773
- if (!isInConfig) {
2774
- orphans.push({ name: installed, type: "formula" });
4149
+ let totalConfigPackages = 0;
4150
+ let totalInstalledPackages = 0;
4151
+ const configuredPackagesPerManager = await getConfiguredPackagesForPlatform(config, platform);
4152
+ for (const { manager, configuredPackages } of configuredPackagesPerManager) {
4153
+ totalConfigPackages += configuredPackages.size;
4154
+ let installedLeaves2;
4155
+ if (manager.listLeaves) {
4156
+ installedLeaves2 = await manager.listLeaves();
4157
+ } else {
4158
+ const installed = await manager.listInstalled();
4159
+ installedLeaves2 = installed.map((p) => p.name);
2775
4160
  }
2776
- }
2777
- const configCasksSet = new Set(config.casks);
2778
- for (const cask of installedCasks) {
2779
- if (!configCasksSet.has(cask)) {
2780
- orphans.push({ name: cask, type: "cask" });
4161
+ totalInstalledPackages += installedLeaves2.length;
4162
+ for (const installed of installedLeaves2) {
4163
+ const installedShort = getPackageName(installed);
4164
+ const isInConfig = configuredPackages.has(installed) || configuredPackages.has(installedShort) || Array.from(configuredPackages).some((cfg) => installed === cfg || installedShort === cfg || getPackageName(cfg) === installedShort || installed.endsWith(`/${getPackageName(cfg)}`));
4165
+ if (!isInConfig) {
4166
+ if (manager.type === "mas") {
4167
+ const appId = parseInt(installed, 10);
4168
+ if (SYSTEM_APP_IDS.includes(appId))
4169
+ continue;
4170
+ }
4171
+ if (manager.type === "homebrew") {
4172
+ const formulas = manager;
4173
+ if (await formulas.hasDependents(installed))
4174
+ continue;
4175
+ }
4176
+ orphans.push({
4177
+ name: installed,
4178
+ type: getOrphanPackageType(manager.type),
4179
+ manager: manager.type
4180
+ });
4181
+ }
2781
4182
  }
2782
4183
  }
2783
4184
  orphans.sort((a, b) => {
2784
4185
  if (a.type !== b.type)
2785
- return a.type === "formula" ? -1 : 1;
4186
+ return a.type.localeCompare(b.type);
2786
4187
  return a.name.localeCompare(b.name);
2787
4188
  });
4189
+ let configFormulas = 0;
4190
+ let configCasks = 0;
4191
+ let installedLeaves = 0;
4192
+ let installedCasks = 0;
4193
+ if (platform.os === "darwin") {
4194
+ configFormulas = (config.global?.packages?.length || 0) + (config.macos?.formulas?.length || 0);
4195
+ configCasks = config.macos?.casks?.length || 0;
4196
+ const formulas = getPackageManager("homebrew");
4197
+ const casks = getPackageManager("homebrew-casks");
4198
+ if (await formulas.isAvailable()) {
4199
+ const leaves = await formulas.listLeaves?.();
4200
+ installedLeaves = leaves?.length || 0;
4201
+ }
4202
+ if (await casks.isAvailable()) {
4203
+ const caskList = await casks.listInstalled();
4204
+ installedCasks = caskList.length;
4205
+ }
4206
+ }
2788
4207
  return {
2789
4208
  orphans,
2790
- configFormulas: config.packages.length,
2791
- configCasks: config.casks.length,
2792
- installedLeaves: installedLeaves.length,
2793
- installedCasks: installedCasks.length
4209
+ configPackages: totalConfigPackages,
4210
+ installedPackages: totalInstalledPackages,
4211
+ configFormulas,
4212
+ configCasks,
4213
+ installedLeaves,
4214
+ installedCasks
2794
4215
  };
2795
4216
  }
2796
4217
  async function addToConfig(pkg) {
2797
4218
  const config = await loadPkgConfig();
2798
- if (pkg.type === "formula") {
2799
- if (!config.packages.includes(pkg.name)) {
2800
- config.packages.push(pkg.name);
2801
- config.packages.sort();
4219
+ const platform = await getPlatformInfo();
4220
+ if (platform.os === "darwin") {
4221
+ if (pkg.type === "formula") {
4222
+ const formulas = config.macos?.formulas || [];
4223
+ if (!formulas.includes(pkg.name)) {
4224
+ config.macos = config.macos || {};
4225
+ config.macos.formulas = [...formulas, pkg.name].sort();
4226
+ }
4227
+ } else if (pkg.type === "cask") {
4228
+ const casks = config.macos?.casks || [];
4229
+ if (!casks.includes(pkg.name)) {
4230
+ config.macos = config.macos || {};
4231
+ config.macos.casks = [...casks, pkg.name].sort();
4232
+ }
2802
4233
  }
2803
4234
  } else {
2804
- if (!config.casks.includes(pkg.name)) {
2805
- config.casks.push(pkg.name);
2806
- config.casks.sort();
4235
+ if (pkg.type === "pacman") {
4236
+ const packages = config.arch?.packages || [];
4237
+ if (!packages.includes(pkg.name)) {
4238
+ config.arch = config.arch || {};
4239
+ config.arch.packages = [...packages, pkg.name].sort();
4240
+ }
4241
+ } else if (pkg.type === "aur") {
4242
+ const aurPkgs = config.arch?.aur || [];
4243
+ if (!aurPkgs.includes(pkg.name)) {
4244
+ config.arch = config.arch || {};
4245
+ config.arch.aur = [...aurPkgs, pkg.name].sort();
4246
+ }
4247
+ } else if (pkg.type === "apt") {
4248
+ const packages = config.debian?.packages || [];
4249
+ if (!packages.includes(pkg.name)) {
4250
+ config.debian = config.debian || {};
4251
+ config.debian.packages = [...packages, pkg.name].sort();
4252
+ }
4253
+ } else if (pkg.type === "dnf") {
4254
+ const packages = config.fedora?.packages || [];
4255
+ if (!packages.includes(pkg.name)) {
4256
+ config.fedora = config.fedora || {};
4257
+ config.fedora.packages = [...packages, pkg.name].sort();
4258
+ }
4259
+ } else if (pkg.type === "flatpak") {
4260
+ const flatpakApps = config.linux?.flatpak || [];
4261
+ if (!flatpakApps.includes(pkg.name)) {
4262
+ config.linux = config.linux || {};
4263
+ config.linux.flatpak = [...flatpakApps, pkg.name].sort();
4264
+ }
2807
4265
  }
2808
4266
  }
2809
4267
  await savePkgConfig(config);
2810
4268
  }
2811
4269
  async function uninstallPackage(pkg) {
2812
- const cmd = pkg.type === "cask" ? ["brew", "uninstall", "--cask", pkg.name] : ["brew", "uninstall", pkg.name];
2813
- const result = await exec(cmd);
2814
- return result.success;
4270
+ const manager = getPackageManager(pkg.manager);
4271
+ await manager.uninstall([pkg.name]);
4272
+ return true;
2815
4273
  }
2816
4274
 
2817
4275
  // src/components/menus/PackageMenu.tsx
@@ -2825,7 +4283,18 @@ function PackageMenu({ onBack }) {
2825
4283
  const [success, setSuccess] = useState8(true);
2826
4284
  const [orphanResult, setOrphanResult] = useState8(null);
2827
4285
  const [isOrphanView, setIsOrphanView] = useState8(false);
4286
+ const [platformInfo, setPlatformInfo] = useState8(null);
4287
+ const [availableManagerNames, setAvailableManagerNames] = useState8([]);
2828
4288
  const isRunningRef = useRef(false);
4289
+ useEffect4(() => {
4290
+ async function loadPlatformInfo() {
4291
+ const info = await getPlatformInfo();
4292
+ setPlatformInfo(info);
4293
+ const managers = await getAvailableManagers();
4294
+ setAvailableManagerNames(managers.map((m) => m.displayName));
4295
+ }
4296
+ loadPlatformInfo();
4297
+ }, []);
2829
4298
  useInput9((input, key) => {
2830
4299
  if (state === "menu" && (key.escape || key.leftArrow || input === "h")) {
2831
4300
  onBack();
@@ -2978,26 +4447,60 @@ function PackageMenu({ onBack }) {
2978
4447
  ]
2979
4448
  }, undefined, true, undefined, this);
2980
4449
  }
4450
+ const platformDisplay = platformInfo ? getPlatformDisplayName(platformInfo) : "Detecting...";
4451
+ const managersDisplay = availableManagerNames.length > 0 ? availableManagerNames.join(", ") : "Detecting...";
2981
4452
  return /* @__PURE__ */ jsxDEV17(Panel, {
2982
4453
  title: "Package Sync",
2983
- children: /* @__PURE__ */ jsxDEV17(VimSelect, {
2984
- options: [
2985
- { label: "Sync packages", value: "sync" },
2986
- { label: "Sync with purge", value: "sync-purge" },
2987
- { label: "Upgrade all (with verification)", value: "upgrade" },
2988
- { label: "Upgrade interactive", value: "upgrade-interactive" },
2989
- { label: "Update lockfile", value: "lock-update" },
2990
- { label: "Lockfile status", value: "lock-status" },
2991
- { label: "Find orphaned packages", value: "orphans" },
2992
- { label: "Back", value: "back" }
2993
- ],
2994
- onChange: handleAction
2995
- }, undefined, false, undefined, this)
2996
- }, undefined, false, undefined, this);
4454
+ children: [
4455
+ /* @__PURE__ */ jsxDEV17(Box14, {
4456
+ marginBottom: 1,
4457
+ flexDirection: "column",
4458
+ children: [
4459
+ /* @__PURE__ */ jsxDEV17(Text13, {
4460
+ children: [
4461
+ /* @__PURE__ */ jsxDEV17(Text13, {
4462
+ dimColor: true,
4463
+ children: "Platform: "
4464
+ }, undefined, false, undefined, this),
4465
+ /* @__PURE__ */ jsxDEV17(Text13, {
4466
+ color: colors.info,
4467
+ children: platformDisplay
4468
+ }, undefined, false, undefined, this)
4469
+ ]
4470
+ }, undefined, true, undefined, this),
4471
+ /* @__PURE__ */ jsxDEV17(Text13, {
4472
+ children: [
4473
+ /* @__PURE__ */ jsxDEV17(Text13, {
4474
+ dimColor: true,
4475
+ children: "Managers: "
4476
+ }, undefined, false, undefined, this),
4477
+ /* @__PURE__ */ jsxDEV17(Text13, {
4478
+ color: colors.info,
4479
+ children: managersDisplay
4480
+ }, undefined, false, undefined, this)
4481
+ ]
4482
+ }, undefined, true, undefined, this)
4483
+ ]
4484
+ }, undefined, true, undefined, this),
4485
+ /* @__PURE__ */ jsxDEV17(VimSelect, {
4486
+ options: [
4487
+ { label: "Sync packages", value: "sync" },
4488
+ { label: "Sync with purge", value: "sync-purge" },
4489
+ { label: "Upgrade all (with verification)", value: "upgrade" },
4490
+ { label: "Upgrade interactive", value: "upgrade-interactive" },
4491
+ { label: "Update lockfile", value: "lock-update" },
4492
+ { label: "Lockfile status", value: "lock-status" },
4493
+ { label: "Find orphaned packages", value: "orphans" },
4494
+ { label: "Back", value: "back" }
4495
+ ],
4496
+ onChange: handleAction
4497
+ }, undefined, false, undefined, this)
4498
+ ]
4499
+ }, undefined, true, undefined, this);
2997
4500
  }
2998
4501
 
2999
4502
  // src/components/menus/ThemeMenu.tsx
3000
- import { useState as useState10, useEffect as useEffect5, useMemo as useMemo4 } from "react";
4503
+ import { useState as useState10, useEffect as useEffect6, useMemo as useMemo4 } from "react";
3001
4504
  import { Box as Box16, Text as Text15 } from "ink";
3002
4505
  import { existsSync as existsSync7, readdirSync as readdirSync5 } from "fs";
3003
4506
  import { join as join6 } from "path";
@@ -3042,7 +4545,7 @@ function ThemeCard({ theme, isSelected, width }) {
3042
4545
  }
3043
4546
 
3044
4547
  // src/hooks/useThemeGrid.ts
3045
- import { useState as useState9, useEffect as useEffect4 } from "react";
4548
+ import { useState as useState9, useEffect as useEffect5 } from "react";
3046
4549
  import { useInput as useInput10 } from "ink";
3047
4550
  function useThemeGrid({
3048
4551
  itemCount,
@@ -3063,7 +4566,7 @@ function useThemeGrid({
3063
4566
  const visibleRows = Math.max(1, Math.floor(availableHeight / cardHeight));
3064
4567
  const selectedRow = Math.floor(selectedIndex / cardsPerRow);
3065
4568
  const totalRows = Math.ceil(itemCount / cardsPerRow);
3066
- useEffect4(() => {
4569
+ useEffect5(() => {
3067
4570
  if (selectedRow < scrollOffset) {
3068
4571
  setScrollOffset(selectedRow);
3069
4572
  } else if (selectedRow >= scrollOffset + visibleRows) {
@@ -3121,6 +4624,7 @@ function useThemeGrid({
3121
4624
  }
3122
4625
 
3123
4626
  // src/lib/theme-parser.ts
4627
+ init_runtime();
3124
4628
  import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
3125
4629
  import { join as join4 } from "path";
3126
4630
  function parseYaml(content) {
@@ -3197,8 +4701,7 @@ async function parseTheme(themePath, themeName) {
3197
4701
  // src/cli/set-theme.ts
3198
4702
  import { parseArgs as parseArgs4 } from "util";
3199
4703
  import { readdirSync as readdirSync4, existsSync as existsSync6, rmSync, symlinkSync, unlinkSync } from "fs";
3200
- import { join as join5, dirname as dirname3 } from "path";
3201
- var LYNK_BROWSER_CSS = join5(HOME_DIR, ".config", "lynk-browser", "style.css");
4704
+ import { join as join5 } from "path";
3202
4705
  var colors5 = {
3203
4706
  red: "\x1B[0;31m",
3204
4707
  green: "\x1B[0;32m",
@@ -3267,12 +4770,6 @@ async function applyTheme(themeName) {
3267
4770
  const backgroundsSource = join5(themeDir, "backgrounds");
3268
4771
  createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
3269
4772
  }
3270
- const styleCssSource = join5(themeDir, "style.css");
3271
- if (existsSync6(styleCssSource)) {
3272
- const lynkBrowserDir = dirname3(LYNK_BROWSER_CSS);
3273
- await ensureDir2(lynkBrowserDir);
3274
- createSymlink(styleCssSource, LYNK_BROWSER_CSS);
3275
- }
3276
4773
  let output = `Theme '${theme.name}' applied successfully`;
3277
4774
  if (theme.metadata?.author) {
3278
4775
  output += `
@@ -3383,7 +4880,7 @@ function ThemeMenu({ onBack }) {
3383
4880
  onBack,
3384
4881
  enabled: state === "menu" && !loading && themes.length > 0
3385
4882
  });
3386
- useEffect5(() => {
4883
+ useEffect6(() => {
3387
4884
  async function loadThemes() {
3388
4885
  if (!existsSync7(THEMES_DIR)) {
3389
4886
  setThemes([]);
@@ -3501,6 +4998,7 @@ function ThemeMenu({ onBack }) {
3501
4998
  }
3502
4999
 
3503
5000
  // src/cli/formalconf.tsx
5001
+ init_runtime();
3504
5002
  import { jsxDEV as jsxDEV20 } from "react/jsx-dev-runtime";
3505
5003
  var BREADCRUMBS = {
3506
5004
  main: ["Main"],
@@ -3517,7 +5015,7 @@ function App() {
3517
5015
  if (input === "q")
3518
5016
  exit();
3519
5017
  });
3520
- useEffect6(() => {
5018
+ useEffect7(() => {
3521
5019
  async function init() {
3522
5020
  await ensureConfigDir();
3523
5021
  const result = await checkPrerequisites();