dsh-plugin-update 0.8.0 → 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 +22 -0
- package/README.md +1 -0
- package/dist/entry-batch.d.ts +11 -2
- package/dist/entry-batch.js +8 -2
- package/dist/entry.d.ts +3 -2
- package/dist/entry.js +3 -3
- package/dist/host-batch.js +81 -36
- package/dist/panel-batch.d.ts +10 -7
- package/dist/panel-batch.js +163 -66
- package/dist/panel.d.ts +2 -0
- package/dist/panel.js +25 -20
- package/dist/service.js +19 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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
|
+
|
|
5
27
|
## [0.8.0] - 2026-10-07
|
|
6
28
|
|
|
7
29
|
### Added
|
package/README.md
CHANGED
|
@@ -160,6 +160,7 @@ mountUpdatePanel(el, { pluginId: 'p', prefix: 'notes', call,
|
|
|
160
160
|
| 文字背景边线 | `text`、`textMuted`、`bg`、`bgSoft`、`border`、`borderStrong`、`buttonBg` |
|
|
161
161
|
| 主色焦点 | `primary`、`primaryDeep`、`focus` |
|
|
162
162
|
| 状态四组 | `okBg / okBorder / okText`、`warnBg / warnBorder / warnText`、`badBg / badBorder / badText`、`busyBg / busyBorder / busyText` |
|
|
163
|
+
| 批量新版 | `newText`(未装好红)、`newOkText`(已装好绿) |
|
|
163
164
|
| 字体阴影 | `fontSans`、`fontSerif`、`fontMono`、`shadow` |
|
|
164
165
|
| 圆角 | `radiusPanel`、`radiusButton`、`radiusBadge` |
|
|
165
166
|
| 入口件尺寸 | `entryFontSize`、`entryPadding`、`entryBorderRadius`、`entryScale`(数字,大于 0) |
|
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
|
@@ -93,8 +93,9 @@ export interface UpdateEntryController {
|
|
|
93
93
|
unmount(): void;
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
|
-
* 状态档:快照六字段 + 失败码推出,顺序固定(活任务 >
|
|
97
|
-
*
|
|
96
|
+
* 状态档:快照六字段 + 失败码推出,顺序固定(活任务 > 失败 > 待重启 > 有新版 > 待查)。
|
|
97
|
+
* 失败码是最近一次通话的新证据,快照是上一次成功的旧读数:新证据赢(#91)。
|
|
98
|
+
* 待重启仍是正常终态,但只在无新失败时展示;正在装的任务比一条陈旧的失败码更可信。
|
|
98
99
|
*/
|
|
99
100
|
export declare function entryStateKind(state: UpdateEntryState | null | undefined): EntryStateKind;
|
|
100
101
|
/**
|
package/dist/entry.js
CHANGED
|
@@ -23,12 +23,12 @@ function entryStateKind(state) {
|
|
|
23
23
|
const snapshot = state?.snapshot ?? null;
|
|
24
24
|
const job = snapshot?.job ?? null;
|
|
25
25
|
if (job && (job.state === "installing" || job.state === "verifying")) return "busy";
|
|
26
|
-
if (snapshot && (snapshot.blockedReason === "pending-restart" || job?.state === "restart-required")) {
|
|
27
|
-
return "restart";
|
|
28
|
-
}
|
|
29
26
|
if (state && state.error || job && (job.state === "failed" || job.state === "interrupted")) {
|
|
30
27
|
return "failed";
|
|
31
28
|
}
|
|
29
|
+
if (snapshot && (snapshot.blockedReason === "pending-restart" || job?.state === "restart-required")) {
|
|
30
|
+
return "restart";
|
|
31
|
+
}
|
|
32
32
|
if (hasUpdateOf(snapshot)) return "update";
|
|
33
33
|
return "idle";
|
|
34
34
|
}
|
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,11 +127,14 @@ export interface BatchLedgerCounts {
|
|
|
127
127
|
* 失败优先(失败行永远算失败,不算待重启);其余按相位落档;不用重启的终态才算「已最新」。
|
|
128
128
|
* 例外一处:「忙失败占位」(phase=failed 但 error=update-busy)其实是**排队**不是失败——
|
|
129
129
|
* 宿主忙时先写占位再回 update-busy,面板把它翻回「可更新」,免得总账把它报成失败。
|
|
130
|
-
* 无轮次 pending 行看知识(#73
|
|
131
|
-
*
|
|
132
|
-
* checking
|
|
130
|
+
* 无轮次 pending 行看知识(#73 口径 = #82 报告 §6 推荐 A),条件与行展示同一处(isInUnfinishedRound):
|
|
131
|
+
* 有新版进可更新、已是最新进已最新、没有结果(无知识 / 查失败)仍待查;
|
|
132
|
+
* checking 行与「有没做完一轮」的行永远走执行态(待查),都不吃知识。
|
|
133
|
+
* 终态 current / 无重启 done 行看世界账(#90 世界账与轮次账分家):知识更新鲜且确有新版即进可更新,
|
|
134
|
+
* 否则沿旧结论(已最新),免得陈旧知识或失败的检查翻转已收尾的轮次。
|
|
135
|
+
* 不传 inventory 即无知识;不传 session 即当无轮次(旧调用零回归)。
|
|
133
136
|
*/
|
|
134
|
-
export declare function batchLedgerCounts(rows: readonly BatchRowView[], inventory?: unknown): BatchLedgerCounts;
|
|
137
|
+
export declare function batchLedgerCounts(rows: readonly BatchRowView[], inventory?: unknown, session?: unknown): BatchLedgerCounts;
|
|
135
138
|
/** 分类计数 → 一句话总账(只出现非零档,顺序固定:可更新 · 安装中 · 待查 · 待重启 · 失败 · 已跳过 · 已最新;#64 单语:缺省 zh 零回归)。 */
|
|
136
139
|
export declare function batchLedgerText(counts: BatchLedgerCounts, lang?: unknown): string;
|
|
137
140
|
/**
|
|
@@ -167,7 +170,7 @@ export declare function batchRowKnowledgeText(row: BatchRowView, entry: BatchKno
|
|
|
167
170
|
* 一行的版本展示(#71 Q3:永远显示当前版;有新版才 `当前 → 最新`)。
|
|
168
171
|
* latest 只做展示,不参与安装决策:会话目标版优先(安装语义),无会话目标时才看知识最新版。
|
|
169
172
|
* - 会话有目标版:current(快照运行版优先)→ 会话目标(current 缺失时占位 `?`,与旧口径一致)。
|
|
170
|
-
* - 会话无目标:current(快照优先,缺失退知识 installedVersion)→ 知识 latest
|
|
173
|
+
* - 会话无目标:current(快照优先,缺失退知识 installedVersion)→ 知识 latest(不等即 → 红;相等即已是最新,只显当前;current 缺失而 latest 有即 `? → latest` 红,与会话缺当前同例)。
|
|
171
174
|
* - 两者皆无:current 为 null、latest 为 null、hasUpdate 为 false(调用方不画版本 span)。
|
|
172
175
|
*/
|
|
173
176
|
export interface BatchRowVersionParts {
|
|
@@ -207,7 +210,7 @@ export interface BatchPanelRenderInput {
|
|
|
207
210
|
noticeKey?: string | null;
|
|
208
211
|
/** 键 -> 该行已取到的日志全文(只传有文本的;取不到与没展开即中性提示,不挡安装)。 */
|
|
209
212
|
changelogs?: Record<string, string | null>;
|
|
210
|
-
/**
|
|
213
|
+
/** 轮次会话(有未终态行才画控制区,并决定在轮的行与总账吃不吃知识;不传按行相位回退旧口径,兼容旧快照)。 */
|
|
211
214
|
session?: unknown;
|
|
212
215
|
/** 知识账本(无轮次时行按知识展示;只做展示,不参与安装决策)。 */
|
|
213
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, inventory) {
|
|
39
|
+
function batchLedgerCounts(rows, inventory, session) {
|
|
39
40
|
const counts = {
|
|
40
41
|
updatable: 0,
|
|
41
42
|
installing: 0,
|
|
@@ -51,20 +52,19 @@ function batchLedgerCounts(rows, inventory) {
|
|
|
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;
|
|
55
57
|
else if (phase === "checking") counts.pending += 1;
|
|
56
58
|
else if (phase === "skipped") counts.skipped += 1;
|
|
57
59
|
else if (row.restartRequired === true) counts.restart += 1;
|
|
58
|
-
else counts.settled += 1;
|
|
60
|
+
else counts[worldUpdateVersion(row, knowledgeEntryOf(inventory, row.key), sessionUpdatedAtOf(session, row.key)) ? "updatable" : "settled"] += 1;
|
|
59
61
|
}
|
|
60
62
|
return counts;
|
|
61
63
|
}
|
|
62
64
|
function ledgerKnowledgeBucket(entry) {
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
if (
|
|
66
|
-
if (entry.latestVersion && !entry.installedVersion) return "updatable";
|
|
67
|
-
if (entry.lastCheckedAt > 0) return "settled";
|
|
65
|
+
const verdict = knowledgeVerdictOf(entry);
|
|
66
|
+
if (verdict === "update") return "updatable";
|
|
67
|
+
if (verdict === "current") return "settled";
|
|
68
68
|
return "pending";
|
|
69
69
|
}
|
|
70
70
|
function batchLedgerText(counts, lang) {
|
|
@@ -100,6 +100,13 @@ function knowledgeEntryOf(inventory, key) {
|
|
|
100
100
|
error: typeof e["error"] === "string" ? e["error"] : null
|
|
101
101
|
};
|
|
102
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
|
+
}
|
|
103
110
|
function langOf(lang) {
|
|
104
111
|
try {
|
|
105
112
|
return normalizeLangTag(typeof lang === "string" ? lang : resolveLang(lang));
|
|
@@ -114,34 +121,52 @@ function batchText(key, lang, values) {
|
|
|
114
121
|
return "";
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
|
-
function
|
|
118
|
-
if (isObject(session))
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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;
|
|
128
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);
|
|
129
164
|
if (rows.length === 0) return false;
|
|
130
165
|
return !rows.every((row) => isTerminalPhase(asBatchPhase(row.phase)));
|
|
131
166
|
}
|
|
132
167
|
function unfinishedCount(rows, session) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (Array.isArray(entries)) {
|
|
136
|
-
let n = 0;
|
|
137
|
-
for (const item of entries) {
|
|
138
|
-
if (!isObject(item)) continue;
|
|
139
|
-
const phase = item["phase"];
|
|
140
|
-
if (typeof phase === "string" && !isTerminalPhase(asBatchPhase(phase))) n += 1;
|
|
141
|
-
}
|
|
142
|
-
return n;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
168
|
+
const entries = sessionEntriesOf(session);
|
|
169
|
+
if (entries !== null) return entries.filter(isUnfinishedEntry).length;
|
|
145
170
|
return rows.filter((row) => !isTerminalPhase(asBatchPhase(row.phase))).length;
|
|
146
171
|
}
|
|
147
172
|
function batchRowStatus(row, skippedVersion, lang) {
|
|
@@ -171,17 +196,10 @@ function batchRowStatus(row, skippedVersion, lang) {
|
|
|
171
196
|
}
|
|
172
197
|
function batchRowKnowledgeText(row, entry, lang) {
|
|
173
198
|
if (asBatchPhase(row.phase) !== "pending" || !entry) return null;
|
|
174
|
-
|
|
175
|
-
if (entry.latestVersion
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (entry.latestVersion && entry.installedVersion && entry.latestVersion === entry.installedVersion) {
|
|
179
|
-
return batchText("batch.row.current", lang);
|
|
180
|
-
}
|
|
181
|
-
if (entry.latestVersion && !entry.installedVersion) {
|
|
182
|
-
return batchText("batch.row.update", lang, { version: entry.latestVersion });
|
|
183
|
-
}
|
|
184
|
-
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);
|
|
185
203
|
return batchText("batch.row.never", lang);
|
|
186
204
|
}
|
|
187
205
|
function batchRowVersionParts(row, knowledge) {
|
|
@@ -197,14 +215,15 @@ function batchRowVersionParts(row, knowledge) {
|
|
|
197
215
|
const latest = knowledge && typeof knowledge.latestVersion === "string" && knowledge.latestVersion ? knowledge.latestVersion : null;
|
|
198
216
|
if (current && latest && latest !== current) return { current, latest, hasUpdate: true };
|
|
199
217
|
if (current) return { current, latest: null, hasUpdate: false };
|
|
200
|
-
if (latest) return { current:
|
|
218
|
+
if (latest) return { current: null, latest, hasUpdate: true };
|
|
201
219
|
return { current: null, latest: null, hasUpdate: false };
|
|
202
220
|
}
|
|
203
221
|
function batchRowVersionHTML(row, knowledge) {
|
|
204
222
|
const parts = batchRowVersionParts(row, knowledge);
|
|
205
223
|
if (parts.hasUpdate && parts.latest) {
|
|
206
224
|
const cur = parts.current ?? "?";
|
|
207
|
-
|
|
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>";
|
|
208
227
|
}
|
|
209
228
|
if (parts.current) return '<span class="dsh-upd-bver">' + escapeHtml(parts.current) + "</span>";
|
|
210
229
|
return "";
|
|
@@ -225,7 +244,7 @@ function renderBatchPanelHTML(input) {
|
|
|
225
244
|
const expandedKey = typeof input.expandedKey === "string" && input.expandedKey ? input.expandedKey : null;
|
|
226
245
|
const lastError = typeof input.lastError === "string" && input.lastError ? input.lastError : null;
|
|
227
246
|
const loaded = input.loaded === void 0 ? rows.length > 0 : input.loaded === true;
|
|
228
|
-
const counts = batchLedgerCounts(rows, input.inventory);
|
|
247
|
+
const counts = batchLedgerCounts(rows, input.inventory, input.session);
|
|
229
248
|
const installing = counts.installing > 0;
|
|
230
249
|
const macroBusy = input.inFlight === true || installing;
|
|
231
250
|
const total = rows.length;
|
|
@@ -247,7 +266,8 @@ function renderBatchPanelHTML(input) {
|
|
|
247
266
|
changelogMarkdown: null,
|
|
248
267
|
lang,
|
|
249
268
|
knowledge: null,
|
|
250
|
-
inRound: false
|
|
269
|
+
inRound: false,
|
|
270
|
+
worldLatest: null
|
|
251
271
|
};
|
|
252
272
|
const parts = [];
|
|
253
273
|
parts.push('<div class="dsh-upd-batch-head">');
|
|
@@ -266,24 +286,13 @@ function renderBatchPanelHTML(input) {
|
|
|
266
286
|
const showControls = total > 0 && hasUnfinishedRows(rows, session ?? void 0);
|
|
267
287
|
const prefs = isObject(input.prefs) ? input.prefs : null;
|
|
268
288
|
const checkOnOpen = prefs === null ? null : prefs["checkOnOpen"] === false ? false : true;
|
|
269
|
-
function roundEntryPhase(key) {
|
|
270
|
-
if (!session) return null;
|
|
271
|
-
const entries = session["entries"];
|
|
272
|
-
if (!Array.isArray(entries)) return null;
|
|
273
|
-
for (const item of entries) {
|
|
274
|
-
if (!isObject(item)) continue;
|
|
275
|
-
const e = item;
|
|
276
|
-
if (e["key"] === key && typeof e["phase"] === "string") return e["phase"];
|
|
277
|
-
}
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
289
|
parts.push('<div class="dsh-upd-btable">');
|
|
281
290
|
for (const row of rows) {
|
|
282
291
|
const raw = skippedVersions[row.key];
|
|
283
292
|
const skipped = typeof raw === "string" && raw ? raw : null;
|
|
284
293
|
const expanded = row.key === expandedKey;
|
|
285
|
-
const
|
|
286
|
-
const
|
|
294
|
+
const inRound = isInUnfinishedRound(input.session, row.key);
|
|
295
|
+
const knowledge = !inRound ? knowledgeEntryOf(input.inventory, row.key) : null;
|
|
287
296
|
parts.push(
|
|
288
297
|
rowSetHTML(row, {
|
|
289
298
|
...ctx,
|
|
@@ -292,8 +301,9 @@ function renderBatchPanelHTML(input) {
|
|
|
292
301
|
notice: expanded && row.key === detailNoticeKey ? notice : null,
|
|
293
302
|
changelogMarkdown: expanded && typeof changelogs[row.key] === "string" ? changelogs[row.key] : null,
|
|
294
303
|
lang,
|
|
295
|
-
knowledge
|
|
296
|
-
inRound
|
|
304
|
+
knowledge,
|
|
305
|
+
inRound,
|
|
306
|
+
worldLatest: worldUpdateVersion(row, knowledge, sessionUpdatedAtOf(input.session, row.key))
|
|
297
307
|
})
|
|
298
308
|
);
|
|
299
309
|
}
|
|
@@ -395,7 +405,7 @@ function rowSetHTML(row, ctx) {
|
|
|
395
405
|
const queued = isQueuedRow(row);
|
|
396
406
|
const position = queued ? queuePositionOf(row) : null;
|
|
397
407
|
const versionHTML = batchRowVersionHTML(row, ctx.knowledge);
|
|
398
|
-
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);
|
|
399
409
|
const actions = [];
|
|
400
410
|
const off = ctx.inFlight ? " disabled" : "";
|
|
401
411
|
if (phase === "installing") {
|
|
@@ -436,7 +446,7 @@ function rowSetHTML(row, ctx) {
|
|
|
436
446
|
const withCode = escaped.includes(codeEsc) ? escaped.replace(codeEsc, "<code>" + codeEsc + "</code>") : escaped;
|
|
437
447
|
return '<span class="dsh-upd-bfail">' + withCode + "</span>";
|
|
438
448
|
})() : "";
|
|
439
|
-
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>";
|
|
440
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>" : "";
|
|
441
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>";
|
|
442
452
|
}
|
|
@@ -458,6 +468,7 @@ function detailHTML(row, ctx) {
|
|
|
458
468
|
theme: ctx.theme,
|
|
459
469
|
themeTokens: ctx.themeTokens,
|
|
460
470
|
profileName: typeof row.profileName === "string" && row.profileName ? row.profileName : null,
|
|
471
|
+
hostKind: typeof row.hostKind === "string" && row.hostKind ? row.hostKind : null,
|
|
461
472
|
actions: "none"
|
|
462
473
|
}, ctx.lang);
|
|
463
474
|
return stripActionButtons(html);
|
|
@@ -486,6 +497,11 @@ function detailActionsHTML(row, ctx) {
|
|
|
486
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>"
|
|
487
498
|
);
|
|
488
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
|
+
}
|
|
489
505
|
if (!queued && phase === "ready" && ctx.skipped === null && version !== null) {
|
|
490
506
|
buttons.push(
|
|
491
507
|
'<button type="button" data-act="row-skip" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.skip", ctx.lang)) + "</button>"
|
|
@@ -507,11 +523,12 @@ function detailActionsHTML(row, ctx) {
|
|
|
507
523
|
function stripActionButtons(html) {
|
|
508
524
|
return html.replace(/<button\b[^>]*data-action=[^>]*>[\s\S]*?<\/button>/g, "");
|
|
509
525
|
}
|
|
510
|
-
function dotToneOf(row, phase, skipped, queued) {
|
|
526
|
+
function dotToneOf(row, phase, skipped, queued, worldLatest = null) {
|
|
511
527
|
if (phase === "failed" && !queued) return "bad";
|
|
512
528
|
if (phase === "installing" || phase === "checking") return "busy";
|
|
513
529
|
if (queued) return "warn";
|
|
514
530
|
if (row.restartRequired === true) return "warn";
|
|
531
|
+
if (worldLatest !== null) return "todo";
|
|
515
532
|
if (phase === "ready") return skipped === null ? "todo" : "idle";
|
|
516
533
|
if (phase === "current" || phase === "done") return "ok";
|
|
517
534
|
return "idle";
|
|
@@ -543,11 +560,15 @@ const UPDATE_BATCH_PANEL_CSS = [
|
|
|
543
560
|
".dsh-upd-bver-cur{opacity:.65}",
|
|
544
561
|
".dsh-upd-bver-arrow{opacity:.6}",
|
|
545
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}",
|
|
546
564
|
".dsh-upd-bstat{font-size:12.5px;opacity:.9;min-width:0;overflow-wrap:anywhere}",
|
|
547
565
|
".dsh-upd-bfail{grid-column:1/-1;display:block;font-size:12.5px;color:var(--dsh-update-bad-border,#dc2626)}",
|
|
548
566
|
".dsh-upd-batch{--dsh-update-new-text:#dc2626}",
|
|
567
|
+
".dsh-upd-batch{--dsh-update-new-ok-text:#047857}",
|
|
549
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}}",
|
|
550
570
|
"@media (forced-colors: active){.dsh-upd-bver-new{color:CanvasText}}",
|
|
571
|
+
"@media (forced-colors: active){.dsh-upd-bver-new-ok{color:CanvasText}}",
|
|
551
572
|
".dsh-upd-blist{font-size:12.5px;margin-top:2px}",
|
|
552
573
|
".dsh-upd-batch-ledger{margin:8px 0 0;font-size:13px;opacity:.85}",
|
|
553
574
|
".dsh-upd-batch-more{margin-top:6px}",
|
|
@@ -585,7 +606,10 @@ const UPDATE_BATCH_PANEL_ARCHIVE_CSS = [
|
|
|
585
606
|
'.dsh-upd[data-theme="archive"] .dsh-upd-bver{font-family:var(--dsh-update-font-mono);color:var(--dsh-update-text-muted)}',
|
|
586
607
|
'.dsh-upd[data-theme="archive"] .dsh-upd-bver-cur{opacity:.75}',
|
|
587
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}}',
|
|
588
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}',
|
|
589
613
|
'@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"] .dsh-upd-batch{--dsh-update-new-text:#ef8a7d}}',
|
|
590
614
|
'.dsh-upd[data-theme="archive"] .dsh-upd-batch-ledger{color:var(--dsh-update-text-muted)}',
|
|
591
615
|
'.dsh-upd[data-theme="archive"] .dsh-upd-batch-sum{color:var(--dsh-update-text)}',
|
|
@@ -631,6 +655,12 @@ function queuedRequestIdOf(row) {
|
|
|
631
655
|
}
|
|
632
656
|
return null;
|
|
633
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
|
+
}
|
|
634
664
|
function cancelQueuePhoneOf(row) {
|
|
635
665
|
const phones = isObject(row.phoneNames) ? row.phoneNames : null;
|
|
636
666
|
if (!phones) return null;
|
|
@@ -703,6 +733,21 @@ function jobFailureOf(row) {
|
|
|
703
733
|
const detail = message.slice(at + 1).trim();
|
|
704
734
|
return { code: code || null, detail: detail || null };
|
|
705
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
|
+
}
|
|
706
751
|
function diagTextOf(row, lang) {
|
|
707
752
|
const snapshot = isObject(row.snapshot) ? row.snapshot : null;
|
|
708
753
|
const queue = isObject(row.queue) ? row.queue : null;
|
|
@@ -710,7 +755,9 @@ function diagTextOf(row, lang) {
|
|
|
710
755
|
const text = (value) => typeof value === "string" && value ? value : null;
|
|
711
756
|
const job = jobFailureOf(row);
|
|
712
757
|
const diagDetail = diag ? text(diag["detail"]) : null;
|
|
713
|
-
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;
|
|
714
761
|
const input = {
|
|
715
762
|
pluginId: pluginIdOf(row),
|
|
716
763
|
code: job !== null && job.code !== null ? job.code : row.error || "check-failed",
|
|
@@ -837,6 +884,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
837
884
|
let notice = null;
|
|
838
885
|
let noticeKey = null;
|
|
839
886
|
let inFlight = false;
|
|
887
|
+
let inFlightIsCheck = false;
|
|
840
888
|
let loaded = false;
|
|
841
889
|
let refreshing = false;
|
|
842
890
|
let confirmCancelArmed = false;
|
|
@@ -1134,6 +1182,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1134
1182
|
async function phone(name, args, busyRowKey = null) {
|
|
1135
1183
|
if (!mounted || inFlight) return null;
|
|
1136
1184
|
inFlight = true;
|
|
1185
|
+
inFlightIsCheck = name === phones.check;
|
|
1137
1186
|
clearNotice();
|
|
1138
1187
|
render();
|
|
1139
1188
|
let out = null;
|
|
@@ -1153,6 +1202,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1153
1202
|
out = null;
|
|
1154
1203
|
} finally {
|
|
1155
1204
|
inFlight = false;
|
|
1205
|
+
inFlightIsCheck = false;
|
|
1156
1206
|
if (mounted) render();
|
|
1157
1207
|
}
|
|
1158
1208
|
maybeAutoRowChangelog(expandedKey);
|
|
@@ -1231,6 +1281,10 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1231
1281
|
const row = typeof key === "string" && key ? rowOfKey(key) : null;
|
|
1232
1282
|
const version = row ? latestVersionOf(row) : null;
|
|
1233
1283
|
if (!row || !version) return;
|
|
1284
|
+
if (!validReleaseVersion(version)) {
|
|
1285
|
+
render();
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1234
1288
|
try {
|
|
1235
1289
|
skipStoreFor(pluginIdOf(row)).skip(version);
|
|
1236
1290
|
} catch {
|
|
@@ -1251,6 +1305,49 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1251
1305
|
render();
|
|
1252
1306
|
return;
|
|
1253
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
|
+
}
|
|
1254
1351
|
// 「取消排队」:走**该家自己的**单插件安装电话(宿主支持 cancelQueued:只撤自己那条 waiting,
|
|
1255
1352
|
// 不碰别家、也不碰正在装的拥有者)。批量取消会清掉整轮会话,所以这里绝不用它顶。
|
|
1256
1353
|
case "row-cancel-queue": {
|
|
@@ -1464,7 +1561,7 @@ function mountUpdateBatchPanel(container, options) {
|
|
|
1464
1561
|
}
|
|
1465
1562
|
const timer = getTimer();
|
|
1466
1563
|
const handle = timer.set(() => {
|
|
1467
|
-
if (inFlight) return;
|
|
1564
|
+
if (inFlight && inFlightIsCheck) return;
|
|
1468
1565
|
void refresh();
|
|
1469
1566
|
}, pollMs);
|
|
1470
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";
|
|
@@ -921,9 +923,12 @@ function renderUpdatePanelKernel(input, view, lang) {
|
|
|
921
923
|
const busyAct = input.busyAct;
|
|
922
924
|
const checkBusy = busyAct === "check";
|
|
923
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;
|
|
924
929
|
actions.push('<div class="dsh-upd-actions">');
|
|
925
930
|
actions.push(
|
|
926
|
-
(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>`)
|
|
927
932
|
);
|
|
928
933
|
if (snapshot?.latestVersion && !view.skippedLatest && view.banner.kind === "update") {
|
|
929
934
|
actions.push(`<button type="button" data-action="skip" title="${escapeHtml(copyText("panel.action.skip-title", l))}">${escapeHtml(copyText("panel.action.skip", l))}</button>`);
|
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 };
|