# Release management in Angular with Lerna

**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](https://github.com/lerna/lerna) and [Commitizen](https://github.com/commitizen/cz-cli). By using commit conventions, we can automate the versioning and get great looking changelogs.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075683862/Nq1GJo3dz.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075685484/YmhBmU5N0.png)

The final code is on [GitHub](https://github.com/melcor76/semver-libs). 📜

## Definitions

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

### Semantic Versioning (SemVer)

Following the [Semantic Versioning spec](https://semver.org/) 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,

1. **MINOR** version when you add functionality in a backward-compatible manner, and

1. **PATCH** version when you make backward-compatible bug fixes.

![[Cube drone](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075687418/KzHdG4TCS.html) by [Curtis Lassam](http://curtis.lassam.net/). Why not Try out [Drone-Ver](http://drone-ver.org/)?](https://cdn-images-1.medium.com/max/2000/0*RuuYEnANgc_Q1EVd.gif)*[Cube drone](https://cube-drone.com/) by [Curtis Lassam](http://curtis.lassam.net/). Why not Try out [Drone-Ver](http://drone-ver.org/)?*

### **Conventional Commits**

The [Conventional Commits specification](https://www.conventionalcommits.org/) 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:

```
<type>[optional scope]: <description>

[optional body]

[optional footer]
```


A simple real-world example can look like this:

```
fix(button): jira-1234 fixed minor bug
```


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

### Commitizen

[Commitizen](https://github.com/commitizen/cz-cli) 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 menu](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075689479/d4UQ5vEqm.png)*Commitizen commit menu*

### Conventional Changelog

[Conventional changelog](https://github.com/conventional-changelog/conventional-changelog) is a tool for generating a `CHANGELOG.md` from git metadata. This tool only works when we follow Conventional Commits rules.

### Husky

Something is needed to stop bad commits. By adding a git hook with [Husky](https://github.com/typicode/husky), we can run custom scripts on the commit before letting it through.

### Commitlint

After using Husky to capture the commit, we use [commitlint](https://conventional-changelog.github.io/commitlint/#/) to check that the commit is using the correct conventions.

### Lerna

[Lerna](https://github.com/lerna/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

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

1. [node and npm](https://nodejs.org/) installed on your computer

1. [git](https://git-scm.com/downloads) ready to run

1. [GitHub](https://github.com/join) account

1. A code editor like [VS Code](https://code.visualstudio.com/)

## Workspace with Libraries

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:

```
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](https://angular.io/guide/file-structure) 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.

```
ng new semver-libs --create-application=false
```


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

```
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:

```
|- semver-libs 
    |- projects
        |- button
        |- input      
```


## Commit to GitHub

To be able to track the changes and see the changelog I need to set up a code repository. I’ll be using [GitHub](https://github.com/).

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.

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


![[xkcd](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075691140/gssVMRsKZ.html)](https://cdn-images-1.medium.com/max/2000/0*0nCtsu5qbobFteus.png)*[xkcd](https://xkcd.com/1296/)*

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

## Setup Lerna

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

```
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.

```
lerna init -i
```


![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075692551/QrTvQzl4u.png)

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.

1. Add the **publish command** and set it to conventional commits.

1. Add the **version command** commit message to be correct format.

1. Delete the `packages` folder that Lerna created for us.

```
// 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:

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


You can read more on this in the [docs](https://github.com/lerna/lerna/tree/master/commands/version#--message-msg).
> # Like this blog post? [Share it](https://twitter.com/intent/tweet?text=I+enjoyed+%22Release+management+in+Angular+with+Lerna%22+by+%40melcor76+%23angular+https%3A%2F%2Fmedium.com%2Fp%2Frelease-management-in-angular-with-lerna-21b4ab417c59) on Twitter! 🐦

## Setup Commitizen

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](https://github.com/commitizen/cz-cli) tools globally:

```
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](https://github.com/commitizen/cz-conventional-changelog) adapter.

```
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](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) over installing Commitizen:

```
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

1. Saves it to package.json’s dependencies or devDependencies

1. Adds the `config.commitizen` key to the root of your package.json

```
"config": {
  "commitizen": {
    "path": "./node_modules/cz-conventional-changelog"
  }
}
```


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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075693757/01TGk5Gwy.png)

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

## Conventional Commits

Now I will 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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075695357/EltP10J_za.png)

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**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075696808/3c141Mcp7.png)

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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075698242/JUA__zT1Q.png)

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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075699702/2_A96WJO1.png)

## Change Log

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](https://conventionalcommits.org/) to [determine the version bump](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-recommended-bump) and [generate CHANGELOG.md files](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli).
> 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.

```
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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075701445/5Mmc550HJ.png)

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075703225/GrNq7Ar-4.png)

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075704540/9XOkkZ1SV.png)

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075705869/uV3tk7YSd.png)

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

## Dependencies

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.

```
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.

```
git add -A
git cz
lerna version --conventional-commits
```


![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075707308/i1EndWPk8.png)

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.`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075708733/y3M4T_dWk.png)

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

## Setup Husky

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](https://github.com/typicode/husky). And as the [documentation](https://github.com/typicode/husky/blob/master/DOCS.md) says we should only add Husky to the root `package.json` in a multi-package repository.

Let’s install Husky to the `devDependencies`.

```
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.

```
"husky": {
  "hooks": {
    "commit-msg": "echo $HUSKY_GIT_PARAMS"
  }
}
```


Husky gives us the needed hook, but another package is needed to [lint](https://en.wikipedia.org/wiki/Lint_(software)) the commit message.

### Commitlint

To lint the commit messages I use [commitlint](https://github.com/conventional-changelog/commitlint). Let’s install it with the conventional format.

```
npm i @commitlint/cli -D
npm i @commitlint/config-conventional -D
```


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

```
"husky": {
  "hooks": {
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}
```


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

```
"commitlint": {
  "extends": [
    "@commitlint/config-conventional"
  ]
}
```


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

```
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:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075710373/HdYx3anLU.png)

The same thing if I try it in VS Code:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075711788/GkQhBkHMM.png)

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075713249/qz4_Y68ey.png)

Success! New feature added with the correct commit format.

## Conclusion

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…

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075715199/RPpYuD5fz.png)

The final code is on [GitHub](https://github.com/melcor76/semver-libs). 📜

Thanks to [Kristiyan Serafimov](https://twitter.com/chrispcode) who put this excellent tech stack together in React. I only copied it to Angular and wrote about it. 😉

### Call to Action

I always enjoy feedback so please 👏, 📝 and [tweet 🐦](https://twitter.com/intent/tweet?text=I+enjoyed+%22Release+management+in+Angular+with+Lerna%22+by+%40melcor76+%23angular+https%3A%2F%2Fmedium.com%2Fp%2Frelease-management-in-angular-with-lerna-21b4ab417c59).
Follow me on [Twitter](https://twitter.com/melcor76) and [Medium](https://medium.com/@michael.karen) for blog updates.

%[https://www.educative.io/courses/game-development-js-tetris]

### Resources

* [Lerna](https://lernajs.io/)

* [Commitizen](https://github.com/commitizen/cz-cli)

* [Semantic Versioning](https://semver.org/)

* [Conventional Commits](https://www.conventionalcommits.org/)

* [Conventional Changelog](https://github.com/conventional-changelog/conventional-changelog)

* [husky](https://github.com/typicode/husky)

* [commitlint](https://conventional-changelog.github.io/commitlint/#/)
