Publicar en CI: la GitHub Action de Changesets
La Action oficial de Changesets tiene dos estados según haya o no changesets pendientes: cuando los hay, abre y mantiene un PR de release que ejecuta changeset version; cuando no los hay, ejecuta el comando de publish al fusionar a main. Montamos el workflow pieza a pieza y pasamos del NPM_TOKEN clásico al publicado con OIDC y provenance de 2026.
Todo lo anterior ocurría en tu máquina; ahora lo delegamos a la integración continua. La GitHub Action changesets/action es el autómata que cierra el círculo: vigila main, y según el estado del repositorio hace una de dos cosas. Si hay changesets pendientes, abre —o actualiza— un Pull Request de release que ejecuta changeset version por ti. Si no los hay, porque ese PR de release acaba de fusionarse, ejecuta el comando de publicación y sube los paquetes al registro. Esa dualidad, gobernada por una sola condición, es todo el secreto. En esta lección la montamos entera y la conectamos con la forma moderna de autenticarse ante npm, que en 2026 ya no pasa por un token eterno guardado en un secreto, sino por identidad efímera y procedencia verificable.
- Comprender los dos estados de
changesets/actiony la condición que decide entre ellos. - Montar el workflow de release pieza a pieza: disparador, permisos, instalación y la Action.
- Distinguir el comando
versiondel comandopublishy por qué el segundo suele construir antes. - Migrar la autenticación del
NPM_TOKENclásico al modelo OIDC con provenance de 2026.
Una Action con dos estados
La clave para entender la Action es que no hace una cosa, sino una de dos, decidida por una única pregunta: ¿quedan changesets pendientes en .changeset/? Es una máquina de dos estados que se ejecuta en cada push a main y se ramifica según esa respuesta.
flowchart TB push[Push a main] --> check[La accion mira los changesets] check -->|Hay changesets| verpr[Abre o actualiza el PR Version Packages] check -->|No hay changesets| pub[Ejecuta el comando de publish] verpr --> merge[Fusionas el PR de release] merge --> push pub --> reg[Publica al registro y crea releases] style verpr fill:#a6e3a1,color:#11111b style pub fill:#89b4fa,color:#11111b
En el primer estado, con changesets pendientes, la Action ejecuta el comando de version en una rama aparte y abre un PR titulado Version Packages. Ese PR contiene exactamente el diff que estudiamos en la lección anterior: versiones subidas, changelog escrito, changesets consumidos. No publica nada todavía; solo prepara la release y la deja lista para revisión. A medida que más PRs con changesets se fusionan a main, la Action reejecuta version y actualiza ese mismo PR, que crece hasta reflejar el lote completo.
En el segundo estado, sin changesets pendientes —lo que ocurre justo después de fusionar el PR de release, porque version los borró—, la Action ejecuta el comando de publish. Ahí es donde los paquetes suben al registro, se crean las etiquetas de git y, si lo pides, se generan los GitHub Releases con las notas del changelog. La genialidad del diseño es que fusionar el PR de release es a la vez el acto que aplica las versiones y el que dispara la publicación: un solo merge, revisado por humanos, desencadena toda la cadena.
El workflow, pieza a pieza
Un workflow de release con Changesets cabe en pocas líneas, pero cada una cumple un papel. Se dispara al empujar a main, protege contra ejecuciones concurrentes, declara los permisos que la Action necesita, prepara el entorno e instala dependencias, y finalmente invoca changesets/action pasándole los dos comandos.
name: Release
on:
push:
branches: [main]
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: changesets/action@v1
with:
version: pnpm changeset version
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: true
Detengámonos en las dos entradas que definen la Action. La entrada version es el comando que ejecuta para preparar la release; por defecto es changeset version, pero puedes envolverlo si necesitas pasos extra, como regenerar un lockfile tras subir versiones. La entrada publish es el comando que ejecuta cuando toca publicar, y aquí hay un matiz decisivo: casi nunca es changeset publish a secas.
- El comando
publishdebe compilar antes de publicar, porque al registro sube el resultado del build, no el código fuente. - Por eso se envuelve en un script propio, típicamente llamado
release, que encadena la compilación y la publicación. changeset publishes idempotente frente al registro: solo sube los paquetes cuya versión aún no existe allí, así que reejecutarlo es seguro.
{
"scripts": {
"build": "pnpm -r build",
"release": "pnpm build && changeset publish"
}
}
Estado versión
Hay changesets pendientes. La Action ejecuta version y mantiene vivo el PR Version Packages con el lote acumulado.
Estado publish
No quedan changesets. La Action ejecuta el release, compila y sube al registro solo los paquetes con versión nueva.
Releases y tags
Con createGithubReleases, cada paquete publicado obtiene su etiqueta de git y su GitHub Release con las notas del changelog.
Autenticación en 2026: del NPM_TOKEN al OIDC
Durante años, publicar desde CI significaba guardar un NPM_TOKEN de larga vida en los secretos del repositorio y pasárselo a la Action por variable de entorno. Funcionaba, pero era una bomba de relojería: un token eterno con permiso para publicar, que si se filtraba comprometía el paquete entero, y que había que rotar a mano. En 2026 el modelo por defecto ya no es ese.
La forma moderna es el trusted publishing con OIDC. En lugar de un token guardado, el runner de GitHub Actions presenta a npm un identidad efímera y verificable —firmada por GitHub para ese workflow concreto— y el registro, que confía previamente en ese workflow, autoriza la publicación sin que exista ningún secreto de larga vida. El permiso id-token: write del workflow es justo lo que habilita esa firma. No hay token que filtrar porque no hay token: la credencial nace y muere dentro de la ejecución.
A esa identidad se suma la provenance: al publicar con NPM_CONFIG_PROVENANCE activado, npm adjunta al paquete una attestation criptográfica que enlaza el artefacto publicado con el commit exacto y el workflow que lo produjo. Cualquiera puede verificar que la versión que instala salió de ese repositorio y esa ejecución, y no de la máquina de un atacante. Provenance convierte la cadena de suministro de opaca a auditable.
id-token: writehabilita la identidad OIDC efímera; sin él, no hay trusted publishing.NPM_CONFIG_PROVENANCEadjunta la procedencia verificable a cada paquete publicado.- Los outputs de la Action —
publishedypublishedPackages— permiten encadenar pasos posteriores, como anunciar la release o desplegar la documentación.
Esos outputs convierten la publicación en el disparador de todo lo que venga después: un anuncio en el chat, un despliegue de la documentación, una nota de release enriquecida. La condición published evita hacer ese trabajo cuando la Action solo actualizó el PR de versiones y no publicó nada.
# Encadenar un paso solo si de verdad se publico algo al registro
- uses: changesets/action@v1
id: changesets
with:
publish: pnpm release
- if: steps.changesets.outputs.published == 'true'
run: pnpm announce -- ${{ steps.changesets.outputs.publishedPackages }}
Hay una trampa clásica: los eventos que la Action genera con el GITHUB_TOKEN automático —abrir el PR de release, crear tags— no disparan otros workflows, por una protección de GitHub contra bucles recursivos. Si necesitas que la creación de un tag lance, por ejemplo, un pipeline de despliegue, deberás usar una GitHub App o un Personal Access Token con los permisos adecuados en lugar del token por defecto. Es la causa más común de el release se publicó pero mi workflow posterior no arrancó.
La lección estratégica de esta Action es una que casi todo el mundo malinterpreta al automatizar. Automatizar bien no significa eliminar la decisión humana; significa eliminar el trabajo mecánico que rodea a la decisión, para que el humano quede libre de ejecutar tareas propensas al error y pueda concentrarse en el único juicio que de verdad requiere su criterio. Mira dónde ha quedado la persona en este flujo: no calcula versiones a mano, no edita changelogs, no recuerda comandos de publicación, no gestiona tokens. Todo eso lo hace el autómata, que no se equivoca ni se cansa. El humano interviene en un solo lugar, el más importante: fusionar el PR de release, ese momento en que revisa el diff completo de versiones y changelog y decide, con toda la información delante, si el lote está listo para salir al mundo. Un clic, pero un clic con criterio, en el punto exacto donde el criterio es irremplazable. Ese es el patrón que separa la automatización madura de la ingenua. La ingenua intenta quitar al humano de todo y termina publicando basura a toda velocidad, porque nadie mira; o lo deja en todos los pasos y no automatiza nada. La madura hace un mapa honesto del proceso, identifica el uno o dos puntos donde el juicio humano añade valor real, blinda esos puntos como compuertas explícitas, y automatiza sin piedad todo lo demás. Aplícalo más allá de las releases: en cualquier pipeline que diseñes, pregúntate dónde importa de verdad un humano y dónde solo estorba, y construye para que la persona aparezca justo en las compuertas y desaparezca del resto. La meta no es un proceso sin humanos, es un proceso donde los humanos solo tocan lo que merece ser tocado por un humano.
- Crea el workflow de release que se dispare al empujar a
maine invoquechangesets/actioncon sus comandosversionypublish. - Escribe un script
releaseque compile antes de publicar, y explica por qué al registro sube el build y no el fuente. - Simula el ciclo completo: fusiona un PR con changeset, observa cómo nace el PR Version Packages y fusiónalo para disparar la publicación.
- Sustituye el
NPM_TOKENclásico por trusted publishing con OIDC y activaNPM_CONFIG_PROVENANCE; razona qué riesgo elimina cada cambio. - Explica por qué el
GITHUB_TOKENpor defecto no dispara workflows posteriores y qué usarías si necesitaras encadenar un despliegue tras el tag.