herdr-remote 0.2.15 → 0.2.17
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/config.example.json +2 -1
- package/dist/tui.mjs +294 -269
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/src/config.js +4 -0
- package/src/herdr-server.js +201 -0
- package/src/host-connector.js +172 -6
- package/src/i18n/en.js +9 -9
- package/src/i18n/zh.js +9 -9
- package/src/lifecycle.js +6 -2
- package/src/settings-model.js +11 -0
- package/src/socket-discovery.js +6 -1
- package/src/updater.js +203 -61
package/dist/tui.mjs
CHANGED
|
@@ -90,7 +90,10 @@ var require_config = __commonJS({
|
|
|
90
90
|
herdr: {
|
|
91
91
|
socketPath: null,
|
|
92
92
|
args: [],
|
|
93
|
-
cwd: os.homedir()
|
|
93
|
+
cwd: os.homedir(),
|
|
94
|
+
// Start Herdr's server when herdr-remote starts (at boot, under keep-alive).
|
|
95
|
+
// Off: Herdr starts when the user runs it, or confirms it from the WebUI.
|
|
96
|
+
autoStart: false
|
|
94
97
|
},
|
|
95
98
|
auth: {
|
|
96
99
|
pairingTtlMs: 10 * 60 * 1e3,
|
|
@@ -251,6 +254,7 @@ var require_config = __commonJS({
|
|
|
251
254
|
if (typeof config.herdr.cwd !== "string" || config.herdr.cwd.length === 0) {
|
|
252
255
|
config.herdr.cwd = os.homedir();
|
|
253
256
|
}
|
|
257
|
+
config.herdr.autoStart = config.herdr.autoStart === true;
|
|
254
258
|
if (config.relay.mode === "remote" && !config.relay.remoteUrl) {
|
|
255
259
|
config.relay.mode = "local";
|
|
256
260
|
}
|
|
@@ -411,6 +415,9 @@ var require_updater = __commonJS({
|
|
|
411
415
|
}
|
|
412
416
|
return "npm";
|
|
413
417
|
}
|
|
418
|
+
function updateChecksEnabled2(env = process.env) {
|
|
419
|
+
return env.HERDR_REMOTE_UPDATE_CHECK !== "0";
|
|
420
|
+
}
|
|
414
421
|
function canSelfUpdate2() {
|
|
415
422
|
return installKind2() === "npm";
|
|
416
423
|
}
|
|
@@ -462,92 +469,173 @@ var require_updater = __commonJS({
|
|
|
462
469
|
}
|
|
463
470
|
return 0;
|
|
464
471
|
}
|
|
465
|
-
async function
|
|
472
|
+
async function fetchJson(url, { timeoutMs, fetchImpl }) {
|
|
466
473
|
const controller = new AbortController();
|
|
467
474
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
468
475
|
try {
|
|
469
|
-
const response = await fetchImpl(
|
|
476
|
+
const response = await fetchImpl(url, {
|
|
470
477
|
signal: controller.signal,
|
|
471
|
-
headers: { Accept: "application/
|
|
478
|
+
headers: { Accept: "application/json", "Cache-Control": "no-cache" }
|
|
472
479
|
});
|
|
473
480
|
if (!response.ok) return { ok: false, message: `HTTP ${response.status ?? "?"}` };
|
|
474
|
-
|
|
475
|
-
const latest = typeof body?.version === "string" ? body.version : null;
|
|
476
|
-
if (!latest) return { ok: false, message: "registry answered without a version" };
|
|
477
|
-
return { ok: true, latest };
|
|
481
|
+
return { ok: true, body: await response.json() };
|
|
478
482
|
} catch (error) {
|
|
479
483
|
return { ok: false, message: error?.name === "AbortError" ? "timed out" : String(error?.message || error) };
|
|
480
484
|
} finally {
|
|
481
485
|
clearTimeout(timer);
|
|
482
486
|
}
|
|
483
487
|
}
|
|
488
|
+
async function askRegistry(registry, { timeoutMs, fetchImpl }) {
|
|
489
|
+
const endpoints = [
|
|
490
|
+
{ url: `${registry}/-/package/${PACKAGE_NAME}/dist-tags`, read: (body) => body?.latest },
|
|
491
|
+
{ url: `${registry}/${PACKAGE_NAME}/latest`, read: (body) => body?.version }
|
|
492
|
+
];
|
|
493
|
+
let message = null;
|
|
494
|
+
for (const endpoint of endpoints) {
|
|
495
|
+
const attempt = await fetchJson(endpoint.url, { timeoutMs, fetchImpl });
|
|
496
|
+
if (!attempt.ok) {
|
|
497
|
+
message = attempt.message;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
const latest = endpoint.read(attempt.body);
|
|
501
|
+
if (typeof latest === "string" && latest) return { ok: true, latest };
|
|
502
|
+
message = "registry answered without a version";
|
|
503
|
+
}
|
|
504
|
+
return { ok: false, message };
|
|
505
|
+
}
|
|
484
506
|
async function checkForUpdate2({
|
|
485
507
|
timeoutMs = 6e3,
|
|
486
508
|
fetchImpl = globalThis.fetch,
|
|
487
509
|
registries = registryCandidates()
|
|
488
510
|
} = {}) {
|
|
489
511
|
const current = currentVersion();
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
|
|
512
|
+
const attempts = await Promise.all(registries.map(async (registry) => ({
|
|
513
|
+
registry,
|
|
514
|
+
...await askRegistry(registry, { timeoutMs, fetchImpl })
|
|
515
|
+
})));
|
|
516
|
+
const answers = attempts.filter((attempt) => attempt.ok);
|
|
517
|
+
if (answers.length === 0) {
|
|
518
|
+
return {
|
|
519
|
+
ok: false,
|
|
520
|
+
current,
|
|
521
|
+
errorKey: "update.errorNetwork",
|
|
522
|
+
message: attempts.map((attempt) => `${attempt.registry}: ${attempt.message}`).join("; "),
|
|
523
|
+
triedRegistries: registries
|
|
524
|
+
};
|
|
503
525
|
}
|
|
526
|
+
const latest = answers.map((answer) => answer.latest).reduce((best, version) => compareVersions(version, best) > 0 ? version : best);
|
|
527
|
+
const sources = answers.filter((answer) => compareVersions(answer.latest, latest) === 0).map((answer) => answer.registry);
|
|
528
|
+
const behind = answers.filter((answer) => compareVersions(answer.latest, latest) < 0).map((answer) => ({ registry: answer.registry, version: answer.latest }));
|
|
504
529
|
return {
|
|
505
|
-
ok:
|
|
530
|
+
ok: true,
|
|
506
531
|
current,
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
532
|
+
latest,
|
|
533
|
+
registry: sources[0],
|
|
534
|
+
sources,
|
|
535
|
+
behind,
|
|
536
|
+
updateAvailable: compareVersions(latest, current) > 0
|
|
510
537
|
};
|
|
511
538
|
}
|
|
512
|
-
function
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
539
|
+
function npmErrorSummary(output) {
|
|
540
|
+
const lines = String(output || "").split(/\r?\n/).map((line) => line.trim()).filter((line) => /^npm (error|ERR!)/.test(line) && !/complete log of this run/i.test(line)).map((line) => line.replace(/^npm (error|ERR!)\s*/, "")).filter(Boolean);
|
|
541
|
+
return (lines.length > 0 ? lines.slice(0, 3) : String(output || "").trim().split(/\r?\n/).slice(-3)).join(" ");
|
|
542
|
+
}
|
|
543
|
+
function isNotYetPublished(output) {
|
|
544
|
+
return /\bETARGET\b|notarget|No matching version found/i.test(String(output || ""));
|
|
545
|
+
}
|
|
546
|
+
function installedVersionOnDisk() {
|
|
547
|
+
try {
|
|
548
|
+
return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8")).version;
|
|
549
|
+
} catch {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function runNpm(spawnImpl, args, timeoutMs) {
|
|
524
554
|
return new Promise((resolve) => {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
555
|
+
let child;
|
|
556
|
+
try {
|
|
557
|
+
child = spawnImpl("npm", args, { timeout: timeoutMs });
|
|
558
|
+
} catch (error) {
|
|
559
|
+
resolve({ ok: false, spawnFailed: true, output: String(error?.message || error) });
|
|
528
560
|
return;
|
|
529
561
|
}
|
|
530
|
-
const args = ["install", "-g", `${PACKAGE_NAME}@latest`];
|
|
531
|
-
const normalizedRegistry = normalizeRegistry(registry);
|
|
532
|
-
if (normalizedRegistry) args.push("--registry", normalizedRegistry);
|
|
533
|
-
const child = spawnImpl("npm", args, {
|
|
534
|
-
encoding: "utf8",
|
|
535
|
-
timeout: timeoutMs
|
|
536
|
-
});
|
|
537
562
|
let output = "";
|
|
563
|
+
let settled = false;
|
|
564
|
+
const finish = (result) => {
|
|
565
|
+
if (!settled) {
|
|
566
|
+
settled = true;
|
|
567
|
+
resolve(result);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
538
570
|
const collect = (chunk) => {
|
|
539
571
|
output += String(chunk);
|
|
540
572
|
};
|
|
541
573
|
child.stdout?.on("data", collect);
|
|
542
574
|
child.stderr?.on("data", collect);
|
|
543
|
-
child.on("error", (error) => {
|
|
544
|
-
|
|
545
|
-
});
|
|
546
|
-
child.on("close", (code) => {
|
|
547
|
-
resolve({ ok: code === 0, output: output.trim().slice(-2e3) });
|
|
548
|
-
});
|
|
575
|
+
child.on("error", (error) => finish({ ok: false, spawnFailed: true, output: error.message }));
|
|
576
|
+
child.on("close", (code) => finish({ ok: code === 0, output: output.trim() }));
|
|
549
577
|
});
|
|
550
578
|
}
|
|
579
|
+
var VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
580
|
+
async function performUpdate2({
|
|
581
|
+
spawnImpl = spawn,
|
|
582
|
+
timeoutMs = 18e4,
|
|
583
|
+
// Seam for tests: the suite runs from a checkout, where the guard below is
|
|
584
|
+
// correctly the only reachable outcome.
|
|
585
|
+
installKindImpl = installKind2,
|
|
586
|
+
// The registries that reported `version`, preferred first. Empty means
|
|
587
|
+
// "whatever npm is configured with", which is right when no check has run.
|
|
588
|
+
registry = "",
|
|
589
|
+
sources = [],
|
|
590
|
+
version = null,
|
|
591
|
+
attempts = 3,
|
|
592
|
+
retryDelayMs = 15e3,
|
|
593
|
+
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
594
|
+
readInstalledVersion = installedVersionOnDisk,
|
|
595
|
+
onAttempt = () => {
|
|
596
|
+
}
|
|
597
|
+
} = {}) {
|
|
598
|
+
const kind = installKindImpl();
|
|
599
|
+
if (kind !== "npm") return { ok: false, errorKey: `update.cannot.${kind}` };
|
|
600
|
+
const target = typeof version === "string" && VERSION_PATTERN.test(version) ? version : null;
|
|
601
|
+
const spec = `${PACKAGE_NAME}@${target || "latest"}`;
|
|
602
|
+
const registries = [];
|
|
603
|
+
for (const candidate of [registry, ...sources]) {
|
|
604
|
+
const normalized = normalizeRegistry(candidate);
|
|
605
|
+
if (normalized && !registries.includes(normalized)) registries.push(normalized);
|
|
606
|
+
}
|
|
607
|
+
if (registries.length === 0) registries.push("");
|
|
608
|
+
let last = { output: "" };
|
|
609
|
+
let notYetPublished = false;
|
|
610
|
+
for (const source of registries) {
|
|
611
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
612
|
+
onAttempt({ registry: source, attempt });
|
|
613
|
+
const args = ["install", "-g", spec, "--prefer-online"];
|
|
614
|
+
if (source) args.push("--registry", source);
|
|
615
|
+
const result = await runNpm(spawnImpl, args, timeoutMs);
|
|
616
|
+
if (result.ok) {
|
|
617
|
+
const installed = readInstalledVersion();
|
|
618
|
+
if (target && installed && compareVersions(installed, target) < 0) {
|
|
619
|
+
return { ok: false, errorKey: "update.errorNotApplied", installed, output: result.output.slice(-2e3) };
|
|
620
|
+
}
|
|
621
|
+
return { ok: true, installed, output: result.output.slice(-2e3) };
|
|
622
|
+
}
|
|
623
|
+
last = result;
|
|
624
|
+
if (result.spawnFailed) {
|
|
625
|
+
return { ok: false, errorKey: "update.errorFailed", output: result.output, summary: result.output };
|
|
626
|
+
}
|
|
627
|
+
notYetPublished = isNotYetPublished(result.output);
|
|
628
|
+
if (!notYetPublished) break;
|
|
629
|
+
if (attempt < attempts) await sleep(retryDelayMs);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
ok: false,
|
|
634
|
+
errorKey: notYetPublished ? "update.errorNotYetPublished" : "update.errorFailed",
|
|
635
|
+
output: String(last.output || "").slice(-2e3),
|
|
636
|
+
summary: npmErrorSummary(last.output)
|
|
637
|
+
};
|
|
638
|
+
}
|
|
551
639
|
module.exports = {
|
|
552
640
|
PACKAGE_NAME,
|
|
553
641
|
DEFAULT_REGISTRY,
|
|
@@ -557,8 +645,11 @@ var require_updater = __commonJS({
|
|
|
557
645
|
compareVersions,
|
|
558
646
|
currentVersion,
|
|
559
647
|
installKind: installKind2,
|
|
648
|
+
installedVersionOnDisk,
|
|
649
|
+
npmErrorSummary,
|
|
560
650
|
performUpdate: performUpdate2,
|
|
561
|
-
registryCandidates
|
|
651
|
+
registryCandidates,
|
|
652
|
+
updateChecksEnabled: updateChecksEnabled2
|
|
562
653
|
};
|
|
563
654
|
}
|
|
564
655
|
});
|
|
@@ -659,7 +750,7 @@ var require_herdr_command = __commonJS({
|
|
|
659
750
|
if (!version) return true;
|
|
660
751
|
return compareVersions(version, minimum) >= 0;
|
|
661
752
|
}
|
|
662
|
-
function
|
|
753
|
+
function herdrVersion2({ command, timeout = 1e4, ...lookup } = {}) {
|
|
663
754
|
const resolved = command || resolveHerdrCommand(lookup);
|
|
664
755
|
const unknown = { command: resolved, version: null, raw: null, supported: true, ok: false };
|
|
665
756
|
let result;
|
|
@@ -692,7 +783,7 @@ var require_herdr_command = __commonJS({
|
|
|
692
783
|
findHerdrCommand,
|
|
693
784
|
herdrNotFoundMessage,
|
|
694
785
|
herdrOutdatedMessage,
|
|
695
|
-
herdrVersion,
|
|
786
|
+
herdrVersion: herdrVersion2,
|
|
696
787
|
meetsMinimum,
|
|
697
788
|
parseHerdrVersion,
|
|
698
789
|
resolveHerdrCommand,
|
|
@@ -839,14 +930,7 @@ var require_en = __commonJS({
|
|
|
839
930
|
"herdr.title": "Herdr integration",
|
|
840
931
|
"herdr.socketPath": "Socket path",
|
|
841
932
|
"herdr.args": "Extra arguments",
|
|
842
|
-
"herdr.
|
|
843
|
-
"herdr.pluginRegistered": "Registered with Herdr.",
|
|
844
|
-
"herdr.pluginMissing": "Not registered with Herdr.",
|
|
845
|
-
"herdr.register": "Register as Herdr plugin",
|
|
846
|
-
"herdr.unregister": "Unregister plugin",
|
|
847
|
-
"herdr.registerDone": "Registered: {path}",
|
|
848
|
-
"herdr.unregisterDone": "Plugin unregistered.",
|
|
849
|
-
"herdr.registerFailed": "Registration failed: {message}",
|
|
933
|
+
"herdr.autoStartHint": "Off: Herdr starts only when you run it, or when you confirm it from a browser. On: herdr-remote also starts it every time it starts, at boot included. Takes effect the next time herdr-remote starts.",
|
|
850
934
|
"herdr.cliMissing": "Command herdr not found. Set HERDR_BIN_PATH to its full path.",
|
|
851
935
|
"herdr.version": "Herdr version",
|
|
852
936
|
"herdr.versionOutdated": 'Herdr {version} is older than {minimum}. Run "herdr update": window titles, background machine activation and large pastes misbehave in browser windows before that release.',
|
|
@@ -883,6 +967,7 @@ var require_en = __commonJS({
|
|
|
883
967
|
"field.publicUrl": "Browser URL override",
|
|
884
968
|
"field.socketPath": "Herdr socket path",
|
|
885
969
|
"field.herdrArgs": "Herdr arguments",
|
|
970
|
+
"field.herdrAutoStart": "Auto-start Herdr",
|
|
886
971
|
"field.language": "Language",
|
|
887
972
|
"field.keepalive": "Keep-alive manager",
|
|
888
973
|
"placeholder.autoDiscovered": "auto-discovered",
|
|
@@ -912,16 +997,22 @@ var require_en = __commonJS({
|
|
|
912
997
|
"hint.save": "s save",
|
|
913
998
|
"hint.editing": "\u21B5 confirm \xB7 esc cancel",
|
|
914
999
|
"update.title": "Updates",
|
|
1000
|
+
"update.banner": "herdr-remote {latest} is out (running {current}). Press 7 and Enter on the update row to install.",
|
|
915
1001
|
"update.check": "Check for updates",
|
|
916
1002
|
"update.checking": "Checking npm\u2026",
|
|
917
1003
|
"update.upToDate": "Up to date ({version})",
|
|
918
1004
|
"update.available": "Update {version} available (press Enter to install)",
|
|
919
1005
|
"update.updating": "Installing {version}\u2026",
|
|
1006
|
+
"update.updatingRetry": "Installing {version}\u2026 (npm has not caught up yet, attempt {attempt})",
|
|
920
1007
|
"update.done": "Updated to {version}",
|
|
921
1008
|
"update.restartHint": "Update installed. Restart herdr-remote to apply.",
|
|
922
1009
|
"update.errorNetwork": "Could not reach any npm registry",
|
|
923
1010
|
"update.errorNetworkDetail": "No registry answered: {message}",
|
|
924
|
-
"update.errorFailed": "Update failed. Run: npm install -g herdr-remote@
|
|
1011
|
+
"update.errorFailed": "Update failed. Run: npm install -g herdr-remote@{version} --prefer-online",
|
|
1012
|
+
"update.errorFailedDetail": "npm: {message}",
|
|
1013
|
+
"update.errorNotYetPublished": "npm has not caught up with {version} yet (common in the first minutes after a release). Press Enter to try again shortly.",
|
|
1014
|
+
"update.errorNotApplied": "npm reported success, but {installed} is still installed",
|
|
1015
|
+
"update.mirrorBehind": "{registries} still has an older version; installing from {source}",
|
|
925
1016
|
"update.cannot.source": "Source checkout \u2014 update with git",
|
|
926
1017
|
"update.cannot.linked": "Linked package \u2014 managed by npm link"
|
|
927
1018
|
};
|
|
@@ -1066,14 +1157,7 @@ var require_zh = __commonJS({
|
|
|
1066
1157
|
"herdr.title": "Herdr \u96C6\u6210",
|
|
1067
1158
|
"herdr.socketPath": "\u5957\u63A5\u5B57\u8DEF\u5F84",
|
|
1068
1159
|
"herdr.args": "\u9644\u52A0\u53C2\u6570",
|
|
1069
|
-
"herdr.
|
|
1070
|
-
"herdr.pluginRegistered": "\u5DF2\u6CE8\u518C\u5230 Herdr\u3002",
|
|
1071
|
-
"herdr.pluginMissing": "\u5C1A\u672A\u6CE8\u518C\u5230 Herdr\u3002",
|
|
1072
|
-
"herdr.register": "\u6CE8\u518C\u4E3A Herdr \u63D2\u4EF6",
|
|
1073
|
-
"herdr.unregister": "\u53D6\u6D88\u6CE8\u518C",
|
|
1074
|
-
"herdr.registerDone": "\u5DF2\u6CE8\u518C\uFF1A{path}",
|
|
1075
|
-
"herdr.unregisterDone": "\u5DF2\u53D6\u6D88\u6CE8\u518C\u3002",
|
|
1076
|
-
"herdr.registerFailed": "\u6CE8\u518C\u5931\u8D25\uFF1A{message}",
|
|
1160
|
+
"herdr.autoStartHint": "\u5173\uFF1AHerdr \u53EA\u5728\u4F60\u624B\u52A8\u8FD0\u884C\uFF0C\u6216\u5728\u6D4F\u89C8\u5668\u91CC\u786E\u8BA4\u540E\u624D\u542F\u52A8\u3002\u5F00\uFF1Aherdr-remote \u6BCF\u6B21\u542F\u52A8\uFF08\u5305\u62EC\u5F00\u673A\uFF09\u65F6\u4E00\u5E76\u542F\u52A8 Herdr\u3002\u4E0B\u6B21 herdr-remote \u542F\u52A8\u65F6\u751F\u6548\u3002",
|
|
1077
1161
|
"herdr.cliMissing": "\u672A\u627E\u5230 herdr \u547D\u4EE4\uFF0C\u53EF\u5C06 HERDR_BIN_PATH \u8BBE\u4E3A\u5176\u5B8C\u6574\u8DEF\u5F84\u3002",
|
|
1078
1162
|
"herdr.version": "Herdr \u7248\u672C",
|
|
1079
1163
|
"herdr.versionOutdated": "Herdr {version} \u4F4E\u4E8E {minimum}\uFF0C\u8BF7\u8FD0\u884C \u201Cherdr update\u201D\uFF1A\u5728\u8BE5\u7248\u672C\u4E4B\u524D\uFF0C\u6D4F\u89C8\u5668\u7A97\u53E3\u7684\u6807\u9898\u3001\u540E\u53F0\u673A\u5668\u6FC0\u6D3B\u4E0E\u5927\u6BB5\u7C98\u8D34\u90FD\u4F1A\u51FA\u95EE\u9898\u3002",
|
|
@@ -1110,6 +1194,7 @@ var require_zh = __commonJS({
|
|
|
1110
1194
|
"field.publicUrl": "\u6D4F\u89C8\u5668\u5730\u5740\uFF08\u8986\u76D6\uFF09",
|
|
1111
1195
|
"field.socketPath": "Herdr \u5957\u63A5\u5B57\u8DEF\u5F84",
|
|
1112
1196
|
"field.herdrArgs": "Herdr \u53C2\u6570",
|
|
1197
|
+
"field.herdrAutoStart": "\u968F\u670D\u52A1\u542F\u52A8 Herdr",
|
|
1113
1198
|
"field.language": "\u8BED\u8A00",
|
|
1114
1199
|
"field.keepalive": "\u4FDD\u6D3B\u7BA1\u7406\u65B9\u5F0F",
|
|
1115
1200
|
"placeholder.autoDiscovered": "\u81EA\u52A8\u63A2\u6D4B",
|
|
@@ -1139,16 +1224,22 @@ var require_zh = __commonJS({
|
|
|
1139
1224
|
"hint.save": "s \u4FDD\u5B58",
|
|
1140
1225
|
"hint.editing": "\u21B5 \u786E\u8BA4 \xB7 esc \u53D6\u6D88",
|
|
1141
1226
|
"update.title": "\u66F4\u65B0",
|
|
1227
|
+
"update.banner": "herdr-remote {latest} \u5DF2\u53D1\u5E03\uFF08\u5F53\u524D {current}\uFF09\uFF0C\u6309 7 \u8FDB\u5165\u300C\u8BED\u8A00\u4E0E\u5173\u4E8E\u300D\uFF0C\u5728\u66F4\u65B0\u4E00\u884C\u6309 Enter \u5B89\u88C5\u3002",
|
|
1142
1228
|
"update.check": "\u68C0\u67E5\u66F4\u65B0",
|
|
1143
1229
|
"update.checking": "\u67E5\u8BE2 npm \u4E2D\u2026",
|
|
1144
1230
|
"update.upToDate": "\u5DF2\u662F\u6700\u65B0\uFF08{version}\uFF09",
|
|
1145
1231
|
"update.available": "\u65B0\u7248\u672C {version} \u53EF\u7528\uFF08\u6309 Enter \u5B89\u88C5\uFF09",
|
|
1146
1232
|
"update.updating": "\u5B89\u88C5 {version} \u4E2D\u2026",
|
|
1233
|
+
"update.updatingRetry": "\u5B89\u88C5 {version} \u4E2D\u2026\uFF08npm \u5C1A\u672A\u540C\u6B65\uFF0C\u7B2C {attempt} \u6B21\u5C1D\u8BD5\uFF09",
|
|
1147
1234
|
"update.done": "\u5DF2\u66F4\u65B0\u81F3 {version}",
|
|
1148
1235
|
"update.restartHint": "\u66F4\u65B0\u5DF2\u5B89\u88C5\uFF0C\u91CD\u542F herdr-remote \u751F\u6548\u3002",
|
|
1149
1236
|
"update.errorNetwork": "\u6240\u6709 npm registry \u5747\u65E0\u6CD5\u8FDE\u63A5",
|
|
1150
1237
|
"update.errorNetworkDetail": "\u65E0 registry \u5E94\u7B54\uFF1A{message}",
|
|
1151
|
-
"update.errorFailed": "\u66F4\u65B0\u5931\u8D25\uFF0C\u53EF\u624B\u52A8\u6267\u884C\uFF1Anpm install -g herdr-remote@
|
|
1238
|
+
"update.errorFailed": "\u66F4\u65B0\u5931\u8D25\uFF0C\u53EF\u624B\u52A8\u6267\u884C\uFF1Anpm install -g herdr-remote@{version} --prefer-online",
|
|
1239
|
+
"update.errorFailedDetail": "npm\uFF1A{message}",
|
|
1240
|
+
"update.errorNotYetPublished": "npm \u8FD8\u6CA1\u540C\u6B65\u5230 {version}\uFF08\u521A\u53D1\u5E03\u7684\u51E0\u5206\u949F\u5185\u5E38\u89C1\uFF09\uFF0C\u7A0D\u540E\u6309 Enter \u91CD\u8BD5\u3002",
|
|
1241
|
+
"update.errorNotApplied": "npm \u62A5\u544A\u6210\u529F\uFF0C\u4F46\u5B89\u88C5\u7684\u4ECD\u662F {installed}",
|
|
1242
|
+
"update.mirrorBehind": "{registries} \u4ECD\u662F\u65E7\u7248\u672C\uFF0C\u5C06\u4ECE {source} \u5B89\u88C5",
|
|
1152
1243
|
"update.cannot.source": "\u6E90\u7801\u76EE\u5F55\u8FD0\u884C\uFF0C\u8BF7\u4F7F\u7528 git \u66F4\u65B0",
|
|
1153
1244
|
"update.cannot.linked": "npm link \u5DE5\u4F5C\u526F\u672C\uFF0C\u7531 npm link \u7BA1\u7406"
|
|
1154
1245
|
};
|
|
@@ -1334,6 +1425,7 @@ var require_settings_model = __commonJS({
|
|
|
1334
1425
|
{ id: "publicUrl", kind: "text", labelKey: "field.publicUrl" },
|
|
1335
1426
|
{ id: "socketPath", kind: "text", labelKey: "field.socketPath" },
|
|
1336
1427
|
{ id: "herdrArgs", kind: "text", labelKey: "field.herdrArgs" },
|
|
1428
|
+
{ id: "herdrAutoStart", kind: "toggle", labelKey: "field.herdrAutoStart" },
|
|
1337
1429
|
{ id: "language", kind: "choice", choices: LANGUAGES, labelKey: "field.language" },
|
|
1338
1430
|
{ id: "keepaliveManager", kind: "choice", choices: KEEPALIVE_MANAGERS, labelKey: "field.keepalive" }
|
|
1339
1431
|
];
|
|
@@ -1371,6 +1463,8 @@ var require_settings_model = __commonJS({
|
|
|
1371
1463
|
return draft.herdr.socketPath || EMPTY;
|
|
1372
1464
|
case "herdrArgs":
|
|
1373
1465
|
return (draft.herdr.args || []).join(" ");
|
|
1466
|
+
case "herdrAutoStart":
|
|
1467
|
+
return draft.herdr.autoStart ? "on" : "off";
|
|
1374
1468
|
case "language":
|
|
1375
1469
|
return draft.ui.language;
|
|
1376
1470
|
case "keepaliveManager":
|
|
@@ -1464,6 +1558,13 @@ var require_settings_model = __commonJS({
|
|
|
1464
1558
|
next.herdr.args = args;
|
|
1465
1559
|
break;
|
|
1466
1560
|
}
|
|
1561
|
+
case "herdrAutoStart": {
|
|
1562
|
+
if (value !== true && value !== false && value !== "on" && value !== "off") {
|
|
1563
|
+
return { draft, errorKey: "error.unknownField" };
|
|
1564
|
+
}
|
|
1565
|
+
next.herdr.autoStart = value === true || value === "on";
|
|
1566
|
+
break;
|
|
1567
|
+
}
|
|
1467
1568
|
case "language": {
|
|
1468
1569
|
if (!LANGUAGES.includes(value)) return { draft, errorKey: "error.invalidLanguage" };
|
|
1469
1570
|
next.ui.language = value;
|
|
@@ -1509,7 +1610,8 @@ var require_settings_model = __commonJS({
|
|
|
1509
1610
|
merged.herdr = {
|
|
1510
1611
|
...current.herdr || {},
|
|
1511
1612
|
socketPath: draft.herdr.socketPath,
|
|
1512
|
-
args: draft.herdr.args
|
|
1613
|
+
args: draft.herdr.args,
|
|
1614
|
+
autoStart: draft.herdr.autoStart === true
|
|
1513
1615
|
};
|
|
1514
1616
|
if (!merged.herdr.socketPath) delete merged.herdr.socketPath;
|
|
1515
1617
|
merged.keepalive = { ...current.keepalive || {}, manager: draft.keepalive.manager };
|
|
@@ -1758,7 +1860,12 @@ var require_socket_discovery = __commonJS({
|
|
|
1758
1860
|
}
|
|
1759
1861
|
return { ok: true, path: socketPath, uid: stat.uid, mode: stat.mode };
|
|
1760
1862
|
} catch (error) {
|
|
1761
|
-
return {
|
|
1863
|
+
return {
|
|
1864
|
+
ok: false,
|
|
1865
|
+
missing: error.code === "ENOENT",
|
|
1866
|
+
reason: error.code === "ENOENT" ? "socket does not exist" : error.message,
|
|
1867
|
+
path: socketPath
|
|
1868
|
+
};
|
|
1762
1869
|
}
|
|
1763
1870
|
}
|
|
1764
1871
|
function assertSocket(socketPath, options = {}) {
|
|
@@ -1810,7 +1917,7 @@ var require_service = __commonJS({
|
|
|
1810
1917
|
} = require_terminal_palette();
|
|
1811
1918
|
var { resolveSocketPath } = require_socket_discovery();
|
|
1812
1919
|
var { preferredLanAddress: preferredLanAddress2 } = require_net_interfaces();
|
|
1813
|
-
var { MIN_HERDR_VERSION: MIN_HERDR_VERSION2, findHerdrCommand, herdrVersion, resolveHerdrCommand } = require_herdr_command();
|
|
1920
|
+
var { MIN_HERDR_VERSION: MIN_HERDR_VERSION2, findHerdrCommand, herdrVersion: herdrVersion2, resolveHerdrCommand } = require_herdr_command();
|
|
1814
1921
|
var RUNTIME_VERSION = 2;
|
|
1815
1922
|
function pidAlive(pid) {
|
|
1816
1923
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -2117,7 +2224,7 @@ var require_service = __commonJS({
|
|
|
2117
2224
|
}
|
|
2118
2225
|
const socketPath = resolveSocketPath(config.herdr.socketPath);
|
|
2119
2226
|
const herdr = findHerdrCommand();
|
|
2120
|
-
const installed = herdr.found ?
|
|
2227
|
+
const installed = herdr.found ? herdrVersion2({ command: herdr.command }) : { version: null, supported: true };
|
|
2121
2228
|
return {
|
|
2122
2229
|
ok: true,
|
|
2123
2230
|
mode: config.relay.mode,
|
|
@@ -2580,8 +2687,8 @@ var require_lifecycle = __commonJS({
|
|
|
2580
2687
|
function startAll2(config = loadConfig2()) {
|
|
2581
2688
|
const managed = managerInUse(config);
|
|
2582
2689
|
if (managed) {
|
|
2583
|
-
keepalive2.restart(config);
|
|
2584
|
-
return { ok: true, managed: true, manager: managed.manager };
|
|
2690
|
+
if (!managed.active) keepalive2.restart(config);
|
|
2691
|
+
return { ok: true, managed: true, manager: managed.manager, alreadyRunning: Boolean(managed.active) };
|
|
2585
2692
|
}
|
|
2586
2693
|
return { ...startServices(), managed: false };
|
|
2587
2694
|
}
|
|
@@ -2613,116 +2720,11 @@ var require_lifecycle = __commonJS({
|
|
|
2613
2720
|
}
|
|
2614
2721
|
});
|
|
2615
2722
|
|
|
2616
|
-
// src/herdr-plugin.js
|
|
2617
|
-
var require_herdr_plugin = __commonJS({
|
|
2618
|
-
"src/herdr-plugin.js"(exports, module) {
|
|
2619
|
-
"use strict";
|
|
2620
|
-
var fs = __require("node:fs");
|
|
2621
|
-
var path = __require("node:path");
|
|
2622
|
-
var { spawnSync } = __require("node:child_process");
|
|
2623
|
-
var { PACKAGE_ROOT } = require_config();
|
|
2624
|
-
var { herdrVersion, resolveHerdrCommand } = require_herdr_command();
|
|
2625
|
-
var PLUGIN_ID = "herdr.remote.web";
|
|
2626
|
-
var MANIFEST_NAME = "herdr-plugin.toml";
|
|
2627
|
-
function manifestPath() {
|
|
2628
|
-
return path.join(PACKAGE_ROOT, MANIFEST_NAME);
|
|
2629
|
-
}
|
|
2630
|
-
function herdrAvailable() {
|
|
2631
|
-
return herdrVersion().ok;
|
|
2632
|
-
}
|
|
2633
|
-
function runHerdr(args, { timeout = 15e3 } = {}) {
|
|
2634
|
-
const result = spawnSync(resolveHerdrCommand(), args, { encoding: "utf8", timeout });
|
|
2635
|
-
if (result.error && result.error.code === "ENOENT") {
|
|
2636
|
-
const error = new Error("herdr command not found");
|
|
2637
|
-
error.code = "HERDR_NOT_FOUND";
|
|
2638
|
-
throw error;
|
|
2639
|
-
}
|
|
2640
|
-
return result;
|
|
2641
|
-
}
|
|
2642
|
-
function parsePluginList(output) {
|
|
2643
|
-
const plugins = [];
|
|
2644
|
-
for (const line of String(output || "").split("\n")) {
|
|
2645
|
-
const match = /^-\s+(\S+)\s+\((.*?)\)\s+(\S+)(?:\s+\[(.*)\])?/.exec(line.trim());
|
|
2646
|
-
if (!match) continue;
|
|
2647
|
-
const [, id, name, state, rawSource] = match;
|
|
2648
|
-
const source = rawSource ? rawSource.split(";")[0].trim() : null;
|
|
2649
|
-
plugins.push({
|
|
2650
|
-
id,
|
|
2651
|
-
name,
|
|
2652
|
-
enabled: state === "enabled",
|
|
2653
|
-
source,
|
|
2654
|
-
warnings: rawSource && rawSource.includes(";") ? rawSource.slice(rawSource.indexOf(";") + 1).trim() : null,
|
|
2655
|
-
localPath: source && source.startsWith("local:") ? source.slice("local:".length) : null
|
|
2656
|
-
});
|
|
2657
|
-
}
|
|
2658
|
-
return plugins;
|
|
2659
|
-
}
|
|
2660
|
-
function registrationStatus() {
|
|
2661
|
-
if (!fs.existsSync(manifestPath())) {
|
|
2662
|
-
return { available: false, registered: false, reason: "manifest missing" };
|
|
2663
|
-
}
|
|
2664
|
-
const { version, supported } = herdrVersion();
|
|
2665
|
-
const installed = { version, versionSupported: supported };
|
|
2666
|
-
let result;
|
|
2667
|
-
try {
|
|
2668
|
-
result = runHerdr(["plugin", "list"]);
|
|
2669
|
-
} catch (error) {
|
|
2670
|
-
if (error.code === "HERDR_NOT_FOUND") return { available: false, registered: false, reason: "herdr not found" };
|
|
2671
|
-
throw error;
|
|
2672
|
-
}
|
|
2673
|
-
if (result.status !== 0) {
|
|
2674
|
-
return { available: true, registered: false, reason: String(result.stderr || "").trim(), ...installed };
|
|
2675
|
-
}
|
|
2676
|
-
const plugins = parsePluginList(result.stdout);
|
|
2677
|
-
const entry = plugins.find((plugin) => plugin.id === PLUGIN_ID);
|
|
2678
|
-
if (!entry) return { available: true, registered: false, packageRoot: PACKAGE_ROOT, ...installed };
|
|
2679
|
-
return {
|
|
2680
|
-
available: true,
|
|
2681
|
-
registered: true,
|
|
2682
|
-
enabled: entry.enabled,
|
|
2683
|
-
linkedPath: entry.localPath,
|
|
2684
|
-
// A stale link pointing at an old checkout is the main failure mode after
|
|
2685
|
-
// switching from a source install to npm.
|
|
2686
|
-
stale: Boolean(entry.localPath) && path.resolve(entry.localPath) !== path.resolve(PACKAGE_ROOT),
|
|
2687
|
-
packageRoot: PACKAGE_ROOT,
|
|
2688
|
-
...installed
|
|
2689
|
-
};
|
|
2690
|
-
}
|
|
2691
|
-
function register() {
|
|
2692
|
-
const current = registrationStatus();
|
|
2693
|
-
if (current.registered && current.stale) {
|
|
2694
|
-
runHerdr(["plugin", "unlink", PLUGIN_ID]);
|
|
2695
|
-
}
|
|
2696
|
-
const result = runHerdr(["plugin", "link", PACKAGE_ROOT]);
|
|
2697
|
-
if (result.status !== 0) {
|
|
2698
|
-
throw new Error(String(result.stderr || result.stdout || "").trim() || "herdr plugin link failed");
|
|
2699
|
-
}
|
|
2700
|
-
return { ok: true, path: PACKAGE_ROOT, output: String(result.stdout || "").trim() };
|
|
2701
|
-
}
|
|
2702
|
-
function unregister() {
|
|
2703
|
-
const result = runHerdr(["plugin", "unlink", PLUGIN_ID]);
|
|
2704
|
-
if (result.status !== 0) {
|
|
2705
|
-
throw new Error(String(result.stderr || result.stdout || "").trim() || "herdr plugin unlink failed");
|
|
2706
|
-
}
|
|
2707
|
-
return { ok: true, output: String(result.stdout || "").trim() };
|
|
2708
|
-
}
|
|
2709
|
-
module.exports = {
|
|
2710
|
-
PLUGIN_ID,
|
|
2711
|
-
manifestPath,
|
|
2712
|
-
herdrAvailable,
|
|
2713
|
-
parsePluginList,
|
|
2714
|
-
registrationStatus,
|
|
2715
|
-
register,
|
|
2716
|
-
unregister
|
|
2717
|
-
};
|
|
2718
|
-
}
|
|
2719
|
-
});
|
|
2720
|
-
|
|
2721
2723
|
// tui/src/index.tsx
|
|
2722
2724
|
import { render } from "ink";
|
|
2723
2725
|
|
|
2724
2726
|
// tui/src/App.tsx
|
|
2725
|
-
import { useCallback as useCallback2, useEffect as
|
|
2727
|
+
import { useCallback as useCallback2, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef3, useState as useState10 } from "react";
|
|
2726
2728
|
import { Box as Box11, Text as Text11, useApp, useInput as useInput9 } from "ink";
|
|
2727
2729
|
|
|
2728
2730
|
// tui/src/mouse/index.tsx
|
|
@@ -3026,7 +3028,6 @@ var import_settings_model = __toESM(require_settings_model());
|
|
|
3026
3028
|
var import_service = __toESM(require_service());
|
|
3027
3029
|
var import_lifecycle = __toESM(require_lifecycle());
|
|
3028
3030
|
var keepalive = __toESM(require_keepalive());
|
|
3029
|
-
var herdrPlugin = __toESM(require_herdr_plugin());
|
|
3030
3031
|
var import_updater = __toESM(require_updater());
|
|
3031
3032
|
function detectLocale(options = {}) {
|
|
3032
3033
|
return (0, import_i18n.detectLocale)(options);
|
|
@@ -3808,24 +3809,15 @@ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
|
3808
3809
|
function HerdrScreen({ ctx }) {
|
|
3809
3810
|
const { t, draft, editingId } = ctx;
|
|
3810
3811
|
const [selected, setSelected] = useState7("socketPath");
|
|
3811
|
-
const [
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
} catch (error) {
|
|
3816
|
-
ctx.notify(error.message, "error");
|
|
3817
|
-
}
|
|
3818
|
-
};
|
|
3819
|
-
useEffect5(reloadRegistration, []);
|
|
3812
|
+
const [installed, setInstalled] = useState7(null);
|
|
3813
|
+
useEffect5(() => {
|
|
3814
|
+
setInstalled((0, import_herdr_command.herdrVersion)());
|
|
3815
|
+
}, []);
|
|
3820
3816
|
const entries = [
|
|
3821
3817
|
{ id: "socketPath", kind: "field", label: t("herdr.socketPath") },
|
|
3822
3818
|
{ id: "herdrArgs", kind: "field", label: t("herdr.args") },
|
|
3823
|
-
{ id: "
|
|
3824
|
-
{
|
|
3825
|
-
id: registration?.registered && !registration?.stale ? "unregister" : "register",
|
|
3826
|
-
kind: "action",
|
|
3827
|
-
label: registration?.registered && !registration?.stale ? t("herdr.unregister") : t("herdr.register")
|
|
3828
|
-
}
|
|
3819
|
+
{ id: "herdrAutoStart", kind: "toggle", label: t("field.herdrAutoStart") },
|
|
3820
|
+
{ id: "save", kind: "action", label: t("common.save") }
|
|
3829
3821
|
];
|
|
3830
3822
|
const applyField = (id, value) => {
|
|
3831
3823
|
const result = (0, import_settings_model.setField)(draft, id, value);
|
|
@@ -3850,26 +3842,11 @@ function HerdrScreen({ ctx }) {
|
|
|
3850
3842
|
ctx.setEditing(id);
|
|
3851
3843
|
return;
|
|
3852
3844
|
}
|
|
3853
|
-
if (id === "
|
|
3854
|
-
|
|
3845
|
+
if (id === "herdrAutoStart") {
|
|
3846
|
+
applyField(id, draft.herdr.autoStart ? "off" : "on");
|
|
3855
3847
|
return;
|
|
3856
3848
|
}
|
|
3857
|
-
|
|
3858
|
-
try {
|
|
3859
|
-
if (id === "register") {
|
|
3860
|
-
const result = herdrPlugin.register();
|
|
3861
|
-
ctx.notify(t("herdr.registerDone", { path: result.path }), "success");
|
|
3862
|
-
} else {
|
|
3863
|
-
herdrPlugin.unregister();
|
|
3864
|
-
ctx.notify(t("herdr.unregisterDone"), "success");
|
|
3865
|
-
}
|
|
3866
|
-
} catch (error) {
|
|
3867
|
-
const failure = error;
|
|
3868
|
-
throw new Error(failure.code === "HERDR_NOT_FOUND" ? t("herdr.cliMissing") : t("herdr.registerFailed", { message: failure.message }));
|
|
3869
|
-
} finally {
|
|
3870
|
-
reloadRegistration();
|
|
3871
|
-
}
|
|
3872
|
-
});
|
|
3849
|
+
if (id === "save") save();
|
|
3873
3850
|
};
|
|
3874
3851
|
useInput6((input, key) => {
|
|
3875
3852
|
const index = entries.findIndex((entry) => entry.id === selected);
|
|
@@ -3880,7 +3857,20 @@ function HerdrScreen({ ctx }) {
|
|
|
3880
3857
|
else if (input === "s") save();
|
|
3881
3858
|
}, { isActive: editingId === null });
|
|
3882
3859
|
return /* @__PURE__ */ jsxs8(Panel, { title: t("herdr.title"), children: [
|
|
3883
|
-
entries.map((entry) => entry.kind === "
|
|
3860
|
+
entries.map((entry) => entry.kind === "toggle" ? /* @__PURE__ */ jsx9(
|
|
3861
|
+
FieldRow,
|
|
3862
|
+
{
|
|
3863
|
+
label: entry.label,
|
|
3864
|
+
selected: selected === entry.id,
|
|
3865
|
+
onSelect: () => {
|
|
3866
|
+
setSelected(entry.id);
|
|
3867
|
+
activate(entry.id);
|
|
3868
|
+
},
|
|
3869
|
+
onHover: () => setSelected(entry.id),
|
|
3870
|
+
children: /* @__PURE__ */ jsx9(Text8, { color: draft.herdr.autoStart ? theme.ok : theme.muted, children: draft.herdr.autoStart ? `[\xD7] ${t("common.on")}` : `[ ] ${t("common.off")}` })
|
|
3871
|
+
},
|
|
3872
|
+
entry.id
|
|
3873
|
+
) : entry.kind === "field" ? /* @__PURE__ */ jsx9(
|
|
3884
3874
|
FieldRow,
|
|
3885
3875
|
{
|
|
3886
3876
|
label: entry.label,
|
|
@@ -3913,41 +3903,31 @@ function HerdrScreen({ ctx }) {
|
|
|
3913
3903
|
children: entry.label
|
|
3914
3904
|
}
|
|
3915
3905
|
) }, entry.id)),
|
|
3906
|
+
selected === "herdrAutoStart" ? /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text8, { color: theme.muted, children: t("herdr.autoStartHint") }) }) : null,
|
|
3916
3907
|
ctx.dirty ? /* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text8, { color: theme.warn, children: t("hint.unsavedChanges") }) }) : null,
|
|
3917
3908
|
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
3918
|
-
/* @__PURE__ */ jsx9(Row, { label: t("herdr.
|
|
3919
|
-
/* @__PURE__ */ jsx9(StatusDot, { level:
|
|
3920
|
-
/* @__PURE__ */
|
|
3921
|
-
" ",
|
|
3922
|
-
registration?.registered ? t("herdr.pluginRegistered") : t("herdr.pluginMissing")
|
|
3923
|
-
] })
|
|
3924
|
-
] }) }),
|
|
3925
|
-
registration?.packageRoot ? /* @__PURE__ */ jsx9(Row, { label: "", children: /* @__PURE__ */ jsx9(Text8, { color: theme.muted, children: registration.packageRoot }) }) : null,
|
|
3926
|
-
registration?.version ? /* @__PURE__ */ jsx9(Row, { label: t("herdr.version"), children: /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
3927
|
-
/* @__PURE__ */ jsx9(StatusDot, { level: registration.versionSupported === false ? "warn" : "ok" }),
|
|
3928
|
-
/* @__PURE__ */ jsx9(Text8, { children: ` ${registration.version}` })
|
|
3909
|
+
installed?.version ? /* @__PURE__ */ jsx9(Row, { label: t("herdr.version"), children: /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
3910
|
+
/* @__PURE__ */ jsx9(StatusDot, { level: installed.supported ? "ok" : "warn" }),
|
|
3911
|
+
/* @__PURE__ */ jsx9(Text8, { children: ` ${installed.version}` })
|
|
3929
3912
|
] }) }) : null,
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
registration?.stale && registration.linkedPath ? (
|
|
3933
|
-
// A leftover link to an old source checkout is the usual state after
|
|
3934
|
-
// switching to the npm install; re-registering repoints it.
|
|
3935
|
-
/* @__PURE__ */ jsx9(Text8, { color: theme.warn, children: registration.linkedPath })
|
|
3936
|
-
) : null
|
|
3913
|
+
installed?.version && !installed.supported ? /* @__PURE__ */ jsx9(Text8, { color: theme.warn, children: t("herdr.versionOutdated", { version: installed.version, minimum: import_herdr_command.MIN_HERDR_VERSION }) }) : null,
|
|
3914
|
+
installed && !installed.ok ? /* @__PURE__ */ jsx9(Text8, { color: theme.warn, children: t("herdr.cliMissing") }) : null
|
|
3937
3915
|
] }),
|
|
3938
3916
|
/* @__PURE__ */ jsx9(Message, { text: ctx.message?.text ?? null, level: ctx.message?.level ?? "info" })
|
|
3939
3917
|
] });
|
|
3940
3918
|
}
|
|
3941
3919
|
|
|
3942
3920
|
// tui/src/screens/About.tsx
|
|
3943
|
-
import { useState as useState8 } from "react";
|
|
3921
|
+
import { useEffect as useEffect6, useState as useState8 } from "react";
|
|
3944
3922
|
import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
|
|
3945
3923
|
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3924
|
+
var registryHost = (registry) => registry.replace(/^https?:\/\//, "");
|
|
3946
3925
|
function About({ ctx }) {
|
|
3947
3926
|
const { t, draft } = ctx;
|
|
3948
3927
|
const [selected, setSelected] = useState8("auto");
|
|
3949
3928
|
const [update, setUpdate] = useState8({ phase: "idle" });
|
|
3950
|
-
const
|
|
3929
|
+
const [sourceNote, setSourceNote] = useState8(null);
|
|
3930
|
+
const checkRef = useState8(() => ({ current: { registry: "", sources: [] } }))[0];
|
|
3951
3931
|
const detected = detectLocale({ preference: "auto" });
|
|
3952
3932
|
const languageOptions = [
|
|
3953
3933
|
{ id: "auto", label: t("about.languageAuto", { detected }) },
|
|
@@ -3964,36 +3944,57 @@ function About({ ctx }) {
|
|
|
3964
3944
|
case "available":
|
|
3965
3945
|
return t("update.available", { version: update.latest });
|
|
3966
3946
|
case "updating":
|
|
3967
|
-
return t("update.updating", { version: update.latest });
|
|
3947
|
+
return update.attempt > 1 ? t("update.updatingRetry", { version: update.latest, attempt: update.attempt }) : t("update.updating", { version: update.latest });
|
|
3968
3948
|
case "done":
|
|
3969
3949
|
return t("update.done", { version: update.latest });
|
|
3970
3950
|
case "error":
|
|
3971
|
-
return t(update.messageKey);
|
|
3951
|
+
return t(update.messageKey, update.params);
|
|
3972
3952
|
default:
|
|
3973
3953
|
return t("update.check");
|
|
3974
3954
|
}
|
|
3975
3955
|
})();
|
|
3976
3956
|
const options = [...languageOptions, { id: "update", label: updateLabel }];
|
|
3957
|
+
const applyCheck = (result) => {
|
|
3958
|
+
checkRef.current = { registry: result.registry ?? "", sources: result.sources ?? [] };
|
|
3959
|
+
const behind = result.behind ?? [];
|
|
3960
|
+
setSourceNote(result.updateAvailable && behind.length > 0 && result.registry ? t("update.mirrorBehind", {
|
|
3961
|
+
registries: behind.map((entry) => `${registryHost(entry.registry)} ${entry.version ?? ""}`.trim()).join(", "),
|
|
3962
|
+
source: registryHost(result.registry)
|
|
3963
|
+
}) : null);
|
|
3964
|
+
setUpdate(result.updateAvailable ? { phase: "available", latest: result.latest } : { phase: "current", latest: result.latest });
|
|
3965
|
+
};
|
|
3966
|
+
useEffect6(() => {
|
|
3967
|
+
if (ctx.updateCheck?.ok && update.phase === "idle") applyCheck(ctx.updateCheck);
|
|
3968
|
+
}, [ctx.updateCheck]);
|
|
3977
3969
|
const runCheck = async () => {
|
|
3978
3970
|
setUpdate({ phase: "checking" });
|
|
3971
|
+
setSourceNote(null);
|
|
3979
3972
|
const result = await (0, import_updater.checkForUpdate)();
|
|
3980
3973
|
if (!result.ok) {
|
|
3981
3974
|
setUpdate({ phase: "error", messageKey: result.errorKey ?? "update.errorNetwork" });
|
|
3982
3975
|
if (result.message) ctx.notify(t("update.errorNetworkDetail", { message: result.message }), "error");
|
|
3983
3976
|
return;
|
|
3984
3977
|
}
|
|
3985
|
-
|
|
3986
|
-
|
|
3978
|
+
ctx.setUpdateCheck(result);
|
|
3979
|
+
applyCheck(result);
|
|
3987
3980
|
};
|
|
3988
3981
|
const runUpdate = async (latest) => {
|
|
3989
|
-
setUpdate({ phase: "updating", latest });
|
|
3990
|
-
const result = await (0, import_updater.performUpdate)({
|
|
3982
|
+
setUpdate({ phase: "updating", latest, attempt: 1 });
|
|
3983
|
+
const result = await (0, import_updater.performUpdate)({
|
|
3984
|
+
registry: checkRef.current.registry,
|
|
3985
|
+
sources: checkRef.current.sources,
|
|
3986
|
+
version: latest,
|
|
3987
|
+
onAttempt: ({ attempt }) => setUpdate({ phase: "updating", latest, attempt })
|
|
3988
|
+
});
|
|
3991
3989
|
if (!result.ok) {
|
|
3992
|
-
|
|
3993
|
-
|
|
3990
|
+
const params = { version: latest, installed: result.installed ?? "" };
|
|
3991
|
+
setUpdate({ phase: "error", messageKey: result.errorKey ?? "update.errorFailed", params });
|
|
3992
|
+
ctx.notify(result.summary ? t("update.errorFailedDetail", { message: result.summary }) : t(result.errorKey ?? "update.errorFailed", params), "error");
|
|
3994
3993
|
return;
|
|
3995
3994
|
}
|
|
3996
3995
|
setUpdate({ phase: "done", latest });
|
|
3996
|
+
setSourceNote(null);
|
|
3997
|
+
ctx.setUpdateCheck(null);
|
|
3997
3998
|
ctx.notify(t("update.restartHint"), "success");
|
|
3998
3999
|
};
|
|
3999
4000
|
const activateUpdate = () => {
|
|
@@ -4052,19 +4053,22 @@ function About({ ctx }) {
|
|
|
4052
4053
|
option.id
|
|
4053
4054
|
)) }),
|
|
4054
4055
|
/* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: t("update.title") }),
|
|
4055
|
-
/* @__PURE__ */
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4056
|
+
/* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
|
|
4057
|
+
/* @__PURE__ */ jsx10(
|
|
4058
|
+
Selectable,
|
|
4059
|
+
{
|
|
4060
|
+
selected: selected === "update",
|
|
4061
|
+
onSelect: () => {
|
|
4062
|
+
setSelected("update");
|
|
4063
|
+
activateUpdate();
|
|
4064
|
+
},
|
|
4065
|
+
onHover: () => setSelected("update"),
|
|
4066
|
+
children: updateLabel
|
|
4067
|
+
}
|
|
4068
|
+
),
|
|
4069
|
+
sourceNote ? /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: ` ${sourceNote}` }) : null
|
|
4070
|
+
] }),
|
|
4071
|
+
/* @__PURE__ */ jsx10(Row, { label: t("about.version"), children: /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: "0.2.17" }) }),
|
|
4068
4072
|
/* @__PURE__ */ jsx10(Row, { label: t("about.configPath"), children: /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: (0, import_config.configPath)() }) }),
|
|
4069
4073
|
/* @__PURE__ */ jsx10(Row, { label: t("about.statePath"), children: /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: (0, import_config.stateDir)() }) }),
|
|
4070
4074
|
/* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text9, { color: theme.muted, children: t("about.docs") }) }),
|
|
@@ -4381,7 +4385,11 @@ function Tab({ label, active, onSelect }) {
|
|
|
4381
4385
|
function Footer({ hints }) {
|
|
4382
4386
|
return /* @__PURE__ */ jsx12(Box11, { marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { color: theme.muted, children: hints.join(" \xB7 ") }) });
|
|
4383
4387
|
}
|
|
4384
|
-
function App({
|
|
4388
|
+
function App({
|
|
4389
|
+
initialLanguage,
|
|
4390
|
+
needsWizard,
|
|
4391
|
+
updateChecker = (0, import_updater.updateChecksEnabled)() ? import_updater.checkForUpdate : null
|
|
4392
|
+
}) {
|
|
4385
4393
|
const { exit } = useApp();
|
|
4386
4394
|
const mouse = useMouse();
|
|
4387
4395
|
const [config, setConfig] = useState10(() => (0, import_config.loadConfig)());
|
|
@@ -4393,6 +4401,18 @@ function App({ initialLanguage, needsWizard }) {
|
|
|
4393
4401
|
const [busy, setBusy] = useState10(false);
|
|
4394
4402
|
const [editingId, setEditingId] = useState10(null);
|
|
4395
4403
|
const [wizardDone, setWizardDone] = useState10(!needsWizard);
|
|
4404
|
+
const [updateCheck, setUpdateCheck] = useState10(null);
|
|
4405
|
+
useEffect7(() => {
|
|
4406
|
+
if (!updateChecker) return void 0;
|
|
4407
|
+
let cancelled = false;
|
|
4408
|
+
updateChecker().then((result) => {
|
|
4409
|
+
if (!cancelled && result?.ok) setUpdateCheck(result);
|
|
4410
|
+
}).catch(() => {
|
|
4411
|
+
});
|
|
4412
|
+
return () => {
|
|
4413
|
+
cancelled = true;
|
|
4414
|
+
};
|
|
4415
|
+
}, [updateChecker]);
|
|
4396
4416
|
const locale = useMemo4(
|
|
4397
4417
|
() => detectLocale({ preference: initialLanguage ?? draft.ui.language }),
|
|
4398
4418
|
[initialLanguage, draft.ui.language]
|
|
@@ -4419,7 +4439,7 @@ function App({ initialLanguage, needsWizard }) {
|
|
|
4419
4439
|
refresh();
|
|
4420
4440
|
});
|
|
4421
4441
|
}, [notify, refresh]);
|
|
4422
|
-
|
|
4442
|
+
useEffect7(() => {
|
|
4423
4443
|
if (!wizardDone) return void 0;
|
|
4424
4444
|
refresh();
|
|
4425
4445
|
const timer = setInterval(() => {
|
|
@@ -4428,7 +4448,7 @@ function App({ initialLanguage, needsWizard }) {
|
|
|
4428
4448
|
return () => clearInterval(timer);
|
|
4429
4449
|
}, [wizardDone, refresh, editingId]);
|
|
4430
4450
|
const mouseChoice = useRef3(null);
|
|
4431
|
-
|
|
4451
|
+
useEffect7(() => {
|
|
4432
4452
|
if (mouseChoice.current !== null) return;
|
|
4433
4453
|
if (mouse.supported && !mouse.enabled) mouse.enable();
|
|
4434
4454
|
}, [mouse.supported, mouse.enabled, mouse]);
|
|
@@ -4448,7 +4468,9 @@ function App({ initialLanguage, needsWizard }) {
|
|
|
4448
4468
|
notify,
|
|
4449
4469
|
run,
|
|
4450
4470
|
refresh,
|
|
4451
|
-
message
|
|
4471
|
+
message,
|
|
4472
|
+
updateCheck,
|
|
4473
|
+
setUpdateCheck
|
|
4452
4474
|
};
|
|
4453
4475
|
useInput9((input, key) => {
|
|
4454
4476
|
if (key.ctrl && input === "c") {
|
|
@@ -4508,9 +4530,12 @@ function App({ initialLanguage, needsWizard }) {
|
|
|
4508
4530
|
t("hint.quit")
|
|
4509
4531
|
].filter(Boolean);
|
|
4510
4532
|
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
|
|
4511
|
-
/* @__PURE__ */ jsxs11(Box11, { marginBottom: 1, children: [
|
|
4512
|
-
/* @__PURE__ */
|
|
4513
|
-
|
|
4533
|
+
/* @__PURE__ */ jsxs11(Box11, { marginBottom: 1, flexDirection: "column", children: [
|
|
4534
|
+
/* @__PURE__ */ jsxs11(Box11, { children: [
|
|
4535
|
+
/* @__PURE__ */ jsx12(Text11, { bold: true, children: t("app.name") }),
|
|
4536
|
+
/* @__PURE__ */ jsx12(Text11, { color: theme.muted, children: ` ${t("app.tagline")}` })
|
|
4537
|
+
] }),
|
|
4538
|
+
updateCheck?.updateAvailable ? /* @__PURE__ */ jsx12(Text11, { color: theme.warn, children: t("update.banner", { latest: updateCheck.latest ?? "", current: updateCheck.current }) }) : null
|
|
4514
4539
|
] }),
|
|
4515
4540
|
/* @__PURE__ */ jsx12(Box11, { marginBottom: 1, children: TABS.map((entry, entryIndex) => /* @__PURE__ */ jsx12(
|
|
4516
4541
|
Tab,
|