summaryrefslogtreecommitdiff
path: root/.github/workflows/release.yml
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-16 18:25:59 +0300
committerGitHub <noreply@github.com>2026-09-16 18:25:59 +0300
commit36db8ff3f4bee21b06f3208422be7cf91c7d3e40 (patch)
tree539cb0114feb45aeb8d483a29cba6e7912f77483 /.github/workflows/release.yml
parentaff99878d3467e809e18f07fb338c78dea50e558 (diff)
ci: promote stable releases from protected previews (#4241)
Diffstat (limited to '.github/workflows/release.yml')
-rw-r--r--.github/workflows/release.yml72
1 files changed, 57 insertions, 15 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5344962c..64ae182b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,7 +13,41 @@ env:
RUST_TOOLCHAIN_VERSION: 1.96.1
jobs:
+ validate-release-source:
+ if: github.repository == 'herdrdev/herdr' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+ runs-on: ubuntu-latest
+ outputs:
+ preview_commit: ${{ steps.source.outputs.preview_commit }}
+ previous_tag: ${{ steps.source.outputs.previous_tag }}
+ steps:
+ - &require-release-admins
+ name: Require repository admins for publishing and reruns
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ set -euo pipefail
+ for actor in "$GITHUB_ACTOR" "$GITHUB_TRIGGERING_ACTOR"; do
+ permission="$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$actor/permission" --jq .permission)"
+ if [ "$permission" != "admin" ]; then
+ echo "error: $actor must have repository admin permission to publish" >&2
+ exit 1
+ fi
+ done
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+ - name: Require a published preview and release-only changes
+ id: source
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ git fetch origin master:refs/remotes/origin/master --tags
+ python3 scripts/release.py check-tag --tag "$GITHUB_REF_NAME" --github-output "$GITHUB_OUTPUT"
+
flake-check:
+ needs: validate-release-source
if: github.repository == 'herdrdev/herdr'
runs-on: ubuntu-latest
permissions:
@@ -37,6 +71,7 @@ jobs:
nix flake check --all-systems --no-build --print-build-logs
build:
+ needs: validate-release-source
if: github.repository == 'herdrdev/herdr'
permissions:
contents: read
@@ -166,6 +201,7 @@ jobs:
path: ${{ matrix.name }}
validate-release-inputs:
+ needs: validate-release-source
if: github.repository == 'herdrdev/herdr'
runs-on: ubuntu-latest
permissions:
@@ -195,12 +231,13 @@ jobs:
release:
needs: [build, flake-check, validate-release-inputs]
- if: github.repository == 'herdrdev/herdr'
+ if: github.repository == 'herdrdev/herdr' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
+ - *require-release-admins
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
@@ -224,13 +261,14 @@ jobs:
update-nix-package:
needs: release
- if: github.repository == 'herdrdev/herdr'
+ if: github.repository == 'herdrdev/herdr' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
timeout-minutes: 2
continue-on-error: true
permissions: {}
steps:
+ - *require-release-admins
- name: Trigger stable Nix package update
env:
GH_TOKEN: ${{ secrets.HERDR_NIX_DISPATCH_TOKEN }}
@@ -242,8 +280,8 @@ jobs:
fi
close-released-issues:
- needs: release
- if: github.repository == 'herdrdev/herdr'
+ needs: [release, validate-release-source]
+ if: github.repository == 'herdrdev/herdr' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
continue-on-error: true
permissions:
@@ -251,6 +289,7 @@ jobs:
issues: write
steps:
+ - *require-release-admins
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
@@ -263,6 +302,7 @@ jobs:
NEXT_RELEASE_LABEL: pending-release
LEGACY_NEXT_RELEASE_LABEL: included-in-next-release
PREVIEW_RELEASED_LABEL: preview-released
+ PREVIOUS_TAG: ${{ needs.validate-release-source.outputs.previous_tag }}
run: |
set -euo pipefail
@@ -270,13 +310,6 @@ jobs:
VERSION="${GITHUB_REF_NAME#v}"
CURRENT_COMMIT="$(git rev-list -n 1 "$GITHUB_REF_NAME")"
- PREVIOUS_TAG="$(git describe --first-parent --tags --match 'v[0-9]*' --abbrev=0 "${CURRENT_COMMIT}^" 2>/dev/null || true)"
-
- if [ -z "$PREVIOUS_TAG" ]; then
- echo "No previous release tag found; skipping issue close."
- exit 0
- fi
-
echo "Scanning released commits in $PREVIOUS_TAG..$GITHUB_REF_NAME for refs #<issue> mentions."
mapfile -t ISSUES < <(
git log --format='%s%n%b' "$PREVIOUS_TAG..$CURRENT_COMMIT" \
@@ -379,8 +412,8 @@ jobs:
done
update-latest-json:
- needs: release
- if: github.repository == 'herdrdev/herdr'
+ needs: [release, validate-release-source]
+ if: github.repository == 'herdrdev/herdr' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
concurrency:
group: docs-publish-master
@@ -389,6 +422,7 @@ jobs:
contents: write
steps:
+ - *require-release-admins
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: master
@@ -398,6 +432,7 @@ jobs:
- name: Publish tagged documentation and update distribution manifest
env:
GH_TOKEN: ${{ github.token }}
+ PREVIEW_COMMIT: ${{ needs.validate-release-source.outputs.preview_commit }}
run: |
VERSION="${GITHUB_REF_NAME#v}"
node scripts/docs/versions.mjs publish "$GITHUB_REF_NAME"
@@ -407,6 +442,7 @@ jobs:
cp "$ANNOUNCEMENT_PATH" "$ANNOUNCEMENT_ORIGINAL_PATH"
python3 scripts/changelog.py validate-product-announcement --path "$ANNOUNCEMENT_PATH"
RELEASE_PROTOCOL=$(git show "${GITHUB_REF_NAME}:src/protocol/wire.rs" | python3 -c 'import re, sys; match = re.search(r"pub const PROTOCOL_VERSION: u32 = (\d+);", sys.stdin.read()); sys.exit(1) if match is None else print(match.group(1))')
+ RELEASE_ENDPOINT_GENERATION=$(git show "${GITHUB_REF_NAME}:src/protocol/endpoint.rs" | python3 -c 'import re, sys; match = re.search(r"pub const ENDPOINT_PROTOCOL_GENERATION: u32 = (\d+);", sys.stdin.read()); sys.exit(1) if match is None else print(match.group(1))')
DOCS_CURRENT=$(node scripts/docs/versions.mjs current)
if [ "$DOCS_CURRENT" != "$VERSION" ]; then
@@ -418,7 +454,12 @@ jobs:
echo "distribution/latest.json is already at v$VERSION"
exit 0
fi
- python3 scripts/changelog.py sync-latest-json --version "$VERSION" --output distribution/latest.json --announcement "$ANNOUNCEMENT_PATH" --protocol "$RELEASE_PROTOCOL"
+ # Apply only release preparation, never the hotfix implementation or newer master code.
+ git diff --binary "$PREVIEW_COMMIT" "$GITHUB_REF_NAME" > "$RUNNER_TEMP/release-preparation.patch"
+ if [ -s "$RUNNER_TEMP/release-preparation.patch" ]; then
+ git apply --3way --index "$RUNNER_TEMP/release-preparation.patch"
+ fi
+ python3 scripts/changelog.py sync-latest-json --version "$VERSION" --output distribution/latest.json --announcement "$ANNOUNCEMENT_PATH" --protocol "$RELEASE_PROTOCOL" --endpoint-generation "$RELEASE_ENDPOINT_GENERATION"
if cmp -s "$ANNOUNCEMENT_ORIGINAL_PATH" docs/next/product-announcement.json; then
printf 'null\n' > docs/next/product-announcement.json
else
@@ -431,7 +472,8 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A README.md README.zh-CN.md docs/versions distribution/latest.json docs/next/product-announcement.json
- git diff --cached --quiet || git commit -m "docs: publish release distribution for v$VERSION"
+ # Release-only changes applied above are already staged by git apply --index.
+ git diff --cached --quiet || git commit -m "release: synchronize metadata for v$VERSION"
for attempt in 1 2 3; do
git pull --rebase origin master
node scripts/docs/versions.mjs check