Semantic release on GitLab

How to setup semantic release on private GitLab and on-premises NPM registry.

Imagine you got yourself a private GitLab instance and your own NPM registry. Now you can achieve automated semantic release nirvana in less than five minutes.

This guide assumes that your GitLab instance runs at https://gitlab.team.com and your private NPM registry is available at https://registry.team.com/.

  • Create a new project "foo" on GitLab, in my case it will be hosted at our private https://gitlab.team.com/gleb/foo server.
  • Create local repo with simple test project.
1
2
3
git init
git remote add origin [email protected]:gleb/foo.git
npm init -y
  • Use scoped package name to prevent accidental publishing to the public registry. For example, we can use @team/foo

  • Create .npmrc file that points at our private NPM registry for packages scoped with @team

1
2
registry=https://registry.npmjs.org
@team:registry=https://registry.team.com
  • We will need to tell semantic release tool where the remote GitLab server lives. Edit the created package.json file and add the repo HTTPS url (and not SSH git url. This will be fixed soon). We also should add the publishing config to point at our private NPM registry, while we are here. I also recommend changing the version to reflect the fact that we will no longer publish it manually.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "@team/foo",
"version": "0.0.0-semantic-release",
"publishConfig": {
"registry": "https://registry.team.com/"
}
,

"release": {
"preset": "angular",
"debug": false
}
,

"repository": {
"type": "git",
"url": "https://gitlab.team.com/gleb/foo.git"
}

}

  • Add actual project source files, tests, etc. For this project, I will just echo a message on npm test
1
2
3
4
5
{
"scripts": {
"test": "echo All good"
}

}

  • Create GitLab CI file with two stages. First stage will just run unit tests. If it passes, then we are going to publish a new package version (if there are semantic changes) to NPM. Here is an example .gitlab.ci file with cached modules for speed.
1
image: node:6

stages:
  - test
  - deploy

test:
  stage: test
  cache:
    paths:
      - node_modules/
    key: "node_modules"
    untracked: true
  script:
    - npm install --silent
    # add "npm run build" any other steps here
    - npm test

publish:
  stage: deploy
  # no need to cache anything, we are just
  # installing new tool
  before_script:
    - npm install -g --quiet semantic-release-gitlab
  # only publish from master, exclude pure tag builds
  only:
    - [email protected]/foo
  script:
    - semantic-release-gitlab || true
  • The actual commit log parsing, version increment, publishing new version and tagging the release is done by the module semantic-release-gitlab.

  • Push the change to the GitLab server and enable a runner for the project at https://gitlab.team.com/gleb/foo/runners

The runner executes the two build steps and should display in the "Deploy" step the following

1
2
3
$ semantic-release-gitlab || true
Unable to determine next version to release.
Likely because there are no changes to release.

Notice that we are ignoring the result of the semantic-release-gitlab call. This is to ignore non-zero exit code when there is nothing to be published.

We probably did not have any commits that followed semantic convention. For example, if we fix a bug, we should commit code with message that starts with fix(what): ...

1
git commit -m "fix(log): fixed logging call"

Similarly, a commit that adds a new feature should start with feat(what): .... Any commit that breaks existing API should have BREAKING CHANGE text somewhere inside the commit message text.

  • Let us add a couple of fake commits to force the publish.
1
2
git commit --allow-empty -m "chore(ci): setup CI"
git commit --allow-empty -m "feat(release): semantic release FTW"

Because we are following Angular commit convention the chore(ci): ... commit message should trigger patch release. But the next commit is a feature commit feat(release): ... it will trigger minor version upgrade. The larger upgrade wins. If you do not want to release a new version, just commit with regular text that does not start with fix, feat, etc.

  • The new test run should complain about missing NPM_TOKEN variable
1
2
3
4
$ semantic-release-gitlab || true
setting auth token for registry https://registry.team.com/
Cannot find NPM_TOKEN
use https://www.npmjs.com/package/get-npm-token to get one

The CI job needs to authenticate with both NPM registry to publish, and with GitLab itself to push new tag.

  • Grab the NPM_TOKEN from your private registry and set it as variable in the GitLab CI https://gitlab.team.com/gleb/foo/variables Set it as the CI variable NPM_TOKEN.

  • While we are setting CI variables, go to GitLab and make yourself a new access token at https://gitlab.team.com/profile/personal_access_tokens. I prefer naming them <project_name> semantic release. Set it as the CI variable GITLAB_AUTH_TOKEN.

  • Go back to the CI pipeline and restart the "Deploy" build. You should see the package publish and release log messages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ semantic-release-gitlab || true
setting auth token for registry https://registry.team.com/
saved /root/.npmrc
npm info it worked if it ends with ok
npm info using [email protected]3.10.3
npm info using [email protected]4.0
npm info lifecycle @team/[email protected]1.0.0~prepublish: @team/[email protected]1.0.0
npm info attempt registry request try #1 at 7:07:42 PM
npm http request PUT https://registry.team.com/@team%2ffoo
npm http 200 https://registry.team.com/@team%2ffoo
+ @team/[email protected]1.0.0
npm info lifecycle @team/[email protected]1.0.0~publish: @team/[email protected]1.0.0
npm info lifecycle @team/[email protected]1.0.0~postpublish: @team/[email protected]1.0.0
npm info ok
Released version 1.0.0
Build succeeded

We just got ourselves package @team/[email protected]! Let us add a few commits, for example a fix commit and watch publishing and tagging happen automatically after pushing the code change to the remote.

  • Add another commit
1
2
git commit --allow-empty -m "fix(oops): good thing I have noticed this"
git push origin master
  • Check the build job and observe new version and tag
1
2
+ @team/[email protected]1.0.1
Released version 1.0.1
  • Take a look at the project tags page https://gitlab.team.com/gleb/foo/tags. It shows nicely formatted change log - you no longer have to generate it manually.

tags

  • Even better, install as-a and available-versions and query the releases and change log from the command line
1
2
3
4
5
6
7
$ as-a gitlab-team vers @team/foo
@team/foo from https://gitlab.team.com/gleb/foo.git
-----------------------------------------------------------------------
version age release dist-tag
------- --------- ----------------------------------------- --------
1.0.0 8 minutes feat(release): semantic release FTW
1.0.1 4 minutes fix(oops): good thing I have noticed this latest

Further info