1import type { ExpoConfig } from '@expo/config-types'; 2import type { JSONObject } from '@expo/json-file'; 3import type { XcodeProject } from 'xcode'; 4 5import type { ConfigPlugin, Mod } from '../Plugin.types'; 6import type { ExpoPlist, InfoPlist } from '../ios/IosConfig.types'; 7import type { AppDelegateProjectFile } from '../ios/Paths'; 8import { get } from '../utils/obj'; 9import { addWarningIOS } from '../utils/warnings'; 10import { withMod } from './withMod'; 11 12type MutateInfoPlistAction = ( 13 expo: ExpoConfig, 14 infoPlist: InfoPlist 15) => Promise<InfoPlist> | InfoPlist; 16 17/** 18 * Helper method for creating mods from existing config functions. 19 * 20 * @param action 21 */ 22export function createInfoPlistPlugin(action: MutateInfoPlistAction, name?: string): ConfigPlugin { 23 const withUnknown: ConfigPlugin = (config) => 24 withInfoPlist(config, async (config) => { 25 config.modResults = await action(config, config.modResults); 26 return config; 27 }); 28 if (name) { 29 Object.defineProperty(withUnknown, 'name', { 30 value: name, 31 }); 32 } 33 return withUnknown; 34} 35 36export function createInfoPlistPluginWithPropertyGuard( 37 action: MutateInfoPlistAction, 38 settings: { 39 infoPlistProperty: string; 40 expoConfigProperty: string; 41 expoPropertyGetter?: (config: ExpoConfig) => string; 42 }, 43 name?: string 44): ConfigPlugin { 45 const withUnknown: ConfigPlugin = (config) => 46 withInfoPlist(config, async (config) => { 47 const existingProperty = settings.expoPropertyGetter 48 ? settings.expoPropertyGetter(config) 49 : get(config, settings.expoConfigProperty); 50 // If the user explicitly sets a value in the infoPlist, we should respect that. 51 if (config.modRawConfig.ios?.infoPlist?.[settings.infoPlistProperty] === undefined) { 52 config.modResults = await action(config, config.modResults); 53 } else if (existingProperty !== undefined) { 54 // Only warn if there is a conflict. 55 addWarningIOS( 56 settings.expoConfigProperty, 57 `"ios.infoPlist.${settings.infoPlistProperty}" is set in the config. Ignoring abstract property "${settings.expoConfigProperty}": ${existingProperty}` 58 ); 59 } 60 61 return config; 62 }); 63 if (name) { 64 Object.defineProperty(withUnknown, 'name', { 65 value: name, 66 }); 67 } 68 return withUnknown; 69} 70 71type MutateEntitlementsPlistAction = (expo: ExpoConfig, entitlements: JSONObject) => JSONObject; 72 73/** 74 * Helper method for creating mods from existing config functions. 75 * 76 * @param action 77 */ 78export function createEntitlementsPlugin( 79 action: MutateEntitlementsPlistAction, 80 name: string 81): ConfigPlugin { 82 const withUnknown: ConfigPlugin = (config) => 83 withEntitlementsPlist(config, async (config) => { 84 config.modResults = await action(config, config.modResults); 85 return config; 86 }); 87 if (name) { 88 Object.defineProperty(withUnknown, 'name', { 89 value: name, 90 }); 91 } 92 return withUnknown; 93} 94 95/** 96 * Provides the AppDelegate file for modification. 97 * 98 * @param config 99 * @param action 100 */ 101export const withAppDelegate: ConfigPlugin<Mod<AppDelegateProjectFile>> = (config, action) => { 102 return withMod(config, { 103 platform: 'ios', 104 mod: 'appDelegate', 105 action, 106 }); 107}; 108 109/** 110 * Provides the Info.plist file for modification. 111 * Keeps the config's expo.ios.infoPlist object in sync with the data. 112 * 113 * @param config 114 * @param action 115 */ 116export const withInfoPlist: ConfigPlugin<Mod<InfoPlist>> = (config, action) => { 117 return withMod<InfoPlist>(config, { 118 platform: 'ios', 119 mod: 'infoPlist', 120 async action(config) { 121 config = await action(config); 122 if (!config.ios) { 123 config.ios = {}; 124 } 125 config.ios.infoPlist = config.modResults; 126 return config; 127 }, 128 }); 129}; 130 131/** 132 * Provides the main .entitlements file for modification. 133 * Keeps the config's expo.ios.entitlements object in sync with the data. 134 * 135 * @param config 136 * @param action 137 */ 138export const withEntitlementsPlist: ConfigPlugin<Mod<JSONObject>> = (config, action) => { 139 return withMod<JSONObject>(config, { 140 platform: 'ios', 141 mod: 'entitlements', 142 async action(config) { 143 config = await action(config); 144 if (!config.ios) { 145 config.ios = {}; 146 } 147 config.ios.entitlements = config.modResults; 148 return config; 149 }, 150 }); 151}; 152 153/** 154 * Provides the Expo.plist for modification. 155 * 156 * @param config 157 * @param action 158 */ 159export const withExpoPlist: ConfigPlugin<Mod<ExpoPlist>> = (config, action) => { 160 return withMod(config, { 161 platform: 'ios', 162 mod: 'expoPlist', 163 action, 164 }); 165}; 166 167/** 168 * Provides the main .xcodeproj for modification. 169 * 170 * @param config 171 * @param action 172 */ 173export const withXcodeProject: ConfigPlugin<Mod<XcodeProject>> = (config, action) => { 174 return withMod(config, { 175 platform: 'ios', 176 mod: 'xcodeproj', 177 action, 178 }); 179}; 180 181/** 182 * Provides the Podfile.properties.json for modification. 183 * 184 * @param config 185 * @param action 186 */ 187export const withPodfileProperties: ConfigPlugin<Mod<Record<string, string>>> = ( 188 config, 189 action 190) => { 191 return withMod(config, { 192 platform: 'ios', 193 mod: 'podfileProperties', 194 action, 195 }); 196}; 197