repowarden 4.8.4-next.1 → 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 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 : tu cliques sur « Install ».
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
- cible="https://github.com/organizations/$owner/settings/apps/new"
78
+ apps="https://github.com/organizations/$owner/settings/apps"
79
79
  else
80
- cible="https://github.com/settings/apps/new"
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 (clé non disponible ici) : vérifiée au premier run du suivi des tickets
157
- info_t app.check_in_ci "https://github.com/apps/$slug/installations/new"
158
- echo
159
- ok_t app.done
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
@@ -252,6 +252,27 @@ YAML
252
252
  app-client-id: ${{ vars.REPOWARDEN_APP_CLIENT_ID }}
253
253
  secrets:
254
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 }}
255
276
  YAML
256
277
  fi
257
278
  fi
@@ -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) : jeton npm temporaire (7 jours, limité au scope du paquet)
13
- # créé et rangé directement en secret NPM_TOKEN, sans être affiché ;
14
- # variable REPOWARDEN_NPM=true ; la pipeline publie la dernière release
15
- # (lancée par la commande), ou la prochaine s'il n'y en a pas encore ;
16
- # 3. publication de confiance (npm trust github), puis jeton révoqué et
17
- # secret supprimé. Interrompue, la commande reprend où elle en était.
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 : nom unique à chaque création (npm refuse
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
- # Créer un jeton et configurer la publication de confiance exigent la 2FA du
123
- # compte (les codes envoyés par e-mail ne sont qu'une vérification de connexion)
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 par la pipeline avec un jeton temporaire
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.step_token
135
- if grep -q '^NPM_TOKEN' <<<"$(gh secret list --repo "$REPO")"; then
136
- ok_t npm.token_exists
137
- else
138
- # npm écrit ses questions et le jeton au même endroit : les questions
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
- gh variable set REPOWARDEN_NPM --repo "$REPO" --body true
172
-
173
- # Release déjà publiée : la pipeline est lancée tout de suite (elle publie
174
- # la dernière release absente de npm) ; sinon, la prochaine release publie
175
- if [ -n "$(git ls-remote --tags origin 'v[0-9]*')" ]; then
176
- branche="$(gh repo view "$REPO" --json defaultBranchRef -q .defaultBranchRef.name)"
177
- gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$branche"
178
- info_t npm.dispatched "$WORKFLOW"
179
- else
180
- info_t npm.waiting "$name"
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
- for i in $(seq 1 360); do
183
- publie && break
184
- [ $((i % 10)) -ne 0 ] || dim_t npm.still_waiting "$((i / 2))"
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
- verifier_trust
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,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
@@ -189,14 +191,21 @@ if [ -n "$integration" ]; then
189
191
  # l'approbation de l'environnement de livraison (« Approve and deploy »),
190
192
  # exigée par une règle de main (déploiement réussi) ; la revue de PR n'est
191
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.
192
199
  cfg_r deliveryApproval deployment
193
200
  DELIVERY="$REPLY"
194
- [[ "$DELIVERY" =~ ^(deployment|review)$ ]] || { err_t ci.proteger.delivery_invalid; exit 1; }
201
+ [[ "$DELIVERY" =~ ^(deployment|review|auto)$ ]] || { err_t ci.proteger.delivery_invalid; exit 1; }
195
202
  deploiement=""
196
203
  if [ "$DELIVERY" = deployment ]; then
197
204
  ENVIRONMENT="${ENVIRONMENT:-production}"
198
205
  livraison="$REVIEWS"
199
206
  deploiement="{\"type\": \"required_deployments\", \"parameters\": {\"required_deployment_environments\": [\"$ENVIRONMENT\"]}}"
207
+ elif [ "$DELIVERY" = auto ]; then
208
+ livraison="$REVIEWS"
200
209
  fi
201
210
  # PR à jour non exigée non plus : le merge commit d'une livraison n'existe
202
211
  # que sur main ; strict, la livraison suivante serait bloquée tant que
@@ -228,6 +237,11 @@ if [ -n "$integration" ]; then
228
237
  cfg_r autoMergeWork true
229
238
  auto_merge="$REPLY"
230
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
231
245
  # Sans ce workflow, les branches mergées ne seraient plus supprimées
232
246
  if ! grep -qs "nettoyage-branches.yml" "$GIT_TOPLEVEL"/.github/workflows/*.y*ml; then
233
247
  attention_t ci.proteger.no_cleanup
@@ -238,6 +252,11 @@ if [ -n "$integration" ]; then
238
252
  ! grep -qs "delivery-environment:" "$GIT_TOPLEVEL"/.github/workflows/*.y*ml; then
239
253
  attention_t ci.proteger.no_delivery_env "$ENVIRONMENT"
240
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
241
260
  # Demande de livraison (manuelle, planifiée) qui ne remplace pas celle en
242
261
  # attente : une demande oubliée bloquerait en silence les suivantes
243
262
  for wf in "$GIT_TOPLEVEL"/.github/workflows/*.y*ml; do
@@ -276,6 +295,8 @@ echo_t ci.proteger.3 "$CHECKS"
276
295
  echo_t ci.proteger.4 "$methods" "$delete_on_merge"
277
296
  if [ -n "$integration" ] && [ "$DELIVERY" = deployment ]; then
278
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"
279
300
  elif [ -n "$integration" ]; then
280
301
  echo_t ci.proteger.delivery "$livraison" "$auto_merge"
281
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,7 +284,10 @@ _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." ;;
@@ -393,19 +396,14 @@ _msg_en() {
393
396
  npm.old_npm) _M="npm %s is too old: 11.15 or later required (npm install -g npm@11)." ;;
394
397
  npm.step_login) _M="1/3 npm account" ;;
395
398
  npm.logged) _M="Logged in to npm: %s" ;;
396
- 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." ;;
397
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." ;;
398
- npm.step_token) _M="2/3 First publication, by the pipeline (temporary token)" ;;
399
- npm.token_exists) _M="NPM_TOKEN secret already present: reused." ;;
400
- npm.ask_password) _M="npm password (hidden input, sent to npm only): " ;;
401
- npm.ask_otp) _M="One-time code (authenticator app or npm email): " ;;
402
- npm.token_failed) _M="Could not create the npm token (see above): nothing was stored." ;;
403
- npm.token_stored) _M="Temporary token (7 days) created and stored as the NPM_TOKEN secret, never displayed." ;;
404
- npm.dispatched) _M="Pipeline started (%s): it publishes the latest release to npm. Waiting… (Ctrl+C: run the command again later, it resumes)" ;;
405
- 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)" ;;
406
- npm.still_waiting) _M="still waiting (%s min)…" ;;
407
- npm.not_yet) _M="Not published yet: run this command again after the next release." ;;
408
- 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." ;;
409
407
  npm.step_trust) _M="3/3 Tokenless publishing (trusted publishing)" ;;
410
408
  npm.trust_exists) _M="Trusted publishing already configured." ;;
411
409
  npm.trust_mismatch) _M="Existing trust relationship does not match expected parameters:" ;;
@@ -467,7 +465,10 @@ _msg_en() {
467
465
  app.step_install) _M="Installing on the repository" ;;
468
466
  app.installed) _M="App %s installed on %s." ;;
469
467
  app.not_installed) _M="App %s not installed on %s yet: run repowarden app init again once installed." ;;
470
- app.check_in_ci) _M="Installation checked by the first ticket tracking run (\"Jeton de l'App\" step); to install it: %s" ;;
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" ;;
471
472
  app.done) _M="repowarden workflows now use the App: what they do triggers the other workflows." ;;
472
473
  app.next) _M="Without the App (variable and secret missing), they fall back to the Actions token." ;;
473
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)." ;;
@@ -579,6 +580,7 @@ _msg_en() {
579
580
  tk.init_auto_create_on) _M="Auto ticket creation: ENABLED (ticketAutoCreate = true)" ;;
580
581
  tk.init_auto_create_off) _M="Auto ticket creation: DISABLED (default). To enable: git config repowarden.ticketAutoCreate true" ;;
581
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." ;;
582
584
  tk.prerelease) _M="Available in pre-release **%s**: to be tested before production." ;;
583
585
  tk.released) _M="Released in **%s**." ;;
584
586
  # Ticket comment
@@ -284,7 +284,10 @@ _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." ;;
@@ -393,19 +396,14 @@ _msg_fr() {
393
396
  npm.old_npm) _M="npm %s trop ancien : 11.15 au minimum (npm install -g npm@11)." ;;
394
397
  npm.step_login) _M="1/3 Compte npm" ;;
395
398
  npm.logged) _M="Connecté à npm : %s" ;;
396
- 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." ;;
397
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." ;;
398
- npm.step_token) _M="2/3 Première publication, par la pipeline (jeton temporaire)" ;;
399
- npm.token_exists) _M="Secret NPM_TOKEN déjà présent : réutilisé." ;;
400
- npm.ask_password) _M="Mot de passe npm (saisie masquée, transmis à npm seulement) : " ;;
401
- npm.ask_otp) _M="Code à usage unique (application d'authentification ou e-mail de npm) : " ;;
402
- npm.token_failed) _M="Création du jeton npm impossible (voir ci-dessus) : rien n'a été enregistré." ;;
403
- npm.token_stored) _M="Jeton temporaire (7 jours) créé et enregistré en secret NPM_TOKEN, sans affichage." ;;
404
- 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)" ;;
405
- 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)" ;;
406
- npm.still_waiting) _M="toujours en attente (%s min)…" ;;
407
- npm.not_yet) _M="Pas encore publié : relance cette commande après la prochaine release." ;;
408
- 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é." ;;
409
407
  npm.step_trust) _M="3/3 Publication sans jeton (publication de confiance)" ;;
410
408
  npm.trust_exists) _M="Publication de confiance déjà configurée." ;;
411
409
  npm.trust_mismatch) _M="Relation de confiance existante mais ne correspond pas aux paramètres attendus :" ;;
@@ -467,7 +465,10 @@ _msg_fr() {
467
465
  app.step_install) _M="Installation sur le dépôt" ;;
468
466
  app.installed) _M="App %s installée sur %s." ;;
469
467
  app.not_installed) _M="App %s pas encore installée sur %s : relance repowarden app init une fois installée." ;;
470
- 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" ;;
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" ;;
471
472
  app.done) _M="Les workflows repowarden utilisent désormais l'App : ce qu'ils font déclenche les autres workflows." ;;
472
473
  app.next) _M="Sans l'App (variable et secret absents), ils reprennent le jeton des Actions." ;;
473
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)." ;;
@@ -579,6 +580,7 @@ _msg_fr() {
579
580
  tk.init_auto_create_on) _M="Création auto de ticket : ACTIVÉE (ticketAutoCreate = true)" ;;
580
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" ;;
581
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é." ;;
582
584
  tk.prerelease) _M="Disponible en préversion **%s** : à tester avant la production." ;;
583
585
  tk.released) _M="Publié dans **%s**." ;;
584
586
  # 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.4-next.1",
3
+ "version": "4.9.0-next.6",
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",