dsh-crossterm 0.6.0 → 0.6.2
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 +39 -0
- package/README.md +11 -9
- package/client/client.js +62 -1
- package/cordis.patch.yml +18 -6
- package/lib/bus.js +1 -1
- package/lib/cloud/layout.js +27 -8
- package/lib/index.js +2 -1
- package/lib/web.js +74 -1
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# dsh-crossterm 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.6.2 — 2026-10-19
|
|
4
|
+
|
|
5
|
+
### 插件页:插件名/简介/组件表/更新日志
|
|
6
|
+
- 插件改名:`locale/zh.json` 的 `meta.title` 从「跨会话」改为 **跨端互通**(英文 `CrossTerm`),
|
|
7
|
+
`meta.description` 写成插件级简介(跨会话/跨终端/跨设备/云端中转与备份,一句话说清)。
|
|
8
|
+
**四个业务组件名不变**:跨会话 / 跨终端 / 跨设备 / 跨账号。
|
|
9
|
+
- `cordis.patch.yml` 结构变化:原来第一行用裸包名 `dsh-crossterm` 承担「跨会话」,
|
|
10
|
+
现在拆成两行 —— 第一行 `dsh-crossterm/parts/session`(跨会话),**末尾新增裸包名行**
|
|
11
|
+
`dsh-crossterm`(`config.part: ui`,不挂任何工具,只承载浏览器半端的配置卡片与插件标题)。
|
|
12
|
+
原因:`dsh-client-modules` 只对裸包名行加载 client bundle(`locatePkgJson()` 对子路径行返回
|
|
13
|
+
undefined),而插件页顶部标题与裸包名行共用同一份 locale meta,二者无法分成两个名字;
|
|
14
|
+
把裸行放到组件列表末尾,前四行仍是四个业务组件。
|
|
15
|
+
- `lib/index.js`:`PARTS` 增 `ui`,`PART_LABEL.ui = '入口(插件卡片)'`;`ui` 不映射任何工具
|
|
16
|
+
(`TOOL_PART` 里没有它,`buildTools()` 过滤后为空),所以不会重复注册工具。
|
|
17
|
+
- `lib/web.js` 新增 `GET /crossterm/api/about`:返回 `title` / `description` / `components[]`
|
|
18
|
+
(读 `parts/*.manifest.json`,与组件行显示同源)/ `changelog`(读包内 `CHANGELOG.md` 最近 3 节)。
|
|
19
|
+
导出 `aboutOf` 供探针使用。
|
|
20
|
+
- `client/client.js`:卡片标题改用 `about.title`;展开后依次显示 **插件简介**、
|
|
21
|
+
**包含的组件(名称 + 简介两列表格)**,底部新增可折叠 **更新日志**(点按钮展开,等宽字体、可滚动)。
|
|
22
|
+
- 探针 `F:\dsh\001X\crossterm-work\probe-about.mjs`:7 项全 PASS(标题、简介、四组件顺序与简介、
|
|
23
|
+
更新日志含本版且不超长、版本一致、缓存命中)。
|
|
24
|
+
|
|
25
|
+
## 0.6.1 — 2026-10-19
|
|
26
|
+
|
|
27
|
+
### 修:旧布局缓存挡住了新增的云端文档
|
|
28
|
+
- 症状:0.5.0 / 0.6.0 升级后,云盘里看不到 `_规则-账号.md`、`_规则-设备.md`、`inbox/_说明.md`,
|
|
29
|
+
`status.home.layout.rulesVersion` 也还是老值 —— 文档写着「命中缓存会自动补写」,实际没补。
|
|
30
|
+
- 根因:`ensureLayout()` 命中缓存时直接沿用 `cached.paths`,而路径是**算出来的**(`buildPaths()`)。
|
|
31
|
+
旧版缓存里没有 `accountRulesFile` / `deviceRulesFile` / `inboxReadmeFile` 三个字段,
|
|
32
|
+
`ensureRules()` 拿到 `undefined` 就跳过,随后仍把 `rulesVersion` 标成最新 —— 等于自己认定「已写」。
|
|
33
|
+
- 修法(`lib/cloud/layout.js`):
|
|
34
|
+
- 命中缓存时用 `buildPaths()` 按 `home / account / deviceId` **重新算路径**,缓存只负责存 `account`、`role`、`ensuredAt` 这类状态;
|
|
35
|
+
- `ensureRules()` 返回 `{ written, failed }`,路径缺失或上传失败都计入 `failed`;
|
|
36
|
+
- 只有 `failed === 0` 才把 `rulesVersion` 抬到 `RULES_VERSION` 并写回缓存,否则保持 0,下次 `ensure` 继续重试;
|
|
37
|
+
- 缓存新增 `home` 字段,非默认 `cloudHome` 也能重建路径。
|
|
38
|
+
- 探针 `F:\dsh\001X\crossterm-work\probe-layout-stale-cache.mjs`:**12 项全 PASS**
|
|
39
|
+
(旧缓存→重算路径→补写三份文档→写回新缓存;第二次不重复上传;上传失败时 `rulesVersion` 不冒充最新)。
|
|
40
|
+
回归:`probe-layout-rules.mjs` 26 PASS、`probe-cloud-home.mjs` 42 PASS、`probe-inbox-readme.mjs` 17 PASS。
|
|
41
|
+
|
|
3
42
|
## 0.6.0 — 2026-10-19
|
|
4
43
|
|
|
5
44
|
### 共享收件箱里也放一份说明
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
|
|
|
12
12
|
|
|
13
13
|
## 零、简介与本版更新(每次发版重写这一节)
|
|
14
14
|
|
|
15
|
-
**插件名称**:`dsh-crossterm
|
|
15
|
+
**插件名称**:`dsh-crossterm`(插件页显示 **跨端互通**)| **插件简介**:同一 DSH 账号下的会话、终端、设备之间互通 —— 特征码链、内联邮件、云端中转站与自动备份。
|
|
16
16
|
|
|
17
17
|
**四个组件(插件页里就是这四个开关,名称与简介来自各自的 manifest)**
|
|
18
18
|
|
|
@@ -23,15 +23,17 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
|
|
|
23
23
|
| 跨设备 | 经云端总线,在多台机器的 DSH 之间互发邮件与文件 | `term_cloud` |
|
|
24
24
|
| 跨账号 | 预留位:后续用于跨账号互通,当前不挂工具 | — |
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
> 插件页里还会多出一行 **跨端互通**(组件列表最后一行):它是「入口行」,裸包名加载浏览器半端,
|
|
27
|
+
> 承载插件标题与配置卡片,不挂任何工具。浏览器 client 半端只认裸包名行,所以这一行没法省。
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
29
|
+
**本版更新(0.6.2)**
|
|
30
|
+
|
|
31
|
+
- **插件页终于像个插件了**:插件改名 **跨端互通**(原来叫「跨会话」,看起来像只管会话),
|
|
32
|
+
并写上了插件级简介;四个业务组件名保持不变。
|
|
33
|
+
- 配置卡片里新增:**插件简介**、**包含的组件(名称 + 简介两列表格)**、底部可折叠 **更新日志**
|
|
34
|
+
(直接读包内 `CHANGELOG.md`,展开就能看最近三版改了什么)。
|
|
35
|
+
- 数据来自新接口 `GET /crossterm/api/about`(组件名与简介读 `parts/*.manifest.json`,与组件行同源)。
|
|
36
|
+
- 上一版(0.6.1)的内容仍在:旧布局缓存不再挡住云端规则文档与 `inbox/_说明.md`。
|
|
35
37
|
|
|
36
38
|
## 一、安装与卸载
|
|
37
39
|
|
package/client/client.js
CHANGED
|
@@ -63,6 +63,23 @@ window.__ModuleLoader__.load({
|
|
|
63
63
|
fontSize: 12,
|
|
64
64
|
},
|
|
65
65
|
msg: { fontSize: 12, opacity: 0.8, whiteSpace: "pre-wrap", wordBreak: "break-word" },
|
|
66
|
+
intro: { lineHeight: 1.6, opacity: 0.85 },
|
|
67
|
+
sectionTitle: { fontWeight: 600, fontSize: 12, opacity: 0.7 },
|
|
68
|
+
comp: { display: "grid", gridTemplateColumns: "max-content 1fr", gap: "4px 12px" },
|
|
69
|
+
compName: { fontWeight: 600 },
|
|
70
|
+
compDesc: { opacity: 0.75, lineHeight: 1.5 },
|
|
71
|
+
log: {
|
|
72
|
+
whiteSpace: "pre-wrap",
|
|
73
|
+
fontSize: 11.5,
|
|
74
|
+
lineHeight: 1.55,
|
|
75
|
+
opacity: 0.85,
|
|
76
|
+
maxHeight: 240,
|
|
77
|
+
overflow: "auto",
|
|
78
|
+
background: "rgba(128,128,128,.09)",
|
|
79
|
+
borderRadius: 8,
|
|
80
|
+
padding: "8px 10px",
|
|
81
|
+
fontFamily: "ui-monospace, Consolas, monospace",
|
|
82
|
+
},
|
|
66
83
|
switchOn: {
|
|
67
84
|
width: 34,
|
|
68
85
|
height: 18,
|
|
@@ -176,6 +193,8 @@ window.__ModuleLoader__.load({
|
|
|
176
193
|
const [busy, setBusy] = React.useState("");
|
|
177
194
|
const [msg, setMsg] = React.useState("");
|
|
178
195
|
const [devices, setDevices] = React.useState(null);
|
|
196
|
+
const [about, setAbout] = React.useState(null);
|
|
197
|
+
const [showLog, setShowLog] = React.useState(false);
|
|
179
198
|
|
|
180
199
|
const load = React.useCallback(() => {
|
|
181
200
|
call("/crossterm/api/status")
|
|
@@ -205,6 +224,16 @@ window.__ModuleLoader__.load({
|
|
|
205
224
|
if (open && !devices) void loadDevices();
|
|
206
225
|
}, [open, devices, loadDevices]);
|
|
207
226
|
|
|
227
|
+
// 插件自述与更新日志(0.6.2):只读一次,读不到就退回内置文案
|
|
228
|
+
React.useEffect(() => {
|
|
229
|
+
if (!open || about) return;
|
|
230
|
+
call("/crossterm/api/about")
|
|
231
|
+
.then((data) => {
|
|
232
|
+
if (data && data.ok) setAbout(data);
|
|
233
|
+
})
|
|
234
|
+
.catch(() => {});
|
|
235
|
+
}, [open, about]);
|
|
236
|
+
|
|
208
237
|
const onCheck = async () => {
|
|
209
238
|
setBusy("check");
|
|
210
239
|
setMsg("正在检查更新…");
|
|
@@ -344,7 +373,7 @@ window.__ModuleLoader__.load({
|
|
|
344
373
|
h(
|
|
345
374
|
"div",
|
|
346
375
|
null,
|
|
347
|
-
h("div", { style: S.title },
|
|
376
|
+
h("div", { style: S.title }, `${(about && about.title) || "跨端互通"} ${version}`),
|
|
348
377
|
h("div", { style: S.sub }, subtitle),
|
|
349
378
|
),
|
|
350
379
|
// h("span", { style: S.chevron }, open ? "\u2303" : "\u2304"),
|
|
@@ -354,6 +383,26 @@ window.__ModuleLoader__.load({
|
|
|
354
383
|
h(
|
|
355
384
|
"div",
|
|
356
385
|
{ style: S.body },
|
|
386
|
+
about && about.description ? h("div", { style: S.intro }, about.description) : null,
|
|
387
|
+
about && about.components && about.components.length
|
|
388
|
+
? h(
|
|
389
|
+
"div",
|
|
390
|
+
null,
|
|
391
|
+
h("div", { style: S.sectionTitle }, "包含的组件"),
|
|
392
|
+
h(
|
|
393
|
+
"div",
|
|
394
|
+
{ style: S.comp },
|
|
395
|
+
...about.components.map((c) =>
|
|
396
|
+
h(
|
|
397
|
+
React.Fragment,
|
|
398
|
+
{ key: c.part },
|
|
399
|
+
h("div", { style: S.compName }, c.name),
|
|
400
|
+
h("div", { style: S.compDesc }, c.description || "—"),
|
|
401
|
+
),
|
|
402
|
+
),
|
|
403
|
+
),
|
|
404
|
+
)
|
|
405
|
+
: null,
|
|
357
406
|
h(FriendlyRow, { label: "当前版本", value: status ? `v${status.version}${mode === "link" ? "(源码 link:)" : ""}` : "" }),
|
|
358
407
|
h(FriendlyRow, {
|
|
359
408
|
label: "当前终端",
|
|
@@ -438,6 +487,18 @@ window.__ModuleLoader__.load({
|
|
|
438
487
|
h("button", { style: S.btn, onClick: onBackupNow, disabled: busy !== "" }, busy === "backup" ? "备份中…" : "备份会话"),
|
|
439
488
|
),
|
|
440
489
|
msg ? h("div", { style: S.msg }, msg) : null,
|
|
490
|
+
about && about.changelog
|
|
491
|
+
? h(
|
|
492
|
+
"div",
|
|
493
|
+
{ style: { display: "flex", flexDirection: "column", gap: 8 } },
|
|
494
|
+
h("div", null, h(
|
|
495
|
+
"button",
|
|
496
|
+
{ style: S.btn, onClick: () => setShowLog((v) => !v) },
|
|
497
|
+
showLog ? "收起更新日志" : "更新日志",
|
|
498
|
+
)),
|
|
499
|
+
showLog ? h("div", { style: S.log }, about.changelog) : null,
|
|
500
|
+
)
|
|
501
|
+
: null,
|
|
441
502
|
),
|
|
442
503
|
);
|
|
443
504
|
}
|
package/cordis.patch.yml
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
# dsh plugin --profile desktop remove dsh-crossterm
|
|
9
9
|
#
|
|
10
10
|
# 每个 insert 行在 DSH「插件 → dsh-crossterm → 包含的组件」里是一行组件,可单独开关。
|
|
11
|
-
#
|
|
11
|
+
# 五行指向同一个包,只是走不同子路径:
|
|
12
12
|
#
|
|
13
|
+
# dsh-crossterm 入口:插件页配置卡片(浏览器半端)+ 全插件简介/更新日志,
|
|
14
|
+
# 不挂任何工具(config.part: ui)
|
|
13
15
|
# dsh-crossterm/parts/session 跨会话:term_link / term_unlink / term_broadcast / term_inbox / term_config
|
|
14
16
|
# dsh-crossterm/parts/terminal 跨终端:term_whoami / term_list / term_send / term_send_files
|
|
15
17
|
# dsh-crossterm/parts/device 跨设备:term_cloud(云端总线同步)
|
|
@@ -18,15 +20,19 @@
|
|
|
18
20
|
# 组件名来自 package.json 的 exports 映射:
|
|
19
21
|
# "./parts/session/package.json" -> "./parts/session.manifest.json"
|
|
20
22
|
# 每个组件显示自己的中文名,实际加载的仍是 lib/index.js,靠 config.part 决定挂哪组工具。
|
|
21
|
-
#
|
|
23
|
+
#
|
|
24
|
+
# 为什么入口行放在最后:浏览器的 client 半端只对「裸包名」行生效(dsh-client-modules 的
|
|
25
|
+
# locatePkgJson 对子路径行直接返回 undefined),而插件页顶部标题与裸包名行共用同一份
|
|
26
|
+
# locale/zh.json 的 meta —— 插件叫「跨端互通」、四个业务组件叫跨会话/跨终端/跨设备/跨账号,
|
|
27
|
+
# 只能让裸包名行单独做入口,且排在组件列表末尾,四个组件仍是列表里的前四行。
|
|
28
|
+
#
|
|
29
|
+
# 五个组件在同一 DSH 进程里共用一份总线实例:心跳只起一次,云同步只在 device 组件启用
|
|
22
30
|
# 且 config.json 里 cloudEnabled=true 时才跑。安装/改动后需重启对应的 DSH 进程。
|
|
23
31
|
# ============================================================================
|
|
24
32
|
|
|
25
33
|
- insert:
|
|
26
34
|
- id: crossterm-session
|
|
27
|
-
|
|
28
|
-
# 插件页里的配置卡片就挂在这一行上。名字仍由 locale/zh.json 的 meta.title 提供。
|
|
29
|
-
name: dsh-crossterm
|
|
35
|
+
name: dsh-crossterm/parts/session
|
|
30
36
|
config:
|
|
31
37
|
part: session
|
|
32
38
|
- id: crossterm-terminal
|
|
@@ -40,4 +46,10 @@
|
|
|
40
46
|
- id: crossterm-account
|
|
41
47
|
name: dsh-crossterm/parts/account
|
|
42
48
|
config:
|
|
43
|
-
part: account
|
|
49
|
+
part: account
|
|
50
|
+
- id: crossterm-ui
|
|
51
|
+
# 裸包名:浏览器 client 半端只认这一行,插件页配置卡片挂在这里。
|
|
52
|
+
# 也是插件页顶部标题/简介的来源(locale/zh.json 的 meta)。
|
|
53
|
+
name: dsh-crossterm
|
|
54
|
+
config:
|
|
55
|
+
part: ui
|
package/lib/bus.js
CHANGED
package/lib/cloud/layout.js
CHANGED
|
@@ -242,17 +242,22 @@ export async function ensureRules(pan, paths, info = {}) {
|
|
|
242
242
|
]
|
|
243
243
|
if (paths.inboxReadmeFile) docs.push([paths.inboxReadmeFile, inboxReadmeMarkdown({ account, device, version })])
|
|
244
244
|
const written = []
|
|
245
|
+
let failed = 0
|
|
245
246
|
for (const [remote, text] of docs) {
|
|
246
247
|
try {
|
|
248
|
+
if (!remote) { failed += 1; continue }
|
|
247
249
|
if (await pan.exists(remote)) continue
|
|
248
250
|
await pan.putBuffer(Buffer.from(text, 'utf8'), remote)
|
|
249
251
|
written.push(remote)
|
|
250
252
|
log(`云端规则已写入:${remote}`)
|
|
251
253
|
} catch (e) {
|
|
254
|
+
failed += 1
|
|
252
255
|
log(`写云端规则失败 ${remote}:${(e && e.message) || String(e)}`)
|
|
253
256
|
}
|
|
254
257
|
}
|
|
255
|
-
|
|
258
|
+
// 收件箱说明算一份必须存在的文档:缺少它的旧路径缓存不能冒充「规则已是最新」
|
|
259
|
+
if (!paths.inboxReadmeFile) failed += 1
|
|
260
|
+
return { written, failed }
|
|
256
261
|
}
|
|
257
262
|
|
|
258
263
|
/**
|
|
@@ -263,18 +268,29 @@ export async function ensureLayout({ pan, cfg = {}, device = {}, cacheFile, forc
|
|
|
263
268
|
const nodeId = sanitize(device.nodeId || 'device')
|
|
264
269
|
const cached = readCache(cacheFile)
|
|
265
270
|
if (!force && cached && cached.deviceId === nodeId && Date.now() - Number(cached.at || 0) < CACHE_MS && cached.paths) {
|
|
271
|
+
// 路径是「算出来的」而不是状态:升级后新增的路径(规则文档、收件箱说明)必须重新算,
|
|
272
|
+
// 直接沿用旧缓存的 paths 会让 ensureRules 拿到 undefined、规则永远写不出去。
|
|
273
|
+
const paths = buildPaths({
|
|
274
|
+
home: cached.home || cfg.cloudHome || DEFAULT_HOME,
|
|
275
|
+
account: cached.account || cached.paths.account || 'default',
|
|
276
|
+
deviceId: nodeId,
|
|
277
|
+
})
|
|
278
|
+
const state = { ...cached, home: cached.home || cfg.cloudHome || DEFAULT_HOME, paths }
|
|
266
279
|
if (Number(cached.rulesVersion || 0) < RULES_VERSION) {
|
|
267
|
-
await ensureRules(pan,
|
|
280
|
+
const r = await ensureRules(pan, paths, {
|
|
268
281
|
log,
|
|
269
|
-
account:
|
|
282
|
+
account: state.account,
|
|
270
283
|
device: nodeId,
|
|
271
|
-
role:
|
|
284
|
+
role: state.role,
|
|
272
285
|
version: device.version || '',
|
|
273
286
|
})
|
|
274
|
-
|
|
275
|
-
|
|
287
|
+
if (!r.failed) {
|
|
288
|
+
state.rulesVersion = RULES_VERSION
|
|
289
|
+
state.rulesWritten = r.written
|
|
290
|
+
writeCache(cacheFile, state)
|
|
291
|
+
}
|
|
276
292
|
}
|
|
277
|
-
return { ...
|
|
293
|
+
return { ...state, cached: true, ensuredAt: cached.at }
|
|
278
294
|
}
|
|
279
295
|
|
|
280
296
|
const who = await resolveAccount(pan, cfg, log)
|
|
@@ -358,9 +374,12 @@ export async function ensureLayout({ pan, cfg = {}, device = {}, cacheFile, forc
|
|
|
358
374
|
deviceId: nodeId,
|
|
359
375
|
account: who.account,
|
|
360
376
|
accountSource: who.source,
|
|
377
|
+
home: cfg.cloudHome || DEFAULT_HOME,
|
|
361
378
|
role,
|
|
362
|
-
|
|
379
|
+
// 有文档没写成功就不要把版本号标上去,下次 ensure 还会重试
|
|
380
|
+
rulesVersion: rules.failed ? 0 : RULES_VERSION,
|
|
363
381
|
rulesWritten: rules.written,
|
|
382
|
+
rulesFailed: rules.failed,
|
|
364
383
|
paths,
|
|
365
384
|
created,
|
|
366
385
|
accountInfo,
|
package/lib/index.js
CHANGED
|
@@ -1315,10 +1315,11 @@ const TOOL_PART = {
|
|
|
1315
1315
|
term_cloud: 'device',
|
|
1316
1316
|
}
|
|
1317
1317
|
|
|
1318
|
-
const PARTS = ['all', 'session', 'terminal', 'device', 'account']
|
|
1318
|
+
const PARTS = ['all', 'ui', 'session', 'terminal', 'device', 'account']
|
|
1319
1319
|
|
|
1320
1320
|
const PART_LABEL = {
|
|
1321
1321
|
all: '全部组件',
|
|
1322
|
+
ui: '入口(插件卡片)',
|
|
1322
1323
|
session: '跨会话',
|
|
1323
1324
|
terminal: '跨终端',
|
|
1324
1325
|
device: '跨设备',
|
package/lib/web.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from 'node:fs'
|
|
4
4
|
import os from 'node:os'
|
|
5
5
|
import path from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
6
7
|
import { spawn } from 'node:child_process'
|
|
7
8
|
import { VERSION, readConfig, writeConfig } from './bus.js'
|
|
8
9
|
import { createPan } from './cloud/pan.js'
|
|
@@ -11,6 +12,8 @@ import { runSetup } from './cloud/setup.js'
|
|
|
11
12
|
|
|
12
13
|
const PKG = 'dsh-crossterm'
|
|
13
14
|
const NPM_REGISTRIES = ['https://registry.npmmirror.com', 'https://registry.npmjs.org']
|
|
15
|
+
// 包根目录:lib/web.js 的上一级(README.md / CHANGELOG.md / locale / parts 都在这里)
|
|
16
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
14
17
|
|
|
15
18
|
let installed = false
|
|
16
19
|
let setupRunning = false
|
|
@@ -263,6 +266,63 @@ function statusOf(term) {
|
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
268
|
|
|
269
|
+
/**
|
|
270
|
+
* 插件自述(0.6.2):插件标题/简介 + 四个组件的名称与简介 + 最近几版更新日志。
|
|
271
|
+
* 卡片(浏览器半端)与插件页顶部共用 locale/zh.json 的 meta,这里读同一份文件,
|
|
272
|
+
* 保证「插件页怎么显示」和「卡片里怎么显示」永远一致。
|
|
273
|
+
*/
|
|
274
|
+
const PART_ORDER = [
|
|
275
|
+
{ part: 'session', fallback: '跨会话' },
|
|
276
|
+
{ part: 'terminal', fallback: '跨终端' },
|
|
277
|
+
{ part: 'device', fallback: '跨设备' },
|
|
278
|
+
{ part: 'account', fallback: '跨账号' },
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
let aboutCache = null
|
|
282
|
+
|
|
283
|
+
function readChangelog(maxSections = 3, maxChars = 6000) {
|
|
284
|
+
try {
|
|
285
|
+
const text = fs.readFileSync(path.join(ROOT, 'CHANGELOG.md'), 'utf8')
|
|
286
|
+
const chunks = text.split(/^## /m)
|
|
287
|
+
const head = chunks.shift() || ''
|
|
288
|
+
const picked = chunks.slice(0, maxSections).map((c) => '## ' + c.trim())
|
|
289
|
+
const body = picked.join('\n\n')
|
|
290
|
+
const out = body || head.trim()
|
|
291
|
+
return out.length > maxChars ? out.slice(0, maxChars) + '\n…' : out
|
|
292
|
+
} catch {
|
|
293
|
+
return ''
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function aboutOf() {
|
|
298
|
+
if (aboutCache && aboutCache.version === VERSION) return aboutCache
|
|
299
|
+
let meta = {}
|
|
300
|
+
try {
|
|
301
|
+
const doc = JSON.parse(fs.readFileSync(path.join(ROOT, 'locale', 'zh.json'), 'utf8'))
|
|
302
|
+
meta = (doc && doc.meta) || {}
|
|
303
|
+
} catch { /* ignore */ }
|
|
304
|
+
const components = PART_ORDER.map((item) => {
|
|
305
|
+
let doc = {}
|
|
306
|
+
try {
|
|
307
|
+
doc = JSON.parse(fs.readFileSync(path.join(ROOT, 'parts', `${item.part}.manifest.json`), 'utf8'))
|
|
308
|
+
} catch { /* ignore */ }
|
|
309
|
+
return {
|
|
310
|
+
part: item.part,
|
|
311
|
+
name: doc.name || item.fallback,
|
|
312
|
+
description: doc.description || '',
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
aboutCache = {
|
|
316
|
+
name: PKG,
|
|
317
|
+
version: VERSION,
|
|
318
|
+
title: meta.title || PKG,
|
|
319
|
+
description: meta.description || '',
|
|
320
|
+
components,
|
|
321
|
+
changelog: readChangelog(),
|
|
322
|
+
}
|
|
323
|
+
return aboutCache
|
|
324
|
+
}
|
|
325
|
+
|
|
266
326
|
/**
|
|
267
327
|
* 只注册一次;行被关闭时随 fiber 一起注销,重新启用会再注册。
|
|
268
328
|
*/
|
|
@@ -281,6 +341,19 @@ export function registerWeb(ctx, term, log) {
|
|
|
281
341
|
handler: (req, res) => json(res, 200, statusOf(term)),
|
|
282
342
|
}),
|
|
283
343
|
)
|
|
344
|
+
disposers.push(
|
|
345
|
+
webCtx.webServer.register({
|
|
346
|
+
kind: 'exact',
|
|
347
|
+
path: '/crossterm/api/about',
|
|
348
|
+
handler: (req, res) => {
|
|
349
|
+
try {
|
|
350
|
+
json(res, 200, { ok: true, ...aboutOf() })
|
|
351
|
+
} catch (error) {
|
|
352
|
+
json(res, 200, { ok: false, message: String((error && error.message) || error) })
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
}),
|
|
356
|
+
)
|
|
284
357
|
disposers.push(
|
|
285
358
|
webCtx.webServer.register({
|
|
286
359
|
kind: 'exact',
|
|
@@ -525,4 +598,4 @@ export function registerWeb(ctx, term, log) {
|
|
|
525
598
|
}
|
|
526
599
|
|
|
527
600
|
// 只给探针用:把内部小工具导出来,宿主运行时不依赖这些导出
|
|
528
|
-
export { decodeOutput, runtimeTools, looksMissingCommand, updateErrorText }
|
|
601
|
+
export { decodeOutput, runtimeTools, looksMissingCommand, updateErrorText, aboutOf }
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
|
-
"title": "
|
|
4
|
-
"description": "
|
|
3
|
+
"title": "CrossTerm",
|
|
4
|
+
"description": "Connect sessions, terminals and devices of one DSH account: chain codes, inline mail, a cloud relay and automatic backups. Four components, each with its own switch."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-crossterm",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "DSH
|
|
3
|
+
"version": "0.6.2",
|
|
4
|
+
"description": "跨端互通:同一 DSH 账号下的会话、终端、设备之间互通 —— 特征码链、内联邮件、云端中转站与自动备份",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"exports": {
|