product:release
Table of contents
- Introduction
- What a release does
- Releasing a given version or product
- Releasing while you develop
- Leaving a channel out
- Releasing in an emergency
- Finishing a release that stopped
- Waiting out PyPI’s new-project limit
- Fixing a refused release
- Image registries
- Publishing to an official repository
- Reference
- Requirements
- See also
Introduction
product:release publishes a new version of your product. It bumps the version, commits the deploy bundle for it, and publishes the package to PyPI, the tag and a release page to the forge, and the image to your registries. A clone of the tag then deploys with docker compose alone.
splent product:release
validate UVL configuration is satisfiable
contract no conflicts detected
features all 7 feature(s) versioned and on PyPI
Reading the current version...
Current version: v1.0.3
Bump type:
[1] patch v1.0.4 bug fixes, no new features
[2] minor v1.1.0 new features, backward compatible
[3] major v2.0.0 breaking changes
[4] cancel
Choice: 1
Will release as v1.0.4
Proceed? [y/N]: y
Releasing my_first_app v1.0.4
Release gate (channels)
forge ready, my-org/my_first_app accepts push, tag and release as drorganvidez
pypi ready, credentials accepted, my_first_app exists so this is a new version
docker ready, credentials accepted for git.splent.io/my-org/my_first_app
Pre-release checks (lint + tests)
...
Re-checking the channels, right before publishing
...
version 1.0.4 written to pyproject.toml
...
lock 45 third-party package(s) pinned in docker/requirements.lock
commit changes committed locally
pypi upload complete
push origin/main updated
tag v1.0.4 pushed to origin
forge release created https://git.splent.io/my-org/my_first_app/releases/tag/v1.0.4
docker git.splent.io/my-org/my_first_app:1.0.4, git.splent.io/my-org/my_first_app:stable
docker push complete
index marketplace index rebuild requested, so this version appears there within a couple of minutes.
my_first_app v1.0.4 released.
The wizard offers the next patch, minor and major version. Build, deploy and release tells this command apart from product:build and product:deploy.
What a release does
Nothing changes until the checks pass. The release first refuses a product it cannot reproduce (see Fixing a refused release), then checks your credentials and asks every channel whether it would accept this version. It runs lint and product:test, and asks the channels again right before publishing.
Then it writes [project].version, writes the release commit and publishes in a fixed order. PyPI goes first, because a PyPI version can never be replaced. The forge comes next with main, the tag v<version> and the release page, and the image comes last.
Releasing a given version or product
splent product:release v1.0.4
splent product:release v1.0.4 --product other_app
Pass the version and the wizard does not ask. --product releases that product, and runs its tests, whichever one is selected.
Releasing while you develop
A release is what is committed. So when your checkout lists features with no version and HEAD pins them all, you can release as usual.
splent product:release
checkout my_app has editable features, so this works from what HEAD holds (3f2a91c08b1d).
The release runs in a temporary workspace holding HEAD. Afterwards your checkout moves forward to the release commit and keeps your pyproject.toml, with the new version in it. Anything else you left uncommitted stays out of the release and stays in your checkout.
If your checkout cannot move, because a file you changed is one the release changed too, it stays as it was and the release tells you what to run once it can.
The release’s tests run in your development stack, so they test what it loads, your working copies included. The CI tests the pinned releases.
Leaving a channel out
splent product:release --no-docker
splent product:release --no-pypi
splent product:release --no-forge
--no-docker publishes no image, and a warning says so. The bundle of that version then builds its image from the repository.
--no-pypi and --no-forge leave this version off PyPI or off the forge for good, so the release asks you to confirm. With --no-forge the commit and the tag stay on your machine.
Releasing in an emergency
splent product:release v1.0.5 --skip-checks
--skip-checks skips lint and tests. The guards and the channel gate still run.
Finishing a release that stopped
When a step after the PyPI upload fails, the channels have diverged. The release says so and prints the command that finishes the rest, image included, without a new bump.
The channels have DIVERGED for this version. Finish it, without bumping the version, with
splent release:resume product:my_first_app
The command names the product and carries --no-pypi, --no-forge and --no-docker when the run used them. Until it runs, the gate refuses a new release of the product. --allow-unfinished releases anyway.
Waiting out PyPI’s new-project limit
splent product:release --wait
The first release of a product creates its PyPI project, and PyPI limits how many you create per hour. When the budget is spent, the release waits up to about an hour with a countdown, or stops before changing anything and says when to come back. --wait waits however long it takes. See PyPI new-project limits.
Fixing a refused release
Two guards run first, before any credential is read, and a refusal leaves the product as it was.
Every feature must be pinned
Cannot release: the following features are editable (no version pinned):
- splent-io/splent_feature_notes (in [tool.splent].features)
A product release must be reproducible. Pin all features first:
splent feature:release <feature>
splent feature:attach <feature> <version>
It refuses when HEAD has editable features too. When only your checkout has them, see Releasing while you develop. This covers features, features_dev and features_prod. Release the feature and pin it with feature:attach, or pin every editable one with feature:pin.
A clone must be able to deploy
A clone of the tag has to deploy and build the image on its own. Each refusal starts with Cannot release and says what to do.
| Fails when | Fix |
|---|---|
The product does not pass the checks of product:build |
Fix what they report |
A pinned feature is not in .splent_cache or not linked in <product>/features/ |
Run splent product:resolve |
| PyPI does not serve the workspace’s framework or the running CLI | Release it first |
docker/Dockerfile.<product>.prod cannot build from the repository |
Run splent product:sync-template <product> --docker and commit it |
.gitignore matches deploy/, docker/requirements.lock, docker/frontend.lock.json, splent.contracts.json or docker/spl/ |
Remove the rules that match |
Image registries
[tool.splent.release]
registries = ["forge"]
mirrors = ["dockerhub"]
The image goes to every registry in registries, forge by default, and to the mirrors it can reach, in the table of your release channels.
| Name | Image | Tags | Login |
|---|---|---|---|
forge |
<forge host>/<org>/<product>, the organization of origin lowercased |
<version>, stable |
SPLENT_FORGE_TOKEN, as SPLENT_FORGE_USER or the token’s account |
dockerhub |
<DOCKERHUB_USERNAME>/<product> |
<version>, stable, latest |
DOCKERHUB_USERNAME and DOCKERHUB_PASSWORD |
A registry is required. The channel gate blocks the release until it accepts the credentials and does not hold <version> yet. The forge token needs the package write scope, write:package on Forgejo (see the forge token).
docker blocked, git.splent.io rejected the credentials (HTTP 401)
SPLENT_FORGE_TOKEN is invalid, expired or lacks the package scope. Run 'splent tokens:setup' for instructions on obtaining tokens.
A mirror is best effort. When its credentials are missing or its login or push fails, it is left out with a warning and the release goes on.
The stable tag
stable moves only when a release publishes, so a host that runs <image>:stable only runs released versions. The deploy bundle runs that tag by default, through SPLENT_IMAGE in deploy/.env.
Publishing to an official repository
When your product’s public home is not the forge, a release can reach it too.
[tool.splent.release.source_mirror]
url = "https://github.com/org/repo"
branches = ["main", "develop"]
The release moves those branches to the commit of its tag, pushes the tag and creates the release there, before it pushes the image. That way a version never reaches production before the page your app links to. branches is main when you leave it out.
A branch only moves forward. The channel gate blocks the release when a branch there has commits your repository lacks, and says how to bring them in.
git fetch git@github.com:org/repo.git main
git merge FETCH_HEAD
Use git merge -s ours FETCH_HEAD when your repository’s tree is the one that stands.
If that repository is down, leave it out and finish it later.
splent product:release --no-mirror
splent release:resume product:<product>
Reference
Options
| Option | Description |
|---|---|
VERSION |
The version to release, such as v1.2.1. Without it the wizard asks |
--product NAME |
Release this product instead of the selected one |
--skip-checks |
Skip lint and tests |
--no-pypi |
Publish nothing to PyPI for this version. Asks you to confirm |
--no-forge |
Push nothing to the forge. Asks you to confirm |
--no-docker |
Build and push no image |
--no-mirror |
Do not publish to the official repository now |
--allow-unfinished |
Release even though the previous version never finished publishing |
--wait |
Wait however long PyPI’s new-project limit takes |
The release commit
The release writes what product:build writes, for the new version, and commits it with the bump. Files that were untracked when the release started stay out.
| What | Detail |
|---|---|
deploy/ |
The bundle. With an image, it runs <registry>/<org>/<product>:stable from the first release on |
[tool.splent.runtime] |
The framework and CLI the image installs |
splent.contracts.json, docker/spl/, docker/frontend.lock.json |
What the image reads and checks |
docker/requirements.lock |
Resolved afresh, so each release picks up PyPI’s fixes. Left as it is with --no-docker |
The image
The image is the build of product:build, made once with every tag from the files of the commit at HEAD. A file git does not track never reaches it, so a registry serves exactly what a clone of the tag builds. No .env file but the *.example templates reaches it.
Errors
| Message | What to do |
|---|---|
Missing required environment variables |
Set the credentials it lists |
no version under [project] |
Add version under [project] in pyproject.toml |
registries is empty, or an unknown registry |
Remove the key, or name forge or dockerhub |
the image's build stage did not build |
Nothing was committed. Undo the bump in pyproject.toml, or release again |
production Dockerfile not found |
Run splent product:sync-template |
could not log into <registry>, the push to <registry> failed |
Fix its credentials, then run release:resume |
mirror blocked |
Follow what it says. Nothing has changed yet |
<tag> on <url> and DIVERGED |
Fix what it names, then run release:resume |
A push or fetch never waits for a password and gives up after 10 minutes.
Credentials
| Variable | Needed | Description |
|---|---|---|
TWINE_USERNAME or PYPI_USERNAME |
Unless --no-pypi |
PyPI user |
TWINE_PASSWORD or PYPI_PASSWORD |
Unless --no-pypi |
PyPI API token |
SPLENT_FORGE_TOKEN |
Unless --no-forge, and for the image while forge is a registry |
Forge token with the package write scope. GITHUB_TOKEN is read when it is unset |
SPLENT_FORGE_USER |
No | The account of the forge registry login |
DOCKERHUB_USERNAME, DOCKERHUB_PASSWORD |
When dockerhub is a registry |
Docker Hub login. A mirror without them is left out |
Check them before you release with splent check:forge and splent check:pypi.
Requirements
- A selected product (
SPLENT_APP), or--product. - Every feature pinned, the credentials above, and Docker unless
--no-docker.
See also
feature:releasereleases one feature.release:resumefinishes a release that stopped halfway.release:cliandrelease:frameworkrelease the CLI and the framework.