文章目录
- 事故现场
- 第一个疑问:项目里明明没有 workspace 依赖
- 背景知识:`workspace:` 协议是什么
- 复现与定位:二分法揪出"真凶"
- 实锤:vant 把 monorepo 的协议发到了 npm
- 科普:`^` 版本号到底是什么意思
- 版本号三段式
- `^`(caret,插入符)
- 常见写法对照表
- 正是 `^` 让我们"中招"
- 解决方案
- 第一步:锁定安全版本(立即止血)
- 第二步:关注上游修复(后续跟进)
- 第三步:治本——统一包管理器(长期建议)
- 经验与反思
事故现场
2026-8-31早上,Jenkins构建突然失败:
npmERR!code EUNSUPPORTEDPROTOCOLnpmERR!Unsupported URL Type"workspace:":workspace:^npmERR!A complete log of this run can be found in: /home/jenkins/.npm/_logs/2026-08-31T02_47_51_131Z-debug-0.logEUNSUPPORTEDPROTOCOL、workspace:^——npm 在安装依赖时遇到了一个它不认识的协议。
第一个疑问:项目里明明没有 workspace 依赖
我在项目里翻了个遍,没有任何workspace:痕迹:
package.json无workspace:协议- 没有
pnpm-workspace.yaml - git 历史里从未出现过
workspace:
关键线索是用户的一句话:“Jenkins 一直用 npm,上周是好的,今天突然报错。”
"上周好、今天坏"说明不是代码变了,而是外部依赖变了。
背景知识:workspace:协议是什么
在 pnpm / Yarn 的 monorepo(多包仓库)里,workspace:^用来引用同仓库内的兄弟包:
{"dependencies":{"@shared/utils":"workspace:^"}}这个协议只有 pnpm / Yarn 认识,npm 完全不支持,解析时一撞上就抛EUNSUPPORTEDPROTOCOL。
所以问题变成:npm 到底在解析哪个包时撞上了workspace:^?
复现与定位:二分法揪出"真凶"
本地用临时目录 +npm install --dry-run(只解析不落盘)完整复现,确认依赖树确实无法用 npm 安装。然后用二分法把 20 多个依赖对半拆、逐个击破,很快锁定:
=== 问题依赖: vant (^4.10.0) === 单独安装该依赖: 失败单独装一个vant都会失败——答案呼之欲出。
实锤:vant 把 monorepo 的协议发到了 npm
官方的
issue链接: https://github.com/youzan/vant/issues/13911
对比vant两个版本:
vant@4.10.0(2026-06-28 发布):
{"@vant/use":"^1.6.0","@vue/shared":"^3.5.39","@vant/popperjs":"^1.3.0"}vant@4.10.1(2026-08-30 发布):
{"@vant/use":"workspace:^","@vue/shared":"^3.5.42","@vant/popperjs":"workspace:^"}真相大白:Vant 发布4.10.1时,把 monorepo 内部的workspace:^协议原封不动带到了 npm。项目声明"vant": "^4.10.0",npm 每次解析最新版,昨天才发的4.10.1今天就被拉到了。
时间线完全吻合:
| 时间 | 事件 |
|---|---|
| 2026-06-28 | vant@4.10.0发布,依赖正常 |
| 2026-08-30 | vant@4.10.1发布,携带workspace:协议 |
| 2026-08-31 | Jenkinsnpm install命中4.10.1→ 报错 |
本地没炸是因为有pnpm-lock.yaml锁着vant@4.10.0;Jenkins 用 npm 且无 lock 文件,只能被最新版牵着走。
科普:^版本号到底是什么意思
版本号三段式
语义化版本(SemVer):主版本.次版本.补丁版本,如4.10.0:
| 段 | 含义 | 示例 |
|---|---|---|
| 主版本(major) | 不兼容的大改动 | 5.0.0 |
| 次版本(minor) | 向后兼容的新功能 | 4.11.0 |
| 补丁版本(patch) | 向后兼容的 bug 修复 | 4.10.1 |
^(caret,插入符)
表示:允许更新次版本和补丁版本,但不允许跨越主版本。
"vant": "^4.10.0" ⇔ >=4.10.0 且 <5.0.0npm 会选范围内最新的版本,但绝不会碰5.0.0。
常见写法对照表
| 写法 | 含义 | 命中范围 |
|---|---|---|
"vant": "4.10.0" | 精确锁定 | 只有4.10.0 |
"vant": "~4.10.0" | 只允许补丁更新 | >=4.10.0 <4.11.0 |
"vant": "^4.10.0" | 允许次版本+补丁更新 | >=4.10.0 <5.0.0 |
"vant": "*" | 任意版本 | 全部 |
正是^让我们"中招"
- 项目写
"vant": "^4.10.0"→ 允许4.10.x任意新版本 - Vant 发
4.10.1,满足^4.10.0范围 - npm 无 lock 文件兜底,每次挑范围内最新版 → 命中带毒的
4.10.1
解决方案
第一步:锁定安全版本(立即止血)
{"dependencies":{"vant":"4.10.0"// 由 ^4.10.0 改为精确锁定}}去掉^,任何包管理器都不会再拉到带毒的4.10.1。提交后 Jenkins 重新构建即恢复。
第二步:关注上游修复(后续跟进)
等 Vant 发布4.10.2+修复版本后,再恢复^4.10.0或升级新版本。
第三步:治本——统一包管理器(长期建议)
项目 README 规定"必须使用 pnpm,禁止 npm / yarn",但 Jenkins 一直在用 npm。pnpm 有 lock 文件锁死版本,上游"带毒发布"也不受影响。建议 Jenkins 改为:
corepackenablepnpminstall--frozen-lockfilepnpmbuild:uat经验与反思
- 版本声明与锁文件,缺一不可。
^意味着"用范围内最新版",等于把命运交给上游;lock 文件的作用正是把范围冻结成具体版本。CI 环境必须有 lock 文件,否则任何一次上游发布都可能变成事故。 - 包管理器要"言行一致"。规范里写 pnpm,CI 却跑 npm,等于自己拆掉了锁文件这道防线。从本地到 CI 应全链路统一。
- “突然变坏"优先怀疑"变化的东西”。代码没改、上周还好,那变化的一定是外部因素——依赖版本、环境、镜像源。
- 二分法是依赖问题的利器。面对几十个依赖,对半拆解、逐个击破,比对着日志猜快得多。
- 大型生态的依赖也可能"带毒"。Vant 是老牌组件库一样会踩
workspace:泄漏的坑,供应链上的任何一环都值得警惕。
事故不可怕,可怕的是没有锁版本。
🚀 感谢阅读!想了解更多?
📖 我的博客网站 | 记录思考,分享干货
🏡 我的个人主页 | 关于我、开源项目