diff --git a/packages/expo-cli/src/commands/build/ios/IOSBuilder.ts b/packages/expo-cli/src/commands/build/ios/IOSBuilder.ts index 59461b9142..07d8d2ce47 100644 --- a/packages/expo-cli/src/commands/build/ios/IOSBuilder.ts +++ b/packages/expo-cli/src/commands/build/ios/IOSBuilder.ts @@ -4,6 +4,7 @@ import get from 'lodash/get'; import { XDLError } from '@expo/xdl'; import { Dictionary } from 'lodash'; +import terminalLink from 'terminal-link'; import BaseBuilder from '../BaseBuilder'; import { PLATFORMS } from '../constants'; import * as utils from '../utils'; @@ -16,7 +17,7 @@ import { displayProjectCredentials } from '../../../credentials/actions/list'; import { SetupIosDist } from '../../../credentials/views/SetupIosDist'; import { SetupIosPush } from '../../../credentials/views/SetupIosPush'; import { SetupIosProvisioningProfile } from '../../../credentials/views/SetupIosProvisioningProfile'; -import CommandError from '../../../CommandError'; +import CommandError, { ErrorCodes } from '../../../CommandError'; import log from '../../../log'; import { @@ -122,16 +123,38 @@ See https://docs.expo.io/versions/latest/distribution/building-standalone-apps/# } await this.produceCredentials(context, experienceName, bundleIdentifier); } catch (e) { - log( - chalk.bold.red( - 'Failed to prepare all credentials. \nThe next time you build, we will automatically use the following configuration:' - ) - ); - throw e; - } finally { - const credentials = await context.ios.getAllCredentials(); - displayProjectCredentials(experienceName, bundleIdentifier, credentials); + if (e.code === ErrorCodes.NON_INTERACTIVE) { + log.newLine(); + const link = terminalLink( + 'expo.fyi/credentials-non-interactive', + 'https://expo.fyi/credentials-non-interactive' + ); + log( + chalk.bold.red( + `Additional information needed to setup credentials in non-interactive mode.` + ) + ); + log(chalk.bold.red(`Learn more about how to resolve this: ${link}.`)); + log.newLine(); + + // We don't want to display project credentials when we bail out due to + // non-interactive mode error, because we are unable to recover without + // user input. + throw new CommandError( + ErrorCodes.NON_INTERACTIVE, + 'Unable to proceed, see the above error message.' + ); + } else { + log( + chalk.bold.red( + 'Failed to prepare all credentials. \nThe next time you build, we will automatically use the following configuration:' + ) + ); + } } + + const credentials = await context.ios.getAllCredentials(); + displayProjectCredentials(experienceName, bundleIdentifier, credentials); } async produceCredentials(ctx: Context, experienceName: string, bundleIdentifier: string) { diff --git a/packages/expo-cli/src/credentials/route.ts b/packages/expo-cli/src/credentials/route.ts index 158766ac5b..308586acc2 100644 --- a/packages/expo-cli/src/credentials/route.ts +++ b/packages/expo-cli/src/credentials/route.ts @@ -1,15 +1,15 @@ import log from '../log'; import { Context, IView } from './context'; -import { IQuit, QuitError, askQuit, doQuit } from './views/Select'; +import { AskQuit, DoQuit, IQuit, QuitError } from './views/Select'; export async function runCredentialsManagerStandalone(ctx: Context, startView: IView) { - const manager = new CredentialsManager(ctx, startView, askQuit); + const manager = new CredentialsManager(ctx, startView, new AskQuit()); await manager.run(); } export async function runCredentialsManager(ctx: Context, startView: IView): Promise { - const manager = new CredentialsManager(ctx, startView, doQuit); + const manager = new CredentialsManager(ctx, startView, new DoQuit()); return await manager.run(); } @@ -39,14 +39,22 @@ export class CredentialsManager { while (true) { try { this._currentView = - (await this._currentView.open(this._ctx)) || (await this._quit(this._mainView)); + (await this._currentView.open(this._ctx)) || (await this._quit.runAsync(this._mainView)); } catch (error) { + // View quit normally, exit normally if (error instanceof QuitError) { return null; + } + + // View encountered error + if (this._quit instanceof DoQuit) { + // propagate error up + throw error; } else { + // fallback to interactive Quit View log(error); await new Promise(res => setTimeout(res, 1000)); - this._currentView = await this._quit(this._mainView); + this._currentView = await this._quit.runAsync(this._mainView); } } } diff --git a/packages/expo-cli/src/credentials/views/Select.ts b/packages/expo-cli/src/credentials/views/Select.ts index d54c7da58d..26f5e0685b 100644 --- a/packages/expo-cli/src/credentials/views/Select.ts +++ b/packages/expo-cli/src/credentials/views/Select.ts @@ -170,26 +170,32 @@ export class QuitError extends Error { } } -export type IQuit = (view: IView) => Promise; +export interface IQuit { + runAsync(mainpage: IView): Promise; +} -export async function doQuit(mainpage: IView): Promise { - throw new QuitError(); +export class DoQuit implements IQuit { + async runAsync(mainpage: IView): Promise { + throw new QuitError(); + } } -export async function askQuit(mainpage: IView): Promise { - const { selected } = await prompt([ - { - type: 'list', - name: 'selected', - message: 'Do you want to quit Credential Manager', - choices: [ - { value: 'exit', name: 'Quit Credential Manager' }, - { value: 'mainpage', name: 'Go back to experience overview.' }, - ], - }, - ]); - if (selected === 'exit') { - process.exit(0); +export class AskQuit implements IQuit { + async runAsync(mainpage: IView): Promise { + const { selected } = await prompt([ + { + type: 'list', + name: 'selected', + message: 'Do you want to quit Credential Manager', + choices: [ + { value: 'exit', name: 'Quit Credential Manager' }, + { value: 'mainpage', name: 'Go back to experience overview.' }, + ], + }, + ]); + if (selected === 'exit') { + process.exit(0); + } + return mainpage; } - return mainpage; }