dsh-crossterm 0.8.11 → 0.8.13
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 +30 -0
- package/README.md +22 -33
- package/lib/bus.js +1 -1
- package/lib/index.js +12 -3
- package/lib/web.js +48 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# dsh-crossterm 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.8.13 — 2026-10-20
|
|
4
|
+
|
|
5
|
+
### 优化:已经是最新就不再重装;没写收件人的信优先投给维护者
|
|
6
|
+
|
|
7
|
+
来源:跨设备邮件(YOCE_C 的 0.8.12 验收报告,经 QQ 会话转交,2026-10-20T07:55:35Z)的两条观察。
|
|
8
|
+
|
|
9
|
+
- **已是最新就短路**:`updatePlugin()` 在 `latest && before && compareVersion(latest, before) <= 0` 时直接返回
|
|
10
|
+
`{ok:true, updated:false, skipped:'latest'}`,不再跑安装 —— 实测等于最新时仍会花 15.7s 重写 node_modules。
|
|
11
|
+
- **无收件人的信优先给维护者**:`relayDeliver()` 的兜底顺序改为「MAINTAINERS.md 主维护会话 → 最近活跃会话 → 第一个会话」,
|
|
12
|
+
回执文案区分「按维护者 MAINTAINERS.md」与「按最近活跃会话」。修 YOCE_C 的验收报告被投进 `im:qq_39` 的问题。
|
|
13
|
+
- **不用跟着动手**:直接升级即可,配置与云端数据格式都没变。
|
|
14
|
+
|
|
15
|
+
## 0.8.12 — 2026-10-20
|
|
16
|
+
|
|
17
|
+
### 修复:更新链路在 pnpm 11 上必定失败;latest 被镜像滞后带偏
|
|
18
|
+
|
|
19
|
+
来源:跨设备邮件(YOCE_C 经「同端多会话2」转发,2026-10-20T06:20:28Z)—— 0.8.11 复测报告。
|
|
20
|
+
|
|
21
|
+
- **`--prefer-online` 是 pnpm 11 的非法选项**:0.8.11 把它拼进了 dsh / cli / pnpm 三级兜底,
|
|
22
|
+
B 机实测 `[ERROR] Unknown option: 'prefer-online'`,exit 1 ⇒ 三条路一起挂。
|
|
23
|
+
现在只给 pnpm 那步加它认识的 `--config.prefer-offline=false`;dsh / cli 两条路不加附加项。
|
|
24
|
+
- **24h 发布年龄策略**:新版本发布不到 24 小时会被 `minimumReleaseAge` 拒装
|
|
25
|
+
(`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`)。失败输出命中该关键字时,用
|
|
26
|
+
`--config.minimumReleaseAge=0` 重试一次,并把「已临时放宽」记进 `logs/update.log`。
|
|
27
|
+
- **`latestVersion()` 取最大版本**:原来「第一个能返回的 registry 就用」,而顺序是 npmmirror 优先,
|
|
28
|
+
镜像滞后时会把 0.8.11 读成 0.8.10(钉版本也会钉到旧号)。现在两源都问、取最大版本,
|
|
29
|
+
请求带 `cache-control: no-cache`。
|
|
30
|
+
- 探针 `probe-0811.mjs` 扩到 **25 项全 PASS**(新增「pnpm 合法选项」「不再出现 --prefer-online 参数」
|
|
31
|
+
「minimumReleaseAge 重试」「两源取最大」四项,替换掉原来断言 --prefer-online 的两项)。
|
|
32
|
+
|
|
3
33
|
## 0.8.11 — 2026-10-20
|
|
4
34
|
|
|
5
35
|
### 更新不再把版本装低(YOCE_C 实测事故)
|
package/README.md
CHANGED
|
@@ -28,39 +28,28 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
|
|
|
28
28
|
> `locatePkgJson()` 对子路径行返回 undefined),而插件页顶部标题与这一行共用同一份 `locale/zh.json` 的 meta,
|
|
29
29
|
> 所以它单独做入口(配置卡片 + 自动更新开关),标题带「(自更新)」;卡片头用 `shortTitle` 显示短名「跨端互通」。
|
|
30
30
|
|
|
31
|
-
**本版更新(0.8.
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
离线时间按 `at → lastSeen → updatedAt` 先归一毫秒再排。
|
|
54
|
-
- **云端设备只列「别人」**:卡片里那行不再显示本机(自己已经在「当前终端」一栏),
|
|
55
|
-
只列同一账号下的其他设备。
|
|
56
|
-
- **每台带权限和状态**:逐台显示 `名称 · 管理员/成员 · 在线/离线(x 分钟前)· v版本号`;
|
|
57
|
-
管理员就是云端 `_account.json` 里 `ownerDevice` 那台,其余都是成员。
|
|
58
|
-
- `term_list` 的云端行也带角色。
|
|
59
|
-
|
|
60
|
-
- 0.8.5:负责人留痕 `MAINTAINERS.md`、运行日志可轮转(5 MB 压 `logs/archive/` 留 20 份)、
|
|
61
|
-
卡片「维护者」行与「运行日志」按钮、`GET /crossterm/api/logs`。
|
|
62
|
-
- 0.8.2:死信按 20s/1m/5m/15m/1h 退避,8 次后进 `dead/`;收件人写设备名会回退「最近活跃会话」;
|
|
63
|
-
同一个信封不会投两次;`/crossterm/api/*` 要求本机回环 `Host`。
|
|
31
|
+
**本版更新(0.8.13)**
|
|
32
|
+
|
|
33
|
+
- **已经是最新就不装**:点「更新」时若 npm 最新版 ≤ 当前版本,直接回「已经是最新(0.8.12),不用更新」,
|
|
34
|
+
不再跑一次 15 秒的安装、也不再重写 `node_modules`。
|
|
35
|
+
- **没写收件人的信优先投给维护者**:跨设备信封没写收件人(或写的名字本机认不出)时,原来按「最近活跃会话」投,
|
|
36
|
+
可能落进 QQ 或无关会话 —— YOCE_C 的 0.8.12 验收报告就投到了 `im:qq_39`。现在先找 `MAINTAINERS.md` 里的
|
|
37
|
+
主维护会话,命中就用它,否则仍按最近活跃投。
|
|
38
|
+
- 本机是 `link:` 源码版,点更新仍提示「改源码后重启」;以上对用 npm 安装的其他设备生效。
|
|
39
|
+
|
|
40
|
+
**上一版(0.8.12)**
|
|
41
|
+
|
|
42
|
+
- **修「点更新必定失败」**:0.8.11 给三级兜底都加了 `--prefer-online`,而这是 pnpm 11 的非法选项;
|
|
43
|
+
现在只给 pnpm 那步加它认识的 `--config.prefer-offline=false`。
|
|
44
|
+
- **新版本发布不到 24h 也能装**:撞 `minimumReleaseAge` 策略时自动用 `--config.minimumReleaseAge=0` 重试一次。
|
|
45
|
+
- **npm 最新版读得准了**:两个源都问、取最大版本(原来谁先返回用谁,镜像滞后会把版本号读旧)。
|
|
46
|
+
|
|
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)**
|
|
48
|
+
|
|
49
|
+
- 0.8.11:钉死版本号、装低自动回滚;同 id 邮件副本不再各自重投。
|
|
50
|
+
- 0.8.10 / 0.8.9 / 0.8.8 / 0.8.7:云端设备只列别人、带权限与状态、版本仅在差异时显示;插件页标题改「跨端互通(自更新)」;
|
|
51
|
+
入口组件从「总开关」改为「自更新开关」,四个业务组件互不依赖。
|
|
52
|
+
- 0.8.5:负责人留痕 `MAINTAINERS.md`、运行日志可轮转、卡片「维护者」行与「运行日志」按钮。
|
|
64
53
|
- 0.8.0 安全加固:附件名净化 + 越界校验、写接口要 `x-crossterm-token`、信封 HMAC 签名、入站限额、按 id 去重。
|
|
65
54
|
|
|
66
55
|
## 一、安装与卸载
|
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.13'
|
|
27
27
|
|
|
28
28
|
/** DSH 的家目录:与宿主其它状态(sessions / profiles)同一个根,多终端天然共享 */
|
|
29
29
|
export function dshHome() {
|
package/lib/index.js
CHANGED
|
@@ -41,7 +41,7 @@ import { createCloudHome } from './cloud/home.js'
|
|
|
41
41
|
import { runSetup } from './cloud/setup.js'
|
|
42
42
|
import { deviceRow, matchDevice } from './cloud/match.js'
|
|
43
43
|
import { registerWeb, selfUpdate } from './web.js'
|
|
44
|
-
import { recordOwnership } from './logs.js'
|
|
44
|
+
import { recordOwnership, readMaintainersDoc } from './logs.js'
|
|
45
45
|
|
|
46
46
|
const PLUGIN = 'dsh-crossterm'
|
|
47
47
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
@@ -852,13 +852,22 @@ class CrossTerm {
|
|
|
852
852
|
let fallbackNote = ''
|
|
853
853
|
if (!targets.length) {
|
|
854
854
|
const list = this.sessions()
|
|
855
|
-
|
|
855
|
+
// 0.8.13:信封没写收件人(或写的名字本机认不出)时,优先投给 MAINTAINERS.md 里的主维护会话,
|
|
856
|
+
// 别让跨设备的维护信落进 QQ / 无关会话 —— YOCE_C 的 0.8.12 验收报告就这样投到了 im:qq_39。
|
|
857
|
+
const doc = readMaintainersDoc(ROOT)
|
|
858
|
+
const owner = doc && doc.current
|
|
859
|
+
const ownerIds = owner ? [owner.short, owner.session, owner.id].map((v) => String(v || '').trim()).filter(Boolean) : []
|
|
860
|
+
const owned = ownerIds.length
|
|
861
|
+
? list.find((s) => s && ownerIds.some((id) => s.id === id || s.short === id || String(s.id || '').startsWith(id)))
|
|
862
|
+
: null
|
|
863
|
+
const latest = owned || list.find((s) => s && s.active) || list[0]
|
|
856
864
|
if (!latest) return { ok: false, reason: `收件人「${rawTargets.join('、') || '空'}」在本机找不到会话,本机也没有可投递的会话` }
|
|
857
865
|
targets = [latest.name || latest.short]
|
|
858
866
|
const what = rawTargets.length ? `收件人「${rawTargets.join('、')}」不是本机会话名` : '信封没写收件人'
|
|
859
867
|
const selfNames = [this.nodeId, this.name, os.hostname()].map((v) => String(v || '').trim().toLowerCase())
|
|
860
868
|
const deviceHint = rawTargets.some((t) => selfNames.includes(String(t).trim().toLowerCase())) ? '(写的是本机设备名)' : ''
|
|
861
|
-
|
|
869
|
+
const how = owned ? '按维护者 MAINTAINERS.md' : '按「最近活跃会话」'
|
|
870
|
+
fallbackNote = `(${what}${deviceHint},${how}投给「${targets[0]}」)`
|
|
862
871
|
} else if (unknown.length) {
|
|
863
872
|
fallbackNote = `(收件人「${unknown.join('、')}」没匹配到会话,已投给能匹配的 ${targets.length} 个)`
|
|
864
873
|
}
|
package/lib/web.js
CHANGED
|
@@ -205,6 +205,10 @@ function compareVersion(a, b) {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
async function latestVersion() {
|
|
208
|
+
// 0.8.12:原来「第一个能返回的 registry 就用」+ 顺序是 npmmirror 优先 → 镜像滞后时
|
|
209
|
+
// 会把 0.8.11 读成 0.8.10,钉版本也钉到旧号(YOCE_C 2026-10-20 实测)。
|
|
210
|
+
// 现在两源都问,取最大的那个版本;单源失败不影响。
|
|
211
|
+
const found = []
|
|
208
212
|
let last = ''
|
|
209
213
|
for (const registry of NPM_REGISTRIES) {
|
|
210
214
|
const ctrl = new AbortController()
|
|
@@ -212,14 +216,17 @@ async function latestVersion() {
|
|
|
212
216
|
try {
|
|
213
217
|
const res = await fetch(`${registry}/${PKG}/latest`, {
|
|
214
218
|
signal: ctrl.signal,
|
|
215
|
-
headers: { accept: 'application/json' },
|
|
219
|
+
headers: { accept: 'application/json', 'cache-control': 'no-cache' },
|
|
216
220
|
})
|
|
217
221
|
if (!res.ok) {
|
|
218
222
|
last = `HTTP ${res.status}`
|
|
219
223
|
continue
|
|
220
224
|
}
|
|
221
225
|
const doc = await res.json()
|
|
222
|
-
if (doc && typeof doc.version === 'string')
|
|
226
|
+
if (doc && typeof doc.version === 'string') {
|
|
227
|
+
found.push(doc.version)
|
|
228
|
+
continue
|
|
229
|
+
}
|
|
223
230
|
last = 'no version field'
|
|
224
231
|
} catch (error) {
|
|
225
232
|
last = String((error && error.message) || error)
|
|
@@ -227,6 +234,7 @@ async function latestVersion() {
|
|
|
227
234
|
clearTimeout(timer)
|
|
228
235
|
}
|
|
229
236
|
}
|
|
237
|
+
if (found.length > 0) return found.sort(compareVersion).pop()
|
|
230
238
|
throw new Error(last || 'unknown')
|
|
231
239
|
}
|
|
232
240
|
|
|
@@ -369,10 +377,31 @@ function appendUpdateLog(cfg, record) {
|
|
|
369
377
|
*/
|
|
370
378
|
async function updatePlugin(term, info, latest) {
|
|
371
379
|
// 0.8.11:不再用 @latest —— pnpm 可能拿缓存里的旧 packument,把机器「更新」成老版本
|
|
372
|
-
// (YOCE_C 实测 0.8.2 → 0.6.1
|
|
380
|
+
// (YOCE_C 实测 0.8.2 → 0.6.1,还判成功)。已知确切版本就钉版本号。
|
|
381
|
+
// 0.8.12:--prefer-online 是 pnpm 11 的非法选项([ERROR] Unknown option),
|
|
382
|
+
// 三级兜底会一起挂;pnpm 只认 --config.prefer-offline=false,所以改成只给 pnpm 步加这条。
|
|
373
383
|
const pin = latest ? `${PKG}@${latest}` : ''
|
|
374
384
|
const target = pin || `${PKG}@latest`
|
|
375
385
|
const before = installedVersion(info)
|
|
386
|
+
// 0.8.13:已经是最新就直接返回,不再白跑一次安装(YOCE_C 实测:等于最新时仍跑了 15.7s
|
|
387
|
+
// 重写 node_modules)。only-when-newer 也避免 @latest 把机器降级。
|
|
388
|
+
if (latest && before && compareVersion(latest, before) <= 0) {
|
|
389
|
+
const out = {
|
|
390
|
+
profile: info.name,
|
|
391
|
+
ok: true,
|
|
392
|
+
updated: false,
|
|
393
|
+
skipped: 'latest',
|
|
394
|
+
via: 'none',
|
|
395
|
+
before,
|
|
396
|
+
installed: before,
|
|
397
|
+
latest,
|
|
398
|
+
downgraded: false,
|
|
399
|
+
rollback: null,
|
|
400
|
+
steps: [],
|
|
401
|
+
}
|
|
402
|
+
appendUpdateLog(term.cfg, out)
|
|
403
|
+
return out
|
|
404
|
+
}
|
|
376
405
|
const steps = []
|
|
377
406
|
const record = async (label, cmd, args, opts) => {
|
|
378
407
|
const started = Date.now()
|
|
@@ -408,23 +437,33 @@ async function updatePlugin(term, info, latest) {
|
|
|
408
437
|
|
|
409
438
|
const { cmd: node, env } = nodeTool(term.cfg)
|
|
410
439
|
const cli = cliScript()
|
|
411
|
-
|
|
412
|
-
|
|
440
|
+
// pnpm 11:--prefer-online 不是合法选项;prefer-offline 只能通过 --config. 前缀关掉。
|
|
441
|
+
// 新版本发布不到 24h 还会撞 minimumReleaseAge 策略,失败时用 policyArgs 重试一次并记日志。
|
|
442
|
+
const pnpmFlags = ['--config.prefer-offline=false']
|
|
443
|
+
const policyArgs = ['--config.minimumReleaseAge=0']
|
|
444
|
+
const policyRe = /minimum[-_]?release[-_]?age/i
|
|
445
|
+
const plan = [{ label: 'dsh', cmd: 'dsh', args: ['plugin', '--profile', info.name, 'add', target], opts: {} }]
|
|
413
446
|
if (fs.existsSync(cli)) {
|
|
414
447
|
plan.push({
|
|
415
448
|
label: 'cli',
|
|
416
449
|
cmd: process.execPath,
|
|
417
|
-
args: ['--expose-internals', cli, 'plugin', '--profile', info.name, 'add', target
|
|
450
|
+
args: ['--expose-internals', cli, 'plugin', '--profile', info.name, 'add', target],
|
|
418
451
|
opts: { shell: false, env: { ELECTRON_RUN_AS_NODE: '1' } },
|
|
419
452
|
})
|
|
420
453
|
}
|
|
421
454
|
for (const pnpm of pnpmCandidates(term.cfg)) {
|
|
422
|
-
plan.push({ label: 'pnpm', cmd: node, args: [pnpm, 'add', target, ...
|
|
455
|
+
plan.push({ label: 'pnpm', cmd: node, args: [pnpm, 'add', target, ...pnpmFlags], opts: { shell: false, env } })
|
|
423
456
|
}
|
|
424
457
|
|
|
425
458
|
let last = null
|
|
426
|
-
for (const
|
|
427
|
-
|
|
459
|
+
for (const base of plan) {
|
|
460
|
+
let step = base
|
|
461
|
+
let res = await record(step.label, step.cmd, step.args, step.opts)
|
|
462
|
+
if (res.code !== 0 && policyRe.test(res.out || '')) {
|
|
463
|
+
const args = [...step.args, ...policyArgs]
|
|
464
|
+
res = await record(`${step.label}+release-age`, step.cmd, args, step.opts)
|
|
465
|
+
step = { ...step, args }
|
|
466
|
+
}
|
|
428
467
|
last = res
|
|
429
468
|
if (res.code !== 0) continue
|
|
430
469
|
const result = await settle(step.label, step)
|