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 upload/download credentials.json to www #455

Merged
merged 3 commits into from
Jun 15, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -2,13 +2,26 @@ import Log from '../../../log';
import { Action, CredentialsManager } from '../../CredentialsManager';
import { Context } from '../../context';
import { updateAndroidCredentialsAsync } from '../../credentialsJson/update';
import { AndroidAppCredentialsQuery } from '../api/graphql/queries/AndroidAppCredentialsQuery';

export class UpdateCredentialsJson implements Action {
constructor(private projectFullName: string) {}

async runAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
const legacyAppCredentials = await AndroidAppCredentialsQuery.withCommonFieldsByApplicationIdentifierAsync(
this.projectFullName,
{
legacyOnly: true,
}
);
const legacyBuildCredentials = legacyAppCredentials?.androidAppBuildCredentialsList[0] ?? null;
if (!legacyBuildCredentials) {
Log.log('You dont have any Expo Classic Android credentials configured at this time');
quinlanj marked this conversation as resolved.
Show resolved Hide resolved
return;
}
Log.log('Updating Android credentials in credentials.json');
await updateAndroidCredentialsAsync(ctx);

await updateAndroidCredentialsAsync(ctx, legacyBuildCredentials);
Log.succeed(
'Android part of your local credentials.json is synced with values stored on EAS servers.'
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
AndroidAppBuildCredentialsFragment,
AndroidKeystoreType,
} from '../../../../graphql/generated';
import Log from '../../../../log';
import { confirmAsync } from '../../../../prompts';
import { Context } from '../../../context';
import { readAndroidCredentialsAsync } from '../../../credentialsJson/read';
import {
SelectAndroidBuildCredentials,
SelectAndroidBuildCredentialsResultType,
} from '../../../manager/SelectAndroidBuildCredentials';
import { AppLookupParams } from '../../api/GraphqlClient';
import { getKeystoreWithType } from '../../utils/keystoreNew';
import { BackupKeystore } from './DownloadKeystore';

export class SetupBuildCredentialsFromCredentialsJson {
constructor(private app: AppLookupParams) {}

async runAsync(ctx: Context): Promise<AndroidAppBuildCredentialsFragment | null> {
if (ctx.nonInteractive) {
throw new Error(
'Setting up build credentials from credentials.json is only available in interactive mode'
);
}

let localCredentials;
try {
localCredentials = await readAndroidCredentialsAsync(ctx.projectDir);
} catch (error) {
Log.error(
'Reading credentials from credentials.json failed. Make sure this file is correct and all credentials are present there.'
);
throw error;
}

const selectBuildCredentialsResult = await new SelectAndroidBuildCredentials(this.app).runAsync(
ctx
);
if (
selectBuildCredentialsResult.resultType ===
SelectAndroidBuildCredentialsResultType.EXISTING_CREDENTIALS &&
selectBuildCredentialsResult.result.androidKeystore
) {
const buildCredentials = selectBuildCredentialsResult.result;
const confirmOverwrite = await confirmAsync({
message: `The build configuration ${buildCredentials.name} already has a keystore configured. Overwrite?`,
});
if (!confirmOverwrite) {
return null;
}
await new BackupKeystore(this.app).runAsync(ctx, buildCredentials);
}

const providedKeystoreWithType = getKeystoreWithType(localCredentials.keystore);
if (providedKeystoreWithType.type === AndroidKeystoreType.Unknown) {
const confirmKeystoreIsSketchy = await confirmAsync({
message: `The keystore you provided could not be parsed and may be corrupt. Proceed anyways?`,
quinlanj marked this conversation as resolved.
Show resolved Hide resolved
});
if (!confirmKeystoreIsSketchy) {
return null;
}
}
const keystoreFragment = await ctx.newAndroid.createKeystoreAsync(
this.app.account,
providedKeystoreWithType
);
let buildCredentials: AndroidAppBuildCredentialsFragment;
if (
selectBuildCredentialsResult.resultType ===
SelectAndroidBuildCredentialsResultType.CREATE_REQUEST
) {
buildCredentials = await ctx.newAndroid.createAndroidAppBuildCredentialsAsync(this.app, {
...selectBuildCredentialsResult.result,
androidKeystoreId: keystoreFragment.id,
});
} else {
buildCredentials = await ctx.newAndroid.updateAndroidAppBuildCredentialsAsync(
selectBuildCredentialsResult.result,
{
androidKeystoreId: keystoreFragment.id,
}
);
}
Log.succeed('Keystore updated');
return buildCredentials;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AndroidAppBuildCredentialsFragment } from '../../../../graphql/generated';
import Log from '../../../../log';
import { Context } from '../../../context';
import { updateAndroidCredentialsAsync } from '../../../credentialsJson/update';

export class UpdateCredentialsJson {
async runAsync(
ctx: Context,
buildCredentials: AndroidAppBuildCredentialsFragment
): Promise<void> {
Log.log('Updating Android credentials in credentials.json');
await updateAndroidCredentialsAsync(ctx, buildCredentials);
Log.succeed(
'Android part of your local credentials.json is synced with values stored on EAS servers.'
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { vol } from 'memfs';

import { AndroidKeystoreType } from '../../../../../graphql/generated';
import { confirmAsync } from '../../../../../prompts';
import {
getNewAndroidApiMockWithoutCredentials,
testAndroidBuildCredentialsFragment,
testJksAndroidKeystoreFragment,
} from '../../../../__tests__/fixtures-android-new';
import { createCtxMock } from '../../../../__tests__/fixtures-context';
import {
SelectAndroidBuildCredentials,
SelectAndroidBuildCredentialsResultType,
} from '../../../../manager/SelectAndroidBuildCredentials';
import { getAppLookupParamsFromContext } from '../../BuildCredentialsUtils';
import { SetupBuildCredentialsFromCredentialsJson } from '../SetupBuildCredentialsFromCredentialsJson';

jest.mock('fs');

jest.mock('../../../../../prompts');
(confirmAsync as jest.Mock).mockImplementation(() => true);
jest.mock('../../../../manager/SelectAndroidBuildCredentials');

const originalConsoleLog = console.log;
const originalConsoleWarn = console.warn;
beforeAll(() => {
console.log = jest.fn();
console.warn = jest.fn();
});
afterAll(() => {
console.log = originalConsoleLog;
console.warn = originalConsoleWarn;
});
beforeEach(() => {
vol.reset();
});

describe(SetupBuildCredentialsFromCredentialsJson, () => {
it('sets up a new build configuration from credentials.json upon user request', async () => {
(SelectAndroidBuildCredentials as any).mockImplementation(() => {
return {
runAsync: () => {
return {
resultType: SelectAndroidBuildCredentialsResultType.CREATE_REQUEST,
result: { isDefault: true, name: 'test configuration' },
};
},
};
});
const ctx = createCtxMock({
nonInteractive: false,
newAndroid: {
...getNewAndroidApiMockWithoutCredentials(),
createKeystoreAsync: jest.fn(() => testJksAndroidKeystoreFragment),
},
});
vol.fromJSON({
'./credentials.json': JSON.stringify({
android: {
keystore: {
keystorePath: 'keystore.jks',
keystorePassword: testJksAndroidKeystoreFragment.keystorePassword,
keyAlias: testJksAndroidKeystoreFragment.keyAlias,
keyPassword: testJksAndroidKeystoreFragment.keyPassword,
},
},
}),
'keystore.jks': 'some-binary-content',
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
appLookupParams
);
await setupBuildCredentialsAction.runAsync(ctx);

// expect keystore to be created with credentials.json content
expect(ctx.newAndroid.createKeystoreAsync as any).toHaveBeenCalledTimes(1);
expect(ctx.newAndroid.createKeystoreAsync as any).toBeCalledWith(appLookupParams.account, {
keystorePassword: testJksAndroidKeystoreFragment.keystorePassword,
keyAlias: testJksAndroidKeystoreFragment.keyAlias,
keyPassword: testJksAndroidKeystoreFragment.keyPassword,
keystore: Buffer.from('some-binary-content').toString('base64'),
type: AndroidKeystoreType.Unknown,
});

// expect new build credentials to be created
expect(ctx.newAndroid.createAndroidAppBuildCredentialsAsync as any).toHaveBeenCalledTimes(1);
});
it('uses an existing build configuration upon user request', async () => {
(SelectAndroidBuildCredentials as any).mockImplementation(() => {
return {
runAsync: () => {
return {
resultType: SelectAndroidBuildCredentialsResultType.EXISTING_CREDENTIALS,
result: testAndroidBuildCredentialsFragment,
};
},
};
});
const ctx = createCtxMock({
nonInteractive: false,
newAndroid: {
...getNewAndroidApiMockWithoutCredentials(),
createKeystoreAsync: jest.fn(() => testJksAndroidKeystoreFragment),
},
});
vol.fromJSON({
'./credentials.json': JSON.stringify({
android: {
keystore: {
keystorePath: 'keystore.jks',
keystorePassword: testJksAndroidKeystoreFragment.keystorePassword,
keyAlias: testJksAndroidKeystoreFragment.keyAlias,
keyPassword: testJksAndroidKeystoreFragment.keyPassword,
},
},
}),
'keystore.jks': 'some-binary-content',
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
appLookupParams
);
await setupBuildCredentialsAction.runAsync(ctx);

// expect keystore to be created with credentials.json content
expect(ctx.newAndroid.createKeystoreAsync as any).toHaveBeenCalledTimes(1);
expect(ctx.newAndroid.createKeystoreAsync as any).toBeCalledWith(appLookupParams.account, {
keystorePassword: testJksAndroidKeystoreFragment.keystorePassword,
keyAlias: testJksAndroidKeystoreFragment.keyAlias,
keyPassword: testJksAndroidKeystoreFragment.keyPassword,
keystore: Buffer.from('some-binary-content').toString('base64'),
type: AndroidKeystoreType.Unknown,
});

// expect existing build credentials to be updated
expect(ctx.newAndroid.updateAndroidAppBuildCredentialsAsync as any).toHaveBeenCalledTimes(1);
});
it('errors in Non-Interactive Mode', async () => {
const ctx = createCtxMock({
nonInteractive: true,
});
const appLookupParams = getAppLookupParamsFromContext(ctx);
const setupBuildCredentialsAction = new SetupBuildCredentialsFromCredentialsJson(
appLookupParams
);
await expect(setupBuildCredentialsAction.runAsync(ctx)).rejects.toThrowError();
});
});
Loading