Software Engineering | 09 Dec 2022 | 12 min
Welcome to the first part of a continuous integration and continuous delivery (CI/CD) blog series!
In this first blog, I’m going to delve into the fundamentals and use the manual trigger workflow as we are going to handle a different scheme. But you can use the steps I am going to discuss with other options as well.
Also, I’m going to use a self-hosted runner and for that, I’m using my personal Mac – the reason is very simple, I exhausted all my free limit balance at a very initial state. Jokes apart, the reason for using a self-hosted runner is that we can go through every step more practically, like folder structure, folder location, and how keychains are being created where cache is stored, and so on.
Coming to the focus of today’s blog, allow me to first tell you about GitHub Actions.
GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that enables you to automate your workflow for building, testing, and deployment. You can create processes that deploy merged pull requests to the production environment, or you can build and test each pull request before committing it to your repository.
Let’s turn to the ‘why’.
We use GitHub Actions to build React Native apps for the following reasons:
Before moving ahead, I would like to highlight how you can understand this blog series better. It is divided into three parts:
Let’s get started with the initial set-up!
Let me give you a little bit of context here. We have two white-label applications: Nitor and NitorPro, and for both, we have different environments like staging, development, and production. Keeping that in mind, we created our workflow.
In this step, we define our action with name keyword and with on keyword we define when its trigger. Under workflow_dispatch we define our inputs with description, required, type, options and default. Also, you can add as many inputs as you want.
jobs: ios-build: name: IOS Production Build runs-on: self-hosted
We can define our job with jobs keyword and runner in runs-on. Here I’m using self-hosted runner but you can use Ubuntu and macOS-latest.
steps: - name: Check out Git repository uses: actions/checkout@v2 with: persist-credentials: false # make it false if you are going to clone private repositories in yarn
We are using “uses: actions/checkout@v2” with “persist-credentials: false” because in our package.json we used a private repo package which can only be accessed by our team.
- name: git config with PAT_GITHUB run: git config --global url.https://${{ secrets.PAT_GITHUB }}@github.com/.insteadOf https://github.com
PAT_GITHUB is your personal access token that you can store inside action secrets.
Every project has .env for production, development, staging which we cannot push to our repo but for the build, we need that .env file. So, how can we get this env?
The approach is very simple – we have to encode our env and decode it in our repo.
For decode, we are using timheuer/base64-to-file@v1 with filename and ‘encodedString’ options. It will create .env file and store it in a temporary folder. We have to move this file into our current directory.
I’m using this approach because we have almost 50+ ENV variables including dev, and staging. But you can use GitHub Actions secrets as well to store your ENV variables like this:
- run: echo BASE_API_URL=${{ secrets.BASE_API_URL }} >> .env if: ${{ github.event.inputs.flavor == 'staging' || github.event.inputs.flavor == 'development' }}
- run: echo BASE_APP_URL=${{ secrets.BASE_APP_URL }} >> .env if: ${{ github.event.inputs.flavor == 'staging' || github.event.inputs.flavor == 'development' }}
- run: echo BASE_UBER_APP_URL=${{ secrets.BASE_UBER_APP_URL }} >> .env if: ${{ github.event.inputs.flavor == 'staging' || github.event.inputs.flavor == 'development' }}
- run: echo APP_LANDING_PAGE_URL=${{ secrets.APP_LANDING_PAGE_URL }} >> .env
- run: echo GOOGLE_MAPS_PLACES_KEY=${{ secrets.GOOGLE_MAPS_PLACES_KEY }} >> .env
- run: echo GOOGLE_MAPS_API_KEY_ANDROID=${{ secrets.GOOGLE_MAPS_API_KEY_ANDROID }} >> .env
if: ${{ github.event.inputs.target == 'Nitor' }}
This will create a .env file in your root directory and store all your ENV variables.
- name: "move NitorPro_staging" if: ${{ github.event.inputs.target == 'NitorPro' && github.event.inputs.flavor == 'staging'}} run: | mv ${{steps.decode_NitorPro_staging.outputs.filePath}} ./ pwd cat .env
We use the move command for moving it from source to destination i.e. our source is steps.decode_NitorPro_staging.outputs.filePath and destination is ./
- name: Set up our node configuration uses: actions/setup-node@v1 with: node-version: 16.x
Here is how you can get yarn cache directory path using actions/cache@v3:
- name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - name: Restore node_modules from cache uses: actions/cache@v3 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-
Initially it will check for cache dir. If it’s not present, I will create one after comletion of all jobs and next time it will use this dir.
- name: Yarn install #yarn install if: | steps.cache-yarn-cache.outputs.cache-hit != 'true' || steps.cache-node-modules.outputs.cache-hit != 'true' run: | yarn install --network-concurrency 1
There you have it, some tips for the initial setup of CI/CD with GitHub Actions – hope you find them helpful! Stay tuned for Part 2 in this series which will help you set up CI/CD in the world of iOS! Meanwhile, send us an email with your thoughts about Part 1 and visit our website to know more about us.
we'll keep you in the loop with everything that's trending in the tech world.