Skip to content

Commit 3ca1fc8

Browse files
authored
Merge pull request #74 from saseungmin/feat/expo-plugin
feat(expo): Add Expo config plugin for automated CodePush setup
2 parents 6bc0236 + 4116589 commit 3ca1fc8

File tree

7 files changed

+275
-4
lines changed

7 files changed

+275
-4
lines changed

README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,31 @@ Add the following line to the end of the file.
161161
}
162162
```
163163

164-
### 4. "CodePush-ify" Your App
164+
### 4. Expo Setup
165+
For Expo projects, you can use the automated config plugin instead of manual setup.
166+
167+
**Add plugin to your Expo configuration:**
168+
```js
169+
// app.config.js
170+
export default {
171+
expo: {
172+
plugins: ["@bravemobile/react-native-code-push"],
173+
},
174+
};
175+
```
176+
177+
**Run prebuild to apply changes:**
178+
```bash
179+
npx expo prebuild
180+
```
181+
182+
> [!NOTE]
183+
> The plugin automatically handles all native iOS and Android code modifications. No manual editing of AppDelegate, MainApplication, or gradle files is required.
184+
185+
**Requirements**
186+
Expo SDK: 50.0.0 or higher
187+
188+
### 5. "CodePush-ify" Your App
165189

166190
The root component of your app should be wrapped with a higher-order component.
167191

@@ -205,7 +229,7 @@ export default CodePush({
205229
> The URL for fetching the release history should point to the resource location generated by the CLI tool.
206230
207231

208-
#### 4-1. Telemetry Callbacks
232+
#### 5-1. Telemetry Callbacks
209233

210234
Please refer to the [CodePushOptions](https://github.com/Soomgo-Mobile/react-native-code-push/blob/f0d26f7614af41c6dd4daecd9f7146e2383b2b0d/typings/react-native-code-push.d.ts#L76-L95) type for more details.
211235
- **onUpdateSuccess:** Triggered when the update bundle is executed successfully.
@@ -215,7 +239,7 @@ Please refer to the [CodePushOptions](https://github.com/Soomgo-Mobile/react-nat
215239
- **onSyncError:** Triggered when an unknown error occurs during the update process. (`CodePush.SyncStatus.UNKNOWN_ERROR` status)
216240

217241

218-
### 5. Configure the CLI Tool
242+
### 6. Configure the CLI Tool
219243

220244
> [!TIP]
221245
> For a more detailed and practical example, refer to the `CodePushDemoApp` in `example` directory. ([link](https://github.com/Soomgo-Mobile/react-native-code-push/tree/master/Examples/CodePushDemoApp))

app.plugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require("./expo/plugin/withCodePush");

expo/plugin/withCodePush.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const { createRunOncePlugin } = require('expo/config-plugins');
2+
const { withAndroidBuildScriptDependency, withAndroidMainApplicationDependency } = require('./withCodePushAndroid');
3+
const { withIosBridgingHeader, withIosAppDelegateDependency } = require('./withCodePushIos');
4+
const pkg = require('../../package.json');
5+
6+
const withCodePush = (config) => {
7+
config = withAndroidBuildScriptDependency(config);
8+
config = withAndroidMainApplicationDependency(config);
9+
config = withIosBridgingHeader(config);
10+
config = withIosAppDelegateDependency(config);
11+
12+
return config;
13+
};
14+
15+
module.exports = createRunOncePlugin(withCodePush, pkg.name, pkg.version);

expo/plugin/withCodePushAndroid.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
const { withAppBuildGradle, withMainApplication, WarningAggregator } = require('expo/config-plugins');
2+
3+
function androidApplyImplementation(appBuildGradle) {
4+
const codePushImplementation = `apply from: "../../node_modules/@bravemobile/react-native-code-push/android/codepush.gradle"`;
5+
6+
if (!appBuildGradle.includes(codePushImplementation)) {
7+
return `${appBuildGradle.trimEnd()}\n${codePushImplementation}\n`;
8+
}
9+
10+
return appBuildGradle;
11+
}
12+
13+
function androidMainApplicationApplyImplementation(
14+
mainApplication,
15+
find,
16+
add,
17+
reverse = false,
18+
) {
19+
if (mainApplication.includes(add)) {
20+
return mainApplication;
21+
}
22+
23+
if (mainApplication.includes(find)) {
24+
return mainApplication.replace(find, reverse ? `${add}\n${find}` : `${find}\n${add}`);
25+
}
26+
27+
WarningAggregator.addWarningAndroid(
28+
'withCodePushAndroid',
29+
`
30+
Failed to detect "${find.replace(/\n/g, '').trim()}" in the MainApplication.kt.
31+
Please add "${add.replace(/\n/g, '').trim()}" to the MainApplication.kt.
32+
Supported format: Expo SDK default template.
33+
34+
Android manual setup: https://github.com/Soomgo-Mobile/react-native-code-push#3-android-setup
35+
`,
36+
);
37+
38+
return mainApplication;
39+
}
40+
41+
const withAndroidBuildScriptDependency = (config) => {
42+
return withAppBuildGradle(config, (action) => {
43+
action.modResults.contents = androidApplyImplementation(
44+
action.modResults.contents,
45+
);
46+
47+
return action;
48+
});
49+
};
50+
51+
const withAndroidMainApplicationDependency = (config) => {
52+
return withMainApplication(config, (action) => {
53+
action.modResults.contents = androidMainApplicationApplyImplementation(
54+
action.modResults.contents,
55+
'class MainApplication : Application(), ReactApplication {',
56+
'import com.microsoft.codepush.react.CodePush\n',
57+
true,
58+
);
59+
60+
action.modResults.contents = androidMainApplicationApplyImplementation(
61+
action.modResults.contents,
62+
'object : DefaultReactNativeHost(this) {',
63+
' override fun getJSBundleFile(): String = CodePush.getJSBundleFile()\n',
64+
);
65+
return action;
66+
});
67+
};
68+
69+
module.exports = {
70+
withAndroidBuildScriptDependency,
71+
withAndroidMainApplicationDependency,
72+
};

expo/plugin/withCodePushIos.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
const { withAppDelegate, withXcodeProject, WarningAggregator } = require('expo/config-plugins');
2+
const { getAppDelegate } = require('@expo/config-plugins/build/ios/Paths');
3+
const path = require('path');
4+
const fs = require('fs');
5+
6+
function iosApplyImplementation(
7+
appDelegate,
8+
find,
9+
add,
10+
replace,
11+
) {
12+
if (appDelegate.includes(add)) {
13+
return appDelegate;
14+
}
15+
16+
if (appDelegate.includes(find)) {
17+
return appDelegate.replace(find, replace ? add : `${find}\n${add}`);
18+
}
19+
20+
WarningAggregator.addWarningIOS(
21+
'withCodePushIos',
22+
`
23+
Failed to detect "${find.replace(/\n/g, '').trim()}" in the AppDelegate.(m|swift).
24+
Please ${replace ? 'replace' : 'add'} "${add.replace(/\n/g, '').trim()}" to the AppDelegate.(m|swift).
25+
Supported format: Expo SDK default template.
26+
27+
iOS manual setup: https://github.com/Soomgo-Mobile/react-native-code-push#2-ios-setup
28+
`,
29+
);
30+
31+
return appDelegate;
32+
}
33+
34+
function getBridgingHeaderFileFromXcode(project) {
35+
const buildConfigs = project.pbxXCBuildConfigurationSection();
36+
37+
for (const key in buildConfigs) {
38+
const config = buildConfigs[key];
39+
if (
40+
typeof config === 'object' &&
41+
config.buildSettings &&
42+
config.buildSettings['SWIFT_OBJC_BRIDGING_HEADER']
43+
) {
44+
const bridgingHeaderFile = config.buildSettings[
45+
'SWIFT_OBJC_BRIDGING_HEADER'
46+
].replace(/"/g, '');
47+
48+
return bridgingHeaderFile;
49+
}
50+
}
51+
return null;
52+
}
53+
54+
const withIosAppDelegateDependency = (config) => {
55+
return withAppDelegate(config, (action) => {
56+
const language = action.modResults.language;
57+
58+
if (['objc', 'objcpp'].includes(language)) {
59+
action.modResults.contents = iosApplyImplementation(
60+
action.modResults.contents,
61+
`#import "AppDelegate.h"`,
62+
`#import <CodePush/CodePush.h>`,
63+
);
64+
action.modResults.contents = iosApplyImplementation(
65+
action.modResults.contents,
66+
`return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];`,
67+
`return [CodePush bundleURL];`,
68+
true,
69+
);
70+
71+
return action;
72+
}
73+
74+
if (language === 'swift') {
75+
action.modResults.contents = iosApplyImplementation(
76+
action.modResults.contents,
77+
`return Bundle.main.url(forResource: "main", withExtension: "jsbundle")`,
78+
`return CodePush.bundleURL()`,
79+
true,
80+
);
81+
82+
return action;
83+
}
84+
85+
WarningAggregator.addWarningIOS(
86+
'withIosAppDelegate',
87+
`${language} AppDelegate file is not supported yet.`,
88+
);
89+
90+
return action;
91+
});
92+
};
93+
94+
const withIosBridgingHeader = (config) => {
95+
return withXcodeProject(config, (action) => {
96+
const projectRoot = action.modRequest.projectRoot;
97+
const appDelegate = getAppDelegate(projectRoot);
98+
99+
if (appDelegate.language === 'swift') {
100+
const bridgingHeaderFile = getBridgingHeaderFileFromXcode(
101+
action.modResults,
102+
);
103+
104+
const bridgingHeaderPath = path.join(
105+
action.modRequest.platformProjectRoot,
106+
bridgingHeaderFile,
107+
);
108+
109+
if (fs.existsSync(bridgingHeaderPath)) {
110+
let content = fs.readFileSync(bridgingHeaderPath, 'utf8');
111+
const codePushImport = '#import <CodePush/CodePush.h>';
112+
113+
if (!content.includes(codePushImport)) {
114+
content += `${codePushImport}\n`;
115+
fs.writeFileSync(bridgingHeaderPath, content);
116+
}
117+
118+
return action;
119+
}
120+
121+
WarningAggregator.addWarningIOS(
122+
'withIosBridgingHeader',
123+
`
124+
Failed to detect ${bridgingHeaderPath} file.
125+
Please add CodePush integration manually:
126+
#import <CodePush/CodePush.h>
127+
128+
Supported format: Expo SDK default template.
129+
iOS manual setup: https://github.com/Soomgo-Mobile/react-native-code-push#2-edit-appdelegate-code
130+
`
131+
);
132+
133+
return action;
134+
}
135+
136+
return action;
137+
});
138+
};
139+
140+
module.exports = {
141+
withIosAppDelegateDependency,
142+
withIosBridgingHeader,
143+
};

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
"homepage": "https://microsoft.github.io/code-push",
88
"keywords": [
99
"react-native",
10+
"expo",
1011
"code",
11-
"push"
12+
"push",
13+
"code-push",
14+
"react-native-code-push",
15+
"expo-code-push"
1216
],
1317
"author": "Soomgo Mobile Team (originally Microsoft Corporation)",
1418
"license": "MIT",
@@ -48,8 +52,14 @@
4852
"yazl": "^3.3.1"
4953
},
5054
"peerDependencies": {
55+
"expo": ">=50.0.0",
5156
"react-native": "*"
5257
},
58+
"peerDependenciesMeta": {
59+
"expo": {
60+
"optional": true
61+
}
62+
},
5363
"devDependencies": {
5464
"@babel/core": "^7.26.0",
5565
"@babel/preset-env": "^7.26.0",

0 commit comments

Comments
 (0)