dsh-plugin-upgrader 1.0.2 → 1.1.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 +124 -0
- package/README.en.md +29 -3
- package/README.md +24 -3
- package/lib/client.js +171 -25
- 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 +62 -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,129 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0 — 2026-10-27
|
|
4
|
+
|
|
5
|
+
The first published release since 1.0.2: the `1.0.3` entries below shipped in
|
|
6
|
+
this version, together with everything listed here.
|
|
7
|
+
|
|
8
|
+
- **Upgrading a theme plugin no longer leaves the page unstyled.** Replacing an
|
|
9
|
+
appearance plugin (`dsh-dream-skin`, `*-skin-*`, `dsh-client-ui-theme`) unloads
|
|
10
|
+
the code that injected its stylesheet, so the page snaps back to the host's
|
|
11
|
+
default look. The settings are NOT lost — `dsh-dream-skin` keeps them in
|
|
12
|
+
`$DSH_HOME/dream-skin.json`, outside the profile and written atomically, which
|
|
13
|
+
is why that file survives an upgrade untouched — but the injected CSS and the
|
|
14
|
+
`data-*` attributes on `<html>` lived only in the page, and the restarted
|
|
15
|
+
plugin has no way to put them back without being asked. A batch that actually
|
|
16
|
+
replaced such a plugin now says so and reloads the page once, after the plugin
|
|
17
|
+
has restarted, so the look comes back from that file. The reload is skipped for
|
|
18
|
+
failed steps, for steps that changed nothing, and for every ordinary plugin — a
|
|
19
|
+
failed upgrade must not also throw the page away.
|
|
20
|
+
- **The upgrader's own row explains itself, and can update itself.** Both halves
|
|
21
|
+
of "这个插件没法管理自己" were real, and they are different problems:
|
|
22
|
+
- *The row was blank.* Installing as `link:`/`file:` made the overview skip the
|
|
23
|
+
registry lookup entirely, so the row showed a version, an arrow with nothing
|
|
24
|
+
after it, and no history. A path install now still reads the published list
|
|
25
|
+
(the directory keeps its own version, which is what is actually loaded) and
|
|
26
|
+
says so, instead of looking like a package that was never published. It stays
|
|
27
|
+
non-upgradable: replacing a `link:` with a registry tarball is a different
|
|
28
|
+
install, not an upgrade.
|
|
29
|
+
- *It could not update itself.* A version change through the batch job starts
|
|
30
|
+
with `removeBundle`, and removing this bundle unloads the very code that
|
|
31
|
+
would run the install that follows — the batch dies with the plugin that was
|
|
32
|
+
driving it, leaving the profile without it. The row now offers the page's own
|
|
33
|
+
installer instead (`plugins.tab` owner prop `install`), which is owned by the
|
|
34
|
+
plugin manager and therefore survives the unload. The batch and "Select all"
|
|
35
|
+
skip this row for the same reason, so one click can no longer strand the
|
|
36
|
+
profile.
|
|
37
|
+
- **The version list no longer silently disappears.** Several plugins drew no
|
|
38
|
+
history button and no explanation, which looked like "this package has no
|
|
39
|
+
published versions" — it was a failed registry lookup being swallowed. Three
|
|
40
|
+
separate causes, all measured against `registry.npmmirror.com`:
|
|
41
|
+
- **The tab asked for every package at once.** Eleven concurrent connections
|
|
42
|
+
timed out at the 10 s deadline while the same eleven asked one at a time
|
|
43
|
+
answered in under 1.2 s. Lookups now go through a four-at-a-time cap.
|
|
44
|
+
- **The documents are large.** `billion-context` is a 26 MB packument and
|
|
45
|
+
10 MB even abbreviated, so a healthy request could lose a 10 s race on its
|
|
46
|
+
own. Lookups now ask for npm's abbreviated form
|
|
47
|
+
(`application/vnd.npm.install-v1+json`, which keeps `time`, `engines` and
|
|
48
|
+
`deprecated`), the deadline moved to 20 s, and a transport failure is
|
|
49
|
+
retried once — a `UND_ERR_CONNECT_TIMEOUT` is socket contention, not a
|
|
50
|
+
verdict about the package.
|
|
51
|
+
- **The failure was invisible.** A row whose lookup failed is now drawn with
|
|
52
|
+
the reason (`版本列表读取失败,点「检查更新」可重试`) instead of looking
|
|
53
|
+
like a package with no history. A failed lookup is also cached for a shorter
|
|
54
|
+
window than a successful one, so one bad render does not make the next
|
|
55
|
+
mount slow too.
|
|
56
|
+
- **A mirror's stale `latest` tag no longer turns an upgrade into a downgrade.**
|
|
57
|
+
npmmirror serves `@deepseek-ai/dsh-subagent-codex` with `latest: 0.0.1-rc.1`
|
|
58
|
+
while the profile runs `0.2.1-rc.3`, so the row claimed the installed build
|
|
59
|
+
was newer than latest and offered a downgrade as an upgrade. A tag that sorts
|
|
60
|
+
below the installed version is now treated as broken and recovered from the
|
|
61
|
+
published list, preferring a stable release over a prerelease. A prerelease
|
|
62
|
+
that merely sorts above a healthy tag still does not steal `latest`.
|
|
63
|
+
- **An upgrade no longer dies on `bundle-in-use`.** Replacing a version means
|
|
64
|
+
remove + install, and `removeBundle` only unloads a bundle it reads as enabled.
|
|
65
|
+
A plugin that is disabled but still holds a live loader fiber therefore went
|
|
66
|
+
straight to the "in use" check and was refused, which made every upgrade of a
|
|
67
|
+
disabled plugin fail. The removal now clears that fiber and retries once.
|
|
68
|
+
- **The unload asks the right question.** 1.0.3's follow-up assumed
|
|
69
|
+
`setBundleEnabled(name, false)` reloads unconditionally, so it would dispose the
|
|
70
|
+
fiber. It does not: `selectBundle` returns early when the bundle is already
|
|
71
|
+
deselected, so the manifest does not change, so `reconcileProfile` has no
|
|
72
|
+
dropped row to dispose and the fiber survives — the retry then failed for
|
|
73
|
+
exactly the same reason. The removal now reads `enabled` first and, for a
|
|
74
|
+
bundle the manager already reads as disabled, re-selects it and deselects it
|
|
75
|
+
again, which is the only form of the toggle that produces a change a live
|
|
76
|
+
process can act on.
|
|
77
|
+
- **A refusal the host cannot fix says so.** A second `bundle-in-use` now comes
|
|
78
|
+
back with the manager's code *and* a bilingual diagnostic ("the loader still
|
|
79
|
+
holds its plugin row; restart DSH and upgrade again"), and the tab shows the
|
|
80
|
+
diagnostic instead of the bare code. Retrying in the same process cannot clear
|
|
81
|
+
it, so the panel has to name the one action that does. The re-select is also
|
|
82
|
+
undone on the way out, so a failed upgrade never leaves the plugin enabled.
|
|
83
|
+
- **The primary button follows the theme.** Its label colour comes from
|
|
84
|
+
`--dsw-alias-label-primary-inverted` instead of a hard-coded white, so it stays
|
|
85
|
+
readable when the host theme uses a light primary fill.
|
|
86
|
+
- **Tests**: `test/install.test.ts` covers the replacement path (the remove +
|
|
87
|
+
install fallback and its fresh request id, the unload retry, the
|
|
88
|
+
already-disabled re-select including a refused one, the enabled-state restore,
|
|
89
|
+
and the restart diagnostic on both refusal spellings); `test/registry.test.ts`
|
|
90
|
+
covers the abbreviated request, the single retry, the shorter failure cache and
|
|
91
|
+
the 404-is-not-contention rule; `test/overview.test.ts` fails if a page load
|
|
92
|
+
stops respecting the cap; `test/shape-packument.test.ts` pins the stale-tag
|
|
93
|
+
rule. `scripts/verify-overview-live.mjs` runs the built host half against the
|
|
94
|
+
real mirror.
|
|
95
|
+
|
|
96
|
+
## 1.0.3 — unreleased as its own version
|
|
97
|
+
|
|
98
|
+
These changes were staged for 1.0.3, which was never published on its own; they
|
|
99
|
+
first shipped in 1.1.0.
|
|
100
|
+
|
|
101
|
+
- **Upgrades report what pnpm is doing.** The host subscribes to
|
|
102
|
+
`plugin-manager/install-log` and `plugin-manager/install-state`, so pnpm's own
|
|
103
|
+
output ("Verifying lockfile against supply-chain policies", `ETIMEDOUT` retries,
|
|
104
|
+
registry fallbacks) now streams into the job log while the step runs, instead of
|
|
105
|
+
only surfacing after it ends. An upgrade that sits for five minutes on the mirror
|
|
106
|
+
retry ladder finally looks like what it is.
|
|
107
|
+
- **The progress bar is honest about long steps.** While a step is in flight the
|
|
108
|
+
panel shows a live elapsed clock, the phase (`installing` / `applying` /
|
|
109
|
+
`cancelling`), the registry attempt when a fallback is in progress, and an
|
|
110
|
+
indeterminate bar. `done/total` only moves when a step really finishes, which
|
|
111
|
+
previously left it pinned at 0% for minutes.
|
|
112
|
+
- **Cancel works at every stage.** The second install of a remove + install used to
|
|
113
|
+
pass `requestId: undefined`, so that whole stretch was uncancellable; each install
|
|
114
|
+
now mints its own id and the job reports it back. Cancelling also explains what
|
|
115
|
+
happened: settled already, past the point of no return, or stopping now.
|
|
116
|
+
- **The heartbeat is a timer (2 s) instead of a per-step write.** A single step over
|
|
117
|
+
the 15-minute stale window was declared abandoned mid-work, which both killed a
|
|
118
|
+
healthy job and let a second batch start against the same profile.
|
|
119
|
+
- **A failed install no longer tries to restore the enabled state** of a package it
|
|
120
|
+
never added, which used to report a second `operation-error` on top of the real
|
|
121
|
+
failure.
|
|
122
|
+
- **The panel no longer sticks after a batch settles.** `submitJob` resolves only
|
|
123
|
+
after the whole batch finished, and the tab wrote a fresh `queued` snapshot over
|
|
124
|
+
the result, leaving the spinner and the disabled buttons behind until the tab was
|
|
125
|
+
remounted.
|
|
126
|
+
|
|
3
127
|
## 1.0.1 — 2026-10-23
|
|
4
128
|
|
|
5
129
|
- **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 中断,界面会把这种情况显示为失败而不是永远转圈。
|
package/lib/client.js
CHANGED
|
@@ -61,6 +61,9 @@ window.__ModuleLoader__.load({
|
|
|
61
61
|
upToDate: "\u5168\u90E8\u5DF2\u662F\u6700\u65B0",
|
|
62
62
|
upgrade: "\u5347\u7EA7",
|
|
63
63
|
upgrading: "\u5347\u7EA7\u4E2D\u2026",
|
|
64
|
+
selfUpdate: "\u7528\u9875\u9762\u5B89\u88C5\u5668\u5347\u7EA7",
|
|
65
|
+
selfUpdateHint: "\u63D2\u4EF6\u4E0D\u80FD\u5378\u8F7D\u81EA\u5DF1\u518D\u88C5\u56DE\u81EA\u5DF1\uFF0C\u6240\u4EE5\u8FD9\u4E00\u6B65\u4EA4\u7ED9\u63D2\u4EF6\u9875\u81EA\u5DF1\u7684\u5B89\u88C5\u6D41\u7A0B",
|
|
66
|
+
selfUpdateUnavailable: "\u8FD9\u4E00\u9875\u6CA1\u6709\u63D0\u4F9B\u5B89\u88C5\u5165\u53E3\uFF0C\u8BF7\u5728\u300C\u5DF2\u5B89\u88C5\u300D\u91CC\u7528\u5B98\u65B9\u5B89\u88C5\u5BF9\u8BDD\u6846\u5347\u7EA7\u672C\u63D2\u4EF6\u3002",
|
|
64
67
|
history: "\u5386\u53F2\u7248\u672C",
|
|
65
68
|
hideHistory: "\u6536\u8D77\u5386\u53F2",
|
|
66
69
|
github: "GitHub \u4E3B\u9875",
|
|
@@ -82,19 +85,32 @@ window.__ModuleLoader__.load({
|
|
|
82
85
|
openPage: "\u5728\u5DF2\u5B89\u88C5\u4E2D\u67E5\u770B",
|
|
83
86
|
noHistory: "\u6CA1\u6709\u53EF\u7528\u7684\u7248\u672C\u8BB0\u5F55",
|
|
84
87
|
noHistoryWhy: "\u8FD9\u4E2A\u5305\u4E0D\u662F\u4ECE npm \u6CE8\u518C\u8868\u5B89\u88C5\u7684\uFF0C\u6CA1\u6709\u53D1\u5E03\u5386\u53F2\u3002",
|
|
88
|
+
historyFailed: "\u7248\u672C\u5217\u8868\u8BFB\u53D6\u5931\u8D25\uFF0C\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u53EF\u91CD\u8BD5",
|
|
85
89
|
versions: (n) => `${n} \u4E2A\u7248\u672C`,
|
|
86
90
|
showingNewest: (n) => `\u5217\u51FA\u6700\u65B0 ${n} \u4E2A`,
|
|
87
91
|
progress: (done, total) => `\u6B63\u5728\u5904\u7406 ${done}/${total}`,
|
|
92
|
+
elapsed: (text) => `\u5DF2\u7528\u65F6 ${text}`,
|
|
93
|
+
phaseInstalling: "\u6B63\u5728\u4E0B\u8F7D\u5E76\u5B89\u88C5",
|
|
94
|
+
phaseApplying: "\u6B63\u5728\u5199\u5165\u914D\u7F6E",
|
|
95
|
+
phaseCancelling: "\u6B63\u5728\u53D6\u6D88",
|
|
96
|
+
phaseWaiting: "\u51C6\u5907\u4E2D",
|
|
97
|
+
registryAttempt: (index, total) => `\u6CE8\u518C\u8868 ${index}/${total}\uFF0C\u4E0A\u4E00\u4E2A\u8D85\u65F6\u4F1A\u81EA\u52A8\u6362\u6E90`,
|
|
98
|
+
slowHint: "\u9996\u6B21\u5B89\u88C5\u6216\u9700\u8981\u6821\u9A8C\u4F9D\u8D56\u65F6\u4F1A\u6BD4\u8F83\u6162\uFF0C\u65E5\u5FD7\u91CC\u80FD\u770B\u5230\u771F\u5B9E\u8FDB\u5EA6\u3002",
|
|
99
|
+
cancelRequested: "\u5DF2\u8BF7\u6C42\u53D6\u6D88",
|
|
100
|
+
stalled: "\u8FD9\u4E00\u6B65\u5DF2\u7ECF\u5F88\u4E45\u6CA1\u6709\u8F93\u51FA\u4E86\uFF0C\u53EF\u4EE5\u53D6\u6D88\u540E\u91CD\u8BD5\u3002",
|
|
101
|
+
staleSnapshot: "\u72B6\u6001\u5DF2\u505C\u6B62\u66F4\u65B0\uFF0C\u8BF7\u68C0\u67E5\u66F4\u65B0",
|
|
88
102
|
cancel: "\u53D6\u6D88",
|
|
89
103
|
cancelling: "\u6B63\u5728\u53D6\u6D88\u2026",
|
|
90
104
|
restart: "\u9700\u8981\u91CD\u542F DSH \u624D\u4F1A\u751F\u6548\u3002",
|
|
91
105
|
restartHint: "\u5DF2\u4FDD\u5B58\u7684\u6539\u52A8\u5728\u4E0B\u6B21\u542F\u52A8\u65F6\u751F\u6548\u3002",
|
|
106
|
+
reloadForAppearance: "\u4E3B\u9898\u63D2\u4EF6\u5DF2\u66F4\u65B0\uFF0C\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u9875\u9762\u4EE5\u6062\u590D\u4E3B\u9898\u5916\u89C2\uFF08\u4E3B\u9898\u914D\u7F6E\u4FDD\u5B58\u5728\u78C1\u76D8\u4E0A\uFF0C\u4E0D\u4F1A\u4E22\u5931\uFF09\u3002",
|
|
92
107
|
done: "\u5B8C\u6210",
|
|
93
108
|
failed: "\u5931\u8D25",
|
|
94
109
|
failedSome: (n) => `${n} \u4E2A\u5931\u8D25`,
|
|
95
110
|
jobDone: "\u6279\u91CF\u5347\u7EA7\u5B8C\u6210",
|
|
96
111
|
jobCancelled: "\u5DF2\u53D6\u6D88",
|
|
97
112
|
busyAdopted: "\u5DF2\u6709\u4E00\u6279\u5347\u7EA7\u6B63\u5728\u8FDB\u884C\uFF0C\u5DF2\u63A5\u7740\u663E\u793A\u5B83\u7684\u8FDB\u5EA6\u3002",
|
|
113
|
+
lostJob: "\u5347\u7EA7\u8FDB\u5EA6\u5DF2\u5931\u53BB\u8054\u7CFB\uFF08\u5BBF\u4E3B\u53EF\u80FD\u91CD\u542F\u8FC7\uFF09\u3002\u8BF7\u68C0\u67E5\u66F4\u65B0\uFF0C\u786E\u8BA4\u63D2\u4EF6\u5F53\u524D\u7248\u672C\u3002",
|
|
98
114
|
log: "\u8FD0\u884C\u65E5\u5FD7",
|
|
99
115
|
showLog: "\u67E5\u770B\u65E5\u5FD7",
|
|
100
116
|
hideLog: "\u6536\u8D77\u65E5\u5FD7",
|
|
@@ -126,6 +142,9 @@ window.__ModuleLoader__.load({
|
|
|
126
142
|
upToDate: "Everything is up to date",
|
|
127
143
|
upgrade: "Upgrade",
|
|
128
144
|
upgrading: "Upgrading\u2026",
|
|
145
|
+
selfUpdate: "Upgrade via the page",
|
|
146
|
+
selfUpdateHint: "A plugin cannot uninstall and reinstall itself, so this runs the plugin page own installer",
|
|
147
|
+
selfUpdateUnavailable: "This page offers no install entry; upgrade this plugin from the Installed tab instead.",
|
|
129
148
|
history: "Version history",
|
|
130
149
|
hideHistory: "Hide history",
|
|
131
150
|
github: "GitHub page",
|
|
@@ -147,19 +166,32 @@ window.__ModuleLoader__.load({
|
|
|
147
166
|
openPage: "Open in Installed",
|
|
148
167
|
noHistory: "No published versions found",
|
|
149
168
|
noHistoryWhy: "This package was not installed from an npm registry, so it has no published history.",
|
|
169
|
+
historyFailed: 'Could not read the version list; "Check for updates" retries it',
|
|
150
170
|
versions: (n) => `${n} versions`,
|
|
151
171
|
showingNewest: (n) => `showing newest ${n}`,
|
|
152
172
|
progress: (done, total) => `Working on ${done}/${total}`,
|
|
173
|
+
elapsed: (text) => `${text} elapsed`,
|
|
174
|
+
phaseInstalling: "Downloading and installing",
|
|
175
|
+
phaseApplying: "Writing the profile",
|
|
176
|
+
phaseCancelling: "Cancelling",
|
|
177
|
+
phaseWaiting: "Preparing",
|
|
178
|
+
registryAttempt: (index, total) => `Registry ${index}/${total}; a timeout falls back to the next`,
|
|
179
|
+
slowHint: "A first install, or one that must verify dependencies, takes a while \u2014 the log shows what is happening.",
|
|
180
|
+
cancelRequested: "Cancellation requested",
|
|
181
|
+
stalled: "This step has produced no output for a long time; cancel it and retry.",
|
|
182
|
+
staleSnapshot: "status stopped updating \u2014 check for updates",
|
|
153
183
|
cancel: "Cancel",
|
|
154
184
|
cancelling: "Cancelling\u2026",
|
|
155
185
|
restart: "Restart DSH to finish.",
|
|
156
186
|
restartHint: "Saved changes take effect on the next start.",
|
|
187
|
+
reloadForAppearance: "The theme plugin was updated; reloading the page to restore its look (its settings are on disk and are not lost).",
|
|
157
188
|
done: "done",
|
|
158
189
|
failed: "failed",
|
|
159
190
|
failedSome: (n) => `${n} failed`,
|
|
160
191
|
jobDone: "Batch upgrade finished",
|
|
161
192
|
jobCancelled: "Cancelled",
|
|
162
193
|
busyAdopted: "A batch is already running; showing its progress instead.",
|
|
194
|
+
lostJob: "Lost contact with the upgrade (the host may have restarted). Check for updates to confirm the installed version.",
|
|
163
195
|
log: "Run log",
|
|
164
196
|
showLog: "Show log",
|
|
165
197
|
hideLog: "Hide log",
|
|
@@ -205,6 +237,16 @@ window.__ModuleLoader__.load({
|
|
|
205
237
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
206
238
|
}
|
|
207
239
|
}
|
|
240
|
+
function formatElapsed(ms) {
|
|
241
|
+
if (!Number.isFinite(ms) || ms < 0) return "0s";
|
|
242
|
+
const total = Math.floor(ms / 1e3);
|
|
243
|
+
if (total < 60) return `${total}s`;
|
|
244
|
+
const minutes = Math.floor(total / 60);
|
|
245
|
+
const seconds = total % 60;
|
|
246
|
+
if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
247
|
+
const hours = Math.floor(minutes / 60);
|
|
248
|
+
return `${hours}h ${String(minutes % 60).padStart(2, "0")}m`;
|
|
249
|
+
}
|
|
208
250
|
function resolveVersionLink(row, version) {
|
|
209
251
|
const raw = typeof version.href === "string" ? version.href : typeof version.url === "string" && typeof row.repoUrl === "string" && version.url.startsWith("/") ? `${row.repoUrl}${version.url}` : version.url;
|
|
210
252
|
return typeof raw === "string" && /^https?:\/\//i.test(raw) ? raw : void 0;
|
|
@@ -335,6 +377,16 @@ window.__ModuleLoader__.load({
|
|
|
335
377
|
);
|
|
336
378
|
return alive;
|
|
337
379
|
}
|
|
380
|
+
var APPEARANCE_PATTERN = /(^|[/@-])(skin|theme|dream-skin|wallpaper|appearance)([/@.-]|$)/i;
|
|
381
|
+
function isAppearancePackage(name2) {
|
|
382
|
+
return APPEARANCE_PATTERN.test(name2);
|
|
383
|
+
}
|
|
384
|
+
function replacedAppearance(results) {
|
|
385
|
+
if (!Array.isArray(results)) return false;
|
|
386
|
+
return results.some(
|
|
387
|
+
(result) => result.ok === true && result.changed !== false && typeof result.name === "string" && isAppearancePackage(result.name)
|
|
388
|
+
);
|
|
389
|
+
}
|
|
338
390
|
|
|
339
391
|
// src/client/styles.ts
|
|
340
392
|
var STYLE_ID = "dsh-plugin-upgrader-style";
|
|
@@ -352,7 +404,7 @@ window.__ModuleLoader__.load({
|
|
|
352
404
|
.dsh-pu-btn { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 12px; border-radius: 8px; font-size: 12px; font-weight: 500; cursor: pointer; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); transition: background .15s ease, border-color .15s ease, opacity .15s ease; font-family: inherit; }
|
|
353
405
|
.dsh-pu-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); border-color: var(--dsw-alias-border-l2); }
|
|
354
406
|
.dsh-pu-btn:disabled { opacity: .5; cursor: default; }
|
|
355
|
-
.dsh-pu-btn.is-primary { background: var(--dsw-alias-button-primary-fill); border-color: transparent; color: #fff; }
|
|
407
|
+
.dsh-pu-btn.is-primary { background: var(--dsw-alias-button-primary-fill); border-color: transparent; color: var(--dsw-alias-label-primary-inverted, #fff); }
|
|
356
408
|
.dsh-pu-btn.is-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover); }
|
|
357
409
|
.dsh-pu-btn.is-ghost { background: transparent; border-color: transparent; color: var(--dsw-alias-label-secondary); padding: 0 8px; }
|
|
358
410
|
.dsh-pu-btn.is-ghost:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
|
|
@@ -384,6 +436,8 @@ window.__ModuleLoader__.load({
|
|
|
384
436
|
.dsh-pu-name { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
385
437
|
.dsh-pu-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
386
438
|
.dsh-pu-desc { font-size: 11px; color: var(--dsw-alias-label-tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
439
|
+
.dsh-pu-note { font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
440
|
+
.dsh-pu-note.is-err { color: var(--dsw-alias-state-error-primary); }
|
|
387
441
|
.dsh-pu-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; color: var(--dsw-alias-label-secondary); }
|
|
388
442
|
.dsh-pu-tag { font-size: 10px; line-height: 1; padding: 3px 6px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l1); color: var(--dsw-alias-label-secondary); font-weight: 500; white-space: nowrap; }
|
|
389
443
|
.dsh-pu-tag.is-new { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent); }
|
|
@@ -412,7 +466,10 @@ window.__ModuleLoader__.load({
|
|
|
412
466
|
.dsh-pu-job-head { display: flex; align-items: center; gap: 10px; font-size: 12px; }
|
|
413
467
|
.dsh-pu-job-head .dsh-pu-grow { flex: 1; min-width: 0; }
|
|
414
468
|
.dsh-pu-bar { height: 4px; border-radius: 999px; background: var(--dsw-alias-bg-layer-3); overflow: hidden; }
|
|
415
|
-
.dsh-pu-bar
|
|
469
|
+
.dsh-pu-bar-inner { height: 100%; border-radius: 999px; background: var(--dsw-alias-brand-primary); transition: width .25s ease; }
|
|
470
|
+
.dsh-pu-bar-inner.is-busy { width: 40%; transition: none; animation: dsh-pu-indeterminate 1.4s ease-in-out infinite; }
|
|
471
|
+
@keyframes dsh-pu-indeterminate { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } }
|
|
472
|
+
.dsh-pu-hint { font-size: 11px; color: var(--dsw-alias-label-tertiary); }
|
|
416
473
|
.dsh-pu-log { max-height: 160px; overflow: auto; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 10.5px; line-height: 1.55; color: var(--dsw-alias-label-secondary); white-space: pre-wrap; word-break: break-word; border-top: 1px dashed var(--dsw-alias-border-l1); padding-top: 8px; margin: 0; }
|
|
417
474
|
.dsh-pu-banner { display: flex; align-items: center; gap: 8px; font-size: 11.5px; padding: 7px 10px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary); }
|
|
418
475
|
.dsh-pu-banner.is-warn { border-color: var(--dsw-alias-state-warn-primary); color: var(--dsw-alias-state-warn-primary); }
|
|
@@ -420,10 +477,23 @@ window.__ModuleLoader__.load({
|
|
|
420
477
|
.dsh-pu-foot { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-tertiary); flex-wrap: wrap; }
|
|
421
478
|
.dsh-pu-spin { width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--dsw-alias-border-l2); border-top-color: var(--dsw-alias-brand-primary); animation: dsh-pu-spin .8s linear infinite; flex: none; }
|
|
422
479
|
@keyframes dsh-pu-spin { to { transform: rotate(360deg); } }
|
|
423
|
-
@media (prefers-reduced-motion: reduce) { .dsh-pu-spin { animation-duration: 2.4s; } }
|
|
480
|
+
@media (prefers-reduced-motion: reduce) { .dsh-pu-spin { animation-duration: 2.4s; } .dsh-pu-bar-inner.is-busy { animation-duration: 3s; } }
|
|
424
481
|
`;
|
|
425
482
|
|
|
426
483
|
// src/client/components/JobPanel.ts
|
|
484
|
+
var CLOCK_MS = 1e3;
|
|
485
|
+
var STALE_SNAPSHOT_MS = 20 * 1e3;
|
|
486
|
+
function useNow(running, from) {
|
|
487
|
+
const React = getReact();
|
|
488
|
+
const [now, setNow] = React.useState(() => Date.now());
|
|
489
|
+
React.useEffect(() => {
|
|
490
|
+
if (!running || from === void 0) return void 0;
|
|
491
|
+
setNow(Date.now());
|
|
492
|
+
const timer = setInterval(() => setNow(Date.now()), CLOCK_MS);
|
|
493
|
+
return () => clearInterval(timer);
|
|
494
|
+
}, [running, from]);
|
|
495
|
+
return now;
|
|
496
|
+
}
|
|
427
497
|
function JobPanel(props) {
|
|
428
498
|
const h = getH();
|
|
429
499
|
const React = getReact();
|
|
@@ -434,6 +504,16 @@ window.__ModuleLoader__.load({
|
|
|
434
504
|
const percent = total === 0 ? 0 : Math.round(done / total * 100);
|
|
435
505
|
const running = job.status === "running" || job.status === "queued";
|
|
436
506
|
const failed = job.failed ?? 0;
|
|
507
|
+
const inStep = running && job.current !== void 0;
|
|
508
|
+
const now = useNow(inStep, job.stepStartedAt);
|
|
509
|
+
const stamp = `${job.id}|${job.stepStartedAt ?? ""}|${job.done ?? 0}|${job.status}`;
|
|
510
|
+
const lastChange = React.useRef({ stamp, at: Date.now() });
|
|
511
|
+
if (lastChange.current.stamp !== stamp) lastChange.current = { stamp, at: Date.now() };
|
|
512
|
+
const stale = inStep && Date.now() - lastChange.current.at > STALE_SNAPSHOT_MS;
|
|
513
|
+
const stepMs = job.stepStartedAt === void 0 ? 0 : Math.max(0, now - job.stepStartedAt);
|
|
514
|
+
const phaseKey = job.phase === "installing" ? "phaseInstalling" : job.phase === "applying" ? "phaseApplying" : job.phase === "cancelling" ? "phaseCancelling" : "phaseWaiting";
|
|
515
|
+
const phaseText = job.attempt !== void 0 && job.attempt.total > 1 ? t("registryAttempt", job.attempt.index, job.attempt.total) : t(phaseKey);
|
|
516
|
+
const headline = running ? t("progress", done, total) : job.status === "cancelled" ? t("jobCancelled") : t("jobDone");
|
|
437
517
|
return h(
|
|
438
518
|
"div",
|
|
439
519
|
{ className: "dsh-pu-job" },
|
|
@@ -444,8 +524,17 @@ window.__ModuleLoader__.load({
|
|
|
444
524
|
h(
|
|
445
525
|
"span",
|
|
446
526
|
{ className: "dsh-pu-grow" },
|
|
447
|
-
|
|
448
|
-
job.current === void 0 ? null : h("span", { className: "dsh-pu-mono" }, ` ${job.current}`)
|
|
527
|
+
headline,
|
|
528
|
+
job.current === void 0 ? null : h("span", { className: "dsh-pu-mono" }, ` ${job.current}`),
|
|
529
|
+
inStep ? h(
|
|
530
|
+
"span",
|
|
531
|
+
{ className: "dsh-pu-dim" },
|
|
532
|
+
` \xB7 ${phaseText} \xB7 ${t("elapsed", formatElapsed(stepMs))}`
|
|
533
|
+
) : null,
|
|
534
|
+
// Once the snapshot stops moving the clock is reporting a number nobody
|
|
535
|
+
// is updating, so say so instead of letting it climb.
|
|
536
|
+
stale ? h("span", { className: "dsh-pu-dim" }, ` \xB7 ${t("staleSnapshot")}`) : null,
|
|
537
|
+
job.abandoned === true ? h("span", { className: "dsh-pu-dim" }, ` \xB7 ${t("stalled")}`) : null
|
|
449
538
|
),
|
|
450
539
|
failed > 0 ? h("span", { className: "dsh-pu-tag is-err" }, t("failedSome", failed)) : null,
|
|
451
540
|
running ? h(
|
|
@@ -459,7 +548,12 @@ window.__ModuleLoader__.load({
|
|
|
459
548
|
showLog ? t("hideLog") : t("showLog")
|
|
460
549
|
)
|
|
461
550
|
),
|
|
462
|
-
h(
|
|
551
|
+
h(
|
|
552
|
+
"div",
|
|
553
|
+
{ className: "dsh-pu-bar" },
|
|
554
|
+
inStep ? h("div", { className: "dsh-pu-bar-inner is-busy" }) : h("div", { className: "dsh-pu-bar-inner", style: { width: `${percent}%` } })
|
|
555
|
+
),
|
|
556
|
+
inStep ? h("div", { className: "dsh-pu-hint" }, t("slowHint")) : null,
|
|
463
557
|
showLog && Array.isArray(job.log) ? h("pre", { className: "dsh-pu-log" }, job.log.join("\n")) : null
|
|
464
558
|
);
|
|
465
559
|
}
|
|
@@ -518,12 +612,17 @@ window.__ModuleLoader__.load({
|
|
|
518
612
|
const h = getH();
|
|
519
613
|
const React = getReact();
|
|
520
614
|
const { row, t, busy, picked, onToggle, onUpgrade, onSwitch, onApprove, onOpenPage } = props;
|
|
615
|
+
const { self, onInstallSelf } = props;
|
|
616
|
+
const { onRetryHistory } = props;
|
|
521
617
|
const [open, setOpen] = React.useState(false);
|
|
522
618
|
const versions = Array.isArray(row.versions) ? row.versions : [];
|
|
523
619
|
const hasHistory = versions.length > 0;
|
|
524
|
-
const
|
|
620
|
+
const selfUpdate = self === true && row.latest !== void 0 && row.latest !== row.version;
|
|
621
|
+
const selectable = row.upgradable === true && self !== true;
|
|
525
622
|
const status = row.error !== void 0 && row.error !== null ? "err" : row.upgradable ? "new" : "idle";
|
|
526
|
-
const
|
|
623
|
+
const failure = row.versionError !== void 0 && row.versionError !== "" ? row.versionError : void 0;
|
|
624
|
+
const retriable = failure !== void 0 && /unreachable|timeout|registry \d|github/i.test(failure);
|
|
625
|
+
const versionNote = failure === void 0 ? void 0 : failure === "not a profile-managed package" ? t("sourcePath") : failure;
|
|
527
626
|
const pendingBuilds = Array.isArray(row.pendingBuilds) ? row.pendingBuilds : [];
|
|
528
627
|
return h(
|
|
529
628
|
"div",
|
|
@@ -566,7 +665,22 @@ window.__ModuleLoader__.load({
|
|
|
566
665
|
"div",
|
|
567
666
|
{ className: "dsh-pu-desc" },
|
|
568
667
|
row.description !== void 0 && row.description !== "" ? row.description : versionNote ?? row.name
|
|
569
|
-
)
|
|
668
|
+
),
|
|
669
|
+
failure !== void 0 && retriable ? h(
|
|
670
|
+
"div",
|
|
671
|
+
{ className: "dsh-pu-note is-err", title: failure },
|
|
672
|
+
`${t("historyFailed")}: ${failure}`,
|
|
673
|
+
typeof onRetryHistory === "function" ? h(
|
|
674
|
+
"button",
|
|
675
|
+
{
|
|
676
|
+
type: "button",
|
|
677
|
+
className: "dsh-pu-btn is-ghost is-tiny",
|
|
678
|
+
disabled: busy,
|
|
679
|
+
onClick: onRetryHistory
|
|
680
|
+
},
|
|
681
|
+
t("retry")
|
|
682
|
+
) : null
|
|
683
|
+
) : null
|
|
570
684
|
),
|
|
571
685
|
h(
|
|
572
686
|
"div",
|
|
@@ -587,6 +701,16 @@ window.__ModuleLoader__.load({
|
|
|
587
701
|
onClick: () => onUpgrade(row)
|
|
588
702
|
},
|
|
589
703
|
t("upgrade")
|
|
704
|
+
) : selfUpdate && typeof onInstallSelf === "function" ? h(
|
|
705
|
+
"button",
|
|
706
|
+
{
|
|
707
|
+
type: "button",
|
|
708
|
+
className: "dsh-pu-btn is-primary is-tiny",
|
|
709
|
+
disabled: busy,
|
|
710
|
+
title: `${t("selfUpdateHint")} \u2192${row.latest}`,
|
|
711
|
+
onClick: () => onInstallSelf(row, row.latest)
|
|
712
|
+
},
|
|
713
|
+
t("selfUpdate")
|
|
590
714
|
) : null,
|
|
591
715
|
typeof row.repoUrl === "string" ? h(
|
|
592
716
|
"button",
|
|
@@ -654,6 +778,8 @@ window.__ModuleLoader__.load({
|
|
|
654
778
|
}
|
|
655
779
|
|
|
656
780
|
// src/client/components/Tab.ts
|
|
781
|
+
var SELF_NAME = "dsh-plugin-upgrader";
|
|
782
|
+
var APPEARANCE_RELOAD_DELAY_MS = 1200;
|
|
657
783
|
function Tab(props) {
|
|
658
784
|
const h = getH();
|
|
659
785
|
const React = getReact();
|
|
@@ -675,14 +801,27 @@ window.__ModuleLoader__.load({
|
|
|
675
801
|
setBusyName(void 0);
|
|
676
802
|
setCancelling(false);
|
|
677
803
|
reload(false);
|
|
804
|
+
if (settled === void 0) {
|
|
805
|
+
setJob(void 0);
|
|
806
|
+
setBanner({ tone: "warn", text: t("lostJob") });
|
|
807
|
+
return settled;
|
|
808
|
+
}
|
|
678
809
|
const failures = Array.isArray(settled?.results) ? settled.results.filter((result) => result.ok !== true) : [];
|
|
679
810
|
if (failures.length > 0) {
|
|
680
811
|
setBanner({
|
|
681
812
|
tone: "err",
|
|
682
|
-
text: failures.map((failure) =>
|
|
813
|
+
text: failures.map((failure) => {
|
|
814
|
+
const code = failure.message ?? "error";
|
|
815
|
+
return failure.detail === void 0 ? `${failure.name}: ${code}` : `${failure.name}: ${failure.detail} (${code})`;
|
|
816
|
+
}).join(" \xB7 ")
|
|
683
817
|
});
|
|
684
818
|
} else if (settled?.results?.some((result) => result.application === "restart-required") === true) {
|
|
685
819
|
setBanner({ tone: "warn", text: t("restart") });
|
|
820
|
+
} else if (replacedAppearance(settled?.results)) {
|
|
821
|
+
setBanner({ tone: "info", text: t("reloadForAppearance") });
|
|
822
|
+
window.setTimeout(() => {
|
|
823
|
+
window.location.reload();
|
|
824
|
+
}, APPEARANCE_RELOAD_DELAY_MS);
|
|
686
825
|
}
|
|
687
826
|
return settled;
|
|
688
827
|
},
|
|
@@ -703,31 +842,23 @@ window.__ModuleLoader__.load({
|
|
|
703
842
|
if (steps.length === 0) return;
|
|
704
843
|
setBanner(void 0);
|
|
705
844
|
setBusyName(label);
|
|
845
|
+
setJob({ id: "", status: "queued", total: steps.length, done: 0, failed: 0, log: [] });
|
|
706
846
|
const outcome = await submitJob(steps, watch);
|
|
707
847
|
if (!alive.current) return;
|
|
708
|
-
if (outcome.kind === "started") {
|
|
709
|
-
|
|
710
|
-
id: outcome.id,
|
|
711
|
-
status: "queued",
|
|
712
|
-
total: steps.length,
|
|
713
|
-
done: 0,
|
|
714
|
-
failed: 0,
|
|
715
|
-
log: []
|
|
716
|
-
});
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
if (outcome.kind === "adopted") {
|
|
720
|
-
setBanner({ tone: "warn", text: t("busyAdopted") });
|
|
848
|
+
if (outcome.kind === "started" || outcome.kind === "adopted") {
|
|
849
|
+
if (outcome.kind === "adopted") setBanner({ tone: "warn", text: t("busyAdopted") });
|
|
721
850
|
return;
|
|
722
851
|
}
|
|
723
852
|
setBusyName(void 0);
|
|
853
|
+
setJob(void 0);
|
|
724
854
|
const failure = outcome.error;
|
|
725
855
|
setBanner({ tone: "err", text: failure instanceof Error ? failure.message : String(failure) });
|
|
726
856
|
},
|
|
727
857
|
[alive, t, watch]
|
|
728
858
|
);
|
|
729
859
|
const rows = Array.isArray(data?.bundles) ? data.bundles : [];
|
|
730
|
-
const
|
|
860
|
+
const batchable = (row) => row.upgradable === true && row.name !== SELF_NAME;
|
|
861
|
+
const upgradable = rows.filter(batchable);
|
|
731
862
|
const needle = query.trim().toLowerCase();
|
|
732
863
|
const filtered = needle === "" ? rows : rows.filter(
|
|
733
864
|
(row) => row.name.toLowerCase().includes(needle) || String(row.title ?? "").toLowerCase().includes(needle)
|
|
@@ -751,6 +882,18 @@ window.__ModuleLoader__.load({
|
|
|
751
882
|
const pendingBuilds = Array.isArray(row.pendingBuilds) ? row.pendingBuilds : [];
|
|
752
883
|
void run([stepOf(row, row.latest ?? row.version ?? "", "upgrade", pendingBuilds)], row.name);
|
|
753
884
|
};
|
|
885
|
+
const installSelf = React.useCallback(
|
|
886
|
+
(row, version) => {
|
|
887
|
+
const install = props.install;
|
|
888
|
+
if (typeof install !== "function") {
|
|
889
|
+
setBanner({ tone: "warn", text: t("selfUpdateUnavailable") });
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
setBanner(void 0);
|
|
893
|
+
install({ spec: `${row.name}@${version}`, title: row.title, version });
|
|
894
|
+
},
|
|
895
|
+
[props.install, t]
|
|
896
|
+
);
|
|
754
897
|
const upgradeAll = () => {
|
|
755
898
|
void run(
|
|
756
899
|
upgradable.map((row) => stepOf(row, row.latest ?? row.version ?? "", "upgrade")),
|
|
@@ -908,7 +1051,10 @@ window.__ModuleLoader__.load({
|
|
|
908
1051
|
onUpgrade: upgradeOne,
|
|
909
1052
|
onSwitch: switchOne,
|
|
910
1053
|
onApprove: approveAndRetry,
|
|
911
|
-
onOpenPage: typeof props.openPackage === "function" ? props.openPackage : void 0
|
|
1054
|
+
onOpenPage: typeof props.openPackage === "function" ? props.openPackage : void 0,
|
|
1055
|
+
self: row.name === SELF_NAME,
|
|
1056
|
+
onInstallSelf: installSelf,
|
|
1057
|
+
onRetryHistory: () => reload(true)
|
|
912
1058
|
})
|
|
913
1059
|
)
|
|
914
1060
|
),
|