dsh-plugin-update 0.3.0 → 0.4.0
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 +16 -0
- package/README.md +79 -8
- package/dist/client.d.ts +4 -4
- package/dist/client.js +2 -1
- package/dist/config.d.ts +4 -1
- package/dist/config.js +8 -1
- package/dist/entry.d.ts +6 -0
- package/dist/entry.js +36 -4
- package/dist/host.d.ts +4 -4
- package/dist/host.js +77 -6
- package/dist/http.d.ts +127 -0
- package/dist/http.js +301 -0
- package/dist/panel-batch.d.ts +9 -0
- package/dist/panel-batch.js +113 -7
- package/dist/panel.d.ts +14 -1
- package/dist/panel.js +141 -16
- package/dist/service.d.ts +14 -0
- package/dist/service.js +50 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
格式为 Keep-a-Changelog 子集:`Added/Fixed/Changed` 必写,`Deprecated/Removed/Security` 有则透传,`Unreleased` 面板忽略,禁止 git-log 直倒。面板缺日志时中性提示,不挡安装。
|
|
4
4
|
|
|
5
|
+
## [0.4.0] - 2026-10-05
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- 更新日志无脑接入(#38):宿主新增 `updateChangelog` 电话(入参版本号,回该版 tarball 内全文,取不到回空;同源 + 精确路径 + `integrity` + 三墙复用,按版本记住结果);面板 `autoChangelog` 默认自动展示(有新版调一次,显式文本仍赢,`false` 退回手动);入口件透传跟上;批量详情行按行自动取(无新增批量电话,单行失败只影响该行);HTTP 版白名单放行,老三电话形状不变。
|
|
9
|
+
- 按钮交互反馈:查新版/安装在途置忙(禁用 + 文案 + `aria-busy` + 纯 CSS 转圈,并发连点只认第一次);默认主题补 hover / 按下下沉 / 过渡;busy 脉冲与 `prefers-reduced-motion` 关闭。
|
|
10
|
+
- 原生 `title` 说明:单面板全部动作按钮、批量宏按钮带悬停一句话(零成本 tooltip,不引入浮层组件)。
|
|
11
|
+
- Toast 语义:复制/重启类回执 5 秒后自动过期,不靠下次点击才消失。
|
|
12
|
+
- 首帧骨架:快照没到之前版本条画微光占位(`aria-hidden`,不进语义)。
|
|
13
|
+
- 批量「取消这一批」鼠标两步确认(第一次上膛红框,点别的自动卸膛;程序调 `act('cancel')` 仍一次即执行)。
|
|
14
|
+
- Esc 关弹窗:单面板 / 批量面板 dialog 与入口件 dialog 均支持(容器契约内,只用 innerHTML + 事件)。
|
|
15
|
+
|
|
16
|
+
## [0.3.1] - 2026-10-05
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- 新增 `dsh-plugin-update/http`:`createHttpCall`(单三电话 + 批量五电话同一内核,`batchPrefix` 独立)+ `mountUpdatePanelHttp`/`mountUpdateEntryHttp`/`mountUpdateBatchPanelHttp`,只传 pluginId + prefix/batchPrefix + baseUrl/routes 即跑通检查/安装/待重启/失败与批量总账/明细/动作;queue/env/session/rows/progress/diag 全透传,传输失败走 `http-transport-failed` 异常通道,取消整批走 `batchCancel` 电话。
|
|
20
|
+
|
|
5
21
|
## [0.3.0] - 2026-10-04
|
|
6
22
|
|
|
7
23
|
### Added
|
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
给 DSH 插件加「检查更新 / 安装更新」能力的 npm 包。宿主侧一段接线,面板侧挂一个现成组件,装不上时给用户一条可复制的手工命令。
|
|
4
4
|
|
|
5
|
-
要求 Node 22 或更高,零运行时依赖。当前版本 `0.
|
|
5
|
+
要求 Node 22 或更高,零运行时依赖。当前版本 `0.4.0`。
|
|
6
6
|
|
|
7
7
|
装上它你会拿到六样东西:
|
|
8
8
|
|
|
9
|
-
-
|
|
9
|
+
- **四个电话**:查状态(只读本地)、查新版(用户点了才联网一次)、装更新(拿凭证提交)、取日志(按版取更新日志,全文或空)。电话指宿主对外提供的方法。
|
|
10
10
|
- **一套落盘**:任务状态、安装锁、回滚凭据,按「插件标识 + 使用范围」隔离,多插件互不干扰。
|
|
11
11
|
- **面板要的派生取值**:电话名与轮询间隔,构建期从本包生成,面板里不写死。
|
|
12
|
-
-
|
|
12
|
+
- **一个现成整组件**:默认内嵌、可切弹窗,调用者传参指定;轮询、安装门控、中文一句话、待重启横幅、手工命令展示与复制、排队可见开关、跳过与恢复、诊断一键复制、**更新日志自动展示**全在组件内部消化。
|
|
13
13
|
- **一个更新入口件**:配置页一行挂上就是「检查更新」按钮(或只给状态点的徽标、或整块内嵌),按钮文案随状态自己变;见第 2.5 节。
|
|
14
14
|
- **一套多目标批量更新**:一个插件管 N 个插件的更新(总账 + 明细 + 动作,一家收尾才起下一家,会话落盘可断点续跑);见第 2.6 节。
|
|
15
15
|
|
|
@@ -52,7 +52,7 @@ for (const [name, handler] of Object.entries(update.handlers)) {
|
|
|
52
52
|
}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
这一步得到四个电话名:`notes.updateStatus`、`notes.updateCheck`、`notes.updateInstall`、`notes.updateChangelog`(取日志:给版本号,回该版 tarball 里的更新日志全文,取不到回空)。
|
|
56
56
|
|
|
57
57
|
`pluginId` 必填(非空字符串,不含路径分隔符)。单例复用键强制含插件标识,多插件不串内存状态与锁。
|
|
58
58
|
除 `pluginId` 之外的配置都可选并带默认值,不传即走默认(见第 3 节)。
|
|
@@ -78,6 +78,7 @@ createHostUpdate(
|
|
|
78
78
|
| `….updateStatus` | `{}` | `{ ok: true, snapshot, manual, receipt: null }` | `{ ok: false, error, errorKind, diag? }` |
|
|
79
79
|
| `….updateCheck` | `{}` | `{ ok: true, snapshot, manual, receipt }` | 同上 |
|
|
80
80
|
| `….updateInstall` | `{ checkId, requestId }` | `{ ok: true, snapshot, manual, receipt: null }` | 同上 |
|
|
81
|
+
| `….updateChangelog` | `{ version }` | `{ ok: true, version, markdown: string \| null }` | 同上 |
|
|
81
82
|
|
|
82
83
|
- `snapshot` 恒为第 5.1 节那六个字段;`manual` 是第 5.3 节那条手工命令(能给则给,不能给为 `null`)。
|
|
83
84
|
- `receipt` 只有查新版给(`{ checkId, checkedAt, expiresAt }`);装更新时把 `checkId` 原样带回来,`requestId` 由面板自己生成(同一个编号重复提交直接返回旧结果)。
|
|
@@ -144,6 +145,21 @@ mountUpdatePanel(slot, { pluginId: 'my-notes-plugin', prefix: 'notes', call: hos
|
|
|
144
145
|
|
|
145
146
|
类型定义随包分发(`dsh-plugin-update/panel` 的 `.d.ts`),不用自编译;面板离线可读,与包版本绑定。
|
|
146
147
|
|
|
148
|
+
整组件 HTTP 版(无 `host.call` 环境即跑:浏览器面板经 HTTP POST 直达宿主网关,只传三样):
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
import { mountUpdatePanelHttp } from 'dsh-plugin-update/http'
|
|
152
|
+
|
|
153
|
+
mountUpdatePanelHttp(document.querySelector('#upd'), {
|
|
154
|
+
pluginId: 'my-plugin',
|
|
155
|
+
prefix: 'myplug',
|
|
156
|
+
baseUrl: 'https://host.local:3000/upd',
|
|
157
|
+
showOthers: false,
|
|
158
|
+
pollMs: 1000,
|
|
159
|
+
})
|
|
160
|
+
// 离开时 panel.unmount():只停轮询,安装在宿主侧继续跑。
|
|
161
|
+
```
|
|
162
|
+
|
|
147
163
|
### 第 2.5 节:更新入口件(配置页上那一颗按钮)
|
|
148
164
|
|
|
149
165
|
目标只有一句:**让用户不用点开就知道有没有事。** 一行挂上:
|
|
@@ -221,10 +237,23 @@ const panel = mountUpdateBatchPanel(el, {
|
|
|
221
237
|
})
|
|
222
238
|
```
|
|
223
239
|
|
|
240
|
+
批量面板 HTTP 版(无 `host.call` 环境即跑:同一传输内核,五电话走同一映射,取消走 `batchCancel`):
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
import { mountUpdateBatchPanelHttp } from 'dsh-plugin-update/http'
|
|
244
|
+
|
|
245
|
+
mountUpdateBatchPanelHttp(document.querySelector('#batch'), {
|
|
246
|
+
batchPrefix: 'life',
|
|
247
|
+
baseUrl: 'https://host.local:3000/upd',
|
|
248
|
+
pollMs: 1500,
|
|
249
|
+
})
|
|
250
|
+
// 取消整批走 batchCancel 电话;unmount 只停轮询,宿主侧照跑。
|
|
251
|
+
```
|
|
252
|
+
|
|
224
253
|
三条硬约束(都在实现里):**一行只回答一个问题**(这家的下一步是什么,状态词全中文可执行);
|
|
225
254
|
**行内动作只作用于该行**,「全部更新」是宏而不是第二个状态机;**待重启与失败常驻横幅**,不藏进展开里。
|
|
226
255
|
|
|
227
|
-
|
|
256
|
+
点任意一行展开该家详情:**内容是单插件那套五章内核的只读渲染,动作由批量面板自己提供**。第 02 章日志由批量面板按行自动取该行自己的 `updateChangelog` 电话(按行+版本记住结果,取不到即中性提示,不挡安装;`autoChangelog: false` 可关)。
|
|
228
257
|
内核渲染时传 `actions: 'none'`——五章内容、进度条、「已跳过」提示一字不减,**动作按钮一个都不画**
|
|
229
258
|
(内核里那些按钮带的是 `data-action`,批量面板只认 `data-act`;照搬 markup 而不接管行为,
|
|
230
259
|
就会得到「可点却没反应」的死按钮——这条缝现在由渲染开关焊死)。详情里的动作行是批量面板自己的:
|
|
@@ -246,11 +275,33 @@ const panel = mountUpdateBatchPanel(el, {
|
|
|
246
275
|
**跨使用范围如实拒绝**:`web` 与 `desktop` 各自排队(装的是不同落点),混在一起的目标会回
|
|
247
276
|
`cross-scope`,本包不会替你跨范围抢锁。
|
|
248
277
|
|
|
278
|
+
### 第 2.7 节:更新日志自动展示(告诉用户更新了什么)
|
|
279
|
+
|
|
280
|
+
任何插件的升级能力都是两件事:**① 给一套面板**(上面第 4 步 / 2.5 / 2.6,挂载一行即跑);**② 告诉用户这次更新了什么**(本节,写 md 即显示,零新增代码)。
|
|
281
|
+
|
|
282
|
+
```js
|
|
283
|
+
import { mountUpdatePanel } from 'dsh-plugin-update/panel'
|
|
284
|
+
|
|
285
|
+
// 与第 4 步同一行,不用加任何参数:有新版时第 02 章“更新日志”自动出现
|
|
286
|
+
const panel = mountUpdatePanel(document.getElementById('update-slot'), {
|
|
287
|
+
pluginId: 'my-notes-plugin',
|
|
288
|
+
prefix: 'notes',
|
|
289
|
+
call: (name, args) => host.call(name, args),
|
|
290
|
+
})
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
作者侧只写文件,不写代码(发布后用户自动看到):
|
|
294
|
+
|
|
295
|
+
1. 包根放 `CHANGELOG.md`(文件名全大写,小写视为没有),版本节形如 `## [1.2.0] - 2026-10-05`(最新在前),分类用 `### Added/Fixed/Changed`(面板展开;`Deprecated/Removed/Security` 折叠;`Unreleased` 与空节忽略)。
|
|
296
|
+
2. `package.json` 的 `files` 白名单加上 `CHANGELOG.md`(否则发出去的包里没有它,面板永远读不到),发布前跑一次 `npm publish --dry-run` 核对。
|
|
297
|
+
|
|
298
|
+
行为:有新版时面板按新版号自动取一次该版 tarball 里的全文(复用官方源 + `integrity` 校验,同一版本只取一次);删了文件或取不到即中性提示,**安装永远不受影响**。想自己接管就传 `autoChangelog: false` 退回手动(备好文本后 `mountUpdatePanel({ changelogMarkdown })` 或 `setChangelogMarkdown` 传入,显式文本永不被覆盖)。入口件与批量面板同样自动:前者透传,后者展开行按行取。细节与边界见第 5.8 节。
|
|
299
|
+
|
|
249
300
|
### 升级本包(已经接入过的项目)
|
|
250
301
|
|
|
251
302
|
宿主种类与安装出口都由本包自己探测和选择,**升级依赖即可,宿主侧与面板侧都不用改代码**:
|
|
252
303
|
|
|
253
|
-
1. 依赖版本提到 `^0.
|
|
304
|
+
1. 依赖版本提到 `^0.4.0`。
|
|
254
305
|
2. 重新装 / 发一版你自己的插件,让新依赖进当前使用范围(运行时用的是 `node_modules` 里那份)。
|
|
255
306
|
3. 如果你自己接过一版宿主安装出口,把它删掉——它会挡在本包的路由前面。
|
|
256
307
|
|
|
@@ -398,7 +449,8 @@ dsh plugin --profile my-web add --save-exact my-notes-plugin@1.2.3 --registry=ht
|
|
|
398
449
|
|
|
399
450
|
- 有新版时面板在横幅下方展示“更新说明(当前版 → 新版):”,按目标包内 `CHANGELOG.md`(Keep-a-Changelog 子集)渲染:`Added/Fixed/Changed` 展开,`Deprecated/Removed/Security` 折叠,`Unreleased` 与空节不展示。
|
|
400
451
|
- 作者未提供说明时显示“作者未提供更新说明,安装不受影响。”——缺日志永不挡安装,不改变 `canInstall` 与 `blockedReason`。
|
|
401
|
-
-
|
|
452
|
+
- 默认自动:面板看到有新版即按新版号调一次 `….updateChangelog`,回来自己填进第 02 章;同一版本只取一次,取不到即中性提示。作者侧只要写好包根 `CHANGELOG.md` 并随包发布,零新增代码。
|
|
453
|
+
- 手动模式(老用法照旧):传 `autoChangelog: false` 即退回手动——已装版离线读本机 `node_modules/<目标包>/CHANGELOG.md`,新版按需取新版 tarball 内同名文件(复用官方源与 `integrity` 校验,取不到即回落中性提示;参考包根导出的 `readInstalledChangelogText` / `fetchReleaseChangelogText`),经 `mountUpdatePanel({ changelogMarkdown })` 或 `setChangelogMarkdown` 交给面板;显式传过的文本自动链路永不覆盖。
|
|
402
454
|
|
|
403
455
|
## 6. 排错
|
|
404
456
|
|
|
@@ -438,6 +490,25 @@ import { mountUpdatePanel } from 'dsh-plugin-update/panel'
|
|
|
438
490
|
mountUpdatePanel(slot, { pluginId: 'notes', prefix: 'notes', call: host.call })
|
|
439
491
|
```
|
|
440
492
|
|
|
493
|
+
**更新日志自动**(#38:写 md 即显示,零新增代码):
|
|
494
|
+
|
|
495
|
+
```js
|
|
496
|
+
// 宿主侧:createHostUpdate 顺手注册第 4 个电话,无新增配置
|
|
497
|
+
// 入参 { version }(发行版号,先验 validReleaseVersion + 通道门禁)
|
|
498
|
+
// 成功 { ok: true, version, markdown: string | null }(取不到即 null,中性提示)
|
|
499
|
+
// 失败复用 error / errorKind 体系;回包无路径、无快照;按版本记住结果
|
|
500
|
+
import { buildChangelogPhoneName } from 'dsh-plugin-update'
|
|
501
|
+
|
|
502
|
+
buildChangelogPhoneName('notes') // 'notes.updateChangelog',与宿主侧同一套拼法
|
|
503
|
+
|
|
504
|
+
// 面板侧:默认自动,有新版调一次;显式文本仍赢,false 退回手动
|
|
505
|
+
mountUpdatePanel(slot, { pluginId: 'my-notes-plugin', prefix: 'notes', call: host.call })
|
|
506
|
+
// mountUpdatePanel(slot, { pluginId, prefix, call, autoChangelog: false })
|
|
507
|
+
// mountUpdatePanel(slot, { pluginId, prefix, call, changelogMarkdown }) // 手动模式
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
- 入口件(`dsh-plugin-update/entry`)把 `autoChangelog / changelogMarkdown` 透传给面板 dialog/inline;批量面板(`dsh-plugin-update/panel-batch`)展开行自动调该行自己的 `updateChangelog`(无新增批量电话,`autoChangelog: false` 可关整批);HTTP 版(`dsh-plugin-update/http`)白名单已放行,默认路径即达。
|
|
511
|
+
|
|
441
512
|
**客户端入口**(`dist/client.js`,构建期打包用):
|
|
442
513
|
|
|
443
514
|
```js
|
|
@@ -478,6 +549,6 @@ queuePositionOf(queueState, 'my-plugin', requestId) // 0 = 在装,1..n = 顺
|
|
|
478
549
|
|
|
479
550
|
## 9. 兼容与稳定性
|
|
480
551
|
|
|
481
|
-
|
|
552
|
+
这些形状稳定,可以放心依赖:四个电话名与入参回参(老三电话一字不动,`updateChangelog` 是新增)、快照六字段、任务公开形状、配置只经函数入参注入、安装配方五键、日志事件字段基线(三个事件各带必填 `pluginId`)、历史落盘路径。
|
|
482
553
|
|
|
483
554
|
向后兼容的扩展:新增可选配置键、新增可选 `readerOverrides`(如 `targetPackageDir`)、新增宿主种类与路由取值。调用方不认新取值时按普通宿主处理即可,不会因此报错。
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type PhoneAction } from './config.js';
|
|
2
|
-
export { buildPhoneName, buildPhoneNames } from './config.js';
|
|
3
|
-
export type { PhoneAction } from './config.js';
|
|
1
|
+
import { type ChangelogPhoneAction, type PhoneAction } from './config.js';
|
|
2
|
+
export { buildChangelogPhoneName, buildPhoneName, buildPhoneNames } from './config.js';
|
|
3
|
+
export type { ChangelogPhoneAction, PhoneAction } from './config.js';
|
|
4
4
|
export { manualCommand } from './commands.js';
|
|
5
5
|
export { QUEUE_INTENT_TTL_MS, cancelEnqueuedInQueue, emptyQueueState, enqueueInQueue, isHeadOfQueue, isQueueBusy, normalizeQueueState, pruneExpiredIntents, queuePositionOf, releaseOwnerInQueue, setOwnerIfFree, visibleQueueFor, } from './queue.js';
|
|
6
6
|
export type { QueuedEntry, QueueOwner, UpdateQueueState, VisibleQueue, VisibleQueueOwner } from './queue.js';
|
|
@@ -12,5 +12,5 @@ export declare const CLIENT_POLL: {
|
|
|
12
12
|
readonly defaultMs: 1000;
|
|
13
13
|
readonly minMs: 250;
|
|
14
14
|
};
|
|
15
|
-
export declare function buildClientPhoneNames(prefix: string): Record<PhoneAction, string>;
|
|
15
|
+
export declare function buildClientPhoneNames(prefix: string): Record<PhoneAction, string> & Record<ChangelogPhoneAction, string>;
|
|
16
16
|
export declare function assertPollInterval(ms: unknown): number;
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/client.ts
|
|
2
2
|
import { buildPhoneNames, DEFAULT_PANEL_POLL_MS, MIN_PANEL_POLL_MS } from "./config.js";
|
|
3
|
-
import { buildPhoneName, buildPhoneNames as buildPhoneNames2 } from "./config.js";
|
|
3
|
+
import { buildChangelogPhoneName, buildPhoneName, buildPhoneNames as buildPhoneNames2 } from "./config.js";
|
|
4
4
|
import { manualCommand as manualCommand2 } from "./commands.js";
|
|
5
5
|
import {
|
|
6
6
|
QUEUE_INTENT_TTL_MS,
|
|
@@ -66,6 +66,7 @@ export {
|
|
|
66
66
|
batchEntryOf,
|
|
67
67
|
batchProgress,
|
|
68
68
|
batchRequestId,
|
|
69
|
+
buildChangelogPhoneName,
|
|
69
70
|
buildClientPhoneNames,
|
|
70
71
|
buildPhoneName,
|
|
71
72
|
buildPhoneNames2 as buildPhoneNames,
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export declare const DEFAULT_PREFIX = "wf";
|
|
2
2
|
export declare const PHONE_ACTIONS: readonly ["updateStatus", "updateCheck", "updateInstall"];
|
|
3
3
|
export type PhoneAction = (typeof PHONE_ACTIONS)[number];
|
|
4
|
+
export declare const CHANGELOG_PHONE_ACTION: "updateChangelog";
|
|
5
|
+
export type ChangelogPhoneAction = typeof CHANGELOG_PHONE_ACTION;
|
|
4
6
|
export declare const DEFAULT_TARGET_PACKAGE = "dsh-mattpocock-skills-deck";
|
|
5
7
|
export declare const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
6
8
|
export declare const LEGACY_PLUGIN_ID = "dsh-mattpocock-skills-deck";
|
|
@@ -44,5 +46,6 @@ export interface ResolvedUpdateConfig {
|
|
|
44
46
|
export declare function assertPluginId(value: unknown): string;
|
|
45
47
|
export declare function assertPrefix(value: unknown, role: string): string;
|
|
46
48
|
export declare function resolveUpdateConfig(input: UpdateConfigInput): ResolvedUpdateConfig;
|
|
47
|
-
export declare function buildPhoneNames(prefix: string): Record<PhoneAction, string>;
|
|
49
|
+
export declare function buildPhoneNames(prefix: string): Record<PhoneAction, string> & Record<ChangelogPhoneAction, string>;
|
|
50
|
+
export declare function buildChangelogPhoneName(prefix: string): string;
|
|
48
51
|
export declare function buildPhoneName(prefix: string, action: PhoneAction): string;
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/config.ts
|
|
2
2
|
const DEFAULT_PREFIX = "wf";
|
|
3
3
|
const PHONE_ACTIONS = ["updateStatus", "updateCheck", "updateInstall"];
|
|
4
|
+
const CHANGELOG_PHONE_ACTION = "updateChangelog";
|
|
4
5
|
const DEFAULT_TARGET_PACKAGE = "dsh-mattpocock-skills-deck";
|
|
5
6
|
const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
6
7
|
const LEGACY_PLUGIN_ID = "dsh-mattpocock-skills-deck";
|
|
@@ -73,14 +74,19 @@ function buildPhoneNames(prefix) {
|
|
|
73
74
|
return {
|
|
74
75
|
updateStatus: checked + ".updateStatus",
|
|
75
76
|
updateCheck: checked + ".updateCheck",
|
|
76
|
-
updateInstall: checked + ".updateInstall"
|
|
77
|
+
updateInstall: checked + ".updateInstall",
|
|
78
|
+
updateChangelog: checked + ".updateChangelog"
|
|
77
79
|
};
|
|
78
80
|
}
|
|
81
|
+
function buildChangelogPhoneName(prefix) {
|
|
82
|
+
return buildPhoneNames(prefix).updateChangelog;
|
|
83
|
+
}
|
|
79
84
|
function buildPhoneName(prefix, action) {
|
|
80
85
|
return buildPhoneNames(prefix)[action];
|
|
81
86
|
}
|
|
82
87
|
export {
|
|
83
88
|
BACKUP_FILE,
|
|
89
|
+
CHANGELOG_PHONE_ACTION,
|
|
84
90
|
DEFAULT_CHECK_TIMEOUT_MS,
|
|
85
91
|
DEFAULT_CONFIRMATION_TTL_MS,
|
|
86
92
|
DEFAULT_INSTALL_TIMEOUT_MS,
|
|
@@ -99,6 +105,7 @@ export {
|
|
|
99
105
|
STATE_FILE,
|
|
100
106
|
assertPluginId,
|
|
101
107
|
assertPrefix,
|
|
108
|
+
buildChangelogPhoneName,
|
|
102
109
|
buildPhoneName,
|
|
103
110
|
buildPhoneNames,
|
|
104
111
|
resolveUpdateConfig
|
package/dist/entry.d.ts
CHANGED
|
@@ -31,6 +31,12 @@ export interface UpdateEntryOptions {
|
|
|
31
31
|
label?: string;
|
|
32
32
|
profileName?: string;
|
|
33
33
|
pollMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* 更新日志自动/手动(#38):透传给面板(dialog 与 inline 同走 mountPanel),默认自动;
|
|
36
|
+
* 显式文本仍赢,false 退回手动。入口件自己不取数。
|
|
37
|
+
*/
|
|
38
|
+
autoChangelog?: boolean;
|
|
39
|
+
changelogMarkdown?: string | null;
|
|
34
40
|
/** variant='badge' 或 openOn='manual' 时,点击交给接入方(自己跳自己的页面)。 */
|
|
35
41
|
onActivate?: (state: {
|
|
36
42
|
hasUpdate: boolean;
|
package/dist/entry.js
CHANGED
|
@@ -48,6 +48,13 @@ const UPDATE_ENTRY_CSS = [
|
|
|
48
48
|
".dsh-upd-entry-btn{font:inherit;border:1px solid var(--dsh-upd-line,#d1d5db);border-radius:6px;",
|
|
49
49
|
"background:var(--dsh-upd-btn,#f9fafb);color:inherit;padding:4px 12px;cursor:pointer}",
|
|
50
50
|
".dsh-upd-entry-btn:hover{border-color:var(--dsh-upd-primary,#2563eb)}",
|
|
51
|
+
".dsh-upd-entry-btn,.dsh-upd-entry-dot{transition:background-color .15s ease,border-color .15s ease,color .15s ease,transform .06s ease}",
|
|
52
|
+
".dsh-upd-entry-btn:active:not(:disabled){transform:translateY(1px)}",
|
|
53
|
+
".dsh-upd-entry-btn:disabled,.dsh-upd-entry-dot:disabled{opacity:.55;cursor:wait}",
|
|
54
|
+
'.dsh-upd-entry-btn[aria-busy="true"]::after{content:"";display:inline-block;width:10px;height:10px;margin-left:7px;vertical-align:-1px;',
|
|
55
|
+
"border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:dsh-upd-entry-spin .8s linear infinite}",
|
|
56
|
+
"@keyframes dsh-upd-entry-spin{to{transform:rotate(360deg)}}",
|
|
57
|
+
'@media (prefers-reduced-motion: reduce){.dsh-upd-entry-btn,.dsh-upd-entry-dot{transition:none}.dsh-upd-entry-btn[aria-busy="true"]::after{animation:none}}',
|
|
51
58
|
".dsh-upd-entry-btn:focus-visible,.dsh-upd-entry-dot:focus-visible{outline:2px solid var(--dsh-upd-focus,#2563eb);outline-offset:1px}",
|
|
52
59
|
'.dsh-upd-entry[data-state="update"] .dsh-upd-entry-btn{border-color:var(--dsh-upd-ok-line,#059669);color:var(--dsh-upd-ok-line,#059669)}',
|
|
53
60
|
'.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-btn{border-color:var(--dsh-upd-warn-line,#d97706);color:var(--dsh-upd-warn-line,#d97706)}',
|
|
@@ -121,6 +128,7 @@ function mountUpdateEntry(container, options) {
|
|
|
121
128
|
let snapshot = null;
|
|
122
129
|
let error = null;
|
|
123
130
|
let note = null;
|
|
131
|
+
let activating = false;
|
|
124
132
|
let mounted = true;
|
|
125
133
|
let panel = null;
|
|
126
134
|
let panelMode = null;
|
|
@@ -156,9 +164,10 @@ function mountUpdateEntry(container, options) {
|
|
|
156
164
|
}
|
|
157
165
|
function entryHTML() {
|
|
158
166
|
const kind = entryStateKind(stateOf());
|
|
159
|
-
const text = currentLabel();
|
|
167
|
+
const text = activating ? "\u6B63\u5728\u67E5\u65B0\u7248\u2026" : currentLabel();
|
|
168
|
+
const busyAttr = activating ? ' disabled aria-busy="true"' : "";
|
|
160
169
|
const themeAttr = theme === "d5-paper" ? ' data-theme="d5-paper"' : "";
|
|
161
|
-
const control = variant === "badge" ? `<button type="button" class="dsh-upd-entry-dot" ${ENTRY_ATTR}="activate" title="${escapeHtml(text)}" aria-label="${escapeHtml(text)}"></button>` : `<button type="button" class="dsh-upd-entry-btn" ${ENTRY_ATTR}="activate">${escapeHtml(text)}</button>`;
|
|
170
|
+
const control = variant === "badge" ? `<button type="button" class="dsh-upd-entry-dot" ${ENTRY_ATTR}="activate" title="${escapeHtml(text)}" aria-label="${escapeHtml(text)}"${busyAttr}></button>` : `<button type="button" class="dsh-upd-entry-btn" ${ENTRY_ATTR}="activate"${busyAttr}>${escapeHtml(text)}</button>`;
|
|
162
171
|
const noteHTML = note ? `<span class="dsh-upd-entry-note" data-dsh-upd-note="1">${escapeHtml(note)}</span>` : "";
|
|
163
172
|
return `<style>${UPDATE_ENTRY_CSS}</style>
|
|
164
173
|
<span class="dsh-upd-entry" data-variant="${variant}" data-state="${kind}"${themeAttr}>${control}${noteHTML}</span>`;
|
|
@@ -208,6 +217,8 @@ function mountUpdateEntry(container, options) {
|
|
|
208
217
|
theme,
|
|
209
218
|
pollMs,
|
|
210
219
|
profileName,
|
|
220
|
+
autoChangelog: options.autoChangelog,
|
|
221
|
+
changelogMarkdown: options.changelogMarkdown ?? null,
|
|
211
222
|
call: panelCall
|
|
212
223
|
});
|
|
213
224
|
panelMode = mode;
|
|
@@ -233,10 +244,15 @@ function mountUpdateEntry(container, options) {
|
|
|
233
244
|
void refresh();
|
|
234
245
|
}
|
|
235
246
|
async function activate() {
|
|
236
|
-
if (!mounted || panelMode !== null) return;
|
|
247
|
+
if (!mounted || panelMode !== null || activating) return;
|
|
248
|
+
activating = true;
|
|
237
249
|
note = null;
|
|
238
250
|
render();
|
|
239
|
-
|
|
251
|
+
try {
|
|
252
|
+
await checkNow();
|
|
253
|
+
} finally {
|
|
254
|
+
activating = false;
|
|
255
|
+
}
|
|
240
256
|
if (!mounted) return;
|
|
241
257
|
if (variant === "badge" || openOn === "manual") {
|
|
242
258
|
if (onActivate) onActivate({ hasUpdate: hasUpdate(), latestVersion: snapshot?.latestVersion ?? null });
|
|
@@ -273,6 +289,14 @@ function mountUpdateEntry(container, options) {
|
|
|
273
289
|
} catch {
|
|
274
290
|
}
|
|
275
291
|
}
|
|
292
|
+
function onKeyDown(ev) {
|
|
293
|
+
try {
|
|
294
|
+
const e = ev;
|
|
295
|
+
if (!mounted || panelMode !== "dialog" || !e || e.key !== "Escape") return;
|
|
296
|
+
close();
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
}
|
|
276
300
|
function unmount() {
|
|
277
301
|
if (!mounted) return;
|
|
278
302
|
mounted = false;
|
|
@@ -287,11 +311,19 @@ function mountUpdateEntry(container, options) {
|
|
|
287
311
|
container.removeEventListener?.("click", onClick);
|
|
288
312
|
} catch {
|
|
289
313
|
}
|
|
314
|
+
try {
|
|
315
|
+
container.removeEventListener?.("keydown", onKeyDown);
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
290
318
|
}
|
|
291
319
|
try {
|
|
292
320
|
container.addEventListener?.("click", onClick);
|
|
293
321
|
} catch {
|
|
294
322
|
}
|
|
323
|
+
try {
|
|
324
|
+
container.addEventListener?.("keydown", onKeyDown);
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
295
327
|
if (variant === "inline") mountPanel("embedded");
|
|
296
328
|
else render();
|
|
297
329
|
if (autoCheck === "mount") void refresh();
|
package/dist/host.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type PhoneAction, type UpdateConfigInput } from './config.js';
|
|
1
|
+
import { type ChangelogPhoneAction, type PhoneAction, type UpdateConfigInput } from './config.js';
|
|
2
2
|
import type { EnvironmentKind, ReleaseChannel } from './ports.js';
|
|
3
3
|
import { type UpdateQueueState } from './queue.js';
|
|
4
|
-
export { buildPhoneName, buildPhoneNames, resolveUpdateConfig } from './config.js';
|
|
5
|
-
export type { PhoneAction, UpdateConfigInput } from './config.js';
|
|
4
|
+
export { buildChangelogPhoneName, buildPhoneName, buildPhoneNames, resolveUpdateConfig } from './config.js';
|
|
5
|
+
export type { ChangelogPhoneAction, PhoneAction, UpdateConfigInput } from './config.js';
|
|
6
6
|
export { parseEventListManifest, checkEventFields, checkEventCounts } from './gate.js';
|
|
7
7
|
export type { GateEventLevel, GateEventKind, GateEventEntry, GateEventCounts, GateEventList, GateFieldCheck, GateCountCheck } from './gate.js';
|
|
8
8
|
export { containingPackage, defaultHomeDir, profileNameValid, registrySpec, resolveTargetPackage } from './reader.js';
|
|
@@ -111,7 +111,7 @@ type LogCtx = {
|
|
|
111
111
|
fire: (level: string, event: string, fields: Record<string, unknown>) => void;
|
|
112
112
|
} | null;
|
|
113
113
|
export interface HostUpdate {
|
|
114
|
-
phoneNames: Record<PhoneAction, string>;
|
|
114
|
+
phoneNames: Record<PhoneAction, string> & Record<ChangelogPhoneAction, string>;
|
|
115
115
|
handlers: Record<string, (args: Record<string, unknown>) => Promise<Record<string, unknown>>>;
|
|
116
116
|
}
|
|
117
117
|
/**
|
package/dist/host.js
CHANGED
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
resolveUpdateConfig
|
|
11
11
|
} from "./config.js";
|
|
12
12
|
import { manualCommand } from "./commands.js";
|
|
13
|
+
import { fetchReleaseChangelogText } from "./changelog-io.js";
|
|
13
14
|
import { buildDiag } from "./diag.js";
|
|
14
|
-
import { isVersionAllowedInChannel, validRequestId } from "./service.js";
|
|
15
|
+
import { fetchNpmVersionRelease, isVersionAllowedInChannel, updateError, validReleaseVersion, validRequestId } from "./service.js";
|
|
15
16
|
import { createUpdateDiskPorts, createUpdateExecutor, createUpdateQueuePorts } from "./store.js";
|
|
16
17
|
import { createUpdateReader, defaultHomeDir, resolveProfileName, resolveTargetPackage } from "./reader.js";
|
|
17
18
|
import {
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
setOwnerIfFree,
|
|
28
29
|
visibleQueueFor
|
|
29
30
|
} from "./queue.js";
|
|
30
|
-
import { buildPhoneName, buildPhoneNames as buildPhoneNames2, resolveUpdateConfig as resolveUpdateConfig2 } from "./config.js";
|
|
31
|
+
import { buildChangelogPhoneName, buildPhoneName, buildPhoneNames as buildPhoneNames2, resolveUpdateConfig as resolveUpdateConfig2 } from "./config.js";
|
|
31
32
|
import { parseEventListManifest, checkEventFields, checkEventCounts } from "./gate.js";
|
|
32
33
|
import { containingPackage, defaultHomeDir as defaultHomeDir2, profileNameValid, registrySpec, resolveTargetPackage as resolveTargetPackage2 } from "./reader.js";
|
|
33
34
|
import {
|
|
@@ -87,7 +88,7 @@ import {
|
|
|
87
88
|
CHANGELOG_TAR_MAX_BYTES,
|
|
88
89
|
CHANGELOG_TARBALL_MAX_BYTES,
|
|
89
90
|
extractChangelogFromTar,
|
|
90
|
-
fetchReleaseChangelogText,
|
|
91
|
+
fetchReleaseChangelogText as fetchReleaseChangelogText2,
|
|
91
92
|
readInstalledChangelogText
|
|
92
93
|
} from "./changelog-io.js";
|
|
93
94
|
import { createUpdateDiskPorts as createUpdateDiskPorts2, createUpdateExecutor as createUpdateExecutor2, pathsForUpdate, resolveCliEntry } from "./store.js";
|
|
@@ -105,7 +106,7 @@ import {
|
|
|
105
106
|
compareReleaseVersions,
|
|
106
107
|
isPrereleaseVersion,
|
|
107
108
|
isVersionAllowedInChannel as isVersionAllowedInChannel2,
|
|
108
|
-
validReleaseVersion,
|
|
109
|
+
validReleaseVersion as validReleaseVersion2,
|
|
109
110
|
validVersion,
|
|
110
111
|
compareVersions
|
|
111
112
|
} from "./service.js";
|
|
@@ -524,6 +525,49 @@ function loggedPhone(method, kind, pluginId, fn, diagCtx) {
|
|
|
524
525
|
}
|
|
525
526
|
};
|
|
526
527
|
}
|
|
528
|
+
function loggedChangelogPhone(method, pluginId, fn, diagCtx) {
|
|
529
|
+
return async function(args) {
|
|
530
|
+
const t0 = Date.now();
|
|
531
|
+
const emit = (level, event, fields) => {
|
|
532
|
+
try {
|
|
533
|
+
if (phoneLogCtx && typeof phoneLogCtx.fire === "function") phoneLogCtx.fire(level, event, fields);
|
|
534
|
+
} catch {
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
const safeArgs = args && typeof args === "object" ? args : {};
|
|
538
|
+
try {
|
|
539
|
+
const out = await fn(safeArgs);
|
|
540
|
+
emit("info", "host.call", { method, latencyMs: Date.now() - t0, ok: true, kind: "update-changelog", pluginId });
|
|
541
|
+
return { ok: true, version: out.version, markdown: out.markdown };
|
|
542
|
+
} catch (error) {
|
|
543
|
+
const payload = toUpdateErrorPayload(error);
|
|
544
|
+
emit("warn", "host.call.fail", { method, kind: "update-changelog", errorHash: hash8(String(error?.message || payload.error)), pluginId });
|
|
545
|
+
let diag;
|
|
546
|
+
try {
|
|
547
|
+
const cfg = diagCtx?.config;
|
|
548
|
+
const overrides = diagCtx?.readerOverrides ?? {};
|
|
549
|
+
const envRaw = overrides?.environmentKind;
|
|
550
|
+
const runningRaw = overrides?.runningVersion;
|
|
551
|
+
const built = buildDiag({
|
|
552
|
+
errorCode: payload.error,
|
|
553
|
+
phoneKind: "update-changelog",
|
|
554
|
+
error,
|
|
555
|
+
args: safeArgs,
|
|
556
|
+
targetPackageName: cfg?.targetPackageName,
|
|
557
|
+
registryUrl: cfg?.registryUrl,
|
|
558
|
+
runningVersion: typeof runningRaw === "string" ? runningRaw : void 0,
|
|
559
|
+
latestVersion: void 0,
|
|
560
|
+
environmentKind: typeof envRaw === "string" ? envRaw : void 0,
|
|
561
|
+
latencyMs: Date.now() - t0
|
|
562
|
+
});
|
|
563
|
+
if (built && typeof built === "object") diag = built;
|
|
564
|
+
} catch {
|
|
565
|
+
diag = void 0;
|
|
566
|
+
}
|
|
567
|
+
return { ok: false, ...payload, ...diag ? { diag } : {} };
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
527
571
|
function createHostUpdate(deps = {}, configInput) {
|
|
528
572
|
const config = resolveUpdateConfig(configInput);
|
|
529
573
|
const pluginId = config.pluginId;
|
|
@@ -533,6 +577,28 @@ function createHostUpdate(deps = {}, configInput) {
|
|
|
533
577
|
else watchDesktopPnpm(ctx);
|
|
534
578
|
const readerOverrides = { ...deps.readerOverrides ?? {}, ctx, ...deps.pluginManager ? { pluginManager: deps.pluginManager } : {} };
|
|
535
579
|
const phoneNames = buildPhoneNames(config.prefix);
|
|
580
|
+
const changelogCache = /* @__PURE__ */ new Map();
|
|
581
|
+
async function readChangelog(args) {
|
|
582
|
+
const raw = args ? args["version"] : void 0;
|
|
583
|
+
if (!validReleaseVersion(raw)) throw updateError("invalid-release");
|
|
584
|
+
const version = raw;
|
|
585
|
+
if (!isVersionAllowedInChannel(version, config.releaseChannel)) throw updateError("invalid-release");
|
|
586
|
+
if (changelogCache.has(version)) return { version, markdown: changelogCache.get(version) ?? null };
|
|
587
|
+
const fetchImpl = readerOverrides.fetchImpl ?? globalThis.fetch;
|
|
588
|
+
const release = await fetchNpmVersionRelease(fetchImpl, config.checkTimeoutMs, {
|
|
589
|
+
targetPackageName: config.targetPackageName,
|
|
590
|
+
registryUrl: config.registryUrl,
|
|
591
|
+
releaseChannel: config.releaseChannel,
|
|
592
|
+
version
|
|
593
|
+
});
|
|
594
|
+
const markdown = await fetchReleaseChangelogText(
|
|
595
|
+
fetchImpl,
|
|
596
|
+
{ tarball: release.tarball, integrity: release.integrity, version },
|
|
597
|
+
{ targetPackageName: config.targetPackageName, registryUrl: config.registryUrl, timeoutMs: config.checkTimeoutMs }
|
|
598
|
+
);
|
|
599
|
+
changelogCache.set(version, markdown);
|
|
600
|
+
return { version, markdown };
|
|
601
|
+
}
|
|
536
602
|
async function readStatus(args) {
|
|
537
603
|
const reader = await getSharedReader(pluginId, config, {
|
|
538
604
|
...readerOverrides,
|
|
@@ -630,6 +696,10 @@ function createHostUpdate(deps = {}, configInput) {
|
|
|
630
696
|
[phoneNames.updateInstall]: loggedPhone(phoneNames.updateInstall, "update-install", pluginId, runInstall, {
|
|
631
697
|
config,
|
|
632
698
|
readerOverrides
|
|
699
|
+
}),
|
|
700
|
+
[phoneNames.updateChangelog]: loggedChangelogPhone(phoneNames.updateChangelog, pluginId, readChangelog, {
|
|
701
|
+
config,
|
|
702
|
+
readerOverrides
|
|
633
703
|
})
|
|
634
704
|
};
|
|
635
705
|
return { phoneNames, handlers };
|
|
@@ -682,6 +752,7 @@ export {
|
|
|
682
752
|
SKIPPED_FILE,
|
|
683
753
|
__resetSharedUpdateReaderForTests,
|
|
684
754
|
addSkipped,
|
|
755
|
+
buildChangelogPhoneName,
|
|
685
756
|
buildDiag2 as buildDiag,
|
|
686
757
|
buildPhoneName,
|
|
687
758
|
buildPhoneNames2 as buildPhoneNames,
|
|
@@ -709,7 +780,7 @@ export {
|
|
|
709
780
|
enforceBudget as enforceDiagBudgetForPhone,
|
|
710
781
|
enqueueInQueue2 as enqueueInQueue,
|
|
711
782
|
extractChangelogFromTar,
|
|
712
|
-
fetchReleaseChangelogText,
|
|
783
|
+
fetchReleaseChangelogText2 as fetchReleaseChangelogText,
|
|
713
784
|
hasVisibleSections,
|
|
714
785
|
isHeadOfQueue2 as isHeadOfQueue,
|
|
715
786
|
isPrereleaseVersion,
|
|
@@ -741,7 +812,7 @@ export {
|
|
|
741
812
|
setOwnerIfFree2 as setOwnerIfFree,
|
|
742
813
|
skipKey,
|
|
743
814
|
truncateToWordBoundary,
|
|
744
|
-
validReleaseVersion,
|
|
815
|
+
validReleaseVersion2 as validReleaseVersion,
|
|
745
816
|
validVersion,
|
|
746
817
|
visibleQueueFor2 as visibleQueueFor
|
|
747
818
|
};
|