Automatic Package Releases using Semantic-Release
Published: 2022-09-24 21:08:23

> This post was originally posted over on my personal website. Maybe you want to check it out [[HERE]](https://christian-hollaender.de/posts/1-automatic-package-releases-using-semantic-release).

---

Writing the release notes and Changelog manually is a time-consuming and complicated task. And then you need to push your new release to your desired Package-registry like NPM or Packagist. Fortunately, there is a better way to do this. Join me in discovering an alternative.

---

When you already maintained an open-source package or developed a package at work, you might know how complicated the job of releasing a new version can be. Most of the time the process contains the following steps:

1. Scim all Commits, Issues, and PRs to see what changed
2. Create a list of the previously collected changes for your Changelog and Release-Notes
3. Decide if you want to create a patch, minor or major release (and probably have to ask the author of the changes)
4. If your package provides the version to its users, update it
5. Add a new Git tag to the repository and let your CI/CD build one last time
6. Push the newly built package to the Registry of your liking (don't forget to authenticate 😉)

Fortunately, there is an easy way to automate all of this. It's called CI/CD. Personally, I use Gitlab CI/CD as all my Code is hosted on Gitlab. But Github Actions and other providers should work similarly. So now we need to think about how we can achieve all our tasks in an automated manner. We as developers are lazy people, so writing a bunch of bash scripts all ourselves is not really an option. Luckily there is a tool we can use to help out:

#### [Semantic-Release](https://github.com/semantic-release/semantic-release)

Semantic-Release is a tool that automates most of the hard tasks needed to release a new version of our package. It actually can also release new versions of our application, but that is a bit more difficult. Its plugin system makes it easy and extendable for multiple package managers and steps.

### So let's get started:
For this example we want to publish a new NPM package as this is the easiest, to begin with.

First, install the necessary packages:
```bash
npm install -D semantic-release @semantic-release/git @semantic-release/gitlab @semantic-release/npm
```

Here I installed the main package `semantic-release` with some plugins that I need for my setup. There is a good list of official and community plugins on the documentation page of the project: https://semantic-release.gitbook.io/semantic-release/extending/plugins-list

You can also add a script section to your `package.json`. For me, it was actually necessary. Otherwise, I could not run the command inside my CI/CD environment. Because it was not in my `PATH`:
```json
{
  "scripts": {
    "semantic-release": "semantic-release"
  }
}
```

Now that we can run Semantic Release, we need to configure the project so that releasing works. This is especially important if you don't want to push a package into the normal NPM registry, but want to use the GitLab registry as I do.
For this, we have to adjust the "publishConfig" in the `package.json`. Enter there the URL of your registry. For me, this is the project-specific URL of the GitLab registry.

```json
{
  "publishConfig": {
    "@<package-scope>:registry": "https://gitlab.com/api/v4/projects/<gitlab-project-id>/packages/npm/"
  }
}
```

> Do not set the `private` attribute in your `package.json` to true, even though it is an internal/private package. This will prevent Semantic-Release from publishing your package to ANY registry.

After that, we still need to authenticate against our internal registry. For this, we use the so-called `.npmrc` file. In this file, we configure our package scope with a target URL and a personal access token for authentication. I found it easiest to put the scope definition directly into the repo. The token setting must never happen in the repo, otherwise, you expose the token to possibly unauthorized third parties.

```
@<package-scope>:registry=https://gitlab.com/api/v4/projects/<gitlab-project-id>/packages/npm/
```

Then you can set your personal access token for local development in your `~/.npmrc` file. This way you can also authenticate in other projects on your machine and you do not accidentally commit your token to the repo. You can do it like this:

```
//gitlab.com/api/v4/packages/npm/:_authToken=<access-token>
```
> Yes the syntax with the `//` at the beginning is correct. It is rather strange, but this is how it is.

As we also need the token in our CI/CD we need to set this inside our pipeline job. I did this like so:

```yaml
  before_script:
    - echo "@${CI_PROJECT_ROOT_NAMESPACE}:registry=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/">.npmrc
    - echo "${CI_API_V4_URL#https?}/packages/npm/:_authToken=${CI_JOB_TOKEN}">>.npmrc
```

I completely overwrite the `.npmrc` with variables provided by the CI environment. The nice thing about GitLab is, that it already provides all necessary tokens and URLs. I'll show the complete Job-configuration later on.

Now that we configured everything for NPM to publish a package, we also need to configure Semantic-Release so that we can trigger the releases correctly. Therefore we can use a variety of file formats. The most popular ones are JSON and Javascript. You can find out more [here](https://semantic-release.gitbook.io/semantic-release/usage/configuration#configuration-file). For this demo I chose `release.config.js` cause it gives us the most dynamic way to configure Semantic-Release:

```js
/** @type {import("semantic-release").Options } */
module.exports = {
  plugins: [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/gitlab",
    "@semantic-release/npm"
  ],
  "branches": [
    "+([0-9])?(.{+([0-9]),x}).x",
    "main",
    "next",
    "next-major",
    {
      name: "beta",
      prerelease: true,
    },
    {
      name: "alpha",
      prerelease: true,
    }
  ],
};
```

> Configuring the branches is currently only necessary because we already use the new git naming for our default branch "main". If you are still using "master", you won't need to configure them.

> If you are using TypeScript like me, you can also install `@types/semantic-release` and use the shown type doc, to get autocompletion in most IDEs.

## Add CI job
Last but not least we need to configure our already mentioned CI-Job. This depends on your environment. As I am using Gitlab-CI/CD I can only show how this works:

```yaml
release:
  stage: release
  image: node:18.7
  only:
    - main
  before_script:
    - echo "@${CI_PROJECT_ROOT_NAMESPACE}:registry=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/">.npmrc
    - echo "${CI_API_V4_URL#https?}/packages/npm/:_authToken=${CI_JOB_TOKEN}">>.npmrc
  script:
    - npm run semantic-release
```

One thing you will also need to set is an environment Token called: `GITLAB_TOKEN`. This is needed for the Gitlab-plugin and to be able to upload Releases to Gitlab. You can find these in the left menu under `Deployments > Releases`.

### Workflow

To use this automatic release setup, you need to use a special Commit syntax. It is actually quite simple, but you need to memorize all the different keys. Like if you write a commit message like `feat: some dope new feature`, Semantic-Release will trigger a minor update of your package. If you use `fix: some small bug fix`, it will trigger a patch release. You can also always do a major release by adding `BREAKING CHANGE: Description what changes.` to your commit. For example:

```
feat: we added a non backward compatible change

BREAKING CHANGE: Please now use this and that.
```

## Conclusion

This should be it. Now you are all set to automatically release your package with the special Commit-Syntax.
If you want to know more I highly suggest reading through the amazing [documentation](https://semantic-release.gitbook.io/semantic-release/). I have already mentioned it here and there throughout this post.

And now the only thing left is to say thank you for reading!