feature:release

Table of contents

  1. Introduction
  2. How a release runs
  3. Choosing the version
  4. Pinning the new version in your product
  5. Rehearsing a release
  6. Where the tests run
  7. Publishing to one channel
  8. Releasing under a PyPI name of your own
  9. Shipping the frontend bundle
    1. Licences and audit
  10. Fixing what stops a release
    1. A breaking parameter change
    2. A migration revision id too long
    3. An SQL or XSS sink
    4. A previous release that never finished
    5. The PyPI new-project limit
    6. A failure after the upload
  11. Advanced
    1. Feature reference
    2. What the release writes
    3. The cache snapshot
  12. Reference
    1. Arguments and options
    2. Exit codes
    3. Errors
  13. Requirements
  14. See also

Introduction

feature:release publishes a new version of a feature you develop at the workspace root. It checks the feature, bumps the version, uploads it to PyPI, tags and releases it on the forge, and keeps a read-only copy of the tag in the cache. Run it when your changes are ready for products to pin.

splent feature:release splent_feature_notes
  Reading the current version...
  Current version: v1.2.6

  Bump type:
    [1] patch  v1.2.7   bug fixes, no new features
    [2] minor  v1.3.0   new features, backward compatible
    [3] major  v2.0.0   breaking changes
    [4] cancel

  Choice: 1

  Will release as v1.2.7
  Proceed? [y/N]: y

  Releasing splent_io/splent_feature_notes v1.2.7

  Release gate (channels)
  forge    ready, splent-io/splent_feature_notes accepts push, tag and release as alice
  pypi     ready, credentials accepted, splent_feature_notes exists so this is a new version
  bundle   building assets/dist in production mode...
  bundle   built, 2 file(s) in assets/dist

  Pre-release checks (lint + tests)
  checks   ruff (lint + format)...
  checks   lint OK
  checks   tests...
  checks   tests OK

  Re-checking the channels, right before publishing
  forge    ready, splent-io/splent_feature_notes accepts push, tag and release as alice
  pypi     ready, credentials accepted, splent_feature_notes exists so this is a new version

  version  1.2.7 written to pyproject.toml
  contract updating from source code...
  contract written to pyproject.toml
  bundle   assets/dist goes into the release commit
  commit   changes committed locally
  pypi     building package...
  pypi     uploading...
  pypi     upload complete
  push     origin/main updated
  tag      v1.2.7 created
  tag      v1.2.7 pushed to origin
  forge    release created https://git.splent.io/splent-io/splent_feature_notes/releases/tag/v1.2.7
  snapshot cloning splent_feature_notes@v1.2.7...
  snapshot /workspace/.splent_cache/features/splent_io/splent_feature_notes@v1.2.7 (read-only)

  index    marketplace index rebuild requested, so this version appears there within a couple of minutes.
  splent_io/splent_feature_notes v1.2.7 released.


  Attach splent_feature_notes@v1.2.7 to my_first_app? [Y/n]:

The pipeline and its gates are the same for every SPLENT release. Release explains them.


How a release runs

The steps of a feature release and where each one publishes CHECKS, NOTHING CHANGES 1 Channel gate forge and PyPI accept it 2 Bundle, lint, tests webpack, ruff, feature:test 3 Channel gate again, the binding one ON THIS MACHINE 4 Version [project].version 5 Contract, bundle written and staged 6 Commit local, not pushed yet PUBLISHES, IN ORDER 7 PyPI build and upload, permanent 8 The forge push the branch, tag, release page 9 Cache snapshot read-only clone of the tag Active product attach, when you say yes A failure after the upload is a divergence. release:resume finishes it with the same version. --dry-run rehearses up to the package build, in a temporary copy, and changes nothing.
PyPI goes first because a version there can never be replaced, while a tag that was never pushed costs nothing.

Nothing changes until the second gate passes, so a failing check leaves no version bumped. The release also refuses, before it starts, a contract requirement that is not PEP 440, a migration id too long, an SQL or XSS sink and a breaking parameter change the version does not announce.


Choosing the version

splent feature:release splent_feature_notes v1.2.7
splent feature:release splent_feature_notes --bump patch

Name the version, or pass --bump, to skip the wizard. The version is MAJOR.MINOR.PATCH, with or without the v.

Both the wizard and --bump start from the highest version among the forge’s tags, the local tags and [project].version, or from v0.0.0. When the forge cannot list its tags and nothing local knows a version, the command refuses to guess.


Pinning the new version in your product

splent feature:release splent_feature_notes --attach

At the end, the release offers to pin the new version in the active product with feature:attach. --attach pins it without asking. Without a terminal, as in CI, it prints the feature:attach command instead.


Rehearsing a release

splent feature:release splent_feature_notes --dry-run --bump major

--dry-run runs every check a real release runs, builds the package in a temporary copy, and changes nothing. It needs no credentials. It prints what a real run would do, then the verdict.

  Dry run. Nothing was changed: no file, no commit, no tag, nothing pushed or uploaded.
  A real release would be refused right now:
    - forge: SPLENT_FORGE_TOKEN is not set
    - pypi: PyPI credentials are not set

It exits 0 when a real release would go through, and 1 otherwise. Without a terminal, pass the version or --bump, since the wizard cannot ask.


Where the tests run

splent feature:release splent_feature_notes --neutral

The gate runs the tests of the working copy you release. Where they run depends on the active product.

The active product Without --neutral With --neutral
declares the feature In the product’s app. A failure stops the release In a neutral app, which gates the release. Then in the product’s app, as a check that only warns
does not declare it In a neutral app, which gates the release The same

The neutral app holds the framework, the feature, the newest releases of what it requires and the default configuration. Nothing of your product reaches it but its test database. Use --neutral when a template or setting of your product should not decide the release of a feature other products use.


Publishing to one channel

splent feature:release splent_feature_notes --no-pypi
splent feature:release splent_feature_notes --no-forge

--no-pypi publishes to the forge only. A production image then installs the version from the tag on the forge.

--no-forge publishes to PyPI only. The commit and the tag stay on this machine, and there is no snapshot and no attach step. Once release:resume has pushed the tag, run the feature:attach command the release prints.

Both ask you to confirm. A feature that never publishes to a channel declares it once, under [tool.splent.release] channels, and is not asked again.


Releasing under a PyPI name of your own

[project]
name = "splent-feature-notes-alice"

The PyPI project is [project].name. When a feature’s name is taken on PyPI, give your copy another one. The import package and the name products use stay the same.

feature:attach records the new name in the product’s [tool.splent.distributions], where feature:pip-install and product:build read it. The forge repository is whatever origin points at, so it can live under your own account.


Shipping the frontend bundle

Every channel installs what the tag holds, so the tag must carry the bundle. The release builds it before the tests, in production mode, into an emptied assets/dist, and commits it with the code.

The feature has The bundle is built
assets/js/webpack.config.js With feature:compile --release, in the active product’s web container
frontend/package.json In the SPLENT toolchain image, with its licences and audit gated, see A feature with frontend/
Neither Not at all

A bundle that fails to build stops the release, with nothing changed.

  error: the frontend bundle of splent_io/splent_feature_notes was not built: feature:compile --release failed.
     ERROR in ./scripts.js
     Module not found: Error: Can't resolve 'mustache'
  The release tags the bundle with the code, so it does not go on without one.
  webpack runs in the product's web container (splent product:up --dev starts it).
  Try the build on its own with: splent feature:compile splent_io/splent_feature_notes --release
  No version bump, no commit, no tag, no release.

A feature with frontend/ also commits assets/meta, with the manifest, third_party.json and an SBOM. The manifest is checked against the files once more before the commit, as feature:compile --verify does.

Stop any feature:compile --watch of the same copy before you release. If assets/dist changes after the build, the release stops with the version bumped but not committed.

Licences and audit

What a bundle may ship depends on the feature’s [project].license. The gate reads the licence of every package the bundle ships, from assets/meta/third_party.json, and of every wheel in splent.vendored.

A package under LGPL feature, or any other licence GPL feature AGPL feature
MIT, ISC, BSD-2/3-Clause, 0BSD, Apache-2.0, Zlib, CC0-1.0, Unlicense, BlueOak-1.0.0, Python-2.0 ships ships ships
MPL-2.0, LGPL-2.1, LGPL-3.0 ships ships ships
GPL-2.0-or-later, GPL-3.0 stops ships ships
GPL-2.0-only stops stops stops
AGPL-3.0 stops warns ships
OFL-1.1, CC-BY-4.0, LPPL-1.3c warns warns warns
No licence, UNLICENSED, SEE LICENSE IN …, an unknown one stops stops stops

An SPDX OR takes the best of its options, and an AND the worst. A package published without its licence file takes the text the feature names for it in splent.licenseTexts.

To ship a package that would stop the release, add an exception to frontend/package.json.

"splent": {
  "licenseExceptions": [
    { "package": "some-editor", "reason": "replaced by Markdown in the next major", "expires": "2027-03-31" }
  ]
}

The release warns 30 days before the expiry, and after it the exception lets nothing through. npm audit stops the release on a critical vulnerability, or when it cannot reach the registry, and warns on a high one.


Fixing what stops a release

A breaking parameter change

  error: since v1.4.0 the parameters of splent_feature_notes changed in a way that breaks a product that sets them:
     NOTES_VIEW no longer accepts mosaic
     v1.5.0 keeps the major version of v1.4.0. Release it with --bump major, or keep the old declaration compatible.

The release compares the feature’s [tool.splent.config_schema] with the one at its latest vX.Y.Z tag. A change that breaks a product setting a parameter needs a new major version, or a new minor one below 1.0.0.

Breaks a product Does not
A key removed, or its type or items changed A key added
A choice dropped, or choices added to a key that had none A choice added
min raised, max lowered, a pattern added or changed A range widened
A key made required A default, label or help changed

A migration revision id too long

  error: these revision ids are longer than the 32 characters alembic_version.version_num holds, so the release did not start:
    notes0011_entries_handed_over_all.py: 'notes0011_entries_handed_over_all' is 33 characters

No database could record that migration after running it. Shorten the revision id and every down_revision that names it. feature:contract refuses it too.

An SQL or XSS sink

  error: splent_feature_notes has 1 SQL or XSS sink(s) the toolchain refuses, so the release did not start.
    routes.py:42 [sql-from-string] text() takes SQL built with an f-string
      Bind the values, text("... WHERE id = :id").bindparams(id=value), or use the ORM.

The release reads the feature’s code and templates as feature:security does, and --skip-checks does not skip it. Fix each line, or mark a line you have reviewed with # splent: allow <rule>.

A previous release that never finished

The release refuses to start while the previous version is on one channel and missing from the other. Finish it with release:resume. If it will never be finished, name the new version and pass --allow-unfinished.

The PyPI new-project limit

splent feature:release splent_feature_tags v0.1.0 --wait

A feature’s first release creates its PyPI project, and PyPI limits how many projects you create. SPLENT counts them in <WORKING_DIR>/.splent/pypi_creations.json and stops two short of each limit. A wait up to 65 minutes happens with a countdown. A longer one stops the release before anything changes and says when to come back, unless you pass --wait.

A new version of an existing project never waits. PyPI new-project limits has the limits and the variables that tune them.

A failure after the upload

  error: splent_io/splent_feature_notes v1.2.7 is already on PyPI, and a later step failed.
  The channels have DIVERGED for this version. Finish it, without bumping the version, with
    splent release:resume splent_feature_notes

A version on PyPI cannot be taken back. Fix the cause and run the command it prints, which finishes the same version.


Advanced

Feature reference

The feature is name or namespace/name, and the namespace defaults to splent_io. Each part holds letters, digits, _, - and ., so a path such as ../x is refused, and so is @version. release:resume applies the same rule.

What the release writes

  • The contract. [tool.splent.contract] is rewritten from the source, as feature:contract --write does. A feature with frontend/ also gets [tool.splent.contract.frontend], a projection of the bundle’s manifest. Never edit it by hand.
  • The commit. Every change of the working tree, except files that were untracked before the release started.
  • The push. To the branch origin calls its default. Pushes and fetches never ask for a password and give up after 10 minutes.

The cache snapshot

The snapshot is a read-only clone of the tag at .splent_cache/features/<namespace>/<feature>@<version>/. An existing clone of that tag is reused. Anything else at that path stops the run, so move it aside and run release:resume.

--skip-checks skips lint and tests, for emergencies only. The channel gate, the security check and the bundle still run.


Reference

Arguments and options

Argument / Option Description
FEATURE_REF splent_feature_notes or splent-io/splent_feature_notes
VERSION The version to release, such as v1.2.7. Without it, the wizard asks
--bump [patch\|minor\|major] Bump the current version without asking. Not with VERSION
--dry-run Rehearse the release and change nothing
--attach Pin the released version in the active product without asking
--skip-checks Skip lint and tests
--no-pypi Publish to the forge only
--no-forge Publish to PyPI only
--allow-unfinished Release although the previous version never finished
--wait Wait for the PyPI new-project limit however long it takes
--neutral Gate on the tests in a neutral app

Exit codes

Code When
0 Released, or a dry run that would go through
1 Refused or failed, or a dry run that would be refused
2 A usage error, such as VERSION together with --bump

Errors

Message What to do
No product selected Select one with product:select, or use --dry-run
editable feature not found at Put the feature at the workspace root, or feature:unlock it
is not a valid feature reference See Feature reference
is not a release version Use MAJOR.MINOR.PATCH
these requirements of the contract are not valid Write a PEP 440 specifier, see Version floors
these revision ids are longer than the 32 characters See A migration revision id too long
SQL or XSS sink(s) the toolchain refuses See An SQL or XSS sink
the parameters of <feature> changed See A breaking parameter change
That release never finished See A previous release that never finished
lint failed Run splent lint --fix
tests failed for <feature> Fix them, see Where the tests run
the frontend bundle of <feature> was not built See Shipping the frontend bundle
no version under [project] Add version = "0.1.0" under [project]
<tag> already exists and points at another commit Release a new version
already exists and is not a clone of <tag> Move the path aside, then release:resume
The channels have DIVERGED Run the release:resume command it prints

Requirements

  • The feature at the workspace root, an editable copy with an origin remote. Run feature:unlock on a pinned one first.
  • A selected product (SPLENT_APP), except for --dry-run. Its web container runs the tests and a webpack bundle, so start it with splent product:up --dev.
  • PyPI credentials, TWINE_USERNAME and TWINE_PASSWORD, or PYPI_USERNAME and PYPI_PASSWORD.
  • SPLENT_FORGE_TOKEN, to push, tag and create the release page. GITHUB_TOKEN is read when it is not set.

See also


Back to top

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