Como gerar CHANGELOG automaticamente
Tags: git, github, devops, javascript
Published: 2020-01-17 12:10:40

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"](https://i.imgflip.com/3lukls.jpg)

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](https://nodejs.org/en/) |
| 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 klauskpm/changelog-cicd %}

----

# Padronizando as mensagens de commit

O padrão que vamos seguir para as mensagens é o [conventional commit specification](https://www.conventionalcommits.org/en/v1.0.0-beta.2/)(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 | :white_check_mark: | Tipo do commit que vai ser feito. Verificar [lista de tipos permitidos](#lista-de-tipos-permitidos). |
| escopo | :x: | Arquivo, domínio, ou módulo que aquele commit se refere |
| descrição | :white_check_mark: | Uma descrição curta sobre o commit |
| corpo | :x: | Uma descrição maior, caso não consiga explicar com clareza tudo o que tem no commit |
| rodapé | :x: | 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

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

### 2) Crie o commitlint.config.js

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

### 3) Configure o `husky` no package.json
```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`

```sh
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](https://github.com/commitizen/cz-cli/raw/master/meta/screenshots/add-commit.png)

----

# 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`](https://www.npmjs.com/package/semantic-release) e o [`standard-version`](https://www.npmjs.com/package/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`

```sh
npm install --save-dev standard-version
```

### 2) Altere seu `package.json`

```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:

```sh
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`](https://github.com/conventional-changelog/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**.

#### :tada: CHANGELOG gerado :tada:

![CHANGELOG.md da versão v1.0.0 do projeto, gerado pelo comando npm run release -- --first-release](https://thepracticaldev.s3.amazonaws.com/i/9n1esq47jyuzuo7zfxsp.png)

> Pode comparar o seu progresso com a [tag v1.0.0 do repositório](https://github.com/klauskpm/changelog-cicd/tree/v1.0.0)

## 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](https://github.com/klauskpm/changelog-cicd/blob/v2.1.0/CHANGELOG.md), 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](https://semver.org/);
- Um fix representa um patch (X.X.1) do [SemVer](https://semver.org/);
- Uma BREAKING CHANGE representa um major (1.X.X) do [SemVer](https://semver.org/);
- 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](https://semver.org/) (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](https://www.npmjs.com/package/standard-version#configuration), mas vamos focar no `.versionrc`

Esse arquivo segue a [especificação de configuração do conventional changelog](https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.1.0/README.md). 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.

```json
{
    "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:

```json
{
  ...,
  "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](https://thepracticaldev.s3.amazonaws.com/i/rbxy02lbfsjyma8imp00.png)

> Pode comparar o seu progresso com a [tag v2.1.1 do repositório](https://github.com/klauskpm/changelog-cicd/tree/v2.1.1)

----

# 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](https://help.github.com/en/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](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/starting-with-preconfigured-workflow-templates).

![Página de Actions do GitHub com o workflows de Node.js e Node.js package](https://thepracticaldev.s3.amazonaws.com/i/b6ie7rmjcte8ns7c1b8z.png)

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

```yml
# 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`?](#1-crie-o-arquivo-raw-versionrc-endraw-) - 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](https://github.com/marketplace/actions/skip-based-on-commit-message) 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`
```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](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/configuring-a-workflow), como funciona a [sintaxe do workflow do GitHub Actions](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions) e [o que é o GITHUB_TOKEN](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-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](https://thepracticaldev.s3.amazonaws.com/i/esqa7kc50dcxujat4r0u.png)

> Pode comparar o seu progresso com a [tag v2.1.2 do repositório](https://github.com/klauskpm/changelog-cicd/tree/v2.1.2)

## Última graça

Por último, só para fazer uma graça, podemos adicionar um [status badge (distintivo/indicador de status) para o nosso build](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/configuring-a-workflow#adding-a-workflow-status-badge-to-your-repository). 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:

```md
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](https://github.com/klauskpm/changelog-cicd/workflows/Gerador%20de%20CHANGELOG/badge.svg)

> Pode comparar o seu progresso com a [tag v2.1.3 do repositório](https://github.com/klauskpm/changelog-cicd/tree/v2.1.3)

----

# 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 klauskpm/changelog-cicd %}

----

Não deixe de me seguir caso queira receber mais artigos como esse, e compartilhar com seus colegas caso tenha :heart:. 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 :grimacing: