feat(ios): add native build and TestFlight automation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
137
.github/workflows/ios-ci.yml
vendored
Normal file
137
.github/workflows/ios-ci.yml
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
# iOS build gate for pull requests and main.
|
||||
#
|
||||
# Requires NO Apple/EAS secrets: it validates the Expo config, exports the iOS JS
|
||||
# bundle, generates the native project, installs CocoaPods, and compiles an
|
||||
# UNSIGNED iPhone Simulator target with xcodebuild. Signing/TestFlight lives in
|
||||
# publish-app-store.yml.
|
||||
#
|
||||
# Cheap platform-neutral checks (typecheck + unit tests) run first on Linux and
|
||||
# gate the costly macOS native build.
|
||||
|
||||
name: iOS CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- "**/*.md"
|
||||
- "docs/**"
|
||||
- "docs-site/**"
|
||||
- "distribution/**"
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- "**/*.md"
|
||||
- "docs/**"
|
||||
- "docs-site/**"
|
||||
- "distribution/**"
|
||||
|
||||
# Cancel superseded runs for the same ref (e.g. new push to an open PR).
|
||||
concurrency:
|
||||
group: ios-ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Typecheck and unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
# Node >= 23.6 runs the TypeScript test files natively (type-stripping),
|
||||
# matching the local toolchain. No build step or extra deps required.
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies (deterministic)
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Typecheck
|
||||
run: npm run typecheck
|
||||
|
||||
- name: Unit tests
|
||||
run: npm test
|
||||
|
||||
ios-build:
|
||||
name: Unsigned iOS Simulator build
|
||||
needs: test
|
||||
# macos-15 ships Xcode 16.x, which React Native 0.81 / Expo SDK 54 require.
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
# No source-map upload from CI (no Sentry auth token here); keep the build hermetic.
|
||||
SENTRY_DISABLE_AUTO_UPLOAD: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies (deterministic)
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Validate Expo config and plugin resolution
|
||||
run: npx expo config --type introspect --json > expo-config.introspect.json
|
||||
|
||||
- name: Export iOS JavaScript bundle
|
||||
run: npx expo export --platform ios --output-dir dist
|
||||
|
||||
- name: Prebuild native iOS project
|
||||
run: npx expo prebuild --platform ios --no-install
|
||||
|
||||
- name: Install CocoaPods dependencies
|
||||
working-directory: ios
|
||||
run: pod install
|
||||
|
||||
- name: Resolve Xcode workspace and scheme
|
||||
id: xc
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
workspaces=(ios/*.xcworkspace)
|
||||
workspace="${workspaces[0]:-}"
|
||||
if [ -z "$workspace" ]; then
|
||||
echo "::error::No .xcworkspace was generated by expo prebuild."
|
||||
exit 1
|
||||
fi
|
||||
scheme=$(xcodebuild -workspace "$workspace" -list -json | node -e "
|
||||
const d = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
||||
const all = (d.workspace && d.workspace.schemes) || [];
|
||||
const app = all.filter((s) => s !== 'Pods' && !s.startsWith('Pods-'));
|
||||
if (app.length === 0) { console.error('No application scheme found in workspace'); process.exit(1); }
|
||||
process.stdout.write(app[0]);
|
||||
")
|
||||
echo "workspace=$workspace" >> "$GITHUB_OUTPUT"
|
||||
echo "scheme=$scheme" >> "$GITHUB_OUTPUT"
|
||||
echo "Using workspace='$workspace' scheme='$scheme'"
|
||||
|
||||
- name: Build unsigned iPhone Simulator app
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NSUnbufferedIO=YES xcodebuild \
|
||||
-workspace "${{ steps.xc.outputs.workspace }}" \
|
||||
-scheme "${{ steps.xc.outputs.scheme }}" \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'generic/platform=iOS Simulator' \
|
||||
-derivedDataPath ios/build \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
build 2>&1 | tee xcodebuild.log
|
||||
|
||||
- name: Upload xcodebuild log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ios-xcodebuild-log
|
||||
path: xcodebuild.log
|
||||
retention-days: 14
|
||||
if-no-files-found: ignore
|
||||
332
.github/workflows/publish-app-store.yml
vendored
332
.github/workflows/publish-app-store.yml
vendored
@@ -1,45 +1,59 @@
|
||||
# STATUS: Validated structure — awaiting Apple Developer Program enrollment approval.
|
||||
# Once enrollment is approved, complete these steps and this workflow is production-ready:
|
||||
# Publish OpenCode for iOS to TestFlight with EAS Build + EAS Submit.
|
||||
#
|
||||
# REMAINING GAPS (must complete before first run):
|
||||
# 1. Update eas.json: replace REPLACE_WITH_APP_STORE_CONNECT_APP_ID and REPLACE_WITH_APPLE_TEAM_ID
|
||||
# (see eas.json.README.md for exact click paths in App Store Connect)
|
||||
# 2. Add GitHub secrets (Settings > Secrets and variables > Actions):
|
||||
# EAS_TOKEN Expo access token (expo.dev > Account > Access Tokens)
|
||||
# APPLE_APP_STORE_CONNECT_API_KEY_ID Key ID from App Store Connect > Users & Access > Integrations > App Store Connect API
|
||||
# APPLE_APP_STORE_CONNECT_ISSUER_ID Issuer ID from same page
|
||||
# APPLE_APP_STORE_CONNECT_API_KEY base64-encoded .p8 file (download at key creation — one time only)
|
||||
# 3. Run `eas login` locally and `eas build:configure` on first run to let EAS set up signing
|
||||
# 4. Manually upload first build to App Store Connect (required once to create the app record)
|
||||
# This workflow FAILS FAST (non-zero exit) instead of "succeeding by skipping":
|
||||
# a release with missing credentials or unfilled identifiers is a hard error, so a
|
||||
# green run always means a real build was produced and submitted.
|
||||
#
|
||||
# OPTIONAL secrets (crash reporting):
|
||||
# EXPO_PUBLIC_SENTRY_DSN SENTRY_AUTH_TOKEN SENTRY_ORG SENTRY_PROJECT
|
||||
# ── HUMAN GATE (one-time, after Apple Developer Program enrollment) ──────────────
|
||||
# Complete ALL of the following before releasing. Do NOT invent any of these IDs.
|
||||
#
|
||||
# Build strategy: EAS Build (Expo Application Services)
|
||||
# - No Mac runner needed; Expo hosts macOS workers with managed certificates.
|
||||
# - Cost: free tier (30 builds/month); upgrade to $19/month for unlimited/priority queue.
|
||||
# - See distribution/ios-enrollment-runbook.md for full enrollment steps.
|
||||
# - See eas.json.README.md for placeholder fill-in instructions.
|
||||
# - Alternative (self-hosted Mac runner): see commented section at bottom of this file.
|
||||
# 1. Fill and commit the eas.json placeholders (see eas.json.README.md for click paths):
|
||||
# submit.production.ios.ascAppId REPLACE_WITH_APP_STORE_CONNECT_APP_ID → numeric App Store Connect App ID
|
||||
# submit.production.ios.appleTeamId REPLACE_WITH_APPLE_TEAM_ID → 10-char Apple Team ID
|
||||
#
|
||||
# 2. Add GitHub Actions secrets (Settings → Secrets and variables → Actions):
|
||||
# EXPO_TOKEN Expo access token (expo.dev → Account settings → Access tokens)
|
||||
# APPLE_APP_STORE_CONNECT_API_KEY_ID ASC API Key ID (App Store Connect → Users and Access → Integrations → App Store Connect API)
|
||||
# APPLE_APP_STORE_CONNECT_ISSUER_ID ASC API Issuer ID (same page)
|
||||
# APPLE_APP_STORE_CONNECT_API_KEY base64 of the .p8 key file: `base64 -i AuthKey_XXXX.p8` (downloadable once)
|
||||
#
|
||||
# 3. Bootstrap iOS signing credentials on EAS once (creates the distribution cert +
|
||||
# provisioning profile so CI never needs to prompt):
|
||||
# eas login && eas build --platform ios --profile production
|
||||
#
|
||||
# Optional crash reporting belongs in the EAS `production` environment because the
|
||||
# iOS bundle is built on a remote EAS worker. Configure EXPO_PUBLIC_SENTRY_DSN,
|
||||
# SENTRY_AUTH_TOKEN, SENTRY_ORG, and SENTRY_PROJECT in Expo before releasing.
|
||||
#
|
||||
# Build number is managed remotely by EAS (eas.json: cli.appVersionSource=remote,
|
||||
# build.production.ios.autoIncrement=buildNumber). No app.json mutation happens here.
|
||||
|
||||
name: Publish to App Store (TestFlight)
|
||||
|
||||
on:
|
||||
# One release ⇒ one build. Triggering only on `release: published` avoids the
|
||||
# duplicate build that a combined release+tag trigger would create.
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-ios:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
EXPO_PUBLIC_SENTRY_DSN: ${{ secrets.EXPO_PUBLIC_SENTRY_DSN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
# Serialize runs per release so a re-trigger cannot start a duplicate concurrent
|
||||
# build/submit. cancel-in-progress:false never kills an in-flight submission.
|
||||
concurrency:
|
||||
group: publish-app-store-${{ github.event.release.tag_name || github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
testflight:
|
||||
name: EAS Build and submit to TestFlight
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
EAS_CLI_VERSION: "21.0.0"
|
||||
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -48,144 +62,148 @@ jobs:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
|
||||
- name: Check Apple prerequisites
|
||||
id: check-apple
|
||||
run: |
|
||||
if [[ -n "${{ secrets.EAS_TOKEN }}" ]]; then
|
||||
echo "proceed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "proceed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning::Apple Developer enrollment pending — EAS_TOKEN not set. Skipping iOS build."
|
||||
fi
|
||||
|
||||
# Install EAS CLI globally. Pin to a recent stable version.
|
||||
- name: Install EAS CLI
|
||||
if: steps.check-apple.outputs.proceed == 'true'
|
||||
run: npm install -g eas-cli@13
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.check-apple.outputs.proceed == 'true'
|
||||
run: npm install --legacy-peer-deps
|
||||
|
||||
# Bump ios.buildNumber to match github.run_number (monotonically increasing).
|
||||
# App Store Connect rejects duplicate build numbers for the same version string.
|
||||
- name: Bump ios.buildNumber in app.json
|
||||
if: steps.check-apple.outputs.proceed == 'true'
|
||||
run: |
|
||||
node -e "
|
||||
const f = 'app.json';
|
||||
const j = require('./' + f);
|
||||
j.expo.ios = j.expo.ios || {};
|
||||
j.expo.ios.buildNumber = String(${{ github.run_number }});
|
||||
require('fs').writeFileSync(f, JSON.stringify(j, null, 2) + '\n');
|
||||
"
|
||||
echo "buildNumber now: $(node -p "require('./app.json').expo.ios.buildNumber")"
|
||||
|
||||
# EAS Build: builds the IPA in Expo's cloud (macOS workers managed by Expo).
|
||||
# --non-interactive: no prompts, suitable for CI.
|
||||
# --platform ios: iOS only (Android is handled by publish-play-store.yml).
|
||||
# --profile production: uses the "production" profile in eas.json (created below if missing).
|
||||
- name: Build IPA via EAS
|
||||
if: steps.check-apple.outputs.proceed == 'true'
|
||||
- name: Preflight — verify credentials and identifiers (fail fast)
|
||||
env:
|
||||
EXPO_TOKEN: ${{ secrets.EAS_TOKEN }}
|
||||
APPLE_APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY_ID }}
|
||||
APPLE_APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_ISSUER_ID }}
|
||||
APPLE_APP_STORE_CONNECT_API_KEY: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY }}
|
||||
ASC_KEY_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY_ID }}
|
||||
ASC_ISSUER_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_ISSUER_ID }}
|
||||
ASC_KEY_B64: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
fail=0
|
||||
need() {
|
||||
if [ -z "${2:-}" ]; then
|
||||
echo "::error::Missing required secret: $1"
|
||||
fail=1
|
||||
fi
|
||||
}
|
||||
need "EXPO_TOKEN" "${EXPO_TOKEN:-}"
|
||||
need "APPLE_APP_STORE_CONNECT_API_KEY_ID" "${ASC_KEY_ID:-}"
|
||||
need "APPLE_APP_STORE_CONNECT_ISSUER_ID" "${ASC_ISSUER_ID:-}"
|
||||
need "APPLE_APP_STORE_CONNECT_API_KEY" "${ASC_KEY_B64:-}"
|
||||
asc_app_id=$(node -p "require('./eas.json').submit.production.ios.ascAppId || ''")
|
||||
team_id=$(node -p "require('./eas.json').submit.production.ios.appleTeamId || ''")
|
||||
case "$asc_app_id" in
|
||||
""|REPLACE_*) echo "::error::eas.json submit.production.ios.ascAppId is unset or still a placeholder"; fail=1 ;;
|
||||
*[!0-9]*) echo "::error::eas.json submit.production.ios.ascAppId must contain only digits"; fail=1 ;;
|
||||
esac
|
||||
case "$team_id" in
|
||||
""|REPLACE_*) echo "::error::eas.json submit.production.ios.appleTeamId is unset or still a placeholder"; fail=1 ;;
|
||||
esac
|
||||
if ! printf '%s' "$team_id" | grep -Eq '^[A-Z0-9]{10}$'; then
|
||||
echo "::error::eas.json submit.production.ios.appleTeamId must be a 10-character Apple Team ID"
|
||||
fail=1
|
||||
fi
|
||||
if [ -n "${ASC_KEY_ID:-}" ] && ! printf '%s' "$ASC_KEY_ID" | grep -Eq '^[A-Z0-9]{10}$'; then
|
||||
echo "::error::APPLE_APP_STORE_CONNECT_API_KEY_ID must be a 10-character key ID"
|
||||
fail=1
|
||||
fi
|
||||
if [ -n "${ASC_ISSUER_ID:-}" ] && ! printf '%s' "$ASC_ISSUER_ID" | grep -Eq '^[0-9a-fA-F-]{36}$'; then
|
||||
echo "::error::APPLE_APP_STORE_CONNECT_ISSUER_ID must be a UUID"
|
||||
fail=1
|
||||
fi
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "::error::BLOCKED: complete the one-time human-gated setup in this workflow's header (GitHub secrets + eas.json identifiers) before releasing. No build was started."
|
||||
exit 1
|
||||
fi
|
||||
echo "Preflight OK — all credentials and identifiers present."
|
||||
|
||||
- name: Install EAS CLI (exact pin)
|
||||
run: npm install -g eas-cli@"$EAS_CLI_VERSION"
|
||||
|
||||
- name: Install dependencies (deterministic)
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Configure App Store Connect API key
|
||||
env:
|
||||
ASC_KEY_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY_ID }}
|
||||
ASC_ISSUER_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_ISSUER_ID }}
|
||||
ASC_KEY_B64: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
key_path="$RUNNER_TEMP/asc_api_key.p8"
|
||||
printf '%s' "$ASC_KEY_B64" | base64 -d > "$key_path"
|
||||
if ! head -n1 "$key_path" | grep -q "BEGIN PRIVATE KEY"; then
|
||||
echo "::error::APPLE_APP_STORE_CONNECT_API_KEY did not base64-decode to a valid .p8 private key."
|
||||
exit 1
|
||||
fi
|
||||
chmod 600 "$key_path"
|
||||
export ASC_KEY_PATH="$key_path"
|
||||
# Expose ASC credentials to EAS Build for non-interactive signing management.
|
||||
{
|
||||
echo "EXPO_ASC_API_KEY_PATH=$key_path"
|
||||
echo "EXPO_ASC_KEY_ID=$ASC_KEY_ID"
|
||||
echo "EXPO_ASC_ISSUER_ID=$ASC_ISSUER_ID"
|
||||
echo "EXPO_APPLE_TEAM_ID=$(node -p "require('./eas.json').submit.production.ios.appleTeamId")"
|
||||
echo "EXPO_APPLE_TEAM_TYPE=COMPANY_OR_ORGANIZATION"
|
||||
} >> "$GITHUB_ENV"
|
||||
# EAS Submit reads the ASC key only from the eas.json submit profile (all three
|
||||
# fields required). Inject them here so no real key IDs are committed to the repo.
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const j = require('./eas.json');
|
||||
j.submit.production.ios.ascApiKeyPath = process.env.ASC_KEY_PATH;
|
||||
j.submit.production.ios.ascApiKeyId = process.env.ASC_KEY_ID;
|
||||
j.submit.production.ios.ascApiKeyIssuerId = process.env.ASC_ISSUER_ID;
|
||||
fs.writeFileSync('eas.json', JSON.stringify(j, null, 2) + '\n');
|
||||
"
|
||||
echo "ASC API key configured for EAS Build and EAS Submit."
|
||||
|
||||
- name: EAS Build (iOS, wait for completion)
|
||||
id: build
|
||||
run: |
|
||||
set -uo pipefail
|
||||
set +e
|
||||
eas build \
|
||||
--platform ios \
|
||||
--profile production \
|
||||
--non-interactive \
|
||||
--no-wait \
|
||||
--json \
|
||||
| tee eas-build-output.json
|
||||
BUILD_ID=$(cat eas-build-output.json | node -e "const d=require('fs').readFileSync('/dev/stdin','utf8');console.log(JSON.parse(d).id)")
|
||||
echo "EAS_BUILD_ID=$BUILD_ID" >> $GITHUB_ENV
|
||||
echo "Build ID: $BUILD_ID"
|
||||
--json > eas-build-output.json
|
||||
rc=$?
|
||||
set -e
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
echo "::error::eas build failed (exit $rc). See the eas-ios-build-metadata artifact."
|
||||
exit "$rc"
|
||||
fi
|
||||
# `eas build --json` prints a JSON ARRAY of completed builds. Select the exact
|
||||
# iOS build id deterministically — never rely on an ambiguous "latest".
|
||||
build_id=$(node -e "
|
||||
const a = JSON.parse(require('fs').readFileSync('eas-build-output.json', 'utf8'));
|
||||
if (!Array.isArray(a)) { console.error('Expected a JSON array from eas build --json'); process.exit(1); }
|
||||
const ios = a.filter((b) => String(b.platform).toUpperCase() === 'IOS');
|
||||
if (ios.length !== 1) { console.error('Expected exactly one iOS build, got ' + ios.length); process.exit(1); }
|
||||
const b = ios[0];
|
||||
if (b.status && String(b.status).toUpperCase() !== 'FINISHED') { console.error('iOS build did not finish: ' + b.status); process.exit(1); }
|
||||
if (!b.id) { console.error('Build object has no id'); process.exit(1); }
|
||||
process.stdout.write(b.id);
|
||||
")
|
||||
echo "build_id=$build_id" >> "$GITHUB_OUTPUT"
|
||||
echo "Selected EAS iOS build id: $build_id"
|
||||
|
||||
# Wait for the EAS build to complete (iOS builds typically take 15–25 minutes).
|
||||
- name: Wait for EAS build
|
||||
if: steps.check-apple.outputs.proceed == 'true'
|
||||
env:
|
||||
EXPO_TOKEN: ${{ secrets.EAS_TOKEN }}
|
||||
run: |
|
||||
echo "Waiting for build $EAS_BUILD_ID to complete..."
|
||||
eas build:view "$EAS_BUILD_ID" --json --wait
|
||||
echo "Build complete."
|
||||
- name: Upload EAS build metadata
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: eas-ios-build-metadata
|
||||
path: eas-build-output.json
|
||||
retention-days: 30
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Submit to TestFlight via EAS Submit. Uses the same App Store Connect API key.
|
||||
# --latest: picks the most recent finished build for this app + platform.
|
||||
- name: Submit to TestFlight via EAS Submit
|
||||
if: steps.check-apple.outputs.proceed == 'true'
|
||||
env:
|
||||
EXPO_TOKEN: ${{ secrets.EAS_TOKEN }}
|
||||
APPLE_APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY_ID }}
|
||||
APPLE_APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_ISSUER_ID }}
|
||||
APPLE_APP_STORE_CONNECT_API_KEY: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY }}
|
||||
- name: Submit exact build to TestFlight
|
||||
run: |
|
||||
set -euo pipefail
|
||||
eas submit \
|
||||
--platform ios \
|
||||
--id "$EAS_BUILD_ID" \
|
||||
--profile production \
|
||||
--id "${{ steps.build.outputs.build_id }}" \
|
||||
--non-interactive
|
||||
|
||||
# Upload release notes to TestFlight (what's new text for testers).
|
||||
# NOTE: EAS Submit does not yet support whatsNew natively; use fastlane pilot
|
||||
# or App Store Connect API directly if per-build release notes are needed.
|
||||
- name: Upload TestFlight release notes (informational)
|
||||
if: steps.check-apple.outputs.proceed == 'true'
|
||||
- name: TestFlight release notes (informational)
|
||||
if: always()
|
||||
run: |
|
||||
echo "TestFlight release notes for this build:"
|
||||
cat distribution/whatsnew-ios/release-notes-en-US.txt
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ALTERNATIVE: Self-hosted Mac runner (macbook13-pro at 100.68.120.26)
|
||||
# ---------------------------------------------------------------------------
|
||||
# To use the Mac mini instead of EAS Build:
|
||||
# 1. SSH to macbook13-pro and set up GitHub self-hosted runner:
|
||||
# https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners
|
||||
# 2. Change "runs-on: ubuntu-latest" above to "runs-on: self-hosted"
|
||||
# and add label "macos" for clarity.
|
||||
# 3. Replace the EAS Build + Submit steps with:
|
||||
#
|
||||
# - name: Install CocoaPods
|
||||
# run: sudo gem install cocoapods
|
||||
#
|
||||
# - name: Expo prebuild (iOS)
|
||||
# run: npx expo prebuild --platform ios --no-install
|
||||
#
|
||||
# - name: Install CocoaPods dependencies
|
||||
# working-directory: ios
|
||||
# run: pod install
|
||||
#
|
||||
# - name: Build IPA
|
||||
# run: |
|
||||
# xcodebuild -workspace ios/opencodemobile.xcworkspace \
|
||||
# -scheme opencodemobile \
|
||||
# -sdk iphoneos \
|
||||
# -configuration Release \
|
||||
# -archivePath $RUNNER_TEMP/opencodemobile.xcarchive \
|
||||
# archive \
|
||||
# CODE_SIGN_STYLE=Manual \
|
||||
# DEVELOPMENT_TEAM=${{ secrets.APPLE_TEAM_ID }} \
|
||||
# CODE_SIGN_IDENTITY="Apple Distribution" \
|
||||
# PROVISIONING_PROFILE_SPECIFIER="${{ secrets.IOS_PROVISIONING_PROFILE_NAME }}"
|
||||
#
|
||||
# - name: Export IPA
|
||||
# run: |
|
||||
# xcodebuild -exportArchive \
|
||||
# -archivePath $RUNNER_TEMP/opencodemobile.xcarchive \
|
||||
# -exportOptionsPlist ios/ExportOptions.plist \
|
||||
# -exportPath $RUNNER_TEMP/export
|
||||
#
|
||||
# - name: Upload to TestFlight (xcrun altool / notarytool)
|
||||
# run: |
|
||||
# xcrun altool --upload-app \
|
||||
# -f "$RUNNER_TEMP/export/opencodemobile.ipa" \
|
||||
# --type ios \
|
||||
# --apiKey "${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY_ID }}" \
|
||||
# --apiIssuer "${{ secrets.APPLE_APP_STORE_CONNECT_ISSUER_ID }}"
|
||||
#
|
||||
# Self-hosted runner cost: $0 compute (your hardware), but requires maintaining
|
||||
# a macOS machine with Xcode, certificates, and provisioning profiles.
|
||||
# EAS Build is strongly recommended for the first release.
|
||||
notes="distribution/whatsnew-ios/release-notes-en-US.txt"
|
||||
if [ -f "$notes" ]; then
|
||||
echo "TestFlight 'What to Test' notes for this release:"
|
||||
cat "$notes"
|
||||
else
|
||||
echo "No release notes file found at $notes"
|
||||
fi
|
||||
|
||||
1
app.json
1
app.json
@@ -25,6 +25,7 @@
|
||||
"NSMicrophoneUsageDescription": "OpenCode uses the microphone to capture your voice when using speech-to-text input.",
|
||||
"NSPhotoLibraryUsageDescription": "OpenCode can attach images from your photo library to messages to your AI coding agent.",
|
||||
"NSCameraUsageDescription": "OpenCode can capture images with your camera and attach them to messages to your AI coding agent.",
|
||||
"NSLocalNetworkUsageDescription": "OpenCode uses your local network to connect to self-hosted OpenCode servers running on your LAN.",
|
||||
"NSAppTransportSecurity": {
|
||||
"NSAllowsArbitraryLoads": true,
|
||||
"NSAllowsArbitraryLoadsInWebContent": false
|
||||
|
||||
BIN
assets/icon.png
BIN
assets/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 28 KiB |
@@ -276,36 +276,28 @@ All icons and screenshots must be provided before submitting for review.
|
||||
|
||||
The 1024×1024 icon must NOT have rounded corners (Apple applies them). No transparency.
|
||||
|
||||
Current status: `assets/icon.json` is a placeholder — **real PNG required before submission**.
|
||||
Current status: **Ready.** `assets/icon.png` is 1024×1024.
|
||||
|
||||
### iPhone Screenshots (REQUIRED)
|
||||
|
||||
Minimum 1 screenshot per device class. Recommended: 3–5 showing key flows.
|
||||
|
||||
| Device | Resolution | Size name in App Store Connect |
|
||||
|---|---|---|
|
||||
| iPhone 6.7" (iPhone 16 Pro Max / 15 Plus) | 1320×2868 or 1290×2796 | 6.7" Super Retina XDR Display |
|
||||
| iPhone 6.5" (iPhone 14 Plus / 11 Pro Max) | 1242×2688 | 6.5" Super Retina XDR Display |
|
||||
| iPhone 5.5" (iPhone 8 Plus) | 1242×2208 | 5.5" Retina HD Display |
|
||||
| Device | Resolution | Size name in App Store Connect | Status |
|
||||
|---|---|---|---|
|
||||
| iPhone 6.7" (iPhone 16 Pro Max / 15 Plus) | 1320×2868 or 1290×2796 | 6.7" Super Retina XDR Display | Placeholder set exists; recapture from current iOS build |
|
||||
| iPhone 6.5" (iPhone 14 Plus / 11 Pro Max) | 1242×2688 | 6.5" Super Retina XDR Display | Placeholder set exists; recapture from current iOS build |
|
||||
| iPhone 5.5" (iPhone 8 Plus) | 1242×2208 | 5.5" Retina HD Display | Optional |
|
||||
|
||||
Note: As of 2024, Apple only requires 6.7" and 6.5" for new submissions. 5.5" is optional but recommended for coverage.
|
||||
|
||||
Suggested screenshot subjects:
|
||||
1. Connection setup screen (add server URL)
|
||||
2. Active chat session — streaming AI response
|
||||
3. File diff view — seeing a code change
|
||||
4. Tool approval dialog
|
||||
5. Session list / multi-session view
|
||||
6. Biometric unlock (if possible to screenshot without triggering auth)
|
||||
|
||||
### iPad Screenshots (REQUIRED for Universal apps)
|
||||
|
||||
Since `supportsTablet: true`, iPad screenshots are required.
|
||||
|
||||
| Device | Resolution | Size name in App Store Connect |
|
||||
|---|---|---|
|
||||
| iPad 12.9" (iPad Pro 6th gen) | 2048×2732 | 12.9" iPad Pro (6th gen) |
|
||||
| iPad 11" (iPad Pro M4) | 1668×2388 | 11" iPad Pro (M4) |
|
||||
| Device | Resolution | Size name in App Store Connect | Status |
|
||||
|---|---|---|---|
|
||||
| iPad 12.9" (iPad Pro 6th gen) | 2048×2732 | 12.9" iPad Pro (6th gen) | Placeholder set exists; recapture from current iOS build |
|
||||
| iPad 11" (iPad Pro M4) | 1668×2388 | 11" iPad Pro (M4) | Optional |
|
||||
|
||||
Minimum 1 per device class required. iPad screenshots can be the same content as iPhone.
|
||||
|
||||
@@ -335,11 +327,11 @@ To use: you need opencode running somewhere accessible (local Wi-Fi, Tailscale,
|
||||
## Pending Before First Submission
|
||||
|
||||
- [ ] Apple Developer Program enrollment approved (D-U-N-S 142059652, VIBE TECHNOLOGIES LLC)
|
||||
- [ ] App Store Connect app record created (bundle ID: ai.opencode.mobile)
|
||||
- [ ] App icon 1024×1024 PNG (no alpha, no rounded corners)
|
||||
- [ ] iPhone screenshots (6.7" minimum; 6.5" strongly recommended)
|
||||
- [ ] iPad screenshots (12.9" minimum)
|
||||
- [ ] Privacy policy live at https://dzianisv.github.io/opencode-mobile/privacy/
|
||||
- [ ] App Store Connect app record created (bundle ID: cc.agentlabs.opencode)
|
||||
- [x] App icon 1024×1024 PNG (no alpha, no rounded corners)
|
||||
- [ ] Capture current iPhone screenshots (6.7" minimum; 6.5" strongly recommended)
|
||||
- [ ] Capture current iPad screenshots (12.9" minimum)
|
||||
- [x] Privacy policy live at https://dzianisv.github.io/opencode-mobile/privacy/
|
||||
- [ ] App Store Connect API key created (for CI — Key ID, Issuer ID, .p8 file)
|
||||
- [ ] Apple Distribution certificate + provisioning profile (or use EAS managed signing)
|
||||
- [ ] Export compliance answered (No to custom encryption)
|
||||
|
||||
@@ -110,10 +110,10 @@ While waiting for Apple's verification call and approval:
|
||||
|
||||
- [x] Prepare App Store listing copy → `distribution/app-store-listing.md`
|
||||
- [x] Write CI workflow (draft) → `.github/workflows/publish-app-store.yml`
|
||||
- [ ] Create app icon 1024×1024 PNG
|
||||
- [ ] Capture iPhone screenshots (use iOS Simulator in Xcode on any Mac)
|
||||
- [ ] Capture iPad screenshots
|
||||
- [ ] Write/publish privacy policy at https://dzianisv.github.io/opencode-mobile/privacy/
|
||||
- [x] Create app icon 1024×1024 PNG
|
||||
- [ ] Replace placeholder iPhone screenshots with captures from the current iOS Simulator build
|
||||
- [ ] Replace placeholder iPad screenshots with captures from the current iOS Simulator build
|
||||
- [x] Write/publish privacy policy at https://dzianisv.github.io/opencode-mobile/privacy/
|
||||
- [ ] Set up EAS account at https://expo.dev/ (free tier, log in with Expo account)
|
||||
- [ ] Add iOS config patches to `app.json` (done in this PR)
|
||||
- [ ] Run `npx expo prebuild --platform ios` on a Mac to validate the Xcode project
|
||||
@@ -127,8 +127,8 @@ While waiting for Apple's verification call and approval:
|
||||
- Platform: iOS
|
||||
- Name: `OpenCode`
|
||||
- Primary Language: English (U.S.)
|
||||
- Bundle ID: `ai.opencode.mobile` — register this explicit App ID first at https://developer.apple.com/account/resources/identifiers/
|
||||
- SKU: `ai.opencode.mobile` (can match bundle ID)
|
||||
- Bundle ID: `cc.agentlabs.opencode` — register this explicit App ID first at https://developer.apple.com/account/resources/identifiers/
|
||||
- SKU: `cc.agentlabs.opencode` (can match bundle ID)
|
||||
|
||||
2. Configure App ID capabilities needed:
|
||||
- Push Notifications (for `expo-notifications`)
|
||||
@@ -141,7 +141,13 @@ While waiting for Apple's verification call and approval:
|
||||
- Note: Key ID and Issuer ID
|
||||
- Base64-encode the .p8 and store in GitHub secret `APPLE_APP_STORE_CONNECT_API_KEY`
|
||||
|
||||
4. Create an internal TestFlight group and add yourself as tester
|
||||
4. Configure the EAS `production` environment for optional crash reporting:
|
||||
- `EXPO_PUBLIC_SENTRY_DSN`
|
||||
- `SENTRY_AUTH_TOKEN` (secret visibility)
|
||||
- `SENTRY_ORG`
|
||||
- `SENTRY_PROJECT`
|
||||
|
||||
5. Create an internal TestFlight group and add yourself as tester
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
# Apple App Store — opencode-mobile
|
||||
|
||||
Operational doc for shipping `ai.opencode.mobile` to Apple App Store under VIBE TECHNOLOGIES, LLC.
|
||||
Operational doc for shipping `cc.agentlabs.opencode` to Apple App Store under VIBE TECHNOLOGIES, LLC.
|
||||
|
||||
For full company facts (D-U-N-S, address, governor) see `~/.agents/skills/vibetechnologies-llc/SKILL.md`.
|
||||
|
||||
---
|
||||
|
||||
## Account state (as of 2026-05-24)
|
||||
## Account state
|
||||
|
||||
The Apple account state below was last recorded on 2026-05-24. Re-verify it in the
|
||||
Apple Developer portal before running the release workflow; this Linux runner has no
|
||||
Apple or EAS credentials and cannot confirm enrollment status.
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Apple ID email | `support@agentlabs.cc` (per decision 2026-05-24) |
|
||||
| Apple Developer Program | ❌ **not enrolled — user signing up now** |
|
||||
| Apple Developer Program | ⚠️ Last recorded as not enrolled; verify current status |
|
||||
| D-U-N-S (for org enrollment) | 142059652 |
|
||||
| Enrollment fee | $99/year (not yet paid) |
|
||||
| Identity verification call | ⏸ pending after enrollment submitted (Apple calls within 2-7 business days) |
|
||||
| App Store Connect record | ⏸ created after enrollment |
|
||||
| TestFlight | ⏸ available after enrollment |
|
||||
| App Store production | ⏸ after TestFlight + Apple review |
|
||||
| Enrollment fee | $99/year |
|
||||
| Identity verification call | ⚠️ Verify current status |
|
||||
| App Store Connect record | ⚠️ No app ID is configured in `eas.json` |
|
||||
| TestFlight | ⏸ No verified build yet |
|
||||
| App Store production | ⏸ After TestFlight + Apple review |
|
||||
|
||||
### Bundle identity
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Bundle identifier | `ai.opencode.mobile` (same as Android — same brand) |
|
||||
| Bundle identifier | `cc.agentlabs.opencode` (same as Android) |
|
||||
| Apple Team ID | ⏸ assigned at enrollment |
|
||||
| App Store Connect App ID | ⏸ assigned on first app creation |
|
||||
|
||||
@@ -57,14 +61,14 @@ Because the answer is "No", no ERN (Encryption Registration Number) is required
|
||||
## What's already done
|
||||
|
||||
1. ✅ iOS section of `app.json` patched:
|
||||
- `ios.buildNumber`: "1" (CI auto-bumps)
|
||||
- `ios.buildNumber`: "1" (initial value; EAS manages production build numbers remotely)
|
||||
- `ios.entitlements.aps-environment`: "production" (push notifications)
|
||||
- `ios.infoPlist.NSAppTransportSecurity.NSAllowsArbitraryLoads`: true (required — connects to user self-hosted opencode servers over HTTP on LAN)
|
||||
- Usage strings: NSFaceIDUsageDescription, NSSpeechRecognitionUsageDescription, NSMicrophoneUsageDescription, NSPhotoLibraryUsageDescription, NSCameraUsageDescription
|
||||
- Usage strings: NSFaceIDUsageDescription, NSSpeechRecognitionUsageDescription, NSMicrophoneUsageDescription, NSPhotoLibraryUsageDescription, NSCameraUsageDescription, NSLocalNetworkUsageDescription
|
||||
- Plugin registrations completed for `expo-notifications`, `expo-image-picker`, `expo-speech-recognition` (were missing — would have caused native iOS setup to silently skip)
|
||||
2. ✅ EAS Build config: `eas.json` with development/preview/production profiles (2 placeholders for App ID + Team ID)
|
||||
3. ✅ Build strategy chosen: **EAS Build** (Expo cloud, free tier 30 builds/mo, managed certs, EAS Submit handles TestFlight upload)
|
||||
4. ✅ CI workflow draft: `.github/workflows/publish-app-store.yml` (DRAFT — needs Apple secrets before enabling)
|
||||
4. ✅ CI workflows: `.github/workflows/ios-ci.yml` validates unsigned Simulator builds; `.github/workflows/publish-app-store.yml` fails fast until Apple/EAS setup is complete
|
||||
5. ✅ Listing copy drafted: `distribution/app-store-listing.md`
|
||||
6. ✅ Enrollment runbook: `distribution/ios-enrollment-runbook.md` (pre-filled with all VIBE TECHNOLOGIES, LLC fields)
|
||||
7. ✅ Release notes scaffold: `distribution/whatsnew-ios/release-notes-en-US.txt`
|
||||
@@ -78,28 +82,28 @@ Because the answer is "No", no ERN (Encryption Registration Number) is required
|
||||
| 1 | Sign in / create Apple ID for `support@agentlabs.cc` w/ 2FA | User | 🔴 user action required |
|
||||
| 2 | Enroll in Apple Developer Program ($99) | User | 🔴 user action required |
|
||||
| 3 | Pass Apple verification call | User | 🔴 user action required |
|
||||
| 4 | App icon — 1024×1024 PNG, opaque (no alpha) | ✅ Done | `assets/icon-appstore.png` (flattened from Android-produced `assets/icon.png`) |
|
||||
| 5 | iPhone screenshots 6.7" (1290×2796) + 6.5" (1242×2688) | ✅ Done | `distribution/app-store-graphics/iphone-67/{01,02,03}.png` + `iphone-65/` — 3 mockup screens: connection, chat, diff |
|
||||
| 6 | iPad screenshots 12.9" (2048×2732) | ✅ Done | `distribution/app-store-graphics/ipad-129/{01,02}.png` — 2 mockup screens |
|
||||
| 4 | App icon — 1024×1024 PNG, opaque (no alpha) | ✅ Done | `assets/icon.png` is RGB with no alpha channel |
|
||||
| 5 | iPhone screenshots 6.7" (1290×2796) + 6.5" (1242×2688) | Mac | 🔴 Placeholder mockups exist; recapture the current app in Simulator |
|
||||
| 6 | iPad screenshots 12.9" (2048×2732) | Mac | 🔴 Placeholder mockups exist; recapture the current app in Simulator |
|
||||
| 7 | Privacy policy — live at https://dzianisv.github.io/opencode-mobile/privacy/ | ✅ done | Live & verified (HTTP 200) on gh-pages. Content handled by Android agent (`distribution/privacy-policy.{md,html}`). iOS-specific ATT / nutrition label addendum written in `distribution/app-store-listing.md`. |
|
||||
| 8 | Privacy nutrition label (App Tracking + Data Collection) | ✅ Done | Updated in `distribution/app-store-listing.md` — ATT explicitly noted (not used), Sentry opt-in status documented |
|
||||
| 9 | Export compliance | ✅ Done | `ITSAppUsesNonExemptEncryption: false` added to `app.json`. Answers + rationale in this doc (see Export Compliance section above) and `distribution/app-store-listing.md`. |
|
||||
| 10 | ATS justification in App Review notes | ✅ Done | Full justification text in `distribution/app-store-listing.md` under "App Review Notes — ATS Justification" |
|
||||
| 11 | Reviewer test instructions | ✅ Done | Updated with correct command (`opencode serve --hostname 0.0.0.0`) in `distribution/app-store-listing.md` |
|
||||
| 12 | GitHub secrets: `EAS_TOKEN`, `APPLE_APP_STORE_CONNECT_API_KEY_ID`, `APPLE_APP_STORE_CONNECT_ISSUER_ID`, `APPLE_APP_STORE_CONNECT_API_KEY` (base64 .p8) | User | 🟡 post-enrollment — see `.github/workflows/publish-app-store.yml` header |
|
||||
| 12 | GitHub secrets: `EXPO_TOKEN`, `APPLE_APP_STORE_CONNECT_API_KEY_ID`, `APPLE_APP_STORE_CONNECT_ISSUER_ID`, `APPLE_APP_STORE_CONNECT_API_KEY` (base64 .p8) | User | 🟡 post-enrollment — see `.github/workflows/publish-app-store.yml` header |
|
||||
| 13 | Update `eas.json` placeholders: `ascAppId` + `appleTeamId` | User | 🟡 post-enrollment — see `eas.json.README.md` for click paths |
|
||||
| 14 | CI workflow validated | ✅ Done | `.github/workflows/publish-app-store.yml` structure verified; comment header updated with remaining gaps |
|
||||
| 14 | CI workflow validated | CI | 🟡 Linux checks pass; PR must prove the macOS Simulator build |
|
||||
| 15 | TestFlight release notes | ✅ Done | `distribution/whatsnew-ios/release-notes-en-US.txt` — polished, 1658 chars (limit 4000) |
|
||||
|
||||
---
|
||||
|
||||
## Publishing process (after enrollment + assets ready)
|
||||
|
||||
1. (manual) Sign in to App Store Connect, create app with bundle id `ai.opencode.mobile`.
|
||||
1. (manual) Sign in to App Store Connect, create app with bundle id `cc.agentlabs.opencode`.
|
||||
2. (manual) Generate App Store Connect API key (App Manager role) → download `.p8` → base64 encode → add as GitHub secret.
|
||||
3. (manual) Update `eas.json` placeholders (Team ID, ASC App ID).
|
||||
4. (manual) `eas login` + `eas build:configure` for first-time setup (managed signing).
|
||||
5. (automated) `git tag v0.2.x && git push --tags` → CI calls EAS Build → EAS Submit → IPA lands in TestFlight.
|
||||
5. (automated) Publish a GitHub Release for the version tag → CI calls EAS Build → EAS Submit → IPA lands in TestFlight.
|
||||
6. (manual, first time) Add internal testers in App Store Connect → distribute via TestFlight.
|
||||
7. (manual) After internal testing OK → submit for App Store review (production).
|
||||
8. Apple review typically 24-48h. 90% of submissions reviewed within 24h.
|
||||
@@ -138,7 +142,8 @@ Upgrade to EAS $19/mo only if free-tier queue (10-30 min wait) becomes a problem
|
||||
|
||||
- `app.json` — iOS config (patched 2026-05-24)
|
||||
- `eas.json` — EAS build profiles (2 placeholders)
|
||||
- `.github/workflows/publish-app-store.yml` — DRAFT CI
|
||||
- `.github/workflows/ios-ci.yml` — PR/main unsigned iOS Simulator build gate
|
||||
- `.github/workflows/publish-app-store.yml` — fail-fast TestFlight release CI
|
||||
- `distribution/app-store-listing.md` — listing copy + answers
|
||||
- `distribution/ios-enrollment-runbook.md` — enrollment runbook
|
||||
- `distribution/whatsnew-ios/release-notes-en-US.txt` — release notes
|
||||
|
||||
8
eas.json
8
eas.json
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 13.0.0"
|
||||
"version": ">= 21.0.0",
|
||||
"appVersionSource": "remote"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
@@ -17,9 +18,10 @@
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"autoIncrement": false,
|
||||
"environment": "production",
|
||||
"ios": {
|
||||
"distribution": "store"
|
||||
"distribution": "store",
|
||||
"autoIncrement": "buildNumber"
|
||||
},
|
||||
"android": {
|
||||
"buildType": "app-bundle"
|
||||
|
||||
@@ -62,20 +62,21 @@ Alternatively, in App Store Connect:
|
||||
|
||||
| Field | Value | Notes |
|
||||
|---|---|---|
|
||||
| `appleId` | `appstore@agentlabs.cc` | The Apple ID used for App Store Connect login — update if different |
|
||||
| `appleId` | `support@agentlabs.cc` | The Apple ID used for App Store Connect login — update if different |
|
||||
| `distribution` (production ios) | `store` | Correct for App Store / TestFlight submissions |
|
||||
| `buildType` (production android) | `app-bundle` | Correct for Play Store AAB submissions |
|
||||
| `autoIncrement` | `false` | Build number is bumped by the CI workflow (github.run_number), not EAS |
|
||||
| `cli.version` | `>= 13.0.0` | Requires EAS CLI 13 or later; CI installs `eas-cli@13` |
|
||||
| `autoIncrement` | `buildNumber` | EAS increments the iOS build number remotely for every production build |
|
||||
| `appVersionSource` | `remote` | EAS is the source of truth for store build numbers |
|
||||
| `cli.version` | `>= 21.0.0` | CI installs the exact supported release, `eas-cli@21.0.0` |
|
||||
|
||||
---
|
||||
|
||||
## After filling in the placeholders
|
||||
|
||||
1. Commit the updated `eas.json` to the repo.
|
||||
2. Add the GitHub Actions secrets (see `.github/workflows/publish-app-store.yml` header for the exact list).
|
||||
3. Tag a release: `git tag v0.2.3 && git push --tags`
|
||||
4. The CI workflow will build the IPA via EAS and submit it to TestFlight automatically.
|
||||
2. Add the `EXPO_TOKEN` and App Store Connect API GitHub Actions secrets (see `.github/workflows/publish-app-store.yml` for the exact list).
|
||||
3. Publish a GitHub Release for the version tag (or manually dispatch the App Store workflow).
|
||||
4. The release event triggers CI to build the IPA via EAS and submit that exact build to TestFlight.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user