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/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/pr-checks
CHANGED
|
@@ -21,9 +21,41 @@ if [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
|
|
21
21
|
fi
|
|
22
22
|
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
23
23
|
|
|
24
|
-
n="${1}"
|
|
24
|
+
n="${1#\#}"
|
|
25
25
|
|
|
26
26
|
step_t pr.checks.checking "$n"
|
|
27
|
-
gh
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
# gh pr checks résout le commit de tête de la PR : les check-runs y sont
|
|
28
|
+
# rattachés, pas à la PR. Le verdict se calcule sur les lignes, pas sur son
|
|
29
|
+
# code ; sans ligne, un code non nul est une vraie erreur (PR introuvable,
|
|
30
|
+
# aucun check), déjà expliquée par gh
|
|
31
|
+
rc=0
|
|
32
|
+
lignes="$(gh pr checks "$n" --json name,bucket,link \
|
|
33
|
+
-q '.[] | [.bucket, .name, .link] | @tsv')" || rc=$?
|
|
34
|
+
if [ -z "$lignes" ]; then
|
|
35
|
+
[ "$rc" -ne 0 ] || info_t pr.checks.none "$n"
|
|
36
|
+
exit "$rc"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
reussis=0 echecs=0 attentes=0
|
|
40
|
+
while IFS=$'\t' read -r etat nom lien; do
|
|
41
|
+
case "$etat" in
|
|
42
|
+
pass | skipping) reussis=$((reussis + 1)) ;;
|
|
43
|
+
fail | cancel)
|
|
44
|
+
echecs=$((echecs + 1))
|
|
45
|
+
err_t pr.checks.failed "$nom" "$lien"
|
|
46
|
+
;;
|
|
47
|
+
pending)
|
|
48
|
+
attentes=$((attentes + 1))
|
|
49
|
+
info_t pr.checks.pending "$nom"
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
done <<<"$lignes"
|
|
53
|
+
|
|
54
|
+
if [ "$echecs" -gt 0 ]; then
|
|
55
|
+
err_t pr.checks.verdict "$n" "$reussis" "$echecs" "$attentes"
|
|
56
|
+
exit 1
|
|
57
|
+
elif [ "$attentes" -gt 0 ]; then
|
|
58
|
+
info_t pr.checks.verdict "$n" "$reussis" "$echecs" "$attentes"
|
|
59
|
+
else
|
|
60
|
+
ok_t pr.checks.green "$n"
|
|
61
|
+
fi
|
package/bin/run-list
CHANGED
|
@@ -14,7 +14,7 @@ aide() {
|
|
|
14
14
|
printf '%b\n' "$_T"
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
|
17
|
+
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
18
18
|
aide
|
|
19
19
|
exit 0
|
|
20
20
|
fi
|
|
@@ -34,14 +34,15 @@ done
|
|
|
34
34
|
|
|
35
35
|
repo="${GH_REPO:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
|
|
36
36
|
|
|
37
|
+
# --jq plutôt que --template : le gabarit Go écrit les identifiants en
|
|
38
|
+
# notation scientifique (3.96e+10), inutilisables par run rerun
|
|
39
|
+
filtre=()
|
|
37
40
|
if [ -n "$workflow" ]; then
|
|
38
41
|
step_t run.listing.workflow "$workflow"
|
|
39
|
-
|
|
40
|
-
--json databaseId,name,conclusion,createdAt,headBranch \
|
|
41
|
-
--template '{{range .}}{{.databaseId}} {{.name}} {{.conclusion}} {{.createdAt}} {{.headBranch}}{{"\n"}}{{end}}'
|
|
42
|
+
filtre=(--workflow "$workflow")
|
|
42
43
|
else
|
|
43
44
|
step_t run.listing.all
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
fi
|
|
46
|
+
gh run list --repo "$repo" ${filtre[@]+"${filtre[@]}"} --limit "$limit" \
|
|
47
|
+
--json databaseId,name,conclusion,createdAt,headBranch \
|
|
48
|
+
--jq '.[] | [.databaseId, .name, .conclusion, .createdAt, .headBranch] | @tsv'
|
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
|
@@ -18,7 +18,9 @@ source "$ROOT/hooks/lib/common.sh"
|
|
|
18
18
|
# shellcheck source=../hooks/lib/tickets.sh
|
|
19
19
|
source "$ROOT/hooks/lib/tickets.sh"
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# Aide sur la sortie standard quand elle est demandée (code 0) ; les cas
|
|
22
|
+
# d'erreur la redirigent eux-mêmes vers stderr
|
|
23
|
+
aide() { _tr help.ticket; printf '%b\n' "$_T"; }
|
|
22
24
|
|
|
23
25
|
[ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
|
|
24
26
|
cd "$GIT_TOPLEVEL"
|
|
@@ -62,9 +64,14 @@ cmd_ticket() {
|
|
|
62
64
|
git fetch -q origin --prune
|
|
63
65
|
ticket_remote_branch_r "$n"
|
|
64
66
|
if [ -n "$REPLY" ]; then
|
|
65
|
-
|
|
67
|
+
local existante="$REPLY"
|
|
68
|
+
switch_to "$existante"
|
|
66
69
|
prendre "$n"
|
|
67
|
-
ok_t tk.cli.switched "$
|
|
70
|
+
ok_t tk.cli.switched "$existante" "$n"
|
|
71
|
+
# Faite avant la validation : jamais recréée, son lien va sur le ticket
|
|
72
|
+
if ticket_note_unlinked_branch "$n" "$existante"; then
|
|
73
|
+
attention_t tk.cli.unlinked "$existante" "$n"
|
|
74
|
+
fi
|
|
68
75
|
return 0
|
|
69
76
|
fi
|
|
70
77
|
json="$(gh issue view "$n" --json state,title,labels \
|
|
@@ -97,12 +104,18 @@ cmd_nouveau() {
|
|
|
97
104
|
--type) shift; type="${1:?--type attend un type}" ;;
|
|
98
105
|
--description) shift; description="${1:?--description attend un texte}" ;;
|
|
99
106
|
--porcelain) porcelain=1 ;;
|
|
107
|
+
-h | --help) aide; exit 0 ;;
|
|
100
108
|
-*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
101
109
|
*) titre="$1" ;;
|
|
102
110
|
esac
|
|
103
111
|
shift
|
|
104
112
|
done
|
|
105
113
|
[ -n "$titre" ] || { err_t tk.cli.title_required; exit 1; }
|
|
114
|
+
# Étiquette « type: … » inexistante : gh refuserait de créer le ticket
|
|
115
|
+
if [ -n "$type" ] && tickets_managed && [[ " $TYPES_TICKET " != *" $type "* ]]; then
|
|
116
|
+
err_t tk.cli.bad_type "$type" "${TYPES_TICKET// /, }"
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
106
119
|
gh_ready || exit 1
|
|
107
120
|
if tickets_managed; then
|
|
108
121
|
etiquettes+=(--label "$S_AVALIDER")
|
|
@@ -146,6 +159,8 @@ case "${1:-}" in
|
|
|
146
159
|
;;
|
|
147
160
|
*)
|
|
148
161
|
if [[ "$1" =~ ^#?[0-9]+$ ]]; then
|
|
162
|
+
# ticket <n> --help : l'aide, sans prendre le ticket
|
|
163
|
+
case "${2:-}" in -h | --help) aide; exit 0 ;; esac
|
|
149
164
|
cmd_ticket "${1#\#}"
|
|
150
165
|
else
|
|
151
166
|
err_t install.unknown_arg "$1"
|
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/commun.sh
CHANGED
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
# Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
|
|
6
6
|
# shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
|
|
7
7
|
|
|
8
|
+
# Ligne de description lue comme référence de ticket (« Ticket : #12 », mot-clé
|
|
9
|
+
# de fermeture) : partagée avec la PR brouillon, qui cite ces lignes reprises
|
|
10
|
+
REF_TICKET_RE='^[[:space:]]*(Tickets?|Refs?|Closes|Fixes|Resolves)[[:space:]]*:?[[:space:]]*(.*)$'
|
|
11
|
+
|
|
8
12
|
# REPLY = numéros de tickets (uniques) cités par la description $1 (« Ticket : #12 »,
|
|
9
13
|
# « Tickets : #3, #4 », ou un mot-clé de fermeture) et la branche $2 (type/12-sujet)
|
|
10
14
|
pr_tickets_r() {
|
|
11
15
|
local body="$1" branch="$2" found="" n line
|
|
12
16
|
while IFS= read -r line; do
|
|
13
|
-
if [[ "$line" =~
|
|
17
|
+
if [[ "$line" =~ $REF_TICKET_RE ]]; then
|
|
14
18
|
for n in $(grep -oE '#[0-9]+' <<<"${BASH_REMATCH[2]}" | tr -d '#'); do
|
|
15
19
|
[[ " $found " == *" $n "* ]] || found="$found $n"
|
|
16
20
|
done
|
|
@@ -53,10 +57,31 @@ statut() {
|
|
|
53
57
|
gh issue edit "$n" --add-label "$s" ${retirer[@]+"${retirer[@]}"} >/dev/null
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
# Pied des commentaires du bot : l'avatar d'une App mène à une page 404 sur
|
|
61
|
+
# GitHub (pas de profil), le pied dit d'où vient le bot. Repère invisible : un
|
|
62
|
+
# texte déjà signé (repris, mis à jour) n'en reçoit pas un second.
|
|
63
|
+
PIED="<!-- repowarden:pied -->"
|
|
64
|
+
|
|
65
|
+
# REPLY = texte $1 terminé par le pied (page de l'App APP_SLUG, doc) ; sans
|
|
66
|
+
# slug (jeton des Actions, github-actions[bot]) ou déjà signé, texte inchangé
|
|
67
|
+
pied_r() {
|
|
68
|
+
REPLY="$1"
|
|
69
|
+
[ -n "${APP_SLUG:-}" ] && [[ "$REPLY" != *"$PIED"* ]] || return 0
|
|
70
|
+
_tr tk.footer "$APP_SLUG" "$APP_SLUG"
|
|
71
|
+
REPLY="$REPLY"$'\n\n'"$PIED<sub>$_T</sub>"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Commentaire $2 du bot sur le ticket $1, avec son pied
|
|
75
|
+
commenter() {
|
|
76
|
+
pied_r "$2"
|
|
77
|
+
gh issue comment "$1" --body "$REPLY" >/dev/null
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
# Ferme la PR $1 avec le commentaire $2 ; déjà fermée ou mergée entre-temps
|
|
57
81
|
# (merge pendant le run) : rien à faire, pas un échec
|
|
58
82
|
fermer_pr() {
|
|
59
|
-
|
|
83
|
+
pied_r "$2"
|
|
84
|
+
gh pr close "$1" --comment "$REPLY" >/dev/null 2>&1 && return 0
|
|
60
85
|
[ "$(gh pr view "$1" --json state -q .state 2>/dev/null)" != OPEN ]
|
|
61
86
|
}
|
|
62
87
|
|
package/ci/tickets/issue.sh
CHANGED
|
@@ -7,17 +7,24 @@
|
|
|
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
|
-
# pour plus tard n'a pas de branche qui vieillit en attendant
|
|
16
|
-
|
|
20
|
+
# pour plus tard n'a pas de branche qui vieillit en attendant. Une branche
|
|
21
|
+
# déjà faite à son nom (avant la validation) est reprise, jamais recréée.
|
|
22
|
+
branche_du_ticket_r "$n"
|
|
23
|
+
if [ -n "$REPLY" ] || [ -n "$(assignes "$n")" ]; then
|
|
17
24
|
creer_branche "$n"
|
|
18
25
|
else
|
|
19
26
|
_tr tk.validated_waiting "$n"
|
|
20
|
-
|
|
27
|
+
commenter "$n" "$_T"
|
|
21
28
|
fi
|
|
22
29
|
while IFS=$'\t' read -r pr head sha base brouillon; do
|
|
23
30
|
[ -n "$pr" ] || continue
|
|
@@ -36,7 +43,8 @@ accepter() {
|
|
|
36
43
|
# GitHub ne garde qu'un run en attente par groupe de concurrence, le run
|
|
37
44
|
# « opened » peut être annulé par ceux des étiquettes posées à la création.
|
|
38
45
|
# Non si déjà validé, ou (hors ouverture) rangé au-delà de « à valider »
|
|
39
|
-
# (« validé » retiré à la main : décision respectée)
|
|
46
|
+
# (« validé » retiré à la main : décision respectée), ou proposé à décider
|
|
47
|
+
# (étiquette « à décider » : le mainteneur pose « validé »). Test seul : appelé dans un « if », où set -e
|
|
40
48
|
# ne s'applique pas.
|
|
41
49
|
a_valider_d_office() {
|
|
42
50
|
local labels l
|
|
@@ -45,6 +53,7 @@ a_valider_d_office() {
|
|
|
45
53
|
[ "${ISSUE_STATE:-open}" = open ] || return 1
|
|
46
54
|
labels="$(etiquettes "$ISSUE")"
|
|
47
55
|
! grep -qxF "$VALIDE" <<<"$labels" || return 1
|
|
56
|
+
! grep -qxF "$A_DECIDER" <<<"$labels" || return 1
|
|
48
57
|
[ "$ACTION" != opened ] || return 0
|
|
49
58
|
while IFS= read -r l; do
|
|
50
59
|
[[ "$l" == "statut: "* && "$l" != "$S_AVALIDER" ]] && return 1
|
|
@@ -110,7 +119,7 @@ cmd_issue() {
|
|
|
110
119
|
gh issue reopen "$ISSUE" >/dev/null
|
|
111
120
|
statut "$ISSUE" "$S_PREPROD"
|
|
112
121
|
_tr tk.reopened "$fpr" "$fbase"
|
|
113
|
-
|
|
122
|
+
commenter "$ISSUE" "$_T"
|
|
114
123
|
notice "$_T"
|
|
115
124
|
return 0
|
|
116
125
|
fi
|
package/ci/tickets/pr.sh
CHANGED
package/ci/tickets/push.sh
CHANGED
|
@@ -90,15 +90,128 @@ criteres_coches_r() {
|
|
|
90
90
|
REPLY="${sortie%$'\n'}"
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
93
|
+
# Fin de chaque commentaire lu d'un bloc (séparateur ASCII) : un texte écrit à
|
|
94
|
+
# la main ne le contient pas, les commentaires se découpent sans ambiguïté
|
|
95
|
+
SEP_COMMENTAIRE=$'\x1e'
|
|
96
|
+
|
|
97
|
+
# REPLY = commentaires de mainteneur du ticket $1, chacun suivi d'une ligne
|
|
98
|
+
# SEP_COMMENTAIRE : comme la conception, seul un mainteneur alimente la PR
|
|
99
|
+
commentaires_mainteneur_r() {
|
|
100
|
+
REPLY="$(gh issue view "$1" --json comments -q '.comments[]
|
|
101
|
+
| select(.authorAssociation == "OWNER" or .authorAssociation == "MEMBER" or .authorAssociation == "COLLABORATOR")
|
|
102
|
+
| .body + "\n\u001e"' 2>/dev/null || true)"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# $1 = ligne d'un commentaire → REPLY : titre décalé d'un niveau (rangé sous la
|
|
106
|
+
# section du bot), ligne citée si pr_tickets_r la lirait : seul le pied compte
|
|
107
|
+
ligne_reprise_r() {
|
|
108
|
+
REPLY="$1"
|
|
109
|
+
[[ ! "$REPLY" =~ ^#{1,5}[[:space:]] ]] || REPLY="#$REPLY"
|
|
110
|
+
[[ ! "$REPLY" =~ $REF_TICKET_RE ]] || REPLY="> $REPLY"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# $1 = texte → REPLY : sans ses lignes vides finales
|
|
114
|
+
sans_fin_vide_r() {
|
|
115
|
+
REPLY="$1"
|
|
116
|
+
while [[ "$REPLY" == *$'\n' ]]; do REPLY="${REPLY%$'\n'}"; done
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Première ligne d'un commentaire d'ajout : titre ou gras « Ajout… », « Constat lié… »
|
|
120
|
+
AJOUT_RE='^(#{1,6}[[:space:]]+|\*\*)?([Aa]jouts?|[Cc]onstats?[[:space:]]+li(é|e)s?)([[:space:]:*(.,-]|$)'
|
|
121
|
+
|
|
122
|
+
# $1 = commentaires (commentaires_mainteneur_r) → REPLY : ceux dont la première
|
|
123
|
+
# ligne est un titre ou un gras « Ajout » / « Constat lié », dans l'ordre :
|
|
124
|
+
# les ajouts faits au ticket après coup, que le corps du ticket n'a pas
|
|
125
|
+
ajouts_ticket_r() {
|
|
126
|
+
local ligne bloc="" premiere="" sortie=""
|
|
127
|
+
while IFS= read -r ligne; do
|
|
128
|
+
ligne="${ligne%$'\r'}"
|
|
129
|
+
if [ "$ligne" = "$SEP_COMMENTAIRE" ]; then
|
|
130
|
+
if [[ "$premiere" =~ $AJOUT_RE ]]; then
|
|
131
|
+
sans_fin_vide_r "$bloc"
|
|
132
|
+
sortie="$sortie${sortie:+$'\n\n'}$REPLY"
|
|
133
|
+
fi
|
|
134
|
+
bloc="" premiere=""
|
|
135
|
+
continue
|
|
136
|
+
fi
|
|
137
|
+
if [ -z "$premiere" ]; then
|
|
138
|
+
[ -n "${ligne//[[:space:]]/}" ] || continue
|
|
139
|
+
premiere="$ligne"
|
|
140
|
+
fi
|
|
141
|
+
ligne_reprise_r "$ligne"
|
|
142
|
+
bloc="$bloc$REPLY"$'\n'
|
|
143
|
+
done <<<"$1"$'\n'"$SEP_COMMENTAIRE"
|
|
144
|
+
REPLY="$sortie"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# $1 = commentaires → REPLY : du dernier « Conception retenue », les blocs « Ce
|
|
148
|
+
# qui change » et « Hors périmètre » (titre ou gras, jusqu'au libellé suivant) :
|
|
149
|
+
# le périmètre sans les détails (fichiers, tests) que montre déjà le diff
|
|
150
|
+
resume_conception_r() {
|
|
151
|
+
local ligne bloc="" dernier="" garder=0 conception=0
|
|
152
|
+
while IFS= read -r ligne; do
|
|
153
|
+
ligne="${ligne%$'\r'}"
|
|
154
|
+
if [ "$ligne" = "$SEP_COMMENTAIRE" ]; then
|
|
155
|
+
[ "$conception" = 0 ] || dernier="$bloc"
|
|
156
|
+
bloc="" garder=0 conception=0
|
|
157
|
+
continue
|
|
158
|
+
fi
|
|
159
|
+
if [[ "$ligne" =~ ^(#{1,6}[[:space:]]|\*\*) ]]; then
|
|
160
|
+
garder=0
|
|
161
|
+
case "$ligne" in
|
|
162
|
+
*[Cc]onception\ retenue*) conception=1; continue ;;
|
|
163
|
+
*[Cc]e\ qui\ change* | *[Hh]ors\ p*) garder=1 ;;
|
|
164
|
+
esac
|
|
165
|
+
fi
|
|
166
|
+
[ "$garder" = 1 ] || continue
|
|
167
|
+
ligne_reprise_r "$ligne"
|
|
168
|
+
bloc="$bloc$REPLY"$'\n'
|
|
169
|
+
done <<<"$1"$'\n'"$SEP_COMMENTAIRE"
|
|
170
|
+
sans_fin_vide_r "$dernier"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# $1 = description actuelle de la PR → REPLY : sa dernière section « ## Réalisé »
|
|
174
|
+
# (jusqu'au titre suivant ou au pied), écrite par l'auteur : reprise telle quelle
|
|
175
|
+
section_realise_r() {
|
|
176
|
+
local ligne dedans=0 section=""
|
|
177
|
+
while IFS= read -r ligne; do
|
|
178
|
+
ligne="${ligne%$'\r'}"
|
|
179
|
+
if [[ "$ligne" =~ ^##[[:space:]]+R(é|e)alis(é|e) ]]; then
|
|
180
|
+
dedans=1 section="$ligne"$'\n'
|
|
181
|
+
elif [ "$dedans" = 1 ]; then
|
|
182
|
+
if [[ "$ligne" =~ ^#{1,2}[[:space:]] || "$ligne" == "Ticket : #"* ]]; then
|
|
183
|
+
dedans=0
|
|
184
|
+
else
|
|
185
|
+
section="$section$ligne"$'\n'
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
188
|
+
done <<<"$1"
|
|
189
|
+
sans_fin_vide_r "$section"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# REPLY = description de la PR brouillon du ticket $1 (branche $2, PR $3 si
|
|
193
|
+
# elle existe) : le ticket (critères en cases à cocher), ses ajouts en
|
|
194
|
+
# commentaire, le résumé de sa conception, les commits, la section « Réalisé »
|
|
195
|
+
# de l'auteur gardée, puis « Ticket : #N ». Le bot ne régénère que ses sections.
|
|
95
196
|
description_pr_brouillon_r() {
|
|
96
|
-
local n="$1" branche="$2" corps commits
|
|
197
|
+
local n="$1" branche="$2" pr="${3:-}" corps commits commentaires
|
|
97
198
|
criteres_coches_r "$(gh issue view "$n" --json body -q .body)"
|
|
98
199
|
corps="$REPLY"
|
|
200
|
+
commentaires_mainteneur_r "$n"
|
|
201
|
+
commentaires="$REPLY"
|
|
202
|
+
ajouts_ticket_r "$commentaires"
|
|
203
|
+
_tr tk.pr_additions
|
|
204
|
+
[ -z "$REPLY" ] || corps="$corps"$'\n\n## '"$_T"$'\n'"$REPLY"
|
|
205
|
+
resume_conception_r "$commentaires"
|
|
206
|
+
_tr tk.pr_design
|
|
207
|
+
[ -z "$REPLY" ] || corps="$corps"$'\n\n## '"$_T"$'\n'"$REPLY"
|
|
99
208
|
commits="$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$branche" \
|
|
100
209
|
-q '.commits[] | "- \(.sha[0:7]) \(.commit.message | split("\n")[0])"' 2>/dev/null || true)"
|
|
101
210
|
[ -z "$commits" ] || corps="$corps"$'\n\n## Commits\n'"$commits"
|
|
211
|
+
if [ -n "$pr" ]; then
|
|
212
|
+
section_realise_r "$(gh pr view "$pr" --json body -q .body 2>/dev/null || true)"
|
|
213
|
+
[ -z "$REPLY" ] || corps="$corps"$'\n\n'"$REPLY"
|
|
214
|
+
fi
|
|
102
215
|
REPLY="$corps"$'\n\nTicket : #'"$n"
|
|
103
216
|
}
|
|
104
217
|
|
|
@@ -124,7 +237,7 @@ cmd_push() {
|
|
|
124
237
|
[ "$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$PUSH_BRANCH" -q .ahead_by)" != 0 ] || return 0
|
|
125
238
|
titre_pr_brouillon_r "$n" "$PUSH_BRANCH"
|
|
126
239
|
titre="$REPLY"
|
|
127
|
-
description_pr_brouillon_r "$n" "$PUSH_BRANCH"
|
|
240
|
+
description_pr_brouillon_r "$n" "$PUSH_BRANCH" "$pr"
|
|
128
241
|
corps="$REPLY"
|
|
129
242
|
if [ -n "$pr" ]; then
|
|
130
243
|
gh pr edit "$pr" --title "$titre" --body "$corps" >/dev/null
|