@objectivex666/dsh-settings-search 1.2.0 → 1.4.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/LICENSE +674 -674
- package/README.en.md +1 -0
- package/README.md +1 -0
- package/lib/client.js +195 -22
- package/package.json +1 -1
- package/scripts/check.mjs +6 -0
package/README.en.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- 📂 **In-page option search (v1.2.0)** — search beyond pages and reach the specific options inside them: Plugins-page tabs (`settings.plugins.tab`), Web UI plugin cards (`web-ui.plugin.item`), general rows, and more, each shown with a "page › option" breadcrumb.
|
|
17
17
|
- 🧭 **Click-to-jump** — selecting a result clicks the matching left-nav entry to open its section; for tab options it also opens the tab and flashes the target row. A manual-path hint appears if automatic navigation is not possible.
|
|
18
18
|
- 🌱 **Progressive indexing** — self-drawing settings rows are harvested from the rendered DOM (via their `[data-slot]` anchors) the first time their page is visited, then stay searchable.
|
|
19
|
+
- 🛠 **Built-in settings page (v1.4.0)** — a "Settings Search" page in the left navigation shows the current version and checks npm for a newer release on demand; when one exists it hands you a copy-ready `dsh plugin update` command.
|
|
19
20
|
- 🌐 **i18n** — built-in Chinese/English dictionaries that follow the DSH localization system and are easy to extend.
|
|
20
21
|
- 🎨 **Theme-aware** — styled entirely with CSS variables, automatically following the DSH theme (light/dark).
|
|
21
22
|
- ⚡ **Reactive** — subscribes to settings entries being added, removed, or changed, and refreshes results automatically.
|
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- 📂 **页内选项搜索(v1.2.0)**:不只搜页面,还能直达每个设置页内部的具体选项——插件页的标签页(`settings.plugins.tab`)、Web UI 插件的卡片(`web-ui.plugin.item`)、通用设置行等,结果以「页面 › 选项」面包屑展示。
|
|
17
17
|
- 🧭 **点击即达**:选中结果后自动点击对应的左侧导航项打开所在分区;若是页内标签页会继续点开对应标签,并高亮闪烁目标选项行。无法自动跳转时显示手动路径提示。
|
|
18
18
|
- 🌱 **渐进索引**:自绘界面的设置行会在其所在页面首次被访问时从渲染 DOM 中收割标题并加入索引(此后始终可搜)。
|
|
19
|
+
- 🛠 **自带设置页(v1.4.0)**:左侧导航新增「设置搜索」页——展示当前版本,一键检查 npm 注册表上的最新版本;有更新时直接给出复制就用的 `dsh plugin update` 命令。
|
|
19
20
|
- 🌐 **多语言支持**:内置中英文国际化,可跟随 DSH 本地化系统扩展。
|
|
20
21
|
- 🎨 **主题适配**:完全使用 CSS 变量,自动跟随 DSH 主题(亮/暗)。
|
|
21
22
|
- ⚡ **动态响应**:实时订阅设置项的新增/删除/变更,搜索结果自动刷新。
|
package/lib/client.js
CHANGED
|
@@ -29,6 +29,9 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
const React = require('react')
|
|
30
30
|
|
|
31
31
|
const NS = 'settings-search-v5'
|
|
32
|
+
/** Single source of truth is package.json — scripts/check.mjs asserts this matches. */
|
|
33
|
+
const PKG_VERSION = '1.4.0'
|
|
34
|
+
const NPM_NAME = '@objectivex666/dsh-settings-search'
|
|
32
35
|
const zh = {
|
|
33
36
|
title: '设置',
|
|
34
37
|
placeholder: '搜索设置…',
|
|
@@ -38,6 +41,17 @@ window.__ModuleLoader__.load({
|
|
|
38
41
|
page: '页面',
|
|
39
42
|
option: '选项',
|
|
40
43
|
jumpFail: '未能自动跳转,请手动打开:{path}',
|
|
44
|
+
updn: '设置搜索',
|
|
45
|
+
updDesc: '设置面板顶部搜索框由本插件提供。在此检查插件的版本与更新。',
|
|
46
|
+
updCurrent: '当前版本',
|
|
47
|
+
updLatestKnown: 'npm 最新版',
|
|
48
|
+
updCheck: '检查更新',
|
|
49
|
+
updChecking: '正在检查…',
|
|
50
|
+
updNewest: '已是最新版本 ✓',
|
|
51
|
+
updFound: '发现新版本 {ver},可执行以下命令更新后重启 DSH:',
|
|
52
|
+
updErr: '检查失败(网络或注册表不可达),请稍后重试。',
|
|
53
|
+
updCopy: '复制命令',
|
|
54
|
+
updCopied: '已复制 ✓',
|
|
41
55
|
}
|
|
42
56
|
const en = {
|
|
43
57
|
title: 'Settings',
|
|
@@ -48,6 +62,17 @@ window.__ModuleLoader__.load({
|
|
|
48
62
|
page: 'Page',
|
|
49
63
|
option: 'Option',
|
|
50
64
|
jumpFail: 'Could not navigate automatically — open it here: {path}',
|
|
65
|
+
updn: 'Settings Search',
|
|
66
|
+
updDesc: "The search box atop the settings panel comes from this plugin. Check its version and updates here.",
|
|
67
|
+
updCurrent: 'Current version',
|
|
68
|
+
updLatestKnown: 'npm latest',
|
|
69
|
+
updCheck: 'Check for updates',
|
|
70
|
+
updChecking: 'Checking…',
|
|
71
|
+
updNewest: 'You are on the latest version ✓',
|
|
72
|
+
updFound: 'Version {ver} available. Run the command below, then restart DSH:',
|
|
73
|
+
updErr: 'Check failed (network or registry unreachable). Try again later.',
|
|
74
|
+
updCopy: 'Copy command',
|
|
75
|
+
updCopied: 'Copied ✓',
|
|
51
76
|
}
|
|
52
77
|
|
|
53
78
|
/** Inner-row slots that represent specific options inside one page. */
|
|
@@ -80,6 +105,19 @@ window.__ModuleLoader__.load({
|
|
|
80
105
|
.sss-guide { padding: 7px 10px; border-radius: 8px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); color: var(--dsw-alias-label-primary); font-size: 12px; line-height: 18px; }
|
|
81
106
|
@keyframes sssFlashBg { 0% { background-color: var(--dsw-alias-interactive-bg-hover); } 100% { background-color: transparent; } }
|
|
82
107
|
.sss-flash { animation: sssFlashBg 1.8s ease-out; border-radius: 8px; }
|
|
108
|
+
.sss-upd { display: flex; flex-direction: column; gap: 12px; padding: 16px; max-width: 560px; }
|
|
109
|
+
.sss-upd-title { font-size: 15px; font-weight: 600; line-height: 22px; color: var(--dsw-alias-label-primary); margin: 0; }
|
|
110
|
+
.sss-upd-desc { margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
111
|
+
.sss-upd-card { display: flex; flex-direction: column; gap: 10px; padding: 14px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px; background: var(--dsw-alias-bg-layer-1); }
|
|
112
|
+
.sss-upd-row { display: flex; align-items: center; gap: 8px; font-size: 13px; line-height: 20px; }
|
|
113
|
+
.sss-upd-key { flex-shrink: 0; width: 88px; color: var(--dsw-alias-label-secondary); }
|
|
114
|
+
.sss-upd-val { color: var(--dsw-alias-label-primary); }
|
|
115
|
+
.sss-upd-badge { flex-shrink: 0; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 18px; }
|
|
116
|
+
.sss-btn { height: 28px; padding: 0 12px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-size: 12px; cursor: pointer; }
|
|
117
|
+
.sss-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
118
|
+
.sss-btn:disabled { opacity: .55; cursor: default; }
|
|
119
|
+
.sss-cmd { user-select: all; font-family: ui-monospace, Consolas, 'Courier New', monospace; font-size: 11.5px; line-height: 20px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; padding: 4px 8px; word-break: break-all; }
|
|
120
|
+
.sss-upd-note { margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
83
121
|
`
|
|
84
122
|
let stylesInstalled = false
|
|
85
123
|
function installStyles() {
|
|
@@ -135,26 +173,50 @@ window.__ModuleLoader__.load({
|
|
|
135
173
|
}
|
|
136
174
|
|
|
137
175
|
/**
|
|
138
|
-
* Read visible titles for self-drawing occupants out of the live DOM
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
176
|
+
* Read visible titles for self-drawing occupants out of the live DOM.
|
|
177
|
+
* Two shell shapes exist for `[data-slot="<name>"]`:
|
|
178
|
+
* - per-occupant anchors: one element each, count === regs → pair ids;
|
|
179
|
+
* - a single WRAPPER around all rows (e.g. settings.general.item's
|
|
180
|
+
* stacked section): harvest each direct child as one row, keyed by
|
|
181
|
+
* render order ("#i") since id pairing is impossible.
|
|
142
182
|
*/
|
|
143
183
|
const runHarvest = () => {
|
|
144
184
|
let grew = false
|
|
145
185
|
if (typeof document === 'undefined') return false
|
|
186
|
+
let map
|
|
146
187
|
for (const cfg of DEEP_SLOTS) {
|
|
147
|
-
let
|
|
148
|
-
try {
|
|
188
|
+
let containers = []
|
|
189
|
+
try { containers = [...document.querySelectorAll(`[data-slot="${cfg.slot}"]`)] } catch { continue }
|
|
149
190
|
const regs = sortEntries(entriesOf(cfg.slot))
|
|
150
|
-
if (
|
|
151
|
-
let
|
|
191
|
+
if (regs.length === 0) continue
|
|
192
|
+
let nodes = []
|
|
193
|
+
if (containers.length === 1 && containers[0].childElementCount > 0) {
|
|
194
|
+
nodes = [...containers[0].children]
|
|
195
|
+
} else {
|
|
196
|
+
nodes = containers.filter((n) => n.childElementCount >= 0)
|
|
197
|
+
}
|
|
198
|
+
if (nodes.length === 0) continue
|
|
199
|
+
map = harvest.get(cfg.slot)
|
|
152
200
|
if (map === undefined) { map = new Map(); harvest.set(cfg.slot, map) }
|
|
201
|
+
if (nodes.length !== regs.length) {
|
|
202
|
+
// wrapper shape: index-keyed loose harvest of every rendered row
|
|
203
|
+
nodes.forEach((node, i) => {
|
|
204
|
+
const key = '#' + i
|
|
205
|
+
if (map.has(key)) return
|
|
206
|
+
const title = titleFromNode(node)
|
|
207
|
+
if (title === '') return
|
|
208
|
+
const text = (node.textContent ?? '').replace(/\s+/g, ' ').trim().slice(0, 160)
|
|
209
|
+
map.set(key, { title, idx: i, text }); grew = true
|
|
210
|
+
})
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
153
213
|
regs.forEach((entry, i) => {
|
|
154
214
|
const id = String(entry.options?.id ?? i)
|
|
155
215
|
if (map.has(id)) return
|
|
156
216
|
const title = titleFromNode(nodes[i])
|
|
157
|
-
if (title
|
|
217
|
+
if (title === '') return
|
|
218
|
+
const text = (nodes[i].textContent ?? '').replace(/\s+/g, ' ').trim().slice(0, 160)
|
|
219
|
+
map.set(id, { title, idx: i, text }); grew = true
|
|
158
220
|
})
|
|
159
221
|
}
|
|
160
222
|
if (grew) harvestSeq += 1
|
|
@@ -194,24 +256,30 @@ window.__ModuleLoader__.load({
|
|
|
194
256
|
const out = []
|
|
195
257
|
for (const cfg of DEEP_SLOTS) {
|
|
196
258
|
const mapped = harvest.get(cfg.slot)
|
|
197
|
-
|
|
259
|
+
sortEntries(entriesOf(cfg.slot)).forEach((entry, i) => {
|
|
198
260
|
const id = String(entry.options?.id ?? '')
|
|
199
|
-
if (id === '')
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
261
|
+
if (id === '') return
|
|
262
|
+
// static label first, then id-paired harvest, then loose index-keyed harvest
|
|
263
|
+
const hitId = mapped?.get(id)
|
|
264
|
+
const hitIdx = mapped?.get('#' + i)
|
|
265
|
+
const hitObj = typeof hitId === 'object' ? hitId : (typeof hitIdx === 'object' ? hitIdx : undefined)
|
|
266
|
+
const label = staticLabel(entry, cfg) || hitObj?.title || ''
|
|
267
|
+
if (label === '') return // unmapped, unharvested — invisible until it renders once
|
|
268
|
+
const key = cfg.slot + '#' + id
|
|
269
|
+
if (out.some((e) => e.key === key)) return
|
|
204
270
|
out.push({
|
|
205
271
|
kind: 'option',
|
|
206
|
-
key
|
|
272
|
+
key,
|
|
207
273
|
slot: cfg.slot,
|
|
208
274
|
id,
|
|
209
275
|
order: entry.options?.order ?? 0,
|
|
210
276
|
label,
|
|
277
|
+
hay: label + ' ' + (hitObj?.text ?? ''),
|
|
278
|
+
rowIndex: hitObj?.idx ?? i,
|
|
211
279
|
parentId: cfg.parent,
|
|
212
280
|
parentLabel: parentLabel(cfg.parent),
|
|
213
281
|
})
|
|
214
|
-
}
|
|
282
|
+
})
|
|
215
283
|
}
|
|
216
284
|
return out.sort((a, b) => a.parentLabel !== b.parentLabel
|
|
217
285
|
? a.parentLabel.localeCompare(b.parentLabel)
|
|
@@ -232,8 +300,10 @@ window.__ModuleLoader__.load({
|
|
|
232
300
|
const want = String(wantText ?? '').trim()
|
|
233
301
|
if (want === '') return false
|
|
234
302
|
const buttons = panelButtons()
|
|
235
|
-
const
|
|
236
|
-
|
|
303
|
+
const textOf = (b) => (b.textContent ?? '').trim()
|
|
304
|
+
const hit = buttons.find((b) => textOf(b) === want)
|
|
305
|
+
?? buttons.find((b) => textOf(b).startsWith(want))
|
|
306
|
+
?? buttons.find((b) => want.length >= 2 && textOf(b) !== '' && (textOf(b).includes(want) || want.includes(textOf(b))))
|
|
237
307
|
if (!hit) return false
|
|
238
308
|
hit.click()
|
|
239
309
|
return true
|
|
@@ -261,8 +331,23 @@ window.__ModuleLoader__.load({
|
|
|
261
331
|
/** Highlight the rendered row card for one indexed option. */
|
|
262
332
|
const flashOptionRow = (item) => {
|
|
263
333
|
try {
|
|
264
|
-
|
|
265
|
-
|
|
334
|
+
let target = null
|
|
335
|
+
if (typeof item.rowIndex === 'number') {
|
|
336
|
+
// wrapper shape: pick the exact rendered row by index
|
|
337
|
+
const container = document.querySelector(`[data-slot="${item.slot}"]`)
|
|
338
|
+
target = container?.children?.[item.rowIndex] ?? null
|
|
339
|
+
if (target && !(target.textContent ?? '').includes(item.label)) {
|
|
340
|
+
// index drift — fall through to textual match below
|
|
341
|
+
target = null
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (!target) {
|
|
345
|
+
const nodes = [...document.querySelectorAll(`[data-slot="${item.slot}"]`)]
|
|
346
|
+
const rows = nodes.length === 1 && nodes[0].childElementCount > 0
|
|
347
|
+
? [...nodes[0].children]
|
|
348
|
+
: nodes
|
|
349
|
+
target = rows.find((n) => (n.textContent ?? '').includes(item.label)) ?? null
|
|
350
|
+
}
|
|
266
351
|
if (!target) return
|
|
267
352
|
target.scrollIntoView({ block: 'nearest' })
|
|
268
353
|
target.classList.remove('sss-flash')
|
|
@@ -342,7 +427,7 @@ window.__ModuleLoader__.load({
|
|
|
342
427
|
? sections.filter((s) => matches(`${s.label} ${s.id}`.toLowerCase()))
|
|
343
428
|
: []
|
|
344
429
|
const shownOptions = q !== ''
|
|
345
|
-
? deeps.filter((o) => matches(`${o.
|
|
430
|
+
? deeps.filter((o) => matches(`${o.hay} ${o.parentLabel} ${o.id} ${o.slot}`.toLowerCase()))
|
|
346
431
|
: []
|
|
347
432
|
const none = q !== '' && shownPages.length === 0 && shownOptions.length === 0
|
|
348
433
|
|
|
@@ -416,6 +501,94 @@ window.__ModuleLoader__.load({
|
|
|
416
501
|
])
|
|
417
502
|
}
|
|
418
503
|
|
|
504
|
+
const compareSemver = (a, b) => {
|
|
505
|
+
const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0)
|
|
506
|
+
const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0)
|
|
507
|
+
for (let i = 0; i < 3; i += 1) {
|
|
508
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0)
|
|
509
|
+
}
|
|
510
|
+
return 0
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function UpdatePanel() {
|
|
514
|
+
const [state, setState] = React.useState('idle') // idle | loading | newest | outdated | error
|
|
515
|
+
const [latest, setLatest] = React.useState('')
|
|
516
|
+
const [copied, setCopied] = React.useState(false)
|
|
517
|
+
|
|
518
|
+
React.useEffect(() => {
|
|
519
|
+
if (!copied) return
|
|
520
|
+
const off = setTimeout(() => setCopied(false), 1600)
|
|
521
|
+
return () => clearTimeout(off)
|
|
522
|
+
}, [copied])
|
|
523
|
+
|
|
524
|
+
const check = async () => {
|
|
525
|
+
setState('loading')
|
|
526
|
+
setCopied(false)
|
|
527
|
+
try {
|
|
528
|
+
const res = await fetch(`https://registry.npmjs.org/${NPM_NAME}/latest?t=${Date.now()}`)
|
|
529
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
530
|
+
const data = await res.json()
|
|
531
|
+
const ver = typeof data?.version === 'string' ? data.version : ''
|
|
532
|
+
if (ver === '') throw new Error('no version in registry response')
|
|
533
|
+
setLatest(ver)
|
|
534
|
+
setState(compareSemver(ver, PKG_VERSION) > 0 ? 'outdated' : 'newest')
|
|
535
|
+
} catch {
|
|
536
|
+
setState('error')
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const copyCmd = async () => {
|
|
541
|
+
const cmd = `dsh plugin update ${NPM_NAME}`
|
|
542
|
+
try { await navigator.clipboard.writeText(cmd); setCopied(true) } catch { /* user can copy manually */ }
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const statusNode = {
|
|
546
|
+
idle: React.createElement('span', { className: 'sss-upd-val' }, '—'),
|
|
547
|
+
loading: React.createElement('span', { className: 'sss-upd-val' }, t('updChecking')),
|
|
548
|
+
newest: React.createElement('span', { className: 'sss-upd-val' }, t('updNewest')),
|
|
549
|
+
error: React.createElement('span', { className: 'sss-upd-val' }, t('updErr')),
|
|
550
|
+
outdated: latest === '' ? null : React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 8 } }, [
|
|
551
|
+
React.createElement('p', { className: 'sss-upd-note' }, t('updFound').replace('{ver}', `v${latest}`)),
|
|
552
|
+
React.createElement('code', { className: 'sss-cmd' }, `dsh plugin update ${NPM_NAME}`),
|
|
553
|
+
React.createElement('div', null,
|
|
554
|
+
React.createElement('button', { className: 'sss-btn', type: 'button', onClick: () => { void copyCmd() } },
|
|
555
|
+
copied ? t('updCopied') : t('updCopy'))),
|
|
556
|
+
]),
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return React.createElement('div', { className: 'sss-upd' }, [
|
|
560
|
+
React.createElement('h3', { className: 'sss-upd-title' }, `${t('updn')} v${PKG_VERSION}`),
|
|
561
|
+
React.createElement('p', { className: 'sss-upd-desc' }, t('updDesc')),
|
|
562
|
+
React.createElement('div', { className: 'sss-upd-card' }, [
|
|
563
|
+
React.createElement('div', { className: 'sss-upd-row' }, [
|
|
564
|
+
React.createElement('span', { className: 'sss-upd-key' }, t('updCurrent')),
|
|
565
|
+
React.createElement('code', { className: 'sss-cmd' }, PKG_VERSION),
|
|
566
|
+
state === 'newest' ? React.createElement('span', { className: 'sss-upd-badge' }, 'npm ✓') : null,
|
|
567
|
+
]),
|
|
568
|
+
state === 'outdated' || state === 'loading'
|
|
569
|
+
? React.createElement('div', { className: 'sss-upd-row' }, [
|
|
570
|
+
React.createElement('span', { className: 'sss-upd-key' }, t('updLatestKnown')),
|
|
571
|
+
state === 'loading' ? React.createElement('span', { className: 'sss-upd-val' }, '…') : React.createElement('code', { className: 'sss-cmd' }, latest),
|
|
572
|
+
])
|
|
573
|
+
: null,
|
|
574
|
+
state === 'outdated' || state === 'loading' || state === 'error' || state === 'idle' || state === 'newest'
|
|
575
|
+
? (state === 'outdated' ? statusNode.outdated : statusNode[state])
|
|
576
|
+
: null,
|
|
577
|
+
React.createElement('div', { className: 'sss-upd-row' },
|
|
578
|
+
React.createElement('button', { className: 'sss-btn', type: 'button', disabled: state === 'loading', onClick: () => { void check() } },
|
|
579
|
+
state === 'loading' ? t('updChecking') : t('updCheck'))),
|
|
580
|
+
]),
|
|
581
|
+
])
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
585
|
+
name: 'settings.section',
|
|
586
|
+
id: 'settings-search',
|
|
587
|
+
order: 1600,
|
|
588
|
+
locale: NS,
|
|
589
|
+
label: () => t('updn'),
|
|
590
|
+
}, UpdatePanel))
|
|
591
|
+
|
|
419
592
|
ctx.slots.inject('settings.header', () => ctx.slots.register({
|
|
420
593
|
name: 'settings.header',
|
|
421
594
|
priority: -1,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectivex666/dsh-settings-search",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "DSH settings panel search plugin – instantly filter setting sections and general items with navigation guidance. Installable via `dsh plugin add`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
package/scripts/check.mjs
CHANGED
|
@@ -54,6 +54,12 @@ check(typeof client.apply === 'function', 'client half must export apply(ctx)')
|
|
|
54
54
|
check(Array.isArray(client.inject) && client.inject.includes('slots') && client.inject.includes('locale'),
|
|
55
55
|
'client half inject must include "slots" and "locale"')
|
|
56
56
|
|
|
57
|
+
// 4. embedded version constant matches the manifest (update panel displays it)
|
|
58
|
+
const versionMatch = /const PKG_VERSION = '([^']+)'/.exec(source)
|
|
59
|
+
check(versionMatch !== null, 'lib/client.js must define const PKG_VERSION')
|
|
60
|
+
check(versionMatch?.[1] === pkg.version,
|
|
61
|
+
`PKG_VERSION (${versionMatch?.[1]}) must equal package.json version (${pkg.version})`)
|
|
62
|
+
|
|
57
63
|
if (failed > 0) {
|
|
58
64
|
console.error(`${failed} check(s) failed`)
|
|
59
65
|
process.exit(1)
|