-
Notifications
You must be signed in to change notification settings - Fork 1
262 lines (237 loc) · 13.6 KB
/
Copy pathrelease.yml
File metadata and controls
262 lines (237 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# Publication d'une version d'OpenScale — docs/02-architecture.md §17.
#
# CE QUE CE WORKFLOW SUPPRIME : le téléversement à la main. Poser un tag suffit, et les
# trois archives de §17.2 apparaissent dans la page « Releases » du dépôt, avec leurs
# empreintes. Personne n'a plus à se demander si le fichier publié est bien celui qui a
# été construit, ni sur quelle machine il l'a été.
#
# git tag -a 2.0.0 -m "Version 2.0.0"
# git push origin 2.0.0
#
# Le déclenchement est le TAG et non la branche : une version est un point figé de
# l'histoire, pas l'état d'une branche au moment où quelqu'un a cliqué.
name: Release
on:
push:
tags:
# Toutes les formes qu'une version prend en pratique : 2.0.0 et v2.0.0, 0.1 et v0.1,
# 2.0.1-rc1 et v0.1-beta. Le préfixe « v » est la convention la plus répandue de
# l'écosystème git, et la première version publiée de ce dépôt s'appelait v0.1 : un
# filtre qui ne l'acceptait pas n'a rien déclenché du tout, et la Release est restée
# sans archives sans qu'aucune exécution n'apparaisse pour l'expliquer.
#
# Ce qui reste exclu est ce qui doit l'être : `git tag banc-de-test` et
# `git tag avant-migration` sont des gestes de travail et ne publient pas.
- "[0-9]*.[0-9]*"
- "v[0-9]*.[0-9]*"
# Pour reconstruire une version sans re-poser son tag, ou pour essayer le workflow.
workflow_dispatch:
inputs:
tag:
description: "Tag à construire (il doit exister)"
required: true
permissions:
# Écrire est ce qui permet de créer la Release et d'y attacher les archives. C'est la
# SEULE permission élevée de ce dépôt, et elle ne vaut que pour ce workflow.
#
# C'EST AUSSI POURQUOI LES ACTIONS D'ICI SONT ÉPINGLÉES SUR UN SHA DE COMMIT (voir la
# note de ci.yml). `softprops/action-gh-release` est une action TIERCE, et elle tourne
# dans le seul job du dépôt qui reçoive `contents: write` et le `github.token`. Sur un
# tag mobile, quiconque peut déplacer `v2` publie ce qu'il veut sous le nom de la
# coopérative — et ce que les postes installent ensuite vient de cette page-là.
contents: write
# Lire les exécutions de l'intégration continue, pour savoir si la révision du tag a
# déjà été prouvée. Lecture seule, et c'est la seule chose qu'elle sert à faire.
actions: read
env:
# La même version qu'en intégration continue, et pour la même raison : les golden de
# rendu de §7.4 ne doivent pas dépendre de la chaîne d'outils.
GO_VERSION: "1.26.5"
NODE_VERSION: "22"
jobs:
release:
name: Archives et publication
runs-on: ubuntu-latest
steps:
# LA PREMIÈRE ÉTAPE VALIDE LE NOM DU TAG, avant tout checkout et toute construction.
#
# Un nom de tag git accepte presque tout — guillemets, points-virgules, substitution
# de commande — et `workflow_dispatch` laisse quiconque a le droit d'écriture en
# fournir un à la main. Le filtre `on: push: tags` ne protège pas ce chemin-là, et il
# est de toute façon un glob : il n'exclut pas les métacaractères.
#
# La valeur passe par l'ENVIRONNEMENT et non par une interpolation dans la ligne de
# commande : `${{ }}` est remplacé par GitHub AVANT que le shell ne lise la ligne, si
# bien qu'un tag contenant `"; curl … | sh; #` deviendrait une commande. Une variable
# d'environnement, elle, n'est jamais relue comme du code.
- name: Le nom du tag est bien une version
env:
TAG: ${{ github.event.inputs.tag || github.ref_name }}
run: |
case "$TAG" in
v[0-9]*.[0-9]*|[0-9]*.[0-9]*) ;;
*) echo "Tag « $TAG » : ce n'est pas une forme de version."; exit 1 ;;
esac
# Seuls les caractères d'un numéro de version sont admis. Tout le reste — espace,
# guillemet, point-virgule, barre verticale, apostrophe inverse, dollar — est
# refusé ici plutôt que d'être échappé plus loin, parce qu'un échappement se perd
# au premier refactoring et qu'aucune version légitime n'en a besoin.
if printf '%s' "$TAG" | grep -qvE '^v?[0-9]+(\.[0-9]+){1,2}(-[A-Za-z0-9.]+)?$'; then
echo "Tag « $TAG » : caractères non admis dans un numéro de version."
exit 1
fi
echo "tag validé : $TAG"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.inputs.tag || github.ref }}
# fetch-depth: 0 EST OBLIGATOIRE ICI, et son absence ne se voit pas tout de
# suite. Le clone par défaut est superficiel et sans tags, si bien que le
# `git describe --tags` dont le Makefile tire VERSION répond par un SHA : les
# archives sortiraient sous le nom openscale-a1b2c3d-linux-amd64.zip alors que
# la Release s'appelle 2.0.0, et personne ne saurait dire six mois plus tard ce
# que contenait la version publiée.
fetch-depth: 0
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ env.GO_VERSION }}
check-latest: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: web/package-lock.json
# L'écran client est RECONSTRUIT, jamais pris tel qu'il est commité.
#
# internal/web/dist est dans le dépôt pour que `go build` fonctionne sur une machine
# sans Node (§14.1), et c'est un cache : rien ne garantit qu'il corresponde aux
# sources du tag. Une version qui embarquerait un écran d'il y a trois commits est
# la panne la plus difficile à croire, parce que le binaire est juste et l'écran ne
# l'est pas.
- name: Construire l'écran client
run: make front
- name: Vérifier que le dist commité était à jour
# Si cette étape trouve une différence, la version publiée est correcte — elle
# vient d'être reconstruite — mais le dépôt porte un dist périmé, et le prochain
# `go build` sans Node produira un binaire différent. C'est un AVERTISSEMENT et
# non un échec : la release, elle, est bonne.
run: |
if ! git diff --quiet -- internal/web/dist; then
echo "::warning::internal/web/dist committed in the repository was stale; the release embeds the freshly built screen. Run 'make front' and commit the result."
git diff --stat -- internal/web/dist
fi
# LA SUITE N'EST PAS REJOUÉE QUAND ELLE VIENT DE PASSER SUR CETTE RÉVISION.
#
# « Une version n'est pas un endroit où gagner du temps » : c'est vrai, et ce n'est
# pas ce qui se passe ici. Le tag pointe un commit de `main`, l'intégration continue
# a tourné dessus, et la rejouer n'est pas une seconde preuve — c'est la même, sur
# les mêmes octets, une demi-heure plus tard. C'en est en revanche une seconde
# OCCASION D'ÉCHOUER : v0.4 est partie sans archives parce qu'un test de
# synchronisation est tombé ici après être passé là.
#
# Quand aucune exécution verte ne couvre la révision — un tag posé hors de `main`,
# ou une CI qui n'a jamais tourné — la suite est jouée ici. Le chemin sûr reste le
# chemin par défaut ; le chemin rapide demande une preuve.
- name: L'intégration continue est-elle verte sur cette révision ?
id: proven
env:
GH_TOKEN: ${{ github.token }}
run: |
revision="$(git rev-parse HEAD)"
green="$(gh run list --workflow ci.yml --commit "$revision" --status completed \
--json conclusion --jq '[.[] | select(.conclusion == "success")] | length')"
if [ "${green:-0}" -gt 0 ]; then
echo "skip=1" >> "$GITHUB_OUTPUT"
echo "CI verte sur $revision : les archives se fabriquent sans rejouer la suite."
else
echo "skip=0" >> "$GITHUB_OUTPUT"
echo "::notice::aucune exécution verte de la CI sur $revision — la suite est jouée ici."
fi
#
# VERSION EST IMPOSÉE, et ne vient pas du `git describe` du Makefile. Deux raisons,
# et la seconde a réellement bloqué une publication :
#
# 1. dans une release, la version EST le tag. La dériver de l'histoire ajoute une
# étape qui peut se tromper là où il n'y a rien à déduire ;
# 2. `git describe --dirty` suffixe « -dirty » dès que l'arbre de travail est
# modifié — et l'étape précédente reconstruit l'écran client, dont les noms de
# fichiers portent une empreinte du contenu. L'arbre est donc SALE au moment où
# les archives se fabriquent, elles sortiraient sous le nom
# openscale-v0.1-dirty-windows-amd64.zip, et le contrôle de cohérence
# ci-dessous refuserait de publier une version pourtant correcte.
- name: Fabriquer les trois archives
env:
TAG: ${{ github.event.inputs.tag || github.ref_name }}
SKIP: ${{ steps.proven.outputs.skip }}
run: make release VERSION="$TAG" SKIP_TESTS="$SKIP"
- name: Ce qui va être publié
run: |
ls -l dist/*.zip
echo '--- empreintes des binaires ---'
cat dist/SHA256SUMS
# L'empreinte des ARCHIVES, qui n'est pas celle des binaires : SHA256SUMS de dist
# porte les trois exécutables, et c'est le zip que le bénévole télécharge. Sans ce
# fichier, personne ne peut vérifier ce qu'il a reçu.
- name: Empreintes des archives
run: |
cd dist && sha256sum *.zip > SHA256SUMS-archives.txt
cat SHA256SUMS-archives.txt
# Le nom du tag doit se retrouver dans le nom des archives. Sans ce contrôle, un
# checkout sans tags publierait sous un nom de révision, et le seul symptôme serait
# un nom de fichier inattendu dans la page Releases — que personne ne relit.
- name: Le nom des archives porte bien la version
env:
TAG: ${{ github.event.inputs.tag || github.ref_name }}
run: |
version="$TAG"
if ! ls dist/openscale-"$version"-windows-amd64.zip >/dev/null 2>&1; then
echo "Les archives ne portent pas la version « $version » :"
ls -1 dist/*.zip
echo "git describe rend : $(git describe --tags --always --dirty)"
exit 1
fi
echo "version publiée : $version"
- name: Publier la Release
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
name: OpenScale ${{ github.event.inputs.tag || github.ref_name }}
# Les notes sont générées par GitHub à partir des commits depuis le tag
# précédent. Les messages de commit de ce dépôt sont écrits pour être lus, donc
# elles valent quelque chose sans travail supplémentaire.
generate_release_notes: true
# Une version qui porte un suffixe — 2.0.1-rc1 — est une préversion, et le dire
# évite qu'un bénévole installe une release candidate en croyant faire une mise
# à jour ordinaire.
prerelease: ${{ contains(github.event.inputs.tag || github.ref_name, '-') }}
body: |
## Installer un poste
Téléchargez l'archive de votre plateforme, puis suivez
[`INSTALLATION.md`](https://github.com/${{ github.repository }}/blob/${{ github.event.inputs.tag || github.ref_name }}/INSTALLATION.md)
— il est écrit pour un bénévole et compte les étapes une par une.
| Plateforme | Archive |
|---|---|
| Windows (les postes de la coopérative) | `openscale-${{ github.event.inputs.tag || github.ref_name }}-windows-amd64.zip` |
| Linux 64 bits | `openscale-${{ github.event.inputs.tag || github.ref_name }}-linux-amd64.zip` |
| Linux ARM 64 bits (Raspberry Pi 4 et suivants) | `openscale-${{ github.event.inputs.tag || github.ref_name }}-linux-arm64.zip` |
Chaque archive contient le binaire, les scripts d'installation et de mise à
jour, la configuration livrée **sans le bloc matériel**, `INSTALLATION.md`,
`TROUBLESHOOTING.md`, la licence et `SHA256SUMS`.
**Vérifier ce que vous avez téléchargé** — les empreintes sont dans
`SHA256SUMS-archives.txt`, publié ci-dessous :
```
sha256sum -c SHA256SUMS-archives.txt
```
Sous Windows : `Get-FileHash openscale-*.zip -Algorithm SHA256`
## Essayer sans balance et sans imprimante
Le [démarrage rapide du README](https://github.com/${{ github.repository }}#essayer-sans-balance-et-sans-imprimante)
fait tourner un poste complet sur une machine ordinaire, avec un catalogue de
démonstration : la grille se remplit, et l'étiquette est écrite dans un
fichier au lieu d'être imprimée.
## Ce que cette version n'a pas encore vu
Aucune étiquette n'est sortie d'une imprimante SATO réelle et aucun octet n'est
venu d'une balance GRAM réelle : les tests qui l'exigent portent
`//go:build hardware` et attendent le banc. Voir
[`SUIVI.md`](https://github.com/${{ github.repository }}/blob/${{ github.event.inputs.tag || github.ref_name }}/SUIVI.md).
files: |
dist/*.zip
dist/SHA256SUMS-archives.txt