Also available on devto
This document contains a step by step guide to implement a minimal example to test how lerna - semantic versioning works.
Inspired by this blog post.
I always prefer to not install dependencies as global if it is possible. So here i'm using lerna as a dev dependency:
yarn init
yarn add lerna -D
yarn lerna init --independent
Note: on initialization it is essential to set the independent flag. It allows us to manage sub-projects versioning independently instead of having a single version for all of them.
With Semantic Versioning there is not one and only rule that establishes how the version of a project should be increased. However, lerna in combination with the Conventional Commits format proposes a solution to automatically determine semantic version bumps and generate change logs. In summary, if a project (or sub-project in our case) has been updated and its commit message implies:
- a BREAKING CHANGE, then bumps the MAJOR number
- a FEATURE, then bumps the MINOR number
- a REFACTOR / BUG-FIX, then bumps the PATCH number
In the process of releasing changes, that is achieved by running
yarn lerna publish
lerna will check the updated sub-projects and will create and push a new commit with version bumps and change logs (it will also update packages inter-dependencies).
To instruct lerna that we have chosen to follow the Conventional Commits format we must specify:
// lerna.json
...
"command": {
"publish": {
"conventionalCommits": true
}
}
...
It can be also useful to customize the publish commit message like this
// lerna.json
...
"command": {
"publish": {
...
"message": "chore(release): publish"
...
}
}
...
Otherwise, it will be "Publish" by default
As final step, updated packages (if public, that is "private" field is setted to false or just missing in the relative package.json) will be published to a registry. To have them published on our private github registry you simply need to add this:
// lerna.json
...
"command": {
"publish": {
...
"registry": "https://npm.pkg.github.com/"
...
}
}
Then, before a release, make sure you are logged into your github-npm account. Otherwise run
npm login --registry=https://npm.pkg.github.com/
Where your password will be a repo and packages scoped github personal access token.
Note: other configurations related to this step will also be made at sub-projects level. We will see it in the next section ...
As mentioned above when "private" field in a package.json is missing or setted to false lerna will try to publish your package on a registry. If it is so and there is no configuration related to the registry url, lerna will try to publish it in the npm public registry by default.
This could be an unwanted behavior! 🙀
If you don't want to publish any package then consider using one of these two commands for the project release:
yarn lerna publish --skip-npm
Or
yarn lerna version
Otherwise make sure you wrote all the necessary configurations: define registry urls for what you want to publish or set packages as private for what you don't want to publish!
Your final config file will look like this:
// lerna.json
{
"packages": ["packages/*"],
"version": "independent",
"command": {
"publish": {
"conventionalCommits": true,
"message": "chore(release): publish",
"registry": "https://npm.pkg.github.com/"
}
}
}
Here is the code for our three subprojects (thanks to which I will explain some concepts in the next section):
- alpha
- beta
- usage
Where alpha and beta are independent packages, while usage depends on alpha and beta.
Note: remember to change @ your-username and github repo occurrencies
mkdir packages/alpha
touch packages/alpha/index.js
touch packages/alpha/package.json
// packages/alpha/index.js
module.exports = "alpa";
// packages/alpha/package.json
{
"name": "@your-username/alpha",
"repository": "git@github.com:your-username/your-repo-name.git",
"version": "0.0.0",
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
}
}
mkdir packages/beta
touch packages/beta/index.js
touch packages/beta/package.json
// packages/beta/index.js
module.exports = "beta";
// packages/beta/package.json
{
"name": "@your-username/beta",
"repository": "git@github.com:your-username/your-repo-name.git",
"version": "0.0.0",
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
}
}
mkdir packages/usage
touch packages/usage/index.js
touch packages/usage/package.json
// packages/usage/index.js
const alpha = require("@your-username/alpha");
const beta = require("@your-username/beta");
console.log(alpha + " " + beta);
// packages/usage/package.json
{
"name": "@your-username/usage",
"version": "0.0.0",
"repository": "git@github.com:your-username/your-repo-name.git",
"dependencies": {
"@your-username/alpha": "^0.0.0",
"@your-username/beta": "^0.0.0"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
}
}
From the project root directory, run
yarn lerna bootstrap
to install dependencies. Note that internal dependencies will be automatically linked by lerna.
Now cd to the usage package and run
node index
You should see "alpa beta" logged into the terminal.
Because commit messages affects how version is bumped and change logs are generated, each commit will be scoped to a particular feat or fix for a particular package.
We are going to commit monorepo boilerplate first
git add .
git reset -- packages
so that we are staging all files except our packages. Then
git commit -m "feat: created a lerna monorepo"
Note that for this particular commit the Conventional Commits format is irrelevant, but it can be a good exercise to apply it right now.
git add packages/alpha
git commit -m "feat: added export of package name"
git add packages/beta
git commit -m "feat: added export of package name"
git add packages/usage
git commit -m "feat: added export of concatenated package dependencies name"
git push origin master
yarn lerna publish
Now we have our packages published at version 0.1.0 (remember how bumping works) and change logs generated per sub-project level.
Check it out!
You may have already noticed that the alpha package has a typo in the name it exports. is "alpa" instead of "alpha". We need to fix it
// packages/alpha/index.js
module.exports = "alpha";
Now we can release a patched version for alpha
git commit -am "fix: typo in message"
git push origin master
yarn lerna publish
You can see also usage package has bumped because of a dependency to alpha that has been automatically updated by lerna.
If you work in a team on a fairly complex project, I think having a code versioned according to these standards can simplify your life and save it in some cases! First, you have a history to go through and if the commits are self-explanatory you also have good documentation for free. Second (valid in general for any type of versioning), in case of emergency in production you can always switch back to a stable version of your product!