Field note
26

Field note · Published Aug 19, 2026

I Moved This Blog to pnpm, Then Found npm Everywhere

The lockfile and CI moved to pnpm in one commit. The harder part was removing the npm instructions that kept telling people and agents to move backward.

A field-guide drawing of a black-tailed deer
In this article4 sections

I moved this blog from npm to pnpm on August 13. Six days later, the repository was still telling me to run npm.

The first migration changed the machinery that actually installs and deploys the site. I deleted package-lock.json, generated pnpm-lock.yaml, pinned pnpm@11.21.0 in package.json, and changed both GitHub Actions workflows to install with pnpm.

That was enough for CI. It wasn’t enough for the repository.

The old package manager survived in plain sight

README.md still opened with npm install. AGENTS.md gave coding agents the same instruction. The local drafting guide, empty-draft screen, and draft generator all printed npm commands.

Even the pnpm-driven validate script called npm run for every nested step:

{
  "scripts": {
    "validate": "npm run check && npm run typecheck && npm run build"
  }
}

That command works because npm is still installed alongside Node. It also makes the migration ambiguous. A person reading the workflow sees pnpm. A person reading the README sees npm. An agent sees both and has to decide which file is stale.

I have written before that repository instructions are part of the build system. This was the same failure in my own site. The executable path changed, but the instructions around it did not.

What actually changed

The complete migration has one package manager at every layer:

LayerBeforeAfter
Dependency lockpackage-lock.jsonpnpm-lock.yaml
Local installnpm installpnpm install
Nested scriptsnpm run buildpnpm run build
CI installnpm cipnpm install --frozen-lockfile --ignore-scripts
CI cache keypackage-lock.jsonpnpm-lock.yaml
Tool versionImplicit npm from NodepackageManager: pnpm@11.21.0

The original lockfile was 7,477 lines. The generated pnpm lockfile was 4,632. I did not migrate for the line count, and I did not benchmark installation time, so I am not claiming a speedup from this site.

The reason was consistency. My Cafyn workspace already uses pnpm, and keeping one set of package-manager habits removes a small but repeated decision from local work and CI.

This is not a monorepo migration

kahwee-com still has its own repository, lockfile, Cloudflare Worker, domain, and deployment workflow. I did not move the blog into the Cafyn workspace.

That boundary matters. pnpm has first-class workspace support, but adopting pnpm does not require turning every project into one workspace. This blog can use the same package manager while keeping its deployment independent.

The lockfile is only half the migration

The final cleanup replaced every operational npm command in current documentation, package scripts, drafting tools, and UI copy. Historical posts keep their original commands when npm is part of what they documented.

CI now installs from the committed lockfile and refuses to rewrite it. pnpm’s CI documentation explains that frozen-lockfile behavior and why the pnpm version should match the lockfile. Its installation guide covers the supported pnpm 11 setup used here.

The migration is finished when the next person can follow any current instruction without accidentally recreating package-lock.json. In this repository, that took a second pass.