Linting code

With your new workflow ci.yml, you are able to run unit tests. But another key operation in most CI/CD workflows is linting the project, ensuring that the code follows a certain standard and set of conventions.

Using Github Actions, we want a pull request to fail if the branch contains poorly linted code.

Given that we have already demonstrated how pull_request events trigger the workflow and have verified that our workflow works correctly with unit testing, we will be demonstrating the aforementioned using the workflow_dispatch event instead for the sake of simplicity.

Constructing the workflow

As described in the previous section, we think of answering two key questions when constructing the workflow:

  1. "When will it run?" — established to be during pull_request (and additionally workflow_dispatch for testing)

  2. "What will it do?" — execute the yarn lint script given in the package.json

However, there is an additional question we will want to answer, given that we already have an existing workflow:

  1. "Is this going to be a separate workflow? A separate job in the same workflow? Or just another step in the existing job?"

There is no right or wrong answer for the above. But it is worth considering the following factors:

  • Is this a part of the CI workflow? — yes, so we might not want to separate it out

  • Is the task a part of unit testing? — no, so we might want to split it out to avoid cluttering a single job

So in this case, we choose to create a separate job within the same workflow ci.yml. By default, jobs will run in parallel, but can be designed to run sequentially. So, we get the added benefit of having both linting and unit tests running in parallel, saving time (arguable since we need to reinstall the project dependencies in each job, but as jobs get more complex, running them in parallel will allow simpler ones to complete first), and preventing the results of one job from affecting the other (one can fail while the others pass).

Designing the workflow

# .github/workflows/ci.yml
name: CI/CD Pipeline
on: [pull_request, workflow_dispatch]
jobs:
  linting:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch repository
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'yarn'
      - name: Install dependencies
        run: |
          yarn
      - name: Lint code
        run: |
          yarn lint

  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch repository
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'yarn'
      - name: Install dependencies
        run: |
          yarn
      - name: Run unit tests
        run: |
          NODE_ENV=production yarn test

Breaking it down

You will notice that every step except the last is the same as the unit-tests job. That is because the initial setup of the virtual machine runner does not change! We still need to

  1. Fetch the repository

  2. Setup Node.js

  3. Install project dependencies

And this is all because all jobs run in separate virtual machine runners! So linting does not share these steps with unit-tests .

Linting code

      - name: Lint code
        run: |
          yarn lint

The only step that differs between linting and unit-tests is the linting step, which we rely on the provided lint script in package.json, which calls eslint ..

Visualizing the workflow

Now, the single workflow has evolved to include two parallel jobs!

Verifying the workflow

As mentioned at the start of this section, we will be verifying that the linting works by using the workflow_dispatch event. So, once again, push the latest changes to ci.yml to your fork and manually run the workflow:

git add .github/workflows/ci.yml
git commit -m "Add linting step"
git push -u origin main

This time, you will see that there are now two separate jobs running within the same workflow:

Both of them will also complete at around the same time since both linting and unit tests are relatively small at this time:

Try playing around with this new job. Create a branch and purposely commit and PR poorly linted code and see what happens! The linting job should fail while the unit-tests job will continue to work.

Poorly linted code

Add the following to the top of the App.tsx file:

  if (true) {
    useEffect(() => console.log("hi"), [])
  }

Amazing! We have not only setup a CI/CD workflow that runs unit tests, but also linting, and both run in parallel and don't affect each other's outcomes!

Let's tackle the the final piece of the puzzle: deploying the application to Github Pages!

Last updated