How the "changelog-updater" Action works

• 3 min read

I've recently published a new GitHub Action called changelog-updater. This post explains how the Action works under the hood.

If you want to learn more about the Action, why I've created it and how you can use it in your projects, read the introduction post here.


In contrast to my previous GitHub Action – git-auto-commit – the changelog-updater-Action is not written in Bash, but in PHP. Or more precisely: the CLI that powers the Action is written in PHP.

Writing software in Bash does not spark as much joy in my life as writing PHP code does. (If you think PHP is a dead language, I encourage you to read PHP in 2021. The language received some significant updates over the last few years and has a thriving community).

The mentioned CLI is called php-changelog-updater and has been created by using the Laravel Zero micro-framework.
My choice fell on Laravel Zero, as it allows me to bundle the CLI into a single executable that can easily be distributed. The user does not have to install any required PHP dependencies. They can just execute the binary by running php changelog-updater. And as the name suggests, Laravel Zero is based on the Laravel framework. As I'm already very familiar with Laravel I could get started with Laravel Zero very quickly.

The "meat" of the CLI is a command that uses the terrific league/commonmark package to turn the passed CHANGELOG.md markdown document into an abstract syntax tree (AST).
The CLI command then traverses the AST to find the previous released version. If one exists, the given version and release notes are added to the top of the document. If no previous versions are found, the CLI attaches the given notes to the end of the document.

If the changelog loosely follows the "Keep A Changelog" format and contains an "Unreleased" heading, the CLI will do a bit of magic.
It will find the "Unreleased" heading and extract the previous version number and repository URL. Together with the new version number, the CLI will generate new compare URLs for headings and will place them all in the right spot in the changelog file.

The command then uses my commonmark-markdown-renderer PHP package to turn the AST back into markdown.

The updated markdown is then pasted back to the CHANGELOG.md file and is ready to be committed.


Initially I wanted to write this Action in JavaScript by using packages from unifiedjs.com. I've built a prototype CLI here but decided against going further with it for a couple of reasons:

  • I lack the experience of writing CLIs in JavaScript/Node.js and I didn't want to create a piece of software that I can't easily update in the future. (I have been bitten to many times by the NPM ecosystem in the past and struggled to get an old project running again)
  • The packages the team behind unified.js are publishing are great, but I spent many frustrating evenings figuring out which of the 150 available repositories/packages I actually need. The fact that the packages are split between two GitHub organisations (unifiedjs and syntax-tree) doesn't make that easier.
  • I personally think the tooling around creating CLIs in Node.js doesn't exist or is very hard to find. I spent some time looking for boilerplates or examples on how to write a Node.js CLI in 2021 but couldn't find anything that was up to date. The PHP ecosystem seems to be much more organized in this regard.

Writing the CLI in PHP was actually a lot of fun. Thanks to my past experience with Laravel I could knock out the CLI in a couple of hours.