6.6 KiB
打包与安装插件
English | 中文
前几篇教程通过 --patch overlay 加载本地插件。本教程把它打包成可安装的组合包,用 dsh plugin add 安装进一个 profile,并解释决定组合后配置的层顺序。请先完成插件配置。
两个概念,两种 manifest(元数据清单)
安装机制建立在两个概念之上。二者都由一份 package.json 描述,但它们在 dsh 键下携带的 manifest 种类不同,回答的问题也不同:
- 组合包是附带一个配置层的 npm 包。它的 manifest 声明
dsh.bundle,回答的是「这个包贡献什么?」:一个插入或覆盖插件行的 patch 文件。 - profile 是位于
$DSH_HOME/profiles/<name>下、描述一份可启动组合的目录。它的 manifest 声明dsh.profile,回答的是「这套配置由哪些组合包按什么顺序组成?」。
组合包是你编写并分发的东西;profile 是用户用 dsh --profile <name> 启动的东西。没有东西同时是两者。
组合包 manifest
hello-plugin/
├── package.json # declares dsh.bundle
├── cordis.patch.yml # the layer applied when a profile lists this bundle
└── index.js # plugin modules the patch rows reference
{
"name": "dsh-hello-plugin",
"version": "0.1.0",
"type": "module",
"main": "index.js",
"files": ["index.js", "cordis.patch.yml"],
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
patch 文件与一直在写的 --patch overlay 一样,是一个 patch 条目的 YAML 数组;区别是插件行按包名而不是相对源码路径引用这个包,这样 Node 的模块解析才能找到已安装的代码:
- insert:
- id: hello
name: dsh-hello-plugin
没有 dsh.bundle 声明的包仍然可以安装,但只作为普通依赖:dsh plugin 会打印警告,且不激活任何层。如果一个库供插件包 import,而不是供用户启用,就使用这种包格式。
profile manifest
profile 目录包含两个文件:
package.json— profile 的树外插件依赖(由 pnpm 管理),加上dsh.profilemanifest 及其有序的bundles列表。cordis.patch.yml— 用户自己的 patch 层,在每个组合包层之后应用。
profile manifest 从不需要手写:dsh plugin 负责创建和维护它。下一节展示其结果。
安装进 profile
dsh plugin --profile <name> <args...> 在 profile 目录内转发给 pnpm,因此所有 pnpm 子命令都可用。从 checkout 安装你的包:
cd hello-plugin
dsh plugin --profile demo add .
首次使用会初始化 profile(@deepseek-ai/dsh-base 作为它的第一个组合包),pnpm 链接该 checkout,而 dsh 因为这个包声明了 dsh.bundle,把它追加进 dsh.profile.bundles:
{
"name": "dsh-profile-demo",
"private": true,
"dependencies": {
"dsh-hello-plugin": "link:/path/to/hello-plugin"
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"dsh-hello-plugin"
]
}
}
}
先不启动、只验证该层,再启动:
dsh --profile demo --dump-config # shows a "# == dsh-hello-plugin" layer
dsh --profile demo
dsh plugin --profile demo remove dsh-hello-plugin 会同时移除依赖和对应的层。
加载顺序
生效配置在空根之上按以下顺序逐层组合:
- profile 的
dsh.profile.bundles列表所列的各个组合包 patch,按列表顺序——先是@deepseek-ai/dsh-base,然后是每个已安装组合包,按其加入顺序。 - profile 自己的
cordis.patch.yml。 - home 级的
$DSH_HOME/cordis.patch.yml——各 profile 共享的机器本地偏好。 - 每个
--patch <path>overlay,按 argv 顺序。 - 启动器 flag patch(例如
dsh web --port)。
后应用的层按行胜出,且 patch 会替换目标行的整个 config 值,而不是深度合并各键。这给组合包作者带来两个推论:
- 你的 patch 可以按
id覆盖前面各层的行——就像dsh-web-app组合包覆盖dsh-base的行那样——但必须重述该行需要的每一个键,而不是只写改动的那个。 - 用户可以在自己 profile 的
cordis.patch.yml中覆盖你的行,无需改动你的包,所以优先给出用户大概率会保留的配置默认值,其余交给 schema 承担。
内置组合包名称始终从 dsh 安装目录本身解析;pnpm 只管理树外的包,所以你的组合包可以放心依赖 @deepseek-ai/dsh-base 存在且与安装保持一致。
从 GitHub 安装:构建脚本这道坎
发布到注册表不是必须的——用户可以直接从 git 托管安装:
dsh plugin --profile demo add github:you/hello-plugin
但 git 安装拉取的是源码,不是构建产物:没有任何环节运行你的 build 脚本,因此 TypeScript 包到手时没有 lib/ 输出,加载会失败。必须两边各做一件事:
-
作者提供一个
prepare脚本——pnpm 在 git 安装后运行它——从源码构建出发布入口,且必须自包含:不能假设仅开发环境才有的上下文,例如旁边有一份 monorepo checkout。turtle-ui 是一个可用的例子:它的prepare运行一份专用的 tsdown 配置,直接转译src/,不用项目引用,也不做类型检查。 -
用户为构建授权。pnpm ≥10 在得到显式允许之前拒绝运行 git 依赖的
prepare脚本,所以第一次add会失败;dsh会指出修法——把 pnpm 打印的确切包键复制进该 profile 的pnpm-workspace.yaml:allowBuilds: dsh-hello-plugin: true然后重新执行
add。
请如实看待这项授权:允许该包的代码在安装时于你的机器上执行,且不在 agent(智能体)运行的任何沙箱之内。只对源码可信的包授权,并锁定 commit(github:you/hello-plugin#<sha>),让后续推送无法悄悄改变实际运行的内容。
如果不想让用户做这项授权,就改为分发构建产物——以下两种形式都不需要任何构建权限:
- 发布到 npm,在
pnpm publish时构建好lib/;dsh plugin add your-package安装的就是预构建代码。 - 交付 tarball:用
pnpm pack打包;用户执行dsh plugin add ./hello-plugin-0.1.0.tgz。