wandres.dev
PNPM CATALOGS · versiones compartidas

Migrar un monorepo real a catalogs

El procedimiento completo para llevar un monorepo existente a catalogs: detectar duplicados y deriva, consolidar en una version canonica por dependencia, referenciar con catalog usando el codemod oficial, y mantener la coherencia en el tiempo con strict y una comprobacion en CI.

⏱ 17 min

Adoptar catalogs en un proyecto verde es trivial; el trabajo de verdad es migrar un monorepo que ya lleva años acumulando deriva. Ese proyecto no tiene una versión de cada dependencia, tiene varias, y el primer acto de la migración no es técnico sino de juicio: decidir cuál de las cinco versiones de React que conviven se vuelve la canónica. Esta lección es el procedimiento de punta a punta —detectar, consolidar, referenciar, blindar— con las herramientas exactas de 2026, incluido el codemod oficial que hace la parte mecánica para que tú te concentres en las decisiones que ninguna máquina puede tomar por ti.

🎯 Al terminar esta lección sabrás
  • Detectar duplicados y deriva antes de tocar nada.
  • Consolidar cada dependencia compartida en una versión canónica.
  • Referenciar con catalog: usando el codemod oficial en vez de a mano.
  • Mantener la coherencia en el tiempo con strict y una comprobación en CI.

Paso 1: detectar duplicados y deriva

No se puede consolidar lo que no se ha medido. El primer paso es un inventario: qué dependencias aparecen en varios paquetes y con qué rangos divergentes. pnpm ya trae parte del instrumental, y syncpack es el complemento clásico para listar los desajustes de versión de forma legible.

# que versiones de una dependencia resuelve el grafo
pnpm why react

# lista de rangos que no casan entre paquetes (herramienta externa)
pnpm dlx syncpack list-mismatches

# duplicados que se podrian colapsar en el arbol de instalacion
pnpm dedupe --check

El producto de este paso es una lista priorizada: las dependencias más compartidas y más divergentes primero, porque son las que más deriva eliminan al catalogarse y las más propensas a los bugs de doble copia. Las que aparecen en un solo paquete no urgen; el catálogo brilla en lo compartido.

Un buen criterio de priorización mezcla dos ejes: cuánto se comparte una dependencia —en cuántos paquetes aparece— y cuánto diverge —cuántos rangos distintos tiene—. La esquina peligrosa es la de alto en ambos: una librería en veinte paquetes con cinco rangos distintos concentra a la vez el mayor riesgo de bug de doble copia y el mayor ahorro al consolidar. Empieza por ese cuadrante y baja; las dependencias de un solo paquete pueden esperar indefinidamente sin que nadie lo note.

Si prefieres no instalar nada extra, pnpm dedupe --check y pnpm why bastan para el primer diagnóstico; syncpack añade una vista más legible y la capacidad de listar y alinear desajustes en lote, pero es un complemento, no un requisito. Lo esencial del paso no es qué herramienta uses, sino el entregable: una lista escrita de qué diverge y cuánto, ordenada por dónde más duele.

flowchart LR
A[detectar duplicados y deriva] --> B[consolidar en version canonica]
B --> C[referenciar con catalog]
C --> D[activar strict y CI]
style A fill:#eba0ac,color:#11111b
style D fill:#a6e3a1,color:#11111b

Paso 2: consolidar en una versión canónica

Aquí está la única parte que exige criterio humano. Para cada dependencia con rangos divergentes, decides una versión canónica y alineas los paquetes a ella. Casi siempre es la más alta compatible, pero no siempre: una dependencia mayor con cambios rompientes puede obligar a mantener, a propósito, dos cohortes durante la transición —y para eso son los catálogos nombrados de la lección 2—.

catalog:
  react: ^18.3.1
  react-dom: ^18.3.1
  typescript: ^5.4.5
  vite: ^7.0.0

# lo que aun no converge, nombrado y a proposito
catalogs:
  react17:
    react: ^17.0.2
    react-dom: ^17.0.2

La regla mental: el catálogo default es donde va la verdad consolidada; los catálogos nombrados son la lista explícita y auditada de las excepciones que todavía no puedes eliminar. Si una excepción no tiene una razón que puedas escribir en una frase, no es una excepción: es deriva disfrazada, y debe converger.

Para elegir la versión canónica, el punto de partida razonable es la más alta que todos los paquetes puedan tolerar sin cambios rompientes, porque minimiza la deuda futura. Pero no lo decidas a ciegas: revisa el changelog entre la versión más baja en uso y la candidata, y si hay un salto de versión mayor de por medio, trátalo como lo que es —un upgrade real— y no como un simple ajuste de catálogo. Consolidar hacia arriba es deseable; consolidar hacia arriba sin mirar qué rompe en el camino es cómo se introduce un incidente con forma de commit inocente.

⚠️
Consolidar una versión puede romper: hazlo con red

Alinear un paquete que estaba en ^17 con un catálogo en ^18 es un upgrade real, con sus posibles cambios rompientes. La migración a catalogs y los upgrades de versión son dos trabajos distintos y conviene no mezclarlos en el mismo commit: primero consolida a la versión que cada paquete ya podía usar, apóyate en tu suite de tests para cada salto que sí implique subir de mayor, y deja para catálogos nombrados lo que aún no pueda dar ese salto. Un diff de migración que además esconde tres upgrades mayores es imposible de revisar y de revertir.

Paso 3: referenciar con el codemod oficial

Con el catálogo definido, falta lo mecánico: reemplazar en decenas de package.json los rangos por catalog:. Hacerlo a mano es tedioso y propenso a errores, así que pnpm publica un codemod oficial que lo automatiza: recorre el workspace, mueve las versiones al catálogo y reescribe las referencias.

# el codemod oficial de pnpm para migrar a catalogs
pnpm dlx codemod pnpm/catalog

El resultado es que cada package.json pasa de declarar números a declarar intención:

// antes
{ "dependencies": { "react": "^18.2.0", "vite": "^6.9.0" } }

// despues
{ "dependencies": { "react": "catalog:", "vite": "catalog:" } }

Tras correr el codemod, ejecuta pnpm install y confirma que el lockfile resuelve las versiones esperadas y que no hay duplicados residuales. El diff será grande pero mecánico y homogéneo —muchas líneas idénticas cambiando ^x.y.z por catalog:—, que es justo la clase de diff que se revisa de un vistazo, al contrario que la consolidación del paso 2, que sí merece revisión línea a línea.

Cierra el paso con una verificación activa, no confiando solo en que compile: un pnpm dedupe para colapsar cualquier duplicado que la consolidación haya dejado resoluble, y una segunda pasada de pnpm why sobre las dependencias que catalogaste para confirmar que ahora resuelven a una única versión. La migración no está terminada cuando el diff pasa el build, sino cuando el grafo demuestra que la deriva que mediste en el paso 1 efectivamente desapareció.

💡
El codemod hace el músculo, tú haces el criterio

El codemod pnpm/catalog reescribe referencias y mueve versiones al catálogo, pero no decide qué versión consolidar cuando hay varias en juego: ante la duda, toma una y te deja el resto. Por eso el orden correcto es humano primero, máquina después: consolida tú las versiones divergentes en el paso 2, y deja que el codemod haga solo la parte mecánica de reescribir sobre un catálogo que ya refleja tus decisiones. Automatizar la reescritura es seguro; automatizar el juicio no lo es.

Paso 4: mantenerlo coherente en el tiempo

Migrar es un acto puntual; mantener la coherencia es continuo. Sin un guardarrail, la deriva reaparece en cuanto alguien haga pnpm add de algo nuevo. Los dos cierres son los de la lección anterior: catalogMode: strict para que nada entre fuera del catálogo, y una comprobación en CI que falle si alguien reintroduce un rango suelto.

# la instalacion frozen falla si el lockfile no cuadra con los manifiestos
pnpm install --frozen-lockfile
# y una verificacion explicita de desajustes de version
pnpm dlx syncpack list-mismatches

La coherencia sostenida no es solo tooling; también es propiedad. Conviene que el catálogo tenga un dueño explícito —una persona o un equipo— que revise los cambios a pnpm-workspace.yaml con el mismo cuidado que un cambio de infraestructura, porque eso es: una edición que afecta a todos los paquetes a la vez. Proteger el archivo del catálogo con una regla de revisión obligatoria convierte cada subida de versión canónica en una decisión consciente y trazable, no en un commit que se cuela entre otros veinte.

ℹ️
El catálogo es infraestructura, y se revisa como tal

Un cambio en una entrada del catálogo se propaga a cada paquete que la referencia en la próxima instalación. Esa amplificación es justo su valor —un cambio, efecto en todo el repo— pero también su riesgo. Trátalo como tratas un cambio en el CI o en el Dockerfile: revisión obligatoria, tests que corran contra la versión nueva antes de mezclar, y un historial claro de quién subió qué versión y por qué. La centralización que te da el catálogo solo es segura si la gobiernas con la seriedad de la infraestructura que en realidad es.

Con el dueño definido, la política strict puesta y la comprobación en CI en su sitio, el monorepo alcanza un estado cualitativamente nuevo: la coherencia de versiones deja de ser una tarea que alguien recuerda hacer y pasa a ser una propiedad que el sistema mantiene solo. Ese es el verdadero punto de llegada de la migración, y la razón por la que el esfuerzo concentrado de los cuatro pasos rinde indefinidamente después.

🔍

Detectar

pnpm why, syncpack list-mismatches y pnpm dedupe --check revelan qué está duplicado y divergente.

🎯

Consolidar

Elige una versión canónica por dependencia; lo que no converge, a un catálogo nombrado y con razón escrita.

🤖

Referenciar

El codemod pnpm/catalog reescribe los package.json a catalog: sin que edites decenas a mano.

🛡️

Blindar

catalogMode: strict más una comprobación en CI impiden que la deriva vuelva a nacer.

La migración es una centralización que se paga una vez y rinde para siempre

Migrar un monorepo a catalogs tiene la forma de las mejores inversiones de ingeniería: un coste concentrado y acotado por adelantado, a cambio de un ahorro difuso pero perpetuo después. El coste es real —un inventario, una ronda de decisiones de consolidación, un diff grande— pero ocurre una sola vez y está limitado por el tamaño actual del repo, no por su futuro. El rendimiento, en cambio, escala con el tiempo y con el equipo: cada upgrade futuro pasa de editar treinta archivos a editar una línea; cada conflicto de merge sobre versiones que ya no ocurre; cada bug de doble copia que nunca se investiga porque nunca sucede. Y hay una lección más honda en cómo se reparte el trabajo de la migración entre la máquina y tú. Las herramientas —el codemod, syncpack, pnpm dedupe— hacen sin esfuerzo toda la parte mecánica: encontrar los duplicados y reescribir las referencias. Lo que no delegan, y no deberían, es el juicio: decidir cuál versión se vuelve canónica, qué excepción merece un catálogo nombrado, qué upgrade es seguro hacer ahora y cuál esperar. Esa asimetría es la firma de la automatización bien entendida: no elimina al ingeniero, lo reubica desde el trabajo repetitivo hacia el trabajo de decisión, que es donde su criterio vale. Un monorepo migrado a catalogs no es solo un repo con menos deriva; es un repo donde la coherencia de versiones dejó de ser una tarea recurrente para convertirse en una propiedad estructural. Eso es lo que separa administrar dependencias de diseñarlas.

⚔️ Planifica y ejecuta una migración
  1. Corre pnpm why sobre tus tres dependencias más compartidas y pnpm dlx syncpack list-mismatches; escribe la lista priorizada de deriva.
  2. Para la peor divergencia, decide la versión canónica y justifica en una frase por qué; separa lo que consolida sin riesgo de lo que implica un upgrade mayor.
  3. Define el catalog default consolidado y, si hace falta, un catálogo nombrado para la cohorte que aún no puede migrar.
  4. Ejecuta pnpm dlx codemod pnpm/catalog, luego pnpm install, y revisa que el diff mecánico y la consolidación queden en commits separados.
  5. Cierra con catalogMode: strict y un paso de CI con pnpm install --frozen-lockfile; verifica que un pnpm add divergente ahora falla.