repowarden 4.7.1 → 4.8.0-next.12

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.
@@ -24,11 +24,13 @@ source "$ROOT/hooks/lib/common.sh"
24
24
  REPO=""
25
25
  WORKFLOW="release.yml"
26
26
  ENVIRONMENT=""
27
+ DRY_RUN=false
27
28
  while [ $# -gt 0 ]; do
28
29
  case "$1" in
29
30
  --repo) shift; REPO="${1:?--repo attend propriétaire/nom}" ;;
30
31
  --workflow) shift; WORKFLOW="${1:?--workflow attend un fichier}" ;;
31
32
  --environnement | --environment) shift; ENVIRONMENT="${1:?--environnement attend un nom}" ;;
33
+ --dry-run) DRY_RUN=true ;;
32
34
  -h | --help) sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
33
35
  *) err_t install.unknown_arg "$1"; exit 1 ;;
34
36
  esac
@@ -50,6 +52,67 @@ name="$(node -p 'require("./package.json").name')"
50
52
  # les doublons, y compris celui d'une tentative interrompue) ; tous révoqués à la fin
51
53
  TOKEN_PREFIX="repowarden-ci-${REPO##*/}"
52
54
 
55
+ # Vérifie si une relation de confiance correspond aux paramètres attendus.
56
+ # Affiche l'écart si --dry-run ou pour confirmation.
57
+ # Retourne : 0 = match, 1 = pas de relation, 2 = relation différente
58
+ verifier_trust() {
59
+ local json attendu_repo="$REPO" attendu_file="$WORKFLOW" attendu_env="$ENVIRONMENT"
60
+ json="$(npm trust list "$name" --json 2>/dev/null || echo '[]')"
61
+ if [ "$json" = "[]" ] || [ -z "$json" ]; then
62
+ return 1
63
+ fi
64
+ local match=0 id repo file env allow_publish
65
+ while IFS=$'\t' read -r id repo file env allow_publish; do
66
+ [ -n "$id" ] || continue
67
+ if [ "$repo" = "$attendu_repo" ] && [ "$file" = "$attendu_file" ] && [ "$env" = "$attendu_env" ] && [ "$allow_publish" = "true" ]; then
68
+ match=1
69
+ break
70
+ fi
71
+ done < <(node -e "
72
+ const data = $json;
73
+ for (const t of data) {
74
+ const id = t.id || t.key;
75
+ const repo = t.repository || t.repo || '';
76
+ const file = t.file || t.workflow || '';
77
+ const env = t.environment || t.env || '';
78
+ const allowPublish = t.allowPublish === true || t['allow-publish'] === true;
79
+ console.log(id + '\t' + repo + '\t' + file + '\t' + env + '\t' + allowPublish);
80
+ }
81
+ ")
82
+ if [ "$match" = 1 ]; then
83
+ return 0
84
+ fi
85
+ return 2
86
+ }
87
+
88
+ # Affiche les détails d'une relation de confiance pour diagnostic
89
+ afficher_trust() {
90
+ local json
91
+ json="$(npm trust list "$name" --json 2>/dev/null || echo '[]')"
92
+ if [ "$json" = "[]" ] || [ -z "$json" ]; then
93
+ echo " (aucune relation de confiance)"
94
+ return
95
+ fi
96
+ node -e "
97
+ const data = $json;
98
+ for (const t of data) {
99
+ const id = t.id || t.key;
100
+ const repo = t.repository || t.repo || '(inconnu)';
101
+ const file = t.file || t.workflow || '(inconnu)';
102
+ const env = t.environment || t.env || '(aucun)';
103
+ const allowPublish = t.allowPublish === true || t['allow-publish'] === true;
104
+ const allowStagePublish = t.allowStagePublish === true || t['allow-stage-publish'] === true;
105
+ console.log(' ID: ' + id);
106
+ console.log(' Dépôt: ' + repo);
107
+ console.log(' Workflow: ' + file);
108
+ console.log(' Environnement: ' + env);
109
+ console.log(' npm publish: ' + (allowPublish ? 'oui' : 'non'));
110
+ console.log(' npm stage publish: ' + (allowStagePublish ? 'oui' : 'non'));
111
+ console.log('');
112
+ }
113
+ "
114
+ }
115
+
53
116
  brand "npm · $name · $REPO"
54
117
 
55
118
  # 1. Connexion npm : entre toi et npm (navigateur ou mot de passe)
@@ -127,16 +190,82 @@ fi
127
190
 
128
191
  # 3. Publication de confiance, puis plus aucun jeton
129
192
  step_t npm.step_trust
130
- if grep -q '"id"' <<<"$(npm trust list "$name" --json 2>/dev/null || true)"; then
193
+ verifier_trust
194
+ cas=$?
195
+ if [ "$cas" = 0 ]; then
131
196
  ok_t npm.trust_exists
197
+ elif [ "$cas" = 1 ]; then
198
+ # Aucune relation : créer
199
+ if [ "$DRY_RUN" = true ]; then
200
+ info_t npm.trust_would_create "$REPO" "$WORKFLOW" "${ENVIRONMENT:-(aucun)}"
201
+ else
202
+ npm trust github "$name" --file "$WORKFLOW" --repo "$REPO" ${ENVIRONMENT:+--env "$ENVIRONMENT"} --allow-publish --yes
203
+ ok_t npm.trust_set "$REPO" "$WORKFLOW"
204
+ fi
132
205
  else
133
- npm trust github "$name" --file "$WORKFLOW" --repo "$REPO" ${ENVIRONMENT:+--env "$ENVIRONMENT"} --allow-publish --yes
134
- ok_t npm.trust_set "$REPO" "$WORKFLOW"
206
+ # Relation différente : afficher l'écart, demander confirmation, corriger sans perte
207
+ attention_t npm.trust_mismatch
208
+ afficher_trust
209
+ echo " Attendu :"
210
+ echo " Dépôt: $REPO"
211
+ echo " Workflow: $WORKFLOW"
212
+ echo " Environnement: ${ENVIRONMENT:-(aucun)}"
213
+ echo " npm publish: oui"
214
+ echo ""
215
+ if [ "$DRY_RUN" = true ]; then
216
+ info_t npm.trust_would_fix
217
+ else
218
+ # Confirmation avant changement
219
+ if [ -t 0 ]; then
220
+ t npm.trust_confirm
221
+ read -r -p " $REPLY" rep
222
+ echo
223
+ [[ "$rep" =~ ^[OoYy]$ ]] || { err_t npm.trust_cancelled; exit 1; }
224
+ fi
225
+ # Créer la nouvelle relation AVANT de révoquer l'ancienne (pas de perte)
226
+ npm trust github "$name" --file "$WORKFLOW" --repo "$REPO" ${ENVIRONMENT:+--env "$ENVIRONMENT"} --allow-publish --yes
227
+ ok_t npm.trust_set "$REPO" "$WORKFLOW"
228
+ # Révoquer l'ancienne relation
229
+ json="$(npm trust list "$name" --json 2>/dev/null || echo '[]')"
230
+ old_id="$(node -e "
231
+ const data = $json;
232
+ for (const t of data) {
233
+ const id = t.id || t.key;
234
+ const repo = t.repository || t.repo || '';
235
+ const file = t.file || t.workflow || '';
236
+ const env = t.environment || t.env || '';
237
+ const allowPublish = t.allowPublish === true || t['allow-publish'] === true;
238
+ if (repo !== '$REPO' || file !== '$WORKFLOW' || env !== '$ENVIRONMENT' || allowPublish !== true) {
239
+ console.log(id);
240
+ }
241
+ }
242
+ ")"
243
+ for id in $old_id; do
244
+ [ -n "$id" ] && npm trust revoke "$name" --id "$id" --yes && ok_t npm.trust_revoked "$id"
245
+ done
246
+ fi
135
247
  fi
136
248
  gh variable set REPOWARDEN_NPM --repo "$REPO" --body true
137
249
  if grep -q '^NPM_TOKEN' <<<"$(gh secret list --repo "$REPO")"; then
138
- gh secret delete NPM_TOKEN --repo "$REPO"
139
- ok_t npm.token_removed
250
+ if [ "$cas" = 0 ] || [ "$cas" = 2 ]; then
251
+ # Relation de confiance OK (ou corrigée) : signaler le token et proposer suppression
252
+ attention_t npm.token_present
253
+ if [ "$DRY_RUN" = true ]; then
254
+ info_t npm.token_would_remove
255
+ elif [ -t 0 ]; then
256
+ t npm.token_remove_confirm
257
+ read -r -p " $REPLY" rep
258
+ echo
259
+ [[ "$rep" =~ ^[OoYy]$ ]] && gh secret delete NPM_TOKEN --repo "$REPO" && ok_t npm.token_removed
260
+ else
261
+ gh secret delete NPM_TOKEN --repo "$REPO"
262
+ ok_t npm.token_removed
263
+ fi
264
+ else
265
+ # Pas de relation de confiance : supprimer silencieusement (comportement existant)
266
+ gh secret delete NPM_TOKEN --repo "$REPO"
267
+ ok_t npm.token_removed
268
+ fi
140
269
  fi
141
270
  # Jetons temporaires révoqués sur npm (retrouvés par leur préfixe). npm peut
142
271
  # demander un code 2FA : la commande garde le terminal (questions visibles)
package/bin/proteger CHANGED
@@ -193,7 +193,15 @@ if [ -n "$integration" ]; then
193
193
  # develop ne l'a pas reçu (retour main → develop vide après chaque
194
194
  # release). Une livraison n'apporte que develop, vérifiée par la CI ; le
195
195
  # retour reste fait quand main a un contenu propre (correctif direct).
196
+ # Garde-fou de livraison (statut posé par release-auto) : la livraison ne
197
+ # se merge que si elle peut aboutir (préversion publiée, protection en place)
198
+ contexts_branches="$contexts"
199
+ if grep -qs "release-auto.yml" "$GIT_TOPLEVEL"/.github/workflows/*.y*ml; then
200
+ contexts="${contexts:+$contexts,} {\"context\": \"livraison\", \"integration_id\": 15368}"
201
+ livraison_exigee=1
202
+ fi
196
203
  [ -z "$others" ] || rulesets+=("$(branch_ruleset repowarden "$others" '"merge"' false "$livraison" "$last_push" "$deploiement")")
204
+ contexts="$contexts_branches"
197
205
  # Branche d'intégration : squash seul (un commit par PR de travail, titre =
198
206
  # message : les commits intermédiaires n'entrent jamais) ; plus de retour
199
207
  # main → develop à merger, seuls develop et ses instantanés livraison/*
@@ -254,6 +262,7 @@ fi
254
262
  echo_t ci.proteger.1 "$REPO"
255
263
  echo_t ci.proteger.2 "$existing"
256
264
  echo_t ci.proteger.3 "$CHECKS"
265
+ [ -z "${livraison_exigee:-}" ] || echo_t ci.proteger.livraison_check
257
266
  echo_t ci.proteger.4 "$methods" "$delete_on_merge"
258
267
  if [ -n "$integration" ] && [ "$DELIVERY" = deployment ]; then
259
268
  echo_t ci.proteger.delivery_deploy "$ENVIRONMENT" "$auto_merge"
@@ -308,10 +317,76 @@ sig_attendue() {
308
317
  "$(sed -n 's/.*"require_last_push_approval": \([a-z]*\).*/\1/p' <<<"$r")" \
309
318
  "$(sed -n 's/.*"require_code_owner_review": \([a-z]*\).*/\1/p' <<<"$r")" "$deps" "$checks"
310
319
  }
320
+ # Anciens rulesets : rulesets de branche du dépôt, sous un autre nom que ceux
321
+ # de repowarden, qui exigent encore la vérification « repogarde » (retirée en
322
+ # v4) ; posés à la main ou par une ancienne version. Laissés à côté, ils
323
+ # bloqueraient les branches pour toujours. Une ligne par ruleset dans ANCIENS :
324
+ # id, nom, branches couvertes (~DEFAULT_BRANCH résolu : la branche par défaut
325
+ # change ensuite dans le flux develop), séparés par des tabulations.
326
+ # shellcheck disable=SC2016 # expressions jq (gh --jq), pas du shell
327
+ anciens_rulesets() {
328
+ local ids id ligne defaut=""
329
+ ANCIENS=""
330
+ ids="$(gh api "repos/$REPO/rulesets" --jq '.[] | select(.target == "branch"
331
+ and (.source_type // "Repository") == "Repository"
332
+ and (.name | test("^(repowarden|repogarde)( \\(.*\\))?$") | not)) | .id')" || return 0
333
+ for id in $ids; do
334
+ ligne="$(gh api "repos/$REPO/rulesets/$id" --jq 'select([.rules[]? | select(.type == "required_status_checks")
335
+ | .parameters.required_status_checks[].context] | index("repogarde"))
336
+ | "\(.id)\t\(.name)\t\(.conditions.ref_name.include | join(" "))"')" || continue
337
+ [ -n "$ligne" ] || continue
338
+ if [[ "$ligne" == *"~DEFAULT_BRANCH"* ]]; then
339
+ [ -n "$defaut" ] || defaut="$(gh api "repos/$REPO" --jq .default_branch)"
340
+ ligne="${ligne//\~DEFAULT_BRANCH/refs/heads/$defaut}"
341
+ fi
342
+ ANCIENS="$ANCIENS$ligne"$'\n'
343
+ done
344
+ }
345
+
346
+ # Ancien ruleset (ligne de ANCIENS) couvrant une des branches du ruleset $2 (JSON)
347
+ couvre() {
348
+ local motifs m b
349
+ read -ra motifs <<<"$(cut -f3 <<<"$1")"
350
+ for m in ${motifs[@]+"${motifs[@]}"}; do
351
+ [ "$m" != "~ALL" ] || return 0
352
+ for b in $(grep -o '"refs/heads/[^"]*"' <<<"$2" | tr -d '"'); do
353
+ # shellcheck disable=SC2053 # motif de GitHub (refs/heads/release/*)
354
+ [[ "$b" != $m ]] || return 0
355
+ done
356
+ done
357
+ return 1
358
+ }
359
+
360
+ # Réponse d'erreur de GitHub (JSON) → « message : erreurs », lisible
361
+ erreur_github() {
362
+ local message="" erreurs="" re_message='"message": *"([^"]*)"' re_erreurs='"errors": *\[([^]]*)\]'
363
+ [[ ! "$1" =~ $re_message ]] || message="${BASH_REMATCH[1]}"
364
+ [[ ! "$1" =~ $re_erreurs ]] || erreurs="${BASH_REMATCH[1]//\"/}"
365
+ if [ -n "$message" ]; then
366
+ REPLY="$message${erreurs:+ : $erreurs}"
367
+ else
368
+ REPLY="$(tr '\n' ' ' <<<"$1")"
369
+ fi
370
+ }
371
+
372
+ # Rulesets indisponibles (dépôt privé d'un compte gratuit : GitHub les refuse
373
+ # en 403) : les réglages du dépôt restent appliqués, les rulesets sautés avec
374
+ # un message clair au lieu d'un arrêt sans explication
375
+ RULESETS_OK=1
376
+ if ! reponse="$(gh api "repos/$REPO/rulesets" 2>&1)" && [[ "$reponse" == *"403"* ]]; then
377
+ RULESETS_OK=""
378
+ fi
379
+ ANCIENS=""
380
+ [ -z "$RULESETS_OK" ] || anciens_rulesets
381
+
311
382
  # shellcheck disable=SC2016 # expression jq (gh --jq), pas du shell
312
383
  SIG_EN_LIGNE='(.rules | map(select(.type == "pull_request"))[0].parameters) as $p
313
384
  | "approbations=\($p.required_approving_review_count) dernier_push=\($p.require_last_push_approval) codeowners=\($p.require_code_owner_review) deploiement=\([.rules[] | select(.type == "required_deployments") | .parameters.required_deployment_environments[]] | join(",")) verifications=\([.rules[] | select(.type == "required_status_checks") | .parameters.required_status_checks[].context] | sort | join(","))"'
314
385
 
386
+ if [ -n "$VERIFY" ] && [ -z "$RULESETS_OK" ]; then
387
+ err_t ci.proteger.rulesets_unavailable "$REPO"
388
+ exit 1
389
+ fi
315
390
  if [ -n "$VERIFY" ]; then
316
391
  ecarts=0
317
392
  for ruleset in "${rulesets[@]}"; do
@@ -335,6 +410,11 @@ if [ -n "$VERIFY" ]; then
335
410
  ecarts=$((ecarts + 1))
336
411
  fi
337
412
  done
413
+ while IFS=$'\t' read -r id name _; do
414
+ [ -n "$id" ] || continue
415
+ err_t ci.proteger.verify_legacy "$name" "$id"
416
+ ecarts=$((ecarts + 1))
417
+ done <<<"$ANCIENS"
338
418
  [ "$ecarts" = 0 ] || { err_t ci.proteger.verify_fix; exit 1; }
339
419
  ok_t ci.proteger.verify_ok
340
420
  exit 0
@@ -348,6 +428,7 @@ if [ -n "$integration" ] && ! gh api "repos/$REPO/labels/urgent" --silent 2>/dev
348
428
  fi
349
429
 
350
430
  if [ -n "$DRY_RUN" ]; then
431
+ [ -n "$RULESETS_OK" ] || attention_t ci.proteger.rulesets_unavailable "$REPO"
351
432
  printf '%s\n' "${rulesets[@]}"
352
433
  [ -z "$env_json" ] || printf '%s\n' "$env_json"
353
434
  exit 0
@@ -361,21 +442,60 @@ gh api -X PATCH "repos/$REPO" --silent "${settings[@]}"
361
442
  gh api -X PUT "repos/$REPO/actions/permissions/workflow" --silent \
362
443
  -f default_workflow_permissions=read -F can_approve_pull_request_reviews=true
363
444
 
364
- for ruleset in "${rulesets[@]}"; do
445
+ echecs=0
446
+ [ -n "$RULESETS_OK" ] || { attention_t ci.proteger.rulesets_unavailable "$REPO"; rulesets=(); }
447
+ for ruleset in ${rulesets[@]+"${rulesets[@]}"}; do
365
448
  name="$(sed -n 's/^ "name": "\(.*\)",$/\1/p' <<<"$ruleset")"
366
449
  # Ruleset posé sous l'ancien nom (repogarde, v3) : repris et renommé, sinon
367
450
  # il resterait à côté et exigerait une vérification « repogarde » disparue
368
451
  old="${name/repowarden/repogarde}"
369
452
  id="$(gh api "repos/$REPO/rulesets" \
370
453
  --jq "([.[] | select(.name == \"$name\")] + [.[] | select(.name == \"$old\")]) | .[0].id // empty")"
454
+ # Sinon, un ancien ruleset sous un autre nom couvrant ses branches : repris
455
+ # (mis à jour en place, jamais un instant sans protection)
456
+ repris=""
457
+ if [ -z "$id" ] && grep -q '"target": "branch"' <<<"$ruleset"; then
458
+ while IFS= read -r ligne; do
459
+ { [ -n "$ligne" ] && couvre "$ligne" "$ruleset"; } || continue
460
+ id="${ligne%%$'\t'*}"
461
+ repris="$(cut -f2 <<<"$ligne")"
462
+ ANCIENS="$(grep -v "^$id"$'\t' <<<"$ANCIENS" || true)"
463
+ break
464
+ done <<<"$ANCIENS"
465
+ fi
371
466
  if [ -n "$id" ]; then
372
- gh api -X PUT "repos/$REPO/rulesets/$id" --input - <<<"$ruleset" >/dev/null
373
- ok_t ci.proteger.10 "$name"
467
+ methode=PUT chemin="repos/$REPO/rulesets/$id"
468
+ else
469
+ methode=POST chemin="repos/$REPO/rulesets"
470
+ fi
471
+ # Réponse gardée : en cas de refus, le détail de GitHub est affiché
472
+ if reponse="$(gh api -X "$methode" "$chemin" --input - <<<"$ruleset" 2>&1)"; then
473
+ if [ -n "$repris" ]; then
474
+ ok_t ci.proteger.legacy_taken "$name" "$repris"
475
+ elif [ -n "$id" ]; then
476
+ ok_t ci.proteger.10 "$name"
477
+ else
478
+ ok_t ci.proteger.11 "$name"
479
+ fi
480
+ elif [ "$name" = "repowarden (tags)" ] && [ "$owner_type" = Organization ] &&
481
+ [[ "$reponse" == *"ruleset source or owner organization"* ]] &&
482
+ gh api -X "$methode" "$chemin" --input - <<<"$(tags_ruleset User)" >/dev/null 2>&1; then
483
+ # Exception GitHub Actions refusée (App non installée dans l'organisation) :
484
+ # règle des comptes personnels, tags ni déplacés ni supprimés
485
+ attention_t ci.proteger.tags_fallback
374
486
  else
375
- gh api -X POST "repos/$REPO/rulesets" --input - <<<"$ruleset" >/dev/null
376
- ok_t ci.proteger.11 "$name"
487
+ erreur_github "$reponse"
488
+ err_t ci.proteger.api_error "$name" "$REPLY"
489
+ echecs=$((echecs + 1))
377
490
  fi
378
491
  done
492
+ # Anciens rulesets non repris (plusieurs sur les mêmes branches) : jamais
493
+ # supprimés ici, la marche à suivre est donnée
494
+ while IFS=$'\t' read -r id name _; do
495
+ [ -n "$id" ] || continue
496
+ err_t ci.proteger.legacy_left "$name" "$id" "$REPO" "$id"
497
+ echecs=$((echecs + 1))
498
+ done <<<"$ANCIENS"
379
499
  if [ -n "$env_json" ]; then
380
500
  gh api -X PUT "repos/$REPO/environments/$ENVIRONMENT" --input - --silent <<<"$env_json"
381
501
  policies="$(gh api "repos/$REPO/environments/$ENVIRONMENT/deployment-branch-policies" \
@@ -389,4 +509,5 @@ if [ -n "$env_json" ]; then
389
509
  fi
390
510
  [ -z "$urgent" ] || gh label create urgent -R "$REPO" --color B60205 \
391
511
  --description "Livraison immédiate après merge" >/dev/null
512
+ [ "$echecs" = 0 ] || { err_t ci.proteger.incomplete; exit 1; }
392
513
  ok_t ci.proteger.13
package/bin/repowarden CHANGED
@@ -3,6 +3,7 @@
3
3
  # repowarden état du poste et étape suivante
4
4
  # repowarden statut (idem)
5
5
  # repowarden install [--global] [--lang fr|en] active les hooks (install.sh)
6
+ # repowarden bots bots du dépôt GitHub (CI, releases, tickets…)
6
7
  # repowarden uninstall [--global] [--purge] les retire (avant npm uninstall -g)
7
8
  # repowarden cc assistant de commit
8
9
  # repowarden code-mort nouveau code mort de la branche
@@ -16,8 +17,10 @@
16
17
  # repowarden run list [--workflow <nom>] liste les runs récents
17
18
  # repowarden tickets init suivi des tickets (GitHub)
18
19
  # repowarden proteger [options] protection des branches (GitHub)
20
+ # repowarden migrer [--pr] [--hooks] passage de repogarde à repowarden
19
21
  # repowarden npm-publication publication npm automatique
20
22
  # repowarden app init GitHub App des workflows (une fois)
23
+ # repowarden bot [init|rapport] bot d'organisation (dépôt de pilotage)
21
24
  # repowarden --version
22
25
  set -euo pipefail
23
26
 
@@ -35,7 +38,7 @@ source "$ROOT/hooks/lib/ui.sh"
35
38
  # shellcheck source=../hooks/lib/i18n.sh
36
39
  source "$ROOT/hooks/lib/i18n.sh"
37
40
 
38
- aide() { sed -n '2,21p' "$ROOT/bin/repowarden" | sed 's/^# \{0,1\}//'; }
41
+ aide() { sed -n '2,24p' "$ROOT/bin/repowarden" | sed 's/^# \{0,1\}//'; }
39
42
 
40
43
  commande="${1:-}"
41
44
  [ $# -eq 0 ] || shift
@@ -61,8 +64,11 @@ case "$commande" in
61
64
  run-list) exec bash "$ROOT/bin/run-list" "$@" ;;
62
65
  pr-checks) exec bash "$ROOT/bin/pr-checks" "$@" ;;
63
66
  proteger | protect) exec bash "$ROOT/bin/proteger" "$@" ;;
67
+ migrer | migrate) exec bash "$ROOT/bin/migrer" "$@" ;;
64
68
  npm-publication | npm-publish) exec bash "$ROOT/bin/npm-publication" "$@" ;;
65
69
  app) exec bash "$ROOT/bin/app" "$@" ;;
70
+ bots) exec bash "$ROOT/bin/bots" "$@" ;;
71
+ bot) exec bash "$ROOT/bin/bot" "$@" ;;
66
72
  -v | --version | version)
67
73
  repowarden_version_r "$ROOT"
68
74
  echo "$REPLY"
package/bin/statut CHANGED
@@ -70,6 +70,18 @@ if [ -n "$GIT_TOPLEVEL" ]; then
70
70
  attention_t st.repo_lefthook "$f"
71
71
  break
72
72
  done
73
+ # Anciens noms (configuration, workflows de l'ancien dépôt, que GitHub ne
74
+ # redirige plus) : la migration les remplace
75
+ if [ -e "$GIT_TOPLEVEL/.repogarde.conf" ] || [ -d "$GIT_TOPLEVEL/.repogarde" ] ||
76
+ grep -qs repogarde "$GIT_TOPLEVEL"/.github/workflows/*.y*ml; then
77
+ attention_t st.repo_migrate
78
+ suivant="${suivant:-repowarden migrer}"
79
+ fi
80
+ # Branche develop présente, flux non déclaré : traité comme « main seule »
81
+ cfg_r integrationBranch ""
82
+ if [ -z "$REPLY" ] && git rev-parse -q --verify refs/remotes/origin/develop >/dev/null; then
83
+ attention_t st.repo_develop_undeclared
84
+ fi
73
85
  cfg_r version ""
74
86
  if [ -n "$REPLY" ]; then
75
87
  attendue="${REPLY#v}"
package/bin/tickets CHANGED
@@ -141,13 +141,11 @@ GH_REPO="$REPO" AUTO_VALIDATE="${AUTO_VALIDATE:-maintainers}" "$ROOT/ci/tickets.
141
141
 
142
142
  # Afficher l'état de ticketAutoCreate
143
143
  echo
144
- if git config --get repowarden.ticketAutoCreate | grep -qE '^(true|oui|yes)$'; then
145
- t tk.init_auto_create_on
146
- hint "$_T"
147
- else
148
- t tk.init_auto_create_off
149
- hint "$_T"
150
- fi
144
+ cfg_r ticketAutoCreate false
145
+ case "$REPLY" in
146
+ true | oui | yes) dim_t tk.init_auto_create_on ;;
147
+ *) dim_t tk.init_auto_create_off ;;
148
+ esac
151
149
 
152
150
  echo
153
151
  ok_t tk.init_done
@@ -100,6 +100,7 @@ _msg_en() {
100
100
  install.replaced) _M="core.hooksPath (%s) was '%s', replaced." ;;
101
101
  install.enabled) _M="Hooks enabled (%s) → %s" ;;
102
102
  install.local_wins) _M="A local core.hooksPath (e.g. husky) still takes precedence in that repository." ;;
103
+ install.bots_never) _M="Repository bots will no longer be offered (repowarden.bots = off); on demand: repowarden bots" ;;
103
104
  install.lang_set) _M="Message language: English (change: %s --global --lang fr)" ;;
104
105
  install.lang_invalid) _M="--lang: fr or en." ;;
105
106
  install.keys_migrated) _M="repogarde.* settings (%s) renamed to repowarden.*" ;;
@@ -232,6 +233,7 @@ _msg_en() {
232
233
  ci.check.16) _M="Target \"%s\" matches the flow." ;;
233
234
  ci.check.17) _M="PR from \"%s\" to \"%s\": expected target %s (integrationBranch setting)." ;;
234
235
  ci.check.18) _M=" Fix: change the PR base branch (Edit, next to the title), or gh pr edit --base %s" ;;
236
+ ci.check.develop_undeclared) _M="PR %s → %s: the develop branch exists but .repowarden.conf does not declare the develop → main flow. Add integrationBranch = develop (work then goes through develop), or delete develop if the project does not want it." ;;
235
237
  ci.check.19) _M="gitleaks not installed: run ci/install-tool.sh gitleaks before this script." ;;
236
238
  ci.check.20) _M="Working tree modified before the check: formatting cannot be verified." ;;
237
239
  ci.check.21) _M="A formatter failed (syntax error, or tool / dependency unavailable): see the log above." ;;
@@ -296,16 +298,57 @@ _msg_en() {
296
298
  ci.proteger.verify_ok) _M="Online rules up to date." ;;
297
299
  ci.proteger.delivery) _M=" delivery into main: %s approval(s) required (moving to a new version); auto-merge of work PRs into develop: %s" ;;
298
300
  ci.proteger.methods_flow) _M="main: merge commit; %s: squash or merge commit" ;;
301
+ ci.proteger.livraison_check) _M=" \"livraison\" check required on the main branch (prerelease published, protection in place)" ;;
299
302
  ci.proteger.tags_org) _M="reserved for workflows" ;;
300
303
  ci.proteger.tags_user) _M="neither moved nor deleted (personal account: creation allowed)" ;;
301
304
  ci.proteger.tags_none) _M="not protected (--sans-tags)" ;;
302
305
  ci.proteger.unknown_team) _M="Unknown or inaccessible team: %s (format @organisation/team)" ;;
303
306
  ci.proteger.unknown_user) _M="Unknown user: %s" ;;
304
- ci.version.usage) _M="Usage: ci/version.sh next|notes [ref]" ;;
307
+ mig.no_major) _M="repowarden major version not found (%s): REPOWARDEN_MAJEURE=4 repowarden migrer." ;;
308
+ mig.dirty) _M="Uncommitted changes: commit or stash before repowarden migrer --pr." ;;
309
+ mig.pr_open) _M="Migration PR already open (%s): nothing to do." ;;
310
+ mig.both) _M="%s and %s both exist: keep the second, merge then delete the first by hand." ;;
311
+ mig.conf) _M=".repogarde.conf → .repowarden.conf ([repowarden] section, version updated)" ;;
312
+ mig.dir) _M=".repogarde/ → .repowarden/" ;;
313
+ mig.workflow) _M="%s: uses: to simdevWarden/repowarden, old names replaced" ;;
314
+ mig.renamed) _M="%s → %s" ;;
315
+ mig.tickets_events) _M="%s: ticket tracking events completed (push, review, assignment, closing): prefilled draft PR on the first push" ;;
316
+ mig.tickets_labels) _M="Tracking labels (type: feat, statuses…): repowarden tickets init (can be rerun, rewrites no file)" ;;
317
+ mig.hooks_moved) _M="Leftover lefthook hooks (%s) moved to %s" ;;
318
+ mig.hooks_left) _M="Leftover lefthook hooks in .git/hooks (%s), ignored: repowarden migrer --hooks to set them aside." ;;
319
+ mig.lefthook_conf) _M="%s: lefthook is no longer supported; move its commands to .repowarden/<hook>, then delete this file." ;;
320
+ mig.secrets) _M="Secrets or variables renamed in the workflows, to recreate under the new name (Settings → Secrets and variables):%s" ;;
321
+ mig.remaining) _M="Still mention repogarde (to review, not changed): %s" ;;
322
+ mig.nothing) _M="Nothing to migrate: the project already uses the repowarden names." ;;
323
+ mig.next) _M="Next: check the diff, then repowarden proteger (the required check is now called repowarden)." ;;
324
+ mig.commit_subject) _M="move from repogarde to repowarden v%s" ;;
325
+ mig.commit_body) _M="Old names and paths replaced by repowarden migrer (docs/migration-v4.md): GitHub no longer redirects the old repository's reusable workflows." ;;
326
+ mig.pr_body) _M="Migration from repogarde to repowarden, made by repowarden migrer (docs/migration-v4.md)." ;;
327
+ mig.pr_after) _M="After merging: run repowarden proteger (the required check is now called repowarden) and recreate any REPOGARDE_* secrets under their new name." ;;
328
+ mig.pr_created) _M="Migration PR opened (%s)." ;;
329
+ st.repo_migrate) _M="Old repogarde names in this project: repowarden migrer" ;;
330
+ st.repo_develop_undeclared) _M="The develop branch exists but the develop → main flow is not declared: add integrationBranch = develop to .repowarden.conf (otherwise work can go straight into main)." ;;
331
+ ci.proteger.legacy_taken) _M="Ruleset \"%s\": old ruleset \"%s\" taken over (repogarde check removed)." ;;
332
+ ci.proteger.legacy_left) _M="Old ruleset \"%s\" (id %s): still requires the repogarde check, removed in v4, and blocks its branches. Delete it: Settings → Rules → Rulesets, or gh api -X DELETE repos/%s/rulesets/%s." ;;
333
+ ci.proteger.verify_legacy) _M="%s (id %s): old ruleset, still requires the repogarde check (removed in v4)" ;;
334
+ ci.proteger.api_error) _M="Ruleset \"%s\" rejected by GitHub: %s" ;;
335
+ ci.proteger.incomplete) _M="Incomplete protection: see the errors above." ;;
336
+ ci.proteger.rulesets_unavailable) _M="Rulesets unavailable for %s: GitHub refuses them on a private repository of a free account. Make the repository public, or move to GitHub Pro / Team; the other settings are applied." ;;
337
+ ci.proteger.tags_fallback) _M="Tags ruleset: \"GitHub Actions\" bypass refused in this organization; tags protected as on a personal account (not moved or deleted, creation allowed)." ;;
338
+ ci.version.usage) _M="Usage: ci/version.sh next|notes|notes-livraison [ref]" ;;
305
339
  ci.version.breaking) _M="Breaking changes" ;;
306
340
  ci.version.features) _M="Features" ;;
307
341
  ci.version.fixes) _M="Bug fixes" ;;
308
342
  ci.version.maintenance) _M="Maintenance" ;;
343
+ ci.version.summary) _M="In short" ;;
344
+ ci.version.todo) _M="To do" ;;
345
+ ci.version.update) _M="Update" ;;
346
+ ci.version.tickets) _M="Delivered tickets" ;;
347
+ ci.livraison.protection) _M="Protection of the %s flow missing (merge commit, no deletion): run repowarden proteger" ;;
348
+ ci.livraison.maintenance) _M="Maintenance only: no version to publish" ;;
349
+ ci.livraison.non_exigee) _M="%s: prerelease not required" ;;
350
+ ci.livraison.ok) _M="%s: prerelease tested, deliverable" ;;
351
+ ci.livraison.preversion) _M="%s: no prerelease %s; wait for the evening one, or Actions → release → Run workflow on %s" ;;
309
352
  ci.install_tool.os) _M="Unsupported system: %s" ;;
310
353
  ci.install_tool.arch) _M="Unsupported architecture: %s" ;;
311
354
  ci.install_tool.unknown) _M="Unknown tool: %s" ;;
@@ -360,7 +403,16 @@ _msg_en() {
360
403
  npm.first_published) _M="%s@%s published by the pipeline." ;;
361
404
  npm.step_trust) _M="3/3 Tokenless publishing (trusted publishing)" ;;
362
405
  npm.trust_exists) _M="Trusted publishing already configured." ;;
406
+ npm.trust_mismatch) _M="Existing trust relationship does not match expected parameters:" ;;
407
+ npm.trust_confirm) _M="Confirm fix (new relationship created before revoking old)? [y/N] " ;;
408
+ npm.trust_cancelled) _M="Fix cancelled." ;;
409
+ npm.trust_would_create) _M="[dry-run] Trust relationship would be created: %s, workflow %s, env %s" ;;
410
+ npm.trust_would_fix) _M="[dry-run] Trust relationship would be fixed (new created, old revoked)" ;;
411
+ npm.trust_revoked) _M="Old trust relationship revoked: %s" ;;
363
412
  npm.trust_set) _M="Trusted publishing: %s, workflow %s." ;;
413
+ npm.token_present) _M="NPM_TOKEN secret still present while trusted publishing works." ;;
414
+ npm.token_remove_confirm) _M="Remove NPM_TOKEN secret? [y/N] " ;;
415
+ npm.token_would_remove) _M="[dry-run] NPM_TOKEN secret would be removed" ;;
364
416
  npm.token_removed) _M="NPM_TOKEN secret removed from the repository." ;;
365
417
  npm.token_revoked) _M="Temporary token revoked on npm." ;;
366
418
  npm.done) _M="Done: every release publishes %s to npm, without a token." ;;
@@ -406,13 +458,93 @@ _msg_en() {
406
458
  app.server_failed) _M="Local page did not start." ;;
407
459
  app.open) _M="Open %s then click GitHub's green button." ;;
408
460
  app.no_code) _M="No answer from GitHub (creation cancelled, or 15 min timeout)." ;;
409
- app.created) _M="App %s created; credentials stored (variable REPOWARDEN_APP_CLIENT_ID, secret REPOWARDEN_APP_KEY)." ;;
461
+ app.created) _M="App %s created; credentials stored (variable %s, secret %s)." ;;
410
462
  app.step_install) _M="Installing on the repository" ;;
411
463
  app.installed) _M="App %s installed on %s." ;;
412
464
  app.not_installed) _M="App %s not installed on %s yet: run repowarden app init again once installed." ;;
413
465
  app.check_in_ci) _M="Installation checked by the first ticket tracking run (\"Jeton de l'App\" step); to install it: %s" ;;
414
466
  app.done) _M="repowarden workflows now use the App: what they do triggers the other workflows." ;;
415
467
  app.next) _M="Without the App (variable and secret missing), they fall back to the Actions token." ;;
468
+ 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)." ;;
469
+ bot.init_exists) _M="%s already exists: left unchanged." ;;
470
+ bot.init_done) _M="%s written: runs every night, and on demand (Actions → bot → Run workflow, dry-run box to see without changing anything)." ;;
471
+ bot.init_next) _M="Bot credentials in this repository: repowarden app init --bot (once, installed on \"All repositories\")." ;;
472
+ bot.no_report) _M="Report not found: %s" ;;
473
+ bot.report_updated) _M="Report published (issue #%s updated)." ;;
474
+ bot.report_created) _M="Report published (new issue)." ;;
475
+ bot.col_repo) _M="Repository" ;;
476
+ bot.col_mode) _M="Mode" ;;
477
+ bot.col_protection) _M="Protection" ;;
478
+ bot.col_migration) _M="Migration" ;;
479
+ bot.repo) _M="%s" ;;
480
+ bot.not_adopted) _M="not adopted (no .repowarden.conf)" ;;
481
+ bot.ignored) _M="ignored (bot = off)" ;;
482
+ bot.mode_invalid) _M="bot = %s: auto, pr or off expected" ;;
483
+ bot.clone_failed) _M="clone failed: %s" ;;
484
+ bot.prot_ok) _M="up to date" ;;
485
+ bot.prot_stale) _M="⚠ outdated: repowarden proteger" ;;
486
+ bot.prot_applied) _M="✔ applied" ;;
487
+ bot.error) _M="✖ %s" ;;
488
+ bot.mig_none) _M="nothing to migrate" ;;
489
+ bot.mig_open) _M="PR already open" ;;
490
+ bot.mig_todo) _M="⚠ to migrate: repowarden migrer" ;;
491
+ bot.mig_pr) _M="✔ PR opened" ;;
492
+ bot.report_head) _M="Run of %s." ;;
493
+ bot.report_dry) _M="Preview (dry-run): nothing was changed." ;;
494
+ bot.failed) _M="Run finished with %s error(s): see the report." ;;
495
+ bots.skip) _M="Repository bots not offered (%s): later, repowarden bots" ;;
496
+ bots.why_repo) _M="not in a Git repository" ;;
497
+ bots.why_github) _M="repository not on GitHub" ;;
498
+ bots.why_gh) _M="gh missing: https://cli.github.com" ;;
499
+ bots.why_gh_login) _M="gh not logged in: gh auth login" ;;
500
+ bots.why_admin) _M="repository admin rights needed" ;;
501
+ bots.why_tty) _M="no terminal to answer the questions" ;;
502
+ bots.ask_global) _M="Set up this repository's bots (CI, releases, tickets, protection…)? Nothing is done without your consent, bot by bot." ;;
503
+ bots.later) _M="No bot set up. Later: repowarden bots" ;;
504
+ bots.flow_question) _M="Branch flow:" ;;
505
+ bots.flow_main) _M="%s only: every PR is merged into it (squash)" ;;
506
+ bots.flow_develop) _M="develop → %s: work is integrated into develop (prereleases), only develop goes into %s (delivery)" ;;
507
+ bots.ask_create_develop) _M="The develop branch does not exist on GitHub: create it from the default branch?" ;;
508
+ bots.one_by_one) _M="One consent per bot (Enter: yes):" ;;
509
+ bots.app_present) _M="Workflows GitHub App already set up: used by the other bots." ;;
510
+ bots.desc_ci) _M=" CI: checks every PR (messages, branch, secrets, formatting, tests); writes .github/workflows/repowarden.yml" ;;
511
+ bots.desc_release) _M=" Releases: version, notes and tag computed from the commits; writes .github/workflows/release.yml" ;;
512
+ bots.desc_nettoyage) _M=" Cleanup: deletes merged branches, keeps long-lived branches; writes .github/workflows/nettoyage.yml" ;;
513
+ bots.desc_tickets) _M=" Tickets: ticket first, prefilled PR, statuses up to production; labels, ticket template, workflow (repowarden tickets init)" ;;
514
+ bots.desc_app) _M=" GitHub App: what the bots do triggers the other workflows; created in the browser (repowarden app init)" ;;
515
+ bots.desc_proteger) _M=" Protection: PR and checks required, no deletion or force push; changes GitHub settings (repowarden proteger)" ;;
516
+ bots.desc_npm) _M=" npm: publishes the package on every release, without a token (repowarden npm-publication)" ;;
517
+ bots.ask_ci) _M="CI?" ;;
518
+ bots.ask_release) _M="Releases?" ;;
519
+ bots.ask_nettoyage) _M="Branch cleanup?" ;;
520
+ bots.ask_tickets) _M="Ticket tracking?" ;;
521
+ bots.ask_app) _M="GitHub App?" ;;
522
+ bots.ask_proteger) _M="Branch protection?" ;;
523
+ bots.ask_npm) _M="npm publishing?" ;;
524
+ bots.preview) _M="Preview" ;;
525
+ bots.prev_develop) _M="branch %s created on GitHub from %s" ;;
526
+ bots.prev_conf) _M=".repowarden.conf: integrationBranch = %s, protected branches" ;;
527
+ bots.prev_review) _M=".repowarden.conf: delivery approved by reviewing its PR (no GitHub App)" ;;
528
+ bots.prev_kept) _M="%s exists: kept as is (difference with the template: %s line(s))" ;;
529
+ bots.prev_file) _M="%s: created" ;;
530
+ bots.prev_tickets) _M="tickets: labels and ticket template on GitHub, tickets.yml workflow" ;;
531
+ bots.prev_app) _M="GitHub App: created in the browser, credentials stored as repository variable and secret" ;;
532
+ bots.prev_proteger) _M="GitHub settings: branch and tag rulesets, merge methods" ;;
533
+ bots.prev_npm) _M="npm: trusted publishing of the package, REPOWARDEN_NPM variable" ;;
534
+ bots.ask_confirm) _M="Apply?" ;;
535
+ bots.run_tickets) _M="Ticket tracking" ;;
536
+ bots.run_app) _M="GitHub App" ;;
537
+ bots.run_proteger) _M="Branch protection" ;;
538
+ bots.run_npm) _M="npm publishing" ;;
539
+ bots.failed_one) _M="%s: failed, see above; the other bots are set up. Run again: repowarden bots" ;;
540
+ bots.develop_created) _M="Branch %s created from %s." ;;
541
+ bots.conf_written) _M="%s updated." ;;
542
+ bots.kept) _M="%s exists: kept as is." ;;
543
+ bots.written) _M="%s written." ;;
544
+ bots.done) _M="Bots set up." ;;
545
+ bots.done_errors) _M="Bots set up, %s failed (see above)." ;;
546
+ bots.commit_files) _M="Files to commit through a PR (workflows act once on the default branch): git status" ;;
547
+ bots.verify) _M="Then: repowarden proteger --verifier, and repowarden statut." ;;
416
548
  tk.reopened) _M="Reopened: closed by GitHub when #%s was merged into \`%s\`, which is not production yet. In pre-production; it will be closed at the release." ;;
417
549
  tk.sync.ready) _M="Ticket #%s assigned: local branch %s ready (repowarden ticket %s)." ;;
418
550
  tk.sync.not_validated) _M="Ticket #%s assigned but not validated yet: no branch for now." ;;