dsh-crossterm 0.8.13 → 0.8.15
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/CHANGELOG.md +29 -0
- package/README.md +14 -14
- package/client/client.js +52 -1
- package/lib/bus.js +1 -1
- package/lib/web.js +25 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# dsh-crossterm 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.8.15 — 2026-10-20
|
|
4
|
+
|
|
5
|
+
**为什么发这版**:YOCE_C 从 0.8.13 点更新到 0.8.14 后回归发现,没重启前 `/status` 仍只有 `version=0.8.13`、
|
|
6
|
+
`/check` 也继续报有更新 —— 因为 `installed` / `needRestart` 是 0.8.14 的**服务端**代码才加的,
|
|
7
|
+
当前进程还跑着 0.8.13,升级当次天然读不到。结论:待重启的提示不能只靠服务端新字段。
|
|
8
|
+
|
|
9
|
+
- **client 自带版本号**:`client/client.js` 顶部新增 `const CLIENT_VERSION = "0.8.15"`,随包发布,等于磁盘上的版本。
|
|
10
|
+
- **点完更新立刻提示**:`onUpdate` 从 `/update` 响应取 `installed` 与 `before`(0.8.13 起响应里本来就有 `needRestart`),
|
|
11
|
+
装成了就 `setRestartPending(true)`,不依赖服务端 `status.needRestart`。
|
|
12
|
+
- **持续推导**:渲染处按「磁盘版本 > 进程版本」判断,`needRestart = status.needRestart || restartPending || onDiskNewer`,
|
|
13
|
+
旧进程也提示得出来;「待重启」行的版本号缺失时用 `CLIENT_VERSION` 兜底。
|
|
14
|
+
- **`/check` 并列返回 `running`**(= 进程版本,与 `current` 同值),旧客户端也能照常解析。
|
|
15
|
+
|
|
16
|
+
回归:`probe-0815.mjs` 19 项。
|
|
17
|
+
|
|
18
|
+
## 0.8.14 — 2026-10-20
|
|
19
|
+
|
|
20
|
+
### 修「更新按钮看起来没用」(YOCE_C 实测反馈)
|
|
21
|
+
|
|
22
|
+
上一版起更新链路已经能装成功,但宿主进程里还是旧版本,卡片继续显示「有更新」,
|
|
23
|
+
再点一次却回「已经是最新」—— 主观上就是点了没用。
|
|
24
|
+
|
|
25
|
+
- `/crossterm/api/status` 新增 `installed`(磁盘上 node_modules 里的真实版本)与
|
|
26
|
+
`needRestart`(磁盘版本 > 进程版本)。
|
|
27
|
+
- `/crossterm/api/check`:磁盘版本已达最新但进程未重启时返回
|
|
28
|
+
`updateAvailable=false, needRestart=true`,不再报「有更新」。
|
|
29
|
+
- `/crossterm/api/update`:已装未重启时提示「已装 X,重启 DSH 后生效(当前进程还在跑 Y)」。
|
|
30
|
+
- 卡片:多一行「待重启」,更新按钮变成不可点的「已装待重启」,检查更新也提示重启后生效。
|
|
31
|
+
|
|
3
32
|
## 0.8.13 — 2026-10-20
|
|
4
33
|
|
|
5
34
|
### 优化:已经是最新就不再重装;没写收件人的信优先投给维护者
|
package/README.md
CHANGED
|
@@ -28,25 +28,25 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
|
|
|
28
28
|
> `locatePkgJson()` 对子路径行返回 undefined),而插件页顶部标题与这一行共用同一份 `locale/zh.json` 的 meta,
|
|
29
29
|
> 所以它单独做入口(配置卡片 + 自动更新开关),标题带「(自更新)」;卡片头用 `shortTitle` 显示短名「跨端互通」。
|
|
30
30
|
|
|
31
|
-
**本版更新(0.8.
|
|
31
|
+
**本版更新(0.8.15)**
|
|
32
32
|
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- 本机是 `link:` 源码版,点更新仍提示「改源码后重启」;以上对用 npm 安装的其他设备生效。
|
|
33
|
+
- **升级当次就能提示「待重启」**:以前这一步要等服务端换成新版才认得出来(YOCE_C 回归的第 3/4 步就是这样「看不见」)。
|
|
34
|
+
现在浏览器 client 自己带版本号(`CLIENT_VERSION`),点完更新就从 `/update` 响应的 `installed` / `before` 判断,
|
|
35
|
+
立刻把卡片切成「已装待重启」;渲染时还会按「磁盘版本 > 进程版本」持续推导,旧进程也提示得出来。
|
|
36
|
+
- `/check` 多返回一个 `running`(= 进程版本),与 `current` 并列,旧客户端也能照常解析。
|
|
37
|
+
- 本机是 `link:` 源码版,没有这层差别;以上对用 npm 安装的设备生效。
|
|
39
38
|
|
|
40
|
-
**上一版(0.8.
|
|
39
|
+
**上一版(0.8.14)**
|
|
41
40
|
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
41
|
+
- **「更新」按钮不再看起来没用**:装完新版但还没重启时,卡片多一行「待重启」、
|
|
42
|
+
更新按钮变成不可点的「已装待重启」,接口 `/check` 改回 `needRestart=true` 而不是继续报「有更新」。
|
|
43
|
+
- 根因是状态表达:宿主进程里还是旧版本号,而磁盘上已经装了新版 —— 现在这两个版本分开显示。
|
|
44
|
+
- 本机是 `link:` 源码版(磁盘版本永远等于源码版本),所以这行只会在 npm 安装的设备上出现。
|
|
46
45
|
|
|
47
|
-
**更早(0.8.11 / 0.8.10 / 0.8.9 / 0.8.8 / 0.8.7 / 0.8.6 / 0.8.5 / 0.8.0)**
|
|
46
|
+
**更早(0.8.13 / 0.8.12 / 0.8.11 / 0.8.10 / 0.8.9 / 0.8.8 / 0.8.7 / 0.8.6 / 0.8.5 / 0.8.0)**
|
|
48
47
|
|
|
49
|
-
- 0.8.
|
|
48
|
+
- 0.8.13:已经是最新就不装;没写收件人的信优先投给 `MAINTAINERS.md` 里的维护者会话。
|
|
49
|
+
- 0.8.12 / 0.8.11:修「点更新必定失败」(pnpm 不认 `--prefer-online`)、装低自动回滚、最新版取两个源的最大值。
|
|
50
50
|
- 0.8.10 / 0.8.9 / 0.8.8 / 0.8.7:云端设备只列别人、带权限与状态、版本仅在差异时显示;插件页标题改「跨端互通(自更新)」;
|
|
51
51
|
入口组件从「总开关」改为「自更新开关」,四个业务组件互不依赖。
|
|
52
52
|
- 0.8.5:负责人留痕 `MAINTAINERS.md`、运行日志可轮转、卡片「维护者」行与「运行日志」按钮。
|
package/client/client.js
CHANGED
|
@@ -8,6 +8,21 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
const React = require("react");
|
|
9
9
|
const h = React.createElement;
|
|
10
10
|
|
|
11
|
+
// 0.8.15:这份 client 随包发布,它的版本号就是「磁盘上的版本」。
|
|
12
|
+
// 服务端进程可能还是旧版(装完没重启),拿它跟 /status.version 一比就知道该不该提示重启 ——
|
|
13
|
+
// 这样绕开了「旧进程读不到新字段」的死角(YOCE_C 的回归结论)。
|
|
14
|
+
const CLIENT_VERSION = "0.8.15";
|
|
15
|
+
|
|
16
|
+
const cmpVer = (a, b) => {
|
|
17
|
+
const pa = String(a || "").split(".").map((x) => Number(x) || 0);
|
|
18
|
+
const pb = String(b || "").split(".").map((x) => Number(x) || 0);
|
|
19
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
20
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
21
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
22
|
+
}
|
|
23
|
+
return 0;
|
|
24
|
+
};
|
|
25
|
+
|
|
11
26
|
const S = {
|
|
12
27
|
card: {
|
|
13
28
|
border: "1px solid rgba(128,128,128,.28)",
|
|
@@ -246,6 +261,10 @@ window.__ModuleLoader__.load({
|
|
|
246
261
|
const [showLog, setShowLog] = React.useState(false);
|
|
247
262
|
const [runLog, setRunLog] = React.useState(null);
|
|
248
263
|
const [showRunLog, setShowRunLog] = React.useState(false);
|
|
264
|
+
// 0.8.15:本次页面会话里「刚装完、还没重启」的一次性标记。
|
|
265
|
+
// 旧版服务端不会返回 status.installed / needRestart,但 /update 的响应里一直带着
|
|
266
|
+
// installed 与 before(0.8.13 起还带 needRestart),所以升级当次就能立刻提示。
|
|
267
|
+
const [restartPending, setRestartPending] = React.useState(false);
|
|
249
268
|
|
|
250
269
|
const load = React.useCallback(() => {
|
|
251
270
|
call("/crossterm/api/status")
|
|
@@ -310,6 +329,8 @@ window.__ModuleLoader__.load({
|
|
|
310
329
|
try {
|
|
311
330
|
const data = await call("/crossterm/api/check", "POST");
|
|
312
331
|
if (!data || !data.ok) setMsg("检查失败:" + ((data && data.message) || "未知错误"));
|
|
332
|
+
else if (data.needRestart || (data.installed && cmpVer(data.installed, data.current) > 0))
|
|
333
|
+
setMsg(`已装 ${data.installed || "新版"},重启 DSH 后生效(当前进程还在跑 ${data.current})`);
|
|
313
334
|
else if (data.updateAvailable) setMsg(`发现新版本 ${data.latest}(当前 ${data.current})`);
|
|
314
335
|
else if (data.ahead) setMsg(`本地 ${data.current} 领先已发布版 ${data.latest}`);
|
|
315
336
|
else setMsg(`已是最新版本 ${data.current}`);
|
|
@@ -326,6 +347,14 @@ window.__ModuleLoader__.load({
|
|
|
326
347
|
try {
|
|
327
348
|
const data = await call("/crossterm/api/update", "POST");
|
|
328
349
|
const text = (data && data.message) || (data && data.ok ? "已更新" : "未能更新");
|
|
350
|
+
// 0.8.15:装完就记一笔「待重启」。旧版服务端的 /status 不返回 installed/needRestart,
|
|
351
|
+
// 但 /update 的响应里有 installed / before(0.8.13 起还有 needRestart),够判断了。
|
|
352
|
+
const after = data && (data.installed || "");
|
|
353
|
+
const beforeVer = data && (data.before || "");
|
|
354
|
+
const justInstalled = Boolean(
|
|
355
|
+
data && data.ok && (data.needRestart === true || (after && beforeVer && after !== beforeVer)),
|
|
356
|
+
);
|
|
357
|
+
if (justInstalled) setRestartPending(true);
|
|
329
358
|
const steps = (data && data.steps) || [];
|
|
330
359
|
// 失败时把每条尝试(dsh / DSH 自带 cli / pnpm)的退出码与输出带上,方便远端排查
|
|
331
360
|
const detail = data && data.ok
|
|
@@ -334,6 +363,7 @@ window.__ModuleLoader__.load({
|
|
|
334
363
|
? "\n\n" + steps.map((s) => `[${s.label}] 退出码 ${s.code} · ${Math.round((s.ms || 0) / 1000)}s` + (s.out ? `\n${String(s.out).slice(-400)}` : "")).join("\n\n")
|
|
335
364
|
: "";
|
|
336
365
|
setMsg(text + detail);
|
|
366
|
+
if (justInstalled) void load();
|
|
337
367
|
} catch (error) {
|
|
338
368
|
setMsg("更新失败:" + String((error && error.message) || error));
|
|
339
369
|
} finally {
|
|
@@ -446,6 +476,12 @@ window.__ModuleLoader__.load({
|
|
|
446
476
|
|
|
447
477
|
const version = status ? "v" + status.version : "";
|
|
448
478
|
const mode = status && status.install ? status.install.mode : "";
|
|
479
|
+
// 0.8.14/0.8.15:磁盘版本 > 进程版本 = 已装好但还没重启。
|
|
480
|
+
// 磁盘版本优先看服务端的 status.installed;旧版服务端没这个字段时用 client 自带的版本号兜底。
|
|
481
|
+
const diskVer = (status && status.installed) || (status ? CLIENT_VERSION : "");
|
|
482
|
+
const onDiskNewer = Boolean(status && status.version && diskVer && cmpVer(status.version, diskVer) < 0);
|
|
483
|
+
const installedVer = (status && status.installed) || (onDiskNewer ? CLIENT_VERSION : "");
|
|
484
|
+
const needRestart = Boolean((status && status.needRestart) || restartPending || onDiskNewer);
|
|
449
485
|
const cloud = (status && status.cloud) || {};
|
|
450
486
|
const autoUpdate = (status && status.autoUpdate) || {};
|
|
451
487
|
const home = (status && status.home) || null;
|
|
@@ -498,6 +534,12 @@ window.__ModuleLoader__.load({
|
|
|
498
534
|
)
|
|
499
535
|
: null,
|
|
500
536
|
h(FriendlyRow, { label: "当前版本", value: status ? `v${status.version}${mode === "link" ? "(源码 link:)" : ""}` : "" }),
|
|
537
|
+
needRestart
|
|
538
|
+
? h(FriendlyRow, {
|
|
539
|
+
label: "待重启",
|
|
540
|
+
value: `已装 v${installedVer || "新版"},重启 DSH 后生效(当前进程还在跑 v${status && status.version})`,
|
|
541
|
+
})
|
|
542
|
+
: null,
|
|
501
543
|
h(FriendlyRow, {
|
|
502
544
|
label: "当前终端",
|
|
503
545
|
value: status ? `${status.node}(${status.nodeId})· ${status.hostname}` : "",
|
|
@@ -613,7 +655,16 @@ window.__ModuleLoader__.load({
|
|
|
613
655
|
"div",
|
|
614
656
|
{ style: S.actions },
|
|
615
657
|
h("button", { style: S.btn, onClick: onCheck, disabled: busy !== "" }, busy === "check" ? "检查中…" : "检查更新"),
|
|
616
|
-
h(
|
|
658
|
+
h(
|
|
659
|
+
"button",
|
|
660
|
+
{
|
|
661
|
+
style: needRestart ? S.btn : S.btnPrimary,
|
|
662
|
+
onClick: needRestart ? undefined : onUpdate,
|
|
663
|
+
disabled: busy !== "" || needRestart,
|
|
664
|
+
title: needRestart ? `已装 v${installedVer},重启 DSH 后生效` : "检查并安装最新版本",
|
|
665
|
+
},
|
|
666
|
+
busy === "update" ? "更新中…" : needRestart ? "已装待重启" : "更新",
|
|
667
|
+
),
|
|
617
668
|
h("button", { style: S.btn, onClick: () => void loadDevices(), disabled: Boolean(devices && devices.loading) }, devices && devices.loading ? "读取中…" : "刷新设备"),
|
|
618
669
|
h("button", { style: S.btn, onClick: onRelayCloud, disabled: busy !== "" }, busy === "relay" ? "收信中…" : "收信"),
|
|
619
670
|
h("button", { style: S.btn, onClick: onBackupNow, disabled: busy !== "" }, busy === "backup" ? "备份中…" : "备份会话"),
|
package/lib/bus.js
CHANGED
|
@@ -23,7 +23,7 @@ import path from 'node:path'
|
|
|
23
23
|
import crypto from 'node:crypto'
|
|
24
24
|
import { rotateIfNeeded } from './logs.js'
|
|
25
25
|
|
|
26
|
-
export const VERSION = '0.8.
|
|
26
|
+
export const VERSION = '0.8.15'
|
|
27
27
|
|
|
28
28
|
/** DSH 的家目录:与宿主其它状态(sessions / profiles)同一个根,多终端天然共享 */
|
|
29
29
|
export function dshHome() {
|
package/lib/web.js
CHANGED
|
@@ -502,6 +502,10 @@ function statusOf(term) {
|
|
|
502
502
|
const cfg = term.cfg || {}
|
|
503
503
|
const info = profileInfo(cfg.busDir)
|
|
504
504
|
const mode = installMode(info && info.spec)
|
|
505
|
+
// 0.8.14:磁盘上装着的版本可能比进程内存里的 VERSION 新(更新完还没重启),
|
|
506
|
+
// 这个状态必须能表达出来,否则卡片一直显示「有更新」、再点又说「已是最新」。
|
|
507
|
+
const onDisk = installedVersion(info) || ''
|
|
508
|
+
const pendingRestart = Boolean(onDisk && compareVersion(onDisk, VERSION) > 0)
|
|
505
509
|
const chains = []
|
|
506
510
|
try {
|
|
507
511
|
for (const link of term.links.values()) if (!chains.includes(link.code)) chains.push(link.code)
|
|
@@ -516,6 +520,8 @@ function statusOf(term) {
|
|
|
516
520
|
return {
|
|
517
521
|
ok: true,
|
|
518
522
|
version: VERSION,
|
|
523
|
+
installed: onDisk,
|
|
524
|
+
needRestart: pendingRestart,
|
|
519
525
|
node: cfg.name || '',
|
|
520
526
|
nodeId: term.nodeId || '',
|
|
521
527
|
hostname: os.hostname(),
|
|
@@ -847,9 +853,17 @@ export function registerWeb(ctx, term, log) {
|
|
|
847
853
|
const latest = await latestVersion()
|
|
848
854
|
// 只有 npm 上的版本「更新」才算有更新:本地源码版常领先于已发布版,
|
|
849
855
|
// 用 !== 会把 0.3.32 说成「发现新版本 0.3.31」。
|
|
850
|
-
const
|
|
851
|
-
const
|
|
852
|
-
|
|
856
|
+
const info = profileInfo(term.cfg && term.cfg.busDir)
|
|
857
|
+
const installed = installedVersion(info) || ''
|
|
858
|
+
// 磁盘上已经装上新版、只是还没重启时:不再报「有更新」,改报「待重启」,
|
|
859
|
+
// 否则用户会以为「点了更新没用」。
|
|
860
|
+
const needRestart = Boolean(installed && compareVersion(installed, latest) >= 0 && compareVersion(installed, VERSION) > 0)
|
|
861
|
+
const base = installed && compareVersion(installed, VERSION) > 0 ? installed : VERSION
|
|
862
|
+
const updateAvailable = !needRestart && compareVersion(latest, base) > 0
|
|
863
|
+
const ahead = !needRestart && compareVersion(VERSION, latest) > 0
|
|
864
|
+
// current 是「进程正在跑的版本」,installed 是「磁盘上的版本」,两个都给,
|
|
865
|
+
// 别让客户端只能靠猜(0.8.15 起多给一个 running 别名,语义更直白)。
|
|
866
|
+
json(res, 200, { ok: true, current: VERSION, running: VERSION, installed, latest, updateAvailable, ahead, needRestart })
|
|
853
867
|
} catch (error) {
|
|
854
868
|
json(res, 200, { ok: false, message: String((error && error.message) || error) })
|
|
855
869
|
}
|
|
@@ -999,7 +1013,14 @@ export function registerWeb(ctx, term, log) {
|
|
|
999
1013
|
const steps = (result.steps || []).map((s) => ({ label: s.label, code: s.code, ms: s.ms, out: s.out }))
|
|
1000
1014
|
let message
|
|
1001
1015
|
if (result.ok && result.updated) message = `已更新到 ${installed},重启 DSH 后生效`
|
|
1002
|
-
else if (result.ok)
|
|
1016
|
+
else if (result.ok) {
|
|
1017
|
+
// 已经装上了新版、只是进程还是旧的:这时候要说清「重启后生效」,
|
|
1018
|
+
// 不能只说「已经是最新」,否则用户会觉得点了没反应。
|
|
1019
|
+
const pending = Boolean(installed && compareVersion(installed, VERSION) > 0)
|
|
1020
|
+
message = pending
|
|
1021
|
+
? `已装 ${installed},重启 DSH 后生效(当前进程还在跑 ${VERSION})`
|
|
1022
|
+
: `已经是最新(${installed || latest}),不用更新`
|
|
1023
|
+
}
|
|
1003
1024
|
else if (result.downgraded) {
|
|
1004
1025
|
message = result.rollback && result.rollback.ok
|
|
1005
1026
|
? `更新把版本装低了(原 ${result.before},装成更旧版本),已恢复到 ${installed};请重试`
|