Changelord, registrar of deeds extraordinaire
Changelord, registrar of deeds extraordinaire
This year’s Perl release brought us some nice candies, no least of all signatures being finally, FINALLY out of experimental status. In honor of it, and because I was feeling nostalgic, I dusted off my 1337 P3rl skillz and wrote a brand new cli application: Changelord, registrar of deeds extraordinaire.
(yeah, it’s a mouthful. I was originally going for the shorter and more to-the-point Changelord, annals master, but I could already hear the snort-giggles from the back of the room, so I reconsidered)
Changelord, registrar of what now?
A long time ago, I was writing a lot of Perl modules. Along the way, I also developed a love for semantic versioning, which allows me the greatest of all luxuries: not to think about things.
To help with the publishing of Perl modules to CPAN, I was (and still am) using dist-zilla and a gazillion plugins. Some of those plugins help the bumping of versions and the generation of the project’s CHANGELOG.
With my setting, I would have the CHANGELOG
have a first section looking
like this:
Revision history for Taskwarrior-Hooks
{{$NEXT}}
[ API CHANGES ]
[ BUG FIXES ]
- blah blah blah
[ DOCUMENTATION ]
[ ENHANCEMENTS ]
- some of blahs
[ NEW FEATURES ]
[ STATISTICS ]
When cutting a new release, the plugins would look into the non-empty
subsections, figure out if the version bump should be a patch / minor / major,
do it, replace the {{$NEXT}}
title with it, remove the empty subsections AND
put some stats of the number of files and lines changed since the last
version.
That, already was already pretty awesome right there.
Of course, in JavaScript land, there are also tons of tools to
help with semantic versioning and all that good stuff. The ones I was
favoring lately glean the changes from commit messages following the pattern
<type of change>: description
. On paper it’s terrific, as the changelog
message is literally part of the commits. In practice, it has its edges.
Forgot to put such a commit message in the branch? Oopsy, time for a
rebase. Oh, that was 3 merges ago? … Well, hope you like pain, ‘cause
Git ordered some for you and the courier is about to ring the doorbell.
So, anyway, because of that I decided to try to take a step back to what was working so nicely. And spruce it up while we’re at it.
Like, let’s use YAML as the source of truth to formalize things (and make them programmatically searchable and stuff).
And let’s formalize the formalization and give that YAML a JSON Schema.
And— ah, but enough tell, let’s show you what I have so far.
Port an already-existing changelog
Let’s assume you already have a project, and it has a changelog looking like so:
# Changelog for Some Glorious App
## v1.0.0, 2022-04-01
- Some stuff
- Some other stuff
## v0.0.1, 2022-01-01
- Initial release.
First thing is to initialize the changelog new source of truth:
$ changelord init
file 'CHANGELOG.yml' created, enjoy!
And indeed, if we look at CHANGELOG.yml
, we have a brand-new stubbed
changelog:
project:
name: ~
homepage: ~
ticket_url: ~
with_stats: true
releases:
- version: NEXT
date: ~
changes: []
change_types:
- keywords:
- feat
level: minor
title: Features
- keywords:
- fix
level: patch
title: Bug fixes
- keywords:
- chore
- maint
- refactor
level: patch
title: Package maintenance
- keywords:
- stats
level: patch
title: Statistics
Right now the changelog has 3 sections. One for the project meta-data, one for the different releases (including the upcoming one). And a last one to describe all the types of changes that we’ll use.
Using our example, let’s fill the project metadata with some non-boring values:
project:
name: Some Glorious App
homepage: https://github.com/yanick/glorious-app
ticket_url: s!#!https://github.com/yanick/glorious-app/issue/!
with_stats: true
Already with that we can render a (pretty blank) changelog:
$ changelord
# Changelog for [Some Glorious App][homepage]
[homepage]: https://github.com/yanick/glorious-app
## NEXT
We can also verify that our yaml file is up-to-snuff with the schema:
$ changelord validate
woo, changelog is valid!
And we can also have a peek at said schema:
$ changelord schema
$defs:
change:
additionalProperties: false
properties:
desc:
type: string
ticket:
type:
- string
- null
type:
type:
- string
- null
required:
- desc
type: object
additionalProperties: false
properties:
change_types:
items:
additionalProperties: false
properties:
keywords:
items:
type: string
type: array
level:
enum:
- major
- minor
- patch
title:
type: string
type: object
type: array
project:
additionalProperties: false
properties:
homepage:
description: url of the project's homepage
examples:
- https://github.com/yanick/app-changelord
type:
- string
- null
name:
description: name of the project
examples:
- App::Changelord
type:
- null
- string
ticket_url:
description: perl code that takes a ticket string (e.g. 'GH123') via the `$_` variable and turns it into a link.
examples:
- s!GH(d+)!https://github.com/yanick/App-Changelord/issue/$1/
- '/^d+$/ ? "https://.../$_"': undef
type: string
with_stats:
description: 'if true, add git statistics when bumping the version.'
type: object
releases:
items:
oneOf:
- type: string
- additionalProperties: false
properties:
changes:
items:
$ref: '#/$defs/change'
type: array
date:
type:
- null
- string
version:
type: string
type: object
type: array
type: object
Okay, we have already two releases. Do we have to convert them into a YAML structure off the bat? I mean, two releases are no big deal, but if you have an older project with hundreds of them…
Don’t worry, as it is, you don’t have to. You can just drop them verbatim right there like a cold clump of congealed spaghetti:
releases:
- version: NEXT
date: ~
changes: []
- |
## v1.0.0, 2022-04-01
- Some stuff
- Some other stuff
## v0.0.1, 2022-01-01
- Initial release.
And with that:
$ changelord
# Changelog for [Some Glorious App][homepage]
[homepage]: https://github.com/yanick/glorious-app
## NEXT
## v1.0.0, 2022-04-01
- Some stuff
- Some other stuff
## v0.0.1, 2022-01-01
- Initial release.
Of course, in due time you probably want to at least convert the most recent release to something more formal:
releases:
- version: NEXT
date: ~
changes: []
- version: v1.0.0
date: 2022-04-01
changes:
- Some stuff
- Some other stuff
- |
## v0.0.1, 2022-01-01
- Initial release.
Let’s add some changes
To add changes, you can edit CHANGELOG.yml
yourself, or you can
delegate the job to changelord:
$ changelord add --type chore some trivial thing
$ changelord add --type feat --ticket '#123' some awesome thing
The yaml file is updated as you would expect:
releases:
- version: NEXT
date: ~
changes:
- desc: some trivial thing
type: chore
- desc: some awesome thing
ticket: '#123'
type: feat
And the rendered version does its thing too:
$ changelord
# Changelog for [Some Glorious App][homepage]
[homepage]: https://github.com/yanick/glorious-app
## NEXT
### Features
* some awesome thing [#123]
### Package maintenance
* some trivial thing
[#123]: https://github.com/yanick/glorious-app/issue/123
## v1.0.0, 2022-04-01
* Some stuff
* Some other stuff
## v0.0.1, 2022-01-01
- Initial release.
Bump the version
And for the (for now) final step, the bumping of the version:
$ changelord bump
new version minted: v1.1.0
Which updates the version
, date
, and statistics
in the yaml file:
releases:
- version: v1.1.0
date: 2022-06-20
changes:
- desc: some trivial thing
type: chore
- desc: some awesome thing
ticket: '#123'
type: feat
- desc: 'code churn: 7 files changed, 179 insertions(+)'
type: stats
Which are themselves reflected in the rendered changelog:
$ changelord
# Changelog for [Some Glorious App][homepage]
[homepage]: https://github.com/yanick/glorious-app
## v1.1.0, 2022-06-20
### Features
* some awesome thing [#123]
### Package maintenance
* some trivial thing
### Statistics
* code churn: 7 files changed, 179 insertions(+)
[#123]: https://github.com/yanick/glorious-app/issue/123
## dummy, 2022-04-01
* Some stuff
* Some other stuff
## v0.0.1, 2022-01-01
- Initial release.
Feeling cute, might publish later
And that’s the extent of what I have so far. If I feel it, I might eventually document the whole thing and publish it to CPAN, until then it lives in its snug little repo.