@bravemobile/react-native-code-push 12.0.2 → 12.1.1
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/README.md +1 -1
- package/android/app/src/debug/AndroidManifest.xml +9 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +1 -52
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +4 -43
- package/bin/code-push.js +6 -0
- package/cli/commands/bundleCommand/{bundleCodePush.js → bundleCodePush.ts} +16 -24
- package/cli/commands/bundleCommand/{index.js → index.ts} +13 -14
- package/cli/commands/createHistoryCommand/{createReleaseHistory.js → createReleaseHistory.ts} +11 -28
- package/cli/commands/createHistoryCommand/{index.js → index.ts} +12 -13
- package/cli/commands/initCommand/{index.js → index.ts} +3 -3
- package/cli/commands/initCommand/{initAndroid.js → initAndroid.ts} +6 -11
- package/cli/commands/initCommand/initIos.ts +123 -0
- package/cli/commands/initCommand/test/initAndroid.test.ts +86 -0
- package/cli/commands/initCommand/test/initIos.test.ts +99 -0
- package/cli/commands/releaseCommand/{addToReleaseHistory.js → addToReleaseHistory.ts} +17 -45
- package/cli/commands/releaseCommand/{index.js → index.ts} +24 -25
- package/cli/commands/releaseCommand/release.ts +72 -0
- package/cli/commands/showHistoryCommand/{index.js → index.ts} +11 -12
- package/cli/commands/updateHistoryCommand/{index.js → index.ts} +17 -18
- package/cli/commands/updateHistoryCommand/{updateReleaseHistory.js → updateReleaseHistory.ts} +15 -41
- package/cli/constant.ts +4 -0
- package/cli/dist/commands/bundleCommand/bundleCodePush.js +34 -0
- package/cli/dist/commands/bundleCommand/index.js +14 -0
- package/cli/dist/commands/createHistoryCommand/createReleaseHistory.js +25 -0
- package/cli/dist/commands/createHistoryCommand/index.js +14 -0
- package/cli/dist/commands/initCommand/index.js +12 -0
- package/cli/dist/commands/initCommand/initAndroid.js +37 -0
- package/cli/{commands → dist/commands}/initCommand/initIos.js +13 -33
- package/cli/dist/commands/initCommand/test/initAndroid.test.js +75 -0
- package/cli/dist/commands/initCommand/test/initIos.test.js +95 -0
- package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +32 -0
- package/cli/dist/commands/releaseCommand/index.js +38 -0
- package/cli/dist/commands/releaseCommand/release.js +36 -0
- package/cli/dist/commands/showHistoryCommand/index.js +14 -0
- package/cli/dist/commands/updateHistoryCommand/index.js +30 -0
- package/cli/dist/commands/updateHistoryCommand/updateReleaseHistory.js +26 -0
- package/cli/dist/constant.js +4 -0
- package/cli/dist/functions/getReactTempDir.js +10 -0
- package/cli/{functions → dist/functions}/makeCodePushBundle.js +5 -11
- package/cli/{functions → dist/functions}/prepareToBundleJS.js +2 -5
- package/cli/{functions → dist/functions}/runExpoBundleCommand.js +3 -21
- package/cli/{functions → dist/functions}/runHermesEmitBinaryCommand.js +12 -68
- package/cli/{functions → dist/functions}/runReactNativeBundleCommand.js +3 -23
- package/cli/dist/index.js +38 -0
- package/cli/dist/utils/file-utils.js +19 -0
- package/cli/dist/utils/fsUtils.js +37 -0
- package/cli/{utils → dist/utils}/hash-utils.js +19 -127
- package/cli/{utils → dist/utils}/promisfied-fs.js +5 -16
- package/cli/dist/utils/showLogo.js +21 -0
- package/cli/{utils → dist/utils}/zip.js +15 -51
- package/cli/functions/{getReactTempDir.js → getReactTempDir.ts} +2 -6
- package/cli/functions/makeCodePushBundle.ts +26 -0
- package/cli/functions/prepareToBundleJS.ts +10 -0
- package/cli/functions/runExpoBundleCommand.ts +45 -0
- package/cli/functions/runHermesEmitBinaryCommand.ts +186 -0
- package/cli/functions/runReactNativeBundleCommand.ts +51 -0
- package/cli/index.ts +48 -0
- package/cli/package.json +33 -0
- package/cli/utils/{file-utils.js → file-utils.ts} +4 -21
- package/cli/utils/{fsUtils.js → fsUtils.ts} +12 -19
- package/cli/utils/hash-utils.ts +146 -0
- package/cli/utils/promisfied-fs.ts +19 -0
- package/cli/utils/{showLogo.js → showLogo.ts} +1 -3
- package/cli/utils/zip.ts +65 -0
- package/package.json +42 -12
- package/{AlertAdapter.js → src/AlertAdapter.js} +5 -5
- package/AGENTS.md +0 -32
- package/CONTRIBUTING.md +0 -134
- package/SECURITY.md +0 -41
- package/android/app/src/main/java/com/microsoft/codepush/react/ReactInstanceHolder.java +0 -17
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -5
- package/android/gradlew +0 -164
- package/android/gradlew.bat +0 -90
- package/babel.config.js +0 -3
- package/cli/commands/releaseCommand/release.js +0 -114
- package/cli/constant.js +0 -6
- package/cli/index.js +0 -49
- package/docs/api-android.md +0 -83
- package/docs/api-ios.md +0 -31
- package/docs/api-js.md +0 -592
- package/docs/multi-deployment-testing-android.md +0 -148
- package/docs/multi-deployment-testing-ios.md +0 -59
- package/eslint.config.mjs +0 -32
- package/scripts/generateBundledResourcesHash.js +0 -125
- package/scripts/getFilesInFolder.js +0 -19
- package/scripts/recordFilesBeforeBundleCommand.js +0 -41
- package/tsconfig.json +0 -18
- package/tslint.json +0 -32
- /package/{CodePush.js → src/CodePush.js} +0 -0
- /package/{logging.js → src/logging.js} +0 -0
- /package/{package-mixins.js → src/package-mixins.js} +0 -0
- /package/{versioning → src/versioning}/BaseVersioning.js +0 -0
- /package/{versioning → src/versioning}/BaseVersioning.test.js +0 -0
- /package/{versioning → src/versioning}/IncrementalVersioning.js +0 -0
- /package/{versioning → src/versioning}/IncrementalVersioning.test.js +0 -0
- /package/{versioning → src/versioning}/SemverVersioning.js +0 -0
- /package/{versioning → src/versioning}/SemverVersioning.test.js +0 -0
- /package/{versioning → src/versioning}/index.js +0 -0
package/README.md
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
|
|
3
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
4
|
+
|
|
5
|
+
<application>
|
|
6
|
+
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
|
7
|
+
</application>
|
|
8
|
+
|
|
9
|
+
</manifest>
|
|
@@ -5,22 +5,17 @@ import android.content.pm.PackageInfo;
|
|
|
5
5
|
import android.content.pm.PackageManager;
|
|
6
6
|
import android.content.res.Resources;
|
|
7
7
|
|
|
8
|
-
import com.facebook.react.ReactInstanceManager;
|
|
9
8
|
import com.facebook.react.ReactPackage;
|
|
10
9
|
import com.facebook.react.bridge.JavaScriptModule;
|
|
11
10
|
import com.facebook.react.bridge.NativeModule;
|
|
12
11
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
13
|
-
import com.facebook.react.devsupport.interfaces.DevSupportManager;
|
|
14
|
-
import com.facebook.react.modules.debug.interfaces.DeveloperSettings;
|
|
15
12
|
import com.facebook.react.uimanager.ViewManager;
|
|
16
13
|
|
|
17
14
|
import org.json.JSONException;
|
|
18
15
|
import org.json.JSONObject;
|
|
19
16
|
|
|
20
|
-
import java.io.File;
|
|
21
17
|
import java.util.ArrayList;
|
|
22
18
|
import java.util.List;
|
|
23
|
-
import java.lang.reflect.Method;
|
|
24
19
|
|
|
25
20
|
public class CodePush implements ReactPackage {
|
|
26
21
|
|
|
@@ -31,7 +26,7 @@ public class CodePush implements ReactPackage {
|
|
|
31
26
|
|
|
32
27
|
private boolean mDidUpdate = false;
|
|
33
28
|
|
|
34
|
-
private String mAssetsBundleFileName;
|
|
29
|
+
private String mAssetsBundleFileName = CodePushConstants.DEFAULT_JS_BUNDLE_NAME;
|
|
35
30
|
|
|
36
31
|
// Helper classes.
|
|
37
32
|
private CodePushUpdateManager mUpdateManager;
|
|
@@ -47,7 +42,6 @@ public class CodePush implements ReactPackage {
|
|
|
47
42
|
|
|
48
43
|
private static String mPublicKey;
|
|
49
44
|
|
|
50
|
-
private static ReactInstanceHolder mReactInstanceHolder;
|
|
51
45
|
private static CodePush mCurrentInstance;
|
|
52
46
|
|
|
53
47
|
public static String getServiceUrl() {
|
|
@@ -87,7 +81,6 @@ public class CodePush implements ReactPackage {
|
|
|
87
81
|
String serverUrlFromStrings = getCustomPropertyFromStringsIfExist("ServerUrl");
|
|
88
82
|
if (serverUrlFromStrings != null) mServerUrl = serverUrlFromStrings;
|
|
89
83
|
|
|
90
|
-
clearDebugCacheIfNeeded(null);
|
|
91
84
|
initializeUpdateAfterRestart();
|
|
92
85
|
}
|
|
93
86
|
|
|
@@ -128,39 +121,6 @@ public class CodePush implements ReactPackage {
|
|
|
128
121
|
return null;
|
|
129
122
|
}
|
|
130
123
|
|
|
131
|
-
private boolean isLiveReloadEnabled(ReactInstanceManager instanceManager) {
|
|
132
|
-
// Use instanceManager for checking if we use LiveReload mode. In this case we should not remove ReactNativeDevBundle.js file
|
|
133
|
-
// because we get error with trying to get this after reloading. Issue: https://github.com/microsoft/react-native-code-push/issues/1272
|
|
134
|
-
if (instanceManager != null) {
|
|
135
|
-
DevSupportManager devSupportManager = instanceManager.getDevSupportManager();
|
|
136
|
-
if (devSupportManager != null) {
|
|
137
|
-
DeveloperSettings devSettings = devSupportManager.getDevSettings();
|
|
138
|
-
Method[] methods = devSettings.getClass().getMethods();
|
|
139
|
-
for (Method m : methods) {
|
|
140
|
-
if (m.getName().equals("isReloadOnJSChangeEnabled")) {
|
|
141
|
-
try {
|
|
142
|
-
return (boolean) m.invoke(devSettings);
|
|
143
|
-
} catch (Exception x) {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
public void clearDebugCacheIfNeeded(ReactInstanceManager instanceManager) {
|
|
155
|
-
if (mIsDebugMode && mSettingsManager.isPendingUpdate(null) && !isLiveReloadEnabled(instanceManager)) {
|
|
156
|
-
// This needs to be kept in sync with https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java#L78
|
|
157
|
-
File cachedDevBundle = new File(mContext.getFilesDir(), "ReactNativeDevBundle.js");
|
|
158
|
-
if (cachedDevBundle.exists()) {
|
|
159
|
-
cachedDevBundle.delete();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
124
|
public boolean didUpdate() {
|
|
165
125
|
return mDidUpdate;
|
|
166
126
|
}
|
|
@@ -368,17 +328,6 @@ public class CodePush implements ReactPackage {
|
|
|
368
328
|
mSettingsManager.removeFailedUpdates();
|
|
369
329
|
}
|
|
370
330
|
|
|
371
|
-
public static void setReactInstanceHolder(ReactInstanceHolder reactInstanceHolder) {
|
|
372
|
-
mReactInstanceHolder = reactInstanceHolder;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
static ReactInstanceManager getReactInstanceManager() {
|
|
376
|
-
if (mReactInstanceHolder == null) {
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
return mReactInstanceHolder.getReactInstanceManager();
|
|
380
|
-
}
|
|
381
|
-
|
|
382
331
|
@Override
|
|
383
332
|
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
|
|
384
333
|
CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager);
|
|
@@ -10,7 +10,6 @@ import android.view.Choreographer;
|
|
|
10
10
|
|
|
11
11
|
import androidx.annotation.OptIn;
|
|
12
12
|
|
|
13
|
-
import com.facebook.react.ReactApplication;
|
|
14
13
|
import com.facebook.react.ReactDelegate;
|
|
15
14
|
import com.facebook.react.ReactHost;
|
|
16
15
|
import com.facebook.react.ReactInstanceManager;
|
|
@@ -116,7 +115,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
116
115
|
|
|
117
116
|
// Use reflection to find and set the appropriate fields on ReactInstanceManager. See #556 for a proposal for a less brittle way
|
|
118
117
|
// to approach this.
|
|
119
|
-
private void setJSBundle(
|
|
118
|
+
private void setJSBundle(String latestJSBundleFile) throws IllegalAccessException {
|
|
120
119
|
try {
|
|
121
120
|
JSBundleLoader latestJSBundleLoader;
|
|
122
121
|
if (latestJSBundleFile.toLowerCase().startsWith("assets://")) {
|
|
@@ -128,7 +127,6 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
128
127
|
ReactHost reactHost = resolveReactHost();
|
|
129
128
|
if (reactHost == null) {
|
|
130
129
|
// Bridge, Old Architecture
|
|
131
|
-
setJSBundleLoaderBridge(instanceManager, latestJSBundleLoader);
|
|
132
130
|
return;
|
|
133
131
|
}
|
|
134
132
|
|
|
@@ -140,12 +138,6 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
140
138
|
}
|
|
141
139
|
}
|
|
142
140
|
|
|
143
|
-
private void setJSBundleLoaderBridge(ReactInstanceManager instanceManager, JSBundleLoader latestJSBundleLoader) throws NoSuchFieldException, IllegalAccessException {
|
|
144
|
-
Field bundleLoaderField = instanceManager.getClass().getDeclaredField("mBundleLoader");
|
|
145
|
-
bundleLoaderField.setAccessible(true);
|
|
146
|
-
bundleLoaderField.set(instanceManager, latestJSBundleLoader);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
141
|
@OptIn(markerClass = UnstableReactNativeAPI.class)
|
|
150
142
|
private void setJSBundleLoaderBridgeless(ReactHost reactHost, JSBundleLoader latestJSBundleLoader) throws NoSuchFieldException, IllegalAccessException {
|
|
151
143
|
Field mReactHostDelegateField = reactHost.getClass().getDeclaredField("mReactHostDelegate");
|
|
@@ -159,27 +151,14 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
159
151
|
|
|
160
152
|
private void loadBundle() {
|
|
161
153
|
clearLifecycleEventListener();
|
|
162
|
-
try {
|
|
163
|
-
mCodePush.clearDebugCacheIfNeeded(resolveInstanceManager());
|
|
164
|
-
} catch(Exception e) {
|
|
165
|
-
// If we got error in out reflection we should clear debug cache anyway.
|
|
166
|
-
mCodePush.clearDebugCacheIfNeeded(null);
|
|
167
|
-
}
|
|
168
154
|
|
|
169
155
|
try {
|
|
170
|
-
// #1) Get the ReactInstanceManager instance, which is what includes the
|
|
171
|
-
// logic to reload the current React context.
|
|
172
|
-
final ReactInstanceManager instanceManager = resolveInstanceManager();
|
|
173
|
-
if (instanceManager == null) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
156
|
String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName());
|
|
178
157
|
|
|
179
|
-
// #
|
|
180
|
-
setJSBundle(
|
|
158
|
+
// #1) Update the locally stored JS bundle file path
|
|
159
|
+
setJSBundle(latestJSBundleFile);
|
|
181
160
|
|
|
182
|
-
// #
|
|
161
|
+
// #2) Get the context creation method and fire it on the UI thread (which RN enforces)
|
|
183
162
|
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
|
184
163
|
@Override
|
|
185
164
|
public void run() {
|
|
@@ -249,24 +228,6 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
249
228
|
}
|
|
250
229
|
}
|
|
251
230
|
|
|
252
|
-
// Use reflection to find the ReactInstanceManager. See #556 for a proposal for a less brittle way to approach this.
|
|
253
|
-
private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldException, IllegalAccessException {
|
|
254
|
-
ReactInstanceManager instanceManager = CodePush.getReactInstanceManager();
|
|
255
|
-
if (instanceManager != null) {
|
|
256
|
-
return instanceManager;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
final Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
260
|
-
if (currentActivity == null) {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
ReactApplication reactApplication = (ReactApplication) currentActivity.getApplication();
|
|
265
|
-
instanceManager = reactApplication.getReactNativeHost().getReactInstanceManager();
|
|
266
|
-
|
|
267
|
-
return instanceManager;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
231
|
private void restartAppInternal(boolean onlyIfUpdateIsPending) {
|
|
271
232
|
if (this._restartInProgress) {
|
|
272
233
|
CodePushUtils.log("Restart request queued until the current restart is completed");
|
package/bin/code-push.js
ADDED
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { prepareToBundleJS } from "../../functions/prepareToBundleJS.js";
|
|
3
|
+
import { runReactNativeBundleCommand } from "../../functions/runReactNativeBundleCommand.js";
|
|
4
|
+
import { runExpoBundleCommand } from "../../functions/runExpoBundleCommand.js";
|
|
5
|
+
import { getReactTempDir } from "../../functions/getReactTempDir.js";
|
|
6
|
+
import { runHermesEmitBinaryCommand } from "../../functions/runHermesEmitBinaryCommand.js";
|
|
7
|
+
import { makeCodePushBundle } from "../../functions/makeCodePushBundle.js";
|
|
8
|
+
import { ROOT_OUTPUT_DIR, ENTRY_FILE } from "../../constant.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* @param framework {string|undefined} 'expo'
|
|
12
|
-
* @param platform {string} 'ios' | 'android'
|
|
13
|
-
* @param outputRootPath {string}
|
|
14
|
-
* @param entryFile {string}
|
|
15
|
-
* @param jsBundleName {string|undefined}
|
|
16
|
-
* @param bundleDirectory {string}
|
|
17
11
|
* @return {Promise<string>} CodePush bundle file name (equals to packageHash)
|
|
18
12
|
*/
|
|
19
|
-
async function bundleCodePush(
|
|
20
|
-
framework,
|
|
21
|
-
platform = 'ios',
|
|
22
|
-
outputRootPath = ROOT_OUTPUT_DIR,
|
|
23
|
-
entryFile = ENTRY_FILE,
|
|
24
|
-
jsBundleName, // JS bundle file name (not CodePush bundle file)
|
|
25
|
-
bundleDirectory, // CodePush bundle output directory
|
|
26
|
-
) {
|
|
13
|
+
export async function bundleCodePush(
|
|
14
|
+
framework: 'expo' | undefined,
|
|
15
|
+
platform: 'ios' | 'android' = 'ios',
|
|
16
|
+
outputRootPath: string = ROOT_OUTPUT_DIR,
|
|
17
|
+
entryFile: string = ENTRY_FILE,
|
|
18
|
+
jsBundleName: string, // JS bundle file name (not CodePush bundle file)
|
|
19
|
+
bundleDirectory: string, // CodePush bundle output directory
|
|
20
|
+
): Promise<string> {
|
|
27
21
|
if (fs.existsSync(outputRootPath)) {
|
|
28
22
|
fs.rmSync(outputRootPath, { recursive: true });
|
|
29
23
|
}
|
|
@@ -67,5 +61,3 @@ async function bundleCodePush(
|
|
|
67
61
|
|
|
68
62
|
return codePushBundleFileName;
|
|
69
63
|
}
|
|
70
|
-
|
|
71
|
-
module.exports = { bundleCodePush: bundleCodePush };
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { program, Option } from "commander";
|
|
2
|
+
import { bundleCodePush } from "./bundleCodePush.js";
|
|
3
|
+
import { OUTPUT_BUNDLE_DIR, ROOT_OUTPUT_DIR, ENTRY_FILE } from "../../constant.js";
|
|
4
|
+
|
|
5
|
+
type Options = {
|
|
6
|
+
framework: 'expo' | undefined;
|
|
7
|
+
platform: 'ios' | 'android';
|
|
8
|
+
outputPath: string;
|
|
9
|
+
entryFile: string;
|
|
10
|
+
bundleName: string;
|
|
11
|
+
outputBundleDir: string;
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
program.command('bundle')
|
|
6
15
|
.description('Creates a CodePush bundle file (assumes Hermes is enabled).')
|
|
@@ -10,17 +19,7 @@ program.command('bundle')
|
|
|
10
19
|
.option('-e, --entry-file <string>', 'path to JS/TS entry file', ENTRY_FILE)
|
|
11
20
|
.option('-b, --bundle-name <string>', 'bundle file name (default-ios: "main.jsbundle" / default-android: "index.android.bundle")')
|
|
12
21
|
.option('--output-bundle-dir <string>', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR)
|
|
13
|
-
|
|
14
|
-
* @param {Object} options
|
|
15
|
-
* @param {string} options.framework
|
|
16
|
-
* @param {string} options.platform
|
|
17
|
-
* @param {string} options.outputPath
|
|
18
|
-
* @param {string} options.entryFile
|
|
19
|
-
* @param {string} options.bundleName
|
|
20
|
-
* @param {string} options.outputBundleDir
|
|
21
|
-
* @return {void}
|
|
22
|
-
*/
|
|
23
|
-
.action((options) => {
|
|
22
|
+
.action((options: Options) => {
|
|
24
23
|
bundleCodePush(
|
|
25
24
|
options.framework,
|
|
26
25
|
options.platform,
|
package/cli/commands/createHistoryCommand/{createReleaseHistory.js → createReleaseHistory.ts}
RENAMED
|
@@ -1,36 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import type { CliConfigInterface, ReleaseHistoryInterface, ReleaseInfo } from "../../../typings/react-native-code-push.d.ts";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* releaseInfo: ReleaseHistoryInterface,
|
|
12
|
-
* platform: string,
|
|
13
|
-
* identifier: string
|
|
14
|
-
* ): Promise<void>}
|
|
15
|
-
* @param platform {"ios" | "android"}
|
|
16
|
-
* @param identifier {string}
|
|
17
|
-
* @returns {Promise<void>}
|
|
18
|
-
*/
|
|
19
|
-
async function createReleaseHistory(
|
|
20
|
-
targetVersion,
|
|
21
|
-
setReleaseHistory,
|
|
22
|
-
platform,
|
|
23
|
-
identifier,
|
|
24
|
-
) {
|
|
25
|
-
const BINARY_RELEASE = {
|
|
5
|
+
export async function createReleaseHistory(
|
|
6
|
+
targetVersion: string,
|
|
7
|
+
setReleaseHistory: CliConfigInterface['setReleaseHistory'],
|
|
8
|
+
platform: 'ios' | 'android',
|
|
9
|
+
identifier?: string,
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
const BINARY_RELEASE: ReleaseInfo = {
|
|
26
12
|
enabled: true,
|
|
27
13
|
mandatory: false,
|
|
28
14
|
downloadUrl: "",
|
|
29
15
|
packageHash: "",
|
|
30
16
|
};
|
|
31
17
|
|
|
32
|
-
|
|
33
|
-
const INITIAL_HISTORY = {
|
|
18
|
+
const INITIAL_HISTORY: ReleaseHistoryInterface = {
|
|
34
19
|
[targetVersion]: BINARY_RELEASE
|
|
35
20
|
};
|
|
36
21
|
|
|
@@ -49,5 +34,3 @@ async function createReleaseHistory(
|
|
|
49
34
|
process.exit(1)
|
|
50
35
|
}
|
|
51
36
|
}
|
|
52
|
-
|
|
53
|
-
module.exports = { createReleaseHistory: createReleaseHistory }
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { program, Option } from "commander";
|
|
2
|
+
import { findAndReadConfigFile } from "../../utils/fsUtils.js";
|
|
3
|
+
import { createReleaseHistory } from "./createReleaseHistory.js";
|
|
4
|
+
import { CONFIG_FILE_NAME } from "../../constant.js";
|
|
5
|
+
|
|
6
|
+
type Options = {
|
|
7
|
+
binaryVersion: string;
|
|
8
|
+
platform: 'ios' | 'android';
|
|
9
|
+
identifier?: string;
|
|
10
|
+
config: string;
|
|
11
|
+
}
|
|
5
12
|
|
|
6
13
|
program.command('create-history')
|
|
7
14
|
.description('Creates a new release history for the binary app.')
|
|
@@ -9,15 +16,7 @@ program.command('create-history')
|
|
|
9
16
|
.addOption(new Option('-p, --platform <type>', 'platform').choices(['ios', 'android']).default('ios'))
|
|
10
17
|
.option('-i, --identifier <string>', 'reserved characters to distinguish the release.')
|
|
11
18
|
.option('-c, --config <path>', 'set config file name (JS/TS)', CONFIG_FILE_NAME)
|
|
12
|
-
|
|
13
|
-
* @param {Object} options
|
|
14
|
-
* @param {string} options.binaryVersion
|
|
15
|
-
* @param {string} options.platform
|
|
16
|
-
* @param {string} options.identifier
|
|
17
|
-
* @param {string} options.config
|
|
18
|
-
* @return {void}
|
|
19
|
-
*/
|
|
20
|
-
.action(async (options) => {
|
|
19
|
+
.action(async (options: Options) => {
|
|
21
20
|
const config = findAndReadConfigFile(process.cwd(), options.config);
|
|
22
21
|
|
|
23
22
|
await createReleaseHistory(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { initAndroid } from "./initAndroid.js";
|
|
2
|
+
import { initIos } from "./initIos.js";
|
|
3
|
+
import { program } from "commander";
|
|
4
4
|
|
|
5
5
|
program
|
|
6
6
|
.command('init')
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { EOL } from "os";
|
|
4
4
|
|
|
5
|
-
function modifyMainApplicationKt(mainApplicationContent) {
|
|
5
|
+
export function modifyMainApplicationKt(mainApplicationContent: string) {
|
|
6
6
|
if (mainApplicationContent.includes('CodePush.getJSBundleFile()')) {
|
|
7
7
|
console.log('log: MainApplication.kt already has CodePush initialized.');
|
|
8
8
|
return mainApplicationContent;
|
|
@@ -12,7 +12,7 @@ function modifyMainApplicationKt(mainApplicationContent) {
|
|
|
12
12
|
.replace('override fun getJSMainModuleName(): String = "index"', `override fun getJSMainModuleName(): String = "index"${EOL} override fun getJSBundleFile(): String = CodePush.getJSBundleFile()`)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
async function initAndroid() {
|
|
15
|
+
export async function initAndroid() {
|
|
16
16
|
console.log('log: Running Android setup...');
|
|
17
17
|
await applyMainApplication();
|
|
18
18
|
}
|
|
@@ -37,12 +37,7 @@ async function applyMainApplication() {
|
|
|
37
37
|
|
|
38
38
|
async function findMainApplication() {
|
|
39
39
|
const searchPath = path.join(process.cwd(), 'android', 'app', 'src', 'main', 'java');
|
|
40
|
-
const files = fs.readdirSync(searchPath, { recursive: true });
|
|
40
|
+
const files = fs.readdirSync(searchPath, { encoding: 'utf-8', recursive: true });
|
|
41
41
|
const mainApplicationFile = files.find(file => file.endsWith('MainApplication.java') || file.endsWith('MainApplication.kt'));
|
|
42
42
|
return mainApplicationFile ? path.join(searchPath, mainApplicationFile) : null;
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
module.exports = {
|
|
46
|
-
initAndroid: initAndroid,
|
|
47
|
-
modifyMainApplicationKt: modifyMainApplicationKt,
|
|
48
|
-
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
// @ts-expect-error -- types for "xcode" are not available
|
|
4
|
+
import xcode from "xcode";
|
|
5
|
+
|
|
6
|
+
export async function initIos() {
|
|
7
|
+
console.log('log: Running iOS setup...');
|
|
8
|
+
const projectDir = path.join(process.cwd(), 'ios');
|
|
9
|
+
const files = fs.readdirSync(projectDir);
|
|
10
|
+
const xcodeprojFile = files.find(file => file.endsWith('.xcodeproj'));
|
|
11
|
+
if (!xcodeprojFile) {
|
|
12
|
+
console.log('log: Could not find .xcodeproj file in ios directory');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const projectName = xcodeprojFile.replace('.xcodeproj', '');
|
|
16
|
+
const appDelegatePath = findAppDelegate(path.join(projectDir, projectName));
|
|
17
|
+
|
|
18
|
+
if (!appDelegatePath) {
|
|
19
|
+
console.log('log: Could not find AppDelegate file');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (appDelegatePath.endsWith('.swift')) {
|
|
24
|
+
await setupSwift(appDelegatePath, projectDir, projectName);
|
|
25
|
+
} else {
|
|
26
|
+
await setupObjectiveC(appDelegatePath);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log('log: Please run `cd ios && pod install` to complete the setup.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function findAppDelegate(searchPath: string) {
|
|
33
|
+
if (!fs.existsSync(searchPath)) return null;
|
|
34
|
+
const files = fs.readdirSync(searchPath);
|
|
35
|
+
const appDelegateFile = files.find(file => file.startsWith('AppDelegate') && (file.endsWith('.m') || file.endsWith('.mm') || file.endsWith('.swift')));
|
|
36
|
+
return appDelegateFile ? path.join(searchPath, appDelegateFile) : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function modifyObjectiveCAppDelegate(appDelegateContent: string) {
|
|
40
|
+
const IMPORT_STATEMENT = '#import <CodePush/CodePush.h>';
|
|
41
|
+
if (appDelegateContent.includes(IMPORT_STATEMENT)) {
|
|
42
|
+
console.log('log: AppDelegate already has CodePush imported.');
|
|
43
|
+
return appDelegateContent;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return appDelegateContent
|
|
47
|
+
.replace('#import "AppDelegate.h"\n', `#import "AppDelegate.h"\n${IMPORT_STATEMENT}\n`)
|
|
48
|
+
.replace('[[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];', '[CodePush bundleURL];');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function modifySwiftAppDelegate(appDelegateContent: string) {
|
|
52
|
+
const CODEPUSH_CALL_STATEMENT = 'CodePush.bundleURL()';
|
|
53
|
+
if (appDelegateContent.includes(CODEPUSH_CALL_STATEMENT)) {
|
|
54
|
+
console.log('log: AppDelegate.swift already configured for CodePush.');
|
|
55
|
+
return appDelegateContent;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return appDelegateContent
|
|
59
|
+
.replace('Bundle.main.url(forResource: "main", withExtension: "jsbundle")', CODEPUSH_CALL_STATEMENT);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function setupObjectiveC(appDelegatePath: string) {
|
|
63
|
+
const appDelegateContent = fs.readFileSync(appDelegatePath, 'utf-8');
|
|
64
|
+
const newContent = modifyObjectiveCAppDelegate(appDelegateContent);
|
|
65
|
+
fs.writeFileSync(appDelegatePath, newContent);
|
|
66
|
+
console.log('log: Successfully updated AppDelegate.m/mm.');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function setupSwift(appDelegatePath: string, projectDir: string, projectName: string) {
|
|
70
|
+
const bridgingHeaderPath = await ensureBridgingHeader(projectDir, projectName);
|
|
71
|
+
if (!bridgingHeaderPath) {
|
|
72
|
+
console.log('log: Failed to create or find bridging header.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const appDelegateContent = fs.readFileSync(appDelegatePath, 'utf-8');
|
|
77
|
+
const newContent = modifySwiftAppDelegate(appDelegateContent);
|
|
78
|
+
fs.writeFileSync(appDelegatePath, newContent);
|
|
79
|
+
console.log('log: Successfully updated AppDelegate.swift.');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function ensureBridgingHeader(projectDir: string, projectName: string) {
|
|
83
|
+
const projectPath = path.join(projectDir, `${projectName}.xcodeproj`, 'project.pbxproj');
|
|
84
|
+
const myProj = xcode.project(projectPath);
|
|
85
|
+
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
myProj.parse(function (err: unknown) {
|
|
88
|
+
if (err) {
|
|
89
|
+
console.error(`Error parsing Xcode project: ${err}`);
|
|
90
|
+
return reject(err);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const bridgingHeaderRelativePath = `${projectName}/${projectName}-Bridging-Header.h`;
|
|
94
|
+
const bridgingHeaderAbsolutePath = path.join(projectDir, bridgingHeaderRelativePath);
|
|
95
|
+
|
|
96
|
+
const configurations = myProj.pbxXCBuildConfigurationSection();
|
|
97
|
+
for (const name in configurations) {
|
|
98
|
+
const config = configurations[name];
|
|
99
|
+
if (config.buildSettings) {
|
|
100
|
+
config.buildSettings.SWIFT_OBJC_BRIDGING_HEADER = `"${bridgingHeaderRelativePath}"`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!fs.existsSync(bridgingHeaderAbsolutePath)) {
|
|
105
|
+
fs.mkdirSync(path.dirname(bridgingHeaderAbsolutePath), { recursive: true });
|
|
106
|
+
fs.writeFileSync(bridgingHeaderAbsolutePath, '#import <CodePush/CodePush.h>\n');
|
|
107
|
+
console.log(`log: Created bridging header at ${bridgingHeaderAbsolutePath}`);
|
|
108
|
+
const groupKey = myProj.findPBXGroupKey({ name: projectName });
|
|
109
|
+
myProj.addHeaderFile(bridgingHeaderRelativePath, { public: true }, groupKey);
|
|
110
|
+
} else {
|
|
111
|
+
const headerContent = fs.readFileSync(bridgingHeaderAbsolutePath, 'utf-8');
|
|
112
|
+
if (!headerContent.includes('#import <CodePush/CodePush.h>')) {
|
|
113
|
+
fs.appendFileSync(bridgingHeaderAbsolutePath, '\n#import <CodePush/CodePush.h>\n');
|
|
114
|
+
console.log(`log: Updated bridging header at ${bridgingHeaderAbsolutePath}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(projectPath, myProj.writeSync());
|
|
119
|
+
console.log('log: Updated Xcode project with bridging header path.');
|
|
120
|
+
resolve(bridgingHeaderAbsolutePath);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { initAndroid, modifyMainApplicationKt } from "../initAndroid.js";
|
|
4
|
+
import { jest, expect, describe, it } from "@jest/globals";
|
|
5
|
+
|
|
6
|
+
const tempDir = path.join(__dirname, 'temp');
|
|
7
|
+
|
|
8
|
+
// https://github.com/react-native-community/template/blob/0.80.2/template/android/app/src/main/java/com/helloworld/MainApplication.kt
|
|
9
|
+
const ktTemplate = `
|
|
10
|
+
package com.helloworld
|
|
11
|
+
|
|
12
|
+
import android.app.Application
|
|
13
|
+
import com.facebook.react.PackageList
|
|
14
|
+
import com.facebook.react.ReactApplication
|
|
15
|
+
import com.facebook.react.ReactHost
|
|
16
|
+
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
|
17
|
+
import com.facebook.react.ReactNativeHost
|
|
18
|
+
import com.facebook.react.ReactPackage
|
|
19
|
+
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
20
|
+
import com.facebook.react.defaults.DefaultReactNativeHost
|
|
21
|
+
|
|
22
|
+
class MainApplication : Application(), ReactApplication {
|
|
23
|
+
|
|
24
|
+
override val reactNativeHost: ReactNativeHost =
|
|
25
|
+
object : DefaultReactNativeHost(this) {
|
|
26
|
+
override fun getPackages(): List<ReactPackage> =
|
|
27
|
+
PackageList(this).packages.apply {
|
|
28
|
+
// Packages that cannot be autolinked yet can be added manually here, for example:
|
|
29
|
+
// add(MyReactNativePackage())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun getJSMainModuleName(): String = "index"
|
|
33
|
+
|
|
34
|
+
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
|
35
|
+
|
|
36
|
+
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
|
37
|
+
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override val reactHost: ReactHost
|
|
41
|
+
get() = getDefaultReactHost(applicationContext, reactNativeHost)
|
|
42
|
+
|
|
43
|
+
override fun onCreate() {
|
|
44
|
+
super.onCreate()
|
|
45
|
+
loadReactNative(this)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
describe('Android init command', () => {
|
|
51
|
+
it('should correctly modify Kotlin MainApplication content', () => {
|
|
52
|
+
const modifiedContent = modifyMainApplicationKt(ktTemplate);
|
|
53
|
+
expect(modifiedContent).toContain('import com.microsoft.codepush.react.CodePush');
|
|
54
|
+
expect(modifiedContent).toContain('override fun getJSBundleFile(): String = CodePush.getJSBundleFile()');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should log a message and exit if MainApplication.java is found', async () => {
|
|
58
|
+
const originalCwd = process.cwd();
|
|
59
|
+
|
|
60
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
61
|
+
process.chdir(tempDir);
|
|
62
|
+
|
|
63
|
+
// Arrange
|
|
64
|
+
const javaAppDir = path.join(tempDir, 'android', 'app', 'src', 'main', 'java', 'com', 'helloworld');
|
|
65
|
+
fs.mkdirSync(javaAppDir, { recursive: true });
|
|
66
|
+
const javaFilePath = path.join(javaAppDir, 'MainApplication.java');
|
|
67
|
+
const originalContent = '// Java file content';
|
|
68
|
+
fs.writeFileSync(javaFilePath, originalContent);
|
|
69
|
+
|
|
70
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Act
|
|
74
|
+
await initAndroid();
|
|
75
|
+
|
|
76
|
+
// Assert
|
|
77
|
+
expect(consoleSpy).toHaveBeenCalledWith('log: MainApplication.java is not supported. Please migrate to MainApplication.kt.');
|
|
78
|
+
const finalContent = fs.readFileSync(javaFilePath, 'utf-8');
|
|
79
|
+
expect(finalContent).toBe(originalContent); // Ensure file is not modified
|
|
80
|
+
|
|
81
|
+
consoleSpy.mockRestore();
|
|
82
|
+
|
|
83
|
+
process.chdir(originalCwd);
|
|
84
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
85
|
+
});
|
|
86
|
+
});
|