@bravemobile/react-native-code-push 12.0.2 → 12.1.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.
Files changed (100) hide show
  1. package/README.md +1 -1
  2. package/android/app/src/debug/AndroidManifest.xml +9 -0
  3. package/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +1 -52
  4. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +4 -43
  5. package/bin/code-push.js +6 -0
  6. package/cli/commands/bundleCommand/{bundleCodePush.js → bundleCodePush.ts} +16 -24
  7. package/cli/commands/bundleCommand/{index.js → index.ts} +13 -14
  8. package/cli/commands/createHistoryCommand/{createReleaseHistory.js → createReleaseHistory.ts} +11 -28
  9. package/cli/commands/createHistoryCommand/{index.js → index.ts} +12 -13
  10. package/cli/commands/initCommand/{index.js → index.ts} +3 -3
  11. package/cli/commands/initCommand/{initAndroid.js → initAndroid.ts} +6 -11
  12. package/cli/commands/initCommand/initIos.ts +123 -0
  13. package/cli/commands/initCommand/test/initAndroid.test.ts +86 -0
  14. package/cli/commands/initCommand/test/initIos.test.ts +99 -0
  15. package/cli/commands/releaseCommand/{addToReleaseHistory.js → addToReleaseHistory.ts} +17 -45
  16. package/cli/commands/releaseCommand/{index.js → index.ts} +24 -25
  17. package/cli/commands/releaseCommand/release.ts +72 -0
  18. package/cli/commands/showHistoryCommand/{index.js → index.ts} +11 -12
  19. package/cli/commands/updateHistoryCommand/{index.js → index.ts} +17 -18
  20. package/cli/commands/updateHistoryCommand/{updateReleaseHistory.js → updateReleaseHistory.ts} +15 -41
  21. package/cli/constant.ts +4 -0
  22. package/cli/dist/commands/bundleCommand/bundleCodePush.js +34 -0
  23. package/cli/dist/commands/bundleCommand/index.js +14 -0
  24. package/cli/dist/commands/createHistoryCommand/createReleaseHistory.js +25 -0
  25. package/cli/dist/commands/createHistoryCommand/index.js +14 -0
  26. package/cli/dist/commands/initCommand/index.js +12 -0
  27. package/cli/dist/commands/initCommand/initAndroid.js +37 -0
  28. package/cli/{commands → dist/commands}/initCommand/initIos.js +13 -33
  29. package/cli/dist/commands/initCommand/test/initAndroid.test.js +75 -0
  30. package/cli/dist/commands/initCommand/test/initIos.test.js +95 -0
  31. package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +32 -0
  32. package/cli/dist/commands/releaseCommand/index.js +38 -0
  33. package/cli/dist/commands/releaseCommand/release.js +36 -0
  34. package/cli/dist/commands/showHistoryCommand/index.js +14 -0
  35. package/cli/dist/commands/updateHistoryCommand/index.js +30 -0
  36. package/cli/dist/commands/updateHistoryCommand/updateReleaseHistory.js +26 -0
  37. package/cli/dist/constant.js +4 -0
  38. package/cli/dist/functions/getReactTempDir.js +10 -0
  39. package/cli/{functions → dist/functions}/makeCodePushBundle.js +5 -11
  40. package/cli/{functions → dist/functions}/prepareToBundleJS.js +2 -5
  41. package/cli/{functions → dist/functions}/runExpoBundleCommand.js +3 -21
  42. package/cli/{functions → dist/functions}/runHermesEmitBinaryCommand.js +12 -68
  43. package/cli/{functions → dist/functions}/runReactNativeBundleCommand.js +3 -23
  44. package/cli/dist/index.js +38 -0
  45. package/cli/dist/utils/file-utils.js +19 -0
  46. package/cli/dist/utils/fsUtils.js +37 -0
  47. package/cli/{utils → dist/utils}/hash-utils.js +19 -127
  48. package/cli/{utils → dist/utils}/promisfied-fs.js +5 -16
  49. package/cli/dist/utils/showLogo.js +21 -0
  50. package/cli/{utils → dist/utils}/zip.js +15 -51
  51. package/cli/functions/{getReactTempDir.js → getReactTempDir.ts} +2 -6
  52. package/cli/functions/makeCodePushBundle.ts +26 -0
  53. package/cli/functions/prepareToBundleJS.ts +10 -0
  54. package/cli/functions/runExpoBundleCommand.ts +45 -0
  55. package/cli/functions/runHermesEmitBinaryCommand.ts +186 -0
  56. package/cli/functions/runReactNativeBundleCommand.ts +51 -0
  57. package/cli/index.ts +48 -0
  58. package/cli/package.json +33 -0
  59. package/cli/utils/{file-utils.js → file-utils.ts} +4 -21
  60. package/cli/utils/{fsUtils.js → fsUtils.ts} +12 -19
  61. package/cli/utils/hash-utils.ts +146 -0
  62. package/cli/utils/promisfied-fs.ts +19 -0
  63. package/cli/utils/{showLogo.js → showLogo.ts} +1 -3
  64. package/cli/utils/zip.ts +65 -0
  65. package/package.json +42 -12
  66. package/{AlertAdapter.js → src/AlertAdapter.js} +5 -5
  67. package/AGENTS.md +0 -32
  68. package/CONTRIBUTING.md +0 -134
  69. package/SECURITY.md +0 -41
  70. package/android/app/src/main/java/com/microsoft/codepush/react/ReactInstanceHolder.java +0 -17
  71. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  72. package/android/gradle/wrapper/gradle-wrapper.properties +0 -5
  73. package/android/gradlew +0 -164
  74. package/android/gradlew.bat +0 -90
  75. package/app.plugin.js +0 -1
  76. package/babel.config.js +0 -3
  77. package/cli/commands/releaseCommand/release.js +0 -114
  78. package/cli/constant.js +0 -6
  79. package/cli/index.js +0 -49
  80. package/docs/api-android.md +0 -83
  81. package/docs/api-ios.md +0 -31
  82. package/docs/api-js.md +0 -592
  83. package/docs/multi-deployment-testing-android.md +0 -148
  84. package/docs/multi-deployment-testing-ios.md +0 -59
  85. package/eslint.config.mjs +0 -32
  86. package/scripts/generateBundledResourcesHash.js +0 -125
  87. package/scripts/getFilesInFolder.js +0 -19
  88. package/scripts/recordFilesBeforeBundleCommand.js +0 -41
  89. package/tsconfig.json +0 -18
  90. package/tslint.json +0 -32
  91. /package/{CodePush.js → src/CodePush.js} +0 -0
  92. /package/{logging.js → src/logging.js} +0 -0
  93. /package/{package-mixins.js → src/package-mixins.js} +0 -0
  94. /package/{versioning → src/versioning}/BaseVersioning.js +0 -0
  95. /package/{versioning → src/versioning}/BaseVersioning.test.js +0 -0
  96. /package/{versioning → src/versioning}/IncrementalVersioning.js +0 -0
  97. /package/{versioning → src/versioning}/IncrementalVersioning.test.js +0 -0
  98. /package/{versioning → src/versioning}/SemverVersioning.js +0 -0
  99. /package/{versioning → src/versioning}/SemverVersioning.test.js +0 -0
  100. /package/{versioning → src/versioning}/index.js +0 -0
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ### 🚀 New Architecture support
10
10
 
11
- Supports React Native 0.74 ~ 0.81.
11
+ Supports React Native 0.74 ~ 0.82.
12
12
 
13
13
  (Tested on the React Native CLI template apps)
14
14
 
@@ -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(ReactInstanceManager instanceManager, String latestJSBundleFile) throws IllegalAccessException {
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
- // #2) Update the locally stored JS bundle file path
180
- setJSBundle(instanceManager, latestJSBundleFile);
158
+ // #1) Update the locally stored JS bundle file path
159
+ setJSBundle(latestJSBundleFile);
181
160
 
182
- // #3) Get the context creation method and fire it on the UI thread (which RN enforces)
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");
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ import('../cli/dist/index.js').catch((error) => {
4
+ console.error(error);
5
+ process.exit(1);
6
+ });
@@ -1,29 +1,23 @@
1
- const fs = require('fs');
2
- const { prepareToBundleJS } = require('../../functions/prepareToBundleJS');
3
- const { runReactNativeBundleCommand } = require('../../functions/runReactNativeBundleCommand');
4
- const { runExpoBundleCommand } = require('../../functions/runExpoBundleCommand');
5
- const { getReactTempDir } = require('../../functions/getReactTempDir');
6
- const { runHermesEmitBinaryCommand } = require('../../functions/runHermesEmitBinaryCommand');
7
- const { makeCodePushBundle } = require('../../functions/makeCodePushBundle');
8
- const { ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../constant');
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
- const { program, Option } = require("commander");
2
- const { bundleCodePush } = require("./bundleCodePush");
3
- const { OUTPUT_BUNDLE_DIR, ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../constant');
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,
@@ -1,36 +1,21 @@
1
- const fs = require('fs');
2
- const path = require('path');
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
- * @param targetVersion {string}
7
- * @param setReleaseHistory {
8
- * function(
9
- * targetBinaryVersion: string,
10
- * jsonFilePath: string,
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
- /** @type {ReleaseHistoryInterface} */
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
- const { program, Option } = require("commander");
2
- const { findAndReadConfigFile } = require("../../utils/fsUtils");
3
- const { createReleaseHistory } = require("./createReleaseHistory");
4
- const { CONFIG_FILE_NAME } = require('../../constant');
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
- const { initAndroid } = require('./initAndroid');
2
- const { initIos } = require('./initIos');
3
- const { program } = require('commander');
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
- const path = require('path');
2
- const fs = require('fs');
3
- const { EOL } = require('os');
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
+ });