@bravemobile/react-native-code-push 8.3.1 → 9.0.0-alpha.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 (65) hide show
  1. package/CodePush.js +107 -136
  2. package/README.md +0 -5
  3. package/babel-plugin-code-push/index.js +197 -0
  4. package/babel-plugin-code-push/package-lock.json +10463 -0
  5. package/babel-plugin-code-push/package.json +26 -0
  6. package/babel-plugin-code-push/test/.babelrc.js +12 -0
  7. package/babel-plugin-code-push/test/cases/test1-config +15 -0
  8. package/babel-plugin-code-push/test/cases/test1-input +3 -0
  9. package/babel-plugin-code-push/test/cases/test1-output +11 -0
  10. package/babel-plugin-code-push/test/cases/test2-config +9 -0
  11. package/babel-plugin-code-push/test/cases/test2-input +3 -0
  12. package/babel-plugin-code-push/test/cases/test2-output +7 -0
  13. package/babel-plugin-code-push/test/codepush.config.js +15 -0
  14. package/babel-plugin-code-push/test/plugin.test.js +44 -0
  15. package/babel.config.js +3 -0
  16. package/cli/commands/bundleCommand/bundleCodePush.js +49 -0
  17. package/cli/commands/bundleCommand/index.js +25 -0
  18. package/cli/commands/createHistoryCommand/createReleaseHistory.js +53 -0
  19. package/cli/commands/createHistoryCommand/index.js +28 -0
  20. package/cli/commands/releaseCommand/addToReleaseHistory.js +75 -0
  21. package/cli/commands/releaseCommand/index.js +61 -0
  22. package/cli/commands/releaseCommand/release.js +88 -0
  23. package/cli/commands/showHistoryCommand/index.js +28 -0
  24. package/cli/commands/updateHistoryCommand/index.js +49 -0
  25. package/cli/commands/updateHistoryCommand/updateReleaseHistory.js +62 -0
  26. package/cli/constant.js +3 -0
  27. package/cli/functions/getReactTempDir.js +16 -0
  28. package/cli/functions/makeCodePushBundle.js +27 -0
  29. package/cli/functions/prepareToBundleJS.js +12 -0
  30. package/cli/functions/runHermesEmitBinaryCommand.js +213 -0
  31. package/cli/functions/runReactNativeBundleCommand.js +59 -0
  32. package/cli/index.js +43 -0
  33. package/cli/utils/file-utils.js +42 -0
  34. package/cli/utils/fsUtils.js +49 -0
  35. package/cli/utils/hash-utils.js +227 -0
  36. package/cli/utils/promisfied-fs.js +29 -0
  37. package/cli/utils/showLogo.js +23 -0
  38. package/cli/utils/zip.js +89 -0
  39. package/code-push.config.example.supabase.ts +114 -0
  40. package/docs/setup-android.md +3 -11
  41. package/eslint.config.mjs +32 -0
  42. package/package-mixins.js +1 -8
  43. package/package.json +26 -23
  44. package/react-native.config.js +3 -1
  45. package/typings/react-native-code-push.d.ts +91 -16
  46. package/versioning/BaseVersioning.js +126 -0
  47. package/versioning/BaseVersioning.test.js +15 -0
  48. package/versioning/IncrementalVersioning.js +9 -0
  49. package/versioning/IncrementalVersioning.test.js +186 -0
  50. package/versioning/SemverVersioning.js +10 -0
  51. package/versioning/SemverVersioning.test.js +205 -0
  52. package/versioning/index.js +7 -0
  53. package/.azurepipelines/build-rn-code-push-1es.yml +0 -104
  54. package/.azurepipelines/test-rn-code-push.yml +0 -94
  55. package/.config/CredScanSuppressions.json +0 -14
  56. package/docs/setup-windows.md +0 -121
  57. package/request-fetch-adapter.js +0 -52
  58. package/scripts/postlink/android/postlink.js +0 -87
  59. package/scripts/postlink/ios/postlink.js +0 -116
  60. package/scripts/postlink/run.js +0 -11
  61. package/scripts/postunlink/android/postunlink.js +0 -74
  62. package/scripts/postunlink/ios/postunlink.js +0 -87
  63. package/scripts/postunlink/run.js +0 -11
  64. package/scripts/tools/linkToolsAndroid.js +0 -57
  65. package/scripts/tools/linkToolsIos.js +0 -130
package/CodePush.js CHANGED
@@ -1,9 +1,8 @@
1
- import { AcquisitionManager as Sdk } from "code-push/script/acquisition-sdk";
2
1
  import { Alert } from "./AlertAdapter";
3
- import requestFetchAdapter from "./request-fetch-adapter";
4
2
  import { AppState, Platform } from "react-native";
5
3
  import log from "./logging";
6
4
  import hoistStatics from 'hoist-non-react-statics';
5
+ import { SemverVersioning } from './versioning/SemverVersioning'
7
6
 
8
7
  let NativeCodePush = require("react-native").NativeModules.CodePush;
9
8
  const PackageMixins = require("./package-mixins")(NativeCodePush);
@@ -27,7 +26,6 @@ async function checkForUpdate(deploymentKey = null, handleBinaryVersionMismatchC
27
26
  * deployments (e.g. an early access deployment for insiders).
28
27
  */
29
28
  const config = deploymentKey ? { ...nativeConfig, ...{ deploymentKey } } : nativeConfig;
30
- const sdk = getPromisifiedSdk(requestFetchAdapter, config);
31
29
 
32
30
  // Use dynamically overridden getCurrentPackage() during tests.
33
31
  const localPackage = await module.exports.getCurrentPackage();
@@ -50,60 +48,102 @@ async function checkForUpdate(deploymentKey = null, handleBinaryVersionMismatchC
50
48
  }
51
49
  }
52
50
 
53
- const update = sharedCodePushOptions.updateChecker
54
- ? await (async () => {
55
- try {
56
- // refer to `UpdateCheckRequest` type inside code-push SDK
57
- const updateRequest = {
58
- deployment_key: config.deploymentKey,
59
- app_version: queryPackage.appVersion,
60
- package_hash: queryPackage.packageHash,
61
- is_companion: config.ignoreAppVersion,
62
- label: queryPackage.label,
63
- client_unique_id: config.clientUniqueId,
64
- };
65
-
66
- const response = await sharedCodePushOptions.updateChecker(updateRequest);
67
-
68
- // extracted from the internal processing of the code-push SDK
69
- const updateInfo = response.update_info;
70
- if (!updateInfo) {
71
- return null;
72
- } else if (updateInfo.update_app_version) {
73
- return { updateAppVersion: true, appVersion: updateInfo.target_binary_range };
74
- } else if (!updateInfo.is_available) {
75
- return null;
76
- }
51
+ const update = await (async () => {
52
+ try {
53
+ // refer to `UpdateCheckRequest` type inside code-push SDK
54
+ const updateRequest = {
55
+ deployment_key: config.deploymentKey,
56
+ app_version: queryPackage.appVersion,
57
+ package_hash: queryPackage.packageHash,
58
+ is_companion: config.ignoreAppVersion,
59
+ label: queryPackage.label,
60
+ client_unique_id: config.clientUniqueId,
61
+ };
62
+
63
+ /**
64
+ * `releaseHistory`
65
+ * @type {ReleaseHistoryInterface}
66
+ */
67
+ const releaseHistory = await sharedCodePushOptions.releaseHistoryFetcher(updateRequest);
68
+
69
+ /**
70
+ * `runtimeVersion`
71
+ * The version of currently running CodePush update. (It can be undefined if the app is running without CodePush update.)
72
+ * @type {string|undefined}
73
+ */
74
+ const runtimeVersion = updateRequest.label;
75
+
76
+ const versioning = new SemverVersioning(releaseHistory);
77
+
78
+ const shouldRollbackToBinary = versioning.shouldRollbackToBinary(runtimeVersion)
79
+ if (shouldRollbackToBinary) {
80
+ // Reset to latest major version and restart
81
+ CodePush.clearUpdates();
82
+ CodePush.allowRestart();
83
+ CodePush.restartApp();
84
+ }
85
+
86
+ const [latestVersion, latestReleaseInfo] = versioning.findLatestRelease();
87
+ const isMandatory = versioning.checkIsMandatory(runtimeVersion);
88
+
89
+ /**
90
+ * Convert the update information decided from `ReleaseHistoryInterface` to be passed to the library core (original CodePush library).
91
+ *
92
+ * @type {UpdateCheckResponse} the interface required by the original CodePush library.
93
+ */
94
+ const updateInfo = {
95
+ download_url: latestReleaseInfo.downloadUrl,
96
+ // (`enabled` will always be true in the release information obtained from the previous process.)
97
+ is_available: latestReleaseInfo.enabled,
98
+ package_hash: latestReleaseInfo.packageHash,
99
+ is_mandatory: isMandatory,
100
+ // 이건 항상 현재 실행중인 바이너리 버전을 전달한다.
101
+ // 조회한 업데이트가 현재 바이너리를 타겟하는가? 를 API 서버에서 판단한 다음, 해당 된다면 런타임 바이너리 버전을 그대로 돌려주던 것임.
102
+ // 우리는 updateChecker 조회 결과가 넘어왔다면 해당 정보는 현재 런타임 바이너리에 호환됨을 전제로 하고있음.
103
+ target_binary_range: updateRequest.app_version,
104
+ /**
105
+ * Retrieve the update version from the ReleaseHistory and store it in the label.
106
+ * This information can be accessed at runtime through the CodePush bundle metadata.
107
+ */
108
+ label: latestVersion,
109
+ // false 전달해야 정상 동작함
110
+ update_app_version: false,
111
+ // 그닥 쓸모 없음
112
+ description: '',
113
+ // 런타임에 안쓰임
114
+ is_disabled: false,
115
+ // 런타임에 안쓰임
116
+ package_size: 0,
117
+ // 런타임에 안쓰임
118
+ should_run_binary_version: false,
119
+ }
77
120
 
78
- // refer to `RemotePackage` type inside code-push SDK
79
- return {
80
- deploymentKey: config.deploymentKey,
81
- description: updateInfo.description ?? '',
82
- label: updateInfo.label ?? '',
83
- appVersion: updateInfo.target_binary_range ?? '',
84
- isMandatory: updateInfo.is_mandatory ?? false,
85
- packageHash: updateInfo.package_hash ?? '',
86
- packageSize: updateInfo.package_size ?? 0,
87
- downloadUrl: updateInfo.download_url ?? '',
88
- };
89
- } catch (error) {
90
- log(`An error has occurred at update checker : ${error.stack}`);
91
- if (sharedCodePushOptions.fallbackToAppCenter) {
92
- return await sdk.queryUpdateWithCurrentPackage(queryPackage);
93
- } else {
94
- // update will not happen
95
- return undefined;
96
- }
97
- }
98
- })()
99
- : await sdk.queryUpdateWithCurrentPackage(queryPackage);
100
121
 
101
- if (sharedCodePushOptions.bundleHost && update) {
102
- const fileName = typeof update.downloadUrl === 'string' ? update.downloadUrl.split('/').pop() : null;
103
- if (fileName) {
104
- update.downloadUrl = sharedCodePushOptions.bundleHost + fileName;
122
+ if (!updateInfo) {
123
+ return null;
124
+ } else if (updateInfo.update_app_version) {
125
+ return { updateAppVersion: true, appVersion: updateInfo.target_binary_range };
126
+ } else if (!updateInfo.is_available) {
127
+ return null;
128
+ }
129
+
130
+ // refer to `RemotePackage` type inside code-push SDK
131
+ return {
132
+ deploymentKey: config.deploymentKey,
133
+ description: updateInfo.description ?? '',
134
+ label: updateInfo.label ?? '',
135
+ appVersion: updateInfo.target_binary_range ?? '',
136
+ isMandatory: updateInfo.is_mandatory ?? false,
137
+ packageHash: updateInfo.package_hash ?? '',
138
+ packageSize: updateInfo.package_size ?? 0,
139
+ downloadUrl: updateInfo.download_url ?? '',
140
+ };
141
+ } catch (error) {
142
+ log(`An error has occurred at update checker :`, error);
143
+ // update will not happen
144
+ return undefined;
105
145
  }
106
- }
146
+ })();
107
147
 
108
148
  /*
109
149
  * There are four cases where checkForUpdate will resolve to null:
@@ -135,7 +175,7 @@ async function checkForUpdate(deploymentKey = null, handleBinaryVersionMismatchC
135
175
 
136
176
  return null;
137
177
  } else {
138
- const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) };
178
+ const remotePackage = { ...update, ...PackageMixins.remote() };
139
179
  remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash);
140
180
  remotePackage.deploymentKey = deploymentKey || nativeConfig.deploymentKey;
141
181
  return remotePackage;
@@ -170,48 +210,6 @@ async function getUpdateMetadata(updateState) {
170
210
  return updateMetadata;
171
211
  }
172
212
 
173
- function getPromisifiedSdk(requestFetchAdapter, config) {
174
- // Use dynamically overridden AcquisitionSdk during tests.
175
- const sdk = new module.exports.AcquisitionSdk(requestFetchAdapter, config);
176
- sdk.queryUpdateWithCurrentPackage = (queryPackage) => {
177
- return new Promise((resolve, reject) => {
178
- module.exports.AcquisitionSdk.prototype.queryUpdateWithCurrentPackage.call(sdk, queryPackage, (err, update) => {
179
- if (err) {
180
- reject(err);
181
- } else {
182
- resolve(update);
183
- }
184
- });
185
- });
186
- };
187
-
188
- sdk.reportStatusDeploy = (deployedPackage, status, previousLabelOrAppVersion, previousDeploymentKey) => {
189
- return new Promise((resolve, reject) => {
190
- module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, deployedPackage, status, previousLabelOrAppVersion, previousDeploymentKey, (err) => {
191
- if (err) {
192
- reject(err);
193
- } else {
194
- resolve();
195
- }
196
- });
197
- });
198
- };
199
-
200
- sdk.reportStatusDownload = (downloadedPackage) => {
201
- return new Promise((resolve, reject) => {
202
- module.exports.AcquisitionSdk.prototype.reportStatusDownload.call(sdk, downloadedPackage, (err) => {
203
- if (err) {
204
- reject(err);
205
- } else {
206
- resolve();
207
- }
208
- });
209
- });
210
- };
211
-
212
- return sdk;
213
- }
214
-
215
213
  // This ensures that notifyApplicationReadyInternal is only called once
216
214
  // in the lifetime of this module instance.
217
215
  const notifyApplicationReady = (() => {
@@ -235,8 +233,7 @@ async function notifyApplicationReadyInternal() {
235
233
 
236
234
  async function tryReportStatus(statusReport, retryOnAppResume) {
237
235
  const config = await getConfiguration();
238
- const previousLabelOrAppVersion = statusReport.previousLabelOrAppVersion;
239
- const previousDeploymentKey = statusReport.previousDeploymentKey || config.deploymentKey;
236
+
240
237
  try {
241
238
  if (statusReport.appVersion) {
242
239
  log(`Reporting binary update (${statusReport.appVersion})`);
@@ -244,9 +241,6 @@ async function tryReportStatus(statusReport, retryOnAppResume) {
244
241
  if (!config.deploymentKey) {
245
242
  throw new Error("Deployment key is missed");
246
243
  }
247
-
248
- const sdk = getPromisifiedSdk(requestFetchAdapter, config);
249
- await sdk.reportStatusDeploy(/* deployedPackage */ null, /* status */ null, previousLabelOrAppVersion, previousDeploymentKey);
250
244
  } else {
251
245
  const label = statusReport.package.label;
252
246
  if (statusReport.status === "DeploymentSucceeded") {
@@ -255,10 +249,6 @@ async function tryReportStatus(statusReport, retryOnAppResume) {
255
249
  log(`Reporting CodePush update rollback (${label})`);
256
250
  await NativeCodePush.setLatestRollbackInfo(statusReport.package.packageHash);
257
251
  }
258
-
259
- config.deploymentKey = statusReport.package.deploymentKey;
260
- const sdk = getPromisifiedSdk(requestFetchAdapter, config);
261
- await sdk.reportStatusDeploy(statusReport.package, statusReport.status, previousLabelOrAppVersion, previousDeploymentKey);
262
252
  }
263
253
 
264
254
  NativeCodePush.recordStatusReported(statusReport);
@@ -346,7 +336,7 @@ function validateRollbackRetryOptions(rollbackRetryOptions) {
346
336
  return true;
347
337
  }
348
338
 
349
- var testConfig;
339
+ let testConfig;
350
340
 
351
341
  // This function is only used for tests. Replaces the default SDK, configuration and native bridge
352
342
  function setUpTestDependencies(testSdk, providedTestConfig, testNativeBridge) {
@@ -565,39 +555,23 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
565
555
  let CodePush;
566
556
 
567
557
  /**
568
- * @callback updateChecker
558
+ * @callback releaseHistoryFetcher
569
559
  * @param {UpdateCheckRequest} updateRequest Current package information to check for updates.
570
- * @returns {Promise<{update_info: UpdateCheckResponse}>} The result of the update check. Follows the AppCenter API response interface.
560
+ * @returns {Promise<ReleaseHistoryInterface>} The release history of the updates deployed for a specific binary version.
571
561
  */
572
562
 
573
563
  /**
574
564
  * If you pass options once when calling `codePushify`, they will be shared with related functions.
575
565
  * @type {{
576
- * bundleHost: string | undefined,
577
- * setBundleHost(host: string): void,
578
- * updateChecker: updateChecker | undefined,
579
- * setUpdateChecker(updateCheckerFunction: updateChecker): void,
580
- * fallbackToAppCenter: boolean,
581
- * setFallbackToAppCenter(enable: boolean): void
566
+ * releaseHistoryFetcher: releaseHistoryFetcher | undefined,
567
+ * setReleaseHistoryFetcher(releaseHistoryFetcherFunction: releaseHistoryFetcher): void,
582
568
  * }}
583
569
  */
584
570
  const sharedCodePushOptions = {
585
- bundleHost: undefined,
586
- setBundleHost(host) {
587
- if (host && typeof host !== 'string') throw new Error('pass a string to setBundleHost');
588
- if (typeof host === 'string' && host.slice(-1) !== '/') {
589
- host += '/';
590
- }
591
- this.bundleHost = host;
592
- },
593
- updateChecker: undefined,
594
- setUpdateChecker(updateCheckerFunction) {
595
- if (updateCheckerFunction && typeof updateCheckerFunction !== 'function') throw new Error('pass a function to setUpdateChecker');
596
- this.updateChecker = updateCheckerFunction;
597
- },
598
- fallbackToAppCenter: true,
599
- setFallbackToAppCenter(enable) {
600
- this.fallbackToAppCenter = enable;
571
+ releaseHistoryFetcher: undefined,
572
+ setReleaseHistoryFetcher(releaseHistoryFetcherFunction) {
573
+ if (!releaseHistoryFetcherFunction || typeof releaseHistoryFetcherFunction !== 'function') throw new Error('pass a function to releaseHistoryFetcher');
574
+ this.releaseHistoryFetcher = releaseHistoryFetcherFunction;
601
575
  }
602
576
  }
603
577
 
@@ -621,9 +595,7 @@ function codePushify(options = {}) {
621
595
  );
622
596
  }
623
597
 
624
- sharedCodePushOptions.setBundleHost(options.bundleHost);
625
- sharedCodePushOptions.setUpdateChecker(options.updateChecker);
626
- sharedCodePushOptions.setFallbackToAppCenter(options.fallbackToAppCenter);
598
+ sharedCodePushOptions.setReleaseHistoryFetcher(options.releaseHistoryFetcher);
627
599
 
628
600
  const decorator = (RootComponent) => {
629
601
  class CodePushComponent extends React.Component {
@@ -696,7 +668,6 @@ function codePushify(options = {}) {
696
668
  if (NativeCodePush) {
697
669
  CodePush = codePushify;
698
670
  Object.assign(CodePush, {
699
- AcquisitionSdk: Sdk,
700
671
  checkForUpdate,
701
672
  getConfiguration,
702
673
  getCurrentPackage,
@@ -756,7 +727,7 @@ if (NativeCodePush) {
756
727
  DEFAULT_ROLLBACK_RETRY_OPTIONS: {
757
728
  delayInHours: 24,
758
729
  maxRetryAttempts: 1
759
- }
730
+ },
760
731
  });
761
732
  } else {
762
733
  log("The CodePush module doesn't appear to be properly installed. Please double-check that everything is setup correctly.");
package/README.md CHANGED
@@ -29,7 +29,6 @@ Specify a function to perform the update check using the `updateChecker` option.
29
29
 
30
30
  (The `bundleHost` option can be used in combination.)
31
31
 
32
- `fallbackToAppCenter` : If an error occurs during the execution of the updateChecker function, the original update check behavior is performed as a fallback. (default: true)
33
32
 
34
33
  ```javascript
35
34
  const codePushOptions = {
@@ -41,7 +40,6 @@ const codePushOptions = {
41
40
  });
42
41
  return response;
43
42
  },
44
- fallbackToAppCenter: true,
45
43
  };
46
44
 
47
45
  export default codePush(codePushOptions)(MyApp);
@@ -72,7 +70,6 @@ This plugin provides client-side integration for the [CodePush service](https://
72
70
  * [Getting Started](#getting-started)
73
71
  * [iOS Setup](docs/setup-ios.md)
74
72
  * [Android Setup](docs/setup-android.md)
75
- * [Windows Setup](docs/setup-windows.md)
76
73
  * [Plugin Usage](#plugin-usage)
77
74
  * [Store Guideline Compliance](#store-guideline-compliance)
78
75
  * [Releasing Updates](#releasing-updates)
@@ -105,7 +102,6 @@ In order to ensure that your end users always have a functioning version of your
105
102
 
106
103
  - iOS (7+)
107
104
  - Android (4.1+) on TLS 1.2 compatible devices
108
- - Windows (UWP)
109
105
 
110
106
  We try our best to maintain backwards compatibility of our plugin with previous versions of React Native, but due to the nature of the platform, and the existence of breaking changes between releases, it is possible that you need to use a specific version of the CodePush plugin in order to support the exact version of React Native you are using. The following table outlines which CodePush plugin versions officially support the respective React Native versions:
111
107
 
@@ -181,7 +177,6 @@ If you want to see how other projects have integrated with CodePush, you can che
181
177
  Then continue with installing the native module
182
178
  * [iOS Setup](docs/setup-ios.md)
183
179
  * [Android Setup](docs/setup-android.md)
184
- * [Windows Setup](docs/setup-windows.md)
185
180
 
186
181
 
187
182
  ## Plugin Usage
@@ -0,0 +1,197 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+ const { parseExpression, parse } = require("@babel/parser");
4
+
5
+ const OPTIONS_TO_BUNDLE = [
6
+ "bundleHost",
7
+ "runtimeVersion",
8
+ "versioning",
9
+ ];
10
+
11
+ module.exports = function (babel, options) {
12
+ const { types: t } = babel;
13
+ const configPath =
14
+ options.configPath != null
15
+ ? path.resolve(options.configPath)
16
+ : path.resolve(process.cwd(), "code-push.config.js");
17
+
18
+ // Load config and imports from `code-push.config.js`
19
+ const { config, configImports, importedIdentifiers } = loadConfig(
20
+ babel,
21
+ configPath
22
+ );
23
+
24
+ return {
25
+ visitor: {
26
+ Program(path) {
27
+ // Track imports in the input file to avoid duplicates
28
+ const existingImports = new Set();
29
+ path.traverse({
30
+ ImportDeclaration(importPath) {
31
+ existingImports.add(importPath.node.source.value);
32
+ },
33
+ });
34
+
35
+ // Add missing imports from code-push.config.js to the input file
36
+ configImports.forEach((importNode) => {
37
+ if (!existingImports.has(importNode.source.value)) {
38
+ // Clone the import node from code-push.config.js and add it to the input file
39
+ path.node.body.unshift(t.cloneNode(importNode));
40
+ }
41
+ });
42
+ },
43
+ ImportDeclaration(path, state) {
44
+ if (
45
+ path.node.source.value.includes("@bravemobile/react-native-code-push")
46
+ ) {
47
+ const defaultImport = path.node.specifiers.find((specifier) =>
48
+ t.isImportDefaultSpecifier(specifier)
49
+ );
50
+
51
+ // Save the imported name (e.g., "codePush") for later use
52
+ if (defaultImport) {
53
+ state.file.metadata.codePushImportName = defaultImport.local.name;
54
+ }
55
+ }
56
+ },
57
+ CallExpression(path, state) {
58
+ const codePushImportName = state.file.metadata.codePushImportName;
59
+ if (!codePushImportName) return;
60
+
61
+ // Check if the current CallExpression is a call to the codePush function
62
+ if (t.isIdentifier(path.node.callee, { name: codePushImportName })) {
63
+ // Create an AST object representation of the configuration options to bundle
64
+ const configObjectExpression = t.objectExpression(
65
+ OPTIONS_TO_BUNDLE.map((key) =>
66
+ t.objectProperty(
67
+ t.identifier(key),
68
+ serializeConfigToNode(babel, importedIdentifiers, config[key])
69
+ )
70
+ )
71
+ );
72
+
73
+ // Replace the arguments of codePush with the generated config object
74
+ path.node.arguments = [configObjectExpression];
75
+ }
76
+ },
77
+ },
78
+ };
79
+ };
80
+
81
+ /** loads config file from configPath */
82
+ function loadConfig(babel, configPath) {
83
+ if (!fs.existsSync(configPath)) {
84
+ throw new Error(
85
+ "code-push.config.js not found. Please ensure it exists in the root directory."
86
+ );
87
+ }
88
+
89
+ const { types: t } = babel;
90
+ const configModule = require(configPath);
91
+
92
+ const configCode = fs.readFileSync(configPath, "utf8");
93
+ const ast = parse(configCode, {
94
+ sourceType: "module",
95
+ });
96
+
97
+ // Extract import declarations and track imported identifiers
98
+ const imports = [];
99
+ const importedIdentifiers = new Set();
100
+
101
+ const convertRequireIntoImportStatement = (declaration) => {
102
+ const moduleName = declaration.init.arguments[0].value;
103
+ if (t.isIdentifier(declaration.id)) {
104
+ // Case for `const fs = require("fs")`
105
+ return t.importDeclaration(
106
+ [t.importDefaultSpecifier(declaration.id)],
107
+ t.stringLiteral(moduleName)
108
+ );
109
+ } else if (t.isObjectPattern(declaration.id)) {
110
+ // Case for `const { parse } = require("module")`
111
+ const importSpecifiers = declaration.id.properties.map((property) =>
112
+ t.importSpecifier(property.value, property.key)
113
+ );
114
+ return t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName));
115
+ }
116
+ };
117
+
118
+ ast.program.body.forEach((node) => {
119
+ if (t.isImportDeclaration(node)) {
120
+ // Handle import statements
121
+ imports.push(node);
122
+ node.specifiers.forEach((specifier) => {
123
+ importedIdentifiers.add(specifier.local.name);
124
+ });
125
+ } else if (t.isVariableDeclaration(node)) {
126
+ // Handle require function
127
+ node.declarations.forEach((declaration) => {
128
+ if (
129
+ t.isCallExpression(declaration.init) &&
130
+ t.isIdentifier(declaration.init.callee, { name: "require" }) &&
131
+ declaration.init.arguments.length === 1 &&
132
+ t.isStringLiteral(declaration.init.arguments[0])
133
+ ) {
134
+ const importDeclaration =
135
+ convertRequireIntoImportStatement(declaration);
136
+ imports.push(importDeclaration);
137
+ declaration.id.properties.forEach((dec) => {
138
+ importedIdentifiers.add(dec.value.name); // Track the imported identifier
139
+ });
140
+ }
141
+ });
142
+ }
143
+ });
144
+
145
+ return {
146
+ config: configModule.default || configModule,
147
+ configImports: imports,
148
+ importedIdentifiers,
149
+ };
150
+ }
151
+
152
+ /** Helper to serialize config values to AST nodes */
153
+ function serializeConfigToNode(babel, importedIdentifiers, value) {
154
+ const { types: t } = babel;
155
+ if (["string", "number", "boolean"].includes(typeof value) || value == null) {
156
+ return t.valueToNode(value); // Handle primitive values
157
+ }
158
+
159
+ if (Array.isArray(value)) {
160
+ return t.arrayExpression(
161
+ // Recursively handle arrays
162
+ value.map((v) => serializeConfigToNode(babel, importedIdentifiers, v))
163
+ );
164
+ }
165
+
166
+ if (typeof value === "object") {
167
+ return t.objectExpression(
168
+ Object.entries(value).map(([key, val]) =>
169
+ t.objectProperty(
170
+ t.identifier(key),
171
+ serializeConfigToNode(babel, importedIdentifiers, val)
172
+ )
173
+ )
174
+ );
175
+ }
176
+
177
+ // Use identifier for imported symbols instead of inlining
178
+ if (importedIdentifiers.has(value.name)) {
179
+ return t.identifier(value.name);
180
+ }
181
+
182
+ // For inline functions, parse and serialize them as expressions
183
+ if (typeof value === "function") {
184
+ const valueString = value.toString();
185
+ try {
186
+ return parseExpression(valueString, { sourceType: "module" });
187
+ } catch (error) {
188
+ throw new Error(
189
+ `Failed to parse function ${value.name || "anonymous"}: ${
190
+ error.message
191
+ }`
192
+ );
193
+ }
194
+ }
195
+
196
+ throw new Error(`Unsupported config value type: ${typeof value}`);
197
+ }