装插件时到底发生了什么

别名:插件装在哪、装了没生效、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 转发器,一次调用做四件事:

  1. 没建过的 profile 先初始化:写出 package.json(含 dsh.profile.bundles 初始列表)、空的 cordis.patch.yml、以及一份 pnpm-workspace.yaml。
  2. 在你的调用目录里把参数原样转给 pnpm,但 pnpm 的 cwd 是该 profile 目录。
  3. pnpm 装完之后对账:把 dsh.profile.bundles 与「装出来的实际状态」对齐(见下文)。装失败(非 0 退出)就不对账。
  4. 把退出码原样返回:pnpm 失败,dsh plugin 就失败。

所以第一条要记住的事:装插件是按 profile 装的。同一个包装进 web 与装进 tui 是两份互不相干的状态,换 profile 就「插件不见了」的原因见 concept/2。

落点:两个 node_modules

路径谁写给它的里面是什么
$DSH_HOME/profiles/<name>/node_modulespnpm(cwd 是该 profile 目录)你自己装的第三方插件
$DSH_HOME/profiles/node_modulesDSH 自己维护出厂插件的一条扁平符号链接兜底目录,每个包一个链接

第二个目录容易让人误会成「公共插件区」。它不是: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 allowBuilds in <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

被 2 篇教程引用 · 被 6 条词条引用

最后更新 2026-10-02