diff --git a/.github/actions/prepare-hermes-v1-app/action.yml b/.github/actions/prepare-hermes-v1-app/action.yml new file mode 100644 index 0000000..1db5c89 --- /dev/null +++ b/.github/actions/prepare-hermes-v1-app/action.yml @@ -0,0 +1,42 @@ +name: prepare-hermes-v1-app +description: Prepares a React Native app with Hermes V1 enabled +inputs: + retry-count: + description: 'Number of times to retry the yarn install on failure' +runs: + using: composite + steps: + - name: Create new app + shell: bash + run: | + cd /tmp + npx @react-native-community/cli init RNApp --skip-install --version nightly + + - name: Apply patch if specified + shell: bash + run: | + if [ -f "$GITHUB_WORKSPACE/patches/hermes-v1.patch" ]; then + echo "Applying patch: hermes-v1.patch" + cd /tmp/RNApp + node "$GITHUB_WORKSPACE/scripts/apply-patch.js" "$GITHUB_WORKSPACE/patches/hermes-v1.patch" + echo "✅ Patch applied successfully" + else + echo "⚠️ Warning: Patch file not found: patches/hermes-v1.patch" + exit 1 + fi + + - name: Install app dependencies with retry + uses: nick-fields/retry@v3 + with: + timeout_minutes: 10 + max_attempts: ${{ inputs.retry-count }} + retry_wait_seconds: 15 + shell: bash + command: | + cd /tmp/RNApp + yarn install + on_retry_command: | + echo "Cleaning up for yarn retry..." + cd /tmp/RNApp + rm -rf node_modules yarn.lock || true + yarn cache clean || true diff --git a/.github/actions/setup-maestro/action.yml b/.github/actions/setup-maestro/action.yml new file mode 100644 index 0000000..82d6ca0 --- /dev/null +++ b/.github/actions/setup-maestro/action.yml @@ -0,0 +1,21 @@ +name: setup-maestro +description: Sets up the Maestro testing environment +runs: + using: composite + steps: + - name: Setup Java + uses: actions/setup-java@v2 + with: + java-version: '17' + distribution: 'zulu' + + - name: Install Maestro + shell: bash + run: export MAESTRO_VERSION=1.40.0; curl -Ls "https://get.maestro.mobile.dev" | bash + + - name: Install Maestro dependencies + if: runner.os == 'macOS' + shell: bash + run: | + brew tap facebook/fb + brew install facebook/fb/idb-companion diff --git a/.github/workflow-scripts/e2e/.maestro/start.yml b/.github/workflow-scripts/e2e/.maestro/start.yml new file mode 100644 index 0000000..7979d56 --- /dev/null +++ b/.github/workflow-scripts/e2e/.maestro/start.yml @@ -0,0 +1,4 @@ +appId: ${APP_ID} # iOS: org.reactjs.native.example.RNApp | Android: com.rnapp +--- +- launchApp +- assertVisible: "Welcome to React Native" diff --git a/.github/workflow-scripts/maestro-android.js b/.github/workflow-scripts/maestro-android.js new file mode 100644 index 0000000..06dc765 --- /dev/null +++ b/.github/workflow-scripts/maestro-android.js @@ -0,0 +1,129 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +const childProcess = require('child_process'); +const fs = require('fs'); + +const usage = ` +=== Usage === +node maestro-android.js + +@param {string} appPath - Path to the app APK +@param {string} appId - App ID that needs to be launched +@param {string} maestroFlow - Path to the maestro flow to be executed +@param {string} flavor - Flavor of the app to be launched. Can be 'release' or 'debug' +@param {string} workingDirectory - Working directory from where to run Metro +============== +`; + +const args = process.argv.slice(2); + +if (args.length !== 5) { + throw new Error(`Invalid number of arguments.\n${usage}`); +} + +const APP_PATH = args[0]; +const APP_ID = args[1]; +const MAESTRO_FLOW = args[2]; +const IS_DEBUG = args[3] === 'debug'; +const WORKING_DIRECTORY = args[4]; + +const MAX_ATTEMPTS = 3; + +async function executeFlowWithRetries(flow, currentAttempt) { + try { + console.info(`Executing flow: ${flow}`); + const timeout = 1000 * 60 * 10; // 10 minutes + childProcess.execSync( + `MAESTRO_DRIVER_STARTUP_TIMEOUT=120000 $HOME/.maestro/bin/maestro test ${flow} --format junit -e APP_ID=${APP_ID} --debug-output /tmp/MaestroLogs`, + {stdio: 'inherit', timeout}, + ); + } catch (err) { + if (currentAttempt < MAX_ATTEMPTS) { + console.info(`Retrying...`); + await executeFlowWithRetries(flow, currentAttempt + 1); + } else { + throw err; + } + } +} + +async function executeFlowInFolder(flowFolder) { + const files = fs.readdirSync(flowFolder); + for (const file of files) { + const filePath = `${flowFolder}/${file}`; + if (fs.lstatSync(filePath).isDirectory()) { + await executeFlowInFolder(filePath); + } else { + await executeFlowWithRetries(filePath, 0); + } + } +} + +async function main() { + console.info('\n=============================='); + console.info('Running tests for Android with the following parameters:'); + console.info(`APP_PATH: ${APP_PATH}`); + console.info(`APP_ID: ${APP_ID}`); + console.info(`MAESTRO_FLOW: ${MAESTRO_FLOW}`); + console.info(`IS_DEBUG: ${IS_DEBUG}`); + console.info(`WORKING_DIRECTORY: ${WORKING_DIRECTORY}`); + console.info('==============================\n'); + + console.info('Install app'); + childProcess.execSync(`adb install ${APP_PATH}`, {stdio: 'ignore'}); + + console.info('Start the app'); + childProcess.execSync(`adb shell monkey -p ${APP_ID} 1`, {stdio: 'ignore'}); + + if (IS_DEBUG) { + console.info('Wait For App to warm from Metro'); + await sleep(25000); + } + + console.info('Start recording to /sdcard/screen.mp4'); + childProcess + .exec('adb shell screenrecord /sdcard/screen.mp4', { + stdio: 'ignore', + detached: true, + }) + .unref(); + + console.info(`Start testing ${MAESTRO_FLOW}`); + let error = null; + try { + //check if MAESTRO_FLOW is a folder + if ( + fs.existsSync(MAESTRO_FLOW) && + fs.lstatSync(MAESTRO_FLOW).isDirectory() + ) { + await executeFlowInFolder(MAESTRO_FLOW); + } else { + await executeFlowWithRetries(MAESTRO_FLOW, 0); + } + } catch (err) { + error = err; + } finally { + console.info('Stop recording'); + childProcess.execSync('adb pull /sdcard/screen.mp4', {stdio: 'ignore'}); + } + + if (error) { + throw error; + } + process.exit(); +} + +function sleep(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +main(); diff --git a/.github/workflow-scripts/maestro-ios.js b/.github/workflow-scripts/maestro-ios.js new file mode 100644 index 0000000..50851a0 --- /dev/null +++ b/.github/workflow-scripts/maestro-ios.js @@ -0,0 +1,178 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +const childProcess = require('child_process'); +const fs = require('fs'); + +const usage = ` +=== Usage === +node maestro-android.js + +@param {string} appPath - Path to the app APK +@param {string} appId - App ID that needs to be launched +@param {string} maestroFlow - Path to the maestro flow to be executed +@param {string} jsengine - The JSEngine to use for the test +@param {string} flavor - Flavor of the app to be launched. Can be 'Release' or 'Debug' +@param {string} workingDirectory - Working directory from where to run Metro +============== +`; + +const args = process.argv.slice(2); + +if (args.length !== 6) { + throw new Error(`Invalid number of arguments.\n${usage}`); +} + +const APP_PATH = args[0]; +const APP_ID = args[1]; +const MAESTRO_FLOW = args[2]; +const JS_ENGINE = args[3]; +const IS_DEBUG = args[4] === 'Debug'; +const WORKING_DIRECTORY = args[5]; + +const MAX_ATTEMPTS = 5; + +function launchSimulator(simulatorName) { + console.log(`Launching simulator ${simulatorName}`); + try { + childProcess.execSync(`xcrun simctl boot "${simulatorName}"`); + } catch (error) { + if ( + !error.message.includes('Unable to boot device in current state: Booted') + ) { + throw error; + } + } +} + +function installAppOnSimulator(appPath) { + console.log(`Installing app at path ${appPath}`); + childProcess.execSync(`xcrun simctl install booted "${appPath}"`); +} + +function extractSimulatorUDID() { + console.log('Retrieving device UDID'); + const command = `xcrun simctl list devices booted -j | jq -r '[.devices[]] | add | first | .udid'`; + const udid = String(childProcess.execSync(command)).trim(); + console.log(`UDID is ${udid}`); + return udid; +} + +function bringSimulatorInForeground() { + console.log('Bringing simulator in foreground'); + childProcess.execSync('open -a simulator'); +} + +function sleep(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +async function launchAppOnSimulator(appId, udid, isDebug) { + console.log('Launch the app'); + childProcess.execSync(`xcrun simctl launch "${udid}" "${appId}"`); + + if (isDebug) { + console.log('Wait for metro to warm'); + await sleep(20 * 1000); + } +} + +function startVideoRecording(jsengine, currentAttempt) { + console.log( + `Start video record using pid: video_record_${currentAttempt}.pid`, + ); + + const recordingArgs = + `simctl io booted recordVideo video_record_${currentAttempt}.mov`.split( + ' ', + ); + const recordingProcess = childProcess.spawn('xcrun', recordingArgs, { + detached: true, + stdio: 'ignore', + }); + + return recordingProcess; +} + +function stopVideoRecording(recordingProcess) { + if (!recordingProcess) { + console.log("Passed a null recording process. Can't kill it"); + return; + } + + console.log(`Stop video record using pid: ${recordingProcess.pid}`); + + recordingProcess.kill('SIGINT'); +} + +async function executeTestsWithRetries( + appId, + udid, + maestroFlow, + jsengine, + currentAttempt, +) { + const recProcess = startVideoRecording(jsengine, currentAttempt); + try { + const timeout = 1000 * 60 * 10; // 10 minutes + const command = `$HOME/.maestro/bin/maestro --udid="${udid}" test "${maestroFlow}" --format junit -e APP_ID="${appId}"`; + console.log(command); + childProcess.execSync(`MAESTRO_DRIVER_STARTUP_TIMEOUT=1500000 ${command}`, { + stdio: 'inherit', + timeout, + }); + + stopVideoRecording(recProcess); + } catch (error) { + await sleep(5000); + // Can't put this in the finally block because it will be executed after the + // recursive call of executeTestsWithRetries + stopVideoRecording(recProcess); + + if (currentAttempt < MAX_ATTEMPTS) { + await executeTestsWithRetries( + appId, + udid, + maestroFlow, + jsengine, + currentAttempt + 1, + ); + } else { + console.error(`Failed to execute flow after ${MAX_ATTEMPTS} attempts.`); + throw error; + } + } +} + +async function main() { + console.info('\n=============================='); + console.info('Running tests for iOS with the following parameters:'); + console.info(`APP_PATH: ${APP_PATH}`); + console.info(`APP_ID: ${APP_ID}`); + console.info(`MAESTRO_FLOW: ${MAESTRO_FLOW}`); + console.info(`JS_ENGINE: ${JS_ENGINE}`); + console.info(`IS_DEBUG: ${IS_DEBUG}`); + console.info(`WORKING_DIRECTORY: ${WORKING_DIRECTORY}`); + console.info('==============================\n'); + + const simulatorName = 'iPhone 16 Pro'; + launchSimulator(simulatorName); + installAppOnSimulator(APP_PATH); + const udid = extractSimulatorUDID(); + bringSimulatorInForeground(); + await launchAppOnSimulator(APP_ID, udid, IS_DEBUG); + await executeTestsWithRetries(APP_ID, udid, MAESTRO_FLOW, JS_ENGINE, 1); + + console.log('Test finished'); + process.exit(0); +} + +main(); diff --git a/.github/workflows/check-nightly.yml b/.github/workflows/check-nightly.yml index 4565c31..190fab7 100644 --- a/.github/workflows/check-nightly.yml +++ b/.github/workflows/check-nightly.yml @@ -45,4 +45,35 @@ jobs: firebase_app_email: ${{ secrets.FIREBASE_APP_EMAIL }} firebase_app_pass: ${{ secrets.FIREBASE_APP_PASS }} firebase_app_projectname: ${{ secrets.FIREBASE_APP_PROJECTNAME }} - firebase_app_apikey: ${{ secrets.FIREBASE_APP_APIKEY }} \ No newline at end of file + firebase_app_apikey: ${{ secrets.FIREBASE_APP_APIKEY }} + + test-hermes-v1-ios: + uses: ./.github/workflows/test-hermes-v1-ios.yml + needs: check-nightly + + test-hermes-v1-android: + uses: ./.github/workflows/test-hermes-v1-android.yml + needs: check-nightly + + collect-results: + runs-on: ubuntu-latest + needs: [test-libraries, test-hermes-v1-ios, test-hermes-v1-android] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Restore outcomes + uses: actions/download-artifact@v4 + with: + pattern: '*-outcome' + path: /tmp + - name: Collect failures + uses: actions/github-script@v6 + env: + FIREBASE_APP_EMAIL: ${{ secrets.firebase_app_email }} + FIREBASE_APP_PASS: ${{ secrets.firebase_app_pass }} + FIREBASE_APP_APIKEY: ${{ secrets.firebase_app_apikey }} + FIREBASE_APP_PROJECTNAME: ${{ secrets.firebase_app_projectname }} + with: + script: | + const {collectResults} = require('./.github/workflow-scripts/collectNightlyOutcomes.js'); + await collectResults('${{secrets.discord_webhook_url}}'); \ No newline at end of file diff --git a/.github/workflows/test-hermes-v1-android.yml b/.github/workflows/test-hermes-v1-android.yml new file mode 100644 index 0000000..ffe3f3a --- /dev/null +++ b/.github/workflows/test-hermes-v1-android.yml @@ -0,0 +1,122 @@ +name: Test Hermes V1 with nightly on Android + +on: + workflow_call: + inputs: + retry-count: + description: 'Number of times to retry the build on failure' + required: false + type: number + default: 3 + +jobs: + test-hermes-v1-android: + name: Test Hermes V1 on Android + runs-on: 4-core-ubuntu + strategy: + matrix: + flavor: [debug, release] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup node.js + uses: actions/setup-node@v4 + with: + node-version: '22.14.0' + cache: yarn + + # Maestro will also setup Java + - name: Setup Maestro + uses: ./.github/actions/setup-maestro + + - name: Prepare the app with Hermes V1 + uses: ./.github/actions/prepare-hermes-v1-app + with: + retry-count: ${{ inputs.retry-count }} + + - name: Build Android with retry + uses: nick-fields/retry@v3 + env: + CMAKE_VERSION: 3.31.5 + ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64 + with: + timeout_minutes: 45 + max_attempts: ${{ inputs.retry-count }} + retry_wait_seconds: 30 + shell: bash + command: | + cd /tmp/RNApp/android + CAPITALIZED_FLAVOR=$(echo "${{ matrix.flavor }}" | awk '{print toupper(substr($0, 1, 1)) substr($0, 2)}') + ./gradlew assemble${CAPITALIZED_FLAVOR} + on_retry_command: | + echo "Cleaning up for Android retry..." + cd /tmp/RNApp/android + ./gradlew clean || true + rm -rf build app/build .gradle || true + + - name: Enable KVM group perms + shell: bash + run: | + # ubuntu machines have hardware acceleration available and when we try to create an emulator, the script pauses asking for user input + # These lines set the rules to reply automatically to that question and unblock the creation of the emulator. + # source: https://github.com/ReactiveCircus/android-emulator-runner?tab=readme-ov-file#running-hardware-accelerated-emulators-on-linux-runners + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Start Metro in debug + if: ${{ matrix.flavor == 'debug' }} + shell: bash + run: | + cd /tmp/RNApp + yarn start & + echo $! > /tmp/metro.pid + sleep 5 + + - name: Run Android E2E tests + id: run-test + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 24 + arch: x86_64 + ram-size: '8192M' + heap-size: '4096M' + disk-size: '6G' + cores: '4' + disable-animations: false + avd-name: e2e_emulator + script: | + node .github/workflow-scripts/maestro-android.js /tmp/RNApp/android/app/build/outputs/apk/${{ matrix.flavor }}/app-${{ matrix.flavor }}.apk com.rnapp ./.github/workflow-scripts/e2e/.maestro/ ${{ matrix.flavor }} /tmp/RNApp + + - name: Store Android tests result + uses: actions/upload-artifact@v4.3.4 + if: success() || failure() + with: + name: e2e_android_hermes_v1_report_${{ matrix.flavor }} + path: | + report.xml + screen.mp4 + + - name: Save outcome + id: save-outcome + if: always() + run: | + LIB_FOLDER=$(echo "Hermes_V1") + echo "Hermes V1 (${{ matrix.flavor }})|${{steps.run-test.outcome}}|${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" > "/tmp/$LIB_FOLDER-android-${{ matrix.flavor }}-outcome" + echo "lib_folder=$LIB_FOLDER" >> $GITHUB_OUTPUT + + - name: Upload outcome + if: always() + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.save-outcome.outputs.lib_folder }}-android-${{ matrix.flavor }}-outcome + path: /tmp/${{ steps.save-outcome.outputs.lib_folder }}-android-${{ matrix.flavor }}-outcome + + - name: Stop Metro in debug + if: ${{ matrix.flavor == 'debug' && always() }} + shell: bash + run: | + METRO_PID=$(cat /tmp/metro.pid) + kill $METRO_PID || true + echo "Metro process $METRO_PID stopped." diff --git a/.github/workflows/test-hermes-v1-ios.yml b/.github/workflows/test-hermes-v1-ios.yml new file mode 100644 index 0000000..d826c9e --- /dev/null +++ b/.github/workflows/test-hermes-v1-ios.yml @@ -0,0 +1,129 @@ +name: Test Hermes V1 with nightly on iOS + +on: + workflow_call: + inputs: + retry-count: + description: 'Number of times to retry the build on failure' + required: false + type: number + default: 3 + +jobs: + test-hermes-v1-ios: + name: Test Hermes V1 on iOS + runs-on: macos-15-large + strategy: + matrix: + flavor: [debug, release] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup node.js + uses: actions/setup-node@v4 + with: + node-version: '22.14.0' + cache: yarn + + - name: Prepare capitalized flavor + id: prepare-flavor + shell: bash + run: | + CAPITALIZED_FLAVOR=$(echo "${{ inputs.flavor }}" | awk '{print toupper(substr($0, 1, 1)) substr($0, 2)}') + echo "capitalized_flavor=$CAPITALIZED_FLAVOR" >> $GITHUB_OUTPUT + + - name: Setup Maestro + uses: ./.github/actions/setup-maestro + + - name: Prepare the app with Hermes V1 + uses: ./.github/actions/prepare-hermes-v1-app + with: + retry-count: ${{ inputs.retry-count }} + + - name: Setup xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: 16.4.0 + + - name: Build iOS with retry + uses: nick-fields/retry@v3 + with: + timeout_minutes: 45 + max_attempts: ${{ inputs.retry-count }} + retry_wait_seconds: 30 + shell: bash + command: | + cd /tmp/RNApp/ios + bundle install + RCT_HERMES_V1_ENABLED=1 bundle exec pod install + xcodebuild build \ + -workspace "RNApp.xcworkspace" \ + -scheme "RNApp" \ + -configuration "${{ steps.prepare-flavor.outputs.capitalized_flavor }}" \ + -sdk "iphonesimulator" \ + -destination "generic/platform=iOS Simulator" \ + -derivedDataPath "/tmp/RNApp" \ + -quiet + on_retry_command: | + echo "Cleaning up for iOS retry..." + cd /tmp/RNApp/ios + rm -rf Pods Podfile.lock build + rm -rf ~/Library/Developer/Xcode/DerivedData/* || true + + - name: Start Metro in debug + if: ${{ matrix.flavor == 'debug' }} + shell: bash + run: | + cd /tmp/RNApp + yarn start & + echo $! > /tmp/metro.pid + sleep 5 + + - name: Run iOS E2E Tests + id: run-test + shell: bash + run: | + node .github/workflow-scripts/maestro-ios.js \ + "/tmp/RNApp/Build/Products/${{ steps.prepare-flavor.outputs.capitalized_flavor }}-iphonesimulator/RNApp.app" \ + "org.reactjs.native.example.RNApp" \ + "./.github/workflow-scripts/e2e/.maestro/" \ + "Hermes" \ + "${{ steps.prepare-flavor.outputs.capitalized_flavor }}" \ + "/tmp/RNApp" + + - name: Store iOS tests result + if: success() || failure()) + uses: actions/upload-artifact@v4.3.4 + with: + name: e2e_ios_hermes_v1_report_${{ matrix.flavor }} + path: | + video_record_1.mov + video_record_2.mov + video_record_3.mov + video_record_4.mov + video_record_5.mov + report.xml + + - name: Save outcome + id: save-outcome + if: always() + run: | + LIB_FOLDER=$(echo "Hermes_V1") + echo "Hermes V1 (${{ matrix.flavor }})|${{steps.run-test.outcome}}|${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" > "/tmp/$LIB_FOLDER-ios-${{ matrix.flavor }}-outcome" + echo "lib_folder=$LIB_FOLDER" >> $GITHUB_OUTPUT + + - name: Upload outcome + if: always() + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.save-outcome.outputs.lib_folder }}-ios-${{ matrix.flavor }}-outcome + path: /tmp/${{ steps.save-outcome.outputs.lib_folder }}-ios-${{ matrix.flavor }}-outcome + + - name: Stop Metro in debug + if: ${{ matrix.flavor == 'debug' && always() }} + shell: bash + run: | + METRO_PID=$(cat /tmp/metro.pid) + kill $METRO_PID || true + echo "Metro process $METRO_PID stopped." diff --git a/.github/workflows/test-libraries-on-nightlies.yml b/.github/workflows/test-libraries-on-nightlies.yml index 099b52e..3113c04 100644 --- a/.github/workflows/test-libraries-on-nightlies.yml +++ b/.github/workflows/test-libraries-on-nightlies.yml @@ -72,26 +72,3 @@ jobs: with: name: ${{ steps.save-outcome.outputs.lib_folder }}-${{ matrix.platform }}-outcome path: /tmp/${{ steps.save-outcome.outputs.lib_folder }}-${{ matrix.platform }}-outcome - - collect-results: - runs-on: ubuntu-latest - needs: [test-library-on-nightly] - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Restore outcomes - uses: actions/download-artifact@v4 - with: - pattern: '*-outcome' - path: /tmp - - name: Collect failures - uses: actions/github-script@v6 - env: - FIREBASE_APP_EMAIL: ${{ secrets.firebase_app_email }} - FIREBASE_APP_PASS: ${{ secrets.firebase_app_pass }} - FIREBASE_APP_APIKEY: ${{ secrets.firebase_app_apikey }} - FIREBASE_APP_PROJECTNAME: ${{ secrets.firebase_app_projectname }} - with: - script: | - const {collectResults} = require('./.github/workflow-scripts/collectNightlyOutcomes.js'); - await collectResults('${{secrets.discord_webhook_url}}'); diff --git a/patches/hermes-v1.patch b/patches/hermes-v1.patch new file mode 100644 index 0000000..6d4e8b5 --- /dev/null +++ b/patches/hermes-v1.patch @@ -0,0 +1,42 @@ +diff --git a/android/gradle.properties b/android/gradle.properties +index 9afe615..4168d4e 100644 +--- a/android/gradle.properties ++++ b/android/gradle.properties +@@ -37,6 +37,7 @@ newArchEnabled=true + # Use this property to enable or disable the Hermes JS engine. + # If set to false, you will be using JSC instead. + hermesEnabled=true ++hermesV1Enabled=true + + # Use this property to enable edge-to-edge display support. + # This allows your app to draw behind system bars for an immersive UI. +diff --git a/android/settings.gradle b/android/settings.gradle +index 63b5d4e..a3a2550 100644 +--- a/android/settings.gradle ++++ b/android/settings.gradle +@@ -4,3 +4,11 @@ extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autoli + rootProject.name = 'RNApp' + include ':app' + includeBuild('../node_modules/@react-native/gradle-plugin') ++ ++includeBuild('../node_modules/react-native') { ++ dependencySubstitution { ++ substitute(module("com.facebook.react:react-android")).using(project(":packages:react-native:ReactAndroid")) ++ substitute(module("com.facebook.react:react-native")).using(project(":packages:react-native:ReactAndroid")) ++ substitute(project(":packages:react-native:ReactAndroid:hermes-engine")).using(module("com.facebook.hermes:hermes-android:250829098.0.2")) ++ } ++} +diff --git a/package.json b/package.json +index f05d51b..56be47f 100644 +--- a/package.json ++++ b/package.json +@@ -35,6 +35,9 @@ + "react-test-renderer": "19.2.0", + "typescript": "^5.8.3" + }, ++ "resolutions": { ++ "hermes-compiler": "250829098.0.2" ++ }, + "engines": { + "node": ">=20" + }