From dc63d3f48718477e0fa9fc4ef2d1c40d3865b8db Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Mon, 14 Sep 2026 12:44:16 +0200 Subject: [PATCH 1/3] ci: replace the PR-title bump script with release-please The bump script derived the version from the merged PR title, committed version.rb locally so the tag pointed at a correct tree, and pushed the tag without ever updating the default branch. lib/getstream_ruby/version.rb on master therefore says 8.0.2 while RubyGems is at 12.1.0. release-please keeps the bump in a reviewable Release PR instead, so version.rb follows the release. Gemfile.lock is gitignored here, so the ruby strategy's Gemfile.lock updater is a no-op. include-component-in-tag keeps the tag as vX.Y.Z: the ruby strategy sets tagSeparator to "/", so a component would make it getstream-ruby/v12.0.1. bootstrap is via last-release-sha, the master commit v12.1.0 was released from. The tag itself sits on the bump commit the old workflow created off-branch, so release-please cannot find it by walking master. The tag, the GitHub Release and the gem push are all irreversible, so the suite now runs before them: a detect job finds the merged Release PR waiting to be tagged and the release job refuses to tag a commit the suite did not run on. The test jobs move to a reusable run_tests.yml that both ci.yml and the release gate call, and ci.yml drops its push trigger. Running on push and pull_request raced the duplicate integration legs against the Stream app several SDK repos share. Also deletes the hand-bump paths that the auto-release made unsafe: scripts/version-bump.sh and the Makefile patch/minor/major targets. --- .github/workflows/ci.yml | 136 +------------- .github/workflows/pr_title.yml | 17 ++ .github/workflows/release.yml | 322 +++++++++++++------------------- .github/workflows/run_tests.yml | 114 +++++++++++ .release-please-manifest.json | 3 + CHANGELOG.md | 36 +++- Makefile | 10 - README.md | 47 +++-- release-please-config.json | 12 ++ scripts/release/bump_version.rb | 143 -------------- scripts/version-bump.sh | 87 --------- 11 files changed, 356 insertions(+), 571 deletions(-) create mode 100644 .github/workflows/pr_title.yml create mode 100644 .github/workflows/run_tests.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json delete mode 100755 scripts/release/bump_version.rb delete mode 100755 scripts/version-bump.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88d12da0..14985d13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,139 +1,17 @@ name: CI +# pull_request only. A push trigger alongside it ran the whole suite twice on the same +# SHA, and the duplicate integration legs raced each other against the Stream app +# several SDK repos share. master is covered by release.yml, which runs this same +# reusable workflow as the gate before tagging. on: - push: - branches: [ master, main ] pull_request: branches: [ master, main ] -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }} - cancel-in-progress: true - permissions: contents: read jobs: - unit: - name: Unit Tests & Code Quality - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1.0' - - - name: Install dependencies - run: bundle install --jobs 4 --retry 3 - - - name: Run unit tests - run: make test - - - name: Run code quality checks - run: | - make format-check - make lint - make security - - integration-chat: - name: Chat Integration Tests - runs-on: ubuntu-latest - environment: ci - if: github.event_name == 'push' || github.event_name == 'pull_request' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1.0' - - - name: Install dependencies - run: bundle install --jobs 4 --retry 3 - - - name: Run chat integration tests - env: - STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} - STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} - STREAM_BASE_URL: ${{ vars.STREAM_BASE_URL }} - run: make test-integration-chat - - integration-feed: - name: Feed Integration Tests - runs-on: ubuntu-latest - environment: ci - if: github.event_name == 'push' || github.event_name == 'pull_request' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1.0' - - - name: Install dependencies - run: bundle install --jobs 4 --retry 3 - - - name: Run feed integration tests - env: - STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} - STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} - STREAM_BASE_URL: ${{ vars.STREAM_BASE_URL }} - run: make test-integration-feed - - integration-video: - name: Video Integration Tests - runs-on: ubuntu-latest - environment: ci - if: github.event_name == 'push' || github.event_name == 'pull_request' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1.0' - - - name: Install dependencies - run: bundle install --jobs 4 --retry 3 - - - name: Run video integration tests - env: - STREAM_API_KEY: ${{ vars.STREAM_VIDEO_API_KEY }} - STREAM_API_SECRET: ${{ secrets.STREAM_VIDEO_API_SECRET }} - STREAM_BASE_URL: ${{ vars.STREAM_VIDEO_BASE_URL }} - run: make test-integration-video - - integration-gcp-lb: - name: GCP load balancer keep-alive - runs-on: ubuntu-latest - environment: ci - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1.0' - - - name: Install dependencies - run: bundle install --jobs 4 --retry 3 - - - name: Run GCP keep-alive integration test - env: - STREAM_API_KEY: ${{ vars.STREAM_GCP_API_KEY || vars.STREAM_API_KEY }} - STREAM_API_SECRET: ${{ secrets.STREAM_GCP_API_SECRET || secrets.STREAM_API_SECRET }} - STREAM_BASE_URL: ${{ vars.STREAM_GCP_BASE_URL }} - run: make test-integration-gcp-lb + tests: + uses: ./.github/workflows/run_tests.yml + secrets: inherit 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 b316bba1..22aed1f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,217 +1,165 @@ name: Release on: + push: + branches: + - master + - '*.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 lib/getstream_ruby/version.rb' + publish_tag: + description: 'Existing tag to (re)publish to RubyGems, e.g. v12.1.1. Leave empty for a normal release run.' required: false - default: false - type: boolean - pull_request: - types: [closed] - branches: - - main - - master + default: '' + +permissions: + contents: read concurrency: - group: release-${{ github.event.pull_request.base.ref || github.ref_name }} + group: release-${{ github.ref_name }} cancel-in-progress: false -permissions: - contents: write - jobs: - release: - name: 🚀 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 == 'master' || endsWith(github.ref_name, '.x')) runs-on: ubuntu-latest - environment: ci + timeout-minutes: 5 + permissions: + contents: write + issues: write + pull-requests: write steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }} - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 + - uses: googleapis/release-please-action@v4 with: - ruby-version: '3.1.0' - - - name: Install dependencies - run: bundle install --jobs 4 --retry 3 - - - 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, the GitHub Release and the gem push are irreversible, so the suite has to + # run before them, which means knowing a release is pending before the suite starts. + detect: + name: Detect pending release + if: >- + (github.event_name == 'push' || inputs.publish_tag == '') && + (github.ref_name == 'master' || 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: | - ruby scripts/release/bump_version.rb \ - --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: | - ruby scripts/release/bump_version.rb \ - --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 - - - name: Stop when release is not required - if: steps.release_meta_final.outputs.should_release != 'true' - run: | - if [ "${{ steps.already_released.outputs.value }}" = "true" ]; then - echo "PR #${{ github.event.pull_request.number }} is already released; skipping." - elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "Manual release was not requested; skipping." - else - echo "No release type found in PR title/body; skipping." - fi - - - name: Run lint and security checks - if: steps.release_meta_final.outputs.should_release == 'true' - run: | - make format-check - make lint - make security - - - name: Run unit tests - if: steps.release_meta_final.outputs.should_release == 'true' - run: make test - - - name: Run chat integration tests - if: steps.release_meta_final.outputs.should_release == 'true' - env: - STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} - STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} - STREAM_BASE_URL: ${{ vars.STREAM_BASE_URL }} - run: make test-integration-chat - - - name: Run feed integration tests - if: steps.release_meta_final.outputs.should_release == 'true' - env: - STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} - STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} - STREAM_BASE_URL: ${{ vars.STREAM_BASE_URL }} - run: make test-integration-feed - - - name: Run video integration tests - if: steps.release_meta_final.outputs.should_release == 'true' - env: - STREAM_API_KEY: ${{ vars.STREAM_VIDEO_API_KEY }} - STREAM_API_SECRET: ${{ secrets.STREAM_VIDEO_API_SECRET }} - STREAM_BASE_URL: ${{ vars.STREAM_VIDEO_BASE_URL }} - run: make test-integration-video - - # 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 - # version.rb on the default branch (see bump_version.rb: find_latest_semver_tag). - - name: Commit version files (local, for tagging only) - if: steps.release_meta_final.outputs.should_release == 'true' + echo "Pending release will be tagged at $sha." + echo "pending=true" >> "$GITHUB_OUTPUT" + echo "sha=$sha" >> "$GITHUB_OUTPUT" + + tests: + name: Tests + needs: detect + if: needs.detect.outputs.pending == 'true' + uses: ./.github/workflows/run_tests.yml + secrets: inherit + + # Irreversible half. + release: + name: 🚀 Tag and release + needs: [detect, tests] + if: needs.detect.outputs.pending == 'true' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + issues: write + pull-requests: write + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + # 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: ${{ steps.release_meta_final.outputs.version }} + TESTED: ${{ github.sha }} + PENDING: ${{ needs.detect.outputs.sha }} run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add lib/getstream_ruby/version.rb - 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 - if: steps.release_meta_final.outputs.should_release == 'true' + - 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 RubyGems + needs: release + if: >- + !cancelled() && + (inputs.publish_tag != '' || needs.release.outputs.release_created == 'true') + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - name: Resolve tag + id: target env: - TAG: ${{ steps.release_meta_final.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" + + - uses: actions/checkout@v4 + with: + ref: ${{ steps.target.outputs.tag }} + # gem build runs the gemspec, which is project code, and nothing here writes + # to the repository, so do not leave GITHUB_TOKEN in .git/config. + persist-credentials: false + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1.0' - name: Build and publish gem - if: steps.release_meta_final.outputs.should_release == 'true' env: GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }} - VERSION: ${{ steps.release_meta_final.outputs.version }} + VERSION: ${{ steps.target.outputs.version }} run: | gem build getstream-ruby.gemspec - gem push "getstream-ruby-${VERSION}.gem" --key "$GEM_HOST_API_KEY" - - - name: Create release on GitHub - if: steps.release_meta_final.outputs.should_release == 'true' - uses: ncipollo/release-action@v1 - with: - tag: ${{ steps.release_meta_final.outputs.tag }} - token: ${{ secrets.GITHUB_TOKEN }} - skipIfReleaseExists: true - body: | - Release v${{ steps.release_meta_final.outputs.version }} - - - Bump type: `${{ steps.release_meta_final.outputs.bump }}` - - Previous: `${{ steps.release_meta_final.outputs.previous_version }}` - - Next: `${{ steps.release_meta_final.outputs.version }}` - - Trigger: `${{ github.event_name }}` - - Install with: `gem install getstream-ruby -v ${{ steps.release_meta_final.outputs.version }}` + gem push "getstream-ruby-${VERSION}.gem" diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 00000000..f27e51aa --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,114 @@ +name: _run-tests + +on: + workflow_call: + secrets: + STREAM_API_SECRET: + required: true + STREAM_VIDEO_API_SECRET: + required: true + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + unit: + name: Unit Tests & Code Quality + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1.0' + + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + + - name: Run unit tests + run: make test + + - name: Run code quality checks + run: | + make format-check + make lint + make security + + integration-chat: + name: Chat Integration Tests + runs-on: ubuntu-latest + timeout-minutes: 30 + environment: ci + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1.0' + + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + + - name: Run chat integration tests + env: + STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} + STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} + STREAM_BASE_URL: ${{ vars.STREAM_BASE_URL }} + run: make test-integration-chat + + integration-feed: + name: Feed Integration Tests + runs-on: ubuntu-latest + timeout-minutes: 30 + environment: ci + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1.0' + + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + + - name: Run feed integration tests + env: + STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} + STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} + STREAM_BASE_URL: ${{ vars.STREAM_BASE_URL }} + run: make test-integration-feed + + integration-video: + name: Video Integration Tests + runs-on: ubuntu-latest + timeout-minutes: 30 + environment: ci + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1.0' + + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + + - name: Run video integration tests + env: + STREAM_API_KEY: ${{ vars.STREAM_VIDEO_API_KEY }} + STREAM_API_SECRET: ${{ secrets.STREAM_VIDEO_API_SECRET }} + STREAM_BASE_URL: ${{ vars.STREAM_VIDEO_BASE_URL }} + run: make test-integration-video diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..ca3a5114 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "12.1.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ecf64a..7bca08c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,28 @@ -## [Unreleased] +# Changelog + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +Entries from 12.1.1 on are generated by release-please from commit messages; earlier +ones were written by hand. + +## [12.1.0] - 2026-09-10 + +### Changed + +- Regenerated the OpenAPI client (FEEDS-1763.1). + +## [12.0.0] - 2026-09-02 + +### Changed + +- **Breaking:** regenerated the OpenAPI client (FEEDS-1830). + +## [11.1.0] - 2026-08-25 + +### Changed + +- Regenerated from the latest OpenAPI (CHA-4947). + +## [11.0.1] - 2026-08-24 ### Fixed @@ -8,6 +32,16 @@ `SSL_read: unexpected eof while reading` (`GetStreamRuby::TransportError`) on the next request. Override with `idle_timeout:` or `STREAM_IDLE_TIMEOUT`. +## [11.0.0] - 2026-08-17 + +### Changed + +- **Breaking:** regenerated from chat v235.17.1. + +### Fixed + +- Name the HTTP status when the error body is not JSON (#76). + ## [10.0.0] - 2026-07-24 ### Added diff --git a/Makefile b/Makefile index 09330152..e9a4d4eb 100644 --- a/Makefile +++ b/Makefile @@ -72,16 +72,6 @@ console: ## Start IRB console with SDK loaded version: ## Show current version @ruby -e "require './lib/getstream_ruby/version'; puts GetStreamRuby::VERSION" -# Version management -patch: ## Bump patch version (0.0.1 -> 0.0.2) - @./scripts/version-bump.sh patch - -minor: ## Bump minor version (0.0.1 -> 0.1.0) - @./scripts/version-bump.sh minor - -major: ## Bump major version (0.0.1 -> 1.0.0) - @./scripts/version-bump.sh major - # Development helpers dev-setup: setup ## Complete development setup @echo "Development setup complete!" diff --git a/README.md b/README.md index f28dde7c..8f88ad69 100644 --- a/README.md +++ b/README.md @@ -386,20 +386,39 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/getstr ## Release Process -Releases use two paths, both handled by `.github/workflows/release.yml`: - -- **Default**: automatic release when a PR is merged to `main`/`master`. The PR title 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 `lib/getstream_ruby/version.rb`. - -Automatic semver bump rules: - -- `feat:` -> minor -- `fix:` (or `bug:`) -> patch -- `feat!:` or `(scope)!:` (the `!` marker) -> major - -PRs with any other prefix do not trigger a release. - -The release pipeline runs lint (`make format-check && make lint && make security`), the unit suite (`make test`), and all three integration suites (chat, feed, video) on the merged commit before publishing to RubyGems. Each step 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 `master` 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 + `lib/getstream_ruby/version.rb` and `CHANGELOG.md`. It is opened by + `github-actions[bot]`, so approve it and run its held checks like any other PR. Never + edit the version by hand. +- Merging the Release PR runs `make format-check`, `make lint`, `make security`, + `make test` and all three integration suites 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 push the gem to RubyGems. The order matters: a tag, a GitHub + Release and a gem push cannot be withdrawn, a failed push can be retried. + +To retry a gem push 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 `master` +with `publish_tag` set to the tag (for example `v12.1.1`), which builds and pushes 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 +`master` 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. `v12.1.0` sits on a bump +commit the previous workflow created off-branch and never pushed, so release-please +cannot reach it by walking `master` and would otherwise treat the whole history as +unreleased. Delete the key once a release-please-created release exists on `master`; 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..18ea2a6b --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "last-release-sha": "283aabc8a7d1886caf0bde9aa7a8ae77f8098a6f", + "packages": { + ".": { + "release-type": "ruby", + "package-name": "getstream-ruby", + "version-file": "lib/getstream_ruby/version.rb", + "include-component-in-tag": false + } + } +} diff --git a/scripts/release/bump_version.rb b/scripts/release/bump_version.rb deleted file mode 100755 index 7bf0829f..00000000 --- a/scripts/release/bump_version.rb +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'optparse' - -def run_command(command) - `#{command}`.to_s.strip -end - -def find_latest_semver_tag - tags = run_command('git tag --list').split(/\R/) - versions = tags.map(&:strip).map { |tag| tag.sub(/^v/, '') }.grep(/^\d+\.\d+\.\d+$/) - return '0.0.0' if versions.empty? - - versions.max_by { |version| version.split('.').map(&:to_i) } -end - -def determine_bump_type(title) - # 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. - match = title.strip.match(/^([a-z]+)(\([^)]+\))?(!)?:/i) - return 'none' unless match - - type = match[1].downcase - return 'major' if match[3] == '!' - return 'minor' if type == 'feat' - return 'patch' if %w[fix bug].include?(type) - - 'none' -end - -def increment_version(version, bump) - major, minor, patch = version.split('.').map(&:to_i) - - case bump - when 'major' - "#{major + 1}.0.0" - when 'minor' - "#{major}.#{minor + 1}.0" - when 'patch' - "#{major}.#{minor}.#{patch + 1}" - else - version - end -end - -def read_version_file(path) - content = File.read(path) - match = content.match(/VERSION\s*=\s*'([^']+)'/) - raise "Could not find VERSION in #{path}" unless match - - match[1] -end - -def update_version_file(path, version) - content = File.read(path) - updated = content.sub(/VERSION = '[^']+'/, "VERSION = '#{version}'") - raise "Could not update VERSION in #{path}" if updated == content - - File.write(path, updated) -end - -def write_outputs(output_path, values) - lines = "#{values.map { |k, v| "#{k}=#{v}" }.join("\n")}\n" - if output_path.empty? - print(lines) - return - end - - File.open(output_path, 'a') { |file| file.write(lines) } -end - -def truthy?(value) - value.to_s.strip.downcase == 'true' -end - -options = { - title: '', - output: '', - manual_bump: '', - use_current_version: 'false', -} - -OptionParser.new do |opts| - - opts.on('--title TITLE') { |value| options[:title] = value } - opts.on('--output FILE') { |value| options[:output] = value } - opts.on('--manual-bump TYPE') { |value| options[:manual_bump] = value } - opts.on('--use-current-version VAL') { |value| options[:use_current_version] = value } - -end.parse! - -VERSION_FILE = 'lib/getstream_ruby/version.rb' - -manual = options[:manual_bump].to_s.strip.downcase - -unless manual.empty? - unless %w[major minor patch].include?(manual) - warn('manual-bump must be one of: major, minor, patch') - exit(1) - end - - previous_version = find_latest_semver_tag - next_version = if truthy?(options[:use_current_version]) - read_version_file(VERSION_FILE) - else - updated = increment_version(previous_version, manual) - update_version_file(VERSION_FILE, updated) - updated - end - - write_outputs(options[:output], { - 'should_release' => 'true', - 'bump' => manual, - 'previous_version' => previous_version, - 'version' => next_version, - 'tag' => "v#{next_version}", - }) - exit(0) -end - -bump = determine_bump_type(options[:title].to_s) -if bump == 'none' - write_outputs(options[:output], { - 'should_release' => 'false', - 'bump' => 'none', - }) - exit(0) -end - -current_version = find_latest_semver_tag -next_version = increment_version(current_version, bump) - -update_version_file(VERSION_FILE, next_version) - -write_outputs(options[:output], { - 'should_release' => 'true', - 'bump' => bump, - 'previous_version' => current_version, - 'version' => next_version, - 'tag' => "v#{next_version}", - }) diff --git a/scripts/version-bump.sh b/scripts/version-bump.sh deleted file mode 100755 index e9cb3bfd..00000000 --- a/scripts/version-bump.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash - -# Version bump script for GetStream Ruby SDK -# Usage: ./scripts/version-bump.sh [major|minor|patch] [release_notes] - -set -e - -VERSION_TYPE="${1:-patch}" -RELEASE_NOTES="${2:-}" - -if [[ ! "$VERSION_TYPE" =~ ^(major|minor|patch)$ ]]; then - echo "Error: Version type must be major, minor, or patch" - echo "Usage: $0 [major|minor|patch] [release_notes]" - exit 1 -fi - -# Get current version -CURRENT_VERSION=$(ruby -r "./lib/getstream_ruby/version.rb" -e "puts GetStreamRuby::VERSION") -echo "Current version: $CURRENT_VERSION" - -# Parse version components -IFS='.' read -r major minor patch <<< "$CURRENT_VERSION" - -# Calculate new version -case "$VERSION_TYPE" in - "major") - NEW_VERSION="$((major + 1)).0.0" - ;; - "minor") - NEW_VERSION="$major.$((minor + 1)).0" - ;; - "patch") - NEW_VERSION="$major.$minor.$((patch + 1))" - ;; -esac - -echo "New version: $NEW_VERSION" - -# Update version.rb (gemspec loads version dynamically from here) -if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' "s/VERSION = '[^']*'/VERSION = '$NEW_VERSION'/" "lib/getstream_ruby/version.rb" -else - sed -i "s/VERSION = '[^']*'/VERSION = '$NEW_VERSION'/" "lib/getstream_ruby/version.rb" -fi - -echo "Updated version files to $NEW_VERSION" - -# Update CHANGELOG -CHANGELOG_FILE="CHANGELOG.md" - -# Create CHANGELOG.md if it doesn't exist -if [ ! -f "$CHANGELOG_FILE" ]; then - cat > "$CHANGELOG_FILE" << 'EOF' -# Changelog - -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). - -EOF -fi - -# Add new version entry -TEMP_FILE=$(mktemp) -echo "## [$NEW_VERSION] - $(date +%Y-%m-%d)" >> "$TEMP_FILE" -echo "" >> "$TEMP_FILE" - -if [ -n "$RELEASE_NOTES" ]; then - echo "$RELEASE_NOTES" >> "$TEMP_FILE" -else - echo "### $VERSION_TYPE^2 changes" >> "$TEMP_FILE" - echo "- " >> "$TEMP_FILE" -fi - -echo "" >> "$TEMP_FILE" -cat "$CHANGELOG_FILE" >> "$TEMP_FILE" -mv "$TEMP_FILE" "$CHANGELOG_FILE" - -echo "Updated CHANGELOG.md" - -echo "✅ Version bump complete: $CURRENT_VERSION → $NEW_VERSION" -echo "Files updated:" -echo " - lib/getstream_ruby/version.rb" -echo " - CHANGELOG.md" -echo "" -echo "Note: getstream-ruby.gemspec loads version dynamically from version.rb" From d886781126dc915313780bb9cf69aaad050aa9b6 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Mon, 14 Sep 2026 15:27:41 +0200 Subject: [PATCH 2/3] ci: restore the gcp-lb job and make a stuck release stand down The reusable workflow was built from a stale local ci.yml and silently dropped integration-gcp-lb, the regression guard for the pooled-TLS bug shipped as 11.0.1. Restored, and integration-feed now runs after integration-chat because both blank and restore the app-global file_upload_config. A release that failed after its Release PR merged kept autorelease: pending, and the old guard then failed every later push to master after running the whole suite. detect now compares the pending release's merge commit with this run's commit itself, gates tests and release on the result, and logs a warning naming the run to go back to. release-pr stands down while any release is pending, so it cannot open a second Release PR covering commits the first one already consumed. detect also queries the autorelease: pending label directly. Listing closed PRs and filtering client-side loses a release that has slipped past the first page, which reads as nothing to release and passes. version.rb is set to 12.1.0 so the tree matches the manifest and the tag, ci.yml covers PRs into N.x hotfix branches, and the changelog gains 10.1.0, which is where the HTTP-status fix actually shipped. --- .github/workflows/ci.yml | 7 +-- .github/workflows/release.yml | 78 +++++++++++++++++++-------------- .github/workflows/run_tests.yml | 30 +++++++++++++ CHANGELOG.md | 2 + README.md | 28 +++++++----- lib/getstream_ruby/version.rb | 2 +- 6 files changed, 101 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14985d13..648867f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,11 +2,12 @@ name: CI # pull_request only. A push trigger alongside it ran the whole suite twice on the same # SHA, and the duplicate integration legs raced each other against the Stream app -# several SDK repos share. master is covered by release.yml, which runs this same -# reusable workflow as the gate before tagging. +# several SDK repos share. The trade is that a merge to master, and a direct push to it, +# now run nothing: release.yml runs this same workflow only when a release is pending, +# so the gate before a tag is covered but the routine post-merge signal is gone. on: pull_request: - branches: [ master, main ] + branches: [ master, main, '*.x' ] permissions: contents: read diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 22aed1f3..7ccb6088 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,12 +20,13 @@ concurrency: cancel-in-progress: false jobs: - # Reversible half: keep the Release PR current. Never gated. + # Reversible half: keep the Release PR current. Stands down only while a release is + # already pending, because until that one is tagged there is no release commit to stop + # the walk at and it would propose the same commits again in a second Release PR. release-pr: name: Release PR - if: >- - (github.event_name == 'push' || inputs.publish_tag == '') && - (github.ref_name == 'master' || endsWith(github.ref_name, '.x')) + needs: detect + if: needs.detect.outputs.pending != 'true' runs-on: ubuntu-latest timeout-minutes: 5 permissions: @@ -42,6 +43,9 @@ jobs: # Tagging, the GitHub Release and the gem push 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 while the suite runs on this + # workflow's own commit, so `ready` also requires those to be the same commit. They + # are, on the path that matters: the push of that merge. detect: name: Detect pending release if: >- @@ -52,30 +56,57 @@ jobs: permissions: contents: read pull-requests: read + issues: read outputs: pending: ${{ steps.find.outputs.pending }} - sha: ${{ steps.find.outputs.sha }} + ready: ${{ steps.find.outputs.ready }} steps: - name: Find a merged Release PR waiting to be tagged id: find env: GH_TOKEN: ${{ github.token }} + BASE: ${{ github.ref_name }} + HEAD_SHA: ${{ github.sha }} run: | - 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')" + pending=false + ready=false + + # Query the label directly. Listing closed PRs and filtering client-side loses + # a release that has slipped past the first page, which reads as "nothing to + # release" and passes. + num="$(gh api "repos/${GITHUB_REPOSITORY}/issues" \ + -X GET -f state=closed -f labels='autorelease: pending' -f per_page=20 \ + --jq '[.[] | select(.pull_request != null)] | .[0].number // empty')" + + sha="" + if [ -n "$num" ]; then + sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${num}" \ + --jq 'select(.merged_at != null and .base.ref == env.BASE) | .merge_commit_sha // empty')" + fi + if [ -z "$sha" ]; then - echo "No pending release." - echo "pending=false" >> "$GITHUB_OUTPUT" - exit 0 + echo "No pending release on ${BASE}." + elif [ "$sha" != "$HEAD_SHA" ]; then + # Reached when an earlier release run failed after the Release PR merged. + # Tagging $sha here would tag a tree this run never tested, and failing + # would redden every later push, so stand down and say why. + pending=true + echo "::warning::Release PR #${num} is still pending at ${sha}, which is not this run's commit ${HEAD_SHA}. Re-run the workflow run for ${sha} to finish that release." + else + pending=true + ready=true + echo "Pending release #${num} will be tagged at ${sha}." fi - echo "Pending release will be tagged at $sha." - echo "pending=true" >> "$GITHUB_OUTPUT" - echo "sha=$sha" >> "$GITHUB_OUTPUT" + + { + echo "pending=${pending}" + echo "ready=${ready}" + } >> "$GITHUB_OUTPUT" tests: name: Tests needs: detect - if: needs.detect.outputs.pending == 'true' + if: needs.detect.outputs.ready == 'true' uses: ./.github/workflows/run_tests.yml secrets: inherit @@ -83,7 +114,7 @@ jobs: release: name: 🚀 Tag and release needs: [detect, tests] - if: needs.detect.outputs.pending == 'true' + if: needs.detect.outputs.ready == 'true' runs-on: ubuntu-latest timeout-minutes: 5 permissions: @@ -94,23 +125,6 @@ jobs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} steps: - # 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: - TESTED: ${{ github.sha }} - PENDING: ${{ needs.detect.outputs.sha }} - run: | - 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." - - uses: googleapis/release-please-action@v4 id: release with: diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index f27e51aa..11d386f8 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -7,6 +7,8 @@ on: required: true STREAM_VIDEO_API_SECRET: required: true + STREAM_GCP_API_SECRET: + required: false concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -67,6 +69,10 @@ jobs: integration-feed: name: Feed Integration Tests + # Runs after chat rather than beside it: both blank and restore the app-global + # file_upload_config, so in parallel one suite's restore lands mid-assertion in + # the other. + needs: integration-chat runs-on: ubuntu-latest timeout-minutes: 30 environment: ci @@ -112,3 +118,27 @@ jobs: STREAM_API_SECRET: ${{ secrets.STREAM_VIDEO_API_SECRET }} STREAM_BASE_URL: ${{ vars.STREAM_VIDEO_BASE_URL }} run: make test-integration-video + + integration-gcp-lb: + name: GCP load balancer keep-alive + runs-on: ubuntu-latest + timeout-minutes: 30 + environment: ci + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1.0' + + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + + - name: Run GCP keep-alive integration test + env: + STREAM_API_KEY: ${{ vars.STREAM_GCP_API_KEY || vars.STREAM_API_KEY }} + STREAM_API_SECRET: ${{ secrets.STREAM_GCP_API_SECRET || secrets.STREAM_API_SECRET }} + STREAM_BASE_URL: ${{ vars.STREAM_GCP_BASE_URL }} + run: make test-integration-gcp-lb diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bca08c6..3b5688e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ ones were written by hand. - **Breaking:** regenerated from chat v235.17.1. +## [10.1.0] - 2026-08-12 + ### Fixed - Name the HTTP status when the error body is not JSON (#76). diff --git a/README.md b/README.md index 8f88ad69..8f4d5b36 100644 --- a/README.md +++ b/README.md @@ -388,24 +388,32 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/getstr Releases are driven by [release-please](https://github.com/googleapis/release-please). -- Merge PRs to `master` 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. +- Merge PRs to `master` with conventional-commit titles, using **Squash and merge**. The + 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. +- Squashing is a convention here, not yet enforced. The repo still has + `allow_merge_commit: true`, `allow_rebase_merge: true` and + `squash_merge_commit_title: COMMIT_OR_PR_TITLE`, and until someone with admin sets + those to `false`, `false` and `PR_TITLE` (as getstream-go has), two things silently + skip a release: a merge commit, whose subject is not conventional and whose body only + yields a plain `feat:`/`fix:` prefix, never `feat!:`; and a single-commit PR, which + squashes to that commit's subject rather than the PR title. `pr_title.yml` only checks + the PR title field, so it passes in both cases. - release-please keeps a Release PR open with the version bump in `lib/getstream_ruby/version.rb` and `CHANGELOG.md`. It is opened by `github-actions[bot]`, so approve it and run its held checks like any other PR. Never edit the version by hand. - Merging the Release PR runs `make format-check`, `make lint`, `make security`, - `make test` and all three integration suites 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 + `make test` and the four integration suites (chat, feed, video, GCP load balancer) 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 push the gem to RubyGems. The order matters: a tag, a GitHub Release and a gem push cannot be withdrawn, a failed push can be retried. -To retry a gem push 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 `master` +If the suite goes red after the Release PR merged, the release stays pending and every +later push to `master` logs a warning naming the commit to go back to, rather than +failing. Recovery in both that case and a failed gem push is "Re-run failed jobs" on the +run for that merge commit. Once GitHub has retired the run, dispatch `Release` from `master` with `publish_tag` set to the tag (for example `v12.1.1`), which builds and pushes that tag without touching release-please. diff --git a/lib/getstream_ruby/version.rb b/lib/getstream_ruby/version.rb index eea7c82e..5aafd1ed 100644 --- a/lib/getstream_ruby/version.rb +++ b/lib/getstream_ruby/version.rb @@ -2,6 +2,6 @@ module GetStreamRuby - VERSION = '8.0.2' + VERSION = '12.1.0' end From 7013b5ebd4dc31e5d4fa296258f249a97a75e14f Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Mon, 14 Sep 2026 15:38:03 +0200 Subject: [PATCH 3/3] ci: pick the pending release by branch, and keep feed reporting detect took the first label-matched PR and only then checked its base branch, so with two releases pending at once, which is the documented N.x hotfix flow, the newer one wins the pick and this branch's release falls into the no-release arm on every run until the other clears. Filter first, then pick. integration-feed inherited an implicit success() from its needs, so a red chat skipped it outright rather than only ordering it. !cancelled() keeps the ordering that the shared file_upload_config requires without losing the feed result. --- .github/workflows/release.yml | 19 +++++++++++++------ .github/workflows/run_tests.yml | 4 +++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ccb6088..11d68749 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,16 +73,23 @@ jobs: # Query the label directly. Listing closed PRs and filtering client-side loses # a release that has slipped past the first page, which reads as "nothing to - # release" and passes. - num="$(gh api "repos/${GITHUB_REPOSITORY}/issues" \ + # release" and passes. Filter before picking, too: a hotfix branch can hold + # its own pending release, and taking the newest label match would drop this + # branch's release on every run until the other one clears. + nums="$(gh api "repos/${GITHUB_REPOSITORY}/issues" \ -X GET -f state=closed -f labels='autorelease: pending' -f per_page=20 \ - --jq '[.[] | select(.pull_request != null)] | .[0].number // empty')" + --jq '.[] | select(.pull_request != null) | .number')" + num="" sha="" - if [ -n "$num" ]; then - sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${num}" \ + for n in $nums; do + sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${n}" \ --jq 'select(.merged_at != null and .base.ref == env.BASE) | .merge_commit_sha // empty')" - fi + if [ -n "$sha" ]; then + num="$n" + break + fi + done if [ -z "$sha" ]; then echo "No pending release on ${BASE}." diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 11d386f8..fcf5760b 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -71,8 +71,10 @@ jobs: name: Feed Integration Tests # Runs after chat rather than beside it: both blank and restore the app-global # file_upload_config, so in parallel one suite's restore lands mid-assertion in - # the other. + # the other. !cancelled() keeps the ordering without inheriting the implicit + # success(), so a red chat no longer hides whether feed passes. needs: integration-chat + if: '!cancelled()' runs-on: ubuntu-latest timeout-minutes: 30 environment: ci