wandres.dev
LOCKFILES · reproducibilidad

La estructura de pnpm-lock.yaml

Anatomía del lockfile de pnpm en 2026: lockfileVersion, settings, importers, la separación packages/snapshots, y los campos resolution e integrity. Cómo leerlo con soltura.

⏱ 15 min

Casi nadie lee su pnpm-lock.yaml: lo tratan como un blob generado que ensucia los diffs. Es un error. El lockfile es un documento legible y muy bien diseñado, y saber recorrerlo te da un superpoder: entender por qué una versión concreta acabó en tu árbol, auditar un cambio sospechoso en un pull request, o diagnosticar un conflicto de peerDependencies sin adivinar. En 2026, con pnpm 10 y el formato de lockfile 9.0, la estructura es más limpia que nunca. Vamos a diseccionarla.

🎯 Al terminar esta lección sabrás
  • Leer el encabezado: lockfileVersion y el bloque settings.
  • Entender importers como el mapa de tus proyectos del workspace.
  • Distinguir packages (metadatos) de snapshots (el grafo de dependencias).
  • Interpretar resolution e integrity para rastrear la procedencia de un paquete.

El encabezado: versión y settings

Las primeras líneas fijan el contrato de formato y las opciones que afectan a la resolución:

lockfileVersion: '9.0'

settings:
  autoInstallPeers: true
  excludeLinksFromLockfile: false

lockfileVersion es crítico: pnpm rechaza (o migra) lockfiles cuyo formato no entiende, y por eso todo el equipo debe usar versiones compatibles de pnpm —se fija con packageManager en package.json y Corepack—. El bloque settings graba las opciones que cambian el árbol resuelto. Se guardan aquí a propósito: si dependieran solo de tu configuración local, dos personas con distinto .npmrc obtendrían árboles distintos, y adiós reproducibilidad.

Los ajustes que verás con más frecuencia:

  • autoInstallPeers — si pnpm instala automáticamente los peerDependencies no satisfechos.
  • excludeLinksFromLockfile — si los enlaces locales se omiten del lockfile.
  • peersSuffixMaxLength — cuánto se abrevia el identificador de peers en las claves (lo veremos abajo).

importers: el mapa de tus proyectos

Aquí es donde el diseño de pnpm brilla frente a otros lockfiles. La sección importers lista cada proyecto del workspace (cada package.json) por su ruta relativa, con sus dependencias declaradas y a qué versión exacta resolvió cada una:

importers:
  .:
    dependencies:
      zod:
        specifier: ^3.23.0
        version: 3.23.8
  packages/ui:
    dependencies:
      react:
        specifier: ^19.0.0
        version: 19.1.0

Cada entrada tiene el par clave: specifier (el rango que escribiste) y version (lo que se resolvió). Esa dualidad es el corazón del lockfile: cuando corres una instalación congelada (nivel 6.3), pnpm compara el specifier del lockfile con el rango de tu package.json; si no coinciden, aborta. En un repo de un solo paquete solo verás el importador .; en un monorepo verás uno por cada workspace, lo que convierte a importers en un índice navegable de todo el repo.

packages y snapshots: metadatos frente a grafo

El formato 9.0 separó en dos secciones lo que antes iba mezclado, y entender la división es la clave para leerlo:

🏷️

packages

Metadatos por versión, independientes del contexto: resolution (procedencia e integridad), engines, los rangos de peerDependencies que declara el paquete. Es la “ficha de identidad” inmutable de cada versión.

🕸️

snapshots

El grafo real: para cada paquete, a qué versión exacta resolvió cada una de sus dependencias en este árbol. Es donde vive la topología, incluida la resolución concreta de los peers.

packages:
  zod@3.23.8:
    resolution: {integrity: sha512-Aek05...MJVR7A==}
    engines: {node: '>=18'}

snapshots:
  zod@3.23.8: {}
  '@tanstack/react-query@5.59.0(react@19.1.0)':
    dependencies:
      '@tanstack/query-core': 5.59.0
      react: 19.1.0

Fíjate en la clave @tanstack/react-query@5.59.0(react@19.1.0): ese sufijo entre paréntesis es un peer identifier. Codifica que esta instancia de react-query se resolvió contra react@19.1.0. Si otro rincón del árbol necesitara react-query contra otra versión de React, aparecería una segunda entrada con distinto sufijo. Así pnpm representa, sin ambigüedad, que “el mismo paquete” puede tener grafos distintos según su contexto de peers. La razón de separar packages de snapshots es evitar duplicar los metadatos: la ficha de una versión se escribe una vez, y sus múltiples instanciaciones por contexto de peer viven baratas en snapshots.

resolution e integrity: identidad y verificación

Dentro de packages, el campo resolution responde a “de dónde sale esto y cómo sé que no ha sido alterado”:

  # desde el registro npm: hash de contenido
  esbuild@0.24.0:
    resolution: {integrity: sha512-vF1...Q==}

  # desde un tarball directo
  mi-lib@1.0.0:
    resolution: {tarball: https://ejemplo.dev/mi-lib-1.0.0.tgz}

El integrity es un hash Subresource Integrity del contenido del paquete. En la instalación, pnpm descarga (o toma del store global de contenido direccionable), recalcula el hash y lo compara: si difiere, falla en vez de instalar algo alterado. Rastrear un paquete es, entonces, mecánico:

  • Búscalo en importers para ver quién lo pide directo y con qué rango.
  • Búscalo en snapshots para ver quién lo arrastra transitivamente y bajo qué contexto de peers.
  • Búscalo en packages para ver su procedencia e integridad.

Si usas catalogs (versiones compartidas de todo el workspace), el lockfile añade además una sección catalogs que graba a qué versión exacta resolvió cada entrada del catálogo: la misma lógica de specifier frente a version, pero elevada al conjunto del monorepo en un único punto de verdad.

flowchart TD
A[lockfileVersion y settings] --> B[importers: tus proyectos]
B --> C[specifier + version por dependencia]
C --> D[snapshots: el grafo resuelto]
D --> E[packages: ficha de cada version]
E --> F[resolution + integrity]
style B fill:#89b4fa,color:#11111b
style D fill:#cba6f7,color:#11111b
style E fill:#a6e3a1,color:#11111b
💡
Deja que la herramienta lea por ti

Leer el lockfile a mano enseña, pero para el día a día usa las consultas que pnpm ofrece. pnpm why <paquete> te dice quién arrastra una dependencia y en qué versión; pnpm list --depth=Infinity despliega el árbol completo; pnpm licenses list inventaría las licencias del grafo. Contrastar tus deducciones manuales con estas salidas es la mejor forma de comprobar que entiendes de verdad la estructura, en vez de solo reconocerla.

El lockfile es documentación ejecutable del grafo

La tentación es tratar el lockfile como ruido: un archivo enorme que hincha los diffs y que nadie revisa. Quien piensa a nivel de sistema hace lo contrario: lo lee como el documento más honesto del repositorio. El package.json expresa intención (rangos, deseos); el lockfile expresa realidad (el grafo exacto que se ejecuta en producción). Cuando un pull request añade una dependencia trivial y el diff del lockfile trae cuatrocientas líneas nuevas, eso no es ruido: es información. Te está diciendo cuántos paquetes transitivos, de cuántos autores distintos, acabas de invitar a tu supply chain. El peer identifier entre paréntesis, la separación packages/snapshots, el hash de integridad: cada pieza existe para que el grafo sea inspeccionable y verificable, no solo instalable. Aprender a leerlo convierte “no sé por qué está esta versión aquí” en una consulta de treinta segundos, y esa fluidez es exactamente lo que distingue a quien depura la cadena de dependencias de quien la sufre.

⚔️ Disecciona tu propio lockfile
  1. Abre tu pnpm-lock.yaml y localiza la sección importers: cuenta cuántos proyectos hay y confirma que coinciden con tus workspace.
  2. Elige una dependencia directa y sigue su rastro: specifier en importers, entrada en snapshots, ficha en packages.
  3. Encuentra un paquete con un peer identifier entre paréntesis y explica contra qué peer se resolvió y por qué aparece así.
  4. Ejecuta pnpm why react (o cualquier paquete tuyo) y contrasta su salida con lo que dedujiste leyendo el lockfile a mano.
  5. Cambia un rango en package.json, corre pnpm install y observa qué líneas del lockfile se movieron: relaciona cada cambio con specifier, version o snapshots.