Newer
Older
This project implements a GitLab CI/CD template to build, test and analyse your [Angular](https://angular.io/) projects.
## Usage
In order to include this template in your project, add the following to your `gitlab-ci.yml`:
```yaml
include:
file: '/templates/gitlab-ci-angular.yml'
```
## Global configuration
The Angular template uses some global configuration used throughout all jobs.
| Name | description | default value |
|----------------|-----------------------------------------------|:--------------------------------------------------------------------------|
| `NG_CLI_IMAGE` | The Docker image used to run Angular-CLI (ng) <br/>:warning: **set the version required by your project** | `registry.hub.docker.com/trion/ng-cli-karma:latest` |
| `NPM_CONFIG_REGISTRY` | NPM [registry](https://docs.npmjs.com/configuring-your-registry-settings-as-an-npm-enterprise-user) | _none_ (defaults to `https://registry.npmjs.org`) |
| `NPM_CONFIG_SCOPED_REGISTRIES` | Space separated list of NPM [scoped registries](https://docs.npmjs.com/cli/v8/using-npm/scope#associating-a-scope-with-a-registry) (formatted as `@somescope:https://some.npm.registry/some/repo @anotherscope:https://another.npm.registry/another/repo`) | _none_ |
| `NG_WORKSPACE_DIR` | Angular workspace directory | `.` |
| `NG_INSTALL_EXTRA_OPTS`| Extra options to install project dependencies (with [`npm ci`](https://docs.npmjs.com/cli/ci.html/)) | _none_ |
### Configuring scoped registries
You may configure [scoped registries](https://docs.npmjs.com/cli/v8/using-npm/scope#associating-a-scope-with-a-registry) with the `$NPM_CONFIG_SCOPED_REGISTRIES` variable.
The value is expected as a (whitespace-separated) list of `@registry_scope:registry_url`.
The Angular template also supports authentication tokens for each, simply by defining `NPM_REGISTRY_<SCOPE>_AUTH` (as project or group secret variables).
:warning: The `<SCOPE>` part is the `registry_scope` transformed in [SCREAMING_SNAKE_CASE](https://en.wikipedia.org/wiki/Snake_case) (uppercase words separated by underscores).
Example: declare the GitLab chart repository from another GitLab project
```yml
variables:
NPM_CONFIG_SCOPED_REGISTRIES: "@public-repo:https://public.npm.registry/some/repo @priv-repo:https://private.npm.registry/another/repo"
# NPM_REGISTRY_PRIV_REPO_AUTH set as a project secret variables
```
## Jobs
### `ng-lint` job
The Angular template features a job `ng-lint` that performs Angular source code **lint**.
It is bound to the `check` stage, and uses the following variable:
| Name | description | default value |
|----------------|----------------------------------------------------------|---------------|
| `NG_LINT_ARGS` | Angular [ng lint](https://angular.io/cli/lint) arguments | `lint` |
### `ng-build` job
The Angular template features a job `ng-build` that performs **build and tests** all at once.
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).
Those stage are bound to the `build` stage, and uses the following variable:
| Name | description | default value |
|-----------------|------------------------------------------------------------|---------------------------------------------------|
| `NG_TEST_ARGS` | Angular [ng test](https://angular.io/cli/test) arguments | `test --code-coverage --reporters progress,junit --watch=false --no-progress` |
| `NG_BUILD_ARGS` | Angular [ng build](https://angular.io/cli/build) arguments | `build` |
The next chapters presents some requirements related to your unit tests (using Karma).
#### Use a headless browser
To be able to launch unit tests with Angular CLI, the Angular template requires a headless browser within the Docker
image `NG_CLI_IMAGE` (it is the case with the default image, [docker-ng-cli-karma](https://github.com/trion-development/docker-ng-cli-karma)).
#### 1. Using Karma
##### Code Coverage reports
In order to be able to compute and enable [GitLab code coverage integration](https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html),
the Angular template expects the following in your `karma.conf.js`:
1. Add the [karma-coverage](https://www.npmjs.com/package/karma-coverage) package:
2. Configure the 2 reporters withing this config section:
coverageReporter: {
dir: 'reports',
subdir: '.',
reporters: [
// 'text-summary' to let GitLab grab coverage from stdout
{type: "text-summary"},
// 'cobertura' to enable GitLab test coverage visualization
{type: 'cobertura', file: 'ng-coverage.cobertura.xml'}
],
},
```
:warning: in case of multiple angular projects in the workspace, each project shall produce its coverage report in `reports/ng-coverage-<projectName>.cobertura.xml` (it can be in sub-folders but must follow the file name pattern).
3. Additionally, if using SonarQube, you may also want to generate [LCOV report](https://docs.sonarqube.org/latest/analysis/test-coverage/javascript-typescript-test-coverage/):
coverageReporter: {
dir: 'reports',
subdir: '.',
reporters: [
// 'text-summary' to let GitLab grab coverage from stdout
{type: "text-summary"},
// 'cobertura' to enable GitLab test coverage visualization
{type: 'cobertura', file: 'ng-coverage.cobertura.xml'},
// 'lcovonly' to enable SonarQube test coverage reporting
{type: 'lcovonly', file: 'ng-coverage.lcov.info'}
],
},
In order to be able to [integrate your test reports to GitLab](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsjunit):
1. Add the [karma-junit-reporter](https://github.com/karma-runner/karma-junit-reporter) package as dev dependency:
```shell
npm install --save-dev karma-junit-reporter
```
2. In your `karma.conf.js`, add the plugin:
// 'karma-junit-reporter' to enable GitLab unit test report integration
require('karma-junit-reporter'),
// 'karma-junit-reporter' to enable GitLab unit test report integration
junitReporter: {
outputDir: 'reports',
outputFile: 'ng-test.xunit.xml',
useBrowserName: false,
...
}
```
:warning: in case of multiple Angular projects in the workspace, each project shall produce its JUnit report either in `reports/ng-test-<projectName>.xunit.xml` or `reports/<projectName>/ng-test.xunit.xml`.
Additionally, if using **SonarQube**, you may also want to generate [SonarQube generic test report](https://docs.sonarqube.org/latest/analysis/generic-test/):
1. Add [karma-sonarqube-execution-reporter](https://github.com/lisrec/karma-sonarqube-execution-reporter) to your project as a dev dependency:
```shell
npm install --save-dev karma-sonarqube-execution-reporter
```
2. In your `karma.conf.js`, add the plugin:
// 'karma-sonarqube-execution-reporter' to enable SonarQube unit test report integration
require('karma-sonarqube-execution-reporter')
// 'karma-sonarqube-execution-reporter' to enable SonarQube unit test report integration
sonarQubeExecutionReporter: {
outputDir: 'reports',
outputFile: 'ng-test.sonar.xml',
...
}
4. Finally add the `sonarqubeUnit` reporter in the reporters parameter of the `NG_TEST_ARGS` variable :
```yaml
NG_TEST_ARGS: test --reporters junit,sonarqubeUnit`
```
To be able to use Jest instead Karma, you first have to install some jest packages.
Then you have to create a dedicated jest config file, and to modify your angular.json and tsconfig.spec.json files to set Jest as test builder.
1. Add [jest](https://www.npmjs.com/package/jest), [jest-junit](https://github.com/jest-community/jest-junit#readme), [jest-preset-angular](https://www.npmjs.com/package/jest-preset-angular), [@types/jest](https://www.npmjs.com/package/@types/jest) and [@angular-builders/jest](https://www.npmjs.com/package/@angular-builders/jest) to your project as a dev dependency:
```shell
npm install jest jest-preset-angular jest-junit @types/jest @angular-builders/jest --save-dev
```
2. Create the file `jest.config.js`, and add the following lines:
```js
module.exports = {
reporters: [
'default',
["jest-junit",
{
outputDirectory: "reports",
outputName: "ng-test.xunit.xml"
}],
],
preset: 'jest-preset-angular',
globalSetup: 'jest-preset-angular/global-setup',
};
```
3. Open the `angular.json` file. Replace the test builder with jest, and convert "inlineStyleLanguage" option to array instead string:
"test": {
// REPLACE: "builder": "@angular-devkit/build-angular:karma",
// With:
"builder": "@angular-builders/jest:run",
...
// REPLACE: "inlineStyleLanguage": "scss",
// With:
"inlineStyleLanguage": ["scss"],
4. Open the `tsconfig.spec.json`file and replace the following line:
"types": [
// REPLACE: "jasmine"
// With:
"jest"
]
##### Code Coverage reports
1. Modify the file `jest.config.js`, and add the following lines into the module.exports:
coverageDirectory: "reports",
coverageReporters: [
// 'text' to let GitLab grab coverage from stdout
"text",
// 'cobertura' to enable GitLab test coverage visualization
["cobertura",{file: 'ng-coverage.cobertura.xml'}],
// [OPTIONAL] only if using SonarQube
// 'lcovonly' to enable SonarQube test coverage reporting
"lcovonly",
],
2. Open the `angular.json` file and add the following line to the test options:
3. Finally, override the NG_TEST_ARGS from your `gitlab-ci.yml` variables:
Additionally, if using **SonarQube**, you may also want to generate [SonarQube generic test report](https://docs.sonarqube.org/latest/analysis/generic-test/):
1. Add [jest-sonar-reporter](https://www.npmjs.com/package/jest-sonar-reporter) to your project as a dev dependency:
```shell
npm install --save-dev jest-sonar-reporter
```
2. In your `jest.config.js`, add this config line to the exports:
testResultsProcessor: "jest-sonar-reporter",
3. In your `jest.config.js`, add a jestSonar section to configure the name of the jest report.
},
"jestSonar": {
"reportPath": "reports",
"reportFile": "ng-test.sonar.xml"
}
```
### `ng-e2e` job
The Angular template features a job `ng-e2e` that performs **protractor tests**
This stage is bound to the `test` stage and uses the following variables :
| Name | description | default value |
|----------------------|------------------------------------------------------------|------------------------------------------|
| `NG_E2E_ARGS` | Angular [ng e2e](https://angular.io/cli/e2e) arguments | `e2e` |
| `NG_E2E_ENABLED` | set to `true`to enable the e2e tests execution | *none (disabled by default)* |
Implementation rely on the official [Angular CLI](https://cli.angular.io/) tool (`ng build` and `ng test` commands).
To enable JUnit reporting on this job, you'll need to add [jasmine-reporters](https://www.npmjs.com/package/jasmine-reporters) dependency to your project and add the following snippet to your protractor config file :
```js
const { JUnitXmlReporter } = require('jasmine-reporters');
exports.config = {
...
onPrepare() {
jasmine.getEnv().addReporter(new JUnitXmlReporter({
consolidateAll: true,
savePath: 'reports',
filePrefix: 'ng-e2e.xunit'
### `ng-sbom` job
This job generates a [SBOM](https://cyclonedx.org/) file listing installed packages using [@cyclonedx/cyclonedx-npm](https://www.npmjs.com/package/@cyclonedx/cyclonedx-npm).
It is bound to the `test` stage, and uses the following variables:
| Name | description | default value |
| --------------------- | -------------------------------------- | ----------------- |
| `NG_SBOM_DISABLED` | Set to `true` to disable this job | _none_ |
| `NG_SBOM_VERSION` | The version of @cyclonedx/cyclonedx-npm used to emit SBOM | _none_ (uses latest) |
| `NG_SBOM_OPTS` | Options for @cyclonedx/cyclonedx-npm used for SBOM analysis | `--omit dev` |
This job [publishes](https://docs.npmjs.com/cli/v6/commands/npm-publish) 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 version pattern (`v?[0-9]+\.[0-9]+\.[0-9]+`, _configurable_).
It uses the following variables:
| Name | description | default value |
|----------------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------|
| `NG_PUBLISH_ENABLED` | Set variable to `true` to enable the publish job | none (disabled) |
| `NG_PUBLISH_PROJECTS` | Space separated list of projects to publish | If no project is specified, all workspace projects are published |
| `NG_PUBLISH_ARGS` | NPM [publish](https://docs.npmjs.com/cli/v6/commands/npm-publish) arguments | `--verbose` |
| `NPM_PUBLISH_REGISTRY` | npm registry to publish to | uses GitLab project npm packages registry | |
| :lock: `NPM_PUBLISH_TOKEN` | NPM publication registry authentication token | none |
:warning: When using the gitlab registry (which is the default behavior), your NPM package name must be in the format of `@scope/package-name`:
* The `@scope` is the root namespace of the GitLab project. It must match exactly, including the case.
* The `package-name` can be whatever you want.
For example, if your project is `https://gitlab.example.com/my-org/engineering-group/team-amazing/analytics`, the root namespace is `my-org`. When you publish a package, it must have `my-org` as the scope.
For more details see [Package naming convention](https://docs.gitlab.com/ee/user/packages/npm_registry/#package-naming-convention).
:warning: Don't forget to specify the publication registry in the **project(s)** to publish `package.json` file (not the workspace top-level one).
```json
{
"name": "@my-org/hello-world",
"version": "0.0.6",
"peerDependencies": {
"@angular/common": "^10.1.6",
"@angular/core": "^10.1.6"
},
"dependencies": {
"tslib": "^2.0.0"
},
"publishConfig": {
"@my-org:registry": "https://<publication-registry-url>"
}
}
```
:information_source: When using the GitLab registry, the registry publication url looks like `https://<gitlab-host>/api/v4/projects/<your_project_id>/packages/npm/`, with:
* `<your_project_id>` is your project ID, **found on the project’s home page**.
## SonarQube analysis
If you're using the SonarQube template to analyse your Angular code, here is a sample `sonar-project.properties` file:
```properties
# 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=app
# 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=app
sonar.test.inclusions=**/*.spec.ts
# tests report: generic format
# set the path configured with karma-sonarqube-execution-reporter
sonar.testExecutionReportPaths=reports/ng-test.sonar.xml
sonar.typescript.tslint.reportPaths=reports/ng-lint.tslint.json
# coverage report: LCOV format
# set the path configured with karma-coverage-istanbul-reporter
sonar.typescript.lcov.reportPaths=reports/ng-coverage.lcov.info
* [TypeScript language support](https://docs.sonarqube.org/latest/analyzing-source-code/test-coverage/javascript-typescript-test-coverage/)
* [test coverage & execution parameters](https://docs.sonarqube.org/latest/analysis/coverage/)
* [third-party issues](https://docs.sonarqube.org/latest/analysis/external-issues/)