repowarden 4.8.3 → 4.9.0-next.10

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/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,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
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 brancher le job tickets de release.yml.
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,15p' "$0" | sed 's/^# \{0,1\}//'
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
- dim_t tk.init_next
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
@@ -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
- # Ouvert par un mainteneur (droits d'écriture) : c'est déjà sa
40
- # décision, validé d'office ; sinon, un mainteneur pose « validé »
41
- if [ "${AUTO_VALIDATE:-maintainers}" = maintainers ] &&
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" ] || return 0
52
- accepter "$ISSUE"
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).
@@ -34,11 +34,32 @@ commit_impact_r() {
34
34
  REPLY="$meilleur"
35
35
  }
36
36
 
37
- # REPLY = titre de la PR brouillon du ticket $1 (branche $2) : « type(portée):
38
- # sujet » depuis le ticket, sinon le commit conforme de plus fort impact de la
39
- # branche, sinon le titre déduit de la branche
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, au titre et à la description repris du ticket, citant le
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
@@ -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 review." ;;
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 create the token and configure tokenless publishing." ;;
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.step_token) _M="2/3 First publication, by the pipeline (temporary token)" ;;
397
- npm.token_exists) _M="NPM_TOKEN secret already present: reused." ;;
398
- npm.ask_password) _M="npm password (hidden input, sent to npm only): " ;;
399
- npm.ask_otp) _M="One-time code (authenticator app or npm email): " ;;
400
- npm.token_failed) _M="Could not create the npm token (see above): nothing was stored." ;;
401
- npm.token_stored) _M="Temporary token (7 days) created and stored as the NPM_TOKEN secret, never displayed." ;;
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:" ;;
@@ -461,11 +461,18 @@ _msg_en() {
461
461
  app.server_failed) _M="Local page did not start." ;;
462
462
  app.open) _M="Open %s then click GitHub's green button." ;;
463
463
  app.no_code) _M="No answer from GitHub (creation cancelled, or 15 min timeout)." ;;
464
+ app.container) _M="Container detected: the local creation page (127.0.0.1) would not be reachable from the host's browser. Nothing was created." ;;
465
+ app.howto) _M="Run the command on the host: %s (or here with REPOWARDEN_CONTENEUR=0 if your browser reaches this container's 127.0.0.1: host network, forwarded port)." ;;
466
+ app.page_timeout) _M="Local page not visited within %s s: stopped, nothing was created." ;;
467
+ app.orphan) _M="If you clicked \"Create\", the App exists without retrieved credentials: delete it (GitHub settings → Developer settings → GitHub Apps) before running again." ;;
464
468
  app.created) _M="App %s created; credentials stored (variable %s, secret %s)." ;;
465
469
  app.step_install) _M="Installing on the repository" ;;
466
470
  app.installed) _M="App %s installed on %s." ;;
467
471
  app.not_installed) _M="App %s not installed on %s yet: run repowarden app init again once installed." ;;
468
- app.check_in_ci) _M="Installation checked by the first ticket tracking run (\"Jeton de l'App\" step); to install it: %s" ;;
472
+ 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" ;;
473
+ 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)." ;;
474
+ 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" ;;
475
+ 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
476
  app.done) _M="repowarden workflows now use the App: what they do triggers the other workflows." ;;
470
477
  app.next) _M="Without the App (variable and secret missing), they fall back to the Actions token." ;;
471
478
  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)." ;;
@@ -524,12 +531,27 @@ _msg_en() {
524
531
  bots.ask_app) _M="GitHub App?" ;;
525
532
  bots.ask_proteger) _M="Branch protection?" ;;
526
533
  bots.ask_npm) _M="npm publishing?" ;;
534
+ bots.desc_delivery) _M="develop → main delivery: automatic (the GitHub App merges the delivery PR as soon as the checks are green; human validation is the merge of each ticket) or human approval (default)" ;;
535
+ bots.ask_delivery_auto) _M="Automatic delivery? (no: human approval)" ;;
536
+ bots.auto_no_app) _M="Automatic delivery without a GitHub App: the automatic merge requires the App (repowarden app init); without it, the delivery PR has to be merged by hand." ;;
537
+ bots.desc_rythme) _M="Pace: on every ticket (prerelease and delivery request on every merge) or grouped (default: evening prerelease, Friday delivery window)" ;;
538
+ bots.ask_each_ticket) _M="On every ticket? (no: grouped)" ;;
539
+ bots.ask_preversion) _M="Prerelease required before a stable version?" ;;
527
540
  bots.preview) _M="Preview" ;;
528
541
  bots.prev_develop) _M="branch %s created on GitHub from %s" ;;
529
542
  bots.prev_conf) _M=".repowarden.conf: integrationBranch = %s, protected branches" ;;
530
543
  bots.prev_review) _M=".repowarden.conf: delivery approved by reviewing its PR (no GitHub App)" ;;
544
+ bots.prev_conf_auto) _M=".repowarden.conf: deliveryApproval = auto (automatic delivery)" ;;
545
+ bots.prev_delivery_auto) _M="release.yml: automatic delivery (delivery-auto), the GitHub App merges the delivery PR as soon as the checks are green" ;;
546
+ bots.prev_delivery_approval) _M="release.yml: delivery approved by a person" ;;
547
+ bots.prev_each_ticket) _M="release.yml: on every ticket, prerelease and delivery request on every merge" ;;
548
+ bots.prev_grouped) _M="release.yml: grouped, evening prerelease, Friday delivery window" ;;
549
+ bots.prev_preversion_on) _M="release.yml: prerelease required before a stable version" ;;
550
+ bots.prev_preversion_off) _M="release.yml: stable version possible without a prerelease (preversion-obligatoire: false)" ;;
531
551
  bots.prev_kept) _M="%s exists: kept as is (difference with the template: %s line(s))" ;;
532
552
  bots.prev_file) _M="%s: created" ;;
553
+ bots.prev_ci_langs) _M=" CI tooling: %s (strict when formatting and tests have their tools)" ;;
554
+ bots.prev_ci_generic) _M=" language not recognized: generic template, add the project tooling (setup-*) then strict: true" ;;
533
555
  bots.prev_tickets) _M="tickets: labels and ticket template on GitHub, tickets.yml workflow" ;;
534
556
  bots.prev_app) _M="GitHub App: created in the browser, credentials stored as repository variable and secret" ;;
535
557
  bots.prev_proteger) _M="GitHub settings: branch and tag rulesets, merge methods" ;;
@@ -577,6 +599,7 @@ _msg_en() {
577
599
  tk.init_auto_create_on) _M="Auto ticket creation: ENABLED (ticketAutoCreate = true)" ;;
578
600
  tk.init_auto_create_off) _M="Auto ticket creation: DISABLED (default). To enable: git config repowarden.ticketAutoCreate true" ;;
579
601
  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)." ;;
602
+ 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
603
  tk.prerelease) _M="Available in pre-release **%s**: to be tested before production." ;;
581
604
  tk.released) _M="Released in **%s**." ;;
582
605
  # Ticket comment
@@ -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 review." ;;
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 créer le jeton et configurer la publication sans jeton." ;;
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.step_token) _M="2/3 Première publication, par la pipeline (jeton temporaire)" ;;
397
- npm.token_exists) _M="Secret NPM_TOKEN déjà présent : réutilisé." ;;
398
- npm.ask_password) _M="Mot de passe npm (saisie masquée, transmis à npm seulement) : " ;;
399
- npm.ask_otp) _M="Code à usage unique (application d'authentification ou e-mail de npm) : " ;;
400
- npm.token_failed) _M="Création du jeton npm impossible (voir ci-dessus) : rien n'a été enregistré." ;;
401
- npm.token_stored) _M="Jeton temporaire (7 jours) créé et enregistré en secret NPM_TOKEN, sans affichage." ;;
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 :" ;;
@@ -461,11 +461,18 @@ _msg_fr() {
461
461
  app.server_failed) _M="Page locale non démarrée." ;;
462
462
  app.open) _M="Ouvre %s puis clique sur le bouton vert de GitHub." ;;
463
463
  app.no_code) _M="Aucune réponse de GitHub (création annulée, ou délai de 15 min dépassé)." ;;
464
+ app.container) _M="Conteneur détecté : la page locale de création (127.0.0.1) ne serait pas joignable depuis le navigateur de l'hôte. Rien n'a été créé." ;;
465
+ app.howto) _M="Lance la commande sur l'hôte : %s (ou ici avec REPOWARDEN_CONTENEUR=0 si ton navigateur joint le 127.0.0.1 de ce conteneur : réseau de l'hôte, port redirigé)." ;;
466
+ app.page_timeout) _M="Page locale non consultée en %s s : arrêt, rien n'a été créé." ;;
467
+ app.orphan) _M="Si tu as cliqué « Create », l'App existe sans identifiants récupérés : supprime-la (paramètres GitHub → Developer settings → GitHub Apps) avant de relancer." ;;
464
468
  app.created) _M="App %s créée ; identifiants rangés (variable %s, secret %s)." ;;
465
469
  app.step_install) _M="Installation sur le dépôt" ;;
466
470
  app.installed) _M="App %s installée sur %s." ;;
467
471
  app.not_installed) _M="App %s pas encore installée sur %s : relance repowarden app init une fois installée." ;;
468
- app.check_in_ci) _M="Installation vérifiée au premier run du suivi des tickets (étape « Jeton de l'App ») ; pour l'installer : %s" ;;
472
+ 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" ;;
473
+ 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)." ;;
474
+ 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" ;;
475
+ 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
476
  app.done) _M="Les workflows repowarden utilisent désormais l'App : ce qu'ils font déclenche les autres workflows." ;;
470
477
  app.next) _M="Sans l'App (variable et secret absents), ils reprennent le jeton des Actions." ;;
471
478
  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)." ;;
@@ -524,12 +531,27 @@ _msg_fr() {
524
531
  bots.ask_app) _M="GitHub App ?" ;;
525
532
  bots.ask_proteger) _M="Protection des branches ?" ;;
526
533
  bots.ask_npm) _M="Publication npm ?" ;;
534
+ bots.desc_delivery) _M="Livraison develop → main : automatique (la GitHub App merge la PR de livraison dès que les vérifications sont vertes ; la validation humaine est le merge de chaque ticket) ou approbation humaine (défaut)" ;;
535
+ bots.ask_delivery_auto) _M="Livraison automatique ? (non : approbation humaine)" ;;
536
+ bots.auto_no_app) _M="Livraison automatique sans GitHub App : le merge automatique exige l'App (repowarden app init) ; sans elle, la PR de livraison reste à merger à la main." ;;
537
+ bots.desc_rythme) _M="Rythme : à chaque ticket (préversion et demande de livraison à chaque merge) ou regroupé (défaut : préversion le soir, fenêtre de livraison le vendredi)" ;;
538
+ bots.ask_each_ticket) _M="À chaque ticket ? (non : regroupé)" ;;
539
+ bots.ask_preversion) _M="Préversion obligatoire avant une version stable ?" ;;
527
540
  bots.preview) _M="Aperçu" ;;
528
541
  bots.prev_develop) _M="branche %s créée sur GitHub depuis %s" ;;
529
542
  bots.prev_conf) _M=".repowarden.conf : integrationBranch = %s, branches protégées" ;;
530
543
  bots.prev_review) _M=".repowarden.conf : livraison validée par la revue de sa PR (sans GitHub App)" ;;
544
+ bots.prev_conf_auto) _M=".repowarden.conf : deliveryApproval = auto (livraison automatique)" ;;
545
+ bots.prev_delivery_auto) _M="release.yml : livraison automatique (delivery-auto), la GitHub App merge la PR de livraison dès que les vérifications sont vertes" ;;
546
+ bots.prev_delivery_approval) _M="release.yml : livraison approuvée par une personne" ;;
547
+ bots.prev_each_ticket) _M="release.yml : à chaque ticket, préversion et demande de livraison à chaque merge" ;;
548
+ bots.prev_grouped) _M="release.yml : regroupé, préversion le soir, fenêtre de livraison le vendredi" ;;
549
+ bots.prev_preversion_on) _M="release.yml : préversion obligatoire avant une version stable" ;;
550
+ bots.prev_preversion_off) _M="release.yml : version stable possible sans préversion (preversion-obligatoire: false)" ;;
531
551
  bots.prev_kept) _M="%s existe : gardé tel quel (écart avec le modèle : %s ligne(s))" ;;
532
552
  bots.prev_file) _M="%s : créé" ;;
553
+ bots.prev_ci_langs) _M=" outillage de la CI : %s (strict si formatage et tests sont outillés)" ;;
554
+ bots.prev_ci_generic) _M=" langage non reconnu : modèle générique, outillage du projet à ajouter (setup-*) puis strict: true" ;;
533
555
  bots.prev_tickets) _M="tickets : étiquettes et modèle de ticket sur GitHub, workflow tickets.yml" ;;
534
556
  bots.prev_app) _M="GitHub App : créée dans le navigateur, identifiants rangés en variable et secret du dépôt" ;;
535
557
  bots.prev_proteger) _M="réglages GitHub : rulesets des branches et des tags, modes de merge" ;;
@@ -577,6 +599,7 @@ _msg_fr() {
577
599
  tk.init_auto_create_on) _M="Création auto de ticket : ACTIVÉE (ticketAutoCreate = true)" ;;
578
600
  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
601
  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)." ;;
602
+ 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
603
  tk.prerelease) _M="Disponible en préversion **%s** : à tester avant la production." ;;
581
604
  tk.released) _M="Publié dans **%s**." ;;
582
605
  # Ticket comment
@@ -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. 72 caractères au plus, coupé sur
67
- # un mot entier. Vide si le titre ne donne rien (l'appelant décide du repli).
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
- sujet="$(printf '%s' "${sujet:0:1}" | LC_ALL=C tr '[:upper:]' '[:lower:]')${sujet:1}"
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="${sujet%?}"; fi
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repowarden",
3
- "version": "4.8.3",
3
+ "version": "4.9.0-next.10",
4
4
  "description": "Hooks git et CI multi-langages : messages de commit, nommage des branches, secrets, formatage, tests ciblés, code mort, releases.",
5
5
  "keywords": [
6
6
  "git-hooks",
@@ -16,7 +16,7 @@
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/simdevWarden/repowarden.git"
18
18
  },
19
- "license": "MIT",
19
+ "license": "GPL-3.0-or-later",
20
20
  "author": "SimBienvenueHoulBoumi (https://github.com/SimBienvenueHoulBoumi)",
21
21
  "bin": {
22
22
  "repowarden": "bin/repowarden",