diff --git a/.github/workflows/build_harnesses.yml b/.github/workflows/build_harnesses.yml new file mode 100644 index 0000000000..3504404665 --- /dev/null +++ b/.github/workflows/build_harnesses.yml @@ -0,0 +1,217 @@ +name: 'Build Harnesses' + +on: + workflow_dispatch: + pull_request: + branches: + - '**' + paths-ignore: + - 'docs/**' + - 'website/**' + - '.spellcheck.dict.txt' + - '**/*.md' + push: + branches: + - main + paths-ignore: + - 'docs/**' + - 'website/**' + - '.spellcheck.dict.txt' + - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + ios: + name: iOS (${{ matrix.harness }}) + runs-on: macos-15 + timeout-minutes: 80 + strategy: + fail-fast: false + matrix: + harness: + - bare + - expo + env: + # Keep in sync with the existing iOS E2E workflow, which tracks latest-stable + # because Firebase iOS SDK updates can require newer Xcode versions. + XCODE_VERSION: latest-stable + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 50 + + # `runner` is not valid in job-level `env`; set paths in a step so + # `${{ runner.temp }}` is evaluated in a context that supports it. + - name: Set CI Firebase config paths + run: | + echo "IOS_GOOGLE_SERVICES_PATH=${{ runner.temp }}/GoogleService-Info.plist" >> "$GITHUB_ENV" + echo "ANDROID_GOOGLE_SERVICES_PATH=${{ runner.temp }}/google-services.json" >> "$GITHUB_ENV" + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: ${{ env.XCODE_VERSION }} + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + + - name: Create CI Firebase config files + run: bash ./scripts/write-ci-firebase-configs.sh "${IOS_GOOGLE_SERVICES_PATH}" "${ANDROID_GOOGLE_SERVICES_PATH}" + + - name: Cache CocoaPods and specs + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/CocoaPods + ~/.cocoapods/repos + apps/build-harness/ios/Pods + key: ${{ runner.os }}-harness-pods-v2-${{ hashFiles('yarn.lock', 'apps/build-harness/ios/Podfile.lock', 'apps/build-harness-expo/package.json', 'apps/build-harness-expo/app.config.js') }} + restore-keys: | + ${{ runner.os }}-harness-pods-v1- + + - name: Yarn install + run: yarn + + - name: Sync bare harness + if: matrix.harness == 'bare' + run: | + bash ./scripts/sync-build-harness.sh sync \ + --ios-google-services "${IOS_GOOGLE_SERVICES_PATH}" \ + --android-google-services "${ANDROID_GOOGLE_SERVICES_PATH}" \ + --no-yarn-install \ + --pod-install + + - name: Build bare iOS harness + if: matrix.harness == 'bare' + working-directory: apps/build-harness/ios + run: | + xcodebuild \ + -workspace BuildHarness.xcworkspace \ + -scheme BuildHarness \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO \ + build + + - name: Sync Expo harness + if: matrix.harness == 'expo' + run: | + bash ./scripts/sync-build-harness-expo.sh sync \ + --ios-google-services "${IOS_GOOGLE_SERVICES_PATH}" \ + --android-google-services "${ANDROID_GOOGLE_SERVICES_PATH}" \ + --no-yarn-install + + - name: Prebuild Expo iOS harness + if: matrix.harness == 'expo' + working-directory: apps/build-harness-expo + run: | + export NODE_PATH="$PWD/node_modules" + CI=1 yarn expo prebuild --clean --platform ios + + - name: Build Expo iOS harness + if: matrix.harness == 'expo' + working-directory: apps/build-harness-expo/ios + run: | + export NODE_PATH="$PWD/../node_modules" + WORKSPACE="RNFBExpoHarness.xcworkspace" + SCHEME="RNFBExpoHarness" + xcodebuild \ + -workspace "${WORKSPACE}" \ + -scheme "${SCHEME}" \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO \ + build + + android: + name: Android (${{ matrix.harness }}) + runs-on: ubuntu-latest + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + harness: + - bare + - expo + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 50 + + - name: Set CI Firebase config paths + run: | + echo "IOS_GOOGLE_SERVICES_PATH=${{ runner.temp }}/GoogleService-Info.plist" >> "$GITHUB_ENV" + echo "ANDROID_GOOGLE_SERVICES_PATH=${{ runner.temp }}/google-services.json" >> "$GITHUB_ENV" + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Configure JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Create CI Firebase config files + run: bash ./scripts/write-ci-firebase-configs.sh "${IOS_GOOGLE_SERVICES_PATH}" "${ANDROID_GOOGLE_SERVICES_PATH}" + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-harness-gradle-v1-${{ hashFiles('yarn.lock', 'apps/build-harness/android/**/*.gradle', 'apps/build-harness/android/gradle.properties', 'apps/build-harness-expo/package.json', 'apps/build-harness-expo/app.config.js') }} + restore-keys: | + ${{ runner.os }}-harness-gradle-v1- + + - name: Yarn install + run: yarn + + - name: Sync bare harness + if: matrix.harness == 'bare' + run: | + bash ./scripts/sync-build-harness.sh sync \ + --ios-google-services "${IOS_GOOGLE_SERVICES_PATH}" \ + --android-google-services "${ANDROID_GOOGLE_SERVICES_PATH}" \ + --no-yarn-install \ + --no-pod-install + + - name: Build bare Android harness + if: matrix.harness == 'bare' + working-directory: apps/build-harness/android + run: ./gradlew :app:assembleDebug + + - name: Sync Expo harness + if: matrix.harness == 'expo' + run: | + bash ./scripts/sync-build-harness-expo.sh sync \ + --ios-google-services "${IOS_GOOGLE_SERVICES_PATH}" \ + --android-google-services "${ANDROID_GOOGLE_SERVICES_PATH}" \ + --no-yarn-install + + - name: Prebuild Expo Android harness + if: matrix.harness == 'expo' + working-directory: apps/build-harness-expo + run: | + export NODE_PATH="$PWD/node_modules" + CI=1 yarn expo prebuild --clean --platform android + + - name: Build Expo Android harness + if: matrix.harness == 'expo' + working-directory: apps/build-harness-expo/android + run: | + export NODE_PATH="$PWD/../node_modules" + export NODE_ENV=production + ./gradlew :app:assembleDebug diff --git a/.gitignore b/.gitignore index 802ffc379e..602ac5a236 100644 --- a/.gitignore +++ b/.gitignore @@ -546,6 +546,10 @@ tests/ios/resetXcode.sh google-services.json GoogleService-Info.plist +apps/build-harness/android/app/google-services.json +apps/build-harness/ios/BuildHarness/GoogleService-Info.plist +apps/build-harness/.build-harness.local.json +apps/build-harness/.bundle/ # generated files RNFBVersion.m diff --git a/apps/README.md b/apps/README.md new file mode 100644 index 0000000000..bfe7d63fc6 --- /dev/null +++ b/apps/README.md @@ -0,0 +1,461 @@ +# RNFB Build Harnesses + +This directory is the main setup guide for the local RNFB harness apps: + +- `apps/build-harness/` for bare React Native CLI validation +- `apps/build-harness-expo/` for Expo development-build validation + +Use the bare harness when you need to validate checked-in native projects, Podfile changes, Gradle changes, or direct `react-native run-*` behavior. + +Use the Expo harness when you need to validate RNFB Expo config plugins, `expo prebuild`, and `expo run:*` behavior. + +## What these harnesses are for + +- validating local workspace `@react-native-firebase/*` packages without touching `tests/` +- comparing workspace packages with published RNFB packages +- swapping Firebase iOS and Android native SDK versions through supported override hooks +- reusing local `GoogleService-Info.plist` and `google-services.json` files without committing them +- smoke-testing that the JavaScript layer loads before doing deeper native validation + +## Environment setup + +Set up these local prerequisites before running either harness: + +- `node` and `yarn` +- `ruby` via a user-managed install such as `rbenv`, `asdf`, or Homebrew Ruby +- `pod` on macOS for iOS builds and Expo iOS prebuilds +- Xcode and iOS simulator support for iOS +- Android SDK, platform tools, emulator/device access, and a valid `ANDROID_HOME` or `ANDROID_SDK_ROOT` for Android + +Avoid using the macOS system Ruby for the harnesses. The bare harness uses Bundler for CocoaPods and Ruby helpers, and `apps/build-harness/Gemfile.lock` pins the Bundler version that must be available in your active Ruby environment. + +For example with `rbenv`: + +```bash +rbenv install 3.3.3 +rbenv global 3.3.3 +``` + +Then reload your shell and run `yarn app:doctor`. The doctor command reports the active Ruby path plus any Bundler mismatch against `apps/build-harness/Gemfile.lock`. + +Only install Bundler manually if `yarn app:doctor` reports that Bundler is missing or mismatched: + +```bash +gem install bundler -v "$(awk '/^BUNDLED WITH$/{getline; gsub(/^[[:space:]]+/, "", $0); print $0}' apps/build-harness/Gemfile.lock)" +``` + +Place your local Firebase config files at the default locations: + +- `~/Downloads/GoogleService-Info.plist` +- `~/Downloads/google-services.json` + +Or pass explicit paths with script flags. + +The sync scripts persist your last local choices in ignored files: + +- `apps/build-harness/.build-harness.local.json` +- `apps/build-harness-expo/.build-harness.local.json` + +Those files store local overrides such as: + +- Firebase config file source paths +- `workspace` vs published RNFB source selection +- published RNFB version override +- React / React Native overrides +- bundle identifier and Android application ID overrides +- Firebase native SDK and Gradle plugin version overrides + +### Script-managed environment variables + +If you use the root commands or sync scripts, you do not need to export these manually. The scripts set them for the native toolchain as needed: + +- `RNFB_FIREBASE_IOS_SDK` +- `FIREBASE_SDK_VERSION` +- `RNFB_FIREBASE_ANDROID_BOM` +- `RNFB_GOOGLE_SERVICES_GRADLE` +- `RNFB_CRASHLYTICS_GRADLE` +- `RNFB_PERF_GRADLE` +- `RNFB_APP_DISTRIBUTION_GRADLE` +- `RNFB_BUILD_HARNESS_ANDROID_APPLICATION_ID` +- `RNFB_BUILD_HARNESS_IOS_BUNDLE_ID` +- `NODE_PATH` for the Expo harness CLI so RNFB workspace config plugins resolve correctly + +## Repo root commands + +### Bare harness commands + +- `yarn app:doctor` + Prints the effective bare harness config, detected defaults, and prerequisite status. +- `yarn app:sync` + Rewrites the bare harness dependencies, copies Firebase config files, patches iOS metadata, and installs dependencies. +- `yarn app:clean` + Removes bare harness build products, Pods, and derived data. +- `yarn app:pod:install` + Re-runs the bare harness iOS pod install using the current effective config. +- `yarn app:start` + Starts Metro for the bare harness with `--reset-cache`. +- `yarn app:ios` + Syncs and runs the bare harness on iOS. +- `yarn app:android` + Syncs and runs the bare harness on Android. + +Examples: + +```bash +yarn app:doctor +yarn app:sync +yarn app:start +yarn app:ios +yarn app:android +yarn app:clean +yarn app:pod:install +``` + +### Expo harness commands + +- `yarn app:expo:doctor` + Prints the effective Expo harness config and prerequisite status. +- `yarn app:expo:sync` + Rewrites the Expo harness dependencies, copies Firebase config files, and validates Expo config. +- `yarn app:expo:clean` + Removes generated Expo native folders and local build artifacts. +- `yarn app:expo:start` + Starts the Expo bundler with the required workspace plugin resolution environment. +- `yarn app:ios:expo` + Syncs the Expo harness, runs `expo prebuild --clean --platform ios`, then runs `expo run:ios`. +- `yarn app:android:expo` + Syncs the Expo harness, runs `expo prebuild --clean --platform android`, then runs `expo run:android`. + +Examples: + +```bash +yarn app:expo:doctor +yarn app:expo:sync +yarn app:expo:start +yarn app:ios:expo +yarn app:android:expo +yarn app:expo:clean +``` + +## Direct script usage + +Use the scripts directly when you need overrides or extra platform-specific arguments. + +### Bare harness script + +```bash +bash ./scripts/sync-build-harness.sh [options] [-- extra react-native args] +``` + +Commands: + +- `doctor` +- `clean` +- `sync` +- `pod-install` +- `build-ios` +- `build-android` + +Examples: + +```bash +bash ./scripts/sync-build-harness.sh doctor +bash ./scripts/sync-build-harness.sh sync +bash ./scripts/sync-build-harness.sh pod-install +bash ./scripts/sync-build-harness.sh build-ios -- --simulator "iPhone 16" +bash ./scripts/sync-build-harness.sh build-android -- --deviceId emulator-5554 +``` + +### Expo harness script + +```bash +bash ./scripts/sync-build-harness-expo.sh [options] [-- extra expo args] +``` + +Commands: + +- `doctor` +- `clean` +- `sync` +- `start` +- `build-ios` +- `build-android` + +Examples: + +```bash +bash ./scripts/sync-build-harness-expo.sh doctor +bash ./scripts/sync-build-harness-expo.sh sync +bash ./scripts/sync-build-harness-expo.sh start -- --tunnel +bash ./scripts/sync-build-harness-expo.sh build-ios -- --device "iPhone 16" +bash ./scripts/sync-build-harness-expo.sh build-android -- --variant debug +``` + +## Flags and examples + +The two sync scripts share the same versioning and Firebase-config override flags except for bare-only `--pod-install` controls. + +### RNFB source selection + +- `--rnfb-source ` + Choose local workspace packages or published RNFB packages. + +```bash +bash ./scripts/sync-build-harness.sh sync --rnfb-source workspace +bash ./scripts/sync-build-harness-expo.sh sync --rnfb-source published --rnfb-version 24.0.0 +``` + +- `--rnfb-version ` + Required when `--rnfb-source published` is used. + +```bash +bash ./scripts/sync-build-harness.sh sync \ + --rnfb-source published \ + --rnfb-version 24.0.0 +``` + +### React and Firebase dependency overrides + +- `--react-native ` +- `--react ` +- `--firebase-js ` + +```bash +bash ./scripts/sync-build-harness.sh sync \ + --react-native 0.78.3 \ + --react 19.0.0 \ + --firebase-js 12.12.0 +``` + +### Native Firebase SDK overrides + +- `--firebase-ios ` +- `--firebase-android-bom ` +- `--google-services-gradle ` +- `--crashlytics-gradle ` +- `--perf-gradle ` +- `--app-distribution-gradle ` + +```bash +bash ./scripts/sync-build-harness.sh sync \ + --firebase-ios 12.13.0 \ + --firebase-android-bom 34.13.0 \ + --google-services-gradle 4.4.4 \ + --crashlytics-gradle 3.0.7 \ + --perf-gradle 2.0.2 \ + --app-distribution-gradle 5.2.1 +``` + +### Firebase config file path overrides + +- `--ios-google-services ` +- `--android-google-services ` + +```bash +bash ./scripts/sync-build-harness.sh sync \ + --ios-google-services "$HOME/Downloads/GoogleService-Info.plist" \ + --android-google-services "$HOME/Downloads/google-services.json" + +bash ./scripts/sync-build-harness-expo.sh sync \ + --ios-google-services "$HOME/Downloads/GoogleService-Info.plist" \ + --android-google-services "$HOME/Downloads/google-services.json" +``` + +### App identifier overrides + +Bare harness: + +- `--ios-bundle-id ` +- `--android-application-id ` + +```bash +bash ./scripts/sync-build-harness.sh sync \ + --ios-bundle-id io.invertase.react-native-demo \ + --android-application-id com.invertase.testing +``` + +Expo harness: + +- `--ios-bundle-id ` +- `--android-application-id ` + +```bash +bash ./scripts/sync-build-harness-expo.sh sync \ + --ios-bundle-id io.invertase.react-native-demo \ + --android-application-id com.invertase.testing +``` + +### Clean and install controls + +- `--clean` + Removes generated build outputs before syncing or building. + +```bash +bash ./scripts/sync-build-harness.sh sync --clean +bash ./scripts/sync-build-harness-expo.sh build-ios --clean +``` + +- `--yarn-install` +- `--no-yarn-install` + Force or skip the root `yarn` install step. + +```bash +bash ./scripts/sync-build-harness.sh sync --no-yarn-install +bash ./scripts/sync-build-harness-expo.sh sync --yarn-install +``` + +Bare harness only: + +- `--pod-install` +- `--no-pod-install` + Force or skip CocoaPods for bare sync and iOS build flows. + +```bash +bash ./scripts/sync-build-harness.sh sync --no-pod-install +bash ./scripts/sync-build-harness.sh build-ios --pod-install +``` + +### Extra CLI arguments after `--` + +Anything after `--` is passed through to the underlying CLI. + +Bare harness examples: + +```bash +bash ./scripts/sync-build-harness.sh build-ios -- --scheme BuildHarness +bash ./scripts/sync-build-harness.sh build-android -- --active-arch-only +``` + +Expo harness examples: + +```bash +bash ./scripts/sync-build-harness-expo.sh start -- --localhost +bash ./scripts/sync-build-harness-expo.sh build-ios -- --configuration Release +bash ./scripts/sync-build-harness-expo.sh build-android -- --variant debug +``` + +## Recommended workflows + +### Bare React Native CLI validation + +```bash +yarn app:doctor +yarn app:sync +yarn app:start +yarn app:ios +``` + +Or: + +```bash +yarn app:doctor +yarn app:sync +yarn app:start +yarn app:android +``` + +### Expo development-build validation + +```bash +yarn app:expo:doctor +yarn app:expo:sync +yarn app:expo:start +yarn app:ios:expo +``` + +Or: + +```bash +yarn app:expo:doctor +yarn app:expo:sync +yarn app:expo:start +yarn app:android:expo +``` + +### Compare workspace packages with a published RNFB release + +```bash +bash ./scripts/sync-build-harness.sh sync \ + --rnfb-source published \ + --rnfb-version 24.0.0 + +bash ./scripts/sync-build-harness-expo.sh sync \ + --rnfb-source published \ + --rnfb-version 24.0.0 +``` + +### Focused manual validation in either harness + +Each harness app now supports a small screen switch in `screenConfig.ts`: + +- set `ACTIVE_SCREEN` to `'reproduction'` to load `Reproduction.tsx` +- set `ACTIVE_SCREEN` to `'home'` to restore the default smoke-test screen + +Use `Reproduction.tsx` as a small manual API sandbox when you want to validate +one package in the running app: + +- add one or more buttons that call the package API you want to inspect +- render loading, success, and error state directly in the screen +- keep any focused listeners, returned payloads, or serialized output visible in the UI +- use the same pattern in both the bare and Expo harnesses when comparing behavior + +For example, after switching to the reproduction screen in the bare harness: + +```bash +bash ./scripts/sync-build-harness.sh sync \ + --rnfb-source published \ + --rnfb-version 24.0.0 + +cd apps/build-harness +yarn start +``` + +Then in another terminal: + +```bash +yarn app:android +``` + +Or in the Expo harness: + +```bash +bash ./scripts/sync-build-harness-expo.sh sync \ + --rnfb-source published \ + --rnfb-version 24.0.0 + +cd apps/build-harness-expo +yarn start +``` + +Then in another terminal: + +```bash +yarn app:android:expo +``` + +If the issue is specifically about TypeScript declarations rather than runtime +behavior, keep the compile-time probe in `Reproduction.tsx` and run `typecheck` +instead. For example, to validate a published RNFB Firestore typing issue in the +bare harness: + +```bash +cd apps/build-harness +yarn typecheck +``` + +Or in the Expo harness: + +```bash +cd apps/build-harness-expo +yarn typecheck +``` + +## Notes + +- Defaults come from `packages/app/package.json` for Firebase JS, Firebase iOS SDK, Android BOM, and Gradle plugin versions. +- The bare harness patches the iOS Xcode project and keeps the Android namespace fixed at `com.invertase.testing`, while only overriding `applicationId`. +- The Expo harness targets development builds, not Expo Go. +- The Expo harness generates native folders via `expo prebuild --clean`; those generated folders stay ignored. +- The module list in each app confirms the JavaScript layer loads, but real validation still requires native builds and runtime checks. +- Type-only regressions should be validated with `yarn typecheck`; Metro and runtime rendering will not catch them. +- These harnesses are intentionally separate from `tests/`, which remains the CI and Detox harness. diff --git a/apps/build-harness-expo/.gitignore b/apps/build-harness-expo/.gitignore new file mode 100644 index 0000000000..467047b72c --- /dev/null +++ b/apps/build-harness-expo/.gitignore @@ -0,0 +1,44 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +# generated native folders +/ios +/android +GoogleService-Info.plist +google-services.json +.build-harness.local.json diff --git a/apps/build-harness-expo/App.tsx b/apps/build-harness-expo/App.tsx new file mode 100644 index 0000000000..9362233cfa --- /dev/null +++ b/apps/build-harness-expo/App.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import HarnessHomeScreen from './HarnessHomeScreen'; +import Reproduction from './Reproduction'; +import {ACTIVE_SCREEN} from './screenConfig'; + +function App(): React.JSX.Element { + if (ACTIVE_SCREEN === 'reproduction') { + return ; + } + + return ; +} + +export default App; diff --git a/apps/build-harness-expo/HarnessHomeScreen.tsx b/apps/build-harness-expo/HarnessHomeScreen.tsx new file mode 100644 index 0000000000..c19a64efb5 --- /dev/null +++ b/apps/build-harness-expo/HarnessHomeScreen.tsx @@ -0,0 +1,217 @@ +import React from 'react'; +import { + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + useColorScheme, + View, +} from 'react-native'; +import ai from '@react-native-firebase/ai'; +import analytics from '@react-native-firebase/analytics'; +import firebaseAppRoot, {getApp} from '@react-native-firebase/app'; +import appCheck from '@react-native-firebase/app-check'; +import appDistribution from '@react-native-firebase/app-distribution'; +import auth from '@react-native-firebase/auth'; +import crashlytics from '@react-native-firebase/crashlytics'; +import database from '@react-native-firebase/database'; +import firestore from '@react-native-firebase/firestore'; +import functions from '@react-native-firebase/functions'; +import inAppMessaging from '@react-native-firebase/in-app-messaging'; +import installations from '@react-native-firebase/installations'; +import messaging from '@react-native-firebase/messaging'; +import ml from '@react-native-firebase/ml'; +import perf from '@react-native-firebase/perf'; +import remoteConfig from '@react-native-firebase/remote-config'; +import storage from '@react-native-firebase/storage'; + +type ModuleCheck = { + name: string; + loaded: boolean; +}; + +const moduleChecks: ModuleCheck[] = [ + {name: 'app', loaded: typeof firebaseAppRoot.app === 'function'}, + {name: 'ai', loaded: typeof ai === 'function'}, + {name: 'analytics', loaded: typeof analytics === 'function'}, + {name: 'app-check', loaded: typeof appCheck === 'function'}, + {name: 'app-distribution', loaded: typeof appDistribution === 'function'}, + {name: 'auth', loaded: typeof auth === 'function'}, + {name: 'crashlytics', loaded: typeof crashlytics === 'function'}, + {name: 'database', loaded: typeof database === 'function'}, + {name: 'firestore', loaded: typeof firestore === 'function'}, + {name: 'functions', loaded: typeof functions === 'function'}, + {name: 'in-app-messaging', loaded: typeof inAppMessaging === 'function'}, + {name: 'installations', loaded: typeof installations === 'function'}, + {name: 'messaging', loaded: typeof messaging === 'function'}, + {name: 'ml', loaded: typeof ml === 'function'}, + {name: 'perf', loaded: typeof perf === 'function'}, + {name: 'remote-config', loaded: typeof remoteConfig === 'function'}, + {name: 'storage', loaded: typeof storage === 'function'}, +]; + +function HarnessHomeScreen(): React.JSX.Element { + const isDarkMode = useColorScheme() === 'dark'; + const backgroundColor = isDarkMode ? '#0f172a' : '#f8fafc'; + const cardColor = isDarkMode ? '#111827' : '#ffffff'; + const primaryText = isDarkMode ? '#f8fafc' : '#0f172a'; + const secondaryText = isDarkMode ? '#cbd5e1' : '#475569'; + let appName = 'Unavailable'; + let projectId = 'Missing'; + let appId = 'Missing'; + let firebaseAppError: string | undefined; + + try { + const firebaseApp = getApp(); + appName = firebaseApp.name; + projectId = firebaseApp.options.projectId ?? 'Missing'; + appId = firebaseApp.options.appId ?? 'Missing'; + } catch (error) { + firebaseAppError = error instanceof Error ? error.message : String(error); + } + + return ( + + + + + + RNFB EXPO HARNESS + + + Expo development-build smoke app + + + Run `yarn app:expo:sync` before `yarn app:ios:expo` or + `yarn app:android:expo` after changing Firebase files or dependency + overrides. + + + + + + Firebase app state + + + App name + + {appName} + + Project ID + + + {projectId} + + App ID + {appId} + {firebaseAppError ? ( + <> + + Initialization error + + + {firebaseAppError} + + + ) : null} + + + + + Module imports + + + These checks confirm the Expo JavaScript layer resolves RNFB + packages. Use the generated native projects to verify config plugin + output and runtime behavior. + + {moduleChecks.map(moduleCheck => ( + + + {moduleCheck.name} + + + {moduleCheck.loaded ? 'ready' : 'missing'} + + + ))} + + + + ); +} + +const styles = StyleSheet.create({ + safeArea: { + flex: 1, + }, + content: { + gap: 16, + padding: 16, + }, + card: { + borderRadius: 16, + elevation: 2, + padding: 16, + shadowColor: '#000000', + shadowOffset: {width: 0, height: 6}, + shadowOpacity: 0.08, + shadowRadius: 12, + }, + eyebrow: { + fontSize: 12, + fontWeight: '700', + letterSpacing: 1, + marginBottom: 8, + }, + title: { + fontSize: 28, + fontWeight: '700', + marginBottom: 12, + }, + body: { + fontSize: 16, + lineHeight: 22, + }, + sectionTitle: { + fontSize: 20, + fontWeight: '700', + marginBottom: 12, + }, + rowLabel: { + fontSize: 12, + fontWeight: '700', + letterSpacing: 0.5, + marginTop: 8, + }, + rowValue: { + fontSize: 16, + marginTop: 2, + }, + errorText: { + fontSize: 14, + lineHeight: 20, + marginTop: 6, + }, + moduleRow: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: 6, + }, + moduleName: { + fontSize: 16, + }, + moduleStatus: { + fontSize: 14, + fontWeight: '700', + textTransform: 'uppercase', + }, +}); + +export default HarnessHomeScreen; diff --git a/apps/build-harness-expo/README.md b/apps/build-harness-expo/README.md new file mode 100644 index 0000000000..3af3d78516 --- /dev/null +++ b/apps/build-harness-expo/README.md @@ -0,0 +1,35 @@ +# RNFB Expo Harness + +This app is the Expo development-build harness under `apps/build-harness-expo/`. + +For the full shared guide covering both the bare and Expo harnesses, see [`../README.md`](../README.md). + +## Use this harness for + +- Expo config plugin validation +- `expo prebuild --clean` validation +- `expo run:ios` and `expo run:android` flows + +## Common commands + +From the repo root: + +```bash +yarn app:expo:doctor +yarn app:expo:sync +yarn app:expo:start +yarn app:ios:expo +# or +yarn app:android:expo +``` + +For direct script usage and all supported flags, see: + +- [`../README.md`](../README.md) +- `scripts/sync-build-harness-expo.sh --help` + +## Notes + +- This harness targets Expo development builds, not Expo Go. +- Native iOS and Android folders are generated by `expo prebuild --clean` and remain ignored. +- The Expo config uses RNFB config plugins plus `expo-build-properties` with `useFrameworks: "static"` on iOS. diff --git a/apps/build-harness-expo/Reproduction.tsx b/apps/build-harness-expo/Reproduction.tsx new file mode 100644 index 0000000000..86381cf01e --- /dev/null +++ b/apps/build-harness-expo/Reproduction.tsx @@ -0,0 +1,271 @@ +import React, {useState} from 'react'; +import { + Button, + Platform, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + useColorScheme, + View, +} from 'react-native'; +import firestore from '@react-native-firebase/firestore'; +import type { + DocumentData, + DocumentReference, + DocumentSnapshot, + Transaction, +} from '@react-native-firebase/firestore'; + +type ReproCase = { + name: string; + status: 'compile-time' | 'runtime'; + detail: string; +}; + +const reproCases: ReproCase[] = [ + { + name: 'DocumentSnapshot generic', + status: 'compile-time', + detail: + 'Validate whether DocumentSnapshot is accepted in type positions.', + }, + { + name: 'DocumentReference generic', + status: 'compile-time', + detail: + 'Validate whether Transaction.update accepts DocumentReference.', + }, +]; + +export function checkDocumentSnapshot( + _snapshot: DocumentSnapshot, +): void {} + +export const checkTransactionReferenceArg: Parameters[0] = + null as unknown as DocumentReference; + +function Reproduction(): React.JSX.Element { + const [runtimeStatus, setRuntimeStatus] = useState< + 'idle' | 'running' | 'success' | 'error' + >('idle'); + const [runtimeOutput, setRuntimeOutput] = useState( + 'Tap "Run Firestore probe" to exercise a runtime API call in the harness app.', + ); + const isDarkMode = useColorScheme() === 'dark'; + const backgroundColor = isDarkMode ? '#0f172a' : '#f8fafc'; + const cardColor = isDarkMode ? '#111827' : '#ffffff'; + const primaryText = isDarkMode ? '#f8fafc' : '#0f172a'; + const secondaryText = isDarkMode ? '#cbd5e1' : '#475569'; + + const runFirestoreProbe = async (): Promise => { + setRuntimeStatus('running'); + + try { + const instance = firestore(); + const reference = instance + .collection('buildHarnessManualProbe') + .doc('runtime'); + + const output = { + appName: instance.app.name, + documentId: reference.id, + documentPath: reference.path, + parentPath: reference.parent.path, + }; + + setRuntimeOutput(JSON.stringify(output, null, 2)); + setRuntimeStatus('success'); + } catch (error) { + setRuntimeOutput(error instanceof Error ? error.message : String(error)); + setRuntimeStatus('error'); + } + }; + + const clearRuntimeOutput = (): void => { + setRuntimeStatus('idle'); + setRuntimeOutput( + 'Tap "Run Firestore probe" to exercise a runtime API call in the harness app.', + ); + }; + + return ( + + + + + + MANUAL API SANDBOX + + + Firestore validation screen + + + Use this screen for focused package validation in the harness app. + Add buttons, listeners, and output panes here when you want to + exercise a runtime API manually in bare or Expo. + + + + + + Runtime probe + + + This example runs a small Firestore API probe without requiring a + write. Replace it with package-specific actions for the issue you + want to investigate. + + +