Continuous Integration

Deno’s built-in tools make it easy to set up Continuous Integration (CI) pipelines for your projects. Testing, linting and formatting of code can all be done with the corresponding commands deno test, deno lint and deno fmt. In addition, you can generate code coverage reports from test results with deno coverage in pipelines.

The example below shows how to set up a basic pipeline for Deno projects in GitHub Actions:

  1. name: Build
  2. on: [ push, pull_request ]
  3. jobs:
  4. build:
  5. runs-on: ubuntu-latest
  6. steps:
  7. - name: Set up Actions
  8. uses: actions/checkout@v2
  9. - name: Set up Deno
  10. uses: denoland/setup-deno@v1.0.0
  11. with:
  12. deno-version: v1.x

All this pipeline does at the moment is set up GitHub Actions and Deno, and it is configured to trigger a workflow run on push and pull request events. Note that in the example the latest version of ubuntu image is used, but you could specify an exact version for added stability in a production pipeline, such as ubuntu-20.04.

To expand the workflow you can add any of the deno CLI commands that you might need. The code below shows how to check the formatting, lint the code, run the tests and generate a test coverage report, all as part of a build job:

  1. jobs:
  2. build:
  3. runs-on: ubuntu-20.04
  4. steps:
  5. - name: Set up Actions
  6. uses: actions/checkout@v2
  7. - name: Set up Deno
  8. uses: denoland/setup-deno@v1.0.0
  9. with:
  10. deno-version: v1.x
  11. - name: Check formatting
  12. run: deno fmt --check
  13. - name: Analyze code
  14. run: deno lint
  15. - name: Run unit and integration tests
  16. run: deno test -A --coverage=cov --doc
  17. - name: Generate coverage report
  18. run: deno coverage --lcov cov > cov.lcov

Let’s go over the steps one by one.

  1. - name: Check formatting
  2. run: deno fmt --check

This simply checks if the project code is formatted according to Deno’s default formatting conventions.

  1. - name: Analyze code
  2. run: deno lint

In this step the deno lint command checks for syntax and style errors. If necessary, you can pass a deno.json configuration file with custom linter rules.

  1. - name: Run unit and integration tests
  2. run: deno test -A --coverage=cov --doc

Here, Deno runs some tests with a lot of options being passed along! This example runs with all permissions (-A) but in reality you may only need a subset of permissions to run your tests, such as --allow-read or --allow-env. Test coverage is generated with --coverage into an output directory cov and finally, --doc is provided to typecheck any code blocks in the project’s documentation.

The final step creates a coverage report from the results of deno test in .lcov format, which you could then upload to one of the various code coverage platforms available on the Web:

  1. - name: Generate coverage report
  2. run: deno coverage --lcov cov > cov.lcov

Cross-platform workflows

As a Deno module maintainer, you probably want to know that your code works on all of the major operating systems in use today: Linux, MacOS and Windows. A Cross-platform workflow can be achieved by running a matrix of parallel jobs in GitHub Actions, each one running your build on a different operating system:

  1. jobs:
  2. build:
  3. runs-on: ${{ matrix.os }}
  4. strategy:
  5. matrix:
  6. os: [ ubuntu-latest, macos-latest, windows-latest ]
  7. steps:
  8. # build goes here

Note: GitHub Actions has a known issue with handling Windows-style line endings (CRLF). This may cause issues when running deno fmt in a pipeline with jobs that run on windows. To solve this, configure the Actions runner to use Linux-style line-endings with git config --system core.autocrlf false and git config --system core.eol lf before running uses: actions/checkout@v2 in the pipeline.

There can be parts of the pipeline that don’t make sense to run for every OS. For example, generating the same coverage report on Linux, MacOS and Windows is a bit redundant. You can use the conditional if keyword in these cases to reduce repetition:

  1. - name: Generate coverage report
  2. if: ${{ matrix.os == 'ubuntu-latest' }}
  3. run: deno coverage --lcov cov > cov.lcov

The same applies to uploading coverage to a reporter like Coveralls, for example:

  1. - name: Upload coverage report
  2. if: ${{ matrix.os == 'ubuntu-latest' }}
  3. uses: coverallsapp/github-action@master
  4. with:
  5. github-token: ${{ secrets.GITHUB_TOKEN }}
  6. path-to-lcov: cov.lcov