Releasing#
This project is consumed by copy-pasting a GitHub Action into repositories the maintainers do not control. A release therefore changes what CI accepts in every one of them, simultaneously, without anyone opting in.
Three version streams#
Stream |
Where |
What it means |
|---|---|---|
CodeMeta 3.1 |
|
The upstream vocabulary. Never modified here — this project profiles it, it does not fork it. |
LUMC profile |
|
Which fields are mandatory, and their constraints. |
rs-metadata |
|
The tool. |
They are separate on purpose. A consumer pinned to report format 1.x keeps working across tool releases; a repository can reason about “which ruleset judged me” independently of which tool build ran.
What counts as breaking#
For the profile, a change is major if metadata that was valid becomes invalid:
a new mandatory field;
a tightened constraint on an existing field;
a severity promoted from warning to error;
a vocabulary narrowed.
These may break every consuming repository at once.
Minor profile changes: new recommendations, relaxed constraints, severities demoted, better messages.
For the tool, a change is major if it removes a CLI flag, an Action input or output, or retires a diagnostic code. Diagnostic codes are a public interface: people filter on them and link to them.
Minor: new adapters, new diagnostic codes, new strategies, new flags with defaults that preserve behavior.
Fixing a false alarm — a finding that fires on correct metadata — is a patch release regardless of how much code it touches. It makes builds go from red to green, which never breaks anyone. Treat these as high priority; a validator that cries wolf gets deleted from CI, and then it catches nothing.
Release steps#
Refresh vendored data if it is stale. The
vendored-dataCI job reports this without blocking. As its own commit, so the provenance diff is visible in review:poetry run python scripts/vendor_reference_data.py
An SPDX list refresh can change results — a license may become deprecated — so it is a profile-affecting change, not housekeeping.
Bump versions. There is exactly one place for each. The tool’s version lives in
pyproject.toml;__version__is read from the installed package metadata, so nothing else needs touching. The profile’s version lives inprofile/lumc-codemeta.yaml, andscripts/build_schema.pycarries it into the generated schema, which is wherePROFILE_VERSIONis read from.Bump the profile only when the ruleset changed. It is versioned separately from the tool on purpose: a repository can reason about which ruleset judged it without tracking which build ran.
Update the schema
$idif the version moved. It embeds the version so a repository can pin a schema URL that does not shift under it. A test asserts the$idmatches the pathdocs/conf.pypublishes it at, so the two cannot drift:https://lumc-dcc.github.io/rs-metadata/schema/1.0.0/codemeta-lumc.schema.json
Publish the new path alongside the old one. Never repoint an existing version URL — editors and external tooling depend on it being stable.
Regenerate the docs.
poetry run python scripts/generate_docs.py
Run the full gate.
poetry run pytest --cov poetry run ruff check . && poetry run ruff format --check . poetry run mypy poetry run python scripts/generate_docs.py --check poetry run rs-metadata validate .
The last one because this repository carries its own metadata and validates it with itself, so a profile change that breaks real repositories usually breaks this one first.
Verify the wheel actually ships its data. The schemas, mappings and vendored vocabularies are read at runtime; a packaging mistake here is invisible in the source tree and fatal on install.
poetry build python -m venv /tmp/check && /tmp/check/bin/pip install dist/*.whl cd /tmp && /tmp/check/bin/rs-metadata validate <path-to>/examples/minimal
Dry-run the upload against TestPyPI. The Publish workflow can be run by hand, which builds, checks and uploads to TestPyPI without touching the real index. Do this at least once for a first release, and any time packaging changed:
Actions → Publish to PyPI → Run workflow → repository:
testpypi.Tag and release. Publishing to PyPI is triggered by publishing a GitHub release, not by pushing the tag, so the tag can be corrected right up until the release is published.
git tag -a v1.2.0 -m "rs-metadata 1.2.0" git push origin v1.2.0
Then draft and publish the release on GitHub. The workflow refuses to upload if the tag and
pyproject.tomldisagree, because that mistake cannot be undone — see below.Move the major tag. This is the one consumers actually reference.
git tag -f v1 v1.2.0 git push -f origin v1
v1may only ever move within major version 1. When version 2 exists,v1freezes at the last 1.x release — repositories pinned to@v1must never be moved to a new major without opting in.
PyPI, and what cannot be undone#
Uploads use Trusted Publishing:
PyPI verifies the workflow’s OIDC identity, so there is no API token stored in
this repository to leak or rotate. It is configured once, per project, at
https://pypi.org/manage/project/rs-metadata/settings/publishing/, naming
this repository, the workflow file publish.yml, and the environment pypi.
A version number is permanent. This is the part worth internalizing before the first release:
A file, once uploaded, cannot be replaced. Uploading a different file under the same name and version is refused.
Deleting a release removes it from the index, but does not free the version number. Nothing can ever be uploaded as
1.2.0again.So a broken release is corrected by publishing
1.2.1, never by fixing1.2.0in place.
What you can do is yank a release. A yanked version stays installable for anyone who pinned it exactly, so lockfiles keep resolving, but it is skipped by every ordinary install. That is the right response to a release that is broken but not dangerous.
Reach for deletion only for something that must not remain downloadable at all — a leaked credential, or content that cannot legally stay up. Yanking is otherwise kinder to everyone downstream.
The pypi environment gives one more safeguard: adding a required reviewer to
it in repository settings turns publishing into an approval step, so an
accidental release has to get past a person.
Announcing a breaking profile change#
Before tagging:
Say which repositories will start failing and why.
Give the exact fix, as a diff someone can apply.
Point at
fail-on: neveras the temporary escape hatch for whoever needs time.
Publishing to PyPI#
poetry publish --build
The Action installs from $GITHUB_ACTION_PATH, not from PyPI, so the two are
independent: an Action release does not require a PyPI release, and vice versa.
Keep them in step anyway — people who install the CLI locally expect it to
match what CI runs.
See also#
How the pieces fit |
|
Report format versioning in detail |
|
Development setup |