diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb13efe6..748ff652 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,9 @@ name: CI (unit) +# pull_request only. A push trigger alongside it ran the whole suite twice on the same +# SHA, and the duplicate legs raced each other against the Stream app several SDK repos +# share. main and *.x are covered by release.yml, which runs this same reusable workflow +# as the gate before tagging. on: - push: - branches: [ "**" ] pull_request: branches: [ "**" ] diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml new file mode 100644 index 00000000..923683d8 --- /dev/null +++ b/.github/workflows/pr_title.yml @@ -0,0 +1,17 @@ +name: Lint PR title + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +permissions: + pull-requests: read + +jobs: + pr_title: + name: 👮 Conventional PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d17339b..5d694bb6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,112 +1,83 @@ name: Release on: + push: + branches: + - main + - '*.x' workflow_dispatch: inputs: - version_bump: - description: 'Version bump type for manual release' - required: true - default: 'patch' - type: choice - options: - - patch - - minor - - major - use_current_version: - description: 'Skip version bump and publish the version already set in pyproject.toml' + publish_tag: + description: 'Existing tag to (re)publish to PyPI, e.g. v6.1.1. Leave empty for a normal release run.' required: false - default: false - type: boolean - pull_request: - types: [closed] - branches: - - main - -concurrency: - group: release-${{ github.event.pull_request.base.ref || github.ref_name }} - cancel-in-progress: false + default: '' permissions: contents: read +concurrency: + group: release-${{ github.ref_name }} + cancel-in-progress: false + jobs: - prepare: - name: Prepare release - if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true + # Reversible half: keep the Release PR current. Never gated. + release-pr: + name: Release PR + if: >- + (github.event_name == 'push' || inputs.publish_tag == '') && + (github.ref_name == 'main' || endsWith(github.ref_name, '.x')) runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.release_meta_final.outputs.should_release }} - bump: ${{ steps.release_meta_final.outputs.bump }} - previous_version: ${{ steps.release_meta_final.outputs.previous_version }} - version: ${{ steps.release_meta_final.outputs.version }} - tag: ${{ steps.release_meta_final.outputs.tag }} + timeout-minutes: 5 + permissions: + contents: write + issues: write + pull-requests: write steps: - - uses: actions/checkout@v5 + - uses: googleapis/release-please-action@v4 with: - fetch-depth: 0 - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }} - - - name: Skip when PR is already released - id: already_released - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "value=false" >> "$GITHUB_OUTPUT" - else - # Idempotency is tracked on the release tag (annotated with the PR number - # in "Create release tag"), not on a default-branch commit: the version - # bump is no longer pushed to the protected default branch. - if git for-each-ref refs/tags --format='%(contents)' | grep -q "(pr #${{ github.event.pull_request.number }})"; then - echo "value=true" >> "$GITHUB_OUTPUT" - else - echo "value=false" >> "$GITHUB_OUTPUT" - fi - fi - - - name: Determine version bump (from PR metadata) - id: release_meta - if: github.event_name == 'pull_request' && steps.already_released.outputs.value != 'true' + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + target-branch: ${{ github.ref_name }} + skip-github-release: true + + # Tagging and the GitHub Release are irreversible, so the suite has to run before + # them, which means knowing a release is pending before the suite starts. The tag + # lands on the merged Release PR's merge commit, not on this branch's tip, so that + # commit is also what gets tested. + detect: + name: Detect pending release + if: >- + (github.event_name == 'push' || inputs.publish_tag == '') && + (github.ref_name == 'main' || endsWith(github.ref_name, '.x')) + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + pull-requests: read + outputs: + pending: ${{ steps.find.outputs.pending }} + sha: ${{ steps.find.outputs.sha }} + steps: + - name: Find a merged Release PR waiting to be tagged + id: find env: - PR_TITLE: ${{ github.event.pull_request.title }} - run: | - python3 scripts/release/bump_version.py \ - --title "$PR_TITLE" \ - --output "$GITHUB_OUTPUT" - - - name: Determine version bump (manual) - id: release_meta_manual - if: github.event_name == 'workflow_dispatch' + GH_TOKEN: ${{ github.token }} run: | - python3 scripts/release/bump_version.py \ - --manual-bump "${{ github.event.inputs.version_bump }}" \ - --use-current-version "${{ github.event.inputs.use_current_version }}" \ - --output "$GITHUB_OUTPUT" - - - name: Consolidate release metadata - id: release_meta_final - run: | - if [ "${{ steps.already_released.outputs.value }}" = "true" ]; then - echo "should_release=false" >> "$GITHUB_OUTPUT" - echo "bump=none" >> "$GITHUB_OUTPUT" + sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls?state=closed&base=${GITHUB_REF_NAME}&sort=updated&direction=desc&per_page=50" \ + --jq '[.[] | select(.merged_at != null and ([.labels[].name] | index("autorelease: pending")))] | .[0].merge_commit_sha // empty')" + if [ -z "$sha" ]; then + echo "No pending release." + echo "pending=false" >> "$GITHUB_OUTPUT" exit 0 fi - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "should_release=${{ steps.release_meta_manual.outputs.should_release }}" >> "$GITHUB_OUTPUT" - echo "bump=${{ steps.release_meta_manual.outputs.bump }}" >> "$GITHUB_OUTPUT" - echo "previous_version=${{ steps.release_meta_manual.outputs.previous_version }}" >> "$GITHUB_OUTPUT" - echo "version=${{ steps.release_meta_manual.outputs.version }}" >> "$GITHUB_OUTPUT" - echo "tag=${{ steps.release_meta_manual.outputs.tag }}" >> "$GITHUB_OUTPUT" - else - echo "should_release=${{ steps.release_meta.outputs.should_release }}" >> "$GITHUB_OUTPUT" - echo "bump=${{ steps.release_meta.outputs.bump }}" >> "$GITHUB_OUTPUT" - echo "previous_version=${{ steps.release_meta.outputs.previous_version }}" >> "$GITHUB_OUTPUT" - echo "version=${{ steps.release_meta.outputs.version }}" >> "$GITHUB_OUTPUT" - echo "tag=${{ steps.release_meta.outputs.tag }}" >> "$GITHUB_OUTPUT" - fi + echo "Pending release will be tagged at $sha." + echo "pending=true" >> "$GITHUB_OUTPUT" + echo "sha=$sha" >> "$GITHUB_OUTPUT" test-unit: name: Test (unit) - needs: prepare - if: needs.prepare.outputs.should_release == 'true' + needs: detect + if: needs.detect.outputs.pending == 'true' uses: ./.github/workflows/run_tests.yml with: marker: 'not integration' @@ -114,84 +85,89 @@ jobs: test-integration: name: Test (integration) - needs: prepare - if: needs.prepare.outputs.should_release == 'true' + needs: detect + if: needs.detect.outputs.pending == 'true' uses: ./.github/workflows/run_tests.yml with: marker: 'integration' secrets: inherit + # Irreversible half. release: - name: 🚀 Release - needs: [prepare, test-unit, test-integration] - if: needs.prepare.outputs.should_release == 'true' + name: 🚀 Tag and release + needs: [detect, test-unit, test-integration] + if: needs.detect.outputs.pending == 'true' runs-on: ubuntu-latest - environment: pypi + timeout-minutes: 5 permissions: contents: write - id-token: write + issues: write + pull-requests: write + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }} - - - uses: ./.github/actions/python-uv-setup - - - name: Apply version bump + # The suite ran against this workflow's own commit, while the tag lands on the + # merged Release PR's merge commit. They are the same commit on the path that + # gets here, a push of that merge. They diverge on a dispatch after the branch + # has moved, which would tag a tree nothing tested, so refuse instead. Recovery + # is "Re-run failed jobs" on the run for the merge itself. + - name: Refuse to tag a commit the suite did not run on env: - VERSION: ${{ needs.prepare.outputs.version }} + TESTED: ${{ github.sha }} + PENDING: ${{ needs.detect.outputs.sha }} run: | - python3 - <<'PY' - import os, re - from pathlib import Path - version = os.environ['VERSION'] - path = Path('pyproject.toml') - text = path.read_text(encoding='utf-8') - pattern = re.compile(r'^version\s*=\s*"\d+\.\d+\.\d+"\s*$', re.MULTILINE) - new_text, count = pattern.subn(f'version = "{version}"', text, count=1) - if count == 0: - raise SystemExit('Could not update version line in pyproject.toml') - path.write_text(new_text, encoding='utf-8') - PY - - # Commit the bump locally only, so the release tag points at a tree with the - # correct version. It is intentionally NOT pushed to the protected default - # branch (which rejects direct pushes). Versioning is driven by tags, not by - # pyproject.toml on the default branch (see bump_version.py: find_latest_semver_tag). - - name: Commit version bump (local, for tagging only) - env: - VERSION: ${{ needs.prepare.outputs.version }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add pyproject.toml - if git diff --cached --quiet; then - echo "No version changes to commit." - else - git commit -m "chore(release): v${VERSION}" + if [ "$TESTED" != "$PENDING" ]; then + echo "::error::The pending release is tagged at $PENDING but this run tested $TESTED." + echo "::error::Re-run the workflow run for $PENDING instead of dispatching from the branch tip." + exit 1 fi + echo "Tagging $PENDING, which is the commit the suite ran on." - - name: Create release tag + - uses: googleapis/release-please-action@v4 + id: release + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + target-branch: ${{ github.ref_name }} + skip-github-pull-request: true + + # Chained rather than triggered on the release event, because a GitHub Release + # created with GITHUB_TOKEN starts no new workflow run. Also reachable on its own + # through workflow_dispatch with publish_tag, which is the recovery path once + # GitHub has retired the original run and "Re-run failed jobs" is gone. + publish: + name: 📦 Publish to PyPI + needs: release + if: >- + !cancelled() && + (inputs.publish_tag != '' || needs.release.outputs.release_created == 'true') + runs-on: ubuntu-latest + timeout-minutes: 15 + # PyPI Trusted Publishing is bound to this workflow file name and this environment. + environment: pypi + permissions: + contents: read + id-token: write + steps: + - name: Resolve tag + id: target env: - TAG: ${{ needs.prepare.outputs.tag }} - PR: ${{ github.event.pull_request.number }} + PUBLISH_TAG: ${{ inputs.publish_tag }} + RELEASE_TAG: ${{ needs.release.outputs.tag_name }} run: | - if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then - echo "Tag ${TAG} already exists; skipping tag creation." - exit 0 - fi - # Annotate the tag with the trigger; the PR number is used by the - # "Skip when PR is already released" idempotency check. - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - git tag -a "${TAG}" -m "chore(release): ${TAG} (manual)" - else - git tag -a "${TAG}" -m "chore(release): ${TAG} (pr #${PR})" - fi - git push origin "${TAG}" + tag="${PUBLISH_TAG:-$RELEASE_TAG}" + echo "tag=${tag}" >> "$GITHUB_OUTPUT" + echo "version=${tag#v}" >> "$GITHUB_OUTPUT" - - name: Clean dist directory - run: rm -rf dist + - uses: actions/checkout@v5 + with: + ref: ${{ steps.target.outputs.tag }} + # uv build and uv publish execute project and dependency code, and nothing + # here writes to the repository, so do not leave GITHUB_TOKEN in .git/config. + persist-credentials: false + + - uses: ./.github/actions/python-uv-setup - name: Build distributions run: uv build @@ -199,25 +175,9 @@ jobs: - name: Publish to PyPI (Trusted Publishing) run: uv publish - - name: Create release on GitHub - uses: ncipollo/release-action@v1 - with: - tag: ${{ needs.prepare.outputs.tag }} - token: ${{ secrets.GITHUB_TOKEN }} - skipIfReleaseExists: true - body: | - Release v${{ needs.prepare.outputs.version }} - - - Bump type: `${{ needs.prepare.outputs.bump }}` - - Previous: `${{ needs.prepare.outputs.previous_version }}` - - Next: `${{ needs.prepare.outputs.version }}` - - Trigger: `${{ github.event_name }}` - - Install with: `pip install getstream==${{ needs.prepare.outputs.version }}` - - name: Verify pip install env: UV_NO_SOURCES: "1" run: | - uv pip install "getstream==${{ needs.prepare.outputs.version }}" || \ + uv pip install "getstream==${{ steps.target.outputs.version }}" || \ echo "WARNING: pip install verification failed (PyPI index may need a moment to propagate)" diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..8ace9152 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "6.1.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 72cace50..bdcbfcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,44 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +Entries from 6.1.1 on are generated by release-please from commit messages; earlier +ones were written by hand in the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +format. -## [Unreleased] +## [6.1.0] - 2026-09-09 + +### Changed + +- Regenerated the OpenAPI client (FEEDS-1763.1). + +## [6.0.0] - 2026-09-03 + +### Changed + +- **Breaking:** regenerated the OpenAPI client (FEEDS-1830). + +### Fixed + +- Scoped the `F401`/`UP035` lint suppressions to the generated models module. + +## [5.1.0] - 2026-08-25 + +### Changed + +- Regenerated the OpenAPI client (CHA-4947). + +## [5.0.0] - 2026-08-17 + +### Changed + +- **Breaking:** regenerated from chat v235.17.1 (#281). + +## [4.3.0] - 2026-08-12 + +### Fixed + +- Name the HTTP status when the error body is not JSON (#280). ## [4.2.0] - 2026-07-24 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index c3d02d84..d41ddd09 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -68,16 +68,12 @@ handle the video/non-video test split or manual test exclusions. Prefer `make` t ## Release -Create a new release on Github, CI handles the rest. If you do need to do it manually follow these instructions: +Releases are generated by release-please and published by CI. See the Releases +section of `README.md`. -``` -rm -rf dist -git tag v0.0.15 -uv run hatch version # this should show the right version -git push origin main --tags -uv build --all -uv publish -``` +Do not tag or publish by hand. A hand-pushed `vX.Y.Z` tag collides with +release-please's own tagging and leaves `.release-please-manifest.json` behind the +released version. ## OpenAPI & Protobuf diff --git a/README.md b/README.md index cd95dee2..642a3054 100644 --- a/README.md +++ b/README.md @@ -257,20 +257,39 @@ uv run pytest tests/test_video.py::test_specific_function -v ## Releases -Releases use two paths: - -- **Default**: automatic release when a PR is merged to `main`. The PR title (and body) drives the semver bump. -- **Fallback**: manual release via the `Release` workflow's `workflow_dispatch` (admin use). Select a `version_bump` (`patch`/`minor`/`major`). `use_current_version=true` skips the bump and publishes whatever is already in `pyproject.toml`. - -Automatic semver bump rules: - -- `feat:` -> minor -- `fix:` (or `bug:`) -> patch -- `feat!:`, `(scope)!:`, or `BREAKING CHANGE` in the PR body/title -> major - -PRs with any other prefix do not trigger a release. - -The release pipeline runs lint, type-check, and the full test matrix (unit + integration, across all supported Python versions) on the merged commit before publishing to PyPI via Trusted Publishing (OIDC). Each step in the publish job is idempotent: a failed run can be re-dispatched from the Actions UI. +Releases are driven by [release-please](https://github.com/googleapis/release-please). + +- Merge PRs to `main` with conventional-commit titles. The repo is squash-only with + `squash_merge_commit_title: PR_TITLE`, so the PR title becomes the commit subject and + decides the next version: `feat:` is a minor, `fix:` and `perf:` are a patch, `feat!:` + or `(scope)!:` is a major. Other types (`chore`, `ci`, `docs`, `test`, + `refactor`) ship nothing. Both settings are load-bearing: release-please reads commit + messages, never PR titles, and a merge commit's subject is not conventional. +- release-please keeps a Release PR open with the version bump in `pyproject.toml`, + `uv.lock` and `CHANGELOG.md`. It is opened by `github-actions[bot]`, so approve it and + run its held checks like any other PR. +- Merging the Release PR runs lint, type-check and the unit and integration matrix on + that merge commit, which is the commit the tag will point at. Only if that is green + does the workflow create the tag and the GitHub Release and publish to PyPI via + Trusted Publishing (OIDC). The order matters: a tag and a GitHub Release cannot be + withdrawn, a failed publish can be retried. If a later dispatch would tag a commit + this run did not test, it fails rather than tagging it. + +To retry a publish that failed after the release was tagged, use "Re-run failed jobs" on +that workflow run. Once GitHub has retired the run, dispatch `Release` from `main` with +`publish_tag` set to the tag (for example `v6.1.1`), which builds and publishes that tag +without touching release-please. + +To force a specific version, type `Release-As: X.Y.Z` in the commit message box of the +squash dialog when merging a PR; the PR description is not copied there. To hotfix while +`main` carries unreleased work, branch `N.x` from the last tag, cherry-pick the fix, and +merge the Release PR that release-please opens against that branch. + +`last-release-sha` in `release-please-config.json` is temporary. `v6.1.0` sits on a bump +commit the previous workflow created off-branch and never pushed, so release-please +cannot reach it by walking `main` and would otherwise treat the whole history as +unreleased. Delete the key once a release-please-created release exists on `main`; the +walk stops at that release commit before it reaches the pin. ## License diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..78421d99 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "last-release-sha": "242d57c68423e13e55a9ae7c3447622127541ab0", + "packages": { + ".": { + "release-type": "python", + "include-component-in-tag": false, + "extra-files": [ + { + "type": "toml", + "path": "uv.lock", + "jsonpath": "$.package[?(@.name.value=='getstream')].version" + } + ] + } + } +} diff --git a/scripts/release/bump_version.py b/scripts/release/bump_version.py deleted file mode 100755 index bdc0d40e..00000000 --- a/scripts/release/bump_version.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python3 -"""Compute the next release version from PR title/body or workflow_dispatch input. - -Usage: - scripts/release/bump_version.py \ - --title "feat: add x" --body-file "$PR_BODY_FILE" \ - --output "$GITHUB_OUTPUT" - - scripts/release/bump_version.py \ - --manual-bump patch \ - --output "$GITHUB_OUTPUT" - - scripts/release/bump_version.py \ - --manual-bump patch --use-current-version true \ - --output "$GITHUB_OUTPUT" - -Outputs (written to --output or stdout) follow the convention shared with the -getstream-php / getstream-ruby / getstream-net / getstream-go / stream-sdk-java -bump scripts: - - should_release=true|false - bump=major|minor|patch|none - previous_version=X.Y.Z - version=X.Y.Z - tag=vX.Y.Z - -Side effect: in auto and manual-with-bump modes, updates the `version` field in -the top-level [project] table of pyproject.toml. -""" - -from __future__ import annotations - -import argparse -import re -import subprocess -import sys -from pathlib import Path - -PYPROJECT_PATH = Path("pyproject.toml") -SEMVER_PATTERN = re.compile(r"^\d+\.\d+\.\d+$") -VERSION_LINE_PATTERN = re.compile( - r'^version\s*=\s*"(\d+\.\d+\.\d+)"\s*$', - re.MULTILINE, -) - - -def run(cmd: list[str]) -> str: - result = subprocess.run(cmd, check=False, capture_output=True, text=True) - return result.stdout.strip() - - -def find_latest_semver_tag() -> str: - raw = run(["git", "tag", "--list"]) - if not raw: - return "0.0.0" - versions: list[tuple[int, int, int]] = [] - for line in raw.splitlines(): - candidate = line.strip().lstrip("v") - if SEMVER_PATTERN.match(candidate): - major, minor, patch = candidate.split(".") - versions.append((int(major), int(minor), int(patch))) - if not versions: - return "0.0.0" - versions.sort() - return "{}.{}.{}".format(*versions[-1]) - - -def determine_bump_type(title: str) -> str: - # Breaking changes are signalled only by the `!` marker in the title - # (e.g. `feat!:`). Free-text body/title prose is not trusted: a PR that - # merely mentions "BREAKING CHANGE" must not force a major bump. - title = title.strip() - match = re.match(r"^([a-zA-Z]+)(\([^)]+\))?(!)?:", title) - if not match: - return "none" - if match.group(3) == "!": - return "major" - type_ = match.group(1).lower() - if type_ == "feat": - return "minor" - if type_ in {"fix", "bug"}: - return "patch" - return "none" - - -def increment_version(current: str, bump: str) -> str: - major, minor, patch = (int(x) for x in current.split(".")) - if bump == "major": - return f"{major + 1}.0.0" - if bump == "minor": - return f"{major}.{minor + 1}.0" - if bump == "patch": - return f"{major}.{minor}.{patch + 1}" - return current - - -def read_pyproject_version(path: Path) -> str: - text = path.read_text(encoding="utf-8") - match = VERSION_LINE_PATTERN.search(text) - if not match: - raise RuntimeError( - 'Could not find a static `version = "X.Y.Z"` line in pyproject.toml' - ) - return match.group(1) - - -def update_pyproject_version(path: Path, version: str) -> None: - text = path.read_text(encoding="utf-8") - new_text, count = VERSION_LINE_PATTERN.subn(f'version = "{version}"', text, count=1) - if count == 0: - raise RuntimeError( - 'Could not update version line in pyproject.toml (expected `version = "X.Y.Z"`)' - ) - path.write_text(new_text, encoding="utf-8") - - -def parse_bool(value: str) -> bool: - return value.strip().lower() == "true" - - -def write_outputs(output_path: str, entries: dict[str, str]) -> None: - if not output_path: - for key, value in entries.items(): - print(f"{key}={value}") - return - with open(output_path, "a", encoding="utf-8") as fh: - for key, value in entries.items(): - fh.write(f"{key}={value}\n") - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--title", default="") - parser.add_argument("--output", default="") - parser.add_argument("--manual-bump", dest="manual_bump", default="") - parser.add_argument( - "--use-current-version", - dest="use_current_version", - default="false", - ) - args = parser.parse_args() - - manual = args.manual_bump.strip().lower() - use_current = parse_bool(args.use_current_version) - - if manual: - if manual not in {"major", "minor", "patch"}: - print("manual-bump must be one of: major, minor, patch", file=sys.stderr) - return 1 - previous = find_latest_semver_tag() - if use_current: - next_version = read_pyproject_version(PYPROJECT_PATH) - else: - next_version = increment_version(previous, manual) - update_pyproject_version(PYPROJECT_PATH, next_version) - write_outputs( - args.output, - { - "should_release": "true", - "bump": manual, - "previous_version": previous, - "version": next_version, - "tag": f"v{next_version}", - }, - ) - return 0 - - bump = determine_bump_type(args.title) - if bump == "none": - write_outputs( - args.output, - { - "should_release": "false", - "bump": "none", - }, - ) - return 0 - - previous = find_latest_semver_tag() - next_version = increment_version(previous, bump) - update_pyproject_version(PYPROJECT_PATH, next_version) - - write_outputs( - args.output, - { - "should_release": "true", - "bump": bump, - "previous_version": previous, - "version": next_version, - "tag": f"v{next_version}", - }, - ) - return 0 - - -if __name__ == "__main__": - sys.exit(main())