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 verifying
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 Bearers, tokens, and usernames 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.