@dahch/heimdall 1.1.0 โ 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/cli.js +228 -81
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
2. **Aggregated Review & Confirmation**: Shows an interactive tabular overview comparing current and latest package versions across all managers, prompting for confirmation before modifying the system.
|
|
21
21
|
- ๐งช **True Simulated Dry-Run (`--dry-run`)**: Traverses the full engine lifecycle, formats outdated packages, and executes mock update routines step-by-step with real-time feedback without touching system files.
|
|
22
22
|
- โก **High-Speed Registry Queries**: Yarn and pipx bypass heavy CLI subprocess spawning by querying the npm Registry and PyPI JSON APIs directly over HTTP with strict 3-second abort timeouts.
|
|
23
|
-
- ๐ **
|
|
23
|
+
- ๐ **Interactive Sudo Acquisition**: When a selected manager requires elevation (`requiresSudo`), `hmd` verifies the cached ticket with `sudo -n true` and, if missing, prompts for the password exactly once through `sudo -v` on the controlling terminal before any spinner takes over the TTY. A background keep-alive refreshes the ticket during long upgrades, and every privileged step re-verifies non-interactive credentials so subprocesses can never hang on a hidden password prompt. Running `sudo hmd` is neither required nor supported (the CLI lives under the user's `PATH`, which root's `secure_path` does not include).
|
|
24
24
|
- ๐จ **Modern Minimalist UX**: Terminal interface powered by `@clack/prompts`, micro-border tabular layouts with directional version transitions (`current โ latest`), persistent real-time step progression with elapsed durations, and actionable error extraction.
|
|
25
25
|
- ๐ท **Dynamic Version Resolution**: Reads package name and version dynamically from `package.json` at runtime via `import.meta.url`, ensuring banners and `--version` flags remain in sync with repository releases.
|
|
26
26
|
- โ **Flexible Targeting**: Run interactively, execute non-interactively with `--yes`, or filter target managers using `--only` and `--exclude`.
|
|
@@ -44,9 +44,9 @@ Heimdall (`hmd`) supports 14 package managers across system, runtime, language,
|
|
|
44
44
|
| **[Ruby Gem](https://rubygems.org)** | ๐ | `language` | `gem outdated` (skips deprecated Apple System Ruby `/usr/bin/gem`) | 1. `gem update --user-install`<br>2. `gem cleanup` |
|
|
45
45
|
| **[MacPorts](https://www.macports.org)** | โ | `system` | `port outdated` + non-interactive `sudo -n true` verification | 1. `sudo port selfupdate`<br>2. `sudo port upgrade outdated` |
|
|
46
46
|
| **[Mac App Store (mas)](https://github.com/mas-cli/mas)** | ๐ | `appstore` | `mas outdated` | `mas upgrade` |
|
|
47
|
-
| **[APT (Debian/Ubuntu)](https://wiki.debian.org/Apt)** | ๐ง | `system` | `apt list --upgradable` + non-interactive `sudo -n true` check | 1. `sudo apt-get update -y`<br>2. `sudo apt-get upgrade -y
|
|
47
|
+
| **[APT (Debian/Ubuntu)](https://wiki.debian.org/Apt)** | ๐ง | `system` | `apt list --upgradable` + non-interactive `sudo -n true` check | 1. `sudo apt-get update -y`<br>2. `sudo apt-get upgrade -y --with-new-pkgs` (avoids leaving dependency-gaining upgrades `kept back`)<br>3. `sudo apt-get autoremove -y` (prompted interactively with confirmation prompt, auto-approved with `--yes` or `--dry-run`, skipped when declined or after a failed upgrade) |
|
|
48
48
|
| **[DNF (Fedora/RHEL)](https://docs.fedoraproject.org/en-US/quick-docs/dnf/)** | ๐ฆ | `system` | `dnf check-update -y` (exit code 100 signals updates) + `sudo -n true` check | 1. `sudo dnf upgrade -y --refresh`<br>2. `sudo dnf autoremove -y` (prompted interactively with confirmation prompt, auto-approved with `--yes` or `--dry-run`) |
|
|
49
|
-
| **[Flatpak](https://flatpak.org)** | ๐ฆ | `system` | `flatpak remote-ls --updates` | `flatpak update -y --noninteractive` |
|
|
49
|
+
| **[Flatpak](https://flatpak.org)** | ๐ฆ | `system` | `flatpak remote-ls --user --updates` + `flatpak remote-ls --system --updates` (both scopes, deduplicated) | 1. `flatpak update --user -y --noninteractive`<br>2. `sudo flatpak update --system -y --noninteractive` (marked `failed` with sudo guidance when system apps exist but privileges are unavailable, `skipped` when no system-scope apps are installed) |
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
@@ -157,7 +157,9 @@ Test coverage includes:
|
|
|
157
157
|
- Concurrency pool bounds and error isolation in [`UpdaterEngine`](src/core/engine.ts).
|
|
158
158
|
- `NpmManager` targeted batch installation, fallback trigger handling, and `--ignore-scripts` resilience.
|
|
159
159
|
- `HomebrewManager` selective formula vs. cask execution branches.
|
|
160
|
-
- Pre-check behaviors (skipping `/usr/bin/gem`, sudo privilege checks for MacPorts and
|
|
160
|
+
- Pre-check behaviors (skipping `/usr/bin/gem`, sudo privilege checks for MacPorts, APT and DNF, interactive sudo acquisition with ticket keep-alive).
|
|
161
|
+
- `FlatpakManager` dual-scope querying (user + system installations) and system-step sudo gating.
|
|
162
|
+
- `AptManager` upgradable parsing (epochs, architecture-qualified names, header/warning lines) and its consent-gated autoremove branch.
|
|
161
163
|
- HTTP registry lookup error handling for Yarn and pipx.
|
|
162
164
|
- Formatting renderers (micro-border table, directional version diffs, execution summary metrics).
|
|
163
165
|
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,26 @@ async function commandExists(command) {
|
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
var SUDO_REQUIRED_HINT = `Run "sudo -v" in this terminal and retry ("sudo hmd" will not work: hmd is not in root's PATH).`;
|
|
22
|
+
async function ensureSudo() {
|
|
23
|
+
if (process.getuid?.() === 0) return true;
|
|
24
|
+
const cachedTicket = await safeExec("sudo", ["-n", "true"], { timeoutMs: 5e3 });
|
|
25
|
+
if (cachedTicket.success) return true;
|
|
26
|
+
if (!process.stdin.isTTY || !await commandExists("sudo")) return false;
|
|
27
|
+
try {
|
|
28
|
+
const result = await execa("sudo", ["-v"], { stdio: "inherit", reject: false });
|
|
29
|
+
return result.exitCode === 0;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function startSudoKeepAlive(intervalMs = 6e4) {
|
|
35
|
+
const timer = setInterval(() => {
|
|
36
|
+
void safeExec("sudo", ["-n", "-v"], { timeoutMs: 1e4 });
|
|
37
|
+
}, intervalMs);
|
|
38
|
+
timer.unref();
|
|
39
|
+
return () => clearInterval(timer);
|
|
40
|
+
}
|
|
21
41
|
async function safeExec(file, args = [], options = {}) {
|
|
22
42
|
const timeout = options.timeoutMs ?? 3e4;
|
|
23
43
|
try {
|
|
@@ -140,6 +160,14 @@ var BasePackageManager = class {
|
|
|
140
160
|
async isAvailable() {
|
|
141
161
|
return commandExists(this.binary);
|
|
142
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Verifies that sudo can run without a password prompt (cached ticket or NOPASSWD rule),
|
|
165
|
+
* so privileged steps fail fast instead of hanging inside a stdin-less subprocess.
|
|
166
|
+
*/
|
|
167
|
+
async hasPasswordlessSudo() {
|
|
168
|
+
const result = await safeExec("sudo", ["-n", "true"], { timeoutMs: 3e3 });
|
|
169
|
+
return result.success;
|
|
170
|
+
}
|
|
143
171
|
async executeStep(stepName, file, args, options, extraExecOptions) {
|
|
144
172
|
const startTime = Date.now();
|
|
145
173
|
options.onStepStart?.(stepName);
|
|
@@ -1311,6 +1339,7 @@ var MacPortsManager = class extends BasePackageManager {
|
|
|
1311
1339
|
name = "MacPorts";
|
|
1312
1340
|
icon = "\u2693";
|
|
1313
1341
|
category = "system";
|
|
1342
|
+
requiresSudo = true;
|
|
1314
1343
|
binary = "port";
|
|
1315
1344
|
async checkUpdates(options) {
|
|
1316
1345
|
const startTime = Date.now();
|
|
@@ -1376,8 +1405,8 @@ var MacPortsManager = class extends BasePackageManager {
|
|
|
1376
1405
|
const startTime = Date.now();
|
|
1377
1406
|
const steps = [];
|
|
1378
1407
|
if (!options.dryRun && process.getuid?.() !== 0) {
|
|
1379
|
-
|
|
1380
|
-
|
|
1408
|
+
if (!await this.hasPasswordlessSudo()) {
|
|
1409
|
+
const error = `MacPorts requires sudo privileges. ${SUDO_REQUIRED_HINT}`;
|
|
1381
1410
|
return {
|
|
1382
1411
|
managerId: this.id,
|
|
1383
1412
|
managerName: this.name,
|
|
@@ -1390,10 +1419,10 @@ var MacPortsManager = class extends BasePackageManager {
|
|
|
1390
1419
|
name: "sudo privileges",
|
|
1391
1420
|
command: "sudo -n true",
|
|
1392
1421
|
status: "failed",
|
|
1393
|
-
error
|
|
1422
|
+
error
|
|
1394
1423
|
}
|
|
1395
1424
|
],
|
|
1396
|
-
error
|
|
1425
|
+
error
|
|
1397
1426
|
};
|
|
1398
1427
|
}
|
|
1399
1428
|
}
|
|
@@ -1614,6 +1643,7 @@ var AptManager = class extends BasePackageManager {
|
|
|
1614
1643
|
name = "APT (Debian/Ubuntu)";
|
|
1615
1644
|
icon = "\u{1F427}";
|
|
1616
1645
|
category = "system";
|
|
1646
|
+
requiresSudo = true;
|
|
1617
1647
|
binary = "apt-get";
|
|
1618
1648
|
async checkUpdates(options) {
|
|
1619
1649
|
const startTime = Date.now();
|
|
@@ -1676,8 +1706,8 @@ var AptManager = class extends BasePackageManager {
|
|
|
1676
1706
|
const startTime = Date.now();
|
|
1677
1707
|
const steps = [];
|
|
1678
1708
|
if (!options.dryRun && process.getuid?.() !== 0) {
|
|
1679
|
-
|
|
1680
|
-
|
|
1709
|
+
if (!await this.hasPasswordlessSudo()) {
|
|
1710
|
+
const error = `APT requires sudo privileges. ${SUDO_REQUIRED_HINT}`;
|
|
1681
1711
|
return {
|
|
1682
1712
|
managerId: this.id,
|
|
1683
1713
|
managerName: this.name,
|
|
@@ -1690,10 +1720,10 @@ var AptManager = class extends BasePackageManager {
|
|
|
1690
1720
|
name: "sudo privileges",
|
|
1691
1721
|
command: "sudo -n true",
|
|
1692
1722
|
status: "failed",
|
|
1693
|
-
error
|
|
1723
|
+
error
|
|
1694
1724
|
}
|
|
1695
1725
|
],
|
|
1696
|
-
error
|
|
1726
|
+
error
|
|
1697
1727
|
};
|
|
1698
1728
|
}
|
|
1699
1729
|
}
|
|
@@ -1707,18 +1737,54 @@ var AptManager = class extends BasePackageManager {
|
|
|
1707
1737
|
const upgradeStep = await this.executeStep(
|
|
1708
1738
|
"sudo apt-get upgrade",
|
|
1709
1739
|
"sudo",
|
|
1710
|
-
["apt-get", "upgrade", "-y"],
|
|
1740
|
+
["apt-get", "upgrade", "-y", "--with-new-pkgs"],
|
|
1711
1741
|
options
|
|
1712
1742
|
);
|
|
1713
1743
|
steps.push(upgradeStep);
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1744
|
+
if (upgradeStep.status === "success") {
|
|
1745
|
+
if (options.dryRun) {
|
|
1746
|
+
const autoremoveStep = await this.executeStep(
|
|
1747
|
+
"sudo apt-get autoremove",
|
|
1748
|
+
"sudo",
|
|
1749
|
+
["apt-get", "autoremove", "-y"],
|
|
1750
|
+
options
|
|
1751
|
+
);
|
|
1752
|
+
steps.push(autoremoveStep);
|
|
1753
|
+
} else if (options.confirmAction) {
|
|
1754
|
+
const shouldAutoremove = await options.confirmAction(
|
|
1755
|
+
"Do you want to run sudo apt-get autoremove to clean up orphaned packages?"
|
|
1756
|
+
);
|
|
1757
|
+
if (shouldAutoremove) {
|
|
1758
|
+
const autoremoveStep = await this.executeStep(
|
|
1759
|
+
"sudo apt-get autoremove",
|
|
1760
|
+
"sudo",
|
|
1761
|
+
["apt-get", "autoremove", "-y"],
|
|
1762
|
+
options
|
|
1763
|
+
);
|
|
1764
|
+
steps.push(autoremoveStep);
|
|
1765
|
+
} else {
|
|
1766
|
+
steps.push({
|
|
1767
|
+
name: "sudo apt-get autoremove",
|
|
1768
|
+
command: "sudo apt-get autoremove -y",
|
|
1769
|
+
status: "skipped"
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
} else {
|
|
1773
|
+
steps.push({
|
|
1774
|
+
name: "sudo apt-get autoremove",
|
|
1775
|
+
command: "sudo apt-get autoremove -y",
|
|
1776
|
+
status: "skipped"
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
} else {
|
|
1780
|
+
steps.push({
|
|
1781
|
+
name: "sudo apt-get autoremove",
|
|
1782
|
+
command: "sudo apt-get autoremove -y",
|
|
1783
|
+
status: "skipped"
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
const executedSteps = steps.filter((s) => s.status !== "skipped");
|
|
1787
|
+
const success = executedSteps.length > 0 && executedSteps.every((s) => s.status === "success");
|
|
1722
1788
|
return {
|
|
1723
1789
|
managerId: this.id,
|
|
1724
1790
|
managerName: this.name,
|
|
@@ -1736,6 +1802,7 @@ var FlatpakManager = class extends BasePackageManager {
|
|
|
1736
1802
|
name = "Flatpak";
|
|
1737
1803
|
icon = "\u{1F4E6}";
|
|
1738
1804
|
category = "system";
|
|
1805
|
+
requiresSudo = true;
|
|
1739
1806
|
binary = "flatpak";
|
|
1740
1807
|
async checkUpdates(options) {
|
|
1741
1808
|
const startTime = Date.now();
|
|
@@ -1752,23 +1819,43 @@ var FlatpakManager = class extends BasePackageManager {
|
|
|
1752
1819
|
};
|
|
1753
1820
|
}
|
|
1754
1821
|
try {
|
|
1755
|
-
const
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
const
|
|
1762
|
-
|
|
1763
|
-
const parts = trimmed.split(/\s+/);
|
|
1764
|
-
updates.push({
|
|
1822
|
+
const timeoutMs = options?.timeoutMs ?? 2e4;
|
|
1823
|
+
const [userRes, systemRes] = await Promise.all([
|
|
1824
|
+
safeExec("flatpak", ["remote-ls", "--user", "--updates"], { timeoutMs }),
|
|
1825
|
+
safeExec("flatpak", ["remote-ls", "--system", "--updates"], { timeoutMs })
|
|
1826
|
+
]);
|
|
1827
|
+
if (!userRes.success && !systemRes.success) {
|
|
1828
|
+
const raw = (systemRes.stderr || userRes.stderr).trim();
|
|
1829
|
+
return {
|
|
1765
1830
|
managerId: this.id,
|
|
1766
1831
|
managerName: this.name,
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1832
|
+
icon: this.icon,
|
|
1833
|
+
category: this.category,
|
|
1834
|
+
available: true,
|
|
1835
|
+
updates: [],
|
|
1836
|
+
durationMs: Date.now() - startTime,
|
|
1837
|
+
error: raw ? extractErrorMessage(raw) : void 0
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
const updates = [];
|
|
1841
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1842
|
+
for (const execRes of [userRes, systemRes]) {
|
|
1843
|
+
for (const line of execRes.stdout.split("\n")) {
|
|
1844
|
+
const trimmed = line.trim();
|
|
1845
|
+
if (!trimmed) continue;
|
|
1846
|
+
const parts = trimmed.split(/\s+/);
|
|
1847
|
+
const name = parts[0];
|
|
1848
|
+
if (seen.has(name)) continue;
|
|
1849
|
+
seen.add(name);
|
|
1850
|
+
updates.push({
|
|
1851
|
+
managerId: this.id,
|
|
1852
|
+
managerName: this.name,
|
|
1853
|
+
name,
|
|
1854
|
+
currentVersion: "installed",
|
|
1855
|
+
latestVersion: parts[1] ?? "latest",
|
|
1856
|
+
type: "flatpak"
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1772
1859
|
}
|
|
1773
1860
|
return {
|
|
1774
1861
|
managerId: this.id,
|
|
@@ -1792,17 +1879,56 @@ var FlatpakManager = class extends BasePackageManager {
|
|
|
1792
1879
|
};
|
|
1793
1880
|
}
|
|
1794
1881
|
}
|
|
1882
|
+
async hasSystemScopeApps() {
|
|
1883
|
+
const result = await safeExec("flatpak", ["list", "--system", "--columns=application"], {
|
|
1884
|
+
timeoutMs: 1e4
|
|
1885
|
+
});
|
|
1886
|
+
return result.success && result.stdout.trim().length > 0;
|
|
1887
|
+
}
|
|
1795
1888
|
async executeUpdate(items, options) {
|
|
1796
1889
|
const startTime = Date.now();
|
|
1797
1890
|
const steps = [];
|
|
1798
|
-
const
|
|
1799
|
-
"flatpak update
|
|
1891
|
+
const userStep = await this.executeStep(
|
|
1892
|
+
"flatpak update --user",
|
|
1800
1893
|
"flatpak",
|
|
1801
|
-
["update", "-y", "--noninteractive"],
|
|
1894
|
+
["update", "--user", "-y", "--noninteractive"],
|
|
1802
1895
|
options
|
|
1803
1896
|
);
|
|
1804
|
-
steps.push(
|
|
1805
|
-
const
|
|
1897
|
+
steps.push(userStep);
|
|
1898
|
+
const sudoStepName = "sudo flatpak update --system";
|
|
1899
|
+
const sudoStepCommand = "sudo flatpak update --system -y --noninteractive";
|
|
1900
|
+
if (options.dryRun) {
|
|
1901
|
+
const systemStep = await this.executeStep(
|
|
1902
|
+
sudoStepName,
|
|
1903
|
+
"sudo",
|
|
1904
|
+
["flatpak", "update", "--system", "-y", "--noninteractive"],
|
|
1905
|
+
options
|
|
1906
|
+
);
|
|
1907
|
+
steps.push(systemStep);
|
|
1908
|
+
} else if (process.getuid?.() === 0 || await this.hasPasswordlessSudo()) {
|
|
1909
|
+
const systemStep = await this.executeStep(
|
|
1910
|
+
sudoStepName,
|
|
1911
|
+
"sudo",
|
|
1912
|
+
["flatpak", "update", "--system", "-y", "--noninteractive"],
|
|
1913
|
+
options
|
|
1914
|
+
);
|
|
1915
|
+
steps.push(systemStep);
|
|
1916
|
+
} else if (await this.hasSystemScopeApps()) {
|
|
1917
|
+
steps.push({
|
|
1918
|
+
name: sudoStepName,
|
|
1919
|
+
command: sudoStepCommand,
|
|
1920
|
+
status: "failed",
|
|
1921
|
+
error: `System-wide Flatpak updates require sudo privileges. ${SUDO_REQUIRED_HINT}`
|
|
1922
|
+
});
|
|
1923
|
+
} else {
|
|
1924
|
+
steps.push({
|
|
1925
|
+
name: sudoStepName,
|
|
1926
|
+
command: sudoStepCommand,
|
|
1927
|
+
status: "skipped"
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
const executedSteps = steps.filter((s) => s.status !== "skipped");
|
|
1931
|
+
const success = executedSteps.length > 0 && executedSteps.every((s) => s.status === "success");
|
|
1806
1932
|
return {
|
|
1807
1933
|
managerId: this.id,
|
|
1808
1934
|
managerName: this.name,
|
|
@@ -1820,6 +1946,7 @@ var DnfManager = class extends BasePackageManager {
|
|
|
1820
1946
|
name = "DNF (Fedora/RHEL)";
|
|
1821
1947
|
icon = "\u{1F4E6}";
|
|
1822
1948
|
category = "system";
|
|
1949
|
+
requiresSudo = true;
|
|
1823
1950
|
binary = "dnf";
|
|
1824
1951
|
async checkUpdates(options) {
|
|
1825
1952
|
const startTime = Date.now();
|
|
@@ -1917,8 +2044,8 @@ var DnfManager = class extends BasePackageManager {
|
|
|
1917
2044
|
const startTime = Date.now();
|
|
1918
2045
|
const steps = [];
|
|
1919
2046
|
if (!options.dryRun && process.getuid?.() !== 0) {
|
|
1920
|
-
|
|
1921
|
-
|
|
2047
|
+
if (!await this.hasPasswordlessSudo()) {
|
|
2048
|
+
const error = `DNF requires sudo privileges. ${SUDO_REQUIRED_HINT}`;
|
|
1922
2049
|
return {
|
|
1923
2050
|
managerId: this.id,
|
|
1924
2051
|
managerName: this.name,
|
|
@@ -1931,10 +2058,10 @@ var DnfManager = class extends BasePackageManager {
|
|
|
1931
2058
|
name: "sudo privileges",
|
|
1932
2059
|
command: "sudo -n true",
|
|
1933
2060
|
status: "failed",
|
|
1934
|
-
error
|
|
2061
|
+
error
|
|
1935
2062
|
}
|
|
1936
2063
|
],
|
|
1937
|
-
error
|
|
2064
|
+
error
|
|
1938
2065
|
};
|
|
1939
2066
|
}
|
|
1940
2067
|
}
|
|
@@ -2341,6 +2468,21 @@ ${pc2.bold("Pending updates")} ${pc2.dim(`(${allUpdates.length} total):`)}`);
|
|
|
2341
2468
|
process.exit(0);
|
|
2342
2469
|
}
|
|
2343
2470
|
const isSimulated = options.dryRun ?? false;
|
|
2471
|
+
let stopSudoKeepAlive;
|
|
2472
|
+
const selectedIds = new Set(managersToUpdate);
|
|
2473
|
+
const needsSudo = selectedManagers.some((m) => selectedIds.has(m.id) && m.requiresSudo);
|
|
2474
|
+
if (needsSudo && !isSimulated) {
|
|
2475
|
+
p.log.step(pc2.dim("Requesting sudo privileges required by system package managers..."));
|
|
2476
|
+
if (await ensureSudo()) {
|
|
2477
|
+
stopSudoKeepAlive = startSudoKeepAlive();
|
|
2478
|
+
} else {
|
|
2479
|
+
p.log.warn(
|
|
2480
|
+
pc2.yellow(
|
|
2481
|
+
'Sudo privileges not granted: system package managers will fail. Run "sudo -v" in this terminal and retry.'
|
|
2482
|
+
)
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2344
2486
|
p.log.step(
|
|
2345
2487
|
pc2.bold(
|
|
2346
2488
|
pc2.cyan(
|
|
@@ -2351,50 +2493,55 @@ ${isSimulated ? "[DRY RUN] Simulating" : "Starting"} updates for ${managersToUpd
|
|
|
2351
2493
|
);
|
|
2352
2494
|
const updateSpinner = p.spinner();
|
|
2353
2495
|
let managerIndex = 0;
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2496
|
+
let summary;
|
|
2497
|
+
try {
|
|
2498
|
+
summary = await engine.execute(
|
|
2499
|
+
managersToUpdate,
|
|
2500
|
+
itemsByManager,
|
|
2501
|
+
{
|
|
2502
|
+
verbose: options.verbose,
|
|
2503
|
+
dryRun: isSimulated,
|
|
2504
|
+
confirmAction: async (message) => {
|
|
2505
|
+
if (options.yes) return true;
|
|
2506
|
+
updateSpinner.stop(pc2.dim("Waiting for user confirmation..."));
|
|
2507
|
+
const confirmed = await p.confirm({ message, initialValue: false });
|
|
2508
|
+
updateSpinner.start(pc2.dim("Continuing update..."));
|
|
2509
|
+
return typeof confirmed === "boolean" ? confirmed : false;
|
|
2510
|
+
},
|
|
2511
|
+
onStepStart: (stepName) => {
|
|
2512
|
+
updateSpinner.message(pc2.dim(`Running: ${stepName}...`));
|
|
2513
|
+
},
|
|
2514
|
+
onStepProgress: (stepName, logLine) => {
|
|
2515
|
+
if (logLine) {
|
|
2516
|
+
const truncated = logLine.length > 55 ? logLine.slice(0, 52) + "..." : logLine;
|
|
2517
|
+
updateSpinner.message(pc2.dim(`${stepName} [${truncated}]`));
|
|
2518
|
+
}
|
|
2519
|
+
},
|
|
2520
|
+
onStepEnd: (stepName, success, error, durationMs) => {
|
|
2521
|
+
const mark = success ? pc2.green("\u2714") : pc2.red("\u2716");
|
|
2522
|
+
const durStr = durationMs !== void 0 ? pc2.dim(` [${formatDuration(durationMs)}]`) : "";
|
|
2523
|
+
const detail = error ? pc2.red(` \u21B3 ${error.split("\n")[0]}`) : "";
|
|
2524
|
+
updateSpinner.stop(` ${mark} ${pc2.dim(stepName)}${durStr}${detail}`);
|
|
2525
|
+
updateSpinner.start(pc2.dim("Working..."));
|
|
2374
2526
|
}
|
|
2375
2527
|
},
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
const detail = error ? pc2.red(` \u21B3 ${error.split("\n")[0]}`) : "";
|
|
2380
|
-
updateSpinner.stop(` ${mark} ${pc2.dim(stepName)}${durStr}${detail}`);
|
|
2381
|
-
updateSpinner.start(pc2.dim("Working..."));
|
|
2382
|
-
}
|
|
2383
|
-
},
|
|
2384
|
-
(mgr) => {
|
|
2385
|
-
managerIndex++;
|
|
2386
|
-
console.log(pc2.bold(pc2.cyan(`
|
|
2528
|
+
(mgr) => {
|
|
2529
|
+
managerIndex++;
|
|
2530
|
+
console.log(pc2.bold(pc2.cyan(`
|
|
2387
2531
|
\u25CF [${managerIndex}/${managersToUpdate.length}] ${mgr.icon} ${mgr.name}`)));
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2532
|
+
updateSpinner.start(pc2.dim("Starting update routine..."));
|
|
2533
|
+
},
|
|
2534
|
+
(mgr, res) => {
|
|
2535
|
+
if (res.success) {
|
|
2536
|
+
updateSpinner.stop(pc2.green(` \u2714 ${mgr.name} completed in ${formatDuration(res.durationMs)}`));
|
|
2537
|
+
} else {
|
|
2538
|
+
updateSpinner.stop(pc2.red(` \u2716 ${mgr.name} completed with issues in ${formatDuration(res.durationMs)}`));
|
|
2539
|
+
}
|
|
2395
2540
|
}
|
|
2396
|
-
|
|
2397
|
-
|
|
2541
|
+
);
|
|
2542
|
+
} finally {
|
|
2543
|
+
stopSudoKeepAlive?.();
|
|
2544
|
+
}
|
|
2398
2545
|
console.log(renderExecutionSummary(summary.results));
|
|
2399
2546
|
if (summary.failedCount === 0) {
|
|
2400
2547
|
p.outro(
|