@higherdev/cli 0.23.0 → 0.24.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/dist/host.js +1 -0
- package/dist/index.js +24 -1
- package/dist/out.js +1 -0
- package/dist/tui/App.js +8 -2
- package/dist/tui/launch.js +2 -0
- package/dist/update-check.js +165 -0
- package/package.json +1 -1
package/dist/host.js
CHANGED
|
@@ -171,6 +171,7 @@ export function hostRollCommand(checkout) {
|
|
|
171
171
|
"git pull --ff-only",
|
|
172
172
|
"pnpm install",
|
|
173
173
|
"systemctl --user restart hdx-runner.service",
|
|
174
|
+
"npm i -g @higherdev/cli@latest",
|
|
174
175
|
].join(" && ");
|
|
175
176
|
}
|
|
176
177
|
async function defaultSsh(address, command) {
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { approveEpic, answerDecision, cancelTicket, mergeTicket, createEpic, del
|
|
|
5
5
|
import { formatDrainStatus, hostRoll, initHost, parseHostFlags, parseHostRollFlags } from "./host.js";
|
|
6
6
|
import { login, parseLoginFlags } from "./login.js";
|
|
7
7
|
import { loadConfig } from "./config.js";
|
|
8
|
+
import { beginUpdateCheck, pendingUpdateNotice, runUpdate, shouldUpdateCheck, } from "./update-check.js";
|
|
8
9
|
import { epicProgressRows, readEpicSpec } from "./epics.js";
|
|
9
10
|
import { roadmapText } from "./roadmap.js";
|
|
10
11
|
import { banner, c, statusChip, table, truncate, usage } from "./out.js";
|
|
@@ -569,11 +570,20 @@ async function cmdHost(argv, deps = {}) {
|
|
|
569
570
|
fail(usage);
|
|
570
571
|
}
|
|
571
572
|
export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
572
|
-
const
|
|
573
|
+
const args = argv.filter((arg) => arg !== "--no-update-check");
|
|
574
|
+
const [cmd, ...rest] = args;
|
|
575
|
+
const checking = shouldUpdateCheck({
|
|
576
|
+
argv, env: deps.env, isTTY: deps.isTTY,
|
|
577
|
+
});
|
|
578
|
+
if (checking)
|
|
579
|
+
beginUpdateCheck(deps);
|
|
580
|
+
let launchedTui = false;
|
|
581
|
+
let skipNotice = cmd === "update";
|
|
573
582
|
try {
|
|
574
583
|
if (!cmd) {
|
|
575
584
|
const { canLaunchApp } = await import("./tui/capability.js");
|
|
576
585
|
if (canLaunchApp()) {
|
|
586
|
+
launchedTui = true;
|
|
577
587
|
const { launchApp } = await import("./tui/launch.js");
|
|
578
588
|
await launchApp();
|
|
579
589
|
return;
|
|
@@ -658,6 +668,7 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
658
668
|
if (cmd === "login") {
|
|
659
669
|
const config = login(parseLoginFlags(rest));
|
|
660
670
|
console.log(`logged in to ${config.slug}`);
|
|
671
|
+
launchedTui = true;
|
|
661
672
|
const { launchApp } = await import("./tui/launch.js");
|
|
662
673
|
await launchApp(config.slug);
|
|
663
674
|
return;
|
|
@@ -667,11 +678,23 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
667
678
|
console.log(report);
|
|
668
679
|
return;
|
|
669
680
|
}
|
|
681
|
+
if (cmd === "update") {
|
|
682
|
+
const { canLaunchApp } = await import("./tui/capability.js");
|
|
683
|
+
await runUpdate(rest, { ...deps, tuiRunning: deps.tuiRunning ?? canLaunchApp() });
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
670
686
|
fail(usage());
|
|
671
687
|
}
|
|
672
688
|
catch (error) {
|
|
673
689
|
fail(error instanceof Error ? error.message : String(error));
|
|
674
690
|
}
|
|
691
|
+
finally {
|
|
692
|
+
if (checking && !launchedTui && !skipNotice) {
|
|
693
|
+
const line = pendingUpdateNotice(deps);
|
|
694
|
+
if (line)
|
|
695
|
+
(deps.error ?? console.error)(line);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
675
698
|
}
|
|
676
699
|
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href)
|
|
677
700
|
void main();
|
package/dist/out.js
CHANGED
|
@@ -81,5 +81,6 @@ export function usage() {
|
|
|
81
81
|
` ${c.blue("hd login --url URL --api-key KEY")} client setup`,
|
|
82
82
|
` ${c.blue("hd init --host HOST [options]")} host setup`,
|
|
83
83
|
` ${c.blue("hd upgrade --host HOST [options]")} refresh host setup`,
|
|
84
|
+
` ${c.blue("hd update")} install the latest CLI`,
|
|
84
85
|
].join("\n");
|
|
85
86
|
}
|
package/dist/tui/App.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Box, Static, Text, useApp, useInput, useStdout } from "ink";
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
|
+
import { pendingUpdateNotice, subscribeUpdateNotice } from "../update-check.js";
|
|
5
6
|
import { epicProgressRows } from "../epics.js";
|
|
6
7
|
import { promptOnStdin } from "../prompt.js";
|
|
7
8
|
import { ticketNew } from "../ticket-commands.js";
|
|
@@ -35,7 +36,7 @@ import { WorkspaceLoads } from "./workspace-load.js";
|
|
|
35
36
|
let messageSeq = 0;
|
|
36
37
|
const nextId = () => `m${messageSeq++}`;
|
|
37
38
|
const tuiPrompt = (question) => promptOnStdin(question, true);
|
|
38
|
-
export function App({ initial }) {
|
|
39
|
+
export function App({ initial, updateNotice = null }) {
|
|
39
40
|
const { exit, suspendTerminal } = useApp();
|
|
40
41
|
const { stdout } = useStdout();
|
|
41
42
|
const columns = stdout?.columns && stdout.columns > 0 ? stdout.columns : 80;
|
|
@@ -55,6 +56,7 @@ export function App({ initial }) {
|
|
|
55
56
|
const [draft, setDraft] = useState("");
|
|
56
57
|
const [busy, setBusy] = useState(false);
|
|
57
58
|
const [notice, setNotice] = useState(null);
|
|
59
|
+
const [updateLine, setUpdateLine] = useState(updateNotice);
|
|
58
60
|
const [ticketKey, setTicketKey] = useState(null);
|
|
59
61
|
const [ticketOffset, setTicketOffset] = useState(0);
|
|
60
62
|
const [roadmapOffset, setRoadmapOffset] = useState(0);
|
|
@@ -83,6 +85,10 @@ export function App({ initial }) {
|
|
|
83
85
|
const acknowledgedMessages = useRef(new Set());
|
|
84
86
|
const loads = useRef(new WorkspaceLoads(initial.workspace.id));
|
|
85
87
|
editingRef.current = editing;
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
setUpdateLine((current) => current ?? pendingUpdateNotice());
|
|
90
|
+
return subscribeUpdateNotice(setUpdateLine);
|
|
91
|
+
}, []);
|
|
86
92
|
useEffect(() => {
|
|
87
93
|
if (messages.length > 0 || view !== "home")
|
|
88
94
|
setStarted(true);
|
|
@@ -877,7 +883,7 @@ export function App({ initial }) {
|
|
|
877
883
|
return _jsx(Bubble, { message: item.message, width: width }, item.key);
|
|
878
884
|
} }), splash ? (_jsx(Splash, { columns: columns, rows: rows, width: width, ready: ready, helpFull: plan.helpFull, animate: plan.fits, onDone: () => setReady(true) })) : null, _jsxs(Box, { flexDirection: "column", width: width, children: [view === "board" && plan.panels > 0 ? _jsx(BoardPanel, { board: board, width: width, rows: plan.panels, cursor: cursor }) : null, view === "roadmap" && plan.panels > 0 ? (_jsx(RoadmapPanel, { board: board, width: width, rows: plan.panels, offset: roadmapOffset })) : null, view === "agents" && plan.panels > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(AgentsPanel, { board: board, width: width, rows: agentsView.top }), agentsView.bottom > 0 ? _jsxs(_Fragment, { children: [_jsx(Box, { height: 1 }), _jsx(StreamPanel, { lines: stream, width: width, rows: agentsView.bottom, live: running > 0 })] }) : null] })) : null, view === "feed" && plan.panels > 0 ? _jsx(FeedPanel, { entries: feed, width: width, rows: plan.panels }) : null, view === "settings" && plan.panels > 0 ? _jsx(SettingsPanel, { entries: settings, width: width, rows: plan.panels, title: "Settings", cursor: field, editing: editing }) : null, view === "inbox" && plan.panels > 0 ? _jsx(InboxPanel, { board: board, width: width, rows: plan.panels, focus: inboxFocus, selectedId: selectedDecisionId, answeringId: answering }) : null, view === "ticket" && plan.panels > 0 ? ticket
|
|
879
885
|
? _jsx(TicketPanel, { ticket: ticket, width: width, rows: plan.panels, offset: ticketOffset, collapsed: ticketCollapsed })
|
|
880
|
-
: _jsxs(Text, { color: UI.warn, children: ["No ticket ", ticketKey, " here."] }) : null, view === "home" && plan.cockpit > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Cockpit, { board: board, width: width, rows: plan.cockpit, cursor: cursor }), _jsx(Box, { height: 1 }), _jsx(StreamPanel, { lines: stream, width: width, rows: plan.stream, live: running > 0 })] })) : null, chatting && plan.panels > 0 && activeThread ? (_jsx(ChatPanel, { thread: activeThread, width: width, rows: plan.panels, offset: chatOffset, label: chatLabel, now: now })) : null, chatting ? null : _jsx(DecisionPanel, { decisions: decisions, board: board, width: width, rows: plan.decision, selectedId: selectedDecisionId }), notice ? _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: UI.warn, children: notice }) }) : null, _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: UI.text, bold: true, children: workspace.slug }), _jsxs(Text, { color: UI.dim, children: [" ", workspace.repo, " "] }), _jsx(Text, { color: live === "live" ? UI.accent : UI.dim, children: "\u25CF " }), _jsxs(Text, { color: UI.dim, children: [live === "live" ? "5s poll" : live, " "] }), _jsx(Text, { color: UI.dim, children: running ? `${running} running ` : "" }), board.decisions.length ? _jsxs(Text, { color: UI.warn, children: [board.decisions.length, " decisions "] }) : null, workspace.paused ? _jsx(Text, { color: UI.warn, children: "paused " }) : null, logsFilter || rawLogs ? _jsxs(Text, { color: UI.warn, children: ["logs ", rawLogs ? "raw " : "", logsFilter ?? "all", " "] }) : null, _jsxs(Text, { color: UI.dim, wrap: "truncate", children: ["\u00B7 ", chatting ? chatLabel : mode, answering ? " esc cancels" : chatting ? " ↑↓ scroll · pgup/pgdn · esc hides" : view === "ticket" ? " ↑↓ scroll · pgup/pgdn · enter folds" : view === "roadmap" ? " ↑↓ scroll · pgup/pgdn" : view === "inbox" ? " ↑↓ decisions · enter answers" : cursor && selected ? ` ${selected.key} ↑↓ move · enter opens · esc leaves` : ""] })] }), _jsx(Box, { children: _jsx(TextInput, { value: draft, onChange: (next) => {
|
|
886
|
+
: _jsxs(Text, { color: UI.warn, children: ["No ticket ", ticketKey, " here."] }) : null, view === "home" && plan.cockpit > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Cockpit, { board: board, width: width, rows: plan.cockpit, cursor: cursor }), _jsx(Box, { height: 1 }), _jsx(StreamPanel, { lines: stream, width: width, rows: plan.stream, live: running > 0 })] })) : null, chatting && plan.panels > 0 && activeThread ? (_jsx(ChatPanel, { thread: activeThread, width: width, rows: plan.panels, offset: chatOffset, label: chatLabel, now: now })) : null, chatting ? null : _jsx(DecisionPanel, { decisions: decisions, board: board, width: width, rows: plan.decision, selectedId: selectedDecisionId }), notice ? _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: UI.warn, children: notice }) }) : null, _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: UI.text, bold: true, children: workspace.slug }), _jsxs(Text, { color: UI.dim, children: [" ", workspace.repo, " "] }), _jsx(Text, { color: live === "live" ? UI.accent : UI.dim, children: "\u25CF " }), _jsxs(Text, { color: UI.dim, children: [live === "live" ? "5s poll" : live, " "] }), _jsx(Text, { color: UI.dim, children: running ? `${running} running ` : "" }), board.decisions.length ? _jsxs(Text, { color: UI.warn, children: [board.decisions.length, " decisions "] }) : null, workspace.paused ? _jsx(Text, { color: UI.warn, children: "paused " }) : null, updateLine ? _jsxs(Text, { color: UI.warn, children: [updateLine, " "] }) : null, logsFilter || rawLogs ? _jsxs(Text, { color: UI.warn, children: ["logs ", rawLogs ? "raw " : "", logsFilter ?? "all", " "] }) : null, _jsxs(Text, { color: UI.dim, wrap: "truncate", children: ["\u00B7 ", chatting ? chatLabel : mode, answering ? " esc cancels" : chatting ? " ↑↓ scroll · pgup/pgdn · esc hides" : view === "ticket" ? " ↑↓ scroll · pgup/pgdn · enter folds" : view === "roadmap" ? " ↑↓ scroll · pgup/pgdn" : view === "inbox" ? " ↑↓ decisions · enter answers" : cursor && selected ? ` ${selected.key} ↑↓ move · enter opens · esc leaves` : ""] })] }), _jsx(Box, { children: _jsx(TextInput, { value: draft, onChange: (next) => {
|
|
881
887
|
setDraft(next);
|
|
882
888
|
if (editingRef.current)
|
|
883
889
|
setEditing({ key: editingRef.current.key, draft: next });
|
package/dist/tui/launch.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from "../config.js";
|
|
2
|
+
import { pendingUpdateNotice } from "../update-check.js";
|
|
2
3
|
import { canLaunchApp } from "./capability.js";
|
|
3
4
|
import { loadSnapshot } from "./data.js";
|
|
4
5
|
/** Opens HigherDEV. Shared by `hd` and by the end of `hd login`. */
|
|
@@ -15,6 +16,7 @@ export async function launchApp(slug) {
|
|
|
15
16
|
]);
|
|
16
17
|
const instance = render(React.createElement(App, {
|
|
17
18
|
initial,
|
|
19
|
+
updateNotice: pendingUpdateNotice(),
|
|
18
20
|
}), { exitOnCtrlC: false });
|
|
19
21
|
await instance.waitUntilExit();
|
|
20
22
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { configPath } from "./config.js";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
export const CLI_PACKAGE = "@higherdev/cli";
|
|
10
|
+
export const CLI_REGISTRY_LATEST = `https://registry.npmjs.org/${CLI_PACKAGE}/latest`;
|
|
11
|
+
export const UPDATE_CHECK_TIMEOUT_MS = 2_000;
|
|
12
|
+
export const UPDATE_USAGE = "usage: hd update";
|
|
13
|
+
const listeners = new Set();
|
|
14
|
+
let inFlight = Promise.resolve();
|
|
15
|
+
export function runningVersion() {
|
|
16
|
+
return require("../package.json").version;
|
|
17
|
+
}
|
|
18
|
+
export function updateCheckPath(configFile = configPath()) {
|
|
19
|
+
return join(dirname(configFile), "update-check.json");
|
|
20
|
+
}
|
|
21
|
+
/** Dotted numeric versions. Pre-release suffixes sort before a release. */
|
|
22
|
+
export function isNewer(candidate, current) {
|
|
23
|
+
const parts = (value) => value
|
|
24
|
+
.split("-")[0]
|
|
25
|
+
.split(".")
|
|
26
|
+
.map((part) => Number.parseInt(part, 10) || 0);
|
|
27
|
+
const a = parts(candidate);
|
|
28
|
+
const b = parts(current);
|
|
29
|
+
for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
|
|
30
|
+
const left = a[i] ?? 0;
|
|
31
|
+
const right = b[i] ?? 0;
|
|
32
|
+
if (left !== right)
|
|
33
|
+
return left > right;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
export function updateNoticeLine(latest, current) {
|
|
38
|
+
return `hd ${latest} is available, you have ${current}. Run hd update.`;
|
|
39
|
+
}
|
|
40
|
+
export function shouldUpdateCheck(opts = {}) {
|
|
41
|
+
const argv = opts.argv ?? [];
|
|
42
|
+
const env = opts.env ?? process.env;
|
|
43
|
+
if (argv.includes("--no-update-check"))
|
|
44
|
+
return false;
|
|
45
|
+
if (env.HD_NO_UPDATE_CHECK === "1")
|
|
46
|
+
return false;
|
|
47
|
+
if (argv.includes("--json"))
|
|
48
|
+
return false;
|
|
49
|
+
return (opts.isTTY ?? process.stdout.isTTY) === true;
|
|
50
|
+
}
|
|
51
|
+
export function readUpdateCheck(path = updateCheckPath()) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
54
|
+
if (typeof parsed.latest !== "string" || !parsed.latest)
|
|
55
|
+
return null;
|
|
56
|
+
return {
|
|
57
|
+
latest: parsed.latest,
|
|
58
|
+
checked_at: typeof parsed.checked_at === "number" ? parsed.checked_at : 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function writeUpdateCheck(state, path = updateCheckPath()) {
|
|
66
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
67
|
+
writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
68
|
+
}
|
|
69
|
+
export function pendingUpdateNotice(deps = {}) {
|
|
70
|
+
const stored = readUpdateCheck(deps.path ?? updateCheckPath());
|
|
71
|
+
const current = deps.version ?? runningVersion();
|
|
72
|
+
if (!stored || !isNewer(stored.latest, current))
|
|
73
|
+
return null;
|
|
74
|
+
return updateNoticeLine(stored.latest, current);
|
|
75
|
+
}
|
|
76
|
+
export function subscribeUpdateNotice(listener) {
|
|
77
|
+
listeners.add(listener);
|
|
78
|
+
return () => {
|
|
79
|
+
listeners.delete(listener);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function emitNotice(deps = {}) {
|
|
83
|
+
const notice = pendingUpdateNotice(deps);
|
|
84
|
+
for (const listener of listeners)
|
|
85
|
+
listener(notice);
|
|
86
|
+
}
|
|
87
|
+
export async function fetchLatest(deps = {}) {
|
|
88
|
+
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
89
|
+
const timeout = deps.timeoutMs ?? UPDATE_CHECK_TIMEOUT_MS;
|
|
90
|
+
// A real timer, not AbortSignal.timeout: on Node 22 that signal's timer does
|
|
91
|
+
// not keep the event loop alive, so a pending check could be abandoned with
|
|
92
|
+
// its promise unresolved (which is exactly what the CI test run reported).
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
const timer = setTimeout(() => controller.abort(new Error("update check timed out")), timeout);
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetchFn(CLI_REGISTRY_LATEST, { signal: controller.signal });
|
|
97
|
+
if (!response.ok)
|
|
98
|
+
return null;
|
|
99
|
+
const data = (await response.json());
|
|
100
|
+
return typeof data.version === "string" && data.version ? data.version : null;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Fire-and-forget registry check. Never throws. The process may stay up to 2s to store the result. */
|
|
110
|
+
export function beginUpdateCheck(deps = {}) {
|
|
111
|
+
inFlight = (async () => {
|
|
112
|
+
try {
|
|
113
|
+
const latest = await fetchLatest(deps);
|
|
114
|
+
if (latest)
|
|
115
|
+
writeUpdateCheck({ latest, checked_at: (deps.now ?? Date.now)() }, deps.path ?? updateCheckPath());
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Offline, timed out, or unwritable cache: the command still succeeds.
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
emitNotice(deps);
|
|
122
|
+
}
|
|
123
|
+
})();
|
|
124
|
+
return inFlight;
|
|
125
|
+
}
|
|
126
|
+
export function updateCheckDone() {
|
|
127
|
+
return inFlight;
|
|
128
|
+
}
|
|
129
|
+
async function defaultInstall() {
|
|
130
|
+
await execFileAsync("npm", ["i", "-g", `${CLI_PACKAGE}@latest`]);
|
|
131
|
+
}
|
|
132
|
+
async function defaultVersionAfter() {
|
|
133
|
+
try {
|
|
134
|
+
const { stdout } = await execFileAsync("npm", ["view", CLI_PACKAGE, "version"]);
|
|
135
|
+
const version = stdout.trim();
|
|
136
|
+
return version || runningVersion();
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return runningVersion();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export function relaunchHd() {
|
|
143
|
+
const child = spawn("hd", [], { stdio: "inherit" });
|
|
144
|
+
child.on("exit", (code, signal) => {
|
|
145
|
+
if (signal)
|
|
146
|
+
process.kill(process.pid, signal);
|
|
147
|
+
else
|
|
148
|
+
process.exit(code ?? 0);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
export async function runUpdate(argv, deps = {}) {
|
|
152
|
+
if (argv.length)
|
|
153
|
+
throw new Error(UPDATE_USAGE);
|
|
154
|
+
const before = deps.version ?? runningVersion();
|
|
155
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
156
|
+
const lines = [`hd ${before}`];
|
|
157
|
+
log(lines[0]);
|
|
158
|
+
await (deps.install ?? defaultInstall)();
|
|
159
|
+
const after = await (deps.versionAfter ?? defaultVersionAfter)();
|
|
160
|
+
lines.push(`hd ${after}`);
|
|
161
|
+
log(lines[1]);
|
|
162
|
+
if (deps.tuiRunning)
|
|
163
|
+
(deps.relaunch ?? relaunchHd)();
|
|
164
|
+
return lines;
|
|
165
|
+
}
|