diff --git a/.github/workflows/create-release-pr.yaml b/.github/workflows/create-release-pr.yaml new file mode 100644 index 00000000..c587a5dd --- /dev/null +++ b/.github/workflows/create-release-pr.yaml @@ -0,0 +1,66 @@ +name: Create Release PR + +on: + workflow_dispatch: + inputs: + bump: + description: 'Type of version bump' + required: true + type: choice + options: + - patch + - minor + - major + default: patch + +jobs: + create-release-pr: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout develop branch + uses: actions/checkout@v4 + with: + ref: develop + fetch-depth: 0 + token: ${{ secrets.GH_TOKEN }} + + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Configure Git + run: | + git config user.name "Deadbot0" + git config user.email "deadbot1101@gmail.com" + + - name: Compute next version + id: version + run: | + git fetch origin master + # Extract the clean MAJOR.MINOR.PATCH from master, ignoring develop's beta suffix + MASTER_VERSION=$(git show origin/master:pyproject.toml | grep -Po '(?<=version = ")[0-9]+\.[0-9]+\.[0-9]+') + + # Calculate the next version using our refactored script + NEXT_VERSION=$(python3 ./scripts/increment_version.py ${{ inputs.bump }} --base $MASTER_VERSION --print-only) + + echo "Next version: $NEXT_VERSION" + echo "version=$NEXT_VERSION" >> "$GITHUB_OUTPUT" + + - name: Create release branch + run: | + git checkout -b release/v${{ steps.version.outputs.version }} + git push origin release/v${{ steps.version.outputs.version }} + + - name: Create Pull Request + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + gh pr create \ + --base master \ + --head release/v${{ steps.version.outputs.version }} \ + --title "Release v${{ steps.version.outputs.version }}" \ + --body "Automated PR to release version ${{ steps.version.outputs.version }} into master. Merging this will trigger the version bump, build, and release pipeline." diff --git a/scripts/increment_version.py b/scripts/increment_version.py index db28497d..21616061 100644 --- a/scripts/increment_version.py +++ b/scripts/increment_version.py @@ -1,7 +1,7 @@ -import sys +import argparse import tomllib -from typing import TypedDict, Optional, Literal from pathlib import Path +from typing import Literal, TypedDict VERSION_DIR = Path('pyproject.toml') VERSION_FILE = Path('src/_version.py') @@ -11,55 +11,20 @@ class VersionInfo(TypedDict): major: int minor: int patch: int - beta: Optional[int] + beta: int | None IncrementType = Literal['major', 'minor', 'patch', 'beta'] -def increment_version(increment_type: IncrementType): - """Increments Deadbot version number - - Args: - increment_type: The type of increment being used - major, minor, patch, or beta - """ - version = read_version() - - match increment_type: - case 'major': - version['major'] += 1 - version['minor'] = 0 - version['patch'] = 0 - version['beta'] = 0 - - case 'minor': - version['minor'] += 1 - version['patch'] = 0 - version['beta'] = 0 - - case 'patch': - version['patch'] += 1 - version['beta'] = 0 - - case 'beta': - version['beta'] += 1 - - write_version(version) - return - - -def read_version() -> VersionInfo: - with open(VERSION_DIR, 'rb') as f: - pyproject_data = tomllib.load(f) - - version = pyproject_data['tool']['poetry']['version'] - version_components = version.split('-') +def parse_version_string(version_str: str) -> VersionInfo: + version_components = version_str.split('-') [major, minor, patch] = version_components[0].split('.') # no beta is more simply represented as "0" as beta starts at "1" beta = 0 if len(version_components) == 2: - beta = version_components[1].split('.')[1] + beta = int(version_components[1].split('.')[1]) return { 'major': int(major), @@ -69,11 +34,45 @@ def read_version() -> VersionInfo: } -def write_version(version: VersionInfo): - version_string = f'{version["major"]}.' f'{version["minor"]}.' f'{version["patch"]}' +def read_version() -> VersionInfo: + with open(VERSION_DIR, 'rb') as f: + pyproject_data = tomllib.load(f) + version = pyproject_data['tool']['poetry']['version'] + return parse_version_string(version) + + +def get_next_version(current: VersionInfo, increment_type: IncrementType) -> VersionInfo: + """Pure function that calculates the next version without side-effects.""" + next_version = current.copy() + + match increment_type: + case 'major': + next_version['major'] += 1 + next_version['minor'] = 0 + next_version['patch'] = 0 + next_version['beta'] = 0 + case 'minor': + next_version['minor'] += 1 + next_version['patch'] = 0 + next_version['beta'] = 0 + case 'patch': + next_version['patch'] += 1 + next_version['beta'] = 0 + case 'beta': + next_version['beta'] += 1 + return next_version + + +def format_version(version: VersionInfo) -> str: + version_string = f'{version["major"]}.{version["minor"]}.{version["patch"]}' if version['beta']: version_string += f'-beta.{version["beta"]}' + return version_string + + +def write_version(version: VersionInfo): + version_string = format_version(version) # Update pyproject.toml lines = [] @@ -93,11 +92,28 @@ def write_version(version: VersionInfo): ) +def increment_version(increment_type: IncrementType): + version = read_version() + next_version = get_next_version(version, increment_type) + write_version(next_version) + + if __name__ == '__main__': - increment_type = sys.argv[1] - valid_types = ['major', 'minor', 'patch', 'beta'] + parser = argparse.ArgumentParser(description='Increment Deadbot version') + parser.add_argument('increment_type', choices=['major', 'minor', 'patch', 'beta']) + parser.add_argument('--print-only', action='store_true', help='Print the next version number without writing to files') + parser.add_argument('--base', type=str, help='Base version string to increment (defaults to reading pyproject.toml)') + + args = parser.parse_args() + + if args.base: + current_version = parse_version_string(args.base) + else: + current_version = read_version() - if increment_type not in valid_types: - raise Exception(f'Invalid increment type "{increment_type}" - must be one of {valid_types}') + next_version = get_next_version(current_version, args.increment_type) - increment_version(increment_type) + if args.print_only: + print(format_version(next_version)) + else: + write_version(next_version)