diff --git a/.github/actions/configure-keystore/action.yml b/.github/actions/configure-keystore/action.yml new file mode 100644 index 00000000..95355933 --- /dev/null +++ b/.github/actions/configure-keystore/action.yml @@ -0,0 +1,148 @@ +name: 'Configure Keystore' +description: 'Assume an AWS role and fetch a secret into environment variables' + +inputs: + aws-role-to-assume: + description: 'The AWS IAM role to assume' + required: true + aws-region: + description: 'The AWS region where the secret is stored' + required: true + secret-name: + description: 'The name of the secret in AWS Secrets Manager' + required: true + platform: + description: 'The platform for which the keystore is being configured (e.g., ios, android)' + required: true + target: + description: 'The target for which the keystore is being configured (e.g., qa, flask, main)' + required: true + +runs: + using: 'composite' + steps: + - name: Determine signing secret name + shell: bash + run: | + case "${{ inputs.target }}" in + qa) + SECRET_NAME="metamask-mobile-qa-signing-certificates" + ;; + flask) + SECRET_NAME="metamask-mobile-flask-signing-certificates" + ;; + main) + SECRET_NAME="metamask-mobile-main-signing-certificates" + ;; + *) + echo "❌ Unknown target: ${{ inputs.target }}" + exit 1 + ;; + esac + echo "AWS_SIGNING_CERT_SECRET_NAME=$SECRET_NAME" >> "$GITHUB_ENV" + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ inputs.aws-role-to-assume }} + aws-region: ${{ inputs.aws-region }} + + - name: Fetch secret and export as environment variables + shell: bash + run: | + echo "🔐 Fetching secret from Secrets Manager..." + secret_json=$(aws secretsmanager get-secret-value \ + --region "${{ inputs.aws-region }}" \ + --secret-id "${AWS_SIGNING_CERT_SECRET_NAME}" \ + --query SecretString \ + --output text) + + keys=$(echo "$secret_json" | jq -r 'keys[]') + for key in $keys; do + value=$(echo "$secret_json" | jq -r --arg k "$key" '.[$k]') + echo "::add-mask::$value" + echo "$key=$(printf '%s' "$value")" >> "$GITHUB_ENV" + echo "✅ Set secret for key: $key" + done + + - name: Configure Android Signing Certificates + if: inputs.platform == 'android' + shell: bash + run: | + echo "📦 Configuring Android keystore..." + if [[ -z "$ANDROID_KEYSTORE" ]]; then + echo "⚠️ ANDROID_KEYSTORE is not set. Skipping keystore decoding." + exit 1 + fi + + # Use provided path if set, fallback to default + KEYSTORE_PATH="${ANDROID_KEYSTORE_PATH:-/tmp/android.keystore}" + echo "$ANDROID_KEYSTORE" | base64 --decode > "$KEYSTORE_PATH" + echo "✅ Android keystore written to $KEYSTORE_PATH" + + - name: Configure iOS Signing Certificates + if: inputs.platform == 'ios' + shell: bash + run: | + echo "📦 Configuring iOS code signing..." + + # Create paths + CERT_PATH="$RUNNER_TEMP/build_certificate.p12" + PROFILE_PATH="$RUNNER_TEMP/build_pp.mobileprovision" + KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" + CERT_PW="${IOS_SIGNING_KEYSTORE_PASSWORD}" + + # Decode base64 files + echo "$IOS_SIGNING_KEYSTORE" | base64 --decode > "$CERT_PATH" + echo "$IOS_SIGNING_PROFILE" | base64 --decode > "$PROFILE_PATH" + echo "✅ Decoded .p12 and provisioning profile" + + # Create and unlock keychain + security create-keychain -p "$CERT_PW" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$CERT_PW" "$KEYCHAIN_PATH" + + # Import cert + echo "🔐 Importing certificate..." + if ! security import "$CERT_PATH" -P "$CERT_PW" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"; then + echo "❌ Failed to import certificate. Check if the password is correct or the .p12 is valid." + exit 1 + fi + echo "✅ Certificate imported" + + # Set key partition list + echo "🔑 Setting key partition list..." + if ! security set-key-partition-list -S apple-tool:,apple: -k "$CERT_PW" "$KEYCHAIN_PATH" 2>/dev/null; then + echo "❌ Failed to set key partition list. Codesigning tools may not have access." + exit 1 + fi + echo "✅ Key partition list set" + + + # Verify signing identities + echo "🔍 Verifying code signing identities in keychain..." + IDENTITIES=$(security find-identity -p codesigning "$KEYCHAIN_PATH") + + if ! echo "$IDENTITIES" | grep -q "Valid identities"; then + echo "❌ No valid code signing identities found in keychain." + echo "$IDENTITIES" + exit 1 + fi + + # Extract and print alias (first CN string) + CERT_ALIAS=$(echo "$IDENTITIES" | awk -F '"' '/"Apple/ {print $2; exit}') + if [[ -n "$CERT_ALIAS" ]]; then + echo "✅ Code signing identity available: $CERT_ALIAS" + else + echo "✅ Code signing identity is available (alias not parsed)" + fi + + # Install provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/ + echo "✅ Installed provisioning profile" + + echo "Configuring default keychain" + security default-keychain -s "$KEYCHAIN_PATH" + echo "✅ default keychain set" + diff --git a/.github/actions/setup-e2e-env-yarn-v3/action.yml b/.github/actions/setup-e2e-env-yarn-v3/action.yml new file mode 100644 index 00000000..aacdc51c --- /dev/null +++ b/.github/actions/setup-e2e-env-yarn-v3/action.yml @@ -0,0 +1,339 @@ +name: 'Setup E2E Test Environment' +description: 'Sets up the environment for running E2E tests' + +inputs: + platform: + description: 'Platform (ios or android)' + required: true + node-version: + description: 'Node.js version' + required: false + default: '20.18.0' + yarn-version: + description: Yarn version to use with Corepack + required: false + default: '3.8.7' + setup-simulator: + description: 'Whether to setup simulator/emulator' + required: false + default: 'false' + bundler-version: + description: 'Bundler version to use (only for iOS)' + required: false + default: '2.5.8' + cache-prefix: + description: 'Cache key prefix' + required: false + default: 'e2e' + ruby-version: + description: Ruby version to use (only for iOS) + required: false + default: '3.1' + xcode-version: + description: Xcode version to select (e.g., 16.2) + required: false + default: '16.2' + jdk-version: + description: JDK version to use (only for Android) + required: false + default: '17' + jdk-distribution: + description: JDK distribution to use (only for Android) + required: false + default: 'temurin' + foundry-version: + description: Foundry version to install + required: false + default: 'v1.2.3' + android-avd-name: + description: 'Name of AVD to create and boot (for Android)' + required: false + default: 'test_e2e_avd' + android-device: + description: 'AVD device profile (e.g. "pixel_5", "pixel", "Nexus 6")' + required: false + default: 'pixel_5' + android-api-level: + description: 'Android API level to use (e.g. "34")' + required: false + default: '34' + android-abi: + description: 'System architecture ABI for the Android system image (e.g. x86_64, arm64-v8a, armeabi-v7a)' + required: false + default: 'x86_64' + android-tag: + description: 'Android system image tag (e.g. google_apis, default)' + required: false + default: 'google_apis' + android-sdcard-size: + description: 'SD card size for AVD (e.g. 8092M)' + required: false + default: '8092M' + configure-keystores: + description: 'Whether to configure keystores for E2E tests' + required: false + default: 'true' + keystore-role-to-assume: + description: 'AWS IAM role to assume for keystore configuration' + required: false + default: 'arn:aws:iam::363762752069:role/metamask-mobile-build-signer-qa' + target: + description: 'Target for which the keystore is being configured (e.g., qa, flask, main)' + required: false + default: 'qa' + +runs: + using: 'composite' + steps: + ## Common Setup ## + - run: echo "Setup E2E Environment started" + shell: bash + + ## Android Setup (early for fail-fast) ## + + # Set Android environment variables (self-hosted runner has SDK pre-installed) + - name: Set Android environment variables + if: ${{ inputs.platform == 'android' }} + run: | + echo "ANDROID_HOME=/opt/android-sdk" >> "$GITHUB_ENV" + echo "ANDROID_SDK_ROOT=/opt/android-sdk" >> "$GITHUB_ENV" + shell: bash + + - name: Configure Android Signing Certificates + if: ${{ inputs.platform == 'android' && inputs.configure-keystores == 'true' }} + uses: MetaMask/github-tools/.github/actions/configure-keystore@e2e-env-actions + with: + aws-role-to-assume: ${{ inputs.keystore-role-to-assume }} + aws-region: 'us-east-2' + platform: 'android' + target: ${{ inputs.target }} + + ## JDK Setup + - name: Setup Java + if: ${{ inputs.platform == 'android' }} + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 + with: + java-version: ${{ inputs.jdk-version }} + distribution: ${{ inputs.jdk-distribution }} + + - name: Install required emulator dependencies + if: ${{ inputs.platform == 'android' && runner.os == 'Linux' }} + run: | + sudo apt-get update + sudo apt-get install -y \ + libpulse0 \ + libglu1-mesa \ + libnss3 \ + libxss1 + + echo "✅ Linux dependencies installed successfully" + shell: bash + + ## Android SDK Setup (SDK pre-installed in container) + + - name: Install additional Android SDK components if needed + if: ${{ inputs.platform == 'android' && (inputs.android-api-level != '34' || inputs.android-abi != 'x86_64') }} + run: | + # Only install if different from pre-installed defaults (API 34, x86_64) + IMAGE="system-images;android-${{ inputs.android-api-level }};google_apis;${{ inputs.android-abi }}" + echo "Installing additional system image: $IMAGE" + echo "y" | "/opt/android-sdk/cmdline-tools/latest/bin/sdkmanager" "$IMAGE" + shell: bash + + ## Launch AVD + + - name: Set ANDROID_AVD_HOME for downstream steps + if: ${{ inputs.platform == 'android'}} + shell: bash + run: | + echo "ANDROID_AVD_HOME=$HOME/.android/avd" >> "$GITHUB_ENV" + mkdir -p "$HOME/.android/avd" + + - name: Create Android Virtual Device (AVD) + if: ${{ inputs.platform == 'android'}} + run: | + IMAGE="system-images;android-${{ inputs.android-api-level }};${{ inputs.android-tag }};${{ inputs.android-abi }}" + echo "Creating AVD with image: $IMAGE" + "/opt/android-sdk/cmdline-tools/latest/bin/avdmanager" --verbose create avd \ + --force \ + --name "${{ inputs.android-avd-name }}" \ + --package "$IMAGE" \ + --device "${{ inputs.android-device }}" \ + --tag "${{ inputs.android-tag }}" \ + --abi "${{ inputs.android-abi }}" \ + --sdcard "${{ inputs.android-sdcard-size }}" + shell: bash + + ## iOS Platform Setup ## + + - name: Configure iOS Signing Certificates + if: ${{ inputs.platform == 'ios' && inputs.configure-keystores == 'true' }} + uses: MetaMask/github-tools/.github/actions/configure-keystore@self-hosted-runners-config + with: + aws-role-to-assume: ${{ inputs.keystore-role-to-assume }} + aws-region: 'us-east-2' + platform: 'ios' + target: ${{ inputs.target }} + + ## Node.js & JavaScript Dependencies Setup ## + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + + ## Yarn Setup & Cache Management + + - name: Corepack + id: corepack + run: corepack enable && corepack prepare yarn@${{ inputs.yarn-version }} --activate + shell: bash + + - name: Restore Yarn cache + uses: actions/cache@v4 + with: + path: | + node_modules + key: ${{ inputs.cache-prefix }}-yarn-${{ inputs.platform }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ inputs.cache-prefix }}-yarn-${{ inputs.platform }}-${{ runner.os }}- + + - name: Install JavaScript dependencies with retry + id: yarn-install + uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 + with: + timeout_minutes: 15 + max_attempts: 3 + retry_wait_seconds: 30 + command: yarn install --immutable + env: + NODE_OPTIONS: --max-old-space-size=4096 # Increase memory limit for Node.js due to large dependencies + + - name: Verify Detox CLI + id: install-detox-cli + uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 + with: + timeout_minutes: 15 + max_attempts: 3 + retry_wait_seconds: 30 + command: yarn detox --version + + - name: Install Foundry + shell: bash + run: | + echo "Installing Foundry via foundryup..." + + export FOUNDRY_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/.foundry" + export FOUNDRY_BIN="$FOUNDRY_DIR/bin" + + mkdir -p "$FOUNDRY_BIN" + + curl -sL https://raw.githubusercontent.com/foundry-rs/foundry/master/foundryup/foundryup -o "$FOUNDRY_BIN/foundryup" + chmod +x "$FOUNDRY_BIN/foundryup" + + echo "$FOUNDRY_BIN" >> "$GITHUB_PATH" + + "$FOUNDRY_BIN/foundryup" + + ## IOS Setup ## + + ## Ruby Setup & Cache Management + - name: Setup Ruby + if: ${{ inputs.platform == 'ios' }} + uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 + with: + ruby-version: ${{ inputs.ruby-version }} + + # Install Bundler first + - name: Install bundler + if: ${{ inputs.platform == 'ios' }} + run: gem install bundler -v ${{ inputs.bundler-version }} + working-directory: ios + shell: bash + + # Restore cached Ruby gems + - name: Restore Bundler cache + if: ${{ inputs.platform == 'ios' }} + uses: actions/cache@v4 + with: + path: ios/vendor/bundle + key: ${{ inputs.cache-prefix }}-bundler-${{ inputs.platform }}-${{ runner.os }}-${{ hashFiles('ios/Gemfile.lock') }} + restore-keys: | + ${{ inputs.cache-prefix }}-bundler-${{ inputs.platform }}-${{ runner.os }}- + + # Configure bundler to use a specific path for gem installation + - name: Configure bundler install path + if: ${{ inputs.platform == 'ios' }} + run: bundle config set path 'vendor/bundle' + working-directory: ios + shell: bash + + # Install Ruby gems into ios/vendor/bundle ( cache management & awareness ) + - name: Install Ruby gems via bundler + if: ${{ inputs.platform == 'ios' }} + run: bundle install + working-directory: ios + shell: bash + + - name: Generate binstubs for CocoaPods + if: ${{ inputs.platform == 'ios' }} + run: bundle binstubs cocoapods --force --path=vendor/bundle/bin + + working-directory: ios + shell: bash + + - name: Add binstubs to PATH + if: ${{ inputs.platform == 'ios' }} + run: echo "$(pwd)/ios/vendor/bundle/bin" >> "$GITHUB_PATH" + shell: bash + + # Verify CocoaPods is available + - name: Verify CocoaPods + if: ${{ inputs.platform == 'ios' }} + run: | + bundle show cocoapods || (echo "❌ CocoaPods not installed from ios/Gemfile" && exit 1) + bundle exec pod --version + working-directory: ios + shell: bash + + # Verify CocoaPods is available + - name: Verify CocoaPods BinStub + if: ${{ inputs.platform == 'ios' }} + run: | + bundle show cocoapods || (echo "❌ CocoaPods not installed from ios/Gemfile" && exit 1) + pod --version + working-directory: ios + shell: bash + + # Select Xcode version + - name: Select Xcode version + if: ${{ inputs.platform == 'ios' }} + run: sudo xcode-select -s /Applications/Xcode_${{ inputs.xcode-version }}.app + shell: bash + + # Restore CocoaPods cache + # - name: Restore CocoaPods cache + # if: ${{ inputs.platform == 'ios'}} + # uses: actions/cache@v4 + # with: + # path: ios/Pods + # key: ${{ inputs.cache-prefix }}-pods-${{ inputs.platform }}-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }} + # restore-keys: | + # ${{ inputs.cache-prefix }}-pods-${{ inputs.platform }}-${{ runner.os }}- + + # Install CocoaPods w/ cached bundler environment + - name: Install CocoaPods via bundler + if: ${{ inputs.platform == 'ios'}} + run: bundle exec pod install --repo-update + working-directory: ios + shell: bash + + - name: Install applesimutils + if: ${{ inputs.platform == 'ios' }} + run: brew tap wix/brew && brew install applesimutils + shell: bash + + - name: Check simutils + if: ${{ inputs.platform == 'ios' }} + run: xcrun simctl list devices + shell: bash diff --git a/.github/actions/setup-e2e-env/action.yml b/.github/actions/setup-e2e-env/action.yml new file mode 100644 index 00000000..b01c0ccd --- /dev/null +++ b/.github/actions/setup-e2e-env/action.yml @@ -0,0 +1,348 @@ +name: 'Setup E2E Test Environment' +description: 'Sets up the environment for running E2E tests' + +inputs: + platform: + description: 'Platform (ios or android)' + required: true + node-version: + description: 'Node.js version' + required: false + default: '20.18.0' + yarn-version: + description: Yarn version to use with Corepack + required: false + default: '1.22.22' + setup-simulator: + description: 'Whether to setup simulator/emulator' + required: false + default: 'false' + bundler-version: + description: 'Bundler version to use (only for iOS)' + required: false + default: '2.5.8' + cache-prefix: + description: 'Cache key prefix' + required: false + default: 'e2e' + ruby-version: + description: Ruby version to use (only for iOS) + required: false + default: '3.1' + xcode-version: + description: Xcode version to select (e.g., 16.2) + required: false + default: '16.2' + jdk-version: + description: JDK version to use (only for Android) + required: false + default: '17' + jdk-distribution: + description: JDK distribution to use (only for Android) + required: false + default: 'temurin' + foundry-version: + description: Foundry version to install + required: false + default: 'v1.2.3' + android-avd-name: + description: 'Name of AVD to create and boot (for Android)' + required: false + default: 'test_e2e_avd' + android-device: + description: 'AVD device profile (e.g. "pixel_5", "pixel", "Nexus 6")' + required: false + default: 'pixel_5' + android-api-level: + description: 'Android API level to use (e.g. "34")' + required: false + default: '34' + android-abi: + description: 'System architecture ABI for the Android system image (e.g. x86_64, arm64-v8a, armeabi-v7a)' + required: false + default: 'x86_64' + android-tag: + description: 'Android system image tag (e.g. google_apis, default)' + required: false + default: 'google_apis' + android-sdcard-size: + description: 'SD card size for AVD (e.g. 8092M)' + required: false + default: '8092M' + configure-keystores: + description: 'Whether to configure keystores for E2E tests' + required: false + default: 'true' + keystore-role-to-assume: + description: 'AWS IAM role to assume for keystore configuration' + required: false + default: 'arn:aws:iam::363762752069:role/metamask-mobile-build-signer-qa' + target: + description: 'Target for which the keystore is being configured (e.g., qa, flask, main)' + required: false + default: 'qa' + +runs: + using: 'composite' + steps: + ## Common Setup ## + - run: echo "Setup E2E Environment started" + shell: bash + + ## Android Setup (early for fail-fast) ## + + # Set Android environment variables (self-hosted runner has SDK pre-installed) + - name: Set Android environment variables + if: ${{ inputs.platform == 'android' }} + run: | + echo "ANDROID_HOME=/opt/android-sdk" >> "$GITHUB_ENV" + echo "ANDROID_SDK_ROOT=/opt/android-sdk" >> "$GITHUB_ENV" + shell: bash + + - name: Configure Android Signing Certificates + if: ${{ inputs.platform == 'android' && inputs.configure-keystores == 'true' }} + uses: MetaMask/github-tools/.github/actions/configure-keystore@e2e-env-actions + with: + aws-role-to-assume: ${{ inputs.keystore-role-to-assume }} + aws-region: 'us-east-2' + platform: 'android' + target: ${{ inputs.target }} + + ## JDK Setup + - name: Setup Java + if: ${{ inputs.platform == 'android' }} + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 + with: + java-version: ${{ inputs.jdk-version }} + distribution: ${{ inputs.jdk-distribution }} + + - name: Install required emulator dependencies + if: ${{ inputs.platform == 'android' && runner.os == 'Linux' }} + run: | + sudo apt-get update + sudo apt-get install -y \ + libpulse0 \ + libglu1-mesa \ + libnss3 \ + libxss1 + + echo "✅ Linux dependencies installed successfully" + shell: bash + + ## Android SDK Setup (SDK pre-installed in container) + + - name: Install additional Android SDK components if needed + if: ${{ inputs.platform == 'android' && (inputs.android-api-level != '34' || inputs.android-abi != 'x86_64') }} + run: | + # Only install if different from pre-installed defaults (API 34, x86_64) + IMAGE="system-images;android-${{ inputs.android-api-level }};google_apis;${{ inputs.android-abi }}" + echo "Installing additional system image: $IMAGE" + echo "y" | "/opt/android-sdk/cmdline-tools/latest/bin/sdkmanager" "$IMAGE" + shell: bash + + ## Launch AVD + + - name: Set ANDROID_AVD_HOME for downstream steps + if: ${{ inputs.platform == 'android'}} + shell: bash + run: | + echo "ANDROID_AVD_HOME=$HOME/.android/avd" >> "$GITHUB_ENV" + mkdir -p "$HOME/.android/avd" + + - name: Create Android Virtual Device (AVD) + if: ${{ inputs.platform == 'android'}} + run: | + IMAGE="system-images;android-${{ inputs.android-api-level }};${{ inputs.android-tag }};${{ inputs.android-abi }}" + echo "Creating AVD with image: $IMAGE" + "/opt/android-sdk/cmdline-tools/latest/bin/avdmanager" --verbose create avd \ + --force \ + --name "${{ inputs.android-avd-name }}" \ + --package "$IMAGE" \ + --device "${{ inputs.android-device }}" \ + --tag "${{ inputs.android-tag }}" \ + --abi "${{ inputs.android-abi }}" \ + --sdcard "${{ inputs.android-sdcard-size }}" + shell: bash + + ## iOS Platform Setup ## + + - name: Configure iOS Signing Certificates + if: ${{ inputs.platform == 'ios' && inputs.configure-keystores == 'true' }} + uses: MetaMask/github-tools/.github/actions/configure-keystore@self-hosted-runners-config + with: + aws-role-to-assume: ${{ inputs.keystore-role-to-assume }} + aws-region: 'us-east-2' + platform: 'ios' + target: ${{ inputs.target }} + + ## Node.js & JavaScript Dependencies Setup ## + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + + ## Yarn Setup & Cache Management + + # - name: Corepack + # id: corepack + # run: corepack enable && corepack prepare yarn@${{ inputs.yarn-version }} --activate + # shell: bash + + - name: Corepack + id: corepack + uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 + with: + timeout_minutes: 15 + max_attempts: 3 + retry_wait_seconds: 30 + command: corepack enable && corepack prepare yarn@${{ inputs.yarn-version }} --activate + + - name: Restore Yarn cache + uses: actions/cache@v4 + with: + path: | + node_modules + key: ${{ inputs.cache-prefix }}-yarn-${{ inputs.platform }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ inputs.cache-prefix }}-yarn-${{ inputs.platform }}-${{ runner.os }}- + + - name: Install JavaScript dependencies with retry + id: yarn-install + uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 + with: + timeout_minutes: 15 + max_attempts: 3 + retry_wait_seconds: 30 + command: yarn install --frozen-lockfile + env: + NODE_OPTIONS: --max-old-space-size=4096 # Increase memory limit for Node.js due to large dependencies + + - name: Install Detox CLI with retry + id: install-detox-cli + uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2 + with: + timeout_minutes: 15 + max_attempts: 3 + retry_wait_seconds: 30 + command: yarn global add detox-cli + + - name: Install Foundry + shell: bash + run: | + echo "Installing Foundry via foundryup..." + + export FOUNDRY_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/.foundry" + export FOUNDRY_BIN="$FOUNDRY_DIR/bin" + + mkdir -p "$FOUNDRY_BIN" + + curl -sL https://raw.githubusercontent.com/foundry-rs/foundry/master/foundryup/foundryup -o "$FOUNDRY_BIN/foundryup" + chmod +x "$FOUNDRY_BIN/foundryup" + + echo "$FOUNDRY_BIN" >> "$GITHUB_PATH" + + "$FOUNDRY_BIN/foundryup" + + ## IOS Setup ## + + ## Ruby Setup & Cache Management + - name: Setup Ruby + if: ${{ inputs.platform == 'ios' }} + uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 + with: + ruby-version: ${{ inputs.ruby-version }} + + # Install Bundler first + - name: Install bundler + if: ${{ inputs.platform == 'ios' }} + run: gem install bundler -v ${{ inputs.bundler-version }} + working-directory: ios + shell: bash + + # Restore cached Ruby gems + - name: Restore Bundler cache + if: ${{ inputs.platform == 'ios' }} + uses: actions/cache@v4 + with: + path: ios/vendor/bundle + key: ${{ inputs.cache-prefix }}-bundler-${{ inputs.platform }}-${{ runner.os }}-${{ hashFiles('ios/Gemfile.lock') }} + restore-keys: | + ${{ inputs.cache-prefix }}-bundler-${{ inputs.platform }}-${{ runner.os }}- + + # Configure bundler to use a specific path for gem installation + - name: Configure bundler install path + if: ${{ inputs.platform == 'ios' }} + run: bundle config set path 'vendor/bundle' + working-directory: ios + shell: bash + + # Install Ruby gems into ios/vendor/bundle ( cache management & awareness ) + - name: Install Ruby gems via bundler + if: ${{ inputs.platform == 'ios' }} + run: bundle install + working-directory: ios + shell: bash + + - name: Generate binstubs for CocoaPods + if: ${{ inputs.platform == 'ios' }} + run: bundle binstubs cocoapods --force --path=vendor/bundle/bin + + working-directory: ios + shell: bash + + - name: Add binstubs to PATH + if: ${{ inputs.platform == 'ios' }} + run: echo "$(pwd)/ios/vendor/bundle/bin" >> "$GITHUB_PATH" + shell: bash + + # Verify CocoaPods is available + - name: Verify CocoaPods + if: ${{ inputs.platform == 'ios' }} + run: | + bundle show cocoapods || (echo "❌ CocoaPods not installed from ios/Gemfile" && exit 1) + bundle exec pod --version + working-directory: ios + shell: bash + + # Verify CocoaPods is available + - name: Verify CocoaPods BinStub + if: ${{ inputs.platform == 'ios' }} + run: | + bundle show cocoapods || (echo "❌ CocoaPods not installed from ios/Gemfile" && exit 1) + pod --version + working-directory: ios + shell: bash + + # Select Xcode version + - name: Select Xcode version + if: ${{ inputs.platform == 'ios' }} + run: sudo xcode-select -s /Applications/Xcode_${{ inputs.xcode-version }}.app + shell: bash + + # Restore CocoaPods cache + # - name: Restore CocoaPods cache + # if: ${{ inputs.platform == 'ios'}} + # uses: actions/cache@v4 + # with: + # path: ios/Pods + # key: ${{ inputs.cache-prefix }}-pods-${{ inputs.platform }}-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }} + # restore-keys: | + # ${{ inputs.cache-prefix }}-pods-${{ inputs.platform }}-${{ runner.os }}- + + # Install CocoaPods w/ cached bundler environment + - name: Install CocoaPods via bundler + if: ${{ inputs.platform == 'ios'}} + run: bundle exec pod install --repo-update + working-directory: ios + shell: bash + + - name: Install applesimutils + if: ${{ inputs.platform == 'ios' }} + run: brew tap wix/brew && brew install applesimutils + shell: bash + + - name: Check simutils + if: ${{ inputs.platform == 'ios' }} + run: xcrun simctl list devices + shell: bash