DevOps & CI/CD

Boas Práticas de Conventional Commits, Semantic Versioning e Changelogs Automatizados para Times Ágeis

12 min de leitura

Boas Práticas de Conventional Commits, Semantic Versioning e Changelogs Automatizados para Times Ágeis

Conventional Commits: A Base Para Versionamento Semântico Um Conventional Commit é um padrão de formatação para mensagens de commit que segue uma convenção simples mas poderosa. Ele permite que ferramentas automatizadas parseiem o histórico do repositório, gerem changelogs, e determinem automaticamente o número da próxima versão. Sem um padrão consistente, o histórico fica caótico e as máquinas não conseguem entender suas intenções. A estrutura básica é composta por um tipo, escopo opcional, descrição e corpo opcional. O tipo indica a natureza da mudança: para novas funcionalidades, para correções, para documentação, para formatação, para refatoração, para melhorias de performance, e para testes. Mudanças quebradoras devem ser indicadas com no corpo ou com após o escopo. Na prática, nem sempre você escreve commits perfeitos na primeira tentativa. Por isso, muitos projetos usam hooks do Git para validar a mensagem antes do commit ser finalizado. Vou mostrar um exemplo com a ferramenta : Para automatizar a validação do commit, você instala o commitlint

<h2>Conventional Commits: A Base Para Versionamento Semântico</h2>

<p>Um Conventional Commit é um padrão de formatação para mensagens de commit que segue uma convenção simples mas poderosa. Ele permite que ferramentas automatizadas parseiem o histórico do repositório, gerem changelogs, e determinem automaticamente o número da próxima versão. Sem um padrão consistente, o histórico fica caótico e as máquinas não conseguem entender suas intenções.</p>

<p>A estrutura básica é composta por um tipo, escopo opcional, descrição e corpo opcional. O tipo indica a natureza da mudança: <code>feat</code> para novas funcionalidades, <code>fix</code> para correções, <code>docs</code> para documentação, <code>style</code> para formatação, <code>refactor</code> para refatoração, <code>perf</code> para melhorias de performance, e <code>test</code> para testes. Mudanças quebradoras devem ser indicadas com <code>BREAKING CHANGE:</code> no corpo ou com <code>!</code> após o escopo.</p>

<pre><code>feat(auth): adicionar autenticação OAuth2

Implementa estratégia OAuth2 com suporte a Google e GitHub.

Usuários agora podem fazer login sem criar conta local.

BREAKING CHANGE: endpoint /login foi removido em favor de /oauth/authorize</code></pre>

<p>Na prática, nem sempre você escreve commits perfeitos na primeira tentativa. Por isso, muitos projetos usam hooks do Git para validar a mensagem antes do commit ser finalizado. Vou mostrar um exemplo com a ferramenta <code>commitlint</code>:</p>

<pre><code class="language-javascript">// .commitlintrc.js - Configuração do commitlint

module.exports = {

extends: [&#039;@commitlint/config-conventional&#039;],

rules: {

&#039;type-enum&#039;: [

2,

&#039;always&#039;,

[&#039;feat&#039;, &#039;fix&#039;, &#039;docs&#039;, &#039;style&#039;, &#039;refactor&#039;, &#039;perf&#039;, &#039;test&#039;, &#039;chore&#039;]

],

&#039;subject-case&#039;: [2, &#039;never&#039;, [&#039;start-case&#039;, &#039;pascal-case&#039;, &#039;upper-case&#039;]],

&#039;subject-empty&#039;: [2, &#039;never&#039;],

&#039;subject-full-stop&#039;: [2, &#039;never&#039;, &#039;.&#039;],

&#039;type-case&#039;: [2, &#039;always&#039;, &#039;lowercase&#039;]

}

};</code></pre>

<p>Para automatizar a validação do commit, você instala o commitlint junto com husky (que gerencia Git hooks):</p>

<pre><code class="language-bash">npm install --save-dev @commitlint/config-conventional @commitlint/cli husky

npx husky install

npx husky add .husky/commit-msg &#039;npx --no -- commitlint --edit &quot;$1&quot;&#039;</code></pre>

<p>Agora, quando você tentar fazer um commit com mensagem inválida, o Git rejeitará:</p>

<pre><code>$ git commit -m &quot;fixed stuff&quot;

⧗ input: fixed stuff

✖ subject may not be empty [subject-empty]

✖ type may not be empty [type-enum]

✖ subject-case [subject-case]

✖ found 3 problems, 0 warnings

(See &quot;https://www.commitlint.js.org&quot; for details)

husky - commit-msg hook exited with code 1 (error)</code></pre>

<h2>Semantic Versioning: Entendendo MAJOR.MINOR.PATCH</h2>

<p>Semantic Versioning (ou SemVer) é um esquema de numeração que comunica o tipo de mudança através dos números de versão. A estrutura é <code>MAJOR.MINOR.PATCH</code>, onde MAJOR é incrementado para mudanças quebradoras, MINOR para novas funcionalidades retrocompatíveis, e PATCH para correções de bugs retrocompatíveis.</p>

<p>Quando você libera uma versão <code>2.5.3</code>, você está dizendo: &quot;Essa é a versão 2 (com quebras) que teve 5 adições de funcionalidades menores e agora tem 3 correções de bugs.&quot; Se um usuário está na versão <code>2.5.0</code> e atualiza para <code>2.5.3</code>, ele sabe que não há risco de quebra. Já uma atualização para <code>3.0.0</code> sinaliza que há mudanças incompatíveis.</p>

<p>O Semantic Versioning segue regras simples: você começa em <code>0.1.0</code>, incrementa MINOR para cada feature enquanto está em fase pré-produção, muda para <code>1.0.0</code> quando tem API estável, e apenas incrementa MAJOR quando há breaking changes. Pre-releases podem ser indicadas com <code>-alpha</code>, <code>-beta</code>, <code>-rc</code> (release candidate).</p>

<pre><code class="language-javascript">// package.json - Exemplo de versionamento semântico

{

&quot;name&quot;: &quot;meu-projeto&quot;,

&quot;version&quot;: &quot;2.5.3&quot;,

&quot;description&quot;: &quot;Uma biblioteca de validação&quot;,

&quot;main&quot;: &quot;dist/index.js&quot;,

&quot;scripts&quot;: {

&quot;version&quot;: &quot;npm run build &amp;&amp; npm run changelog&quot;

}

}</code></pre>

<p>A maioria dos projetos usa a ferramenta <code>standard-version</code> ou <code>semantic-release</code> para automatizar esse processo. Vou mostrar como configurar <code>semantic-release</code>, que combina Conventional Commits + SemVer + Changelog automaticamente:</p>

<pre><code class="language-bash">npm install --save-dev semantic-release

npx semantic-release --init</code></pre>

<p>Isso gera uma configuração que detecta automaticamente que tipo de versão deve ser liberada analisando os commits desde a última release:</p>

<pre><code class="language-javascript">// .releaserc.json - Configuração do semantic-release

{

&quot;branches&quot;: [&quot;main&quot;, &quot;develop&quot;],

&quot;plugins&quot;: [

&quot;@semantic-release/commit-analyzer&quot;,

&quot;@semantic-release/release-notes-generator&quot;,

&quot;@semantic-release/changelog&quot;,

&quot;@semantic-release/npm&quot;,

&quot;@semantic-release/git&quot;,

&quot;@semantic-release/github&quot;

]

}</code></pre>

<h2>Changelogs Automatizados: Gerando Documentação do Histórico</h2>

<p>Um changelog é um arquivo que documenta todas as mudanças significativas entre versões. Ao invés de escrever manualmente, você pode gerar automaticamente a partir dos Conventional Commits. Isso garante que o changelog sempre reflita o histórico real e reduz erros.</p>

<p>A ferramenta mais popular é <code>conventional-changelog</code>, que lê os commits e cria um arquivo <code>CHANGELOG.md</code> bem formatado. Aqui está como configurar:</p>

<pre><code class="language-bash">npm install --save-dev conventional-changelog-cli

npx conventional-changelog -p angular -i CHANGELOG.md -s</code></pre>

<p>O comando acima gera um changelog no formato Angular (que segue Conventional Commits). Você pode adicionar isso como script no package.json para rodar automaticamente:</p>

<pre><code class="language-json">{

&quot;scripts&quot;: {

&quot;changelog&quot;: &quot;conventional-changelog -p angular -i CHANGELOG.md -s -r 0&quot;,

&quot;release&quot;: &quot;npm run changelog &amp;&amp; npm version &amp;&amp; npm publish&quot;

}

}</code></pre>

<p>Um changelog gerado automaticamente fica assim:</p>

<pre><code class="language-markdown"># 2.5.0 (2024-01-15)

Features

  • auth: adicionar suporte OAuth2 (abc1234)
  • api: endpoint para listar usuários (def5678)

Bug Fixes

  • validation: corrigir validação de email (ghi9012)

BREAKING CHANGES

  • auth: endpoint /login foi removido em favor de /oauth/authorize (abc1234)

---

2.4.0 (2024-01-08)

Features

  • database: adicionar suporte PostgreSQL (jkl3456)</code></pre>

<p>Para uma solução completa que automatiza tudo (Conventional Commits + versionamento + changelog), a melhor abordagem é usar <code>semantic-release</code>. Ele não apenas gera o changelog, mas também cria a tag Git, publica no npm, e pode enviar notificações. Aqui está um workflow completo do GitHub Actions que dispara automaticamente:</p>

<pre><code class="language-yaml"># .github/workflows/release.yml

name: Release

on:

push:

branches: [main]

jobs:

release:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v3

with:

fetch-depth: 0

  • uses: actions/setup-node@v3

with:

node-version: &#039;18&#039;

registry-url: &#039;https://registry.npmjs.org&#039;

  • run: npm ci
  • run: npm run build
  • run: npm run test
  • run: npx semantic-release

env:

GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

NPM_TOKEN: ${{ secrets.NPM_TOKEN }}</code></pre>

<h2>Integração Prática: Pondo Tudo Junto</h2>

<p>Para dominar esses conceitos na prática, você precisa ver como eles trabalham juntos em um projeto real. Vou criar um exemplo completo de um projeto Node.js que implementa todos os três elementos.</p>

<p>Primeiro, a configuração inicial:</p>

<pre><code class="language-bash">mkdir meu-projeto &amp;&amp; cd meu-projeto

npm init -y

npm install --save-dev \

@commitlint/config-conventional \

@commitlint/cli \

husky \

conventional-changelog-cli \

semantic-release \

@semantic-release/changelog \

@semantic-release/git \

@semantic-release/npm

npx husky install

npx husky add .husky/commit-msg &#039;npx --no -- commitlint --edit &quot;$1&quot;&#039;</code></pre>

<p>Agora os arquivos de configuração:</p>

<pre><code class="language-javascript">// .commitlintrc.js

module.exports = {

extends: [&#039;@commitlint/config-conventional&#039;],

rules: {

&#039;type-enum&#039;: [

2,

&#039;always&#039;,

[&#039;feat&#039;, &#039;fix&#039;, &#039;docs&#039;, &#039;style&#039;, &#039;refactor&#039;, &#039;perf&#039;, &#039;test&#039;, &#039;chore&#039;]

],

&#039;subject-case&#039;: [2, &#039;never&#039;, [&#039;start-case&#039;, &#039;pascal-case&#039;, &#039;upper-case&#039;]],

&#039;subject-empty&#039;: [2, &#039;never&#039;],

&#039;subject-full-stop&#039;: [2, &#039;never&#039;, &#039;.&#039;]

}

};</code></pre>

<pre><code class="language-json">// .releaserc.json

{

&quot;branches&quot;: [&quot;main&quot;],

&quot;plugins&quot;: [

[

&quot;@semantic-release/commit-analyzer&quot;,

{

&quot;preset&quot;: &quot;angular&quot;,

&quot;releaseRules&quot;: [

{ &quot;type&quot;: &quot;feat&quot;, &quot;release&quot;: &quot;minor&quot; },

{ &quot;type&quot;: &quot;fix&quot;, &quot;release&quot;: &quot;patch&quot; },

{ &quot;type&quot;: &quot;perf&quot;, &quot;release&quot;: &quot;patch&quot; },

{ &quot;breaking&quot;: true, &quot;release&quot;: &quot;major&quot; }

]

}

],

[

&quot;@semantic-release/release-notes-generator&quot;,

{

&quot;preset&quot;: &quot;angular&quot;

}

],

&quot;@semantic-release/changelog&quot;,

&quot;@semantic-release/npm&quot;,

[

&quot;@semantic-release/git&quot;,

{

&quot;assets&quot;: [&quot;package.json&quot;, &quot;package-lock.json&quot;, &quot;CHANGELOG.md&quot;],

&quot;message&quot;: &quot;chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}&quot;

}

]

]

}</code></pre>

<pre><code class="language-json">// package.json - seção scripts

{

&quot;scripts&quot;: {

&quot;changelog&quot;: &quot;conventional-changelog -p angular -i CHANGELOG.md -s&quot;,

&quot;version&quot;: &quot;npm run changelog &amp;&amp; git add CHANGELOG.md&quot;

}

}</code></pre>

<p>Com essa configuração, aqui está o fluxo normal de trabalho:</p>

<pre><code class="language-bash"># 1. Criar uma feature

git checkout -b feature/nova-funcionalidade

2. Fazer alterações e commitar com Conventional Commit

echo &quot;console.log(&#039;nova feature&#039;)&quot; &gt; src/feature.js

git add src/feature.js

git commit -m &quot;feat(core): adicionar nova funcionalidade&quot;

3. Push e fazer PR

git push origin feature/nova-funcionalidade

4. Depois do merge para main, semantic-release roda automaticamente (CI/CD)

Ele detecta que houve um &#039;feat&#039;, incrementa MINOR version,

gera o changelog, cria a tag, publica no npm, tudo automaticamente</code></pre>

<p>Quando você tiver vários commits antes da próxima release, o semantic-release analisa todos:</p>

<pre><code>- feat(api): novo endpoint de relatórios → MINOR (+1)

  • fix(validation): corrigir regex de email → PATCH (+0.0.1)
  • docs: atualizar README → SEM MUDANÇA DE VERSÃO
  • perf(database): otimizar query → PATCH (+0.0.1)

Resultado: De 2.5.0 vai para 2.6.0 (feature é a mudança mais significativa)</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu três conceitos que, juntos, transformam o versionamento de um projeto. <strong>Primeiro</strong>, Conventional Commits fornece a estrutura semântica que máquinas conseguem ler. <strong>Segundo</strong>, Semantic Versioning comunica claramente o impacto de cada release através de números. <strong>Terceiro</strong>, Changelogs automatizados geram documentação confiável sem esforço manual, reduzindo erros e mantendo sincronização com o código real.</p>

<p>A chave é começar implementando Conventional Commits (pois tudo depende disso), depois automatizar o versionamento com ferramentas como semantic-release, e deixar o changelog ser um efeito colateral natural. Um projeto bem estruturado nesse aspecto economiza horas em documentação e reduz drasticamente problemas de comunicação entre equipe e usuários sobre quais mudanças foram feitas em cada versão.</p>

<h2>Referências</h2>

<ul>

<li>https://www.conventionalcommits.org/ — Documentação oficial do Conventional Commits</li>

<li>https://semver.org/ — Especificação oficial do Semantic Versioning</li>

<li>https://semantic-release.gitbook.io/ — Documentação completa do semantic-release</li>

<li>https://github.com/conventional-changelog/conventional-changelog — Repositório oficial do conventional-changelog</li>

<li>https://commitlint.js.org/ — Documentação do commitlint para validação de commits</li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em DevOps & CI/CD

Como Usar Shell Scripting Avançado: Processos, Sinais, Trap e Scripts Robustos em Produção
Como Usar Shell Scripting Avançado: Processos, Sinais, Trap e Scripts Robustos em Produção

Entendendo Processos em Shell Um processo em Unix/Linux é uma instância de um...

Boas Práticas de SAST e DAST em Pipelines: Trivy, Snyk, SonarQube e OWASP ZAP para Times Ágeis
Boas Práticas de SAST e DAST em Pipelines: Trivy, Snyk, SonarQube e OWASP ZAP para Times Ágeis

SAST e DAST: Fundamentos e Diferenças A segurança de aplicações é um dos pila...

Redes e Volumes Avançados no Docker: Bridge, Overlay e Bind Mounts: Do Básico ao Avançado
Redes e Volumes Avançados no Docker: Bridge, Overlay e Bind Mounts: Do Básico ao Avançado

Introdução: A Importância da Comunicação e Persistência de Dados em Container...