@expofacto/cli
v0.1.142
Published
Self-host Expo EAS iOS builds on Mac hardware you control.
Maintainers
Readme
Expo Facto
Self-host Expo EAS iOS builds on Mac hardware you control.
Expo is a great way to build native apps. Paying for every cloud build while you are iterating is the painful part. Expo Facto gives Expo apps a small controller, worker, and CLI for running the expensive iOS build step on your own Mac, then optionally submitting the IPA to TestFlight.
30-Second Example
Install Expo Facto in an Expo app repo:
npm install @expofacto/cli
npm run setup
npm run deployOr submit a build job directly:
npx --package @expofacto/cli expofacto build ios \
--controller-url http://localhost:4100 \
--token "$FACTO_API_TOKEN" \
--project my-app \
--repo [email protected]:OWNER/REPO.git \
--ref main \
--path packages/app \
--profile production \
--submit testflightResult: an iOS IPA built with eas build --local on your Mac worker instead of Expo's remote build infrastructure.
Why Engineers Use It
- Avoid paid remote build minutes. Keep Expo, but move iOS build compute onto your hardware.
- Works with real Expo apps. The worker runs install, checks, prebuild, local EAS build, and optional EAS Submit.
- Good for frequent iteration. Build as often as your Mac can handle while testing app binaries on devices.
- Drop-in app setup.
npm run setupcreates.expofacto/config.yml,.expofacto/secrets.env, and deploy scripts. - Plain infrastructure. A Node controller, a polling worker, SQLite job state, and normal Git refs.
What It Solves
Expo Facto is built for searches like:
- Self-host Expo EAS builds on your own Mac
- Replace Expo cloud iOS builds with local Mac workers
- Run TestFlight submissions from a self-hosted Expo build pipeline
- Configure Expo Facto for a monorepo Expo app
App Setup
Install:
npm install @expofacto/cliThe postinstall step adds safe ignore rules for local secrets and artifacts, creates .expofacto/deploy.sh, and adds package scripts when they do not already exist.
Run setup:
npm run setupSetup creates:
.expofacto/config.yml.expofacto/secrets.env.expofacto/deploy.sh
Fill any missing FACTO_CONTROLLER_URL, FACTO_API_TOKEN, and EXPO_TOKEN values before deploying.
Deploy:
npm run deployLocal Controller And Worker
Install dependencies:
npm installCreate local env files:
npm run setup:localRun the controller:
FACTO_ENV_FILE=.facto/controller.env npm run dev:controllerRun a worker in another terminal:
FACTO_ENV_FILE=.facto/worker.env npm run dev:workerRun the macOS runner preflight by itself:
npm run preflight:runner -- --verboseRun a hosted macOS runner from a clean machine with the API key inline:
curl -fsSL https://raw.githubusercontent.com/smccamley/facto/main/scripts/install-runner.sh | bash -s -- --api-key YOUR_FACTO_API_KEYOr use an existing environment variable:
export FACTO_API_KEY=YOUR_FACTO_API_KEY
curl -fsSL https://raw.githubusercontent.com/smccamley/facto/main/scripts/install-runner.sh | bashThe installer creates ~/facto-runner, checks for Node.js 24+ and npx, installs nvm and Node.js when they are missing, then starts the hosted runner. expofacto start runner runs the macOS preflight before polling for jobs. The preflight reads docs/runner-toolchain.md, installs or upgrades Homebrew/npm tools, macOS updates, and Xcode, verifies GitHub access, the iOS SDK, and App Store Connect credentials, then stops early with a clear error only when the runner cannot repair itself. Set XCODES_USERNAME and XCODES_PASSWORD for unattended Xcode installs. Add --verbose to the installer command to mirror redacted build output to the runner terminal as well as the controller logs.
Open http://localhost:4100 for the operational status page.
Compatibility
| Area | Support |
| --- | --- |
| Runtime | Node.js 24+ |
| App platform | iOS |
| App framework | Expo / React Native apps using EAS |
| Build mode | eas build --local |
| Submit mode | Optional eas submit to TestFlight |
| Worker OS | macOS with Xcode and Apple signing access |
| Package managers | npm by default; generated config can be edited |
Trust And Package Quality
- TypeScript source with emitted declaration files.
- MIT license.
- CI runs install, typecheck, tests, build, and package preview.
- npm publishes from GitHub Actions with provenance.
- Minimal runtime dependencies:
expressandyaml. - Secrets are loaded from env files and redacted from worker logs.
See docs/secrets.md for credential setup and storage.
Not Included Yet
- Scaleway provisioning.
launchdinstallation.- Controller-side encrypted secret storage.
- App Store Connect API key management.
- Warm-build change classification.
