pnpm-workspace.yaml: declarar el monorepo
El archivo que convierte un directorio en un workspace: el campo packages con globs, la topología apps/ y packages/, la exclusión con ! y por qué en 2026 este YAML absorbió la configuración del monorepo.
Un monorepo no es una carpeta con muchos proyectos dentro: es un conjunto de paquetes que pnpm entiende como un grafo único, con un solo lockfile, un node_modules virtual compartido y dependencias que se enlazan entre sí sin pasar jamás por el registro. Lo que dispara toda esa maquinaria es un archivo diminuto en la raíz: pnpm-workspace.yaml. Sin él tienes carpetas sueltas; con él tienes un workspace.
- Entender que
pnpm-workspace.yamles la señal que define la raíz del workspace. - Declarar los paquetes con el campo
packagesy patrones glob. - Diseñar la topología canónica
apps/frente apackages/. - Conocer cómo en 2026 este archivo absorbió catalogs y la configuración del monorepo.
El archivo que crea el monorepo
pnpm decide que un directorio es la raíz de un workspace por una única señal: la presencia de pnpm-workspace.yaml. Ese archivo, aunque estuviera casi vacío, cambia el comportamiento de todos los comandos. A partir de él, pnpm install deja de operar sobre un solo paquete y pasa a resolver, enlazar e instalar un conjunto de paquetes a la vez, compartiendo un único store por contenido y un único lockfile (pnpm-lock.yaml) en la raíz.
Esa consolidación es lo que hace que instalar diez paquetes cueste casi lo mismo que instalar uno: las dependencias comunes se enlazan una sola vez desde el store global direccionado por contenido, y cada paquete recibe un node_modules con enlaces en lugar de copias.
Su corazón es el campo packages, una lista de patrones glob que enumeran dónde viven los paquetes:
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 'tooling/*'
Cada ruta que casa con un patrón y contiene un package.json con un campo name se convierte en un workspace project. El directorio raíz también es un proyecto del workspace —el proyecto raíz—, aunque no aparezca en packages; casi siempre lleva "private": true en su package.json para que nadie lo publique por accidente y para alojar las dependencias de tooling comunes.
En un workspace pnpm no hay un lockfile por paquete: hay un único pnpm-lock.yaml en la raíz que describe el grafo completo, todas las versiones resueltas y todos los enlaces internos. Es la fuente de verdad de la instalación reproducible y lo que debes commitear siempre.
apps/ y packages/: la topología canónica
La convención que domina el ecosistema en 2026 separa dos naturalezas distintas de paquete:
apps/
Cosas que se despliegan: la web en Astro, el sitio de docs, una API en el edge. Son consumidores finales del grafo; nadie depende de ellas. No se publican al registro y suelen ser privadas.
packages/
Código compartido: la librería de UI, utilidades, el cliente de la API, configuraciones (eslint-config, tsconfig). Son productores; las apps y otros paquetes dependen de ellos.
La regla mental es la dirección de las flechas: las dependencias siempre apuntan de apps/ hacia packages/, y dentro de packages/ de lo específico hacia lo genérico. Muchos equipos añaden un tercer directorio, tooling/ o config/, para paquetes que solo existen para configurar a los demás (presets de ESLint, tsconfig base, configuración de Tailwind).
En el disco, un monorepo canónico se ve así:
acme/
├─ pnpm-workspace.yaml # define el workspace
├─ package.json # raiz privada, scripts orquestadores
├─ pnpm-lock.yaml # un unico lockfile para todo
├─ apps/
│ ├─ web/ # @acme/web (Astro, se despliega)
│ └─ docs/ # @acme/docs
└─ packages/
├─ ui/ # @acme/ui (libreria compartida)
└─ utils/ # @acme/utils
flowchart TD R[raiz: pnpm-workspace.yaml] --> A[apps] R --> P[packages] R --> T[tooling] A --> A1[web] A --> A2[docs] P --> P1[ui] P --> P2[utils] T --> T1[eslint-config] T --> T2[tsconfig] A1 --> P1 A1 --> P2 P1 --> P2
Globs, exclusiones y el paquete raíz
Los patrones son globs, no rutas literales, y esa distinción importa:
packages/*casa solo con hijos directos:packages/ui,packages/utils.packages/**desciende a cualquier profundidad: útil si agrupas por dominio, comopackages/data/dbopackages/data/schema.- Un patrón que empieza por
!excluye. El caso típico es descartar fixtures de test que llevan su propiopackage.jsonde mentira:
packages:
- 'packages/**'
- 'apps/*'
- '!**/test/fixtures/**'
Solo cuentan los directorios que, además de casar con un glob, contengan un package.json con nombre. Una carpeta vacía o sin manifiesto se ignora en silencio. Si un paquete que esperabas no aparece en pnpm list -r, la causa casi siempre es un glob que no lo alcanza o un name ausente.
La lista de packages es un conjunto, no una secuencia: da igual si apps/* va antes o después de packages/*. El orden en que se construyen los paquetes no sale de este archivo, sino del grafo de dependencias workspace: que verás en el nivel 7.4. Este YAML declara qué paquetes existen; el grafo decide en qué orden se tocan.
El YAML que se lo comió todo (2026)
Hasta pnpm 9 la configuración del monorepo estaba repartida entre .npmrc, el campo pnpm del package.json raíz y este archivo. pnpm 10 consolidó casi todo dentro de pnpm-workspace.yaml, que hoy es el panel de control del repo. Además de packages, aquí viven los catalogs (versiones centralizadas) y onlyBuiltDependencies, la allowlist de scripts de instalación:
packages:
- 'apps/*'
- 'packages/*'
catalog:
react: ^19.0.0
typescript: ^5.7.0
onlyBuiltDependencies:
- esbuild
- '@parcel/watcher'
catalog define una versión única para una dependencia que cualquier paquete referencia luego con catalog: (lo verás en el nivel 7.5). onlyBuiltDependencies es la respuesta de pnpm 10 al riesgo de supply-chain: por defecto ya no ejecuta los scripts postinstall de tus dependencias, y solo corren los que pongas explícitamente en esa lista.
Entre las claves que hoy viven en pnpm-workspace.yaml, además de packages, están:
catalogycatalogs— versiones centralizadas, la política de versión única del repo.onlyBuiltDependencies— allowlist de paquetes con permiso para ejecutar scripts de instalación.overrides— forzar una dependencia transitiva a una versión concreta en todo el grafo.patchedDependencies— parches locales aplicados conpnpm patch.packageExtensions— corregir metadatos incorrectos de paquetes de terceros sin hacer fork.
Reunir todo esto en un solo archivo tiene una consecuencia práctica: el estado del monorepo se lee de un vistazo, versionado en git, sin bucear entre un .npmrc oculto y el campo pnpm disperso por varios package.json.
pnpm-workspace.yaml parece un detalle de configuración, pero lo que declara es una ontología: qué paquetes existen en este mundo cerrado y cuáles no. Esa frontera es la que da a pnpm su superpoder frente a instalar cada proyecto por separado. Dentro de la frontera, pnpm garantiza que un import de @acme/ui se resuelve al código local por enlace simbólico, no a una copia descargada del registro; que todos comparten una única resolución de versiones en un solo lockfile; y que una tarea puede recorrer el grafo en orden topológico. Cambiar los globs no es editar texto: es redibujar el universo de paquetes que pnpm considera reales. Por eso el diseño de esta topología —qué es una app, qué es un paquete compartido, dónde vive el tooling— es la decisión de arquitectura más temprana y más duradera de un monorepo. Se hace en cinco líneas de YAML y condiciona los siguientes tres años de vida del repositorio: dónde se pone el código nuevo, qué puede depender de qué, y cómo de rápido compila. La brevedad del archivo es inversamente proporcional a su peso.
- Crea un directorio nuevo con
pnpm-workspace.yamldeclarandoapps/*ypackages/*, y unpackage.jsonraíz con"private": true. - Añade
apps/webypackages/utils, cada uno con supackage.jsony unnamecon scope (por ejemplo@acme/utils). - Ejecuta
pnpm instally luegopnpm list -r: confirma que pnpm ve los dos paquetes y el proyecto raíz. - Añade un glob de exclusión
!**/fixtures/**y crea una carpetafixturescon unpackage.jsonfalso: verifica que ya no aparece. - Investiga: abre el
pnpm-lock.yamlgenerado y localiza la secciónimporters; ahí está listado cada paquete del workspace.