Release

Table of contents

  1. Introduction
  2. How a release runs
  3. Choosing the version
  4. Checking the channels first
  5. Lint and tests before the release
  6. Publishing to one channel only
  7. Finishing a release that failed halfway
  8. Creating new PyPI projects
  9. Advanced
  10. Reference
    1. Environment variables
    2. Errors
  11. 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

The release pipeline CHECKS, NOTHING IS CHANGED Credentials for each channel Gate, pass one forge, PyPI, registries Lint and tests ruff and the test suite Gate, pass two the binding verdict CHANGES, IN THIS ORDER Bump project version Commit after the hook Upload to PyPI permanent Push and tag forge release Post hook image or snapshot local, nothing published yet release:resume when a step here fails
A refusal before the first change leaves nothing behind. A failure after the upload is finished with release:resume, at the same version.
  1. Credentials for every enabled channel.
  2. The channel gate, a quick first pass.
  3. The build hook, such as a feature’s frontend bundle.
  4. Lint and tests.
  5. The channel gate again, the verdict acted on.
  6. The version bump.
  7. The pre-commit hook, such as a feature’s contract.
  8. A local commit. Files untracked before the release stay out.
  9. The upload to PyPI.
  10. The push of the branch and the tag, and the forge release.
  11. The post hook, such as a feature’s snapshot or a product’s image.
  12. 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-checks skips 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


Table of contents


Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es