Skip to content
Merged
69 changes: 63 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,28 @@ name: Release
on:
push:
branches: [main]
# Recovery path. A release tag is created by the job below and only that run
# carries `release_created`, so a publish that fails for a reason outside the
# tag — a missing tool on the runner, a registry outage — cannot be retried by
# re-running it: the re-run replays the workflow file of that commit, which is
# the one that failed.
#
# Dispatch this **with the release tag selected as the ref**, not from a
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
# branch with the tag as a parameter. Taking it as a free-form input would let
# a dispatch publish any branch or commit through a job holding npm OIDC
# rights, and npm's provenance records the event's ref and sha rather than
# whatever the workspace was checked out to — so a tarball built from a tag
# while the event pointed at a branch would be attested to the wrong commit.
# The ref picker only offers refs that exist, and `ref_type` below rejects
# everything that is not a tag.
#
# The consequence, which is the price of the above: a dispatch runs the
# workflow file stored in the selected tag, so this only rescues tags created
# from this commit onwards. A tag older than this file has no dispatch trigger
# to select and cannot be recovered here — delete it along with its GitHub
# release and let the next release supersede it, since a tag that never
# reached npm has nothing depending on it.
workflow_dispatch:
Comment thread
jlucaso1 marked this conversation as resolved.
Comment thread
jlucaso1 marked this conversation as resolved.

permissions:
contents: read
Expand All @@ -26,6 +48,7 @@ env:
jobs:
release-please:
name: Prepare release
if: github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
Expand Down Expand Up @@ -77,17 +100,46 @@ jobs:
verify:
name: Verify the tag
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
# `!cancelled()` rather than `always()`: the dispatch path needs this to run
# past a skipped release-please, but a cancelled run must not go on to
# publish.
if: >-
!cancelled() &&
(needs.release-please.outputs.release_created == 'true' ||
(github.event_name == 'workflow_dispatch' && github.ref_type == 'tag'))
Comment thread
jlucaso1 marked this conversation as resolved.
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.release-please.outputs.tag_name }}
# The commit the event recorded, never a ref. A ref can move between
# the event and either checkout, and npm records this sha as the
# package's source regardless of what the workspace holds — so
# resolving a tag here could publish one tree while attesting another.
ref: ${{ github.sha }}
persist-credentials: false

# Guards the assumption on the push path: release-please tags the commit
# it just merged, so a tag pointing elsewhere means this run would verify
# and publish something the release does not name.
- name: Confirm the release tag points at this commit
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.release-please.outputs.tag_name }}
run: |
ref=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG")
target=$(jq -r '.object.sha' <<<"$ref")
if [ "$(jq -r '.object.type' <<<"$ref")" = tag ]; then
target=$(gh api "repos/$GITHUB_REPOSITORY/git/tags/$target" --jq '.object.sha')
fi
if [ "$target" != "$GITHUB_SHA" ]; then
echo "::error::$TAG points at $target, not $GITHUB_SHA"
exit 1
fi

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Install Rust
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master
with:
Expand All @@ -100,7 +152,9 @@ jobs:
- name: Install wasm-pack
uses: taiki-e/install-action@91ddec75689c4c78665b598d188dc821c5a43e5c # v2.85.9
with:
tool: wasm-pack
# protoc: `bun run build` regenerates the proto codec, which ci.yml
# never exercises because it calls wasm-pack and build:ts directly.
tool: wasm-pack,protoc

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
Expand Down Expand Up @@ -131,7 +185,7 @@ jobs:
publish:
name: Publish to npm
needs: [release-please, verify]
if: needs.release-please.outputs.release_created == 'true'
if: ${{ !cancelled() && needs.verify.result == 'success' }}
Comment thread
jlucaso1 marked this conversation as resolved.
runs-on: ubuntu-latest
timeout-minutes: 45
# Gate for the publish step: add required reviewers here to make releases
Expand All @@ -145,7 +199,8 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.release-please.outputs.tag_name }}
# Same immutable commit verify checked out and vouched for.
ref: ${{ github.sha }}
# Nothing here talks to the remote after checkout, and the steps below
# run cargo, bun and third-party build scripts in a job holding OIDC
# publish rights. Don't leave the token in .git/config for them.
Expand All @@ -163,7 +218,9 @@ jobs:
- name: Install wasm-pack
uses: taiki-e/install-action@91ddec75689c4c78665b598d188dc821c5a43e5c # v2.85.9
with:
tool: wasm-pack
# protoc: `bun run build` regenerates the proto codec, which ci.yml
# never exercises because it calls wasm-pack and build:ts directly.
tool: wasm-pack,protoc
Comment thread
jlucaso1 marked this conversation as resolved.
Outdated

# Pinned, unlike ci.yml: this bun builds the wasm artifact that ships to
# npm, so a floating version would change the published bytes without a
Expand Down