mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 00:01:06 +00:00
ci: promote stable releases from protected previews (#4241)
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
name: Preview
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit:
|
||||
description: Optional master commit SHA to publish
|
||||
required: false
|
||||
type: string
|
||||
push:
|
||||
tags:
|
||||
- "preview-*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -21,7 +18,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
preflight:
|
||||
if: github.repository == 'herdrdev/herdr'
|
||||
if: github.repository == 'herdrdev/herdr' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/preview-')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -34,7 +31,22 @@ jobs:
|
||||
built_at: ${{ steps.plan.outputs.built_at }}
|
||||
base_version: ${{ steps.plan.outputs.base_version }}
|
||||
protocol: ${{ steps.plan.outputs.protocol }}
|
||||
endpoint_generation: ${{ steps.plan.outputs.endpoint_generation }}
|
||||
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:
|
||||
ref: master
|
||||
@@ -46,16 +58,11 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin master --tags
|
||||
requested="${{ github.event.inputs.commit || '' }}"
|
||||
if [ -n "$requested" ]; then
|
||||
commit="$(git rev-parse "$requested^{commit}")"
|
||||
if ! git merge-base --is-ancestor "$commit" origin/master; then
|
||||
echo "error: requested commit $commit is not reachable from origin/master" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
commit="$(python3 scripts/preview.py select-commit --ref origin/master)"
|
||||
git fetch --prune origin '+refs/heads/master:refs/remotes/origin/master' '+refs/heads/release/*:refs/remotes/origin/release/*' --tags
|
||||
commit="$(python3 scripts/release.py preview-source --commit "$GITHUB_SHA")"
|
||||
if [ "$(git rev-parse "refs/tags/$GITHUB_REF_NAME^{commit}")" != "$commit" ]; then
|
||||
echo "error: preview tag no longer points at the triggering commit" >&2
|
||||
exit 1
|
||||
fi
|
||||
current_preview="$(python3 scripts/preview.py current-commit --manifest distribution/preview.json || true)"
|
||||
if [ "$current_preview" = "$commit" ] && node scripts/docs/preview.mjs check; then
|
||||
@@ -69,8 +76,13 @@ jobs:
|
||||
built_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
build_id="$day-$short_sha"
|
||||
tag="preview-$build_id"
|
||||
if [ "$GITHUB_REF_NAME" != "$tag" ]; then
|
||||
echo "error: expected preview tag $tag; use just preview" >&2
|
||||
exit 1
|
||||
fi
|
||||
base_version="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)"
|
||||
protocol="$(python3 -c 'import re; print(re.search(r"pub const PROTOCOL_VERSION: u32 = (\d+);", open("src/protocol/wire.rs").read()).group(1))')"
|
||||
endpoint_generation="$(python3 -c 'import re; print(re.search(r"pub const ENDPOINT_PROTOCOL_GENERATION: u32 = (\d+);", open("src/protocol/endpoint.rs").read()).group(1))')"
|
||||
{
|
||||
echo "should_publish=true"
|
||||
echo "commit=$commit"
|
||||
@@ -80,6 +92,7 @@ jobs:
|
||||
echo "built_at=$built_at"
|
||||
echo "base_version=$base_version"
|
||||
echo "protocol=$protocol"
|
||||
echo "endpoint_generation=$endpoint_generation"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Install Rust
|
||||
@@ -119,7 +132,7 @@ jobs:
|
||||
|
||||
build:
|
||||
needs: preflight
|
||||
if: github.repository == 'herdrdev/herdr' && needs.preflight.outputs.should_publish == 'true'
|
||||
if: github.repository == 'herdrdev/herdr' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/preview-') && needs.preflight.outputs.should_publish == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
@@ -268,7 +281,7 @@ jobs:
|
||||
|
||||
publish:
|
||||
needs: [preflight, build]
|
||||
if: github.repository == 'herdrdev/herdr' && needs.preflight.outputs.should_publish == 'true'
|
||||
if: github.repository == 'herdrdev/herdr' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/preview-') && needs.preflight.outputs.should_publish == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: docs-publish-master
|
||||
@@ -277,6 +290,7 @@ jobs:
|
||||
contents: write
|
||||
issues: write
|
||||
steps:
|
||||
- *require-release-admins
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: master
|
||||
@@ -311,7 +325,6 @@ jobs:
|
||||
--previous '${{ steps.previous-preview.outputs.range_base }}' \
|
||||
--commit '${{ needs.preflight.outputs.commit }}' \
|
||||
--build-id '${{ needs.preflight.outputs.build_id }}' \
|
||||
--base-version '${{ needs.preflight.outputs.base_version }}' \
|
||||
--output PREVIEW_NOTES.md
|
||||
python3 - <<'PY'
|
||||
import json, pathlib
|
||||
@@ -344,7 +357,7 @@ jobs:
|
||||
echo "error: $PREVIEW_TAG exists as an immutable release with the wrong release type" >&2
|
||||
exit 1
|
||||
fi
|
||||
tag_commit="$(git ls-remote origin "refs/tags/${PREVIEW_TAG}" | awk 'NR == 1 {print $1}')"
|
||||
tag_commit="$(git rev-parse "refs/tags/${PREVIEW_TAG}^{commit}")"
|
||||
if [ "$tag_commit" != "$PREVIEW_COMMIT" ]; then
|
||||
echo "error: immutable release $PREVIEW_TAG points at $tag_commit, expected $PREVIEW_COMMIT" >&2
|
||||
exit 1
|
||||
@@ -405,6 +418,7 @@ jobs:
|
||||
--built-at '${{ needs.preflight.outputs.built_at }}' \
|
||||
--base-version '${{ needs.preflight.outputs.base_version }}' \
|
||||
--protocol '${{ needs.preflight.outputs.protocol }}' \
|
||||
--endpoint-generation '${{ needs.preflight.outputs.endpoint_generation }}' \
|
||||
--notes PREVIEW_NOTES.md \
|
||||
--sha-file preview-sha256.json \
|
||||
--retain 30
|
||||
@@ -522,26 +536,3 @@ jobs:
|
||||
continue
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Prune old preview prereleases
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release list --repo "$GITHUB_REPOSITORY" --limit 100 --json tagName,isPrerelease,createdAt > preview-releases.json
|
||||
python3 - <<'PY' > old-preview-tags.txt
|
||||
import json
|
||||
with open("preview-releases.json", encoding="utf-8") as handle:
|
||||
data = json.load(handle)
|
||||
releases = [
|
||||
release for release in data
|
||||
if release.get("isPrerelease") and str(release.get("tagName", "")).startswith("preview-")
|
||||
]
|
||||
releases.sort(key=lambda release: str(release.get("createdAt", "")), reverse=True)
|
||||
for release in releases[30:]:
|
||||
print(release["tagName"])
|
||||
PY
|
||||
while IFS= read -r tag; do
|
||||
[ -n "$tag" ] || continue
|
||||
gh release delete "$tag" --repo "$GITHUB_REPOSITORY" --yes --cleanup-tag
|
||||
done < old-preview-tags.txt
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user