This is the last in a series of posts about creating my very own release toolkit for automated package publishing. The other posts are here:
Finally, publishing! This has been a fun process, it's nice to be almost finished. It wasn't a terribly large project, but big enough that I kept putting it off. Breaking it down into small chunks helped me wrap my head around it. So did writing about it.
Much of the work since the last post was plugging away at small tasks, building
out the version
, verify
, and publish
commands. Each one had small, clear
goals so it was easy to find time here and there to work on it.
I did end up using commander
to pass in arguments from the command line so I
didn't need to answer the prompts by hand every time. I also created a release
script, just a wrapper around version
and changelog
so I could create a
release in one line:
yarn release --minor --type Feature --message "- Sweet feature"
As always, writing tests along the way made it painless when I needed to make changes, and helped me quickly discover when I had broken things.
This step actually took me longer than I expected. It's simple, basically just
npm publish
, but creating a GitHub release caused some friction. I initially
started with tags, but releases are nice becuase you can see right from the repo
homepage what the latest version is.
One thing: once you've published, npm
and GitHub make you jump through some
hoops to discourage un-publishing a version of a package, since it would cause
things to break for anyone using that version as a dependency. After the
left-pad
incident
1
a couple years ago npm
put even stricter protections
in place.
Even if you can un-publish, I try to be very careful about making sure
something is ready before releasing a version. You can publish a patch of
course, it's just a little embarassing putting something into the public and
then realizing you missed something. I gave publish
a --dry-run
option to
see the output of the publishing step without actually publishing.
This is also why I included a verify
step: with GitHub branch protection you
can make sure nothing gets merged to the main
branch without first verify
ing
that the version and changelog have been updated. If you run this on all your
branches (alongside commands like test
and lint
) and require pipelines to
pass before merging to main
, you've taken an extra step to make sure you're
really ready to publish. At that point you can set it up to automatically
publish
whenever branches merge to main
. That's CI/CD (continuous
integration / continuous delivery) in a nutshell!
The most annoying part of this was publishing a GitHub release. Turns out it was mosly my own dang fault. Their documentation is usually so good, but for some reason it was near impossible to find documentation for this specifically: automatically creating a release from a GitHub workflow.
Doing this from the command line using curl
? Easy. Locally in Node with
node-fetch
or axios
? No problem. Directly in a GitHub workflow run
command
using the GITHUB_TOKEN
environment variable? Piece of cake.
But making an authenticated request in a workflow from Node to /release
eluded
me for hours.
I tried every combination of authentication I thought would work, stirring a
cauldron of Bearer
s, token
s, and username
s while muttering incantations,
but my attempts did not bear fruit. I know there are third-party actions for
this, but where' the fun in that? These 7 year old forum posts aren't going to
read themselves at 1:00am!
Pipeline debugging is my least favorite kind, due to the long feedback cycle.
Make a change, push it, watch the pipeline fail after 45 seconds, rinse and
repeat for three hours. Sometimes there are even more steps in there if I'm
testing changes that only happen on a protected main
branch. There are a few
tricks to shorten the loop—similar to all other debugging the key is
isolation—in this case we can comment out pipeline steps to move things
along faster.
Engineers spend a lot more time debugging than we'd like to admit, so my #1 debugging rule is enjoy it as much as you can. I have a friendly relationship with debugging, I always try to think of it as a game, a puzzle, or a detective story. It really does make it more satisfying when you say "Forget it, Jake. It's Chinatown" when you finally squash that bug.
So, what was the issue? I was getting the repository name with
git remote get-url origin
and pulling the name out of the url. Turns out that
command returns a different url format locally than it does in the CI pipeline,
so my /release
request was failing not due to authentication but due to an
invalid url (the response is 404 in either case, so it wasn't obvious). GitHub
actually provides a GITHUB_REPOSITORY
environment variable, so... time well
spent. 🙄
Got everything working quickly after that, and then spent some time cleaning up
and documenting in the README
.
This has been a fun little journey! I learned a bit more than I thought I would, and got to set up my custom release toolkit exactly how I want it.
1: For more information see this article, or literally just google "left pad incident." As long as you're googling incidents, look up "The Spaghetti Incident?" 2 Really an underrated album, some of Duff McKagan's best work.
2: As long as you're googling pasta music, look up "The Tortellini Accident." Really an underrated album, some of Buster Poindexter's best work.