1import { IOSConfig, XcodeProject } from '@expo/config-plugins'; 2import fs from 'fs'; 3 4export type CodeSigningInfo = Record< 5 string, 6 { 7 developmentTeams: string[]; 8 provisioningProfiles: string[]; 9 } 10>; 11 12/** Find the development team and provisioning profile that's currently in use by the Xcode project. */ 13export function getCodeSigningInfoForPbxproj(projectRoot: string): CodeSigningInfo { 14 const project = IOSConfig.XcodeUtils.getPbxproj(projectRoot); 15 const targets = IOSConfig.Target.findSignableTargets(project); 16 17 const signingInfo: CodeSigningInfo = {}; 18 for (const [nativeTargetId, nativeTarget] of targets) { 19 const developmentTeams: string[] = []; 20 const provisioningProfiles: string[] = []; 21 22 IOSConfig.XcodeUtils.getBuildConfigurationsForListId( 23 project, 24 nativeTarget.buildConfigurationList 25 ) 26 .filter( 27 ([, item]: IOSConfig.XcodeUtils.ConfigurationSectionEntry) => 28 item.buildSettings.PRODUCT_NAME 29 ) 30 .forEach(([, item]: IOSConfig.XcodeUtils.ConfigurationSectionEntry) => { 31 const { DEVELOPMENT_TEAM, PROVISIONING_PROFILE } = item.buildSettings; 32 if ( 33 typeof DEVELOPMENT_TEAM === 'string' && 34 // If the user selects "Team: none" in Xcode, it'll be an empty string. 35 !!DEVELOPMENT_TEAM && 36 // xcode package sometimes reads an empty string as a quoted empty string. 37 DEVELOPMENT_TEAM !== '""' 38 ) { 39 developmentTeams.push(DEVELOPMENT_TEAM); 40 } 41 if (typeof PROVISIONING_PROFILE === 'string' && !!PROVISIONING_PROFILE) { 42 provisioningProfiles.push(PROVISIONING_PROFILE); 43 } 44 }); 45 signingInfo[nativeTargetId] = { 46 developmentTeams, 47 provisioningProfiles, 48 }; 49 } 50 51 return signingInfo; 52} 53 54/** 55 * Set the development team and configure the Xcode project for automatic code signing, 56 * this helps us resolve the code signing on subsequent runs and emulates Xcode behavior. 57 * 58 * @param props.project xcode project object from `xcode` package. 59 * @param props.appleTeamId Apple Team ID to use for code signing. 60 */ 61export function mutateXcodeProjectWithAutoCodeSigningInfo({ 62 project, 63 appleTeamId, 64}: { 65 project: XcodeProject; 66 appleTeamId: string; 67}): XcodeProject { 68 const targets = IOSConfig.Target.findSignableTargets(project); 69 70 const quotedAppleTeamId = ensureQuotes(appleTeamId); 71 72 for (const [nativeTargetId, nativeTarget] of targets) { 73 IOSConfig.XcodeUtils.getBuildConfigurationsForListId( 74 project, 75 nativeTarget.buildConfigurationList 76 ) 77 .filter( 78 ([, item]: IOSConfig.XcodeUtils.ConfigurationSectionEntry) => 79 item.buildSettings.PRODUCT_NAME 80 ) 81 .forEach(([, item]: IOSConfig.XcodeUtils.ConfigurationSectionEntry) => { 82 item.buildSettings.DEVELOPMENT_TEAM = quotedAppleTeamId; 83 item.buildSettings.CODE_SIGN_IDENTITY = '"Apple Development"'; 84 item.buildSettings.CODE_SIGN_STYLE = 'Automatic'; 85 }); 86 87 Object.entries(IOSConfig.XcodeUtils.getProjectSection(project)) 88 .filter(IOSConfig.XcodeUtils.isNotComment) 89 .forEach(([, item]: IOSConfig.XcodeUtils.ProjectSectionEntry) => { 90 if (!item.attributes.TargetAttributes[nativeTargetId]) { 91 item.attributes.TargetAttributes[nativeTargetId] = {}; 92 } 93 94 item.attributes.TargetAttributes[nativeTargetId].DevelopmentTeam = quotedAppleTeamId; 95 item.attributes.TargetAttributes[nativeTargetId].ProvisioningStyle = 'Automatic'; 96 }); 97 } 98 99 return project; 100} 101 102/** 103 * Configures the Xcode project for automatic code signing and persists the results. 104 */ 105export function setAutoCodeSigningInfoForPbxproj( 106 projectRoot: string, 107 { appleTeamId }: { appleTeamId: string } 108): void { 109 const project = IOSConfig.XcodeUtils.getPbxproj(projectRoot); 110 mutateXcodeProjectWithAutoCodeSigningInfo({ project, appleTeamId }); 111 112 fs.writeFileSync(project.filepath, project.writeSync()); 113} 114 115const ensureQuotes = (value: string) => { 116 if (!value.match(/^['"]/)) { 117 return `"${value}"`; 118 } 119 return value; 120}; 121