Skip to main content

NodeJS Quality

Status: production
This workflow is stable and widely used in production environments.
ESLint, Prettier, TSC and Application Tests for NodeJS projects
PR comments from ESLint

PR comments from ESLint

PR comments from TSC

PR comments from TSC

Purpose

This workflow is essentially a quality and style check for a NodeJS project.

It uses ESLint to run checks for problems with your code, prettier to format your code and then a job that will execute your tests. For typescript projects, it will also run the typescript compiler to check for type errors.

Usage

Minimal Example

Below is an example of the minimal configuration required to run the workflow. It's based on sensible defaults but will not satisfy all use cases.

To see how to customise the job(s) to work for your case, refer to inputs, secrets and the Additional examples.

Bare minimum code to insert in your workflow
jobs:
node-quality:
uses: erzz/toolbox/.github/workflows/node-quality.yml@v1
# no mandatory inputs
# no mandatory secrets

Inputs

InputTypeDescriptionRequiredDefault
node-versionstringThe version of NodeJS to usefalse20
install-commandstringUsed to override the default command to install your dependencies e.g. yarn installfalseyarn install --silent
pathstringRelative path from project root to your node application's source filesfalse.
eslint-enablebooleanTo disable the eslint job set to falsefalsetrue
eslint-commandstringUse to execute a standard ES Lint command (e.g. npm lint:ci) instead of the reviewdog actionfalsenull
eslint-report-pathstringRelative path to any eslint report file(s) you wish to persist as job artifacts (e.g. eslint-report.*)falsenull
eslint-flagsstringProvide additional flags to the eslint commandfalse. --ext .js,.jsx,.ts,.tsx
eslint-reporterstringChoose the reporter to use for eslint (github-pr-review, github-pr-check, github-check)falsegithub-check
eslint-levelstringChoose the level of eslint to use (error, warning, info)falseerror
eslint-fail-on-errorbooleanTo not fail the job on eslint errors set to falsefalsetrue
test-enablebooleanTo disable the unit-tests job set to falsefalsetrue
test-commandstringUsed to override the default command to run your unit testsfalseyarn test:unit-ci
coverage-report-enablebooleanTo enable PR coverage report set to true. Requires that the test-command generates a 'coverage/cobertura-coverage.xml' file.falsefalse
prettier-enablebooleanTo disable the prettier job set to falsefalsetrue
prettier-commandstringUsed to override the default command to run prettierfalseyarn prettier:ci
prettier-commitbooleanIf using the write mode of prettier, you can set to true if you wish to commit the changes back to the repositoryfalsefalse
tsc-enablebooleanTo disable the tsc job set to falsefalsetrue
tsc-flagsstringProvide additional flags to the tsc commandfalsenull
tsc-reporterstringChoose the reporter to use for tsc (github-pr-review, github-pr-check, github-check)falsegithub-check
tsc-levelstringChoose the level of tsc to use (error, warning, info)falseerror
tsc-fail-on-errorbooleanTo not fail the job on tsc errors set to falsefalsetrue
artifact-prefixstringA prefix to apply to the name of the artifacts to upload (useful for matrix builds)falsenull

Secrets

InputDescriptionRequired
auth-tokenArbitrary token to be exposed as environment variable in the format VARIABLE=valuefalse
auth-token-2Arbitrary token to be exposed as environment variable in the format VARIABLE=valuefalse

Outputs

OutputDescriptionValue
ref-slugA URL sanitized version of the github ref${{ jobs.lint.outputs.ref-slug }}
short-shaCaptures the short SHA for use in this or later workflow jobs${{ jobs.lint.outputs.short-sha }}

Configuration Notes

info

The workflow itself will not provide the scripts and dependencies. It simply executes your commands and provide artifacts if the appropriate dependencies are installed and configured.

Build authentication

Secrets are provided for build-time authentication with private package registries such as Artifactory, NPM etc.

There are options to provide an auth-token and/or auth-token-2 where the environment variable they are exposed as is configurable.

For example you could expose auth-token: NPM_TOKEN=${{ secrets.SOME_SECRET }} or auth-token: ARTIFACTORY_AUTH_TOKEN=${{ secrets.SOME_SECRET }} depending on what your build process requires.

ESLint

By default the ESLint job utilises an action from reviewdog.

To use your own ESLint script, set the eslint-command input to anything you like (e.g. `yarn lint:ci) and your command will be used instead of reviewdog.

Reviewdog offers different "reporters" to provide feedback on your code. The default reporter is github-check which works for both push and pull_request events. It provides any findings in the summary of the workflow as well as in the logs of the job itself.

The github-pr-check works in a similar way, but only for pull_request events.

The final option github-pr-review which provides review comments on the pull request itself. This is of course only available for pull_request workflows.

Prettier

Executes any prettier command including the option to write the changes back to the files. If you want the job to commit those changes back to the branch, set the prettier-commit input to true.

Unit Tests

Executes any arbitrary test command(s). If those commands/scripts produce test or coverage reports then they can be published as artifacts. See configuration tips below.

Code Coverage Report as comment in Pull Requests

To get a code coverage report as a comment in the pull request, you can use:

node-quality:
uses: ingka-group-digital/workflows/.github/workflows/node-quality.yml@v2
with:
coverage-report-enable: true

Make sure your test-command actually generates a coverage report, otherwise the job will fail.

Additional Examples

Disable specific jobs

For example, for non-typescript projects you can disable the tsc job:

node-quality:
uses: erzz/toolbox/.github/workflows/node-quality.yml@v1
with:
tsc-enable: false

Custom commands and auth tokens

Most relevant commands are customisable, even with multiple commands using the | syntax. You can pass in any environment variables you need to the commands for authentication with private package registries.

node-tests:
uses: erzz/toolbox/.github/workflows/node-quality.yml@v1
with:
install-command: "npm ci"
test-command: |
echo "Running tests"
npm run test
secrets:
auth-token: "NPM_TOKEN=${{ secrets.GH_NPM_AUTH_TOKEN }}"
auth-token-2: "ARTIFACTORY_AUTH_TOKEN=${{ secrets.ARTIFACTORY_AUTH_TOKEN }}"

Tips for Script Configuration

It's difficult to give hard and fast rules about how your node project should be configured, but in order to produce the relevant artifacts something similar to below should be added.

info

These are just examples! Hopefully you know what to do from there for your special ❄️ snowflake ❄️

ESLint HTML Report

To produce an HTML report in your ESLint test script you would use something like:

"lint:ci": "eslint --ext .js,.ts,.vue . -f node_modules/eslint-html-reporter/reporter.js -o $GITHUB_WORKSPACE/eslint-report.html"

However, some reporters such as the one in the example above will not produce stdout output in the job log if they are sending it to file.

If you wish results to be published in multiple formats such as stdout, html and junit - then a package such as eslint-output works fantastic!

You can see here for an example.

Unit Tests

To produce code coverage and HTML reports in your unit test script, you would use something like:

"test:unit-ci": "jest --selectProjects unit --ci --runInBand --reporters jest-html-reporter jest-junit"

with a jest reporter configuration like:

collectCoverage: true,
coverageReporters: ['text', 'cobertura'],
collectCoverageFrom: [
'<rootDir>/components/**/*.vue',
'<rootDir>/pages/**/*.vue',
'<rootDir>/store/**/*.ts',
'<rootDir>/utils/**/*.ts',
],

Workflow details

Source code

View on Github
.github/workflows/node-quality.yml
name: NodeJS Quality
permissions:
checks: write
contents: write
pull-requests: write
on:
workflow_call:
inputs:
node-version:
type: string
description: The version of NodeJS to use
required: false
default: "20"
install-command:
required: false
type: string
description: Used to override the default command to install your dependencies
e.g. yarn install
default: yarn install --silent
path:
required: false
type: string
description: Relative path from project root to your node application's source files
default: .
eslint-enable:
required: false
type: boolean
description: To disable the eslint job set to false
default: true
eslint-command:
required: false
type: string
description: Use to execute a standard ES Lint command (e.g. npm lint:ci)
instead of the reviewdog action
eslint-report-path:
required: false
type: string
description: Relative path to any eslint report file(s) you wish to persist as
job artifacts (e.g. eslint-report.*)
eslint-flags:
required: false
type: string
description: Provide additional flags to the eslint command
default: . --ext .js,.jsx,.ts,.tsx
eslint-reporter:
required: false
type: string
description: Choose the reporter to use for eslint (github-pr-review,
github-pr-check, github-check)
default: github-check
eslint-level:
required: false
type: string
description: Choose the level of eslint to use (error, warning, info)
default: error
eslint-fail-on-error:
required: false
type: boolean
description: To not fail the job on eslint errors set to false
default: true
test-enable:
required: false
type: boolean
description: To disable the unit-tests job set to false
default: true
test-command:
required: false
type: string
description: Used to override the default command to run your unit tests
default: yarn test:unit-ci
coverage-report-enable:
required: false
type: boolean
description: To enable PR coverage report set to true. Requires that the
test-command generates a 'coverage/cobertura-coverage.xml' file.
default: false
prettier-enable:
required: false
type: boolean
description: To disable the prettier job set to false
default: true
prettier-command:
required: false
type: string
description: Used to override the default command to run prettier
default: yarn prettier:ci
prettier-commit:
required: false
type: boolean
description: If using the write mode of prettier, you can set to true if you
wish to commit the changes back to the repository
default: false
tsc-enable:
required: false
type: boolean
description: To disable the tsc job set to false
default: true
tsc-flags:
required: false
type: string
description: Provide additional flags to the tsc command
tsc-reporter:
required: false
type: string
description: Choose the reporter to use for tsc (github-pr-review,
github-pr-check, github-check)
default: github-check
tsc-level:
required: false
type: string
description: Choose the level of tsc to use (error, warning, info)
default: error
tsc-fail-on-error:
required: false
type: boolean
description: To not fail the job on tsc errors set to false
default: true
artifact-prefix:
type: string
description: A prefix to apply to the name of the artifacts to upload (useful
for matrix builds)
required: false
secrets:
auth-token:
required: false
description: Arbitrary token to be exposed as environment variable in the format
VARIABLE=value
auth-token-2:
required: false
description: Arbitrary token to be exposed as environment variable in the format
VARIABLE=value
outputs:
ref-slug:
description: A URL sanitized version of the github ref
value: ${{ jobs.lint.outputs.ref-slug }}
short-sha:
description: Captures the short SHA for use in this or later workflow jobs
value: ${{ jobs.lint.outputs.short-sha }}
jobs:
lint:
name: Lint
if: ${{ inputs.eslint-enable }}
runs-on: ubuntu-24.04
outputs:
short-sha: ${{ env.GITHUB_SHA_SHORT }}
ref-slug: ${{ env.GITHUB_REF_SLUG_URL }}
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Slugify github variables
uses: rlespinasse/github-slug-[email protected]
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Export auth token 1
env:
TOKEN: ${{ secrets.auth-token }}
if: ${{ env.TOKEN != '' }}
run: |
echo "::add-mask::${TOKEN#*=}"
echo "${{ env.TOKEN }}" >> $GITHUB_ENV
- name: Export auth token 2
env:
TOKEN: ${{ secrets.auth-token-2 }}
if: ${{ env.TOKEN != '' }}
run: |
echo "::add-mask::${TOKEN#*=}"
echo "${{ env.TOKEN }}" >> $GITHUB_ENV
- name: Install Dependencies
working-directory: ${{ inputs.path }}
run: |
${{ inputs.install-command }}
- name: Execute ESLint (command)
working-directory: ${{ inputs.path }}
if: ${{ inputs.eslint-command }}
run: |
${{ inputs.eslint-command }}
- name: Upload artifacts (ES Lint Report)
uses: actions/upload-artifact@v4
if: inputs.eslint-command && inputs.eslint-report-path
with:
name: ${{ inputs.artifact-prefix }}ES Lint Report
path: ${{ inputs.eslint-report-path }}
- name: Execute ESLint (reviewdog)
if: ${{ !inputs.eslint-command }}
uses: reviewdog/action-eslint@v1
with:
workdir: ${{ inputs.path }}
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: ${{ inputs.eslint-reporter }}
eslint_flags: ${{ inputs.eslint-flags }}
level: ${{ inputs.eslint-level }}
fail_on_error: ${{ inputs.eslint-fail-on-error }}
prettier:
name: Prettier
if: ${{ inputs.prettier-enable }}
runs-on: ubuntu-24.04
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Export variables
run: |
echo "${{ secrets.auth-token }}" >> $GITHUB_ENV
echo "${{ secrets.auth-token-2 }}" >> $GITHUB_ENV
- name: Install Dependencies
working-directory: ${{ inputs.path }}
run: |
${{ inputs.install-command }}
- name: Prettier
working-directory: ${{ inputs.path }}
run: |
${{ inputs.prettier-command }}
- name: Commit prettier changes
if: ${{ inputs.prettier-commit }}
uses: EndBug/add-and-commit@v9
with:
message: "style: apply prettier formatting"
tests:
name: Tests
runs-on: ubuntu-24.04
if: ${{ inputs.test-enable }}
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Export variables
run: |
echo "${{ secrets.auth-token }}" >> $GITHUB_ENV
echo "${{ secrets.auth-token-2 }}" >> $GITHUB_ENV
- name: Install Dependencies
working-directory: ${{ inputs.path }}
run: |
${{ inputs.install-command }}
- name: Execute Tests
working-directory: ${{ inputs.path }}
run: |
${{ inputs.test-command }}
- name: Generate Coverage Report
uses: clearlyip/code-coverage-report-action@v5
id: code_coverage_report_action
if: inputs.coverage-report-enable && github.actor != 'dependabot[bot]'
with:
filename: ${{ inputs.path }}/coverage/cobertura-coverage.xml
artifact_download_workflow_names: ${{ github.workflow }}
- name: Add Coverage PR Comment
uses: marocchino/sticky-pull-request-comment@v2
if: inputs.coverage-report-enable &&
steps.code_coverage_report_action.outputs.file != '' &&
github.event_name == 'pull_request' && (success() || failure())
with:
recreate: true
path: code-coverage-results.md
- name: Upload Unit Test Reports
uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ inputs.artifact-prefix }}Unit Test & Coverage Reports
path: |
${{ inputs.path }}/junit.xml
${{ inputs.path }}/test-report.html
${{ inputs.path }}/coverage/test-report.html
${{ inputs.path }}/coverage/cobertura-coverage.xml
tsc:
name: TSC
if: ${{ inputs.tsc-enable }}
runs-on: ubuntu-24.04
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Slugify github variables
uses: rlespinasse/github-slug-[email protected]
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Export auth token 1
env:
TOKEN: ${{ secrets.auth-token }}
if: ${{ env.TOKEN != '' }}
run: |
echo "::add-mask::${TOKEN#*=}"
echo "${{ env.TOKEN }}" >> $GITHUB_ENV
- name: Export auth token 2
env:
TOKEN: ${{ secrets.auth-token-2 }}
if: ${{ env.TOKEN != '' }}
run: |
echo "::add-mask::${TOKEN#*=}"
echo "${{ env.TOKEN }}" >> $GITHUB_ENV
- name: Install Dependencies
working-directory: ${{ inputs.path }}
run: |
${{ inputs.install-command }}
- name: Execute TSC
uses: EPMatt/reviewdog-action-tsc@v1
with:
workdir: ${{ inputs.path }}
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: ${{ inputs.tsc-reporter }}
tsc_flags: ${{ inputs.tsc-flags }}
level: ${{ inputs.tsc-level }}
fail_on_error: ${{ inputs.tsc-fail-on-error }}

Job definitions

StepUsesConditional
Checkout Sourceactions/checkout@v4false
Slugify github variablesrlespinasse/[email protected]false
Setup NodeJSactions/setup-node@v4false
Export auth token 1scripttrue
Export auth token 2scripttrue
Install Dependenciesscriptfalse
Execute ESLint (command)scripttrue
Upload artifacts (ES Lint Report)actions/upload-artifact@v4true
Execute ESLint (reviewdog)reviewdog/action-eslint@v1true

StepUsesConditional
Checkout Sourceactions/checkout@v4false
Setup NodeJSactions/setup-node@v4false
Export variablesscriptfalse
Install Dependenciesscriptfalse
Prettierscriptfalse
Commit prettier changesEndBug/add-and-commit@v9true

StepUsesConditional
Checkout Sourceactions/checkout@v4false
Setup NodeJSactions/setup-node@v4false
Export variablesscriptfalse
Install Dependenciesscriptfalse
Execute Testsscriptfalse
Generate Coverage Reportclearlyip/code-coverage-report-action@v5true
Add Coverage PR Commentmarocchino/sticky-pull-request-comment@v2true
Upload Unit Test Reportsactions/upload-artifact@v4true

StepUsesConditional
Checkout Sourceactions/checkout@v4false
Slugify github variablesrlespinasse/[email protected]false
Setup NodeJSactions/setup-node@v4false
Export auth token 1scripttrue
Export auth token 2scripttrue
Install Dependenciesscriptfalse
Execute TSCEPMatt/reviewdog-action-tsc@v1false

Configuration Files

This workflow does not provide any default configuration files

Diagram