feat: add Expo 56 support#360
Conversation
There was a problem hiding this comment.
Pull request overview
Adds first-class Expo SDK 56 support across the repo (examples, Expo config plugin, CLI packaging utilities, and CI lanes) while tightening Android/iOS packaging behavior for both bare RN and Expo hosts.
Changes:
- Introduces
apps/ExpoApp56and wires it into AndroidApp/AppleApp + CI matrices as the newexpolane. - Updates the Expo config plugin to better inherit host app native settings (iOS deployment target, Android compileSdk) and improves idempotent Podfile/Xcode project mutations.
- Refactors iOS packaging in the CLI to build/merge XCFramework slices and copy required RN/Hermes prebuilts, plus adds Android worklets CMake patching.
Reviewed changes
Copilot reviewed 116 out of 140 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react-native-brownfield/src/expo-config-plugin/withBrownfield.ts | Exposes resolveConfig; adjusts defaulting for iOS/Android native floors. |
| packages/react-native-brownfield/src/expo-config-plugin/types/ios/BrownfieldPluginIosConfig.ts | Updates iOS deployment target defaulting/type resolution. |
| packages/react-native-brownfield/src/expo-config-plugin/types/android/BrownfieldPluginAndroidConfig.ts | Updates Android compile/target SDK docs and resolved type. |
| packages/react-native-brownfield/src/expo-config-plugin/template/android/build.gradle.kts | Improves publishing variant, JNI packaging, and root-property resolution helper. |
| packages/react-native-brownfield/src/expo-config-plugin/ios/xcodeHelpers.ts | Adds framework-target dedupe + deployment target inheritance helpers. |
| packages/react-native-brownfield/src/expo-config-plugin/ios/withFmtFix.ts | Makes fmt Podfile patch idempotent + migrates legacy unmarked blocks. |
| packages/react-native-brownfield/src/expo-config-plugin/ios/withBrownfieldIos.ts | Inherits deployment target from host target when available. |
| packages/react-native-brownfield/src/expo-config-plugin/ios/podfileHelpers.ts | Adds/updates marked post_install blocks (defines, Swift overrides, prebuilts interop). |
| packages/react-native-brownfield/src/expo-config-plugin/ios/tests/xcodeHelpers.test.ts | Tests new deployment target lookup + target reuse logic. |
| packages/react-native-brownfield/src/expo-config-plugin/ios/tests/withFmtFix.test.ts | Expands fmt-fix tests for idempotence and legacy replacement. |
| packages/react-native-brownfield/src/expo-config-plugin/ios/tests/podfileHelpers.test.ts | Adds tests for post_install insertion + marked-block replacement. |
| packages/react-native-brownfield/src/expo-config-plugin/android/withAndroidModuleFiles.ts | Makes compileSdk inherit from root project when not overridden. |
| packages/react-native-brownfield/src/expo-config-plugin/android/utils/gradleHelpers.ts | Normalizes/updates settings.gradle pluginManagement includeBuild strategy. |
| packages/react-native-brownfield/src/expo-config-plugin/android/tests/withAndroidModuleFiles.test.ts | Tests compileSdk inheritance + JNI packaging exclusions. |
| packages/react-native-brownfield/src/expo-config-plugin/android/tests/gradleHelpers.test.ts | Adds tests for settings.gradle rewriting/idempotence. |
| packages/react-native-brownfield/src/expo-config-plugin/tests/withBrownfield.test.ts | Adds tests for config resolution behavior. |
| packages/react-native-brownfield/package.json | Widens Expo config-plugin peer range; bumps dependency; ships app.plugin.js. |
| packages/react-native-brownfield/app.plugin.js | Adds root shim for Expo plugin resolution. |
| packages/cli/src/brownfield/utils/stripFrameworkBinary.ts | Refactors to use execFileSync + supports framework/static-library slices + modulemap sanitization. |
| packages/cli/src/brownfield/utils/sanitizeSwiftInterfaces.ts | Adds sanitizer to remove self-imports from emitted Swift interfaces. |
| packages/cli/src/brownfield/utils/sanitizeFrameworkModuleMap.ts | Normalizes module.modulemap for packaged frameworks. |
| packages/cli/src/brownfield/utils/patchAndroidWorkletsImportPaths.ts | Patches legacy worklets CMake IMPORTED_LOCATION paths for new AGP outputs. |
| packages/cli/src/brownfield/utils/normalizeLibraryXcframework.ts | Converts static-library XCFramework slices into framework slices with plists/modulemaps. |
| packages/cli/src/brownfield/utils/mergeStaticLibraryXcframework.ts | Creates XCFramework from device/simulator static libs + stages headers/swiftmodules. |
| packages/cli/src/brownfield/utils/mergeFrameworkSlicesManually.ts | Manually merges framework slices and writes XCFramework Info.plist. |
| packages/cli/src/brownfield/utils/copyReactXcframeworks.ts | Copies RN prebuilts and materializes virtual header aliases. |
| packages/cli/src/brownfield/utils/copyHermesXcframework.ts | Copies Hermes XCFramework into package output. |
| packages/cli/src/brownfield/utils/tests/sanitize-swift-interfaces.test.ts | Tests Swift interface sanitization. |
| packages/cli/src/brownfield/utils/tests/patch-android-worklets-import-paths.test.ts | Tests worklets path patching logic. |
| packages/cli/src/brownfield/commands/packageIos.ts | Replaces plugin action with explicit build/merge/copy/sanitize packaging pipeline. |
| packages/cli/src/brownfield/commands/packageAndroid.ts | Adds Android worklets import-path patching pre-packaging. |
| packages/cli/src/brownfield/commands/tests/packageIos.action.test.ts | Updates mocks/expectations for new iOS packaging flow. |
| packages/brownfield-navigation/src/NativeBrownfieldNavigation.ts | Updates TurboModule spec for new navigation APIs + user type definitions. |
| packages/brownfield-navigation/src/index.ts | Exposes new navigation methods from JS wrapper. |
| packages/brownfield-navigation/ios/NativeBrownfieldNavigation.mm | Implements new methods bridging to Swift delegate. |
| packages/brownfield-navigation/ios/BrownfieldNavigationModels.swift | Adds generated Swift models + NSDictionary decoding helpers. |
| packages/brownfield-navigation/ios/BrownfieldNavigationDelegate.swift | Defines new delegate API for settings/referrals navigation. |
| packages/brownfield-navigation/android/src/main/java/com/callstack/nativebrownfieldnavigation/NativeBrownfieldNavigationModule.kt | Implements new methods on Android using ReadableMap parsing. |
| packages/brownfield-navigation/android/src/main/java/com/callstack/nativebrownfieldnavigation/BrownfieldNavigationModels.kt | Adds Kotlin models + ReadableMap converters. |
| packages/brownfield-navigation/android/src/main/java/com/callstack/nativebrownfieldnavigation/BrownfieldNavigationDelegate.kt | Expands delegate interface with new methods. |
| package.json | Bumps repo-level Expo config tooling deps. |
| gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantTaskProvider.kt | Makes Expo Updates task dependency conditional on task existence. |
| gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/AssetTaskProcessor.kt | Aligns asset/update task wiring for Expo vs bare RN builds. |
| gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNSourceSets.kt | Avoids hard dependency on Expo Updates tasks; uses builtBy when present. |
| gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/ProjectConfigurations.kt | Tightens attribute copying generics/null handling. |
| gradle-plugins/react/brownfield/gradle/libs.versions.toml | Updates Kotlin JVM version. |
| docs/superpowers/plans/2026-06-01-expo-56-support.md | Adds implementation plan doc for Expo 56 lane work. |
| docs/docs/docs/guides/expo-updates/how-to.mdx | Links to ExpoApp56 in Expo Updates guide. |
| docs/docs/docs/getting-started/examples.mdx | Documents ExpoApp56 and switches expo alias to Expo 56. |
| apps/RNApp/ios/RNApp/AppDelegate.swift | Seeds Brownie store both in app and framework registry. |
| apps/RNApp/ios/RNApp.xcodeproj/project.pbxproj | Updates iOS build settings/framework refs for current pod layout. |
| apps/RNApp/ios/Podfile.lock | Updates pod versions/checksums for RNApp iOS integration. |
| apps/RNApp/ios/Podfile | Sets prebuilt env defaults; disables Swift interface generation in Debug for select pods. |
| apps/RNApp/ios/BrownfieldLib/BrownfieldLib.swift | Adds framework-side Brownie store registration helper. |
| apps/RNApp/android/settings.gradle | Adds pluginManagement repositories + dynamic includeBuild for brownfield plugin sources. |
| apps/RNApp/android/build.gradle | Removes legacy brownfield-gradle-plugin classpath entry. |
| apps/RNApp/android/BrownfieldLib/build.gradle.kts | Aligns publishing variant and buildFeatures; singleVariant publishing. |
| apps/RNApp/android/app/src/main/java/com/rnapp/MainApplication.kt | Registers initial Brownie store at app startup. |
| apps/README.md | Documents ExpoApp56 and updates example build instructions. |
| apps/ExpoApp56/tsconfig.json | Adds Expo 56 app TS config. |
| apps/ExpoApp56/src/utils/expo-rn-updates.ts | Adds Updates check/fetch/reload helper. |
| apps/ExpoApp56/src/hooks/use-theme.ts | Adds theme hook based on scheme. |
| apps/ExpoApp56/src/hooks/use-color-scheme.web.ts | Adds hydration-safe web color-scheme hook. |
| apps/ExpoApp56/src/hooks/use-color-scheme.ts | Re-exports native useColorScheme. |
| apps/ExpoApp56/src/global.css | Adds web global CSS variables. |
| apps/ExpoApp56/src/constants/theme.ts | Defines colors/fonts/spacing and pulls in global CSS. |
| apps/ExpoApp56/src/components/web-badge.tsx | Shows Expo version badge on web. |
| apps/ExpoApp56/src/components/ui/collapsible.tsx | Adds animated collapsible component. |
| apps/ExpoApp56/src/components/themed-view.tsx | Adds themed view helper component. |
| apps/ExpoApp56/src/components/themed-text.tsx | Adds themed text variants. |
| apps/ExpoApp56/src/components/postMessage/MessageBubble.tsx | Adds animated message bubble UI. |
| apps/ExpoApp56/src/components/postMessage/Message.ts | Defines message type for postMessage demo. |
| apps/ExpoApp56/src/components/hint-row.tsx | Adds hint-row component. |
| apps/ExpoApp56/src/components/external-link.tsx | Adds in-app browser link helper. |
| apps/ExpoApp56/src/components/counter/index.tsx | Adds Brownie counter demo. |
| apps/ExpoApp56/src/components/app-tabs.web.tsx | Web tabs using expo-router/ui. |
| apps/ExpoApp56/src/components/app-tabs.tsx | Native tabs using unstable native tabs. |
| apps/ExpoApp56/src/components/animated-icon.web.tsx | Web splash/icon animation component. |
| apps/ExpoApp56/src/components/animated-icon.tsx | Native splash/icon animations using worklets/reanimated. |
| apps/ExpoApp56/src/components/animated-icon.module.css | CSS for web splash background. |
| apps/ExpoApp56/src/app/postMessage.tsx | Adds postMessage demo tab. |
| apps/ExpoApp56/src/app/index.tsx | Home screen with Expo 56 starter content + Updates button. |
| apps/ExpoApp56/src/app/explore.tsx | Explore screen starter content. |
| apps/ExpoApp56/src/app/_layout.tsx | Sets up router ThemeProvider + tabs + splash overlay. |
| apps/ExpoApp56/RNApp.tsx | Adds Brownfield demo RNApp wrapper for consumer apps. |
| apps/ExpoApp56/README.md | Adds Expo scaffold README. |
| apps/ExpoApp56/package.json | Adds Expo 56 app deps/scripts and brownfield packaging commands. |
| apps/ExpoApp56/jest/cssMock.js | Stubs CSS imports for Jest. |
| apps/ExpoApp56/jest.setup.js | Hooks shared test setup. |
| apps/ExpoApp56/jest.config.js | Adds Expo/Jest config with moduleNameMapper. |
| apps/ExpoApp56/eslint.config.js | Adds ESLint flat config tuned for workspace imports + Jest config. |
| apps/ExpoApp56/entry.tsx | Registers router app + RNApp entrypoint. |
| apps/ExpoApp56/BrownfieldStore.brownie.ts | Declares Brownie stores module augmentation. |
| apps/ExpoApp56/brownfield.navigation.ts | Declares navigation schema/types for the example. |
| apps/ExpoApp56/assets/expo.icon/icon.json | Adds iOS icon asset definition. |
| apps/ExpoApp56/assets/expo.icon/Assets/expo-symbol 2.svg | Adds Expo icon SVG asset. |
| apps/ExpoApp56/app.json | Adds Expo app config with brownfield plugin enabled. |
| apps/ExpoApp56/.gitignore | Adds app-local ignore rules (native folders, expo, etc). |
| apps/ExpoApp56/tests/brownfield.example.test.tsx | Runs shared example test suites against ExpoApp56. |
| apps/ExpoApp55/package.json | Adds patches for expo-modules-core and reanimated dependencies. |
| apps/ExpoApp55/eslint.config.js | Aligns lint rule ignore for workspace imports. |
| apps/AppleApp/prepareXCFrameworks.js | Allows new packaged XCFramework name ReactAppDependencyProvider.xcframework. |
| apps/AppleApp/package.json | Adds Expo 56 consumer build script and sets expo alias to Expo 56. |
| apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist | Registers Expo 56 scheme ordering/visibility. |
| apps/AppleApp/Brownfield Apple App.xcodeproj/xcshareddata/xcschemes/Brownfield Apple App Expo 56.xcscheme | Adds new shared Xcode scheme for ExpoApp56 consumer target. |
| apps/AndroidApp/package.json | Adds Expo 56 build script and sets expo alias to Expo 56. |
| apps/AndroidApp/gradle/libs.versions.toml | Adds Expo 56 brownfieldlib dependency coordinate. |
| apps/AndroidApp/app/src/expo56/java/com/callstack/brownfield/android/expo/ReactNativeHostManager.kt | Adds Expo 56 typealias to packaged host manager. |
| apps/AndroidApp/app/src/expo56/java/com/callstack/brownfield/android/expo/ReactNativeConstants.kt | Adds Expo 56 app constants. |
| apps/AndroidApp/app/src/expo56/java/com/callstack/brownfield/android/expo/BrownfieldStore.kt | Adds Expo 56 typealiases to packaged Brownie store models. |
| apps/AndroidApp/app/build.gradle.kts | Adds Expo 56 flavor + dependency wiring. |
| .yarn/patches/react-native-reanimated-npm-4.2.1-brownfield.patch | Patches reanimated to find worklets via CMake package + link target. |
| .yarn/patches/expo-updates-npm-56.0.17-0d9c6d9af6.patch | Makes expo-updates podspec dev-client resolve resilient. |
| .yarn/patches/expo-modules-core-npm-55.0.25-brownfield.patch | Adds optional find_package(worklets) fallback logic for Expo 55. |
| .github/workflows/ci.yml | Adds Expo 56 path filter and matrix lanes. |
| .github/actions/androidapp-road-test/action.yml | Updates AAR verification to accept release/default artifact names. |
| .changeset/calm-poets-film.md | Adds changeset announcing Expo SDK 56 support. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <Button | ||
| title="Navigate to Settings" | ||
| onPress={() => BrownfieldNavigation.navigateToSettings()} | ||
| /> |
| - (void)navigateToSettings:(NSDictionary *)user { | ||
| UserType *userModel = user == nil ? nil : [UserType fromDictionary:user]; | ||
| [[[BrownfieldNavigationManager shared] getDelegate] navigateToSettings:userModel]; | ||
| } |
| android: androidPackage | ||
| ? { | ||
| moduleName: androidModuleName, | ||
| packageName: config.android?.packageName ?? androidPackage, | ||
| minSdkVersion: config.android?.minSdkVersion ?? 24, | ||
| targetSdkVersion: config.android?.targetSdkVersion ?? 35, | ||
| compileSdkVersion: config.android?.compileSdkVersion ?? 35, | ||
| compileSdkVersion: config.android?.compileSdkVersion, | ||
| groupId: config.android?.groupId ?? androidPackage, |
| This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds. | ||
|
|
||
| This repository uses `/Users/adam.trzcinski/.codex/agent-rules/PLANS.md` as the canonical ExecPlan format. This document must be maintained in accordance with it. | ||
|
|
| const sourcePath = path.join( | ||
| sourceDir, | ||
| `Pods/hermes-engine/destroot/Library/Frameworks/universal/${hermesFrameworkName}` | ||
| ); | ||
| const destinationPath = path.join(destinationDir, hermesFrameworkName); | ||
|
|
||
| if (fs.existsSync(destinationPath)) { | ||
| fs.rmSync(destinationPath, { recursive: true, force: true }); | ||
| } | ||
|
|
||
| fs.cpSync(sourcePath, destinationPath, { recursive: true, force: true }); | ||
| logger.debug(`Copied ${colorLink(destinationPath)}`); |
| sourceSet.assets.srcDirs(bundlePathSegments.map { "$appBuildDir/generated/assets/$it" }) | ||
| sourceSet.res.srcDirs(bundlePathSegments.map { "$appBuildDir/generated/res/$it" }) | ||
| sourceSet.assets.srcDirs(assetPathSegments.map { "$appBuildDir/generated/assets/$it" }) | ||
| sourceSet.res.srcDirs(assetPathSegments.map { "$appBuildDir/generated/res/$it" }) | ||
| if (appProject.tasks.names.contains(updateResourcesTaskName)) { | ||
| val updateResourcesTask = appProject.tasks.named(updateResourcesTaskName) | ||
| sourceSet.assets.srcDir( | ||
| project.files("$appBuildDir/generated/assets/$updateResourcesPathSegment").builtBy(updateResourcesTask), | ||
| ) | ||
| sourceSet.res.srcDir( | ||
| project.files("$appBuildDir/generated/res/$updateResourcesPathSegment").builtBy(updateResourcesTask), | ||
| ) | ||
| } |
There was a problem hiding this comment.
Q: can you add some context on why this change is required?
| ) | ||
| val capitalizedBundledAssetsVariantName = bundledAssetsVariantName.capitalized() | ||
| val appProject = project.rootProject.project(projectExtension.appProjectName) | ||
| val updatesResourcesTaskName = "create${variant.name.capitalized()}UpdatesResources" |
There was a problem hiding this comment.
Can we not use capitalizedBundledAssetsVariantName instea?
| assetsTask.dependsOn("${appProject.path}:createBundle${capitalizedBundledAssetsVariantName}JsAndAssets") | ||
|
|
||
| if (Utils.isExpoProject(project) && appProject.tasks.names.contains(updatesResourcesTaskName)) { | ||
| assetsTask.dependsOn( | ||
| "${appProject.path}:$updatesResourcesTaskName", | ||
| ) | ||
| } |
There was a problem hiding this comment.
In VariantTaskProvider we are already having a dependency for preBuildTask - I don't think we should add it in two places.
There was a problem hiding this comment.
This and the following navigation files should not be committed. They are auto generated.
| projectRoot: '/repo', | ||
| projectRoot: '/private/tmp/repo', |
There was a problem hiding this comment.
Q: Is this change and the following ones for path, strictly required?
| }, | ||
| platformConfig | ||
| ); | ||
| const iosConfig = getValidProjectConfig('ios', projectRoot, platformConfig); |
There was a problem hiding this comment.
This change targets the core behavior. Can you explain why we need to ditch rock's packageIosAction?
| @@ -0,0 +1,74 @@ | |||
| import fs from 'node:fs'; | |||
| import path from 'node:path'; | |||
|
|
|||
There was a problem hiding this comment.
Q: Why do we need this file? Is it only Expo56 related?
| import { brownfieldGradlePluginDependency } from './constants'; | ||
| import { Logger } from '../../logging'; | ||
|
|
||
| const brownfieldPluginIncludeBuildSnippet = `def brownfieldGradlePlugin = |
There was a problem hiding this comment.
Can you explain why we need this change?
| content: renderTemplate('android', 'build.gradle.kts', { | ||
| '{{PACKAGE_NAME}}': android.packageName, | ||
| '{{MIN_SDK_VERSION}}': android.minSdkVersion.toString(), | ||
| '{{COMPILE_SDK_VERSION}}': android.compileSdkVersion.toString(), |
There was a problem hiding this comment.
Is it required because of SDK56?
Summary
Closes #355.
Adds first-class Expo SDK 56 support and stabilizes the example build matrix across bare React Native and Expo hosts.
What changed
ExpoApp56example and aligns Expo-specific repo and CI wiring with Expo 56react-native-workletsvia CMake package lookupRNAppiOS example pods and project integration for the current React Native prebuilt pod layoutvanilla,expo55, andexpo56Testing matrix
RNApp)ExpoApp55)ExpoApp56)iOS was also visually checked on the simulator for
RNApp,ExpoApp55, andExpoApp56.