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/** 80 * Interface representing available configuration for iOS native build properties. 81 * @platform ios 82 */ 83export interface PluginConfigTypeIos { 84 /** 85 * Enable React Native new architecture for iOS platform. 86 */ 87 newArchEnabled?: boolean; 88 /** 89 * Override the default iOS "Deployment Target" version in the following projects: 90 * - in CocoaPods projects, 91 * - `PBXNativeTarget` with "com.apple.product-type.application" `productType` in the app project. 92 */ 93 deploymentTarget?: string; 94 /** 95 * Enable [`use_frameworks!`](https://guides.cocoapods.org/syntax/podfile.html#use_frameworks_bang) 96 * in `Podfile` to use frameworks instead of static libraries for Pods. 97 */ 98 useFrameworks?: 'static' | 'dynamic'; 99} 100 101/** 102 * 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). 103 * @platform android 104 */ 105export interface PluginConfigTypeAndroidPackagingOptions { 106 /** 107 * Array of patterns for native libraries where only the first occurrence is packaged in the APK. 108 */ 109 pickFirst?: string[]; 110 /** 111 * Array of patterns for native libraries that should be excluded from being packaged in the APK. 112 */ 113 exclude?: string[]; 114 /** 115 * Array of patterns for native libraries where all occurrences are concatenated and packaged in the APK. 116 */ 117 merge?: string[]; 118 /** 119 * Array of patterns for native libraries that should not be stripped of debug symbols. 120 */ 121 doNotStrip?: string[]; 122} 123 124const schema: JSONSchemaType<PluginConfigType> = { 125 type: 'object', 126 properties: { 127 android: { 128 type: 'object', 129 properties: { 130 newArchEnabled: { type: 'boolean', nullable: true }, 131 minSdkVersion: { type: 'integer', nullable: true }, 132 compileSdkVersion: { type: 'integer', nullable: true }, 133 targetSdkVersion: { type: 'integer', nullable: true }, 134 buildToolsVersion: { type: 'string', nullable: true }, 135 kotlinVersion: { type: 'string', nullable: true }, 136 137 enableProguardInReleaseBuilds: { type: 'boolean', nullable: true }, 138 extraProguardRules: { type: 'string', nullable: true }, 139 140 packagingOptions: { 141 type: 'object', 142 properties: { 143 pickFirst: { type: 'array', items: { type: 'string' }, nullable: true }, 144 exclude: { type: 'array', items: { type: 'string' }, nullable: true }, 145 merge: { type: 'array', items: { type: 'string' }, nullable: true }, 146 doNotStrip: { type: 'array', items: { type: 'string' }, nullable: true }, 147 }, 148 nullable: true, 149 }, 150 }, 151 nullable: true, 152 }, 153 ios: { 154 type: 'object', 155 properties: { 156 newArchEnabled: { type: 'boolean', nullable: true }, 157 deploymentTarget: { type: 'string', pattern: '\\d+\\.\\d+', nullable: true }, 158 useFrameworks: { type: 'string', enum: ['static', 'dynamic'], nullable: true }, 159 }, 160 nullable: true, 161 }, 162 }, 163}; 164 165// note(Kudo): For the implementation, we check items one by one because Ajv does not well support custom error message. 166/** 167 * Checks if specified versions meets Expo minimal supported versions. 168 * Will throw error message whenever there are invalid versions. 169 * 170 * @param config The validated config passed from Ajv. 171 * @ignore 172 */ 173function maybeThrowInvalidVersions(config: PluginConfigType) { 174 const checkItems = [ 175 { 176 name: 'android.minSdkVersion', 177 configVersion: config.android?.minSdkVersion, 178 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.minSdkVersion, 179 }, 180 { 181 name: 'android.compileSdkVersion', 182 configVersion: config.android?.compileSdkVersion, 183 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.compileSdkVersion, 184 }, 185 { 186 name: 'android.targetSdkVersion', 187 configVersion: config.android?.targetSdkVersion, 188 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.targetSdkVersion, 189 }, 190 { 191 name: 'android.kotlinVersion', 192 configVersion: config.android?.kotlinVersion, 193 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.kotlinVersion, 194 }, 195 { 196 name: 'ios.deploymentTarget', 197 configVersion: config.ios?.deploymentTarget, 198 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.ios.deploymentTarget, 199 }, 200 ]; 201 202 for (const { name, configVersion, minimalVersion } of checkItems) { 203 if ( 204 typeof configVersion === 'number' && 205 typeof minimalVersion === 'number' && 206 configVersion < minimalVersion 207 ) { 208 throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); 209 } 210 if ( 211 typeof configVersion === 'string' && 212 typeof minimalVersion === 'string' && 213 semver.lt(semver.coerce(configVersion) ?? '0.0.0', semver.coerce(minimalVersion) ?? '0.0.0') 214 ) { 215 throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); 216 } 217 } 218} 219 220/** 221 * @ignore 222 */ 223export function validateConfig(config: any): PluginConfigType { 224 const validate = new Ajv().compile(schema); 225 if (!validate(config)) { 226 throw new Error('Invalid expo-build-properties config: ' + JSON.stringify(validate.errors)); 227 } 228 229 maybeThrowInvalidVersions(config); 230 return config; 231} 232