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. - 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.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 with autogenerated notes. - 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.