repowarden 4.1.2 → 4.2.0-next.2
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 +146 -0
- package/bin/repowarden +3 -1
- package/bin/tickets +5 -0
- package/hooks/lib/i18n/en.sh +12 -0
- package/hooks/lib/i18n/fr.sh +12 -0
- package/package.json +1 -1
package/bin/app
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# GitHub App du dépôt : identité d'automatisation des workflows repowarden.
|
|
3
|
+
# Ce que fait le jeton des Actions ne déclenche aucun workflow (PR brouillon
|
|
4
|
+
# sans CI, merge automatique sans préversion…) ; un jeton de l'App, si.
|
|
5
|
+
#
|
|
6
|
+
# repowarden app init [--repo propriétaire/nom] [--nom NOM] [--administration]
|
|
7
|
+
#
|
|
8
|
+
# 1. création (flux « manifest » de GitHub) : une page locale envoie la
|
|
9
|
+
# description de l'App à GitHub, tu cliques sur « Create » ; permissions
|
|
10
|
+
# minimales (contenu, PR, tickets, statuts ; administration seulement avec
|
|
11
|
+
# --administration, pour appliquer la configuration depuis la CI) ;
|
|
12
|
+
# 2. identifiants rangés sans être affichés : variable REPOWARDEN_APP_CLIENT_ID,
|
|
13
|
+
# secret REPOWARDEN_APP_KEY (clé privée) ;
|
|
14
|
+
# 3. installation sur le dépôt : tu cliques sur « Install ».
|
|
15
|
+
# Relançable : une App déjà configurée n'est pas recréée. Demande gh et node.
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
19
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
20
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
21
|
+
|
|
22
|
+
aide() { sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; }
|
|
23
|
+
|
|
24
|
+
commande="${1:-}"
|
|
25
|
+
[ $# -eq 0 ] || shift
|
|
26
|
+
REPO="" NOM="" ADMIN=""
|
|
27
|
+
while [ $# -gt 0 ]; do
|
|
28
|
+
case "$1" in
|
|
29
|
+
--repo) shift; REPO="${1:?--repo attend propriétaire/nom}" ;;
|
|
30
|
+
--nom) shift; NOM="${1:?--nom attend un nom}" ;;
|
|
31
|
+
--administration) ADMIN=1 ;;
|
|
32
|
+
-h | --help) aide; exit 0 ;;
|
|
33
|
+
*) err_t install.unknown_arg "$1"; exit 1 ;;
|
|
34
|
+
esac
|
|
35
|
+
shift
|
|
36
|
+
done
|
|
37
|
+
if [ "$commande" != init ]; then
|
|
38
|
+
aide
|
|
39
|
+
[ -z "$commande" ] || [ "$commande" = -h ] || [ "$commande" = --help ]
|
|
40
|
+
exit
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
|
|
44
|
+
command -v node >/dev/null || { err_t app.no_node; exit 1; }
|
|
45
|
+
[ -n "$REPO" ] || REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner)"
|
|
46
|
+
owner="${REPO%%/*}"
|
|
47
|
+
NOM="${NOM:-repowarden-$owner}"
|
|
48
|
+
brand "app · $REPO"
|
|
49
|
+
|
|
50
|
+
# Ouvre une adresse dans le navigateur (REPOWARDEN_OPEN : commande à utiliser)
|
|
51
|
+
ouvrir() {
|
|
52
|
+
if [ -n "${REPOWARDEN_OPEN:-}" ]; then "$REPOWARDEN_OPEN" "$1"
|
|
53
|
+
elif command -v open >/dev/null; then open "$1"
|
|
54
|
+
elif command -v xdg-open >/dev/null; then xdg-open "$1" >/dev/null 2>&1
|
|
55
|
+
elif command -v cmd.exe >/dev/null; then cmd.exe /c start "" "$1"
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# 1. Création, si l'App n'est pas déjà configurée pour ce dépôt
|
|
60
|
+
step_t app.step_create
|
|
61
|
+
client_id="$(gh variable get REPOWARDEN_APP_CLIENT_ID --repo "$REPO" 2>/dev/null || true)"
|
|
62
|
+
if [ -n "$client_id" ] && grep -q '^REPOWARDEN_APP_KEY' <<<"$(gh secret list --repo "$REPO")"; then
|
|
63
|
+
ok_t app.exists "$client_id"
|
|
64
|
+
slug="$(gh variable get REPOWARDEN_APP_SLUG --repo "$REPO" 2>/dev/null || true)"
|
|
65
|
+
else
|
|
66
|
+
if [ "$(gh api "users/$owner" -q .type)" = Organization ]; then
|
|
67
|
+
cible="https://github.com/organizations/$owner/settings/apps/new"
|
|
68
|
+
else
|
|
69
|
+
cible="https://github.com/settings/apps/new"
|
|
70
|
+
fi
|
|
71
|
+
sortie="$(mktemp)"
|
|
72
|
+
trap 'rm -f "$sortie"' EXIT
|
|
73
|
+
# Page locale : envoie la description à GitHub (formulaire POST), puis
|
|
74
|
+
# 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 '
|
|
76
|
+
const http = require("http"), crypto = require("crypto");
|
|
77
|
+
const etat = crypto.randomBytes(16).toString("hex");
|
|
78
|
+
const srv = http.createServer((req, res) => {
|
|
79
|
+
const u = new URL(req.url, "http://127.0.0.1");
|
|
80
|
+
if (u.pathname === "/fin") {
|
|
81
|
+
if (u.searchParams.get("state") !== etat) { res.writeHead(400); res.end("état invalide"); return; }
|
|
82
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
83
|
+
res.end("<p>App créée : retourne au terminal.</p>");
|
|
84
|
+
console.log("CODE " + u.searchParams.get("code"));
|
|
85
|
+
res.on("finish", () => process.exit(0));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const port = srv.address().port;
|
|
89
|
+
const perms = { contents: "write", pull_requests: "write", issues: "write", statuses: "write", metadata: "read" };
|
|
90
|
+
if (process.env.ADMIN) perms.administration = "write";
|
|
91
|
+
const manifeste = JSON.stringify({
|
|
92
|
+
name: process.env.NOM, url: "https://github.com/" + process.env.REPO,
|
|
93
|
+
hook_attributes: { active: false }, public: false,
|
|
94
|
+
redirect_url: "http://127.0.0.1:" + port + "/fin", default_permissions: perms,
|
|
95
|
+
}).replace(/&/g, "&").replace(/"/g, """);
|
|
96
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
97
|
+
res.end("<form id=f method=post action=\"" + process.env.CIBLE + "?state=" + etat + "\">" +
|
|
98
|
+
"<input type=hidden name=manifest value=\"" + manifeste + "\"></form>" +
|
|
99
|
+
"<script>document.getElementById(\"f\").submit()</script>");
|
|
100
|
+
});
|
|
101
|
+
srv.listen(0, "127.0.0.1", () => console.log("PORT " + srv.address().port));
|
|
102
|
+
setTimeout(() => { console.log("DELAI"); process.exit(1); }, 15 * 60 * 1000);
|
|
103
|
+
' >"$sortie" &
|
|
104
|
+
serveur=$!
|
|
105
|
+
for _ in $(seq 1 50); do grep -q '^PORT ' "$sortie" && break; sleep 0.1; done
|
|
106
|
+
port="$(sed -n 's/^PORT //p' "$sortie")"
|
|
107
|
+
[ -n "$port" ] || { err_t app.server_failed; exit 1; }
|
|
108
|
+
info_t app.open "http://127.0.0.1:$port/"
|
|
109
|
+
ouvrir "http://127.0.0.1:$port/"
|
|
110
|
+
wait "$serveur" || true
|
|
111
|
+
code="$(sed -n 's/^CODE //p' "$sortie")"
|
|
112
|
+
[ -n "$code" ] || { err_t app.no_code; exit 1; }
|
|
113
|
+
# Conversion du code : identifiants de l'App, rangés sans être affichés
|
|
114
|
+
reponse="$(gh api -X POST "app-manifests/$code/conversions")"
|
|
115
|
+
client_id="$(node -p 'JSON.parse(require("fs").readFileSync(0, "utf8")).client_id' <<<"$reponse")"
|
|
116
|
+
slug="$(node -p 'JSON.parse(require("fs").readFileSync(0, "utf8")).slug' <<<"$reponse")"
|
|
117
|
+
node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(0, "utf8")).pem)' <<<"$reponse" |
|
|
118
|
+
gh secret set REPOWARDEN_APP_KEY --repo "$REPO"
|
|
119
|
+
reponse=""
|
|
120
|
+
gh variable set REPOWARDEN_APP_CLIENT_ID --repo "$REPO" --body "$client_id"
|
|
121
|
+
gh variable set REPOWARDEN_APP_SLUG --repo "$REPO" --body "$slug"
|
|
122
|
+
ok_t app.created "$slug"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# 2. Installation sur le dépôt (vérifiée depuis tes installations)
|
|
126
|
+
step_t app.step_install
|
|
127
|
+
installee() {
|
|
128
|
+
local id
|
|
129
|
+
id="$(gh api user/installations --paginate -q ".installations[] | select(.app_slug == \"$slug\") | .id" 2>/dev/null | sed -n 1p)"
|
|
130
|
+
[ -n "$id" ] || return 1
|
|
131
|
+
grep -qxF "$REPO" <<<"$(gh api "user/installations/$id/repositories" --paginate -q '.repositories[].full_name' 2>/dev/null)"
|
|
132
|
+
}
|
|
133
|
+
if [ -n "$slug" ] && ! installee; then
|
|
134
|
+
info_t app.open "https://github.com/apps/$slug/installations/new"
|
|
135
|
+
ouvrir "https://github.com/apps/$slug/installations/new"
|
|
136
|
+
for _ in $(seq 1 120); do installee && break; sleep 5; done
|
|
137
|
+
fi
|
|
138
|
+
if [ -n "$slug" ] && installee; then
|
|
139
|
+
ok_t app.installed "$slug" "$REPO"
|
|
140
|
+
else
|
|
141
|
+
attention_t app.not_installed "$slug" "$REPO"
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
echo
|
|
145
|
+
ok_t app.done
|
|
146
|
+
dim_t app.next
|
package/bin/repowarden
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# repowarden tickets init suivi des tickets (GitHub)
|
|
12
12
|
# repowarden proteger [options] protection des branches (GitHub)
|
|
13
13
|
# repowarden npm-publication publication npm automatique
|
|
14
|
+
# repowarden app init GitHub App des workflows (une fois)
|
|
14
15
|
# repowarden --version
|
|
15
16
|
set -euo pipefail
|
|
16
17
|
|
|
@@ -28,7 +29,7 @@ source "$ROOT/hooks/lib/ui.sh"
|
|
|
28
29
|
# shellcheck source=../hooks/lib/i18n.sh
|
|
29
30
|
source "$ROOT/hooks/lib/i18n.sh"
|
|
30
31
|
|
|
31
|
-
aide() { sed -n '2,
|
|
32
|
+
aide() { sed -n '2,15p' "$ROOT/bin/repowarden" | sed 's/^# \{0,1\}//'; }
|
|
32
33
|
|
|
33
34
|
commande="${1:-}"
|
|
34
35
|
[ $# -eq 0 ] || shift
|
|
@@ -49,6 +50,7 @@ case "$commande" in
|
|
|
49
50
|
ticket) exec bash "$ROOT/bin/ticket" "$@" ;;
|
|
50
51
|
proteger | protect) exec bash "$ROOT/bin/proteger" "$@" ;;
|
|
51
52
|
npm-publication | npm-publish) exec bash "$ROOT/bin/npm-publication" "$@" ;;
|
|
53
|
+
app) exec bash "$ROOT/bin/app" "$@" ;;
|
|
52
54
|
-v | --version | version)
|
|
53
55
|
repowarden_version_r "$ROOT"
|
|
54
56
|
echo "$REPLY"
|
package/bin/tickets
CHANGED
|
@@ -124,6 +124,11 @@ jobs:
|
|
|
124
124
|
tickets:
|
|
125
125
|
uses: SimBienvenueHoulBoumi/repowarden/.github/workflows/tickets.yml@v4
|
|
126
126
|
permissions: { contents: write, issues: write, pull-requests: write, statuses: write }
|
|
127
|
+
# GitHub App (repowarden app init) : ses actions déclenchent les autres workflows
|
|
128
|
+
with:
|
|
129
|
+
app-client-id: ${{ vars.REPOWARDEN_APP_CLIENT_ID }}
|
|
130
|
+
secrets:
|
|
131
|
+
app-key: ${{ secrets.REPOWARDEN_APP_KEY }}
|
|
127
132
|
YAML
|
|
128
133
|
|
|
129
134
|
# 3. Tickets et branches existants (relançable : rien n'est fait deux fois)
|
package/hooks/lib/i18n/en.sh
CHANGED
|
@@ -364,6 +364,18 @@ _msg_en() {
|
|
|
364
364
|
tk.merged) _M="Merged by PR #%s into %s: in pre-production, available in the next pre-release." ;;
|
|
365
365
|
tk.branch_created) _M="Ticket validated: its branch \`%s\` is created from \`%s\`.\n\nTo work on it:\n\n\`\`\`bash\nrepowarden ticket %s\n\`\`\`\n\n(or \`git fetch && git switch %s\`). The first push opens the draft PR." ;;
|
|
366
366
|
tk.branch_notice) _M="Branch %s created for ticket #%s" ;;
|
|
367
|
+
app.no_node) _M="node is required (local page creating the App): https://nodejs.org" ;;
|
|
368
|
+
app.step_create) _M="Creating the GitHub App" ;;
|
|
369
|
+
app.exists) _M="App already set up for this repository (client %s): nothing to recreate." ;;
|
|
370
|
+
app.server_failed) _M="Local page did not start." ;;
|
|
371
|
+
app.open) _M="Open %s then click GitHub's green button." ;;
|
|
372
|
+
app.no_code) _M="No answer from GitHub (creation cancelled, or 15 min timeout)." ;;
|
|
373
|
+
app.created) _M="App %s created; credentials stored (variable REPOWARDEN_APP_CLIENT_ID, secret REPOWARDEN_APP_KEY)." ;;
|
|
374
|
+
app.step_install) _M="Installing on the repository" ;;
|
|
375
|
+
app.installed) _M="App %s installed on %s." ;;
|
|
376
|
+
app.not_installed) _M="App %s not installed on %s yet: run repowarden app init again once installed." ;;
|
|
377
|
+
app.done) _M="repowarden workflows now use the App: what they do triggers the other workflows." ;;
|
|
378
|
+
app.next) _M="Without the App (variable and secret missing), they fall back to the Actions token." ;;
|
|
367
379
|
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." ;;
|
|
368
380
|
tk.sync.ready) _M="Ticket #%s assigned: local branch %s ready (repowarden ticket %s)." ;;
|
|
369
381
|
tk.sync.not_validated) _M="Ticket #%s assigned but not validated yet: no branch for now." ;;
|
package/hooks/lib/i18n/fr.sh
CHANGED
|
@@ -364,6 +364,18 @@ _msg_fr() {
|
|
|
364
364
|
tk.merged) _M="Mergé par la PR #%s dans %s : en préprod, disponible dans la prochaine préversion." ;;
|
|
365
365
|
tk.branch_created) _M="Ticket validé : sa branche \`%s\` est créée depuis \`%s\`.\n\nPour y travailler :\n\n\`\`\`bash\nrepowarden ticket %s\n\`\`\`\n\n(ou \`git fetch && git switch %s\`). Le premier push ouvre la PR en brouillon." ;;
|
|
366
366
|
tk.branch_notice) _M="Branche %s créée pour le ticket #%s" ;;
|
|
367
|
+
app.no_node) _M="node est nécessaire (page locale de création de l'App) : https://nodejs.org" ;;
|
|
368
|
+
app.step_create) _M="Création de la GitHub App" ;;
|
|
369
|
+
app.exists) _M="App déjà configurée pour ce dépôt (client %s) : rien à recréer." ;;
|
|
370
|
+
app.server_failed) _M="Page locale non démarrée." ;;
|
|
371
|
+
app.open) _M="Ouvre %s puis clique sur le bouton vert de GitHub." ;;
|
|
372
|
+
app.no_code) _M="Aucune réponse de GitHub (création annulée, ou délai de 15 min dépassé)." ;;
|
|
373
|
+
app.created) _M="App %s créée ; identifiants rangés (variable REPOWARDEN_APP_CLIENT_ID, secret REPOWARDEN_APP_KEY)." ;;
|
|
374
|
+
app.step_install) _M="Installation sur le dépôt" ;;
|
|
375
|
+
app.installed) _M="App %s installée sur %s." ;;
|
|
376
|
+
app.not_installed) _M="App %s pas encore installée sur %s : relance repowarden app init une fois installée." ;;
|
|
377
|
+
app.done) _M="Les workflows repowarden utilisent désormais l'App : ce qu'ils font déclenche les autres workflows." ;;
|
|
378
|
+
app.next) _M="Sans l'App (variable et secret absents), ils reprennent le jeton des Actions." ;;
|
|
367
379
|
tk.reopened) _M="Rouvert : fermé par GitHub au merge de #%s dans \`%s\`, ce n'est pas encore la production. En préprod ; il sera fermé à la release." ;;
|
|
368
380
|
tk.sync.ready) _M="Ticket #%s assigné : branche locale %s prête (repowarden ticket %s)." ;;
|
|
369
381
|
tk.sync.not_validated) _M="Ticket #%s assigné mais pas encore validé : pas de branche pour l'instant." ;;
|
package/package.json
CHANGED