repowarden 4.13.2-next.1 → 4.14.0-next.5
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/garde +142 -0
- package/ci/tickets/verification.sh +44 -0
- package/hooks/lib/i18n/en.sh +7 -1
- package/hooks/lib/i18n/fr.sh +7 -1
- package/package.json +1 -1
package/bin/garde
CHANGED
|
@@ -152,6 +152,142 @@ fichier_ignore() {
|
|
|
152
152
|
git -C "./$cdup" check-ignore -q -- "$prefixe$reste" 2>/dev/null
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
# Vrai si le nom $1 (relatif à la racine du dépôt) est la configuration d'un
|
|
156
|
+
# outil de vérification : la régler est une décision du mainteneur (#518)
|
|
157
|
+
config_outil() {
|
|
158
|
+
case "$1" in
|
|
159
|
+
.github/actionlint.yaml | .github/actionlint.yml | .shellcheckrc | \
|
|
160
|
+
.markdownlint* | .yamllint* | .gitleaks.toml | .gitleaksignore | \
|
|
161
|
+
.pre-commit-config.yaml | .repowarden.conf) return 0 ;;
|
|
162
|
+
esac
|
|
163
|
+
return 1
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# Vrai si le chemin $1 peut être une telle configuration : son nom seul le dit,
|
|
167
|
+
# avant de consulter le dépôt — un fichier ordinaire ne coûte aucune commande
|
|
168
|
+
config_outil_possible() {
|
|
169
|
+
local n="${1##*/}"
|
|
170
|
+
config_outil "$n" && return 0
|
|
171
|
+
config_outil ".github/$n"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# REPLY = chemin $1 relatif à la racine du dépôt : dans un sous-shell on se
|
|
175
|
+
# place dans son dossier existant, et on ajoute le reste au préfixe git ; hors
|
|
176
|
+
# dépôt, chaîne vide (les formes Windows C:/… et D:\… restent lues)
|
|
177
|
+
chemin_racine_r() {
|
|
178
|
+
local p="${1//\\//}" e prefixe reste
|
|
179
|
+
dossier_r "$p"
|
|
180
|
+
e="$REPLY"
|
|
181
|
+
REPLY="$(
|
|
182
|
+
cd "$e" 2>/dev/null || exit 0
|
|
183
|
+
prefixe="$(git rev-parse --show-prefix 2>/dev/null)" || exit 0
|
|
184
|
+
reste="${p#"$e"}"
|
|
185
|
+
reste="${reste#/}"
|
|
186
|
+
printf '%s%s' "$prefixe" "$reste"
|
|
187
|
+
)"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Refus si le fichier $1 (depuis le dossier $2) est la configuration d'un outil
|
|
191
|
+
# de vérification : réglage du mainteneur, en tout lieu d'un dépôt repowarden
|
|
192
|
+
verifier_config_cible() {
|
|
193
|
+
local p="${1%$'\r'}" d
|
|
194
|
+
case "$p" in /* | [A-Za-z]:[/\\]*) ;; *) p="$2/$p" ;; esac
|
|
195
|
+
config_outil_possible "$p" || return 0
|
|
196
|
+
dossier_r "$2"
|
|
197
|
+
d="$REPLY"
|
|
198
|
+
(
|
|
199
|
+
cd "$d" 2>/dev/null || exit 0
|
|
200
|
+
depot_repowarden || exit 0
|
|
201
|
+
cfg_r agentsConfig false
|
|
202
|
+
[ "$REPLY" != true ] || exit 0
|
|
203
|
+
chemin_racine_r "$p"
|
|
204
|
+
config_outil "$REPLY" || exit 0
|
|
205
|
+
refuser garde.config_verification "$REPLY"
|
|
206
|
+
) || exit $?
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# Cibles de redirection des mots $@ (commande de $dir) : écrire la configuration
|
|
210
|
+
# d'un outil de vérification s'y refuse, comme par un outil d'édition
|
|
211
|
+
verifier_redirections() {
|
|
212
|
+
local mot cible attendu=""
|
|
213
|
+
for mot in "$@"; do
|
|
214
|
+
if [ -n "$attendu" ]; then
|
|
215
|
+
attendu=""
|
|
216
|
+
cible="$mot"
|
|
217
|
+
else
|
|
218
|
+
# Après le chevron (> et >> y compris) : vide = cible mot suivant
|
|
219
|
+
cible="${mot#*>}"
|
|
220
|
+
[ "$cible" != "$mot" ] || continue
|
|
221
|
+
while [ "${cible#>}" != "$cible" ]; do cible="${cible#>}"; done
|
|
222
|
+
if [ -z "$cible" ]; then
|
|
223
|
+
attendu=1
|
|
224
|
+
continue
|
|
225
|
+
fi
|
|
226
|
+
fi
|
|
227
|
+
verifier_config_cible "$cible" "$dir"
|
|
228
|
+
done
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Cibles écrites par une commande shell (cp, mv, install, ln, tee, touch,
|
|
232
|
+
# truncate, rm, sed -i, perl -i) : la configuration d'un outil s'y refuse aussi
|
|
233
|
+
verifier_ecriture_fichiers() {
|
|
234
|
+
local m mot cible="" inplace="" vu="" n d i
|
|
235
|
+
m="${1//[\"\']/}"
|
|
236
|
+
m="${m##*/}"
|
|
237
|
+
m="${m##*\\}"
|
|
238
|
+
case "$m" in
|
|
239
|
+
*.exe | *.EXE | *.cmd | *.CMD | *.bat | *.BAT) m="${m%.*}" ;;
|
|
240
|
+
esac
|
|
241
|
+
case "$m" in
|
|
242
|
+
tee | touch | truncate | rm)
|
|
243
|
+
# Tous les arguments non option sont des fichiers écrits
|
|
244
|
+
for mot in "${@:2}"; do
|
|
245
|
+
case "$mot" in -*) continue ;; esac
|
|
246
|
+
verifier_config_cible "$mot" "$dir"
|
|
247
|
+
done
|
|
248
|
+
;;
|
|
249
|
+
cp | mv | install | ln)
|
|
250
|
+
# Le dernier argument non option est la cible ; si c'est un
|
|
251
|
+
# dossier existant (vu depuis $dir) ou finit par /, chaque source
|
|
252
|
+
# y est écrite : vérifier alors <cible>/<nom de la source>
|
|
253
|
+
n=0
|
|
254
|
+
for mot in "${@:2}"; do
|
|
255
|
+
case "$mot" in -*) continue ;; esac
|
|
256
|
+
n=$((n + 1))
|
|
257
|
+
cible="$mot"
|
|
258
|
+
done
|
|
259
|
+
[ "$n" -ne 0 ] || return 0
|
|
260
|
+
verifier_config_cible "$cible" "$dir"
|
|
261
|
+
d="$cible"
|
|
262
|
+
while [ "${d%/}" != "$d" ]; do d="${d%/}"; done
|
|
263
|
+
[ -n "$d" ] || d=/
|
|
264
|
+
case "$d" in /* | [A-Za-z]:[/\\]*) ;; *) d="$dir/$d" ;; esac
|
|
265
|
+
case "$cible" in */) ;; *) [ -d "$d" ] || return 0 ;; esac
|
|
266
|
+
i=0
|
|
267
|
+
for mot in "${@:2}"; do
|
|
268
|
+
case "$mot" in -*) continue ;; esac
|
|
269
|
+
i=$((i + 1))
|
|
270
|
+
[ "$i" -lt "$n" ] || break
|
|
271
|
+
verifier_config_cible "$d/${mot##*/}" "$dir"
|
|
272
|
+
done
|
|
273
|
+
;;
|
|
274
|
+
sed | perl)
|
|
275
|
+
# Avec -i (--in-place), les arguments après le script sont modifiés
|
|
276
|
+
for mot in "${@:2}"; do
|
|
277
|
+
case "$mot" in
|
|
278
|
+
--in-place | --in-place=* | -i* | -[!-]*i*) inplace=1 ;;
|
|
279
|
+
esac
|
|
280
|
+
done
|
|
281
|
+
[ -n "$inplace" ] || return 0
|
|
282
|
+
for mot in "${@:2}"; do
|
|
283
|
+
case "$mot" in -*) continue ;; esac
|
|
284
|
+
if [ -z "$vu" ]; then vu=1; continue; fi
|
|
285
|
+
verifier_config_cible "$mot" "$dir"
|
|
286
|
+
done
|
|
287
|
+
;;
|
|
288
|
+
esac
|
|
289
|
+
}
|
|
290
|
+
|
|
155
291
|
# Modification du fichier $1 : seulement sur une branche de travail (de ticket,
|
|
156
292
|
# si le dépôt suit ses tickets) ; un fichier ignoré par git l'est partout
|
|
157
293
|
verifier_fichier() {
|
|
@@ -160,6 +296,8 @@ verifier_fichier() {
|
|
|
160
296
|
# être en forme Windows (D:/…), absolu lui aussi
|
|
161
297
|
local d="$REPLY" f="${1%$'\r'}"
|
|
162
298
|
case "$f" in /* | [A-Za-z]:[/\\]*) ;; *) f="$cwd/$f" ;; esac
|
|
299
|
+
# Configuration d'outil de vérification : refusée avant toute autre règle
|
|
300
|
+
verifier_config_cible "$f" "$d"
|
|
163
301
|
(
|
|
164
302
|
cd "$d" 2>/dev/null || exit 0
|
|
165
303
|
depot_repowarden || exit 0
|
|
@@ -587,6 +725,10 @@ verifier_shell() {
|
|
|
587
725
|
esac
|
|
588
726
|
done
|
|
589
727
|
[ $# -gt 0 ] || continue
|
|
728
|
+
# Redirection vers la configuration d'un outil de vérification : refusée
|
|
729
|
+
# comme la modification du fichier, avant même la commande
|
|
730
|
+
verifier_redirections "$@"
|
|
731
|
+
verifier_ecriture_fichiers "$@"
|
|
590
732
|
# Commande : dernier élément du chemin (/ ou \), sans .exe/.cmd/.bat
|
|
591
733
|
m="${1//[\"\']/}"
|
|
592
734
|
m="${m##*/}"
|
|
@@ -63,6 +63,7 @@ verifier() {
|
|
|
63
63
|
# déclencherait aucun workflow (préversion, livraison, tickets).
|
|
64
64
|
merge_auto() {
|
|
65
65
|
local pr="$1" base="$2" brouillon="$3"
|
|
66
|
+
local expected online online_ok missing _rest _one
|
|
66
67
|
[ "${AUTO_MERGE:-true}" = true ] && [ "$base" = "${INTEGRATION:-develop}" ] || return 0
|
|
67
68
|
if [ "$brouillon" = true ]; then
|
|
68
69
|
gh pr merge "$pr" --disable-auto >/dev/null 2>&1 || true
|
|
@@ -72,6 +73,49 @@ merge_auto() {
|
|
|
72
73
|
notice_t tk.automerge_no_app "$pr"
|
|
73
74
|
return 0
|
|
74
75
|
fi
|
|
76
|
+
# Vérifications attendues (requiredChecks) vs règles en ligne : si une
|
|
77
|
+
# vérification attendue manque en ligne, l'auto-merge n'est pas armé, on
|
|
78
|
+
# signale et on commente la PR une seule fois (repère regles-perimees).
|
|
79
|
+
# Absence de requiredChecks : rien à comparer, comportement historique.
|
|
80
|
+
# Échec de lecture des règles (droits, réseau) : on arme comme avant.
|
|
81
|
+
cfg_r requiredChecks ""
|
|
82
|
+
expected="$REPLY"
|
|
83
|
+
if [ -n "$expected" ]; then
|
|
84
|
+
online_ok=0
|
|
85
|
+
online="$(gh api "repos/$GH_REPO/rules/branches/$base" \
|
|
86
|
+
--jq '.[] | select(.type=="required_status_checks") | .parameters.required_status_checks[].context' \
|
|
87
|
+
2>/dev/null)" && online_ok=1 || online_ok=0
|
|
88
|
+
if [ "$online_ok" = 1 ]; then
|
|
89
|
+
missing=""
|
|
90
|
+
# Parcours des vérifications attendues (virgule), espaces possibles
|
|
91
|
+
_rest="$expected"
|
|
92
|
+
while [ -n "$_rest" ]; do
|
|
93
|
+
case "$_rest" in
|
|
94
|
+
*,*) _one="${_rest%%,*}"; _rest="${_rest#*,}" ;;
|
|
95
|
+
*) _one="$_rest"; _rest="" ;;
|
|
96
|
+
esac
|
|
97
|
+
# suppression des espaces de bord
|
|
98
|
+
_one="${_one#"${_one%%[![:space:]]*}"}"
|
|
99
|
+
_one="${_one%"${_one##*[![:space:]]}"}"
|
|
100
|
+
[ -z "$_one" ] && continue
|
|
101
|
+
if ! grep -Fxq "$_one" <<<"$online"; then
|
|
102
|
+
if [ -z "$missing" ]; then missing="$_one"; else missing="$missing, $_one"; fi
|
|
103
|
+
fi
|
|
104
|
+
done
|
|
105
|
+
if [ -n "$missing" ]; then
|
|
106
|
+
notice_t tk.automerge_rules_stale "$pr" "$missing" "$expected"
|
|
107
|
+
if ! gh pr view "$pr" --json comments -q '.comments[].body' 2>/dev/null \
|
|
108
|
+
| grep -qF '<!-- repowarden:regles-perimees -->'; then
|
|
109
|
+
_tr tk.automerge_rules_stale "$pr" "$missing" "$expected"
|
|
110
|
+
gh pr comment "$pr" --body "$_T"$'\n\n''<!-- repowarden:regles-perimees -->' \
|
|
111
|
+
>/dev/null 2>&1 || true
|
|
112
|
+
fi
|
|
113
|
+
return 0
|
|
114
|
+
fi
|
|
115
|
+
else
|
|
116
|
+
notice_t tk.automerge_rules_unread "$pr"
|
|
117
|
+
fi
|
|
118
|
+
fi
|
|
75
119
|
if gh pr merge "$pr" --auto --squash >/dev/null 2>&1; then
|
|
76
120
|
notice_t tk.automerge_armed "$pr"
|
|
77
121
|
else
|
package/hooks/lib/i18n/en.sh
CHANGED
|
@@ -410,7 +410,7 @@ _msg_en() {
|
|
|
410
410
|
ci.livraison.maintenance) _M="Maintenance only: no version to publish" ;;
|
|
411
411
|
ci.livraison.non_exigee) _M="%s: prerelease not required" ;;
|
|
412
412
|
ci.livraison.ok) _M="%s: prerelease tested, deliverable" ;;
|
|
413
|
-
ci.livraison.preversion) _M="%s:
|
|
413
|
+
ci.livraison.preversion) _M="%s: waiting for a prerelease: no %s contains this commit; wait for the evening one, or Actions → release → Run workflow on %s" ;;
|
|
414
414
|
ci.install_tool.os) _M="Unsupported system: %s" ;;
|
|
415
415
|
ci.install_tool.arch) _M="Unsupported architecture: %s" ;;
|
|
416
416
|
ci.install_tool.unknown) _M="Unknown tool: %s" ;;
|
|
@@ -435,6 +435,9 @@ _msg_en() {
|
|
|
435
435
|
dc.knip_file) _M="file never imported" ;;
|
|
436
436
|
dc.knip_export) _M="export never imported:" ;;
|
|
437
437
|
dc.knip_dep) _M="dependency never imported:" ;;
|
|
438
|
+
# Tests removed by a PR (tests check)
|
|
439
|
+
ci.check.tests_down) _M="%s: %s → %s tests (before → after); a PR does not remove tests." ;;
|
|
440
|
+
ci.check.tests_fix) _M=" Fix: restore the removed tests, or write it in the PR description (line \"Tests removed: <reason>\", \"Tests retirés : …\" also accepted)." ;;
|
|
438
441
|
# Functions added without a comment
|
|
439
442
|
cm.section) _M="Functions without a comment (new code only)" ;;
|
|
440
443
|
cm.no_base) _M="No base: comment check skipped." ;;
|
|
@@ -552,6 +555,8 @@ _msg_en() {
|
|
|
552
555
|
tk.automerge_armed) _M="PR #%s: auto-merge armed (squash), as soon as the checks are green." ;;
|
|
553
556
|
tk.automerge_no_app) _M="PR #%s: auto-merge not armed, no GitHub App (repowarden app init): with the Actions token, the merge would trigger no workflow." ;;
|
|
554
557
|
tk.automerge_failed) _M="PR #%s: auto-merge not armed (auto-merge not allowed on the repository: repowarden proteger)." ;;
|
|
558
|
+
tk.automerge_rules_stale) _M="PR #%s: auto-merge not armed (online rules stale: %s). Run: repowarden proteger --checks \"%s\"." ;;
|
|
559
|
+
tk.automerge_rules_unread) _M="PR #%s: online rules unreadable (permissions or network): auto-merge armed without comparing requiredChecks." ;;
|
|
555
560
|
app.no_node) _M="node is required (local page creating the App): https://nodejs.org" ;;
|
|
556
561
|
app.step_create) _M="Creating the GitHub App" ;;
|
|
557
562
|
app.exists) _M="App already set up for this repository (client %s): nothing to recreate." ;;
|
|
@@ -749,6 +754,7 @@ _msg_en() {
|
|
|
749
754
|
garde.detache) _M="change refused: detached HEAD in %s.\n → Switch to a ticket's branch: repowarden ticket <n>" ;;
|
|
750
755
|
garde.protegee) _M="change refused: \"%s\" is a protected branch.\n → Work on a ticket's branch: repowarden ticket <n>" ;;
|
|
751
756
|
garde.protegee_libre) _M="change refused: \"%s\" is a protected branch.\n → Create a work branch: git switch -c <type>/<topic>" ;;
|
|
757
|
+
garde.config_verification) _M="tool verification setting: the maintainer's decision; fix the code rather than the rule (\"%s\").\n → Fix the code, never the rule; only the maintainer sets the tool (setting repowarden.agentsConfig = true)." ;;
|
|
752
758
|
garde.carte) _M=" → Before changing %s, once on the right branch: repowarden carte %s (functions, tests, tickets)" ;;
|
|
753
759
|
garde.hors_ticket) _M="change refused: branch \"%s\" is not a ticket's branch (<type>/<n>-<topic>).\n → Take the ticket: repowarden ticket <n>" ;;
|
|
754
760
|
garde.branche) _M="command refused: \"%s\" creates a branch; here, branches come from tickets.\n → repowarden ticket <n>" ;;
|
package/hooks/lib/i18n/fr.sh
CHANGED
|
@@ -411,7 +411,7 @@ _msg_fr() {
|
|
|
411
411
|
ci.livraison.maintenance) _M="Maintenance seule : aucune version à publier" ;;
|
|
412
412
|
ci.livraison.non_exigee) _M="%s : préversion non exigée" ;;
|
|
413
413
|
ci.livraison.ok) _M="%s : préversion testée, livrable" ;;
|
|
414
|
-
ci.livraison.preversion) _M="%s :
|
|
414
|
+
ci.livraison.preversion) _M="%s : en attente de préversion : aucune %s ne contient ce commit ; attendre celle du soir, ou Actions → release → Run workflow sur %s" ;;
|
|
415
415
|
ci.install_tool.os) _M="Système non supporté : %s" ;;
|
|
416
416
|
ci.install_tool.arch) _M="Architecture non supportée : %s" ;;
|
|
417
417
|
ci.install_tool.unknown) _M="Outil inconnu : %s" ;;
|
|
@@ -436,6 +436,9 @@ _msg_fr() {
|
|
|
436
436
|
dc.knip_file) _M="fichier jamais importé" ;;
|
|
437
437
|
dc.knip_export) _M="export jamais importé :" ;;
|
|
438
438
|
dc.knip_dep) _M="dépendance jamais importée :" ;;
|
|
439
|
+
# Tests retirés par une PR (vérification tests)
|
|
440
|
+
ci.check.tests_down) _M="%s : %s → %s tests (avant → après) ; une PR ne retire pas de tests." ;;
|
|
441
|
+
ci.check.tests_fix) _M=" Corriger : remettre les tests retirés, ou l'écrire dans la description de la PR (ligne « Tests retirés : <raison> », « Tests removed: … » en anglais)." ;;
|
|
439
442
|
# Fonctions ajoutées sans commentaire
|
|
440
443
|
cm.section) _M="Fonctions sans commentaire (nouveau code uniquement)" ;;
|
|
441
444
|
cm.no_base) _M="Pas de base : contrôle des commentaires ignoré." ;;
|
|
@@ -553,6 +556,8 @@ _msg_fr() {
|
|
|
553
556
|
tk.automerge_armed) _M="PR #%s : merge automatique armé (squash), dès que les vérifications sont vertes." ;;
|
|
554
557
|
tk.automerge_no_app) _M="PR #%s : merge automatique non armé, pas de GitHub App (repowarden app init) : avec le jeton des Actions, le merge ne déclencherait aucun workflow." ;;
|
|
555
558
|
tk.automerge_failed) _M="PR #%s : merge automatique non armé (dépôt sans merge automatique autorisé : repowarden proteger)." ;;
|
|
559
|
+
tk.automerge_rules_stale) _M="PR #%s : merge automatique non armé (règles en ligne périmées : %s). Lancer : repowarden proteger --checks \"%s\"." ;;
|
|
560
|
+
tk.automerge_rules_unread) _M="PR #%s : règles en ligne illisibles (droits ou réseau) : merge automatique armé sans comparer requiredChecks." ;;
|
|
556
561
|
app.no_node) _M="node est nécessaire (page locale de création de l'App) : https://nodejs.org" ;;
|
|
557
562
|
app.step_create) _M="Création de la GitHub App" ;;
|
|
558
563
|
app.exists) _M="App déjà configurée pour ce dépôt (client %s) : rien à recréer." ;;
|
|
@@ -750,6 +755,7 @@ _msg_fr() {
|
|
|
750
755
|
garde.detache) _M="modification refusée : HEAD détaché dans %s.\n → Se placer sur la branche d'un ticket : repowarden ticket <n>" ;;
|
|
751
756
|
garde.protegee) _M="modification refusée : « %s » est une branche protégée.\n → Travailler sur la branche d'un ticket : repowarden ticket <n>" ;;
|
|
752
757
|
garde.protegee_libre) _M="modification refusée : « %s » est une branche protégée.\n → Créer une branche de travail : git switch -c <type>/<sujet>" ;;
|
|
758
|
+
garde.config_verification) _M="réglage d'un outil de vérification : décision du mainteneur ; corriger le code plutôt que la règle (« %s »).\n → Corriger le code, jamais la règle ; seul le mainteneur règle l'outil (réglage repowarden.agentsConfig = true)." ;;
|
|
753
759
|
garde.carte) _M=" → Avant de modifier %s, une fois sur la bonne branche : repowarden carte %s (fonctions, tests, tickets)" ;;
|
|
754
760
|
garde.hors_ticket) _M="modification refusée : la branche « %s » n'est pas celle d'un ticket (<type>/<n>-<sujet>).\n → Prendre le ticket : repowarden ticket <n>" ;;
|
|
755
761
|
garde.branche) _M="commande refusée : « %s » crée une branche ; ici, la branche naît du ticket.\n → repowarden ticket <n>" ;;
|
package/package.json
CHANGED