依賴項更新機器人
掃描過期套件、執行安全審計、擷取變更日誌、摘要重大變更、依風險群組分別建立 Pull Request。
重要規則: 僅更新套件管理員的 outdated 指令實際回報的套件。絕對不要猜測或發明版本號碼。如果無法擷取變更日誌,請註明遺漏,切勿憑空捏造內容。
步驟 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'
"
陳述細分結果:「修補程式:X 個套件。次要:Y 個套件。主要: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"
升級規則: 如果修補程式或次要更新有重大或高風險 CVE,將其提升為主要優先級:它將獲得專屬 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 註冊表 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": "你是一位正在為依賴項更新撰寫 GitHub PR 說明的開發者。給定一份正在更新的套件清單及其原始變更日誌內容,請以 Markdown 格式撰寫一份簡潔的 PR 內文。規則:對於每個套件,僅列出舊版本與新版本之間的變更。使用項目符號。使用 BREAKING 前綴標記重大變更。使用 SECURITY 前綴及 CVE ID 標記 CVE 修復。每個套件區塊最多保持 3-5 個項目符號。如果找不到某個套件的變更日誌,請寫上『無可用變更日誌。』。請勿使用 em 破折號。請勿使用以下詞彙:seamless、robust、leverage、transform、innovative。僅輸出 Markdown PR 內文,無需任何評論。"
}]
},
"contents": [{
"parts": [{
"text": "此處填入套件及變更日誌"
}]
}],
"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。每個主要更新套件一個專屬 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
主要更新會獲得標籤 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 |
|------------|----------|-----|
| 修補程式 | lodash 4.17.19→4.17.21, axios 0.21.1→0.21.4 | #42 |
| 次要 | express 4.17.1→4.18.2 | #43 |
| 主要 | react 17.0.2→18.2.0 | #44 |
已開啟的 PR:3 個
無變更日誌的套件:some-obscure-pkg(註冊表中無 GitHub 儲存庫)
安裝失敗:無
下一步:在合併之前,請個別審查主要更新的 PR。