wandres.dev
EL REGISTRO NPM · publicar paquetes

Publicar: el flujo de npm publish

Qué ocurre exactamente cuando ejecutas npm publish: los scripts de ciclo de vida, el empaquetado en tarball, la integridad SRI y la actualización del packument. Qué se sube según el campo files frente a .npmignore, y npm pack para auditar el paquete antes de exponerlo al mundo de forma irreversible.

⏱ 17 min

Publicar un paquete es, en esencia, un acto irreversible: en cuanto una versión existe en el registro, cualquier instalación del planeta puede depender de ella y ya no podrás alterarla sin romper builds ajenos. Entender con precisión qué hace npm publish —qué compila, qué empaqueta, qué calcula, qué sube— separa a quien expone su trabajo con control quirúrgico de quien filtra secretos y basura sin enterarse. Aquí está el flujo, capa por capa.

🎯 Al terminar esta lección sabrás
  • Reconstruir el pipeline interno de npm publish: scripts, empaquetado, integridad SRI y packument.
  • Ordenar los scripts de ciclo de vida y evitar el error de subir un dist a medio construir.
  • Controlar con exactitud qué ficheros entran en el tarball con el campo files frente a .npmignore.
  • Auditar el paquete con npm pack y --dry-run antes de exponerlo de forma irreversible.

Qué ocurre en npm publish

Cuando invocas npm publish, el cliente no “sube tu carpeta”: ejecuta una secuencia determinista de fases. Primero resuelve credenciales leyendo el .npmrc en cascada —proyecto, usuario, global— para obtener el token de autenticación; en 2026 lo habitual en CI es el trusted publishing con OIDC, sin tokens de larga vida almacenados.

Después empaqueta el árbol de trabajo en un tarball comprimido con gzip —el .tgz— aplicando las reglas de inclusión, calcula su integridad y lo sube con un HTTP PUT. El registro valida que esa versión exacta no exista ya —la regla de inmutabilidad— y actualiza el packument, el JSON maestro que describe todas las versiones del paquete.

El pipeline, desglosado, atraviesa fases nítidas:

  • Credenciales: lee el token del .npmrc en cascada, o usa OIDC en CI.
  • Scripts: dispara prepublishOnly, prepare y prepack antes de empaquetar.
  • Empaquetado: comprime el árbol ya filtrado en un tarball .tgz.
  • Integridad: calcula el hash SHA-512 en formato SRI, el sha512-... del lockfile.
  • Subida: hace un HTTP PUT con el tarball y el documento de metadatos.
  • Validación: rechaza versiones repetidas y mueve el dist-tag latest.
flowchart LR
A[npm publish] --> B[scripts prepublishOnly y prepack]
B --> C[empaqueta tarball tgz]
C --> D[integridad sha512 SRI]
D --> E[HTTP PUT al registro]
E --> F[valida version no repetida]
F --> G[actualiza packument y latest]
style A fill:#89b4fa,color:#11111b
style D fill:#cba6f7,color:#11111b
style G fill:#a6e3a1,color:#11111b
ℹ️
La inmutabilidad no es negociable

El registro público rechaza republicar una versión ya existente. Ni siquiera un unpublish la revive: solo dispones de una ventana de 72 horas para retirar una publicación reciente, y pasado ese plazo la versión queda congelada para siempre. Por eso la disciplina de versionado —cada publicación es un número nuevo e inmutable— no es una convención estética, sino la garantía sobre la que se apoya la reproducibilidad de todo el ecosistema.

Los scripts de ciclo de vida

Entre “tengo el código” y “el tarball está sellado” corre una coreografía de scripts que conviene no confundir, porque un error de encuadre publica artefactos a medio construir. Si compilas en un script que no se ejecuta antes de empaquetar, subes un dist viejo o vacío sin que nada te avise. Cada gancho tiene un momento y un propósito exactos:

  • prepare: corre antes de empaquetar —y también al instalar desde un repositorio git—; es el lugar canónico para compilar el dist.
  • prepublishOnly: corre solo en publish, nunca en un install; ideal para los tests y el lint que solo tienen sentido al liberar.
  • prepack y postpack: envuelven el momento exacto del empaquetado.
  • postpublish: corre tras subir con éxito; útil para anuncios o limpieza.

La regla segura es repartir responsabilidades: genera el build en prepare, pon las verificaciones de release en prepublishOnly, y confirma siempre con un ensayo antes de liberar. Observar qué scripts se disparan y en qué orden es la mejor defensa contra publicar un artefacto incompleto.

# el orden efectivo, simplificado:
# prepublishOnly -> prepare -> prepack -> [empaqueta] -> postpack -> [sube] -> publish -> postpublish
npm publish --dry-run
💡
Sin tokens de larga vida en CI

Publicar desde una máquina personal exige doble factor; en automatización, el patrón moderno evita los tokens de larga vida. Los granular access tokens acotan permisos y caducidad, pero el estándar de 2026 es el trusted publishing con OIDC: el registro confía en la identidad del workflow y no guardas ningún secreto que robar. Lo verás a fondo en la lección de procedencia.

Qué se sube: files frente a .npmignore

El tarball rara vez coincide con tu carpeta. npm ofrece dos mecanismos opuestos para decidir su contenido. El campo files en package.json es una lista de permitidos: solo entra lo que enumeras, y es la opción recomendada por explícita y segura por defecto.

El fichero .npmignore es una lista de excluidos, con la sintaxis de .gitignore: entra todo salvo lo que vetes. Si .npmignore no existe, npm recurre a tu .gitignore como denylist, un detalle que sorprende a muchos.

files (allowlist)

Solo viaja lo que enumeras. Explícito y seguro por defecto: lo recomendado. Un patrón negado con ! afina la selección dentro de la propia lista.

🚫

.npmignore (denylist)

Viaja todo salvo lo vetado, con sintaxis de .gitignore. Si falta, npm cae a tu .gitignore. Cómodo, pero propenso a filtrar lo que olvidaste ignorar.

Por encima de ambos hay reglas inamovibles. Siempre viajan, los listes o no: el package.json, el README, la LICENSE, y los ficheros apuntados por main y por bin. Y siempre quedan fuera, aunque los incluyas: node_modules y .git por peso y seguridad, .npmrc porque podría contener tu token, y package-lock.json porque no tiene sentido dentro de una dependencia. El patrón profesional es declarar un files mínimo que apunte al dist:

{
  "name": "@acme/widget",
  "version": "2.1.0",
  "type": "module",
  "files": ["dist", "!dist/**/*.test.js"],
  "main": "./dist/index.js",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  }
}

Los patrones negados con ! permiten refinar dentro del allowlist —aquí, excluir los tests transpilados que se colaron en dist—. La regla mental es simple: si dudas de si un fichero viajará, no lo dejes al azar de un denylist; enuméralo en files y verifícalo.

⚠️
El allowlist te salva de filtrar secretos

Sin un files explícito, un npm publish distraído sube el árbol entero: fuentes sin transpilar, notas internas, ficheros .env con claves, capturas de depuración. Ha habido filtraciones sonadas de credenciales por exactamente esto. Un allowlist estrecho convierte “todo salvo lo que recuerde ignorar” en “nada salvo lo que decida enviar” —la postura correcta cuando el resultado es público e inmutable—.

npm pack: inspeccionar antes de publicar

La herramienta que cierra el bucle es npm pack: genera el mismo tarball que subiría publish, pero lo deja en disco sin exponer nada. Con --dry-run ni siquiera escribe el fichero; solo imprime el manifiesto de lo que contendría.

Y npm publish --dry-run simula la publicación completa —scripts, reglas de inclusión, tamaño final— sin tocar el registro. Es tu red de seguridad obligatoria antes de un acto irreversible.

# lista el contenido exacto del tarball sin escribir nada
npm pack --dry-run

# simula la publicacion completa: scripts, reglas, tamano final
npm publish --dry-run

# inspecciona un tarball ya generado
tar -tzf acme-widget-2.1.0.tgz

El informe condensa lo que importa auditar:

  • package size: los bytes comprimidos que descarga quien instala.
  • unpacked size: lo que ocupa ya instalado en disco.
  • total files: cuántos ficheros viajan; un salto inesperado delata un intruso.
  • shasum e integrity: las huellas que verificará cada instalación futura.

En 2026 la inspección va más allá del tamaño: publint valida que tu campo exports y tus extensiones sean coherentes con ESM y CJS, y arethetypeswrong (el binario attw) comprueba que los tipos se resuelvan bien bajo cada estrategia de módulos. Ambos se corren sobre el tarball, no sobre el árbol de trabajo, para auditar exactamente lo que verá quien instale.

El tarball es el contrato real, no tu repositorio

El error mental del principiante es creer que publica su proyecto; en realidad publica un artefacto derivado —el tarball— cuyo contenido puede diferir radicalmente del repositorio. Todo lo demás se sigue de aquí. Las reglas de files y .npmignore no son burocracia: son la definición precisa de la frontera entre tu taller y lo que el mundo consume. Los scripts de ciclo de vida no son ruido: deciden si el dist que viaja es el recién compilado o un fantasma de una build anterior. La integridad SRI no es un adorno: es lo que permite a cada instalación futura verificar que recibió exactamente esos bytes, ni uno más. Y la inmutabilidad no es una limitación molesta: es la razón por la que un lockfile de hace tres años sigue resolviendo hoy al mismo código. Cuando interiorizas que npm publish construye y sella un contrato binario, dejas de publicar a ciegas. Corres npm pack --dry-run, lees el manifiesto como quien revisa una escritura antes de firmarla, pasas publint y attw, y solo entonces liberas la versión. Esa disciplina —auditar el artefacto, no el repositorio— es la que distingue a quien mantiene paquetes de los que depende medio ecosistema de quien sube carpetas y reza.

⚔️ Audita tu primer tarball
  1. Añade un campo files a un package.json de práctica y ejecuta npm pack --dry-run; lee el manifiesto completo.
  2. Crea un .npmignore y compara cómo cambia el contenido frente a usar solo files; observa la precedencia.
  3. Pon un console.log en un script prepublishOnly y otro en prepare, y comprueba con npm publish --dry-run cuál corre y en qué orden.
  4. Corre npm publish --dry-run e identifica el tamaño empaquetado y el desempaquetado en el informe.
  5. Instala y pasa publint y attw sobre el tarball; corrige cualquier aviso de exports o de tipos.