Releasing
For maintainers. A release is a signed tag on a commit of main that CI
passed on; two workflows do the rest, and a person approves each step that
cannot be undone.
What a release is
| Part | Where | Made by |
|---|---|---|
| Linux binaries (x86_64, aarch64), SBOMs, checksums, Sigstore bundles | the GitHub release | .github/workflows/release.yml, on the tag |
| The image, linux/amd64 and linux/arm64, signed | ghcr.io/wasmkeep/wasmkeep:<version> | release.yml |
| The Rust SDK | crates.io, wasmkeep-sdk | .github/workflows/publish.yml, when the release is published |
| The TypeScript SDK | npm, @wasmkeep/sdk | publish.yml |
| The Python SDK | PyPI, wasmkeep | publish.yml |
| The Go SDK | this repository, tag sdks/go/v<version> | a maintainer, with the release’s tag |
One version for all of it: the workspace version in Cargo.toml, which
every SDK manifest carries (scripts/check-versions.py).
Steps
The SDKs are published with every release from 0.2.0 on; 0.1.0 is on no registry.
- Prepare. In one pull request: the version in
Cargo.tomland every manifest,publish = trueincrates/wasmkeep-sdk/Cargo.toml(it stays so from then on), the release’s section inCHANGELOG.md(it becomes the release text), and install commands in the docs at the new version. Merge it, and wait for CI to pass on that commit ofmain. - Tag. Two signed tags on that commit:
v<version>, andsdks/go/v<version>for the Go module.release.ymlwaits for CI on the commit, builds and signs everything, pushes the image and opens a draft release. A tag on a commit CI did not pass on fails here: run CI again on the commit, then the workflow again. Never move or delete a tag: the module proxy and the registries keep what they saw. - Publish the draft. Read it, then publish it. That starts
publish.yml. - Approve the registries.
publish.ymlfirst checks the release (the tag is the version of every manifest, CI passed on its commit, theregistriesenvironment is protected), builds every package from that commit and asks each registry what it already has. Only then does each registry that lacks the version wait for a reviewer. Before approving, look at the run: it ispublish.yml, started by the release, on the tag. Approving publishes: a version on a registry stays there for good. - Check from outside. In an empty directory:
cargo add wasmkeep-sdk,npm install @wasmkeep/sdk,pip install wasmkeepandgo get github.com/wasmkeep/wasmkeep/sdks/go@v<version>each give the new version, and each registry’s page names this repository as the source.
If a run stops half-way
- A registry refused, or an approval was rejected or ran out: use “Re-run failed jobs” on the run, or run the workflow by hand on the tag (Run workflow, with the tag as the ref). It publishes the same files.
publish.ymlitself is wrong at the tag: it cannot be fixed for that tag, since a run always uses the tag’s own copy. Publish what is missing by hand from a checkout of the tag (cargo publish,npm publish,twine upload), and fix the workflow for the next release.
Either way a registry that has the version starts no job and asks for no approval, and PyPI is asked file by file, so a release that got its wheel but not its source archive gets the missing one. Nothing is retried in a loop: a registry that refused keeps its error in the job’s log.
Set up once
The registries trust this repository’s publish.yml by its file name and
its environment, with no stored token (“trusted publishing”). They do not
check the branch or the tag: a copy of the file on any branch has the same
identity. The registries environment is therefore the boundary, and its
settings matter.
- GitHub, the
registriesenvironment: required reviewers; deployment limited to the tagsv*; administrators not allowed to bypass it; “prevent self-review” once there is more than one maintainer.publish.ymlrefuses to run if the environment is missing, has no reviewers or accepts any ref. - GitHub, the tags: the ruleset that keeps
v*andsdks/go/v*from being moved or deleted. With several maintainers, also restrict who may create them. - The names. A registry can trust a workflow only for a package that
exists, except PyPI. Reserve the crate and the npm package once, by
hand, with a placeholder version
0.0.0that says so in its description:cargo publishof a crate namedwasmkeep-sdk, andnpm publishof@wasmkeep/sdk. Reserve the unscoped npm namewasmkeep-tsthe same way, as a placeholder that points to@wasmkeep/sdk: it is the name of the SDK’s build command, andnpxlooks a command up as a package when it is not installed. Every real version then goes out throughpublish.yml, with a provenance statement. - crates.io: in the crate’s settings, a trusted publisher: owner
WasmKeep, repositorywasmkeep, workflowpublish.yml, environmentregistries. - npm: in the package’s settings, a trusted publisher with the same
values, with
npm publishallowed (a new one allows only staged publishes otherwise). npm removes a trusted publisher that has not published within two days of being created: create it during step 1 of the first release, not earlier. The fields are case-sensitive, andrepository.urlinpackage.jsonmust be this repository. - PyPI: a pending publisher for the project
wasmkeepwith the same four values. The first run creates the project. A pending publisher does not reserve the name: make the first release soon after.
publish.yml keeps its file name and its jobs stay in it: a renamed or
reusable workflow would be refused by the registries.