依赖更新机器人
扫描过时的包。运行安全审计。获取变更日志。汇总重大变更。为每个风险组创建一个 PR。
关键规则: 仅更新包管理器过时命令实际报告的包。切勿猜测或编造版本号。如果无法获取变更日志,请说明缺失而非编造内容。
第 1 步:环境检查
echo "GEMINI_API_KEY: ${GEMINI_API_KEY:+set}"
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-未设置,获取变更日志速率限制为 60/小时}"
gh auth status 2>/dev/null | head -1 || echo "gh: 未认证"
如果 GEMINI_API_KEY 缺失: 停止。告知用户:“需要 GEMINI_API_KEY。请在 aistudio.google.com 获取。并将其添加到 .env 文件中。”
如果 gh 未认证: 停止。告知用户:“必须认证 GitHub CLI。请运行:gh auth login”
检测包管理器:
ls package.json 2>/dev/null && echo "npm"
ls requirements.txt pyproject.toml 2>/dev/null && echo "pip"
ls Cargo.toml 2>/dev/null && echo "cargo"
ls go.mod 2>/dev/null && echo "go"
ls Gemfile 2>/dev/null && echo "ruby"
如果发现多个,询问:“找到 [列表]。要扫描哪一个?(all / npm / pip / cargo / go / ruby)”
第 2 步:检测过时包
npm:
npm outdated --json --long 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
for name, info in data.items():
print(json.dumps({'name': name, 'current': info.get('current','?'), 'latest': info.get('latest','?'), 'dep_type': info.get('type','dependencies')}))
"
pip:
pip list --outdated --format=json 2>/dev/null | python3 -c "
import sys, json
for p in json.load(sys.stdin):
print(json.dumps({'name': p['name'], 'current': p['version'], 'latest': p['latest_version']}))
"
Cargo (Rust):
cargo outdated --format json 2>/dev/null || \
cargo outdated 2>/dev/null | grep -v "^---" | tail -n +3 | head -30
# 如果 cargo-outdated 未安装:cargo install cargo-outdated
Go 模块:
go list -u -m -json all 2>/dev/null | python3 -c "
import sys, json
decoder = json.JSONDecoder()
buf = sys.stdin.read()
pos = 0
while pos < len(buf):
try:
obj, idx = decoder.raw_decode(buf, pos)
if obj.get('Update'):
print(json.dumps({'name': obj['Path'], 'current': obj['Version'], 'latest': obj['Update']['Version']}))
pos += idx
except: break
"
Ruby (Bundler):
bundle outdated --parseable 2>/dev/null | python3 -c "
import sys
for line in sys.stdin:
parts = line.strip().split()
if len(parts) >= 4:
print('{\"name\":\"' + parts[0] + '\",\"current\":\"' + parts[3].strip('()') + '\",\"latest\":\"' + parts[1] + '\"}')
"
如果全部返回为空:“所有包都是最新的。” 停止。
继续前说明数量:“发现 X 个过时的包。”
第 3 步:按风险级别分类
解析版本号提升(当前 → 最新):
- MAJOR:第一个数字改变(1.x.x → 2.x.x)
- MINOR:第二个数字改变(1.2.x → 1.3.x)
- PATCH:第三个数字改变(1.2.3 → 1.2.4)
python3 -c "
def classify(current, latest):
try:
c = [int(x) for x in current.lstrip('v').split('.')[:3]]
l = [int(x) for x in latest.lstrip('v').split('.')[:3]]
if l[0] > c[0]: return 'major'
if len(l) > 1 and len(c) > 1 and l[1] > c[1]: return 'minor'
return 'patch'
except: return 'unknown'
"
说明细分情况:“Patch:X 个包。Minor:Y 个包。Major:Z 个包。”
第 4 步:安全审计
在创建任何 PR 之前运行 CVE 扫描。这决定了优先级。
npm:
npm audit --json 2>/dev/null | python3 -c "
import sys, json
d = json.load(sys.stdin)
vulns = d.get('vulnerabilities', {})
for pkg, info in vulns.items():
sev = info.get('severity', 'unknown')
via = [v.get('title','') for v in info.get('via',[]) if isinstance(v, dict)]
print(f' [{sev.upper()}] {pkg}: {via[0] if via else \"see npm audit\"}')
" 2>/dev/null || echo "未发现漏洞或 npm audit 不可用"
pip:
pip-audit --format=json 2>/dev/null | python3 -c "
import sys, json
for vuln in json.load(sys.stdin):
print(f' [{vuln.get(\"aliases\",[\"\"])[0]}] {vuln[\"name\"]} {vuln[\"version\"]}: {vuln[\"description\"][:80]}')
" 2>/dev/null || echo "pip-audit 未安装。请运行:pip install pip-audit"
Cargo:
cargo audit 2>/dev/null | grep -E "^(ID|Package|Severity|URL)" | head -30 \
|| echo "cargo-audit 未安装。请运行:cargo install cargo-audit"
升级规则: 如果 PATCH 或 MINOR 更新包含严重(Critical)或高危(High)CVE,则将其提升为 MAJOR 优先级:为其单独创建 PR,并将 CVE 详情包含在 PR 正文中。
在继续之前报告安全发现:
安全审计:发现 [N] 个漏洞
[CRITICAL] lodash 4.17.19: 原型污染 (CVE-2021-23337)
[HIGH] axios 0.21.1: 服务器端请求伪造 (CVE-2021-3749)
如果没有漏洞:“安全审计:通过。”
第 5 步:获取变更日志
对于每个包,按顺序尝试以下来源。在第一个返回内容的来源处停止。
来源 1:GitHub Releases API
从仓库获取仓库 URL:
# npm
curl -s "https://registry.npmjs.org/{PACKAGE}/latest" \
| python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('repository',{}); print(r.get('url','') if isinstance(r,dict) else str(r))"
# pip
curl -s "https://pypi.org/pypi/{PACKAGE}/json" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('info',{}).get('home_page','') or d.get('info',{}).get('project_urls',{}).get('Source',''))"
获取最近 5 个版本:
AUTH_HEADER=""
[ -n "$GITHUB_TOKEN" ] && AUTH_HEADER="-H \"Authorization: Bearer $GITHUB_TOKEN\""
curl -s $AUTH_HEADER \
"https://api.github.com/repos/{OWNER}/{REPO}/releases?per_page=5" \
| python3 -c "import sys,json; [print(json.dumps({'tag':r.get('tag_name',''),'body':r.get('body','')[:1500]})) for r in json.load(sys.stdin)]"
仅保留在当前版本和最新版本之间的版本。
来源 2:npm registry README(回退)
curl -s "https://registry.npmjs.org/{PACKAGE}" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('readme','')[:3000])"
来源 3:PyPI 描述(最后手段,适用于 pip)
curl -s "https://pypi.org/pypi/{PACKAGE}/json" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('info',{}).get('description','')[:2000])"
如果没有来源返回内容:注明“未找到变更日志”,然后继续。
第 6 步:使用 Gemini 汇总
每个风险组一个请求。包含任何受 CVE 影响的包的安全发现:
cat > /tmp/deps-summary-request.json << 'ENDJSON'
{
"system_instruction": {
"parts": [{
"text": "You are a developer writing a GitHub PR description for a dependency update. Given a list of packages being updated and their raw changelog content, write a concise PR body in Markdown. Rules: For each package, list only what changed between the OLD version and the NEW version. Use bullet points. Flag breaking changes with a BREAKING prefix. Flag CVE fixes with a SECURITY prefix and include the CVE ID. Keep each package section to 3-5 bullets maximum. If no changelog was found for a package, write 'No changelog available.' Do not use em dashes. Do not use these words: seamless, robust, leverage, transform, innovative. Output only the Markdown PR body, no commentary."
}]
},
"contents": [{
"parts": [{
"text": "PACKAGES_AND_CHANGELOGS_HERE"
}]
}],
"generationConfig": {
"temperature": 0.2,
"maxOutputTokens": 2048
}
}
ENDJSON
curl -s -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/deps-summary-request.json \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['candidates'][0]['content']['parts'][0]['text'])"
第 7 步:创建 PR
每个非空风险组创建一个 PR。对于 major 更新,为每个包单独创建一个 PR(需要逐个审查)。
1. 创建分支:
BRANCH="deps/{RISK}-updates-$(date +%Y%m%d)"
git checkout -b "$BRANCH"
2. 更新包文件:
npm:
npm install {package}@{latest_version} --save-exact
# devDependencies:
npm install {package}@{latest_version} --save-dev --save-exact
pip:
python3 -c "
import re, sys
pkg, version, filename = sys.argv[1], sys.argv[2], sys.argv[3]
with open(filename) as f: content = f.read()
pattern = rf'^{re.escape(pkg)}[>=<!\s].*$'
new_content = re.sub(pattern, f'{pkg}=={version}', content, flags=re.MULTILINE|re.IGNORECASE)
if new_content == content: new_content = content + f'\n{pkg}=={version}'
open(filename, 'w').write(new_content)
" "{PACKAGE}" "{LATEST}" "requirements.txt"
Cargo:
# 编辑 Cargo.toml 中该包的版本字段,然后:
cargo update {package}
Go:
go get {module}@{latest_version}
go mod tidy
Ruby:
bundle update {gem_name}
3. 提交:
git add -A
git commit -m "chore(deps): update {RISK} dependencies $(date +%Y-%m-%d)"
4. 创建 PR:
cat > /tmp/dep-pr-body-{RISK}.md << 'ENDMD'
PR_BODY_FROM_GEMINI
ENDMD
gh pr create \
--title "chore(deps): update {RISK} dependencies" \
--body-file /tmp/dep-pr-body-{RISK}.md \
--label "dependencies" \
--base main
Major 更新添加标签 dependencies,breaking-change。修复 CVE 的更新添加标签 dependencies,security。
每次创建 PR 后,返回到主分支:git checkout main
第 8 步:诊断模式
触发条件: 如果任何包安装命令在运行中失败,则进入诊断模式,而不是停止。
检测失败类型:
| 错误模式 | 可能原因 | 建议修复 |
|---|---|---|
peer dep conflict | 对等依赖不兼容 | 显示冲突对,建议使用 --legacy-peer-deps 标志或降级 |
ERESOLVE | npm 解析冲突 | 仅为受影响的包运行 npm install --legacy-peer-deps |
version not found | 仓库中不存在该版本 | 使用 npm view {pkg} versions 检查仓库 |
python requires | Python 版本不兼容 | 注明所需的 Python 版本,跳过此包 |
cargo E0463 | Rust 版本不兼容 | 标记为手动审查 |
输出诊断摘要:
安装失败:{package}:{错误类型}
可能原因:{解释}
建议修复:{具体命令或操作}
剩余包:继续处理 {N} 个成功的包。
不要因为一个包失败而停止整个运行。继续处理成功的包。
第 9 步:输出摘要
## 依赖更新摘要:[YYYY-MM-DD]
### 安全
[CRITICAL] lodash:CVE-2021-23337 已在 4.17.21 中修复:PR #42
[HIGH] axios:CVE-2021-3749 已在 0.21.4 中修复:PR #42
| 风险级别 | 包 | PR |
|------------|----------|-----|
| Patch | lodash 4.17.19→4.17.21, axios 0.21.1→0.21.4 | #42 |
| Minor | express 4.17.1→4.18.2 | #43 |
| Major | react 17.0.2→18.2.0 | #44 |
创建的 PR 数:3
未找到变更日志的包:some-obscure-pkg(仓库中无 GitHub 仓库)
安装失败:无
下一步操作:在合并前单独审查 major 更新 PR。