Conventional Commits e semantic versioning: automação de changelog e release

1. Fundamentos do Conventional Commits

Conventional Commits é uma especificação que estabelece uma convenção para mensagens de commit, criando um formato padronizado que permite a automação de processos como geração de changelog e versionamento semântico. A estrutura básica de uma mensagem de commit segue este padrão:

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

<corpo opcional>

<rodapé opcional>

Os tipos principais são:

  • feat: introduz uma nova funcionalidade
  • fix: corrige um bug
  • BREAKING CHANGE: indica uma mudança que quebra compatibilidade

Tipos auxiliares incluem docs (documentação), refactor (refatoração sem mudança de comportamento), test (adição de testes) e chore (tarefas de manutenção).

Exemplos práticos:

feat(autenticação): adicionar login com Google OAuth

Implementa fluxo completo de autenticação via Google.
BREAKING CHANGE: endpoint de login agora requer token OAuth
fix(api): corrigir validação de email no cadastro

O campo email aceitava formatos inválidos devido a regex incorreta.

A relação com versionamento semântico é direta: BREAKING CHANGE incrementa MAJOR, feat incrementa MINOR e fix incrementa PATCH.

2. Versionamento Semântico (SemVer) na Prática

SemVer define versões no formato MAJOR.MINOR.PATCH. As regras de incremento são:

  • MAJOR: incrementado quando há mudanças incompatíveis na API
  • MINOR: incrementado quando novas funcionalidades são adicionadas de forma compatível
  • PATCH: incrementado quando correções de bugs compatíveis são aplicadas

Exemplo de aplicação com Conventional Commits:

v1.0.0 → commit feat → v1.1.0
v1.1.0 → commit fix → v1.1.1
v1.1.1 → commit BREAKING CHANGE → v2.0.0

Pré-lançamentos usam sufixos como -alpha.1, -beta.2 ou -rc.1. Metadados de build podem ser adicionados com +build.1234.

3. Ferramentas para Automação de Changelog

standard-version

Ferramenta que automatiza bump de versão e geração de changelog baseada em commits convencionais.

npm install --save-dev standard-version

Configuração no package.json:

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

semantic-release

Pipeline completo que analisa commits, versiona automaticamente e publica pacotes.

npm install --save-dev semantic-release

Configuração mínima no package.json:

{
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/github"
  ]
}

commit-and-tag-version

Alternativa moderna ao standard-version com suporte a monorepo.

npm install --save-dev commit-and-tag-version

4. Configuração de Pipeline de Release Automatizado

Exemplo de workflow com GitHub Actions:

name: Release
on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - run: npm ci
      - run: npx semantic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Os plugins do semantic-release funcionam em sequência:

  1. commit-analyzer: analisa commits desde último release
  2. release-notes-generator: gera notas de release em Markdown
  3. changelog: atualiza arquivo CHANGELOG.md
  4. github: cria release no GitHub com changelog gerado

5. Automação de Changelog com Scripts Customizados

Script em Node.js para extrair commits convencionais:

const { execSync } = require('child_process');

function generateChangelog() {
  const log = execSync('git log --oneline --format="%s"').toString();
  const commits = log.split('\n').filter(c => c.match(/^(feat|fix|docs|refactor|test|chore)/));

  const sections = {
    'Features': commits.filter(c => c.startsWith('feat')),
    'Bug Fixes': commits.filter(c => c.startsWith('fix')),
    'Documentation': commits.filter(c => c.startsWith('docs'))
  };

  let changelog = '# Changelog\n\n';
  for (const [section, items] of Object.entries(sections)) {
    if (items.length > 0) {
      changelog += `## ${section}\n`;
      items.forEach(item => changelog += `- ${item}\n`);
      changelog += '\n';
    }
  }
  return changelog;
}

console.log(generateChangelog());

Script shell para filtrar commits por tipo:

#!/bin/bash
echo "### Features"
git log --oneline --grep="^feat"
echo ""
echo "### Bug Fixes"
git log --oneline --grep="^fix"
echo ""
echo "### Breaking Changes"
git log --oneline --grep="BREAKING CHANGE"

6. Boas Práticas e Validação de Commits

commitlint

Valida mensagens de commit no momento do git commit:

npm install --save-dev @commitlint/cli @commitlint/config-conventional

Configuração commitlint.config.js:

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'refactor', 'test', 'chore']],
    'subject-case': [2, 'always', 'lower-case']
  }
};

husky

Configura hooks para validação automática:

npm install husky --save-dev
npx husky init

Criar hook .husky/commit-msg:

#!/bin/sh
npx --no -- commitlint --edit $1

Para monorepo, configure escopos específicos:

rules: {
  'scope-enum': [2, 'always', ['frontend', 'backend', 'shared']]
}

7. Casos Especiais e Resolução de Problemas

Merges e rebases

Após um merge, use --no-ff para preservar histórico. Em rebases, evite squash de commits convencionais com tipos diferentes.

Commits com múltiplos tipos

Priorize o tipo mais significativo. Um commit que adiciona feature e corrige bug deve usar feat se a feature for o foco principal.

Monorepo com versionamento independente

Use commit-and-tag-version com configuração por pacote:

{
  "bumpFiles": ["packages/*/package.json"],
  "skip": {
    "tag": true
  }
}

Ou utilize semantic-release com o plugin @semantic-release/monorepo:

{
  "plugins": [
    "@semantic-release/monorepo",
    "@semantic-release/commit-analyzer"
  ]
}

Conclusão

A adoção de Conventional Commits combinada com versionamento semântico cria um fluxo de desenvolvimento previsível e automatizável. Ferramentas como semantic-release e standard-version eliminam erros manuais, enquanto scripts customizados oferecem flexibilidade para projetos específicos. A validação com commitlint e husky garante consistência desde o início, reduzindo retrabalho e melhorando a documentação automática do projeto.


Referências