mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b943a351c |
No files matched your search
@@ -14,6 +14,7 @@ import {
|
||||
} from '../../__tests__/fixtures-ios';
|
||||
import IosCredentialsProvider from '../IosCredentialsProvider';
|
||||
import { getAppLookupParamsFromContextAsync } from '../actions/BuildCredentialsUtils';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('../validators/validateProvisioningProfile', () => ({
|
||||
@@ -76,7 +77,7 @@ describe(IosCredentialsProvider, () => {
|
||||
distribution: 'store',
|
||||
});
|
||||
await expect(provider.getCredentialsAsync(CredentialsSource.REMOTE)).rejects.toThrowError(
|
||||
/Credentials are not set up/
|
||||
MissingCredentialsNonInteractiveError
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export class CreateProvisioningProfile {
|
||||
) {}
|
||||
|
||||
async runAsync(ctx: CredentialsContext): Promise<AppleProvisioningProfileMutationResult> {
|
||||
// TODO: Remove this
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'Creating Provisioning Profiles is only supported in interactive mode.'
|
||||
|
||||
@@ -2,11 +2,13 @@ import assert from 'assert';
|
||||
import chalk from 'chalk';
|
||||
import dateformat from 'dateformat';
|
||||
|
||||
import { formatAppleTeam } from './AppleTeamFormatting';
|
||||
import { AccountFragment, AppleDistributionCertificateFragment } from '../../../graphql/generated';
|
||||
import Log, { learnMore } from '../../../log';
|
||||
import { promptAsync } from '../../../prompts';
|
||||
import { fromNow } from '../../../utils/date';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
|
||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||
import {
|
||||
@@ -17,7 +19,6 @@ import { filterRevokedDistributionCertsFromEasServers } from '../appstore/Creden
|
||||
import { AppleTooManyCertsError } from '../appstore/distributionCertificate';
|
||||
import { distributionCertificateSchema } from '../credentials';
|
||||
import { validateDistributionCertificateAsync } from '../validators/validateDistributionCertificate';
|
||||
import { formatAppleTeam } from './AppleTeamFormatting';
|
||||
|
||||
export function formatDistributionCertificate(
|
||||
distributionCertificate: AppleDistributionCertificateFragment,
|
||||
@@ -212,7 +213,7 @@ async function generateDistributionCertificateAsync(
|
||||
Log.warn(APPLE_DIST_CERTS_TOO_MANY_GENERATED_ERROR);
|
||||
|
||||
if (ctx.nonInteractive) {
|
||||
throw new Error(
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
"Start the CLI without the '--non-interactive' flag to revoke existing certificates."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,14 +48,14 @@ export class SetUpDistributionCertificate {
|
||||
}
|
||||
|
||||
private async runNonInteractiveAsync(
|
||||
_ctx: CredentialsContext,
|
||||
ctx: CredentialsContext,
|
||||
currentCertificate: AppleDistributionCertificateFragment | null
|
||||
): Promise<AppleDistributionCertificateFragment> {
|
||||
// TODO: implement validation
|
||||
Log.addNewLineIfNone();
|
||||
Log.warn('Distribution Certificate is not validated for non-interactive builds.');
|
||||
if (!currentCertificate) {
|
||||
throw new MissingCredentialsNonInteractiveError();
|
||||
return await this.createNewDistCertAsync(ctx);
|
||||
}
|
||||
return currentCertificate;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import {
|
||||
AppleDistributionCertificateFragment,
|
||||
AppleProvisioningProfileFragment,
|
||||
IosAppBuildCredentialsFragment,
|
||||
IosDistributionType,
|
||||
} from '../../../graphql/generated';
|
||||
import { getApplePlatformFromTarget } from '../../../project/ios/target';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../errors';
|
||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
|
||||
import { Target } from '../types';
|
||||
import { validateProvisioningProfileAsync } from '../validators/validateProvisioningProfile';
|
||||
import {
|
||||
assignBuildCredentialsAsync,
|
||||
getBuildCredentialsAsync,
|
||||
@@ -23,6 +9,20 @@ import { ConfigureProvisioningProfile } from './ConfigureProvisioningProfile';
|
||||
import { CreateProvisioningProfile } from './CreateProvisioningProfile';
|
||||
import { formatProvisioningProfileFromApple } from './ProvisioningProfileUtils';
|
||||
import { SetUpDistributionCertificate } from './SetUpDistributionCertificate';
|
||||
import {
|
||||
AppleDistributionCertificateFragment,
|
||||
AppleProvisioningProfileFragment,
|
||||
AppleTeamFragment,
|
||||
IosAppBuildCredentialsFragment,
|
||||
IosDistributionType,
|
||||
} from '../../../graphql/generated';
|
||||
import { getApplePlatformFromTarget } from '../../../project/ios/target';
|
||||
import { confirmAsync } from '../../../prompts';
|
||||
import { CredentialsContext } from '../../context';
|
||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
|
||||
import { Target } from '../types';
|
||||
import { validateProvisioningProfileAsync } from '../validators/validateProvisioningProfile';
|
||||
|
||||
/**
|
||||
* Sets up either APP_STORE or ENTERPRISE provisioning profiles
|
||||
@@ -47,6 +47,7 @@ export class SetUpProvisioningProfile {
|
||||
const buildCredentials = await this.createAndAssignProfileAsync(ctx, distCert);
|
||||
// delete 'currentProfile' since its no longer valid
|
||||
await ctx.ios.deleteProvisioningProfilesAsync(ctx.graphqlClient, [currentProfile.id]);
|
||||
await ctx.appStore.revokeProvisioningProfileAsync(this.app.bundleIdentifier, currentProfile.id);
|
||||
return buildCredentials;
|
||||
}
|
||||
|
||||
@@ -59,12 +60,17 @@ export class SetUpProvisioningProfile {
|
||||
this.target,
|
||||
distCert
|
||||
).runAsync(ctx);
|
||||
const appleTeam: AppleTeamFragment = nullthrows(
|
||||
distCert.appleTeam ?? provisioningProfile.appleTeam ?? null
|
||||
);
|
||||
|
||||
return await assignBuildCredentialsAsync(
|
||||
ctx,
|
||||
this.app,
|
||||
this.distributionType,
|
||||
distCert,
|
||||
provisioningProfile
|
||||
provisioningProfile,
|
||||
appleTeam ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,11 +108,6 @@ export class SetUpProvisioningProfile {
|
||||
if (areBuildCredentialsSetup) {
|
||||
return nullthrows(await getBuildCredentialsAsync(ctx, this.app, this.distributionType));
|
||||
}
|
||||
if (ctx.nonInteractive) {
|
||||
throw new MissingCredentialsNonInteractiveError(
|
||||
'Provisioning profile is not configured correctly. Run this command again in interactive mode.'
|
||||
);
|
||||
}
|
||||
|
||||
const currentProfile = await getProvisioningProfileAsync(ctx, this.app, this.distributionType);
|
||||
if (!currentProfile) {
|
||||
@@ -114,7 +115,8 @@ export class SetUpProvisioningProfile {
|
||||
}
|
||||
|
||||
// See if the profile we have exists on the Apple Servers
|
||||
const applePlatform = await getApplePlatformFromTarget(this.target);
|
||||
const applePlatform = getApplePlatformFromTarget(this.target);
|
||||
// TODO: Could it ask for login in non-interactive mode?
|
||||
const existingProfiles = await ctx.appStore.listProvisioningProfilesAsync(
|
||||
this.app.bundleIdentifier,
|
||||
applePlatform
|
||||
@@ -127,6 +129,11 @@ export class SetUpProvisioningProfile {
|
||||
return await this.assignNewAndDeleteOldProfileAsync(ctx, distCert, currentProfile);
|
||||
}
|
||||
|
||||
// TODO: Should we instead reuse original profile?
|
||||
if (ctx.nonInteractive) {
|
||||
return await this.assignNewAndDeleteOldProfileAsync(ctx, distCert, currentProfile);
|
||||
}
|
||||
|
||||
const confirm = await confirmAsync({
|
||||
message: `${formatProvisioningProfileFromApple(
|
||||
currentProfileFromServer
|
||||
|
||||
+175
-10
@@ -12,14 +12,24 @@ import {
|
||||
testAppleAppIdentifierFragment,
|
||||
testCommonIosAppCredentialsFragment,
|
||||
testIosAppBuildCredentialsFragment,
|
||||
testProvisioningProfileFragment,
|
||||
testTarget,
|
||||
testTargets,
|
||||
} from '../../../__tests__/fixtures-ios';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
// import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
import { validateProvisioningProfileAsync } from '../../validators/validateProvisioningProfile';
|
||||
import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils';
|
||||
import * as BuildCredentialsUtils from '../BuildCredentialsUtils';
|
||||
import {
|
||||
getAppLookupParamsFromContextAsync,
|
||||
// getProvisioningProfileAsync,
|
||||
} from '../BuildCredentialsUtils';
|
||||
import { SetUpProvisioningProfile } from '../SetUpProvisioningProfile';
|
||||
import { MissingCredentialsNonInteractiveError } from '../../../errors';
|
||||
|
||||
const getProvisioningProfileAsync = jest.spyOn(
|
||||
BuildCredentialsUtils,
|
||||
'getProvisioningProfileAsync'
|
||||
);
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mocked(confirmAsync).mockImplementation(async () => true);
|
||||
jest.mock('../SetUpDistributionCertificate');
|
||||
@@ -181,10 +191,172 @@ describe('SetUpProvisioningProfile', () => {
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('errors in Non Interactive Mode', async () => {
|
||||
it('repairs existing Provisioning Profile with bad build credentials in Non Interactive Mode', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
listProvisioningProfilesAsync: jest.fn(() => [
|
||||
{
|
||||
provisioningProfileId: nullthrows(
|
||||
testCommonIosAppCredentialsFragment.iosAppBuildCredentialsList[0].provisioningProfile
|
||||
).developerPortalIdentifier,
|
||||
},
|
||||
]),
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
createOrUpdateIosAppBuildCredentialsAsync: jest.fn(
|
||||
() => testIosAppBuildCredentialsFragment
|
||||
),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
1
|
||||
);
|
||||
// expect provisioning profile to be deleted from expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('sets up a new Provisioning Profile with bad build credentials in Non Interactive Mode', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
listProvisioningProfilesAsync: jest.fn(() => []),
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
1
|
||||
);
|
||||
// expect provisioning profile to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(1);
|
||||
});
|
||||
it('skips setting up a Provisioning Profile with prior build credentials configured properly in Non Interactive Mode', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => true);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
//listProvisioningProfilesAsync: jest.fn(() => []),
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
getIosAppCredentialsWithBuildCredentialsAsync: jest.fn(
|
||||
() => testCommonIosAppCredentialsFragment
|
||||
),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
0
|
||||
);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it('sets up a Provisioning Profile with no prior build credentials configured in Non Interactive Mode', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => testAuthCtx),
|
||||
authCtx: testAuthCtx,
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
findApplicationTarget(testTargets)
|
||||
);
|
||||
const setupProvisioningProfileAction = new SetUpProvisioningProfile(
|
||||
appLookupParams,
|
||||
testTarget,
|
||||
IosDistributionType.AppStore
|
||||
);
|
||||
await setupProvisioningProfileAction.runAsync(ctx);
|
||||
|
||||
// expect build credentials to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
1
|
||||
);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
it.only('errors in Non Interactive Mode if not authenticated with App Store', async () => {
|
||||
jest.mocked(validateProvisioningProfileAsync).mockImplementation(async () => false);
|
||||
jest
|
||||
.mocked(getProvisioningProfileAsync)
|
||||
.mockImplementation(async () => testProvisioningProfileFragment);
|
||||
|
||||
const ctx = createCtxMock({
|
||||
nonInteractive: true,
|
||||
appStore: {
|
||||
...getAppstoreMock(),
|
||||
ensureAuthenticatedAsync: jest.fn(() => null),
|
||||
authCtx: null,
|
||||
},
|
||||
ios: {
|
||||
...getNewIosApiMock(),
|
||||
createOrGetExistingAppleAppIdentifierAsync: jest.fn(() => testAppleAppIdentifierFragment),
|
||||
},
|
||||
});
|
||||
const appLookupParams = await getAppLookupParamsFromContextAsync(
|
||||
ctx,
|
||||
@@ -198,12 +370,5 @@ describe('SetUpProvisioningProfile', () => {
|
||||
await expect(setupProvisioningProfileAction.runAsync(ctx)).rejects.toThrowError(
|
||||
MissingCredentialsNonInteractiveError
|
||||
);
|
||||
|
||||
// expect build credentials not to be created or updated on expo servers
|
||||
expect(jest.mocked(ctx.ios.createOrUpdateIosAppBuildCredentialsAsync).mock.calls.length).toBe(
|
||||
0
|
||||
);
|
||||
// expect provisioning profile not to be deleted on expo servers
|
||||
expect(jest.mocked(ctx.ios.deleteProvisioningProfilesAsync).mock.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -160,11 +160,10 @@ export default class AppStoreApi {
|
||||
|
||||
public async revokeProvisioningProfileAsync(
|
||||
bundleIdentifier: string,
|
||||
applePlatform: ApplePlatform,
|
||||
profileClass?: ProfileClass
|
||||
profileId: string
|
||||
): Promise<void> {
|
||||
const ctx = await this.ensureAuthenticatedAsync();
|
||||
return await revokeProvisioningProfileAsync(ctx, bundleIdentifier, applePlatform, profileClass);
|
||||
return await revokeProvisioningProfileAsync(ctx, bundleIdentifier, profileId);
|
||||
}
|
||||
|
||||
public async createOrReuseAdhocProvisioningProfileAsync(
|
||||
|
||||
@@ -243,20 +243,18 @@ export async function createProvisioningProfileAsync(
|
||||
export async function revokeProvisioningProfileAsync(
|
||||
authCtx: AuthCtx,
|
||||
bundleIdentifier: string,
|
||||
applePlatform: ApplePlatform,
|
||||
profileClass: ProfileClass = ProfileClass.General
|
||||
profileId: string
|
||||
): Promise<void> {
|
||||
const spinner = ora(`Revoking Apple provisioning profile`).start();
|
||||
try {
|
||||
const context = getRequestContext(authCtx);
|
||||
|
||||
const profiles = await getProfilesForBundleIdAsync(context, bundleIdentifier);
|
||||
const profileType = resolveProfileType(applePlatform, profileClass, authCtx.team.inHouse);
|
||||
await Promise.all(
|
||||
profiles
|
||||
.filter(profile => profile.attributes.profileType === profileType)
|
||||
.map(profile => Profile.deleteAsync(context, { id: profile.id }))
|
||||
);
|
||||
const profile = profiles.find(profile => profile.id === profileId);
|
||||
// TODO: See if we can just deleteAsync
|
||||
if (profile) {
|
||||
await Profile.deleteAsync(context, { id: profile.id });
|
||||
}
|
||||
spinner.succeed('Revoked Apple provisioning profile');
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to revoke Apple provisioning profile');
|
||||
|
||||
Reference in new issue
Block a user