Compare commits

...
4 Commits
Author SHA1 Message Date
Test User 175fc2b30e v0.9.1 2021-04-09 14:43:36 -07:00
Quinlan Jung aec9347d69 [eas-cli] fix validation check of provisioning profile (#318)
* [eas-cli] fix validation check of provisioning profile

* changelog and stuff

* quin not dominik

* move the changelog thing
2021-04-09 13:05:25 -07:00
Quinlan Jung 91500d07c0 [eas-cli] replace SetupProvisioningProfile (#317)
* [eas-cli] replace SetupProvisioningProfile

* changelog

* more changelog
2021-04-09 13:02:12 -07:00
Test User 62ca14b504 Commit README updates 2021-04-09 10:13:28 -07:00
9 changed files with 54 additions and 326 deletions

No files matched your search

+3
View File
@@ -16,9 +16,12 @@ This is the log of notable changes to EAS CLI and related packages.
- Fix the issue where the first iOS build fails for a project without `ios.bundleIdentifier` set in `app.json`. ([#319](https://github.com/expo/eas-cli/pull/319) by [@dsokal](https://github.com/dsokal))
- Bump @expo/config-plugins to fix `eas build:configure` on Windows
- Fix provisioning profile validation to actually check status. ([#318](https://github.com/expo/eas-cli/pull/318) by [@quinlanj](https://github.com/quinlanj))
### 🧹 Chores
- Replace Credentials REST API calls with GraphQL counterparts, also improved DX. ([#293](https://github.com/expo/eas-cli/pull/293), [#299](https://github.com/expo/eas-cli/pull/299), [#301](https://github.com/expo/eas-cli/pull/301), [#317](https://github.com/expo/eas-cli/pull/317) by [@quinlanj](https://github.com/quinlanj))
## [0.8.1](https://github.com/expo/eas-cli/releases/tag/v0.8.1) - 2021-04-06
### 🐛 Bug fixes
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.9.0"
"version": "0.9.1"
}
+32 -12
View File
@@ -69,7 +69,7 @@ ALIASES
$ eas login
```
_See code: [build/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/account/login.ts)_
_See code: [build/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/account/login.ts)_
## `eas account:logout`
@@ -83,7 +83,7 @@ ALIASES
$ eas logout
```
_See code: [build/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/account/logout.ts)_
_See code: [build/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/account/logout.ts)_
## `eas account:view`
@@ -97,7 +97,7 @@ ALIASES
$ eas whoami
```
_See code: [build/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/account/view.ts)_
_See code: [build/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -108,6 +108,8 @@ USAGE
$ eas analytics [STATUS]
```
_See code: [build/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/analytics.ts)_
## `eas build`
Start a build
@@ -125,7 +127,7 @@ OPTIONS
--[no-]wait Wait for build(s) to complete
```
_See code: [build/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/build/index.ts)_
_See code: [build/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -136,6 +138,8 @@ USAGE
$ eas build:cancel [BUILD_ID]
```
_See code: [build/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/cancel.ts)_
## `eas build:configure`
Configure the project to support EAS Build.
@@ -149,7 +153,7 @@ OPTIONS
--allow-experimental Enable experimental configuration steps.
```
_See code: [build/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/build/configure.ts)_
_See code: [build/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/configure.ts)_
## `eas build:list`
@@ -165,7 +169,7 @@ OPTIONS
--status=(in-queue|in-progress|errored|finished|canceled)
```
_See code: [build/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/build/list.ts)_
_See code: [build/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/list.ts)_
## `eas build:view [BUILD_ID]`
@@ -176,7 +180,7 @@ USAGE
$ eas build:view [BUILD_ID]
```
_See code: [build/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/build/view.ts)_
_See code: [build/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/build/view.ts)_
## `eas credentials`
@@ -187,7 +191,7 @@ USAGE
$ eas credentials
```
_See code: [build/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/credentials.ts)_
_See code: [build/commands/credentials.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/credentials.ts)_
## `eas device:create`
@@ -198,7 +202,7 @@ USAGE
$ eas device:create
```
_See code: [build/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/device/create.ts)_
_See code: [build/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/device/create.ts)_
## `eas device:list`
@@ -212,7 +216,7 @@ OPTIONS
--apple-team-id=apple-team-id
```
_See code: [build/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/device/list.ts)_
_See code: [build/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/device/list.ts)_
## `eas device:view [UDID]`
@@ -223,7 +227,7 @@ USAGE
$ eas device:view [UDID]
```
_See code: [build/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/device/view.ts)_
_See code: [build/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/device/view.ts)_
## `eas help [COMMAND]`
@@ -256,6 +260,8 @@ OPTIONS
--value=value Value of the secret
```
_See code: [build/commands/secrets/create.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/secrets/create.ts)_
## `eas secrets:delete [ID]`
Delete an environment secret by ID.
@@ -271,6 +277,8 @@ DESCRIPTION
Unsure where to find the secret's ID? Run eas secrets:list
```
_See code: [build/commands/secrets/delete.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/secrets/delete.ts)_
## `eas secrets:list`
Lists environment secrets available for your current app
@@ -280,6 +288,8 @@ USAGE
$ eas secrets:list
```
_See code: [build/commands/secrets/list.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/secrets/list.ts)_
## `eas submit --platform=(android|ios)`
Submits build artifact to app store
@@ -357,7 +367,7 @@ EXAMPLES
and provide its App ID
```
_See code: [build/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.9.0/build/commands/submit.ts)_
_See code: [build/commands/submit.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/submit.ts)_
## `eas webhook:create`
@@ -376,6 +386,8 @@ OPTIONS
--url=url Webhook URL
```
_See code: [build/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
Delete a webhook on the current project.
@@ -388,6 +400,8 @@ ARGUMENTS
ID ID of the webhook to delete
```
_See code: [build/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/delete.ts)_
## `eas webhook:list`
List webhooks on the current project.
@@ -400,6 +414,8 @@ OPTIONS
--event=(BUILD) Event type that triggers the webhook
```
_See code: [build/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/list.ts)_
## `eas webhook:update`
Create a webhook on the current project.
@@ -418,6 +434,8 @@ OPTIONS
--url=url Webhook URL
```
_See code: [build/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/update.ts)_
## `eas webhook:view ID`
View a webhook on the current project.
@@ -429,4 +447,6 @@ USAGE
ARGUMENTS
ID ID of the webhook to view
```
_See code: [build/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v0.9.1/build/commands/webhook/view.ts)_
<!-- commandsstop -->
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "0.9.0",
"version": "0.9.1",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
@@ -1,45 +0,0 @@
import assert from 'assert';
import Log from '../../../log';
import { Action, CredentialsManager } from '../../CredentialsManager';
import { Context } from '../../context';
import { askForUserProvidedAsync } from '../../utils/promptForCredentials';
import { ProvisioningProfile } from '../appstore/Credentials.types';
import { AppLookupParams, provisioningProfileSchema } from '../credentials';
import { displayIosAppCredentials } from '../utils/printCredentials';
import { generateProvisioningProfileAsync } from './ProvisioningProfileUtils';
export class CreateProvisioningProfileStandaloneManager implements Action {
constructor(private app: AppLookupParams) {}
async runAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
await manager.runActionAsync(new CreateProvisioningProfile(this.app));
const appCredentials = await ctx.ios.getAppCredentialsAsync(this.app);
displayIosAppCredentials(appCredentials);
Log.newLine();
}
}
export class CreateProvisioningProfile implements Action {
constructor(private app: AppLookupParams) {}
async runAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
const provisioningProfile = await this.provideOrGenerateAsync(ctx);
await ctx.ios.updateProvisioningProfileAsync(this.app, provisioningProfile);
Log.succeed('Created provisioning profile');
}
private async provideOrGenerateAsync(ctx: Context): Promise<ProvisioningProfile> {
const userProvided = await askForUserProvidedAsync(provisioningProfileSchema);
if (userProvided) {
// userProvided profiles don't come with ProvisioningProfileId's (only accessible from Apple Portal API)
Log.warn('Provisioning profile: Unable to validate specified profile.');
return userProvided;
}
const distCert = await ctx.ios.getDistributionCertificateAsync(this.app);
assert(distCert, 'missing distribution certificate');
return await generateProvisioningProfileAsync(ctx, this.app.bundleIdentifier, distCert);
}
}
@@ -12,8 +12,8 @@ import { isCredentialsMap, readIosCredentialsAsync } from '../../credentialsJson
import { AppLookupParams, IosAppCredentials, IosDistCredentials } from '../credentials';
import { displayProjectCredentials } from '../utils/printCredentials';
import { readAppleTeam } from '../utils/provisioningProfile';
import { SetupProvisioningProfile } from './SetupProvisioningProfile';
import { SetupAdhocProvisioningProfile } from './new/SetupAdhocProvisioningProfile';
import { SetupProvisioningProfile } from './new/SetupProvisioningProfile';
type AppCredentialsAndDistCert = {
appCredentials: IosAppCredentials;
@@ -32,11 +32,11 @@ export class SetupBuildCredentials implements Action {
let iosAppBuildCredentials: IosAppBuildCredentialsFragment | null = null;
try {
const account = findAccountByName(ctx.user.accounts, this.app.accountName);
if (!account) {
throw new Error(`You do not have access to the ${this.app.accountName} account`);
}
if (this.distribution === 'internal') {
const account = findAccountByName(ctx.user.accounts, this.app.accountName);
if (!account) {
throw new Error(`You do not have access to the ${this.app.accountName} account`);
}
const action = new SetupAdhocProvisioningProfile({
account,
projectName: this.app.projectName,
@@ -45,7 +45,12 @@ export class SetupBuildCredentials implements Action {
await manager.runActionAsync(action);
iosAppBuildCredentials = action.iosAppBuildCredentials;
} else {
await manager.runActionAsync(new SetupProvisioningProfile(this.app));
const setupProvisioningProfileAction = new SetupProvisioningProfile({
account,
projectName: this.app.projectName,
bundleIdentifier: this.app.bundleIdentifier,
});
await setupProvisioningProfileAction.runAsync(manager, ctx);
}
} catch (error) {
Log.error('Failed to setup credentials.');
@@ -1,109 +0,0 @@
import Log from '../../../log';
import { confirmAsync, promptAsync } from '../../../prompts';
import { Action, CredentialsManager } from '../../CredentialsManager';
import { Context } from '../../context';
import { AppLookupParams } from '../credentials';
import { validateDistributionCertificateAsync } from '../validators/validateDistributionCertificate';
import { CreateDistributionCertificate } from './CreateDistributionCertificate';
import {
formatDistributionCertificate,
getValidDistCertsAsync,
} from './DistributionCertificateUtils';
import {
UseExistingDistributionCertificate,
UseSpecificDistributionCertificate,
} from './UseDistributionCertificate';
export class SetupDistributionCertificateForApp implements Action {
constructor(private app: AppLookupParams) {}
public async runAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
if (await this.isCurrentCertificateValidAsync(ctx)) {
return;
}
if (ctx.nonInteractive) {
throw new Error(
'Distribution Certificate is not configured correctly. Please run this command again in interactive mode.'
);
}
const existingCertificates = await getValidDistCertsAsync(ctx, this.app.accountName);
if (existingCertificates.length === 0) {
await this.createNewCertificateAsync(manager, ctx);
return;
}
// autoselect credentials if we find valid certs
const autoselectedCertificate = existingCertificates[0];
const credentials = await ctx.ios.getAllCredentialsAsync(this.app.accountName);
const usedByApps = credentials.appCredentials.filter(
cred => cred.distCredentialsId === autoselectedCertificate.id
);
const confirm = await confirmAsync({
message: `${formatDistributionCertificate(
autoselectedCertificate,
usedByApps,
ctx.appStore.authCtx ? 'VALID' : 'UNKNOWN'
)} \n Would you like to use this certificate?`,
});
if (!confirm) {
await this.createOrReuseAsync(manager, ctx);
return;
}
Log.log(`Using Distribution Certificate: ${autoselectedCertificate.certId || '-----'}`);
await manager.runActionAsync(
new UseSpecificDistributionCertificate(this.app, autoselectedCertificate.id)
);
}
private async isCurrentCertificateValidAsync(ctx: Context): Promise<boolean> {
const currentCertificate = await ctx.ios.getDistributionCertificateAsync(this.app);
if (!currentCertificate) {
return false;
}
if (ctx.appStore.authCtx) {
const isValid = await validateDistributionCertificateAsync(ctx, currentCertificate);
if (!isValid) {
Log.warn("Current Distribution Certificate is no longer valid on Apple's server");
}
return isValid;
}
return true;
}
private async createNewCertificateAsync(
manager: CredentialsManager,
ctx: Context
): Promise<void> {
const action = new CreateDistributionCertificate(this.app.accountName);
await manager.runActionAsync(action);
await manager.runActionAsync(
new UseSpecificDistributionCertificate(this.app, action.distCredentials.id)
);
}
private async createOrReuseAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
const { action } = await promptAsync({
type: 'select',
name: 'action',
message: 'Select an iOS Distribution Certificate to use for code signing:',
choices: [
{
title: '[Choose existing certificate] (Recommended)',
value: 'CHOOSE_EXISTING',
},
{ title: '[Add a new certificate]', value: 'GENERATE' },
],
});
if (action === 'GENERATE') {
await this.createNewCertificateAsync(manager, ctx);
} else if (action === 'CHOOSE_EXISTING') {
await manager.runActionAsync(new UseExistingDistributionCertificate(this.app));
}
}
}
@@ -1,152 +0,0 @@
import assert from 'assert';
import pick from 'lodash/pick';
import Log from '../../../log';
import { confirmAsync, promptAsync } from '../../../prompts';
import { Action, CredentialsManager } from '../../CredentialsManager';
import { Context } from '../../context';
import { ProvisioningProfileStoreInfo } from '../appstore/Credentials.types';
import { AppLookupParams, IosDistCredentials } from '../credentials';
import {
validateProvisioningProfileAsync,
validateProvisioningProfileWithoutApple,
} from '../validators/validateProvisioningProfile';
import {
ConfigureProvisioningProfile,
UseExistingProvisioningProfile,
} from './ConfigureProvisioningProfile';
import { CreateProvisioningProfile } from './CreateProvisioningProfile';
import { formatProvisioningProfileFromApple } from './ProvisioningProfileUtils';
import { SetupDistributionCertificateForApp } from './SetupDistributionCertificate';
export class SetupProvisioningProfile implements Action {
constructor(private app: AppLookupParams) {}
async runAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
await manager.runActionAsync(new SetupDistributionCertificateForApp(this.app));
if (await this.isCurrentProfileValidAsync(ctx)) {
return;
}
if (ctx.nonInteractive) {
throw new Error(
'Provisioning profile is not configured correctly. Please run this command again in interactive mode.'
);
}
if (!ctx.appStore.authCtx) {
await manager.runActionAsync(new CreateProvisioningProfile(this.app));
return;
}
const existingProfiles = await ctx.appStore.listProvisioningProfilesAsync(
this.app.bundleIdentifier
);
if (existingProfiles.length === 0) {
await manager.runActionAsync(new CreateProvisioningProfile(this.app));
return;
}
const distCert = await ctx.ios.getDistributionCertificateAsync(this.app);
assert(distCert, 'missing distribution certificate');
const autoselectedProfile = this.choosePreferred(existingProfiles, distCert);
// autoselect credentials if we find valid certs
if (!ctx.nonInteractive) {
const confirm = await confirmAsync({
message: `${formatProvisioningProfileFromApple(
autoselectedProfile
)} \n Would you like to use this profile?`,
});
if (!confirm) {
await this.createOrReuseAsync(manager, ctx);
return;
}
} else {
Log.log(`Using provisioning profile: ${autoselectedProfile.provisioningProfileId}`);
}
await ctx.ios.updateProvisioningProfileAsync(
this.app,
pick(autoselectedProfile, [
'provisioningProfile',
'provisioningProfileId',
'teamId',
'teamName',
])
);
await manager.runActionAsync(new ConfigureProvisioningProfile(this.app));
}
private async isCurrentProfileValidAsync(ctx: Context): Promise<boolean> {
const currentProfile = await ctx.ios.getProvisioningProfileAsync(this.app);
const currentCertificate = await ctx.ios.getDistributionCertificateAsync(this.app);
if (!currentProfile || !currentCertificate) {
return false;
}
if (!ctx.appStore.authCtx || !currentProfile.provisioningProfileId) {
if (!currentProfile.provisioningProfileId) {
Log.warn(
"The provisioning profile we have on file cannot be validated on Apple's servers."
);
}
const validationResult = validateProvisioningProfileWithoutApple(
currentProfile,
currentCertificate,
this.app.bundleIdentifier
);
if (!validationResult.ok) {
Log.warn(validationResult.error);
return false;
}
return true;
}
const validationResult = await validateProvisioningProfileAsync(
ctx,
currentProfile,
currentCertificate,
this.app.bundleIdentifier
);
if (!validationResult.ok) {
Log.warn(validationResult.error);
return false;
}
return true;
}
private choosePreferred(
profiles: ProvisioningProfileStoreInfo[],
distCert: IosDistCredentials
): ProvisioningProfileStoreInfo {
// prefer the profile that already has the same dist cert associated with it
const profileWithSameCert = profiles.find(profile =>
profile.certificates.some(cert => cert.id === distCert.certId)
);
// if not, just get an arbitrary profile
return profileWithSameCert || profiles[0];
}
private async createOrReuseAsync(manager: CredentialsManager, ctx: Context): Promise<void> {
const { action } = await promptAsync({
type: 'select',
name: 'action',
message: 'Select a provisioning profile:',
choices: [
{
title: '[Choose existing provisioning profile] (Recommended)',
value: 'CHOOSE_EXISTING',
},
{ title: '[Add a new provisioning profile]', value: 'GENERATE' },
],
});
if (action === 'GENERATE') {
await manager.runActionAsync(new CreateProvisioningProfile(this.app));
} else if (action === 'CHOOSE_EXISTING') {
await manager.runActionAsync(new UseExistingProvisioningProfile(this.app));
}
}
}
@@ -58,6 +58,12 @@ async function validateProvisioningProfileWithAppleAsync(
ok: false,
};
}
if (configuredProfileFromApple.status !== 'ACTIVE') {
return {
error: `Provisioning profile (id: ${profile.provisioningProfileId}) is no longer valid`,
ok: false,
};
}
return { ok: true };
}