更新依赖项
目标是使项目的依赖项保持最新且没有已知漏洞,并作为用户可信任的变更交付:每一步都由项目自身的检查验证,每个有风险的步骤都被隔离,以便可以单独审查或回滚。
依赖项工作通常以两种方式失败。一种是“大爆炸式”升级,所有内容同时升级,导致某些功能损坏,没人知道是哪个升级引起的。另一种是隐藏问题的“修复”,例如 --force、--legacy-peer-deps、放宽版本范围或抑制安全公告。以下工作流程旨在避免这两种情况。
0. 确定任务类型
- 仅检查(“有漏洞吗?”、“哪些过时了?”):执行步骤 1-3,报告并停止。不要修改文件。
- 更新(默认用于“更新我的依赖项”、“修复审计警告”、“保持包更新”):执行整个工作流程。
- 单个包(“将 vite 升级到 v8”):执行步骤 1-2,然后对该包执行步骤 6(以及任何必须随之移动的包),然后执行步骤 7-8。
1. 预备检查
-
阅读仓库的贡献者文档(
AGENTS.md、CLAUDE.md、CONTRIBUTING.md)和 CI 配置(.github/workflows/*),了解验证命令(lint、typecheck、test、build)和提交消息约定。使用仓库实际在 CI 中运行的内容。如果没有任何文档,则使用清单中定义的脚本。 -
确保工作目录是干净的。如果不是,停止并询问。将用户未提交的工作混入依赖项提交会造成真正的损害。
-
通过锁文件和清单检测生态系统(一个仓库可以有多个生态系统):
文件 参考文档 package-lock.json,pnpm-lock.yaml,yarn.lock,bun.lockreferences/node.mdrequirements*.txt,pyproject.toml,uv.lock,poetry.lock,Pipfile.lockreferences/python.mdCargo.toml/Cargo.lockreferences/rust.mdgo.mod/go.sumreferences/go.md其他(Gemfile.lock, composer.lock, …) 使用生态系统自身的工具,加上 scripts/osv_check.py进行审计阅读相应的参考文件。它列出了确切的命令和该生态系统的常见陷阱。
-
找到项目目标的运行时版本:
.nvmrc、engines、.python-version、requires-python、rust-version、go指令或 CI 中的版本。某些包必须跟踪运行时,而不是最新版本。例如,@types/node应与项目运行的 Node 主版本匹配,因此将其升级到更新的 Node 主版本会让代码使用在生产环境中崩溃的 API。将“最新”视为候选版本,而非目标版本。 -
在更改任何内容之前运行验证命令并记录结果。如果已有失败,你现在就需要知道。否则你会将本已存在的失败归咎于升级,或者交付一个红色的分支。如果基准是红色的,告诉用户并询问是否继续。
2. 清单
使用生态系统的工具(参见参考文档)收集两个列表:
- 漏洞:本地审计工具。如果未安装且无法临时运行,请使用捆绑的扫描器:
python3 <skill-dir>/scripts/osv_check.py <lockfile>...(查询 osv.dev;仅使用标准库)。 同时运行 OSV 扫描以及本地工具成本低廉且通常值得,因为本地工具有时会遗漏嵌套或重复副本包中的公告。 - 过时的包:当前版本 → 期望版本(在声明范围内)→ 最新版本。
对于每个漏洞,找出哪个直接依赖项引入了易受攻击的包,以及哪个版本的该直接依赖项修复了它。用户操作的是直接依赖项。仅凭传递依赖项的名称是无法操作的。
3. 计划并报告
将所有内容分为三类,并在进行更改前以简短表格形式显示计划:
- 安全修复:任何移除公告的内容。最紧急。如果修复需要主要版本升级,则属于主要版本(第 3 类),但请在其中列在前面并说明是安全驱动的。
- 安全升级:semver 范围内的补丁/次要版本(对于 0.x 包,次要版本视为破坏性变更)。
- 主要版本:其他所有内容,每行一个。必须一起移动的包(框架及其插件、工具及其类型定义、共享对等依赖项的包)应分组,并将每组视为一个单元。
严重性对排序很重要,可及性对解释也很重要。开发专用测试运行器中的关键公告仍然值得修复,但应明确说明是开发专用,而不是让用户惊慌。对于仅检查请求,此报告就是交付物。包括严重性、公告 ID、依赖项路径和修复版本。
4. 分支
从最新的默认分支创建 chore/deps-YYYY-MM-DD(今天的日期),除非用户已经在为此工作创建的分支上。
5. 安全修复和安全升级
- 应用范围内的安全修复,重新运行验证命令,然后提交。
- 一起进行安全的补丁/次要版本升级,重新运行验证命令,然后提交。 如果验证失败,进行二分查找:回退一半的更改并重试,直到找到问题所在,然后将其拉出并像主要版本一样处理。
让包管理器写入锁文件。切勿手动编辑锁文件。
6. 主要版本,一次一个单元
对于每个主要单元,按顺序(安全驱动的先,然后是基础工具,再是依赖于它们的工具,例如编译器先于依赖于它的 linter 插件):
- 阅读变更内容。找到你正在跨越的每个主要版本的迁移指南或发布说明(v2→v5 意味着阅读 v3、v4 和 v5)。好的来源是项目的
CHANGELOG.md、GitHub 发布以及文档站点的“迁移”页面。提取适用于此代码库的破坏性变更:搜索已删除或重命名的 API 和配置键。 - 检查对等依赖项。确保新版本满足其周围所有内容的对等依赖项和引擎要求。有时最新主要版本尚无法采用,因为某个插件尚未跟上。然后采用可用的最新版本,并记录阻碍其余版本的原因。
- 升级并修复。升级该单元,应用迁移(配置重命名、API 更改、codemods(如果项目附带)),并重新运行验证命令,直到它们通过。正确修复代码。不要削弱测试、添加
skip,或使用any或@ts-ignore风格的转义来静默类型错误以达到绿色。 - 单独提交该单元,将迁移说明放在提交正文中。
- 知道何时停止。如果一个单元需要大规模重构或产品决策(移除的功能、无法验证的运行时行为更改),或者无法通过合理努力使其变为绿色,则干净地回滚(
git checkout -- .回到上一个提交)并将其记录为延迟,并附带具体的阻塞原因。一个带有明确原因的延迟主要版本是好的结果。半成品则不是。
7. 重新验证和重新审计
在最终状态下运行完整的验证命令并再次进行审计。所有公告现在都应已修复或已解释。对于无法修复的公告:
- 如果是传递依赖项且父包尚未发布修复,则一个覆盖/解析操作来锁定已修补的传递版本是仅当该版本与父包期望的版本 semver 兼容时才可接受。验证它,并在 PR 中注明,以便以后有人移除它。
- 如果任何地方都没有修补版本,请说明,并评估易受攻击的代码路径在此项目中是否可达。
- 切勿使用忽略/白名单机制来静默地通过审计。
8. 交付
提交遵循仓库的约定。如果仓库使用 Conventional Commits 且没有更具体的约定,请使用:
fix(deps): ...用于运行时依赖项的安全修复build(deps): ...用于升级,以及仅用于开发工具的安全修复- 摘要命名包和目标,例如
build(deps): bump vitest to v5。正文列出已修复的公告和所做的任何迁移更改。
推送并打开一个 PR(gh pr create),除非用户另有要求或没有远程仓库。使用以下正文:
## 摘要
<一或两句话:哪些内容移动了,解决了多少公告>
## 安全
| 公告 | 严重性 | 包(路径) | 修复版本 |
|---|---|---|---|
## 更新
| 包 | 从 | 到 | 类型 | 说明 |
|---|---|---|---|---|
<类型 = security / patch / minor / major;说明 = 所做的迁移更改>
## 延迟
| 包 | 最新版本 | 为何现在不行 |
|---|---|---|
<对等依赖项阻塞、需要重构、运行时固定等;如果为空,则填写“无”>
## 剩余公告
<无,或每个公告附带原因和可及性评估>
## 验证
<在最终提交上运行的确切命令及其结果>
以简短的聊天摘要结束:PR 链接、已修复的公告数量与剩余数量、以及带有一行原因的延迟主要版本。