Release management in Angular with Lerna

Post Editor

In this article we will learn about release management in Angular with Lerna.

10 min read
post

Release management in Angular with Lerna

In this article we will learn about release management in Angular with Lerna.

post
post
10 min read

Automate component library releases through commit conventions
Link to this section

Release new versions of your code using tools like Lerna and Commitizen. By using commit conventions we can automate the versioning and get great looking changelogs.

Scenario: We need to release component libraries to npm because we want to consume them inside our Angular applications. We want to be able to release the components independently. Furthermore, we have some dependencies between the libraries.

Does this sound complicated? What if I tell you we want to use Semantic versioning and release multiple commits at once. And of course, we want professional looking changelogs like all the other cool projects. Does this sound too complex to you?

“Simple things should be simple, complex things should be possible.” — Alan Kay

In this article, I will show how to release new versions of your code using tools like Lerna and Commitizen. By using commit conventions, we can automate the versioning and get great looking changelogs.

changelogchangelog
code diffcode diff

The final code is on GitHub.

Definitions
Link to this section

Before beginning, we need to go through some concepts and tools.

Semantic Versioning (SemVer)
Link to this section

Following the Semantic Versioning spec helps other developers who depend on your code to understand the extent of changes in a given version and adjust their code if necessary.

Given a version number MAJOR.MINOR.PATCH, increment the:

  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backward-compatible manner, and
  3. PATCH version when you make backward-compatible bug fixes.

Conventional Commits
Link to this section

The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history, which makes it easier to write automated tools. With these tools we can do things like:

  • Automatically generate change logs.
  • Automatically determine semantic version bumps

By better communicating the nature of changes we make it easier for people to contribute to your projects.

The commit message should be structured as follows:

<>Copy
<type>[optional scope]: <description>[optional body][optional footer]

A simple real-world example can look like this:

<>Copy
fix(button): jira-1234 fixed minor bug

Here we did a bug-fix for the button connected to jira-1234.

Commitizen
Link to this section

Commitizen gives conventional commit messages as a global utility. I’ll use it to create git commit messages that can be analyzed to determine the next version. If installed globally, we can use git cz instead of git commit when committing code.

commitizen commit menucommitizen commit menu

Conventional Changelog
Link to this section

Conventional changelog is a tool for generating a CHANGELOG.md from git metadata. This tool only works when we follow Conventional Commits rules.

Husky
Link to this section

Something is needed to stop bad commits. By adding a git hook with Husky, we can run custom scripts on the commit before letting it through.

Commitlint
Link to this section

After using Husky to capture the commit, we use commitlint to check that the commit is using the correct conventions.

Lerna
Link to this section

Lerna is a tool for managing JavaScript projects with multiple packages. It allows you to manage your project using one of two modes:

  • fixed mode keeps all versions of packages at the same level
  • independent mode allows independent versions of each package

Prerequisites
Link to this section

If you want to do more than read the article you need:

  1. node and npm installed on your computer
  2. git ready to run
  3. GitHub account
  4. A code editor like VS Code

Workspace with Libraries
Link to this section

The goal is to have multiple libraries that can be versioned independently and handle dependencies. For this, I’m creating an empty workspace with two libraries.

Make sure you have Angular CLI installed globally with:

<>Copy
npm i @angular/cli -g

In global mode (i.e., with -g or --global appended to the command), it installs the current package context (i.e., the current working directory) as a global package.

To begin with, I’m going to create an empty workspace called semver-libs.

I don’t want to have an application and use the --create-application flag with the ng new command. Setting this to false creates an empty workspace with no initial app.

<>Copy
ng new semver-libs --create-application=false

With the application in place let’s create the libraries:

<>Copy
cd semver-libs ng g lib button ng g lib input

The lib command will create a new folder called projects containing the newly created libraries:

<>Copy
|- semver-libs |- projects |- button |- input

Commit to GitHub
Link to this section

To be able to track the changes and see the changelog I need to set up a code repository. I’ll be using GitHub.

First, I create a repository with the same name as our project, semver-libs. Secondly, I commit all the code changes and push the code to GitHub by following the instructions given after creating the repository.

<>Copy
git commit -a -m "Initial commit" git remote add origin https://github.com/melcor76/semver-libs.git git push -u origin master

Now the repository is ready so let’s continue setting up the environment.

Setup Lerna
Link to this section

Lerna is a CLI (command line interface) tool, so I install it globally.

<>Copy
npm i lerna -g

To integrate it to the project, to help manage the libraries, I can initialize the workspace by running lerna init, that creates a new Lerna repository. I’m using independent mode with -i to be able to increment package versions independently of each other.

<>Copy
lerna init -i

lerna initlerna init

The command added Lerna to devDependencies in package.json.

It also created the Lerna configuration file lerna.json in the root path. By default, Lerna points its packages to the packages folder. I need to make a few changes:

  1. Change packages to the projects folder.
  2. Add the publish command and set it to conventional commits.
  3. Add the version command commit message to be correct format.
  4. Delete the packages folder that Lerna created for us.
<>Copy
// lerna.json { "packages": ["projects/*"], "version": "independent", "command": { "publish": { "conventionalCommits": true }, "version": { "message": "chore(release): release" } } }

Now Lerna is set up to read conventional commits.

It is important to set the commit message in the correct format or Husky will stop the commit. We could also do something like this to not push on version and set the commit message for publish instead:

<>Copy
"command": { "publish": { "conventionalCommits": true, "message": "chore(release): release" }, "version": { "push": false } }

You can read more on this in the docs.

Setup Commitizen
Link to this section

It’s possible to write the commits in conventional style, but I will use Commitizen to create git commit messages that can be analyzed to determine the next version.

First, install the Commitizen CLI tools globally:

<>Copy
npm i commitizen -g

Next, we need to choose an adapter to create the changelogs. The adapter tells which template our contributors should follow. Let’s use the conventional changelog adapter.

<>Copy
commitizen init cz-conventional-changelog -D -E

-D, --save-dev: Package will appear in your devDependencies.
-E, --save-exact: Saved dependencies will be configured with an exact version rather than using npm’s default semver range operator.

Or if you prefer npx over installing Commitizen:

<>Copy
npx commitizen init cz-conventional-changelog -D -E

The above command does three things for you:

  1. Installs the cz-conventional-changelog adapter npm module
  2. Saves it to package.json’s dependencies or devDependencies
  3. Adds the config.commitizen key to the root of your package.json
<>Copy
"config": { "commitizen": { "path": "./node_modules/cz-conventional-changelog" } }

Now you are all set to run your commits through Commitizen with git cz.

commitizen menucommitizen menu

You can commit and push these changes to GitHub before we try some conventional commits.

Conventional Commits
Link to this section

Now we make a small change to button.component.ts and capitalize “Button works!” Then add the file to be committed.

git add --all

Commit with git cz and answer the questions.

commitizen questionscommitizen questions

If I now run lerna version, it will look for conventional commits and figure out that, since I chose the change type refactor, the version bump needed for the button is patch.

lerna versionlerna version

If I instead run lerna publish it will run the same process but also ask if I want to publish the packages. If I answer yes it will try to publish to npm. But since I have not yet set this up, it will not work.

lerna publishlerna publish

If we check the commits on GitHub, we can see that it added a Publish commit where it increased the version of the button to 0.0.3.

publish commitpublish commit

Change Log
Link to this section

Now we have a process to version our commits automatically. But it would also be great to have a changelog that picks up all the changes. By adding a flag to Lerna, we can get that.

When running with --conventional-commits, lerna version will use the Conventional Commits Specification to determine the version bump and generate CHANGELOG.md files.

A reminder of SemVer version bumps: MAJOR.MINOR.PATCH

Examples of how the versions are decided:

  • Fix/refactor = patch version bump
  • Feature = minor version bump
  • Breaking changes = major version bump

Let’s make a few changes to try out this magic.

<>Copy
git add -A git cz lerna version --conventional-commits

If we check under commits in GitHub, we see that we got one commit for the change (fix) and one for the Publish.

github commitsgithub commits

If we click on Publish, we can see that it updated the version in package.json and updated CHANGELOG.md with the changes.

changelog diffchangelog diff

And if we open the file, we see how it will look on the web when we publish changes to the libraries.

changelogchangelog

We can see that I fixed a bug and we get a link to the changes.

code diffcode diff

Not too bad! One could even say this looks professional!

Dependencies
Link to this section

Let’s create a dependency between the libraries and see what Lerna does with it. We can use the add command to add button as a dependency to input.

<>Copy
lerna add button --scope=input

The button was added to the dependencies in the package.json of input. Let’s not worry too much about the install errors but instead see what happens when the button is updated.

First, I’ll commit and push the dependency change and then make a small change to the button again before I commit and run Lerna.

<>Copy
git add -A git cz lerna version --conventional-commits

lerna version changeslerna version changes

And here we can see that even though I didn’t make any changes to input, it had its version patched because it now depends on the button. And since button got its version bumped up, then input automatically got the version updated in its package.json.

version bumpversion bump

In other words, Lerna is now taking care of the dependencies for us.

Setup Husky
Link to this section

Now that I use Lerna to calculate the versions of the packages I need to make sure that all commits have the correct format. To get a git hook to the commit command, we can use Husky. And as the documentation says we should only add Husky to the root package.json in a multi-package repository.

Let’s install Husky to the devDependencies.

<>Copy
npm i husky -D

Git hooks can get parameters via command-line arguments, and Husky makes them accessible via HUSKY_GIT_PARAMS. Husky’s commit-msg hook can be used to lint commits before they are created.

<>Copy
"husky": { "hooks": { "commit-msg": "echo $HUSKY_GIT_PARAMS" } }

Husky gives us the needed hook, but another package is needed to lint the commit message.

Commitlint
Link to this section

To lint the commit messages I use commitlint. Let’s install it with the conventional format.

<>Copy
npm i @commitlint/cli -D npm i @commitlint/config-conventional -D

Now the Husky hooks can be added in package.json:

<>Copy
"husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }

And lastly, tell which rules are used by again adding to package.json:

<>Copy
"commitlint": { "extends": [ "@commitlint/config-conventional" ] }

If I now try a git commit with a message that is not correctly formatted:

<>Copy
git commit -a -m "Add Husky and commitlint"

-a, — all Tells the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected.

I get stopped by husky and commitlint:

commitlint errorcommitlint error

The same thing if I try it in VS Code:

vs code errorvs code error

That’s what I wanted, so that’s great. Let’s see if I can commit with git cz.

commitizen commitcommitizen commit

Success! New feature added with the correct commit format.

Conclusion
Link to this section

By using Lerna together with a few other tools, we can improve the release process. And by using Conventional commits and Semantic versioning we can get proper documentation of our changes that benefits not only us but all who depend upon our libraries.

Go forth now and publish your packages...

like a bosslike a boss

The final code is on GitHub.

Thanks to Kristiyan Serafimov who put this excellent tech stack together in React. I only copied it to Angular and wrote about it.

Resources
Link to this section

Share

About the author

author_image

Angular by day. React by night. Full-stack when needed. I like blogging about web stuff. Co-organizer ngVikings.

author_image

About the author

Michael Karén

Angular by day. React by night. Full-stack when needed. I like blogging about web stuff. Co-organizer ngVikings.

About the author

author_image

Angular by day. React by night. Full-stack when needed. I like blogging about web stuff. Co-organizer ngVikings.

Looking for a JS job?
Job logo
Senior Frontend Software Engineer (Angular)

Argument

Ukraine
Remote
$54k - $72k
Job logo
Front-End Web Software Engineer (Angular12 + ASP.NET)

MWS Technology

Ukraine
Remote
$36k - $60k
Job logo
Angular Software Developer

Salamander Technologies

America
Remote
$80k - $95k
Job logo
Senior Front End Developer - Angular

triValence

United States
Remote
$125k - $160k
More jobs

Featured articles