Expo React Native Complete CI / CD Workflow Using Github Actions

Expo React Native Complete CI / CD Workflow Using Github Actions

In this article, I will show how we can set up a CI-CD workflow using Github. Actions. Whenever I search for expo cicd workflow articles there were only steps to build the iOS IPA file. And the apk file. here I'm going to show how we can push a build to the Google Play store & TestFlight using our Github Action CI-CD.

TL: DR

Looks likes it's two commands to upload build to google play store and TestFlight. But it's not we have some configurations when we are uploading builds. first of all ill explain what exactly ou workflow looks like. we will have a bigger event on our workflow. for this I have chooseed on push event. when event some push to release branch then our trigger will be activated. After that, we have 4 jobs. Two build jobs for IOS and Android. Two Publishing jobs to IOS and Android. the following diagram will show a detailed view of our workflow. Both Publishing builds will need their parent to build to complete.

  • Build-For-Android

  • Push-To-GooglePlay [Wait Until Android Build Complete]

  • Build-For-IOS

  • Push-To-Test-flight [Wait Until IOS Build Complete]

🚀 Let's write our workflow start and triggers first. after will jump into android than iOS.

Workflow Setup

In your GitHub repo, you can see a tab called Action. navigate to the Actions tab and create a manual workflow it will give a sample workflow script for you. Delete all those things will write this from scratch.

name: Expo CI-CD

on:
  push :
    branches : [release]

First, we have a name for our workflow. After that we have created a trigger to workflow in this I have used the on push branch to rigger our workflow. looks like bread and butter thanks to GitHub Actions. Now will see the android workflow setup.

Prerequisite for Android Workflow

  • Google Developer Account
  • Expo Application With Expo Account
  • Google Service Account
  • At least one time you need to submit your build manually in Google Play.

Let’s create Android Job now!

jobs:
  Build-for-android:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - uses: actions/setup-java@v1.4.3
        with:
          java-version: '9.0.4' # The JDK version to make available on the path.
          java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
          architecture: x64 # (x64 or x86) - defaults to x64
      - uses: expo/expo-github-action@v5
        with:
          expo-version: 3.x
          expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
          expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
      - name: Install deps
        run: yarn install
      - name: Build Android APK Bundle
        run: expo build:android

In this step you can see I have started with jobs. so our first job is Build-for-android. This will run on macOS-latest.inside the job we have sub-steps to follow.first sub-step to use actions/checkout@v2 this is used to get access to our workflow. You can see more about this with the following link.

Second sub-step we have used setup-node@v1. This is used to install the node to our resources. We can define a node version by using syntax. Here I have used node-version: 12.x. Third sub-step is to setup java. Our resources. you can define java properties by using syntax.

Forth sub-step I have used expo/expo-github-acitons@v5. This will allow us to use Expo CLI commands. we can sign in to our expo dashboard by this command. here you might wonder what are the secrets. It's a security step that I had taken to mask my credentials. You can navigate to your repository setting by top navbar. in. the settings there will be a tab called secrets. you can add secrets variables here.I have added two secrets for now.

Then we have the Install deps step. This step will run the can install command to install all dependencies that need our project. After that, we have the. The main step Building our android APK.this will run and wait until the build queue finishes on you. Expo dashboard. These steps will take some time depending on queue status.

Hurray, we have completed the first job. Now we have to push to play store our APK file.

Let’s create Publish Playstore Job now!

ush-to-Playstore:
    needs: [Build-for-android]
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - uses: actions/setup-java@v1.4.3
        with:
          java-version: '9.0.4' # The JDK version to make available on the path.
          java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
          architecture: x64 # (x64 or x86) - defaults to x64
      - uses: expo/expo-github-action@v5
        with:
          expo-version: 3.x
          expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
          expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
      - name: Install deps
        run: yarn install
      - name: Upload to Google Play Console
        run: expo upload:android --latest --key <Path to google service json file>

In this job, you can see only two differences which are the second line and the last step.in the second line, I have added a wait check for this job. this job will wait until our build job complete. so we can get the latest build from expo servers. seems like other steps are pretty much the same. one last step upload to google play console will run the command. in this command you have to give — latest tag and — key tag where the Google service account JSON file path. if you don’t have a google service account please generate the file. You can find the steps from the following link.

Yeassssh! we are done with our android workflow. Let's jump into IOS Workflow Now!

Prerequisite for IOS Workflow

  • Expo Application With Expo Account
  • Apple Developer Account
  • Apple App-Specific Password

Let’s create an iOS Job now!

build-for-IOS:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - uses: actions/setup-java@v1.4.3
        with:
          java-version: '9.0.4' # The JDK version to make available on the path.
          java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
          architecture: x64 # (x64 or x86) - defaults to x64
      - uses: sinoru/actions-setup-xcode@v1.1.2
        with:
          xcode-version: '11.2.1' # Exact version of a Xcode version to use
          apple-id: ${{ secrets.EXPO_APPLE_ID }} # Apple ID to download from Apple Developer when Xcode not available in local
          apple-id-password: ${{ secrets.EXPO_APPLE_PASSWORD }}
      - uses: expo/expo-github-action@v5
        with:
          expo-version: 3.x
          expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
          expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
      - name: Install deps
        run: yarn install
      - name: Build iOS app
        run: expo build:ios
        env:
            EXPO_APPLE_ID: ${{secrets.EXPO_APPLE_ID}}
            EXPO_APPLE_ID_PASSWORD: ${{secrets.EXPO_APPLE_PASSWORD}}

Seems like the first three jobs are the same as the android one. let's look at sub-step four. I have installed Xcode into our resources. we have used syntax to add configurations to Xcode installation. I have used Xcode version 11.2.1 and pass my Apple ID and password using secrets. you already know how to use this secret. the next step is also the same step that we used in the android build. final step is to use expo build:ios .in here we cant say like just expo build iOS this will require our apple io and password so I have used secrets to provide Apple ID and password.

Eureka we have complete up to 75% now. The only thing is left we have to wait until the iOS build finishes and upload it to TestFlight.Let's start.

Let’s create Publish TestFlight Job now!

publish-to-testflight:
    needs: [build-for-IOS]
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - uses: actions/setup-java@v1.4.3
        with:
          java-version: '9.0.4' # The JDK version to make available on the path.
          java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
          architecture: x64 # (x64 or x86) - defaults to x64
      - uses: sinoru/actions-setup-xcode@v1.1.2
        with:
          xcode-version: '11.2.1' # Exact version of a Xcode version to use
          apple-id: ${{ secrets.EXPO_APPLE_ID }} # Apple ID to download from Apple Developer when Xcode not available in local
          apple-id-password: ${{ secrets.EXPO_APPLE_PASSWORD }}
      - uses: expo/expo-github-action@v5
        with:
          expo-version: 3.x
          expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
          expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
      - name: Install deps
        run: yarn install
      - uses: jakemwood/expo-apple-2fa@main
        env:
          ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
        with:
          expo_apple_id: ${{ secrets.EXPO_APPLE_ID }}
          expo_apple_password: ${{ secrets.EXPO_APPLE_PASSWORD }}
          app_specific_password: ${{ secrets.EXPO_APP_SPECIFIC_PASSWORD }}
          tfa_phone_number: "+11 (123) 456-6789"

Finally, we are here to publish our build to TestFlight.this is a job that depends on our iOS build job. So I have used need syntax to wait until our iOS build job is completed. This job is also using the same checkout, node, java, Xcode, expo-action steps. the only special thing here is I have used this amazing action called Expo upload:ios with 2FA support.

to bypass the 2FA. It's a real masterpiece of action setup that allows me to pass the 2FA. All thanks go to the main author of this package.

That package needs env ACTIONS_ALLOW_UNSECURE_COMMANDS: true flag to run the commands. also, it needs our Apple ID, password, app-specific password if you don’t know what this app-specific password please feel free to check the following link.

Also, this will need your mobile number to send the auth code. once this command is running you will get a ngrok link that you can enter the auth code. Once you enter that our workflow is fully completed.

Let’s wrap it up!


name: Expo CI-CD

on:
  push :
    branches : [release]

jobs:
  Build-for-android:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - uses: actions/setup-java@v1.4.3
        with:
          java-version: '9.0.4' # The JDK version to make available on the path.
          java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
          architecture: x64 # (x64 or x86) - defaults to x64
      - uses: expo/expo-github-action@v5
        with:
          expo-version: 3.x
          expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
          expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
      - name: Install deps
        run: yarn install
      - name: Build Android APK Bundle
        run: expo build:android
  Push-to-Playstore:
    needs: [Build-for-android]
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - uses: actions/setup-java@v1.4.3
        with:
          java-version: '9.0.4' # The JDK version to make available on the path.
          java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
          architecture: x64 # (x64 or x86) - defaults to x64
      - uses: expo/expo-github-action@v5
        with:
          expo-version: 3.x
          expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
          expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
      - name: Install deps
        run: yarn install
      - name: Upload to Google Play Console
        run: expo upload:android --latest --key <Path to Google Sevice Account Json>
  build-for-IOS:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - uses: actions/setup-java@v1.4.3
        with:
          java-version: '9.0.4' # The JDK version to make available on the path.
          java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
          architecture: x64 # (x64 or x86) - defaults to x64
      - uses: sinoru/actions-setup-xcode@v1.1.2
        with:
          xcode-version: '11.2.1' # Exact version of a Xcode version to use
          apple-id: ${{ secrets.EXPO_APPLE_ID }} # Apple ID to download from Apple Developer when Xcode not available in local
          apple-id-password: ${{ secrets.EXPO_APPLE_PASSWORD }}
      - uses: expo/expo-github-action@v5
        with:
          expo-version: 3.x
          expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
          expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
      - name: Install deps
        run: yarn install
      - name: Build iOS app
        run: expo build:ios
        env:
            EXPO_APPLE_ID: ${{secrets.EXPO_APPLE_ID}}
            EXPO_APPLE_ID_PASSWORD: ${{secrets.EXPO_APPLE_PASSWORD}}    
  publish-to-testflight:
    needs: [build-for-IOS]
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - uses: actions/setup-java@v1.4.3
        with:
          java-version: '9.0.4' # The JDK version to make available on the path.
          java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
          architecture: x64 # (x64 or x86) - defaults to x64
      - uses: sinoru/actions-setup-xcode@v1.1.2
        with:
          xcode-version: '11.2.1' # Exact version of a Xcode version to use
          apple-id: ${{ secrets.EXPO_APPLE_ID }} # Apple ID to download from Apple Developer when Xcode not available in local
          apple-id-password: ${{ secrets.EXPO_APPLE_PASSWORD }}
      - uses: expo/expo-github-action@v5
        with:
          expo-version: 3.x
          expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
          expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
      - name: Install deps
        run: yarn install
      - uses: jakemwood/expo-apple-2fa@main
        env:
          ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
        with:
          expo_apple_id: ${{ secrets.EXPO_APPLE_ID }}
          expo_apple_password: ${{ secrets.EXPO_APPLE_PASSWORD }}
          app_specific_password: ${{ secrets.EXPO_APP_SPECIFIC_PASSWORD }}
          tfa_phone_number: "+11 (123) 456-789"

The above snippet shows our full workflow code that you can get a clear idea of where things are should.

Expo CI-CD setup is a really enjoyable experience I hope eventing will run smooth with your workflows. Cheers.🚀 😎!