import { ExpoConfig } from '@expo/config-types' ; import plist , { PlistObject } from '@expo/plist' ; import assert from 'assert' ; import fs from 'fs-extra' ; import xcode , { XCBuildConfiguration } from 'xcode' ; import { ConfigPlugin } from '../Plugin.types' ; import { withDangerousMod } from '../plugins/withDangerousMod' ; import { InfoPlist } from './IosConfig.types' ; import { getAllInfoPlistPaths , getAllPBXProjectPaths , getPBXProjectPath } from './Paths' ; import { findFirstNativeTarget , getXCBuildConfigurationFromPbxproj } from './Target' ; import { ConfigurationSectionEntry , getBuildConfigurationsForListId } from './utils/Xcodeproj' ; import { trimQuotes } from './utils/string' ; export const withBundleIdentifier = ( config , { bundleIdentifier } ) => { return withDangerousMod ( config , [ 'ios' , async ( config ) => { const bundleId = bundleIdentifier ?? config . ios ?. bundleIdentifier ; assert ( bundleId , '`bundleIdentifier` must be defined in the app config (`expo.ios.bundleIdentifier`) or passed to the plugin `withBundleIdentifier`.' ) ; await setBundleIdentifierForPbxproj ( config . modRequest . projectRoot , bundleId ! ) ; return config ; } , ] ) ; } ; function getBundleIdentifier ( config ) { return config . ios ?. bundleIdentifier ?? null ; } /**
 * In Turtle v1 we set the bundleIdentifier directly on Info.plist rather
 * than in pbxproj
 */ function setBundleIdentifier ( config , infoPlist ) { const bundleIdentifier = getBundleIdentifier ( config ) ; if ( ! bundleIdentifier ) { return infoPlist ; } return { ... infoPlist , CFBundleIdentifier : bundleIdentifier } ; } param { string } projectRoot param { string } targetName param { string } buildConfiguration returns { string | null } function getBundleIdentifierFromPbxproj ( projectRoot , { targetName , buildConfiguration = 'Release' , } = { } ) { let pbxprojPath ; try { pbxprojPath = getPBXProjectPath ( projectRoot ) ; } catch { return null ; } const project = xcode . project ( pbxprojPath ) ; project . parseSync ( ) ; const xcBuildConfiguration = getXCBuildConfigurationFromPbxproj ( project , { targetName , buildConfiguration } ) ; if ( ! xcBuildConfiguration ) { return null ; } return getProductBundleIdentifierFromBuildConfiguration ( xcBuildConfiguration ) ; } function getProductBundleIdentifierFromBuildConfiguration ( xcBuildConfiguration ) { const bundleIdentifierRaw = xcBuildConfiguration . buildSettings . PRODUCT_BUNDLE_IDENTIFIER ; if ( bundleIdentifierRaw ) { const bundleIdentifier = trimQuotes ( bundleIdentifierRaw ) ; const bundleIdentifierParts = bundleIdentifier . split ( '.' ) ; if ( bundleIdentifierParts [ bundleIdentifierParts . length - 1 ] === '$(PRODUCT_NAME:rfc1034identifier)' && xcBuildConfiguration . buildSettings . PRODUCT_NAME ) { bundleIdentifierParts [ bundleIdentifierParts . length - 1 ] = xcBuildConfiguration . buildSettings . PRODUCT_NAME . replace ( /[^a-zA-Z0-9]/g , '-' ) ; } return bundleIdentifierParts . join ( '.' ) ; } else { return null ; } } param { string } pbxprojPath param { string } bundleIdentifier param { boolean } updateProductName function updateBundleIdentifierForPbxproj ( pbxprojPath , bundleIdentifier , updateProductName = true ) { const project = xcode . project ( pbxprojPath ) ; project . parseSync ( ) ; const [ , nativeTarget ] = findFirstNativeTarget ( project ) ; getBuildConfigurationsForListId ( project , nativeTarget . buildConfigurationList ) . forEach ( ( [ , item ] ) => { if ( item . buildSettings . PRODUCT_BUNDLE_IDENTIFIER === bundleIdentifier ) { return ; } item . buildSettings . PRODUCT_BUNDLE_IDENTIFIER = `"${ bundleIdentifier }"` ; if ( updateProductName ) { const productName = bundleIdentifier . split ( '.' ) . pop ( ) ; if ( ! productName ?. includes ( '$' ) ) { item . buildSettings . PRODUCT_NAME = productName ; } } } ) ; fs . writeFileSync ( pbxprojPath , project . writeSync ( ) ) ; } param { string } projectRoot param { string } bundleIdentifier param { boolean } updateProductName function setBundleIdentifierForPbxproj ( projectRoot , bundleIdentifier , updateProductName = true ) { let pbxprojPaths = [ ] ; try { pbxprojPaths = getAllPBXProjectPaths ( projectRoot ) ; } catch { } for ( const pbxprojPath of pbxprojPaths ) { updateBundleIdentifierForPbxproj ( pbxprojPath , bundleIdentifier , updateProductName ) ; } } /**
 * Reset bundle identifier field in Info.plist to use PRODUCT_BUNDLE_IDENTIFIER, as recommended by Apple.
 */ const defaultBundleId = '$(PRODUCT_BUNDLE_IDENTIFIER)' ; function resetAllPlistBundleIdentifiers ( projectRoot ) { const infoPlistPaths = getAllInfoPlistPaths ( projectRoot ) ; for ( const plistPath of infoPlistPaths ) { resetPlistBundleIdentifier ( plistPath ) ; } } function resetPlistBundleIdentifier ( plistPath ) { const rawPlist = fs . readFileSync ( plistPath , 'utf8' ) ; const plistObject = plist . parse ( rawPlist ) as PlistObject ; if ( plistObject . CFBundleIdentifier ) { if ( plistObject . CFBundleIdentifier === defaultBundleId ) return ; const format = { pretty : true , indent : `\t` } ; const xml = plist . build ( { ... plistObject , CFBundleIdentifier : defaultBundleId } , format ) ; if ( xml !== rawPlist ) { fs . writeFileSync ( plistPath , xml ) ; } } } export { getBundleIdentifier , setBundleIdentifier , getBundleIdentifierFromPbxproj , updateBundleIdentifierForPbxproj , setBundleIdentifierForPbxproj , resetAllPlistBundleIdentifiers , resetPlistBundleIdentifier , } ;