repowarden 0.0.0-stage → 4.1.0
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/LICENSE +21 -0
- package/README.md +103 -2
- package/bin/code-mort +33 -0
- package/bin/commit +412 -0
- package/bin/npm-publication +151 -0
- package/bin/proteger +283 -0
- package/bin/repogarde +20 -0
- package/bin/repowarden +63 -0
- package/bin/statut +100 -0
- package/bin/ticket +129 -0
- package/bin/tickets +135 -0
- package/hooks/commit-msg +64 -0
- package/hooks/lang/actions.sh +9 -0
- package/hooks/lang/ansible.sh +18 -0
- package/hooks/lang/cpp.sh +22 -0
- package/hooks/lang/dart.sh +20 -0
- package/hooks/lang/deno.sh +15 -0
- package/hooks/lang/docker.sh +26 -0
- package/hooks/lang/dotnet.sh +15 -0
- package/hooks/lang/elixir.sh +13 -0
- package/hooks/lang/go.sh +22 -0
- package/hooks/lang/gradle.sh +28 -0
- package/hooks/lang/helm.sh +28 -0
- package/hooks/lang/kubernetes.sh +29 -0
- package/hooks/lang/maven.sh +27 -0
- package/hooks/lang/node.sh +75 -0
- package/hooks/lang/packer.sh +16 -0
- package/hooks/lang/php.sh +20 -0
- package/hooks/lang/python.sh +79 -0
- package/hooks/lang/ruby.sh +24 -0
- package/hooks/lang/rust.sh +17 -0
- package/hooks/lang/shell.sh +9 -0
- package/hooks/lang/swift.sh +20 -0
- package/hooks/lang/tasks.sh +16 -0
- package/hooks/lang/terraform.sh +29 -0
- package/hooks/lib/common.sh +393 -0
- package/hooks/lib/compat.sh +24 -0
- package/hooks/lib/deadcode.sh +153 -0
- package/hooks/lib/forge.sh +39 -0
- package/hooks/lib/i18n/en.sh +393 -0
- package/hooks/lib/i18n/fr.sh +393 -0
- package/hooks/lib/i18n.sh +81 -0
- package/hooks/lib/lang.sh +338 -0
- package/hooks/lib/safety.sh +108 -0
- package/hooks/lib/tickets.sh +184 -0
- package/hooks/lib/ui.sh +40 -0
- package/hooks/post-checkout +26 -0
- package/hooks/post-merge +47 -0
- package/hooks/pre-commit +58 -0
- package/hooks/pre-push +91 -0
- package/hooks/prepare-commit-msg +48 -0
- package/install.sh +245 -0
- package/package.json +31 -4
package/bin/ticket
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Travailler à partir d'un ticket : le ticket d'abord, la branche en découle.
|
|
3
|
+
# repowarden ticket <n> prend le ticket (assigné à soi) et se place sur
|
|
4
|
+
# sa branche ; la crée, liée au ticket, au besoin
|
|
5
|
+
# repowarden ticket nouveau "<titre>" [--type fix] [--description "…"]
|
|
6
|
+
# crée le ticket (« à valider » si le suivi
|
|
7
|
+
# est géré par repowarden, sinon avec sa branche)
|
|
8
|
+
# repowarden ticket ticket de la branche courante
|
|
9
|
+
# Demande un gh connecté (https://cli.github.com).
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
13
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
14
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
15
|
+
# shellcheck source=../hooks/lib/tickets.sh
|
|
16
|
+
source "$ROOT/hooks/lib/tickets.sh"
|
|
17
|
+
|
|
18
|
+
aide() { sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'; }
|
|
19
|
+
|
|
20
|
+
[ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
|
|
21
|
+
cd "$GIT_TOPLEVEL"
|
|
22
|
+
|
|
23
|
+
# Se place sur la branche $1 (locale, ou suivie depuis origin)
|
|
24
|
+
switch_to() {
|
|
25
|
+
if git show-ref -q --verify "refs/heads/$1"; then
|
|
26
|
+
git switch -q "$1"
|
|
27
|
+
else
|
|
28
|
+
git switch -q --track -c "$1" "origin/$1"
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Prendre le ticket $1 : assigné à soi (le suivi le passe en cours)
|
|
33
|
+
prendre() { gh issue edit "$1" --add-assignee @me >/dev/null 2>&1 || true; }
|
|
34
|
+
|
|
35
|
+
cmd_ticket() {
|
|
36
|
+
local n="$1" json etat titre etiquettes base
|
|
37
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
38
|
+
step_t tk.cli.fetch "$n"
|
|
39
|
+
git fetch -q origin --prune
|
|
40
|
+
ticket_remote_branch_r "$n"
|
|
41
|
+
if [ -n "$REPLY" ]; then
|
|
42
|
+
switch_to "$REPLY"
|
|
43
|
+
prendre "$n"
|
|
44
|
+
ok_t tk.cli.switched "$REPLY" "$n"
|
|
45
|
+
return 0
|
|
46
|
+
fi
|
|
47
|
+
json="$(gh issue view "$n" --json state,title,labels \
|
|
48
|
+
-q '[.state, .title, ([.labels[].name] | join("\n"))] | @tsv')"
|
|
49
|
+
IFS=$'\t' read -r etat titre etiquettes <<<"$json"
|
|
50
|
+
etiquettes="${etiquettes//\\n/$'\n'}"
|
|
51
|
+
if [ "$etat" != OPEN ]; then
|
|
52
|
+
err_t tk.cli.closed "$n"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
# Suivi géré par repowarden : la branche naît de la validation
|
|
56
|
+
if tickets_managed && ! grep -qxF "$VALIDE" <<<"$etiquettes"; then
|
|
57
|
+
err_t tk.cli.not_validated "$n" "$VALIDE"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
ticket_type_r "$etiquettes"
|
|
61
|
+
ticket_branch_r "$n" "$titre" "$REPLY"
|
|
62
|
+
local branche="$REPLY"
|
|
63
|
+
ticket_base_r
|
|
64
|
+
base="$REPLY"
|
|
65
|
+
gh issue develop "$n" --name "$branche" --base "$base" --checkout >/dev/null
|
|
66
|
+
prendre "$n"
|
|
67
|
+
ok_t tk.cli.created_branch "$branche" "$base" "$n"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
cmd_nouveau() {
|
|
71
|
+
local titre="" type="" description="" porcelain="" url n etiquettes=()
|
|
72
|
+
while [ $# -gt 0 ]; do
|
|
73
|
+
case "$1" in
|
|
74
|
+
--type) shift; type="${1:?--type attend un type}" ;;
|
|
75
|
+
--description) shift; description="${1:?--description attend un texte}" ;;
|
|
76
|
+
--porcelain) porcelain=1 ;;
|
|
77
|
+
-*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
78
|
+
*) titre="$1" ;;
|
|
79
|
+
esac
|
|
80
|
+
shift
|
|
81
|
+
done
|
|
82
|
+
[ -n "$titre" ] || { err_t tk.cli.title_required; exit 1; }
|
|
83
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
84
|
+
if tickets_managed; then
|
|
85
|
+
etiquettes+=(--label "$S_AVALIDER")
|
|
86
|
+
[ -z "$type" ] || etiquettes+=(--label "type: $type")
|
|
87
|
+
fi
|
|
88
|
+
_tr tk.cli.issue_body
|
|
89
|
+
url="$(gh issue create --title "$titre" --body "${description:-$_T}" ${etiquettes[@]+"${etiquettes[@]}"})"
|
|
90
|
+
n="${url##*/}"
|
|
91
|
+
if [ -n "$porcelain" ]; then
|
|
92
|
+
echo "$n"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
if tickets_managed; then
|
|
96
|
+
ok_t tk.cli.issue_created "$n" "$n"
|
|
97
|
+
else
|
|
98
|
+
# Sans suivi automatique, pas d'étape de validation : la branche suit
|
|
99
|
+
ok_t tk.cli.issue_created_branch "$n"
|
|
100
|
+
ticket_branch_r "$n" "$titre" "${type:-feat}"
|
|
101
|
+
local branche="$REPLY"
|
|
102
|
+
ticket_base_r
|
|
103
|
+
gh issue develop "$n" --name "$branche" --base "$REPLY" --checkout >/dev/null
|
|
104
|
+
ok_t tk.cli.created_branch "$branche" "$REPLY" "$n"
|
|
105
|
+
fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
case "${1:-}" in
|
|
109
|
+
-h | --help | help) aide ;;
|
|
110
|
+
nouveau | new) shift; cmd_nouveau "$@" ;;
|
|
111
|
+
"")
|
|
112
|
+
branche="$(git symbolic-ref --short HEAD 2>/dev/null || echo "")"
|
|
113
|
+
branch_ticket_r "$branche"
|
|
114
|
+
if [ -n "$REPLY" ]; then
|
|
115
|
+
info_t tk.cli.current "$branche" "$REPLY"
|
|
116
|
+
else
|
|
117
|
+
ticket_hint "$branche"
|
|
118
|
+
fi
|
|
119
|
+
;;
|
|
120
|
+
*)
|
|
121
|
+
if [[ "$1" =~ ^#?[0-9]+$ ]]; then
|
|
122
|
+
cmd_ticket "${1#\#}"
|
|
123
|
+
else
|
|
124
|
+
err_t install.unknown_arg "$1"
|
|
125
|
+
aide >&2
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
;;
|
|
129
|
+
esac
|
package/bin/tickets
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Prépare le suivi des tickets d'un dépôt GitHub (relançable sans risque) :
|
|
3
|
+
# repowarden tickets init [--repo propriétaire/nom] [--sans-validation-auto]
|
|
4
|
+
# repowarden tickets sync branches locales des tickets qui me sont assignés
|
|
5
|
+
# (aussi automatique : réglage ticketSync = auto|manual|off)
|
|
6
|
+
# - étiquettes : « validé », les statuts (à valider, backlog, en cours,
|
|
7
|
+
# en relecture, préprod, done) et les types (type: feat, type: fix…), qui
|
|
8
|
+
# donnent le préfixe de la branche du ticket ;
|
|
9
|
+
# - modèle de ticket .github/ISSUE_TEMPLATE/ticket.yml (créé « à valider ») ;
|
|
10
|
+
# - workflow .github/workflows/tickets.yml (s'ils n'existent pas déjà) ;
|
|
11
|
+
# - tickets existants : sans statut, rangés (validé ou ouverts par un
|
|
12
|
+
# mainteneur → backlog, sinon à valider ; --sans-validation-auto : tous à
|
|
13
|
+
# valider) ; branches des PR ouvertes et mergées inscrites sur leurs tickets.
|
|
14
|
+
# Ensuite : committer ces fichiers, exiger la vérification « ticket »
|
|
15
|
+
# (repowarden proteger --checks "…,ticket") et brancher le job tickets de release.yml.
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
19
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
20
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
21
|
+
|
|
22
|
+
REPO=""
|
|
23
|
+
commande="${1:-}"
|
|
24
|
+
[ $# -eq 0 ] || shift
|
|
25
|
+
while [ $# -gt 0 ]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--repo) shift; REPO="${1:?--repo attend propriétaire/nom}" ;;
|
|
28
|
+
--sans-validation-auto) AUTO_VALIDATE=never ;;
|
|
29
|
+
*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
30
|
+
esac
|
|
31
|
+
shift
|
|
32
|
+
done
|
|
33
|
+
if [ "$commande" = sync ]; then
|
|
34
|
+
# shellcheck source=../hooks/lib/tickets.sh
|
|
35
|
+
source "$ROOT/hooks/lib/tickets.sh"
|
|
36
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
37
|
+
[ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
|
|
38
|
+
cd "$GIT_TOPLEVEL"
|
|
39
|
+
tickets_sync force
|
|
40
|
+
ok_t tk.sync.done
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
if [ "$commande" != init ]; then
|
|
44
|
+
sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'
|
|
45
|
+
[ "$commande" = "" ] || [ "$commande" = -h ] || [ "$commande" = --help ]
|
|
46
|
+
exit
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
50
|
+
[ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
|
|
51
|
+
cd "$GIT_TOPLEVEL"
|
|
52
|
+
[ -n "$REPO" ] || REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner)"
|
|
53
|
+
brand "tickets · $REPO"
|
|
54
|
+
|
|
55
|
+
# 1. Étiquettes (créées ou mises à jour)
|
|
56
|
+
step_t tk.init_labels
|
|
57
|
+
while IFS='|' read -r nom couleur description; do
|
|
58
|
+
gh label create "$nom" --repo "$REPO" --color "$couleur" --description "$description" --force >/dev/null
|
|
59
|
+
echo " $nom"
|
|
60
|
+
done <<'ETIQUETTES'
|
|
61
|
+
validé|0e8a16|Ticket accepté par un mainteneur (débloque ses PR)
|
|
62
|
+
statut: à valider|fbca04|Ticket créé, en attente de validation
|
|
63
|
+
statut: backlog|c5def5|Validé, pas encore commencé
|
|
64
|
+
statut: en cours|1d76db|Travail commencé (PR en brouillon)
|
|
65
|
+
statut: en relecture|5319e7|PR prête, en relecture
|
|
66
|
+
statut: préprod|d93f0b|Mergé dans develop : en préversion de test
|
|
67
|
+
statut: done|0e8a16|Publié en production
|
|
68
|
+
type: feat|a2eeef|Nouvelle fonctionnalité (branche feat/…)
|
|
69
|
+
type: fix|d73a4a|Correction de bug (branche fix/…)
|
|
70
|
+
type: docs|0075ca|Documentation (branche docs/…)
|
|
71
|
+
type: refactor|cfd3d7|Restructuration (branche refactor/…)
|
|
72
|
+
type: chore|ededed|Maintenance (branche chore/…)
|
|
73
|
+
ETIQUETTES
|
|
74
|
+
|
|
75
|
+
# 2. Modèle de ticket et workflow (jamais écrasés)
|
|
76
|
+
ecrire() { # $1 chemin ; contenu sur l'entrée standard
|
|
77
|
+
if [ -e "$1" ]; then
|
|
78
|
+
info_t tk.init_exists "$1"
|
|
79
|
+
cat >/dev/null
|
|
80
|
+
else
|
|
81
|
+
mkdir -p "$(dirname "$1")"
|
|
82
|
+
cat >"$1"
|
|
83
|
+
ok_t tk.init_written "$1"
|
|
84
|
+
fi
|
|
85
|
+
}
|
|
86
|
+
step_t tk.init_files
|
|
87
|
+
ecrire .github/ISSUE_TEMPLATE/ticket.yml <<'YAML'
|
|
88
|
+
name: Ticket
|
|
89
|
+
description: Une tâche à réaliser (fonctionnalité, correction, maintenance)
|
|
90
|
+
labels: ["statut: à valider"]
|
|
91
|
+
body:
|
|
92
|
+
- type: textarea
|
|
93
|
+
id: besoin
|
|
94
|
+
attributes:
|
|
95
|
+
label: Besoin
|
|
96
|
+
description: Le problème à résoudre, pas seulement la solution imaginée
|
|
97
|
+
validations:
|
|
98
|
+
required: true
|
|
99
|
+
- type: textarea
|
|
100
|
+
id: termine
|
|
101
|
+
attributes:
|
|
102
|
+
label: Terminé quand…
|
|
103
|
+
description: Critères d'acceptation, vérifiables
|
|
104
|
+
YAML
|
|
105
|
+
ecrire .github/workflows/tickets.yml <<'YAML'
|
|
106
|
+
# Suivi des tickets : le ticket d'abord, la branche en découle (créée à la
|
|
107
|
+
# validation, liée au ticket), le premier push ouvre la PR en brouillon ;
|
|
108
|
+
# statuts tenus à jour jusqu'à la production.
|
|
109
|
+
name: tickets
|
|
110
|
+
|
|
111
|
+
on:
|
|
112
|
+
pull_request_target:
|
|
113
|
+
types: [opened, reopened, edited, synchronize, ready_for_review, converted_to_draft, closed]
|
|
114
|
+
pull_request_review:
|
|
115
|
+
types: [submitted]
|
|
116
|
+
issues:
|
|
117
|
+
types: [opened, labeled, assigned, closed]
|
|
118
|
+
push:
|
|
119
|
+
branches-ignore: [main, master, develop]
|
|
120
|
+
|
|
121
|
+
permissions: {}
|
|
122
|
+
|
|
123
|
+
jobs:
|
|
124
|
+
tickets:
|
|
125
|
+
uses: SimBienvenueHoulBoumi/repowarden/.github/workflows/tickets.yml@v4
|
|
126
|
+
permissions: { contents: write, issues: write, pull-requests: write, statuses: write }
|
|
127
|
+
YAML
|
|
128
|
+
|
|
129
|
+
# 3. Tickets et branches existants (relançable : rien n'est fait deux fois)
|
|
130
|
+
step_t tk.init_adopt
|
|
131
|
+
GH_REPO="$REPO" AUTO_VALIDATE="${AUTO_VALIDATE:-maintainers}" "$ROOT/ci/tickets.sh" adopter | sed 's/^/ /'
|
|
132
|
+
|
|
133
|
+
echo
|
|
134
|
+
ok_t tk.init_done
|
|
135
|
+
dim_t tk.init_next
|
package/hooks/commit-msg
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Hook commit-msg : impose le format Conventional Commits.
|
|
3
|
+
# Ex : "feat(auth): ajoute la connexion JWT", "fix: corrige le calcul de TVA"
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
source "$(dirname "$0")/lib/common.sh"
|
|
7
|
+
exit_if_skipped
|
|
8
|
+
run_local_hook "$@"
|
|
9
|
+
|
|
10
|
+
MSG_FILE="$1"
|
|
11
|
+
# Première ligne non commentée
|
|
12
|
+
SUBJECT="$(grep -v '^#' "$MSG_FILE" | sed -n 1p)"
|
|
13
|
+
|
|
14
|
+
# Commits générés par git : on laisse passer
|
|
15
|
+
if [[ "$SUBJECT" =~ ^(Merge|Revert|fixup!|squash!) ]]; then
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if ! [[ "$SUBJECT" =~ $CC_PATTERN ]]; then
|
|
20
|
+
if [ "${REPOWARDEN_LANG:-}" = en ]; then
|
|
21
|
+
cat >&2 <<EOF
|
|
22
|
+
✖ Invalid commit message: "$SUBJECT"
|
|
23
|
+
|
|
24
|
+
Expected format: <type>(<optional scope>): <description>
|
|
25
|
+
e.g.: feat(user): add sign-up
|
|
26
|
+
fix: fix the database timeout
|
|
27
|
+
refactor!: change the public API (! = breaking change)
|
|
28
|
+
|
|
29
|
+
Types:
|
|
30
|
+
EOF
|
|
31
|
+
types_help
|
|
32
|
+
cat >&2 <<EOF
|
|
33
|
+
|
|
34
|
+
👉 Tip: name your branch <type>/<topic> (e.g. feat/user) and the prefix
|
|
35
|
+
is added automatically: git commit -m "add sign-up" (or use git cc)
|
|
36
|
+
Your message is not lost: git commit -e -F .git/COMMIT_EDITMSG
|
|
37
|
+
EOF
|
|
38
|
+
else
|
|
39
|
+
cat >&2 <<EOF
|
|
40
|
+
✖ Message de commit invalide : "$SUBJECT"
|
|
41
|
+
|
|
42
|
+
Format attendu : <type>(<scope optionnel>): <description>
|
|
43
|
+
ex. : feat(user): ajoute l'inscription
|
|
44
|
+
fix: corrige le timeout de la base
|
|
45
|
+
refactor!: change l'API publique (! = breaking change)
|
|
46
|
+
|
|
47
|
+
Types :
|
|
48
|
+
EOF
|
|
49
|
+
types_help
|
|
50
|
+
cat >&2 <<EOF
|
|
51
|
+
|
|
52
|
+
👉 Astuce : nomme ta branche <type>/<sujet> (ex. feat/user) et le préfixe
|
|
53
|
+
sera ajouté automatiquement : git commit -m "ajoute l'inscription" (ou git cc)
|
|
54
|
+
Ton message n'est pas perdu : git commit -e -F .git/COMMIT_EDITMSG
|
|
55
|
+
EOF
|
|
56
|
+
fi
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
authored_length "$SUBJECT"
|
|
61
|
+
if [ "$REPLY" -gt 72 ]; then
|
|
62
|
+
err_t hook.commit_msg.1 "$REPLY" >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Workflows GitHub Actions : actionlint au push / en CI
|
|
3
|
+
register actions ".github/workflows" '^$'
|
|
4
|
+
|
|
5
|
+
actions_test() {
|
|
6
|
+
has actionlint || { warn "GitHub Actions : actionlint absent, workflows non vérifiés."; return 0; }
|
|
7
|
+
step_t lang.actions.1
|
|
8
|
+
actionlint
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Ansible (playbooks, rôles, collections) : reformatage YAML par ansible-lint,
|
|
3
|
+
# règles ansible-lint vérifiées au push / en CI.
|
|
4
|
+
register ansible "ansible.cfg galaxy.yml requirements.yml .ansible-lint .ansible-lint.yml" '\.ya?ml$'
|
|
5
|
+
|
|
6
|
+
ansible_format() {
|
|
7
|
+
has ansible-lint || { tool_missing "Ansible : ansible-lint absent, formatage ignoré."; return 0; }
|
|
8
|
+
step_t lang.ansible.1
|
|
9
|
+
# --fix=none : reformatage YAML seul ; les violations de règles sont
|
|
10
|
+
# signalées par ansible_test (push / CI), pas au commit.
|
|
11
|
+
ansible-lint -q --nocolor --fix=none "$@" >/dev/null 2>&1 || true
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
ansible_test() {
|
|
15
|
+
has ansible-lint || { warn "Ansible : ansible-lint absent, vérification ignorée."; return 0; }
|
|
16
|
+
step_t lang.ansible.2
|
|
17
|
+
ansible-lint -q --nocolor
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# C / C++ / Objective-C (CMake, Meson) : clang-format si .clang-format existe, ctest / meson test
|
|
3
|
+
register cpp "CMakeLists.txt meson.build" '\.(c|h|cc|cpp|cxx|hh|hpp|hxx|m|mm)$' standalone outermost
|
|
4
|
+
|
|
5
|
+
cpp_format() {
|
|
6
|
+
has clang-format || { tool_missing "C/C++ : clang-format absent, formatage ignoré."; return 0; }
|
|
7
|
+
find_up .clang-format >/dev/null || find_up _clang-format >/dev/null ||
|
|
8
|
+
{ tool_missing "C/C++ : pas de .clang-format, formatage ignoré."; return 0; }
|
|
9
|
+
step_t lang.cpp.1; clang-format -i --style=file "$@"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
cpp_test() {
|
|
13
|
+
# Recompile avant de tester : sinon les tests tournent sur l'ancien binaire
|
|
14
|
+
if [ -f build/CTestTestfile.cmake ] && has ctest; then
|
|
15
|
+
step_t lang.cpp.2
|
|
16
|
+
cmake --build build && ctest --test-dir build --output-on-failure
|
|
17
|
+
elif [ -f builddir/build.ninja ] && has meson; then
|
|
18
|
+
step_t lang.cpp.3; meson test -C builddir
|
|
19
|
+
else
|
|
20
|
+
info_t lang.cpp.4
|
|
21
|
+
fi
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Dart / Flutter : dart format, flutter test / dart test
|
|
3
|
+
register dart "pubspec.yaml" '\.dart$' standalone
|
|
4
|
+
|
|
5
|
+
dart_format() {
|
|
6
|
+
has dart || { tool_missing "Dart : dart absent, formatage ignoré."; return 0; }
|
|
7
|
+
step_t lang.dart.1
|
|
8
|
+
dart format -o write "$@" >/dev/null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dart_test() {
|
|
12
|
+
[ -d test ] || { info "Dart : pas de dossier test/."; return 0; }
|
|
13
|
+
if grep -qs 'sdk: flutter' pubspec.yaml; then
|
|
14
|
+
has flutter || { warn "Flutter : flutter absent, tests ignorés."; return 0; }
|
|
15
|
+
step_t lang.dart.2; flutter test
|
|
16
|
+
else
|
|
17
|
+
has dart || { warn "Dart : dart absent, tests ignorés."; return 0; }
|
|
18
|
+
step_t lang.dart.3; dart test
|
|
19
|
+
fi
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Deno : deno fmt, deno test
|
|
3
|
+
register deno "deno.json deno.jsonc" '\.(ts|tsx|js|jsx|json|jsonc|md)$'
|
|
4
|
+
|
|
5
|
+
deno_format() {
|
|
6
|
+
has deno || { tool_missing "Deno : deno absent, formatage ignoré."; return 0; }
|
|
7
|
+
step_t lang.deno.1
|
|
8
|
+
deno fmt --quiet "$@"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
deno_test() {
|
|
12
|
+
has deno || { warn "Deno : deno absent, tests ignorés."; return 0; }
|
|
13
|
+
step_t lang.deno.2
|
|
14
|
+
deno test --quiet
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Docker : hadolint (Dockerfile), validation des fichiers Compose.
|
|
3
|
+
register docker "Dockerfile Containerfile *.Dockerfile compose.yaml compose.yml docker-compose.yaml docker-compose.yml" '(^|/)(Dockerfile|Containerfile)[^/]*$|\.Dockerfile$'
|
|
4
|
+
|
|
5
|
+
docker_test() {
|
|
6
|
+
local f
|
|
7
|
+
for f in Dockerfile* Containerfile *.Dockerfile; do
|
|
8
|
+
[ -f "$f" ] || continue
|
|
9
|
+
if has hadolint; then
|
|
10
|
+
step_t lang.docker.1 "$f"
|
|
11
|
+
hadolint "$f" || return 1
|
|
12
|
+
else
|
|
13
|
+
warn_t lang.docker.2 "$f"
|
|
14
|
+
fi
|
|
15
|
+
done
|
|
16
|
+
for f in compose.yaml compose.yml docker-compose.yaml docker-compose.yml; do
|
|
17
|
+
[ -f "$f" ] || continue
|
|
18
|
+
if docker compose version >/dev/null 2>&1; then
|
|
19
|
+
step_t lang.docker.3 "$f"
|
|
20
|
+
# --no-interpolate : pas d'échec sur les variables d'environnement absentes
|
|
21
|
+
docker compose -f "$f" config -q --no-interpolate || return 1
|
|
22
|
+
else
|
|
23
|
+
warn_t lang.docker.4 "$f"
|
|
24
|
+
fi
|
|
25
|
+
done
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# .NET (ASP.NET, C#, F#) : dotnet format, dotnet test
|
|
3
|
+
register dotnet "*.sln *.slnx *.csproj *.fsproj *.vbproj" '\.(cs|fs|vb)$' outermost
|
|
4
|
+
|
|
5
|
+
dotnet_format() {
|
|
6
|
+
has dotnet || { tool_missing ".NET : dotnet absent, formatage ignoré."; return 0; }
|
|
7
|
+
step_t lang.dotnet.1
|
|
8
|
+
dotnet format --include "$@"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dotnet_test() {
|
|
12
|
+
has dotnet || { warn ".NET : dotnet absent, tests ignorés."; return 0; }
|
|
13
|
+
step_t lang.dotnet.2
|
|
14
|
+
dotnet test --nologo -v q
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Elixir (Phoenix…) : mix format, mix test
|
|
3
|
+
register elixir "mix.exs" '\.(ex|exs|heex)$'
|
|
4
|
+
|
|
5
|
+
elixir_format() {
|
|
6
|
+
has mix || { tool_missing "Elixir : mix absent, formatage ignoré."; return 0; }
|
|
7
|
+
step_t lang.elixir.1; mix format "$@"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
elixir_test() {
|
|
11
|
+
has mix || { warn "Elixir : mix absent, tests ignorés."; return 0; }
|
|
12
|
+
step_t lang.elixir.2; mix test
|
|
13
|
+
}
|
package/hooks/lang/go.sh
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Go : gofmt, go test
|
|
3
|
+
register go "go.mod" '\.go$' standalone
|
|
4
|
+
|
|
5
|
+
go_format() {
|
|
6
|
+
has gofmt || { tool_missing "Go : gofmt absent, formatage ignoré."; return 0; }
|
|
7
|
+
step_t lang.go.1
|
|
8
|
+
gofmt -w "$@"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
go_test() {
|
|
12
|
+
has go || { warn "Go : go absent, tests ignorés."; return 0; }
|
|
13
|
+
step_t lang.go.2
|
|
14
|
+
go test ./...
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Code mort prouvé : identifiants non exportés jamais utilisés (staticcheck U1000)
|
|
18
|
+
go_deadcode() {
|
|
19
|
+
has staticcheck || { deadcode_tool_missing Go staticcheck; return 0; }
|
|
20
|
+
staticcheck -checks U1000 ./... 2>/dev/null |
|
|
21
|
+
sed -nE 's/^(.+):([0-9]+):[0-9]+: (.*) \(U1000\)$/prouve\t\1\t\2\t\3/p'
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Java / Kotlin / Gradle (Spring Boot, Android…) : Spotless si configuré
|
|
3
|
+
# (fichiers stagés uniquement), gradle check
|
|
4
|
+
register gradle "settings.gradle settings.gradle.kts build.gradle build.gradle.kts" '\.(java|kt|kts|groovy)$' outermost
|
|
5
|
+
|
|
6
|
+
gradle_cmd() { if [ -x ./gradlew ]; then echo ./gradlew; else echo gradle; fi; }
|
|
7
|
+
|
|
8
|
+
gradle_format() {
|
|
9
|
+
# Spotless peut être déclaré à la racine, dans un sous-projet ou dans buildSrc
|
|
10
|
+
find . -maxdepth 3 \( -name '*.gradle' -o -name '*.gradle.kts' \) -not -path '*/build/*' -print0 2>/dev/null |
|
|
11
|
+
xargs -0 grep -qs spotless ||
|
|
12
|
+
{ tool_missing "Gradle : Spotless non configuré, formatage ignoré."; return 0; }
|
|
13
|
+
# Fichiers ciblés uniquement (chemins absolus, au format Windows si besoin :
|
|
14
|
+
# Gradle tourne sur Java) — plus rapide qu'un spotlessApply complet
|
|
15
|
+
local f files=""
|
|
16
|
+
for f in "$@"; do
|
|
17
|
+
f="$PWD/$f"
|
|
18
|
+
if has cygpath; then f="$(cygpath -m "$f")"; fi
|
|
19
|
+
files="${files:+$files,}$f"
|
|
20
|
+
done
|
|
21
|
+
step_t lang.gradle.1
|
|
22
|
+
"$(gradle_cmd)" -q spotlessApply -PspotlessIdeHook="$files"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
gradle_test() { step "Gradle : $(gradle_cmd) check"; "$(gradle_cmd)" -q check; }
|
|
26
|
+
|
|
27
|
+
# Code mort prouvé (PMD) : voir hooks/lib/deadcode.sh
|
|
28
|
+
gradle_deadcode() { java_deadcode; }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Helm : helm lint + rendu (helm template) + schémas (kubeconform) + helm unittest.
|
|
3
|
+
# Les fichiers d'un chart sont des templates Go : JAMAIS de formatage YAML
|
|
4
|
+
# (prettier les casserait) — d'où l'absence de helm_format.
|
|
5
|
+
register helm "Chart.yaml" '\.(ya?ml|tpl|txt)$'
|
|
6
|
+
|
|
7
|
+
helm_test() {
|
|
8
|
+
has helm || { warn "Helm : helm absent, vérification ignorée."; return 0; }
|
|
9
|
+
local rendered rc=0
|
|
10
|
+
if grep -qs '^dependencies:' Chart.yaml; then
|
|
11
|
+
step_t lang.helm.1
|
|
12
|
+
helm dependency build >/dev/null || return 1
|
|
13
|
+
fi
|
|
14
|
+
step_t lang.helm.2
|
|
15
|
+
helm lint --quiet . || return 1
|
|
16
|
+
rendered="$(mktemp)"
|
|
17
|
+
helm template repowarden . >"$rendered" || { rm -f "$rendered"; return 1; }
|
|
18
|
+
if has kubeconform; then
|
|
19
|
+
step_t lang.helm.3
|
|
20
|
+
kubeconform -strict -summary -ignore-missing-schemas -cache "$(kubeconform_cache)" <"$rendered" || rc=1
|
|
21
|
+
fi
|
|
22
|
+
rm -f "$rendered"
|
|
23
|
+
[ "$rc" = 0 ] || return 1
|
|
24
|
+
if [ -d tests ] && grep -q '^unittest' <<<"$(helm plugin list 2>/dev/null)"; then
|
|
25
|
+
step_t lang.helm.4
|
|
26
|
+
helm unittest .
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Kubernetes (kustomize) : prettier sur les manifestes, rendu kustomize +
|
|
3
|
+
# validation des schémas (kubeconform, lu sur stdin : il ignore les fichiers
|
|
4
|
+
# sans extension .yaml) au push / en CI.
|
|
5
|
+
register kubernetes "kustomization.yaml kustomization.yml Kustomization" '\.ya?ml$'
|
|
6
|
+
|
|
7
|
+
kubernetes_format() { node_format "$@"; }
|
|
8
|
+
|
|
9
|
+
kubernetes_test() {
|
|
10
|
+
local rendered rc=0
|
|
11
|
+
rendered="$(mktemp)"
|
|
12
|
+
if has kubectl; then
|
|
13
|
+
step_t lang.kubernetes.1
|
|
14
|
+
kubectl kustomize . >"$rendered" || rc=1
|
|
15
|
+
elif has kustomize; then
|
|
16
|
+
step_t lang.kubernetes.2
|
|
17
|
+
kustomize build . >"$rendered" || rc=1
|
|
18
|
+
else
|
|
19
|
+
warn_t lang.kubernetes.3
|
|
20
|
+
rm -f "$rendered"
|
|
21
|
+
return 0
|
|
22
|
+
fi
|
|
23
|
+
if [ "$rc" = 0 ] && has kubeconform; then
|
|
24
|
+
step_t lang.kubernetes.4
|
|
25
|
+
kubeconform -strict -summary -ignore-missing-schemas -cache "$(kubeconform_cache)" <"$rendered" || rc=1
|
|
26
|
+
fi
|
|
27
|
+
rm -f "$rendered"
|
|
28
|
+
return "$rc"
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Java / Maven (Spring Boot, Quarkus…) : Spotless si configuré, mvn verify
|
|
3
|
+
register maven "pom.xml" '\.java$' outermost
|
|
4
|
+
|
|
5
|
+
maven_cmd() { if [ -x ./mvnw ]; then echo ./mvnw; else echo mvn; fi; }
|
|
6
|
+
|
|
7
|
+
maven_format() {
|
|
8
|
+
grep -q spotless pom.xml || { tool_missing "Maven : Spotless non configuré dans pom.xml, formatage ignoré."; return 0; }
|
|
9
|
+
step_t lang.maven.1
|
|
10
|
+
# Nom complet du plugin : pas de résolution du préfixe « spotless: » sur un
|
|
11
|
+
# index distant (un incident Maven Central faisait échouer le commit) ; la
|
|
12
|
+
# version utilisée reste celle déclarée dans le pom.xml.
|
|
13
|
+
"$(maven_cmd)" -q com.diffplug.spotless:spotless-maven-plugin:apply \
|
|
14
|
+
-DspotlessFiles="$(printf '%s\n' "$@" | spotless_files_regex)"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
maven_test() { step "Maven : $(maven_cmd) verify"; "$(maven_cmd)" -q verify; }
|
|
18
|
+
|
|
19
|
+
# Chemins relatifs (stdin) → liste de regex pour -DspotlessFiles.
|
|
20
|
+
# Spotless compare au chemin absolu : ".*[\\/]src[\\/]A\.java" (/ ou \ pour Windows).
|
|
21
|
+
# shellcheck disable=SC2016 # regex littérales
|
|
22
|
+
spotless_files_regex() {
|
|
23
|
+
sed -e 's/[][\.*^$()+?{}|]/\\&/g' -e 's#/#[\\\\/]#g' -e 's#^#.*[\\\\/]#' | paste -sd, -
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Code mort prouvé (PMD) : voir hooks/lib/deadcode.sh
|
|
27
|
+
maven_deadcode() { java_deadcode; }
|