CH 23 · 发布与分发
本章目标
PART 04 一路走下来,你已经会装插件、会写插件了——但它现在还躺在你自己工作区里的文件夹里,别人怎么拿到它? 这一章把它变成"别人一条命令就能装"的发布物:先讲清别人装插件的底层机制(bundle 组合包),再给三种分发方式,最后发布前先自己装一遍验证,确认没问题再发出去。
先搞清楚:别人怎么装你的插件
dsh 没有内置插件市场,官方安装统一走 dsh plugin --profile <名字> add <来源>。来源就四种:npm 包名 / 本地目录 / tarball / GitHub Release(CH 17 系统讲过)。所以你要做的,就是把你写的插件变成这四种来源里的任何一种。
而"能被人 add 的插件",在 dsh 里有个正式名字——bundle(组合包)。两个概念别混:
- bundle 是你编写并分发的东西。它的 package.json 声明
dsh.bundle,回答的是"这个包贡献什么"——一个插入或覆盖插件行的 patch 文件。 - profile 是用户用
dsh --profile <名字>启动的东西。它的 package.json 声明dsh.profile,回答的是"这套配置由哪些 bundle 按什么顺序组成"。
没有东西同时是两者。你分发 bundle,用户用 profile 装它、跑它。
一个可发布的插件长什么样
你写好的插件目录里,最关键的三个东西:
your-plugin/
├── package.json # 声明 dsh.bundle(带 UI 的还要声明 dsh.client)
├── cordis.patch.yml # 被安装时应用的那一层 patch
└── lib/ # 构建产物——别人装到的就是它package.json 里这些字段,一个都不能含糊:
| 字段 | 说明 |
|---|---|
name | 包名,和插件匹配、读起来合适 |
version | 语义化版本,主版本.次版本.修订号,初始 0.0.1 |
description | 一句话说清插件是干嘛的,会显示在安装列表里 |
type | module(ESM) |
main | 指向构建产物,如 lib/index.js |
files | 只打包要发的东西:lib、cordis.patch.yml、LICENSE 等。漏了 lib 别人装完加载必失败 |
keywords | 搜索关键词,["deepseek","harness","dsh","dsh-plugin",...]——dsh-plugin 是标配,方便被搜到 |
license | 开源必须声明,如 MIT |
dsh.bundle | {"patch":"./cordis.patch.yml"}——声明这是一个组合包,装进去自动挂载 |
dsh.client | 带 UI 的插件声明 client 注入(CH 22 讲过那套) |
三种分发方式
由轻到重。第一种纯本地自测,不需要任何账号;后面两种都要注册账号——GitHub 那步要 GitHub 账号,npm 那步要 npm 账号。
方式一:本地目录 / tarball——发布前先自测
- 本地目录:
dsh plugin --profile demo add ./your-plugin,pnpm 直接 link 那个目录,适合开发阶段快速试。 - tarball:在插件目录里
pnpm pack打出.tgz,再dsh plugin --profile demo add ./your-plugin-0.0.1.tgz。
发布之前,一定先用 tarball 自己装一遍——tarball 和你发出去的东西一模一样,能装通、能跑,才轮得到考虑往外发。完整自测三步:
# 在插件目录打包
pnpm pack
# 换一个干净的临时 profile 装上
dsh plugin --profile test add ./your-plugin-0.0.1.tgz
# 看它有没有进配置树
dsh --profile test --dump-config--dump-config 输出里能看到你插件那一层,说明组合包加载成功了。然后 dsh --profile test 启动,测插件的功能是否正常,都正常再发。
两个细节记牢:
- bundle 成员变更后要重启 profile 才生效——运行中的 profile 保留它启动那一刻的 bundle 集合(CH 17 那个边界),装完先重启再测。
- 加载顺序:profile 的 bundles 列表 → profile 自己的 cordis.patch.yml →
$DSH_HOME/cordis.patch.yml→--patchoverlay,后应用的行胜出。你的插件在里面属于第一层。
你也可以直接让 dsh 帮你做:把下面这条发给它,它自己读文档、打包、装临时 profile、验证,你只管验收。
把我当前工作区里的插件打包成可发布的形式,并做一次发布前自测:先去 dsh 官方文档读清楚插件怎么打包、怎么安装(bundle 组合包、dsh plugin add 的四种来源),按官方规范来;把插件打包成 tarball,检查 package.json 元数据(版本号、description、files、keywords、license)是否齐全;用一个临时 profile 装上它并验证能加载、功能正常;最后把验证结果和打包产物路径告诉我,并说明发布到 npm 还需要我做什么。方式二:GitHub——别人一个 URL 就能装
社区里最多的就是这种:构建好产物,传到 GitHub Release,别人拿一个 URL 直接装。我们 CH 12 装的 DSH Skill & MCP Panel 就是这么发的。
做法三步:
- 打包:在插件目录
pnpm pack,得到your-plugin-0.0.1.tgz。 - 上传:把仓库推到 GitHub,到仓库页建一个 Release(写个版本号
v0.0.1),把.tgz传上去。 - 给安装地址:别人用这个地址装:
dsh plugin --profile web add https://github.com/你的用户名/仓库名/releases/download/v0.0.1/your-plugin-0.0.1.tgz装的是构建产物,不需要 allowBuilds 授权,和 npm 装起来一样省事。想固定一个"永远指向最新版"的地址,把 download/v0.0.1/... 换成 releases/latest/download/your-plugin.tgz(文件名别带版本号)。
注意:走这条路的插件,lib/ 一般不进 git——仓库里只放源码,发布前本地 build 好、pnpm pack 出 tarball 再传 Release。
进阶:GitHub Actions 自动打包发布
手动打包、建 Release 传文件做一次还行,版本多了就烦。可以让 GitHub Actions 替你做:往仓库推一个 v0.0.1 这样的 tag,Actions 自动跑 build → pnpm pack → 创建 Release → 把 tarball 传上去。以后发新版本只需要打个 tag 推上去,别的全自动。
典型 workflow 放在仓库的 .github/workflows/release.yml:触发条件是 push: tags: ['v*'],步骤依次为 checkout 代码、安装 Node、pnpm install、pnpm build、pnpm pack、用 softprops/action-gh-release 创建 Release 并把生成的 .tgz 作为附件上传。配好一次,以后每个版本都不用再手动传文件。
你也可以直接让 dsh 帮你发
打包、推仓库、建 Release 这些重复活,直接交给 dsh 自己干。在 Web UI 输入框发这条:
把我当前工作区里的插件发布到 GitHub,发 v0.0.1 版本,按 dsh 官方规范用 GitHub Actions 自动打包发布,最后告诉我别人用什么地址可以安装。它会自己读文档、自己跑命令、自己处理 git 和 Release。你只需要在它推仓库、建 Release 这类越界操作时按权限策略确认一下,最后验收安装地址对不对。
方式三:发布到 npm——装完别人直接写包名
把插件发到 npm,别人装起来最简洁:dsh plugin --profile web add your-plugin。
流程四步:
- 注册并登录 npm:在 npm 官网 注册账号(免费),然后
npm login(会让你输用户名、密码和邮箱)。 - 构建:确保
lib/是构建产物。源码型插件要先pnpm build再发——npm 装的是预构建代码,不构建就发,别人拿到手是空的。 - 发布:在插件目录执行
pnpm publish。 - 验证:换一个干净 profile,
dsh plugin --profile test add your-plugin,确认能装上、能跑。
发布完:打 dsh-plugin 话题
无论是哪种方式,在 GitHub 仓库的 Topics 里加上 dsh-plugin——官方明确建议这么做,让其他人更容易找到你的插件。npm 包的 keywords 里也记得带上 dsh-plugin,双管齐下。
常见坑
| 问题 | 怎么回事 | 怎么处理 |
|---|---|---|
| 装完没生效 | bundle 成员变更没重启 | 装完重启 profile 再测 |
| 别人装完加载失败 | files 漏了 lib/,只发了源码 | files 里加上构建产物目录,重新打包 |
| 版本号重复 | 同版本号 publish 会被 npm 永久拒绝 | 改个版本号再发 |
| 包名被占用 | npm 上已有同名包 | 换个不冲突的包名 |
| 装了但没激活 | 包没声明 dsh.bundle | 补上 "dsh":{"bundle":{...}},否则只作普通依赖不挂载 |
这一章你学到了什么
能自己完成下面几条,就算过关:
- [ ] 知道 bundle 是"你分发的东西"、profile 是"用户启动的东西",两者 manifest 不同
- [ ] 能给一个可发布插件写出正确的 package.json:name / version / files / keywords / license / dsh.bundle,带 UI 的还有 dsh.client
- [ ] 知道三种分发方式各自的适用场景:本地/tarball 自测、GitHub Release tarball(主流一键装,支持 Actions 自动发布)、npm 发布
- [ ] 会发布前自测:
pnpm pack→ 临时 profileadd→--dump-config确认 → 重启验证 - [ ] 知道可以让 dsh 帮你完成打包自测和发布到 GitHub,不用手动一步步操作
- [ ] 知道发布后给 GitHub 仓库打
dsh-plugin话题、npm 包 keywords 带dsh-plugin - [ ] 能避开常见坑:
files漏了lib/、bundle 变更没重启、包没声明dsh.bundle
