Command Palette

Search for a command to run...

Command Palette

Search for a command to run...

Tech
Previous

Let Your Dependencies Explain Themselves

pnpm catalog is more than version management — semantic named catalogs make your intent readable by both humans and tools.

Open any package.json using pnpm catalog, and you'll likely see something like this:

{
  "dependencies": {
    "react": "catalog:",
    "puppeteer": "catalog:",
    "shadcn": "catalog:"
  },
  "devDependencies": {
    "@types/react": "catalog:",
    "typescript": "catalog:",
    "tailwindcss": "catalog:"
  }
}

Catalog centralizes versions. That's good. But what about semantics?

react is a runtime dependency. puppeteer is an automation script. shadcn is a codegen tool. tailwindcss is a CSS compiler. @types/react is a type-only declaration. From the catalog's perspective, they all look identical — just the string "catalog:", with no other information. You have to dig into pnpm-workspace.yaml and find the comments to figure out who is who.

Comments Are For Humans

The common pattern is to group packages with comments in pnpm-workspace.yaml:

catalog:
  # Framework
  next: 16.1.7
  react: 19.2.6
 
  # Tooling
  typescript: ^6.0.3
  puppeteer: ^24.43.1
 
  # Types
  "@types/react": 19.2.14
  "@types/node": ^25.9.1

Readable to humans. But tools can't read it. # Types means nothing to Vite, unbuild, or a vulnerability scanner. They see a flat version table — no way to distinguish runtime from dev-only, or type-only from bundled.

Catalog Names Are Semantics

pnpm's named catalogs aren't just for version isolation — they can be semantic labels.

Replace "catalog:" with "catalog:build", "catalog:types", "catalog:script", and you no longer need to check the comments in pnpm-workspace.yaml. The reason each package belongs here is right there in package.json:

{
  "devDependencies": {
    "typescript": "catalog:build",
    "tailwindcss": "catalog:build",
    "@types/react": "catalog:types",
    "@types/node": "catalog:types",
    "puppeteer": "catalog:script",
    "shadcn": "catalog:script"
  }
}

The corresponding pnpm-workspace.yaml:

catalogs:
  build:
    typescript: ^6.0.3
    tailwindcss: ^4.3.0
    vite: npm:@voidzero-dev/vite-plus-core@^0.1.24
 
  types:
    "@types/react": 19.2.14
    "@types/node": ^25.9.1
    "@types/hast": ^3.0.4
 
  script:
    puppeteer: ^24.43.1
    shadcn: ^4.10.0

Tools Start Talking Back

Put semantics into names, and tools can start understanding your classification. taze is a version bumping tool with pnpm catalog support. After this restructuring, its output shifted from a flat list to groups organized by catalog name:

pnpm-catalog:default  — 2 minor, 4 patch
  posthog-js       ^1.376.4 → ^1.379.1
  react-hook-form  ^7.76.1  → ^7.77.0
  geist            ^1.7.1   → ^1.7.2
 
pnpm-catalog:build    — 2 patch
  vite      ^0.1.23 → ^0.1.24
  vite-plus ^0.1.23 → ^0.1.24
 
pnpm-catalog:script   — 2 minor
  @fission-ai/openspec ^1.3.1 → ^1.4.1
  shadcn               ^4.8.3 → ^4.10.0

default updates are runtime — test them. build updates are toolchain — lower risk. script updates are codegen — usually don't affect production. You no longer need to look up each package to judge its blast radius. The catalog name already told you.

This is just the beginning. If build tools start reading catalog metadata, Vite could precisely control which packages to pre-optimize, unbuild could decide externalization with precision, and vulnerability scanners could apply different severity levels based on catalog. Semantics in the name is what makes any of that possible.

Let Catalog Names Explain the Reason

dependencies vs devDependencies was the npm-era distinction. It made sense then. But modern toolchains have overloaded devDependencies with too many meanings — bundled, type-only, test runners, codegen scripts, all mixed together. Even the clearest comment is only human-readable documentation, not machine-understandable information.

Named catalogs are an opportunity to lift semantics to a machine-readable layer. And the cost is low — just change a string.

Let your catalog names explain the reason, not the comments.