import { ExpoConfig } from '@expo/config-types'; import { XcodeProject } from 'xcode'; import { InfoPlist } from './IosConfig.types'; import { findFirstNativeTarget } from './Target'; import { ConfigurationSectionEntry, getBuildConfigurationsForListId, sanitizedName, } from './utils/Xcodeproj'; import { ConfigPlugin } from '../Plugin.types'; import { createInfoPlistPluginWithPropertyGuard, withXcodeProject } from '../plugins/ios-plugins'; export const withDisplayName = createInfoPlistPluginWithPropertyGuard( setDisplayName, { infoPlistProperty: 'CFBundleDisplayName', expoConfigProperty: 'name', }, 'withDisplayName' ); export const withName = createInfoPlistPluginWithPropertyGuard( setName, { infoPlistProperty: 'CFBundleName', expoConfigProperty: 'name', }, 'withName' ); /** Set the PRODUCT_NAME variable in the xcproj file based on the app.json name property. */ export const withProductName: ConfigPlugin = (config) => { return withXcodeProject(config, (config) => { config.modResults = setProductName(config, config.modResults); return config; }); }; export function getName(config: Pick) { return typeof config.name === 'string' ? config.name : null; } /** * CFBundleDisplayName is used for most things: the name on the home screen, in * notifications, and others. */ export function setDisplayName( configOrName: Pick | string, { CFBundleDisplayName, ...infoPlist }: InfoPlist ): InfoPlist { let name: string | null = null; if (typeof configOrName === 'string') { name = configOrName; } else { name = getName(configOrName); } if (!name) { return infoPlist; } return { ...infoPlist, CFBundleDisplayName: name, }; } /** * CFBundleName is recommended to be 16 chars or less and is used in lists, eg: * sometimes on the App Store */ export function setName( config: Pick, { CFBundleName, ...infoPlist }: InfoPlist ): InfoPlist { const name = getName(config); if (!name) { return infoPlist; } return { ...infoPlist, CFBundleName: name, }; } export function setProductName( config: Pick, project: XcodeProject ): XcodeProject { const name = sanitizedName(getName(config) ?? ''); if (!name) { return project; } const quotedName = ensureQuotes(name); const [, nativeTarget] = findFirstNativeTarget(project); getBuildConfigurationsForListId(project, nativeTarget.buildConfigurationList).forEach( ([, item]: ConfigurationSectionEntry) => { item.buildSettings.PRODUCT_NAME = quotedName; } ); return project; } const ensureQuotes = (value: string) => { if (!value.match(/^['"]/)) { return `"${value}"`; } return value; };