diff --git a/.changeset/config.json b/.changeset/config.json index 3d2359e5..76d8cc4b 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -14,6 +14,7 @@ "@callstack/brownfield-example-android-app", "@callstack/brownfield-example-ios-app", "@callstack/brownfield-example-rn-app", + "@callstack/brownfield-example-expo-app", "@callstack/react-native-brownfield-tester-integrated", "@callstack/brownfield-gradle-plugin-react" ], diff --git a/.changeset/deep-garlics-sit.md b/.changeset/deep-garlics-sit.md new file mode 100644 index 00000000..31d7bd4d --- /dev/null +++ b/.changeset/deep-garlics-sit.md @@ -0,0 +1,5 @@ +--- +'@callstack/brownfield-cli': major +--- + +feat: support for CNG with new Expo config plugin diff --git a/.changeset/tasty-apes-retire.md b/.changeset/tasty-apes-retire.md new file mode 100644 index 00000000..201dd68e --- /dev/null +++ b/.changeset/tasty-apes-retire.md @@ -0,0 +1,5 @@ +--- +'@callstack/react-native-brownfield': major +--- + +feat: add Expo config plugin diff --git a/.github/actions/androidapp-road-test/action.yml b/.github/actions/androidapp-road-test/action.yml new file mode 100644 index 00000000..62c95970 --- /dev/null +++ b/.github/actions/androidapp-road-test/action.yml @@ -0,0 +1,63 @@ +name: Android road test (selected RN app & AndroidApp) +description: Package the given RN app as AAR, publish to Maven Local, and build the corresponding AndroidApp flavor + +inputs: + flavor: + description: 'AndroidApp flavor to build (expo or vanilla)' + required: true + + rn-project-path: + description: 'Path to the RN project to build' + required: true + + rn-project-maven-path: + description: 'Maven path to the RN project, e.g. com/rnapp/brownfieldlib' + required: true + +runs: + using: composite + steps: + - name: Setup + uses: ./.github/actions/setup + + - name: Prepare Android environment + uses: ./.github/actions/prepare-android + + - name: Restore Gradle cache + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-android-androidapp-${{ inputs.flavor }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-android-androidapp-${{ inputs.flavor }}-gradle- + ${{ runner.os }}-android-androidapp-gradle- + + # == RN app == + + - name: Package AAR with the Brownfield CLI + run: | + cd ${{ inputs.rn-project-path }} + yarn run brownfield:package:android + shell: bash + + - name: Publish AAR artifact to Maven Local + run: | + cd ${{ inputs.rn-project-path }} + yarn run brownfield:publish:android + shell: bash + + - name: Verify debug AAR exists in Maven Local + run: stat ~/.m2/repository/${{ inputs.rn-project-maven-path }}/0.0.1-SNAPSHOT/brownfieldlib-0.0.1-SNAPSHOT-debug.aar + shell: bash + + - name: Verify release AAR exists in Maven Local + run: stat ~/.m2/repository/${{ inputs.rn-project-maven-path }}/0.0.1-SNAPSHOT/brownfieldlib-0.0.1-SNAPSHOT-release.aar + shell: bash + + # == AndroidApp == + + - name: Build native Android Brownfield app + run: yarn run build:example:android-consumer:${{ inputs.flavor }} + shell: bash diff --git a/.github/actions/appleapp-road-test/action.yml b/.github/actions/appleapp-road-test/action.yml new file mode 100644 index 00000000..fae4039e --- /dev/null +++ b/.github/actions/appleapp-road-test/action.yml @@ -0,0 +1,85 @@ +name: Apple road test (selected RN app & AppleApp) +description: Package the given RN app as XCFramework, and build the corresponding AppleApp variant + +inputs: + variant: + description: 'AppleApp yarn command variant to run (expo or vanilla)' + required: true + + rn-project-path: + description: 'Path to the RN project to build' + required: true + +runs: + using: composite + env: + USE_CCACHE: 1 + CCACHE_DIR: ${{ github.workspace }}/.ios_ccache + CCACHE_BASEDIR: ${{ github.workspace }} + CCACHE_COMPRESS: '1' + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + + - name: Setup + uses: ./.github/actions/setup + + - name: Prepare iOS environment + uses: ./.github/actions/prepare-ios + + - name: Install ccache + run: brew install ccache + + - name: Enable ccache + run: echo "$(brew --prefix)/opt/ccache/libexec" >> $GITHUB_PATH + + - name: Restore RNApp & AppleApp ccache + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 + with: + path: | + .ios_ccache + key: ${{ runner.os }}-rnapp-appleapp-${{ inputs.variant }}-ios-ccache-${{ hashFiles(format('{0}/ios/Podfile.lock', inputs.rn-project-path), format('{0}/ios/*.xcodeproj/project.pbxproj', inputs.rn-project-path), 'apps/AppleApp/Brownfield Apple App.xcodeproj/project.pbxproj') }} + restore-keys: | + ${{ runner.os }}-rnapp-appleapp-${{ inputs.variant }}-ios-ccache- + + # == RN app == + + - name: Restore Pods cache (RN ${{ inputs.variant }} app) + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 + with: + path: | + ${{ inputs.rn-project-path }}/ios/Pods + key: ${{ runner.os }}-rnapp-${{ inputs.variant }}-ios-pods-${{ hashFiles(format('{0}/ios/Podfile.lock', inputs.rn-project-path)) }} + restore-keys: | + ${{ runner.os }}-rnapp-${{ inputs.variant }}-ios-pods- + + - name: Install pods (RN ${{ inputs.variant }} app) + run: | + cd ${{ inputs.rn-project-path }}/ios + pod install + + - name: Restore DerivedData cache (RN ${{ inputs.variant }} app) + uses: actions/cache@v5 + with: + path: ${{ inputs.rn-project-path }}/ios/build + key: ${{ runner.os }}-ios-rnapp-${{ inputs.variant }}-derived-data-${{ hashFiles(format('{0}/ios/Podfile.lock', inputs.rn-project-path), format('{0}/ios/*.xcodeproj/project.pbxproj', inputs.rn-project-path)) }} + restore-keys: | + ${{ runner.os }}-ios-rnapp-${{ inputs.variant }}-derived-data- + + - name: Package iOS framework with the Brownfield CLI + run: | + cd ${{ inputs.rn-project-path }} + yarn run brownfield:package:ios + + # == AppleApp == + + - name: Build Brownfield iOS native app (${{ inputs.variant }}) + run: | + yarn run build:example:ios-consumer:${{ inputs.variant }} + + # ============== + + - name: Log ccache stats + uses: ./.github/actions/ccache-summary + with: + name: RN ${{ inputs.variant }} app & AppleApp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38070c67..2a48f079 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,8 +83,8 @@ jobs: - name: Build integrated Android tester app run: yarn run build:tester-integrated:android - android-rnapp-androidapp: - name: Android road test (RNApp & AndroidApp) + android-androidapp-expo: + name: Android road test (RNApp & AndroidApp - Expo) runs-on: ubuntu-latest needs: build-lint @@ -92,52 +92,35 @@ jobs: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - - name: Setup - uses: ./.github/actions/setup - - - name: Prepare Android environment - uses: ./.github/actions/prepare-android - - - name: Restore Gradle cache - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 + - name: Run RNApp -> AndroidApp road test (Expo) + uses: ./.github/actions/androidapp-road-test with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-android-rnapp-androidapp-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-android-rnapp-androidapp-gradle- - - # == RNApp == - - - name: Package AAR with the Brownfield CLI - run: | - cd apps/RNApp - yarn run brownfield:package:android - - - name: Publish AAR artifact to Maven Local - run: | - cd apps/RNApp - yarn run brownfield:publish:android - - - name: Verify debug AAR exists in Maven Local - run: stat ~/.m2/repository/com/rnapp/brownfieldlib/0.0.1-local/brownfieldlib-0.0.1-local-debug.aar + flavor: expo + rn-project-path: apps/ExpoApp + rn-project-maven-path: com/expoapp/brownfieldlib - - name: Verify release AAR exists in Maven Local - run: stat ~/.m2/repository/com/rnapp/brownfieldlib/0.0.1-local/brownfieldlib-0.0.1-local-release.aar + android-androidapp-vanilla: + name: Android road test (RNApp & AndroidApp - Vanilla) + runs-on: ubuntu-latest + needs: build-lint - # == AndroidApp == + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - - name: Build native Android Brownfield app - run: | - yarn run build:example:android-consumer + - name: Run RNApp -> AndroidApp road test (Vanilla) + uses: ./.github/actions/androidapp-road-test + with: + flavor: vanilla + rn-project-path: apps/RNApp + rn-project-maven-path: com/rnapp/brownfieldlib ios-tester-integrated: name: iOS road test (TesterIntegrated) runs-on: macos-26 needs: build-lint - env: &ios_env + env: USE_CCACHE: 1 CCACHE_DIR: ${{ github.workspace }}/.ios_ccache CCACHE_BASEDIR: ${{ github.workspace }} @@ -203,76 +186,32 @@ jobs: with: name: TesterIntegrated - ios-rnapp-appleapp: - name: iOS road test (RNApp & AppleApp) - runs-on: macos-26 + ios-appleapp-vanilla: + name: iOS road test (RNApp & AppleApp - Vanilla) + runs-on: ubuntu-latest needs: build-lint - env: *ios_env - steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - - name: Setup - uses: ./.github/actions/setup - - - name: Prepare iOS environment - uses: ./.github/actions/prepare-ios - - - name: Install ccache - run: brew install ccache - - - name: Enable ccache - run: echo "$(brew --prefix)/opt/ccache/libexec" >> $GITHUB_PATH - - - name: Restore RNApp & AppleApp ccache - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 - with: - path: | - .ios_ccache - key: ${{ runner.os }}-rnapp-appleapp-ios-ccache-${{ hashFiles('apps/RNApp/ios/Podfile.lock', 'apps/RNApp/ios/RNApp.xcodeproj/project.pbxproj', 'apps/AppleApp/Brownfield Apple App.xcodeproj/project.pbxproj') }} - restore-keys: | - ${{ runner.os }}-rnapp-appleapp-ios-ccache- - - # == RNApp == - - - name: Restore Pods cache (RNApp) - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 - with: - path: | - apps/RNApp/ios/Pods - key: ${{ runner.os }}-rnapp-ios-pods-${{ hashFiles('apps/RNApp/ios/Podfile.lock') }} - restore-keys: | - ${{ runner.os }}-rnapp-ios-pods- - - - name: Install pods (RNApp) - run: | - cd apps/RNApp/ios - pod install - - - name: Restore DerivedData cache (RNApp) - uses: actions/cache@v5 + - name: Run RNApp -> AppleApp road test (Vanilla) + uses: ./.github/actions/appleapp-road-test with: - path: apps/RNApp/ios/build - key: ${{ runner.os }}-ios-rnapp-derived-data-${{ hashFiles('apps/RNApp/ios/Podfile.lock', 'apps/RNApp/ios/RNApp.xcodeproj/project.pbxproj') }} - restore-keys: | - ${{ runner.os }}-ios-rnapp-derived-data- + variant: vanilla + rn-project-path: apps/RNApp - - name: Package iOS framework with the Brownfield CLI - run: | - cd apps/RNApp - yarn run brownfield:package:ios - - # == AppleApp == - - - name: Build Brownfield iOS native app - run: | - yarn run build:example:ios-consumer + ios-appleapp-expo: + name: iOS road test (RNApp & AppleApp - Expo) + runs-on: ubuntu-latest + needs: build-lint - # ============== + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - - name: Log ccache stats - uses: ./.github/actions/ccache-summary + - name: Run RNApp -> AppleApp road test (Expo) + uses: ./.github/actions/appleapp-road-test with: - name: RNApp & AppleApp + variant: expo + rn-project-path: apps/RNApp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7220e114..a57e51d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ Run `yarn` in the root of the repository to install all dependencies. Depending on your needs, you may need to install CocoaPods in a subset of the below directories: + - example React Native iOS app: `cd apps/RNApp/ios && pod install` - integrated iOS tester app: `cd apps/TesterIntegrated/swift && pod install` @@ -18,17 +19,18 @@ We use [changesets](https://github.com/changesets/changesets) to make it easier ## Scripts -- `lint` - runs linting on all JS/TS source files in the monorepo *[Turbo]* +- `lint` - runs linting on all JS/TS source files in the monorepo _[Turbo]_ - `gradle-plugin:lint` - runs linting on the Brownfield Gradle plugin source code -- `typecheck` - runs TypeScript type checking on all TS source files in the monorepo *[Turbo]* -- `build` - runs all `build*` tasks in the Turbo repo - see below for more details *[Turbo]* +- `typecheck` - runs TypeScript type checking on all TS source files in the monorepo _[Turbo]_ +- `build` - runs all `build*` tasks in the Turbo repo - see below for more details _[Turbo]_ - `dev` - runs all `dev` tasks in all workspaces - `brownfield:plugin:publish:local` - publishes the Brownfield Gradle plugin to your local Maven repository for testing purposes -- `build:brownfield` - builds the React Native Brownfield package (`packages/react-native-brownfield`) *[Turbo]* -- `build:docs` - builds the documentation site (`docs/`) *[Turbo]* +- `build:brownfield` - builds the React Native Brownfield package (`packages/react-native-brownfield`) _[Turbo]_ +- `build:docs` - builds the documentation site (`docs/`) _[Turbo]_ - `build:tester-integrated:android` - builds the Android integrated tester app (`apps/TesterIntegrated/android`) - `build:tester-integrated:ios` - builds the iOS integrated tester app (`apps/TesterIntegrated/swift`) - `build:example:android-rn` - builds the example React Native app for Android (`apps/RNApp/android`) - `build:example:ios-rn` - builds the example React Native app for iOS (`apps/RNApp/ios`) -- `build:example:android-consumer` - builds the example native Android consumer app (`apps/AndroidApp`) -- `build:example:ios-consumer` - builds the example native Apple consumer app (`apps/AppleApp`) \ No newline at end of file +- `build:example:android-consumer:expo` - builds the example native Android consumer (`apps/AndroidApp`) app's flavor consuming the Expo RN app (`apps/ExpoApp`) artifact +- - `build:example:android-consumer:vanilla` - builds the example native Android consumer (`apps/AndroidApp`) app's flavor consuming the vanilla RN app (`apps/RNApp`) artifact +- `build:example:ios-consumer` - builds the example native Apple consumer app (`apps/AppleApp`) diff --git a/apps/AndroidApp/app/build.gradle.kts b/apps/AndroidApp/app/build.gradle.kts index 081884be..2b6cd3a6 100644 --- a/apps/AndroidApp/app/build.gradle.kts +++ b/apps/AndroidApp/app/build.gradle.kts @@ -28,6 +28,16 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + flavorDimensions += "app" + productFlavors { + create("expo") { + dimension = "app" + } + create("vanilla") { + dimension = "app" + } + } + buildTypes { release { isMinifyEnabled = false @@ -35,6 +45,7 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + signingConfig = signingConfigs.getByName("debug") } } compileOptions { @@ -53,13 +64,16 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) + implementation(libs.material) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) implementation(libs.androidx.appcompat) - implementation(libs.brownfieldlib) + add("expoImplementation", libs.brownfieldlib.expo) + add("vanillaImplementation", libs.brownfieldlib.vanilla) + implementation(libs.androidx.fragment.compose) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) diff --git a/apps/AndroidApp/app/src/expo/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt b/apps/AndroidApp/app/src/expo/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt new file mode 100644 index 00000000..422da818 --- /dev/null +++ b/apps/AndroidApp/app/src/expo/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt @@ -0,0 +1,5 @@ +package com.callstack.brownfield.android.example + +object ReactNativeConstants { + const val MAIN_MODULE_NAME = "main" +} diff --git a/apps/AndroidApp/app/src/expo/java/com/callstack/brownfield/android/example/ReactNativeHostManager.kt b/apps/AndroidApp/app/src/expo/java/com/callstack/brownfield/android/example/ReactNativeHostManager.kt new file mode 100644 index 00000000..f47c28c0 --- /dev/null +++ b/apps/AndroidApp/app/src/expo/java/com/callstack/brownfield/android/example/ReactNativeHostManager.kt @@ -0,0 +1,3 @@ +package com.callstack.brownfield.android.example + +typealias ReactNativeHostManager = com.callstack.rnbrownfield.demo.expoapp.ReactNativeHostManager diff --git a/apps/AndroidApp/app/src/main/AndroidManifest.xml b/apps/AndroidApp/app/src/main/AndroidManifest.xml index 0e7bc5b2..bda9be43 100644 --- a/apps/AndroidApp/app/src/main/AndroidManifest.xml +++ b/apps/AndroidApp/app/src/main/AndroidManifest.xml @@ -11,12 +11,12 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.AndroidBrownfielApp" + android:theme="@style/Theme.AndroidBrownfieldApp" android:usesCleartextTraffic="true"> + android:theme="@style/Theme.AndroidBrownfieldApp"> diff --git a/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt b/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt index 14908ac0..4143acf2 100644 --- a/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt +++ b/apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt @@ -1,5 +1,6 @@ package com.callstack.brownfield.android.example +import android.content.res.Configuration import android.os.Bundle import android.widget.Toast import androidx.activity.compose.setContent @@ -33,9 +34,14 @@ import androidx.fragment.compose.AndroidFragment import com.callstack.brownfield.android.example.ui.theme.AndroidBrownfieldAppTheme import com.callstack.reactnativebrownfield.ReactNativeFragment import com.callstack.reactnativebrownfield.constants.ReactNativeFragmentArgNames -import com.rnapp.brownfieldlib.ReactNativeHostManager class MainActivity : AppCompatActivity() { + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + + ReactNativeHostManager.onConfigurationChanged(application, newConfig) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(null) enableEdgeToEdge() @@ -135,7 +141,7 @@ fun ReactNativeView( arguments = Bundle().apply { putString( ReactNativeFragmentArgNames.ARG_MODULE_NAME, - "RNApp" + ReactNativeConstants.MAIN_MODULE_NAME ) } ) diff --git a/apps/AndroidApp/app/src/main/res/values/themes.xml b/apps/AndroidApp/app/src/main/res/values/themes.xml index ac29b85b..6a69b7e8 100644 --- a/apps/AndroidApp/app/src/main/res/values/themes.xml +++ b/apps/AndroidApp/app/src/main/res/values/themes.xml @@ -1,5 +1,5 @@ -