repowarden 4.8.3 → 4.9.0-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/bin/app +20 -8
- package/bin/bots +27 -3
- package/bin/npm-publication +37 -67
- package/bin/proteger +34 -3
- package/bin/tickets +9 -3
- package/ci/tickets/issue.sh +38 -9
- package/ci/tickets/push.sh +26 -4
- package/hooks/lib/i18n/en.sh +18 -14
- package/hooks/lib/i18n/fr.sh +18 -14
- package/hooks/lib/tickets.sh +9 -5
- package/package.json +1 -1
package/bin/app
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# --administration, pour appliquer la configuration depuis la CI) ;
|
|
16
16
|
# 2. identifiants rangés sans être affichés : variable REPOWARDEN_APP_CLIENT_ID,
|
|
17
17
|
# secret REPOWARDEN_APP_KEY (clé privée) ;
|
|
18
|
-
# 3. installation sur le dépôt :
|
|
18
|
+
# 3. installation sur le dépôt : « Install » (vérifiée avec la clé ; relance : « non vérifiée »).
|
|
19
19
|
# Relançable : une App déjà configurée n'est pas recréée. Demande gh et node.
|
|
20
20
|
set -euo pipefail
|
|
21
21
|
|
|
@@ -75,9 +75,20 @@ if [ -n "$client_id" ] && grep -q "^${PREFIXE}_KEY" <<<"$(gh secret list --repo
|
|
|
75
75
|
slug="$(gh variable get "${PREFIXE}_SLUG" --repo "$REPO" 2>/dev/null || true)"
|
|
76
76
|
else
|
|
77
77
|
if [ "$(gh api "users/$owner" -q .type)" = Organization ]; then
|
|
78
|
-
|
|
78
|
+
apps="https://github.com/organizations/$owner/settings/apps"
|
|
79
79
|
else
|
|
80
|
-
|
|
80
|
+
apps="https://github.com/settings/apps"
|
|
81
|
+
fi
|
|
82
|
+
cible="$apps/new"
|
|
83
|
+
# Nom déjà pris (souvent une App orpheline d'un essai interrompu) : GitHub
|
|
84
|
+
# refuserait la création ; arrêt avec la marche à suivre. Slug : nom en
|
|
85
|
+
# minuscules, autres caractères en tirets ; une App privée d'un autre
|
|
86
|
+
# compte reste invisible (rappel à l'ouverture de la page)
|
|
87
|
+
slug_voulu="$(printf '%s' "$NOM" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')"
|
|
88
|
+
if [ "$(gh api "apps/$slug_voulu" -q .slug 2>/dev/null || true)" = "$slug_voulu" ]; then
|
|
89
|
+
err_t app.name_taken "$NOM"
|
|
90
|
+
info_t app.name_taken_fix "$apps"
|
|
91
|
+
exit 1
|
|
81
92
|
fi
|
|
82
93
|
sortie="$(mktemp)"
|
|
83
94
|
trap 'rm -f "$sortie"' EXIT
|
|
@@ -120,10 +131,11 @@ else
|
|
|
120
131
|
port="$(sed -n 's/^PORT //p' "$sortie")"
|
|
121
132
|
[ -n "$port" ] || { err_t app.server_failed; exit 1; }
|
|
122
133
|
info_t app.open "http://127.0.0.1:$port/"
|
|
134
|
+
dim_t app.name_hint "$NOM" "$apps"
|
|
123
135
|
ouvrir "http://127.0.0.1:$port/"
|
|
124
136
|
wait "$serveur" || true
|
|
125
137
|
code="$(sed -n 's/^CODE //p' "$sortie")"
|
|
126
|
-
[ -n "$code" ] || { err_t app.no_code; exit 1; }
|
|
138
|
+
[ -n "$code" ] || { err_t app.no_code; info_t app.name_taken_fix "$apps"; exit 1; }
|
|
127
139
|
# Conversion du code : identifiants de l'App, rangés sans être affichés
|
|
128
140
|
reponse="$(gh api -X POST "app-manifests/$code/conversions")"
|
|
129
141
|
client_id="$(node -p 'JSON.parse(require("fs").readFileSync(0, "utf8")).client_id' <<<"$reponse")"
|
|
@@ -153,10 +165,10 @@ installee() {
|
|
|
153
165
|
}
|
|
154
166
|
cle="${cle:-}"
|
|
155
167
|
if [ -z "$cle" ]; then
|
|
156
|
-
# Relance
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
168
|
+
# Relance : la clé privée n'est pas ici, et le jeton de gh ne peut pas
|
|
169
|
+
# lister les installations d'une App (user/installations : 403) ; état
|
|
170
|
+
# « non vérifié » plutôt qu'un succès non constaté
|
|
171
|
+
attention_t app.not_verified "${slug:-?}" "$REPO" "https://github.com/apps/${slug:-<slug>}/installations/new"
|
|
160
172
|
exit 0
|
|
161
173
|
fi
|
|
162
174
|
if [ -n "$slug" ] && ! installee; then
|
package/bin/bots
CHANGED
|
@@ -80,7 +80,7 @@ t bots.flow_question
|
|
|
80
80
|
printf '%s\n' "$REPLY" >&2
|
|
81
81
|
t bots.flow_main "$PRINCIPALE"
|
|
82
82
|
printf ' 1) %s\n' "$REPLY" >&2
|
|
83
|
-
t bots.flow_develop "$PRINCIPALE"
|
|
83
|
+
t bots.flow_develop "$PRINCIPALE" "$PRINCIPALE"
|
|
84
84
|
printf ' 2) %s\n' "$REPLY" >&2
|
|
85
85
|
# develop → main par défaut : seul develop entre dans main
|
|
86
86
|
choix_flux=2
|
|
@@ -90,8 +90,9 @@ case "${reponse:-$choix_flux}" in
|
|
|
90
90
|
*) FLUX=main; INTEGRATION="" ;;
|
|
91
91
|
esac
|
|
92
92
|
CREER_DEVELOP=""
|
|
93
|
+
# Nom renvoyé comparé : sur une 404, gh écrit le corps de l'erreur sur stdout
|
|
93
94
|
if [ "$FLUX" = develop ] &&
|
|
94
|
-
[
|
|
95
|
+
[ "$(gh api "repos/$REPO/branches/$INTEGRATION" --jq .name 2>/dev/null || true)" != "$INTEGRATION" ]; then
|
|
95
96
|
if demander bots.ask_create_develop o; then
|
|
96
97
|
CREER_DEVELOP=1
|
|
97
98
|
else
|
|
@@ -251,6 +252,27 @@ YAML
|
|
|
251
252
|
app-client-id: ${{ vars.REPOWARDEN_APP_CLIENT_ID }}
|
|
252
253
|
secrets:
|
|
253
254
|
app-key: ${{ secrets.REPOWARDEN_APP_KEY }}
|
|
255
|
+
YAML
|
|
256
|
+
fi
|
|
257
|
+
fi
|
|
258
|
+
# Tickets livrés : statut done et fermeture à la release, préprod à la
|
|
259
|
+
# préversion ; seulement avec le suivi des tickets (sinon le job échoue)
|
|
260
|
+
if choisi tickets || [ -e .github/workflows/tickets.yml ]; then
|
|
261
|
+
cat <<'YAML'
|
|
262
|
+
|
|
263
|
+
tickets:
|
|
264
|
+
needs: release
|
|
265
|
+
if: needs.release.outputs.release_created == 'true' || needs.release.outputs.prerelease_created == 'true'
|
|
266
|
+
uses: simdevWarden/repowarden/.github/workflows/tickets.yml@v4
|
|
267
|
+
permissions: { contents: write, issues: write, pull-requests: write, statuses: write }
|
|
268
|
+
with:
|
|
269
|
+
tag: ${{ needs.release.outputs.tag_name || needs.release.outputs.prerelease_tag }}
|
|
270
|
+
YAML
|
|
271
|
+
if [ -n "$AVEC_APP" ]; then
|
|
272
|
+
cat <<'YAML'
|
|
273
|
+
app-client-id: ${{ vars.REPOWARDEN_APP_CLIENT_ID }}
|
|
274
|
+
secrets:
|
|
275
|
+
app-key: ${{ secrets.REPOWARDEN_APP_KEY }}
|
|
254
276
|
YAML
|
|
255
277
|
fi
|
|
256
278
|
fi
|
|
@@ -332,7 +354,9 @@ if [ -n "$CREER_DEVELOP" ]; then
|
|
|
332
354
|
fi
|
|
333
355
|
if [ "$FLUX" = develop ]; then
|
|
334
356
|
conf="$GIT_TOPLEVEL/.repowarden.conf"
|
|
335
|
-
git config
|
|
357
|
+
# Valeur déjà en place : fichier laissé tel quel (git config le réindenterait)
|
|
358
|
+
[ "$(git config -f "$conf" --get repowarden.integrationBranch || true)" = "$INTEGRATION" ] ||
|
|
359
|
+
git config -f "$conf" repowarden.integrationBranch "$INTEGRATION"
|
|
336
360
|
git config -f "$conf" --get repowarden.protectedBranches >/dev/null ||
|
|
337
361
|
git config -f "$conf" repowarden.protectedBranches "$PRINCIPALE $INTEGRATION"
|
|
338
362
|
if [ -z "$AVEC_APP" ]; then
|
package/bin/npm-publication
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
# de confiance, OIDC) et joint la provenance au paquet.
|
|
6
6
|
#
|
|
7
7
|
# bin/npm-publication [--repo propriétaire/nom] [--workflow release.yml]
|
|
8
|
-
# [--environnement NOM]
|
|
8
|
+
# [--environnement NOM] [--dry-run]
|
|
9
9
|
#
|
|
10
10
|
# 1. connexion npm (npm login si besoin) ;
|
|
11
11
|
# 2. paquet absent de npm (la publication de confiance exige un paquet
|
|
12
|
-
# existant) :
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
12
|
+
# existant) : la dernière release est publiée d'ici, par npm publish lancé
|
|
13
|
+
# dans le terminal (npm conduit sa validation : navigateur ou code) ;
|
|
14
|
+
# aucune release : rien n'est publié, relancer après la première ;
|
|
15
|
+
# 3. publication de confiance (npm trust github), variable REPOWARDEN_NPM ;
|
|
16
|
+
# un ancien secret NPM_TOKEN et d'anciens jetons temporaires sont retirés.
|
|
17
|
+
# Interrompue, la commande reprend où elle en était.
|
|
18
18
|
set -euo pipefail
|
|
19
19
|
|
|
20
20
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
@@ -48,8 +48,7 @@ cd "$GIT_TOPLEVEL"
|
|
|
48
48
|
[ -f package.json ] || { err_t npm.no_package; exit 1; }
|
|
49
49
|
[ -n "$REPO" ] || REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner)"
|
|
50
50
|
name="$(node -p 'require("./package.json").name')"
|
|
51
|
-
# Préfixe des jetons temporaires
|
|
52
|
-
# les doublons, y compris celui d'une tentative interrompue) ; tous révoqués à la fin
|
|
51
|
+
# Préfixe des jetons temporaires de l'ancien parcours, révoqués à la fin
|
|
53
52
|
TOKEN_PREFIX="repowarden-ci-${REPO##*/}"
|
|
54
53
|
|
|
55
54
|
# Vérifie si une relation de confiance correspond aux paramètres attendus.
|
|
@@ -119,8 +118,8 @@ brand "npm · $name · $REPO"
|
|
|
119
118
|
step_t npm.step_login
|
|
120
119
|
npm whoami >/dev/null 2>&1 || npm login
|
|
121
120
|
ok_t npm.logged "$(npm whoami)"
|
|
122
|
-
#
|
|
123
|
-
#
|
|
121
|
+
# Configurer la publication de confiance exige la 2FA du compte (les codes
|
|
122
|
+
# envoyés par e-mail ne sont qu'une vérification de connexion)
|
|
124
123
|
if [ "$(npm profile get --json 2>/dev/null | node -e 'try{const p=JSON.parse(require("fs").readFileSync(0,"utf8"));process.stdout.write(p.tfa&&p.tfa.mode?"1":"")}catch{}')" != 1 ]; then
|
|
125
124
|
err_t npm.no_2fa
|
|
126
125
|
dim_t npm.hint_2fa "$(npm whoami)"
|
|
@@ -129,69 +128,40 @@ fi
|
|
|
129
128
|
|
|
130
129
|
publie() { [ -n "$(npm view "$name" name 2>/dev/null || true)" ]; }
|
|
131
130
|
|
|
132
|
-
# 2. Première publication, faite
|
|
131
|
+
# 2. Première publication, faite ici depuis la dernière release : npm conduit
|
|
132
|
+
# lui-même sa validation (navigateur ou code), dans le terminal. Aucun jeton
|
|
133
|
+
# créé ni stocké, aucun mot de passe saisi par repowarden
|
|
133
134
|
if ! publie; then
|
|
134
|
-
step_t npm.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# sont posées ici (saisie masquée) et le jeton va droit au secret
|
|
140
|
-
if [[ "$name" == @*/* ]]; then portee=(--scopes "${name%%/*}"); else portee=(--packages-all); fi
|
|
141
|
-
t npm.ask_password
|
|
142
|
-
read -r -s -p " $REPLY" mdp
|
|
143
|
-
echo
|
|
144
|
-
otp="" sortie=""
|
|
145
|
-
for essai in 1 2 3 4; do
|
|
146
|
-
# Sortie texte, pas --json : npm y masque le jeton (« npm_*** »)
|
|
147
|
-
if sortie="$(npm_config_password="$mdp" npm_config_otp="$otp" npm token create \
|
|
148
|
-
--name "$TOKEN_PREFIX-$(date +%Y%m%d%H%M%S)" --expires 7 "${portee[@]}" \
|
|
149
|
-
--packages-and-scopes-permission read-write --bypass-2fa </dev/null 2>&1)"; then
|
|
150
|
-
break
|
|
151
|
-
fi
|
|
152
|
-
# Code à usage unique (application ou e-mail) demandé : on le pose
|
|
153
|
-
if [ "$essai" -lt 4 ] && grep -qiE 'otp|one-time|two-factor' <<<"$sortie"; then
|
|
154
|
-
t npm.ask_otp
|
|
155
|
-
read -r -p " $REPLY" otp
|
|
156
|
-
continue
|
|
157
|
-
fi
|
|
158
|
-
sed -E 's/npm_[A-Za-z0-9]+/npm_***/g' <<<"$sortie" >&2
|
|
159
|
-
mdp=""
|
|
160
|
-
err_t npm.token_failed
|
|
161
|
-
exit 1
|
|
162
|
-
done
|
|
163
|
-
mdp=""
|
|
164
|
-
jeton="$(grep -oE 'npm_[A-Za-z0-9]+' <<<"$sortie" | sed -n 1p || true)"
|
|
165
|
-
sortie=""
|
|
166
|
-
[ -n "$jeton" ] || { err_t npm.token_failed; exit 1; }
|
|
167
|
-
printf '%s' "$jeton" | gh secret set NPM_TOKEN --repo "$REPO"
|
|
168
|
-
jeton=""
|
|
169
|
-
ok_t npm.token_stored
|
|
135
|
+
step_t npm.step_first
|
|
136
|
+
tag="$(gh release view --repo "$REPO" --json tagName -q .tagName 2>/dev/null || true)"
|
|
137
|
+
if [ -z "$tag" ]; then
|
|
138
|
+
attention_t npm.no_release "$name"
|
|
139
|
+
exit 0
|
|
170
140
|
fi
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
141
|
+
version="${tag#v}"
|
|
142
|
+
if [ "$DRY_RUN" = true ]; then
|
|
143
|
+
info_t npm.would_publish "$name" "$version" "$tag"
|
|
144
|
+
exit 0
|
|
145
|
+
fi
|
|
146
|
+
clone="$(mktemp -d "${TMPDIR:-/tmp}/repowarden-npm.XXXXXX")"
|
|
147
|
+
trap 'rm -rf "$clone"' EXIT
|
|
148
|
+
gh repo clone "$REPO" "$clone/src" -- -q --depth 1 --branch "$tag"
|
|
149
|
+
info_t npm.publishing "$name" "$version" "$tag"
|
|
150
|
+
# Sortie non capturée, entrée héritée : npm pose ses questions lui-même
|
|
151
|
+
if ! (cd "$clone/src" && npm pkg set "version=$version" && npm publish --access public); then
|
|
152
|
+
err_t npm.publish_failed "$name"
|
|
153
|
+
exit 1
|
|
181
154
|
fi
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
sleep 30
|
|
186
|
-
done
|
|
187
|
-
publie || { attention_t npm.not_yet; exit 0; }
|
|
188
|
-
ok_t npm.first_published "$name" "$(npm view "$name" version)"
|
|
155
|
+
rm -rf "$clone"
|
|
156
|
+
trap - EXIT
|
|
157
|
+
ok_t npm.first_published "$name" "$version"
|
|
189
158
|
fi
|
|
190
159
|
|
|
191
160
|
# 3. Publication de confiance, puis plus aucun jeton
|
|
192
161
|
step_t npm.step_trust
|
|
193
|
-
|
|
194
|
-
cas
|
|
162
|
+
# Pas de relation (1) ou relation différente (2) : ne doit pas arrêter le script (set -e)
|
|
163
|
+
cas=0
|
|
164
|
+
verifier_trust || cas=$?
|
|
195
165
|
if [ "$cas" = 0 ]; then
|
|
196
166
|
ok_t npm.trust_exists
|
|
197
167
|
elif [ "$cas" = 1 ]; then
|
package/bin/proteger
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
# - tags v* : créés, déplacés ou supprimés uniquement par les workflows ;
|
|
8
8
|
# - GitHub Actions autorisé à créer des PR (releases automatiques) ;
|
|
9
9
|
# - validation humaine (optionnelle) : approbations obligatoires, revue des
|
|
10
|
-
# CODEOWNERS, environnement de déploiement approuvé par des personnes
|
|
10
|
+
# CODEOWNERS, environnement de déploiement approuvé par des personnes ;
|
|
11
|
+
# livraison approuvée (deliveryApproval deployment ou review, défaut) ou
|
|
12
|
+
# automatique (auto : vérifications exigées gardées).
|
|
11
13
|
#
|
|
12
14
|
# bin/proteger [--repo propriétaire/nom] [--checks "repowarden,tests (ubuntu-latest)"] [--relecteurs N]
|
|
13
15
|
# [--codeowners] [--environnement NOM] [--approbateurs "login …"]
|
|
@@ -54,7 +56,7 @@ while [ $# -gt 0 ]; do
|
|
|
54
56
|
--sans-tags) NO_TAGS=1 ;;
|
|
55
57
|
--dry-run) DRY_RUN=1 ;;
|
|
56
58
|
--verifier) VERIFY=1 ;;
|
|
57
|
-
-h | --help) sed -n '2,
|
|
59
|
+
-h | --help) sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
58
60
|
*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
59
61
|
esac
|
|
60
62
|
shift
|
|
@@ -84,6 +86,16 @@ for b in $protected; do
|
|
|
84
86
|
[ "$(gh api "repos/$REPO/branches/$b" --jq .name 2>/dev/null)" = "$b" ] && existing="$existing $b"
|
|
85
87
|
done
|
|
86
88
|
[ -n "$existing" ] || { err_t ci.proteger.no_branch "$protected" "$REPO"; exit 1; }
|
|
89
|
+
# Branche d'intégration absente : arrêt avant toute modification (GitHub
|
|
90
|
+
# refuserait une branche par défaut inexistante, réglages posés à moitié)
|
|
91
|
+
if [ -n "$integration" ] &&
|
|
92
|
+
[ "$(gh api "repos/$REPO/branches/$integration" --jq .name 2>/dev/null)" != "$integration" ]; then
|
|
93
|
+
principale=main
|
|
94
|
+
for b in $existing; do [ "$b" = "$integration" ] || { principale="$b"; break; }; done
|
|
95
|
+
err_t ci.proteger.no_integration "$integration" "$REPO"
|
|
96
|
+
dim_t ci.proteger.no_integration_hint "$principale" "$integration"
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
87
99
|
|
|
88
100
|
# Vérifications séparées par des virgules : un nom de job peut contenir des
|
|
89
101
|
# espaces (« tests (ubuntu-latest) »)
|
|
@@ -179,14 +191,21 @@ if [ -n "$integration" ]; then
|
|
|
179
191
|
# l'approbation de l'environnement de livraison (« Approve and deploy »),
|
|
180
192
|
# exigée par une règle de main (déploiement réussi) ; la revue de PR n'est
|
|
181
193
|
# plus nécessaire (relectures configurées : gardées). review : revue de PR.
|
|
194
|
+
# auto : choix du projet, la validation humaine est le merge de chaque
|
|
195
|
+
# ticket ; la GitHub App arme le merge de la livraison (release-auto,
|
|
196
|
+
# delivery-auto), qui part quand les vérifications exigées sont vertes.
|
|
197
|
+
# Ni règle de déploiement ni environnement créé d'office ; vérifications
|
|
198
|
+
# et relectures configurées gardées.
|
|
182
199
|
cfg_r deliveryApproval deployment
|
|
183
200
|
DELIVERY="$REPLY"
|
|
184
|
-
[[ "$DELIVERY" =~ ^(deployment|review)$ ]] || { err_t ci.proteger.delivery_invalid; exit 1; }
|
|
201
|
+
[[ "$DELIVERY" =~ ^(deployment|review|auto)$ ]] || { err_t ci.proteger.delivery_invalid; exit 1; }
|
|
185
202
|
deploiement=""
|
|
186
203
|
if [ "$DELIVERY" = deployment ]; then
|
|
187
204
|
ENVIRONMENT="${ENVIRONMENT:-production}"
|
|
188
205
|
livraison="$REVIEWS"
|
|
189
206
|
deploiement="{\"type\": \"required_deployments\", \"parameters\": {\"required_deployment_environments\": [\"$ENVIRONMENT\"]}}"
|
|
207
|
+
elif [ "$DELIVERY" = auto ]; then
|
|
208
|
+
livraison="$REVIEWS"
|
|
190
209
|
fi
|
|
191
210
|
# PR à jour non exigée non plus : le merge commit d'une livraison n'existe
|
|
192
211
|
# que sur main ; strict, la livraison suivante serait bloquée tant que
|
|
@@ -218,6 +237,11 @@ if [ -n "$integration" ]; then
|
|
|
218
237
|
cfg_r autoMergeWork true
|
|
219
238
|
auto_merge="$REPLY"
|
|
220
239
|
[[ "$auto_merge" =~ ^(true|false)$ ]] || { err_t ci.proteger.automerge_invalid; exit 1; }
|
|
240
|
+
# Livraison automatique : merge automatique du dépôt indispensable
|
|
241
|
+
if [ "$DELIVERY" = auto ] && [ "$auto_merge" = false ]; then
|
|
242
|
+
err_t ci.proteger.auto_needs_automerge
|
|
243
|
+
exit 1
|
|
244
|
+
fi
|
|
221
245
|
# Sans ce workflow, les branches mergées ne seraient plus supprimées
|
|
222
246
|
if ! grep -qs "nettoyage-branches.yml" "$GIT_TOPLEVEL"/.github/workflows/*.y*ml; then
|
|
223
247
|
attention_t ci.proteger.no_cleanup
|
|
@@ -228,6 +252,11 @@ if [ -n "$integration" ]; then
|
|
|
228
252
|
! grep -qs "delivery-environment:" "$GIT_TOPLEVEL"/.github/workflows/*.y*ml; then
|
|
229
253
|
attention_t ci.proteger.no_delivery_env "$ENVIRONMENT"
|
|
230
254
|
fi
|
|
255
|
+
# Livraison automatique que rien n'arme : la PR se mergera à la main
|
|
256
|
+
if [ "$DELIVERY" = auto ] &&
|
|
257
|
+
! grep -qsE "^[[:space:]]*delivery-auto:[[:space:]]*true" "$GIT_TOPLEVEL"/.github/workflows/*.y*ml; then
|
|
258
|
+
attention_t ci.proteger.no_delivery_auto
|
|
259
|
+
fi
|
|
231
260
|
# Demande de livraison (manuelle, planifiée) qui ne remplace pas celle en
|
|
232
261
|
# attente : une demande oubliée bloquerait en silence les suivantes
|
|
233
262
|
for wf in "$GIT_TOPLEVEL"/.github/workflows/*.y*ml; do
|
|
@@ -266,6 +295,8 @@ echo_t ci.proteger.3 "$CHECKS"
|
|
|
266
295
|
echo_t ci.proteger.4 "$methods" "$delete_on_merge"
|
|
267
296
|
if [ -n "$integration" ] && [ "$DELIVERY" = deployment ]; then
|
|
268
297
|
echo_t ci.proteger.delivery_deploy "$ENVIRONMENT" "$auto_merge"
|
|
298
|
+
elif [ -n "$integration" ] && [ "$DELIVERY" = auto ]; then
|
|
299
|
+
echo_t ci.proteger.delivery_auto "$livraison"
|
|
269
300
|
elif [ -n "$integration" ]; then
|
|
270
301
|
echo_t ci.proteger.delivery "$livraison" "$auto_merge"
|
|
271
302
|
fi
|
package/bin/tickets
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
# mainteneur → backlog, sinon à valider ; --sans-validation-auto : tous à
|
|
13
13
|
# valider) ; branches des PR ouvertes et mergées inscrites sur leurs tickets.
|
|
14
14
|
# Ensuite : committer ces fichiers, exiger la vérification « ticket »
|
|
15
|
-
# (repowarden proteger --checks "…,ticket") et
|
|
15
|
+
# (repowarden proteger --checks "…,ticket") et, s'il n'y est pas, brancher le
|
|
16
|
+
# job tickets de release.yml (repowarden bots l'y écrit).
|
|
16
17
|
set -euo pipefail
|
|
17
18
|
|
|
18
19
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
@@ -41,7 +42,7 @@ if [ "$commande" = sync ]; then
|
|
|
41
42
|
exit 0
|
|
42
43
|
fi
|
|
43
44
|
if [ "$commande" != init ]; then
|
|
44
|
-
sed -n '2,
|
|
45
|
+
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
|
|
45
46
|
[ "$commande" = "" ] || [ "$commande" = -h ] || [ "$commande" = --help ]
|
|
46
47
|
exit
|
|
47
48
|
fi
|
|
@@ -149,4 +150,9 @@ esac
|
|
|
149
150
|
|
|
150
151
|
echo
|
|
151
152
|
ok_t tk.init_done
|
|
152
|
-
|
|
153
|
+
# Job tickets de release.yml déjà branché (modèle de repowarden bots) : rien à ajouter
|
|
154
|
+
if grep -qs 'workflows/tickets\.yml@' .github/workflows/release.yml; then
|
|
155
|
+
dim_t tk.init_next_wired
|
|
156
|
+
else
|
|
157
|
+
dim_t tk.init_next
|
|
158
|
+
fi
|
package/ci/tickets/issue.sh
CHANGED
|
@@ -31,27 +31,56 @@ accepter() {
|
|
|
31
31
|
-q '.[] | [.number, .headRefName, .headRefOid, .baseRefName, .isDraft] | @tsv')
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
# Ticket ouvert par un mainteneur (droits d'écriture) : c'est déjà sa
|
|
35
|
+
# décision, validé d'office. Vérifié par tout événement du ticket (idempotent) :
|
|
36
|
+
# GitHub ne garde qu'un run en attente par groupe de concurrence, le run
|
|
37
|
+
# « opened » peut être annulé par ceux des étiquettes posées à la création.
|
|
38
|
+
# Non si déjà validé, ou (hors ouverture) rangé au-delà de « à valider »
|
|
39
|
+
# (« validé » retiré à la main : décision respectée). Test seul : appelé dans un « if », où set -e
|
|
40
|
+
# ne s'applique pas.
|
|
41
|
+
a_valider_d_office() {
|
|
42
|
+
local labels l
|
|
43
|
+
[ "${AUTO_VALIDATE:-maintainers}" = maintainers ] || return 1
|
|
44
|
+
est_mainteneur "${ISSUE_ASSOCIATION:-}" || return 1
|
|
45
|
+
[ "${ISSUE_STATE:-open}" = open ] || return 1
|
|
46
|
+
labels="$(etiquettes "$ISSUE")"
|
|
47
|
+
! grep -qxF "$VALIDE" <<<"$labels" || return 1
|
|
48
|
+
[ "$ACTION" != opened ] || return 0
|
|
49
|
+
while IFS= read -r l; do
|
|
50
|
+
[[ "$l" == "statut: "* && "$l" != "$S_AVALIDER" ]] && return 1
|
|
51
|
+
done <<<"$labels"
|
|
52
|
+
return 0
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
valider_d_office() {
|
|
56
|
+
gh issue edit "$ISSUE" --add-label "$VALIDE" >/dev/null
|
|
57
|
+
statut "$ISSUE" "$S_AVALIDER"
|
|
58
|
+
accepter "$ISSUE"
|
|
59
|
+
}
|
|
60
|
+
|
|
34
61
|
cmd_issue() {
|
|
35
62
|
local pr sha head body
|
|
36
63
|
case "$ACTION" in
|
|
37
64
|
opened)
|
|
38
65
|
est_valide "$ISSUE" && return 0
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
est_mainteneur "${ISSUE_ASSOCIATION:-}"; then
|
|
43
|
-
gh issue edit "$ISSUE" --add-label "$VALIDE" >/dev/null
|
|
44
|
-
statut "$ISSUE" "$S_AVALIDER"
|
|
45
|
-
accepter "$ISSUE"
|
|
66
|
+
# Validé d'office, sinon un mainteneur pose « validé »
|
|
67
|
+
if a_valider_d_office; then
|
|
68
|
+
valider_d_office
|
|
46
69
|
else
|
|
47
70
|
statut "$ISSUE" "$S_AVALIDER"
|
|
48
71
|
fi
|
|
49
72
|
;;
|
|
50
73
|
labeled)
|
|
51
|
-
[ "${LABEL:-}" = "$VALIDE" ]
|
|
52
|
-
|
|
74
|
+
if [ "${LABEL:-}" = "$VALIDE" ]; then
|
|
75
|
+
accepter "$ISSUE"
|
|
76
|
+
elif a_valider_d_office; then
|
|
77
|
+
valider_d_office
|
|
78
|
+
fi
|
|
53
79
|
;;
|
|
54
80
|
assigned)
|
|
81
|
+
if a_valider_d_office; then
|
|
82
|
+
valider_d_office
|
|
83
|
+
fi
|
|
55
84
|
# Pris en charge : sa branche (s'il est validé), en cours s'il est
|
|
56
85
|
# validé ET assigné (quelle que soit la présence du label backlog,
|
|
57
86
|
# pour gérer la race validation+assignation quasi simultanée).
|
package/ci/tickets/push.sh
CHANGED
|
@@ -34,11 +34,32 @@ commit_impact_r() {
|
|
|
34
34
|
REPLY="$meilleur"
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
# REPLY =
|
|
38
|
-
#
|
|
39
|
-
#
|
|
37
|
+
# REPLY = en-tête du premier commit de la branche $1 par rapport à la branche
|
|
38
|
+
# d'intégration, sans « (#N) » final ; vide s'il n'est pas utilisable (aucun
|
|
39
|
+
# commit, non conforme, plus de 72 caractères). Lu par l'API : le checkout du
|
|
40
|
+
# workflow est celui de repowarden, pas l'historique du projet.
|
|
41
|
+
premier_commit_r() {
|
|
42
|
+
local s
|
|
43
|
+
s="$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$1" \
|
|
44
|
+
-q '.commits[0].commit.message // "" | split("\n")[0] // ""' 2>/dev/null || true)"
|
|
45
|
+
s="${s%%$'\n'*}"
|
|
46
|
+
s="${s%$'\r'}"
|
|
47
|
+
[[ "$s" =~ ^(.*[^[:space:]])[[:space:]]+\(#[0-9]+\)$ ]] && s="${BASH_REMATCH[1]}"
|
|
48
|
+
REPLY=""
|
|
49
|
+
[[ "$s" =~ $CC_PATTERN ]] || return 0
|
|
50
|
+
authored_length "$s"
|
|
51
|
+
[ "$REPLY" -le 72 ] || { REPLY=""; return 0; }
|
|
52
|
+
REPLY="$s"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# REPLY = titre de la PR brouillon du ticket $1 (branche $2) : en-tête du
|
|
56
|
+
# premier commit de la branche (déjà validé par les hooks, il devient le commit
|
|
57
|
+
# de squash), sinon « type(portée): sujet » depuis le ticket, sinon le commit
|
|
58
|
+
# conforme de plus fort impact, sinon le titre déduit de la branche
|
|
40
59
|
titre_pr_brouillon_r() {
|
|
41
60
|
local n="$1" branche="$2" type
|
|
61
|
+
premier_commit_r "$branche"
|
|
62
|
+
[ -z "$REPLY" ] || return 0
|
|
42
63
|
ticket_type_r "$(etiquettes "$n")"
|
|
43
64
|
type="$REPLY"
|
|
44
65
|
ticket_pr_title_r "$type" "$(gh issue view "$n" --json title -q .title)"
|
|
@@ -82,7 +103,8 @@ description_pr_brouillon_r() {
|
|
|
82
103
|
}
|
|
83
104
|
|
|
84
105
|
# Premier push sur la branche d'un ticket : PR brouillon vers la branche
|
|
85
|
-
# d'intégration,
|
|
106
|
+
# d'intégration, titrée par le premier commit (repli : le ticket), à la
|
|
107
|
+
# description reprise du ticket, citant le
|
|
86
108
|
# ticket, assignée à l'auteur du push ; ticket en cours. Ouverte par le jeton
|
|
87
109
|
# des Actions, elle ne lance pas la CI : elle tourne au push suivant, ou au
|
|
88
110
|
# passage « prête pour revue ». Tant qu'elle est en brouillon, chaque push met
|
package/hooks/lib/i18n/en.sh
CHANGED
|
@@ -284,10 +284,15 @@ _msg_en() {
|
|
|
284
284
|
ci.proteger.reviews_invalid) _M="--relecteurs: a number from 0 to 9 is expected." ;;
|
|
285
285
|
ci.proteger.codeowners_invalid) _M="codeOwnerReview: true or false." ;;
|
|
286
286
|
ci.proteger.delivery_deploy) _M=" delivery into main: approval of the \"%s\" deployment (Approve and deploy), then merged by the GitHub App; auto-merge of work PRs into develop: %s" ;;
|
|
287
|
-
ci.proteger.delivery_invalid) _M="deliveryApproval: deployment or
|
|
287
|
+
ci.proteger.delivery_invalid) _M="deliveryApproval: deployment, review or auto." ;;
|
|
288
|
+
ci.proteger.auto_needs_automerge) _M="deliveryApproval = auto needs the repository's auto-merge: remove autoMergeWork = false from .repowarden.conf, or choose deliveryApproval = deployment or review." ;;
|
|
289
|
+
ci.proteger.no_delivery_auto) _M="deliveryApproval = auto, but no workflow calls release-auto.yml with delivery-auto: true: nothing will arm the delivery merge, which will be done by hand (see docs/releases.md, automatic delivery)." ;;
|
|
290
|
+
ci.proteger.delivery_auto) _M=" delivery into main: automatic (the GitHub App merges it as soon as the required checks are green; %s approval(s) required)" ;;
|
|
288
291
|
ci.proteger.automerge_invalid) _M="autoMergeWork: true or false." ;;
|
|
289
292
|
ci.proteger.unattributed_invalid) _M="unattributedApproval: true or false." ;;
|
|
290
293
|
ci.proteger.no_branch) _M="None of the branches \"%s\" exists on %s." ;;
|
|
294
|
+
ci.proteger.no_integration) _M="Integration branch \"%s\" missing on %s: nothing was changed." ;;
|
|
295
|
+
ci.proteger.no_integration_hint) _M=" Create it from the main branch: git push origin %s:refs/heads/%s (or repowarden bots), then run again." ;;
|
|
291
296
|
ci.proteger.no_cleanup) _M="Automatic branch deletion disabled (it would delete develop): add a workflow calling nettoyage-branches.yml, otherwise merged branches remain (see In an organisation)." ;;
|
|
292
297
|
ci.proteger.no_delivery_env) _M="main will require a successful \"%s\" deployment, but no workflow creates that deployment: a deployment nothing creates would block main forever. Call release-auto.yml with delivery-environment (see docs/releases.md, develop flow), or set deliveryApproval = review in .repowarden.conf." ;;
|
|
293
298
|
ci.proteger.stale_concurrency) _M="%s: a pending delivery request (manual or scheduled) would silently block the next ones. Set cancel-in-progress: \${{ github.event_name != 'push' }} (a new request replaces the pending one) and one group per trigger: release-\${{ github.ref_name }}-\${{ github.event.schedule || github.event_name }}." ;;
|
|
@@ -391,19 +396,14 @@ _msg_en() {
|
|
|
391
396
|
npm.old_npm) _M="npm %s is too old: 11.15 or later required (npm install -g npm@11)." ;;
|
|
392
397
|
npm.step_login) _M="1/3 npm account" ;;
|
|
393
398
|
npm.logged) _M="Logged in to npm: %s" ;;
|
|
394
|
-
npm.no_2fa) _M="Two-factor authentication (2FA) is not enabled on the npm account: npm requires it to
|
|
399
|
+
npm.no_2fa) _M="Two-factor authentication (2FA) is not enabled on the npm account: npm requires it to configure tokenless publishing." ;;
|
|
395
400
|
npm.hint_2fa) _M="Enable it: npm profile enable-2fa auth-and-writes (QR code for an authenticator app), or https://www.npmjs.com/settings/%s/tfa; then run this command again." ;;
|
|
396
|
-
npm.
|
|
397
|
-
npm.
|
|
398
|
-
npm.
|
|
399
|
-
npm.
|
|
400
|
-
npm.
|
|
401
|
-
npm.
|
|
402
|
-
npm.dispatched) _M="Pipeline started (%s): it publishes the latest release to npm. Waiting… (Ctrl+C: run the command again later, it resumes)" ;;
|
|
403
|
-
npm.waiting) _M="The next release publishes %s: merge a PR (or let the bot merge the release PR). Waiting… (Ctrl+C: run the command again later, it resumes)" ;;
|
|
404
|
-
npm.still_waiting) _M="still waiting (%s min)…" ;;
|
|
405
|
-
npm.not_yet) _M="Not published yet: run this command again after the next release." ;;
|
|
406
|
-
npm.first_published) _M="%s@%s published by the pipeline." ;;
|
|
401
|
+
npm.step_first) _M="2/3 First publication, from this machine (latest release)" ;;
|
|
402
|
+
npm.no_release) _M="No release yet: nothing is published. Run this command again after the first release of %s." ;;
|
|
403
|
+
npm.would_publish) _M="[dry-run] %s@%s would be published from release %s (npm publish, in this terminal)" ;;
|
|
404
|
+
npm.publishing) _M="Publishing %s@%s (release %s): npm may ask for a validation (browser or code)." ;;
|
|
405
|
+
npm.publish_failed) _M="Publication of %s refused (see above): nothing else was configured. Run this command again." ;;
|
|
406
|
+
npm.first_published) _M="%s@%s published." ;;
|
|
407
407
|
npm.step_trust) _M="3/3 Tokenless publishing (trusted publishing)" ;;
|
|
408
408
|
npm.trust_exists) _M="Trusted publishing already configured." ;;
|
|
409
409
|
npm.trust_mismatch) _M="Existing trust relationship does not match expected parameters:" ;;
|
|
@@ -465,7 +465,10 @@ _msg_en() {
|
|
|
465
465
|
app.step_install) _M="Installing on the repository" ;;
|
|
466
466
|
app.installed) _M="App %s installed on %s." ;;
|
|
467
467
|
app.not_installed) _M="App %s not installed on %s yet: run repowarden app init again once installed." ;;
|
|
468
|
-
app.
|
|
468
|
+
app.not_verified) _M="Installation of App %s on %s not verified: the private key is not on this machine (only the App can list its installations). If it is not installed, workflows will fail at the \"Jeton de l'App\" step. To install it: %s" ;;
|
|
469
|
+
app.name_taken) _M="A GitHub App named %s already exists (often an orphan App from an interrupted attempt: created on GitHub, credentials never stored)." ;;
|
|
470
|
+
app.name_taken_fix) _M="Either run again with --nom <other name>, or delete the orphan App (Settings → Developer settings → GitHub Apps → the App → Advanced → Delete): %s" ;;
|
|
471
|
+
app.name_hint) _M="If GitHub refuses the name %s (already taken): Ctrl+C, then run again with --nom <other name>, or delete the orphan App: %s" ;;
|
|
469
472
|
app.done) _M="repowarden workflows now use the App: what they do triggers the other workflows." ;;
|
|
470
473
|
app.next) _M="Without the App (variable and secret missing), they fall back to the Actions token." ;;
|
|
471
474
|
app.bot_all_repos) _M="Organization bot: when installing, choose \"All repositories\" (new repositories are covered automatically; only those with a .repowarden.conf are managed)." ;;
|
|
@@ -577,6 +580,7 @@ _msg_en() {
|
|
|
577
580
|
tk.init_auto_create_on) _M="Auto ticket creation: ENABLED (ticketAutoCreate = true)" ;;
|
|
578
581
|
tk.init_auto_create_off) _M="Auto ticket creation: DISABLED (default). To enable: git config repowarden.ticketAutoCreate true" ;;
|
|
579
582
|
tk.init_next) _M="Next: commit these files; require the \"ticket\" check (repowarden proteger --checks \"…,ticket\"); in release.yml, wire the tickets job (see the Tickets docs)." ;;
|
|
583
|
+
tk.init_next_wired) _M="Next: commit these files; require the \"ticket\" check (repowarden proteger --checks \"…,ticket\"). The tickets job of release.yml is already wired." ;;
|
|
580
584
|
tk.prerelease) _M="Available in pre-release **%s**: to be tested before production." ;;
|
|
581
585
|
tk.released) _M="Released in **%s**." ;;
|
|
582
586
|
# Ticket comment
|
package/hooks/lib/i18n/fr.sh
CHANGED
|
@@ -284,10 +284,15 @@ _msg_fr() {
|
|
|
284
284
|
ci.proteger.reviews_invalid) _M="--relecteurs : nombre de 0 à 9 attendu." ;;
|
|
285
285
|
ci.proteger.codeowners_invalid) _M="codeOwnerReview : true ou false." ;;
|
|
286
286
|
ci.proteger.delivery_deploy) _M=" livraison vers main : approbation du déploiement « %s » (Approve and deploy), puis merge par la GitHub App ; merge automatique des PR de travail vers develop : %s" ;;
|
|
287
|
-
ci.proteger.delivery_invalid) _M="deliveryApproval : deployment ou
|
|
287
|
+
ci.proteger.delivery_invalid) _M="deliveryApproval : deployment, review ou auto." ;;
|
|
288
|
+
ci.proteger.auto_needs_automerge) _M="deliveryApproval = auto demande le merge automatique du dépôt : retirer autoMergeWork = false de .repowarden.conf, ou choisir deliveryApproval = deployment ou review." ;;
|
|
289
|
+
ci.proteger.no_delivery_auto) _M="deliveryApproval = auto, mais aucun workflow n'appelle release-auto.yml avec delivery-auto: true : rien n'armera le merge de la livraison, qui se fera à la main (voir docs/releases.md, livraison automatique)." ;;
|
|
290
|
+
ci.proteger.delivery_auto) _M=" livraison vers main : automatique (la GitHub App la merge dès que les vérifications exigées sont vertes ; %s approbation(s) exigée(s))" ;;
|
|
288
291
|
ci.proteger.automerge_invalid) _M="autoMergeWork : true ou false." ;;
|
|
289
292
|
ci.proteger.unattributed_invalid) _M="unattributedApproval : true ou false." ;;
|
|
290
293
|
ci.proteger.no_branch) _M="Aucune des branches « %s » n'existe sur %s." ;;
|
|
294
|
+
ci.proteger.no_integration) _M="Branche d'intégration « %s » absente de %s : rien n'a été modifié." ;;
|
|
295
|
+
ci.proteger.no_integration_hint) _M=" La créer depuis la branche principale : git push origin %s:refs/heads/%s (ou repowarden bots), puis relancer." ;;
|
|
291
296
|
ci.proteger.no_cleanup) _M="Suppression automatique des branches désactivée (elle supprimerait develop) : ajouter un workflow qui appelle nettoyage-branches.yml, sinon les branches mergées restent (voir En organisation)." ;;
|
|
292
297
|
ci.proteger.no_delivery_env) _M="main exigera un déploiement « %s » réussi, mais aucun workflow ne crée ce déploiement : un déploiement que rien ne crée bloquerait main pour toujours. Appeler release-auto.yml avec delivery-environment (voir docs/releases.md, flux develop), ou deliveryApproval = review dans .repowarden.conf." ;;
|
|
293
298
|
ci.proteger.stale_concurrency) _M="%s : une demande de livraison (manuelle ou planifiée) en attente bloquerait en silence les suivantes. Mettre cancel-in-progress: \${{ github.event_name != 'push' }} (une nouvelle demande remplace celle en attente) et un groupe par déclencheur : release-\${{ github.ref_name }}-\${{ github.event.schedule || github.event_name }}." ;;
|
|
@@ -391,19 +396,14 @@ _msg_fr() {
|
|
|
391
396
|
npm.old_npm) _M="npm %s trop ancien : 11.15 au minimum (npm install -g npm@11)." ;;
|
|
392
397
|
npm.step_login) _M="1/3 Compte npm" ;;
|
|
393
398
|
npm.logged) _M="Connecté à npm : %s" ;;
|
|
394
|
-
npm.no_2fa) _M="Double authentification (2FA) non activée sur le compte npm : npm l'exige pour
|
|
399
|
+
npm.no_2fa) _M="Double authentification (2FA) non activée sur le compte npm : npm l'exige pour configurer la publication sans jeton." ;;
|
|
395
400
|
npm.hint_2fa) _M="Activer : npm profile enable-2fa auth-and-writes (QR code pour une application d'authentification), ou https://www.npmjs.com/settings/%s/tfa ; puis relancer cette commande." ;;
|
|
396
|
-
npm.
|
|
397
|
-
npm.
|
|
398
|
-
npm.
|
|
399
|
-
npm.
|
|
400
|
-
npm.
|
|
401
|
-
npm.
|
|
402
|
-
npm.dispatched) _M="Pipeline lancée (%s) : elle publie la dernière release sur npm. Attente… (Ctrl+C : relancer la commande plus tard, elle reprendra)" ;;
|
|
403
|
-
npm.waiting) _M="La prochaine release publie %s : merge une PR (ou laisse le bot merger la PR de release). Attente… (Ctrl+C : relancer la commande plus tard, elle reprendra)" ;;
|
|
404
|
-
npm.still_waiting) _M="toujours en attente (%s min)…" ;;
|
|
405
|
-
npm.not_yet) _M="Pas encore publié : relance cette commande après la prochaine release." ;;
|
|
406
|
-
npm.first_published) _M="%s@%s publié par la pipeline." ;;
|
|
401
|
+
npm.step_first) _M="2/3 Première publication, depuis ce poste (dernière release)" ;;
|
|
402
|
+
npm.no_release) _M="Aucune release encore : rien n'est publié. Relance cette commande après la première release de %s." ;;
|
|
403
|
+
npm.would_publish) _M="[dry-run] %s@%s serait publié depuis la release %s (npm publish, dans ce terminal)" ;;
|
|
404
|
+
npm.publishing) _M="Publication de %s@%s (release %s) : npm peut demander une validation (navigateur ou code)." ;;
|
|
405
|
+
npm.publish_failed) _M="Publication de %s refusée (voir ci-dessus) : rien d'autre n'a été configuré. Relance cette commande." ;;
|
|
406
|
+
npm.first_published) _M="%s@%s publié." ;;
|
|
407
407
|
npm.step_trust) _M="3/3 Publication sans jeton (publication de confiance)" ;;
|
|
408
408
|
npm.trust_exists) _M="Publication de confiance déjà configurée." ;;
|
|
409
409
|
npm.trust_mismatch) _M="Relation de confiance existante mais ne correspond pas aux paramètres attendus :" ;;
|
|
@@ -465,7 +465,10 @@ _msg_fr() {
|
|
|
465
465
|
app.step_install) _M="Installation sur le dépôt" ;;
|
|
466
466
|
app.installed) _M="App %s installée sur %s." ;;
|
|
467
467
|
app.not_installed) _M="App %s pas encore installée sur %s : relance repowarden app init une fois installée." ;;
|
|
468
|
-
app.
|
|
468
|
+
app.not_verified) _M="Installation de l'App %s sur %s non vérifiée : la clé privée n'est pas sur ce poste (seule l'App peut lister ses installations). Si elle n'est pas installée, les workflows échoueront à l'étape « Jeton de l'App ». L'installer : %s" ;;
|
|
469
|
+
app.name_taken) _M="Une GitHub App s'appelle déjà %s (souvent une App orpheline d'un essai interrompu : créée sur GitHub, identifiants jamais rangés)." ;;
|
|
470
|
+
app.name_taken_fix) _M="Au choix : relancer avec --nom <autre nom>, ou supprimer l'App orpheline (Settings → Developer settings → GitHub Apps → l'App → Advanced → Delete) : %s" ;;
|
|
471
|
+
app.name_hint) _M="Si GitHub refuse le nom %s (déjà utilisé) : Ctrl+C, puis relancer avec --nom <autre nom>, ou supprimer l'App orpheline : %s" ;;
|
|
469
472
|
app.done) _M="Les workflows repowarden utilisent désormais l'App : ce qu'ils font déclenche les autres workflows." ;;
|
|
470
473
|
app.next) _M="Sans l'App (variable et secret absents), ils reprennent le jeton des Actions." ;;
|
|
471
474
|
app.bot_all_repos) _M="Bot d'organisation : à l'installation, choisir « All repositories » (les nouveaux dépôts seront couverts d'office ; seuls ceux avec un .repowarden.conf sont gérés)." ;;
|
|
@@ -577,6 +580,7 @@ _msg_fr() {
|
|
|
577
580
|
tk.init_auto_create_on) _M="Création auto de ticket : ACTIVÉE (ticketAutoCreate = true)" ;;
|
|
578
581
|
tk.init_auto_create_off) _M="Création auto de ticket : DÉSACTIVÉE (défaut). Pour l'activer : git config repowarden.ticketAutoCreate true" ;;
|
|
579
582
|
tk.init_next) _M="Ensuite : committer ces fichiers ; exiger la vérification « ticket » (repowarden proteger --checks \"…,ticket\") ; dans release.yml, brancher le job tickets (voir la doc Tickets)." ;;
|
|
583
|
+
tk.init_next_wired) _M="Ensuite : committer ces fichiers ; exiger la vérification « ticket » (repowarden proteger --checks \"…,ticket\"). Le job tickets de release.yml est déjà branché." ;;
|
|
580
584
|
tk.prerelease) _M="Disponible en préversion **%s** : à tester avant la production." ;;
|
|
581
585
|
tk.released) _M="Publié dans **%s**." ;;
|
|
582
586
|
# Ticket comment
|
package/hooks/lib/tickets.sh
CHANGED
|
@@ -63,8 +63,10 @@ ticket_branch_r() {
|
|
|
63
63
|
# REPLY = titre de PR d'un ticket : $1 type (« feat » par défaut), $2 titre du
|
|
64
64
|
# ticket → « feat(portée): sujet ». Portée = préfixe du titre avant le premier
|
|
65
65
|
# « : » (« Tickets : … » → « tickets »), 20 caractères au plus, omise si vide ;
|
|
66
|
-
# sujet = le reste, première lettre abaissée
|
|
67
|
-
#
|
|
66
|
+
# sujet = le reste, première lettre abaissée sauf sur un sigle (README, IA :
|
|
67
|
+
# initiale suivie d'une majuscule). 72 caractères au plus, coupé à la dernière
|
|
68
|
+
# espace, jamais dans un mot. Vide si le titre ne donne rien (l'appelant décide
|
|
69
|
+
# du repli).
|
|
68
70
|
ticket_pr_title_r() {
|
|
69
71
|
local type="${1:-feat}" titre="$2" portee sujet tete res
|
|
70
72
|
sujet="$titre"
|
|
@@ -75,15 +77,17 @@ ticket_pr_title_r() {
|
|
|
75
77
|
[ "${#portee}" -le 20 ] || portee=""
|
|
76
78
|
sujet="${BASH_REMATCH[2]}"
|
|
77
79
|
fi
|
|
78
|
-
|
|
80
|
+
[[ "${sujet:1:1}" == [[:upper:]] ]] ||
|
|
81
|
+
sujet="$(printf '%s' "${sujet:0:1}" | LC_ALL=C tr '[:upper:]' '[:lower:]')${sujet:1}"
|
|
79
82
|
tete="$type${portee:+($portee)}: "
|
|
80
83
|
REPLY=""
|
|
81
84
|
[ -n "$sujet" ] || return 0
|
|
82
85
|
res="$tete$sujet"
|
|
83
|
-
# 72 caractères au plus, coupé sur un mot entier (authored_length → REPLY)
|
|
86
|
+
# 72 caractères au plus, coupé sur un mot entier (authored_length → REPLY) ;
|
|
87
|
+
# un mot seul trop long n'est pas tronqué : titre vide
|
|
84
88
|
authored_length "$res"
|
|
85
89
|
while [ "$REPLY" -gt 72 ] && [ -n "$sujet" ]; do
|
|
86
|
-
if [[ "$sujet" == *" "* ]]; then sujet="${sujet% *}"; else sujet="
|
|
90
|
+
if [[ "$sujet" == *" "* ]]; then sujet="${sujet% *}"; else sujet=""; fi
|
|
87
91
|
res="$tete$sujet"
|
|
88
92
|
authored_length "$res"
|
|
89
93
|
done
|
package/package.json
CHANGED