1import chalk from 'chalk'; 2 3import * as Log from '../../../log'; 4import { env } from '../../../utils/env'; 5import { CommandError } from '../../../utils/errors'; 6import { learnMore } from '../../../utils/link'; 7import { selectAsync } from '../../../utils/prompts'; 8import * as Security from './Security'; 9import { getLastDeveloperCodeSigningIdAsync, setLastDeveloperCodeSigningIdAsync } from './settings'; 10 11/** 12 * Sort the code signing items so the last selected item (user's default) is the first suggested. 13 */ 14export async function sortDefaultIdToBeginningAsync( 15 identities: Security.CertificateSigningInfo[] 16): Promise<[Security.CertificateSigningInfo[], string | null]> { 17 const lastSelected = await getLastDeveloperCodeSigningIdAsync(); 18 19 if (lastSelected) { 20 let iterations = 0; 21 while (identities[0].signingCertificateId !== lastSelected && iterations < identities.length) { 22 identities.push(identities.shift()!); 23 iterations++; 24 } 25 } 26 return [identities, lastSelected]; 27} 28 29/** 30 * Assert that the computer needs code signing setup. 31 * This links to an FYI page that was user tested internally. 32 */ 33function assertCodeSigningSetup(): never { 34 // TODO: We can probably do this too automatically. 35 Log.log( 36 `\u203A Your computer requires some additional setup before you can build onto physical iOS devices.\n ${chalk.bold( 37 learnMore('https://expo.fyi/setup-xcode-signing') 38 )}` 39 ); 40 41 throw new CommandError('No code signing certificates are available to use.'); 42} 43 44/** 45 * Resolve the best certificate signing identity from a given list of IDs. 46 * - If no IDs: Assert that the user has to setup code signing. 47 * - If one ID: Return the first ID. 48 * - If multiple IDs: Ask the user to select one, then store the value to be suggested first next time (since users generally use the same ID). 49 */ 50export async function resolveCertificateSigningIdentityAsync( 51 ids: string[] 52): Promise<Security.CertificateSigningInfo> { 53 // The user has no valid code signing identities. 54 if (!ids.length) { 55 assertCodeSigningSetup(); 56 } 57 58 // One ID available Program is not interactive 59 // 60 // using the the first available option 61 if (ids.length === 1 || env.CI) { 62 // This method is cheaper than `resolveIdentitiesAsync` and checking the 63 // cached user preference so we should use this as early as possible. 64 return Security.resolveCertificateSigningInfoAsync(ids[0]); 65 } 66 67 // Get identities and sort by the one that the user is most likely to choose. 68 const [identities, preferred] = await sortDefaultIdToBeginningAsync( 69 await Security.resolveIdentitiesAsync(ids) 70 ); 71 72 const selected = await selectDevelopmentTeamAsync(identities, preferred); 73 74 // Store the last used value and suggest it as the first value 75 // next time the user has to select a code signing identity. 76 await setLastDeveloperCodeSigningIdAsync(selected.signingCertificateId); 77 78 return selected; 79} 80 81/** Prompt the user to select a development team, highlighting the preferred value based on the user history. */ 82export async function selectDevelopmentTeamAsync( 83 identities: Security.CertificateSigningInfo[], 84 preferredId: string | null 85): Promise<Security.CertificateSigningInfo> { 86 const index = await selectAsync( 87 'Development team for signing the app', 88 identities.map((value, i) => { 89 const format = 90 value.signingCertificateId === preferredId ? chalk.bold : (message: string) => message; 91 return { 92 // Formatted like: `650 Industries, Inc. (A1BCDEF234) - Apple Development: Evan Bacon (AA00AABB0A)` 93 title: format( 94 [value.appleTeamName, `(${value.appleTeamId}) -`, value.codeSigningInfo].join(' ') 95 ), 96 value: i, 97 }; 98 }) 99 ); 100 101 return identities[index]; 102} 103