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
14 changes: 2 additions & 12 deletions .github/workflows/bump-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,6 @@ jobs:
- name: Install commitizen
run: uv tool install commitizen

- name: Configure git
- name: Bump version for ${{ inputs.package_name }}
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"

- name: Create bump and changelog for ${{ inputs.package_name }}
run: |
cd ${{ inputs.package_dir }}
echo "Bumping version for ${{ inputs.package_name }} package..."
uv run cz bump --changelog --yes

# Push changes
git push origin main --follow-tags
uv run python scripts/bump_package.py "${{ inputs.package_name }}" "${{ inputs.package_dir }}"
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
strategy:
matrix:
package: ${{ fromJson(needs.detect-changes.outputs.changed-packages) }}
max-parallel: 1 # Process packages sequentially to avoid race conditions
uses: ./.github/workflows/bump-package.yml
with:
package_name: ${{ matrix.package.package_name }}
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ on:
jobs:
detect-package:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
package: ${{ steps.detect.outputs.package }}
version: ${{ steps.detect.outputs.version }}
package-dir: ${{ steps.detect.outputs.package-dir }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Python
uses: actions/setup-python@v5
Expand Down Expand Up @@ -53,10 +57,13 @@ jobs:
needs: [detect-package]
environment: pypi-release
permissions:
contents: read
id-token: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Python
uses: actions/setup-python@v5
Expand All @@ -71,7 +78,7 @@ jobs:

- name: Build package
run: |
uv build --package ${PACKAGE}
uv build --package ${{ needs.detect-package.outputs.package }}

- name: Publish to PyPI
run: |
Expand Down
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ changelog-preview BASE_BRANCH="origin/main":
preview-versions FORMAT="markdown":
uv run python scripts/version_preview.py --format {{FORMAT}}

# Bump version for a specific package
bump-package PACKAGE_NAME PACKAGE_DIR:
uv run python scripts/bump_package.py {{PACKAGE_NAME}} {{PACKAGE_DIR}}

# Detect packages with unreleased changes
detect-changes:
uv run python scripts/changelog.py changes --output-format github
Expand Down
200 changes: 200 additions & 0 deletions scripts/bump_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#!/usr/bin/env python3
"""
Bump package version script.

This script handles version bumping for a specific package using commitizen,
including retry logic for pushing changes to avoid race conditions.
"""

import argparse
import subprocess
import sys
import time
from pathlib import Path


def run_command(cmd: list[str], cwd: str | None = None) -> tuple[int, str, str]:
"""Run a command and return exit code, stdout, and stderr."""
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
cwd=cwd,
check=False
)
return result.returncode, result.stdout.strip(), result.stderr.strip()
except Exception as e:
return 1, "", str(e)


def configure_git() -> None:
"""Configure git for automated commits."""
print("Configuring git...")
run_command(["git", "config", "--local", "user.email", "action@github.com"])
run_command(["git", "config", "--local", "user.name", "GitHub Action"])


def pull_latest_changes() -> bool:
"""Pull latest changes from origin/main."""
print("Pulling latest changes from origin/main...")
exit_code, stdout, stderr = run_command(["git", "pull", "origin", "main"])

if exit_code != 0:
print(f"Failed to pull latest changes: {stderr}")
return False

print("Successfully pulled latest changes")
return True


def run_bump(package_dir: str, package_name: str) -> bool:
"""Run commitizen bump in the specified package directory."""
print(f"Running version bump for {package_name} in {package_dir}...")

exit_code, stdout, stderr = run_command(
["uv", "run", "cz", "bump", "--changelog", "--yes"],
cwd=package_dir
)

if exit_code != 0:
print(f"Failed to bump version: {stderr}")
return False

print("Version bump completed successfully")
print(stdout)
return True


def push_changes_with_retry(max_attempts: int = 3) -> bool:
"""Push changes to origin/main with retry logic."""
# First, let's check what tags were created
exit_code, stdout, stderr = run_command(["git", "tag", "--list", "--sort=-version:refname"])
if exit_code == 0 and stdout:
print(f"Local tags found: {stdout}")
# Show the most recent tag
recent_tags = stdout.split('\n')[:3]
print(f"Most recent tags: {recent_tags}")

for attempt in range(1, max_attempts + 1):
print(f"Attempting to push changes (attempt {attempt}/{max_attempts})...")

exit_code, stdout, stderr = run_command(
["git", "push", "origin", "main", "--follow-tags"]
)

if exit_code == 0:
print(f"Successfully pushed changes on attempt {attempt}")

# Explicitly push tags to ensure they're uploaded
print("Explicitly pushing tags...")
tag_exit_code, tag_stdout, tag_stderr = run_command(
["git", "push", "origin", "--tags"]
)

if tag_exit_code == 0:
print("Successfully pushed tags")
else:
print(f"Warning: Failed to push tags: {tag_stderr}")
# Don't fail the whole process for tag push failure

return True

print(f"Push failed on attempt {attempt}: {stderr}")

if attempt < max_attempts:
print("Pulling latest changes and retrying...")

# Pull with rebase to handle any new commits
exit_code, stdout, stderr = run_command(
["git", "pull", "origin", "main", "--rebase"]
)

if exit_code != 0:
print(f"Failed to pull with rebase: {stderr}")
continue

print("Waiting 2 seconds before retry...")
time.sleep(2)
else:
print(f"Failed to push after {max_attempts} attempts")
return False

return False


def bump_package(package_name: str, package_dir: str) -> bool:
"""
Bump version for a specific package.

Args:
package_name: Name of the package (e.g., keycardai-oauth)
package_dir: Directory path of the package (e.g., packages/oauth)

Returns:
True if successful, False otherwise
"""
print(f"Starting version bump for {package_name} package...")

# Ensure package directory exists
if not Path(package_dir).exists():
print(f"Error: Package directory {package_dir} does not exist")
return False

# Configure git for automated commits
configure_git()

# Pull latest changes to avoid conflicts
if not pull_latest_changes():
return False

# Run the version bump
if not run_bump(package_dir, package_name):
return False

# Push changes with retry logic
if not push_changes_with_retry():
return False

print(f"Successfully completed version bump for {package_name}")
return True


def main():
"""Main function with argument parsing."""
parser = argparse.ArgumentParser(
description="Bump package version using commitizen"
)
parser.add_argument(
"package_name",
help="Name of the package to bump (e.g., keycardai-oauth)"
)
parser.add_argument(
"package_dir",
help="Directory of the package (e.g., packages/oauth)"
)
parser.add_argument(
"--max-retry-attempts",
type=int,
default=3,
help="Maximum number of push retry attempts (default: 3)"
)

args = parser.parse_args()

# Set max retry attempts globally
global MAX_RETRY_ATTEMPTS
MAX_RETRY_ATTEMPTS = args.max_retry_attempts

# Run the bump
success = bump_package(args.package_name, args.package_dir)

if not success:
print("Version bump failed")
sys.exit(1)

print("Version bump completed successfully")


if __name__ == "__main__":
main()