CI/CD for NativeScript apps with Fastlane and GitHub Actions
A few months ago I wrote the blog post Automatic NativeScript App Deployments with fastlane showing how to set up fastlane for NativeScript…
Take control of your career. Build JavaScript mobile apps.
A few months ago I wrote the blog post Automatic NativeScript App Deployments with fastlane showing how to set up fastlane for NativeScript apps. Now, this article takes it a step forward: running the fastlane build process in a GitHub action. Here, we will show you how to build and deploy your app in the cloud automatically and practically for free!
Note: This article Builds on top of Automatic NativeScript App Deployments with fastlane and assumes you have that part working.
You can see the complete fastlane and GitHub setup used in this article at https://github.com/tralves/ns-github-actions-demo.
Why?
I am not going to repeat myself on why deployments are so boring and hard to do.
fastlane will make this chore much easier, but still, there are good reasons to move the entire process to the cloud.
The main reason is that you no longer need to worry about configuring the deployment environment for each team member. Even if match makes it much easier, you would still have to share secrets and certificates with your team, configure Xcode accounts, etc. The developer doesn't even need to have a mac to release a new iOS version! Not only that, but anyone in the team will be able to deploy the app (reducing the bus factor of the project).
The last is that with fastlane you would need to run only 2 commands to
deploy (e.g. fastlane ios beta
and fastlane android beta
), but with GitHub actions, you will need 0
commands, which is even better.
GitHub includes 2000 minutes of GitHub action runtime per month for free plans and this number grows in paid plans. Let's say each deploy takes 40 minutes. That's 50 deployments per month! It's a deployment machine for free.
To add a GitHub action that runs your fastlane configuration, we will need to:
- Add secrets to the GitHub repo;
- Adjust the fastlane configuration to run on a cloud instance;
- Create the GitHub Action workflow;
We will detail all these steps next.
Configuring the secrets
In the fastlane blog post
I suggested adding the project secrets (passwords, keys, etc.) in the file .env.default
. Well, this file
should not be commited along with the source code. Instead, we will add those secret variables to the repository secrets. You will find them in GitHub under "Settings > Secrets" (see here).
These are the required secrets:
FASTLANE_PASSWORD
: The AppleId account password. More info here;FASTLANE_USER
: The AppleId account email. More info here;KEYSTORE_ALIAS
: keystore alias to be used;KEYSTORE_ALIAS_PASSWORD
: password of the keystore alias;KEYSTORE_FILE_B64
: keystore file encoded to base64. This can be generated withbase64 fastlane/keys/mobile.keystore
. Copy/paste the base64 string directly to the secret;KEYSTORE_PASSWORD
: Password of the keystore;MATCH_GIT_BASIC_AUTHORIZATION
: Basic authorization for a GitHub user that can access the match git repository. You can generate it withecho "<github-user>:<github-password>" | base64
;MATCH_PASSWORD
: Encryption password for the match repository herePLAYSTORE_JSON_KEY_DATA
: contents of thePLAYSTORE_JSON_KEY_DATA
var in.env.default
;SIGNING_IDENTITY
: Code signing identity to use. e.g.Apple Distribution: Tiago Alves (47XC3F8J4V)
. See the certificate name in Keychain Access.
I highly recommend creating a new AppleId account without 2FA and add it as a developer to the Apple developer Team. This will avoid lots of problems with 2FA which will cause the workflow to stop with no warning. Otherwise, you will have to add the FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
and FASTLANE_SESSION
(more info here). Not only that, but FASTLANE_SESSION
will have to be manually re-generated at least once a month, but depending on your region it could be once per day (as it happened to me)! Not good...
Adjusting the fastlane configuration
There is but one thing we need to add to the Fastfile
(fastlane/Fastfile
): the ability to use a custom macOS
Keychain. We have to do this because we don't have access to the default Keychain in the instance. Throughout the deployment process, fastlane will need to add certificates and provisioning profiles to the Keychain.
In the ios
platform, we will add a new lane like so:
private_lane :setup_project do
if is_ci?
create_keychain(
name: "actiontest_keychain",
password: "meow",
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: false
)
end
end
This lane will create a new Keychain and set it as default. The if is_ci?
will make sure it is only executed when fastlane is running in a GitHub action. This way, you will still be able to run fastlane locally without messing your computer's Keychain Access.
We will also need to tell match to use this Keychain. In the ios build
lane, replace match(type: "appstore")
with:
match(
type: "appstore",
readonly: is_ci,
keychain_name: is_ci? ? "actiontest_keychain" : nil,
keychain_password: is_ci? ? "meow" : nil)
And done! Finally, we can build the workflow!
The workflow
A GitHub Action workflow is configured in a yaml file in the folder .github/workflows
, for example
.github/workflows/publish.yml
.
You can find the full example file at the demo repo. Let's break it down.
Triggering the action
The GitHub action needs a trigger, which is configured with on:
. For example,
on:
push:
tags:
- 'v*'
This triggers the deployment every time we add a git tag that starts with v
(for example, v1.0.3
).
There are lots of other ways to trigger the workflow (see the docs here).
Tip: While configuring the GitHub Action, set it to
on: push
so it runs every time you change something.
Jobs and runners
A GitHub action can contain multiple jobs. Each job will run in parallel in separate instances. Each job contains one or more steps. In our example, we have:
jobs:
publish_apps:
runs-on: macos-latest
steps:
Our workflow has only one job, which will run on a macOS
instance. We need macOS
to build and deploy
for iOS.
git checkout
The first step will be checking out the project with the checkout action with:
- uses: actions/checkout@v2
yarn cache
The cache action will cache the yarn dependencies to avoid downloading and installing every dependency on each run:
- name: Get yarn cache
id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
You can cache npm dependencies too.
Install NativeScript
The macOS
instance comes packed with almost everything we need.
We only have to install the NativeScript tools with:
- name: Install six
run: pip install six
- name: Install NativeScript
run: |
yarn global add nativescript@latest
tns doctor
tns package-manager set yarn
Run the fastlane deploy action for iOS
- name: tns prepare ios
run: tns prepare ios --bundle --release --clean --env.production
- name: fastlane ios beta
env:
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
SIGNING_IDENTITY: ${{ secrets.SIGNING_IDENTITY }}
LANG: en_GB.UTF-8
LC_ALL: en_GB.UTF-8
uses: maierj/fastlane-action@v1.2.0
with:
lane: 'ios beta'
skip-tracking: true
We are running the tns prepare ios
step previously to the fastlane action itself as a
workaround for a filesystem permission issue (... tell me if you find a better solution...).
We use the fastlane action to run the fastlane lane ios beta
.
In this step, we see a bunch of ${{ secrets.* }}
set to the environment. These will be replaced at runtime with the secret variables we set previously.
Run the fastlane deploy action for Android
- name: Create android keystore
run: |
mkdir fastlane/keys
echo "${{ secrets.KEYSTORE_FILE_B64 }}" | base64 -D > fastlane/keys/nsgithubactionsdemo.keystore
- name: fastlane android alpha
env:
KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.KEYSTORE_ALIAS_PASSWORD }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
PLAYSTORE_JSON_KEY_DATA: ${{ secrets.PLAYSTORE_JSON_KEY_DATA }}
LANG: en_GB.UTF-8
LC_ALL: en_GB.UTF-8
uses: maierj/fastlane-action@v1.2.0
with:
lane: 'android beta'
skip-tracking: true
The first step in this section decodes the keystore that was stored in the repository secrets and saves it to a file that will be used later.
The second step runs the fastlane lane android beta
, again with several secrets added to the
environment.
And done!
And that's it! If all goes well, you will see the workflow running on GitHub under "Actions" (e.g. https://github.com/tralves/ns-github-actions-demo/actions) each time you add a new git tag.
I hope this helps! If it did, please share it with me! If it didn't, hit me up on Twitter or the NativeScript Slack and maybe I can help.