Um check de changelog para o GitHub Actions
6 min de leitura
Todo time que mantém um changelog manualmente já teve a mesma conversa depois do mesmo incidente: um release saiu sem entrada, alguém pergunta por quê, e a resposta honesta é que a pessoa que a teria escrito estava correndo e o passo do changelog só existia na memória. Automação de changelog cobre o que um pipeline pode automatizar com segurança e o que ainda precisa de uma pessoa; um check de changelog no CI é a outra metade desse problema, porque automatizar a escrita não ajuda se ninguém é obrigado a acioná-la em primeiro lugar. O GitHub Actions é onde a maioria dos times já roda os checks dos pull requests, então é lá que este também vive.
Por que “pedimos para as pessoas adicionarem uma entrada” falha num padrão previsível?
Porque isso compete por atenção com tudo mais em um pull request, e é a única parte sem consequência imediata ao pular. Testes falham alto e bloqueiam o merge. Uma entrada de changelog faltando não bloqueia nada, então perde assim que alguém está com pressa, o que na prática é a maior parte do tempo. Uma política imposta pela memória se degrada exatamente no ritmo que se esperaria: bem nas primeiras semanas depois de todo mundo concordar, depois abandonada em silêncio assim que a pessoa que se importava sai de férias ou muda de time.
O que um check de CI para uma entrada de changelog realmente verifica?
Não a qualidade da escrita, só que a entrada existe e está bem formada, o que é o escopo certo para um check de changelog que roda no CI em vez de na cabeça de uma pessoa. Uma forma comum: o check olha o diff do PR e exige ou um arquivo novo em um diretório de changesets (o padrão que Changesets e ferramentas parecidas usam) ou uma linha modificada em um arquivo de changelog, e falha o build se nenhum dos dois existir. A revisão do que a entrada realmente diz continua acontecendo onde sempre aconteceu, no code review, porque esse julgamento não pertence a um script.
| O que o check de CI verifica | O que ele não verifica |
|---|---|
| Existe um changeset ou linha de changelog no diff | Se a redação está clara |
| A entrada referencia o pacote certo, em um monorepo | Se a mudança sequer merece uma entrada |
| O arquivo é sintaticamente válido (frontmatter, forma JSON) | Se a entrada é honesta sobre o impacto |
Todo PR precisa de uma, ou algumas mudanças são isentas?
Algumas são isentas, e a lista de isenções é onde esses sistemas realmente se constroem ou são abandonados. Um bump de dependência sem efeito visível, uma mudança só de testes, um refactor interno sem mudança de comportamento: nenhum desses deveria forçar quem contribui a inventar uma entrada de changelog para algo que não importa para ninguém que lê o changelog. O padrão que funciona é uma etiqueta ou flag que quem contribui pode aplicar (no-changelog-needed) e que satisfaz o check de CI sem arquivo, revisada por quem aprova o PR, para que a própria isenção passe pelo mesmo escrutínio que uma entrada passaria.
O que acontece com exceções legítimas, como um hotfix urgente?
O gate pertence ao merge, não ao deploy: um hotfix sob pressão real de tempo pode mergear com uma entrada provisória ou um ticket de acompanhamento, desde que o check de CI seja satisfeito pela intenção em vez de só um parágrafo terminado; alguns times aceitam um stub de uma linha que uma mantenedora aprimora antes do próximo corte de release. O que o gate nunca deveria permitir é pular o passo em silêncio, porque um stub esquecido é uma falha menor do que uma entrada que nunca existiu, e um stub pelo menos deixa um rastro que alguém pode encontrar depois.
# .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
Como vocês sabem que o check em si está correto antes que ele comece a bloquear PRs de verdade?
Abram primeiro um pull request de teste contra uma branch descartável: um com changeset, um sem, e um carregando a etiqueta de isenção, e confirmem que os três dão o resultado esperado antes que o check passe a valer para o trabalho de qualquer outra pessoa. Um check de changelog que falha aberto, aprovando todo PR porque uma condição foi escrita ao contrário, é pior do que não ter check nenhum, porque parece cobertura que não existe. Um workflow_dispatch no mesmo arquivo, rodado manualmente contra alguns PRs recentes já mergeados, pega a maioria desses erros sem precisar de um pull request ao vivo.
A mesma ideia funciona fora do GitHub Actions?
A forma se mantém, só a sintaxe muda. O GitLab CI expressa a mesma regra como um bloco rules de job checando $CI_MERGE_REQUEST_LABELS em vez de um if do GitHub Actions, e uma aprovação obrigatória de merge request pode substituir o passo de revisão da isenção. O check que este artigo descreve é do GitHub Actions porque é a plataforma em que a maioria de quem está lendo já está, mas a exigência de fundo, um gate verificado por máquina em vez de uma convenção pedida, é a mesma em qualquer lugar onde o CI roda antes de um merge.
Isso funciona do mesmo jeito em um monorepo?
Precisa de mais uma peça: para qual pacote é a entrada. Changelogs de monorepo cobre por que um único arquivo para o repositório inteiro para de funcionar assim que os pacotes são lançados independentemente; o check de CI herda a mesma exigência; um changeset que não nomeia um pacote não é uma evidência útil de que o changelog certo vai atualizar, só que algum arquivo mudou em algum lugar do diff. Ferramentas construídas para isso (Changesets é a comum no ecossistema JavaScript) pedem para quem contribui escolher o pacote afetado e um bump de semver no mesmo momento em que o changeset é criado, então o check de CI ganha as duas peças de graça em vez de inferir depois.
FAQ
O check de CI deveria bloquear o merge, ou só avisar? Bloquear. Um aviso é funcionalmente idêntico a pedir educadamente, o que já falhou. A etiqueta de isenção existe exatamente para que um caso genuíno de só-aviso ainda tenha um caminho legítimo pelo mesmo gate rígido.
Quem revisa se uma etiqueta de isenção foi aplicada corretamente? Quem quer que aprove o pull request, como parte da revisão que já está fazendo de qualquer forma. A etiqueta nunca deveria ser autoaplicada e não revisada, ou ela vira a mesma brecha silenciosa que o gate deveria fechar.
Exigir isso em CI substitui a necessidade de um pipeline de automação de changelog? Não, alimenta um. Automação de changelog cobre transformar entradas estruturadas em uma página, um feed e um e-mail; o check de CI é o que garante que essas entradas estruturadas existam para serem automatizadas em primeiro lugar.
Qual é a menor versão disso que vale a pena construir primeiro? Um único check que falha se nenhum arquivo mudou embaixo de um diretório de changelog designado, com uma etiqueta de isenção. Roteamento por pacote e inferência de semver para um monorepo podem vir depois; o hábito central, uma entrada existe ou alguém disse explicitamente que não é necessária, é o que vale a pena ter desde o primeiro dia.
As afirmações técnicas deste artigo não foram revisadas de forma independente. Se algo estiver errado, avise a gente e vamos corrigir.