Building a React Native mobile app via Github Actions CI, working locally but overflows in the CI Pipeline

595 Views Asked by At

I have been working on setting up an integration pipeline, such that I can quickly build new APK files for android phones when I push a new version to the main branch. For this, I have created the following CI build file:


name: React Native build android

on:
  pull_request:
    branches: [ "main", "test-branch" ]

jobs:
  build-android:
    runs-on: ubuntu-latest
    env:
      NODE_ENV: production
    steps:
      - name: install Linux dependencies
        run: sudo apt-get update || true

      - name: Checkout
        uses: actions/checkout@v2

      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install npm dependencies
        run: npm install

      - name: make gradlew executable
        run: chmod +x ./android/gradlew

      - name: Build android APK
        run: cd ./android && ./gradlew assembleRelease --stacktrace --no-daemon

      - name: Upload Artifact
        uses: actions/upload-artifact@v1
        with:
          name: PFM-release.apk
          path: android/app/build/outputs/apk/release/

Quite straightforwardly, it does some updating, sets up node and builds the android .APK file. Then, it should upload the generated artifact.

Now, this fails on building the android APK. It goes as follows,


org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:bundleReleaseJsAndAssets'.
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:142)
    at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:140)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:128)
    at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
    at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
    at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
    at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
    at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:69)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:327)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:314)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:307)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:293)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:420)
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:342)
    at org.gradle.execution.plan.DefaultPlanExecutor.process(DefaultPlanExecutor.java:96)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.executeWithServices(DefaultTaskExecutionGraph.java:140)
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph.execute(DefaultTaskExecutionGraph.java:125)
    at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:39)
    at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:51)
    at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:54)
    at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor$ExecuteTasks.call(BuildOperationFiringBuildWorkerExecutor.java:43)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.execution.BuildOperationFiringBuildWorkerExecutor.execute(BuildOperationFiringBuildWorkerExecutor.java:40)
    at org.gradle.internal.build.DefaultBuildLifecycleController.lambda$executeTasks$7(DefaultBuildLifecycleController.java:161)
    at org.gradle.internal.model.StateTransitionController.doTransition(StateTransitionController.java:247)
    at org.gradle.internal.model.StateTransitionController.lambda$tryTransition$7(StateTransitionController.java:174)
    at org.gradle.internal.work.DefaultSynchronizer.withLock(DefaultSynchronizer.java:44)
    at org.gradle.internal.model.StateTransitionController.tryTransition(StateTransitionController.java:174)
    at org.gradle.internal.build.DefaultBuildLifecycleController.executeTasks(DefaultBuildLifecycleController.java:161)
    at org.gradle.internal.build.DefaultBuildWorkGraphController$DefaultBuildWorkGraph.runWork(DefaultBuildWorkGraphController.java:156)
    at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:249)
    at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:109)
    at org.gradle.composite.internal.DefaultBuildController.doRun(DefaultBuildController.java:164)
    at org.gradle.composite.internal.DefaultBuildController.access$000(DefaultBuildController.java:45)
    at org.gradle.composite.internal.DefaultBuildController$BuildOpRunnable.run(DefaultBuildController.java:183)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
Caused by: org.gradle.process.internal.ExecException: Process 'command 'node'' finished with non-zero exit value 1
    at org.gradle.process.internal.DefaultExecHandle$ExecResultImpl.assertNormalExitValue(DefaultExecHandle.java:415)
    at org.gradle.process.internal.DefaultExecAction.execute(DefaultExecAction.java:38)
    at org.gradle.api.tasks.AbstractExecTask.exec(AbstractExecTask.java:73)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:125)
    at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
    at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
    at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
    at org.gradle.api.internal.tasks.execution.TaskExecution$3.run(TaskExecution.java:236)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
    at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:221)
    at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:204)
    at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:187)
    at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:165)
    at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:89)
    at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:40)
    at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:53)
    at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:50)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
    at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:50)
    at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:40)
    at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:68)
    at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:38)
    at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:41)
    at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:74)
    at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)
    at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:51)
    at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:29)
    at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.executeDelegateBroadcastingChanges(CaptureStateAfterExecutionStep.java:124)
    at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:80)
    at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:58)
    at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:48)
    at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:36)
    at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:181)
    at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:71)
    at org.gradle.internal.Either$Right.fold(Either.java:175)
    at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:59)
    at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:69)
    at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:47)
    at ...

and then continues with


java.lang.StackOverflowError
    at org.gradle.execution.plan.FinalizerGroup.getFinalizedNodes(FinalizerGroup.java:77)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:153)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:155)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:155)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:155)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:155)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:155)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:155)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:155)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:155)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
    at org.gradle.execution.plan.FinalizerGroup.isCanCancel(FinalizerGroup.java:155)
    at org.gradle.execution.plan.CompositeNodeGroup.isCanCancel(CompositeNodeGroup.java:101)
    at org.gradle.execution.plan.Node.isCanCancel(Node.java:232)
...

It is the same when I tried cleaning it beforehand, with cd ./android && ./gradlew --clean or when I stopped gradlew before running it again with cd ./android && ./gradlew --clean.

I have found out that this will not occur when doing cd ./android && ./gradlew assembleRelease -x bundleReleaseJsAndAssets which works because we are simply not bundling the assets. This leads me to believe that bundling is where it fails. Now, then I thought about bundling beforehand, which shouldn't be necessary, but nevertheless I was hoping for it to work. This did not help me in my issue at all.

Now, locally all is fine. It builds and creates an .APK file with the assets and all, it also automatically bundles these assets. I expected this to happen on the CI pipeline as well. I have also tried making use of already existing templates for creating a .APK via GitHub Actions, but this didn't work out.

My package.json might be of interest, so here it is. But, do note that everything works for me locally.

{
  "name": "MYAPP",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "server:mock": "ts-node scripts/mock/run-mocked-db.ts",
    "test": "cross-env NODE_ENV=test jest",
    "test:snap": "cross-env NODE_ENV=test jest --config ./tests/snapshot/config.js",
    "e2e:build": "cross-env NODE_ENV=test detox build --configuration",
    "e2e": "cross-env NODE_ENV=test detox test --configuration",
    "e2e:android:nobuild": "cross-env NODE_ENV=test npm run e2e android",
    "e2e:android": "cross-env NODE_ENV=test npm run e2e:build android && npm run e2e android",
    "e2e:ios": "cross-env NODE_ENV=test yarn e2e:build ios && yarn e2e ios",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
    "lint:nofix": "eslint \"{src,test}/**/*.ts\"",
    "prettier": "prettier --check {src,test}/**/*.ts",
    "precommit-msg": "echo 'Running pre-commit checks... [package.json]' && exit 0"
  },
  "pre-commit": [
    "precommit-msg",
    "prettier",
    "lint:nofix",
    "test:snap",
    "e2e:android"
  ],
  "dependencies": {
    "@react-native-async-storage/async-storage": "^1.17.11",
    "@react-native-community/cli": "^10.1.3",
    "@react-native-cookies/cookies": "^6.2.1",
    "@react-navigation/native": "^6.1.2",
    "@react-navigation/native-stack": "^6.9.8",
    "@tanstack/react-query": "^4.20.4",
    "appcenter": "4.4.5",
    "appcenter-analytics": "4.4.5",
    "appcenter-crashes": "4.4.5",
    "axios": "^1.2.2",
    "babel-jest": "^29.3.1",
    "detox": "^20.1.0",
    "dotenv-flow": "^3.2.0",
    "metro-config": "^0.74.0",
    "react": "18.1.0",
    "react-native": "0.70.6",
    "react-native-dotenv": "^3.4.7",
    "react-native-linear-gradient": "^2.6.2",
    "react-native-paper": "^5.1.2",
    "react-native-safe-area-context": "^4.5.0",
    "react-native-screens": "^3.19.0",
    "react-native-svg": "^13.7.0",
    "react-native-vector-icons": "^9.2.0",
    "rn-android-keyboard-adjust": "^2.1.1",
    "rn-secure-storage": "^2.0.8",
    "ts-jest": "^29.0.3"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/runtime": "^7.12.5",
    "@faker-js/faker": "^7.6.0",
    "@react-native-community/eslint-config": "^2.0.0",
    "@tsconfig/react-native": "^2.0.2",
    "@types/dotenv-flow": "^3.2.0",
    "@types/express": "^4.17.15",
    "@types/jest": "^26.0.23",
    "@types/react": "^18.0.21",
    "@types/react-native": "^0.70.6",
    "@types/react-native-android-keyboard-adjust": "^1.0.0",
    "@types/react-native-vector-icons": "^6.4.13",
    "@types/react-test-renderer": "^18.0.0",
    "@typescript-eslint/eslint-plugin": "^5.37.0",
    "@typescript-eslint/parser": "^5.37.0",
    "axios-mock-adapter": "^1.21.2",
    "body-parser": "^1.20.1",
    "cross-env": "^7.0.3",
    "eslint": "^7.32.0",
    "express": "^4.18.2",
    "jest": "^29.3.1",
    "metro-react-native-babel-preset": "0.72.3",
    "pre-commit": "^1.2.2",
    "react-native-svg-transformer": "^1.0.0",
    "react-test-renderer": "18.1.0",
    "ts-node": "^10.9.1",
    "typescript": "^4.8.3"
  },
  "jest": {
    "preset": "react-native",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  }
}

Doing things such as replicating my local environment via the build file did not work for me either. I tried applying windows-latest since I have a Windows machine, but I ended up on the same issue. I also tried checking whether it was because I forgot some configuration by trying to build it on a clean machine, which I could do (but, this is local, I want to automate the build process.)

1

There are 1 best solutions below

0
Lucas Ouwens On BEST ANSWER

It turns out that the issue occurred due to two separate factors. First, the recursion issue is due to an issue within gradle itself -- specifically for 7.4.2 till 7.5.2. Updating to gradle 7.6 in the gradle-wrapper.properties file, e.g. https\://services.gradle.org/distributions/gradle-7.6-all.zip of react native will solve this.

As for the bundling part, I was unable to figure it out. I ended up bundling 'manually' beforehand in the build file. My build file ended up looking as follows:

name: React Native build android

on:
  push:
    branches: [ "main" ]

jobs:
  build-android:
    runs-on: ubuntu-latest
    env:
      NODE_ENV: production
    steps:
      - name: install Linux dependencies
        run: sudo apt-get update || true

      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install npm dependencies
        run: npm install

      - name: Install java
        uses: actions/setup-java@v3
        with:
          java-version: "18"
          distribution: "zulu"

      - name: make gradlew executable
        run: chmod +x ./android/gradlew

      - name: Bundle RN Android assets
        run: npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res
      - name: Clean up after bundling
        run: rm -rf ./android/app/src/main/res/drawable-* && rm -rf ./android/app/src/main/res/raw

      - name: Build android APK
        run: cd ./android && ./gradlew clean app:assembleRelease -x bundleReleaseJsAndAssets

      - name: Upload Artifact
        uses: actions/upload-artifact@v1
        with:
          name: app-release.apk
          path: android/app/build/outputs/apk/release/

Basically, I ended up bundling beforehand, and opting out of bundling if it fails in the assembleRelease itself.