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 experimental Network Inspector for [Development builds](https://docs.expo.dev/develop/development-builds/introduction/). 94 * SDK 49+ is required. 95 */ 96 unstable_networkInspector?: boolean; 97} 98 99/** 100 * Interface representing available configuration for iOS native build properties. 101 * @platform ios 102 */ 103export interface PluginConfigTypeIos { 104 /** 105 * Enable React Native new architecture for iOS platform. 106 */ 107 newArchEnabled?: boolean; 108 /** 109 * Override the default iOS "Deployment Target" version in the following projects: 110 * - in CocoaPods projects, 111 * - `PBXNativeTarget` with "com.apple.product-type.application" `productType` in the app project. 112 */ 113 deploymentTarget?: string; 114 115 /** 116 * Enable [`use_frameworks!`](https://guides.cocoapods.org/syntax/podfile.html#use_frameworks_bang) 117 * in `Podfile` to use frameworks instead of static libraries for Pods. 118 * 119 * > You cannot use `useFrameworks` and `flipper` at the same time , and 120 * doing so will generate an error. 121 */ 122 useFrameworks?: 'static' | 'dynamic'; 123 124 /** 125 * Enable [Flipper](https://fbflipper.com/) when running your app on iOS in 126 * Debug mode. Setting `true` enables the default version of Flipper, while 127 * setting a semver string will enable a specific version of Flipper you've 128 * declared in your **package.json**. The default for this configuration is `false`. 129 * 130 * > You cannot use `flipper` at the same time as `useFrameworks`, and 131 * doing so will generate an error. 132 */ 133 flipper?: boolean | string; 134 135 /** 136 * Enable the experimental Network Inspector for [Development builds](https://docs.expo.dev/develop/development-builds/introduction/). 137 * SDK 49+ is required. 138 */ 139 unstable_networkInspector?: boolean; 140} 141 142/** 143 * 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). 144 * @platform android 145 */ 146export interface PluginConfigTypeAndroidPackagingOptions { 147 /** 148 * Array of patterns for native libraries where only the first occurrence is packaged in the APK. 149 */ 150 pickFirst?: string[]; 151 /** 152 * Array of patterns for native libraries that should be excluded from being packaged in the APK. 153 */ 154 exclude?: string[]; 155 /** 156 * Array of patterns for native libraries where all occurrences are concatenated and packaged in the APK. 157 */ 158 merge?: string[]; 159 /** 160 * Array of patterns for native libraries that should not be stripped of debug symbols. 161 */ 162 doNotStrip?: string[]; 163} 164 165const schema: JSONSchemaType<PluginConfigType> = { 166 type: 'object', 167 properties: { 168 android: { 169 type: 'object', 170 properties: { 171 newArchEnabled: { type: 'boolean', nullable: true }, 172 minSdkVersion: { type: 'integer', nullable: true }, 173 compileSdkVersion: { type: 'integer', nullable: true }, 174 targetSdkVersion: { type: 'integer', nullable: true }, 175 buildToolsVersion: { type: 'string', nullable: true }, 176 kotlinVersion: { type: 'string', nullable: true }, 177 178 enableProguardInReleaseBuilds: { type: 'boolean', nullable: true }, 179 enableShrinkResourcesInReleaseBuilds: { type: 'boolean', nullable: true }, 180 extraProguardRules: { type: 'string', nullable: true }, 181 182 flipper: { 183 type: 'string', 184 nullable: true, 185 }, 186 187 packagingOptions: { 188 type: 'object', 189 properties: { 190 pickFirst: { type: 'array', items: { type: 'string' }, nullable: true }, 191 exclude: { type: 'array', items: { type: 'string' }, nullable: true }, 192 merge: { type: 'array', items: { type: 'string' }, nullable: true }, 193 doNotStrip: { type: 'array', items: { type: 'string' }, nullable: true }, 194 }, 195 nullable: true, 196 }, 197 198 unstable_networkInspector: { type: 'boolean', nullable: true }, 199 }, 200 nullable: true, 201 }, 202 ios: { 203 type: 'object', 204 properties: { 205 newArchEnabled: { type: 'boolean', nullable: true }, 206 deploymentTarget: { type: 'string', pattern: '\\d+\\.\\d+', nullable: true }, 207 useFrameworks: { type: 'string', enum: ['static', 'dynamic'], nullable: true }, 208 209 flipper: { 210 type: ['boolean', 'string'], 211 nullable: true, 212 }, 213 214 unstable_networkInspector: { type: 'boolean', nullable: true }, 215 }, 216 nullable: true, 217 }, 218 }, 219}; 220 221// note(Kudo): For the implementation, we check items one by one because Ajv does not well support custom error message. 222/** 223 * Checks if specified versions meets Expo minimal supported versions. 224 * Will throw error message whenever there are invalid versions. 225 * 226 * @param config The validated config passed from Ajv. 227 * @ignore 228 */ 229function maybeThrowInvalidVersions(config: PluginConfigType) { 230 const checkItems = [ 231 { 232 name: 'android.minSdkVersion', 233 configVersion: config.android?.minSdkVersion, 234 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.minSdkVersion, 235 }, 236 { 237 name: 'android.compileSdkVersion', 238 configVersion: config.android?.compileSdkVersion, 239 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.compileSdkVersion, 240 }, 241 { 242 name: 'android.targetSdkVersion', 243 configVersion: config.android?.targetSdkVersion, 244 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.targetSdkVersion, 245 }, 246 { 247 name: 'android.kotlinVersion', 248 configVersion: config.android?.kotlinVersion, 249 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.kotlinVersion, 250 }, 251 { 252 name: 'ios.deploymentTarget', 253 configVersion: config.ios?.deploymentTarget, 254 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.ios.deploymentTarget, 255 }, 256 ]; 257 258 for (const { name, configVersion, minimalVersion } of checkItems) { 259 if ( 260 typeof configVersion === 'number' && 261 typeof minimalVersion === 'number' && 262 configVersion < minimalVersion 263 ) { 264 throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); 265 } 266 if ( 267 typeof configVersion === 'string' && 268 typeof minimalVersion === 'string' && 269 semver.lt(semver.coerce(configVersion) ?? '0.0.0', semver.coerce(minimalVersion) ?? '0.0.0') 270 ) { 271 throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); 272 } 273 } 274} 275 276/** 277 * @ignore 278 */ 279export function validateConfig(config: any): PluginConfigType { 280 const validate = new Ajv({ allowUnionTypes: true }).compile(schema); 281 if (!validate(config)) { 282 throw new Error('Invalid expo-build-properties config: ' + JSON.stringify(validate.errors)); 283 } 284 285 maybeThrowInvalidVersions(config); 286 287 // explicitly block using use_frameworks and Flipper in iOS 288 // https://github.com/facebook/flipper/issues/2414 289 if (Boolean(config.ios?.flipper) && config.ios?.useFrameworks !== undefined) { 290 throw new Error('`ios.flipper` cannot be enabled when `ios.useFrameworks` is set.'); 291 } 292 293 if ( 294 config.android?.enableShrinkResourcesInReleaseBuilds === true && 295 config.android?.enableProguardInReleaseBuilds !== true 296 ) { 297 throw new Error( 298 '`android.enableShrinkResourcesInReleaseBuilds` requires `android.enableProguardInReleaseBuilds` to be enabled.' 299 ); 300 } 301 302 return config; 303} 304