mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
* feat: add version management system with build number tracking - Add version.json to track iOS/Android build numbers separately - Create version.cjs script for build number management - Add Fastlane version_manager.rb helper - Keep npm version for semver, version.json for build tracking * feat: integrate version.json with Fastlane deployment process ## What Changed - Updated iOS and Android Fastlane lanes to use version.json for build number management - Added automatic build number increment on deployment - Added deployment timestamp tracking ## How It Works ### iOS Deployment 1. Reads current build number from version.json 2. Increments iOS build number (e.g., 148 → 149) 3. Updates Xcode project with new build number via increment_build_number 4. Proceeds with TestFlight deployment 5. Updates lastDeployed timestamp on successful upload ### Android Deployment 1. Reads current build number from version.json 2. Increments Android build number (e.g., 82 → 83) 3. Updates build.gradle with new version code via increment_version_code 4. Proceeds with Play Store deployment 5. Updates lastDeployed timestamp on successful upload ## Why This Change - Eliminates manual version/build number entry - Prevents version conflicts between deployments - Provides single source of truth for build numbers - Enables automatic deployments without human intervention - Tracks deployment history with timestamps ## Dependencies - Requires version.json file (already created in previous commit) - Uses existing Fastlane plugins: - increment_build_number (iOS - built-in) - increment_version_code (Android - from plugin) - Version numbers still managed by npm version command * feat: enhance deploy confirmation with version.json info * fix: use ENV variable directly in increment_build_number to avoid secret masking * fix: correct xcodeproj path for GitHub Actions workflow * feat: add test mode to workflow for safe testing - Skip store uploads when test_mode is true - Test version bumps and builds without deployment - Prevent accidental pushes to TestFlight/Play Store * fix: use gradle_file_path instead of gradle_file for increment_version_code * fix: use gsub to remove ../ prefix for CI compatibility * chore: remove accidentally committed files - Remove .cursor/mcp.json - Remove .cursorignore - Remove deployment-automation-summary.md - Remove deployment-meeting-questions.md - Remove pipeline.md * feat: auto-commit version.json after successful deployment - Commits version.json changes back to repository - Only runs when test_mode is false - Uses [skip ci] to prevent infinite loops - Checks for actual changes before committing * feat : update package.json in build step using npm version * feat: add comprehensive caching to mobile deployment workflow - Add caching for Yarn dependencies, Ruby gems, CocoaPods, Gradle, and Android NDK - Implement cache versioning strategy for easy cache invalidation - Fix cache order: caches now restored after checkout but before dependency installation - Update mobile-setup action to skip installs when dependencies are cached - Add cache size monitoring to track usage against GitHub's 10GB limit - Fix Slack notification bug: skip notifications in test_mode - Add detailed logging for package.json version updates (show from/to versions) Expected performance improvement: ~50% faster builds (from ~15min to ~7-10min) * fix: move bundler config after Ruby setup in mobile-setup action * fix: rename cache env vars to avoid Yarn conflicts Yarn was interpreting YARN_CACHE_VERSION as its own config setting. Prefixed all cache version env vars with GH_ to avoid conflicts. * fix: remove bundler deployment mode to allow Gemfile updates The deployment mode was causing bundler to fail when Gemfile changed (nokogiri was removed). CI should be able to update the lockfile as needed. * feat: implement strict lock file enforcement (Option 1) - Re-enable bundler deployment mode for strict Gemfile.lock checking - Use yarn install --immutable for strict yarn.lock checking - Add clear error messages when lock files are out of date - Add pre-checks to verify lock files exist - This ensures reproducible builds and makes caching maximally effective When developers change dependencies, they must now: 1. Run yarn install or bundle install locally 2. Commit the updated lock files 3. CI will fail with helpful instructions if they forget * fix: update Gemfile.lock for CI environment Remove nokogiri from Gemfile.lock since it's excluded in CI environments (GITHUB_ACTIONS=true). This allows the strict lock file checks to pass in CI. * fix: correct yarn.lock path for monorepo workspace The project uses Yarn workspaces with yarn.lock at the repository root, not in the app directory. Updated paths to check for yarn.lock at workspace root and use it for cache keys. * fix: handle both boolean and string test_mode parameter The test_mode parameter was only checking for string 'true' but could be passed as boolean true from command line. Now handles both cases to ensure test mode works correctly for iOS and Android. * fix: address code review feedback for mobile deployment workflow - Replace jq with Node.js for version extraction (jq not available on macOS runners) - Fix concurrent commit race condition by creating separate update-version job - Add platform validation to version_manager.rb and version.cjs scripts - Use POSIX-compatible single = for shell string comparisons - Ensure single atomic commit when deploying to both platforms * fix: formatting and linting issues - Remove trailing spaces from workflow YAML file - Fix prettier formatting in JavaScript files - Add -y flag to yarn version command for non-interactive mode - Address all lint warnings from CI * feat: implement automated branch-based mobile deployments - Add mobile-deploy-auto.yml workflow that triggers on PR merges to dev/main - Update mobile-deploy.yml to support workflow_call for reusability - Add deployment_track, version_bump, and auto_deploy parameters - Create new Fastlane lanes (deploy_auto) for iOS and Android - Implement smart version bumping based on PR labels (major/minor/patch) - Add graceful error handling for Play Store permission issues - Enhance Slack notifications with deployment track information This enables automatic deployments when PRs are merged: - dev branch → internal testing track - main branch → production track - Skip deployment with [skip-deploy] in PR or no-deploy label * feat: add automated git tagging and release system - Add automatic git tagging for production deployments (v2.5.5, platform-specific tags) - Create GitHub releases with changelogs for production deployments - Add manual release script (yarn release) for version bumping and tagging - Implement simple changelog generation from git history - Add comprehensive deployment documentation in .github/MOBILE_DEPLOYMENT.md - Update app/README.md with deployment commands and workflows This completes the release automation system requested in the ticket for manual tagging and versioning with automated changelogs and release notes. --------- Co-authored-by: Jayaditya Gupta <nightmare@Jayadityas-MacBook-Pro.local>
975 lines
42 KiB
YAML
975 lines
42 KiB
YAML
name: Mobile App Deployments
|
||
|
||
env:
|
||
# Build environment versions
|
||
NODE_VERSION: 18
|
||
RUBY_VERSION: 3.2
|
||
JAVA_VERSION: 17
|
||
ANDROID_API_LEVEL: 35
|
||
ANDROID_NDK_VERSION: 26.1.10909125
|
||
|
||
# Cache versioning - increment these to bust caches when needed
|
||
GH_CACHE_VERSION: v1 # Global cache version
|
||
GH_YARN_CACHE_VERSION: v1 # Yarn-specific cache version
|
||
GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
|
||
GH_PODS_CACHE_VERSION: v1 # CocoaPods cache version
|
||
GH_GRADLE_CACHE_VERSION: v1 # Gradle cache version
|
||
|
||
# Path configuration
|
||
WORKSPACE: ${{ github.workspace }}
|
||
APP_PATH: ${{ github.workspace }}/app
|
||
|
||
# Certificate/keystore paths
|
||
ANDROID_KEYSTORE_PATH: /android/app/upload-keystore.jks
|
||
ANDROID_PLAY_STORE_JSON_KEY_PATH: /android/play-store-key.json
|
||
IOS_DIST_CERT_PATH: /ios/certs/dist_cert.p12
|
||
IOS_CONNECT_API_KEY_PATH: /ios/certs/connect_api_key.p8
|
||
IOS_PROV_PROFILE_PROJ_PATH: /ios/certs/profile.mobileprovision
|
||
IOS_PROV_PROFILE_DIRECTORY: "~/Library/MobileDevice/Provisioning\ Profiles/"
|
||
|
||
permissions:
|
||
contents: write
|
||
pull-requests: write
|
||
|
||
on:
|
||
workflow_dispatch:
|
||
inputs:
|
||
platform:
|
||
description: "Select platform to build"
|
||
required: true
|
||
default: "both"
|
||
type: choice
|
||
options:
|
||
- ios
|
||
- android
|
||
- both
|
||
test_mode:
|
||
description: "Test mode (skip upload to stores)"
|
||
required: false
|
||
type: boolean
|
||
default: false
|
||
deployment_track:
|
||
description: "Deployment track (internal/production)"
|
||
required: false
|
||
type: choice
|
||
default: "internal"
|
||
options:
|
||
- internal
|
||
- production
|
||
version_bump:
|
||
description: "Version bump type"
|
||
required: false
|
||
type: choice
|
||
default: "build"
|
||
options:
|
||
- build
|
||
- patch
|
||
- minor
|
||
- major
|
||
|
||
workflow_call:
|
||
inputs:
|
||
platform:
|
||
type: string
|
||
required: true
|
||
deployment_track:
|
||
type: string
|
||
required: false
|
||
default: "internal"
|
||
version_bump:
|
||
type: string
|
||
required: false
|
||
default: "build"
|
||
auto_deploy:
|
||
type: boolean
|
||
required: false
|
||
default: false
|
||
test_mode:
|
||
type: boolean
|
||
required: false
|
||
default: false
|
||
|
||
jobs:
|
||
build-ios:
|
||
runs-on: macos-latest
|
||
if: github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'ios' || github.event.inputs.platform == 'both')
|
||
steps:
|
||
- name: Mobile deployment status
|
||
run: |
|
||
if [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
|
||
echo "📱 Mobile deployment is disabled for pull requests"
|
||
echo "🚀 To deploy, use the workflow_dispatch trigger (Run workflow button)"
|
||
echo "✅ Deployment steps will be skipped for this PR"
|
||
else
|
||
echo "🚀 Mobile deployment is enabled - proceeding with iOS build"
|
||
fi
|
||
|
||
- uses: actions/checkout@v4
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||
|
||
- name: Set up Xcode
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||
uses: maxim-lobanov/setup-xcode@v1
|
||
# with:
|
||
# # some cocoapods won't compile with xcode 16.3
|
||
# xcode-version: "16.2"
|
||
|
||
- name: Cache Yarn dependencies
|
||
id: yarn-cache
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: |
|
||
${{ env.APP_PATH }}/node_modules
|
||
~/.cache/yarn
|
||
key: ${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-${{ hashFiles('yarn.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-
|
||
${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-
|
||
|
||
- name: Cache Ruby gems
|
||
id: gems-cache
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: ${{ env.APP_PATH }}/vendor/bundle
|
||
key: ${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-
|
||
${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-
|
||
|
||
- name: Cache CocoaPods
|
||
id: pods-cache
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: ${{ env.APP_PATH }}/ios/Pods
|
||
key: ${{ runner.os }}-pods-${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-pods-${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }}-
|
||
${{ runner.os }}-pods-${{ env.GH_CACHE_VERSION }}-
|
||
|
||
- name: Log cache status
|
||
run: |
|
||
echo "Cache hit results:"
|
||
echo "- Yarn cache hit: ${{ steps.yarn-cache.outputs.cache-hit }}"
|
||
echo "- Gems cache hit: ${{ steps.gems-cache.outputs.cache-hit }}"
|
||
echo "- Pods cache hit: ${{ steps.pods-cache.outputs.cache-hit }}"
|
||
|
||
- name: Verify lock files are up to date
|
||
run: |
|
||
echo "🔍 Checking if lock files are in sync with dependency files..."
|
||
|
||
# For yarn workspaces, yarn.lock is at root
|
||
if [ ! -f "${{ env.WORKSPACE }}/yarn.lock" ]; then
|
||
echo "❌ ERROR: yarn.lock file is missing at workspace root!"
|
||
echo "Run 'yarn install' at the repository root and commit the yarn.lock file."
|
||
exit 1
|
||
fi
|
||
|
||
# Gemfile.lock is in app directory
|
||
if [ ! -f "${{ env.APP_PATH }}/Gemfile.lock" ]; then
|
||
echo "❌ ERROR: Gemfile.lock file is missing!"
|
||
echo "Run 'bundle install' in the app directory and commit the Gemfile.lock file."
|
||
exit 1
|
||
fi
|
||
|
||
echo "✅ Lock files exist"
|
||
|
||
- name: Install Mobile Dependencies
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||
uses: ./.github/actions/mobile-setup
|
||
with:
|
||
app_path: ${{ env.APP_PATH }}
|
||
node_version: ${{ env.NODE_VERSION }}
|
||
ruby_version: ${{ env.RUBY_VERSION }}
|
||
workspace: ${{ env.WORKSPACE }}
|
||
|
||
- name: Verify iOS Secrets
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||
run: |
|
||
# Verify App Store Connect API Key exists and contains PEM header
|
||
if [ -z "${{ secrets.IOS_CONNECT_API_KEY_BASE64 }}" ]; then
|
||
echo "❌ Error: App Store Connect API Key cannot be empty"
|
||
exit 1
|
||
fi
|
||
# Verify Issuer ID is in correct format (UUID)
|
||
if ! echo "${{ secrets.IOS_CONNECT_ISSUER_ID }}" | grep -E "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" >/dev/null; then
|
||
echo "❌ Error: Invalid App Store Connect Issuer ID format (should be UUID)"
|
||
exit 1
|
||
fi
|
||
# Verify Key ID is in correct format (alphanumeric)
|
||
if ! echo "${{ secrets.IOS_CONNECT_KEY_ID }}" | grep -E "^[A-Z0-9]{10}$" >/dev/null; then
|
||
echo "❌ Error: Invalid App Store Connect Key ID format"
|
||
exit 1
|
||
fi
|
||
# Verify P12 password is not empty and meets basic security requirements
|
||
if [ -z "${{ secrets.IOS_P12_PASSWORD }}" ]; then
|
||
echo "❌ Error: P12 password cannot be empty"
|
||
exit 1
|
||
fi
|
||
# Verify base64 secrets are not empty
|
||
if [ -z "${{ secrets.IOS_DIST_CERT_BASE64 }}" ]; then
|
||
echo "❌ Error: Distribution certificate cannot be empty"
|
||
exit 1
|
||
fi
|
||
if [ -z "${{ secrets.IOS_PROV_PROFILE_BASE64 }}" ]; then
|
||
echo "❌ Error: Provisioning profile cannot be empty"
|
||
exit 1
|
||
fi
|
||
echo "✅ All iOS secrets verified successfully!"
|
||
|
||
- name: Decode certificate and profile
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||
run: |
|
||
mkdir -p "${{ env.APP_PATH }}$(dirname "${{ env.IOS_DIST_CERT_PATH }}")"
|
||
echo "${{ secrets.IOS_DIST_CERT_BASE64 }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}
|
||
echo "${{ secrets.IOS_PROV_PROFILE_BASE64 }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}
|
||
echo "${{ secrets.IOS_CONNECT_API_KEY_BASE64 }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.IOS_CONNECT_API_KEY_PATH }}
|
||
|
||
# for debugging...which can take some time :(
|
||
- name: Verify ios secret checksums
|
||
if: false # for debugging
|
||
run: |
|
||
echo "SHA256 of dist_cert.p12:"
|
||
shasum -a 256 ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}
|
||
echo "SHA256 of profile.mobileprovision:"
|
||
shasum -a 256 ${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}
|
||
echo "SHA256 of connect_api_key.p8:"
|
||
shasum -a 256 ${{ env.APP_PATH }}${{ env.IOS_CONNECT_API_KEY_PATH }}
|
||
echo "Certificate file size:"
|
||
ls -l ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}
|
||
echo "SHA256 of password:"
|
||
echo -n "${{ secrets.IOS_P12_PASSWORD }}" | shasum -a 256
|
||
echo "SHA256 of connect_api_key_base64:"
|
||
echo -n "${{ secrets.IOS_CONNECT_API_KEY_BASE64 }}" | shasum -a 256
|
||
echo "Verifying certificate..."
|
||
if openssl pkcs12 -in ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }} -password pass:'${{ secrets.IOS_P12_PASSWORD }}' -info >/dev/null 2>&1 || openssl pkcs12 -in ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }} -password pass:'${{ secrets.IOS_P12_PASSWORD }}' -info 2>&1 | grep -q "MAC:"; then
|
||
echo "✅ Certificate verification successful (algorithm warning can be safely ignored)"
|
||
else
|
||
echo "❌ Certificate verification failed - please check certificate validity"
|
||
exit 1
|
||
fi
|
||
|
||
- name: Verify iOS certificate and environment
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android' && !env.ACT
|
||
run: |
|
||
# Check if certificate directory exists
|
||
if [ ! -d "${{ env.APP_PATH }}/ios/certs" ]; then
|
||
echo "❌ Error: iOS certificates directory not found at ${{ env.APP_PATH }}/ios/certs"
|
||
exit 1
|
||
fi
|
||
|
||
# Check if certificate file exists
|
||
if [ ! -f "${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}" ]; then
|
||
echo "❌ Error: Distribution certificate not found at ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}"
|
||
exit 1
|
||
fi
|
||
|
||
# Check certificate file permissions
|
||
CERT_PERMS=$(ls -l "${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}" | awk '{print $1}')
|
||
if [ "$CERT_PERMS" != "-rw-r--r--" ]; then
|
||
echo "❌ Error: Distribution certificate has incorrect permissions: $CERT_PERMS"
|
||
echo "Expected: -rw-r--r--"
|
||
exit 1
|
||
fi
|
||
|
||
# Check certificate file size
|
||
CERT_SIZE=$(stat -f%z "${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}" 2>/dev/null || stat -c%s "${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }}")
|
||
if [ "$CERT_SIZE" -lt 1000 ]; then
|
||
echo "❌ Error: Distribution certificate file size ($CERT_SIZE bytes) is suspiciously small"
|
||
exit 1
|
||
fi
|
||
|
||
# Check if we can create a test keychain
|
||
TEST_KEYCHAIN="test.keychain"
|
||
if ! security create-keychain -p "" "$TEST_KEYCHAIN" >/dev/null 2>&1; then
|
||
echo "❌ Error: Unable to create test keychain. Check permissions."
|
||
exit 1
|
||
fi
|
||
security delete-keychain "$TEST_KEYCHAIN" >/dev/null 2>&1
|
||
|
||
echo "✅ Certificate and environment verification passed!"
|
||
|
||
- name: Install certificate
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android' && !env.ACT
|
||
run: |
|
||
security create-keychain -p "" build.keychain >/dev/null 2>&1
|
||
security default-keychain -s build.keychain >/dev/null 2>&1
|
||
security unlock-keychain -p "" build.keychain >/dev/null 2>&1
|
||
security import ${{ env.APP_PATH }}${{ env.IOS_DIST_CERT_PATH }} -k build.keychain -P '${{ secrets.IOS_P12_PASSWORD }}' -T /usr/bin/codesign >/dev/null 2>&1
|
||
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain >/dev/null 2>&1
|
||
|
||
- name: Install provisioning profile
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android' && !env.ACT
|
||
env:
|
||
IOS_APP_IDENTIFIER: ${{ secrets.IOS_APP_IDENTIFIER }}
|
||
IOS_PROV_PROFILE_NAME: ${{ secrets.IOS_PROV_PROFILE_NAME }}
|
||
IOS_PROV_PROFILE_PATH: ${{ env.IOS_PROV_PROFILE_PATH }}
|
||
IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
|
||
run: |
|
||
# Verify file exists before proceeding
|
||
echo "Checking for provisioning profile at: ${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}"
|
||
ls -l "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}"
|
||
if [ ! -f "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" ]; then
|
||
echo "❌ Error: Provisioning profile not found at specified path."
|
||
exit 1
|
||
fi
|
||
echo "Provisioning profile found."
|
||
|
||
# Print file details
|
||
echo "Provisioning Profile File Details:"
|
||
echo "--------------------------------"
|
||
echo "File size: $(stat -f%z "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" 2>/dev/null || stat -c%s "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}") bytes"
|
||
echo "File permissions: $(ls -l "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" | awk '{print $1}')"
|
||
echo "File owner: $(ls -l "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" | awk '{print $3}')"
|
||
echo "--------------------------------"
|
||
|
||
# Create a temporary plist file to extract UUID
|
||
TEMP_PLIST_PATH=$(mktemp /tmp/profile_plist.XXXXXX)
|
||
|
||
# Extract plist from mobileprovision file
|
||
echo "Extracting plist from provisioning profile..."
|
||
security cms -D -i "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" -o "$TEMP_PLIST_PATH"
|
||
if [ $? -ne 0 ]; then
|
||
echo "❌ Error: Failed to extract plist from provisioning profile"
|
||
exit 1
|
||
fi
|
||
|
||
# Extract UUID and profile name from plist
|
||
echo "Extracting UUID and profile name from plist..."
|
||
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print :UUID" "$TEMP_PLIST_PATH" 2>/dev/null)
|
||
if [ $? -ne 0 ] || [ -z "$PROFILE_UUID" ]; then
|
||
echo "❌ Error: Failed to extract UUID from provisioning profile"
|
||
cat "$TEMP_PLIST_PATH" | head -20
|
||
exit 1
|
||
fi
|
||
|
||
# Extract the actual profile name from within the file
|
||
PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print :Name" "$TEMP_PLIST_PATH" 2>/dev/null)
|
||
if [ $? -ne 0 ] || [ -z "$PROFILE_NAME" ]; then
|
||
echo "⚠️ Warning: Failed to extract Name from provisioning profile, will use provided IOS_PROV_PROFILE_NAME"
|
||
PROFILE_NAME="$IOS_PROV_PROFILE_NAME"
|
||
fi
|
||
|
||
echo "Profile UUID: $PROFILE_UUID"
|
||
echo "Profile Name: $PROFILE_NAME"
|
||
|
||
# Install provisioning profile in the correct location with UUID filename
|
||
echo "Installing provisioning profile in filesystem..."
|
||
mkdir -p "/Users/runner/Library/MobileDevice/Provisioning Profiles"
|
||
|
||
# Copy with the UUID as filename
|
||
UUID_TARGET_PATH="/Users/runner/Library/MobileDevice/Provisioning Profiles/${PROFILE_UUID}.mobileprovision"
|
||
cp -v "${{ env.APP_PATH }}${{ env.IOS_PROV_PROFILE_PROJ_PATH }}" "$UUID_TARGET_PATH"
|
||
|
||
# Set correct permissions on the profile
|
||
chmod 644 "$UUID_TARGET_PATH"
|
||
chown runner:staff "$UUID_TARGET_PATH"
|
||
|
||
# Save the profile path and name to environment for later steps
|
||
echo "IOS_PROV_PROFILE_PATH=$UUID_TARGET_PATH" >> $GITHUB_ENV
|
||
echo "IOS_PROV_PROFILE_NAME=$PROFILE_NAME" >> $GITHUB_ENV
|
||
|
||
# Print provisioning profile information
|
||
echo "Provisioning Profile Information:"
|
||
echo "--------------------------------"
|
||
echo "Profile Name (from file): $PROFILE_NAME"
|
||
echo "Profile Name (from env): $IOS_PROV_PROFILE_NAME"
|
||
echo "Profile Path: $UUID_TARGET_PATH"
|
||
echo "Profile UUID: $PROFILE_UUID"
|
||
echo "App Identifier: $IOS_APP_IDENTIFIER"
|
||
echo "Team ID: $IOS_TEAM_ID"
|
||
echo "--------------------------------"
|
||
|
||
# List all provisioning profiles in the system with detailed info
|
||
echo "List of all provisioning profiles in system:"
|
||
ls -la "/Users/runner/Library/MobileDevice/Provisioning Profiles/"
|
||
|
||
# Clean up temp file
|
||
rm -f "$TEMP_PLIST_PATH"
|
||
|
||
echo "✅ Provisioning profile installation steps completed."
|
||
|
||
# act won't work with macos, but you can test with `bundle exec fastlane ios ...`
|
||
- name: Build and upload to App Store Connect/TestFlight
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android' && !env.ACT
|
||
env:
|
||
IOS_APP_IDENTIFIER: ${{ secrets.IOS_APP_IDENTIFIER }}
|
||
IOS_CONNECT_API_KEY_BASE64: ${{ secrets.IOS_CONNECT_API_KEY_BASE64 }}
|
||
IOS_CONNECT_API_KEY_PATH: ${{ env.APP_PATH }}${{ env.IOS_CONNECT_API_KEY_PATH }}
|
||
IOS_CONNECT_ISSUER_ID: ${{ secrets.IOS_CONNECT_ISSUER_ID }}
|
||
IOS_CONNECT_KEY_ID: ${{ secrets.IOS_CONNECT_KEY_ID }}
|
||
IOS_P12_PASSWORD: ${{ secrets.IOS_P12_PASSWORD }}
|
||
IOS_PROJECT_NAME: ${{ secrets.IOS_PROJECT_NAME }}
|
||
IOS_PROJECT_SCHEME: ${{ secrets.IOS_PROJECT_SCHEME }}
|
||
IOS_PROV_PROFILE_DIR: ${{ env.IOS_PROV_PROFILE_DIRECTORY }}
|
||
IOS_PROV_PROFILE_NAME: ${{ secrets.IOS_PROV_PROFILE_NAME }}
|
||
IOS_PROV_PROFILE_PATH: ${{ env.IOS_PROV_PROFILE_PATH }}
|
||
IOS_SIGNING_CERTIFICATE: ${{ secrets.IOS_SIGNING_CERTIFICATE }}
|
||
IOS_TESTFLIGHT_GROUPS: ${{ secrets.IOS_TESTFLIGHT_GROUPS }}
|
||
IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
|
||
IOS_TEAM_NAME: ${{ secrets.IOS_TEAM_NAME }}
|
||
NODE_OPTIONS: "--max-old-space-size=8192"
|
||
SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }}
|
||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}
|
||
SLACK_ANNOUNCE_CHANNEL_NAME: ${{ secrets.SLACK_ANNOUNCE_CHANNEL_NAME }}
|
||
timeout-minutes: 90
|
||
run: |
|
||
cd ${{ env.APP_PATH }}
|
||
echo "--- Pre-Fastlane Diagnostics ---"
|
||
echo "Running as user: $(whoami)"
|
||
echo "Default keychain:"
|
||
security list-keychains -d user
|
||
echo "Identities in build.keychain:"
|
||
security find-identity -v -p codesigning build.keychain || echo "Failed to find identities in build.keychain"
|
||
echo "--- Starting Fastlane ---"
|
||
|
||
# Determine deployment track and version bump
|
||
DEPLOYMENT_TRACK="${{ inputs.deployment_track || 'internal' }}"
|
||
VERSION_BUMP="${{ inputs.version_bump || 'build' }}"
|
||
TEST_MODE="${{ inputs.test_mode || false }}"
|
||
|
||
echo "📱 Deployment Configuration:"
|
||
echo " - Track: $DEPLOYMENT_TRACK"
|
||
echo " - Version Bump: $VERSION_BUMP"
|
||
echo " - Test Mode: $TEST_MODE"
|
||
|
||
if [ "$TEST_MODE" = "true" ]; then
|
||
echo "🧪 Running in TEST MODE - will skip upload to TestFlight"
|
||
bundle exec fastlane ios deploy_auto \
|
||
deployment_track:$DEPLOYMENT_TRACK \
|
||
version_bump:$VERSION_BUMP \
|
||
test_mode:true \
|
||
--verbose
|
||
else
|
||
echo "🚀 Deploying to App Store Connect..."
|
||
bundle exec fastlane ios deploy_auto \
|
||
deployment_track:$DEPLOYMENT_TRACK \
|
||
version_bump:$VERSION_BUMP \
|
||
--verbose
|
||
fi
|
||
|
||
# Version updates moved to separate job to avoid race conditions
|
||
|
||
- name: Remove project.pbxproj updates we don't want to commit
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||
run: |
|
||
PBXPROJ_FILE="app/ios/Self.xcodeproj/project.pbxproj"
|
||
|
||
# Create a temporary file to store version info
|
||
echo "Extracting version information..."
|
||
rm -f versions.txt
|
||
grep -E 'CURRENT_PROJECT_VERSION = [0-9]+;|MARKETING_VERSION = [0-9]+\.[0-9]+\.[0-9]+;' "${PBXPROJ_FILE}" > versions.txt
|
||
|
||
# Check if we have version information
|
||
if [ -s versions.txt ]; then
|
||
echo "Found version information. Resetting file and re-applying versions..."
|
||
|
||
# Store the version values
|
||
CURRENT_VERSION=$(grep 'CURRENT_PROJECT_VERSION' versions.txt | head -1 | sed 's/.*CURRENT_PROJECT_VERSION = \([0-9]*\);.*/\1/')
|
||
MARKETING_VERSION=$(grep 'MARKETING_VERSION' versions.txt | head -1 | sed 's/.*MARKETING_VERSION = \([0-9]*\.[0-9]*\.[0-9]*\);.*/\1/')
|
||
|
||
echo "Current version: $CURRENT_VERSION"
|
||
echo "Marketing version: $MARKETING_VERSION"
|
||
|
||
# Reset the file to HEAD
|
||
git checkout HEAD -- "${PBXPROJ_FILE}"
|
||
|
||
# Update the versions if they exist
|
||
if [ ! -z "$CURRENT_VERSION" ]; then
|
||
sed -i '' "s/\(CURRENT_PROJECT_VERSION = \)[0-9]*;/\1$CURRENT_VERSION;/g" "${PBXPROJ_FILE}"
|
||
fi
|
||
|
||
if [ ! -z "$MARKETING_VERSION" ]; then
|
||
sed -i '' "s/\(MARKETING_VERSION = \)[0-9]*\.[0-9]*\.[0-9]*;/\1$MARKETING_VERSION;/g" "${PBXPROJ_FILE}"
|
||
fi
|
||
|
||
echo "Version information successfully applied."
|
||
else
|
||
echo "No version information found. Resetting file..."
|
||
git checkout HEAD -- "${PBXPROJ_FILE}"
|
||
fi
|
||
|
||
# Clean up
|
||
rm -f versions.txt
|
||
|
||
- name: Get version from package.json
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||
uses: ./.github/actions/get-version
|
||
with:
|
||
app_path: ${{ env.APP_PATH }}
|
||
|
||
- name: Commit updated build number
|
||
# disable for now, commit doesn't work as expected
|
||
if: false #${{ !env.ACT }}
|
||
uses: ./.github/actions/push-changes
|
||
with:
|
||
commit_message: "incrementing ios build number for version ${{ env.VERSION }}"
|
||
commit_paths: "./app/ios/OpenPassport/Info.plist ./app/ios/Self.xcodeproj/project.pbxproj"
|
||
|
||
- name: Monitor cache usage
|
||
if: always()
|
||
run: |
|
||
echo "📊 Cache Size Report (iOS Build)"
|
||
echo "================================"
|
||
|
||
if [ -d "${{ env.APP_PATH }}/node_modules" ]; then
|
||
NODE_SIZE=$(du -sh "${{ env.APP_PATH }}/node_modules" | cut -f1)
|
||
echo "Node modules: $NODE_SIZE"
|
||
fi
|
||
|
||
if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
|
||
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
|
||
echo "Ruby gems: $GEMS_SIZE"
|
||
fi
|
||
|
||
if [ -d "${{ env.APP_PATH }}/ios/Pods" ]; then
|
||
PODS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/Pods" | cut -f1)
|
||
echo "CocoaPods: $PODS_SIZE"
|
||
fi
|
||
|
||
echo "================================"
|
||
echo "💡 GitHub Actions cache limit: 10GB per repository"
|
||
|
||
build-android:
|
||
runs-on: ubuntu-latest
|
||
if: github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'android' || github.event.inputs.platform == 'both')
|
||
steps:
|
||
- name: Mobile deployment status
|
||
run: |
|
||
if [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
|
||
echo "📱 Mobile deployment is disabled for pull requests"
|
||
echo "🚀 To deploy, use the workflow_dispatch trigger (Run workflow button)"
|
||
echo "✅ Deployment steps will be skipped for this PR"
|
||
else
|
||
echo "🚀 Mobile deployment is enabled - proceeding with Android build"
|
||
fi
|
||
|
||
- uses: actions/checkout@v4
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||
|
||
- name: Cache Yarn dependencies
|
||
id: yarn-cache
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: |
|
||
${{ env.APP_PATH }}/node_modules
|
||
~/.cache/yarn
|
||
key: ${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-${{ hashFiles('yarn.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-
|
||
${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-
|
||
|
||
- name: Cache Ruby gems
|
||
id: gems-cache
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: ${{ env.APP_PATH }}/vendor/bundle
|
||
key: ${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-
|
||
${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-
|
||
|
||
- name: Cache Gradle dependencies
|
||
id: gradle-cache
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: |
|
||
~/.gradle/caches
|
||
~/.gradle/wrapper
|
||
key: ${{ runner.os }}-gradle-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GRADLE_CACHE_VERSION }}-${{ hashFiles('app/android/**/*.gradle*', 'app/android/gradle/wrapper/gradle-wrapper.properties') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-gradle-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GRADLE_CACHE_VERSION }}-
|
||
${{ runner.os }}-gradle-${{ env.GH_CACHE_VERSION }}-
|
||
|
||
- name: Cache Android NDK
|
||
id: ndk-cache
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: ${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }}
|
||
key: ${{ runner.os }}-ndk-${{ env.ANDROID_NDK_VERSION }}
|
||
|
||
- name: Log cache status
|
||
run: |
|
||
echo "Cache hit results:"
|
||
echo "- Yarn cache hit: ${{ steps.yarn-cache.outputs.cache-hit }}"
|
||
echo "- Gems cache hit: ${{ steps.gems-cache.outputs.cache-hit }}"
|
||
echo "- Gradle cache hit: ${{ steps.gradle-cache.outputs.cache-hit }}"
|
||
echo "- NDK cache hit: ${{ steps.ndk-cache.outputs.cache-hit }}"
|
||
|
||
- name: Verify lock files are up to date
|
||
run: |
|
||
echo "🔍 Checking if lock files are in sync with dependency files..."
|
||
|
||
# For yarn workspaces, yarn.lock is at root
|
||
if [ ! -f "${{ env.WORKSPACE }}/yarn.lock" ]; then
|
||
echo "❌ ERROR: yarn.lock file is missing at workspace root!"
|
||
echo "Run 'yarn install' at the repository root and commit the yarn.lock file."
|
||
exit 1
|
||
fi
|
||
|
||
# Gemfile.lock is in app directory
|
||
if [ ! -f "${{ env.APP_PATH }}/Gemfile.lock" ]; then
|
||
echo "❌ ERROR: Gemfile.lock file is missing!"
|
||
echo "Run 'bundle install' in the app directory and commit the Gemfile.lock file."
|
||
exit 1
|
||
fi
|
||
|
||
echo "✅ Lock files exist"
|
||
|
||
- name: Install Mobile Dependencies
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||
uses: ./.github/actions/mobile-setup
|
||
with:
|
||
app_path: ${{ env.APP_PATH }}
|
||
node_version: ${{ env.NODE_VERSION }}
|
||
ruby_version: ${{ env.RUBY_VERSION }}
|
||
workspace: ${{ env.WORKSPACE }}
|
||
|
||
# android specific steps
|
||
- name: Setup Java environment
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||
uses: actions/setup-java@v4
|
||
with:
|
||
distribution: "temurin"
|
||
java-version: ${{ env.JAVA_VERSION }}
|
||
|
||
- name: Setup Android SDK
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||
uses: android-actions/setup-android@v3
|
||
with:
|
||
accept-android-sdk-licenses: true
|
||
|
||
- name: Install NDK
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios' && steps.ndk-cache.outputs.cache-hit != 'true'
|
||
run: |
|
||
max_attempts=5
|
||
attempt=1
|
||
while [ $attempt -le $max_attempts ]; do
|
||
echo "Attempt $attempt of $max_attempts to install NDK..."
|
||
if sdkmanager "ndk;${{ env.ANDROID_NDK_VERSION }}"; then
|
||
echo "Successfully installed NDK"
|
||
exit 0
|
||
fi
|
||
echo "Failed to install NDK on attempt $attempt"
|
||
if [ $attempt -eq $max_attempts ]; then
|
||
echo "All attempts to install NDK failed"
|
||
exit 1
|
||
fi
|
||
# Exponential backoff: 2^attempt seconds
|
||
wait_time=$((2 ** attempt))
|
||
echo "Waiting $wait_time seconds before retrying..."
|
||
sleep $wait_time
|
||
attempt=$((attempt + 1))
|
||
done
|
||
|
||
- name: Set Gradle JVM options
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios' && env.ACT
|
||
run: |
|
||
echo "org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8" >> ${{ env.APP_PATH }}/android/gradle.properties
|
||
|
||
- name: Decode Android Secrets
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||
run: |
|
||
echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}
|
||
echo "${{ secrets.ANDROID_PLAY_STORE_JSON_KEY_BASE64 }}" | base64 --decode > ${{ env.APP_PATH }}${{ env.ANDROID_PLAY_STORE_JSON_KEY_PATH }}
|
||
|
||
# run secrets check after keytool has been setup
|
||
- name: Verify Android Secrets
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||
run: |
|
||
# Verify Play Store JSON key base64 secret exists and is valid
|
||
if [ -z "${{ secrets.ANDROID_PLAY_STORE_JSON_KEY_BASE64 }}" ]; then
|
||
echo "❌ Error: Play Store JSON key base64 secret cannot be empty"
|
||
exit 1
|
||
fi
|
||
# Verify the base64 can be decoded
|
||
if ! echo "${{ secrets.ANDROID_PLAY_STORE_JSON_KEY_BASE64 }}" | base64 --decode >/dev/null 2>&1; then
|
||
echo "❌ Error: Invalid Play Store JSON key base64 format"
|
||
exit 1
|
||
fi
|
||
# Verify keystore file exists and is valid
|
||
if [ ! -f "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" ]; then
|
||
echo "❌ Error: Keystore file was not created successfully"
|
||
exit 1
|
||
fi
|
||
# Try to verify the keystore with the provided password
|
||
if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >/dev/null 2>&1; then
|
||
echo "❌ Error: Invalid keystore password"
|
||
exit 1
|
||
fi
|
||
# Verify the key alias exists
|
||
if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" >/dev/null 2>&1; then
|
||
echo "❌ Error: Key alias '${{ secrets.ANDROID_KEY_ALIAS }}' not found in keystore"
|
||
exit 1
|
||
fi
|
||
# Verify the key password
|
||
if ! keytool -list -v -keystore "${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}" -storepass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" -alias "${{ secrets.ANDROID_KEY_ALIAS }}" -keypass "${{ secrets.ANDROID_KEY_PASSWORD }}" >/dev/null 2>&1; then
|
||
echo "❌ Error: Invalid key password"
|
||
exit 1
|
||
fi
|
||
echo "✅ All Android secrets verified successfully!"
|
||
|
||
- name: Build and upload to Google Play Internal Testing
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||
env:
|
||
ANDROID_KEYSTORE: ${{ secrets.ANDROID_KEYSTORE }}
|
||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||
ANDROID_KEYSTORE_PATH: ${{ env.APP_PATH }}${{ env.ANDROID_KEYSTORE_PATH }}
|
||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||
ANDROID_PACKAGE_NAME: ${{ secrets.ANDROID_PACKAGE_NAME }}
|
||
ANDROID_PLAY_STORE_JSON_KEY_PATH: ${{ env.APP_PATH }}${{ env.ANDROID_PLAY_STORE_JSON_KEY_PATH }}
|
||
NODE_OPTIONS: "--max-old-space-size=8192"
|
||
SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }}
|
||
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}
|
||
SLACK_ANNOUNCE_CHANNEL_NAME: ${{ secrets.SLACK_ANNOUNCE_CHANNEL_NAME }}
|
||
run: |
|
||
cd ${{ env.APP_PATH }}
|
||
|
||
# Determine deployment track and version bump
|
||
DEPLOYMENT_TRACK="${{ inputs.deployment_track || 'internal' }}"
|
||
VERSION_BUMP="${{ inputs.version_bump || 'build' }}"
|
||
TEST_MODE="${{ inputs.test_mode || false }}"
|
||
|
||
echo "🤖 Deployment Configuration:"
|
||
echo " - Track: $DEPLOYMENT_TRACK"
|
||
echo " - Version Bump: $VERSION_BUMP"
|
||
echo " - Test Mode: $TEST_MODE"
|
||
|
||
if [ "$TEST_MODE" = "true" ]; then
|
||
echo "🧪 Running in TEST MODE - will skip upload to Play Store"
|
||
bundle exec fastlane android deploy_auto \
|
||
deployment_track:$DEPLOYMENT_TRACK \
|
||
version_bump:$VERSION_BUMP \
|
||
test_mode:true \
|
||
--verbose
|
||
else
|
||
echo "🚀 Deploying to Google Play Store..."
|
||
bundle exec fastlane android deploy_auto \
|
||
deployment_track:$DEPLOYMENT_TRACK \
|
||
version_bump:$VERSION_BUMP \
|
||
--verbose
|
||
fi
|
||
|
||
# Version updates moved to separate job to avoid race conditions
|
||
|
||
- name: Get version from package.json
|
||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||
uses: ./.github/actions/get-version
|
||
with:
|
||
app_path: ${{ env.APP_PATH }}
|
||
|
||
- name: Commit updated build version
|
||
# disable for now, commit doesn't work as expected
|
||
if: false #${{ !env.ACT }}
|
||
uses: ./.github/actions/push-changes
|
||
with:
|
||
commit_message: "incrementing android build version for version ${{ env.VERSION }}"
|
||
commit_paths: "./app/android/app/build.gradle"
|
||
|
||
- name: Monitor cache usage
|
||
if: always()
|
||
run: |
|
||
echo "📊 Cache Size Report (Android Build)"
|
||
echo "===================================="
|
||
|
||
if [ -d "${{ env.APP_PATH }}/node_modules" ]; then
|
||
NODE_SIZE=$(du -sh "${{ env.APP_PATH }}/node_modules" | cut -f1)
|
||
echo "Node modules: $NODE_SIZE"
|
||
fi
|
||
|
||
if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
|
||
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
|
||
echo "Ruby gems: $GEMS_SIZE"
|
||
fi
|
||
|
||
if [ -d "$HOME/.gradle/caches" ]; then
|
||
GRADLE_SIZE=$(du -sh "$HOME/.gradle/caches" | cut -f1)
|
||
echo "Gradle caches: $GRADLE_SIZE"
|
||
fi
|
||
|
||
if [ -d "${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }}" ]; then
|
||
NDK_SIZE=$(du -sh "${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }}" | cut -f1)
|
||
echo "Android NDK: $NDK_SIZE"
|
||
fi
|
||
|
||
echo "===================================="
|
||
echo "💡 GitHub Actions cache limit: 10GB per repository"
|
||
|
||
# Separate job to update version files after successful deployment
|
||
# This avoids race conditions when both iOS and Android run in parallel
|
||
update-version:
|
||
runs-on: ubuntu-latest
|
||
needs: [build-ios, build-android]
|
||
if: |
|
||
always() &&
|
||
github.event.inputs.test_mode != 'true' &&
|
||
(needs.build-ios.result == 'success' || needs.build-android.result == 'success')
|
||
env:
|
||
NODE_VERSION: 18
|
||
APP_PATH: ${{ github.workspace }}/app
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
token: ${{ github.token }}
|
||
fetch-depth: 0
|
||
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
|
||
- name: Update package.json version
|
||
run: |
|
||
cd ${{ env.APP_PATH }}
|
||
|
||
# Get current version from package.json
|
||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||
|
||
# Get new version from version.json (if it exists and has version field)
|
||
if [ -f version.json ] && grep -q '"version"' version.json; then
|
||
NEW_VERSION=$(node -pe 'require("./version.json").version' 2>/dev/null || echo "")
|
||
else
|
||
# Fallback: use current version from package.json
|
||
NEW_VERSION="$CURRENT_VERSION"
|
||
fi
|
||
|
||
# Only update if versions differ
|
||
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ] && [ -n "$NEW_VERSION" ]; then
|
||
echo "📦 Updating package.json version:"
|
||
echo " From: v$CURRENT_VERSION"
|
||
echo " To: v$NEW_VERSION"
|
||
|
||
# Use yarn to update package.json and the lockfile
|
||
yarn version --new-version "$NEW_VERSION" --no-git-tag-version -y
|
||
else
|
||
echo "ℹ️ Version already up to date or no version field in version.json"
|
||
fi
|
||
|
||
- name: Commit and push version files
|
||
run: |
|
||
cd ${{ github.workspace }}
|
||
|
||
# Configure git
|
||
git config user.name "github-actions[bot]"
|
||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||
|
||
# Check if there are any changes to commit
|
||
if git diff --quiet app/version.json app/package.json yarn.lock 2>/dev/null; then
|
||
echo "No changes to version files, skipping commit"
|
||
else
|
||
# Stage the changes
|
||
git add app/version.json app/package.json yarn.lock 2>/dev/null || true
|
||
|
||
# Create commit message based on which platforms were deployed
|
||
COMMIT_MSG="chore: update version files after"
|
||
if [ "${{ needs.build-ios.result }}" = "success" ] && [ "${{ needs.build-android.result }}" = "success" ]; then
|
||
COMMIT_MSG="$COMMIT_MSG iOS and Android deployment"
|
||
elif [ "${{ needs.build-ios.result }}" = "success" ]; then
|
||
COMMIT_MSG="$COMMIT_MSG iOS deployment"
|
||
else
|
||
COMMIT_MSG="$COMMIT_MSG Android deployment"
|
||
fi
|
||
COMMIT_MSG="$COMMIT_MSG [skip ci]"
|
||
|
||
# Commit and push
|
||
git commit -m "$COMMIT_MSG"
|
||
git push
|
||
echo "✅ Committed version file changes"
|
||
fi
|
||
|
||
# Create git tags after successful deployment
|
||
create-release-tags:
|
||
needs: [build-ios, build-android, update-version-files]
|
||
if: |
|
||
always() &&
|
||
needs.update-version-files.result == 'success' &&
|
||
(needs.build-ios.result == 'success' || needs.build-android.result == 'success') &&
|
||
(inputs.deployment_track == 'production' || (github.event_name == 'workflow_dispatch' && github.event.inputs.deployment_track == 'production'))
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
token: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
- name: Configure Git
|
||
run: |
|
||
git config --global user.name "GitHub Actions"
|
||
git config --global user.email "actions@github.com"
|
||
|
||
- name: Create and push tags
|
||
run: |
|
||
cd ${{ env.APP_PATH }}
|
||
|
||
# Read current version info
|
||
VERSION=$(cat package.json | jq -r .version)
|
||
IOS_BUILD=$(cat version.json | jq -r .ios.build)
|
||
ANDROID_BUILD=$(cat version.json | jq -r .android.build)
|
||
|
||
echo "📦 Creating tags for version $VERSION"
|
||
|
||
# Create main version tag
|
||
if ! git tag -l | grep -q "^v${VERSION}$"; then
|
||
git tag -a "v${VERSION}" -m "Release ${VERSION}"
|
||
echo "✅ Created tag: v${VERSION}"
|
||
else
|
||
echo "⏭️ Tag v${VERSION} already exists"
|
||
fi
|
||
|
||
# Create platform-specific tags if deployments succeeded
|
||
if [ "${{ needs.build-ios.result }}" = "success" ]; then
|
||
TAG_NAME="v${VERSION}-ios-${IOS_BUILD}"
|
||
if ! git tag -l | grep -q "^${TAG_NAME}$"; then
|
||
git tag -a "${TAG_NAME}" -m "iOS Release ${VERSION} (Build ${IOS_BUILD})"
|
||
echo "✅ Created tag: ${TAG_NAME}"
|
||
fi
|
||
fi
|
||
|
||
if [ "${{ needs.build-android.result }}" = "success" ]; then
|
||
TAG_NAME="v${VERSION}-android-${ANDROID_BUILD}"
|
||
if ! git tag -l | grep -q "^${TAG_NAME}$"; then
|
||
git tag -a "${TAG_NAME}" -m "Android Release ${VERSION} (Build ${ANDROID_BUILD})"
|
||
echo "✅ Created tag: ${TAG_NAME}"
|
||
fi
|
||
fi
|
||
|
||
# Push all tags
|
||
git push origin --tags
|
||
echo "🚀 Tags pushed to repository"
|
||
|
||
- name: Generate changelog for release
|
||
id: changelog
|
||
run: |
|
||
cd ${{ env.APP_PATH }}
|
||
|
||
# Find the previous version tag
|
||
PREV_TAG=$(git tag -l "v*" | grep -v "-" | sort -V | tail -2 | head -1 || echo "")
|
||
|
||
# Generate simple changelog
|
||
echo "## What's Changed" > release_notes.md
|
||
echo "" >> release_notes.md
|
||
|
||
if [ -n "$PREV_TAG" ]; then
|
||
git log --pretty=format:"- %s" ${PREV_TAG}..HEAD --no-merges | grep -v "^- Merge" >> release_notes.md
|
||
else
|
||
echo "Initial release" >> release_notes.md
|
||
fi
|
||
|
||
echo "" >> release_notes.md
|
||
echo "## Build Information" >> release_notes.md
|
||
echo "- iOS Build: ${IOS_BUILD}" >> release_notes.md
|
||
echo "- Android Build: ${ANDROID_BUILD}" >> release_notes.md
|
||
|
||
# Set output for next step
|
||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||
|
||
- name: Create GitHub Release
|
||
uses: softprops/action-gh-release@v1
|
||
with:
|
||
tag_name: v${{ steps.changelog.outputs.version }}
|
||
name: Release ${{ steps.changelog.outputs.version }}
|
||
body_path: ${{ env.APP_PATH }}/release_notes.md
|
||
draft: false
|
||
prerelease: false
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|