diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000..835a58af --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,5 @@ +self-hosted-runner: + labels: + # GitHub-hosted public preview label announced in actions/runner-images#14404. + # Remove this override after actionlint recognizes the hosted label. + - xcode-27 diff --git a/.github/scripts/validate-release-policy.sh b/.github/scripts/validate-release-policy.sh new file mode 100644 index 00000000..27ec7b59 --- /dev/null +++ b/.github/scripts/validate-release-policy.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${PR_TITLE:?PR_TITLE is required}" +: "${BASE_SHA:?BASE_SHA is required}" +: "${HEAD_SHA:?HEAD_SHA is required}" +is_release_message() { + local message="$1" + local header + header=$(sed -n '/[^[:space:]]/{p;q;}' <<< "$message") + + case "$header" in + fix:*|fix\(*|feat:*|feat\(*|perf:*|perf\(*|revert:*|revert\(*|Revert\ \"*) + return 0 + ;; + esac + + if grep -Eq '^[[:alpha:]]+(\([^)]*\))?!:' <<< "$header"; then + return 0 + fi + + if grep -Eq '(^|[[:space:]])(BREAKING CHANGES?:|BREAKING-CHANGE:)' <<< "$message"; then + return 0 + fi + + if grep -Eq '(^|[[:space:]])This reverts commit[[:space:]]' <<< "$message"; then + return 0 + fi + + return 1 +} + +pr_release=false +commit_release=false + +if is_release_message "$PR_TITLE"; then + pr_release=true +fi + +git cat-file -e "$BASE_SHA^{commit}" +git cat-file -e "$HEAD_SHA^{commit}" +commit_messages_file=$(mktemp) +trap 'rm -f "$commit_messages_file"' EXIT +git log --format='%B%x00' "$BASE_SHA..$HEAD_SHA" > "$commit_messages_file" + +while IFS= read -r -d '' commit_message; do + if is_release_message "$commit_message"; then + commit_release=true + break + fi +done < "$commit_messages_file" + +full_validation=false + +if [[ "$pr_release" == 'true' || "$commit_release" == 'true' || "${CI_FULL:-false}" == 'true' ]]; then + full_validation=true +fi + +if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + { + echo "pr_release=$pr_release" + echo "commit_release=$commit_release" + echo "full_validation=$full_validation" + } >> "$GITHUB_OUTPUT" +fi + +echo "Distribution policy classified (pr_release=$pr_release, commit_release=$commit_release, full_validation=$full_validation)." 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..6e2deff6 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: @@ -76,15 +81,26 @@ jobs: # Copy the tracked project file to the root of the repository cp -R SampleApp.xcodeproj.tracked SampleApp.xcodeproj + - name: Test deep-link acknowledgement state + if: ${{ inputs.platform == 'ios' && inputs.app_name == 'APN' }} + shell: bash + working-directory: . + run: | + xcrun swiftc \ + ios/wrappers/CustomerIOReactNativeDeepLinkRequestStore.swift \ + scripts/test_ios_deep_link_request_store.swift \ + -o "$RUNNER_TEMP/test-ios-deep-link-request-store" + "$RUNNER_TEMP/test-ios-deep-link-request-store" + # Install CLI tools, Ruby, and Ruby dependencies for Fastlane - name: Set IS_PRIMARY_APP run: | if [[ "${{ inputs.app_name }}" == "APN" ]]; then - echo "IS_PRIMARY_APP=true" >> $GITHUB_ENV - echo "PUSH_PROVIDER=apn" >> $GITHUB_ENV + echo "IS_PRIMARY_APP=true" >> "$GITHUB_ENV" + echo "PUSH_PROVIDER=apn" >> "$GITHUB_ENV" else - echo "IS_PRIMARY_APP=false" >> $GITHUB_ENV - echo "PUSH_PROVIDER=fcm" >> $GITHUB_ENV + echo "IS_PRIMARY_APP=false" >> "$GITHUB_ENV" + echo "PUSH_PROVIDER=fcm" >> "$GITHUB_ENV" fi - name: Set Default Firebase Distribution Groups @@ -109,7 +125,8 @@ jobs: fi # Export the groups as an environment variable - echo "firebase_distribution_groups=$(IFS=','; echo "${distribution_groups[*]}")" >> $GITHUB_ENV + groups=$(IFS=','; echo "${distribution_groups[*]}") + echo "firebase_distribution_groups=$groups" >> "$GITHUB_ENV" - name: Install sd CLI for Android builds if: ${{ inputs.platform == 'android' }} @@ -147,9 +164,11 @@ jobs: id: export-vars run: | echo "Exporting SDK and App Version..." - echo "sdk_version_name=${SDK_VERSION_NAME}" >> $GITHUB_OUTPUT - echo "app_version_name=${APP_VERSION_NAME}" >> $GITHUB_OUTPUT - echo "app_version_code=${APP_VERSION_CODE}" >> $GITHUB_OUTPUT + { + echo "sdk_version_name=${SDK_VERSION_NAME}" + echo "app_version_name=${APP_VERSION_NAME}" + echo "app_version_code=${APP_VERSION_CODE}" + } >> "$GITHUB_OUTPUT" env: SDK_VERSION_NAME: ${{ env.SDK_VERSION_NAME }} APP_VERSION_NAME: ${{ env.APP_VERSION_NAME }} @@ -180,10 +199,12 @@ jobs: APP_VERSION_CODE: ${{ env.APP_VERSION_CODE }} - name: Set Git Context Variables + env: + BRANCH_NAME_RAW: ${{ github.head_ref || github.ref_name }} + COMMIT_HASH_RAW: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} run: | - echo "BRANCH_NAME=${{ github.head_ref || github.ref_name }}" >> $GITHUB_ENV - COMMIT_HASH="${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}" - echo "COMMIT_HASH=${COMMIT_HASH:0:7}" >> $GITHUB_ENV + echo "BRANCH_NAME=$BRANCH_NAME_RAW" >> "$GITHUB_ENV" + echo "COMMIT_HASH=${COMMIT_HASH_RAW:0:7}" >> "$GITHUB_ENV" - name: Setup workspace credentials in React Native environment files run: | @@ -298,10 +319,13 @@ jobs: uses: customerio/customerio-android/.github/actions/setup-android@main - name: Setup ${{ inputs.platform_name }} environment for the ${{ inputs.platform }} sample app + # This action only selects the repository's supported Xcode version. + # Signing credentials are scoped to the distribution-only Fastlane step. if: ${{ inputs.platform == 'ios' }} 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 +338,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 +375,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..7209a065 100644 --- a/.github/workflows/build-test-sample-apps.yml +++ b/.github/workflows/build-test-sample-apps.yml @@ -1,18 +1,126 @@ 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, edited, labeled] + paths-ignore: + - '**/*.md' workflow_dispatch: + inputs: + verification_only: + description: Run unsigned compatibility builds without distribution + type: boolean + required: false + default: true push: branches: [main, feature/*] concurrency: # cancel previous workflow run if one exists. - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + # Unrelated labels and body-only edits must not cancel an in-flight release or + # ci:full run. They share a separate group, where unsigned validation repeats. + group: >- + ${{ github.workflow }}-${{ github.ref }}-${{ + github.event_name == 'workflow_dispatch' && inputs.verification_only == true && + 'manual-verification' || + github.event_name == 'pull_request' && + ((github.event.action == 'labeled' && github.event.label.name != 'ci:full') || + (github.event.action == 'edited' && + github.event.changes.title == null && + github.event.changes.base == null)) && + 'metadata' || 'validation' + }} + # Manual verification has its own group, so it can be superseded without + # canceling or delaying a signed main-branch distribution. + cancel-in-progress: >- + ${{ github.event_name == 'pull_request' || + (github.event_name == 'workflow_dispatch' && inputs.verification_only == true) || + startsWith(github.ref, 'refs/heads/feature/') }} jobs: + release_policy: + if: github.event_name == 'pull_request' + uses: ./.github/workflows/reusable-release-policy.yml + permissions: + contents: read + with: + pr_title: ${{ github.event.pull_request.title }} + base_sha: ${{ github.event.pull_request.base.sha }} + head_sha: ${{ github.event.pull_request.head.sha }} + ci_full: ${{ contains(github.event.pull_request.labels.*.name, 'ci:full') }} + + prepare: + name: Select sample app lane + # Fail closed on metadata events: an edited or unrelated-label run repeats + # unsigned verification instead of replacing a failed check with a skip. + if: always() + needs: release_policy + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.select.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Select verification or distribution matrix + id: select + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + VERIFICATION_ONLY: ${{ github.event_name == 'workflow_dispatch' && inputs.verification_only == true }} + FULL_PR: >- + ${{ needs.release_policy.outputs.full_validation == 'true' && + (github.event.action != 'edited' || + github.event.changes.title != null || + github.event.changes.base != null) && + (github.event.action != 'labeled' || github.event.label.name == 'ci:full') && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' }} + run: | + set -euo pipefail + distribution_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}]' + + if [[ "$EVENT_NAME" == 'workflow_dispatch' && "$VERIFICATION_ONLY" == 'true' ]]; then + verification_matrix=$(jq -c 'map(.should_distribute = false)' <<< "$distribution_matrix") + echo "matrix=$verification_matrix" >> "$GITHUB_OUTPUT" + elif [[ "$EVENT_NAME" == 'pull_request' && "$FULL_PR" != 'true' ]]; then + 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}]' + fcm_required=false + changed_paths="$RUNNER_TEMP/react-native-changed-paths" + git cat-file -e "$BASE_SHA^{commit}" + git cat-file -e "$HEAD_SHA^{commit}" + git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$changed_paths" + + while IFS= read -r -d '' path; do + case "$path" in + ios/*|example/*|customerio-reactnative*.podspec|package.json|package-lock.json|scripts/*|.github/workflows/build-sample-app.yml|.github/workflows/build-test-sample-apps.yml) + fcm_required=true + ;; + esac + done < "$changed_paths" + + if [[ "$fcm_required" == 'true' ]]; then + matrix=$(jq -c '. + [{"name":"FCM","cio-workspace-name":"Mobile: xReact Native FCM workspace","platform":"ios","platform_name":"iOS","platform_name_upper":"IOS","should_distribute":false}]' <<< "$matrix") + fi + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + else + echo "matrix=$distribution_matrix" >> "$GITHUB_OUTPUT" + fi + update-pr-comment: - if: ${{ github.event_name == 'pull_request' }} + if: >- + always() && + github.event_name == 'pull_request' && + needs.release_policy.outputs.full_validation == 'true' && + (github.event.action != 'edited' || + github.event.changes.title != null || + github.event.changes.base != null) && + (github.event.action != 'labeled' || github.event.label.name == 'ci:full') && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' + needs: release_policy runs-on: ubuntu-latest permissions: pull-requests: write # to be able to comment on PR @@ -40,33 +148,20 @@ 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. + # Forks and Dependabot deliberately reach this job through prepare's + # unsigned matrix. Only signing and distribution are restricted. + if: >- + always() && + needs.prepare.result == 'success' + 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,13 +171,23 @@ 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' }} - needs: [build-sample-apps, update-pr-comment] + if: >- + always() && + github.event_name == 'pull_request' && + needs.release_policy.outputs.full_validation == 'true' && + (github.event.action != 'edited' || + github.event.changes.title != null || + github.event.changes.base != null) && + (github.event.action != 'labeled' || github.event.label.name == 'ci:full') && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' + needs: [release_policy, build-sample-apps, update-pr-comment] runs-on: ubuntu-latest permissions: pull-requests: write # to be able to comment on PR @@ -97,7 +202,7 @@ jobs: platform_name: 'iOS' steps: - name: Update sample builds PR comment with build information - if: ${{ github.event_name == 'pull_request' }} + if: needs.build-sample-apps.result == 'success' uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 with: comment-id: ${{ needs.update-pr-comment.outputs.comment-id }} @@ -107,7 +212,7 @@ jobs: edit-mode: append # append new line to the existing PR comment to build a list of all sample app builds. - name: Update sample builds PR comment with build failure message - if: ${{ github.event_name == 'pull_request' && failure() }} + if: needs.build-sample-apps.result != 'success' uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 with: comment-id: ${{ needs.update-pr-comment.outputs.comment-id }} diff --git a/.github/workflows/pr-helper.yml b/.github/workflows/pr-helper.yml index 114ecdc7..bd4d6aa0 100644 --- a/.github/workflows/pr-helper.yml +++ b/.github/workflows/pr-helper.yml @@ -2,12 +2,16 @@ name: Semantic PR helper on: pull_request: - types: [opened, reopened, edited, synchronize, labeled] + types: [opened, reopened, edited, synchronize] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: lint-pr-title: runs-on: ubuntu-latest permissions: pull-requests: write # to comment on PRs - steps: + steps: - uses: levibostian/action-conventional-pr-linter@acd7e6035a4c70ae2e6aab469c791cc5ca2a989d # v4.0.1 diff --git a/.github/workflows/react-native-scene-e2e-full.yml b/.github/workflows/react-native-scene-e2e-full.yml new file mode 100644 index 00000000..b8cb10fa --- /dev/null +++ b/.github/workflows/react-native-scene-e2e-full.yml @@ -0,0 +1,83 @@ +name: Full React Native scene routing E2E + +on: + pull_request: + types: [opened, synchronize, reopened, labeled] + paths-ignore: + - '**/*.md' + +permissions: + contents: read + +jobs: + select: + name: Select scene-routing E2E lane + runs-on: ubuntu-latest + outputs: + run: ${{ steps.paths.outputs.run }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Select manual or affected-path validation + id: paths + shell: bash + env: + ACTION: ${{ github.event.action }} + LABEL: ${{ github.event.label.name }} + CI_FULL: ${{ contains(github.event.pull_request.labels.*.name, 'ci:full') }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + run=false + + if [[ "$ACTION" == 'labeled' ]]; then + if [[ "$LABEL" == 'ci:full' ]]; then + run=true + fi + elif [[ "$CI_FULL" == 'true' ]]; then + # Keep validating new commits after ci:full has been applied. + run=true + else + changed_paths="$RUNNER_TEMP/react-native-scene-e2e-paths" + git cat-file -e "$BASE_SHA^{commit}" + git cat-file -e "$HEAD_SHA^{commit}" + git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$changed_paths" + + while IFS= read -r -d '' path; do + case "$path" in + .github/workflows/react-native-scene-e2e.yml|.github/workflows/react-native-scene-e2e-full.yml|.maestro/*|scripts/test_ios_deep_link_request_store.swift) + run=true + break + ;; + ios/wrappers/CustomerIOReactNativeDeepLinkRouter.swift|ios/wrappers/CustomerIOReactNativeDeepLinkRequestStore.swift|ios/wrappers/NativeCustomerIO.swift|ios/wrappers/NativeCustomerIO.mm|ios/wrappers/liveactivities/NativeLiveActivities.*|ios/wrappers/push/*) + run=true + break + ;; + ios/wrappers/utils/CioConfigUtils.swift|src/utils/native-bridge.ts|src/utils/param-validation.ts) + run=true + break + ;; + src/customerio-cdp.ts|src/customerio-push.ts|src/customerio-liveactivities.ts|src/specs/modules/NativeCustomerIO.ts|src/specs/modules/NativeCustomerIOMessagingPush.ts|src/specs/modules/NativeCustomerIOLiveActivities.ts) + run=true + break + ;; + esac + done < "$changed_paths" + fi + + echo "run=$run" >> "$GITHUB_OUTPUT" + + full-scene-e2e: + needs: select + # Direct scene-routing paths run automatically. ci:full remains an explicit + # escape hatch for any other non-documentation PR. + if: >- + needs.select.outputs.run == 'true' && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' + uses: ./.github/workflows/react-native-scene-e2e.yml + with: + full: true diff --git a/.github/workflows/react-native-scene-e2e.yml b/.github/workflows/react-native-scene-e2e.yml index 6bf4db17..41dd5663 100644 --- a/.github/workflows/react-native-scene-e2e.yml +++ b/.github/workflows/react-native-scene-e2e.yml @@ -1,60 +1,27 @@ name: React Native scene routing E2E on: - pull_request: - 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: + workflow_call: + inputs: + full: + description: Run the label-triggered full lane. + type: boolean + required: false + default: false permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-${{ inputs.full && 'full' || 'direct' }} + 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 runs-on: xcode-27 timeout-minutes: 60 @@ -93,15 +60,6 @@ jobs: unzip -q "$maestro_zip" -d "$maestro_dir" echo "$maestro_dir/maestro/bin" >> "$GITHUB_PATH" - - name: Test deep-link acknowledgement state - shell: bash - run: | - xcrun swiftc \ - ios/wrappers/CustomerIOReactNativeDeepLinkRequestStore.swift \ - scripts/test_ios_deep_link_request_store.swift \ - -o "$RUNNER_TEMP/test-ios-deep-link-request-store" - "$RUNNER_TEMP/test-ios-deep-link-request-store" - - name: Test warm and terminated notification routing shell: bash env: diff --git a/.github/workflows/reusable-release-policy.yml b/.github/workflows/reusable-release-policy.yml new file mode 100644 index 00000000..07f12233 --- /dev/null +++ b/.github/workflows/reusable-release-policy.yml @@ -0,0 +1,41 @@ +name: Reusable release policy + +on: + workflow_call: + inputs: + pr_title: + type: string + required: true + base_sha: + type: string + required: true + head_sha: + type: string + required: true + ci_full: + type: boolean + required: true + outputs: + full_validation: + description: Whether release intent or ci:full requests distribution validation. + value: ${{ jobs.classify.outputs.full_validation }} + +jobs: + classify: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + full_validation: ${{ steps.policy.outputs.full_validation }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Classify release validation + id: policy + env: + PR_TITLE: ${{ inputs.pr_title }} + BASE_SHA: ${{ inputs.base_sha }} + HEAD_SHA: ${{ inputs.head_sha }} + CI_FULL: ${{ inputs.ci_full }} + run: bash .github/scripts/validate-release-policy.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5a1df8c0..c4b77cb3 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: @@ -11,6 +20,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '24' + cache: 'npm' - run: npm ci - name: Compile