dsh-my-guardian 0.4.4 → 0.4.6
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 +44 -0
- package/README.md +13 -3
- package/lib/api.js +11 -5
- package/lib/client.js +166 -16
- package/lib/dep-precheck.js +192 -39
- package/lib/events.js +102 -23
- package/lib/host-inventory.js +220 -0
- package/lib/index.js +28 -9
- package/lib/mount.js +21 -9
- package/lib/parts/row.js +22 -3
- package/lib/parts/styles.js +13 -1
- package/lib/parts/util.js +40 -7
- package/lib/startup-check.js +95 -43
- package/lib/state.js +62 -8
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
|
|
9
|
+
## [0.4.6] - 2026-10-17
|
|
10
|
+
|
|
11
|
+
### 变更
|
|
12
|
+
|
|
13
|
+
- fix(i18n): 插件文案优先跟随宿主 html lang
|
|
14
|
+
- fix(compat): 适配 DSH 0.2.1 移除 webRuntime 服务
|
|
15
|
+
|
|
16
|
+
## [0.4.5] - 2026-10-17
|
|
17
|
+
|
|
18
|
+
### 变更
|
|
19
|
+
|
|
20
|
+
- chore(guardian): 还原 dsh-my-guardian 并修复 #481 依赖预检误报 (#484)
|
|
21
|
+
- chore(plugins)!: 删除 5 个插件及其全仓引用 (#464)
|
|
22
|
+
- fix(compat): 插件适配 DSH 0.2.0-rc.2 宿主(peer 声明 + 取证通道 + fixture)
|
|
23
|
+
- fix(guardian): #438 teardown 期间 entry dispose 诊断不再丢失 (#441)
|
|
24
|
+
- fix(guardian): #429 移除永不派发的死监听并修 entry-init 记录标识 (#434)
|
|
25
|
+
- fix(guardian): #423 roster 行自身回退 profiles 根 + #424 scoped 行纳入预检 (#425)
|
|
26
|
+
- fix(guardian): #410 依赖预检出口分离「版本不满足」与「缺失」 (#420)
|
|
27
|
+
- fix(verify): #402 门禁假红根因——等待预算与真实墙钟解耦(用例鲁棒化 + flaky 复测分类缺口) (#417)
|
|
28
|
+
|
|
8
29
|
## [0.4.4] - 2026-09-25
|
|
9
30
|
|
|
10
31
|
### 变更
|
|
@@ -17,7 +38,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
17
38
|
|
|
18
39
|
### 变更
|
|
19
40
|
|
|
41
|
+
- fix(guardian): #481 依赖预检识别桌面版宿主内置包——读 `app.asar/dsh/desktop-runtime.json` 的
|
|
42
|
+
`sharedPackages`(287 个包带版本)与 `files`(覆盖面更广、含第三方包)建索引;裸名
|
|
43
|
+
`cordis` / `schemastery` 回退 `@deepseek-ai/<name>`;客户端插件(`dsh.client`)把宿主前端注入的
|
|
44
|
+
9 个静态模块(`react` / `react-dom` / client UI 包)视为已满足。宿主内置包版本不满足降级为警告
|
|
45
|
+
(提示升级 DSH,不再产出 `dsh plugin add`);名册非包名行(`cordis:group` / YAML 折叠标量 `>-`)
|
|
46
|
+
不再报 `unresolvable`。真实桌面名册实测:依赖告警 1 → 0、非包名行 0 误报;纯 Node 环境降级不抛错。
|
|
47
|
+
- fix(guardian): #429 移除宿主从不派发的死监听 `hmr/config-update-failed`。0.1.7-rc.2 的参考源与已装宿主
|
|
48
|
+
双向 0 命中(`@deepseek-ai/dsh-hmr` 只剩 `hmr/change` / `hmr/reload`),注册它既不触发也不报错;原功能的
|
|
49
|
+
诊断**并未失效**——宿主在同一 catch 里先打 `config reload at %C failed` warn 序列,由结构化日志通道单独
|
|
50
|
+
承担(旧宿主 cordis-plugin-hmr 也是同一顺序,事件通道对两个版本都是冗余的)
|
|
51
|
+
- fix(guardian): #429 `loader/entry-init` 诊断记录不再退化成 `entry ? initialized`——loader 在 Entry 构造函数里
|
|
52
|
+
emit,此刻 `entry.options` 还是 `{}`(vendor/loader/src/config/entry.ts:50/58),改为推迟一个 microtask 再落笔;
|
|
53
|
+
隔离实例实测热挂载记录带真实 entry id
|
|
54
|
+
- test(guardian): #429 新增 test/host-event-live.mjs——用**真实** cordis loader(已装宿主)证明 `loader/entry-init` /
|
|
55
|
+
`loader/partial-dispose` 真会触发;契约测试新增「源码不得再订阅不存在的事件」判据
|
|
56
|
+
- fix(guardian): #438 teardown 收尾期同批并发 entry 的 dispose 诊断不再丢失(收尾写入窗口 + 排空落盘)
|
|
57
|
+
- fix(guardian): #438 第二种形态——整树卸载期 `loader/partial-dispose` 监听器与 teardown disposer 同批被
|
|
58
|
+
`Promise.all` 摘除,事件根本到不了 guardian(隔离实例 + SIGTERM 实测:修前 `state.json.events` 恒 1 条、
|
|
59
|
+
entry-dispose 0 条)。改为收尾起手同步抓 loader 树 entry 快照、unmount 后取差集记录「收尾释放了哪些 entry」,
|
|
60
|
+
不再依赖事件通道;实测 183 个活条目全部记到(state.json 按 20 条环形上限保留末 20 条)
|
|
61
|
+
- test(guardian): #438 新增第二种形态防复发——fake tree 三例(事件通道静默时全量记录 / 存活条目不误记 /
|
|
62
|
+
teardown 返回后不得覆盖快照)+ test/teardown-releases-live.mjs(真实 cordis 整树卸载取证快照前提与差集)
|
|
20
63
|
- fix(guardian): 依赖预检解析 profiles 根 node_modules 的宿主包与子路径导出,消除「缺少依赖」误报
|
|
64
|
+
- fix(guardian): #410 依赖预检出口把「版本不满足」与「缺失」分成独立文案/字段/徽标(`dependency-missing` / `dependency-mismatch`),宿主提供的包不再给 `dsh plugin add` 建议,消除含空格/管道的畸形安装命令
|
|
21
65
|
|
|
22
66
|
## [0.4.3] - 2026-09-25
|
|
23
67
|
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
- **两段式加载**:新插件先进候选区,坏插件不再拖垮 `dsh web` 启动(DSH 启动名册是 all-or-nothing,任一插件失败即整个进程起不来)。
|
|
14
14
|
- **失败自动隔离**:挂载失败记录尝试次数 + 失败类型 + 错误摘要,连续失败 **3 次冻结**,需手动重试。
|
|
15
|
-
- **挂载前依赖预检**:检查候选插件 `peerDependencies`
|
|
15
|
+
- **挂载前依赖预检**:检查候选插件 `peerDependencies` 是否安装、版本是否满足;**硬缺失**与**版本不满足**分开成句、分开字段、分开徽标,不进入挂载。宿主提供的包(`@deepseek-ai/*`、`react`/`react-dom`)不给 `dsh plugin add` 建议(按提示执行会把宿主自有包装进 profile)。桌面版宿主的 287 个 `@deepseek-ai/*` 包归档在 `app.asar` 内,预检会先读 `desktop-runtime.json` 建宿主清单再判定,不再把它们误报为缺失;客户端插件(有 `dsh.client`)的 9 个前端种子模块(`react` / `react-dom` 等)视为已满足(#481)。
|
|
16
16
|
- **成功自动转正**:挂载成功的插件进入持久化清单,后续启动自动恢复。
|
|
17
17
|
- **运行中热挂载**:运行期间往候选区加条目即自动挂载,无需重启。
|
|
18
18
|
- **安全模式**:一键跳过全部候选/已转正插件,快速救回被插件搞坏的环境。
|
|
@@ -37,9 +37,19 @@ dsh plugin --profile web add link:<仓库路径>/plugins/dsh-my-guardian
|
|
|
37
37
|
[{ "id": "my-plugin", "name": "dsh-my-plugin", "config": { "option": 1 } }]
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
写入后自动挂载:成功则该条从候选文件移除(转正),失败则保留。面板状态:运行中(可移除)/ 待加载(安全模式等)/ 失败 ×N(重试或移除)/ 冻结(连续失败 3 次,重试解除冻结)。失败条目带失败类型徽标(依赖缺失 / 代码错误 /
|
|
40
|
+
写入后自动挂载:成功则该条从候选文件移除(转正),失败则保留。面板状态:运行中(可移除)/ 待加载(安全模式等)/ 失败 ×N(重试或移除)/ 冻结(连续失败 3 次,重试解除冻结)。失败条目带失败类型徽标(依赖缺失 / 版本不满足 / 代码错误 / 其他),并在存在**可执行**的修复命令时附安装建议(宿主提供的包、含空格/管道的版本范围不给命令)。
|
|
41
41
|
|
|
42
|
-
**启动名册静态预检**:直接写进 `cordis.patch.yml` / profile / bundles 的插件仍由 DSH 启动时 all-or-nothing
|
|
42
|
+
**启动名册静态预检**:直接写进 `cordis.patch.yml` / profile / bundles 的插件仍由 DSH 启动时 all-or-nothing 加载,守护每次启动对名册做静态预检,问题写入 `$DSH_HOME/guardian/startup-issues.json`(`type` 区分 `unresolvable` / `dependency-missing` / `dependency-mismatch` / `duplicate-id`,依赖字段分 `missingDeps` / `mismatchedDeps`)并在面板「最近事件」置顶展示;**预检只记录告警,不阻断启动**。
|
|
43
|
+
|
|
44
|
+
检查范围与解析顺序(逐行显式,不留静默免检):
|
|
45
|
+
|
|
46
|
+
| 检查 | 覆盖范围 | 解析顺序 |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| 包可解析(`unresolvable`) | **所有行**(含 scoped `@scope/pkg` 与子路径 `pkg/sub`,按 `basePackage()` 归一) | `<profileDir>/node_modules` → `<profileDir>/../node_modules`(`$DSH_HOME/profiles/node_modules`),两处都无才报 |
|
|
49
|
+
| `peerDependencies` | **仅非 scoped 行**(保持既有范围,不因 scoped 行放开而扩大) | 插件自身 nested → profile → profiles 根 |
|
|
50
|
+
| 重复 entry id | 全名册 | — |
|
|
51
|
+
|
|
52
|
+
**宿主供给行**(`@deepseek-ai/*`、`react` / `react-dom`)跳过可解析性检查:它们由宿主安装目录供给,不在上述两段根内,硬查只会误报。跳过**不是静默的**——报告里有 `skippedHostRows` 计数与 `notes` 说明(例如「已跳过 8 个宿主供给行…」),快照同步携带,用来区分「都查过且没问题」与「有 N 行没查」。
|
|
43
53
|
|
|
44
54
|
## 配置
|
|
45
55
|
|
package/lib/api.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* appears), trust fence, method dispatch and the panel snapshot.
|
|
5
5
|
*/
|
|
6
6
|
import { readStagedFile, writeStagedFile } from './state.js';
|
|
7
|
-
import {
|
|
7
|
+
import { createTrustFence, readJsonBody, writeJson } from 'dsh-shared';
|
|
8
8
|
/** Bind the API entry points to one guardian instance's shared state. */
|
|
9
9
|
export function createApi(ctx, shared) {
|
|
10
10
|
return {
|
|
@@ -22,13 +22,15 @@ function ensureApi(ctx, shared) {
|
|
|
22
22
|
const webServer = ctx.get('webServer');
|
|
23
23
|
if (webServer === undefined)
|
|
24
24
|
return;
|
|
25
|
-
|
|
25
|
+
// 信任源迁移(DSH 0.2.1 移除 webRuntime):每次请求现读
|
|
26
|
+
// webStartup.trustedHosts + webServer.host,旧宿主回退 webRuntime(dsh-shared 内部兼容)。
|
|
27
|
+
const fence = createTrustFence(ctx);
|
|
26
28
|
shared.apiRegistered = true;
|
|
27
29
|
try {
|
|
28
30
|
ctx.effect(() => webServer.register({
|
|
29
31
|
kind: 'prefix',
|
|
30
32
|
path: '/guardian/api',
|
|
31
|
-
handler: (request, response) => handleApiRequest(ctx, shared,
|
|
33
|
+
handler: (request, response) => handleApiRequest(ctx, shared, fence, request, response),
|
|
32
34
|
}), 'dsh-my-guardian: /guardian/api routes');
|
|
33
35
|
}
|
|
34
36
|
catch (error) {
|
|
@@ -38,8 +40,8 @@ function ensureApi(ctx, shared) {
|
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
/** Unified route handler: fence → method dispatch → 404/error fallback. */
|
|
41
|
-
async function handleApiRequest(ctx, shared,
|
|
42
|
-
if (!
|
|
43
|
+
async function handleApiRequest(ctx, shared, fence, request, response) {
|
|
44
|
+
if (!fence(request)) {
|
|
43
45
|
writeJson(response, 403, { ok: false, error: { code: 'forbidden', message: 'forbidden' } });
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
@@ -170,6 +172,7 @@ function entrySnapshot(shared, id, record, isStaged) {
|
|
|
170
172
|
lastFailedAt: record.lastFailedAt,
|
|
171
173
|
failureType: record.failureType ?? null,
|
|
172
174
|
missingDeps: record.missingDeps ?? [],
|
|
175
|
+
mismatchedDeps: record.mismatchedDeps ?? [],
|
|
173
176
|
installHint: record.installHint ?? null,
|
|
174
177
|
status,
|
|
175
178
|
};
|
|
@@ -189,5 +192,8 @@ function snapshot(shared) {
|
|
|
189
192
|
// startup-roster pre-check report (issue #144): empty array = healthy
|
|
190
193
|
startupIssues: Array.isArray(shared.startupIssues) ? shared.startupIssues : [],
|
|
191
194
|
startupCheckedAt: typeof shared.startupCheckedAt === 'number' ? shared.startupCheckedAt : null,
|
|
195
|
+
// #424 A′: 宿主供给行被跳过,但跳过本身必须可见(计数 + 说明)。
|
|
196
|
+
startupSkippedHostRows: typeof shared.startupSkippedHostRows === 'number' ? shared.startupSkippedHostRows : 0,
|
|
197
|
+
startupNotes: Array.isArray(shared.startupNotes) ? shared.startupNotes : [],
|
|
192
198
|
};
|
|
193
199
|
}
|
package/lib/client.js
CHANGED
|
@@ -117,12 +117,22 @@ const STYLES = `
|
|
|
117
117
|
.dsh-my-guardian-badge-pending { color:var(--dsw-alias-accent); background:color-mix(in srgb, var(--dsw-alias-accent) 12%, transparent); }
|
|
118
118
|
.dsh-my-guardian-badge-failed { color:var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent); }
|
|
119
119
|
.dsh-my-guardian-badge-frozen { color:var(--dsw-alias-state-warn-primary); background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 16%, transparent); }
|
|
120
|
-
/* failure-classification badge chips (issue #86
|
|
120
|
+
/* failure-classification badge chips (issue #86, split by #410):
|
|
121
|
+
dependency-missing / dependency-mismatch (+ legacy dependency) / code / other */
|
|
121
122
|
.dsh-my-guardian-category { flex:none; display:inline-flex; align-items:center; justify-content:center; height:17px; padding:0 5px; border-radius:4px;
|
|
122
123
|
font:var(--dsw-font-xxxs-strong-11); }
|
|
124
|
+
.dsh-my-guardian-category-dependency-missing,
|
|
123
125
|
.dsh-my-guardian-category-dependency { color:var(--dsw-alias-state-warn-primary); background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 16%, transparent); }
|
|
126
|
+
.dsh-my-guardian-category-dependency-mismatch { color:var(--dsw-alias-state-accent-primary); background:color-mix(in srgb, var(--dsw-alias-state-accent-primary) 14%, transparent); }
|
|
124
127
|
.dsh-my-guardian-category-code { color:var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); }
|
|
125
128
|
.dsh-my-guardian-category-other { color:var(--dsw-alias-label-tertiary); background:var(--dsw-alias-interactive-bg-hover); }
|
|
129
|
+
/* version-mismatch detail: 声明范围 vs 实装版本 (issue #410) */
|
|
130
|
+
.dsh-my-guardian-mismatch { display:flex; align-items:center; flex-wrap:wrap; gap:5px; padding:3px 6px; border-radius:6px;
|
|
131
|
+
background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 6%, transparent); font:var(--dsw-font-xxxs-11);
|
|
132
|
+
color:var(--dsw-alias-label-tertiary); }
|
|
133
|
+
.dsh-my-guardian-mismatch-label { flex:none; }
|
|
134
|
+
.dsh-my-guardian-mismatch code { font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:var(--dsw-font-xxxs-11);
|
|
135
|
+
color:var(--dsw-alias-label-secondary); word-break:break-all; }
|
|
126
136
|
/* install-suggestion line for dependency failures */
|
|
127
137
|
.dsh-my-guardian-install-hint { display:flex; align-items:center; gap:5px; padding:3px 6px; border-radius:6px;
|
|
128
138
|
background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 6%, transparent);
|
|
@@ -211,7 +221,9 @@ const STYLES = `
|
|
|
211
221
|
font:var(--dsw-font-s-strong-14); color:var(--dsw-alias-label-primary); }
|
|
212
222
|
.dsh-my-guardian-startup-issue-badge-unresolvable { color:var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent); }
|
|
213
223
|
.dsh-my-guardian-startup-issue-badge-duplicate-id { color:var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent); }
|
|
224
|
+
.dsh-my-guardian-startup-issue-badge-dependency-missing,
|
|
214
225
|
.dsh-my-guardian-startup-issue-badge-dependency { color:var(--dsw-alias-state-warn-primary); background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 16%, transparent); }
|
|
226
|
+
.dsh-my-guardian-startup-issue-badge-dependency-mismatch { color:var(--dsw-alias-state-accent-primary); background:color-mix(in srgb, var(--dsw-alias-state-accent-primary) 14%, transparent); }
|
|
215
227
|
.dsh-my-guardian-startup-issue-message { font:var(--dsw-font-xxs-12); color:var(--dsw-alias-label-secondary); line-height:1.6; }
|
|
216
228
|
.dsh-my-guardian-startup-issue-line { display:flex; align-items:flex-start; gap:5px; padding:2px 0;
|
|
217
229
|
font:var(--dsw-font-xxxs-11); color:var(--dsw-alias-label-tertiary); line-height:1.6; min-width:0; }
|
|
@@ -225,8 +237,14 @@ const STYLES = `
|
|
|
225
237
|
|
|
226
238
|
'use strict'
|
|
227
239
|
// ── i18n ──────────────────────────────────────────────────────────────
|
|
228
|
-
/**
|
|
240
|
+
/** 当前界面语言是否为中文:宿主 <html lang> 优先,其次浏览器语言,再其次英文。 */
|
|
229
241
|
function isZh() {
|
|
242
|
+
try {
|
|
243
|
+
const host = (document.documentElement.lang || '').toLowerCase()
|
|
244
|
+
if (host !== '') return host.startsWith('zh')
|
|
245
|
+
} catch {
|
|
246
|
+
/* 非浏览器环境(node 渲染)无 document */
|
|
247
|
+
}
|
|
230
248
|
try {
|
|
231
249
|
return (navigator.language || 'en').toLowerCase().startsWith('zh')
|
|
232
250
|
} catch {
|
|
@@ -271,16 +289,25 @@ const strings = {
|
|
|
271
289
|
isZh()
|
|
272
290
|
? '已冻结:连续失败停止自动重试——点击刷新按钮手动重试,或移除该条目'
|
|
273
291
|
: 'Frozen: auto-retry stopped after repeated failures — retry manually or remove the entry',
|
|
274
|
-
|
|
292
|
+
// #410: 「依赖缺失」与「版本不满足」是两种结论,徽标文案必须能区分;
|
|
293
|
+
// 'dependency' 是 pre-#410 的旧分类(含运行期 module 解析失败),按「依赖错误」渲染。
|
|
294
|
+
failureDependencyMissing: () => (isZh() ? '依赖缺失' : 'Missing dependency'),
|
|
295
|
+
failureDependencyMismatch: () => (isZh() ? '版本不满足' : 'Version mismatch'),
|
|
296
|
+
failureDependencyUnknown: () => (isZh() ? '依赖错误' : 'Dependency error'),
|
|
275
297
|
failureCode: () => (isZh() ? '代码错误' : 'Code error'),
|
|
276
298
|
failureOther: () => (isZh() ? '其他' : 'Other'),
|
|
277
299
|
installHint: () => (isZh() ? '安装建议' : 'Install'),
|
|
300
|
+
/** 版本不满足的明细行:声明范围 vs 实装版本(#410)。 */
|
|
301
|
+
mismatchLine: (item) =>
|
|
302
|
+
isZh() ? `声明 ${item.expected},当前 ${item.found}` : `declared ${item.expected}, installed ${item.found}`,
|
|
278
303
|
// ── startup-roster issues (issue #144) ─────────────────────────────────
|
|
279
304
|
startupIssues: () => (isZh() ? '启动区问题' : 'Startup roster issues'),
|
|
280
305
|
startupIssueFix: () => (isZh() ? '修复' : 'Fix'),
|
|
281
306
|
startupIssueRemove: () => (isZh() ? '移除' : 'Remove'),
|
|
282
307
|
startupIssueUnresolvable: () => (isZh() ? '包不可解析' : 'Unresolvable'),
|
|
283
|
-
|
|
308
|
+
startupIssueDependencyMissing: () => (isZh() ? '依赖缺失' : 'Missing dependency'),
|
|
309
|
+
startupIssueDependencyMismatch: () => (isZh() ? '版本不满足' : 'Version mismatch'),
|
|
310
|
+
startupIssueDependencyUnknown: () => (isZh() ? '依赖错误' : 'Dependency error'),
|
|
284
311
|
startupIssueDuplicate: () => (isZh() ? '重复 id' : 'Duplicate id'),
|
|
285
312
|
}
|
|
286
313
|
// ── api ───────────────────────────────────────────────────────────────
|
|
@@ -323,11 +350,17 @@ function statusLabel(status) {
|
|
|
323
350
|
return status
|
|
324
351
|
}
|
|
325
352
|
}
|
|
326
|
-
/** Failure-classification badge label (issue #86
|
|
353
|
+
/** Failure-classification badge label (issue #86, split by #410):
|
|
354
|
+
* dependency-missing / dependency-mismatch / legacy dependency / code / other. */
|
|
327
355
|
function failureTypeLabel(type) {
|
|
328
356
|
switch (type) {
|
|
357
|
+
case 'dependency-missing':
|
|
358
|
+
return strings.failureDependencyMissing()
|
|
359
|
+
case 'dependency-mismatch':
|
|
360
|
+
return strings.failureDependencyMismatch()
|
|
329
361
|
case 'dependency':
|
|
330
|
-
|
|
362
|
+
// pre-#410 的记录(也含 mount 期 module 解析失败)仍渲染出徽章
|
|
363
|
+
return strings.failureDependencyUnknown()
|
|
331
364
|
case 'code':
|
|
332
365
|
return strings.failureCode()
|
|
333
366
|
case 'other':
|
|
@@ -336,6 +369,13 @@ function failureTypeLabel(type) {
|
|
|
336
369
|
return type
|
|
337
370
|
}
|
|
338
371
|
}
|
|
372
|
+
/** 版本不满足时的明细文本(声明 vs 当前);无明细时返回空数组(不渲染该行)。 */
|
|
373
|
+
function mismatchLabels(entry) {
|
|
374
|
+
const items = Array.isArray(entry.mismatchedDeps) ? entry.mismatchedDeps : []
|
|
375
|
+
return items
|
|
376
|
+
.filter((item) => item !== null && typeof item === 'object')
|
|
377
|
+
.map((item) => `${item.name}:${strings.mismatchLine(item)}`)
|
|
378
|
+
}
|
|
339
379
|
// ── event log ─────────────────────────────────────────────────────────
|
|
340
380
|
// Event type → badge label + color variant (mirrors the dfa-op chip style).
|
|
341
381
|
const EVENT_LABELS = {
|
|
@@ -370,13 +410,18 @@ function eventVariant(type) {
|
|
|
370
410
|
return 'neutral'
|
|
371
411
|
}
|
|
372
412
|
}
|
|
373
|
-
/** Startup-issue badge label (issue #144): unresolvable /
|
|
413
|
+
/** Startup-issue badge label (issue #144, split by #410): unresolvable /
|
|
414
|
+
* dependency-missing / dependency-mismatch / legacy dependency / dup. */
|
|
374
415
|
function startupIssueLabel(type) {
|
|
375
416
|
switch (type) {
|
|
376
417
|
case 'unresolvable':
|
|
377
418
|
return strings.startupIssueUnresolvable()
|
|
419
|
+
case 'dependency-missing':
|
|
420
|
+
return strings.startupIssueDependencyMissing()
|
|
421
|
+
case 'dependency-mismatch':
|
|
422
|
+
return strings.startupIssueDependencyMismatch()
|
|
378
423
|
case 'dependency':
|
|
379
|
-
return strings.
|
|
424
|
+
return strings.startupIssueDependencyUnknown()
|
|
380
425
|
case 'duplicate-id':
|
|
381
426
|
return strings.startupIssueDuplicate()
|
|
382
427
|
default:
|
|
@@ -527,7 +572,7 @@ const icon = {
|
|
|
527
572
|
size,
|
|
528
573
|
),
|
|
529
574
|
// 代码(issue #54 阶段 1 新增):尖括号 `</>`,预览/代码切换的代码视图
|
|
530
|
-
//
|
|
575
|
+
// 代码图标(预览 / 代码切换的代码视图),stroke=currentColor 风格与其余图标一致。
|
|
531
576
|
code: (size = 16) =>
|
|
532
577
|
iconSvg(
|
|
533
578
|
[
|
|
@@ -536,8 +581,8 @@ const icon = {
|
|
|
536
581
|
],
|
|
537
582
|
size,
|
|
538
583
|
),
|
|
539
|
-
// 下载(issue #85
|
|
540
|
-
//
|
|
584
|
+
// 下载(issue #85 新增):箭头入托盘,mermaid 卡片导出 PNG/SVG 用,
|
|
585
|
+
// stroke=currentColor 风格与其余图标一致。
|
|
541
586
|
download: (size = 16) =>
|
|
542
587
|
iconSvg(
|
|
543
588
|
[
|
|
@@ -547,8 +592,8 @@ const icon = {
|
|
|
547
592
|
],
|
|
548
593
|
size,
|
|
549
594
|
),
|
|
550
|
-
// 复制(issue #85
|
|
551
|
-
//
|
|
595
|
+
// 复制(issue #85 新增):双层矩形,mermaid 卡片复制源码用,
|
|
596
|
+
// stroke=currentColor 风格与其余图标一致。
|
|
552
597
|
copy: (size = 16) =>
|
|
553
598
|
iconSvg(
|
|
554
599
|
[
|
|
@@ -557,6 +602,92 @@ const icon = {
|
|
|
557
602
|
],
|
|
558
603
|
size,
|
|
559
604
|
),
|
|
605
|
+
// ── 控件图标化(issue #486):卡片工具条「文字收起、只留图标」需要每个按钮都有图标 ──
|
|
606
|
+
// 全屏浮窗(四角向外),mermaid 缩放条用。
|
|
607
|
+
maximize: (size = 16) =>
|
|
608
|
+
iconSvg(
|
|
609
|
+
[
|
|
610
|
+
createElement('path', { d: 'M8 3H5a2 2 0 0 0-2 2v3' }),
|
|
611
|
+
createElement('path', { d: 'M16 3h3a2 2 0 0 1 2 2v3' }),
|
|
612
|
+
createElement('path', { d: 'M21 16v3a2 2 0 0 1-2 2h-3' }),
|
|
613
|
+
createElement('path', { d: 'M3 16v3a2 2 0 0 0 2 2h3' }),
|
|
614
|
+
],
|
|
615
|
+
size,
|
|
616
|
+
),
|
|
617
|
+
// 适应容器(「完整显示整张图」):外框套内框,表达「内容装进可用宽度」。
|
|
618
|
+
fit: (size = 16) =>
|
|
619
|
+
iconSvg(
|
|
620
|
+
[
|
|
621
|
+
createElement('rect', { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
|
|
622
|
+
createElement('rect', { x: 8, y: 8, width: 8, height: 8, rx: 1 }),
|
|
623
|
+
],
|
|
624
|
+
size,
|
|
625
|
+
),
|
|
626
|
+
// 适应宽度 / 铺满:两侧容器边界 + 水平双向箭头。
|
|
627
|
+
fill: (size = 16) =>
|
|
628
|
+
iconSvg(
|
|
629
|
+
[
|
|
630
|
+
createElement('path', { d: 'M3 5v14' }),
|
|
631
|
+
createElement('path', { d: 'M21 5v14' }),
|
|
632
|
+
createElement('path', { d: 'M7 12h10' }),
|
|
633
|
+
createElement('path', { d: 'm9 9-3 3 3 3' }),
|
|
634
|
+
createElement('path', { d: 'm15 9 3 3-3 3' }),
|
|
635
|
+
],
|
|
636
|
+
size,
|
|
637
|
+
),
|
|
638
|
+
// 缩小:与既有 plus 同粗细的单横线。
|
|
639
|
+
minus: (size = 16) => iconSvg([createElement('line', { x1: 5, y1: 12, x2: 19, y2: 12 })], size),
|
|
640
|
+
// 自动换行:折行箭头(lucide wrap-text 同形),代码块换行切换用。
|
|
641
|
+
wrap: (size = 16) =>
|
|
642
|
+
iconSvg(
|
|
643
|
+
[
|
|
644
|
+
createElement('line', { x1: 3, y1: 6, x2: 21, y2: 6 }),
|
|
645
|
+
createElement('path', { d: 'M3 12h15a3 3 0 1 1 0 6h-4' }),
|
|
646
|
+
createElement('polyline', { points: '16 16 14 18 16 20' }),
|
|
647
|
+
createElement('line', { x1: 3, y1: 18, x2: 10, y2: 18 }),
|
|
648
|
+
],
|
|
649
|
+
size,
|
|
650
|
+
),
|
|
651
|
+
// markdown 文本(框内 M↓):「渲染为 Markdown」用。
|
|
652
|
+
markdown: (size = 16) =>
|
|
653
|
+
iconSvg(
|
|
654
|
+
[
|
|
655
|
+
createElement('rect', { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
|
|
656
|
+
createElement('path', { d: 'M7 15V9l3 3 3-3v6' }),
|
|
657
|
+
createElement('path', { d: 'M17 9v6' }),
|
|
658
|
+
createElement('path', { d: 'm15 13 2 2 2-2' }),
|
|
659
|
+
],
|
|
660
|
+
size,
|
|
661
|
+
),
|
|
662
|
+
// 更多(三点):卡片工具条的「更多」菜单触发器 —— 低频按钮收进菜单,不长期霸占头部。
|
|
663
|
+
more: (size = 16) =>
|
|
664
|
+
iconSvg(
|
|
665
|
+
[
|
|
666
|
+
createElement('circle', { cx: 5, cy: 12, r: 1.6, fill: 'currentColor', stroke: 'none' }),
|
|
667
|
+
createElement('circle', { cx: 12, cy: 12, r: 1.6, fill: 'currentColor', stroke: 'none' }),
|
|
668
|
+
createElement('circle', { cx: 19, cy: 12, r: 1.6, fill: 'currentColor', stroke: 'none' }),
|
|
669
|
+
],
|
|
670
|
+
size,
|
|
671
|
+
),
|
|
672
|
+
// 图表(柱状):mermaid 图表卡片标题与「渲染为图表」选项用。
|
|
673
|
+
chart: (size = 16) =>
|
|
674
|
+
iconSvg(
|
|
675
|
+
[
|
|
676
|
+
createElement('line', { x1: 6, y1: 20, x2: 6, y2: 10 }),
|
|
677
|
+
createElement('line', { x1: 12, y1: 20, x2: 12, y2: 4 }),
|
|
678
|
+
createElement('line', { x1: 18, y1: 20, x2: 18, y2: 14 }),
|
|
679
|
+
],
|
|
680
|
+
size,
|
|
681
|
+
),
|
|
682
|
+
// 预览(眼睛):视图切换的「预览」用 —— 比 file 更贴语义。
|
|
683
|
+
eye: (size = 16) =>
|
|
684
|
+
iconSvg(
|
|
685
|
+
[
|
|
686
|
+
createElement('path', { d: 'M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7z' }),
|
|
687
|
+
createElement('circle', { cx: 12, cy: 12, r: 3 }),
|
|
688
|
+
],
|
|
689
|
+
size,
|
|
690
|
+
),
|
|
560
691
|
// 箭头向上(更新图标):向上的箭头,表示更新操作
|
|
561
692
|
arrowUp: (size = 16) =>
|
|
562
693
|
iconSvg(
|
|
@@ -870,6 +1001,26 @@ function RowActions({ entry, busy, onRetry, onRemove }) {
|
|
|
870
1001
|
),
|
|
871
1002
|
)
|
|
872
1003
|
}
|
|
1004
|
+
/** 依赖类失败(#410 分两类;'dependency' 是 pre-#410 的旧分类,含 mount 期 module 解析失败)。 */
|
|
1005
|
+
function isDependencyFailure(type) {
|
|
1006
|
+
return type === 'dependency' || type === 'dependency-missing' || type === 'dependency-mismatch'
|
|
1007
|
+
}
|
|
1008
|
+
/** 依赖失败行展示的安装命令;其他失败类型、空命令、宿主提供的包(无命令)都不展示(#410)。 */
|
|
1009
|
+
function installCommandOf(entry) {
|
|
1010
|
+
if (!isDependencyFailure(entry.failureType)) return null
|
|
1011
|
+
return typeof entry.installHint === 'string' && entry.installHint !== '' ? entry.installHint : null
|
|
1012
|
+
}
|
|
1013
|
+
/** 版本不满足明细行(#410):声明范围 vs 实装版本;无明细时不渲染。 */
|
|
1014
|
+
function MismatchHint({ entry }) {
|
|
1015
|
+
const labels = mismatchLabels(entry)
|
|
1016
|
+
if (labels.length === 0) return null
|
|
1017
|
+
return createElement(
|
|
1018
|
+
'div',
|
|
1019
|
+
{ className: 'dsh-my-guardian-mismatch' },
|
|
1020
|
+
createElement('span', { className: 'dsh-my-guardian-mismatch-label' }, strings.failureDependencyMismatch()),
|
|
1021
|
+
labels.map((label, index) => createElement('code', { key: index }, label)),
|
|
1022
|
+
)
|
|
1023
|
+
}
|
|
873
1024
|
/** 冻结行提示(连败停止自动重试,需手动操作)。 */
|
|
874
1025
|
function FrozenHint({ status }) {
|
|
875
1026
|
if (status !== 'frozen') return null
|
|
@@ -886,9 +1037,7 @@ function EntryRow({ entry, source, onAction }) {
|
|
|
886
1037
|
const [confirming, setConfirming] = useState(false)
|
|
887
1038
|
const [busy, setBusy] = useState(false)
|
|
888
1039
|
const hasError = typeof entry.lastError === 'string' && entry.lastError !== ''
|
|
889
|
-
const
|
|
890
|
-
const installHint =
|
|
891
|
-
isDepFailure && typeof entry.installHint === 'string' && entry.installHint !== '' ? entry.installHint : null
|
|
1040
|
+
const installHint = installCommandOf(entry)
|
|
892
1041
|
const run = (kind) => {
|
|
893
1042
|
setBusy(true)
|
|
894
1043
|
Promise.resolve(onAction(kind, entry)).finally(() => setBusy(false))
|
|
@@ -899,6 +1048,7 @@ function EntryRow({ entry, source, onAction }) {
|
|
|
899
1048
|
createElement(RowHead, { entry, source }),
|
|
900
1049
|
createElement(RowMeta, { entry }),
|
|
901
1050
|
createElement(FrozenHint, { status: entry.status }),
|
|
1051
|
+
createElement(MismatchHint, { entry }),
|
|
902
1052
|
installHint
|
|
903
1053
|
? createElement(
|
|
904
1054
|
'div',
|