diff --git a/.github/actions/ci-guard/action.yml b/.github/actions/ci-guard/action.yml new file mode 100644 index 00000000..70a6473e --- /dev/null +++ b/.github/actions/ci-guard/action.yml @@ -0,0 +1,23 @@ +name: Guard +description: > + Loads the current PR title (not the stale event payload) and cancels the workflow when it + contains skip-ci, case-insensitive. No-op when the event is not pull_request. +inputs: + github_token: + description: Token with actions:write and pull-requests:read + required: true +runs: + using: composite + steps: + - run: | + set -euo pipefail + if [ "${{ github.event_name }}" != "pull_request" ]; then + exit 0 + fi + PR_TITLE=$(gh pr view ${{ github.event.pull_request.number }} --repo "${{ github.repository }}" --json title --jq .title) + if printf '%s' "$PR_TITLE" | grep -Fqi 'skip-ci'; then + gh run cancel "${{ github.run_id }}" --repo "${{ github.repository }}" + fi + shell: bash + env: + GH_TOKEN: ${{ inputs.github_token }} diff --git a/.github/workflows/cron-checks.yml b/.github/workflows/cron-checks.yml index eeaac6f6..d66bcb63 100644 --- a/.github/workflows/cron-checks.yml +++ b/.github/workflows/cron-checks.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: include: - - ios: "26.1" + - ios: "26.2" device: "iPhone 17 Pro" setup_runtime: false - ios: "18.5" @@ -33,13 +33,10 @@ jobs: - ios: "16.4" device: "iPhone 14 Pro" setup_runtime: true - - ios: "15.5" - device: "iPhone 13 Pro" - setup_runtime: true fail-fast: false runs-on: macos-15 env: - XCODE_VERSION: "26.1.1" + XCODE_VERSION: "26.2" IOS_SIMULATOR_DEVICE: "${{ matrix.device }} (${{ matrix.ios }})" steps: - uses: actions/checkout@v4.1.1 @@ -74,7 +71,7 @@ jobs: strategy: matrix: include: - - xcode: 26.1.1 # swift 6.2 + - xcode: 26.2 # swift 6.2 os: macos-15 - xcode: 16.4 # swift 6.1 os: macos-15 diff --git a/.github/workflows/sdk-size-metrics.yml b/.github/workflows/sdk-size-metrics.yml index e71245a9..ec7e2d3c 100644 --- a/.github/workflows/sdk-size-metrics.yml +++ b/.github/workflows/sdk-size-metrics.yml @@ -16,17 +16,22 @@ jobs: sdk_size: name: Metrics runs-on: macos-15 + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false env: GITHUB_TOKEN: '${{ secrets.CI_BOT_GITHUB_TOKEN }}' GITHUB_PR_NUM: ${{ github.event.pull_request.number }} steps: + - uses: actions/checkout@v3.1.0 + + - uses: ./.github/actions/ci-guard + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Connect Bot uses: webfactory/ssh-agent@v0.7.0 with: ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} - - uses: actions/checkout@v3.1.0 - - uses: ./.github/actions/bootstrap - name: Run General SDK Size Metrics diff --git a/.github/workflows/smoke-checks.yml b/.github/workflows/smoke-checks.yml index a5de8059..9869b343 100644 --- a/.github/workflows/smoke-checks.yml +++ b/.github/workflows/smoke-checks.yml @@ -20,14 +20,24 @@ concurrency: env: HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI - IOS_SIMULATOR_DEVICE: "iPhone 17 Pro (26.1)" + IOS_SIMULATOR_DEVICE: "iPhone 17 Pro (26.2)" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_PR_NUM: ${{ github.event.pull_request.number }} jobs: + guard: + name: Guard + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.1.1 + - uses: ./.github/actions/ci-guard + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + test-llc: name: Test LLC runs-on: macos-15 + needs: guard steps: - uses: actions/checkout@v4.1.1 with: @@ -74,6 +84,7 @@ jobs: automated-code-review: name: Automated Code Review runs-on: macos-15 + needs: guard env: XCODE_VERSION: "16.0" steps: @@ -89,6 +100,7 @@ jobs: build-apps: name: Build Demo App runs-on: macos-15 + needs: guard steps: - uses: actions/checkout@v4.1.1 - uses: ./.github/actions/ruby-cache @@ -99,6 +111,7 @@ jobs: build-spm-integration: name: Build SPM Integration App runs-on: macos-15 + needs: guard steps: - uses: actions/checkout@v4.1.1 - uses: ./.github/actions/ruby-cache diff --git a/AGENTS.md b/AGENTS.md index 7ed5382c..59d108cc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,10 @@ DemoApp/ # The demo app for testing Use the closest folderโ€™s conventions when editing. Query actual target/product names from Package.swift before building. -Local setup (SPM) +### New files & target membership + โ€ข When creating new source or resource files, add them to the correct Xcode target(s). Update the project (e.g. project.pbxproj) so each new file is included in the appropriate target's "Compile Sources" (or "Copy Bundle Resources" for assets). Match the target(s) used by sibling files in the same directory (e.g. Sources/StreamFeeds/ โ†’ StreamFeeds; Tests/StreamFeedsTests/ โ†’ StreamFeedsTests). Omitting target membership will cause build failures or unused files. + +### Local setup (SPM) 1. Clone the repository and open it in Xcode (root contains Package.swift). 2. Resolve packages. 3. Choose an iOS Simulator (e.g., iPhone 15) and Build. diff --git a/CHANGELOG.md b/CHANGELOG.md index 282fbced..a506882c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### ๐Ÿ”„ Changed +# [0.5.3](https://github.com/GetStream/stream-feeds-swift/releases/tag/0.5.3) +_June 11, 2026_ + +### ๐Ÿ”„ Changed +- Update StreamCore to 0.7.0, which fixes `Sendable` errors when building with Xcode 27 [#68](https://github.com/GetStream/stream-feeds-swift/pull/68) + # [0.5.2](https://github.com/GetStream/stream-feeds-swift/releases/tag/0.5.2) _February 09, 2026_ diff --git a/Package.swift b/Package.swift index 77faff1c..8bfae80e 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/GetStream/stream-core-swift.git", exact: "0.6.2") + .package(url: "https://github.com/GetStream/stream-core-swift.git", from: "0.7.0") ], targets: [ .target( diff --git a/Sources/StreamFeeds/Info.plist b/Sources/StreamFeeds/Info.plist index cb2edbea..80fb0b9d 100644 --- a/Sources/StreamFeeds/Info.plist +++ b/Sources/StreamFeeds/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 0.5.2 + 0.5.3 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivitiesQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivitiesQuery.swift index a2743d3e..2e505e55 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivitiesQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivitiesQuery.swift @@ -220,7 +220,7 @@ public struct ActivitiesSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivityReactionsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivityReactionsQuery.swift index 36eb445a..28e24e50 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivityReactionsQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/ActivityReactionsQuery.swift @@ -157,7 +157,7 @@ public struct ActivityReactionsSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarkFoldersQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarkFoldersQuery.swift index e1a01130..460c4f01 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarkFoldersQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarkFoldersQuery.swift @@ -161,7 +161,7 @@ public struct BookmarkFoldersSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarksQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarksQuery.swift index 319b51c2..a6e9d3f9 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarksQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/BookmarksQuery.swift @@ -166,7 +166,7 @@ public struct BookmarksSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/CommentReactionsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/CommentReactionsQuery.swift index 6048c72e..8ab1128a 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/CommentReactionsQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/CommentReactionsQuery.swift @@ -164,7 +164,7 @@ public struct CommentReactionsSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/FeedsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/FeedsQuery.swift index 572dfae0..8dcb5ed3 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/FeedsQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/FeedsQuery.swift @@ -256,7 +256,7 @@ public struct FeedsSortField: SortField { /// The string value representing the field name in the API for remote sorting. public let rawValue: String - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/FollowsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/FollowsQuery.swift index 0be07bb6..96c2bc92 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/FollowsQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/FollowsQuery.swift @@ -156,7 +156,7 @@ public struct FollowsSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/MembersQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/MembersQuery.swift index e00cb101..e71e9558 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/MembersQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/MembersQuery.swift @@ -175,7 +175,7 @@ public struct MembersSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/ModerationConfigsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/ModerationConfigsQuery.swift index 781b6876..bc7f184f 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/ModerationConfigsQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/ModerationConfigsQuery.swift @@ -177,7 +177,7 @@ public struct ModerationConfigsSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/PollVotesQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/PollVotesQuery.swift index cd571640..84ae3a76 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/PollVotesQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/PollVotesQuery.swift @@ -187,7 +187,7 @@ public struct PollVotesSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/StateLayer/PaginatedLists/PollsQuery.swift b/Sources/StreamFeeds/StateLayer/PaginatedLists/PollsQuery.swift index 37b9ad22..ff43463d 100644 --- a/Sources/StreamFeeds/StateLayer/PaginatedLists/PollsQuery.swift +++ b/Sources/StreamFeeds/StateLayer/PaginatedLists/PollsQuery.swift @@ -197,7 +197,7 @@ public struct PollsSortField: SortField { /// - Parameters: /// - rawValue: The string value representing the field name in the API. /// - localValue: A closure that extracts the comparable value from the model. - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/Sources/StreamFeeds/Utils/SystemEnvironment+Version.swift b/Sources/StreamFeeds/Utils/SystemEnvironment+Version.swift index a70aa648..40b42092 100644 --- a/Sources/StreamFeeds/Utils/SystemEnvironment+Version.swift +++ b/Sources/StreamFeeds/Utils/SystemEnvironment+Version.swift @@ -6,5 +6,5 @@ import Foundation extension SystemEnvironment { /// A Stream Feeds version. - public static let version: String = "0.5.2" + public static let version: String = "0.5.3" } diff --git a/StreamFeeds.xcodeproj/project.pbxproj b/StreamFeeds.xcodeproj/project.pbxproj index bb3da354..b6f08a2e 100644 --- a/StreamFeeds.xcodeproj/project.pbxproj +++ b/StreamFeeds.xcodeproj/project.pbxproj @@ -863,8 +863,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/GetStream/stream-core-swift.git"; requirement = { - kind = exactVersion; - version = 0.6.2; + kind = upToNextMajorVersion; + minimumVersion = 0.7.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Tests/StreamFeedsTests/Extensions/ArrayExtensions_Tests.swift b/Tests/StreamFeedsTests/Extensions/ArrayExtensions_Tests.swift index ed7e5179..1aaf9eac 100644 --- a/Tests/StreamFeedsTests/Extensions/ArrayExtensions_Tests.swift +++ b/Tests/StreamFeedsTests/Extensions/ArrayExtensions_Tests.swift @@ -23,7 +23,7 @@ struct ArrayExtensions_Tests { public let comparator: AnySortComparator public let rawValue: String - public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable { + public init(_ rawValue: String, localValue: @escaping @Sendable (Model) -> Value) where Value: Comparable & Sendable { comparator = AnySortComparator(localValue: localValue) self.rawValue = rawValue } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 2ce6f8ce..1c69e892 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -6,7 +6,7 @@ require 'json' require 'net/http' import 'Sonarfile' -xcode_version = ENV['XCODE_VERSION'] || '26.1.1' +xcode_version = ENV['XCODE_VERSION'] || '26.2' xcode_project = 'StreamFeeds.xcodeproj' sdk_names = ['StreamFeeds'] github_repo = ENV['GITHUB_REPOSITORY'] || 'GetStream/stream-feeds-swift' @@ -247,12 +247,12 @@ end lane :sources_matrix do { - llc: ['Sources/StreamFeeds', 'Tests/StreamFeedsTests', 'Tests/Shared', xcode_project], - sample_apps: ['Sources', 'DemoApp', xcode_project], - integration: ['Sources', 'Integration', xcode_project], - ruby: ['fastlane', 'Gemfile', 'Gemfile.lock'], - size: ['Sources', xcode_project], - public_interface: ['Sources'] + llc: ['Sources/StreamFeeds', 'Tests/StreamFeedsTests', 'Tests/Shared', '.github/workflows/smoke-checks.yml', xcode_project], + sample_apps: ['Sources', 'DemoApp', '.github/workflows/smoke-checks.yml', xcode_project], + integration: ['Sources', 'Integration', '.github/workflows/smoke-checks.yml', xcode_project], + ruby: ['fastlane', 'Gemfile', 'Gemfile.lock', '.github/workflows/smoke-checks.yml'], + size: ['Sources', '.github/workflows/smoke-checks.yml', xcode_project], + public_interface: ['Sources', '.github/workflows/smoke-checks.yml'] } end