DEV Community

Cover image for GitHub Actions With Deno
Craig Morten
Craig Morten

Posted on • Updated on

GitHub Actions With Deno

If you've written a Deno module recently, you most likely will want to ensure that you can lint, test and build it in an automated process!

If you are using a public GitHub repository to store your code then you are in luck! You can use GitHub Actions to automate all of your CI/CD needs for your GitHub project 🎉

Getting started

In your project add the following directory structure and files:

.
├── .github/
│    └── workflows/
│        └── test.yml
│
... // other folders and files
Enter fullscreen mode Exit fullscreen mode

A .github directory in the root of your project containing a workflows directory, and finally a test.yml files in the workflows directory.

This test.yml file will contain all of the instructions for our GitHub Action for testing Deno.

The test file

Add the following to your test.yml file:

name: Test Deno Module

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        deno-version: [1.2.2, 1.3.0]

    steps:
      - name: Git Checkout Deno Module
        uses: actions/checkout@v2
      - name: Use Deno Version ${{ matrix.deno-version }}
        uses: denolib/setup-deno@v2
        with:
          deno-version: ${{ matrix.deno-version }}
      - name: Lint Deno Module
        run: deno fmt --check
      - name: Build Deno Module
        run: deno run --reload mod.ts
      - name: Test Deno Module
        run: deno test --allow-none
Enter fullscreen mode Exit fullscreen mode

There's a lot going on here! So let's break it down 😄

name: Test Deno Module
Enter fullscreen mode Exit fullscreen mode

First we add a name property to the test.yml file. You can change this to something relevant to your module. GitHub displays this as the name of your workflow on your repository's actions page.

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
Enter fullscreen mode Exit fullscreen mode

Next we set the on property to define what triggers we would like to automatically start our action's workflow. For this example we have opted to trigger this workflow whenever a git push is made to the main branch, and also whenever there is a Pull Request that wishes to merge changes into the main branch.

If instead you wanted the action to trigger on every push no matter what branch is used, you could do something like:

on: push
Enter fullscreen mode Exit fullscreen mode

Or if you wanted it to trigger on every push and pull request no matter what branch is used, you could do:

on: [push, pull_request]
Enter fullscreen mode Exit fullscreen mode

You can also specify multiple branches, or a different branch to the main branch.

Finally we define the main part of our GitHub Action workflow, the jobs section:

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        deno-version: [1.2.2, 1.3.0]

    // ... remainder of workflow file
Enter fullscreen mode Exit fullscreen mode

Here we define the a jobs property and add a single job called test. This job is what we will be using to run our lint, test and build.

In the test object we define a runs-on property which let's GitHub know what machine OS we want to use, in this case ubuntu-latest, but there is also windows-latest, macos-latest and others to choose from.

Next we define a strategy object which allows us to define a list of Deno versions that we want this workflow to run against. Each of the subsequent steps are executed for each value in the provided array, in this example we have chosen to test against some of the version 1.x.x releases of Deno.

    steps:
      - name: Git Checkout Deno Module
        uses: actions/checkout@v2
      - name: Use Deno Version ${{ matrix.deno-version }}
        uses: denolib/setup-deno@v2
        with:
          deno-version: ${{ matrix.deno-version }}
      - name: Lint Deno Module
        run: deno fmt --check
      - name: Build Deno Module
        run: deno run --reload mod.ts
      - name: Test Deno Module
        run: deno test --allow-none
Enter fullscreen mode Exit fullscreen mode

Lastly we define our job's steps.

Here we define a list of steps, each with an optional name to make it clear what the step does, and then some other keys to provide instructions on what to run in that step:

  1. First we use the official actions/checkout@v2 action for performing a git checkout of our git repository onto the GitHub Action build server so we are able to test our module. This is defined using the uses parameter.

  2. Next we use the denolib/setup-deno@v2 action to install and configure Deno on the GitHub Action build server.

    Here we also use the additional with property to specify the variable deno-version, which the setup-deno action requires in order to know what version of Deno to install.

    Notice here we are using a special syntax ${{ matrix.deno-version }}. This is called an expression and it will evaluate to the value inside the double curly braces, in this case, the deno-version which is taken from the matrix list we provided earlier.

  3. Finally we define three more steps: one to lint, one to build and one to test our Deno module. Here we use the run property which allows us to run any command we like using the build server's shell.

    For linting we use the deno fmt --check command and flag which will check if our code is properly formatted, and exit with an error if not. If this errors, you can fix your Deno module's code formatting by running the deno fmt command (without any flags).

    For building our module we run deno run --reload mod.ts, where mod.ts is the main entypoint / starting point of our code (change the file depending on your repository setup). Here we are using the --reload flag to ensure that we fully rebuild the module and all of it's third party modules. This is to make sure that we don't accidentally rely on a cached version of the module or any third party dependencies, as this would give us a false impression of whether the module will build properly on a new module user's computer.

    For testing we use the deno test --allow-none command which will run all tests available in your repository. The --allow-none flag means that the command won't fail if your repository doesn't have any tests. If you want the test to fail if it can't find any tests, just remove the flag.

If any of the above steps fail the GitHub Action workflow will report the failure with the logs messages.

And that's it! 🎉 🎉

This workflow will work with any Deno project straight away so long as it uses the main branch as it's main branch. All you need to do is copy the test.yml file to the correct place in your repository and git push the code to GitHub.

You can then check your actions using the Actions page on your GitHub repo, and every Pull Request into the main branch and every commit to main will get a status check tick or cross depending on success or failure.

If you want to find out more about GitHub Actions, check out their Workflow Syntax Docs.

That's it peeps! Would love to hear your thoughts and how you're getting on with Deno and if you've done anything cool with GitHub Actions - drop your comments and questions below!

Top comments (1)

Collapse
 
th0rgall profile image
Thor Galle

Thanks for the detailed writeup! I didn't know about the version matrix option, might use it in the future.

With two years passed since this post, some parts here are oudated:

Deno also has official docs now for setting up a Github worfklow.

I also believe the deno run --reload step is both unnecessary and outdated. Unnecessary, because by default Github Action runners don't cache dependencies. And outdated, because if you would want to cache deps across runner sections, you should use actions/cache as described in the Deno docs. Also, deno cache --reload seems preferable here over the deno run --reload command. By using run, you run your actual code in preparation of running tests, which might not be desirable (in my case, my code runs a server).

With those changes, I got the following single-version workflow for my project, without cache reloading:

name: tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  DENO_VERSION: "1.30.3"
  DENO_DIR: ~/.deno-cache

jobs:
  test:
    runs-on: ubuntu-22.04
    steps:
      - name: Git Checkout Deno Module
        uses: actions/checkout@v3
      - name: Use Deno Version ${{ env.DENO_VERSION }}
        uses: denoland/setup-deno@v1
        with:
          deno-version: ${{ env.DENO_VERSION }}
      - name: Lint Deno Module
        run: deno fmt --check
      # Follows https://deno.land/manual@v1.30.1/advanced/continuous_integration#caching-dependencies
      # but use the latest cache@v3
      - name: Cache Deno dependencies 
        uses: actions/cache@v3
        with:
          path: ${{ env.DENO_DIR }}
          key: ${{ hashFiles('deno.lock') }}
      - name: Run Deno Tests
        run: deno test --allow-read --allow-env
Enter fullscreen mode Exit fullscreen mode