Scripts
Every command in package.json - what it does, which flags it takes, and which ones survive setup.
All CLIs are thin files in scripts/; the logic lives in scripts/lib/*.ts. They run under
plain node (Node 22.18+ strips TypeScript types natively), so bun run, npm run, pnpm run
and yarn run are interchangeable - see Package managers. Every script
accepts --root <dir> (default: cwd), which is how the unit tests run them against fixture
trees.
Scripts fall into two groups. Stays after setup - what lives in the tree you ship:
doctor, gen, gen:theme, gen:links, gen:privacy, gen:assets, gen:graph,
test:e2e, plus typecheck / lint / format / test. Module system - leaves with
modules/ unless you pass --keep-modules: setup itself (it stays as a one-line stub that
says it already ran) and clean.
Setup and health
bun run setup
This is the one that assembles your tree.
bun run setup [--preset minimal|default|saas] [--<category> <option>]…
[--yes] [--dry-run] [--no-install] [--keep-modules]
[--with-examples] [--pm bun|pnpm|yarn|npm] [--root <dir>]It copies the selected modules into the tree, generates the providers, env and
.readynative.json, patches package.json / tsconfig.json, strips the demo content (unless
--with-examples) and, unless --keep-modules, finalizes the tree: the kit moves to
src/components/ui and scripts/ is trimmed to the ones marked "stays" above. Every step is in
How setup works. On the Free tier none of this exists: the tree ships
already set up, without scripts/.
| Flag | Effect |
|---|---|
--preset <name> | Start from minimal, default or saas |
--<category> <opt> | Override one category, e.g. --data none, --ui tamagui, --auth supabase |
--yes | No prompts; take preset/flags (or the previous .readynative.json) |
--dry-run | Print the plan (selection, package manager, Expo Go verdict, dependency delta, files) and change nothing |
--no-install | Skip the install and expo install after applying |
--keep-modules | Do not delete modules/ afterwards - required to run setup again with another selection |
--with-examples | Keep the weather demo, the example screens and the UI catalog (default: strip them) |
--pm <name> | Force bun, pnpm, yarn or npm instead of the detected one |
--help | Usage plus every category and option |
It's idempotent: the same selection twice produces no diff. Unknown options, unmet requires
and conflicts are errors raised before anything is written. When it finishes, it prints the
post-setup steps of every selected service, one guide link each, and a
https://readynative.app/docs/?stack=… URL that opens these docs pre-filtered to your
selection.
bun run doctor
The post-setup checklist, with a non-zero exit on failures: .readynative.json, the env keys
your modules need, placeholders in readynative.config.ts, eas whoami, the native toolchains
when a module needs them, your package manager, and the bun and Node versions. --store turns
the "Before you ship" warnings into failures. It closes with the ?stack= docs link for your
selection. Row by row: Doctor.
Generators
All of these stay in your tree after setup. gen module is the exception in practice: it
scaffolds into modules/, so it's useful only while that folder exists.
| Script | What it does |
|---|---|
bun run gen:theme [--check] | src/theme/tokens.ts → src/global.css (nativewind5), src/global.css + tailwind.config.js (nativewind4), tamagui.config.ts; writes the active stack's file at the root (and, while modules/ exists, every stack's copy under modules/ui/<stack>/files/). --check exits 1 on drift. See Styling → Theme tokens and gen:theme |
bun run gen:links [--check] | links in readynative.config.ts → public/.well-known/apple-app-site-association + assetlinks.json for the prod/preview/dev ids |
bun run gen:privacy [--owner "<entity>"] | readynative.config.ts + .readynative.json (the selected modules' privacy declarations) → docs/privacy-policy.md, a privacy-policy draft matching what the build ships. --stdout prints instead. Re-run after setup changes a module; not legal advice. |
bun run gen:assets [--source <png>] | assets/brand/icon.png + brand.primary → icon, adaptive icon layers, splash icon, favicon (runs under Node) |
bun run gen:graph [--check] | The tree → docs/graph.json + docs/ARCHITECTURE.md: modules, providers, lib shims, routes, screens, features, stores, hooks, env keys, services and scripts, with the imports / provides / reads / renders / configures edges between them. --check exits 1 when the committed files are stale - run it before you commit, or add it to your own CI |
bun run gen screen <name> | src/screens/<name>/<name>-screen.tsx + the src/app/<name>.tsx route. --tab [--icon <sf-symbol>] [--icon-fallback <material>] [--title "<Title>"] routes it under src/app/(tabs)/ and inserts a <NativeTabs.Trigger> entry (SF Symbol + Material icon, localised label) at the {/* gen:tabs */} marker in src/app/(tabs)/_layout.tsx (plus the title key in src/locales/en.json); --modal writes src/app/<name>/{_layout,index}.tsx with presentation: "modal" and registers it at the {/* gen:stack */} marker in src/app/_layout.tsx. Mutually exclusive; a missing marker prints the block to paste |
bun run gen module <category> <option> | modules/<category>/<option>/{module.json,files/,docs.md} scaffold - then fill module.json and docs.md |
Run gen:graph whenever you add a route, a lib shim, a provider or an env key - it's how
agents working in your repo find their way around. To extend it without touching the script, see
Conventions → Extending the graph.
Housekeeping
This one exists only while modules/ does: before setup, or after setup --keep-modules.
Finalizing removes it, and the two example apps never had it. In a finalized tree, delete the
files it lists by hand.
| Script | What it does |
|---|---|
bun run clean [--no-examples] [--yes] | Remove demo content - what setup already does unless --with-examples: src/app/_catalog.tsx and its Settings row, placeholder assets, plus the weather demo (src/app/(tabs)/cities/, src/screens/weather/, src/lib/weather/, src/stores/weather.ts, src/hooks/use-forecast.ts, its tab), src/app/examples/, src/screens/examples/, src/examples.ts, the example hook/store (and their tests) and the Examples row in Settings. The home screen becomes a plain welcome and the Weather tab turns back into Home. --no-examples keeps the weather demo and the examples tab |
Checks
The four I run before every commit, plus the end-to-end runner.
| Script | Command |
|---|---|
bun run typecheck | tsc --noEmit |
bun run lint | expo lint (ESLint flat config) |
bun run lint:fix | expo lint --fix (auto-fixable rules + Prettier formatting) |
bun run format | prettier --write . |
bun run test | jest - present only with testing=jest; testing=none removes the script and every __tests__ |
bun run test:e2e | node scripts/e2e.ts - runs the Maestro flows in .maestro/flows against an installed dev build, with APP_ID / APP_NAME read from readynative.config.ts. --variant dev|preview|prod picks the ids (default dev), --dry-run prints the maestro command |
Your tree ships no git hooks and no CI: nothing runs on git commit or push. Run
bun run typecheck, bun run lint, bun run test and bun run gen:graph --check yourself, or
wire them into your own CI. For checks before each commit, add a hook runner
yourself - for example bun add -d lefthook, a lefthook.yml with a pre-commit job, then
bunx lefthook install.
Expo
bun run start / ios / android / web wrap expo start, expo run:ios, expo run:android
and expo start --web. start is expo start --go when every selected module runs in Expo Go
and expo start --dev-client otherwise (the tree always carries expo-dev-client, so a bare
expo start would wait for a dev build); bun run start:dev always targets the dev build.
There's no release script in your tree: a release is npm version <patch|minor|major> plus
git push --follow-tags, then an EAS production build - see
Ship to TestFlight → Automate it.
Module-provided scripts
A module may add scripts through package.scripts in its module.json; they disappear when the
module isn't selected (test from testing/jest and i18n:extract from i18n/lingui are two).
The one with its own file is push:test, from push/expo-notifications Pro: a
tree without that module has no push:test and no scripts/push-test.ts. It sends one
notification through the Expo push service to a device token (read it from
push.usePushToken().token, or from /examples/push when you kept the examples):
bun run push:test "ExponentPushToken[…]" "Title" "Body"It runs scripts/push-test.ts on plain node, so it works with any package manager
(npm run push:test -- … on npm), and it survives finalizing: any scripts/ file a
package.json script runs is kept. The notification deep-links to /examples/push, which only
exists with --with-examples; without the examples, pass --url / (or any route you have).
Remote push needs a dev build on a physical device - see
Add push notifications.