Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions packages/build-tools/src/ios/__tests__/configure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Ios.Job>,
},
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(
{
Expand Down
4 changes: 4 additions & 0 deletions packages/build-tools/src/ios/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -56,6 +57,9 @@ async function configureCredentialsAsync(
profileName: profile.name,
appleTeamId: profile.teamId,
buildConfiguration,
...(credentials.distributionType === DistributionType.DEVELOPMENT && {
codeSignIdentity: profile.certificateCommonName,
}),
}
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface ProvisioningProfileData {
export enum DistributionType {
AD_HOC = 'ad-hoc',
APP_STORE = 'app-store',
DEVELOPMENT = 'development',
ENTERPRISE = 'enterprise',
}

Expand Down Expand Up @@ -137,6 +138,10 @@ export default class ProvisioningProfile<TJob extends Ios.Job> {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand Down
4 changes: 4 additions & 0 deletions packages/build-tools/src/steps/utils/ios/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -29,6 +30,9 @@ export async function configureCredentialsAsync(
profileName: profile.name,
appleTeamId: profile.teamId,
buildConfiguration,
...(credentials.distributionType === DistributionType.DEVELOPMENT && {
codeSignIdentity: profile.certificateCommonName,
}),
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ProvisioningProfileData {
export enum DistributionType {
AD_HOC = 'ad-hoc',
APP_STORE = 'app-store',
DEVELOPMENT = 'development',
ENTERPRISE = 'enterprise',
}

Expand Down Expand Up @@ -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;
Expand Down
20 changes: 15 additions & 5 deletions packages/eas-cli/src/credentials/ios/IosCredentialsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand All @@ -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.`
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading