Initial Upload
Some checks failed
CI / Lint & Typecheck (push) Has been cancelled
CI / Test (routes) (push) Has been cancelled
CI / Test (security) (push) Has been cancelled
CI / Test (services) (push) Has been cancelled
CI / Test (unit) (push) Has been cancelled
CI / Test (integration) (push) Has been cancelled
CI / Test Coverage (push) Has been cancelled
CI / Build (push) Has been cancelled
Some checks failed
CI / Lint & Typecheck (push) Has been cancelled
CI / Test (routes) (push) Has been cancelled
CI / Test (security) (push) Has been cancelled
CI / Test (services) (push) Has been cancelled
CI / Test (unit) (push) Has been cancelled
CI / Test (integration) (push) Has been cancelled
CI / Test Coverage (push) Has been cancelled
CI / Build (push) Has been cancelled
This commit is contained in:
454
.github/workflows/mobile-build.yml
vendored
Normal file
454
.github/workflows/mobile-build.yml
vendored
Normal file
@@ -0,0 +1,454 @@
|
||||
name: Mobile Build (Local)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git tag to build (e.g., v1.2.3-beta.1)'
|
||||
required: true
|
||||
type: string
|
||||
platform:
|
||||
description: 'Platform to build'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- android
|
||||
- ios
|
||||
- both
|
||||
default: 'both'
|
||||
distribution:
|
||||
description: 'Distribution method'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- internal
|
||||
- store
|
||||
default: 'internal'
|
||||
profile:
|
||||
description: 'Build profile'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- preview
|
||||
- production
|
||||
default: 'preview'
|
||||
|
||||
# Prevent concurrent builds that could cause version conflicts
|
||||
concurrency:
|
||||
group: mobile-build
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
jobs:
|
||||
# Atomically prepare and reserve version numbers before builds start
|
||||
prepare-versions:
|
||||
name: Prepare Versions
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/mobile
|
||||
outputs:
|
||||
marketing-version: ${{ steps.versions.outputs.marketing-version }}
|
||||
android-version-code: ${{ steps.versions.outputs.android-version-code }}
|
||||
ios-build-number: ${{ steps.versions.outputs.ios-build-number }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
working-directory: .
|
||||
|
||||
- name: Setup Expo
|
||||
uses: expo/expo-github-action@main
|
||||
with:
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: Reserve version numbers
|
||||
id: versions
|
||||
env:
|
||||
EAS_PROJECT_ID: "2e0b9595-ac62-493f-9a10-4f8758bb4b2d"
|
||||
APP_IDENTIFIER: "com.tracearr.app"
|
||||
run: |
|
||||
# Extract marketing version from git tag
|
||||
VERSION="${{ inputs.tag }}"
|
||||
VERSION="${VERSION#v}"
|
||||
VERSION="${VERSION%%-*}"
|
||||
echo "Marketing version: $VERSION"
|
||||
echo "marketing-version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
# Get current versions from EAS remote
|
||||
echo "Getting current versions from EAS remote..."
|
||||
REMOTE_VERSIONS=$(eas build:version:get --platform all --json --non-interactive 2>/dev/null || echo '{}')
|
||||
echo "Remote versions: $REMOTE_VERSIONS"
|
||||
|
||||
# Parse current versions (default to 0 if not set)
|
||||
CURRENT_ANDROID_VC=$(echo "$REMOTE_VERSIONS" | jq -r '.versionCode // "0"')
|
||||
CURRENT_IOS_BN=$(echo "$REMOTE_VERSIONS" | jq -r '.buildNumber // "0"')
|
||||
|
||||
# Increment versions
|
||||
NEXT_ANDROID_VC=$((CURRENT_ANDROID_VC + 1))
|
||||
NEXT_IOS_BN=$((CURRENT_IOS_BN + 1))
|
||||
|
||||
echo "Android versionCode: $CURRENT_ANDROID_VC -> $NEXT_ANDROID_VC"
|
||||
echo "iOS buildNumber: $CURRENT_IOS_BN -> $NEXT_IOS_BN"
|
||||
|
||||
# Reserve versions on EAS remote via GraphQL API
|
||||
echo "Reserving Android version $NEXT_ANDROID_VC on EAS..."
|
||||
ANDROID_RESULT=$(curl -sf -X POST https://api.expo.dev/graphql \
|
||||
-H "Authorization: Bearer $EXPO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"query\":\"mutation { appVersion { createAppVersion(appVersionInput: { appId: \\\"$EAS_PROJECT_ID\\\", platform: ANDROID, applicationIdentifier: \\\"$APP_IDENTIFIER\\\", storeVersion: \\\"$VERSION\\\", buildVersion: \\\"$NEXT_ANDROID_VC\\\" }) { id } } }\"}")
|
||||
echo "Android result: $ANDROID_RESULT"
|
||||
|
||||
echo "Reserving iOS version $NEXT_IOS_BN on EAS..."
|
||||
IOS_RESULT=$(curl -sf -X POST https://api.expo.dev/graphql \
|
||||
-H "Authorization: Bearer $EXPO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"query\":\"mutation { appVersion { createAppVersion(appVersionInput: { appId: \\\"$EAS_PROJECT_ID\\\", platform: IOS, applicationIdentifier: \\\"$APP_IDENTIFIER\\\", storeVersion: \\\"$VERSION\\\", buildVersion: \\\"$NEXT_IOS_BN\\\" }) { id } } }\"}")
|
||||
echo "iOS result: $IOS_RESULT"
|
||||
|
||||
# Output for build jobs
|
||||
echo "android-version-code=$NEXT_ANDROID_VC" >> $GITHUB_OUTPUT
|
||||
echo "ios-build-number=$NEXT_IOS_BN" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Versions reserved successfully"
|
||||
|
||||
build-android:
|
||||
name: Build Android
|
||||
needs: [prepare-versions]
|
||||
if: inputs.platform == 'android' || inputs.platform == 'both'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/mobile
|
||||
outputs:
|
||||
artifact-path: ${{ steps.build.outputs.artifact-path }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Free disk space
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
working-directory: .
|
||||
|
||||
- name: Build shared package
|
||||
run: pnpm --filter @tracearr/shared build
|
||||
working-directory: .
|
||||
|
||||
- name: Setup Expo
|
||||
uses: expo/expo-github-action@main
|
||||
with:
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: Set versions from prepare job
|
||||
run: |
|
||||
VERSION="${{ needs.prepare-versions.outputs.marketing-version }}"
|
||||
VERSION_CODE="${{ needs.prepare-versions.outputs.android-version-code }}"
|
||||
|
||||
echo "Setting version: $VERSION, versionCode: $VERSION_CODE"
|
||||
|
||||
jq --arg v "$VERSION" \
|
||||
--argjson vc "$VERSION_CODE" \
|
||||
'.expo.version = $v | .expo.android.versionCode = $vc' \
|
||||
app.json > tmp.json && mv tmp.json app.json
|
||||
|
||||
- name: Build Android locally
|
||||
id: build
|
||||
env:
|
||||
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
|
||||
run: |
|
||||
BUILD_TYPE="${{ inputs.distribution == 'internal' && 'apk' || 'app-bundle' }}"
|
||||
OUTPUT_EXT="${{ inputs.distribution == 'internal' && 'apk' || 'aab' }}"
|
||||
OUTPUT_FILE="tracearr-${{ inputs.tag }}-android.${OUTPUT_EXT}"
|
||||
|
||||
echo "Building $BUILD_TYPE for ${{ inputs.profile }} profile..."
|
||||
|
||||
eas build \
|
||||
--platform android \
|
||||
--profile ${{ inputs.profile }} \
|
||||
--local \
|
||||
--output "./${OUTPUT_FILE}" \
|
||||
--non-interactive
|
||||
|
||||
echo "artifact-path=apps/mobile/${OUTPUT_FILE}" >> $GITHUB_OUTPUT
|
||||
echo "Build complete: ${OUTPUT_FILE}"
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: android-build
|
||||
path: ${{ steps.build.outputs.artifact-path }}
|
||||
retention-days: 30
|
||||
|
||||
build-ios:
|
||||
name: Build iOS
|
||||
needs: [prepare-versions]
|
||||
if: inputs.platform == 'ios' || inputs.platform == 'both'
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 90
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/mobile
|
||||
outputs:
|
||||
artifact-path: ${{ steps.build.outputs.artifact-path }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
working-directory: .
|
||||
|
||||
- name: Build shared package
|
||||
run: pnpm --filter @tracearr/shared build
|
||||
working-directory: .
|
||||
|
||||
- name: Setup Expo
|
||||
uses: expo/expo-github-action@main
|
||||
with:
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: Set versions from prepare job
|
||||
run: |
|
||||
VERSION="${{ needs.prepare-versions.outputs.marketing-version }}"
|
||||
BUILD_NUMBER="${{ needs.prepare-versions.outputs.ios-build-number }}"
|
||||
|
||||
echo "Setting version: $VERSION, buildNumber: $BUILD_NUMBER"
|
||||
|
||||
jq --arg v "$VERSION" \
|
||||
--arg bn "$BUILD_NUMBER" \
|
||||
'.expo.version = $v | .expo.ios.buildNumber = $bn' \
|
||||
app.json > tmp.json && mv tmp.json app.json
|
||||
|
||||
- name: Build iOS locally
|
||||
id: build
|
||||
run: |
|
||||
OUTPUT_FILE="tracearr-${{ inputs.tag }}-ios.ipa"
|
||||
|
||||
echo "Building iOS for ${{ inputs.profile }} profile..."
|
||||
|
||||
eas build \
|
||||
--platform ios \
|
||||
--profile ${{ inputs.profile }} \
|
||||
--local \
|
||||
--output "./${OUTPUT_FILE}" \
|
||||
--non-interactive
|
||||
|
||||
echo "artifact-path=apps/mobile/${OUTPUT_FILE}" >> $GITHUB_OUTPUT
|
||||
echo "Build complete: ${OUTPUT_FILE}"
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ios-build
|
||||
path: ${{ steps.build.outputs.artifact-path }}
|
||||
retention-days: 30
|
||||
|
||||
upload-and-submit:
|
||||
name: Upload and Submit
|
||||
needs: [prepare-versions, build-android, build-ios]
|
||||
if: always() && (needs.build-android.result == 'success' || needs.build-ios.result == 'success')
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/mobile
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
working-directory: .
|
||||
|
||||
- name: Setup Expo
|
||||
uses: expo/expo-github-action@main
|
||||
with:
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: Download Android artifact
|
||||
if: needs.build-android.result == 'success'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: android-build
|
||||
path: apps/mobile/
|
||||
|
||||
- name: Download iOS artifact
|
||||
if: needs.build-ios.result == 'success'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ios-build
|
||||
path: apps/mobile/
|
||||
|
||||
- name: Upload Android to EAS
|
||||
if: needs.build-android.result == 'success'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
APK_FILE=$(ls *.apk 2>/dev/null || ls *.aab 2>/dev/null)
|
||||
echo "Uploading $APK_FILE to EAS..."
|
||||
eas upload --platform android --build-path "./${APK_FILE}" --non-interactive
|
||||
|
||||
- name: Upload iOS to EAS
|
||||
if: needs.build-ios.result == 'success'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
IPA_FILE=$(ls *.ipa)
|
||||
echo "Uploading $IPA_FILE to EAS..."
|
||||
eas upload --platform ios --build-path "./${IPA_FILE}" --non-interactive
|
||||
|
||||
- name: Decode Google Service Account
|
||||
if: inputs.distribution == 'store' && needs.build-android.result == 'success'
|
||||
run: |
|
||||
mkdir -p credentials
|
||||
echo "${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }}" | base64 -d > credentials/google-service-account.json
|
||||
|
||||
- name: Submit Android to Play Store
|
||||
if: inputs.distribution == 'store' && needs.build-android.result == 'success'
|
||||
run: |
|
||||
AAB_FILE=$(ls *.aab)
|
||||
echo "Submitting $AAB_FILE to Play Store..."
|
||||
eas submit --platform android --path "./${AAB_FILE}" --profile ${{ inputs.profile }} --non-interactive
|
||||
|
||||
- name: Submit iOS to App Store
|
||||
if: inputs.distribution == 'store' && needs.build-ios.result == 'success'
|
||||
env:
|
||||
EXPO_APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.EXPO_APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
run: |
|
||||
IPA_FILE=$(ls *.ipa)
|
||||
echo "Submitting $IPA_FILE to App Store..."
|
||||
eas submit --platform ios --path "./${IPA_FILE}" --profile ${{ inputs.profile }} --non-interactive
|
||||
|
||||
summary:
|
||||
name: Build Summary
|
||||
needs: [prepare-versions, build-android, build-ios, upload-and-submit]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Generate summary
|
||||
run: |
|
||||
{
|
||||
echo "## Mobile Build Summary"
|
||||
echo ""
|
||||
echo "**Tag**: ${{ inputs.tag }}"
|
||||
echo "**Platform**: ${{ inputs.platform }}"
|
||||
echo "**Distribution**: ${{ inputs.distribution }}"
|
||||
echo "**Profile**: ${{ inputs.profile }}"
|
||||
echo ""
|
||||
echo "### Version Info"
|
||||
echo "- **Marketing Version**: ${{ needs.prepare-versions.outputs.marketing-version }}"
|
||||
echo "- **Android versionCode**: ${{ needs.prepare-versions.outputs.android-version-code }}"
|
||||
echo "- **iOS buildNumber**: ${{ needs.prepare-versions.outputs.ios-build-number }}"
|
||||
echo ""
|
||||
echo "### Build Results"
|
||||
echo ""
|
||||
|
||||
if [[ "${{ inputs.platform }}" == "android" || "${{ inputs.platform }}" == "both" ]]; then
|
||||
if [[ "${{ needs.build-android.result }}" == "success" ]]; then
|
||||
echo "[OK] Android: Built successfully"
|
||||
else
|
||||
echo "[FAIL] Android: ${{ needs.build-android.result }}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${{ inputs.platform }}" == "ios" || "${{ inputs.platform }}" == "both" ]]; then
|
||||
if [[ "${{ needs.build-ios.result }}" == "success" ]]; then
|
||||
echo "[OK] iOS: Built successfully"
|
||||
else
|
||||
echo "[FAIL] iOS: ${{ needs.build-ios.result }}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "### Upload and Submit"
|
||||
if [[ "${{ needs.upload-and-submit.result }}" == "success" ]]; then
|
||||
echo "[OK] Uploaded to EAS"
|
||||
if [[ "${{ inputs.distribution }}" == "store" ]]; then
|
||||
echo "[OK] Submitted to stores"
|
||||
else
|
||||
echo "[INFO] Internal distribution - check Expo dashboard for download links"
|
||||
fi
|
||||
else
|
||||
echo "[WARN] Upload/Submit: ${{ needs.upload-and-submit.result }}"
|
||||
fi
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
Reference in New Issue
Block a user