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.
- package/README.md +5 -1
- package/bin/app +24 -10
- package/bin/bot +231 -0
- package/bin/bots +370 -0
- package/bin/migrer +205 -0
- package/bin/npm-publication +134 -5
- package/bin/proteger +126 -5
- package/bin/repowarden +7 -1
- package/bin/statut +12 -0
- package/bin/tickets +5 -7
- package/hooks/lib/i18n/en.sh +134 -2
- package/hooks/lib/i18n/fr.sh +134 -2
- package/install.sh +19 -1
- package/package.json +1 -1
package/bin/bots
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Bots du dépôt GitHub courant, configurés avec l'accord de l'utilisateur :
|
|
3
|
+
# repowarden bots questions, aperçu, puis confirmation
|
|
4
|
+
# repowarden install --bots (idem, après l'installation des hooks)
|
|
5
|
+
# repowarden install --sans-bots
|
|
6
|
+
# ne plus jamais le proposer (repowarden.bots = off)
|
|
7
|
+
# Rien n'est fait sans accord : une question globale (non par défaut), le
|
|
8
|
+
# flux (main seul, ou develop → main), un accord par bot (CI, releases,
|
|
9
|
+
# nettoyage, tickets, App, protection, npm), l'aperçu de ce qui sera créé ou
|
|
10
|
+
# modifié, puis une confirmation finale. Un fichier existant n'est jamais
|
|
11
|
+
# écrasé. Relançable pour compléter. Demande un terminal, gh connecté et les
|
|
12
|
+
# droits d'administration du dépôt.
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
16
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
17
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
18
|
+
|
|
19
|
+
aide() { sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; }
|
|
20
|
+
|
|
21
|
+
# Commandes enchaînées (remplaçables dans les tests)
|
|
22
|
+
BIN="${REPOWARDEN_BIN:-$ROOT/bin}"
|
|
23
|
+
DEPUIS_INSTALL=""
|
|
24
|
+
while [ $# -gt 0 ]; do
|
|
25
|
+
case "$1" in
|
|
26
|
+
--depuis-install) DEPUIS_INSTALL=1 ;;
|
|
27
|
+
-h | --help) aide; exit 0 ;;
|
|
28
|
+
*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
29
|
+
esac
|
|
30
|
+
shift
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
# Rien n'est proposé : une ligne dit pourquoi, et comment le faire plus tard
|
|
34
|
+
pas_maintenant() { # $1 clé de la raison
|
|
35
|
+
t "$1"
|
|
36
|
+
info_t bots.skip "$REPLY"
|
|
37
|
+
exit 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Refus permanent (install --sans-bots) : silence depuis l'installation
|
|
41
|
+
if [ -n "$DEPUIS_INSTALL" ] && [ "$(git config --get repowarden.bots 2>/dev/null || true)" = off ]; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
[ -n "$GIT_TOPLEVEL" ] || pas_maintenant bots.why_repo
|
|
45
|
+
cd "$GIT_TOPLEVEL"
|
|
46
|
+
[ "$FORGE" = github ] || pas_maintenant bots.why_github
|
|
47
|
+
command -v gh >/dev/null || pas_maintenant bots.why_gh
|
|
48
|
+
gh auth status >/dev/null 2>&1 || pas_maintenant bots.why_gh_login
|
|
49
|
+
REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || true)"
|
|
50
|
+
[ -n "$REPO" ] || pas_maintenant bots.why_github
|
|
51
|
+
[ "$(gh api "repos/$REPO" --jq .permissions.admin 2>/dev/null || true)" = true ] || pas_maintenant bots.why_admin
|
|
52
|
+
# Terminal (ou réponses fournies, tests) : sinon, jamais de question
|
|
53
|
+
[ -t 0 ] || [ -n "${REPOWARDEN_INTERACTIF:-}" ] || pas_maintenant bots.why_tty
|
|
54
|
+
|
|
55
|
+
# Question fermée : $1 clé, $2 défaut (o ou n) ; vrai si oui
|
|
56
|
+
demander() {
|
|
57
|
+
local reponse choix="o/N"
|
|
58
|
+
[ "$2" = n ] || choix="O/n"
|
|
59
|
+
t "$1"
|
|
60
|
+
printf '%s [%s] ' "$REPLY" "$choix" >&2
|
|
61
|
+
IFS= read -r reponse || reponse=""
|
|
62
|
+
case "$reponse" in
|
|
63
|
+
"") [ "$2" = o ] ;;
|
|
64
|
+
[oOyY]*) return 0 ;;
|
|
65
|
+
*) return 1 ;;
|
|
66
|
+
esac
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
brand "bots · $REPO"
|
|
70
|
+
demander bots.ask_global n || { info_t bots.later; exit 0; }
|
|
71
|
+
|
|
72
|
+
# --- Flux ------------------------------------------------------------------
|
|
73
|
+
cfg_r integrationBranch ""
|
|
74
|
+
INTEGRATION="$REPLY"
|
|
75
|
+
DEFAUT="$(gh api "repos/$REPO" --jq .default_branch)"
|
|
76
|
+
PRINCIPALE=main
|
|
77
|
+
[ "$DEFAUT" = master ] && PRINCIPALE=master
|
|
78
|
+
echo >&2
|
|
79
|
+
t bots.flow_question
|
|
80
|
+
printf '%s\n' "$REPLY" >&2
|
|
81
|
+
t bots.flow_main "$PRINCIPALE"
|
|
82
|
+
printf ' 1) %s\n' "$REPLY" >&2
|
|
83
|
+
t bots.flow_develop "$PRINCIPALE"
|
|
84
|
+
printf ' 2) %s\n' "$REPLY" >&2
|
|
85
|
+
# develop → main par défaut : seul develop entre dans main
|
|
86
|
+
choix_flux=2
|
|
87
|
+
read -r -p "[$choix_flux] : " reponse || reponse=""
|
|
88
|
+
case "${reponse:-$choix_flux}" in
|
|
89
|
+
2*) FLUX=develop; INTEGRATION="${INTEGRATION:-develop}" ;;
|
|
90
|
+
*) FLUX=main; INTEGRATION="" ;;
|
|
91
|
+
esac
|
|
92
|
+
CREER_DEVELOP=""
|
|
93
|
+
if [ "$FLUX" = develop ] &&
|
|
94
|
+
[ -z "$(gh api "repos/$REPO/branches/$INTEGRATION" --jq .name 2>/dev/null || true)" ]; then
|
|
95
|
+
if demander bots.ask_create_develop o; then
|
|
96
|
+
CREER_DEVELOP=1
|
|
97
|
+
else
|
|
98
|
+
FLUX=main INTEGRATION=""
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# --- Un accord par bot -----------------------------------------------------
|
|
103
|
+
echo >&2
|
|
104
|
+
info_t bots.one_by_one
|
|
105
|
+
APP_PRESENTE=""
|
|
106
|
+
[ -z "$(gh variable get REPOWARDEN_APP_CLIENT_ID --repo "$REPO" 2>/dev/null || true)" ] || APP_PRESENTE=1
|
|
107
|
+
NPM_POSSIBLE=""
|
|
108
|
+
[ ! -f package.json ] || grep -qE '"private": *true' package.json || NPM_POSSIBLE=1
|
|
109
|
+
BOTS=()
|
|
110
|
+
for bot in ci release nettoyage tickets app proteger npm; do
|
|
111
|
+
case "$bot" in
|
|
112
|
+
nettoyage) [ "$FLUX" = develop ] || continue ;;
|
|
113
|
+
app) [ -z "$APP_PRESENTE" ] || { info_t bots.app_present; continue; } ;;
|
|
114
|
+
npm) [ -n "$NPM_POSSIBLE" ] || continue ;;
|
|
115
|
+
esac
|
|
116
|
+
dim_t "bots.desc_$bot"
|
|
117
|
+
demander "bots.ask_$bot" o && BOTS+=("$bot")
|
|
118
|
+
done
|
|
119
|
+
[ "${#BOTS[@]}" -gt 0 ] || [ -n "$CREER_DEVELOP" ] || { info_t bots.later; exit 0; }
|
|
120
|
+
choisi() { [[ " ${BOTS[*]+"${BOTS[*]}"} " == *" $1 "* ]]; }
|
|
121
|
+
AVEC_APP=""
|
|
122
|
+
{ [ -n "$APP_PRESENTE" ] || choisi app; } && AVEC_APP=1
|
|
123
|
+
|
|
124
|
+
# --- Modèles des workflows -------------------------------------------------
|
|
125
|
+
modele_ci() {
|
|
126
|
+
cat <<'YAML'
|
|
127
|
+
# Vérifications repowarden en CI (obligatoires pour merger : repowarden proteger)
|
|
128
|
+
name: repowarden
|
|
129
|
+
|
|
130
|
+
on:
|
|
131
|
+
pull_request:
|
|
132
|
+
# edited : revérifie quand le titre de la PR change (message du commit en squash)
|
|
133
|
+
types: [opened, synchronize, reopened, edited]
|
|
134
|
+
push:
|
|
135
|
+
YAML
|
|
136
|
+
if [ "$FLUX" = develop ]; then
|
|
137
|
+
echo " branches: [$PRINCIPALE, $INTEGRATION]"
|
|
138
|
+
else
|
|
139
|
+
echo " branches: [$PRINCIPALE]"
|
|
140
|
+
fi
|
|
141
|
+
cat <<'YAML'
|
|
142
|
+
# Lancé par le workflow de release sur sa PR (release-auto)
|
|
143
|
+
workflow_dispatch:
|
|
144
|
+
|
|
145
|
+
permissions:
|
|
146
|
+
contents: read
|
|
147
|
+
YAML
|
|
148
|
+
[ "$FLUX" != develop ] || echo " pull-requests: write # fix-pr : PR mal visée reciblée"
|
|
149
|
+
cat <<'YAML'
|
|
150
|
+
|
|
151
|
+
jobs:
|
|
152
|
+
repowarden:
|
|
153
|
+
runs-on: ubuntu-latest
|
|
154
|
+
steps:
|
|
155
|
+
- uses: actions/checkout@v7
|
|
156
|
+
with:
|
|
157
|
+
fetch-depth: 0 # historique complet : messages de commit, diff depuis la base
|
|
158
|
+
# Outils du projet nécessaires au formatage et aux tests, par exemple :
|
|
159
|
+
# - uses: actions/setup-node@v7
|
|
160
|
+
# with: { node-version: 24, cache: npm }
|
|
161
|
+
# - run: npm ci
|
|
162
|
+
- uses: simdevWarden/repowarden@v4
|
|
163
|
+
YAML
|
|
164
|
+
if [ "$FLUX" = develop ]; then
|
|
165
|
+
cat <<'YAML'
|
|
166
|
+
with:
|
|
167
|
+
# strict: true # outil absent bloquant, une fois l'outillage installé ci-dessus
|
|
168
|
+
# Seule la branche d'intégration entre dans la principale : une PR
|
|
169
|
+
# de travail mal visée est reciblée avant les vérifications
|
|
170
|
+
fix-pr: "true"
|
|
171
|
+
YAML
|
|
172
|
+
else
|
|
173
|
+
cat <<'YAML'
|
|
174
|
+
# with:
|
|
175
|
+
# strict: true # outil absent bloquant, une fois l'outillage installé ci-dessus
|
|
176
|
+
YAML
|
|
177
|
+
fi
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
modele_release() {
|
|
181
|
+
if [ "$FLUX" = develop ]; then
|
|
182
|
+
cat <<YAML
|
|
183
|
+
# Releases, flux $INTEGRATION → $PRINCIPALE (mode tag) : préversion vX.Y.Z-next.N le
|
|
184
|
+
# soir si $INTEGRATION a changé ; le vendredi matin (ou à la main), une demande
|
|
185
|
+
# de livraison $INTEGRATION → $PRINCIPALE, qui publie le tag vX.Y.Z et la release.
|
|
186
|
+
name: release
|
|
187
|
+
|
|
188
|
+
on:
|
|
189
|
+
push:
|
|
190
|
+
branches: [$PRINCIPALE, $INTEGRATION]
|
|
191
|
+
schedule:
|
|
192
|
+
- cron: "0 19 * * *" # préversion du soir, si $INTEGRATION a changé
|
|
193
|
+
- cron: "0 7 * * 5" # fenêtre de livraison : le vendredi matin
|
|
194
|
+
workflow_dispatch: # à la main (et urgence) : préversion et demande de livraison
|
|
195
|
+
|
|
196
|
+
# Une file par branche et par déclencheur ; une demande manuelle ou planifiée
|
|
197
|
+
# remplace celle en attente ; un push n'annule jamais un run en cours
|
|
198
|
+
concurrency:
|
|
199
|
+
group: release-\${{ github.ref_name }}-\${{ github.event.schedule || github.event_name }}
|
|
200
|
+
cancel-in-progress: \${{ github.event_name != 'push' }}
|
|
201
|
+
|
|
202
|
+
permissions: {}
|
|
203
|
+
|
|
204
|
+
jobs:
|
|
205
|
+
release:
|
|
206
|
+
uses: simdevWarden/repowarden/.github/workflows/release-auto.yml@v4
|
|
207
|
+
permissions: { contents: write, pull-requests: write, actions: write, checks: read, statuses: write }
|
|
208
|
+
with:
|
|
209
|
+
mode: tag
|
|
210
|
+
integration-branch: $INTEGRATION
|
|
211
|
+
main-branch: $PRINCIPALE
|
|
212
|
+
delivery-schedule: "0 7 * * 5" # même cron que la fenêtre ci-dessus
|
|
213
|
+
YAML
|
|
214
|
+
if [ -n "$AVEC_APP" ]; then
|
|
215
|
+
cat <<'YAML'
|
|
216
|
+
delivery-environment: production # « Approve and deploy » valide la livraison
|
|
217
|
+
app-client-id: ${{ vars.REPOWARDEN_APP_CLIENT_ID }} # la GitHub App merge la livraison approuvée
|
|
218
|
+
secrets:
|
|
219
|
+
app-key: ${{ secrets.REPOWARDEN_APP_KEY }}
|
|
220
|
+
YAML
|
|
221
|
+
fi
|
|
222
|
+
else
|
|
223
|
+
cat <<'YAML'
|
|
224
|
+
# Releases automatiques : à chaque merge sur la branche principale, la version
|
|
225
|
+
# (semver) est calculée depuis les Conventional Commits ; une PR « release
|
|
226
|
+
# x.y.z » est validée par la CI puis mergée, et le tag et la release créés.
|
|
227
|
+
name: release
|
|
228
|
+
|
|
229
|
+
on:
|
|
230
|
+
push:
|
|
231
|
+
YAML
|
|
232
|
+
echo " branches: [$PRINCIPALE]"
|
|
233
|
+
cat <<'YAML'
|
|
234
|
+
workflow_dispatch:
|
|
235
|
+
|
|
236
|
+
# Une release à la fois
|
|
237
|
+
concurrency:
|
|
238
|
+
group: release
|
|
239
|
+
cancel-in-progress: false
|
|
240
|
+
|
|
241
|
+
permissions: {}
|
|
242
|
+
|
|
243
|
+
jobs:
|
|
244
|
+
release:
|
|
245
|
+
uses: simdevWarden/repowarden/.github/workflows/release-auto.yml@v4
|
|
246
|
+
permissions: { contents: write, pull-requests: write, actions: write, checks: read, statuses: write }
|
|
247
|
+
YAML
|
|
248
|
+
if [ -n "$AVEC_APP" ]; then
|
|
249
|
+
cat <<'YAML'
|
|
250
|
+
with:
|
|
251
|
+
app-client-id: ${{ vars.REPOWARDEN_APP_CLIENT_ID }}
|
|
252
|
+
secrets:
|
|
253
|
+
app-key: ${{ secrets.REPOWARDEN_APP_KEY }}
|
|
254
|
+
YAML
|
|
255
|
+
fi
|
|
256
|
+
fi
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
modele_nettoyage() {
|
|
260
|
+
cat <<'YAML'
|
|
261
|
+
# Branches de PR supprimées après merge (la suppression automatique de GitHub
|
|
262
|
+
# est désactivée dans le flux develop : elle supprimerait la branche
|
|
263
|
+
# d'intégration au merge de la livraison). Les branches longues et toute
|
|
264
|
+
# branche encore source d'une PR ouverte sont conservées.
|
|
265
|
+
name: nettoyage
|
|
266
|
+
|
|
267
|
+
on:
|
|
268
|
+
pull_request:
|
|
269
|
+
types: [closed]
|
|
270
|
+
|
|
271
|
+
permissions: {}
|
|
272
|
+
|
|
273
|
+
jobs:
|
|
274
|
+
nettoyage:
|
|
275
|
+
uses: simdevWarden/repowarden/.github/workflows/nettoyage-branches.yml@v4
|
|
276
|
+
permissions:
|
|
277
|
+
contents: write
|
|
278
|
+
pull-requests: read
|
|
279
|
+
YAML
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
fichier_du() { # $1 bot → REPLY : chemin de son workflow (vide : aucun)
|
|
283
|
+
case "$1" in
|
|
284
|
+
ci) REPLY=.github/workflows/repowarden.yml ;;
|
|
285
|
+
release) REPLY=.github/workflows/release.yml ;;
|
|
286
|
+
nettoyage) REPLY=.github/workflows/nettoyage.yml ;;
|
|
287
|
+
*) REPLY="" ;;
|
|
288
|
+
esac
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# --- Aperçu, puis confirmation ---------------------------------------------
|
|
292
|
+
echo >&2
|
|
293
|
+
step_t bots.preview
|
|
294
|
+
[ -z "$CREER_DEVELOP" ] || info_t bots.prev_develop "$INTEGRATION" "$PRINCIPALE"
|
|
295
|
+
if [ "$FLUX" = develop ]; then
|
|
296
|
+
info_t bots.prev_conf "$INTEGRATION"
|
|
297
|
+
[ -n "$AVEC_APP" ] || info_t bots.prev_review
|
|
298
|
+
fi
|
|
299
|
+
for bot in ${BOTS[@]+"${BOTS[@]}"}; do
|
|
300
|
+
fichier_du "$bot"
|
|
301
|
+
f="$REPLY"
|
|
302
|
+
if [ -n "$f" ]; then
|
|
303
|
+
if [ -e "$f" ]; then
|
|
304
|
+
ecart="$(diff <("modele_$bot") "$f" | grep -c '^[<>]' || true)"
|
|
305
|
+
info_t bots.prev_kept "$f" "$ecart"
|
|
306
|
+
else
|
|
307
|
+
info_t bots.prev_file "$f"
|
|
308
|
+
fi
|
|
309
|
+
else
|
|
310
|
+
info_t "bots.prev_$bot"
|
|
311
|
+
fi
|
|
312
|
+
done
|
|
313
|
+
demander bots.ask_confirm n || { info_t bots.later; exit 0; }
|
|
314
|
+
|
|
315
|
+
# --- Application -----------------------------------------------------------
|
|
316
|
+
echecs=0
|
|
317
|
+
lancer() { # $1 bot, puis la commande : une erreur n'arrête pas les autres
|
|
318
|
+
local bot="$1"
|
|
319
|
+
shift
|
|
320
|
+
echo >&2
|
|
321
|
+
step_t "bots.run_$bot"
|
|
322
|
+
if ! "$@"; then
|
|
323
|
+
err_t bots.failed_one "$bot"
|
|
324
|
+
echecs=$((echecs + 1))
|
|
325
|
+
fi
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if [ -n "$CREER_DEVELOP" ]; then
|
|
329
|
+
sha="$(gh api "repos/$REPO/git/ref/heads/$DEFAUT" --jq .object.sha)"
|
|
330
|
+
gh api -X POST "repos/$REPO/git/refs" -f "ref=refs/heads/$INTEGRATION" -f "sha=$sha" --silent
|
|
331
|
+
ok_t bots.develop_created "$INTEGRATION" "$DEFAUT"
|
|
332
|
+
fi
|
|
333
|
+
if [ "$FLUX" = develop ]; then
|
|
334
|
+
conf="$GIT_TOPLEVEL/.repowarden.conf"
|
|
335
|
+
git config -f "$conf" repowarden.integrationBranch "$INTEGRATION"
|
|
336
|
+
git config -f "$conf" --get repowarden.protectedBranches >/dev/null ||
|
|
337
|
+
git config -f "$conf" repowarden.protectedBranches "$PRINCIPALE $INTEGRATION"
|
|
338
|
+
if [ -z "$AVEC_APP" ]; then
|
|
339
|
+
git config -f "$conf" --get repowarden.deliveryApproval >/dev/null ||
|
|
340
|
+
git config -f "$conf" repowarden.deliveryApproval review
|
|
341
|
+
fi
|
|
342
|
+
ok_t bots.conf_written .repowarden.conf
|
|
343
|
+
fi
|
|
344
|
+
for bot in ${BOTS[@]+"${BOTS[@]}"}; do
|
|
345
|
+
fichier_du "$bot"
|
|
346
|
+
f="$REPLY"
|
|
347
|
+
if [ -n "$f" ]; then
|
|
348
|
+
if [ -e "$f" ]; then
|
|
349
|
+
info_t bots.kept "$f"
|
|
350
|
+
else
|
|
351
|
+
mkdir -p "${f%/*}"
|
|
352
|
+
"modele_$bot" >"$f"
|
|
353
|
+
ok_t bots.written "$f"
|
|
354
|
+
fi
|
|
355
|
+
fi
|
|
356
|
+
done
|
|
357
|
+
choisi tickets && lancer tickets bash "$BIN/tickets" init --repo "$REPO"
|
|
358
|
+
choisi app && lancer app bash "$BIN/app" init --repo "$REPO"
|
|
359
|
+
choisi proteger && lancer proteger bash "$BIN/proteger" --repo "$REPO"
|
|
360
|
+
choisi npm && lancer npm bash "$BIN/npm-publication" --repo "$REPO"
|
|
361
|
+
|
|
362
|
+
echo >&2
|
|
363
|
+
if [ "$echecs" = 0 ]; then
|
|
364
|
+
ok_t bots.done
|
|
365
|
+
else
|
|
366
|
+
attention_t bots.done_errors "$echecs"
|
|
367
|
+
fi
|
|
368
|
+
[ -z "$(git status --porcelain -- .github .repowarden.conf 2>/dev/null)" ] || info_t bots.commit_files
|
|
369
|
+
dim_t bots.verify
|
|
370
|
+
[ "$echecs" = 0 ]
|
package/bin/migrer
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Passage de repogarde à repowarden dans le projet courant (docs/migration-v4.md) :
|
|
3
|
+
# repowarden migrer applique la migration dans l'arbre de travail
|
|
4
|
+
# repowarden migrer --pr sur une branche chore/migration-repowarden-vN :
|
|
5
|
+
# commit, push et PR (rien si une PR est ouverte)
|
|
6
|
+
# repowarden migrer --hooks déplace aussi les hooks lefthook résiduels de
|
|
7
|
+
# .git/hooks dans .git/hooks-lefthook/
|
|
8
|
+
# Configuration (.repowarden.conf, .repowarden/), workflows (uses: vers le
|
|
9
|
+
# dépôt repowarden et sa version majeure, anciens noms, permissions), puis la
|
|
10
|
+
# liste de ce qui reste à faire à la main. Relançable : rien n'est fait deux fois.
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
14
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
15
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
16
|
+
|
|
17
|
+
aide() { sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; }
|
|
18
|
+
|
|
19
|
+
PR="" HOOKS_LEFTHOOK=""
|
|
20
|
+
while [ $# -gt 0 ]; do
|
|
21
|
+
case "$1" in
|
|
22
|
+
--pr) PR=1 ;;
|
|
23
|
+
--hooks) HOOKS_LEFTHOOK=1 ;;
|
|
24
|
+
-h | --help) aide; exit 0 ;;
|
|
25
|
+
*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
26
|
+
esac
|
|
27
|
+
shift
|
|
28
|
+
done
|
|
29
|
+
[ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
|
|
30
|
+
cd "$GIT_TOPLEVEL"
|
|
31
|
+
|
|
32
|
+
# Version majeure dont le tag flottant (vN) est publié : celle de repowarden ;
|
|
33
|
+
# préversion d'une nouvelle majeure (X.0.0-next) : X-1, vX n'existant pas encore
|
|
34
|
+
majeure_r() {
|
|
35
|
+
local v
|
|
36
|
+
repowarden_version_r "$ROOT"
|
|
37
|
+
v="$REPLY"
|
|
38
|
+
REPLY="${v%%.*}"
|
|
39
|
+
REPLY="${REPLY%%-*}"
|
|
40
|
+
[[ "$v" != *.0.0-* ]] || REPLY=$((REPLY - 1))
|
|
41
|
+
}
|
|
42
|
+
if [ -n "${REPOWARDEN_MAJEURE:-}" ]; then
|
|
43
|
+
MAJEURE="$REPOWARDEN_MAJEURE"
|
|
44
|
+
else
|
|
45
|
+
majeure_r
|
|
46
|
+
MAJEURE="$REPLY"
|
|
47
|
+
fi
|
|
48
|
+
[[ "$MAJEURE" =~ ^[1-9][0-9]*$ ]] || { err_t mig.no_major "$MAJEURE"; exit 1; }
|
|
49
|
+
BRANCHE="chore/migration-repowarden-v$MAJEURE"
|
|
50
|
+
|
|
51
|
+
if [ -n "$PR" ]; then
|
|
52
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
53
|
+
[ -z "$(git status --porcelain)" ] || { err_t mig.dirty; exit 1; }
|
|
54
|
+
if [ -n "$(gh pr list --head "$BRANCHE" --state open --json number --jq '.[].number' 2>/dev/null)" ]; then
|
|
55
|
+
info_t mig.pr_open "$BRANCHE"
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
git switch -q -c "$BRANCHE"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
FAITS=0
|
|
62
|
+
fait() { FAITS=$((FAITS + 1)); ok_t "$@"; }
|
|
63
|
+
|
|
64
|
+
# Fichier réécrit par sed (-E), sans -i (BSD et GNU diffèrent)
|
|
65
|
+
reecrire() { # $1 fichier, $2… expressions sed
|
|
66
|
+
local f="$1" tmp
|
|
67
|
+
shift
|
|
68
|
+
tmp="$(mktemp)"
|
|
69
|
+
sed -E "$@" "$f" >"$tmp"
|
|
70
|
+
if cmp -s "$f" "$tmp"; then
|
|
71
|
+
rm -f "$tmp"
|
|
72
|
+
return 1
|
|
73
|
+
fi
|
|
74
|
+
cat "$tmp" >"$f"
|
|
75
|
+
rm -f "$tmp"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# 1. Configuration du projet
|
|
79
|
+
if [ -f .repogarde.conf ]; then
|
|
80
|
+
if [ -e .repowarden.conf ]; then
|
|
81
|
+
attention_t mig.both .repogarde.conf .repowarden.conf
|
|
82
|
+
else
|
|
83
|
+
git mv .repogarde.conf .repowarden.conf 2>/dev/null || mv .repogarde.conf .repowarden.conf
|
|
84
|
+
reecrire .repowarden.conf -e 's/repogarde/repowarden/g' \
|
|
85
|
+
-e "s/^([[:space:]]*version[[:space:]]*=[[:space:]]*).*/\\1$MAJEURE/" || true
|
|
86
|
+
fait mig.conf
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
if [ -d .repogarde ]; then
|
|
90
|
+
if [ -e .repowarden ]; then
|
|
91
|
+
attention_t mig.both .repogarde/ .repowarden/
|
|
92
|
+
else
|
|
93
|
+
git mv .repogarde .repowarden 2>/dev/null || mv .repogarde .repowarden
|
|
94
|
+
fait mig.dir
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# 2. Workflows
|
|
99
|
+
secrets=""
|
|
100
|
+
for wf in .github/workflows/*.yml .github/workflows/*.yaml; do
|
|
101
|
+
[ -f "$wf" ] || continue
|
|
102
|
+
grep -qi repogarde "$wf" || continue
|
|
103
|
+
# shellcheck disable=SC2013 # noms sans espace
|
|
104
|
+
for nom in $(grep -oE '(secrets|vars)\.REPOGARDE_[A-Z0-9_]+' "$wf" | sort -u); do
|
|
105
|
+
secrets="$secrets ${nom/REPOGARDE_/REPOWARDEN_}"
|
|
106
|
+
done
|
|
107
|
+
# uses: vers l'ancien dépôt, toute référence (tag, branche d'essai) ;
|
|
108
|
+
# entrée retirée (version désormais fixée par uses:) ; anciens noms ;
|
|
109
|
+
# tickets.yml de la v4 : création de branches (contents: write)
|
|
110
|
+
change=""
|
|
111
|
+
reecrire "$wf" \
|
|
112
|
+
-e "s#(uses:[[:space:]]*)[A-Za-z0-9_.-]+/repogarde((/[^@[:space:]]+)?)@[^[:space:]]+#\\1simdevWarden/repowarden\\2@v$MAJEURE#" \
|
|
113
|
+
-e '/^[[:space:]]*repogarde-ref:/d' \
|
|
114
|
+
-e 's#SimBienvenueHoulBoumi/repogarde#simdevWarden/repowarden#g' \
|
|
115
|
+
-e 's/repogarde/repowarden/g' -e 's/REPOGARDE_/REPOWARDEN_/g' && change=1
|
|
116
|
+
# permissions du job (pas celles du workflow, souvent « {} »)
|
|
117
|
+
if grep -q 'repowarden/.github/workflows/tickets.yml' "$wf"; then
|
|
118
|
+
reecrire "$wf" -e '/^[[:space:]]*permissions:[[:space:]]*\{[^}]*issues:/{/contents:/!s/\{[[:space:]]*/{ contents: write, /;}' && change=1
|
|
119
|
+
fi
|
|
120
|
+
[ -z "$change" ] || fait mig.workflow "$wf"
|
|
121
|
+
done
|
|
122
|
+
if [ -f .github/workflows/repogarde.yml ] && [ ! -e .github/workflows/repowarden.yml ]; then
|
|
123
|
+
git mv .github/workflows/repogarde.yml .github/workflows/repowarden.yml 2>/dev/null ||
|
|
124
|
+
mv .github/workflows/repogarde.yml .github/workflows/repowarden.yml
|
|
125
|
+
fait mig.renamed .github/workflows/repogarde.yml .github/workflows/repowarden.yml
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Suivi des tickets d'une version antérieure : sans l'événement push, le
|
|
129
|
+
# premier push d'une branche de ticket n'ouvre pas la PR brouillon préremplie
|
|
130
|
+
# (ni revue, assignation, fermeture : statuts figés). Section on: remplacée
|
|
131
|
+
# par celle du modèle (repowarden tickets init), le reste gardé.
|
|
132
|
+
for wf in .github/workflows/*.yml .github/workflows/*.yaml; do
|
|
133
|
+
[ -f "$wf" ] || continue
|
|
134
|
+
grep -q 'repowarden/.github/workflows/tickets.yml' "$wf" || continue
|
|
135
|
+
grep -qE '^ push:' "$wf" && continue
|
|
136
|
+
tmp="$(mktemp)"
|
|
137
|
+
awk '
|
|
138
|
+
/^on:/ {
|
|
139
|
+
print "on:"
|
|
140
|
+
print " pull_request_target:"
|
|
141
|
+
print " types: [opened, reopened, edited, synchronize, ready_for_review, converted_to_draft, closed]"
|
|
142
|
+
print " pull_request_review:"
|
|
143
|
+
print " types: [submitted]"
|
|
144
|
+
print " issues:"
|
|
145
|
+
print " types: [opened, labeled, assigned, closed]"
|
|
146
|
+
print " push:"
|
|
147
|
+
print " branches-ignore: [main, master, develop]"
|
|
148
|
+
dans = 1
|
|
149
|
+
next
|
|
150
|
+
}
|
|
151
|
+
dans && /^[^ #]/ { dans = 0 }
|
|
152
|
+
!dans { print }
|
|
153
|
+
' "$wf" >"$tmp"
|
|
154
|
+
cat "$tmp" >"$wf"
|
|
155
|
+
rm -f "$tmp"
|
|
156
|
+
fait mig.tickets_events "$wf"
|
|
157
|
+
info_t mig.tickets_labels
|
|
158
|
+
done
|
|
159
|
+
|
|
160
|
+
# 3. Hooks lefthook résiduels (lefthook n'est plus pris en charge)
|
|
161
|
+
hooks_dir="$(git rev-parse --git-common-dir)/hooks"
|
|
162
|
+
residuels=""
|
|
163
|
+
for h in "$hooks_dir"/*; do
|
|
164
|
+
[ -f "$h" ] && [[ "$h" != *.sample ]] && grep -qs lefthook "$h" && residuels="$residuels ${h##*/}"
|
|
165
|
+
done
|
|
166
|
+
if [ -n "$residuels" ]; then
|
|
167
|
+
if [ -n "$HOOKS_LEFTHOOK" ]; then
|
|
168
|
+
mkdir -p "$hooks_dir-lefthook"
|
|
169
|
+
for h in $residuels; do mv "$hooks_dir/$h" "$hooks_dir-lefthook/$h"; done
|
|
170
|
+
ok_t mig.hooks_moved "$residuels" "$hooks_dir-lefthook/"
|
|
171
|
+
else
|
|
172
|
+
attention_t mig.hooks_left "$residuels"
|
|
173
|
+
fi
|
|
174
|
+
fi
|
|
175
|
+
for f in lefthook.yml lefthook.yaml .lefthook.yml .lefthook.yaml; do
|
|
176
|
+
[ -f "$f" ] && attention_t mig.lefthook_conf "$f"
|
|
177
|
+
done
|
|
178
|
+
|
|
179
|
+
# 4. À faire à la main
|
|
180
|
+
[ -z "$secrets" ] || attention_t mig.secrets "$secrets"
|
|
181
|
+
restants="$(git grep -il repogarde -- . ':!CHANGELOG.md' 2>/dev/null | tr '\n' ' ' || true)"
|
|
182
|
+
[ -z "$restants" ] || info_t mig.remaining "$restants"
|
|
183
|
+
|
|
184
|
+
if [ "$FAITS" = 0 ]; then
|
|
185
|
+
ok_t mig.nothing
|
|
186
|
+
if [ -n "$PR" ]; then
|
|
187
|
+
git switch -q -
|
|
188
|
+
git branch -q -D "$BRANCHE"
|
|
189
|
+
fi
|
|
190
|
+
exit 0
|
|
191
|
+
fi
|
|
192
|
+
info_t mig.next
|
|
193
|
+
|
|
194
|
+
[ -n "$PR" ] || exit 0
|
|
195
|
+
git add -A
|
|
196
|
+
t mig.commit_subject "$MAJEURE"
|
|
197
|
+
sujet="$REPLY"
|
|
198
|
+
t mig.commit_body
|
|
199
|
+
bash "$ROOT/bin/commit" -m "$sujet" --type chore --scope migration --body "$REPLY"
|
|
200
|
+
git push -q -u origin "$BRANCHE"
|
|
201
|
+
t mig.pr_body
|
|
202
|
+
corps="$REPLY"
|
|
203
|
+
t mig.pr_after
|
|
204
|
+
gh pr create --head "$BRANCHE" --title "chore(migration): $sujet" --body "$corps"$'\n\n'"$REPLY" >/dev/null
|
|
205
|
+
ok_t mig.pr_created "$BRANCHE"
|