Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cutting a Release

A release publishes seven artifacts to three registries, through six workflows. All of them are triggered automatically when a GitHub Release is published, so the bulk of cutting a release is: get the version numbers right, land the bump, then draft the release.

What gets published

ArtifactRegistryWorkflowSourceVersion comes from
uniffi-bindgen-react-nativenpmnpm.ymlrepo rootpackage.json
@ubjs/corenpmnpm-core.ymltypescripttypescript/package.json
@ubjs/node + @ubjs/node-<platform>npmnapi-publish.ymlruntimes/napiruntimes/napi/package.json
@ubjs/wasmnpmnpm-wasm.ymlruntimes/wasmruntimes/wasm/package.json
uniffi-runtime-javascriptcrates.iocrates-io.yamlcrates/uniffi-runtime-javascriptcrates/uniffi-runtime-javascript/Cargo.toml
uniffi-runtime-wasmcrates.iocrates-io.yamlruntimes/wasm/helper-crateruntimes/wasm/helper-crate/Cargo.toml
uniffi-bindgen-react-native (Pod)CocoaPodscocoapods.ymluniffi-bindgen-react-native.podspecpackage.json (the podspec reads package['version'])

crates-io.yaml publishes both crates from one matrixed job, with fail-fast: false — neither crate depends on the other, so one failure does not cancel the other’s publish.

@ubjs/node is the N-API runtime. Its workflow first builds a native binary for every supported target (macOS x64/arm64, Linux gnu/musl on x64/arm64, Windows x64/arm64), publishes each as a platform package (@ubjs/node-darwin-arm64, @ubjs/node-linux-x64-gnu, …), then publishes the @ubjs/node root package whose optionalDependencies point at them. If the build matrix fails for any target, the publish job does not run.

@ubjs/wasm is the wasm2 player runtime, and uniffi-runtime-wasm is the helper crate a consuming cdylib links. @ubjs/wasm declares @ubjs/core as a peerDependency, so that range has to move with the version too.

Steps

  1. Increment the version number, keeping all seven files in sync:
  2. Update the lockfiles to follow, rather than editing them by hand:
    • cargo metadata --offline > /dev/null refreshes Cargo.lock
    • npm install --package-lock-only in each of typescript, runtimes/napi and runtimes/wasm
  3. Update the version references outside the manifests:
    • docs/src/reference/config-yaml.md — the runtimeVersion default
    • runtimes/wasm/helper-crate/README.md — the Cargo.toml snippet
    • crates/ubrn_cli/fixtures/defaults/package.json — the @ubjs/core dependency
    • Leave statements dating a feature to the release that introduced it (e.g. “As of 0.31.0-3” in the Node.js reference) alone — those are history.
  4. Update the CHANGELOG. If the CHANGELOG is up-to-date, then this should be minimal.
    • Add a new version title at the top
    • Update the Full Changelog link to go from new release to main
    • Move the bottom of the “upcoming release” section to the top
    • Update the Full Changelog link to go from previous release to new release
  5. Push as a PR as usual, with subject: Release ${VERSION_NUMBER}.
  6. (Optional but recommended) Run a dry-run of the publish workflows — see Testing a release before tagging.
  7. Once the PR has landed, draft a new release.
  8. Create a new tag (in the choose-a-tag dialog) with the version number (without a v).
  9. Use the version number again, but with a v prepended, for the release title: v${VERSION_NUMBER}.
  10. Publish the release.
  11. Wait for the six publish workflows to go green:
  12. Verify the release landed.
  13. Tell your friends, make a song and dance, you’ve done a new release.

Testing a release before tagging

Five of the six publish workflows can be run manually from the Actions tab (workflow_dispatch) with a dry-run input that defaults to true. Use this to validate packaging — cargo publish --dry-run, npm publish --dry-run — without pushing anything to a registry:

Warning

cocoapods.yml has no dry-run input. Triggering it manually runs a real pod trunk push. To only validate the podspec, run pod spec lint uniffi-bindgen-react-native.podspec locally instead.

A real release fires every workflow on the release: published event; the dry-run path is reachable only through manual workflow_dispatch.

After publishing

Confirm each artifact actually went out:

If a workflow fails part-way, re-run just that workflow from the Actions tab (workflow_dispatch, dry-run false) once the underlying problem is fixed — you do not need to cut a new tag. npm and crates.io reject re-publishing a version that already exists, so a re-run after a partial @ubjs/node publish will skip the platform packages that already landed and publish the rest.

Version numbers

uniffi-rs uses a semver versioning scheme (e.g. 0.31.0).

uniffi-bindgen-react-native tracks the uniffi-rs version and appends a -N variant number. The variant number increases monotonically across releases and is not reset when the uniffi-rs version changes.

For example, if the last release was 0.30.0-1 and uniffi-rs is bumped to 0.31.0, the next release is 0.31.0-2 (not 0.31.0-0).

Compatibility with other packages

Other versioning we should take care to note:

  • React Native
  • create-react-native-library

Compatibility matrices are built by the nightly matrix (iOS + Android, the full date-derived window) and gated per-PR by the PR gate (latest RN only).

The matrix is date-derived: .github/scripts/compat-matrix.mjs picks, for each React Native release published in the last 365 days, the create-react-native-library version and CI runner image that were current at that RN’s publish date. Runner images come from .github/compat-runner-schedule.jsonthe one place to maintain. When GitHub ships a new macOS/ubuntu generation, append a row ({ "since": "<GA date>", "ios": "<label>", "android": "<label>" }); when an old label is retired, bump the oldest row to the oldest still-hosted label.