Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[eas-cli] action to select android build credentials #438

Merged
merged 3 commits into from
Jun 7, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,39 @@ export async function createOrUpdateDefaultAndroidAppBuildCredentialsAsync(
{ androidKeystoreId }
);
}
const providedName = await promptForNameAsync();
return await ctx.newAndroid.createAndroidAppBuildCredentialsAsync(appLookupParams, {
name: providedName,
isDefault: true,
androidKeystoreId,
});
}

export async function promptForNameAsync(): Promise<string> {
const { providedName } = await promptAsync({
type: 'text',
name: 'providedName',
message: 'Assign a name to your build credentials:',
initial: generateRandomName(),
validate: (input: string) => input !== '',
});
return await ctx.newAndroid.createAndroidAppBuildCredentialsAsync(appLookupParams, {
name: providedName,
isDefault: true,
androidKeystoreId,
return providedName;
}

/**
* sort a build credentials array in descending order of preference
* prefer default credentials, then prefer names that come first lexicographically
*/
export function sortBuildCredentials(
androidAppBuildCredentialsList: AndroidAppBuildCredentialsFragment[]
): AndroidAppBuildCredentialsFragment[] {
return androidAppBuildCredentialsList.sort((buildCredentialsA, buildCredentialsB) => {
if (buildCredentialsA.isDefault) {
return -1;
} else if (buildCredentialsB.isDefault) {
return 1;
}
return buildCredentialsA.name.localeCompare(buildCredentialsB.name);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ import gql from 'graphql-tag';
import { graphqlClient, withErrorHandlingAsync } from '../../../../../graphql/client';
import {
AndroidAppBuildCredentialsFragment,
AndroidAppBuildCredentialsInput,
CreateAndroidAppBuildCredentialsMutation,
SetKeystoreMutation,
} from '../../../../../graphql/generated';
import { AndroidAppBuildCredentialsFragmentNode } from '../../../../../graphql/types/credentials/AndroidAppBuildCredentials';

export type AndroidAppBuildCredentialsMetadataInput = Omit<
AndroidAppBuildCredentialsInput,
'keystoreId'
>;
const AndroidAppBuildCredentialsMutation = {
async createAndroidAppBuildCredentialsAsync(
androidAppBuildCredentialsInput: {
isDefault: boolean;
name: string;
keystoreId: string;
},
androidAppBuildCredentialsInput: AndroidAppBuildCredentialsInput,
androidAppCredentialsId: string
): Promise<AndroidAppBuildCredentialsFragment> {
const data = await withErrorHandlingAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '../../../graphql/generated';
import Log from '../../../log';
import { fromNow } from '../../../utils/date';
import { sortBuildCredentials } from '../actions/BuildCredentialsUtils';
import { AppLookupParams } from '../api/GraphqlClient';

export function displayEmptyAndroidCredentials(appLookupParams: AppLookupParams): void {
Expand All @@ -19,23 +20,6 @@ export function displayEmptyAndroidCredentials(appLookupParams: AppLookupParams)
Log.log(` No credentials set up yet!`);
}

/**
* sort a build credentials array in descending order of preference
* prefer default credentials, then prefer names that come first lexicographically
*/
function sortBuildCredentials(
androidAppBuildCredentialsList: AndroidAppBuildCredentialsFragment[]
): AndroidAppBuildCredentialsFragment[] {
return androidAppBuildCredentialsList.sort((buildCredentialsA, buildCredentialsB) => {
if (buildCredentialsA.isDefault) {
return -1;
} else if (buildCredentialsB.isDefault) {
return 1;
}
return buildCredentialsA.name.localeCompare(buildCredentialsB.name);
});
}

function displayLegacyAndroidAppCredentials(
legacyAppCredentials: CommonAndroidAppCredentialsFragment,
appCredentials: CommonAndroidAppCredentialsFragment | null
Expand Down
46 changes: 44 additions & 2 deletions packages/eas-cli/src/credentials/manager/ManageAndroidBeta.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import Log from '../../log';
import { getProjectAccountName } from '../../project/projectUtils';
import { promptAsync } from '../../prompts';
import { findAccountByName } from '../../user/Account';
import { ensureActorHasUsername } from '../../user/actions';
import { Action, CredentialsManager } from '../CredentialsManager';
import { getAppLookupParamsFromContext } from '../android/actions/BuildCredentialsUtils';
import { CreateKeystore } from '../android/actions/new/CreateKeystore';
import {
displayAndroidAppCredentials,
displayEmptyAndroidCredentials,
} from '../android/utils/printCredentialsBeta';
import { Context } from '../context';
import { PressAnyKeyToContinue } from './HelperActions';
import {
SelectAndroidBuildCredentials,
SelectAndroidBuildCredentialsResultType,
} from './SelectAndroidBuildCredentials';

enum ActionType {}
enum ActionType {
CreateKeystore,
}

enum Scope {
Project,
Expand Down Expand Up @@ -52,7 +60,41 @@ export class ManageAndroid implements Action {
} else {
displayAndroidAppCredentials({ appLookupParams, legacyAppCredentials, appCredentials });
}
throw new Error('Not Implemented Yet');
}
const actions: { value: ActionType; title: string }[] = [
{
value: ActionType.CreateKeystore,
title: 'Set up a new keystore',
},
];
const { action: chosenAction } = await promptAsync({
type: 'select',
name: 'action',
message: 'What do you want to do?',
choices: actions,
});
if (chosenAction === ActionType.CreateKeystore) {
const appLookupParams = getAppLookupParamsFromContext(ctx);
const selectBuildCredentialsResult = await new SelectAndroidBuildCredentials(
appLookupParams
).runAsync(ctx);
const keystore = await new CreateKeystore(appLookupParams.account).runAsync(ctx);
if (
selectBuildCredentialsResult.resultType ===
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST
) {
await ctx.newAndroid.createAndroidAppBuildCredentialsAsync(appLookupParams, {
...selectBuildCredentialsResult.result,
androidKeystoreId: keystore.id,
});
} else {
await ctx.newAndroid.updateAndroidAppBuildCredentialsAsync(
selectBuildCredentialsResult.result,
{
androidKeystoreId: keystore.id,
}
);
}
}
} catch (err) {
Log.error(err);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { AndroidAppBuildCredentialsFragment } from '../../graphql/generated';
import { promptAsync } from '../../prompts';
import { promptForNameAsync, sortBuildCredentials } from '../android/actions/BuildCredentialsUtils';
import { AppLookupParams } from '../android/api/GraphqlClient';
import { AndroidAppBuildCredentialsMetadataInput } from '../android/api/graphql/mutations/AndroidAppBuildCredentialsMutation';
import { Context } from '../context';

export enum SelectAndroidBuildCredentialsResultType {
CREATE_REQUEST,
EXISTING_CREDENTIALS,
}
/**
* Return a selected Android Build Credential, or a request to make a new one
*/
export class SelectAndroidBuildCredentials {
constructor(private app: AppLookupParams) {}

async runAsync(
ctx: Context
): Promise<
| {
resultType: SelectAndroidBuildCredentialsResultType.CREATE_REQUEST;
result: AndroidAppBuildCredentialsMetadataInput;
}
| {
resultType: SelectAndroidBuildCredentialsResultType.EXISTING_CREDENTIALS;
result: AndroidAppBuildCredentialsFragment;
}
> {
await ctx.newAndroid.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync(
this.app
);
const buildCredentialsList = await ctx.newAndroid.getAndroidAppBuildCredentialsListAsync(
this.app
);
if (buildCredentialsList.length === 0) {
return {
resultType: SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
result: {
isDefault: true,
name: await promptForNameAsync(),
},
};
}
const sortedBuildCredentialsList = sortBuildCredentials(buildCredentialsList);
const sortedBuildCredentialsChoices = sortedBuildCredentialsList.map(buildCredentials => ({
title: buildCredentials.isDefault
? `${buildCredentials.name} (Default)`
: buildCredentials.name,
value: buildCredentials,
}));

const buildCredentialsResultOrRequestToCreateNew:
| AndroidAppBuildCredentialsFragment
| SelectAndroidBuildCredentialsResultType.CREATE_REQUEST = (
await promptAsync({
type: 'select',
name: 'buildCredentialsResultOrRequestToCreateNew',
message: 'Select build credentials',
choices: [
...sortedBuildCredentialsChoices,
{
title: 'Create A New Build Credential Configuration',
value: SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
},
],
})
).buildCredentialsResultOrRequestToCreateNew;
if (
buildCredentialsResultOrRequestToCreateNew !==
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST
) {
return {
resultType: SelectAndroidBuildCredentialsResultType.EXISTING_CREDENTIALS,
result: buildCredentialsResultOrRequestToCreateNew,
};
}

const defaultCredentialsExists = buildCredentialsList.some(
buildCredentials => buildCredentials.isDefault
);
return {
resultType: SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
result: {
isDefault: !defaultCredentialsExists, // make default if there isn't one
name: await promptForNameAsync(),
},
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { promptAsync } from '../../../prompts';
import {
getNewAndroidApiMockWithoutCredentials,
testAndroidBuildCredentialsFragment,
} from '../../__tests__/fixtures-android-new';
import { createCtxMock } from '../../__tests__/fixtures-context';
import { getAppLookupParamsFromContext } from '../../android/actions/BuildCredentialsUtils';
import {
SelectAndroidBuildCredentials,
SelectAndroidBuildCredentialsResultType,
} from '../SelectAndroidBuildCredentials';

const TEST_STRING = 'TEST_STRING';
jest.mock('../../../prompts');

beforeEach(() => {
(promptAsync as jest.Mock).mockReset();
});

describe(SelectAndroidBuildCredentials, () => {
it('returns a request to make default build credentials when there are no credentials', async () => {
(promptAsync as jest.Mock).mockImplementation(() => ({
providedName: TEST_STRING,
}));
const ctx = createCtxMock({
newAndroid: {
...getNewAndroidApiMockWithoutCredentials(),
getAndroidAppBuildCredentialsListAsync: jest.fn(() => []),
},
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const selectAndroidBuildCredentialsAction = new SelectAndroidBuildCredentials(appLookupParams);
const buildCredentialsMetadataInput = await selectAndroidBuildCredentialsAction.runAsync(ctx);
expect(buildCredentialsMetadataInput).toMatchObject({
resultType: SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
result: { isDefault: true, name: TEST_STRING },
});
});
it('returns a request to make build credentials when the user chooses to make a new one', async () => {
(promptAsync as jest.Mock).mockImplementation(() => ({
buildCredentialsResultOrRequestToCreateNew:
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
providedName: TEST_STRING,
}));
const ctx = createCtxMock({
newAndroid: {
...getNewAndroidApiMockWithoutCredentials(),
getAndroidAppBuildCredentialsListAsync: jest.fn(() => [
testAndroidBuildCredentialsFragment,
]),
},
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const selectAndroidBuildCredentialsAction = new SelectAndroidBuildCredentials(appLookupParams);
const buildCredentialsMetadataInput = await selectAndroidBuildCredentialsAction.runAsync(ctx);
expect(buildCredentialsMetadataInput).toMatchObject({
resultType: SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
result: { isDefault: false, name: TEST_STRING },
});
});
it('returns a request to make default build credentials when the user chooses to make a new one, and if they have no existing credentials', async () => {
(promptAsync as jest.Mock).mockImplementation(() => ({
buildCredentialsResultOrRequestToCreateNew:
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
providedName: TEST_STRING,
}));
const ctx = createCtxMock({
newAndroid: {
...getNewAndroidApiMockWithoutCredentials(),
getAndroidAppBuildCredentialsListAsync: jest.fn(() => []),
},
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const selectAndroidBuildCredentialsAction = new SelectAndroidBuildCredentials(appLookupParams);
const buildCredentialsMetadataInput = await selectAndroidBuildCredentialsAction.runAsync(ctx);
expect(buildCredentialsMetadataInput).toMatchObject({
resultType: SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
result: { isDefault: true, name: TEST_STRING },
});
});
it('returns buildCredentials of the users choice', async () => {
(promptAsync as jest.Mock).mockImplementation(() => ({
buildCredentialsResultOrRequestToCreateNew: testAndroidBuildCredentialsFragment,
}));
const ctx = createCtxMock({
newAndroid: {
...getNewAndroidApiMockWithoutCredentials(),
getAndroidAppBuildCredentialsListAsync: jest.fn(() => [
testAndroidBuildCredentialsFragment,
]),
},
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const selectAndroidBuildCredentialsAction = new SelectAndroidBuildCredentials(appLookupParams);
const buildCredentialsMetadataInput = await selectAndroidBuildCredentialsAction.runAsync(ctx);
expect(buildCredentialsMetadataInput).toMatchObject({
resultType: SelectAndroidBuildCredentialsResultType.EXISTING_CREDENTIALS,
result: testAndroidBuildCredentialsFragment,
});
});
});