@callstack/react-native-brownfield 3.7.0 → 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/ReactBrownfield.podspec +1 -0
- package/ios/ExpoHostRuntime.swift +23 -5
- package/ios/ReactNativeBrownfield.swift +17 -0
- package/ios/ReactNativeHostRuntime.swift +10 -0
- package/ios/ReactNativeViewController.swift +58 -6
- package/lib/commonjs/expo-config-plugin/android/utils/androidManifest.js +2 -0
- package/lib/commonjs/expo-config-plugin/android/utils/androidManifest.js.map +1 -0
- package/lib/commonjs/expo-config-plugin/android/utils/constants.js +1 -1
- package/lib/commonjs/expo-config-plugin/android/utils/expo-updates.js +2 -0
- package/lib/commonjs/expo-config-plugin/android/utils/expo-updates.js.map +1 -0
- package/lib/commonjs/expo-config-plugin/android/withAndroidModuleFiles.js +1 -1
- package/lib/commonjs/expo-config-plugin/android/withAndroidModuleFiles.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/android/withBrownfieldAndroid.js +1 -1
- package/lib/commonjs/expo-config-plugin/android/withBrownfieldAndroid.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/expoUtils.js +1 -1
- package/lib/commonjs/expo-config-plugin/expoUtils.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/utils/expo-updates.js +2 -0
- package/lib/commonjs/expo-config-plugin/ios/utils/expo-updates.js.map +1 -0
- package/lib/commonjs/expo-config-plugin/ios/withBrownfieldIos.js +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withBrownfieldIos.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js +1 -1
- package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/template/android/AndroidManifest.xml +1 -1
- package/lib/commonjs/expo-config-plugin/template/android/ReactNativeHostManager.pre55.kt +6 -1
- package/lib/commonjs/expo-config-plugin/template/android/strings.xml +4 -0
- package/lib/commonjs/expo-config-plugin/template/ios/patchExpoPre55.sh +2 -0
- package/lib/module/expo-config-plugin/android/utils/androidManifest.js +2 -0
- package/lib/module/expo-config-plugin/android/utils/androidManifest.js.map +1 -0
- package/lib/module/expo-config-plugin/android/utils/constants.js +1 -1
- package/lib/module/expo-config-plugin/android/utils/expo-updates.js +2 -0
- package/lib/module/expo-config-plugin/android/utils/expo-updates.js.map +1 -0
- package/lib/module/expo-config-plugin/android/withAndroidModuleFiles.js +1 -1
- package/lib/module/expo-config-plugin/android/withAndroidModuleFiles.js.map +1 -1
- package/lib/module/expo-config-plugin/android/withBrownfieldAndroid.js +1 -1
- package/lib/module/expo-config-plugin/android/withBrownfieldAndroid.js.map +1 -1
- package/lib/module/expo-config-plugin/expoUtils.js +1 -1
- package/lib/module/expo-config-plugin/expoUtils.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/utils/expo-updates.js +2 -0
- package/lib/module/expo-config-plugin/ios/utils/expo-updates.js.map +1 -0
- package/lib/module/expo-config-plugin/ios/withBrownfieldIos.js +1 -1
- package/lib/module/expo-config-plugin/ios/withBrownfieldIos.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/xcodeHelpers.js +1 -1
- package/lib/module/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
- package/lib/module/expo-config-plugin/template/android/AndroidManifest.xml +1 -1
- package/lib/module/expo-config-plugin/template/android/ReactNativeHostManager.pre55.kt +6 -1
- package/lib/module/expo-config-plugin/template/android/strings.xml +4 -0
- package/lib/module/expo-config-plugin/template/ios/patchExpoPre55.sh +2 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/android/utils/androidManifest.d.ts +13 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/android/utils/androidManifest.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/android/utils/constants.d.ts +2 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/android/utils/expo-updates.d.ts +12 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/android/utils/expo-updates.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts +20 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/android/withBrownfieldAndroid.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/expoUtils.d.ts +1 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/expoUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/utils/expo-updates.d.ts +30 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/utils/expo-updates.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/withBrownfieldIos.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts +29 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/android/utils/androidManifest.d.ts +13 -0
- package/lib/typescript/module/src/expo-config-plugin/android/utils/androidManifest.d.ts.map +1 -0
- package/lib/typescript/module/src/expo-config-plugin/android/utils/constants.d.ts +2 -2
- package/lib/typescript/module/src/expo-config-plugin/android/utils/expo-updates.d.ts +12 -0
- package/lib/typescript/module/src/expo-config-plugin/android/utils/expo-updates.d.ts.map +1 -0
- package/lib/typescript/module/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts +20 -1
- package/lib/typescript/module/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/android/withBrownfieldAndroid.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/expoUtils.d.ts +1 -0
- package/lib/typescript/module/src/expo-config-plugin/expoUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/utils/expo-updates.d.ts +30 -0
- package/lib/typescript/module/src/expo-config-plugin/ios/utils/expo-updates.d.ts.map +1 -0
- package/lib/typescript/module/src/expo-config-plugin/ios/withBrownfieldIos.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts +29 -0
- package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/expo-config-plugin/android/utils/androidManifest.ts +131 -0
- package/src/expo-config-plugin/android/utils/constants.ts +1 -1
- package/src/expo-config-plugin/android/utils/expo-updates.ts +106 -0
- package/src/expo-config-plugin/android/withAndroidModuleFiles.ts +122 -8
- package/src/expo-config-plugin/android/withBrownfieldAndroid.ts +18 -1
- package/src/expo-config-plugin/expoUtils.ts +14 -0
- package/src/expo-config-plugin/ios/utils/expo-updates.ts +168 -0
- package/src/expo-config-plugin/ios/withBrownfieldIos.ts +13 -1
- package/src/expo-config-plugin/ios/xcodeHelpers.ts +217 -4
- package/src/expo-config-plugin/template/android/AndroidManifest.xml +1 -1
- package/src/expo-config-plugin/template/android/ReactNativeHostManager.pre55.kt +6 -1
- package/src/expo-config-plugin/template/android/strings.xml +4 -0
- package/src/expo-config-plugin/template/ios/patchExpoPre55.sh +2 -0
|
@@ -9,7 +9,17 @@ import type {
|
|
|
9
9
|
} from '../types';
|
|
10
10
|
import { Logger } from '../logging';
|
|
11
11
|
import { renderTemplate } from '../template/engine';
|
|
12
|
-
import { getExpoInfo } from '../expoUtils';
|
|
12
|
+
import { getExpoInfo, hasExpoUpdatesInstalled } from '../expoUtils';
|
|
13
|
+
import {
|
|
14
|
+
type AndroidManifestMetaDataEntry,
|
|
15
|
+
type AndroidStringResourceEntry,
|
|
16
|
+
renderLibraryManifestApplication,
|
|
17
|
+
renderLibraryStringResources,
|
|
18
|
+
} from './utils/androidManifest';
|
|
19
|
+
import {
|
|
20
|
+
readExpoUpdatesApplicationMetaData,
|
|
21
|
+
readExpoUpdatesStringResources,
|
|
22
|
+
} from './utils/expo-updates';
|
|
13
23
|
import { getHermesArtifact } from './utils/hermes';
|
|
14
24
|
|
|
15
25
|
/**
|
|
@@ -20,7 +30,13 @@ export function createAndroidModule({
|
|
|
20
30
|
config,
|
|
21
31
|
rnVersion,
|
|
22
32
|
isExpoPre55,
|
|
33
|
+
projectRoot,
|
|
23
34
|
}: {
|
|
35
|
+
/**
|
|
36
|
+
* Expo app root (used to detect optional dependencies such as expo-updates)
|
|
37
|
+
*/
|
|
38
|
+
projectRoot?: string;
|
|
39
|
+
|
|
24
40
|
/**
|
|
25
41
|
* Whether the Expo project is pre-55
|
|
26
42
|
*/
|
|
@@ -43,6 +59,7 @@ export function createAndroidModule({
|
|
|
43
59
|
}): void {
|
|
44
60
|
const { android } = config;
|
|
45
61
|
const moduleDir = path.join(androidDir, android.moduleName);
|
|
62
|
+
const hasExpoUpdates = hasExpoUpdatesInstalled(projectRoot);
|
|
46
63
|
|
|
47
64
|
Logger.logDebug(`Creating Android module in: ${androidDir}`);
|
|
48
65
|
|
|
@@ -70,10 +87,6 @@ export function createAndroidModule({
|
|
|
70
87
|
relativePath: 'gradle.properties',
|
|
71
88
|
content: renderTemplate('android', 'gradle.properties', {}),
|
|
72
89
|
},
|
|
73
|
-
{
|
|
74
|
-
relativePath: 'src/main/AndroidManifest.xml',
|
|
75
|
-
content: renderTemplate('android', 'AndroidManifest.xml', {}),
|
|
76
|
-
},
|
|
77
90
|
{
|
|
78
91
|
relativePath: `src/main/java/${config.android.packageName.replace(/\./g, '/')}/ReactNativeHostManager.kt`,
|
|
79
92
|
content: renderTemplate(
|
|
@@ -81,9 +94,19 @@ export function createAndroidModule({
|
|
|
81
94
|
isExpoPre55
|
|
82
95
|
? 'ReactNativeHostManager.pre55.kt'
|
|
83
96
|
: 'ReactNativeHostManager.post55.kt',
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
isExpoPre55
|
|
98
|
+
? {
|
|
99
|
+
'{{PACKAGE_NAME}}': android.packageName,
|
|
100
|
+
'{{EXPO_UPDATES_IMPORTS}}': hasExpoUpdates
|
|
101
|
+
? 'import expo.modules.updates.UpdatesController'
|
|
102
|
+
: '',
|
|
103
|
+
'{{EXPO_UPDATES_REACT_HOST_BLOCK}}': hasExpoUpdates
|
|
104
|
+
? '\n UpdatesController.setReactHost(reactHost)\n'
|
|
105
|
+
: '\n',
|
|
106
|
+
}
|
|
107
|
+
: {
|
|
108
|
+
'{{PACKAGE_NAME}}': android.packageName,
|
|
109
|
+
}
|
|
87
110
|
),
|
|
88
111
|
},
|
|
89
112
|
{
|
|
@@ -111,11 +134,101 @@ export function createAndroidModule({
|
|
|
111
134
|
Logger.logDebug(`Created file: ${filePath}`);
|
|
112
135
|
}
|
|
113
136
|
|
|
137
|
+
syncAndroidModuleExpoUpdatesFromAppFiles({
|
|
138
|
+
androidDir,
|
|
139
|
+
config,
|
|
140
|
+
});
|
|
141
|
+
|
|
114
142
|
Logger.logDebug(
|
|
115
143
|
`Android module "${android.moduleName}" created at ${moduleDir}`
|
|
116
144
|
);
|
|
117
145
|
}
|
|
118
146
|
|
|
147
|
+
export function syncAndroidModuleManifest({
|
|
148
|
+
androidDir,
|
|
149
|
+
config,
|
|
150
|
+
expoUpdatesMetaData,
|
|
151
|
+
}: {
|
|
152
|
+
androidDir: string;
|
|
153
|
+
config: ResolvedBrownfieldPluginConfigWithAndroid;
|
|
154
|
+
expoUpdatesMetaData: AndroidManifestMetaDataEntry[];
|
|
155
|
+
}): void {
|
|
156
|
+
writeAndroidModuleFile(
|
|
157
|
+
path.join(androidDir, config.android.moduleName),
|
|
158
|
+
'src/main/AndroidManifest.xml',
|
|
159
|
+
renderTemplate('android', 'AndroidManifest.xml', {
|
|
160
|
+
'{{APPLICATION_BLOCK}}':
|
|
161
|
+
renderLibraryManifestApplication(expoUpdatesMetaData),
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function syncAndroidModuleStringResources({
|
|
167
|
+
androidDir,
|
|
168
|
+
config,
|
|
169
|
+
expoUpdatesStringResources,
|
|
170
|
+
}: {
|
|
171
|
+
androidDir: string;
|
|
172
|
+
config: ResolvedBrownfieldPluginConfigWithAndroid;
|
|
173
|
+
expoUpdatesStringResources: AndroidStringResourceEntry[];
|
|
174
|
+
}): void {
|
|
175
|
+
writeAndroidModuleFile(
|
|
176
|
+
path.join(androidDir, config.android.moduleName),
|
|
177
|
+
'src/main/res/values/strings.xml',
|
|
178
|
+
renderTemplate('android', 'strings.xml', {
|
|
179
|
+
'{{STRING_RESOURCES}}': renderLibraryStringResources(
|
|
180
|
+
expoUpdatesStringResources
|
|
181
|
+
),
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function writeAndroidModuleFile(
|
|
187
|
+
moduleDir: string,
|
|
188
|
+
relativePath: string,
|
|
189
|
+
content: string
|
|
190
|
+
): void {
|
|
191
|
+
const filePath = path.join(moduleDir, relativePath);
|
|
192
|
+
const fileDir = path.dirname(filePath);
|
|
193
|
+
|
|
194
|
+
if (!fs.existsSync(fileDir)) {
|
|
195
|
+
fs.mkdirSync(fileDir, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
199
|
+
Logger.logDebug(`Created file: ${filePath}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function syncAndroidModuleExpoUpdatesFromAppFiles({
|
|
203
|
+
androidDir,
|
|
204
|
+
config,
|
|
205
|
+
}: {
|
|
206
|
+
androidDir: string;
|
|
207
|
+
config: ResolvedBrownfieldPluginConfigWithAndroid;
|
|
208
|
+
}): void {
|
|
209
|
+
const appModuleName = 'app';
|
|
210
|
+
const expoUpdatesMetaData = readExpoUpdatesApplicationMetaData(
|
|
211
|
+
androidDir,
|
|
212
|
+
appModuleName
|
|
213
|
+
);
|
|
214
|
+
const expoUpdatesStringResources = readExpoUpdatesStringResources(
|
|
215
|
+
androidDir,
|
|
216
|
+
appModuleName,
|
|
217
|
+
expoUpdatesMetaData
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
syncAndroidModuleManifest({
|
|
221
|
+
androidDir,
|
|
222
|
+
config,
|
|
223
|
+
expoUpdatesMetaData,
|
|
224
|
+
});
|
|
225
|
+
syncAndroidModuleStringResources({
|
|
226
|
+
androidDir,
|
|
227
|
+
config,
|
|
228
|
+
expoUpdatesStringResources,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
119
232
|
/**
|
|
120
233
|
* Dangerous mod that creates the Android module directory and files
|
|
121
234
|
*/
|
|
@@ -154,6 +267,7 @@ export const withAndroidModuleFiles: ConfigPlugin<
|
|
|
154
267
|
config: props,
|
|
155
268
|
rnVersion,
|
|
156
269
|
isExpoPre55,
|
|
270
|
+
projectRoot: dangerousConfig.modRequest.projectRoot,
|
|
157
271
|
});
|
|
158
272
|
|
|
159
273
|
return dangerousConfig;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
withProjectBuildGradle,
|
|
3
3
|
withSettingsGradle,
|
|
4
|
+
withFinalizedMod,
|
|
4
5
|
type ConfigPlugin,
|
|
5
6
|
} from '@expo/config-plugins';
|
|
6
7
|
|
|
@@ -8,7 +9,10 @@ import {
|
|
|
8
9
|
modifyRootBuildGradle,
|
|
9
10
|
modifySettingsGradle,
|
|
10
11
|
} from './utils/gradleHelpers';
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
syncAndroidModuleExpoUpdatesFromAppFiles,
|
|
14
|
+
withAndroidModuleFiles,
|
|
15
|
+
} from './withAndroidModuleFiles';
|
|
12
16
|
import type { ResolvedBrownfieldPluginConfigWithAndroid } from '../types';
|
|
13
17
|
|
|
14
18
|
/**
|
|
@@ -47,5 +51,18 @@ export const withBrownfieldAndroid: ConfigPlugin<
|
|
|
47
51
|
// Step 3: create the Android module files using dangerous mod
|
|
48
52
|
config = withAndroidModuleFiles(config, props);
|
|
49
53
|
|
|
54
|
+
// Step 4: sync module metadata after Expo writes all Android files
|
|
55
|
+
config = withFinalizedMod(config, [
|
|
56
|
+
'android',
|
|
57
|
+
async (finalizedConfig) => {
|
|
58
|
+
syncAndroidModuleExpoUpdatesFromAppFiles({
|
|
59
|
+
androidDir: finalizedConfig.modRequest.platformProjectRoot,
|
|
60
|
+
config: props,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return finalizedConfig;
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
|
|
50
67
|
return config;
|
|
51
68
|
};
|
|
@@ -10,3 +10,17 @@ export function getExpoInfo(config: ExpoConfig) {
|
|
|
10
10
|
isExpoPre55,
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
export function hasExpoUpdatesInstalled(
|
|
15
|
+
projectRoot: string | undefined
|
|
16
|
+
): boolean {
|
|
17
|
+
if (!projectRoot) return false;
|
|
18
|
+
try {
|
|
19
|
+
require.resolve('expo-updates/package.json', {
|
|
20
|
+
paths: [projectRoot],
|
|
21
|
+
});
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { XcodeProject } from '@expo/config-plugins';
|
|
2
|
+
|
|
3
|
+
import { Logger } from '../../logging';
|
|
4
|
+
import { SourceModificationError } from '../../errors/SourceModificationError';
|
|
5
|
+
import { ensureTargetHasFileReferenceInResourcesBuildPhase } from '../xcodeHelpers';
|
|
6
|
+
|
|
7
|
+
type PbxFileReference = {
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
name?: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type PbxGroupLike = {
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
name?: string;
|
|
16
|
+
path?: string;
|
|
17
|
+
children?: Array<{ value?: string; comment?: string } | string>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const EXPO_PLIST_FILE_NAME = 'Expo.plist';
|
|
21
|
+
const EXPO_PLIST_PRIMARY_RELATIVE_PATH = 'Supporting/Expo.plist';
|
|
22
|
+
const RESOURCES_BUILD_PHASE_COMMENT = 'Resources';
|
|
23
|
+
const EXPO_PLIST_RESOURCE_COMMENT = 'Expo.plist in Resources';
|
|
24
|
+
|
|
25
|
+
function normalizePbxString(value: unknown): string {
|
|
26
|
+
return String(value ?? '').replace(/^"(.*)"$/, '$1');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizePbxPathLike(value: unknown): string {
|
|
30
|
+
return normalizePbxString(value).replace(/^\.\//, '');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isSupportingGroup(group: PbxGroupLike): boolean {
|
|
34
|
+
const groupName = normalizePbxString(group.name);
|
|
35
|
+
const groupPath = normalizePbxString(group.path);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
groupName === 'Supporting' ||
|
|
39
|
+
groupPath === 'Supporting' ||
|
|
40
|
+
groupPath.endsWith('/Supporting')
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function groupContainsFileReference(
|
|
45
|
+
group: PbxGroupLike,
|
|
46
|
+
fileRefUuid: string
|
|
47
|
+
): boolean {
|
|
48
|
+
const children = Array.isArray(group.children) ? group.children : [];
|
|
49
|
+
|
|
50
|
+
return children.some((child) => {
|
|
51
|
+
if (typeof child === 'string') {
|
|
52
|
+
return child === fileRefUuid;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return child?.value === fileRefUuid;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isPrimaryExpoPlistMatch(
|
|
60
|
+
fileRefUuid: string,
|
|
61
|
+
fileRef: PbxFileReference,
|
|
62
|
+
groups?: Record<string, PbxGroupLike | string>
|
|
63
|
+
): boolean {
|
|
64
|
+
const normalizedPath = normalizePbxPathLike(fileRef.path);
|
|
65
|
+
const fileName = normalizePbxString(fileRef.name);
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
normalizedPath === EXPO_PLIST_PRIMARY_RELATIVE_PATH ||
|
|
69
|
+
normalizedPath.endsWith(`/${EXPO_PLIST_PRIMARY_RELATIVE_PATH}`)
|
|
70
|
+
) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
normalizedPath !== EXPO_PLIST_FILE_NAME &&
|
|
76
|
+
fileName !== EXPO_PLIST_FILE_NAME
|
|
77
|
+
) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Object.entries(groups ?? {}).some(([groupUuid, group]) => {
|
|
82
|
+
if (groupUuid.endsWith('_comment') || typeof group === 'string') {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
isSupportingGroup(group) && groupContainsFileReference(group, fileRefUuid)
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Selects an existing PBXFileReference for the app-level Expo plist.
|
|
94
|
+
*
|
|
95
|
+
* Matches either:
|
|
96
|
+
* - a file reference whose own path is `Supporting/Expo.plist`
|
|
97
|
+
* - or an `Expo.plist` file reference that lives under a `Supporting` PBXGroup
|
|
98
|
+
*/
|
|
99
|
+
export function selectExpoPlistFileReference(
|
|
100
|
+
fileReferences: Record<string, PbxFileReference | string>,
|
|
101
|
+
groups?: Record<string, PbxGroupLike | string>
|
|
102
|
+
): string | null {
|
|
103
|
+
for (const [fileRefUuid, fileRef] of Object.entries(fileReferences)) {
|
|
104
|
+
if (fileRefUuid.endsWith('_comment') || typeof fileRef === 'string') {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isPrimaryExpoPlistMatch(fileRefUuid, fileRef, groups)) {
|
|
109
|
+
return fileRefUuid;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getExpoPlistFileRefOrThrow(project: XcodeProject): string {
|
|
117
|
+
const fileReferences = project.pbxFileReferenceSection() as Record<
|
|
118
|
+
string,
|
|
119
|
+
PbxFileReference | string
|
|
120
|
+
>;
|
|
121
|
+
const groups = (project as any).hash?.project?.objects?.PBXGroup as
|
|
122
|
+
| Record<string, PbxGroupLike | string>
|
|
123
|
+
| undefined;
|
|
124
|
+
const existingExpoPlistFileRefUuid = selectExpoPlistFileReference(
|
|
125
|
+
fileReferences,
|
|
126
|
+
groups
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (existingExpoPlistFileRefUuid) {
|
|
130
|
+
return existingExpoPlistFileRefUuid;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
throw new SourceModificationError(
|
|
134
|
+
`Could not find the "${EXPO_PLIST_PRIMARY_RELATIVE_PATH}" PBXFileReference needed for Expo.plist resource wiring`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Ensures the framework target contains the app-level `Supporting/Expo.plist`
|
|
140
|
+
* in a `PBXResourcesBuildPhase`. This is idempotent and safe to call repeatedly.
|
|
141
|
+
*/
|
|
142
|
+
export function ensureFrameworkHasExpoPlistResource(
|
|
143
|
+
project: XcodeProject,
|
|
144
|
+
frameworkTargetUUID: string
|
|
145
|
+
): void {
|
|
146
|
+
const expoPlistFileRefUuid = getExpoPlistFileRefOrThrow(project);
|
|
147
|
+
const didAddExpoPlistResource =
|
|
148
|
+
ensureTargetHasFileReferenceInResourcesBuildPhase(
|
|
149
|
+
project,
|
|
150
|
+
frameworkTargetUUID,
|
|
151
|
+
expoPlistFileRefUuid,
|
|
152
|
+
{
|
|
153
|
+
resourcesBuildPhaseComment: RESOURCES_BUILD_PHASE_COMMENT,
|
|
154
|
+
buildFileComment: EXPO_PLIST_RESOURCE_COMMENT,
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (!didAddExpoPlistResource) {
|
|
159
|
+
Logger.logDebug(
|
|
160
|
+
'Framework resources already include Supporting/Expo.plist'
|
|
161
|
+
);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
Logger.logDebug(
|
|
166
|
+
'Added Supporting/Expo.plist to framework PBXResourcesBuildPhase'
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -11,10 +11,11 @@ import {
|
|
|
11
11
|
copyBundleReactNativePhase,
|
|
12
12
|
} from './xcodeHelpers';
|
|
13
13
|
import { modifyPodfile } from './podfileHelpers';
|
|
14
|
+
import { ensureFrameworkHasExpoPlistResource } from './utils/expo-updates';
|
|
14
15
|
import { withIosFrameworkFiles } from './withIosFrameworkFiles';
|
|
15
16
|
import type { ResolvedBrownfieldPluginConfigWithIos } from '../types';
|
|
16
17
|
import { Logger } from '../logging';
|
|
17
|
-
import { getExpoInfo } from '../expoUtils';
|
|
18
|
+
import { getExpoInfo, hasExpoUpdatesInstalled } from '../expoUtils';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* iOS Config Plugin for integration with @callstack/react-native-brownfield.
|
|
@@ -34,6 +35,7 @@ export const withBrownfieldIos: ConfigPlugin<
|
|
|
34
35
|
// Step 1: modify the Xcode project to add framework target &
|
|
35
36
|
config = withXcodeProject(config, (xcodeConfig) => {
|
|
36
37
|
const { modResults: project, modRequest } = xcodeConfig;
|
|
38
|
+
const hasExpoUpdates = hasExpoUpdatesInstalled(modRequest.projectRoot);
|
|
37
39
|
|
|
38
40
|
const { frameworkTargetUUID, targetAlreadyExists } = addFrameworkTarget(
|
|
39
41
|
project,
|
|
@@ -41,6 +43,16 @@ export const withBrownfieldIos: ConfigPlugin<
|
|
|
41
43
|
props.ios
|
|
42
44
|
);
|
|
43
45
|
|
|
46
|
+
// Ensure Expo.plist is present in the framework resources phase when
|
|
47
|
+
// expo-updates is installed, including for pre-existing framework targets.
|
|
48
|
+
if (hasExpoUpdates) {
|
|
49
|
+
ensureFrameworkHasExpoPlistResource(project, frameworkTargetUUID);
|
|
50
|
+
} else {
|
|
51
|
+
Logger.logDebug(
|
|
52
|
+
'Skipping Expo.plist framework resource wiring because expo-updates is not installed'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
44
56
|
if (targetAlreadyExists) {
|
|
45
57
|
Logger.logDebug(
|
|
46
58
|
`Skipping further Xcode modifications as framework target was already present`
|
|
@@ -182,6 +182,220 @@ export function addSourceFilesBuildPhase(
|
|
|
182
182
|
);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
export type PbxReferenceLike = { value?: string; comment?: string } | string;
|
|
186
|
+
|
|
187
|
+
export type PbxNativeTarget = {
|
|
188
|
+
buildPhases?: PbxReferenceLike[];
|
|
189
|
+
name?: string;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export type PbxResourcesBuildPhase = {
|
|
193
|
+
isa: 'PBXResourcesBuildPhase';
|
|
194
|
+
buildActionMask: number;
|
|
195
|
+
files?: PbxReferenceLike[];
|
|
196
|
+
runOnlyForDeploymentPostprocessing: number;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export type PbxBuildFile = {
|
|
200
|
+
isa: 'PBXBuildFile';
|
|
201
|
+
fileRef: string;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export type PbxCommentedReference = { value: string; comment: string };
|
|
205
|
+
|
|
206
|
+
const PBX_BUILD_ACTION_MASK_ALL = 2147483647;
|
|
207
|
+
const PBX_RUN_ONLY_FOR_DEPLOYMENT_POSTPROCESSING_DISABLED = 0;
|
|
208
|
+
const PBX_RESOURCES_BUILD_PHASE_ISA = 'PBXResourcesBuildPhase';
|
|
209
|
+
const PBX_BUILD_FILE_ISA = 'PBXBuildFile';
|
|
210
|
+
|
|
211
|
+
export function createPbxCommentedReference(
|
|
212
|
+
value: string,
|
|
213
|
+
comment: string
|
|
214
|
+
): PbxCommentedReference {
|
|
215
|
+
return { value, comment };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function createResourcesBuildPhase(): PbxResourcesBuildPhase {
|
|
219
|
+
return {
|
|
220
|
+
isa: PBX_RESOURCES_BUILD_PHASE_ISA,
|
|
221
|
+
buildActionMask: PBX_BUILD_ACTION_MASK_ALL,
|
|
222
|
+
files: [],
|
|
223
|
+
runOnlyForDeploymentPostprocessing:
|
|
224
|
+
PBX_RUN_ONLY_FOR_DEPLOYMENT_POSTPROCESSING_DISABLED,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function createBuildFile(fileRef: string): PbxBuildFile {
|
|
229
|
+
return {
|
|
230
|
+
isa: PBX_BUILD_FILE_ISA,
|
|
231
|
+
fileRef,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getRawProjectObjects(project: XcodeProject): Record<string, any> {
|
|
236
|
+
return (project as any).hash?.project?.objects ?? {};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getReferencedUuid(
|
|
240
|
+
reference: PbxReferenceLike | undefined
|
|
241
|
+
): string | null {
|
|
242
|
+
if (!reference) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return typeof reference === 'string' ? reference : (reference.value ?? null);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getFrameworkTargetOrThrow(
|
|
250
|
+
project: XcodeProject,
|
|
251
|
+
frameworkTargetUUID: string
|
|
252
|
+
): PbxNativeTarget {
|
|
253
|
+
const nativeTargets = getRawProjectObjects(project).PBXNativeTarget as
|
|
254
|
+
| Record<string, PbxNativeTarget>
|
|
255
|
+
| undefined;
|
|
256
|
+
const frameworkTarget = nativeTargets?.[frameworkTargetUUID];
|
|
257
|
+
|
|
258
|
+
if (!frameworkTarget) {
|
|
259
|
+
throw new SourceModificationError(
|
|
260
|
+
`Framework target UUID "${frameworkTargetUUID}" not found while wiring a resources build phase`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return frameworkTarget;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function getOrCreateResourcesBuildPhaseForTarget(
|
|
268
|
+
project: XcodeProject,
|
|
269
|
+
frameworkTargetUUID: string,
|
|
270
|
+
frameworkTarget: PbxNativeTarget,
|
|
271
|
+
resourcesBuildPhaseComment: string
|
|
272
|
+
): PbxResourcesBuildPhase {
|
|
273
|
+
const rawProjectObjects = getRawProjectObjects(project);
|
|
274
|
+
const resourceBuildPhases = (rawProjectObjects.PBXResourcesBuildPhase ??
|
|
275
|
+
(rawProjectObjects.PBXResourcesBuildPhase = {})) as Record<
|
|
276
|
+
string,
|
|
277
|
+
PbxResourcesBuildPhase | string
|
|
278
|
+
>;
|
|
279
|
+
|
|
280
|
+
const resourcesBuildPhaseUuid = (frameworkTarget.buildPhases ?? [])
|
|
281
|
+
.map((phase) => getReferencedUuid(phase))
|
|
282
|
+
.find((phaseUuid): phaseUuid is string => {
|
|
283
|
+
return !!phaseUuid && !!resourceBuildPhases[phaseUuid];
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (resourcesBuildPhaseUuid) {
|
|
287
|
+
return resourceBuildPhases[
|
|
288
|
+
resourcesBuildPhaseUuid
|
|
289
|
+
] as PbxResourcesBuildPhase;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const createdResourcesBuildPhaseUuid = (project as any).generateUuid();
|
|
293
|
+
resourceBuildPhases[createdResourcesBuildPhaseUuid] =
|
|
294
|
+
createResourcesBuildPhase();
|
|
295
|
+
resourceBuildPhases[`${createdResourcesBuildPhaseUuid}_comment`] =
|
|
296
|
+
resourcesBuildPhaseComment;
|
|
297
|
+
|
|
298
|
+
const targetBuildPhases = Array.isArray(frameworkTarget.buildPhases)
|
|
299
|
+
? frameworkTarget.buildPhases
|
|
300
|
+
: [];
|
|
301
|
+
targetBuildPhases.push(
|
|
302
|
+
createPbxCommentedReference(
|
|
303
|
+
createdResourcesBuildPhaseUuid,
|
|
304
|
+
resourcesBuildPhaseComment
|
|
305
|
+
)
|
|
306
|
+
);
|
|
307
|
+
frameworkTarget.buildPhases = targetBuildPhases;
|
|
308
|
+
|
|
309
|
+
Logger.logDebug(
|
|
310
|
+
`Created missing PBXResourcesBuildPhase for framework target "${frameworkTargetUUID}"`
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
return resourceBuildPhases[
|
|
314
|
+
createdResourcesBuildPhaseUuid
|
|
315
|
+
] as PbxResourcesBuildPhase;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function hasBuildFileForFileRef(
|
|
319
|
+
project: XcodeProject,
|
|
320
|
+
resourcesBuildPhase: PbxResourcesBuildPhase,
|
|
321
|
+
fileRefUuid: string
|
|
322
|
+
): boolean {
|
|
323
|
+
const buildFileSection = project.pbxBuildFileSection() as Record<
|
|
324
|
+
string,
|
|
325
|
+
PbxBuildFile
|
|
326
|
+
>;
|
|
327
|
+
const files = Array.isArray(resourcesBuildPhase.files)
|
|
328
|
+
? resourcesBuildPhase.files
|
|
329
|
+
: [];
|
|
330
|
+
|
|
331
|
+
return files.some((phaseFile) => {
|
|
332
|
+
const buildFileUuid = getReferencedUuid(phaseFile);
|
|
333
|
+
if (!buildFileUuid) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return buildFileSection[buildFileUuid]?.fileRef === fileRefUuid;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function addBuildFileToResourcesPhase(
|
|
342
|
+
project: XcodeProject,
|
|
343
|
+
resourcesBuildPhase: PbxResourcesBuildPhase,
|
|
344
|
+
fileRefUuid: string,
|
|
345
|
+
buildFileComment: string
|
|
346
|
+
): void {
|
|
347
|
+
const buildFileSection = project.pbxBuildFileSection() as Record<
|
|
348
|
+
string,
|
|
349
|
+
PbxBuildFile | string
|
|
350
|
+
>;
|
|
351
|
+
const buildFileUuid = (project as any).generateUuid();
|
|
352
|
+
|
|
353
|
+
buildFileSection[buildFileUuid] = createBuildFile(fileRefUuid);
|
|
354
|
+
buildFileSection[`${buildFileUuid}_comment`] = buildFileComment;
|
|
355
|
+
|
|
356
|
+
const files = Array.isArray(resourcesBuildPhase.files)
|
|
357
|
+
? resourcesBuildPhase.files
|
|
358
|
+
: [];
|
|
359
|
+
files.push(createPbxCommentedReference(buildFileUuid, buildFileComment));
|
|
360
|
+
resourcesBuildPhase.files = files;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
type ResourcesBuildPhaseOptions = {
|
|
364
|
+
resourcesBuildPhaseComment: string;
|
|
365
|
+
buildFileComment: string;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export function ensureTargetHasFileReferenceInResourcesBuildPhase(
|
|
369
|
+
project: XcodeProject,
|
|
370
|
+
frameworkTargetUUID: string,
|
|
371
|
+
fileRefUuid: string,
|
|
372
|
+
{ resourcesBuildPhaseComment, buildFileComment }: ResourcesBuildPhaseOptions
|
|
373
|
+
): boolean {
|
|
374
|
+
const frameworkTarget = getFrameworkTargetOrThrow(
|
|
375
|
+
project,
|
|
376
|
+
frameworkTargetUUID
|
|
377
|
+
);
|
|
378
|
+
const resourcesBuildPhase = getOrCreateResourcesBuildPhaseForTarget(
|
|
379
|
+
project,
|
|
380
|
+
frameworkTargetUUID,
|
|
381
|
+
frameworkTarget,
|
|
382
|
+
resourcesBuildPhaseComment
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
if (hasBuildFileForFileRef(project, resourcesBuildPhase, fileRefUuid)) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
addBuildFileToResourcesPhase(
|
|
390
|
+
project,
|
|
391
|
+
resourcesBuildPhase,
|
|
392
|
+
fileRefUuid,
|
|
393
|
+
buildFileComment
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
|
|
185
399
|
/**
|
|
186
400
|
* Returns build settings for the framework target
|
|
187
401
|
* @param options The user configuration
|
|
@@ -277,10 +491,9 @@ export function copyBundleReactNativePhase(
|
|
|
277
491
|
(phase: { value: string }) => phase.value === existingPhaseUuid
|
|
278
492
|
)
|
|
279
493
|
) {
|
|
280
|
-
target.buildPhases.push(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
});
|
|
494
|
+
target.buildPhases.push(
|
|
495
|
+
createPbxCommentedReference(existingPhaseUuid, buildPhaseName)
|
|
496
|
+
);
|
|
284
497
|
|
|
285
498
|
Logger.logDebug(
|
|
286
499
|
`Added "${buildPhaseName}" build phase to framework target ${target.name}`
|
|
@@ -10,6 +10,7 @@ import com.facebook.react.defaults.DefaultReactNativeHost
|
|
|
10
10
|
import expo.modules.ApplicationLifecycleDispatcher
|
|
11
11
|
import expo.modules.ExpoReactHostFactory
|
|
12
12
|
import expo.modules.ReactNativeHostWrapper
|
|
13
|
+
{{EXPO_UPDATES_IMPORTS}}
|
|
13
14
|
|
|
14
15
|
object ReactNativeHostManager {
|
|
15
16
|
fun initialize(application: Application, onJSBundleLoaded: OnJSBundleLoaded? = null) {
|
|
@@ -33,11 +34,15 @@ object ReactNativeHostManager {
|
|
|
33
34
|
override fun getBundleAssetName(): String = "index.android.bundle"
|
|
34
35
|
})
|
|
35
36
|
|
|
37
|
+
|
|
36
38
|
ReactNativeBrownfield.initialize(application, onJSBundleLoaded) {
|
|
37
|
-
ExpoReactHostFactory.createFromReactNativeHost(
|
|
39
|
+
val reactHost = ExpoReactHostFactory.createFromReactNativeHost(
|
|
38
40
|
context = application.applicationContext,
|
|
39
41
|
reactNativeHost = reactNativeHost
|
|
40
42
|
)
|
|
43
|
+
|
|
44
|
+
{{EXPO_UPDATES_REACT_HOST_BLOCK}}
|
|
45
|
+
reactHost
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
|