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 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, "&amp;").replace(/"/g, "&quot;");
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,14p' "$ROOT/bin/repowarden" | sed 's/^# \{0,1\}//'; }
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)
@@ -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." ;;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repowarden",
3
- "version": "4.1.2",
3
+ "version": "4.2.0-next.2",
4
4
  "description": "Hooks git et CI multi-langages : messages de commit, nommage des branches, secrets, formatage, tests ciblés, code mort, releases.",
5
5
  "keywords": [
6
6
  "git-hooks",