AI 写完 package.json 之后:vibe 项目的依赖升级与维护期 SOP
AI 帮你写完了 package.json,但不会在凌晨三点帮你合并第 47 个 Dependabot PR。这套一人维护者 SOP 覆盖 lockfile 军规、升级分级决策、Dependabot/Renovate 自动化、major 升级评估三件套、CI 验证矩阵、CVE 修复 SLA、5 分钟回滚流程与季度健康度复盘,附 8 个血泪踩坑。

开篇划界:这篇文章不是讲"装什么"的
如果你看过本站的《vibe 项目依赖供应链安全指南》,你会记得那篇讲的是"装什么":如何在 npm install 之前识别恶意包、防范 slopsquatting、做上线前的安全审查——那是上线前的功夫。本篇讲的是上线之后的事:维护期怎么持续更新依赖——版本策略、分级决策、自动化工具、回滚链路、季度复盘。两篇在时间轴上前后衔接、视角不重叠:前一篇帮你把第一道门守住,这一篇帮你在项目活着的每一天里,不被依赖升级拖死。
一个残酷的事实:vibe 项目在维护期的线上事故,十有八九和依赖升级有关——不是"升级一时爽",就是"三年不升级、一次升出个大窟窿"。AI 帮你写完了 package.json,但它不会在凌晨三点帮你合并 Dependabot 的第 47 个 PR。本篇给你一套一人维护者也能跑起来的 SOP:lockfile 军规 → 分级决策 → 自动化配置 → major 评估 → CI 验证矩阵 → 安全 SLA → 回滚 SOP → 季度复盘,外加 8 个血泪踩坑。每节都可直接复制配置和代码。
一、维护期真相:先立 lockfile 军规
在谈任何升级策略之前,先解决一个基础问题:你的 lockfile(package-lock.json / pnpm-lock.yaml)必须提交到 Git,并且 CI 里必须用锁定安装。这是所有 SOP 的地基。没有锁定的依赖树,任何"分级决策"都是空谈——因为你本地和生产装的可能根本不是同一个版本。
lockfile 三条军规:
- 军规一:lockfile 必须进 Git。把它加进
.gitignore是最常见的作死行为。lockfile 是你生产环境依赖树的唯一事实来源。 - 军规二:CI 必须用
--frozen-lockfile(npm 是npm ci)。这样如果有人改了package.json却没更新 lockfile,CI 会直接失败,而不是悄悄装个新版本。 - 军规三:永远不要手改 lockfile。冲突了就删掉重生成(
rm package-lock.json && npm install),不要试图手动合并——后面踩坑清单里有血泪案例。
# .github/workflows/ci.yml —— 锁定安装是第一道防线
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci # 注意:不是 npm install
- run: npm test
如果你用 pnpm,CI 里就是 pnpm install --frozen-lockfile;yarn 则是 yarn install --frozen-lockfile。口诀:本地开发可以用 install,CI 和生产永远用 frozen。
二、升级分级决策表:patch 自动合、minor 看 changelog、major 建分支
一人维护者最大的敌人是决策疲劳:每个 PR 都要想"这个能不能合",一周下来就被 Dependabot 淹没了。解法是把决策前置,做成一张分级表——版本一出来,级别就决定了流程,不用每次重新思考。
分级依据是 SemVer(语义化版本):MAJOR.MINOR.PATCH。patch(第三位)= 修 bug,理论上完全兼容;minor(第二位)= 向下兼容的新功能;major(第一位)= 可能有破坏性变更。三档的风险和处理流程如下表:
- PATCH(如 1.2.3 → 1.2.4):自动合并。风险极低。Dependabot 发 PR → CI 通过 → 自动合并,连看都不用看。唯一的例外是安全相关的 patch——它本来就该更快合。
- MINOR(如 1.2.3 → 1.3.0):读 changelog + 冒烟验证。快速扫一眼 Release Notes 确认没有 deprecation 警告,跑一遍核心流程冒烟测试,手动点合并。每天花 10 分钟批量处理一批。
- MAJOR(如 1.x → 2.0):建独立分支 + 完整评估。绝不直接合进 main。走第四节的 major 升级评估清单,跑类型检查 + 冒烟脚本,必要时跑 codemod。一个人维护也至少留出半天。

图 1:升级分级决策流程。patch 走自动合并通道,minor 走 changelog 速读 + CI 验证,major 必须建独立分支做人工评估。三条通道互不干扰,一人维护者每周只需关注 minor 和 major 两条。
判断小技巧:看包的 0.x 版本要特别小心——SemVer 规定 0.x 的 minor 也可能含破坏性变更。对 0.x 的包,一律按 major 级别处理。
三、Dependabot / Renovate 最小可用配置
自动化工具的价值不是"帮你升级",而是帮你把升级变成标准化的 PR:有 diff、有 CI、有 changelog 链接。Dependabot(GitHub 原生)和 Renovate(二选一即可)都支持分组、合并窗口、patch 自动合并。下面是一套经过实战检验的最小配置,直接复制可用。
Dependabot 配置(.github/dependabot.yml)
version: 2
updates:
# 1. npm 依赖:分组 + 每周一批
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Asia/Shanghai"
open-pull-requests-limit: 10 # 一次最多开 10 个 PR,防轰炸
groups:
# patch 打包成一个 PR,减少噪音
patch-updates:
patterns: ["*"]
update-types: ["patch"]
# devDependencies 单独一组,风险低
dev-dependencies:
dependency-type: "development"
labels: ["dependencies"]
# 忽略 alpha/beta/rc 预发布版(除非你主动要尝鲜)
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
# major 升级改为手动:先注释掉自动提 PR
# 2. GitHub Actions 本身也要升级
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
actions-patch:
patterns: ["*"]
update-types: ["patch", "minor"]
patch 自动合并(.github/workflows/automerge.yml)
# 只有 patch 分组 + CI 全绿 + 非 draft,才自动合并
name: automerge-patch
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
automerge:
if: >
github.actor == 'dependabot[bot]' &&
contains(github.event.pull_request.title, 'patch')
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: 'npm' }
- run: npm ci && npm test
- name: auto merge
if: success()
run: gh pr merge --auto --squash "$PR_URL"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
关键设计点:自动合并只开给 patch,minor 和 major 必须人工点合并;自动合并前必须跑全量测试(不是只跑 lint);合并窗口固定在周一上午——这样一周的升级噪音集中在一天处理,而不是每天被打断。Renovate 用户可以用 renovate.json 的 packageRules + automerge: true + matchUpdateTypes: ["patch"] 实现同样效果。
四、major 升级评估清单:changelog 关键词扫描 + 类型检查 + 冒烟脚本
major 升级是维护期风险最高的动作,一人团队没有"测试团队兜底"这回事。用这套三件套评估法,把一次 major 升级压缩到可控的半天工作量:
第一件:changelog 关键词速读(15 分钟)
打开包的 Release 页面(GitHub Releases 或 CHANGELOG.md),用 Ctrl+F 扫这组关键词:breaking、removed、deprecated、rename、migration、codemod、dropped support、requires。重点看三件事:① 有没有官方 codemod(比如 Next.js 大版本升级官方会给 npx @next/codemod);② peerDependencies 要求变了没;③ 最低 Node 版本要求提了没。把找到的 breaking changes 列成清单,每条标注"影响我的代码 / 不影响"。
第二件:类型检查 + 单元测试(30 分钟)
# 建独立分支,升级,跑三板斧
git checkout -b upgrade/next-16
npm install next@16 # 只升目标包,不要顺手 npm update 全升
npx tsc --noEmit # TypeScript 项目:类型检查是第一道网
npm test # 全量单测
npm run build # 生产构建必须过(很多坑只在 build 时暴露)
TypeScript 项目的类型检查是 major 升级的免费午餐:API 签名变了,tsc 会直接把调用处全指出来。JS 项目建议至少对升级涉及的模块先补几个关键测试再升。
第三件:冒烟脚本(20 分钟)
写一个覆盖核心路径的冒烟脚本(登录 → 核心操作 → 支付/关键写操作 → 退出),每次 major 升级后在 staging 跑一遍。别用"我手动点点看"代替——人会忘,脚本不会。
// scripts/smoke.ts —— 核心路径冒烟(示例:Next.js + Playwright)
import { chromium } from '@playwright/test';
const BASE = process.env.STAGING_URL!;
const flows = [
{ name: '首页加载', run: async (p) => p.goto(BASE) },
{ name: '登录', run: async (p) => {
await p.goto(`${BASE}/login`);
await p.fill('[name=email]', 'smoke@test.dev');
await p.fill('[name=password]', process.env.SMOKE_PW!);
await p.click('button[type=submit]');
await p.waitForURL('**/dashboard');
}},
{ name: '核心写操作', run: async (p) => {
await p.goto(`${BASE}/dashboard`);
await p.click('text=新建');
await p.fill('[name=title]', 'smoke-' + Date.now());
await p.click('button:has-text("保存")');
await p.waitForSelector('text=保存成功');
}},
];
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
for (const f of flows) { await f.run(page); console.log('OK:', f.name); }
await browser.close();
})();
三件套全部通过 → 合并 → 按第七节的发布策略上线。任何一件挂了 → 先看是不是 breaking change 没处理完,而不是"回滚 lockfile 下次再说"——升级分支烂尾是技术债的最大来源,要么修完合,要么明确记录为什么放弃。
五、CI 验证矩阵:升级前后对比,一次失败就打回
依赖升级的 CI 不能只跑"当前版本",要跑矩阵:多 Node 版本 × 升级前后的对比。这样升级 PR 的 CI 失败时,你能立刻区分是"升级引入的问题"还是"本来就挂的测试"。
# .github/workflows/dependency-verify.yml
name: dependency-verify
on:
pull_request:
paths: ['package.json', 'package-lock.json', 'pnpm-lock.yaml']
jobs:
matrix:
runs-on: ubuntu-latest
strategy:
fail-fast: true # 任一组合失败立刻停,省 CI 分钟数
matrix:
node: [18, 20, 22] # 覆盖你声明支持的 LTS 范围
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: ${{ matrix.node }}, cache: 'npm' }
- run: npm ci
- run: npm run lint
- run: npm test -- --ci
- run: npm run build # 构建产物验证,很多升级坑在这里
# 升级前后对比:把 base 分支的测试结果也跑一遍(用 workflow_run 或手动触发)
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm audit --audit-level=high
# high 及以上漏洞直接让 CI 变红,低风险只告警
矩阵设计的三条经验:① Node 版本矩阵只覆盖你 package.json engines 里声明的版本,别为了"全"去测 Node 16(如果你的用户早就不该用了);② fail-fast: true 对一人团队是省钱的——第一个组合挂了就停,别把 3 个版本全跑完;③ 依赖升级 PR 的 CI 失败时,先在 base 分支跑同样测试:如果 base 也挂,说明是测试本身 flaky 或环境问题,不是升级的锅。
六、AI 生成的依赖,怎么审:幽灵依赖 / 废弃包 / 重复版本三查
AI 写代码有个坏习惯:import 了包却不写进 package.json(靠 node_modules 里碰巧存在)、装了包却一次没用、或者装了三四个功能重叠的包。项目上线后,这些就是维护期的暗雷。每月跑一次这三查:
查一:幽灵依赖(phantom dependencies)
代码里 import 了,但 package.json 里没声明——靠的是某个间接依赖把它带进了 node_modules。一旦那个间接依赖升级把它删了,你的构建就炸了。用 depcheck 一键查:
npx depcheck
# 输出示例:
# Unused dependencies
# * lodash <- package.json 里有,代码里没用
# Missing dependencies
# * date-fns <- 代码里 import 了,package.json 里没写(幽灵依赖!)
# * sharp <- 同上,常见于 AI 生成的图片处理代码
修复:missing 的补进 dependencies,unused 的删掉。注意 depcheck 对动态 require 有误报,删之前 grep 确认一下。
查二:废弃包(deprecated packages)
npm install 2>&1 | grep -i deprecat
# 或更系统地:
npx npm-check --skip-unused | grep -i deprecat
# 典型输出:request@2.88.2 (deprecated: har2 已废弃…) → 换 got/axios/undici
废弃包 = 作者已放弃维护 = 不会再有安全补丁。AI 特别喜欢用训练数据里的"经典老包"(request、moment),这些包 2026 年还在你项目里的话,优先级提到最高处理。
查三:重复版本(duplicate versions)
npm ls some-pkg # 看一个包被装了几个版本
npm dedupe # 自动合并可兼容的重复版本
# 看总体积:
npx cost-of-modules # 哪个包占了 node_modules 的大头
# 典型发现:react 17 和 react 18 同时存在(某个老插件拖进来的)→
# 用 overrides 强制统一:
// package.json —— 强制统一有安全/兼容问题的传递依赖版本
{
"overrides": {
"lodash": "^4.17.21",
"semver": "^7.5.2"
}
}
三查建议做成每月的 cron 任务(GitHub Actions schedule),输出报告发到你的邮箱或钉钉。AI 生成的代码尤其要查——它不会告诉你它装了三个日期库。
七、安全升级 vs 功能升级:CVE 修复的 SLA 分级
不是所有升级都一样急。把安全升级从功能升级里剥离出来,按 CVE 严重度定 SLA——这是维护期最重要的时间管理:
- Critical(CVSS 9.0+):24 小时内修复上线。别等 Dependabot 的周一批,直接手动升。有临时方案就先上临时方案:比如用
overrides把有漏洞的传递依赖 pin 到修复版,不等直接依赖发新版。 - High(CVSS 7.0-8.9):7 天内修复。走正常 PR 流程,但插队到本周最高优先级。
- Medium 及以下:随常规升级窗口处理。别为一个 medium CVE 打乱发布节奏,但要进待办、不能无限期拖。
# 紧急 CVE 的临时 pin 方案:不等上游发版,先锁传递依赖
# 假设 CVE-2026-xxxx 影响 foo@<2.0.1(它是 bar 的传递依赖)
# package.json:
{
"overrides": {
"foo": "^2.0.1" # 强制所有 foo 都用修复版
}
}
npm install && npm ls foo # 确认只剩一个版本
# 等 bar 官方发版修复后,再去掉 overrides(别让它变成永久债)
订阅 npm audit / GitHub Dependabot alerts / Snyk 的告警,critical 漏洞要能推送到你手机上,而不是躺在邮箱里。一人维护者的安全 SLA 全靠"告警能吵醒你"这一环。
八、5 分钟回滚 SOP:升级炸了之后的恢复流程
再好的验证也拦不住所有问题——区别只在于炸了之后你几分钟能恢复。把回滚练成肌肉记忆,而不是现想现查:
# 5 分钟回滚 SOP(计时开始)
# 第 1 分钟:确认问题来自这次升级(看监控/错误率曲线的时间拐点)
# 第 2 分钟:revert 升级提交(不要 git reset,不要 force push)
git revert <升级提交的 SHA> --no-edit
git push origin main
# 第 3 分钟:触发重新部署(Vercel/云平台会自动部署 main;自建则跑 deploy 脚本)
# 第 4 分钟:清依赖缓存,确认回滚后的版本
npm ci # 用回滚后的 lockfile 重装
npm ls <出问题的包> # 确认版本回到旧版
# 第 5 分钟:验证核心路径恢复(跑冒烟脚本),在事故群/频道里同步"已回滚"
回滚的四个铁律:① 用 git revert 不用 reset——revert 留痕、可审计,reset 会把历史搞乱;② 回滚的是"升级提交"而不是"整个 main"——如果升级 PR 里混了业务代码,先 revert 再 cherry-pick 业务部分回来;③ 回滚后必须清依赖缓存重装——CI 缓存里可能还躺着新版的包;④ 每次回滚后写 5 行复盘(为什么没拦住:是 changelog 没看、冒烟没覆盖、还是 CI 矩阵缺了某个 Node 版本),记进第九节的复盘表。

图 2:维护期升级流水线全景。正常链路从左到右:自动化发 PR → CI 矩阵验证 → staging 冒烟 → 生产发布;红色链路是回滚通道:监控告警 → git revert → 重新部署上一个健康构建 → 缓存失效与复盘。两条链路缺一不可。
九、季度依赖健康度复盘表:三个指标看长期趋势
周报式升级处理的是"眼前",季度复盘看的是"趋势"。每季度花 1 小时填这张表,三个指标足以判断你的依赖是在变健康还是在烂掉:
- 过期 major 数:
npx npm-check --skip-unused或npm outdated里 major 落后的包数量。趋势上升 = 你在积累"升级债",迟早要还一次大的。健康线:核心依赖(框架、ORM、鉴权)零 major 落后。 - 安全债:
npm audit里 high+critical 未修复的数量和最长存活时间。任何 critical 存活超过 24 小时都是红灯。 - 体积增长:
node_modules体积和生产构建产物体积的季度环比。AI 生成代码最常见的慢性病就是依赖体积无声膨胀——每个季度看一眼,涨幅超过 20% 就要查是谁拖进来的。
# 季度复盘一键采集脚本(输出贴进复盘表)
echo "=== 过期 major ==="; npm outdated | awk '$4 ~ /^[0-9]/ {print}'
echo "=== 安全债 ==="; npm audit --json | python3 -c \
"import json,sys; d=json.load(sys.stdin); m=d['metadata']['vulnerabilities']; \
print({k:m[k] for k in ('critical','high','moderate','low')})"
echo "=== 体积 ==="; du -sh node_modules; ls -lh .next/static 2>/dev/null || true
复盘表的真正价值是决策依据:过期 major 连续两季度上升 → 下季度排一个"升级周";体积连续膨胀 → 该做依赖瘦身专项了。没有数据,一人团队的维护决策全靠"感觉",而感觉在凌晨三点最不靠谱。
十、8 个踩坑:都是真金白银换来的
- lockfile 冲突手改。合并分支时
package-lock.json冲突了,顺手在编辑器里"合并一下"。结果:lockfile 结构被破坏,CI 用npm ci直接报错,生产部署卡住。正确做法:git checkout --theirs package-lock.json && npm install重新生成。 - peerDependencies 地狱。升级 React 18 时,某个 UI 库的 peerDependencies 还写着
^17.0.0,npm install直接 ERESOLVE 报错。别急着--legacy-peer-deps(那是把问题埋起来),先看这个库有没有新版支持 18,没有就换库——peer 冲突是"这个库已死"的强烈信号。 - 升级 Next.js 大版本没跑 codemod。Next 官方每个大版本都配 codemod(
npx @next/codemod@latest upgrade latest),能自动改 80% 的 breaking changes。不跑 codemod 手改,漏一个getServerSideProps的改名就能让你找半天。 - 自动合并把 lockfile 搞坏。两个 Dependabot PR 同时自动合并,第二个的 lockfile 基于旧 main 生成,合并后 lockfile 与 package.json 不一致。解法:开 rebase/merge queue(GitHub 的 merge queue),让自动合并串行化;或者把 patch 分组,减少并发 PR 数。
npm update一把梭。心情好npm update全升了,package.json 里十几个包一起跳 minor,炸了都不知道是谁的锅。永远一次只升一个包(或一个 Dependabot 分组),出问题二分法定位。- 只在最新 Node 上测。本地 Node 22 跑得好好的,生产是 Node 18,某个包的新版用了 20+ 的 API,部署即炸。CI 矩阵必须覆盖生产实际跑的 Node 版本——
engines字段不是摆设。 - 把
overrides当永久方案。为修 CVE 加的临时 overrides,三个月后上游早发版了,你的 overrides 还在,甚至挡住了正常升级。每次加 overrides 都在旁边写注释# TODO: bar>2.1 发版后删除,季度复盘时检查。 - 升级分支烂尾。major 升级分支开了,CI 挂了两个测试,"等有空再修",三个月后分支和 main 差了 200 个提交,只能删掉重来。规则:升级分支存活不超过两周,搞不定就 revert 掉、记进复盘表,下个季度再战。烂尾分支比不升级更贵。
结语:维护期拼的不是技术,是纪律
依赖升级这件事,技术含量其实不高:SemVer 分级、自动化 PR、CI 矩阵、回滚 SOP,都是现成的套路。vibe 项目维护期真正的杀手是纪律缺失——lockfile 不锁、升级一把梭、分支烂尾、CVE 躺三个月。把这篇的 SOP 落成三件事:① 今天就把 CI 换成 npm ci;② 这周配好 Dependabot 分组 + patch 自动合并;③ 每月跑一次三查、每季度填一次复盘表。剩下的,交给流程。
评论 (0)
相关文章

console.log 里躺着用户邮箱和登录 token?这篇指南给你一人团队一天能落地的日志脱敏全方案:PII 四级分级表、可直接复制的 TypeScript 脱敏三件套、console.log 到 pino 的最小迁移、请求日志中间件模板、Sentry beforeSend 配置、分级保留策略与成本账,外加 8 个真实踩坑。

备份是最后的防线,回收站是第一道。这篇实战指南讲应用层的删除语义:三种删除语义决策表、deleted_at 数据模型、partial 唯一索引、Prisma 查询层防线、回收站保留期分级、GDPR 与个保法删除权对账,以及 8 项上线 checklist。

没有后台的上线项目,第一次运维请求就会把你逼回 ssh 手敲 SQL。这篇指南从“上线第 1 天”的功能 MVP 清单讲起:用户/订单/内容/配置四类实体的后台功能矩阵,super_admin/operator/support 三档权限与三层隔离,高危动作的人审二次确认,追加写不可篡改的审计日志,impersonate 的限时 token 安全链路,以及 Retool/自建低代码选型对照和 8 项上线 checklist。