Prerelease mode
In molt, a prerelease is cut with a stateless invocation flag -- molt version --pre rc -- not a persistent mode you enter and later have to remember to exit. There is no pre.json, no branch state, and nothing to clean up.
PEP 440 defines a closed set of prerelease spellings, so the arbitrary tag names a persistent mode exists to carry have no Python form. Making the phase a per-run flag is what fits the standard.
The flag
You pass --pre on the one run where you want a prerelease, and the next plain run is back to normal:
The only state is the version number itself. Because a PEP 440 version is 1.2.1rc0, the prerelease phase and its counter live in the number, on disk, where you can read them.
PEP 440 identifiers only
PEP 440 defines a fixed set of prerelease spellings, and --pre accepts exactly those phases:
A named channel such as 1.2.0-next.0 is not expressible in PEP 440 and is rejected. There is no --pre next, because PyPI would reject or renormalize it. If you are used to named prerelease channels, map yours onto one of a / b / rc / dev.
How the counter advances
The prerelease number is derived from the package's current version, so successive prerelease runs walk the counter forward on their own -- no separate bookkeeping.
Starting from acme-core 1.2.0 with a pending patch changeset:
Each run computes the target stable version from the pending changesets (here 1.2.1, a patch), then attaches the prerelease phase. When the current version is already a prerelease of that target, molt reads its number and increments it: rc0 -> rc1 -> rc2. The rule is simply "if the current version is already a prerelease, advance the counter; otherwise start at 0."
Your changesets stay in place while you iterate -- they describe the stable release you are heading toward, and each prerelease is a preview of it.
Exiting: a plain molt version lands on stable
There is no exit command because there is no mode to leave. When you are ready for the real release, run molt version with no --pre:
This works because of PEP 440's prerelease-aware bump arithmetic: a bump applied to a version that is already a prerelease of the target simply strips the prerelease. patch on 1.2.1rc2 is 1.2.1, not 1.2.2 -- the increment was already "spent" when the prerelease was first cut. The pending changesets are consumed on this run, exactly as in a normal release, and their summaries fold into the changelog.
So the full arc reads naturally, with the version number carrying all the state:
Prereleases and dependent propagation
Prerelease versions interact with the release plan through the same PEP 440 range rules as everything else. Two consequences worth knowing:
- A dependent whose constraint does not opt into prereleases is not satisfied by a prerelease of its dependency. Python resolvers skip prereleases by default, so molt does too, and a prerelease cannot reach an install that did not ask for one.
- A dependent that has opted into prereleases (its constraint already names one) is not re-released each time its dependency steps
rc0 -> rc1, because that constraint genuinely still accepts the new prerelease. If you want lockstep movement instead, that isupdate_internal_dependents = "always", an explicit choice.
Where to go next
molt version-- the command and its--preflag.molt pre-- prerelease-related helpers.- Versioning and PEP 440 -- the bump arithmetic that makes exiting a prerelease land on the stable version.
- Snapshot releases -- the other short-lived release shape, for testing an exact commit.
- Glossary -- prerelease, snapshot, and the rest.