From 5e05c6c7fcecd86a698429c8acd97e21ad666d4a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 22:57:18 +0000 Subject: [PATCH 01/12] chore: Parallelize CI builds to reduce wall clock time - Break monolithic ci composite action into focused actions: lint, build-ios, build-macos, build-tvos, build-watchos, test-swiftpm, contract-tests - Run each as a separate parallel job in ci.yml - Remove Mint in favor of direct brew install swiftlint - Add simulator warmup step for iOS/tvOS/watchOS builds - Use --quick flag for pod lib lint since dedicated build jobs compile - Update release-please, manual-publish, and manual-publish-docs workflows to use new individual composite actions - Add setup-xcode step to build-docs job Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/build-ios/action.yml | 24 +++++ .github/actions/build-macos/action.yml | 13 +++ .github/actions/build-tvos/action.yml | 20 +++++ .github/actions/build-watchos/action.yml | 20 +++++ .github/actions/ci/action.yml | 78 ----------------- .github/actions/contract-tests/action.yml | 13 +++ .github/actions/lint/action.yml | 22 +++++ .github/actions/test-swiftpm/action.yml | 9 ++ .github/workflows/ci.yml | 102 +++++++++++++++++++--- .github/workflows/manual-publish-docs.yml | 19 +++- .github/workflows/manual-publish.yml | 18 +++- .github/workflows/release-please.yml | 25 +++++- Mintfile | 1 - 13 files changed, 269 insertions(+), 95 deletions(-) create mode 100644 .github/actions/build-ios/action.yml create mode 100644 .github/actions/build-macos/action.yml create mode 100644 .github/actions/build-tvos/action.yml create mode 100644 .github/actions/build-watchos/action.yml delete mode 100644 .github/actions/ci/action.yml create mode 100644 .github/actions/contract-tests/action.yml create mode 100644 .github/actions/lint/action.yml create mode 100644 .github/actions/test-swiftpm/action.yml delete mode 100644 Mintfile diff --git a/.github/actions/build-ios/action.yml b/.github/actions/build-ios/action.yml new file mode 100644 index 0000000..ce9d567 --- /dev/null +++ b/.github/actions/build-ios/action.yml @@ -0,0 +1,24 @@ +name: Build & Test iOS +description: 'Build for iOS device and run tests on iOS Simulator.' +inputs: + ios-sim: + description: 'iOS Simulator to use for testing' + required: true + +runs: + using: composite + steps: + # Workaround for intermittent macos-15 runner issue where simulator + # runtimes aren't loaded. Listing devices forces the simulator service + # to initialize. See https://github.com/actions/runner-images/issues/12948 + - name: Prepare iOS Simulator runtime + shell: bash + run: xcrun simctl list devices available + + - name: Build Tests for iOS device + shell: bash + run: xcodebuild build-for-testing -scheme 'LDSwiftEventSource' -sdk iphoneos CODE_SIGN_IDENTITY= | xcpretty + + - name: Build & Test on iOS Simulator + shell: bash + run: xcodebuild test -scheme 'LDSwiftEventSource' -sdk iphonesimulator -destination '${{ inputs.ios-sim }}' CODE_SIGN_IDENTITY= | xcpretty diff --git a/.github/actions/build-macos/action.yml b/.github/actions/build-macos/action.yml new file mode 100644 index 0000000..01d2737 --- /dev/null +++ b/.github/actions/build-macos/action.yml @@ -0,0 +1,13 @@ +name: Build & Test macOS +description: 'Build and test for macOS.' + +runs: + using: composite + steps: + - name: Build & Test on macOS + shell: bash + run: xcodebuild test -scheme 'LDSwiftEventSource' -sdk macosx -destination 'platform=macOS' | xcpretty + + - name: Build for ARM64 macOS + shell: bash + run: xcodebuild build -scheme 'LDSwiftEventSource' -arch arm64e -sdk macosx | xcpretty diff --git a/.github/actions/build-tvos/action.yml b/.github/actions/build-tvos/action.yml new file mode 100644 index 0000000..7152e1a --- /dev/null +++ b/.github/actions/build-tvos/action.yml @@ -0,0 +1,20 @@ +name: Build & Test tvOS +description: 'Build for tvOS device and run tests on tvOS Simulator.' + +runs: + using: composite + steps: + # Workaround for intermittent macos-15 runner issue where simulator + # runtimes aren't loaded. Listing devices forces the simulator service + # to initialize. See https://github.com/actions/runner-images/issues/12948 + - name: Prepare tvOS Simulator runtime + shell: bash + run: xcrun simctl list devices available + + - name: Build Tests for tvOS device + shell: bash + run: xcodebuild build-for-testing -scheme 'LDSwiftEventSource' -sdk appletvos CODE_SIGN_IDENTITY= | xcpretty + + - name: Build & Test on tvOS Simulator + shell: bash + run: xcodebuild test -scheme 'LDSwiftEventSource' -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV' | xcpretty diff --git a/.github/actions/build-watchos/action.yml b/.github/actions/build-watchos/action.yml new file mode 100644 index 0000000..8e5e673 --- /dev/null +++ b/.github/actions/build-watchos/action.yml @@ -0,0 +1,20 @@ +name: Build watchOS +description: 'Build for watchOS device and simulator.' + +runs: + using: composite + steps: + # Workaround for intermittent macos-15 runner issue where simulator + # runtimes aren't loaded. Listing devices forces the simulator service + # to initialize. See https://github.com/actions/runner-images/issues/12948 + - name: Prepare watchOS Simulator runtime + shell: bash + run: xcrun simctl list devices available + + - name: Build for watchOS simulator + shell: bash + run: xcodebuild build -scheme 'LDSwiftEventSource' -sdk watchsimulator | xcpretty + + - name: Build for watchOS device + shell: bash + run: xcodebuild build -scheme 'LDSwiftEventSource' -sdk watchos | xcpretty diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml deleted file mode 100644 index d17638c..0000000 --- a/.github/actions/ci/action.yml +++ /dev/null @@ -1,78 +0,0 @@ -# This is a composite to allow sharing these steps into other workflows. -# For instance it could be used by regular CI as well as the release process. - -name: CI Workflow -description: 'Shared CI workflow.' -inputs: - xcode-version: - description: 'Which version of xcode should be installed' - required: true - ios-sim: - description: 'iOS Simulator to use for testing' - required: true - token: - description: 'Token to use for publishing.' - required: true - -runs: - using: composite - steps: - - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd - with: - xcode-version: ${{ inputs.xcode-version }} - - - name: Install mint - shell: bash - run: brew install mint - - - name: Bootstrap Mint packages - shell: bash - run: mint bootstrap - - - name: Install cocoapods - shell: bash - run: gem install cocoapods - - - name: Lint the podspec - shell: bash - run: pod spec lint LDSwiftEventSource.podspec - - - name: Build & Test on macOS Simulator - shell: bash - run: xcodebuild test -scheme 'LDSwiftEventSource' -sdk macosx -destination 'platform=macOS' | xcpretty - - - name: Build for ARM64 macOS - shell: bash - run: xcodebuild build -scheme 'LDSwiftEventSource' -arch arm64e -sdk macosx | xcpretty - - - name: Build Tests for iOS device - shell: bash - run: xcodebuild build-for-testing -scheme 'LDSwiftEventSource' -sdk iphoneos CODE_SIGN_IDENTITY= | xcpretty - - - name: Build & Test on iOS Simulator - shell: bash - run: xcodebuild test -scheme 'LDSwiftEventSource' -sdk iphonesimulator -destination '${{ inputs.ios-sim }}' CODE_SIGN_IDENTITY= | xcpretty - - - name: Build Tests for tvOS device - shell: bash - run: xcodebuild build-for-testing -scheme 'LDSwiftEventSource' -sdk appletvos CODE_SIGN_IDENTITY= | xcpretty - - - name: Build & Test on tvOS Simulator - shell: bash - run: xcodebuild test -scheme 'LDSwiftEventSource' -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV' | xcpretty - - - name: Build for watchOS simulator # No XCTest testing on watchOS - shell: bash - run: xcodebuild build -scheme 'LDSwiftEventSource' -sdk watchsimulator | xcpretty - - - name: Build for watchOS device # No XCTest testing on watchOS - shell: bash - run: xcodebuild build -scheme 'LDSwiftEventSource' -sdk watchos | xcpretty - - - name: Build & Test with swiftpm - shell: bash - run: swift test -v 2>&1 | xcpretty - - - name: Run contract tests - shell: bash - run: make contract-tests diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml new file mode 100644 index 0000000..a7f45e6 --- /dev/null +++ b/.github/actions/contract-tests/action.yml @@ -0,0 +1,13 @@ +name: Contract Tests +description: 'Build and run SDK contract tests.' +inputs: + token: + description: 'GH token used to download SDK test harness.' + required: true + +runs: + using: composite + steps: + - name: Run contract tests + shell: bash + run: make contract-tests diff --git a/.github/actions/lint/action.yml b/.github/actions/lint/action.yml new file mode 100644 index 0000000..2913fc8 --- /dev/null +++ b/.github/actions/lint/action.yml @@ -0,0 +1,22 @@ +name: Lint +description: 'Run podspec and swiftlint checks.' + +runs: + using: composite + steps: + - name: Install swiftlint + shell: bash + run: brew install swiftlint + + - name: Install cocoapods + shell: bash + run: gem install cocoapods + + - name: Lint the podspec + shell: bash + # --quick skips building since dedicated build jobs already compile all platforms + run: pod lib lint LDSwiftEventSource.podspec --allow-warnings --quick + + - name: Run swiftlint + shell: bash + run: swiftlint lint diff --git a/.github/actions/test-swiftpm/action.yml b/.github/actions/test-swiftpm/action.yml new file mode 100644 index 0000000..1501547 --- /dev/null +++ b/.github/actions/test-swiftpm/action.yml @@ -0,0 +1,9 @@ +name: Test SwiftPM +description: 'Build and test using Swift Package Manager.' + +runs: + using: composite + steps: + - name: Build & Test with SwiftPM + shell: bash + run: swift test -v 2>&1 | xcpretty diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e30afc5..b48db8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,26 +10,104 @@ on: - '**.md' jobs: - macos-build: - runs-on: ${{ matrix.os }} + lint: + runs-on: macos-15 - strategy: - fail-fast: false - matrix: - include: - - xcode-version: 16.4 - ios-sim: 'platform=iOS Simulator,name=iPhone 16' - os: macos-15 + steps: + - uses: actions/checkout@v4 + + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd + with: + xcode-version: 16.4 + + - uses: ./.github/actions/lint + + build-ios: + runs-on: macos-15 steps: - uses: actions/checkout@v4 - - uses: ./.github/actions/ci + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd + with: + xcode-version: 16.4 + + - uses: ./.github/actions/build-ios + with: + ios-sim: 'platform=iOS Simulator,name=iPhone 16' + + build-macos: + runs-on: macos-15 + + steps: + - uses: actions/checkout@v4 + + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd + with: + xcode-version: 16.4 + + - uses: ./.github/actions/build-macos + + build-tvos: + runs-on: macos-15 + + steps: + - uses: actions/checkout@v4 + + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd + with: + xcode-version: 16.4 + + - uses: ./.github/actions/build-tvos + + build-watchos: + runs-on: macos-15 + + steps: + - uses: actions/checkout@v4 + + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd + with: + xcode-version: 16.4 + + - uses: ./.github/actions/build-watchos + + test-swiftpm: + runs-on: macos-15 + + steps: + - uses: actions/checkout@v4 + + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd + with: + xcode-version: 16.4 + + - uses: ./.github/actions/test-swiftpm + + contract-tests: + runs-on: macos-15 + + steps: + - uses: actions/checkout@v4 + + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd + with: + xcode-version: 16.4 + + - uses: ./.github/actions/contract-tests with: - xcode-version: ${{ matrix.xcode-version }} - ios-sim: ${{ matrix.ios-sim }} token: ${{ secrets.GITHUB_TOKEN }} + build-docs: + runs-on: macos-15 + + steps: + - uses: actions/checkout@v4 + + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd + with: + xcode-version: 16.4 + - uses: ./.github/actions/build-docs linux-build: diff --git a/.github/workflows/manual-publish-docs.yml b/.github/workflows/manual-publish-docs.yml index 1a43ce4..9f2b787 100644 --- a/.github/workflows/manual-publish-docs.yml +++ b/.github/workflows/manual-publish-docs.yml @@ -13,11 +13,26 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Build and Test - uses: ./.github/actions/ci + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd with: xcode-version: 16.4 + + - uses: ./.github/actions/lint + + - uses: ./.github/actions/build-ios + with: ios-sim: 'platform=iOS Simulator,name=iPhone 16' + + - uses: ./.github/actions/build-macos + + - uses: ./.github/actions/build-tvos + + - uses: ./.github/actions/build-watchos + + - uses: ./.github/actions/test-swiftpm + + - uses: ./.github/actions/contract-tests + with: token: ${{ secrets.GITHUB_TOKEN }} - uses: ./.github/actions/build-docs diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index c660025..51f931c 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -25,10 +25,26 @@ jobs: aws_assume_role: ${{ vars.AWS_ROLE_ARN }} ssm_parameter_pairs: '/production/common/releasing/cocoapods/token = COCOAPODS_TRUNK_TOKEN' - - uses: ./.github/actions/ci + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd with: xcode-version: 16.4 + + - uses: ./.github/actions/lint + + - uses: ./.github/actions/build-ios + with: ios-sim: 'platform=iOS Simulator,name=iPhone 16' + + - uses: ./.github/actions/build-macos + + - uses: ./.github/actions/build-tvos + + - uses: ./.github/actions/build-watchos + + - uses: ./.github/actions/test-swiftpm + + - uses: ./.github/actions/contract-tests + with: token: ${{ secrets.GITHUB_TOKEN }} - uses: ./.github/actions/publish diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 6acdcb0..ed5069d 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -42,11 +42,34 @@ jobs: aws_assume_role: ${{ vars.AWS_ROLE_ARN }} ssm_parameter_pairs: '/production/common/releasing/cocoapods/token = COCOAPODS_TRUNK_TOKEN' - - uses: ./.github/actions/ci + - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd if: ${{ steps.release.outputs.releases_created == 'true' }} with: xcode-version: 16.4 + + - uses: ./.github/actions/lint + if: ${{ steps.release.outputs.releases_created == 'true' }} + + - uses: ./.github/actions/build-ios + if: ${{ steps.release.outputs.releases_created == 'true' }} + with: ios-sim: 'platform=iOS Simulator,name=iPhone 16' + + - uses: ./.github/actions/build-macos + if: ${{ steps.release.outputs.releases_created == 'true' }} + + - uses: ./.github/actions/build-tvos + if: ${{ steps.release.outputs.releases_created == 'true' }} + + - uses: ./.github/actions/build-watchos + if: ${{ steps.release.outputs.releases_created == 'true' }} + + - uses: ./.github/actions/test-swiftpm + if: ${{ steps.release.outputs.releases_created == 'true' }} + + - uses: ./.github/actions/contract-tests + if: ${{ steps.release.outputs.releases_created == 'true' }} + with: token: ${{ secrets.GITHUB_TOKEN }} - uses: ./.github/actions/build-docs diff --git a/Mintfile b/Mintfile deleted file mode 100644 index 556adef..0000000 --- a/Mintfile +++ /dev/null @@ -1 +0,0 @@ -realm/SwiftLint@0.56.2 From dd2000d35ce2162a16f6554b57231f3348b28a2a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:04:33 +0000 Subject: [PATCH 02/12] fix: Use shared contract-tests action for authenticated test harness download Incorporates fix from devin/1772832423-fix-contract-test-version branch. Replaces make contract-tests with launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.3.0 which properly downloads the test harness. Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index a7f45e6..33964bd 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -8,6 +8,20 @@ inputs: runs: using: composite steps: - - name: Run contract tests + - name: Build contract test service + shell: bash + run: make build-contract-tests + + - name: Start contract test service shell: bash - run: make contract-tests + run: make start-contract-test-service-bg + + - name: Run contract tests + uses: launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.3.0 + with: + repo: sse-contract-tests + branch: main + test_service_port: 8000 + debug_logging: true + token: ${{ inputs.token }} + extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'" From bfd193cf0a0e932046cd045e954d2d60fb48c281 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:06:17 +0000 Subject: [PATCH 03/12] fix: Disable persistence tests flag for SSE contract tests The sse-contract-tests binary does not support the -enable-persistence-tests flag that contract-tests-v1.3.0 enables by default. Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index 33964bd..1916f31 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -23,5 +23,6 @@ runs: branch: main test_service_port: 8000 debug_logging: true + enable_persistence_tests: false token: ${{ inputs.token }} extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'" From 0c3603653ac1621e53246a80e8955884bc22be3a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:08:08 +0000 Subject: [PATCH 04/12] ci: Re-trigger CI to work around transient GitHub API failure in contract test version resolution Co-Authored-By: rlamb@launchdarkly.com From 8e990e4cc62da7394bbe3478e6a9fdbdc0d7eab1 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:10:14 +0000 Subject: [PATCH 05/12] fix: Pin contract-tests action to commit SHA per Semgrep recommendation Pins launchdarkly/gh-actions/actions/contract-tests to full commit SHA 5adb11fd6953e1bc35d9cf1fc1b4374c464e3a8b (contract-tests-v1.3.0) to mitigate supply chain risk. Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index 1916f31..e4089af 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -17,7 +17,7 @@ runs: run: make start-contract-test-service-bg - name: Run contract tests - uses: launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.3.0 + uses: launchdarkly/gh-actions/actions/contract-tests@5adb11fd6953e1bc35d9cf1fc1b4374c464e3a8b # contract-tests-v1.3.0 with: repo: sse-contract-tests branch: main From b6e0f3937b42adf4d114e7b2b8ee13505fcca815 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:27:25 +0000 Subject: [PATCH 06/12] debug: Add inline downloader script with debugging for contract test release resolution Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 144 ++++++++++++++++++++-- 1 file changed, 134 insertions(+), 10 deletions(-) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index e4089af..78f8abc 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -16,13 +16,137 @@ runs: shell: bash run: make start-contract-test-service-bg - - name: Run contract tests - uses: launchdarkly/gh-actions/actions/contract-tests@5adb11fd6953e1bc35d9cf1fc1b4374c464e3a8b # contract-tests-v1.3.0 - with: - repo: sse-contract-tests - branch: main - test_service_port: 8000 - debug_logging: true - enable_persistence_tests: false - token: ${{ inputs.token }} - extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'" + # DEBUG: Running the downloader script inline with added debugging + # instead of using the shared action, to diagnose release resolution failure. + # Original action usage commented out at the bottom of this file. + - name: Run contract tests (debug) + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.token }} + VERSION: v2 + PARAMS: "-url http://localhost:8000 -port 8111 -skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks' -debug -stop-service-at-end" + run: | + set -e + + echo "=== DEBUG: Contract test downloader script (inline with debugging) ===" + echo "VERSION=$VERSION" + echo "PARAMS=$PARAMS" + echo "GITHUB_TOKEN is set: $([ -n "$GITHUB_TOKEN" ] && echo 'yes' || echo 'no')" + echo "GITHUB_TOKEN length: ${#GITHUB_TOKEN}" + + RELEASES_API_URL=https://api.github.com/repos/launchdarkly/sse-contract-tests/releases + RELEASES_SITE_URL=https://github.com/launchdarkly/sse-contract-tests/releases + EXECUTABLE_ARCHIVE_NAME=sse-contract-tests_$(uname -s)_$(uname -m).tar.gz + + echo "=== DEBUG: OS=$(uname -s) ARCH=$(uname -m) ===" + echo "=== DEBUG: EXECUTABLE_ARCHIVE_NAME=$EXECUTABLE_ARCHIVE_NAME ===" + + if [ -z "${VERSION}" -o -z "${PARAMS}" ]; then + echo 'You must specify a version string in $VERSION and command parameters in $PARAMS' >&2 + exit 1 + fi + + resolve_version() { + if echo "$1" | grep -q '^v[^.][^.]*\.[^.][^.]*\.'; then + echo "=== DEBUG: Version '$1' is already a complete version string ===" >&2 + echo "$1" + return + fi + + echo "=== DEBUG: Version '$1' is partial, resolving from API ===" >&2 + echo "=== DEBUG: API URL: ${RELEASES_API_URL} ===" >&2 + + # Try with auth token (matching how the action passes it) + if [ -n "${GITHUB_TOKEN}" ]; then + echo "=== DEBUG: Fetching releases WITH auth token ===" >&2 + API_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "${RELEASES_API_URL}") + else + echo "=== DEBUG: Fetching releases WITHOUT auth token ===" >&2 + API_RESPONSE=$(curl -s "${RELEASES_API_URL}") + fi + + echo "=== DEBUG: API response length: ${#API_RESPONSE} ===" >&2 + echo "=== DEBUG: First 500 chars of response: ===" >&2 + echo "${API_RESPONSE}" | head -c 500 >&2 + echo "" >&2 + + # Check for rate limiting or error + if echo "${API_RESPONSE}" | grep -q '"message"'; then + echo "=== DEBUG: API returned a message (possible error/rate limit): ===" >&2 + echo "${API_RESPONSE}" | grep '"message"' >&2 + fi + + # Check for tag_name entries + TAG_COUNT=$(echo "${API_RESPONSE}" | grep "tag_name" | wc -l) + echo "=== DEBUG: Found $TAG_COUNT tag_name entries ===" >&2 + + if [ "$TAG_COUNT" -gt 0 ]; then + echo "=== DEBUG: All tags found: ===" >&2 + echo "${API_RESPONSE}" | grep "tag_name" | sed -e 's/.*:[^"]*"\([^"]*\).*/\1/' >&2 + fi + + RESOLVED=$(echo "${API_RESPONSE}" \ + | grep "tag_name" \ + | sed -e 's/.*:[^"]*"\([^"]*\).*/\1/' \ + | grep "^$1\." \ + | head -n 1) + + echo "=== DEBUG: grep pattern used: '^$1.' ===" >&2 + echo "=== DEBUG: Resolved version: '${RESOLVED}' ===" >&2 + echo "${RESOLVED}" + } + + VERSION_TO_DOWNLOAD=$(resolve_version "${VERSION}") + if [ -z "${VERSION_TO_DOWNLOAD}" ]; then + echo "Unable to find a release matching '${VERSION}'" >&2 + + echo "=== DEBUG: Trying paginated API (per_page=100) ===" >&2 + if [ -n "${GITHUB_TOKEN}" ]; then + PAGINATED_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "${RELEASES_API_URL}?per_page=100") + else + PAGINATED_RESPONSE=$(curl -s "${RELEASES_API_URL}?per_page=100") + fi + echo "=== DEBUG: Paginated response length: ${#PAGINATED_RESPONSE} ===" >&2 + PAGINATED_TAGS=$(echo "${PAGINATED_RESPONSE}" | grep "tag_name" | sed -e 's/.*:[^"]*"\([^"]*\).*/\1/') + echo "=== DEBUG: Paginated tags: ===" >&2 + echo "${PAGINATED_TAGS}" >&2 + + # Also check rate limit status + echo "=== DEBUG: Checking rate limit ===" >&2 + if [ -n "${GITHUB_TOKEN}" ]; then + curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/rate_limit >&2 + else + curl -s https://api.github.com/rate_limit >&2 + fi + + exit 1 + fi + + TEMP_DIR="/tmp/sse-contract-tests_${VERSION_TO_DOWNLOAD}" + EXECUTABLE="${TEMP_DIR}/sse-contract-tests" + DOWNLOAD_URL="${RELEASES_SITE_URL}/download/${VERSION_TO_DOWNLOAD}/${EXECUTABLE_ARCHIVE_NAME}" + + echo "=== DEBUG: Will download from ${DOWNLOAD_URL} ===" + + if [ ! -x "${EXECUTABLE}" ]; then + rm -rf "${TEMP_DIR}" + mkdir "${TEMP_DIR}" + echo "Downloading ${DOWNLOAD_URL}" + curl --fail -s -L -o "${TEMP_DIR}/archive.tar.gz" "${DOWNLOAD_URL}" || (echo "Download failed" >&2; exit 1) + tar -xf "${TEMP_DIR}/archive.tar.gz" -C "${TEMP_DIR}" + fi + + echo "=== DEBUG: Running contract tests ===" + sh -c "${EXECUTABLE} $PARAMS" + + # COMMENTED OUT: Original action usage - restore after debugging + # - name: Run contract tests + # uses: launchdarkly/gh-actions/actions/contract-tests@5adb11fd6953e1bc35d9cf1fc1b4374c464e3a8b # contract-tests-v1.3.0 + # with: + # repo: sse-contract-tests + # branch: main + # test_service_port: 8000 + # debug_logging: true + # enable_persistence_tests: false + # token: ${{ inputs.token }} + # extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'" From 55c9f9659a2ebe8e2102c927d15889627b44f752 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:30:59 +0000 Subject: [PATCH 07/12] fix: Use inline contract test downloader with authenticated API calls The upstream sse-contract-tests/downloader/run.sh does not pass the GITHUB_TOKEN to the GitHub API call that resolves partial version strings (e.g. 'v2' -> 'v2.31.0'). This causes rate-limit failures on shared CI runners. Using an inline script that passes the token fixes the issue. Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 82 ++--------------------- 1 file changed, 5 insertions(+), 77 deletions(-) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index 78f8abc..dba9a7b 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -16,10 +16,11 @@ runs: shell: bash run: make start-contract-test-service-bg - # DEBUG: Running the downloader script inline with added debugging - # instead of using the shared action, to diagnose release resolution failure. - # Original action usage commented out at the bottom of this file. - - name: Run contract tests (debug) + # Inline downloader script instead of shared action to ensure the GitHub + # API call for resolving the release version uses an auth token. The + # upstream sse-contract-tests/downloader/run.sh does not pass the token, + # which causes rate-limit failures on shared CI runners. + - name: Run contract tests shell: bash env: GITHUB_TOKEN: ${{ inputs.token }} @@ -28,19 +29,10 @@ runs: run: | set -e - echo "=== DEBUG: Contract test downloader script (inline with debugging) ===" - echo "VERSION=$VERSION" - echo "PARAMS=$PARAMS" - echo "GITHUB_TOKEN is set: $([ -n "$GITHUB_TOKEN" ] && echo 'yes' || echo 'no')" - echo "GITHUB_TOKEN length: ${#GITHUB_TOKEN}" - RELEASES_API_URL=https://api.github.com/repos/launchdarkly/sse-contract-tests/releases RELEASES_SITE_URL=https://github.com/launchdarkly/sse-contract-tests/releases EXECUTABLE_ARCHIVE_NAME=sse-contract-tests_$(uname -s)_$(uname -m).tar.gz - echo "=== DEBUG: OS=$(uname -s) ARCH=$(uname -m) ===" - echo "=== DEBUG: EXECUTABLE_ARCHIVE_NAME=$EXECUTABLE_ARCHIVE_NAME ===" - if [ -z "${VERSION}" -o -z "${PARAMS}" ]; then echo 'You must specify a version string in $VERSION and command parameters in $PARAMS' >&2 exit 1 @@ -48,77 +40,28 @@ runs: resolve_version() { if echo "$1" | grep -q '^v[^.][^.]*\.[^.][^.]*\.'; then - echo "=== DEBUG: Version '$1' is already a complete version string ===" >&2 echo "$1" return fi - echo "=== DEBUG: Version '$1' is partial, resolving from API ===" >&2 - echo "=== DEBUG: API URL: ${RELEASES_API_URL} ===" >&2 - - # Try with auth token (matching how the action passes it) if [ -n "${GITHUB_TOKEN}" ]; then - echo "=== DEBUG: Fetching releases WITH auth token ===" >&2 API_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "${RELEASES_API_URL}") else - echo "=== DEBUG: Fetching releases WITHOUT auth token ===" >&2 API_RESPONSE=$(curl -s "${RELEASES_API_URL}") fi - echo "=== DEBUG: API response length: ${#API_RESPONSE} ===" >&2 - echo "=== DEBUG: First 500 chars of response: ===" >&2 - echo "${API_RESPONSE}" | head -c 500 >&2 - echo "" >&2 - - # Check for rate limiting or error - if echo "${API_RESPONSE}" | grep -q '"message"'; then - echo "=== DEBUG: API returned a message (possible error/rate limit): ===" >&2 - echo "${API_RESPONSE}" | grep '"message"' >&2 - fi - - # Check for tag_name entries - TAG_COUNT=$(echo "${API_RESPONSE}" | grep "tag_name" | wc -l) - echo "=== DEBUG: Found $TAG_COUNT tag_name entries ===" >&2 - - if [ "$TAG_COUNT" -gt 0 ]; then - echo "=== DEBUG: All tags found: ===" >&2 - echo "${API_RESPONSE}" | grep "tag_name" | sed -e 's/.*:[^"]*"\([^"]*\).*/\1/' >&2 - fi - RESOLVED=$(echo "${API_RESPONSE}" \ | grep "tag_name" \ | sed -e 's/.*:[^"]*"\([^"]*\).*/\1/' \ | grep "^$1\." \ | head -n 1) - echo "=== DEBUG: grep pattern used: '^$1.' ===" >&2 - echo "=== DEBUG: Resolved version: '${RESOLVED}' ===" >&2 echo "${RESOLVED}" } VERSION_TO_DOWNLOAD=$(resolve_version "${VERSION}") if [ -z "${VERSION_TO_DOWNLOAD}" ]; then echo "Unable to find a release matching '${VERSION}'" >&2 - - echo "=== DEBUG: Trying paginated API (per_page=100) ===" >&2 - if [ -n "${GITHUB_TOKEN}" ]; then - PAGINATED_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "${RELEASES_API_URL}?per_page=100") - else - PAGINATED_RESPONSE=$(curl -s "${RELEASES_API_URL}?per_page=100") - fi - echo "=== DEBUG: Paginated response length: ${#PAGINATED_RESPONSE} ===" >&2 - PAGINATED_TAGS=$(echo "${PAGINATED_RESPONSE}" | grep "tag_name" | sed -e 's/.*:[^"]*"\([^"]*\).*/\1/') - echo "=== DEBUG: Paginated tags: ===" >&2 - echo "${PAGINATED_TAGS}" >&2 - - # Also check rate limit status - echo "=== DEBUG: Checking rate limit ===" >&2 - if [ -n "${GITHUB_TOKEN}" ]; then - curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/rate_limit >&2 - else - curl -s https://api.github.com/rate_limit >&2 - fi - exit 1 fi @@ -126,8 +69,6 @@ runs: EXECUTABLE="${TEMP_DIR}/sse-contract-tests" DOWNLOAD_URL="${RELEASES_SITE_URL}/download/${VERSION_TO_DOWNLOAD}/${EXECUTABLE_ARCHIVE_NAME}" - echo "=== DEBUG: Will download from ${DOWNLOAD_URL} ===" - if [ ! -x "${EXECUTABLE}" ]; then rm -rf "${TEMP_DIR}" mkdir "${TEMP_DIR}" @@ -136,17 +77,4 @@ runs: tar -xf "${TEMP_DIR}/archive.tar.gz" -C "${TEMP_DIR}" fi - echo "=== DEBUG: Running contract tests ===" sh -c "${EXECUTABLE} $PARAMS" - - # COMMENTED OUT: Original action usage - restore after debugging - # - name: Run contract tests - # uses: launchdarkly/gh-actions/actions/contract-tests@5adb11fd6953e1bc35d9cf1fc1b4374c464e3a8b # contract-tests-v1.3.0 - # with: - # repo: sse-contract-tests - # branch: main - # test_service_port: 8000 - # debug_logging: true - # enable_persistence_tests: false - # token: ${{ inputs.token }} - # extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'" From 6f02a53c92895058c83fdb8d220362978788372e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:11:58 +0000 Subject: [PATCH 08/12] fix: Switch back to shared gh-actions/contract-tests action Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 71 ++++------------------- 1 file changed, 10 insertions(+), 61 deletions(-) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index dba9a7b..5f42dc2 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -16,65 +16,14 @@ runs: shell: bash run: make start-contract-test-service-bg - # Inline downloader script instead of shared action to ensure the GitHub - # API call for resolving the release version uses an auth token. The - # upstream sse-contract-tests/downloader/run.sh does not pass the token, - # which causes rate-limit failures on shared CI runners. - name: Run contract tests - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.token }} - VERSION: v2 - PARAMS: "-url http://localhost:8000 -port 8111 -skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks' -debug -stop-service-at-end" - run: | - set -e - - RELEASES_API_URL=https://api.github.com/repos/launchdarkly/sse-contract-tests/releases - RELEASES_SITE_URL=https://github.com/launchdarkly/sse-contract-tests/releases - EXECUTABLE_ARCHIVE_NAME=sse-contract-tests_$(uname -s)_$(uname -m).tar.gz - - if [ -z "${VERSION}" -o -z "${PARAMS}" ]; then - echo 'You must specify a version string in $VERSION and command parameters in $PARAMS' >&2 - exit 1 - fi - - resolve_version() { - if echo "$1" | grep -q '^v[^.][^.]*\.[^.][^.]*\.'; then - echo "$1" - return - fi - - if [ -n "${GITHUB_TOKEN}" ]; then - API_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "${RELEASES_API_URL}") - else - API_RESPONSE=$(curl -s "${RELEASES_API_URL}") - fi - - RESOLVED=$(echo "${API_RESPONSE}" \ - | grep "tag_name" \ - | sed -e 's/.*:[^"]*"\([^"]*\).*/\1/' \ - | grep "^$1\." \ - | head -n 1) - - echo "${RESOLVED}" - } - - VERSION_TO_DOWNLOAD=$(resolve_version "${VERSION}") - if [ -z "${VERSION_TO_DOWNLOAD}" ]; then - echo "Unable to find a release matching '${VERSION}'" >&2 - exit 1 - fi - - TEMP_DIR="/tmp/sse-contract-tests_${VERSION_TO_DOWNLOAD}" - EXECUTABLE="${TEMP_DIR}/sse-contract-tests" - DOWNLOAD_URL="${RELEASES_SITE_URL}/download/${VERSION_TO_DOWNLOAD}/${EXECUTABLE_ARCHIVE_NAME}" - - if [ ! -x "${EXECUTABLE}" ]; then - rm -rf "${TEMP_DIR}" - mkdir "${TEMP_DIR}" - echo "Downloading ${DOWNLOAD_URL}" - curl --fail -s -L -o "${TEMP_DIR}/archive.tar.gz" "${DOWNLOAD_URL}" || (echo "Download failed" >&2; exit 1) - tar -xf "${TEMP_DIR}/archive.tar.gz" -C "${TEMP_DIR}" - fi - - sh -c "${EXECUTABLE} $PARAMS" + uses: launchdarkly/gh-actions/actions/contract-tests@main + with: + repo: sse-contract-tests + version: v2 + token: ${{ inputs.token }} + test_service_port: '8000' + test_harness_port: '8111' + debug_logging: 'true' + stop_service: true + extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'" From b7426ee9af6d5cb18a6340b7623db1f99e8e7cdc Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:14:03 +0000 Subject: [PATCH 09/12] fix: Add branch: main for sse-contract-tests downloader resolution The shared gh-actions/contract-tests action defaults branch to 'v2', but sse-contract-tests uses 'main' as its default branch. Without this, the curl fetch of the downloader script returns a 404. Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index 5f42dc2..716334d 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -20,6 +20,7 @@ runs: uses: launchdarkly/gh-actions/actions/contract-tests@main with: repo: sse-contract-tests + branch: main version: v2 token: ${{ inputs.token }} test_service_port: '8000' From ede23afd3af5130fa01b27d6f7d3678b53f57a53 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:15:53 +0000 Subject: [PATCH 10/12] fix: Disable enable_persistence_tests for sse-contract-tests The sse-contract-tests binary does not support the -enable-persistence-tests flag. The shared action defaults this to true, so we must explicitly disable it. Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index 716334d..6cdb2b9 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -27,4 +27,5 @@ runs: test_harness_port: '8111' debug_logging: 'true' stop_service: true + enable_persistence_tests: false extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'" From 8155ae5dca5319552a259972f22720b85c6a9132 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:40:13 +0000 Subject: [PATCH 11/12] refactor: Remove default values from contract-tests action Per reviewer feedback, remove test_harness_port, stop_service, and enable_persistence_tests since they match the shared action's defaults. Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index 6cdb2b9..b22e133 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -24,8 +24,5 @@ runs: version: v2 token: ${{ inputs.token }} test_service_port: '8000' - test_harness_port: '8111' debug_logging: 'true' - stop_service: true - enable_persistence_tests: false extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'" From b2e2d4ba1d1954413b1386fde6ef9459cf6bb097 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:42:30 +0000 Subject: [PATCH 12/12] fix: Re-add enable_persistence_tests: false (not a default) The shared action defaults enable_persistence_tests to true (line 27 of gh-actions/actions/contract-tests/action.yml), not false as the README states. sse-contract-tests doesn't support that flag, so we must explicitly disable it. Co-Authored-By: rlamb@launchdarkly.com --- .github/actions/contract-tests/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index b22e133..3272f2f 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -25,4 +25,5 @@ runs: token: ${{ inputs.token }} test_service_port: '8000' debug_logging: 'true' + enable_persistence_tests: 'false' extra_params: "-skip 'basic parsing/large message in one chunk' -skip 'basic parsing/large message in two chunks'"