Automate CHANGELOGs to Ease your Release

keatenh

Keaten Holley

Posted on April 4, 2023

Automate CHANGELOGs to Ease your Release

How to automate proper CHANGELOGs in software projects with many contributors.

Includes suggested practices and tools for JavaScript or TypeScript development, but only requires npm or similar to execute.


Changelogs are an excellent way to communicate updates between versions of a project. However, they can also be messy, overly technical, verbose, or generally unhelpful. For large projects with regular releases, it can be time-consuming to track down every change and document it properly.

In my case, I have a GitHub project with approximately 30 contributors, 5 maintainers (to decide milestones and review pull requests), and 100s of consuming projects whose operators each require one of two different levels of technical explanation.

The task of creating a proper release process - which prioritized documentation without drowning developers in extra work - seemed daunting at first. I'm here to say it was much more straightforward than I imagined. This blog post will walk you through how I automated the creation of ✨quality✨ changelogs in this project from our commits.


The Task

Taking inspiration from Gene Kim's "3 Ways of DevOps", I set up the project concentrated on a goal of

"Never pass a known defect to downstream work centers".

This meant advocating for and enabling practices like Test Driven Development and putting those checks in early in the product lifecycle.

Coming back to changelogs, I wanted to similarly shift our documentation work left by setting up an understandable contract with my fellow contributors and keeping the onus to wrangle commits and document changes off of the maintainers.

This meant enabling developers in 3 stages of the SDLC by introducing:

  1. Clean git Process
  2. Tools for Formatting Commits
  3. Workflows for Generating Changelogs

Elephant standing in an empty room

Surprise, An Elephant Appears!

To achieve our end goal of "No manual changelogs", we have to first address a potential elephant in the room: Not everyone agrees you should automate changelogs the way I describe here. One of the fastest ways to discover this is to visit https://keepachangelog.com which uses the clever tagline:

"Don’t let your friends dump git logs into changelogs."

The site goes on to describe some guiding principles, which I would encourage you to check out. The TL;DR is:

  • Follow Semantic Versioning (Or mention your strategy)
  • Log every version
  • Group your changes by type
  • Write Changelogs for Humans and NOT Machines

There is clearly a gap between git logs and what we want consumers to read for our changes. The next section will tackle what processes we want in place to make our commits resemble human-readable changes, and the following section will discuss tooling to make that process obtainable by your team.

As for the elephant, only you can determine your trade-offs. Make your decision of what to automate a deliberate one, that reflects the long-term goals for your project.

Close up of hand of person holding bucket of cleaning supplies

Clean git Process

In order to enable our automation with specialized tools, we will need to meet a convention with our commit messages and version tags. The standards I'm referring to here are helpfully called Conventional Commits and Semantic Versioning.

Semantic Versioning

This is a very common versioning standard met by most npm packages (in the case of JS). The important part of our versioning strategy is that it controls when we will generate changelogs: The diff in commit logs between the previous version tag and the current version tag represents the new information entering our changelogs on a release. In the case of semver, these are 3 sets of numbers that increment based on the impact of your changes. See Semantic Versioning for more info.

Conventional Commits

This is how we can parse information about the change, such as the type, from the commit. See the full spec at Conventional Commits .
This brings with it the concept I find the most difficult in practice:

Each commit should represent a single change of a known type and optional scope.

For example: fix(core): remove bad behavior xyz

This is a big step up from the bad habits many of us start with when using git. However, it becomes much easier with some tools, a little practice, and the right merge strategies set up in GitHub.

Branch and Merge Strategies

With the commit history theoretically reflecting our change history 1:1, the cleanest results will come from simple branching strategies, like trunk-based development, where everyone cuts features from a common main branch (which requires Pull Requests to update). Requiring your feature commits to be squashed in Pull Requests by configuring the Rebase+Squash merge strategy in GitHub (instead of the default) will also keep the history clean and make it easier to avoid issues with the upcoming tools I want to talk about.

Tools for Formatting Commits

With our standards defined, we can reach into our toolbag and grab some CLI leverage to help unify the contributors and communicate what we need for success. The tools I mention here are geared for JS/TS projects that utilize package runners like npm. Keep in mind other projects could utilize them with a little extra work if you are willing to use Node.js or make some alterations.

Commitizen

Writing in the conventional commit style I mentioned above is pretty straightforward, but not everything you and your fellow contributors work on will have the same requirements. Also, accidents happen. It helps to have a way to construct these messages the same way every time.

commitizen is a CLI tool that you can introduce to create commits with your rules, which handily default to the Conventional Commit format! I installed commitizen from npm into my project's devDependencies so that the whole team has the same ruleset. To continue setting up without relying on global installs, I used npx:



npx commitizen init cz-conventional-changelog


Enter fullscreen mode Exit fullscreen mode

This command updates the rest of what we need in our project's package.json file with the standards we already set



"devDependencies": {
  ...
  "cz-conventional-changelog": "^3.3.0"
 },
 "config": {
   "commitizen": {
     "path": "./node_modules/cz-conventional-changelog"
 }


Enter fullscreen mode Exit fullscreen mode

Once you get to this point, you can fire off an npx cz and see what we want the developer to see when making their commit:

Computer terminal with 'npx cz' command output

cz's nice command prompt makes it where we don't leave anything needed out of our commit, and problems like character count in the subject line get called out as we write our messages.
Scope, change type, issues/tickets affected, and breaking changes are all accounted for.

Husky

Now that we have a tool to help us make great commits, let's make it the default way anyone commits to the project. We can set the expectation for contributors to use the command line if they want help getting the right commits every time.

Husky is my tool of choice for setting up git hooks. With this tool, we can make sure that every step of the commit process has passed the checks we care about. Unit tests, linting, Typescript builds, and more can be called from scripts every time a user walks through their commit process to push to the GitHub repository from their local machine.

To ensure developers have access to the same hooks you do, you can install this package into your devDependencies. Besides installing using npm, yarn, etc - We also need to run an npx husky install to set up the hooks. To ensure this happens on the other developer workspaces, we slap this bit into our prepare block of the package.json:



"scripts": {
   "prepare": "is-ci || husky install",


Enter fullscreen mode Exit fullscreen mode

Here, is-ci is another optional devDependency that will keep hooks from registering in your remote CI environments, where you might be adding chore commits, tags, etc.
Following cz's docs we can then tap into the git commit command prompt using:



npx husky add .husky/prepare-commit-msg "exec < /dev/tty && npx cz --hook || true"


Enter fullscreen mode Exit fullscreen mode

Then BOOM, when a contributor installs your project and types "git commit", they will be met with the same CLI we saw in the previous section. Pretty snazzy 😎

Commitlint

So now all of our developers are automagically 🪄 making good commit messages, right?
If you want to ensure that what makes it into the feature branch will be usable for the last step of converting git logs to changelogs, we need one more thing: commit linting.

As you probably guessed by now, yes, there are npm packages for that.
Installing @commitlint/cli and @commitlint/config-conventional and setting up a post-commit or pre-push git hook with husky is one option. Another is logging your latest commit on your pull requests and running merge checks from, say, a GitHub Workflow. Ultimately, it depends on how and where you want to communicate problems in the incoming change.
An example of the latter can look like:



git log -n 1 --format=%B | npx commitlint


Enter fullscreen mode Exit fullscreen mode

Automatically Generating Changelogs

Person walking in wheat field with harvest in hand

conventional-changelog-cli

Lastly, with the work we have put into setting up our repo, we can move from sowing to reaping with one more handy tool. conventional-changelog-cli can be used whenever and wherever you are handling a version change in your project. Their documentation on the linked npm page walks you through how to configure it to the "npm version" command, but if you need to simply run the package after bumping your version(s), you can use:



npx conventional-changelog-cli -i 'CHANGELOG.md' -s -t v -p angular


Enter fullscreen mode Exit fullscreen mode

from an environment that has access to all the previous git tags. Using the conventional-changelog-cli repository as an example, you can see the types of logs you can expect from this CLI:

Change logs listed for a version with features/fixes added

That's our goal. We asked the contributors to use the terminal for their git commits and in turn we can automatically generate changelogs based on every pull request to our repository. Obviously, this still requires understandable commit messages from developers, but that feedback is now visible on the Pull Request with the rest of the incoming work. No more hunting down a single contributor for context on release day when a feature takes longer than you both expected.


Although nothing is ever complete in terms of automation, taking the time to enable every contributor to consider the changes they commit and communicate them properly has helped our releases become less stressful. I hope the processes and tools I've mentioned show you similar results, or help you consider the options that make documentation easier and clearer.

But what do you think?

  • Let me know if you Agree or Disagree with my tools and processes.
  • What are you using for changelogs (or other release automation)?
  • What more would you like to read about this topic (such as handling changelogs in a monorepo)?

Keaten Holley
https://github.com/Keatenh

💖 💪 🙅 🚩
keatenh
Keaten Holley

Posted on April 4, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related