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