The starting point for a new React Native app: an Expo project with the signing, the pipelines, the emulators and the store submissions already working.
Badges are per branch — these are main's. See CI.
Every React Native project pays the same tax before it ships anything. Signing that only fails on a Tuesday. An emulator that hangs in CI and nowhere else. The eleventh linter. A release nobody remembers the order of. Months of it, and none of it is the app itself.
That tax is paid here once. Press Use this template, run make init, and
the result is an app that builds, tests itself on real devices, and ships to
both stores from a merged pull request.
flowchart LR
push[push] --> checks[Checks] --> unit[Unit] --> e2e[E2E on device]
checks --> sec[Security scans]
e2e --> merge[merge to main]
merge --> relpr[Release PR] --> build[Signed builds] --> stores[TestFlight and Play]
Where to start. Three ways through this repository:
- Starting a new app — Getting started to run it, then
Using this template for what
make initrewrites and local-dev.md for the long version. - Working in the app — What is in here maps every directory, architecture.md covers data flow, providers and env, quality.md says which linter owns which rule.
- Shipping it — The pipelines lists every workflow and job, release-runbook.md is what this app sets for a release (the procedure itself is in shared-workflows), store-accounts.md is the accounts and credentials.
make setup # blank machine → ready: toolchain, deps, Maestro, Android SDK + emulator, iOS
make doctor # verify it (one line per tool)
make dev-api # terminal 1: GraphQL mock API
make dev # terminal 2: Metro for the dev client
make dev-ios # or: make dev-androidmake setup is idempotent (re-run it when the toolchain misbehaves) and asks
before accepting the Android SDK licences; ARGS=--yes agrees up front, for CI.
The pitfalls it guards against, symptom by symptom, are in
.claude/skills/native-setup.
make check && make test-unit runs every gate CI runs. make help lists the
rest, grouped by prefix: check- gates, test- tests, build- artifacts,
dev- the app locally, gen- generated files, fix- rewrites in place,
verify- inspects a build.
If a command is not in the Makefile, CI does not know how to run it either. That is the rule the whole repo is built on, and
make ciruns the whole of CI locally.
Press "Use this template" on GitHub, then run make init: it renames the
project, optionally drops the web target, and deletes itself. The full walkthrough
— what it asks, what it rewrites and what to do afterwards — is in
docs/template-usage.md.
Continuous Native Generation. Native config is a TypeScript plugin, not a diff
someone applied to an Xcode project two years ago and cannot explain.
make prebuild regenerates both. Neither is committed.
The workflows live in
blinkbitcoin/shared-workflows,
and so do the programs, presets and store lanes they run: the
@blinkbitcoin/app-tooling package, installed from the same commit. Both are
pinned here to one commit that Dependabot moves. What is left in this repo is
twelve short files naming which ones to run, and the settings that are this
app's own. A fix to the Android emulator boot lands once, for every app in the
family.
CI runs make. So a red build is reproducible from the job name, and a new
gate is one Makefile line instead of a YAML negotiation.
Builds go unsigned. Signing switches on with a repo variable, uploading with another. A release can be watched end to end before the store accounts exist.
| Path | Responsibility |
|---|---|
src/app/ |
expo-router routes. A file here is a screen; nothing else is |
src/components/ |
The themed component kit, each with its own test |
src/features/ |
Feature modules — the screens' actual logic, kept out of the route files |
src/graphql/ |
Queries and mutations, plus the typed documents codegen writes from them |
src/config/ |
zod-parsed EXPO_PUBLIC_* env. Nothing reads process.env directly |
src/i18n/ |
Lingui setup and the en and es catalogs |
src/theme/ |
Design tokens, colours and typography |
src/lib/ |
Shared helpers that belong to no single feature |
src/services/ |
The Apollo client and its retry, auth and error links |
modules/ |
A local Expo native module (hello-native) — the worked example of native code |
plugins/ |
Config plugins. with-build-stamp.ts shows the pattern: native config as TypeScript |
fastlane/ |
A Fastfile that imports the shared lanes, store metadata, Matchfile for signing |
scripts/ |
init, the E2E wrappers and this repo's own guard tests; the gates run from the package |
mocks/ |
The GraphQL mock API — one schema, served to Jest via MSW and to E2E as a server |
.maestro/, e2e/ |
Maestro flows for device E2E, Playwright specs for web |
assets/ |
Icons, splash screens and fonts |
certs/ |
The public OTA code-signing certificate. Never a private key |
docs/ |
Thirteen pages, indexed by question in docs/README.md |
.claude/ |
The native-setup skill, and the settings that enable the shared store-release plugin |
Not in here, deliberately: ios/ and android/. They are generated.
Twelve workflow files. Each is a thin caller —
shared-workflows
holds what they actually do. Job names are what the Actions graph shows.
CI — on a pull request and on main
| Workflow | Jobs | Fires on |
|---|---|---|
ci.yml |
ChecksUnitE2ESecurityBadges |
Push, PR, dispatch. Each job gates the next, so a failed unit run never reaches E2E; E2E skips a change it cannot affect; Security runs the scanners beside them |
ci-codeql.yml |
Analyze |
Push, PR, weekly. Informational, never a required check |
ci-web.yml |
Web |
PR and release — the web export and Playwright suite, skipped on a PR it cannot affect |
ci-pr-title.yml |
Title |
Conventional Commits on the PR title |
ci-pr-closed.yml |
Cleanup |
Cancels the closed PR's runs, drops its badges |
CD — on a merge, a release, or a deliberate dispatch
| Workflow | Jobs | Fires on |
|---|---|---|
cd-release.yml |
ReleaseStore notes |
Push to main. Maintains the version PR and drafts the store notes into it; dispatches beta and web at a cut release |
cd-internal.yml |
Internal / PrepareInternal / Build iOSInternal / Build AndroidInternal / Upload iOSInternal / Upload AndroidInternal / Upload HuaweiInternal / Pre-releaseInternal / OTA |
Push to main, once CI is green for that sha. TestFlight, the Play internal trackand, when it is on, Huawei AppGallery |
cd-beta.yml |
Beta / PrepareBeta / Promote iOSBeta / Promote AndroidBeta / Promote HuaweiBeta / ReleaseBeta / Attach store notesBeta / OTA |
Dispatched by cd-release.yml at the tag. Promotes the internal build rather than rebuilding |
cd-production.yml |
Production / PrepareProduction / SecurityProduction / Release iOSProduction / Release AndroidProduction / Release HuaweiProduction / Phased iOSProduction / Rollout AndroidProduction / Halt AndroidProduction / ReleaseProduction / Attach stage noteProduction / OTAProduction / Web |
Dispatch only, carrying the action: release, rollout, halt, resume or complete.Security checks the release's binaries first; phased release and staged rollout, with a halt |
cd-store-listing.yml |
Store listing |
Dispatch. Pushes the store text and screenshots in fastlane/ to the consoles, or pulls what they hold |
cd-beta-retry.yml |
Retry Beta |
A failed beta run. Retries it without a human |
cd-ota-hotfix.yml |
Publish |
Dispatch. Ships JS without a store round trip, gated on the native fingerprint |
Merge a pull request and release-please opens the version PR. Merge that, and the tag, the signed builds, the artifact verification, the GitHub release and the TestFlight and Play submissions all happen without anyone typing a command. Beta promotion and the staged production rollout are one dispatch each, and a bad rollout is halted the same way.
Release runbook holds what this app sets for the
path, and links the shared runbook for the six steps and how to rehearse them.
Store accounts covers getting the
accounts and credentials in the first place — Apple, Google, Huawei, Samsung.
In Claude Code, the store-release plugin from shared-workflows, which
.claude/settings.json enables, walks through the same setup step by step.
docs/README.md says which page answers which question. Two to
start with: local-dev to get the app running,
architecture for how it is laid out. AGENTS.md is
the rules-of-the-road file, for humans and coding agents alike.
MIT — see LICENSE.