diff --git a/expo.js b/expo.js index c27719a4..ac80f5e3 100644 --- a/expo.js +++ b/expo.js @@ -193,21 +193,41 @@ const withAndroidMainApplication = (config) => { } } - // --- 4. Add getJSBundleFile method --- - 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; + // --- 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 (!content.includes("override fun getJSBundleFile(): String")) { 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 { - WarningAggregator.addWarningAndroid('codepush-plugin', 'Could not find `isHermesEnabled` property to anchor `getJSBundleFile()` insertion. Please review `MainApplication.kt`.'); + // 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 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 7ab4a3a8..8713a82b 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 the CodePush bundle file 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 the CodePush bundle file 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,