← Retour aux articles
Tooling HuskyGitDX

Husky, lint-staged et Commitlint : automatiser la qualité du code avec les Git hooks

· 6 min de lecture

Les code reviews attrapent des bugs, mais elles ne devraient pas attraper des problèmes de formatage, des violations de lint ou des messages de commit cassés. C’est du travail mécanique qui appartient aux machines. Husky intercepte les Git hooks pour lancer des vérifications automatiques avant que le code n’atteigne le dépôt distant. En l’associant à lint-staged pour du linting ciblé et Commitlint pour des messages de commit structurés, on obtient un pipeline qui enforce les standards de qualité silencieusement, à chaque commit, sans compter sur la discipline des développeurs.

Husky : des Git hooks qui tournent vraiment

Git supporte les hooks nativement, des scripts shell dans .git/hooks/ qui se déclenchent sur des événements comme pre-commit ou commit-msg. Le problème, c’est que .git/ n’est pas suivi par le contrôle de version. Chaque développeur de l’équipe doit configurer les hooks manuellement, et personne ne le fait. Husky résout ça en stockant les configurations de hooks dans le dépôt et en les installant automatiquement.

{
  "scripts": {
    "prepare": "husky"
  }
}
npx lint-staged

Le script prepare s’exécute après npm install ou pnpm install, garantissant que les hooks sont actifs pour chaque développeur qui clone le dépôt. Pas de documentation de setup, pas d’étape d’onboarding, pas de “ça marche sur ma machine.” Les hooks vivent dans .husky/ et sont versionnés comme n’importe quel autre fichier.

lint-staged : linter uniquement ce qui a changé

Lancer ESLint et Prettier sur toute la codebase à chaque commit, c’est lent et bruyant. lint-staged cible les vérifications aux fichiers dans la staging area Git, ceux qui sont réellement committés.

export default {
  '*.{ts,tsx}': ['eslint --fix', 'prettier --write'],
  '*.{css,scss}': ['prettier --write'],
  '*.{json,md,mdx}': ['prettier --write'],
};

Chaque pattern glob correspond à un tableau de commandes. lint-staged passe les chemins des fichiers stagés en arguments, donc ESLint ne vérifie que les fichiers touchés. Si un fix est appliqué (règle de lint auto-fixable ou formatage Prettier), lint-staged re-stage le fichier modifié automatiquement.

C’est critique pour les grosses codebases. Un monorepo avec 500 fichiers TypeScript n’a pas besoin de tous les linter parce qu’on a changé un composant. lint-staged garde le hook pre-commit sous la seconde pour la plupart des commits.

Commitlint : des messages de commit structurés

Les messages de commit sont de la documentation. Un historique Git bien structuré raconte pourquoi un changement a été fait, pas juste quels fichiers ont été modifiés. Commitlint enforce la spécification Conventional Commits, en rejetant les messages qui ne suivent pas le format.

npx --no -- commitlint --edit $1
export default {
  extends: ['@commitlint/config-conventional'],
};

Le format Conventional Commits suit une structure stricte.

type(scope): description

feat(auth): add OAuth2 login flow
fix(cart): prevent duplicate items on rapid clicks
docs(readme): update deployment instructions
refactor(api): extract validation into middleware
chore(deps): update eslint to v9

Les types valides incluent feat, fix, docs, style, refactor, perf, test, build, ci, chore et revert. Le scope est optionnel mais utile pour filtrer l’historique. Commitlint rejette tout ce qui ne correspond pas, donc “fix stuff” ou “wip” n’atterrissent jamais dans le log.

Le retour sur investissement se compose avec le temps. git log --oneline devient un changelog lisible. Les outils de release automatisés comme semantic-release peuvent générer des numéros de version et des changelogs à partir des types de commit. feat bumpe le minor, fix bumpe le patch, BREAKING CHANGE dans le footer bumpe le major.

Combiner les trois

Le setup complet chaîne Husky, lint-staged et Commitlint dans deux Git hooks qui couvrent la qualité du code et l’hygiène des commits.

npx lint-staged
npx --no -- commitlint --edit $1
export default {
  '*.{ts,tsx}': ['eslint --fix', 'prettier --write'],
  '*.{css,scss}': ['prettier --write'],
  '*.{json,md,mdx}': ['prettier --write'],
};
export default {
  extends: ['@commitlint/config-conventional'],
};

Le workflow devient invisible. Un développeur écrit du code, stage des fichiers et commit. lint-staged auto-fixe le formatage et attrape les erreurs de lint avant que le commit ne soit créé. Commitlint valide le format du message. Si l’un des deux échoue, le commit est rejeté avec une erreur claire. Si les deux passent, le commit passe sans friction.

Ajouter les vérifications TypeScript au pipeline

Le linting attrape les problèmes de style, mais pas les erreurs de types. Ajouter une vérification TypeScript au hook pre-commit garantit que des types cassés n’atteignent jamais le dépôt.

npx lint-staged
npx tsc --noEmit

Contrairement à lint-staged, tsc doit tourner sur le projet complet parce que la vérification de types est globale. Un changement dans un fichier peut casser les types dans un autre. Ça ajoute quelques secondes au commit, mais attraper une erreur de type localement est infiniment plus rapide que la découvrir en CI vingt minutes plus tard.

Pour les gros projets où tsc est trop lent pour chaque commit, on peut le déplacer dans un hook pre-push.

npx tsc --noEmit

Ça garde les commits rapides tout en attrapant les erreurs de types avant que le code n’atteigne le remote.

Gérer les cas particuliers

Parfois un développeur doit bypasser les hooks. Un hotfix d’urgence, un commit work-in-progress sur une feature branch, ou un commit qui casse intentionnellement une règle de lint. Git supporte ça nativement.

git commit --no-verify -m "wip: temporary broken state"

Le flag --no-verify saute tous les hooks. Ça doit être rare et délibéré. Si une équipe l’utilise fréquemment, les hooks sont probablement trop stricts ou trop lents, deux problèmes qu’on peut résoudre.

Pour lint-staged spécifiquement, on peut ignorer des fichiers en les ajoutant au .lintstagedrc avec un tableau de commandes vide, ou en utilisant les commentaires disable inline d’ESLint pour les cas exceptionnels. L’objectif ce sont des garde-fous, pas une prison.

Conclusion

Husky, lint-staged et Commitlint forment un quality gate léger qui tourne à chaque commit sans ralentir personne. Le formatage est consistant, les règles de lint sont enforcées, les messages de commit suivent une convention, et les erreurs de types sont attrapées tôt. Le setup prend dix minutes et se rentabilise dès la première pull request qui n’a pas besoin d’un commentaire “fix formatting.”