Robot de mise à jour des dépendances
Analyse les packages obsolètes. Exécute un audit de sécurité. Récupère les changelogs. Résume les breaking changes. Ouvre une PR par groupe de risque.
Règle critique : Ne mettre à jour que les packages que la commande outdated du gestionnaire de packages signale effectivement. Ne jamais deviner ni inventer de numéros de version. Si un changelog ne peut pas être récupéré, noter l'absence plutôt que d'inventer du contenu.
Étape 1 : Vérification de la configuration
echo "GEMINI_API_KEY: ${GEMINI_API_KEY:+défini}"
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-non défini, récupération des changelogs limitée à 60/heure}"
gh auth status 2>/dev/null | head -1 || echo "gh : non authentifié"
Si GEMINI_API_KEY est manquant : Arrêter. Dire à l'utilisateur : « GEMINI_API_KEY est requis. Obtenez-la sur aistudio.google.com. Ajoutez-la à votre fichier .env. »
Si gh n'est pas authentifié : Arrêter. Dire à l'utilisateur : « GitHub CLI doit être authentifié. Exécutez : gh auth login »
Détecter le(s) gestionnaire(s) de packages :
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"
Si plusieurs sont trouvés, demander : « [liste] trouvé. Lequel dois-je analyser ? (all / npm / pip / cargo / go / ruby) »
Étape 2 : Détecter les packages obsolètes
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
# Si cargo-outdated n'est pas installé : cargo install cargo-outdated
Modules 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] + '\"}')
"
Si tout est vide : « Tous les packages sont à jour. » Arrêter.
Indiquer le compte avant de continuer : « X packages obsolètes trouvés. »
Étape 3 : Classer par niveau de risque
Analyser le saut de version (actuelle → dernière) :
- MAJEUR : premier chiffre modifié (1.x.x → 2.x.x)
- MINEUR : deuxième chiffre modifié (1.2.x → 1.3.x)
- PATCH : troisième chiffre modifié (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'
"
Indiquer la répartition : « Patch : X packages. Mineur : Y packages. Majeur : Z packages. »
Étape 4 : Audit de sécurité
Executer une analyse CVE avant de créer des PR. Cela détermine l'urgence.
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 \"voir npm audit\"}')
" 2>/dev/null || echo "Aucune vulnérabilité trouvée ou npm audit non disponible"
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 non installé. Exécutez : pip install pip-audit"
Cargo :
cargo audit 2>/dev/null | grep -E "^(ID|Package|Severity|URL)" | head -30 \
|| echo "cargo-audit non installé. Exécutez : cargo install cargo-audit"
Règle d'escalade : Si une mise à jour PATCH ou MINEUR a une CVE Critique ou Élevée, la promouvoir au niveau MAJEUR : elle obtient sa propre PR et les détails de la CVE sont inclus dans le corps de la PR.
Signaler les résultats de sécurité avant de continuer :
Audit de sécurité : [N] vulnérabilités trouvées
[CRITIQUE] lodash 4.17.19 : Prototype Pollution (CVE-2021-23337)
[ÉLEVÉ] axios 0.21.1 : Server-Side Request Forgery (CVE-2021-3749)
Si aucune vulnérabilité : « Audit de sécurité : propre. »
Étape 5 : Récupérer les changelogs
Pour chaque package, essayer les sources dans l'ordre. S'arrêter à la première qui renvoie du contenu.
Source 1 : API GitHub Releases
Obtenir l'URL du dépôt depuis le registre :
# 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',''))"
Récupérer les 5 dernières versions :
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)]"
Ne conserver que les versions comprises entre la version actuelle et la dernière.
Source 2 : README du registre npm (solution de repli)
curl -s "https://registry.npmjs.org/{PACKAGE}" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('readme','')[:3000])"
Source 3 : Description PyPI (dernier recours pour pip)
curl -s "https://pypi.org/pypi/{PACKAGE}/json" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('info',{}).get('description','')[:2000])"
Si aucune source ne renvoie de contenu : noter « Aucun changelog trouvé » et continuer.
Étape 6 : Résumer avec Gemini
Une requête par groupe de risque. Inclure les résultats de sécurité pour tout package affecté par une CVE :
cat > /tmp/deps-summary-request.json << 'ENDJSON'
{
"system_instruction": {
"parts": [{
"text": "Vous êtes un développeur qui rédige une description de PR GitHub pour une mise à jour de dépendances. À partir d'une liste de packages mis à jour et de leur contenu brut de changelog, rédigez un corps de PR concis en Markdown. Règles : Pour chaque package, listez uniquement ce qui a changé entre l'ANCIENNE version et la NOUVELLE version. Utilisez des points de liste. Signalez les breaking changes avec un préfixe BREAKING. Signalez les correctifs CVE avec un préfixe SECURITY et incluez l'ID CVE. Limitez chaque section de package à 3-5 points maximum. Si aucun changelog n'a été trouvé pour un package, écrivez 'Aucun changelog disponible'. N'utilisez pas de tirets cadratins. N'utilisez pas ces mots : seamless, robust, leverage, transform, innovative. Produisez uniquement le corps de la PR en Markdown, sans commentaire."
}]
},
"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'])"
Étape 7 : Créer les PR
Une PR par groupe de risque non vide. Une PR par package pour les mises à jour majeures (revue individuelle requise).
1. Créer la branche :
BRANCH="deps/{RISK}-updates-$(date +%Y%m%d)"
git checkout -b "$BRANCH"
2. Mettre à jour le fichier de package :
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 :
# Modifier le champ version dans Cargo.toml pour le package, puis :
cargo update {package}
Go :
go get {module}@{latest_version}
go mod tidy
Ruby :
bundle update {gem_name}
3. Commiter :
git add -A
git commit -m "chore(deps): update {RISK} dependencies $(date +%Y-%m-%d)"
4. Créer la 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
Les mises à jour majeures reçoivent le label dependencies,breaking-change. Les mises à jour corrigeant une CVE reçoivent le label dependencies,security.
Après chaque PR, revenir à main : git checkout main
Étape 8 : Mode diagnostic
Déclenchement : Si une commande d'installation de package échoue en cours d'exécution, entrer en mode diagnostic au lieu de s'arrêter.
Détecter le type d'échec :
| Schéma d'erreur | Cause probable | Correctif suggéré |
|---|---|---|
peer dep conflict | Incompatibilité de dépendance homologue | Afficher la paire conflictuelle, suggérer le flag --legacy-peer-deps ou une rétrogradation |
ERESOLVE | Conflit de résolution npm | Exécuter npm install --legacy-peer-deps pour le package concerné uniquement |
version not found | La version n'existe pas dans le registre | Vérifier le registre avec npm view {pkg} versions |
python requires | Incompatibilité de version Python | Noter la version Python requise, ignorer le package |
cargo E0463 | Incompatibilité d'édition Rust | Marquer pour révision manuelle |
Présenter un résumé du diagnostic :
Échec de l'installation pour {package} : {type d'erreur}
Cause probable : {explication}
Correctif suggéré : {commande ou action spécifique}
Packages restants : poursuite avec {N} qui ont réussi.
Ne pas arrêter toute l'exécution quand un package échoue. Continuer avec les packages qui réussissent.
Étape 9 : Résumé de sortie
## Résumé de la mise à jour des dépendances : [AAAA-MM-JJ]
### Sécurité
[CRITIQUE] lodash : CVE-2021-23337 corrigée dans 4.17.21 : PR #42
[ÉLEVÉ] axios : CVE-2021-3749 corrigée dans 0.21.4 : PR #42
| Niveau de risque | Packages | PR |
|-----------------|----------|-----|
| Patch | lodash 4.17.19→4.17.21, axios 0.21.1→0.21.4 | #42 |
| Mineur | express 4.17.1→4.18.2 | #43 |
| Majeur | react 17.0.2→18.2.0 | #44 |
PR ouvertes : 3
Packages sans changelog : some-obscure-pkg (aucun dépôt GitHub dans le registre)
Échecs d'installation : aucun
Action suivante : examiner les PR de mise à jour majeure individuellement avant de fusionner.