dsh-plugin-upgrader 1.0.2 → 1.1.1
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 +134 -0
- package/README.en.md +29 -3
- package/README.md +24 -3
- package/lib/client.js +178 -26
- package/lib/host/install.d.ts +11 -1
- package/lib/host/install.d.ts.map +1 -1
- package/lib/host/install.js +189 -6
- package/lib/host/install.js.map +1 -1
- package/lib/host/manager.d.ts +35 -0
- package/lib/host/manager.d.ts.map +1 -1
- package/lib/host/manager.js.map +1 -1
- package/lib/host/overview.d.ts.map +1 -1
- package/lib/host/overview.js +68 -5
- package/lib/host/overview.js.map +1 -1
- package/lib/host/progress.d.ts +28 -0
- package/lib/host/progress.d.ts.map +1 -0
- package/lib/host/progress.js +21 -0
- package/lib/host/progress.js.map +1 -0
- package/lib/host/registry.d.ts +48 -1
- package/lib/host/registry.d.ts.map +1 -1
- package/lib/host/registry.js +124 -22
- package/lib/host/registry.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +174 -30
- package/lib/index.js.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,139 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.1 — 2026-10-27
|
|
4
|
+
|
|
5
|
+
- **The row no longer offers to switch the running version to itself.** A
|
|
6
|
+
path-installed bundle asked the registry for its version list but did not pass
|
|
7
|
+
its installed version along, so no row was marked as the one loaded. The row
|
|
8
|
+
the user is already on then drew a live "back to latest" button that pointed at
|
|
9
|
+
that same version. Two fixes: the installed version is passed through for path
|
|
10
|
+
installs too, and the button label no longer keys on "this is the latest
|
|
11
|
+
release" alone — a row that is both current and latest reads "switch", disabled.
|
|
12
|
+
|
|
13
|
+
## 1.1.0 — 2026-10-27
|
|
14
|
+
|
|
15
|
+
The first published release since 1.0.2: the `1.0.3` entries below shipped in
|
|
16
|
+
this version, together with everything listed here.
|
|
17
|
+
|
|
18
|
+
- **Upgrading a theme plugin no longer leaves the page unstyled.** Replacing an
|
|
19
|
+
appearance plugin (`dsh-dream-skin`, `*-skin-*`, `dsh-client-ui-theme`) unloads
|
|
20
|
+
the code that injected its stylesheet, so the page snaps back to the host's
|
|
21
|
+
default look. The settings are NOT lost — `dsh-dream-skin` keeps them in
|
|
22
|
+
`$DSH_HOME/dream-skin.json`, outside the profile and written atomically, which
|
|
23
|
+
is why that file survives an upgrade untouched — but the injected CSS and the
|
|
24
|
+
`data-*` attributes on `<html>` lived only in the page, and the restarted
|
|
25
|
+
plugin has no way to put them back without being asked. A batch that actually
|
|
26
|
+
replaced such a plugin now says so and reloads the page once, after the plugin
|
|
27
|
+
has restarted, so the look comes back from that file. The reload is skipped for
|
|
28
|
+
failed steps, for steps that changed nothing, and for every ordinary plugin — a
|
|
29
|
+
failed upgrade must not also throw the page away.
|
|
30
|
+
- **The upgrader's own row explains itself, and can update itself.** Both halves
|
|
31
|
+
of "这个插件没法管理自己" were real, and they are different problems:
|
|
32
|
+
- *The row was blank.* Installing as `link:`/`file:` made the overview skip the
|
|
33
|
+
registry lookup entirely, so the row showed a version, an arrow with nothing
|
|
34
|
+
after it, and no history. A path install now still reads the published list
|
|
35
|
+
(the directory keeps its own version, which is what is actually loaded) and
|
|
36
|
+
says so, instead of looking like a package that was never published. It stays
|
|
37
|
+
non-upgradable: replacing a `link:` with a registry tarball is a different
|
|
38
|
+
install, not an upgrade.
|
|
39
|
+
- *It could not update itself.* A version change through the batch job starts
|
|
40
|
+
with `removeBundle`, and removing this bundle unloads the very code that
|
|
41
|
+
would run the install that follows — the batch dies with the plugin that was
|
|
42
|
+
driving it, leaving the profile without it. The row now offers the page's own
|
|
43
|
+
installer instead (`plugins.tab` owner prop `install`), which is owned by the
|
|
44
|
+
plugin manager and therefore survives the unload. The batch and "Select all"
|
|
45
|
+
skip this row for the same reason, so one click can no longer strand the
|
|
46
|
+
profile.
|
|
47
|
+
- **The version list no longer silently disappears.** Several plugins drew no
|
|
48
|
+
history button and no explanation, which looked like "this package has no
|
|
49
|
+
published versions" — it was a failed registry lookup being swallowed. Three
|
|
50
|
+
separate causes, all measured against `registry.npmmirror.com`:
|
|
51
|
+
- **The tab asked for every package at once.** Eleven concurrent connections
|
|
52
|
+
timed out at the 10 s deadline while the same eleven asked one at a time
|
|
53
|
+
answered in under 1.2 s. Lookups now go through a four-at-a-time cap.
|
|
54
|
+
- **The documents are large.** `billion-context` is a 26 MB packument and
|
|
55
|
+
10 MB even abbreviated, so a healthy request could lose a 10 s race on its
|
|
56
|
+
own. Lookups now ask for npm's abbreviated form
|
|
57
|
+
(`application/vnd.npm.install-v1+json`, which keeps `time`, `engines` and
|
|
58
|
+
`deprecated`), the deadline moved to 20 s, and a transport failure is
|
|
59
|
+
retried once — a `UND_ERR_CONNECT_TIMEOUT` is socket contention, not a
|
|
60
|
+
verdict about the package.
|
|
61
|
+
- **The failure was invisible.** A row whose lookup failed is now drawn with
|
|
62
|
+
the reason (`版本列表读取失败,点「检查更新」可重试`) instead of looking
|
|
63
|
+
like a package with no history. A failed lookup is also cached for a shorter
|
|
64
|
+
window than a successful one, so one bad render does not make the next
|
|
65
|
+
mount slow too.
|
|
66
|
+
- **A mirror's stale `latest` tag no longer turns an upgrade into a downgrade.**
|
|
67
|
+
npmmirror serves `@deepseek-ai/dsh-subagent-codex` with `latest: 0.0.1-rc.1`
|
|
68
|
+
while the profile runs `0.2.1-rc.3`, so the row claimed the installed build
|
|
69
|
+
was newer than latest and offered a downgrade as an upgrade. A tag that sorts
|
|
70
|
+
below the installed version is now treated as broken and recovered from the
|
|
71
|
+
published list, preferring a stable release over a prerelease. A prerelease
|
|
72
|
+
that merely sorts above a healthy tag still does not steal `latest`.
|
|
73
|
+
- **An upgrade no longer dies on `bundle-in-use`.** Replacing a version means
|
|
74
|
+
remove + install, and `removeBundle` only unloads a bundle it reads as enabled.
|
|
75
|
+
A plugin that is disabled but still holds a live loader fiber therefore went
|
|
76
|
+
straight to the "in use" check and was refused, which made every upgrade of a
|
|
77
|
+
disabled plugin fail. The removal now clears that fiber and retries once.
|
|
78
|
+
- **The unload asks the right question.** 1.0.3's follow-up assumed
|
|
79
|
+
`setBundleEnabled(name, false)` reloads unconditionally, so it would dispose the
|
|
80
|
+
fiber. It does not: `selectBundle` returns early when the bundle is already
|
|
81
|
+
deselected, so the manifest does not change, so `reconcileProfile` has no
|
|
82
|
+
dropped row to dispose and the fiber survives — the retry then failed for
|
|
83
|
+
exactly the same reason. The removal now reads `enabled` first and, for a
|
|
84
|
+
bundle the manager already reads as disabled, re-selects it and deselects it
|
|
85
|
+
again, which is the only form of the toggle that produces a change a live
|
|
86
|
+
process can act on.
|
|
87
|
+
- **A refusal the host cannot fix says so.** A second `bundle-in-use` now comes
|
|
88
|
+
back with the manager's code *and* a bilingual diagnostic ("the loader still
|
|
89
|
+
holds its plugin row; restart DSH and upgrade again"), and the tab shows the
|
|
90
|
+
diagnostic instead of the bare code. Retrying in the same process cannot clear
|
|
91
|
+
it, so the panel has to name the one action that does. The re-select is also
|
|
92
|
+
undone on the way out, so a failed upgrade never leaves the plugin enabled.
|
|
93
|
+
- **The primary button follows the theme.** Its label colour comes from
|
|
94
|
+
`--dsw-alias-label-primary-inverted` instead of a hard-coded white, so it stays
|
|
95
|
+
readable when the host theme uses a light primary fill.
|
|
96
|
+
- **Tests**: `test/install.test.ts` covers the replacement path (the remove +
|
|
97
|
+
install fallback and its fresh request id, the unload retry, the
|
|
98
|
+
already-disabled re-select including a refused one, the enabled-state restore,
|
|
99
|
+
and the restart diagnostic on both refusal spellings); `test/registry.test.ts`
|
|
100
|
+
covers the abbreviated request, the single retry, the shorter failure cache and
|
|
101
|
+
the 404-is-not-contention rule; `test/overview.test.ts` fails if a page load
|
|
102
|
+
stops respecting the cap; `test/shape-packument.test.ts` pins the stale-tag
|
|
103
|
+
rule. `scripts/verify-overview-live.mjs` runs the built host half against the
|
|
104
|
+
real mirror.
|
|
105
|
+
|
|
106
|
+
## 1.0.3 — unreleased as its own version
|
|
107
|
+
|
|
108
|
+
These changes were staged for 1.0.3, which was never published on its own; they
|
|
109
|
+
first shipped in 1.1.0.
|
|
110
|
+
|
|
111
|
+
- **Upgrades report what pnpm is doing.** The host subscribes to
|
|
112
|
+
`plugin-manager/install-log` and `plugin-manager/install-state`, so pnpm's own
|
|
113
|
+
output ("Verifying lockfile against supply-chain policies", `ETIMEDOUT` retries,
|
|
114
|
+
registry fallbacks) now streams into the job log while the step runs, instead of
|
|
115
|
+
only surfacing after it ends. An upgrade that sits for five minutes on the mirror
|
|
116
|
+
retry ladder finally looks like what it is.
|
|
117
|
+
- **The progress bar is honest about long steps.** While a step is in flight the
|
|
118
|
+
panel shows a live elapsed clock, the phase (`installing` / `applying` /
|
|
119
|
+
`cancelling`), the registry attempt when a fallback is in progress, and an
|
|
120
|
+
indeterminate bar. `done/total` only moves when a step really finishes, which
|
|
121
|
+
previously left it pinned at 0% for minutes.
|
|
122
|
+
- **Cancel works at every stage.** The second install of a remove + install used to
|
|
123
|
+
pass `requestId: undefined`, so that whole stretch was uncancellable; each install
|
|
124
|
+
now mints its own id and the job reports it back. Cancelling also explains what
|
|
125
|
+
happened: settled already, past the point of no return, or stopping now.
|
|
126
|
+
- **The heartbeat is a timer (2 s) instead of a per-step write.** A single step over
|
|
127
|
+
the 15-minute stale window was declared abandoned mid-work, which both killed a
|
|
128
|
+
healthy job and let a second batch start against the same profile.
|
|
129
|
+
- **A failed install no longer tries to restore the enabled state** of a package it
|
|
130
|
+
never added, which used to report a second `operation-error` on top of the real
|
|
131
|
+
failure.
|
|
132
|
+
- **The panel no longer sticks after a batch settles.** `submitJob` resolves only
|
|
133
|
+
after the whole batch finished, and the tab wrote a fresh `queued` snapshot over
|
|
134
|
+
the result, leaving the spinner and the disabled buttons behind until the tab was
|
|
135
|
+
remounted.
|
|
136
|
+
|
|
3
137
|
## 1.0.1 — 2026-10-23
|
|
4
138
|
|
|
5
139
|
- **Rewritten in TypeScript.** The whole project is now typed; `pnpm run typecheck`
|
package/README.en.md
CHANGED
|
@@ -18,6 +18,9 @@ English · [中文](./README.md)
|
|
|
18
18
|
|
|
19
19
|
- **One-click upgrade** — bump every upgradable plugin to its newest version, with
|
|
20
20
|
progress, per-plugin results and failure reasons.
|
|
21
|
+
- **Progress you can actually see** — the raw pnpm output (registry retries, lockfile
|
|
22
|
+
verification), a live elapsed clock per step, an indeterminate bar while a step
|
|
23
|
+
runs, and a cancel button that works.
|
|
21
24
|
- **Multi-select upgrade** — tick any subset of plugins and upgrade only those.
|
|
22
25
|
- **Version history / rollback** — expand any plugin to see every published version
|
|
23
26
|
(with publish date and engine requirement) and switch to it in one click.
|
|
@@ -75,8 +78,8 @@ host half:
|
|
|
75
78
|
| --- | --- |
|
|
76
79
|
| `GET /api/plugin-upgrader/overview` | installed plugins plus their published versions (registry packument, 5-minute cache, 10-second timeout) |
|
|
77
80
|
| `POST /api/plugin-upgrader/job` | queue a batch of upgrade / switch steps, return a job id immediately |
|
|
78
|
-
| `GET /api/plugin-upgrader/job?id=…` | poll progress
|
|
79
|
-
| `POST /api/plugin-upgrader/cancel` | abort the step in flight |
|
|
81
|
+
| `GET /api/plugin-upgrader/job?id=…` | poll progress, current phase, elapsed time, per-plugin results and the live log |
|
|
82
|
+
| `POST /api/plugin-upgrader/cancel` | abort the step in flight, including the second install of a remove + install |
|
|
80
83
|
|
|
81
84
|
Every route is loopback-only: a batch installs packages, so a paired remote device
|
|
82
85
|
or a cross-site page must never reach it.
|
|
@@ -94,6 +97,29 @@ Removing a plugin drops its enabled / disabled state, so each step re-applies
|
|
|
94
97
|
`setBundleEnabled` with the state it had before the upgrade. A disabled plugin stays
|
|
95
98
|
disabled after being upgraded.
|
|
96
99
|
|
|
100
|
+
### Why an upgrade sometimes takes minutes
|
|
101
|
+
|
|
102
|
+
Not this plugin: pnpm. Before installing, pnpm 11 re-checks every lockfile entry
|
|
103
|
+
against the registry (`Verifying lockfile against supply-chain policies`), and a
|
|
104
|
+
mirror such as `registry.npmmirror.com` answers `ETIMEDOUT` for plenty of packages,
|
|
105
|
+
which starts a 10-second → 1-minute retry ladder. Upgrading an already-installed
|
|
106
|
+
package additionally costs two pnpm runs (remove, then install).
|
|
107
|
+
|
|
108
|
+
The old build showed nothing during that time and moved the bar once per completed
|
|
109
|
+
step, so it read as a hang. The host now subscribes to
|
|
110
|
+
`plugin-manager/install-log` and `plugin-manager/install-state` and streams pnpm's
|
|
111
|
+
own output, the phase and the retry count into the job log, while the panel shows an
|
|
112
|
+
elapsed clock and an indeterminate bar — a slow install looks like a retrying one
|
|
113
|
+
instead of a frozen 0%.
|
|
114
|
+
|
|
115
|
+
Two related bugs fixed with it:
|
|
116
|
+
|
|
117
|
+
- The heartbeat is a timer (2 s), no longer a write per completed step. A single step
|
|
118
|
+
lasting over 15 minutes used to be declared abandoned, cancelling a healthy job and
|
|
119
|
+
letting a second batch start on the same profile.
|
|
120
|
+
- The second install of a remove + install passed `requestId: undefined`, making that
|
|
121
|
+
stretch uncancellable. Every install now has its own id, so cancel works throughout.
|
|
122
|
+
|
|
97
123
|
### One batch at a time
|
|
98
124
|
|
|
99
125
|
The host runs one job at a time. A second submission gets `409 busy` with the id of
|
|
@@ -104,7 +130,7 @@ error:
|
|
|
104
130
|
the running batch
|
|
105
131
|
- `overview` carries an `active` field, so a freshly mounted tab reconnects to work
|
|
106
132
|
in progress
|
|
107
|
-
- a job
|
|
133
|
+
- a job re-stamps a heartbeat every 2 seconds; a job left behind by a host restart is
|
|
108
134
|
declared abandoned once the heartbeat goes stale, so it cannot block every later
|
|
109
135
|
upgrade with a permanent `busy`
|
|
110
136
|
|
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ DSH 自带的插件页能装、能卸、能看已安装列表,但缺两件事
|
|
|
15
15
|
## 功能
|
|
16
16
|
|
|
17
17
|
- **一键升级** —— 一次把全部可升级插件升到最新版,带进度、逐项结果和失败原因。
|
|
18
|
+
- **看得见的进度** —— 升级时直接显示 pnpm 的原始输出(含换源重试、lockfile 校验),
|
|
19
|
+
每步都有已用时计时和不确定态进度条,卡住时可以一键取消。
|
|
18
20
|
- **多选升级** —— 勾选任意几个插件,只升级选中的这些。
|
|
19
21
|
- **版本历史 / 回滚** —— 展开任意插件即可看到已发布的全部版本(含发布时间与引擎要求),点一下切到那个版本。
|
|
20
22
|
- **一键跳 GitHub** —— 每行的 `GH` 按钮打开插件仓库主页;点版本号直接进该版本的 GitHub 发布页。
|
|
@@ -67,8 +69,8 @@ dsh plugin --profile web add "$PWD"
|
|
|
67
69
|
| --- | --- |
|
|
68
70
|
| `GET /api/plugin-upgrader/overview` | 已安装插件 + 各插件已发布版本(registry packument,5 分钟缓存,10 秒超时) |
|
|
69
71
|
| `POST /api/plugin-upgrader/job` | 提交一批升级 / 切换步骤,立即返回 job id |
|
|
70
|
-
| `GET /api/plugin-upgrader/job?id=…` |
|
|
71
|
-
| `POST /api/plugin-upgrader/cancel` |
|
|
72
|
+
| `GET /api/plugin-upgrader/job?id=…` | 轮询进度、当前阶段、已用时、逐项结果与实时日志 |
|
|
73
|
+
| `POST /api/plugin-upgrader/cancel` | 中断当前步骤(含「先卸载再安装」的第二次安装) |
|
|
72
74
|
|
|
73
75
|
所有路由仅限本机回环访问:批量操作会安装软件包,配对设备或跨站页面一律拒绝。
|
|
74
76
|
|
|
@@ -83,6 +85,25 @@ dsh plugin --profile web add "$PWD"
|
|
|
83
85
|
卸载会丢掉插件原有的启用 / 禁用状态,因此每一步在安装完成后会按操作前的状态
|
|
84
86
|
重新 `setBundleEnabled`,禁用中的插件升级后仍然是禁用状态。
|
|
85
87
|
|
|
88
|
+
### 为什么升级有时候要好几分钟
|
|
89
|
+
|
|
90
|
+
不是这个插件慢,是 pnpm 慢。pnpm 11 在 `install` 前会拿锁文件里的每个条目去 registry
|
|
91
|
+
重新核对一次(`Verifying lockfile against supply-chain policies`),国内镜像
|
|
92
|
+
(`registry.npmmirror.com`)对不少包会 `ETIMEDOUT`,于是走 10 秒 → 1 分钟的重试阶梯。
|
|
93
|
+
一次「升级」如果目标是已安装的包,还是 remove + install 两条 pnpm 命令,两段各等一次。
|
|
94
|
+
|
|
95
|
+
旧版本在这里什么都不显示,进度条只在「整步完成」时动一格,所以看起来就是死住了。
|
|
96
|
+
现在宿主端订阅了 `plugin-manager/install-log` 和 `plugin-manager/install-state`,
|
|
97
|
+
把 pnpm 的原始输出、当前阶段、重试次数实时转进 job 日志,界面同时显示已用时和
|
|
98
|
+
不确定态进度条:慢的时候你能看见它在重试,而不是盯着一个不动的 0%。
|
|
99
|
+
|
|
100
|
+
顺带修掉的两个坑:
|
|
101
|
+
|
|
102
|
+
- 心跳改成定时器(2 秒),不再按「完成一步」写。单步超过 15 分钟以前会被误判成
|
|
103
|
+
「job 已放弃」,于是一个健康的任务被作废,还能再并进来一批。
|
|
104
|
+
- 「先卸载再安装」的第二次安装以前用 `requestId: undefined`,导致那一段无法取消;
|
|
105
|
+
现在每次安装都有自己的 id,取消在任何阶段都有效。
|
|
106
|
+
|
|
86
107
|
### 同一时刻只跑一批
|
|
87
108
|
|
|
88
109
|
宿主端一次只允许一个 job。重复提交会拿到 `409 busy`(带正在跑的那个 job id),
|
|
@@ -90,7 +111,7 @@ dsh plugin --profile web add "$PWD"
|
|
|
90
111
|
|
|
91
112
|
- 新开一个插件页标签、或在进度跑着时再点「一键升级」,都会看到正在进行的那一批
|
|
92
113
|
- `overview` 里带 `active` 字段,标签页一挂载就能接上正在跑的任务
|
|
93
|
-
- job
|
|
114
|
+
- job 每 2 秒刷一次心跳时间戳,宿主重启后遗留的「僵尸 job」会按心跳超时自动作废,
|
|
94
115
|
不会永久占着位置让之后所有升级都撞 `busy`
|
|
95
116
|
|
|
96
117
|
主进程重启会让正在跑的 job 中断,界面会把这种情况显示为失败而不是永远转圈。
|