Automatizando o versionamento semântico com Git Hooks

Automatizando o versionamento semântico com Git Hooks

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.

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, que promete ser invocado sempre que um merge é realizado com sucesso. Contei com a ajuda da lib husky para lidar melhor com os hooks.

#!/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

//./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.

Please follow and like us:
Pin Share