Automatizando o versionamento semântico com Git Hooks
Published: 2024-07-06 01:06:23

---
title: Automatizando o versionamento semântico com Git Hooks
published: true
description:
tags: 
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uaudx5prnr2bajfv4gd4.png
# Use a ratio of 100:42 for best results.
# published_at: 2024-07-05 23:47 +0000
---

## Cenário

Venho trabalhando na criação de uma biblioteca para componentizar um design system e durante este processo, senti a necessidade de automatizar a atualização de versão para evitar precisar fazer isso manualmente toda vez.

Pensei em criar algo que pudesse pelo menos semi-automatizar os incrementos de cada seção da versão, assim, toda vez que houvesse um merge na branch master, a versão seria incrementada. Já que normalmente a master é a linha do tempo estável, para produção. 

## Contexto

No GitFlow a master pode ser atualizada de duas maneiras: a primeira é pelo fechamento de uma release. A outra, pelo fechamento de uma hotfix.

| ![master sendo atualizada pelo fechamento de uma release](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/abkh5iuls4kal7wybqk7.png) | ![master sendo atualizada pelo fechamento de uma hotfix](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzs2k9abjwt4flpla4a9.png) |
|---------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|

Normalmente, o que é tratado em uma `hotfix` tende a ser ajustes pontuais ou correções de bugs, sendo assim, atualizar apenas o patch está de bom tamanho. 

Já para o que vem de uma release é um conteúdo mais substancial, então eu queria a possibilidade de escolher se eu atualizo a major, minor ou patch da nova versão.

## Abordagem

Pensei em criar um script para rodar na linha de comando para lidar com estes casos, e aliar isso aos hooks do git acaba sendo bastante pertinente.


### O git hook

Para o hook, dado o contexto, faz sentido usar o de [`post-merge`](https://git-scm.com/docs/githooks#_post_merge), que promete ser invocado sempre que um merge é realizado com sucesso. Contei com a ajuda da lib [`husky`](https://typicode.github.io/husky/) para lidar melhor com os hooks.

```bash 
#!/bin/bash

current_branch=$(git rev-parse --abbrev-ref HEAD)
commit_message=$(git log -1 --pretty=%B)

if [ "$current_branch" == "master" ]; then
  npm test
  if [[ "$commit_message" == *"release/"* ]]; then
    start node bump.js ; exit
  elif [[ "$commit_message" == *"hotfix/"* ]]; then
    npm version patch
  fi
fi
```
O comando `git rev-parse --abbrev-ref HEAD` nos diz qual é a branch atual e o `git log -1 --pretty=%B` recupera qual foi a última mensagem de commit.

No GitFlow, sempre quando mergeamos uma branch, uma mensagem de commit é aplicada no modelo *Merge branch release/nome_da_release*, o mesmo acontece com a hotfix. Por isso recuperar a última mensagem de commit é importante, é nela que terá nosso fator condicional.

Um dos requisitos é  que funcionasse apenas na branch master o que explica a primeira condicional, uma vez que estamos na master, é verificado se o ultimo commit vem de uma release ou hotflix, como está no trecho acima.

### O script de escolha

Essa é a parte do texto onde confesso que nunca tinha feito um CLI antes. Não sabia por onde começar. Foi então que lembrei que o CLI do Vite é bem legalzinho e fui ver o que eles usam por lá.

Para minha felicidade, é feito em JavaScript. Usando um conjuntos dos pacotes `cross-spawn`, `prompts` e `kolorist` 

- `cross-spawn`: para rodar comandos de linha de comando.
- `prompts`: para criar prompts interativos e inquerir informações do usuário
- `kolorist`: adiciona cores aos prompts

```javascript
//./bump.js

import spawn from 'cross-spawn';
import prompts from "prompts";
import pkg from "./package.json" assert { type: 'json' };

import {
  cyan,
  green,
  magenta,
  yellow
} from 'kolorist';

(async () => {
  const current_major = Number(pkg.version[0])
  const current_minor = Number(pkg.version[2])
  const current_patch = Number(pkg.version[4])

  const choices = [
    {
      title: green(`Major`),
      description: `${pkg.version} ➡️  ${`${current_major + 1}.0.0`}`,
      value: 'npm version major',
    },
    {
      title: cyan(`Minor`),
      description: `${pkg.version} ➡️  ${current_major}.${current_minor + 1}.0`,
      value: 'npm version minor',
    },
    {
      title: yellow("Patch"),
      description: `${pkg.version} ➡️  ${current_major}.${current_minor}.${current_patch + 1}`,
      value: 'npm version patch',
    }
  ]

  const response = await prompts({
    type: 'select',
    name: 'value',
    message: `Você acabou de realizar o fechamento de uma release. 
    
  Para que o fluxo possa continuar, atualize a versão da lib ${magenta(pkg.name)}.
    
  Qual das opções abaixo mais faz sentido para as alterações presentes nesta release? 

  Lembrando:

  ${green('MAJOR')}: é a versão que contém mudanças incopatíveis, breaking changes.
  ${cyan('MINOR')}: é a versão que adiciona funcionalidades, com campatibilidade.
  ${yellow('PATCH')}: é a versão que adiciona ajustes gerais ou de bugs, mantendo compatibilidade.

  Escolha 👇
  
  `,
    initial: 2,
    choices
  });

  const { status } = spawn.sync(response.value, [], {
    stdio: 'inherit',
  })

  process.exit(status ?? 0)
})();
```
No código, a ideia foi pegar a versão atual importando o package.json e em cima disso, calcular as possíveis novas versões para major, minor e patch e transformar isso em escolhas para o usuário. Dependendo da escolha, o comando npm é rodado para atualizar a versão.

Com isso, o objetivo foi atingido. agora, sempre que a master for alimentada pela hotfix ou release, a versão será incrementada para patch ou abrirá o terminal para escolha dependendo da qualidade do conteúdo da release, a escolha do usuário.

![script rodando com cursor na opção patch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hlbkj3dw7kbcqdvcbpg9.png)![script rodando com cursor na opção minor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a3us0wsorv9c7n2qg3o6.png)![script rodando com cursor na opção major](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bm343m6gspt33arbtwf7.png)