dsh-plugin-update 0.1.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 +21 -0
- package/README.md +253 -0
- package/dist/client.js +25 -0
- package/dist/commands.js +80 -0
- package/dist/config.js +97 -0
- package/dist/gate.js +115 -0
- package/dist/host.js +279 -0
- package/dist/ports.js +5 -0
- package/dist/reader.js +193 -0
- package/dist/service.js +355 -0
- package/dist/store.js +346 -0
- package/event-list.template.json +6 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 王辰浩
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# dsh-plugin-update(更新包)
|
|
2
|
+
|
|
3
|
+
可复用的更新系统装成的 npm 包:任何 DSH 插件照着文档能集成检查更新等功能。
|
|
4
|
+
地图 #579 的一部分,本包包含更新核心、落盘与执行跑腿、读取器、宿主入口、客户端入口,集成步骤另见后续集成文档票(#585)。
|
|
5
|
+
|
|
6
|
+
全程用词:更新系统指更新功能本身(引擎、对外接口、文档);更新包指装着更新系统的这个 npm 包。电话指宿主对外提供的方法;落盘指宿主统一写本地文件的动作。
|
|
7
|
+
|
|
8
|
+
两个名字不要搞混:本包自己的名字是 `dsh-plugin-update`(包自己叫什么);目标包名参数是要检查更新的那个包是谁(默认是当前插件的旧包名 `dsh-mattpocock-skills-deck`)。新包查谁,由调用方传入目标包名决定。
|
|
9
|
+
|
|
10
|
+
## 1. 安装
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install dsh-plugin-update
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
要求 Node 22 或更高。当前版本 `0.1.0`。零运行时依赖。
|
|
17
|
+
|
|
18
|
+
包还没公开发布时,这条命令装不上:先用本地路径代替(例如 `npm install ../dsh-plugin-update`),等包发布后才用上面的命令。发布前用 `npm view dsh-plugin-update` 查一次重名。
|
|
19
|
+
|
|
20
|
+
## 2. 双入口
|
|
21
|
+
|
|
22
|
+
包根即宿主侧入口(`package.json` 的 `exports` 只暴露包根与 `./package.json`):
|
|
23
|
+
|
|
24
|
+
- 包根(宿主侧用):建更新能力、拼电话名、给调用方回电话名与处理器。宿主侧用标准模块写法,直接 `import`。
|
|
25
|
+
- `dist/client.js`(客户端侧用):电话名拼法、轮询时间口径、手工兜底命令形状。客户端侧用文本拼接消费(取编译后函数的声明体拼进插件主文件闭包),面板界面不进包。
|
|
26
|
+
|
|
27
|
+
## 3. 三步接入(双入口)
|
|
28
|
+
|
|
29
|
+
下面例子假设你的插件标识是 `my-notes-plugin`、电话名前缀是 `notes`、要查更新的包也是 `my-notes-plugin`。
|
|
30
|
+
把三处名字都换成你自己的,不要照抄 `wf`(`wf` 只是当前插件的历史取值,新插件传自己的前缀即隔离)。
|
|
31
|
+
|
|
32
|
+
第 1 步:装包。
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm install dsh-plugin-update
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
要求 Node 22 或更高。零运行时依赖。包还没公开发布时先用本地路径代替(例如 `npm install ../dsh-plugin-update`),发布前用 `npm view dsh-plugin-update` 查一次重名。
|
|
39
|
+
|
|
40
|
+
第 2 步:宿主侧接线(包根直接 `import`)。
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import { createHostUpdate } from 'dsh-plugin-update'
|
|
44
|
+
|
|
45
|
+
const update = createHostUpdate(
|
|
46
|
+
{ ctx, logCtx },
|
|
47
|
+
{
|
|
48
|
+
pluginId: 'my-notes-plugin',
|
|
49
|
+
prefix: 'notes',
|
|
50
|
+
targetPackageName: 'my-notes-plugin',
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
// 把 update.handlers 按键注册进自己的电话表,电话名从 update.phoneNames 读到,不要自己拼字符串。
|
|
54
|
+
for (const [name, handler] of Object.entries(update.handlers)) {
|
|
55
|
+
registry.set(name, handler)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
这一步得到该插件的一组电话名与处理器:`notes.updateStatus`、`notes.updateCheck`、`notes.updateInstall`。
|
|
60
|
+
单例复用键强制含插件标识,多插件不串内存状态与锁。其余配置(官方源、目录根、超时、轮询)全可选并带默认值,默认值等于现状,不传即走现状。
|
|
61
|
+
|
|
62
|
+
第 3 步:客户端侧接线(`dist/client.js` 按文本拼接消费)。
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
// 电话名拼法与宿主侧同一套,传入同一个前缀。
|
|
66
|
+
// 下面三行是形状说明,实际消费时取 dist/client.js 里对应函数的声明体、
|
|
67
|
+
// 去行首 export 后拼进插件主文件闭包,打包工具不解析 import(面板界面不进包)。
|
|
68
|
+
buildClientPhoneNames('notes') // 得到 notes.updateStatus / notes.updateCheck / notes.updateInstall
|
|
69
|
+
assertPollInterval(1000) // 面板轮询间隔,不得小于 250 毫秒
|
|
70
|
+
manualCommand({ profileName, latestVersion, installedVersion, runningVersion, jobTargetVersion, blockedReason, sourceInstall: false, targetPackageName: 'my-notes-plugin', registryUrl: 'https://registry.npmjs.org/' })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
客户端只做三件事:照前缀拼电话名、按间隔轮询查状态、装不上时展示手工兜底命令与待重启提示(见第 9、10 节)。
|
|
74
|
+
`pluginId` 必填(非空字符串且不含路径分隔符),`prefix` 不传即 `wf`,新插件务必传自己的前缀。
|
|
75
|
+
|
|
76
|
+
## 4. 配置默认值(规格 #591 冻结)
|
|
77
|
+
|
|
78
|
+
| 配置键 | 默认 | 说明 |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `pluginId` | 必填,无默认 | 非空字符串且不含路径分隔符 |
|
|
81
|
+
| `prefix` | `wf` | 电话名前缀;新电话名 = 前缀 + 点 + 动作名 |
|
|
82
|
+
| `targetPackageName` | `dsh-mattpocock-skills-deck` | 要检查更新的那个包是谁 |
|
|
83
|
+
| `registryUrl` | `https://registry.npmjs.org/` | 官方源 |
|
|
84
|
+
| `homeDir` | 现推导 | 环境变量 `DSH_HOME` 优先,否则家目录下 `.dsh` |
|
|
85
|
+
| `checkTimeoutMs` | `10000` | 联网超时,须为有限大于 0 的数 |
|
|
86
|
+
| `confirmationTtlMs` | `600000` | 凭证有效期,须为有限大于 0 的数 |
|
|
87
|
+
| `installTimeoutMs` | `900000` | 安装时限,须为有限大于 0 的数 |
|
|
88
|
+
| `panelPollMs` | `1000` | 面板轮询,不得小于 250 毫秒 |
|
|
89
|
+
|
|
90
|
+
落盘目录按标识派生:家目录下 `updates` 加插件标识加使用范围短指纹,三文件名保持 `state.json`、`install.lock`、`before.json` 不变。标识取旧值时路径与旧原文一字不差(永久冻结)。读走双读(先新后旧),写只写新,旧路径只读保留。
|
|
91
|
+
|
|
92
|
+
安装执行两条路由:桌面宿主走桌面服务(激活范围对不上宁可不装,转手工命令),普通宿主走子进程参数数组(不经 shell、不用 `PATH` 名、不按系统分支)。
|
|
93
|
+
|
|
94
|
+
## 5. 冻结清单(动任一条即走破冰讨论)
|
|
95
|
+
|
|
96
|
+
默认电话名、入参回参形状、配置写法(只经函数入参注入)、配方五键形状、事件字段基线(三事件各加必填 `pluginId`)、默认旧路径。加可选键只需同步改文档与白名单。完整结论见规格 #591。
|
|
97
|
+
|
|
98
|
+
## 6. 门禁模板自查(#584)
|
|
99
|
+
|
|
100
|
+
每条事件四样东西:事件名、级别(`error`、`warn`、`info`、`debug`)、允许字段(之外的键一律不记)、脱敏引用(`codes` 是截断或散列代号,`rules` 是具名正则名,都是引用名,命中只记规则名不记原文)。`kind` 只为计数检查服务:`resident` 常驻(始终落盘的轻量轨迹)、`ondemand` 按需(只在调试开关打开时记)、`selfmon` 自监控(日志管道自己的故障行),三类实际条数须与清单自报的 `counts` 逐项核对。`guard` 可选,一句话写清采样或节流,无特殊守卫不写。
|
|
101
|
+
|
|
102
|
+
空模板见包内的 `event-list.template.json`(模板里的 `pluginId` 换成自己插件的标识)。调用方把清单拼成对象再传入,更新包不读盘:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
import { readFileSync } from 'node:fs'
|
|
106
|
+
|
|
107
|
+
const myEventList = JSON.parse(readFileSync('./event-list.my-notes-plugin.json', 'utf8'))
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
路径形式请调用方自己读成对象再传入,更新包不读盘(字符串直接传给检查器会报错并提示先读成对象)。对象形式的清单当场验形状,错了直接报错(含中文说明),不静默修补;数组形式同样被拦下(只收对象,数组多半是把事件表直接当成了清单)。
|
|
111
|
+
|
|
112
|
+
检查器经包根导出,都是纯函数,不新增日志事件:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
import { parseEventListManifest, checkEventFields, checkEventCounts } from 'dsh-plugin-update'
|
|
116
|
+
|
|
117
|
+
const manifest = parseEventListManifest(myEventList)
|
|
118
|
+
checkEventFields(manifest, 'host.call', ['method', 'pluginId'])
|
|
119
|
+
checkEventCounts(manifest)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`parseEventListManifest` 验形状,`checkEventFields` 做字段白名单检查(未知事件名、未知字段键都算不通过,并把名单带回给调用方),`checkEventCounts` 做计数检查(增删事件必须同步改清单的 `counts`,否则这里变红)。本仓现有 55 事件对照仍以 `research/489-appendix.md` 与 `tests/verify-log-*.js` 为准,本包只给格式与检查器,不复刻那张表,免得两处对照要双写同步。
|
|
123
|
+
|
|
124
|
+
第二家同机隔离自查(电话名、目录、锁互不串):两家各传自己的插件标识与电话名前缀,电话名逐个不同,落盘目录与锁文件逐个不同,各拿各的锁互不阻塞。完整断言见包内 `tests/gate.test.mjs` 的双前缀一节,照抄即用。
|
|
125
|
+
|
|
126
|
+
零运行时依赖与数组无 shell 同样在 `tests/gate.test.mjs` 里断言:`package.json` 无 `dependencies`,包内只引用相对路径与 `node:` 内建;执行器只走参数数组,不经 shell、不用 `PATH` 名、不按系统分支。
|
|
127
|
+
|
|
128
|
+
## 7. 发布前 build 与门禁跑法
|
|
129
|
+
|
|
130
|
+
发布前按顺序两步(演练只跑 dry-run,不真发):
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
node packages/dsh-plugin-update/build.mjs
|
|
134
|
+
cd packages/dsh-plugin-update && npm publish --dry-run
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
编译产物在 `dist` 下(9 个 JS:`client.js`、`commands.js`、`config.js`、`gate.js`、`host.js`、`ports.js`、`reader.js`、`service.js`、`store.js`),本地生成、不入库。发布白名单(`files`)共 4 项(`dist`、`event-list.template.json`、`README.md`、`LICENSE`,另加隐含的 `package.json`;`dist` 下 9 个 JS 全带上)。加新文件进包时同步改 `files` 并重跑 dry-run 确认文件数。
|
|
138
|
+
|
|
139
|
+
门禁跑法(改包后全跑,退出码全 0 才算过):
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
node --test packages/dsh-plugin-update/tests/*.test.mjs
|
|
143
|
+
node tests/verify-update-regression.js
|
|
144
|
+
node tests/verify-update-freshness.js
|
|
145
|
+
node tests/verify-update-install.js
|
|
146
|
+
node tests/verify-update-routes.js
|
|
147
|
+
node tests/verify-update-panel.js
|
|
148
|
+
node tests/verify-log-fields.js
|
|
149
|
+
node tests/verify-log-count.js
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 8. 八种装不了原因(`blockedReason` 对照表)
|
|
153
|
+
|
|
154
|
+
查状态返回的快照里有六个字段,其中 `canInstall` 说能不能装、`blockedReason` 说装不了的原因。
|
|
155
|
+
能装时原因是空,不用看表;装不了时原因一定是下面八种之一,照表处理,不要自己猜。
|
|
156
|
+
|
|
157
|
+
| 原因 | 中文含义 | 用户该做什么 |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `unknown-profile` | 使用范围认不出(名字非法或目录不存在) | 检查使用范围名是否含特殊字符、目录是否还在;这种情形不给手工命令,先把范围修好 |
|
|
160
|
+
| `source-install` | 当前是从源码装的,不是按版本号装的 | 这种情形不给手工命令;想走更新先按版本号重装一次 |
|
|
161
|
+
| `invalid-installation` | 已装的包不完整(名字对不上、版本非法、入口文件缺失) | 重装当前版本,修好已装目录再查更新 |
|
|
162
|
+
| `installation-changed` | 安装位置在使用中途变了(换了目录或换了包) | 重新打开宿主再查一次,让指纹重新绑定;还出现就重装 |
|
|
163
|
+
| `pending-restart` | 新版已装到磁盘,正在跑的还是旧版(见第 10 节) | 重启宿主,让新版跑起来;这是正常终态,不是失败 |
|
|
164
|
+
| `registry-conflict` | 本地声明的版本与磁盘实际版本互相矛盾 | 打开使用范围的清单文件,看目标包名那一行写的是不是版本号,改成版本号再试 |
|
|
165
|
+
| `incompatible-node` | 新版要求的 Node 与当前运行的 Node 对不上 | 先升级 Node 到 22 或更高,再查更新 |
|
|
166
|
+
| `recovery-required` | 上次安装被打断,留下一个半截任务 | 重新点一次安装;一直出现就按第 12 节排错 |
|
|
167
|
+
|
|
168
|
+
面板拿到非空原因时,直接展示上表“用户该做什么”那一列的一句话,不要只展示英文原因本身。
|
|
169
|
+
|
|
170
|
+
## 9. 手工兜底命令(自动装不上时给人复制执行)
|
|
171
|
+
|
|
172
|
+
每次查状态与查新版都会顺带回一条手工命令(字段名 `manual`),能给则给、不能给则为空。
|
|
173
|
+
为空是正常的两种情形:源码安装与认不出使用范围(上表前两行),此时不展示命令,只展示原因。
|
|
174
|
+
|
|
175
|
+
命令形状固定(精确版本、官方源、`--save-exact`),例子里的名字是第 3 节那家自己的名字:
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
dsh plugin --profile my-web add --save-exact my-notes-plugin@1.2.3 --registry=https://registry.npmjs.org/
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
其中 `my-web` 是当前使用范围名,`my-notes-plugin@1.2.3` 是要装的目标包加精确版本。
|
|
182
|
+
使用范围名含空格或特殊字符时命令里会自动加引号,复制整行执行即可。
|
|
183
|
+
网络受限时用户可自行去掉 `--registry=` 尾巴走本地源;桌面宿主激活范围对不上时自动装会诚实失败,同样复制这条命令手工执行。
|
|
184
|
+
|
|
185
|
+
宿主侧每次回包都带 `manual`,面板侧每次拿到都刷新展示,不要缓存旧命令。
|
|
186
|
+
|
|
187
|
+
## 10. ⚠️ 待重启提示(显眼,必须做)
|
|
188
|
+
|
|
189
|
+
`pending-restart` 不是失败,是“磁盘已是新版、正在跑的还是旧版”,必须重启宿主才生效。
|
|
190
|
+
面板必须满足三条,少一条即不合格:
|
|
191
|
+
|
|
192
|
+
1. 用显眼样式单独展示(例如顶部横幅加 ⚠️),不要只在日志或悬停提示里藏一行小字。
|
|
193
|
+
2. 文案说清两件事:新版号是多少、重启后才生效。例子:`⚠️ 新版 1.2.3 已装好,正在跑的还是 1.2.2,重启宿主后生效。`
|
|
194
|
+
3. 待重启期间不再提供安装按钮(快照里 `canInstall` 已为假),只提供重启指引与第 9 节的手工命令查询入口。
|
|
195
|
+
|
|
196
|
+
判断条件只看快照:`blockedReason === 'pending-restart'` 即展示,`installedVersion !== runningVersion` 可作辅助校验,不要自己另起一套版本号比较。
|
|
197
|
+
|
|
198
|
+
## 11. 轮询与凭证有效期可调
|
|
199
|
+
|
|
200
|
+
四个时间都可在第 3 节第 2 步的配置里调,不传即走默认值(默认值等于现状,默认行为不变)。
|
|
201
|
+
只卡下限,不设上限,越界直接抛错而不是静默取整。
|
|
202
|
+
|
|
203
|
+
| 配置键 | 默认 | 下限 | 调大场景 |
|
|
204
|
+
|---|---|---|---|
|
|
205
|
+
| `checkTimeoutMs`(联网超时) | `10000`(10 秒) | 须为有限大于 0 的数 | 慢网络查新版总超时 |
|
|
206
|
+
| `confirmationTtlMs`(凭证有效期) | `600000`(10 分钟) | 须为有限大于 0 的数 | 用户查完新版隔很久才点安装,凭证过期要重查 |
|
|
207
|
+
| `installTimeoutMs`(安装时限) | `900000`(15 分钟) | 须为有限大于 0 的数 | 大包安装超过 15 分钟被终止 |
|
|
208
|
+
| `panelPollMs`(面板轮询) | `1000`(1 秒) | 不得小于 250 毫秒 | 轮询太密想调疏,或想调密但不低于 250 毫秒 |
|
|
209
|
+
|
|
210
|
+
例子(还是第 3 节那家自己的名字,只调两项,其余走默认):
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
const update = createHostUpdate(
|
|
214
|
+
{ ctx, logCtx },
|
|
215
|
+
{
|
|
216
|
+
pluginId: 'my-notes-plugin',
|
|
217
|
+
prefix: 'notes',
|
|
218
|
+
targetPackageName: 'my-notes-plugin',
|
|
219
|
+
confirmationTtlMs: 20 * 60_000,
|
|
220
|
+
panelPollMs: 2000,
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
客户端轮询间隔同样走 `assertPollInterval` 校验,小于 250 毫秒直接抛错。
|
|
226
|
+
另有两处固定不变、不要当成可调项写进文档:查新版 2 秒内重复点击复用上次结果、安装执行终止宽限 3 秒。
|
|
227
|
+
|
|
228
|
+
凭证过期(`check-expired`)是正常错误码,不是程序缺陷:用户拿着过期的检查编号点安装一定失败,面板此时重新调一次查新版、拿新凭证再提交即可。
|
|
229
|
+
|
|
230
|
+
## 12. 排错
|
|
231
|
+
|
|
232
|
+
按从常见到少见的顺序查,一次只动一处,动完重查一次状态。
|
|
233
|
+
|
|
234
|
+
1. `pluginId` 报错说必填或含路径分隔符:换成自己插件的标识,非空且不含 `/` `\`。
|
|
235
|
+
2. 电话名串台(两家插件收到对方的电话):检查两家 `prefix` 是否相同,相同即改成不同的前缀;电话名永远从 `update.phoneNames` 读到,不要自己拼字符串。
|
|
236
|
+
3. 面板轮询报错说小于 250 毫秒:把 `panelPollMs` 调到 250 或更大。
|
|
237
|
+
4. 查新版总超时:先调大 `checkTimeoutMs`,再检查官方源地址是否写错、网络是否通官方源。
|
|
238
|
+
5. 点安装报 `check-expired`:凭证过期了,重新查一次新版再点安装;不要重试旧编号。
|
|
239
|
+
6. 点安装报 `update-busy`:同一使用范围同时只装一个,等当前任务结束再点;面板轮询看到任务离开 `installing`/`verifying` 即为空闲。
|
|
240
|
+
7. 手工命令为空:对照第 8 节前两行(源码安装或认不出使用范围),先修范围再要命令。
|
|
241
|
+
8. 装完版本号没变:先看是否 `pending-restart`(见第 10 节),是则重启宿主;不是则按第 8 节的表格查原因。
|
|
242
|
+
9. 桌面宿主自动装失败但命令端能装:检查桌面当前激活的使用范围是不是插件所在的那一个,对不上时自动装一定诚实失败,复制第 9 节命令手工执行。
|
|
243
|
+
10. 还定位不到:打开调试日志,按插件标识过滤 `host.call`、`host.call.fail`、`update.install.exec` 三个事件,看 `pluginId` 与 `route`、`exitCode` 字段;日志里不记命令与路径原文,只看枚举与数字即可定位。
|
|
244
|
+
|
|
245
|
+
## 13. 新人三步自查(默认路径跑通清单)
|
|
246
|
+
|
|
247
|
+
新人照第 3 节例子把名字换成自己的之后,按下面三条逐项打勾,全勾即默认路径跑通:
|
|
248
|
+
|
|
249
|
+
1. 第 2 步调通:宿主启动不报错,`update.phoneNames` 读到自家前缀的三电话名(例如 `notes.updateStatus`),查状态返回六字段快照。
|
|
250
|
+
2. 第 3 步调通:面板按间隔轮询到快照,能展示第 8 节的原因文案;有新版时安装按钮可用,无新版与待重启时按钮状态正确。
|
|
251
|
+
3. 待重启与兜底可见:模拟一次 `pending-restart` 看到第 10 节横幅,模拟一次自动装失败看到第 9 节手工命令可复制执行。
|
|
252
|
+
|
|
253
|
+
本节三条全绿即达到本票验收“新人照文档三步跑通默认路径”。
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// AUTO-GENERATED by node packages/dsh-plugin-update/build.mjs — DO NOT EDIT. Source: packages/dsh-plugin-update/src/client.ts
|
|
2
|
+
import { buildPhoneNames, DEFAULT_PANEL_POLL_MS, MIN_PANEL_POLL_MS } from "./config.js";
|
|
3
|
+
import { buildPhoneName, buildPhoneNames as buildPhoneNames2 } from "./config.js";
|
|
4
|
+
import { manualCommand as manualCommand2 } from "./commands.js";
|
|
5
|
+
const CLIENT_POLL = {
|
|
6
|
+
defaultMs: DEFAULT_PANEL_POLL_MS,
|
|
7
|
+
minMs: MIN_PANEL_POLL_MS
|
|
8
|
+
};
|
|
9
|
+
function buildClientPhoneNames(prefix) {
|
|
10
|
+
return buildPhoneNames(prefix);
|
|
11
|
+
}
|
|
12
|
+
function assertPollInterval(ms) {
|
|
13
|
+
if (typeof ms !== "number" || !Number.isFinite(ms) || ms < MIN_PANEL_POLL_MS) {
|
|
14
|
+
throw new Error("[dsh-plugin-update] \u9762\u677F\u8F6E\u8BE2\u95F4\u9694\u975E\u6CD5\uFF1A\u4E0D\u5F97\u5C0F\u4E8E 250 \u6BEB\u79D2\uFF08\u6536\u5230 " + JSON.stringify(ms) + "\uFF09");
|
|
15
|
+
}
|
|
16
|
+
return ms;
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
CLIENT_POLL,
|
|
20
|
+
assertPollInterval,
|
|
21
|
+
buildClientPhoneNames,
|
|
22
|
+
buildPhoneName,
|
|
23
|
+
buildPhoneNames2 as buildPhoneNames,
|
|
24
|
+
manualCommand2 as manualCommand
|
|
25
|
+
};
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// AUTO-GENERATED by node packages/dsh-plugin-update/build.mjs — DO NOT EDIT. Source: packages/dsh-plugin-update/src/commands.ts
|
|
2
|
+
const PACKAGE_NAME = "dsh-mattpocock-skills-deck";
|
|
3
|
+
const NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
4
|
+
const INSTALL_TIMEOUT_MS = 15 * 6e4;
|
|
5
|
+
function validVersion(v) {
|
|
6
|
+
return typeof v === "string" && /^\d+\.\d+\.\d+$/.test(v);
|
|
7
|
+
}
|
|
8
|
+
function parseTriple(v) {
|
|
9
|
+
const parts = String(v).split(".");
|
|
10
|
+
if (parts.length > 3) return null;
|
|
11
|
+
const nums = [];
|
|
12
|
+
for (const p of parts) {
|
|
13
|
+
if (!/^\d+$/.test(p)) return null;
|
|
14
|
+
const n = Number(p);
|
|
15
|
+
if (!Number.isSafeInteger(n)) return null;
|
|
16
|
+
nums.push(n);
|
|
17
|
+
}
|
|
18
|
+
while (nums.length < 3) nums.push(0);
|
|
19
|
+
return [nums[0], nums[1], nums[2]];
|
|
20
|
+
}
|
|
21
|
+
function compareVersions(a, b) {
|
|
22
|
+
const pa = parseTriple(a);
|
|
23
|
+
const pb = parseTriple(b);
|
|
24
|
+
if (!pa || !pb) throw new Error("invalid-release");
|
|
25
|
+
for (let i = 0; i < 3; i++) {
|
|
26
|
+
if (pa[i] < pb[i]) return -1;
|
|
27
|
+
if (pa[i] > pb[i]) return 1;
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
function usableProfileName(raw) {
|
|
32
|
+
const name = typeof raw === "string" ? raw.trim() : "";
|
|
33
|
+
if (!name || name.length > 255 || name.startsWith("-")) return null;
|
|
34
|
+
if ([".", "..", "node_modules"].includes(name)) return null;
|
|
35
|
+
return name;
|
|
36
|
+
}
|
|
37
|
+
function installRecipe(input) {
|
|
38
|
+
const name = usableProfileName(input?.profileName);
|
|
39
|
+
const version = input?.version;
|
|
40
|
+
if (!name || !validVersion(version)) return null;
|
|
41
|
+
const kind = input?.environmentKind;
|
|
42
|
+
if (kind !== "desktop" && kind !== "cli") return null;
|
|
43
|
+
const targetName = input?.targetPackageName ?? PACKAGE_NAME;
|
|
44
|
+
const registry = input?.registryUrl ?? NPM_REGISTRY;
|
|
45
|
+
const timeoutMs = input?.timeoutMs ?? INSTALL_TIMEOUT_MS;
|
|
46
|
+
if (!targetName || !registry) return null;
|
|
47
|
+
return {
|
|
48
|
+
route: kind === "desktop" ? "desktop-service" : "cli-process",
|
|
49
|
+
profileName: name,
|
|
50
|
+
version,
|
|
51
|
+
// 参数数组原样交给命令行工具:使用范围名不做引号包裹、不按空格拆分。
|
|
52
|
+
pluginArgs: ["add", "--save-exact", `${targetName}@${version}`, `--registry=${registry}`],
|
|
53
|
+
timeoutMs
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function manualCommand(input) {
|
|
57
|
+
if (input.sourceInstall || input.blockedReason === "source-install" || input.blockedReason === "unknown-profile") return null;
|
|
58
|
+
const name = usableProfileName(input.profileName);
|
|
59
|
+
if (!name) return null;
|
|
60
|
+
const targetName = input.targetPackageName ?? PACKAGE_NAME;
|
|
61
|
+
const registry = input.registryUrl ?? NPM_REGISTRY;
|
|
62
|
+
if (!targetName || !registry) return null;
|
|
63
|
+
const arg = /^[A-Za-z0-9_.-]+$/.test(name) ? name : JSON.stringify(name);
|
|
64
|
+
const picks = [input.latestVersion, input.jobTargetVersion, input.installedVersion].filter(validVersion);
|
|
65
|
+
let version = picks.length > 0 ? picks[0] : "latest";
|
|
66
|
+
try {
|
|
67
|
+
const ranked = picks.filter((v) => compareVersions(v, input.runningVersion) >= 0);
|
|
68
|
+
if (ranked.length > 0) {
|
|
69
|
+
version = ranked[0];
|
|
70
|
+
for (const v of ranked) if (compareVersions(v, version) === 1) version = v;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
return `dsh plugin --profile ${arg} add --save-exact ${targetName}@${version} --registry=${registry}`;
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
INSTALL_TIMEOUT_MS,
|
|
78
|
+
installRecipe,
|
|
79
|
+
manualCommand
|
|
80
|
+
};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// AUTO-GENERATED by node packages/dsh-plugin-update/build.mjs — DO NOT EDIT. Source: packages/dsh-plugin-update/src/config.ts
|
|
2
|
+
const DEFAULT_PREFIX = "wf";
|
|
3
|
+
const PHONE_ACTIONS = ["updateStatus", "updateCheck", "updateInstall"];
|
|
4
|
+
const DEFAULT_TARGET_PACKAGE = "dsh-mattpocock-skills-deck";
|
|
5
|
+
const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
6
|
+
const LEGACY_PLUGIN_ID = "dsh-mattpocock-skills-deck";
|
|
7
|
+
const LEGACY_DIR_SEGMENT = "updates";
|
|
8
|
+
const STATE_FILE = "state.json";
|
|
9
|
+
const LOCK_FILE = "install.lock";
|
|
10
|
+
const BACKUP_FILE = "before.json";
|
|
11
|
+
const DEFAULT_CHECK_TIMEOUT_MS = 1e4;
|
|
12
|
+
const DEFAULT_CONFIRMATION_TTL_MS = 10 * 6e4;
|
|
13
|
+
const DEFAULT_INSTALL_TIMEOUT_MS = 15 * 6e4;
|
|
14
|
+
const DEFAULT_PANEL_POLL_MS = 1e3;
|
|
15
|
+
const RECHECK_WINDOW_MS = 2e3;
|
|
16
|
+
const MIN_PANEL_POLL_MS = 250;
|
|
17
|
+
function assertPluginId(value) {
|
|
18
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
19
|
+
throw new Error("[dsh-plugin-update] \u63D2\u4EF6\u6807\u8BC6 pluginId \u5FC5\u586B\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
|
|
20
|
+
}
|
|
21
|
+
if (value.includes("/") || value.includes("\\") || value.includes("\0")) {
|
|
22
|
+
throw new Error("[dsh-plugin-update] \u63D2\u4EF6\u6807\u8BC6 pluginId \u975E\u6CD5\uFF1A\u4E0D\u5F97\u542B\u6709\u8DEF\u5F84\u5206\u9694\u7B26\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function assertPrefix(value, role) {
|
|
27
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
28
|
+
throw new Error("[dsh-plugin-update] " + role + " \u975E\u6CD5\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
|
|
29
|
+
}
|
|
30
|
+
if (value.includes(".") || value.includes("/") || value.includes("\\") || /\s/.test(value)) {
|
|
31
|
+
throw new Error("[dsh-plugin-update] " + role + " \u975E\u6CD5\uFF1A\u4E0D\u5F97\u542B\u6709\u70B9\u3001\u8DEF\u5F84\u5206\u9694\u7B26\u6216\u7A7A\u767D\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function assertPositiveFinite(value, role) {
|
|
36
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
37
|
+
throw new Error("[dsh-plugin-update] " + role + " \u975E\u6CD5\uFF1A\u987B\u4E3A\u6709\u9650\u5927\u4E8E 0 \u7684\u6570\uFF08\u6536\u5230 " + JSON.stringify(value) + "\uFF09");
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
function resolveUpdateConfig(input) {
|
|
42
|
+
if (!input || typeof input !== "object") {
|
|
43
|
+
throw new Error("[dsh-plugin-update] \u5EFA\u66F4\u65B0\u80FD\u529B\u7F3A\u5C11\u914D\u7F6E\uFF1A\u63D2\u4EF6\u6807\u8BC6 pluginId \u5FC5\u586B");
|
|
44
|
+
}
|
|
45
|
+
const pluginId = assertPluginId(input.pluginId);
|
|
46
|
+
const prefix = input.prefix === void 0 ? DEFAULT_PREFIX : assertPrefix(input.prefix, "\u7535\u8BDD\u540D\u524D\u7F00 prefix");
|
|
47
|
+
const targetPackageName = input.targetPackageName === void 0 ? DEFAULT_TARGET_PACKAGE : String(input.targetPackageName);
|
|
48
|
+
if (!targetPackageName) {
|
|
49
|
+
throw new Error("[dsh-plugin-update] \u76EE\u6807\u5305\u540D targetPackageName \u975E\u6CD5\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
50
|
+
}
|
|
51
|
+
const registryUrl = input.registryUrl === void 0 ? DEFAULT_REGISTRY : String(input.registryUrl);
|
|
52
|
+
if (!registryUrl) {
|
|
53
|
+
throw new Error("[dsh-plugin-update] \u5B98\u65B9\u6E90 registryUrl \u975E\u6CD5\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
54
|
+
}
|
|
55
|
+
const homeDir = input.homeDir === void 0 ? null : String(input.homeDir);
|
|
56
|
+
const checkTimeoutMs = input.checkTimeoutMs === void 0 ? DEFAULT_CHECK_TIMEOUT_MS : assertPositiveFinite(input.checkTimeoutMs, "\u8054\u7F51\u8D85\u65F6 checkTimeoutMs");
|
|
57
|
+
const confirmationTtlMs = input.confirmationTtlMs === void 0 ? DEFAULT_CONFIRMATION_TTL_MS : assertPositiveFinite(input.confirmationTtlMs, "\u51ED\u8BC1\u6709\u6548\u671F confirmationTtlMs");
|
|
58
|
+
const installTimeoutMs = input.installTimeoutMs === void 0 ? DEFAULT_INSTALL_TIMEOUT_MS : assertPositiveFinite(input.installTimeoutMs, "\u5B89\u88C5\u65F6\u9650 installTimeoutMs");
|
|
59
|
+
const panelPollMs = input.panelPollMs === void 0 ? DEFAULT_PANEL_POLL_MS : assertPositiveFinite(input.panelPollMs, "\u9762\u677F\u8F6E\u8BE2 panelPollMs");
|
|
60
|
+
if (panelPollMs < MIN_PANEL_POLL_MS) {
|
|
61
|
+
throw new Error("[dsh-plugin-update] \u9762\u677F\u8F6E\u8BE2 panelPollMs \u975E\u6CD5\uFF1A\u4E0D\u5F97\u5C0F\u4E8E 250 \u6BEB\u79D2\uFF08\u6536\u5230 " + JSON.stringify(input.panelPollMs) + "\uFF09");
|
|
62
|
+
}
|
|
63
|
+
return { pluginId, prefix, targetPackageName, registryUrl, homeDir, checkTimeoutMs, confirmationTtlMs, installTimeoutMs, panelPollMs };
|
|
64
|
+
}
|
|
65
|
+
function buildPhoneNames(prefix) {
|
|
66
|
+
const checked = assertPrefix(prefix, "\u7535\u8BDD\u540D\u524D\u7F00 prefix");
|
|
67
|
+
return {
|
|
68
|
+
updateStatus: checked + ".updateStatus",
|
|
69
|
+
updateCheck: checked + ".updateCheck",
|
|
70
|
+
updateInstall: checked + ".updateInstall"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function buildPhoneName(prefix, action) {
|
|
74
|
+
return buildPhoneNames(prefix)[action];
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
BACKUP_FILE,
|
|
78
|
+
DEFAULT_CHECK_TIMEOUT_MS,
|
|
79
|
+
DEFAULT_CONFIRMATION_TTL_MS,
|
|
80
|
+
DEFAULT_INSTALL_TIMEOUT_MS,
|
|
81
|
+
DEFAULT_PANEL_POLL_MS,
|
|
82
|
+
DEFAULT_PREFIX,
|
|
83
|
+
DEFAULT_REGISTRY,
|
|
84
|
+
DEFAULT_TARGET_PACKAGE,
|
|
85
|
+
LEGACY_DIR_SEGMENT,
|
|
86
|
+
LEGACY_PLUGIN_ID,
|
|
87
|
+
LOCK_FILE,
|
|
88
|
+
MIN_PANEL_POLL_MS,
|
|
89
|
+
PHONE_ACTIONS,
|
|
90
|
+
RECHECK_WINDOW_MS,
|
|
91
|
+
STATE_FILE,
|
|
92
|
+
assertPluginId,
|
|
93
|
+
assertPrefix,
|
|
94
|
+
buildPhoneName,
|
|
95
|
+
buildPhoneNames,
|
|
96
|
+
resolveUpdateConfig
|
|
97
|
+
};
|
package/dist/gate.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// AUTO-GENERATED by node packages/dsh-plugin-update/build.mjs — DO NOT EDIT. Source: packages/dsh-plugin-update/src/gate.ts
|
|
2
|
+
import { assertPluginId } from "./config.js";
|
|
3
|
+
const EVENT_LEVELS = ["error", "warn", "info", "debug"];
|
|
4
|
+
const EVENT_KINDS = ["resident", "ondemand", "selfmon"];
|
|
5
|
+
function isNonEmptyString(value) {
|
|
6
|
+
return typeof value === "string" && value.length > 0;
|
|
7
|
+
}
|
|
8
|
+
function assertStringArray(value, what) {
|
|
9
|
+
if (!Array.isArray(value)) throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A" + what + " \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4");
|
|
10
|
+
const seen = [];
|
|
11
|
+
for (const item of value) {
|
|
12
|
+
if (!isNonEmptyString(item)) throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A" + what + " \u91CC\u6709\u7A7A\u5B57\u6BB5\u540D");
|
|
13
|
+
if (seen.indexOf(item) >= 0) throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A" + what + " \u91CC\u5B57\u6BB5\u540D\u91CD\u590D\uFF1A" + item);
|
|
14
|
+
seen.push(item);
|
|
15
|
+
}
|
|
16
|
+
return seen;
|
|
17
|
+
}
|
|
18
|
+
function parseEventListManifest(value) {
|
|
19
|
+
if (typeof value === "string") {
|
|
20
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u8DEF\u5F84\u5F62\u5F0F\u8BF7\u8C03\u7528\u65B9\u81EA\u5DF1\u8BFB\u6210\u5BF9\u8C61\u518D\u4F20\u5165\uFF0C\u66F4\u65B0\u5305\u4E0D\u8BFB\u76D8");
|
|
21
|
+
}
|
|
22
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
23
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u53EA\u6536\u5BF9\u8C61\u5F62\u5F0F\uFF08\u7A7A\u6A21\u677F\u89C1\u5305\u5185\u7684 event-list.template.json\uFF09");
|
|
24
|
+
}
|
|
25
|
+
const input = value;
|
|
26
|
+
if (input["version"] !== 1) {
|
|
27
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1Aversion \u73B0\u5728\u53EA\u8BA4 1\uFF08\u6536\u5230 " + JSON.stringify(input["version"]) + "\uFF09");
|
|
28
|
+
}
|
|
29
|
+
const pluginId = assertPluginId(input["pluginId"]);
|
|
30
|
+
const countsRaw = input["counts"];
|
|
31
|
+
if (!countsRaw || typeof countsRaw !== "object" || Array.isArray(countsRaw)) {
|
|
32
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1Acounts \u5FC5\u987B\u662F\u542B\u4E09\u7C7B\u8BA1\u6570\u7684\u5BF9\u8C61");
|
|
33
|
+
}
|
|
34
|
+
const countsRecord = countsRaw;
|
|
35
|
+
const counts = { resident: 0, ondemand: 0, selfmon: 0 };
|
|
36
|
+
for (const kind of EVENT_KINDS) {
|
|
37
|
+
const n = countsRecord[kind];
|
|
38
|
+
if (typeof n !== "number" || !isFinite(n) || Math.floor(n) !== n || n < 0) {
|
|
39
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1Acounts." + kind + " \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570");
|
|
40
|
+
}
|
|
41
|
+
counts[kind] = n;
|
|
42
|
+
}
|
|
43
|
+
const eventsRaw = input["events"];
|
|
44
|
+
if (!eventsRaw || typeof eventsRaw !== "object" || Array.isArray(eventsRaw)) {
|
|
45
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1Aevents \u5FC5\u987B\u662F\u4E8B\u4EF6\u540D\u5230\u6761\u76EE\u7684\u5BF9\u8C61");
|
|
46
|
+
}
|
|
47
|
+
const events = {};
|
|
48
|
+
for (const name of Object.keys(eventsRaw)) {
|
|
49
|
+
events[name] = parseEventEntry(name, eventsRaw[name]);
|
|
50
|
+
}
|
|
51
|
+
return { version: 1, pluginId, counts, events };
|
|
52
|
+
}
|
|
53
|
+
function parseEventEntry(name, value) {
|
|
54
|
+
if (!isNonEmptyString(name)) throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
55
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
56
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
57
|
+
}
|
|
58
|
+
const input = value;
|
|
59
|
+
if (EVENT_LEVELS.indexOf(input["level"]) < 0) {
|
|
60
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u7684 level \u53EA\u8BB8 error\u3001warn\u3001info\u3001debug");
|
|
61
|
+
}
|
|
62
|
+
if (EVENT_KINDS.indexOf(input["kind"]) < 0) {
|
|
63
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u7684 kind \u53EA\u8BB8 resident\u3001ondemand\u3001selfmon");
|
|
64
|
+
}
|
|
65
|
+
for (const key of Object.keys(input)) {
|
|
66
|
+
if (["level", "kind", "fields", "codes", "rules", "guard"].indexOf(key) < 0) {
|
|
67
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u6709\u4E0D\u8BA4\u8BC6\u7684\u952E " + key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const entry = {
|
|
71
|
+
level: input["level"],
|
|
72
|
+
kind: input["kind"],
|
|
73
|
+
fields: assertStringArray(input["fields"], "\u4E8B\u4EF6 " + name + " \u7684 fields")
|
|
74
|
+
};
|
|
75
|
+
if (input["codes"] !== void 0) entry.codes = assertStringArray(input["codes"], "\u4E8B\u4EF6 " + name + " \u7684 codes");
|
|
76
|
+
if (input["rules"] !== void 0) entry.rules = assertStringArray(input["rules"], "\u4E8B\u4EF6 " + name + " \u7684 rules");
|
|
77
|
+
if (input["guard"] !== void 0) {
|
|
78
|
+
if (typeof input["guard"] !== "string") {
|
|
79
|
+
throw new Error("[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355 eventList \u975E\u6CD5\uFF1A\u4E8B\u4EF6 " + name + " \u7684 guard \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
80
|
+
}
|
|
81
|
+
entry.guard = input["guard"];
|
|
82
|
+
}
|
|
83
|
+
return entry;
|
|
84
|
+
}
|
|
85
|
+
function checkEventFields(manifest, eventName, fieldNames) {
|
|
86
|
+
const entry = manifest.events[eventName];
|
|
87
|
+
if (!entry) return { ok: false, unknownEvent: true, unknownFields: [] };
|
|
88
|
+
const allowed = new Set(entry.fields);
|
|
89
|
+
const unknownFields = (Array.isArray(fieldNames) ? fieldNames : []).filter(
|
|
90
|
+
(field) => !allowed.has(field)
|
|
91
|
+
);
|
|
92
|
+
return { ok: unknownFields.length === 0, unknownEvent: false, unknownFields };
|
|
93
|
+
}
|
|
94
|
+
function checkEventCounts(manifest) {
|
|
95
|
+
const actual = { resident: 0, ondemand: 0, selfmon: 0 };
|
|
96
|
+
for (const name of Object.keys(manifest.events)) {
|
|
97
|
+
actual[manifest.events[name].kind] += 1;
|
|
98
|
+
}
|
|
99
|
+
const problems = [];
|
|
100
|
+
for (const kind of EVENT_KINDS) {
|
|
101
|
+
if (actual[kind] !== manifest.counts[kind]) {
|
|
102
|
+
problems.push(
|
|
103
|
+
"[dsh-plugin-update] \u4E8B\u4EF6\u6E05\u5355\u8BA1\u6570\u5BF9\u4E0D\u4E0A\uFF1A" + kind + " \u7C7B\u5B9E\u9645 " + actual[kind] + " \u6761\uFF0C\u6E05\u5355\u81EA\u62A5 " + manifest.counts[kind] + " \u6761"
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { ok: problems.length === 0, problems };
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
EVENT_KINDS,
|
|
111
|
+
EVENT_LEVELS,
|
|
112
|
+
checkEventCounts,
|
|
113
|
+
checkEventFields,
|
|
114
|
+
parseEventListManifest
|
|
115
|
+
};
|