repowarden 4.10.2 → 4.11.0-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/bot +18 -1
- package/bin/bots +4 -3
- package/bin/garde +20 -3
- package/bin/migrer +4 -18
- package/bin/pr-checks +36 -4
- package/bin/run-list +9 -8
- package/bin/statut +48 -0
- package/bin/ticket +18 -3
- package/bin/tickets +2 -1
- package/ci/tickets/adopter.sh +3 -2
- package/ci/tickets/commun.sh +27 -2
- package/ci/tickets/issue.sh +16 -7
- package/ci/tickets/pr.sh +1 -1
- package/ci/tickets/push.sh +117 -4
- package/ci/tickets/registre.sh +12 -20
- package/ci/tickets/release.sh +19 -8
- package/ci/tickets.sh +6 -5
- package/hooks/commit-msg +15 -9
- package/hooks/lib/commentaires.sh +123 -0
- package/hooks/lib/common.sh +25 -3
- package/hooks/lib/i18n/en.sh +37 -4
- package/hooks/lib/i18n/fr.sh +37 -4
- package/hooks/lib/lang.sh +26 -2
- package/hooks/lib/secrets.sh +52 -0
- package/hooks/lib/tickets.sh +55 -0
- package/hooks/lib/ui.sh +12 -0
- package/hooks/pre-commit +17 -8
- package/hooks/pre-push +45 -3
- package/package.json +1 -1
package/hooks/lib/i18n/fr.sh
CHANGED
|
@@ -50,11 +50,13 @@ _msg_fr() {
|
|
|
50
50
|
cc.refs_prompt_ticket) _M="Références (« - » pour aucune) [%s] : " ;;
|
|
51
51
|
cc.refs_prompt) _M="Références (ex. %s ; Entrée pour aucune) : " ;;
|
|
52
52
|
tk.cli.no_ticket) _M="Branche %s sans ticket : ce travail ne sera relié à aucun ticket (non bloquant)." ;;
|
|
53
|
+
tk.cli.explain) _M="Ticket : issue GitHub d'où part chaque modification (branche type/<n°>-sujet, PR qui la cite) ; son cycle : https://simdevwarden.github.io/repowarden/tickets/" ;;
|
|
53
54
|
tk.cli.propose) _M="Partir d'un ticket : repowarden ticket nouveau \"<titre>\"." ;;
|
|
54
55
|
tk.cli.propose_cc) _M="Partir d'un ticket : repowarden ticket nouveau \"<titre>\" (ou réponds oui à git cc)." ;;
|
|
55
56
|
tk.cli.silence) _M="Projet sans suivi de tickets ? git config repowarden.skip tickets pour ne plus le voir." ;;
|
|
56
57
|
tk.cli.fetch) _M="Branche du ticket #%s…" ;;
|
|
57
58
|
tk.cli.switched) _M="Sur la branche %s (ticket #%s)." ;;
|
|
59
|
+
tk.cli.unlinked) _M="Branche %s créée avant la validation : GitHub ne la lie pas au ticket #%s après coup, son lien est inscrit sur le ticket." ;;
|
|
58
60
|
tk.cli.created_branch) _M="Branche %s créée depuis %s, liée au ticket #%s." ;;
|
|
59
61
|
tk.cli.not_validated) _M="Ticket #%s pas encore validé : sa branche sera créée automatiquement à la validation (étiquette « %s »)." ;;
|
|
60
62
|
tk.cli.closed) _M="Ticket #%s fermé : pas de branche à créer." ;;
|
|
@@ -62,6 +64,7 @@ _msg_fr() {
|
|
|
62
64
|
tk.cli.issue_created) _M="Ticket #%s créé, à valider. Sa branche sera créée à la validation ; ensuite : repowarden ticket %s" ;;
|
|
63
65
|
tk.cli.issue_created_branch) _M="Ticket #%s créé." ;;
|
|
64
66
|
tk.cli.title_required) _M="Titre du ticket obligatoire : repowarden ticket nouveau \"<titre>\"" ;;
|
|
67
|
+
tk.cli.bad_type) _M="Type de ticket inconnu : %s (types : %s)" ;;
|
|
65
68
|
tk.cli.current) _M="Branche %s : ticket #%s." ;;
|
|
66
69
|
tk.cli.no_ticket_here) _M="Aucun ticket pour cette branche." ;;
|
|
67
70
|
cc.ticket_offer) _M="Aucun ticket pour cette branche. En créer un maintenant (titre : la description) ? [o/N] : " ;;
|
|
@@ -109,7 +112,6 @@ _msg_fr() {
|
|
|
109
112
|
# Hooks et plugins de langage
|
|
110
113
|
hook.pre_commit.1) _M="Commit refusé : nom de branche non conforme." ;;
|
|
111
114
|
hook.pre_commit.2) _M="Commit direct sur '%s' interdit. Crée une branche : git switch -c feat/ma-feature" ;;
|
|
112
|
-
hook.pre_commit.3) _M=" (désactiver pour ce dépôt : git config repowarden.skip protect-branch)" ;;
|
|
113
115
|
hook.pre_commit.4) _M="Détection de secrets (gitleaks)" ;;
|
|
114
116
|
hook.pre_commit.5) _M="Secret détecté : commit refusé." ;;
|
|
115
117
|
hook.secrets.where) _M=" %s:%s (%s)" ;;
|
|
@@ -120,7 +122,9 @@ _msg_fr() {
|
|
|
120
122
|
hook.secrets.todo_3) _M=" 3. Clé qui a pu fuir ailleurs (déjà poussée, partagée) : la révoquer chez son fournisseur." ;;
|
|
121
123
|
hook.secrets.todo_4) _M=" Faux positif : ajouter son empreinte à .gitleaksignore :" ;;
|
|
122
124
|
hook.secrets.failed) _M="gitleaks en échec (code %s) : commit refusé, détection de secrets non faite." ;;
|
|
123
|
-
hook.pre_commit.6) _M="gitleaks absent,
|
|
125
|
+
hook.pre_commit.6) _M="gitleaks absent : secrets non vérifiés avant le commit, la CI (vérification secrets) fera le contrôle. Installation : %s" ;;
|
|
126
|
+
hook.sensitive.found) _M="Fichier sensible ni suivi ni ignoré : un git add . le publierait. Lignes à ajouter à .gitignore :" ;;
|
|
127
|
+
hook.sensitive.hint) _M=" (noms comparés, contenu jamais lu ; motifs : repowarden.sensitiveFiles ; désactiver : git config repowarden.skip sensitive-files)" ;;
|
|
124
128
|
hook.pre_commit.7) _M="Pre-commit OK" ;;
|
|
125
129
|
hook.commit_msg.1) _M="Première ligne trop longue (%s > 72 caractères)." ;;
|
|
126
130
|
hook.commit_msg.type_unknown) _M="Type « %s » inconnu. Voulais-tu : %s" ;;
|
|
@@ -128,13 +132,16 @@ _msg_fr() {
|
|
|
128
132
|
hook.commit_msg.desc_empty) _M="Description manquante après « %s » : décris le changement à l'impératif." ;;
|
|
129
133
|
hook.commit_msg.kept) _M=" Ton message n'est pas perdu : git commit -e -F .git/COMMIT_EDITMSG" ;;
|
|
130
134
|
hook.commit_msg.dot) _M="Point final retiré de la description (même règle que git cc)." ;;
|
|
135
|
+
hook.commit_msg.detached) _M="👉 HEAD détachée (bisect, essai) : aucun préfixe ne vient d'une branche, écris l'en-tête complet (ex. fix: corrige le calcul)." ;;
|
|
131
136
|
hook.pre_push.1) _M="Push refusé : nom de branche non conforme." ;;
|
|
132
137
|
hook.pre_push.2) _M="Push direct refusé sur '%s' : elle n'évolue que par Pull Request." ;;
|
|
133
138
|
hook.pre_push.3) _M=" Crée une branche : git switch -c feat/ma-feature, puis ouvre une PR." ;;
|
|
134
|
-
hook.pre_push.4) _M=" (désactiver pour ce dépôt : git config repowarden.skip protect-branch)" ;;
|
|
135
139
|
hook.pre_push.5) _M="%s : aucun fichier modifié, rien à tester." ;;
|
|
136
140
|
hook.pre_push.6) _M="Rien à tester (suppression de branche uniquement)." ;;
|
|
137
141
|
hook.pre_push.7) _M="Pre-push OK" ;;
|
|
142
|
+
hook.pre_push.msg) _M="Push refusé : message de commit non conforme (même règle que la CI)." ;;
|
|
143
|
+
hook.pre_push.msg_fix) _M=" Corriger : git rebase -i %s (reword), puis relancer le push." ;;
|
|
144
|
+
hook.pre_push.nothing) _M="Rien à pousser." ;;
|
|
138
145
|
hook.pre_push.workflow_scope) _M="Jeton gh sans le scope « workflow » : GitHub refusera un push touchant .github/workflows/. L'autoriser (puis se reconnecter) : gh auth refresh -h github.com -s workflow" ;;
|
|
139
146
|
hook.pre_push.lint.missing) _M="%s absent, %s non vérifié (installation : %s)." ;;
|
|
140
147
|
hook.pre_push.lint.run) _M="%s : %s" ;;
|
|
@@ -238,7 +245,7 @@ _msg_fr() {
|
|
|
238
245
|
help.code-mort) _M="Usage : repowarden code-mort [base]\n\nCode mort introduit par ta branche, comme en CI : seules les lignes ajoutées\nou modifiées depuis la base comptent (commits, modifications en cours,\nnouveaux fichiers). Défaut : point de départ de la branche depuis origin/main.\nVoir docs/code-mort.md : ce qui est prouvé (bloquant) et ce qui est candidat." ;;
|
|
239
246
|
help.ticket) _M="Usage : repowarden ticket <n> branche du ticket (créée, liée, si besoin)\n repowarden ticket ticket de la branche courante\n repowarden ticket nouveau \"<titre>\" [--type fix] [--description \"…\"]\n repowarden ticket comment <n> \"<texte>\" commentaire sur le ticket\n repowarden ticket label <n> <label> [--remove] labels du ticket\n\nLe ticket d'abord : prendre <n> l'assigne à toi et place sur sa branche\n(créée depuis develop au besoin) ; nouveau crée le ticket (« à valider » si\nle suivi est géré par repowarden). Demande gh connecté (https://cli.github.com)." ;;
|
|
240
247
|
help.tickets) _M="Usage : repowarden tickets init [--repo propriétaire/nom] [--sans-validation-auto]\n repowarden tickets sync\n\nPrépare le suivi des tickets d'un dépôt GitHub (relançable sans risque) :\nétiquettes (« validé », statuts et types, préfixe des branches), modèle de\nticket, workflow ; tickets existants rangés ; branches des PR inscrites sur\nleurs tickets. sync : branches locales des tickets qui me sont assignés\n(automatique avec ticketSync = auto, défaut manual)." ;;
|
|
241
|
-
help.pr) _M="Usage : repowarden pr label <n> <label> [--remove]\n repowarden pr checks <n>\n\nlabel ajoute ou retire un label sur la PR #<n> ; checks
|
|
248
|
+
help.pr) _M="Usage : repowarden pr label <n> <label> [--remove]\n repowarden pr checks <n>\n\nlabel ajoute ou retire un label sur la PR #<n> ; checks liste ses jobs en\néchec (avec leur lien) et en attente, puis le verdict ; code 1 si un job échoue.\nExemples :\n repowarden pr label 123 \"ready-for-review\"\n repowarden pr label 123 \"needs-changes\" --remove\n repowarden pr checks 123" ;;
|
|
242
249
|
help.run) _M="Usage : repowarden run rerun <run-id|--failed|--workflow <nom>>\n repowarden run list [--workflow <nom>] [--limit N]\n\nrerun relance un workflow GitHub Actions : un run par son id, le dernier\néchoué (--failed) ou le dernier d'un workflow (--workflow) ; list affiche\nles runs récents (--limit, défaut 20).\nExemples :\n repowarden run rerun --failed\n repowarden run list --workflow \"CI\" --limit 10" ;;
|
|
243
250
|
help.proteger) _M="Usage : repowarden proteger [--repo propriétaire/nom] [--checks \"repowarden,tests (ubuntu-latest)\"]\n [--relecteurs N] [--codeowners] [--environnement NOM] [--approbateurs \"login …\"]\n [--sans-tags] [--dry-run] [--verifier]\n\nProtège le dépôt GitHub courant d'après .repowarden.conf (relançable sans\nrisque) : PR obligatoire, vérifications exigées, ni suppression ni push forcé\n(ruleset « repowarden »), merge squash, tags v* réservés aux workflows,\nvalidation humaine optionnelle. --verifier compare les règles en ligne sans\nrien modifier. Réglages équivalents dans .repowarden.conf. Demande gh et les\ndroits d'administration du dépôt." ;;
|
|
244
251
|
help.migrer) _M="Usage : repowarden migrer [--pr] [--hooks]\n\nPassage de repogarde à repowarden dans le projet courant\n(docs/migration-v4.md) : configuration (.repowarden.conf, .repowarden/),\nworkflows (uses: vers le dépôt repowarden et sa version majeure, anciens\nnoms), puis la liste de ce qui reste à faire à la main. Relançable : rien\nn'est fait deux fois.\n --pr sur la branche chore/migration-repowarden-vN : commit, push et PR\n --hooks déplace aussi les hooks lefthook résiduels dans .git/hooks-lefthook/" ;;
|
|
@@ -288,6 +295,11 @@ _msg_fr() {
|
|
|
288
295
|
ci.fix_pr.2) _M="PR #%s reciblée : %s → %s (flux %s)" ;;
|
|
289
296
|
ci.fix_pr.3) _M="Titre conforme : %s" ;;
|
|
290
297
|
ci.fix_pr.4) _M="Titre « %s » remplacé par « %s »" ;;
|
|
298
|
+
ci.verrous_pip.compile) _M="Verrou %s : commande de son en-tête relancée" ;;
|
|
299
|
+
ci.verrous_pip.refused) _M="Verrou %s ignoré : la sortie de la commande de son en-tête n'est pas ce fichier" ;;
|
|
300
|
+
ci.verrous_pip.compliant) _M="Verrous pip conformes à leur en-tête : aucun commit" ;;
|
|
301
|
+
ci.verrous_pip.committed) _M="Verrous régénérés et commités : %s" ;;
|
|
302
|
+
ci.verrous_pip.pushed) _M="Commit poussé sur %s" ;;
|
|
291
303
|
ci.version.1) _M="version annoncée par la PR de livraison" ;;
|
|
292
304
|
ci.version.2) _M="Aucun feat, fix, perf ni changement incompatible depuis %s : pas de version." ;;
|
|
293
305
|
ci.version.3) _M="première version" ;;
|
|
@@ -421,6 +433,12 @@ _msg_fr() {
|
|
|
421
433
|
dc.knip_file) _M="fichier jamais importé" ;;
|
|
422
434
|
dc.knip_export) _M="export jamais importé :" ;;
|
|
423
435
|
dc.knip_dep) _M="dépendance jamais importée :" ;;
|
|
436
|
+
# Fonctions ajoutées sans commentaire
|
|
437
|
+
cm.section) _M="Fonctions sans commentaire (nouveau code uniquement)" ;;
|
|
438
|
+
cm.no_base) _M="Pas de base : contrôle des commentaires ignoré." ;;
|
|
439
|
+
cm.missing) _M="%s : fonction %s sans commentaire juste au-dessus" ;;
|
|
440
|
+
cm.summary) _M="%s fonction(s) ajoutée(s) sans commentaire : une à deux lignes juste au-dessus, qui disent ce qu'elle fait et pourquoi (avertissement, non bloquant)." ;;
|
|
441
|
+
cm.none) _M="Chaque fonction ajoutée est commentée." ;;
|
|
424
442
|
ci.section.commits) _M="Messages de commit (Conventional Commits)" ;;
|
|
425
443
|
ci.section.branch) _M="Nom de branche" ;;
|
|
426
444
|
ci.section.secrets) _M="Secrets (gitleaks)" ;;
|
|
@@ -487,6 +505,10 @@ _msg_fr() {
|
|
|
487
505
|
st.repo_version_ok) _M="Version demandée par le projet (%s) : respectée" ;;
|
|
488
506
|
st.repo_forge) _M="Plateforme : %s" ;;
|
|
489
507
|
st.repo_rules) _M="Règles GitHub à jour ? repowarden proteger --verifier (lecture seule)" ;;
|
|
508
|
+
st.wf_owner) _M="%s:%s : repowarden appelé depuis un autre dépôt que le dépôt officiel (%s) ; attendu : %s" ;;
|
|
509
|
+
st.wf_branch) _M="%s:%s : repowarden appelé sur une branche (@%s), cassé dès qu'elle disparaît ; attendu : %s" ;;
|
|
510
|
+
st.wf_old) _M="%s:%s : repowarden appelé dans une majeure dépassée (@%s) ; attendu : %s" ;;
|
|
511
|
+
st.wf_fix) _M="corriger les lignes uses: signalées ci-dessus" ;;
|
|
490
512
|
st.tools) _M="Outils présents : %s · absents : %s (selon les langages des projets)" ;;
|
|
491
513
|
st.next) _M="Étape suivante :" ;;
|
|
492
514
|
st.ready) _M="Tout est prêt : git cc pour commiter, repowarden code-mort sur une branche." ;;
|
|
@@ -542,6 +564,7 @@ _msg_fr() {
|
|
|
542
564
|
bot.ignored) _M="ignoré (bot = off)" ;;
|
|
543
565
|
bot.mode_invalid) _M="bot = %s : auto, pr ou off attendu" ;;
|
|
544
566
|
bot.clone_failed) _M="clone impossible : %s" ;;
|
|
567
|
+
bot.email_fallback) _M="Utilisateur bot %s introuvable : commits signés %s, rattachés à aucun compte (REPOWARDEN_BOT_EMAIL pour fixer l'adresse)." ;;
|
|
545
568
|
bot.prot_ok) _M="à jour" ;;
|
|
546
569
|
bot.prot_stale) _M="⚠ périmée : repowarden proteger" ;;
|
|
547
570
|
bot.prot_applied) _M="✔ appliquée" ;;
|
|
@@ -634,6 +657,7 @@ _msg_fr() {
|
|
|
634
657
|
tk.pr_backmerge_empty) _M="PR fermée automatiquement : \`%s\` n'apporte aucun contenu à \`%s\`. Le retour est fait automatiquement après une release, seulement quand il y a quelque chose à rapporter." ;;
|
|
635
658
|
tk.pr_delivery_manual) _M="PR fermée automatiquement : la livraison \`%s\` → \`%s\` est ouverte par le workflow de release ; une PR ouverte à la main n'aurait jamais d'approbation de déploiement. Lancer *Actions → release → Run workflow* sur \`%s\` (ou \`gh workflow run release.yml --ref %s\`), puis **Approve and deploy** sur le run." ;;
|
|
636
659
|
tk.branch_registered) _M="Branche du ticket : \`%s\`." ;;
|
|
660
|
+
tk.branch_unlinked) _M="Branche du ticket : [\`%s\`](%s). Créée avant la validation du ticket : GitHub ne lie pas une branche existante, elle n'apparaît donc pas dans « Development » ; ce commentaire en garde le lien." ;;
|
|
637
661
|
tk.branch_second) _M="Seconde branche pour ce ticket : \`%s\` (déjà : %s). Un ticket, une branche : regrouper le travail, ou ouvrir un ticket séparé." ;;
|
|
638
662
|
tk.adopted) _M="Tickets existants : %s rangé(s), %s branche(s) inscrite(s)." ;;
|
|
639
663
|
tk.init_adopt) _M="Tickets et branches existants" ;;
|
|
@@ -642,6 +666,8 @@ _msg_fr() {
|
|
|
642
666
|
tk.branch_kept) _M="Branche \`%s\` gardée : %s commit(s) absent(s) de la branche d'intégration, PR ouverte(s) : %s. À supprimer à la main si le travail est ailleurs." ;;
|
|
643
667
|
tk.draft_opened) _M="PR #%s ouverte en brouillon pour le ticket #%s" ;;
|
|
644
668
|
tk.draft_updated) _M="PR #%s (ticket #%s) : titre et description repris du ticket" ;;
|
|
669
|
+
tk.pr_additions) _M="Ajouts du ticket" ;;
|
|
670
|
+
tk.pr_design) _M="Conception (résumé)" ;;
|
|
645
671
|
tk.created_body) _M="Ticket créé automatiquement pour la PR #%s, qui n'en citait aucun. À valider par un mainteneur (étiquette « validé »)." ;;
|
|
646
672
|
tk.created) _M="Ticket #%s créé pour la PR #%s et relié à sa description : à valider par un mainteneur." ;;
|
|
647
673
|
tk.init_labels) _M="Étiquettes (statuts et validation)" ;;
|
|
@@ -655,6 +681,8 @@ _msg_fr() {
|
|
|
655
681
|
tk.init_next_wired) _M="Ensuite : committer ces fichiers ; exiger la vérification « ticket » (repowarden proteger --checks \"…,ticket\"). Le job tickets de release.yml est déjà branché." ;;
|
|
656
682
|
tk.prerelease) _M="Disponible en préversion **%s** : à tester avant la production." ;;
|
|
657
683
|
tk.released) _M="Publié dans **%s**." ;;
|
|
684
|
+
tk.footer) _M="Message de l'App GitHub de ce dépôt ([doc repowarden](https://simdevwarden.github.io/repowarden/industrialisation/#github-app-des-workflows)) : [%s](https://github.com/apps/%s)" ;;
|
|
685
|
+
tk.delivered) _M="Livré sur **%s**, sans nouvelle version (commit %s)." ;;
|
|
658
686
|
# Ticket comment
|
|
659
687
|
tk.comment.adding) _M="Ajout d'un commentaire sur le ticket #%s..." ;;
|
|
660
688
|
tk.comment.added) _M="Commentaire ajouté au ticket #%s." ;;
|
|
@@ -680,6 +708,11 @@ _msg_fr() {
|
|
|
680
708
|
run.listing.workflow) _M="Liste des runs du workflow « %s »..." ;;
|
|
681
709
|
# PR checks
|
|
682
710
|
pr.checks.checking) _M="Vérification des checks de la PR #%s..." ;;
|
|
711
|
+
pr.checks.failed) _M="En échec : %s %s" ;;
|
|
712
|
+
pr.checks.pending) _M="En attente : %s" ;;
|
|
713
|
+
pr.checks.verdict) _M="PR #%s : %s réussi(s), %s en échec, %s en attente." ;;
|
|
714
|
+
pr.checks.green) _M="Tous les checks de la PR #%s sont verts." ;;
|
|
715
|
+
pr.checks.none) _M="Aucun check sur la PR #%s." ;;
|
|
683
716
|
# Garde-fou des agents IA (repowarden garde, repowarden agents)
|
|
684
717
|
garde.format) _M="Format inconnu « %s » : claude ou opencode." ;;
|
|
685
718
|
garde.no_jq) _M="repowarden garde : jq absent, action non vérifiée (installer jq : https://jqlang.org)." ;;
|
package/hooks/lib/lang.sh
CHANGED
|
@@ -54,6 +54,9 @@ done
|
|
|
54
54
|
# Détection de code mort (fonctions <plugin>_deadcode des plugins ci-dessus)
|
|
55
55
|
# shellcheck source=deadcode.sh
|
|
56
56
|
source "$(dirname "${BASH_SOURCE[0]}")/deadcode.sh"
|
|
57
|
+
# Fonctions ajoutées sans commentaire (pre-commit et CI)
|
|
58
|
+
# shellcheck source=commentaires.sh
|
|
59
|
+
source "$(dirname "${BASH_SOURCE[0]}")/commentaires.sh"
|
|
57
60
|
unset _plugin
|
|
58
61
|
|
|
59
62
|
# --- Détection ---------------------------------------------------------------
|
|
@@ -300,8 +303,11 @@ projects_for_files() {
|
|
|
300
303
|
}
|
|
301
304
|
|
|
302
305
|
# Lance les tests de chaque projet touché. Lecture des fichiers modifiés sur stdin.
|
|
306
|
+
# $1 = resume (pre-push) : sortie de chaque lanceur gardée de côté ; succès →
|
|
307
|
+
# sa dernière ligne seule (« 2 passed in 0.5s »), échec → sortie complète. Sans
|
|
308
|
+
# argument (CI) : sortie complète, le journal sert au diagnostic.
|
|
303
309
|
test_projects() {
|
|
304
|
-
local cmd dir p plugins key ran=0
|
|
310
|
+
local mode="${1:-}" cmd dir p plugins key ran=0 out="" line last rc
|
|
305
311
|
cmd="$(cfg test)"
|
|
306
312
|
if [ -n "$cmd" ]; then
|
|
307
313
|
# Fichiers modifiés transmis sur l'entrée standard : la commande peut
|
|
@@ -326,13 +332,31 @@ test_projects() {
|
|
|
326
332
|
ran=1
|
|
327
333
|
if [ "$dir" = . ]; then key="$p"; else key="$p ($dir)"; fi
|
|
328
334
|
info_t hook.lang.5 "$key"
|
|
329
|
-
if
|
|
335
|
+
if [ "$mode" != resume ]; then
|
|
336
|
+
if ! (cd "$dir" && "${p}_test") </dev/null; then
|
|
337
|
+
err_t hook.lang.6 "$key" >&2
|
|
338
|
+
return 1
|
|
339
|
+
fi
|
|
340
|
+
continue
|
|
341
|
+
fi
|
|
342
|
+
[ -n "$out" ] || out="$(mktemp)"
|
|
343
|
+
rc=0
|
|
344
|
+
(cd "$dir" && "${p}_test") </dev/null >"$out" 2>&1 || rc=$?
|
|
345
|
+
if [ "$rc" -ne 0 ]; then
|
|
346
|
+
cat "$out"
|
|
347
|
+
rm -f "$out"
|
|
330
348
|
err_t hook.lang.6 "$key" >&2
|
|
331
349
|
return 1
|
|
332
350
|
fi
|
|
351
|
+
last=""
|
|
352
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
353
|
+
[ -n "${line//[[:space:]]/}" ] && last="$line"
|
|
354
|
+
done <"$out"
|
|
355
|
+
[ -z "$last" ] || printf ' %s\n' "$last"
|
|
333
356
|
done
|
|
334
357
|
done < <(projects_for_files)
|
|
335
358
|
|
|
359
|
+
[ -z "$out" ] || rm -f "$out"
|
|
336
360
|
[ "$ran" = 1 ] || info_t hook.lang.7
|
|
337
361
|
return 0
|
|
338
362
|
}
|
package/hooks/lib/secrets.sh
CHANGED
|
@@ -47,3 +47,55 @@ secrets_report() {
|
|
|
47
47
|
printf '%s' "$empreintes"
|
|
48
48
|
fi
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
# REPLY = commande d'installation de gitleaks pour ce système (bash le connaît
|
|
52
|
+
# sans sous-processus)
|
|
53
|
+
gitleaks_install_r() {
|
|
54
|
+
case "${OSTYPE:-}" in
|
|
55
|
+
darwin*) REPLY="brew install gitleaks" ;;
|
|
56
|
+
msys* | cygwin* | win*) REPLY="winget install gitleaks" ;;
|
|
57
|
+
*) REPLY="https://github.com/gitleaks/gitleaks#installing" ;;
|
|
58
|
+
esac
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Noms de fichiers sensibles par défaut (réglage repowarden.sensitiveFiles)
|
|
62
|
+
SENSITIVE_FILES_DEFAULT='.env .env.* *token* *secret* *.pem *.key id_rsa*'
|
|
63
|
+
|
|
64
|
+
# REPLY = fichiers non suivis et non ignorés (.gitignore, .git/info/exclude,
|
|
65
|
+
# exclusion globale) au nom sensible, un par ligne depuis la racine : un
|
|
66
|
+
# « git add . » les publierait, et gitleaks ne les lit qu'une fois indexés.
|
|
67
|
+
# Seul le nom est comparé (sans casse) : le contenu n'est jamais lu.
|
|
68
|
+
# .env.example, modèle fait pour être versionné, n'est jamais signalé.
|
|
69
|
+
sensitive_files_r() {
|
|
70
|
+
local f nom m trouves="" nocase=""
|
|
71
|
+
local -a motifs=()
|
|
72
|
+
cfg_r sensitiveFiles "$SENSITIVE_FILES_DEFAULT"
|
|
73
|
+
# read -a : motifs découpés sans être développés sur les fichiers présents
|
|
74
|
+
read -r -a motifs <<<"${REPLY//,/ }"
|
|
75
|
+
shopt -q nocasematch && nocase=1
|
|
76
|
+
shopt -s nocasematch
|
|
77
|
+
while IFS= read -r -d '' f; do
|
|
78
|
+
nom="${f##*/}"
|
|
79
|
+
[[ "$nom" != .env.example ]] || continue
|
|
80
|
+
for m in ${motifs[@]+"${motifs[@]}"}; do
|
|
81
|
+
# shellcheck disable=SC2053 # $m est un motif
|
|
82
|
+
if [[ "$nom" == $m ]]; then
|
|
83
|
+
trouves="$trouves${trouves:+$'\n'}$f"
|
|
84
|
+
break
|
|
85
|
+
fi
|
|
86
|
+
done
|
|
87
|
+
done < <(git -C "$GIT_TOPLEVEL" ls-files -z --others --exclude-standard 2>/dev/null)
|
|
88
|
+
[ -n "$nocase" ] || shopt -u nocasematch
|
|
89
|
+
REPLY="$trouves"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Avertit, sans bloquer, des fichiers sensibles $1 (un par ligne, sortie de
|
|
93
|
+
# sensitive_files_r) avec les lignes à ajouter à .gitignore
|
|
94
|
+
sensitive_files_warn() {
|
|
95
|
+
local f
|
|
96
|
+
warn_t hook.sensitive.found
|
|
97
|
+
while IFS= read -r f; do
|
|
98
|
+
echo " $f"
|
|
99
|
+
done <<<"$1"
|
|
100
|
+
echo_t hook.sensitive.hint
|
|
101
|
+
}
|
package/hooks/lib/tickets.sh
CHANGED
|
@@ -12,6 +12,10 @@ S_ENCOURS="statut: en cours"
|
|
|
12
12
|
S_RELECTURE="statut: en relecture"
|
|
13
13
|
S_PREPROD="statut: préprod"
|
|
14
14
|
S_DONE="statut: done"
|
|
15
|
+
# Posée à l'ouverture : pas de validation d'office, un mainteneur décide
|
|
16
|
+
A_DECIDER="à décider"
|
|
17
|
+
# Types de ticket (étiquettes « type: … », préfixe de la branche)
|
|
18
|
+
TYPES_TICKET="feat fix docs refactor chore"
|
|
15
19
|
|
|
16
20
|
# Vrai si les étiquettes $1 (une par ligne) disent le travail terminé : mergé
|
|
17
21
|
# (préprod) ou en production (done). Un tel ticket n'a plus de branche à créer.
|
|
@@ -108,6 +112,49 @@ ticket_type_r() {
|
|
|
108
112
|
done <<<"$1"
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
# Registre des branches d'un ticket : repère invisible dans les commentaires
|
|
116
|
+
# du bot. GitHub ne lie une branche au ticket qu'à sa création, et une branche
|
|
117
|
+
# liée supprimée y perd son nom : le registre garde le nom de chaque branche.
|
|
118
|
+
REPERE="repowarden:branche"
|
|
119
|
+
|
|
120
|
+
repere() { printf '<!-- %s %s -->' "$REPERE" "$1"; }
|
|
121
|
+
|
|
122
|
+
# REPLY = branches inscrites sur le ticket $1 (séparées par des espaces)
|
|
123
|
+
branches_inscrites_r() {
|
|
124
|
+
local corps
|
|
125
|
+
corps="$(gh issue view "$1" --json comments -q '.comments[].body' 2>/dev/null || true)"
|
|
126
|
+
REPLY="$(grep -oE "<!-- $REPERE [^ ]+ -->" <<<"$corps" | awk '{ printf "%s ", $3 }' || true)"
|
|
127
|
+
REPLY="${REPLY% }"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# REPLY = adresse de la branche $2 si elle n'est pas liée au ticket $1, vide si
|
|
131
|
+
# elle l'est ou si GitHub ne répond pas (dans le doute, on ne signale rien)
|
|
132
|
+
ticket_unlinked_branch_url_r() {
|
|
133
|
+
local lignes url
|
|
134
|
+
REPLY=""
|
|
135
|
+
# shellcheck disable=SC2016 # variables GraphQL, pas du shell
|
|
136
|
+
lignes="$(gh api graphql -F owner='{owner}' -F repo='{repo}' -F n="$1" -f query='query($owner: String!, $repo: String!, $n: Int!) {
|
|
137
|
+
repository(owner: $owner, name: $repo) { url issue(number: $n) { linkedBranches(first: 50) { nodes { ref { name } } } } }
|
|
138
|
+
}' -q '.data.repository.url, .data.repository.issue.linkedBranches.nodes[].ref.name' 2>/dev/null)" || return 0
|
|
139
|
+
url="${lignes%%$'\n'*}"
|
|
140
|
+
[ -n "$url" ] || return 0
|
|
141
|
+
grep -qxF "$2" <<<"$lignes" || REPLY="$url/tree/$2"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Branche $2 du ticket $1 créée avant sa validation : GitHub ne lie pas une
|
|
145
|
+
# branche existante (createLinkedBranch répond null), son lien est donc inscrit
|
|
146
|
+
# sur le ticket, une seule fois. Vrai si le commentaire a été posté.
|
|
147
|
+
ticket_note_unlinked_branch() {
|
|
148
|
+
local n="$1" b="$2" url
|
|
149
|
+
ticket_unlinked_branch_url_r "$n" "$b"
|
|
150
|
+
url="$REPLY"
|
|
151
|
+
[ -n "$url" ] || return 1
|
|
152
|
+
branches_inscrites_r "$n"
|
|
153
|
+
[[ " $REPLY " != *" $b "* ]] || return 1
|
|
154
|
+
_tr tk.branch_unlinked "$b" "$url"
|
|
155
|
+
gh issue comment "$n" --body "$_T"$'\n\n'"$(repere "$b")" >/dev/null
|
|
156
|
+
}
|
|
157
|
+
|
|
111
158
|
# Vrai si la branche $1 est une branche de travail : ni protégée, ni une
|
|
112
159
|
# exception (main, develop, branches des bots…)
|
|
113
160
|
work_branch() {
|
|
@@ -164,13 +211,21 @@ ticket_auto_create() {
|
|
|
164
211
|
# Branche de travail sans ticket : avertissement, jamais bloquant. Création
|
|
165
212
|
# proposée seulement si repowarden gère la vie des tickets du projet (et que
|
|
166
213
|
# gh est là pour la faire) ; sinon, comment faire taire l'avertissement.
|
|
214
|
+
# Projet sans suivi géré : une fois par branche seulement (le répéter à chaque
|
|
215
|
+
# commit n'apprend rien), mémorisé dans la config locale de la branche, que git
|
|
216
|
+
# efface avec elle.
|
|
167
217
|
ticket_hint() {
|
|
168
218
|
local branch="$1"
|
|
169
219
|
skipped tickets && return 0
|
|
170
220
|
work_branch "$branch" || return 0
|
|
171
221
|
branch_ticket_r "$branch"
|
|
172
222
|
[ -z "$REPLY" ] || return 0
|
|
223
|
+
if ! tickets_managed; then
|
|
224
|
+
[ "$(git config --get "branch.$branch.repowardenTicketWarned" 2>/dev/null)" != true ] || return 0
|
|
225
|
+
git config "branch.$branch.repowardenTicketWarned" true 2>/dev/null || true
|
|
226
|
+
fi
|
|
173
227
|
attention_t tk.cli.no_ticket "$branch"
|
|
228
|
+
dim_t tk.cli.explain
|
|
174
229
|
if tickets_managed && has gh; then
|
|
175
230
|
# git cc ne propose de créer le ticket qu'avec ticketAutoCreate
|
|
176
231
|
if ticket_auto_create; then dim_t tk.cli.propose_cc; else dim_t tk.cli.propose; fi
|
package/hooks/lib/ui.sh
CHANGED
|
@@ -56,5 +56,17 @@ repowarden_version_r() {
|
|
|
56
56
|
fi
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
# REPLY = version majeure dont le tag flottant (vN) est publié, pour la
|
|
60
|
+
# version $1 de repowarden : sa majeure ; préversion d'une nouvelle majeure
|
|
61
|
+
# (X.0.0-next) : X-1, vX n'existant pas encore ; 0.0.0-dev (clone sans
|
|
62
|
+
# tags) : vide. REPOWARDEN_MAJEURE l'impose (tests, clone sans tags).
|
|
63
|
+
repowarden_majeure_r() {
|
|
64
|
+
if [ -n "${REPOWARDEN_MAJEURE:-}" ]; then REPLY="$REPOWARDEN_MAJEURE"; return; fi
|
|
65
|
+
if [ -z "$1" ] || [ "$1" = 0.0.0-dev ]; then REPLY=""; return; fi
|
|
66
|
+
REPLY="${1%%.*}"
|
|
67
|
+
REPLY="${REPLY%%-*}"
|
|
68
|
+
[[ "$1" != *.0.0-* ]] || REPLY=$((REPLY - 1))
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
# En-tête de marque
|
|
60
72
|
brand() { echo "${UI_C}${UI_B}◆ repowarden${UI_N}${UI_D}${1:+ · $1}${UI_N}"; }
|
package/hooks/pre-commit
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Hook pre-commit : bloque les commits directs sur main, détecte les secrets,
|
|
3
|
-
#
|
|
3
|
+
# formate les fichiers stagés selon le(s) langage(s) détecté(s), puis signale
|
|
4
|
+
# les fonctions ajoutées sans commentaire.
|
|
4
5
|
set -euo pipefail
|
|
5
6
|
|
|
6
7
|
source "$(dirname "$0")/lib/common.sh"
|
|
@@ -26,7 +27,6 @@ fi
|
|
|
26
27
|
PROTECTED="$(cfg protectedBranches "main master")"
|
|
27
28
|
if ! skipped protect-branch && [[ " $PROTECTED " == *" $BRANCH "* ]] && git rev-parse --verify -q HEAD >/dev/null; then
|
|
28
29
|
err_t hook.pre_commit.2 "$BRANCH" >&2
|
|
29
|
-
echo_t hook.pre_commit.3 >&2
|
|
30
30
|
exit 1
|
|
31
31
|
fi
|
|
32
32
|
|
|
@@ -55,15 +55,18 @@ if ! skipped secrets; then
|
|
|
55
55
|
fi
|
|
56
56
|
rm -f "$rapport" "$journal"
|
|
57
57
|
else
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
darwin*) warn_t hook.pre_commit.6 "brew install gitleaks" ;;
|
|
61
|
-
msys* | cygwin* | win*) warn_t hook.pre_commit.6 "winget install gitleaks" ;;
|
|
62
|
-
*) warn_t hook.pre_commit.6 "https://github.com/gitleaks/gitleaks#installing" ;;
|
|
63
|
-
esac
|
|
58
|
+
gitleaks_install_r
|
|
59
|
+
warn_t hook.pre_commit.6 "$REPLY"
|
|
64
60
|
fi
|
|
65
61
|
fi
|
|
66
62
|
|
|
63
|
+
# 2 bis. Fichier sensible ni suivi ni ignoré : un « git add . » le publierait
|
|
64
|
+
# (avertissement seulement, contenu jamais lu)
|
|
65
|
+
if ! skipped secrets && ! skipped sensitive-files; then
|
|
66
|
+
sensitive_files_r
|
|
67
|
+
[ -z "$REPLY" ] || sensitive_files_warn "$REPLY" >&2
|
|
68
|
+
fi
|
|
69
|
+
|
|
67
70
|
# 3. Formatage : chaque fichier stagé avec l'outil de son langage / projet
|
|
68
71
|
if ! skipped format; then
|
|
69
72
|
# Ne formater (et re-stager) que le contenu stagé, jamais le travail en cours
|
|
@@ -72,4 +75,10 @@ if ! skipped format; then
|
|
|
72
75
|
restore_unstaged
|
|
73
76
|
fi
|
|
74
77
|
|
|
78
|
+
# 4. Fonctions ajoutées sans commentaire juste au-dessus (avertissement seulement,
|
|
79
|
+
# après le formatage : les lignes signalées sont celles du commit)
|
|
80
|
+
if ! skipped comments && git rev-parse -q --verify HEAD >/dev/null; then
|
|
81
|
+
comments_check HEAD ""
|
|
82
|
+
fi
|
|
83
|
+
|
|
75
84
|
ok_t hook.pre_commit.7
|
package/hooks/pre-push
CHANGED
|
@@ -41,13 +41,49 @@ if ! skipped protect-branch; then
|
|
|
41
41
|
[[ "$remote_sha" =~ ^0+$ ]] && continue
|
|
42
42
|
err_t hook.pre_push.2 "$branch" >&2
|
|
43
43
|
echo_t hook.pre_push.3 >&2
|
|
44
|
-
echo_t hook.pre_push.4 >&2
|
|
45
44
|
exit 1
|
|
46
45
|
done <<<"$REFS"
|
|
47
46
|
fi
|
|
48
47
|
|
|
49
48
|
REMOTE="${1:-origin}"
|
|
50
49
|
|
|
50
|
+
# Messages des commits poussés : même règle et mêmes messages que la CI, pour
|
|
51
|
+
# un commit créé hors hooks (autre outil, rebase sans hooks, --no-verify).
|
|
52
|
+
# Seuls les commits absents du remote sont vus : l'historique déjà poussé
|
|
53
|
+
# n'est pas rejugé.
|
|
54
|
+
if ! skipped commit-msg; then
|
|
55
|
+
MSG_BAD=""
|
|
56
|
+
while read -r _ local_sha remote_ref remote_sha; do
|
|
57
|
+
[[ "$remote_ref" == refs/heads/* ]] || continue
|
|
58
|
+
[[ "$local_sha" =~ ^0+$ ]] && continue
|
|
59
|
+
if [[ ! "$remote_sha" =~ ^0+$ ]] && git cat-file -e "$remote_sha^{commit}" 2>/dev/null; then
|
|
60
|
+
plage=("$remote_sha..$local_sha")
|
|
61
|
+
else
|
|
62
|
+
plage=("$local_sha" --not --remotes="$REMOTE")
|
|
63
|
+
fi
|
|
64
|
+
# Séparateur 0x1f (non blanc) : un champ vide ne décale pas les suivants
|
|
65
|
+
while IFS=$'\x1f' read -r sha email subject; do
|
|
66
|
+
[ -n "$sha" ] || continue
|
|
67
|
+
commit_subject_problem_r "$subject" "$email"
|
|
68
|
+
[ -n "$REPLY" ] || continue
|
|
69
|
+
[ -n "$MSG_BAD" ] || err_t hook.pre_push.msg
|
|
70
|
+
# git log va du plus récent au plus ancien : MSG_BAD finit sur le
|
|
71
|
+
# plus ancien commit fautif, point de départ de la correction
|
|
72
|
+
MSG_BAD="$sha"
|
|
73
|
+
_tr "$REPLY" "${sha:0:7}" "$subject"
|
|
74
|
+
echo " $_T" >&2
|
|
75
|
+
done < <(git log --no-merges --format='%H%x1f%ae%x1f%s' "${plage[@]}")
|
|
76
|
+
done <<<"$REFS"
|
|
77
|
+
if [ -n "$MSG_BAD" ]; then
|
|
78
|
+
if git rev-parse -q --verify "$MSG_BAD^" >/dev/null; then
|
|
79
|
+
echo_t hook.pre_push.msg_fix "${MSG_BAD:0:7}^" >&2
|
|
80
|
+
else
|
|
81
|
+
echo_t hook.pre_push.msg_fix "--root" >&2
|
|
82
|
+
fi
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
51
87
|
# Fichiers modifiés par le push de <sha> : depuis l'état distant connu, ou pour
|
|
52
88
|
# une nouvelle branche depuis le dernier commit déjà présent sur le remote.
|
|
53
89
|
changed_files() {
|
|
@@ -177,7 +213,8 @@ fi
|
|
|
177
213
|
|
|
178
214
|
skipped tests && { info "Tests désactivés (repowarden.skip)."; exit 0; }
|
|
179
215
|
|
|
180
|
-
|
|
216
|
+
# Succès : une ligne de résumé par projet ; échec : sortie complète
|
|
217
|
+
run_tests_for() { test_projects resume <"$1"; }
|
|
181
218
|
|
|
182
219
|
TESTED=""
|
|
183
220
|
CHANGED="$(git rev-parse --absolute-git-dir)/repowarden-changed-files"
|
|
@@ -196,5 +233,10 @@ while read -r _ local_sha _ remote_sha; do
|
|
|
196
233
|
done <<<"$REFS"
|
|
197
234
|
|
|
198
235
|
rm -f "$CHANGED"
|
|
199
|
-
|
|
236
|
+
# Entrée vide : git n'a rien à envoyer (« Everything up-to-date »)
|
|
237
|
+
if [ -z "$REFS" ]; then
|
|
238
|
+
info_t hook.pre_push.nothing
|
|
239
|
+
elif [ -z "$TESTED" ]; then
|
|
240
|
+
info_t hook.pre_push.6
|
|
241
|
+
fi
|
|
200
242
|
ok_t hook.pre_push.7
|
package/package.json
CHANGED