From babcfbca6b952fb19427b2ce55d6a0a77b6ad056 Mon Sep 17 00:00:00 2001 From: Ben Boral Date: Thu, 26 Mar 2026 11:53:40 -0500 Subject: [PATCH 1/5] change insert location for new architecture (android) --- expo.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/expo.js b/expo.js index c27719a48..e744a82ca 100644 --- a/expo.js +++ b/expo.js @@ -198,13 +198,24 @@ const withAndroidMainApplication = (config) => { override fun getJSBundleFile(): String { return CodePush.getJSBundleFile() }`; - const hermesEnabledAnchor = /(override\s+val\s+isHermesEnabled:\s*Boolean\s*=\s*BuildConfig\.IS_HERMES_ENABLED)\s*\n/m; if (!content.includes("override fun getJSBundleFile(): String")) { + const hermesEnabledAnchor = /(override\s+val\s+isHermesEnabled:\s*Boolean\s*=\s*BuildConfig\.IS_HERMES_ENABLED)\s*\n/m; if (hermesEnabledAnchor.test(content)) { + // RN < 0.83: anchor after isHermesEnabled content = content.replace(hermesEnabledAnchor, `$1\n${getJSBundleFileMethodString}\n`); + } else if (content.includes('override fun onCreate()')) { + // RN 0.83+: isHermesEnabled was removed, insert before onCreate() + content = content.replace( + 'override fun onCreate()', + `${getJSBundleFileMethodString}\n\n override fun onCreate()` + ); } else { - WarningAggregator.addWarningAndroid('codepush-plugin', 'Could not find `isHermesEnabled` property to anchor `getJSBundleFile()` insertion. Please review `MainApplication.kt`.'); + // Fallback: insert inside the class body + content = content.replace( + /(class MainApplication[^{]*\{)/, + `$1\n${getJSBundleFileMethodString}` + ); } } From dcd648b605050dad3a170b00bcdbcf210a627f9e Mon Sep 17 00:00:00 2001 From: Ben Boral Date: Thu, 26 Mar 2026 12:32:02 -0500 Subject: [PATCH 2/5] new anchor point --- expo.js | 54 +++++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/expo.js b/expo.js index e744a82ca..1b00a1842 100644 --- a/expo.js +++ b/expo.js @@ -193,32 +193,36 @@ const withAndroidMainApplication = (config) => { } } - // --- 4. Add getJSBundleFile method --- - const getJSBundleFileMethodString = ` - override fun getJSBundleFile(): String { - return CodePush.getJSBundleFile() - }`; - - if (!content.includes("override fun getJSBundleFile(): String")) { - const hermesEnabledAnchor = /(override\s+val\s+isHermesEnabled:\s*Boolean\s*=\s*BuildConfig\.IS_HERMES_ENABLED)\s*\n/m; - if (hermesEnabledAnchor.test(content)) { - // RN < 0.83: anchor after isHermesEnabled - content = content.replace(hermesEnabledAnchor, `$1\n${getJSBundleFileMethodString}\n`); - } else if (content.includes('override fun onCreate()')) { - // RN 0.83+: isHermesEnabled was removed, insert before onCreate() - content = content.replace( - 'override fun onCreate()', - `${getJSBundleFileMethodString}\n\n override fun onCreate()` - ); - } else { - // Fallback: insert inside the class body - content = content.replace( - /(class MainApplication[^{]*\{)/, - `$1\n${getJSBundleFileMethodString}` - ); + // --- 4. Wire up CodePush bundle file --- + if (!content.includes("CodePush.getJSBundleFile()")) { + const hermesEnabledAnchor = /(override\s+val\s+isHermesEnabled:\s*Boolean\s*=\s*BuildConfig\.IS_HERMES_ENABLED)\s*\n/m; + if (hermesEnabledAnchor.test(content)) { + // RN < 0.83: uses ReactNativeHost with getJSBundleFile() override + const getJSBundleFileMethodString = ` + override fun getJSBundleFile(): String { + return CodePush.getJSBundleFile() + }`; + content = content.replace(hermesEnabledAnchor, `$1\n${getJSBundleFileMethodString}\n`); + } else { + // RN 0.83+: uses ReactHost via getDefaultReactHost() — pass jsBundleFilePath parameter + // Match the closing parenthesis of the getDefaultReactHost() call + const reactHostCallRegex = /(getDefaultReactHost\([\s\S]*?packageList\s*=[\s\S]*?\})([\s\S]*?\))/m; + if (reactHostCallRegex.test(content)) { + content = content.replace(reactHostCallRegex, (match, beforeClose, closing) => { + // Check if jsBundleFilePath is already set + if (match.includes('jsBundleFilePath')) return match; + // Insert the parameter before the closing parentheses + return `${beforeClose},\n jsBundleFilePath = CodePush.getJSBundleFile()${closing}`; + }); + } else { + WarningAggregator.addWarningAndroid( + 'codepush-plugin', + 'Could not find getDefaultReactHost() call in MainApplication. CodePush bundle file path not configured.' + ); + } + } } - } - + modConfig.modResults.contents = content; return modConfig; }); From 8c1d25bef91a1e5b2d2e3160814dc669fb921386 Mon Sep 17 00:00:00 2001 From: Ben Boral Date: Thu, 26 Mar 2026 13:15:52 -0500 Subject: [PATCH 3/5] 82 not 83 --- expo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/expo.js b/expo.js index 1b00a1842..c0a4783df 100644 --- a/expo.js +++ b/expo.js @@ -197,14 +197,14 @@ const withAndroidMainApplication = (config) => { if (!content.includes("CodePush.getJSBundleFile()")) { const hermesEnabledAnchor = /(override\s+val\s+isHermesEnabled:\s*Boolean\s*=\s*BuildConfig\.IS_HERMES_ENABLED)\s*\n/m; if (hermesEnabledAnchor.test(content)) { - // RN < 0.83: uses ReactNativeHost with getJSBundleFile() override + // RN < 0.82: uses ReactNativeHost with getJSBundleFile() override const getJSBundleFileMethodString = ` override fun getJSBundleFile(): String { return CodePush.getJSBundleFile() }`; content = content.replace(hermesEnabledAnchor, `$1\n${getJSBundleFileMethodString}\n`); } else { - // RN 0.83+: uses ReactHost via getDefaultReactHost() — pass jsBundleFilePath parameter + // RN 0.82+: uses ReactHost via getDefaultReactHost() — pass jsBundleFilePath parameter // Match the closing parenthesis of the getDefaultReactHost() call const reactHostCallRegex = /(getDefaultReactHost\([\s\S]*?packageList\s*=[\s\S]*?\})([\s\S]*?\))/m; if (reactHostCallRegex.test(content)) { From 438550376a70746f60635a49dd9499ba723b98f3 Mon Sep 17 00:00:00 2001 From: Minsik Kim Date: Wed, 8 Apr 2026 13:36:04 +0900 Subject: [PATCH 4/5] Fix Expo Android CodePush bundle wiring --- expo.js | 57 ++++++++++++++++++++++++-------------------- test/test.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 26 deletions(-) diff --git a/expo.js b/expo.js index c0a4783df..ac80f5e32 100644 --- a/expo.js +++ b/expo.js @@ -193,35 +193,40 @@ const withAndroidMainApplication = (config) => { } } - // --- 4. Wire up CodePush bundle file --- - if (!content.includes("CodePush.getJSBundleFile()")) { - const hermesEnabledAnchor = /(override\s+val\s+isHermesEnabled:\s*Boolean\s*=\s*BuildConfig\.IS_HERMES_ENABLED)\s*\n/m; - if (hermesEnabledAnchor.test(content)) { - // RN < 0.82: uses ReactNativeHost with getJSBundleFile() override - const getJSBundleFileMethodString = ` - override fun getJSBundleFile(): String { - return CodePush.getJSBundleFile() - }`; - content = content.replace(hermesEnabledAnchor, `$1\n${getJSBundleFileMethodString}\n`); + // --- 4. Wire up CodePush bundle file --- + if (!content.includes("CodePush.getJSBundleFile()")) { + const getJSBundleFileMethodString = ` + override fun getJSBundleFile(): String { + return CodePush.getJSBundleFile() + }`; + const hermesEnabledAnchor = /(override\s+val\s+isHermesEnabled:\s*Boolean\s*=\s*BuildConfig\.IS_HERMES_ENABLED)\s*\n/m; + const jsMainModuleAnchor = /(override fun getJSMainModuleName\(\): String = [^\n]+)\n/m; + + if (hermesEnabledAnchor.test(content)) { + // RN < 0.82 and some bare RN templates still expose isHermesEnabled + content = content.replace(hermesEnabledAnchor, `$1\n${getJSBundleFileMethodString}\n`); + } else if (content.includes('ReactNativeHostWrapper(') && jsMainModuleAnchor.test(content)) { + // Expo CNG wraps the host and delegates getJSBundleFile() back to the host override. + content = content.replace(jsMainModuleAnchor, `$1\n${getJSBundleFileMethodString}\n`); + } else { + // RN 0.82+: uses ReactHost via getDefaultReactHost() — pass jsBundleFilePath parameter + // Match the closing parenthesis of the getDefaultReactHost() call + const reactHostCallRegex = /(getDefaultReactHost\([\s\S]*?packageList\s*=[\s\S]*?\})([\s\S]*?\))/m; + if (reactHostCallRegex.test(content)) { + content = content.replace(reactHostCallRegex, (match, beforeClose, closing) => { + // Check if jsBundleFilePath is already set + if (match.includes('jsBundleFilePath')) return match; + // Insert the parameter before the closing parentheses + return `${beforeClose},\n jsBundleFilePath = CodePush.getJSBundleFile()${closing}`; + }); } else { - // RN 0.82+: uses ReactHost via getDefaultReactHost() — pass jsBundleFilePath parameter - // Match the closing parenthesis of the getDefaultReactHost() call - const reactHostCallRegex = /(getDefaultReactHost\([\s\S]*?packageList\s*=[\s\S]*?\})([\s\S]*?\))/m; - if (reactHostCallRegex.test(content)) { - content = content.replace(reactHostCallRegex, (match, beforeClose, closing) => { - // Check if jsBundleFilePath is already set - if (match.includes('jsBundleFilePath')) return match; - // Insert the parameter before the closing parentheses - return `${beforeClose},\n jsBundleFilePath = CodePush.getJSBundleFile()${closing}`; - }); - } else { - WarningAggregator.addWarningAndroid( - 'codepush-plugin', - 'Could not find getDefaultReactHost() call in MainApplication. CodePush bundle file path not configured.' - ); - } + WarningAggregator.addWarningAndroid( + 'codepush-plugin', + 'Could not find a supported MainApplication anchor to configure CodePush bundle file.' + ); } } + } modConfig.modResults.contents = content; return modConfig; diff --git a/test/test.ts b/test/test.ts index 7ab4a3a8c..c629457c3 100644 --- a/test/test.ts +++ b/test/test.ts @@ -536,11 +536,78 @@ const UpdateSync = "updateSync.js"; const UpdateSync2x = "updateSync2x.js"; const UpdateNotifyApplicationReadyConditional = "updateNARConditional.js"; +const ExpoAndroidMainApplicationPathSegments = [ + "android", + "app", + "src", + "main", + "java", + "com", + "testcodepush", + "MainApplication.kt", +]; + +function getExpoAndroidMainApplicationPath(projectDirectory: string): string { + return path.join(projectDirectory, TestConfig.TestAppName, ...ExpoAndroidMainApplicationPathSegments); +} + +function assertExpoAndroidCodePushBundleWiring(projectDirectory: string): void { + const mainApplicationPath = getExpoAndroidMainApplicationPath(projectDirectory); + assert.ok(fs.existsSync(mainApplicationPath), `Expected generated MainApplication.kt at ${mainApplicationPath}`); + + const content = fs.readFileSync(mainApplicationPath, "utf8"); + + if (content.includes("ReactNativeHostWrapper(")) { + assert.ok( + /override fun getJSBundleFile\(\): String\s*\{\s*return CodePush\.getJSBundleFile\(\)\s*\}/m.test(content), + `Expected CodePush getJSBundleFile() override in ${mainApplicationPath}` + ); + } else { + assert.ok( + /getDefaultReactHost\([\s\S]*jsBundleFilePath = CodePush\.getJSBundleFile\(\)[\s\S]*\)/m.test(content), + `Expected CodePush jsBundleFilePath wiring in ${mainApplicationPath}` + ); + } +} + ////////////////////////////////////////////////////////////////////////////////////////// // Initialize the tests. PluginTestingFramework.initializeTests(new RNProjectManager(), supportedTargetPlatforms, (projectManager: ProjectManager, targetPlatform: Platform.IPlatform) => { + TestBuilder.describe("#expo.android.mainApplication", + () => { + TestBuilder.it("wires CodePush through jsBundleFilePath for the test app", false, + (done: Mocha.Done) => { + if (!TestConfig.isExpoApp || targetPlatform.getName() !== "android") { + done(); + return; + } + + try { + assertExpoAndroidCodePushBundleWiring(TestConfig.testRunDirectory); + done(); + } catch (error) { + done(error); + } + }); + + TestBuilder.it("wires CodePush through jsBundleFilePath for the update app", false, + (done: Mocha.Done) => { + if (!TestConfig.isExpoApp || targetPlatform.getName() !== "android") { + done(); + return; + } + + try { + assertExpoAndroidCodePushBundleWiring(TestConfig.updatesDirectory); + done(); + } catch (error) { + done(error); + } + }); + }); + TestBuilder.describe("#window.codePush.checkForUpdate", () => { TestBuilder.it("window.codePush.checkForUpdate.noUpdate", false, From 24af134b0f8a4f92c975e6df2dde18322cb8d562 Mon Sep 17 00:00:00 2001 From: Minsik Kim Date: Wed, 8 Apr 2026 13:37:20 +0900 Subject: [PATCH 5/5] Clarify Expo Android regression test names --- test/test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test.ts b/test/test.ts index c629457c3..8713a82b0 100644 --- a/test/test.ts +++ b/test/test.ts @@ -577,7 +577,7 @@ PluginTestingFramework.initializeTests(new RNProjectManager(), supportedTargetPl (projectManager: ProjectManager, targetPlatform: Platform.IPlatform) => { TestBuilder.describe("#expo.android.mainApplication", () => { - TestBuilder.it("wires CodePush through jsBundleFilePath for the test app", false, + TestBuilder.it("wires the CodePush bundle file for the test app", false, (done: Mocha.Done) => { if (!TestConfig.isExpoApp || targetPlatform.getName() !== "android") { done(); @@ -592,7 +592,7 @@ PluginTestingFramework.initializeTests(new RNProjectManager(), supportedTargetPl } }); - TestBuilder.it("wires CodePush through jsBundleFilePath for the update app", false, + TestBuilder.it("wires the CodePush bundle file for the update app", false, (done: Mocha.Done) => { if (!TestConfig.isExpoApp || targetPlatform.getName() !== "android") { done();