From 438b970db2736531630bea6b56a00c214f8a5e45 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 27 Oct 2025 22:55:35 +0900 Subject: [PATCH 1/2] fix(Expo): fix app stuck on splash screen in SDK 54 --- expo/plugin/withCodePushAndroid.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/expo/plugin/withCodePushAndroid.js b/expo/plugin/withCodePushAndroid.js index 35446fb1..95095ac4 100644 --- a/expo/plugin/withCodePushAndroid.js +++ b/expo/plugin/withCodePushAndroid.js @@ -68,10 +68,19 @@ const withAndroidMainApplicationDependency = (config) => { if (action.modResults.contents.includes(RN_082_MARKER)) { action.modResults.contents = addJsBundleFilePathArgument(action.modResults.contents); } else { + // https://github.com/Soomgo-Mobile/react-native-code-push/issues/97 + const isExpoSDK54 = config.sdkVersion?.startsWith('54.') ?? false; + const addingCode = isExpoSDK54 + ? ' override fun getJSBundleFile(): String {\n' + + ' CodePush.getInstance(applicationContext, BuildConfig.DEBUG)\n' + + ' return CodePush.getJSBundleFile()\n' + + ' }\n' + : ' override fun getJSBundleFile(): String = CodePush.getJSBundleFile()\n'; action.modResults.contents = androidMainApplicationApplyImplementation( action.modResults.contents, 'object : DefaultReactNativeHost(this) {', - ' override fun getJSBundleFile(): String = CodePush.getJSBundleFile()\n'); + addingCode, + ); } } From 9a08cea5a8e3fe7bf12b59128ba4483dcc257a7e Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 27 Oct 2025 23:19:23 +0900 Subject: [PATCH 2/2] test(Expo): add tests for withAndroidMainApplicationDependency --- .../__tests__/withCodePushAndroid.test.ts | 178 ++++++++++++++++++ package.json | 2 +- 2 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 expo/plugin/__tests__/withCodePushAndroid.test.ts diff --git a/expo/plugin/__tests__/withCodePushAndroid.test.ts b/expo/plugin/__tests__/withCodePushAndroid.test.ts new file mode 100644 index 00000000..00e91411 --- /dev/null +++ b/expo/plugin/__tests__/withCodePushAndroid.test.ts @@ -0,0 +1,178 @@ +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; + +jest.mock( + "expo/config-plugins", + () => ({ + withMainApplication: jest.fn((config, apply) => + // @ts-expect-error -- mocking + apply({ modResults: { contents: config._contents ?? "" } }) + ), + WarningAggregator: { + addWarningAndroid: jest.fn(), + }, + }), + { virtual: true } +); + +const { withMainApplication, WarningAggregator } = require("expo/config-plugins"); +const { withAndroidMainApplicationDependency } = require("../withCodePushAndroid.js"); + +// https://github.com/expo/expo/blob/deeaccf50bbc5b904c0b67c120efcf7e0accfd0b/templates/expo-template-bare-minimum/android/app/src/main/java/com/helloworld/MainApplication.kt +const expo54Template = ` +package com.helloworld + +import android.app.Application +import android.content.res.Configuration + +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.ReactHost +import com.facebook.react.common.ReleaseLevel +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint +import com.facebook.react.defaults.DefaultReactNativeHost + +import expo.modules.ApplicationLifecycleDispatcher +import expo.modules.ReactNativeHostWrapper + +class MainApplication : Application(), ReactApplication { + + override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( + this, + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + } + + override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + } + ) + + override val reactHost: ReactHost + get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + DefaultNewArchitectureEntryPoint.releaseLevel = try { + ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase()) + } catch (e: IllegalArgumentException) { + ReleaseLevel.STABLE + } + loadReactNative(this) + ApplicationLifecycleDispatcher.onApplicationCreate(this) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) + } +} +`; + +// https://github.com/expo/expo/blob/main/templates/expo-template-bare-minimum/android/app/src/main/java/com/helloworld/MainApplication.kt +const expo55Template = ` +package com.helloworld + +import android.app.Application +import android.content.res.Configuration + +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative +import com.facebook.react.ReactPackage +import com.facebook.react.ReactHost +import com.facebook.react.common.ReleaseLevel +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint + +import expo.modules.ApplicationLifecycleDispatcher +import expo.modules.ExpoReactHostFactory + +class MainApplication : Application(), ReactApplication { + + override val reactHost: ReactHost by lazy { + ExpoReactHostFactory.getDefaultReactHost( + context = applicationContext, + packageList = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + } + ) + } + + override fun onCreate() { + super.onCreate() + DefaultNewArchitectureEntryPoint.releaseLevel = try { + ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase()) + } catch (e: IllegalArgumentException) { + ReleaseLevel.STABLE + } + loadReactNative(this) + ApplicationLifecycleDispatcher.onApplicationCreate(this) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) + } +} +`; + +const withMainApplicationMock = withMainApplication as jest.Mock; +const addWarningAndroidMock = WarningAggregator.addWarningAndroid as jest.Mock; + +describe("withAndroidMainApplicationDependency", () => { + beforeEach(() => { + withMainApplicationMock.mockClear(); + addWarningAndroidMock.mockClear(); + }); + + it("adds CodePush import and method override in Expo SDK 53", () => { + const result = withAndroidMainApplicationDependency({ + _contents: expo54Template, + sdkVersion: "53.0.0", + }); + + const modifiedContent = result.modResults.contents; + + expect(modifiedContent).toContain("import com.microsoft.codepush.react.CodePush"); + expect(modifiedContent).toContain("override fun getJSBundleFile(): String = CodePush.getJSBundleFile()"); + }); + + it("adds CodePush import and method override with getInstance() in Expo SDK 54", () => { + const result = withAndroidMainApplicationDependency({ + _contents: expo54Template, + sdkVersion: "54.0.0", + }); + + const modifiedContent = result.modResults.contents; + + expect(modifiedContent).toContain("import com.microsoft.codepush.react.CodePush"); + expect(modifiedContent).toContain("CodePush.getInstance(applicationContext, BuildConfig.DEBUG)"); + expect(modifiedContent).toContain("return CodePush.getJSBundleFile()"); + }); + + it("adds CodePush import and jsBundleFilePath argument in Expo SDK 55", () => { + const result = withAndroidMainApplicationDependency({ + _contents: expo55Template, + sdkVersion: "55.0.0", + }); + + const modifiedContent = result.modResults.contents; + + expect(modifiedContent).toContain("import com.microsoft.codepush.react.CodePush"); + expect(modifiedContent).toContain("jsBundleFilePath = CodePush.getJSBundleFile()"); + + const packageListIndex = modifiedContent.indexOf("packageList ="); + const jsBundleIndex = modifiedContent.indexOf("jsBundleFilePath = CodePush.getJSBundleFile()"); + expect(jsBundleIndex).toBeGreaterThan(packageListIndex); + }); +}); diff --git a/package.json b/package.json index 034a8390..46cc9dd1 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "prepack": "npm run build:cli", "publish": "npm publish --access=public", "eslint": "eslint --quiet .", - "jest": "jest src/versioning/* && npm run --workspace cli test" + "jest": "jest src/versioning/* expo/* && npm run --workspace cli test" }, "repository": { "type": "git",