dsh-app-updater 0.0.0-stage → 1.2.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,61 @@
1
+ # 更新日志
2
+
3
+ 本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
+
5
+ ## [1.2.0] — 2026-10-31
6
+
7
+ ### 新增
8
+
9
+ - **运行环境识别**(`lib/platform.js`):识别 Windows / macOS / 其它系统、具体版本与名称
10
+ (`Windows 11(build 26300)`、`macOS 15(Sequoia)`)、CPU 架构(ARM64 / x64 / Intel / x86),
11
+ 并记录 CPU 型号与线程数。Windows on ARM 上跑 x64 的 Node 时,靠 `PROCESSOR_ARCHITEW6432`
12
+ 仍能还原出真实架构,而不是被骗成 x64。
13
+ - **按架构挑选安装包**:Windows 侧把识别到的架构传给 winget 的 `--architecture`,让「官网全量包」
14
+ 与当前系统/芯片匹配;清单里没有该架构的安装包时自动退回自动匹配,并在日志里说明。
15
+ macOS 侧由 Homebrew 按本机架构取对应 cask。新增开关「按本机架构安装包」(默认开启)。
16
+ - **macOS 更新引擎(Homebrew cask)**:用 `brew list --cask --versions` 拿已装版本、
17
+ `brew outdated --json=v2 --cask --greedy` 拿可升级版本,`brew upgrade --cask <token>` 更新;
18
+ 演练模式改用 `brew fetch --cask <token>`,只下载安装包不安装。没有 Homebrew 时明确报告
19
+ 「不可用」,而不是假装能更新。
20
+ - **界面**:状态行与设置面板新增「运行环境」(系统 · 架构 · 引擎);引擎不可用时红字给出原因;
21
+ 应用来源新增 `homebrew` 徽章。
22
+ - `/health.json` 增加 `version` / `env` / `envSummary` / `engine` / `capabilities`;
23
+ `state.json` 同步带出这些字段。
24
+
25
+ ### 说明
26
+
27
+ - macOS 路径无法在开发机(Windows)上真机验证,因此识别与 Homebrew 输出解析均以注入式单测覆盖
28
+ (`test/unit.mjs` 共 66 项),安装/更新命令按 Homebrew 官方 CLI 行为编写。
29
+ - 扫描/更新接口按平台放行:Windows 与 macOS 通过,其它系统返回 400 并说明原因。
30
+
31
+ ## [1.0.0] — 2026-10-31
32
+
33
+ 首个可用版本。
34
+
35
+ ### 检测
36
+
37
+ - 用 `winget list` 列出已安装应用(含 winget 目录收录的软件与商店 / MSIX 包),并用注册表三处
38
+ `Uninstall` 键补齐 winget 目录未收录的软件。全程只读注册表与文件,**不启动任何被检测的应用**。
39
+ - 用 `winget upgrade --include-unknown` 取「已装版本 → 可升级版本」,按包 Id 与清单合并。
40
+ - 版本比较支持 Windows 风格的 `1.2.3.4`、日期风格 `2026.7.992920`、带后缀 `0.9.2+2026103013`
41
+ 以及 `1.2.3-beta` 这类预发布标记。
42
+ - 定宽表格解析按**显示宽度**切列(CJK 占 2 列),并剔除 `15 upgrades available.` 之类的尾部提示行。
43
+
44
+ ### 更新
45
+
46
+ - 一键更新链路:`winget download` 从**发布方官网**拉取**完整安装包**(下载时核对清单里的 SHA-256)→
47
+ 解析随包落盘的 winget 清单,取发布方自己声明的 `InstallerSwitches.Silent` →
48
+ 静默安装 → 用 `winget list --id` 复核安装后版本。
49
+ - 官网包哈希与目录记录不一致(发布方重签了安装包)或清单禁止直接下载时,自动退回
50
+ `winget upgrade --id … --exact --silent`(同源全量包,下载与安装交给 winget)。
51
+ - 安装包与清单留档在下载目录,便于核对来源;可关闭「保留安装包」在安装后自动清理。
52
+ - 需要管理员权限的安装包会正常弹出 UAC,插件不做静默提权。
53
+
54
+ ### 界面
55
+
56
+ - 设置 → 应用更新:工具条(刷新检测 / 全部更新 / 更新所选 / 取消 / 打开下载目录)、
57
+ 状态行、进度条、筛选行、应用表格、可折叠日志。
58
+ - 表格列宽默认给足以完整显示应用名,并且**每列可拖拽调宽、双击恢复默认**,偏好记在浏览器本地。
59
+ - 插件设置面板:安装包下载目录(含绝对路径校验)、保留安装包、演练模式、下载失败回退 winget、
60
+ 包含商店应用、包含目录未收录应用。演练模式开启时页面顶部有醒目提示。
61
+ - 全部写操作要求本机(loopback)来源并显式带 `confirm:true`。
package/CONTRACT.md ADDED
@@ -0,0 +1,106 @@
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
+ │ ├─ platform.js # 运行环境识别:Windows / macOS、版本、CPU 架构、引擎选择
14
+ │ ├─ engine.js # 扫描 / 下载 / 安装编排(Windows=winget / macOS=Homebrew)
15
+ │ ├─ winget.js # winget 定位、调用、表格解析
16
+ │ ├─ registry.js # 注册表 Uninstall 键枚举(覆盖 winget 未收录的应用)
17
+ │ ├─ version.js # 版本比较(Windows 风格 1.2.3.4 + 字母后缀)
18
+ │ ├─ state.js # 持久化状态(~/.dsh/dsh-app-updater/state.json)
19
+ │ └─ client.js # 浏览器半身:设置页 UI
20
+ └─ README.md
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
+
31
+ ## 宿主 HTTP 路由
32
+
33
+ 全部 `kind: "exact"`,前缀 `/dsh-app-updater/`。写操作要求 loopback + `confirm: true`。
34
+
35
+ ### GET /dsh-app-updater/state.json
36
+
37
+ ```jsonc
38
+ {
39
+ "ok": true,
40
+ "platform": "win32",
41
+ "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 },
42
+ "envSummary": "Windows 11(build 26300) · x64(AMD64)",
43
+ "engine": { "id": "winget", "available": true, "version": "1.29.380" },
44
+ "capabilities": { "scan": true, "update": true, "checkVersions": true, "storeAppsIncluded": true, "note": "" },
45
+ "winget": { "available": true, "version": "v1.29.380", "path": "C:\\...\\winget.exe" },
46
+ "phase": "idle", // idle | scanning | updating
47
+ "scan": { "running": false, "startedAt": 0, "finishedAt": 0, "error": null },
48
+ "update": { "running": false, "total": 0, "done": 0, "current": null, "error": null },
49
+ "apps": { "scannedAt": 0, "stats": { "total": 0, "upgradable": 0, "unsupported": 0 }, "items": [], "engine": "winget" },
50
+ "log": [ { "at": 0, "level": "info|warn|error|success", "text": "..." } ],
51
+ "settings": { "includeStoreApps": true, "includeRegistryOnly": true, "downloadDir": "", "keepInstallers": true, "hashFallbackUpgrade": true, "dryRun": false, "matchSystemArch": true }
52
+ }
53
+ ```
54
+
55
+ `apps.items[]`(单条应用):
56
+
57
+ ```jsonc
58
+ {
59
+ "id": "Google.Chrome", // winget id;注册表项 "reg:<key>";macOS "brew:<cask token>"
60
+ "name": "Google Chrome",
61
+ "version": "155.0.8059.85", // 已安装版本,未知时 ""
62
+ "available": "156.0.8078.48", // 可升级到的版本,无更新时 ""
63
+ "publisher": "Google LLC",
64
+ "source": "winget", // winget | msstore | registry | homebrew
65
+ "installLocation": "C:\\Program Files\\Google\\Chrome\\Application",
66
+ "hasUpdate": true,
67
+ "updateSupported": true, // 本插件能否驱动更新
68
+ "reason": null, // null | "not-in-catalog" | "unknown-version" | "pinned" | "store-managed" | "system-component"
69
+ "kind": "exe" // exe | msi | msix | inno | cask | null(未知)
70
+ }
71
+ ```
72
+
73
+ ### POST /dsh-app-updater/scan
74
+
75
+ body `{ "confirm": true, "force": true }` → `{ "ok": true, "started": true }`(异步;结果看 state.json / apps.json)
76
+
77
+ ### GET /dsh-app-updater/apps.json
78
+
79
+ `{ "ok": true, "scannedAt": 0, "stats": {...}, "items": [App...], "warnings": ["..."] }`
80
+
81
+ ### POST /dsh-app-updater/update
82
+
83
+ body `{ "confirm": true, "ids": ["Google.Chrome", "..."], "mode": "silent" }`
84
+ → `{ "ok": true, "started": true, "queued": 3 }`(串行执行,逐条下载官网全量包 → 校验 → 静默安装)
85
+
86
+ ### POST /dsh-app-updater/cancel
87
+
88
+ → `{ "ok": true }`(取消尚未开始的项目;正在安装的进程会收到终止请求)
89
+
90
+ ### POST /dsh-app-updater/settings
91
+
92
+ body `{ "confirm": true, "settings": { ... } }` → `{ "ok": true, "settings": {...} }`
93
+
94
+ ### POST /dsh-app-updater/open-downloads
95
+
96
+ → `{ "ok": true, "path": "..." }`(在资源管理器中打开下载目录)
97
+
98
+ ## 客户端要求
99
+
100
+ - `window.__ModuleLoader__.load({ id: "dsh-app-updater", factory: (require) => { ... } })`
101
+ - `exports.inject = ["slots", "locale"]`
102
+ - 注册 `settings.section`:`{ name: "settings.section", id: "dsh-app-updater", order: 42, label: () => "应用更新" }`
103
+ - 只用 `require("react")`;不引入其它运行时依赖
104
+ - 页面:顶部工具条(刷新检测 / 全部更新 / 取消 / 打开下载目录)+ 统计条 + 可筛选可多选的表格 + 底部日志
105
+ - 表格列:勾选 / 应用名 / 已装版本 / 可升级到 / 来源 / 状态 / 操作
106
+ - 所有请求带 `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,207 @@
1
+ # dsh-app-updater — App Updater for DeepSeek Harness
2
+
3
+ **English** | [简体中文](README.md)
4
+ [![npm](https://img.shields.io/npm/v/dsh-app-updater?style=flat-square&label=npm&color=0891b2&logo=npm)](https://www.npmjs.com/package/dsh-app-updater)
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 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
+ Windows (winget) and macOS (Homebrew), architecture-aware: ARM64 and x64/Intel installers are
14
+ chosen to match the machine.
15
+
16
+ Where to find it: DeepSeek Harness → Settings → **应用更新 (App Updater)**
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ # npm package (recommended; also one-click installable from the plugin center)
22
+ dsh plugin --profile web add dsh-app-updater
23
+ ```
24
+
25
+ Without npm, install from source or from the prebuilt tarball:
26
+
27
+ ```bash
28
+ # source repository
29
+ dsh plugin --profile web add github:19952730389/dsh-app-updater
30
+
31
+ # prebuilt tgz (see Releases)
32
+ dsh plugin --profile web add https://github.com/19952730389/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
33
+ ```
34
+
35
+ Restart DSH afterwards; the plugin lives at **Settings → 应用更新**. The Electron-managed
36
+ `desktop` profile does not accept `dsh plugin` writes, so link the package into that
37
+ profile's `node_modules` and add it to `dsh.profile.bundles` manually.
38
+
39
+ ---
40
+
41
+ ## What it actually does
42
+
43
+ | Step | How |
44
+ |---|---|
45
+ | 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. |
46
+ | 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. |
47
+ | 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`. |
48
+ | 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. |
49
+
50
+ Those download URLs really are the vendors' own: `github.com/git-for-windows/…`,
51
+ `dl.google.com/dl/chrome/install/…`, `download.cpuid.com/cpu-z/…`, `dl.hdslb.com/…`.
52
+ Both the installer and its `.yaml` manifest are kept in the download folder, so the
53
+ `InstallerUrl`, `InstallerSha256` and silent switches can be audited afterwards.
54
+
55
+ ---
56
+
57
+ ## Usage
58
+
59
+ 1. Open **Settings → 应用更新**.
60
+ 2. Click **刷新检测** (nothing runs in the background; scanning only happens when you ask).
61
+ 3. The table sorts updateable apps first. Use the search box and the
62
+ **全部 / 仅可更新 / 未收录** segments to narrow it down.
63
+ 4. Tick the apps to update (or press **全部更新**), or update a single app from its row.
64
+ 5. While updating you get a progress bar, the current app name and a per-item log;
65
+ **取消** stops the items that have not started yet.
66
+ 6. **打开下载目录** reveals the downloaded vendor installers in Explorer.
67
+
68
+ ### Status values
69
+
70
+ | Status | Meaning |
71
+ |---|---|
72
+ | **有新版本** | The catalog reports a higher version; one-click update is available. |
73
+ | **已是最新** | No newer version in the catalog. |
74
+ | **未收录** | An update exists in the catalog but this plugin cannot drive it (see reasons below). |
75
+
76
+ | Reason | Meaning |
77
+ |---|---|
78
+ | 软件目录未收录 | The app is not in the winget catalog (registry-only software), so its official update cannot be discovered or downloaded. |
79
+ | 由应用商店管理 | MSIX / Store app — update it through Microsoft Store. |
80
+ | 版本未知 | The installed version cannot be read (winget reports `Unknown`); updating is still allowed. |
81
+ | 系统组件 | System component / runtime; not driven by this plugin by default. |
82
+
83
+ ---
84
+
85
+ ## Settings
86
+
87
+ State file: `%USERPROFILE%\.dsh\dsh-app-updater\state.json`
88
+
89
+ | Key | Default | Meaning |
90
+ |---|---|---|
91
+ | `includeStoreApps` | `true` | List Store (MSIX) apps too (display only). |
92
+ | `includeRegistryOnly` | `true` | Fill in apps missing from the winget catalog using the registry (display only). |
93
+ | `downloadDir` | empty | Custom download folder; empty = `%USERPROFILE%\Downloads\DSH App Updater`. Must be absolute. |
94
+ | `keepInstallers` | `true` | Keep the installer after a successful update. |
95
+ | `hashFallbackUpgrade` | `true` | Fall back to `winget upgrade` when the download fails. |
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
+
99
+ Everything above is editable from the plugin's own settings panel inside the settings page.
100
+ The panel also accepts API calls:
101
+
102
+ ```bash
103
+ curl -X POST -H "Content-Type: application/json" \
104
+ -d '{"confirm":true,"settings":{"dryRun":true}}' \
105
+ http://127.0.0.1:<port>/dsh-app-updater/settings
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Safety boundaries
111
+
112
+ - **Every write requires loopback + explicit confirmation**: `scan` / `update` / `cancel` /
113
+ `settings` / `open-downloads` accept only requests from loopback with `confirm:true`.
114
+ - **No guessed silent switches**: the publisher's declared `Silent` switches are preferred;
115
+ generic defaults are used only when a manifest is unavailable.
116
+ - **No silent elevation**: installers that need administrator rights show the normal UAC prompt.
117
+ - **Version is verified after installing** and recorded in the log.
118
+ - **System components** (`.NET`, VC++ runtimes, …) are listed but not updated by default.
119
+ - **Only its own directory is written**: state and logs live in `~/.dsh/dsh-app-updater/`.
120
+
121
+ ## Requirements
122
+
123
+ - **Windows**: `winget.exe` (Microsoft App Installer). Without winget the plugin can still
124
+ list registry apps, but cannot tell whether they are up to date.
125
+ - **macOS**: `brew` ([Homebrew](https://brew.sh)). Without Homebrew the plugin reports the engine
126
+ as unavailable instead of pretending it can update anything.
127
+ - No third-party npm dependencies: the host half uses Node built-ins only, the browser half
128
+ only the host-provided `react`.
129
+
130
+ ## Supported systems and architectures
131
+
132
+ The plugin detects its own environment at startup and picks the installer that matches it:
133
+
134
+ | System | Architecture | Engine | Notes |
135
+ |---|---|---|---|
136
+ | 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 |
137
+ | macOS 11+ | Apple Silicon / Intel | **Homebrew casks** | Homebrew itself resolves the right cask for the machine; without Homebrew the engine is reported unavailable |
138
+ | anything else | — | none | the scan/update routes answer 400 with the reason |
139
+
140
+ The result is shown in the status line and in the settings panel's “运行环境 / Environment” row, e.g.
141
+ `Windows 11(build 26300) · x64(AMD64) · winget 1.29.380` or
142
+ `macOS 15(Sequoia) · Apple Silicon(ARM64) · Homebrew 4.5.0`.
143
+
144
+ The “按本机架构安装包 / match system architecture” switch (on by default) can be turned off to hand
145
+ the choice back to winget; Homebrew always resolves for the local architecture.
146
+
147
+ > The macOS/Homebrew path follows Homebrew's documented CLI behaviour and its detection and parsing
148
+ > logic are covered by unit tests, but it has not been verified on a real Mac — this plugin is
149
+ > developed on Windows. Please open an issue if something misbehaves there.
150
+
151
+ ## HTTP API (host half)
152
+
153
+ | Method | Path | Purpose |
154
+ |---|---|---|
155
+ | GET | `/dsh-app-updater/state.json` | Status, inventory, log, settings |
156
+ | GET | `/dsh-app-updater/apps.json` | Inventory and statistics only |
157
+ | GET | `/dsh-app-updater/health.json` | Self-check: version, environment, engine, capabilities, route count, data directory |
158
+ | POST | `/dsh-app-updater/scan` | `{confirm:true, force:true}` start scanning |
159
+ | POST | `/dsh-app-updater/update` | `{confirm:true, ids:[...]}` start the update queue |
160
+ | POST | `/dsh-app-updater/cancel` | `{confirm:true}` cancel items not yet started |
161
+ | POST | `/dsh-app-updater/settings` | `{confirm:true, settings:{...}}` |
162
+ | POST | `/dsh-app-updater/open-downloads` | `{confirm:true}` open the download folder |
163
+
164
+ ## Install (from a checkout)
165
+
166
+ ```bash
167
+ # from a local checkout
168
+ dsh plugin --profile <profile> add file:/path/to/dsh-app-updater
169
+
170
+ # or as a local link
171
+ dsh plugin --profile <profile> add link:/path/to/dsh-app-updater
172
+ ```
173
+
174
+ Then add `dsh-app-updater` to `dsh.profile.bundles` in the profile's `package.json`
175
+ (the bundled `cordis.patch.yml` mounts the host half) and reload DSH.
176
+
177
+ ## Uninstall
178
+
179
+ 1. Remove `dsh-app-updater` from `dsh.profile.bundles` and from `dependencies` in the profile's `package.json`.
180
+ 2. Delete `<profile>/node_modules/dsh-app-updater`.
181
+ 3. Restart DSH; the data directory `%USERPROFILE%\.dsh\dsh-app-updater` can be removed as well.
182
+
183
+ ## Tests
184
+
185
+ ```bash
186
+ node test/unit.mjs # 66 checks: table parsing, version compare, manifest & switches, OS/arch detection, Homebrew parsing
187
+ node test/render.mjs # 32 checks: settings panel, rehearsal banner, column drag, request bodies
188
+ node test/harness.mjs --scan # boots the host half against a fake cordis ctx + real HTTP
189
+ ```
190
+
191
+ ## Layout
192
+
193
+ | File | Purpose |
194
+ |---|---|
195
+ | `lib/index.js` | Host half: HTTP routes, write gate, scan/update scheduling |
196
+ | `lib/engine.js` | Inventory merge, vendor installer download, silent install, winget fallback, version re-check |
197
+ | `lib/winget.js` | winget discovery/invocation, fixed-width table parsing (CJK-aware) |
198
+ | `lib/registry.js` | Registry `Uninstall` enumeration (PowerShell, UTF-8) |
199
+ | `lib/version.js` | Windows-style version comparison |
200
+ | `lib/state.js` | Settings and state persistence (atomic writes) |
201
+ | `lib/proc.js` | Child process runner (timeouts, cancellation, UTF-8) |
202
+ | `lib/client.js` | Browser half: the settings page UI |
203
+ | `CONTRACT.md` | Host ↔ client interface contract |
204
+
205
+ ## License
206
+
207
+ MIT
package/README.md CHANGED
@@ -1,3 +1,210 @@
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
+ 检测本机**所有已安装应用**是否有新版本,并从各自**官网下载完整安装包**一键静默更新。
4
+ 检测过程**不需要打开被检测的软件**。
5
+ 自动识别 **Windows / macOS** 与 **ARM64 / x64(Intel)** 架构,按当前系统与芯片挑选对应的安装包。
6
+
7
+ 入口:DeepSeek Harness → 设置 → **应用更新**
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ # npm 包(推荐,插件中心同样可一键安装)
13
+ dsh plugin --profile web add dsh-app-updater
14
+ ```
15
+
16
+ 不想走 npm,也可以用源码或发行包:
17
+
18
+ ```bash
19
+ # 源码仓库
20
+ dsh plugin --profile web add github:19952730389/dsh-app-updater
21
+
22
+ # 预构建 tgz(见 Releases)
23
+ dsh plugin --profile web add https://github.com/19952730389/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
24
+ ```
25
+
26
+ 装好后重启 DSH,在 **设置 → 应用更新** 使用。桌面端(Electron 托管的 `desktop` profile)不允许
27
+ `dsh plugin` 直接改写,需在 profile 的 `node_modules` 下建 junction 并手动补 `bundles`。
28
+
29
+ [![npm](https://img.shields.io/npm/v/dsh-app-updater?style=flat-square&label=npm&color=0891b2&logo=npm)](https://www.npmjs.com/package/dsh-app-updater)
30
+ ![license](https://img.shields.io/badge/license-MIT-2f6f4f?style=flat-square)
31
+ ![platform](https://img.shields.io/badge/platform-Windows-7da1de?style=flat-square)
32
+ ![dsh-plugin](https://img.shields.io/badge/DSH-plugin-263146?style=flat-square)
33
+
34
+ **简体中文** · [English](README.en.md) · [更新日志](CHANGELOG.md) · MIT
35
+
36
+ ---
37
+
38
+ ## 它到底做了什么
39
+
40
+ | 步骤 | 实现 |
41
+ |---|---|
42
+ | ① 列出已装应用 | `winget list`(覆盖 winget 目录收录的软件 + 商店/MSIX 包)+ Windows 注册表三处 `Uninstall` 键(覆盖 winget 未收录的软件)。全程只读文件与注册表,不启动任何应用。 |
43
+ | ② 判断有没有新版本 | `winget upgrade --include-unknown`:由 winget 官方软件目录给出「已装版本 → 可升级版本」,并与 ①的清单按 Id 合并。 |
44
+ | ③ 一键更新 | `winget download` 把**发布方官网的完整安装包**下载到你指定的目录(下载时按清单核对 SHA-256),解析随包落盘的 winget 清单得到发布方自己的静默参数,再执行静默安装;安装完成后用 `winget list --id` 复核版本号。 |
45
+ | ④ 失败兜底 | 若官网安装包被发布方重新签发导致哈希与目录记录不一致、或清单禁止直接下载,自动退回 `winget upgrade --id <id> --exact --silent`(同样是从官网拉取完整安装包,只是下载与安装过程交给 winget)。 |
46
+
47
+ **「从对应官网下载」是真的官网**:安装包地址来自 winget 清单里的 `InstallerUrl`,例如
48
+ - Git → `https://github.com/git-for-windows/git/releases/download/...`
49
+ - CPU-Z → `https://download.cpuid.com/cpu-z/...`
50
+ - 哔哩哔哩 → `https://dl.hdslb.com/...`
51
+ - Chrome → `https://dl.google.com/dl/chrome/install/...`
52
+
53
+ 下载目录(默认 `%USERPROFILE%\Downloads\DSH App Updater`)里会同时留一份 `.yaml` 清单,
54
+ 里面有发布方声明的 `InstallerUrl` / `InstallerSha256` / `InstallerSwitches.Silent`,可以自行核对来源。
55
+
56
+ ---
57
+
58
+ ## 使用
59
+
60
+ 1. 打开 **设置 → 应用更新**。
61
+ 2. 点 **刷新检测**(不会自动跑后台任务;只有你点了才扫)。
62
+ 3. 列表默认按「有新版本优先」排序,可用搜索框和 **全部 / 仅可更新 / 未收录** 分段筛选。
63
+ 4. 勾选要更新的应用(或点 **全部更新** 一次处理当前筛选下所有可更新项),点行尾 **更新** 也可单个更新。
64
+ 5. 更新期间有进度条、当前应用名与逐条日志;**取消** 会停止尚未开始的项目。
65
+ 6. **打开下载目录** 可以在资源管理器中查看已下载的官网安装包。
66
+ 7. 同一页的 **插件设置** 面板里可以改下载目录、保留安装包、演练模式等开关(见「设置项」)。
67
+ 8. 表格列宽默认给足以完整显示应用名;**拖动表头右侧的分隔条** 可以调整该列宽度(双击恢复默认),
68
+ 偏好记在浏览器本地,下次打开仍是你的宽度。
69
+
70
+ ### 状态含义
71
+
72
+ | 状态 | 含义 |
73
+ |---|---|
74
+ | **有新版本** | 软件目录给出更高的版本号,可一键更新。 |
75
+ | **已是最新** | 目录里没有更高版本。 |
76
+ | **未收录** | 目录里找到了更新,但本插件无法驱动它更新(见下)。 |
77
+
78
+ | 原因 | 含义 |
79
+ |---|---|
80
+ | 软件目录未收录 | 该软件不在 winget 目录里(例如只在注册表登记的国产软件),无法判断/下载官网更新。 |
81
+ | 由应用商店管理 | MSIX / 商店应用,请用 Microsoft Store 更新。 |
82
+ | 版本未知 | 应用当前版本读不出来(winget 显示 `Unknown`),仍然允许尝试更新。 |
83
+ | 系统组件 | 系统组件/运行时,默认不建议由第三方驱动更新。 |
84
+
85
+ ---
86
+
87
+ ## 设置项
88
+
89
+ 状态文件:`%USERPROFILE%\.dsh\dsh-app-updater\state.json`
90
+
91
+ | 键 | 默认 | 说明 |
92
+ |---|---|---|
93
+ | `includeStoreApps` | `true` | 是否把商店(MSIX)应用列进来(只展示,不会被更新)。 |
94
+ | `includeRegistryOnly` | `true` | 是否用注册表补齐 winget 未收录的应用(只展示,不能更新)。 |
95
+ | `downloadDir` | 空 | 自定义安装包下载目录;空 = `%USERPROFILE%\Downloads\DSH App Updater`。 |
96
+ | `keepInstallers` | `true` | 更新后保留安装包;`false` 则在安装完成后删掉该应用刚下载的包。 |
97
+ | `hashFallbackUpgrade` | `true` | 下载失败时退回 `winget upgrade` 兜底。 |
98
+ | `dryRun` | `false` | **演练模式**:只下载官网全量安装包、不执行安装。用来先看会装什么。 |
99
+ | `matchSystemArch` | `true` | 按识别到的系统与芯片架构挑选安装包(Windows 传 `--architecture`;macOS 由 Homebrew 决定)。 |
100
+
101
+ 用 API 改设置(等价于设置面板,适合脚本化):
102
+
103
+ ```bash
104
+ curl -X POST -H "Content-Type: application/json" \
105
+ -d '{"confirm":true,"settings":{"dryRun":true}}' \
106
+ http://127.0.0.1:<port>/dsh-app-updater/settings
107
+ ```
108
+
109
+ `downloadDir` 会被校验:必须留空(用默认目录)或是绝对路径(`D:\Apps`)/UNC 路径(`\\server\share`),
110
+ 否则返回 `400` 并在面板里显示红字提示,不会把安装包写到意料之外的位置。
111
+
112
+ ---
113
+
114
+ ## 安全边界
115
+
116
+ - **写操作全部要求本机来源 + 显式确认**:`scan` / `update` / `cancel` / `settings` / `open-downloads` 只接受 loopback 请求且必须带 `confirm:true`。
117
+ - **不猜测静默参数**:优先用发布方在清单里声明的 `Silent` 参数;清单缺失时才按安装包类型用保守的默认开关。
118
+ - **不静默提权**:需要管理员权限的安装包会正常弹出 UAC,由你决定是否放行。
119
+ - **安装后复核版本**:更新完会重新查询一次版本并记录,日志里能看到「更新完成,当前版本 X」。
120
+ - **不做系统组件更新**:`.NET` 运行时、VC++ 运行库等仍会列出来,但默认不作为一键更新的目标。
121
+ - **只写自己的目录**:状态与日志写在 `~/.dsh/dsh-app-updater/`,不碰 profile 与宿主配置。
122
+
123
+ ---
124
+
125
+ ## 依赖
126
+
127
+ - **Windows**:`winget.exe`(Microsoft「应用安装程序 / App Installer」)。没装 winget 时插件仍能列出注册表里的应用,但无法判断是否有新版本。
128
+ - **macOS**:`brew`([Homebrew](https://brew.sh))。没装 Homebrew 时明确报告引擎不可用,不会假装能更新。
129
+ - 不需要任何第三方 npm 依赖;宿主半身只用 Node 内置模块,浏览器半身只用宿主提供的 `react`。
130
+
131
+ ## 接口(宿主半身)
132
+
133
+ | 方法 | 路径 | 说明 |
134
+ |---|---|---|
135
+ | GET | `/dsh-app-updater/state.json` | 当前状态、应用清单、日志、设置 |
136
+ | GET | `/dsh-app-updater/apps.json` | 只取应用清单与统计 |
137
+ | GET | `/dsh-app-updater/health.json` | 自检:版本、运行环境、引擎与能力、路由数、数据目录 |
138
+ | POST | `/dsh-app-updater/scan` | `{confirm:true, force:true}` 开始扫描 |
139
+ | POST | `/dsh-app-updater/update` | `{confirm:true, ids:[...]}` 开始更新队列 |
140
+ | POST | `/dsh-app-updater/cancel` | `{confirm:true}` 取消未开始项 |
141
+ | POST | `/dsh-app-updater/settings` | `{confirm:true, settings:{...}}` |
142
+ | POST | `/dsh-app-updater/open-downloads` | `{confirm:true}` 打开下载目录 |
143
+
144
+ ## 安装(开发)
145
+
146
+ ```bash
147
+ # 本地目录安装
148
+ dsh plugin --profile <profile> add file:/path/to/dsh-app-updater
149
+
150
+ # 或者用链接(开发时改代码即时生效)
151
+ dsh plugin --profile <profile> add link:/path/to/dsh-app-updater
152
+ ```
153
+
154
+ 装好后把 `dsh-app-updater` 加进 profile `package.json` 的 `dsh.profile.bundles` 清单
155
+ (包内自带的 `cordis.patch.yml` 会挂载宿主半身),然后重启 DSH 即可在设置页看到入口。
156
+
157
+ > 桌面端(Electron 托管)的 `desktop` profile 不允许用 `dsh plugin` 直接改写,
158
+ > 需要在 profile 的 `node_modules` 下建立指向本包的 junction 并手动补 `bundles` 与依赖。
159
+
160
+ ## 测试
161
+
162
+ ```bash
163
+ node test/unit.mjs # 66 项:定宽表格解析、版本比较、清单与静默参数、系统/架构识别、Homebrew 解析
164
+ node test/render.mjs # 32 项:设置面板、演练提示、列宽拖拽、请求体
165
+ node test/harness.mjs --scan # 用假 cordis ctx + 真 HTTP 把宿主半身跑起来
166
+ ```
167
+
168
+ ## 支持的系统与架构
169
+
170
+ 插件启动时会识别本机环境,并据此挑选对应架构的安装包:
171
+
172
+ | 系统 | 架构 | 更新引擎 | 说明 |
173
+ |---|---|---|---|
174
+ | Windows 10 / 11 | x64 / ARM64 / x86 | **winget** | 把识别到的架构传给 `winget --architecture`;清单没有该架构的包时自动退回自动匹配并记录日志 |
175
+ | macOS 11+ | Apple Silicon / Intel | **Homebrew cask** | Homebrew 自己按本机架构取 cask;没有 Homebrew 时明确报告引擎不可用 |
176
+ | 其它 | — | 无 | 扫描/更新接口返回 400 并说明原因 |
177
+
178
+ 识别结果会显示在状态行与设置面板的「运行环境」里,例如
179
+ `Windows 11(build 26300) · x64(AMD64) · winget 1.29.380` 或
180
+ `macOS 15(Sequoia) · Apple Silicon(ARM64) · Homebrew 4.5.0`。
181
+
182
+ 还可以在设置面板里关掉「按本机架构安装包」——关闭后把选择权交回 winget 自己(Homebrew 一直按本机架构)。
183
+
184
+ > macOS 的 Homebrew 路径是按 Homebrew 官方 CLI 行为实现的,识别与解析逻辑有单测覆盖;
185
+ > 但本插件主要在 Windows 上开发,macOS 上的安装/更新没有真机验证过,遇到问题请开 Issue。
186
+
187
+ ## 卸载
188
+
189
+ 1. 从 profile 的 `package.json` 里删掉 `dsh.profile.bundles` 中的 `dsh-app-updater` 与 `dependencies` 中的对应条目。
190
+ 2. 删除 `%USERPROFILE%\.dsh\profiles\<profile>\node_modules\dsh-app-updater`。
191
+ 3. 重启 DSH;数据目录 `%USERPROFILE%\.dsh\dsh-app-updater` 可自行删除。
192
+
193
+ ## 文件
194
+
195
+ | 文件 | 作用 |
196
+ |---|---|
197
+ | `lib/index.js` | 宿主半身:HTTP 路由、写操作闸门、扫描/更新任务调度 |
198
+ | `lib/engine.js` | 扫描合并、官网包下载、静默安装、winget 兜底、版本复核 |
199
+ | `lib/winget.js` | winget 定位与调用、定宽表格解析(CJK 宽度感知) |
200
+ | `lib/registry.js` | 注册表 `Uninstall` 键枚举(PowerShell,UTF-8) |
201
+ | `lib/version.js` | Windows 风格版本比较 |
202
+ | `lib/state.js` | 状态与设置持久化(原子写) |
203
+ | `lib/proc.js` | 子进程执行(超时/取消/UTF-8) |
204
+ | `lib/client.js` | 浏览器半身:设置页 UI(设置面板、可拖拽列宽、进度与日志) |
205
+ | `CONTRACT.md` | 宿主 ↔ 客户端接口契约 |
206
+ | `test/unit.mjs` | 解析器 / 版本 / 清单单测(36 项) |
207
+ | `test/render.mjs` | 前端渲染与交互自测(32 项) |
208
+ | `test/harness.mjs` | 宿主路由端到端 harness(假 ctx + 真 HTTP) |
209
+ | `icon.svg` | 插件图标 |
210
+ | `LICENSE` `CHANGELOG.md` `README.en.md` | MIT 许可证、更新日志、英文说明 |