Skip to content

Work / Charades / Wiki / Concepts

package-manager

Conceptverified 2026-10-30

package-manager

This project uses pnpm (the only lockfile is pnpm-lock.yaml, and every CI workflow runs pnpm install --frozen-lockfile). Do not use npm or yarn, except for npm version, which is the release trigger.

A stray yarn.lock or package-lock.json in the working tree is not harmless. @expo/package-manager resolves in the order bun, yarn, npm, pnpm and returns the first lockfile it finds, so one silently makes every expo install, expo prebuild and expo run:* shell out to that manager and rewrite its own lockfile, a loop that survives until the file is deleted. .gitignore covers them so one cannot be committed, but ignoring a file does not stop Expo from reading it. Deleting it is the only thing that stops the loop. A packageManager field in package.json would not help either: Expo's resolver only looks at lockfiles, and pr-check.yml pins pnpm itself for the reason documented there.

pnpm-workspace.yaml carries two settings that matter:

  • nodeLinker: hoisted: Expo requires it. @expo/cli, metro and react-native deep-import internals of their own dependencies, which pnpm's default isolated layout does not expose, and the dev server dies with "Cannot find module @expo/metro/metro/lib/formatBundlingError.js".
  • minimumReleaseAge: 1440: a version published less than 24h ago will not install. This is the guard against a maintainer-account takeover shipping a credential stealer in a patch release. pnpm install --frozen-lockfile validates the committed lockfile against it too, so a lockfile written with the policy switched off fails in CI.