dsh plugin 报 reading 'prepare':npm、pnpm 与 yarn 三种安装器差异排查
上周同事在一台新机器上装 dsh plugin,npm 退出码是 1,日志里只有一行 reading 'prepare' of undefined。代码仓库一行没改、版本也没换,问题出在安装器这一侧的构建阶段。
一次 dsh plugin 安装在 prepare 阶段停住
错误发生在安装阶段,插件代码还没轮到执行。
那条日志的措辞很误导人,看起来像是在读取某个对象的 prepare 属性,实际是安装脚本没有跑完就被中断。npm 在解析依赖、下载包之后,会按生命周期执行 prepare,把源码编译成可加载的产物。产物没生成,后续读取自然拿到 undefined。
同事的第一反应是回滚版本,但旧版本同样报错,说明问题不在某一次提交。我们又把项目删掉重装,错误依旧,范围缩到了本机的安装器配置。这一步很重要,它把排查从代码层挪到了环境层。
当时机器上用的是 pnpm,项目从别的仓库拷贝过来,带着一份不完整的构建审批配置。依赖的构建脚本没有被批准,安装过程直接跳过 prepare,日志里只留下那一行结果性的报错。前因后果到这里才对上。
我把过程记下来,是因为同类问题会换一种面貌出现。有人看到的是 prepare 失败,有人看到的是模块找不到,背后都可能是构建脚本没执行。先分清阶段,比盯着错误文本猜要有效得多。
DeepSeek Harness 插件为什么需要 prepare
prepare 是安装期的构建动作,缺了它,包可能只有源码没有产物。
npm 的生命周期脚本里,prepare 在包被安装或发布前执行,常用来编译 TypeScript、生成入口文件或拷贝静态资源。对从源码分发的 DeepSeek插件 来说,这一步不是可选项,而是让包变得可加载的必要环节。如果安装器跳过了这一步,包目录里往往只剩源码和配置文件。
DeepSeek Harness 的插件安装会经过包管理器,dsh 本身不替代这套流程。安装器决定脚本跑不跑、在哪个目录跑、以什么权限跑,dsh 只负责把结果写进 profile。所以同一份插件包,在不同机器上完全可能一份能用、一份报错。
日志里 prepare 这个词出现的位置也很关键。出现在脚本执行阶段,说明构建被调用但失败了;只出现在最后的错误摘要里,往往意味着脚本根本没被调用。前者查构建依赖,后者查安装器策略。
理解这一点之后,就不会再急着改插件源码。绝大多数情况下,源码是好的,缺的是那次没有发生的构建。把安装器当成排查对象,思路会清楚很多。
pnpm 的构建审批怎样拦下 DSH插件 的 prepare
pnpm 默认不信任依赖的构建脚本,未获批准的包会被跳过。
pnpm 从某个版本开始,把依赖的 postinstall、prepare 一类脚本纳入审批,只有显式允许的包才会执行。这个设计是为了防止供应链攻击,但对需要构建的插件并不友好,表现就是装完了却没有产物。审批结果通常记录在项目配置里,不会自动跟随全局设置。
那次故障里,项目的审批名单只写了几个常见原生模块,插件包不在其中。安装日志没有大张旗鼓地报错,只在构建阶段标记为 ignored。同事看到的是最终的 prepare 报错,中间这条 ignored 被淹没了。
顺着这个线索,我让他先看审批配置,再重装一次。下面这段是当时的日志形态,关键在 ignored build 那一行,而不是最后一行错误。对照日志和配置,才能确认包是被跳过,而不是构建时报错。
Progress: resolved 312, reused 298, downloaded 14
Ignored build scripts: @acme/dsh-plugin-demo
Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.
Done in 8.4s
Error: Cannot read properties of undefined (reading 'prepare')
看到 Ignored build scripts 就应该停下来,而不是继续往下翻错误。执行 pnpm approve-builds,勾选对应插件,再清掉 store 里的旧记录重装,构建才会真正发生。审批状态是跟着项目配置走的,换一个目录可能又要重新确认。
dsh plugin 在 npm 与 yarn 下的拦截差异
同一个症状,npm 和 yarn 的成因往往不在同一个地方。
npm 默认执行生命周期脚本,很少出现静默跳过。它更常见的问题是 ignore-scripts 被全局或项目级打开,或者缓存里存着上一次构建失败的残缺包。排查时先看配置,再看日志里 prepare 有没有被调用。
yarn 的情况更分裂。经典版默认执行脚本,Berry 系则对构建有更严格的限制,还要区分 nodeLinker 与依赖提升方式。同一个包在经典版能装上,换成 Berry 可能就缺产物,日志格式也完全不同。
这也是我不建议跨安装器套结论的原因。在 pnpm 上有效的审批修复,放到 npm 上无从下手;在 npm 上清缓存能解决的,放到 Berry 可能要改配置。先确认命令和锁文件属于哪个安装器,再谈具体动作。
为了少走弯路,我后来把三种安装器的常见拦截点记成了一张小表。它不是穷举,只是提醒自己先往哪个方向看。每次遇到新版本,表里的细节都可能变。
| 安装器 | 默认脚本策略 | 常见拦截 | 先看什么 |
|---|---|---|---|
| npm | 执行生命周期脚本 | ignore-scripts | 项目与全局配置 |
| pnpm | 依赖构建需审批 | ignored build scripts | approve-builds 与 store |
| yarn | 随版本而变 | 生命周期限制 | 版本、nodeLinker 与缓存 |
dsh plugin 的排查顺序:从日志到安装器配置
先定位阶段和安装器,再动配置,最后才怀疑插件。
排查顺序比具体命令更重要。错误文本会把注意力引向读取动作,但真正要回答的是:构建有没有被调用、被谁拦住、拦住之后留下了什么。把这三个问题依次回答完,修复方案通常就浮出来了。我习惯从安装日志的中段看起,而不是从最后一行看起。prepare 前后往往夹着安装器的决策信息,比如跳过、复用缓存或审批未通过。这些信息比最终的堆栈更接近原因。
确认原因之后再改配置,一次只改一处。改完清掉相关缓存重装,观察日志里对应那一行有没有变化。下面是我现在用的检查顺序,基本覆盖了常见情况。
- 用锁文件和命令确认当前安装器及版本。
- 在日志里找到 prepare 前后,判断脚本是否被调用。
- npm 查 ignore-scripts,pnpm 查构建审批,yarn 查版本策略。
- 清理对应缓存或 store,用固定版本重装。
- 检查产物文件与
dsh plugin list,确认插件可加载。
如果三种安装器都失败,可以改用本地 tarball 或固定版本安装,绕开脚本差异。装完再用 dsh plugin list 核对包与版本,确认不是插件本身缺文件。插件条目可以在 DSH Plugin Hub 里对照。
回头看,那次故障最有价值的部分不是某条命令,而是对安装阶段的分层理解。错误文本只说明结果,安装器的策略才说明原因。把这层关系理清,换一台机器、换一个包,排查路径依然成立。
常见问题
dsh plugin 报 reading 'prepare' 多数是安装阶段的问题,不是插件运行代码本身。安装器没有正确执行或注入 prepare 脚本时,就会在读取阶段报这个错。
npm 环境下 dsh plugin 报 prepare 错误,先查是否禁用了脚本与依赖是否完整。再看安装日志里的 prepare 阶段,确认脚本有没有真正执行。
pnpm 的 allowBuilds 会拦截依赖的构建脚本,prepare 也可能被跳过,导致 dsh plugin 安装不完整。需要按 pnpm 版本确认构建审批配置。
yarn 安装 dsh plugin 报 prepare,常见差异在脚本执行策略与依赖提升方式。yarn 的构建限制与缓存行为不同,排查时要看它自己的日志。
DeepSeek Harness 的三种安装器都报 prepare 时,改用本地 tarball 或固定版本安装,绕开安装器的脚本差异。装完再用命令行核对包与版本,确认不是插件本身缺文件。
相关术语
- prepare script(准备脚本)
- prepare script 是包在安装或发布前后执行的构建脚本,常用于生成产物。安装器跳过或拦截它时,包可能缺少可加载文件。— https://docs.npmjs.com/cli/v10/using-npm/scripts
- allowBuilds(构建审批)
- allowBuilds 是 pnpm 对依赖构建脚本的审批配置,未批准的包不会执行构建。它会让需要 prepare 的插件安装不完整。— https://pnpm.io/cli/build
- ignore-scripts(忽略脚本)
- ignore-scripts 是 npm 的配置项,开启后安装时不执行包的脚本。它常被用于安全加固,但也会让依赖构建缺失。— https://docs.npmjs.com/cli/v10/using-npm/scripts
- package manager difference(安装器差异)
- package manager difference 是 npm、pnpm 与 yarn 在脚本执行、依赖提升与缓存上的行为差异。同一个包在不同安装器下可能表现不同。— https://pnpm.io/cli/build
来源
- npm Docs — scripts· npm
- pnpm Docs — build· pnpm
- DeepSeek Harness 文档 — 开发第一个插件· deepseek-harness.github.io