Deno and GitHub Actions workflows
Tags: deno, devops, githubactions
Published: 2024-03-09 10:12:15

Today I want to talk about Deno and GitHub Actions. For the last year or so, I have only built new projects with Deno. I really like the runtime, it's typescript out of the box config, formatting, linting etc. It is just a lot of fun to work with.

Since I still wanted to make sure that everything works when pushed to our Github repositories, I had to build some GitHub actions. Today I want to share a repository where I collect useful actions. At the time of writing, the repository contains 3 different workflows. You can find the repository at [niklasmtj/deno-actions on GitHub](https://github.com/niklasmtj/deno-actions).

## CI
The CI workflow runs on every PR creation or commit. It checks the format, runs tests, and lints the whole project. Since these steps are usually really fast, I did not limit it to just the changed files.

```yaml
name: "CI"

on:
  - pull_request

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - name: "Checkout"
        uses: actions/checkout@v4

      - name: "Setup Deno"
        uses: denoland/setup-deno@v1
        with:
          deno-version: "1.x"

      - name: "Run Tests"
        run: deno task test

      - name: "Run Lint"
        run: deno task lint

      - name: "Run Format"
        run: deno task format
```

To run this workflow you need to set up 3 tasks in the `deno.json` file. First set up `test` with the required permissions. Then `lint` and `format`. This is done in `deno.json` under the `tasks` key as you can see in the following block of code:
```json
{
  "tasks": {
    "lint": "deno lint",
    "format": "deno fmt --check",
    "test": "deno test --allow-read --allow-write --allow-net",
  }
}
```

This example uses the `--allow-read --allow-write --allow-net' flags to run the tests. When setting up your workflow tasks, you can easily check the commands locally. Deno will help you figure out the permission flags you need.

## Build Docker Container Images
The second action will build a container image using Docker. The workflow runs when new pushes are made to the `main' branch. This happens most often after merging a pull request.

Make sure that you have a `Dockerfile` describing your desired container image in the root directory of the repository. This is used by the `build-and-push` action as it's image recipe.

It is set up using `buildx`, which allows you to build multi-architecture images. For example, if you are building your containers locally on an M1 Mac, these are `arm64` images. If your hosting provider does not support `arm64` images, but rather uses Intel or AMD processors on their servers, you will need to build `amd64` images. Using `buildx` and `qemu` we can make sure we're able to build both in the same build. This is why you see `linux/amd64,linux/arm64` in the platforms fields in the following workflow file:

```yaml
name: "Build Container Image"

on:
  push:
    branches:
      - main

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - name: "Checkout"
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
        with:
          platforms: linux/amd64,linux/arm64

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          platforms: linux/amd64,linux/arm64

      # Login to Docker Hub or any other registry here
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: niklasmtj/deno-image:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
```

The built image here will then be pushed to Docker Hub after login. Make sure your organization or repository already has the `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` secrets set. These are set in a repository under `Settings -> Secrets and Variables -> Actions -> Repository secrets'. How to obtain a Docker Hub token can be found in the [Docker documentation](https://docs.docker.com/security/for-developers/access-tokens/#create-an-access-token).

Caching is also set up when the container image is built to ensure that the subsequent execution is as fast as possible.

## Create a Test Coverage Website and Publish to GitHub Pages
Testing should be an integral part of application development. To make sure that every part of the application is covered with tests, Deno supports creating a coverage report right out of the box (🤝). The third action supports building just that. Create the coverage and push it to GitHub pages. To be able to publish, you need to make sure that GitHub Pages is enabled for the repository you want to build coverage for.

```yaml
name: Code Coverage

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  ci:
    runs-on: ubuntu-latest

    steps:
      - name: Setup repo
        uses: actions/checkout@v4

      - name: Setup Deno
        uses: denoland/setup-deno@v1

      - name: Create lcov file
        run: deno task coverage

      - name: Setup Pages
        uses: actions/configure-pages@v4

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: './coverage/html'

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
```

One thing you need to make sure is that GitHub Actions has permissions to publish the created website. Make sure you set it under the `permissions` key in the yaml file.

## Conclusion
This repository is not set in stone. I will try to keep the dependencies up to date (shout out to Dependabot) and will add or change actions in the future. So make sure you keep an eye on the repo (click the watch button in the top right corner).

Thanks for reading and have a great day!