Inginerie

Un check de changelog pentru GitHub Actions

5 min de citit

Fiecare echipă care ține un changelog manual a avut aceeași conversație după același incident: o lansare a ieșit fără intrare, cineva întreabă de ce, iar răspunsul sincer e că persoana care ar fi scris-o mergea repede și pasul de changelog trăia doar în memorie. Automatizarea changelog-ului acoperă ce poate automatiza sigur un pipeline și ce mai are nevoie de o persoană; un check de changelog în CI e cealaltă jumătate a acestei probleme, pentru că automatizarea scrierii nu ajută dacă nimeni nu e obligat s-o declanșeze în primul rând. GitHub Actions e locul unde majoritatea echipelor rulează deja verificările pentru pull request-uri, deci acolo trăiește și acesta.

De ce eșuează “îi rugăm pe oameni să adauge o intrare” după un tipar previzibil?

Pentru că concurează pentru atenție cu tot restul dintr-un pull request, și e singura parte fără consecință imediată dacă e sărită. Testele eșuează zgomotos și blochează merge-ul. O intrare lipsă din changelog nu blochează nimic, deci pierde de îndată ce cineva se grăbește, ceea ce în practică e majoritatea timpului. O politică impusă prin memorie se degradează exact în ritmul la care te-ai aștepta: bine primele câteva săptămâni după ce toată lumea e de acord, apoi abandonată tăcut de îndată ce persoana căreia îi păsa pleacă în vacanță sau schimbă echipa.

Ce verifică de fapt un check CI pentru o intrare de changelog?

Nu calitatea redactării, doar că o intrare există și e bine formată, ceea ce e domeniul corect pentru un check de changelog care rulează în CI și nu în capul cuiva. O formă comună: check-ul se uită la diff-ul PR-ului și cere fie un fișier nou într-un director de changeset-uri (tiparul folosit de Changesets și unelte similare) fie o linie modificată într-un fișier de changelog, și face build-ul să eșueze dacă niciunul nu există. Revizuirea a ceea ce spune de fapt intrarea continuă să se întâmple acolo unde s-a întâmplat mereu, în code review, pentru că acea judecată nu aparține unui script.

Ce verifică check-ul CICe nu verifică
Există un changeset sau o linie de changelog în diffDacă formularea e clară
Intrarea referențiază pachetul corect, într-un monorepoDacă schimbarea chiar merită o intrare
Fișierul e valid sintactic (front matter, formă JSON)Dacă intrarea e sinceră despre impact

Are fiecare PR nevoie de una, sau unele schimbări sunt scutite?

Unele sunt scutite, iar lista de scutiri e locul unde astfel de sisteme se construiesc sau se abandonează cu adevărat. O actualizare de dependență fără efect vizibil, o schimbare doar de teste, un refactor intern fără schimbare de comportament: niciuna dintre acestea n-ar trebui să forțeze o contribuitoare să inventeze o intrare de changelog pentru ceva de care nu-i pasă nimănui care citește changelog-ul. Tiparul care funcționează e o etichetă sau un flag pe care o contribuitoare îl poate aplica (no-changelog-needed) și care satisface check-ul CI fără fișier, revizuit de cine aprobă PR-ul, astfel încât scutirea însăși trece prin aceeași verificare prin care ar trece o intrare.

Ce se întâmplă cu excepțiile legitime, precum un hotfix urgent?

Poarta aparține merge-ului, nu deploy-ului: un hotfix sub presiune reală de timp poate face merge cu o intrare provizorie sau un tichet de urmărire, cu condiția ca check-ul CI să fie satisfăcut de intenție în loc de doar un paragraf terminat; unele echipe acceptă un stub de o linie pe care o întreținătoare îl șlefuiește înainte de următoarea tăietură de lansare. Ce n-ar trebui să permită niciodată poarta e sărirea pasului în tăcere, pentru că un stub uitat e un eșec mai mic decât o intrare care n-a existat niciodată, iar un stub lasă cel puțin o urmă pe care cineva o poate găsi mai târziu.

# .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 # diff-ul are nevoie de ramura de bază
      - 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

De unde știți că check-ul în sine e corect înainte să înceapă să blocheze PR-uri reale?

Deschideți întâi un pull request de probă pe o ramură de unică folosință: unul cu un changeset, unul fără, și unul cu eticheta de scutire, și confirmați că toate trei primesc rezultatul așteptat înainte ca check-ul să se aplice muncii altcuiva. Un check de changelog care eșuează deschis, trecând orice PR pentru că o condiție a fost scrisă invers, e mai rău decât lipsa oricărui check, pentru că arată ca o acoperire care de fapt nu există. workflow_dispatch pe același fișier, rulat manual pe câteva PR-uri recente deja unite, prinde majoritatea acestor greșeli fără să fie nevoie deloc de un pull request real.

Funcționează aceeași idee și în afara GitHub Actions?

Forma se transferă, doar sintaxa se schimbă. GitLab CI exprimă aceeași regulă ca un bloc rules al unui job care verifică $CI_MERGE_REQUEST_LABELS în loc de un if din GitHub Actions, iar o aprobare obligatorie a merge request-ului poate ține loc de pasul de revizuire a scutirii. Check-ul descris în acest articol e din GitHub Actions pentru că e platforma pe care majoritatea celor care îl citesc deja se află, dar cerința de bază, o poartă verificată de mașină în loc de o convenție cerută frumos, e aceeași oriunde rulează CI înainte de un merge.

Funcționează la fel într-un monorepo?

Are nevoie de o piesă în plus: pentru care pachet e intrarea. Changelog-uri de monorepo acoperă de ce un singur fișier pentru tot repo-ul încetează să funcționeze de îndată ce pachetele sunt lansate independent; check-ul CI moștenește aceeași cerință; un changeset care nu numește un pachet nu e o dovadă utilă că changelog-ul corect se va actualiza, doar că un fișier s-a schimbat undeva în diff. Uneltele construite pentru asta (Changesets e cea comună în ecosistemul JavaScript) cer contribuitoarei să aleagă pachetul afectat și o creștere semver chiar în momentul în care changeset-ul e creat, deci check-ul CI primește ambele piese gratis în loc să le deducă mai târziu.

FAQ

Ar trebui check-ul CI să blocheze merge-ul, sau doar să avertizeze? Să blocheze. Un avertisment e funcțional identic cu a cere frumos, ceea ce a eșuat deja. Eticheta de scutire există tocmai pentru ca un caz autentic de doar-avertizare să aibă totuși o cale legitimă prin aceeași poartă strictă.

Cine verifică dacă o etichetă de scutire a fost aplicată corect? Cine aprobă pull request-ul, ca parte a revizuirii pe care oricum o face deja. Eticheta n-ar trebui niciodată aplicată de sine stătător și nerevizuită, altfel devine aceeași portiță tăcută pe care poarta trebuia s-o închidă.

Impunerea asta în CI înlocuiește nevoia de un pipeline de automatizare a changelog-ului? Nu, îl hrănește. Automatizarea changelog-ului acoperă transformarea intrărilor structurate într-o pagină, un flux și un e-mail; check-ul CI e ce garantează că acele intrări structurate există în primul rând ca să fie automatizate.

Care e cea mai mică versiune a asta care merită construită prima? Un singur check care eșuează dacă niciun fișier nu s-a schimbat sub un director de changelog desemnat, cu o etichetă de scutire. Rutarea pe pachet și deducerea semver pentru un monorepo pot veni mai târziu; obiceiul central, o intrare există sau cineva a spus explicit că nu e nevoie de una, e ce merită avut din prima zi.


Afirmațiile tehnice din acest articol nu au fost verificate independent. Dacă ceva nu e corect, spune-ne și vom corecta.

Pe changeloop: Comparație instrumente changelog, Generator de changelog

changeloop
Echipa care construiește un changelog care închide bucla. Utilizatorii cer ceva, echipa ta livrează, cel care a cerut află.