Have you ever read a guide on how to write commit messages, tried to stick to it and… failed? We’ve all been there. Using random phrases, like “fixes”, “final changes”, “added [something]”, or even forgetting what you really wanted to include in the message leads straight to a commit history from your worst nightmares. Such messages tell us absolutely nothing about the project, and soon turn following changes into hell. Sad reality of being a software developer?
Not anymore! We hope that after reading this article you will no longer have problems with self-discipline, and you will learn how to better document your work. We will also show you how to automate it all, avoid inappropriate commit message format and properly version the modules.
Introduction
Semantic Versioning, Conventional Commits and related tools are powerful — and very easy to use! They’ll not only take your skills to the next level, but also speed up the development process. Their advantages are significant:
→ generating changelogs and determining the next module version can be automated (based on commit messages),
→ the whole process of building and publishing the module can be automated,
→ contributing to your project is easier thanks to a more structured commit history.
Before we start with Conventional Commits, we need to know what the Semantic Versioning is.
1. Semantic Versioning
You probably already use something similar to Semantic Versioning in your projects, but that’s not enough, especially when the project is a complex one. At some point you might get stuck in a dependency hell — and you really wouldn’t like that:
“In the world of software management there exists a dreaded place called dependency hell_. The bigger your system grows and the more packages you integrate into your software, the more likely you are to find yourself, one day, in this pit of despair. In systems with many dependencies, releasing new package versions can quickly become a nightmare. If the dependency specifications are too tight, you are in danger of version lock (the inability to upgrade a package without having to release new versions of every dependent package). If dependencies are specified too loosely, you will inevitably be bitten by version promiscuity (assuming compatibility with more future versions than is reasonable)._ Dependency hell is where you are when version lock and/or version promiscuity prevent you from easily and safely moving your project forward. ”
Source: semver.org
So, what is Semantic Versioning?
It’s the way we version the project, and the main guidelines are the following:
- Increment major version when you make incompatible API changes
- Increment minor version when you add functionality in a backwards compatible manner
- Increment patch version when you make backwards compatible bug fixes
- Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format
And that’s it! Isn’t it simple? You can learn more about Semantic Versioning at semver.org.
2. Conventional Commits
The big questions is: which version should we increment? Conventional Commits come to the rescue! This lightweight specification provides an easy set of rules for creating an explicit commit history. Features, fixes and breaking changes made in commit messages allow to determine which version should be released next.
The commit message should be structured as follows:
type(optional scope): description
[blank line]
[optional body]
[blank line]
[optional footer(s)]
Types of commits:
* fix: it patches a bug in your codebase (this correlates with PATCH in semantic versioning).
* feat: it introduces a new feature to the codebase (this correlates with MINOR in semantic versioning).
* BREAKING CHANGE: a commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in semantic versioning). A BREAKING CHANGE can be part of commits of any type.
Types other than fix and feat are of course allowed — the Angular convention recommends: build, chore, ci, docs, style, refactor, perf, test, and others. Learn more here: conventionalcommits.org.
3. Lint commit messages
Once you know what Conventional Commits are, it’s a good idea to be consistent and follow these rules, and preferably use a lint to keep a wrong commit message from passing on to the git history. With this tool, you will find out where you made a mistake when writing your message, and get tips on how to fix it.
How to use it?
Install commitlint dependencies:
npm install --save-dev @commitlint/config-conventional @commitlint/cli
Create commitlint.config.js file with the following content:
module.exports = {extends: ['@commitlint/config-conventional']};
Install husky:
npm install --save-dev husky
Extend your package.json:
// package.json
{
"husky":
{
"hooks":
{
"commit-msg": "commitlint -E HUSKY\_GIT\_PARAMS"
}
}
}
Test your latest commit:
npx commitlint --from HEAD~1 --to HEAD -verbose
This configuration is enough for a start, and if you want to learn more about the potential of this tool, have a look here: commitlint.js.org.
4. Conventional Changelog
How to generate a changelog with Conventional Commits? There are several ways to do this, but the best one is conventional-changelog-cli, which is a standardized cli tool. How to use this tool? Just install it globally using npm install -g conventional-changelog-cli If this is your first time using this tool, and you want to generate all previous changelogs, you could do it as follows:
conventional-changelog -p angular -i CHANGELOG.md -s -r 0
You could use conventional-changelog -p angular -i CHANGELOG.md -s to extend the changelog with the changes since the last version. All available command line parameters can be listed using CLI: conventional-changelog -help. Get packages at conventional-changelog.
5. npm version
Okay, we know how to generate a changelog, but how to automate it? To do this, we will use the npm version script and it's pretty easy to do, just take a look at this command:
npm version [| major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=] | from-git]
For example, you can use such combinations:
npm version major
npm version minor
npm version patch
npm version prerelease --preid=alpha
npm version prerelease --preid=beta
npm version prerelease --preid=rc
Once you know what the npm version command looks like, you can upgrade it with an additional changelog feature. Just type into package.json scripts:
"version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md"
This will extend CHANGELOG.md file with a log of recent changes in the code, based on the commit messages. You could also use preversion and postversion scripts to run tests before the version script, and push changes or publish package after that. Learn more here: docs.npmjs.com/cli/version.
6. .npmrc
Can the process of versioning be even more automated? Sure! By adding a .npmrc file you could also automate your commit message and set your tag prefix. To do this, simply create.npmrc file or run the npm config command with the following content:
tag-version-prefix=""
message="chore(release): %s :tada:"
This way, every time a new version is called up, a new commit will be created with a Conventional Commit message. Learn more about the configuration here: docs.npmjs.com/configuring-npm/npmrc
Summary
As you can see, a few easy steps can take your project management to a higher level, and help you stop treating commit messages like a burden.
This article is also a good introduction to process automation in software development. What we have shown is only one of the possibilities! You have many conventions to choose from, you can expand your scripts with additional features and adapt them to each of your projects.
Top comments (0)