name: Mobile CI env: # Build environment versions # Node version is read from .nvmrc during workflow execution RUBY_VERSION: 3.2 JAVA_VERSION: 17 ANDROID_NDK_VERSION: 27.0.12077973 XCODE_VERSION: 16.4 # Path configuration WORKSPACE: ${{ github.workspace }} APP_PATH: ${{ github.workspace }}/app # Cache versions GH_CACHE_VERSION: v2 # Global cache version - bumped to invalidate caches GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version # Performance optimizations GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=4 -Dorg.gradle.parallel=true -Dorg.gradle.configureondemand=true -Dorg.gradle.caching=true CI: true on: pull_request: branches: - dev - staging - main paths: - "common/**" - "app/**" - "packages/mobile-sdk-alpha/**" - ".github/workflows/mobile-ci.yml" - ".github/actions/**" workflow_dispatch: {} concurrency: group: mobile-ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build-deps: runs-on: ubuntu-latest timeout-minutes: 60 steps: - uses: actions/checkout@v6 - name: Read and sanitize Node.js version shell: bash run: | if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then echo "❌ .nvmrc is missing or empty"; exit 1; fi VERSION="$(tr -d '\r\n' < .nvmrc)" VERSION="${VERSION#v}" if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then echo "Invalid .nvmrc content: '$VERSION'"; exit 1; fi echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV" echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV" - name: Use Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Enable Corepack run: corepack enable - name: Activate Yarn 4.12.0 run: corepack prepare yarn@4.12.0 --activate - name: Cache Yarn uses: ./.github/actions/cache-yarn with: path: | .yarn/cache node_modules app/node_modules cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} - name: Install Dependencies uses: ./.github/actions/yarn-install - name: Cache Built Dependencies id: built-deps uses: ./.github/actions/cache-built-deps with: cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} - name: Build dependencies (cache miss) # Temporarily disabled due to `yarn types` failures when cache is used. # if: steps.built-deps.outputs.cache-hit != 'true' run: | echo "Cache miss for built dependencies. Building now..." yarn workspace @selfxyz/mobile-app run build:deps - name: Run linter run: yarn lint working-directory: ./app - name: Run prettier run: yarn fmt working-directory: ./app - name: Check App Types run: yarn types working-directory: ./app test: runs-on: ubuntu-latest needs: build-deps timeout-minutes: 60 steps: - uses: actions/checkout@v6 - name: Read and sanitize Node.js version shell: bash run: | if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then echo "❌ .nvmrc is missing or empty"; exit 1; fi VERSION="$(tr -d '\r\n' < .nvmrc)" VERSION="${VERSION#v}" if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then echo "Invalid .nvmrc content: '$VERSION'"; exit 1; fi echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV" echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV" - name: Use Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Enable Corepack run: corepack enable - name: Activate Yarn 4.12.0 run: corepack prepare yarn@4.12.0 --activate - name: Cache Yarn uses: ./.github/actions/cache-yarn with: path: | .yarn/cache node_modules app/node_modules cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} - name: Install Dependencies uses: ./.github/actions/yarn-install - name: Cache Built Dependencies id: built-deps uses: ./.github/actions/cache-built-deps with: cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} - name: Debug Cache Restoration run: | echo "Cache hit: ${{ steps.built-deps.outputs.cache-hit }}" echo "Cache key: built-deps-${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('common/**/*', 'packages/mobile-sdk-alpha/**/*', '!common/dist/**', '!packages/mobile-sdk-alpha/dist/**') }}" echo "Checking if cached files exist:" ls -la packages/mobile-sdk-alpha/dist/ || echo "❌ mobile-sdk-alpha dist not found" ls -la packages/mobile-sdk-alpha/dist/cjs/ || echo "❌ mobile-sdk-alpha dist/cjs not found" ls -la packages/mobile-sdk-alpha/dist/cjs/index.cjs || echo "❌ mobile-sdk-alpha dist/cjs/index.cjs not found" ls -la common/dist/ || echo "❌ common dist not found" ls -la common/dist/cjs/ || echo "❌ common dist/cjs not found" ls -la common/dist/cjs/index.cjs || echo "❌ common dist/cjs/index.cjs not found" - name: Build dependencies (always - debugging CI) # Temporarily always build to debug CI issues # TODO: Re-enable cache after fixing: if: steps.built-deps.outputs.cache-hit != 'true' run: | echo "Building dependencies (cache temporarily disabled for debugging)..." yarn workspace @selfxyz/mobile-app run build:deps # Verify build completed successfully if [ ! -f "packages/mobile-sdk-alpha/dist/cjs/index.cjs" ] || [ ! -f "common/dist/cjs/index.cjs" ]; then echo "❌ Build failed - required files missing after build" ls -la packages/mobile-sdk-alpha/dist/ || echo "mobile-sdk-alpha dist not found" ls -la common/dist/ || echo "common dist not found" exit 1 fi echo "✅ Build completed successfully" - name: Force Build Dependencies If Missing run: | # Force build if required files don't exist, regardless of cache status if [ ! -f "packages/mobile-sdk-alpha/dist/cjs/index.cjs" ] || [ ! -f "common/dist/cjs/index.cjs" ]; then echo "❌ Required dependency files missing, forcing rebuild..." echo "Missing files:" [ ! -f "packages/mobile-sdk-alpha/dist/cjs/index.cjs" ] && echo " - packages/mobile-sdk-alpha/dist/cjs/index.cjs" [ ! -f "common/dist/cjs/index.cjs" ] && echo " - common/dist/cjs/index.cjs" yarn workspace @selfxyz/mobile-app run build:deps # Verify build completed successfully if [ ! -f "packages/mobile-sdk-alpha/dist/cjs/index.cjs" ] || [ ! -f "common/dist/cjs/index.cjs" ]; then echo "❌ Forced build failed - required files still missing" ls -la packages/mobile-sdk-alpha/ || echo "packages/mobile-sdk-alpha not found" ls -la packages/mobile-sdk-alpha/dist/ || echo "mobile-sdk-alpha dist not found" ls -la common/ || echo "common not found" ls -la common/dist/ || echo "common dist not found" exit 1 fi echo "✅ Forced build completed successfully" else echo "✅ All required dependency files exist" fi - name: Check for nested require() in tests run: node scripts/check-test-requires.cjs working-directory: ./app - name: App Tests env: # Increase Node.js memory to prevent hermes-parser WASM memory errors NODE_OPTIONS: --max-old-space-size=4096 # Override production NODE_ENV for tests - React's production build doesn't include testing utilities NODE_ENV: test run: | # Final verification from app directory perspective echo "Final verification before running tests (from app directory)..." if [ ! -f "../packages/mobile-sdk-alpha/dist/cjs/index.cjs" ] || [ ! -f "../common/dist/cjs/index.cjs" ]; then echo "❌ Dependencies still not found from app directory" ls -la ../packages/mobile-sdk-alpha/dist/ || echo "mobile-sdk-alpha dist not found" ls -la ../common/dist/ || echo "common dist not found" exit 1 fi echo "✅ All dependencies verified, running tests..." # Run jest through yarn to avoid the build:deps step since CI already built dependencies yarn test:ci working-directory: ./app build-ios: # This is mostly covered in mobile-e2e.yml so we don't need to run it here frequently if: github.event_name == 'workflow_dispatch' # runs-on: macos-latest-large runs-on: namespace-profile-apple-silicon-6cpu needs: build-deps timeout-minutes: 60 env: # iOS project configuration - hardcoded for CI stability IOS_PROJECT_NAME: "Self" IOS_PROJECT_SCHEME: "OpenPassport" steps: - uses: actions/checkout@v6 - name: Read and sanitize Node.js version shell: bash run: | if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then echo "❌ .nvmrc is missing or empty"; exit 1; fi VERSION="$(tr -d '\r\n' < .nvmrc)" VERSION="${VERSION#v}" if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then echo "Invalid .nvmrc content: '$VERSION'"; exit 1; fi echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV" echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV" - name: Use Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Enable Corepack run: corepack enable - name: Activate Yarn 4.12.0 run: corepack prepare yarn@4.12.0 --activate - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: ${{ env.XCODE_VERSION }} - name: Configure Xcode path run: | echo "🔧 Configuring Xcode path to fix iOS SDK issues..." # Fix for macOS 15 runner iOS SDK issues # See: https://github.com/actions/runner-images/issues/12758 sudo xcode-select --switch /Applications/Xcode_${{ env.XCODE_VERSION }}.app echo "✅ Xcode path configured" # Verify Xcode setup echo "Xcode version:" xcodebuild -version echo "Xcode path:" xcode-select -p - name: Check Java installation run: | echo "INSTALL_JAVA=false" >> "$GITHUB_ENV" if command -v java &> /dev/null && java -version &> /dev/null; then echo "Java already installed: $(java -version 2>&1 | head -n 1)" else echo "Java not found or not working, will install..." echo "INSTALL_JAVA=true" >> "$GITHUB_ENV" fi - name: Setup Java environment if: env.INSTALL_JAVA == 'true' uses: actions/setup-java@v4 with: distribution: "temurin" java-version: ${{ env.JAVA_VERSION }} - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ env.RUBY_VERSION }} bundler-cache: false - name: Cache Yarn uses: ./.github/actions/cache-yarn with: path: | .yarn/cache node_modules app/node_modules cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} - name: Cache Ruby gems uses: ./.github/actions/cache-bundler with: path: app/vendor/bundle lock-file: app/Gemfile.lock cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }} - name: Cache Pods uses: ./.github/actions/cache-pods with: path: | app/ios/Pods ~/Library/Caches/CocoaPods lockfile: app/ios/Podfile.lock cache-version: ${{ env.GH_CACHE_VERSION }} - name: Cache Xcode build uses: actions/cache@v4 with: path: | app/ios/build ~/Library/Developer/Xcode/DerivedData ~/Library/Caches/com.apple.dt.Xcode key: ${{ runner.os }}-xcode-${{ env.XCODE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock', 'app/ios/OpenPassport.xcworkspace/contents.xcworkspacedata', 'app/ios/Self.xcworkspace/contents.xcworkspacedata') }} restore-keys: | ${{ runner.os }}-xcode-${{ env.XCODE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock') }}- ${{ runner.os }}-xcode-${{ env.XCODE_VERSION }}- - name: Cache Xcode Index uses: actions/cache@v4 with: path: app/ios/build/Index.noindex key: ${{ runner.os }}-xcode-index-${{ env.XCODE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock') }} restore-keys: | ${{ runner.os }}-xcode-index-${{ env.XCODE_VERSION }}- - name: Generate token for self repositories if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }} uses: ./.github/actions/generate-github-token id: github-token with: app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }} private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }} configure-netrc: "true" - name: Install Mobile Dependencies uses: ./.github/actions/yarn-install - name: Cache Built Dependencies id: built-deps uses: ./.github/actions/cache-built-deps with: cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} - name: Build dependencies (cache miss) # if: steps.built-deps.outputs.cache-hit != 'true' env: SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token || '' }} run: | echo "Cache miss for built dependencies. Building now..." yarn workspace @selfxyz/mobile-app run build:deps - name: Install Ruby Dependencies run: | echo "Installing Ruby dependencies..." bundle config set --local path 'vendor/bundle' bundle install --jobs 4 --retry 3 working-directory: ./app - name: Install iOS Dependencies uses: nick-fields/retry@v3 with: timeout_minutes: 20 max_attempts: 3 retry_wait_seconds: 10 command: | cd app/ios bundle exec bash scripts/pod-install-with-cache-fix.sh env: SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }} - name: Resolve iOS workspace run: | WORKSPACE_OPEN="ios/OpenPassport.xcworkspace" WORKSPACE_SELF="ios/Self.xcworkspace" if xcodebuild -list -workspace "$WORKSPACE_OPEN" 2>/dev/null | grep -q "OpenPassport"; then WORKSPACE_PATH="$WORKSPACE_OPEN" else WORKSPACE_PATH="$WORKSPACE_SELF" fi echo "WORKSPACE_PATH=$WORKSPACE_PATH" >> "$GITHUB_ENV" echo "Resolved workspace: $WORKSPACE_PATH" working-directory: ./app - name: Verify iOS Workspace run: | echo "Verifying iOS workspace setup..." if [ -z "$WORKSPACE_PATH" ]; then echo "❌ WORKSPACE_PATH is not set" exit 1 fi if [ ! -d "$WORKSPACE_PATH" ]; then echo "❌ Workspace not found at: $WORKSPACE_PATH" echo "Available workspaces:" find ios -name "*.xcworkspace" -type d exit 1 fi if [ ! -d "ios/Pods" ]; then echo "❌ Pods directory is missing" exit 1 fi # Verify scheme exists by listing available schemes echo "Verifying scheme availability..." AVAILABLE_SCHEMES=$(xcodebuild -list -workspace "$WORKSPACE_PATH" 2>/dev/null | grep -A 200 "Schemes:" | grep -v "Schemes:" | xargs) echo "Available schemes (first 20): $(echo $AVAILABLE_SCHEMES | cut -d' ' -f1-20)..." if [[ ! "$AVAILABLE_SCHEMES" =~ ${{ env.IOS_PROJECT_SCHEME }} ]]; then echo "❌ Scheme '${{ env.IOS_PROJECT_SCHEME }}' not found" echo "Full scheme list:" xcodebuild -list -workspace "$WORKSPACE_PATH" 2>/dev/null | grep -A 200 "Schemes:" | grep -v "Schemes:" | head -50 exit 1 fi echo "✅ iOS workspace is properly configured" echo "✅ Using workspace: $WORKSPACE_PATH" echo "✅ Using scheme: ${{ env.IOS_PROJECT_SCHEME }}" working-directory: ./app - name: Build iOS run: | echo "Building iOS app for simulator (no signing required)..." echo "Project: ${{ env.IOS_PROJECT_NAME }}, Scheme: ${{ env.IOS_PROJECT_SCHEME }}" # Build for iOS Simulator to avoid code signing issues in CI xcodebuild -workspace "$WORKSPACE_PATH" \ -scheme ${{ env.IOS_PROJECT_SCHEME }} \ -configuration Release \ -sdk iphonesimulator \ -destination "generic/platform=iOS Simulator" \ -derivedDataPath ios/build \ -jobs "$(sysctl -n hw.ncpu)" \ -parallelizeTargets \ -quiet || { echo "❌ iOS build failed"; exit 1; } echo "✅ iOS build succeeded" working-directory: ./app build-android: runs-on: ubuntu-latest needs: build-deps # This is mostly covered in mobile-e2e.yml so we don't need to run it here frequently if: github.event_name == 'workflow_dispatch' timeout-minutes: 60 steps: - uses: actions/checkout@v6 - name: Read and sanitize Node.js version shell: bash run: | if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then echo "❌ .nvmrc is missing or empty"; exit 1; fi VERSION="$(tr -d '\r\n' < .nvmrc)" VERSION="${VERSION#v}" if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then echo "Invalid .nvmrc content: '$VERSION'"; exit 1; fi echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV" echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV" - name: Use Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Enable Corepack run: corepack enable - name: Activate Yarn 4.12.0 run: corepack prepare yarn@4.12.0 --activate - name: Cache Yarn uses: ./.github/actions/cache-yarn with: path: | .yarn/cache node_modules app/node_modules cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} - name: Free up disk space uses: ./.github/actions/free-disk-space - name: Cache Gradle uses: ./.github/actions/cache-gradle with: cache-version: ${{ env.GH_CACHE_VERSION }} - name: Setup Java environment uses: actions/setup-java@v4 with: distribution: "temurin" java-version: ${{ env.JAVA_VERSION }} - name: Setup Android SDK uses: android-actions/setup-android@v3 with: accept-android-sdk-licenses: true - name: Cache NDK id: ndk-cache uses: actions/cache@v4 with: path: ${{ env.ANDROID_HOME }}/ndk/${{ env.ANDROID_NDK_VERSION }} key: ${{ runner.os }}-ndk-${{ env.ANDROID_NDK_VERSION }} - name: Install NDK if: steps.ndk-cache.outputs.cache-hit != 'true' uses: nick-fields/retry@v3 with: timeout_minutes: 15 max_attempts: 3 retry_wait_seconds: 10 command: sdkmanager "ndk;${{ env.ANDROID_NDK_VERSION }}" - name: Install Mobile Dependencies uses: ./.github/actions/yarn-install - name: Cache Built Dependencies id: built-deps uses: ./.github/actions/cache-built-deps with: cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} - name: Build dependencies (cache miss) if: steps.built-deps.outputs.cache-hit != 'true' run: | echo "Cache miss for built dependencies. Building now..." yarn workspace @selfxyz/mobile-app run build:deps - name: Generate token for self repositories if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }} uses: ./.github/actions/generate-github-token id: github-token with: app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }} private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }} - name: Setup Android private modules run: | cd ${{ env.APP_PATH }} PLATFORM=android node scripts/setup-private-modules.cjs env: SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }} CI: true - name: Build Android (with AAPT2 symlink fix) run: yarn android:ci working-directory: ./app - name: Clean up Gradle build artifacts uses: ./.github/actions/cleanup-gradle-artifacts