dsh-plugin-update 0.3.1 → 0.5.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 CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  格式为 Keep-a-Changelog 子集:`Added/Fixed/Changed` 必写,`Deprecated/Removed/Security` 有则透传,`Unreleased` 面板忽略,禁止 git-log 直倒。面板缺日志时中性提示,不挡安装。
4
4
 
5
+ ## [0.5.0] - 2026-10-05
6
+
7
+ ### Added
8
+ - 主题首选名 `archive`(档案卷纸面浅色):`d5-paper` 为旧别名仍可用,渲染逐字相同;入口件与批量面板同一套取值。
9
+ - 弹窗关闭落地 `onCloseRequested`:dialog 下点「关闭」/按 Esc 先交调用方撤 DOM 再停轮询;入口件打开的 dialog 已内置(收 dialog + 还原按钮 + 重查一次)。
10
+
11
+ ### Fixed
12
+ - 弹窗点「关闭」没反应:此前只停轮询不撤 DOM(入口件打开的 dialog 还会卡死),现走关闭落地。
13
+ - 悬停/聚焦闪烁:轮询每秒整树重写 innerHTML 打断 hover/focus,现输出逐字相同时不碰 DOM(状态变化仍即时重绘;单面板与批量面板同口径)。
14
+
15
+ ## [0.4.0] - 2026-10-05
16
+
17
+ ### Added
18
+ - 更新日志无脑接入(#38):宿主新增 `updateChangelog` 电话(入参版本号,回该版 tarball 内全文,取不到回空;同源 + 精确路径 + `integrity` + 三墙复用,按版本记住结果);面板 `autoChangelog` 默认自动展示(有新版调一次,显式文本仍赢,`false` 退回手动);入口件透传跟上;批量详情行按行自动取(无新增批量电话,单行失败只影响该行);HTTP 版白名单放行,老三电话形状不变。
19
+ - 按钮交互反馈:查新版/安装在途置忙(禁用 + 文案 + `aria-busy` + 纯 CSS 转圈,并发连点只认第一次);默认主题补 hover / 按下下沉 / 过渡;busy 脉冲与 `prefers-reduced-motion` 关闭。
20
+ - 原生 `title` 说明:单面板全部动作按钮、批量宏按钮带悬停一句话(零成本 tooltip,不引入浮层组件)。
21
+ - Toast 语义:复制/重启类回执 5 秒后自动过期,不靠下次点击才消失。
22
+ - 首帧骨架:快照没到之前版本条画微光占位(`aria-hidden`,不进语义)。
23
+ - 批量「取消这一批」鼠标两步确认(第一次上膛红框,点别的自动卸膛;程序调 `act('cancel')` 仍一次即执行)。
24
+ - Esc 关弹窗:单面板 / 批量面板 dialog 与入口件 dialog 均支持(容器契约内,只用 innerHTML + 事件)。
25
+
5
26
  ## [0.3.1] - 2026-10-05
6
27
 
7
28
  ### Added
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  给 DSH 插件加「检查更新 / 安装更新」能力的 npm 包。宿主侧一段接线,面板侧挂一个现成组件,装不上时给用户一条可复制的手工命令。
4
4
 
5
- 要求 Node 22 或更高,零运行时依赖。当前版本 `0.3.1`。
5
+ 要求 Node 22 或更高,零运行时依赖。当前版本 `0.5.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
- 这一步得到三个电话名:`notes.updateStatus`、`notes.updateCheck`、`notes.updateInstall`。
55
+ 这一步得到四个电话名:`notes.updateStatus`、`notes.updateCheck`、`notes.updateInstall`、`notes.updateChangelog`(取日志:给版本号,回该版 tarball 里的更新日志全文,取不到回空)。
56
56
 
57
57
  `pluginId` 必填(非空字符串,不含路径分隔符)。单例复用键强制含插件标识,多插件不串内存状态与锁。
58
58
  除 `pluginId` 之外的配置都可选并带默认值,不传即走默认(见第 3 节)。
@@ -69,7 +69,7 @@ createHostUpdate(
69
69
 
70
70
  本包是 ESM(`"type": "module"`):ESM 插件直接 `import`;CJS 插件用 `await import('dsh-plugin-update')`。
71
71
 
72
- ### 三个电话的入参与回参
72
+ ### 四个电话的入参与回参
73
73
 
74
74
  面板与宿主两侧共用这一份契约(`…` 是你的前缀):
75
75
 
@@ -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` 由面板自己生成(同一个编号重复提交直接返回旧结果)。
@@ -127,6 +128,7 @@ const panel = mountUpdatePanel(document.getElementById('update-slot'), {
127
128
  call: (name, args) => host.call(name, args),
128
129
  })
129
130
  // 离开时 panel.unmount():只停轮询,安装在宿主侧继续跑;重开面板立刻重查,1 秒内恢复显示。
131
+ // 弹窗版想让「关闭」按钮真把弹窗撤掉:传 onCloseRequested(面板点关闭/Esc 时先调它撤 DOM,再停轮询;入口件打开的 dialog 已内置)。
130
132
  ```
131
133
 
132
134
  组件内部消化的事(调用者不再写):按 `panelPollMs` 轮询查状态(下限 250 毫秒);安装按钮状态跟随快照的 `canInstall`,不另写门控规则;装不了的原因按第 5.2 节展示中文一句话;`pending-restart` 单独横幅加重启指引,不再给安装按钮;手工命令展示与复制;排队位置展示与 `showOthers` 开关;跳过按版本记(“已跳过 X.Y.Z · 恢复”在同一行,不藏进设置页);失败时旁边的“复制诊断”一键给出脱敏后的自包含文本(稳定码、版本、宿主、队列位置),深挖仍看日志(组件里留着第 6 节第 11 条的过滤口径)。
@@ -140,7 +142,7 @@ mountUpdatePanel(slot, { pluginId: 'my-notes-plugin', prefix: 'notes', call: hos
140
142
 
141
143
  面板画成档案五章:**01 检查与安装、02 更新日志、03 更新队列、04 错误信息、05 手工命令**(五章恒在,缺内容给中性提示,不跳号)。档案头一行是「插件名 + 使用范围 + `profile` 牌」:使用范围由面板自动向宿主索取(电话入参 `includeEnv`,宿主只回**范围名与宿主种类**、不回任何路径),显示的就是更新要落到的那个 profile——`web` 与 `desktop` 各装一份,装错范围是严重故障,所以这一栏宁可显示“未知”也不猜。调用方知道得更准时可以用 `profileName` 显式覆盖。
142
144
 
143
- 可选专业主题(D5 档案卷,不替换默认):挂载时加 `theme: 'd5-paper'` 即换肤(右上大印章「待查/可装/安装中/待重启/受阻/已最新」+ 横幅小印章一字 + profile 牌 + 待重启衬线横幅配手绘 SVG 标 + 窄屏印章固定 + 省略号逐字折叠 + 浅深双主题跟随系统),内核 DOM 顺序不动、复制诊断常在;不传即最小可用默认样式。运行时用 `panel.setTheme('d5-paper' | 'default')` 可切。
145
+ 可选档案卷主题(纸面浅色案卷风,不替换默认):挂载时加 `theme: 'archive'` 即换肤(右上大印章「待查/可装/安装中/待重启/受阻/已最新」+ 横幅小印章一字 + profile 牌 + 待重启衬线横幅配手绘 SVG 标 + 窄屏印章固定 + 省略号逐字折叠 + 浅深双主题跟随系统),内核 DOM 顺序不动、复制诊断常在;不传即最小可用默认深色样式。运行时用 `panel.setTheme('archive' | 'default')` 可切。旧值 `theme: 'd5-paper'` 仍可用(同一套渲染)。入口件与批量面板是同一个 `theme` 参数,取值同一套。
144
146
 
145
147
  类型定义随包分发(`dsh-plugin-update/panel` 的 `.d.ts`),不用自编译;面板离线可读,与包版本绑定。
146
148
 
@@ -215,7 +217,7 @@ for (const [name, handler] of Object.entries(multi.handlers)) registry.set(name,
215
217
  ```
216
218
 
217
219
  五个批量电话(`<prefix>` 即上面的 `life`):`batchStatus` / `batchCheck` / `batchInstall` / `batchResume` / `batchCancel`;
218
- 每个目标的三个单插件电话照旧以**各自前缀**暴露(`ilife-bill.updateStatus` 等)。
220
+ 每个目标的四个单插件电话照旧以**各自前缀**暴露(`ilife-bill.updateStatus` 等)。
219
221
 
220
222
  回包形状(成功恰好四项,失败只有三项):
221
223
 
@@ -232,7 +234,7 @@ import { mountUpdateBatchPanel } from 'dsh-plugin-update/panel-batch'
232
234
  const panel = mountUpdateBatchPanel(el, {
233
235
  prefix: 'life',
234
236
  call: (name, args) => host.call(name, args),
235
- theme: 'd5-paper', // 与单插件面板同一套皮肤
237
+ theme: 'archive', // 与单插件面板同一套皮肤(archive = 档案卷,旧值 d5-paper 仍可用)
236
238
  })
237
239
  ```
238
240
 
@@ -252,7 +254,7 @@ mountUpdateBatchPanelHttp(document.querySelector('#batch'), {
252
254
  三条硬约束(都在实现里):**一行只回答一个问题**(这家的下一步是什么,状态词全中文可执行);
253
255
  **行内动作只作用于该行**,「全部更新」是宏而不是第二个状态机;**待重启与失败常驻横幅**,不藏进展开里。
254
256
 
255
- 点任意一行展开该家详情:**内容是单插件那套五章内核的只读渲染,动作由批量面板自己提供**。
257
+ 点任意一行展开该家详情:**内容是单插件那套五章内核的只读渲染,动作由批量面板自己提供**。第 02 章日志由批量面板按行自动取该行自己的 `updateChangelog` 电话(按行+版本记住结果,取不到即中性提示,不挡安装;`autoChangelog: false` 可关)。
256
258
  内核渲染时传 `actions: 'none'`——五章内容、进度条、「已跳过」提示一字不减,**动作按钮一个都不画**
257
259
  (内核里那些按钮带的是 `data-action`,批量面板只认 `data-act`;照搬 markup 而不接管行为,
258
260
  就会得到「可点却没反应」的死按钮——这条缝现在由渲染开关焊死)。详情里的动作行是批量面板自己的:
@@ -274,11 +276,33 @@ mountUpdateBatchPanelHttp(document.querySelector('#batch'), {
274
276
  **跨使用范围如实拒绝**:`web` 与 `desktop` 各自排队(装的是不同落点),混在一起的目标会回
275
277
  `cross-scope`,本包不会替你跨范围抢锁。
276
278
 
279
+ ### 第 2.7 节:更新日志自动展示(告诉用户更新了什么)
280
+
281
+ 任何插件的升级能力都是两件事:**① 给一套面板**(上面第 4 步 / 2.5 / 2.6,挂载一行即跑);**② 告诉用户这次更新了什么**(本节,写 md 即显示,零新增代码)。
282
+
283
+ ```js
284
+ import { mountUpdatePanel } from 'dsh-plugin-update/panel'
285
+
286
+ // 与第 4 步同一行,不用加任何参数:有新版时第 02 章“更新日志”自动出现
287
+ const panel = mountUpdatePanel(document.getElementById('update-slot'), {
288
+ pluginId: 'my-notes-plugin',
289
+ prefix: 'notes',
290
+ call: (name, args) => host.call(name, args),
291
+ })
292
+ ```
293
+
294
+ 作者侧只写文件,不写代码(发布后用户自动看到):
295
+
296
+ 1. 包根放 `CHANGELOG.md`(文件名全大写,小写视为没有),版本节形如 `## [1.2.0] - 2026-10-05`(最新在前),分类用 `### Added/Fixed/Changed`(面板展开;`Deprecated/Removed/Security` 折叠;`Unreleased` 与空节忽略)。
297
+ 2. `package.json` 的 `files` 白名单加上 `CHANGELOG.md`(否则发出去的包里没有它,面板永远读不到),发布前跑一次 `npm publish --dry-run` 核对。
298
+
299
+ 行为:有新版时面板按新版号自动取一次该版 tarball 里的全文(复用官方源 + `integrity` 校验,同一版本只取一次);删了文件或取不到即中性提示,**安装永远不受影响**。想自己接管就传 `autoChangelog: false` 退回手动(备好文本后 `mountUpdatePanel({ changelogMarkdown })` 或 `setChangelogMarkdown` 传入,显式文本永不被覆盖)。入口件与批量面板同样自动:前者透传,后者展开行按行取。细节与边界见第 5.8 节。
300
+
277
301
  ### 升级本包(已经接入过的项目)
278
302
 
279
303
  宿主种类与安装出口都由本包自己探测和选择,**升级依赖即可,宿主侧与面板侧都不用改代码**:
280
304
 
281
- 1. 依赖版本提到 `^0.3.1`。
305
+ 1. 依赖版本提到 `^0.5.0`。
282
306
  2. 重新装 / 发一版你自己的插件,让新依赖进当前使用范围(运行时用的是 `node_modules` 里那份)。
283
307
  3. 如果你自己接过一版宿主安装出口,把它删掉——它会挡在本包的路由前面。
284
308
 
@@ -426,7 +450,8 @@ dsh plugin --profile my-web add --save-exact my-notes-plugin@1.2.3 --registry=ht
426
450
 
427
451
  - 有新版时面板在横幅下方展示“更新说明(当前版 → 新版):”,按目标包内 `CHANGELOG.md`(Keep-a-Changelog 子集)渲染:`Added/Fixed/Changed` 展开,`Deprecated/Removed/Security` 折叠,`Unreleased` 与空节不展示。
428
452
  - 作者未提供说明时显示“作者未提供更新说明,安装不受影响。”——缺日志永不挡安装,不改变 `canInstall` 与 `blockedReason`。
429
- - 说明文本由集成方备好后传入:已装版离线读本机 `node_modules/<目标包>/CHANGELOG.md`,新版按需取新版 tarball 内同名文件(复用官方源与 `integrity` 校验,取不到即回落中性提示;参考包根导出的 `readInstalledChangelogText` / `fetchReleaseChangelogText`),经 `mountUpdatePanel({ changelogMarkdown })` 或 `setChangelogMarkdown` 交给面板。
453
+ - 默认自动:面板看到有新版即按新版号调一次 `….updateChangelog`,回来自己填进第 02 章;同一版本只取一次,取不到即中性提示。作者侧只要写好包根 `CHANGELOG.md` 并随包发布,零新增代码。
454
+ - 手动模式(老用法照旧):传 `autoChangelog: false` 即退回手动——已装版离线读本机 `node_modules/<目标包>/CHANGELOG.md`,新版按需取新版 tarball 内同名文件(复用官方源与 `integrity` 校验,取不到即回落中性提示;参考包根导出的 `readInstalledChangelogText` / `fetchReleaseChangelogText`),经 `mountUpdatePanel({ changelogMarkdown })` 或 `setChangelogMarkdown` 交给面板;显式传过的文本自动链路永不覆盖。
430
455
 
431
456
  ## 6. 排错
432
457
 
@@ -450,7 +475,7 @@ dsh plugin --profile my-web add --save-exact my-notes-plugin@1.2.3 --registry=ht
450
475
 
451
476
  照第 2 节把名字换成自己的之后,逐条打勾:
452
477
 
453
- 1. 宿主启动不报错,`update.phoneNames` 读到自家前缀的三个电话名,查状态返回六字段快照。
478
+ 1. 宿主启动不报错,`update.phoneNames` 读到自家前缀的四个电话名,查状态返回六字段快照。
454
479
  2. 面板按间隔轮询到快照,能展示第 5.2 节的原因文案;有新版时安装按钮可用,无新版与待重启时按钮状态正确。
455
480
  3. 模拟一次 `pending-restart` 能看到第 5.4 节的横幅,模拟一次自动装失败能看到第 5.3 节的命令可复制执行。
456
481
  4. 第二家同机隔离与串行:两家各传自己的插件标识与电话名前缀,电话名、落盘目录、锁文件逐个不同;安装执行跨插件串行——同范围撞上时后到者报 `update-busy`,凭队列位置(第 5.7 节)重试。
@@ -466,6 +491,25 @@ import { mountUpdatePanel } from 'dsh-plugin-update/panel'
466
491
  mountUpdatePanel(slot, { pluginId: 'notes', prefix: 'notes', call: host.call })
467
492
  ```
468
493
 
494
+ **更新日志自动**(#38:写 md 即显示,零新增代码):
495
+
496
+ ```js
497
+ // 宿主侧:createHostUpdate 顺手注册第 4 个电话,无新增配置
498
+ // 入参 { version }(发行版号,先验 validReleaseVersion + 通道门禁)
499
+ // 成功 { ok: true, version, markdown: string | null }(取不到即 null,中性提示)
500
+ // 失败复用 error / errorKind 体系;回包无路径、无快照;按版本记住结果
501
+ import { buildChangelogPhoneName } from 'dsh-plugin-update'
502
+
503
+ buildChangelogPhoneName('notes') // 'notes.updateChangelog',与宿主侧同一套拼法
504
+
505
+ // 面板侧:默认自动,有新版调一次;显式文本仍赢,false 退回手动
506
+ mountUpdatePanel(slot, { pluginId: 'my-notes-plugin', prefix: 'notes', call: host.call })
507
+ // mountUpdatePanel(slot, { pluginId, prefix, call, autoChangelog: false })
508
+ // mountUpdatePanel(slot, { pluginId, prefix, call, changelogMarkdown }) // 手动模式
509
+ ```
510
+
511
+ - 入口件(`dsh-plugin-update/entry`)把 `autoChangelog / changelogMarkdown` 透传给面板 dialog/inline;批量面板(`dsh-plugin-update/panel-batch`)展开行自动调该行自己的 `updateChangelog`(无新增批量电话,`autoChangelog: false` 可关整批);HTTP 版(`dsh-plugin-update/http`)白名单已放行,默认路径即达。
512
+
469
513
  **客户端入口**(`dist/client.js`,构建期打包用):
470
514
 
471
515
  ```js
@@ -506,6 +550,6 @@ queuePositionOf(queueState, 'my-plugin', requestId) // 0 = 在装,1..n = 顺
506
550
 
507
551
  ## 9. 兼容与稳定性
508
552
 
509
- 这些形状稳定,可以放心依赖:三个电话名与入参回参、快照六字段、任务公开形状、配置只经函数入参注入、安装配方五键、日志事件字段基线(三个事件各带必填 `pluginId`)、历史落盘路径。
553
+ 这些形状稳定,可以放心依赖:四个电话名与入参回参(老三电话一字不动,`updateChangelog` 是新增)、快照六字段、任务公开形状、配置只经函数入参注入、安装配方五键、日志事件字段基线(三个事件各带必填 `pluginId`)、历史落盘路径。
510
554
 
511
555
  向后兼容的扩展:新增可选配置键、新增可选 `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
@@ -22,7 +22,8 @@ export interface UpdateEntryOptions {
22
22
  /** 调宿主电话:(phoneName, args) => Promise<reply>。 */
23
23
  call: (name: string, args: Record<string, unknown>) => Promise<unknown>;
24
24
  variant?: EntryVariant;
25
- theme?: 'default' | 'd5-paper';
25
+ /** 主题:`default` 最小可用深色,`archive` 档案卷纸面浅色(`d5-paper` 为旧别名仍可用)。 */
26
+ theme?: UpdatePanelTheme;
26
27
  /** 缺省 'mount':进页面静默查一次(只读)。'never' 则只在用户点击时查。 */
27
28
  autoCheck?: EntryAutoCheck;
28
29
  /** 缺省 'has-update':有新版才开面板,没有就只在原地给一句提示。 */
@@ -31,6 +32,12 @@ export interface UpdateEntryOptions {
31
32
  label?: string;
32
33
  profileName?: string;
33
34
  pollMs?: number;
35
+ /**
36
+ * 更新日志自动/手动(#38):透传给面板(dialog 与 inline 同走 mountPanel),默认自动;
37
+ * 显式文本仍赢,false 退回手动。入口件自己不取数。
38
+ */
39
+ autoChangelog?: boolean;
40
+ changelogMarkdown?: string | null;
34
41
  /** variant='badge' 或 openOn='manual' 时,点击交给接入方(自己跳自己的页面)。 */
35
42
  onActivate?: (state: {
36
43
  hasUpdate: boolean;
@@ -46,7 +53,7 @@ export interface UpdateEntryController {
46
53
  close(): void;
47
54
  /** 当前按钮上的状态文案(接入方做自定义排版时读它)。 */
48
55
  label(): string;
49
- setTheme(theme: 'default' | 'd5-paper'): void;
56
+ setTheme(theme: UpdatePanelTheme): void;
50
57
  unmount(): void;
51
58
  }
52
59
  /**
package/dist/entry.js CHANGED
@@ -2,7 +2,8 @@
2
2
  import { DEFAULT_PREFIX, MIN_PANEL_POLL_MS, buildPhoneNames } from "./config.js";
3
3
  import {
4
4
  failureCodeOf,
5
- mountUpdatePanel
5
+ mountUpdatePanel,
6
+ normalizePanelTheme
6
7
  } from "./panel.js";
7
8
  const LABEL_IDLE = "\u68C0\u67E5\u66F4\u65B0";
8
9
  const LABEL_FAILED = "\u66F4\u65B0\u5931\u8D25\uFF0C\u70B9\u6B64\u67E5\u770B";
@@ -48,6 +49,13 @@ const UPDATE_ENTRY_CSS = [
48
49
  ".dsh-upd-entry-btn{font:inherit;border:1px solid var(--dsh-upd-line,#d1d5db);border-radius:6px;",
49
50
  "background:var(--dsh-upd-btn,#f9fafb);color:inherit;padding:4px 12px;cursor:pointer}",
50
51
  ".dsh-upd-entry-btn:hover{border-color:var(--dsh-upd-primary,#2563eb)}",
52
+ ".dsh-upd-entry-btn,.dsh-upd-entry-dot{transition:background-color .15s ease,border-color .15s ease,color .15s ease,transform .06s ease}",
53
+ ".dsh-upd-entry-btn:active:not(:disabled){transform:translateY(1px)}",
54
+ ".dsh-upd-entry-btn:disabled,.dsh-upd-entry-dot:disabled{opacity:.55;cursor:wait}",
55
+ '.dsh-upd-entry-btn[aria-busy="true"]::after{content:"";display:inline-block;width:10px;height:10px;margin-left:7px;vertical-align:-1px;',
56
+ "border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:dsh-upd-entry-spin .8s linear infinite}",
57
+ "@keyframes dsh-upd-entry-spin{to{transform:rotate(360deg)}}",
58
+ '@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
59
  ".dsh-upd-entry-btn:focus-visible,.dsh-upd-entry-dot:focus-visible{outline:2px solid var(--dsh-upd-focus,#2563eb);outline-offset:1px}",
52
60
  '.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
61
  '.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)}',
@@ -106,10 +114,10 @@ function mountUpdateEntry(container, options) {
106
114
  if (openOn !== "has-update" && openOn !== "always" && openOn !== "manual") {
107
115
  throw new Error(`[dsh-plugin-update] \u70B9\u51FB\u53BB\u5411\u975E\u6CD5\uFF1A\u53EA\u6536 has-update / always / manual\uFF08\u6536\u5230 ${JSON.stringify(options.openOn)}\uFF09`);
108
116
  }
109
- let theme = options.theme ?? "default";
110
- if (theme !== "default" && theme !== "d5-paper") {
111
- throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
117
+ if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive" && options.theme !== "d5-paper") {
118
+ throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08d5-paper \u4E3A\u65E7\u522B\u540D\u4ECD\u53EF\u7528\uFF09\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
112
119
  }
120
+ let theme = normalizePanelTheme(options.theme ?? "default");
113
121
  const pollMs = options.pollMs;
114
122
  if (pollMs !== void 0 && (typeof pollMs !== "number" || !Number.isFinite(pollMs) || pollMs < MIN_PANEL_POLL_MS)) {
115
123
  throw new Error(`[dsh-plugin-update] \u9762\u677F\u8F6E\u8BE2\u95F4\u9694\u975E\u6CD5\uFF1A\u4E0D\u5F97\u5C0F\u4E8E 250 \u6BEB\u79D2\uFF08\u6536\u5230 ${JSON.stringify(options.pollMs)}\uFF09`);
@@ -121,6 +129,7 @@ function mountUpdateEntry(container, options) {
121
129
  let snapshot = null;
122
130
  let error = null;
123
131
  let note = null;
132
+ let activating = false;
124
133
  let mounted = true;
125
134
  let panel = null;
126
135
  let panelMode = null;
@@ -156,9 +165,10 @@ function mountUpdateEntry(container, options) {
156
165
  }
157
166
  function entryHTML() {
158
167
  const kind = entryStateKind(stateOf());
159
- const text = currentLabel();
168
+ const text = activating ? "\u6B63\u5728\u67E5\u65B0\u7248\u2026" : currentLabel();
169
+ const busyAttr = activating ? ' disabled aria-busy="true"' : "";
160
170
  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>`;
171
+ 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
172
  const noteHTML = note ? `<span class="dsh-upd-entry-note" data-dsh-upd-note="1">${escapeHtml(note)}</span>` : "";
163
173
  return `<style>${UPDATE_ENTRY_CSS}</style>
164
174
  <span class="dsh-upd-entry" data-variant="${variant}" data-state="${kind}"${themeAttr}>${control}${noteHTML}</span>`;
@@ -208,6 +218,10 @@ function mountUpdateEntry(container, options) {
208
218
  theme,
209
219
  pollMs,
210
220
  profileName,
221
+ autoChangelog: options.autoChangelog,
222
+ changelogMarkdown: options.changelogMarkdown ?? null,
223
+ // 面板点「关闭」即走入口件的完整关闭(收 dialog + 还原按钮 + 重查一次),不再是面板自己停轮询。
224
+ onCloseRequested: () => close(),
211
225
  call: panelCall
212
226
  });
213
227
  panelMode = mode;
@@ -233,10 +247,15 @@ function mountUpdateEntry(container, options) {
233
247
  void refresh();
234
248
  }
235
249
  async function activate() {
236
- if (!mounted || panelMode !== null) return;
250
+ if (!mounted || panelMode !== null || activating) return;
251
+ activating = true;
237
252
  note = null;
238
253
  render();
239
- await checkNow();
254
+ try {
255
+ await checkNow();
256
+ } finally {
257
+ activating = false;
258
+ }
240
259
  if (!mounted) return;
241
260
  if (variant === "badge" || openOn === "manual") {
242
261
  if (onActivate) onActivate({ hasUpdate: hasUpdate(), latestVersion: snapshot?.latestVersion ?? null });
@@ -252,11 +271,11 @@ function mountUpdateEntry(container, options) {
252
271
  render();
253
272
  }
254
273
  function setTheme(next) {
255
- if (next !== "default" && next !== "d5-paper") {
256
- throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 ${JSON.stringify(next)}\uFF09`);
274
+ if (next !== "default" && next !== "archive" && next !== "d5-paper") {
275
+ throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08d5-paper \u4E3A\u65E7\u522B\u540D\u4ECD\u53EF\u7528\uFF09\uFF08\u6536\u5230 ${JSON.stringify(next)}\uFF09`);
257
276
  }
258
- theme = next;
259
- if (panel) void panel.setTheme(next);
277
+ theme = normalizePanelTheme(next);
278
+ if (panel) void panel.setTheme(theme);
260
279
  else render();
261
280
  }
262
281
  function onClick(ev) {
@@ -273,6 +292,14 @@ function mountUpdateEntry(container, options) {
273
292
  } catch {
274
293
  }
275
294
  }
295
+ function onKeyDown(ev) {
296
+ try {
297
+ const e = ev;
298
+ if (!mounted || panelMode !== "dialog" || !e || e.key !== "Escape") return;
299
+ close();
300
+ } catch {
301
+ }
302
+ }
276
303
  function unmount() {
277
304
  if (!mounted) return;
278
305
  mounted = false;
@@ -287,11 +314,19 @@ function mountUpdateEntry(container, options) {
287
314
  container.removeEventListener?.("click", onClick);
288
315
  } catch {
289
316
  }
317
+ try {
318
+ container.removeEventListener?.("keydown", onKeyDown);
319
+ } catch {
320
+ }
290
321
  }
291
322
  try {
292
323
  container.addEventListener?.("click", onClick);
293
324
  } catch {
294
325
  }
326
+ try {
327
+ container.addEventListener?.("keydown", onKeyDown);
328
+ } catch {
329
+ }
295
330
  if (variant === "inline") mountPanel("embedded");
296
331
  else render();
297
332
  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
  };