dsh-my-guardian 0.3.5 → 0.3.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 +14 -1
- package/lib/client.js +29 -6
- package/lib/events.js +21 -2
- package/lib/parts/row.part.js +14 -1
- package/lib/parts/styles.part.js +1 -0
- package/lib/parts/util.part.js +6 -2
- package/lib/parts/view.part.js +8 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,14 +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
|
-
## [0.3.
|
|
8
|
+
## [0.3.6] - 2026-09-04
|
|
9
9
|
|
|
10
10
|
### 变更
|
|
11
11
|
|
|
12
|
+
- fix(guardian): 事件日志不再访问 entry.id getter——构造期 parent.tree 未就绪会抛错炸掉 DSH 启动(隔离实例复现)
|
|
13
|
+
- fix(guardian): 诊断面板可读性 + entry 标识修复——插件失败时不再"看不懂/兜不住"
|
|
14
|
+
- fix(guardian): #86 发版一致性修复——package.json 0.3.5 + CHANGELOG 0.3.5 段(rebase 冲突还原恢复)
|
|
12
15
|
- chore(release): dsh-my-guardian v0.3.5(#86 依赖预检 + 验证清单)
|
|
13
16
|
- feat(guardian): #86 候选区插件依赖预检 + 失败分类 (#124)
|
|
14
17
|
- docs: #106 安装命令统一加 --trust-lockfile (#113)
|
|
15
18
|
|
|
19
|
+
## [0.3.5] - 2026-09-04
|
|
20
|
+
|
|
21
|
+
### 新增
|
|
22
|
+
|
|
23
|
+
- [#86](https://github.com/baosfeng/my-dsh-plugins/issues/86) 候选区插件依赖预检 + 失败分类:热挂载前校验依赖就绪,失败按类别诊断(缺失依赖/锁定冲突/加载错误),诊断面板展示分类详情
|
|
24
|
+
|
|
25
|
+
### 变更
|
|
26
|
+
|
|
27
|
+
- 验证清单与效果图同步(发版前功能级验证归档)
|
|
28
|
+
|
|
16
29
|
## [0.3.4] - 2026-09-02
|
|
17
30
|
|
|
18
31
|
### 变更
|
package/lib/client.js
CHANGED
|
@@ -122,6 +122,7 @@ const STYLES = `
|
|
|
122
122
|
color:var(--dsw-alias-state-success-primary); word-break:break-all; }
|
|
123
123
|
.dsh-my-guardian-row-meta { display:flex; align-items:center; gap:6px; font:var(--dsw-font-xxxs-11); color:var(--dsw-alias-label-tertiary); }
|
|
124
124
|
.dsh-my-guardian-attempts { color:var(--dsw-alias-state-error-primary); }
|
|
125
|
+
.dsh-my-guardian-freeze-hint { font:var(--dsw-font-xxxs-11); color:var(--dsw-alias-state-warn-primary); line-height:1.6; }
|
|
125
126
|
.dsh-my-guardian-link { display:inline-flex; align-items:center; gap:3px; padding:0; border:none; background:transparent; cursor:pointer;
|
|
126
127
|
font:var(--dsw-font-xxxs-11); color:var(--dsw-alias-label-tertiary);
|
|
127
128
|
transition:color var(--ds-transition-duration-slow) var(--ds-ease-in-out); }
|
|
@@ -199,8 +200,8 @@ const strings = {
|
|
|
199
200
|
safeMode: () => (isZh() ? '安全模式' : 'Safe mode'),
|
|
200
201
|
safeModeDesc: () =>
|
|
201
202
|
isZh()
|
|
202
|
-
? '
|
|
203
|
-
: 'Skips every staged/promoted plugin mount — fast recovery',
|
|
203
|
+
? '开启后所有候选/已转正插件(候选区 cordis.staged.json 中的条目)都不再加载,用于快速恢复环境。注意:安全模式只作用于启动后挂载的候选插件;若插件直接写进启动名册(cordis.patch.yml / profile),DSH 启动时仍是 all-or-nothing——请把新插件先放进候选区。'
|
|
204
|
+
: 'Skips every staged/promoted plugin mount — fast recovery. Note: this only covers entries mounted after boot (cordis.staged.json); plugins in the boot roster (cordis.patch.yml / profile) are still all-or-nothing — put new plugins in the staged file first.',
|
|
204
205
|
staged: () => (isZh() ? '候选' : 'staged'),
|
|
205
206
|
promoted: () => (isZh() ? '转正' : 'promoted'),
|
|
206
207
|
entries: () => (isZh() ? '插件条目' : 'Plugin entries'),
|
|
@@ -228,6 +229,10 @@ const strings = {
|
|
|
228
229
|
loading: () => (isZh() ? '加载中…' : 'Loading…'),
|
|
229
230
|
events: () => (isZh() ? '最近事件' : 'Recent events'),
|
|
230
231
|
attempts: (n) => (isZh() ? `失败 ${n} 次` : `failed ×${n}`),
|
|
232
|
+
frozenHint: () =>
|
|
233
|
+
isZh()
|
|
234
|
+
? '已冻结:连续失败停止自动重试——点击刷新按钮手动重试,或移除该条目'
|
|
235
|
+
: 'Frozen: auto-retry stopped after repeated failures — retry manually or remove the entry',
|
|
231
236
|
failureDependency: () => (isZh() ? '依赖缺失' : 'Dependency'),
|
|
232
237
|
failureCode: () => (isZh() ? '代码错误' : 'Code error'),
|
|
233
238
|
failureOther: () => (isZh() ? '其他' : 'Other'),
|
|
@@ -784,8 +789,20 @@ function RowActions({ entry, busy, onRetry, onRemove }) {
|
|
|
784
789
|
)
|
|
785
790
|
}
|
|
786
791
|
|
|
792
|
+
/** 冻结行提示(连败停止自动重试,需手动操作)。 */
|
|
793
|
+
function FrozenHint({ status }) {
|
|
794
|
+
if (status !== 'frozen') return null
|
|
795
|
+
return createElement('div', { className: 'dsh-my-guardian-freeze-hint' }, strings.frozenHint())
|
|
796
|
+
}
|
|
797
|
+
|
|
787
798
|
function EntryRow({ entry, source, onAction }) {
|
|
788
|
-
|
|
799
|
+
// 失败/冻结行默认展开错误详情(用户之前必须手动点开才看得到真正报错)。
|
|
800
|
+
const [expanded, setExpanded] = useState(
|
|
801
|
+
() =>
|
|
802
|
+
typeof entry.lastError === 'string' &&
|
|
803
|
+
entry.lastError !== '' &&
|
|
804
|
+
(entry.status === 'failed' || entry.status === 'frozen'),
|
|
805
|
+
)
|
|
789
806
|
const [confirming, setConfirming] = useState(false)
|
|
790
807
|
const [busy, setBusy] = useState(false)
|
|
791
808
|
const hasError = typeof entry.lastError === 'string' && entry.lastError !== ''
|
|
@@ -803,6 +820,7 @@ function EntryRow({ entry, source, onAction }) {
|
|
|
803
820
|
{ className: 'dsh-my-guardian-row' },
|
|
804
821
|
createElement(RowHead, { entry, source }),
|
|
805
822
|
createElement(RowMeta, { entry }),
|
|
823
|
+
createElement(FrozenHint, { status: entry.status }),
|
|
806
824
|
installHint
|
|
807
825
|
? createElement(
|
|
808
826
|
'div',
|
|
@@ -957,14 +975,19 @@ function EntryList({ rows, onAction }) {
|
|
|
957
975
|
)
|
|
958
976
|
}
|
|
959
977
|
|
|
960
|
-
/**
|
|
978
|
+
/** 高频噪音事件:每次启动/热重载都会大量产生,挤掉真正重要的诊断信息。 */
|
|
979
|
+
const EVENT_NOISE = new Set(['entry-init', 'entry-dispose'])
|
|
980
|
+
|
|
981
|
+
/** Recent guardian event log: badge + key info + time per entry.
|
|
982
|
+
* 过滤 entry-init/entry-dispose 噪音,优先展示隔离/冻结/更新失败等关键事件。 */
|
|
961
983
|
function EventList({ events }) {
|
|
962
|
-
|
|
984
|
+
const important = events.filter((event) => !EVENT_NOISE.has(event.type))
|
|
985
|
+
if (important.length === 0) return null
|
|
963
986
|
return createElement(
|
|
964
987
|
'div',
|
|
965
988
|
{ className: 'dsh-my-guardian-events' },
|
|
966
989
|
createElement('div', { className: 'dsh-my-guardian-events-title' }, icon.clock(14), strings.events()),
|
|
967
|
-
|
|
990
|
+
important.map((event, index) =>
|
|
968
991
|
createElement(
|
|
969
992
|
'div',
|
|
970
993
|
{
|
package/lib/events.js
CHANGED
|
@@ -17,10 +17,10 @@ export function logEvent(shared, type, message) {
|
|
|
17
17
|
/** Register the loader/HMR diagnostic listeners (R9/R10). */
|
|
18
18
|
export function attachEventListeners(ctx, shared) {
|
|
19
19
|
ctx.on('loader/entry-init', (entry) => {
|
|
20
|
-
logEvent(shared, 'entry-init', `entry ${entry
|
|
20
|
+
logEvent(shared, 'entry-init', `entry ${entryLabelOf(entry)} initialized`)
|
|
21
21
|
})
|
|
22
22
|
ctx.on('loader/partial-dispose', (entry) => {
|
|
23
|
-
logEvent(shared, 'entry-dispose', `entry ${entry
|
|
23
|
+
logEvent(shared, 'entry-dispose', `entry ${entryLabelOf(entry)} disposed`)
|
|
24
24
|
})
|
|
25
25
|
ctx.on('hmr/config-update-failed', (filename, error) => {
|
|
26
26
|
logEvent(shared, 'update-failed', `${filename}: ${error instanceof Error ? error.message : String(error)}`)
|
|
@@ -28,3 +28,22 @@ export function attachEventListeners(ctx, shared) {
|
|
|
28
28
|
shared.persistSoon()
|
|
29
29
|
})
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* entry 可读标识——⚠️ 只读 options 字段,绝不访问 `entry.id` getter:
|
|
34
|
+
* loader 在 Entry **构造函数中** emit `loader/entry-init`,此时
|
|
35
|
+
* `parent.tree` 尚未就绪,访问 getter 会抛 "Cannot read properties of
|
|
36
|
+
* undefined (reading 'tree')" —— 这个异常发生在启动阶段,会让整个
|
|
37
|
+
* DSH 服务起不来(守护插件自己炸启动,实锤隔离实例复现)。
|
|
38
|
+
* (此前读 `entry?.options?.id` 安全但恒为空显示 '?';本次改为
|
|
39
|
+
* options.id → options.name → '?' 的回退,全部属性访问都可安全兜底。)
|
|
40
|
+
*/
|
|
41
|
+
function entryLabelOf(entry) {
|
|
42
|
+
if (entry === null || typeof entry !== 'object') return '?'
|
|
43
|
+
const options = entry.options
|
|
44
|
+
if (options !== null && typeof options === 'object') {
|
|
45
|
+
if (options.id !== undefined && options.id !== null) return String(options.id)
|
|
46
|
+
if (typeof options.name === 'string' && options.name !== '') return options.name
|
|
47
|
+
}
|
|
48
|
+
return '?'
|
|
49
|
+
}
|
package/lib/parts/row.part.js
CHANGED
|
@@ -132,8 +132,20 @@ function RowActions({ entry, busy, onRetry, onRemove }) {
|
|
|
132
132
|
)
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/** 冻结行提示(连败停止自动重试,需手动操作)。 */
|
|
136
|
+
function FrozenHint({ status }) {
|
|
137
|
+
if (status !== 'frozen') return null
|
|
138
|
+
return createElement('div', { className: 'dsh-my-guardian-freeze-hint' }, strings.frozenHint())
|
|
139
|
+
}
|
|
140
|
+
|
|
135
141
|
function EntryRow({ entry, source, onAction }) {
|
|
136
|
-
|
|
142
|
+
// 失败/冻结行默认展开错误详情(用户之前必须手动点开才看得到真正报错)。
|
|
143
|
+
const [expanded, setExpanded] = useState(
|
|
144
|
+
() =>
|
|
145
|
+
typeof entry.lastError === 'string' &&
|
|
146
|
+
entry.lastError !== '' &&
|
|
147
|
+
(entry.status === 'failed' || entry.status === 'frozen'),
|
|
148
|
+
)
|
|
137
149
|
const [confirming, setConfirming] = useState(false)
|
|
138
150
|
const [busy, setBusy] = useState(false)
|
|
139
151
|
const hasError = typeof entry.lastError === 'string' && entry.lastError !== ''
|
|
@@ -151,6 +163,7 @@ function EntryRow({ entry, source, onAction }) {
|
|
|
151
163
|
{ className: 'dsh-my-guardian-row' },
|
|
152
164
|
createElement(RowHead, { entry, source }),
|
|
153
165
|
createElement(RowMeta, { entry }),
|
|
166
|
+
createElement(FrozenHint, { status: entry.status }),
|
|
154
167
|
installHint
|
|
155
168
|
? createElement(
|
|
156
169
|
'div',
|
package/lib/parts/styles.part.js
CHANGED
|
@@ -88,6 +88,7 @@ const STYLES = `
|
|
|
88
88
|
color:var(--dsw-alias-state-success-primary); word-break:break-all; }
|
|
89
89
|
.dsh-my-guardian-row-meta { display:flex; align-items:center; gap:6px; font:var(--dsw-font-xxxs-11); color:var(--dsw-alias-label-tertiary); }
|
|
90
90
|
.dsh-my-guardian-attempts { color:var(--dsw-alias-state-error-primary); }
|
|
91
|
+
.dsh-my-guardian-freeze-hint { font:var(--dsw-font-xxxs-11); color:var(--dsw-alias-state-warn-primary); line-height:1.6; }
|
|
91
92
|
.dsh-my-guardian-link { display:inline-flex; align-items:center; gap:3px; padding:0; border:none; background:transparent; cursor:pointer;
|
|
92
93
|
font:var(--dsw-font-xxxs-11); color:var(--dsw-alias-label-tertiary);
|
|
93
94
|
transition:color var(--ds-transition-duration-slow) var(--ds-ease-in-out); }
|
package/lib/parts/util.part.js
CHANGED
|
@@ -12,8 +12,8 @@ const strings = {
|
|
|
12
12
|
safeMode: () => (isZh() ? '安全模式' : 'Safe mode'),
|
|
13
13
|
safeModeDesc: () =>
|
|
14
14
|
isZh()
|
|
15
|
-
? '
|
|
16
|
-
: 'Skips every staged/promoted plugin mount — fast recovery',
|
|
15
|
+
? '开启后所有候选/已转正插件(候选区 cordis.staged.json 中的条目)都不再加载,用于快速恢复环境。注意:安全模式只作用于启动后挂载的候选插件;若插件直接写进启动名册(cordis.patch.yml / profile),DSH 启动时仍是 all-or-nothing——请把新插件先放进候选区。'
|
|
16
|
+
: 'Skips every staged/promoted plugin mount — fast recovery. Note: this only covers entries mounted after boot (cordis.staged.json); plugins in the boot roster (cordis.patch.yml / profile) are still all-or-nothing — put new plugins in the staged file first.',
|
|
17
17
|
staged: () => (isZh() ? '候选' : 'staged'),
|
|
18
18
|
promoted: () => (isZh() ? '转正' : 'promoted'),
|
|
19
19
|
entries: () => (isZh() ? '插件条目' : 'Plugin entries'),
|
|
@@ -41,6 +41,10 @@ const strings = {
|
|
|
41
41
|
loading: () => (isZh() ? '加载中…' : 'Loading…'),
|
|
42
42
|
events: () => (isZh() ? '最近事件' : 'Recent events'),
|
|
43
43
|
attempts: (n) => (isZh() ? `失败 ${n} 次` : `failed ×${n}`),
|
|
44
|
+
frozenHint: () =>
|
|
45
|
+
isZh()
|
|
46
|
+
? '已冻结:连续失败停止自动重试——点击刷新按钮手动重试,或移除该条目'
|
|
47
|
+
: 'Frozen: auto-retry stopped after repeated failures — retry manually or remove the entry',
|
|
44
48
|
failureDependency: () => (isZh() ? '依赖缺失' : 'Dependency'),
|
|
45
49
|
failureCode: () => (isZh() ? '代码错误' : 'Code error'),
|
|
46
50
|
failureOther: () => (isZh() ? '其他' : 'Other'),
|
package/lib/parts/view.part.js
CHANGED
|
@@ -123,14 +123,19 @@ function EntryList({ rows, onAction }) {
|
|
|
123
123
|
)
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
/**
|
|
126
|
+
/** 高频噪音事件:每次启动/热重载都会大量产生,挤掉真正重要的诊断信息。 */
|
|
127
|
+
const EVENT_NOISE = new Set(['entry-init', 'entry-dispose'])
|
|
128
|
+
|
|
129
|
+
/** Recent guardian event log: badge + key info + time per entry.
|
|
130
|
+
* 过滤 entry-init/entry-dispose 噪音,优先展示隔离/冻结/更新失败等关键事件。 */
|
|
127
131
|
function EventList({ events }) {
|
|
128
|
-
|
|
132
|
+
const important = events.filter((event) => !EVENT_NOISE.has(event.type))
|
|
133
|
+
if (important.length === 0) return null
|
|
129
134
|
return createElement(
|
|
130
135
|
'div',
|
|
131
136
|
{ className: 'dsh-my-guardian-events' },
|
|
132
137
|
createElement('div', { className: 'dsh-my-guardian-events-title' }, icon.clock(14), strings.events()),
|
|
133
|
-
|
|
138
|
+
important.map((event, index) =>
|
|
134
139
|
createElement(
|
|
135
140
|
'div',
|
|
136
141
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-my-guardian",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "DSH 插件治理守护:新装/更新插件先进候选区,启动后热挂载——成功转正、失败自动禁用、连续失败冻结,一键安全模式,侧边栏诊断面板。DSH web plugin: staged plugin loading with auto-disable on failure, freeze, safe mode and a sidebar panel.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|