Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 31 additions & 23 deletions expo.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,35 +193,43 @@ 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 = `
// --- 4. Wire up CodePush bundle file ---
if (!content.includes("CodePush.getJSBundleFile()")) {
const getJSBundleFileMethodString = `
override fun getJSBundleFile(): String {
return CodePush.getJSBundleFile()
}`;
content = content.replace(hermesEnabledAnchor, `$1\n${getJSBundleFileMethodString}\n`);
const reactNativeHostAnchors = [
/(override\s+fun\s+getJSMainModuleName\(\):\s*String\s*=\s*[^\n]+)\s*\n/m,
/(override\s+fun\s+getUseDeveloperSupport\(\):\s*Boolean\s*=\s*BuildConfig\.DEBUG)\s*\n/m,
/(override\s+val\s+isHermesEnabled:\s*Boolean\s*=\s*BuildConfig\.IS_HERMES_ENABLED)\s*\n/m,
/(override\s+val\s+isNewArchEnabled:\s*Boolean\s*=\s*BuildConfig\.IS_NEW_ARCHITECTURE_ENABLED)\s*\n/m,
];
const reactNativeHostAnchor = reactNativeHostAnchors.find(anchor => anchor.test(content));

if (reactNativeHostAnchor) {
// RN <= 0.81 and Expo SDK 54 still configure the bundle via ReactNativeHost.
// Expo wraps the host, but ReactNativeHostWrapper delegates getJSBundleFile() to the wrapped host.
content = content.replace(reactNativeHostAnchor, `$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 detect a supported React host configuration in MainApplication. CodePush bundle file path not configured.'
);
}
}
}

modConfig.modResults.contents = content;
return modConfig;
Expand Down
24 changes: 9 additions & 15 deletions test/template/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,15 @@
"slug": "TestCodePush",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.testcodepush"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true,
"package": "com.testcodepush"
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
[
"@code-push-next/react-native-code-push/expo",
Expand All @@ -40,7 +26,15 @@
"CodePushServerURL": "http://10.0.2.2:3001"
}
}
],
[
"expo-build-properties",
{
"ios": {
"deploymentTarget": "15.5"
}
}
]
]
}
}
}
48 changes: 46 additions & 2 deletions test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,44 @@ import Q = require("q");

import del = require("del");

function ensureAndroidCleartextTraffic(androidManifestPath: string): void {
const androidManifestContents = fs.readFileSync(androidManifestPath, "utf8");

if (androidManifestContents.includes("android:usesCleartextTraffic=\"true\"")) {
return;
}

let nextContents = androidManifestContents;

if (androidManifestContents.includes("android:usesCleartextTraffic=\"false\"")) {
nextContents = androidManifestContents.replace("android:usesCleartextTraffic=\"false\"", "android:usesCleartextTraffic=\"true\"");
} else if (/<application\b/.test(androidManifestContents)) {
nextContents = androidManifestContents.replace(/<application\b/, "<application android:usesCleartextTraffic=\"true\"");
Comment thread
kmsbernard marked this conversation as resolved.
} else {
throw new Error(`Could not find <application> tag in AndroidManifest.xml: ${androidManifestPath}`);
}

if (nextContents !== androidManifestContents) {
fs.writeFileSync(androidManifestPath, nextContents, "utf8");
}
}

function installExpoBundleTooling(projectPath: string): Q.Promise<void> {
const packageJsonPath = path.join(projectPath, "package.json");
const packageJsonContents = fs.readFileSync(packageJsonPath, "utf8");
const packageJson = JSON.parse(packageJsonContents);
const reactNativeVersion = packageJson.dependencies && packageJson.dependencies["react-native"];

if (!reactNativeVersion) {
throw new Error(`Could not determine react-native version from ${packageJsonPath}`);
}

return TestUtil.getProcessOutput(
`npm install --save-dev @react-native/metro-config@${reactNativeVersion}`,
{ cwd: projectPath }
).then(() => { return null; });
}

//////////////////////////////////////////////////////////////////////////////////////////
// Create the platforms to run the tests on.

Expand Down Expand Up @@ -86,7 +124,7 @@ class RNAndroid extends Platform.Android implements RNPlatform {
// we use hard-coded deployment key and server url in app.json
return Q.Promise<void>((resolve, reject) => {
TestUtil.replaceString(androidMainActivityPath, "\"main\"", `"${TestConfig.TestAppName}"`);
TestUtil.replaceString(AndroidManifest, "\\${usesCleartextTraffic}", "true");
ensureAndroidCleartextTraffic(AndroidManifest);
resolve(null);
});
}
Expand Down Expand Up @@ -329,11 +367,17 @@ class RNProjectManager extends ProjectManager {
mkdirp.sync(projectDirectory);

if (TestConfig.isExpoApp) {
return TestUtil.getProcessOutput(`npx create-expo-app@latest ${appName} --template blank`, { cwd: projectDirectory, timeout: 30 * 60 * 1000 })
return TestUtil.getProcessOutput(`npx create-expo-app@latest ${appName} --template blank@sdk-55`, { cwd: projectDirectory, timeout: 30 * 60 * 1000 })
.then((e) => { console.log(`"npx expo init ${appName}" success. cwd=${projectDirectory}`); return e; })
.then(this.copyTemplate.bind(this, templatePath, projectDirectory))
.then<void>(TestUtil.getProcessOutput.bind(undefined, TestConfig.thisPluginInstallString, { cwd: path.join(projectDirectory, TestConfig.TestAppName) }))
.then(installExpoBundleTooling.bind(undefined, path.join(projectDirectory, TestConfig.TestAppName)))
.then<void>(TestUtil.getProcessOutput.bind(undefined, "npx expo install expo-build-properties", { cwd: path.join(projectDirectory, TestConfig.TestAppName) }))
.then(TestUtil.getProcessOutput.bind(undefined, `npx expo prebuild --clean`, { cwd: path.join(projectDirectory, TestConfig.TestAppName) }))
.then(() => {
ensureAndroidCleartextTraffic(path.join(projectDirectory, TestConfig.TestAppName, "android", "app", "src", "main", "AndroidManifest.xml"));
return null;
})
.then(() => { return null; });
} else {
return TestUtil.getProcessOutput("npx @react-native-community/cli init " + appName + " --version 0.82.1 --install-pods", { cwd: projectDirectory, timeout: 30 * 60 * 1000 })
Expand Down
Loading