Skip to content

ci: Automate crates.io + GitHub publishing on merge (port Leo's release-plz flow) #3291

Description

@mohammadfawaz

Goal

Port Leo's automated publishing flow to snarkVM so that merging a version-bump PR into staging automatically publishes the workspace crates to crates.io and cuts a GitHub release — no manual cargo publish, no long-lived crates.io tokens.

Key difference from Leo: snarkVM is lockstep-versioned (all 64 crates carry the same literal version, currently 4.7.3). We want one git tag (v4.7.4) and one GitHub release per workspace version — not Leo's per-crate tags (leo-lsp-v4.0.2). release-plz is package-centric, so this needs explicit config (see step 1).

Reference (Leo)

snarkVM specifics to account for

  • 64 workspace crates, lockstep-versioned — every crate has a literal version = "4.7.3" (no version.workspace, no [workspace.package] block). Published as snarkvm-*; the root crate is snarkvm.
  • Default branch is staging (Leo uses master). All workflow triggers/refs below must target staging.
  • Library workspace — no binary artifact releases — so Leo's release-crate.yml binary-dispatch half is out of scope. We only need crates.io publishing + a single GitHub release/tag.
  • Owner org on GitHub and crates.io: ProvableHQ.

Steps

1. Add release-plz.toml — configure single tag + lockstep

release-plz defaults to one tag/release per package in a multi-crate workspace. To get a single unified tag/release:

  • Disable tags + releases workspace-wide, then re-enable them on one umbrella crate (the root snarkvm):
    [workspace]
    git_tag_enable = false
    git_release_enable = false
    
    [[package]]
    name = "snarkvm"
    git_tag_enable = true
    git_tag_name = "v{{ version }}"
    git_release_enable = true
    git_release_name = "v{{ version }}"
  • Keep all crates in lockstep with version_group (assign every workspace member to the same group so they always bump to the same version together).
  • release-plz still publishes all crates to crates.io regardless of tag config; tags/releases are only created for the umbrella crate.
  • Decide changelog strategy (single workspace changelog vs. none) — release-plz generates per-crate changelogs by default; may want to disable or consolidate.

2. Add .github/workflows/publish-crates.yml

Port from Leo's publish-crates.yml, adjusted for snarkVM:

  • Trigger on push to staging (paths: Cargo.toml, **/Cargo.toml, release-plz.toml, the workflow file), plus workflow_dispatch with a dry_run input.
  • concurrency group to prevent overlapping publishes.
  • dry-run job: release-plz/action@v0.5, command: release, dry_run: true.
  • publish job permissions: contents: write (tag + release) and id-token: write (OIDC for Trusted Publishing).
  • Guard manual dispatches so they only run from staging.
  • Drop Leo's "Dispatch Binary Release Workflows" step (no binary crates here).

3. Configure crates.io Trusted Publishing (required, per crate, one-time)

Trusted Publishing uses GitHub OIDC instead of a crates.io API token. It is inherently per-crate — there's no workspace-wide setting — so it must be configured once for every already-published snarkvm-* crate. This is unavoidable but is a one-time, scriptable setup (it does not mean per-crate tags at release time).

For each crate, on crates.io → crate SettingsTrusted PublishingAdd:

  • Owner: ProvableHQ
  • Repository: snarkVM
  • Workflow filename: publish-crates.yml
  • Environment: (leave blank unless we add a GitHub Environment to the publish job)

64 crates — script this against the crates.io API and track which are done. Requires crates.io owner permissions on every snarkvm-* crate.

4. Bootstrap any brand-new crates

crates.io Trusted Publishing can only be configured on a crate name that already exists. Any new snarkvm-* crate needs one initial manual cargo publish (with a token) to reserve the name, after which Trusted Publishing can be set up and the workflow takes over. Document this.

5. Single GitHub release / tag

  • The umbrella-crate config in step 1 yields one tag v{version} and one GitHub release per workspace version, with generated notes.
  • Skip Leo's compatibility-metadata job unless we specifically want machine-readable release metadata.

6. Documentation

  • Add/extend a RELEASING.md: the normal path (bump workspace version PR → merge to staging → auto-publish all crates + single release), how to dry-run (workflow_dispatch), how to backfill, the Trusted Publisher setup, and the new-crate bootstrap.

7. Validation

  • actionlint on the new workflow.
  • workflow_dispatch with dry_run: true → confirm release-plz reports the expected unpublished crates and a single planned tag, without publishing.

Open questions

  • Confirm the umbrella-crate (snarkvm) single-tag pattern works end-to-end with release-plz, and that version_group keeps all 64 crates locked to one version.
  • Changelog: single consolidated changelog, or disable release-plz changelogs entirely?
  • Do we want a GitHub Environment gate on the publish job for a manual approval before crates.io publishing?
  • Migrate the manual version-bump step to release-plz's release-PR, or keep bumping versions by hand and let the workflow only publish?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions