@basiljohn/appdeploy
v0.1.1
Published
Local-first EAS iOS/Android build & submit: avoid cloud build quota using your Mac and Apple/Google credentials (Keychain-backed).
Readme
appdeploy
CLI for local-first Expo EAS workflows: run eas build --local on your machine to avoid EAS cloud build quota, then submit to TestFlight or Google Play using credentials you control.
- iOS (macOS only): App Store Connect API key (Issuer, Key ID, Team ID,
.p8) is stored in the macOS Keychain (no Apple ID password in the tool). - Android: A Google Play service account JSON is written to
.appdeploy/google-service-account.jsonand, on macOS, also saved in Keychain. Add.appdeploy/to.gitignoreif you do not commit secrets.
This does not remove: Apple Developer Program fees, App Store / Play review, or the requirement to use a Mac for iOS store binaries. It does avoid using Expo’s shared build servers for compiles, which is where the free plan monthly limits apply.
Prerequisites
- Expo project with
eas.jsonand a valid build profile (for exampleproduction). Install EAS:npm i -g eas-cliandeas login(EAS is still the orchestration layer; local builds are documented). - Apple (iOS): Enrolled in the Apple Developer Program ($99/yr). Create an App Store Connect API key with App Manager (or similar) access.
- Google (Android): Service account with Play API access, linked in Play Console, per Expo’s guide.
Install
From the npm registry (package @basiljohn/appdeploy, version 0.1.1):
npm i -g @basiljohn/appdeploy
appdeploy --helpIn an Expo app as a dev dependency (recommended so the version is pinned):
npm i -D @basiljohn/[email protected]
npx appdeploy doctorThe global/local CLI command is still appdeploy (the bin name), not the scoped package name.
From a git clone of this repo:
npm install && npm run build
npm i -g .
# or: npm link
appdeploy --helpQuick start: iOS
In your Expo app directory:
appdeploy ios setupYou will be asked for Issuer ID, Key ID, Team ID, and the path to AuthKey_xxx.p8 (one-time; stored in Keychain as
appdeploy.asc).Ensure your App Store app exists and you know the App Store Connect app Apple ID (numeric) — this is not the bundle id; see expo.fyi/asc-app-id.
In
eas.json, add a submit profile (often matching your build profile name) with at leastascAppIdandappleTeamId:{ "submit": { "production": { "ios": { "ascAppId": "1234567890", "appleTeamId": "AB12XYZ34S" } } } }Optional: you can use
.appdeploy/appdeploy.config.json(see Configuration) to keepascAppIdout of the maineas.jsonif you prefer.Local build (uses your Mac CPU, not EAS cloud workers):
appdeploy ios build --profile productionSubmit the generated
.ipa:appdeploy ios submit --path ./path/to/app.ipa --profile productionOne-shot build + write artifact to
.appdeploy/local-build.ipa+ submit:appdeploy ios ship
The submit and ship steps set the same ASC API environment variables that eas expects: EXPO_ASC_API_KEY_PATH, EXPO_ASC_KEY_ID, EXPO_ASC_ISSUER_ID, and EXPO_APPLE_TEAM_ID (from your Keychain data).
Quick start: Android
appdeploy android setup— pick your service account JSON. This writes.appdeploy/google-service-account.json.In
eas.json, point the submit profile at that file and choose a track:{ "submit": { "production": { "android": { "serviceAccountKeyPath": ".appdeploy/google-service-account.json", "track": "internal" } } } }appdeploy android build --profile productionappdeploy android submit --path ./path/to/app.aabOr
appdeploy android ship(output:.appdeploy/local-build.aab; if youreas.jsonprofile builds an APK instead, useandroid buildandandroid submit --pathto that file).
EAS login vs “fully local” native build
eas build --local(what this CLI runs) is the supported way to run the same EAS build pipeline on your machine. It still uses your Expo account for project identity and, if you use EAS managed credentials, to sync signing. It does not consume EAS cloud build minutes.- Full escape hatch:
npx expo prebuildand open Xcode / Android Studio yourself, then upload with Transporter (iOS) oreas submitwith a path to the archive.appdeployis aimed at theeaspath so you keep one consistent workflow witheas.json.
Configuration (optional)
Create .appdeploy/appdeploy.config.json in the project root to override or supplement submit metadata (merged with eas.json for known fields):
{
"ascAppId": "1234567890",
"appleTeamId": "AB12XYZ34S",
"serviceAccountKeyPath": ".appdeploy/google-service-account.json"
}You can also use a top-level appdeploy.config.json in the project root; both are supported. Team ID is already stored when you run appdeploy ios setup; the submit path still requires ascAppId in eas non-interactive mode, so you must set it in eas.json or in one of these config files (see submit reference).
Commands
| Command | Description |
|--------|-------------|
| appdeploy doctor | iOS (mac) + Android tool checks: Node, Xcode, CocoaPods, eas, ANDROID_HOME |
| appdeploy ios setup | Keychain wizard for App Store Connect API key |
| appdeploy ios build | eas build --local --platform ios |
| appdeploy ios submit --path <ipa> | eas submit with Keychain-sourced EXPO_ASC_* |
| appdeploy ios ship | local build to .appdeploy/local-build.ipa + submit |
| appdeploy android setup | Write Play service account to project (+ Keychain on macOS) |
| appdeploy android build | eas build --local --platform android |
| appdeploy android submit --path <aab\|apk> | eas submit after ensuring JSON exists on disk |
| appdeploy android ship | local AAB to .appdeploy/local-build.aab + submit |
Flags: --log <file> appends a tee of eas output (ANSI stripped). --skip-doctor skips preflight. --profile / -e select the EAS profile (default production).
Security notes
- Prefer App Store Connect API keys; do not use your main Apple ID password in automation. Rotate keys in App Store Connect if a machine is lost.
- Treat
.appdeploy/and Keychain data as sensitive. Use.gitignorefor generated JSON in CI clones.
Development
npm install
npm run build
node dist/cli.js doctorLicense
MIT
