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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -2
  3. package/bin/code-mort +33 -0
  4. package/bin/commit +412 -0
  5. package/bin/npm-publication +151 -0
  6. package/bin/proteger +283 -0
  7. package/bin/repogarde +20 -0
  8. package/bin/repowarden +63 -0
  9. package/bin/statut +100 -0
  10. package/bin/ticket +129 -0
  11. package/bin/tickets +135 -0
  12. package/hooks/commit-msg +64 -0
  13. package/hooks/lang/actions.sh +9 -0
  14. package/hooks/lang/ansible.sh +18 -0
  15. package/hooks/lang/cpp.sh +22 -0
  16. package/hooks/lang/dart.sh +20 -0
  17. package/hooks/lang/deno.sh +15 -0
  18. package/hooks/lang/docker.sh +26 -0
  19. package/hooks/lang/dotnet.sh +15 -0
  20. package/hooks/lang/elixir.sh +13 -0
  21. package/hooks/lang/go.sh +22 -0
  22. package/hooks/lang/gradle.sh +28 -0
  23. package/hooks/lang/helm.sh +28 -0
  24. package/hooks/lang/kubernetes.sh +29 -0
  25. package/hooks/lang/maven.sh +27 -0
  26. package/hooks/lang/node.sh +75 -0
  27. package/hooks/lang/packer.sh +16 -0
  28. package/hooks/lang/php.sh +20 -0
  29. package/hooks/lang/python.sh +79 -0
  30. package/hooks/lang/ruby.sh +24 -0
  31. package/hooks/lang/rust.sh +17 -0
  32. package/hooks/lang/shell.sh +9 -0
  33. package/hooks/lang/swift.sh +20 -0
  34. package/hooks/lang/tasks.sh +16 -0
  35. package/hooks/lang/terraform.sh +29 -0
  36. package/hooks/lib/common.sh +393 -0
  37. package/hooks/lib/compat.sh +24 -0
  38. package/hooks/lib/deadcode.sh +153 -0
  39. package/hooks/lib/forge.sh +39 -0
  40. package/hooks/lib/i18n/en.sh +393 -0
  41. package/hooks/lib/i18n/fr.sh +393 -0
  42. package/hooks/lib/i18n.sh +81 -0
  43. package/hooks/lib/lang.sh +338 -0
  44. package/hooks/lib/safety.sh +108 -0
  45. package/hooks/lib/tickets.sh +184 -0
  46. package/hooks/lib/ui.sh +40 -0
  47. package/hooks/post-checkout +26 -0
  48. package/hooks/post-merge +47 -0
  49. package/hooks/pre-commit +58 -0
  50. package/hooks/pre-push +91 -0
  51. package/hooks/prepare-commit-msg +48 -0
  52. package/install.sh +245 -0
  53. 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
@@ -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
+ }
@@ -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; }