DEV Community

Klaus Kazlauskas
Klaus Kazlauskas

Posted on

Como gerar CHANGELOG automaticamente

Oie! Se você já usou bibliotecas, ou viu repositórios open-source, você já deve ter visto um arquivo chamado CHANGELOG.md. Até dá pra fazer ele manualmente, mas...

Meme do fantoche Pedro falando "Quando você mostra o resultado de 3h de trabalho manual e sua colega mostra um artigo de como fazer automaticamente"

Você luta contra muitos fatores e perde muito tempo. Além disso, é muito simples fazer de forma automática. Só precisamos:

  • Definir um padrão para os nossos commits
  • Usar pacotes para auxiliar nesses padrões
  • Usar um pacote para gerar o CHANGELOG

Então, vamos começar a gerar nosso CHANGELOG.


Preparação

Na época que esse guia foi preparado, foram usadas as seguintes tecnologias:

tecnologia versão instalação
Node.js 11.6.0 como instalar
NPM 6.13.4 Já vem com o Node.js

Todo o código desse projeto está no GitHub, então, caso você se perca em qualquer parte, pode fazer uma comparação direta. Além disso, todos os passos desse artigo estão lá de forma resumida:

GitHub logo klauskpm / changelog-cicd

Repositório para ensinar a criar CHANGELOG automaticamente

changelog-cicd

Status Badge do Gerador de CHANGELOG

Repositório com passo-a-passo de como gerar um CHANGELOG automaticamente.

Esse passo-a-passo é a versão muito resumida do artigo: Como gerar CHANGELOG automaticamente

Gerando um CHANGELOG automaticamente

1) Instale as dependências

npm install --global commitizen
# dentro do seu projeto
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional standard-version

2) Configure o commitizen

# dentro do seu projeto
commitizen init cz-conventional-changelog --save-dev --save-exact

3) Crie o arquivo commitlint.config.js

module.exports = {
    extends: ['@commitlint/config-conventional']
};

4) Altere o seu package.json

{
  ...,
  // opcional
  "repository": {
    "url": "git@gitlab.com:meu-usuario/meu-repo.git"
  }
  ...,
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
  "scripts": {
    "release": "standard-version"
  }
}

5) Crie o arquivo .versionrc

{
  "types": [
    {"type

Padronizando as mensagens de commit

O padrão que vamos seguir para as mensagens é o conventional commit specification(especificação convencional de commit). Esse padrão é escrito da seguinte forma:

<tipo>[(escopo opcional)]: <descrição>

[corpo opcional]

[rodapé opcional]
campo obrigatório descrição
tipo Tipo do commit que vai ser feito. Verificar lista de tipos permitidos.
escopo Arquivo, domínio, ou módulo que aquele commit se refere
descrição Uma descrição curta sobre o commit
corpo Uma descrição maior, caso não consiga explicar com clareza tudo o que tem no commit
rodapé Fechamento de tarefas e/ou informação sobre breaking changes

Lista de tipos permitidos

  • feat: Uma funcionalidade
  • fix: Um ajuste de erro/bug
  • docs: Modificação na documentação
  • style: Mudança de estilo (ponto, vírgula, indentação)
  • refactor: Mudança de código que não adiciona funcionalidade ou arruma um erro
  • perf: Mudança que altera performance
  • test: Novos testes ou correção de antigos
  • build: Mudanças que afetam o build ou dependências exeternas (gulp, npm)
  • ci: Mudanças na configuração da Integração Contínua (Travis, Circle)
  • chore: Outras mudanças que não são nos arquivos de src ou test
  • revert: Reversão de um commit

Exemplos de commit

feat(cadastro): adiciona integração com Gugou Sign-in
fix(pagamento): muda a chave do RecebaSeguro

A chave que estava sendo usada era de desenvolvimento,
mas acabou sendo enviada para produção

Fecha a tarefa #457
refactor(produto): remove o método #adicionarAoCarrinho

O método já tinha sido depreciado e agora foi removido

BREAKING CHANGE: o método públic #adicionarAoCarrinho foi removido

Facilitando a padronização

Apesar da padronização facilitar a automatização, ela pode ser muita coisa pra gravar de primeira. Então, para facilitar a aplicação desse padrão, nós vamos usar o Commitizen e o Commitlint.

Commitlint

O commitlint vai verificar se nossos commits estão seguindo o Conventional Commit Specification. Ele vai fazer isso com a ajuda do husky, que irá disparar o commitlint toda vez que for feito um commit.

1) Instale as dependências

# dentro do seu projeto
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional

2) Crie o commitlint.config.js

module.exports = {
    extends: ['@commitlint/config-conventional']
};

3) Configure o husky no package.json

{
  ...,
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
}

4) Teste para ver se está funcionando (opcional)

A configuração está pronta e já deve estar funcionando. Se quiser, pode fazer uma adição no seu projeto e commitar como teste para quebrar. Você deve receber um erro explicando o que está faltando no commit.

Commitizen

Agora que temos algo para verificar nossos commits, precisamos fazer eles de uma maneira mais fácil. Com o commitizen, nós vamos commitar usando o comando git cz, que vai fazer perguntas passo-a-passo até ter o commit final.

1) Instale e configure o commitizen

npm install --global commitizen

# dentro do seu projeto
commitizen init cz-conventional-changelog --save-dev --save-exact

2) Teste para ver se está funcionando

Faça uma pequena alteração, adicione ela com git add . e faça um git cz.

Terminal listando os tipos de commit que podem ser escolhidos, após ter executado o commitizen


Automatizando o CHANGELOG

Agora que nós temos os commits seguindo um padrão, podemos gerar o nosso arquivo CHANGELOG. Os dois pacotes mais utilizados para fazer essa tarefa hoje, são o semantic-release e o standard-version. Vamos seguir com o standard-version pela facilidade do uso.

standard-version

Esse pacote vai ser responsável por analisar nossos commits, gerar uma nova versão pro projeto e gerar o CHANGELOG.

1) Instale o standard-version

npm install --save-dev standard-version

2) Altere seu package.json

{
  "scripts": {
    "release": "standard-version"
  }
}

3) Faça seu primeiro release

Agora que tudo está configurado, commite essas alterações e execute o seguinte comando:

npm run release -- --first-release

O npm run release executa as seguintes etapas:

  1. Modifica a versão do seu package.json
  2. Usa o conventional-changelog para criar/atualizar o CHANGELOG.md
  3. Commita o package.json e o CHANGELOG.md
  4. Gera uma nova tag

A diferença dele para o --first-release, é que o --first-release ignora o passo 1.

🎉 CHANGELOG gerado 🎉

CHANGELOG.md da versão v1.0.0 do projeto, gerado pelo comando npm run release -- --first-release

Pode comparar o seu progresso com a tag v1.0.0 do repositório

Explorando o CHANGELOG

Agora que temos tudo, vamos entender como o CHANGELOG é gerado e o que podemos fazer com ele.

Se você for no repositório na v2.1.0, você verá que foram criados vários commits e versões. Olhando essas versões criadas, podemos observar que:

  • Uma feat representa um minor (X.1.X) do SemVer;
  • Um fix representa um patch (X.X.1) do SemVer;
  • Uma BREAKING CHANGE representa um major (1.X.X) do SemVer;
  • Só aumenta um número por vez. 3 feats aumentam o mesmo que 1;
  • O maior número é que manda. Se tem feat, não importa o fix;
  • Só aparecem os commits com feat e fix;
  • Todos os links estão apontando para o repositório do GitHub;

Tirando os dois últimos pontos, o resto são regras do próprio SemVer (Semantic Versioning), que é quem dita as regras do versionamento (major.minor.patch).

Esses dois últimos pontos ganharam destaque porque eles são muito interessantes. Fazendo uma reflexão, parece que você teria que fazer uma solução própria caso quisesse exibir commits de outros tipos ou se quisesse usar o GitLab. Felizmente, o standard-version tem uma solução bem prática pra isso. Você só precisa passar um arquivo de configuração.

Configurando a geração do CHANGELOG

Podemos passar a configuração pro standard-version de diversas formas, mas vamos focar no .versionrc

Esse arquivo segue a especificação de configuração do conventional changelog. Então, se quiser exibir um outro tipo de commit, botar em português, e usar o GitLab:

1) Crie o arquivo .versionrc

Aqui nós estamos adicionando o tipo perf ao CHANGELOG.md, ao mesmo tempo que estamos também definindo os nomes das sessões em português. Por último, estamos adicionando o [skip ci] na mensagem de commit que é gerada pelo standard-version. Isso vai ser útil no futuro.

{
    "types": [
        {"type": "feat", "section": "Funcionalidades"},
        {"type": "fix", "section": "Errors Corrigidos"},
        {"type": "chore", "hidden": true},
        {"type": "docs", "hidden": true},
        {"type": "style", "hidden": true},
        {"type": "refactor", "hidden": true},
        {"type": "perf", "section": "Melhorias de Performance"},
        {"type": "test", "hidden": true}
    ],
    "releaseCommitMessageFormat": "chore(release): {{currentTag}} [skip ci]"
}

2) Altere o package.json (Opcional: GitLab)

Se quiser mesmo usar outro repositório, é bem provável que você só precise adicionar o repository.url no seu package.json. O standard-version usa essa URL para definir as URLs do CHANGELOG:

{
  ...,
  "repository": {
    "url": "git@gitlab.com:meu-usuario/meu-repo.git"
  }
}

Se tiver problema nas URLs que foram geradas no seu CHANGELOG.md, você pode adicionar uma configuração customizada para o seguintes campos no seu .versionrc: commitUrlFormat, compareUrlFormat e issueUrlFormat.

CHANGELOG novo na v2.1.1 com Melhorias de Performance sendo exibida

Pode comparar o seu progresso com a tag v2.1.1 do repositório


Automatizando a automatização com integração contínua

O nosso CHANGELOG já é gerado graças ao standard-version, mas ainda precisamos rodar o script de forma manual. O ideal é que isso aconteça sempre que o código atinja a versão final / produção. Para isso, vamos usar o GitHub Actions como nossa ferramenta de integração contínua (CI - Continuous Integration).

No repositório do GitHub, na aba Ações/Actions, vamos criar um workflow de Node.js.

Página de Actions do GitHub com o workflows de Node.js e Node.js package

E esse é o arquivo que é gerado por padrão nesse workflow, mas não vamos usa-lo:

# nome do processo
name: Node CI

# o processo roda quando tiver um push
on: [push]

jobs:
  build:

    # roda o build no ubuntu
    runs-on: ubuntu-latest

    strategy:
      # o processo vai executar uma vez para cada configuração do node
      matrix:
        node-version: [8.x, 10.x, 12.x]

    steps:
    # esse passo pega uma cópia dos seu repositório
    - uses: actions/checkout@v1

    # esse passo instala a versão do node
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}

    # esse passo faz a instalação das dependências, roda o build e o teste
    - name: npm install, build, and test
      run: |
        npm ci
        npm run build --if-present
        npm test
      env:
        CI: true

Nós vamos usar esse arquivo como base para construir o nosso próprio. Se olhar bem, nós não precisamos realizar um teste, criar uma build ou muito menos rodar em várias versões do node, mas precisamos gerar o CHANGELOG e ele precisa de permissões para commitar e pushar no GitHub.

Além disso, tem um ponto que precisamos prestar muita atenção. Como o nosso processo de automatização gera um commit e roda a cada push, nós vamos criar um loop infinito. Felizmente, eu já passei 1 dia resolvendo isso, e depois de 50 commits disparados automaticamente, achei a solução.

Nos outros sistemas de integração contínua (CI) se fizermos um commit com skip ci - se lembra do .versionrc? - esse commit é automaticamente ignorado. Pena que não funciona assim no GitHub. Felizmente, a internet é um lugar maravilhoso e as pessoas conseguiram desenvolver uma solução para isso.

Juntando os fatores que precisávamos para gerar o CHANGELOG.md e aplicando essa solução que previne o loop, temos o seguinte arquivo:

.github/workflows/gerador-de-changelog.yml

name: Gerador de CHANGELOG

# só executa no push de commit da branch master
on:
  push:
    branches:
      - master

jobs:
  build:
    # só deixa executar se o último commit não conter 'skip ci' na mensagem
    if: "!contains(github.event.head_commit.message, 'skip ci')"

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    # configura o GITHUB_TOKEN
    # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token
    - name: Configura o GitHub token
      uses: fregante/setup-git-token@v1
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
        name: "Gerador de changelog"
        email: "changelog@users.noreply.github.com"

    # instala a versão 11.x do Node.js
    - name: Instala o Node.js 11.x
      uses: actions/setup-node@v1
      with:
        node-version: 11.x

    # instala as dependências, vai para a master
    # executa o standard-version, e envia o commit e tag
    - name: Gera o CHANGELOG
      run: |
        npm ci
        git checkout master
        npm run release
        git push origin master --follow-tags

Se quiser saber mais sobre como configurar um workflow, como funciona a sintaxe do workflow do GitHub Actions e o que é o GITHUB_TOKEN, sugiro seguir esses links.

Pronto! Agora nossa automatização está completa!

Painel de Workflows do GitHub com o Gerador de CHANGELOG aberto após ter rodado com sucesso

Pode comparar o seu progresso com a tag v2.1.2 do repositório

Última graça

Por último, só para fazer uma graça, podemos adicionar um status badge (distintivo/indicador de status) para o nosso build. Isso é uma imagem que indica se o build passou ou não. Podemos adicionar isso no nosso README.md seguindo esse padrão de URL:

URL 
![](https://github.com/<CONTA>/<REPOSITORIO>/workflows/<NOME_WORKFLOW>/badge.svg)

Exemplo do meu repositório:
![](https://github.com/klauskpm/changelog-cicd/workflows/Gerador%20de%20CHANGELOG/badge.svg)

Sendo klauskpm meu usuário, changelog-cicd o repositório que estou usando, e Gerador de CHANGELOG o nome do workflow, não o do arquivo, com %20 em vez de espaços.

Assim que sua badge será exibida no seu README.md:

Status Badge do Gerador de CHANGELOG

Pode comparar o seu progresso com a tag v2.1.3 do repositório


Conclusão

Com esse artigo nós:

  • Melhoramos a qualidade dos commits
  • Automatizamos a geração de CHANGELOG e subida de versão
  • Fizemos integração contínua com GitHub Actions

Caso algo tenha dado errado, pode sempre consultar o repositório com a versão final e um passo-a-passo super resumido desse artigo.

GitHub logo klauskpm / changelog-cicd

Repositório para ensinar a criar CHANGELOG automaticamente

changelog-cicd

Status Badge do Gerador de CHANGELOG

Repositório com passo-a-passo de como gerar um CHANGELOG automaticamente.

Esse passo-a-passo é a versão muito resumida do artigo: Como gerar CHANGELOG automaticamente

Gerando um CHANGELOG automaticamente

1) Instale as dependências

npm install --global commitizen
# dentro do seu projeto
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional standard-version

2) Configure o commitizen

# dentro do seu projeto
commitizen init cz-conventional-changelog --save-dev --save-exact

3) Crie o arquivo commitlint.config.js

module.exports = {
    extends: ['@commitlint/config-conventional']
};

4) Altere o seu package.json

{
  ...,
  // opcional
  "repository": {
    "url": "git@gitlab.com:meu-usuario/meu-repo.git"
  }
  ...,
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
  "scripts": {
    "release": "standard-version"
  }
}

5) Crie o arquivo .versionrc

{
  "types": [
    {"type

Não deixe de me seguir caso queira receber mais artigos como esse, e compartilhar com seus colegas caso tenha ❤️. Não deixe de comentar se tiver algo que acha que eu deva mudar no artigo.

Um forte abraço e te vejo na próxima 😬

Top comments (2)

Collapse
 
robarros profile image
robarros

ola, se eu tiver uma branch dev e fazer o merge para a master ele ira fazer esta processo?
e se na mensagem do merge tem que haver os padroes da mensagens como feat fix etc??

vlw

Collapse
 
klauskpm profile image
Klaus Kazlauskas

Ola o/

Se usar esse processo, que é de push na master, ele deve ativar no merge sim.

O merge em si não precisa ter esses padrões nas mensagens. Se ele não tiver, ele só vai ser ignorado no changelog.

Se quiser fazer um merge usando squash, recomendo colocar esses padrões na mensagem do merge, pois você vai perder todas as outras mensagens e não teria nada para gerar no novo changelog, só uma mudança de versões.