From 622abcb8bf30f0b048ac61946ffe3ab7e71fd7bf Mon Sep 17 00:00:00 2001 From: Nils Homer Date: Tue, 16 Jun 2026 07:59:17 -0700 Subject: [PATCH] ci: align release-plz with the standard fg-labs publish workflow Replace the upstream-style `release-plz.yml` (which ran release-plz's `release` command with the default `GITHUB_TOKEN`) with the canonical `publish.yml` used across the other fg-labs/fulcrumgenomics Rust crates. The new workflow splits work into two jobs: - `release-pr`: release-plz opens/updates the version-bump + changelog PR. - `publish`: on merge, publishes to crates.io via Trusted Publishing (OIDC) and tags + creates the GitHub release idempotently. Both jobs authenticate with the `FG_LABS_BOT` GitHub App token instead of the default `GITHUB_TOKEN`, so the release PR and tags can trigger other workflows. `release-plz.toml` now delegates publishing, tagging, and GitHub releases to the `publish` job (`publish`/`git_tag_enable`/`git_release_enable` are disabled), enables `cargo update` and a `release` PR label, and keeps `semver_check` on (redskull is already past v0.1.0). The existing "Keep a Changelog" format is retained. --- .github/workflows/publish.yml | 123 ++++++++++++++++++++++++++++++ .github/workflows/release-plz.yml | 55 ------------- README.md | 16 ++-- release-plz.toml | 29 ++++--- 4 files changed, 145 insertions(+), 78 deletions(-) create mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/release-plz.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..897303d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,123 @@ +name: Manage Release PRs and Publish Crates + +on: + push: + branches: + - main + +concurrency: + group: release-plz-${{ github.ref }} + cancel-in-progress: false + +jobs: + release-pr: + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'fg-labs' }} + permissions: + # release-plz authenticates with the app token (GITHUB_TOKEN env below), + # so the default token only needs read access plus PR write to open the + # release PR. + contents: read + pull-requests: write + steps: + - name: Generate app token + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + app-id: ${{ secrets.FG_LABS_BOT_APP_ID }} + private-key: ${{ secrets.FG_LABS_BOT_PRIVATE_KEY }} + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + # release-plz uses the app token via GITHUB_TOKEN, so the checkout + # credential does not need to persist in git config. + persist-credentials: false + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + - name: Create or update release PR + uses: release-plz/action@064f4d1e36c843611ddf013be726beaa4ad804db # v0.5.129 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + + publish: + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'fg-labs' }} + permissions: + id-token: write + contents: write + steps: + - name: Generate app token + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + app-id: ${{ secrets.FG_LABS_BOT_APP_ID }} + private-key: ${{ secrets.FG_LABS_BOT_PRIVATE_KEY }} + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + - name: Authenticate to crates.io via Trusted Publishing + id: crates-auth + uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4 + - name: Publish crate + id: publish + env: + CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }} + run: | + set -euo pipefail + + VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r \ + '.packages[] | select(.name == "redskull") | .version') + + PUBLISHED_VERSION=$(curl -sS \ + -H "User-Agent: redskull-ci (https://github.com/fg-labs/redskull)" \ + "https://crates.io/api/v1/crates/redskull" | jq -r \ + '.crate.max_version // "0.0.0"') + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + if [ "$VERSION" = "$PUBLISHED_VERSION" ]; then + echo "redskull v$VERSION already on crates.io -- nothing to publish" + echo "published=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "Publishing redskull v$VERSION..." + cargo publish -p redskull + echo "Published redskull v$VERSION" + echo "published=true" >> "$GITHUB_OUTPUT" + + - name: Create GitHub release + # Gate on `version` rather than `published` so a re-run can still create + # the GitHub release idempotently if a prior run published the crate but + # failed before tagging/releasing. + if: steps.publish.outputs.version != '' + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + VERSION: ${{ steps.publish.outputs.version }} + run: | + set -euo pipefail + TAG="v${VERSION}" + + # Idempotent: skip if tag/release already exist + if ! git rev-parse "$TAG" >/dev/null 2>&1; then + git tag "$TAG" + fi + if ! git ls-remote --tags origin "$TAG" | grep -q "$TAG"; then + git push origin "$TAG" + fi + if ! gh release view "$TAG" >/dev/null 2>&1; then + gh release create "$TAG" \ + --title "v${VERSION}" \ + --generate-notes \ + --latest + else + echo "Release $TAG already exists -- skipping" + fi diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml deleted file mode 100644 index 5f3bbc3..0000000 --- a/.github/workflows/release-plz.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Release-plz - -on: - push: - branches: - - main - -jobs: - # Release unpublished packages. - release-plz-release: - name: Release-plz release - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'fg-labs' }} - permissions: - contents: write - # Required for crates.io Trusted Publishing (OIDC). - id-token: write - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - - name: Run release-plz - uses: release-plz/action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128 - with: - command: release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Create a PR with the new versions and changelog, preparing the next release. - release-plz-pr: - name: Release-plz PR - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'fg-labs' }} - permissions: - contents: write - pull-requests: write - concurrency: - group: release-plz-${{ github.ref }} - cancel-in-progress: false - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - - name: Run release-plz - uses: release-plz/action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128 - with: - command: release-pr - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index b0abb7b..919c2b9 100644 --- a/README.md +++ b/README.md @@ -186,10 +186,12 @@ Releases are automated with [`release-plz`][release-plz-link], driven by [Conven ### How it works -On every push to `main`, the `release-plz` workflow (`.github/workflows/release-plz.yml`) runs and does two things: +On every push to `main`, the `publish` workflow (`.github/workflows/publish.yml`) runs two jobs: -1. **Opens or updates a release PR** that bumps the version in `Cargo.toml` and `Cargo.lock` and updates `CHANGELOG.md` based on commit messages since the last tag. -2. **Publishes to crates.io and creates a GitHub release** when a release PR is merged, tagging the commit and pushing the crate to crates.io. +1. **`release-pr`** — `release-plz` opens or updates a release PR that bumps the version in `Cargo.toml` and `Cargo.lock` and updates `CHANGELOG.md` based on commit messages since the last tag. +2. **`publish`** — when a release PR is merged (so the version in `Cargo.toml` no longer matches the latest version on crates.io), the crate is published to crates.io via Trusted Publishing and a GitHub release is tagged and created. + +Both jobs authenticate using a short-lived GitHub App token (`FG_LABS_BOT_APP_ID` / `FG_LABS_BOT_PRIVATE_KEY`) rather than the default `GITHUB_TOKEN`, so that the release PR and tags it creates can trigger other workflows. ### Conventional Commits @@ -206,15 +208,15 @@ This tool follows [Semantic Versioning](https://semver.org/). 1. Merge your changes to `main` using Conventional Commit messages. 2. Review the release PR opened by `release-plz` (version bump + changelog). -3. Merge the release PR. `release-plz` publishes to crates.io and creates the GitHub release automatically. +3. Merge the release PR. The `publish` job then publishes to crates.io and creates the GitHub release automatically. ### Publishing credentials: Trusted Publishing This repository uses [crates.io Trusted Publishing][trusted-publishing-link] (OIDC) instead of a long-lived -`CARGO_REGISTRY_TOKEN` secret. The `release-plz` workflow requests a short-lived token from crates.io via +`CARGO_REGISTRY_TOKEN` secret. The `publish` job requests a short-lived token from crates.io via GitHub's OIDC provider at publish time — nothing needs to be stored in the repository. -The workflow already sets `id-token: write` on the release job, which is required for OIDC. +The workflow already sets `id-token: write` on the `publish` job, which is required for OIDC. #### First release @@ -236,7 +238,7 @@ After `0.1.0` is on crates.io, configure a Trusted Publisher for this repository * Repository owner: `fg-labs` * Repository name: `redskull` -* Workflow filename: `release-plz.yml` +* Workflow filename: `publish.yml` * Environment: *(leave blank)* Once configured, merging a release PR on `main` will publish automatically. diff --git a/release-plz.toml b/release-plz.toml index 1a64075..189d214 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -1,19 +1,16 @@ [workspace] -# Enable changelog generation driven by Conventional Commits. +# Update the changelog from Conventional Commits when preparing a release. changelog_update = true -# Create a GitHub release when a release PR is merged. -git_release_enable = true -# Tag each release in git. -git_tag_enable = true -# Publish the crate to crates.io on release. -publish = true -# Verify the package builds before publishing. -publish_allow_dirty = false -# Use semver-compatible version bumps. +# Update dependencies with `cargo update` in the release PR. +dependencies_update = true +# Label applied to the release PR. +pr_labels = ["release"] +# Verify semver compatibility with cargo-semver-checks before releasing. semver_check = true - -[[package]] -name = "redskull" -# Group pre-1.0 releases under a single major so breaking changes -# can still bump the minor version appropriately. -changelog_include = ["redskull"] +# Publishing (crates.io upload, git tag, and GitHub release) is handled by the +# `publish` job in .github/workflows/publish.yml so that we publish via +# crates.io Trusted Publishing (OIDC). release-plz only opens and maintains the +# release PR, so disable the release-side actions here. +publish = false +git_tag_enable = false +git_release_enable = false