name: Release Tag
on:
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
tag:
name: Promote to main and tag
runs-on: ubuntu-latest
permissions:
contents: write
actions: write pull-requests: write
steps:
- name: Require develop
if: github.ref != 'refs/heads/develop'
run: |
echo "::error::Run this workflow from develop (started from ${GITHUB_REF})."
exit 1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
ref: develop
fetch-depth: 0
- name: Read version from Cargo.toml
id: version
run: |
VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Tag will be: v${VERSION}"
- name: Verify release commit is on develop
run: |
VERSION="${{ steps.version.outputs.version }}"
# The release PR's commit, or its squash merge ("Release vX.Y.Z (#N)").
RELEASE_SHA=$(git log -E --grep="^Release v${VERSION//./\\.}( \(#[0-9]+\))?$" \
--format="%H" | head -1)
if [ -z "$RELEASE_SHA" ]; then
echo "::error::No 'Release v${VERSION}' commit found on develop. Has the release PR been merged into develop?"
exit 1
fi
- name: Verify CI is green on develop
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
SHA=$(git rev-parse HEAD)
RESULT=$(gh run list \
--branch develop \
--workflow ci.yml \
--json headSha,status,conclusion \
--limit 20 \
| jq -r --arg sha "$SHA" '
[.[] | select(.headSha == $sha)] as $runs
| ([$runs[] | select(.status == "completed")] | first | .conclusion)
// (if ($runs | length) > 0 then "still-running" else "not-found" end)')
case "$RESULT" in
success ) echo "ci.yml on develop@${SHA}: success" ;;
still-running )
echo "::error::ci.yml on develop@${SHA} has not finished yet — re-run this workflow once it has."
exit 1 ;;
* )
echo "::error::CI conclusion on develop@${SHA} is '${RESULT}' — must be 'success' before tagging."
exit 1 ;;
esac
- name: Check tag does not already exist
run: |
VERSION="${{ steps.version.outputs.version }}"
if git ls-remote --tags origin "refs/tags/v${VERSION}" | grep -q .; then
echo "::error::Tag v${VERSION} already exists — was this release already published?"
exit 1
fi
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Promote develop → main via PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.version.outputs.version }}"
BRANCH="release-promote/v${VERSION}"
git fetch origin main
git merge origin/main --allow-unrelated-histories -s ours \
-m "Release v${VERSION}: promote develop to main"
git push origin "HEAD:refs/heads/${BRANCH}"
PR_URL=$(gh pr create \
--base main \
--head "${BRANCH}" \
--title "Release v${VERSION}" \
--body "Promote release v${VERSION} from develop to main.")
MERGED=false
for attempt in 1 2 3; do
if gh pr merge "$PR_URL" --merge; then
MERGED=true
break
fi
[ "$attempt" -lt 3 ] && sleep 10
done
if [ "$MERGED" != true ]; then
echo "::error::Could not merge $PR_URL into main — not tagging."
exit 1
fi
git push origin --delete "${BRANCH}" || true
- name: Create and push tag
run: |
VERSION="${{ steps.version.outputs.version }}"
git fetch origin main
# Release tags cannot be moved or deleted (ruleset), so refuse to tag
# unless main now holds exactly the tree that was promoted.
if [ "$(git rev-parse 'origin/main^{tree}')" != "$(git rev-parse 'HEAD^{tree}')" ]; then
echo "::error::origin/main does not match the promoted release commit — not tagging v${VERSION}."
exit 1
fi
git checkout main
git tag "v${VERSION}" -m "Release v${VERSION}"
git push origin "v${VERSION}"
- name: Trigger publish workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.version.outputs.version }}"
gh workflow run publish.yml --ref "v${VERSION}"
echo "Triggered Publish workflow for v${VERSION}."