DevOps Admin

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

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

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: feat para novas funcionalidades, fix para correções, docs para documentação, style para formatação, refactor para refatoração, perf para melhorias de performance, e test para testes. Mudanças quebradoras devem ser indicadas com BREAKING CHANGE: no corpo ou com ! após o escopo.

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

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

// .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']
  }
};

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

npm install --save-dev @commitlint/config-conventional @commitlint/cli husky
npx husky install
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

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

$ 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)

Semantic Versioning: Entendendo MAJOR.MINOR.PATCH

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 é MAJOR.MINOR.PATCH, onde MAJOR é incrementado para mudanças quebradoras, MINOR para novas funcionalidades retrocompatíveis, e PATCH para correções de bugs retrocompatíveis.

Quando você libera uma versão 2.5.3, 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 2.5.0 e atualiza para 2.5.3, ele sabe que não há risco de quebra. Já uma atualização para 3.0.0 sinaliza que há mudanças incompatíveis.

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

// 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"
  }
}

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

npm install --save-dev semantic-release
npx semantic-release --init

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

// .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"
  ]
}

Changelogs Automatizados: Gerando Documentação do Histórico

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.

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

npm install --save-dev conventional-changelog-cli
npx conventional-changelog -p angular -i CHANGELOG.md -s

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:

{
  "scripts": {
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
    "release": "npm run changelog && npm version && npm publish"
  }
}

Um changelog gerado automaticamente fica assim:

# [2.5.0](https://github.com/usuario/projeto/compare/2.4.0...2.5.0) (2024-01-15)

### Features

* **auth**: adicionar suporte OAuth2 ([abc1234](https://github.com/usuario/projeto/commit/abc1234))
* **api**: endpoint para listar usuários ([def5678](https://github.com/usuario/projeto/commit/def5678))

### Bug Fixes

* **validation**: corrigir validação de email ([ghi9012](https://github.com/usuario/projeto/commit/ghi9012))

### BREAKING CHANGES

* **auth**: endpoint /login foi removido em favor de /oauth/authorize ([abc1234](https://github.com/usuario/projeto/commit/abc1234))

---

# [2.4.0](https://github.com/usuario/projeto/compare/2.3.1...2.4.0) (2024-01-08)

### Features

* **database**: adicionar suporte PostgreSQL ([jkl3456](https://github.com/usuario/projeto/commit/jkl3456))

Para uma solução completa que automatiza tudo (Conventional Commits + versionamento + changelog), a melhor abordagem é usar semantic-release. 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:

# .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 }}

Integração Prática: Pondo Tudo Junto

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.

Primeiro, a configuração inicial:

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"'

Agora os arquivos de configuração:

// .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', '.']
  }
};
// .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}"
      }
    ]
  ]
}
// package.json - seção scripts
{
  "scripts": {
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
    "version": "npm run changelog && git add CHANGELOG.md"
  }
}

Com essa configuração, aqui está o fluxo normal de trabalho:

# 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

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

- 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)

Conclusão

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

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.

Referências

  • https://www.conventionalcommits.org/ — Documentação oficial do Conventional Commits
  • https://semver.org/ — Especificação oficial do Semantic Versioning
  • https://semantic-release.gitbook.io/ — Documentação completa do semantic-release
  • https://github.com/conventional-changelog/conventional-changelog — Repositório oficial do conventional-changelog
  • https://commitlint.js.org/ — Documentação do commitlint para validação de commits

Artigos relacionados