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 hooks: el concepto de base
Section titled “Git hooks: el concepto de base”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:
| Hook | Cuándo se ejecuta | Si falla… |
|---|---|---|
pre-commit | Justo antes de crear el commit | el commit no se crea |
commit-msg | Después de escribir el mensaje, antes de crear el commit | el commit no se crea |
pre-push | Antes de enviar los commits al remoto | el 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í.
Cómo se instala
Section titled “Cómo se instala”En el package.json hay un script llamado prepare:
{ "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:
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/.
Estructura de la carpeta
Section titled “Estructura de la carpeta”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:
npx --no -- commitlint --edit "$1"lint-staged
Section titled “lint-staged”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.
Cómo funciona
Section titled “Cómo funciona”-
Pregunta a Git qué archivos hay en staging (los que has añadido con
git add). -
Los agrupa según los patrones de su configuración.
-
Ejecuta el comando indicado, pasándole solo esa lista de archivos como argumentos.
-
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. -
Si algún comando falla, aborta el commit.
La configuración: .lintstagedrc
Section titled “La configuración: .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
.vuepasa 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/prettierque aportaeslint-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.
Flags útiles
Section titled “Flags útiles”npx lint-staged --concurrent false --relative| Flag | Efecto |
|---|---|
--concurrent false | Ejecuta 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. |
--relative | Pasa 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. |
--debug | Muestra exactamente qué comandos se ejecutan y con qué archivos. La primera opción cuando algo no cuadra. |
Los dos juntos: el flujo completo
Section titled “Los dos juntos: el flujo completo”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 creadoProblemas habituales
Section titled “Problemas habituales”| Síntoma | Causa probable | Solución |
|---|---|---|
| Los hooks no se ejecutan | Husky 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 executable | En sistemas Unix el script no tiene permiso de ejecución. | chmod +x .husky/pre-commit |
El pre-commit tarda muchísimo | No 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 Windows | Rutas absolutas con letra de unidad. | Añadir --relative. |