dsh-app-updater 0.0.0-stage → 1.0.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 ADDED
@@ -0,0 +1,35 @@
1
+ # 更新日志
2
+
3
+ 本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
+
5
+ ## [1.0.0] — 2026-10-31
6
+
7
+ 首个可用版本。
8
+
9
+ ### 检测
10
+
11
+ - 用 `winget list` 列出已安装应用(含 winget 目录收录的软件与商店 / MSIX 包),并用注册表三处
12
+ `Uninstall` 键补齐 winget 目录未收录的软件。全程只读注册表与文件,**不启动任何被检测的应用**。
13
+ - 用 `winget upgrade --include-unknown` 取「已装版本 → 可升级版本」,按包 Id 与清单合并。
14
+ - 版本比较支持 Windows 风格的 `1.2.3.4`、日期风格 `2026.7.992920`、带后缀 `0.9.2+2026103013`
15
+ 以及 `1.2.3-beta` 这类预发布标记。
16
+ - 定宽表格解析按**显示宽度**切列(CJK 占 2 列),并剔除 `15 upgrades available.` 之类的尾部提示行。
17
+
18
+ ### 更新
19
+
20
+ - 一键更新链路:`winget download` 从**发布方官网**拉取**完整安装包**(下载时核对清单里的 SHA-256)→
21
+ 解析随包落盘的 winget 清单,取发布方自己声明的 `InstallerSwitches.Silent` →
22
+ 静默安装 → 用 `winget list --id` 复核安装后版本。
23
+ - 官网包哈希与目录记录不一致(发布方重签了安装包)或清单禁止直接下载时,自动退回
24
+ `winget upgrade --id … --exact --silent`(同源全量包,下载与安装交给 winget)。
25
+ - 安装包与清单留档在下载目录,便于核对来源;可关闭「保留安装包」在安装后自动清理。
26
+ - 需要管理员权限的安装包会正常弹出 UAC,插件不做静默提权。
27
+
28
+ ### 界面
29
+
30
+ - 设置 → 应用更新:工具条(刷新检测 / 全部更新 / 更新所选 / 取消 / 打开下载目录)、
31
+ 状态行、进度条、筛选行、应用表格、可折叠日志。
32
+ - 表格列宽默认给足以完整显示应用名,并且**每列可拖拽调宽、双击恢复默认**,偏好记在浏览器本地。
33
+ - 插件设置面板:安装包下载目录(含绝对路径校验)、保留安装包、演练模式、下载失败回退 winget、
34
+ 包含商店应用、包含目录未收录应用。演练模式开启时页面顶部有醒目提示。
35
+ - 全部写操作要求本机(loopback)来源并显式带 `confirm:true`。
package/CONTRACT.md ADDED
@@ -0,0 +1,93 @@
1
+ # dsh-app-updater 契约(宿主 ↔ 客户端)
2
+
3
+ 插件 id:`dsh-app-updater` 显示名:应用更新(App Updater)
4
+
5
+ ## 文件布局
6
+
7
+ ```
8
+ dsh-app-updater/
9
+ ├─ package.json # main: ./lib/index.js, exports ./client, dsh.bundle.patch, dsh.client
10
+ ├─ cordis.patch.yml # insert 行:id=dsh-app-updater, name=dsh-app-updater
11
+ ├─ lib/
12
+ │ ├─ index.js # 宿主半身:cordis 插件,注册 /dsh-app-updater/* 路由
13
+ │ ├─ engine.js # 扫描 / 下载 / 安装编排
14
+ │ ├─ winget.js # winget 定位、调用、表格解析
15
+ │ ├─ registry.js # 注册表 Uninstall 键枚举(覆盖 winget 未收录的应用)
16
+ │ ├─ version.js # 版本比较(Windows 风格 1.2.3.4 + 字母后缀)
17
+ │ ├─ state.js # 持久化状态(~/.dsh/dsh-app-updater/state.json)
18
+ │ └─ client.js # 浏览器半身:设置页 UI
19
+ └─ README.md
20
+ ```
21
+
22
+ ## 宿主 HTTP 路由
23
+
24
+ 全部 `kind: "exact"`,前缀 `/dsh-app-updater/`。写操作要求 loopback + `confirm: true`。
25
+
26
+ ### GET /dsh-app-updater/state.json
27
+
28
+ ```jsonc
29
+ {
30
+ "ok": true,
31
+ "platform": "win32",
32
+ "winget": { "available": true, "version": "v1.29.380", "path": "C:\\...\\winget.exe" },
33
+ "phase": "idle", // idle | scanning | updating
34
+ "scan": { "running": false, "startedAt": 0, "finishedAt": 0, "error": null },
35
+ "update": { "running": false, "total": 0, "done": 0, "current": null, "error": null },
36
+ "apps": { "scannedAt": 0, "stats": { "total": 0, "upgradable": 0, "unsupported": 0 }, "items": [] },
37
+ "log": [ { "at": 0, "level": "info|warn|error|success", "text": "..." } ],
38
+ "settings": { "includeStoreApps": true, "includeRegistryOnly": true, "downloadDir": "", "keepInstallers": true, "hashFallbackUpgrade": true, "dryRun": false }
39
+ }
40
+ ```
41
+
42
+ `apps.items[]`(单条应用):
43
+
44
+ ```jsonc
45
+ {
46
+ "id": "Google.Chrome", // winget id;纯注册表项为 "reg:<GUID 或 key 尾段>"
47
+ "name": "Google Chrome",
48
+ "version": "155.0.8059.85", // 已安装版本,未知时 ""
49
+ "available": "156.0.8078.48", // 可升级到的版本,无更新时 ""
50
+ "publisher": "Google LLC",
51
+ "source": "winget", // winget | msstore | registry
52
+ "installLocation": "C:\\Program Files\\Google\\Chrome\\Application",
53
+ "hasUpdate": true,
54
+ "updateSupported": true, // 本插件能否驱动更新
55
+ "reason": null, // null | "not-in-catalog" | "unknown-version" | "pinned" | "store-managed" | "system-component"
56
+ "kind": "exe" // exe | msi | msix | inno | null(未知)
57
+ }
58
+ ```
59
+
60
+ ### POST /dsh-app-updater/scan
61
+
62
+ body `{ "confirm": true, "force": true }` → `{ "ok": true, "started": true }`(异步;结果看 state.json / apps.json)
63
+
64
+ ### GET /dsh-app-updater/apps.json
65
+
66
+ `{ "ok": true, "scannedAt": 0, "stats": {...}, "items": [App...], "warnings": ["..."] }`
67
+
68
+ ### POST /dsh-app-updater/update
69
+
70
+ body `{ "confirm": true, "ids": ["Google.Chrome", "..."], "mode": "silent" }`
71
+ → `{ "ok": true, "started": true, "queued": 3 }`(串行执行,逐条下载官网全量包 → 校验 → 静默安装)
72
+
73
+ ### POST /dsh-app-updater/cancel
74
+
75
+ → `{ "ok": true }`(取消尚未开始的项目;正在安装的进程会收到终止请求)
76
+
77
+ ### POST /dsh-app-updater/settings
78
+
79
+ body `{ "confirm": true, "settings": { ... } }` → `{ "ok": true, "settings": {...} }`
80
+
81
+ ### POST /dsh-app-updater/open-downloads
82
+
83
+ → `{ "ok": true, "path": "..." }`(在资源管理器中打开下载目录)
84
+
85
+ ## 客户端要求
86
+
87
+ - `window.__ModuleLoader__.load({ id: "dsh-app-updater", factory: (require) => { ... } })`
88
+ - `exports.inject = ["slots", "locale"]`
89
+ - 注册 `settings.section`:`{ name: "settings.section", id: "dsh-app-updater", order: 42, label: () => "应用更新" }`
90
+ - 只用 `require("react")`;不引入其它运行时依赖
91
+ - 页面:顶部工具条(刷新检测 / 全部更新 / 取消 / 打开下载目录)+ 统计条 + 可筛选可多选的表格 + 底部日志
92
+ - 表格列:勾选 / 应用名 / 已装版本 / 可升级到 / 来源 / 状态 / 操作
93
+ - 所有请求带 `cache: "no-store"`;轮询 state.json(扫描/更新中 800ms,空闲 4s)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 19952730389
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,177 @@
1
+ # dsh-app-updater — App Updater for DeepSeek Harness
2
+
3
+ **English** | [简体中文](README.md)
4
+ ![version](https://img.shields.io/badge/version-1.0.0-4b6fff?style=flat-square)
5
+ ![license](https://img.shields.io/badge/license-MIT-2f6f4f?style=flat-square)
6
+ ![platform](https://img.shields.io/badge/platform-Windows-7da1de?style=flat-square)
7
+ ![dsh-plugin](https://img.shields.io/badge/DSH-plugin-263146?style=flat-square)
8
+
9
+
10
+ Detect every installed Windows application that has a newer version, then update them
11
+ with one click — downloading the **full installer from each vendor's own website** and
12
+ installing it silently. **The detected applications are never launched.**
13
+
14
+ Where to find it: DeepSeek Harness → Settings → **应用更新 (App Updater)**
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ # straight from the source repository
20
+ dsh plugin --profile web add github:19952730389/dsh-app-updater
21
+ ```
22
+
23
+ Prebuilt `.tgz` (see [Releases](https://github.com/19952730389/dsh-app-updater/releases)):
24
+
25
+ ```bash
26
+ dsh plugin --profile web add https://github.com/19952730389/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
27
+ ```
28
+
29
+ Restart DSH afterwards; the plugin lives at **Settings → 应用更新**. The Electron-managed
30
+ `desktop` profile does not accept `dsh plugin` writes, so link the package into that
31
+ profile's `node_modules` and add it to `dsh.profile.bundles` manually.
32
+
33
+ ---
34
+
35
+ ## What it actually does
36
+
37
+ | Step | How |
38
+ |---|---|
39
+ | 1. List installed apps | `winget list` (apps known to the winget catalog plus Store/MSIX packages) **and** the three Windows registry `Uninstall` keys (apps the catalog does not know). Read-only: no application is ever started. |
40
+ | 2. Decide whether an update exists | `winget upgrade --include-unknown` gives "installed version → available version" from the official winget catalog, merged into the inventory by package id. |
41
+ | 3. One-click update | `winget download` fetches the **vendor's full installer** from the URL recorded in the winget manifest (SHA-256 verified), the downloaded manifest supplies the publisher's own `InstallerSwitches.Silent`, the installer runs silently, and the resulting version is re-read with `winget list --id`. |
42
+ | 4. Fallback | If the vendor re-signed the installer (hash no longer matches the catalog) or the manifest forbids direct download, it falls back to `winget upgrade --id … --exact --silent` — still the full installer from the vendor, with winget doing the fetching and installing. |
43
+
44
+ Those download URLs really are the vendors' own: `github.com/git-for-windows/…`,
45
+ `dl.google.com/dl/chrome/install/…`, `download.cpuid.com/cpu-z/…`, `dl.hdslb.com/…`.
46
+ Both the installer and its `.yaml` manifest are kept in the download folder, so the
47
+ `InstallerUrl`, `InstallerSha256` and silent switches can be audited afterwards.
48
+
49
+ ---
50
+
51
+ ## Usage
52
+
53
+ 1. Open **Settings → 应用更新**.
54
+ 2. Click **刷新检测** (nothing runs in the background; scanning only happens when you ask).
55
+ 3. The table sorts updateable apps first. Use the search box and the
56
+ **全部 / 仅可更新 / 未收录** segments to narrow it down.
57
+ 4. Tick the apps to update (or press **全部更新**), or update a single app from its row.
58
+ 5. While updating you get a progress bar, the current app name and a per-item log;
59
+ **取消** stops the items that have not started yet.
60
+ 6. **打开下载目录** reveals the downloaded vendor installers in Explorer.
61
+
62
+ ### Status values
63
+
64
+ | Status | Meaning |
65
+ |---|---|
66
+ | **有新版本** | The catalog reports a higher version; one-click update is available. |
67
+ | **已是最新** | No newer version in the catalog. |
68
+ | **未收录** | An update exists in the catalog but this plugin cannot drive it (see reasons below). |
69
+
70
+ | Reason | Meaning |
71
+ |---|---|
72
+ | 软件目录未收录 | The app is not in the winget catalog (registry-only software), so its official update cannot be discovered or downloaded. |
73
+ | 由应用商店管理 | MSIX / Store app — update it through Microsoft Store. |
74
+ | 版本未知 | The installed version cannot be read (winget reports `Unknown`); updating is still allowed. |
75
+ | 系统组件 | System component / runtime; not driven by this plugin by default. |
76
+
77
+ ---
78
+
79
+ ## Settings
80
+
81
+ State file: `%USERPROFILE%\.dsh\dsh-app-updater\state.json`
82
+
83
+ | Key | Default | Meaning |
84
+ |---|---|---|
85
+ | `includeStoreApps` | `true` | List Store (MSIX) apps too (display only). |
86
+ | `includeRegistryOnly` | `true` | Fill in apps missing from the winget catalog using the registry (display only). |
87
+ | `downloadDir` | empty | Custom download folder; empty = `%USERPROFILE%\Downloads\DSH App Updater`. Must be absolute. |
88
+ | `keepInstallers` | `true` | Keep the installer after a successful update. |
89
+ | `hashFallbackUpgrade` | `true` | Fall back to `winget upgrade` when the download fails. |
90
+ | `dryRun` | `false` | **Rehearsal mode**: download the vendor's full installer but never install it. |
91
+
92
+ Everything above is editable from the plugin's own settings panel inside the settings page.
93
+ The panel also accepts API calls:
94
+
95
+ ```bash
96
+ curl -X POST -H "Content-Type: application/json" \
97
+ -d '{"confirm":true,"settings":{"dryRun":true}}' \
98
+ http://127.0.0.1:<port>/dsh-app-updater/settings
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Safety boundaries
104
+
105
+ - **Every write requires loopback + explicit confirmation**: `scan` / `update` / `cancel` /
106
+ `settings` / `open-downloads` accept only requests from loopback with `confirm:true`.
107
+ - **No guessed silent switches**: the publisher's declared `Silent` switches are preferred;
108
+ generic defaults are used only when a manifest is unavailable.
109
+ - **No silent elevation**: installers that need administrator rights show the normal UAC prompt.
110
+ - **Version is verified after installing** and recorded in the log.
111
+ - **System components** (`.NET`, VC++ runtimes, …) are listed but not updated by default.
112
+ - **Only its own directory is written**: state and logs live in `~/.dsh/dsh-app-updater/`.
113
+
114
+ ## Requirements
115
+
116
+ - **Windows** with `winget.exe` (Microsoft App Installer). Without winget the plugin can still
117
+ list registry apps, but cannot tell whether they are up to date.
118
+ - No third-party npm dependencies: the host half uses Node built-ins only, the browser half
119
+ only the host-provided `react`.
120
+
121
+ ## HTTP API (host half)
122
+
123
+ | Method | Path | Purpose |
124
+ |---|---|---|
125
+ | GET | `/dsh-app-updater/state.json` | Status, inventory, log, settings |
126
+ | GET | `/dsh-app-updater/apps.json` | Inventory and statistics only |
127
+ | GET | `/dsh-app-updater/health.json` | Self-check (route count, data directory) |
128
+ | POST | `/dsh-app-updater/scan` | `{confirm:true, force:true}` start scanning |
129
+ | POST | `/dsh-app-updater/update` | `{confirm:true, ids:[...]}` start the update queue |
130
+ | POST | `/dsh-app-updater/cancel` | `{confirm:true}` cancel items not yet started |
131
+ | POST | `/dsh-app-updater/settings` | `{confirm:true, settings:{...}}` |
132
+ | POST | `/dsh-app-updater/open-downloads` | `{confirm:true}` open the download folder |
133
+
134
+ ## Install (from a checkout)
135
+
136
+ ```bash
137
+ # from a local checkout
138
+ dsh plugin --profile <profile> add file:/path/to/dsh-app-updater
139
+
140
+ # or as a local link
141
+ dsh plugin --profile <profile> add link:/path/to/dsh-app-updater
142
+ ```
143
+
144
+ Then add `dsh-app-updater` to `dsh.profile.bundles` in the profile's `package.json`
145
+ (the bundled `cordis.patch.yml` mounts the host half) and reload DSH.
146
+
147
+ ## Uninstall
148
+
149
+ 1. Remove `dsh-app-updater` from `dsh.profile.bundles` and from `dependencies` in the profile's `package.json`.
150
+ 2. Delete `<profile>/node_modules/dsh-app-updater`.
151
+ 3. Restart DSH; the data directory `%USERPROFILE%\.dsh\dsh-app-updater` can be removed as well.
152
+
153
+ ## Tests
154
+
155
+ ```bash
156
+ node test/unit.mjs # 36 checks: table parsing, version compare, manifest & switch parsing
157
+ node test/render.mjs # 32 checks: settings panel, rehearsal banner, column drag, request bodies
158
+ node test/harness.mjs --scan # boots the host half against a fake cordis ctx + real HTTP
159
+ ```
160
+
161
+ ## Layout
162
+
163
+ | File | Purpose |
164
+ |---|---|
165
+ | `lib/index.js` | Host half: HTTP routes, write gate, scan/update scheduling |
166
+ | `lib/engine.js` | Inventory merge, vendor installer download, silent install, winget fallback, version re-check |
167
+ | `lib/winget.js` | winget discovery/invocation, fixed-width table parsing (CJK-aware) |
168
+ | `lib/registry.js` | Registry `Uninstall` enumeration (PowerShell, UTF-8) |
169
+ | `lib/version.js` | Windows-style version comparison |
170
+ | `lib/state.js` | Settings and state persistence (atomic writes) |
171
+ | `lib/proc.js` | Child process runner (timeouts, cancellation, UTF-8) |
172
+ | `lib/client.js` | Browser half: the settings page UI |
173
+ | `CONTRACT.md` | Host ↔ client interface contract |
174
+
175
+ ## License
176
+
177
+ MIT
package/README.md CHANGED
@@ -1,3 +1,184 @@
1
- # Temporary Holding Version
1
+ # dsh-app-updater — 应用更新(DSH 插件)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 检测本机**所有已安装的 Windows 应用**是否有新版本,并从各自**官网下载完整安装包**一键静默更新。
4
+ 检测过程**不需要打开被检测的软件**。
5
+
6
+ 入口:DeepSeek Harness → 设置 → **应用更新**
7
+
8
+ ## 安装
9
+
10
+ ```bash
11
+ # 从源码仓库安装(复制即可)
12
+ dsh plugin --profile web add github:19952730389/dsh-app-updater
13
+ ```
14
+
15
+ 发行 `.tgz` 见 [Releases](https://github.com/19952730389/dsh-app-updater/releases):
16
+
17
+ ```bash
18
+ dsh plugin --profile web add https://github.com/19952730389/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
19
+ ```
20
+
21
+ 装好后重启 DSH,在 **设置 → 应用更新** 使用。桌面端(Electron 托管的 `desktop` profile)不允许
22
+ `dsh plugin` 直接改写,需在 profile 的 `node_modules` 下建 junction 并手动补 `bundles`。
23
+
24
+ ![version](https://img.shields.io/badge/version-1.0.0-4b6fff?style=flat-square)
25
+ ![license](https://img.shields.io/badge/license-MIT-2f6f4f?style=flat-square)
26
+ ![platform](https://img.shields.io/badge/platform-Windows-7da1de?style=flat-square)
27
+ ![dsh-plugin](https://img.shields.io/badge/DSH-plugin-263146?style=flat-square)
28
+
29
+ **简体中文** · [English](README.en.md) · [更新日志](CHANGELOG.md) · MIT
30
+
31
+ ---
32
+
33
+ ## 它到底做了什么
34
+
35
+ | 步骤 | 实现 |
36
+ |---|---|
37
+ | ① 列出已装应用 | `winget list`(覆盖 winget 目录收录的软件 + 商店/MSIX 包)+ Windows 注册表三处 `Uninstall` 键(覆盖 winget 未收录的软件)。全程只读文件与注册表,不启动任何应用。 |
38
+ | ② 判断有没有新版本 | `winget upgrade --include-unknown`:由 winget 官方软件目录给出「已装版本 → 可升级版本」,并与 ①的清单按 Id 合并。 |
39
+ | ③ 一键更新 | `winget download` 把**发布方官网的完整安装包**下载到你指定的目录(下载时按清单核对 SHA-256),解析随包落盘的 winget 清单得到发布方自己的静默参数,再执行静默安装;安装完成后用 `winget list --id` 复核版本号。 |
40
+ | ④ 失败兜底 | 若官网安装包被发布方重新签发导致哈希与目录记录不一致、或清单禁止直接下载,自动退回 `winget upgrade --id <id> --exact --silent`(同样是从官网拉取完整安装包,只是下载与安装过程交给 winget)。 |
41
+
42
+ **「从对应官网下载」是真的官网**:安装包地址来自 winget 清单里的 `InstallerUrl`,例如
43
+ - Git → `https://github.com/git-for-windows/git/releases/download/...`
44
+ - CPU-Z → `https://download.cpuid.com/cpu-z/...`
45
+ - 哔哩哔哩 → `https://dl.hdslb.com/...`
46
+ - Chrome → `https://dl.google.com/dl/chrome/install/...`
47
+
48
+ 下载目录(默认 `%USERPROFILE%\Downloads\DSH App Updater`)里会同时留一份 `.yaml` 清单,
49
+ 里面有发布方声明的 `InstallerUrl` / `InstallerSha256` / `InstallerSwitches.Silent`,可以自行核对来源。
50
+
51
+ ---
52
+
53
+ ## 使用
54
+
55
+ 1. 打开 **设置 → 应用更新**。
56
+ 2. 点 **刷新检测**(不会自动跑后台任务;只有你点了才扫)。
57
+ 3. 列表默认按「有新版本优先」排序,可用搜索框和 **全部 / 仅可更新 / 未收录** 分段筛选。
58
+ 4. 勾选要更新的应用(或点 **全部更新** 一次处理当前筛选下所有可更新项),点行尾 **更新** 也可单个更新。
59
+ 5. 更新期间有进度条、当前应用名与逐条日志;**取消** 会停止尚未开始的项目。
60
+ 6. **打开下载目录** 可以在资源管理器中查看已下载的官网安装包。
61
+ 7. 同一页的 **插件设置** 面板里可以改下载目录、保留安装包、演练模式等开关(见「设置项」)。
62
+ 8. 表格列宽默认给足以完整显示应用名;**拖动表头右侧的分隔条** 可以调整该列宽度(双击恢复默认),
63
+ 偏好记在浏览器本地,下次打开仍是你的宽度。
64
+
65
+ ### 状态含义
66
+
67
+ | 状态 | 含义 |
68
+ |---|---|
69
+ | **有新版本** | 软件目录给出更高的版本号,可一键更新。 |
70
+ | **已是最新** | 目录里没有更高版本。 |
71
+ | **未收录** | 目录里找到了更新,但本插件无法驱动它更新(见下)。 |
72
+
73
+ | 原因 | 含义 |
74
+ |---|---|
75
+ | 软件目录未收录 | 该软件不在 winget 目录里(例如只在注册表登记的国产软件),无法判断/下载官网更新。 |
76
+ | 由应用商店管理 | MSIX / 商店应用,请用 Microsoft Store 更新。 |
77
+ | 版本未知 | 应用当前版本读不出来(winget 显示 `Unknown`),仍然允许尝试更新。 |
78
+ | 系统组件 | 系统组件/运行时,默认不建议由第三方驱动更新。 |
79
+
80
+ ---
81
+
82
+ ## 设置项
83
+
84
+ 状态文件:`%USERPROFILE%\.dsh\dsh-app-updater\state.json`
85
+
86
+ | 键 | 默认 | 说明 |
87
+ |---|---|---|
88
+ | `includeStoreApps` | `true` | 是否把商店(MSIX)应用列进来(只展示,不会被更新)。 |
89
+ | `includeRegistryOnly` | `true` | 是否用注册表补齐 winget 未收录的应用(只展示,不能更新)。 |
90
+ | `downloadDir` | 空 | 自定义安装包下载目录;空 = `%USERPROFILE%\Downloads\DSH App Updater`。 |
91
+ | `keepInstallers` | `true` | 更新后保留安装包;`false` 则在安装完成后删掉该应用刚下载的包。 |
92
+ | `hashFallbackUpgrade` | `true` | 下载失败时退回 `winget upgrade` 兜底。 |
93
+ | `dryRun` | `false` | **演练模式**:只下载官网全量安装包、不执行安装。用来先看会装什么。 |
94
+
95
+ 用 API 改设置(等价于设置面板,适合脚本化):
96
+
97
+ ```bash
98
+ curl -X POST -H "Content-Type: application/json" \
99
+ -d '{"confirm":true,"settings":{"dryRun":true}}' \
100
+ http://127.0.0.1:<port>/dsh-app-updater/settings
101
+ ```
102
+
103
+ `downloadDir` 会被校验:必须留空(用默认目录)或是绝对路径(`D:\Apps`)/UNC 路径(`\\server\share`),
104
+ 否则返回 `400` 并在面板里显示红字提示,不会把安装包写到意料之外的位置。
105
+
106
+ ---
107
+
108
+ ## 安全边界
109
+
110
+ - **写操作全部要求本机来源 + 显式确认**:`scan` / `update` / `cancel` / `settings` / `open-downloads` 只接受 loopback 请求且必须带 `confirm:true`。
111
+ - **不猜测静默参数**:优先用发布方在清单里声明的 `Silent` 参数;清单缺失时才按安装包类型用保守的默认开关。
112
+ - **不静默提权**:需要管理员权限的安装包会正常弹出 UAC,由你决定是否放行。
113
+ - **安装后复核版本**:更新完会重新查询一次版本并记录,日志里能看到「更新完成,当前版本 X」。
114
+ - **不做系统组件更新**:`.NET` 运行时、VC++ 运行库等仍会列出来,但默认不作为一键更新的目标。
115
+ - **只写自己的目录**:状态与日志写在 `~/.dsh/dsh-app-updater/`,不碰 profile 与宿主配置。
116
+
117
+ ---
118
+
119
+ ## 依赖
120
+
121
+ - **Windows**(`winget.exe`,即 Microsoft「应用安装程序 / App Installer」)。没装 winget 时插件仍能列出注册表里的应用,但无法判断是否有新版本。
122
+ - 不需要任何第三方 npm 依赖;宿主半身只用 Node 内置模块,浏览器半身只用宿主提供的 `react`。
123
+
124
+ ## 接口(宿主半身)
125
+
126
+ | 方法 | 路径 | 说明 |
127
+ |---|---|---|
128
+ | GET | `/dsh-app-updater/state.json` | 当前状态、应用清单、日志、设置 |
129
+ | GET | `/dsh-app-updater/apps.json` | 只取应用清单与统计 |
130
+ | GET | `/dsh-app-updater/health.json` | 自检(路由数、数据目录) |
131
+ | POST | `/dsh-app-updater/scan` | `{confirm:true, force:true}` 开始扫描 |
132
+ | POST | `/dsh-app-updater/update` | `{confirm:true, ids:[...]}` 开始更新队列 |
133
+ | POST | `/dsh-app-updater/cancel` | `{confirm:true}` 取消未开始项 |
134
+ | POST | `/dsh-app-updater/settings` | `{confirm:true, settings:{...}}` |
135
+ | POST | `/dsh-app-updater/open-downloads` | `{confirm:true}` 打开下载目录 |
136
+
137
+ ## 安装(开发)
138
+
139
+ ```bash
140
+ # 本地目录安装
141
+ dsh plugin --profile <profile> add file:/path/to/dsh-app-updater
142
+
143
+ # 或者用链接(开发时改代码即时生效)
144
+ dsh plugin --profile <profile> add link:/path/to/dsh-app-updater
145
+ ```
146
+
147
+ 装好后把 `dsh-app-updater` 加进 profile `package.json` 的 `dsh.profile.bundles` 清单
148
+ (包内自带的 `cordis.patch.yml` 会挂载宿主半身),然后重启 DSH 即可在设置页看到入口。
149
+
150
+ > 桌面端(Electron 托管)的 `desktop` profile 不允许用 `dsh plugin` 直接改写,
151
+ > 需要在 profile 的 `node_modules` 下建立指向本包的 junction 并手动补 `bundles` 与依赖。
152
+
153
+ ## 测试
154
+
155
+ ```bash
156
+ node test/unit.mjs # 36 项:定宽表格解析、版本比较、安装包清单与静默参数
157
+ node test/render.mjs # 32 项:设置面板、演练提示、列宽拖拽、请求体
158
+ node test/harness.mjs --scan # 用假 cordis ctx + 真 HTTP 把宿主半身跑起来
159
+ ```
160
+
161
+ ## 卸载
162
+
163
+ 1. 从 profile 的 `package.json` 里删掉 `dsh.profile.bundles` 中的 `dsh-app-updater` 与 `dependencies` 中的对应条目。
164
+ 2. 删除 `%USERPROFILE%\.dsh\profiles\<profile>\node_modules\dsh-app-updater`。
165
+ 3. 重启 DSH;数据目录 `%USERPROFILE%\.dsh\dsh-app-updater` 可自行删除。
166
+
167
+ ## 文件
168
+
169
+ | 文件 | 作用 |
170
+ |---|---|
171
+ | `lib/index.js` | 宿主半身:HTTP 路由、写操作闸门、扫描/更新任务调度 |
172
+ | `lib/engine.js` | 扫描合并、官网包下载、静默安装、winget 兜底、版本复核 |
173
+ | `lib/winget.js` | winget 定位与调用、定宽表格解析(CJK 宽度感知) |
174
+ | `lib/registry.js` | 注册表 `Uninstall` 键枚举(PowerShell,UTF-8) |
175
+ | `lib/version.js` | Windows 风格版本比较 |
176
+ | `lib/state.js` | 状态与设置持久化(原子写) |
177
+ | `lib/proc.js` | 子进程执行(超时/取消/UTF-8) |
178
+ | `lib/client.js` | 浏览器半身:设置页 UI(设置面板、可拖拽列宽、进度与日志) |
179
+ | `CONTRACT.md` | 宿主 ↔ 客户端接口契约 |
180
+ | `test/unit.mjs` | 解析器 / 版本 / 清单单测(36 项) |
181
+ | `test/render.mjs` | 前端渲染与交互自测(32 项) |
182
+ | `test/harness.mjs` | 宿主路由端到端 harness(假 ctx + 真 HTTP) |
183
+ | `icon.svg` | 插件图标 |
184
+ | `LICENSE` `CHANGELOG.md` `README.en.md` | MIT 许可证、更新日志、英文说明 |
@@ -0,0 +1,8 @@
1
+ # dsh-app-updater 的 bundle 补丁层。
2
+ #
3
+ # 一行 insert 挂载宿主半身;浏览器半身由 package.json 的 dsh.client 声明,
4
+ # 并绑定在 name 为裸包名的这一行上。
5
+ - insert:
6
+ - id: dsh-app-updater
7
+ name: 'dsh-app-updater'
8
+ config: {}
package/icon.svg ADDED
@@ -0,0 +1,33 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="DSH 应用更新">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#5b9bff"/>
5
+ <stop offset="1" stop-color="#2f6f4f"/>
6
+ </linearGradient>
7
+ <linearGradient id="win" x1="0" y1="0" x2="0" y2="1">
8
+ <stop offset="0" stop-color="#ffffff"/>
9
+ <stop offset="1" stop-color="#dbe7ff"/>
10
+ </linearGradient>
11
+ </defs>
12
+
13
+ <rect x="4" y="4" width="120" height="120" rx="28" fill="url(#bg)"/>
14
+
15
+ <!-- 应用窗口 -->
16
+ <rect x="26" y="30" width="76" height="58" rx="10" fill="url(#win)"/>
17
+ <rect x="26" y="30" width="76" height="16" rx="10" fill="#1f2937" opacity="0.82"/>
18
+ <circle cx="36" cy="38" r="3.2" fill="#f87171"/>
19
+ <circle cx="46" cy="38" r="3.2" fill="#fbbf24"/>
20
+ <circle cx="56" cy="38" r="3.2" fill="#34d399"/>
21
+ <rect x="36" y="54" width="34" height="5" rx="2.5" fill="#2f4f7a" opacity="0.55"/>
22
+ <rect x="36" y="64" width="48" height="5" rx="2.5" fill="#2f4f7a" opacity="0.3"/>
23
+ <rect x="36" y="74" width="26" height="5" rx="2.5" fill="#2f4f7a" opacity="0.3"/>
24
+
25
+ <!-- 下载箭头 -->
26
+ <path d="M64 60 L64 92" stroke="#ffffff" stroke-width="9" stroke-linecap="round"/>
27
+ <path d="M48 80 L64 96 L80 80" fill="none" stroke="#ffffff" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
28
+ <rect x="42" y="100" width="44" height="8" rx="4" fill="#ffffff" opacity="0.9"/>
29
+
30
+ <!-- 环形更新标记 -->
31
+ <path d="M92 44 A30 30 0 0 1 92 84" fill="none" stroke="#ffffff" stroke-width="7" stroke-linecap="round" opacity="0.85"/>
32
+ <path d="M88 78 L96 86 L88 92" fill="none" stroke="#ffffff" stroke-width="7" stroke-linecap="round" stroke-linejoin="round" opacity="0.85"/>
33
+ </svg>