@dahch/heimdall 1.0.0 โ 1.1.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 +4 -3
- package/dist/cli.js +290 -96
- package/package.json +3 -3
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
|
-
- ๐ **Non-Interactive Sudo Pre-Checks**: Privileged managers (MacPorts, Linux APT) verify cached credentials using `sudo -n true` beforehand, preventing terminal lockups and password prompt hangs.
|
|
23
|
+
- ๐ **Non-Interactive Sudo Pre-Checks**: Privileged managers (MacPorts, Linux APT, Linux DNF) verify cached credentials using `sudo -n true` beforehand, preventing terminal lockups and password prompt hangs.
|
|
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`.
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
## ๐ฆ Supported Package Managers
|
|
31
31
|
|
|
32
|
-
Heimdall (`hmd`) supports
|
|
32
|
+
Heimdall (`hmd`) supports 14 package managers across system, runtime, language, and application store categories:
|
|
33
33
|
|
|
34
34
|
| Manager | Icon | Category | Detection Strategy | Update Routine & Resilience Rules |
|
|
35
35
|
| :--- | :---: | :---: | :--- | :--- |
|
|
@@ -45,6 +45,7 @@ Heimdall (`hmd`) supports 13 package managers across system, runtime, language,
|
|
|
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
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`<br>3. `sudo apt-get autoremove -y` |
|
|
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`) |
|
|
48
49
|
| **[Flatpak](https://flatpak.org)** | ๐ฆ | `system` | `flatpak remote-ls --updates` | `flatpak update -y --noninteractive` |
|
|
49
50
|
|
|
50
51
|
---
|
|
@@ -178,7 +179,7 @@ Heimdall employs an automated GitHub Actions CI/CD workflow ([`.github/workflows
|
|
|
178
179
|
|
|
179
180
|
- [SPEC.md](SPEC.md): Technical specification, CLI contract, data models, and exit codes.
|
|
180
181
|
- [DESIGN.md](DESIGN.md): System architecture, Two-Phase workflow, Engine concurrency pool, and Mermaid diagrams.
|
|
181
|
-
- [ADR.md](ADR.md): Architectural Decision Records (ADR-001 through ADR-
|
|
182
|
+
- [ADR.md](ADR.md): Architectural Decision Records (ADR-001 through ADR-010) documenting key engineering decisions.
|
|
182
183
|
- [AGENTS.md](AGENTS.md): Developer and AI agent reference manual for maintaining and extending the codebase.
|
|
183
184
|
|
|
184
185
|
---
|
package/dist/cli.js
CHANGED
|
@@ -1513,6 +1513,101 @@ var MacAppStoreManager = class extends BasePackageManager {
|
|
|
1513
1513
|
}
|
|
1514
1514
|
};
|
|
1515
1515
|
|
|
1516
|
+
// src/utils/formatting.ts
|
|
1517
|
+
import pc from "picocolors";
|
|
1518
|
+
import Table from "cli-table3";
|
|
1519
|
+
function formatDuration(ms) {
|
|
1520
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
1521
|
+
const seconds = (ms / 1e3).toFixed(1);
|
|
1522
|
+
return `${seconds}s`;
|
|
1523
|
+
}
|
|
1524
|
+
function renderUpdatesTable(items) {
|
|
1525
|
+
if (items.length === 0) {
|
|
1526
|
+
return pc.dim(" No updates pending.");
|
|
1527
|
+
}
|
|
1528
|
+
const table = new Table({
|
|
1529
|
+
head: [
|
|
1530
|
+
pc.dim("Manager"),
|
|
1531
|
+
pc.dim("Package"),
|
|
1532
|
+
pc.dim("Type"),
|
|
1533
|
+
pc.dim("Version Transition")
|
|
1534
|
+
],
|
|
1535
|
+
style: {
|
|
1536
|
+
head: [],
|
|
1537
|
+
border: ["dim"]
|
|
1538
|
+
},
|
|
1539
|
+
chars: {
|
|
1540
|
+
top: "\u2500",
|
|
1541
|
+
"top-mid": "\u2500",
|
|
1542
|
+
"top-left": "\u250C",
|
|
1543
|
+
"top-right": "\u2510",
|
|
1544
|
+
bottom: "\u2500",
|
|
1545
|
+
"bottom-mid": "\u2500",
|
|
1546
|
+
"bottom-left": "\u2514",
|
|
1547
|
+
"bottom-right": "\u2518",
|
|
1548
|
+
left: "\u2502",
|
|
1549
|
+
"left-mid": "\u251C",
|
|
1550
|
+
mid: "\u2500",
|
|
1551
|
+
"mid-mid": "\u253C",
|
|
1552
|
+
right: "\u2502",
|
|
1553
|
+
"right-mid": "\u2524",
|
|
1554
|
+
middle: "\u2502"
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
for (const item of items) {
|
|
1558
|
+
const typeLabel = item.type ? pc.dim(`[${item.type}]`) : pc.dim("-");
|
|
1559
|
+
const currentVer = pc.yellow(item.currentVersion || "?");
|
|
1560
|
+
const arrow = pc.dim(" \u2192 ");
|
|
1561
|
+
const latestVer = pc.green(pc.bold(item.latestVersion || "?"));
|
|
1562
|
+
table.push([
|
|
1563
|
+
pc.cyan(item.managerName),
|
|
1564
|
+
pc.bold(pc.white(item.name)),
|
|
1565
|
+
typeLabel,
|
|
1566
|
+
`${currentVer}${arrow}${latestVer}`
|
|
1567
|
+
]);
|
|
1568
|
+
}
|
|
1569
|
+
return table.toString();
|
|
1570
|
+
}
|
|
1571
|
+
function renderExecutionSummary(results) {
|
|
1572
|
+
const total = results.length;
|
|
1573
|
+
const successful = results.filter((r) => r.success).length;
|
|
1574
|
+
const failed = results.filter((r) => !r.success).length;
|
|
1575
|
+
const totalUpdated = results.reduce((acc, r) => acc + r.updatedCount, 0);
|
|
1576
|
+
const lines = [];
|
|
1577
|
+
lines.push("");
|
|
1578
|
+
lines.push(pc.dim("\u2500".repeat(60)));
|
|
1579
|
+
lines.push(` ${pc.bold(pc.cyan("Heimdall"))} ${pc.dim("\u2022 Execution Summary")}`);
|
|
1580
|
+
lines.push(pc.dim("\u2500".repeat(60)));
|
|
1581
|
+
for (const res of results) {
|
|
1582
|
+
const icon = res.icon || "\u{1F4E6}";
|
|
1583
|
+
const status = res.success ? pc.green("\u2714 SUCCESS") : pc.red("\u2716 FAILED");
|
|
1584
|
+
const time = pc.dim(`(${formatDuration(res.durationMs)})`);
|
|
1585
|
+
const count = res.success ? pc.dim(`\u2022 ${res.updatedCount} updated`) : pc.dim(`\u2022 ${res.updatedCount} updated, ${res.steps.filter((s) => s.status === "failed").length} issue(s)`);
|
|
1586
|
+
lines.push(` ${icon} ${pc.bold(res.managerName)} ${status} ${count} ${time}`);
|
|
1587
|
+
if (!res.success && res.error) {
|
|
1588
|
+
lines.push(` ${pc.red("\u21B3 Error:")} ${pc.dim(res.error.trim().split("\n")[0])}`);
|
|
1589
|
+
}
|
|
1590
|
+
if (res.steps.length > 0) {
|
|
1591
|
+
for (const step of res.steps) {
|
|
1592
|
+
if (step.status === "skipped") continue;
|
|
1593
|
+
const stepStatus = step.status === "success" ? pc.green("\u2714") : step.status === "failed" ? pc.red("\u2716") : pc.dim("\u25CB");
|
|
1594
|
+
const stepDuration = step.durationMs ? pc.dim(`[${formatDuration(step.durationMs)}]`) : "";
|
|
1595
|
+
const stepError = step.error && step.status === "failed" ? pc.red(` (${step.error.split("\n")[0]})`) : "";
|
|
1596
|
+
lines.push(` ${stepStatus} ${pc.dim(step.name)} ${stepDuration}${stepError}`);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
lines.push(pc.dim("\u2500".repeat(60)));
|
|
1601
|
+
const summaryParts = [
|
|
1602
|
+
pc.green(`${successful}/${total} managers succeeded`),
|
|
1603
|
+
failed > 0 ? pc.red(`${failed} failed`) : null,
|
|
1604
|
+
pc.cyan(`${totalUpdated} package(s) updated`)
|
|
1605
|
+
].filter(Boolean);
|
|
1606
|
+
lines.push(` ${summaryParts.join(" \u2022 ")}`);
|
|
1607
|
+
lines.push(pc.dim("\u2500".repeat(60)));
|
|
1608
|
+
return lines.join("\n");
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1516
1611
|
// src/managers/linux.ts
|
|
1517
1612
|
var AptManager = class extends BasePackageManager {
|
|
1518
1613
|
id = "apt";
|
|
@@ -1720,6 +1815,192 @@ var FlatpakManager = class extends BasePackageManager {
|
|
|
1720
1815
|
};
|
|
1721
1816
|
}
|
|
1722
1817
|
};
|
|
1818
|
+
var DnfManager = class extends BasePackageManager {
|
|
1819
|
+
id = "dnf";
|
|
1820
|
+
name = "DNF (Fedora/RHEL)";
|
|
1821
|
+
icon = "\u{1F4E6}";
|
|
1822
|
+
category = "system";
|
|
1823
|
+
binary = "dnf";
|
|
1824
|
+
async checkUpdates(options) {
|
|
1825
|
+
const startTime = Date.now();
|
|
1826
|
+
const isAvail = await this.isAvailable();
|
|
1827
|
+
if (!isAvail) {
|
|
1828
|
+
return {
|
|
1829
|
+
managerId: this.id,
|
|
1830
|
+
managerName: this.name,
|
|
1831
|
+
icon: this.icon,
|
|
1832
|
+
category: this.category,
|
|
1833
|
+
available: false,
|
|
1834
|
+
updates: [],
|
|
1835
|
+
durationMs: 0
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
try {
|
|
1839
|
+
const execRes = await safeExec(this.binary, ["check-update", "-y"], {
|
|
1840
|
+
timeoutMs: options?.timeoutMs ?? 25e3
|
|
1841
|
+
});
|
|
1842
|
+
if (execRes.exitCode !== 0 && execRes.exitCode !== 100) {
|
|
1843
|
+
return {
|
|
1844
|
+
managerId: this.id,
|
|
1845
|
+
managerName: this.name,
|
|
1846
|
+
icon: this.icon,
|
|
1847
|
+
category: this.category,
|
|
1848
|
+
available: true,
|
|
1849
|
+
updates: [],
|
|
1850
|
+
durationMs: Date.now() - startTime,
|
|
1851
|
+
error: (execRes.stderr || execRes.stdout).trim() ? extractErrorMessage(execRes.stderr || execRes.stdout) : `dnf check-update failed with code ${execRes.exitCode}`
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
if (execRes.exitCode === 0) {
|
|
1855
|
+
return {
|
|
1856
|
+
managerId: this.id,
|
|
1857
|
+
managerName: this.name,
|
|
1858
|
+
icon: this.icon,
|
|
1859
|
+
category: this.category,
|
|
1860
|
+
available: true,
|
|
1861
|
+
updates: [],
|
|
1862
|
+
durationMs: Date.now() - startTime
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
const updates = [];
|
|
1866
|
+
const lines = execRes.stdout.split("\n");
|
|
1867
|
+
let pendingPkg = null;
|
|
1868
|
+
for (const rawLine of lines) {
|
|
1869
|
+
const line = rawLine.trim();
|
|
1870
|
+
if (!line) continue;
|
|
1871
|
+
if (line.startsWith("Updating and loading repositories") || line.startsWith("Repositories loaded") || line.startsWith("Last metadata expiration check") || line.startsWith("Obsoleting Packages") || line.startsWith("Loaded plugins") || line.startsWith("Security:")) {
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
const combinedLine = pendingPkg ? `${pendingPkg} ${line}` : line;
|
|
1875
|
+
const match = combinedLine.match(/^(\S+?)\.([a-zA-Z0-9_]+)\s+(\S+)\s+(\S+)/);
|
|
1876
|
+
if (match) {
|
|
1877
|
+
const [, name, arch, latestVersion, repo] = match;
|
|
1878
|
+
updates.push({
|
|
1879
|
+
managerId: this.id,
|
|
1880
|
+
managerName: this.name,
|
|
1881
|
+
name,
|
|
1882
|
+
currentVersion: "installed",
|
|
1883
|
+
latestVersion,
|
|
1884
|
+
type: "rpm",
|
|
1885
|
+
extra: `${repo} (${arch})`
|
|
1886
|
+
});
|
|
1887
|
+
pendingPkg = null;
|
|
1888
|
+
} else if (!pendingPkg && /^\S+?\.[a-zA-Z0-9_]+$/.test(line)) {
|
|
1889
|
+
pendingPkg = line;
|
|
1890
|
+
} else {
|
|
1891
|
+
pendingPkg = null;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return {
|
|
1895
|
+
managerId: this.id,
|
|
1896
|
+
managerName: this.name,
|
|
1897
|
+
icon: this.icon,
|
|
1898
|
+
category: this.category,
|
|
1899
|
+
available: true,
|
|
1900
|
+
updates,
|
|
1901
|
+
durationMs: Date.now() - startTime
|
|
1902
|
+
};
|
|
1903
|
+
} catch (err) {
|
|
1904
|
+
return {
|
|
1905
|
+
managerId: this.id,
|
|
1906
|
+
managerName: this.name,
|
|
1907
|
+
icon: this.icon,
|
|
1908
|
+
category: this.category,
|
|
1909
|
+
available: true,
|
|
1910
|
+
updates: [],
|
|
1911
|
+
durationMs: Date.now() - startTime,
|
|
1912
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
async executeUpdate(items, options) {
|
|
1917
|
+
const startTime = Date.now();
|
|
1918
|
+
const steps = [];
|
|
1919
|
+
if (!options.dryRun && process.getuid?.() !== 0) {
|
|
1920
|
+
const sudoCheck = await safeExec("sudo", ["-n", "true"], { timeoutMs: 3e3 });
|
|
1921
|
+
if (!sudoCheck.success) {
|
|
1922
|
+
return {
|
|
1923
|
+
managerId: this.id,
|
|
1924
|
+
managerName: this.name,
|
|
1925
|
+
icon: this.icon,
|
|
1926
|
+
success: false,
|
|
1927
|
+
updatedCount: 0,
|
|
1928
|
+
durationMs: Date.now() - startTime,
|
|
1929
|
+
steps: [
|
|
1930
|
+
{
|
|
1931
|
+
name: "sudo privileges",
|
|
1932
|
+
command: "sudo -n true",
|
|
1933
|
+
status: "failed",
|
|
1934
|
+
error: 'DNF requires sudo privileges. Run "sudo -v" before running hmd.'
|
|
1935
|
+
}
|
|
1936
|
+
],
|
|
1937
|
+
error: 'DNF requires sudo privileges. Run "sudo -v" before running hmd.'
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
const upgradeStep = await this.executeStep(
|
|
1942
|
+
"sudo dnf upgrade --refresh",
|
|
1943
|
+
"sudo",
|
|
1944
|
+
["dnf", "upgrade", "-y", "--refresh"],
|
|
1945
|
+
options
|
|
1946
|
+
);
|
|
1947
|
+
steps.push(upgradeStep);
|
|
1948
|
+
if (upgradeStep.status === "success") {
|
|
1949
|
+
if (options.dryRun) {
|
|
1950
|
+
const autoremoveStep = await this.executeStep(
|
|
1951
|
+
"sudo dnf autoremove",
|
|
1952
|
+
"sudo",
|
|
1953
|
+
["dnf", "autoremove", "-y"],
|
|
1954
|
+
options
|
|
1955
|
+
);
|
|
1956
|
+
steps.push(autoremoveStep);
|
|
1957
|
+
} else if (options.confirmAction) {
|
|
1958
|
+
const shouldAutoremove = await options.confirmAction(
|
|
1959
|
+
"Do you want to run sudo dnf autoremove to clean up orphaned packages?"
|
|
1960
|
+
);
|
|
1961
|
+
if (shouldAutoremove) {
|
|
1962
|
+
const autoremoveStep = await this.executeStep(
|
|
1963
|
+
"sudo dnf autoremove",
|
|
1964
|
+
"sudo",
|
|
1965
|
+
["dnf", "autoremove", "-y"],
|
|
1966
|
+
options
|
|
1967
|
+
);
|
|
1968
|
+
steps.push(autoremoveStep);
|
|
1969
|
+
} else {
|
|
1970
|
+
steps.push({
|
|
1971
|
+
name: "sudo dnf autoremove",
|
|
1972
|
+
command: "sudo dnf autoremove -y",
|
|
1973
|
+
status: "skipped"
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
} else {
|
|
1977
|
+
steps.push({
|
|
1978
|
+
name: "sudo dnf autoremove",
|
|
1979
|
+
command: "sudo dnf autoremove -y",
|
|
1980
|
+
status: "skipped"
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
} else {
|
|
1984
|
+
steps.push({
|
|
1985
|
+
name: "sudo dnf autoremove",
|
|
1986
|
+
command: "sudo dnf autoremove -y",
|
|
1987
|
+
status: "skipped"
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
const nonSkippedSteps = steps.filter((s) => s.status !== "skipped");
|
|
1991
|
+
const success = nonSkippedSteps.length > 0 && nonSkippedSteps.every((s) => s.status === "success");
|
|
1992
|
+
return {
|
|
1993
|
+
managerId: this.id,
|
|
1994
|
+
managerName: this.name,
|
|
1995
|
+
icon: this.icon,
|
|
1996
|
+
success,
|
|
1997
|
+
updatedCount: success ? items.length : 0,
|
|
1998
|
+
durationMs: Date.now() - startTime,
|
|
1999
|
+
steps,
|
|
2000
|
+
error: !success ? steps.find((s) => s.status === "failed")?.error : void 0
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
1723
2004
|
|
|
1724
2005
|
// src/managers/registry.ts
|
|
1725
2006
|
function createDefaultManagers() {
|
|
@@ -1736,6 +2017,7 @@ function createDefaultManagers() {
|
|
|
1736
2017
|
new MacPortsManager(),
|
|
1737
2018
|
new MacAppStoreManager(),
|
|
1738
2019
|
new AptManager(),
|
|
2020
|
+
new DnfManager(),
|
|
1739
2021
|
new FlatpakManager()
|
|
1740
2022
|
];
|
|
1741
2023
|
}
|
|
@@ -1872,108 +2154,13 @@ var UpdaterEngine = class {
|
|
|
1872
2154
|
}
|
|
1873
2155
|
};
|
|
1874
2156
|
|
|
1875
|
-
// src/utils/formatting.ts
|
|
1876
|
-
import pc from "picocolors";
|
|
1877
|
-
import Table from "cli-table3";
|
|
1878
|
-
function formatDuration(ms) {
|
|
1879
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
1880
|
-
const seconds = (ms / 1e3).toFixed(1);
|
|
1881
|
-
return `${seconds}s`;
|
|
1882
|
-
}
|
|
1883
|
-
function renderUpdatesTable(items) {
|
|
1884
|
-
if (items.length === 0) {
|
|
1885
|
-
return pc.dim(" No updates pending.");
|
|
1886
|
-
}
|
|
1887
|
-
const table = new Table({
|
|
1888
|
-
head: [
|
|
1889
|
-
pc.dim("Manager"),
|
|
1890
|
-
pc.dim("Package"),
|
|
1891
|
-
pc.dim("Type"),
|
|
1892
|
-
pc.dim("Version Transition")
|
|
1893
|
-
],
|
|
1894
|
-
style: {
|
|
1895
|
-
head: [],
|
|
1896
|
-
border: ["dim"]
|
|
1897
|
-
},
|
|
1898
|
-
chars: {
|
|
1899
|
-
top: "\u2500",
|
|
1900
|
-
"top-mid": "\u2500",
|
|
1901
|
-
"top-left": "\u250C",
|
|
1902
|
-
"top-right": "\u2510",
|
|
1903
|
-
bottom: "\u2500",
|
|
1904
|
-
"bottom-mid": "\u2500",
|
|
1905
|
-
"bottom-left": "\u2514",
|
|
1906
|
-
"bottom-right": "\u2518",
|
|
1907
|
-
left: "\u2502",
|
|
1908
|
-
"left-mid": "\u251C",
|
|
1909
|
-
mid: "\u2500",
|
|
1910
|
-
"mid-mid": "\u253C",
|
|
1911
|
-
right: "\u2502",
|
|
1912
|
-
"right-mid": "\u2524",
|
|
1913
|
-
middle: "\u2502"
|
|
1914
|
-
}
|
|
1915
|
-
});
|
|
1916
|
-
for (const item of items) {
|
|
1917
|
-
const typeLabel = item.type ? pc.dim(`[${item.type}]`) : pc.dim("-");
|
|
1918
|
-
const currentVer = pc.yellow(item.currentVersion || "?");
|
|
1919
|
-
const arrow = pc.dim(" \u2192 ");
|
|
1920
|
-
const latestVer = pc.green(pc.bold(item.latestVersion || "?"));
|
|
1921
|
-
table.push([
|
|
1922
|
-
pc.cyan(item.managerName),
|
|
1923
|
-
pc.bold(pc.white(item.name)),
|
|
1924
|
-
typeLabel,
|
|
1925
|
-
`${currentVer}${arrow}${latestVer}`
|
|
1926
|
-
]);
|
|
1927
|
-
}
|
|
1928
|
-
return table.toString();
|
|
1929
|
-
}
|
|
1930
|
-
function renderExecutionSummary(results) {
|
|
1931
|
-
const total = results.length;
|
|
1932
|
-
const successful = results.filter((r) => r.success).length;
|
|
1933
|
-
const failed = results.filter((r) => !r.success).length;
|
|
1934
|
-
const totalUpdated = results.reduce((acc, r) => acc + r.updatedCount, 0);
|
|
1935
|
-
const lines = [];
|
|
1936
|
-
lines.push("");
|
|
1937
|
-
lines.push(pc.dim("\u2500".repeat(60)));
|
|
1938
|
-
lines.push(` ${pc.bold(pc.cyan("Heimdall"))} ${pc.dim("\u2022 Execution Summary")}`);
|
|
1939
|
-
lines.push(pc.dim("\u2500".repeat(60)));
|
|
1940
|
-
for (const res of results) {
|
|
1941
|
-
const icon = res.icon || "\u{1F4E6}";
|
|
1942
|
-
const status = res.success ? pc.green("\u2714 SUCCESS") : pc.red("\u2716 FAILED");
|
|
1943
|
-
const time = pc.dim(`(${formatDuration(res.durationMs)})`);
|
|
1944
|
-
const count = res.success ? pc.dim(`\u2022 ${res.updatedCount} updated`) : pc.dim(`\u2022 ${res.updatedCount} updated, ${res.steps.filter((s) => s.status === "failed").length} issue(s)`);
|
|
1945
|
-
lines.push(` ${icon} ${pc.bold(res.managerName)} ${status} ${count} ${time}`);
|
|
1946
|
-
if (!res.success && res.error) {
|
|
1947
|
-
lines.push(` ${pc.red("\u21B3 Error:")} ${pc.dim(res.error.trim().split("\n")[0])}`);
|
|
1948
|
-
}
|
|
1949
|
-
if (res.steps.length > 0) {
|
|
1950
|
-
for (const step of res.steps) {
|
|
1951
|
-
if (step.status === "skipped") continue;
|
|
1952
|
-
const stepStatus = step.status === "success" ? pc.green("\u2714") : step.status === "failed" ? pc.red("\u2716") : pc.dim("\u25CB");
|
|
1953
|
-
const stepDuration = step.durationMs ? pc.dim(`[${formatDuration(step.durationMs)}]`) : "";
|
|
1954
|
-
const stepError = step.error && step.status === "failed" ? pc.red(` (${step.error.split("\n")[0]})`) : "";
|
|
1955
|
-
lines.push(` ${stepStatus} ${pc.dim(step.name)} ${stepDuration}${stepError}`);
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
lines.push(pc.dim("\u2500".repeat(60)));
|
|
1960
|
-
const summaryParts = [
|
|
1961
|
-
pc.green(`${successful}/${total} managers succeeded`),
|
|
1962
|
-
failed > 0 ? pc.red(`${failed} failed`) : null,
|
|
1963
|
-
pc.cyan(`${totalUpdated} package(s) updated`)
|
|
1964
|
-
].filter(Boolean);
|
|
1965
|
-
lines.push(` ${summaryParts.join(" \u2022 ")}`);
|
|
1966
|
-
lines.push(pc.dim("\u2500".repeat(60)));
|
|
1967
|
-
return lines.join("\n");
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
2157
|
// src/utils/version.ts
|
|
1971
2158
|
import { fileURLToPath } from "url";
|
|
1972
2159
|
import { dirname, resolve } from "path";
|
|
1973
2160
|
import { existsSync, readFileSync } from "fs";
|
|
1974
2161
|
var FALLBACK_METADATA = {
|
|
1975
2162
|
name: "@dahch/heimdall",
|
|
1976
|
-
version: "1.0.
|
|
2163
|
+
version: "1.0.1",
|
|
1977
2164
|
description: "Heimdall: resilient validator and updater for system and global packages"
|
|
1978
2165
|
};
|
|
1979
2166
|
var cachedMetadata = null;
|
|
@@ -2170,6 +2357,13 @@ ${isSimulated ? "[DRY RUN] Simulating" : "Starting"} updates for ${managersToUpd
|
|
|
2170
2357
|
{
|
|
2171
2358
|
verbose: options.verbose,
|
|
2172
2359
|
dryRun: isSimulated,
|
|
2360
|
+
confirmAction: async (message) => {
|
|
2361
|
+
if (options.yes) return true;
|
|
2362
|
+
updateSpinner.stop(pc2.dim("Waiting for user confirmation..."));
|
|
2363
|
+
const confirmed = await p.confirm({ message, initialValue: false });
|
|
2364
|
+
updateSpinner.start(pc2.dim("Continuing update..."));
|
|
2365
|
+
return typeof confirmed === "boolean" ? confirmed : false;
|
|
2366
|
+
},
|
|
2173
2367
|
onStepStart: (stepName) => {
|
|
2174
2368
|
updateSpinner.message(pc2.dim(`Running: ${stepName}...`));
|
|
2175
2369
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dahch/heimdall",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Heimdall: resilient validator and updater for system and global packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
12
|
"bin": {
|
|
13
|
-
"hmd": "
|
|
14
|
-
"heimdall": "
|
|
13
|
+
"hmd": "dist/cli.js",
|
|
14
|
+
"heimdall": "dist/cli.js"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"dev": "tsx src/cli.ts",
|