Release Angular libraries with lerna & commitizen

Release Angular libraries with lerna & commitizen

In this tutorial, we’ll learn how to streamline Angular library releases with commit conventions and commitizen.

Let’s suppose, we want to release Angular libraries to npm to be able import them inside our Angular applications. We also want to to release the library separately from the application.

We’ll use semantic versioning for our library and release multiple commits at once and we’ll see how to generate professional changelogs.

We’ll learn how to release new versions of your library using lerna and commitizen. Thanks to commit conventions, we’ll be able to automate the versioning and easily obtain changelogs.

Setting up lerna for conventional commits

We can make it simpler for other developers to collaborate on projects by better expressing the changes that occur in our code. Thanks to conventional commits, we can write a clear and efficient commit history. This also makes using tools to streamline release management easier including automatically generating changelogs and upgrading versions.

Conventional commits refer to a minimalist convention built on top of commit messages. It offers a simple set of principles for writing clear commits.

The commit message should be written as follows:

<type>[optional scope]: <description>

[optional body]

[optional footer]

This is an example

fix(ui): fixed minor bug

We’ll use commitizen which is a CLI utiltiy that we can use to generate Git commit messages following the convention. and conventional changelog for producing a CHANGELOG.mdfrom Git metadata.

We’ll also be using Husky to execute custom scripts to hook into the commit and make sure it’s written in the correct format using the commitlint utility.

We’ve previously created an Angular workspace and generated a bunch of apps and libraries inside the workspace’ projects/ folder which was also configured as the packages folder for lerna.

Now, let’s get started by setting up lerna to support conventional commits.

To monitor changes and view the changelog, we need to create a code repository. We'll be working with GitHub.

To begin, create a repository called angularmonorepo, which is the same name as our project. Second, commit all changes to the code and push it to GitHub using the guidance provided after starting the repository:

git commit -m "first commit"
git remote add origin https://github.com/techiediaries/angularmonorepo.git

Now that the repository has been created, let's continue configuring the environment.

Open the lerna.json file and update it as follows:

{
  "packages": [
    "projects/*"
  ],
  "version": "independent",
  "command": {
    "publish": {
      "conventionalCommits": true
    },
    "version": {
      "message": "chore(release): release"
    }  
  }
}

Next, install Commitizen CLI globally using the following command:

npm install commitizen --global

Next, initialize your monorepo project to use the cz-conventional-changelog adapter using the following command:

commitizen init cz-conventional-changelog --save-dev --save-exact

Open the package.json file at the root of your monorepo project, you should notice the following changes made by the command:

  1. It installs the cz-conventional-changelog adapter npm module as a development dependency
  2. It added the config.commitizen key to the root of your package.json file as shown here:
"config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }

After this, you can add commits with Commitizen using the git czcommand instead of git commit.

Now, push your code to GitHub using the following command before trying conventional commits:

git add -A
git push -u origin master

Next, go ahead and change something in your code. For example, open the projects/shared/src/lib/shared.component.ts file and update it as follows:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'lib-shared',
  template: `
    <p>
      It works!
    </p>
  `,
  styles: [
  ]
})
export class SharedComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

Simply change the text in the template from “shared works!” to “It works!” then save it. If you run the git status command, you’ll get the following output:

On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   projects/shared/src/lib/shared.component.ts

Next, run the following commands to stage and commit the changes:

git add -A
git cz 

Remember instead of using the standard git commit command, we use the git cz command to create an appropriate conventional commit.

You’ll be prompted to select the change that you are committing:

cz-cli@4.2.4, cz-conventional-changelog@3.3.0

? Select the type of change that you're committing: (Use arrow keys)
❯ feat:     A new feature 
  fix:      A bug fix 
  docs:     Documentation only changes 
  style:    Changes that do not affect the meaning of the code (white-space, formatting
, missing semi-colons, etc) 
  refactor: A code change that neither fixes a bug nor adds a feature 
  perf:     A code change that improves performance 
(Move up and down to reveal more choices)

Since we simply did a code change that neither fixes a bug nor adds a feature, we need to select the refactor option and answer the other questions as follows:

? Select the type of change that you're committing: refactor: A code change that neithe
r fixes a bug nor adds a feature
? What is the scope of this change (e.g. component or file name): (press enter to skip)

? Write a short, imperative tense description of the change (max 90 chars):
 (20) change template text
? Provide a longer description of the change: (press enter to skip)

? Are there any breaking changes? No
? Does this change affect any open issues? No
[master e8cd517] refactor: change template text
 1 file changed, 1 insertion(+), 1 deletion(-)

Following that, run the lerna version command which should figure out the version from the conventional commit and display the following output:

info cli using local version of lerna
lerna notice cli v4.0.0
lerna info versioning independent
info Assuming all packages changed
info getChangelogConfig Successfully resolved 
preset "conventional-changelog-angular"

Changes:
 - demoapp: 0.0.1 => 0.0.2
 - shared: 0.0.1 => 0.0.2

? Are you sure you want to create these versions? Yes
lerna info execute Skipping releases
lerna info git Pushing tags...
Username for 'https://github.com': techiediaries
Password for 'https://techiediaries@github.com': 
lerna success version finished

Lerna searched for conventional commits and figure out that, since we chose the change type refactor ,the version bump needed for the shared library. If you answer yes for Are you sure you want to create these versions? It will push the changes to GitHub.

If you go to the GitHub commit, you can see that it updated the version in package.jsonand updated projects/shared/CHANGELOG.md with the added changes.

Notice that version of demoapp was also bumped even without making any changes to the application! That’s because the shared library is added as a dependency to the application in the previous section. You can see that by opening the package.json file of the demoapp application where you should find it in the dependencies object:

{
    "name": "demoapp",
    "version": "0.0.2",
    "scripts": {
        "start": "ng serve demoapp",
        "build": "ng build demoapp",
        "test": "ng test demoapp",
        "lint": "ng lint demoapp"
    },
    "dependencies": {
        "shared": "^0.0.2"
    }
}

This means that lerna takes care of the dependencies too by modifying the version of the dependency in the demoapp’s package.json file.

Conclusion

We can enhance the releasing process by combining lerna with a few additional utilities and conventions. Thanks to using conventional commits and semantic versioning, we can easily generate adequate documentation and change logs for our code updates.

Now that we are using lerna to determine package versions, we need to ensure that all commits follow the proper syntax. We can use husky to add a git hook to the commit command.