dsh-plugin-update 0.7.1 → 0.9.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 +32 -0
- package/README.md +26 -24
- package/dist/entry-batch.d.ts +11 -2
- package/dist/entry-batch.js +8 -2
- package/dist/entry.d.ts +7 -2
- package/dist/entry.js +35 -11
- package/dist/host-batch.js +81 -36
- package/dist/panel-batch.d.ts +10 -4
- package/dist/panel-batch.js +186 -65
- package/dist/panel.d.ts +2 -0
- package/dist/panel.js +35 -20
- package/dist/service.js +19 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
格式为 Keep-a-Changelog 子集:`Added/Fixed/Changed` 必写,`Deprecated/Removed/Security` 有则透传,`Unreleased` 面板忽略,禁止 git-log 直倒。面板缺日志时中性提示,不挡安装。
|
|
4
4
|
|
|
5
|
+
## [0.9.0] - 2026-10-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- 批量详情独立动作矩阵(#84):详情弹窗按单插件内核只读 + 批量自出动作行,支持单查 / 重试 / 复制诊断 / 复制手工 / 跳过,行单查透 `phoneNames`、诊断进 `diag/env/queue`;死按钮摘净、不嵌套滚动;文案零新增。
|
|
9
|
+
- 批量两段式决策 ADR-0004 proposed(#86):先全查再串行装(生产者 `checkAll` + 消费者复用 `driveLocked/runBatch`)、查并发取 3、消费前重查(不做常驻 reader)、公平性有界与回退线;本版只落决策不改驱动。
|
|
10
|
+
- 主题新增 `newOkText`(#71 修订 Q1):done 行新版走绿 `newOkText`,其余带箭头行走红 `newText`,相等无箭头;README 键表同步,深浅 / 档案 / forced-colors 回退。
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- 总账吃知识(#73 口径 = #82 推荐 A)+ 世界账与轮次账分家(#90):无轮次 pending 行按知识四态(有新版进可更新 / 已是最新进已最新 / 无知识与查失败仍待查),有轮次与 checking 行恒走执行态;终态 current / 无重启 done 行被更新鲜知识推翻为可更新(`lastCheckedAt > 会话行 updatedAt` 且快照比对确有新版),陈旧知识与失败检查永不推翻;入口徽标 `batchEntrySummary` 同口径(含 openOn='has-update' 在轮不开面板)。
|
|
14
|
+
- 检查诚实语义(#90 R2/R5 + #73):宿主 `defaultCheck` 回包成功却无版本永不判 current,改判 `check-failed`;知识“查过但无版本”判 never(回“还没查过”),不再坍缩为“已是最新”;知识查失败归待查,不冒充安装失败(顶部不再喊“N 家安装失败”)。
|
|
15
|
+
- 驱动进行中检查回忙(#90 R4)+ 装类在途轮询放行:`checkPhone` 在 `driveActive` 时回 `update-busy`,不用旧表冒充 fresh 结果;批量驱动收尾前可观测中间进度。
|
|
16
|
+
- 判定收敛(#73 code-review):新增 `sessionEntriesOf/entryPhaseOf/isUnfinishedEntry` 与 `knowledgeVerdictOf` 唯一判定,总账与行共用“在轮 / 知识”两处判定,注释正错(首个匹配即返、任一条目非终态即在轮)。
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- 明细行版本与对齐一致性(#83):知识孤版红 + 入口吃知识,会话目标赢知识;三列网格(名 5em / 版 18ch / 态 1fr)与 #71 无回归。
|
|
20
|
+
- 单面板宏忙守卫与批量一致(#87):installing / verifying 快照与 busyAct 在途时查 / 装两键同灰,只加 disabled,不换文案不加键。
|
|
21
|
+
- 详情 row-skip 非法版本与单面板同口径(#88):不记不 toast 静默重绘;entry locale 注释正错。
|
|
22
|
+
- 重启后待重启自动回查清零(#91):任务完成态换算落盘(restart-required→completed), done + 待重启在快照已生效时清零、真未生效时保留;只读失败不展示陈旧待重启。
|
|
23
|
+
- 单面板安装门控稳定(#87 附带):settled 等自动查回包;抖动预留与闪空行留存沿用既有载体(68 预留 + 焦点滚动 + 轮询让路,74 prefs-only)。
|
|
24
|
+
|
|
25
|
+
门禁:`npm test` 869 例(868 过 / 1 跳过既有 derive-client / 0 失败)+ `gate:bilingual` 四道硬门禁过(文案零新增,312 draft 未动;`--release` 仍如实红,非阻断)。
|
|
26
|
+
|
|
27
|
+
## [0.8.0] - 2026-10-07
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- 入口件无新版摆法 `upToDateDisplay`(`note` / `button`(默认) / `tooltip`):默认按钮本身即`已是最新 X.Y.Z`(单按钮不占宽,可点再查,hover 回`检查更新`暗示动作;无新版走中性弱边、hover 才走主色,复用既有 token,不新增文案与 token);`tooltip` 按钮仍是`检查更新`、版本收进悬停;`note` 回右侧小字旧样子(显式传回)。入口 `label()` 与按钮正文同口径,`badge` 形态不受影响;README 2.5 节补自由度与参数说明,单测覆盖三摆法与非法校验。
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- 查新版瞬时抖动补强(面板):忙闲两帧同高(动作区按钮 nowrap + 最小宽预留、转圈槽常驻)+ 横幅/复制区最小高占位,查新版与轮询回包帧不再跳动。
|
|
34
|
+
- 入口件深色默认主题补 `text-muted`(`#9ca3af`),按钮直显弱边在深底可读。
|
|
35
|
+
- 批量 prefs-only 存偏好回归测试补齐(#74):偏好保存不闪空行、失败保留行(测试覆盖,行为零变更)。
|
|
36
|
+
|
|
5
37
|
## [0.7.1] - 2026-10-06
|
|
6
38
|
|
|
7
39
|
### Fixed
|
package/README.md
CHANGED
|
@@ -144,28 +144,29 @@ mountUpdatePanel(slot, { pluginId: 'my-notes-plugin', prefix: 'notes', call: hos
|
|
|
144
144
|
|
|
145
145
|
可选档案卷主题(纸面浅色案卷风,不替换默认):挂载时加 `theme: 'archive'` 即换肤(右上大印章「待查/可装/安装中/待重启/受阻/已最新」+ 横幅小印章一字 + profile 牌 + 待重启衬线横幅配手绘 SVG 标 + 窄屏印章固定 + 省略号逐字折叠 + 浅深双主题跟随系统),内核 DOM 顺序不动、复制诊断常在;不传即最小可用默认深色样式。运行时用 `panel.setTheme('archive' | 'default')` 可切。入口件与批量面板是同一个 `theme` 参数,取值同一套。
|
|
146
146
|
|
|
147
|
-
主题变量覆盖(第三方换肤):四个 UI 口(单面板、批量面板、入口件、批量入口件)都收 `themeTokens`,键为语义名,一次传入即换肤,不传即零回归。入口件打开的 dialog 面板同步透传;运行时用 `setThemeTokens({...})` 即时换肤,传 `undefined` 即清掉覆盖:
|
|
148
|
-
|
|
149
|
-
```js
|
|
150
|
-
mountUpdatePanel(el, { pluginId: 'p', prefix: 'notes', call,
|
|
151
|
-
theme: 'archive',
|
|
152
|
-
themeTokens: { primary: '#c8402a', bg: '#fffdf6', radiusButton: '3px' },
|
|
153
|
-
})
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
键与样式表里的 `--dsh-update-*` 变量一一对应(括号里是变量名),手写 CSS 变量等价生效:
|
|
157
|
-
|
|
158
|
-
| 组 | 键(变量) |
|
|
159
|
-
|---|---|
|
|
160
|
-
| 文字背景边线 | `text`、`textMuted`、`bg`、`bgSoft`、`border`、`borderStrong`、`buttonBg` |
|
|
161
|
-
| 主色焦点 | `primary`、`primaryDeep`、`focus` |
|
|
162
|
-
| 状态四组 | `okBg / okBorder / okText`、`warnBg / warnBorder / warnText`、`badBg / badBorder / badText`、`busyBg / busyBorder / busyText` |
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
147
|
+
主题变量覆盖(第三方换肤):四个 UI 口(单面板、批量面板、入口件、批量入口件)都收 `themeTokens`,键为语义名,一次传入即换肤,不传即零回归。入口件打开的 dialog 面板同步透传;运行时用 `setThemeTokens({...})` 即时换肤,传 `undefined` 即清掉覆盖:
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
mountUpdatePanel(el, { pluginId: 'p', prefix: 'notes', call,
|
|
151
|
+
theme: 'archive',
|
|
152
|
+
themeTokens: { primary: '#c8402a', bg: '#fffdf6', radiusButton: '3px' },
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
键与样式表里的 `--dsh-update-*` 变量一一对应(括号里是变量名),手写 CSS 变量等价生效:
|
|
157
|
+
|
|
158
|
+
| 组 | 键(变量) |
|
|
159
|
+
|---|---|
|
|
160
|
+
| 文字背景边线 | `text`、`textMuted`、`bg`、`bgSoft`、`border`、`borderStrong`、`buttonBg` |
|
|
161
|
+
| 主色焦点 | `primary`、`primaryDeep`、`focus` |
|
|
162
|
+
| 状态四组 | `okBg / okBorder / okText`、`warnBg / warnBorder / warnText`、`badBg / badBorder / badText`、`busyBg / busyBorder / busyText` |
|
|
163
|
+
| 批量新版 | `newText`(未装好红)、`newOkText`(已装好绿) |
|
|
164
|
+
| 字体阴影 | `fontSans`、`fontSerif`、`fontMono`、`shadow` |
|
|
165
|
+
| 圆角 | `radiusPanel`、`radiusButton`、`radiusBadge` |
|
|
166
|
+
| 入口件尺寸 | `entryFontSize`、`entryPadding`、`entryBorderRadius`、`entryScale`(数字,大于 0) |
|
|
167
|
+
|
|
168
|
+
三条约束:颜色只收 hex(`#rgb`/`#rrggbb`/`#rrggbbaa`)或英文名单词(含 `transparent`),`rgb()`/`color-mix()` 请自行换算成 hex;其余值走注入检查(分号引号括号等不许过,见 `sizing` 同口径);未知键与非法值挂载即抛错,不静默吞掉。被覆盖的 token 不再跟随深色模式(内联值固定,调用方自己保证深浅可读)。
|
|
169
|
+
|
|
169
170
|
类型定义随包分发(`dsh-plugin-update/panel` 的 `.d.ts`),不用自编译;面板离线可读,与包版本绑定。
|
|
170
171
|
|
|
171
172
|
整组件 HTTP 版(无 `host.call` 环境即跑:浏览器面板经 HTTP POST 直达宿主网关,只传三样):
|
|
@@ -204,12 +205,13 @@ const entry = mountUpdateEntry(document.getElementById('upd-entry'), {
|
|
|
204
205
|
| 摆什么 | `button` / `badge` / `inline` | `button` | 按钮;只给一个状态点;面板本体直接嵌进来 |
|
|
205
206
|
| 什么时候查 | `mount` / `never` | `mount` | 进页面静默查一次(**只调 `.updateStatus`,只读**);`never` 则只在点击时查 |
|
|
206
207
|
| 点了做什么 | `has-update` / `always` / `manual` / `direct` | `has-update` | 有新版才开面板;检查完总是开;交给 `onActivate` 自己跳;点开即弹窗、不预查 |
|
|
208
|
+
| 无新版摆哪 | `button` / `tooltip` / `note` | `button` | 版本即按钮本身;收进悬停;回右侧小字旧样子 |
|
|
207
209
|
|
|
208
|
-
|
|
210
|
+
按钮文案随状态自己变(按宿主语言单语渲染:中文界面纯中文、英文界面纯英文,一次只出现一种语言):`检查更新`(还没查到)/ `已是最新 X.Y.Z`(无新版,默认即按钮本身)/ `有新版 1.1.0` / `正在安装…` / `待重启` / `更新失败,点此查看`(英文见集中字典同 key)。
|
|
209
211
|
|
|
210
212
|
语言跟随(单语):入口件默认跟随 `document.documentElement.lang` > `navigator.languages` > 中文兜底;显式覆盖传 `locale: 'zh' | 'en' | { getActive(): string; subscribe?(cb): () => void }`(对象形态可接宿主语言服务,切换即时重绘,`unmount` 后停订;无 DOM 环境不抛,落中文)。
|
|
211
213
|
|
|
212
|
-
|
|
214
|
+
无新版时版本号摆哪(`upToDateDisplay`,缺省 `'button'`):`button` 即按钮本身就是`已是最新 X.Y.Z`(单按钮不占宽,可点再查,hover 回`检查更新`暗示动作;无新版中性弱边,hover 才走主色);`tooltip` 按钮还是`检查更新`、版本收进悬停;`note` 回旧样子(按钮旁右侧小字,只在 `openOn: 'has-update'` 下点击确认无新版后出现)。旧版默认即 `note` 的样子,要回去就显式传 `upToDateDisplay: 'note'`。想连按钮都不留痕,就用 `openOn: 'always'`(检查完总是开弹窗,无新版在弹窗里看“已是最新”)或 `openOn: 'direct'`(点开即弹窗,连预查都省了,面板挂载即自查;徽标形态仍走回调口径)。
|
|
213
215
|
|
|
214
216
|
按钮尺寸(跟头行其他控件同高/整体缩放):传 `sizing` 只改按钮本体,不碰面板;不传即默认外观。`scale` 是整体缩放(默认 `1`,如 `1.2` 放大、`0.9` 缩小),原来写在容器上的 `zoom` 可整体搬进来:
|
|
215
217
|
|
package/dist/entry-batch.d.ts
CHANGED
|
@@ -15,10 +15,19 @@ export type BatchEntryAutoCheck = 'mount' | 'never';
|
|
|
15
15
|
export type BatchEntryOpenOn = 'has-update' | 'always' | 'manual' | 'direct';
|
|
16
16
|
/** 批量入口件的状态档:只用来算文案与 data-state,不另立状态机。 */
|
|
17
17
|
export type BatchEntryStateKind = 'idle' | 'update' | 'busy' | 'restart' | 'failed';
|
|
18
|
-
/** 算文案要的全部输入:批量七行 + 最近一次批量电话的失败稳定码(没查过、没失败为 null
|
|
18
|
+
/** 算文案要的全部输入:批量七行 + 最近一次批量电话的失败稳定码(没查过、没失败为 null)+ 知识账本 + 会话(后两样只做展示,不参与安装决策;缺省即旧语义)。 */
|
|
19
19
|
export interface UpdateBatchEntryState {
|
|
20
20
|
rows: readonly BatchRowView[] | null;
|
|
21
21
|
error: string | null;
|
|
22
|
+
/** 知识账本 inventory(batchStatus/batchCheck 回包里的同形状透传;#83 与面板总账同口径)。 */
|
|
23
|
+
inventory?: unknown;
|
|
24
|
+
/**
|
|
25
|
+
* 会话(同回包里透传):只用来认「有没做完的一轮」——在轮行走执行态、不吃知识,
|
|
26
|
+
* 徽标才与面板总账同一句话(#73 / #82 §6 细则 3、ADR-0003 §2)。缺省即当无轮次。
|
|
27
|
+
* 连带影响:在轮时聚合档位是 idle(不是 update),openOn='has-update' 因此不会开面板——
|
|
28
|
+
* 徽标说「检查更新」、点一下真去查,与面板同一句话。
|
|
29
|
+
*/
|
|
30
|
+
session?: unknown;
|
|
22
31
|
}
|
|
23
32
|
/** 聚合后的读数(onActivate 与测试共用同一份,不各算一份)。 */
|
|
24
33
|
export interface BatchEntrySummary {
|
|
@@ -89,7 +98,7 @@ export interface UpdateBatchEntryController {
|
|
|
89
98
|
unmount(): void;
|
|
90
99
|
}
|
|
91
100
|
/**
|
|
92
|
-
* 聚合读数:计数唯一出处 batchLedgerCounts
|
|
101
|
+
* 聚合读数:计数唯一出处 batchLedgerCounts(含 inventory 即与面板总账同一份知识口径,忙失败占位翻回可更新)。
|
|
93
102
|
* hasUpdate = 忙/失败/待重启/可更新任一非零(openOn has-update 按它开面板;全 settled/待查/跳过即 idle)。
|
|
94
103
|
* v2 单语:label 与 HTML 出口同语言(lang 显式入参,缺省跟随全局;zh 口径逐字兼容旧中文)。
|
|
95
104
|
*/
|
package/dist/entry-batch.js
CHANGED
|
@@ -26,7 +26,9 @@ function escapeHtml(value) {
|
|
|
26
26
|
}
|
|
27
27
|
function batchEntrySummary(state, lang) {
|
|
28
28
|
const rows = state?.rows ?? null;
|
|
29
|
-
const
|
|
29
|
+
const inventory = state?.inventory ?? void 0;
|
|
30
|
+
const session = state?.session;
|
|
31
|
+
const counts = batchLedgerCounts(Array.isArray(rows) ? rows : [], inventory, session);
|
|
30
32
|
const total = Array.isArray(rows) ? rows.length : 0;
|
|
31
33
|
let kind;
|
|
32
34
|
if (!Array.isArray(rows) || rows.length === 0) {
|
|
@@ -178,6 +180,8 @@ function mountUpdateBatchEntry(container, options) {
|
|
|
178
180
|
const ENTRY_SELECTOR = "[data-dsh-upd-entry]";
|
|
179
181
|
let rows = null;
|
|
180
182
|
let error = null;
|
|
183
|
+
let inventory = null;
|
|
184
|
+
let session = null;
|
|
181
185
|
let note = null;
|
|
182
186
|
let loaded = false;
|
|
183
187
|
let activating = false;
|
|
@@ -205,7 +209,7 @@ function mountUpdateBatchEntry(container, options) {
|
|
|
205
209
|
return isObject(reply) ? reply : {};
|
|
206
210
|
}
|
|
207
211
|
function stateOf() {
|
|
208
|
-
return { rows, error };
|
|
212
|
+
return { rows, error, inventory, session };
|
|
209
213
|
}
|
|
210
214
|
function summaryOf() {
|
|
211
215
|
return batchEntrySummary(stateOf(), currentLang());
|
|
@@ -241,6 +245,8 @@ function mountUpdateBatchEntry(container, options) {
|
|
|
241
245
|
}
|
|
242
246
|
const next = asRows(reply["rows"]);
|
|
243
247
|
if (next) rows = next;
|
|
248
|
+
if (isObject(reply) && "inventory" in reply) inventory = reply["inventory"] ?? null;
|
|
249
|
+
if (isObject(reply) && "session" in reply) session = reply["session"] ?? null;
|
|
244
250
|
error = null;
|
|
245
251
|
loaded = true;
|
|
246
252
|
}
|
package/dist/entry.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type EntryAutoCheck = 'mount' | 'never';
|
|
|
12
12
|
* `manual` 交给 onActivate;`direct` 点开即弹窗、不预查(面板挂载即自查;badge 仍走回调口径)。
|
|
13
13
|
*/
|
|
14
14
|
export type EntryOpenOn = 'has-update' | 'always' | 'manual' | 'direct';
|
|
15
|
+
/** 无新版时版本号摆哪:`note` 右侧小字(旧默认);`button` 按钮本身即版本(默认);`tooltip` 收进悬停。 */
|
|
16
|
+
export type UpToDateDisplay = 'note' | 'button' | 'tooltip';
|
|
15
17
|
/** 入口件主题(与面板同一套取值,切换即换肤)。 */
|
|
16
18
|
export type EntryTheme = UpdatePanelTheme;
|
|
17
19
|
/** 入口件的状态档:只用来算文案与 data-state,不另立状态机。 */
|
|
@@ -52,6 +54,8 @@ export interface UpdateEntryOptions {
|
|
|
52
54
|
autoCheck?: EntryAutoCheck;
|
|
53
55
|
/** 缺省 'has-update':有新版才开面板,没有就只在原地给一句提示。 */
|
|
54
56
|
openOn?: EntryOpenOn;
|
|
57
|
+
/** 无新版时版本号摆哪:缺省 'button'(按钮本身即版本,单按钮不占宽);'note' 回旧样子(右侧小字);'tooltip' 收进悬停。 */
|
|
58
|
+
upToDateDisplay?: UpToDateDisplay;
|
|
55
59
|
/** 覆盖默认按钮文案(不传就用状态联动文案)。 */
|
|
56
60
|
label?: string;
|
|
57
61
|
/** 按钮尺寸覆盖(#69):见 EntrySizing;不传即零回归,badge/inline 不受影响。 */
|
|
@@ -89,8 +93,9 @@ export interface UpdateEntryController {
|
|
|
89
93
|
unmount(): void;
|
|
90
94
|
}
|
|
91
95
|
/**
|
|
92
|
-
* 状态档:快照六字段 + 失败码推出,顺序固定(活任务 >
|
|
93
|
-
*
|
|
96
|
+
* 状态档:快照六字段 + 失败码推出,顺序固定(活任务 > 失败 > 待重启 > 有新版 > 待查)。
|
|
97
|
+
* 失败码是最近一次通话的新证据,快照是上一次成功的旧读数:新证据赢(#91)。
|
|
98
|
+
* 待重启仍是正常终态,但只在无新失败时展示;正在装的任务比一条陈旧的失败码更可信。
|
|
94
99
|
*/
|
|
95
100
|
export declare function entryStateKind(state: UpdateEntryState | null | undefined): EntryStateKind;
|
|
96
101
|
/**
|
package/dist/entry.js
CHANGED
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
} from "./panel.js";
|
|
9
9
|
import { BILINGUAL_CSS, copyHTML, copyText } from "./bilingual.js";
|
|
10
10
|
import { resolveLang, subscribeLang } from "./lang.js";
|
|
11
|
+
function upToDateVersionOf(snapshot, error) {
|
|
12
|
+
if (error || !snapshot || hasUpdateOf(snapshot)) return null;
|
|
13
|
+
const v = snapshot.runningVersion;
|
|
14
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
15
|
+
}
|
|
11
16
|
function hasUpdateOf(snapshot) {
|
|
12
17
|
if (!snapshot) return false;
|
|
13
18
|
const latest = snapshot.latestVersion;
|
|
@@ -18,12 +23,12 @@ function entryStateKind(state) {
|
|
|
18
23
|
const snapshot = state?.snapshot ?? null;
|
|
19
24
|
const job = snapshot?.job ?? null;
|
|
20
25
|
if (job && (job.state === "installing" || job.state === "verifying")) return "busy";
|
|
21
|
-
if (snapshot && (snapshot.blockedReason === "pending-restart" || job?.state === "restart-required")) {
|
|
22
|
-
return "restart";
|
|
23
|
-
}
|
|
24
26
|
if (state && state.error || job && (job.state === "failed" || job.state === "interrupted")) {
|
|
25
27
|
return "failed";
|
|
26
28
|
}
|
|
29
|
+
if (snapshot && (snapshot.blockedReason === "pending-restart" || job?.state === "restart-required")) {
|
|
30
|
+
return "restart";
|
|
31
|
+
}
|
|
27
32
|
if (hasUpdateOf(snapshot)) return "update";
|
|
28
33
|
return "idle";
|
|
29
34
|
}
|
|
@@ -123,6 +128,9 @@ const UPDATE_ENTRY_CSS = [
|
|
|
123
128
|
'.dsh-upd-entry[data-state="update"] .dsh-upd-entry-dot{background:var(--dsh-update-ok-border,#059669)}',
|
|
124
129
|
'.dsh-upd-entry[data-state="busy"] .dsh-upd-entry-dot,.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-dot{background:var(--dsh-update-warn-border,#d97706)}',
|
|
125
130
|
'.dsh-upd-entry[data-state="failed"] .dsh-upd-entry-dot{background:var(--dsh-update-bad-border,#dc2626)}',
|
|
131
|
+
// C 直显(upToDateDisplay='button'):无新版时按钮本身即版本,走中性弱边(不抢有新版的红/绿),hover 才走 primary 暗示可再查;复用既有 token,不加新键。
|
|
132
|
+
'.dsh-upd-entry[data-known="uptodate"] .dsh-upd-entry-btn{border-color:var(--dsh-update-border,#d1d5db);color:var(--dsh-update-text-muted,#6b7280)}',
|
|
133
|
+
'.dsh-upd-entry[data-known="uptodate"] .dsh-upd-entry-btn:hover{border-color:var(--dsh-update-primary,#2563eb);color:var(--dsh-update-primary,#2563eb)}',
|
|
126
134
|
// 小字自带底(深色宿主 + 浅色变量时也读得出;浅底宿主上只是多一圈细线,不抢戏)。
|
|
127
135
|
".dsh-upd-entry-note{font-size:12.5px;opacity:.9;background:var(--dsh-update-bg,#ffffff);border:1px solid var(--dsh-update-border,#e5e7eb);border-radius:var(--dsh-update-radius-badge,4px);padding:1px 8px}",
|
|
128
136
|
'.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-note{background:var(--dsh-update-bg);border-color:var(--dsh-update-border-strong);color:var(--dsh-update-text)}',
|
|
@@ -133,7 +141,7 @@ const UPDATE_ENTRY_CSS = [
|
|
|
133
141
|
'.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-btn{border-color:var(--dsh-update-border-strong);background:var(--dsh-update-bg);color:var(--dsh-update-text);border-radius:var(--dsh-update-entry-border-radius,3px)}',
|
|
134
142
|
'.dsh-upd-entry[data-theme="archive"] .dsh-upd-entry-btn:hover{border-color:var(--dsh-update-primary);color:var(--dsh-update-primary)}',
|
|
135
143
|
'@media (prefers-color-scheme: dark){.dsh-upd-entry[data-theme="archive"]{--dsh-update-text:#ece5d3;--dsh-update-text-muted:#a89c83;--dsh-update-border-strong:#5c4e3b;--dsh-update-primary:#e0684e;--dsh-update-bg:#1e1a15}}',
|
|
136
|
-
"@media (prefers-color-scheme: dark){.dsh-upd-entry{color:#e5e7eb}",
|
|
144
|
+
"@media (prefers-color-scheme: dark){.dsh-upd-entry{color:#e5e7eb;--dsh-update-text-muted:#9ca3af}",
|
|
137
145
|
".dsh-upd-entry-btn{--dsh-update-button-bg:#1f2937;--dsh-update-border:#374151}}"
|
|
138
146
|
].join("\n");
|
|
139
147
|
const ENTRY_ATTR = "data-dsh-upd-entry";
|
|
@@ -179,6 +187,10 @@ function mountUpdateEntry(container, options) {
|
|
|
179
187
|
if (openOn !== "has-update" && openOn !== "always" && openOn !== "manual" && openOn !== "direct") {
|
|
180
188
|
throw new Error(`[dsh-plugin-update] \u70B9\u51FB\u53BB\u5411\u975E\u6CD5\uFF1A\u53EA\u6536 has-update / always / manual / direct\uFF08\u6536\u5230 ${JSON.stringify(options.openOn)}\uFF09`);
|
|
181
189
|
}
|
|
190
|
+
const upToDateDisplay = options.upToDateDisplay ?? "button";
|
|
191
|
+
if (upToDateDisplay !== "note" && upToDateDisplay !== "button" && upToDateDisplay !== "tooltip") {
|
|
192
|
+
throw new Error(`[dsh-plugin-update] invalid upToDateDisplay: expected note / button / tooltip (got ${JSON.stringify(options.upToDateDisplay)})`);
|
|
193
|
+
}
|
|
182
194
|
if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive") {
|
|
183
195
|
throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
|
|
184
196
|
}
|
|
@@ -237,7 +249,13 @@ function mountUpdateEntry(container, options) {
|
|
|
237
249
|
return { snapshot, error };
|
|
238
250
|
}
|
|
239
251
|
function currentLabel() {
|
|
240
|
-
|
|
252
|
+
if (labelOverride) return labelOverride;
|
|
253
|
+
const lang = currentLang();
|
|
254
|
+
if (!activating && upToDateDisplay === "button" && entryStateKind(stateOf()) === "idle") {
|
|
255
|
+
const v = upToDateVersionOf(snapshot, error);
|
|
256
|
+
if (v) return copyText("entry.note.up-to-date", lang, { version: v });
|
|
257
|
+
}
|
|
258
|
+
return entryLabelFor(stateOf(), lang);
|
|
241
259
|
}
|
|
242
260
|
function hasUpdate() {
|
|
243
261
|
return hasUpdateOf(snapshot);
|
|
@@ -248,18 +266,24 @@ function mountUpdateEntry(container, options) {
|
|
|
248
266
|
function entryHTML() {
|
|
249
267
|
const kind = entryStateKind(stateOf());
|
|
250
268
|
const lang = currentLang();
|
|
251
|
-
const
|
|
252
|
-
const
|
|
269
|
+
const knownUpToDate = !activating && kind === "idle" ? upToDateVersionOf(snapshot, error) : null;
|
|
270
|
+
const buttonMode = !labelOverride && upToDateDisplay === "button" && knownUpToDate;
|
|
271
|
+
const tooltipMode = !labelOverride && upToDateDisplay === "tooltip" && knownUpToDate;
|
|
272
|
+
const labelHTML = labelOverride ? escapeHtml(labelOverride) : activating ? copyHTML("entry.action.checking", lang) : buttonMode && knownUpToDate ? copyHTML("entry.note.up-to-date", lang, { version: knownUpToDate }) : copyHTML(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf()));
|
|
273
|
+
const labelText = labelOverride ?? (activating ? copyText("entry.action.checking", lang) : buttonMode && knownUpToDate ? copyText("entry.note.up-to-date", lang, { version: knownUpToDate }) : copyText(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf())));
|
|
274
|
+
const titleText = labelOverride ? labelOverride : activating ? copyText("entry.action.checking", lang) : buttonMode && knownUpToDate ? copyText(entryBilingualKeyFor(stateOf()), lang, entryBilingualValuesFor(stateOf())) : tooltipMode && knownUpToDate ? copyText("entry.note.up-to-date", lang, { version: knownUpToDate }) : labelText;
|
|
253
275
|
const busyAttr = activating ? ' disabled aria-busy="true"' : "";
|
|
254
276
|
const themeAttr = theme === "archive" ? ' data-theme="archive"' : "";
|
|
255
277
|
const tokensStyle = themeTokensStyleFor(themeTokens ?? void 0);
|
|
256
278
|
const styleBody = [sizingStyle, tokensStyle].filter((part) => part).join(";");
|
|
257
279
|
const styleAttr = styleBody ? ` style="${styleBody}"` : "";
|
|
258
|
-
const
|
|
259
|
-
const
|
|
280
|
+
const knownAttr = buttonMode ? ' data-known="uptodate"' : "";
|
|
281
|
+
const displayAttr = ` data-uptodate="${upToDateDisplay}"`;
|
|
282
|
+
const control = variant === "badge" ? `<button type="button" class="dsh-upd-entry-dot" ${ENTRY_ATTR}="activate" title="${escapeHtml(titleText)}" aria-label="${escapeHtml(labelText)}"${busyAttr}></button>` : `<button type="button" class="dsh-upd-entry-btn" ${ENTRY_ATTR}="activate" title="${escapeHtml(titleText)}" aria-label="${escapeHtml(labelText)}"${busyAttr}>${labelHTML}</button>`;
|
|
283
|
+
const noteHTML = upToDateDisplay === "note" && noteVersion ? `<span class="dsh-upd-entry-note" data-dsh-upd-note="1">${copyHTML("entry.note.up-to-date", lang, { version: noteVersion })}</span>` : "";
|
|
260
284
|
return `<style>${UPDATE_ENTRY_CSS}
|
|
261
285
|
${BILINGUAL_CSS}</style>
|
|
262
|
-
<span class="dsh-upd-entry" data-variant="${variant}" data-state="${kind}"${themeAttr}${styleAttr}>${control}${noteHTML}</span>`;
|
|
286
|
+
<span class="dsh-upd-entry" data-variant="${variant}" data-state="${kind}"${displayAttr}${knownAttr}${themeAttr}${styleAttr}>${control}${noteHTML}</span>`;
|
|
263
287
|
}
|
|
264
288
|
function render() {
|
|
265
289
|
if (!mounted) return;
|
|
@@ -361,7 +385,7 @@ ${BILINGUAL_CSS}</style>
|
|
|
361
385
|
openDialog();
|
|
362
386
|
return;
|
|
363
387
|
}
|
|
364
|
-
noteVersion = snapshot.runningVersion;
|
|
388
|
+
if (upToDateDisplay === "note") noteVersion = snapshot.runningVersion;
|
|
365
389
|
render();
|
|
366
390
|
}
|
|
367
391
|
function setTheme(next) {
|
package/dist/host-batch.js
CHANGED
|
@@ -203,6 +203,7 @@ const EMPTY_CACHE = {
|
|
|
203
203
|
manual: null,
|
|
204
204
|
queue: null,
|
|
205
205
|
profileName: null,
|
|
206
|
+
hostKind: null,
|
|
206
207
|
error: null,
|
|
207
208
|
diag: null
|
|
208
209
|
};
|
|
@@ -235,11 +236,21 @@ async function callPhone(rt, name, args) {
|
|
|
235
236
|
if ("snapshot" in value) next.snapshot = value["snapshot"] ?? null;
|
|
236
237
|
if ("manual" in value) next.manual = firstText(value["manual"]);
|
|
237
238
|
if ("queue" in value) next.queue = value["queue"] ?? null;
|
|
238
|
-
if ("env" in value)
|
|
239
|
+
if ("env" in value) {
|
|
240
|
+
next.profileName = firstText(asRecord(value["env"])["profileName"]);
|
|
241
|
+
next.hostKind = firstText(asRecord(value["env"])["environmentKind"]);
|
|
242
|
+
}
|
|
239
243
|
next.error = null;
|
|
240
244
|
next.diag = null;
|
|
241
245
|
} else {
|
|
242
246
|
next.diag = "diag" in value ? value["diag"] ?? null : null;
|
|
247
|
+
if ("env" in value) {
|
|
248
|
+
const envRec = asRecord(value["env"]);
|
|
249
|
+
const pn = firstText(envRec["profileName"]);
|
|
250
|
+
const hk = firstText(envRec["environmentKind"]);
|
|
251
|
+
if (pn !== null) next.profileName = pn;
|
|
252
|
+
if (hk !== null) next.hostKind = hk;
|
|
253
|
+
}
|
|
243
254
|
}
|
|
244
255
|
rt.cache = next;
|
|
245
256
|
return value;
|
|
@@ -249,7 +260,7 @@ async function defaultCheck(rt) {
|
|
|
249
260
|
if (reply["ok"] !== true) return { kind: "failed", error: codeOfReply(reply) };
|
|
250
261
|
const snapshot = snapshotOf(reply);
|
|
251
262
|
const latest = firstText(snapshot["latestVersion"]);
|
|
252
|
-
if (!latest) return { kind: "
|
|
263
|
+
if (!latest) return { kind: "failed", error: "check-failed" };
|
|
253
264
|
if (isVersionSkipped(await skippedVersionsOf(rt), latest)) return { kind: "skipped", version: latest };
|
|
254
265
|
const base = firstText(snapshot["installedVersion"], snapshot["runningVersion"]);
|
|
255
266
|
if (base) {
|
|
@@ -490,6 +501,33 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
490
501
|
return null;
|
|
491
502
|
}
|
|
492
503
|
}
|
|
504
|
+
async function reconcileRestart(rt, entry) {
|
|
505
|
+
if (entry.phase !== "done" || entry.restartRequired !== true) return null;
|
|
506
|
+
const snapshot = asRecord(rt.cache.snapshot);
|
|
507
|
+
const running = firstText(snapshot["runningVersion"]);
|
|
508
|
+
const installed = firstText(snapshot["installedVersion"]);
|
|
509
|
+
if (!running || !installed) return null;
|
|
510
|
+
const target = entry.targetVersion ?? installed;
|
|
511
|
+
if (!target) return null;
|
|
512
|
+
if (running !== target) return null;
|
|
513
|
+
if (installed !== running) return null;
|
|
514
|
+
if (firstText(snapshot["blockedReason"]) === "pending-restart") return null;
|
|
515
|
+
const job = snapshot["job"];
|
|
516
|
+
const jobState = job && typeof job === "object" && !Array.isArray(job) ? firstText(job["state"]) : null;
|
|
517
|
+
if (jobState === "restart-required") return null;
|
|
518
|
+
try {
|
|
519
|
+
const latest = await readSession();
|
|
520
|
+
const current = batchEntryOf(latest, entry.key);
|
|
521
|
+
if (!current || current.phase !== "done" || current.restartRequired !== true) return null;
|
|
522
|
+
const marked = markBatchEntry(latest, entry.key, { phase: "done", restartRequired: false, error: null }, now());
|
|
523
|
+
if (!marked.changed) return null;
|
|
524
|
+
await saveSession(marked.session);
|
|
525
|
+
const corrected = batchEntryOf(marked.session, entry.key);
|
|
526
|
+
return corrected ? { session: marked.session, entry: corrected } : null;
|
|
527
|
+
} catch {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
493
531
|
async function buildRows(session, refresh) {
|
|
494
532
|
let current = session;
|
|
495
533
|
const rows = [];
|
|
@@ -505,6 +543,13 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
505
543
|
}
|
|
506
544
|
}
|
|
507
545
|
}
|
|
546
|
+
if (entry && !driveActive) {
|
|
547
|
+
const correctedRestart = await reconcileRestart(rt, entry);
|
|
548
|
+
if (correctedRestart) {
|
|
549
|
+
current = correctedRestart.session;
|
|
550
|
+
entry = correctedRestart.entry;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
508
553
|
const phase = entry ? entry.phase : "pending";
|
|
509
554
|
rows.push({
|
|
510
555
|
key: rt.spec.key,
|
|
@@ -517,6 +562,7 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
517
562
|
manual: rt.cache.manual,
|
|
518
563
|
queue: rt.cache.queue,
|
|
519
564
|
profileName: rt.cache.profileName,
|
|
565
|
+
hostKind: rt.cache.hostKind,
|
|
520
566
|
phoneNames: rt.host.phoneNames,
|
|
521
567
|
// 新增两格(#25 解法三,只增不改):该目标的宿主插件标识,与最近一次失败回包的诊断。
|
|
522
568
|
pluginId: rt.pluginId,
|
|
@@ -647,44 +693,43 @@ function createMultiHostUpdate(deps = {}, options) {
|
|
|
647
693
|
async function checkPhone() {
|
|
648
694
|
const refuse = denied();
|
|
649
695
|
if (refuse) return refuse;
|
|
696
|
+
if (driveActive) return { ok: false, error: "update-busy", errorKind: "update-busy" };
|
|
650
697
|
const session = await readSession();
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
698
|
+
const at = now();
|
|
699
|
+
let prev = emptyBatchInventory();
|
|
700
|
+
try {
|
|
701
|
+
prev = await readInventory();
|
|
702
|
+
} catch {
|
|
703
|
+
prev = emptyBatchInventory();
|
|
704
|
+
}
|
|
705
|
+
const entries = { ...prev.entries };
|
|
706
|
+
for (const rt of runtimes) {
|
|
654
707
|
try {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: outcome.version, canInstall, error: null };
|
|
670
|
-
} else if (outcome.kind === "current") {
|
|
671
|
-
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: installed, canInstall: false, error: null };
|
|
672
|
-
} else if (outcome.kind === "skipped") {
|
|
673
|
-
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: outcome.version, canInstall: false, error: null };
|
|
674
|
-
} else {
|
|
675
|
-
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: firstText(snapshot["latestVersion"]), canInstall, error: outcome.error };
|
|
676
|
-
}
|
|
677
|
-
} catch (error) {
|
|
678
|
-
const code = errorPayloadOf(error).error;
|
|
679
|
-
rt.cache = { ...rt.cache, error: code };
|
|
680
|
-
const snapshot = asRecord(rt.cache.snapshot);
|
|
681
|
-
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: firstText(snapshot["installedVersion"], snapshot["runningVersion"]), latestVersion: firstText(snapshot["latestVersion"]), canInstall: typeof snapshot["canInstall"] === "boolean" ? snapshot["canInstall"] : null, error: code };
|
|
708
|
+
const outcome = await rt.check(rt.spec.key, rt.spec);
|
|
709
|
+
rt.cache = { ...rt.cache, error: outcome.kind === "failed" ? outcome.error : null };
|
|
710
|
+
const snapshot = asRecord(rt.cache.snapshot);
|
|
711
|
+
const installed = firstText(snapshot["installedVersion"], snapshot["runningVersion"]);
|
|
712
|
+
const canInstallRaw = snapshot["canInstall"];
|
|
713
|
+
const canInstall = typeof canInstallRaw === "boolean" ? canInstallRaw : null;
|
|
714
|
+
if (outcome.kind === "update") {
|
|
715
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: outcome.version, canInstall, error: null };
|
|
716
|
+
} else if (outcome.kind === "current") {
|
|
717
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: installed, canInstall: false, error: null };
|
|
718
|
+
} else if (outcome.kind === "skipped") {
|
|
719
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: outcome.version, canInstall: false, error: null };
|
|
720
|
+
} else {
|
|
721
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: installed, latestVersion: firstText(snapshot["latestVersion"]), canInstall, error: outcome.error };
|
|
682
722
|
}
|
|
723
|
+
} catch (error) {
|
|
724
|
+
const code = errorPayloadOf(error).error;
|
|
725
|
+
rt.cache = { ...rt.cache, error: code };
|
|
726
|
+
const snapshot = asRecord(rt.cache.snapshot);
|
|
727
|
+
entries[rt.spec.key] = { lastCheckedAt: at, installedVersion: firstText(snapshot["installedVersion"], snapshot["runningVersion"]), latestVersion: firstText(snapshot["latestVersion"]), canInstall: typeof snapshot["canInstall"] === "boolean" ? snapshot["canInstall"] : null, error: code };
|
|
683
728
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
687
|
-
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
await writeInventory(normalizeBatchInventory({ version: 1, updatedAt: at, entries }, at));
|
|
732
|
+
} catch {
|
|
688
733
|
}
|
|
689
734
|
return await table(session, false);
|
|
690
735
|
}
|
package/dist/panel-batch.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export interface BatchPanelOptions {
|
|
|
72
72
|
autoResume?: boolean;
|
|
73
73
|
}
|
|
74
74
|
/** 面板可点的动作(HTML 上 data-act 一一对应;测试走同一条路)。 */
|
|
75
|
-
export type BatchPanelActionKind = 'check' | 'install' | 'resume' | 'cancel' | 'row-install' | 'row-skip' | 'row-resume-skip' | 'row-cancel-queue' | 'row-copy-manual' | 'row-copy-diag' | 'toggle-details' | 'restart' | 'close' | 'toggle-check-on-open';
|
|
75
|
+
export type BatchPanelActionKind = 'check' | 'install' | 'resume' | 'cancel' | 'row-install' | 'row-check' | 'row-skip' | 'row-resume-skip' | 'row-cancel-queue' | 'row-copy-manual' | 'row-copy-diag' | 'toggle-details' | 'restart' | 'close' | 'toggle-check-on-open';
|
|
76
76
|
/** 挂载点:只要有 innerHTML 的容器即可(浏览器元素或测试替身都行)。 */
|
|
77
77
|
export interface BatchPanelContainer {
|
|
78
78
|
innerHTML: string;
|
|
@@ -127,8 +127,14 @@ export interface BatchLedgerCounts {
|
|
|
127
127
|
* 失败优先(失败行永远算失败,不算待重启);其余按相位落档;不用重启的终态才算「已最新」。
|
|
128
128
|
* 例外一处:「忙失败占位」(phase=failed 但 error=update-busy)其实是**排队**不是失败——
|
|
129
129
|
* 宿主忙时先写占位再回 update-busy,面板把它翻回「可更新」,免得总账把它报成失败。
|
|
130
|
+
* 无轮次 pending 行看知识(#73 口径 = #82 报告 §6 推荐 A),条件与行展示同一处(isInUnfinishedRound):
|
|
131
|
+
* 有新版进可更新、已是最新进已最新、没有结果(无知识 / 查失败)仍待查;
|
|
132
|
+
* checking 行与「有没做完一轮」的行永远走执行态(待查),都不吃知识。
|
|
133
|
+
* 终态 current / 无重启 done 行看世界账(#90 世界账与轮次账分家):知识更新鲜且确有新版即进可更新,
|
|
134
|
+
* 否则沿旧结论(已最新),免得陈旧知识或失败的检查翻转已收尾的轮次。
|
|
135
|
+
* 不传 inventory 即无知识;不传 session 即当无轮次(旧调用零回归)。
|
|
130
136
|
*/
|
|
131
|
-
export declare function batchLedgerCounts(rows: readonly BatchRowView[]): BatchLedgerCounts;
|
|
137
|
+
export declare function batchLedgerCounts(rows: readonly BatchRowView[], inventory?: unknown, session?: unknown): BatchLedgerCounts;
|
|
132
138
|
/** 分类计数 → 一句话总账(只出现非零档,顺序固定:可更新 · 安装中 · 待查 · 待重启 · 失败 · 已跳过 · 已最新;#64 单语:缺省 zh 零回归)。 */
|
|
133
139
|
export declare function batchLedgerText(counts: BatchLedgerCounts, lang?: unknown): string;
|
|
134
140
|
/**
|
|
@@ -164,7 +170,7 @@ export declare function batchRowKnowledgeText(row: BatchRowView, entry: BatchKno
|
|
|
164
170
|
* 一行的版本展示(#71 Q3:永远显示当前版;有新版才 `当前 → 最新`)。
|
|
165
171
|
* latest 只做展示,不参与安装决策:会话目标版优先(安装语义),无会话目标时才看知识最新版。
|
|
166
172
|
* - 会话有目标版:current(快照运行版优先)→ 会话目标(current 缺失时占位 `?`,与旧口径一致)。
|
|
167
|
-
* - 会话无目标:current(快照优先,缺失退知识 installedVersion)→ 知识 latest
|
|
173
|
+
* - 会话无目标:current(快照优先,缺失退知识 installedVersion)→ 知识 latest(不等即 → 红;相等即已是最新,只显当前;current 缺失而 latest 有即 `? → latest` 红,与会话缺当前同例)。
|
|
168
174
|
* - 两者皆无:current 为 null、latest 为 null、hasUpdate 为 false(调用方不画版本 span)。
|
|
169
175
|
*/
|
|
170
176
|
export interface BatchRowVersionParts {
|
|
@@ -204,7 +210,7 @@ export interface BatchPanelRenderInput {
|
|
|
204
210
|
noticeKey?: string | null;
|
|
205
211
|
/** 键 -> 该行已取到的日志全文(只传有文本的;取不到与没展开即中性提示,不挡安装)。 */
|
|
206
212
|
changelogs?: Record<string, string | null>;
|
|
207
|
-
/**
|
|
213
|
+
/** 轮次会话(有未终态行才画控制区,并决定在轮的行与总账吃不吃知识;不传按行相位回退旧口径,兼容旧快照)。 */
|
|
208
214
|
session?: unknown;
|
|
209
215
|
/** 知识账本(无轮次时行按知识展示;只做展示,不参与安装决策)。 */
|
|
210
216
|
inventory?: unknown;
|
package/dist/panel-batch.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
failureCopy,
|
|
17
17
|
isKnownFailureCode,
|
|
18
18
|
normalizePanelTheme,
|
|
19
|
+
readDiagTolerant,
|
|
19
20
|
renderUpdatePanelHTML,
|
|
20
21
|
themeTokensStyleFor
|
|
21
22
|
} from "./panel.js";
|
|
@@ -35,7 +36,7 @@ function buildBatchPhoneNames(prefix) {
|
|
|
35
36
|
prefsSave: p + ".batchPrefsSave"
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
|
-
function batchLedgerCounts(rows) {
|
|
39
|
+
function batchLedgerCounts(rows, inventory, session) {
|
|
39
40
|
const counts = {
|
|
40
41
|
updatable: 0,
|
|
41
42
|
installing: 0,
|
|
@@ -51,13 +52,21 @@ function batchLedgerCounts(rows) {
|
|
|
51
52
|
else if (phase === "failed") counts.failed += 1;
|
|
52
53
|
else if (phase === "installing") counts.installing += 1;
|
|
53
54
|
else if (phase === "ready") counts.updatable += 1;
|
|
54
|
-
else if (phase === "pending"
|
|
55
|
+
else if (phase === "pending")
|
|
56
|
+
counts[isInUnfinishedRound(session, row.key) ? "pending" : ledgerKnowledgeBucket(knowledgeEntryOf(inventory, row.key))] += 1;
|
|
57
|
+
else if (phase === "checking") counts.pending += 1;
|
|
55
58
|
else if (phase === "skipped") counts.skipped += 1;
|
|
56
59
|
else if (row.restartRequired === true) counts.restart += 1;
|
|
57
|
-
else counts.settled += 1;
|
|
60
|
+
else counts[worldUpdateVersion(row, knowledgeEntryOf(inventory, row.key), sessionUpdatedAtOf(session, row.key)) ? "updatable" : "settled"] += 1;
|
|
58
61
|
}
|
|
59
62
|
return counts;
|
|
60
63
|
}
|
|
64
|
+
function ledgerKnowledgeBucket(entry) {
|
|
65
|
+
const verdict = knowledgeVerdictOf(entry);
|
|
66
|
+
if (verdict === "update") return "updatable";
|
|
67
|
+
if (verdict === "current") return "settled";
|
|
68
|
+
return "pending";
|
|
69
|
+
}
|
|
61
70
|
function batchLedgerText(counts, lang) {
|
|
62
71
|
const l = langOf(lang);
|
|
63
72
|
const parts = [];
|
|
@@ -91,6 +100,13 @@ function knowledgeEntryOf(inventory, key) {
|
|
|
91
100
|
error: typeof e["error"] === "string" ? e["error"] : null
|
|
92
101
|
};
|
|
93
102
|
}
|
|
103
|
+
function knowledgeVerdictOf(entry) {
|
|
104
|
+
if (!entry) return "never";
|
|
105
|
+
if (entry.error) return "failed";
|
|
106
|
+
if (entry.latestVersion && entry.installedVersion) return entry.latestVersion !== entry.installedVersion ? "update" : "current";
|
|
107
|
+
if (entry.latestVersion && !entry.installedVersion) return "update";
|
|
108
|
+
return "never";
|
|
109
|
+
}
|
|
94
110
|
function langOf(lang) {
|
|
95
111
|
try {
|
|
96
112
|
return normalizeLangTag(typeof lang === "string" ? lang : resolveLang(lang));
|
|
@@ -105,34 +121,52 @@ function batchText(key, lang, values) {
|
|
|
105
121
|
return "";
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
|
-
function
|
|
109
|
-
if (isObject(session))
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
function sessionEntriesOf(session) {
|
|
125
|
+
if (!isObject(session)) return null;
|
|
126
|
+
const entries = session["entries"];
|
|
127
|
+
if (!Array.isArray(entries)) return null;
|
|
128
|
+
return entries.filter((item) => isObject(item));
|
|
129
|
+
}
|
|
130
|
+
function entryPhaseOf(entry) {
|
|
131
|
+
const phase = entry["phase"];
|
|
132
|
+
return typeof phase === "string" ? asBatchPhase(phase) : null;
|
|
133
|
+
}
|
|
134
|
+
function isUnfinishedEntry(entry) {
|
|
135
|
+
const phase = entryPhaseOf(entry);
|
|
136
|
+
return phase !== null && !isTerminalPhase(phase);
|
|
137
|
+
}
|
|
138
|
+
function isInUnfinishedRound(session, key) {
|
|
139
|
+
const entries = sessionEntriesOf(session);
|
|
140
|
+
if (entries === null) return false;
|
|
141
|
+
return entries.some((entry) => entry["key"] === key && isUnfinishedEntry(entry));
|
|
142
|
+
}
|
|
143
|
+
function sessionUpdatedAtOf(session, key) {
|
|
144
|
+
const entries = sessionEntriesOf(session);
|
|
145
|
+
if (entries === null) return null;
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
if (entry["key"] !== key) continue;
|
|
148
|
+
const at = entry["updatedAt"];
|
|
149
|
+
if (typeof at === "number" && Number.isFinite(at)) return at;
|
|
119
150
|
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function worldUpdateVersion(row, knowledge, sessionUpdatedAt) {
|
|
154
|
+
const phase = asBatchPhase(row.phase);
|
|
155
|
+
if (phase !== "current" && !(phase === "done" && row.restartRequired !== true)) return null;
|
|
156
|
+
if (!knowledge || knowledge.error) return null;
|
|
157
|
+
if (sessionUpdatedAt !== null && !(knowledge.lastCheckedAt > sessionUpdatedAt)) return null;
|
|
158
|
+
const parts = batchRowVersionParts(row, knowledge);
|
|
159
|
+
return parts.hasUpdate && parts.latest ? parts.latest : null;
|
|
160
|
+
}
|
|
161
|
+
function hasUnfinishedRows(rows, session) {
|
|
162
|
+
const entries = sessionEntriesOf(session);
|
|
163
|
+
if (entries !== null) return entries.some(isUnfinishedEntry);
|
|
120
164
|
if (rows.length === 0) return false;
|
|
121
165
|
return !rows.every((row) => isTerminalPhase(asBatchPhase(row.phase)));
|
|
122
166
|
}
|
|
123
167
|
function unfinishedCount(rows, session) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (Array.isArray(entries)) {
|
|
127
|
-
let n = 0;
|
|
128
|
-
for (const item of entries) {
|
|
129
|
-
if (!isObject(item)) continue;
|
|
130
|
-
const phase = item["phase"];
|
|
131
|
-
if (typeof phase === "string" && !isTerminalPhase(asBatchPhase(phase))) n += 1;
|
|
132
|
-
}
|
|
133
|
-
return n;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
168
|
+
const entries = sessionEntriesOf(session);
|
|
169
|
+
if (entries !== null) return entries.filter(isUnfinishedEntry).length;
|
|
136
170
|
return rows.filter((row) => !isTerminalPhase(asBatchPhase(row.phase))).length;
|
|
137
171
|
}
|
|
138
172
|
function batchRowStatus(row, skippedVersion, lang) {
|
|
@@ -162,17 +196,10 @@ function batchRowStatus(row, skippedVersion, lang) {
|
|
|
162
196
|
}
|
|
163
197
|
function batchRowKnowledgeText(row, entry, lang) {
|
|
164
198
|
if (asBatchPhase(row.phase) !== "pending" || !entry) return null;
|
|
165
|
-
|
|
166
|
-
if (entry.latestVersion
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (entry.latestVersion && entry.installedVersion && entry.latestVersion === entry.installedVersion) {
|
|
170
|
-
return batchText("batch.row.current", lang);
|
|
171
|
-
}
|
|
172
|
-
if (entry.latestVersion && !entry.installedVersion) {
|
|
173
|
-
return batchText("batch.row.update", lang, { version: entry.latestVersion });
|
|
174
|
-
}
|
|
175
|
-
if (entry.lastCheckedAt > 0) return batchText("batch.row.current", lang);
|
|
199
|
+
const verdict = knowledgeVerdictOf(entry);
|
|
200
|
+
if (verdict === "update" && entry.latestVersion) return batchText("batch.row.update", lang, { version: entry.latestVersion });
|
|
201
|
+
if (verdict === "current") return batchText("batch.row.current", lang);
|
|
202
|
+
if (verdict === "failed") return batchText("batch.row.failed", lang);
|
|
176
203
|
return batchText("batch.row.never", lang);
|
|
177
204
|
}
|
|
178
205
|
function batchRowVersionParts(row, knowledge) {
|
|
@@ -188,14 +215,15 @@ function batchRowVersionParts(row, knowledge) {
|
|
|
188
215
|
const latest = knowledge && typeof knowledge.latestVersion === "string" && knowledge.latestVersion ? knowledge.latestVersion : null;
|
|
189
216
|
if (current && latest && latest !== current) return { current, latest, hasUpdate: true };
|
|
190
217
|
if (current) return { current, latest: null, hasUpdate: false };
|
|
191
|
-
if (latest) return { current:
|
|
218
|
+
if (latest) return { current: null, latest, hasUpdate: true };
|
|
192
219
|
return { current: null, latest: null, hasUpdate: false };
|
|
193
220
|
}
|
|
194
221
|
function batchRowVersionHTML(row, knowledge) {
|
|
195
222
|
const parts = batchRowVersionParts(row, knowledge);
|
|
196
223
|
if (parts.hasUpdate && parts.latest) {
|
|
197
224
|
const cur = parts.current ?? "?";
|
|
198
|
-
|
|
225
|
+
const newCls = asBatchPhase(row.phase) === "done" ? "dsh-upd-bver-new-ok" : "dsh-upd-bver-new";
|
|
226
|
+
return '<span class="dsh-upd-bver"><span class="dsh-upd-bver-cur">' + escapeHtml(cur) + '</span><span class="dsh-upd-bver-arrow" aria-hidden="true"> \u2192 </span><span class="' + newCls + '">' + escapeHtml(parts.latest) + "</span></span>";
|
|
199
227
|
}
|
|
200
228
|
if (parts.current) return '<span class="dsh-upd-bver">' + escapeHtml(parts.current) + "</span>";
|
|
201
229
|
return "";
|
|
@@ -216,7 +244,7 @@ function renderBatchPanelHTML(input) {
|
|
|
216
244
|
const expandedKey = typeof input.expandedKey === "string" && input.expandedKey ? input.expandedKey : null;
|
|
217
245
|
const lastError = typeof input.lastError === "string" && input.lastError ? input.lastError : null;
|
|
218
246
|
const loaded = input.loaded === void 0 ? rows.length > 0 : input.loaded === true;
|
|
219
|
-
const counts = batchLedgerCounts(rows);
|
|
247
|
+
const counts = batchLedgerCounts(rows, input.inventory, input.session);
|
|
220
248
|
const installing = counts.installing > 0;
|
|
221
249
|
const macroBusy = input.inFlight === true || installing;
|
|
222
250
|
const total = rows.length;
|
|
@@ -238,7 +266,8 @@ function renderBatchPanelHTML(input) {
|
|
|
238
266
|
changelogMarkdown: null,
|
|
239
267
|
lang,
|
|
240
268
|
knowledge: null,
|
|
241
|
-
inRound: false
|
|
269
|
+
inRound: false,
|
|
270
|
+
worldLatest: null
|
|
242
271
|
};
|
|
243
272
|
const parts = [];
|
|
244
273
|
parts.push('<div class="dsh-upd-batch-head">');
|
|
@@ -257,24 +286,13 @@ function renderBatchPanelHTML(input) {
|
|
|
257
286
|
const showControls = total > 0 && hasUnfinishedRows(rows, session ?? void 0);
|
|
258
287
|
const prefs = isObject(input.prefs) ? input.prefs : null;
|
|
259
288
|
const checkOnOpen = prefs === null ? null : prefs["checkOnOpen"] === false ? false : true;
|
|
260
|
-
function roundEntryPhase(key) {
|
|
261
|
-
if (!session) return null;
|
|
262
|
-
const entries = session["entries"];
|
|
263
|
-
if (!Array.isArray(entries)) return null;
|
|
264
|
-
for (const item of entries) {
|
|
265
|
-
if (!isObject(item)) continue;
|
|
266
|
-
const e = item;
|
|
267
|
-
if (e["key"] === key && typeof e["phase"] === "string") return e["phase"];
|
|
268
|
-
}
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
289
|
parts.push('<div class="dsh-upd-btable">');
|
|
272
290
|
for (const row of rows) {
|
|
273
291
|
const raw = skippedVersions[row.key];
|
|
274
292
|
const skipped = typeof raw === "string" && raw ? raw : null;
|
|
275
293
|
const expanded = row.key === expandedKey;
|
|
276
|
-
const
|
|
277
|
-
const
|
|
294
|
+
const inRound = isInUnfinishedRound(input.session, row.key);
|
|
295
|
+
const knowledge = !inRound ? knowledgeEntryOf(input.inventory, row.key) : null;
|
|
278
296
|
parts.push(
|
|
279
297
|
rowSetHTML(row, {
|
|
280
298
|
...ctx,
|
|
@@ -283,8 +301,9 @@ function renderBatchPanelHTML(input) {
|
|
|
283
301
|
notice: expanded && row.key === detailNoticeKey ? notice : null,
|
|
284
302
|
changelogMarkdown: expanded && typeof changelogs[row.key] === "string" ? changelogs[row.key] : null,
|
|
285
303
|
lang,
|
|
286
|
-
knowledge
|
|
287
|
-
inRound
|
|
304
|
+
knowledge,
|
|
305
|
+
inRound,
|
|
306
|
+
worldLatest: worldUpdateVersion(row, knowledge, sessionUpdatedAtOf(input.session, row.key))
|
|
288
307
|
})
|
|
289
308
|
);
|
|
290
309
|
}
|
|
@@ -386,7 +405,7 @@ function rowSetHTML(row, ctx) {
|
|
|
386
405
|
const queued = isQueuedRow(row);
|
|
387
406
|
const position = queued ? queuePositionOf(row) : null;
|
|
388
407
|
const versionHTML = batchRowVersionHTML(row, ctx.knowledge);
|
|
389
|
-
const rowStatusText = queued ? batchQueuedStatus(position, ctx.lang) : ctx.inRound ? batchRowKnowledgeText(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang) : batchRowPendingStatus(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang);
|
|
408
|
+
const rowStatusText = queued ? batchQueuedStatus(position, ctx.lang) : ctx.worldLatest !== null ? batchText("batch.row.update", ctx.lang, { version: ctx.worldLatest }) : ctx.inRound ? batchRowKnowledgeText(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang) : batchRowPendingStatus(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang);
|
|
390
409
|
const actions = [];
|
|
391
410
|
const off = ctx.inFlight ? " disabled" : "";
|
|
392
411
|
if (phase === "installing") {
|
|
@@ -427,7 +446,7 @@ function rowSetHTML(row, ctx) {
|
|
|
427
446
|
const withCode = escaped.includes(codeEsc) ? escaped.replace(codeEsc, "<code>" + codeEsc + "</code>") : escaped;
|
|
428
447
|
return '<span class="dsh-upd-bfail">' + withCode + "</span>";
|
|
429
448
|
})() : "";
|
|
430
|
-
const main = '<button type="button" class="dsh-upd-brow-main" data-act="toggle-details" data-key="' + keyAttr + '" aria-expanded="' + (ctx.expanded ? "true" : "false") + '"><span class="dsh-upd-updot" data-tone="' + dotToneOf(row, phase, skipped, queued) + '"></span><span class="dsh-upd-bname">' + escapeHtml(titleOf(row, ctx.titles)) + "</span>" + versionHTML + '<span class="dsh-upd-bstat">' + escapeHtml(rowStatusText) + (ctx.inRound && !queued ? "\uFF08" + escapeHtml(batchText("batch.row.unfinished-tag", ctx.lang)) + "\uFF09" : "") + "</span>" + failLine + "</button>";
|
|
449
|
+
const main = '<button type="button" class="dsh-upd-brow-main" data-act="toggle-details" data-key="' + keyAttr + '" aria-expanded="' + (ctx.expanded ? "true" : "false") + '"><span class="dsh-upd-updot" data-tone="' + dotToneOf(row, phase, skipped, queued, ctx.worldLatest) + '"></span><span class="dsh-upd-bname">' + escapeHtml(titleOf(row, ctx.titles)) + "</span>" + versionHTML + '<span class="dsh-upd-bstat">' + escapeHtml(rowStatusText) + (ctx.inRound && !queued ? "\uFF08" + escapeHtml(batchText("batch.row.unfinished-tag", ctx.lang)) + "\uFF09" : "") + "</span>" + failLine + "</button>";
|
|
431
450
|
const detail = ctx.expanded ? '<div class="dsh-upd-bdetail" data-key="' + keyAttr + '">' + detailActionsHTML(row, ctx) + (ctx.notice !== null ? '<div class="dsh-upd-bdetail-notice" role="status">' + escapeHtml(ctx.notice) + "</div>" : "") + detailHTML(row, ctx) + "</div>" : "";
|
|
432
451
|
return '<div class="dsh-upd-brow-set" data-key="' + keyAttr + '"><div class="dsh-upd-brow" data-phase="' + escapeHtml(phase) + '" data-key="' + keyAttr + '"' + (queued ? ' data-queued="1"' : "") + ">" + main + '<span class="dsh-upd-brow-actions">' + actions.join("") + "</span></div>" + detail + "</div>";
|
|
433
452
|
}
|
|
@@ -449,6 +468,7 @@ function detailHTML(row, ctx) {
|
|
|
449
468
|
theme: ctx.theme,
|
|
450
469
|
themeTokens: ctx.themeTokens,
|
|
451
470
|
profileName: typeof row.profileName === "string" && row.profileName ? row.profileName : null,
|
|
471
|
+
hostKind: typeof row.hostKind === "string" && row.hostKind ? row.hostKind : null,
|
|
452
472
|
actions: "none"
|
|
453
473
|
}, ctx.lang);
|
|
454
474
|
return stripActionButtons(html);
|
|
@@ -477,6 +497,11 @@ function detailActionsHTML(row, ctx) {
|
|
|
477
497
|
'<button type="button" data-act="row-install" data-key="' + keyAttr + '" data-primary="1"' + off + ">" + escapeHtml(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : version !== null ? batchText("batch.row-action.install-version", ctx.lang, { version }) : batchText("batch.row-action.install-generic", ctx.lang)) + "</button>"
|
|
478
498
|
);
|
|
479
499
|
}
|
|
500
|
+
if (phase !== "installing" && singleCheckPhoneOf(row) !== null) {
|
|
501
|
+
buttons.push(
|
|
502
|
+
'<button type="button" data-act="row-check" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.action.check", ctx.lang)) + "</button>"
|
|
503
|
+
);
|
|
504
|
+
}
|
|
480
505
|
if (!queued && phase === "ready" && ctx.skipped === null && version !== null) {
|
|
481
506
|
buttons.push(
|
|
482
507
|
'<button type="button" data-act="row-skip" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.skip", ctx.lang)) + "</button>"
|
|
@@ -498,11 +523,12 @@ function detailActionsHTML(row, ctx) {
|
|
|
498
523
|
function stripActionButtons(html) {
|
|
499
524
|
return html.replace(/<button\b[^>]*data-action=[^>]*>[\s\S]*?<\/button>/g, "");
|
|
500
525
|
}
|
|
501
|
-
function dotToneOf(row, phase, skipped, queued) {
|
|
526
|
+
function dotToneOf(row, phase, skipped, queued, worldLatest = null) {
|
|
502
527
|
if (phase === "failed" && !queued) return "bad";
|
|
503
528
|
if (phase === "installing" || phase === "checking") return "busy";
|
|
504
529
|
if (queued) return "warn";
|
|
505
530
|
if (row.restartRequired === true) return "warn";
|
|
531
|
+
if (worldLatest !== null) return "todo";
|
|
506
532
|
if (phase === "ready") return skipped === null ? "todo" : "idle";
|
|
507
533
|
if (phase === "current" || phase === "done") return "ok";
|
|
508
534
|
return "idle";
|
|
@@ -534,11 +560,15 @@ const UPDATE_BATCH_PANEL_CSS = [
|
|
|
534
560
|
".dsh-upd-bver-cur{opacity:.65}",
|
|
535
561
|
".dsh-upd-bver-arrow{opacity:.6}",
|
|
536
562
|
".dsh-upd-bver-new{color:var(--dsh-update-new-text,#dc2626);font-weight:700}",
|
|
563
|
+
".dsh-upd-bver-new-ok{color:var(--dsh-update-new-ok-text,#047857);font-weight:700}",
|
|
537
564
|
".dsh-upd-bstat{font-size:12.5px;opacity:.9;min-width:0;overflow-wrap:anywhere}",
|
|
538
565
|
".dsh-upd-bfail{grid-column:1/-1;display:block;font-size:12.5px;color:var(--dsh-update-bad-border,#dc2626)}",
|
|
539
566
|
".dsh-upd-batch{--dsh-update-new-text:#dc2626}",
|
|
567
|
+
".dsh-upd-batch{--dsh-update-new-ok-text:#047857}",
|
|
540
568
|
"@media (prefers-color-scheme: dark){.dsh-upd-batch{--dsh-update-new-text:#f87171}}",
|
|
569
|
+
"@media (prefers-color-scheme: dark){.dsh-upd-batch{--dsh-update-new-ok-text:#34d399}}",
|
|
541
570
|
"@media (forced-colors: active){.dsh-upd-bver-new{color:CanvasText}}",
|
|
571
|
+
"@media (forced-colors: active){.dsh-upd-bver-new-ok{color:CanvasText}}",
|
|
542
572
|
".dsh-upd-blist{font-size:12.5px;margin-top:2px}",
|
|
543
573
|
".dsh-upd-batch-ledger{margin:8px 0 0;font-size:13px;opacity:.85}",
|
|
544
574
|
".dsh-upd-batch-more{margin-top:6px}",
|
|
@@ -576,7 +606,10 @@ const UPDATE_BATCH_PANEL_ARCHIVE_CSS = [
|
|
|
576
606
|
'.dsh-upd[data-theme="archive"] .dsh-upd-bver{font-family:var(--dsh-update-font-mono);color:var(--dsh-update-text-muted)}',
|
|
577
607
|
'.dsh-upd[data-theme="archive"] .dsh-upd-bver-cur{opacity:.75}',
|
|
578
608
|
'.dsh-upd[data-theme="archive"] .dsh-upd-bver-new{color:var(--dsh-update-new-text,#b3261e)}',
|
|
609
|
+
'.dsh-upd[data-theme="archive"] .dsh-upd-bver-new-ok{color:var(--dsh-update-new-ok-text,#1a7f37)}',
|
|
610
|
+
'@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"] .dsh-upd-batch{--dsh-update-new-ok-text:#8fd6a4}}',
|
|
579
611
|
'.dsh-upd[data-theme="archive"] .dsh-upd-batch{--dsh-update-new-text:#b3261e}',
|
|
612
|
+
'.dsh-upd[data-theme="archive"] .dsh-upd-batch{--dsh-update-new-ok-text:#1a7f37}',
|
|
580
613
|
'@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"] .dsh-upd-batch{--dsh-update-new-text:#ef8a7d}}',
|
|
581
614
|
'.dsh-upd[data-theme="archive"] .dsh-upd-batch-ledger{color:var(--dsh-update-text-muted)}',
|
|
582
615
|
'.dsh-upd[data-theme="archive"] .dsh-upd-batch-sum{color:var(--dsh-update-text)}',
|
|
@@ -622,6 +655,12 @@ function queuedRequestIdOf(row) {
|
|
|
622
655
|
}
|
|
623
656
|
return null;
|
|
624
657
|
}
|
|
658
|
+
function singleCheckPhoneOf(row) {
|
|
659
|
+
const phones = isObject(row.phoneNames) ? row.phoneNames : null;
|
|
660
|
+
if (!phones) return null;
|
|
661
|
+
const check = phones["updateCheck"];
|
|
662
|
+
return typeof check === "string" && check ? check : null;
|
|
663
|
+
}
|
|
625
664
|
function cancelQueuePhoneOf(row) {
|
|
626
665
|
const phones = isObject(row.phoneNames) ? row.phoneNames : null;
|
|
627
666
|
if (!phones) return null;
|
|
@@ -694,6 +733,21 @@ function jobFailureOf(row) {
|
|
|
694
733
|
const detail = message.slice(at + 1).trim();
|
|
695
734
|
return { code: code || null, detail: detail || null };
|
|
696
735
|
}
|
|
736
|
+
function diagStructuredSuffix(diagValue) {
|
|
737
|
+
const t = readDiagTolerant(diagValue);
|
|
738
|
+
const parts = [];
|
|
739
|
+
if (t.stage) parts.push("stage=" + t.stage);
|
|
740
|
+
if (t.route) parts.push("route=" + t.route);
|
|
741
|
+
if (t.method) parts.push("method=" + t.method);
|
|
742
|
+
if (typeof t.httpStatus === "number") parts.push("HTTP=" + String(t.httpStatus));
|
|
743
|
+
if (typeof t.exitCode === "number") parts.push("exit=" + String(t.exitCode));
|
|
744
|
+
if (typeof t.latencyMs === "number") parts.push("latency=" + String(t.latencyMs) + "ms");
|
|
745
|
+
if (t.registryHost) parts.push("source=" + t.registryHost);
|
|
746
|
+
if (t.action) parts.push("action=" + t.action);
|
|
747
|
+
if (t.requestId) parts.push("request=" + t.requestId);
|
|
748
|
+
if (t.checkId) parts.push("check=" + t.checkId);
|
|
749
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : null;
|
|
750
|
+
}
|
|
697
751
|
function diagTextOf(row, lang) {
|
|
698
752
|
const snapshot = isObject(row.snapshot) ? row.snapshot : null;
|
|
699
753
|
const queue = isObject(row.queue) ? row.queue : null;
|
|
@@ -701,7 +755,9 @@ function diagTextOf(row, lang) {
|
|
|
701
755
|
const text = (value) => typeof value === "string" && value ? value : null;
|
|
702
756
|
const job = jobFailureOf(row);
|
|
703
757
|
const diagDetail = diag ? text(diag["detail"]) : null;
|
|
704
|
-
const
|
|
758
|
+
const baseDetail = diagDetail !== null ? diagDetail : job !== null && job.detail !== null ? job.detail + batchText("batch.diag.source-job", langOf(lang)) : null;
|
|
759
|
+
const structured = diagStructuredSuffix(row.diag);
|
|
760
|
+
const detail = baseDetail !== null && structured !== null ? baseDetail + " [" + structured + "]" : baseDetail ?? structured;
|
|
705
761
|
const input = {
|
|
706
762
|
pluginId: pluginIdOf(row),
|
|
707
763
|
code: job !== null && job.code !== null ? job.code : row.error || "check-failed",
|
|
@@ -828,6 +884,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
828
884
|
let notice = null;
|
|
829
885
|
let noticeKey = null;
|
|
830
886
|
let inFlight = false;
|
|
887
|
+
let inFlightIsCheck = false;
|
|
831
888
|
let loaded = false;
|
|
832
889
|
let refreshing = false;
|
|
833
890
|
let confirmCancelArmed = false;
|
|
@@ -1072,10 +1129,25 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1072
1129
|
return;
|
|
1073
1130
|
}
|
|
1074
1131
|
if (reply["ok"] === true) {
|
|
1075
|
-
const
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1132
|
+
const hasSession = "session" in reply && reply["session"] !== void 0;
|
|
1133
|
+
const hasRows = "rows" in reply && reply["rows"] !== void 0;
|
|
1134
|
+
if (!hasSession && !hasRows) {
|
|
1135
|
+
if (isObject(reply["prefs"])) lastPrefs = reply["prefs"];
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
if (hasSession) {
|
|
1139
|
+
const session = normalizeBatchSession(reply["session"]);
|
|
1140
|
+
lastSession = session;
|
|
1141
|
+
if (hasRows) {
|
|
1142
|
+
const parsed = readRows(reply["rows"]);
|
|
1143
|
+
rows = parsed.length > 0 ? parsed : rowsFromSession(session);
|
|
1144
|
+
} else {
|
|
1145
|
+
rows = rowsFromSession(session);
|
|
1146
|
+
}
|
|
1147
|
+
} else {
|
|
1148
|
+
const parsed = readRows(reply["rows"]);
|
|
1149
|
+
if (parsed.length > 0) rows = parsed;
|
|
1150
|
+
}
|
|
1079
1151
|
if (isObject(reply["inventory"])) {
|
|
1080
1152
|
lastInventory = reply["inventory"];
|
|
1081
1153
|
const at = reply["inventory"]["updatedAt"];
|
|
@@ -1110,6 +1182,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1110
1182
|
async function phone(name, args, busyRowKey = null) {
|
|
1111
1183
|
if (!mounted || inFlight) return null;
|
|
1112
1184
|
inFlight = true;
|
|
1185
|
+
inFlightIsCheck = name === phones.check;
|
|
1113
1186
|
clearNotice();
|
|
1114
1187
|
render();
|
|
1115
1188
|
let out = null;
|
|
@@ -1129,6 +1202,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1129
1202
|
out = null;
|
|
1130
1203
|
} finally {
|
|
1131
1204
|
inFlight = false;
|
|
1205
|
+
inFlightIsCheck = false;
|
|
1132
1206
|
if (mounted) render();
|
|
1133
1207
|
}
|
|
1134
1208
|
maybeAutoRowChangelog(expandedKey);
|
|
@@ -1207,6 +1281,10 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1207
1281
|
const row = typeof key === "string" && key ? rowOfKey(key) : null;
|
|
1208
1282
|
const version = row ? latestVersionOf(row) : null;
|
|
1209
1283
|
if (!row || !version) return;
|
|
1284
|
+
if (!validReleaseVersion(version)) {
|
|
1285
|
+
render();
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1210
1288
|
try {
|
|
1211
1289
|
skipStoreFor(pluginIdOf(row)).skip(version);
|
|
1212
1290
|
} catch {
|
|
@@ -1227,6 +1305,49 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1227
1305
|
render();
|
|
1228
1306
|
return;
|
|
1229
1307
|
}
|
|
1308
|
+
// 「查这一家」(#84):走该家自己的单插件查新版电话(行 phoneNames 透传;老宿主没给即无入口)。
|
|
1309
|
+
// 单插件回包形状与批量回包不同,不进 applyReply:成功靠随后一次 refresh 把新快照读回来;
|
|
1310
|
+
// 失败只在该行留一句失败回执(failureCopy 已有键),行相位不动,不编造。
|
|
1311
|
+
case "row-check": {
|
|
1312
|
+
const row = typeof key === "string" && key ? rowOfKey(key) : null;
|
|
1313
|
+
if (!row) return;
|
|
1314
|
+
const phoneName = singleCheckPhoneOf(row);
|
|
1315
|
+
if (phoneName === null) return;
|
|
1316
|
+
if (inFlight) return;
|
|
1317
|
+
inFlight = true;
|
|
1318
|
+
inFlightIsCheck = true;
|
|
1319
|
+
clearNotice();
|
|
1320
|
+
render();
|
|
1321
|
+
try {
|
|
1322
|
+
const reply = await call(phoneName, { includeEnv: true, includeQueue: true });
|
|
1323
|
+
if (!mounted) return;
|
|
1324
|
+
if (isObject(reply) && reply["ok"] !== true) {
|
|
1325
|
+
const rec = reply;
|
|
1326
|
+
const kind2 = typeof rec["errorKind"] === "string" && rec["errorKind"].trim() ? rec["errorKind"].trim() : "";
|
|
1327
|
+
const err = typeof rec["error"] === "string" && rec["error"].trim() ? rec["error"].trim() : "";
|
|
1328
|
+
const code = kind2 || err || "check-failed";
|
|
1329
|
+
const copy = failureCopy(code, currentLang());
|
|
1330
|
+
say(copy ? copy.zh + "\uFF5C" + copy.act : code, row.key);
|
|
1331
|
+
}
|
|
1332
|
+
} catch {
|
|
1333
|
+
if (mounted) {
|
|
1334
|
+
const copy = failureCopy("check-failed", currentLang());
|
|
1335
|
+
say(copy ? copy.zh + "\uFF5C" + copy.act : "check-failed", row.key);
|
|
1336
|
+
}
|
|
1337
|
+
} finally {
|
|
1338
|
+
inFlight = false;
|
|
1339
|
+
inFlightIsCheck = false;
|
|
1340
|
+
}
|
|
1341
|
+
try {
|
|
1342
|
+
for (const k of [...rowFailedAt.keys()]) {
|
|
1343
|
+
if (k === row.key || k.startsWith(row.key + "\0")) rowFailedAt.delete(k);
|
|
1344
|
+
}
|
|
1345
|
+
} catch {
|
|
1346
|
+
}
|
|
1347
|
+
await refresh();
|
|
1348
|
+
maybeAutoRowChangelog(expandedKey);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1230
1351
|
// 「取消排队」:走**该家自己的**单插件安装电话(宿主支持 cancelQueued:只撤自己那条 waiting,
|
|
1231
1352
|
// 不碰别家、也不碰正在装的拥有者)。批量取消会清掉整轮会话,所以这里绝不用它顶。
|
|
1232
1353
|
case "row-cancel-queue": {
|
|
@@ -1440,7 +1561,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1440
1561
|
}
|
|
1441
1562
|
const timer = getTimer();
|
|
1442
1563
|
const handle = timer.set(() => {
|
|
1443
|
-
if (inFlight) return;
|
|
1564
|
+
if (inFlight && inFlightIsCheck) return;
|
|
1444
1565
|
void refresh();
|
|
1445
1566
|
}, pollMs);
|
|
1446
1567
|
try {
|
package/dist/panel.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface UpdateThemeTokens {
|
|
|
39
39
|
busyText?: string;
|
|
40
40
|
/** 有新版版本号的醒目红(#71 Q1 专用;缺省跟随坏项红的对比度口径,深浅主题另覆)。 */
|
|
41
41
|
newText?: string;
|
|
42
|
+
/** 已装好新版版本号的绿色(#71 状态跟随修订:done 行走绿,其余走红;深浅主题另覆)。 */
|
|
43
|
+
newOkText?: string;
|
|
42
44
|
fontSans?: string;
|
|
43
45
|
fontSerif?: string;
|
|
44
46
|
fontMono?: string;
|
package/dist/panel.js
CHANGED
|
@@ -39,6 +39,7 @@ const THEME_TOKEN_VARS = {
|
|
|
39
39
|
busyBorder: "--dsh-update-busy-border",
|
|
40
40
|
busyText: "--dsh-update-busy-text",
|
|
41
41
|
newText: "--dsh-update-new-text",
|
|
42
|
+
newOkText: "--dsh-update-new-ok-text",
|
|
42
43
|
fontSans: "--dsh-update-font-sans",
|
|
43
44
|
fontSerif: "--dsh-update-font-serif",
|
|
44
45
|
fontMono: "--dsh-update-font-mono",
|
|
@@ -74,7 +75,8 @@ const THEME_TOKEN_COLOR_KEYS = /* @__PURE__ */ new Set([
|
|
|
74
75
|
"busyBg",
|
|
75
76
|
"busyBorder",
|
|
76
77
|
"busyText",
|
|
77
|
-
"newText"
|
|
78
|
+
"newText",
|
|
79
|
+
"newOkText"
|
|
78
80
|
]);
|
|
79
81
|
function themeTokensError(raw) {
|
|
80
82
|
return new Error(`[dsh-plugin-update] \u4E3B\u9898\u53C2\u6570 themeTokens \u975E\u6CD5\uFF1A\u53EA\u6536\u5DF2\u77E5 token \u952E\uFF08\u989C\u8272\u7528 hex \u6216\u82F1\u6587\u540D\uFF0C\u5B57\u4F53/\u5706\u89D2/\u9634\u5F71/\u5C3A\u5BF8\u4E3A\u5B89\u5168 CSS \u503C\uFF0CentryScale \u4E3A\u5927\u4E8E 0 \u7684\u6709\u9650\u6570\uFF09\uFF08\u6536\u5230 ${JSON.stringify(raw ?? null)})`);
|
|
@@ -479,24 +481,6 @@ function panelViewModelCore(input, lang) {
|
|
|
479
481
|
queueNote
|
|
480
482
|
};
|
|
481
483
|
}
|
|
482
|
-
if (snapshot.blockedReason === "pending-restart") {
|
|
483
|
-
const latest = snapshot.latestVersion ?? snapshot.installedVersion ?? "";
|
|
484
|
-
return {
|
|
485
|
-
banner: {
|
|
486
|
-
kind: "restart",
|
|
487
|
-
// 文案照原型(archive.html:329):不带 emoji——警示由横幅左侧的手绘 SVG 标承担,
|
|
488
|
-
// 印章在状态一侧,两者各司其职,不再三重标记。
|
|
489
|
-
title: copyText("panel.banner.restart-title", l, { latest }),
|
|
490
|
-
action: blockedCopy("pending-restart", l)?.action ?? ""
|
|
491
|
-
},
|
|
492
|
-
installEnabled: false,
|
|
493
|
-
installLabel: copyText("panel.action.install", l),
|
|
494
|
-
skippedLatest: false,
|
|
495
|
-
showManual: manual ? true : false,
|
|
496
|
-
showReset: false,
|
|
497
|
-
queueNote
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
484
|
if (jobState === "installing" || jobState === "verifying") {
|
|
501
485
|
const ver = typeof job?.targetVersion === "string" && job.targetVersion.trim() ? job.targetVersion.trim() : "";
|
|
502
486
|
let installingTitle = copyText("panel.banner.installing-title", l, { version: ver || " " });
|
|
@@ -534,6 +518,24 @@ function panelViewModelCore(input, lang) {
|
|
|
534
518
|
queueNote
|
|
535
519
|
};
|
|
536
520
|
}
|
|
521
|
+
if (snapshot.blockedReason === "pending-restart") {
|
|
522
|
+
const latest = snapshot.latestVersion ?? snapshot.installedVersion ?? "";
|
|
523
|
+
return {
|
|
524
|
+
banner: {
|
|
525
|
+
kind: "restart",
|
|
526
|
+
// 文案照原型(archive.html:329):不带 emoji——警示由横幅左侧的手绘 SVG 标承担,
|
|
527
|
+
// 印章在状态一侧,两者各司其职,不再三重标记。
|
|
528
|
+
title: copyText("panel.banner.restart-title", l, { latest }),
|
|
529
|
+
action: blockedCopy("pending-restart", l)?.action ?? ""
|
|
530
|
+
},
|
|
531
|
+
installEnabled: false,
|
|
532
|
+
installLabel: copyText("panel.action.install", l),
|
|
533
|
+
skippedLatest: false,
|
|
534
|
+
showManual: manual ? true : false,
|
|
535
|
+
showReset: false,
|
|
536
|
+
queueNote
|
|
537
|
+
};
|
|
538
|
+
}
|
|
537
539
|
if (snapshot.blockedReason) {
|
|
538
540
|
const copy = blockedCopy(snapshot.blockedReason, l);
|
|
539
541
|
const stop = l === "en" ? "." : "\u3002";
|
|
@@ -696,6 +698,15 @@ const UPDATE_PANEL_CSS = [
|
|
|
696
698
|
'.dsh-upd-actions button:first-child:not([aria-busy="true"])::after{content:"";display:inline-block;width:11px;height:11px;margin-left:8px;visibility:hidden}',
|
|
697
699
|
".dsh-upd-banner{min-height:1.2em}",
|
|
698
700
|
".dsh-upd-strip{min-height:48px}",
|
|
701
|
+
// —— 查新版瞬时抖动补强:忙闲两帧同高 + 横幅不动(只追加覆盖,不改上面既有串)——
|
|
702
|
+
".dsh-upd-actions{align-content:flex-start}",
|
|
703
|
+
".dsh-upd-actions button{white-space:nowrap}",
|
|
704
|
+
".dsh-upd-actions button:first-child{min-width:10em}",
|
|
705
|
+
'.dsh-upd-actions button[data-primary="1"]{min-width:9em}',
|
|
706
|
+
".dsh-upd-banner{min-height:3.4em;display:flex;flex-direction:column;justify-content:center}",
|
|
707
|
+
".dsh-upd-banner{animation:none}",
|
|
708
|
+
".dsh-upd-copy{min-height:1.75em}",
|
|
709
|
+
".dsh-upd-copy--empty{visibility:hidden}",
|
|
699
710
|
"@media (prefers-color-scheme: dark){.dsh-upd{--dsh-update-text:#e5e7eb;--dsh-update-bg:#111827;--dsh-update-border:#374151;",
|
|
700
711
|
"--dsh-update-button-bg:#1f2937;--dsh-update-bg-soft:#1f2937;--dsh-update-primary:#3b82f6;--dsh-update-focus:#93c5fd;",
|
|
701
712
|
// 横幅深色覆盖:底色用低透明度同色系(不是浅色原值),边线提亮,保证「深底浅字」可读。
|
|
@@ -912,9 +923,12 @@ function renderUpdatePanelKernel(input, view, lang) {
|
|
|
912
923
|
const busyAct = input.busyAct;
|
|
913
924
|
const checkBusy = busyAct === "check";
|
|
914
925
|
const installBusy = busyAct === "install";
|
|
926
|
+
const jobState = snapshot?.job?.state;
|
|
927
|
+
const snapshotBusy = jobState === "installing" || jobState === "verifying";
|
|
928
|
+
const macroBusy = busyAct === "check" || busyAct === "install" || snapshotBusy;
|
|
915
929
|
actions.push('<div class="dsh-upd-actions">');
|
|
916
930
|
actions.push(
|
|
917
|
-
(checkBusy ? `<button type="button" data-action="check" disabled aria-busy="true" title="${escapeHtml(copyText("panel.action.checking-busy-title", l))}">${escapeHtml(copyText("panel.action.checking-busy", l))}</button>` : `<button type="button" data-action="check" title="${escapeHtml(copyText("panel.action.check-title", l))}">${escapeHtml(copyText("panel.action.check", l))}</button>`) + (installBusy ? `<button type="button" data-action="install" data-primary="1" disabled aria-busy="true" title="${escapeHtml(copyText("panel.action.installing-busy-title", l))}">${escapeHtml(copyText("panel.action.installing-busy", l))}</button>` : `<button type="button" data-action="install" data-primary="1" title="${escapeHtml(copyText("panel.action.install-title", l))}"${view.installEnabled ? "" : "
|
|
931
|
+
(checkBusy ? `<button type="button" data-action="check" disabled aria-busy="true" title="${escapeHtml(copyText("panel.action.checking-busy-title", l))}">${escapeHtml(copyText("panel.action.checking-busy", l))}</button>` : `<button type="button" data-action="check" title="${escapeHtml(copyText("panel.action.check-title", l))}"${macroBusy ? " disabled" : ""}>${escapeHtml(copyText("panel.action.check", l))}</button>`) + (installBusy ? `<button type="button" data-action="install" data-primary="1" disabled aria-busy="true" title="${escapeHtml(copyText("panel.action.installing-busy-title", l))}">${escapeHtml(copyText("panel.action.installing-busy", l))}</button>` : `<button type="button" data-action="install" data-primary="1" title="${escapeHtml(copyText("panel.action.install-title", l))}"${macroBusy || !view.installEnabled ? " disabled" : ""}>${escapeHtml(view.installLabel)}</button>`)
|
|
918
932
|
);
|
|
919
933
|
if (snapshot?.latestVersion && !view.skippedLatest && view.banner.kind === "update") {
|
|
920
934
|
actions.push(`<button type="button" data-action="skip" title="${escapeHtml(copyText("panel.action.skip-title", l))}">${escapeHtml(copyText("panel.action.skip", l))}</button>`);
|
|
@@ -1084,6 +1098,7 @@ ${changelogHTML}
|
|
|
1084
1098
|
}
|
|
1085
1099
|
}
|
|
1086
1100
|
if (copyNotice) errLines.push(`<div class="dsh-upd-copy" role="status">${escapeHtml(copyNotice)}</div>`);
|
|
1101
|
+
else errLines.push('<div class="dsh-upd-copy dsh-upd-copy--empty" aria-hidden="true"></div>');
|
|
1087
1102
|
chapters.push(chapterOf(4, errLines.join(""), "", l));
|
|
1088
1103
|
}
|
|
1089
1104
|
chapters.push(
|
package/dist/service.js
CHANGED
|
@@ -364,16 +364,32 @@ function createUpdateCore(ports) {
|
|
|
364
364
|
job
|
|
365
365
|
};
|
|
366
366
|
}
|
|
367
|
+
function jobsEqual(a, b) {
|
|
368
|
+
if (a === b) return true;
|
|
369
|
+
if (!a || !b) return false;
|
|
370
|
+
return a.id === b.id && a.state === b.state && a.targetVersion === b.targetVersion && a.message === b.message && (a.requestId ?? null) === (b.requestId ?? null);
|
|
371
|
+
}
|
|
372
|
+
async function healedJob(env) {
|
|
373
|
+
const loaded = await loadJob();
|
|
374
|
+
const healed = healJob(loaded, env);
|
|
375
|
+
if (!jobsEqual(loaded, healed)) {
|
|
376
|
+
try {
|
|
377
|
+
await saveJob(healed);
|
|
378
|
+
} catch {
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return healed;
|
|
382
|
+
}
|
|
367
383
|
async function status() {
|
|
368
384
|
const env = await ports.readInstalled();
|
|
369
|
-
const job =
|
|
385
|
+
const job = await healedJob(env);
|
|
370
386
|
return buildSnapshot(env, job);
|
|
371
387
|
}
|
|
372
388
|
async function check() {
|
|
373
389
|
if (checking) return checking;
|
|
374
390
|
if (checked?.checkId && ports.now() - lastCheckAt < RECHECK_WINDOW_MS) {
|
|
375
391
|
const env = await ports.readInstalled();
|
|
376
|
-
const snapshot = buildSnapshot(env,
|
|
392
|
+
const snapshot = buildSnapshot(env, await healedJob(env));
|
|
377
393
|
return { snapshot, receipt: snapshot.canInstall ? toReceipt() : null };
|
|
378
394
|
}
|
|
379
395
|
lastCheckAt = ports.now();
|
|
@@ -389,7 +405,7 @@ function createUpdateCore(ports) {
|
|
|
389
405
|
installationKey: env.installationKey,
|
|
390
406
|
blockedReason: satisfiesNodeRange(ports.nodeVersion, release2.nodeRange) ? null : "incompatible-node"
|
|
391
407
|
};
|
|
392
|
-
const snapshot = buildSnapshot(env,
|
|
408
|
+
const snapshot = buildSnapshot(env, await healedJob(env));
|
|
393
409
|
return { snapshot, receipt: snapshot.canInstall ? toReceipt() : null };
|
|
394
410
|
} catch (error) {
|
|
395
411
|
if (checked) checked = { ...checked, checkId: null, expiresAt: 0 };
|