dsh-plugin-update 0.5.0 → 0.5.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 +19 -0
- package/README.md +14 -5
- package/dist/changelog-io.d.ts +5 -0
- package/dist/changelog.d.ts +66 -3
- package/dist/changelog.js +412 -35
- package/dist/entry.d.ts +5 -1
- package/dist/entry.js +15 -7
- package/dist/host.d.ts +2 -2
- package/dist/host.js +57 -7
- package/dist/panel-batch.js +34 -5
- package/dist/panel.d.ts +2 -2
- package/dist/panel.js +61 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
格式为 Keep-a-Changelog 子集:`Added/Fixed/Changed` 必写,`Deprecated/Removed/Security` 有则透传,`Unreleased` 面板忽略,禁止 git-log 直倒。面板缺日志时中性提示,不挡安装。
|
|
4
4
|
|
|
5
|
+
## [0.5.2] - 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Security 必显:`### Security` 与 `Added/Fixed/Changed` 同级展开(页面查找可命中);单类超 200 条时前 200 条展开、其余收进该类内部折叠并标计数(64K/单条 500 字墙保留)。
|
|
9
|
+
- 诚实截断计数:超限类标题下加小字“共 M 条,仅显示前 N 条”(M 该版该类原始条数、N 实际渲染数,多版本各自标;未超限不打扰)。
|
|
10
|
+
- 撤回警告:目标版本标题含 `[YANKED]` 时第 02 章区间标题下方加警告横幅(`role=alert`):“目标版本 X.Y.Z 已被作者撤回(yanked),安装不受影响,继续前请确认。”;中间版本只保留标题后缀,不挡安装、不碰快照门控。
|
|
11
|
+
- 破坏标记:条目开头 `BREAKING:`/`不兼容:`(大小写不敏感,中英文冒号皆可,匹配前剥行首加粗/引用装饰)行首挂徽标(`role=img`)+ 前缀加粗,正文一字不动;正文中间散文永不误标。
|
|
12
|
+
- `validateChangelog(markdown)` 纯函数(零导入、永不抛错,与 parse 共享扫描仪):回 `{ ok, diagnostics: { line, code, hint }[] }`(line 原文件 1-based 行号,按行号排序);码表 `E_VERSION_TITLE`/`E_CATEGORY`/`E_BULLET_ORPHAN`(Error,会丢内容)与 `W_BREAKING_MAYBE`/`W_YANKED`/`W_TRUNCATED`/`W_TRUNCATED_FILE`/`W_EMPTY`(Warning);仅 CI/发布前用,运行时永不调用。
|
|
13
|
+
- 记住语义澄清:成功与取不到(null)按版本永久记(面板内存会话级 + 宿主进程级,不落盘;显式文本永不覆盖);传输失败不进缓存,手动查/换版/重开立即重试,轮询按退避问(`shouldFetchChangelog` 纯策略三处共用);`autoChangelog: false` 关闭整链。
|
|
14
|
+
- 文档:README §2.7/§5.8 同步必显/计数/横幅/标记/校验/记住的用户做法(只加 MUST/SHOULD,不放设计 rationale,详见 ADR-0002)。
|
|
15
|
+
|
|
16
|
+
## [0.5.1] - 2026-10-06
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- 入口件 `openOn: 'direct'`:点开即弹窗、不预查(面板挂载即自查;徽标形态仍走回调口径)。
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- 入口件小字对比度:原地提示与档案卷按钮脸自带底,深色宿主下也读得出。
|
|
23
|
+
|
|
5
24
|
## [0.5.0] - 2026-10-05
|
|
6
25
|
|
|
7
26
|
### Added
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
给 DSH 插件加「检查更新 / 安装更新」能力的 npm 包。宿主侧一段接线,面板侧挂一个现成组件,装不上时给用户一条可复制的手工命令。
|
|
4
4
|
|
|
5
|
-
要求 Node 22 或更高,零运行时依赖。当前版本 `0.5.
|
|
5
|
+
要求 Node 22 或更高,零运行时依赖。当前版本 `0.5.2`。
|
|
6
6
|
|
|
7
7
|
装上它你会拿到六样东西:
|
|
8
8
|
|
|
@@ -181,10 +181,12 @@ const entry = mountUpdateEntry(document.getElementById('upd-entry'), {
|
|
|
181
181
|
|---|---|---|---|
|
|
182
182
|
| 摆什么 | `button` / `badge` / `inline` | `button` | 按钮;只给一个状态点;面板本体直接嵌进来 |
|
|
183
183
|
| 什么时候查 | `mount` / `never` | `mount` | 进页面静默查一次(**只调 `.updateStatus`,只读**);`never` 则只在点击时查 |
|
|
184
|
-
| 点了做什么 | `has-update` / `always` / `manual` | `has-update` |
|
|
184
|
+
| 点了做什么 | `has-update` / `always` / `manual` / `direct` | `has-update` | 有新版才开面板;检查完总是开;交给 `onActivate` 自己跳;点开即弹窗、不预查 |
|
|
185
185
|
|
|
186
186
|
按钮文案随状态自己变:`检查更新` / `有新版 1.1.0` / `正在安装…` / `待重启` / `更新失败,点此查看`。
|
|
187
187
|
|
|
188
|
+
无新版时原地那句小字(`已是最新 X.Y.Z`)只在 `has-update` 下出现:不想看它就用 `openOn: 'always'`(检查完总是开弹窗,无新版在弹窗里看“已是最新”)或 `openOn: 'direct'`(点开即弹窗,连预查都省了,面板挂载即自查;徽标形态仍走回调口径)。
|
|
189
|
+
|
|
188
190
|
**一条铁律:检查是只读、安装是写入,两者不许合并成一个动作。** 入口件永远只做「查 + 打开面板」,
|
|
189
191
|
任何路径都不自动安装;用户必须在面板里明确点「安装」。想让点击交给自己(例如你已有自己的更新页):
|
|
190
192
|
|
|
@@ -293,16 +295,18 @@ const panel = mountUpdatePanel(document.getElementById('update-slot'), {
|
|
|
293
295
|
|
|
294
296
|
作者侧只写文件,不写代码(发布后用户自动看到):
|
|
295
297
|
|
|
296
|
-
1. 包根放 `CHANGELOG.md`(文件名全大写,小写视为没有),版本节形如 `## [1.2.0] - 2026-10-05`(最新在前),分类用 `### Added/Fixed/Changed
|
|
298
|
+
1. 包根放 `CHANGELOG.md`(文件名全大写,小写视为没有),版本节形如 `## [1.2.0] - 2026-10-05`(最新在前),分类用 `### Added/Fixed/Changed/Security`(面板必显展开;`Deprecated/Removed` 折叠;`Unreleased` 与空节忽略)。
|
|
297
299
|
2. `package.json` 的 `files` 白名单加上 `CHANGELOG.md`(否则发出去的包里没有它,面板永远读不到),发布前跑一次 `npm publish --dry-run` 核对。
|
|
298
300
|
|
|
299
301
|
行为:有新版时面板按新版号自动取一次该版 tarball 里的全文(复用官方源 + `integrity` 校验,同一版本只取一次);删了文件或取不到即中性提示,**安装永远不受影响**。想自己接管就传 `autoChangelog: false` 退回手动(备好文本后 `mountUpdatePanel({ changelogMarkdown })` 或 `setChangelogMarkdown` 传入,显式文本永不被覆盖)。入口件与批量面板同样自动:前者透传,后者展开行按行取。细节与边界见第 5.8 节。
|
|
300
302
|
|
|
303
|
+
安全事项必显:`### Security` 与 `Added/Fixed/Changed` 同级展开。单类超 200 条时前 200 条展开、其余收进该类内部折叠并标计数。目标版本被撤回(标题含 `[YANKED]`)时,第 02 章顶部加一条警告横幅,安装不受影响。条目开头写 `BREAKING:` 或 `不兼容:`(大小写不敏感,中英文冒号皆可)即挂破坏标记,正文不动。同一版本日志只取一次;取不到显示中性提示,安装不受影响。
|
|
304
|
+
|
|
301
305
|
### 升级本包(已经接入过的项目)
|
|
302
306
|
|
|
303
307
|
宿主种类与安装出口都由本包自己探测和选择,**升级依赖即可,宿主侧与面板侧都不用改代码**:
|
|
304
308
|
|
|
305
|
-
1. 依赖版本提到 `^0.5.
|
|
309
|
+
1. 依赖版本提到 `^0.5.2`。
|
|
306
310
|
2. 重新装 / 发一版你自己的插件,让新依赖进当前使用范围(运行时用的是 `node_modules` 里那份)。
|
|
307
311
|
3. 如果你自己接过一版宿主安装出口,把它删掉——它会挡在本包的路由前面。
|
|
308
312
|
|
|
@@ -448,10 +452,15 @@ dsh plugin --profile my-web add --save-exact my-notes-plugin@1.2.3 --registry=ht
|
|
|
448
452
|
|
|
449
453
|
### 5.8 更新说明
|
|
450
454
|
|
|
451
|
-
- 有新版时面板在横幅下方展示“更新说明(当前版 → 新版):”,按目标包内 `CHANGELOG.md`(Keep-a-Changelog 子集)渲染:`Added/Fixed/Changed`
|
|
455
|
+
- 有新版时面板在横幅下方展示“更新说明(当前版 → 新版):”,按目标包内 `CHANGELOG.md`(Keep-a-Changelog 子集)渲染:`Added/Fixed/Changed/Security` 必显展开,`Deprecated/Removed` 折叠,`Unreleased` 与空节不展示。
|
|
452
456
|
- 作者未提供说明时显示“作者未提供更新说明,安装不受影响。”——缺日志永不挡安装,不改变 `canInstall` 与 `blockedReason`。
|
|
453
457
|
- 默认自动:面板看到有新版即按新版号调一次 `….updateChangelog`,回来自己填进第 02 章;同一版本只取一次,取不到即中性提示。作者侧只要写好包根 `CHANGELOG.md` 并随包发布,零新增代码。
|
|
454
458
|
- 手动模式(老用法照旧):传 `autoChangelog: false` 即退回手动——已装版离线读本机 `node_modules/<目标包>/CHANGELOG.md`,新版按需取新版 tarball 内同名文件(复用官方源与 `integrity` 校验,取不到即回落中性提示;参考包根导出的 `readInstalledChangelogText` / `fetchReleaseChangelogText`),经 `mountUpdatePanel({ changelogMarkdown })` 或 `setChangelogMarkdown` 交给面板;显式传过的文本自动链路永不覆盖。
|
|
459
|
+
- Security 必显;超限进内折叠。截断计数按类标注“共 M 条,仅显示前 N 条”(M 原始条数、N 显示数)。
|
|
460
|
+
- to 版 yanked 时 02 章顶部横幅:“目标版本 X.Y.Z 已被作者撤回(yanked),安装不受影响,继续前请确认。”;中间版本只保留标题后缀。
|
|
461
|
+
- `BREAKING:`/`不兼容:` 只认条目开头,badge + 前缀加粗;疑似误写由 validate 报 warning。
|
|
462
|
+
- `validateChangelog(markdown)` 为纯函数(零导入、永不抛错),回 `{ ok, diagnostics: { line, code, hint }[] }`,line 为原文件行号;仅 CI/发布前用。
|
|
463
|
+
- memo:成功与取不到(null)按版本永久记;传输失败不记,手动查/换版/重开立即重试,轮询按退避问;`autoChangelog: false` 关闭整链。
|
|
455
464
|
|
|
456
465
|
## 6. 排错
|
|
457
466
|
|
package/dist/changelog-io.d.ts
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* - 已装版离线读:`node_modules/<目标包>/CHANGELOG.md`(与版本 pin 绑定,零新增联网);
|
|
10
10
|
* - 新版按需取:新版 tarball 内同名文件,复用官方源 + integrity 校验;
|
|
11
11
|
* 取不到一律回 null(调用方渲染中性提示,不挡安装、不写 blockedReason)。
|
|
12
|
+
*
|
|
13
|
+
* 记住策略(#41 终裁,三墙不动,只澄清注释):成功与取不到(null)按版本永久记
|
|
14
|
+
* (面板内存会话级 + 宿主进程级,不落盘,显式文本永不覆盖);传输失败不进缓存,
|
|
15
|
+
* 手动查新版/换版/重开面板立即重问,轮询按退避问(三处共用 shouldFetchChangelog,各存各的)。
|
|
16
|
+
* 三墙(8MB 压缩态/32MB 解压后/64K 日志文本)一字不动,超限回 null 由调用方按取不到记住。
|
|
12
17
|
*/
|
|
13
18
|
/** tarball 下载上限(字节,压缩态):超限直接回落中性提示,不解包、不OOM。 */
|
|
14
19
|
export declare const CHANGELOG_TARBALL_MAX_BYTES: number;
|
package/dist/changelog.d.ts
CHANGED
|
@@ -9,14 +9,16 @@
|
|
|
9
9
|
* 解析只做字符串切分,不读盘、不联网;版本语义与核心同口径
|
|
10
10
|
* (validReleaseVersion / compareReleaseVersions,stable 纯三段、prerelease 显式)。
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* - `Added/Fixed/Changed`
|
|
12
|
+
* 面板约定(#23 原文 + #41 四处优化终裁):
|
|
13
|
+
* - `Added/Fixed/Changed/Security` 必显展开;`Deprecated/Removed` 透传折叠;
|
|
14
14
|
* - `Unreleased` 与空节忽略;
|
|
15
15
|
* - 缺日志中性提示,不挡安装、不写 blockedReason、不动快照六字段
|
|
16
16
|
* (本模块不碰快照,面板只增一节 HTML,安装门控只跟快照);
|
|
17
17
|
* - 已装版离线读、新版按需取 tarball 同名文件复用官方源 integrity
|
|
18
18
|
* (I/O 在 `src/changelog-io.ts` Node 侧,取不到回落中性提示);
|
|
19
|
-
* -
|
|
19
|
+
* - 纯函数解析器(仅复用版本语义,不碰 Node 专属;新增 validate 与策略函数零新增导入)。
|
|
20
|
+
* - 截断数值不动(100 节/每类 200 条/单条 500 字/全文 64K),解析附计数元数据只记数字;
|
|
21
|
+
* 超限类标计数小字,Security 超限进内折叠;BREAKING 前缀识别 + 行首徽标;yanked 横幅只看 to 版。
|
|
20
22
|
*/
|
|
21
23
|
/** 包内日志文件名(包根,与 tarball 内同名文件同一名字)。 */
|
|
22
24
|
export declare const CHANGELOG_FILENAME = "CHANGELOG.md";
|
|
@@ -54,6 +56,8 @@ export interface ChangelogEntry {
|
|
|
54
56
|
yanked: boolean;
|
|
55
57
|
/** 六类各自的条目(空类为空数组,空节忽略由渲染层执行)。 */
|
|
56
58
|
sections: Record<ChangelogCategory, string[]>;
|
|
59
|
+
/** 每类原始条数(截断前计数,只记数字不留文本;缺省视为与 sections 等长)。 */
|
|
60
|
+
counts?: Record<ChangelogCategory, number>;
|
|
57
61
|
}
|
|
58
62
|
/** 是否 Unreleased(大小写不敏感,前后空白忽略)。 */
|
|
59
63
|
export declare function isUnreleasedVersion(v: unknown): boolean;
|
|
@@ -62,6 +66,41 @@ export declare function isUnreleasedVersion(v: unknown): boolean;
|
|
|
62
66
|
* 非字符串/空串回 [];超长截断后解析;未知版本标题与其下条目一律丢弃;
|
|
63
67
|
* 全空节版本直接丢弃(空节忽略);Unreleased 保留(渲染与区间时忽略)。
|
|
64
68
|
*/
|
|
69
|
+
/** 共享扫描仪(parse 与 validate 同一套,不许第二套正则)。 */
|
|
70
|
+
export declare const RE_VERSION_HEADING: RegExp;
|
|
71
|
+
export declare const RE_CATEGORY_HEADING: RegExp;
|
|
72
|
+
export declare const RE_BULLET: RegExp;
|
|
73
|
+
export declare const RE_FENCE_TOGGLE: RegExp;
|
|
74
|
+
export declare const RE_VERSION_NUM: RegExp;
|
|
75
|
+
export declare const RE_VERSION_DATE: RegExp;
|
|
76
|
+
export declare const RE_YANKED: RegExp;
|
|
77
|
+
export declare const RE_UNRELEASED: RegExp;
|
|
78
|
+
export declare function matchVersionHeading(line: unknown): string | null;
|
|
79
|
+
export declare function matchCategoryHeading(line: unknown): string | null;
|
|
80
|
+
export declare function matchBulletBody(line: unknown): string | null;
|
|
81
|
+
export declare function isFenceToggle(line: unknown): boolean;
|
|
82
|
+
/** 破坏标记徽标文案(中文 UI 一致,原文前缀另行加粗保留)。 */
|
|
83
|
+
export declare const BREAKING_BADGE_TEXT = "\u4E0D\u517C\u5BB9";
|
|
84
|
+
/** 剥行首 markdown 装饰(加粗/引用/前后空白),只为识别,渲染保留原文一字不动。 */
|
|
85
|
+
export declare function stripBreakingDecorations(s: unknown): string;
|
|
86
|
+
/** 条目是否为破坏标记(去饰后开头匹配,大小写不敏感,中英文冒号皆可)。 */
|
|
87
|
+
export declare function isBreakingChangelogItem(item: unknown): boolean;
|
|
88
|
+
/** 拆出破坏前缀(保留原文拼写与冒号),命中才回,否则 null。 */
|
|
89
|
+
export declare function splitBreakingPrefix(item: string): {
|
|
90
|
+
head: string;
|
|
91
|
+
prefix: string;
|
|
92
|
+
rest: string;
|
|
93
|
+
} | null;
|
|
94
|
+
/** 轮询退避(毫秒):传输失败后轮询按此退避,手动/换版/重开立即重问。 */
|
|
95
|
+
export declare const CHANGELOG_POLL_BACKOFF_MS: number;
|
|
96
|
+
/** 三处共用(宿主电话/单面板/批量按行,各存各的):成功与取不到已记住即不再问。 */
|
|
97
|
+
export declare function shouldFetchChangelog(opts: {
|
|
98
|
+
hasCache: boolean;
|
|
99
|
+
failedAt: number | null;
|
|
100
|
+
now: number;
|
|
101
|
+
isManual: boolean;
|
|
102
|
+
backoffMs?: number;
|
|
103
|
+
}): boolean;
|
|
65
104
|
export declare function parseChangelog(markdown: unknown): ChangelogEntry[];
|
|
66
105
|
/** 该节是否有可见内容(六类任一非空)。 */
|
|
67
106
|
export declare function hasVisibleSections(entry: ChangelogEntry | null | undefined): boolean;
|
|
@@ -81,6 +120,14 @@ export declare function selectChangelogEntries(entries: unknown, fromExclusive:
|
|
|
81
120
|
* 任一缺失/非法即回 [](调用方渲染中性提示,不挡安装)。
|
|
82
121
|
*/
|
|
83
122
|
export declare function changelogForUpdate(entries: unknown, runningVersion: unknown, latestVersion: unknown, installedVersion?: unknown): ChangelogEntry[];
|
|
123
|
+
/** 计数元数据取值(缺省视为与 sections 等长,只记数字)。 */
|
|
124
|
+
export declare function countsOf(entry: ChangelogEntry | null | undefined): Record<ChangelogCategory, number>;
|
|
125
|
+
/** 超限小字(诚实截断):共 M 条,仅显示前 N 条。 */
|
|
126
|
+
export declare function truncatedNoteHTML(total: number, shown: number): string;
|
|
127
|
+
/** 撤回横幅(只看 to 版,纯展示,不挡安装,文案冻结)。 */
|
|
128
|
+
export declare function yankedBannerHTML(version: unknown): string;
|
|
129
|
+
/** 单条目 HTML(含破坏标记徽标与前缀加粗,正文一字不动)。 */
|
|
130
|
+
export declare function renderChangelogItem(item: string): string;
|
|
84
131
|
/** 单节 HTML(调用方保证已过滤 Unreleased 与空节,本函数再兜底一次)。 */
|
|
85
132
|
export declare function renderChangelogSection(entry: ChangelogEntry): string;
|
|
86
133
|
/** 中性提示 HTML(缺日志、取不到、无区间内容时统一用它,不挡安装)。 */
|
|
@@ -94,3 +141,19 @@ export declare function renderChangelogHTML(entries: unknown, opts?: {
|
|
|
94
141
|
from?: string | null;
|
|
95
142
|
to?: string | null;
|
|
96
143
|
}): string;
|
|
144
|
+
/** 校验诊断(一行一码,行号为原文件 1-based,诊断按行号排序)。 */
|
|
145
|
+
export interface ChangelogDiagnostic {
|
|
146
|
+
line: number;
|
|
147
|
+
code: string;
|
|
148
|
+
hint: string;
|
|
149
|
+
}
|
|
150
|
+
export interface ChangelogValidation {
|
|
151
|
+
ok: boolean;
|
|
152
|
+
diagnostics: ChangelogDiagnostic[];
|
|
153
|
+
}
|
|
154
|
+
/** 校验提示文(与终裁码表一字对应,仅 CI/发布前用,运行时永不调用)。 */
|
|
155
|
+
export declare const CHANGELOG_VALIDATE_HINTS: Record<string, string>;
|
|
156
|
+
/** 疑似破坏误写(仅行首可疑,不报正文中间散文)。 */
|
|
157
|
+
export declare function isMaybeBreakingMisuse(bulletBody: unknown): boolean;
|
|
158
|
+
/** 纯校验(零新增导入,与 parse 共享扫描仪,永不抛错,仅 CI/发布前用)。 */
|
|
159
|
+
export declare function validateChangelog(markdown: unknown): ChangelogValidation;
|
package/dist/changelog.js
CHANGED
|
@@ -15,8 +15,8 @@ const CHANGELOG_ALL_CATEGORIES = [
|
|
|
15
15
|
"Removed",
|
|
16
16
|
"Security"
|
|
17
17
|
];
|
|
18
|
-
const CHANGELOG_MUST_SHOW = ["Added", "Fixed", "Changed"];
|
|
19
|
-
const CHANGELOG_FOLDED = ["Deprecated", "Removed"
|
|
18
|
+
const CHANGELOG_MUST_SHOW = ["Added", "Fixed", "Changed", "Security"];
|
|
19
|
+
const CHANGELOG_FOLDED = ["Deprecated", "Removed"];
|
|
20
20
|
const CHANGELOG_CATEGORY_ZH = {
|
|
21
21
|
Added: "\u65B0\u589E",
|
|
22
22
|
Fixed: "\u4FEE\u590D",
|
|
@@ -28,6 +28,9 @@ const CHANGELOG_CATEGORY_ZH = {
|
|
|
28
28
|
function emptySections() {
|
|
29
29
|
return { Added: [], Fixed: [], Changed: [], Deprecated: [], Removed: [], Security: [] };
|
|
30
30
|
}
|
|
31
|
+
function emptyCounts() {
|
|
32
|
+
return { Added: 0, Fixed: 0, Changed: 0, Deprecated: 0, Removed: 0, Security: 0 };
|
|
33
|
+
}
|
|
31
34
|
function isCategoryName(v) {
|
|
32
35
|
return CHANGELOG_ALL_CATEGORIES.includes(v);
|
|
33
36
|
}
|
|
@@ -60,6 +63,119 @@ function truncateBullet(text) {
|
|
|
60
63
|
if (t.length <= CHANGELOG_MAX_BULLET_CHARS) return t;
|
|
61
64
|
return `${t.slice(0, CHANGELOG_MAX_BULLET_CHARS - 1)}\u2026`;
|
|
62
65
|
}
|
|
66
|
+
const RE_VERSION_HEADING = /^##(?!#)\s*(.+?)\s*$/;
|
|
67
|
+
const RE_CATEGORY_HEADING = /^###\s*(.+?)\s*$/;
|
|
68
|
+
const RE_BULLET = /^\s*[-*+]\s+(.+?)\s*$/;
|
|
69
|
+
const RE_FENCE_TOGGLE = new RegExp("^\\s*(" + String.fromCharCode(96).repeat(3) + "|~~~)");
|
|
70
|
+
const RE_VERSION_NUM = /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/;
|
|
71
|
+
const RE_VERSION_DATE = /(\d{4}-\d{2}-\d{2})/;
|
|
72
|
+
const RE_YANKED = /\[YANKED\]/i;
|
|
73
|
+
const RE_UNRELEASED = /unreleased/i;
|
|
74
|
+
function matchVersionHeading(line) {
|
|
75
|
+
try {
|
|
76
|
+
const s = String(line !== null && line !== void 0 ? line : "");
|
|
77
|
+
const m = s.match(RE_VERSION_HEADING);
|
|
78
|
+
if (!m) return null;
|
|
79
|
+
return String(m[1] !== void 0 && m[1] !== null ? m[1] : "").trim();
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function matchCategoryHeading(line) {
|
|
85
|
+
try {
|
|
86
|
+
const s = String(line !== null && line !== void 0 ? line : "");
|
|
87
|
+
const m = s.match(RE_CATEGORY_HEADING);
|
|
88
|
+
if (!m) return null;
|
|
89
|
+
return String(m[1] !== void 0 && m[1] !== null ? m[1] : "").trim();
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function matchBulletBody(line) {
|
|
95
|
+
try {
|
|
96
|
+
const s = String(line !== null && line !== void 0 ? line : "");
|
|
97
|
+
const m = s.match(RE_BULLET);
|
|
98
|
+
if (!m) return null;
|
|
99
|
+
return String(m[1] !== void 0 && m[1] !== null ? m[1] : "");
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function isFenceToggle(line) {
|
|
105
|
+
try {
|
|
106
|
+
return RE_FENCE_TOGGLE.test(String(line !== null && line !== void 0 ? line : ""));
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const BREAKING_BADGE_TEXT = "\u4E0D\u517C\u5BB9";
|
|
112
|
+
function stripBreakingDecorations(s) {
|
|
113
|
+
try {
|
|
114
|
+
let t = String(s !== null && s !== void 0 ? s : "").trim();
|
|
115
|
+
let changed = true;
|
|
116
|
+
while (changed) {
|
|
117
|
+
changed = false;
|
|
118
|
+
if (t.charAt(0) === ">") {
|
|
119
|
+
t = t.slice(1).trim();
|
|
120
|
+
changed = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (t.slice(0, 2) === "**" || t.slice(0, 2) === "__") {
|
|
124
|
+
t = t.slice(2).trim();
|
|
125
|
+
changed = true;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (t.charAt(0) === "*" || t.charAt(0) === "_") {
|
|
129
|
+
t = t.slice(1).trim();
|
|
130
|
+
changed = true;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return t;
|
|
135
|
+
} catch {
|
|
136
|
+
return "";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function isBreakingChangelogItem(item) {
|
|
140
|
+
try {
|
|
141
|
+
if (typeof item !== "string") return false;
|
|
142
|
+
const t = stripBreakingDecorations(item);
|
|
143
|
+
if (!t) return false;
|
|
144
|
+
return /^(breaking|不兼容)\s*[::]/i.test(t);
|
|
145
|
+
} catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function splitBreakingPrefix(item) {
|
|
150
|
+
try {
|
|
151
|
+
const s = String(item !== null && item !== void 0 ? item : "");
|
|
152
|
+
const m = s.match(/^(\s*(?:>\s*|\*\*\s*|__\s*|\*\s*|_\s*)*)(breaking|不兼容)(\s*[::])/i);
|
|
153
|
+
if (!m) return null;
|
|
154
|
+
const head = String(m[1] !== void 0 && m[1] !== null ? m[1] : "");
|
|
155
|
+
const core = String(m[2] !== void 0 && m[2] !== null ? m[2] : "");
|
|
156
|
+
const colon = String(m[3] !== void 0 && m[3] !== null ? m[3] : "");
|
|
157
|
+
const prefix = core + colon;
|
|
158
|
+
const rest = s.slice(m[0].length);
|
|
159
|
+
return { head, prefix, rest };
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const CHANGELOG_POLL_BACKOFF_MS = 30 * 1e3;
|
|
165
|
+
function shouldFetchChangelog(opts) {
|
|
166
|
+
try {
|
|
167
|
+
const o = opts;
|
|
168
|
+
if (o.hasCache === true) return false;
|
|
169
|
+
if (o.isManual === true) return true;
|
|
170
|
+
const now = typeof o.now === "number" && Number.isFinite(o.now) ? o.now : Date.now();
|
|
171
|
+
const failedAt = typeof o.failedAt === "number" && Number.isFinite(o.failedAt) ? o.failedAt : null;
|
|
172
|
+
if (failedAt === null) return true;
|
|
173
|
+
const backoff = typeof o.backoffMs === "number" && Number.isFinite(o.backoffMs) && o.backoffMs > 0 ? Math.floor(o.backoffMs) : CHANGELOG_POLL_BACKOFF_MS;
|
|
174
|
+
return now - failedAt >= backoff;
|
|
175
|
+
} catch {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
63
179
|
function parseChangelog(markdown) {
|
|
64
180
|
try {
|
|
65
181
|
if (typeof markdown !== "string" || !markdown.trim()) return [];
|
|
@@ -86,32 +202,38 @@ function parseChangelog(markdown) {
|
|
|
86
202
|
};
|
|
87
203
|
for (const rawLine of lines) {
|
|
88
204
|
const line = String(rawLine ?? "");
|
|
89
|
-
if (
|
|
205
|
+
if (isFenceToggle(line)) {
|
|
90
206
|
inFence = !inFence;
|
|
91
207
|
continue;
|
|
92
208
|
}
|
|
93
209
|
if (inFence) continue;
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
210
|
+
const versionTitle = matchVersionHeading(line);
|
|
211
|
+
if (versionTitle !== null) {
|
|
96
212
|
pushCurrent();
|
|
97
213
|
cur = null;
|
|
98
214
|
curCat = null;
|
|
99
|
-
const title =
|
|
215
|
+
const title = versionTitle;
|
|
100
216
|
const { version, date, yanked } = extractVersionDateAndYanked(title);
|
|
101
217
|
if (!version) continue;
|
|
102
|
-
cur = { version, date, yanked, sections: emptySections() };
|
|
218
|
+
cur = { version, date, yanked, sections: emptySections(), counts: emptyCounts() };
|
|
103
219
|
continue;
|
|
104
220
|
}
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
const cat = normalizeCategory(
|
|
221
|
+
const catTitle = matchCategoryHeading(line);
|
|
222
|
+
if (catTitle !== null) {
|
|
223
|
+
const cat = normalizeCategory(catTitle);
|
|
108
224
|
curCat = cur && cat && isCategoryName(cat) ? cat : null;
|
|
109
225
|
continue;
|
|
110
226
|
}
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
const item = truncateBullet(
|
|
114
|
-
if (item)
|
|
227
|
+
const bulletBody = matchBulletBody(line);
|
|
228
|
+
if (bulletBody !== null && cur && curCat) {
|
|
229
|
+
const item = truncateBullet(bulletBody);
|
|
230
|
+
if (item) {
|
|
231
|
+
cur.sections[curCat].push(item);
|
|
232
|
+
try {
|
|
233
|
+
if (cur.counts) cur.counts[curCat] += 1;
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
115
237
|
continue;
|
|
116
238
|
}
|
|
117
239
|
const trimmed = line.trim();
|
|
@@ -189,46 +311,119 @@ function changelogForUpdate(entries, runningVersion, latestVersion, installedVer
|
|
|
189
311
|
function escapeChangelogHtml(text) {
|
|
190
312
|
return String(text ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
191
313
|
}
|
|
314
|
+
function countsOf(entry) {
|
|
315
|
+
try {
|
|
316
|
+
const out = { Added: 0, Fixed: 0, Changed: 0, Deprecated: 0, Removed: 0, Security: 0 };
|
|
317
|
+
if (!entry || typeof entry !== "object") return out;
|
|
318
|
+
for (const c of CHANGELOG_ALL_CATEGORIES) {
|
|
319
|
+
const n = entry.counts;
|
|
320
|
+
const v = n && typeof n[c] === "number" && Number.isFinite(n[c]) ? Math.floor(n[c]) : null;
|
|
321
|
+
if (v !== null && v >= 0) {
|
|
322
|
+
out[c] = v;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const list = entry.sections;
|
|
326
|
+
const arr = list ? list[c] : null;
|
|
327
|
+
out[c] = Array.isArray(arr) ? arr.length : 0;
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
} catch {
|
|
331
|
+
return { Added: 0, Fixed: 0, Changed: 0, Deprecated: 0, Removed: 0, Security: 0 };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function truncatedNoteHTML(total, shown) {
|
|
335
|
+
try {
|
|
336
|
+
const m = Math.floor(total);
|
|
337
|
+
const n = Math.floor(shown);
|
|
338
|
+
return '<div class="dsh-upd-changelog-count">\u5171 ' + String(m) + " \u6761\uFF0C\u4EC5\u663E\u793A\u524D " + String(n) + " \u6761</div>";
|
|
339
|
+
} catch {
|
|
340
|
+
return "";
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function yankedBannerHTML(version) {
|
|
344
|
+
try {
|
|
345
|
+
const v = typeof version === "string" ? version.trim() : "";
|
|
346
|
+
if (!v) return "";
|
|
347
|
+
return '<div class="dsh-upd-changelog-yanked" role="alert">\u76EE\u6807\u7248\u672C ' + escapeChangelogHtml(v) + " \u5DF2\u88AB\u4F5C\u8005\u64A4\u56DE\uFF08yanked\uFF09\uFF0C\u5B89\u88C5\u4E0D\u53D7\u5F71\u54CD\uFF0C\u7EE7\u7EED\u524D\u8BF7\u786E\u8BA4\u3002</div>";
|
|
348
|
+
} catch {
|
|
349
|
+
return "";
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function renderChangelogItem(item) {
|
|
353
|
+
try {
|
|
354
|
+
const s = String(item !== null && item !== void 0 ? item : "");
|
|
355
|
+
if (!s) return "";
|
|
356
|
+
const split = splitBreakingPrefix(s);
|
|
357
|
+
if (!split) return "<li>" + escapeChangelogHtml(s) + "</li>";
|
|
358
|
+
const head = escapeChangelogHtml(split.head);
|
|
359
|
+
const prefix = escapeChangelogHtml(split.prefix);
|
|
360
|
+
const rest = escapeChangelogHtml(split.rest);
|
|
361
|
+
const badge = '<span class="dsh-upd-breaking-badge" role="img" aria-label="\u7834\u574F\u6027\u53D8\u66F4">' + BREAKING_BADGE_TEXT + "</span> ";
|
|
362
|
+
return "<li>" + head + badge + "<strong>" + prefix + "</strong>" + rest + "</li>";
|
|
363
|
+
} catch {
|
|
364
|
+
return "";
|
|
365
|
+
}
|
|
366
|
+
}
|
|
192
367
|
function renderChangelogSection(entry) {
|
|
193
368
|
try {
|
|
194
369
|
if (!entry || typeof entry !== "object") return "";
|
|
195
|
-
const version = String(entry.version
|
|
370
|
+
const version = String(entry.version !== void 0 && entry.version !== null ? String(entry.version) : "").trim();
|
|
196
371
|
if (!version || isUnreleasedVersion(version)) return "";
|
|
197
372
|
if (!hasVisibleSections(entry)) return "";
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
373
|
+
const dateRaw = entry.date;
|
|
374
|
+
const date = typeof dateRaw === "string" ? dateRaw : "";
|
|
375
|
+
const yankedFlag = entry.yanked === true;
|
|
376
|
+
const yankedSuffix = yankedFlag ? " \xB7 \u5DF2\u64A4\u56DE" : "";
|
|
377
|
+
const title = (date ? version + " \xB7 " + date : version) + yankedSuffix;
|
|
378
|
+
const counts = countsOf(entry);
|
|
201
379
|
const parts = [];
|
|
202
|
-
parts.push(
|
|
203
|
-
parts.push(
|
|
380
|
+
parts.push('<div class="dsh-upd-changelog-version" data-version="' + escapeChangelogHtml(version) + '">');
|
|
381
|
+
parts.push('<div class="dsh-upd-changelog-title">' + escapeChangelogHtml(title) + "</div>");
|
|
204
382
|
for (const cat of CHANGELOG_MUST_SHOW) {
|
|
205
|
-
const items = Array.isArray(entry.sections
|
|
206
|
-
if (items.length === 0) continue;
|
|
207
|
-
const label =
|
|
208
|
-
parts.push(
|
|
209
|
-
parts.push(
|
|
383
|
+
const items = Array.isArray(entry.sections ? entry.sections[cat] : null) ? entry.sections[cat] : [];
|
|
384
|
+
if (!items || items.length === 0) continue;
|
|
385
|
+
const label = cat + " \xB7 " + CHANGELOG_CATEGORY_ZH[cat];
|
|
386
|
+
parts.push('<div class="dsh-upd-changelog-cat" data-cat="' + cat + '">');
|
|
387
|
+
parts.push('<div class="dsh-upd-changelog-catname">' + escapeChangelogHtml(label) + "</div>");
|
|
388
|
+
const total = counts[cat];
|
|
389
|
+
const shown = items.length;
|
|
390
|
+
if (total > shown) {
|
|
391
|
+
parts.push(truncatedNoteHTML(total, shown));
|
|
392
|
+
}
|
|
210
393
|
parts.push("<ul>");
|
|
211
394
|
for (const item of items) {
|
|
212
395
|
if (!item) continue;
|
|
213
|
-
|
|
396
|
+
const li = renderChangelogItem(item);
|
|
397
|
+
if (li) parts.push(li);
|
|
398
|
+
}
|
|
399
|
+
parts.push("</ul>");
|
|
400
|
+
if (cat === "Security" && total > shown) {
|
|
401
|
+
const restCount = total - shown;
|
|
402
|
+
parts.push('<details class="dsh-upd-changelog-security-more"><summary>\u5176\u4F59 ' + String(restCount) + ' \u6761</summary><div class="dsh-upd-changelog-more-note">\u4E3A\u4FDD\u6301\u9762\u677F\u6027\u80FD\uFF0C\u5176\u4F59\u6761\u76EE\u5DF2\u6298\u53E0\uFF0C\u53EF\u67E5\u770B\u539F\u6587\u3002</div></details>');
|
|
214
403
|
}
|
|
215
|
-
parts.push("</
|
|
404
|
+
parts.push("</div>");
|
|
216
405
|
}
|
|
217
406
|
for (const cat of CHANGELOG_FOLDED) {
|
|
218
|
-
const items = Array.isArray(entry.sections
|
|
219
|
-
if (items.length === 0) continue;
|
|
220
|
-
const label =
|
|
221
|
-
parts.push(
|
|
222
|
-
parts.push(
|
|
407
|
+
const items = Array.isArray(entry.sections ? entry.sections[cat] : null) ? entry.sections[cat] : [];
|
|
408
|
+
if (!items || items.length === 0) continue;
|
|
409
|
+
const label = cat + " \xB7 " + CHANGELOG_CATEGORY_ZH[cat] + "\uFF08" + String(items.length) + "\uFF09";
|
|
410
|
+
parts.push('<details class="dsh-upd-changelog-fold" data-cat="' + cat + '">');
|
|
411
|
+
parts.push("<summary>" + escapeChangelogHtml(label) + "</summary>");
|
|
412
|
+
const total = counts[cat];
|
|
413
|
+
const shown = items.length;
|
|
414
|
+
if (total > shown) {
|
|
415
|
+
parts.push(truncatedNoteHTML(total, shown));
|
|
416
|
+
}
|
|
223
417
|
parts.push("<ul>");
|
|
224
418
|
for (const item of items) {
|
|
225
419
|
if (!item) continue;
|
|
226
|
-
|
|
420
|
+
const li = renderChangelogItem(item);
|
|
421
|
+
if (li) parts.push(li);
|
|
227
422
|
}
|
|
228
423
|
parts.push("</ul></details>");
|
|
229
424
|
}
|
|
230
425
|
parts.push("</div>");
|
|
231
|
-
return parts.join(
|
|
426
|
+
return parts.join(String.fromCharCode(10));
|
|
232
427
|
} catch {
|
|
233
428
|
return "";
|
|
234
429
|
}
|
|
@@ -264,7 +459,165 @@ ${blocks.join("\n")}
|
|
|
264
459
|
return renderChangelogNeutral();
|
|
265
460
|
}
|
|
266
461
|
}
|
|
462
|
+
const CHANGELOG_VALIDATE_HINTS = {
|
|
463
|
+
E_VERSION_TITLE: "\u8BE5\u6807\u9898\u4E0D\u662F\u5408\u6CD5\u7248\u672C\u53F7\uFF0C\u5176\u4E0B\u6761\u76EE\u5DF2\u88AB\u5FFD\u7565\uFF0C\u8BF7\u6539\u6210 ## [x.y.z] \u5F62\u6001",
|
|
464
|
+
E_CATEGORY: "\u672A\u77E5\u5206\u7C7B\uFF0C\u5176\u4E0B\u6761\u76EE\u5DF2\u88AB\u5FFD\u7565\uFF0C\u4EC5\u6536 Added/Fixed/Changed/Deprecated/Removed/Security",
|
|
465
|
+
E_BULLET_ORPHAN: "\u8BE5\u6761\u76EE\u4E0D\u5728\u7248\u672C\u8282\u4E0E\u5206\u7C7B\u4E0B\uFF0C\u5DF2\u88AB\u5FFD\u7565",
|
|
466
|
+
W_BREAKING_MAYBE: "\u7591\u4F3C\u7834\u574F\u6807\u8BB0\u8BEF\u5199\uFF08\u7F3A\u5192\u53F7/\u62FC\u5199\u63A5\u8FD1/\u4F4D\u7F6E\u7591\u4F3C\u653E\u9519\uFF09\uFF0C\u8BF7\u68C0\u67E5\u662F\u5426\u60F3\u5199 BREAKING:/\u4E0D\u517C\u5BB9:",
|
|
467
|
+
W_YANKED: "\u8BE5\u7248\u672C\u5DF2\u88AB\u6807\u8BB0\u64A4\u56DE\uFF08yanked\uFF09\uFF0C\u5B89\u88C5\u4E0D\u53D7\u5F71\u54CD\uFF0C\u7EE7\u7EED\u524D\u8BF7\u786E\u8BA4",
|
|
468
|
+
W_TRUNCATED: "\u8BE5\u7C7B\u5171 M \u6761\uFF0C\u4EC5\u663E\u793A\u524D N \u6761",
|
|
469
|
+
W_TRUNCATED_FILE: "\u6587\u4EF6\u8D85 64K\uFF0C\u4EC5\u6821\u9A8C\u524D 64K",
|
|
470
|
+
W_EMPTY: "\u672A\u89E3\u6790\u5230\u4EFB\u4F55\u7248\u672C\u8282\uFF0C\u9762\u677F\u5C06\u663E\u793A\u4E2D\u6027\u63D0\u793A"
|
|
471
|
+
};
|
|
472
|
+
function isMaybeBreakingMisuse(bulletBody) {
|
|
473
|
+
try {
|
|
474
|
+
if (typeof bulletBody !== "string") return false;
|
|
475
|
+
const t = stripBreakingDecorations(bulletBody);
|
|
476
|
+
if (!t) return false;
|
|
477
|
+
if (isBreakingChangelogItem(bulletBody)) return false;
|
|
478
|
+
if (/^breaking\s*$/i.test(t)) return true;
|
|
479
|
+
if (/^breaking\s+[^::]/i.test(t)) return true;
|
|
480
|
+
if (/^breaking[^::\s\w]/i.test(t)) return true;
|
|
481
|
+
if (/^不兼容\s*$/i.test(t)) return true;
|
|
482
|
+
if (/^不兼容\s*[^::]/i.test(t)) return true;
|
|
483
|
+
if (/^(braking|breking|breakign|brekaing|braeking|breakingg)\b/i.test(t)) return true;
|
|
484
|
+
if (/^break[^i::\s]/i.test(t)) return true;
|
|
485
|
+
return false;
|
|
486
|
+
} catch {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function validateChangelog(markdown) {
|
|
491
|
+
try {
|
|
492
|
+
if (typeof markdown !== "string" || !markdown.trim()) {
|
|
493
|
+
return { ok: true, diagnostics: [{ line: 1, code: "W_EMPTY", hint: CHANGELOG_VALIDATE_HINTS.W_EMPTY }] };
|
|
494
|
+
}
|
|
495
|
+
let raw = String(markdown).replace(/\r\n/g, String.fromCharCode(10));
|
|
496
|
+
let fileTruncated = false;
|
|
497
|
+
if (raw.length > CHANGELOG_MAX_CHARS) {
|
|
498
|
+
fileTruncated = true;
|
|
499
|
+
raw = raw.slice(0, CHANGELOG_MAX_CHARS);
|
|
500
|
+
}
|
|
501
|
+
const lines = raw.split(String.fromCharCode(10));
|
|
502
|
+
const diags = [];
|
|
503
|
+
let inFence = false;
|
|
504
|
+
let curValid = false;
|
|
505
|
+
let curCat = null;
|
|
506
|
+
let catLineByCat = {};
|
|
507
|
+
let countByCat = {};
|
|
508
|
+
let hasVisibleVersion = false;
|
|
509
|
+
const flushCounts = function() {
|
|
510
|
+
try {
|
|
511
|
+
for (const k of Object.keys(countByCat)) {
|
|
512
|
+
const n = countByCat[k];
|
|
513
|
+
if (n > CHANGELOG_MAX_BULLETS_PER_SECTION) {
|
|
514
|
+
const lineNo = catLineByCat[k] ? catLineByCat[k] : lines.length;
|
|
515
|
+
const hint = "\u5206\u7C7B " + k + " \u5171 " + String(n) + " \u6761\uFF0C\u4EC5\u663E\u793A\u524D " + String(CHANGELOG_MAX_BULLETS_PER_SECTION) + " \u6761";
|
|
516
|
+
diags.push({ line: lineNo, code: "W_TRUNCATED", hint });
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
} catch {
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
for (let i = 0; i < lines.length; i++) {
|
|
523
|
+
const lineNo = i + 1;
|
|
524
|
+
const line = String(lines[i] !== void 0 && lines[i] !== null ? lines[i] : "");
|
|
525
|
+
if (isFenceToggle(line)) {
|
|
526
|
+
inFence = !inFence;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (inFence) continue;
|
|
530
|
+
const vTitle = matchVersionHeading(line);
|
|
531
|
+
if (vTitle !== null) {
|
|
532
|
+
flushCounts();
|
|
533
|
+
catLineByCat = {};
|
|
534
|
+
countByCat = {};
|
|
535
|
+
curValid = false;
|
|
536
|
+
curCat = null;
|
|
537
|
+
const info = extractVersionDateAndYanked(vTitle);
|
|
538
|
+
if (!info.version) {
|
|
539
|
+
diags.push({ line: lineNo, code: "E_VERSION_TITLE", hint: CHANGELOG_VALIDATE_HINTS.E_VERSION_TITLE });
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (isUnreleasedVersion(info.version)) {
|
|
543
|
+
curValid = false;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
curValid = true;
|
|
547
|
+
hasVisibleVersion = true;
|
|
548
|
+
if (info.yanked) {
|
|
549
|
+
diags.push({ line: lineNo, code: "W_YANKED", hint: CHANGELOG_VALIDATE_HINTS.W_YANKED });
|
|
550
|
+
}
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
const cTitle = matchCategoryHeading(line);
|
|
554
|
+
if (cTitle !== null) {
|
|
555
|
+
const cat = normalizeCategory(cTitle);
|
|
556
|
+
if (!cat) {
|
|
557
|
+
curCat = null;
|
|
558
|
+
diags.push({ line: lineNo, code: "E_CATEGORY", hint: CHANGELOG_VALIDATE_HINTS.E_CATEGORY });
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (!curValid) {
|
|
562
|
+
curCat = null;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
curCat = cat;
|
|
566
|
+
catLineByCat[cat] = lineNo;
|
|
567
|
+
if (countByCat[cat] === void 0) countByCat[cat] = 0;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
const body = matchBulletBody(line);
|
|
571
|
+
if (body !== null) {
|
|
572
|
+
if (!curValid || !curCat) {
|
|
573
|
+
diags.push({ line: lineNo, code: "E_BULLET_ORPHAN", hint: CHANGELOG_VALIDATE_HINTS.E_BULLET_ORPHAN });
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
countByCat[curCat] = (countByCat[curCat] ? countByCat[curCat] : 0) + 1;
|
|
577
|
+
if (isMaybeBreakingMisuse(body)) {
|
|
578
|
+
diags.push({ line: lineNo, code: "W_BREAKING_MAYBE", hint: CHANGELOG_VALIDATE_HINTS.W_BREAKING_MAYBE });
|
|
579
|
+
}
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
flushCounts();
|
|
584
|
+
if (fileTruncated) {
|
|
585
|
+
diags.push({ line: lines.length, code: "W_TRUNCATED_FILE", hint: CHANGELOG_VALIDATE_HINTS.W_TRUNCATED_FILE });
|
|
586
|
+
}
|
|
587
|
+
if (!hasVisibleVersion) {
|
|
588
|
+
let anyVisible = false;
|
|
589
|
+
try {
|
|
590
|
+
const parsed = parseChangelog(markdown);
|
|
591
|
+
for (const e of parsed) {
|
|
592
|
+
if (e && !isUnreleasedVersion(e.version) && hasVisibleSections(e)) {
|
|
593
|
+
anyVisible = true;
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
} catch {
|
|
598
|
+
anyVisible = false;
|
|
599
|
+
}
|
|
600
|
+
if (!anyVisible) {
|
|
601
|
+
diags.push({ line: lines.length, code: "W_EMPTY", hint: CHANGELOG_VALIDATE_HINTS.W_EMPTY });
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
diags.sort(function(a, b) {
|
|
605
|
+
return a.line - b.line;
|
|
606
|
+
});
|
|
607
|
+
let ok = true;
|
|
608
|
+
for (const d of diags) {
|
|
609
|
+
if (d.code.charAt(0) === "E") {
|
|
610
|
+
ok = false;
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return { ok, diagnostics: diags };
|
|
615
|
+
} catch {
|
|
616
|
+
return { ok: false, diagnostics: [] };
|
|
617
|
+
}
|
|
618
|
+
}
|
|
267
619
|
export {
|
|
620
|
+
BREAKING_BADGE_TEXT,
|
|
268
621
|
CHANGELOG_ALL_CATEGORIES,
|
|
269
622
|
CHANGELOG_CATEGORY_ZH,
|
|
270
623
|
CHANGELOG_FILENAME,
|
|
@@ -276,12 +629,36 @@ export {
|
|
|
276
629
|
CHANGELOG_MUST_SHOW,
|
|
277
630
|
CHANGELOG_NEUTRAL_HINT,
|
|
278
631
|
CHANGELOG_NEUTRAL_LINE,
|
|
632
|
+
CHANGELOG_POLL_BACKOFF_MS,
|
|
633
|
+
CHANGELOG_VALIDATE_HINTS,
|
|
634
|
+
RE_BULLET,
|
|
635
|
+
RE_CATEGORY_HEADING,
|
|
636
|
+
RE_FENCE_TOGGLE,
|
|
637
|
+
RE_UNRELEASED,
|
|
638
|
+
RE_VERSION_DATE,
|
|
639
|
+
RE_VERSION_HEADING,
|
|
640
|
+
RE_VERSION_NUM,
|
|
641
|
+
RE_YANKED,
|
|
279
642
|
changelogForUpdate,
|
|
643
|
+
countsOf,
|
|
280
644
|
hasVisibleSections,
|
|
645
|
+
isBreakingChangelogItem,
|
|
646
|
+
isFenceToggle,
|
|
647
|
+
isMaybeBreakingMisuse,
|
|
281
648
|
isUnreleasedVersion,
|
|
649
|
+
matchBulletBody,
|
|
650
|
+
matchCategoryHeading,
|
|
651
|
+
matchVersionHeading,
|
|
282
652
|
parseChangelog,
|
|
283
653
|
renderChangelogHTML,
|
|
654
|
+
renderChangelogItem,
|
|
284
655
|
renderChangelogNeutral,
|
|
285
656
|
renderChangelogSection,
|
|
286
|
-
selectChangelogEntries
|
|
657
|
+
selectChangelogEntries,
|
|
658
|
+
shouldFetchChangelog,
|
|
659
|
+
splitBreakingPrefix,
|
|
660
|
+
stripBreakingDecorations,
|
|
661
|
+
truncatedNoteHTML,
|
|
662
|
+
validateChangelog,
|
|
663
|
+
yankedBannerHTML
|
|
287
664
|
};
|
package/dist/entry.d.ts
CHANGED
|
@@ -5,7 +5,11 @@ export type EntryVariant = 'button' | 'badge' | 'inline';
|
|
|
5
5
|
/** 什么时候主动查一次(只读,联网一次)。 */
|
|
6
6
|
export type EntryAutoCheck = 'mount' | 'never';
|
|
7
7
|
/** 用户点下去之后做什么。 */
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* 点下去之后做什么:`has-update` 有新版才开面板(无事只给一句小字);`always` 检查完总是开;
|
|
10
|
+
* `manual` 交给 onActivate;`direct` 点开即弹窗、不预查(面板挂载即自查;badge 仍走回调口径)。
|
|
11
|
+
*/
|
|
12
|
+
export type EntryOpenOn = 'has-update' | 'always' | 'manual' | 'direct';
|
|
9
13
|
/** 入口件主题(与面板同一套取值,切换即换肤)。 */
|
|
10
14
|
export type EntryTheme = UpdatePanelTheme;
|
|
11
15
|
/** 入口件的状态档:只用来算文案与 data-state,不另立状态机。 */
|
package/dist/entry.js
CHANGED
|
@@ -64,10 +64,15 @@ const UPDATE_ENTRY_CSS = [
|
|
|
64
64
|
'.dsh-upd-entry[data-state="update"] .dsh-upd-entry-dot{background:var(--dsh-upd-ok-line,#059669)}',
|
|
65
65
|
'.dsh-upd-entry[data-state="busy"] .dsh-upd-entry-dot,.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-dot{background:var(--dsh-upd-warn-line,#d97706)}',
|
|
66
66
|
'.dsh-upd-entry[data-state="failed"] .dsh-upd-entry-dot{background:var(--dsh-upd-bad-line,#dc2626)}',
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
'.dsh-upd-entry[data-theme="d5-paper"] .dsh-upd-entry-
|
|
70
|
-
'.dsh-upd-entry[data-theme="d5-paper"]
|
|
67
|
+
// 小字自带底(深色宿主 + 浅色变量时也读得出;浅底宿主上只是多一圈细线,不抢戏)。
|
|
68
|
+
".dsh-upd-entry-note{font-size:12.5px;opacity:.9;background:var(--dsh-upd-bg,#ffffff);border:1px solid var(--dsh-upd-line,#e5e7eb);border-radius:4px;padding:1px 8px}",
|
|
69
|
+
'.dsh-upd-entry[data-theme="d5-paper"] .dsh-upd-entry-note{background:var(--d5-card);border-color:var(--d5-line-strong);color:var(--d5-ink)}',
|
|
70
|
+
'.dsh-upd-entry[data-theme="d5-paper"]{--d5-ink:#1a1a1a;--d5-muted:#6f675a;--d5-line-strong:#c4b896;--d5-accent:#c8402a;--d5-card:#fffdf6;',
|
|
71
|
+
'font-family:Georgia,"Songti SC","STSong","SimSun",serif;color:var(--d5-ink)}',
|
|
72
|
+
// 按钮脸自己不透明(深色宿主 + 浅色系统变量时也读得出;hover 红在深浅底上都可见)。
|
|
73
|
+
'.dsh-upd-entry[data-theme="d5-paper"] .dsh-upd-entry-btn{border-color:var(--d5-line-strong);background:var(--d5-card);color:var(--d5-ink);border-radius:3px}',
|
|
74
|
+
'.dsh-upd-entry[data-theme="d5-paper"] .dsh-upd-entry-btn:hover{border-color:var(--d5-accent);color:var(--d5-accent)}',
|
|
75
|
+
'@media (prefers-color-scheme: dark){.dsh-upd-entry[data-theme="d5-paper"]{--d5-ink:#ece5d3;--d5-muted:#a89c83;--d5-line-strong:#5c4e3b;--d5-accent:#e0684e;--d5-card:#1e1a15}}',
|
|
71
76
|
"@media (prefers-color-scheme: dark){.dsh-upd-entry{color:#e5e7eb}",
|
|
72
77
|
".dsh-upd-entry-btn{--dsh-upd-btn:#1f2937;--dsh-upd-line:#374151}}"
|
|
73
78
|
].join("\n");
|
|
@@ -111,8 +116,8 @@ function mountUpdateEntry(container, options) {
|
|
|
111
116
|
throw new Error(`[dsh-plugin-update] \u81EA\u52A8\u68C0\u67E5\u65F6\u673A\u975E\u6CD5\uFF1A\u53EA\u6536 mount \u6216 never\uFF08\u6536\u5230 ${JSON.stringify(options.autoCheck)}\uFF09`);
|
|
112
117
|
}
|
|
113
118
|
const openOn = options.openOn ?? "has-update";
|
|
114
|
-
if (openOn !== "has-update" && openOn !== "always" && openOn !== "manual") {
|
|
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`);
|
|
119
|
+
if (openOn !== "has-update" && openOn !== "always" && openOn !== "manual" && openOn !== "direct") {
|
|
120
|
+
throw new Error(`[dsh-plugin-update] \u70B9\u51FB\u53BB\u5411\u975E\u6CD5\uFF1A\u53EA\u6536 has-update / always / manual / direct\uFF08\u6536\u5230 ${JSON.stringify(options.openOn)}\uFF09`);
|
|
116
121
|
}
|
|
117
122
|
if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive" && options.theme !== "d5-paper") {
|
|
118
123
|
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`);
|
|
@@ -248,6 +253,10 @@ function mountUpdateEntry(container, options) {
|
|
|
248
253
|
}
|
|
249
254
|
async function activate() {
|
|
250
255
|
if (!mounted || panelMode !== null || activating) return;
|
|
256
|
+
if (openOn === "direct" && variant === "button") {
|
|
257
|
+
openDialog();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
251
260
|
activating = true;
|
|
252
261
|
note = null;
|
|
253
262
|
render();
|
|
@@ -288,7 +297,6 @@ function mountUpdateEntry(container, options) {
|
|
|
288
297
|
void activate();
|
|
289
298
|
return;
|
|
290
299
|
}
|
|
291
|
-
if (panelMode === "dialog" && closest('[data-action="close-view"]')) close();
|
|
292
300
|
} catch {
|
|
293
301
|
}
|
|
294
302
|
}
|
package/dist/host.d.ts
CHANGED
|
@@ -10,8 +10,8 @@ export { COPY_BUDGET_CHARS, DETAIL_MAX_CHARS, DIAG_DROP_ORDER, DIAG_INLINE_BUDGE
|
|
|
10
10
|
export type { DiagDropKey, RedactionRuleName } from './redaction.js';
|
|
11
11
|
export { DIAG_ACTIONS, DIAG_KEYS, DIAG_KNOWN_TYPES, DIAG_STAGE_EXPECT, DIAG_STAGES, DIAG_VERSION, buildDiag, deriveAction, deriveRouteMethod, deriveStage, enforceBudget as enforceDiagBudgetForPhone, } from './diag.js';
|
|
12
12
|
export type { DiagAction, DiagInput, DiagObject, DiagStage } from './diag.js';
|
|
13
|
-
export { CHANGELOG_ALL_CATEGORIES, CHANGELOG_FILENAME, CHANGELOG_FOLDED, CHANGELOG_MAX_BULLETS_PER_SECTION, CHANGELOG_MAX_BULLET_CHARS, CHANGELOG_MAX_CHARS, CHANGELOG_MAX_ENTRIES, CHANGELOG_MUST_SHOW, CHANGELOG_NEUTRAL_HINT, CHANGELOG_NEUTRAL_LINE, changelogForUpdate, hasVisibleSections, isUnreleasedVersion, parseChangelog, renderChangelogHTML, renderChangelogNeutral, renderChangelogSection, selectChangelogEntries, } from './changelog.js';
|
|
14
|
-
export type { ChangelogCategory, ChangelogEntry } from './changelog.js';
|
|
13
|
+
export { CHANGELOG_ALL_CATEGORIES, CHANGELOG_FILENAME, CHANGELOG_FOLDED, CHANGELOG_MAX_BULLETS_PER_SECTION, CHANGELOG_MAX_BULLET_CHARS, CHANGELOG_MAX_CHARS, CHANGELOG_MAX_ENTRIES, CHANGELOG_MUST_SHOW, CHANGELOG_NEUTRAL_HINT, CHANGELOG_NEUTRAL_LINE, BREAKING_BADGE_TEXT, CHANGELOG_POLL_BACKOFF_MS, changelogForUpdate, countsOf, hasVisibleSections, isBreakingChangelogItem, isUnreleasedVersion, parseChangelog, renderChangelogHTML, renderChangelogItem, renderChangelogNeutral, renderChangelogSection, selectChangelogEntries, shouldFetchChangelog, splitBreakingPrefix, truncatedNoteHTML, validateChangelog, yankedBannerHTML, } from './changelog.js';
|
|
14
|
+
export type { ChangelogCategory, ChangelogDiagnostic, ChangelogEntry, ChangelogValidation } from './changelog.js';
|
|
15
15
|
export { CHANGELOG_DEFAULT_REGISTRY, CHANGELOG_FETCH_MAX_BYTES, CHANGELOG_TAR_MAX_BYTES, CHANGELOG_TARBALL_MAX_BYTES, extractChangelogFromTar, fetchReleaseChangelogText, readInstalledChangelogText, } from './changelog-io.js';
|
|
16
16
|
export type { ReleaseChangelogRef } from './changelog-io.js';
|
|
17
17
|
export { createUpdateDiskPorts, createUpdateExecutor, pathsForUpdate, resolveCliEntry } from './store.js';
|
package/dist/host.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "./config.js";
|
|
12
12
|
import { manualCommand } from "./commands.js";
|
|
13
13
|
import { fetchReleaseChangelogText } from "./changelog-io.js";
|
|
14
|
+
import { shouldFetchChangelog } from "./changelog.js";
|
|
14
15
|
import { buildDiag } from "./diag.js";
|
|
15
16
|
import { fetchNpmVersionRelease, isVersionAllowedInChannel, updateError, validReleaseVersion, validRequestId } from "./service.js";
|
|
16
17
|
import { createUpdateDiskPorts, createUpdateExecutor, createUpdateQueuePorts } from "./store.js";
|
|
@@ -73,14 +74,24 @@ import {
|
|
|
73
74
|
CHANGELOG_MUST_SHOW,
|
|
74
75
|
CHANGELOG_NEUTRAL_HINT,
|
|
75
76
|
CHANGELOG_NEUTRAL_LINE,
|
|
77
|
+
BREAKING_BADGE_TEXT,
|
|
78
|
+
CHANGELOG_POLL_BACKOFF_MS,
|
|
76
79
|
changelogForUpdate,
|
|
80
|
+
countsOf,
|
|
77
81
|
hasVisibleSections,
|
|
82
|
+
isBreakingChangelogItem,
|
|
78
83
|
isUnreleasedVersion,
|
|
79
84
|
parseChangelog,
|
|
80
85
|
renderChangelogHTML,
|
|
86
|
+
renderChangelogItem,
|
|
81
87
|
renderChangelogNeutral,
|
|
82
88
|
renderChangelogSection,
|
|
83
|
-
selectChangelogEntries
|
|
89
|
+
selectChangelogEntries,
|
|
90
|
+
shouldFetchChangelog as shouldFetchChangelog2,
|
|
91
|
+
splitBreakingPrefix,
|
|
92
|
+
truncatedNoteHTML,
|
|
93
|
+
validateChangelog,
|
|
94
|
+
yankedBannerHTML
|
|
84
95
|
} from "./changelog.js";
|
|
85
96
|
import {
|
|
86
97
|
CHANGELOG_DEFAULT_REGISTRY,
|
|
@@ -578,12 +589,24 @@ function createHostUpdate(deps = {}, configInput) {
|
|
|
578
589
|
const readerOverrides = { ...deps.readerOverrides ?? {}, ctx, ...deps.pluginManager ? { pluginManager: deps.pluginManager } : {} };
|
|
579
590
|
const phoneNames = buildPhoneNames(config.prefix);
|
|
580
591
|
const changelogCache = /* @__PURE__ */ new Map();
|
|
592
|
+
const changelogFailedAt = /* @__PURE__ */ new Map();
|
|
581
593
|
async function readChangelog(args) {
|
|
582
594
|
const raw = args ? args["version"] : void 0;
|
|
583
595
|
if (!validReleaseVersion(raw)) throw updateError("invalid-release");
|
|
584
596
|
const version = raw;
|
|
585
597
|
if (!isVersionAllowedInChannel(version, config.releaseChannel)) throw updateError("invalid-release");
|
|
586
598
|
if (changelogCache.has(version)) return { version, markdown: changelogCache.get(version) ?? null };
|
|
599
|
+
try {
|
|
600
|
+
let nowMs = 0;
|
|
601
|
+
try {
|
|
602
|
+
nowMs = Date.now();
|
|
603
|
+
} catch {
|
|
604
|
+
nowMs = 0;
|
|
605
|
+
}
|
|
606
|
+
const failedAt = changelogFailedAt.has(version) ? changelogFailedAt.get(version) : null;
|
|
607
|
+
void shouldFetchChangelog({ hasCache: false, failedAt, now: nowMs, isManual: true });
|
|
608
|
+
} catch {
|
|
609
|
+
}
|
|
587
610
|
const fetchImpl = readerOverrides.fetchImpl ?? globalThis.fetch;
|
|
588
611
|
const release = await fetchNpmVersionRelease(fetchImpl, config.checkTimeoutMs, {
|
|
589
612
|
targetPackageName: config.targetPackageName,
|
|
@@ -591,12 +614,29 @@ function createHostUpdate(deps = {}, configInput) {
|
|
|
591
614
|
releaseChannel: config.releaseChannel,
|
|
592
615
|
version
|
|
593
616
|
});
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
617
|
+
let markdown;
|
|
618
|
+
try {
|
|
619
|
+
markdown = await fetchReleaseChangelogText(
|
|
620
|
+
fetchImpl,
|
|
621
|
+
{ tarball: release.tarball, integrity: release.integrity, version },
|
|
622
|
+
{ targetPackageName: config.targetPackageName, registryUrl: config.registryUrl, timeoutMs: config.checkTimeoutMs }
|
|
623
|
+
);
|
|
624
|
+
} catch (err) {
|
|
625
|
+
try {
|
|
626
|
+
changelogFailedAt.set(version, Date.now());
|
|
627
|
+
} catch {
|
|
628
|
+
try {
|
|
629
|
+
changelogFailedAt.set(version, 0);
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
throw err;
|
|
634
|
+
}
|
|
599
635
|
changelogCache.set(version, markdown);
|
|
636
|
+
try {
|
|
637
|
+
changelogFailedAt.delete(version);
|
|
638
|
+
} catch {
|
|
639
|
+
}
|
|
600
640
|
return { version, markdown };
|
|
601
641
|
}
|
|
602
642
|
async function readStatus(args) {
|
|
@@ -713,6 +753,7 @@ function __resetSharedUpdateReaderForTests() {
|
|
|
713
753
|
sharedDesktopPnpm = null;
|
|
714
754
|
}
|
|
715
755
|
export {
|
|
756
|
+
BREAKING_BADGE_TEXT,
|
|
716
757
|
CHANGELOG_ALL_CATEGORIES,
|
|
717
758
|
CHANGELOG_DEFAULT_REGISTRY,
|
|
718
759
|
CHANGELOG_FETCH_MAX_BYTES,
|
|
@@ -725,6 +766,7 @@ export {
|
|
|
725
766
|
CHANGELOG_MUST_SHOW,
|
|
726
767
|
CHANGELOG_NEUTRAL_HINT,
|
|
727
768
|
CHANGELOG_NEUTRAL_LINE,
|
|
769
|
+
CHANGELOG_POLL_BACKOFF_MS,
|
|
728
770
|
CHANGELOG_TARBALL_MAX_BYTES,
|
|
729
771
|
CHANGELOG_TAR_MAX_BYTES,
|
|
730
772
|
COPY_BUDGET_CHARS,
|
|
@@ -764,6 +806,7 @@ export {
|
|
|
764
806
|
compareReleaseVersions,
|
|
765
807
|
compareVersions,
|
|
766
808
|
containingPackage,
|
|
809
|
+
countsOf,
|
|
767
810
|
createHostUpdate,
|
|
768
811
|
createSkipDiskPorts,
|
|
769
812
|
createUpdateDiskPorts2 as createUpdateDiskPorts,
|
|
@@ -782,6 +825,7 @@ export {
|
|
|
782
825
|
extractChangelogFromTar,
|
|
783
826
|
fetchReleaseChangelogText2 as fetchReleaseChangelogText,
|
|
784
827
|
hasVisibleSections,
|
|
828
|
+
isBreakingChangelogItem,
|
|
785
829
|
isHeadOfQueue2 as isHeadOfQueue,
|
|
786
830
|
isPrereleaseVersion,
|
|
787
831
|
isUnreleasedVersion,
|
|
@@ -800,6 +844,7 @@ export {
|
|
|
800
844
|
registrySpec,
|
|
801
845
|
releaseOwnerInQueue2 as releaseOwnerInQueue,
|
|
802
846
|
renderChangelogHTML,
|
|
847
|
+
renderChangelogItem,
|
|
803
848
|
renderChangelogNeutral,
|
|
804
849
|
renderChangelogSection,
|
|
805
850
|
resolveCliEntry,
|
|
@@ -810,9 +855,14 @@ export {
|
|
|
810
855
|
sanitizeForCopy,
|
|
811
856
|
selectChangelogEntries,
|
|
812
857
|
setOwnerIfFree2 as setOwnerIfFree,
|
|
858
|
+
shouldFetchChangelog2 as shouldFetchChangelog,
|
|
813
859
|
skipKey,
|
|
860
|
+
splitBreakingPrefix,
|
|
814
861
|
truncateToWordBoundary,
|
|
862
|
+
truncatedNoteHTML,
|
|
815
863
|
validReleaseVersion2 as validReleaseVersion,
|
|
816
864
|
validVersion,
|
|
817
|
-
|
|
865
|
+
validateChangelog,
|
|
866
|
+
visibleQueueFor2 as visibleQueueFor,
|
|
867
|
+
yankedBannerHTML
|
|
818
868
|
};
|
package/dist/panel-batch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/panel-batch.ts
|
|
2
2
|
import { assertPrefix, MIN_PANEL_POLL_MS } from "./config.js";
|
|
3
3
|
import { validReleaseVersion } from "./service.js";
|
|
4
|
+
import { shouldFetchChangelog } from "./changelog.js";
|
|
4
5
|
import {
|
|
5
6
|
isTerminalPhase,
|
|
6
7
|
normalizeBatchSession
|
|
@@ -631,7 +632,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
631
632
|
const autoChangelogEnabled = options.autoChangelog !== false;
|
|
632
633
|
const rowChangelog = /* @__PURE__ */ new Map();
|
|
633
634
|
const rowInflight = /* @__PURE__ */ new Set();
|
|
634
|
-
const
|
|
635
|
+
const rowFailedAt = /* @__PURE__ */ new Map();
|
|
635
636
|
let rowAutoSeq = 0;
|
|
636
637
|
let rows = [];
|
|
637
638
|
let expandedKey = null;
|
|
@@ -672,7 +673,19 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
672
673
|
const cached = rowChangelog.get(key);
|
|
673
674
|
if (cached && cached.version === version) return;
|
|
674
675
|
if (rowInflight.has(key)) return;
|
|
675
|
-
|
|
676
|
+
try {
|
|
677
|
+
const failKey = key + "\0" + version;
|
|
678
|
+
const hasCache = false;
|
|
679
|
+
const failedAt = rowFailedAt.has(failKey) ? rowFailedAt.get(failKey) : null;
|
|
680
|
+
let nowMs = 0;
|
|
681
|
+
try {
|
|
682
|
+
nowMs = Date.now();
|
|
683
|
+
} catch {
|
|
684
|
+
nowMs = 0;
|
|
685
|
+
}
|
|
686
|
+
if (!shouldFetchChangelog({ hasCache, failedAt, now: nowMs, isManual: false })) return;
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
676
689
|
const phone2 = changelogPhoneOf(row);
|
|
677
690
|
if (!phone2) return;
|
|
678
691
|
rowInflight.add(key);
|
|
@@ -682,7 +695,14 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
682
695
|
rowInflight.delete(key);
|
|
683
696
|
if (!mounted || seq !== rowAutoSeq) return;
|
|
684
697
|
if (!isObject(reply) || reply["ok"] !== true) {
|
|
685
|
-
|
|
698
|
+
try {
|
|
699
|
+
rowFailedAt.set(key + "\0" + version, Date.now());
|
|
700
|
+
} catch {
|
|
701
|
+
try {
|
|
702
|
+
rowFailedAt.set(key + "\0" + version, 0);
|
|
703
|
+
} catch {
|
|
704
|
+
}
|
|
705
|
+
}
|
|
686
706
|
return;
|
|
687
707
|
}
|
|
688
708
|
const md = reply["markdown"];
|
|
@@ -691,7 +711,16 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
691
711
|
},
|
|
692
712
|
() => {
|
|
693
713
|
rowInflight.delete(key);
|
|
694
|
-
if (mounted && seq === rowAutoSeq)
|
|
714
|
+
if (mounted && seq === rowAutoSeq) {
|
|
715
|
+
try {
|
|
716
|
+
rowFailedAt.set(key + "\0" + version, Date.now());
|
|
717
|
+
} catch {
|
|
718
|
+
try {
|
|
719
|
+
rowFailedAt.set(key + "\0" + version, 0);
|
|
720
|
+
} catch {
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
695
724
|
}
|
|
696
725
|
);
|
|
697
726
|
}
|
|
@@ -808,7 +837,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
808
837
|
return;
|
|
809
838
|
}
|
|
810
839
|
applyReply(reply);
|
|
811
|
-
|
|
840
|
+
rowFailedAt.clear();
|
|
812
841
|
} catch {
|
|
813
842
|
if (!mounted) return;
|
|
814
843
|
lastError = "check-failed";
|
package/dist/panel.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type VisibleQueue } from './queue.js';
|
|
|
2
2
|
import type { BlockedReason, UpdateSnapshot } from './ports.js';
|
|
3
3
|
/** 摆放形态:默认内嵌,切弹窗走同一参数。 */
|
|
4
4
|
export type UpdatePanelMode = 'embedded' | 'dialog';
|
|
5
|
-
/** 面板主题:默认最小可用样式;`d5-paper`
|
|
5
|
+
/** 面板主题:默认最小可用样式;`archive` 为档案卷纸面浅色(只换肤,不换 DOM 顺序;`d5-paper` 为旧别名)。 */
|
|
6
6
|
/**
|
|
7
7
|
* 面板主题:`default` 最小可用深色;`archive` 档案卷纸面浅色(原型敲定的案卷风格,只换肤)。
|
|
8
8
|
* `d5-paper` 是 archive 的旧别名(历史取值),仍可用,渲染逐字相同。
|
|
@@ -26,7 +26,7 @@ export interface UpdatePanelOptions {
|
|
|
26
26
|
prefix?: string;
|
|
27
27
|
/** 摆放形态:默认内嵌。 */
|
|
28
28
|
mode?: UpdatePanelMode;
|
|
29
|
-
/** 面板主题:默认 `default`(最小可用样式,一字不动);传 `d5-paper`
|
|
29
|
+
/** 面板主题:默认 `default`(最小可用样式,一字不动);传 `archive` 切档案卷(`d5-paper` 为旧别名)。 */
|
|
30
30
|
theme?: UpdatePanelTheme;
|
|
31
31
|
/** 是否看他人排队明细:默认只看自己的(他人仅露正忙占位,位置照给)。 */
|
|
32
32
|
showOthers?: boolean;
|
package/dist/panel.js
CHANGED
|
@@ -5,7 +5,9 @@ import { validReleaseVersion } from "./service.js";
|
|
|
5
5
|
import {
|
|
6
6
|
changelogForUpdate,
|
|
7
7
|
parseChangelog,
|
|
8
|
-
renderChangelogHTML
|
|
8
|
+
renderChangelogHTML,
|
|
9
|
+
shouldFetchChangelog,
|
|
10
|
+
yankedBannerHTML
|
|
9
11
|
} from "./changelog.js";
|
|
10
12
|
function normalizePanelTheme(value) {
|
|
11
13
|
return value === "d5-paper" || value === "archive" ? "d5-paper" : "default";
|
|
@@ -516,6 +518,12 @@ const UPDATE_PANEL_CSS = [
|
|
|
516
518
|
".dsh-upd-changelog li{margin:2px 0}",
|
|
517
519
|
".dsh-upd-changelog-fold{margin:4px 0}",
|
|
518
520
|
".dsh-upd-changelog-fold>summary{cursor:pointer}",
|
|
521
|
+
".dsh-upd-changelog-count{font-size:12px;opacity:.7;margin:2px 0 4px}",
|
|
522
|
+
".dsh-upd-changelog-yanked{border-left:4px solid var(--dsh-upd-warn-line,#d97706);background:var(--dsh-upd-warn-bg,#fffbeb);padding:6px 10px;margin:6px 0;font-size:13px}",
|
|
523
|
+
".dsh-upd-breaking-badge{display:inline-block;font-size:11px;font-weight:700;border:1px solid currentColor;border-radius:3px;padding:0 5px;margin-right:6px;vertical-align:baseline}",
|
|
524
|
+
".dsh-upd-changelog-security-more{margin:4px 0 6px}",
|
|
525
|
+
".dsh-upd-changelog-security-more>summary{cursor:pointer;font-size:12px;opacity:.8}",
|
|
526
|
+
".dsh-upd-changelog-more-note{font-size:12px;opacity:.7;margin:2px 0 4px}",
|
|
519
527
|
".dsh-upd-changelog-neutral{color:inherit;opacity:.8}",
|
|
520
528
|
".dsh-upd-overlay{position:fixed;inset:0;background:rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;z-index:9999}",
|
|
521
529
|
".dsh-upd-overlay .dsh-upd{background:var(--dsh-upd-bg,#ffffff);max-height:85vh;overflow:auto}",
|
|
@@ -593,7 +601,8 @@ const UPDATE_PANEL_D5_CSS = [
|
|
|
593
601
|
"--d5-shadow:0 1px 2px rgba(60,40,20,.08),0 12px 32px rgba(60,40,20,.10);",
|
|
594
602
|
"font-family:var(--d5-sans);color:var(--d5-ink);background:var(--d5-card);",
|
|
595
603
|
"border:1px solid var(--d5-line-strong);border-radius:4px;box-shadow:var(--d5-shadow)}",
|
|
596
|
-
|
|
604
|
+
// 按钮脸自己不透明(宿主底色未知时也读得出;卡片上渲染与 transparent 逐字同色)。
|
|
605
|
+
'.dsh-upd[data-theme="d5-paper"] button{border-color:var(--d5-line-strong);background:var(--d5-card);color:var(--d5-ink);border-radius:3px;font-family:var(--d5-sans)}',
|
|
597
606
|
'.dsh-upd[data-theme="d5-paper"] button:hover:not(:disabled){border-color:var(--d5-accent);color:var(--d5-accent)}',
|
|
598
607
|
'.dsh-upd[data-theme="d5-paper"] button[data-primary="1"]{background:var(--d5-accent);border-color:var(--d5-accent);color:#fff}',
|
|
599
608
|
'.dsh-upd[data-theme="d5-paper"] button[data-primary="1"]:hover:not(:disabled){background:var(--d5-accent-deep);color:#fff}',
|
|
@@ -604,6 +613,10 @@ const UPDATE_PANEL_D5_CSS = [
|
|
|
604
613
|
'.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="restart"]{border-color:var(--d5-warn);background:var(--d5-warn-bg);font-family:var(--d5-serif);border-width:2px}',
|
|
605
614
|
'.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="failed"],.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="blocked"]{border-color:var(--d5-bad);background:var(--d5-bad-bg)}',
|
|
606
615
|
'.dsh-upd[data-theme="d5-paper"] .dsh-upd-banner[data-kind="done"]{border-color:var(--d5-ok);background:var(--d5-ok-bg)}',
|
|
616
|
+
'.dsh-upd[data-theme="d5-paper"] .dsh-upd-changelog-yanked{border-color:var(--d5-warn);background:var(--d5-warn-bg);color:var(--d5-ink)}',
|
|
617
|
+
'.dsh-upd[data-theme="d5-paper"] .dsh-upd-breaking-badge{color:var(--d5-accent);border-color:var(--d5-accent)}',
|
|
618
|
+
'.dsh-upd[data-theme="d5-paper"] .dsh-upd-changelog-count{color:var(--d5-muted)}',
|
|
619
|
+
'.dsh-upd[data-theme="d5-paper"] .dsh-upd-changelog-more-note{color:var(--d5-muted)}',
|
|
607
620
|
// —— 大印章(原型 :206 `.seal`:右上 88px、旋转 -7°、双细框;内容与色调来自根属性,不加节点)——
|
|
608
621
|
'.dsh-upd[data-theme="d5-paper"]{position:relative;padding:22px 26px 20px}',
|
|
609
622
|
'.dsh-upd[data-theme="d5-paper"]::before{content:attr(data-seal);position:absolute;top:20px;right:24px;width:88px;height:88px;',
|
|
@@ -791,7 +804,22 @@ function renderUpdatePanelKernel(input, view) {
|
|
|
791
804
|
const fromText = String(snapshot.runningVersion ?? "");
|
|
792
805
|
const toText = String(snapshot.latestVersion ?? "");
|
|
793
806
|
const rangeTitle = fromText && toText ? `\u66F4\u65B0\u8BF4\u660E\uFF08${fromText} \u2192 ${toText}\uFF09\uFF1A` : "\u66F4\u65B0\u8BF4\u660E\uFF1A";
|
|
794
|
-
|
|
807
|
+
let yankedBanner = "";
|
|
808
|
+
try {
|
|
809
|
+
const toEntry = Array.isArray(ranged) ? ranged.find(function(e) {
|
|
810
|
+
try {
|
|
811
|
+
return e && e.version === toText;
|
|
812
|
+
} catch {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
}) : null;
|
|
816
|
+
if (toEntry && toEntry.yanked === true && toText) {
|
|
817
|
+
yankedBanner = yankedBannerHTML(toText);
|
|
818
|
+
}
|
|
819
|
+
} catch {
|
|
820
|
+
yankedBanner = "";
|
|
821
|
+
}
|
|
822
|
+
inner = `<div class="dsh-upd-changelog-wrap"><div>${escapeHtml(rangeTitle)}</div>${yankedBanner}
|
|
795
823
|
${changelogHTML}
|
|
796
824
|
</div>`;
|
|
797
825
|
} catch {
|
|
@@ -951,7 +979,7 @@ function mountUpdatePanel(container, options) {
|
|
|
951
979
|
let manualChangelogOverride = typeof options.changelogMarkdown === "string";
|
|
952
980
|
const changelogCache = /* @__PURE__ */ new Map();
|
|
953
981
|
const changelogInflight = /* @__PURE__ */ new Set();
|
|
954
|
-
const
|
|
982
|
+
const changelogFailedAt = /* @__PURE__ */ new Map();
|
|
955
983
|
let autoSeq = 0;
|
|
956
984
|
const pollMs = options.pollMs === void 0 ? DEFAULT_PANEL_POLL_MS : options.pollMs;
|
|
957
985
|
if (typeof pollMs !== "number" || !Number.isFinite(pollMs) || pollMs < MIN_PANEL_POLL_MS) {
|
|
@@ -1069,7 +1097,16 @@ function mountUpdatePanel(container, options) {
|
|
|
1069
1097
|
if (skipStore.has(v)) return null;
|
|
1070
1098
|
} catch {
|
|
1071
1099
|
}
|
|
1072
|
-
if (
|
|
1100
|
+
if (changelogInflight.has(v)) return null;
|
|
1101
|
+
const hasCache = changelogCache.has(v);
|
|
1102
|
+
const failedAt = changelogFailedAt.has(v) ? changelogFailedAt.get(v) : null;
|
|
1103
|
+
let nowMs = 0;
|
|
1104
|
+
try {
|
|
1105
|
+
nowMs = Date.now();
|
|
1106
|
+
} catch {
|
|
1107
|
+
nowMs = 0;
|
|
1108
|
+
}
|
|
1109
|
+
if (!shouldFetchChangelog({ hasCache, failedAt, now: nowMs, isManual: false })) return null;
|
|
1073
1110
|
return v;
|
|
1074
1111
|
}
|
|
1075
1112
|
function maybeAutoChangelog() {
|
|
@@ -1082,7 +1119,14 @@ function mountUpdatePanel(container, options) {
|
|
|
1082
1119
|
changelogInflight.delete(v);
|
|
1083
1120
|
if (!mounted || seq !== autoSeq) return;
|
|
1084
1121
|
if (!reply || typeof reply !== "object" || reply["ok"] !== true) {
|
|
1085
|
-
|
|
1122
|
+
try {
|
|
1123
|
+
changelogFailedAt.set(v, Date.now());
|
|
1124
|
+
} catch {
|
|
1125
|
+
try {
|
|
1126
|
+
changelogFailedAt.set(v, 0);
|
|
1127
|
+
} catch {
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1086
1130
|
return;
|
|
1087
1131
|
}
|
|
1088
1132
|
const md = reply["markdown"];
|
|
@@ -1094,7 +1138,16 @@ function mountUpdatePanel(container, options) {
|
|
|
1094
1138
|
},
|
|
1095
1139
|
() => {
|
|
1096
1140
|
changelogInflight.delete(v);
|
|
1097
|
-
if (mounted && seq === autoSeq)
|
|
1141
|
+
if (mounted && seq === autoSeq) {
|
|
1142
|
+
try {
|
|
1143
|
+
changelogFailedAt.set(v, Date.now());
|
|
1144
|
+
} catch {
|
|
1145
|
+
try {
|
|
1146
|
+
changelogFailedAt.set(v, 0);
|
|
1147
|
+
} catch {
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1098
1151
|
}
|
|
1099
1152
|
);
|
|
1100
1153
|
}
|
|
@@ -1126,7 +1179,7 @@ function mountUpdatePanel(container, options) {
|
|
|
1126
1179
|
const reply = await call(phoneNames.updateCheck, queueArgs());
|
|
1127
1180
|
if (!mounted) return;
|
|
1128
1181
|
applyStatusReply(reply);
|
|
1129
|
-
|
|
1182
|
+
changelogFailedAt.clear();
|
|
1130
1183
|
} catch {
|
|
1131
1184
|
if (!mounted) return;
|
|
1132
1185
|
lastError = "check-failed";
|