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