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 installbun run setupnpm 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:
node-linker=hoistedThat'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:
| Order | Source |
|---|---|
| 1 | The --pm <name> flag |
| 2 | npm_config_user_agent - the package manager that launched the script |
| 3 | The packageManager field in package.json (what corepack reads) |
| 4 | A lockfile on disk: bun.lock / bun.lockb, pnpm-lock.yaml, yarn.lock, package-lock.json |
| 5 | bun 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 manager | install | CI install | run a script | run a binary |
|---|---|---|---|---|
| bun | bun install | bun install --frozen-lockfile | bun run <script> | bunx <bin> |
| pnpm | pnpm install | pnpm install --frozen-lockfile | pnpm run <script> | pnpm exec <bin> |
| yarn | yarn install | yarn install --immutable | yarn run <script> | yarn run <bin> |
| npm | npm install | npm ci | npm 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:
- Delete the old lockfile (and a
"packageManager"field inpackage.json, if you're leaving bun). - Install with the new package manager so it writes its own lockfile.
- 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.
Update from upstream
Pull fixes and new modules from a newer ReadyNative release into your app - with modules/ kept, after finalizing, and on the Free tier.
Troubleshooting
Fixes for the most common failures - setup, bun and Node, Expo Go vs dev builds, env keys on EAS, sign-in redirects, paywalls, push and Metro.