herdr-remote 0.2.20 → 0.2.21
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/i18n/en.js +12 -0
- package/dist/i18n/zh.js +12 -0
- package/dist/relay-updater.js +81 -0
- package/dist/tui/App.js +34 -8
- package/dist/tui/api.js +4 -3
- package/dist/tui/screens/About.js +27 -12
- package/dist/tui/screens/Herdr.js +5 -4
- package/dist/tui/screens/Keepalive.js +8 -7
- package/dist/tui/screens/Pair.js +1 -1
- package/dist/tui/screens/Relay.js +9 -8
- package/dist/tui/screens/Services.js +4 -4
- package/dist/tui/screens/Wizard.js +5 -4
- package/dist/tui/screens/useRelayUpdate.js +122 -0
- package/dist/updater.js +7 -6
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
package/dist/i18n/en.js
CHANGED
|
@@ -219,5 +219,17 @@ const en = {
|
|
|
219
219
|
'update.mirrorBehind': '{registries} still has an older version; installing from {source}',
|
|
220
220
|
'update.cannot.source': 'Source checkout — update with git',
|
|
221
221
|
'update.cannot.linked': 'Linked package — managed by npm link',
|
|
222
|
+
'relayUpdate.check': 'Relay {version}: check for updates',
|
|
223
|
+
'relayUpdate.checking': 'Relay: checking npm…',
|
|
224
|
+
'relayUpdate.upToDate': 'Relay up to date ({version})',
|
|
225
|
+
'relayUpdate.available': 'Relay {latest} available, installed {installed} (press Enter to install)',
|
|
226
|
+
'relayUpdate.needsCli': 'Relay {latest} needs a newer herdr-remote: update herdr-remote first',
|
|
227
|
+
'relayUpdate.updating': 'Installing relay {version}…',
|
|
228
|
+
'relayUpdate.updatingRetry': 'Installing relay {version}… (npm has not caught up yet, attempt {attempt})',
|
|
229
|
+
'relayUpdate.restart': 'Relay {version} installed, {running} still running (press Enter to restart)',
|
|
230
|
+
'relayUpdate.restarting': 'Restarting herdr-remote…',
|
|
231
|
+
'relayUpdate.restarted': 'herdr-remote restarted; the relay now runs {version}.',
|
|
232
|
+
'relayUpdate.errorNotApplied': 'npm finished, but relay {installed} is still installed',
|
|
233
|
+
'relayUpdate.banner': 'Relay {latest} is out (installed {current}). Press 7 and Enter on the relay row to install.',
|
|
222
234
|
};
|
|
223
235
|
export default en;
|
package/dist/i18n/zh.js
CHANGED
|
@@ -218,5 +218,17 @@ const zh = {
|
|
|
218
218
|
'update.mirrorBehind': '{registries} 仍是旧版本,将从 {source} 安装',
|
|
219
219
|
'update.cannot.source': '源码目录运行,请使用 git 更新',
|
|
220
220
|
'update.cannot.linked': 'npm link 工作副本,由 npm link 管理',
|
|
221
|
+
'relayUpdate.check': 'Relay {version}:检查更新',
|
|
222
|
+
'relayUpdate.checking': 'Relay:查询 npm 中…',
|
|
223
|
+
'relayUpdate.upToDate': 'Relay 已是最新({version})',
|
|
224
|
+
'relayUpdate.available': 'Relay 新版本 {latest} 可用,当前 {installed}(按 Enter 安装)',
|
|
225
|
+
'relayUpdate.needsCli': 'Relay {latest} 需要更新 herdr-remote,请先更新上一行',
|
|
226
|
+
'relayUpdate.updating': '安装 relay {version} 中…',
|
|
227
|
+
'relayUpdate.updatingRetry': '安装 relay {version} 中…(npm 尚未同步,第 {attempt} 次尝试)',
|
|
228
|
+
'relayUpdate.restart': 'Relay {version} 已安装,运行中的仍是 {running}(按 Enter 重启生效)',
|
|
229
|
+
'relayUpdate.restarting': '正在重启 herdr-remote…',
|
|
230
|
+
'relayUpdate.restarted': 'herdr-remote 已重启,relay 现在运行 {version}。',
|
|
231
|
+
'relayUpdate.errorNotApplied': 'npm 已完成,但安装的仍是 relay {installed}',
|
|
232
|
+
'relayUpdate.banner': 'Relay {latest} 已发布(当前 {current}),按 7 进入「语言与关于」,在 Relay 一行按 Enter 安装。',
|
|
221
233
|
};
|
|
222
234
|
export default zh;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Updating the relay a local or LAN workstation runs, on its own.
|
|
2
|
+
//
|
|
3
|
+
// The relay is a package of its own that herdr-remote installs. A release of
|
|
4
|
+
// the relay alone leaves herdr-remote's version where it was, so the update
|
|
5
|
+
// check for herdr-remote says "up to date" while the relay on disk falls
|
|
6
|
+
// behind. This checks the relay by itself, and installs the newest relay that
|
|
7
|
+
// this herdr-remote's dependency range allows.
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import { PACKAGE_ROOT } from './paths.js';
|
|
12
|
+
import { checkForUpdate, compareVersions, currentVersion, performUpdate, } from './updater.js';
|
|
13
|
+
const RELAY_PACKAGE = 'herdr-remote-relay';
|
|
14
|
+
/** The relay installed under this herdr-remote, read from disk: an update moves it. */
|
|
15
|
+
export function installedRelayVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const manifest = createRequire(import.meta.url).resolve(`${RELAY_PACKAGE}/package.json`);
|
|
18
|
+
return JSON.parse(fs.readFileSync(manifest, 'utf8')).version;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The relay versions this herdr-remote declares it works with, e.g. `^0.3.9`. */
|
|
25
|
+
function relayRange() {
|
|
26
|
+
try {
|
|
27
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
28
|
+
return manifest.dependencies?.[RELAY_PACKAGE] ?? null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether `version` is inside a caret range, the only kind herdr-remote
|
|
36
|
+
* declares. Below 1.0 a caret holds the minor version (`^0.3.9` is `0.3.x`
|
|
37
|
+
* from `0.3.9`), which is exactly the line a relay must not cross alone.
|
|
38
|
+
*/
|
|
39
|
+
export function withinCaretRange(version, range) {
|
|
40
|
+
const match = /^\^(\d+)\.(\d+)\.(\d+)$/.exec(range.trim());
|
|
41
|
+
if (!match)
|
|
42
|
+
return false;
|
|
43
|
+
const floor = `${match[1]}.${match[2]}.${match[3]}`;
|
|
44
|
+
const [major, minor] = version.split('.').map((part) => Number.parseInt(part, 10));
|
|
45
|
+
const [floorMajor, floorMinor] = [Number(match[1]), Number(match[2])];
|
|
46
|
+
if (compareVersions(version, floor) < 0 || major !== floorMajor)
|
|
47
|
+
return false;
|
|
48
|
+
return floorMajor > 0 || minor === floorMinor;
|
|
49
|
+
}
|
|
50
|
+
/** Whether a newer relay than the installed one is out, and whether this herdr-remote can take it. */
|
|
51
|
+
export async function checkForRelayUpdate(options = {}) {
|
|
52
|
+
const installed = installedRelayVersion();
|
|
53
|
+
const result = await checkForUpdate({
|
|
54
|
+
...options,
|
|
55
|
+
packageName: RELAY_PACKAGE,
|
|
56
|
+
current: installed ?? '0.0.0',
|
|
57
|
+
});
|
|
58
|
+
if (!result.ok || !result.updateAvailable || !result.latest)
|
|
59
|
+
return result;
|
|
60
|
+
const range = relayRange();
|
|
61
|
+
if (range && !withinCaretRange(result.latest, range)) {
|
|
62
|
+
return { ...result, updateAvailable: false, needsNewerCli: true };
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Install the newest relay this herdr-remote allows: the same herdr-remote,
|
|
68
|
+
* reinstalled, which npm resolves afresh, relay included. The installed
|
|
69
|
+
* herdr-remote does not change, and neither does anything it was tested with
|
|
70
|
+
* but the relay. Success is the relay on disk, not npm's exit code.
|
|
71
|
+
*/
|
|
72
|
+
export async function performRelayUpdate({ relayVersion, readRelayVersion = installedRelayVersion, ...options }) {
|
|
73
|
+
const result = await performUpdate({ ...options, version: currentVersion() });
|
|
74
|
+
if (!result.ok)
|
|
75
|
+
return result;
|
|
76
|
+
const installed = readRelayVersion();
|
|
77
|
+
if (!installed || compareVersions(installed, relayVersion) < 0) {
|
|
78
|
+
return { ok: false, errorKey: 'relayUpdate.errorNotApplied', installed, output: result.output };
|
|
79
|
+
}
|
|
80
|
+
return { ...result, installed };
|
|
81
|
+
}
|
package/dist/tui/App.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
3
3
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
4
|
import { useMouse, useMouseTarget } from './mouse/index.js';
|
|
5
5
|
import { theme } from './theme.js';
|
|
6
|
-
import { checkForUpdate, createDraft, createTranslator, detectLocale, ensureRuntime, fullStatus, loadConfig, requiresRestart, updateChecksEnabled, } from './api.js';
|
|
6
|
+
import { checkForRelayUpdate, checkForUpdate, createDraft, createTranslator, detectLocale, ensureRuntime, fullStatus, loadConfig, requiresRestart, runsLocalRelay, updateChecksEnabled, } from './api.js';
|
|
7
7
|
import { Overview } from './screens/Overview.js';
|
|
8
8
|
import { PairScreen } from './screens/Pair.js';
|
|
9
9
|
import { Services } from './screens/Services.js';
|
|
@@ -30,7 +30,7 @@ function Tab({ label, active, onSelect, }) {
|
|
|
30
30
|
function Footer({ hints }) {
|
|
31
31
|
return (_jsx(Box, { marginTop: 1, paddingX: 1, children: _jsx(Text, { color: theme.muted, children: hints.join(' · ') }) }));
|
|
32
32
|
}
|
|
33
|
-
export function App({ initialLanguage, needsWizard, updateChecker = updateChecksEnabled() ? checkForUpdate : null, }) {
|
|
33
|
+
export function App({ initialLanguage, needsWizard, updateChecker = updateChecksEnabled() ? checkForUpdate : null, relayUpdateChecker = updateChecksEnabled() ? checkForRelayUpdate : null, }) {
|
|
34
34
|
const { exit } = useApp();
|
|
35
35
|
const mouse = useMouse();
|
|
36
36
|
const [config, setConfig] = useState(() => loadConfig());
|
|
@@ -38,11 +38,13 @@ export function App({ initialLanguage, needsWizard, updateChecker = updateChecks
|
|
|
38
38
|
const [runtime, setRuntime] = useState(() => ensureRuntime());
|
|
39
39
|
const [status, setStatus] = useState(null);
|
|
40
40
|
const [tab, setTab] = useState('overview');
|
|
41
|
-
const [
|
|
41
|
+
const [notice, setNotice] = useState(null);
|
|
42
42
|
const [busy, setBusy] = useState(false);
|
|
43
43
|
const [editingId, setEditingId] = useState(null);
|
|
44
44
|
const [wizardDone, setWizardDone] = useState(!needsWizard);
|
|
45
45
|
const [updateCheck, setUpdateCheck] = useState(null);
|
|
46
|
+
const [relayUpdateCheck, setRelayUpdateCheck] = useState(null);
|
|
47
|
+
const localRelay = runsLocalRelay(config);
|
|
46
48
|
// Every time the TUI opens: that is when someone is here to act on it.
|
|
47
49
|
// Failure says nothing; the About tab can still be asked by hand.
|
|
48
50
|
useEffect(() => {
|
|
@@ -59,13 +61,32 @@ export function App({ initialLanguage, needsWizard, updateChecker = updateChecks
|
|
|
59
61
|
cancelled = true;
|
|
60
62
|
};
|
|
61
63
|
}, [updateChecker]);
|
|
64
|
+
// A relay somewhere else is somebody else's to update.
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!relayUpdateChecker || !localRelay)
|
|
67
|
+
return undefined;
|
|
68
|
+
let cancelled = false;
|
|
69
|
+
relayUpdateChecker()
|
|
70
|
+
.then((result) => {
|
|
71
|
+
if (!cancelled && result?.ok)
|
|
72
|
+
setRelayUpdateCheck(result);
|
|
73
|
+
})
|
|
74
|
+
.catch(() => { });
|
|
75
|
+
return () => {
|
|
76
|
+
cancelled = true;
|
|
77
|
+
};
|
|
78
|
+
}, [relayUpdateChecker, localRelay]);
|
|
62
79
|
const locale = useMemo(() => detectLocale({ preference: initialLanguage ?? draft.ui.language }), [initialLanguage, draft.ui.language]);
|
|
63
80
|
const t = useMemo(() => createTranslator(locale), [locale]);
|
|
64
81
|
const dirty = useMemo(() => JSON.stringify(config) !== JSON.stringify(draft), [config, draft]);
|
|
65
82
|
const restartPending = useMemo(() => dirty && requiresRestart(config, draft), [config, draft, dirty]);
|
|
66
|
-
const notify = useCallback((
|
|
67
|
-
|
|
83
|
+
const notify = useCallback((next, level = 'info') => {
|
|
84
|
+
setNotice({ notice: next, level });
|
|
68
85
|
}, []);
|
|
86
|
+
const message = notice && {
|
|
87
|
+
text: typeof notice.notice === 'string' ? notice.notice : notice.notice(t),
|
|
88
|
+
level: notice.level,
|
|
89
|
+
};
|
|
69
90
|
const refresh = useCallback(() => {
|
|
70
91
|
fullStatus(loadConfig())
|
|
71
92
|
.then((next) => setStatus(next))
|
|
@@ -132,6 +153,8 @@ export function App({ initialLanguage, needsWizard, updateChecker = updateChecks
|
|
|
132
153
|
message,
|
|
133
154
|
updateCheck,
|
|
134
155
|
setUpdateCheck,
|
|
156
|
+
relayUpdateCheck,
|
|
157
|
+
setRelayUpdateCheck,
|
|
135
158
|
};
|
|
136
159
|
useInput((input, key) => {
|
|
137
160
|
if (key.ctrl && input === 'c') {
|
|
@@ -143,13 +166,13 @@ export function App({ initialLanguage, needsWizard, updateChecker = updateChecks
|
|
|
143
166
|
return;
|
|
144
167
|
}
|
|
145
168
|
if (input === 'r') {
|
|
146
|
-
|
|
169
|
+
setNotice(null);
|
|
147
170
|
refresh();
|
|
148
171
|
return;
|
|
149
172
|
}
|
|
150
173
|
if (input === 'm') {
|
|
151
174
|
if (!mouse.supported) {
|
|
152
|
-
notify(t('hint.mouseUnsupported'), 'info');
|
|
175
|
+
notify((t) => t('hint.mouseUnsupported'), 'info');
|
|
153
176
|
return;
|
|
154
177
|
}
|
|
155
178
|
mouseChoice.current = !mouse.enabled;
|
|
@@ -191,5 +214,8 @@ export function App({ initialLanguage, needsWizard, updateChecker = updateChecks
|
|
|
191
214
|
: t('hint.mouseUnsupported'),
|
|
192
215
|
t('hint.quit'),
|
|
193
216
|
].filter(Boolean);
|
|
194
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: t('app.name') }), _jsx(Text, { color: theme.muted, children: ` ${t('app.tagline')}` })] }), updateCheck?.updateAvailable ? (_jsx(Text, { color: theme.warn, children: t('update.banner', { latest: updateCheck.latest ?? '', current: updateCheck.current }) })) : null
|
|
217
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: t('app.name') }), _jsx(Text, { color: theme.muted, children: ` ${t('app.tagline')}` })] }), updateCheck?.updateAvailable ? (_jsx(Text, { color: theme.warn, children: t('update.banner', { latest: updateCheck.latest ?? '', current: updateCheck.current }) })) : null, localRelay && relayUpdateCheck?.updateAvailable ? (_jsx(Text, { color: theme.warn, children: t('relayUpdate.banner', {
|
|
218
|
+
latest: relayUpdateCheck.latest ?? '',
|
|
219
|
+
current: relayUpdateCheck.current,
|
|
220
|
+
}) })) : null] }), _jsx(Box, { marginBottom: 1, children: TABS.map((entry, entryIndex) => (_jsx(Tab, { label: `${entryIndex + 1} ${t(entry.labelKey)}`, active: entry.id === tab, onSelect: () => setTab(entry.id) }, entry.id))) }), tab === 'overview' ? _jsx(Overview, { ctx: context }) : null, tab === 'pair' ? _jsx(PairScreen, { ctx: context }) : null, tab === 'services' ? _jsx(Services, { ctx: context }) : null, tab === 'relay' ? _jsx(RelayScreen, { ctx: context }) : null, tab === 'keepalive' ? _jsx(Keepalive, { ctx: context }) : null, tab === 'herdr' ? _jsx(HerdrScreen, { ctx: context }) : null, tab === 'about' ? _jsx(About, { ctx: context }) : null, restartPending ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.warn, children: t('hint.restartRequired') }) })) : null, _jsx(Footer, { hints: hints })] }));
|
|
195
221
|
}
|
package/dist/tui/api.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// single surface to call.
|
|
6
6
|
import { OFFICIAL_RELAY_URL, configExists, loadConfig } from '../config.js';
|
|
7
7
|
import { configPath, stateDir } from '../paths.js';
|
|
8
|
-
import { bindAddress, resolveAdminOrigin } from '../relay-urls.js';
|
|
8
|
+
import { bindAddress, resolveAdminOrigin, runsLocalRelay } from '../relay-urls.js';
|
|
9
9
|
import { MIN_HERDR_VERSION, herdrVersion } from '../herdr-command.js';
|
|
10
10
|
import { createTranslator, detectLocale } from '../i18n/index.js';
|
|
11
11
|
import { listReachableAddresses } from '../net-interfaces.js';
|
|
@@ -15,8 +15,9 @@ import { ensureRuntime, regenerateHostIdentity, setRelayPassword } from '../runt
|
|
|
15
15
|
import { pair, readLogTail } from '../service.js';
|
|
16
16
|
import { fullStatus, restartAll, startAll, stopAll } from '../lifecycle.js';
|
|
17
17
|
import * as keepalive from '../keepalive/index.js';
|
|
18
|
-
import { canSelfUpdate, checkForUpdate, currentVersion, updateChecksEnabled, installKind, performUpdate, } from '../updater.js';
|
|
19
|
-
|
|
18
|
+
import { canSelfUpdate, checkForUpdate, compareVersions, currentVersion, updateChecksEnabled, installKind, performUpdate, } from '../updater.js';
|
|
19
|
+
import { checkForRelayUpdate, installedRelayVersion, performRelayUpdate, } from '../relay-updater.js';
|
|
20
|
+
export { detectLocale, listReachableAddresses, MIN_HERDR_VERSION, OFFICIAL_RELAY_URL, isOfficialRelay, selectedMode, canSelfUpdate, checkForUpdate, compareVersions, currentVersion, updateChecksEnabled, installKind, performUpdate, checkForRelayUpdate, installedRelayVersion, performRelayUpdate, runsLocalRelay, bindAddress, configExists, configPath, createDraft, createTranslator, ensureRuntime, extractPairingCode, fieldsForMode, fullStatus, getField, getFieldPlaceholder, herdrVersion, keepalive, loadConfig, pair, readLogTail, regenerateHostIdentity, requiresRestart, restartAll, saveDraft, setField, setRelayPassword, startAll, stateDir, stopAll, };
|
|
20
21
|
/** The command that starts a matching relay, ready to paste on the server. */
|
|
21
22
|
export function relayStartCommand(config, password) {
|
|
22
23
|
const publicUrl = config.relay.remoteUrl
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
4
|
import { theme } from '../theme.js';
|
|
5
|
+
import { useRelayUpdate } from './useRelayUpdate.js';
|
|
5
6
|
import { Message, Panel, Row, Selectable } from '../components/common.js';
|
|
6
7
|
import { canSelfUpdate, checkForUpdate, configPath, currentVersion, detectLocale, installKind, performUpdate, saveDraft, setField, stateDir, } from '../api.js';
|
|
7
8
|
/** `https://registry.npmmirror.com` → `registry.npmmirror.com`, for one line of text. */
|
|
@@ -42,7 +43,12 @@ export function About({ ctx }) {
|
|
|
42
43
|
return t('update.check');
|
|
43
44
|
}
|
|
44
45
|
})();
|
|
45
|
-
const
|
|
46
|
+
const relay = useRelayUpdate(ctx);
|
|
47
|
+
const options = [
|
|
48
|
+
...languageOptions,
|
|
49
|
+
{ id: 'update', label: updateLabel },
|
|
50
|
+
...(relay.visible ? [{ id: 'relay-update', label: relay.label }] : []),
|
|
51
|
+
];
|
|
46
52
|
/** Show a check's answer, whether this screen asked or the TUI did on opening. */
|
|
47
53
|
const applyCheck = (result) => {
|
|
48
54
|
checkRef.current = { registry: result.registry ?? '', sources: result.sources ?? [] };
|
|
@@ -50,12 +56,12 @@ export function About({ ctx }) {
|
|
|
50
56
|
// answer. It is still asked, and said to be behind.
|
|
51
57
|
const behind = result.behind ?? [];
|
|
52
58
|
setSourceNote(result.updateAvailable && behind.length > 0 && result.registry
|
|
53
|
-
?
|
|
59
|
+
? {
|
|
54
60
|
registries: behind
|
|
55
61
|
.map((entry) => `${registryHost(entry.registry)} ${entry.version ?? ''}`.trim())
|
|
56
62
|
.join(', '),
|
|
57
63
|
source: registryHost(result.registry),
|
|
58
|
-
}
|
|
64
|
+
}
|
|
59
65
|
: null);
|
|
60
66
|
setUpdate(result.updateAvailable
|
|
61
67
|
? { phase: 'available', latest: result.latest }
|
|
@@ -76,8 +82,9 @@ export function About({ ctx }) {
|
|
|
76
82
|
// Which registries were tried, and what each of them said. Without this
|
|
77
83
|
// the row reads "could not reach npm registry" on a machine where
|
|
78
84
|
// `npm install` works perfectly, and there is nothing to act on.
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
const { message } = result;
|
|
86
|
+
if (message)
|
|
87
|
+
ctx.notify((t) => t('update.errorNetworkDetail', { message }), 'error');
|
|
81
88
|
return;
|
|
82
89
|
}
|
|
83
90
|
ctx.setUpdateCheck(result);
|
|
@@ -95,7 +102,7 @@ export function About({ ctx }) {
|
|
|
95
102
|
const params = { version: latest, installed: result.installed ?? '' };
|
|
96
103
|
setUpdate({ phase: 'error', messageKey: result.errorKey ?? 'update.errorFailed', params });
|
|
97
104
|
// npm's own words: "update failed" alone is what left this unfixable.
|
|
98
|
-
ctx.notify(result.summary
|
|
105
|
+
ctx.notify((t) => result.summary
|
|
99
106
|
? t('update.errorFailedDetail', { message: result.summary })
|
|
100
107
|
: t(result.errorKey ?? 'update.errorFailed', params), 'error');
|
|
101
108
|
return;
|
|
@@ -104,7 +111,7 @@ export function About({ ctx }) {
|
|
|
104
111
|
setSourceNote(null);
|
|
105
112
|
// Installed: the line under the title has nothing left to announce.
|
|
106
113
|
ctx.setUpdateCheck(null);
|
|
107
|
-
ctx.notify(t('update.restartHint'), 'success');
|
|
114
|
+
ctx.notify((t) => t('update.restartHint'), 'success');
|
|
108
115
|
};
|
|
109
116
|
/**
|
|
110
117
|
* One row, two meanings: check first, then install what the check found.
|
|
@@ -126,21 +133,26 @@ export function About({ ctx }) {
|
|
|
126
133
|
};
|
|
127
134
|
const choose = (language) => {
|
|
128
135
|
const result = setField(draft, 'language', language);
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
const { errorKey } = result;
|
|
137
|
+
if (errorKey) {
|
|
138
|
+
ctx.notify((t) => t(errorKey), 'error');
|
|
131
139
|
return;
|
|
132
140
|
}
|
|
133
141
|
ctx.updateDraft(result.draft);
|
|
134
142
|
try {
|
|
135
143
|
saveDraft(result.draft);
|
|
136
144
|
ctx.reloadConfig();
|
|
137
|
-
ctx.notify(t('common.saved', { path: configPath() }), 'success');
|
|
145
|
+
ctx.notify((t) => t('common.saved', { path: configPath() }), 'success');
|
|
138
146
|
}
|
|
139
147
|
catch (error) {
|
|
140
|
-
ctx.notify(t('error.saveFailed', { message: error.message }), 'error');
|
|
148
|
+
ctx.notify((t) => t('error.saveFailed', { message: error.message }), 'error');
|
|
141
149
|
}
|
|
142
150
|
};
|
|
143
151
|
const activate = (id) => {
|
|
152
|
+
if (id === 'relay-update') {
|
|
153
|
+
relay.activate();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
144
156
|
if (id === 'update') {
|
|
145
157
|
activateUpdate();
|
|
146
158
|
return;
|
|
@@ -162,5 +174,8 @@ export function About({ ctx }) {
|
|
|
162
174
|
}, onHover: () => setSelected(option.id), children: `${option.label}${draft.ui.language === option.id ? ' ✓' : ''}` }, option.id))) }), _jsx(Text, { color: theme.muted, children: t('update.title') }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [_jsx(Selectable, { selected: selected === 'update', onSelect: () => {
|
|
163
175
|
setSelected('update');
|
|
164
176
|
activateUpdate();
|
|
165
|
-
}, onHover: () => setSelected('update'), children: updateLabel }), sourceNote ? _jsx(Text, { color: theme.muted, children: ` ${
|
|
177
|
+
}, onHover: () => setSelected('update'), children: updateLabel }), sourceNote ? (_jsx(Text, { color: theme.muted, children: ` ${t('update.mirrorBehind', sourceNote)}` })) : null, relay.visible ? (_jsx(Selectable, { selected: selected === 'relay-update', onSelect: () => {
|
|
178
|
+
setSelected('relay-update');
|
|
179
|
+
relay.activate();
|
|
180
|
+
}, onHover: () => setSelected('relay-update'), children: relay.label })) : null] }), _jsx(Row, { label: t('about.version'), children: _jsx(Text, { color: theme.muted, children: currentVersion() }) }), relay.visible ? (_jsx(Row, { label: t('about.relayPackage'), children: _jsx(Text, { color: theme.muted, children: relay.installed ?? '?' }) })) : null, _jsx(Row, { label: t('about.configPath'), children: _jsx(Text, { color: theme.muted, children: configPath() }) }), _jsx(Row, { label: t('about.statePath'), children: _jsx(Text, { color: theme.muted, children: stateDir() }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: t('about.docs') }) }), _jsx(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? 'info' })] }));
|
|
166
181
|
}
|
|
@@ -20,8 +20,9 @@ export function HerdrScreen({ ctx }) {
|
|
|
20
20
|
];
|
|
21
21
|
const applyField = (id, value) => {
|
|
22
22
|
const result = setField(draft, id, value);
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const { errorKey } = result;
|
|
24
|
+
if (errorKey) {
|
|
25
|
+
ctx.notify((t) => t(errorKey), 'error');
|
|
25
26
|
return false;
|
|
26
27
|
}
|
|
27
28
|
ctx.updateDraft(result.draft);
|
|
@@ -31,10 +32,10 @@ export function HerdrScreen({ ctx }) {
|
|
|
31
32
|
try {
|
|
32
33
|
const result = saveDraft(draft);
|
|
33
34
|
ctx.reloadConfig();
|
|
34
|
-
ctx.notify(t('common.saved', { path: result.path }), 'success');
|
|
35
|
+
ctx.notify((t) => t('common.saved', { path: result.path }), 'success');
|
|
35
36
|
}
|
|
36
37
|
catch (error) {
|
|
37
|
-
ctx.notify(t('error.saveFailed', { message: error.message }), 'error');
|
|
38
|
+
ctx.notify((t) => t('error.saveFailed', { message: error.message }), 'error');
|
|
38
39
|
}
|
|
39
40
|
};
|
|
40
41
|
const activate = (id) => {
|
|
@@ -27,22 +27,22 @@ export function Keepalive({ ctx }) {
|
|
|
27
27
|
ctx.run(() => {
|
|
28
28
|
if (id === 'install') {
|
|
29
29
|
const result = keepalive.install(ctx.config);
|
|
30
|
-
ctx.notify(t('keepalive.installed', { manager: result.manager }), 'success');
|
|
30
|
+
ctx.notify((t) => t('keepalive.installed', { manager: result.manager }), 'success');
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
if (id === 'uninstall') {
|
|
34
34
|
keepalive.uninstall(ctx.config);
|
|
35
|
-
ctx.notify(t('keepalive.uninstalled'), 'success');
|
|
35
|
+
ctx.notify((t) => t('keepalive.uninstalled'), 'success');
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
if (id === 'restart') {
|
|
39
39
|
keepalive.restart(ctx.config);
|
|
40
|
-
ctx.notify(t('keepalive.restarted'), 'success');
|
|
40
|
+
ctx.notify((t) => t('keepalive.restarted'), 'success');
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
if (id === 'linger') {
|
|
44
44
|
const result = keepalive.enableLinger();
|
|
45
|
-
ctx.notify(t('keepalive.lingerDone', { username: result.username }), 'success');
|
|
45
|
+
ctx.notify((t) => t('keepalive.lingerDone', { username: result.username }), 'success');
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
};
|
|
@@ -58,8 +58,9 @@ export function Keepalive({ ctx }) {
|
|
|
58
58
|
if (editingId === 'keepaliveManager') {
|
|
59
59
|
return (_jsx(Panel, { title: t('field.keepalive'), children: _jsx(ChoiceList, { options: MANAGERS.map((manager) => ({ id: manager, label: manager })), current: draft.keepalive.manager, onPick: (manager) => {
|
|
60
60
|
const result = setField(draft, 'keepaliveManager', manager);
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const { errorKey } = result;
|
|
62
|
+
if (errorKey) {
|
|
63
|
+
ctx.notify((t) => t(errorKey), 'error');
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
ctx.updateDraft(result.draft);
|
|
@@ -68,7 +69,7 @@ export function Keepalive({ ctx }) {
|
|
|
68
69
|
ctx.reloadConfig();
|
|
69
70
|
}
|
|
70
71
|
catch (error) {
|
|
71
|
-
ctx.notify(t('error.saveFailed', { message: error.message }), 'error');
|
|
72
|
+
ctx.notify((t) => t('error.saveFailed', { message: error.message }), 'error');
|
|
72
73
|
}
|
|
73
74
|
ctx.setEditing(null);
|
|
74
75
|
}, onCancel: () => ctx.setEditing(null) }) }));
|
package/dist/tui/screens/Pair.js
CHANGED
|
@@ -43,7 +43,7 @@ export function PairScreen({ ctx }) {
|
|
|
43
43
|
};
|
|
44
44
|
}, [pairing, columns]);
|
|
45
45
|
const generate = () => run(async () => {
|
|
46
|
-
ctx.notify(t('pair.working'));
|
|
46
|
+
ctx.notify((t) => t('pair.working'));
|
|
47
47
|
try {
|
|
48
48
|
const result = await pair();
|
|
49
49
|
const code = extractPairingCode(result);
|
|
@@ -65,8 +65,9 @@ export function RelayScreen({ ctx }) {
|
|
|
65
65
|
let next = draft;
|
|
66
66
|
for (const [id, value] of updates) {
|
|
67
67
|
const result = setField(next, id, value);
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
const { errorKey } = result;
|
|
69
|
+
if (errorKey) {
|
|
70
|
+
ctx.notify((t) => t(errorKey), 'error');
|
|
70
71
|
return false;
|
|
71
72
|
}
|
|
72
73
|
next = result.draft;
|
|
@@ -80,11 +81,11 @@ export function RelayScreen({ ctx }) {
|
|
|
80
81
|
try {
|
|
81
82
|
const result = saveDraft(draft);
|
|
82
83
|
ctx.reloadConfig();
|
|
83
|
-
ctx.notify(t('common.saved', { path: result.path }), 'success');
|
|
84
|
+
ctx.notify((t) => t('common.saved', { path: result.path }), 'success');
|
|
84
85
|
}
|
|
85
86
|
catch (error) {
|
|
86
87
|
const problems = error.problems;
|
|
87
|
-
ctx.notify(problems
|
|
88
|
+
ctx.notify((t) => problems
|
|
88
89
|
? problems.map((key) => t(key)).join(' ')
|
|
89
90
|
: t('error.saveFailed', { message: error.message }), 'error');
|
|
90
91
|
}
|
|
@@ -94,7 +95,7 @@ export function RelayScreen({ ctx }) {
|
|
|
94
95
|
if (!entry)
|
|
95
96
|
return;
|
|
96
97
|
if (entry.kind === 'readonly') {
|
|
97
|
-
ctx.notify(t('relay.officialFixed'), 'info');
|
|
98
|
+
ctx.notify((t) => t('relay.officialFixed'), 'info');
|
|
98
99
|
return;
|
|
99
100
|
}
|
|
100
101
|
if (entry.kind === 'field' || entry.kind === 'password') {
|
|
@@ -117,14 +118,14 @@ export function RelayScreen({ ctx }) {
|
|
|
117
118
|
ctx.run(() => {
|
|
118
119
|
regenerateHostIdentity();
|
|
119
120
|
ctx.reloadConfig();
|
|
120
|
-
ctx.notify(t('relay.regenerated'), 'success');
|
|
121
|
+
ctx.notify((t) => t('relay.regenerated'), 'success');
|
|
121
122
|
});
|
|
122
123
|
return;
|
|
123
124
|
}
|
|
124
125
|
if (id === 'test') {
|
|
125
126
|
ctx.run(async () => {
|
|
126
127
|
const result = await probeRelay(ctx.config);
|
|
127
|
-
ctx.notify(result.ok
|
|
128
|
+
ctx.notify((t) => result.ok
|
|
128
129
|
? t('relay.testOk', { version: result.version ?? '?', hosts: result.hosts ?? 0 })
|
|
129
130
|
: t('relay.testFailed', { message: result.message ?? '' }), result.ok ? 'success' : 'error');
|
|
130
131
|
});
|
|
@@ -193,7 +194,7 @@ export function RelayScreen({ ctx }) {
|
|
|
193
194
|
setPassword(value);
|
|
194
195
|
setRelayPassword(value);
|
|
195
196
|
ctx.setEditing(null);
|
|
196
|
-
ctx.notify(t('relay.passwordSaved'), 'success');
|
|
197
|
+
ctx.notify((t) => t('relay.passwordSaved'), 'success');
|
|
197
198
|
}, onCancel: () => ctx.setEditing(null) })) : entry.kind === 'readonly' ? (
|
|
198
199
|
// Fixed by us, so it is printed rather than offered for editing.
|
|
199
200
|
_jsx(Text, { color: theme.muted, children: `${getField(draft, entry.id) || placeholderText(ctx, entry.id)} (${t('relay.locked')})` })) : entry.fieldKind === 'choice' ? (
|
|
@@ -18,27 +18,27 @@ export function Services({ ctx }) {
|
|
|
18
18
|
// top of an unsaved draft silently applies the old settings — which reads
|
|
19
19
|
// as "I changed the relay and it never took effect". Stopping is unaffected.
|
|
20
20
|
if (ctx.dirty && id !== 'stop') {
|
|
21
|
-
ctx.notify(t('services.unsavedBlocked'), 'error');
|
|
21
|
+
ctx.notify((t) => t('services.unsavedBlocked'), 'error');
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
return run(() => {
|
|
25
25
|
const config = ctx.config;
|
|
26
26
|
if (id === 'start') {
|
|
27
27
|
const result = startAll(config);
|
|
28
|
-
ctx.notify(result.managed
|
|
28
|
+
ctx.notify((t) => result.managed
|
|
29
29
|
? t('services.managedNotice', { manager: result.manager ?? '' })
|
|
30
30
|
: t('services.started'), 'success');
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
if (id === 'stop') {
|
|
34
34
|
const result = stopAll(config);
|
|
35
|
-
ctx.notify(result.managed
|
|
35
|
+
ctx.notify((t) => result.managed
|
|
36
36
|
? t('services.managedNotice', { manager: result.manager ?? '' })
|
|
37
37
|
: t('services.stopped'), 'success');
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
const result = restartAll(config);
|
|
41
|
-
ctx.notify(result.managed
|
|
41
|
+
ctx.notify((t) => result.managed
|
|
42
42
|
? t('services.managedNotice', { manager: result.manager ?? '' })
|
|
43
43
|
: t('services.restarted'), 'success');
|
|
44
44
|
});
|
|
@@ -50,8 +50,9 @@ export function Wizard({ ctx, onDone }) {
|
|
|
50
50
|
let next = draft;
|
|
51
51
|
for (const [id, value] of updates) {
|
|
52
52
|
const result = setField(next, id, value);
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const { errorKey } = result;
|
|
54
|
+
if (errorKey) {
|
|
55
|
+
ctx.notify((t) => t(errorKey), 'error');
|
|
55
56
|
return false;
|
|
56
57
|
}
|
|
57
58
|
next = result.draft;
|
|
@@ -102,7 +103,7 @@ export function Wizard({ ctx, onDone }) {
|
|
|
102
103
|
catch (error) {
|
|
103
104
|
// Keep-alive is a convenience; a container without systemd should not
|
|
104
105
|
// block a working setup.
|
|
105
|
-
ctx.notify(t('keepalive.failed', { message: error.message }), 'error');
|
|
106
|
+
ctx.notify((t) => t('keepalive.failed', { message: error.message }), 'error');
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
if (startNow && !keepaliveInstalled)
|
|
@@ -173,7 +174,7 @@ export function Wizard({ ctx, onDone }) {
|
|
|
173
174
|
if (step === 'relayUrl') {
|
|
174
175
|
return (_jsxs(Panel, { title: t('wizard.relayTitle'), children: [header, _jsx(FieldRow, { label: t('wizard.relayUrlLabel'), selected: true, onSelect: () => { }, children: _jsx(TextField, { value: draft.relay.remoteUrl, placeholder: "wss://relay.example.com", active: true, onSubmit: (value) => {
|
|
175
176
|
if (!value) {
|
|
176
|
-
ctx.notify(t('error.remoteUrlRequired'), 'error');
|
|
177
|
+
ctx.notify((t) => t('error.remoteUrlRequired'), 'error');
|
|
177
178
|
return;
|
|
178
179
|
}
|
|
179
180
|
if (apply('remoteUrl', value))
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// The relay row on the About tab: check for, install and switch to a newer
|
|
2
|
+
// relay. Only a machine that runs its own relay has one to update.
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { canSelfUpdate, checkForRelayUpdate, compareVersions, currentVersion, installKind, installedRelayVersion, performRelayUpdate, restartAll, runsLocalRelay, } from '../api.js';
|
|
5
|
+
export function useRelayUpdate(ctx) {
|
|
6
|
+
const { t } = ctx;
|
|
7
|
+
const [state, setState] = useState({ phase: 'idle' });
|
|
8
|
+
const [source, setSource] = useState({
|
|
9
|
+
registry: '',
|
|
10
|
+
sources: [],
|
|
11
|
+
});
|
|
12
|
+
const visible = runsLocalRelay(ctx.config);
|
|
13
|
+
const installed = installedRelayVersion();
|
|
14
|
+
const health = ctx.status?.relay.health;
|
|
15
|
+
const running = typeof health?.version === 'string' ? health.version : null;
|
|
16
|
+
const busy = state.phase === 'checking' || state.phase === 'updating';
|
|
17
|
+
// Installed but not yet running: an update from here, or from npm by hand.
|
|
18
|
+
const restartNeeded = !busy &&
|
|
19
|
+
state.phase !== 'restarting' &&
|
|
20
|
+
!!installed &&
|
|
21
|
+
!!running &&
|
|
22
|
+
compareVersions(installed, running) > 0;
|
|
23
|
+
const applyCheck = (result) => {
|
|
24
|
+
setSource({ registry: result.registry ?? '', sources: result.sources ?? [] });
|
|
25
|
+
const latest = result.latest;
|
|
26
|
+
if (result.needsNewerCli)
|
|
27
|
+
setState({ phase: 'needsCli', latest });
|
|
28
|
+
else if (result.updateAvailable)
|
|
29
|
+
setState({ phase: 'available', latest });
|
|
30
|
+
else
|
|
31
|
+
setState({ phase: 'current', version: result.current });
|
|
32
|
+
};
|
|
33
|
+
// The TUI asked when it opened; start from that answer.
|
|
34
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: only a new answer from the opening check matters, not each render's applyCheck or phase
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (ctx.relayUpdateCheck?.ok && state.phase === 'idle')
|
|
37
|
+
applyCheck(ctx.relayUpdateCheck);
|
|
38
|
+
}, [ctx.relayUpdateCheck]);
|
|
39
|
+
const check = async () => {
|
|
40
|
+
setState({ phase: 'checking' });
|
|
41
|
+
const result = await checkForRelayUpdate();
|
|
42
|
+
if (!result.ok) {
|
|
43
|
+
setState({ phase: 'error', messageKey: result.errorKey ?? 'update.errorNetwork' });
|
|
44
|
+
const { message } = result;
|
|
45
|
+
if (message)
|
|
46
|
+
ctx.notify((t) => t('update.errorNetworkDetail', { message }), 'error');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
ctx.setRelayUpdateCheck(result);
|
|
50
|
+
applyCheck(result);
|
|
51
|
+
};
|
|
52
|
+
const install = async (latest) => {
|
|
53
|
+
setState({ phase: 'updating', latest, attempt: 1 });
|
|
54
|
+
const result = await performRelayUpdate({
|
|
55
|
+
...source,
|
|
56
|
+
relayVersion: latest,
|
|
57
|
+
onAttempt: ({ attempt }) => setState({ phase: 'updating', latest, attempt }),
|
|
58
|
+
});
|
|
59
|
+
if (!result.ok) {
|
|
60
|
+
const params = { version: currentVersion(), installed: result.installed ?? '' };
|
|
61
|
+
const messageKey = result.errorKey ?? 'update.errorFailed';
|
|
62
|
+
setState({ phase: 'error', messageKey, params });
|
|
63
|
+
const { summary } = result;
|
|
64
|
+
ctx.notify((t) => summary ? t('update.errorFailedDetail', { message: summary }) : t(messageKey, params), 'error');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
ctx.setRelayUpdateCheck(null);
|
|
68
|
+
setState({ phase: 'current', version: latest });
|
|
69
|
+
};
|
|
70
|
+
const restart = () => {
|
|
71
|
+
setState({ phase: 'restarting' });
|
|
72
|
+
ctx.run(() => {
|
|
73
|
+
restartAll(ctx.config);
|
|
74
|
+
const version = installed ?? '';
|
|
75
|
+
setState({ phase: 'current', version });
|
|
76
|
+
ctx.notify((t) => t('relayUpdate.restarted', { version }), 'success');
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
/** One row: restart onto a relay already installed, install one found, or check. */
|
|
80
|
+
const activate = () => {
|
|
81
|
+
if (busy || state.phase === 'restarting')
|
|
82
|
+
return;
|
|
83
|
+
if (restartNeeded) {
|
|
84
|
+
restart();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (state.phase === 'available') {
|
|
88
|
+
if (!canSelfUpdate()) {
|
|
89
|
+
setState({ phase: 'error', messageKey: `update.cannot.${installKind()}` });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
void install(state.latest);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
void check();
|
|
96
|
+
};
|
|
97
|
+
const label = (() => {
|
|
98
|
+
if (restartNeeded)
|
|
99
|
+
return t('relayUpdate.restart', { version: installed ?? '', running: running ?? '' });
|
|
100
|
+
switch (state.phase) {
|
|
101
|
+
case 'checking':
|
|
102
|
+
return t('relayUpdate.checking');
|
|
103
|
+
case 'current':
|
|
104
|
+
return t('relayUpdate.upToDate', { version: state.version });
|
|
105
|
+
case 'available':
|
|
106
|
+
return t('relayUpdate.available', { latest: state.latest, installed: installed ?? '?' });
|
|
107
|
+
case 'needsCli':
|
|
108
|
+
return t('relayUpdate.needsCli', { latest: state.latest });
|
|
109
|
+
case 'updating':
|
|
110
|
+
return state.attempt > 1
|
|
111
|
+
? t('relayUpdate.updatingRetry', { version: state.latest, attempt: state.attempt })
|
|
112
|
+
: t('relayUpdate.updating', { version: state.latest });
|
|
113
|
+
case 'restarting':
|
|
114
|
+
return t('relayUpdate.restarting');
|
|
115
|
+
case 'error':
|
|
116
|
+
return t(state.messageKey, state.params);
|
|
117
|
+
default:
|
|
118
|
+
return t('relayUpdate.check', { version: installed ?? '?' });
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
return { visible, installed, label, activate };
|
|
122
|
+
}
|
package/dist/updater.js
CHANGED
|
@@ -165,13 +165,13 @@ async function fetchJson(url, { timeoutMs, fetchImpl }) {
|
|
|
165
165
|
* format, which npmjs refuses on that path with a 406, so every check quietly
|
|
166
166
|
* fell through to a mirror that had not synced the release yet.
|
|
167
167
|
*/
|
|
168
|
-
async function askRegistry(registry, { timeoutMs, fetchImpl }) {
|
|
168
|
+
async function askRegistry(registry, { timeoutMs, fetchImpl, packageName, }) {
|
|
169
169
|
const endpoints = [
|
|
170
170
|
{
|
|
171
|
-
url: `${registry}/-/package/${
|
|
171
|
+
url: `${registry}/-/package/${packageName}/dist-tags`,
|
|
172
172
|
read: (body) => body?.latest,
|
|
173
173
|
},
|
|
174
|
-
{ url: `${registry}/${
|
|
174
|
+
{ url: `${registry}/${packageName}/latest`, read: (body) => body?.version },
|
|
175
175
|
];
|
|
176
176
|
let message = null;
|
|
177
177
|
for (const endpoint of endpoints) {
|
|
@@ -199,11 +199,12 @@ async function askRegistry(registry, { timeoutMs, fetchImpl }) {
|
|
|
199
199
|
* newest version, preferred order first, which is where an install should come
|
|
200
200
|
* from; `behind` lists the ones that do not, so the screen can say why.
|
|
201
201
|
*/
|
|
202
|
-
async function checkForUpdate({ timeoutMs = 6000, fetchImpl = globalThis.fetch, registries = registryCandidates(),
|
|
203
|
-
|
|
202
|
+
async function checkForUpdate({ timeoutMs = 6000, fetchImpl = globalThis.fetch, registries = registryCandidates(),
|
|
203
|
+
// Another package this one installs, and the version of it on disk: the relay.
|
|
204
|
+
packageName = PACKAGE_NAME, current = currentVersion(), } = {}) {
|
|
204
205
|
const attempts = await Promise.all(registries.map(async (registry) => ({
|
|
205
206
|
registry,
|
|
206
|
-
...(await askRegistry(registry, { timeoutMs, fetchImpl })),
|
|
207
|
+
...(await askRegistry(registry, { timeoutMs, fetchImpl, packageName })),
|
|
207
208
|
})));
|
|
208
209
|
const answers = attempts.filter((attempt) => attempt.ok);
|
|
209
210
|
if (answers.length === 0) {
|
package/herdr-plugin.toml
CHANGED