dist-tags: latest, next y beta sin romper a nadie
Los dist-tags como punteros con nombre a versiones concretas: por qué latest gobierna las instalaciones por defecto, cómo las prereleases de SemVer quedan fuera de los rangos normales, cómo publicar líneas beta o next sin perturbar a los usuarios estables, y cómo automatizar el dist-tag desde la rama en CI.
Publicar la próxima versión mayor sin que estalle en producción de miles de proyectos parece imposible, y sin embargo es rutina diaria en el ecosistema. El truco está en un mecanismo humilde y mal comprendido: los dist-tags. Son punteros con nombre —como ramas de git, pero sobre versiones publicadas— que desacoplan “qué versiones existen” de “cuál recibe el usuario por defecto”. Dominarlos es poder iterar en público sin romper a nadie.
- Entender el dist-tag como puntero con nombre y por qué
latestgobierna la instalación por defecto. - Aprovechar que las prereleases de SemVer quedan fuera de los rangos normales como red de seguridad.
- Gestionar, mover y corregir punteros con la familia de comandos
npm dist-tag. - Automatizar la elección del dist-tag desde la rama en CI con changesets o semantic-release.
Qué es un dist-tag
Un dist-tag es una etiqueta legible que apunta a una versión concreta ya publicada. La analogía exacta es la rama de git: main no es un commit, es un puntero móvil a uno. Igual, latest no es una versión, es un puntero a la versión que el registro considera la actual.
Su papel es especialísimo: cuando alguien escribe npm install @acme/ui sin indicar versión, el registro resuelve el paquete que señala latest.
Ese único puntero decide qué recibe por defecto todo el que llega sin pedir nada concreto. Y como es móvil, mover latest cambia al instante qué código sirve una instalación limpia, sin tocar una sola línea del paquete.
latest
El puntero por defecto: lo que recibe quien instala sin pedir versión. Debe señalar siempre la última versión estable.
next y beta
Líneas de preestreno: la próxima mayor en preparación, o fases tempranas. Solo llegan a quien las pide por su nombre.
canary
Builds automáticos por commit, a menudo versiones sintéticas con el hash. Para probar el filo absoluto sin esperar a un release.
Por eso npm publish mueve latest a la versión recién subida salvo que le digas lo contrario. Y ahí está la clave de todo: si publicas una prerelease inestable y dejas que latest la señale, se la sirves de golpe a cada instalación nueva del mundo. La solución es publicar bajo otro puntero, dejando latest anclado en la última versión estable. Instalar una línea concreta es tan simple como nombrar su tag: npm install @acme/ui@next trae lo que señale next, sin tocar a nadie más.
flowchart LR V1[2.5.0 estable] --> V2[3.0.0-beta.1] V2 --> V3[3.0.0-rc.1] V3 --> V4[3.0.0 estable] L[tag latest] --> V1 N[tag next] --> V3 L2[tag latest se mueve] -.-> V4 style L fill:#a6e3a1,color:#11111b style N fill:#fab387,color:#11111b style L2 fill:#a6e3a1,color:#11111b
Prereleases que no rompen rangos
Los dist-tags no trabajan solos: se apoyan en una regla profunda de SemVer que actúa como segunda red de seguridad. Una versión de prerelease —3.0.0-beta.1, 3.0.0-rc.0— lleva un identificador tras el guion que la marca como incompleta.
Y la especificación es tajante: los rangos normales no capturan prereleases. La consecuencia práctica se resume en dos reglas:
- Un
^2.4.0jamás resolverá a3.0.0-beta.1, aunque sea numéricamente mayor: un rango sin prerelease explícita ignora por diseño todo lo que lleve guion. - Solo recibe una prerelease quien la pide de forma deliberada: nombrando el tag, o escribiendo un rango que la incluya, como
3.0.0-beta.
Esto significa que, aunque por descuido publicaras una beta y movieras latest hacia ella, quien tenga fijado un rango estable seguiría intacto: su resolución nunca contempla la prerelease. Es un doble candado: el dist-tag decide qué señala el puntero por defecto, y la regla de prereleases garantiza que los rangos existentes ni se enteren de las versiones marcadas como experimentales.
La regla de oro es que latest señale la última versión estable, nunca una prerelease. Si por accidente publicaste una rc y latest se movió hacia ella, el síntoma es que instalaciones nuevas empiezan a recibir código no acabado. La corrección es inmediata y no destructiva: reancla latest a la última versión buena. El puntero es móvil por naturaleza, así que arreglar el error es mover una etiqueta, no republicar nada.
Gestionar y publicar bajo un tag
El flujo práctico combina el flag --tag al publicar con la familia npm dist-tag para inspeccionar y mover punteros después.
Publicar una prerelease es versionar con sufijo y subirla bajo un tag no estable; promoverla a estable, más tarde, es solo mover latest.
# publica una prerelease sin tocar a los usuarios estables
npm version 3.0.0-beta.1
npm publish --tag next
# inspecciona y mueve punteros despues
npm dist-tag ls @acme/ui
npm dist-tag add @acme/ui@3.0.0 latest
npm dist-tag rm @acme/ui next
# instala una linea concreta de forma deliberada
npm install @acme/ui@next
Las convenciones del ecosistema son estables y conviene reconocerlas todas, porque un nombre bien elegido comunica intención:
latest: la última versión estable; el defecto de cualquier instalación.next: la próxima versión mayor en preparación, usable pero no definitiva.betayalpha: fases tempranas, con la API aún en movimiento.rc: release candidate, una beta que aspira a volverse estable sin más cambios.canaryonightly: builds automáticos por commit, para el filo absoluto.legacy: una línea mayor antigua que sigue recibiendo parches de mantenimiento.
El tag legacy merece una nota: permite mantener viva una versión mayor anterior —parches de seguridad para quienes aún no migran— sin que latest deje de señalar la línea actual. Distribuir dos líneas en paralelo es, otra vez, cuestión de punteros.
Del lado de quien consume, elegir la línea es igual de explícito. Cada forma de instalar pide un puntero o un rango distinto:
# instalar cada linea de forma explicita
npm i @acme/ui # el puntero latest, la version estable
npm i @acme/ui@next # la linea de preestreno
npm i @acme/ui@3.0.0-beta.1 # una prerelease exacta y fijada
npm i @acme/ui@^3.0.0-0 # un rango que SI admite prereleases de la 3
El sufijo -0 en el rango es el truco que le dice al resolutor que, esta vez, sí considere las prereleases de esa línea mayor. Sin él, ningún rango las tocaría: es la puerta que abre voluntariamente el consumidor cuando quiere vivir en el filo de una versión concreta.
Antes de mover un puntero conviene ver el mapa completo: npm dist-tag ls @acme/ui lista cada tag y la versión que señala, y npm view @acme/ui dist-tags muestra lo mismo desde el packument. Es el equivalente a git branch -v antes de un merge: nunca muevas latest a ciegas sin saber dónde apunta ahora y qué versiones estables tienes disponibles para reanclar si algo sale mal.
Automatizar el release desde CI
Nada de esto se hace a mano en proyectos serios: teclear --tag en cada publicación es una fuente de errores humanos.
La automatización deriva el tag de la rama en CI, de modo que empujar a una rama concreta publica en la línea correcta sin intervención:
- changesets: acumula notas de cambio con su nivel de versión y, al fusionar, versiona y publica; con su modo prerelease dirige los releases al tag
next. - semantic-release: analiza los mensajes de commit convencionales, decide el salto de versión y publica bajo el tag que corresponde a la rama.
- canary por commit: un job publica versiones sintéticas tipo
0.0.0-canaryseguidas del hash corto bajo el tagcanary, para probar cada commit sin tocarlatest.
# changesets: entra en el modo prerelease de la linea next
npx changeset pre enter next
npx changeset publish # cada release sale bajo el dist-tag next
# al cerrar la beta, sal del modo prerelease y vuelve a latest
npx changeset pre exit
El principio común es que la máquina, no la memoria del humano, elige el número de versión y el dist-tag a partir de señales explícitas —la rama, los commits, los changesets—. Publicar deja de ser un ritual manual y se vuelve una consecuencia determinista de fusionar código.
La idea que cristaliza este nivel es que publicar y distribuir son dos actos separados. Publicar crea una versión inmutable en el registro —eso es irreversible—. Distribuir es decidir a qué versión apunta cada puntero con nombre —y eso es completamente reversible—. Los dist-tags viven en esa segunda capa: no cambian el código, cambian a qué código llega quien no especifica nada. Cuando entiendes esto, la maniobra que parecía peligrosa —sacar una versión mayor con cambios rompedores— se vuelve un baile controlado: publicas la beta bajo next, la maduras con quien la pide voluntariamente, y solo cuando confías en ella mueves latest. Mientras tanto, la regla de prereleases de SemVer protege el flanco pasivo: los rangos estables existentes son ciegos a todo lo que lleve guion. Dos mecanismos ortogonales —punteros móviles y exclusión de prereleases— se combinan para darte lo que parecía contradictorio: iterar agresivamente en público y, a la vez, no romperle el build a nadie que no haya pedido explícitamente vivir en el filo. Y cuando delegas la elección del tag a la CI, esa separación entre existencia y distribución deja de depender de tu disciplina para volverse una propiedad estructural del pipeline. Esa es la maquinaria que sostiene el release continuo de librerías de las que dependen millones.
- Versiona un paquete de práctica como
1.0.0y publícalo; confirma quelatestlo señala connpm dist-tag ls. - Publica
2.0.0-beta.0bajo--tag nexty verifica que un^1.0.0en otro proyecto sigue resolviendo a la 1. - Instala deliberadamente la beta con el sufijo
@nexty observa la diferencia. - Promueve la estable moviendo
latestconnpm dist-tag add, y retira el tagnextcuando ya no haga falta. - Investiga cómo
changesetsosemantic-releaseelegirían el tag desde la rama en CI, sin flags manuales.