Skip to content

[Improve] Refine Automations filtering and editing (#1036) #740

[Improve] Refine Automations filtering and editing (#1036)

[Improve] Refine Automations filtering and editing (#1036) #740

Workflow file for this run

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"