I made a Julia fork of Release Please. It has been useful enough that I thought it is worth sharing!
For context, Release Please is a release automation tool. For every commit to a GitHub repository, it:
- looks at the commits since the last release,
- decides, based on Conventional Commits, whether a new release should be proposed,
- if so, opens a PR that bumps the version and updates the changelog. The maintainer can merge this release PR at their leisure - it will update automatically for new changes.
The version bump follows SemVer, with the major, minor, or patch change inferred from the commit messages. Once that PR is merged, Release Please creates the tag and GitHub release.
In this Julia version, I also have it set up to trigger JuliaRegistrator automatically, so there is no need to manually comment on a release commit. It removes a lot of the friction!
Conventional Commits
A “Conventional Commit” is a specific structure to commit messages. The standards are:
fix:for a bug fixfeat:for a new featuredocs:for documentation changesrefactor:for internal code changes that do not change behaviortest:for test changesci:for CI and workflow changeschore:for maintenance work (used by dependabot)
Commits can also include a scope, such as feat(core): or fix(parser):, if you want to categorize changes more precisely.
Breaking changes should be marked explicitly, usually with ! (for example feat!: or feat(core)!:). Putting BREAKING CHANGE: in the commit body also works.
The scope part is optional, but it’s useful in larger repositories because it makes changelog entries easier to scan (it will automatically break it down by this). For release purposes, the main signals are still fix, feat, and explicit breaking changes. If you have a change that does too many things to describe in one commit, it’s probably a good indication to split things up into smaller commits.
Why use this instead of TagBot + Registrator?
The usual Julia workflow is some combination of JuliaRegistrator, TagBot, and manual version or changelog updates. That works, but I have found there is a lot of friction involved to create (or even remember to create) a release, and then comment on the GitHub commit after it gets merged.
Release Please makes releases represented as a PR. The version bump is there, the changelog update is there (generated by the commit history), and the merge itself is what triggers the release. I find this easier to inspect and review.
GitHub Workflow
This is the workflow I am using now instead of TagBot + JuliaRegistrator comments. Feel free to copy.
GitHub action file: .github/workflows/release-please.yml (note that you might need to change the branch name from main - in three places here)
name: release-please
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
issues: write
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/checkout@v6
with:
repository: MilesCranmerBot/release-please
ref: 911ad18bdc3bb52bfef564cfbcb35f31aac01df3
path: release-please-src
- uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install release-please deps
working-directory: release-please-src
run: npm ci
- name: Build release-please
working-directory: release-please-src
run: npm run compile
- name: Create or update release PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node release-please-src/build/src/bin/release-please.js release-pr \
--token "$GITHUB_TOKEN" \
--repo-url "https://github.com/${{ github.repository }}" \
--target-branch main \
--config-file release-please-config.json \
--manifest-file .release-please-manifest.json
- name: Create GitHub release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node release-please-src/build/src/bin/release-please.js github-release \
--token "$GITHUB_TOKEN" \
--repo-url "https://github.com/${{ github.repository }}" \
--target-branch main \
--config-file release-please-config.json \
--manifest-file .release-please-manifest.json
Then, it needs a config for release please in release-please-config.json:
{
"bootstrap-sha": "TODO: current HEAD commit before merging bootstrap PR",
"packages": {
".": {
"release-type": "julia",
"include-component-in-tag": false,
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true
}
}
}
If you have multiple packages in a single repository, you include the subfolder instead of ".", and also set include-component-in-tag to true as needed. Note that the bump-minor-pre-major stuff is so it will be compatible with 0.x.y style releases.
You need to also set a .release-please-manifest.json with the current version of your package(s):
{
".": "1.12.0"
}
What it looks like:
I backported a commit to my release-v1 compatibility branch: fix: enforce constraints in hall-of-fame (backport) (#593) · astroautomata/SymbolicRegression.jl@cc7e4c0 · GitHub. This uses the fix: prefix so will show up as a bug fix.
Fixes trigger new patch versions, and features trigger new minor versions. Thus, my CI (which I have a dedicated release-v1 workflow for) suggested making a new release:
This was automatically inferred to be a patch (1.13.1 → 1.13.2), since it only includes a bug fix, without any new features.
I merged that PR on this commit: chore(release-v1): release 1.13.2 (#597) · astroautomata/SymbolicRegression.jl@a8a11b9 · GitHub. release-please runs again, and sees that the release was just triggered. So it creates the tag, and also comments to let JuliaRegistrator know:
This kicks off the rest of the registration pipeline, creating 1.13.2.
I have it set up to automatically set the subdir argument of register as well, so that this should work (untested) for multi-package repositories as well!
Also, the upstreaming PR to release-please proper is here (feel free to
so it gets merged faster…). But the fork should work too.
Happy releasing!


