dsh-plugin-pack 0.0.0-stage → 1.0.3

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-plugin-pack contributors
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 CHANGED
@@ -1,3 +1,537 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # dsh-plugin-pack
2
+
3
+ [![npm](https://img.shields.io/npm/v/dsh-plugin-pack)](https://www.npmjs.com/package/dsh-plugin-pack)
4
+ [![license](https://img.shields.io/npm/l/dsh-plugin-pack)](LICENSE)
5
+
6
+ DeepSeek Harness 的**插件整合包**工具:把当前 profile 里装的所有第三方插件,连运行时依赖和 `cordis.patch.yml` 配置,一键打成一个 zip;换机器、换 profile 时拖进去就能还原。
7
+
8
+ **有图形界面**——不用记命令,点几下就完成。桌面客户端和 Web 端都能用。
9
+
10
+ ```bash
11
+ npm i dsh-plugin-pack
12
+ ```
13
+
14
+ 或者直接克隆:
15
+
16
+ ```bash
17
+ git clone https://github.com/HTRP1486/dsh-plugin-pack.git
18
+ ```
19
+
20
+ 零依赖,只用 Node 内置模块。162 个测试。
21
+
22
+ ---
23
+
24
+ ## 界面在哪(不用敲命令)
25
+
26
+ 装好重启 dsh 后,入口就在「插件」页里:
27
+
28
+ ```
29
+ 插件页
30
+ ├ 已安装 │ 发现 ← 宿主原有
31
+ ├ 导入 ← 本插件加的
32
+ └ 导出 ← 本插件加的
33
+ ```
34
+
35
+ | 入口 | 位置 | 说明 |
36
+ | --- | --- | --- |
37
+ | **导入标签页** | 插件页顶部「导入」 | 两个来源:**整合包**(zip)/ **本地插件**(本机目录) |
38
+ | **导出标签页** | 插件页顶部「导出」 | 两个范围:**单个插件**(分享用)/ **整合包**(全量备份) || **插件行详情** | 「已安装」里点 `dsh-plugin-pack` 那一行 | 同一套面板,含备份回滚与说明 |
39
+ | 设置页 | 设置 → **插件整合包** | 同上,整页版 |
40
+
41
+ > 「已安装 / 发现」旁边那两个标签页是本插件用宿主 `plugins.tab` 插槽注册的。
42
+
43
+ > 如果「插件」列表里看得到 `dsh-plugin-pack` 但点开是空白的,那是客户端 bundle 没加载成功。
44
+ > 排查顺序:① `package.json` 里要有 `exports["./client"]`;② `client.js` 要在包根目录;
45
+ > ③ 改完点面板里的「热加载界面」即可,不必重启。
46
+
47
+ 每个页面做什么:
48
+
49
+ ```
50
+ 导入
51
+ 导入(只有一个拖拽框,按扩展名自动分流)
52
+ ├ .zip / .tgz / .tar.gz 拖进来即导入
53
+ └ 插件文件夹 拖进来按本地插件导入
54
+
55
+ 导出(勾选数量自动决定类型)
56
+ ├ 勾 1 个 → .tgz(npm tarball,别人能 npm i ./dsh-pet-0.3.8.tgz)
57
+ └ 勾多个 → .zip(整合包:插件 + 依赖 + 配置)
58
+
59
+ 另含:备份与回滚(每次导入的快照,一键还原)、怎么用(四步说明)
60
+ ```
61
+
62
+ ### 导出格式:单个插件 vs 整合包
63
+
64
+ | | 单个插件 `.tgz` | 整合包 `.zip` |
65
+ | --- | --- | --- |
66
+ | 结构 | `package/` 下是一个标准 npm 包 | `packages/<插件>/` + `node_modules/` + `manifest.json` |
67
+ | 能给别人 | ✅ `npm i ./x.tgz` 直接可用 | 只能由本插件还原 |
68
+ | 含依赖 | ✅ 扁平化进 `package/node_modules/` | ✅ |
69
+ | 含配置 | 只含插件自带的 | ✅ 含 `cordis.patch.yml` 里属于它的行 |
70
+
71
+ 两种格式都经过交叉验证:系统 `tar` 可解开,`npm install` 可安装。
72
+
73
+ ### 选择器怎么用(导出/导入都用它)
74
+
75
+ | 动作 | 效果 |
76
+ | --- | --- |
77
+ | 顶部盘符按钮 | 直接换到 C:\ D:\ |
78
+ | 输入框粘路径 + 回车 | 跳到那个目录;粘一个 zip 会跳到它的目录并选中它 |
79
+ | 面包屑 | 点任意一级跳回去 |
80
+ | **单击**文件夹 | 选目录模式下:**只选中**,不进入 |
81
+ | **双击**文件夹 | 进入该文件夹 |
82
+ | 行尾「选它 / 进入」按钮 | 选目录时是「选它」,否则是「进入」 |
83
+ | 底部主按钮 | 选目录时固定是**用当前这个目录**——导出最常用的动作 |
84
+ | ↑↓ / 回车 / Esc | 键盘全程可用 |
85
+
86
+ ### 给不懂技术的人的三句话
87
+
88
+ 1. 点「插件」→ 顶部「导出」标签,全选,点**开始导出**,得到一个 zip。
89
+ 2. 把 zip 拷到新电脑,点「插件」→「导入」,把它**拖进那个框**里。
90
+ 3. **重启 dsh**。插件、依赖、配置全部照原样回来。
91
+
92
+ 界面上的事,跟 `/pack` 命令完全等价——不会因为用了界面而少做任何一步。
93
+
94
+ ---
95
+
96
+ ## 热加载(改完不用重启)
97
+
98
+ 普通插件改一行客户端代码就得重启 dsh。这个插件把它变成了一个按钮。
99
+
100
+ **三种触发方式**(等价):
101
+
102
+ ```
103
+ 界面:面板右上角「热加载界面」按钮
104
+ 命令:/pack hotload
105
+ 工具:plugin_pack_hotload({ package: 'dsh-plugin-pack' })
106
+ ```
107
+
108
+ ### 为什么需要它
109
+
110
+ 读宿主 `dsh-client-modules` 源码后确认的机制:
111
+
112
+ 1. 宿主启动时扫描 Loader 行 → 读 `package.json` → 判定 `dsh.client.platform === 'web'`
113
+ 且 `exports["./client"]` 存在 → 才把包收进客户端图表,之后
114
+ `/plugins/??<id>/client.js&rev=<hash>` 才有内容(否则 404)。
115
+ 2. 判定结果——**包括"不是客户端包"这种否定结论**——缓存在
116
+ `pkgMeta: Map<sourceKey, meta|null>` 里,`resolveMeta()` 命中缓存就直接返回,
117
+ **从不重读 `package.json`,也没有失效机制**。
118
+ 3. 唯一能把改动送进图表的是 `rebuilt(id)`,但它只对已在 `table` 里的包有效。
119
+ 4. `dirty: Set<entryName>` + `flush(onError)` 是重扫入口,走到
120
+ `processOne → resolveSource → resolveMeta` 会**再判定一次**。
121
+
122
+ 所以热加载 = **删缓存 + 标记 dirty + flush + rebuilt**,全在宿主进程内完成。
123
+
124
+ ### 附加行为
125
+
126
+ - **启动自愈**:插件启动时会自己热加载一次(`selfHotload: true`)。如果你是在装好之后
127
+ 才补 `exports["./client"]` 的,宿主缓存里的旧判定会在启动 1.2 秒后被自动纠正。
128
+ - **幂等**:已在图表里就跳过,不重复解析。
129
+ - **如实报错**:bundle 文件缺失、`platform` 不是 `web`、缺 `exports["./client"]`
130
+ 这三种情况分别给出明确原因,不假装成功。
131
+ - **只影响客户端**:服务端工具与 `/plugin-pack/*` 路由本来就是热生效的。
132
+
133
+ ### 边界
134
+
135
+ 热加载**只管客户端 bundle**。以下改动仍然需要重启 dsh:
136
+
137
+ - `cordis.patch.yml` 里新增/删除 Loader 行(新增一个插件)
138
+ - 插件服务端入口 `lib/index.js` 的代码
139
+ - `package.json` 的 `dsh.bundle.patch` 路径
140
+
141
+ ---
142
+
143
+ ## 后台任务(切走画面不丢进度)
144
+
145
+ 导出/导入要跑几十秒。**任务本身跑在宿主进程里,切走画面不会中断** ——
146
+ 浏览器的 `fetch` 断掉只是"不等结果了",宿主照样把文件写完。
147
+
148
+ 真正的问题是**结果会丢**:面板重新挂载,之前那次导出的路径、体积全没了。
149
+ 所以加了任务记录:
150
+
151
+ ```
152
+ ┌ 后台任务 ─────────────────────────────── [清空已完成] ┐
153
+ │ ● 导出 导出 5 个插件 已完成 15:43:21 │
154
+ │ D:\backup\dsh-plugin-pack-desktop-20261028.zip │
155
+ │ · 165.8 MiB · 5 插件 · 24.4s │
156
+ │ │
157
+ │ ◐ 导出 导出全部插件 进行中 15:58:10 │
158
+ │ ▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░ 52% │
159
+ │ 正在打包 dsh-ops → dsh-ops-0.4.1.tgz │
160
+ └────────────────────────────────────────────────────────┘
161
+ ```
162
+
163
+ - **切走再回来**:任务列表还在,进行中的继续显示进度,已完成显示结果
164
+ - **进行中**:每 1.5 秒自动刷新,跑完就停(不空转)
165
+ - **进度**:后端在关键节点上报(读包 → 解包 → 写配置 → 建链接)
166
+ - **保留 30 分钟 / 最多 40 条**,超了自动清理
167
+ - **结果摘要**不含 manifest 等大对象,接口响应很小
168
+
169
+ 任务记录存在内存里,进程重启即清 —— 任务本身也随进程结束,这是合理的。
170
+
171
+ ### 接口
172
+
173
+ ```
174
+ GET /plugin-pack/jobs 列出任务(jobs + running)
175
+ GET /plugin-pack/jobs?id=37a1159f 查单个任务
176
+ POST /plugin-pack/jobs/clear 清空已完成记录
177
+ ```
178
+
179
+ 每次导出/导入的响应里也会带 `jobId`。
180
+
181
+ ---
182
+
183
+ ## 它解决什么问题
184
+
185
+ dsh 的插件散在三处:
186
+
187
+ | 位置 | 内容 |
188
+ | --- | --- |
189
+ | `profiles/<name>/package.json` | `dependencies` 版本声明 + `dsh.profile.bundles` 加载顺序 |
190
+ | `profiles/<name>/node_modules/` | 插件本体 + 各自的运行时依赖(pnpm 符号链接) |
191
+ | `profiles/<name>/cordis.patch.yml` | 每个插件的 config 覆盖(模型、强度、开关、路径) |
192
+
193
+ 手工迁移要同时搬这三处,漏一处就是「插件装上了但配置没了」或者「配置在但包没装」。这个插件把它们当一个整体处理。
194
+
195
+ ---
196
+
197
+ ## 安装
198
+
199
+ ### 方式一:npm(推荐)
200
+
201
+ ```powershell
202
+ cd $env:DSH_HOME\profiles\desktop
203
+ npm i dsh-plugin-pack
204
+ ```
205
+
206
+ 然后在 `profiles/desktop/package.json` 的 `dsh.profile.bundles` 里加一行:
207
+
208
+ ```json
209
+ "dsh.profile": {
210
+ "bundles": [ "...其它 bundle...", "dsh-plugin-pack" ]
211
+ }
212
+ ```
213
+
214
+ 重启 dsh。
215
+
216
+ ### 方式二:从 GitHub 克隆
217
+
218
+ ```powershell
219
+ git clone https://github.com/HTRP1486/dsh-plugin-pack.git D:\Documents\dsh-plugin-pack
220
+ cd D:\Documents\dsh-plugin-pack
221
+ node scripts\install-to-profile.mjs
222
+ ```
223
+
224
+ 安装脚本做三件事(幂等,可重复跑):
225
+
226
+ 1. 复制到 `profiles/desktop/local-plugins/dsh-plugin-pack`
227
+ 2. 写 `dependencies`(`link:./local-plugins/dsh-plugin-pack`)和 `bundles`
228
+ 3. 建 `node_modules` junction
229
+
230
+ ### 方式三:让 agent 代装
231
+
232
+ 把仓库路径发给 dsh 里的 agent:
233
+
234
+ > 把 D:\Documents\dsh-plugin-pack 装进当前 profile
235
+
236
+ ### 装完检查
237
+
238
+ 重启 dsh 后打开「插件」页,顶部应该多了 **导入** 和 **导出** 两个标签。
239
+
240
+ 看不到?确认声明完整:
241
+
242
+ ```powershell
243
+ node -e "const p=require('./package.json'); console.log(p.dsh.client)"
244
+ # 应输出 { platform: 'web', immediately: true, inject: [ 'slots', 'locale' ] }
245
+ ```
246
+
247
+ 也可以让 agent 代劳:把本目录路径给它,让它调 `plugin_pack_import` 之外的普通文件工具写这两处配置。
248
+
249
+ ---
250
+
251
+ ## 第一次用:三步备份,三步还原
252
+
253
+ ### 备份(在旧机器上)
254
+
255
+ 1. 打开「插件」页 → **导出** 标签
256
+ 2. 全选插件(或只勾要带走的),选保存位置
257
+ 3. 点 **开始导出** → 得到一个 `.zip`
258
+
259
+ > 插件多、体积大时可能要等 20~30 秒。期间可以切走,任务在后台跑,回来还能看到进度。
260
+
261
+ ### 还原(在新机器上)
262
+
263
+ 1. 装好 dsh 和本插件
264
+ 2. 打开「插件」页 → **导入** 标签
265
+ 3. 把那个 `.zip` **拖进框里**
266
+
267
+ 拖完自动导入。遇到重名插件会弹窗问「覆盖」还是「跳过」。
268
+
269
+ ### 收尾
270
+
271
+ **重启 dsh。** 插件、依赖、配置、开关全部恢复。
272
+
273
+ 不放心?每次导入前都会自动备份,点「备份与回滚」能一键回到导入前的状态。
274
+
275
+ ---
276
+
277
+ ## 用法
278
+
279
+ ### 图形界面
280
+
281
+ 三个入口,内容一样:
282
+
283
+ | 入口 | 位置 |
284
+ | --- | --- |
285
+ | **导入 / 导出 标签** | 「插件」页顶部,`已安装` 和 `发现` 旁边 |
286
+ | **插件行详情** | 「已安装」列表里点 `dsh-plugin-pack` 那一行 |
287
+ | **设置页** | 设置 → 插件整合包(整页版) |
288
+
289
+ ### 直接说话(agent 帮你调工具)
290
+
291
+ > 把现在的插件导出成一个整合包
292
+ > 看看这个包里有什么
293
+ > 导入 D:\backup\dsh-plugin-pack-desktop-20261028-112000.zip
294
+ > 回滚上一次导入
295
+
296
+ ### 工具清单
297
+
298
+ | 工具 | 作用 |
299
+ | --- | --- |
300
+ | `plugin_pack_list` | 列出可导出的插件、依赖数、patch 行数、体积、数据目录、历史备份 |
301
+ | `plugin_pack_export` | 导出(勾 1 个得 tgz,勾多个得 zip) |
302
+ | `plugin_pack_inspect` | 读包,列出内容与警告 |
303
+ | `plugin_pack_import` | 导入(`dryRun: true` 预演) |
304
+ | `plugin_pack_rollback` | 回滚最近一次导入 |
305
+ | `plugin_pack_hotload` | 热加载客户端界面,改完代码不用重启 |
306
+ | `plugin_pack_client_status` | 查客户端界面在宿主图表里的状态,排查「装了但看不到」 |
307
+
308
+ ### 斜杠命令
309
+
310
+ ```
311
+ /pack list
312
+ /pack export [路径] [--include=a,b] [--exclude=a,b] [--data] [--secrets] [--dry]
313
+ /pack inspect <包路径>
314
+ /pack import <包路径> [--dry] [--skip] [--no-data] [--no-link] [--overwrite]
315
+ /pack rollback [备份名]
316
+ /pack backups
317
+ /pack hotload [包名]
318
+ /pack client [包名]
319
+ ```
320
+
321
+ ### 命令行(不需要 dsh 在跑)
322
+
323
+ ```powershell
324
+ dsh-plugin-pack list
325
+ dsh-plugin-pack export --out D:\backup
326
+ dsh-plugin-pack inspect D:\backup\dsh-plugin-pack-desktop-20261028-112000.zip
327
+ dsh-plugin-pack import D:\backup\dsh-plugin-pack-desktop-20261028-112000.zip --dry
328
+ dsh-plugin-pack rollback
329
+ dsh-plugin-pack backups
330
+ ```
331
+
332
+ 没装到 PATH 时用 `node bin\dsh-plugin-pack.js <命令>`。
333
+
334
+ ---
335
+
336
+ ## 整合包里有什么
337
+
338
+ ```
339
+ manifest.json # 插件元数据、patch 行、来源 profile
340
+ README.txt # 人读的清单
341
+ packages/<插件名>/... # 插件本体(跳过 .git / node_modules)
342
+ packages/<插件名>/node_modules/<依赖>/ # 扁平化的运行时依赖
343
+ data/<插件名>/<数据目录>/... # 可选,--data 才有
344
+ ```
345
+
346
+ - **依赖闭包**:从插件 `package.json` 的 `dependencies` 递归解析,跳过 `peerDependencies`、`@deepseek-ai/*`、`@types/*`。pnpm 的符号链接会被 dereference 成真实文件。
347
+ - **patch 行**:从 `cordis.patch.yml` 里按包名/绝对路径匹配出属于该插件的行,分两类保存:
348
+ - 顶层 row(`- id: dsh-pet` + `config:`)——导入后原样追加到顶层,保持「按 id 覆盖」语义;
349
+ - insert 子行(`- insert:` 下面的 `- id:`)——导入时重新包成 `- insert:` 块。
350
+ - **路径插件**:`cordis.patch.yml` 里写死 `name: 'D:/Documents/dsh-pwsh7-preference/index.js'` 的插件(比如 `pwsh7-preference`)也会被识别、打包,导入时把绝对路径改回包名。
351
+
352
+ 默认**不打包**:`.env`、`.env.*`、`*.pem`、`*.key`、`*.p12`、`*.pfx`、`credentials.json`、`auth.json`、`.npmrc`、`.netrc` 等凭证文件;以及 `.git`、`node_modules`(依赖走闭包单独收)、`*.log`、`.cache`。
353
+
354
+ 要带上凭证:`--secrets`(想清楚再开,包是要传出去的)。
355
+
356
+ ---
357
+
358
+ ## 导入做了什么
359
+
360
+ 1. **预检**:读 `manifest.json`,校验格式和每个插件的 `package.json`。
361
+ 2. **备份**(在 `profiles/<name>/.plugin-backups/plugin-pack-<时间戳>/`):
362
+ - `package.json`
363
+ - `cordis.patch.yml`
364
+ - 被覆盖的插件目录
365
+ - `record.json`(回滚依据)
366
+ 3. **落地**:插件解到 `profiles/<name>/local-plugins/<插件名>/`(scoped 包名压成 `@scope__name` 单层目录)。
367
+ 4. **写回配置**:
368
+ - `dependencies[<插件名>] = "link:./local-plugins/<插件名>"`
369
+ - `dsh.profile.bundles` 追加缺失的插件名
370
+ - `cordis.patch.yml` 追加缺失的 patch 行(同 id 默认不覆盖,`--overwrite` 才覆盖)
371
+ 5. **建链接**:`node_modules/<插件名>` → `local-plugins/<插件名>`(Windows 用 junction,不需要管理员权限)。原来指向别处的链接或实体目录会先备份再替换。
372
+ 6. **数据目录**:包里带 `data/` 时还原到 `$DSH_HOME/<目录>`,已存在的文件不覆盖。
373
+ 7. **提示重启**。
374
+
375
+ 任何一步失败都不会静默:失败项写进报告,`record.json` 仍然落盘,可以回滚。
376
+
377
+ ---
378
+
379
+ ## 回滚
380
+
381
+ ```powershell
382
+ /pack rollback # 最近一次
383
+ /pack rollback plugin-pack-20261026-145723
384
+ ```
385
+
386
+ 恢复 `package.json`、`cordis.patch.yml`、被覆盖的插件目录,删掉本次新增的插件和链接。**重启 dsh 生效。**
387
+
388
+ ---
389
+
390
+ ## 注意事项
391
+
392
+ - **体积**:带原生二进制的插件(如 `sherpa-onnx-node`)会让包变得很大。默认上限 512 MiB,用 `--include` 只挑需要的插件,或调 `maxBytes`。
393
+ - **内存**:打包时所有文件会进内存,峰值约为包体积的 2 倍。几百 MB 以内没问题。
394
+ - **重启**:插件树是启动时组装的,导入/回滚后必须重启 dsh。
395
+ - **pnpm**:如果之后跑了 `pnpm install`,`link:` 依赖会被重建,不影响;但如果插件目录被删了,链接会断。
396
+ - **跨平台**:包本身是平台无关的 zip,但依赖里的原生二进制(`.node` / `.exe`)是平台相关的。Windows 导出的包导到 Linux 上,带原生模块的插件会加载失败——这类插件建议在目标平台重装。
397
+ - **密钥**:默认剔除凭证文件,但插件把 token 存在 `config` 或数据目录里的情况无法自动识别。分享包之前 `plugin_pack_inspect` 看一眼 `manifest.json` 的 patch 行。
398
+
399
+ ---
400
+
401
+ ## 开发
402
+
403
+ ```powershell
404
+ node --test D:\Documents\dsh-plugin-pack\test\
405
+ ```
406
+
407
+ 模块职责:
408
+
409
+ | 文件 | 职责 |
410
+ | --- | --- |
411
+ | `client.js` | **客户端界面**(设置页 + 浮动面板,React 组件,无 JSX 编译) |
412
+ | `lib/server.js` | **HTTP 桥**:`/plugin-pack/*` 九个路由,供界面调用 |
413
+ | `lib/zip.js` | 零依赖 zip 读(deflate/store,只依赖 `node:zlib`) |
414
+ | `lib/zip-stream.js` | 流式 zip 写:逐个文件落盘,555 MiB 级大包不爆内存 |
415
+ | `lib/patch-text.js` | `cordis.patch.yml` 的块级文本处理(保留注释与缩进) |
416
+ | `lib/scan.js` | 扫描插件、依赖闭包、patch 行、数据目录 |
417
+ | `lib/export.js` | 组装 manifest + 文件树 → zip |
418
+ | `lib/import.js` | 解包、备份、落地、合并配置、回滚 |
419
+ | `lib/index.js` | 工具注册 + `/pack` 命令 + 路由挂载 |
420
+ | `bin/dsh-plugin-pack.js` | 脱离宿主的 CLI |
421
+
422
+ ### 界面为什么这么设计
423
+
424
+ - **客户端拿不到文件系统**:所有真实操作都在宿主侧,界面只调 `/plugin-pack/*` 接口。
425
+ - **不用 JSX 编译**:`client.js` 是一个 `window.__ModuleLoader__.load({ factory })` 包装,
426
+ 内部 `require('react')` 后全用 `react.createElement`,宿主加载时不需要任何构建步骤。
427
+ - **不一次读整个包**:导出用 `writeZipStream` 逐条写盘;导入用 `readZipMeta` 只读中央目录,
428
+ 再逐条 `extractEntry`。555 MiB 的包峰值内存只有单个文件那么大。
429
+ - **全链路异步**:压缩用 `deflateRaw` / `createGzip` 而不是 `*Sync`。同步版本会把整个
430
+ 事件循环卡死 —— 实测 64 MiB 同步 gzip 耗时 413ms,期间定时器一次都不触发,
431
+ dsh 的对话等请求全部无响应。导出几百 MiB 就是好几秒的完全冻结。
432
+ - **不擅自重启**:界面只提示「重启后生效」,不替用户杀进程。
433
+
434
+ ---
435
+
436
+ ## 欢迎开发者
437
+
438
+ 这个项目是从零手写出来的,**没有任何第三方运行时依赖**,全部只用 Node 内置模块
439
+ (`node:fs` / `node:path` / `node:zlib` / `node:crypto` / `node:module`)。
440
+ 如果你对下面任何一块感兴趣,都欢迎参与。
441
+
442
+ ### 最值得动手的几块
443
+
444
+ | 方向 | 现状 | 可以怎么改 |
445
+ | --- | --- | --- |
446
+ | **压缩** | `zip-stream.js` 用 `deflateRaw` 逐条压缩,`tar.js` 用 `createGzip` 流式 | 换 zstd / 并行压缩 worker;大包分片 |
447
+ | **依赖解析** | `scan.js` 按 `dependencies` 递归,跳过 peer / `@deepseek-ai/*` | pnpm workspace 协议、`peerDependencies` 可选补齐、版本冲突合并 |
448
+ | **tar 实现** | `tar.js` 手写 ustar,支持 prefix 拆分,不支持 ZIP64 / PAX | 加 PAX 长文件名、大文件(>8 GiB)支持 |
449
+ | **界面** | `client.js` 单文件 ~97 kB,全 `createElement` | 拆组件、加虚拟滚动(插件多时列表会卡) |
450
+ | **后台任务** | `jobs.js` 内存记录,保留 30 分钟 | 落盘持久化、任务取消、并发限制 |
451
+ | **导入策略** | 逐插件覆盖/跳过 | 差异预览(哪个文件会变)、版本回退、依赖冲突检查 |
452
+ | **测试** | 162 个,覆盖导出/导入/冲突/回滚/界面渲染 | 加模糊测试(畸形 zip/tgz)、跨平台 CI(Linux/macOS) |
453
+
454
+ ### 开发流程
455
+
456
+ ```bash
457
+ git clone https://github.com/HTRP1486/dsh-plugin-pack.git
458
+ cd dsh-plugin-pack
459
+ npm test # 162 个测试,约 40 秒
460
+ ```
461
+
462
+ ### 改完怎么生效
463
+
464
+ **只改 `client.js`**(界面)——不用重启 dsh:
465
+
466
+ ```powershell
467
+ node scripts\install-to-profile.mjs
468
+ # 然后在面板里点「热加载界面」,或:
469
+ dsh-plugin-pack → 界面右上角按钮
470
+ ```
471
+
472
+ 热加载原理在 [`hotload.js`](hotload.js) 顶部注释里写得很细:宿主把「这个包不是客户端包」
473
+ 的判定缓存在 `pkgMeta` 里且永不失效,所以要删缓存 → 标 dirty → flush → rebuilt。
474
+
475
+ **改 `lib/*.js`**(服务端)——需要重启 dsh。导出/导入这类操作走的是新起的 worker
476
+ 子进程,会重新读磁盘上的模块,但工具注册和路由在启动时就固定了。
477
+
478
+ ### 代码地图
479
+
480
+ ```
481
+ client.js 界面(~97 kB,单文件,无 JSX)
482
+ hotload.js 客户端热加载(删缓存 + flush + rebuilt)
483
+ lib/
484
+ ├── index.js 插件入口:7 个工具 + /pack 命令
485
+ ├── server.js HTTP 桥 11 个路由(/plugin-pack/*)
486
+ ├── scan.js 扫描插件、依赖闭包、patch 行;60 秒缓存
487
+ ├── export.js 导出:单插件 → tgz,多插件 → zip(内含 tgz)
488
+ ├── import.js 导入 zip:解包、备份、落地、合并配置、回滚
489
+ ├── import-tgz.js 导入 tgz:解包成 npm 包 → 走本地插件导入
490
+ ├── import-plugin.js 导入本地插件目录
491
+ ├── tar.js 零依赖 tar 读写(ustar + prefix 拆分)
492
+ ├── zip.js 零依赖 zip 读(central directory + CRC 校验)
493
+ ├── zip-stream.js 零依赖 zip 写(异步 deflate,store 已压缩条目)
494
+ ├── patch-text.js cordis.patch.yml 的块级文本处理(保留注释缩进)
495
+ ├── jobs.js 后台任务记录(进度、结果、失败原因)
496
+ ├── inbox.js 拖放上传:multipart 解析 + 落盘
497
+ ├── util.js 文件工具 + walkTree(同步 / 异步两版)
498
+ └── home.js DSH_HOME / profile 路径解析
499
+ ```
500
+
501
+ ### 提交前请确认
502
+
503
+ ```bash
504
+ npm test # 全绿
505
+ grep -r "C:/Users" . --include=*.js --include=*.mjs # 不该有机器路径
506
+ ```
507
+
508
+ 代码里**不要出现**:个人路径、token、内网地址、真实 profile 内容。
509
+ 测试需要 fixture 时放 `test/fixtures/`,不要读用户机器上的真实文件。
510
+
511
+ ### 欢迎的 PR 类型
512
+
513
+ - **修 bug**:尤其是导入后插件加载失败的场景(历史上出过 scoped 包名被压扁的坑)
514
+ - **加平台支持**:现在主要在 Windows 上测过,Linux / macOS 的路径处理欢迎验证
515
+ - **性能**:大包导出(GB 级)、万级文件扫描
516
+ - **界面**:交互细节、无障碍、i18n(目前只有中英)
517
+ - **文档**:某个你踩过的坑,写进 README 或注释
518
+
519
+ 不欢迎的:引入运行时依赖(这个项目的卖点就是零依赖)、改包格式但不考虑向后兼容
520
+ (导入端同时支持 `@1` 和 `@2`,新格式请照这个做)。
521
+
522
+ ### 遇到问题
523
+
524
+ - 提 [Issue](https://github.com/HTRP1486/dsh-plugin-pack/issues),附上 `dsh-plugin-pack list` 的输出和错误信息
525
+ - 排查「装了但界面不显示」:先跑 `plugin_pack_client_status`,它会告你包在不在宿主图表里
526
+ - 包导入后插件报 `ERR_MODULE_NOT_FOUND`:检查 `node_modules` 里的 scoped 包是不是
527
+ `@scope/name` 两级结构(历史 bug,见 `test/scoped-deps.test.mjs`)
528
+
529
+ ---
530
+
531
+ ## 许可
532
+
533
+ MIT,随便用、随便改、随便发。改了不用署名,但欢迎回来提个 PR。
534
+
535
+ 如果这个工具帮你省了事,给个 Star 就是最好的感谢。
536
+
537
+ MIT