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/README.md
CHANGED
|
@@ -99,7 +99,11 @@ Sans Node : `git clone https://github.com/simdevWarden/repowarden.git ~/repoward
|
|
|
99
99
|
|
|
100
100
|
### 2. Dans un projet d'équipe : les mêmes règles pour tous
|
|
101
101
|
|
|
102
|
-
Chaque personne de l'équipe installe repowarden une fois sur son poste (partie 1).
|
|
102
|
+
Chaque personne de l'équipe installe repowarden une fois sur son poste (partie 1).
|
|
103
|
+
|
|
104
|
+
**Sur GitHub, en une commande** : dans le projet, `repowarden bots` (aussi proposé par `repowarden install`) configure les bots du dépôt, avec ton accord pour chacun : CI, releases, nettoyage des branches, tickets, GitHub App, protection, npm. Il demande le flux (`main` seule, ou `develop` → `main`), montre l'aperçu, puis attend ta confirmation ; un fichier existant n'est jamais écrasé.
|
|
105
|
+
|
|
106
|
+
**À la main**, le projet ajoute deux fichiers, à copier depuis [`templates/project/`](templates/project) :
|
|
103
107
|
|
|
104
108
|
| Fichier | Rôle |
|
|
105
109
|
|---|---|
|
package/bin/app
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
# sans CI, merge automatique sans préversion…) ; un jeton de l'App, si.
|
|
5
5
|
#
|
|
6
6
|
# repowarden app init [--repo propriétaire/nom] [--nom NOM] [--administration]
|
|
7
|
+
# repowarden app init --bot App du bot d'organisation (repowarden bot),
|
|
8
|
+
# dans le dépôt de pilotage : workflows et
|
|
9
|
+
# administration en plus ; identifiants
|
|
10
|
+
# REPOWARDEN_BOT_CLIENT_ID, REPOWARDEN_BOT_KEY
|
|
7
11
|
#
|
|
8
12
|
# 1. création (flux « manifest » de GitHub) : une page locale envoie la
|
|
9
13
|
# description de l'App à GitHub, tu cliques sur « Create » ; permissions
|
|
@@ -19,16 +23,17 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
|
19
23
|
# shellcheck source=../hooks/lib/common.sh
|
|
20
24
|
source "$ROOT/hooks/lib/common.sh"
|
|
21
25
|
|
|
22
|
-
aide() { sed -n '2,
|
|
26
|
+
aide() { sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; }
|
|
23
27
|
|
|
24
28
|
commande="${1:-}"
|
|
25
29
|
[ $# -eq 0 ] || shift
|
|
26
|
-
REPO="" NOM="" ADMIN=""
|
|
30
|
+
REPO="" NOM="" ADMIN="" BOT=""
|
|
27
31
|
while [ $# -gt 0 ]; do
|
|
28
32
|
case "$1" in
|
|
29
33
|
--repo) shift; REPO="${1:?--repo attend propriétaire/nom}" ;;
|
|
30
34
|
--nom) shift; NOM="${1:?--nom attend un nom}" ;;
|
|
31
35
|
--administration) ADMIN=1 ;;
|
|
36
|
+
--bot) BOT=1 ADMIN=1 ;;
|
|
32
37
|
-h | --help) aide; exit 0 ;;
|
|
33
38
|
*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
34
39
|
esac
|
|
@@ -44,6 +49,12 @@ command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
|
44
49
|
command -v node >/dev/null || { err_t app.no_node; exit 1; }
|
|
45
50
|
[ -n "$REPO" ] || REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner)"
|
|
46
51
|
owner="${REPO%%/*}"
|
|
52
|
+
# App du bot : identifiants à part (dépôt de pilotage), droits sur les workflows
|
|
53
|
+
PREFIXE=REPOWARDEN_APP
|
|
54
|
+
if [ -n "$BOT" ]; then
|
|
55
|
+
PREFIXE=REPOWARDEN_BOT
|
|
56
|
+
NOM="${NOM:-repowarden-bot-$owner}"
|
|
57
|
+
fi
|
|
47
58
|
NOM="${NOM:-repowarden-$owner}"
|
|
48
59
|
brand "app · $REPO"
|
|
49
60
|
|
|
@@ -58,10 +69,10 @@ ouvrir() {
|
|
|
58
69
|
|
|
59
70
|
# 1. Création, si l'App n'est pas déjà configurée pour ce dépôt
|
|
60
71
|
step_t app.step_create
|
|
61
|
-
client_id="$(gh variable get
|
|
62
|
-
if [ -n "$client_id" ] && grep -q
|
|
72
|
+
client_id="$(gh variable get "${PREFIXE}_CLIENT_ID" --repo "$REPO" 2>/dev/null || true)"
|
|
73
|
+
if [ -n "$client_id" ] && grep -q "^${PREFIXE}_KEY" <<<"$(gh secret list --repo "$REPO")"; then
|
|
63
74
|
ok_t app.exists "$client_id"
|
|
64
|
-
slug="$(gh variable get
|
|
75
|
+
slug="$(gh variable get "${PREFIXE}_SLUG" --repo "$REPO" 2>/dev/null || true)"
|
|
65
76
|
else
|
|
66
77
|
if [ "$(gh api "users/$owner" -q .type)" = Organization ]; then
|
|
67
78
|
cible="https://github.com/organizations/$owner/settings/apps/new"
|
|
@@ -72,7 +83,7 @@ else
|
|
|
72
83
|
trap 'rm -f "$sortie"' EXIT
|
|
73
84
|
# Page locale : envoie la description à GitHub (formulaire POST), puis
|
|
74
85
|
# reçoit le code de retour ; le code est écrit sur la sortie standard
|
|
75
|
-
NOM="$NOM" REPO="$REPO" ADMIN="$ADMIN" CIBLE="$cible" node -e '
|
|
86
|
+
NOM="$NOM" REPO="$REPO" ADMIN="$ADMIN" BOT="$BOT" CIBLE="$cible" node -e '
|
|
76
87
|
const http = require("http"), crypto = require("crypto");
|
|
77
88
|
const etat = crypto.randomBytes(16).toString("hex");
|
|
78
89
|
const srv = http.createServer((req, res) => {
|
|
@@ -88,6 +99,8 @@ else
|
|
|
88
99
|
const port = srv.address().port;
|
|
89
100
|
const perms = { contents: "write", pull_requests: "write", issues: "write", statuses: "write", metadata: "read" };
|
|
90
101
|
if (process.env.ADMIN) perms.administration = "write";
|
|
102
|
+
// Bot : PR de migration qui modifient les workflows des projets
|
|
103
|
+
if (process.env.BOT) perms.workflows = "write";
|
|
91
104
|
const manifeste = JSON.stringify({
|
|
92
105
|
name: process.env.NOM, url: "https://github.com/" + process.env.REPO,
|
|
93
106
|
// Webhook inactif : GitHub exige tout de même son adresse
|
|
@@ -117,11 +130,11 @@ else
|
|
|
117
130
|
slug="$(node -p 'JSON.parse(require("fs").readFileSync(0, "utf8")).slug' <<<"$reponse")"
|
|
118
131
|
# Clé gardée en mémoire le temps de vérifier l'installation, jamais affichée
|
|
119
132
|
cle="$(node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(0, "utf8")).pem)' <<<"$reponse")"
|
|
120
|
-
printf '%s' "$cle" | gh secret set
|
|
133
|
+
printf '%s' "$cle" | gh secret set "${PREFIXE}_KEY" --repo "$REPO"
|
|
121
134
|
reponse=""
|
|
122
|
-
gh variable set
|
|
123
|
-
gh variable set
|
|
124
|
-
ok_t app.created "$slug"
|
|
135
|
+
gh variable set "${PREFIXE}_CLIENT_ID" --repo "$REPO" --body "$client_id"
|
|
136
|
+
gh variable set "${PREFIXE}_SLUG" --repo "$REPO" --body "$slug"
|
|
137
|
+
ok_t app.created "$slug" "${PREFIXE}_CLIENT_ID" "${PREFIXE}_KEY"
|
|
125
138
|
fi
|
|
126
139
|
|
|
127
140
|
# 2. Installation sur le dépôt (vérifiée depuis tes installations)
|
|
@@ -147,6 +160,7 @@ if [ -z "$cle" ]; then
|
|
|
147
160
|
exit 0
|
|
148
161
|
fi
|
|
149
162
|
if [ -n "$slug" ] && ! installee; then
|
|
163
|
+
[ -z "$BOT" ] || info_t app.bot_all_repos
|
|
150
164
|
info_t app.open "https://github.com/apps/$slug/installations/new"
|
|
151
165
|
ouvrir "https://github.com/apps/$slug/installations/new"
|
|
152
166
|
for _ in $(seq 1 120); do installee && break; sleep 5; done
|
package/bin/bot
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Bot d'organisation : tient à jour les projets où l'App du bot est installée.
|
|
3
|
+
# repowarden bot [--repo o/r]… [--dry-run] [--rapport FICHIER]
|
|
4
|
+
# passage : protection (proteger) et migration
|
|
5
|
+
# (migrer --pr) de chaque projet adopté
|
|
6
|
+
# repowarden bot rapport FICHIER
|
|
7
|
+
# rapport publié dans l'issue « Rapport du bot
|
|
8
|
+
# repowarden » du dépôt courant (créée ou mise à jour)
|
|
9
|
+
# repowarden bot init workflow du dépôt de pilotage (.github/workflows/bot.yml)
|
|
10
|
+
# Un projet est adopté s'il a un .repowarden.conf ; sa clé bot y décide :
|
|
11
|
+
# auto (défaut : protection appliquée, PR de migration), pr (PR seulement),
|
|
12
|
+
# off (ignoré). GH_TOKEN : jeton d'installation de l'App (repowarden app init --bot).
|
|
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
|
+
# Scripts appelés (remplaçables dans les tests) et origine des clones
|
|
22
|
+
BIN="${REPOWARDEN_BIN:-$ROOT/bin}"
|
|
23
|
+
GIT_URL="${REPOWARDEN_BOT_GIT:-https://github.com}"
|
|
24
|
+
TITRE_RAPPORT="Rapport du bot repowarden"
|
|
25
|
+
|
|
26
|
+
commande="${1:-}"
|
|
27
|
+
case "$commande" in
|
|
28
|
+
init | rapport) shift ;;
|
|
29
|
+
-h | --help) aide; exit 0 ;;
|
|
30
|
+
*) commande=passage ;;
|
|
31
|
+
esac
|
|
32
|
+
|
|
33
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
34
|
+
|
|
35
|
+
# --- repowarden bot init : workflow du dépôt de pilotage -------------------
|
|
36
|
+
if [ "$commande" = init ]; then
|
|
37
|
+
[ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
|
|
38
|
+
f="$GIT_TOPLEVEL/.github/workflows/bot.yml"
|
|
39
|
+
if [ -e "$f" ]; then
|
|
40
|
+
attention_t bot.init_exists ".github/workflows/bot.yml"
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
mkdir -p "${f%/*}"
|
|
44
|
+
cat >"$f" <<'YAML'
|
|
45
|
+
# Bot repowarden : chaque nuit, tient à jour les projets où son App est
|
|
46
|
+
# installée (protection, PR de migration) ; rapport dans l'issue « Rapport du
|
|
47
|
+
# bot repowarden ». Identifiants : repowarden app init --bot.
|
|
48
|
+
name: bot
|
|
49
|
+
|
|
50
|
+
on:
|
|
51
|
+
schedule:
|
|
52
|
+
- cron: "17 3 * * *"
|
|
53
|
+
workflow_dispatch:
|
|
54
|
+
inputs:
|
|
55
|
+
dry-run:
|
|
56
|
+
description: Voir ce qui serait fait, sans rien modifier
|
|
57
|
+
type: boolean
|
|
58
|
+
default: false
|
|
59
|
+
|
|
60
|
+
# Un passage à la fois
|
|
61
|
+
concurrency:
|
|
62
|
+
group: bot
|
|
63
|
+
cancel-in-progress: false
|
|
64
|
+
|
|
65
|
+
permissions: {}
|
|
66
|
+
|
|
67
|
+
jobs:
|
|
68
|
+
bot:
|
|
69
|
+
uses: simdevWarden/repowarden/.github/workflows/bot.yml@v4
|
|
70
|
+
permissions: { contents: read, issues: write }
|
|
71
|
+
with:
|
|
72
|
+
app-client-id: ${{ vars.REPOWARDEN_BOT_CLIENT_ID }}
|
|
73
|
+
dry-run: ${{ inputs.dry-run || false }}
|
|
74
|
+
secrets:
|
|
75
|
+
app-key: ${{ secrets.REPOWARDEN_BOT_KEY }}
|
|
76
|
+
YAML
|
|
77
|
+
ok_t bot.init_done ".github/workflows/bot.yml"
|
|
78
|
+
dim_t bot.init_next
|
|
79
|
+
exit 0
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# --- repowarden bot rapport FICHIER : issue unique du dépôt de pilotage -----
|
|
83
|
+
if [ "$commande" = rapport ]; then
|
|
84
|
+
fichier="${1:?repowarden bot rapport FICHIER}"
|
|
85
|
+
[ -f "$fichier" ] || { err_t bot.no_report "$fichier"; exit 1; }
|
|
86
|
+
numero="$(gh issue list --state open --search "\"$TITRE_RAPPORT\" in:title" --json number,title \
|
|
87
|
+
--jq ".[] | select(.title == \"$TITRE_RAPPORT\") | .number" | sed -n 1p)"
|
|
88
|
+
if [ -n "$numero" ]; then
|
|
89
|
+
gh issue edit "$numero" --body-file "$fichier" >/dev/null
|
|
90
|
+
ok_t bot.report_updated "$numero"
|
|
91
|
+
else
|
|
92
|
+
gh issue create --title "$TITRE_RAPPORT" --body-file "$fichier" >/dev/null
|
|
93
|
+
ok_t bot.report_created
|
|
94
|
+
fi
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# --- Passage ----------------------------------------------------------------
|
|
99
|
+
REPOS="" DRY_RUN="" RAPPORT=""
|
|
100
|
+
while [ $# -gt 0 ]; do
|
|
101
|
+
case "$1" in
|
|
102
|
+
--repo) shift; REPOS="$REPOS ${1:?--repo attend propriétaire/nom}" ;;
|
|
103
|
+
--dry-run) DRY_RUN=1 ;;
|
|
104
|
+
--rapport) shift; RAPPORT="${1:?--rapport attend un fichier}" ;;
|
|
105
|
+
*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
106
|
+
esac
|
|
107
|
+
shift
|
|
108
|
+
done
|
|
109
|
+
if [ -z "$REPOS" ]; then
|
|
110
|
+
REPOS="$(gh api installation/repositories --paginate \
|
|
111
|
+
--jq '.repositories[] | select(.archived | not) | .full_name')"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
TRAVAIL="$(mktemp -d)"
|
|
115
|
+
trap 'rm -rf "$TRAVAIL"' EXIT
|
|
116
|
+
# Identité des commits du bot (workflow : <slug>[bot]), crédits de gh
|
|
117
|
+
NOM_BOT="${REPOWARDEN_BOT_NAME:-repowarden-bot}"
|
|
118
|
+
COURRIEL_BOT="${REPOWARDEN_BOT_EMAIL:-repowarden-bot@users.noreply.github.com}"
|
|
119
|
+
|
|
120
|
+
# Dernière ligne d'erreur d'une sortie (✖ …), pour le rapport
|
|
121
|
+
derniere_erreur_r() {
|
|
122
|
+
REPLY="$(grep '✖' <<<"$1" | tail -n 1 | sed 's/^[^✖]*✖ *//')"
|
|
123
|
+
[ -n "$REPLY" ] || REPLY="$(tail -n 1 <<<"$1")"
|
|
124
|
+
REPLY="${REPLY//|/\\|}"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Vérifications exigées : requiredChecks du projet, sinon celles du ruleset
|
|
128
|
+
# repowarden en ligne (jamais retirées), sinon repowarden
|
|
129
|
+
verifications_r() { # $1 dépôt, $2 configuration
|
|
130
|
+
REPLY="$(git config -f "$2" repowarden.requiredChecks 2>/dev/null || true)"
|
|
131
|
+
[ -z "$REPLY" ] || return 0
|
|
132
|
+
local id
|
|
133
|
+
id="$(gh api "repos/$1/rulesets" --jq '.[] | select(.name == "repowarden") | .id' 2>/dev/null || true)"
|
|
134
|
+
[ -z "$id" ] || REPLY="$(gh api "repos/$1/rulesets/$id" \
|
|
135
|
+
--jq '[.rules[] | select(.type == "required_status_checks") | .parameters.required_status_checks[].context] | join(",")' \
|
|
136
|
+
2>/dev/null || true)"
|
|
137
|
+
REPLY="${REPLY:-repowarden}"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
t bot.col_repo; c1="$REPLY"
|
|
141
|
+
t bot.col_mode; c2="$REPLY"
|
|
142
|
+
t bot.col_protection; c3="$REPLY"
|
|
143
|
+
t bot.col_migration; c4="$REPLY"
|
|
144
|
+
# Sorties de migrer reconnues : rien à migrer, PR déjà ouverte
|
|
145
|
+
t mig.nothing; MIG_RIEN="$REPLY"
|
|
146
|
+
t mig.pr_open ""; MIG_OUVERTE="${REPLY%%(*}"
|
|
147
|
+
lignes="| $c1 | $c2 | $c3 | $c4 |"$'\n'"|---|---|---|---|"
|
|
148
|
+
erreurs=0
|
|
149
|
+
ligne() { lignes="$lignes"$'\n'"| \`$1\` | $2 | $3 | $4 |"; }
|
|
150
|
+
|
|
151
|
+
for depot in $REPOS; do
|
|
152
|
+
step_t bot.repo "$depot"
|
|
153
|
+
conf="$TRAVAIL/${depot//\//_}.conf"
|
|
154
|
+
if ! gh api -H "Accept: application/vnd.github.raw" "repos/$depot/contents/.repowarden.conf" >"$conf" 2>/dev/null &&
|
|
155
|
+
! gh api -H "Accept: application/vnd.github.raw" "repos/$depot/contents/.repogarde.conf" >"$conf" 2>/dev/null; then
|
|
156
|
+
t bot.not_adopted; info "$REPLY"
|
|
157
|
+
ligne "$depot" "—" "$REPLY" "—"
|
|
158
|
+
continue
|
|
159
|
+
fi
|
|
160
|
+
sed -i.bak 's/^\[repogarde\]/[repowarden]/' "$conf"
|
|
161
|
+
mode="$(git config -f "$conf" repowarden.bot 2>/dev/null || echo auto)"
|
|
162
|
+
case "$mode" in
|
|
163
|
+
off)
|
|
164
|
+
t bot.ignored; info "$REPLY"
|
|
165
|
+
ligne "$depot" off "$REPLY" "—"
|
|
166
|
+
continue ;;
|
|
167
|
+
auto | pr) ;;
|
|
168
|
+
*)
|
|
169
|
+
t bot.mode_invalid "$mode"; err "$REPLY"
|
|
170
|
+
ligne "$depot" "\`$mode\`" "✖ $REPLY" "—"
|
|
171
|
+
erreurs=$((erreurs + 1))
|
|
172
|
+
continue ;;
|
|
173
|
+
esac
|
|
174
|
+
|
|
175
|
+
clone="$TRAVAIL/${depot//\//_}"
|
|
176
|
+
if ! git clone -q --depth 1 -c credential.helper= -c 'credential.helper=!gh auth git-credential' \
|
|
177
|
+
"$GIT_URL/$depot.git" "$clone" 2>"$TRAVAIL/clone.err"; then
|
|
178
|
+
derniere_erreur_r "$(cat "$TRAVAIL/clone.err")"
|
|
179
|
+
err_t bot.clone_failed "$REPLY"
|
|
180
|
+
ligne "$depot" "$mode" "✖ $REPLY" "—"
|
|
181
|
+
erreurs=$((erreurs + 1))
|
|
182
|
+
continue
|
|
183
|
+
fi
|
|
184
|
+
git -C "$clone" config user.name "$NOM_BOT"
|
|
185
|
+
git -C "$clone" config user.email "$COURRIEL_BOT"
|
|
186
|
+
|
|
187
|
+
# Protection : vérifiée, appliquée seulement en auto (hors --dry-run)
|
|
188
|
+
verifications_r "$depot" "$conf"
|
|
189
|
+
checks="$REPLY"
|
|
190
|
+
if sortie="$(cd "$clone" && bash "$BIN/proteger" --repo "$depot" --checks "$checks" --verifier 2>&1)"; then
|
|
191
|
+
t bot.prot_ok
|
|
192
|
+
elif [ "$mode" = pr ] || [ -n "$DRY_RUN" ]; then
|
|
193
|
+
t bot.prot_stale
|
|
194
|
+
elif sortie="$(cd "$clone" && bash "$BIN/proteger" --repo "$depot" --checks "$checks" 2>&1)"; then
|
|
195
|
+
t bot.prot_applied
|
|
196
|
+
else
|
|
197
|
+
derniere_erreur_r "$sortie"
|
|
198
|
+
t bot.error "$REPLY"
|
|
199
|
+
erreurs=$((erreurs + 1))
|
|
200
|
+
fi
|
|
201
|
+
protection="$REPLY"
|
|
202
|
+
info "$protection"
|
|
203
|
+
|
|
204
|
+
# Migration : par PR seulement (dans le clone jetable en --dry-run)
|
|
205
|
+
options=(--pr)
|
|
206
|
+
[ -z "$DRY_RUN" ] || options=()
|
|
207
|
+
if sortie="$(cd "$clone" && bash "$BIN/migrer" ${options[@]+"${options[@]}"} 2>&1)"; then
|
|
208
|
+
case "$sortie" in
|
|
209
|
+
*"$MIG_RIEN"*) t bot.mig_none ;;
|
|
210
|
+
*"$MIG_OUVERTE"*) t bot.mig_open ;;
|
|
211
|
+
*) if [ -n "$DRY_RUN" ]; then t bot.mig_todo; else t bot.mig_pr; fi ;;
|
|
212
|
+
esac
|
|
213
|
+
else
|
|
214
|
+
derniere_erreur_r "$sortie"
|
|
215
|
+
t bot.error "$REPLY"
|
|
216
|
+
erreurs=$((erreurs + 1))
|
|
217
|
+
fi
|
|
218
|
+
migration="$REPLY"
|
|
219
|
+
info "$migration"
|
|
220
|
+
ligne "$depot" "$mode" "$protection" "$migration"
|
|
221
|
+
done
|
|
222
|
+
|
|
223
|
+
t bot.report_head "$(date -u '+%Y-%m-%d %H:%M UTC')"
|
|
224
|
+
entete="$REPLY"
|
|
225
|
+
[ -z "$DRY_RUN" ] || { t bot.report_dry; entete="$entete $REPLY"; }
|
|
226
|
+
rapport="## $TITRE_RAPPORT"$'\n\n'"$entete"$'\n\n'"$lignes"
|
|
227
|
+
echo
|
|
228
|
+
echo "$rapport"
|
|
229
|
+
[ -z "$RAPPORT" ] || echo "$rapport" >"$RAPPORT"
|
|
230
|
+
[ -z "${GITHUB_STEP_SUMMARY:-}" ] || echo "$rapport" >>"$GITHUB_STEP_SUMMARY"
|
|
231
|
+
[ "$erreurs" = 0 ] || { err_t bot.failed "$erreurs"; exit 1; }
|