repowarden 4.7.0 → 4.7.2-next.6
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/README.md +68 -36
- package/bin/commit +10 -4
- package/bin/npm-publication +134 -5
- package/bin/proteger +96 -4
- package/hooks/commit-msg +33 -0
- package/hooks/lib/common.sh +17 -0
- package/hooks/lib/i18n/en.sh +24 -2
- package/hooks/lib/i18n/fr.sh +24 -2
- package/hooks/lib/tickets.sh +2 -1
- package/hooks/lib/ui.sh +21 -1
- package/hooks/prepare-commit-msg +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,29 +27,47 @@
|
|
|
27
27
|
|
|
28
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://simdevwarden.github.io/repowarden/technologies/). Linux, macOS, Windows.
|
|
29
29
|
|
|
30
|
-
##
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
## Le cycle d'une modification
|
|
31
|
+
|
|
32
|
+
De l'idée à la production, chaque changement suit le même chemin. Tu n'agis qu'aux étapes **en gras** ; le reste est automatique, et chaque étape est vérifiée par la CI.
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
flowchart LR
|
|
36
|
+
A[Ticket] -->|validé| B[Conception]
|
|
37
|
+
B --> C[Branche du ticket]
|
|
38
|
+
C -->|commits + push| D[PR brouillon]
|
|
39
|
+
D -->|Ready for review| E{CI verte ?}
|
|
40
|
+
E -->|oui| F[develop]
|
|
41
|
+
E -->|non| C
|
|
42
|
+
F -->|vendredi, ou à la main| G[Demande de livraison]
|
|
43
|
+
G -->|Approve and deploy| H[main : release]
|
|
44
|
+
```
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
| # | Étape | Qui | Ce qui se passe | Statut du ticket |
|
|
47
|
+
|---|---|---|---|---|
|
|
48
|
+
| 1 | Ouvrir un ticket (issue) | toi | rien ne se code sans ticket | à valider |
|
|
49
|
+
| 2 | **Valider le ticket** | toi | étiquette `validé` (posée d'office si un mainteneur ouvre le ticket) | backlog |
|
|
50
|
+
| 3 | **Écrire la conception** | toi | commentaire « Conception retenue » sur le ticket : ce qui change, fichiers touchés, hors périmètre. Sans elle, la vérification `ticket` de la PR échoue | backlog |
|
|
51
|
+
| 4 | **Prendre le ticket** | toi | `repowarden ticket 12` : assigné, branche `fix/12-sujet` créée depuis `develop` | en cours |
|
|
52
|
+
| 5 | Coder et committer | toi | `git cc` ; les hooks vérifient message, branche, secrets, formatage, puis les tests au push | en cours |
|
|
53
|
+
| 6 | Premier push | auto | PR **brouillon** vers `develop` ouverte, titre et description tirés du ticket | en cours |
|
|
54
|
+
| 7 | **Sortir du brouillon** | toi | bouton *Ready for review* (`gh pr ready`) | en relecture |
|
|
55
|
+
| 8 | Vérifier et intégrer | auto | CI complète ; verte → **merge automatique** (squash) dans `develop`. Rouge → corriger et pousser, retour en 5 | préprod |
|
|
56
|
+
| 9 | Préversion | auto | chaque soir si `develop` a changé : `vX.Y.Z-next.N` (paquet `repowarden@next`), pour tester avant la prod | préprod |
|
|
57
|
+
| 10 | Demande de livraison | auto | le vendredi matin, ou à la main (*Actions → release → Run workflow*, ou étiquette `urgent` sur une PR `fix/…`) : instantané `livraison/vX.Y.Z` et sa PR vers `main` | préprod |
|
|
58
|
+
| 11 | **Approuver la livraison** | toi | sur la page du run : **Review deployments → Approve and deploy** (pas « Approve » sur la PR, qui est une simple relecture) | préprod |
|
|
59
|
+
| 12 | Publier | auto | la GitHub App merge vers `main` : tag `vX.Y.Z`, release, paquet npm, documentation | done, ticket fermé |
|
|
60
|
+
|
|
61
|
+
Seul `develop` entre dans `main`, et jamais sans l'approbation de l'étape 11. Une PR `develop` → `main` ouverte à la main est fermée automatiquement, avec la marche à suivre. Pas à pas, commandes et pièges : [où cliquer](https://simdevwarden.github.io/repowarden/validation/#ou-cliquer).
|
|
44
62
|
|
|
45
63
|
## Démarrage
|
|
46
64
|
|
|
47
65
|
### 1. Sur ton poste, en une minute (tous tes dépôts)
|
|
48
66
|
|
|
49
67
|
```bash
|
|
50
|
-
npm install -g repowarden
|
|
51
|
-
repowarden install --global
|
|
52
|
-
repowarden
|
|
68
|
+
npm install -g repowarden # 1. le paquet (yarn global add, pnpm add -g : idem)
|
|
69
|
+
repowarden install --global # 2. active les hooks : demande la langue, installe git cc
|
|
70
|
+
repowarden # 3. vérifie : état du poste et étape suivante
|
|
53
71
|
```
|
|
54
72
|
|
|
55
73
|
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`.
|
|
@@ -59,7 +77,7 @@ cd mon-projet
|
|
|
59
77
|
git switch -c feat/panier # nom de branche vérifié
|
|
60
78
|
git add . && git cc # assistant : type, scope, description… puis commit
|
|
61
79
|
git push -u origin feat/panier # tests des projets touchés avant l'envoi
|
|
62
|
-
repowarden code-mort
|
|
80
|
+
repowarden code-mort # nouveau code mort de ta branche
|
|
63
81
|
```
|
|
64
82
|
|
|
65
83
|
| Action | Commande |
|
|
@@ -67,25 +85,17 @@ repowarden code-mort # nouveau code mort de ta branche
|
|
|
67
85
|
| État du poste, étape suivante | `repowarden` (ou `repowarden statut`) |
|
|
68
86
|
| Toutes les commandes | `repowarden --help` (`cc`, `code-mort`, `proteger`, `npm-publication`…) |
|
|
69
87
|
| Mettre à jour | `npm update -g repowarden` (les hooks suivent) |
|
|
88
|
+
| Tester la prochaine version | `npm install -g repowarden@next` (retour : `repowarden@latest`) ; en CI : `uses: simdevWarden/repowarden@develop` |
|
|
70
89
|
| 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é) |
|
|
71
90
|
|
|
72
91
|
Sans Node : `git clone https://github.com/simdevWarden/repowarden.git ~/repowarden && ~/repowarden/install.sh --global` (mise à jour : `git -C ~/repowarden pull`).
|
|
73
92
|
|
|
74
|
-
|
|
93
|
+
**Nouvelle machine** : ces trois commandes suffisent. Les règles du dépôt, l'environnement de livraison, la GitHub App, ses secrets et la publication npm vivent sur GitHub et npm : rien à reconfigurer. La clé privée de l'App ne doit jamais se trouver sur un poste.
|
|
75
94
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
repowarden app init
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
1. Ouvre la page locale → clique "Create GitHub App" sur GitHub
|
|
83
|
-
2. Clique "Install" sur ton repo/org
|
|
84
|
-
3. Les secrets `REPOWARDEN_APP_CLIENT_ID` / `REPOWARDEN_APP_KEY` sont configurés automatiquement
|
|
85
|
-
|
|
86
|
-
Sans cette étape, le `GITHUB_TOKEN` des Actions est utilisé : **ce qu'il fait ne déclenche pas les autres workflows** (merge auto, tickets, préversions).
|
|
87
|
-
|
|
88
|
-
**Tester la prochaine version** (avant sa sortie en production) : `npm install -g repowarden@next`, ou `uses: simdevWarden/repowarden@develop` en CI. Retour à la version stable : `npm install -g repowarden@latest`. Les problèmes se signalent en [issue](https://github.com/simdevWarden/repowarden/issues).
|
|
95
|
+
> [!NOTE]
|
|
96
|
+
> **GitHub CLI (`gh`) : facultatif.**
|
|
97
|
+
> Sans `gh` : tous les hooks (commits, branches, secrets, formatage, tests), `git cc`, `repowarden code-mort`, `repowarden statut`, et la CI (GitHub ou GitLab), qui n'utilise jamais le `gh` du poste.
|
|
98
|
+
> Avec `gh` connecté (`gh auth login`), en plus : les tickets (`repowarden ticket`, `tickets`, branches de tes tickets créées en local) et la gestion du dépôt (`proteger`, `app init`, `npm-publication`, `pr-*`, `run-*`). Utile au mainteneur du dépôt GitHub ; chaque commande le signale s'il manque.
|
|
89
99
|
|
|
90
100
|
### 2. Dans un projet d'équipe : les mêmes règles pour tous
|
|
91
101
|
|
|
@@ -107,22 +117,44 @@ L'essentiel des deux fichiers :
|
|
|
107
117
|
```ini
|
|
108
118
|
# .repowarden.conf : un poste en retard est prévenu, avec la commande de mise à jour
|
|
109
119
|
[repowarden]
|
|
110
|
-
version =
|
|
120
|
+
version = 4
|
|
111
121
|
```
|
|
112
122
|
|
|
113
|
-
Puis rendre la CI obligatoire pour merger
|
|
123
|
+
Puis rendre la CI obligatoire pour merger (`gh` connecté, admin du dépôt) :
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
repowarden proteger --checks "repowarden,tests" # toutes les vérifications exigées, séparées par des virgules
|
|
127
|
+
repowarden proteger --checks "repowarden,tests" --verifier # plus tard : règles en ligne à jour ? (lecture seule)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Toujours passer **toutes** les vérifications à `--checks` : sans lui, seule `repowarden` reste exigée. Avec un flux `develop` → `main`, `proteger` crée aussi l'étiquette `urgent` et exige l'approbation de chaque livraison : utiliser le [modèle de release du flux develop](https://simdevwarden.github.io/repowarden/releases/), sinon `main` attendrait un déploiement que rien ne crée (`proteger` le signale).
|
|
114
131
|
|
|
115
132
|
Exemple complet : [repowarden-demo](https://github.com/simdevWarden/repowarden-demo) · Releases automatiques : [documentation](https://simdevwarden.github.io/repowarden/releases/).
|
|
116
133
|
|
|
117
|
-
### 3.
|
|
134
|
+
### 3. GitHub App pour l'automatisation (merge auto, tickets, releases)
|
|
135
|
+
|
|
136
|
+
Pour que les workflows déclenchent les autres workflows (merge auto, tickets, releases), il faut une GitHub App au lieu du `GITHUB_TOKEN` :
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
repowarden app init
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
1. Ouvre la page locale → clique « Create GitHub App » sur GitHub
|
|
143
|
+
2. Clique « Install » sur ton dépôt ou ton organisation
|
|
144
|
+
3. Les secrets `REPOWARDEN_APP_CLIENT_ID` / `REPOWARDEN_APP_KEY` sont configurés automatiquement
|
|
145
|
+
|
|
146
|
+
Sans cette étape, le `GITHUB_TOKEN` des Actions est utilisé : **ce qu'il fait ne déclenche pas les autres workflows** (merge auto, tickets, préversions).
|
|
147
|
+
|
|
148
|
+
### 4. Contribuer à repowarden
|
|
118
149
|
|
|
119
150
|
```bash
|
|
120
151
|
git clone https://github.com/simdevWarden/repowarden.git && cd repowarden
|
|
121
152
|
./install.sh # repowarden se vérifie lui-même (hooks de ce dépôt)
|
|
122
|
-
|
|
153
|
+
brew install shellcheck bats-core actionlint gitleaks # outils des vérifications (ou équivalent Linux)
|
|
154
|
+
bats test/ # tests, aussi lancés au push
|
|
123
155
|
```
|
|
124
156
|
|
|
125
|
-
Les PR visent `develop` ; `main` ne reçoit que les livraisons, qui déclenchent les releases.
|
|
157
|
+
Les PR visent `develop` ; `main` ne reçoit que les livraisons, qui déclenchent les releases. Tout part d'un ticket : voir [AGENTS.md](AGENTS.md) et [CONTRIBUTING.md](CONTRIBUTING.md). Les problèmes se signalent en [issue](https://github.com/simdevWarden/repowarden/issues).
|
|
126
158
|
|
|
127
159
|
**Dans VS Code** : l'extension [repowarden-vscode](https://github.com/simdevWarden/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/simdevWarden/repowarden-vscode/releases), puis `code --install-extension repowarden-X.Y.Z.vsix`.
|
|
128
160
|
|
package/bin/commit
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# sans cela, la ligne suivante serait lue
|
|
7
7
|
# comme une nouvelle réponse)
|
|
8
8
|
# git cc -m "description" [--type T] [--scope S] [--breaking NOTE]
|
|
9
|
-
# [--body TEXTE] [--refs "
|
|
9
|
+
# [--body TEXTE] [--refs "Ticket : #12"]
|
|
10
10
|
# sans question (scripts, agents) : mêmes
|
|
11
11
|
# contrôles, toute réponse refusée arrête
|
|
12
12
|
# tout ; type et scope déduits de la
|
|
@@ -173,14 +173,19 @@ while :; do
|
|
|
173
173
|
type="$answer"
|
|
174
174
|
break
|
|
175
175
|
fi
|
|
176
|
-
|
|
176
|
+
type_proche_r "$(printf '%s' "$answer" | tr '[:upper:]' '[:lower:]')"
|
|
177
|
+
if [ -n "$REPLY" ]; then
|
|
178
|
+
t cc.type_invalid_suggest "$answer" "$REPLY" "${#TYPES[@]}"
|
|
179
|
+
else
|
|
180
|
+
t cc.type_invalid "$answer" "${#TYPES[@]}"
|
|
181
|
+
fi
|
|
177
182
|
refuse "$REPLY"
|
|
178
183
|
done
|
|
179
184
|
|
|
180
185
|
# 2. Scope (optionnel)
|
|
181
186
|
t cc.step2
|
|
182
187
|
etape 2 "$REPLY"
|
|
183
|
-
t cc.scope_hint
|
|
188
|
+
if [ -n "$default_scope" ]; then t cc.scope_hint_default "$default_scope"; else t cc.scope_hint; fi
|
|
184
189
|
hint "$REPLY"
|
|
185
190
|
while :; do
|
|
186
191
|
if [ -n "$default_scope" ]; then
|
|
@@ -352,7 +357,8 @@ fi
|
|
|
352
357
|
if [ -n "$ticket" ]; then
|
|
353
358
|
t cc.refs_prompt_ticket "$ticket"
|
|
354
359
|
else
|
|
355
|
-
|
|
360
|
+
cfg_r integrationBranch ""
|
|
361
|
+
if [ -n "$REPLY" ]; then t cc.refs_prompt "Ticket : #12"; else t cc.refs_prompt "$FORGE_REFS"; fi
|
|
356
362
|
fi
|
|
357
363
|
ANSWER="$OPT_REFS"
|
|
358
364
|
ask "$REPLY"
|
package/bin/npm-publication
CHANGED
|
@@ -24,11 +24,13 @@ source "$ROOT/hooks/lib/common.sh"
|
|
|
24
24
|
REPO=""
|
|
25
25
|
WORKFLOW="release.yml"
|
|
26
26
|
ENVIRONMENT=""
|
|
27
|
+
DRY_RUN=false
|
|
27
28
|
while [ $# -gt 0 ]; do
|
|
28
29
|
case "$1" in
|
|
29
30
|
--repo) shift; REPO="${1:?--repo attend propriétaire/nom}" ;;
|
|
30
31
|
--workflow) shift; WORKFLOW="${1:?--workflow attend un fichier}" ;;
|
|
31
32
|
--environnement | --environment) shift; ENVIRONMENT="${1:?--environnement attend un nom}" ;;
|
|
33
|
+
--dry-run) DRY_RUN=true ;;
|
|
32
34
|
-h | --help) sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
33
35
|
*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
34
36
|
esac
|
|
@@ -50,6 +52,67 @@ name="$(node -p 'require("./package.json").name')"
|
|
|
50
52
|
# les doublons, y compris celui d'une tentative interrompue) ; tous révoqués à la fin
|
|
51
53
|
TOKEN_PREFIX="repowarden-ci-${REPO##*/}"
|
|
52
54
|
|
|
55
|
+
# Vérifie si une relation de confiance correspond aux paramètres attendus.
|
|
56
|
+
# Affiche l'écart si --dry-run ou pour confirmation.
|
|
57
|
+
# Retourne : 0 = match, 1 = pas de relation, 2 = relation différente
|
|
58
|
+
verifier_trust() {
|
|
59
|
+
local json attendu_repo="$REPO" attendu_file="$WORKFLOW" attendu_env="$ENVIRONMENT"
|
|
60
|
+
json="$(npm trust list "$name" --json 2>/dev/null || echo '[]')"
|
|
61
|
+
if [ "$json" = "[]" ] || [ -z "$json" ]; then
|
|
62
|
+
return 1
|
|
63
|
+
fi
|
|
64
|
+
local match=0 id repo file env allow_publish
|
|
65
|
+
while IFS=$'\t' read -r id repo file env allow_publish; do
|
|
66
|
+
[ -n "$id" ] || continue
|
|
67
|
+
if [ "$repo" = "$attendu_repo" ] && [ "$file" = "$attendu_file" ] && [ "$env" = "$attendu_env" ] && [ "$allow_publish" = "true" ]; then
|
|
68
|
+
match=1
|
|
69
|
+
break
|
|
70
|
+
fi
|
|
71
|
+
done < <(node -e "
|
|
72
|
+
const data = $json;
|
|
73
|
+
for (const t of data) {
|
|
74
|
+
const id = t.id || t.key;
|
|
75
|
+
const repo = t.repository || t.repo || '';
|
|
76
|
+
const file = t.file || t.workflow || '';
|
|
77
|
+
const env = t.environment || t.env || '';
|
|
78
|
+
const allowPublish = t.allowPublish === true || t['allow-publish'] === true;
|
|
79
|
+
console.log(id + '\t' + repo + '\t' + file + '\t' + env + '\t' + allowPublish);
|
|
80
|
+
}
|
|
81
|
+
")
|
|
82
|
+
if [ "$match" = 1 ]; then
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
return 2
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Affiche les détails d'une relation de confiance pour diagnostic
|
|
89
|
+
afficher_trust() {
|
|
90
|
+
local json
|
|
91
|
+
json="$(npm trust list "$name" --json 2>/dev/null || echo '[]')"
|
|
92
|
+
if [ "$json" = "[]" ] || [ -z "$json" ]; then
|
|
93
|
+
echo " (aucune relation de confiance)"
|
|
94
|
+
return
|
|
95
|
+
fi
|
|
96
|
+
node -e "
|
|
97
|
+
const data = $json;
|
|
98
|
+
for (const t of data) {
|
|
99
|
+
const id = t.id || t.key;
|
|
100
|
+
const repo = t.repository || t.repo || '(inconnu)';
|
|
101
|
+
const file = t.file || t.workflow || '(inconnu)';
|
|
102
|
+
const env = t.environment || t.env || '(aucun)';
|
|
103
|
+
const allowPublish = t.allowPublish === true || t['allow-publish'] === true;
|
|
104
|
+
const allowStagePublish = t.allowStagePublish === true || t['allow-stage-publish'] === true;
|
|
105
|
+
console.log(' ID: ' + id);
|
|
106
|
+
console.log(' Dépôt: ' + repo);
|
|
107
|
+
console.log(' Workflow: ' + file);
|
|
108
|
+
console.log(' Environnement: ' + env);
|
|
109
|
+
console.log(' npm publish: ' + (allowPublish ? 'oui' : 'non'));
|
|
110
|
+
console.log(' npm stage publish: ' + (allowStagePublish ? 'oui' : 'non'));
|
|
111
|
+
console.log('');
|
|
112
|
+
}
|
|
113
|
+
"
|
|
114
|
+
}
|
|
115
|
+
|
|
53
116
|
brand "npm · $name · $REPO"
|
|
54
117
|
|
|
55
118
|
# 1. Connexion npm : entre toi et npm (navigateur ou mot de passe)
|
|
@@ -127,16 +190,82 @@ fi
|
|
|
127
190
|
|
|
128
191
|
# 3. Publication de confiance, puis plus aucun jeton
|
|
129
192
|
step_t npm.step_trust
|
|
130
|
-
|
|
193
|
+
verifier_trust
|
|
194
|
+
cas=$?
|
|
195
|
+
if [ "$cas" = 0 ]; then
|
|
131
196
|
ok_t npm.trust_exists
|
|
197
|
+
elif [ "$cas" = 1 ]; then
|
|
198
|
+
# Aucune relation : créer
|
|
199
|
+
if [ "$DRY_RUN" = true ]; then
|
|
200
|
+
info_t npm.trust_would_create "$REPO" "$WORKFLOW" "${ENVIRONMENT:-(aucun)}"
|
|
201
|
+
else
|
|
202
|
+
npm trust github "$name" --file "$WORKFLOW" --repo "$REPO" ${ENVIRONMENT:+--env "$ENVIRONMENT"} --allow-publish --yes
|
|
203
|
+
ok_t npm.trust_set "$REPO" "$WORKFLOW"
|
|
204
|
+
fi
|
|
132
205
|
else
|
|
133
|
-
|
|
134
|
-
|
|
206
|
+
# Relation différente : afficher l'écart, demander confirmation, corriger sans perte
|
|
207
|
+
attention_t npm.trust_mismatch
|
|
208
|
+
afficher_trust
|
|
209
|
+
echo " Attendu :"
|
|
210
|
+
echo " Dépôt: $REPO"
|
|
211
|
+
echo " Workflow: $WORKFLOW"
|
|
212
|
+
echo " Environnement: ${ENVIRONMENT:-(aucun)}"
|
|
213
|
+
echo " npm publish: oui"
|
|
214
|
+
echo ""
|
|
215
|
+
if [ "$DRY_RUN" = true ]; then
|
|
216
|
+
info_t npm.trust_would_fix
|
|
217
|
+
else
|
|
218
|
+
# Confirmation avant changement
|
|
219
|
+
if [ -t 0 ]; then
|
|
220
|
+
t npm.trust_confirm
|
|
221
|
+
read -r -p " $REPLY" rep
|
|
222
|
+
echo
|
|
223
|
+
[[ "$rep" =~ ^[OoYy]$ ]] || { err_t npm.trust_cancelled; exit 1; }
|
|
224
|
+
fi
|
|
225
|
+
# Créer la nouvelle relation AVANT de révoquer l'ancienne (pas de perte)
|
|
226
|
+
npm trust github "$name" --file "$WORKFLOW" --repo "$REPO" ${ENVIRONMENT:+--env "$ENVIRONMENT"} --allow-publish --yes
|
|
227
|
+
ok_t npm.trust_set "$REPO" "$WORKFLOW"
|
|
228
|
+
# Révoquer l'ancienne relation
|
|
229
|
+
json="$(npm trust list "$name" --json 2>/dev/null || echo '[]')"
|
|
230
|
+
old_id="$(node -e "
|
|
231
|
+
const data = $json;
|
|
232
|
+
for (const t of data) {
|
|
233
|
+
const id = t.id || t.key;
|
|
234
|
+
const repo = t.repository || t.repo || '';
|
|
235
|
+
const file = t.file || t.workflow || '';
|
|
236
|
+
const env = t.environment || t.env || '';
|
|
237
|
+
const allowPublish = t.allowPublish === true || t['allow-publish'] === true;
|
|
238
|
+
if (repo !== '$REPO' || file !== '$WORKFLOW' || env !== '$ENVIRONMENT' || allowPublish !== true) {
|
|
239
|
+
console.log(id);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
")"
|
|
243
|
+
for id in $old_id; do
|
|
244
|
+
[ -n "$id" ] && npm trust revoke "$name" --id "$id" --yes && ok_t npm.trust_revoked "$id"
|
|
245
|
+
done
|
|
246
|
+
fi
|
|
135
247
|
fi
|
|
136
248
|
gh variable set REPOWARDEN_NPM --repo "$REPO" --body true
|
|
137
249
|
if grep -q '^NPM_TOKEN' <<<"$(gh secret list --repo "$REPO")"; then
|
|
138
|
-
|
|
139
|
-
|
|
250
|
+
if [ "$cas" = 0 ] || [ "$cas" = 2 ]; then
|
|
251
|
+
# Relation de confiance OK (ou corrigée) : signaler le token et proposer suppression
|
|
252
|
+
attention_t npm.token_present
|
|
253
|
+
if [ "$DRY_RUN" = true ]; then
|
|
254
|
+
info_t npm.token_would_remove
|
|
255
|
+
elif [ -t 0 ]; then
|
|
256
|
+
t npm.token_remove_confirm
|
|
257
|
+
read -r -p " $REPLY" rep
|
|
258
|
+
echo
|
|
259
|
+
[[ "$rep" =~ ^[OoYy]$ ]] && gh secret delete NPM_TOKEN --repo "$REPO" && ok_t npm.token_removed
|
|
260
|
+
else
|
|
261
|
+
gh secret delete NPM_TOKEN --repo "$REPO"
|
|
262
|
+
ok_t npm.token_removed
|
|
263
|
+
fi
|
|
264
|
+
else
|
|
265
|
+
# Pas de relation de confiance : supprimer silencieusement (comportement existant)
|
|
266
|
+
gh secret delete NPM_TOKEN --repo "$REPO"
|
|
267
|
+
ok_t npm.token_removed
|
|
268
|
+
fi
|
|
140
269
|
fi
|
|
141
270
|
# Jetons temporaires révoqués sur npm (retrouvés par leur préfixe). npm peut
|
|
142
271
|
# demander un code 2FA : la commande garde le terminal (questions visibles)
|
package/bin/proteger
CHANGED
|
@@ -308,6 +308,60 @@ sig_attendue() {
|
|
|
308
308
|
"$(sed -n 's/.*"require_last_push_approval": \([a-z]*\).*/\1/p' <<<"$r")" \
|
|
309
309
|
"$(sed -n 's/.*"require_code_owner_review": \([a-z]*\).*/\1/p' <<<"$r")" "$deps" "$checks"
|
|
310
310
|
}
|
|
311
|
+
# Anciens rulesets : rulesets de branche du dépôt, sous un autre nom que ceux
|
|
312
|
+
# de repowarden, qui exigent encore la vérification « repogarde » (retirée en
|
|
313
|
+
# v4) ; posés à la main ou par une ancienne version. Laissés à côté, ils
|
|
314
|
+
# bloqueraient les branches pour toujours. Une ligne par ruleset dans ANCIENS :
|
|
315
|
+
# id, nom, branches couvertes (~DEFAULT_BRANCH résolu : la branche par défaut
|
|
316
|
+
# change ensuite dans le flux develop), séparés par des tabulations.
|
|
317
|
+
# shellcheck disable=SC2016 # expressions jq (gh --jq), pas du shell
|
|
318
|
+
anciens_rulesets() {
|
|
319
|
+
local ids id ligne defaut=""
|
|
320
|
+
ANCIENS=""
|
|
321
|
+
ids="$(gh api "repos/$REPO/rulesets" --jq '.[] | select(.target == "branch"
|
|
322
|
+
and (.source_type // "Repository") == "Repository"
|
|
323
|
+
and (.name | test("^(repowarden|repogarde)( \\(.*\\))?$") | not)) | .id')" || return 0
|
|
324
|
+
for id in $ids; do
|
|
325
|
+
ligne="$(gh api "repos/$REPO/rulesets/$id" --jq 'select([.rules[]? | select(.type == "required_status_checks")
|
|
326
|
+
| .parameters.required_status_checks[].context] | index("repogarde"))
|
|
327
|
+
| "\(.id)\t\(.name)\t\(.conditions.ref_name.include | join(" "))"')" || continue
|
|
328
|
+
[ -n "$ligne" ] || continue
|
|
329
|
+
if [[ "$ligne" == *"~DEFAULT_BRANCH"* ]]; then
|
|
330
|
+
[ -n "$defaut" ] || defaut="$(gh api "repos/$REPO" --jq .default_branch)"
|
|
331
|
+
ligne="${ligne//\~DEFAULT_BRANCH/refs/heads/$defaut}"
|
|
332
|
+
fi
|
|
333
|
+
ANCIENS="$ANCIENS$ligne"$'\n'
|
|
334
|
+
done
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# Ancien ruleset (ligne de ANCIENS) couvrant une des branches du ruleset $2 (JSON)
|
|
338
|
+
couvre() {
|
|
339
|
+
local motifs m b
|
|
340
|
+
read -ra motifs <<<"$(cut -f3 <<<"$1")"
|
|
341
|
+
for m in ${motifs[@]+"${motifs[@]}"}; do
|
|
342
|
+
[ "$m" != "~ALL" ] || return 0
|
|
343
|
+
for b in $(grep -o '"refs/heads/[^"]*"' <<<"$2" | tr -d '"'); do
|
|
344
|
+
# shellcheck disable=SC2053 # motif de GitHub (refs/heads/release/*)
|
|
345
|
+
[[ "$b" != $m ]] || return 0
|
|
346
|
+
done
|
|
347
|
+
done
|
|
348
|
+
return 1
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
# Réponse d'erreur de GitHub (JSON) → « message : erreurs », lisible
|
|
352
|
+
erreur_github() {
|
|
353
|
+
local message="" erreurs="" re_message='"message": *"([^"]*)"' re_erreurs='"errors": *\[([^]]*)\]'
|
|
354
|
+
[[ ! "$1" =~ $re_message ]] || message="${BASH_REMATCH[1]}"
|
|
355
|
+
[[ ! "$1" =~ $re_erreurs ]] || erreurs="${BASH_REMATCH[1]//\"/}"
|
|
356
|
+
if [ -n "$message" ]; then
|
|
357
|
+
REPLY="$message${erreurs:+ : $erreurs}"
|
|
358
|
+
else
|
|
359
|
+
REPLY="$(tr '\n' ' ' <<<"$1")"
|
|
360
|
+
fi
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
anciens_rulesets
|
|
364
|
+
|
|
311
365
|
# shellcheck disable=SC2016 # expression jq (gh --jq), pas du shell
|
|
312
366
|
SIG_EN_LIGNE='(.rules | map(select(.type == "pull_request"))[0].parameters) as $p
|
|
313
367
|
| "approbations=\($p.required_approving_review_count) dernier_push=\($p.require_last_push_approval) codeowners=\($p.require_code_owner_review) deploiement=\([.rules[] | select(.type == "required_deployments") | .parameters.required_deployment_environments[]] | join(",")) verifications=\([.rules[] | select(.type == "required_status_checks") | .parameters.required_status_checks[].context] | sort | join(","))"'
|
|
@@ -335,6 +389,11 @@ if [ -n "$VERIFY" ]; then
|
|
|
335
389
|
ecarts=$((ecarts + 1))
|
|
336
390
|
fi
|
|
337
391
|
done
|
|
392
|
+
while IFS=$'\t' read -r id name _; do
|
|
393
|
+
[ -n "$id" ] || continue
|
|
394
|
+
err_t ci.proteger.verify_legacy "$name" "$id"
|
|
395
|
+
ecarts=$((ecarts + 1))
|
|
396
|
+
done <<<"$ANCIENS"
|
|
338
397
|
[ "$ecarts" = 0 ] || { err_t ci.proteger.verify_fix; exit 1; }
|
|
339
398
|
ok_t ci.proteger.verify_ok
|
|
340
399
|
exit 0
|
|
@@ -361,6 +420,7 @@ gh api -X PATCH "repos/$REPO" --silent "${settings[@]}"
|
|
|
361
420
|
gh api -X PUT "repos/$REPO/actions/permissions/workflow" --silent \
|
|
362
421
|
-f default_workflow_permissions=read -F can_approve_pull_request_reviews=true
|
|
363
422
|
|
|
423
|
+
echecs=0
|
|
364
424
|
for ruleset in "${rulesets[@]}"; do
|
|
365
425
|
name="$(sed -n 's/^ "name": "\(.*\)",$/\1/p' <<<"$ruleset")"
|
|
366
426
|
# Ruleset posé sous l'ancien nom (repogarde, v3) : repris et renommé, sinon
|
|
@@ -368,14 +428,45 @@ for ruleset in "${rulesets[@]}"; do
|
|
|
368
428
|
old="${name/repowarden/repogarde}"
|
|
369
429
|
id="$(gh api "repos/$REPO/rulesets" \
|
|
370
430
|
--jq "([.[] | select(.name == \"$name\")] + [.[] | select(.name == \"$old\")]) | .[0].id // empty")"
|
|
431
|
+
# Sinon, un ancien ruleset sous un autre nom couvrant ses branches : repris
|
|
432
|
+
# (mis à jour en place, jamais un instant sans protection)
|
|
433
|
+
repris=""
|
|
434
|
+
if [ -z "$id" ] && grep -q '"target": "branch"' <<<"$ruleset"; then
|
|
435
|
+
while IFS= read -r ligne; do
|
|
436
|
+
{ [ -n "$ligne" ] && couvre "$ligne" "$ruleset"; } || continue
|
|
437
|
+
id="${ligne%%$'\t'*}"
|
|
438
|
+
repris="$(cut -f2 <<<"$ligne")"
|
|
439
|
+
ANCIENS="$(grep -v "^$id"$'\t' <<<"$ANCIENS" || true)"
|
|
440
|
+
break
|
|
441
|
+
done <<<"$ANCIENS"
|
|
442
|
+
fi
|
|
371
443
|
if [ -n "$id" ]; then
|
|
372
|
-
|
|
373
|
-
|
|
444
|
+
methode=PUT chemin="repos/$REPO/rulesets/$id"
|
|
445
|
+
else
|
|
446
|
+
methode=POST chemin="repos/$REPO/rulesets"
|
|
447
|
+
fi
|
|
448
|
+
# Réponse gardée : en cas de refus, le détail de GitHub est affiché
|
|
449
|
+
if reponse="$(gh api -X "$methode" "$chemin" --input - <<<"$ruleset" 2>&1)"; then
|
|
450
|
+
if [ -n "$repris" ]; then
|
|
451
|
+
ok_t ci.proteger.legacy_taken "$name" "$repris"
|
|
452
|
+
elif [ -n "$id" ]; then
|
|
453
|
+
ok_t ci.proteger.10 "$name"
|
|
454
|
+
else
|
|
455
|
+
ok_t ci.proteger.11 "$name"
|
|
456
|
+
fi
|
|
374
457
|
else
|
|
375
|
-
|
|
376
|
-
|
|
458
|
+
erreur_github "$reponse"
|
|
459
|
+
err_t ci.proteger.api_error "$name" "$REPLY"
|
|
460
|
+
echecs=$((echecs + 1))
|
|
377
461
|
fi
|
|
378
462
|
done
|
|
463
|
+
# Anciens rulesets non repris (plusieurs sur les mêmes branches) : jamais
|
|
464
|
+
# supprimés ici, la marche à suivre est donnée
|
|
465
|
+
while IFS=$'\t' read -r id name _; do
|
|
466
|
+
[ -n "$id" ] || continue
|
|
467
|
+
err_t ci.proteger.legacy_left "$name" "$id" "$REPO" "$id"
|
|
468
|
+
echecs=$((echecs + 1))
|
|
469
|
+
done <<<"$ANCIENS"
|
|
379
470
|
if [ -n "$env_json" ]; then
|
|
380
471
|
gh api -X PUT "repos/$REPO/environments/$ENVIRONMENT" --input - --silent <<<"$env_json"
|
|
381
472
|
policies="$(gh api "repos/$REPO/environments/$ENVIRONMENT/deployment-branch-policies" \
|
|
@@ -389,4 +480,5 @@ if [ -n "$env_json" ]; then
|
|
|
389
480
|
fi
|
|
390
481
|
[ -z "$urgent" ] || gh label create urgent -R "$REPO" --color B60205 \
|
|
391
482
|
--description "Livraison immédiate après merge" >/dev/null
|
|
483
|
+
[ "$echecs" = 0 ] || { err_t ci.proteger.incomplete; exit 1; }
|
|
392
484
|
ok_t ci.proteger.13
|
package/hooks/commit-msg
CHANGED
|
@@ -39,6 +39,30 @@ if [[ "$SUBJECT" =~ ^(Merge|Revert|fixup!|squash!) ]]; then
|
|
|
39
39
|
exit 0
|
|
40
40
|
fi
|
|
41
41
|
|
|
42
|
+
# En-tête tenté mais refusé : type inconnu (ou en majuscules), description vide
|
|
43
|
+
if ! [[ "$SUBJECT" =~ $CC_PATTERN ]] &&
|
|
44
|
+
[[ "$SUBJECT" =~ ^([A-Za-z]+)(\([^\)]*\))?(!?):[[:space:]]*(.*)$ ]]; then
|
|
45
|
+
typ="${BASH_REMATCH[1]}" portee="${BASH_REMATCH[2]}" bang="${BASH_REMATCH[3]}" desc="${BASH_REMATCH[4]}"
|
|
46
|
+
typ_min="$(printf '%s' "$typ" | tr '[:upper:]' '[:lower:]')"
|
|
47
|
+
propose="$typ_min"
|
|
48
|
+
[[ "$typ_min" =~ ^($CC_TYPES)$ ]] || { type_proche_r "$typ_min"; propose="$REPLY"; }
|
|
49
|
+
if [ -z "$desc" ]; then
|
|
50
|
+
err_t hook.commit_msg.desc_empty "${propose:-$typ}$portee$bang:"
|
|
51
|
+
echo_t hook.commit_msg.kept >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
if [ "$propose" != "$typ" ]; then
|
|
55
|
+
if [ -n "$propose" ]; then
|
|
56
|
+
err_t hook.commit_msg.type_unknown "$typ" "$propose$portee$bang: $desc"
|
|
57
|
+
else
|
|
58
|
+
err_t hook.commit_msg.type_none "$typ"
|
|
59
|
+
types_help
|
|
60
|
+
fi
|
|
61
|
+
echo_t hook.commit_msg.kept >&2
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
42
66
|
if ! [[ "$SUBJECT" =~ $CC_PATTERN ]]; then
|
|
43
67
|
if [ "${REPOWARDEN_LANG:-}" = en ]; then
|
|
44
68
|
cat >&2 <<EOF
|
|
@@ -80,6 +104,15 @@ EOF
|
|
|
80
104
|
exit 1
|
|
81
105
|
fi
|
|
82
106
|
|
|
107
|
+
# Point final retiré, comme dans git cc (pas des points de suspension) ; seule
|
|
108
|
+
# la première ligne est réécrite, le corps reste intact
|
|
109
|
+
if [[ "$SUBJECT" == *. && "$SUBJECT" != *.. ]]; then
|
|
110
|
+
SUBJECT="${SUBJECT%.}"
|
|
111
|
+
contenu="$(awk -v s="$SUBJECT" '!fait && !/^#/ { print s; fait = 1; next } { print }' "$MSG_FILE")"
|
|
112
|
+
printf '%s\n' "$contenu" >"$MSG_FILE"
|
|
113
|
+
info_t hook.commit_msg.dot >&2
|
|
114
|
+
fi
|
|
115
|
+
|
|
83
116
|
authored_length "$SUBJECT"
|
|
84
117
|
if [ "$REPLY" -gt 72 ]; then
|
|
85
118
|
err_t hook.commit_msg.1 "$REPLY" >&2
|
package/hooks/lib/common.sh
CHANGED
|
@@ -84,6 +84,23 @@ cfg() {
|
|
|
84
84
|
CC_TYPES="feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert"
|
|
85
85
|
CC_PATTERN="^($CC_TYPES)(\([a-z0-9._-]+\))?!?: .+"
|
|
86
86
|
|
|
87
|
+
# Type le plus proche d'un type inconnu : alias courants, puis préfixe commun
|
|
88
|
+
# (doc → docs, feature → feat, Feat → feat) ; vide si aucun
|
|
89
|
+
type_proche_r() {
|
|
90
|
+
local t="$1" c
|
|
91
|
+
case "$t" in
|
|
92
|
+
feature | features) REPLY="feat"; return ;;
|
|
93
|
+
doc | documentation) REPLY="docs"; return ;;
|
|
94
|
+
fixe | fixed | fixes | bugfix | hotfix) REPLY="fix"; return ;;
|
|
95
|
+
tests) REPLY="test"; return ;;
|
|
96
|
+
refacto | refactoring) REPLY="refactor"; return ;;
|
|
97
|
+
esac
|
|
98
|
+
REPLY=""
|
|
99
|
+
for c in ${CC_TYPES//|/ }; do
|
|
100
|
+
if [[ "$c" == "$t"* || "$t" == "$c"* ]]; then REPLY="$c"; return; fi
|
|
101
|
+
done
|
|
102
|
+
}
|
|
103
|
+
|
|
87
104
|
# REPLY = longueur de la première ligne écrite par l'auteur : le suffixe
|
|
88
105
|
# « (#123) » ajouté par GitHub lors d'un merge squash n'est pas compté.
|
|
89
106
|
authored_length() {
|
package/hooks/lib/i18n/en.sh
CHANGED
|
@@ -25,8 +25,10 @@ _msg_en() {
|
|
|
25
25
|
cc.option_value) _M="Option %s expects a value." ;;
|
|
26
26
|
cc.batch_message_required) _M="Without questions, the description is required: git cc -m \"<description>\" [--type T] [--scope S] [--body …] [--refs …] [--breaking …]." ;;
|
|
27
27
|
cc.type_invalid) _M="\"%s\": pick a number (1 to %s) or a name from the list." ;;
|
|
28
|
+
cc.type_invalid_suggest) _M="\"%s\": unknown type. Did you mean \"%s\"? Number (1 to %s) or a name from the list." ;;
|
|
28
29
|
cc.step2) _M="Scope (optional)" ;;
|
|
29
30
|
cc.scope_hint) _M="Part of the project affected, e.g. api, auth, chart; Enter for none." ;;
|
|
31
|
+
cc.scope_hint_default) _M="Part of the project, e.g. api, auth, chart; Enter keeps \"%s\", \"-\" for none." ;;
|
|
30
32
|
cc.scope_prompt_default) _M="Scope (\"-\" for none) [%s]: " ;;
|
|
31
33
|
cc.scope_prompt) _M="Scope: " ;;
|
|
32
34
|
cc.scope_fixed) _M="Scope: lowercase letters, digits, . _ - only. Suggested: %s (Enter to accept)." ;;
|
|
@@ -40,7 +42,7 @@ _msg_en() {
|
|
|
40
42
|
cc.breaking_note_prompt) _M="What changes: " ;;
|
|
41
43
|
cc.breaking_note_invalid) _M="Describe the change (at least 10 characters); otherwise answer no to the previous question." ;;
|
|
42
44
|
cc.step4) _M="Description" ;;
|
|
43
|
-
cc.desc_hint) _M="Imperative mood, no
|
|
45
|
+
cc.desc_hint) _M="Imperative mood, no final period, lowercase start except acronyms or proper nouns; %s characters max." ;;
|
|
44
46
|
cc.desc_required) _M="The description is required." ;;
|
|
45
47
|
cc.header_too_long) _M="Header too long: %s characters (72 max). Shorten the description." ;;
|
|
46
48
|
cc.step5) _M="Details (optional)" ;;
|
|
@@ -48,7 +50,8 @@ _msg_en() {
|
|
|
48
50
|
cc.refs_prompt_ticket) _M="References (\"-\" for none) [%s]: " ;;
|
|
49
51
|
cc.refs_prompt) _M="References (e.g. %s; Enter for none): " ;;
|
|
50
52
|
tk.cli.no_ticket) _M="Branch %s has no ticket: this work will not be linked to any ticket (not blocking)." ;;
|
|
51
|
-
tk.cli.propose) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\"
|
|
53
|
+
tk.cli.propose) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\"." ;;
|
|
54
|
+
tk.cli.propose_cc) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\" (or answer yes in git cc)." ;;
|
|
52
55
|
tk.cli.silence) _M="Project without ticket tracking? git config repowarden.skip tickets to stop seeing this." ;;
|
|
53
56
|
tk.cli.fetch) _M="Branch of ticket #%s…" ;;
|
|
54
57
|
tk.cli.switched) _M="On branch %s (ticket #%s)." ;;
|
|
@@ -111,6 +114,11 @@ _msg_en() {
|
|
|
111
114
|
hook.pre_commit.6) _M="gitleaks not installed, secret detection skipped (install: %s)." ;;
|
|
112
115
|
hook.pre_commit.7) _M="Pre-commit OK" ;;
|
|
113
116
|
hook.commit_msg.1) _M="First line too long (%s > 72 characters)." ;;
|
|
117
|
+
hook.commit_msg.type_unknown) _M="Unknown type \"%s\". Did you mean: %s" ;;
|
|
118
|
+
hook.commit_msg.type_none) _M="Unknown type \"%s\". Possible types:" ;;
|
|
119
|
+
hook.commit_msg.desc_empty) _M="Missing description after \"%s\": describe the change in the imperative mood." ;;
|
|
120
|
+
hook.commit_msg.kept) _M=" Your message is not lost: git commit -e -F .git/COMMIT_EDITMSG" ;;
|
|
121
|
+
hook.commit_msg.dot) _M="Final period removed from the description (same rule as git cc)." ;;
|
|
114
122
|
hook.pre_push.1) _M="Push refused: branch name does not follow the convention." ;;
|
|
115
123
|
hook.pre_push.2) _M="Direct push to '%s' refused: it only changes through pull requests." ;;
|
|
116
124
|
hook.pre_push.3) _M=" Create a branch: git switch -c feat/my-feature, then open a PR." ;;
|
|
@@ -293,6 +301,11 @@ _msg_en() {
|
|
|
293
301
|
ci.proteger.tags_none) _M="not protected (--sans-tags)" ;;
|
|
294
302
|
ci.proteger.unknown_team) _M="Unknown or inaccessible team: %s (format @organisation/team)" ;;
|
|
295
303
|
ci.proteger.unknown_user) _M="Unknown user: %s" ;;
|
|
304
|
+
ci.proteger.legacy_taken) _M="Ruleset \"%s\": old ruleset \"%s\" taken over (repogarde check removed)." ;;
|
|
305
|
+
ci.proteger.legacy_left) _M="Old ruleset \"%s\" (id %s): still requires the repogarde check, removed in v4, and blocks its branches. Delete it: Settings → Rules → Rulesets, or gh api -X DELETE repos/%s/rulesets/%s." ;;
|
|
306
|
+
ci.proteger.verify_legacy) _M="%s (id %s): old ruleset, still requires the repogarde check (removed in v4)" ;;
|
|
307
|
+
ci.proteger.api_error) _M="Ruleset \"%s\" rejected by GitHub: %s" ;;
|
|
308
|
+
ci.proteger.incomplete) _M="Incomplete protection: see the errors above." ;;
|
|
296
309
|
ci.version.usage) _M="Usage: ci/version.sh next|notes [ref]" ;;
|
|
297
310
|
ci.version.breaking) _M="Breaking changes" ;;
|
|
298
311
|
ci.version.features) _M="Features" ;;
|
|
@@ -352,7 +365,16 @@ _msg_en() {
|
|
|
352
365
|
npm.first_published) _M="%s@%s published by the pipeline." ;;
|
|
353
366
|
npm.step_trust) _M="3/3 Tokenless publishing (trusted publishing)" ;;
|
|
354
367
|
npm.trust_exists) _M="Trusted publishing already configured." ;;
|
|
368
|
+
npm.trust_mismatch) _M="Existing trust relationship does not match expected parameters:" ;;
|
|
369
|
+
npm.trust_confirm) _M="Confirm fix (new relationship created before revoking old)? [y/N] " ;;
|
|
370
|
+
npm.trust_cancelled) _M="Fix cancelled." ;;
|
|
371
|
+
npm.trust_would_create) _M="[dry-run] Trust relationship would be created: %s, workflow %s, env %s" ;;
|
|
372
|
+
npm.trust_would_fix) _M="[dry-run] Trust relationship would be fixed (new created, old revoked)" ;;
|
|
373
|
+
npm.trust_revoked) _M="Old trust relationship revoked: %s" ;;
|
|
355
374
|
npm.trust_set) _M="Trusted publishing: %s, workflow %s." ;;
|
|
375
|
+
npm.token_present) _M="NPM_TOKEN secret still present while trusted publishing works." ;;
|
|
376
|
+
npm.token_remove_confirm) _M="Remove NPM_TOKEN secret? [y/N] " ;;
|
|
377
|
+
npm.token_would_remove) _M="[dry-run] NPM_TOKEN secret would be removed" ;;
|
|
356
378
|
npm.token_removed) _M="NPM_TOKEN secret removed from the repository." ;;
|
|
357
379
|
npm.token_revoked) _M="Temporary token revoked on npm." ;;
|
|
358
380
|
npm.done) _M="Done: every release publishes %s to npm, without a token." ;;
|
package/hooks/lib/i18n/fr.sh
CHANGED
|
@@ -25,8 +25,10 @@ _msg_fr() {
|
|
|
25
25
|
cc.option_value) _M="L'option %s attend une valeur." ;;
|
|
26
26
|
cc.batch_message_required) _M="Sans question, la description est obligatoire : git cc -m \"<description>\" [--type T] [--scope S] [--body …] [--refs …] [--breaking …]." ;;
|
|
27
27
|
cc.type_invalid) _M="« %s » : choisis un numéro (1 à %s) ou un nom de la liste." ;;
|
|
28
|
+
cc.type_invalid_suggest) _M="« %s » : type inconnu. Voulais-tu « %s » ? Numéro (1 à %s) ou nom de la liste." ;;
|
|
28
29
|
cc.step2) _M="Scope (optionnel)" ;;
|
|
29
30
|
cc.scope_hint) _M="Partie du projet concernée, ex. api, auth, chart ; Entrée pour aucun." ;;
|
|
31
|
+
cc.scope_hint_default) _M="Partie du projet concernée, ex. api, auth, chart ; Entrée garde « %s », « - » pour aucun." ;;
|
|
30
32
|
cc.scope_prompt_default) _M="Scope (« - » pour aucun) [%s] : " ;;
|
|
31
33
|
cc.scope_prompt) _M="Scope : " ;;
|
|
32
34
|
cc.scope_fixed) _M="Scope : minuscules, chiffres, . _ - uniquement. Proposé : %s (Entrée pour l'accepter)." ;;
|
|
@@ -40,7 +42,7 @@ _msg_fr() {
|
|
|
40
42
|
cc.breaking_note_prompt) _M="Ce qui change : " ;;
|
|
41
43
|
cc.breaking_note_invalid) _M="Décris le changement (10 caractères au moins) ; sinon, réponds non à la question précédente." ;;
|
|
42
44
|
cc.step4) _M="Description" ;;
|
|
43
|
-
cc.desc_hint) _M="À l'impératif, sans
|
|
45
|
+
cc.desc_hint) _M="À l'impératif, sans point final, minuscule initiale sauf sigle ou nom propre ; %s caractères au plus." ;;
|
|
44
46
|
cc.desc_required) _M="La description est obligatoire." ;;
|
|
45
47
|
cc.header_too_long) _M="En-tête trop long : %s caractères (72 max). Raccourcis la description." ;;
|
|
46
48
|
cc.step5) _M="Détails (optionnels)" ;;
|
|
@@ -48,7 +50,8 @@ _msg_fr() {
|
|
|
48
50
|
cc.refs_prompt_ticket) _M="Références (« - » pour aucune) [%s] : " ;;
|
|
49
51
|
cc.refs_prompt) _M="Références (ex. %s ; Entrée pour aucune) : " ;;
|
|
50
52
|
tk.cli.no_ticket) _M="Branche %s sans ticket : ce travail ne sera relié à aucun ticket (non bloquant)." ;;
|
|
51
|
-
tk.cli.propose) _M="
|
|
53
|
+
tk.cli.propose) _M="Partir d'un ticket : repowarden ticket nouveau \"<titre>\"." ;;
|
|
54
|
+
tk.cli.propose_cc) _M="Partir d'un ticket : repowarden ticket nouveau \"<titre>\" (ou réponds oui à git cc)." ;;
|
|
52
55
|
tk.cli.silence) _M="Projet sans suivi de tickets ? git config repowarden.skip tickets pour ne plus le voir." ;;
|
|
53
56
|
tk.cli.fetch) _M="Branche du ticket #%s…" ;;
|
|
54
57
|
tk.cli.switched) _M="Sur la branche %s (ticket #%s)." ;;
|
|
@@ -109,6 +112,11 @@ _msg_fr() {
|
|
|
109
112
|
hook.pre_commit.6) _M="gitleaks absent, détection de secrets ignorée (installation : %s)." ;;
|
|
110
113
|
hook.pre_commit.7) _M="Pre-commit OK" ;;
|
|
111
114
|
hook.commit_msg.1) _M="Première ligne trop longue (%s > 72 caractères)." ;;
|
|
115
|
+
hook.commit_msg.type_unknown) _M="Type « %s » inconnu. Voulais-tu : %s" ;;
|
|
116
|
+
hook.commit_msg.type_none) _M="Type « %s » inconnu. Types possibles :" ;;
|
|
117
|
+
hook.commit_msg.desc_empty) _M="Description manquante après « %s » : décris le changement à l'impératif." ;;
|
|
118
|
+
hook.commit_msg.kept) _M=" Ton message n'est pas perdu : git commit -e -F .git/COMMIT_EDITMSG" ;;
|
|
119
|
+
hook.commit_msg.dot) _M="Point final retiré de la description (même règle que git cc)." ;;
|
|
112
120
|
hook.pre_push.1) _M="Push refusé : nom de branche non conforme." ;;
|
|
113
121
|
hook.pre_push.2) _M="Push direct refusé sur '%s' : elle n'évolue que par Pull Request." ;;
|
|
114
122
|
hook.pre_push.3) _M=" Crée une branche : git switch -c feat/ma-feature, puis ouvre une PR." ;;
|
|
@@ -293,6 +301,11 @@ _msg_fr() {
|
|
|
293
301
|
ci.proteger.tags_none) _M="non protégés (--sans-tags)" ;;
|
|
294
302
|
ci.proteger.unknown_team) _M="Équipe inconnue ou inaccessible : %s (format @organisation/equipe)" ;;
|
|
295
303
|
ci.proteger.unknown_user) _M="Utilisateur inconnu : %s" ;;
|
|
304
|
+
ci.proteger.legacy_taken) _M="Ruleset « %s » : ancien ruleset « %s » repris (vérification repogarde retirée)." ;;
|
|
305
|
+
ci.proteger.legacy_left) _M="Ancien ruleset « %s » (id %s) : exige encore la vérification repogarde, retirée en v4, et bloque ses branches. Le supprimer : Settings → Rules → Rulesets, ou gh api -X DELETE repos/%s/rulesets/%s." ;;
|
|
306
|
+
ci.proteger.verify_legacy) _M="%s (id %s) : ancien ruleset, exige encore la vérification repogarde (retirée en v4)" ;;
|
|
307
|
+
ci.proteger.api_error) _M="Ruleset « %s » refusé par GitHub : %s" ;;
|
|
308
|
+
ci.proteger.incomplete) _M="Protection incomplète : voir les erreurs ci-dessus." ;;
|
|
296
309
|
ci.version.usage) _M="Usage : ci/version.sh next|notes [ref]" ;;
|
|
297
310
|
ci.version.breaking) _M="Changements incompatibles" ;;
|
|
298
311
|
ci.version.features) _M="Fonctionnalités" ;;
|
|
@@ -352,7 +365,16 @@ _msg_fr() {
|
|
|
352
365
|
npm.first_published) _M="%s@%s publié par la pipeline." ;;
|
|
353
366
|
npm.step_trust) _M="3/3 Publication sans jeton (publication de confiance)" ;;
|
|
354
367
|
npm.trust_exists) _M="Publication de confiance déjà configurée." ;;
|
|
368
|
+
npm.trust_mismatch) _M="Relation de confiance existante mais ne correspond pas aux paramètres attendus :" ;;
|
|
369
|
+
npm.trust_confirm) _M="Confirmer la correction (nouvelle relation créée avant révocation de l'ancienne) ? [o/N] " ;;
|
|
370
|
+
npm.trust_cancelled) _M="Correction annulée." ;;
|
|
371
|
+
npm.trust_would_create) _M="[dry-run] Relation de confiance serait créée : %s, workflow %s, env %s" ;;
|
|
372
|
+
npm.trust_would_fix) _M="[dry-run] Relation de confiance serait corrigée (nouvelle créée, ancienne révoquée)" ;;
|
|
373
|
+
npm.trust_revoked) _M="Ancienne relation de confiance révoquée : %s" ;;
|
|
355
374
|
npm.trust_set) _M="Publication de confiance : %s, workflow %s." ;;
|
|
375
|
+
npm.token_present) _M="Secret NPM_TOKEN encore présent alors que la publication de confiance fonctionne." ;;
|
|
376
|
+
npm.token_remove_confirm) _M="Supprimer le secret NPM_TOKEN ? [o/N] " ;;
|
|
377
|
+
npm.token_would_remove) _M="[dry-run] Secret NPM_TOKEN serait supprimé" ;;
|
|
356
378
|
npm.token_removed) _M="Secret NPM_TOKEN supprimé du dépôt." ;;
|
|
357
379
|
npm.token_revoked) _M="Jeton temporaire révoqué sur npm." ;;
|
|
358
380
|
npm.done) _M="Terminé : chaque release publie %s sur npm, sans jeton." ;;
|
package/hooks/lib/tickets.sh
CHANGED
|
@@ -168,7 +168,8 @@ ticket_hint() {
|
|
|
168
168
|
[ -z "$REPLY" ] || return 0
|
|
169
169
|
attention_t tk.cli.no_ticket "$branch"
|
|
170
170
|
if tickets_managed && has gh; then
|
|
171
|
-
|
|
171
|
+
# git cc ne propose de créer le ticket qu'avec ticketAutoCreate
|
|
172
|
+
if ticket_auto_create; then dim_t tk.cli.propose_cc; else dim_t tk.cli.propose; fi
|
|
172
173
|
else
|
|
173
174
|
dim_t tk.cli.silence
|
|
174
175
|
fi
|
package/hooks/lib/ui.sh
CHANGED
|
@@ -31,7 +31,27 @@ repowarden_version_r() {
|
|
|
31
31
|
local tag=""
|
|
32
32
|
REPLY="$(sed -n 's/^ "version": *"\([^"]*\)".*/\1/p' "$1/package.json" 2>/dev/null | sed -n 1p)"
|
|
33
33
|
if [ -e "$1/.git" ]; then
|
|
34
|
-
|
|
34
|
+
# Sous-shell sans les variables que git exporte aux hooks (worktree) :
|
|
35
|
+
# elles l'emportent sur -C, ce seraient les tags du projet. Tag le plus
|
|
36
|
+
# proche de HEAD (git describe), remplacé par un tag de release dont le
|
|
37
|
+
# commit mergé est au moins aussi proche : la release est taguée sur le
|
|
38
|
+
# merge de la livraison dans main, invisible depuis develop pour
|
|
39
|
+
# describe ; à égalité, la version stable l'emporte sur la préversion
|
|
40
|
+
tag="$(
|
|
41
|
+
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR
|
|
42
|
+
best="$(git -C "$1" describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null || true)"
|
|
43
|
+
dist=""
|
|
44
|
+
[ -z "$best" ] || dist="$(git -C "$1" rev-list --count "$best..HEAD")"
|
|
45
|
+
while IFS= read -r t; do
|
|
46
|
+
git -C "$1" merge-base --is-ancestor "$t^2" HEAD 2>/dev/null || continue
|
|
47
|
+
d="$(git -C "$1" rev-list --count "$t^2..HEAD")"
|
|
48
|
+
if [ -z "$best" ] || [ "$d" -lt "$dist" ] ||
|
|
49
|
+
{ [ "$d" -eq "$dist" ] && [[ "$t" != *-* && "$best" == *-* ]]; }; then
|
|
50
|
+
best="$t" dist="$d"
|
|
51
|
+
fi
|
|
52
|
+
done < <(git -C "$1" tag --no-merged HEAD --list 'v[0-9]*' 2>/dev/null)
|
|
53
|
+
echo "$best"
|
|
54
|
+
)"
|
|
35
55
|
[ -z "$tag" ] || REPLY="${tag#v}"
|
|
36
56
|
fi
|
|
37
57
|
}
|
package/hooks/prepare-commit-msg
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Hook prepare-commit-msg : déduit le préfixe Conventional Commits du nom de branche.
|
|
3
3
|
# branche feat/bean : git commit → éditeur prérempli avec "feat(bean): "
|
|
4
4
|
# git commit -m "ajoute X" → "feat(bean): ajoute X"
|
|
5
|
-
# Un message déjà conforme n'est pas modifié.
|
|
5
|
+
# Un message déjà conforme, ou qui a la forme d'un en-tête, n'est pas modifié.
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
8
8
|
source "$(dirname "$0")/lib/common.sh"
|
|
@@ -36,6 +36,9 @@ fi
|
|
|
36
36
|
|
|
37
37
|
SUBJECT="$(grep -v '^#' "$MSG_FILE" | sed -n 1p || true)"
|
|
38
38
|
[[ "$SUBJECT" =~ $CC_PATTERN ]] && exit 0
|
|
39
|
+
# En-tête déjà tenté (mot: ou mot(portée):), même raté : jamais enveloppé,
|
|
40
|
+
# commit-msg le juge (« doc: x » deviendrait « feat(y): doc: x », accepté)
|
|
41
|
+
[[ "$SUBJECT" =~ ^[A-Za-z]+(\([^\)]*\))?!?: ]] && exit 0
|
|
39
42
|
[[ "$SUBJECT" =~ ^(Merge|Revert|fixup!|squash!) ]] && exit 0
|
|
40
43
|
|
|
41
44
|
# Ajoute le préfixe en tête de la première ligne (vide ou non)
|
package/package.json
CHANGED