GitLab CI template for Node.js¶
This project implements a GitLab CI/CD template to build, test and analyse your JavaScript/TypeScript/Node.js projects.
More precisely, it can be used by all projects based on npm, yarn or pnpm package managers.
Usage¶
This template can be used both as a CI/CD component
or using the legacy include:project
syntax.
Use as a CI/CD component¶
Add the following to your .gitlab-ci.yml
:
include:
# 1: include the component
- component: $CI_SERVER_FQDN/to-be-continuous/node/gitlab-ci-node@4.1.0
# 2: set/override component inputs
inputs:
image: "registry.hub.docker.com/library/node:20" # ⚠ this is only an example
lint-enabled: "true"
Use as a CI/CD template (legacy)¶
Add the following to your .gitlab-ci.yml
:
include:
# 1: include the template
- project: "to-be-continuous/node"
ref: "4.1.0"
file: "/templates/gitlab-ci-node.yml"
variables:
# 2: set/override template variables
NODE_IMAGE: "registry.hub.docker.com/library/node:20" # ⚠ this is only an example
NODE_LINT_ENABLED: "true"
Global configuration¶
The Node.js template uses some global configuration used throughout all jobs.
Input / Variable | Description | Default value |
---|---|---|
image / NODE_IMAGE |
The Docker image used to run Node.js set the version required by your project |
registry.hub.docker.com/library/node:lts-alpine |
manager / NODE_MANAGER |
The package manager used by your project (one of npm , yarn or pnpm )If undefined, automatic detection |
none (auto) |
config-registry / NODE_CONFIG_REGISTRY |
Main npm registry to use | none |
config-scoped-registries / NODE_CONFIG_SCOPED_REGISTRIES |
Space separated list of npm scoped registries (formatted as @somescope:https://some.npm.registry/some/repo @anotherscope:https://another.npm.registry/another/repo ) |
none |
project-dir / NODE_PROJECT_DIR |
Node project root directory | . |
source-dir / NODE_SOURCE_DIR |
Sources directory | src |
install-extra-opts / NODE_INSTALL_EXTRA_OPTS |
Extra options to install project dependencies (either npm ci , yarn install or pnpm install ) |
none |
Using scoped registries¶
Scoped registries allow to pull and publish packages using multiple registries.
Examples:
npm install foo
installsfoo
package from https://www.npmjs.com/ by default,npm install @angular/core
installs@angular/core
package from https://www.npmjs.com/ if no npm registry associated to scope@angular
is declared,npm install @acme-corp/bar
installs@acme-corp/bar
package from https://registry.acme.corp/npm if this registry url is associated to scope@acme-corp
.
First of all, be aware that the Node.js template automatically configures the GitLab's project-level npm packages registry associated to a scope corresponding to the root of the project (ex: project https://gitlab.example.com/my-org/engineering-group/team-amazing/analytics
will have GitLab's project-level npm packages registry scope @my-org
).
Therefore, GitLab's project-level npm packages registry can freely be used both to install packages (with the right scope) or even to publish your own packages.
You may configure additional scoped registries with the $NODE_CONFIG_SCOPED_REGISTRIES
variable.
The value is expected as a (whitespace-separated) list of @registry-scope:registry-url
.
The Node.js template also supports authentication for each, simply by defining the appropriate variable (as project or group secret variables) depending on the desired authentication method:
NODE_REGISTRY_<SCOPE>_AUTH_TOKEN
: authentication tokenNODE_REGISTRY_<SCOPE>_AUTH_BASIC
: base64 authentication string (base64(username + ':' + password)
)
The <SCOPE>
part is the registry-scope
transformed in SCREAMING_SNAKE_CASE (uppercase words separated by underscores).
Example:
variables:
NODE_CONFIG_SCOPED_REGISTRIES: "@public-repo:https://public.npm.registry/some/repo @my-priv-repo:https://private.npm.registry/another/repo"
# NODE_REGISTRY_MY_PRIV_REPO_AUTH set as a project secret variables
Jobs¶
node-lint
job¶
The Node template features a node-lint
job that performs a code analysis with ESLint.
This job is disabled by default. It can be activated by setting NODE_LINT_ENABLED
.
It is bound to the build
stage, and uses the following variable:
Input / Variable | Description | Default value |
---|---|---|
lint-enabled / NODE_LINT_ENABLED |
Set to true to enable lint analysis |
none (disabled) |
lint-args / NODE_LINT_ARGS |
npm run script arguments to execute the lint analysis yarn run script arguments to execute the lint analysis pnpm run script arguments to execute the lint analysis |
run lint |
In addition to a textual report in the console, this job produces the following reports, kept for one day:
Report | Format | Usage |
---|---|---|
$NODE_PROJECT_DIR/reports/node-lint.gitlab.json |
GitLab | GitLab integration |
$NODE_PROJECT_DIR/reports/node-lint.xslint.json |
JSON ESLint | SonarQube integration |
Report | Format | Usage |
---|---|---|
$NODE_PROJECT_DIR/reports/npm-audit.native.json |
JSON | DefectDojo integration This report is generated only if DefectDojo template is detected, if needed, you can force it with $DEFECTDOJO_NPMAUDIT_REPORTS |
node-build
job¶
The Node template features a job node-build
that performs build and tests all at once. You can disable the build using the variable NODE_BUILD_DISABLED
Those stages are performed in a single job for optimization purpose (it saves time) and also for jobs dependency reasons (some jobs such as SONAR analysis have a dependency on test results).
This job is bound to the build
stage, and uses the following variables:
Input / Variable | Description | Default value |
---|---|---|
build-disabled / NODE_BUILD_DISABLED |
Set to true to disable build |
none (enabled) |
build-dir / NODE_BUILD_DIR |
Variable to define build directory | dist |
build-args / NODE_BUILD_ARGS |
npm run script arguments yarn run script arguments pnpm run script arguments ⚠ default value should be overridden for pnpm as --prod is not a valid option. |
run build --prod |
test-args / NODE_TEST_ARGS |
npm test arguments yarn test arguments pnpm test arguments |
test -- --coverage |
Unit Tests and Code Coverage reports¶
This chapter details the required configuration (depending on the unit testing framework you're using) in order to integrate your unit tests reports and code coverage reports to GitLab.
Additionally, if also using SonarQube, you'll have to enable some extra reporters.
Unit testing with Jest¶
Here is the required configuration if you're using Jest as unit testing framework.
Reporter | Needs npm install |
Expected report file | Usage |
---|---|---|---|
jest-junit | Yes | reports/node-test.xunit.xml |
GitLab unit tests integration (JUnit format) |
istanbul text | No | N/A (stdout) | GitLab MR test coverage results (GitLab grabs coverage from stdout) |
istanbul cobertura | No | reports/cobertura-coverage.xml |
GitLab code coverage integration (Cobertura format) |
jest-sonar | Yes | reports/node-test.sonar.xml |
SonarQube unit tests integration (generic SonarQube format) |
istanbul lcovonly | No | reports/lcov.info |
SonarQube code coverage integration (JS/TS LCOV format) |
Here is an example of a jest.config.js
configuration file with all the above reporters configured as expected:
reporters: [
"default",
// 'jest-junit' to enable GitLab unit test report integration
[
"jest-junit",
{
outputDirectory: "reports",
outputName: "node-test.xunit.xml",
},
],
// [OPTIONAL] only if using SonarQube
// 'jest-sonar' to enable SonarQube unit test report integration
[
"jest-sonar",
{
outputDirectory: "reports",
outputName: "node-test.sonar.xml",
},
],
],
coverageDirectory: "reports",
coverageReporters: [
// 'text' to let GitLab grab coverage from stdout
"text",
// 'cobertura' to enable GitLab test coverage visualization
"cobertura",
// [OPTIONAL] only if using SonarQube
// 'lcovonly' to enable SonarQube test coverage reporting
"lcovonly",
],
Unit testing with Mocha¶
Here is the required configuration if you're using Mocha as unit testing framework.
Reporter | Needs npm install |
Expected report file | Usage |
---|---|---|---|
mocha-junit-reporter | Yes | reports/node-test.xunit.xml |
GitLab unit tests integration (JUnit format) |
istanbul text | Yes (in nyc package) |
N/A (stdout) | GitLab MR test coverage results (GitLab grabs coverage from stdout) |
istanbul cobertura | Yes (in nyc package) |
reports/cobertura-coverage.xml |
GitLab code coverage integration (Cobertura format) |
mocha-sonarqube-reporter | Yes | reports/node-test.sonar.xml |
SonarQube unit tests integration (generic SonarQube format) |
istanbul lcovonly | Yes (in nyc package) |
reports/lcov.info |
SonarQube code coverage integration (JS/TS LCOV format) |
Remarks:
- By default - unlike Jest - Mocha doesn't provide code coverage. To do so you need to install Istanbul package (
nyc
):
npm install --save-dev nyc
- the default
xunit
Mocha reporter doesn't produce a JUnit format supported by GitLab, that's why we recommend you to usemocha-junit-reporter
instead. - Mocha doesn't support multiple unit tests reporters. So unfortunaltely, if you're using SonarQube, you'll have to choose which report you want to generate. Another option is to use mocha-multi-reporters (see documentation)
Mocha may be either configured with CLI options of using separate Mocha and nyc
config files.
Here is the required configuration with CLI options directly in the package.json
file:
"scripts": {
"test": "npm run mocha",
"mocha": "nyc --report-dir=reports --reporter=text --reporter=lcovonly --reporter=cobertura mocha --reporter mocha-junit-reporter --reporter-option mochaFile=reports/node-test.xunit.xml test/*.js",
...
},
...
Here is the equivalent using separate config files:
package.json
:
"scripts": {
"test": "npm run mocha",
"mocha": "nyc mocha test/*.js",
...
},
...
-
.mocharc.json
: -
with
mocha-junit-reporter
(for GitLab):{ "reporter": "mocha-junit-reporter", "reporter-option": ["mochaFile=reports/node-test.xunit.xml"] }
-
with
mocha-sonarqube-reporter
(for SonarQube):{ "reporter": "mocha-sonarqube-reporter", "reporter-option": ["output=reports/node-test.sonar.xml"] }
-
with both (using
mocha-multi-reporters
):{ "reporter": "mocha-multi-reporters", "reporter-option": ["configFile=.mmr.json"] }
With
.mmr.json
:{ "reporterEnabled": "spec, mocha-junit-reporter, mocha-sonarqube-reporter", "mochaJunitReporterReporterOptions": { "mochaFile": "reports/node-test.xunit.xml" }, "mochaSonarqubeReporterReporterOptions": { "output": "reports/node-test.sonar.xml" } }
-
.nycrc.json
:
{
"reporter": ["text", "cobertura", "lcovonly"],
"report-dir": "reports"
}
Unit testing with Jasmine¶
Support of Jasmine as unit testing framework is not documented yet and will come soon in a further version of this template.
SonarQube analysis¶
If you're using the SonarQube template to analyse your Node code, here are 2 sample sonar-project.properties
files.
If using JavaScript language:
# see: https://docs.sonarqube.org/latest/analyzing-source-code/test-coverage/javascript-typescript-test-coverage/
# set your source directory(ies) here (relative to the sonar-project.properties file)
sonar.sources=.
# exclude unwanted directories and files from being analysed
sonar.exclusions=node_modules/**,dist/**,**/*.test.js
# set your tests directory(ies) here (relative to the sonar-project.properties file)
sonar.tests=.
sonar.test.inclusions=**/*.test.js
# tests report: generic format
sonar.testExecutionReportPaths=reports/node-test.sonar.xml
# lint report: ESLint JSON
sonar.eslint.reportPaths=reports/node-lint.xslint.json
# coverage report: LCOV format
sonar.javascript.lcov.reportPaths=reports/lcov.info
If using TypeScript language:
# see: https://docs.sonarqube.org/latest/analyzing-source-code/test-coverage/javascript-typescript-test-coverage/
# set your source directory(ies) here (relative to the sonar-project.properties file)
sonar.sources=src
# exclude unwanted directories and files from being analysed
sonar.exclusions=node_modules/**,dist/**,**/*.spec.ts
# set your tests directory(ies) here (relative to the sonar-project.properties file)
sonar.tests=src
sonar.test.inclusions=**/*.spec.ts
# tests report: generic format
sonar.testExecutionReportPaths=reports/node-test.sonar.xml
# lint report: TSLint JSON
sonar.typescript.tslint.reportPaths=reports/node-lint.xslint.json
# coverage report: LCOV format
sonar.typescript.lcov.reportPaths=reports/lcov.info
More info:
- JavaScript language support
- TypeScript language support
- test coverage & execution parameters
- third-party issues
node-audit
job¶
The Node template features a job node-audit
that performs an audit (npm audit, yarn audit or pnpm audit) to find vulnerabilities (security).
It is bound to the test
stage.
Input / Variable | Description | Default value |
---|---|---|
audit-disabled / NODE_AUDIT_DISABLED |
Set to true to disable npm audit |
none (enabled) |
audit-args / NODE_AUDIT_ARGS |
npm audit arguments yarn audit arguments pnpm audit arguments |
--audit-level=low |
In addition to a textual report in the console, this job produces the following report, kept for one day and only available for download by users with the Developer role or higher:
Report | Format | Usage |
---|---|---|
$NODE_PROJECT_DIR/reports/npm-audit.native.json |
JSON | DefectDojo integration This report is generated only if DefectDojo template is detected, if needed, you can force it with $DEFECTDOJO_NPMAUDIT_REPORTS |
node-outdated
job¶
The Node template features a job node-outdated
that performs outdated analysis (npm outdated, yarn outdated or pnpm outdated to find dependencies that might be updated.
It is bound to the test
stage.
Input / Variable | Description | Default value |
---|---|---|
outdated-disabled / NODE_OUTDATED_DISABLED |
Set to true to disable npm outdated |
none (enabled) |
outdated-args / NODE_OUTDATED_ARGS |
npm outdated arguments yarn outdated arguments pnpm outdated arguments |
--long |
The job generates an outdated report that you will find here: NODE_PROJECT_DIR/reports/npm-outdated-report.json
. This report is only available for download by users with the Developer role or higher
node-semgrep
job¶
The Node template features a job node-semgrep
that performs a Semgrep analysis.
It is bound to the test
stage, and uses the following variables:
Input / Variable | Description | Default value |
---|---|---|
semgrep-disabled / NODE_SEMGREP_DISABLED |
Set to true to disable this job |
none |
semgrep-image / NODE_SEMGREP_IMAGE |
The Docker image used to run Semgrep | registry.hub.docker.com/semgrep/semgrep:latest |
semgrep-args / NODE_SEMGREP_ARGS |
Semgrep scan options | --metrics off --disable-version-check --no-suppress-errors |
semgrep-rules / NODE_SEMGREP_RULES |
Space-separated list of Semgrep rules. Can be both local YAML files or remote rules from the Semgrep Registry (denoted by the p/ prefix). |
p/javascript p/eslint p/gitlab-eslint |
semgrep-registry-base-url / NODE_SEMGREP_REGISTRY_BASE_URL |
The Semgrep Registry base URL that is used to download the rules. No trailing slash. | https://semgrep.dev/c |
semgrep-download-rules-enabled / NODE_SEMGREP_DOWNLOAD_RULES_ENABLED |
Download Semgrep remote rules | true |
Semgrep may collect some metrics, especially when using rules from the Semgrep Registry. To protect your privacy and let you run Semgrep in air-gap environments, this template disables all Semgrep metrics by default:
- rules from the Semgrep registry are pre-downloaded and passed to Semgrep as local rule files (can be disabled by setting
semgrep-download-rules-enabled
/NODE_SEMGREP_DOWNLOAD_RULES_ENABLED
tofalse
),- the
--metrics
option is set tooff
,- the
--disable-version-check
option is set.
In addition to a textual report in the console, this job produces the following reports, kept for one week and only available for download by users with the Developer role or higher:
Report | Format | Usage |
---|---|---|
$NODE_PROJECT_DIR/reports/node-semgrep.gitlab.json |
GitLab's SAST format | GitLab integration |
$NODE_PROJECT_DIR/reports/node-semgrep.native.json |
Semgrep's JSON format | DefectDojo integration This report is generated only if DefectDojo template is detected |
node-sbom
job¶
This job generates a SBOM file listing installed packages using @cyclonedx/cyclonedx-npm.
It is bound to the test
stage, and uses the following variables:
Input / Variable | Description | Default value |
---|---|---|
sbom-disabled / NODE_SBOM_DISABLED |
Set to true to disable this job |
none |
sbom-version / NODE_SBOM_VERSION |
The version of @cyclonedx/cyclonedx-npm used to emit SBOM | none (uses latest) |
sbom-opts / NODE_SBOM_OPTS |
Options for @cyclonedx/cyclonedx-npm used for SBOM analysis | --omit dev |
node-publish
job¶
This job publishes the project packages to a npm registry.
This job is bound to the publish
stage and is disabled by default.
When enabled, it is executed on a Git tag with a semantic versioning pattern (v?[0-9]+\.[0-9]+\.[0-9]+
, configurable).
It uses the following variables:
Input / Variable | Description | Default value |
---|---|---|
publish-enabled / NODE_PUBLISH_ENABLED |
Set to true to enable the publish job |
none (disabled) |
publish-args / NODE_PUBLISH_ARGS |
npm publish extra arguments yarn publish extra arguments pnpm publish extra arguments |
none |
NODE_PUBLISH_TOKEN |
npm publication registry authentication token | none |
Configure the target registry¶
The target registry url for publication shall be configured in the publishConfig
of your package.json
file. \
If no registry is configured in publishConfig
, it will publish to default target registry 'https://registry.npmjs.org/' only if access
is set to 'public' in the publishConfig
of your package.json
file. \
Examples:
- for an unscoped package:
{
"name": "my-package",
// ...
"publishConfig": {
"registry": "https://registry.acme.corp/npm"
}
// ...
}
- for a scoped package:
{
"name": "@acme/my-package",
// ...
"publishConfig": {
"@acme:registry": "https://registry.acme.corp/npm"
}
// ...
}
Then simply declare the registry authentication token with NODE_PUBLISH_TOKEN
.
it is not mandatory to declare the registry if you wish to use the GitLab
project-level npm packages registry (it is declared by default by the template, with the required credentials). All you have to do to is to make sure your npm package name
uses the right scope.
For example, if your project is https://gitlab.example.com/my-org/engineering-group/team-amazing/analytics
, the root namespace is my-org
, and your package name must have the @my-org
scope (probable package fullname: @my-org/analytics
).
Exclude resources from package¶
Don't forget to exclude undesired folders and files from the package resources (simply add them to your .gitignore
or .npmignore
file):
- the
.npm/
,.yarn/
or.pnpm-store
folder, that is used internally by the Node template to storenpm
,yarn
orpnpm
cache (depending on the package manager you're actually using), - the
reports/
folder, that is used by most to be continuous to output all kind of reports, - the Node.js build output dir (if any),
- any other undesired file & folder that you don't want to appear in your published package(s).
Variants¶
The Node template can be used in conjunction with template variants to cover specific cases.
Vault variant¶
This variant allows delegating your secrets management to a Vault server.
Configuration¶
In order to be able to communicate with the Vault server, the variant requires the additional configuration parameters:
Input / Variable | Description | Default value |
---|---|---|
TBC_VAULT_IMAGE |
The Vault Secrets Provider image to use (can be overridden) | registry.gitlab.com/to-be-continuous/tools/vault-secrets-provider:latest |
vault-base-url / VAULT_BASE_URL |
The Vault server base API url | none |
vault-oidc-aud / VAULT_OIDC_AUD |
The aud claim for the JWT |
$CI_SERVER_URL |
VAULT_ROLE_ID |
The AppRole RoleID | must be defined |
VAULT_SECRET_ID |
The AppRole SecretID | must be defined |
Usage¶
Then you may retrieve any of your secret(s) from Vault using the following syntax:
@url@http://vault-secrets-provider/api/secrets/{secret_path}?field={field}
With:
Parameter | Description |
---|---|
secret_path (path parameter) |
this is your secret location in the Vault server |
field (query parameter) |
parameter to access a single basic field from the secret JSON payload |
Example¶
include:
# main template
- component: $CI_SERVER_FQDN/to-be-continuous/node/gitlab-ci-node@4.1.0
# Vault variant
- component: $CI_SERVER_FQDN/to-be-continuous/node/gitlab-ci-node-vault@4.1.0
inputs:
# audience claim for JWT
vault-oidc-aud: "https://vault.acme.host"
vault-base-url: "https://vault.acme.host/v1"
# $VAULT_ROLE_ID and $VAULT_SECRET_ID defined as a secret CI/CD variable
variables:
NODE_CONFIG_SCOPED_REGISTRIES: "@public-repo:https://public.npm.registry/some/repo @my-priv-repo:https://private.npm.registry/another/repo"
# retrieve private repo auth token from Vault
NODE_REGISTRY_MY_PRIV_REPO_AUTH: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/node/priv-repo/creds?field=token"