DEV Community

Cover image for Automatic Package Releases using Semantic-Release
Christian Holländer
Christian Holländer

Posted on • Updated on • Originally published at christian-hollaender.de

Automatic Package Releases using Semantic-Release

This post was originally posted over on my personal website. Maybe you want to check it out [HERE].


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

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:

npm install -D semantic-release @semantic-release/git @semantic-release/gitlab @semantic-release/npm
Enter fullscreen mode Exit fullscreen mode

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:

{
  "scripts": {
    "semantic-release": "semantic-release"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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/
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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:

  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
Enter fullscreen mode Exit fullscreen mode

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. For this demo I chose release.config.js cause it gives us the most dynamic way to configure Semantic-Release:

/** @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,
    }
  ],
};
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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. I have already mentioned it here and there throughout this post.

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

Top comments (0)