diff --git a/.changeset/seven-chefs-trade.md b/.changeset/seven-chefs-trade.md new file mode 100644 index 0000000000..9e36e91d41 --- /dev/null +++ b/.changeset/seven-chefs-trade.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/sandbox': patch +--- + +Print bootstrap url when browser cannot be opened diff --git a/packages/sandbox/src/file_watching_sandbox.test.ts b/packages/sandbox/src/file_watching_sandbox.test.ts index 390d917426..68e410a56f 100644 --- a/packages/sandbox/src/file_watching_sandbox.test.ts +++ b/packages/sandbox/src/file_watching_sandbox.test.ts @@ -187,6 +187,53 @@ void describe('Sandbox to check if region is bootstrapped', () => { openMock.mock.calls[0].arguments[0], getBootstrapUrl(region) ); + assert.strictEqual(printer.log.mock.callCount(), 1); + assert.strictEqual( + printer.log.mock.calls[0].arguments[0], + 'The given region has not been bootstrapped. Sign in to console as a Root user or Admin to complete the bootstrap process, then restart the sandbox.' + ); + assert.strictEqual(printer.log.mock.calls[0].arguments[1], undefined); + }); + + void it('when region has not bootstrapped, and opening console url fails prints url to initiate bootstrap', async () => { + ssmClientSendMock.mock.mockImplementationOnce(() => { + throw new ParameterNotFound({ + $metadata: {}, + message: 'Parameter not found', + }); + }); + + openMock.mock.mockImplementationOnce(() => + Promise.reject(new Error('open error')) + ); + + await sandboxInstance.start({ + dir: 'testDir', + exclude: ['exclude1', 'exclude2'], + }); + + assert.strictEqual(ssmClientSendMock.mock.callCount(), 1); + assert.strictEqual(openMock.mock.callCount(), 1); + assert.strictEqual( + openMock.mock.calls[0].arguments[0], + getBootstrapUrl(region) + ); + assert.strictEqual(printer.log.mock.callCount(), 3); + assert.strictEqual( + printer.log.mock.calls[0].arguments[0], + 'The given region has not been bootstrapped. Sign in to console as a Root user or Admin to complete the bootstrap process, then restart the sandbox.' + ); + assert.strictEqual(printer.log.mock.calls[0].arguments[1], undefined); + assert.strictEqual( + printer.log.mock.calls[1].arguments[0], + 'Unable to open bootstrap url, open error' + ); + assert.strictEqual(printer.log.mock.calls[1].arguments[1], LogLevel.DEBUG); + assert.strictEqual( + printer.log.mock.calls[2].arguments[0], + `Open ${getBootstrapUrl(region)} in the browser.` + ); + assert.strictEqual(printer.log.mock.calls[2].arguments[1], undefined); }); void it('when user does not have proper credentials throw user error', async () => { diff --git a/packages/sandbox/src/file_watching_sandbox.ts b/packages/sandbox/src/file_watching_sandbox.ts index 62e9a5cb94..aa1bb1c6a1 100644 --- a/packages/sandbox/src/file_watching_sandbox.ts +++ b/packages/sandbox/src/file_watching_sandbox.ts @@ -37,6 +37,7 @@ import { BackendIdentifierConversions, } from '@aws-amplify/platform-core'; import { LambdaFunctionLogStreamer } from './lambda_function_log_streamer.js'; + /** * CDK stores bootstrap version in parameter store. Example parameter name looks like /cdk-bootstrap//version. * The default value for qualifier is hnb659fds, i.e. default parameter path is /cdk-bootstrap/hnb659fds/version. @@ -125,7 +126,23 @@ export class FileWatchingSandbox extends EventEmitter implements Sandbox { ); // get region from an available sdk client; const region = await this.ssmClient.config.region(); - await this.open(getBootstrapUrl(region)); + const bootstrapUrl = getBootstrapUrl(region); + try { + await this.open(bootstrapUrl); + } catch (e) { + // If opening the link fails for any reason we fall back to + // printing the url in the console. + // This might happen: + // - in headless environments + // - if user does not have any app to open URL + // - if browser crashes + let logEntry = 'Unable to open bootstrap url'; + if (e instanceof Error) { + logEntry = `${logEntry}, ${e.message}`; + } + this.printer.log(logEntry, LogLevel.DEBUG); + this.printer.log(`Open ${bootstrapUrl} in the browser.`); + } return; }