local-expo-build 0.2.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +120 -0
- package/README.md +180 -23
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/build.js +180 -4
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/doctor.js +28 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/update.js +120 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/core/ios/credentials.js +50 -0
- package/dist/core/ios/credentials.js.map +1 -0
- package/dist/core/ios/detect.js +36 -0
- package/dist/core/ios/detect.js.map +1 -0
- package/dist/core/ios/exportOptions.js +68 -0
- package/dist/core/ios/exportOptions.js.map +1 -0
- package/dist/core/ios/xcodebuild.js +54 -0
- package/dist/core/ios/xcodebuild.js.map +1 -0
- package/dist/core/pinGradle.js +58 -3
- package/dist/core/pinGradle.js.map +1 -1
- package/dist/core/setupSigning.js +161 -31
- package/dist/core/setupSigning.js.map +1 -1
- package/dist/util/platform.js +34 -0
- package/dist/util/platform.js.map +1 -0
- package/package.json +9 -4
- package/templates/scripts/build.js +86 -46
- package/templates/scripts/bump-version.js +2 -0
- package/templates/scripts/pin-gradle.js +52 -22
- package/templates/scripts/print-artifact.js +2 -0
- package/templates/scripts/setup-signing.js +127 -32
- package/templates/scripts/sync-eas-version.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,126 @@ All notable changes to `local-expo-build` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.1] — 2026-06-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Interactive `--clean` prompt.** When you run `build android` / `build
|
|
13
|
+
ios` (runner mode) or `npm run build:android:*` (scaffold mode) in an
|
|
14
|
+
interactive terminal, the build asks once at the top whether to clean.
|
|
15
|
+
Hint text explains when to say yes (Expo SDK upgrade, plugin change,
|
|
16
|
+
"android project is malformed" / "MainActivity not found" errors).
|
|
17
|
+
Default is **No** — most builds don't need clean, and forgetting to
|
|
18
|
+
clean fails loud (errors) while forgetting NOT to clean wastes 1-2 min
|
|
19
|
+
silently every time.
|
|
20
|
+
- **`--no-clean` flag** to skip the prompt and force no-clean. Useful when
|
|
21
|
+
scripting / aliasing the build invocation.
|
|
22
|
+
- **Scaffold mode now honors `--clean` / `--no-clean`.** Previously these
|
|
23
|
+
flags only worked in runner mode; the templated `scripts/build.js`
|
|
24
|
+
silently ignored them. Now they're parsed from argv and propagated to
|
|
25
|
+
`expo prebuild`. Pass via `npm run build:android:aab -- --clean`.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- All templated scripts bumped to `v0.4.1` version stamp. Users on v0.4.0
|
|
30
|
+
will see them as outdated when running `npx local-expo-build update-scripts`.
|
|
31
|
+
|
|
32
|
+
### Behavior in non-interactive environments
|
|
33
|
+
|
|
34
|
+
- CI / non-TTY: defaults to no-clean (unchanged from v0.4.0).
|
|
35
|
+
- `--dry-run`: defaults to no-clean (so the dry-run output is deterministic).
|
|
36
|
+
- Explicit `--clean` or `--no-clean` always wins, no prompt.
|
|
37
|
+
|
|
38
|
+
## [0.4.0] — 2026-06-28
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **iOS build support (EXPERIMENTAL, macOS only).** New `local-expo-build
|
|
43
|
+
build ios` subcommand orchestrates `expo prebuild --platform ios` →
|
|
44
|
+
`xcodebuild archive` → `xcodebuild -exportArchive` → signed `.ipa`. Five
|
|
45
|
+
steps with the same numbered progress + dry-run support as the Android
|
|
46
|
+
pipeline. Flags: `--method app-store|ad-hoc|development|enterprise`,
|
|
47
|
+
`--scheme`, `--configuration`, `--team-id`, `--bundle-id`,
|
|
48
|
+
`--profile-name`, `--clean`, `--no-bump`, `--no-prebuild`.
|
|
49
|
+
- **`src/core/ios/` module suite.** `xcodebuild.ts` (archive + export
|
|
50
|
+
wrappers), `exportOptions.ts` (generates `export-options.plist` per
|
|
51
|
+
build), `credentials.ts` (reads the `ios` section of `credentials.json`),
|
|
52
|
+
`detect.ts` (auto-detects `ios/<Workspace>.xcworkspace`).
|
|
53
|
+
- **`assertMacOS` guard.** Every iOS code path hard-throws on non-Darwin
|
|
54
|
+
hosts with an actionable message (no cryptic `command not found` for
|
|
55
|
+
`xcodebuild`).
|
|
56
|
+
- **Loud experimental banner.** iOS commands print a yellow warning line
|
|
57
|
+
before doing anything so users know to expect rough edges and to file
|
|
58
|
+
issues. No opt-in flag required (would be annoying after the first run).
|
|
59
|
+
- **Doctor adds iOS prerequisite checks** on macOS (`xcodebuild`,
|
|
60
|
+
`xcrun`). Non-macOS hosts get a single dim "skipped — iOS builds require
|
|
61
|
+
macOS" row instead of failures.
|
|
62
|
+
- **14 new `node:test` cases** for the iOS-testable bits: plist
|
|
63
|
+
generation, credentials.json parsing, workspace detection,
|
|
64
|
+
`assertMacOS` guard. Total: 37 tests across 7 suites.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **README restructured** with platform-tab sections — Android (stable)
|
|
69
|
+
and iOS (experimental) have separate Quick Start blocks, "Bringing your
|
|
70
|
+
own credentials" walkthroughs, and file tables.
|
|
71
|
+
- **Comparison table + Roadmap updated** to reflect iOS support and to
|
|
72
|
+
retire the closed items (iOS basic, remote `GRADLE_PIN` manifest).
|
|
73
|
+
- **Package keywords + description** now mention iOS / `.ipa` /
|
|
74
|
+
`xcodebuild`.
|
|
75
|
+
|
|
76
|
+
### Known limitations of iOS support (read before reporting)
|
|
77
|
+
|
|
78
|
+
The iOS pipeline was implemented from Apple's documented `xcodebuild`
|
|
79
|
+
interface but **not validated on macOS by the maintainer** (Windows-only
|
|
80
|
+
dev environment). Code is correct in theory; please file issues with
|
|
81
|
+
your `eas-cli` version, `xcodebuild -version` output, and the failing
|
|
82
|
+
command if something doesn't work. Specifically not yet supported:
|
|
83
|
+
|
|
84
|
+
- Automated `.p12` keychain import (you double-click the file).
|
|
85
|
+
- Automated provisioning profile install (you double-click the file).
|
|
86
|
+
- TestFlight / App Store upload (use `xcrun altool` manually after the build).
|
|
87
|
+
- iOS-specific `CFBundleVersion` (buildNumber) bump.
|
|
88
|
+
- Multi-bundle apps (extensions / widgets / watch apps).
|
|
89
|
+
|
|
90
|
+
All of the above are on the roadmap for future releases.
|
|
91
|
+
|
|
92
|
+
## [0.3.0] — 2026-06-28
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
|
|
96
|
+
- **Live `GRADLE_PIN` manifest.** `pinGradle` now fetches the latest pin table
|
|
97
|
+
from `manifest/gradle-pins.json` in the GitHub repo (3 s timeout, silent
|
|
98
|
+
fallback to the bundled table). New Expo SDK shipping a problematic Gradle
|
|
99
|
+
default → edit the manifest, commit, and every existing CLI user gets the
|
|
100
|
+
new pin on their next build. No `npm publish` required.
|
|
101
|
+
- **`local-expo-build update-scripts` subcommand.** Diffs scaffolded
|
|
102
|
+
`scripts/*.js` against the bundled templates by version stamp + content.
|
|
103
|
+
Shows a table of up-to-date / outdated / missing scripts. Prompts before
|
|
104
|
+
overwriting (or pass `--yes`). Honors `--dry-run`. Every scaffolded script
|
|
105
|
+
now carries a `// Generated by local-expo-build vX.Y.Z` header.
|
|
106
|
+
- **Multi-strategy `signingConfigs.release` injection.** `setupSigning` now
|
|
107
|
+
tries three strategies in order: exact-match → tolerant block-inject →
|
|
108
|
+
synthesize new block inside `android { }`. Survives whitespace drift,
|
|
109
|
+
comments, and even Expo dropping the default debug signing block from
|
|
110
|
+
prebuild output.
|
|
111
|
+
- **`local-expo-build` test suite.** 23 tests across 5 suites using
|
|
112
|
+
`node:test` (no new devDep). Covers gradle injection, buildType rewiring,
|
|
113
|
+
EAS link detection, and rehydrate candidate matching — the bits that
|
|
114
|
+
break first when Expo shifts. Run with `npm test`.
|
|
115
|
+
- **CI matrix:** 3 OS × 2 Node versions = 6 jobs per PR
|
|
116
|
+
(ubuntu/macos/windows × Node 20/22). Catches Windows-specific regressions
|
|
117
|
+
before users do.
|
|
118
|
+
|
|
119
|
+
### Changed
|
|
120
|
+
|
|
121
|
+
- **Node 20 minimum** (`engines.node`). Node 18 reached EOL in April 2025;
|
|
122
|
+
bumping the floor lets us use native `fetch` for the manifest and
|
|
123
|
+
`node:test` for the test suite without polyfills.
|
|
124
|
+
- Doctor's `Node` check now requires `>= 20` instead of `>= 18`.
|
|
125
|
+
- `pinGradle` is now async (was sync). The single internal caller in
|
|
126
|
+
`build.ts` was updated.
|
|
127
|
+
|
|
8
128
|
## [0.2.0] — 2026-06-28
|
|
9
129
|
|
|
10
130
|
### Added
|
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# local-expo-build
|
|
2
2
|
|
|
3
|
-
> One-stop CLI for **local** Expo Android APK / AAB builds
|
|
3
|
+
> One-stop CLI for **local** Expo Android APK / AAB builds — **works on Windows, macOS, and Linux** (unlike `eas build --local`, which rejects Windows). Own your signing, skip the EAS cloud queue, no eas.json required.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/local-expo-build)
|
|
6
6
|
[](https://www.npmjs.com/package/local-expo-build)
|
|
7
7
|
[](https://github.com/nikhild64/local-expo-build/blob/main/LICENSE)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
|
|
10
|
-
`local-expo-build` automates the painful parts of running `expo prebuild` + `gradlew bundleRelease` yourself:
|
|
10
|
+
`local-expo-build` automates the painful parts of running `expo prebuild` + `gradlew bundleRelease` (or `xcodebuild archive`) yourself:
|
|
11
|
+
|
|
12
|
+
**Android (stable):**
|
|
11
13
|
|
|
12
14
|
- Detects your Expo SDK and pins the Gradle wrapper to a version that actually works (e.g. SDK 55 → Gradle 8.13, working around the `expo-manifests` `components.release` bug).
|
|
13
15
|
- Bumps your app version and pulls the next `versionCode` from EAS so Play Store ingest doesn't reject the upload.
|
|
@@ -17,6 +19,13 @@
|
|
|
17
19
|
- Runs `gradlew assembleRelease` / `bundleRelease` and prints the absolute path + size of the produced artifact.
|
|
18
20
|
- Pushes the new `versionCode` back to EAS via GraphQL so `eas build` / `eas submit` stay in sync.
|
|
19
21
|
|
|
22
|
+
**iOS (experimental, macOS only — community-tested):**
|
|
23
|
+
|
|
24
|
+
- Orchestrates `xcodebuild archive` + `-exportArchive` for `.ipa` output.
|
|
25
|
+
- Auto-detects the workspace + scheme produced by `expo prebuild --platform ios`.
|
|
26
|
+
- Generates `export-options.plist` per build with sensible defaults for `app-store` / `ad-hoc` / `development` / `enterprise` distribution methods.
|
|
27
|
+
- Reads `.p12` + provisioning profile paths from `credentials.json` (same file format EAS downloads).
|
|
28
|
+
|
|
20
29
|
**`doctor` is a setup wizard, not just a health check.** It detects missing pieces (`expo.android.package`, EAS link, `eas.json`, keystore) and offers to fix each one interactively — `eas init`, `eas build:configure`, keystore picker (with one-prompt `rehydrate` from `credentials.json` when possible), all chained.
|
|
21
30
|
|
|
22
31
|

|
|
@@ -68,14 +77,15 @@ local-expo-build init [--force] [--no-keystore] [--no-doctor]
|
|
|
68
77
|
Scaffold scripts + package.json entries
|
|
69
78
|
(runs `doctor` first by default)
|
|
70
79
|
|
|
71
|
-
local-expo-build build android [--apk|--aab] [--profile <name>]
|
|
72
|
-
[--clean] [--no-bump] [--no-sync] [--no-prebuild]
|
|
73
|
-
Run the full pipeline → .aab|.apk
|
|
74
|
-
|
|
75
80
|
local-expo-build doctor Env check + interactive auto-fix wizard
|
|
76
81
|
(eas init → eas build:configure →
|
|
77
82
|
keystore setup)
|
|
78
83
|
|
|
84
|
+
# ── Android (stable) ──
|
|
85
|
+
local-expo-build build android [--apk|--aab] [--profile <name>]
|
|
86
|
+
[--clean] [--no-bump] [--no-sync] [--no-prebuild]
|
|
87
|
+
Run the full Android pipeline → .aab|.apk
|
|
88
|
+
|
|
79
89
|
local-expo-build keystore setup Interactive picker:
|
|
80
90
|
rehydrate | existing | generate | EAS
|
|
81
91
|
local-expo-build keystore import Register an existing .jks
|
|
@@ -84,7 +94,19 @@ local-expo-build keystore fetch Open `eas credentials` to download a
|
|
|
84
94
|
local-expo-build keystore rehydrate [--move]
|
|
85
95
|
Bind credentials.json + .jks into
|
|
86
96
|
keystore.properties (no password re-entry).
|
|
87
|
-
|
|
97
|
+
|
|
98
|
+
# ── iOS (experimental, macOS only) ──
|
|
99
|
+
local-expo-build build ios [--method <m>] [--scheme <s>] [--configuration <c>]
|
|
100
|
+
[--team-id <id>] [--profile-name <n>] [--bundle-id <id>]
|
|
101
|
+
[--clean] [--no-bump] [--no-prebuild]
|
|
102
|
+
Run the full iOS pipeline → .ipa
|
|
103
|
+
--method = app-store | ad-hoc |
|
|
104
|
+
development | enterprise
|
|
105
|
+
|
|
106
|
+
# ── Shared ──
|
|
107
|
+
local-expo-build update-scripts [-y|--yes]
|
|
108
|
+
Refresh scaffolded scripts/*.js to the
|
|
109
|
+
version bundled with this CLI.
|
|
88
110
|
```
|
|
89
111
|
|
|
90
112
|
Global flags: `--cwd <path>`, `--verbose`, `--dry-run`.
|
|
@@ -95,22 +117,52 @@ Global flags: `--cwd <path>`, `--verbose`, `--dry-run`.
|
|
|
95
117
|
> npx local-expo-build --dry-run build android --aab # runner mode
|
|
96
118
|
> npm run build:android:aab -- --dry-run # scaffold mode (or: node scripts/build.js aab --dry-run)
|
|
97
119
|
> ```
|
|
120
|
+
>
|
|
121
|
+
> **`--clean`** prompts interactively when neither `--clean` nor `--no-clean` is passed. Default is **No** (faster). Say yes after an Expo SDK upgrade, plugin change, or `MainActivity not found` / "android project is malformed" errors. Pass `--clean` or `--no-clean` explicitly to skip the prompt.
|
|
98
122
|
|
|
99
123
|

|
|
100
124
|
|
|
101
125
|
## How it compares
|
|
102
126
|
|
|
103
|
-
| | `eas build` (cloud) | `npx expo run:android` | `local-expo-build` |
|
|
104
|
-
| --- | --- | --- | --- |
|
|
105
|
-
| Runs locally | No | Yes | **Yes** |
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
127
|
+
| | `eas build` (cloud) | `eas build --local` | `npx expo run:android/ios` | `local-expo-build` |
|
|
128
|
+
| --- | --- | --- | --- | --- |
|
|
129
|
+
| Runs locally | No | Yes | Yes | **Yes** |
|
|
130
|
+
| **Works on Windows** | Yes (cloud) | **No** ¹ | Yes (debug only) | **Yes** |
|
|
131
|
+
| Works on macOS / Linux | Yes (cloud) | Yes | Yes | **Yes** |
|
|
132
|
+
| Produces a signed release `.aab` / `.apk` | Yes | Yes | No (debug) | **Yes (Android)** |
|
|
133
|
+
| Produces a signed release `.ipa` | Yes | Yes | No (debug) | **Yes (iOS, experimental)** |
|
|
134
|
+
| Counts against EAS free build quota | **Yes** | No | No | No |
|
|
135
|
+
| Manages release signing config for you | Yes | Yes | No | **Yes** |
|
|
136
|
+
| Bumps `versionCode` from EAS automatically | Yes | Yes | No | **Yes (Android)** |
|
|
137
|
+
| Wait in cloud queue | Sometimes | Never | Never | Never |
|
|
138
|
+
| Needs `eas-cli` installed | Yes | **Yes** | No | Optional ² |
|
|
139
|
+
| Needs `eas.json` set up | Yes | **Yes** | No | No |
|
|
140
|
+
| Needs EAS account / login | Yes | **Yes** | No | Optional ² |
|
|
141
|
+
| Pipeline editable per-project | No | No | No | **Yes** (scaffold mode) |
|
|
142
|
+
|
|
143
|
+
¹ `eas build --platform android --local` errors with `"Unsupported platform, macOS or Linux is required to build apps for Android"` on Windows — EAS's local build infrastructure uses Unix-only tooling.
|
|
144
|
+
|
|
145
|
+
² Only needed for: EAS keystore fetch, versionCode sync back to EAS, or doctor's `eas init` / `eas build:configure` auto-fixes. The build itself runs without any EAS dependency.
|
|
146
|
+
|
|
147
|
+
**TL;DR:**
|
|
112
148
|
|
|
113
|
-
|
|
149
|
+
- **On Windows?** `local-expo-build` is the only option in the Expo ecosystem to build Android locally.
|
|
150
|
+
- **On Mac/Linux + happy with EAS?** Use `eas build --local` — it's the official path with managed credentials + `eas submit` integration.
|
|
151
|
+
- **On Mac/Linux + want lightweight / no EAS lock-in?** `local-expo-build` is your fit — no eas.json, no login, fully editable pipeline.
|
|
152
|
+
|
|
153
|
+
## Why not just use `eas build --local`?
|
|
154
|
+
|
|
155
|
+
If you're on Mac or Linux **and** already invested in EAS (account, eas.json, profiles, `eas submit` workflow), `eas build --platform android --local` is the official Expo path and you should use it. It integrates seamlessly with `eas submit`, managed credentials, and the rest of the EAS toolbox.
|
|
156
|
+
|
|
157
|
+
Use `local-expo-build` instead when **any** of these is true:
|
|
158
|
+
|
|
159
|
+
1. **You're on Windows.** `eas build --local` literally won't run — it errors out with `"Unsupported platform, macOS or Linux is required to build apps for Android"`. This is the killer use case: there's no other path to local Expo Android builds on Windows.
|
|
160
|
+
2. **You don't want an EAS account.** No login, no `eas.json`, no project-link ceremony. Just point at any Expo project and get a signed `.aab`.
|
|
161
|
+
3. **You want to vendor + edit the build pipeline in your repo.** Scaffold mode drops 6 readable JS files into `scripts/` that you can customize per-project (custom version bumping, project-specific signing tweaks, pre/post hooks). `eas build` is a black box by design.
|
|
162
|
+
4. **Your project pre-dates EAS** (SDK 50 era) and you never set up the EAS link. `local-expo-build` works on any Expo SDK ≥ 50 with no migration needed.
|
|
163
|
+
5. **You want to script around predictable artifact paths.** Output always lands at `android/app/build/outputs/{apk,bundle}/release/...`. EAS local copies through temp dirs.
|
|
164
|
+
|
|
165
|
+
Both tools can coexist. The CLI also writes a `credentials.json` at project root, which `eas submit` (and `eas build` cloud, if you ever want to use it) reads directly — your local-built `.aab` can still be uploaded via `eas submit --path /path/to/app.aab` without re-signing.
|
|
114
166
|
|
|
115
167
|
## Keystore sources
|
|
116
168
|
|
|
@@ -180,6 +232,97 @@ npx local-expo-build build android --aab # runner mode
|
|
|
180
232
|
|
|
181
233
|
> Tip: you can skip step 2 entirely — `doctor` detects the rehydrate state automatically and offers the same one-prompt fix inline.
|
|
182
234
|
|
|
235
|
+
## iOS (experimental, macOS only)
|
|
236
|
+
|
|
237
|
+
> **Status: community-tested.** The iOS pipeline ships behind an experimental banner because the maintainer develops on Windows and can't validate every build configuration. The code is built on Apple's documented `xcodebuild` interface and follows the same patterns as the Android side, but please [file issues](https://github.com/nikhild64/local-expo-build/issues) when you hit something — and PRs are very welcome.
|
|
238
|
+
|
|
239
|
+
### Prerequisites
|
|
240
|
+
|
|
241
|
+
iOS local builds are constrained by Apple and require all of:
|
|
242
|
+
|
|
243
|
+
- **macOS** (Xcode is macOS-only — Apple does not ship `xcodebuild` for Linux or Windows)
|
|
244
|
+
- **Xcode** 14+ with Command Line Tools (`xcode-select --install`)
|
|
245
|
+
- **Apple Developer account** ($99/yr — required for distribution signing certificates)
|
|
246
|
+
- **A distribution `.p12`** + **provisioning profile** installed in your keychain (drag the `.p12` into Keychain Access; double-click the `.mobileprovision` to install)
|
|
247
|
+
|
|
248
|
+
`local-expo-build doctor` checks for Xcode / `xcodebuild` automatically on macOS hosts and skips the checks elsewhere.
|
|
249
|
+
|
|
250
|
+
### Quick start (iOS)
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# 1. Download credentials from EAS (same flow as Android)
|
|
254
|
+
npx local-expo-build keystore fetch
|
|
255
|
+
# In the EAS menu pick: iOS → Download credentials to credentials.json
|
|
256
|
+
# This produces:
|
|
257
|
+
# - credentials.json with an `ios` section
|
|
258
|
+
# - ios/certs/dist.p12 (distribution certificate)
|
|
259
|
+
# - ios/certs/profile.mobileprovision (provisioning profile)
|
|
260
|
+
|
|
261
|
+
# 2. (one-time, manual) Import the .p12 into your login keychain
|
|
262
|
+
# and double-click the .mobileprovision file. Both go into ~/Library/...
|
|
263
|
+
# Xcode does this automatically on double-click.
|
|
264
|
+
|
|
265
|
+
# 3. Build the .ipa
|
|
266
|
+
npx local-expo-build build ios --method app-store \
|
|
267
|
+
--team-id ABCDE12345 \
|
|
268
|
+
--bundle-id com.yourcompany.yourapp \
|
|
269
|
+
--profile-name "Your Profile Name"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The build runs through these 5 steps:
|
|
273
|
+
|
|
274
|
+
```text
|
|
275
|
+
1/5 expo prebuild (ios)
|
|
276
|
+
2/5 bump version
|
|
277
|
+
3/5 detect Xcode workspace + credentials
|
|
278
|
+
4/5 xcodebuild archive
|
|
279
|
+
5/5 xcodebuild -exportArchive (method=app-store)
|
|
280
|
+
|
|
281
|
+
Build complete (.ipa, 14.3 MB):
|
|
282
|
+
/path/to/your/app/ios/build/export/YourApp.ipa
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Use `--dry-run` to preview the 5 steps without invoking xcodebuild.
|
|
286
|
+
|
|
287
|
+
### What the CLI does NOT handle (yet)
|
|
288
|
+
|
|
289
|
+
By design, the experimental iOS support keeps a tight scope so we don't ship untested Apple-keychain code that could leave your machine in a weird state. You're still responsible for:
|
|
290
|
+
|
|
291
|
+
- **Keychain `.p12` import.** Double-click the file in Finder, or `security import dist.p12 -k ~/Library/Keychains/login.keychain-db -P <password>`.
|
|
292
|
+
- **Provisioning profile install.** Double-click the `.mobileprovision`, or copy to `~/Library/MobileDevice/Provisioning Profiles/<UUID>.mobileprovision`.
|
|
293
|
+
- **TestFlight / App Store upload.** Use `xcrun altool --upload-app` or Apple's Transporter app after the `.ipa` is built.
|
|
294
|
+
|
|
295
|
+
These are on the roadmap (full `certs setup` flow + EAS cert fetch + TestFlight upload) for a future release once there's been enough community testing of the core build flow.
|
|
296
|
+
|
|
297
|
+
### iOS-specific files
|
|
298
|
+
|
|
299
|
+
| Path | Purpose | Gitignored? |
|
|
300
|
+
| --- | --- | --- |
|
|
301
|
+
| `credentials.json` → `ios` block | EAS local-credentials shape for iOS (`distributionCertificate.path/password`, `provisioningProfilePath`) | Yes (auto via the Android setup) |
|
|
302
|
+
| `ios/build/export-options.plist` | Regenerated per build with the chosen distribution method | No — derived per-build, safe to commit or gitignore as you prefer |
|
|
303
|
+
| `ios/build/<Scheme>.xcarchive` | Intermediate archive produced by `xcodebuild archive` | Yes (recommend adding `ios/build/` to `.gitignore`) |
|
|
304
|
+
| `ios/build/export/<Scheme>.ipa` | The final `.ipa` | Yes (recommend adding `ios/build/` to `.gitignore`) |
|
|
305
|
+
|
|
306
|
+
### Known caveats
|
|
307
|
+
|
|
308
|
+
- **Auto-detected scheme is the workspace basename.** Works for Expo's default prebuild output; pass `--scheme MyOtherScheme` if your project has multiple schemes.
|
|
309
|
+
- **Manual signing requires `--team-id`.** Without it, `xcodebuild` falls back to automatic signing, which only works for `development` builds if your Apple ID is logged into Xcode.
|
|
310
|
+
- **Multi-bundle apps not handled.** If your project has app extensions (widgets, watch app, share extension), each needs its own provisioning profile and the current `--profile-name` flag only sets one. File an issue with your `app.json` `extra.eas.build.experimental.ios.appExtensions` setup if you hit this.
|
|
311
|
+
- **No `bumpVersion` for iOS-specific `buildNumber`.** Today the bump step still updates `versionCode` in `android/app/build.gradle` (no-op on iOS-only builds). Expo's prebuild reads `expo.version` from `app.json` and writes it into `Info.plist`'s `CFBundleShortVersionString` — for `CFBundleVersion` (the iOS equivalent of `versionCode`) you currently need to manage that in `app.json` → `ios.buildNumber` manually. PRs to wire this up are welcome.
|
|
312
|
+
|
|
313
|
+
### How it works (iOS pipeline)
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
expo prebuild --platform ios
|
|
317
|
+
→ (you import .p12 + provisioning profile manually, one-time)
|
|
318
|
+
→ bump app.json version (src/core/bumpVersion.ts)
|
|
319
|
+
→ detect ios/<Workspace>.xcworkspace (src/core/ios/detect.ts)
|
|
320
|
+
→ read credentials.json `ios` section (src/core/ios/credentials.ts)
|
|
321
|
+
→ write ios/build/export-options.plist (src/core/ios/exportOptions.ts)
|
|
322
|
+
→ xcodebuild archive (src/core/ios/xcodebuild.ts)
|
|
323
|
+
→ xcodebuild -exportArchive → .ipa
|
|
324
|
+
```
|
|
325
|
+
|
|
183
326
|
## Files this CLI creates / touches
|
|
184
327
|
|
|
185
328
|
| Path | Purpose | Gitignored? |
|
|
@@ -215,13 +358,24 @@ If your SDK isn't pinned, `pinGradle` is a no-op. Add a row + open a PR if a fut
|
|
|
215
358
|
|
|
216
359
|
## Requirements
|
|
217
360
|
|
|
218
|
-
|
|
361
|
+
**All platforms:**
|
|
362
|
+
|
|
363
|
+
- Node ≥ 20 (Node 18 reached EOL in April 2025)
|
|
364
|
+
- `eas-cli` is **optional** — only needed for EAS version sync, EAS credentials fetch, or doctor's `eas init` / `eas build:configure` auto-fixes
|
|
365
|
+
|
|
366
|
+
**Android builds (cross-platform):**
|
|
367
|
+
|
|
219
368
|
- JDK 17 (recommended for Expo SDK 55)
|
|
220
369
|
- Android SDK + `ANDROID_HOME` env var
|
|
221
370
|
- `keytool` on `PATH` (ships with the JDK)
|
|
222
|
-
- `eas-cli` is **optional** — only needed for EAS version sync, EAS keystore fetch, or doctor's `eas init` / `eas build:configure` auto-fixes
|
|
223
371
|
|
|
224
|
-
|
|
372
|
+
**iOS builds (macOS only):**
|
|
373
|
+
|
|
374
|
+
- macOS (Apple does not ship `xcodebuild` for Linux or Windows)
|
|
375
|
+
- Xcode 14+ with Command Line Tools (`xcode-select --install`)
|
|
376
|
+
- Apple Developer account ($99/yr — for distribution signing certificates)
|
|
377
|
+
|
|
378
|
+
Run `local-expo-build doctor` to verify all of the above — iOS-specific checks are auto-skipped on non-macOS hosts.
|
|
225
379
|
|
|
226
380
|
## How it works (pipeline)
|
|
227
381
|
|
|
@@ -350,9 +504,12 @@ node /abs/path/local-expo-build/bin/local-expo-build.js doctor --cwd /abs/path/m
|
|
|
350
504
|
|
|
351
505
|
## Roadmap
|
|
352
506
|
|
|
353
|
-
- [
|
|
354
|
-
- [
|
|
355
|
-
- [ ]
|
|
507
|
+
- [x] iOS local builds (experimental in v0.4.0 — awaiting community testing)
|
|
508
|
+
- [x] Auto-update `GRADLE_PIN` table from a hosted manifest (v0.3.0)
|
|
509
|
+
- [ ] `certs setup` interactive flow for iOS (.p12 import + provisioning profile install via `security` / Keychain Access automation)
|
|
510
|
+
- [ ] iOS `buildNumber` (CFBundleVersion) bump + EAS sync — parity with Android's `versionCode` flow
|
|
511
|
+
- [ ] TestFlight / App Store Connect upload (`xcrun altool` / `notarytool` wrapper)
|
|
512
|
+
- [ ] Symbol upload (`mapping.txt` → Play Console / Sentry; iOS `.dSYM` to Sentry)
|
|
356
513
|
- [ ] CI presets (`init --ci` that scaffolds a GitHub Actions / GitLab CI workflow with base64-encoded secrets)
|
|
357
514
|
|
|
358
515
|
## Contributing
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ const build_1 = require("./commands/build");
|
|
|
9
9
|
const init_1 = require("./commands/init");
|
|
10
10
|
const keystore_1 = require("./commands/keystore");
|
|
11
11
|
const doctor_1 = require("./commands/doctor");
|
|
12
|
+
const update_1 = require("./commands/update");
|
|
12
13
|
const pkg = require('../package.json');
|
|
13
14
|
const program = new commander_1.Command();
|
|
14
15
|
program
|
|
@@ -23,6 +24,7 @@ program
|
|
|
23
24
|
(0, init_1.registerInitCommand)(program);
|
|
24
25
|
(0, keystore_1.registerKeystoreCommand)(program);
|
|
25
26
|
(0, doctor_1.registerDoctorCommand)(program);
|
|
27
|
+
(0, update_1.registerUpdateCommand)(program);
|
|
26
28
|
program
|
|
27
29
|
.parseAsync(process.argv)
|
|
28
30
|
.catch((err) => {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;AAAA,yCAAoC;AACpC,kDAA0B;AAC1B,4CAAwD;AACxD,0CAAsD;AACtD,kDAA8D;AAC9D,8CAA0D;AAE1D,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEvC,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,kBAAkB,CAAC;KACxB,WAAW,CACV,4DAA4D;IAC1D,gFAAgF,CACnF;KACA,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;KACpB,MAAM,CAAC,cAAc,EAAE,4CAA4C,CAAC;KACpE,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAAC;KACtC,MAAM,CAAC,WAAW,EAAE,mDAAmD,CAAC,CAAC;AAE5E,IAAA,4BAAoB,EAAC,OAAO,CAAC,CAAC;AAC9B,IAAA,0BAAmB,EAAC,OAAO,CAAC,CAAC;AAC7B,IAAA,kCAAuB,EAAC,OAAO,CAAC,CAAC;AACjC,IAAA,8BAAqB,EAAC,OAAO,CAAC,CAAC;AAE/B,OAAO;KACJ,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;KACxB,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACb,OAAO,CAAC,KAAK,CAAC,eAAK,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,IAAI,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;AAAA,yCAAoC;AACpC,kDAA0B;AAC1B,4CAAwD;AACxD,0CAAsD;AACtD,kDAA8D;AAC9D,8CAA0D;AAC1D,8CAA0D;AAE1D,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEvC,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,kBAAkB,CAAC;KACxB,WAAW,CACV,4DAA4D;IAC1D,gFAAgF,CACnF;KACA,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;KACpB,MAAM,CAAC,cAAc,EAAE,4CAA4C,CAAC;KACpE,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAAC;KACtC,MAAM,CAAC,WAAW,EAAE,mDAAmD,CAAC,CAAC;AAE5E,IAAA,4BAAoB,EAAC,OAAO,CAAC,CAAC;AAC9B,IAAA,0BAAmB,EAAC,OAAO,CAAC,CAAC;AAC7B,IAAA,kCAAuB,EAAC,OAAO,CAAC,CAAC;AACjC,IAAA,8BAAqB,EAAC,OAAO,CAAC,CAAC;AAC/B,IAAA,8BAAqB,EAAC,OAAO,CAAC,CAAC;AAE/B,OAAO;KACJ,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;KACxB,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACb,OAAO,CAAC,KAAK,CAAC,eAAK,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,IAAI,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|