1import Ajv, { JSONSchemaType } from 'ajv'; 2import semver from 'semver'; 3 4/** 5 * The minimal supported versions. These values should align to SDK. 6 * @ignore 7 */ 8const EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS = { 9 android: { 10 minSdkVersion: 21, 11 compileSdkVersion: 31, 12 targetSdkVersion: 31, 13 kotlinVersion: '1.6.10', 14 }, 15 ios: { 16 deploymentTarget: '13.0', 17 }, 18}; 19 20/** 21 * Interface representing base build properties configuration. 22 */ 23export interface PluginConfigType { 24 /** 25 * Interface representing available configuration for Android native build properties. 26 * @platform android 27 */ 28 android?: PluginConfigTypeAndroid; 29 /** 30 * Interface representing available configuration for iOS native build properties. 31 * @platform ios 32 */ 33 ios?: PluginConfigTypeIos; 34} 35 36/** 37 * Interface representing available configuration for Android native build properties. 38 * @platform android 39 */ 40export interface PluginConfigTypeAndroid { 41 /** 42 * Enable React Native new architecture for Android platform. 43 */ 44 newArchEnabled?: boolean; 45 /** 46 * Override the default `minSdkVersion` version number in **build.gradle**. 47 * */ 48 minSdkVersion?: number; 49 /** 50 * Override the default `compileSdkVersion` version number in **build.gradle**. 51 */ 52 compileSdkVersion?: number; 53 /** 54 * Override the default `targetSdkVersion` version number in **build.gradle**. 55 */ 56 targetSdkVersion?: number; 57 /** 58 * Override the default `buildToolsVersion` version number in **build.gradle**. 59 */ 60 buildToolsVersion?: string; 61 /** 62 * Override the Kotlin version used when building the app. 63 */ 64 kotlinVersion?: string; 65 /** 66 * Enable [Proguard or R8](https://developer.android.com/studio/build/shrink-code) in release builds to obfuscate Java code and reduce app size. 67 */ 68 enableProguardInReleaseBuilds?: boolean; 69 /** 70 * Enable [`shrinkResources`](https://developer.android.com/studio/build/shrink-code#shrink-resources) in release builds to remove unused resources from the app. 71 * This property should be used in combination with `enableProguardInReleaseBuilds`. 72 */ 73 enableShrinkResourcesInReleaseBuilds?: boolean; 74 /** 75 * Append custom [Proguard rules](https://www.guardsquare.com/manual/configuration/usage) to **android/app/proguard-rules.pro**. 76 */ 77 extraProguardRules?: string; 78 /** 79 * Interface representing available configuration for Android Gradle plugin [PackagingOptions](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions). 80 */ 81 packagingOptions?: PluginConfigTypeAndroidPackagingOptions; 82 83 /** 84 * By default, Flipper is enabled with the version that comes bundled with `react-native`. 85 * 86 * Use this to change the [Flipper](https://fbflipper.com/) version when 87 * running your app on Android. You can set the `flipper` property to a 88 * semver string and specify an alternate Flipper version. 89 */ 90 flipper?: string; 91 92 /** 93 * Enable the Network Inspector. 94 * 95 * @default true 96 */ 97 networkInspector?: boolean; 98 99 /** 100 * Add extra maven repositories to all gradle projects. 101 * 102 * This acts like to add the following code to **android/build.gradle**: 103 * ```groovy 104 * allprojects { 105 * repositories { 106 * maven { 107 * url [THE_EXTRA_MAVEN_REPOSITORY] 108 * } 109 * } 110 * } 111 * ``` 112 * 113 * @hide For the implementation details, 114 * this property is actually handled by `expo-modules-autolinking` but not the config-plugins inside expo-build-properties. 115 */ 116 extraMavenRepos?: string[]; 117 /** 118 * Indicates whether the app intends to use cleartext network traffic. 119 * 120 * @default false 121 * 122 * @see [Android documentation](https://developer.android.com/guide/topics/manifest/application-element#usesCleartextTraffic) 123 */ 124 usesCleartextTraffic?: boolean; 125} 126 127/** 128 * Interface representing available configuration for iOS native build properties. 129 * @platform ios 130 */ 131export interface PluginConfigTypeIos { 132 /** 133 * Enable React Native new architecture for iOS platform. 134 */ 135 newArchEnabled?: boolean; 136 /** 137 * Override the default iOS "Deployment Target" version in the following projects: 138 * - in CocoaPods projects, 139 * - `PBXNativeTarget` with "com.apple.product-type.application" `productType` in the app project. 140 */ 141 deploymentTarget?: string; 142 143 /** 144 * Enable [`use_frameworks!`](https://guides.cocoapods.org/syntax/podfile.html#use_frameworks_bang) 145 * in `Podfile` to use frameworks instead of static libraries for Pods. 146 * 147 * > You cannot use `useFrameworks` and `flipper` at the same time, and 148 * doing so will generate an error. 149 */ 150 useFrameworks?: 'static' | 'dynamic'; 151 152 /** 153 * Enable [Flipper](https://fbflipper.com/) when running your app on iOS in 154 * Debug mode. Setting `true` enables the default version of Flipper, while 155 * setting a semver string will enable a specific version of Flipper you've 156 * declared in your **package.json**. The default for this configuration is `false`. 157 * 158 * > You cannot use `flipper` at the same time as `useFrameworks`, and 159 * doing so will generate an error. 160 */ 161 flipper?: boolean | string; 162 163 /** 164 * Enable the Network Inspector. 165 * 166 * @default true 167 */ 168 networkInspector?: boolean; 169 170 /** 171 * Add extra CocoaPods dependencies for all targets. 172 * 173 * This acts like to add the following code to **ios/Podfile**: 174 * ``` 175 * pod '[EXTRA_POD_NAME]', '~> [EXTRA_POD_VERSION]' 176 * # e.g. 177 * pod 'Protobuf', '~> 3.14.0' 178 * ``` 179 * 180 * @hide For the implementation details, 181 * this property is actually handled by `expo-modules-autolinking` but not the config-plugins inside expo-build-properties. 182 */ 183 extraPods?: ExtraIosPodDependency[]; 184} 185 186/** 187 * Interface representing extra CocoaPods dependency. 188 * @see [Podfile syntax reference](https://guides.cocoapods.org/syntax/podfile.html#pod) 189 * @platform ios 190 */ 191export interface ExtraIosPodDependency { 192 /** 193 * Name of the pod. 194 */ 195 name: string; 196 /** 197 * Version of the pod. 198 * CocoaPods supports various [versioning options](https://guides.cocoapods.org/using/the-podfile.html#pod). 199 * @example `~> 0.1.2` 200 */ 201 version?: string; 202 /** 203 * Build configurations for which the pod should be installed. 204 * @example `['Debug', 'Release']` 205 */ 206 configurations?: string[]; 207 /** 208 * Whether this pod should use modular headers. 209 */ 210 modular_headers?: boolean; 211 /** 212 * Custom source to search for this dependency. 213 * @example `https://github.com/CocoaPods/Specs.git` 214 */ 215 source?: string; 216 /** 217 * Custom local filesystem path to add the dependency. 218 * @example `~/Documents/AFNetworking` 219 */ 220 path?: string; 221 /** 222 * Custom podspec path. 223 * @example `https://example.com/JSONKit.podspec` 224 */ 225 podspec?: string; 226 /** 227 * Test specs can be optionally included via the :testspecs option. By default, none of a Pod's test specs are included. 228 * @example `['UnitTests', 'SomeOtherTests']` 229 */ 230 testspecs?: string[]; 231 /** 232 * Use the bleeding edge version of a Pod. 233 * 234 * @example 235 * ``` 236 * { "name": "AFNetworking", "git": "https://github.com/gowalla/AFNetworking.git", "tag": "0.7.0" } 237 * ``` 238 * 239 * This acts like to add this pod dependency statement: 240 * ``` 241 * pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0' 242 * ``` 243 */ 244 git?: string; 245 /** 246 * The git branch to fetch. See the {@link git} property for more information. 247 */ 248 branch?: string; 249 /** 250 * The git tag to fetch. See the {@link git} property for more information. 251 */ 252 tag?: string; 253 /** 254 * The git commit to fetch. See the {@link git} property for more information. 255 */ 256 commit?: string; 257} 258 259/** 260 * Interface representing available configuration for Android Gradle plugin [PackagingOptions](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions). 261 * @platform android 262 */ 263export interface PluginConfigTypeAndroidPackagingOptions { 264 /** 265 * Array of patterns for native libraries where only the first occurrence is packaged in the APK. 266 */ 267 pickFirst?: string[]; 268 /** 269 * Array of patterns for native libraries that should be excluded from being packaged in the APK. 270 */ 271 exclude?: string[]; 272 /** 273 * Array of patterns for native libraries where all occurrences are concatenated and packaged in the APK. 274 */ 275 merge?: string[]; 276 /** 277 * Array of patterns for native libraries that should not be stripped of debug symbols. 278 */ 279 doNotStrip?: string[]; 280} 281 282const schema: JSONSchemaType<PluginConfigType> = { 283 type: 'object', 284 properties: { 285 android: { 286 type: 'object', 287 properties: { 288 newArchEnabled: { type: 'boolean', nullable: true }, 289 minSdkVersion: { type: 'integer', nullable: true }, 290 compileSdkVersion: { type: 'integer', nullable: true }, 291 targetSdkVersion: { type: 'integer', nullable: true }, 292 buildToolsVersion: { type: 'string', nullable: true }, 293 kotlinVersion: { type: 'string', nullable: true }, 294 295 enableProguardInReleaseBuilds: { type: 'boolean', nullable: true }, 296 enableShrinkResourcesInReleaseBuilds: { type: 'boolean', nullable: true }, 297 extraProguardRules: { type: 'string', nullable: true }, 298 299 flipper: { 300 type: 'string', 301 nullable: true, 302 }, 303 304 packagingOptions: { 305 type: 'object', 306 properties: { 307 pickFirst: { type: 'array', items: { type: 'string' }, nullable: true }, 308 exclude: { type: 'array', items: { type: 'string' }, nullable: true }, 309 merge: { type: 'array', items: { type: 'string' }, nullable: true }, 310 doNotStrip: { type: 'array', items: { type: 'string' }, nullable: true }, 311 }, 312 nullable: true, 313 }, 314 315 networkInspector: { type: 'boolean', nullable: true }, 316 317 extraMavenRepos: { type: 'array', items: { type: 'string' }, nullable: true }, 318 319 usesCleartextTraffic: { type: 'boolean', nullable: true }, 320 }, 321 nullable: true, 322 }, 323 ios: { 324 type: 'object', 325 properties: { 326 newArchEnabled: { type: 'boolean', nullable: true }, 327 deploymentTarget: { type: 'string', pattern: '\\d+\\.\\d+', nullable: true }, 328 useFrameworks: { type: 'string', enum: ['static', 'dynamic'], nullable: true }, 329 330 flipper: { 331 type: ['boolean', 'string'], 332 nullable: true, 333 }, 334 335 networkInspector: { type: 'boolean', nullable: true }, 336 337 extraPods: { 338 type: 'array', 339 items: { 340 type: 'object', 341 required: ['name'], 342 properties: { 343 name: { type: 'string' }, 344 version: { type: 'string', nullable: true }, 345 configurations: { type: 'array', items: { type: 'string' }, nullable: true }, 346 modular_headers: { type: 'boolean', nullable: true }, 347 source: { type: 'string', nullable: true }, 348 path: { type: 'string', nullable: true }, 349 podspec: { type: 'string', nullable: true }, 350 testspecs: { type: 'array', items: { type: 'string' }, nullable: true }, 351 git: { type: 'string', nullable: true }, 352 branch: { type: 'string', nullable: true }, 353 tag: { type: 'string', nullable: true }, 354 commit: { type: 'string', nullable: true }, 355 }, 356 }, 357 nullable: true, 358 }, 359 }, 360 nullable: true, 361 }, 362 }, 363}; 364 365// note(Kudo): For the implementation, we check items one by one because Ajv does not well support custom error message. 366/** 367 * Checks if specified versions meets Expo minimal supported versions. 368 * Will throw error message whenever there are invalid versions. 369 * 370 * @param config The validated config passed from Ajv. 371 * @ignore 372 */ 373function maybeThrowInvalidVersions(config: PluginConfigType) { 374 const checkItems = [ 375 { 376 name: 'android.minSdkVersion', 377 configVersion: config.android?.minSdkVersion, 378 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.minSdkVersion, 379 }, 380 { 381 name: 'android.compileSdkVersion', 382 configVersion: config.android?.compileSdkVersion, 383 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.compileSdkVersion, 384 }, 385 { 386 name: 'android.targetSdkVersion', 387 configVersion: config.android?.targetSdkVersion, 388 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.targetSdkVersion, 389 }, 390 { 391 name: 'android.kotlinVersion', 392 configVersion: config.android?.kotlinVersion, 393 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.kotlinVersion, 394 }, 395 { 396 name: 'ios.deploymentTarget', 397 configVersion: config.ios?.deploymentTarget, 398 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.ios.deploymentTarget, 399 }, 400 ]; 401 402 for (const { name, configVersion, minimalVersion } of checkItems) { 403 if ( 404 typeof configVersion === 'number' && 405 typeof minimalVersion === 'number' && 406 configVersion < minimalVersion 407 ) { 408 throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); 409 } 410 if ( 411 typeof configVersion === 'string' && 412 typeof minimalVersion === 'string' && 413 semver.lt(semver.coerce(configVersion) ?? '0.0.0', semver.coerce(minimalVersion) ?? '0.0.0') 414 ) { 415 throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); 416 } 417 } 418} 419 420/** 421 * @ignore 422 */ 423export function validateConfig(config: any): PluginConfigType { 424 const validate = new Ajv({ allowUnionTypes: true }).compile(schema); 425 if (!validate(config)) { 426 throw new Error('Invalid expo-build-properties config: ' + JSON.stringify(validate.errors)); 427 } 428 429 maybeThrowInvalidVersions(config); 430 431 // explicitly block using use_frameworks and Flipper in iOS 432 // https://github.com/facebook/flipper/issues/2414 433 if (Boolean(config.ios?.flipper) && config.ios?.useFrameworks !== undefined) { 434 throw new Error('`ios.flipper` cannot be enabled when `ios.useFrameworks` is set.'); 435 } 436 437 if ( 438 config.android?.enableShrinkResourcesInReleaseBuilds === true && 439 config.android?.enableProguardInReleaseBuilds !== true 440 ) { 441 throw new Error( 442 '`android.enableShrinkResourcesInReleaseBuilds` requires `android.enableProguardInReleaseBuilds` to be enabled.' 443 ); 444 } 445 446 return config; 447} 448