Verificar el paquete: publint y attw
Las dos herramientas que auditan un paquete antes de publicarlo: publint valida la coherencia de exports, extensiones y orden de conditions, y arethetypeswrong comprueba que los tipos resuelvan bajo cada estrategia de módulos. Cómo correrlas sobre el tarball y meterlas en CI.
Un exports mal configurado no falla en tu máquina: falla en la de miles de personas, semanas después, de formas silenciosas que ningún test tuyo detecta. Publicar es irreversible, así que la única defensa es auditar el paquete antes de exponerlo. En 2026 dos herramientas se han vuelto el estándar de facto para esa auditoría: publint, que valida la coherencia del empaquetado, y arethetypeswrong —el binario attw—, que valida que los tipos resuelvan de verdad. Correrlas no es opcional: es la diferencia entre publicar con control y publicar a ciegas.
- Auditar la coherencia de
exports, extensiones y conditions conpublint. - Detectar tipos que no resuelven bajo cada estrategia con
attw. - Correr ambas sobre el tarball, no sobre el árbol de trabajo.
- Integrar la verificación en CI como puerta previa a publicar.
publint: la coherencia del empaquetado
publint analiza tu package.json y los archivos que apunta, y verifica que todo el andamiaje del empaquetado sea coherente. No mira si tu código funciona: mira si tu paquete está bien construido para que otros lo consuman. Es rápido, no necesita configuración, y su lista de comprobaciones cubre justo los errores que no se ven a ojo.
# audita el paquete en el directorio actual
npx publint
# audita el tarball exacto que se publicaria
npm pack && npx publint ./acme-core-2.1.0.tgz
Lo que detecta es precisamente el catálogo de fallos de las lecciones anteriores. Que una condition apunte a un archivo con la extensión equivocada para su formato. Que el orden de las conditions esté mal, con types después de import o default antes de otra rama. Que una ruta de exports apunte a un archivo que no existe en el paquete publicado. Que falte la condition types o que el campo main contradiga a exports. Cada aviso viene con la explicación de por qué importa y cómo corregirlo.
Su salida es un informe legible que nombra el punto exacto del package.json y la corrección concreta, no un críptico código de error:
$ publint --strict
Errors:
1. pkg.exports["."] types condition should be the first
2. pkg.main is ./dist/index.cjs but the file does not exist
Warnings:
1. pkg.exports["."].require should be a .cjs file or use "type": "commonjs"
Leído así, cada línea es una tarea accionable: reordena las conditions, comprueba que el build generó el archivo, ajusta la extensión. La herramienta traduce las reglas abstractas del empaquetado en un checklist concreto sobre tu paquete real.
publint clasifica sus hallazgos en errores, avisos y sugerencias. Un error es un fallo que romperá a consumidores reales —una ruta que no existe, una extensión incompatible—; una sugerencia es una mejora de higiene. En CI conviene fallar el pipeline ante los errores y, según la madurez del paquete, también ante los avisos. No todos los hallazgos tienen el mismo peso, pero ninguno se ignora sin haberlo leído.
attw: los tipos que mienten
publint valida el empaquetado; arethetypeswrong valida algo más específico y más traicionero: que las declaraciones de tipos resuelvan correctamente bajo cada estrategia de resolución de módulos que un consumidor pueda usar. Un paquete puede tener un exports que publint aprueba y aun así entregar tipos rotos para quien resuelve con nodenext, o unos tipos que dicen ESM mientras el runtime entrega CommonJS.
# analiza el paquete tal como lo veria cada resolver
npx attw --pack
# sobre un tarball ya generado
attw ./acme-core-2.1.0.tgz
La aportación única de attw es que simula varios resolvers a la vez —node10, node16 en modo ESM y CJS, bundler— y te muestra una matriz de en cuáles tus tipos resuelven bien y en cuáles no. Su valor está en nombrar los desajustes sutiles entre lo que los tipos prometen y lo que el runtime entrega, esos que producen un error de tipos sin error de runtime, o al revés.
Masquerading ESM o CJS
Los tipos dicen que el módulo es ESM pero el runtime entrega CommonJS, o al revés. El type-checker aprueba código que fallará al ejecutarse.
No resolution
Bajo un resolver concreto, TypeScript no encuentra las declaraciones. El consumidor recibe any implícito y pierde todo el tipado.
Tipos por formato ausentes
El paquete es dual pero sirve el mismo .d.ts a ESM y a CJS, cuando la forma del módulo difiere entre ambos.
Fallos solo en un resolver
Todo funciona en bundler pero se rompe en node16. La matriz de attw es la única forma de ver ese hueco antes de publicarlo.
flowchart LR A[tarball del paquete] --> B[publint valida exports y formatos] A --> C[attw valida resolucion de tipos] B --> D[avisos de conditions y extensiones] C --> E[matriz de resolvers con fallos] D --> F[corregir antes de publicar] E --> F style B fill:#89b4fa,color:#11111b style C fill:#cba6f7,color:#11111b style F fill:#a6e3a1,color:#11111b
Cada columna de la matriz es un resolver —node10 heredado, node16 en sus modos ESM y CJS, bundler— y cada fila un subpath de tu paquete. Una celda verde significa que ese resolver encuentra tipos coherentes; una roja, que no. Lo valioso es el patrón: fallos solo en la columna node16 CJS apuntan a que te falta el .d.cts o que anidaste mal los tipos por formato; fallos en node10 suelen ser aceptables si ya no soportas resolvers antiguos. attw también existe como aplicación web sobre la que pegar el nombre de un paquete publicado, útil para auditar dependencias de terceros antes de adoptarlas.
Auditar el tarball, no el repositorio
Un detalle metodológico que decide si la auditoría vale algo: ambas herramientas deben correr sobre el tarball, no sobre tu árbol de trabajo. La razón es que el tarball rara vez coincide con tu carpeta —el campo files, el .npmignore y los scripts de build deciden qué viaja de verdad—, y un exports impecable que apunte a un dist que no se empaquetó produce un paquete roto que la auditoría del árbol de trabajo jamás detectaría.
Por eso el patrón correcto es empaquetar en seco y auditar el resultado. Tanto publint como attw aceptan la bandera --pack o una ruta a un .tgz, y con ella analizan exactamente los bytes que recibirá quien instale, no los que tú tienes en local:
# audita lo que el mundo veria, no lo que tu tienes
npm pack --dry-run
npx publint --pack
npx attw --pack
La verificación manual se olvida; la de CI, no. El patrón de 2026 es un job que corre publint y attw sobre el tarball en cada pull request y antes de cada publicación, y falla el pipeline si aparece un error. Combinado con npm publish --dry-run, forma la puerta de calidad que ningún cambio de empaquetado puede cruzar sin pasar. El coste es un minuto de CI; el beneficio es no publicar una versión rota de forma irreversible.
La forma más limpia de fijar esa puerta es un script que encadene las comprobaciones y colgarlo del gancho prepublishOnly, de modo que sea imposible publicar sin haber auditado:
{
"scripts": {
"lint:pkg": "publint --strict && attw --pack",
"prepublishOnly": "npm run build && npm run lint:pkg"
}
}
Con publint --strict, incluso los avisos de menor severidad detienen la publicación; el attw --pack valida la matriz de tipos sobre el tarball recién construido. Al colgar ambos de prepublishOnly, la verificación deja de ser una buena intención y pasa a ser una precondición mecánica: si algo falla, npm publish aborta antes de tocar el registro.
El error mental que estas dos herramientas corrigen es creer que, si tu código funciona en tu máquina, tu paquete funciona. No es lo mismo. Tu código y tu paquete son dos cosas distintas: el código es lo que escribes, el paquete es el artefacto que otros resuelven a través de un laberinto de conditions, extensiones, resolvers y estrategias de módulos que tú nunca ejecutas en tu propio desarrollo. Un exports puede estar mal ordenado, apuntar a archivos que no se empaquetaron, servir tipos que mienten sobre el formato, o resolver bien en tu bundler y romperse en node16 —y nada de eso lo revela ejecutar tus tests, porque tus tests importan tu código, no tu tarball—. publint y attw son los primeros consumidores de tu paquete que lo miran como lo mirará el mundo: desde fuera, a través del package.json, bajo cada resolver posible. Correrlas sobre el tarball es leer el contrato antes de firmarlo, y firmarlo es irreversible: una vez publicada, la versión es inmutable y cualquier fallo se paga con un parche de emergencia y la confianza de quien ya la instaló. La disciplina que separa a quien mantiene paquetes de los que depende medio ecosistema del que sube carpetas y reza es exactamente esta: empaquetar en seco, leer el manifiesto como quien revisa una escritura, pasar publint y attw sobre esos bytes, corregir cada aviso, y solo entonces liberar. No es burocracia. Es la única forma de saber lo que estás firmando.
- Ejecuta
npx publint --packsobre una librería tuya y corrige cada error deexports, extensión u orden de conditions que reporte. - Corre
npx attw --packy lee la matriz de resolvers; identifica en cuáles tus tipos resuelven y en cuáles no. - Provoca un masquerading: sirve un
.d.tsque diga ESM mientras el runtime entrega CJS, y observa cómo lo nombraattw. - Rompe una ruta de
exportsapuntando a un archivo excluido porfilesy confirma que solo la auditoría del tarball lo detecta. - Añade un job de CI que corra ambas herramientas sobre el tarball y falle el pipeline ante cualquier error, como puerta previa a publicar.