Un check di changelog per GitHub Actions
5 min di lettura
Ogni team che mantiene un changelog a mano ha avuto la stessa conversazione dopo lo stesso incidente: un rilascio è uscito senza voce, qualcuno chiede perché, e la risposta onesta è che la persona che l’avrebbe scritta stava andando veloce e il passaggio del changelog viveva solo nella memoria. Automazione del changelog copre cosa una pipeline può automatizzare in sicurezza e cosa ha ancora bisogno di una persona; un check di changelog in CI è l’altra metà di questo problema, perché automatizzare la scrittura non aiuta se nessuno è obbligato a innescarla in primo luogo. GitHub Actions è dove la maggior parte dei team esegue già i controlli delle proprie pull request, quindi è lì che vive anche questo.
Perché “chiediamo alle persone di aggiungere una voce” fallisce con un modello prevedibile?
Perché compete per l’attenzione con tutto il resto in una pull request, ed è l’unica parte senza conseguenza immediata nel saltarla. I test falliscono rumorosamente e bloccano il merge. Una voce di changelog mancante non blocca niente, quindi perde nel momento in cui qualcuno ha fretta, che nella pratica è la maggior parte delle volte. Una politica imposta dalla memoria si degrada esattamente al ritmo che ci si aspetterebbe: bene per le prime settimane dopo che tutti sono d’accordo, poi silenziosamente abbandonata appena la persona a cui importava va in vacanza o cambia team.
Cosa verifica davvero un check CI per una voce di changelog?
Non la qualità della scrittura, solo che una voce esista e sia ben formata, che è l’ambito giusto per un check di changelog che gira in CI invece che nella testa di una persona. Una forma comune: il check guarda il diff della PR e richiede o un nuovo file in una directory di changeset (il modello che Changesets e strumenti simili usano) o una riga modificata in un file di changelog, e fa fallire la build se nessuno dei due esiste. La revisione di cosa dice davvero la voce accade ancora dove è sempre accaduta, nel code review, perché quel giudizio non appartiene a uno script.
| Cosa verifica il check CI | Cosa non verifica |
|---|---|
| Esiste un changeset o una riga di changelog nel diff | Se la formulazione è chiara |
| La voce fa riferimento al pacchetto giusto, in un monorepo | Se il cambiamento merita davvero una voce |
| Il file è sintatticamente valido (front matter, forma JSON) | Se la voce è onesta sull’impatto |
Ogni PR ne ha bisogno, o alcuni cambiamenti sono esenti?
Alcuni sono esenti, e la lista delle eccezioni è dove questi sistemi vengono davvero costruiti o
abbandonati. Un aggiornamento di dipendenza senza effetto visibile, un cambiamento solo di test,
un refactor interno senza cambiamento di comportamento: nessuno di questi dovrebbe costringere chi
contribuisce a inventare una voce di changelog per qualcosa a cui nessuno che legge il changelog
tiene. Il modello che funziona è un’etichetta o un flag che chi contribuisce può applicare
(no-changelog-needed) e che soddisfa il check CI senza file, revisionato da chi approva la PR,
così l’eccezione stessa passa lo stesso controllo che passerebbe una voce.
Cosa succede alle eccezioni legittime, come un hotfix urgente?
Il gate appartiene al merge, non al deploy: un hotfix sotto vera pressione di tempo può fare merge con una voce segnaposto o un ticket di follow-up, purché il check CI sia soddisfatto dall’intenzione invece che solo da un paragrafo finito; alcuni team accettano uno stub di una riga che una maintainer rifinisce prima del prossimo taglio di rilascio. Ciò che il gate non dovrebbe mai permettere è saltare il passaggio in silenzio, perché uno stub che viene dimenticato è un fallimento minore di una voce mai esistita, e uno stub lascia almeno una traccia che qualcuno può trovare dopo.
# .github/workflows/changelog-check.yml
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
changelog:
if: >-
!contains(github.event.pull_request.labels.*.name,
'no-changelog-needed')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the diff needs the base branch
- name: Require changelog entry
run: |
base="origin/${{ github.base_ref }}"
if ! git diff --name-only "$base"...HEAD \
| grep -q '^\.changeset/'; then
echo "No changeset. Add one, or have a maintainer"
echo "apply the no-changelog-needed label."
exit 1
fi
Come si fa a sapere che il check stesso è corretto prima che inizi a bloccare PR reali?
Aprite prima una pull request di prova su un branch usa-e-getta: una con un changeset, una senza,
e una con l’etichetta di eccezione, e confermate che tutte e tre ottengono l’esito atteso prima che
il check si applichi al lavoro di qualcun altro. Un check di changelog che fallisce aperto, facendo
passare ogni PR perché una condizione è stata scritta al contrario, è peggio che non avere nessun
check, perché sembra una copertura che non esiste davvero. workflow_dispatch sullo stesso file,
eseguito manualmente contro un paio di PR recenti già mergiate, cattura la maggior parte di questi
errori senza bisogno di una pull request live.
La stessa idea funziona anche fuori da GitHub Actions?
La forma si porta dietro, cambia solo la sintassi. GitLab CI esprime la stessa regola come un blocco
rules di un job che controlla $CI_MERGE_REQUEST_LABELS invece di un if di GitHub Actions, e
un’approvazione obbligatoria della merge request può sostituire il passaggio di revisione
dell’eccezione. Il check descritto in questo articolo è su GitHub Actions perché è la piattaforma su
cui già si trova la maggior parte di chi lo legge, ma il requisito di fondo, un gate verificato da
una macchina invece di una convenzione chiesta a voce, è lo stesso ovunque giri una CI prima di un
merge.
Funziona allo stesso modo in un monorepo?
Serve un pezzo in più: per quale pacchetto è la voce. Changelog di monorepo copre perché un singolo file per l’intero repo smette di funzionare appena i pacchetti vengono rilasciati indipendentemente; il check CI eredita lo stesso requisito; un changeset che non nomina un pacchetto non è una prova utile che il changelog giusto si aggiornerà, solo che qualche file è cambiato da qualche parte nel diff. Gli strumenti costruiti per questo (Changesets è quello comune nell’ecosistema JavaScript) chiedono a chi contribuisce di scegliere il pacchetto interessato e un bump semver nello stesso momento in cui il changeset viene creato, così il check CI ottiene entrambi i pezzi gratis invece di inferirli dopo.
FAQ
Il check CI dovrebbe bloccare il merge, o solo avvisare? Bloccare. Un avviso è funzionalmente identico a chiedere gentilmente, che è proprio la cosa già fallita. L’etichetta di eccezione esiste proprio perché un caso genuino di solo-avviso abbia comunque un percorso legittimo attraverso lo stesso gate rigido.
Chi revisiona se un’etichetta di eccezione è stata applicata correttamente? Chi approva la pull request, come parte della revisione che sta già facendo comunque. L’etichetta non dovrebbe mai essere auto-applicata e non revisionata, o diventa la stessa scappatoia silenziosa che il gate doveva chiudere.
Imporlo in CI sostituisce il bisogno di una pipeline di automazione del changelog? No, la alimenta. Automazione del changelog copre come trasformare voci strutturate in una pagina, un feed e un’email; il check CI è ciò che garantisce che quelle voci strutturate esistano per automatizzarle in primo luogo.
Qual è la versione più piccola di questo che vale la pena costruire per prima? Un singolo check che fallisce se nessun file è cambiato sotto una directory di changelog designata, con un’etichetta di eccezione. Il routing per pacchetto e l’inferenza semver per un monorepo possono arrivare dopo; l’abitudine centrale, una voce esiste o qualcuno ha detto esplicitamente che non serve, è quella che vale la pena avere fin dal primo giorno.
Le affermazioni tecniche di questo articolo non sono state verificate in modo indipendente. Se qualcosa non è corretto, faccelo sapere e lo correggeremo.