I keep forgetting how to release my own software. My major project Clientele has a PUBLISHING.md file with every step written down, but I release so rarely that I end up with the file open in one window and a terminal in the other, copying and pasting commands and fat-fingering version numbers. Writing the steps down helped, but it is still tedious to copy and paste - why can't I just execute the file? So, following pauldot and paulias, I built paulrun.
Paul (me). Run(books). ๐ I think I am getting a bit of psychosis.
A paulrun runbook is just a markdown file. It reads like normal documentation, renders fine on GitHub, and you can still follow it by hand if you want. Mark a code block with run and paulrun executes it. Inputs get declared once in the frontmatter, checked against a pattern, and filled in wherever a placeholder like <VERSION> appears. And a confirm block pauses for the bits only a person can do, like clicking buttons on GitHub:
RELEASING.md---
title: Release my-package
inputs:
- name: VERSION
description: The version to release, e.g. 1.2.0
pattern: '^\d+\.\d+\.\d+$'
---
# Releasing my-package
Make sure the changelog is up to date first. This line is just documentation.
## Test and tag
```sh run
make test
git tag <VERSION>
git push origin <VERSION>
```
## Publish on GitHub
```confirm
Create a GitHub release from tag <VERSION>, then come back here.
```
shelluv tool install paulrun
paulrun check RELEASING.md # find mistakes before anything runs
paulrun go RELEASING.md --dry # show every command, run nothing
paulrun go RELEASING.md # ask for VERSION, then run each step
Once I had a few runbooks working I wanted them everywhere, but not every runbook belongs to a single project. Updating dependencies is the same in every repo I have, and my routine for rebuilding a frontend at work is personal - it doesn't belong in a work repository. So 0.2.0 introduces the runbook library: one folder (~/runbooks by default) that holds my whole collection, where every .md file is a book I can run by name from anywhere. To make that work, blocks now run in whichever folder I start paulrun from rather than the folder the runbook lives in, which means one book works on every project:
shellpaulrun books # see what's in my collection ๐
cd ~/code/clientele
paulrun go update-deps # run a book from my library, right here
paulrun go # forgot the name? pick from a list
And of course, I dogfood all of it. paulrun releases itself with its own PUBLISHING.md, which checks I'm on an up-to-date main with the changelog ready, then runs the tests, bumps the version, tags, deploys the docs and publishes to PyPI.
The docs are here, the code is on GitHub, and it's MIT licensed - so as always, fork away. ๐โโ๏ธโโก๏ธ