repowarden 4.10.2-next.1 → 4.11.0-next.13
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/statut +48 -0
- package/bin/ticket +5 -0
- package/bin/tickets +2 -1
- package/ci/tickets/adopter.sh +3 -2
- package/ci/tickets/issue.sh +10 -3
- package/ci/tickets/release.sh +18 -8
- package/ci/tickets.sh +4 -3
- package/hooks/commit-msg +15 -9
- package/hooks/lib/common.sh +25 -3
- package/hooks/lib/i18n/en.sh +15 -3
- package/hooks/lib/i18n/fr.sh +15 -3
- package/hooks/lib/lang.sh +23 -2
- package/hooks/lib/secrets.sh +52 -0
- package/hooks/lib/tickets.sh +5 -0
- package/hooks/lib/ui.sh +12 -0
- package/hooks/pre-commit +9 -7
- package/hooks/pre-push +45 -3
- package/package.json +1 -1
package/bin/bot
CHANGED
|
@@ -115,7 +115,24 @@ TRAVAIL="$(mktemp -d)"
|
|
|
115
115
|
trap 'rm -rf "$TRAVAIL"' EXIT
|
|
116
116
|
# Identité des commits du bot (workflow : <slug>[bot]), crédits de gh
|
|
117
117
|
NOM_BOT="${REPOWARDEN_BOT_NAME:-repowarden-bot}"
|
|
118
|
-
|
|
118
|
+
|
|
119
|
+
# Adresse <id>+<slug>[bot]@users.noreply.github.com, comme bot.yml : seule
|
|
120
|
+
# forme que GitHub rattache à l'App. Slug tiré du nom, car un jeton
|
|
121
|
+
# d'installation ne peut pas lire /app ; repli signalé si l'id est introuvable.
|
|
122
|
+
courriel_bot_r() {
|
|
123
|
+
REPLY="${REPOWARDEN_BOT_EMAIL:-}"
|
|
124
|
+
[ -z "$REPLY" ] || return 0
|
|
125
|
+
local slug="${NOM_BOT%\[bot\]}" id
|
|
126
|
+
id="$(gh api "users/${slug}[bot]" --jq .id 2>/dev/null || true)"
|
|
127
|
+
case "$id" in
|
|
128
|
+
"" | *[!0-9]*)
|
|
129
|
+
REPLY="repowarden-bot@users.noreply.github.com"
|
|
130
|
+
attention_t bot.email_fallback "${slug}[bot]" "$REPLY" ;;
|
|
131
|
+
*) REPLY="${id}+${slug}[bot]@users.noreply.github.com" ;;
|
|
132
|
+
esac
|
|
133
|
+
}
|
|
134
|
+
courriel_bot_r
|
|
135
|
+
COURRIEL_BOT="$REPLY"
|
|
119
136
|
|
|
120
137
|
# Dernière ligne d'erreur d'une sortie (✖ …), pour le rapport
|
|
121
138
|
derniere_erreur_r() {
|
package/bin/bots
CHANGED
|
@@ -431,18 +431,19 @@ YAML
|
|
|
431
431
|
YAML
|
|
432
432
|
fi
|
|
433
433
|
fi
|
|
434
|
-
# Tickets livrés : statut done et fermeture à la release
|
|
435
|
-
# préversion ; seulement avec le suivi des tickets (sinon le job échoue)
|
|
434
|
+
# Tickets livrés : statut done et fermeture à la release (ou à une
|
|
435
|
+
# livraison sans nouvelle version), préprod à la préversion ; seulement avec le suivi des tickets (sinon le job échoue)
|
|
436
436
|
if choisi tickets || [ -e .github/workflows/tickets.yml ]; then
|
|
437
437
|
cat <<'YAML'
|
|
438
438
|
|
|
439
439
|
tickets:
|
|
440
440
|
needs: release
|
|
441
|
-
if: needs.release.outputs.release_created == 'true' || needs.release.outputs.prerelease_created == 'true'
|
|
441
|
+
if: needs.release.outputs.release_created == 'true' || needs.release.outputs.prerelease_created == 'true' || needs.release.outputs.delivered_without_version != ''
|
|
442
442
|
uses: simdevWarden/repowarden/.github/workflows/tickets.yml@v4
|
|
443
443
|
permissions: { contents: write, issues: write, pull-requests: write, statuses: write }
|
|
444
444
|
with:
|
|
445
445
|
tag: ${{ needs.release.outputs.tag_name || needs.release.outputs.prerelease_tag }}
|
|
446
|
+
delivered-sha: ${{ needs.release.outputs.delivered_without_version }}
|
|
446
447
|
YAML
|
|
447
448
|
if [ -n "$AVEC_APP" ]; then
|
|
448
449
|
cat <<'YAML'
|
package/bin/garde
CHANGED
|
@@ -100,6 +100,16 @@ verifier_commit() {
|
|
|
100
100
|
fi
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
# Vrai si les mots $@ demandent une lecture seule (aide, aperçu) : rien n'est
|
|
104
|
+
# commité, rien à refuser
|
|
105
|
+
lecture_seule() {
|
|
106
|
+
local mot
|
|
107
|
+
for mot in "$@"; do
|
|
108
|
+
case "$mot" in -h | --help | --dry-run) return 0 ;; esac
|
|
109
|
+
done
|
|
110
|
+
return 1
|
|
111
|
+
}
|
|
112
|
+
|
|
103
113
|
# repowarden cc / commit (ou git cc), lancé depuis le dossier $dir
|
|
104
114
|
verifier_cc() {
|
|
105
115
|
dossier_r "$dir"
|
|
@@ -169,6 +179,7 @@ verifier_git() {
|
|
|
169
179
|
done
|
|
170
180
|
case "$sous" in
|
|
171
181
|
commit)
|
|
182
|
+
lecture_seule ${args_git[@]+"${args_git[@]}"} && exit 0
|
|
172
183
|
amend="" noedit=""
|
|
173
184
|
for mot in ${args_git[@]+"${args_git[@]}"}; do
|
|
174
185
|
case "$mot" in
|
|
@@ -183,7 +194,7 @@ verifier_git() {
|
|
|
183
194
|
refuser garde.commit "$ligne"
|
|
184
195
|
;;
|
|
185
196
|
cc)
|
|
186
|
-
verifier_commit "$ligne"
|
|
197
|
+
lecture_seule ${args_git[@]+"${args_git[@]}"} || verifier_commit "$ligne"
|
|
187
198
|
;;
|
|
188
199
|
add)
|
|
189
200
|
for mot in ${args_git[@]+"${args_git[@]}"}; do
|
|
@@ -227,7 +238,7 @@ verifier_git() {
|
|
|
227
238
|
# Ligne de commande shell : chaque commande (&&, ||, ;, |, retour à la ligne),
|
|
228
239
|
# « cd » suivi, variables d'environnement en tête ignorées
|
|
229
240
|
verifier_shell() {
|
|
230
|
-
local commande="$1" segment mots m nl=$'\n' lignes
|
|
241
|
+
local commande="$1" segment mots m cmd_cc nl=$'\n' lignes
|
|
231
242
|
dir="$cwd"
|
|
232
243
|
# Une commande par ligne. Découpage hors de <( … ) : bash 3.2 (macOS) y
|
|
233
244
|
# lit mal ces motifs (« bad substitution ») et ne vérifierait plus rien
|
|
@@ -264,7 +275,13 @@ verifier_shell() {
|
|
|
264
275
|
verifier_git "${@//[\"\']/}"
|
|
265
276
|
;;
|
|
266
277
|
repowarden | */repowarden)
|
|
267
|
-
case "${2//[\"\']/}" in
|
|
278
|
+
case "${2//[\"\']/}" in
|
|
279
|
+
cc | commit)
|
|
280
|
+
cmd_cc="repowarden ${2//[\"\']/}"
|
|
281
|
+
shift 2
|
|
282
|
+
lecture_seule "${@//[\"\']/}" || verifier_cc "$cmd_cc"
|
|
283
|
+
;;
|
|
284
|
+
esac
|
|
268
285
|
;;
|
|
269
286
|
esac
|
|
270
287
|
done <<<"$lignes"
|
package/bin/migrer
CHANGED
|
@@ -29,24 +29,10 @@ done
|
|
|
29
29
|
[ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
|
|
30
30
|
cd "$GIT_TOPLEVEL"
|
|
31
31
|
|
|
32
|
-
# Version majeure dont le tag flottant (vN) est publié
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
repowarden_version_r "$ROOT"
|
|
37
|
-
v="$REPLY"
|
|
38
|
-
# 0.0.0-dev : version inconnue (clone sans tags), rien à déduire
|
|
39
|
-
if [ "$v" = 0.0.0-dev ]; then REPLY=""; return; fi
|
|
40
|
-
REPLY="${v%%.*}"
|
|
41
|
-
REPLY="${REPLY%%-*}"
|
|
42
|
-
[[ "$v" != *.0.0-* ]] || REPLY=$((REPLY - 1))
|
|
43
|
-
}
|
|
44
|
-
if [ -n "${REPOWARDEN_MAJEURE:-}" ]; then
|
|
45
|
-
MAJEURE="$REPOWARDEN_MAJEURE"
|
|
46
|
-
else
|
|
47
|
-
majeure_r
|
|
48
|
-
MAJEURE="$REPLY"
|
|
49
|
-
fi
|
|
32
|
+
# Version majeure dont le tag flottant (vN) est publié
|
|
33
|
+
repowarden_version_r "$ROOT"
|
|
34
|
+
repowarden_majeure_r "$REPLY"
|
|
35
|
+
MAJEURE="$REPLY"
|
|
50
36
|
[ -n "$MAJEURE" ] || { err_t mig.unknown_version "$ROOT"; exit 1; }
|
|
51
37
|
[[ "$MAJEURE" =~ ^[1-9][0-9]*$ ]] || { err_t mig.no_major "$MAJEURE"; exit 1; }
|
|
52
38
|
BRANCHE="chore/migration-repowarden-v$MAJEURE"
|
package/bin/statut
CHANGED
|
@@ -9,6 +9,8 @@ set -euo pipefail
|
|
|
9
9
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
10
10
|
# shellcheck source=../hooks/lib/common.sh
|
|
11
11
|
source "$ROOT/hooks/lib/common.sh"
|
|
12
|
+
# shellcheck source=../hooks/lib/secrets.sh
|
|
13
|
+
source "$ROOT/hooks/lib/secrets.sh"
|
|
12
14
|
exec 2>&1 # une seule sortie, dans l'ordre (avertissements compris)
|
|
13
15
|
# shellcheck disable=SC2034 # lu par check_version (common.sh)
|
|
14
16
|
HOOKS_DIR="$ROOT/hooks"
|
|
@@ -78,6 +80,42 @@ if [ -n "$GIT_TOPLEVEL" ]; then
|
|
|
78
80
|
attention_t st.repo_migrate
|
|
79
81
|
suivant="${suivant:-repowarden migrer}"
|
|
80
82
|
fi
|
|
83
|
+
# Workflows qui appellent repowarden : dépôt officiel et tag vN de la
|
|
84
|
+
# majeure installée. Autre dépôt (ancien propriétaire, fork), branche
|
|
85
|
+
# (cassé quand elle disparaît) ou majeure dépassée : la ligne à corriger
|
|
86
|
+
repowarden_majeure_r "$version"
|
|
87
|
+
majeure="$REPLY"
|
|
88
|
+
re_uses='^[[:space:]]*(-[[:space:]]+)?uses:[[:space:]]*["'"'"']?([A-Za-z0-9_.-]+)/repowarden(/[^@[:space:]"'"'"']*)?@([^[:space:]"'"'"'#]+)'
|
|
89
|
+
re_tag='^v([0-9]+)(\.[0-9]+)*(-[0-9A-Za-z.]+)?$'
|
|
90
|
+
re_sha='^[0-9a-f]{40}$'
|
|
91
|
+
for wf in "$GIT_TOPLEVEL"/.github/workflows/*.yml "$GIT_TOPLEVEL"/.github/workflows/*.yaml; do
|
|
92
|
+
[ -f "$wf" ] || continue
|
|
93
|
+
n=0
|
|
94
|
+
while IFS= read -r ligne || [ -n "$ligne" ]; do
|
|
95
|
+
n=$((n + 1))
|
|
96
|
+
[[ "$ligne" =~ $re_uses ]] || continue
|
|
97
|
+
proprio="${BASH_REMATCH[2]}" chemin="${BASH_REMATCH[3]}" ref="${BASH_REMATCH[4]}"
|
|
98
|
+
attendu="simdevWarden/repowarden$chemin@v${majeure:-N}"
|
|
99
|
+
shopt -s nocasematch
|
|
100
|
+
officiel=""
|
|
101
|
+
[[ "$proprio" != simdevwarden ]] || officiel=1
|
|
102
|
+
shopt -u nocasematch
|
|
103
|
+
cle=""
|
|
104
|
+
if [ -z "$officiel" ]; then
|
|
105
|
+
cle=st.wf_owner arg="$proprio/repowarden"
|
|
106
|
+
elif [[ "$ref" =~ $re_tag ]]; then
|
|
107
|
+
if [ -n "$majeure" ] && [ "${BASH_REMATCH[1]}" -lt "$majeure" ]; then
|
|
108
|
+
cle=st.wf_old arg="$ref"
|
|
109
|
+
fi
|
|
110
|
+
elif ! [[ "$ref" =~ $re_sha ]]; then
|
|
111
|
+
cle=st.wf_branch arg="$ref"
|
|
112
|
+
fi
|
|
113
|
+
[ -n "$cle" ] || continue
|
|
114
|
+
attention_t "$cle" "${wf#"$GIT_TOPLEVEL"/}" "$n" "$arg" "$attendu"
|
|
115
|
+
t st.wf_fix
|
|
116
|
+
suivant="${suivant:-$REPLY}"
|
|
117
|
+
done <"$wf"
|
|
118
|
+
done
|
|
81
119
|
# Branche develop présente, flux non déclaré : traité comme « main seule »
|
|
82
120
|
cfg_r integrationBranch ""
|
|
83
121
|
if [ -z "$REPLY" ] && git rev-parse -q --verify refs/remotes/origin/develop >/dev/null; then
|
|
@@ -93,6 +131,11 @@ if [ -n "$GIT_TOPLEVEL" ]; then
|
|
|
93
131
|
ok_t st.repo_version_ok "$attendue"
|
|
94
132
|
fi
|
|
95
133
|
fi
|
|
134
|
+
# Fichier sensible ni suivi ni ignoré (même avertissement qu'au pre-commit)
|
|
135
|
+
if ! skipped secrets && ! skipped sensitive-files; then
|
|
136
|
+
sensitive_files_r
|
|
137
|
+
[ -z "$REPLY" ] || sensitive_files_warn "$REPLY"
|
|
138
|
+
fi
|
|
96
139
|
info_t st.repo_forge "$FORGE"
|
|
97
140
|
[ "$FORGE" != github ] || dim_t st.repo_rules
|
|
98
141
|
fi
|
|
@@ -103,6 +146,11 @@ for outil in gitleaks ruff vulture staticcheck pmd; do
|
|
|
103
146
|
if has "$outil"; then presents="$presents $outil"; else absents="$absents $outil"; fi
|
|
104
147
|
done
|
|
105
148
|
info_t st.tools "${presents# }" "${absents# }"
|
|
149
|
+
# Sans gitleaks, les secrets ne sont vérifiés que par la CI
|
|
150
|
+
if ! has gitleaks && ! skipped secrets; then
|
|
151
|
+
gitleaks_install_r
|
|
152
|
+
suivant="${suivant:-$REPLY}"
|
|
153
|
+
fi
|
|
106
154
|
|
|
107
155
|
echo
|
|
108
156
|
if [ -n "$suivant" ]; then
|
package/bin/ticket
CHANGED
|
@@ -103,6 +103,11 @@ cmd_nouveau() {
|
|
|
103
103
|
shift
|
|
104
104
|
done
|
|
105
105
|
[ -n "$titre" ] || { err_t tk.cli.title_required; exit 1; }
|
|
106
|
+
# Étiquette « type: … » inexistante : gh refuserait de créer le ticket
|
|
107
|
+
if [ -n "$type" ] && tickets_managed && [[ " $TYPES_TICKET " != *" $type "* ]]; then
|
|
108
|
+
err_t tk.cli.bad_type "$type" "${TYPES_TICKET// /, }"
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
106
111
|
gh_ready || exit 1
|
|
107
112
|
if tickets_managed; then
|
|
108
113
|
etiquettes+=(--label "$S_AVALIDER")
|
package/bin/tickets
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# repowarden tickets init [--repo propriétaire/nom] [--sans-validation-auto]
|
|
4
4
|
# repowarden tickets sync branches locales des tickets qui me sont assignés
|
|
5
5
|
# (automatique si réglage ticketSync = auto ; défaut manual)
|
|
6
|
-
# - étiquettes : « validé », les statuts (à valider, backlog, en cours,
|
|
6
|
+
# - étiquettes : « validé », « à décider » (suspend la validation d'office), les statuts (à valider, backlog, en cours,
|
|
7
7
|
# en relecture, préprod, done) et les types (type: feat, type: fix…), qui
|
|
8
8
|
# donnent le préfixe de la branche du ticket ;
|
|
9
9
|
# - modèle de ticket .github/ISSUE_TEMPLATE/ticket.yml (créé « à valider ») ;
|
|
@@ -60,6 +60,7 @@ while IFS='|' read -r nom couleur description; do
|
|
|
60
60
|
done <<'ETIQUETTES'
|
|
61
61
|
validé|0e8a16|Ticket accepté par un mainteneur (débloque ses PR)
|
|
62
62
|
statut: à valider|fbca04|Ticket créé, en attente de validation
|
|
63
|
+
à décider|d4c5f9|Proposé : pas de validation d'office, un mainteneur décide
|
|
63
64
|
statut: backlog|c5def5|Validé, pas encore commencé
|
|
64
65
|
statut: en cours|1d76db|Travail commencé (PR en brouillon)
|
|
65
66
|
statut: en relecture|5319e7|PR prête, en relecture
|
package/ci/tickets/adopter.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
# Mise en route sur un dépôt existant (tickets init, relançable) : tickets
|
|
8
8
|
# ouverts sans statut rangés (validé ou ouvert par un mainteneur → backlog,
|
|
9
|
-
# sinon à valider) ; branches des PR ouvertes et mergées inscrites sur les
|
|
9
|
+
# sinon, ou « à décider », à valider) ; branches des PR ouvertes et mergées inscrites sur les
|
|
10
10
|
# tickets qu'elles citent. Aucune branche créée en masse : elle naît quand le
|
|
11
11
|
# ticket est pris (repowarden ticket N).
|
|
12
12
|
cmd_adopter() {
|
|
@@ -17,7 +17,8 @@ cmd_adopter() {
|
|
|
17
17
|
! grep -q '^statut: ' <<<"$labels" || continue
|
|
18
18
|
if grep -qxF "$VALIDE" <<<"$labels"; then
|
|
19
19
|
statut "$n" "$S_BACKLOG"
|
|
20
|
-
elif [ "${AUTO_VALIDATE:-maintainers}" = maintainers ] && est_mainteneur "$assoc"
|
|
20
|
+
elif [ "${AUTO_VALIDATE:-maintainers}" = maintainers ] && est_mainteneur "$assoc" \
|
|
21
|
+
&& ! grep -qxF "$A_DECIDER" <<<"$labels"; then
|
|
21
22
|
gh issue edit "$n" --add-label "$VALIDE" >/dev/null
|
|
22
23
|
statut "$n" "$S_BACKLOG"
|
|
23
24
|
else
|
package/ci/tickets/issue.sh
CHANGED
|
@@ -7,10 +7,15 @@
|
|
|
7
7
|
# Ticket $1 validé : backlog s'il attendait la validation, sa branche, et
|
|
8
8
|
# vérification relancée sur les PR ouvertes qui l'attendaient
|
|
9
9
|
accepter() {
|
|
10
|
-
local n="$1" pr head sha body base brouillon
|
|
11
|
-
|
|
10
|
+
local n="$1" pr head sha body base brouillon labels
|
|
11
|
+
labels="$(etiquettes "$n")"
|
|
12
|
+
if grep -qxF "$S_AVALIDER" <<<"$labels"; then
|
|
12
13
|
statut "$n" "$S_BACKLOG"
|
|
13
14
|
fi
|
|
15
|
+
# Décision prise : « à décider » n'a plus lieu d'être
|
|
16
|
+
if grep -qxF "$A_DECIDER" <<<"$labels"; then
|
|
17
|
+
gh issue edit "$n" --remove-label "$A_DECIDER" >/dev/null
|
|
18
|
+
fi
|
|
14
19
|
# La branche naît quand le ticket est pris (assigné) : un ticket validé
|
|
15
20
|
# pour plus tard n'a pas de branche qui vieillit en attendant
|
|
16
21
|
if [ -n "$(assignes "$n")" ]; then
|
|
@@ -36,7 +41,8 @@ accepter() {
|
|
|
36
41
|
# GitHub ne garde qu'un run en attente par groupe de concurrence, le run
|
|
37
42
|
# « opened » peut être annulé par ceux des étiquettes posées à la création.
|
|
38
43
|
# Non si déjà validé, ou (hors ouverture) rangé au-delà de « à valider »
|
|
39
|
-
# (« validé » retiré à la main : décision respectée)
|
|
44
|
+
# (« validé » retiré à la main : décision respectée), ou proposé à décider
|
|
45
|
+
# (étiquette « à décider » : le mainteneur pose « validé »). Test seul : appelé dans un « if », où set -e
|
|
40
46
|
# ne s'applique pas.
|
|
41
47
|
a_valider_d_office() {
|
|
42
48
|
local labels l
|
|
@@ -45,6 +51,7 @@ a_valider_d_office() {
|
|
|
45
51
|
[ "${ISSUE_STATE:-open}" = open ] || return 1
|
|
46
52
|
labels="$(etiquettes "$ISSUE")"
|
|
47
53
|
! grep -qxF "$VALIDE" <<<"$labels" || return 1
|
|
54
|
+
! grep -qxF "$A_DECIDER" <<<"$labels" || return 1
|
|
48
55
|
[ "$ACTION" != opened ] || return 0
|
|
49
56
|
while IFS= read -r l; do
|
|
50
57
|
[[ "$l" == "statut: "* && "$l" != "$S_AVALIDER" ]] && return 1
|
package/ci/tickets/release.sh
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
# shellcheck shell=bash
|
|
2
|
-
# Suivi des tickets, préversion
|
|
3
|
-
# prévenus, ou passés en done et fermés.
|
|
2
|
+
# Suivi des tickets, préversion, release ou livraison sans nouvelle version
|
|
3
|
+
# (ci/tickets.sh release) : tickets prévenus, ou passés en done et fermés.
|
|
4
4
|
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
5
5
|
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
6
6
|
|
|
7
|
+
# TAG : release (vX.Y.Z) ou préversion (vX.Y.Z-next.N) ; sans TAG,
|
|
8
|
+
# DELIVERED_SHA : commit livré sur la branche principale sans nouvelle version
|
|
9
|
+
# (livraison sans fix ni feat), les tickets livrés sont fermés tout de même
|
|
7
10
|
cmd_release() {
|
|
8
|
-
local n
|
|
11
|
+
local n tag="${TAG:-}" sha="${DELIVERED_SHA:-}"
|
|
12
|
+
if [ -n "$tag" ]; then
|
|
13
|
+
if [[ "$tag" == *-* ]]; then _tr tk.prerelease "$tag"; else _tr tk.released "$tag"; fi
|
|
14
|
+
elif [ -n "$sha" ]; then
|
|
15
|
+
_tr tk.delivered "${MAIN:-main}" "${sha:0:7}"
|
|
16
|
+
else
|
|
17
|
+
echo "ci/tickets.sh release : TAG ou DELIVERED_SHA requis" >&2
|
|
18
|
+
return 2
|
|
19
|
+
fi
|
|
20
|
+
local msg="$_T"
|
|
9
21
|
while IFS= read -r n; do
|
|
10
22
|
[ -n "$n" ] || continue
|
|
11
|
-
if [[ "$
|
|
12
|
-
|
|
13
|
-
gh issue comment "$n" --body "$_T" >/dev/null
|
|
23
|
+
if [[ "$tag" == *-* ]]; then
|
|
24
|
+
gh issue comment "$n" --body "$msg" >/dev/null
|
|
14
25
|
else
|
|
15
|
-
_tr tk.released "$TAG"
|
|
16
26
|
statut "$n" "$S_DONE"
|
|
17
|
-
gh issue close "$n" --comment "$
|
|
27
|
+
gh issue close "$n" --comment "$msg" >/dev/null
|
|
18
28
|
nettoyer_branche "$n"
|
|
19
29
|
fi
|
|
20
30
|
done < <(gh issue list --state open --label "$S_PREPROD" --limit 500 --json number -q '.[].number')
|
package/ci/tickets.sh
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
# ci/tickets.sh push premier push sur la branche d'un ticket : PR
|
|
13
13
|
# brouillon ouverte, ticket en cours
|
|
14
14
|
# ci/tickets.sh revue corrections demandées en revue → en cours
|
|
15
|
-
# ci/tickets.sh release release publiée
|
|
16
|
-
#
|
|
15
|
+
# ci/tickets.sh release release publiée, ou livraison sur la branche
|
|
16
|
+
# principale sans nouvelle version : tickets en
|
|
17
|
+
# préprod → done (fermés) ; ou prévenus de la préversion
|
|
17
18
|
# ci/tickets.sh adopter mise en route (tickets init) : tickets existants
|
|
18
19
|
# rangés, branches des PR inscrites sur leurs tickets
|
|
19
20
|
#
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
# Variables : GH_TOKEN, GH_REPO, INTEGRATION ; pr : PR, ACTION, MERGED, DRAFT,
|
|
28
29
|
# HEAD, BASE, HEAD_SHA, AUTHOR ; issue : ISSUE, ACTION, LABEL, STATE_REASON ;
|
|
29
30
|
# push : PUSH_BRANCH, DELETED, PUSHER, SHA ; revue : PR, HEAD, REVIEW_STATE ;
|
|
30
|
-
# release : TAG
|
|
31
|
+
# release : TAG, ou DELIVERED_SHA et MAIN (livraison sans nouvelle version)
|
|
31
32
|
set -euo pipefail
|
|
32
33
|
|
|
33
34
|
REPOWARDEN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
package/hooks/commit-msg
CHANGED
|
@@ -74,13 +74,6 @@ Expected format: <type>(<optional scope>): <description>
|
|
|
74
74
|
refactor!: change the public API (! = breaking change)
|
|
75
75
|
|
|
76
76
|
Types:
|
|
77
|
-
EOF
|
|
78
|
-
types_help
|
|
79
|
-
cat >&2 <<EOF
|
|
80
|
-
|
|
81
|
-
👉 Tip: name your branch <type>/<topic> (e.g. feat/user) and the prefix
|
|
82
|
-
is added automatically: git commit -m "add sign-up" (or use git cc)
|
|
83
|
-
Your message is not lost: git commit -e -F .git/COMMIT_EDITMSG
|
|
84
77
|
EOF
|
|
85
78
|
else
|
|
86
79
|
cat >&2 <<EOF
|
|
@@ -93,9 +86,22 @@ Format attendu : <type>(<scope optionnel>): <description>
|
|
|
93
86
|
|
|
94
87
|
Types :
|
|
95
88
|
EOF
|
|
96
|
-
|
|
89
|
+
fi
|
|
90
|
+
types_help
|
|
91
|
+
echo >&2
|
|
92
|
+
# HEAD détachée (bisect, essai, rebase) : pas de branche dont tirer le
|
|
93
|
+
# préfixe, le conseil de nommage ne s'applique pas
|
|
94
|
+
if ! git symbolic-ref -q HEAD >/dev/null; then
|
|
95
|
+
echo_t hook.commit_msg.detached >&2
|
|
96
|
+
echo_t hook.commit_msg.kept >&2
|
|
97
|
+
elif [ "${REPOWARDEN_LANG:-}" = en ]; then
|
|
98
|
+
cat >&2 <<EOF
|
|
99
|
+
👉 Tip: name your branch <type>/<topic> (e.g. feat/user) and the prefix
|
|
100
|
+
is added automatically: git commit -m "add sign-up" (or use git cc)
|
|
101
|
+
Your message is not lost: git commit -e -F .git/COMMIT_EDITMSG
|
|
102
|
+
EOF
|
|
103
|
+
else
|
|
97
104
|
cat >&2 <<EOF
|
|
98
|
-
|
|
99
105
|
👉 Astuce : nomme ta branche <type>/<sujet> (ex. feat/user) et le préfixe
|
|
100
106
|
sera ajouté automatiquement : git commit -m "ajoute l'inscription" (ou git cc)
|
|
101
107
|
Ton message n'est pas perdu : git commit -e -F .git/COMMIT_EDITMSG
|
package/hooks/lib/common.sh
CHANGED
|
@@ -218,6 +218,30 @@ header_valid() {
|
|
|
218
218
|
[ "$REPLY" -le 72 ]
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
# Bots de mise à jour des dépendances : leurs titres (« bump <paquet> from X
|
|
222
|
+
# to Y ») dépassent souvent 72 caractères ; seul leur format est exigé.
|
|
223
|
+
BOTS='^[0-9]*\+?(dependabot|renovate)\[bot\]@'
|
|
224
|
+
|
|
225
|
+
# Règle des en-têtes de commit, commune au pre-push et à la CI : même verdict,
|
|
226
|
+
# même message, que le commit soit vu avant le push ou après.
|
|
227
|
+
# REPLY = clé du message de refus de l'en-tête $1 (e-mail de l'auteur $2),
|
|
228
|
+
# vide si l'en-tête est admis
|
|
229
|
+
commit_subject_problem_r() {
|
|
230
|
+
if [[ "$1" =~ ^(fixup|squash|amend)! ]]; then
|
|
231
|
+
REPLY=ci.check.3
|
|
232
|
+
elif [[ "$1" =~ ^(Merge|Revert) ]]; then
|
|
233
|
+
REPLY=""
|
|
234
|
+
elif ! [[ "$1" =~ $CC_PATTERN ]]; then
|
|
235
|
+
REPLY=ci.check.4
|
|
236
|
+
elif [[ "$2" =~ $BOTS ]]; then
|
|
237
|
+
REPLY=""
|
|
238
|
+
elif authored_length "$1" && [ "$REPLY" -gt 72 ]; then
|
|
239
|
+
REPLY=ci.check.5
|
|
240
|
+
else
|
|
241
|
+
REPLY=""
|
|
242
|
+
fi
|
|
243
|
+
}
|
|
244
|
+
|
|
221
245
|
# REPLY = titre de PR conforme déduit de la branche $1 :
|
|
222
246
|
# feat/ajout-panier → « feat: ajout panier », hotfix/crash → « fix: crash »,
|
|
223
247
|
# release/1.2.0 → « chore(release): 1.2.0 », develop → livraison sur main
|
|
@@ -310,7 +334,6 @@ EOF
|
|
|
310
334
|
|
|
311
335
|
Allowed exceptions: $allowed
|
|
312
336
|
(change: git config repowarden.allowedBranches "main develop release/*")
|
|
313
|
-
Disable for this repository: git config repowarden.skip branch-name
|
|
314
337
|
EOF
|
|
315
338
|
else
|
|
316
339
|
cat >&2 <<EOF
|
|
@@ -333,7 +356,6 @@ EOF
|
|
|
333
356
|
|
|
334
357
|
Exceptions autorisées : $allowed
|
|
335
358
|
(modifier : git config repowarden.allowedBranches "main develop release/*")
|
|
336
|
-
Désactiver pour ce dépôt : git config repowarden.skip branch-name
|
|
337
359
|
EOF
|
|
338
360
|
fi
|
|
339
361
|
}
|
|
@@ -341,7 +363,7 @@ EOF
|
|
|
341
363
|
# Vrai si l'élément est désactivé (repowarden.skip, git config ou .repowarden.conf).
|
|
342
364
|
# Valeurs : true|all, ou liste séparée par espaces/virgules parmi
|
|
343
365
|
# pre-commit prepare-commit-msg commit-msg pre-push post-checkout
|
|
344
|
-
# branch-name protect-branch secrets format tests, ou un langage (node, python…)
|
|
366
|
+
# branch-name protect-branch secrets sensitive-files format tests, ou un langage (node, python…)
|
|
345
367
|
skipped() {
|
|
346
368
|
local v
|
|
347
369
|
cfg_r skip
|
package/hooks/lib/i18n/en.sh
CHANGED
|
@@ -50,6 +50,7 @@ _msg_en() {
|
|
|
50
50
|
cc.refs_prompt_ticket) _M="References (\"-\" for none) [%s]: " ;;
|
|
51
51
|
cc.refs_prompt) _M="References (e.g. %s; Enter for none): " ;;
|
|
52
52
|
tk.cli.no_ticket) _M="Branch %s has no ticket: this work will not be linked to any ticket (not blocking)." ;;
|
|
53
|
+
tk.cli.explain) _M="Ticket: the GitHub issue each change starts from (branch type/<n>-topic, PR citing it); its lifecycle: https://simdevwarden.github.io/repowarden/en/tickets/" ;;
|
|
53
54
|
tk.cli.propose) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\"." ;;
|
|
54
55
|
tk.cli.propose_cc) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\" (or answer yes in git cc)." ;;
|
|
55
56
|
tk.cli.silence) _M="Project without ticket tracking? git config repowarden.skip tickets to stop seeing this." ;;
|
|
@@ -62,6 +63,7 @@ _msg_en() {
|
|
|
62
63
|
tk.cli.issue_created) _M="Ticket #%s created, awaiting validation. Its branch will be created on validation; then: repowarden ticket %s" ;;
|
|
63
64
|
tk.cli.issue_created_branch) _M="Ticket #%s created." ;;
|
|
64
65
|
tk.cli.title_required) _M="Ticket title required: repowarden ticket nouveau \"<title>\"" ;;
|
|
66
|
+
tk.cli.bad_type) _M="Unknown ticket type: %s (types: %s)" ;;
|
|
65
67
|
tk.cli.current) _M="Branch %s: ticket #%s." ;;
|
|
66
68
|
tk.cli.no_ticket_here) _M="No ticket for this branch." ;;
|
|
67
69
|
cc.ticket_offer) _M="No ticket for this branch. Create one now (title: the description)? [y/N]: " ;;
|
|
@@ -130,7 +132,6 @@ _msg_en() {
|
|
|
130
132
|
# Hooks and language plugins
|
|
131
133
|
hook.pre_commit.1) _M="Commit refused: branch name does not follow the convention." ;;
|
|
132
134
|
hook.pre_commit.2) _M="Direct commit on '%s' forbidden. Create a branch: git switch -c feat/my-feature" ;;
|
|
133
|
-
hook.pre_commit.3) _M=" (disable for this repository: git config repowarden.skip protect-branch)" ;;
|
|
134
135
|
hook.pre_commit.4) _M="Secret detection (gitleaks)" ;;
|
|
135
136
|
hook.pre_commit.5) _M="Secret detected: commit refused." ;;
|
|
136
137
|
hook.secrets.where) _M=" %s:%s (%s)" ;;
|
|
@@ -141,7 +142,9 @@ _msg_en() {
|
|
|
141
142
|
hook.secrets.todo_3) _M=" 3. Key that may have leaked elsewhere (already pushed, shared): revoke it at its provider." ;;
|
|
142
143
|
hook.secrets.todo_4) _M=" False positive: add its fingerprint to .gitleaksignore:" ;;
|
|
143
144
|
hook.secrets.failed) _M="gitleaks failed (code %s): commit refused, secret detection not done." ;;
|
|
144
|
-
hook.pre_commit.6) _M="gitleaks not installed,
|
|
145
|
+
hook.pre_commit.6) _M="gitleaks not installed: secrets not checked before the commit, the CI (secrets check) will do it. Install: %s" ;;
|
|
146
|
+
hook.sensitive.found) _M="Sensitive file neither tracked nor ignored: git add . would publish it. Lines to add to .gitignore:" ;;
|
|
147
|
+
hook.sensitive.hint) _M=" (names compared, content never read; patterns: repowarden.sensitiveFiles; disable: git config repowarden.skip sensitive-files)" ;;
|
|
145
148
|
hook.pre_commit.7) _M="Pre-commit OK" ;;
|
|
146
149
|
hook.commit_msg.1) _M="First line too long (%s > 72 characters)." ;;
|
|
147
150
|
hook.commit_msg.type_unknown) _M="Unknown type \"%s\". Did you mean: %s" ;;
|
|
@@ -149,13 +152,16 @@ _msg_en() {
|
|
|
149
152
|
hook.commit_msg.desc_empty) _M="Missing description after \"%s\": describe the change in the imperative mood." ;;
|
|
150
153
|
hook.commit_msg.kept) _M=" Your message is not lost: git commit -e -F .git/COMMIT_EDITMSG" ;;
|
|
151
154
|
hook.commit_msg.dot) _M="Final period removed from the description (same rule as git cc)." ;;
|
|
155
|
+
hook.commit_msg.detached) _M="👉 Detached HEAD (bisect, trial): no prefix comes from a branch, write the full header (e.g. fix: correct the total)." ;;
|
|
152
156
|
hook.pre_push.1) _M="Push refused: branch name does not follow the convention." ;;
|
|
153
157
|
hook.pre_push.2) _M="Direct push to '%s' refused: it only changes through pull requests." ;;
|
|
154
158
|
hook.pre_push.3) _M=" Create a branch: git switch -c feat/my-feature, then open a PR." ;;
|
|
155
|
-
hook.pre_push.4) _M=" (disable for this repository: git config repowarden.skip protect-branch)" ;;
|
|
156
159
|
hook.pre_push.5) _M="%s: no changed file, nothing to test." ;;
|
|
157
160
|
hook.pre_push.6) _M="Nothing to test (branch deletion only)." ;;
|
|
158
161
|
hook.pre_push.7) _M="Pre-push OK" ;;
|
|
162
|
+
hook.pre_push.msg) _M="Push refused: commit message does not follow the convention (same rule as CI)." ;;
|
|
163
|
+
hook.pre_push.msg_fix) _M=" Fix: git rebase -i %s (reword), then push again." ;;
|
|
164
|
+
hook.pre_push.nothing) _M="Nothing to push." ;;
|
|
159
165
|
hook.pre_push.workflow_scope) _M="gh token without the 'workflow' scope: GitHub will refuse a push touching .github/workflows/. Allow it (then sign in again): gh auth refresh -h github.com -s workflow" ;;
|
|
160
166
|
hook.pre_push.lint.missing) _M="%s not installed, %s not checked (install: %s)." ;;
|
|
161
167
|
hook.pre_push.lint.run) _M="%s: %s" ;;
|
|
@@ -486,6 +492,10 @@ _msg_en() {
|
|
|
486
492
|
st.repo_version_ok) _M="Version required by the project (%s): met" ;;
|
|
487
493
|
st.repo_forge) _M="Platform: %s" ;;
|
|
488
494
|
st.repo_rules) _M="GitHub rules up to date? repowarden proteger --verifier (read-only)" ;;
|
|
495
|
+
st.wf_owner) _M="%s:%s: repowarden called from another repository than the official one (%s); expected: %s" ;;
|
|
496
|
+
st.wf_branch) _M="%s:%s: repowarden called on a branch (@%s), broken as soon as it is deleted; expected: %s" ;;
|
|
497
|
+
st.wf_old) _M="%s:%s: repowarden called in an outdated major version (@%s); expected: %s" ;;
|
|
498
|
+
st.wf_fix) _M="fix the uses: lines reported above" ;;
|
|
489
499
|
st.tools) _M="Tools present: %s · missing: %s (depending on the projects' languages)" ;;
|
|
490
500
|
st.next) _M="Next step:" ;;
|
|
491
501
|
st.ready) _M="All set: git cc to commit, repowarden code-mort on a branch." ;;
|
|
@@ -541,6 +551,7 @@ _msg_en() {
|
|
|
541
551
|
bot.ignored) _M="ignored (bot = off)" ;;
|
|
542
552
|
bot.mode_invalid) _M="bot = %s: auto, pr or off expected" ;;
|
|
543
553
|
bot.clone_failed) _M="clone failed: %s" ;;
|
|
554
|
+
bot.email_fallback) _M="Bot user %s not found: commits signed %s, linked to no account (set REPOWARDEN_BOT_EMAIL to fix the address)." ;;
|
|
544
555
|
bot.prot_ok) _M="up to date" ;;
|
|
545
556
|
bot.prot_stale) _M="⚠ outdated: repowarden proteger" ;;
|
|
546
557
|
bot.prot_applied) _M="✔ applied" ;;
|
|
@@ -654,6 +665,7 @@ _msg_en() {
|
|
|
654
665
|
tk.init_next_wired) _M="Next: commit these files; require the \"ticket\" check (repowarden proteger --checks \"…,ticket\"). The tickets job of release.yml is already wired." ;;
|
|
655
666
|
tk.prerelease) _M="Available in pre-release **%s**: to be tested before production." ;;
|
|
656
667
|
tk.released) _M="Released in **%s**." ;;
|
|
668
|
+
tk.delivered) _M="Delivered to **%s**, without a new version (commit %s)." ;;
|
|
657
669
|
# Ticket comment
|
|
658
670
|
tk.comment.adding) _M="Adding comment to ticket #%s..." ;;
|
|
659
671
|
tk.comment.added) _M="Comment added to ticket #%s." ;;
|
package/hooks/lib/i18n/fr.sh
CHANGED
|
@@ -50,6 +50,7 @@ _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." ;;
|
|
@@ -62,6 +63,7 @@ _msg_fr() {
|
|
|
62
63
|
tk.cli.issue_created) _M="Ticket #%s créé, à valider. Sa branche sera créée à la validation ; ensuite : repowarden ticket %s" ;;
|
|
63
64
|
tk.cli.issue_created_branch) _M="Ticket #%s créé." ;;
|
|
64
65
|
tk.cli.title_required) _M="Titre du ticket obligatoire : repowarden ticket nouveau \"<titre>\"" ;;
|
|
66
|
+
tk.cli.bad_type) _M="Type de ticket inconnu : %s (types : %s)" ;;
|
|
65
67
|
tk.cli.current) _M="Branche %s : ticket #%s." ;;
|
|
66
68
|
tk.cli.no_ticket_here) _M="Aucun ticket pour cette branche." ;;
|
|
67
69
|
cc.ticket_offer) _M="Aucun ticket pour cette branche. En créer un maintenant (titre : la description) ? [o/N] : " ;;
|
|
@@ -109,7 +111,6 @@ _msg_fr() {
|
|
|
109
111
|
# Hooks et plugins de langage
|
|
110
112
|
hook.pre_commit.1) _M="Commit refusé : nom de branche non conforme." ;;
|
|
111
113
|
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
114
|
hook.pre_commit.4) _M="Détection de secrets (gitleaks)" ;;
|
|
114
115
|
hook.pre_commit.5) _M="Secret détecté : commit refusé." ;;
|
|
115
116
|
hook.secrets.where) _M=" %s:%s (%s)" ;;
|
|
@@ -120,7 +121,9 @@ _msg_fr() {
|
|
|
120
121
|
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
122
|
hook.secrets.todo_4) _M=" Faux positif : ajouter son empreinte à .gitleaksignore :" ;;
|
|
122
123
|
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,
|
|
124
|
+
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" ;;
|
|
125
|
+
hook.sensitive.found) _M="Fichier sensible ni suivi ni ignoré : un git add . le publierait. Lignes à ajouter à .gitignore :" ;;
|
|
126
|
+
hook.sensitive.hint) _M=" (noms comparés, contenu jamais lu ; motifs : repowarden.sensitiveFiles ; désactiver : git config repowarden.skip sensitive-files)" ;;
|
|
124
127
|
hook.pre_commit.7) _M="Pre-commit OK" ;;
|
|
125
128
|
hook.commit_msg.1) _M="Première ligne trop longue (%s > 72 caractères)." ;;
|
|
126
129
|
hook.commit_msg.type_unknown) _M="Type « %s » inconnu. Voulais-tu : %s" ;;
|
|
@@ -128,13 +131,16 @@ _msg_fr() {
|
|
|
128
131
|
hook.commit_msg.desc_empty) _M="Description manquante après « %s » : décris le changement à l'impératif." ;;
|
|
129
132
|
hook.commit_msg.kept) _M=" Ton message n'est pas perdu : git commit -e -F .git/COMMIT_EDITMSG" ;;
|
|
130
133
|
hook.commit_msg.dot) _M="Point final retiré de la description (même règle que git cc)." ;;
|
|
134
|
+
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
135
|
hook.pre_push.1) _M="Push refusé : nom de branche non conforme." ;;
|
|
132
136
|
hook.pre_push.2) _M="Push direct refusé sur '%s' : elle n'évolue que par Pull Request." ;;
|
|
133
137
|
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
138
|
hook.pre_push.5) _M="%s : aucun fichier modifié, rien à tester." ;;
|
|
136
139
|
hook.pre_push.6) _M="Rien à tester (suppression de branche uniquement)." ;;
|
|
137
140
|
hook.pre_push.7) _M="Pre-push OK" ;;
|
|
141
|
+
hook.pre_push.msg) _M="Push refusé : message de commit non conforme (même règle que la CI)." ;;
|
|
142
|
+
hook.pre_push.msg_fix) _M=" Corriger : git rebase -i %s (reword), puis relancer le push." ;;
|
|
143
|
+
hook.pre_push.nothing) _M="Rien à pousser." ;;
|
|
138
144
|
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
145
|
hook.pre_push.lint.missing) _M="%s absent, %s non vérifié (installation : %s)." ;;
|
|
140
146
|
hook.pre_push.lint.run) _M="%s : %s" ;;
|
|
@@ -487,6 +493,10 @@ _msg_fr() {
|
|
|
487
493
|
st.repo_version_ok) _M="Version demandée par le projet (%s) : respectée" ;;
|
|
488
494
|
st.repo_forge) _M="Plateforme : %s" ;;
|
|
489
495
|
st.repo_rules) _M="Règles GitHub à jour ? repowarden proteger --verifier (lecture seule)" ;;
|
|
496
|
+
st.wf_owner) _M="%s:%s : repowarden appelé depuis un autre dépôt que le dépôt officiel (%s) ; attendu : %s" ;;
|
|
497
|
+
st.wf_branch) _M="%s:%s : repowarden appelé sur une branche (@%s), cassé dès qu'elle disparaît ; attendu : %s" ;;
|
|
498
|
+
st.wf_old) _M="%s:%s : repowarden appelé dans une majeure dépassée (@%s) ; attendu : %s" ;;
|
|
499
|
+
st.wf_fix) _M="corriger les lignes uses: signalées ci-dessus" ;;
|
|
490
500
|
st.tools) _M="Outils présents : %s · absents : %s (selon les langages des projets)" ;;
|
|
491
501
|
st.next) _M="Étape suivante :" ;;
|
|
492
502
|
st.ready) _M="Tout est prêt : git cc pour commiter, repowarden code-mort sur une branche." ;;
|
|
@@ -542,6 +552,7 @@ _msg_fr() {
|
|
|
542
552
|
bot.ignored) _M="ignoré (bot = off)" ;;
|
|
543
553
|
bot.mode_invalid) _M="bot = %s : auto, pr ou off attendu" ;;
|
|
544
554
|
bot.clone_failed) _M="clone impossible : %s" ;;
|
|
555
|
+
bot.email_fallback) _M="Utilisateur bot %s introuvable : commits signés %s, rattachés à aucun compte (REPOWARDEN_BOT_EMAIL pour fixer l'adresse)." ;;
|
|
545
556
|
bot.prot_ok) _M="à jour" ;;
|
|
546
557
|
bot.prot_stale) _M="⚠ périmée : repowarden proteger" ;;
|
|
547
558
|
bot.prot_applied) _M="✔ appliquée" ;;
|
|
@@ -655,6 +666,7 @@ _msg_fr() {
|
|
|
655
666
|
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
667
|
tk.prerelease) _M="Disponible en préversion **%s** : à tester avant la production." ;;
|
|
657
668
|
tk.released) _M="Publié dans **%s**." ;;
|
|
669
|
+
tk.delivered) _M="Livré sur **%s**, sans nouvelle version (commit %s)." ;;
|
|
658
670
|
# Ticket comment
|
|
659
671
|
tk.comment.adding) _M="Ajout d'un commentaire sur le ticket #%s..." ;;
|
|
660
672
|
tk.comment.added) _M="Commentaire ajouté au ticket #%s." ;;
|
package/hooks/lib/lang.sh
CHANGED
|
@@ -300,8 +300,11 @@ projects_for_files() {
|
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
# Lance les tests de chaque projet touché. Lecture des fichiers modifiés sur stdin.
|
|
303
|
+
# $1 = resume (pre-push) : sortie de chaque lanceur gardée de côté ; succès →
|
|
304
|
+
# sa dernière ligne seule (« 2 passed in 0.5s »), échec → sortie complète. Sans
|
|
305
|
+
# argument (CI) : sortie complète, le journal sert au diagnostic.
|
|
303
306
|
test_projects() {
|
|
304
|
-
local cmd dir p plugins key ran=0
|
|
307
|
+
local mode="${1:-}" cmd dir p plugins key ran=0 out="" line last rc
|
|
305
308
|
cmd="$(cfg test)"
|
|
306
309
|
if [ -n "$cmd" ]; then
|
|
307
310
|
# Fichiers modifiés transmis sur l'entrée standard : la commande peut
|
|
@@ -326,13 +329,31 @@ test_projects() {
|
|
|
326
329
|
ran=1
|
|
327
330
|
if [ "$dir" = . ]; then key="$p"; else key="$p ($dir)"; fi
|
|
328
331
|
info_t hook.lang.5 "$key"
|
|
329
|
-
if
|
|
332
|
+
if [ "$mode" != resume ]; then
|
|
333
|
+
if ! (cd "$dir" && "${p}_test") </dev/null; then
|
|
334
|
+
err_t hook.lang.6 "$key" >&2
|
|
335
|
+
return 1
|
|
336
|
+
fi
|
|
337
|
+
continue
|
|
338
|
+
fi
|
|
339
|
+
[ -n "$out" ] || out="$(mktemp)"
|
|
340
|
+
rc=0
|
|
341
|
+
(cd "$dir" && "${p}_test") </dev/null >"$out" 2>&1 || rc=$?
|
|
342
|
+
if [ "$rc" -ne 0 ]; then
|
|
343
|
+
cat "$out"
|
|
344
|
+
rm -f "$out"
|
|
330
345
|
err_t hook.lang.6 "$key" >&2
|
|
331
346
|
return 1
|
|
332
347
|
fi
|
|
348
|
+
last=""
|
|
349
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
350
|
+
[ -n "${line//[[:space:]]/}" ] && last="$line"
|
|
351
|
+
done <"$out"
|
|
352
|
+
[ -z "$last" ] || printf ' %s\n' "$last"
|
|
333
353
|
done
|
|
334
354
|
done < <(projects_for_files)
|
|
335
355
|
|
|
356
|
+
[ -z "$out" ] || rm -f "$out"
|
|
336
357
|
[ "$ran" = 1 ] || info_t hook.lang.7
|
|
337
358
|
return 0
|
|
338
359
|
}
|
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.
|
|
@@ -171,6 +175,7 @@ ticket_hint() {
|
|
|
171
175
|
branch_ticket_r "$branch"
|
|
172
176
|
[ -z "$REPLY" ] || return 0
|
|
173
177
|
attention_t tk.cli.no_ticket "$branch"
|
|
178
|
+
dim_t tk.cli.explain
|
|
174
179
|
if tickets_managed && has gh; then
|
|
175
180
|
# git cc ne propose de créer le ticket qu'avec ticketAutoCreate
|
|
176
181
|
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
|
@@ -26,7 +26,6 @@ fi
|
|
|
26
26
|
PROTECTED="$(cfg protectedBranches "main master")"
|
|
27
27
|
if ! skipped protect-branch && [[ " $PROTECTED " == *" $BRANCH "* ]] && git rev-parse --verify -q HEAD >/dev/null; then
|
|
28
28
|
err_t hook.pre_commit.2 "$BRANCH" >&2
|
|
29
|
-
echo_t hook.pre_commit.3 >&2
|
|
30
29
|
exit 1
|
|
31
30
|
fi
|
|
32
31
|
|
|
@@ -55,15 +54,18 @@ if ! skipped secrets; then
|
|
|
55
54
|
fi
|
|
56
55
|
rm -f "$rapport" "$journal"
|
|
57
56
|
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
|
|
57
|
+
gitleaks_install_r
|
|
58
|
+
warn_t hook.pre_commit.6 "$REPLY"
|
|
64
59
|
fi
|
|
65
60
|
fi
|
|
66
61
|
|
|
62
|
+
# 2 bis. Fichier sensible ni suivi ni ignoré : un « git add . » le publierait
|
|
63
|
+
# (avertissement seulement, contenu jamais lu)
|
|
64
|
+
if ! skipped secrets && ! skipped sensitive-files; then
|
|
65
|
+
sensitive_files_r
|
|
66
|
+
[ -z "$REPLY" ] || sensitive_files_warn "$REPLY" >&2
|
|
67
|
+
fi
|
|
68
|
+
|
|
67
69
|
# 3. Formatage : chaque fichier stagé avec l'outil de son langage / projet
|
|
68
70
|
if ! skipped format; then
|
|
69
71
|
# Ne formater (et re-stager) que le contenu stagé, jamais le travail en cours
|
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