@callstack/react-native-brownfield 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/gradle-plugin/brownfield/build.gradle.kts +4 -0
- package/gradle-plugin/brownfield/gradle/libs.versions.toml +2 -0
- package/gradle-plugin/brownfield/gradle.properties +1 -1
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNBrownfieldPlugin.kt +24 -7
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNSourceSets.kt +4 -37
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/JNILibsProcessor.kt +206 -104
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ResourceTaskProcessor.kt +201 -4
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantHelper.kt +7 -0
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantTaskProvider.kt +4 -0
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/GradleExclude.kt +31 -0
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/MergeClassesTask.kt +2 -0
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/PublishingMetadataInjector.kt +28 -3
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/AndroidArchiveLibrary.kt +41 -4
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Extension.kt +14 -1
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/TaskOutputs.kt +40 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/plugin/AgpCompatibilityMatrixTest.kt +14 -6
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/plugin/HardExcludePredicateTest.kt +28 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/processors/ConfigureProcessJniTaskTest.kt +60 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/processors/CopyExplodedResTaskTest.kt +368 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/processors/JniLibsPlanTest.kt +252 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/processors/MergeClassesLocalJarsTest.kt +73 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/processors/MetroResDirWiringTest.kt +39 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/processors/ProcessAndCopyJniLibsTaskTest.kt +136 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/shared/GradleExcludesTest.kt +81 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/shared/PublishingMetadataInjectorTest.kt +56 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/utils/AndroidArchiveLibraryNamespaceFallbackTest.kt +137 -0
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/utils/ExtensionTest.kt +5 -0
- package/ios/Expo/ExpoHostRuntime.swift +3 -1
- package/ios/Expo/ExpoUpdatesStartup.swift +2 -1
- package/lib/commonjs/expo-config-plugin/android/utils/constants.js +1 -1
- 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/ios/podfileHelpers.js +2 -2
- package/lib/commonjs/expo-config-plugin/ios/podfileHelpers.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withIosFrameworkFiles.js +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withIosFrameworkFiles.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js +2 -2
- package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/template/android/ReactNativeHostManager.vanilla.kt +5 -7
- package/lib/commonjs/expo-config-plugin/template/android/build.gradle.kts +1 -1
- package/lib/commonjs/expo-config-plugin/template/ios/ExpoModulesProviderExtension.swift +7 -0
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/scaffold/index.js +1 -1
- package/lib/commonjs/scaffold/index.js.map +1 -1
- package/lib/commonjs/scaffold/projectFiles.js +1 -1
- package/lib/commonjs/scaffold/projectFiles.js.map +1 -1
- package/lib/module/expo-config-plugin/android/utils/constants.js +1 -1
- 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/ios/podfileHelpers.js +2 -2
- package/lib/module/expo-config-plugin/ios/podfileHelpers.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/withIosFrameworkFiles.js +1 -1
- package/lib/module/expo-config-plugin/ios/withIosFrameworkFiles.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/xcodeHelpers.js +2 -2
- package/lib/module/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
- package/lib/module/expo-config-plugin/template/android/ReactNativeHostManager.vanilla.kt +5 -7
- package/lib/module/expo-config-plugin/template/android/build.gradle.kts +1 -1
- package/lib/module/expo-config-plugin/template/ios/ExpoModulesProviderExtension.swift +7 -0
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/scaffold/index.js +1 -1
- package/lib/module/scaffold/index.js.map +1 -1
- package/lib/module/scaffold/projectFiles.js +1 -1
- package/lib/module/scaffold/projectFiles.js.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/android/utils/constants.d.ts +2 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/podfileHelpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts +5 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts +5 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/types/android/BrownfieldPluginAndroidConfig.d.ts +6 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/types/android/BrownfieldPluginAndroidConfig.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/scaffold/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/scaffold/projectFiles.d.ts +2 -2
- package/lib/typescript/commonjs/src/scaffold/projectFiles.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/android/utils/constants.d.ts +2 -2
- package/lib/typescript/module/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/podfileHelpers.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts +5 -0
- package/lib/typescript/module/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts +5 -0
- package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/types/android/BrownfieldPluginAndroidConfig.d.ts +6 -0
- package/lib/typescript/module/src/expo-config-plugin/types/android/BrownfieldPluginAndroidConfig.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/scaffold/index.d.ts.map +1 -1
- package/lib/typescript/module/src/scaffold/projectFiles.d.ts +2 -2
- package/lib/typescript/module/src/scaffold/projectFiles.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/expo-config-plugin/android/utils/constants.ts +1 -1
- package/src/expo-config-plugin/android/withAndroidModuleFiles.ts +42 -0
- package/src/expo-config-plugin/ios/podfileHelpers.ts +47 -13
- package/src/expo-config-plugin/ios/withIosFrameworkFiles.ts +11 -1
- package/src/expo-config-plugin/ios/xcodeHelpers.ts +67 -9
- package/src/expo-config-plugin/template/android/ReactNativeHostManager.vanilla.kt +5 -7
- package/src/expo-config-plugin/template/android/build.gradle.kts +1 -1
- package/src/expo-config-plugin/template/ios/ExpoModulesProviderExtension.swift +7 -0
- package/src/expo-config-plugin/types/android/BrownfieldPluginAndroidConfig.ts +7 -0
- package/src/index.ts +4 -2
- package/src/scaffold/index.ts +61 -4
- package/src/scaffold/projectFiles.ts +5 -3
|
@@ -40,6 +40,43 @@ function renderMissingDimensionStrategyBlock(
|
|
|
40
40
|
return ` missingDimensionStrategy(${args})`;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
const COORDINATE_PART = /^[A-Za-z0-9._-]+$/;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Renders `excludeDependencies` as a `configurations.all` block. The Brownfield Gradle plugin
|
|
47
|
+
* leaves anything excluded this way out of the published POM and module.json. Entries become
|
|
48
|
+
* Kotlin string literals, so anything but a plain coordinate is rejected.
|
|
49
|
+
*/
|
|
50
|
+
function renderExcludeDependenciesBlock(excludeDependencies: string[]): string {
|
|
51
|
+
// It comes from user JSON, so the type isn't guaranteed.
|
|
52
|
+
if (!Array.isArray(excludeDependencies)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'android.excludeDependencies must be a list of "group:module" or "group" strings.'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (excludeDependencies.length === 0) {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const lines = excludeDependencies.map((entry) => {
|
|
62
|
+
const parts = entry.split(':');
|
|
63
|
+
if (
|
|
64
|
+
parts.length > 2 ||
|
|
65
|
+
!parts.every((part) => COORDINATE_PART.test(part))
|
|
66
|
+
) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`android.excludeDependencies: '${entry}' is not in the form "group:module" or "group".`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const [group, module] = parts;
|
|
72
|
+
return module
|
|
73
|
+
? ` exclude(group = "${group}", module = "${module}")`
|
|
74
|
+
: ` exclude(group = "${group}")`;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return `configurations.all {\n${lines.join('\n')}\n}\n\n`;
|
|
78
|
+
}
|
|
79
|
+
|
|
43
80
|
export function resolveCompileSdkVersionExpression(
|
|
44
81
|
config: ResolvedBrownfieldPluginConfigWithAndroid
|
|
45
82
|
): string {
|
|
@@ -154,6 +191,10 @@ export function createAndroidModule({
|
|
|
154
191
|
android.missingDimensionStrategies
|
|
155
192
|
);
|
|
156
193
|
|
|
194
|
+
const excludeDependenciesBlock = renderExcludeDependenciesBlock(
|
|
195
|
+
android.excludeDependencies
|
|
196
|
+
);
|
|
197
|
+
|
|
157
198
|
// generate module files
|
|
158
199
|
const files: RenderedTemplateFile[] = [
|
|
159
200
|
{
|
|
@@ -171,6 +212,7 @@ export function createAndroidModule({
|
|
|
171
212
|
'{{IS_MINIFY_ENABLED}}': minifyEnabled.toString(),
|
|
172
213
|
'{{MISSING_DIMENSION_STRATEGIES}}': missingDimensionStrategiesList,
|
|
173
214
|
'{{MISSING_DIMENSION_STRATEGY_BLOCK}}': missingDimensionStrategyBlock,
|
|
215
|
+
'{{EXCLUDE_DEPENDENCIES_BLOCK}}': excludeDependenciesBlock,
|
|
174
216
|
}),
|
|
175
217
|
},
|
|
176
218
|
{
|
|
@@ -20,7 +20,7 @@ function ensureExpoDefinesForSDK55AndAbove(
|
|
|
20
20
|
const defaultDeploymentTarget = expoMajor >= 56 ? '16.4' : '15.1';
|
|
21
21
|
const hook = `
|
|
22
22
|
${BROWNFIELD_EXPO_GTE_55_SWIFT_DEFINES_MARKER_START}
|
|
23
|
-
brownfield_ios_deployment_target = podfile_properties['ios.deploymentTarget'] || '${defaultDeploymentTarget}'
|
|
23
|
+
brownfield_ios_deployment_target = (defined?(podfile_properties) && podfile_properties['ios.deploymentTarget']) || '${defaultDeploymentTarget}'
|
|
24
24
|
installer.pods_project.targets.each do |target|
|
|
25
25
|
if target.name == 'ReactBrownfield'
|
|
26
26
|
puts "[Brownfield] Adding definition of EXPO_SDK_GTE_55 to target: #{target.name}"
|
|
@@ -65,11 +65,23 @@ export function modifyPodfile(
|
|
|
65
65
|
expoMajor?: number
|
|
66
66
|
): string {
|
|
67
67
|
// check if the framework target is already included
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
if (
|
|
69
|
+
podfile.includes(`target '${frameworkName}'`) ||
|
|
70
|
+
podfile.includes(`target "${frameworkName}"`)
|
|
71
|
+
) {
|
|
72
|
+
if (typeof expoMajor !== 'number') {
|
|
73
|
+
Logger.logDebug(
|
|
74
|
+
`Framework target "${frameworkName}" already in Podfile, skipping modification`
|
|
75
|
+
);
|
|
76
|
+
return podfile;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// A React Native CLI app can add Expo after it was scaffolded, so a re-run
|
|
80
|
+
// switches the existing target to the Expo host.
|
|
81
|
+
return applyExpoHooks(
|
|
82
|
+
ensureExpoHostInFrameworkTarget(podfile, frameworkName),
|
|
83
|
+
expoMajor
|
|
71
84
|
);
|
|
72
|
-
return podfile;
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
Logger.logDebug(`Modifying Podfile for framework: ${frameworkName}`);
|
|
@@ -113,12 +125,39 @@ export function modifyPodfile(
|
|
|
113
125
|
Logger.logDebug(`Added framework target "${frameworkName}" to Podfile`);
|
|
114
126
|
|
|
115
127
|
// Vanilla projects (expoMajor === undefined) get no Expo-only hooks.
|
|
116
|
-
// Early-returning here lets TypeScript narrow `expoMajor` to `number`
|
|
117
|
-
// for the rest of the function (no casts needed below).
|
|
118
128
|
if (typeof expoMajor !== 'number') {
|
|
119
129
|
return modifiedPodfile;
|
|
120
130
|
}
|
|
121
131
|
|
|
132
|
+
return applyExpoHooks(modifiedPodfile, expoMajor);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function ensureExpoHostInFrameworkTarget(
|
|
136
|
+
podfile: string,
|
|
137
|
+
frameworkName: string
|
|
138
|
+
): string {
|
|
139
|
+
if (/^[ \t]*ENV\['REACT_NATIVE_BROWNFIELD_USE_EXPO_HOST'\]/m.test(podfile)) {
|
|
140
|
+
return podfile;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const escapedName = frameworkName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
144
|
+
const targetLine = new RegExp(
|
|
145
|
+
`^([ \\t]*)(target\\s+['"]${escapedName}['"]\\s+do\\b[^\\r\\n]*(\\r?\\n))`,
|
|
146
|
+
'm'
|
|
147
|
+
);
|
|
148
|
+
if (!targetLine.test(podfile)) {
|
|
149
|
+
throw new SourceModificationError(
|
|
150
|
+
`Couldn't find the "target '${frameworkName}' do" line in the Podfile to enable the Expo host. Add ENV['REACT_NATIVE_BROWNFIELD_USE_EXPO_HOST'] = '1' inside that target block manually.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return podfile.replace(
|
|
155
|
+
targetLine,
|
|
156
|
+
`$1$2$1 ENV['REACT_NATIVE_BROWNFIELD_USE_EXPO_HOST'] = '1'$3`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function applyExpoHooks(podfile: string, expoMajor: number): string {
|
|
122
161
|
if (expoMajor < MIN_SUPPORTED_EXPO_SDK_MAJOR_VERSION) {
|
|
123
162
|
const versionLabel = expoMajor < 0 ? 'unknown' : String(expoMajor);
|
|
124
163
|
throw new SourceModificationError(
|
|
@@ -126,10 +165,5 @@ export function modifyPodfile(
|
|
|
126
165
|
);
|
|
127
166
|
}
|
|
128
167
|
|
|
129
|
-
|
|
130
|
-
modifiedPodfile,
|
|
131
|
-
expoMajor
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
return modifiedPodfile;
|
|
168
|
+
return ensureExpoDefinesForSDK55AndAbove(podfile, expoMajor);
|
|
135
169
|
}
|
|
@@ -26,9 +26,19 @@ export function getFrameworkSourceFiles(
|
|
|
26
26
|
* opposite polarity: `false` here === 'vanilla' there.
|
|
27
27
|
*/
|
|
28
28
|
useExpoHost?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Vanilla template only: a React Native CLI app that uses Expo modules
|
|
31
|
+
* also needs `ensureExpoModulesProvider()` (the Expo template has it).
|
|
32
|
+
*/
|
|
33
|
+
withExpoModules?: boolean;
|
|
29
34
|
}
|
|
30
35
|
): RenderedTemplateFile[] {
|
|
31
36
|
const useExpoHost = options?.useExpoHost ?? true;
|
|
37
|
+
const vanillaInterface =
|
|
38
|
+
renderTemplate('ios', 'FrameworkInterface.vanilla.swift', {}) +
|
|
39
|
+
(options?.withExpoModules
|
|
40
|
+
? renderTemplate('ios', 'ExpoModulesProviderExtension.swift', {})
|
|
41
|
+
: '');
|
|
32
42
|
|
|
33
43
|
return [
|
|
34
44
|
{
|
|
@@ -37,7 +47,7 @@ export function getFrameworkSourceFiles(
|
|
|
37
47
|
? renderTemplate('ios', 'FrameworkInterface.swift', {
|
|
38
48
|
'{{BUNDLE_IDENTIFIER}}': ios.bundleIdentifier,
|
|
39
49
|
})
|
|
40
|
-
:
|
|
50
|
+
: vanillaInterface,
|
|
41
51
|
},
|
|
42
52
|
{
|
|
43
53
|
relativePath: 'Info.plist',
|
|
@@ -216,19 +216,29 @@ export function addSourceFilesBuildPhase(
|
|
|
216
216
|
(filePath) => !filePath.endsWith('.plist')
|
|
217
217
|
);
|
|
218
218
|
|
|
219
|
-
// Idempotency: skip when the target already has a sources phase
|
|
220
|
-
//
|
|
221
|
-
|
|
219
|
+
// Idempotency: skip when the target already has a sources phase, so
|
|
220
|
+
// re-running scaffolding does not duplicate the build phase. CocoaPods
|
|
221
|
+
// rewrites the phase's comment to "Sources" on `pod install`, so match by
|
|
222
|
+
// the phase type as well as by our comment.
|
|
223
|
+
const rawObjects = getRawProjectObjects(project);
|
|
224
|
+
const nativeTargets = rawObjects.PBXNativeTarget as
|
|
222
225
|
| Record<string, PbxNativeTarget>
|
|
223
226
|
| undefined;
|
|
227
|
+
const sourcesPhases = (rawObjects.PBXSourcesBuildPhase ?? {}) as Record<
|
|
228
|
+
string,
|
|
229
|
+
unknown
|
|
230
|
+
>;
|
|
224
231
|
const targetBuildPhases =
|
|
225
232
|
nativeTargets?.[frameworkTargetUUID]?.buildPhases ?? [];
|
|
226
|
-
const alreadyHasSourcePhase = targetBuildPhases.some((phase) =>
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
const alreadyHasSourcePhase = targetBuildPhases.some((phase) => {
|
|
234
|
+
const phaseUuid = getReferencedUuid(phase);
|
|
235
|
+
return (
|
|
236
|
+
hasBuildPhaseComment(
|
|
237
|
+
{ comment: typeof phase === 'string' ? undefined : phase.comment },
|
|
238
|
+
options.frameworkName
|
|
239
|
+
) || Boolean(phaseUuid && sourcesPhases[phaseUuid])
|
|
240
|
+
);
|
|
241
|
+
});
|
|
232
242
|
if (alreadyHasSourcePhase) {
|
|
233
243
|
Logger.logDebug(
|
|
234
244
|
`Sources build phase "${options.frameworkName}" already present on target ${frameworkTargetUUID}, skipping`
|
|
@@ -812,3 +822,51 @@ export function resolveFrameworkDeploymentTarget(
|
|
|
812
822
|
|
|
813
823
|
return appTargetDeploymentTarget ?? '15.0';
|
|
814
824
|
}
|
|
825
|
+
|
|
826
|
+
function isVersionLower(version: string, minimum: string): boolean {
|
|
827
|
+
const a = version.split('.').map(Number);
|
|
828
|
+
const b = minimum.split('.').map(Number);
|
|
829
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
830
|
+
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
|
831
|
+
if (diff !== 0) {
|
|
832
|
+
return diff < 0;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Raises the framework target's iOS deployment target to `minimum` in every
|
|
840
|
+
* build configuration where it is lower. Returns whether anything changed.
|
|
841
|
+
*/
|
|
842
|
+
export function raiseFrameworkDeploymentTarget(
|
|
843
|
+
project: XcodeProject,
|
|
844
|
+
frameworkTargetUUID: string,
|
|
845
|
+
minimum: string
|
|
846
|
+
): boolean {
|
|
847
|
+
const rawObjects = getRawProjectObjects(project);
|
|
848
|
+
const target = rawObjects.PBXNativeTarget?.[frameworkTargetUUID] as
|
|
849
|
+
| PbxNativeTarget
|
|
850
|
+
| undefined;
|
|
851
|
+
const configList = target?.buildConfigurationList
|
|
852
|
+
? rawObjects.XCConfigurationList?.[target.buildConfigurationList]
|
|
853
|
+
: undefined;
|
|
854
|
+
const configs = rawObjects.XCBuildConfiguration ?? {};
|
|
855
|
+
|
|
856
|
+
let changed = false;
|
|
857
|
+
for (const ref of configList?.buildConfigurations ?? []) {
|
|
858
|
+
const uuid = getReferencedUuid(ref);
|
|
859
|
+
const buildSettings = uuid ? configs[uuid]?.buildSettings : undefined;
|
|
860
|
+
if (!buildSettings) {
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
const current = normalizeXcodeBuildSettingValue(
|
|
864
|
+
buildSettings.IPHONEOS_DEPLOYMENT_TARGET
|
|
865
|
+
);
|
|
866
|
+
if (!current || isVersionLower(current, minimum)) {
|
|
867
|
+
buildSettings.IPHONEOS_DEPLOYMENT_TARGET = minimum;
|
|
868
|
+
changed = true;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return changed;
|
|
872
|
+
}
|
|
@@ -5,15 +5,16 @@ import android.content.res.Configuration
|
|
|
5
5
|
import com.callstack.reactnativebrownfield.OnJSBundleLoaded
|
|
6
6
|
import com.callstack.reactnativebrownfield.ReactNativeBrownfield
|
|
7
7
|
import com.facebook.react.PackageList
|
|
8
|
-
import com.facebook.react.ReactHost
|
|
9
|
-
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
|
10
8
|
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
9
|
+
import com.facebook.react.views.view.setEdgeToEdgeFeatureFlagOn
|
|
11
10
|
|
|
12
11
|
object ReactNativeHostManager {
|
|
13
12
|
fun initialize(application: Application, onJSBundleLoaded: OnJSBundleLoaded? = null) {
|
|
14
|
-
|
|
13
|
+
if (BuildConfig.IS_EDGE_TO_EDGE_ENABLED) {
|
|
14
|
+
setEdgeToEdgeFeatureFlagOn()
|
|
15
|
+
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
ReactNativeBrownfield.initialize(application, onJSBundleLoaded) {
|
|
17
18
|
getDefaultReactHost(
|
|
18
19
|
context = application,
|
|
19
20
|
packageList = PackageList(application).packages,
|
|
@@ -24,12 +25,9 @@ object ReactNativeHostManager {
|
|
|
24
25
|
jsRuntimeFactory = null
|
|
25
26
|
)
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
-
ReactNativeBrownfield.initialize(application, reactHost, onJSBundleLoaded)
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
fun onConfigurationChanged(application: Application, newConfig: Configuration) {
|
|
32
31
|
// no-op (kept for API symmetry with Expo variant)
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
|
-
|
|
@@ -69,6 +69,13 @@ export interface BrownfieldPluginAndroidConfig {
|
|
|
69
69
|
* @default []
|
|
70
70
|
*/
|
|
71
71
|
missingDimensionStrategies?: string[];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Dependencies to keep out of the published AAR's POM and module.json, as
|
|
75
|
+
* "group:module" or "group" for a whole group, e.g. "com.caverock:androidsvg".
|
|
76
|
+
* @default []
|
|
77
|
+
*/
|
|
78
|
+
excludeDependencies?: string[];
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
/**
|
package/src/index.ts
CHANGED
|
@@ -96,11 +96,13 @@ const ReactNativeBrownfield = {
|
|
|
96
96
|
): { remove: () => void } => {
|
|
97
97
|
const subscription = ReactNativeBrownfieldModule.onBrownfieldMessage(
|
|
98
98
|
(payload) => {
|
|
99
|
+
let data: unknown;
|
|
99
100
|
try {
|
|
100
|
-
|
|
101
|
+
data = JSON.parse(payload.text);
|
|
101
102
|
} catch {
|
|
102
|
-
|
|
103
|
+
data = payload.text;
|
|
103
104
|
}
|
|
105
|
+
callback({ data });
|
|
104
106
|
}
|
|
105
107
|
);
|
|
106
108
|
return { remove: () => subscription.remove() };
|
package/src/scaffold/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
addFrameworkTarget,
|
|
27
27
|
addSourceFilesBuildPhase,
|
|
28
28
|
copyBundleReactNativePhase,
|
|
29
|
+
raiseFrameworkDeploymentTarget,
|
|
29
30
|
} from '../expo-config-plugin/ios/xcodeHelpers';
|
|
30
31
|
import { createIosFramework } from '../expo-config-plugin/ios/withIosFrameworkFiles';
|
|
31
32
|
import {
|
|
@@ -167,6 +168,35 @@ function resolveReactNativeVersion(projectRoot: string): string {
|
|
|
167
168
|
return rnPkg.version;
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Major Expo SDK version when the app itself depends on `expo` (installed
|
|
173
|
+
* with `install-expo-modules`), otherwise undefined. Only the app's own
|
|
174
|
+
* package.json counts: in a monorepo, `expo` can resolve from a hoisted
|
|
175
|
+
* node_modules even though this app never set it up. Returns -1 when the
|
|
176
|
+
* dependency is declared but not installed, which modifyPodfile reports.
|
|
177
|
+
*/
|
|
178
|
+
function resolveExpoMajor(projectRoot: string): number | undefined {
|
|
179
|
+
const pkg = JSON.parse(
|
|
180
|
+
fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')
|
|
181
|
+
);
|
|
182
|
+
if (!pkg.dependencies?.expo && !pkg.devDependencies?.expo) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const localRequire: NodeRequire =
|
|
187
|
+
typeof require === 'undefined'
|
|
188
|
+
? createRequire(path.join(process.cwd(), 'noop.js'))
|
|
189
|
+
: require;
|
|
190
|
+
try {
|
|
191
|
+
const expoPkg = localRequire(
|
|
192
|
+
localRequire.resolve('expo/package.json', { paths: [projectRoot] })
|
|
193
|
+
);
|
|
194
|
+
return Number.parseInt(String(expoPkg.version).split('.')[0] ?? '', 10);
|
|
195
|
+
} catch {
|
|
196
|
+
return -1;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
170
200
|
export async function scaffoldBrownfieldInRncCliProject(
|
|
171
201
|
options: BrownfieldScaffoldOptions = {}
|
|
172
202
|
): Promise<void> {
|
|
@@ -202,6 +232,12 @@ export async function scaffoldBrownfieldInRncCliProject(
|
|
|
202
232
|
: path.join(projectRoot, iosSourceDir || 'ios');
|
|
203
233
|
|
|
204
234
|
const rnVersion = resolveReactNativeVersion(projectRoot);
|
|
235
|
+
const expoMajor = resolveExpoMajor(projectRoot);
|
|
236
|
+
if (expoMajor !== undefined) {
|
|
237
|
+
Logger.logInfo(
|
|
238
|
+
'Expo modules detected. The iOS framework will start React Native through the Expo host.'
|
|
239
|
+
);
|
|
240
|
+
}
|
|
205
241
|
|
|
206
242
|
const iosFrameworkName = options.iosFrameworkName ?? 'BrownfieldLib';
|
|
207
243
|
const androidModuleName = options.androidModuleName ?? 'brownfieldlib';
|
|
@@ -265,6 +301,7 @@ export async function scaffoldBrownfieldInRncCliProject(
|
|
|
265
301
|
useLocalGradlePlugin: false,
|
|
266
302
|
useLocalMaven: false,
|
|
267
303
|
missingDimensionStrategies: [],
|
|
304
|
+
excludeDependencies: [],
|
|
268
305
|
},
|
|
269
306
|
ios: null,
|
|
270
307
|
debug: options.debug ?? false,
|
|
@@ -306,7 +343,9 @@ export async function scaffoldBrownfieldInRncCliProject(
|
|
|
306
343
|
// an iOS deployment target directly.
|
|
307
344
|
// TODO: derive from the Podfile `platform :ios` line or RN's
|
|
308
345
|
// min_ios_version_supported, or expose a flag.
|
|
309
|
-
|
|
346
|
+
// Expo SDK 56+ modules require iOS 16.4.
|
|
347
|
+
deploymentTarget:
|
|
348
|
+
expoMajor !== undefined && expoMajor >= 56 ? '16.4' : '15.0',
|
|
310
349
|
// Standard CFBundleShortVersionString for the generated framework
|
|
311
350
|
// (matches the Expo plugin default); unrelated to package versioning.
|
|
312
351
|
frameworkVersion: '1',
|
|
@@ -330,13 +369,25 @@ export async function scaffoldBrownfieldInRncCliProject(
|
|
|
330
369
|
projectName: path.basename(xcodeprojPath, '.xcodeproj'),
|
|
331
370
|
};
|
|
332
371
|
|
|
333
|
-
const { frameworkTargetUUID } = addFrameworkTarget(
|
|
372
|
+
const { frameworkTargetUUID, targetAlreadyExists } = addFrameworkTarget(
|
|
334
373
|
project,
|
|
335
374
|
modRequest,
|
|
336
375
|
resolvedIosConfig.ios,
|
|
337
376
|
{ useExpoHost: false }
|
|
338
377
|
);
|
|
339
378
|
|
|
379
|
+
// A target created before Expo was added keeps its old deployment target.
|
|
380
|
+
if (
|
|
381
|
+
targetAlreadyExists &&
|
|
382
|
+
expoMajor !== undefined &&
|
|
383
|
+
expoMajor >= 56 &&
|
|
384
|
+
raiseFrameworkDeploymentTarget(project, frameworkTargetUUID, '16.4')
|
|
385
|
+
) {
|
|
386
|
+
Logger.logInfo(
|
|
387
|
+
`Raised the ${iosFrameworkName} deployment target to iOS 16.4, which Expo SDK 56 and newer require.`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
340
391
|
copyBundleReactNativePhase(project, frameworkTargetUUID);
|
|
341
392
|
addSourceFilesBuildPhase(
|
|
342
393
|
project,
|
|
@@ -355,10 +406,16 @@ export async function scaffoldBrownfieldInRncCliProject(
|
|
|
355
406
|
if (!podfile) {
|
|
356
407
|
throw new Error(`Missing ${podfilePath}`);
|
|
357
408
|
}
|
|
358
|
-
writeFileIfChanged(
|
|
409
|
+
writeFileIfChanged(
|
|
410
|
+
podfilePath,
|
|
411
|
+
modifyPodfile(podfile, iosFrameworkName, expoMajor)
|
|
412
|
+
);
|
|
359
413
|
|
|
360
414
|
currentStep = 'ios-sources';
|
|
361
|
-
createIosFramework(iosDir, resolvedIosConfig, {
|
|
415
|
+
createIosFramework(iosDir, resolvedIosConfig, {
|
|
416
|
+
useExpoHost: false,
|
|
417
|
+
withExpoModules: expoMajor !== undefined,
|
|
418
|
+
});
|
|
362
419
|
|
|
363
420
|
// --- App package wiring: Brownfield deps + package:* scripts ---
|
|
364
421
|
// The scaffolded Kotlin host imports com.callstack.reactnativebrownfield.*,
|
|
@@ -120,15 +120,17 @@ export function addBrownfieldDependencies(projectRoot: string): string[] {
|
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
122
|
* Adds the `package:ios` / `package:android` scripts from the docs' manual
|
|
123
|
-
* steps (settings come from the generated brownfield.config.json, so no
|
|
124
|
-
* are duplicated here). Existing scripts with the same names are kept.
|
|
123
|
+
* steps (settings come from the generated brownfield.config.json, so no other
|
|
124
|
+
* flags are duplicated here). Existing scripts with the same names are kept.
|
|
125
125
|
*/
|
|
126
126
|
export function addBrownfieldPackageScripts(projectRoot: string): string[] {
|
|
127
127
|
const pkg = readPackageJson(projectRoot);
|
|
128
128
|
pkg.scripts = pkg.scripts ?? {};
|
|
129
129
|
|
|
130
130
|
const desiredScripts: Record<string, string> = {
|
|
131
|
-
|
|
131
|
+
// Also emit a local Swift package, so the host app adds one package
|
|
132
|
+
// instead of every XCFramework.
|
|
133
|
+
'package:ios': 'brownfield package:ios --add-spm-package',
|
|
132
134
|
'package:android': 'brownfield package:android',
|
|
133
135
|
};
|
|
134
136
|
|