Setting up GitHub Actions for a React/Node Project

This week I had a chance to try out GitHub Actions — GitHub’s continuous integration solution. I was at the point in a project where I would normally turn to CircleCI, so I thought I’d give GitHub Actions a try. If it worked out, that would be one less service I’d have to sign up and pay for.

At first glance, GitHub Actions looks a lot like CircleCI. You have similar concepts like workflows, jobs, and steps defined in a config file using YAML syntax. (The web project I’m trying this out on uses React, Node, Postgres, Yarn, and Typescript running on Heroku.)

GitHub Actions provides many workflow templates to get you started. I didn’t find one that exactly matched my scenario, so I started with the “Simple Workflow” template.

When to Trigger the Workflow

I want to trigger my workflow in two cases:

  • When someone opens a pull request from any branch.
  • When anyone checks in code to my Develop or Master branch.

This requires a simple on: statement at the top of the file:

name: CI
on:
  pull_request:
  push:
    branches:
      - master
      - dev

Jobs

I want the workflow to run two jobs: one job runs my tests, and the other deploys to Heroku. I want the test job to run every time the workflow is triggered. I want the deploy job to run:

jobs: test: name: Test # ... deploy: name: Deploy needs: test if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev') # ...

The deploy job has a needs: test statement that will only run the Deploy job if the Test job was successful. The if: statement applies to the entire job and will only run it if we are pushing to the Develop or Master branches.

Testing

Now let’s get into more detail on how the test job is configured. I need to check out the code, set up Node and Postgres, install the dependencies utilizing a cache to speed up the process, and run the tests.

test:
  name: Test
  runs-on: ubuntu-latest
  env:
    NODE_ENV: test

  steps:
  - name: Checkout
    uses: actions/checkout@v1

  - 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-

  - uses: actions/setup-node@v1.1.0
    with:
      node-version: '10.x'

  - run: yarn install

  - name: Setup PostgreSQL
    uses: harmon758/postgresql-action@v1.0.0
    with:
      postgresql version: 11
      postgresql db: databasename-test
      postgresql user: dev
      postgresql password: dev

  - run: yarn lint
  - run: knex migrate:latest
  - run: yarn test

This is probably a good time to introduce the GitHub Marketplace for Actions . In the script above, whenever you see a step that has a uses: statement, I am using an Action from the GitHub Action Marketplace to do a task for me.

There are many Actions you can use to do all sorts of tasks in your workflow. In my test job, I am using actions for checking out the code, caching yarn dependencies, setting up Node, and setting up Postgres. All of them were easy to set up and were well documented.

Any of the other steps in the test job that use the run: statement are running commands on the virtual machine in my project directory. Most of these yarn commands are ones that I have defined in my package.json file, so they are somewhat unique to my project.

Deploy

My project is running on Heroku. I have two apps on Heroku: one for QA and the other for production. If I am triggering this workflow from the Develop branch, I want to deploy to the QA app. If I am on the Master branch, I want to deploy to production. I do this by setting an environment variable called ENVIRONMENT . Then I use this environment variable to resolve the name of the appropriate Heroku app.

The other environment variable I configure is the HEROKU_API_KEY . The key is stored as a GitHub Secret with the same name. Secrets are not automatically pulled into your workflow, so this step of assigning it to a local env: statement is necessary.

To specify which branch/commit to push to Heroku, I am using an automatic environment variable provided to me by GitHub Actions. The GITHUB_SHA is the unique ID for the current commit that triggered the workflow.

deploy:
  name: Deploy
  runs-on: ubuntu-latest
  needs: test
  if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev')
  env:
    HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}

  steps:
    - name: Checkout
      uses: actions/checkout@v1

    - name: Set QA Environment
      run: echo "::set-env name=ENVIRONMENT::qa"

    - name: Set Production Environment
      if: github.ref == 'refs/heads/master'
      run: echo "::set-env name=ENVIRONMENT::prod"

    - name: Push to Heroku
      run: git push --force https://heroku:$HEROKU_API_KEY@git.heroku.com/my-app-name-$ENVIRONMENT.git $GITHUB_SHA:master

    - name: Run Database Migrations
      run: heroku run knex migrate:latest --app my-app-name-$ENVIRONMENT

The Full Workflow

Here is the full workflow that contains my two jobs.

name: CI
on:
  pull_request:
  push:
    branches:
      - master
      - dev

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    env:
      NODE_ENV: test

    steps:
    - name: Checkout
      uses: actions/checkout@v1

    - 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-

    - uses: actions/setup-node@v1.1.0
      with:
        node-version: '10.x'

    - run: yarn install

    - name: Setup PostgreSQL
      uses: harmon758/postgresql-action@v1.0.0
      with:
        postgresql version: 11
        postgresql db: databasename-test
        postgresql user: dev
        postgresql password: dev

    - run: yarn lint
    - run: knex migrate:latest
    - run: yarn test:server

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: test
    if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev')
    env:
      HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Set QA Environment
        run: echo "::set-env name=ENVIRONMENT::qa"

      - name: Set Production Environment
        if: github.ref == 'refs/heads/master'
        run: echo "::set-env name=ENVIRONMENT::prod"

      - name: Configure Heroku
        run: heroku config:set GIT_HASH=${GITHUB_SHA} GIT_BRANCH=${GITHUB_REF} --app my-app-name-$ENVIRONMENT

      - name: Push to Heroku
        run: git push --force https://heroku:$HEROKU_API_KEY@git.heroku.com/my-app-name-$ENVIRONMENT.git $GITHUB_SHA:master

      - name: Run Database Migrations
        run: heroku run knex migrate:latest --app my-app-name-$ENVIRONMENT

When a pull request is created or when code is finally merged to Develop or Master, GitHub will run my workflow. You can check the status of your workflow by clicking on the “Actions” tab on your GitHub project page.

If you are needing a CI server, and you are already paying for GitHub, give GitHub Actions a try. It did everything I needed it to do, so I will not be going back to CircleCI.

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章