name: Create release PR on: workflow_dispatch: inputs: version: description: "Version to release (e.g., 0.6.6)" required: true permissions: contents: write pull-requests: write jobs: release-pr: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 with: fetch-depth: 0 ref: main - name: Setup uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # setup-uv v8.1.0; uv 0.11.14 with: version: "0.11.14" enable-cache: true - name: Fetch tags run: git fetch origin --tags --prune - name: Ensure release branch does not exist env: RELEASE_VERSION: ${{ inputs.version }} run: | branch="release/v${RELEASE_VERSION}" if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then echo "Branch $branch already exists on origin." >&2 exit 1 fi - name: Update version env: RELEASE_VERSION: ${{ inputs.version }} run: | python - <<'PY' import os import pathlib import re import sys version = os.environ["RELEASE_VERSION"] if version.startswith("v"): print("Version must not start with 'v' (use x.y.z...).", file=sys.stderr) sys.exit(1) if ".." in version: print("Version contains consecutive dots (use x.y.z...).", file=sys.stderr) sys.exit(1) if not re.match(r"^\d+\.\d+(\.\d+)*([a-zA-Z0-9\.-]+)?$", version): print( "Version must be semver-like (e.g., 0.6.6, 0.6.6-rc1, 0.6.6.dev1).", file=sys.stderr, ) sys.exit(1) path = pathlib.Path("pyproject.toml") text = path.read_text() updated, count = re.subn( r'(?m)^version\s*=\s*"[^\"]+"', f'version = "{version}"', text, ) if count != 1: print("Expected to update exactly one version line.", file=sys.stderr) sys.exit(1) if updated == text: print("Version already set; no changes made.", file=sys.stderr) sys.exit(1) path.write_text(updated) PY - name: Sync dependencies run: make sync - name: Configure git run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Create release branch and commit env: RELEASE_VERSION: ${{ inputs.version }} run: | branch="release/v${RELEASE_VERSION}" git checkout -b "$branch" git add pyproject.toml uv.lock if git diff --cached --quiet; then echo "No changes to commit." >&2 exit 1 fi git commit -m "Bump version to ${RELEASE_VERSION}" git push --set-upstream origin "$branch" - name: Build PR body env: RELEASE_VERSION: ${{ inputs.version }} run: | printf 'Release PR for %s.\n\nThe release readiness report will be prepared manually.\n' "$RELEASE_VERSION" > pr-body.md - name: Create or update PR env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_VERSION: ${{ inputs.version }} run: | set -euo pipefail head_branch="release/v${RELEASE_VERSION}" milestone_name="$(python .github/scripts/select-release-milestone.py --version "$RELEASE_VERSION")" pr_number="$(gh pr list --head "$head_branch" --base "main" --json number --jq '.[0].number // empty')" if [ -z "$pr_number" ]; then create_args=( --title "Release ${RELEASE_VERSION}" --body-file pr-body.md --base "main" --head "$head_branch" --label "project" ) if [ -n "$milestone_name" ]; then create_args+=(--milestone "$milestone_name") fi if ! gh pr create "${create_args[@]}"; then echo "PR create with label/milestone failed; retrying without them." >&2 gh pr create \ --title "Release ${RELEASE_VERSION}" \ --body-file pr-body.md \ --base "main" \ --head "$head_branch" fi else edit_args=( --title "Release ${RELEASE_VERSION}" --body-file pr-body.md --add-label "project" ) if [ -n "$milestone_name" ]; then edit_args+=(--milestone "$milestone_name") fi if ! gh pr edit "$pr_number" "${edit_args[@]}"; then echo "PR edit with label/milestone failed; retrying without them." >&2 gh pr edit "$pr_number" --title "Release ${RELEASE_VERSION}" --body-file pr-body.md fi fi