repowarden 0.0.0-stage → 4.1.1-next.2
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 +394 -0
- package/hooks/lib/i18n/fr.sh +394 -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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SimBienvenueHoulBoumi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,104 @@
|
|
|
1
|
-
#
|
|
1
|
+
# repowarden
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/SimBienvenueHoulBoumi/repowarden/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/SimBienvenueHoulBoumi/repowarden/actions/workflows/e2e.yml)
|
|
5
|
+
[](https://github.com/SimBienvenueHoulBoumi/repowarden/releases)
|
|
6
|
+
[](https://www.npmjs.com/package/repowarden)
|
|
7
|
+
[](https://simbienvenuehoulboumi.github.io/repowarden/)
|
|
8
|
+
[](https://scorecard.dev/viewer/?uri=github.com/SimBienvenueHoulBoumi/repowarden)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
**repowarden** garde l'entrée de vos dépôts Git : messages de commit, nommage des branches, secrets, formatage et tests, **quel que soit le langage** — sur les postes (hooks), en CI (GitHub, GitLab) et côté serveur.
|
|
12
|
+
|
|
13
|
+
📖 **Documentation : https://simbienvenuehoulboumi.github.io/repowarden/** · 🇬🇧 **English: https://simbienvenuehoulboumi.github.io/repowarden/en/**
|
|
14
|
+
|
|
15
|
+
> **v4 : repogarde devient repowarden** (paquet npm `repowarden`) et lefthook n'est plus pris en charge. Les anciens noms restent lus en v4. Marche à suivre : [Passer à la v4](https://simbienvenuehoulboumi.github.io/repowarden/migration-v4/).
|
|
16
|
+
|
|
17
|
+
## Ce qu'il fait
|
|
18
|
+
|
|
19
|
+
- **Commits** : Conventional Commits imposés, préfixe déduit de la branche ; assistant interactif `git cc` (en-tête, corps, pied).
|
|
20
|
+
- **Branches** : format `<type>/<sujet>`, commande de renommage proposée, branches mergées supprimées automatiquement.
|
|
21
|
+
- **Secrets** : gitleaks avant le commit et en CI.
|
|
22
|
+
- **Formatage** : seul le contenu stagé, avec l'outil du projet ; le travail en cours est préservé.
|
|
23
|
+
- **Tests** : au push et en CI, seulement les projets touchés, sur le commit poussé.
|
|
24
|
+
- **Code mort** : seul le nouveau code mort est signalé, prouvé (bloquant) ou candidat (à vérifier) ; `repowarden code-mort` en local.
|
|
25
|
+
- **Tickets** : chaque PR est reliée à un ticket validé (créé automatiquement s'il manque), suivi de « à valider » à « done » à la release ; `repowarden tickets init`.
|
|
26
|
+
- **Versions et releases** : les commits étant conventionnels, la version suivante (semver) et le changelog se déduisent de l'historique ; un workflow réutilisable en fait des releases automatiques (PR de release validée par la CI puis mergée, tag, release), sans jeton ni intervention.
|
|
27
|
+
|
|
28
|
+
**Plus de 25 technologies** : Java (Maven, Gradle), JavaScript / TypeScript, Python, Go, Rust, PHP, Ruby, .NET, Dart / Flutter, Swift, Elixir, C / C++, Shell, Terraform, Packer, Ansible, Helm, Kubernetes, Docker, GitHub Actions — [détail](https://simbienvenuehoulboumi.github.io/repowarden/technologies/). Linux, macOS, Windows.
|
|
29
|
+
|
|
30
|
+
## Démarrage
|
|
31
|
+
|
|
32
|
+
### 1. Sur ton poste, en une minute (tous tes dépôts)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g repowarden # 1. le paquet (yarn global add, pnpm add -g : idem)
|
|
36
|
+
repowarden install --global # 2. active les hooks : demande la langue, installe git cc
|
|
37
|
+
repowarden # 3. vérifie : état du poste et étape suivante
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
L'étape 2 est indispensable : par sécurité, le paquet n'exécute rien à l'installation (aucun script `postinstall`, que npm 12 bloque d'ailleurs). Ensuite, dans **n'importe quel dépôt**, les hooks s'appliquent dès le prochain commit. Rien n'est ajouté aux projets ni à leur `package.json`.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd mon-projet
|
|
44
|
+
git switch -c feat/panier # nom de branche vérifié
|
|
45
|
+
git add . && git cc # assistant : type, scope, description… puis commit
|
|
46
|
+
git push -u origin feat/panier # tests des projets touchés avant l'envoi
|
|
47
|
+
repowarden code-mort # nouveau code mort de ta branche
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Action | Commande |
|
|
51
|
+
|---|---|
|
|
52
|
+
| État du poste, étape suivante | `repowarden` (ou `repowarden statut`) |
|
|
53
|
+
| Toutes les commandes | `repowarden --help` (`cc`, `code-mort`, `proteger`, `npm-publication`…) |
|
|
54
|
+
| Mettre à jour | `npm update -g repowarden` (les hooks suivent) |
|
|
55
|
+
| Désinstaller | `repowarden uninstall --global`, **puis** `npm uninstall -g repowarden` (dans cet ordre : npm n'exécute rien à la désinstallation, les hooks resteraient branchés sur un dossier supprimé) |
|
|
56
|
+
|
|
57
|
+
Sans Node : `git clone https://github.com/SimBienvenueHoulBoumi/repowarden.git ~/repowarden && ~/repowarden/install.sh --global` (mise à jour : `git -C ~/repowarden pull`).
|
|
58
|
+
|
|
59
|
+
**Tester la prochaine version** (avant sa sortie en production) : `npm install -g repowarden@next`, ou `uses: SimBienvenueHoulBoumi/repowarden@develop` en CI. Retour à la version stable : `npm install -g repowarden@latest`. Les problèmes se signalent en [issue](https://github.com/SimBienvenueHoulBoumi/repowarden/issues).
|
|
60
|
+
|
|
61
|
+
### 2. Dans un projet d'équipe : les mêmes règles pour tous
|
|
62
|
+
|
|
63
|
+
Chaque personne de l'équipe installe repowarden une fois sur son poste (partie 1). Le projet, lui, ajoute deux fichiers, à copier depuis [`templates/project/`](templates/project) :
|
|
64
|
+
|
|
65
|
+
| Fichier | Rôle |
|
|
66
|
+
|---|---|
|
|
67
|
+
| [`.github/workflows/repowarden.yml`](templates/project/.github/workflows/repowarden.yml) | la CI refait toutes les vérifications sur chaque PR : c'est elle qui fait foi, même si quelqu'un contourne les hooks (`git commit --no-verify`) |
|
|
68
|
+
| [`.repowarden.conf`](templates/project/.repowarden.conf) | réglages communs à toute l'équipe, dont la version minimale de repowarden attendue sur les postes |
|
|
69
|
+
|
|
70
|
+
L'essentiel des deux fichiers :
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
# .github/workflows/repowarden.yml : vérifications de la PR (dernière version 4.x)
|
|
74
|
+
- uses: SimBienvenueHoulBoumi/repowarden@v4
|
|
75
|
+
with: { strict: true } # un outil manquant fait échouer la CI
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```ini
|
|
79
|
+
# .repowarden.conf : un poste en retard est prévenu, avec la commande de mise à jour
|
|
80
|
+
[repowarden]
|
|
81
|
+
version = 3
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Puis rendre la CI obligatoire pour merger : `repowarden proteger` (protection de `main`, check `repowarden` exigé).
|
|
85
|
+
|
|
86
|
+
Exemple complet : [repowarden-demo](https://github.com/SimBienvenueHoulBoumi/repowarden-demo) · Releases automatiques : [documentation](https://simbienvenuehoulboumi.github.io/repowarden/releases/).
|
|
87
|
+
|
|
88
|
+
### 3. Contribuer à repowarden
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git clone https://github.com/SimBienvenueHoulBoumi/repowarden.git && cd repowarden
|
|
92
|
+
./install.sh # repowarden se vérifie lui-même (hooks de ce dépôt)
|
|
93
|
+
bats test/ # tests (bats-core), aussi lancés au push
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Les PR visent `develop` ; `main` ne reçoit que les livraisons, qui déclenchent les releases. Voir [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
97
|
+
|
|
98
|
+
**Dans VS Code** : l'extension [repowarden-vscode](https://github.com/SimBienvenueHoulBoumi/repowarden-vscode) guide la rédaction des commits, signale une branche mal nommée et des hooks inactifs. En attendant sa publication sur le Marketplace : `.vsix` dans ses [releases](https://github.com/SimBienvenueHoulBoumi/repowarden-vscode/releases), puis `code --install-extension repowarden-X.Y.Z.vsix`.
|
|
99
|
+
|
|
100
|
+
## Liens
|
|
101
|
+
|
|
102
|
+
[Démarrage rapide](https://simbienvenuehoulboumi.github.io/repowarden/demarrage/) · [Hooks et règles](https://simbienvenuehoulboumi.github.io/repowarden/hooks/) · [Technologies](https://simbienvenuehoulboumi.github.io/repowarden/technologies/) · [Configuration](https://simbienvenuehoulboumi.github.io/repowarden/configuration/) · [CI](https://simbienvenuehoulboumi.github.io/repowarden/ci/) · [Versions et releases](https://simbienvenuehoulboumi.github.io/repowarden/releases/) · [Validation humaine](https://simbienvenuehoulboumi.github.io/repowarden/validation/) · [Code mort](https://simbienvenuehoulboumi.github.io/repowarden/code-mort/) · [En organisation](https://simbienvenuehoulboumi.github.io/repowarden/industrialisation/) · [Sources internes (Nexus, Artifactory)](https://simbienvenuehoulboumi.github.io/repowarden/industrialisation/#8-sources-internes-reseau-ferme-nexus-artifactory) · [Désinstallation](https://simbienvenuehoulboumi.github.io/repowarden/desinstallation/) · [Sécurité](SECURITY.md) · [Contribuer](CONTRIBUTING.md) · [Changelog](CHANGELOG.md)
|
|
103
|
+
|
|
104
|
+
Licence [MIT](LICENSE).
|
package/bin/code-mort
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Code mort introduit par ta branche, comme en CI : seules les lignes ajoutées ou
|
|
3
|
+
# modifiées depuis la base comptent (commits, modifications en cours, nouveaux fichiers).
|
|
4
|
+
# bin/code-mort [base] défaut : point de départ de la branche depuis origin/main
|
|
5
|
+
# Voir docs/code-mort.md : ce qui est prouvé (bloquant) et ce qui est candidat.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
9
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
10
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
11
|
+
# shellcheck source=../hooks/lib/lang.sh
|
|
12
|
+
source "$ROOT/hooks/lib/lang.sh"
|
|
13
|
+
cd "$GIT_TOPLEVEL"
|
|
14
|
+
|
|
15
|
+
base="${1:-}"
|
|
16
|
+
label="$base"
|
|
17
|
+
if [ -z "$base" ]; then
|
|
18
|
+
default="$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null || echo origin/main)"
|
|
19
|
+
label="$default"
|
|
20
|
+
base="$(git merge-base HEAD "$default" 2>/dev/null || git rev-list --max-parents=0 HEAD | tail -n1)"
|
|
21
|
+
fi
|
|
22
|
+
t dc.section
|
|
23
|
+
printf "\n%s━━ %s ━━%s\n" "$UI_B" "$REPLY" "$UI_N"
|
|
24
|
+
changed="$({
|
|
25
|
+
git diff --name-only --diff-filter=ACMR "$base"
|
|
26
|
+
git ls-files --others --exclude-standard
|
|
27
|
+
} | sort -u | without_excluded)"
|
|
28
|
+
# Rien de modifié (ex. sur main à jour) : le dire, plutôt que « aucun langage »
|
|
29
|
+
if [ -z "$changed" ]; then
|
|
30
|
+
info_t dc.no_changes "$label"
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
deadcode_projects "$base" <<<"$changed"
|
package/bin/commit
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Assistant de commit conventionnel : guide l'en-tête, le corps et le pied.
|
|
3
|
+
# git cc [options de git commit] alias installé par install.sh
|
|
4
|
+
# bin/commit --dry-run affiche le message sans commiter
|
|
5
|
+
# git cc --strict une réponse refusée arrête tout (scripts :
|
|
6
|
+
# sans cela, la ligne suivante serait lue
|
|
7
|
+
# comme une nouvelle réponse)
|
|
8
|
+
# git cc -m "description" [--type T] [--scope S] [--breaking NOTE]
|
|
9
|
+
# [--body TEXTE] [--refs "Closes #12"]
|
|
10
|
+
# sans question (scripts, agents) : mêmes
|
|
11
|
+
# contrôles, toute réponse refusée arrête
|
|
12
|
+
# tout ; type et scope déduits de la
|
|
13
|
+
# branche s'ils sont omis
|
|
14
|
+
# Les réponses sont lues sur l'entrée standard (terminal avec édition de
|
|
15
|
+
# ligne, ou fichier/tube pour les tests). Le commit passe ensuite par les
|
|
16
|
+
# hooks habituels. Couleurs désactivables : NO_COLOR=1.
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
20
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
21
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
22
|
+
# shellcheck source=../hooks/lib/tickets.sh
|
|
23
|
+
source "$ROOT/hooks/lib/tickets.sh"
|
|
24
|
+
|
|
25
|
+
# Charte visuelle commune (hooks/lib/ui.sh, chargée par common.sh)
|
|
26
|
+
B="$UI_B" D="$UI_D" G="$UI_G" R="$UI_R" C="$UI_C" N="$UI_N"
|
|
27
|
+
STEPS=5
|
|
28
|
+
|
|
29
|
+
# Sans question (BATCH) : ni étapes ni aides, seulement les erreurs et l'aperçu
|
|
30
|
+
etape() { [ -z "$BATCH" ] || return 0; printf '\n%s◇ %s/%s%s %s%s%s\n' "$C" "$1" "$STEPS" "$N" "$B" "$2" "$N" >&2; }
|
|
31
|
+
hint() { [ -z "$BATCH" ] || return 0; printf ' %s%s%s\n' "$D" "$*" "$N" >&2; }
|
|
32
|
+
fail() { printf ' %s✖ %s%s\n' "$R" "$*" "$N" >&2; }
|
|
33
|
+
# Réponse refusée : la question est reposée ; avec --strict, abandon (rien
|
|
34
|
+
# n'est commité) plutôt que de lire la réponse suivante d'un script
|
|
35
|
+
refuse() {
|
|
36
|
+
fail "$1"
|
|
37
|
+
[ -z "$STRICT" ] || { t cc.strict_abort; fail "$REPLY"; exit 1; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Réponse dans REPLY, nettoyée : séquences d'échappement (flèches, Échap) et
|
|
41
|
+
# caractères de contrôle retirés, espaces de début et de fin supprimés.
|
|
42
|
+
# Dans un terminal, édition de ligne (flèches, effacement) avec read -e.
|
|
43
|
+
# Sans question (BATCH) : la réponse est ANSWER, fixée par l'appelant.
|
|
44
|
+
ask() {
|
|
45
|
+
if [ -n "$BATCH" ]; then
|
|
46
|
+
REPLY="$ANSWER"
|
|
47
|
+
elif [ -t 0 ]; then
|
|
48
|
+
# Ctrl+D (fin de saisie) : abandon
|
|
49
|
+
IFS= read -e -r -p "$1" REPLY || { echo >&2; t cc.aborted; fail "$REPLY"; exit 1; }
|
|
50
|
+
else
|
|
51
|
+
printf '%s' "$1" >&2
|
|
52
|
+
# Fin de la saisie (entrée épuisée) : abandon, jamais de boucle infinie
|
|
53
|
+
if ! IFS= read -r REPLY; then
|
|
54
|
+
[ -n "$REPLY" ] || { echo >&2; t cc.aborted; fail "$REPLY"; exit 1; }
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
REPLY="$(printf '%s' "$REPLY" | LC_ALL=C sed -e $'s/\x1b\\[[0-9;?]*[ -\\/]*[@-~]//g' -e $'s/\x1b.//g' |
|
|
58
|
+
LC_ALL=C tr -d '\000-\010\013-\037\177')"
|
|
59
|
+
REPLY="${REPLY#"${REPLY%%[![:space:]]*}"}"
|
|
60
|
+
REPLY="${REPLY%"${REPLY##*[![:space:]]}"}"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Question oui/non : $1 invite, $2 réponse par défaut (o ou n). Toute autre
|
|
64
|
+
# réponse est refusée et la question reposée. Résultat : YN=1 pour oui.
|
|
65
|
+
ask_yn() {
|
|
66
|
+
while :; do
|
|
67
|
+
ask "$1"
|
|
68
|
+
case "${REPLY:-$2}" in
|
|
69
|
+
o | O | oui | Oui | OUI | y | Y | yes | Yes | YES) YN=1; return 0 ;;
|
|
70
|
+
n | N | non | Non | NON | no | No | NO) YN=""; return 0 ;;
|
|
71
|
+
esac
|
|
72
|
+
t cc.yn_invalid
|
|
73
|
+
refuse "$REPLY"
|
|
74
|
+
done
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
DRY_RUN="" STRICT="" BATCH="" ANSWER=""
|
|
78
|
+
PASS=() # options transmises à git commit (--no-verify…)
|
|
79
|
+
OPT_TYPE="" OPT_SCOPE="" OPT_MESSAGE="" OPT_BODY="" OPT_REFS="" OPT_BREAKING=""
|
|
80
|
+
# Valeur d'une option : « --type feat » ou « --type=feat »
|
|
81
|
+
option_value() {
|
|
82
|
+
case "$1" in
|
|
83
|
+
*=*) REPLY="${1#*=}"; SHIFT=1 ;;
|
|
84
|
+
*)
|
|
85
|
+
[ $# -ge 2 ] || { t cc.option_value "$1"; fail "$REPLY"; exit 1; }
|
|
86
|
+
REPLY="$2"; SHIFT=2
|
|
87
|
+
;;
|
|
88
|
+
esac
|
|
89
|
+
}
|
|
90
|
+
while [ $# -gt 0 ]; do
|
|
91
|
+
SHIFT=1
|
|
92
|
+
case "$1" in
|
|
93
|
+
--dry-run) DRY_RUN=1 ;;
|
|
94
|
+
--strict) STRICT=1 ;;
|
|
95
|
+
--type | --type=*) option_value "$@"; OPT_TYPE="$REPLY"; BATCH=1 ;;
|
|
96
|
+
--scope | --scope=*) option_value "$@"; OPT_SCOPE="$REPLY"; BATCH=1 ;;
|
|
97
|
+
-m | --message | --message=*) option_value "$@"; OPT_MESSAGE="$REPLY"; BATCH=1 ;;
|
|
98
|
+
--body | --body=*) option_value "$@"; OPT_BODY="$REPLY"; BATCH=1 ;;
|
|
99
|
+
--refs | --refs=*) option_value "$@"; OPT_REFS="$REPLY"; BATCH=1 ;;
|
|
100
|
+
--breaking | --breaking=*) option_value "$@"; OPT_BREAKING="$REPLY"; BATCH=1 ;;
|
|
101
|
+
--) shift; PASS+=("$@"); break ;;
|
|
102
|
+
*) PASS+=("$1") ;;
|
|
103
|
+
esac
|
|
104
|
+
shift "$SHIFT"
|
|
105
|
+
done
|
|
106
|
+
# Sans question : les valeurs viennent des options, aucune n'est redemandée
|
|
107
|
+
if [ -n "$BATCH" ]; then
|
|
108
|
+
STRICT=1
|
|
109
|
+
[ -n "$OPT_MESSAGE" ] || { t cc.batch_message_required; fail "$REPLY"; exit 1; }
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if [ -z "$DRY_RUN" ] && git diff --cached --quiet; then
|
|
113
|
+
t cc.nothing_staged
|
|
114
|
+
fail "$REPLY"
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
TYPES=(feat fix docs style refactor perf test build ci chore revert)
|
|
119
|
+
# Emojis à la gitmoji : repères visuels du menu, jamais écrits dans le message
|
|
120
|
+
EMOJIS=(✨ 🐛 📝 🎨 ♻️ ⚡ ✅ 📦 👷 🔧 ⏪)
|
|
121
|
+
DESCRIPTIONS=()
|
|
122
|
+
for _type in "${TYPES[@]}"; do
|
|
123
|
+
t "cc.type.$_type"
|
|
124
|
+
DESCRIPTIONS+=("$REPLY")
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
# Valeurs proposées d'après la branche : feat/panier → feat, panier. Branche
|
|
128
|
+
# hors convention (wip/xyz, essai/xyz) : rien n'est proposé, le type doit être
|
|
129
|
+
# choisi (Entrée donnerait sinon un feat, donc une version mineure, par erreur).
|
|
130
|
+
branch="$(git symbolic-ref --short HEAD 2>/dev/null || echo "")"
|
|
131
|
+
first_commit=""
|
|
132
|
+
git rev-parse -q --verify HEAD >/dev/null || first_commit=1
|
|
133
|
+
default_type="feat"
|
|
134
|
+
default_scope=""
|
|
135
|
+
if [[ "$branch" == */* ]]; then
|
|
136
|
+
prefix="${branch%%/*}"
|
|
137
|
+
case "$prefix" in feature) prefix=feat ;; bugfix | hotfix) prefix=fix ;; esac
|
|
138
|
+
if [[ "$prefix" =~ ^($CC_TYPES)$ ]]; then
|
|
139
|
+
default_type="$prefix"
|
|
140
|
+
default_scope="$(echo "${branch#*/}" | tr '[:upper:]/' '[:lower:]-' | tr -cd 'a-z0-9._-')"
|
|
141
|
+
[ "${#default_scope}" -le 20 ] || default_scope=""
|
|
142
|
+
else
|
|
143
|
+
default_type=""
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
if [ -z "$BATCH" ]; then
|
|
148
|
+
t cc.title
|
|
149
|
+
brand "$REPLY" >&2
|
|
150
|
+
t cc.help
|
|
151
|
+
printf '%s%s%s\n' "$D" "$REPLY" "$N" >&2
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# 1. Type
|
|
155
|
+
t cc.step1
|
|
156
|
+
etape 1 "$REPLY"
|
|
157
|
+
[ -n "$BATCH" ] || for i in "${!TYPES[@]}"; do
|
|
158
|
+
printf ' %s%2d%s %s %s%-9s%s %s%s%s\n' "$D" "$((i + 1))" "$N" "${EMOJIS[$i]}" "$B" "${TYPES[$i]}" "$N" "$D" "${DESCRIPTIONS[$i]}" "$N" >&2
|
|
159
|
+
done
|
|
160
|
+
while :; do
|
|
161
|
+
if [ -n "$default_type" ]; then t cc.type_prompt "$default_type"; else t cc.type_prompt_required; fi
|
|
162
|
+
ANSWER="$OPT_TYPE"
|
|
163
|
+
ask "$REPLY"
|
|
164
|
+
answer="${REPLY:-$default_type}"
|
|
165
|
+
if [ -z "$answer" ]; then
|
|
166
|
+
t cc.type_required
|
|
167
|
+
refuse "$REPLY"
|
|
168
|
+
continue
|
|
169
|
+
elif [[ "$answer" =~ ^[0-9]+$ ]] && [ "$answer" -ge 1 ] && [ "$answer" -le "${#TYPES[@]}" ]; then
|
|
170
|
+
type="${TYPES[$((answer - 1))]}"
|
|
171
|
+
break
|
|
172
|
+
elif [[ "$answer" =~ ^($CC_TYPES)$ ]]; then
|
|
173
|
+
type="$answer"
|
|
174
|
+
break
|
|
175
|
+
fi
|
|
176
|
+
t cc.type_invalid "$answer" "${#TYPES[@]}"
|
|
177
|
+
refuse "$REPLY"
|
|
178
|
+
done
|
|
179
|
+
|
|
180
|
+
# 2. Scope (optionnel)
|
|
181
|
+
t cc.step2
|
|
182
|
+
etape 2 "$REPLY"
|
|
183
|
+
t cc.scope_hint
|
|
184
|
+
hint "$REPLY"
|
|
185
|
+
while :; do
|
|
186
|
+
if [ -n "$default_scope" ]; then
|
|
187
|
+
t cc.scope_prompt_default "$default_scope"
|
|
188
|
+
ANSWER="$OPT_SCOPE"
|
|
189
|
+
ask "$REPLY"
|
|
190
|
+
scope="${REPLY:-$default_scope}"
|
|
191
|
+
[ "$scope" = - ] && scope=""
|
|
192
|
+
else
|
|
193
|
+
t cc.scope_prompt
|
|
194
|
+
ANSWER="$OPT_SCOPE"
|
|
195
|
+
ask "$REPLY"
|
|
196
|
+
scope="$REPLY"
|
|
197
|
+
[ "$scope" = - ] && scope="" # « - » : aucun scope, comme avec une proposition
|
|
198
|
+
fi
|
|
199
|
+
[[ -z "$scope" || "$scope" =~ ^[a-z0-9._-]+$ ]] && break
|
|
200
|
+
# Version corrigée proposée (« Doc Test » → « doc-test ») : Entrée pour l'accepter
|
|
201
|
+
fixed="$(printf '%s' "$scope" | tr '[:upper:] ' '[:lower:]-' | LC_ALL=C tr -cd 'a-z0-9._-' | tr -s '-')"
|
|
202
|
+
fixed="${fixed#-}"
|
|
203
|
+
fixed="${fixed%-}"
|
|
204
|
+
if [ -n "$fixed" ]; then
|
|
205
|
+
t cc.scope_fixed "$fixed"
|
|
206
|
+
default_scope="$fixed"
|
|
207
|
+
else
|
|
208
|
+
t cc.scope_invalid
|
|
209
|
+
fi
|
|
210
|
+
refuse "$REPLY"
|
|
211
|
+
done
|
|
212
|
+
|
|
213
|
+
# Branche protégée (main…) : le commit y serait refusé. On le dit tout de suite
|
|
214
|
+
# et on propose une branche <type>/<scope>, les modifications suivent.
|
|
215
|
+
cfg_r protectedBranches "main master"
|
|
216
|
+
protected="$REPLY" # à garder : skipped réécrit REPLY
|
|
217
|
+
if [ -z "$first_commit" ] && [ -z "$DRY_RUN" ] && ! skipped protect-branch &&
|
|
218
|
+
[[ " $protected " == *" $branch "* ]]; then
|
|
219
|
+
t cc.topic_default
|
|
220
|
+
suggestion="$type/${scope:-$REPLY}"
|
|
221
|
+
t cc.protected_hint "$branch"
|
|
222
|
+
printf '\n %s%s%s\n' "$B" "$REPLY" "$N" >&2
|
|
223
|
+
while :; do
|
|
224
|
+
t cc.new_branch_prompt "$suggestion"
|
|
225
|
+
ANSWER=n # sans question : jamais de branche créée à la place de l'utilisateur
|
|
226
|
+
ask "$REPLY"
|
|
227
|
+
name="${REPLY:-$suggestion}"
|
|
228
|
+
# Réponse incomplète ou approximative : complétée (« feat » → feat/<scope>,
|
|
229
|
+
# « Mon Test » → feat/mon-test) puis proposée, sans refus sec
|
|
230
|
+
if [[ "$name" != */* && "$name" =~ ^($CC_TYPES|feature|bugfix|hotfix)$ ]]; then
|
|
231
|
+
name="$name/${suggestion#*/}"
|
|
232
|
+
fi
|
|
233
|
+
case "$name" in n | N | non | no)
|
|
234
|
+
t cc.aborted
|
|
235
|
+
printf '%s%s%s\n' "$D" "$REPLY" "$N" >&2
|
|
236
|
+
exit 1
|
|
237
|
+
;;
|
|
238
|
+
esac
|
|
239
|
+
if branch_name_valid "$name" && ! git show-ref -q --verify "refs/heads/$name" &&
|
|
240
|
+
git switch -q -c "$name" </dev/null >/dev/null 2>&1; then
|
|
241
|
+
branch="$name"
|
|
242
|
+
t cc.branch_created "$name"
|
|
243
|
+
hint "$REPLY"
|
|
244
|
+
break
|
|
245
|
+
fi
|
|
246
|
+
if git show-ref -q --verify "refs/heads/$name"; then
|
|
247
|
+
t cc.branch_exists "$name"
|
|
248
|
+
else
|
|
249
|
+
suggestion="$(suggest_branch_name "$name")"
|
|
250
|
+
t cc.branch_fixed "$name" "$suggestion"
|
|
251
|
+
fi
|
|
252
|
+
refuse "$REPLY"
|
|
253
|
+
done
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# 3. Changement incompatible (version majeure) : sans objet au premier commit
|
|
257
|
+
t cc.step3
|
|
258
|
+
etape 3 "$REPLY"
|
|
259
|
+
breaking=""
|
|
260
|
+
breaking_note=""
|
|
261
|
+
if [ -n "$first_commit" ]; then
|
|
262
|
+
t cc.first_commit
|
|
263
|
+
hint "$REPLY"
|
|
264
|
+
else
|
|
265
|
+
t cc.breaking_hint1
|
|
266
|
+
hint "$REPLY"
|
|
267
|
+
t cc.breaking_hint2
|
|
268
|
+
hint "$REPLY"
|
|
269
|
+
t cc.breaking_prompt
|
|
270
|
+
ANSWER="${OPT_BREAKING:+o}"
|
|
271
|
+
ask_yn "$REPLY" n
|
|
272
|
+
breaking="$YN"
|
|
273
|
+
if [ -n "$breaking" ]; then
|
|
274
|
+
t cc.breaking_note_hint
|
|
275
|
+
hint "$REPLY"
|
|
276
|
+
while :; do
|
|
277
|
+
t cc.breaking_note_prompt
|
|
278
|
+
ANSWER="$OPT_BREAKING"
|
|
279
|
+
ask "$REPLY"
|
|
280
|
+
if [ "${#REPLY}" -ge 10 ] && [[ ! "$REPLY" =~ ^(RAS|ras|rien|aucun|none|-|\.)$ ]]; then
|
|
281
|
+
breaking_note="$REPLY"
|
|
282
|
+
break
|
|
283
|
+
fi
|
|
284
|
+
t cc.breaking_note_invalid
|
|
285
|
+
refuse "$REPLY"
|
|
286
|
+
done
|
|
287
|
+
fi
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
header_prefix="$type${scope:+($scope)}${breaking:+!}: "
|
|
291
|
+
|
|
292
|
+
# 4. Description, avec contrôle de longueur de l'en-tête
|
|
293
|
+
t cc.step4
|
|
294
|
+
etape 4 "$REPLY"
|
|
295
|
+
authored_length "$header_prefix"
|
|
296
|
+
max=$((72 - REPLY))
|
|
297
|
+
t cc.desc_hint "$max"
|
|
298
|
+
hint "$REPLY"
|
|
299
|
+
while :; do
|
|
300
|
+
ANSWER="$OPT_MESSAGE"
|
|
301
|
+
ask "$header_prefix"
|
|
302
|
+
description="$REPLY"
|
|
303
|
+
description="${description%.}"
|
|
304
|
+
if [ -z "$description" ]; then
|
|
305
|
+
t cc.desc_required
|
|
306
|
+
refuse "$REPLY"
|
|
307
|
+
continue
|
|
308
|
+
fi
|
|
309
|
+
authored_length "$header_prefix$description"
|
|
310
|
+
if [ "$REPLY" -gt 72 ]; then
|
|
311
|
+
t cc.header_too_long "$REPLY"
|
|
312
|
+
refuse "$REPLY"
|
|
313
|
+
continue
|
|
314
|
+
fi
|
|
315
|
+
break
|
|
316
|
+
done
|
|
317
|
+
|
|
318
|
+
# 5. Détails (optionnels) : corps puis références
|
|
319
|
+
t cc.step5
|
|
320
|
+
etape 5 "$REPLY"
|
|
321
|
+
t cc.body_hint
|
|
322
|
+
hint "$REPLY"
|
|
323
|
+
body=""
|
|
324
|
+
if [ -n "$BATCH" ]; then
|
|
325
|
+
[ -z "$OPT_BODY" ] || body="${OPT_BODY%$'\n'}"$'\n'
|
|
326
|
+
fi
|
|
327
|
+
while [ -z "$BATCH" ]; do
|
|
328
|
+
ask "> "
|
|
329
|
+
# Ligne vide, ou faite seulement de ponctuation (« ; », « . »…) : fin du corps
|
|
330
|
+
[[ -n "$REPLY" && ! "$REPLY" =~ ^[[:punct:]]+$ ]] || break
|
|
331
|
+
body="$body$REPLY"$'\n'
|
|
332
|
+
done
|
|
333
|
+
# Branche type/12-sujet : ticket #12 proposé (Entrée l'accepte, « - » : aucun)
|
|
334
|
+
ticket=""
|
|
335
|
+
branch_ticket_r "$branch"
|
|
336
|
+
[ -z "$REPLY" ] || ticket="Ticket : #$REPLY"
|
|
337
|
+
# Branche de travail sans ticket : prévenu, jamais bloqué. Création proposée
|
|
338
|
+
# seulement si repowarden gère la vie des tickets du projet (et sans -m)
|
|
339
|
+
if [ -z "$ticket" ] && [ -z "$DRY_RUN" ] && ! skipped tickets && work_branch "$branch"; then
|
|
340
|
+
if [ -z "$BATCH" ] && tickets_managed && has gh; then
|
|
341
|
+
t cc.ticket_offer
|
|
342
|
+
ask_yn "$REPLY" n
|
|
343
|
+
if [ -n "$YN" ] && n="$(bash "$ROOT/bin/ticket" nouveau "$description" --type "$type" --porcelain)" && [ -n "$n" ]; then
|
|
344
|
+
ticket="Ticket : #$n"
|
|
345
|
+
t cc.ticket_created "$n"
|
|
346
|
+
hint "$REPLY"
|
|
347
|
+
fi
|
|
348
|
+
else
|
|
349
|
+
ticket_hint "$branch"
|
|
350
|
+
fi
|
|
351
|
+
fi
|
|
352
|
+
if [ -n "$ticket" ]; then
|
|
353
|
+
t cc.refs_prompt_ticket "$ticket"
|
|
354
|
+
else
|
|
355
|
+
t cc.refs_prompt "$FORGE_REFS"
|
|
356
|
+
fi
|
|
357
|
+
ANSWER="$OPT_REFS"
|
|
358
|
+
ask "$REPLY"
|
|
359
|
+
refs="${REPLY:-$ticket}"
|
|
360
|
+
[ "$refs" != - ] || refs=""
|
|
361
|
+
|
|
362
|
+
# Message final : en-tête, puis corps et pied séparés par une ligne vide
|
|
363
|
+
footer=""
|
|
364
|
+
[ -z "$breaking_note" ] || footer="BREAKING CHANGE: $breaking_note"$'\n'
|
|
365
|
+
[ -z "$refs" ] || footer="$footer$refs"$'\n'
|
|
366
|
+
message="$header_prefix$description"$'\n'
|
|
367
|
+
[ -z "$body" ] || message="$message"$'\n'"$body"
|
|
368
|
+
[ -z "$footer" ] || message="$message"$'\n'"$footer"
|
|
369
|
+
|
|
370
|
+
t cc.preview
|
|
371
|
+
printf '\n%s┌─ %s ───────────────────────────────────────────────%s\n' "$D" "$REPLY" "$N" >&2
|
|
372
|
+
while IFS= read -r line; do
|
|
373
|
+
printf '%s│%s %s\n' "$D" "$N" "$line" >&2
|
|
374
|
+
done <<<"${message%$'\n'}"
|
|
375
|
+
printf '%s└─────────────────────────────────────────────────────────%s\n' "$D" "$N" >&2
|
|
376
|
+
|
|
377
|
+
if [ -n "$DRY_RUN" ]; then
|
|
378
|
+
printf '%s' "$message"
|
|
379
|
+
exit 0
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
t cc.confirm
|
|
383
|
+
ANSWER=o
|
|
384
|
+
ask_yn "$REPLY" o
|
|
385
|
+
if [ -z "$YN" ]; then
|
|
386
|
+
t cc.aborted
|
|
387
|
+
printf '%s%s%s\n' "$D" "$REPLY" "$N" >&2
|
|
388
|
+
exit 1
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
# Message gardé dans le dépôt si le commit est refusé (hook, secret…) : rien à ressaisir
|
|
392
|
+
file="$(git rev-parse --git-dir)/repowarden-message"
|
|
393
|
+
printf '%s' "$message" >"$file"
|
|
394
|
+
if ! git commit -F "$file" ${PASS[@]+"${PASS[@]}"}; then
|
|
395
|
+
t cc.kept "$file"
|
|
396
|
+
printf '\n%s%s%s\n' "$B" "$REPLY" "$N" >&2
|
|
397
|
+
exit 1
|
|
398
|
+
fi
|
|
399
|
+
rm -f "$file"
|
|
400
|
+
|
|
401
|
+
# Suite : rappel de la convention de branches
|
|
402
|
+
cfg_r protectedBranches "main master"
|
|
403
|
+
protected="$REPLY"
|
|
404
|
+
t cc.created
|
|
405
|
+
created="$REPLY"
|
|
406
|
+
if [[ " $protected " == *" $branch "* ]]; then
|
|
407
|
+
t cc.topic
|
|
408
|
+
t cc.next_branch "${B}git switch -c $type/<$REPLY>$N" "$FORGE_PR"
|
|
409
|
+
else
|
|
410
|
+
t cc.next_push "${B}git push -u origin $branch$N"
|
|
411
|
+
fi
|
|
412
|
+
printf '\n%s%s✔ %s%s %s\n' "$G" "$B" "$created" "$N" "$REPLY" >&2
|