装插件时到底发生了什么
别名:插件装在哪、装了没生效、allowBuilds、profile node_modules
跟着 `dsh plugin --profile <name> add <包>` 走一遍全过程:它其实是 pnpm 的一个转发器,装完还要把依赖对账进 dsh.profile.bundles;搞清落点、构建授权与「装了没生效」的三类原因,就不用再猜。
一条命令,四个动作
dsh plugin --profile web add some-plugin
这条命令不是 DSH 自己实现的安装逻辑。按本机实测的实现(@deepseek-ai/dsh 的 lib/plugin-*.js),它是一个 pnpm 转发器,一次调用做四件事:
- 没建过的 profile 先初始化:写出
package.json(含dsh.profile.bundles初始列表)、空的cordis.patch.yml、以及一份pnpm-workspace.yaml。 - 在你的调用目录里把参数原样转给 pnpm,但 pnpm 的 cwd 是该 profile 目录。
- pnpm 装完之后对账:把
dsh.profile.bundles与「装出来的实际状态」对齐(见下文)。装失败(非 0 退出)就不对账。 - 把退出码原样返回:pnpm 失败,
dsh plugin就失败。
所以第一条要记住的事:装插件是按 profile 装的。同一个包装进 web 与装进 tui 是两份互不相干的状态,换 profile 就「插件不见了」的原因见 concept/2。
落点:两个 node_modules
| 路径 | 谁写给它的 | 里面是什么 |
|---|---|---|
$DSH_HOME/profiles/<name>/node_modules | pnpm(cwd 是该 profile 目录) | 你自己装的第三方插件 |
$DSH_HOME/profiles/node_modules | DSH 自己维护 | 出厂插件的一条扁平符号链接兜底目录,每个包一个链接 |
第二个目录容易让人误会成「公共插件区」。它不是:DSH 每次启动会按安装包的依赖闭包重建这一层链接(healProfilesModuleFallback),为的是让出厂 bundle 在任何 profile 里都能被 Node 解析到——这就是「bundle 来自安装目录」那条契约的实现方式。别往里装东西,它不是给你用的。
profile 目录里的 pnpm-workspace.yaml 还有两条出厂设置:nodeLinker: hoisted 与 autoInstallPeers: false。前者决定了依赖是平铺落盘,后者决定 pnpm 不会自动替你装 peer 依赖。
对账:这一步决定「装上」与「生效」
pnpm 装完,包里多了个依赖,但 DSH 还不会用它——它必须出现在 profile package.json 的 dsh.profile.bundles 列表里。对账逻辑实测是这样的:
- 遍历
dependencies的每个包名,解析它、读它的package.json:有dsh.bundle声明的,追加进bundles列表(按依赖顺序);没有的,打一行告警declares no dsh.bundle — installed as a plain dependency, not a profile layer。 - 反过来也做:已经在
bundles里、但依赖项没了或新版本不再声明dsh.bundle的,从列表里移除。 - 对账依据是装出来的状态,不是依赖的 diff。所以「更新一个包」可以让一个以前不是层的包自动变成层——前提是新版本补上了
dsh.bundle。 - 出厂 bundle(
@deepseek-ai/dsh-base、@deepseek-ai/dsh-web-app)不是依赖项,永远不动。
用这一条就能读懂市场账本:一个整合包的 bundles 与 dependencies 是两份清单,bundles 决定装配顺序,dependencies 决定装什么版本。比如 plugin/2 在两个包里都是这样成对出现的:
"bundles": ["@deepseek-ai/dsh-base", "…", "@dsh-packforge/dsh-pack-plugin"],
"dependencies": { "@dsh-packforge/dsh-pack-plugin": "0.3.5" }
dsh-loader 则是「钉了一个开发版」的实例:"@dsh-plugin/dsh-loader": "1.3.3-dev.33218986978"。版本字符串就是从这里进的包,钉开发版意味着上游一变你就跟着变。
构建授权:GitHub 来源的第一次装通常会失败
插件里有 github:(或 git+)来源时,安装往往第一次就失败。原因是 pnpm 在安装 git 依赖时会跑它的 prepare 脚本,而 pnpm 默认拦截构建脚本。dsh plugin 在失败时会直接把下一步提示出来:
git-hosted plugins build on install via their prepare script, which pnpm blocks until allowed — add the exact key pnpm printed above under
allowBuildsin<profile 目录>/pnpm-workspace.yaml, then re-run
照着做:把 pnpm 打印的那个键名(用报错原文里的那一个,不要自己拼)加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds,再跑一次同样的命令。这条是安全边界,不是 bug——任意来源的包都能在安装时执行代码,pnpm 选择默认拦下。
启动时:从 bundles 到那棵树
下次启动,loadProfile 按顺序对每个 bundles 条目做三件事:从两个锚点里解析出包的绝对目录(安装目录优先,profile 目录次之)、读它 package.json 的 dsh.bundle.patch、把补丁文件读成一段补丁列表。任何一条解析不出、或列在这里的包没有 dsh.bundle,就是抛错而不是跳过。
完整层序(bundle 层 → profile 层 → home 层 → --patch → 遥测开关)与补丁怎么作用在行上,见 concept/1;客户端半端那一条链见 concept/4。
「装了没生效」的五个检查点
| 症状 | 查什么 |
|---|---|
| 安装成功,功能没有 | 打开 profile 的 package.json,看它在不在 dsh.profile.bundles 里 |
| 安装时出现过告警 | 那行 declares no dsh.bundle 就是答案:这个包不是层,装上也不会被装配 |
| 在 A profile 生效、B 不生效 | 装插件是按 profile 的;给 B 也装一次 |
| 启动只多了一行告警 | 补丁没命中目标,被跳过(命不中不报错,见 concept/1) |
| 界面没出现 | 宿主那行没挂上,客户端入口图里就不会有它(见 concept/4) |
怎么核对真实装配结果
别只信任何工具的预览,用 DSH 自己的两棵树的对比:
dsh --profile web --dump-default-config # 只有 bundle 层
dsh --profile web --dump-config # 叠上 profile 层、home 层与 --patch
两者的差异就是「你或插件改的」。这也是确认「对账到底把哪一行加进了 bundles」的最快办法。
卸载与回滚
- 卸载:
dsh plugin --profile <name> remove <包>走的还是同一条链路——pnpm 移除,然后对账把它从bundles里摘掉。 - 回滚一个版本:用 pnpm 的版本语法装回旧版(例如显式指定版本号)即可,对账会按装出来的状态重新决定它算不算层。
- 装坏了起不来:profile 的
cordis.patch.yml与$DSH_HOME/cordis.patch.yml是纯文本,删掉可疑的那一段就能回到上一状态;两者的层序差别见 concept/1,失效模式的系统梳理见 tutorial/1。
未核实 / 未声明
- pnpm 版本与
allowBuilds的键名格式:本轮核实了代码里的提示文案与配置文件位置(<profile>/pnpm-workspace.yaml),没有实测安装一个 GitHub 来源的插件去看 pnpm 实际打印什么;键名以报错原文为准。 nodeLinker: hoisted下的实际磁盘布局:只核实到这项写在 profile 模板的pnpm-workspace.yaml里,具体软链层级未核实。- 各命名的 profile(
web/desktop/tui)哪些是出厂自带:见 concept/2 的「未核实」一节,本轮不重复断言。 dsh plugin的完整参数面:它把剩余参数原样转发给 pnpm,pnpm 自己支持多少子命令就是多少;本篇只覆盖add/remove两个最常用形态。
相关教程
谁引用了这一条
信息表
- appliesTo
- dsh 0.1.0-rc.6(实测,本机 @deepseek-ai/dsh 的 lib/plugin-*.js 与 @deepseek-ai/dsh-app-boot 的实际实现);命令形态未随版本核实
- category
- ops.install
- difficulty
- beginner
- origin
- original
- plugins
- [object Object]、[object Object]
- prereq
- concept/2、concept/1
- related
- tutorial/1、tutorial/2
- updatedAt
- 2026-10-02
标签:安装、排错、依赖、profile