Skip to content

Commit fd3c3c5

Browse files
committed
feat(_release-rust): draft→upload→publish for immutable releases
GitHub immutable releases lock a release's tag and assets at publish time — assets can no longer be added, modified, or deleted once published. The prior flow created the release already-published and uploaded assets afterward, which is incompatible: the post-publish uploads would be rejected. Always create the release as a draft, attach every asset, then flip it to published as the final step (unless release_draft asks to stop at the draft for manual review). The publish transition still fires the `release: published` event downstream workflows depend on, and the final state is identical for repos without immutability enabled, so this is backward-compatible. Refs kunobi-ninja/kache#481
1 parent 928fc5b commit fd3c3c5

1 file changed

Lines changed: 22 additions & 8 deletions

File tree

.github/workflows/_release-rust.yml

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ on:
4747
type: boolean
4848
default: true
4949
release_draft:
50-
description: "Create the GitHub Release as a draft"
50+
# Assets are always uploaded to a draft first (required for immutable
51+
# releases); this flag controls whether the draft is left unpublished
52+
# for manual review (true) or automatically published (false).
53+
description: "Leave the GitHub Release as an unpublished draft (skip the auto-publish step)"
5154
required: false
5255
type: boolean
5356
default: false
@@ -651,23 +654,34 @@ jobs:
651654
GH_TOKEN: ${{ github.token }}
652655
TAG_NAME: ${{ github.ref_name }}
653656
run: |
654-
# Create release if it doesn't exist
657+
# Always create the release as a DRAFT first, attach every asset, and
658+
# only THEN publish it. This ordering is mandatory for GitHub's
659+
# immutable releases: once a release is published its assets can't be
660+
# added, modified, or deleted, so all assets must be in place before
661+
# the publish transition. It is also strictly backward-compatible for
662+
# repos without immutability enabled — the final state is an identical
663+
# published release with all assets attached.
655664
if ! gh release view "$TAG_NAME" &>/dev/null; then
656-
DRAFT_FLAG=""
657-
if [ "${{ inputs.release_draft }}" = "true" ]; then
658-
DRAFT_FLAG="--draft"
659-
fi
660665
# Tags carrying a known pre-release identifier (rc, alpha,
661666
# beta, pre/preview, dev) are published as pre-releases,
662667
# with "Latest" left pinned to the most recent stable tag
663668
# — otherwise an RC silently becomes the release that
664669
# `@latest` installers and the release badge resolve to.
670+
# Set the pre-release/latest attributes at create time; they persist
671+
# through the publish transition below.
665672
PRERELEASE_FLAG=""
666673
case "$TAG_NAME" in
667674
*-rc*|*-alpha*|*-beta*|*-pre*|*-dev*)
668675
PRERELEASE_FLAG="--prerelease --latest=false" ;;
669676
esac
670-
gh release create "$TAG_NAME" $DRAFT_FLAG $PRERELEASE_FLAG --generate-notes
677+
gh release create "$TAG_NAME" --draft $PRERELEASE_FLAG --generate-notes
671678
fi
672-
# Upload assets (--clobber overwrites existing)
679+
# Attach all assets to the (still draft) release (--clobber overwrites
680+
# existing on re-runs).
673681
gh release upload "$TAG_NAME" artifacts/* --clobber
682+
# Publish the draft — this transition fires the `release: published`
683+
# event and, when immutable releases are enabled, locks the tag and
684+
# assets. Skip it when the caller opted into a draft for manual review.
685+
if [ "${{ inputs.release_draft }}" != "true" ]; then
686+
gh release edit "$TAG_NAME" --draft=false
687+
fi

0 commit comments

Comments
 (0)