dsh-app-updater 1.0.0 → 1.3.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 +82 -0
- package/CONTRACT.md +55 -9
- package/README.en.md +48 -10
- package/README.md +57 -11
- package/RELEASING.md +76 -0
- package/lib/client.js +72 -9
- package/lib/elevate.js +205 -0
- package/lib/engine.js +671 -48
- package/lib/index.js +146 -16
- package/lib/platform.js +198 -0
- package/lib/state.js +8 -2
- package/lib/version.js +3 -0
- package/lib/winget.js +65 -20
- package/package.json +4 -3
- package/test/fixture-winget-list-zh.txt +133 -0
- package/test/fixture-winget-upgrade-zh.txt +8 -0
- package/test/render.mjs +1 -1
- package/test/unit.mjs +192 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,88 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [1.3.0] — 2026-10-31
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
|
|
9
|
+
- **机器级安装全部失败的问题**:进程没有管理员令牌时,MSI 直接失败(msiexec 1603,
|
|
10
|
+
MSI 日志里是 `Error 1925: You do not have sufficient privileges…`),而部分安装器是
|
|
11
|
+
**静默退出 0 却什么都没装**(实测 Logitech G HUB)。两个现象同源,都是权限。
|
|
12
|
+
- **安装后复核**:退出码 0 不再等于成功。安装完成后用「安装前版本 / 安装后版本 / 目标版本」
|
|
13
|
+
判断是否真的生效,没生效就如实报失败 —— 原来只看退出码,会把 G HUB 那种
|
|
14
|
+
「退出码 0、版本没变」的情况误报成「更新完成」。
|
|
15
|
+
- **架构过滤回退**:`winget download/upgrade --architecture x64` 在清单里没有该架构时现在能
|
|
16
|
+
正确识别(中文报错是「找不到适用的安装程序」,旧正则只匹配了「没有适用」)并退回不限架构
|
|
17
|
+
重试;`winget upgrade` 兜底同时补上 `--include-unknown`(虎牙直播的已装版本被读成 Unknown,
|
|
18
|
+
不带它直接「目录里没有适用的更新」)。
|
|
19
|
+
- **Edge**:winget 目录里的 `Microsoft.Edge` 是企业版 MSI,与本机消费版 Edge 的安装技术不匹配,
|
|
20
|
+
winget 直接拒绝(`0x8A15008E`「安装技术与当前安装的版本不同,请卸载包并安装较新版本」),
|
|
21
|
+
现在先走 Edge 自带的更新服务 `MicrosoftEdgeUpdate.exe /ua`;若它没生效(实测该机的
|
|
22
|
+
EdgeUpdate 引擎目录是 `1.3.311.5` 而服务主程序还是 `1.3.275.13`,服务起不来,`/ua` 直接
|
|
23
|
+
`0x80070002`),自动退回安装 winget 目录里的官网全量包(企业版 MSI,提权执行)——
|
|
24
|
+
实测这一步能把 Edge 正常装到目标版本,winget 拒绝执行只是因为它的策略。
|
|
25
|
+
- **读不到版本的应用**:虎牙直播的卸载信息里没有版本号,winget 永远显示 `Unknown`。
|
|
26
|
+
复核阶段不再把它误判为失败,改为「已安装、未复核」写进结果与日志。
|
|
27
|
+
- **「安装包不生效」的软件给出可操作提示**:实测 Logitech G HUB 的更新包早已下载到
|
|
28
|
+
`%ProgramData%\LGHUB\next.json` 指向的 depots(build 880806 / 2026.7.992920),但安装器
|
|
29
|
+
`--silent` 与 `winget upgrade` 都会在 3 秒内退出 0 且什么都不装,只有**启动 G HUB 本体**
|
|
30
|
+
之后 `current.json` 才跳到新版本。插件承诺不启动用户的软件,所以这类情况现在会在失败
|
|
31
|
+
原因里直接给出「手动打开一次该应用即可完成升级」的提示(`updateHintFor`)。
|
|
32
|
+
- **MSI 日志**:MSI 安装现在带 `/l*v <下载目录>\<包名>.msi.log`,失败时把日志路径写进错误信息
|
|
33
|
+
(「Error 1925」就是这么被找到的)。
|
|
34
|
+
- **残留清单**:关闭「保留安装包」时,现在连 winget 落盘的 `.yaml` 清单一并删除。
|
|
35
|
+
|
|
36
|
+
### 新增
|
|
37
|
+
|
|
38
|
+
- **自动提权安装**(`lib/elevate.js`):需要管理员的安装类命令交给 `Start-Process -Verb RunAs`
|
|
39
|
+
的提权副本执行,退出码与输出写进临时文件后读回。新增开关「自动提权安装」(默认开启)。
|
|
40
|
+
UAC 为「始终通知」时每次安装会弹一次确认;把 `ConsentPromptBehaviorAdmin` 设为 0
|
|
41
|
+
(管理员静默提权)时完全无感。
|
|
42
|
+
- 说明:提权副本是独立进程,取消更新只会停掉启动器,已经开始的那次安装会自己跑完。
|
|
43
|
+
|
|
44
|
+
### 安全
|
|
45
|
+
|
|
46
|
+
- **接入宿主 connection 准入**(插件市场 dshopen.org 对 1.2.0 的复核意见):所有
|
|
47
|
+
`/dsh-app-updater/*` 路由先过 `ctx.connection.admit(request)` —— DSH 用来做 Host/Origin
|
|
48
|
+
信任校验与浏览器会话 / API 认证的那套边界,宿主自己的 `/api` 路由用的就是它。
|
|
49
|
+
未通过准入的请求直接得到 `401`/`403`;带凭据的请求一律以准入结果为准。
|
|
50
|
+
只有在宿主没有 `connection` 服务、或者请求不带任何连接凭据且来自回环地址时,才退回旧的
|
|
51
|
+
「回环」规则,并在响应头标注 `X-DSH-Admission: loopback-legacy`(宿主自己的 Electron
|
|
52
|
+
载体与本机脚本都不带 Cookie,需要一个不误伤它们的路径)。
|
|
53
|
+
- 因此 README 里「用 curl 直接改设置」的例子现在需要带上 DSH 的会话 Cookie,或在
|
|
54
|
+
`connection` 不可用的宿主上从回环地址调用。
|
|
55
|
+
|
|
56
|
+
### 说明
|
|
57
|
+
|
|
58
|
+
- 读类命令(`winget list` / `upgrade --include-unknown` / `--version`)不提权。
|
|
59
|
+
- `update.results[]` 新增 `installOk`(安装器自身是否报成功)与 `manifest`(落盘的清单位置)。
|
|
60
|
+
|
|
61
|
+
## [1.2.0] — 2026-10-31
|
|
62
|
+
|
|
63
|
+
### 新增
|
|
64
|
+
|
|
65
|
+
- **运行环境识别**(`lib/platform.js`):识别 Windows / macOS / 其它系统、具体版本与名称
|
|
66
|
+
(`Windows 11(build 26300)`、`macOS 15(Sequoia)`)、CPU 架构(ARM64 / x64 / Intel / x86),
|
|
67
|
+
并记录 CPU 型号与线程数。Windows on ARM 上跑 x64 的 Node 时,靠 `PROCESSOR_ARCHITEW6432`
|
|
68
|
+
仍能还原出真实架构,而不是被骗成 x64。
|
|
69
|
+
- **按架构挑选安装包**:Windows 侧把识别到的架构传给 winget 的 `--architecture`,让「官网全量包」
|
|
70
|
+
与当前系统/芯片匹配;清单里没有该架构的安装包时自动退回自动匹配,并在日志里说明。
|
|
71
|
+
macOS 侧由 Homebrew 按本机架构取对应 cask。新增开关「按本机架构安装包」(默认开启)。
|
|
72
|
+
- **macOS 更新引擎(Homebrew cask)**:用 `brew list --cask --versions` 拿已装版本、
|
|
73
|
+
`brew outdated --json=v2 --cask --greedy` 拿可升级版本,`brew upgrade --cask <token>` 更新;
|
|
74
|
+
演练模式改用 `brew fetch --cask <token>`,只下载安装包不安装。没有 Homebrew 时明确报告
|
|
75
|
+
「不可用」,而不是假装能更新。
|
|
76
|
+
- **界面**:状态行与设置面板新增「运行环境」(系统 · 架构 · 引擎);引擎不可用时红字给出原因;
|
|
77
|
+
应用来源新增 `homebrew` 徽章。
|
|
78
|
+
- `/health.json` 增加 `version` / `env` / `envSummary` / `engine` / `capabilities`;
|
|
79
|
+
`state.json` 同步带出这些字段。
|
|
80
|
+
|
|
81
|
+
### 说明
|
|
82
|
+
|
|
83
|
+
- macOS 路径无法在开发机(Windows)上真机验证,因此识别与 Homebrew 输出解析均以注入式单测覆盖
|
|
84
|
+
(`test/unit.mjs` 共 66 项),安装/更新命令按 Homebrew 官方 CLI 行为编写。
|
|
85
|
+
- 扫描/更新接口按平台放行:Windows 与 macOS 通过,其它系统返回 400 并说明原因。
|
|
86
|
+
|
|
5
87
|
## [1.0.0] — 2026-10-31
|
|
6
88
|
|
|
7
89
|
首个可用版本。
|
package/CONTRACT.md
CHANGED
|
@@ -10,7 +10,8 @@ dsh-app-updater/
|
|
|
10
10
|
├─ cordis.patch.yml # insert 行:id=dsh-app-updater, name=dsh-app-updater
|
|
11
11
|
├─ lib/
|
|
12
12
|
│ ├─ index.js # 宿主半身:cordis 插件,注册 /dsh-app-updater/* 路由
|
|
13
|
-
│ ├─
|
|
13
|
+
│ ├─ platform.js # 运行环境识别:Windows / macOS、版本、CPU 架构、引擎选择
|
|
14
|
+
│ ├─ engine.js # 扫描 / 下载 / 安装编排(Windows=winget / macOS=Homebrew)
|
|
14
15
|
│ ├─ winget.js # winget 定位、调用、表格解析
|
|
15
16
|
│ ├─ registry.js # 注册表 Uninstall 键枚举(覆盖 winget 未收录的应用)
|
|
16
17
|
│ ├─ version.js # 版本比较(Windows 风格 1.2.3.4 + 字母后缀)
|
|
@@ -19,9 +20,31 @@ dsh-app-updater/
|
|
|
19
20
|
└─ README.md
|
|
20
21
|
```
|
|
21
22
|
|
|
23
|
+
## 运行环境与引擎
|
|
24
|
+
|
|
25
|
+
- `platform.js` 输出 `env`:`{ platform, family: windows|macos|other, osName, osVersion, osBuild, arch: arm64|x64|x86, archLabel, wingetArch, cpuModel, cpuCount }`。
|
|
26
|
+
- 引擎:Windows → `winget`;macOS → `homebrew`;其它 → `none`(扫描/更新接口返回 400)。
|
|
27
|
+
- 架构匹配:`settings.matchSystemArch !== false` 时,Windows 侧把 `env.wingetArch` 传给
|
|
28
|
+
`winget download/upgrade --architecture`;清单缺该架构时自动退回自动匹配并写日志。
|
|
29
|
+
macOS 侧由 Homebrew 决定 cask 架构。
|
|
30
|
+
- 提权(`lib/elevate.js`):`settings.autoElevate !== false` 且当前进程没有管理员令牌时,
|
|
31
|
+
安装类命令(安装包本体、`winget upgrade` 兜底)交给 `Start-Process -Verb RunAs` 的提权副本
|
|
32
|
+
执行,退出码与输出写进临时文件后读回;`cancelled` 表示 UAC 被取消(1223)。
|
|
33
|
+
读类命令(`winget list` / `upgrade --include-unknown` / `--version`)不提权。
|
|
34
|
+
- 复核:安装后必须用「安装前版本 / 安装后版本 / 目标版本」判断是否真的生效,退出码 0 不算成功
|
|
35
|
+
(机器级安装包在未提权时会静默退出 0 却什么都没装)。`update.results[].installOk` 记录
|
|
36
|
+
安装器自身是否报成功,`verified` 记录复核时读到的版本。
|
|
37
|
+
- 自带更新服务:Edge 的 winget 目录条目是企业版 MSI,与本机消费版安装技术不匹配
|
|
38
|
+
(`0x8A15008E`),改走 `MicrosoftEdgeUpdate.exe /ua`,再由复核轮询确认版本就位。
|
|
39
|
+
|
|
22
40
|
## 宿主 HTTP 路由
|
|
23
41
|
|
|
24
|
-
全部 `kind: "exact"`,前缀 `/dsh-app-updater
|
|
42
|
+
全部 `kind: "exact"`,前缀 `/dsh-app-updater/`。每个请求先过 **connection 准入**:
|
|
43
|
+
`ctx.connection.admit(req)`(DSH 的 Host/Origin 信任校验 + 浏览器会话 / API 认证,
|
|
44
|
+
宿主自己的 `/api` 路由用的同一套),被拒即返回 `401`(未认证)/ `403`(不可信来源);
|
|
45
|
+
写操作再要求 `confirm: true`。
|
|
46
|
+
宿主没有 `connection` 服务、或请求不带任何连接凭据且来自回环地址时,退回旧的「回环」检查,
|
|
47
|
+
并在响应头标注 `X-DSH-Admission: loopback-legacy`(`lib/index.js` 的 `admissionGate`)。
|
|
25
48
|
|
|
26
49
|
### GET /dsh-app-updater/state.json
|
|
27
50
|
|
|
@@ -29,13 +52,17 @@ dsh-app-updater/
|
|
|
29
52
|
{
|
|
30
53
|
"ok": true,
|
|
31
54
|
"platform": "win32",
|
|
55
|
+
"env": { "platform": "win32", "family": "windows", "osName": "Windows 11", "osVersion": "10.0.26300", "osBuild": "26300", "arch": "x64", "archLabel": "x64(AMD64)", "wingetArch": "x64", "cpuModel": "13th Gen Intel(R) Core(TM) i7-13700K", "cpuCount": 24 },
|
|
56
|
+
"envSummary": "Windows 11(build 26300) · x64(AMD64)",
|
|
57
|
+
"engine": { "id": "winget", "available": true, "version": "1.29.380" },
|
|
58
|
+
"capabilities": { "scan": true, "update": true, "checkVersions": true, "storeAppsIncluded": true, "note": "" },
|
|
32
59
|
"winget": { "available": true, "version": "v1.29.380", "path": "C:\\...\\winget.exe" },
|
|
33
60
|
"phase": "idle", // idle | scanning | updating
|
|
34
61
|
"scan": { "running": false, "startedAt": 0, "finishedAt": 0, "error": null },
|
|
35
62
|
"update": { "running": false, "total": 0, "done": 0, "current": null, "error": null },
|
|
36
|
-
"apps": { "scannedAt": 0, "stats": { "total": 0, "upgradable": 0, "unsupported": 0 }, "items": [] },
|
|
63
|
+
"apps": { "scannedAt": 0, "stats": { "total": 0, "upgradable": 0, "unsupported": 0 }, "items": [], "engine": "winget" },
|
|
37
64
|
"log": [ { "at": 0, "level": "info|warn|error|success", "text": "..." } ],
|
|
38
|
-
"settings": { "includeStoreApps": true, "includeRegistryOnly": true, "downloadDir": "", "keepInstallers": true, "hashFallbackUpgrade": true, "dryRun": false }
|
|
65
|
+
"settings": { "includeStoreApps": true, "includeRegistryOnly": true, "downloadDir": "", "keepInstallers": true, "hashFallbackUpgrade": true, "dryRun": false, "matchSystemArch": true, "autoElevate": true }
|
|
39
66
|
}
|
|
40
67
|
```
|
|
41
68
|
|
|
@@ -43,17 +70,17 @@ dsh-app-updater/
|
|
|
43
70
|
|
|
44
71
|
```jsonc
|
|
45
72
|
{
|
|
46
|
-
"id": "Google.Chrome", // winget id
|
|
73
|
+
"id": "Google.Chrome", // winget id;注册表项 "reg:<key>";macOS "brew:<cask token>"
|
|
47
74
|
"name": "Google Chrome",
|
|
48
75
|
"version": "155.0.8059.85", // 已安装版本,未知时 ""
|
|
49
76
|
"available": "156.0.8078.48", // 可升级到的版本,无更新时 ""
|
|
50
77
|
"publisher": "Google LLC",
|
|
51
|
-
"source": "winget", // winget | msstore | registry
|
|
78
|
+
"source": "winget", // winget | msstore | registry | homebrew
|
|
52
79
|
"installLocation": "C:\\Program Files\\Google\\Chrome\\Application",
|
|
53
80
|
"hasUpdate": true,
|
|
54
81
|
"updateSupported": true, // 本插件能否驱动更新
|
|
55
82
|
"reason": null, // null | "not-in-catalog" | "unknown-version" | "pinned" | "store-managed" | "system-component"
|
|
56
|
-
"kind": "exe" // exe | msi | msix | inno | null(未知)
|
|
83
|
+
"kind": "exe" // exe | msi | msix | inno | cask | null(未知)
|
|
57
84
|
}
|
|
58
85
|
```
|
|
59
86
|
|
|
@@ -68,11 +95,30 @@ body `{ "confirm": true, "force": true }` → `{ "ok": true, "started": true }`
|
|
|
68
95
|
### POST /dsh-app-updater/update
|
|
69
96
|
|
|
70
97
|
body `{ "confirm": true, "ids": ["Google.Chrome", "..."], "mode": "silent" }`
|
|
71
|
-
→ `{ "ok": true, "started": true, "queued": 3 }
|
|
98
|
+
→ `{ "ok": true, "started": true, "queued": 3 }`
|
|
99
|
+
(串行执行,逐条:下载官网全量包 → 校验哈希 → 静默安装(需要管理员时自动提权)→ 复核版本。
|
|
100
|
+
Edge 由 `MicrosoftEdgeUpdate.exe /ua` 处理。`settings.dryRun` 时只下载、不安装、不复核。)
|
|
101
|
+
|
|
102
|
+
`update.results[]`(每条一次更新):
|
|
103
|
+
|
|
104
|
+
```jsonc
|
|
105
|
+
{
|
|
106
|
+
"id": "Microsoft.Edge",
|
|
107
|
+
"ok": false, // 最终结论:复核不通过就是 false,哪怕安装器报成功
|
|
108
|
+
"installOk": true, // 安装器自身是否报成功(退出码 0 / winget 成功)
|
|
109
|
+
"error": "安装报告成功,但没有生效:版本没有变化(仍为 156.0.4314.50,目标 156.0.4314.58)",
|
|
110
|
+
"installer": "C:\\...\\Microsoft Edge_156.0.4314.58_Machine_X64_msi_en-US.msi",
|
|
111
|
+
"manifest": "C:\\...\\Microsoft Edge_156.0.4314.58_Machine_X64_msi_en-US.yaml",
|
|
112
|
+
"verified": "156.0.4314.50", // 复核时读到的已装版本
|
|
113
|
+
"selfUpdater": "edgeupdate", // 走自带更新服务时存在
|
|
114
|
+
"note": "已交给 Microsoft Edge 更新服务,等待版本就位",
|
|
115
|
+
"durationMs": 33288
|
|
116
|
+
}
|
|
117
|
+
```
|
|
72
118
|
|
|
73
119
|
### POST /dsh-app-updater/cancel
|
|
74
120
|
|
|
75
|
-
→ `{ "ok": true }
|
|
121
|
+
→ `{ "ok": true }`(取消尚未开始的项目;提权副本是独立进程,取消只会停掉启动器,已经开始的那次安装会自己跑完)
|
|
76
122
|
|
|
77
123
|
### POST /dsh-app-updater/settings
|
|
78
124
|
|
package/README.en.md
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
# dsh-app-updater — App Updater for DeepSeek Harness
|
|
2
2
|
|
|
3
3
|
**English** | [简体中文](README.md)
|
|
4
|
-
](https://www.npmjs.com/package/dsh-app-updater)
|
|
5
5
|

|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
Detect every installed
|
|
10
|
+
Detect every installed application that has a newer version, then update them
|
|
11
11
|
with one click — downloading the **full installer from each vendor's own website** and
|
|
12
12
|
installing it silently. **The detected applications are never launched.**
|
|
13
|
+
Windows (winget) and macOS (Homebrew), architecture-aware: ARM64 and x64/Intel installers are
|
|
14
|
+
chosen to match the machine.
|
|
13
15
|
|
|
14
16
|
Where to find it: DeepSeek Harness → Settings → **应用更新 (App Updater)**
|
|
15
17
|
|
|
16
18
|
## Install
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
|
-
#
|
|
20
|
-
dsh plugin --profile web add
|
|
21
|
+
# npm package (recommended; also one-click installable from the plugin center)
|
|
22
|
+
dsh plugin --profile web add dsh-app-updater
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
Without npm, install from source or from the prebuilt tarball:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
28
|
+
# source repository
|
|
29
|
+
dsh plugin --profile web add github:19952730389/dsh-app-updater
|
|
30
|
+
|
|
31
|
+
# prebuilt tgz (see Releases)
|
|
26
32
|
dsh plugin --profile web add https://github.com/19952730389/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
|
|
27
33
|
```
|
|
28
34
|
|
|
@@ -88,6 +94,8 @@ State file: `%USERPROFILE%\.dsh\dsh-app-updater\state.json`
|
|
|
88
94
|
| `keepInstallers` | `true` | Keep the installer after a successful update. |
|
|
89
95
|
| `hashFallbackUpgrade` | `true` | Fall back to `winget upgrade` when the download fails. |
|
|
90
96
|
| `dryRun` | `false` | **Rehearsal mode**: download the vendor's full installer but never install it. |
|
|
97
|
+
| `matchSystemArch` | `true` | Match the installer to the detected system and CPU architecture (Windows passes `--architecture`; macOS is resolved by Homebrew). |
|
|
98
|
+
| `autoElevate` | `true` | **Automatic elevation**: install commands that need administrator rights run in an elevated copy (UAC prompts once per install, when UAC prompts at all). |
|
|
91
99
|
|
|
92
100
|
Everything above is editable from the plugin's own settings panel inside the settings page.
|
|
93
101
|
The panel also accepts API calls:
|
|
@@ -106,25 +114,55 @@ curl -X POST -H "Content-Type: application/json" \
|
|
|
106
114
|
`settings` / `open-downloads` accept only requests from loopback with `confirm:true`.
|
|
107
115
|
- **No guessed silent switches**: the publisher's declared `Silent` switches are preferred;
|
|
108
116
|
generic defaults are used only when a manifest is unavailable.
|
|
109
|
-
- **
|
|
110
|
-
|
|
117
|
+
- **Elevation only for installs, and on demand**: install commands that need administrator rights
|
|
118
|
+
(MSI packages, installers writing into `Program Files`, the `winget upgrade` fallback) run in an
|
|
119
|
+
elevated copy through `Start-Process -Verb RunAs`. With UAC set to "always notify" you get one
|
|
120
|
+
prompt per install; with `ConsentPromptBehaviorAdmin = 0` it is completely silent. Reads
|
|
121
|
+
(`winget list` / `upgrade --include-unknown` / `--version`) never elevate.
|
|
122
|
+
- **A zero exit code is not success**: after installing, the plugin compares the installed version
|
|
123
|
+
against the target version and reports a failure when nothing actually changed — machine-scope
|
|
124
|
+
installers silently exit 0 without installing anything when they lack administrator rights
|
|
125
|
+
(Logitech G HUB used to be reported as "updated" that way).
|
|
111
126
|
- **System components** (`.NET`, VC++ runtimes, …) are listed but not updated by default.
|
|
112
127
|
- **Only its own directory is written**: state and logs live in `~/.dsh/dsh-app-updater/`.
|
|
113
128
|
|
|
114
129
|
## Requirements
|
|
115
130
|
|
|
116
|
-
- **Windows
|
|
131
|
+
- **Windows**: `winget.exe` (Microsoft App Installer). Without winget the plugin can still
|
|
117
132
|
list registry apps, but cannot tell whether they are up to date.
|
|
133
|
+
- **macOS**: `brew` ([Homebrew](https://brew.sh)). Without Homebrew the plugin reports the engine
|
|
134
|
+
as unavailable instead of pretending it can update anything.
|
|
118
135
|
- No third-party npm dependencies: the host half uses Node built-ins only, the browser half
|
|
119
136
|
only the host-provided `react`.
|
|
120
137
|
|
|
138
|
+
## Supported systems and architectures
|
|
139
|
+
|
|
140
|
+
The plugin detects its own environment at startup and picks the installer that matches it:
|
|
141
|
+
|
|
142
|
+
| System | Architecture | Engine | Notes |
|
|
143
|
+
|---|---|---|---|
|
|
144
|
+
| Windows 10 / 11 | x64 / ARM64 / x86 | **winget** | the detected architecture is passed as `winget --architecture`; if the manifest has no installer for it, the plugin falls back to automatic matching and says so in the log |
|
|
145
|
+
| macOS 11+ | Apple Silicon / Intel | **Homebrew casks** | Homebrew itself resolves the right cask for the machine; without Homebrew the engine is reported unavailable |
|
|
146
|
+
| anything else | — | none | the scan/update routes answer 400 with the reason |
|
|
147
|
+
|
|
148
|
+
The result is shown in the status line and in the settings panel's “运行环境 / Environment” row, e.g.
|
|
149
|
+
`Windows 11(build 26300) · x64(AMD64) · winget 1.29.380` or
|
|
150
|
+
`macOS 15(Sequoia) · Apple Silicon(ARM64) · Homebrew 4.5.0`.
|
|
151
|
+
|
|
152
|
+
The “按本机架构安装包 / match system architecture” switch (on by default) can be turned off to hand
|
|
153
|
+
the choice back to winget; Homebrew always resolves for the local architecture.
|
|
154
|
+
|
|
155
|
+
> The macOS/Homebrew path follows Homebrew's documented CLI behaviour and its detection and parsing
|
|
156
|
+
> logic are covered by unit tests, but it has not been verified on a real Mac — this plugin is
|
|
157
|
+
> developed on Windows. Please open an issue if something misbehaves there.
|
|
158
|
+
|
|
121
159
|
## HTTP API (host half)
|
|
122
160
|
|
|
123
161
|
| Method | Path | Purpose |
|
|
124
162
|
|---|---|---|
|
|
125
163
|
| GET | `/dsh-app-updater/state.json` | Status, inventory, log, settings |
|
|
126
164
|
| GET | `/dsh-app-updater/apps.json` | Inventory and statistics only |
|
|
127
|
-
| GET | `/dsh-app-updater/health.json` | Self-check
|
|
165
|
+
| GET | `/dsh-app-updater/health.json` | Self-check: version, environment, engine, capabilities, route count, data directory |
|
|
128
166
|
| POST | `/dsh-app-updater/scan` | `{confirm:true, force:true}` start scanning |
|
|
129
167
|
| POST | `/dsh-app-updater/update` | `{confirm:true, ids:[...]}` start the update queue |
|
|
130
168
|
| POST | `/dsh-app-updater/cancel` | `{confirm:true}` cancel items not yet started |
|
|
@@ -153,7 +191,7 @@ Then add `dsh-app-updater` to `dsh.profile.bundles` in the profile's `package.js
|
|
|
153
191
|
## Tests
|
|
154
192
|
|
|
155
193
|
```bash
|
|
156
|
-
node test/unit.mjs #
|
|
194
|
+
node test/unit.mjs # 66 checks: table parsing, version compare, manifest & switches, OS/arch detection, Homebrew parsing
|
|
157
195
|
node test/render.mjs # 32 checks: settings panel, rehearsal banner, column drag, request bodies
|
|
158
196
|
node test/harness.mjs --scan # boots the host half against a fake cordis ctx + real HTTP
|
|
159
197
|
```
|
package/README.md
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
# dsh-app-updater — 应用更新(DSH 插件)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
检测本机**所有已安装应用**是否有新版本,并从各自**官网下载完整安装包**一键静默更新。
|
|
4
4
|
检测过程**不需要打开被检测的软件**。
|
|
5
|
+
自动识别 **Windows / macOS** 与 **ARM64 / x64(Intel)** 架构,按当前系统与芯片挑选对应的安装包。
|
|
5
6
|
|
|
6
7
|
入口:DeepSeek Harness → 设置 → **应用更新**
|
|
7
8
|
|
|
8
9
|
## 安装
|
|
9
10
|
|
|
10
11
|
```bash
|
|
11
|
-
#
|
|
12
|
-
dsh plugin --profile web add
|
|
12
|
+
# npm 包(推荐,插件中心同样可一键安装)
|
|
13
|
+
dsh plugin --profile web add dsh-app-updater
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
> 刚发布的新版本可能被 pnpm 的 `minimumReleaseAge` 策略暂缓一段时间;想立刻装到指定版本就显式写版本号:
|
|
17
|
+
> `dsh plugin --profile web add dsh-app-updater@1.2.0`(细节见 [RELEASING.md](RELEASING.md))。
|
|
18
|
+
|
|
19
|
+
不想走 npm,也可以用源码或发行包:
|
|
16
20
|
|
|
17
21
|
```bash
|
|
22
|
+
# 源码仓库
|
|
23
|
+
dsh plugin --profile web add github:19952730389/dsh-app-updater
|
|
24
|
+
|
|
25
|
+
# 预构建 tgz(见 Releases)
|
|
18
26
|
dsh plugin --profile web add https://github.com/19952730389/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
|
|
19
27
|
```
|
|
20
28
|
|
|
21
29
|
装好后重启 DSH,在 **设置 → 应用更新** 使用。桌面端(Electron 托管的 `desktop` profile)不允许
|
|
22
30
|
`dsh plugin` 直接改写,需在 profile 的 `node_modules` 下建 junction 并手动补 `bundles`。
|
|
23
31
|
|
|
24
|
-
](https://www.npmjs.com/package/dsh-app-updater)
|
|
25
33
|

|
|
26
34
|

|
|
27
35
|

|
|
@@ -91,6 +99,8 @@ dsh plugin --profile web add https://github.com/19952730389/dsh-app-updater/rele
|
|
|
91
99
|
| `keepInstallers` | `true` | 更新后保留安装包;`false` 则在安装完成后删掉该应用刚下载的包。 |
|
|
92
100
|
| `hashFallbackUpgrade` | `true` | 下载失败时退回 `winget upgrade` 兜底。 |
|
|
93
101
|
| `dryRun` | `false` | **演练模式**:只下载官网全量安装包、不执行安装。用来先看会装什么。 |
|
|
102
|
+
| `matchSystemArch` | `true` | 按识别到的系统与芯片架构挑选安装包(Windows 传 `--architecture`;macOS 由 Homebrew 决定)。 |
|
|
103
|
+
| `autoElevate` | `true` | **自动提权安装**:需要管理员的安装包(MSI、写入 `Program Files`)改由管理员上下文执行;UAC 开启时每次安装会弹一次确认。 |
|
|
94
104
|
|
|
95
105
|
用 API 改设置(等价于设置面板,适合脚本化):
|
|
96
106
|
|
|
@@ -107,10 +117,17 @@ curl -X POST -H "Content-Type: application/json" \
|
|
|
107
117
|
|
|
108
118
|
## 安全边界
|
|
109
119
|
|
|
110
|
-
-
|
|
120
|
+
- **接入宿主 connection 准入**:每个请求先过 `ctx.connection.admit(request)` —— DSH 用来做
|
|
121
|
+
Host/Origin 信任校验与浏览器会话 / API 认证的那套边界(宿主自己的 `/api` 路由用的就是它),
|
|
122
|
+
未通过的请求直接得到 `401`/`403`。宿主没有 `connection` 服务、或请求不带任何连接凭据且来自
|
|
123
|
+
回环地址时,退回旧的「回环」规则,并在响应头标注 `X-DSH-Admission: loopback-legacy`。
|
|
124
|
+
- **写操作还要显式确认**:`scan` / `update` / `cancel` / `settings` / `open-downloads` 必须带 `confirm:true`。
|
|
111
125
|
- **不猜测静默参数**:优先用发布方在清单里声明的 `Silent` 参数;清单缺失时才按安装包类型用保守的默认开关。
|
|
112
|
-
-
|
|
113
|
-
-
|
|
126
|
+
- **需要管理员时自动提权**:机器级安装包(MSI、写入 `Program Files`)交给
|
|
127
|
+
`Start-Process -Verb RunAs` 的提权副本执行(可在设置里关掉「自动提权安装」);
|
|
128
|
+
UAC 为「始终通知」时每次安装会弹一次确认。
|
|
129
|
+
- **安装后复核版本**:用「安装前 / 安装后 / 目标」三个版本判断这次更新是否真的生效 ——
|
|
130
|
+
退出码 0 不再等于成功(机器级安装包在没提权时会静默退出 0 却什么都没装)。
|
|
114
131
|
- **不做系统组件更新**:`.NET` 运行时、VC++ 运行库等仍会列出来,但默认不作为一键更新的目标。
|
|
115
132
|
- **只写自己的目录**:状态与日志写在 `~/.dsh/dsh-app-updater/`,不碰 profile 与宿主配置。
|
|
116
133
|
|
|
@@ -118,7 +135,8 @@ curl -X POST -H "Content-Type: application/json" \
|
|
|
118
135
|
|
|
119
136
|
## 依赖
|
|
120
137
|
|
|
121
|
-
- **Windows
|
|
138
|
+
- **Windows**:`winget.exe`(Microsoft「应用安装程序 / App Installer」)。没装 winget 时插件仍能列出注册表里的应用,但无法判断是否有新版本。
|
|
139
|
+
- **macOS**:`brew`([Homebrew](https://brew.sh))。没装 Homebrew 时明确报告引擎不可用,不会假装能更新。
|
|
122
140
|
- 不需要任何第三方 npm 依赖;宿主半身只用 Node 内置模块,浏览器半身只用宿主提供的 `react`。
|
|
123
141
|
|
|
124
142
|
## 接口(宿主半身)
|
|
@@ -127,7 +145,7 @@ curl -X POST -H "Content-Type: application/json" \
|
|
|
127
145
|
|---|---|---|
|
|
128
146
|
| GET | `/dsh-app-updater/state.json` | 当前状态、应用清单、日志、设置 |
|
|
129
147
|
| GET | `/dsh-app-updater/apps.json` | 只取应用清单与统计 |
|
|
130
|
-
| GET | `/dsh-app-updater/health.json` |
|
|
148
|
+
| GET | `/dsh-app-updater/health.json` | 自检:版本、运行环境、引擎与能力、路由数、数据目录 |
|
|
131
149
|
| POST | `/dsh-app-updater/scan` | `{confirm:true, force:true}` 开始扫描 |
|
|
132
150
|
| POST | `/dsh-app-updater/update` | `{confirm:true, ids:[...]}` 开始更新队列 |
|
|
133
151
|
| POST | `/dsh-app-updater/cancel` | `{confirm:true}` 取消未开始项 |
|
|
@@ -153,11 +171,39 @@ dsh plugin --profile <profile> add link:/path/to/dsh-app-updater
|
|
|
153
171
|
## 测试
|
|
154
172
|
|
|
155
173
|
```bash
|
|
156
|
-
node test/unit.mjs #
|
|
174
|
+
node test/unit.mjs # 66 项:定宽表格解析、版本比较、清单与静默参数、系统/架构识别、Homebrew 解析
|
|
157
175
|
node test/render.mjs # 32 项:设置面板、演练提示、列宽拖拽、请求体
|
|
158
176
|
node test/harness.mjs --scan # 用假 cordis ctx + 真 HTTP 把宿主半身跑起来
|
|
159
177
|
```
|
|
160
178
|
|
|
179
|
+
## 支持的系统与架构
|
|
180
|
+
|
|
181
|
+
插件启动时会识别本机环境,并据此挑选对应架构的安装包:
|
|
182
|
+
|
|
183
|
+
| 系统 | 架构 | 更新引擎 | 说明 |
|
|
184
|
+
|---|---|---|---|
|
|
185
|
+
| Windows 10 / 11 | x64 / ARM64 / x86 | **winget** | 把识别到的架构传给 `winget --architecture`;清单没有该架构的包时自动退回自动匹配并记录日志 |
|
|
186
|
+
| macOS 11+ | Apple Silicon / Intel | **Homebrew cask** | Homebrew 自己按本机架构取 cask;没有 Homebrew 时明确报告引擎不可用 |
|
|
187
|
+
| 其它 | — | 无 | 扫描/更新接口返回 400 并说明原因 |
|
|
188
|
+
|
|
189
|
+
识别结果会显示在状态行与设置面板的「运行环境」里,例如
|
|
190
|
+
`Windows 11(build 26300) · x64(AMD64) · winget 1.29.380` 或
|
|
191
|
+
`macOS 15(Sequoia) · Apple Silicon(ARM64) · Homebrew 4.5.0`。
|
|
192
|
+
|
|
193
|
+
还可以在设置面板里关掉「按本机架构安装包」——关闭后把选择权交回 winget 自己(Homebrew 一直按本机架构)。
|
|
194
|
+
|
|
195
|
+
机器级安装(MSI、写入 `Program Files`)需要管理员权限,否则 MSI 会返回 1603(MSI 日志里是
|
|
196
|
+
`Error 1925: You do not have sufficient privileges…`),部分安装器则可能**静默退出 0 却什么都没装**。
|
|
197
|
+
因此安装类命令默认自动提权执行(设置里的「自动提权安装」,默认开启):安装包本体与
|
|
198
|
+
`winget upgrade` 兜底都交给 `Start-Process -Verb RunAs` 的提权副本,装完再按
|
|
199
|
+
「安装前版本 / 安装后版本 / 目标版本」复核这次更新是否真的生效。
|
|
200
|
+
UAC 为「始终通知」时每次安装会弹一次确认;把 DSH 本身以管理员身份运行则不会再弹。
|
|
201
|
+
Edge 走它自带的更新服务:winget 目录里的 `Microsoft.Edge` 是企业版 MSI,与本机消费版的
|
|
202
|
+
安装技术不匹配(`0x8A15008E`),winget 会直接拒绝。
|
|
203
|
+
|
|
204
|
+
> macOS 的 Homebrew 路径是按 Homebrew 官方 CLI 行为实现的,识别与解析逻辑有单测覆盖;
|
|
205
|
+
> 但本插件主要在 Windows 上开发,macOS 上的安装/更新没有真机验证过,遇到问题请开 Issue。
|
|
206
|
+
|
|
161
207
|
## 卸载
|
|
162
208
|
|
|
163
209
|
1. 从 profile 的 `package.json` 里删掉 `dsh.profile.bundles` 中的 `dsh-app-updater` 与 `dependencies` 中的对应条目。
|
package/RELEASING.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 发版流程
|
|
2
|
+
|
|
3
|
+
这个仓库同时是 **GitHub 插件源码** 和 **npm 包 `dsh-app-updater`**,两者版本必须一致。
|
|
4
|
+
|
|
5
|
+
## 1. 改版本号与更新日志
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# package.json 的 version 改成新版本,例如 1.0.1
|
|
9
|
+
# CHANGELOG.md 顶部加一节(遵循 keep-a-changelog 的写法)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 2. 本地自测
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm test # unit 36 项 + render 32 项
|
|
16
|
+
node test/harness.mjs --scan # 宿主半身跑真实 HTTP(需要 Windows + winget)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 3. 发布到 npm
|
|
20
|
+
|
|
21
|
+
npm 目前要求发布必须通过 2FA,或使用**开启 Bypass 2FA 的 Granular Access Token**:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# 方式 A:账号已开 2FA
|
|
25
|
+
npm publish --access public --otp=<6 位动态码>
|
|
26
|
+
|
|
27
|
+
# 方式 B:用带 Bypass 2FA 的 token(写入临时 userconfig,别写进仓库)
|
|
28
|
+
npm publish --access public --userconfig=<某个仓库外的 .npmrc>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> 注意:带 Bypass 2FA 的令牌可以做发布,但**不能**做 `npm unpublish` / 账号类操作,
|
|
32
|
+
> 那些仍然需要在网页上以 2FA 完成。
|
|
33
|
+
|
|
34
|
+
## 4. 打 GitHub Release(附预构建包)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm pack --silent # 产出 dsh-app-updater-<version>.tgz
|
|
38
|
+
mv dsh-app-updater-*.tgz dsh-app-updater.tgz # 资产名不要带版本号
|
|
39
|
+
gh release create v<version> dsh-app-updater.tgz \
|
|
40
|
+
--title "v<version>" --notes-file <(sed -n '/^## \[<version>\]/,/^## \[/p' CHANGELOG.md)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
资产名必须**不带版本号**:README 与各插件市场用的是
|
|
44
|
+
`releases/latest/download/dsh-app-updater.tgz`,`latest/download/` 只解析 `latest`,
|
|
45
|
+
文件名是按字面取的——一旦资产名带版本号,下一次发版这个链接就会 404。
|
|
46
|
+
如果确实想让文件名带版本号,就把链接改成钉住 tag 的形式(`releases/download/v1.0.0/…`)。
|
|
47
|
+
|
|
48
|
+
## 5. 市场收录怎么更新
|
|
49
|
+
|
|
50
|
+
- **dsh-plugin.org / dshopen.org / awesome-dsh-plugin**:仓库更新后它们按各自刷新周期同步,
|
|
51
|
+
一般不需要重新提交;条目信息(版本、Star、更新时间)会自动跟上。
|
|
52
|
+
- **dsh.works / DSH.SO / DSH STORE**:全部自动抓取,无需任何操作。
|
|
53
|
+
- **DSH Plugin Index(realoiyo.github.io)**:只抓 npm 包,`npm publish` 后下次重建即更新。
|
|
54
|
+
- 提交入口(首次收录用):
|
|
55
|
+
- dsh-plugin.org → https://github.com/dshplugin/dsh-plugin-hub/issues (模板见其 submit 页)
|
|
56
|
+
- dshopen.org → https://github.com/polang233/dsh-plugin-index/issues/new?template=plugin-add.yml
|
|
57
|
+
- awesome-dsh-plugin → 向仓库提 PR,新增 `data/plugins/<owner>__<repo>.yml`(见其 contributing.md)
|
|
58
|
+
|
|
59
|
+
### 刚发版时别人可能装到旧版本
|
|
60
|
+
|
|
61
|
+
pnpm 默认有 `minimumReleaseAge`(新发布的包先观察一段时间才参与解析)。profile 里若没写
|
|
62
|
+
`minimumReleaseAge: 0`,`dsh plugin add <包名>` 在发版后的一段时间内可能仍解析到上一个版本:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 立刻拿到刚发布的版本:显式指定版本号
|
|
66
|
+
dsh plugin --profile web add dsh-app-updater@<version>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
这不是包的问题,是消费端的供应链策略;等宽限期过去后不带版本号也会解析到新版。
|
|
70
|
+
(排查时看 profile 的 `pnpm-workspace.yaml`:`minimumReleaseAge` 与 `minimumReleaseAgeExclude`。)
|
|
71
|
+
|
|
72
|
+
## 6. 本地开发时的桌面端安装
|
|
73
|
+
|
|
74
|
+
Electron 托管的 `desktop` profile 不接受 `dsh plugin` 写入。做法是在
|
|
75
|
+
`<profile>/node_modules` 下建立指向本仓库的目录 junction,并把包名加进 profile
|
|
76
|
+
`package.json` 的 `dsh.profile.bundles` 与 `dependencies`,重启 DSH 生效。
|