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 * Configuration for `expo-build-properties` 22 */ 23export interface PluginConfigType { 24 android?: PluginConfigTypeAndroid; 25 ios?: PluginConfigTypeIos; 26} 27 28/** 29 * Config for Android native build properties 30 */ 31export interface PluginConfigTypeAndroid { 32 /** Override the default `minSdkVersion` version number in `build.gradle` */ 33 minSdkVersion?: number; 34 35 /** Override the default `compileSdkVersion` version number in `build.gradle` */ 36 compileSdkVersion?: number; 37 38 /** Override the default `targetSdkVersion` version number in `build.gradle` */ 39 targetSdkVersion?: number; 40 41 /** Override the default `buildToolsVersion` version number in `build.gradle` */ 42 buildToolsVersion?: string; 43 44 /** Override the default Kotlin version when building the app */ 45 kotlinVersion?: string; 46 47 /** Enable Proguard (R8) in release builds to obfuscate Java code and reduce app size */ 48 enableProguardInReleaseBuilds?: boolean; 49 50 /** Append custom [Proguard rules](https://www.guardsquare.com/manual/configuration/usage) to `android/app/proguard-rules.pro` */ 51 extraProguardRules?: string; 52 53 /** AGP [PackagingOptions](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions) */ 54 packagingOptions?: PluginConfigTypeAndroidPackagingOptions; 55} 56 57/** 58 * Config for iOS native build properties 59 */ 60export interface PluginConfigTypeIos { 61 /** 62 * Override the default iOS *Deployment Target* version in the following projects: 63 * - in CocoaPods projects 64 * - `PBXNativeTarget` with `com.apple.product-type.application` productType in the app project 65 */ 66 deploymentTarget?: string; 67 68 /** Enable [`use_frameworks!`](https://guides.cocoapods.org/syntax/podfile.html#use_frameworks_bang) in `Podfile` */ 69 useFrameworks?: 'static' | 'dynamic'; 70} 71 72/** 73 * AGP [PackagingOptions](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions) 74 */ 75export interface PluginConfigTypeAndroidPackagingOptions { 76 /** Adds a first-pick pattern */ 77 pickFirst?: string[]; 78 79 /** Adds an excluded pattern */ 80 exclude?: string[]; 81 82 /** Adds a merge pattern */ 83 merge?: string[]; 84 85 /** Adds a doNotStrip pattern */ 86 doNotStrip?: string[]; 87} 88 89const schema: JSONSchemaType<PluginConfigType> = { 90 type: 'object', 91 properties: { 92 android: { 93 type: 'object', 94 properties: { 95 minSdkVersion: { type: 'integer', nullable: true }, 96 compileSdkVersion: { type: 'integer', nullable: true }, 97 targetSdkVersion: { type: 'integer', nullable: true }, 98 buildToolsVersion: { type: 'string', nullable: true }, 99 kotlinVersion: { type: 'string', nullable: true }, 100 101 enableProguardInReleaseBuilds: { type: 'boolean', nullable: true }, 102 extraProguardRules: { type: 'string', nullable: true }, 103 104 packagingOptions: { 105 type: 'object', 106 properties: { 107 pickFirst: { type: 'array', items: { type: 'string' }, nullable: true }, 108 exclude: { type: 'array', items: { type: 'string' }, nullable: true }, 109 merge: { type: 'array', items: { type: 'string' }, nullable: true }, 110 doNotStrip: { type: 'array', items: { type: 'string' }, nullable: true }, 111 }, 112 nullable: true, 113 }, 114 }, 115 nullable: true, 116 }, 117 ios: { 118 type: 'object', 119 properties: { 120 deploymentTarget: { type: 'string', pattern: '\\d+\\.\\d+', nullable: true }, 121 useFrameworks: { type: 'string', enum: ['static', 'dynamic'], nullable: true }, 122 }, 123 nullable: true, 124 }, 125 }, 126}; 127 128/** 129 * Check versions to meet expo minimal supported versions. 130 * Will throw error message whenever there are invalid versions. 131 * For the implementation, we check items one by one because ajv does not well support custom error message. 132 * 133 * @param config the validated config passed from ajv 134 * @ignore 135 */ 136function maybeThrowInvalidVersions(config: PluginConfigType) { 137 const checkItems = [ 138 { 139 name: 'android.minSdkVersion', 140 configVersion: config.android?.minSdkVersion, 141 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.minSdkVersion, 142 }, 143 { 144 name: 'android.compileSdkVersion', 145 configVersion: config.android?.compileSdkVersion, 146 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.compileSdkVersion, 147 }, 148 { 149 name: 'android.targetSdkVersion', 150 configVersion: config.android?.targetSdkVersion, 151 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.targetSdkVersion, 152 }, 153 { 154 name: 'android.kotlinVersion', 155 configVersion: config.android?.kotlinVersion, 156 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.kotlinVersion, 157 }, 158 { 159 name: 'ios.deploymentTarget', 160 configVersion: config.ios?.deploymentTarget, 161 minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.ios.deploymentTarget, 162 }, 163 ]; 164 165 for (const { name, configVersion, minimalVersion } of checkItems) { 166 if ( 167 typeof configVersion === 'number' && 168 typeof minimalVersion === 'number' && 169 configVersion < minimalVersion 170 ) { 171 throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); 172 } 173 if ( 174 typeof configVersion === 'string' && 175 typeof minimalVersion === 'string' && 176 semver.lt(semver.coerce(configVersion) ?? '0.0.0', semver.coerce(minimalVersion) ?? '0.0.0') 177 ) { 178 throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`); 179 } 180 } 181} 182 183/** 184 * @ignore 185 */ 186export function validateConfig(config: any): PluginConfigType { 187 const validate = new Ajv().compile(schema); 188 if (!validate(config)) { 189 throw new Error('Invalid expo-build-properties config: ' + JSON.stringify(validate.errors)); 190 } 191 192 maybeThrowInvalidVersions(config); 193 return config; 194} 195