This document is for maintainers who publish releases of the lager-cli package to PyPI.
If you are looking to contribute code, see CONTRIBUTING.md.
- Python 3.10+
build(pip install build)twine(pip install twine)- A PyPI API token with upload access to the
lager-cliproject - Push access to
lagerdata/lageron GitHub
Contributors work from personal forks. By convention:
| Remote | Points to |
|---|---|
origin |
Your fork (e.g. youruser/lager) |
upstream |
Canonical repo (lagerdata/lager) |
# One-time setup (after forking on GitHub)
git clone git@github.com:<youruser>/lager.git
cd lager
git remote add upstream git@github.com:lagerdata/lager.git
git fetch upstream- Create a feature branch from the latest upstream main:
git fetch upstream git checkout -b my-feature upstream/main
- Make changes, commit, and push to your fork:
git push -u origin my-feature
- Open a PR against the upstream repo:
gh pr create --repo lagerdata/lager
- After review, the PR is merged into upstream
main.
Before starting a release, verify:
- All PRs merged to upstream
mainand CI is green - Tests pass against target hardware boxes
-
CHANGELOG.mdhas entries for all user-facing changes -
SECURITY.mdsupported-version table matches the release you are about to cut - No open security issues that should block the release
Throughout this guide, replace X.Y.Z with the actual version number (e.g., 0.4.2).
Edit the version in cli/__init__.py:
__version__ = 'X.Y.Z'Add a new section at the top of CHANGELOG.md following the Keep a Changelog format:
## [X.Y.Z] - YYYY-MM-DD
### Added
- New features
### Changed
- Changes to existing functionality
### Fixed
- Bug fixes
### Removed
- Removed featuresTo see what changed since the last release, review the commit history:
git log upstream/main..HEAD --oneline --no-decorateCreate docs/source/release-notes/vX.Y.Z.mdx:
---
title: "Version X.Y.Z"
description: "Month DD, YYYY"
---
## <u>Features</u>
- Feature description
## <u>Bug Fixes</u>
- Bug fix description
## <u>Improvements</u>
- Improvement description
## <u>Installation</u>
To install this version:
\`\`\`bash
pip install lager-cli==X.Y.Z
\`\`\`
To upgrade from a previous version:
\`\`\`bash
pip install --upgrade lager-cli
\`\`\`
## Resources
[View Release on PyPI](https://pypi.org/project/lager-cli/X.Y.Z/)Categorization guidelines:
- Features: New functionality, new commands, new device support
- Bug Fixes: Fixes for issues, crashes, incorrect behavior
- Improvements: Performance, code cleanup, minor enhancements
If this release drops support for an older minor version, also bump the table in
SECURITY.md.
Terminology: Use "Lager Box" (not "gateway"), "Lager Boxes" (not "gateways").
Add the new version to the top of the Release Notes list in docs/docs.json:
{
"tab": "Release Notes",
"groups": [
{
"group": "Version History",
"pages": [
"source/release-notes/vX.Y.Z",
"source/release-notes/v0.4.2",
...
]
}
]
}The main branch requires all changes to go through a pull request (direct pushes are blocked). Create a release branch, push it to your fork, and open a PR:
git fetch upstream
git checkout main
git reset --hard upstream/main
git checkout -b release/vX.Y.Z
git add cli/__init__.py CHANGELOG.md docs/source/release-notes/vX.Y.Z.mdx docs/docs.json
git commit -m "vX.Y.Z"
git push -u origin release/vX.Y.Z
gh pr create --repo lagerdata/lager --base main --title "vX.Y.Z"After the PR is reviewed and approved, merge it using Squash and merge or Rebase and merge — the branch requires linear history, so standard merge commits are not allowed.
Create an annotated tag on main:
git tag -a vX.Y.Z -m "vX.Y.Z"
git push upstream vX.Y.ZPushing the tag triggers two workflows in parallel:
-
Release: Validate Tag (
.github/workflows/release-validation.yml) — builds the sdist and wheel from the tagged commit, runstwine check, installs each into a clean venv, asserts the installedlager --versionequals the tag, import-walks the installed package againsttools/packaging_import_baseline.txt, and uploads the result as a workflow artifact nameddist-vX.Y.Z(kept 90 days). -
Release: Publish Box Image (
.github/workflows/box-image-publish.yml) — buildsbox/lager/docker/box.Dockerfileand pushesbox/lager/docker/box.Dockerfileand pushesghcr.io/lagerdata/lager-box:vX.Y.Z(and:X.Y.Z), labelled with the tag and commit it was built from.lager update --pull --version vX.Y.Zfetches that image by digest instead of building on the box; without--pull(the default while this soaks) nothing consumes it. For a box to pull it anonymously the package must be public — set that once in the GitHub UI after the first successful publish (Packages → lager-box → Package settings).The
org.opencontainers.image.versionlabel is what the client checks before deploying a pulled image. If this workflow is ever changed such that the label stops matching the tag, every box silently falls back to building — slower, but never wrong.
Wait for Validate Tag to go green, then download the artifact:
gh run list --repo lagerdata/lager --workflow release-validation.yml --limit 1
gh run download <run-id> --repo lagerdata/lager --name dist-vX.Y.Z --dir distDo not rebuild locally. The artifact is the set of bytes the validation proved; a local rebuild is a different, unproven build. (If the workflow is red, the tag has a real problem -- fix it before anything reaches PyPI.)
A red box-image publish does not block the PyPI upload — no box depends on that image yet — but fix it before cutting the next release.
Upload the downloaded artifact:
twine upload dist/*When prompted, use __token__ as the username and your PyPI API token as the password.
Create a file with the release notes (copy the relevant section from CHANGELOG.md):
gh release create vX.Y.Z --repo lagerdata/lager --title "vX.Y.Z" --notes-file release-notes.mdAlternatively, go to Releases and create a new release manually:
- Tag: Select the
vX.Y.Ztag - Title:
vX.Y.Z - Description: Copy the relevant section from
CHANGELOG.md
Pinning: Boxes pin to a release via its tag —
lager update --version vX.Y.Z. The CLI also accepts the bare formX.Y.Zand resolves it to thevX.Y.Ztag. Do not create a per-version branch; tags are the single source of truth for pinned versions.
- Verify the release is live:
pip install lager-cli==X.Y.Z lager --version
- Sync your fork with upstream:
git fetch upstream git checkout main git reset --hard upstream/main git push origin main
This project follows Semantic Versioning:
- MAJOR (
X): Breaking changes to CLI commands or Python API - MINOR (
Y): New features, new device/instrument support - PATCH (
Z): Bug fixes, documentation, minor improvements
Note: Releases are identified by tags (
vX.Y.Z). Older releases also had a matchingX.Y.Zbranch used for box pinning; these are deprecated —lager update/lager installnow resolve aX.Y.Zpin to thevX.Y.Ztag, so version branches are no longer created. Any remainingX.Y.Zbranches can be recreated from the tag if ever needed (git push origin vX.Y.Z^{}:refs/heads/X.Y.Z).
python -m build fails: Make sure build is installed (pip install build) and you are in the cli/ directory. (Local builds are only needed for debugging; releases use the CI artifact from step 7.)
twine upload fails: Verify your PyPI API token is valid and the version does not already exist on PyPI. Check that the package name is lager-cli.
Import errors during build: Ensure all dependencies listed in cli/setup.py are available in your environment.
Tag already exists: If you need to re-tag (e.g., after a fix), delete the old tag first: git tag -d vX.Y.Z && git push upstream :refs/tags/vX.Y.Z. Only do this if the release has not been published to PyPI.