dsh-app-updater 1.3.14 → 1.3.15
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 +65 -0
- package/CONTRACT.md +26 -7
- package/README.en.md +23 -6
- package/README.md +40 -14
- package/RELEASING.md +78 -2
- package/lib/appcast.js +8 -2
- package/lib/client.js +165 -31
- package/lib/elevate.js +4 -1
- package/lib/engine.js +356 -86
- package/lib/index.js +119 -28
- package/lib/macos.js +111 -22
- package/lib/platform.js +10 -0
- package/lib/proc.js +25 -10
- package/lib/registry.js +10 -6
- package/lib/state.js +39 -7
- package/lib/version.js +18 -9
- package/package.json +5 -5
- package/test/deny.mjs +18 -0
- package/test/harness.mjs +12 -2
- package/test/render.mjs +26 -2
- package/test/unit.mjs +129 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [1.3.15] — 2026-11-04
|
|
6
|
+
|
|
7
|
+
### 安全:写操作默认拒绝再加固(`csrfToken` + `method`)
|
|
8
|
+
|
|
9
|
+
- 宿主半身对 5 个写路由**显式校验 `method === "POST"`**(不匹配返回 `405`),不再依赖
|
|
10
|
+
宿主 `webServer.register` 的分发实现——原来「GET 带 body」也能触发扫描/更新/改设置。
|
|
11
|
+
- 新增进程内 `csrfToken`(由 `GET /state.json` 下发、**不落盘**):所有写操作在
|
|
12
|
+
`confirm:true` 之外必须原样带回,否则 `403`。兼容模式下无 Cookie 的跨站/脚本请求
|
|
13
|
+
拿不到它,无法再驱动「下载官网包 + UAC 提权静默安装」。
|
|
14
|
+
- `admissionGate` 的 `500` 不再把宿主内部错误文案回给客户端;`readJsonBody` 改用
|
|
15
|
+
Buffer 累积(防中文跨 chunk 变 `U+FFFD`)、按字节计大小、超大请求体排空、只接受对象。
|
|
16
|
+
- `/cancel` 支持 `target: "scan" | "update" | "all"`(默认 `all` 兼容旧行为),界面在
|
|
17
|
+
扫描中也能取消,不再「一按把扫描和更新一起停」。
|
|
18
|
+
|
|
19
|
+
### 可靠性:下载并发化、结果可信度、生命周期
|
|
20
|
+
|
|
21
|
+
- `winget download` 改为 **3 路并发预下载**,每次下载用独立私有 `workDir`,彻底消除
|
|
22
|
+
「共享下载目录前后 diff」的并发竞态;安装/兜底/复核仍严格串行(UAC 互斥)。
|
|
23
|
+
- `moveIntoDownloadDir` 回迁处理 `EXDEV` 跨卷,并写 `.dsh-app-updater` **侧车标记**;
|
|
24
|
+
4 GiB 磁盘配额 `pruneDownloadDir` **只删带标记的本插件文件**,绝不误删用户放在同一
|
|
25
|
+
下载目录里的 `.exe` / `.dmg` / `.yaml`。
|
|
26
|
+
- 安装后复核改为「首次立即 + 递增退避(`1.5^i`,cap 30s)+ 总时限」;网络抖动下载
|
|
27
|
+
最多重试 2 次(1s / 3s,仅超时/网络类错误);`dryRun` 不再被 `keepInstallers=false` 删包。
|
|
28
|
+
- `unverifiable`(读不到已装版本)新增明确字段与「已安装·未复核」状态;Windows 补
|
|
29
|
+
`manual` 统计;结果面板渲染 `installOk / verified / hash / durationMs / selfUpdater /
|
|
30
|
+
dryRun / unverifiable`。
|
|
31
|
+
- cordis 卸载时 `abort()` 后台 scan / update 并清掉「更新后 4 秒自动重扫」timer;
|
|
32
|
+
`startUpdate` 补任务所有权校验(与 `startScan` 对齐)。
|
|
33
|
+
- `state.json` 损坏时先备份为 `state.json.corrupt-*` 再回退;`save()` 失败记
|
|
34
|
+
`lastSaveError` 而非静默吞掉;`load()` / `openFolder` / `route dispose` 失败全部留痕。
|
|
35
|
+
- 下发 `state.json` 前再裁一次平台专属设置(堵住运行期外部写入)。`/settings` 的
|
|
36
|
+
`downloadDir` 现在接受 macOS 以 `/` 开头的 POSIX 绝对路径。
|
|
37
|
+
|
|
38
|
+
### 正确性:版本算法与子进程
|
|
39
|
+
|
|
40
|
+
- `version.js`:`+build` 按 SemVer 视为元数据(与正式版相等)、尾缀大小写不敏感、
|
|
41
|
+
`normalizeVersionKey` 加分隔符消除 `1.2-3` vs `1.23` 碰撞、模块级 `/g` 正则改用
|
|
42
|
+
`matchAll`、`v` 前缀只剥「v 紧跟数字」。
|
|
43
|
+
- `proc.js` / `registry.js`:`StringDecoder` 逐块解码(中文跨 chunk 不再变 `U+FFFD`);
|
|
44
|
+
`proc.js` 在 POSIX 上 `detached` + 按进程组 kill(不留孤儿安装器)。
|
|
45
|
+
- 扫描侧与复核侧版本归一统一到 `macos.js` 的 `mainVersionOf`,但 `splitCaskVersion`
|
|
46
|
+
保留原始串(不砍 `_` / `+`),避免破坏 1.3.13 的构建号识别。
|
|
47
|
+
- macOS `reason` 细分 `not-in-catalog` / `unknown-version`(可一键更新集合逐位不变);
|
|
48
|
+
cask 缓存带 `source` / `count` 与 `readCatalogCacheMeta`,stale 告警写明来源与缓存天数。
|
|
49
|
+
|
|
50
|
+
### 界面与文档
|
|
51
|
+
|
|
52
|
+
- 浏览器半身:`csrfToken` 统一注入、首帧不再闪 Windows 专属设置、结果字段补全、
|
|
53
|
+
`source` 徽章白名单、日志稳定排序、取消扫描、`doOpenDownloads` 回读 state。
|
|
54
|
+
- `CONTRACT.md` / `README.md` / `README.en.md` / `RELEASING.md` 对齐代码:轮询
|
|
55
|
+
`900/5000ms`(原写 `800/4s`)、6 列 + 徽章、`/cancel` 的 `target` 与 1.3.5 杀树实现、
|
|
56
|
+
`csrfToken` / `saveError`、settings 全集、测试数改为「以 `npm test` 输出为准」、
|
|
57
|
+
发版脚本加资产名断言。
|
|
58
|
+
|
|
59
|
+
### 修复引入的回归(本轮自查发现并修掉)
|
|
60
|
+
|
|
61
|
+
- `splitCaskVersion` 一度误用 `mainVersionOf` 砍掉下划线构建段 → 恢复 + 加回归断言。
|
|
62
|
+
- `pruneDownloadDir` 一度按扩展名清理(会误删用户文件)→ 改侧车标记 + 加断言锁住。
|
|
63
|
+
|
|
64
|
+
### 测试
|
|
65
|
+
|
|
66
|
+
`unit 363 / render 87 / deny 16`(本轮基线;发版前以 `npm test` 实际输出为准)。
|
|
67
|
+
16 个 `lib/*.js` + `test/*.mjs` 全部 `node --check` 通过,`test/harness.mjs` 真实 HTTP
|
|
68
|
+
端到端通过,6 项 HTTP 探针(CSRF / 405 / macOS 路径 / cancel target / 相对路径拒绝)全通过。
|
|
69
|
+
|
|
5
70
|
## [1.3.14] — 2026-11-03
|
|
6
71
|
|
|
7
72
|
### 新增:更新前探测下载源,不再陪跑两小时
|
package/CONTRACT.md
CHANGED
|
@@ -15,6 +15,10 @@ dsh-app-updater/
|
|
|
15
15
|
│ ├─ winget.js # winget 定位、调用、表格解析
|
|
16
16
|
│ ├─ registry.js # 注册表 Uninstall 键枚举(覆盖 winget 未收录的应用)
|
|
17
17
|
│ ├─ version.js # 版本比较(Windows 风格 1.2.3.4 + 字母后缀)
|
|
18
|
+
│ ├─ elevate.js # Windows UAC 提权执行(临时文件 IPC + 进程树取消)
|
|
19
|
+
│ ├─ proc.js # 子进程底座(UTF-8 / 超时 / AbortSignal / shell:false)
|
|
20
|
+
│ ├─ macos.js # macOS 数据层:.app 枚举 / cask 目录竞速 / 列表组装
|
|
21
|
+
│ ├─ appcast.js # 非 Homebrew 应用官网更新源(Sparkle / Electron,只读)
|
|
18
22
|
│ ├─ state.js # 持久化状态(~/.dsh/dsh-app-updater/state.json)
|
|
19
23
|
│ └─ client.js # 浏览器半身:设置页 UI
|
|
20
24
|
└─ README.md
|
|
@@ -66,7 +70,9 @@ dsh-app-updater/
|
|
|
66
70
|
"update": { "running": false, "total": 0, "done": 0, "current": null, "error": null },
|
|
67
71
|
"apps": { "scannedAt": 0, "stats": { "total": 0, "upgradable": 0, "unsupported": 0 }, "items": [], "engine": "winget" },
|
|
68
72
|
"log": [ { "at": 0, "level": "info|warn|error|success", "text": "..." } ],
|
|
69
|
-
"settings": { "includeStoreApps": true, "includeRegistryOnly": true, "downloadDir": "", "keepInstallers": true, "hashFallbackUpgrade": true, "dryRun": false, "matchSystemArch": true, "autoElevate": true },
|
|
73
|
+
"settings": { "includeStoreApps": true, "includeRegistryOnly": true, "includeUnmanagedApps": true, "checkOfficialSources": true, "downloadDir": "", "keepInstallers": true, "useWingetDownload": true, "hashFallbackUpgrade": true, "dryRun": false, "matchSystemArch": true, "autoElevate": true, "useSelfUpdater": true },
|
|
74
|
+
// 写接口必须带回的 CSRF token(进程内生成、不落盘);saveError 为最近一次写盘失败原因
|
|
75
|
+
"csrfToken": "<hex>", "saveError": null,
|
|
70
76
|
// 准入现状,供界面与复核确认当前走哪条路径(默认 "deny" 不会出现在能读到响应的地方)
|
|
71
77
|
"admission": { "mode": "connection|connection+loopback-legacy|loopback-legacy-optin|deny", "service": true, "allowLegacyLoopback": false, "warning": null }
|
|
72
78
|
}
|
|
@@ -105,7 +111,8 @@ body `{ "confirm": true, "force": true }` → `{ "ok": true, "started": true }`
|
|
|
105
111
|
|
|
106
112
|
### POST /dsh-app-updater/update
|
|
107
113
|
|
|
108
|
-
body `{ "confirm": true, "ids": ["Google.Chrome", "..."]
|
|
114
|
+
body `{ "confirm": true, "csrfToken": "<来自 state.json>", "ids": ["Google.Chrome", "..."] }`
|
|
115
|
+
(`mode` 为历史残留字段,宿主不读取;所有写接口一律额外要求 `csrfToken`,见「写操作闸门」。)
|
|
109
116
|
→ `{ "ok": true, "started": true, "queued": 3 }`
|
|
110
117
|
(串行执行,逐条:下载官网全量包 → 校验哈希 → 静默安装(需要管理员时自动提权)→ 复核版本。
|
|
111
118
|
Edge 由 `MicrosoftEdgeUpdate.exe /ua` 处理。`settings.dryRun` 时只下载、不安装、不复核。)
|
|
@@ -129,11 +136,18 @@ Edge 由 `MicrosoftEdgeUpdate.exe /ua` 处理。`settings.dryRun` 时只下载
|
|
|
129
136
|
|
|
130
137
|
### POST /dsh-app-updater/cancel
|
|
131
138
|
|
|
132
|
-
|
|
139
|
+
body `{ "confirm": true, "csrfToken": "<...>", "target": "scan|update|all" }`(`target` 默认 `"all"`,兼容旧行为)
|
|
140
|
+
→ `{ "ok": true, "cancelled": true, "target": "all" }`
|
|
141
|
+
|
|
142
|
+
> 旧文档写「提权副本是独立进程,取消只会停掉启动器,已经开始的安装会自己跑完」——
|
|
143
|
+
> 这是 1.3.5 之前的行为。当前 `lib/elevate.js` 用 `cancel.flag` + 助手脚本 `taskkill /T /F`
|
|
144
|
+
> **杀掉整棵提权进程树**;`lib/proc.js` 在 POSIX 上按进程组 kill。取消仍只能停掉「尚未完成」
|
|
145
|
+
> 的那一次,已经提交给安装器的动作无法回滚。
|
|
133
146
|
|
|
134
147
|
### POST /dsh-app-updater/settings
|
|
135
148
|
|
|
136
|
-
body `{ "confirm": true, "settings": { ... } }` → `{ "ok": true, "settings": {...} }`
|
|
149
|
+
body `{ "confirm": true, "csrfToken": "<...>", "settings": { ... } }` → `{ "ok": true, "settings": {...} }`
|
|
150
|
+
(`downloadDir` 接受 Windows 盘符 / UNC **或 macOS 以 `/` 开头的 POSIX 绝对路径**。)
|
|
137
151
|
|
|
138
152
|
### POST /dsh-app-updater/open-downloads
|
|
139
153
|
|
|
@@ -145,6 +159,11 @@ body `{ "confirm": true, "settings": { ... } }` → `{ "ok": true, "settings": {
|
|
|
145
159
|
- `exports.inject = ["slots", "locale"]`
|
|
146
160
|
- 注册 `settings.section`:`{ name: "settings.section", id: "dsh-app-updater", order: 42, label: () => "应用更新" }`
|
|
147
161
|
- 只用 `require("react")`;不引入其它运行时依赖
|
|
148
|
-
- 页面:顶部工具条(刷新检测 / 全部更新 / 取消 / 打开下载目录)+ 统计条 + 可筛选可多选的表格 + 底部日志
|
|
149
|
-
- 表格列:勾选 / 应用名 / 已装版本 / 可升级到 /
|
|
150
|
-
- 所有请求带 `cache: "no-store"`;轮询 state.json(扫描/更新中
|
|
162
|
+
- 页面:顶部工具条(刷新检测 / 全部更新 / 取消 / 打开下载目录)+ 统计条 + 可筛选可多选的表格 + 更新结果面板 + 底部日志
|
|
163
|
+
- 表格列:勾选 / 应用名 / 已装版本 / 可升级到 / 状态 / 操作(「来源」是状态单元格里的徽章,不是独立列)
|
|
164
|
+
- 所有请求带 `cache: "no-store"`;轮询 state.json(扫描/更新中 **900ms**,空闲 **5000ms**)
|
|
165
|
+
(旧文档写 800ms/4s,与 `lib/client.js:20-21` 不符)
|
|
166
|
+
- **写操作闸门**:宿主对写路由显式校验 `method === "POST"`(否则 405),并在 `confirm:true`
|
|
167
|
+
之外要求 `csrfToken`(由 `GET /state.json` 下发、不落盘,兼容模式下尤其重要)
|
|
168
|
+
- 结果面板渲染 `installOk` / `verified` / `hash` / `durationMs` / `selfUpdater` / `dryRun` /
|
|
169
|
+
`unverifiable`;`unverifiable`(读不到已装版本)单列为「已安装·未复核」状态
|
package/README.en.md
CHANGED
|
@@ -26,10 +26,10 @@ Without npm, install from source or from the prebuilt tarball:
|
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
# source repository
|
|
29
|
-
dsh plugin --profile web add github:
|
|
29
|
+
dsh plugin --profile web add github:ccgo26/dsh-app-updater
|
|
30
30
|
|
|
31
31
|
# prebuilt tgz (see Releases)
|
|
32
|
-
dsh plugin --profile web add https://github.com/
|
|
32
|
+
dsh plugin --profile web add https://github.com/ccgo26/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Restart DSH afterwards; the plugin lives at **Settings → 应用更新**. The Electron-managed
|
|
@@ -224,15 +224,32 @@ Then add `dsh-app-updater` to `dsh.profile.bundles` in the profile's `package.js
|
|
|
224
224
|
2. Delete `<profile>/node_modules/dsh-app-updater`.
|
|
225
225
|
3. Restart DSH; the data directory `%USERPROFILE%\.dsh\dsh-app-updater` can be removed as well.
|
|
226
226
|
|
|
227
|
+
## 1.3.15 hardening (vs. 1.3.14)
|
|
228
|
+
|
|
229
|
+
- Host half now explicitly validates `method === "POST"` on write routes (405 otherwise) and
|
|
230
|
+
requires a process-local `csrfToken` (delivered by `GET /state.json`, never persisted) in
|
|
231
|
+
addition to `confirm:true`.
|
|
232
|
+
- Downloads are pre-fetched concurrently into private working directories (no shared-directory
|
|
233
|
+
diff race); a 4 GiB quota only removes files carrying the plugin's `.dsh-app-updater` sidecar.
|
|
234
|
+
- Post-install verification uses immediate-first + exponential backoff + a total time limit;
|
|
235
|
+
`unverifiable` is now an explicit result state. Uninstall aborts background tasks.
|
|
236
|
+
- Version parsing follows SemVer `+build`, is case-insensitive on suffixes, and no longer
|
|
237
|
+
collides in `normalizeVersionKey`. `StringDecoder` and POSIX process-group cancellation were
|
|
238
|
+
added to the process layer.
|
|
239
|
+
- See `CHANGELOG.md` for the full list and `CONTRACT.md` for the current HTTP contract.
|
|
240
|
+
|
|
227
241
|
## Tests
|
|
228
242
|
|
|
229
243
|
```bash
|
|
230
|
-
|
|
231
|
-
node test/
|
|
232
|
-
node test/
|
|
233
|
-
node test/
|
|
244
|
+
npm test # = unit + render + deny; the live output is authoritative for check counts
|
|
245
|
+
node test/unit.mjs # table parsing, version compare, manifest & switches, OS/arch detection, Homebrew parsing, macOS enumeration, admission gate
|
|
246
|
+
node test/render.mjs # settings panel (per platform), banners, column drag, request bodies, result panel
|
|
247
|
+
node test/deny.mjs # deny-by-default per route + CSRF / method gates
|
|
248
|
+
node test/harness.mjs --scan # boots the host half against a fake cordis ctx + real HTTP (print-only, not a scored test)
|
|
234
249
|
```
|
|
235
250
|
|
|
251
|
+
> Historic check counts in older docs (e.g. `153 / 51 / 12`) are stale — **always trust `npm test` output**.
|
|
252
|
+
|
|
236
253
|
> The tests do not depend on whether Homebrew is installed locally: the `findBrew` assertion checks
|
|
237
254
|
> existence across the three default prefixes (Apple Silicon / Intel / Linuxbrew), so it stays green
|
|
238
255
|
> on a real Mac (1.3.2 and earlier hard-coded `=== null` and was always red there; fixed in 1.3.3).
|
package/README.md
CHANGED
|
@@ -20,10 +20,10 @@ dsh plugin --profile web add dsh-app-updater
|
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
# 源码仓库
|
|
23
|
-
dsh plugin --profile web add github:
|
|
23
|
+
dsh plugin --profile web add github:ccgo26/dsh-app-updater
|
|
24
24
|
|
|
25
25
|
# 预构建 tgz(见 Releases)
|
|
26
|
-
dsh plugin --profile web add https://github.com/
|
|
26
|
+
dsh plugin --profile web add https://github.com/ccgo26/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
装好后重启 DSH,在 **设置 → 应用更新** 使用。桌面端(Electron 托管的 `desktop` profile)不允许
|
|
@@ -262,12 +262,16 @@ dsh plugin --profile <profile> add link:/path/to/dsh-app-updater
|
|
|
262
262
|
## 测试
|
|
263
263
|
|
|
264
264
|
```bash
|
|
265
|
-
|
|
266
|
-
node test/
|
|
267
|
-
node test/
|
|
268
|
-
node test/
|
|
265
|
+
npm test # = unit + render + deny,具体项数以命令输出为准
|
|
266
|
+
node test/unit.mjs # 定宽表格解析、版本比较、清单与静默参数、系统/架构识别、Homebrew 解析、macOS 应用枚举与 cask 匹配、准入闸门
|
|
267
|
+
node test/render.mjs # 设置面板(按平台)、演练提示、扫描警告横幅、列宽拖拽、请求体、结果面板
|
|
268
|
+
node test/deny.mjs # 无 connection 服务时逐路由验证默认拒绝 + CSRF / method 闸门
|
|
269
|
+
node test/harness.mjs --scan # 用假 cordis ctx + 真 HTTP 把宿主半身跑起来(非计分工具,需 Windows + winget 才能真扫描)
|
|
269
270
|
```
|
|
270
271
|
|
|
272
|
+
> 文档里的历史项数(如 `153 / 51 / 12`、`36 / 32`)都是旧值,**一律以 `npm test` 的实际输出为准**。
|
|
273
|
+
> `test/harness.mjs` 没有断言、不在 `npm test` 里、不计分,不要误当成第四套测试。
|
|
274
|
+
|
|
271
275
|
> 测试不依赖本机是否装了 Homebrew:`findBrew` 的断言按「存在性」覆盖 Apple Silicon /
|
|
272
276
|
> Intel / Linuxbrew 三处默认前缀(1.3.2 及以前这条断言写死 `=== null`,在真实 Mac 上恒红,
|
|
273
277
|
> 1.3.3 已修正)。
|
|
@@ -304,6 +308,24 @@ Edge 走它自带的更新服务:winget 目录里的 `Microsoft.Edge` 是企
|
|
|
304
308
|
> macOS 上只枚举标准应用目录。应用如果装在别处(例如 `~/Desktop` 或自定义目录),
|
|
305
309
|
> 本插件看不到它 —— 这不是漏扫,是扫描范围之外。
|
|
306
310
|
|
|
311
|
+
## 1.3.15 加固(相对 1.3.14 发布版)
|
|
312
|
+
|
|
313
|
+
- **写操作双闸门**:宿主对写路由显式校验 `method === "POST"`,并在 `confirm:true` 之外
|
|
314
|
+
要求 `csrfToken`(由 `GET /state.json` 下发、不落盘);兼容模式下无凭据的跨站 / 脚本请求
|
|
315
|
+
拿不到它,无法再驱动「下载 + 提权静默安装」。
|
|
316
|
+
- **`/cancel` 可指定目标**:`target: "scan" | "update" | "all"`(默认 `all`),不再一按全停;
|
|
317
|
+
界面在扫描中也能取消。
|
|
318
|
+
- **macOS 自定义下载目录**:`/settings` 的 `downloadDir` 现在接受以 `/` 开头的 POSIX 绝对路径。
|
|
319
|
+
- **卸载清理**:cordis 卸载时 abort 后台 scan / update 任务并清掉自动重扫 timer,不再留幽灵任务。
|
|
320
|
+
- **版本算法**:`+build` 按 SemVer 视为元数据、尾缀大小写不敏感、`normalizeVersionKey` 不再碰撞。
|
|
321
|
+
- **子进程**:`StringDecoder` 逐块解码(中文跨 chunk 不再变 U+FFFD)、POSIX 按进程组取消。
|
|
322
|
+
- **下载**:并发预下载(独立子目录消除目录 diff 竞态)、网络抖动重试、磁盘配额与清理、
|
|
323
|
+
`dryRun` 不再被删包;复核改为首立即 + 递增退避 + 总时限。
|
|
324
|
+
- **界面**:结果面板补全 `installOk / verified / hash / durationMs / unverifiable` 等字段、
|
|
325
|
+
启用 `locale`(`zh-CN` / `en`)、键盘可调列宽等 a11y 改进。
|
|
326
|
+
|
|
327
|
+
> 具体改动与验证结果见仓库工作区的 `dsh-app-updater-修复说明-*.md`(不随本包发布)。
|
|
328
|
+
|
|
307
329
|
## 卸载
|
|
308
330
|
|
|
309
331
|
1. 从 profile 的 `package.json` 里删掉 `dsh.profile.bundles` 中的 `dsh-app-updater` 与 `dependencies` 中的对应条目。
|
|
@@ -314,17 +336,21 @@ Edge 走它自带的更新服务:winget 目录里的 `Microsoft.Edge` 是企
|
|
|
314
336
|
|
|
315
337
|
| 文件 | 作用 |
|
|
316
338
|
|---|---|
|
|
317
|
-
| `lib/index.js` | 宿主半身:HTTP
|
|
318
|
-
| `lib/engine.js` |
|
|
339
|
+
| `lib/index.js` | 宿主半身:HTTP 路由、准入 / `confirm` / `csrfToken` / method 闸门、扫描/更新任务调度 |
|
|
340
|
+
| `lib/engine.js` | 扫描合并、官网包下载(并发预下载)、静默安装、winget 兜底、版本复核、下载源可达性探测 |
|
|
319
341
|
| `lib/winget.js` | winget 定位与调用、定宽表格解析(CJK 宽度感知) |
|
|
320
342
|
| `lib/registry.js` | 注册表 `Uninstall` 键枚举(PowerShell,UTF-8) |
|
|
321
|
-
| `lib/version.js` | Windows
|
|
322
|
-
| `lib/state.js` |
|
|
323
|
-
| `lib/proc.js` |
|
|
324
|
-
| `lib/
|
|
343
|
+
| `lib/version.js` | Windows 风格版本比较(含 SemVer `+build` / 尾缀自然序) |
|
|
344
|
+
| `lib/state.js` | 状态与设置持久化(原子写)、CSRF token、跨平台设置裁剪 |
|
|
345
|
+
| `lib/proc.js` | 子进程执行(超时 / AbortSignal / 进程组取消 / StringDecoder) |
|
|
346
|
+
| `lib/macos.js` | macOS 数据层:`.app` 枚举、cask 目录竞速与缓存、列表组装 |
|
|
347
|
+
| `lib/appcast.js` | 非 Homebrew 应用官网更新源(Sparkle / Electron,只读) |
|
|
348
|
+
| `lib/elevate.js` | Windows UAC 提权(临时文件 IPC + 进程树取消) |
|
|
349
|
+
| `lib/platform.js` | 系统 / 架构 / 引擎识别 |
|
|
350
|
+
| `lib/client.js` | 浏览器半身:设置页 UI(设置面板、可拖拽列宽、进度、结果面板与日志;`zh-CN` / `en` 字典) |
|
|
325
351
|
| `CONTRACT.md` | 宿主 ↔ 客户端接口契约 |
|
|
326
|
-
| `test/unit.mjs` | 解析器 / 版本 /
|
|
327
|
-
| `test/render.mjs` |
|
|
352
|
+
| `test/unit.mjs` | 解析器 / 版本 / 清单 / 准入闸门单测(项数以 `npm test` 输出为准) |
|
|
353
|
+
| `test/render.mjs` | 前端渲染与交互自测(项数以 `npm test` 输出为准) |
|
|
328
354
|
| `test/harness.mjs` | 宿主路由端到端 harness(假 ctx + 真 HTTP) |
|
|
329
355
|
| `icon.svg` | 插件图标 |
|
|
330
356
|
| `LICENSE` `CHANGELOG.md` `README.en.md` | MIT 许可证、更新日志、英文说明 |
|
package/RELEASING.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
这个仓库同时是 **GitHub 插件源码** 和 **npm 包 `dsh-app-updater`**,两者版本必须一致。
|
|
4
4
|
|
|
5
|
+
## 0. 两台机器同时维护:先同步,再动手
|
|
6
|
+
|
|
7
|
+
这个仓库在两台 Mac 上维护(家里 / 本机)。**最容易出的事故是两边各自提交、历史分叉**,
|
|
8
|
+
尤其可怕的是「npm 先发了、GitHub 没推」——registry 里的 `gitHead` 指向一个
|
|
9
|
+
GitHub 上并不存在的提交,别人从 npm 追溯源码时找不到对应代码(实测踩过:
|
|
10
|
+
发布 1.3.14 时 `gitHead: bf1a34d` 在 GitHub API 返回 422)。
|
|
11
|
+
|
|
12
|
+
**每次开工前先 pull,收工前必推**:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# ① 开工:先对齐远端,别在分叉的历史上改
|
|
16
|
+
git pull --rebase origin main
|
|
17
|
+
|
|
18
|
+
# ② 干活、提交
|
|
19
|
+
git add -A && git commit -m "feat: ..."
|
|
20
|
+
|
|
21
|
+
# ③ 收工:推送,确认远端已有你的提交
|
|
22
|
+
git push origin main
|
|
23
|
+
git log --oneline -1 origin/main # 必须能看到刚才那个提交
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**发版顺序永远是:GitHub 推送 → 再 npm publish。** 反过来就会留下
|
|
27
|
+
「npm 有版本、GitHub 没有对应提交」的不一致。发版前用这条命令自检:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 发布前最后一道检查:本地 HEAD 必须已在远端
|
|
31
|
+
git fetch origin
|
|
32
|
+
test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" && echo "OK:可以发布" || echo "先推送!"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> 若两台机器改到同一处代码,`git pull --rebase` 会要求你解决冲突。
|
|
36
|
+
> 冲突时**不要**用 `git push --force` 覆盖另一台的提交 —— 那是别人已经发布过的工作。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
5
40
|
## 1. 改版本号与更新日志
|
|
6
41
|
|
|
7
42
|
```bash
|
|
@@ -12,8 +47,8 @@
|
|
|
12
47
|
## 2. 本地自测
|
|
13
48
|
|
|
14
49
|
```bash
|
|
15
|
-
npm test # unit
|
|
16
|
-
node test/harness.mjs --scan # 宿主半身跑真实 HTTP
|
|
50
|
+
npm test # unit + render + deny 必须全绿(项数以实际输出为准)
|
|
51
|
+
node test/harness.mjs --scan # 宿主半身跑真实 HTTP(非计分工具;需要 Windows + winget)
|
|
17
52
|
```
|
|
18
53
|
|
|
19
54
|
## 3. 发布到 npm
|
|
@@ -38,6 +73,12 @@ npm pack --silent # 产出 dsh-app-updater-<version>.tgz
|
|
|
38
73
|
mv dsh-app-updater-*.tgz dsh-app-updater.tgz # 资产名不要带版本号
|
|
39
74
|
gh release create v<version> dsh-app-updater.tgz \
|
|
40
75
|
--title "v<version>" --notes-file <(sed -n '/^## \[<version>\]/,/^## \[/p' CHANGELOG.md)
|
|
76
|
+
|
|
77
|
+
# 发布后断言:资产名必须恰好是 dsh-app-updater.tgz(latest/download 按字面取文件名)。
|
|
78
|
+
# 漏做 `mv` 或带上版本号,所有市场链接要到「下一次发版」才暴露 404 —— 这里提前拦住。
|
|
79
|
+
gh release view v<version> --json assets --jq '.assets[].name' \
|
|
80
|
+
| grep -qx 'dsh-app-updater.tgz' \
|
|
81
|
+
&& echo "✓ 资产名正确" || { echo "✗ 资产名不是 dsh-app-updater.tgz"; exit 1; }
|
|
41
82
|
```
|
|
42
83
|
|
|
43
84
|
资产名必须**不带版本号**:README 与各插件市场用的是
|
|
@@ -45,6 +86,41 @@ gh release create v<version> dsh-app-updater.tgz \
|
|
|
45
86
|
文件名是按字面取的——一旦资产名带版本号,下一次发版这个链接就会 404。
|
|
46
87
|
如果确实想让文件名带版本号,就把链接改成钉住 tag 的形式(`releases/download/v1.0.0/…`)。
|
|
47
88
|
|
|
89
|
+
## 4.5 发版检查清单(照着勾)
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# ① 同步与推送
|
|
93
|
+
git pull --rebase origin main
|
|
94
|
+
git fetch origin && test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" \
|
|
95
|
+
&& echo "✓ HEAD 已在远端" || echo "✗ 先 git push"
|
|
96
|
+
|
|
97
|
+
# ② 三套测试必须全绿
|
|
98
|
+
npm test
|
|
99
|
+
|
|
100
|
+
# ③ 资产名断言(防止人肉 mv 漏做)
|
|
101
|
+
# gh release view ... | grep -qx 'dsh-app-updater.tgz'(见上文)
|
|
102
|
+
|
|
103
|
+
# ④ 改名 / 回退后核对各市场 sourceUrl 仍指向当前仓库(1.3.6 / 1.3.7 的教训)
|
|
104
|
+
|
|
105
|
+
# ③ 版本一致:package.json / CHANGELOG 顶部 / 即将打的 tag
|
|
106
|
+
grep '"version"' package.json
|
|
107
|
+
head -5 CHANGELOG.md | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+'
|
|
108
|
+
|
|
109
|
+
# ④ 发布 npm
|
|
110
|
+
npm publish --access public
|
|
111
|
+
|
|
112
|
+
# ⑤ 发布后核对 gitHead 指向的提交真实存在于 GitHub
|
|
113
|
+
curl -s https://registry.npmjs.org/dsh-app-updater/<version> \
|
|
114
|
+
| python3 -c "import json,sys;d=json.load(sys.stdin);print('gitHead:',d['gitHead'])"
|
|
115
|
+
# 拿上面打印的 sha 去 GitHub 查,必须 200(422/404 说明没推)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> **别只看 npm view 就以为没发出去**:国内默认 registry 常是
|
|
119
|
+
> `registry.npmmirror.com`,镜像有缓存延迟,刚发布时它仍显示旧版本。
|
|
120
|
+
> 核对用官方源:`npm view dsh-app-updater version --registry=https://registry.npmjs.org`。
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
48
124
|
## 5. 市场收录怎么更新
|
|
49
125
|
|
|
50
126
|
- **dsh-plugin.org / dshopen.org / awesome-dsh-plugin**:仓库更新后它们按各自刷新周期同步,
|
package/lib/appcast.js
CHANGED
|
@@ -246,8 +246,14 @@ export async function checkAppUpdateSources(targets, opts = {}) {
|
|
|
246
246
|
if (version && looksLikeVersion(version)) {
|
|
247
247
|
out.set(target.key, { version, via: source.kind });
|
|
248
248
|
}
|
|
249
|
-
} catch {
|
|
250
|
-
/*
|
|
249
|
+
} catch (err) {
|
|
250
|
+
/* 单个应用的更新源读不到,不影响其它应用,也不该中断扫描;
|
|
251
|
+
但留一条 info 日志,用户才能察觉哪些官网源没查成(原来完全静默)。 */
|
|
252
|
+
if (log) {
|
|
253
|
+
const label = target.key || target.path;
|
|
254
|
+
const why = String((err && err.message) || err || "未知原因");
|
|
255
|
+
log("info", `${label} 的官网更新源读取失败,已跳过(${why})`);
|
|
256
|
+
}
|
|
251
257
|
}
|
|
252
258
|
}
|
|
253
259
|
};
|