Releasing¶
How to cut a new jsonatapy/jsonata-core release.
Before dispatching¶
- Keep
CHANGELOG.md's[Unreleased]section current as you go, not just at release time — add an entry under### Added/### Changed/### Fixed/etc. as part of the PR that introduces the change, the same way you'd update tests. The release workflow enforces this: it refuses to release if[Unreleased]has no entries (see below), but a changelog written after-the-fact from a commit-message dump is much lower quality than one written when the change was still fresh in your head. - Confirm
cargo test --all-features,cargo clippy --all-targets --all-features -- -D warnings,cargo fmt --check, and the Python reference suite are all green onmain. - Decide the version number. This project's release versions track the jsonata-js major/minor
version they target (patch numbers are independent — see the README) to reduce
API-compatibility confusion for adopters. E.g. a release whose features/fixes target
jsonata-js
2.2.xshould itself be numbered2.2.x, not a simple patch-increment of whatever the previous jsonatapy release happened to be.
Dispatching a release¶
Trigger the Release workflow manually
(workflow_dispatch) from the Actions tab, or via gh workflow run release.yml -f version=X.Y.Z.
Set dry_run: true first if you want to exercise the build/test/publish steps without actually
publishing to PyPI/crates.io.
What the workflow does automatically¶
- Validates the version string is well-formed semver.
- Bumps
Cargo.toml/pyproject.toml/Cargo.lockto the new version, and thejsonata-core = "…"install snippet inREADME.md(it warns, without failing, if that snippet no longer matches). - Bumps
CHANGELOG.md: renames the accumulated## [Unreleased]section to## [$VERSION] - $DATEand inserts a fresh, empty[Unreleased]template above it (scripts/bump-changelog.sh). This step fails the release if[Unreleased]has no bullet entries — that's a strong signal the changelog wasn't kept current during development (see step 1 above). If you hit this and the release genuinely has nothing user-facing to note, add a bullet by hand (e.g. "Internal maintenance release, no user-facing changes.") rather than bypassing the check. - Commits the version bump (including the updated
CHANGELOG.mdandREADME.md) and creates thevX.Y.Ztag — idempotently: re-dispatching the same version after a partial failure reuses the existing commit/tag/changelog entry instead of duplicating them, but fails loudly (does not silently proceed) if the tag already exists at a different commit than the one this run would release, since that's previously caused stale-commit bugs in benchmark/build jobs that all check outrefs/tags/v$VERSIONexplicitly. - Builds wheels for all target platforms, builds the sdist, runs the test suite against the
built wheels, and (unless
dry_run: true) publishes to PyPI and crates.io, then creates the GitHub Release. The release notes are this version'sCHANGELOG.mdsection (scripts/release-notes.sh, with empty subheadings dropped), followed by a link to the full commit comparison and the install instructions. What you write in the changelog is what readers see on the release page, so write it for them. The notes are generated before anything is published, and the release fails if the section is missing or empty. - Runs the performance benchmark suite on the self-hosted Mac Mini runner (not a GitHub-hosted
runner — single-sample wall-clock benchmarks on shared/virtualized cloud runners are dominated
by noise, confirmed empirically: identical code measured twice swung -66% to +120%), records the
results to the
benchmark-databranch underresults/macos-arm64/, regeneratesdocs/performance.mdfrom this release's numbers and commits it tomain, and opens an issue if any benchmark regressed more than 10% versus the previous release. - This runner needs Node.js manually provisioned once (
scripts/setup-macos-benchmark-runner.sh, run directly on the runner host, not in CI) — everything else (Python viauv, the Rust toolchain) is installed fresh by the workflow's own GitHub Actions on every run. See that script's comments for why (sudo/Homebrew weren't available, so it installs Node as a plain user-space binary and extends the runner's own.pathfile instead). - A separate, PR/push-triggered workflow (
.github/workflows/benchmark.yml) runs onubuntu-latestand maintains its own, separate baseline underresults/ubuntu-latest/— intentionally decoupled from this job's macOS baseline, since comparing aubuntu-latestPR run against a Mac Mini release baseline would be exactly the apples-to-oranges mismatch this setup exists to avoid.
After a release¶
- Verify the new version on PyPI/crates.io and that
import jsonatapy; print(jsonatapy.__version__)reports the expected version. - If anything about the release process itself needs fixing, that's a
.github/workflows/release.ymlchange like any other — it's not a separate, undocumented process.