Qué es Changesets y el flujo de la intención de publicar
Un changeset es la declaración de intención de publicar que acompaña a cada Pull Request: qué paquetes cambian, con qué severidad y por qué. Changesets desplaza la decisión de versionado del final del proceso al principio, donde vive el contexto, y la convierte en un artefacto revisable que se acumula hasta que decides cortar una release.
Changesets no es un publicador ni un incrementador automático de versiones: es un sistema para capturar, en el instante mismo en que se produce el cambio, la decisión humana de qué versión merece. Cada Pull Request que altera un paquete deja tras de sí un pequeño archivo markdown —un changeset— que declara qué paquetes cambian, con qué severidad y por qué. Esa intención viaja con el código, se revisa como código y se acumula hasta que alguien decide cortar una release. Entender Changesets es entender un desplazamiento sutil pero profundo: mover la decisión de versionado del final del proceso, donde suele improvisarse a toda prisa, al principio, donde todavía está fresco el contexto de por qué se tocó lo que se tocó.
- Definir un changeset como una declaración de intención de publicar, no como un commit ni como una versión.
- Comprender por qué el versionado semántico es un juicio humano que ningún diff puede inferir por sí solo.
- Recorrer el flujo completo: declarar en el PR, acumular en
mainy publicar en lote. - Contrastar Changesets con el enfoque de Conventional Commits y
semantic-release.
La versión es un juicio, no un dato
Un diff te dice qué líneas cambiaron, pero nunca qué significan esas líneas para quien consume tu paquete. El versionado semántico —major para lo que rompe, minor para lo que añade sin romper, patch para lo que corrige— es una promesa dirigida a terceros, y esa promesa exige interpretar la intención, no solo leer el código. Renombrar un parámetro interno es un patch; renombrar un parámetro exportado es un major. La diferencia no está en el número de líneas, sino en el contrato con quien depende de ti, y ese contrato solo lo conoce la persona que hizo el cambio, en el momento en que lo hizo.
El error histórico de muchas cadenas de publicación fue posponer ese juicio. Se acumulaban decenas de commits durante semanas y, al llegar la hora de publicar, alguien miraba el registro de cambios intentando reconstruir, a posteriori, si el conjunto rompía algo. Para entonces el contexto se había evaporado: quien publica no siempre es quien programó, y la memoria de por qué un cambio era delicado se pierde en días. La versión terminaba siendo una conjetura, no una decisión informada.
Changesets invierte el orden. Pide la decisión de versionado en el mismo Pull Request que introduce el cambio, cuando el autor tiene todo el contexto en la cabeza y el revisor puede discutirla. La pregunta esto rompe algo deja de contestarse semanas después, con la información degradada, para contestarse ahora, con la información completa. Es un desplazamiento temporal barato que compra una enorme reducción de riesgo.
El coste de equivocar ese juicio no es lineal, es sistémico. Un patch que en realidad rompía compatibilidad se instala solo y en silencio, porque los rangos de versión aceptan los parches sin preguntar; y como tu paquete puede estar enterrado bajo varias capas de dependencias, el fallo aflora lejos de su origen, en el proyecto de alguien que ni sabe que te usa. Versionar es de las pocas decisiones cuyo error se paga en cabeza ajena, y esa asimetría es la que justifica tanta ceremonia:
- Un
patchmal declarado se propaga solo y rompe sin aviso a quien confiaba en él. - Un
minorque en realidad eramajorengaña a los rangos de compatibilidad de medio ecosistema. - Un
majorpuesto por miedo donde bastaba unminorfuerza migraciones inútiles y frena la adopción.
Declarar el nivel correcto, en el momento y con el contexto, no es burocracia: es la cortesía mínima de quien publica hacia quien consume.
El changeset: la intención de publicar hecha archivo
Un changeset es, físicamente, un archivo markdown que Changesets deposita en una carpeta .changeset/ en la raíz del repositorio. Lo generas con un asistente interactivo que te pregunta qué paquetes cambian y con qué severidad, y que redacta el resumen que verá el usuario final. El archivo recibe un nombre aleatorio y memorable, del estilo .changeset/tall-lions-attack.md, para que dos PRs simultáneos jamás colisionen.
# Instalar y arrancar Changesets en el repositorio
pnpm add -Dw @changesets/cli
pnpm changeset init # crea la carpeta .changeset con config.json y README.md
# Declarar la intencion de publicar en tu rama
pnpm changeset # asistente interactivo: paquetes, bump y resumen
El asistente escribe algo tan simple como esto, que revisaremos con lupa en la lección siguiente:
---
"@acme/botones": minor
---
Añade la variante fantasma al componente Botón, con sus estados de foco y disabled.
Tres propiedades hacen del changeset una pieza distinta de un commit. Primero, es declarativo: no describe lo que hiciste, sino el efecto que ese cambio debe tener sobre la versión pública. Segundo, es aditivo: un PR puede llevar varios changesets, o ninguno, y se van amontonando en .changeset/ sin pisarse. Tercero, está desacoplado del historial de git: no depende de que escribas los mensajes de commit con una sintaxis especial, así que puedes reescribir, aplastar o reordenar commits sin miedo a romper el versionado.
Con el tiempo, la carpeta .changeset/ acumula intenciones que esperan la próxima release, cada una nacida de un PR distinto:
.changeset/
config.json # como versiona y publica este repositorio
README.md # nota para quien abra la carpeta
tall-lions-attack.md # intencion pendiente de un PR
neat-rivers-smile.md # intencion pendiente de otro PR
No todo PR merece release. Un ajuste de documentación, un test o un cambio en la configuración de CI no alteran ningún paquete publicado. Para esos casos existe changeset add --empty, que crea un changeset sin bumps: satisface la comprobación de CI que exige un changeset por PR sin forzar una versión nueva. Y para imponer esa comprobación, changeset status --since=origin/main falla si tu rama no declara ningún cambio, convirtiendo el changeset en un requisito de revisión.
El flujo, de la rama al registro
El ciclo de vida de un cambio, de la rama al registro público, tiene una forma fija que conviene memorizar porque es la columna vertebral de todo el nivel. Cada paso ocurre en un lugar distinto y lo ejecuta un actor distinto, humano o máquina.
- Abres una rama, haces el cambio y ejecutas
pnpm changesetpara declarar su impacto. - Confirmas el archivo
.changeset/xxx.mdjunto al código, en el mismo PR, para que se revise a la vez. - Fusionas a
main. El changeset queda ahí, pendiente, esperando compañía. - Una GitHub Action detecta changesets pendientes y abre un PR de release titulado Version Packages.
- Ese PR se actualiza solo a medida que más ramas aportan changesets, acumulando el lote.
- Cuando fusionas el PR de release, las versiones ya están subidas y la Action publica al registro.
Declarar
En el PR, pnpm changeset captura qué paquetes cambian y por qué. La decisión se toma con el contexto caliente, no meses después.
Acumular
Los changesets se amontonan en .changeset/ sobre main. Muchos PRs aportan intenciones que esperan juntas la próxima release.
Publicar
Un PR de release consume el lote, sube versiones, escribe el changelog y publica. La release es un acto deliberado, no un efecto colateral de cada merge.
flowchart TB pr[Rama y PR con un cambio] --> cs[Creas un changeset markdown] cs --> merge[Fusionas a main] merge --> bot[La accion junta los changesets pendientes] bot --> relpr[PR de release con versiones y changelog] relpr --> pub[Al fusionar publica al registro] style cs fill:#a6e3a1,color:#11111b style pub fill:#89b4fa,color:#11111b
La comparación con la escuela rival aclara qué elige Changesets. semantic-release y las convenciones de Conventional Commits derivan la versión del mensaje de commit: si escribes feat: sube el minor, si escribes fix: sube el patch, y cada merge a main dispara una publicación automática. Es un modelo elegante para un único paquete con integración continua agresiva, pero paga dos peajes. El primero es que ata el versionado a la disciplina de escribir commits, algo frágil en equipos grandes. El segundo es que publica en cada merge, lo que multiplica las versiones y hace difícil agrupar varios cambios en una release coherente.
Changesets apuesta por lo contrario: la intención es un archivo explícito y revisable, no un prefijo de commit inferido; y la publicación es un lote deliberado, no un reflejo por cada merge. Ese diseño encaja de forma natural con el monorepo, donde un solo commit puede tocar cinco paquetes con cinco severidades distintas, algo que un único prefijo de commit no sabe expresar. No es que un enfoque sea correcto y el otro erróneo: es que Changesets optimiza para repositorios multipaquete y releases agrupadas, y ese es justo el terreno donde vive el desarrollo moderno de librerías.
Resumida como tabla mental, la diferencia entre las dos escuelas se sostiene sobre cuatro ejes:
- Origen de la versión: el prefijo del commit en
semantic-release; un archivo explícito y revisable en Changesets. - Momento de publicar: cada merge a
mainensemantic-release; un lote deliberado en Changesets. - Unidad natural: un solo paquete en
semantic-release; el monorepo multipaquete en Changesets. - Punto de fallo: la disciplina de escribir bien los commits frente a la de añadir un changeset por PR.
Changesets expone además un comando de diagnóstico que cierra el círculo de revisión: changeset status te dice, antes de fusionar, qué versiones saldrían del lote actual, y en CI puede exigir que cada rama aporte su intención.
# Previsualiza el lote sin aplicarlo: que versiones saldrian hoy
pnpm changeset status --verbose
# En un PR, falla si la rama no aporta ningun changeset
pnpm changeset status --since=origin/main
La lección profunda de Changesets no es técnica, es organizativa. En los sistemas frágiles, publicar es un evento: un momento de tensión en el que una persona, a solas y bajo presión, decide a ojo qué versión sale y reconstruye de memoria qué cambió. Changesets disuelve ese evento y lo reemplaza por un flujo de artefactos. Cada intención de publicar se materializa en un archivo diminuto que nace pegado a su cambio, se revisa con él, se versiona con él y se acumula con los demás hasta formar, por sedimentación, la próxima release. El resultado es que el conocimiento sobre por qué algo merece un major deja de vivir en la cabeza de una persona y pasa a vivir en el repositorio, donde todo el equipo lo ve, lo discute y lo audita. Fíjate en el patrón, porque trasciende a esta herramienta: los sistemas maduros convierten las decisiones críticas en artefactos versionados y revisables en lugar de dejarlas como actos improvisados de una persona. Un changeset es a la release lo que una migración es al esquema de la base de datos: la forma de que un cambio delicado sea explícito, ordenado y reproducible en vez de un ritual manual que solo sale bien si la persona correcta está despierta. Quien interioriza esto deja de temer las releases y empieza a tratarlas como lo que deben ser: la consecuencia previsible de un proceso, no la apuesta nerviosa de un viernes por la tarde.
- En un repositorio de prueba, ejecuta
pnpm changeset inity examina qué crea dentro de.changeset/. - Haz un cambio trivial en un paquete y declara su impacto con
pnpm changeset; abre el archivo generado y lee su contenido. - Añade un segundo changeset en el mismo PR y comprueba que ambos conviven en
.changeset/sin pisarse. - Crea un changeset vacío con
changeset add --emptyy razona en qué casos —docs, tests, CI— es la respuesta correcta. - Explica en dos frases por qué Changesets separa la intención de publicar del acto de publicar, y qué gana el equipo con ese desacople.