ReadyNative

Package managers

bun is the recommendation; npm, pnpm and yarn are fully supported - how setup detects yours, and what changes in the tree.

Every command in these docs is written as a bun command and rendered for four package managers. Switch with the tab strip above each block; your choice sticks across the whole site. Nothing in ReadyNative requires bun.

bun is the default, not a requirement

I recommend bun because it's the fastest of the four here and doubles as a TypeScript runtime. But setup, doctor and the generators are plain node scripts/*.ts files, so they run under whichever package manager you use:

bun install
bun run setup

npm needs a double dash

npm hands flags to itself unless you separate them: npm run setup -- --preset saas --yes. The tabs above already do this for you; pnpm, yarn and bun pass flags straight through.

Node version

scripts/*.ts run under plain node, which strips TypeScript types natively from 22.18 onwards - that's what engines.node in package.json asks for, and Node 24 LTS is what I run. setup and doctor run scripts/check-node.cjs first, a plain CommonJS check that works on any Node, so an older one gets "ReadyNative needs Node ≥ 22.18 (you have vX)" instead of a syntax error. doctor also fails its Node row below 22.18.

pnpm needs a hoisted node_modules

pnpm's default layout is a tree of symlinks, and Metro doesn't resolve the app's dependencies through it reliably. Put this .npmrc at the root before the first pnpm install:

.npmrc
node-linker=hoisted

That's the configuration I test pnpm trees with. npm, yarn and bun need nothing.

How setup picks your package manager

setup, finalize and doctor all resolve it the same way, first match wins:

OrderSource
1The --pm <name> flag
2npm_config_user_agent - the package manager that launched the script
3The packageManager field in package.json (what corepack reads)
4A lockfile on disk: bun.lock / bun.lockb, pnpm-lock.yaml, yarn.lock, package-lock.json
5bun if it is on your PATH, otherwise npm

In practice step 2 is enough: run pnpm install && pnpm run setup and the whole tree is a pnpm tree. To force it explicitly:

bun run setup --pm pnpm

--pm only accepts bun, pnpm, yarn or npm; anything else is an error rather than a silent fallback. setup prints the answer in its plan (package manager: pnpm (pnpm-lock.yaml)), writes the name into .readynative.json, and bun run doctor shows it as its own row.

What each one runs

Here's how the everyday commands map across the four:

Package managerinstallCI installrun a scriptrun a binary
bunbun installbun install --frozen-lockfilebun run <script>bunx <bin>
pnpmpnpm installpnpm install --frozen-lockfilepnpm run <script>pnpm exec <bin>
yarnyarn installyarn install --immutableyarn run <script>yarn run <bin>
npmnpm installnpm cinpm run <script> -- <args>npx --no-install <bin>

The "run a binary" column never reaches out to a registry: pnpm exec and npx --no-install fail loudly instead of downloading something, and bunx prefers node_modules/.bin. That's what expo install, expo start and eas-cli go through.

Lockfiles

The repo you clone ships bun.lock. When setup finalizes the tree with another package manager it deletes the lockfiles that don't belong to it - a stale bun.lock next to package-lock.json is exactly the kind of thing that makes CI install the wrong versions.

It also drops a "packageManager": "bun@…" field from package.json when you pick npm, pnpm or yarn: they read that field through corepack and would refuse to run if it named a different tool. setup never adds the field, so the lockfile is the declaration.

CI

Your tree ships no CI workflows. If you add your own, use the frozen/immutable install command from the table above, and your package manager's script syntax (-- included where npm needs it). Expo's GitHub Actions guide covers the EAS side.

Switching later

A finalized tree switches by hand, because setup is a stub by then:

  1. Delete the old lockfile (and a "packageManager" field in package.json, if you're leaving bun).
  2. Install with the new package manager so it writes its own lockfile.
  3. If you added your own CI, update its setup and install steps - the table above has the commands.

In a tree kept with --keep-modules, bun run setup --pm npm --yes --keep-modules removes the other lockfiles and installs with npm; edit any CI you added the same way.

On this page

Get ReadyNative