Skip to content

[NT-2885] Create Kotlin SDK for Android #1190

[NT-2885] Create Kotlin SDK for Android

[NT-2885] Create Kotlin SDK for Android #1190

name: Main Pipeline
permissions:
contents: read
on:
pull_request:
push:
branches:
- main
concurrency:
group: main-pipeline-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
name: 🔎 Detect Changes
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
outputs:
build: ${{ steps.filter.outputs.build }}
unit: ${{ steps.filter.outputs.unit }}
e2e_node_sdk: ${{ steps.filter.outputs.e2e_node_sdk }}
e2e_node_sdk_web_sdk: ${{ steps.filter.outputs.e2e_node_sdk_web_sdk }}
e2e_web_sdk: ${{ steps.filter.outputs.e2e_web_sdk }}
e2e_web_sdk_react: ${{ steps.filter.outputs.e2e_web_sdk_react }}
e2e_react_web_sdk: ${{ steps.filter.outputs.e2e_react_web_sdk }}
e2e_react_native_android: ${{ steps.filter.outputs.e2e_react_native_android }}
e2e_android: ${{ steps.filter.outputs.e2e_android }}
e2e_ios: ${{ steps.filter.outputs.e2e_ios }}
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
# Intentional CI policy:
# - Implementation E2E jobs run only for implementations relevant to changed paths.
# - Skipped E2E jobs due to unmatched filters are expected behavior.
# - Keep this mapping aligned with CONTRIBUTING.md ("E2E Coverage and Environment").
filters: |
build:
- 'lib/**'
- 'packages/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'tsconfig*.json'
- '.github/workflows/main-pipeline.yaml'
unit:
- 'lib/**'
- 'packages/**'
- '**/rstest.config.ts'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.github/workflows/main-pipeline.yaml'
# Node SSR Only implementation E2E coverage scope.
e2e_node_sdk:
- 'implementations/node-sdk/**'
- 'lib/**'
- 'packages/node/node-sdk/**'
- 'packages/universal/core-sdk/**'
- 'packages/universal/api-client/**'
- 'packages/universal/api-schemas/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'
# Node SSR + Web Vanilla implementation E2E coverage scope.
e2e_node_sdk_web_sdk:
- 'implementations/node-sdk+web-sdk/**'
- 'lib/**'
- 'packages/node/node-sdk/**'
- 'packages/web/web-sdk/**'
- 'packages/web/preview-panel/**'
- 'packages/universal/core-sdk/**'
- 'packages/universal/api-client/**'
- 'packages/universal/api-schemas/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'
# Web Vanilla implementation E2E coverage scope.
e2e_web_sdk:
- 'implementations/web-sdk/**'
- 'lib/**'
- 'packages/web/web-sdk/**'
- 'packages/web/preview-panel/**'
- 'packages/universal/core-sdk/**'
- 'packages/universal/api-client/**'
- 'packages/universal/api-schemas/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'
# React + Web SDK implementation E2E coverage scope.
e2e_web_sdk_react:
- 'implementations/web-sdk_react/**'
- 'lib/**'
- 'packages/web/web-sdk/**'
- 'packages/web/preview-panel/**'
- 'packages/universal/core-sdk/**'
- 'packages/universal/api-client/**'
- 'packages/universal/api-schemas/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'
# React Web SDK (optimization-react-web) implementation E2E coverage scope.
e2e_react_web_sdk:
- 'implementations/react-web-sdk/**'
- 'lib/**'
- 'packages/web/frameworks/react-web-sdk/**'
- 'packages/web/web-sdk/**'
- 'packages/web/preview-panel/**'
- 'packages/universal/core-sdk/**'
- 'packages/universal/api-client/**'
- 'packages/universal/api-schemas/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'
# React Native Android implementation E2E coverage scope.
e2e_react_native_android:
- 'implementations/react-native-sdk/**'
- 'lib/**'
- 'packages/react-native-sdk/**'
- 'packages/universal/core-sdk/**'
- 'packages/universal/api-client/**'
- 'packages/universal/api-schemas/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'
# Android native implementation E2E coverage scope.
e2e_android:
- 'implementations/android-sdk/**'
- 'lib/mocks/**'
- 'packages/android/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'
# iOS native implementation E2E coverage scope.
e2e_ios:
- 'implementations/ios-sdk/**'
- 'lib/mocks/**'
- 'packages/ios/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'
setup:
name: 🛠️ pnpm install
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up pnpm cache (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
license-check:
name: 📄 License Check
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
needs: setup
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
filters: |
lock:
- 'pnpm-lock.yaml'
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up pnpm cache (Namespace)
if: steps.filter.outputs.lock == 'true'
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- if: steps.filter.outputs.lock == 'true'
run: pnpm install --prefer-offline --frozen-lockfile
- if: steps.filter.outputs.lock == 'true'
run: |
echo "In case of error, please see ./CONTRIBUTING.md"
pnpx license-checker \
--summary \
--production \
--relativeLicensePath \
--onlyAllow 'MIT;Apache-2.0;ISC;BSD-3-Clause;BSD-2-Clause;MIT*;Apache 2.0;Unlicense;Unlicensed;:CC0-1.0;CC-BY-4.0;WTFPL;0BSD;UNLICENSED;Python-2.0;MPL-2.0;CC-BY-3.0;CC0-1.0'
format:
name: 🎨 Format Check
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
needs: setup
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- run: pnpm format:check
build:
name: 📦 Build
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: setup
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- run: pnpm build:ci
- name: Pack SDK tarballs for implementations
run: |
rm -rf pkgs
mkdir -p pkgs
pnpm run pack:pkgs
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sdk-package-tarballs
path: pkgs/*.tgz
if-no-files-found: error
retention-days: 1
type-check:
name: 🔷 Type Check
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
needs: setup
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- run: pnpm typecheck
lint:
name: 🎨 Lint
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
needs: [setup, build]
if: >-
always() && needs.setup.result == 'success' && (needs.build.result == 'success' ||
needs.build.result == 'skipped')
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
if: needs.build.result == 'success'
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm lint
- if: needs.build.result == 'skipped'
run: pnpm build:pkgs
- run: pnpm store prune
- run: pnpm run implementation:install -- --no-frozen-lockfile
- run: pnpm implementation:lint
test-unit:
name: 🧪 Test (${{ matrix.package }})
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes]
if: needs.changes.outputs.unit == 'true'
strategy:
fail-fast: true
matrix:
include:
- package: '@contentful/optimization-api-schemas'
- package: '@contentful/optimization-api-client'
- package: '@contentful/optimization-core'
- package: '@contentful/optimization-node'
- package: '@contentful/optimization-web'
- package: '@contentful/optimization-web-preview-panel'
- package: '@contentful/optimization-react-native'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- run: pnpm --filter ${{ matrix.package }} test:unit
e2e-node-sdk:
name: 🖥️ E2E Node SSR Only
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_node_sdk == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: cp implementations/node-sdk/.env.example implementations/node-sdk/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run: pnpm run implementation:node-sdk -- implementation:install -- --no-frozen-lockfile
- run: pnpm run implementation:node-sdk -- implementation:playwright:install -- --with-deps
- run: pnpm run implementation:node-sdk -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-node-sdk
path: |
./implementations/node-sdk/playwright-report/
./implementations/node-sdk/test-results/
retention-days: 1
e2e-node-sdk-web-sdk:
name: 🖥️ E2E Node SSR + Web Vanilla
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_node_sdk_web_sdk == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: >
cp implementations/node-sdk+web-sdk/.env.example implementations/node-sdk+web-sdk/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run:
pnpm run implementation:node-sdk+web-sdk -- implementation:install -- --no-frozen-lockfile
- run:
pnpm run implementation:node-sdk+web-sdk -- implementation:playwright:install --
--with-deps
- run: pnpm run implementation:node-sdk+web-sdk -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-node-sdk+web-sdk
path: |
./implementations/node-sdk+web-sdk/playwright-report/
./implementations/node-sdk+web-sdk/test-results/
retention-days: 1
e2e-web-sdk:
name: 🖥️ E2E Web Vanilla
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_web_sdk == 'true'
steps:
- uses: docker/setup-compose-action@8cccb8c14b6500aaffebff1aa49c502c34d2e5e6 # v2.1.0
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: cp implementations/web-sdk/.env.example implementations/web-sdk/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run: pnpm run implementation:web-sdk -- implementation:install -- --no-frozen-lockfile
- run: pnpm run implementation:web-sdk -- implementation:playwright:install -- --with-deps
- run: pnpm run implementation:web-sdk -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-web-sdk
path: |
./implementations/web-sdk/playwright-report/
./implementations/web-sdk/test-results/
retention-days: 1
e2e-web-sdk_react:
name: 🖥️ E2E React + Web SDK
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_web_sdk_react == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: cp implementations/web-sdk_react/.env.example implementations/web-sdk_react/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run: pnpm run implementation:web-sdk_react -- implementation:install -- --no-frozen-lockfile
- run:
pnpm run implementation:web-sdk_react -- implementation:playwright:install -- --with-deps
- run: pnpm run implementation:web-sdk_react -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-web-sdk_react
path: |
./implementations/web-sdk_react/playwright-report/
./implementations/web-sdk_react/test-results/
retention-days: 1
e2e-react-web-sdk:
name: ⚛️ E2E React Web SDK
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_react_web_sdk == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: cp implementations/react-web-sdk/.env.example implementations/react-web-sdk/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run: pnpm run implementation:react-web-sdk -- implementation:install -- --no-frozen-lockfile
- run:
pnpm run implementation:react-web-sdk -- implementation:playwright:install -- --with-deps
- run: pnpm run implementation:react-web-sdk -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-react-web-sdk
path: |
./implementations/react-web-sdk/playwright-report/
./implementations/react-web-sdk/test-results/
retention-days: 1
e2e-react-native-android-build:
name: 📱 Build React Native Android APK
runs-on: namespace-profile-linux-16-vcpu-32-gb-ram-optimal
timeout-minutes: 30
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_react_native_android == 'true'
env:
CI: 'true'
GRADLE_OPTS: >-
-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.jvmargs=-Xmx4g
-Dkotlin.daemon.jvm.options=-Xmx2g
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set Android SDK environment variables
run: |
echo "ANDROID_SDK_ROOT=$HOME/.android/sdk" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$HOME/.android/sdk" >> "$GITHUB_ENV"
- name: Prepare cache directories
run: |
mkdir -p "$HOME/.android/sdk" "$HOME/.android/avd" "$HOME/.android/cache"
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
gradle
path: |
~/.android/sdk
~/.android/cache
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Install JS dependencies
run: pnpm install --prefer-offline --frozen-lockfile
- name: Download SDK package tarballs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- name: Prune pnpm store metadata cache
run: pnpm store prune
- name: Install React Native implementation dependencies
run: >
pnpm run implementation:react-native-sdk -- implementation:install -- --no-frozen-lockfile
- name: Create .env from .env.example
run: cp implementations/react-native-sdk/.env.example implementations/react-native-sdk/.env
- name: Build Android app (Detox release)
run: pnpm run implementation:react-native-sdk -- test:e2e:android:build:release
- name: Upload Detox APKs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detox-android-apks
path: implementations/react-native-sdk/android/app/build/outputs/apk/
if-no-files-found: error
retention-days: 1
e2e-android-sdk:
name: 🤖 E2E Android Native
runs-on: namespace-profile-linux-16-vcpu-32-gb-ram-optimal
timeout-minutes: 45
needs: [setup, changes]
if: needs.changes.outputs.e2e_android == 'true'
env:
CI: 'true'
GRADLE_OPTS: >-
-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.jvmargs=-Xmx4g
-Dkotlin.daemon.jvm.options=-Xmx2g
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set Android SDK environment variables
run: |
echo "ANDROID_SDK_ROOT=$HOME/.android/sdk" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$HOME/.android/sdk" >> "$GITHUB_ENV"
- name: Prepare cache directories
run: |
mkdir -p "$HOME/.android/sdk" "$HOME/.android/avd" "$HOME/.android/cache"
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
gradle
path: |
~/.android/sdk
~/.android/avd
~/.android/cache
- name: Install system dependencies (Android emulator)
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
ca-certificates curl unzip zip git \
netcat-openbsd cpu-checker \
libgl1 libnss3 libx11-6 libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 libxtst6 \
libxi6 libxrender1 libxkbcommon0 libgbm1 libdbus-1-3 libdrm2 libpulse0
sudo apt-get install -y --no-install-recommends libasound2 || sudo apt-get install -y --no-install-recommends libasound2t64
- name: Verify KVM is available
run: |
if [ ! -e /dev/kvm ]; then
echo "/dev/kvm not found; Android hardware acceleration will not work." >&2
exit 1
fi
ls -l /dev/kvm
sudo kvm-ok || true
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Install JS dependencies
run: pnpm install --prefer-offline --frozen-lockfile
- name: Build Android bridge JS bundle
run: pnpm --filter @contentful/optimization-android-bridge build
- name: Build app and test APKs
working-directory: implementations/android-sdk
run: ./gradlew :app:assembleDebug :uitests:assembleDebug
- name: Start Mock Server
run: |
pnpm --dir lib/mocks serve > /tmp/mock-server.log 2>&1 &
echo $! > /tmp/mock-server.pid
for i in {1..60}; do
if nc -z localhost 8000 2>/dev/null; then
echo "Mock server is ready"
break
fi
echo "Waiting for mock server... ($i/60)"
sleep 1
done
if ! nc -z localhost 8000 2>/dev/null; then
echo "Mock server failed to start:"
cat /tmp/mock-server.log
exit 1
fi
- name: Run Android E2E Tests (emulator)
uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0
with:
api-level: 35
arch: x86_64
target: google_apis
profile: pixel_7
avd-name: test
force-avd-creation: true
emulator-boot-timeout: 600
cores: 6
ram-size: 4096M
disk-size: 8G
disable-animations: true
emulator-options: -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect
script: |
echo "Disabling animations..."
adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global animator_duration_scale 0
echo "Installing APKs..."
adb install -r implementations/android-sdk/app/build/outputs/apk/debug/app-debug.apk
adb install -r implementations/android-sdk/uitests/build/outputs/apk/debug/uitests-debug.apk
echo "Setting up adb reverse port forwarding..."
adb reverse tcp:8000 tcp:8000
sleep 3
adb shell "for i in 1 2 3 4 5 6 7 8 9 10; do nc -z localhost 8000 2>/dev/null && echo 'Mock server tunnel verified' && exit 0; sleep 1; done; echo 'WARNING: tunnel verification timed out'"
echo "Running UI Automator 2 E2E tests..."
adb shell am instrument -w com.contentful.optimization.uitests/androidx.test.runner.AndroidJUnitRunner 2>&1 | tee /tmp/test-output.log
grep -q "FAILURES" /tmp/test-output.log && { echo "::error::Android UI tests failed"; exit 1; } || true
grep -q "Process crashed" /tmp/test-output.log && { echo "::error::Test process crashed"; exit 1; } || true
- name: Upload logs on failure
if: failure()
run: |
echo "=== Mock Server Logs ==="
cat /tmp/mock-server.log || echo "No mock server logs found"
- name: Stop Mock Server
if: always()
run: |
kill $(cat /tmp/mock-server.pid) 2>/dev/null || true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: ci-results-android-sdk
path: |
implementations/android-sdk/logs/
/tmp/mock-server.log
/tmp/test-output.log
retention-days: 7
e2e-ios-sdk-build:
name: 🍎 Build iOS UI Test Bundles
runs-on: namespace-profile-macos-apple-silicon-arm64-6-cpu-14-gb
timeout-minutes: 30
needs: [setup, changes]
if: needs.changes.outputs.e2e_ios == 'true'
env:
DERIVED_DATA: /tmp/optimization-ios-derived-data
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Install XcodeGen and xcbeautify
run: brew install xcodegen xcbeautify
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
path: |
~/Library/Caches/org.swift.swiftpm
- name: Show toolchain
run: |
xcodebuild -version
xcrun simctl list runtimes | head
- run: pnpm install --prefer-offline --frozen-lockfile
- name: Build iOS UI test bundles (SwiftUI + UIKit)
run: pnpm run implementation:ios-sdk -- test:e2e:ios:build:release
- name: Stage Build/Products for artifact
run: |
mkdir -p /tmp/ios-artifact
cp -R "$DERIVED_DATA/Build/Products" /tmp/ios-artifact/Products
ls /tmp/ios-artifact/Products/*.xctestrun
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ios-uitest-bundles
path: /tmp/ios-artifact/
if-no-files-found: error
retention-days: 1
e2e-react-native-android:
name: 📱 E2E React Native Android (shard ${{ matrix.shard }}/2)
runs-on: namespace-profile-linux-16-vcpu-32-gb-ram-optimal
timeout-minutes: 45
needs: [setup, changes, e2e-react-native-android-build]
if: needs.changes.outputs.e2e_react_native_android == 'true'
strategy:
fail-fast: false
matrix:
shard: [1, 2]
env:
DETOX_AVD_NAME: test
CI: 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set Android SDK environment variables
run: |
echo "ANDROID_SDK_ROOT=$HOME/.android/sdk" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$HOME/.android/sdk" >> "$GITHUB_ENV"
- name: Prepare cache directories
run: |
mkdir -p "$HOME/.android/sdk" "$HOME/.android/avd" "$HOME/.android/cache"
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
apt
pnpm
path: |
~/.android/sdk
~/.android/avd
~/.android/cache
- name: Install system dependencies (Android emulator + React Native)
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
ca-certificates curl unzip zip git \
python3 python3-pip build-essential \
netcat-openbsd watchman \
cpu-checker \
libgl1 libnss3 libx11-6 libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 libxtst6 \
libxi6 libxrender1 libxkbcommon0 libgbm1 libdbus-1-3 libdrm2 libpulse0
sudo apt-get install -y --no-install-recommends libasound2 || sudo apt-get install -y --no-install-recommends libasound2t64
- name: Verify KVM is available
run: |
if [ ! -e /dev/kvm ]; then
echo "/dev/kvm not found; Android hardware acceleration will not work." >&2
exit 1
fi
ls -l /dev/kvm
sudo kvm-ok || true
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Install JS dependencies
run: pnpm install --prefer-offline --frozen-lockfile
- name: Download SDK package tarballs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- name: Prune pnpm store metadata cache
run: pnpm store prune
- name: Install React Native implementation dependencies
run: >
pnpm run implementation:react-native-sdk -- implementation:install -- --no-frozen-lockfile
- name: Create .env from .env.example
run: cp implementations/react-native-sdk/.env.example implementations/react-native-sdk/.env
- name: Download pre-built Detox APKs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: detox-android-apks
path: implementations/react-native-sdk/android/app/build/outputs/apk/
- name: Start Mock Server
run: |
pnpm --dir lib/mocks serve > /tmp/mock-server.log 2>&1 &
echo $! > /tmp/mock-server.pid
for i in {1..60}; do
if nc -z localhost 8000 2>/dev/null; then
echo "Mock server is ready"
break
fi
echo "Waiting for mock server... ($i/60)"
sleep 1
done
if ! nc -z localhost 8000 2>/dev/null; then
echo "Mock server failed to start:"
cat /tmp/mock-server.log
exit 1
fi
- name: Run Android E2E Tests (emulator)
uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0
with:
api-level: 36
arch: x86_64
target: google_apis
profile: pixel_tablet
avd-name: test
force-avd-creation: true
emulator-boot-timeout: 600
cores: 6
ram-size: 4096M
disk-size: 8G
disable-animations: true
emulator-options: -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect
script: |
echo "Verifying JAVA_HOME: $JAVA_HOME"
java -version
echo "Setting up adb reverse port forwarding..."
adb reverse tcp:8000 tcp:8000
echo "Running E2E tests..."
pnpm run implementation:react-native-sdk -- test:e2e:android:run:release -- --retries 1 --shard=${{ matrix.shard }}/2
- name: Upload logs on failure
if: failure()
run: |
echo "=== Mock Server Logs ==="
cat /tmp/mock-server.log || echo "No mock server logs found"
- name: Stop Mock Server
if: always()
run: |
kill $(cat /tmp/mock-server.pid) 2>/dev/null || true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: ci-results-react-native-android-shard-${{ matrix.shard }}
path: |
implementations/react-native-sdk/.detox/
/tmp/mock-server.log
retention-days: 7
e2e-ios-sdk:
name: 🍎 E2E iOS UI (${{ matrix.scheme }})
runs-on: namespace-profile-macos-apple-silicon-arm64-6-cpu-14-gb
timeout-minutes: 45
needs: [setup, changes, e2e-ios-sdk-build]
if: needs.changes.outputs.e2e_ios == 'true'
strategy:
fail-fast: false
matrix:
include:
- scheme: SwiftUI
- scheme: UIKit
env:
DERIVED_DATA: /tmp/optimization-ios-derived-data
IOS_SCHEME: ${{ matrix.scheme }}
IOS_SIM_NAME: 'iPhone 16'
IOS_SIM_OS: 'latest'
# Smoke mode for the first PR — restrict to one test class per scheme.
# Remove this env var in a follow-up PR to enable the full suite.
IOS_ONLY_TESTING: OptimizationAppUITests${{ matrix.scheme }}/PreviewPanelTests
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Install xcbeautify
run: brew install xcbeautify
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ios-uitest-bundles
path: /tmp/ios-artifact/
- name: Reconstruct DerivedData layout at stable path
run: |
mkdir -p "$DERIVED_DATA/Build"
mv /tmp/ios-artifact/Products "$DERIVED_DATA/Build/Products"
ls "$DERIVED_DATA/Build/Products"
- name: Boot iOS Simulator
run: |
DEVICE_UDID=$(xcrun simctl create "ci-${{ matrix.scheme }}" "$IOS_SIM_NAME")
echo "DEVICE_UDID=$DEVICE_UDID" >> "$GITHUB_ENV"
xcrun simctl boot "$DEVICE_UDID"
xcrun simctl bootstatus "$DEVICE_UDID" -b
- name: Start Mock Server
run: |
pnpm --dir lib/mocks serve > /tmp/mock-server.log 2>&1 &
echo $! > /tmp/mock-server.pid
for i in {1..60}; do
if nc -z localhost 8000 2>/dev/null; then
echo "Mock server is ready"
break
fi
echo "Waiting for mock server... ($i/60)"
sleep 1
done
if ! nc -z localhost 8000 2>/dev/null; then
echo "Mock server failed to start:"
cat /tmp/mock-server.log
exit 1
fi
- name: Verify built xctestrun exists for ${{ matrix.scheme }}
shell: 'bash -eo pipefail {0}'
run: |
shopt -s nullglob
matches=("$DERIVED_DATA"/Build/Products/OptimizationApp"$IOS_SCHEME"_*.xctestrun)
if [ ${#matches[@]} -eq 0 ]; then
echo "No xctestrun found for scheme $IOS_SCHEME under $DERIVED_DATA/Build/Products/" >&2
ls -la "$DERIVED_DATA/Build/Products/" || true
exit 1
fi
printf 'Found xctestrun: %s\n' "${matches[@]}"
- name: Run iOS UI tests (${{ matrix.scheme }})
shell: 'bash -eo pipefail {0}'
run: pnpm run implementation:ios-sdk -- test:e2e:ios:run:release
- name: Stop Mock Server
if: always()
run: kill $(cat /tmp/mock-server.pid) 2>/dev/null || true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-ios-${{ matrix.scheme }}
path: |
/tmp/optimization-ios-derived-data/Test-*.xcresult
/tmp/mock-server.log
retention-days: 7