diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a83508c95..d0db011c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,13 @@ name: CI on: push: - branches: [master, beta] + branches: [master, beta, ci2] pull_request: branches: [master, beta] workflow_dispatch: jobs: cancel_previous: + if: ${{ github.event_name != 'push' }} runs-on: ubuntu-latest steps: - uses: styfle/cancel-workflow-action@0.12.1 @@ -15,85 +16,121 @@ jobs: build-and-test: needs: cancel_previous runs-on: 'ubuntu-latest' - env: - YARN_ENABLE_HARDENED_MODE: 0 steps: - uses: actions/checkout@v4 - # Workaround for corepack enable in node - # Source: (https://github.com/actions/setup-node/issues/899#issuecomment-1828798029) - - uses: actions/setup-node@v4 + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@v1.3.1 + - name: devbox installer + uses: jetify-com/devbox-install-action@v0.14.0 with: - node-version: 20 - - run: corepack enable - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: yarn - # End workaround - - - name: Install - run: yarn install --immutable - - name: Build - run: yarn build - - name: Lint - run: yarn lint - - name: Test - run: yarn test --coverage + enable-cache: 'true' + - name: Nix GC (pre-build) + run: | + nix-collect-garbage -d || true + du -sh /nix/store || true + - name: build + run: devbox run build run-e2e-ios: - runs-on: 'macos-13' + runs-on: ['macos-15' ] env: YARN_ENABLE_HARDENED_MODE: 0 + XCODE_VERSION: '26.1.1' + strategy: + matrix: + include: + - name: ios-min + ios-device: "iPhone 14" + ios-runtime: "18.5" + - name: ios-latest + ios-device: "iPhone 17" + ios-runtime: "26.1" steps: + - uses: actions/checkout@v4 + - name: Free Disk Space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable - - name: Install applesimutils - run: | - HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null - HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null - - - uses: actions/checkout@v4 - # Workaround for corepack enable in node - # Source: (https://github.com/actions/setup-node/issues/899#issuecomment-1828798029) - - uses: actions/setup-node@v4 + xcode-version: '26.1.1' + - name: CocoaPods cache + uses: actions/cache@v4 with: - node-version: 20 - - run: corepack enable - - uses: actions/setup-node@v4 + path: | + ~/Library/Caches/CocoaPods + key: cocoapods-${{ runner.os }}-${{ env.XCODE_VERSION }}-${{ hashFiles('examples/E2E/ios/Podfile.lock') }} + restore-keys: | + cocoapods-${{ runner.os }}-${{ env.XCODE_VERSION }}- + cocoapods-${{ runner.os }}- + - name: DerivedData cache + uses: actions/cache@v4 with: - node-version: 20 - cache: yarn - # End workaround - - - name: Bootstrap - run: yarn install && yarn e2e install && yarn e2e pods - - - name: Bundle Build - run: yarn build - - - name: Detox - Build - run: RCT_NO_LAUNCH_PACKAGER=1 yarn e2e build:ios - - - name: Detox - Test - run: yarn e2e test:ios + path: | + ~/Library/Developer/Xcode/DerivedData + key: derived-${{ runner.os }}-${{ env.XCODE_VERSION }}-${{ hashFiles('examples/E2E/ios/Podfile.lock') }} + restore-keys: | + derived-${{ runner.os }}-${{ env.XCODE_VERSION }}- + derived-${{ runner.os }}- + - name: devbox installer + uses: jetify-com/devbox-install-action@v0.14.0 + with: + enable-cache: 'true' + # --omit-nix-env is important to use the macos system c toolchain instead of the nix toolchain + - name: iOS setup (simulators, pods) + env: + IOS_DEVICE_NAMES: ${{ matrix.ios-device }} + IOS_RUNTIME: ${{ matrix.ios-runtime }} + run: | + set -euo pipefail + devbox shell --omit-nix-env --command "devbox run setup-ios && yarn install && yarn e2e install && yarn e2e pods" + - name: iOS build + run: | + set -euo pipefail + devbox shell --omit-nix-env --command "yarn build && yarn e2e build:ios" + - name: iOS E2E Tests + env: + DETOX_IOS_DEVICE: ${{ matrix.ios-device }} + run: | + set -euo pipefail + devbox shell --omit-nix-env --command "DETOX_IOS_DEVICE='${{ matrix.ios-device }}' yarn e2e test:ios" run-e2e-android: - runs-on: 'macos-13' # This is important, linux cannot run the emulator graphically for e2e tests + runs-on: 'ubuntu-22.04' strategy: matrix: - api-level: [21] - profile: ['pixel_xl'] - env: - YARN_ENABLE_HARDENED_MODE: 0 + include: + - name: android-min + avd-name: pixel_API21_x86_64 + start-script: start-android-minsdk + - name: android-latest + avd-name: medium_phone_API33_x86_64 + start-script: start-android-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - name: Yarn cache + uses: actions/cache@v4 with: - distribution: 'temurin' - java-version: '17' - cache: 'gradle' - + path: | + ~/.cache/yarn + .yarn/cache + key: yarn-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + yarn-${{ runner.os }}- + - name: devbox installer + uses: jetify-com/devbox-install-action@v0.14.0 + with: + enable-cache: 'true' + - name: Nix GC (pre-build) + run: | + nix-collect-garbage -d || true + du -sh /nix/store || true + - name: Disk space (start) + run: | + df -h + df -h /nix/store || true - name: Gradle cache uses: actions/cache@v4 with: @@ -101,7 +138,6 @@ jobs: ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }} - - name: AVD cache uses: actions/cache@v4 id: avd-cache @@ -109,50 +145,28 @@ jobs: path: | ~/.android/avd/* ~/.android/adb* - key: avd-${{ matrix.api-level }}-${{matrix.profile}} + key: avd-${{ matrix.avd-name }} - - name: create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - profile: ${{matrix.profile}} - avd-name: Pixel_API_21_AOSP - target: default - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - # Workaround for corepack enable in node - # Source: (https://github.com/actions/setup-node/issues/899#issuecomment-1828798029) - - uses: actions/setup-node@v4 - with: - node-version: 20 - - run: corepack enable - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: yarn - # End workaround - - - name: Bootstrap - run: yarn install && yarn e2e install # No need to run bootstrap here since we don't need cocoapods - - - name: Bundle build - run: yarn build - - - name: Detox - Build - run: RCT_NO_LAUNCH_PACKAGER=1 yarn e2e build:android + - name: Android setup (AVD + deps) + env: + DETOX_AVD: ${{ matrix.avd-name }} + EMU_HEADLESS: "1" + AVD_FLAVOR: ${{ startsWith(matrix.avd-name, 'pixel') && 'minsdk' || 'latest' }} + run: | + set -euo pipefail + devbox run ${{ matrix.start-script }} + devbox run setup-android + yarn install + yarn e2e install - - name: Detox - Test - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - profile: ${{matrix.profile}} - avd-name: Pixel_API_21_AOSP - target: default - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: yarn e2e test:android + - name: Android build + run: | + set -euo pipefail + devbox run setup-android + yarn build + yarn e2e build:android + + - name: Android E2E Tests + env: + DETOX_AVD: ${{ matrix.avd-name }} + run: devbox run test-android diff --git a/.github/workflows/create_jira.yml b/.github/workflows/create_jira.yml deleted file mode 100644 index 16e5a4596..000000000 --- a/.github/workflows/create_jira.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Create Jira Ticket - -on: - issues: - types: - - opened - -jobs: - create_jira: - name: Create Jira Ticket - runs-on: ubuntu-latest - environment: IssueTracker - steps: - - name: Checkout - uses: actions/checkout@master - - name: Login - uses: atlassian/gajira-login@master - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_TOKEN }} - - - name: Create - id: create - uses: atlassian/gajira-create@master - with: - project: LIBRARIES - issuetype: Bug - summary: | - [${{ github.event.repository.name }}] (${{ github.event.issue.number }}): ${{ github.event.issue.title }} - description: | - Github Link: ${{ github.event.issue.html_url }} - ${{ github.event.issue.body }} - # Parent and Epic Link fields (set to same) - fields: '{ - "parent": {"key": "LIBRARIES-2048"}, - "customfield_10002": "LIBRARIES-2048" - }' - - - name: Log created issue - run: echo "Issue ${{ steps.create.outputs.issue }} was created" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f19960e67..c1bfacc9b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -35,18 +35,15 @@ jobs: cache: yarn # End workaround - - name: Config and Build - run: | - npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} - yarn install --immutable - yarn build - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: devbox installer + uses: jetify-com/devbox-install-action@v0.14.0 + with: + enable-cache: 'true' - - name: Publish (All) - if: github.event.inputs.workspace == '' - run: yarn release + - name: Config, Build, Release + run: devbox run release env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} GH_TOKEN: ${{ secrets.GH_TOKEN }} YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..1df1938ee --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,29 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- Yarn workspaces live under `packages`: `core` (SDK runtime), `sovran` (state store), `shared` (cross-package utilities), and `plugins/*` (destination and helper plugins). Native iOS/Android sources reside in each package’s `ios` and `android` folders. +- Example apps sit in `examples/AnalyticsReactNativeExample` (manual QA) and `examples/E2E` (Detox). Scripts are in `scripts/`; configuration lives alongside packages (e.g., `tsconfig.json`, `babel.config.js`, `jest.config.js`). + +## Build, Test, and Development Commands +- `yarn bootstrap`: install root + workspace deps and pod install for example/e2e apps. +- `yarn build`: run workspace builds in topo order. +- `yarn testAll` / `yarn test`: workspace Jest suite or root Jest run. +- `yarn lint`; `yarn lint --fix`: ESLint across the monorepo. +- `yarn typescript`: type-check without emitting. +- Example app: `yarn example start | ios | android`. Detox: `yarn e2e start:e2e` then platform builds/tests (e.g., `yarn e2e e2e:build:ios` + `yarn e2e e2e:test:ios`). + +## Coding Style & Naming Conventions +- TypeScript-first; native code should mirror existing Swift/Obj-C/Kotlin style. Two-space indentation and Prettier formatting via ESLint rules. +- Prefer camelCase for variables/functions, PascalCase for React components/classes, and UPPER_SNAKE for constants. Plugin packages follow `plugin-*` folder naming and publish as scoped `@segment/*`. +- Keep public APIs typed and documented; colocate utilities with their feature module (e.g., `src/plugins`, `src/__tests__`). + +## Testing Guidelines +- Unit tests use Jest with tests under `__tests__` near source; snapshots live in `__tests__/__snapshots__`. +- End-to-end coverage uses Detox in `examples/E2E`; build and run per platform before pushing. Add regression tests for new behaviors and keep existing snapshots updated only when intentional. + +## Commit & Pull Request Guidelines +- Commit messages follow Conventional Commits (`feat`, `fix`, `chore`, etc.); enforced by commitlint and release automation. +- For PRs, keep scope narrow, link issues when relevant, and note user-facing changes. Ensure `yarn lint`, `yarn typescript`, and the relevant `yarn test*`/Detox flows pass. Include screenshots only when UI changes affect the example app. + +## Security & Configuration Tips +- Do not commit real Segment write keys or private endpoints; use placeholder values in examples and tests. Keep secrets out of `examples/` and CI config. When testing proxies/CDN settings, prefer environment-driven config rather than hardcoding. diff --git a/devbox.json b/devbox.json new file mode 100644 index 000000000..205ab17ec --- /dev/null +++ b/devbox.json @@ -0,0 +1,131 @@ +{ + "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.14.2/.schema/devbox.schema.json", + "packages": { + "cocoapods": { + "version": "latest", + "platforms": ["x86_64-darwin", "aarch64-darwin"], + }, + "yarn-berry": "latest", + "jdk17": "latest", + "gradle": "latest", + "jq": "latest", + "path:./nix#android-sdk": "", + "act": "latest", + }, + "shell": { + "init_hook": [ + "echo 'Welcome to analytics-react-native devbox!' > /dev/null", + "source \"$DEVBOX_PROJECT_ROOT/scripts/android-env.sh\"", + "echo 'Android SDK env configured (details: wiki/devbox.md#devbox-android).'", + ], + "scripts": { + "clean": [ + "rm -rf $DEVBOX_PROJECT_ROOT/examples/E2E/ios/Podfile.lock", + "rm -rf $DEVBOX_PROJECT_ROOT/examples/E2E/ios/Pods", + "cd $DEVBOX_PROJECT_DIR/examples/E2E/android && gradle clean", + "yarn cache clean", + "find $DEVBOX_PROJECT_DIR -type d -name node_modules -exec rmdir {} \\;", + ], + "build": [ + "yarn install --immutable", + "yarn build", + "yarn lint", + "yarn test --coverage", + ], + "test-android": [ + "devbox run setup-android", + "yarn install", + "yarn e2e install", + "yarn build", + "yarn e2e build:android", + "yarn e2e test:android", + ], + "test-ios": [ + "devbox run setup-ios", + "yarn install", + "yarn e2e install", + "yarn e2e pods", + "yarn build", + "yarn e2e build:ios", + "yarn e2e test:ios", + ], + "act-ci": [ + "bash $DEVBOX_PROJECT_ROOT/scripts/act-ci.sh --platform ubuntu-latest=node:20-bullseye" + ], + "setup-android": [ + "bash $DEVBOX_PROJECT_ROOT/scripts/android-setup.sh", + ], + "setup-ios": [ + "bash $DEVBOX_PROJECT_ROOT/scripts/ios-setup.sh", + ], + "start-emulator": [ + "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start", + ], + "start-ios": [ + "bash $DEVBOX_PROJECT_ROOT/scripts/ios-manager.sh start", + ], + "start-android-minsdk": [ + "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start", + ], + "start-android-latest": [ + "AVD_FLAVOR=latest bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start", + ], + "start-android": [ + "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start", + ], + "release": [ + "npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}", + "yarn install --immutable", + "yarn build", + "yarn release", + ], + "reset-android": [ + "rm -rf ~/.android/avd", + "rm -f ~/.android/adbkey*", + "echo \"AVDs and adb keys removed. Recreate via devbox run start-android* as needed.\"", + ], + "reset-ios": [ + "xcrun simctl shutdown all || true", + "xcrun simctl erase all || true", + "rm -rf ~/Library/Developer/CoreSimulator/Devices", + "echo \"Simulators reset. Recreate via devbox run start-ios.\"", + ], + "stop-android": [ + "if command -v adb >/dev/null 2>&1; then", + " devices=$(adb devices -l 2>/dev/null | tail -n +2 | awk '{print $1}' | tr '\\n' ' ');", + " if [[ -n \"$devices\" ]]; then", + " echo \"Stopping Android emulators: $devices\";", + " for d in $devices; do adb -s \"$d\" emu kill >/dev/null 2>&1 || true; done;", + " else", + " echo \"No Android emulators detected via adb.\";", + " fi;", + "else", + " echo \"adb not found; skipping Android emulator shutdown.\";", + "fi", + "pkill -f \"emulator@\" >/dev/null 2>&1 || true", + "echo \"Android emulators stopped (if any were running).\"", + ], + "stop-ios": [ + "if command -v xcrun >/dev/null 2>&1 && xcrun -f simctl >/dev/null 2>&1; then", + " if xcrun simctl list devices booted | grep -q \"Booted\"; then", + " echo \"Shutting down booted iOS simulators...\";", + " xcrun simctl shutdown all >/dev/null 2>&1 || true;", + " else", + " echo \"No booted iOS simulators detected.\";", + " fi;", + "else", + " echo \"simctl not available; skipping iOS shutdown.\";", + "fi", + "echo \"iOS simulators shutdown (if any were running).\"", + ], + "stop": [ + "devbox run stop-android", + "devbox run stop-ios", + ], + "test": [ + "devbox run test-android", + "devbox run test-ios", + ], + }, + }, +} diff --git a/devbox.lock b/devbox.lock new file mode 100644 index 000000000..049772504 --- /dev/null +++ b/devbox.lock @@ -0,0 +1,326 @@ +{ + "lockfile_version": "1", + "packages": { + "act@latest": { + "last_modified": "2026-01-09T13:41:53Z", + "resolved": "github:NixOS/nixpkgs/5f02c91314c8ba4afe83b256b023756412218535#act", + "source": "devbox-search", + "version": "0.2.84", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/a66igc76c2vzyl3q5z7c7kf7dr4b9is5-act-0.2.84", + "default": true + } + ], + "store_path": "/nix/store/a66igc76c2vzyl3q5z7c7kf7dr4b9is5-act-0.2.84" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/wwrpc61p07x97b27ryhyks8zc9bq4j5c-act-0.2.84", + "default": true + } + ], + "store_path": "/nix/store/wwrpc61p07x97b27ryhyks8zc9bq4j5c-act-0.2.84" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/v5fv7inyakck6m1f5q4xz78gfk7r1al7-act-0.2.84", + "default": true + } + ], + "store_path": "/nix/store/v5fv7inyakck6m1f5q4xz78gfk7r1al7-act-0.2.84" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/vpqyxvqac1f8pwva5ffxidr98qbfhn20-act-0.2.84", + "default": true + } + ], + "store_path": "/nix/store/vpqyxvqac1f8pwva5ffxidr98qbfhn20-act-0.2.84" + } + } + }, + "cocoapods@latest": { + "last_modified": "2025-12-31T03:27:36Z", + "resolved": "github:NixOS/nixpkgs/f665af0cdb70ed27e1bd8f9fdfecaf451260fc55#cocoapods", + "source": "devbox-search", + "version": "1.16.2", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/av5g6hfp0yiir3iavg72js70ian8hxyf-cocoapods-1.16.2", + "default": true + } + ], + "store_path": "/nix/store/av5g6hfp0yiir3iavg72js70ian8hxyf-cocoapods-1.16.2" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/har71589bwmh6h6skisd20b3c6lrwmz7-cocoapods-1.16.2", + "default": true + } + ], + "store_path": "/nix/store/har71589bwmh6h6skisd20b3c6lrwmz7-cocoapods-1.16.2" + } + } + }, + "github:NixOS/nixpkgs/nixpkgs-unstable": { + "last_modified": "2026-01-14T04:14:06Z", + "resolved": "github:NixOS/nixpkgs/ea30586ee015f37f38783006a9bc9e4aa64d7d61?lastModified=1768364046" + }, + "gradle@latest": { + "last_modified": "2025-12-31T03:27:36Z", + "plugin_version": "0.0.1", + "resolved": "github:NixOS/nixpkgs/f665af0cdb70ed27e1bd8f9fdfecaf451260fc55#gradle", + "source": "devbox-search", + "version": "8.14.3", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/v2xbkgrvn0b4g4qq7j5x60va0d4gf0kw-gradle-8.14.3", + "default": true + } + ], + "store_path": "/nix/store/v2xbkgrvn0b4g4qq7j5x60va0d4gf0kw-gradle-8.14.3" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/mvs8d5c60yyx3sxpppg1r67yjvlrrhhh-gradle-8.14.3", + "default": true + } + ], + "store_path": "/nix/store/mvs8d5c60yyx3sxpppg1r67yjvlrrhhh-gradle-8.14.3" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/jy3fpkvcymjaglzi9z2gbpzcyqypgfxh-gradle-8.14.3", + "default": true + } + ], + "store_path": "/nix/store/jy3fpkvcymjaglzi9z2gbpzcyqypgfxh-gradle-8.14.3" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/80fgl9ffaxlxl9s4i1jb37krcljx669g-gradle-8.14.3", + "default": true + } + ], + "store_path": "/nix/store/80fgl9ffaxlxl9s4i1jb37krcljx669g-gradle-8.14.3" + } + } + }, + "jdk17@latest": { + "last_modified": "2025-10-22T20:59:19Z", + "resolved": "github:NixOS/nixpkgs/01b6809f7f9d1183a2b3e081f0a1e6f8f415cb09#jdk17", + "source": "devbox-search", + "version": "17.0.12", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/hlm8a8cnp4hm8xkg0a2yy4kv7cq44jii-zulu-ca-jdk-17.0.12", + "default": true + } + ], + "store_path": "/nix/store/hlm8a8cnp4hm8xkg0a2yy4kv7cq44jii-zulu-ca-jdk-17.0.12" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/i4zgq9685y6284hbf5a7ac9ysb88dvlz-zulu-ca-jdk-17.0.12", + "default": true + } + ], + "store_path": "/nix/store/i4zgq9685y6284hbf5a7ac9ysb88dvlz-zulu-ca-jdk-17.0.12" + } + } + }, + "jq@latest": { + "last_modified": "2026-01-12T00:44:08Z", + "resolved": "github:NixOS/nixpkgs/3fbab70c6e69c87ea2b6e48aa6629da2aa6a23b0#jq", + "source": "devbox-search", + "version": "1.8.1", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "bin", + "path": "/nix/store/9rm6fm3zq1jq8rgsx528cw8wkmfya2gf-jq-1.8.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/cv999saj62xhq7xv5i7q6944vljykfmw-jq-1.8.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/5camppj4hz2mgkdbxs0kr6nvh6qa65wf-jq-1.8.1-dev" + }, + { + "name": "doc", + "path": "/nix/store/lak094rhhxlaj1qycadmxyfphgjadj5r-jq-1.8.1-doc" + }, + { + "name": "out", + "path": "/nix/store/g371yvjasdr552v98p5kav7n35s1dfib-jq-1.8.1" + } + ], + "store_path": "/nix/store/9rm6fm3zq1jq8rgsx528cw8wkmfya2gf-jq-1.8.1-bin" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "bin", + "path": "/nix/store/m8qv4g54q3jmjb8i33v9lljcwhydx2vd-jq-1.8.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/9x2457g76jikfy7xq4mjqwzl8iz3zvxj-jq-1.8.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/5ykn83b3hhvnnq0p5vqgcrzihrl9wpsl-jq-1.8.1-dev" + }, + { + "name": "doc", + "path": "/nix/store/37ypy1595g6rj3cymh1mpk2b25fx40g7-jq-1.8.1-doc" + }, + { + "name": "out", + "path": "/nix/store/16lg603jzppwjanlakcak1ais69mkd03-jq-1.8.1" + } + ], + "store_path": "/nix/store/m8qv4g54q3jmjb8i33v9lljcwhydx2vd-jq-1.8.1-bin" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "bin", + "path": "/nix/store/kkb17whpkdrmn9g3gk7y6l69vipxsw0i-jq-1.8.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/iwr61wi83kflqvz8j5nf7ridaqq6nh2w-jq-1.8.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/lypnqs272644l8ff6wfji9rg5jw10v7h-jq-1.8.1-dev" + }, + { + "name": "doc", + "path": "/nix/store/nyw97c4pywfcqqap5hyk9xjghczlbshl-jq-1.8.1-doc" + }, + { + "name": "out", + "path": "/nix/store/ri930a557685c64bdh88a5031i7hx3vy-jq-1.8.1" + } + ], + "store_path": "/nix/store/kkb17whpkdrmn9g3gk7y6l69vipxsw0i-jq-1.8.1-bin" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "bin", + "path": "/nix/store/zssasryipb2x4gk2ahzacl4mvvcmk48j-jq-1.8.1-bin", + "default": true + }, + { + "name": "man", + "path": "/nix/store/7d4pv1iymyqk2lykwj1ydml3rjhc6gl3-jq-1.8.1-man", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/rmxxm5jnxq93kvkhbr2b3hzj6v3ldp8z-jq-1.8.1-dev" + }, + { + "name": "doc", + "path": "/nix/store/mkhfvc69grlky3iblibkw9wcc12jcdqq-jq-1.8.1-doc" + }, + { + "name": "out", + "path": "/nix/store/807g765zgpmp1c8fm5y40rw2gbr1k6dk-jq-1.8.1" + } + ], + "store_path": "/nix/store/zssasryipb2x4gk2ahzacl4mvvcmk48j-jq-1.8.1-bin" + } + } + }, + "yarn-berry@latest": { + "last_modified": "2025-12-31T03:27:36Z", + "resolved": "github:NixOS/nixpkgs/f665af0cdb70ed27e1bd8f9fdfecaf451260fc55#yarn-berry", + "source": "devbox-search", + "version": "4.12.0", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/2l7sbyyqardvrzr35zkrw67gbng5gb8y-yarn-berry-4.12.0", + "default": true + } + ], + "store_path": "/nix/store/2l7sbyyqardvrzr35zkrw67gbng5gb8y-yarn-berry-4.12.0" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/klx9ndw1djgx0zhhyrkcn9an094rmmwv-yarn-berry-4.12.0", + "default": true + } + ], + "store_path": "/nix/store/klx9ndw1djgx0zhhyrkcn9an094rmmwv-yarn-berry-4.12.0" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/m6cwiya6hrbwnlprh2cbnmz6c7mkylrf-yarn-berry-4.12.0", + "default": true + } + ], + "store_path": "/nix/store/m6cwiya6hrbwnlprh2cbnmz6c7mkylrf-yarn-berry-4.12.0" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/q1gys3zgijcciiafbh9nfawkx5wj8179-yarn-berry-4.12.0", + "default": true + } + ], + "store_path": "/nix/store/q1gys3zgijcciiafbh9nfawkx5wj8179-yarn-berry-4.12.0" + } + } + } + } +} diff --git a/examples/E2E/.detoxrc.js b/examples/E2E/.detoxrc.js index 98759a8cb..fbf3cca1b 100644 --- a/examples/E2E/.detoxrc.js +++ b/examples/E2E/.detoxrc.js @@ -51,7 +51,8 @@ module.exports = { simulator: { type: 'ios.simulator', device: { - type: 'iPhone 14' + // Allow CI/local override; defaults to iPhone 17 on latest runtime. + type: process.env.DETOX_IOS_DEVICE || 'iPhone 17' } }, attached: { @@ -63,7 +64,12 @@ module.exports = { emulator: { type: 'android.emulator', device: { - avdName: process.env.CI ? 'Pixel_API_21_AOSP': 'Pixel_3a_API_32' + // Default to latest AVD name (arch-aware); override via DETOX_AVD. For minsdk testing, set DETOX_AVD to an API 21 AVD. + avdName: (() => { + if (process.env.DETOX_AVD) return process.env.DETOX_AVD; + const arch = require('os').arch(); + return arch === 'arm64' ? 'medium_phone_API33_arm64_v8a' : 'medium_phone_API33_x86_64'; + })() } } }, diff --git a/examples/E2E/android/app/build.gradle b/examples/E2E/android/app/build.gradle index bc8fe887d..2afd7a5c5 100644 --- a/examples/E2E/android/app/build.gradle +++ b/examples/E2E/android/app/build.gradle @@ -112,12 +112,6 @@ dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") - debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { - exclude group:'com.squareup.okhttp3', module:'okhttp' - } - - debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { diff --git a/examples/E2E/android/app/src/main/java/com/analyticsreactnativeexample/MainApplication.java b/examples/E2E/android/app/src/main/java/com/analyticsreactnativeexample/MainApplication.java index cd5b079b6..b7ee722d5 100644 --- a/examples/E2E/android/app/src/main/java/com/analyticsreactnativeexample/MainApplication.java +++ b/examples/E2E/android/app/src/main/java/com/analyticsreactnativeexample/MainApplication.java @@ -57,6 +57,5 @@ public void onCreate() { // If you opted-in for the New Architecture, we load the native entry point for this app. DefaultNewArchitectureEntryPoint.load(); } - ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); } } diff --git a/examples/E2E/android/build.gradle b/examples/E2E/android/build.gradle index 9c2d29f43..b75c49d5e 100644 --- a/examples/E2E/android/build.gradle +++ b/examples/E2E/android/build.gradle @@ -2,7 +2,8 @@ buildscript { ext { - buildToolsVersion = "33.0.0" + // Default to the build-tools pinned in devbox; allow override via ANDROID_BUILD_TOOLS_VERSION. + buildToolsVersion = System.getenv("ANDROID_BUILD_TOOLS_VERSION") ?: "33.0.0" minSdkVersion = 21 compileSdkVersion = 33 targetSdkVersion = 33 @@ -34,4 +35,4 @@ allprojects { jcenter() maven { url 'https://www.jitpack.io' } } -} \ No newline at end of file +} diff --git a/examples/E2E/android/gradle.properties b/examples/E2E/android/gradle.properties index a3b2fa124..a46a5b90f 100644 --- a/examples/E2E/android/gradle.properties +++ b/examples/E2E/android/gradle.properties @@ -24,9 +24,6 @@ android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true -# Version of flipper SDK to use with React Native -FLIPPER_VERSION=0.182.0 - # Use this property to specify which architecture you want to build. # You can also override it from the CLI using # ./gradlew -PreactNativeArchitectures=x86_64 diff --git a/examples/E2E/ios/Podfile b/examples/E2E/ios/Podfile index 0e91586a8..e8c21a1c7 100644 --- a/examples/E2E/ios/Podfile +++ b/examples/E2E/ios/Podfile @@ -8,17 +8,6 @@ require Pod::Executable.execute_command('node', ['-p', platform :ios, min_ios_version_supported prepare_react_native_project! -# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. -# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded -# -# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js` -# ```js -# module.exports = { -# dependencies: { -# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), -# ``` -flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled - linkage = ENV['USE_FRAMEWORKS'] if linkage != nil Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green @@ -36,11 +25,6 @@ target 'AnalyticsReactNativeE2E' do # Hermes is now enabled by default. Disable by setting this flag to false. :hermes_enabled => flags[:hermes_enabled], :fabric_enabled => flags[:fabric_enabled], - # Enables Flipper. - # - # Note that if you have use_frameworks! enabled, Flipper will not work and - # you should disable the next line. - :flipper_configuration => flipper_config, # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." ) @@ -51,6 +35,11 @@ target 'AnalyticsReactNativeE2E' do end post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4' + end + end # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 react_native_post_install( installer, diff --git a/examples/E2E/ios/Podfile.lock b/examples/E2E/ios/Podfile.lock index 3b2b01152..0039daf46 100644 --- a/examples/E2E/ios/Podfile.lock +++ b/examples/E2E/ios/Podfile.lock @@ -1,6 +1,5 @@ PODS: - boost (1.76.0) - - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - FBLazyVector (0.72.9) - FBReactNativeSpec (0.72.9): @@ -10,71 +9,12 @@ PODS: - React-Core (= 0.72.9) - React-jsi (= 0.72.9) - ReactCommon/turbomodule/core (= 0.72.9) - - Flipper (0.182.0): - - Flipper-Folly (~> 2.6) - - Flipper-Boost-iOSX (1.76.0.1.11) - - Flipper-DoubleConversion (3.2.0.1) - - Flipper-Fmt (7.1.7) - - Flipper-Folly (2.6.10): - - Flipper-Boost-iOSX - - Flipper-DoubleConversion - - Flipper-Fmt (= 7.1.7) - - Flipper-Glog - - libevent (~> 2.1.12) - - OpenSSL-Universal (= 1.1.1100) - - Flipper-Glog (0.5.0.5) - - Flipper-PeerTalk (0.0.4) - - FlipperKit (0.182.0): - - FlipperKit/Core (= 0.182.0) - - FlipperKit/Core (0.182.0): - - Flipper (~> 0.182.0) - - FlipperKit/CppBridge - - FlipperKit/FBCxxFollyDynamicConvert - - FlipperKit/FBDefines - - FlipperKit/FKPortForwarding - - SocketRocket (~> 0.6.0) - - FlipperKit/CppBridge (0.182.0): - - Flipper (~> 0.182.0) - - FlipperKit/FBCxxFollyDynamicConvert (0.182.0): - - Flipper-Folly (~> 2.6) - - FlipperKit/FBDefines (0.182.0) - - FlipperKit/FKPortForwarding (0.182.0): - - CocoaAsyncSocket (~> 7.6) - - Flipper-PeerTalk (~> 0.0.4) - - FlipperKit/FlipperKitHighlightOverlay (0.182.0) - - FlipperKit/FlipperKitLayoutHelpers (0.182.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutTextSearchable - - FlipperKit/FlipperKitLayoutIOSDescriptors (0.182.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutHelpers - - YogaKit (~> 1.18) - - FlipperKit/FlipperKitLayoutPlugin (0.182.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutHelpers - - FlipperKit/FlipperKitLayoutIOSDescriptors - - FlipperKit/FlipperKitLayoutTextSearchable - - YogaKit (~> 1.18) - - FlipperKit/FlipperKitLayoutTextSearchable (0.182.0) - - FlipperKit/FlipperKitNetworkPlugin (0.182.0): - - FlipperKit/Core - - FlipperKit/FlipperKitReactPlugin (0.182.0): - - FlipperKit/Core - - FlipperKit/FlipperKitUserDefaultsPlugin (0.182.0): - - FlipperKit/Core - - FlipperKit/SKIOSNetworkPlugin (0.182.0): - - FlipperKit/Core - - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - hermes-engine (0.72.9): - hermes-engine/Pre-built (= 0.72.9) - hermes-engine/Pre-built (0.72.9) - libevent (2.1.12) - - OpenSSL-Universal (1.1.1100) - RCT-Folly (2021.07.22.00): - boost - DoubleConversion @@ -499,45 +439,22 @@ PODS: - RNScreens (3.27.0): - RCT-Folly (= 2021.07.22.00) - React-Core - - segment-analytics-react-native (2.20.2): + - segment-analytics-react-native (2.21.4): - React-Core - sovran-react-native - SocketRocket (0.6.1) - - sovran-react-native (1.1.2): + - sovran-react-native (1.1.3): - React-Core - Yoga (1.14.0) - - YogaKit (1.18.1): - - Yoga (~> 1.14) DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - - Flipper (= 0.182.0) - - Flipper-Boost-iOSX (= 1.76.0.1.11) - - Flipper-DoubleConversion (= 3.2.0.1) - - Flipper-Fmt (= 7.1.7) - - Flipper-Folly (= 2.6.10) - - Flipper-Glog (= 0.5.0.5) - - Flipper-PeerTalk (= 0.0.4) - - FlipperKit (= 0.182.0) - - FlipperKit/Core (= 0.182.0) - - FlipperKit/CppBridge (= 0.182.0) - - FlipperKit/FBCxxFollyDynamicConvert (= 0.182.0) - - FlipperKit/FBDefines (= 0.182.0) - - FlipperKit/FKPortForwarding (= 0.182.0) - - FlipperKit/FlipperKitHighlightOverlay (= 0.182.0) - - FlipperKit/FlipperKitLayoutPlugin (= 0.182.0) - - FlipperKit/FlipperKitLayoutTextSearchable (= 0.182.0) - - FlipperKit/FlipperKitNetworkPlugin (= 0.182.0) - - FlipperKit/FlipperKitReactPlugin (= 0.182.0) - - FlipperKit/FlipperKitUserDefaultsPlugin (= 0.182.0) - - FlipperKit/SKIOSNetworkPlugin (= 0.182.0) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - libevent (~> 2.1.12) - - OpenSSL-Universal (= 1.1.1100) - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) @@ -545,7 +462,6 @@ DEPENDENCIES: - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - React-Codegen (from `build/generated/ios`) - React-Core (from `../node_modules/react-native/`) - - React-Core/DevSupport (from `../node_modules/react-native/`) - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) @@ -584,20 +500,9 @@ DEPENDENCIES: SPEC REPOS: trunk: - - CocoaAsyncSocket - - Flipper - - Flipper-Boost-iOSX - - Flipper-DoubleConversion - - Flipper-Fmt - - Flipper-Folly - - Flipper-Glog - - Flipper-PeerTalk - - FlipperKit - fmt - libevent - - OpenSSL-Universal - SocketRocket - - YogaKit EXTERNAL SOURCES: boost: @@ -697,23 +602,13 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7dcd2de282d72e344012f7d6564d024930a6a440 - CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: dc178b8748748c036ef9493a5d59d6d1f91a36ce FBReactNativeSpec: d0aaae78e93c89dc2d691d8052a4d2aeb1b461ee - Flipper: 6edb735e6c3e332975d1b17956bcc584eccf5818 - Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c - Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 - Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b - Flipper-Folly: 584845625005ff068a6ebf41f857f468decd26b3 - Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446 - Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 - FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b hermes-engine: 9b9bb14184a11b8ceb4131b09abf634880f0f46d libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: f30c3213569b1dc43659ecc549a6536e1e11139e RCTTypeSafety: e1ed3137728804fa98bce30b70e3da0b8e23054e @@ -752,12 +647,11 @@ SPEC CHECKSUMS: RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489 RNGestureHandler: 32a01c29ecc9bb0b5bf7bc0a33547f61b4dc2741 RNScreens: 3c2d122f5e08c192e254c510b212306da97d2581 - segment-analytics-react-native: 53785e35d44a0643beffa40eada68a4cbdf7292e + segment-analytics-react-native: 05c3bf2adb8a3be2c273808a6fdaced06d927917 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 - sovran-react-native: 5f02bd2d111ffe226d00c7b0435290eae6f10934 + sovran-react-native: eec37f82e4429f0e3661f46aaf4fcd85d1b54f60 Yoga: eddf2bbe4a896454c248a8f23b4355891eb720a6 - YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 9d352ca8db1e31a063d2585ed47fdadabf87fe90 +PODFILE CHECKSUM: a4c187e503408b85ffe8c89a4cb726ec541057ce -COCOAPODS: 1.11.3 +COCOAPODS: 1.16.2 diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..a468eda9e --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.home=/nix/store/hlm8a8cnp4hm8xkg0a2yy4kv7cq44jii-zulu-ca-jdk-17.0.12 diff --git a/nix/.cache/nix/fetcher-cache-v4.sqlite b/nix/.cache/nix/fetcher-cache-v4.sqlite new file mode 100644 index 000000000..e6742ac8c Binary files /dev/null and b/nix/.cache/nix/fetcher-cache-v4.sqlite differ diff --git a/nix/flake.lock b/nix/flake.lock new file mode 100644 index 000000000..9bfc9cd96 --- /dev/null +++ b/nix/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1768364046, + "narHash": "sha256-PDFfpswLiuG/DcadTBb7dEfO3jX1fcGlCD4ZKSkC0M8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ea30586ee015f37f38783006a9bc9e4aa64d7d61", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/nix/flake.nix b/nix/flake.nix new file mode 100644 index 000000000..cfda02227 --- /dev/null +++ b/nix/flake.nix @@ -0,0 +1,47 @@ +{ + description = "Slim Android SDK tools for Devbox via flakes"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + outputs = { self, nixpkgs }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + forAllSystems = f: + builtins.listToAttrs (map (system: { + name = system; + value = f system; + }) systems); + in + { + packages = forAllSystems (system: + let + pkgs = import nixpkgs { + inherit system; + config = { + allowUnfree = true; + android_sdk.accept_license = true; + }; + }; + + androidPkgs = pkgs.androidenv.composeAndroidPackages { + # Keep API 21 images for the AVD and add API 33 for React Native builds. + platformVersions = [ "21" "33" ]; + buildToolsVersions = [ "30.0.3" "33.0.0" "latest" ]; + cmdLineToolsVersion = "19.0"; + includeEmulator = true; + includeSystemImages = true; + includeNDK = true; + }; + in + { + android-sdk = androidPkgs.androidsdk; + default = androidPkgs.androidsdk; + }); + }; +} diff --git a/scripts/act-ci.sh b/scripts/act-ci.sh new file mode 100755 index 000000000..71ae350f7 --- /dev/null +++ b/scripts/act-ci.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Run GitHub Actions workflows locally via act. +# Usage: scripts/act-ci.sh [--job JOB] [--platform ubuntu-latest=node:20-bullseye] + +JOB="" +PLATFORM="ubuntu-latest=node:20-bullseye" + +while [[ $# -gt 0 ]]; do + case "$1" in + -j|--job) + JOB="$2"; shift 2 ;; + -p|--platform) + PLATFORM="$2"; shift 2 ;; + *) echo "Unknown option: $1" >&2; exit 1 ;; + esac +done + +CMD=(act) +CMD+=(--pull=false) +CMD+=(--platform "${PLATFORM}") +CMD+=(--input ACT=true) +if [[ -n "$JOB" ]]; then + CMD+=(--job "$JOB") +fi + +printf 'Running: %s\n' "${CMD[*]}" +exec "${CMD[@]}" diff --git a/scripts/android-env.sh b/scripts/android-env.sh new file mode 100755 index 000000000..a73e9d3f5 --- /dev/null +++ b/scripts/android-env.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Sets ANDROID_SDK_ROOT/ANDROID_HOME and PATH to the flake-pinned SDK if not already set. + +# Only act if neither var is already provided. +if [[ -z "${ANDROID_SDK_ROOT:-}" && -z "${ANDROID_HOME:-}" ]]; then + DEVBOX_SDK_OUT=$( + nix --extra-experimental-features 'nix-command flakes' \ + eval --raw "path:${DEVBOX_PROJECT_ROOT}/nix#android-sdk.outPath" 2>/dev/null || true + ) + if [[ -n "${DEVBOX_SDK_OUT:-}" && -d "$DEVBOX_SDK_OUT/libexec/android-sdk" ]]; then + ANDROID_SDK_ROOT="$DEVBOX_SDK_OUT/libexec/android-sdk" + ANDROID_HOME="$ANDROID_SDK_ROOT" + fi +fi + +if [[ -z "${ANDROID_SDK_ROOT:-}" && -n "${ANDROID_HOME:-}" ]]; then + ANDROID_SDK_ROOT="$ANDROID_HOME" +fi + +if [[ -n "${ANDROID_SDK_ROOT:-}" && -z "${ANDROID_HOME:-}" ]]; then + ANDROID_HOME="$ANDROID_SDK_ROOT" +fi + +export ANDROID_SDK_ROOT ANDROID_HOME + +if [[ -n "${ANDROID_SDK_ROOT:-}" ]]; then + # Prefer cmdline-tools;latest, or fall back to the highest numbered cmdline-tools folder. + cmdline_tools_bin="" + if [[ -d "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" ]]; then + cmdline_tools_bin="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" + else + cmdline_tools_dir=$(find "$ANDROID_SDK_ROOT/cmdline-tools" -maxdepth 1 -mindepth 1 -type d -not -name latest 2>/dev/null | sort -V | tail -n 1) + if [[ -n "${cmdline_tools_dir:-}" && -d "$cmdline_tools_dir/bin" ]]; then + cmdline_tools_bin="$cmdline_tools_dir/bin" + fi + fi + + new_path=( + "$ANDROID_SDK_ROOT/emulator" + "$ANDROID_SDK_ROOT/platform-tools" + ) + + if [[ -n "${cmdline_tools_bin:-}" ]]; then + new_path+=("$cmdline_tools_bin") + fi + + new_path+=("$ANDROID_SDK_ROOT/tools/bin" "$PATH") + export PATH="$(IFS=:; echo "${new_path[*]}")" + echo "Using Android SDK: $ANDROID_SDK_ROOT" + if [[ "${ANDROID_SDK_ROOT}" == /nix/store/* ]]; then + echo "Source: Nix flake (reproducible, pinned). To use your local SDK instead, set ANDROID_HOME/ANDROID_SDK_ROOT before starting devbox shell." + else + echo "Source: User/local SDK. To use the pinned Nix SDK, unset ANDROID_HOME/ANDROID_SDK_ROOT before starting devbox shell." + fi +else + echo "Android SDK not set; using system PATH" +fi diff --git a/scripts/android-manager.sh b/scripts/android-manager.sh new file mode 100755 index 000000000..776d16a06 --- /dev/null +++ b/scripts/android-manager.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +action="${1:-}"; shift || true + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/android-env.sh" + +start_android() { + local flavor="${AVD_FLAVOR:-minsdk}" headless="${EMU_HEADLESS:-}" port="${EMU_PORT:-5554}" + local avd="${DETOX_AVD:-}" + + if [[ -z "$avd" ]]; then + if [[ "$flavor" == "latest" ]]; then + local host_arch + host_arch="$(uname -m)" + avd="medium_phone_API33_$( [[ "$host_arch" == "arm64" || "$host_arch" == "aarch64" ]] && echo arm64_v8a || echo x86_64 )" + else + avd="pixel_API21_$(uname -m | grep -qi arm && echo arm64_v8a || echo x86_64)" + fi + fi + + devbox run setup-android + local target_serial="emulator-${port}" + if command -v adb >/dev/null 2>&1; then + adb devices | awk 'NR>1 && $2=="offline" {print $1}' | while read -r d; do adb -s "$d" emu kill >/dev/null 2>&1 || true; done + fi + echo "Starting Android emulator: ${avd} (flavor ${flavor}, port ${port}, headless=${headless:-0})" + emulator -avd "${avd}" ${headless:+-no-window} -port "${port}" -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -accel on -writable-system -no-snapshot-save & + adb -s "${target_serial}" wait-for-device + local boot_completed="" + until [ "$boot_completed" = "1" ]; do + boot_completed=$(adb -s "${target_serial}" shell getprop sys.boot_completed 2>/dev/null | tr -d "\r") + sleep 5 + done + adb -s "${target_serial}" shell settings put global window_animation_scale 0 + adb -s "${target_serial}" shell settings put global transition_animation_scale 0 + adb -s "${target_serial}" shell settings put global animator_duration_scale 0 +} + +stop_android() { + devbox run stop-android +} + +reset_android() { + devbox run reset-android +} + +case "$action" in + start) start_android ;; + stop) stop_android ;; + reset) reset_android ;; + *) echo "Usage: android-manager.sh {start|stop|reset}" >&2; exit 1 ;; +esac diff --git a/scripts/android-setup.sh b/scripts/android-setup.sh new file mode 100755 index 000000000..6aede99cd --- /dev/null +++ b/scripts/android-setup.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Creates AVDs using the Android SDK provided by devbox/flake (system images, emulator, NDK already installed). +# Run inside a devbox shell so SDK tools are available. +# Configurable via env: +# AVD_API (default 21) +# AVD_DEVICE (default "pixel") +# AVD_TAG (default "google_apis") +# AVD_ABI (preferred ABI; optional) +# AVD_NAME (override final AVD name; otherwise computed) +# Secondary AVD (created in addition to the primary): +# AVD_SECONDARY_API (default 33) +# AVD_SECONDARY_DEVICE (default "medium_phone") +# AVD_SECONDARY_TAG (default "google_apis") +# AVD_SECONDARY_ABI (preferred ABI; optional) +# AVD_SECONDARY_NAME (override final name) + +require_tool() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required tool: $1. Ensure devbox shell is active and required packages are installed." >&2 + exit 1 + fi +} + +detect_sdk_root() { + if [[ -n "${ANDROID_SDK_ROOT:-}" ]]; then + echo "$ANDROID_SDK_ROOT" + return + fi + + local sm + sm="$(command -v sdkmanager 2>/dev/null || true)" + if [[ -z "$sm" ]]; then + return + fi + sm="$(readlink -f "$sm")" + local candidates=( + "$(dirname "$sm")/.." + "$(dirname "$sm")/../share/android-sdk" + "$(dirname "$sm")/../libexec/android-sdk" + "$(dirname "$sm")/../.." + ) + for c in "${candidates[@]}"; do + if [[ -d "$c/platform-tools" || -d "$c/platforms" || -d "$c/system-images" ]]; then + echo "$c" + return + fi + done +} + +avd_exists() { + local name="$1" + avdmanager list avd | grep -q "Name: ${name}" +} + +pick_image() { + local api="$1" tag="$2" preferred_abi="$3" + local host_arch + host_arch="$(uname -m)" + + local candidates=() + if [[ -n "${preferred_abi:-}" ]]; then + candidates=("$preferred_abi") + else + case "$host_arch" in + arm64|aarch64) candidates=("arm64-v8a" "x86_64" "x86") ;; + *) candidates=("x86_64" "x86" "arm64-v8a") ;; + esac + fi + + for abi in "${candidates[@]}"; do + local image="system-images;android-${api};${tag};${abi}" + local path="${ANDROID_SDK_ROOT}/system-images/android-${api}/${tag}/${abi}" + if [[ -d "$path" ]]; then + echo "$image" + return 0 + fi + done + + return 1 +} + +create_avd() { + local name="$1" device="$2" image="$3" + local abi="${image##*;}" + + if avd_exists "$name"; then + echo "AVD ${name} already exists." + return 0 + fi + + echo "Creating AVD ${name} with ${image}..." + avdmanager create avd --force --name "$name" --package "$image" --device "$device" --abi "$abi" --sdcard 512M +} + +main() { + local detected_sdk_root + detected_sdk_root="$(detect_sdk_root)" + + if [[ -z "${ANDROID_SDK_ROOT:-}" && -n "$detected_sdk_root" ]]; then + export ANDROID_SDK_ROOT="$detected_sdk_root" + fi + + if [[ -z "${ANDROID_SDK_ROOT:-}" && -z "${ANDROID_HOME:-}" ]]; then + echo "ANDROID_SDK_ROOT/ANDROID_HOME must be set. In a devbox shell, the flake-provided SDK should supply sdkmanager in PATH; if not, set ANDROID_SDK_ROOT to the flake's android-sdk path." >&2 + exit 1 + fi + + export ANDROID_HOME="${ANDROID_HOME:-$ANDROID_SDK_ROOT}" + + require_tool avdmanager + require_tool emulator + + local primary_api="${AVD_API:-21}" + local primary_tag="${AVD_TAG:-google_apis}" + local primary_device="${AVD_DEVICE:-pixel}" + local primary_preferred_abi="${AVD_ABI:-}" + + local secondary_api="${AVD_SECONDARY_API:-33}" + local secondary_tag="${AVD_SECONDARY_TAG:-google_apis}" + local secondary_device="${AVD_SECONDARY_DEVICE:-medium_phone}" + local secondary_preferred_abi="${AVD_SECONDARY_ABI:-}" + + local targets=( + "$primary_api|$primary_tag|$primary_device|$primary_preferred_abi|${AVD_NAME:-}" + "$secondary_api|$secondary_tag|$secondary_device|$secondary_preferred_abi|${AVD_SECONDARY_NAME:-}" + ) + + for target in "${targets[@]}"; do + IFS="|" read -r api tag device preferred_abi name_override <<<"$target" + + local api_image + if ! api_image="$(pick_image "$api" "$tag" "$preferred_abi")"; then + echo "Expected API ${api} system image (${tag}; preferred ABI ${preferred_abi:-auto}) not found under ${ANDROID_SDK_ROOT}/system-images/android-${api}." >&2 + echo "Re-enter the devbox shell (flake should provide images) or rebuild Devbox to fetch them." >&2 + continue + fi + + local abi="${api_image##*;}" + local avd_name="${name_override:-$(printf '%s_API%s_%s' "$device" "$api" "${abi//-/_}")}" + + create_avd "$avd_name" "$device" "$api_image" + if avd_exists "$avd_name"; then + echo "AVD ready: ${avd_name} (${api_image})" + fi + done + + echo "AVDs ready. Boot with: emulator -avd --netdelay none --netspeed full" +} + +main "$@" diff --git a/scripts/ios-manager.sh b/scripts/ios-manager.sh new file mode 100755 index 000000000..1574bfd4f --- /dev/null +++ b/scripts/ios-manager.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +action="${1:-}" +shift || true + +start_ios() { + local flavor="${IOS_FLAVOR:-latest}" + if [[ "$flavor" == "minsdk" ]]; then + export IOS_DEVICE_NAMES="iPhone 14" + export IOS_RUNTIME="18.5" + export DETOX_IOS_DEVICE="${DETOX_IOS_DEVICE:-iPhone 14}" + else + export IOS_DEVICE_NAMES="${IOS_DEVICE_NAMES:-iPhone 14,iPhone 17}" + export IOS_RUNTIME="${IOS_RUNTIME:-26.1}" + export DETOX_IOS_DEVICE="${DETOX_IOS_DEVICE:-iPhone 17}" + fi + + devbox run setup-ios + local sim_device="${DETOX_IOS_DEVICE}" + if ! xcrun simctl list devices | grep -q "${sim_device}"; then + echo "Simulator ${sim_device} not found; ensure setup-ios created it." >&2 + exit 1 + fi + echo "Starting iOS simulator: ${sim_device} (runtime ${IOS_RUNTIME})" + xcrun simctl boot "$sim_device" || true + open -a Simulator +} + +stop_ios() { + devbox run stop-ios +} + +reset_ios() { + devbox run reset-ios +} + +case "$action" in + start) start_ios ;; + stop) stop_ios ;; + reset) reset_ios ;; + *) echo "Usage: ios-manager.sh {start|stop|reset}" >&2; exit 1 ;; +esac diff --git a/scripts/ios-setup.sh b/scripts/ios-setup.sh new file mode 100755 index 000000000..8c4c565ce --- /dev/null +++ b/scripts/ios-setup.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Creates local iOS simulators for common targets. Requires Xcode command-line tools and jq. +# Env overrides: +# IOS_DEVICE_NAMES="iPhone 15,iPhone 17" (comma-separated) +# IOS_RUNTIME="26.1" (preferred runtime prefix; falls back to latest available) +# IOS_DOWNLOAD_RUNTIME=1 to attempt xcodebuild -downloadPlatform iOS when the preferred runtime is missing +# IOS_DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer" to override the Xcode path; defaults to xcode-select -p or the standard Xcode.app if found + +require_tool() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required tool: $1. Install Xcode CLI tools before running (xcode-select --install or Xcode.app + xcode-select -s)." >&2 + exit 1 + fi +} + +ensure_developer_dir() { + local desired="${IOS_DEVELOPER_DIR:-}" + if [[ -z "$desired" ]]; then + if xcode-select -p >/dev/null 2>&1; then + desired="$(xcode-select -p)" + elif [[ -d /Applications/Xcode.app/Contents/Developer ]]; then + desired="/Applications/Xcode.app/Contents/Developer" + fi + fi + + if [[ -n "$desired" && -d "$desired" ]]; then + export DEVELOPER_DIR="$desired" + export PATH="$DEVELOPER_DIR/usr/bin:$PATH" + return 0 + fi + + echo "Xcode developer directory not found. Install Xcode/CLI tools or set IOS_DEVELOPER_DIR to an Xcode path (e.g., /Applications/Xcode.app/Contents/Developer)." >&2 + exit 1 +} + +ensure_developer_dir + +require_tool xcrun +require_tool jq + +ensure_simctl() { + if xcrun -f simctl >/dev/null 2>&1; then + return 0 + fi + cat >&2 <<'EOF' +Missing simctl. +- The standalone Command Line Tools do NOT include simctl; you need full Xcode. +- Install/locate Xcode.app, then select it: + sudo xcode-select -s /Applications/Xcode.app/Contents/Developer +- You can also set IOS_DEVELOPER_DIR to your Xcode path for this script. +EOF + exit 1 +} + +ensure_simctl + +pick_runtime() { + local preferred="$1" + local json choice + json="$(xcrun simctl list runtimes -j)" + choice="$(echo "$json" | jq -r --arg v "$preferred" '.runtimes[] | select(.isAvailable and (.name|startswith("iOS \($v)"))) | "\(.identifier)|\(.name)"' | head -n1)" + if [[ -z "$choice" || "$choice" == "null" ]]; then + choice="$(echo "$json" | jq -r '.runtimes[] | select(.isAvailable and (.name|startswith("iOS "))) | "\(.version)|\(.identifier)|\(.name)"' | sort -Vr | head -n1 | cut -d"|" -f2-)" + fi + [[ -n "$choice" && "$choice" != "null" ]] || return 1 + echo "$choice" +} + +resolve_runtime() { + local preferred="$1" + if choice=$(pick_runtime "$preferred"); then + echo "$choice" + return 0 + fi + + if [[ "${IOS_DOWNLOAD_RUNTIME:-1}" != "0" ]] && command -v xcodebuild >/dev/null 2>&1; then + echo "Preferred runtime iOS ${preferred} not found. Attempting to download via xcodebuild -downloadPlatform iOS..." >&2 + if xcodebuild -downloadPlatform iOS; then + if choice=$(pick_runtime "$preferred"); then + echo "$choice" + return 0 + fi + else + echo "xcodebuild -downloadPlatform iOS failed; continuing with available runtimes." >&2 + fi + fi + + pick_runtime "$preferred" +} + +existing_device_udid_any_runtime() { + local name="$1" + xcrun simctl list devices -j | jq -r --arg name "$name" '.devices[]?[]? | select(.name == $name) | .udid' | head -n1 +} + +devicetype_id_for_name() { + local name="$1" + xcrun simctl list devicetypes -j | jq -r --arg name "$name" '.devicetypes[] | select((.name|ascii_downcase) == ($name|ascii_downcase)) | .identifier' | head -n1 +} + +ensure_device() { + local base_name="$1" preferred_runtime="$2" + + # If a device with this name already exists anywhere, reuse it. + if existing_udid=$(existing_device_udid_any_runtime "$base_name"); [[ -n "${existing_udid}" ]]; then + echo "Found existing ${base_name}: ${existing_udid}" + return 0 + fi + + local choice runtime_id runtime_name + if ! choice=$(resolve_runtime "$preferred_runtime"); then + echo "No available iOS simulator runtime found. Install one in Xcode (Settings > Platforms) and retry." >&2 + return 1 + fi + runtime_id="$(echo "$choice" | cut -d'|' -f1)" + runtime_name="$(echo "$choice" | cut -d'|' -f2)" + + local display_name="${base_name} (${runtime_name})" + + if ! device_type=$(devicetype_id_for_name "$base_name"); then + echo "Device type '${base_name}' is unavailable in this Xcode install. Skipping ${display_name}." >&2 + return 0 + fi + + # Also check for an existing device with the runtime-qualified display name. + if existing_udid=$(existing_device_udid_any_runtime "$display_name"); [[ -n "${existing_udid}" ]]; then + echo "Found existing ${display_name}: ${existing_udid}" + return 0 + fi + + echo "Creating ${display_name}..." + xcrun simctl create "$display_name" "$device_type" "$runtime_id" + echo "Created ${display_name}" +} + +main() { + IFS=',' read -r -a devices <<<"${IOS_DEVICE_NAMES:-iPhone 14,iPhone 17}" + local runtime="${IOS_RUNTIME:-18.5}" + for device in "${devices[@]}"; do + ensure_device "$(echo "$device" | xargs)" "$runtime" + done + echo "Done. Launch via Xcode > Devices or 'xcrun simctl boot \"\"' then 'open -a Simulator'." +} + +main "$@" diff --git a/wiki/devbox.md b/wiki/devbox.md new file mode 100644 index 000000000..696a54684 --- /dev/null +++ b/wiki/devbox.md @@ -0,0 +1,71 @@ +## Devbox Overview + +This repo ships a Devbox environment that preinstalls the Android SDK and common build tools like Gradle and Yarn. Devbox uses Nix under the hood to pin versions so everyone has the same setup. You don’t need to know Nix to use it. + +Enter the environment with `devbox shell`. The init hook wires `ANDROID_SDK_ROOT`/`ANDROID_HOME` and PATH. Common scripts run via `devbox run`, for example `devbox run build`. For Devbox basics, see the official docs: https://www.jetify.com/devbox/docs/. + +## Getting started + +- Install Devbox (https://www.jetify.com/devbox/docs/install/). +- From the repo root: `devbox install`. +- Enter the shell: `devbox shell`. +- Build/test: `devbox run build`, `devbox run test-android`, `devbox run test-ios`. + +## Android + +By default, Devbox uses the flake-pinned SDK (`path:./nix#android-sdk`). It sets `ANDROID_SDK_ROOT`/`ANDROID_HOME` and adds emulator/platform-tools/cmdline-tools to `PATH` via `scripts/android-env.sh`. To use a local SDK instead, launch with `ANDROID_HOME="$HOME/Library/Android/sdk" devbox shell` (or set `ANDROID_SDK_ROOT`). Clear both env vars to return to the Nix SDK. Inspect the active SDK with `echo "$ANDROID_SDK_ROOT"` and `which sdkmanager` inside the shell. Create/boot AVDs via `devbox run start-android*` (uses `scripts/android-setup.sh` + `scripts/android-manager.sh`). + +### Emulator/AVD scripts + +- `devbox run start-android` launches the default “latest” AVD (API 33, Medium Phone). On arm64 hosts it uses the arm64-v8a image; on Intel it uses x86_64. Override with `AVD_FLAVOR=minsdk` to launch the API 21 Pixel AVD instead. You can also set `DETOX_AVD` to pick an exact AVD name. +- `devbox run start-android-latest` / `start-android-minsdk` explicitly launch the latest (API 33) or minsdk (API 21) AVDs. Both will create the AVD first via `scripts/android-setup.sh` if it does not exist. +- `scripts/android-setup.sh` now accepts env overrides: `AVD_API`, `AVD_DEVICE`, `AVD_TAG`, `AVD_ABI`, `AVD_NAME`. Defaults target API 21 for minsdk; CI passes API 33 for latest. The script auto-selects the best ABI for the host (arm64-v8a on arm, x86_64 on Intel) if `AVD_ABI` is unset. +- `devbox run reset-android` removes local AVDs/adb keys if you need a clean slate. +- `EMU_HEADLESS=1 devbox run start-android*` to run the emulator headless (CI sets this); omit for a visible emulator locally. +- `EMU_PORT=5554 devbox run start-android*` to set the emulator port/serial (defaults to 5554) and avoid adb conflicts. +- `devbox run test-android` runs `setup-android` first to ensure AVDs exist, then delegates startup to Detox. It will not boot an emulator itself; use `start-android*` if you want it running beforehand. + +### Detox defaults + +- Android Detox defaults to the latest AVD (`medium_phone_API33_arm64_v8a` on arm hosts, x86_64 otherwise). Set `DETOX_AVD=pixel_API21_*` to run against the minsdk AVD. +- CI Android E2E runs both API 21 (Pixel) and API 33 (Medium Phone) in parallel using these scripts by default. Override `ANDROID_MATRIX` in the workflow/job env to change the AVD matrix if needed. + +### Updating Android min/latest versions + +- Bump pinned SDKs in `nix/flake.nix` (platformVersions/buildToolsVersions/cmdLineToolsVersion). Rebuild devbox (`devbox shell --rebuild`) so everyone gets the new SDK. +- Update AVD defaults/names if you change API levels: + - `devbox.json` (`start-android-*` scripts) for default AVD names. + - `examples/E2E/.detoxrc.js` for the default `DETOX_AVD`. + - CI matrix in `.github/workflows/ci.yml` (`ANDROID_MATRIX` entries). +- Gradle uses `buildToolsVersion` from `examples/E2E/android/build.gradle`; it defaults to the devbox version but can be overridden via `ANDROID_BUILD_TOOLS_VERSION` to stay in sync. + +## iOS + +iOS uses the host Xcode toolchain. There is no Nix-provisioned iOS SDK. Run `devbox run setup-ios` to install pods and bootstrap the iOS example/E2E apps. Full Xcode is required for `simctl` (Command Line Tools alone are not enough). Make sure Xcode command line tools are selected (`xcode-select --print-path` or `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer`) and that you have agreed to the license if prompted. + +> Important: `devbox run` by itself uses the Nix compiler toolchain on macOS. For iOS work you must route commands through `devbox shell --omit-nix-env --command ""` (or run an interactive `devbox shell --omit-nix-env` first) so the Xcode toolchain is used. CI does this for setup/tests. + +### Simulators and Detox + +- `devbox run setup-ios` provisions simulators. Defaults are `IOS_DEVICE_NAMES="iPhone 14,iPhone 17"` and `IOS_RUNTIME="18.5"` (will fall back to the latest installed iOS runtime if 18.5 isn’t available). Override via env vars to target a specific device/runtime. Set `IOS_DOWNLOAD_RUNTIME=0` to skip attempting `xcodebuild -downloadPlatform iOS` when the preferred runtime is missing. Set `IOS_DEVELOPER_DIR` (e.g., `/Applications/Xcode.app/Contents/Developer`) to point at a specific Xcode; otherwise it uses `xcode-select -p` or the default Xcode.app if found. +- On macOS, use `devbox shell --omit-nix-env --command ""` when invoking iOS builds/tests to ensure the Xcode toolchain is used instead of the Nix compiler toolchain. +- `devbox run start-ios` provisions simulators (via `setup-ios`), then boots the chosen device (`DETOX_IOS_DEVICE` or default `iPhone 17`) and opens Simulator. Set `IOS_FLAVOR=minsdk` to target the min sim (iPhone 14 @ iOS 18.5) or leave default for latest. Internally uses `scripts/ios-manager.sh`. +- `devbox run reset-ios` shuts down/erases and removes all local simulator devices. +- `devbox run stop-android` / `stop-ios` / `stop` to shut down running emulators/simulators (handy for headless runs). +- Detox defaults to `iPhone 17` for local runs; override with `DETOX_IOS_DEVICE`. CI runs a matrix: min sim (iPhone 14 @ iOS 18.5) and latest (iPhone 17 @ iOS 26.1). +- `devbox run test-ios` runs `setup-ios` first to ensure simulators exist; Detox handles booting. Use `start-ios` if you want to pre-boot. + +### Common env knobs + +- Android: `AVD_FLAVOR` (minsdk/latest), `DETOX_AVD` (explicit AVD name), `EMU_HEADLESS` (1 for headless), `EMU_PORT` (emulator port/serial), `ANDROID_BUILD_TOOLS_VERSION` (override build-tools). +- iOS: `IOS_FLAVOR` (minsdk/latest), `DETOX_IOS_DEVICE` (explicit sim device), `IOS_RUNTIME` (preferred runtime), `IOS_DEVICE_NAMES` (comma list to create), `IOS_DEVELOPER_DIR` (Xcode path), `IOS_DOWNLOAD_RUNTIME` (0 to skip runtime download attempt). + +### Releases + +- `devbox run release` runs npm auth, yarn install/build, and `yarn release`. Requires `NPM_TOKEN` (and `GH_TOKEN`/`YARN_NPM_AUTH_TOKEN` for publishing). Used by the publish workflow. + +### Updating iOS min/latest versions + +- Adjust simulator defaults in `scripts/ios-setup.sh` (`IOS_DEVICE_NAMES`/`IOS_RUNTIME`) and `devbox.json` (`start-ios` uses `IOS_FLAVOR` to pick minsdk/latest). +- Update Detox default device in `examples/E2E/.detoxrc.js` if the default device changes. +- Update CI matrix in `.github/workflows/ci.yml` (ios-min/ios-latest rows: device and runtime values) and Xcode version if needed. diff --git a/yarn.lock b/yarn.lock index 4767704ef..3eee16575 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3380,7 +3380,7 @@ __metadata: version: 0.0.0-use.local resolution: "@segment/analytics-react-native-plugin-amplitude-session@workspace:packages/plugins/plugin-amplitudeSession" dependencies: - "@segment/analytics-react-native": "npm:^2.18.0" + "@segment/analytics-react-native": "npm:^2.21.4" "@segment/analytics-rn-shared": "workspace:^" "@segment/sovran-react-native": "npm:^1.1.0" "@types/jest": "npm:^29.5.8" @@ -3615,7 +3615,7 @@ __metadata: languageName: unknown linkType: soft -"@segment/analytics-react-native@npm:^2.18.0, @segment/analytics-react-native@workspace:packages/core": +"@segment/analytics-react-native@npm:^2.18.0, @segment/analytics-react-native@npm:^2.21.4, @segment/analytics-react-native@workspace:packages/core": version: 0.0.0-use.local resolution: "@segment/analytics-react-native@workspace:packages/core" dependencies: