@react-native-oh/react-native-harmony 0.72.82 → 0.77.18

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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSE-Meta +21 -0
  3. package/Libraries/Alert/delegates/AlertDelegate.harmony.ts +84 -0
  4. package/Libraries/Alert/{AlertManager.ts → delegates/AlertManager.harmony.ts} +10 -4
  5. package/Libraries/Animated/shouldUseTurboAnimatedModule.harmony.ts +10 -0
  6. package/Libraries/Components/AccessibilityInfo/delegates/AccessibilityInfoDelegate.harmony.ts +44 -0
  7. package/Libraries/Components/AccessibilityInfo/{NativeAccessibilityInfoHarmony.ts → delegates/NativeAccessibilityInfoHarmony.harmony.ts} +9 -2
  8. package/Libraries/Components/Keyboard/delegates/KeyboardAvoidingViewDelegate.harmony.ts +42 -0
  9. package/Libraries/Components/RefreshControl/delegates/RefreshControlDelegate.harmony.tsx +29 -0
  10. package/Libraries/Components/SafeAreaView/SafeAreaView.harmony.tsx +93 -31
  11. package/Libraries/Components/ScrollView/delegates/ScrollViewDelegate.harmony.tsx +41 -0
  12. package/Libraries/Components/ScrollView/delegates/ScrollViewNativeComponentDelegate.harmony.ts +89 -0
  13. package/Libraries/Components/ScrollView/processDecelerationRate.harmony.ts +19 -0
  14. package/Libraries/Components/StatusBar/delegates/NativeStatusBarManagerHarmony.harmony.ts +53 -0
  15. package/Libraries/Components/StatusBar/delegates/StatusBarDelegate.harmony.ts +83 -0
  16. package/Libraries/Components/TextInput/delegates/TextInputDelegate.harmony.tsx +98 -0
  17. package/Libraries/Components/TextInput/delegates/TextInputStateDelegate.harmony.tsx +20 -0
  18. package/Libraries/Components/Touchable/delegates/TouchableHighlightDelegate.harmony.ts +14 -0
  19. package/Libraries/Components/Touchable/delegates/TouchableNativeFeedbackDelegate.harmony.ts +14 -0
  20. package/Libraries/Components/Touchable/delegates/TouchableWithoutFeedbackDelegate.harmony.ts +14 -0
  21. package/Libraries/Components/delegates/ButtonDelegate.harmony.ts +41 -0
  22. package/Libraries/Core/setUpPlatform.harmony.js +30 -0
  23. package/Libraries/Image/AssetSourceResolver.harmony.ts +75 -29
  24. package/Libraries/Image/Image.harmony.ts +17 -0
  25. package/Libraries/NativeComponent/BaseViewConfig.harmony.js +12 -326
  26. package/Libraries/NativeComponent/delegates/ViewConfigIgnoreDelegate.harmony.ts +13 -0
  27. package/Libraries/ReactNative/delegates/BridgelessUIManagerDelegate.harmony.ts +14 -0
  28. package/Libraries/ReactNative/delegates/I18nManagerDelegate.harmony.ts +22 -0
  29. package/Libraries/Settings/Settings.harmony.ts +20 -0
  30. package/Libraries/Share/delegates/ShareDelegate.harmony.ts +42 -0
  31. package/Libraries/StyleSheet/NativePlatformColor.harmony.ts +15 -0
  32. package/Libraries/StyleSheet/PlatformColorValueTypes.harmony.ts +8 -1
  33. package/Libraries/Utilities/BackHandler.harmony.ts +10 -0
  34. package/Libraries/Utilities/NativePlatformConstantsHarmony.harmony.ts +17 -0
  35. package/Libraries/Utilities/Platform.harmony.ts +38 -13
  36. package/Libraries/Vibration/delegates/VibrationDelegate.harmony.ts +14 -0
  37. package/NOTICE.md +846 -0
  38. package/README.md +2 -2
  39. package/index.js +53 -63
  40. package/jest.config.js +0 -7
  41. package/metro.config.d.ts +17 -0
  42. package/metro.config.js +398 -115
  43. package/package.json +58 -37
  44. package/react-native.config.js +57 -9
  45. package/react_native_openharmony.har +0 -0
  46. package/tsconfig.json +10 -4
  47. package/types/index.harmony.d.ts +99 -0
  48. package/Libraries/Alert/Alert.harmony.js +0 -77
  49. package/Libraries/Animated/NativeAnimatedHelper.harmony.js +0 -601
  50. package/Libraries/Components/AccessibilityInfo/AccessibilityInfo.harmony.js +0 -441
  51. package/Libraries/Components/Button/Button.harmony.js +0 -451
  52. package/Libraries/Components/Image/Image.flow.harmony.js +0 -53
  53. package/Libraries/Components/Image/Image.harmony.js +0 -317
  54. package/Libraries/Components/Image/NativeImageLoaderHarmony.js +0 -38
  55. package/Libraries/Components/Keyboard/KeyboardAvoidingView.harmony.js +0 -256
  56. package/Libraries/Components/RefreshControl/RefreshControl.harmony.js +0 -210
  57. package/Libraries/Components/ScrollView/ScrollView.harmony.js +0 -1951
  58. package/Libraries/Components/ScrollView/processDecelerationRate.harmony.js +0 -24
  59. package/Libraries/Components/StatusBar/NativeStatusBarManagerHarmony.js +0 -71
  60. package/Libraries/Components/StatusBar/StatusBar.harmony.js +0 -447
  61. package/Libraries/Components/TextInput/TextInput.harmony.js +0 -1716
  62. package/Libraries/Components/TextInput/TextInputState.harmony.js +0 -220
  63. package/Libraries/Components/Touchable/TouchableHighlight.harmony.js +0 -396
  64. package/Libraries/Components/Touchable/TouchableNativeFeedback.harmony.js +0 -364
  65. package/Libraries/Components/Touchable/TouchableWithoutFeedback.harmony.js +0 -227
  66. package/Libraries/Components/View/View.harmony.js +0 -149
  67. package/Libraries/Core/setUpReactDevTools.harmony.js +0 -93
  68. package/Libraries/ReactNative/I18nManager.harmony.js +0 -78
  69. package/Libraries/ReactNative/UIManager.harmony.js +0 -210
  70. package/Libraries/Settings/Settings.harmony.js +0 -15
  71. package/Libraries/Share/Share.harmony.js +0 -174
  72. package/Libraries/StyleSheet/NativePlatformColor.ts +0 -8
  73. package/Libraries/Utilities/BackHandler.harmony.js +0 -109
  74. package/Libraries/Utilities/NativePlatformConstants.harmony.ts +0 -8
  75. package/Libraries/Utilities/Platform.d.ts +0 -117
  76. package/Libraries/Utilities/createPerformanceLogger.harmony.js +0 -328
  77. package/Libraries/Vibration/Vibration.harmony.js +0 -88
  78. package/harmony/.keep +0 -0
  79. package/harmony/rnoh-hvigor-plugin-0.2.0.tgz +0 -0
  80. package/react_native_openharmony_release.har +0 -0
  81. package/types/index.d.ts +0 -108
package/metro.config.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) 2024 Huawei Technologies Co., Ltd.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
- * LICENSE-MIT file in the root directory of this source tree.
5
+ * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
8
  //@ts-check
@@ -10,7 +10,8 @@ const pathUtils = require('path');
10
10
  const fs = require('fs');
11
11
  const colors = require('colors/safe');
12
12
 
13
- let shouldPrintInfoAboutRNRedirection = true;
13
+ const HARMONY_PLATFORM_NAME = 'harmony';
14
+ const RNOH_FALLBACK_PLATFORM_NAME = 'ios';
14
15
 
15
16
  /**
16
17
  * @param msg {string}
@@ -21,12 +22,28 @@ function info(msg) {
21
22
  }
22
23
 
23
24
  /**
24
- * @param options {{reactNativeHarmonyPackageName: string} | undefined}
25
+ * @type {string | null}
26
+ */
27
+ let REQUEST_RESOLUTION_LATEST_PLATFORM = null;
28
+
29
+ /**
30
+ * @param options {import("./metro.config").HarmonyMetroConfigOptions}
25
31
  * @returns {import("metro-config").InputConfigT}
26
32
  */
27
33
  function createHarmonyMetroConfig(options) {
28
- const reactNativeHarmonyName =
34
+ /**
35
+ * The default value needs to be changed to @react-native-oh/react-native-harmony but this is a breaking change.
36
+ */
37
+ const reactNativeHarmonyPackageName =
29
38
  options?.reactNativeHarmonyPackageName ?? 'react-native-harmony';
39
+ const reactNativeHarmonyPattern =
40
+ options?.__reactNativeHarmonyPattern ??
41
+ pathUtils.sep +
42
+ reactNativeHarmonyPackageName.replace('/', pathUtils.sep) +
43
+ pathUtils.sep;
44
+ const reactNativeInteropLibraryPackagePattern =
45
+ options?.__reactNativeInteropLibraryPackagePattern;
46
+
30
47
  return {
31
48
  transformer: {
32
49
  assetRegistryPath: 'react-native/Libraries/Image/AssetRegistry',
@@ -37,45 +54,149 @@ function createHarmonyMetroConfig(options) {
37
54
  },
38
55
  }),
39
56
  },
57
+ serializer: {
58
+ getModulesRunBeforeMainModule: () => {
59
+ if (REQUEST_RESOLUTION_LATEST_PLATFORM !== 'harmony') {
60
+ return [];
61
+ }
62
+ return [require.resolve('./Libraries/Core/InitializeCore')];
63
+ },
64
+ },
40
65
  resolver: {
41
- blockList: [/third-party(\\\\|\\|\/)hermes/],
66
+ blockList: [/\.cxx/],
42
67
  resolveRequest: (ctx, moduleName, platform) => {
43
- if (platform === 'harmony') {
44
- if (shouldPrintInfoAboutRNRedirection) {
45
- info(
46
- `Redirected imports from ${colors.bold(
47
- colors.gray('react-native')
48
- )} to ${colors.bold(reactNativeHarmonyName)}`
68
+ REQUEST_RESOLUTION_LATEST_PLATFORM = platform;
69
+ const nodeModulesPaths = [
70
+ pathUtils.resolve('node_modules'),
71
+ ...(ctx.nodeModulesPaths ?? []),
72
+ ];
73
+ if (platform === HARMONY_PLATFORM_NAME) {
74
+ if (
75
+ moduleName === 'react-native' ||
76
+ moduleName.startsWith(`react-native/`)
77
+ ) {
78
+ /**
79
+ * Importing from `react-native` when preparing offline bundle.
80
+ * For some reason there's a difference in behavior when a bundle is provided via Metro server and when creating an offline bundle.
81
+ */
82
+ const newModuleName = moduleName.replace(
83
+ 'react-native',
84
+ reactNativeHarmonyPackageName
49
85
  );
50
- shouldPrintInfoAboutRNRedirection = false;
51
- }
52
- if (moduleName === 'react-native') {
53
- return ctx.resolveRequest(ctx, reactNativeHarmonyName, platform);
54
- } else if (moduleName.startsWith('react-native/')) {
55
- return ctx.resolveRequest(ctx, moduleName, 'ios');
86
+ /**
87
+ * Special case for a library which hijacks imports to react-native-harmony.
88
+ */
89
+ const maybeInteropLibraryResult = resolveIfInteropLibraryRequest(
90
+ ctx,
91
+ newModuleName,
92
+ nodeModulesPaths,
93
+ reactNativeHarmonyPackageName,
94
+ reactNativeInteropLibraryPackagePattern
95
+ );
96
+ if (maybeInteropLibraryResult) {
97
+ return maybeInteropLibraryResult;
98
+ }
99
+ try {
100
+ return ctx.resolveRequest(
101
+ ctx,
102
+ newModuleName,
103
+ HARMONY_PLATFORM_NAME
104
+ );
105
+ } catch {
106
+ return ctx.resolveRequest(
107
+ ctx,
108
+ newModuleName,
109
+ RNOH_FALLBACK_PLATFORM_NAME
110
+ );
111
+ }
56
112
  } else if (
57
- isInternalReactNativeRelativeImport(ctx.originModulePath)
113
+ moduleName === reactNativeHarmonyPackageName ||
114
+ moduleName.startsWith(`${reactNativeHarmonyPackageName}/`)
58
115
  ) {
59
- if (moduleName.startsWith('.')) {
60
- const moduleAbsPath = pathUtils.resolve(
61
- pathUtils.dirname(ctx.originModulePath),
62
- moduleName
63
- );
64
- const [_, modulePathRelativeToReactNative] = moduleAbsPath.split(
65
- `${pathUtils.sep}node_modules${pathUtils.sep}react-native${pathUtils.sep}`
116
+ /**
117
+ * Importing from `react-native` when bundle is provided from Metro server.
118
+ *
119
+ * `moduleName` is equal here to the value provided in
120
+ * react-native-harmony/react-native.config.js::config::platforms::harmony::npmPackageName
121
+ * when importing from `react-native`.
122
+ */
123
+
124
+ /**
125
+ * Special case for a library which hijacks imports to react-native-harmony.
126
+ */
127
+ const result = resolveIfInteropLibraryRequest(
128
+ ctx,
129
+ moduleName,
130
+ nodeModulesPaths,
131
+ reactNativeHarmonyPackageName,
132
+ reactNativeInteropLibraryPackagePattern
133
+ );
134
+ if (result) {
135
+ return result;
136
+ }
137
+
138
+ try {
139
+ return ctx.resolveRequest(ctx, moduleName, HARMONY_PLATFORM_NAME);
140
+ } catch {
141
+ return ctx.resolveRequest(
142
+ ctx,
143
+ moduleName,
144
+ RNOH_FALLBACK_PLATFORM_NAME
66
145
  );
67
- try {
68
- return ctx.resolveRequest(
69
- ctx,
70
- `${reactNativeHarmonyName}${pathUtils.sep}${modulePathRelativeToReactNative}`,
71
- 'harmony'
146
+ }
147
+ } else if (ctx.originModulePath.includes(reactNativeHarmonyPattern)) {
148
+ const rnInteropLibraryPackage =
149
+ getHarmonyPackageByAliasMap(nodeModulesPaths)[
150
+ reactNativeHarmonyPackageName
151
+ ];
152
+ // Redirect internal react-native-harmony imports to the interop package
153
+ if (rnInteropLibraryPackage && moduleName.startsWith('.')) {
154
+ const rnInteropLibraryPackageName = rnInteropLibraryPackage.name;
155
+ const redirectInternalImports =
156
+ rnInteropLibraryPackage.redirectInternalImports;
157
+ if (redirectInternalImports) {
158
+ const moduleAbsPath = pathUtils.resolve(
159
+ pathUtils.dirname(ctx.originModulePath),
160
+ moduleName
72
161
  );
73
- } catch (err) {}
162
+
163
+ try {
164
+ // We have to replace either /react-native-harmony/ or /@react-native-oh/react-native-harmony/ with the interop package name.
165
+ const newModuleName = moduleAbsPath.replace(
166
+ reactNativeHarmonyPattern,
167
+ reactNativeInteropLibraryPackagePattern ??
168
+ getRNInteropLibraryPackagePattern(
169
+ rnInteropLibraryPackageName
170
+ )
171
+ );
172
+
173
+ return ctx.resolveRequest(
174
+ ctx,
175
+ newModuleName,
176
+ HARMONY_PLATFORM_NAME
177
+ );
178
+ } catch {}
179
+ }
180
+ }
181
+ // Internal RN imports
182
+ const maybeResult = resolveRequestOnlyForHarmony(ctx, moduleName);
183
+ if (maybeResult) {
184
+ return maybeResult;
74
185
  }
75
- return ctx.resolveRequest(ctx, moduleName, 'ios');
76
- } else if(isHarmonyPackageInternalImport(ctx.originModulePath, moduleName)) {
186
+ return ctx.resolveRequest(
187
+ ctx,
188
+ moduleName,
189
+ RNOH_FALLBACK_PLATFORM_NAME
190
+ );
191
+ } else if (
192
+ isHarmonyPackageInternalImport(
193
+ nodeModulesPaths,
194
+ ctx.originModulePath,
195
+ moduleName
196
+ )
197
+ ) {
77
198
  /**
78
- * Replace internal imports in `react-native-foo` with equivalent files from `react-native-harmony-foo`
199
+ * Replace internal imports in `react-native-foo` with equivalent files from `react-native-harmony-foo`
79
200
  * if a package has internal import redirection enabled in its package.json configuration e.g.
80
201
  *
81
202
  * react-native-harmony-foo/package.json:
@@ -84,30 +205,52 @@ function createHarmonyMetroConfig(options) {
84
205
  * "redirectInternalImports": true,
85
206
  * }
86
207
  */
87
- const alias = getPackageNameFromOriginModulePath(ctx.originModulePath);
208
+ const alias = getPackageNameFromOriginModulePath(
209
+ ctx.originModulePath
210
+ );
211
+
88
212
  if (alias) {
89
- const harmonyPackage = getHarmonyPackageByAliasMap(".");
213
+ const harmonyPackage =
214
+ getHarmonyPackageByAliasMap(nodeModulesPaths);
215
+
90
216
  const harmonyPackageName = harmonyPackage[alias]?.name;
91
- const redirectInternalImports = harmonyPackage[alias]?.redirectInternalImports;
92
- if (harmonyPackageName && !isRequestFromHarmonyPackage(ctx.originModulePath, harmonyPackageName) && redirectInternalImports) {
217
+
218
+ const redirectInternalImports =
219
+ harmonyPackage[alias]?.redirectInternalImports;
220
+
221
+ if (
222
+ harmonyPackageName &&
223
+ !isRequestFromHarmonyPackage(
224
+ ctx.originModulePath,
225
+ harmonyPackageName
226
+ ) &&
227
+ redirectInternalImports
228
+ ) {
93
229
  const moduleAbsPath = pathUtils.resolve(
94
230
  pathUtils.dirname(ctx.originModulePath),
95
- moduleName,
231
+ moduleName
96
232
  );
233
+
97
234
  const slashes = new RegExp('/', 'g');
98
- const [_, modulePathRelativeToOriginalPackage] = moduleAbsPath.split(
99
- `${pathUtils.sep}node_modules${pathUtils.sep}${alias.replace(slashes, pathUtils.sep)}${pathUtils.sep}`,
100
- );
235
+ const [_, modulePathRelativeToOriginalPackage] =
236
+ moduleAbsPath.split(
237
+ `${pathUtils.sep}node_modules${
238
+ pathUtils.sep
239
+ }${alias.replace(slashes, pathUtils.sep)}${pathUtils.sep}`
240
+ );
101
241
  const backslashes = new RegExp('\\\\', 'g');
102
- const pathToHarmonyModule = `${harmonyPackageName}/${modulePathRelativeToOriginalPackage.replace(backslashes, "/")}`;
242
+ const newModuleName = `${harmonyPackageName}/${modulePathRelativeToOriginalPackage.replace(
243
+ backslashes,
244
+ '/'
245
+ )}`;
103
246
  try {
104
247
  return ctx.resolveRequest(
105
248
  ctx,
106
- pathToHarmonyModule,
107
- 'harmony',
249
+ newModuleName,
250
+ HARMONY_PLATFORM_NAME
108
251
  );
109
- } catch (err) {
110
- }
252
+ } catch (err) {}
253
+ } else {
111
254
  }
112
255
  }
113
256
  } else {
@@ -119,7 +262,8 @@ function createHarmonyMetroConfig(options) {
119
262
  * "alias": "react-native-foo"
120
263
  * }
121
264
  */
122
- const harmonyPackageByAlias = getHarmonyPackageByAliasMap(".");
265
+ const harmonyPackageByAlias =
266
+ getHarmonyPackageByAliasMap(nodeModulesPaths);
123
267
  const alias = getPackageName(moduleName);
124
268
  if (alias) {
125
269
  const harmonyPackageName = harmonyPackageByAlias[alias]?.name;
@@ -149,6 +293,84 @@ module.exports = {
149
293
  createHarmonyMetroConfig,
150
294
  };
151
295
 
296
+ /**
297
+ * @param ctx {Parameters<NonNullable<import("metro-config").ResolverConfigT["resolveRequest"]>>[0]}
298
+ * @param moduleName {string}
299
+ * @param nodeModulesPaths {string[]}
300
+ * @param reactNativeHarmonyPackageName {string}
301
+ * @param reactNativeInteropLibraryPackagePattern {string | undefined}
302
+ */
303
+ function resolveIfInteropLibraryRequest(
304
+ ctx,
305
+ moduleName,
306
+ nodeModulesPaths,
307
+ reactNativeHarmonyPackageName,
308
+ reactNativeInteropLibraryPackagePattern
309
+ ) {
310
+ const rnInteropLibraryPackageName =
311
+ getHarmonyPackageByAliasMap(nodeModulesPaths)[reactNativeHarmonyPackageName]
312
+ ?.name;
313
+
314
+ /**
315
+ * We have to check if the module is not resolved from interop package
316
+ * to prevent an infinite resolution loop caused by a circular dependency.
317
+ * e.g.
318
+ * origin module: react-native-harmony/Libraries/Image/ImageSourceUtils.js
319
+ * redirected to module: react-native-harmony-61-interop/Libraries/Image/ImageSourceUtils.js
320
+ * and react-native-harmony-61-interop/Libraries/Image/ImageSourceUtils.js imports react-native-harmony/Libraries/Image/ImageSourceUtils.js
321
+ * This creates a circular dependency, causing an infinite resolution loop.
322
+ */
323
+ if (
324
+ rnInteropLibraryPackageName &&
325
+ !ctx.originModulePath.includes(
326
+ reactNativeInteropLibraryPackagePattern ??
327
+ getRNInteropLibraryPackagePattern(rnInteropLibraryPackageName)
328
+ )
329
+ ) {
330
+ try {
331
+ const newModuleName = moduleName.replace(
332
+ reactNativeHarmonyPackageName,
333
+ rnInteropLibraryPackageName
334
+ );
335
+ return ctx.resolveRequest(ctx, newModuleName, HARMONY_PLATFORM_NAME);
336
+ } catch {}
337
+ }
338
+ return null;
339
+ }
340
+
341
+ /**
342
+ * Let's say we have following files:
343
+ * foo.js
344
+ * foo.harmony.tsx
345
+ *
346
+ * By default, in that situation foo.js will be resolved. This function however chooses foo.harmony.tsx.
347
+ * In the past, RNOH redirected imports back to the RN package, and RNOH used different extensions than original files.
348
+ *
349
+ * @param ctx {Parameters<NonNullable<import("metro-config").ResolverConfigT["resolveRequest"]>>[0]}
350
+ * @param moduleName {string}
351
+ */
352
+ function resolveRequestOnlyForHarmony(ctx, moduleName) {
353
+ for (const sourceExt of ctx.sourceExts) {
354
+ const newCtx = { ...ctx };
355
+ newCtx.sourceExts = [sourceExt];
356
+ try {
357
+ const result = ctx.resolveRequest(
358
+ newCtx,
359
+ moduleName,
360
+ HARMONY_PLATFORM_NAME
361
+ );
362
+ if (result.type === 'sourceFile') {
363
+ const lastDotIndex = result.filePath.lastIndexOf('.');
364
+ const beforeLastDot = result.filePath.substring(0, lastDotIndex);
365
+ if (beforeLastDot.endsWith('.' + HARMONY_PLATFORM_NAME)) {
366
+ return result;
367
+ }
368
+ }
369
+ } catch {}
370
+ }
371
+ return null;
372
+ }
373
+
152
374
  /**
153
375
  * @param moduleName {string}
154
376
  * @returns {string | null}
@@ -176,52 +398,53 @@ function getPackageName(moduleName) {
176
398
  * @returns {string}
177
399
  */
178
400
  function getPackageNameFromOriginModulePath(originModulePath) {
179
- const nodeModulesPosition = originModulePath.search("node_modules");
180
- const pathRelativeToNodeModules = originModulePath.substring(nodeModulesPosition);
401
+ const nodeModulesPosition = originModulePath.search('node_modules');
402
+ const pathRelativeToNodeModules =
403
+ originModulePath.substring(nodeModulesPosition);
181
404
  const pathSegments = pathRelativeToNodeModules.split(pathUtils.sep);
182
405
  const module = pathSegments[1];
183
406
  if (module.startsWith('@')) {
184
407
  return `${pathSegments[1]}/${pathSegments[2]}`;
185
- }
186
- else {
408
+ } else {
187
409
  return pathSegments[1];
188
410
  }
189
411
  }
190
412
 
191
413
  /**
414
+ * @param nodeModulesPaths {readonly string[]}
192
415
  * @param originModulePath {string}
193
416
  * @param moduleName {string}
194
417
  * @returns {boolean}
195
418
  */
196
- function isHarmonyPackageInternalImport(originModulePath, moduleName) {
197
- if (moduleName.startsWith(".")) {
419
+ function isHarmonyPackageInternalImport(
420
+ nodeModulesPaths,
421
+ originModulePath,
422
+ moduleName
423
+ ) {
424
+ if (moduleName.startsWith('.')) {
198
425
  const alias = getPackageNameFromOriginModulePath(originModulePath);
199
426
  const slashes = new RegExp('/', 'g');
200
- if (alias && originModulePath.includes(`${pathUtils.sep}node_modules${pathUtils.sep}${alias.replace(slashes, pathUtils.sep)}${pathUtils.sep}`)) {
201
- const harmonyPackage = getHarmonyPackageByAliasMap(".");
427
+ if (
428
+ alias &&
429
+ originModulePath.includes(
430
+ `${pathUtils.sep}node_modules${pathUtils.sep}${alias.replace(
431
+ slashes,
432
+ pathUtils.sep
433
+ )}${pathUtils.sep}`
434
+ )
435
+ ) {
436
+ const harmonyPackage = getHarmonyPackageByAliasMap(nodeModulesPaths);
202
437
  const harmonyPackageName = harmonyPackage[alias]?.name;
203
438
  if (
204
439
  harmonyPackageName &&
205
- !isRequestFromHarmonyPackage(
206
- originModulePath,
207
- harmonyPackageName,
208
- )
440
+ !isRequestFromHarmonyPackage(originModulePath, harmonyPackageName)
209
441
  ) {
210
442
  return true;
211
443
  }
212
444
  }
213
445
  }
214
- return false;
215
- }
216
446
 
217
- /**
218
- * @param originModulePath {string}
219
- * @returns {boolean}
220
- */
221
- function isInternalReactNativeRelativeImport(originModulePath) {
222
- return originModulePath.includes(
223
- `${pathUtils.sep}node_modules${pathUtils.sep}react-native${pathUtils.sep}`
224
- );
447
+ return false;
225
448
  }
226
449
 
227
450
  /**
@@ -232,6 +455,7 @@ function isInternalReactNativeRelativeImport(originModulePath) {
232
455
  function isRequestFromHarmonyPackage(originModulePath, harmonyPackageName) {
233
456
  const slashes = new RegExp('/', 'g');
234
457
  const packagePath = harmonyPackageName.replace(slashes, pathUtils.sep);
458
+
235
459
  return originModulePath.includes(
236
460
  `${pathUtils.sep}node_modules${pathUtils.sep}${packagePath}${pathUtils.sep}`
237
461
  );
@@ -243,9 +467,9 @@ function isRequestFromHarmonyPackage(originModulePath, harmonyPackageName) {
243
467
  let cachedHarmonyPackageByAliasMap = undefined;
244
468
 
245
469
  /**
246
- * @param projectRootPath {string}
470
+ * @param nodeModulesPaths {readonly string[]}
247
471
  */
248
- function getHarmonyPackageByAliasMap(projectRootPath) {
472
+ function getHarmonyPackageByAliasMap(nodeModulesPaths) {
249
473
  /**
250
474
  * @type {Record<string, {name: string, redirectInternalImports: boolean}>}
251
475
  */
@@ -254,51 +478,71 @@ function getHarmonyPackageByAliasMap(projectRootPath) {
254
478
  return cachedHarmonyPackageByAliasMap;
255
479
  }
256
480
  cachedHarmonyPackageByAliasMap = findHarmonyNodeModulePaths(
257
- findHarmonyNodeModuleSearchPaths(projectRootPath),
258
- ).reduce((acc, harmonyNodeModulePath) => {
259
- const harmonyNodeModulePathSegments = harmonyNodeModulePath.split(
260
- pathUtils.sep,
261
- );
262
- let harmonyNodeModuleName =
263
- harmonyNodeModulePathSegments[harmonyNodeModulePathSegments.length - 1];
264
- if (harmonyNodeModulePathSegments.length > 1) {
265
- const harmonyNodeModuleParentDirName =
266
- harmonyNodeModulePathSegments[harmonyNodeModulePathSegments.length - 2];
267
- if (harmonyNodeModuleParentDirName.startsWith('@')) {
268
- harmonyNodeModuleName = `${harmonyNodeModuleParentDirName}/${harmonyNodeModuleName}`;
481
+ findHarmonyNodeModuleSearchPaths(nodeModulesPaths)
482
+ ).reduce(
483
+ (
484
+ acc,
485
+ {
486
+ resolvedPath: harmonyNodeModuleResolvedPath,
487
+ unresolvedPath: harmonyNodeModuleUnresolvedPath,
269
488
  }
270
- }
271
- const packageJSONPath = `${harmonyNodeModulePath}${pathUtils.sep}package.json`;
272
- const packageJSON = readHarmonyModulePackageJSON(packageJSONPath);
273
- const alias = packageJSON.harmony?.alias;
274
- const redirectInternalImports = packageJSON?.harmony?.redirectInternalImports ?? false;
275
- if (alias) {
276
- acc[alias] ={
277
- name: harmonyNodeModuleName,
278
- redirectInternalImports: redirectInternalImports
489
+ ) => {
490
+ // use unresolved path for monorepos to find actual package name (which can be installed under an alias)
491
+ const harmonyNodeModulePath = harmonyNodeModuleResolvedPath.includes(
492
+ 'node_modules'
493
+ )
494
+ ? harmonyNodeModuleResolvedPath
495
+ : harmonyNodeModuleUnresolvedPath;
496
+
497
+ const harmonyNodeModulePathSegments = harmonyNodeModulePath.split(
498
+ pathUtils.sep
499
+ );
500
+
501
+ let harmonyNodeModuleName =
502
+ harmonyNodeModulePathSegments[harmonyNodeModulePathSegments.length - 1];
503
+ if (harmonyNodeModulePathSegments.length > 1) {
504
+ const harmonyNodeModuleParentDirName =
505
+ harmonyNodeModulePathSegments[
506
+ harmonyNodeModulePathSegments.length - 2
507
+ ];
508
+ if (harmonyNodeModuleParentDirName.startsWith('@')) {
509
+ harmonyNodeModuleName = `${harmonyNodeModuleParentDirName}/${harmonyNodeModuleName}`;
510
+ }
279
511
  }
280
- }
281
- return acc;
282
- }, initialAcc);
512
+ const packageJsonPath = `${harmonyNodeModuleResolvedPath}${pathUtils.sep}package.json`;
513
+ const packageJson = readHarmonyModulePackageJSON(packageJsonPath);
514
+ const alias = packageJson.harmony?.alias;
515
+ const redirectInternalImports =
516
+ packageJson?.harmony?.redirectInternalImports ?? false;
517
+ if (alias) {
518
+ acc[alias] = {
519
+ name: harmonyNodeModuleName,
520
+ redirectInternalImports: redirectInternalImports,
521
+ };
522
+ }
523
+ return acc;
524
+ },
525
+ initialAcc
526
+ );
283
527
  const harmonyPackagesCount = Object.keys(
284
- cachedHarmonyPackageByAliasMap,
528
+ cachedHarmonyPackageByAliasMap
285
529
  ).length;
286
530
  if (harmonyPackagesCount > 0) {
287
531
  const prettyHarmonyPackagesCount = colors.bold(
288
532
  harmonyPackagesCount > 0
289
533
  ? colors.green(harmonyPackagesCount.toString())
290
- : harmonyPackagesCount.toString(),
534
+ : harmonyPackagesCount.toString()
291
535
  );
292
536
  info(
293
- `Redirected imports to ${prettyHarmonyPackagesCount} harmony-specific third-party package(s):`,
537
+ `Redirected imports to ${prettyHarmonyPackagesCount} harmony-specific third-party package(s):`
294
538
  );
295
539
  if (harmonyPackagesCount > 0) {
296
540
  Object.entries(cachedHarmonyPackageByAliasMap).forEach(
297
- ([original, {name: alias}]) => {
541
+ ([original, { name: alias }]) => {
298
542
  info(
299
- `• ${colors.bold(colors.gray(original))} → ${colors.bold(alias)}`,
543
+ `• ${colors.bold(colors.gray(original))} → ${colors.bold(alias)}`
300
544
  );
301
- },
545
+ }
302
546
  );
303
547
  }
304
548
  } else {
@@ -309,30 +553,55 @@ function getHarmonyPackageByAliasMap(projectRootPath) {
309
553
  }
310
554
 
311
555
  /**
312
- * @param projectRootPath {string}
556
+ * @param nodeModulesPaths {readonly string[]}
313
557
  * @returns {string[]}
314
558
  */
315
- function findHarmonyNodeModuleSearchPaths(projectRootPath) {
316
- const nodeModulesPath = `${projectRootPath}${pathUtils.sep}node_modules`;
317
- const searchPaths = fs
318
- .readdirSync(nodeModulesPath)
319
- .filter((dirName) => dirName.startsWith('@'))
320
- .map((dirName) => `${nodeModulesPath}${pathUtils.sep}${dirName}`);
321
- searchPaths.push(nodeModulesPath);
559
+ function findHarmonyNodeModuleSearchPaths(nodeModulesPaths) {
560
+ /**
561
+ * @type string[]
562
+ */
563
+ let searchPaths = [];
564
+ for (const nodeModulesPath of nodeModulesPaths) {
565
+ if (fs.existsSync(nodeModulesPath)) {
566
+ fs.readdirSync(nodeModulesPath)
567
+ .filter((dirName) => dirName.startsWith('@'))
568
+ .forEach((dirName) =>
569
+ searchPaths.push(`${nodeModulesPath}${pathUtils.sep}${dirName}`)
570
+ );
571
+ searchPaths.push(nodeModulesPath);
572
+ }
573
+ }
322
574
  return searchPaths;
323
575
  }
324
576
 
325
577
  /**
326
578
  * @param searchPaths {string[]}
327
- * @returns {string[]}
579
+ * @returns {{resolvedPath:string, unresolvedPath: string}[]}
328
580
  */
329
581
  function findHarmonyNodeModulePaths(searchPaths) {
330
582
  return searchPaths
331
583
  .map((searchPath) => {
332
584
  return fs
333
- .readdirSync(searchPath)
334
- .map((dirName) => `${searchPath}${pathUtils.sep}${dirName}`)
335
- .filter(hasPackageJSON);
585
+ .readdirSync(searchPath, { withFileTypes: true })
586
+ .map((dirent) => {
587
+ const direntPath =
588
+ (dirent.parentPath ?? dirent.path) + pathUtils.sep + dirent.name;
589
+ if (dirent.isSymbolicLink()) {
590
+ return {
591
+ resolvedPath: pathUtils.resolve(
592
+ dirent.parentPath ?? dirent.path,
593
+ fs.readlinkSync(direntPath)
594
+ ),
595
+ unresolvedPath: pathUtils.resolve(
596
+ dirent.parentPath ?? dirent.path,
597
+ direntPath
598
+ ),
599
+ };
600
+ } else {
601
+ return { resolvedPath: direntPath, unresolvedPath: direntPath };
602
+ }
603
+ })
604
+ .filter(({ resolvedPath }) => hasPackageJSON(resolvedPath));
336
605
  })
337
606
  .flat();
338
607
  }
@@ -342,7 +611,12 @@ function findHarmonyNodeModulePaths(searchPaths) {
342
611
  * @returns {boolean}
343
612
  */
344
613
  function hasPackageJSON(nodeModulePath) {
345
- if (!fs.lstatSync(nodeModulePath).isDirectory()) return false;
614
+ if (!fs.existsSync(nodeModulePath)) {
615
+ return false;
616
+ }
617
+ if (!fs.lstatSync(nodeModulePath).isDirectory()) {
618
+ return false;
619
+ }
346
620
  const nodeModuleContentNames = fs.readdirSync(nodeModulePath);
347
621
  return nodeModuleContentNames.includes('package.json');
348
622
  }
@@ -354,3 +628,12 @@ function hasPackageJSON(nodeModulePath) {
354
628
  function readHarmonyModulePackageJSON(packageJSONPath) {
355
629
  return JSON.parse(fs.readFileSync(packageJSONPath).toString());
356
630
  }
631
+
632
+ /**
633
+ * @param rnInteropLibraryPackageName {string}
634
+ * @param isInMonorepo {boolean}
635
+ * @returns {string} - Either package name without the scope or the full package name with platform specific separator
636
+ */
637
+ function getRNInteropLibraryPackagePattern(rnInteropLibraryPackageName) {
638
+ return `${pathUtils.sep}${rnInteropLibraryPackageName.replace('/', pathUtils.sep)}${pathUtils.sep}`;
639
+ }