Versions and changelog¶
How rab is versioned¶
rab (release-kit until 1.7.0) is tagged vX.Y.Z:
- a patch (
v2.0.0→v2.0.1) fixes a bug; - a minor (
v2.0.x→v2.1.0) adds something that existingrab.ymlfiles andrelease.ymlworkflows keep working with; - a major (
v3.0.0) needs repositories to change their configuration or workflow. Each repository moves when it installs the new version.
release.py --version prints the version of the script (rab 2.0.0). An
installed copy records where it came from in .github/rab/manifest.json:
{
"source": "buchochelliq-labs/release-asset-bundle",
"version": "v2.0.0",
"commit": "874e21cbe8bdf07c9f1a613e116fbd7542950b41",
"files": {
".github/rab/scripts/release.py": "<sha256>",
"...": "..."
}
}
version is git describe --tags --always --dirty of the checkout it was
installed from: v2.0.0 from a tag, v2.0.0-1-g874e21c from a commit after
it, and one from a checkout with local edits ends in -dirty. files maps
every installed file to its SHA-256; release.py vendored compares against
it, and prints the version (rab v2.0.0: the installed copy is unmodified.).
To move a repository to a new version: pull the kit, check out the tag you
want, run install, and open a PR. From release-kit 1.x, see
Migrating from release-kit 1.x.
Changelog¶
v2.0.1¶
Fixed: publish-pypi uploaded nothing. It ran
pypa/gh-action-pypi-publish, which runs a Docker image it names from the
calling action's repository and ref. Nested in rab's composite action those
are not set, so it fell back to the release's own repository and tag:
rs-rich-cli's first rab release ran docker run …
ghcr.io/buchochelliq-labs/rs-rich-cli:release-0.0.25 and was denied. pypa
documents that the action does not work from a composite action (called
directly from a job, as rs-rich-cli's old workflow did, it works).
publish-pypi now does the upload itself, with no Docker action: it
installs twine 7.0.0 and pypi-attestations 0.0.30 into a virtualenv from its
new requirements.txt (pip install --require-hashes, every package pinned
with its hashes), and runs the new release.py pypi-upload. That exchanges
the job's OIDC token for a short-lived PyPI token as
PyPI documents
(/_/oidc/audience, GitHub's OIDC token, /_/oidc/mint-token), masks it,
signs PEP 740 attestations with pypi-attestations sign, prints each file's
digests, and runs twine upload --attestations. A refused exchange shows
PyPI's reasons and the token's claims. Attestations are kept, as the pypa
action made them; the new attestations: "false" input turns them off. The
trusted publisher on PyPI does not change. See PyPI.
A test now refuses a Docker-based action (docker://,
pypa/gh-action-pypi-publish) in any rab composite, and any third-party
action there that has not been read at its pinned commit. The others were
read: actions/setup-python, actions/setup-node and PyO3/maturin-action
are Node actions (maturin-action's manylinux images are named from its
inputs), dtolnay/rust-toolchain is a composite of shell steps, and
actions/attest-build-provenance is a composite around the Node action
actions/attest; none reads github.action_repository or action_ref.
To pick it up, install v2.0.1 (release.yml does not change: install
adds .github/rab/publish-pypi/requirements.txt). A release whose
publish-pypi job failed this way published nothing on PyPI: after
installing, run release.py tag again and the new run publishes only PyPI.
v2.0.0¶
release-kit is now rab, the Release Asset Bundle (BAB, after its
mascot), at
buchochelliq-labs/release-asset-bundle.
This major version renames what repositories keep on disk, and gathers
everything since 1.7.0: Open VSX checked like the Marketplace, a
machine-readable release record, a retry policy per channel with tag
finishing Homebrew and Chocolatey, crates.io pre-checks in check, release
builds from a record tag with no caches, and a rejected Chocolatey version as
a terminal state. Repositories must copy template changes into their
release.yml: see Migrating from release-kit 1.x.
The rename. The configuration is rab.yml (was rab.yml), the
installed copy is .github/rab/ (was .github/release-kit/), the build
workflow is .github/workflows/rab-build.yml (was release-kit-build.yml),
and RAB_OFFLINE replaces RELEASE_KIT_OFFLINE. The script keeps its name:
python3 .github/rab/scripts/release.py. install writes the new names and
migrates a 1.x layout: it moves rab.yml to rab.yml (git mv when
tracked), removes the old copy and build workflow, rewrites the old paths in
the repository's workflows, and prints each change. Until rab 3.0.0 the old
names still work with a warning: every command reads rab.yml, else
rab.yml (and refuses when both exist), vendored accepts a copy at
.github/release-kit/, and RELEASE_KIT_OFFLINE is read when RAB_OFFLINE
is unset. The actions are named rab: …, the build workflow rab build, and
release.py --version prints the version. The release record's schema keeps
schema_version: 1; its $id moves to the new site.
A rejected Chocolatey version is terminal. The community repository keeps
a rejected version and refuses every push of it, while a version still in
moderation can be pushed again. So rejected is now neither done nor due:
the gate does not select it, tag does not retry or wait for it and notes
chocolatey of <package> is rejected: publish a new version; not retried,
and status shows rejected: publish a new version. Fix the package and
release a new version. See the retry policy.
Open VSX is checked like the Marketplace. A vscode package takes
open-vsx: true: Open VSX is then part of its publication. The version is
due until both registries have it, preflight skips the registry that
already has it (so a re-run uploads only the missing one) and refuses an
Open VSX upload without a token, and verify waits for
open-vsx.org/api/<publisher>/<name>/<version> too. Add open-vsx: true to
a package published to Open VSX: without it, the upload still happens when
OVSX_PAT is set, with a warning, but is not checked.
A machine-readable release record. Every release run uploads the artifact
release-record, holding release-record.json: the release, its tag and
commit, the run, each job's result, and every package's outcome
(published, already-published, failed, skipped or unknown), its
registry URL and the SHA-256 of the files built for it, with an overall
result (complete, partial, nothing-published). A vscode package
with open-vsx: true counts as published only on both registries, and also
gets open_vsx_url. A github package's due Homebrew and Chocolatey jobs
count toward result; one selected only to finish them is
already-published with those jobs' results in channels. A coordinator
releasing a project across repositories downloads it with gh run download
<run> -n release-record. A new last job in the template, record, writes it
with the new release.py record command, even when publishing fails, with no
new permissions. The record is versioned (schema_version: 1), and its JSON
Schema is published as
release-record.schema.json. Installed
repositories must add the record job to their release.yml: install
does not change an existing one. Copy it from templates/release.yml. See
The release record.
Retry policy per channel; tag finishes Homebrew and Chocolatey. Each
channel has a retry policy defined by rab: once for crates.io, PyPI,
npm and the VS Code Marketplace, finish for GitHub Releases, idempotent
for a Homebrew tap and until-accepted for Chocolatey. The derived channels
now have a state, read live: Homebrew is done when the tap's default branch
has the formula at the version or a pull request from <name>-<version> is
open; Chocolatey when the community API lists the version or it is in
moderation. When a github package is published but one of its channels is
not done, tag goes back to its commit (finish <tag>) and pushes the
release tag again, and the gate selects the package for that channel only:
each selection entry gains channels, the gate's github flag is false for
it, and homebrew/chocolatey are true only when that channel is due. tag
also waits briefly for a release's channels and names any not done.
status prints each channel's policy and state. Update an installed
release.yml: give publish-homebrew and publish-chocolatey needs:
[gate, ci, build, publish-github] and let them run when publish-github was
skipped because nothing was due on GitHub (copy the two if: conditions from
templates/release.yml); without that, a run that only finishes a channel
publishes nothing. A choco push refused because the community repository
already has the version ("already exists and cannot be modified") counts as
pushed, unless the version was rejected. See the retry policy.
Catch a crate crates.io would refuse at check. For every crates.io
package, release.py check reads the manifests with cargo metadata and
refuses publish = false (or other registries only), git or path
dependencies without a version, dependencies from other registries, a
dependency that publishing would take from crates.io when crates.io has no
matching version (unless this release publishes it first), a name someone else
owns (from the public owners API), a failing cargo package --list, and
include_str!-style embeds the package does not carry. Each message names the
fix. It warns about library crates in the workspace that rab.yml
publishes nowhere. check --offline (or RAB_OFFLINE=1) skips the
questions to crates.io; check --publish-dry-run runs
cargo publish --dry-run over the crates, for CI. preflight --registry
crates.io runs the same checks on the selected crates. New page:
Publishing to crates.io. In a
Rust repository, check now runs cargo and asks crates.io (about one
request a second); without cargo it warns and skips these checks.
Release builds check out a record tag and write no caches. The build
workflow's plan and build jobs check out a record tag the gate checked
(refs/tags/<name>-v<version>) instead of the bare sha, and stop before
anything from the checkout runs unless the tag still names sha, so a tag
moved during the run builds nothing; the record job checks its release
tag against sha the same way. Every actions/setup-node in the kit
and the template sets package-manager-cache: false, which setup-node
otherwise turns on by itself when package.json names npm as its
packageManager, and PyO3/maturin-action sets sccache: "false": a run
dispatched from main shares the default branch's cache, and a release run
adds nothing to it. GitHub CodeQL's
actions/cache-poisoning/poisonable-step alerts on an installed copy (a
checkout of inputs.sha followed by the plan and build actions) go
away. Install the new version; release.yml needs no change, except to add
package-manager-cache: false to the setup-node steps of an installed
publish-npm or publish-vscode job. See
No caches.
v1.2.0 to v1.7.0¶
These shipped across v1.2.0 to v1.7.0 and are recorded together, with
release-kit's names (release-kit.yml, .github/release-kit/).
Promote reviewed CI artifacts. A new artifact kind, ci-artifact,
publishes the files of a pinned GitHub Actions artifact unchanged instead of
building them again: for releases that must ship exactly the bytes CI built
and tested. The archive's SHA-256 is pinned, an expired artifact is refused,
and the tagged commit must be the commit that built it or a descendant that
changes only release-kit.yml, the pin itself (rule 4). Promoted files must
carry exactly the package's version. The build workflow and the template's
build job gain actions: read; add it to the build job of an installed
release.yml to use this kind.
GitHub releases are drafts until complete. publish-github creates the
release as a draft, uploads and checks every file, then publishes it. A
draft does not count as published, so a run that stopped halfway is
finished by the next one instead of leaving an incomplete public release.
Commands run in bash on Windows. prepare, test, command and
notes run as bash -c <command>. They used shell=True with bash as the
executable, which on Windows still builds <shell> /c "...", so they
failed on Windows runners.
GitHub release options. A github package takes notes (a command
whose output is the release's notes, for example a CHANGELOG section;
otherwise GitHub generates them), checksums (one SHA256SUMS-style file
over every asset, uploaded and checked with them) and attest: true (build
provenance attestations for every asset). The template's publish-github
job gains id-token: write and attestations: write; add them to an
installed release.yml to use attest.
Homebrew and Chocolatey. A github package takes homebrew (a
formula rendered from a template and the release's checksums, attached to the
release and proposed to a tap as a pull request) and chocolatey (a package
rendered from a directory of templates, installed and tested on Windows, then
pushed). The template gains the publish-homebrew and publish-chocolatey
jobs, the gate the homebrew and chocolatey outputs, and release.py the
render and homebrew-tap commands.
prepare. Every artifact kind takes a prepare command, run in the
build job before anything is built (for maturin, before maturin-action):
generated files the build ships, or tools it needs.
musl targets. x86_64-unknown-linux-musl and
aarch64-unknown-linux-musl have default runners, and a rust-binary musl
build installs musl-tools so C dependencies link statically.
maturin from a workspace. A pyproject.toml with dynamic =
["version"] takes its version from the crate maturin builds (in PEP 440
form). When [tool.maturin] manifest-path names the crate, maturin runs in
the pyproject.toml's directory and finds it. Wheels build with --locked.
New crates and crates.io's rate limit. New crates publish one at a time;
a 429 for too many new crates waits until the time crates.io names, then
tries again.
crates.io first versions. preflight --registry crates.io now says which
selected crates do not exist yet, as the outputs new_crates, new_crate and
existing_crate. The new release.py publish-crates dry-runs every selected
crate, then publishes each consecutive run of crates that share a credential,
in order: the Trusted Publishing token for crates already on crates.io, and
the crates-io environment's CARGO_REGISTRY_TOKEN secret for a crate's
first version. It refuses before uploading anything if a needed token is
missing. The template requests a Trusted Publishing token only when an
existing crate is selected, and passes the secret only when a new one is.
Tag ownership. A package tag already on the remote at another commit must
be that package's own record, whose message starts
<name> <version> (<registry>). Otherwise tag and the gate refuse, and say
how to fix it: a tag format of its own (for example tag: "python-v{version}"
for a PyPI package named like a crate), or a version bump. A tag on a commit
with no release-kit.yml, from before the repository adopted release-kit, is
refused with an explanation instead of a git error.
Pinned actions. Every third-party action in the kit and the template is
pinned to a commit SHA with its version in a comment. A test refuses an
unpinned uses:, and Dependabot keeps the pins current.
Consumer checks. The new release.py consume uses each published package
as a stranger would, from the registry with no credentials: cargo install
or a consumer crate for crates.io, pip install into a new virtualenv for
PyPI, and npm view for npm. Each attempt is retried while a registry's index
catches up. The crates.io and PyPI publish actions run it after verify.
Verify-only runs. The release workflow's manual dispatch has a
verify_only input. The gate (gate --verify-only) then selects the
release's published packages, the build is skipped, and each publish action
(with its new verify-only input) only verifies.
Artifact tests. Every artifact kind takes a test: a command, or
{run, targets} to run it on some targets only. It runs in the build job on
the files just built, with DIST, FILES, VERSION, PACKAGE and TARGET
set, before anything can publish them.
Waiting. tag --timeout defaults to 10800 seconds (three hours). When it
stops while the run is still live, it names the run; running tag again keeps
waiting and changes nothing while the run is live.
Every publish job waits for the build. publish-crates and publish-npm
now need build too, so a release whose artifacts fail to build or fail their
test publishes nothing anywhere. publish-npm runs consume after a normal
upload, and once in a verify-only run. check refuses test.targets on kinds
that are not built per target, where it could never match.
Documentation. This site, built with Zensical from mkdocs.yml and
published to GitHub Pages by .github/workflows/docs.yml. The README is now a
short summary that links here, and no longer claims the repository is
private.
v1.1.0¶
Installed copies. Repositories use the kit as a copy under
.github/release-kit/, written by release.py install with a
manifest.json of checksums, plus .github/workflows/release-kit-build.yml
and a starter release.yml. release.py vendored fails CI when the copy is
edited.
v1.0.0¶
The release standard. release-kit.yml declares each package, its
registry, manifest, version and the artifacts it is built into; release.py
checks, syncs, tags, gates, plans, builds, preflights and verifies; composite
actions and a build workflow publish to GitHub Releases, crates.io, PyPI, npm
and the VS Code Marketplace.