From dc734c4b6b1d9447380d0b31ba88961a41e73350 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Tue, 13 Jan 2026 20:21:00 -0600 Subject: [PATCH 01/17] ci2 attempt --- .github/workflows/ci.yml | 112 +++--------- devbox.json | 61 +++++++ devbox.lock | 162 ++++++++++++++++++ examples/E2E/.detoxrc.js | 4 +- examples/E2E/android/app/build.gradle | 6 - .../MainApplication.java | 1 - examples/E2E/android/gradle.properties | 3 - examples/E2E/ios/Podfile | 21 +-- examples/E2E/ios/Podfile.lock | 118 +------------ gradle.properties | 1 + yarn.lock | 4 +- 11 files changed, 267 insertions(+), 226 deletions(-) create mode 100644 devbox.json create mode 100644 devbox.lock create mode 100644 gradle.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a83508c95..71bb6e57f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,85 +15,52 @@ 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: 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: build + run: devbox run build run-e2e-ios: - runs-on: 'macos-13' + runs-on: 'macos-15' env: YARN_ENABLE_HARDENED_MODE: 0 steps: - 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 - + xcode-version: '26.1.1' - 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: 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: 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 + enable-cache: 'true' + # --omit-nix-env is important to use the macos system c toolchain instead of the nix toolchain + - name: devbox shell + run: devbox shell --omit-nix-env + - name: setup ios devices + run: devbox run setup-ios-devices + - name: IOS E2E Tests + run: devbox run 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 + api-level: [24] + profile: ['medium_phone'] steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - name: devbox installer + uses: jetify-com/devbox-install-action@v0.14.0 with: - distribution: 'temurin' - java-version: '17' - cache: 'gradle' - + enable-cache: 'true' + - name: devbox shell + run: devbox shell - name: Gradle cache uses: actions/cache@v4 with: @@ -101,7 +68,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 @@ -117,42 +83,20 @@ jobs: with: api-level: ${{ matrix.api-level }} profile: ${{matrix.profile}} - avd-name: Pixel_API_21_AOSP + avd-name: Medium_Phone_API_24 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: Detox - Test uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} profile: ${{matrix.profile}} - avd-name: Pixel_API_21_AOSP + avd-name: Medium_Phone_API_24 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 + script: devbox run test-android diff --git a/devbox.json b/devbox.json new file mode 100644 index 000000000..d44cad9d6 --- /dev/null +++ b/devbox.json @@ -0,0 +1,61 @@ +{ + "$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" + }, + "shell": { + "init_hook": [ + "echo 'Welcome to analytics-react-native devbox!' > /dev/null", + "export IOS_RUNTIME=$(xcrun simctl list runtimes --json | jq -r '.runtimes[0] | .identifier')" + ], + "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" + ], + "setup-ios-devices": [ + "xcrun simctl list devicetypes", + "xcrun simctl list runtimes", + "xcrun simctl delete 'iPhone 14'", + "xcrun simctl delete 'iPhone 17'", + "xcrun simctl create 'iPhone 14' 'iPhone 14' $IOS_RUNTIME", + "xcrun simctl create 'iPhone 17' 'iPhone 17' $IOS_RUNTIME", + "xcrun simctl list devices" + ], + "test-android": [ + "yarn install", + "yarn e2e install", + "yarn build", + "yarn e2e build:android", + "yarn e2e test:android" + ], + "test-ios": [ + "yarn install", + "yarn e2e install", + "yarn e2e pods", + "yarn build", + "yarn e2e build:ios", + "yarn e2e test:ios" + ], + "test": [ + "devbox run test-android", + "devbox run test-ios --omit-nix-env" + ] + } + } +} diff --git a/devbox.lock b/devbox.lock new file mode 100644 index 000000000..56e97705f --- /dev/null +++ b/devbox.lock @@ -0,0 +1,162 @@ +{ + "lockfile_version": "1", + "packages": { + "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-12T00:44:08Z", + "resolved": "github:NixOS/nixpkgs/3fbab70c6e69c87ea2b6e48aa6629da2aa6a23b0?lastModified=1768178648" + }, + "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" + } + } + }, + "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..998a71b9d 100644 --- a/examples/E2E/.detoxrc.js +++ b/examples/E2E/.detoxrc.js @@ -51,7 +51,7 @@ module.exports = { simulator: { type: 'ios.simulator', device: { - type: 'iPhone 14' + type: 'iPhone 17' } }, attached: { @@ -63,7 +63,7 @@ module.exports = { emulator: { type: 'android.emulator', device: { - avdName: process.env.CI ? 'Pixel_API_21_AOSP': 'Pixel_3a_API_32' + avdName: process.env.CI ? 'Pixel_API_21_AOSP': 'Medium_Phone_API_24' } } }, 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/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/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: From a401eef7c47100e6d2f38581061c5bc867045fad Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Tue, 13 Jan 2026 20:21:44 -0600 Subject: [PATCH 02/17] ci2 attempt --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71bb6e57f..9639ba5c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: CI on: push: - branches: [master, beta] + branches: [master, beta, ci2] pull_request: branches: [master, beta] workflow_dispatch: From a95e2baaa6d2708845a190faf13ffa5b54634053 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 08:07:45 -0600 Subject: [PATCH 03/17] ci2 attempt 2 --- .github/workflows/ci.yml | 2 +- devbox.json | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9639ba5c8..f90cd6d74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: strategy: matrix: api-level: [24] - profile: ['medium_phone'] + profile: ['Medium_Phone'] steps: - uses: actions/checkout@v4 - name: devbox installer diff --git a/devbox.json b/devbox.json index d44cad9d6..18f275b51 100644 --- a/devbox.json +++ b/devbox.json @@ -31,8 +31,6 @@ "setup-ios-devices": [ "xcrun simctl list devicetypes", "xcrun simctl list runtimes", - "xcrun simctl delete 'iPhone 14'", - "xcrun simctl delete 'iPhone 17'", "xcrun simctl create 'iPhone 14' 'iPhone 14' $IOS_RUNTIME", "xcrun simctl create 'iPhone 17' 'iPhone 17' $IOS_RUNTIME", "xcrun simctl list devices" From 4fd3770a74bd16fcb9a281330ff82306feb92646 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 08:19:08 -0600 Subject: [PATCH 04/17] ci2 attempt 3 --- .github/workflows/ci.yml | 8 ++++---- devbox.json | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f90cd6d74..d3b98f927 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,8 +51,8 @@ jobs: runs-on: 'ubuntu-22.04' strategy: matrix: - api-level: [24] - profile: ['Medium_Phone'] + api-level: [21] + profile: ['Pixel'] steps: - uses: actions/checkout@v4 - name: devbox installer @@ -83,7 +83,7 @@ jobs: with: api-level: ${{ matrix.api-level }} profile: ${{matrix.profile}} - avd-name: Medium_Phone_API_24 + avd-name: Pixel_API_21 target: default force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none @@ -94,7 +94,7 @@ jobs: with: api-level: ${{ matrix.api-level }} profile: ${{matrix.profile}} - avd-name: Medium_Phone_API_24 + avd-name: Pixel_API_21 target: default force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none diff --git a/devbox.json b/devbox.json index 18f275b51..622e991a5 100644 --- a/devbox.json +++ b/devbox.json @@ -29,6 +29,7 @@ "yarn test --coverage" ], "setup-ios-devices": [ + "xcodebuild -downloadPlatform", "xcrun simctl list devicetypes", "xcrun simctl list runtimes", "xcrun simctl create 'iPhone 14' 'iPhone 14' $IOS_RUNTIME", From b25842e678e141933079093304958068894bc5ba Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 08:30:33 -0600 Subject: [PATCH 05/17] attempt 4 --- .github/workflows/ci.yml | 2 +- devbox.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3b98f927..7b5ded671 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: strategy: matrix: api-level: [21] - profile: ['Pixel'] + profile: ['pixel'] steps: - uses: actions/checkout@v4 - name: devbox installer diff --git a/devbox.json b/devbox.json index 622e991a5..1ffaa4c06 100644 --- a/devbox.json +++ b/devbox.json @@ -29,7 +29,7 @@ "yarn test --coverage" ], "setup-ios-devices": [ - "xcodebuild -downloadPlatform", + "xcodebuild -downloadPlatform iOS", "xcrun simctl list devicetypes", "xcrun simctl list runtimes", "xcrun simctl create 'iPhone 14' 'iPhone 14' $IOS_RUNTIME", From 808ae376a2f810febbd09f40d7ecc09182db9c00 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 08:37:29 -0600 Subject: [PATCH 06/17] add jq --- devbox.json | 23 ++++++----- devbox.lock | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 11 deletions(-) diff --git a/devbox.json b/devbox.json index 1ffaa4c06..3fc247a2f 100644 --- a/devbox.json +++ b/devbox.json @@ -3,16 +3,17 @@ "packages": { "cocoapods": { "version": "latest", - "platforms": ["x86_64-darwin", "aarch64-darwin"] + "platforms": ["x86_64-darwin", "aarch64-darwin"], }, "yarn-berry": "latest", "jdk17": "latest", - "gradle": "latest" + "gradle": "latest", + "jq": "latest", }, "shell": { "init_hook": [ "echo 'Welcome to analytics-react-native devbox!' > /dev/null", - "export IOS_RUNTIME=$(xcrun simctl list runtimes --json | jq -r '.runtimes[0] | .identifier')" + "export IOS_RUNTIME=$(xcrun simctl list runtimes --json | jq -r '.runtimes[0] | .identifier')", ], "scripts": { "clean": [ @@ -26,7 +27,7 @@ "yarn install --immutable", "yarn build", "yarn lint", - "yarn test --coverage" + "yarn test --coverage", ], "setup-ios-devices": [ "xcodebuild -downloadPlatform iOS", @@ -34,14 +35,14 @@ "xcrun simctl list runtimes", "xcrun simctl create 'iPhone 14' 'iPhone 14' $IOS_RUNTIME", "xcrun simctl create 'iPhone 17' 'iPhone 17' $IOS_RUNTIME", - "xcrun simctl list devices" + "xcrun simctl list devices", ], "test-android": [ "yarn install", "yarn e2e install", "yarn build", "yarn e2e build:android", - "yarn e2e test:android" + "yarn e2e test:android", ], "test-ios": [ "yarn install", @@ -49,12 +50,12 @@ "yarn e2e pods", "yarn build", "yarn e2e build:ios", - "yarn e2e test:ios" + "yarn e2e test:ios", ], "test": [ "devbox run test-android", - "devbox run test-ios --omit-nix-env" - ] - } - } + "devbox run test-ios --omit-nix-env", + ], + }, + }, } diff --git a/devbox.lock b/devbox.lock index 56e97705f..1abb9a817 100644 --- a/devbox.lock +++ b/devbox.lock @@ -110,6 +110,122 @@ } } }, + "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", From 4fd8d47dd769bc6355c118f8dd846db4058154b2 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 08:38:35 -0600 Subject: [PATCH 07/17] move iosruntime --- devbox.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devbox.json b/devbox.json index 3fc247a2f..82f3df3cf 100644 --- a/devbox.json +++ b/devbox.json @@ -13,7 +13,6 @@ "shell": { "init_hook": [ "echo 'Welcome to analytics-react-native devbox!' > /dev/null", - "export IOS_RUNTIME=$(xcrun simctl list runtimes --json | jq -r '.runtimes[0] | .identifier')", ], "scripts": { "clean": [ @@ -30,12 +29,13 @@ "yarn test --coverage", ], "setup-ios-devices": [ + "IOS_RUNTIME=$(xcrun simctl list runtimes --json | jq -r '.runtimes[0] | .identifier')", "xcodebuild -downloadPlatform iOS", "xcrun simctl list devicetypes", "xcrun simctl list runtimes", "xcrun simctl create 'iPhone 14' 'iPhone 14' $IOS_RUNTIME", "xcrun simctl create 'iPhone 17' 'iPhone 17' $IOS_RUNTIME", - "xcrun simctl list devices", + "xcrun simctl list devices" ], "test-android": [ "yarn install", From a59e57974b003284b2bc9847420f95ca2a2d746e Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 11:47:34 -0600 Subject: [PATCH 08/17] macos-15-intel --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b5ded671..d48321f54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: run: devbox run build run-e2e-ios: - runs-on: 'macos-15' + runs-on: ['macos-15', 'macos-15-intel' ] env: YARN_ENABLE_HARDENED_MODE: 0 steps: From b3139657ab1be02a778c37a9c28600784e0e1c53 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 14:09:49 -0600 Subject: [PATCH 09/17] flake' --- AGENTS.md | 29 +++++++++ devbox.json | 31 +++++---- devbox.lock | 4 +- nix/.cache/nix/fetcher-cache-v4.sqlite | Bin 0 -> 12288 bytes nix/android-tools.nix | 12 ++++ nix/flake.nix | 40 ++++++++++++ scripts/setup-android.sh | 86 +++++++++++++++++++++++++ scripts/setup-ios.sh | 83 ++++++++++++++++++++++++ 8 files changed, 269 insertions(+), 16 deletions(-) create mode 100644 AGENTS.md create mode 100644 nix/.cache/nix/fetcher-cache-v4.sqlite create mode 100644 nix/android-tools.nix create mode 100644 nix/flake.nix create mode 100755 scripts/setup-android.sh create mode 100755 scripts/setup-ios.sh 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 index 82f3df3cf..c5f093c45 100644 --- a/devbox.json +++ b/devbox.json @@ -5,14 +5,20 @@ "version": "latest", "platforms": ["x86_64-darwin", "aarch64-darwin"], }, - "yarn-berry": "latest", - "jdk17": "latest", - "gradle": "latest", - "jq": "latest", + "android-tools": "flake:./nix#android-sdk", + "yarn-berry": "latest", + "jdk17": "latest", + "gradle": "latest", + "jq": "latest", }, "shell": { "init_hook": [ "echo 'Welcome to analytics-react-native devbox!' > /dev/null", + "OS_NAME=$(uname -s)", + "if [ \"$OS_NAME\" = \"Darwin\" ]; then SDK_ROOT_DEFAULT=\"$HOME/Library/Android/sdk\"; else SDK_ROOT_DEFAULT=\"$HOME/Android/Sdk\"; fi", + "export ANDROID_SDK_ROOT=\"${ANDROID_SDK_ROOT:-$SDK_ROOT_DEFAULT}\"", + "export ANDROID_HOME=\"$ANDROID_SDK_ROOT\"", + "export PATH=\"$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH\"", ], "scripts": { "clean": [ @@ -28,15 +34,6 @@ "yarn lint", "yarn test --coverage", ], - "setup-ios-devices": [ - "IOS_RUNTIME=$(xcrun simctl list runtimes --json | jq -r '.runtimes[0] | .identifier')", - "xcodebuild -downloadPlatform iOS", - "xcrun simctl list devicetypes", - "xcrun simctl list runtimes", - "xcrun simctl create 'iPhone 14' 'iPhone 14' $IOS_RUNTIME", - "xcrun simctl create 'iPhone 17' 'iPhone 17' $IOS_RUNTIME", - "xcrun simctl list devices" - ], "test-android": [ "yarn install", "yarn e2e install", @@ -52,9 +49,15 @@ "yarn e2e build:ios", "yarn e2e test:ios", ], + "setup-android": [ + "bash $DEVBOX_PROJECT_ROOT/scripts/setup-android.sh", + ], + "setup-ios": [ + "bash $DEVBOX_PROJECT_ROOT/scripts/setup-ios.sh", + ], "test": [ "devbox run test-android", - "devbox run test-ios --omit-nix-env", + "devbox run test-ios", ], }, }, diff --git a/devbox.lock b/devbox.lock index 1abb9a817..e3cb89a8b 100644 --- a/devbox.lock +++ b/devbox.lock @@ -30,8 +30,8 @@ } }, "github:NixOS/nixpkgs/nixpkgs-unstable": { - "last_modified": "2026-01-12T00:44:08Z", - "resolved": "github:NixOS/nixpkgs/3fbab70c6e69c87ea2b6e48aa6629da2aa6a23b0?lastModified=1768178648" + "last_modified": "2026-01-13T11:13:53Z", + "resolved": "github:NixOS/nixpkgs/61db79b0c6b838d9894923920b612048e1201926?lastModified=1768302833" }, "gradle@latest": { "last_modified": "2025-12-31T03:27:36Z", diff --git a/nix/.cache/nix/fetcher-cache-v4.sqlite b/nix/.cache/nix/fetcher-cache-v4.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..1e4127039112df7d15a472eeda70c9ad45e39f60 GIT binary patch literal 12288 zcmeI$&rZTH90%}jq9Tczw_Tf^AP@s{I3O|MPq^X1KX+1C!N!frCN&aO>Nzm ztmpF<>)&iHdh4>?xNg(yMzhnV$$gs5P!(PnFfoc}lFua_g{n_|zmODf`68~U_@p>v z{*-@Jlp^30$$~p7Mv@PC^x?^OkqB6{pz#~Bn>`fbB$vtM^gZ*;)QA{)d_Ecb>SW(# zQyC_CH`#l)lnn8?J{@}=s;(IiL^T8iAOHafKmY;|fB*y_009U<00Mt4z_s1{xRn|T z* { } }: + +let + androidPkgs = pkgs.androidenv.composeAndroidPackages { + licenseAccepted = true; + numLatestPlatformVersions = 1; + includeEmulator = "yes"; + includeSystemImages = "yes"; + includeNDK = "yes"; + }; +in +androidPkgs.androidsdk diff --git a/nix/flake.nix b/nix/flake.nix new file mode 100644 index 000000000..24481eac0 --- /dev/null +++ b/nix/flake.nix @@ -0,0 +1,40 @@ +{ + 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; + }; + + androidPkgs = pkgs.androidenv.composeAndroidPackages { + licenseAccepted = true; + numLatestPlatformVersions = 1; + includeEmulator = "yes"; + includeSystemImages = "yes"; + includeNDK = "yes"; + }; + in + { + android-sdk = androidPkgs.androidsdk; + }); + }; +} diff --git a/scripts/setup-android.sh b/scripts/setup-android.sh new file mode 100755 index 000000000..2d73600ec --- /dev/null +++ b/scripts/setup-android.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Installs required Android system images and creates AVDs for Pixel (API 21) and medium_phone (latest). +# Run inside a devbox shell so SDK tools are available. + +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 +} + +accept_licenses() { + yes | sdkmanager --licenses >/dev/null +} + +install_package() { + local pkg="$1" + echo "Installing ${pkg} (if needed)..." + yes | sdkmanager "$pkg" >/dev/null +} + +latest_google_apis_image() { + sdkmanager --list | tr -d '\r' | grep -o 'system-images;android-[0-9][0-9]*;google_apis;[^ ]*' | sort -t'-' -k2,2n | tail -n1 +} + +avd_exists() { + local name="$1" + avdmanager list avd | grep -q "Name: ${name}" +} + +ensure_cmdline_tools() { + if command -v avdmanager >/dev/null 2>&1; then + return 0 + fi + echo "Installing Android cmdline-tools (required for avdmanager)..." + yes | sdkmanager "cmdline-tools;latest" >/dev/null + export PATH="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH" + if ! command -v avdmanager >/dev/null 2>&1; then + echo "avdmanager still unavailable after installing cmdline-tools. Check ANDROID_SDK_ROOT and sdkmanager output." >&2 + exit 1 + fi +} + +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() { + if [[ -z "${ANDROID_SDK_ROOT:-}" && -z "${ANDROID_HOME:-}" ]]; then + echo "ANDROID_SDK_ROOT/ANDROID_HOME must be set (devbox shell init sets these)." >&2 + exit 1 + fi + require_tool sdkmanager + + ensure_cmdline_tools + install_package "platform-tools" + install_package "emulator" + accept_licenses + + local api21_image="system-images;android-21;google_apis;x86_64" + install_package "platforms;android-21" + install_package "$api21_image" + create_avd "Pixel_API21" "pixel" "$api21_image" + + if latest_image=$(latest_google_apis_image); then + install_package "$latest_image" + create_avd "MediumPhone_Latest" "medium_phone" "$latest_image" + else + echo "Could not determine latest google_apis system image. Check sdkmanager --list output." >&2 + fi + + echo "AVDs ready. Boot with: emulator -avd Pixel_API21 (or MediumPhone_Latest) --netdelay none --netspeed full" +} + +main "$@" diff --git a/scripts/setup-ios.sh b/scripts/setup-ios.sh new file mode 100755 index 000000000..7cb2221c5 --- /dev/null +++ b/scripts/setup-ios.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Creates local iOS simulators for common targets. Requires Xcode command-line tools and jq. + +require_tool() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required tool: $1. Install Xcode CLI tools before running." >&2 + exit 1 + fi +} + +require_tool xcrun +require_tool jq + +runtime_info_for_version() { + local version="$1" + local json runtime_id runtime_name + json="$(xcrun simctl list runtimes -j)" + runtime_id="$(echo "$json" | jq -r --arg v "$version" '.runtimes[] | select(.isAvailable and (.name|startswith("iOS \($v)"))) | .identifier' | head -n1)" + runtime_name="$(echo "$json" | jq -r --arg v "$version" '.runtimes[] | select(.isAvailable and (.name|startswith("iOS \($v)"))) | .name' | head -n1)" + + if [[ -z "$runtime_id" || "$runtime_id" == "null" ]]; then + return 1 + fi + printf "match|%s|%s\n" "$runtime_id" "$runtime_name" +} + +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" os_version="$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 info status runtime_id runtime_name + if ! info=$(runtime_info_for_version "$os_version"); then + echo "Required runtime iOS ${os_version} not found. Install it in Xcode (Preferences > Platforms) and retry." >&2 + return 0 + fi + IFS="|" read -r status runtime_id runtime_name <<<"$info" + + 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 + + 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" +} + +main() { + ensure_device "iPhone 14" "16" + ensure_device "iPhone 17" "26.1.1" + echo "Done. Launch via Xcode > Devices or 'xcrun simctl boot \"\"' then 'open -a Simulator'." +} + +main "$@" From 99e198834bb8b6f439498bfb6d6294d012b70226 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 14:26:21 -0600 Subject: [PATCH 10/17] android sdk --- devbox.json | 10 +++++----- nix/flake.lock | 27 +++++++++++++++++++++++++++ nix/flake.nix | 9 ++++----- 3 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 nix/flake.lock diff --git a/devbox.json b/devbox.json index c5f093c45..99446e0a9 100644 --- a/devbox.json +++ b/devbox.json @@ -5,11 +5,11 @@ "version": "latest", "platforms": ["x86_64-darwin", "aarch64-darwin"], }, - "android-tools": "flake:./nix#android-sdk", - "yarn-berry": "latest", - "jdk17": "latest", - "gradle": "latest", - "jq": "latest", + "yarn-berry": "latest", + "jdk17": "latest", + "gradle": "latest", + "jq": "latest", + "./nix#android-sdk": "", }, "shell": { "init_hook": [ 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 index 24481eac0..b88aef382 100644 --- a/nix/flake.nix +++ b/nix/flake.nix @@ -26,11 +26,10 @@ }; androidPkgs = pkgs.androidenv.composeAndroidPackages { - licenseAccepted = true; - numLatestPlatformVersions = 1; - includeEmulator = "yes"; - includeSystemImages = "yes"; - includeNDK = "yes"; + platformVersions = ["21"]; + includeEmulator = true; + includeSystemImages = true; + includeNDK = true; }; in { From 966e3e0ba86039cdb549a16cb732413d1a2be387 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 14:55:27 -0600 Subject: [PATCH 11/17] checkpoint --- devbox.json | 20 +++++++--- devbox.lock | 4 +- nix/android-tools.nix | 12 ------ nix/flake.nix | 4 ++ scripts/setup-android.sh | 82 +++++++++++++++++++++------------------- 5 files changed, 63 insertions(+), 59 deletions(-) delete mode 100644 nix/android-tools.nix diff --git a/devbox.json b/devbox.json index 99446e0a9..71fea19c3 100644 --- a/devbox.json +++ b/devbox.json @@ -9,16 +9,24 @@ "jdk17": "latest", "gradle": "latest", "jq": "latest", - "./nix#android-sdk": "", + "path:./nix#android-sdk": "", + }, + "nix_config": { + "extra-experimental-features": "nix-command flakes" }, "shell": { "init_hook": [ "echo 'Welcome to analytics-react-native devbox!' > /dev/null", - "OS_NAME=$(uname -s)", - "if [ \"$OS_NAME\" = \"Darwin\" ]; then SDK_ROOT_DEFAULT=\"$HOME/Library/Android/sdk\"; else SDK_ROOT_DEFAULT=\"$HOME/Android/Sdk\"; fi", - "export ANDROID_SDK_ROOT=\"${ANDROID_SDK_ROOT:-$SDK_ROOT_DEFAULT}\"", - "export ANDROID_HOME=\"$ANDROID_SDK_ROOT\"", - "export PATH=\"$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH\"", + "sdk_source=\"\"", + "if [ -n \"${ANDROID_SDK_ROOT:-}\" ]; then sdk_source=\"user-defined ANDROID_SDK_ROOT\"; fi", + "if [ -z \"$sdk_source\" ] && [ -n \"${ANDROID_HOME:-}\" ]; then ANDROID_SDK_ROOT=\"$ANDROID_HOME\"; sdk_source=\"user-defined ANDROID_HOME\"; fi", + "if [ -z \"$sdk_source\" ]; then SM=\"\"; for CAND in $(command -v -a sdkmanager 2>/dev/null || true); do if echo \"$CAND\" | grep -q '^/nix/store/'; then SM=\"$CAND\"; break; fi; done; if [ -n \"$SM\" ]; then SM=$(readlink -f \"$SM\" 2>/dev/null || echo \"$SM\"); for C in \"$(dirname \"$SM\")/..\" \"$(dirname \"$SM\")/../share/android-sdk\" \"$(dirname \"$SM\")/../libexec/android-sdk\" \"$(dirname \"$SM\")/../..\"; do if [ -d \"$C/platform-tools\" ] || [ -d \"$C/platforms\" ] || [ -d \"$C/system-images\" ]; then ANDROID_SDK_ROOT=\"$C\"; sdk_source=\"nix sdkmanager\"; break; fi; done; fi; fi", + "if [ -z \"$sdk_source\" ]; then if [ \"$(uname -s)\" = \"Darwin\" ]; then ANDROID_SDK_ROOT=\"$HOME/Library/Android/sdk\"; else ANDROID_SDK_ROOT=\"$HOME/Android/Sdk\"; fi; sdk_source=\"OS default\"; fi", + "export ANDROID_SDK_ROOT", + "if [ -z \"${ANDROID_HOME:-}\" ]; then export ANDROID_HOME=\"$ANDROID_SDK_ROOT\"; fi", + "if [ -n \"${ANDROID_SDK_ROOT:-}\" ]; then export PATH=\"$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH\"; fi", + "echo \"Using Android SDK: $ANDROID_SDK_ROOT (source: $sdk_source)\"", + "if [ \"$sdk_source\" = \"OS default\" ]; then echo \"Note: Nix flake android-sdk not detected; run 'devbox install' (or 'devbox add path:./nix#android-sdk') to ensure the flake SDK is pulled.\"; fi", ], "scripts": { "clean": [ diff --git a/devbox.lock b/devbox.lock index e3cb89a8b..61f6d9afb 100644 --- a/devbox.lock +++ b/devbox.lock @@ -30,8 +30,8 @@ } }, "github:NixOS/nixpkgs/nixpkgs-unstable": { - "last_modified": "2026-01-13T11:13:53Z", - "resolved": "github:NixOS/nixpkgs/61db79b0c6b838d9894923920b612048e1201926?lastModified=1768302833" + "last_modified": "2026-01-14T04:14:06Z", + "resolved": "github:NixOS/nixpkgs/ea30586ee015f37f38783006a9bc9e4aa64d7d61?lastModified=1768364046" }, "gradle@latest": { "last_modified": "2025-12-31T03:27:36Z", diff --git a/nix/android-tools.nix b/nix/android-tools.nix deleted file mode 100644 index 79d634d77..000000000 --- a/nix/android-tools.nix +++ /dev/null @@ -1,12 +0,0 @@ -{ pkgs ? import { } }: - -let - androidPkgs = pkgs.androidenv.composeAndroidPackages { - licenseAccepted = true; - numLatestPlatformVersions = 1; - includeEmulator = "yes"; - includeSystemImages = "yes"; - includeNDK = "yes"; - }; -in -androidPkgs.androidsdk diff --git a/nix/flake.nix b/nix/flake.nix index b88aef382..bc0bdb362 100644 --- a/nix/flake.nix +++ b/nix/flake.nix @@ -23,6 +23,10 @@ let pkgs = import nixpkgs { inherit system; + config = { + allowUnfree = true; + android_sdk.accept_license = true; + }; }; androidPkgs = pkgs.androidenv.composeAndroidPackages { diff --git a/scripts/setup-android.sh b/scripts/setup-android.sh index 2d73600ec..87341285f 100755 --- a/scripts/setup-android.sh +++ b/scripts/setup-android.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -# Installs required Android system images and creates AVDs for Pixel (API 21) and medium_phone (latest). +# Creates an AVD using the Android SDK provided by devbox/flake (system images, emulator, NDK already installed). # Run inside a devbox shell so SDK tools are available. require_tool() { @@ -11,18 +11,30 @@ require_tool() { fi } -accept_licenses() { - yes | sdkmanager --licenses >/dev/null -} - -install_package() { - local pkg="$1" - echo "Installing ${pkg} (if needed)..." - yes | sdkmanager "$pkg" >/dev/null -} +detect_sdk_root() { + if [[ -n "${ANDROID_SDK_ROOT:-}" ]]; then + echo "$ANDROID_SDK_ROOT" + return + fi -latest_google_apis_image() { - sdkmanager --list | tr -d '\r' | grep -o 'system-images;android-[0-9][0-9]*;google_apis;[^ ]*' | sort -t'-' -k2,2n | tail -n1 + 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() { @@ -30,19 +42,6 @@ avd_exists() { avdmanager list avd | grep -q "Name: ${name}" } -ensure_cmdline_tools() { - if command -v avdmanager >/dev/null 2>&1; then - return 0 - fi - echo "Installing Android cmdline-tools (required for avdmanager)..." - yes | sdkmanager "cmdline-tools;latest" >/dev/null - export PATH="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH" - if ! command -v avdmanager >/dev/null 2>&1; then - echo "avdmanager still unavailable after installing cmdline-tools. Check ANDROID_SDK_ROOT and sdkmanager output." >&2 - exit 1 - fi -} - create_avd() { local name="$1" device="$2" image="$3" local abi="${image##*;}" @@ -57,29 +56,34 @@ create_avd() { } 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 (devbox shell init sets these)." >&2 + 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 - require_tool sdkmanager - ensure_cmdline_tools - install_package "platform-tools" - install_package "emulator" - accept_licenses + export ANDROID_HOME="${ANDROID_HOME:-$ANDROID_SDK_ROOT}" + + require_tool avdmanager + require_tool emulator local api21_image="system-images;android-21;google_apis;x86_64" - install_package "platforms;android-21" - install_package "$api21_image" - create_avd "Pixel_API21" "pixel" "$api21_image" - if latest_image=$(latest_google_apis_image); then - install_package "$latest_image" - create_avd "MediumPhone_Latest" "medium_phone" "$latest_image" - else - echo "Could not determine latest google_apis system image. Check sdkmanager --list output." >&2 + local image_path="${ANDROID_SDK_ROOT}/system-images/android-21/google_apis/x86_64" + if [[ ! -d "$image_path" ]]; then + echo "Expected system image not found at ${image_path}." >&2 + echo "Re-enter the devbox shell (flake should provide images) or rebuild Devbox to fetch them." >&2 + exit 1 fi + create_avd "Pixel_API21" "pixel" "$api21_image" + echo "AVDs ready. Boot with: emulator -avd Pixel_API21 (or MediumPhone_Latest) --netdelay none --netspeed full" } From 1be24c41baddd5c84d6a8a76747888596ddfc4fc Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 17:41:27 -0600 Subject: [PATCH 12/17] big. huge. immaculate even. all to fall to a little red x --- .github/workflows/ci.yml | 122 ++++++++++++++------ .github/workflows/publish.yml | 17 ++- devbox.json | 85 +++++++++++--- examples/E2E/.detoxrc.js | 10 +- examples/E2E/android/build.gradle | 5 +- nix/.cache/nix/fetcher-cache-v4.sqlite | Bin 12288 -> 12288 bytes nix/flake.nix | 6 +- scripts/android-env.sh | 57 ++++++++++ scripts/android-manager.sh | 53 +++++++++ scripts/android-setup.sh | 152 +++++++++++++++++++++++++ scripts/ios-manager.sh | 43 +++++++ scripts/ios-setup.sh | 147 ++++++++++++++++++++++++ scripts/setup-android.sh | 90 --------------- scripts/setup-ios.sh | 83 -------------- wiki/devbox.md | 71 ++++++++++++ 15 files changed, 703 insertions(+), 238 deletions(-) create mode 100755 scripts/android-env.sh create mode 100755 scripts/android-manager.sh create mode 100755 scripts/android-setup.sh create mode 100755 scripts/ios-manager.sh create mode 100755 scripts/ios-setup.sh delete mode 100755 scripts/setup-android.sh delete mode 100755 scripts/setup-ios.sh create mode 100644 wiki/devbox.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d48321f54..886e34d26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,40 +27,92 @@ jobs: run: devbox run build run-e2e-ios: - runs-on: ['macos-15', 'macos-15-intel' ] + 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: maxim-lobanov/setup-xcode@v1 with: xcode-version: '26.1.1' - uses: actions/checkout@v4 + - name: CocoaPods cache + uses: actions/cache@v4 + with: + 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: + 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: devbox shell - run: devbox shell --omit-nix-env - - name: setup ios devices - run: devbox run setup-ios-devices - - name: IOS E2E Tests - run: devbox run test-ios + - 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: 'ubuntu-22.04' + env: + # Default to both minsdk and latest; override ANDROID_MATRIX env to narrow/expand if needed. + ANDROID_MATRIX: > + [ + {"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"} + ] strategy: matrix: - api-level: [21] - profile: ['pixel'] + include: ${{ fromJson(env.ANDROID_MATRIX) }} steps: - uses: actions/checkout@v4 + - name: Yarn cache + uses: actions/cache@v4 + with: + 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: devbox shell - run: devbox shell - name: Gradle cache uses: actions/cache@v4 with: @@ -75,28 +127,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 - 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." - - name: Detox - Test - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - profile: ${{matrix.profile}} - avd-name: Pixel_API_21 - target: default - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: devbox run test-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: 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/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/devbox.json b/devbox.json index 71fea19c3..90c23a403 100644 --- a/devbox.json +++ b/devbox.json @@ -11,22 +11,11 @@ "jq": "latest", "path:./nix#android-sdk": "", }, - "nix_config": { - "extra-experimental-features": "nix-command flakes" - }, "shell": { "init_hook": [ "echo 'Welcome to analytics-react-native devbox!' > /dev/null", - "sdk_source=\"\"", - "if [ -n \"${ANDROID_SDK_ROOT:-}\" ]; then sdk_source=\"user-defined ANDROID_SDK_ROOT\"; fi", - "if [ -z \"$sdk_source\" ] && [ -n \"${ANDROID_HOME:-}\" ]; then ANDROID_SDK_ROOT=\"$ANDROID_HOME\"; sdk_source=\"user-defined ANDROID_HOME\"; fi", - "if [ -z \"$sdk_source\" ]; then SM=\"\"; for CAND in $(command -v -a sdkmanager 2>/dev/null || true); do if echo \"$CAND\" | grep -q '^/nix/store/'; then SM=\"$CAND\"; break; fi; done; if [ -n \"$SM\" ]; then SM=$(readlink -f \"$SM\" 2>/dev/null || echo \"$SM\"); for C in \"$(dirname \"$SM\")/..\" \"$(dirname \"$SM\")/../share/android-sdk\" \"$(dirname \"$SM\")/../libexec/android-sdk\" \"$(dirname \"$SM\")/../..\"; do if [ -d \"$C/platform-tools\" ] || [ -d \"$C/platforms\" ] || [ -d \"$C/system-images\" ]; then ANDROID_SDK_ROOT=\"$C\"; sdk_source=\"nix sdkmanager\"; break; fi; done; fi; fi", - "if [ -z \"$sdk_source\" ]; then if [ \"$(uname -s)\" = \"Darwin\" ]; then ANDROID_SDK_ROOT=\"$HOME/Library/Android/sdk\"; else ANDROID_SDK_ROOT=\"$HOME/Android/Sdk\"; fi; sdk_source=\"OS default\"; fi", - "export ANDROID_SDK_ROOT", - "if [ -z \"${ANDROID_HOME:-}\" ]; then export ANDROID_HOME=\"$ANDROID_SDK_ROOT\"; fi", - "if [ -n \"${ANDROID_SDK_ROOT:-}\" ]; then export PATH=\"$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH\"; fi", - "echo \"Using Android SDK: $ANDROID_SDK_ROOT (source: $sdk_source)\"", - "if [ \"$sdk_source\" = \"OS default\" ]; then echo \"Note: Nix flake android-sdk not detected; run 'devbox install' (or 'devbox add path:./nix#android-sdk') to ensure the flake SDK is pulled.\"; fi", + "source \"$DEVBOX_PROJECT_ROOT/scripts/android-env.sh\"", + "echo 'Android SDK env configured (details: wiki/devbox.md#devbox-android).'", ], "scripts": { "clean": [ @@ -43,6 +32,7 @@ "yarn test --coverage", ], "test-android": [ + "devbox run setup-android", "yarn install", "yarn e2e install", "yarn build", @@ -50,6 +40,7 @@ "yarn e2e test:android", ], "test-ios": [ + "devbox run setup-ios", "yarn install", "yarn e2e install", "yarn e2e pods", @@ -58,10 +49,74 @@ "yarn e2e test:ios", ], "setup-android": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/setup-android.sh", + "bash $DEVBOX_PROJECT_ROOT/scripts/android-setup.sh", ], "setup-ios": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/setup-ios.sh", + "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", diff --git a/examples/E2E/.detoxrc.js b/examples/E2E/.detoxrc.js index 998a71b9d..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 17' + // 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': 'Medium_Phone_API_24' + // 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/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/nix/.cache/nix/fetcher-cache-v4.sqlite b/nix/.cache/nix/fetcher-cache-v4.sqlite index 1e4127039112df7d15a472eeda70c9ad45e39f60..e6742ac8c90411848ba4d4dd965608b70d399208 100644 GIT binary patch delta 336 zcmZojXh_(=BEZGWcZ-34Cch)!t<8c0XZV;>nJ3Sdm(j2^GfOp2HAqUdFf%Z=OioEM zHL*-IG)qo0H#RacG)^&0G)hV}wlJT3UtT;3C}Lz@o0-A%IE;x^xlorSJ+mYzwahuc zG_RyusVKEf$x4Y>1C?sQI>36BtSn3n!RAh$DWA>7%x}QJZ@|BczZ~dPC4N>n7VSdD p$qVGg>xnm<*@;EDkdY$yF}VWm/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/scripts/setup-android.sh b/scripts/setup-android.sh deleted file mode 100755 index 87341285f..000000000 --- a/scripts/setup-android.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Creates an AVD using the Android SDK provided by devbox/flake (system images, emulator, NDK already installed). -# Run inside a devbox shell so SDK tools are available. - -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}" -} - -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 api21_image="system-images;android-21;google_apis;x86_64" - - local image_path="${ANDROID_SDK_ROOT}/system-images/android-21/google_apis/x86_64" - if [[ ! -d "$image_path" ]]; then - echo "Expected system image not found at ${image_path}." >&2 - echo "Re-enter the devbox shell (flake should provide images) or rebuild Devbox to fetch them." >&2 - exit 1 - fi - - create_avd "Pixel_API21" "pixel" "$api21_image" - - echo "AVDs ready. Boot with: emulator -avd Pixel_API21 (or MediumPhone_Latest) --netdelay none --netspeed full" -} - -main "$@" diff --git a/scripts/setup-ios.sh b/scripts/setup-ios.sh deleted file mode 100755 index 7cb2221c5..000000000 --- a/scripts/setup-ios.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Creates local iOS simulators for common targets. Requires Xcode command-line tools and jq. - -require_tool() { - if ! command -v "$1" >/dev/null 2>&1; then - echo "Missing required tool: $1. Install Xcode CLI tools before running." >&2 - exit 1 - fi -} - -require_tool xcrun -require_tool jq - -runtime_info_for_version() { - local version="$1" - local json runtime_id runtime_name - json="$(xcrun simctl list runtimes -j)" - runtime_id="$(echo "$json" | jq -r --arg v "$version" '.runtimes[] | select(.isAvailable and (.name|startswith("iOS \($v)"))) | .identifier' | head -n1)" - runtime_name="$(echo "$json" | jq -r --arg v "$version" '.runtimes[] | select(.isAvailable and (.name|startswith("iOS \($v)"))) | .name' | head -n1)" - - if [[ -z "$runtime_id" || "$runtime_id" == "null" ]]; then - return 1 - fi - printf "match|%s|%s\n" "$runtime_id" "$runtime_name" -} - -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" os_version="$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 info status runtime_id runtime_name - if ! info=$(runtime_info_for_version "$os_version"); then - echo "Required runtime iOS ${os_version} not found. Install it in Xcode (Preferences > Platforms) and retry." >&2 - return 0 - fi - IFS="|" read -r status runtime_id runtime_name <<<"$info" - - 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 - - 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" -} - -main() { - ensure_device "iPhone 14" "16" - ensure_device "iPhone 17" "26.1.1" - 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. From 564f3ce796703739cf97b871222f04f2a91290d6 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 17:48:49 -0600 Subject: [PATCH 13/17] act and fixes --- .github/workflows/ci.yml | 16 +++++++------- devbox.json | 40 ++++++++++++++++++--------------- devbox.lock | 48 ++++++++++++++++++++++++++++++++++++++++ scripts/act-ci.sh | 28 +++++++++++++++++++++++ 4 files changed, 106 insertions(+), 26 deletions(-) create mode 100755 scripts/act-ci.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 886e34d26..da288afd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: workflow_dispatch: jobs: cancel_previous: + if: ${{ !env.ACT }} runs-on: ubuntu-latest steps: - uses: styfle/cancel-workflow-action@0.12.1 @@ -88,16 +89,15 @@ jobs: run-e2e-android: runs-on: 'ubuntu-22.04' - env: - # Default to both minsdk and latest; override ANDROID_MATRIX env to narrow/expand if needed. - ANDROID_MATRIX: > - [ - {"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"} - ] strategy: matrix: - include: ${{ fromJson(env.ANDROID_MATRIX) }} + 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 - name: Yarn cache diff --git a/devbox.json b/devbox.json index 90c23a403..205ab17ec 100644 --- a/devbox.json +++ b/devbox.json @@ -5,11 +5,12 @@ "version": "latest", "platforms": ["x86_64-darwin", "aarch64-darwin"], }, - "yarn-berry": "latest", - "jdk17": "latest", - "gradle": "latest", - "jq": "latest", + "yarn-berry": "latest", + "jdk17": "latest", + "gradle": "latest", + "jq": "latest", "path:./nix#android-sdk": "", + "act": "latest", }, "shell": { "init_hook": [ @@ -48,6 +49,9 @@ "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", ], @@ -55,36 +59,36 @@ "bash $DEVBOX_PROJECT_ROOT/scripts/ios-setup.sh", ], "start-emulator": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start" + "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start", ], "start-ios": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/ios-manager.sh start" + "bash $DEVBOX_PROJECT_ROOT/scripts/ios-manager.sh start", ], "start-android-minsdk": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start" + "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start", ], "start-android-latest": [ - "AVD_FLAVOR=latest bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start" + "AVD_FLAVOR=latest bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start", ], "start-android": [ - "bash $DEVBOX_PROJECT_ROOT/scripts/android-manager.sh start" + "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" + "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.\"" + "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.\"" + "echo \"Simulators reset. Recreate via devbox run start-ios.\"", ], "stop-android": [ "if command -v adb >/dev/null 2>&1; then", @@ -99,24 +103,24 @@ " 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).\"" + "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;", + " echo \"Shutting down booted iOS simulators...\";", + " xcrun simctl shutdown all >/dev/null 2>&1 || true;", " else", - " echo \"No booted iOS simulators detected.\";", + " echo \"No booted iOS simulators detected.\";", " fi;", "else", " echo \"simctl not available; skipping iOS shutdown.\";", "fi", - "echo \"iOS simulators shutdown (if any were running).\"" + "echo \"iOS simulators shutdown (if any were running).\"", ], "stop": [ "devbox run stop-android", - "devbox run stop-ios" + "devbox run stop-ios", ], "test": [ "devbox run test-android", diff --git a/devbox.lock b/devbox.lock index 61f6d9afb..049772504 100644 --- a/devbox.lock +++ b/devbox.lock @@ -1,6 +1,54 @@ { "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", diff --git a/scripts/act-ci.sh b/scripts/act-ci.sh new file mode 100755 index 000000000..977bd0eb8 --- /dev/null +++ b/scripts/act-ci.sh @@ -0,0 +1,28 @@ +#!/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}") +if [[ -n "$JOB" ]]; then + CMD+=(--job "$JOB") +fi + +printf 'Running: %s\n' "${CMD[*]}" +exec "${CMD[@]}" From ea3318d9c1d6994265869d5823071a2362fbd4ad Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 17:50:50 -0600 Subject: [PATCH 14/17] fix --- .github/workflows/ci.yml | 2 +- scripts/act-ci.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da288afd8..ba1d124ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: jobs: cancel_previous: - if: ${{ !env.ACT }} + if: ${{ github.event_name != 'push' }} runs-on: ubuntu-latest steps: - uses: styfle/cancel-workflow-action@0.12.1 diff --git a/scripts/act-ci.sh b/scripts/act-ci.sh index 977bd0eb8..71ae350f7 100755 --- a/scripts/act-ci.sh +++ b/scripts/act-ci.sh @@ -20,6 +20,7 @@ done CMD=(act) CMD+=(--pull=false) CMD+=(--platform "${PLATFORM}") +CMD+=(--input ACT=true) if [[ -n "$JOB" ]]; then CMD+=(--job "$JOB") fi From 322b395ca6888ad76c4ec65ee9b2bca668b1801b Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 19:07:39 -0600 Subject: [PATCH 15/17] disk space --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba1d124ef..1b12994e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,10 @@ jobs: runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v4 + - name: Disk space (start) + run: | + df -h + df -h /nix/store || true # Workaround for corepack enable in node # Source: (https://github.com/actions/setup-node/issues/899#issuecomment-1828798029) - name: devbox installer @@ -46,6 +50,10 @@ jobs: with: xcode-version: '26.1.1' - uses: actions/checkout@v4 + - name: Disk space (start) + run: | + df -h + df -h /nix/store || true - name: CocoaPods cache uses: actions/cache@v4 with: @@ -113,6 +121,10 @@ jobs: uses: jetify-com/devbox-install-action@v0.14.0 with: enable-cache: 'true' + - name: Disk space (start) + run: | + df -h + df -h /nix/store || true - name: Gradle cache uses: actions/cache@v4 with: From b79c9bf3062470b39accbf3d45475ad89df8ac87 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 19:22:14 -0600 Subject: [PATCH 16/17] disk space management --- .github/workflows/ci.yml | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b12994e3..d0db011c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,16 +18,16 @@ jobs: runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v4 - - name: Disk space (start) - run: | - df -h - df -h /nix/store || true - # Workaround for corepack enable in node - # Source: (https://github.com/actions/setup-node/issues/899#issuecomment-1828798029) + - 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: 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 @@ -46,14 +46,16 @@ jobs: 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: '26.1.1' - - uses: actions/checkout@v4 - - name: Disk space (start) - run: | - df -h - df -h /nix/store || true - name: CocoaPods cache uses: actions/cache@v4 with: @@ -121,6 +123,10 @@ jobs: 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 From fc0a481bf3f1d3b1d960c79fe5fff9339e1422ba Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 14 Jan 2026 19:31:06 -0600 Subject: [PATCH 17/17] remove jira workflow --- .github/workflows/create_jira.yml | 41 ------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 .github/workflows/create_jira.yml 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"