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