Files
self/.github/workflows/mobile-ci.yml
Justin Hernandez 6172497abf chore: upgrade yarn to 4.12.0 (#1530)
* use yarn 4.12.0

* upgrade tsx

* update 4.6.0 references to 4.12.0

* update lock file

* update lock file

* update lock
2025-12-25 12:12:38 -08:00

521 lines
21 KiB
YAML

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