<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: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore']
],
'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'type-case': [2, 'always', 'lowercase']
}
};</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 'npx --no -- commitlint --edit "$1"'</code></pre>
<p>Agora, quando você tentar fazer um commit com mensagem inválida, o Git rejeitará:</p>
<pre><code>$ git commit -m "fixed stuff"
⧗ 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 "https://www.commitlint.js.org" 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: "Essa é a versão 2 (com quebras) que teve 5 adições de funcionalidades menores e agora tem 3 correções de bugs." 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
{
"name": "meu-projeto",
"version": "2.5.3",
"description": "Uma biblioteca de validação",
"main": "dist/index.js",
"scripts": {
"version": "npm run build && npm run changelog"
}
}</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
{
"branches": ["main", "develop"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/git",
"@semantic-release/github"
]
}</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">{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"release": "npm run changelog && npm version && npm publish"
}
}</code></pre>
<p>Um changelog gerado automaticamente fica assim:</p>
<pre><code class="language-markdown"># 2.5.0 (2024-01-15)
Features
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: '18'
registry-url: 'https://registry.npmjs.org'
- 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 && 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 'npx --no -- commitlint --edit "$1"'</code></pre>
<p>Agora os arquivos de configuração:</p>
<pre><code class="language-javascript">// .commitlintrc.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore']
],
'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.']
}
};</code></pre>
<pre><code class="language-json">// .releaserc.json
{
"branches": ["main"],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "angular",
"releaseRules": [
{ "type": "feat", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "perf", "release": "patch" },
{ "breaking": true, "release": "major" }
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "angular"
}
],
"@semantic-release/changelog",
"@semantic-release/npm",
[
"@semantic-release/git",
{
"assets": ["package.json", "package-lock.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}</code></pre>
<pre><code class="language-json">// package.json - seção scripts
{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"version": "npm run changelog && git add CHANGELOG.md"
}
}</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 "console.log('nova feature')" > src/feature.js
git add src/feature.js
git commit -m "feat(core): adicionar nova funcionalidade"
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 'feat', 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><!-- FIM --></p>