Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/scripts/bump_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ def bump(version: str) -> None:
r'(?m)(^name = "sbx"\nversion = ")[^"]+("$)',
rf"\g<1>{version}\2",
)
if ".dev" not in version:
replace_once(
ROOT / "README.md",
r"git\+https://github\.com/nueces/sbx\.git@v[^\s]+",
f"git+https://github.com/nueces/sbx.git@v{version}",
)


if __name__ == "__main__":
Expand Down
48 changes: 45 additions & 3 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Publish release

# Publishes merged release/v* PRs by creating the git tag, GitHub release, and next dev-bump PR.
# Publishes merged release/v* PRs by creating the git tag, GitHub release, website PR, and next dev-bump PR.
# Previous: release-pr-checks.yml validates the release PR.
# Next: merge the dev-bump PR manually.
# Next: merge the website and dev-bump PRs manually.

on:
pull_request:
Expand Down Expand Up @@ -37,12 +37,17 @@ jobs:

git fetch origin main --tags
changed="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")"
bad_files="$(echo "$changed" | grep -Ev '^(pyproject.toml|src/sbx/__init__.py|uv.lock)$' || true)"
bad_files="$(echo "$changed" | grep -Ev '^(README.md|pyproject.toml|src/sbx/__init__.py|uv.lock)$' || true)"
if [ -n "$bad_files" ]; then
echo "release PR contains unexpected files:" >&2
echo "$bad_files" >&2
exit 1
fi
counts="$(git diff --numstat "$BASE_SHA" "$HEAD_SHA" | awk '{ added += $1; deleted += $2 } END { print added+0 " " deleted+0 }')"
if [ "$counts" != "4 4" ]; then
echo "release PR should be exactly 4 insertions and 4 deletions; got $counts" >&2
exit 1
fi

bad_init="$(git diff --unified=0 "$BASE_SHA" "$HEAD_SHA" -- src/sbx/__init__.py | grep -E '^[+-][^+-]' | grep -Ev '^[+-]__version__ = "[0-9]+\.[0-9]+\.[0-9]+"$' || true)"
if [ -n "$bad_init" ]; then
Expand All @@ -58,6 +63,13 @@ jobs:
exit 1
fi

bad_readme="$(git diff --unified=0 "$BASE_SHA" "$HEAD_SHA" -- README.md | grep -E '^[+-][^+-]' | grep -Ev '^[+-]uv tool install git\+https://github\.com/nueces/sbx\.git@v[0-9]+\.[0-9]+\.[0-9]+$' || true)"
if [ -n "$bad_readme" ]; then
echo "README.md may only change the install tag" >&2
echo "$bad_readme" >&2
exit 1
fi

grep -qx "version = \"$version\"" pyproject.toml
grep -qx "__version__ = \"$version\"" src/sbx/__init__.py
python .github/scripts/bump_version.py "$version"
Expand All @@ -67,6 +79,7 @@ jobs:
echo "tag already exists: $tag" >&2
exit 1
fi
echo "RELEASE_VERSION=$version" >> "$GITHUB_ENV"
echo "RELEASE_TAG=$tag" >> "$GITHUB_ENV"

- name: Create GitHub release
Expand All @@ -75,6 +88,30 @@ jobs:
git push origin "$RELEASE_TAG"
gh release create "$RELEASE_TAG" --title "$RELEASE_TAG" --generate-notes

- name: Open website release PR
run: |
set -euo pipefail
token="${RELEASE_PR_TOKEN:-$GH_TOKEN}"
export GH_TOKEN="$token"
webpage_branch="webpage/release-$RELEASE_TAG"

if git ls-remote --exit-code --heads origin "$webpage_branch" >/dev/null 2>&1; then
echo "branch already exists: $webpage_branch"
exit 0
fi

git clone --branch webpage/main --single-branch "https://x-access-token:${token}@github.com/${GITHUB_REPOSITORY}.git" webpage-release
cd webpage-release
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "$webpage_branch"
python ../.github/scripts/bump_webpage_version.py "$RELEASE_VERSION" index.html
git add index.html
git diff --cached --quiet && { echo "webpage already matches $RELEASE_VERSION"; exit 0; }
git commit -m "Update website for $RELEASE_TAG"
git push origin "$webpage_branch"
gh pr create --repo "$GITHUB_REPOSITORY" --base webpage/main --head "$webpage_branch" --title "Update website for $RELEASE_TAG" --body "Updates the website after $RELEASE_TAG was published."

- name: Open next dev version PR
run: |
set -euo pipefail
Expand All @@ -98,6 +135,11 @@ jobs:
git switch -c "$dev_branch" origin/main
python .github/scripts/bump_version.py "$dev_version"
git diff --quiet && { echo "main already at $dev_version"; exit 0; }
counts="$(git diff --numstat | awk '{ added += $1; deleted += $2 } END { print added+0 " " deleted+0 }')"
if [ "$counts" != "3 3" ]; then
echo "dev-bump PR should be exactly 3 insertions and 3 deletions; got $counts" >&2
exit 1
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/release-pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,17 @@ jobs:
git fetch origin main --tags
version="${HEAD_REF#release/v}"
changed="$(git diff --name-only "$BASE_REF...HEAD")"
bad_files="$(echo "$changed" | grep -Ev '^(pyproject.toml|src/sbx/__init__.py|uv.lock)$' || true)"
bad_files="$(echo "$changed" | grep -Ev '^(README.md|pyproject.toml|src/sbx/__init__.py|uv.lock)$' || true)"
if [ -n "$bad_files" ]; then
echo "release PR contains unexpected files:" >&2
echo "$bad_files" >&2
exit 1
fi
counts="$(git diff --numstat "$BASE_REF...HEAD" | awk '{ added += $1; deleted += $2 } END { print added+0 " " deleted+0 }')"
if [ "$counts" != "4 4" ]; then
echo "release PR should be exactly 4 insertions and 4 deletions; got $counts" >&2
exit 1
fi

bad_init="$(git diff --unified=0 "$BASE_REF...HEAD" -- src/sbx/__init__.py | grep -E '^[+-][^+-]' | grep -Ev '^[+-]__version__ = "[0-9]+\.[0-9]+\.[0-9]+"$' || true)"
if [ -n "$bad_init" ]; then
Expand All @@ -50,6 +55,13 @@ jobs:
exit 1
fi

bad_readme="$(git diff --unified=0 "$BASE_REF...HEAD" -- README.md | grep -E '^[+-][^+-]' | grep -Ev '^[+-]uv tool install git\+https://github\.com/nueces/sbx\.git@v[0-9]+\.[0-9]+\.[0-9]+$' || true)"
if [ -n "$bad_readme" ]; then
echo "README.md may only change the install tag" >&2
echo "$bad_readme" >&2
exit 1
fi

grep -qx "version = \"$version\"" pyproject.toml
grep -qx "__version__ = \"$version\"" src/sbx/__init__.py
python .github/scripts/bump_version.py "$version"
Expand Down
31 changes: 7 additions & 24 deletions .github/workflows/start-release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Start release

# Manual release entry point: opens the package release PR and matching website PR.
# Manual release entry point: opens the package release PR.
# Previous: none.
# Next: release-pr-checks.yml runs on the opened release PR.

Expand Down Expand Up @@ -44,7 +44,6 @@ jobs:
fi
release_tag="v$version"
release_branch="release/$release_tag"
webpage_branch="webpage/release-$release_tag"

if git rev-parse -q --verify "refs/tags/$release_tag" >/dev/null; then
echo "tag already exists: $release_tag" >&2
Expand All @@ -54,46 +53,30 @@ jobs:
echo "branch already exists: $release_branch" >&2
exit 1
fi
if git ls-remote --exit-code --heads origin "$webpage_branch" >/dev/null 2>&1; then
echo "branch already exists: $webpage_branch" >&2
exit 1
fi

echo "Previous release: ${previous_tag:-none}"
echo "Current release: $release_tag"
echo "Previous release: ${previous_tag:-none}" >> "$GITHUB_STEP_SUMMARY"
echo "Current release: $release_tag" >> "$GITHUB_STEP_SUMMARY"
echo "RELEASE_VERSION=$version" >> "$GITHUB_ENV"
echo "RELEASE_TAG=$release_tag" >> "$GITHUB_ENV"
echo "RELEASE_BRANCH=$release_branch" >> "$GITHUB_ENV"
echo "WEBPAGE_BRANCH=$webpage_branch" >> "$GITHUB_ENV"

- name: Create main release PR
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "$RELEASE_BRANCH"
python .github/scripts/bump_version.py "$RELEASE_VERSION"
git add pyproject.toml src/sbx/__init__.py uv.lock
git add pyproject.toml src/sbx/__init__.py uv.lock README.md
git diff --cached --quiet && { echo "version files already match $RELEASE_VERSION" >&2; exit 1; }
counts="$(git diff --cached --numstat | awk '{ added += $1; deleted += $2 } END { print added+0 " " deleted+0 }')"
if [ "$counts" != "4 4" ]; then
echo "release PR should be exactly 4 insertions and 4 deletions; got $counts" >&2
exit 1
fi
git commit -m "Release $RELEASE_TAG"
git push origin "$RELEASE_BRANCH"
pr_url="$(gh pr create --base main --head "$RELEASE_BRANCH" --title "Release $RELEASE_TAG" --body "Bumps package files for $RELEASE_TAG.")"
pr_number="${pr_url##*/}"
echo "RELEASE_PR=$pr_number" >> "$GITHUB_ENV"
echo "Main release PR: $pr_url" >> "$GITHUB_STEP_SUMMARY"

- name: Create webpage release PR
run: |
git clone --branch webpage/main --single-branch "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" webpage-release
cd webpage-release
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "$WEBPAGE_BRANCH"
python ../.github/scripts/bump_webpage_version.py "$RELEASE_VERSION" index.html
git add index.html
git diff --cached --quiet && { echo "webpage already matches $RELEASE_VERSION" >&2; exit 1; }
git commit -m "Update website for $RELEASE_TAG"
git push origin "$WEBPAGE_BRANCH"
pr_url="$(gh pr create --repo "$GITHUB_REPOSITORY" --base webpage/main --head "$WEBPAGE_BRANCH" --title "Update website for $RELEASE_TAG" --body "Depends on #${RELEASE_PR}. Merge after $RELEASE_TAG exists.")"
echo "Webpage PR: $pr_url" >> "$GITHUB_STEP_SUMMARY"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,9 @@ Leave `version` blank to use the next patch version.

Release workflows:

- `.github/workflows/start-release.yml` (`Start release`): manual entry point; opens the package release PR and matching website PR.
- `.github/workflows/start-release.yml` (`Start release`): manual entry point; opens the package release PR, including the README install tag.
- `.github/workflows/release-pr-checks.yml` (`Release PR checks`): validates `release/v*` PRs only change version files.
- `.github/workflows/publish-release.yml` (`Publish release`): after the release PR is merged, creates the `v0.2.1` tag, GitHub release, and a PR bumping `main` to the next dev version, for example `0.2.2.dev0`.
- `.github/workflows/publish-release.yml` (`Publish release`): after the release PR is merged, creates the `v0.2.1` tag, GitHub release, website PR, and a PR bumping `main` to the next dev version, for example `0.2.2.dev0`.

## Notes

Expand Down
7 changes: 7 additions & 0 deletions tests/test_bump_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def test_bump_version_updates_project_files(
'name = "other"\nversion = "9.9.9"\n\nname = "sbx"\nversion = "0.1.0"\n',
encoding="utf-8",
)
(tmp_path / "README.md").write_text(
"uv tool install git+https://github.com/nueces/sbx.git@v0.1.0\n",
encoding="utf-8",
)
monkeypatch.setattr(bump_version, "ROOT", tmp_path)

bump_version.bump(version)
Expand All @@ -39,6 +43,9 @@ def test_bump_version_updates_project_files(
lock = (tmp_path / "uv.lock").read_text(encoding="utf-8")
assert 'name = "other"\nversion = "9.9.9"' in lock
assert f'name = "sbx"\nversion = "{version}"' in lock
readme = (tmp_path / "README.md").read_text(encoding="utf-8")
readme_version = "0.1.0" if ".dev" in version else version
assert f"git+https://github.com/nueces/sbx.git@v{readme_version}" in readme


@pytest.mark.parametrize("version", ["1.2", "v1.2.3", "1.2.3-rc1"])
Expand Down