diff --git a/CHANGELOG.md b/CHANGELOG.md index af680d3ea5..ecfb652b71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This is the log of notable changes to EAS CLI and related packages. ### ๐Ÿ› Bug fixes +- [build-tools][eas-cli] Detect iOS Development provisioning profiles and set correct code signing identity instead of treating them as Ad Hoc. ([#3496](https://github.com/expo/eas-cli/pull/3496) by [@qwertey6](https://github.com/qwertey6)) - [build-tools] Prevent detecting Yarn Modern as Classic based on lockfile ([#3572](https://github.com/expo/eas-cli/pull/3572) by [@kitten](https://github.com/kitten)) ### ๐Ÿงน Chores diff --git a/packages/build-tools/src/ios/__tests__/configure.test.ts b/packages/build-tools/src/ios/__tests__/configure.test.ts index b5ddd0a134..175647c920 100644 --- a/packages/build-tools/src/ios/__tests__/configure.test.ts +++ b/packages/build-tools/src/ios/__tests__/configure.test.ts @@ -165,6 +165,52 @@ describe(configureXcodeProject, () => { 'Info.plist application target' ); }); + it('configures credentials with Apple Development identity for development profile', async () => { + vol.fromJSON( + { + 'ios/testapp.xcodeproj/project.pbxproj': originalFs.readFileSync( + path.join(__dirname, 'fixtures/simple-project.pbxproj'), + 'utf-8' + ), + 'ios/testapp/AppDelegate.m': 'placeholder', + }, + '/app' + ); + const options = { + credentials: { + keychainPath: 'fake/path', + targetProvisioningProfiles: { + testapp: { + path: 'fake/path.mobileprovision', + target: 'testapp', + bundleIdentifier: 'abc', + teamId: 'ABCDEFGH', + uuid: 'abc', + name: 'profile name', + developerCertificate: Buffer.from('test'), + certificateCommonName: 'Apple Development: Test User', + distributionType: DistributionType.DEVELOPMENT, + }, + }, + distributionType: DistributionType.DEVELOPMENT, + teamId: 'ABCDEFGH', + applicationTargetProvisioningProfile: {} as ProvisioningProfile, + }, + buildConfiguration: 'Release', + }; + const ctx = { + getReactNativeProjectDirectory: () => '/app', + logger: { info: jest.fn() }, + job: {}, + }; + await configureXcodeProject(ctx as any, options); + const pbxproj = vol.readFileSync( + '/app/ios/testapp.xcodeproj/project.pbxproj', + 'utf-8' + ) as string; + expect(pbxproj).toContain('CODE_SIGN_IDENTITY = "Apple Development: Test User"'); + expect(pbxproj).not.toContain('"iPhone Distribution"'); + }); it('configures credentials and versions for multi target project', async () => { vol.fromJSON( { diff --git a/packages/build-tools/src/ios/configure.ts b/packages/build-tools/src/ios/configure.ts index 15f36ef943..827034362a 100644 --- a/packages/build-tools/src/ios/configure.ts +++ b/packages/build-tools/src/ios/configure.ts @@ -6,6 +6,7 @@ import uniq from 'lodash/uniq'; import path from 'path'; import { Credentials } from './credentials/manager'; +import { DistributionType } from './credentials/provisioningProfile'; import { BuildContext } from '../context'; async function configureXcodeProject( @@ -56,6 +57,9 @@ async function configureCredentialsAsync( profileName: profile.name, appleTeamId: profile.teamId, buildConfiguration, + ...(credentials.distributionType === DistributionType.DEVELOPMENT && { + codeSignIdentity: profile.certificateCommonName, + }), } ); } diff --git a/packages/build-tools/src/ios/credentials/provisioningProfile.ts b/packages/build-tools/src/ios/credentials/provisioningProfile.ts index 4926e1d783..222c8948d2 100644 --- a/packages/build-tools/src/ios/credentials/provisioningProfile.ts +++ b/packages/build-tools/src/ios/credentials/provisioningProfile.ts @@ -24,6 +24,7 @@ export interface ProvisioningProfileData { export enum DistributionType { AD_HOC = 'ad-hoc', APP_STORE = 'app-store', + DEVELOPMENT = 'development', ENTERPRISE = 'enterprise', } @@ -137,6 +138,10 @@ export default class ProvisioningProfile { if (plistData.ProvisionsAllDevices) { return DistributionType.ENTERPRISE; } else if (plistData.ProvisionedDevices) { + const entitlements = plistData.Entitlements as plist.PlistObject | undefined; + if (entitlements?.['get-task-allow']) { + return DistributionType.DEVELOPMENT; + } return DistributionType.AD_HOC; } else { return DistributionType.APP_STORE; diff --git a/packages/build-tools/src/steps/utils/ios/__tests__/configure.test.ts b/packages/build-tools/src/steps/utils/ios/__tests__/configure.test.ts index 6cc2e47c68..72c9876e53 100644 --- a/packages/build-tools/src/steps/utils/ios/__tests__/configure.test.ts +++ b/packages/build-tools/src/steps/utils/ios/__tests__/configure.test.ts @@ -50,6 +50,47 @@ describe(configureCredentialsAsync, () => { vol.readFileSync('/app/ios/testapp.xcodeproj/project.pbxproj', 'utf-8') ).toMatchSnapshot(); }); + it('configures credentials with Apple Development identity for development profile', async () => { + vol.fromJSON( + { + 'ios/testapp.xcodeproj/project.pbxproj': originalFs.readFileSync( + path.join(__dirname, 'fixtures/simple-project.pbxproj'), + 'utf-8' + ), + 'ios/testapp/AppDelegate.m': 'placeholder', + }, + '/app' + ); + const options = { + credentials: { + keychainPath: 'fake/path', + targetProvisioningProfiles: { + testapp: { + path: 'fake/path.mobileprovision', + target: 'testapp', + bundleIdentifier: 'abc', + teamId: 'ABCDEFGH', + uuid: 'abc', + name: 'profile name', + developerCertificate: Buffer.from('test'), + certificateCommonName: 'Apple Development: Test User', + distributionType: DistributionType.DEVELOPMENT, + }, + }, + distributionType: DistributionType.DEVELOPMENT, + teamId: 'ABCDEFGH', + applicationTargetProvisioningProfile: {} as ProvisioningProfile, + }, + buildConfiguration: 'Release', + }; + await configureCredentialsAsync({ info: jest.fn() } as any, '/app', options); + const pbxproj = vol.readFileSync( + '/app/ios/testapp.xcodeproj/project.pbxproj', + 'utf-8' + ) as string; + expect(pbxproj).toContain('CODE_SIGN_IDENTITY = "Apple Development: Test User"'); + expect(pbxproj).not.toContain('"iPhone Distribution"'); + }); it('configures credentials for multi target project', async () => { vol.fromJSON( { diff --git a/packages/build-tools/src/steps/utils/ios/configure.ts b/packages/build-tools/src/steps/utils/ios/configure.ts index 1867e76a38..e37e66170a 100644 --- a/packages/build-tools/src/steps/utils/ios/configure.ts +++ b/packages/build-tools/src/steps/utils/ios/configure.ts @@ -6,6 +6,7 @@ import uniq from 'lodash/uniq'; import path from 'path'; import { Credentials } from './credentials/manager'; +import { DistributionType } from './credentials/provisioningProfile'; export async function configureCredentialsAsync( logger: bunyan, @@ -29,6 +30,9 @@ export async function configureCredentialsAsync( profileName: profile.name, appleTeamId: profile.teamId, buildConfiguration, + ...(credentials.distributionType === DistributionType.DEVELOPMENT && { + codeSignIdentity: profile.certificateCommonName, + }), }); } } diff --git a/packages/build-tools/src/steps/utils/ios/credentials/provisioningProfile.ts b/packages/build-tools/src/steps/utils/ios/credentials/provisioningProfile.ts index 6e07f02d77..42b31a2677 100644 --- a/packages/build-tools/src/steps/utils/ios/credentials/provisioningProfile.ts +++ b/packages/build-tools/src/steps/utils/ios/credentials/provisioningProfile.ts @@ -23,6 +23,7 @@ export interface ProvisioningProfileData { export enum DistributionType { AD_HOC = 'ad-hoc', APP_STORE = 'app-store', + DEVELOPMENT = 'development', ENTERPRISE = 'enterprise', } @@ -133,6 +134,10 @@ export default class ProvisioningProfile { if (plistData.ProvisionsAllDevices) { return DistributionType.ENTERPRISE; } else if (plistData.ProvisionedDevices) { + const entitlements = plistData.Entitlements as plist.PlistObject | undefined; + if (entitlements?.['get-task-allow']) { + return DistributionType.DEVELOPMENT; + } return DistributionType.AD_HOC; } else { return DistributionType.APP_STORE; diff --git a/packages/eas-cli/src/credentials/ios/IosCredentialsProvider.ts b/packages/eas-cli/src/credentials/ios/IosCredentialsProvider.ts index a46b3bf738..43bd7cc8d8 100644 --- a/packages/eas-cli/src/credentials/ios/IosCredentialsProvider.ts +++ b/packages/eas-cli/src/credentials/ios/IosCredentialsProvider.ts @@ -10,7 +10,11 @@ import { getAppFromContextAsync } from './actions/BuildCredentialsUtils'; import { SetUpBuildCredentials } from './actions/SetUpBuildCredentials'; import { SetUpPushKey } from './actions/SetUpPushKey'; import { App, IosCredentials, Target } from './types'; -import { isAdHocProfile, isEnterpriseUniversalProfile } from './utils/provisioningProfile'; +import { + isAdHocProfile, + isDevelopmentProfile, + isEnterpriseUniversalProfile, +} from './utils/provisioningProfile'; import { CommonIosAppCredentialsFragment } from '../../graphql/generated'; import Log from '../../log'; import { findApplicationTarget } from '../../project/ios/target'; @@ -150,6 +154,7 @@ export default class IosCredentialsProvider { private assertProvisioningProfileType(provisioningProfile: string, targetName?: string): void { const isAdHoc = isAdHocProfile(provisioningProfile); + const isDevelopment = isDevelopmentProfile(provisioningProfile); const isEnterprise = isEnterpriseUniversalProfile(provisioningProfile); if (this.options.distribution === 'internal') { if (this.options.enterpriseProvisioning === 'universal' && !isEnterprise) { @@ -164,16 +169,21 @@ export default class IosCredentialsProvider { targetName ? ` (target '${targetName})'` : '' } for internal distribution if you specified "enterpriseProvisioning": "adhoc" in eas.json` ); - } else if (!this.options.enterpriseProvisioning && !isEnterprise && !isAdHoc) { + } else if ( + !this.options.enterpriseProvisioning && + !isEnterprise && + !isAdHoc && + !isDevelopment + ) { throw new Error( - `You must use an adhoc provisioning profile${ + `You must use an adhoc or development provisioning profile${ targetName ? ` (target '${targetName})'` : '' } for internal distribution.` ); } - } else if (isAdHoc) { + } else if (isAdHoc || isDevelopment) { throw new Error( - `You can't use an adhoc provisioning profile${ + `You can't use ${isAdHoc ? 'an adhoc' : 'a development'} provisioning profile${ targetName ? ` (target '${targetName}')` : '' } for app store distribution.` ); diff --git a/packages/eas-cli/src/credentials/ios/utils/provisioningProfile.ts b/packages/eas-cli/src/credentials/ios/utils/provisioningProfile.ts index 74507389f4..315c8dcf45 100644 --- a/packages/eas-cli/src/credentials/ios/utils/provisioningProfile.ts +++ b/packages/eas-cli/src/credentials/ios/utils/provisioningProfile.ts @@ -23,7 +23,21 @@ export function readProfileName(dataBase64: string): string { export function isAdHocProfile(dataBase64: string): boolean { const profilePlist = parse(dataBase64); const provisionedDevices = profilePlist['ProvisionedDevices'] as string[] | undefined; - return Array.isArray(provisionedDevices); + if (!Array.isArray(provisionedDevices)) { + return false; + } + const entitlements = profilePlist['Entitlements'] as PlistObject | undefined; + return !entitlements?.['get-task-allow']; +} + +export function isDevelopmentProfile(dataBase64: string): boolean { + const profilePlist = parse(dataBase64); + const provisionedDevices = profilePlist['ProvisionedDevices'] as string[] | undefined; + if (!Array.isArray(provisionedDevices)) { + return false; + } + const entitlements = profilePlist['Entitlements'] as PlistObject | undefined; + return !!entitlements?.['get-task-allow']; } export function isEnterpriseUniversalProfile(dataBase64: string): boolean {