Scaffolding a project using lerna and lerna-changelog
Lerna is a tool for managing monorepos. This article talks about an ideal setup that covers the development, release and maintenance phases.
Lerna allows you to maintain multiple packages using yarn workspaces. This is useful when we build an application which has optional plugins like Babel.
- First, we will start by installing lerna using
npm install lerna — save-dev
This installs lerna and adds it as a
devDependency in our package.json file.
2. For initiating lerna, we need need to run
npx lerna init
3. This will create a lerna.json file in our project with some basic configurations. Lerna operates the project in two modes, Fixed/Locked and the other is Independent. By default,
lerna init starts the project in fixed mode.
packages folder stores all the packages that are being developed. It can be of any name, but the folder name should match with the name that we specified in the lerna.json file.
It refers to the registry client that we like to use. By default, lerna uses yarn and its workspace feature. When choosing npm, you might run into an issue when resolving packages because npm resolves them only at the root level.
In fixed mode, all packages follow the same version which we configure in lerna.json. And in independent mode, the version of each package is maintained individually. The best reference in what to choose is clearly explained in this repo.
4. Now, we need to make some changes in our package.json file to use workspaces.
The workspaces attribute defines the folder, which should be used as a workspace. We can have multiple workspaces configured under one project.
private attribute should be
true in the package.json file. This specifies it as a root and is not supposed to be used as a package.
Now that the setup and configuration are done, we need to start creating our packages. This can be done by using
lerna create <package-name>
This will create a package under the workspace that we configured in lerna.json file.
We created three packages called Sun, Moon and Sky. Now, we want to add Sky as a dependency for Sun and Moon. So, we need to update packages of Sun and Moon by adding Sky as a dependency in their package.json files.
Which looks like this
For importing Sky in both Sun and Moon, we need to go inside the packages folder and run
yarn install . This creates
symlinks between packages and makes them available under `node_modules`.
Now, when we run
lerna version or
lerna publish , it will update the version of all the packages everywhere it is referred. Then it pushes them as git release or an npm release.
Running Commands across all packages
Lerna can run npm commands across all the packages at once. It gives the status about execution if the command is a success or a failure.
Let us consider where we have test cases written in all our packages, and we need to run tests in all packages before publishing. For this, we need to add a test/build/lint script in the package.json of the packages.
If we want to do a
build before bumping the version for release, and we want the build to run only in Sun and Moon packages but not in Sky. So, we will write a build script only in Sun and Moon.
lerna run <npm-script>
lerna run build
This will run the
build scripts in Sun and Moon packages.
A changelog keeps track of all changes that are being achieved in any milestone. It acts as a single source of truth and releases notes as well.
There are two ways to achieve this, one is by accessing commit messages via conventional commits. Another way is to use lerna-changelog.
If the project follows conventional commits (which you can learn more here), we access them by adding access attribute in lerna.json.
After this, the changelog can be generated using
lerna version <minor | major> — conventional-commits
This will update the version number for all packages and generates a changelog. It segregates them using milestones and commit prefixes.
If we have a milestone from 0.1 to 0.2, instead of combining all the commit messages, it will divide them into paragraphs. It uses Features (feat), Tests (test), Chore (chore) prefixes that we added in the commit.
The one downside of this, it combines all the commit messages which we don’t need. And sometimes 3–4 commit messages may reflect a fix or a feature in one PR.
If we are working on an issue and want to mention it as fixed in the release notes or in the changelog. In the journey, we added a fix, a test, and a chore commit. Three messages get added in different locations in the changelog, which makes it confusing to read. Messages related to one issue will be mentioned in different locations.
lerna-changelog is a PR based changelog management tool. Every PR speaks only about one entity, it can be either a fix, a feature, or an enhancement. A release notes or the changelog with all the PR’s merged makes the notes look much cleaner.
As a bonus, it even mentions the person who fixed the issue or built a feature.
By default, it comes with the support for monorepo. It adds the package names in which the fixes or features were made. One of the awesome implementations of this is Babel’s Changelog.
First, we need to install lerna-changelog on our project as devDependency
npm install lerna-changelog — save-dev
To generate a changelog we need to run
We can pass optional
— from and
— to flags, when we want changelog between specific milestones. By default, lerna-changelog generates it from all the PR’s that were merged, after the latest git tag/release.
Let's see how it is done, with the help of snippets below.
Each label in the PR is mapped to attribute in the lerna.json file, which is picked as headings in the changelog.
ignoreCommiters is used to ignore any specific users PR’s, into the changelog. The ideal example will be the PRs that are raised by bots.
When I tried to set-up changelog for a project, I was overwhelmed with the number of ways we can do this. After leveraging all the possibilities, lerna-changelog works like a charm for any project.
Here is my repository which is configured with lerna. Feel free to reach out to me if you come across any queries.