Build and Release
The document outlines the steps to build and release deeptab package. It is assumed that all feature branches and PRs for the release have been reviewed, approved, and merged into main before starting this process.
Release workflow
%%{init: {'theme': 'base', 'themeVariables': {
'primaryColor': '#dbeafe',
'primaryTextColor': '#1e3a5f',
'primaryBorderColor': '#3b82f6',
'lineColor': '#6b7280',
'secondaryColor': '#ede9fe',
'tertiaryColor': '#f0fdf4',
'edgeLabelBackground': '#f9fafb'
}}}%%
flowchart TD
A["git checkout main && git pull<br/>git checkout -b release/vX.Y.Z"]:::git --> B[Hotfixes & doc updates]:::setup
B --> QA["Quality checks<br/>lint → format → check → test"]:::ci
QA --> QAP{All pass?}:::decision
QAP -->|No| B
QAP -->|Yes| D["Build docs: just docs"]:::setup
D --> E[Commit changes & push branch]:::git
E --> F{Release type?}:::decision
F -->|RC| RC1["just bump-rc-preview<br/>then just bump-rc<br/><i>(append --increment MAJOR<br/>if not auto-detected)</i>"]:::setup
F -->|Stable| ST1["just bump-preview<br/>then just bump<br/><i>(append --increment MAJOR<br/>if not auto-detected)</i>"]:::setup
RC1 --> RC2["git push --follow-tags<br/>(pushes vX.Y.ZrcN)"]:::git
RC2 --> RC3[CI: publish-testpypi.yml]:::ci
RC3 --> RC4[TestPyPI + GitHub pre-release]:::rc
RC4 -->|Issues found| B
RC4 -->|RC approved| ST1
ST1 --> ST2["Open PR: release/vX.Y.Z → main"]:::pr
ST2 --> ST3{Review & approve}:::decision
ST3 --> ST4[Merge PR into main]:::pr
ST4 --> ST5["git checkout main && git pull<br/>git tag -d vX.Y.Z (local)<br/>git tag vX.Y.Z && git push origin vX.Y.Z"]:::git
ST5 --> ST6[CI: publish-pypi.yml]:::ci
ST6 --> ST7[PyPI + GitHub Release]:::stable
classDef setup fill:#dbeafe,stroke:#3b82f6,color:#1e3a5f
classDef pr fill:#ede9fe,stroke:#8b5cf6,color:#3b0764
classDef decision fill:#fef9c3,stroke:#ca8a04,color:#713f12
classDef git fill:#f0fdf4,stroke:#22c55e,color:#14532d
classDef ci fill:#fff7ed,stroke:#f97316,color:#7c2d12
classDef rc fill:#fdf4ff,stroke:#d946ef,color:#701a75
classDef stable fill:#ecfdf5,stroke:#10b981,color:#064e3b
1. Create the release branch
After all PRs for the release are merged into main, create a dedicated release branch.
Important
Always branch off from an up-to-date main. Never start a release from a stale local copy.
git checkout main && git pull
git checkout -b release/vX.Y.Z
git push -u origin release/vX.Y.Z
2. Apply hotfixes and documentation updates
Use the release branch to apply any last-minute bug fixes, dependency security patches, or documentation updates required before the release.
Note
Only bug fixes and documentation changes belong on the release branch. New features must target the next release cycle via main.
Warning
If you update any dependencies (e.g. to resolve security findings), upgrade the specific package (this also rewrites poetry.lock):
poetry update <package> # no just recipe: targets a single package
Then verify the change does not break any tests.
Security audit: run just audit and resolve any vulnerability with an available fix before bumping the version:
just audit
Vulnerabilities with no upstream fix available should be noted and tracked as known accepted risks.
3. Quality checks
Run all checks in the order shown below. Each step must pass cleanly before proceeding to the next.
3. Quality checks
Run these in order. Each must pass cleanly before proceeding to the next.
Step |
Command |
What it does |
|---|---|---|
Lint |
|
|
Format |
|
|
Hooks |
|
All pre-commit hooks across all files: ruff, prettier, and Pyright. |
Tests |
|
Full test suite with coverage. |
Important
If just check modifies any files, stage and commit them before continuing:
git add -u && git commit -m "style: apply pre-commit formatting"
Warning
A test failure at this stage must be fixed on the release branch before proceeding. Do not skip, suppress, or comment out failing tests.
4. Documentation
Build the HTML docs locally. Sphinx treats all warnings as errors (-W), so every warning must be resolved before proceeding.
just docs
Review the rendered output in docs/_build/html/. Check for broken links, missing API entries, and any rendering issues on new or changed pages.
Note
See the Documentation Guide for docstring conventions and tips on building docs locally.
5. Commit all changes
Once all checks and the documentation build pass cleanly, stage and commit any outstanding changes:
git add -A
git commit -m "chore(release): pre-release fixes and QA for vX.Y.Z"
git push origin release/vX.Y.Z
Note
Prefer just commit over a manual git commit to stay consistent with the conventional commit style enforced by commitizen.
6. Version bump
The version bump is driven entirely by Commitizen. The increment (MAJOR / MINOR / PATCH) is inferred from the conventional-commit messages since the last tag, the version in pyproject.toml is updated, and the matching CHANGELOG.md section is generated and committed with an annotated tag.
Important
Always run --dry-run first and review the proposed version and CHANGELOG entries carefully before applying the bump.
The following Commitizen settings in pyproject.toml shape this behaviour:
Setting |
Value |
Effect |
|---|---|---|
|
|
Prereleases start at |
|
|
On the stable bump, all |
|
|
|
The bump commands are wrapped in just recipes so the correct Commitizen flags are applied consistently:
Recipe |
Wraps |
Use for |
|---|---|---|
|
|
Preview an RC bump |
|
|
Apply an RC bump |
|
|
Preview a stable bump |
|
|
Apply a stable bump |
Each recipe forwards extra arguments to Commitizen, so flags such as --increment MAJOR can be appended directly (e.g. just bump-rc-preview --increment MAJOR).
6a. Release candidate bump
Use the bump-rc recipes to cut an RC. The first prerelease of a cycle is rc1 (thanks to prerelease_offset = 1); subsequent ones increment to rc2, rc3, and so on.
Note
Commitizen can only infer the target version from commits. For a major release where the commit history does not contain a feat!: / BREAKING CHANGE: marker, force the increment explicitly with --increment MAJOR (likewise MINOR / PATCH).
Step 1, preview:
# Append --increment MAJOR/MINOR/PATCH if the bump is not inferred correctly
just bump-rc-preview
Confirm the proposed tag (e.g. v2.0.0rc1) and that the CHANGELOG entries are complete and correctly classified.
Step 2, apply:
just bump-rc
This updates version in pyproject.toml, appends the vX.Y.ZrcN section to CHANGELOG.md, creates the bump: commit, and creates the vX.Y.ZrcN tag locally.
Step 3, review and push (commit and tag together):
git show HEAD
git push --follow-tags origin release/vX.Y.Z
Note
--follow-tags pushes the annotated vX.Y.ZrcN tag along with the branch in one step, which triggers publish-testpypi.yml. See step 7, Tag and publish a release candidate, below if you prefer to push the tag separately.
6b. Stable bump
When all RCs are approved, cut the final stable version with the plain bump recipes. Because changelog_merge_prerelease = true, the intermediate rcN sections are merged into a single complete vX.Y.Z CHANGELOG section, so end users see the full release notes in one place.
just bump-preview # append --increment MAJOR if needed
just bump
This updates pyproject.toml, regenerates CHANGELOG.md (with prereleases merged), and creates the bump: commit plus the vX.Y.Z tag.
Review the bump commit:
git show HEAD
Check that pyproject.toml shows the correct version and that CHANGELOG.md reads cleanly. Manually amend duplicate entries if present, then push the branch (the stable tag is pushed later, after the release PR is merged — see step 9):
git push origin release/vX.Y.Z
See Versioning for the full SemVer rules and commit-type reference.
7. Tag and publish a release candidate
cz bump --prerelease rc (step 6a) already created the annotated vX.Y.ZrcN tag locally. RC tags are pushed directly from the release branch, with no PR to main required.
If you pushed with git push --follow-tags in step 6a, the tag is already live and you can skip to verifying CI. Otherwise, push the tag explicitly:
git push origin vX.Y.ZrcN
This triggers publish-testpypi.yml, which publishes to TestPyPI and creates a GitHub pre-release.
If issues are found, fix them on the release branch (return to step 2), bump to the next RC with cz bump --prerelease rc (which yields rcN+1), and repeat.
8. Release PR (stable only)
Once all RCs are approved (or skipping RC for a straightforward release), open a PR from release/vX.Y.Z to main on GitHub.
After review and approval, merge the PR
Merging to
maindoes NOT trigger a PyPI release
Important
Promoting a model from experimental to stable? Verify the Model Promotion Policy checklist is complete before merging a release PR that includes a promotion.
9. Create and push the stable tag
The stable cz bump in step 6b created a local vX.Y.Z tag on the release branch, but the authoritative tag must point at the merge commit on main. After the release PR is merged into main, drop the local tag and re-create it on main:
git checkout main && git pull
git tag -d vX.Y.Z # remove the local tag created by cz bump
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.Z
Warning
Pushing the tag triggers PyPI publication immediately and cannot be undone. Confirm that main is in the expected state and the version in pyproject.toml is correct before pushing.
10. Publish package
The tag push automatically triggers the appropriate GitHub Actions workflow. Both use OIDC Trusted Publishing, so no API tokens are required. See CI/CD for full details.
Tag pushed |
Workflow |
Result |
|---|---|---|
|
|
PyPI + GitHub Release |
|
|
TestPyPI + GitHub pre-release |
11. GitHub Release
The GitHub Release is created automatically by the publish workflow. Once it appears, verify that:
The release notes reflect the correct CHANGELOG section
Assets (wheel and sdist) are attached
The release is marked stable (not pre-release) for a stable tag
Add any manual context or migration notes to the release description if needed.