scripts y los hooks de ciclo de vida
El campo scripts como interfaz de comandos del proyecto: los hooks pre y post, el papel especial de prepare, la disciplina de instalacion, el paso de argumentos con pnpm y por que los scripts de instalacion son la puerta favorita de la cadena de suministro.
El campo scripts convierte el package.json en la interfaz de comandos del proyecto: un lugar único y descubrible donde build, test, lint o dev significan siempre lo mismo para cualquiera que abra el repositorio. Pero bajo esa comodidad hay una maquinaria de ciclo de vida —hooks que se disparan solos antes y después, scripts que corren al instalar, ganchos que se ejecutan al publicar— que conviene conocer al detalle, porque es a la vez la fuente de la automatización elegante y el vector de ataque más explotado de todo el ecosistema.
- Usar
scriptscomo interfaz estable de comandos y ejecutarlos con pnpm. - Dominar los hooks
preyposty el orden exacto en que se encadenan. - Entender
preparey la disciplina de instalación y publicación que orquesta. - Pasar argumentos a un script y valorar el riesgo de los scripts de instalación de terceros.
scripts: la interfaz de comandos del proyecto
Cada clave de scripts es un comando con nombre que se ejecuta en una shell con el node_modules/.bin del proyecto ya en el PATH, de modo que puedes invocar binarios locales (tsc, vite, eslint) sin ruta ni instalación global. Se lanzan con pnpm run <nombre> o, para nombres no reservados, con el atajo pnpm <nombre>.
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test": "vitest run",
"lint": "oxlint ."
}
}
pnpm run build # forma explicita
pnpm build # atajo para nombres no reservados
pnpm dev # arranca el dev server
pnpm -r run build # ejecuta build en cada paquete del workspace
pnpm --filter web build # solo en el paquete "web"
Los hooks pre y post: encadenado automático
Para cualquier script foo, el gestor busca y ejecuta automáticamente prefoo justo antes y postfoo justo después. No hay que invocarlos: existir es participar. Esto permite componer tuberías declarativas sin acoplar los comandos entre sí.
flowchart LR A[pnpm run build] --> B[prebuild] B --> C[build] C --> D[postbuild]
{
"scripts": {
"prebuild": "rimraf dist",
"build": "vite build",
"postbuild": "node scripts/report-size.mjs"
}
}
En pnpm, los hooks pre y post de tus propios scripts están activos por defecto (enable-pre-post-scripts). Conviene usarlos con mesura: un prebuild que limpia dist es legítimo y legible, pero abusar de la magia implícita hace que el flujo real sea difícil de seguir para quien lee el package.json por primera vez.
El ciclo de instalación y prepare
Existe una familia de hooks reservados que el gestor dispara en momentos concretos del ciclo de vida, no cuando tú los invocas:
preinstall,install,postinstall: corren durante la instalación del paquete.prepare: corre después de instalar en local, antes de empaquetar enpackopublish, y —crucialmente— al instalar el paquete desde un repositorio git, lo que permite compilar fuentes sin publicar artefactos.prepublishOnly: corre solo antes depublish, nunca durante una instalación; el lugar idóneo para tests y comprobaciones de última hora.prepackypostpack: envuelven la creación del tarball.
Es fácil confundirlos y elegir mal. postinstall corre en la máquina de quien te instala, cada vez, lo que lo hace tentador pero arriesgado para compilar. prepare corre en tu máquina —al instalar en local, antes de publicar y al consumirte desde git— y nunca en una instalación normal desde el registro, porque para entonces el artefacto ya viaja compilado. La regla es simple: compila en prepare, no en postinstall.
{
"scripts": {
"prepare": "tsc -p tsconfig.build.json",
"prepublishOnly": "pnpm test && pnpm lint"
}
}
De usuario
dev, build, test, lint: los invocas tú con pnpm run. Nombre libre, significado convenido por el equipo.
pre y post
prebuild y postbuild se disparan solos alrededor de build. Componen tuberías sin acoplar comandos.
De instalación
preinstall, install, postinstall: corren al instalar. Poderosos y peligrosos a partes iguales.
De publicación
prepare, prepublishOnly, prepack: preparan y validan el paquete antes de que salga al registro.
Pasar argumentos y ejecutar con pnpm
Con pnpm puedes anexar argumentos directamente al final del comando, y se reenvían al script sin ceremonia. npm, en cambio, exige el separador -- para distinguir sus propios flags de los del script. Merece la pena grabar la diferencia, porque es una fuente recurrente de confusión al migrar comandos entre gestores.
pnpm test --watch # pnpm reenvia --watch a vitest
pnpm run build --sourcemap # directo, sin separador
npm run build -- --sourcemap # npm necesita el -- explicito
Dentro de un script tienes acceso a variables que el gestor inyecta, útiles para condicionar comportamiento sin duplicar datos en el código: npm_package_name, npm_package_version y npm_lifecycle_event, que contiene el nombre del script en curso.
# dentro de cualquier script, estas variables ya existen
echo "publicando $npm_package_name en su version $npm_package_version"
echo "hook actual: $npm_lifecycle_event"
En un monorepo la ejecución se vuelve topológica. pnpm -r run build construye cada paquete respetando el grafo de dependencias internas —primero las librerías, luego quien las consume—, y --filter acota el alcance a un paquete y, opcionalmente, a su vecindario en el grafo.
pnpm -r run build # todos los paquetes, en orden topologico
pnpm --filter web... build # el paquete web y todo lo que necesita
pnpm --filter ...ui build # ui y todo lo que depende de ui
pnpm --filter "./apps/*" test # por patron de ruta
Tres atajos que ahorran fricción a diario. Dentro de un script, cualquier binario de node_modules/.bin ya está en el PATH, así que escribes vite, no la ruta completa. Fuera de un script, pnpm exec te da ese mismo PATH para una orden puntual. Y pnpm dlx descarga y ejecuta una herramienta efímera sin ensuciar tus dependencias: es el sustituto correcto de instalar algo global solo para usarlo una vez.
Sin argumentos, pnpm run a secas lista todos los scripts del proyecto: la forma más rápida de descubrir qué ofrece un repositorio ajeno sin abrir el package.json. Para orquestar varios, && los encadena deteniéndose al primer fallo, mientras que utilidades como npm-run-all o concurrently los lanzan en paralelo —la diferencia entre un dev que arranca servidor y watcher a la vez y uno que se queda a medias—.
Un detalle que muerde en equipos mixtos: los scripts corren en la shell del sistema, que en Windows no es la de macOS o Linux. Escribir NODE_ENV=production comando falla en Windows; por eso sobrevive cross-env, aunque en 2026 muchos equipos lo evitan trasladando la configuración a archivos en lugar de a la línea de comandos.
El mismo mecanismo que hace elegante la automatización es el agujero de seguridad más explotado del ecosistema. Cuando instalas una dependencia, su postinstall puede ejecutar código arbitrario en tu máquina o en tu CI con tus permisos, tus tokens y tus variables de entorno a la vista. Los ataques de cadena de suministro más sonados de los últimos años no comprometieron el código que importabas: comprometieron el script que corría al instalarlo, mucho antes de que ejecutaras una sola línea tuya. La respuesta del ecosistema en 2026 es un cambio de postura por defecto: pnpm ya no ejecuta los scripts de instalación de las dependencias salvo que las incluyas explícitamente en una lista de confianza (onlyBuiltDependencies, gestionada con pnpm approve-builds). Es un giro copernicano —de confía salvo que prohíbas a desconfía salvo que apruebes— y conviene entenderlo, no combatirlo. La regla práctica es doble: en tus propios scripts, trata prepare como el lugar honesto para compilar y reserva postinstall para lo imprescindible; y en las dependencias ajenas, considera cada script de instalación como código no auditado que corre con tus llaves, y decide conscientemente a cuáles concedes ese privilegio. La comodidad de un hook automático nunca justifica ejecutar a ciegas el código de un desconocido.
- Añade un
prebuildque limpiedisty unpostbuildque imprima el tamaño del bundle; ejecutapnpm buildy confirma que los tres corren en orden. - Convierte un script en fallido a propósito dentro de un
prepublishOnlyy comprueba quepnpm publish --dry-runse detiene antes de empaquetar. - Ejecuta
pnpm installcon--ignore-scriptsen un proyecto con dependencias que compilan binarios y observa qué deja de funcionar y por qué. - Revisa qué paquetes tienes aprobados en
onlyBuiltDependenciesy justifica, uno a uno, por qué merecen ejecutar código en tu máquina.