Some time ago, I wrote an article, how to build standalone apps with Expo. It’s not a very complicated process, but if you lack knowledge about DevOps, you could be a bit confused. So, I was looking for a solution: how to make it easier. Unexpectedly, Github extended a helping hand by providing Github Actions - easy to configure set for CI and CD for projects based on their repositories. I’ve checked it, and it works great!
Check the working example on my repository: Expo standalone with GitHub Actions
Github released their Actions
for public beta tests in August 2019. From that time, they’ve updated it to version 2.x and filled with many useful features. From building a mobile application point of view, they allow using a macOS runner, which is necessary to build IPA packages for iOS. Thanks to that, we can run all processes of building standalone applications without any other scripts.
How does it work? Github allows running building pipeline, triggered by one of many actions (like pull request, merge, comment, etc.). We define our actions in the workflow file, and we describe, step by step, how we want to build our application.
In building standalone mobile applications based on Expo, we have to go through a few important steps.
All our sources have to be exported to a public server (with SSL). This host will serve sources for our Over-The-Air updates. The application will check it on every launch (or however we set it up).
So, let’s start from the beginning and go through the first part of our workflow file:
jobs:
export:
name: Export assets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12.x
- uses: expo/expo-github-action@v5
with:
expo-version: 3.x
expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}
- run: yarn install
- run: expo export --public-url ${{ secrets.ASSETS_URL }}
- uses: sebastianpopp/ftp-action@v2.0.0
with:
host: ${{ secrets.ASSETS_FTP_HOST }}
user: ${{ secrets.ASSETS_FTP_USER }}
password: ${{ secrets.ASSETS_FTP_PASSWORD }}
localDir: dist
remoteDir: ${{ secrets.ASSETS_FTP_REMOTE_DIR }}
As you can see, we define the first job export
, where we do all these things. We use expo/expo-github-action
dependency, which is an official set of actions for Expo. With its help, we sign in to expo servers with our Expo credentials.
Next, we run installing dependencies with yarn
and call expo export...
command to minify and prepare sources for publishing.
The last step is to upload this package on an FTP server (we use for it another, simple, dependency: sebastianpopp/ftp-action@v2.0.0
).
After a few minutes of exporting and uploading sources to FTP, we can build an Android package.
jobs:
(...)
apk:
name: Build .apk file
runs-on: ubuntu-latest
needs: export
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12.x
- run: ./.github/scripts/decrypt_secret.sh
env:
SECRET_FILES_PASSWORD: ${{ secrets.SECRET_FILES_PASSWORD }}
- uses: actions/cache@v2
id: turtle-cache
with:
path: ~/.turtle
key: ${{ runner.os }}-turtle
- run: |
yarn install
npm install -g turtle-cli
turtle build:android -o ./build.apk --keystore-path ./secrets/build.keystore --keystore-alias ${{ secrets.ANDROID_KEYSTORE_ALIAS }} --public-url ${{ secrets.ASSETS_URL }}/android-index.json -t apk
env:
EXPO_ANDROID_KEY_PASSWORD: ${{ secrets.EXPO_ANDROID_KEY_PASSWORD }}
EXPO_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.EXPO_ANDROID_KEYSTORE_PASSWORD }}
- name: Archive .apk build
uses: actions/upload-artifact@v1
with:
name: build.apk
path: ./build.apk
At the very beginning, we noticed we need export to be finished (needs: export
). Next, we go through the standard process, where we run turtle
script. In this file, we see two extraordinary places:
decrypt_secret.sh
file in which we run bash’s gpg
commands to get our secret filesAt the very end of this process, we save our package in ./build.apk
file, which pops up on the screen when the building is finished.
The last thing is to build an iOS package. It could be run simultaneously with building apk, but as in the previous step, we have to wait for export
job anyway.
Let’s take a look at the second build:
jobs:
ipa:
name: Build .ipa file
runs-on: macos-latest
needs: export
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12.x
- run: ./.github/scripts/decrypt_secret.sh
env:
SECRET_FILES_PASSWORD: ${{ secrets.SECRET_FILES_PASSWORD }}
- uses: actions/cache@v2
id: turtle-cache
with:
path: ~/.turtle
key: ${{ runner.os }}-turtle
- run: |
yarn install
npm install -g turtle-cli
turtle build:ios -o build.ipa --team-id ${{ secrets.APPLE_TEAM_ID }} --dist-p12-path ./secrets/cert.p12 --provisioning-profile-path ./secrets/profile.mobileprovision --public-url ${{ secrets.ASSETS_URL }}/ios-index.json
env:
EXPO_IOS_DIST_P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
- name: Archive .ipa build
uses: actions/upload-artifact@v1
with:
name: build.ipa
path: ./build.ipa
It seems to be even simpler than .apk. And it actually is! The only thing which could bother is the number of certificates to prepare.
To proper build, we need at least a .p12
certificate file and provisioning profile. Both of them we have to create through App Store Connect panel and on macOS.
Speaking of macOS, keep in mind that for .ipa build, you have to use macos-latest
runner, which is required.
The whole building process is described in the separate post - if you want details, you definitely should go there. Here we prepared the simplest version of it by using Github Actions. This service is sufficient to set up the whole process with any other exceptions we want. We are limited by our imagination.
I share this example on my Github repository, where I list all needed secrets and explain how to encrypt secret files. Feel free to extend it or fork it into your project!