Release
Table of contents
- Introduction
- How a release runs
- Choosing the version
- Checking the channels first
- Lint and tests before the release
- Publishing to one channel only
- Finishing a release that failed halfway
- Creating new PyPI projects
- Advanced
- Reference
- See also
Introduction
The release commands publish a SPLENT package to the git forge and to PyPI. They share one pipeline, which checks every channel before it changes anything and uploads to PyPI first, since PyPI never takes a version back.
| Command | Releases |
|---|---|
feature:release |
One feature |
product:release |
One product, Docker image included |
release:cli |
splent_cli |
release:framework |
splent_framework |
release:resume |
Nothing new. It finishes a release that stopped halfway |
You need a forge token and a PyPI token (tokens:setup), not splent login.
splent release:framework 1.30.1
Release gate (channels)
forge ready, splent-io/splent_framework accepts push, tag and release as drorganvidez
pypi ready, credentials accepted, splent_framework exists so this is a new version
version 1.30.1 written to pyproject.toml
commit changes committed locally
pypi upload complete
push origin/main updated
tag v1.30.1 pushed to origin
forge release created https://git.splent.io/splent-io/splent_framework/releases/tag/v1.30.1
splent_framework v1.30.1 released.
How a release runs
- Credentials for every enabled channel.
- The channel gate, a quick first pass.
- The build hook, such as a feature’s frontend bundle.
- Lint and tests.
- The channel gate again, the verdict acted on.
- The version bump.
- The pre-commit hook, such as a feature’s contract.
- A local commit. Files untracked before the release stay out.
- The upload to PyPI.
- The push of the branch and the tag, and the forge release.
- The post hook, such as a feature’s snapshot or a product’s image.
- A request to rebuild the marketplace index, which never fails the release.
A refusal before step 6 changes nothing.
Choosing the version
Leave the version out and a wizard offers the next patch, minor or major, from the highest of the forge’s newest tag, your local tags and [project].version. A version you pass must be MAJOR.MINOR.PATCH, with or without a leading v.
The release writes [project].version only, never the SPL pin of [tool.splent.spl_model].
Checking the channels first
Before the first change, the gate asks each channel whether it takes this version. Each line says ready, blocked with the cause, or SKIPPED. Doubt counts as blocked, and a refusal changes nothing.
| The gate refuses when | What to do |
|---|---|
| The forge token is missing or cannot push, or the repository is archived | Fix the token or the repository |
| The forge or PyPI rate limits you | Wait |
| The version is below the newest tag | Release a higher version |
| The tag or the PyPI version already exists | Release a higher version, or resume |
| PyPI rejects the token, or it is scoped to another project | Use a token for this project |
| A product’s image registry rejects the credentials or is unreachable | Fix the registry credentials |
| The previous version is tagged but missing from PyPI or the forge | Resume it, or pass --allow-unfinished |
A version released with --no-pypi counts as finished. A new PyPI project reads ready with a note, since no check can see PyPI’s creation limit.
Lint and tests before the release
| Command | Lint | Tests |
|---|---|---|
release:cli, release:framework |
ruff check and ruff format --check, as splent lint |
pytest tests in the package |
feature:release |
The same | feature:test, in a neutral app when your product does not declare the feature or with --neutral (see Where the tests run) |
product:release |
The same | product:test |
A failure stops the release with nothing changed. splent lint --fix fixes the formatting.
--skip-checksskips lint and tests, for emergencies only. The gate still runs.
Publishing to one channel only
[tool.splent.release]
channels = ["forge"]
A package that never goes to PyPI declares it once. Without the table it goes to the forge and PyPI. "github" is read as "forge", and an unknown key or channel is an error. A product adds registries and mirrors for its image.
For one release only, pass --no-forge or --no-pypi and confirm. With --no-forge the commit and the tag stay local.
Finishing a release that failed halfway
The channels have DIVERGED for this version. Finish it, without bumping the version, with
splent release:resume splent_feature_notes
A step that fails after the upload leaves the channels out of step. Run the command it prints, never a new release, which would burn a version number. With --no-pypi a failed push publishes nothing.
Creating new PyPI projects
New versions are never limited, new projects are. PyPI allows 20 an hour per user, 40 per IP and 10 a day per organization, in a moving window that ignores refused attempts. SPLENT stays 2 below, at 18 an hour, and at 8 a day when SPLENT_PYPI_ORGANIZATION is set, counting this machine’s creations in <WORKING_DIR>/.splent/pypi_creations.json (~/.splent/ outside a workspace).
A wait of up to 65 minutes happens on its own, with a countdown. A longer one stops with nothing changed and says when to come back, unless you pass --wait.
error: PyPI would refuse creating splent_feature_notes now (HTTP 429, too many new projects).
New projects are allowed again at 2026-10-31 18:05:12 UTC, in 2 h 10 min.
If the upload itself is refused, the release waits once and uploads again. A second refusal stops it with the release:resume command, and nothing diverged.
Advanced
The release pushes the branch origin calls its default, usually
main. Pushes and fetches never prompt and give up after 10 minutes. After the upload, that is a divergence.
Reference
Environment variables
| Variable | Default | Effect |
|---|---|---|
SPLENT_FORGE_TOKEN, TWINE_USERNAME, TWINE_PASSWORD |
Your tokens | |
SPLENT_PYPI_CREATE_PER_HOUR |
20 |
PyPI’s hourly limit per user |
SPLENT_PYPI_CREATE_PER_DAY_ORG |
10 |
PyPI’s daily limit per organization |
SPLENT_PYPI_ORGANIZATION |
unset | The PyPI organization new projects go to |
SPLENT_PYPI_LEDGER |
see above | The ledger’s path |
Errors
| Message | What to do |
|---|---|
'<version>' is not a release version |
Use MAJOR.MINOR.PATCH |
no version under [project] in <path> |
Add version = "0.1.0" to [project] |
release refused, <channel> will not accept it |
See the gate table |
<tag> is tagged but missing from ... |
Resume it, or --allow-unfinished |
lint failed, tests failed for <name> |
Fix them |
The channels have DIVERGED |
Run the printed release:resume |
See also
check:releasescompares the git tags with PyPI across the workspace.check:forgeandcheck:pypicheck your tokens.