Skip to content

feat: add Expo 56 support#360

Open
adamTrz wants to merge 16 commits into
mainfrom
adamTrz/expo-56-support
Open

feat: add Expo 56 support#360
adamTrz wants to merge 16 commits into
mainfrom
adamTrz/expo-56-support

Conversation

@adamTrz

@adamTrz adamTrz commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

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

  • adds the ExpoApp56 example and aligns Expo-specific repo and CI wiring with Expo 56
  • stabilizes Android source-mode resolution for the Brownfield Gradle plugin
  • fixes Expo-only Android asset and update task wiring so bare RN does not depend on Expo tasks
  • patches Expo 55 Android native dependencies to resolve react-native-worklets via CMake package lookup
  • updates the RNApp iOS example pods and project integration for the current React Native prebuilt pod layout
  • keeps consumer-app lanes aligned with vanilla, expo55, and expo56

Testing matrix

Setup Android iOS
Bare RN (RNApp) ✅ build + launch ✅ build + launch
Expo 55 (ExpoApp55) ✅ build + launch ✅ build + launch
Expo 56 (ExpoApp56) ✅ build + launch ✅ build + launch

iOS was also visually checked on the simulator for RNApp, ExpoApp55, and ExpoApp56.

@adamTrz adamTrz changed the title [codex] add Expo 56 support and inherit native floors from Expo apps [feat] add Expo 56 support Jun 2, 2026
Comment thread packages/cli/src/brownfield/utils/stripFrameworkBinary.ts Fixed
@adamTrz adamTrz marked this pull request as ready for review June 12, 2026 11:08
@adamTrz adamTrz requested a review from hurali97 June 15, 2026 07:23
@artus9033 artus9033 requested review from artus9033 and Copilot June 15, 2026 10:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/ExpoApp56 and wires it into AndroidApp/AppleApp + CI matrices as the new expo lane.
  • 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.

Comment thread apps/ExpoApp56/RNApp.tsx
Comment on lines +29 to +32
<Button
title="Navigate to Settings"
onPress={() => BrownfieldNavigation.navigateToSettings()}
/>
Comment on lines +11 to +14
- (void)navigateToSettings:(NSDictionary *)user {
UserType *userModel = user == nil ? nil : [UserType fromDictionary:user];
[[[BrownfieldNavigationManager shared] getDelegate] navigateToSettings:userModel];
}
Comment on lines 45 to 52
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,
Comment on lines +3 to +6
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.

Comment on lines +20 to +31
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)}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this now

Comment on lines -73 to +85
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),
)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not use capitalizedBundledAssetsVariantName instea?

Comment on lines +30 to +36
assetsTask.dependsOn("${appProject.path}:createBundle${capitalizedBundledAssetsVariantName}JsAndAssets")

if (Utils.isExpoProject(project) && appProject.tasks.names.contains(updatesResourcesTaskName)) {
assetsTask.dependsOn(
"${appProject.path}:$updatesResourcesTaskName",
)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In VariantTaskProvider we are already having a dependency for preBuildTask - I don't think we should add it in two places.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the following navigation files should not be committed. They are auto generated.

Comment on lines -63 to +70
projectRoot: '/repo',
projectRoot: '/private/tmp/repo',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Is this change and the following ones for path, strictly required?

},
platformConfig
);
const iosConfig = getValidProjectConfig('ios', projectRoot, platformConfig);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Why do we need this file? Is it only Expo56 related?

import { brownfieldGradlePluginDependency } from './constants';
import { Logger } from '../../logging';

const brownfieldPluginIncludeBuildSnippet = `def brownfieldGradlePlugin =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it required because of SDK56?

@artus9033 artus9033 changed the title [feat] add Expo 56 support feat: add Expo 56 support Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for Expo SDK 56

4 participants