Skip to content

Husky y lint-staged

Estas dos herramientas no revisan nada por sí mismas. Son fontanería: se encargan de que los linters se ejecuten en el momento adecuado y sobre los archivos adecuados.

Git tiene un sistema de hooks: scripts que se ejecutan automáticamente en determinados momentos. Viven en la carpeta .git/hooks/ de cada repositorio y los más usados son:

HookCuándo se ejecutaSi falla…
pre-commitJusto antes de crear el commitel commit no se crea
commit-msgDespués de escribir el mensaje, antes de crear el commitel commit no se crea
pre-pushAntes de enviar los commits al remotoel push no se realiza

Husky permite guardar los hooks en una carpeta del repositorio (.husky/), que sí se versiona, y le dice a Git que busque los hooks ahí.

En el package.json hay un script llamado prepare:

package.json
{
"scripts": {
"prepare": "husky"
}
}

prepare es un script especial de npm/pnpm: se ejecuta automáticamente después de cada install. Así que cuando alguien clona el repositorio y hace pnpm install, Husky se configura solo. Nadie tiene que acordarse de nada.

Lo que hace husky al ejecutarse es una única cosa relevante:

Terminal window
git config core.hooksPath .husky/_

Con eso Git deja de mirar en .git/hooks/ y empieza a mirar en .husky/_/, una carpeta de arranque que Husky genera y que reenvía la ejecución a los scripts que hay en .husky/.

  • Directory.husky/
    • Directory_/ carpeta generada por Husky; no se toca ni se versiona
      • …
    • pre-commit script que se ejecuta antes de crear el commit
    • commit-msg script que valida el mensaje

Los archivos son scripts de shell normales. El de commit-msg es de una línea:

.husky/commit-msg
npx --no -- commitlint --edit "$1"

Pasar ESLint por un proyecto Nuxt entero tarda minutos. Si el pre-commit hiciera eso, todo el equipo empezaría a usar --no-verify en una semana. lint-staged lo evita: ejecuta cada comando solo sobre los archivos que están en el área de staging.

  1. Pregunta a Git qué archivos hay en staging (los que has añadido con git add).

  2. Los agrupa según los patrones de su configuración.

  3. Ejecuta el comando indicado, pasándole solo esa lista de archivos como argumentos.

  4. Si el comando ha modificado archivos (por ejemplo con --fix), los vuelve a añadir al staging automáticamente, para que las correcciones entren en el commit.

  5. Si algún comando falla, aborta el commit.

.lintstagedrc
{
"**/*.{ts,js,vue}": "eslint --fix",
"**/*.{vue,css,scss}": "stylelint --fix"
}

Se lee así: “para todo archivo en staging cuya extensión sea .ts, .js o .vue, ejecuta eslint --fix; para todo archivo .vue, .css o .scss, ejecuta stylelint --fix”.

Dos consecuencias que conviene entender:

  • Un .vue pasa por las dos herramientas. Coincide con los dos patrones, así que se le aplica ESLint (a su <script> y <template>) y Stylelint (a su <style>). Es intencionado.
  • Prettier no aparece, y no es un olvido. Prettier se ejecuta dentro de ESLint, a través de la regla prettier/prettier que aporta eslint-plugin-prettier. Añadir aquí una entrada para Prettier sería redundante y además peligroso: dos herramientas reescribiendo el mismo archivo en paralelo pueden pisarse.
Terminal window
npx lint-staged --concurrent false --relative
FlagEfecto
--concurrent falseEjecuta las tareas una detrás de otra en lugar de en paralelo. Más lento, pero evita que ESLint y Stylelint escriban el mismo .vue a la vez.
--relativePasa las rutas relativas al directorio de trabajo en lugar de absolutas. Necesario en Windows, donde las rutas absolutas con letra de unidad (C:\) rompen algunos patrones de glob.
--debugMuestra exactamente qué comandos se ejecutan y con qué archivos. La primera opción cuando algo no cuadra.
git commit -m "feat(cart): añadido el cupón"
│
│ Git mira core.hooksPath → .husky/_
│
├─► .husky/pre-commit
│ └── npx lint-staged --concurrent false --relative
│ ├── eslint --fix app/components/Cupon.vue app/utils/precio.ts
│ └── stylelint --fix app/components/Cupon.vue
│ │
│ ├── ¿errores no arreglables? → ❌ commit abortado
│ └── ¿todo ok? → los cambios se re-añaden al staging
│
└─► .husky/commit-msg
└── npx --no -- commitlint --edit .git/COMMIT_EDITMSG
├── ¿mensaje inválido? → ❌ commit abortado
└── ¿válido? → ✅ commit creado
SíntomaCausa probableSolución
Los hooks no se ejecutanHusky no está instalado en tu clon: core.hooksPath no apunta a .husky/_.pnpm install (dispara prepare) o npx husky directamente.
hook was ignored because it's not set as executableEn sistemas Unix el script no tiene permiso de ejecución.chmod +x .husky/pre-commit
El pre-commit tarda muchísimoNo es lint-staged: normalmente es otro paso del script, como un typecheck completo del proyecto.Mira el contenido de .husky/pre-commit.
lint-staged no encuentra archivos en WindowsRutas absolutas con letra de unidad.Añadir --relative.