From d17081665b589496f015d699c74a5b5587bfa280 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Sat, 29 Aug 2026 11:59:04 -0400 Subject: [PATCH 1/2] ci: add fast and full PR validation lanes --- .github/workflows/api-validation.yml | 11 ++- .github/workflows/build-sample-app.yml | 44 ++++++++++-- .github/workflows/build-test-sample-apps.yml | 71 ++++++++++++-------- .github/workflows/react-native-scene-e2e.yml | 11 ++- .github/workflows/test.yml | 11 ++- 5 files changed, 109 insertions(+), 39 deletions(-) diff --git a/.github/workflows/api-validation.yml b/.github/workflows/api-validation.yml index 7f023d44..1e56e81c 100644 --- a/.github/workflows/api-validation.yml +++ b/.github/workflows/api-validation.yml @@ -1,6 +1,13 @@ name: Public API change Validation -on: [pull_request] +on: + pull_request: + paths-ignore: + - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: validate-api: @@ -18,4 +25,4 @@ jobs: run: npm run prepare - name: Validate API - run: npx api-extractor run \ No newline at end of file + run: npx api-extractor run diff --git a/.github/workflows/build-sample-app.yml b/.github/workflows/build-sample-app.yml index b175274b..e5e4eb2f 100644 --- a/.github/workflows/build-sample-app.yml +++ b/.github/workflows/build-sample-app.yml @@ -32,6 +32,11 @@ on: description: "Name of the platform in all upper case (IOS or ANDROID)" required: true type: string + should_distribute: + description: "Whether to sign and publish the sample app" + required: false + default: true + type: boolean outputs: sdk_version_name: @@ -302,6 +307,7 @@ jobs: uses: customerio/mobile-ci-tools/github-actions/ios/setup-ios/v1@main - name: Build and upload ${{ inputs.platform_name }} ${{ inputs.app_name }} sample app via Fastlane + if: ${{ inputs.should_distribute }} id: build_app uses: maierj/fastlane-action@5a3b971aaa26776459bb26894d6c1a1a84a311a7 # v3.1.0 with: @@ -314,8 +320,27 @@ jobs: FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64: ${{ secrets.FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_CREDS_B64 }} continue-on-error: true + - name: Compile ${{ inputs.platform_name }} ${{ inputs.app_name }} sample app (verification only) + if: ${{ !inputs.should_distribute }} + id: verify_build + shell: bash + run: | + set -euo pipefail + if [[ "${{ inputs.platform }}" == 'android' ]]; then + ./android/gradlew -p android :app:assembleRelease + else + xcodebuild \ + -workspace ios/SampleApp.xcworkspace \ + -scheme SampleApp \ + -configuration Release \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO \ + build + fi + - name: Send slack notification for ${{ inputs.platform_name }} ${{ inputs.app_name }} sample app builds - if: ${{ always() && env.IS_PRIMARY_APP == 'true' }} + if: ${{ always() && inputs.should_distribute && env.IS_PRIMARY_APP == 'true' }} uses: customerio/mobile-ci-tools/github-actions/slack-notify-sample-app/v1@main with: build_status: ${{ steps.build_app.outcome }} @@ -332,11 +357,18 @@ jobs: slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} - name: Check build statuses and mark failure + if: ${{ always() }} + env: + SHOULD_DISTRIBUTE: ${{ inputs.should_distribute }} + DISTRIBUTION_OUTCOME: ${{ steps.build_app.outcome }} + VERIFICATION_OUTCOME: ${{ steps.verify_build.outcome }} run: | - FAILED_BUILDS=() - [ "${{ steps.build_app.outcome }}" != "success" ] && FAILED_BUILDS+=("(${{ inputs.platform_name }})") - - if [ ${#FAILED_BUILDS[@]} -ne 0 ]; then - echo "Build failed for: ${FAILED_BUILDS[*]}" + if [[ "$SHOULD_DISTRIBUTE" == 'true' ]]; then + outcome="$DISTRIBUTION_OUTCOME" + else + outcome="$VERIFICATION_OUTCOME" + fi + if [[ "$outcome" != 'success' ]]; then + echo "${{ inputs.platform_name }} build failed with outcome: $outcome" exit 1 fi diff --git a/.github/workflows/build-test-sample-apps.yml b/.github/workflows/build-test-sample-apps.yml index 77a4ae22..4bd2100a 100644 --- a/.github/workflows/build-test-sample-apps.yml +++ b/.github/workflows/build-test-sample-apps.yml @@ -1,18 +1,44 @@ name: Publish Test Sample Apps on: - pull_request: # build sample apps for every commit pushed to an open pull request (including drafts) + pull_request: + types: [opened, synchronize, reopened, labeled] + paths-ignore: + - '**/*.md' workflow_dispatch: push: - branches: [main, feature/*] + branches: [main] concurrency: # cancel previous workflow run if one exists. - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }}-${{ (github.event.action == 'labeled' && github.event.label.name != 'ci:full') && github.run_id || 'validation' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: + prepare: + name: Select sample app lane + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.select.outputs.matrix }} + steps: + - name: Select verification or distribution matrix + id: select + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + FULL_PR: ${{ contains(github.event.pull_request.labels.*.name, 'ci:full') && github.event.pull_request.head.repo.full_name == github.repository }} + run: | + if [[ "$EVENT_NAME" == 'pull_request' && "$FULL_PR" != 'true' ]]; then + echo 'matrix=[{"name":"APN","cio-workspace-name":"Mobile: React Native","platform":"ios","platform_name":"iOS","platform_name_upper":"IOS","should_distribute":false},{"name":"APN","cio-workspace-name":"Mobile: React Native","platform":"android","platform_name":"Android","platform_name_upper":"ANDROID","should_distribute":false}]' >> "$GITHUB_OUTPUT" + else + echo 'matrix=[{"name":"APN","cio-workspace-name":"Mobile: React Native","platform":"ios","platform_name":"iOS","platform_name_upper":"IOS","should_distribute":true},{"name":"FCM","cio-workspace-name":"Mobile: xReact Native FCM workspace","platform":"ios","platform_name":"iOS","platform_name_upper":"IOS","should_distribute":true},{"name":"APN","cio-workspace-name":"Mobile: React Native","platform":"android","platform_name":"Android","platform_name_upper":"ANDROID","should_distribute":true}]' >> "$GITHUB_OUTPUT" + fi + update-pr-comment: - if: ${{ github.event_name == 'pull_request' }} + if: >- + (github.event.action != 'labeled' || github.event.label.name == 'ci:full') && + github.event_name == 'pull_request' && + contains(github.event.pull_request.labels.*.name, 'ci:full') && + github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest permissions: pull-requests: write # to be able to comment on PR @@ -40,33 +66,19 @@ jobs: Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request. --- - ${{ steps.build.outputs.build-log }} + Builds are in progress. This comment will be updated when they finish. edit-mode: replace # replace the existing comment with new content since we are creating new builds build-sample-apps: - if: ${{ always() }} # do not skip running this step if update-pr-comment does not run - needs: [update-pr-comment] # wait for PR comment to be created saying new builds are being made. + if: >- + always() && + needs.prepare.result == 'success' && + (github.event.action != 'labeled' || github.event.label.name == 'ci:full') + needs: [prepare, update-pr-comment] strategy: fail-fast: false # if one sample app fails to build, let the other sample apps continue to build and not cancel them. matrix: # Use a matrix allowing us to build multiple apps in parallel. Just add an entry to the matrix and it will build! - sample-app: - # List all sample apps you want to have compiled. - # List item is name of directory inside of "Apps" directory for the corresponding app to compile. - - name: 'APN' - cio-workspace-name: 'Mobile: React Native' - platform: 'ios' - platform_name: 'iOS' - platform_name_upper: 'IOS' - - name: 'FCM' - cio-workspace-name: 'Mobile: xReact Native FCM workspace' - platform: 'ios' - platform_name: 'iOS' - platform_name_upper: 'IOS' - - name: 'APN' - cio-workspace-name: 'Mobile: React Native' - platform: 'android' - platform_name: 'Android' - platform_name_upper: 'ANDROID' + sample-app: ${{ fromJSON(needs.prepare.outputs.matrix) }} name: Building the ${{ matrix.sample-app.platform_name }} ${{ matrix.sample-app.name }} sample app uses: ./.github/workflows/build-sample-app.yml @@ -76,12 +88,17 @@ jobs: platform: ${{ matrix.sample-app.platform }} platform_name: ${{ matrix.sample-app.platform_name }} platform_name_upper: ${{ matrix.sample-app.platform_name_upper }} + should_distribute: ${{ matrix.sample-app.should_distribute }} secrets: inherit # Update PR comment with build information update-pr-comment-with-status: - if: ${{ github.event_name == 'pull_request' }} + if: >- + (github.event.action != 'labeled' || github.event.label.name == 'ci:full') && + github.event_name == 'pull_request' && + contains(github.event.pull_request.labels.*.name, 'ci:full') && + github.event.pull_request.head.repo.full_name == github.repository needs: [build-sample-apps, update-pr-comment] runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/react-native-scene-e2e.yml b/.github/workflows/react-native-scene-e2e.yml index 6bf4db17..2c409d0f 100644 --- a/.github/workflows/react-native-scene-e2e.yml +++ b/.github/workflows/react-native-scene-e2e.yml @@ -2,6 +2,7 @@ name: React Native scene routing E2E on: pull_request: + types: [opened, synchronize, reopened, labeled] paths: - '.github/workflows/react-native-scene-e2e.yml' - '.maestro/fixtures/customerio_scene_cold.apns' @@ -48,13 +49,17 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-${{ (github.event.action == 'labeled' && github.event.label.name != 'ci:full') && github.run_id || 'validation' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: route-notifications: name: RN scene notification routing on Xcode 27 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + if: >- + (github.event.action != 'labeled' || github.event.label.name == 'ci:full') && + (github.event_name != 'pull_request' || + (contains(github.event.pull_request.labels.*.name, 'ci:full') && + github.event.pull_request.head.repo.full_name == github.repository)) runs-on: xcode-27 timeout-minutes: 60 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5a1df8c0..229a22be 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,15 @@ name: Test -on: [push, pull_request] +on: + push: + branches: [main] + pull_request: + paths-ignore: + - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: test-deploy: From c09cbd41494aa4f34fae8e702c9a712b72fa4d60 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Sat, 29 Aug 2026 17:49:01 -0400 Subject: [PATCH 2/2] ci: tighten and streamline PR validation --- .github/workflows/build-sample-app.yml | 9 +++-- .github/workflows/pr-helper.yml | 2 +- .github/workflows/react-native-scene-e2e.yml | 38 -------------------- .github/workflows/test.yml | 1 + 4 files changed, 8 insertions(+), 42 deletions(-) diff --git a/.github/workflows/build-sample-app.yml b/.github/workflows/build-sample-app.yml index e5e4eb2f..cc9ae0c4 100644 --- a/.github/workflows/build-sample-app.yml +++ b/.github/workflows/build-sample-app.yml @@ -227,10 +227,13 @@ jobs: if: ${{ inputs.platform == 'ios' }} uses: actions/cache@v4 with: - path: example/Pods - key: ${{ runner.os }}-${{ inputs.app_name }}-Pods-${{ github.ref }} + path: | + ~/Library/Caches/CocoaPods + ~/.cocoapods + example/ios/Pods + key: ${{ runner.os }}-${{ inputs.app_name }}-Pods-${{ hashFiles('package.json', 'package-lock.json', 'example/package.json', 'example/package-lock.json', 'example/ios/Podfile', 'example/Gemfile.lock', 'customerio-reactnative.podspec', 'customerio-reactnative-richpush.podspec') }} restore-keys: | - ${{ runner.os }}-${{ inputs.app_name }}-Pods + ${{ runner.os }}-${{ inputs.app_name }}-Pods- - name: Install dependencies to build SDK run: npm ci diff --git a/.github/workflows/pr-helper.yml b/.github/workflows/pr-helper.yml index 114ecdc7..ed43e73a 100644 --- a/.github/workflows/pr-helper.yml +++ b/.github/workflows/pr-helper.yml @@ -2,7 +2,7 @@ name: Semantic PR helper on: pull_request: - types: [opened, reopened, edited, synchronize, labeled] + types: [opened, reopened, edited, synchronize] jobs: lint-pr-title: diff --git a/.github/workflows/react-native-scene-e2e.yml b/.github/workflows/react-native-scene-e2e.yml index 2c409d0f..62400dec 100644 --- a/.github/workflows/react-native-scene-e2e.yml +++ b/.github/workflows/react-native-scene-e2e.yml @@ -3,44 +3,6 @@ name: React Native scene routing E2E on: pull_request: types: [opened, synchronize, reopened, labeled] - paths: - - '.github/workflows/react-native-scene-e2e.yml' - - '.maestro/fixtures/customerio_scene_cold.apns' - - '.maestro/fixtures/customerio_scene_declined.apns' - - '.maestro/fixtures/customerio_scene_warm.apns' - - '.maestro/fixtures/react-native-scene/**' - - '.maestro/run_scene_push.sh' - - '.maestro/scene_push_open.yaml' - - '.maestro/scene_push_declined.yaml' - - '.maestro/scene_push_prepare.yaml' - - '.maestro/scene_push_warm.yaml' - - '.maestro/scene_url_open.yaml' - - 'src/customerio-cdp.ts' - - 'src/customerio-push.ts' - - 'src/index.ts' - - 'src/native-logger-listener.ts' - - 'src/specs/modules/NativeCustomerIO.ts' - - 'src/specs/modules/NativeCustomerIOMessagingPush.ts' - - 'src/types/data-pipelines.ts' - - 'src/types/index.ts' - - 'src/types/internal.ts' - - 'src/types/push.ts' - - 'src/utils/native-bridge.ts' - - 'src/utils/param-validation.ts' - - 'ios/wrappers/CustomerIOReactNativeDeepLinkRouter.swift' - - 'ios/wrappers/CustomerIOReactNativeDeepLinkRequestStore.swift' - - 'ios/wrappers/NativeCustomerIO.swift' - - 'ios/wrappers/NativeCustomerIO.mm' - - 'ios/wrappers/liveactivities/NativeLiveActivities.swift' - - 'ios/wrappers/push/**' - - 'ios/wrappers/utils/CioConfigUtils.swift' - - 'ios/wrappers/utils/CioConstants.swift' - - 'ios/wrappers/CustomerioReactnative-Bridging-Header.h' - - 'ios/cocoapods_deployment_target.rb' - - 'customerio-reactnative.podspec' - - 'scripts/test_ios_deep_link_request_store.swift' - - 'package.json' - - 'package-lock.json' schedule: - cron: '47 7 * * *' workflow_dispatch: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 229a22be..c4b77cb3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,6 +20,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '24' + cache: 'npm' - run: npm ci - name: Compile