Pre-commit hooks com Husky

1. Introdução aos Pre-commit Hooks e ao Husky

Hooks do Git são scripts que o Git executa automaticamente em momentos específicos do ciclo de vida de um repositório. O hook pre-commit é disparado imediatamente antes de um commit ser criado, permitindo que você valide ou modifique o conteúdo que será commitado.

Problemas comuns que hooks resolvem incluem:
- Commits com erros de lint não detectados
- Código com formatação inconsistente
- Testes quebrados sendo commitados acidentalmente
- Mensagens de commit mal formatadas

O Husky é uma ferramenta que simplifica drasticamente o gerenciamento de hooks Git. Em vez de editar manualmente a pasta .git/hooks/ (que não é versionada), o Husky cria uma pasta .husky/ versionável e fornece uma interface amigável para criar e gerenciar hooks.

2. Instalação e Configuração Inicial do Husky

A instalação pode ser feita via npm, yarn ou pnpm. O método mais rápido é usando o comando de inicialização:

npx husky-init && npm install

Para yarn:

npx husky-init && yarn

Para pnpm:

pnpm exec husky-init && pnpm install

Esse comando cria a estrutura de pastas necessária:

.husky/
  pre-commit
  _
  .gitignore

O arquivo pre-commit inicial contém:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm test

Após a instalação, é necessário habilitar os hooks no repositório. O Husky já configura isso automaticamente no package.json:

{
  "scripts": {
    "prepare": "husky install"
  }
}

O script prepare garante que husky install seja executado sempre que alguém rodar npm install no projeto.

3. Criando seu Primeiro Hook pre-commit

Vamos criar um hook que executa o linter antes de cada commit. Primeiro, adicione o script de lint no package.json:

{
  "scripts": {
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "prepare": "husky install"
  }
}

Agora, modifique o arquivo .husky/pre-commit:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint

Para testar o hook, tente fazer um commit com código que viole as regras do ESLint:

git add .
git commit -m "teste: hook deve impedir commit com erro de lint"

Se o lint falhar, o commit será abortado e você verá os erros no terminal. Para verificar se o hook está ativo, use:

git config core.hooksPath

A saída deve mostrar .husky (ou o caminho configurado).

4. Ferramentas Populares para Usar com Husky

lint-staged

O lint-staged permite rodar linters apenas nos arquivos que estão staged, economizando tempo em projetos grandes. Instale:

npm install --save-dev lint-staged

Configure no package.json:

{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{css,scss}": ["stylelint --fix", "prettier --write"],
    "*.{json,md}": ["prettier --write"]
  }
}

Atualize o hook pre-commit:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

Prettier

O Prettier formata automaticamente o código. Combinado com lint-staged, ele corrige a formatação dos arquivos staged antes do commit.

ESLint / Stylelint

ESLint para JavaScript/TypeScript e Stylelint para CSS podem ser configurados juntos no lint-staged, como no exemplo acima.

5. Boas Práticas na Escrita de Hooks

Mantenha hooks rápidos: hooks demorados frustram a equipe. Evite rodar a suíte completa de testes no pre-commit; deixe isso para o CI.

Tratamento de erros: sempre forneça mensagens claras sobre o que falhou:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo "🔍 Rodando validações pré-commit..."
npx lint-staged
if [ $? -ne 0 ]; then
  echo "❌ Validações falharam. Corrija os erros antes de commitar."
  exit 1
fi
echo "✅ Validações passaram!"

Controle de saída: use exit 0 para permitir o commit e exit 1 para abortá-lo.

Ignorar hooks temporariamente: use git commit --no-verify em situações excepcionais (commits de emergência, por exemplo).

6. Personalização e Hooks Adicionais

Além do pre-commit, o Husky permite criar hooks para outros eventos:

Hook commit-msg com commitlint

Instale o commitlint:

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

Crie o arquivo .husky/commit-msg:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no -- commitlint --edit $1

Crie o arquivo de configuração commitlint.config.js:

module.exports = {
  extends: ['@commitlint/config-conventional']
};

Hook pre-push

Para rodar testes antes de enviar para o repositório remoto:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run test

Usando variáveis de ambiente

Você pode acessar variáveis do Git nos hooks. Por exemplo, no commit-msg, $1 contém o caminho do arquivo de mensagem de commit.

7. Integração com Workflows em Equipe

Versionando a pasta .husky/: adicione .husky/ ao repositório. Isso garante que todos tenham os mesmos hooks.

Script postinstall: configure no package.json:

{
  "scripts": {
    "postinstall": "husky install",
    "prepare": "husky install"
  }
}

O postinstall garante que hooks sejam instalados mesmo se o prepare falhar.

Resolução de conflitos: em diferentes sistemas operacionais, problemas comuns incluem:
- Permissões de execução (no Windows, use git config core.hooksPath .husky)
- Caminhos de shell (use sh em vez de bash para compatibilidade)

8. Resolução de Problemas e Debug

Hooks não executando: verifique as permissões do arquivo:

chmod +x .husky/pre-commit

Verificar instalação do Git: confirme que o Git está configurado corretamente:

git config core.hooksPath

Modo debug do Husky:

npx husky --debug

Logs de erro: verifique a saída do terminal ao executar um commit. O Husky exibe erros diretamente lá.

Comando útil para diagnóstico:

git config --list | grep hooks

Se precisar resetar a configuração de hooks:

git config --unset core.hooksPath

Referências