[Improve] Refine Automations filtering and editing (#1036) #740
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| branches: | |
| - develop | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: Unshipped product version to refresh from develop | |
| required: true | |
| type: string | |
| # Every push re-checks the live develop version. Tagged versions are no-ops; | |
| # an untagged version means a maintainer merged a release PR and is ready for | |
| # the frozen promotion gate. Maintainers can explicitly refresh an unshipped | |
| # candidate after amending its release notes on develop. | |
| concurrency: | |
| group: release-develop | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| promote: | |
| name: Open or refresh Promote PR | |
| runs-on: blacksmith-4vcpu-ubuntu-2404 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| token: ${{ secrets.RELEASE_BOT_TOKEN || secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| ref: develop | |
| - name: Cut or refresh release candidate and open promote PR | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| GH_TOKEN: ${{ secrets.RELEASE_BOT_TOKEN || secrets.GITHUB_TOKEN }} | |
| REQUESTED_VERSION: ${{ inputs.version }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| current_version="$(node -p "require('./package.json').version")" | |
| version="$current_version" | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| version="${REQUESTED_VERSION#v}" | |
| if [ "$version" != "$current_version" ]; then | |
| echo "Cannot refresh ${version}: develop is currently version ${current_version}." | |
| exit 1 | |
| fi | |
| fi | |
| : "${version:?missing package.json version}" | |
| tag="v${version}" | |
| if git rev-parse "$tag" >/dev/null 2>&1 || git ls-remote --tags origin "refs/tags/$tag" | grep -q .; then | |
| echo "Tag $tag already exists; nothing to promote." | |
| exit 0 | |
| fi | |
| git fetch origin "refs/heads/main:refs/remotes/origin/main" --quiet | |
| if ! git show-ref --verify --quiet refs/remotes/origin/main; then | |
| echo "origin/main is missing; create main before promoting." | |
| exit 1 | |
| fi | |
| bump_sha="$(node scripts/release/find-version-commit.mjs "$version" HEAD)" | |
| release_branch="release/${tag}" | |
| existing="" | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| if ! git ls-remote --exit-code --heads origin "$release_branch" >/dev/null 2>&1; then | |
| echo "Cannot refresh missing release branch ${release_branch}." | |
| exit 1 | |
| fi | |
| git fetch origin "refs/heads/${release_branch}:refs/remotes/origin/${release_branch}" --quiet | |
| candidate_sha="$(git rev-parse "origin/${release_branch}")" | |
| if git merge-base --is-ancestor "$candidate_sha" origin/main; then | |
| echo "Cannot refresh ${tag}: main already contains candidate ${candidate_sha}." | |
| exit 1 | |
| fi | |
| if ! git merge-base --is-ancestor "$candidate_sha" HEAD; then | |
| echo "Cannot refresh ${tag}: ${release_branch} has diverged from develop." | |
| exit 1 | |
| fi | |
| existing="$(gh pr list --base main --head "$release_branch" --state open --json url --jq '.[0].url // empty')" | |
| if [ -z "$existing" ]; then | |
| echo "Cannot refresh ${tag}: no open Promote PR targets main." | |
| exit 1 | |
| fi | |
| pending_changesets="$(git ls-files '.changeset/*.md' | grep -iv '/README.md$' || true)" | |
| if [ -n "$pending_changesets" ]; then | |
| echo "Cannot refresh ${tag} with pending changesets. Amend the current release notes first:" | |
| echo "$pending_changesets" | |
| exit 1 | |
| fi | |
| # Minimize the merge race by repeating the shipped-state and PR | |
| # checks immediately before moving the candidate branch. | |
| git fetch origin "refs/heads/main:refs/remotes/origin/main" --tags --quiet | |
| if git rev-parse "$tag" >/dev/null 2>&1 || git merge-base --is-ancestor "$candidate_sha" origin/main; then | |
| echo "Cannot refresh ${tag}: the candidate reached main while this refresh was running." | |
| exit 1 | |
| fi | |
| existing="$(gh pr list --base main --head "$release_branch" --state open --json url --jq '.[0].url // empty')" | |
| if [ -z "$existing" ]; then | |
| echo "Cannot refresh ${tag}: the Promote PR closed while this refresh was running." | |
| exit 1 | |
| fi | |
| release_sha="$(git rev-parse HEAD)" | |
| git push origin "${release_sha}:refs/heads/${release_branch}" | |
| candidate_status="Candidate refreshed to \`${release_sha}\` from \`develop\`." | |
| echo "Refreshed ${release_branch} to ${release_sha}" | |
| else | |
| # Freeze the release at the commit that introduced this version (the | |
| # release PR merge), not at the moving develop tip. Later commits are | |
| # included only through an explicit guarded refresh. | |
| if git merge-base --is-ancestor "$bump_sha" origin/main; then | |
| echo "main already contains ${bump_sha}; nothing to promote." | |
| exit 0 | |
| fi | |
| release_sha="$bump_sha" | |
| candidate_status="Frozen at \`${release_sha}\` — the commit where \`${version}\` was versioned. Commits merged to \`develop\` afterward require an explicit candidate refresh or ship in the next release." | |
| if git ls-remote --exit-code --heads origin "$release_branch" >/dev/null 2>&1; then | |
| echo "Release branch ${release_branch} already exists; leaving it frozen." | |
| else | |
| git push origin "${release_sha}:refs/heads/${release_branch}" | |
| echo "Cut ${release_branch} at ${release_sha}" | |
| fi | |
| fi | |
| notes="$(node scripts/release/extract-changelog-section.mjs "$version" || true)" | |
| body_file="$(mktemp)" | |
| { | |
| echo "## Promote ${tag}" | |
| echo | |
| echo "$candidate_status" | |
| echo | |
| echo "- Merge this PR with a **merge commit** (do not squash or rebase)." | |
| echo "- If multiple promote PRs are open, merge them in version order (oldest first)." | |
| echo "- Merging publishes a GitHub Release for \`${tag}\` and triggers the existing GHCR \`v*\` image publish (\`latest\` channel)." | |
| echo "- The \`${release_branch}\` branch can be deleted after this PR merges." | |
| echo | |
| echo "### Changelog" | |
| echo | |
| if [ -n "$notes" ]; then | |
| echo "$notes" | |
| else | |
| echo "_No CHANGELOG section found for ${version}; see commits on develop._" | |
| fi | |
| } > "$body_file" | |
| if [ -z "$existing" ]; then | |
| existing="$(gh pr list --base main --head "$release_branch" --state open --json url --jq '.[0].url // empty')" | |
| fi | |
| if [ -n "$existing" ]; then | |
| echo "Promote PR already open: $existing" | |
| gh pr edit "$existing" --title "Promote ${tag} to production" --body-file "$body_file" | |
| rm -f "$body_file" | |
| exit 0 | |
| fi | |
| gh pr create \ | |
| --base main \ | |
| --head "$release_branch" \ | |
| --title "Promote ${tag} to production" \ | |
| --body-file "$body_file" | |
| rm -f "$body_file" |