mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f20d970616 | ||
|
|
f3712d5dfb | ||
|
|
cba211d82c |
No files matched your search
Generated
+10
@@ -22838,6 +22838,16 @@
|
|||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "DevDomainName",
|
||||||
|
"description": "A DevDomainName for a deployed serverless app",
|
||||||
|
"fields": null,
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": null,
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "DeploymentsMutation",
|
"name": "DeploymentsMutation",
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import { Analytics, AnalyticsEventProperties, BuildEvent } from '../analytics/An
|
|||||||
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
|
import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectConfigContextField';
|
||||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
import { CredentialsContext } from '../credentials/context';
|
import { CredentialsContext } from '../credentials/context';
|
||||||
|
import { AppStoreApiKeyPurpose } from '../credentials/ios/actions/AscApiKeyUtils';
|
||||||
|
import { getAppFromContextAsync } from '../credentials/ios/actions/BuildCredentialsUtils';
|
||||||
|
import { GetAscApiKey } from '../credentials/ios/actions/GetAscApiKey';
|
||||||
import { CustomBuildConfigMetadata } from '../project/customBuildConfig';
|
import { CustomBuildConfigMetadata } from '../project/customBuildConfig';
|
||||||
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
|
import { getOwnerAccountForProjectIdAsync } from '../project/projectUtils';
|
||||||
import { resolveWorkflowAsync } from '../project/workflow';
|
import { resolveWorkflowAsync } from '../project/workflow';
|
||||||
@@ -155,10 +158,24 @@ export async function createBuildContextAsync<T extends Platform>({
|
|||||||
} as BuildContext<T>;
|
} as BuildContext<T>;
|
||||||
} else {
|
} else {
|
||||||
const common = commonContext as CommonContext<Platform.IOS>;
|
const common = commonContext as CommonContext<Platform.IOS>;
|
||||||
return {
|
const iosBuildCtx = {
|
||||||
...common,
|
...common,
|
||||||
ios: await createIosContextAsync(common),
|
ios: await createIosContextAsync(common),
|
||||||
} as BuildContext<T>;
|
} as BuildContext<T>;
|
||||||
|
const appLookupParams = {
|
||||||
|
...(await getAppFromContextAsync(credentialsCtx)),
|
||||||
|
bundleIdentifier: (iosBuildCtx as BuildContext<Platform.IOS>).ios.bundleIdentifier,
|
||||||
|
};
|
||||||
|
const getAscApiKeyAction = new GetAscApiKey(
|
||||||
|
appLookupParams,
|
||||||
|
AppStoreApiKeyPurpose.BUILD_SERVICE
|
||||||
|
);
|
||||||
|
const ascApiKeyManagedByExpo = await getAscApiKeyAction.runAsync(credentialsCtx);
|
||||||
|
if (ascApiKeyManagedByExpo) {
|
||||||
|
// TODO: set credentialCtx with the correct options
|
||||||
|
}
|
||||||
|
|
||||||
|
return iosBuildCtx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import * as AndroidGraphqlClient from './android/api/GraphqlClient';
|
|||||||
import * as IosGraphqlClient from './ios/api/GraphqlClient';
|
import * as IosGraphqlClient from './ios/api/GraphqlClient';
|
||||||
import AppStoreApi from './ios/appstore/AppStoreApi';
|
import AppStoreApi from './ios/appstore/AppStoreApi';
|
||||||
import { AuthenticationMode } from './ios/appstore/authenticateTypes';
|
import { AuthenticationMode } from './ios/appstore/authenticateTypes';
|
||||||
|
import { MinimalAscApiKey } from './ios/credentials';
|
||||||
import { Analytics } from '../analytics/AnalyticsManager';
|
import { Analytics } from '../analytics/AnalyticsManager';
|
||||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||||
|
import { AppStoreConnectApiKeyFragment } from '../graphql/generated';
|
||||||
import Log from '../log';
|
import Log from '../log';
|
||||||
import { getPrivateExpoConfig } from '../project/expoConfig';
|
import { getPrivateExpoConfig } from '../project/expoConfig';
|
||||||
import { confirmAsync } from '../prompts';
|
import { confirmAsync } from '../prompts';
|
||||||
@@ -85,6 +87,28 @@ export class CredentialsContext {
|
|||||||
getPrivateExpoConfig(this.options.projectDir);
|
getPrivateExpoConfig(this.options.projectDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public configureAppStoreAuth(ascApiKey: AppStoreConnectApiKeyFragment): void {
|
||||||
|
/* keyP8: string;
|
||||||
|
keyId: string;
|
||||||
|
issuerId: string;
|
||||||
|
teamId?: string;
|
||||||
|
teamName?: string;
|
||||||
|
roles?: UserRole[];
|
||||||
|
name?: string; */
|
||||||
|
this.appStore.authCtx = {
|
||||||
|
ascApiKey: {
|
||||||
|
keyP8: ascApiKey.keyP8,
|
||||||
|
keyId: ascApiKey.keyIdentifier,
|
||||||
|
issuerId: ascApiKey.issuerIdentifier,
|
||||||
|
},
|
||||||
|
appleTeam: {
|
||||||
|
teamIdentifier: ascApiKey.appleTeam?.appleTeamIdentifier ?? '',
|
||||||
|
teamName: ascApiKey.appleTeam?.appleTeamName,
|
||||||
|
teamType: ascApiKey.appleTeam,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async bestEffortAppStoreAuthenticateAsync(): Promise<void> {
|
async bestEffortAppStoreAuthenticateAsync(): Promise<void> {
|
||||||
if (this.appStore.authCtx || !this.shouldAskAuthenticateAppStore) {
|
if (this.appStore.authCtx || !this.shouldAskAuthenticateAppStore) {
|
||||||
// skip prompts if already have apple ctx or already asked about it
|
// skip prompts if already have apple ctx or already asked about it
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { UserRole } from '@expo/apple-utils';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
@@ -24,10 +25,19 @@ import { isAscApiKeyValidAndTrackedAsync } from '../validators/validateAscApiKey
|
|||||||
|
|
||||||
export enum AppStoreApiKeyPurpose {
|
export enum AppStoreApiKeyPurpose {
|
||||||
SUBMISSION_SERVICE = 'EAS Submit',
|
SUBMISSION_SERVICE = 'EAS Submit',
|
||||||
|
BUILD_SERVICE = 'EAS Build',
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function promptForAscApiKeyPathAsync(ctx: CredentialsContext): Promise<AscApiKeyPath> {
|
export const PURPOSE_TO_ROLES: Record<AppStoreApiKeyPurpose, UserRole[]> = {
|
||||||
const { keyId, keyP8Path } = await promptForKeyP8AndIdAsync();
|
[AppStoreApiKeyPurpose.SUBMISSION_SERVICE]: [UserRole.ADMIN],
|
||||||
|
[AppStoreApiKeyPurpose.BUILD_SERVICE]: [UserRole.ADMIN],
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function promptForAscApiKeyPathAsync(
|
||||||
|
ctx: CredentialsContext,
|
||||||
|
roles: UserRole[]
|
||||||
|
): Promise<AscApiKeyPath> {
|
||||||
|
const { keyId, keyP8Path } = await promptForKeyP8AndIdAsync(roles);
|
||||||
|
|
||||||
const bestEffortIssuerId = await getBestEffortIssuerIdAsync(ctx, keyId);
|
const bestEffortIssuerId = await getBestEffortIssuerIdAsync(ctx, keyId);
|
||||||
if (bestEffortIssuerId) {
|
if (bestEffortIssuerId) {
|
||||||
@@ -60,15 +70,15 @@ export async function getMinimalAscApiKeyAsync(ascApiKey: AscApiKey): Promise<Mi
|
|||||||
|
|
||||||
export async function provideOrGenerateAscApiKeyAsync(
|
export async function provideOrGenerateAscApiKeyAsync(
|
||||||
ctx: CredentialsContext,
|
ctx: CredentialsContext,
|
||||||
purpose: AppStoreApiKeyPurpose
|
roles: UserRole[]
|
||||||
): Promise<MinimalAscApiKey> {
|
): Promise<MinimalAscApiKey> {
|
||||||
if (ctx.nonInteractive) {
|
if (ctx.nonInteractive) {
|
||||||
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userProvided = await promptForAscApiKeyAsync(ctx);
|
const userProvided = await promptForAscApiKeyAsync(ctx, roles);
|
||||||
if (!userProvided) {
|
if (!userProvided) {
|
||||||
return await generateAscApiKeyAsync(ctx, purpose);
|
return await generateAscApiKeyAsync(ctx, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx.appStore.authCtx) {
|
if (!ctx.appStore.authCtx) {
|
||||||
@@ -86,39 +96,49 @@ export async function provideOrGenerateAscApiKeyAsync(
|
|||||||
if (useUserProvided) {
|
if (useUserProvided) {
|
||||||
return userProvided;
|
return userProvided;
|
||||||
}
|
}
|
||||||
return await provideOrGenerateAscApiKeyAsync(ctx, purpose);
|
return await provideOrGenerateAscApiKeyAsync(ctx, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateAscApiKeyAsync(
|
async function generateAscApiKeyAsync(
|
||||||
ctx: CredentialsContext,
|
ctx: CredentialsContext,
|
||||||
purpose: AppStoreApiKeyPurpose
|
roles: UserRole[]
|
||||||
): Promise<MinimalAscApiKey> {
|
): Promise<MinimalAscApiKey> {
|
||||||
await ctx.appStore.ensureAuthenticatedAsync();
|
await ctx.appStore.ensureAuthenticatedAsync();
|
||||||
const ascApiKey = await ctx.appStore.createAscApiKeyAsync(ctx.analytics, {
|
const ascApiKey = await ctx.appStore.createAscApiKeyAsync(ctx.analytics, {
|
||||||
nickname: getAscApiKeyName(purpose),
|
nickname: getAscApiKeyName(),
|
||||||
|
roles,
|
||||||
});
|
});
|
||||||
return await getMinimalAscApiKeyAsync(ascApiKey);
|
return await getMinimalAscApiKeyAsync(ascApiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAscApiKeyName(purpose: AppStoreApiKeyPurpose): string {
|
export function getAscApiKeyName(): string {
|
||||||
const nameParts = ['[Expo]', purpose, nanoid(10)];
|
const nameParts = ['[Expo]', nanoid(10)];
|
||||||
return nameParts.join(' ');
|
return nameParts.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promptForAscApiKeyAsync(ctx: CredentialsContext): Promise<MinimalAscApiKey | null> {
|
async function promptForAscApiKeyAsync(
|
||||||
|
ctx: CredentialsContext,
|
||||||
|
roles: UserRole[]
|
||||||
|
): Promise<MinimalAscApiKey | null> {
|
||||||
const shouldAutoGenerateCredentials = await shouldAutoGenerateCredentialsAsync(ascApiKeyIdSchema);
|
const shouldAutoGenerateCredentials = await shouldAutoGenerateCredentialsAsync(ascApiKeyIdSchema);
|
||||||
if (shouldAutoGenerateCredentials) {
|
if (shouldAutoGenerateCredentials) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx);
|
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx, roles);
|
||||||
const { keyP8Path, keyId, issuerId } = ascApiKeyPath;
|
const { keyP8Path, keyId, issuerId } = ascApiKeyPath;
|
||||||
return { keyP8: await fs.readFile(keyP8Path, 'utf-8'), keyId, issuerId };
|
return { keyP8: await fs.readFile(keyP8Path, 'utf-8'), keyId, issuerId };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promptForKeyP8AndIdAsync(): Promise<Pick<AscApiKeyPath, 'keyP8Path' | 'keyId'>> {
|
async function promptForKeyP8AndIdAsync(
|
||||||
|
roles: UserRole[]
|
||||||
|
): Promise<Pick<AscApiKeyPath, 'keyP8Path' | 'keyId'>> {
|
||||||
|
const rolesStringified = roles.map(r => r.toString()).join(', ');
|
||||||
|
const isPlural = roles.length > 1;
|
||||||
Log.log(
|
Log.log(
|
||||||
chalk.bold(
|
chalk.bold(
|
||||||
'An App Store Connect Api key is required to upload your app to the Apple App Store Connect'
|
`An App Store Connect Api key with the ${rolesStringified} role${
|
||||||
|
isPlural ? 's' : ''
|
||||||
|
} is required to upload your app to the Apple App Store Connect`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
Log.log(
|
Log.log(
|
||||||
@@ -241,13 +261,16 @@ export function sortAscApiKeysByUpdatedAtDesc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatAscApiKey(ascApiKey: AppStoreConnectApiKeyFragment): string {
|
export function formatAscApiKey(ascApiKey: AppStoreConnectApiKeyFragment): string {
|
||||||
const { keyIdentifier, appleTeam, name, updatedAt } = ascApiKey;
|
const { keyIdentifier, appleTeam, roles, name, updatedAt } = ascApiKey;
|
||||||
let line: string = '';
|
let line: string = '';
|
||||||
line += `Key ID: ${keyIdentifier}`;
|
line += `Key ID: ${keyIdentifier}`;
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
line += chalk.gray(`\n Name: ${name}`);
|
line += chalk.gray(`\n Name: ${name}`);
|
||||||
}
|
}
|
||||||
|
if (roles && roles.length > 0) {
|
||||||
|
line += chalk.gray(`\n Roles: ${roles.join(', ')}`);
|
||||||
|
}
|
||||||
if (appleTeam) {
|
if (appleTeam) {
|
||||||
line += chalk.gray(`\n ${formatAppleTeam(appleTeam)}`);
|
line += chalk.gray(`\n ${formatAppleTeam(appleTeam)}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { resolveAppleTeamIfAuthenticatedAsync } from './AppleTeamUtils';
|
|
||||||
import { AppStoreApiKeyPurpose } from './AscApiKeyUtils';
|
import { AppStoreApiKeyPurpose } from './AscApiKeyUtils';
|
||||||
|
import { ChooseAssociatedAppleTeamId } from './ChooseAssociatedAppleTeamId';
|
||||||
|
import { UpdateAppleTeamInfo } from './UpdateAppleTeamInfo';
|
||||||
import {
|
import {
|
||||||
AppStoreConnectApiKeyFragment,
|
AppStoreConnectApiKeyFragment,
|
||||||
|
AppleTeamFragment,
|
||||||
CommonIosAppCredentialsFragment,
|
CommonIosAppCredentialsFragment,
|
||||||
} from '../../../graphql/generated';
|
} from '../../../graphql/generated';
|
||||||
import Log from '../../../log';
|
import Log from '../../../log';
|
||||||
@@ -16,12 +18,15 @@ export class AssignAscApiKey {
|
|||||||
ascApiKey: AppStoreConnectApiKeyFragment,
|
ascApiKey: AppStoreConnectApiKeyFragment,
|
||||||
purpose: AppStoreApiKeyPurpose
|
purpose: AppStoreApiKeyPurpose
|
||||||
): Promise<CommonIosAppCredentialsFragment> {
|
): Promise<CommonIosAppCredentialsFragment> {
|
||||||
const appleTeam =
|
const [ascApiKeyWithAppleTeam, appleTeam] = await this.ensureKeyHasAppleTeamInfoAsync(
|
||||||
(await resolveAppleTeamIfAuthenticatedAsync(ctx, this.app)) ?? ascApiKey.appleTeam ?? null;
|
ctx,
|
||||||
|
ascApiKey
|
||||||
|
);
|
||||||
|
|
||||||
const appCredentials = await ctx.ios.createOrGetIosAppCredentialsWithCommonFieldsAsync(
|
const appCredentials = await ctx.ios.createOrGetIosAppCredentialsWithCommonFieldsAsync(
|
||||||
ctx.graphqlClient,
|
ctx.graphqlClient,
|
||||||
this.app,
|
this.app,
|
||||||
{ appleTeam: appleTeam ?? undefined }
|
{ appleTeam }
|
||||||
);
|
);
|
||||||
let updatedAppCredentials;
|
let updatedAppCredentials;
|
||||||
if (purpose === AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
if (purpose === AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
||||||
@@ -29,7 +34,15 @@ export class AssignAscApiKey {
|
|||||||
ctx.graphqlClient,
|
ctx.graphqlClient,
|
||||||
appCredentials,
|
appCredentials,
|
||||||
{
|
{
|
||||||
ascApiKeyIdForSubmissions: ascApiKey.id,
|
ascApiKeyIdForSubmissions: ascApiKeyWithAppleTeam.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (purpose === AppStoreApiKeyPurpose.BUILD_SERVICE) {
|
||||||
|
updatedAppCredentials = await ctx.ios.updateIosAppCredentialsAsync(
|
||||||
|
ctx.graphqlClient,
|
||||||
|
appCredentials,
|
||||||
|
{
|
||||||
|
ascApiKeyIdForBuilds: ascApiKeyWithAppleTeam.id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -40,4 +53,28 @@ export class AssignAscApiKey {
|
|||||||
);
|
);
|
||||||
return updatedAppCredentials;
|
return updatedAppCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ensureKeyHasAppleTeamInfoAsync(
|
||||||
|
ctx: CredentialsContext,
|
||||||
|
ascApiKey: AppStoreConnectApiKeyFragment
|
||||||
|
): Promise<[AppStoreConnectApiKeyFragment, AppleTeamFragment]> {
|
||||||
|
let appleTeamIdentifier = ascApiKey.appleTeam?.appleTeamIdentifier;
|
||||||
|
if (!appleTeamIdentifier) {
|
||||||
|
const chooseAssociatedAppleTeamIdAction = new ChooseAssociatedAppleTeamId(
|
||||||
|
'App Store Connect API Key'
|
||||||
|
);
|
||||||
|
appleTeamIdentifier = await chooseAssociatedAppleTeamIdAction.runAsync(ctx);
|
||||||
|
}
|
||||||
|
const updateAppleTeamAction = new UpdateAppleTeamInfo(this.app.account, appleTeamIdentifier);
|
||||||
|
const appleTeam = await updateAppleTeamAction.runAsync(ctx);
|
||||||
|
if (!ascApiKey.appleTeam) {
|
||||||
|
const updatedAscApiKey = await ctx.ios.updateAscApiKeyAsync(
|
||||||
|
ctx.graphqlClient,
|
||||||
|
ascApiKey,
|
||||||
|
appleTeam
|
||||||
|
);
|
||||||
|
return [updatedAscApiKey, appleTeam];
|
||||||
|
}
|
||||||
|
return [ascApiKey, appleTeam];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Teams } from '@expo/apple-utils';
|
||||||
|
|
||||||
|
import { promptAsync } from '../../../prompts';
|
||||||
|
import { CredentialsContext } from '../../context';
|
||||||
|
|
||||||
|
export class ChooseAssociatedAppleTeamId {
|
||||||
|
constructor(private associatedCredential: string) {}
|
||||||
|
|
||||||
|
public async runAsync(ctx: CredentialsContext): Promise<string> {
|
||||||
|
if (!ctx.appStore.authCtx) {
|
||||||
|
return await this.promptForAppleTeamIdAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
const teams = await Teams.getTeamsAsync();
|
||||||
|
const chosenTeamId = await this.promptForAppleTeamIdFromDeveloperPortalAsync(
|
||||||
|
teams.map(team => ({ prettyName: `${team.name} (${team.type})`, teamId: team.teamId }))
|
||||||
|
);
|
||||||
|
return chosenTeamId ?? (await this.promptForAppleTeamIdAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async promptForAppleTeamIdFromDeveloperPortalAsync(
|
||||||
|
teamChoices: { prettyName: string; teamId: string }[]
|
||||||
|
): Promise<string | null> {
|
||||||
|
if (teamChoices.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { appleTeamId } = await promptAsync({
|
||||||
|
type: 'select',
|
||||||
|
message: `Select the Apple Team associated with your ${this.associatedCredential}:`,
|
||||||
|
name: 'appleTeamId',
|
||||||
|
choices: [
|
||||||
|
...teamChoices.map(({ prettyName, teamId }) => ({ title: prettyName, value: teamId })),
|
||||||
|
{ title: 'None of the above', value: null },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return appleTeamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async promptForAppleTeamIdAsync(): Promise<string> {
|
||||||
|
const { appleTeamId } = await promptAsync({
|
||||||
|
type: 'text',
|
||||||
|
name: 'appleTeamId',
|
||||||
|
message: `Input the Apple Team ID associated with your ${this.associatedCredential}:`,
|
||||||
|
validate: (val: string) => val !== '',
|
||||||
|
});
|
||||||
|
return appleTeamId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,17 @@
|
|||||||
import { AppStoreApiKeyPurpose, provideOrGenerateAscApiKeyAsync } from './AscApiKeyUtils';
|
import {
|
||||||
import { AccountFragment, AppStoreConnectApiKeyFragment } from '../../../graphql/generated';
|
AppStoreApiKeyPurpose,
|
||||||
|
PURPOSE_TO_ROLES,
|
||||||
|
provideOrGenerateAscApiKeyAsync,
|
||||||
|
} from './AscApiKeyUtils';
|
||||||
|
import { ChooseAssociatedAppleTeamId } from './ChooseAssociatedAppleTeamId';
|
||||||
|
import { UpdateAppleTeamInfo } from './UpdateAppleTeamInfo';
|
||||||
|
import {
|
||||||
|
AccountFragment,
|
||||||
|
AppStoreConnectApiKeyFragment,
|
||||||
|
AppleTeamFragment,
|
||||||
|
} from '../../../graphql/generated';
|
||||||
import { CredentialsContext } from '../../context';
|
import { CredentialsContext } from '../../context';
|
||||||
|
import { MinimalAscApiKey } from '../credentials';
|
||||||
|
|
||||||
export class CreateAscApiKey {
|
export class CreateAscApiKey {
|
||||||
constructor(private account: AccountFragment) {}
|
constructor(private account: AccountFragment) {}
|
||||||
@@ -12,8 +23,32 @@ export class CreateAscApiKey {
|
|||||||
if (ctx.nonInteractive) {
|
if (ctx.nonInteractive) {
|
||||||
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
throw new Error(`A new App Store Connect API Key cannot be created in non-interactive mode.`);
|
||||||
}
|
}
|
||||||
|
const roles = PURPOSE_TO_ROLES[purpose];
|
||||||
|
if (!roles) {
|
||||||
|
throw new Error(`Unsupported purpose: ${purpose}`);
|
||||||
|
}
|
||||||
|
|
||||||
const ascApiKey = await provideOrGenerateAscApiKeyAsync(ctx, purpose);
|
const ascApiKey = await provideOrGenerateAscApiKeyAsync(ctx, roles);
|
||||||
return await ctx.ios.createAscApiKeyAsync(ctx.graphqlClient, this.account, ascApiKey);
|
const appleTeam = await this.ensureKeyHasAppleTeamInfoAsync(ctx, ascApiKey);
|
||||||
|
|
||||||
|
return await ctx.ios.createAscApiKeyAsync(ctx.graphqlClient, this.account, {
|
||||||
|
...ascApiKey,
|
||||||
|
teamId: appleTeam.appleTeamIdentifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureKeyHasAppleTeamInfoAsync(
|
||||||
|
ctx: CredentialsContext,
|
||||||
|
ascApiKey: MinimalAscApiKey
|
||||||
|
): Promise<AppleTeamFragment> {
|
||||||
|
let appleTeamIdentifier = ascApiKey.teamId;
|
||||||
|
if (!appleTeamIdentifier) {
|
||||||
|
const chooseAssociatedAppleTeamIdAction = new ChooseAssociatedAppleTeamId(
|
||||||
|
'App Store Connect API Key'
|
||||||
|
);
|
||||||
|
appleTeamIdentifier = await chooseAssociatedAppleTeamIdAction.runAsync(ctx);
|
||||||
|
}
|
||||||
|
const updateAppleTeamAction = new UpdateAppleTeamInfo(this.account, appleTeamIdentifier);
|
||||||
|
return await updateAppleTeamAction.runAsync(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { AppStoreApiKeyPurpose } from './AscApiKeyUtils';
|
||||||
|
import { AppStoreConnectApiKeyFragment } from '../../../graphql/generated';
|
||||||
|
import Log from '../../../log';
|
||||||
|
import { CredentialsContext } from '../../context';
|
||||||
|
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||||
|
|
||||||
|
export class GetAscApiKey {
|
||||||
|
constructor(
|
||||||
|
private app: AppLookupParams,
|
||||||
|
private purpose: AppStoreApiKeyPurpose
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async runAsync(ctx: CredentialsContext): Promise<AppStoreConnectApiKeyFragment | null> {
|
||||||
|
const appCredentials = await ctx.ios.getIosAppCredentialsWithCommonFieldsAsync(
|
||||||
|
ctx.graphqlClient,
|
||||||
|
this.app
|
||||||
|
);
|
||||||
|
if (this.purpose === AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
||||||
|
return appCredentials?.appStoreConnectApiKeyForSubmissions ?? null;
|
||||||
|
} else if (this.purpose === AppStoreApiKeyPurpose.BUILD_SERVICE) {
|
||||||
|
const ascApiKey = appCredentials?.appStoreConnectApiKeyForBuilds;
|
||||||
|
if (!ascApiKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const appleTeam = ascApiKey.appleTeam;
|
||||||
|
const hasAppleTeamInfo =
|
||||||
|
!!appleTeam && !!appleTeam.appleTeamIdentifier && !!appleTeam.appleTeamType;
|
||||||
|
if (!hasAppleTeamInfo) {
|
||||||
|
Log.warn(
|
||||||
|
`App Store Connect API Key for ${this.purpose} is missing Apple Team information. Run "eas credentials" and select "Use an existing API Key" to update the missing information.`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ascApiKey;
|
||||||
|
} else {
|
||||||
|
throw new Error(`App Store Connect API Keys are not yet supported for ${this.purpose}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -110,10 +110,13 @@ export class SetUpAscApiKey {
|
|||||||
ctx.graphqlClient,
|
ctx.graphqlClient,
|
||||||
this.app
|
this.app
|
||||||
);
|
);
|
||||||
if (purpose !== AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
if (purpose === AppStoreApiKeyPurpose.SUBMISSION_SERVICE) {
|
||||||
|
return !!appCredentials?.appStoreConnectApiKeyForSubmissions;
|
||||||
|
} else if (purpose === AppStoreApiKeyPurpose.BUILD_SERVICE) {
|
||||||
|
return !!appCredentials?.appStoreConnectApiKeyForBuilds;
|
||||||
|
} else {
|
||||||
throw new Error(`App Store Connect API Key setup is not yet supported for ${purpose}.`);
|
throw new Error(`App Store Connect API Key setup is not yet supported for ${purpose}.`);
|
||||||
}
|
}
|
||||||
return !!appCredentials?.appStoreConnectApiKeyForSubmissions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processChoicesAsync(
|
private async processChoicesAsync(
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { Teams } from '@expo/apple-utils';
|
||||||
|
|
||||||
|
import { AccountFragment, AppleTeamFragment, AppleTeamType } from '../../../graphql/generated';
|
||||||
|
import { promptAsync } from '../../../prompts';
|
||||||
|
import { CredentialsContext } from '../../context';
|
||||||
|
import { AppleTeamQuery } from '../api/graphql/queries/AppleTeamQuery';
|
||||||
|
|
||||||
|
/* *
|
||||||
|
* Update the Apple Team for the app.
|
||||||
|
* If the user is logged in, we sync the Team info from the Developer Portal
|
||||||
|
* Else we ask the user to choose their Team Type (Company/Organization, Individual, In-House)
|
||||||
|
*/
|
||||||
|
export class UpdateAppleTeamInfo {
|
||||||
|
constructor(
|
||||||
|
private account: AccountFragment,
|
||||||
|
private appleTeamIdentifier: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async runAsync(ctx: CredentialsContext): Promise<AppleTeamFragment> {
|
||||||
|
const appleTeam = await AppleTeamQuery.getByAppleTeamIdentifierAsync(
|
||||||
|
ctx.graphqlClient,
|
||||||
|
this.account.id,
|
||||||
|
this.appleTeamIdentifier
|
||||||
|
);
|
||||||
|
const hasAppleTeamInfo = !!appleTeam && !!appleTeam.appleTeamType;
|
||||||
|
if (hasAppleTeamInfo) {
|
||||||
|
return appleTeam;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.appStore.authCtx) {
|
||||||
|
return await this.promptAndUpdateAppleTeamTypeAsync(ctx);
|
||||||
|
}
|
||||||
|
const teams = await Teams.getTeamsAsync();
|
||||||
|
const team = teams.find(t => t.teamId === this.appleTeamIdentifier);
|
||||||
|
if (!team) {
|
||||||
|
return await this.promptAndUpdateAppleTeamTypeAsync(ctx);
|
||||||
|
}
|
||||||
|
const { teamId, type, name } = team;
|
||||||
|
const teamType = appStoreTeamTypeToEnum(type);
|
||||||
|
return await ctx.ios.createOrUpdateExistingAppleTeamAsync(ctx.graphqlClient, this.account, {
|
||||||
|
appleTeamIdentifier: teamId,
|
||||||
|
appleTeamName: name,
|
||||||
|
appleTeamType: teamType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async promptAndUpdateAppleTeamTypeAsync(
|
||||||
|
ctx: CredentialsContext
|
||||||
|
): Promise<AppleTeamFragment> {
|
||||||
|
if (ctx.nonInteractive) {
|
||||||
|
throw new Error(
|
||||||
|
'Unable to get Apple Team information in non-interactive mode. Please re-run with interactive mode.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const teamType = await promptForAppleTeamTypeAsync();
|
||||||
|
return await ctx.ios.createOrUpdateExistingAppleTeamAsync(ctx.graphqlClient, this.account, {
|
||||||
|
appleTeamIdentifier: this.appleTeamIdentifier,
|
||||||
|
appleTeamType: teamType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appStoreTeamTypeToEnum(
|
||||||
|
teamType: 'In-House' | 'Company/Organization' | 'Individual' | string
|
||||||
|
): AppleTeamType | null {
|
||||||
|
if (teamType === 'In-House') {
|
||||||
|
return AppleTeamType.InHouse;
|
||||||
|
} else if (teamType === 'Company/Organization') {
|
||||||
|
return AppleTeamType.CompanyOrOrganization;
|
||||||
|
} else if (teamType === 'Individual') {
|
||||||
|
return AppleTeamType.Individual;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function promptForAppleTeamTypeAsync(): Promise<AppleTeamType> {
|
||||||
|
const { appleTeamType } = await promptAsync({
|
||||||
|
type: 'select',
|
||||||
|
message: 'Select your Apple Team Type:',
|
||||||
|
name: 'appleTeamType',
|
||||||
|
choices: [
|
||||||
|
{ title: 'Enterprise', value: AppleTeamType.InHouse },
|
||||||
|
{ title: 'Company/Organization', value: AppleTeamType.CompanyOrOrganization },
|
||||||
|
{ title: 'Individual', value: AppleTeamType.Individual },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return appleTeamType;
|
||||||
|
}
|
||||||
@@ -1,21 +1,15 @@
|
|||||||
|
import { UserRole } from '@expo/apple-utils';
|
||||||
|
|
||||||
import { promptAsync } from '../../../../prompts';
|
import { promptAsync } from '../../../../prompts';
|
||||||
import { getAppstoreMock, testAuthCtx } from '../../../__tests__/fixtures-appstore';
|
import { getAppstoreMock, testAuthCtx } from '../../../__tests__/fixtures-appstore';
|
||||||
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
import { createCtxMock } from '../../../__tests__/fixtures-context';
|
||||||
import { testAscApiKey } from '../../../__tests__/fixtures-ios';
|
import { testAscApiKey } from '../../../__tests__/fixtures-ios';
|
||||||
import { getCredentialsFromUserAsync } from '../../../utils/promptForCredentials';
|
import { getCredentialsFromUserAsync } from '../../../utils/promptForCredentials';
|
||||||
import {
|
import { getAscApiKeyName, promptForAscApiKeyPathAsync } from '../AscApiKeyUtils';
|
||||||
AppStoreApiKeyPurpose,
|
|
||||||
getAscApiKeyName,
|
|
||||||
promptForAscApiKeyPathAsync,
|
|
||||||
} from '../AscApiKeyUtils';
|
|
||||||
|
|
||||||
jest.mock('../../../../prompts');
|
jest.mock('../../../../prompts');
|
||||||
jest.mock('../../../utils/promptForCredentials');
|
jest.mock('../../../utils/promptForCredentials');
|
||||||
|
|
||||||
function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] {
|
|
||||||
return Object.keys(obj) as K[];
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.mocked(promptAsync).mockClear();
|
jest.mocked(promptAsync).mockClear();
|
||||||
jest.mocked(getCredentialsFromUserAsync).mockClear();
|
jest.mocked(getCredentialsFromUserAsync).mockClear();
|
||||||
@@ -24,11 +18,8 @@ afterEach(() => {
|
|||||||
describe(getAscApiKeyName, () => {
|
describe(getAscApiKeyName, () => {
|
||||||
// Apple enforces a 30 char limit on this name
|
// Apple enforces a 30 char limit on this name
|
||||||
it('produces a name under 30 chars', async () => {
|
it('produces a name under 30 chars', async () => {
|
||||||
for (const value of enumKeys(AppStoreApiKeyPurpose)) {
|
const name = getAscApiKeyName();
|
||||||
const purpose = AppStoreApiKeyPurpose[value];
|
|
||||||
const name = getAscApiKeyName(purpose);
|
|
||||||
expect(name.length < 30).toBe(true);
|
expect(name.length < 30).toBe(true);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -51,7 +42,7 @@ describe(promptForAscApiKeyPathAsync, () => {
|
|||||||
authCtx: null,
|
authCtx: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx);
|
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx, [UserRole.ADMIN]);
|
||||||
expect(ascApiKeyPath).toEqual({
|
expect(ascApiKeyPath).toEqual({
|
||||||
keyId: 'test-key-id',
|
keyId: 'test-key-id',
|
||||||
issuerId: 'test-issuer-id',
|
issuerId: 'test-issuer-id',
|
||||||
@@ -76,7 +67,7 @@ describe(promptForAscApiKeyPathAsync, () => {
|
|||||||
getAscApiKeyAsync: jest.fn(() => testAscApiKey),
|
getAscApiKeyAsync: jest.fn(() => testAscApiKey),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx);
|
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx, [UserRole.ADMIN]);
|
||||||
expect(ascApiKeyPath).toEqual({
|
expect(ascApiKeyPath).toEqual({
|
||||||
keyId: 'test-key-id',
|
keyId: 'test-key-id',
|
||||||
issuerId: 'test-issuer-id-from-apple',
|
issuerId: 'test-issuer-id-from-apple',
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
AppleDistributionCertificateFragment,
|
AppleDistributionCertificateFragment,
|
||||||
ApplePushKeyFragment,
|
ApplePushKeyFragment,
|
||||||
AppleTeamFragment,
|
AppleTeamFragment,
|
||||||
|
AppleTeamInput,
|
||||||
CommonIosAppCredentialsFragment,
|
CommonIosAppCredentialsFragment,
|
||||||
IosAppBuildCredentialsFragment,
|
IosAppBuildCredentialsFragment,
|
||||||
IosDistributionType,
|
IosDistributionType,
|
||||||
@@ -189,29 +190,27 @@ export async function updateIosAppCredentialsAsync(
|
|||||||
{
|
{
|
||||||
applePushKeyId,
|
applePushKeyId,
|
||||||
ascApiKeyIdForSubmissions,
|
ascApiKeyIdForSubmissions,
|
||||||
|
ascApiKeyIdForBuilds,
|
||||||
}: {
|
}: {
|
||||||
applePushKeyId?: string;
|
applePushKeyId?: string;
|
||||||
ascApiKeyIdForSubmissions?: string;
|
ascApiKeyIdForSubmissions?: string;
|
||||||
|
ascApiKeyIdForBuilds?: string;
|
||||||
}
|
}
|
||||||
): Promise<CommonIosAppCredentialsFragment> {
|
): Promise<CommonIosAppCredentialsFragment> {
|
||||||
let updatedAppCredentials = appCredentials;
|
const updatedAppCredentials = appCredentials;
|
||||||
if (applePushKeyId) {
|
if (!applePushKeyId && !ascApiKeyIdForSubmissions && !ascApiKeyIdForBuilds) {
|
||||||
updatedAppCredentials = await IosAppCredentialsMutation.setPushKeyAsync(
|
|
||||||
graphqlClient,
|
|
||||||
updatedAppCredentials.id,
|
|
||||||
applePushKeyId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (ascApiKeyIdForSubmissions) {
|
|
||||||
updatedAppCredentials =
|
|
||||||
await IosAppCredentialsMutation.setAppStoreConnectApiKeyForSubmissionsAsync(
|
|
||||||
graphqlClient,
|
|
||||||
updatedAppCredentials.id,
|
|
||||||
ascApiKeyIdForSubmissions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return updatedAppCredentials;
|
return updatedAppCredentials;
|
||||||
}
|
}
|
||||||
|
return await IosAppCredentialsMutation.updateIosAppCredentialsAsync(
|
||||||
|
graphqlClient,
|
||||||
|
appCredentials.id,
|
||||||
|
{
|
||||||
|
pushKeyId: applePushKeyId,
|
||||||
|
appStoreConnectApiKeyForSubmissionsId: ascApiKeyIdForSubmissions,
|
||||||
|
appStoreConnectApiKeyForBuildsId: ascApiKeyIdForBuilds,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function createOrGetExistingIosAppCredentialsWithBuildCredentialsAsync(
|
async function createOrGetExistingIosAppCredentialsWithBuildCredentialsAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
@@ -263,7 +262,42 @@ async function createOrGetExistingIosAppCredentialsWithBuildCredentialsAsync(
|
|||||||
export async function createOrGetExistingAppleTeamAsync(
|
export async function createOrGetExistingAppleTeamAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
account: AccountFragment,
|
account: AccountFragment,
|
||||||
{ appleTeamIdentifier, appleTeamName }: { appleTeamIdentifier: string; appleTeamName?: string }
|
{ appleTeamIdentifier, appleTeamName, appleTeamType }: AppleTeamInput
|
||||||
|
): Promise<AppleTeamFragment> {
|
||||||
|
return await createOrManageExistingAppleTeamAsync(
|
||||||
|
graphqlClient,
|
||||||
|
account,
|
||||||
|
{
|
||||||
|
appleTeamIdentifier,
|
||||||
|
appleTeamName,
|
||||||
|
appleTeamType,
|
||||||
|
},
|
||||||
|
{ updateMissingFields: false }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createOrUpdateExistingAppleTeamAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
account: AccountFragment,
|
||||||
|
{ appleTeamIdentifier, appleTeamName, appleTeamType }: AppleTeamInput
|
||||||
|
): Promise<AppleTeamFragment> {
|
||||||
|
return await createOrManageExistingAppleTeamAsync(
|
||||||
|
graphqlClient,
|
||||||
|
account,
|
||||||
|
{
|
||||||
|
appleTeamIdentifier,
|
||||||
|
appleTeamName,
|
||||||
|
appleTeamType,
|
||||||
|
},
|
||||||
|
{ updateMissingFields: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOrManageExistingAppleTeamAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
account: AccountFragment,
|
||||||
|
{ appleTeamIdentifier, appleTeamName, appleTeamType }: AppleTeamInput,
|
||||||
|
{ updateMissingFields }: { updateMissingFields: boolean }
|
||||||
): Promise<AppleTeamFragment> {
|
): Promise<AppleTeamFragment> {
|
||||||
const appleTeam = await AppleTeamQuery.getByAppleTeamIdentifierAsync(
|
const appleTeam = await AppleTeamQuery.getByAppleTeamIdentifierAsync(
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
@@ -271,11 +305,24 @@ export async function createOrGetExistingAppleTeamAsync(
|
|||||||
appleTeamIdentifier
|
appleTeamIdentifier
|
||||||
);
|
);
|
||||||
if (appleTeam) {
|
if (appleTeam) {
|
||||||
|
const didTeamNameChange = appleTeamName && appleTeam.appleTeamName !== appleTeamName;
|
||||||
|
const didTeamTypeChange = appleTeamType && appleTeam.appleTeamType !== appleTeamType;
|
||||||
|
const didFieldsChange = didTeamNameChange || didTeamTypeChange;
|
||||||
|
if (updateMissingFields && didFieldsChange) {
|
||||||
|
return await AppleTeamMutation.updateAppleTeamAsync(
|
||||||
|
graphqlClient,
|
||||||
|
{
|
||||||
|
appleTeamName: didTeamNameChange ? appleTeamName : undefined,
|
||||||
|
appleTeamType: didTeamTypeChange ? appleTeamType : undefined,
|
||||||
|
},
|
||||||
|
appleTeam.id
|
||||||
|
);
|
||||||
|
}
|
||||||
return appleTeam;
|
return appleTeam;
|
||||||
} else {
|
} else {
|
||||||
return await AppleTeamMutation.createAppleTeamAsync(
|
return await AppleTeamMutation.createAppleTeamAsync(
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
{ appleTeamIdentifier, appleTeamName },
|
{ appleTeamIdentifier, appleTeamName, appleTeamType },
|
||||||
account.id
|
account.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -521,6 +568,20 @@ export async function createAscApiKeyAsync(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateAscApiKeyAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
ascApiKey: AppStoreConnectApiKeyFragment,
|
||||||
|
appleTeam: AppleTeamFragment
|
||||||
|
): Promise<AppStoreConnectApiKeyFragment> {
|
||||||
|
return await AppStoreConnectApiKeyMutation.updateAppStoreConnectApiKeyAsync(
|
||||||
|
graphqlClient,
|
||||||
|
{
|
||||||
|
appleTeamId: appleTeam.id,
|
||||||
|
},
|
||||||
|
ascApiKey.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAscApiKeysForAccountAsync(
|
export async function getAscApiKeysForAccountAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
account: AccountFragment
|
account: AccountFragment
|
||||||
|
|||||||
+40
@@ -7,8 +7,10 @@ import { withErrorHandlingAsync } from '../../../../../graphql/client';
|
|||||||
import {
|
import {
|
||||||
AppStoreConnectApiKeyFragment,
|
AppStoreConnectApiKeyFragment,
|
||||||
AppStoreConnectApiKeyInput,
|
AppStoreConnectApiKeyInput,
|
||||||
|
AppStoreConnectApiKeyUpdateInput,
|
||||||
CreateAppStoreConnectApiKeyMutation,
|
CreateAppStoreConnectApiKeyMutation,
|
||||||
DeleteAppStoreConnectApiKeyMutation,
|
DeleteAppStoreConnectApiKeyMutation,
|
||||||
|
UpdateAppStoreConnectApiKeyMutation,
|
||||||
} from '../../../../../graphql/generated';
|
} from '../../../../../graphql/generated';
|
||||||
import { AppStoreConnectApiKeyFragmentNode } from '../../../../../graphql/types/credentials/AppStoreConnectApiKey';
|
import { AppStoreConnectApiKeyFragmentNode } from '../../../../../graphql/types/credentials/AppStoreConnectApiKey';
|
||||||
|
|
||||||
@@ -51,6 +53,44 @@ export const AppStoreConnectApiKeyMutation = {
|
|||||||
);
|
);
|
||||||
return data.appStoreConnectApiKey.createAppStoreConnectApiKey;
|
return data.appStoreConnectApiKey.createAppStoreConnectApiKey;
|
||||||
},
|
},
|
||||||
|
async updateAppStoreConnectApiKeyAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
appStoreConnectApiKeyUpdateInput: AppStoreConnectApiKeyUpdateInput,
|
||||||
|
appStoreConnectApiKeyId: string
|
||||||
|
): Promise<AppStoreConnectApiKeyFragment> {
|
||||||
|
const data = await withErrorHandlingAsync(
|
||||||
|
graphqlClient
|
||||||
|
.mutation<UpdateAppStoreConnectApiKeyMutation>(
|
||||||
|
gql`
|
||||||
|
mutation UpdateAppStoreConnectApiKeyMutation(
|
||||||
|
$appStoreConnectApiKeyUpdateInput: AppStoreConnectApiKeyUpdateInput!
|
||||||
|
$id: ID!
|
||||||
|
) {
|
||||||
|
appStoreConnectApiKey {
|
||||||
|
updateAppStoreConnectApiKey(
|
||||||
|
appStoreConnectApiKeyUpdateInput: $appStoreConnectApiKeyUpdateInput
|
||||||
|
id: $id
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
...AppStoreConnectApiKeyFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${print(AppStoreConnectApiKeyFragmentNode)}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
appStoreConnectApiKeyUpdateInput,
|
||||||
|
appStoreConnectApiKeyId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toPromise()
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
data.appStoreConnectApiKey.updateAppStoreConnectApiKey,
|
||||||
|
'GraphQL: `updateAppStoreConnectApiKey` not defined in server response'
|
||||||
|
);
|
||||||
|
return data.appStoreConnectApiKey.updateAppStoreConnectApiKey;
|
||||||
|
},
|
||||||
async deleteAppStoreConnectApiKeyAsync(
|
async deleteAppStoreConnectApiKeyAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
appStoreConnectApiKeyId: string
|
appStoreConnectApiKeyId: string
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import {
|
|||||||
AccountFragment,
|
AccountFragment,
|
||||||
AppleTeamFragment,
|
AppleTeamFragment,
|
||||||
AppleTeamInput,
|
AppleTeamInput,
|
||||||
|
AppleTeamUpdateInput,
|
||||||
CreateAppleTeamMutation,
|
CreateAppleTeamMutation,
|
||||||
|
UpdateAppleTeamMutation,
|
||||||
} from '../../../../../graphql/generated';
|
} from '../../../../../graphql/generated';
|
||||||
import { AccountFragmentNode } from '../../../../../graphql/types/Account';
|
import { AccountFragmentNode } from '../../../../../graphql/types/Account';
|
||||||
import { AppleTeamFragmentNode } from '../../../../../graphql/types/credentials/AppleTeam';
|
import { AppleTeamFragmentNode } from '../../../../../graphql/types/credentials/AppleTeam';
|
||||||
@@ -50,4 +52,40 @@ export const AppleTeamMutation = {
|
|||||||
);
|
);
|
||||||
return data.appleTeam.createAppleTeam;
|
return data.appleTeam.createAppleTeam;
|
||||||
},
|
},
|
||||||
|
async updateAppleTeamAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
appleTeamUpdateInput: AppleTeamUpdateInput,
|
||||||
|
appleTeamId: string
|
||||||
|
): Promise<AppleTeamMutationResult> {
|
||||||
|
const data = await withErrorHandlingAsync(
|
||||||
|
graphqlClient
|
||||||
|
.mutation<UpdateAppleTeamMutation>(
|
||||||
|
gql`
|
||||||
|
mutation UpdateAppleTeamMutation(
|
||||||
|
$appleTeamUpdateInput: AppleTeamUpdateInput!
|
||||||
|
$appleTeamId: ID!
|
||||||
|
) {
|
||||||
|
appleTeam {
|
||||||
|
updateAppleTeam(appleTeamUpdateInput: $appleTeamUpdateInput, id: $appleTeamId) {
|
||||||
|
id
|
||||||
|
...AppleTeamFragment
|
||||||
|
account {
|
||||||
|
id
|
||||||
|
...AccountFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${print(AppleTeamFragmentNode)}
|
||||||
|
${print(AccountFragmentNode)}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
appleTeamUpdateInput,
|
||||||
|
appleTeamId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toPromise()
|
||||||
|
);
|
||||||
|
return data.appleTeam.updateAppleTeam;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
+39
@@ -10,6 +10,7 @@ import {
|
|||||||
IosAppCredentialsInput,
|
IosAppCredentialsInput,
|
||||||
SetAppStoreConnectApiKeyForSubmissionsMutation,
|
SetAppStoreConnectApiKeyForSubmissionsMutation,
|
||||||
SetPushKeyMutation,
|
SetPushKeyMutation,
|
||||||
|
UpdateIosAppCredentialsMutation,
|
||||||
} from '../../../../../graphql/generated';
|
} from '../../../../../graphql/generated';
|
||||||
import { CommonIosAppCredentialsFragmentNode } from '../../../../../graphql/types/credentials/IosAppCredentials';
|
import { CommonIosAppCredentialsFragmentNode } from '../../../../../graphql/types/credentials/IosAppCredentials';
|
||||||
|
|
||||||
@@ -56,6 +57,44 @@ export const IosAppCredentialsMutation = {
|
|||||||
);
|
);
|
||||||
return data.iosAppCredentials.createIosAppCredentials;
|
return data.iosAppCredentials.createIosAppCredentials;
|
||||||
},
|
},
|
||||||
|
async updateIosAppCredentialsAsync(
|
||||||
|
graphqlClient: ExpoGraphqlClient,
|
||||||
|
iosAppCredentialsId: string,
|
||||||
|
iosAppCredentialsInput: IosAppCredentialsInput
|
||||||
|
): Promise<CommonIosAppCredentialsFragment> {
|
||||||
|
const data = await withErrorHandlingAsync(
|
||||||
|
graphqlClient
|
||||||
|
.mutation<UpdateIosAppCredentialsMutation>(
|
||||||
|
gql`
|
||||||
|
mutation UpdateIosAppCredentialsMutation(
|
||||||
|
$iosAppCredentialsInput: IosAppCredentialsInput!
|
||||||
|
$iosAppCredentialsId: ID!
|
||||||
|
) {
|
||||||
|
iosAppCredentials {
|
||||||
|
updateIosAppCredentials(
|
||||||
|
id: $iosAppCredentialsId
|
||||||
|
iosAppCredentialsInput: $iosAppCredentialsInput
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
...CommonIosAppCredentialsFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${print(CommonIosAppCredentialsFragmentNode)}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
iosAppCredentialsInput,
|
||||||
|
iosAppCredentialsId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toPromise()
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
data.iosAppCredentials.updateIosAppCredentials,
|
||||||
|
'GraphQL: `updateIosAppCredentials` not defined in server response'
|
||||||
|
);
|
||||||
|
return data.iosAppCredentials.updateIosAppCredentials;
|
||||||
|
},
|
||||||
async setPushKeyAsync(
|
async setPushKeyAsync(
|
||||||
graphqlClient: ExpoGraphqlClient,
|
graphqlClient: ExpoGraphqlClient,
|
||||||
iosAppCredentialsId: string,
|
iosAppCredentialsId: string,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ProfileType } from '@expo/app-store';
|
import { ProfileType, UserRole } from '@expo/app-store';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AscApiKey,
|
AscApiKey,
|
||||||
@@ -45,16 +45,23 @@ import { createOrReuseAdhocProvisioningProfileAsync } from './provisioningProfil
|
|||||||
import { createPushKeyAsync, listPushKeysAsync, revokePushKeyAsync } from './pushKey';
|
import { createPushKeyAsync, listPushKeysAsync, revokePushKeyAsync } from './pushKey';
|
||||||
import { hasAscEnvVars } from './resolveCredentials';
|
import { hasAscEnvVars } from './resolveCredentials';
|
||||||
import { Analytics } from '../../../analytics/AnalyticsManager';
|
import { Analytics } from '../../../analytics/AnalyticsManager';
|
||||||
|
import { AppleTeamType } from '../../../graphql/generated';
|
||||||
import Log from '../../../log';
|
import Log from '../../../log';
|
||||||
|
import { MinimalAscApiKey } from '../credentials';
|
||||||
|
|
||||||
|
type AscApiKeyAuth = {
|
||||||
|
ascApiKey: MinimalAscApiKey;
|
||||||
|
appleTeam: { teamIdentifier: string; teamName?: string; teamType: AppleTeamType };
|
||||||
|
};
|
||||||
export default class AppStoreApi {
|
export default class AppStoreApi {
|
||||||
public authCtx?: AuthCtx;
|
public authCtx?: AuthCtx;
|
||||||
public defaultAuthenticationMode: AuthenticationMode;
|
public defaultAuthenticationMode: AuthenticationMode;
|
||||||
|
public readonly ascApiKeyAuth?: AscApiKeyAuth;
|
||||||
|
|
||||||
constructor() {
|
constructor(options: { ascApiKeyAuth?: AscApiKeyAuth } = {}) {
|
||||||
this.defaultAuthenticationMode = hasAscEnvVars()
|
this.ascApiKeyAuth = options.ascApiKeyAuth;
|
||||||
? AuthenticationMode.API_KEY
|
this.defaultAuthenticationMode =
|
||||||
: AuthenticationMode.USER;
|
hasAscEnvVars() || this.ascApiKeyAuth ? AuthenticationMode.API_KEY : AuthenticationMode.USER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ensureUserAuthenticatedAsync(options?: AuthenticateOptions): Promise<UserAuthCtx> {
|
public async ensureUserAuthenticatedAsync(options?: AuthenticateOptions): Promise<UserAuthCtx> {
|
||||||
@@ -195,10 +202,10 @@ export default class AppStoreApi {
|
|||||||
|
|
||||||
public async createAscApiKeyAsync(
|
public async createAscApiKeyAsync(
|
||||||
analytics: Analytics,
|
analytics: Analytics,
|
||||||
{ nickname }: { nickname: string }
|
{ nickname, roles }: { nickname: string; roles: UserRole[] }
|
||||||
): Promise<AscApiKey> {
|
): Promise<AscApiKey> {
|
||||||
const userCtx = await this.ensureUserAuthenticatedAsync();
|
const userCtx = await this.ensureUserAuthenticatedAsync();
|
||||||
return await createAscApiKeyAsync(analytics, userCtx, { nickname });
|
return await createAscApiKeyAsync(analytics, userCtx, { nickname, roles });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async revokeAscApiKeyAsync(keyId: string): Promise<AscApiKeyInfo> {
|
public async revokeAscApiKeyAsync(keyId: string): Promise<AscApiKeyInfo> {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export enum AuthenticationMode {
|
|||||||
USER,
|
USER,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move this to graphql types
|
||||||
export enum AppleTeamType {
|
export enum AppleTeamType {
|
||||||
IN_HOUSE = 'IN_HOUSE',
|
IN_HOUSE = 'IN_HOUSE',
|
||||||
COMPANY_OR_ORGANIZATION = 'COMPANY_OR_ORGANIZATION',
|
COMPANY_OR_ORGANIZATION = 'COMPANY_OR_ORGANIZATION',
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ Developer Portal ID 9839M6AY8W
|
|||||||
Apple Team 77KQ969CHE
|
Apple Team 77KQ969CHE
|
||||||
Updated 6 months ago
|
Updated 6 months ago
|
||||||
|
|
||||||
App Store Connect API Key
|
EAS Submit App Store Connect API Key
|
||||||
Developer Portal ID NYN468XNN3
|
Developer Portal ID NYN468XNN3
|
||||||
Name Expo Submissions pl94k8_lnS
|
Name Expo Submissions pl94k8_lnS
|
||||||
Issuer ID 69a6de93-e4e4-47e3-e053-5b8c7c11a4d1
|
Issuer ID 69a6de93-e4e4-47e3-e053-5b8c7c11a4d1
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { APPLE_DEVICE_CLASS_LABELS } from '../../../graphql/types/credentials/Ap
|
|||||||
import Log from '../../../log';
|
import Log from '../../../log';
|
||||||
import { fromNow } from '../../../utils/date';
|
import { fromNow } from '../../../utils/date';
|
||||||
import formatFields from '../../../utils/formatFields';
|
import formatFields from '../../../utils/formatFields';
|
||||||
|
import { AppStoreApiKeyPurpose } from '../actions/AscApiKeyUtils';
|
||||||
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
|
||||||
import { App, IosAppBuildCredentialsMap, IosAppCredentialsMap, Target } from '../types';
|
import { App, IosAppBuildCredentialsMap, IosAppCredentialsMap, Target } from '../types';
|
||||||
|
|
||||||
@@ -92,7 +93,12 @@ export function displayIosCredentials(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { appleTeam, pushKey, appStoreConnectApiKeyForSubmissions } = targetAppCredentials;
|
const {
|
||||||
|
appleTeam,
|
||||||
|
pushKey,
|
||||||
|
appStoreConnectApiKeyForSubmissions,
|
||||||
|
appStoreConnectApiKeyForBuilds,
|
||||||
|
} = targetAppCredentials;
|
||||||
if (appleTeam) {
|
if (appleTeam) {
|
||||||
const { appleTeamIdentifier, appleTeamName } = appleTeam;
|
const { appleTeamIdentifier, appleTeamName } = appleTeam;
|
||||||
fields.push({
|
fields.push({
|
||||||
@@ -107,7 +113,15 @@ export function displayIosCredentials(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (appStoreConnectApiKeyForSubmissions) {
|
if (appStoreConnectApiKeyForSubmissions) {
|
||||||
displayAscApiKey(appStoreConnectApiKeyForSubmissions, fields);
|
displayAscApiKey(
|
||||||
|
AppStoreApiKeyPurpose.SUBMISSION_SERVICE,
|
||||||
|
appStoreConnectApiKeyForSubmissions,
|
||||||
|
fields
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appStoreConnectApiKeyForBuilds) {
|
||||||
|
displayAscApiKey(AppStoreApiKeyPurpose.BUILD_SERVICE, appStoreConnectApiKeyForBuilds, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedIosAppBuildCredentialsList = sortBuildCredentialsByDistributionType(
|
const sortedIosAppBuildCredentialsList = sortBuildCredentialsByDistributionType(
|
||||||
@@ -236,10 +250,11 @@ function displayApplePushKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function displayAscApiKey(
|
function displayAscApiKey(
|
||||||
|
purpose: AppStoreApiKeyPurpose,
|
||||||
maybeAscApiKey: AppStoreConnectApiKeyFragment | null,
|
maybeAscApiKey: AppStoreConnectApiKeyFragment | null,
|
||||||
fields: { label: string; value: string }[]
|
fields: { label: string; value: string }[]
|
||||||
): void {
|
): void {
|
||||||
fields.push({ label: 'App Store Connect API Key', value: '' });
|
fields.push({ label: `${purpose.toString()} App Store Connect API Key`, value: '' });
|
||||||
if (maybeAscApiKey) {
|
if (maybeAscApiKey) {
|
||||||
const { keyIdentifier, issuerIdentifier, appleTeam, name, roles, updatedAt } = maybeAscApiKey;
|
const { keyIdentifier, issuerIdentifier, appleTeam, name, roles, updatedAt } = maybeAscApiKey;
|
||||||
fields.push({ label: 'Developer Portal ID', value: keyIdentifier });
|
fields.push({ label: 'Developer Portal ID', value: keyIdentifier });
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export enum AndroidActionType {
|
|||||||
UseExistingGsaKeyForSubmissions,
|
UseExistingGsaKeyForSubmissions,
|
||||||
RemoveGsaKey,
|
RemoveGsaKey,
|
||||||
SetUpGsaKeyForSubmissions,
|
SetUpGsaKeyForSubmissions,
|
||||||
CreateGsaKeyForFcmV1,
|
|
||||||
UseExistingGsaKeyForFcmV1,
|
UseExistingGsaKeyForFcmV1,
|
||||||
RemoveGsaKeyForFcmV1,
|
RemoveGsaKeyForFcmV1,
|
||||||
SetUpGsaKeyForFcmV1,
|
SetUpGsaKeyForFcmV1,
|
||||||
@@ -56,8 +55,8 @@ export enum IosActionType {
|
|||||||
CreatePushKey,
|
CreatePushKey,
|
||||||
UseExistingPushKey,
|
UseExistingPushKey,
|
||||||
RemovePushKey,
|
RemovePushKey,
|
||||||
SetUpAscApiKeyForSubmissions,
|
SetUpAscApiKeyForService,
|
||||||
UseExistingAscApiKeyForSubmissions,
|
CreateAscApiKeyForService,
|
||||||
CreateAscApiKeyForSubmissions,
|
UseExistingAscApiKeyForService,
|
||||||
RemoveAscApiKey,
|
RemoveAscApiKey,
|
||||||
}
|
}
|
||||||
@@ -77,22 +77,22 @@ export function getPushKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAscApiKeyActions(ctx: CredentialsContext): ActionInfo[] {
|
export function getAscApiKeyActions(): ActionInfo[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
value: IosActionType.SetUpAscApiKeyForSubmissions,
|
value: IosActionType.SetUpAscApiKeyForService,
|
||||||
title: 'Set up your project to use an API Key for EAS Submit',
|
title: `Set up your project to use an API Key`,
|
||||||
scope: Scope.Project,
|
scope: Scope.Project,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: IosActionType.UseExistingAscApiKeyForSubmissions,
|
value: IosActionType.UseExistingAscApiKeyForService,
|
||||||
title: 'Use an existing API Key for EAS Submit',
|
title: `Use an existing API Key in your project`,
|
||||||
scope: Scope.Project,
|
scope: Scope.Project,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: IosActionType.CreateAscApiKeyForSubmissions,
|
value: IosActionType.CreateAscApiKeyForService,
|
||||||
title: 'Add a new API Key For EAS Submit',
|
title: `Add a new API Key to use in your project`,
|
||||||
scope: ctx.hasProjectContext ? Scope.Project : Scope.Account,
|
scope: Scope.Project,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: IosActionType.RemoveAscApiKey,
|
value: IosActionType.RemoveAscApiKey,
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export class ManageIos {
|
|||||||
});
|
});
|
||||||
const buildCredentialsActions = getBuildCredentialsActions(ctx);
|
const buildCredentialsActions = getBuildCredentialsActions(ctx);
|
||||||
const pushKeyActions = getPushKeyActions(ctx);
|
const pushKeyActions = getPushKeyActions(ctx);
|
||||||
const ascApiKeyActions = getAscApiKeyActions(ctx);
|
const ascApiKeyActions = getAscApiKeyActions();
|
||||||
|
|
||||||
await ctx.bestEffortAppStoreAuthenticateAsync();
|
await ctx.bestEffortAppStoreAuthenticateAsync();
|
||||||
|
|
||||||
@@ -226,8 +226,6 @@ export class ManageIos {
|
|||||||
await new CreateDistributionCertificate(account).runAsync(ctx);
|
await new CreateDistributionCertificate(account).runAsync(ctx);
|
||||||
} else if (action === IosActionType.CreatePushKey) {
|
} else if (action === IosActionType.CreatePushKey) {
|
||||||
await new CreatePushKey(account).runAsync(ctx);
|
await new CreatePushKey(account).runAsync(ctx);
|
||||||
} else if (action === IosActionType.CreateAscApiKeyForSubmissions) {
|
|
||||||
await new CreateAscApiKey(account).runAsync(ctx, AppStoreApiKeyPurpose.SUBMISSION_SERVICE);
|
|
||||||
} else if (action === IosActionType.RemovePushKey) {
|
} else if (action === IosActionType.RemovePushKey) {
|
||||||
await new SelectAndRemovePushKey(account).runAsync(ctx);
|
await new SelectAndRemovePushKey(account).runAsync(ctx);
|
||||||
} else if (action === IosActionType.RemoveAscApiKey) {
|
} else if (action === IosActionType.RemoveAscApiKey) {
|
||||||
@@ -354,40 +352,29 @@ export class ManageIos {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case IosActionType.SetUpAscApiKeyForSubmissions: {
|
case IosActionType.SetUpAscApiKeyForService: {
|
||||||
await new SetUpAscApiKey(
|
const purpose = await this.selectAscApiKeyPurposeAsync();
|
||||||
appLookupParams,
|
await new SetUpAscApiKey(appLookupParams, purpose).runAsync(ctx);
|
||||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
|
||||||
).runAsync(ctx);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case IosActionType.UseExistingAscApiKeyForSubmissions: {
|
case IosActionType.UseExistingAscApiKeyForService: {
|
||||||
|
const purpose = await this.selectAscApiKeyPurposeAsync();
|
||||||
const ascApiKey = await selectAscApiKeysFromAccountAsync(ctx, appLookupParams.account, {
|
const ascApiKey = await selectAscApiKeysFromAccountAsync(ctx, appLookupParams.account, {
|
||||||
filterDifferentAppleTeam: true,
|
filterDifferentAppleTeam: true,
|
||||||
});
|
});
|
||||||
if (ascApiKey) {
|
if (ascApiKey) {
|
||||||
await new AssignAscApiKey(appLookupParams).runAsync(
|
await new AssignAscApiKey(appLookupParams).runAsync(ctx, ascApiKey, purpose);
|
||||||
ctx,
|
|
||||||
ascApiKey,
|
|
||||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case IosActionType.CreateAscApiKeyForSubmissions: {
|
case IosActionType.CreateAscApiKeyForService: {
|
||||||
const ascApiKey = await new CreateAscApiKey(appLookupParams.account).runAsync(
|
const purpose = await this.selectAscApiKeyPurposeAsync();
|
||||||
ctx,
|
const ascApiKey = await new CreateAscApiKey(appLookupParams.account).runAsync(ctx, purpose);
|
||||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
|
||||||
);
|
|
||||||
const confirm = await confirmAsync({
|
const confirm = await confirmAsync({
|
||||||
message: `Do you want ${appLookupParams.projectName} to use the new API Key?`,
|
message: `Do you want ${appLookupParams.projectName} to use the new API Key?`,
|
||||||
});
|
});
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
await new AssignAscApiKey(appLookupParams).runAsync(
|
await new AssignAscApiKey(appLookupParams).runAsync(ctx, ascApiKey, purpose);
|
||||||
ctx,
|
|
||||||
ascApiKey,
|
|
||||||
AppStoreApiKeyPurpose.SUBMISSION_SERVICE
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -431,4 +418,17 @@ export class ManageIos {
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async selectAscApiKeyPurposeAsync(): Promise<AppStoreApiKeyPurpose> {
|
||||||
|
const { keyPurpose } = await promptAsync({
|
||||||
|
type: 'select',
|
||||||
|
message: `Select the service you'd like to use your ASC Api Key for:`,
|
||||||
|
name: 'keyPurpose',
|
||||||
|
choices: [
|
||||||
|
{ title: 'EAS Submit', value: AppStoreApiKeyPurpose.SUBMISSION_SERVICE },
|
||||||
|
{ title: 'EAS Build', value: AppStoreApiKeyPurpose.BUILD_SERVICE },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return keyPurpose;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+5839
-634
File diff suppressed because it is too large.
Load diff
@@ -5,5 +5,6 @@ export const AppleTeamFragmentNode = gql`
|
|||||||
id
|
id
|
||||||
appleTeamIdentifier
|
appleTeamIdentifier
|
||||||
appleTeamName
|
appleTeamName
|
||||||
|
appleTeamType
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -30,6 +30,10 @@ export const CommonIosAppCredentialsWithoutBuildCredentialsFragmentNode = gql`
|
|||||||
id
|
id
|
||||||
...AppStoreConnectApiKeyFragment
|
...AppStoreConnectApiKeyFragment
|
||||||
}
|
}
|
||||||
|
appStoreConnectApiKeyForBuilds {
|
||||||
|
id
|
||||||
|
...AppStoreConnectApiKeyFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
${AppFragmentNode}
|
${AppFragmentNode}
|
||||||
${AppleTeamFragmentNode}
|
${AppleTeamFragmentNode}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { UserRole } from '@expo/apple-utils';
|
||||||
import { Platform } from '@expo/eas-build-job';
|
import { Platform } from '@expo/eas-build-job';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import nullthrows from 'nullthrows';
|
import nullthrows from 'nullthrows';
|
||||||
@@ -189,7 +190,7 @@ async function handlePromptSourceAsync(
|
|||||||
ctx: SubmissionContext<Platform.IOS>,
|
ctx: SubmissionContext<Platform.IOS>,
|
||||||
_source: AscApiKeyPromptSource
|
_source: AscApiKeyPromptSource
|
||||||
): Promise<AscApiKeyPath> {
|
): Promise<AscApiKeyPath> {
|
||||||
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx.credentialsCtx);
|
const ascApiKeyPath = await promptForAscApiKeyPathAsync(ctx.credentialsCtx, [UserRole.ADMIN]);
|
||||||
return await getAscApiKeyPathAsync(ctx, {
|
return await getAscApiKeyPathAsync(ctx, {
|
||||||
sourceType: AscApiKeySourceType.path,
|
sourceType: AscApiKeySourceType.path,
|
||||||
path: ascApiKeyPath,
|
path: ascApiKeyPath,
|
||||||
|
|||||||
Reference in new issue
Block a user