05.06 — Husky + lint-staged + commitlint + commitizen#

Discipline de commits stricte. Conventionnels, signés, format-checkés.

Outils#

OutilRôle
HuskyInstalle les hooks Git locaux (pre-commit, commit-msg)
lint-stagedLance des outils seulement sur les fichiers staged
commitlintVérifie que le message de commit suit la convention
commitizen + cz-commitlintCLI interactive pour composer un message conforme

Config#

.husky/#

Dossier contenant les hook scripts :

.husky/
├── pre-commit
└── commit-msg

pre-commit :

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

commit-msg :

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm exec commitlint --edit "$1"

.lintstagedrc.cjs#

module.exports = {
  "*.{js,ts,jsx,tsx,json,md,mdx,html,css,scss,yml,yaml}": ["prettier --write"],
};

→ Sur chaque fichier staged : prettier --write. Si Prettier modifie, les modifications sont automatiquement rattachées au commit.

.commitlintrc.cjs#

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

→ Hérite de @commitlint/config-conventional :

<type>(<scope>): <subject>

<body>

<footer>

Types acceptés : feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.

.czrc#

{ "path": "@commitlint/cz-commitlint" }

→ Indique à commitizen d’utiliser @commitlint/cz-commitlint comme adapter. La commande pnpm commit lance la CLI interactive.

Chaîne de commit#

1. Développeur : git add foo.tsx
   ↓
2. Développeur : pnpm commit
   ↓
3. commitizen (cz-commitlint) ouvre une CLI interactive :
      ? Select the type of change: feat
      ? What is the scope (optional): dashboard
      ? Write a short, imperative tense description: add OTP screen
      ? Provide a longer description: ...
      ? Are there any breaking changes? no
      ? Issues this commit closes: -
   ↓
4. Le message est validé localement contre la config conventional
   ↓
5. git-cz appelle `git commit` avec le message construit
   ↓
6. .husky/pre-commit s'exécute :
      lint-staged → prettier --write sur les fichiers staged
   ↓
7. .husky/commit-msg s'exécute :
      commitlint --edit COMMIT_EDITMSG
      → vérifie une dernière fois (au cas où l'utilisateur a fait git commit -m direct)
   ↓
8. Si tout passe : commit créé

Pipeline alternatif : git commit -m "..."#

Si le développeur shortcut via git commit -m "fix some bug" (sans utiliser pnpm commit) :

  1. pre-commit lance lint-staged (prettier sur les staged).
  2. commit-msg lance commitlint --edit "$1".
    • « fix: some bug » serait OK. « stuff » serait rejeté.

Donc : on peut shortcut, mais le commitlint impose la convention quand même.

is-ci || husky dans prepare#

"scripts": {
  "prepare": "is-ci || husky"
}

→ Le script prepare est exécuté automatiquement par pnpm install (ou npm install).

  1. Si on est en CI (is-ci retourne true), || court-circuite, husky n’est pas installé.
  2. Si on est en local, is-ci retourne false, husky est installé (= les hooks .husky/* sont enregistrés dans .git/hooks/*).

Discipline#

BénéficeDétail
Historique git lisiblegit log --oneline donne feat(dashboard): add OTP screen, exploitable par gh release (changelog)
Pas de PR avec format casséPrettier auto-applique sur staged, on ne commit jamais du code non-formaté
Plus de discussion sur le format en revueLe format est décidé par Prettier, pas par la revue

Format vs contenu#

Le pre-commit lint-staged applique Prettier mais n’applique pas ESLint.

  • Prettier corrige le format sans changer la sémantique. Auto-safe.
  • ESLint peut détecter des erreurs sémantiques (unused var, missing dep dans useEffect). Si on le lançait en pre-commit, il pourrait bloquer le commit pour quelque chose qui mérite d’être discuté en revue.

→ Le lint est laissé pour la CI (wireit lint). Le format est immédiat.

Pourquoi l’Igoristan a tout ça pour un projet « démo »#

Pour avoir l’approbation de Napoléon.