Linting en CI/CD
Ejecuta ESLint, Prettier y verificación de tipos con TypeScript en GitHub Actions para garantizar la calidad del código en cada solicitud de extracción.
Receta
Tarjeta de receta de referencia rápida — lista para copiar y pegar.
// scripts de package.json
{
"scripts": {
"lint": "next lint",
"format:check": "prettier --check .",
"type-check": "tsc --noEmit"
}
}# Ejecuta todos los controles localmente (igual que CI)
npm run lint && npm run format:check && npm run type-checkCuándo usarlo: En cada proyecto que usa solicitudes de extracción. CI es tu red de seguridad para detectar problemas que los hooks previos a commit no detectan.
Ejemplo de Funcionamiento
# .github/workflows/code-quality.yml
name: Code Quality
on:
pull_request:
branches: [main]
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
name: Lint, Format & Type Check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: ESLint
run: npm run lint
- name: Prettier
run: npm run format:check
- name: TypeScript
run: npm run type-checkLo que esto demuestra:
- Tres controles se ejecutan en secuencia: ESLint, verificación de formato de Prettier y verificación de tipos de TypeScript
npm ciinstala versiones exactas del archivo de bloqueo (más rápido y determinista)cache: "npm"cacheanode_modulesentre ejecuciones para mayor velocidadconcurrencycancela las ejecuciones en progreso cuando se envían nuevos commitstimeout-minutesevita que los trabajos atascados se ejecuten indefinidamente
Análisis Profundo
Cómo Funciona
- GitHub Actions ejecuta el flujo de trabajo en cada solicitud de extracción y envío a
main - Cada paso sale con un código distinto de cero en caso de fallo, lo que marca la verificación de PR como fallida
npm run lintejecutanext lint, que sale con código 1 si hay erroresprettier --checksale con código 1 si algún archivo no está formateado correctamentetsc --noEmitsale con código 1 si hay errores de tipo- Los controles fallidos bloquean la fusión cuando las reglas de protección de rama lo requieren
Variaciones
Trabajos paralelos (más rápido para proyectos grandes):
jobs:
lint:
name: ESLint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- run: npm run lint
format:
name: Prettier
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- run: npm run format:check
type-check:
name: TypeScript
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- run: npm run type-checkCon Biome (un único control reemplaza lint + formato):
- name: Biome
run: npx biome check .Comentarios de revisión de PR con reviewdog:
- name: ESLint with reviewdog
uses: reviewdog/action-eslint@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
eslint_flags: "src/"Esto publica errores de ESLint como comentarios de revisión en la PR en las líneas exactas que necesitan corrección.
Caché para pnpm:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfileConfiguración de protección de rama:
GitHub repo → Settings → Branches → Branch protection rules:
✓ Require status checks to pass before merging
✓ Require branches to be up to date before merging
Status checks: "Lint, Format & Type Check" (o nombres de trabajos individuales)
Notas sobre TypeScript
// tsc --noEmit verifica TODO el proyecto, no solo los archivos modificados.
// Esto es intencional — un cambio en un archivo puede romper tipos en otro lugar.
// Ejemplo: cambiar un tipo compartido
// types.ts
export type User = {
name: string;
email: string;
role: "admin" | "user"; // Agregar "moderator" aquí es seguro
};
// Pero eliminar "admin" rompe todo archivo que usa User.role === "admin"
// Solo tsc detecta esto — ESLint no puede.Trampas
Cosas que te atraparán. Cada trampa incluye qué sale mal, por qué sucede y la corrección.
-
CI pasa pero falla localmente (o viceversa) — Diferentes versiones de Node.js, diferentes versiones de dependencias o comportamiento específico del SO. Corrección: Fija la versión de Node.js en CI para que coincida con la local. Usa
npm ci(nonpm install) para usar versiones exactas del archivo de bloqueo. -
Errores de lint en archivos generados — CI lintea archivos que se generan durante la compilación (p. ej., cliente de Prisma, tipos de GraphQL). Corrección: Agrega directorios generados a
.eslintignoreo al arrayignoreseneslint.config.mjsy.prettierignore. -
tsc es lento en CI — La verificación de tipos de TypeScript puede tomar 30 segundos o más en proyectos grandes. Corrección: Habilita
"incremental": trueentsconfig.jsony cachea el archivo.tsbuildinfoentre ejecuciones de CI. Alternativamente, usa@vercel/next-swcque realiza verificación de tipos más rápido. -
La verificación de Prettier falla en finales de línea — Los desarrolladores de Windows envían archivos con CRLF, CI se ejecuta en Linux con LF. Corrección: Establece
"endOfLine": "lf"en.prettierrcy configura Git:git config --global core.autocrlf input. -
Protección de rama no aplicada — Los controles de estado solo bloquean la fusión si la protección de rama está configurada. Sin él, cualquiera puede fusionar PRs fallidas. Corrección: Habilita reglas de protección de rama en
mainy requiere que el trabajo de CI pase.
Alternativas
Otras formas de resolver el mismo problema — y cuándo cada una es la mejor opción.
| Alternativa | Úsalo Cuando | No lo Uses Cuando |
|---|---|---|
| Husky + lint-staged | Quieres detectar problemas antes de que lleguen a CI | Necesitas una red de seguridad de CI (usa ambos) |
| GitLab CI / CircleCI | No estás en GitHub | Estás en GitHub (Actions es nativo) |
trunk check | Quieres una herramienta de CI unificada que gestione linters por ti | Quieres control total sobre tu pipeline de CI |
| Vercel deployment checks | Solo te importan errores en tiempo de compilación | Quieres aplicación de lint y formato |
Preguntas Frecuentes
¿Por qué necesito linting en CI si ya tengo hooks previos a commit?
- Los hooks de Git son solo locales y se pueden omitir con
--no-verify. - Los entornos de CI no ejecutan hooks previos a commit.
- CI es la red de seguridad que detecta lo que los hooks pierden.
¿Qué hace npm ci diferente de npm install?
npm ciinstala versiones exactas del archivo de bloqueo (determinista).- Elimina
node_modulesprimero para una instalación limpia. - Es más rápido que
npm installen CI porque omite la resolución de dependencias.
¿Cómo ayuda la configuración de concurrency?
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true- Cancela las ejecuciones de flujo de trabajo en progreso cuando se envían nuevos commits a la misma rama.
- Esto ahorra minutos de CI y evita resultados obsoletos.
¿Debo ejecutar lint, verificación de formato y verificación de tipo en paralelo o en secuencia?
- Secuencial (trabajo único) es más simple y usa una instalación
npm ci. - Paralelo (trabajos separados) es más rápido para proyectos grandes pero triplica el tiempo de instalación.
- Para la mayoría de proyectos, secuencial es suficiente.
Trampa: ¿Por qué CI pasa pero el linting local falla (o viceversa)?
- Diferentes versiones de Node.js entre local y CI.
npm installlocalmente puede resolver versiones diferentes quenpm cien CI.- Fija la versión de Node.js en CI para que coincida con la local, y siempre usa
npm ci.
¿Cómo uso Biome en lugar de ESLint + Prettier en CI?
- name: Biome
run: npx biome check .Un único comando reemplaza tanto lint como verificación de formato.
¿Qué es reviewdog y por qué lo usaría?
- Publica errores de ESLint como comentarios de revisión en la PR en las líneas exactas que necesitan corrección.
- Los desarrolladores ven problemas directamente en el diff de PR en lugar de leer registros de CI.
- Usa la GitHub Action
reviewdog/action-eslint@v1.
Trampa: ¿Por qué Prettier falla en finales de línea en CI?
- Los desarrolladores de Windows envían archivos con CRLF, pero CI se ejecuta en Linux con LF.
- Establece
"endOfLine": "lf"en.prettierrc. - Configura Git con
git config --global core.autocrlf input.
¿Cómo hago cumplir que los controles de CI deben pasar antes de fusionar?
- Ve a GitHub repo Settings, luego Branches, luego Branch protection rules.
- Habilita "Require status checks to pass before merging."
- Selecciona el nombre de tu trabajo de CI como un control requerido.
- Sin esto, cualquiera puede fusionar PRs fallidas.
¿Por qué tsc --noEmit verifica todo el proyecto en lugar de solo los archivos modificados?
- TypeScript necesita contexto completo del proyecto porque un cambio en un archivo puede romper tipos en otro lugar.
- Por ejemplo, eliminar un miembro de un tipo compartido rompe todo archivo que lo usa.
- ESLint no puede detectar errores de tipo entre archivos; solo
tscpuede.
¿Cómo acelero tsc en CI?
- Habilita
"incremental": trueentsconfig.json. - Cachea el archivo
.tsbuildinfoentre ejecuciones de CI. - Esto omite la verificación de archivos que no han cambiado.
¿Cómo evito que CI lintee archivos generados?
- Agrega directorios generados al array
ignoreseneslint.config.mjs. - Agrega también a
.prettierignore. - Ejemplos comunes: cliente de Prisma, tipos de GraphQL, archivos de migración.
Relacionado
- Configuración de ESLint para Next.js — Configuración de ESLint que CI ejecuta
- Configuración de Prettier — Configuración de Prettier para verificación de formato
- Modo Estricto de TypeScript — Opciones de tsconfig que
tsc --noEmitaplica - Husky y lint-staged — Los hooks previos a commit complementan a CI
- Integración ESLint + Prettier — Asegurar comportamiento consistente entre local y CI