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
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ # JavaScript / TypeScript (React, Next.js, Vue, Angular, Nest…) : prettier, <pm> test
3
+ # Extensions de code (analyse du code mort) : les autres (md, yml, json…) ne
4
+ # sont rattachées à node que pour le formatage
5
+ # shellcheck disable=SC2034 # lu par deadcode.sh (${PLUGIN}_CODE_EXT)
6
+ NODE_CODE_EXT='\.(js|jsx|ts|tsx|mjs|cjs|mts|cts|vue|svelte|astro)$'
7
+ register node "package.json" '\.(js|jsx|ts|tsx|mjs|cjs|mts|cts|vue|svelte|astro|json|jsonc|css|scss|less|html|md|mdx|ya?ml|graphql)$' standalone
8
+
9
+ # Gestionnaire de paquets d'après le lockfile (y compris à la racine d'un monorepo)
10
+ node_pm() {
11
+ if find_up pnpm-lock.yaml >/dev/null; then echo pnpm
12
+ elif find_up yarn.lock >/dev/null; then echo yarn
13
+ elif find_up bun.lockb >/dev/null || find_up bun.lock >/dev/null; then echo bun
14
+ else echo npm
15
+ fi
16
+ }
17
+
18
+ node_format() {
19
+ local prettier
20
+ prettier="$(find_up node_modules/.bin/prettier || command -v prettier || true)"
21
+ [ -n "$prettier" ] || { tool_missing "Node : prettier introuvable (npm i -D prettier), formatage ignoré."; return 0; }
22
+ step_t lang.node.1
23
+ "$prettier" --write --ignore-unknown --log-level warn "$@"
24
+ }
25
+
26
+ node_test() {
27
+ has node || { warn "Node : node absent, tests ignorés."; return 0; }
28
+ # Ignore le script "test" par défaut généré par npm init
29
+ if ! node -e 'const t=(require("./package.json").scripts||{}).test; process.exit(t && !/no test specified/.test(t) ? 0 : 1)'; then
30
+ info_t lang.node.2
31
+ return 0
32
+ fi
33
+ local pm; pm="$(node_pm)"
34
+ has "$pm" || { warn "Node : $pm absent, tests ignorés."; return 0; }
35
+ step_t lang.node.3 "$pm"
36
+ case "$pm" in
37
+ bun) bun run test ;;
38
+ npm) npm test --silent ;;
39
+ *) "$pm" test ;;
40
+ esac
41
+ }
42
+
43
+ # Code mort : TypeScript (variables, paramètres, imports inutilisés, code
44
+ # inaccessible : prouvé) ; knip s'il est installé dans le projet (fichiers,
45
+ # exports, dépendances non référencés : candidats)
46
+ node_deadcode() {
47
+ local ran=""
48
+ if [ -f tsconfig.json ] && [ -x node_modules/.bin/tsc ]; then
49
+ ran=1
50
+ node_modules/.bin/tsc --noEmit -p . --noUnusedLocals --noUnusedParameters \
51
+ --allowUnreachableCode false --pretty false 2>/dev/null |
52
+ sed -nE 's/^(.+)\(([0-9]+),[0-9]+\): error (TS6133|TS6138|TS6192|TS6196|TS6198|TS7027): (.*)$/prouve\t\1\t\2\t\4/p'
53
+ fi
54
+ if [ -x node_modules/.bin/knip ] && has node; then
55
+ ran=1
56
+ _tr dc.knip_file
57
+ local m_file="$_T"
58
+ _tr dc.knip_export
59
+ local m_export="$_T"
60
+ _tr dc.knip_dep
61
+ local m_dep="$_T"
62
+ node_modules/.bin/knip --reporter json --no-progress 2>/dev/null |
63
+ M_FILE="$m_file" M_EXPORT="$m_export" M_DEP="$m_dep" node -e '
64
+ let r; try { r = JSON.parse(require("fs").readFileSync(0, "utf8")); } catch { process.exit(0); }
65
+ const out = (f, l, m) => console.log(["candidat", f, l, m].join("\t"));
66
+ for (const f of r.files || []) out(f, 0, process.env.M_FILE);
67
+ for (const i of r.issues || []) {
68
+ for (const k of ["exports", "types", "nsExports", "nsTypes"])
69
+ for (const e of i[k] || []) out(i.file, e.line || 0, process.env.M_EXPORT + " " + e.name);
70
+ for (const k of ["dependencies", "devDependencies"])
71
+ for (const d of i[k] || []) out(i.file, 0, process.env.M_DEP + " " + d.name);
72
+ }'
73
+ fi
74
+ [ -n "$ran" ] || deadcode_tool_missing "JavaScript / TypeScript" "typescript / knip"
75
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ # Packer : packer fmt ; validation syntaxique au push / en CI
3
+ register packer "*.pkr.hcl" '\.(pkr|pkrvars)\.hcl$' standalone
4
+
5
+ packer_format() {
6
+ has packer || { tool_missing "Packer : packer absent, formatage ignoré."; return 0; }
7
+ step_t lang.packer.1
8
+ local f
9
+ for f in "$@"; do packer fmt "$f" >/dev/null || return 1; done
10
+ }
11
+
12
+ packer_test() {
13
+ has packer || { warn "Packer : packer absent, validation ignorée."; return 0; }
14
+ step_t lang.packer.2
15
+ packer validate -syntax-only .
16
+ }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # PHP (Laravel, Symfony…) : pint / php-cs-fixer, composer test / artisan / pest / phpunit
3
+ register php "composer.json" '\.php$'
4
+
5
+ php_format() {
6
+ if [ -x vendor/bin/pint ]; then step_t lang.php.1; vendor/bin/pint -q "$@"
7
+ elif [ -x vendor/bin/php-cs-fixer ]; then step "PHP : php-cs-fixer"; vendor/bin/php-cs-fixer fix -q "$@"
8
+ elif has php-cs-fixer; then step "PHP : php-cs-fixer"; php-cs-fixer fix -q "$@"
9
+ else tool_missing "PHP : ni pint ni php-cs-fixer, formatage ignoré."
10
+ fi
11
+ }
12
+
13
+ php_test() {
14
+ if grep -q '"test"[[:space:]]*:' composer.json && has composer; then step_t lang.php.2; composer test
15
+ elif [ -f artisan ]; then step "PHP : artisan test"; php artisan test
16
+ elif [ -x vendor/bin/pest ]; then step "PHP : pest"; vendor/bin/pest
17
+ elif [ -x vendor/bin/phpunit ]; then step "PHP : phpunit"; vendor/bin/phpunit
18
+ else info "PHP : aucun lanceur de tests trouvé (composer install ?)."
19
+ fi
20
+ }
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ # Python (Django, FastAPI, Flask, scripts…) : ruff/black, pytest ou manage.py test
3
+ # Les outils du projet (.venv, uv) sont prioritaires : même version pour tous.
4
+ register python "pyproject.toml setup.py setup.cfg requirements.txt Pipfile manage.py" '\.pyi?$' standalone
5
+
6
+ # Exécute dans l'environnement du projet (uv, poetry, pipenv, .venv)
7
+ python_run() {
8
+ if [ -f uv.lock ] && has uv; then uv run --no-sync "$@"
9
+ elif [ -f poetry.lock ] && has poetry; then poetry run "$@"
10
+ elif [ -f Pipfile.lock ] && has pipenv; then pipenv run "$@"
11
+ elif [ -d .venv/bin ]; then PATH="$PWD/.venv/bin:$PATH" "$@"
12
+ elif [ -d .venv/Scripts ]; then PATH="$PWD/.venv/Scripts:$PATH" "$@"
13
+ else "$@"
14
+ fi
15
+ }
16
+
17
+ # Vrai si l'outil $1 est disponible (dans le projet, sinon sur le poste)
18
+ python_has() { python_run "$1" --version >/dev/null 2>&1; }
19
+
20
+ python_format() {
21
+ if python_has ruff; then step_t lang.python.1; python_run ruff format -q "$@"
22
+ elif python_has black; then step "Python : black"; python_run black -q "$@"
23
+ else tool_missing "Python : ni ruff ni black installé, formatage ignoré."
24
+ fi
25
+ }
26
+
27
+ # Vrai si le projet contient des tests : un requirements.txt seul (outils de
28
+ # documentation, scripts) ne doit pas exiger pytest.
29
+ python_has_tests() {
30
+ [ -f manage.py ] && return 0
31
+ # -quit : premier fichier trouvé suffit (pas de tube vers grep -q : SIGPIPE)
32
+ [ -n "$(find . \( -name node_modules -o -name .venv -o -name .git -o -name site-packages \) -prune -o \
33
+ \( -name 'test_*.py' -o -name '*_test.py' -o -name conftest.py \) -print -quit 2>/dev/null)" ]
34
+ }
35
+
36
+ python_test() {
37
+ local rc=0 cache
38
+ # Cache de bytecode neuf : Python réutilise un .pyc si la source a même taille
39
+ # et même date à la seconde près — fréquent quand le hook met de côté puis
40
+ # restaure des fichiers. Sans ça, les tests tourneraient sur l'ancien code.
41
+ cache="$(mktemp -d)"
42
+ export PYTHONPYCACHEPREFIX="$cache"
43
+ # Django sans pytest-django : runner intégré
44
+ if [ -f manage.py ] && ! grep -qs pytest-django pyproject.toml requirements*.txt setup.cfg Pipfile; then
45
+ step_t lang.python.2
46
+ python_run python manage.py test || rc=$?
47
+ rm -rf "$cache"
48
+ return "$rc"
49
+ fi
50
+ if ! python_has_tests; then info_t lang.python.3; rm -rf "$cache"; return 0; fi
51
+ if ! python_has pytest; then warn_t lang.python.4; rm -rf "$cache"; return 0; fi
52
+ step_t lang.python.5
53
+ # Racine du projet importable (comme « python -m pytest ») : tests/ importe
54
+ # le paquet sans installation ni conftest.py
55
+ PYTHONPATH="$PWD${PYTHONPATH:+:$PYTHONPATH}" python_run pytest -q -p no:cacheprovider || rc=$?
56
+ rm -rf "$cache"
57
+ # 5 = aucun test collecté : pas une erreur
58
+ if [ "$rc" -eq 5 ]; then info_t lang.python.6; return 0; fi
59
+ return "$rc"
60
+ }
61
+
62
+ # Code mort : ruff (imports et variables inutilisés, redéfinitions : prouvé) ;
63
+ # vulture (confiance 100 % : prouvé, en dessous : candidat — les frameworks
64
+ # appellent des fonctions par leur nom)
65
+ python_deadcode() {
66
+ local ran=""
67
+ if python_has ruff; then
68
+ ran=1
69
+ python_run ruff check --select F401,F811,F841 --output-format concise --quiet --exit-zero . 2>/dev/null |
70
+ sed -nE 's/^(.+):([0-9]+):[0-9]+: F[0-9]+ (\[\*\] )?(.*)$/prouve\t\1\t\2\t\4/p'
71
+ fi
72
+ if python_has vulture; then
73
+ ran=1
74
+ python_run vulture . --min-confidence 60 --exclude ".venv,venv,node_modules,build,dist,site-packages" 2>/dev/null |
75
+ sed -nE -e 's/^(.+):([0-9]+): (.*) \(100% confidence\)$/prouve\t\1\t\2\t\3/p' \
76
+ -e 's/^(.+):([0-9]+): (.*) \(([0-9]+)% confidence\)$/candidat\t\1\t\2\t\3 (\4 %)/p'
77
+ fi
78
+ [ -n "$ran" ] || deadcode_tool_missing Python "ruff / vulture"
79
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ # Ruby (Rails…) : rubocop -a / standardrb, rails test / rspec / rake test
3
+ register ruby "Gemfile" '(\.(rb|rake|gemspec)|(^|/)(Rakefile|Gemfile))$'
4
+
5
+ ruby_format() {
6
+ if grep -qs '^ rubocop ' Gemfile.lock; then
7
+ step_t lang.ruby.1
8
+ bundle exec rubocop -a --format quiet --fail-level F "$@"
9
+ elif grep -qs '^ standard ' Gemfile.lock; then
10
+ step_t lang.ruby.2
11
+ bundle exec standardrb --fix "$@" || true # les offenses non corrigeables ne bloquent pas
12
+ else
13
+ tool_missing "Ruby : ni rubocop ni standard dans le Gemfile, formatage ignoré."
14
+ fi
15
+ }
16
+
17
+ ruby_test() {
18
+ has bundle || { warn "Ruby : bundler absent, tests ignorés."; return 0; }
19
+ if [ -x bin/rails ]; then step_t lang.ruby.3; bin/rails test
20
+ elif [ -d spec ] && grep -qs '^ rspec' Gemfile.lock; then step "Ruby : rspec"; bundle exec rspec
21
+ elif [ -f Rakefile ]; then step "Ruby : rake test"; bundle exec rake test
22
+ else info "Ruby : aucun lanceur de tests trouvé."
23
+ fi
24
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ # Rust : rustfmt (fichiers stagés uniquement), cargo test
3
+ register rust "Cargo.toml" '\.rs$' standalone outermost
4
+
5
+ rust_format() {
6
+ has rustfmt || { tool_missing "Rust : rustfmt absent, formatage ignoré."; return 0; }
7
+ local edition
8
+ edition="$(grep -hsE '^[[:space:]]*edition[[:space:]]*=' Cargo.toml ./*/Cargo.toml | grep -oE '20[0-9]{2}' | sed -n 1p)"
9
+ step_t lang.rust.1
10
+ rustfmt --edition "${edition:-2021}" "$@"
11
+ }
12
+
13
+ rust_test() {
14
+ has cargo || { warn "Rust : cargo absent, tests ignorés."; return 0; }
15
+ step_t lang.rust.2
16
+ cargo test --quiet
17
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ # Scripts shell : shfmt (respecte .editorconfig, sinon indentation 4 espaces)
3
+ register shell "" '\.(sh|bash)$' standalone
4
+
5
+ shell_format() {
6
+ has shfmt || { tool_missing "Shell : shfmt absent, formatage ignoré."; return 0; }
7
+ step_t lang.shell.1
8
+ if find_up .editorconfig >/dev/null; then shfmt -w "$@"; else shfmt -w -i 4 -ci "$@"; fi
9
+ }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # Swift (SwiftPM) : swift-format / SwiftFormat si une config existe, swift test
3
+ # Pas de style canonique en Swift : formatage seulement avec .swift-format ou .swiftformat
4
+ register swift "Package.swift" '\.swift$' standalone
5
+
6
+ swift_format() {
7
+ if find_up .swift-format >/dev/null && swift format --version >/dev/null 2>&1; then
8
+ step_t lang.swift.1; swift format --in-place "$@"
9
+ elif find_up .swiftformat >/dev/null && has swiftformat; then
10
+ step_t lang.swift.2; swiftformat --quiet "$@"
11
+ else
12
+ tool_missing "Swift : pas de .swift-format/.swiftformat (ou outil absent), formatage ignoré."
13
+ fi
14
+ }
15
+
16
+ swift_test() {
17
+ [ -d Tests ] || { info "Swift : pas de dossier Tests/."; return 0; }
18
+ has swift || { warn "Swift : swift absent, tests ignorés."; return 0; }
19
+ step_t lang.swift.3; swift test
20
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ # Projets sans langage reconnu : cible "test" d'un Makefile, justfile ou Taskfile
3
+ register tasks "Makefile makefile GNUmakefile justfile Justfile .justfile Taskfile.yml Taskfile.yaml" '^$' fallback
4
+
5
+ tasks_test() {
6
+ if compgen -G "[Mm]akefile" >/dev/null || [ -f GNUmakefile ]; then
7
+ if has make && make -n test >/dev/null 2>&1; then step_t lang.tasks.1; make test; return; fi
8
+ fi
9
+ if compgen -G "*ustfile" >/dev/null || [ -f .justfile ]; then
10
+ if has just && just --show test >/dev/null 2>&1; then step_t lang.tasks.2; just test; return; fi
11
+ fi
12
+ if compgen -G "Taskfile.y*ml" >/dev/null; then
13
+ if has task && grep -q '^\* test:' <<<"$(task --list-all 2>/dev/null)"; then step_t lang.tasks.3; task test; return; fi
14
+ fi
15
+ info_t lang.tasks.4
16
+ }
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ # Terraform / OpenTofu : fmt ; init (sans backend) + validate + tflint au push / en CI
3
+ register terraform "*.tf" '\.(tf|tfvars|tftest\.hcl)$' standalone
4
+
5
+ terraform_bin() { command -v terraform || command -v tofu || true; }
6
+
7
+ terraform_format() {
8
+ local bin
9
+ bin="$(terraform_bin)"
10
+ [ -n "$bin" ] || { tool_missing "Terraform : terraform/tofu absent, formatage ignoré."; return 0; }
11
+ step_t lang.terraform.1
12
+ "$bin" fmt "$@" >/dev/null
13
+ }
14
+
15
+ terraform_test() {
16
+ local bin
17
+ bin="$(terraform_bin)"
18
+ [ -n "$bin" ] || { warn "Terraform : terraform/tofu absent, validation ignorée."; return 0; }
19
+ step_t lang.terraform.2
20
+ # Providers partagés entre modules et entre lancements
21
+ TF_PLUGIN_CACHE_DIR="${TF_PLUGIN_CACHE_DIR:-$(cache_dir terraform-plugins)}" \
22
+ "$bin" init -backend=false -input=false -no-color >/dev/null || return 1
23
+ "$bin" validate -no-color || return 1
24
+ if has tflint; then
25
+ step_t lang.terraform.3
26
+ tflint --init >/dev/null 2>&1
27
+ tflint --no-color || return 1
28
+ fi
29
+ }