1import { Podspec } from '../../../CocoaPods';
2import { FileTransforms } from '../../../Transforms.types';
3import { VersioningModuleConfig } from '../types';
4
5const objcFilesPattern = '*.{h,m,mm,cpp}';
6const swiftFilesPattern = '*.swift';
7
8export function getCommonExpoModulesTransforms(prefix: string): FileTransforms {
9  return {
10    path: [
11      // Here we prefix names of Objective-C/C++ files.
12      // There is no need to do the same for Swift files
13      // as we don't change the symbols inside. In Swift we don't use `EX` nor `UM`
14      // for symbols as the framework name takes part of the symbol signature,
15      // so there is no way we'll get duplicate symbols compilation errors.
16      // Files starting with `Expo` needs to be prefixed though,
17      // for umbrella headers (e.g. `ExpoModulesCore.h`).
18      {
19        find: /\b(Expo|EX|UM|EAS)([^/]*\.)(h|m|mm|cpp)\b/,
20        replaceWith: `${prefix}$1$2$3`,
21      },
22      {
23        // versioning category files, e.g. RCTComponentData+Privates.h
24        find: /\b(RCT)([^/]*)\+([^/]*\.)(h|m|mm|cpp)\b/,
25        replaceWith: `${prefix}$1$2+$3$4`,
26      },
27      {
28        // expo-gl
29        find: /\bEXWebGL([^/]*)\.def\b/,
30        replaceWith: `${prefix}EXWebGL$1.def`,
31      },
32    ],
33    content: [
34      {
35        find: /\bReact(?!Common)/g,
36        replaceWith: `${prefix}React`,
37      },
38      {
39        // Prefix symbols and imports.
40        find: /\b(EX|UM|RCT|ExpoBridgeModule)/g,
41        replaceWith: `${prefix}$1`,
42      },
43
44      // Only Swift
45
46      {
47        paths: swiftFilesPattern,
48        find: /\bimport (Expo|EX|EAS)(\w+)/g,
49        replaceWith: `import ${prefix}$1$2`,
50      },
51      {
52        paths: swiftFilesPattern,
53        find: /@objc\((Expo|EX|EAS)/g,
54        replaceWith: `@objc(${prefix}$1`,
55      },
56      {
57        paths: swiftFilesPattern,
58        find: /(for)?[rR](eactTag)/gi,
59        replaceWith: (_, p1, p2) => `${p1 ?? ''}${p1 ? prefix : prefix.toLowerCase()}R${p2}`,
60      },
61      {
62        // Prefixes name of the Expo modules provider.
63        paths: swiftFilesPattern,
64        find: /"(ExpoModulesProvider)"/g,
65        replaceWith: `"${prefix}$1"`,
66      },
67
68      // Only Objective-C
69
70      {
71        // Prefix `Expo*` frameworks in imports.
72        paths: objcFilesPattern,
73        find: /#(import |include |if __has_include\()<(Expo|EAS)(.*?)\//g,
74        replaceWith: `#$1<${prefix}$2$3/`,
75      },
76      {
77        paths: objcFilesPattern,
78        find: /#(import |include |if __has_include\()<(.*?)\/(Expo|EAS)(.*?)\.h>/g,
79        replaceWith: `#$1<$2/${prefix}$3$4.h>`,
80      },
81      {
82        // Rename Swift compatibility headers from frameworks starting with `Expo`.
83        paths: objcFilesPattern,
84        find: /#import "(Expo|EAS)(.+?)-Swift\.h"/g,
85        replaceWith: `#import "${prefix}$1$2-Swift.h"`,
86      },
87      {
88        paths: objcFilesPattern,
89        find: /@import (Expo|EX|EAS)(\w+)/g,
90        replaceWith: `@import ${prefix}$1$2`,
91      },
92      {
93        // Prefixes imports from other React Native libs
94        paths: objcFilesPattern,
95        find: new RegExp(`#(import|include) <(ReactCommon|jsi)/(${prefix})?`, 'g'),
96        replaceWith: `#$1 <${prefix}$2/${prefix}`,
97      },
98      {
99        // Prefixes versionable namespaces
100        paths: objcFilesPattern,
101        find: /\bnamespace (expo|facebook)\b/g,
102        replaceWith: `namespace ${prefix}$1`,
103      },
104      {
105        // Prefixes usages of versionable namespaces
106        paths: objcFilesPattern,
107        find: /\b(expo|facebook)::/g,
108        replaceWith: `${prefix}$1::`,
109      },
110
111      // Prefixes versionable namespaces (react namespace is already prefixed with uppercased "R")
112      {
113        paths: objcFilesPattern,
114        find: /\busing namespace react;/g,
115        replaceWith: `using namespace ${prefix}React;`,
116      },
117      {
118        paths: objcFilesPattern,
119        find: /::react(::|;)/g,
120        replaceWith: `::${prefix}React$1`,
121      },
122      {
123        paths: objcFilesPattern,
124        find: /\bnamespace react(\s+[^=])/g,
125        replaceWith: `namespace ${prefix}React$1`,
126      },
127      {
128        paths: objcFilesPattern,
129        find: /\b((include|import|__has_include).*\/)(JSCRuntime\.h)/g,
130        replaceWith: `$1${prefix}$3`,
131      },
132      {
133        paths: 'EXGLImageUtils.cpp',
134        find: '#define STB_IMAGE_IMPLEMENTATION',
135        replaceWith: '',
136      },
137
138      // Prefix umbrella header imports
139      {
140        paths: '*.h',
141        // Use negative look ahead regexp for `prefix` to prevent duplicated versioning
142        find: new RegExp(`[\b/](!?${prefix})(\w+-umbrella\.h)\b`, 'g'),
143        replaceWith: `${prefix}$1`,
144      },
145
146      {
147        // Dynamically remove the prefix from the "moduleName" method in the view manager adapter.
148        paths: 'EXViewManagerAdapter.{m,mm}',
149        find: /return (NSStringFromClass\(self\));/g,
150        replaceWith: `NSString *className = $1;\n  return [className hasPrefix:@"${prefix}"] ? [className substringFromIndex:${prefix.length}] : className;`,
151      },
152    ],
153  };
154}
155
156export function getVersioningExpoModuleConfig(
157  prefix: string,
158  moduleName: string
159): VersioningModuleConfig {
160  const config: Record<string, VersioningModuleConfig> = {
161    'expo-constants': {
162      mutatePodspec: removeScriptPhasesAndResourceBundles,
163    },
164    'expo-updates': {
165      mutatePodspec: removeScriptPhasesAndResourceBundles,
166    },
167    'expo-screen-orientation': {
168      // Versioned expo-screen-orientation shouldn't include its own registry, it should use the unversioned one instead.
169      transforms: {
170        path: [],
171        content: [
172          {
173            paths: 'ScreenOrientationRegistry.swift',
174            find: /(.|\n)*/,
175            replaceWith: [
176              '// The original implementations of `ScreenOrientationRegistry` and `ScreenOrientationController`',
177              '// were removed from this file as part of the versioning process to always use their "unversioned" version.',
178              'import ExpoScreenOrientation',
179              '',
180              'typealias ScreenOrientationRegistry = ExpoScreenOrientation.ScreenOrientationRegistry',
181              'typealias ScreenOrientationController = ExpoScreenOrientation.ScreenOrientationController',
182              '',
183            ].join('\n'),
184          },
185        ],
186      },
187      mutatePodspec(podspec: Podspec) {
188        // Versioned screen orientation must depend on unversioned copy to use unversioned singleton object.
189        addDependency(podspec, podspec.name.replace(prefix, ''));
190      },
191    },
192  };
193  return config[moduleName] ?? {};
194}
195
196function removeScriptPhasesAndResourceBundles(podspec: Podspec): void {
197  // For expo-updates and expo-constants in Expo Go, we don't need app.config and app.manifest in versioned code.
198  delete podspec['script_phases'];
199  delete podspec['resource_bundles'];
200}
201
202function addDependency(podspec: Podspec, dependencyName: string) {
203  if (!podspec.dependencies) {
204    podspec.dependencies = {};
205  }
206  podspec.dependencies[dependencyName] = [];
207}
208