@bravemobile/react-native-code-push 10.0.0-beta.5 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CodePush.js +3 -3
- package/README.md +53 -30
- package/android/app/proguard-rules.pro +0 -4
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +14 -35
- package/app.plugin.js +1 -0
- package/cli/commands/bundleCommand/bundleCodePush.js +21 -7
- package/cli/commands/bundleCommand/index.js +3 -0
- package/cli/commands/releaseCommand/index.js +3 -0
- package/cli/commands/releaseCommand/release.js +3 -1
- package/cli/functions/runExpoBundleCommand.js +53 -0
- package/expo/plugin/withCodePush.js +15 -0
- package/expo/plugin/withCodePushAndroid.js +72 -0
- package/expo/plugin/withCodePushIos.js +143 -0
- package/package.json +12 -2
package/CodePush.js
CHANGED
|
@@ -253,11 +253,11 @@ async function tryReportStatus(statusReport, retryOnAppResume) {
|
|
|
253
253
|
const label = statusReport.package.label;
|
|
254
254
|
if (statusReport.status === "DeploymentSucceeded") {
|
|
255
255
|
log(`Reporting CodePush update success (${label})`);
|
|
256
|
-
sharedCodePushOptions?.onUpdateSuccess(label);
|
|
256
|
+
sharedCodePushOptions?.onUpdateSuccess?.(label);
|
|
257
257
|
} else {
|
|
258
258
|
log(`Reporting CodePush update rollback (${label})`);
|
|
259
259
|
await NativeCodePush.setLatestRollbackInfo(statusReport.package.packageHash);
|
|
260
|
-
sharedCodePushOptions?.onUpdateRollback(label);
|
|
260
|
+
sharedCodePushOptions?.onUpdateRollback?.(label);
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
@@ -564,7 +564,7 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
|
|
|
564
564
|
}
|
|
565
565
|
} catch (error) {
|
|
566
566
|
syncStatusChangeCallback(CodePush.SyncStatus.UNKNOWN_ERROR);
|
|
567
|
-
sharedCodePushOptions?.onSyncError(remotePackageLabel ?? 'unknown', error);
|
|
567
|
+
sharedCodePushOptions?.onSyncError?.(remotePackageLabel ?? 'unknown', error);
|
|
568
568
|
log(error.message);
|
|
569
569
|
throw error;
|
|
570
570
|
}
|
package/README.md
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
# @bravemobile/react-native-code-push
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
This package was created to continue using React Native CodePush without AppCenter.
|
|
7
|
-
|
|
8
|
-
It allows self-hosting of CodePush deployments while retaining essential operational features.
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
### Seamless Transition from AppCenter to a Fully Self-Hosted CodePush
|
|
11
4
|
|
|
5
|
+
- **No API Server Needed** – Use static hosting solutions (e.g., AWS S3) without maintaining additional API servers.
|
|
6
|
+
- **Familiar API** – Built on `microsoft/react-native-code-push`, ensuring compatibility and stability.
|
|
7
|
+
- **Flexible Deployment** – Implement your own release workflow, giving you complete control over the deployment process.
|
|
12
8
|
|
|
13
9
|
### 🚀 New Architecture support
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
| RN Version | Old Architecture | New Architecture | New Architecture Bridgeless |
|
|
18
|
-
|--------|--------|--------|--------|
|
|
19
|
-
| 0.73.11 | ✅ | ✅ | Unsupported |
|
|
20
|
-
| 0.74.7 | ✅ | ✅ | ✅ |
|
|
21
|
-
| 0.75.5 | ✅ | ✅ | ✅ |
|
|
22
|
-
| 0.76.7 | ✅ | ✅ | ✅ |
|
|
23
|
-
| 0.77.1 | ✅ | ✅ | ✅ |
|
|
24
|
-
| 0.78.0 | ✅ | ✅ | ✅ |
|
|
11
|
+
Supports React Native 0.74 ~ 0.80.
|
|
25
12
|
|
|
13
|
+
(Tested on the React Native CLI template apps)
|
|
26
14
|
|
|
27
15
|
## 🚗 Migration Guide
|
|
28
16
|
|
|
@@ -173,7 +161,31 @@ Add the following line to the end of the file.
|
|
|
173
161
|
}
|
|
174
162
|
```
|
|
175
163
|
|
|
176
|
-
### 4.
|
|
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
|
|
177
189
|
|
|
178
190
|
The root component of your app should be wrapped with a higher-order component.
|
|
179
191
|
|
|
@@ -217,7 +229,7 @@ export default CodePush({
|
|
|
217
229
|
> The URL for fetching the release history should point to the resource location generated by the CLI tool.
|
|
218
230
|
|
|
219
231
|
|
|
220
|
-
####
|
|
232
|
+
#### 5-1. Telemetry Callbacks
|
|
221
233
|
|
|
222
234
|
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.
|
|
223
235
|
- **onUpdateSuccess:** Triggered when the update bundle is executed successfully.
|
|
@@ -227,7 +239,7 @@ Please refer to the [CodePushOptions](https://github.com/Soomgo-Mobile/react-nat
|
|
|
227
239
|
- **onSyncError:** Triggered when an unknown error occurs during the update process. (`CodePush.SyncStatus.UNKNOWN_ERROR` status)
|
|
228
240
|
|
|
229
241
|
|
|
230
|
-
###
|
|
242
|
+
### 6. Configure the CLI Tool
|
|
231
243
|
|
|
232
244
|
> [!TIP]
|
|
233
245
|
> 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))
|
|
@@ -342,7 +354,7 @@ Create a new release history for a specific binary app version.
|
|
|
342
354
|
**Example:**
|
|
343
355
|
- Create a new release history for the binary app version `1.0.0`.
|
|
344
356
|
|
|
345
|
-
```
|
|
357
|
+
```bash
|
|
346
358
|
npx code-push create-history --binary-version 1.0.0 --platform ios --identifier staging
|
|
347
359
|
```
|
|
348
360
|
|
|
@@ -354,7 +366,7 @@ Display the release history for a specific binary app version.
|
|
|
354
366
|
**Example:**
|
|
355
367
|
- Show the release history for the binary app version `1.0.0`.
|
|
356
368
|
|
|
357
|
-
```
|
|
369
|
+
```bash
|
|
358
370
|
npx code-push show-history --binary-version 1.0.0 --platform ios --identifier staging
|
|
359
371
|
```
|
|
360
372
|
|
|
@@ -366,16 +378,20 @@ Release a CodePush update for a specific binary app version.
|
|
|
366
378
|
**Example:**
|
|
367
379
|
- Release a CodePush update `1.0.1` targeting the binary app version `1.0.0`.
|
|
368
380
|
|
|
369
|
-
```
|
|
370
|
-
npx code-push release --
|
|
381
|
+
```bash
|
|
382
|
+
npx code-push release --binary-version 1.0.0 --app-version 1.0.1 \
|
|
371
383
|
--platform ios --identifier staging --entry-file index.js \
|
|
372
384
|
--mandatory true
|
|
385
|
+
|
|
386
|
+
# Expo project
|
|
387
|
+
npx code-push release --framework expo --binary-version 1.0.0 --app-version 1.0.1 --platform ios
|
|
373
388
|
```
|
|
374
|
-
- `--
|
|
389
|
+
- `--framework`(`-f`) : Framework type (expo)
|
|
390
|
+
- `--binary-version`: The version of the binary app that the CodePush update is targeting.
|
|
375
391
|
- `--app-version`: The version of the CodePush update itself.
|
|
376
392
|
|
|
377
393
|
> [!IMPORTANT]
|
|
378
|
-
> `--app-version` should be greater than `--
|
|
394
|
+
> `--app-version` should be greater than `--binary-version` (SemVer comparison).
|
|
379
395
|
|
|
380
396
|
|
|
381
397
|
#### `update-history`
|
|
@@ -387,8 +403,8 @@ Update the release history for a specific CodePush update.
|
|
|
387
403
|
**Example:**
|
|
388
404
|
- Rollback the CodePush update `1.0.1` (targeting the binary app version `1.0.0`).
|
|
389
405
|
|
|
390
|
-
```
|
|
391
|
-
npx code-push update-history --
|
|
406
|
+
```bash
|
|
407
|
+
npx code-push update-history --binary-version 1.0.0 --app-version 1.0.1 \
|
|
392
408
|
--platform ios --identifier staging \
|
|
393
409
|
--enable false
|
|
394
410
|
```
|
|
@@ -398,10 +414,17 @@ npx code-push update-history --target-binary-version 1.0.0 --app-version 1.0.1 \
|
|
|
398
414
|
Create a CodePush bundle file.
|
|
399
415
|
|
|
400
416
|
**Example:**
|
|
401
|
-
```
|
|
417
|
+
```bash
|
|
402
418
|
npx code-push bundle --platform android --entry-file index.js
|
|
419
|
+
|
|
420
|
+
# Expo project
|
|
421
|
+
npx code-push bundle --framework expo --platform android --entry-file index.js
|
|
403
422
|
```
|
|
423
|
+
- `--framework`(`-f`): Framework type (expo)
|
|
404
424
|
|
|
405
425
|
By default, the bundle file is created in the `/build/bundleOutput` directory.
|
|
406
426
|
|
|
427
|
+
> [!NOTE]
|
|
428
|
+
> For Expo projects, the CLI uses `expo export:embed` command for bundling instead of React Native's bundle command.
|
|
429
|
+
|
|
407
430
|
(The file name represents a hash value of the bundle content.)
|
|
@@ -24,10 +24,6 @@
|
|
|
24
24
|
private ** mReactHost; # bridgeless
|
|
25
25
|
public void reload(...); # RN 0.74 and above
|
|
26
26
|
}
|
|
27
|
-
# RN 0.74 and above
|
|
28
|
-
-keepclassmembers class com.facebook.react.ReactActivity {
|
|
29
|
-
public ** getReactDelegate(...);
|
|
30
|
-
}
|
|
31
27
|
# bridgeless
|
|
32
28
|
-keepclassmembers class com.facebook.react.defaults.DefaultReactHostDelegate {
|
|
33
29
|
private ** jsBundleLoader;
|
|
@@ -6,6 +6,7 @@ import android.os.AsyncTask;
|
|
|
6
6
|
import android.os.Handler;
|
|
7
7
|
import android.os.Looper;
|
|
8
8
|
import android.view.View;
|
|
9
|
+
import android.view.Choreographer;
|
|
9
10
|
|
|
10
11
|
import androidx.annotation.OptIn;
|
|
11
12
|
|
|
@@ -25,7 +26,6 @@ import com.facebook.react.bridge.ReactMethod;
|
|
|
25
26
|
import com.facebook.react.bridge.ReadableMap;
|
|
26
27
|
import com.facebook.react.bridge.WritableMap;
|
|
27
28
|
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
|
|
28
|
-
import com.facebook.react.modules.core.ChoreographerCompat;
|
|
29
29
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
30
30
|
import com.facebook.react.modules.core.ReactChoreographer;
|
|
31
31
|
import com.facebook.react.runtime.ReactHostDelegate;
|
|
@@ -99,7 +99,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
private void loadBundleLegacy() {
|
|
102
|
-
final Activity currentActivity = getCurrentActivity();
|
|
102
|
+
final Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
103
103
|
if (currentActivity == null) {
|
|
104
104
|
// The currentActivity can be null if it is backgrounded / destroyed, so we simply
|
|
105
105
|
// no-op to prevent any null pointer exceptions.
|
|
@@ -128,7 +128,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
128
128
|
|
|
129
129
|
ReactHost reactHost = resolveReactHost();
|
|
130
130
|
if (reactHost == null) {
|
|
131
|
-
// Bridge, Old Architecture
|
|
131
|
+
// Bridge, Old Architecture
|
|
132
132
|
setJSBundleLoaderBridge(instanceManager, latestJSBundleLoader);
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
@@ -184,29 +184,14 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
184
184
|
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
|
185
185
|
@Override
|
|
186
186
|
public void run() {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// so, we need to check if reload method exists and call it
|
|
190
|
-
try {
|
|
191
|
-
ReactDelegate reactDelegate = resolveReactDelegate();
|
|
192
|
-
if (reactDelegate == null) {
|
|
193
|
-
throw new NoSuchMethodException("ReactDelegate doesn't have reload method in RN < 0.74");
|
|
194
|
-
}
|
|
187
|
+
ReactDelegate reactDelegate = resolveReactDelegate();
|
|
188
|
+
assert reactDelegate != null;
|
|
195
189
|
|
|
196
|
-
|
|
190
|
+
resetReactRootViews(reactDelegate);
|
|
197
191
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// RN < 0.74 calls ReactInstanceManager.recreateReactContextInBackground() directly
|
|
202
|
-
instanceManager.recreateReactContextInBackground();
|
|
203
|
-
}
|
|
204
|
-
mCodePush.initializeUpdateAfterRestart();
|
|
205
|
-
} catch (Exception e) {
|
|
206
|
-
// The recreation method threw an unknown exception
|
|
207
|
-
// so just simply fallback to restarting the Activity (if it exists)
|
|
208
|
-
loadBundleLegacy();
|
|
209
|
-
}
|
|
192
|
+
reactDelegate.reload();
|
|
193
|
+
|
|
194
|
+
mCodePush.initializeUpdateAfterRestart();
|
|
210
195
|
}
|
|
211
196
|
});
|
|
212
197
|
|
|
@@ -223,7 +208,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
223
208
|
// React Native uses the id field to track react tags and will overwrite this field.
|
|
224
209
|
// If that is fine, explicitly overwrite the id field to View.NO_ID before calling addRootView."
|
|
225
210
|
private void resetReactRootViews(ReactDelegate reactDelegate) {
|
|
226
|
-
ReactActivity currentActivity = (ReactActivity) getCurrentActivity();
|
|
211
|
+
ReactActivity currentActivity = (ReactActivity) getReactApplicationContext().getCurrentActivity();
|
|
227
212
|
if (currentActivity != null) {
|
|
228
213
|
ReactRootView reactRootView = reactDelegate.getReactRootView();
|
|
229
214
|
if (reactRootView != null) {
|
|
@@ -242,18 +227,12 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
242
227
|
}
|
|
243
228
|
|
|
244
229
|
private ReactDelegate resolveReactDelegate() {
|
|
245
|
-
ReactActivity currentActivity = (ReactActivity) getCurrentActivity();
|
|
230
|
+
ReactActivity currentActivity = (ReactActivity) getReactApplicationContext().getCurrentActivity();
|
|
246
231
|
if (currentActivity == null) {
|
|
247
232
|
return null;
|
|
248
233
|
}
|
|
249
234
|
|
|
250
|
-
|
|
251
|
-
Method getReactDelegateMethod = currentActivity.getClass().getMethod("getReactDelegate");
|
|
252
|
-
return (ReactDelegate) getReactDelegateMethod.invoke(currentActivity);
|
|
253
|
-
} catch (Exception e) {
|
|
254
|
-
// RN < 0.74 doesn't have getReactDelegate method
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
235
|
+
return currentActivity.getReactDelegate();
|
|
257
236
|
}
|
|
258
237
|
|
|
259
238
|
private ReactHost resolveReactHost() {
|
|
@@ -278,7 +257,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
278
257
|
return instanceManager;
|
|
279
258
|
}
|
|
280
259
|
|
|
281
|
-
final Activity currentActivity = getCurrentActivity();
|
|
260
|
+
final Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
282
261
|
if (currentActivity == null) {
|
|
283
262
|
return null;
|
|
284
263
|
}
|
|
@@ -390,7 +369,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
390
369
|
getReactApplicationContext().runOnUiQueueThread(new Runnable() {
|
|
391
370
|
@Override
|
|
392
371
|
public void run() {
|
|
393
|
-
ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new
|
|
372
|
+
ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new Choreographer.FrameCallback() {
|
|
394
373
|
@Override
|
|
395
374
|
public void doFrame(long frameTimeNanos) {
|
|
396
375
|
if (!latestDownloadProgress.isCompleted()) {
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("./expo/plugin/withCodePush");
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const { prepareToBundleJS } = require('../../functions/prepareToBundleJS');
|
|
3
3
|
const { runReactNativeBundleCommand } = require('../../functions/runReactNativeBundleCommand');
|
|
4
|
+
const { runExpoBundleCommand } = require('../../functions/runExpoBundleCommand');
|
|
4
5
|
const { getReactTempDir } = require('../../functions/getReactTempDir');
|
|
5
6
|
const { runHermesEmitBinaryCommand } = require('../../functions/runHermesEmitBinaryCommand');
|
|
6
7
|
const { makeCodePushBundle } = require('../../functions/makeCodePushBundle');
|
|
7
8
|
const { ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../constant');
|
|
8
9
|
|
|
9
10
|
/**
|
|
11
|
+
* @param framework {string|undefined} 'expo'
|
|
10
12
|
* @param platform {string} 'ios' | 'android'
|
|
11
13
|
* @param outputRootPath {string}
|
|
12
14
|
* @param entryFile {string}
|
|
@@ -15,6 +17,7 @@ const { ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../constant');
|
|
|
15
17
|
* @return {Promise<string>} CodePush bundle file name (equals to packageHash)
|
|
16
18
|
*/
|
|
17
19
|
async function bundleCodePush(
|
|
20
|
+
framework,
|
|
18
21
|
platform = 'ios',
|
|
19
22
|
outputRootPath = ROOT_OUTPUT_DIR,
|
|
20
23
|
entryFile = ENTRY_FILE,
|
|
@@ -32,13 +35,24 @@ async function bundleCodePush(
|
|
|
32
35
|
|
|
33
36
|
prepareToBundleJS({ deleteDirs: [outputRootPath, getReactTempDir()], makeDir: OUTPUT_CONTENT_PATH });
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (framework === 'expo') {
|
|
39
|
+
runExpoBundleCommand(
|
|
40
|
+
_jsBundleName,
|
|
41
|
+
OUTPUT_CONTENT_PATH,
|
|
42
|
+
platform,
|
|
43
|
+
SOURCEMAP_OUTPUT,
|
|
44
|
+
entryFile,
|
|
45
|
+
);
|
|
46
|
+
} else {
|
|
47
|
+
runReactNativeBundleCommand(
|
|
48
|
+
_jsBundleName,
|
|
49
|
+
OUTPUT_CONTENT_PATH,
|
|
50
|
+
platform,
|
|
51
|
+
SOURCEMAP_OUTPUT,
|
|
52
|
+
entryFile,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
console.log('log: JS bundling complete');
|
|
43
57
|
|
|
44
58
|
await runHermesEmitBinaryCommand(
|
|
@@ -4,6 +4,7 @@ const { OUTPUT_BUNDLE_DIR, ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../consta
|
|
|
4
4
|
|
|
5
5
|
program.command('bundle')
|
|
6
6
|
.description('Creates a CodePush bundle file (assumes Hermes is enabled).')
|
|
7
|
+
.addOption(new Option('-f, --framework <type>', 'framework type (expo)').choices(['expo']))
|
|
7
8
|
.addOption(new Option('-p, --platform <type>', 'platform').choices(['ios', 'android']).default('ios'))
|
|
8
9
|
.option('-o, --output-path <string>', 'path to output root directory', ROOT_OUTPUT_DIR)
|
|
9
10
|
.option('-e, --entry-file <string>', 'path to JS/TS entry file', ENTRY_FILE)
|
|
@@ -11,6 +12,7 @@ program.command('bundle')
|
|
|
11
12
|
.option('--output-bundle-dir <string>', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR)
|
|
12
13
|
/**
|
|
13
14
|
* @param {Object} options
|
|
15
|
+
* @param {string} options.framework
|
|
14
16
|
* @param {string} options.platform
|
|
15
17
|
* @param {string} options.outputPath
|
|
16
18
|
* @param {string} options.entryFile
|
|
@@ -20,6 +22,7 @@ program.command('bundle')
|
|
|
20
22
|
*/
|
|
21
23
|
.action((options) => {
|
|
22
24
|
bundleCodePush(
|
|
25
|
+
options.framework,
|
|
23
26
|
options.platform,
|
|
24
27
|
options.outputPath,
|
|
25
28
|
options.entryFile,
|
|
@@ -7,6 +7,7 @@ program.command('release')
|
|
|
7
7
|
.description('Deploys a new CodePush update for a target binary app.\nAfter creating the CodePush bundle, it uploads the file and updates the ReleaseHistory information.\n`bundleUploader`, `getReleaseHistory`, and `setReleaseHistory` functions should be implemented in the config file.')
|
|
8
8
|
.requiredOption('-b, --binary-version <string>', '(Required) The target binary version')
|
|
9
9
|
.requiredOption('-v, --app-version <string>', '(Required) The app version to be released. It must be greater than the binary version.')
|
|
10
|
+
.addOption(new Option('-f, --framework <type>', 'framework type (expo)').choices(['expo']))
|
|
10
11
|
.addOption(new Option('-p, --platform <type>', 'platform').choices(['ios', 'android']).default('ios'))
|
|
11
12
|
.option('-i, --identifier <string>', 'reserved characters to distinguish the release.')
|
|
12
13
|
.option('-c, --config <path>', 'set config file name (JS/TS)', CONFIG_FILE_NAME)
|
|
@@ -22,6 +23,7 @@ program.command('release')
|
|
|
22
23
|
* @param {Object} options
|
|
23
24
|
* @param {string} options.binaryVersion
|
|
24
25
|
* @param {string} options.appVersion
|
|
26
|
+
* @param {string} options.framework
|
|
25
27
|
* @param {string} options.platform
|
|
26
28
|
* @param {string} options.identifier
|
|
27
29
|
* @param {string} options.config
|
|
@@ -44,6 +46,7 @@ program.command('release')
|
|
|
44
46
|
config.setReleaseHistory,
|
|
45
47
|
options.binaryVersion,
|
|
46
48
|
options.appVersion,
|
|
49
|
+
options.framework,
|
|
47
50
|
options.platform,
|
|
48
51
|
options.identifier,
|
|
49
52
|
options.outputPath,
|
|
@@ -25,6 +25,7 @@ const { addToReleaseHistory } = require("./addToReleaseHistory");
|
|
|
25
25
|
* ): Promise<void>}
|
|
26
26
|
* @param binaryVersion {string}
|
|
27
27
|
* @param appVersion {string}
|
|
28
|
+
* @param framework {string|undefined} 'expo'
|
|
28
29
|
* @param platform {"ios" | "android"}
|
|
29
30
|
* @param identifier {string?}
|
|
30
31
|
* @param outputPath {string}
|
|
@@ -43,6 +44,7 @@ async function release(
|
|
|
43
44
|
setReleaseHistory,
|
|
44
45
|
binaryVersion,
|
|
45
46
|
appVersion,
|
|
47
|
+
framework,
|
|
46
48
|
platform,
|
|
47
49
|
identifier,
|
|
48
50
|
outputPath,
|
|
@@ -56,7 +58,7 @@ async function release(
|
|
|
56
58
|
) {
|
|
57
59
|
const bundleFileName = skipBundle
|
|
58
60
|
? readBundleFileNameFrom(bundleDirectory)
|
|
59
|
-
: await bundleCodePush(platform, outputPath, entryFile, jsBundleName, bundleDirectory);
|
|
61
|
+
: await bundleCodePush(framework, platform, outputPath, entryFile, jsBundleName, bundleDirectory);
|
|
60
62
|
const bundleFilePath = `${bundleDirectory}/${bundleFileName}`;
|
|
61
63
|
|
|
62
64
|
const downloadUrl = await (async () => {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const shell = require('shelljs');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Run `expo bundle` CLI command
|
|
6
|
+
*
|
|
7
|
+
* @param bundleName {string} JS bundle file name
|
|
8
|
+
* @param entryFile {string} App code entry file name (default: index.ts)
|
|
9
|
+
* @param outputPath {string} Path to output JS bundle file and assets
|
|
10
|
+
* @param platform {string} Platform (ios | android)
|
|
11
|
+
* @param sourcemapOutput {string} Path to output sourcemap file (Warning: if sourcemapOutput points to the outputPath, the sourcemap will be included in the CodePush bundle and increase the deployment size)
|
|
12
|
+
* @return {void}
|
|
13
|
+
*/
|
|
14
|
+
function runExpoBundleCommand(
|
|
15
|
+
bundleName,
|
|
16
|
+
outputPath,
|
|
17
|
+
platform,
|
|
18
|
+
sourcemapOutput,
|
|
19
|
+
entryFile,
|
|
20
|
+
) {
|
|
21
|
+
/**
|
|
22
|
+
* @return {string}
|
|
23
|
+
*/
|
|
24
|
+
function getCliPath() {
|
|
25
|
+
return path.join('node_modules', '.bin', 'expo');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @type {string[]}
|
|
30
|
+
*/
|
|
31
|
+
const expoBundleArgs = [
|
|
32
|
+
'export:embed',
|
|
33
|
+
'--assets-dest',
|
|
34
|
+
outputPath,
|
|
35
|
+
'--bundle-output',
|
|
36
|
+
path.join(outputPath, bundleName),
|
|
37
|
+
'--dev',
|
|
38
|
+
'false',
|
|
39
|
+
'--entry-file',
|
|
40
|
+
entryFile,
|
|
41
|
+
'--platform',
|
|
42
|
+
platform,
|
|
43
|
+
'--sourcemap-output',
|
|
44
|
+
sourcemapOutput,
|
|
45
|
+
'--reset-cache',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
console.log('Running "expo export:embed" command:\n');
|
|
49
|
+
|
|
50
|
+
shell.exec(`${getCliPath()} ${expoBundleArgs.join(' ')}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { runExpoBundleCommand };
|
|
@@ -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);
|
|
@@ -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
|
+
};
|
|
@@ -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/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bravemobile/react-native-code-push",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"description": "React Native plugin for the CodePush service",
|
|
5
5
|
"main": "CodePush.js",
|
|
6
6
|
"typings": "typings/react-native-code-push.d.ts",
|
|
7
7
|
"homepage": "https://microsoft.github.io/code-push",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"react-native",
|
|
10
|
+
"expo",
|
|
10
11
|
"code",
|
|
11
|
-
"push"
|
|
12
|
+
"push",
|
|
13
|
+
"code-push",
|
|
14
|
+
"react-native-code-push",
|
|
15
|
+
"expo-code-push"
|
|
12
16
|
],
|
|
13
17
|
"author": "Soomgo Mobile Team (originally Microsoft Corporation)",
|
|
14
18
|
"license": "MIT",
|
|
@@ -48,8 +52,14 @@
|
|
|
48
52
|
"yazl": "^3.3.1"
|
|
49
53
|
},
|
|
50
54
|
"peerDependencies": {
|
|
55
|
+
"expo": ">=50.0.0",
|
|
51
56
|
"react-native": "*"
|
|
52
57
|
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"expo": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
53
63
|
"devDependencies": {
|
|
54
64
|
"@babel/core": "^7.26.0",
|
|
55
65
|
"@babel/preset-env": "^7.26.0",
|