diff --git a/.changeset/ninety-coins-jog.md b/.changeset/ninety-coins-jog.md new file mode 100644 index 0000000000..462f5f2b66 --- /dev/null +++ b/.changeset/ninety-coins-jog.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-cli': patch +--- + +Added error mapping for app name not available in the region diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts index 004d94a267..e09c76036f 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.test.ts @@ -1,7 +1,10 @@ import { beforeEach, describe, it, mock } from 'node:test'; import { GenerateGraphqlClientCodeCommand } from './generate_graphql_client_code_command.js'; import yargs, { CommandModule } from 'yargs'; -import { TestCommandRunner } from '../../../test-utils/command_runner.js'; +import { + TestCommandError, + TestCommandRunner, +} from '../../../test-utils/command_runner.js'; import assert from 'node:assert'; import { BackendIdentifier } from '@aws-amplify/plugin-types'; import path from 'path'; @@ -18,6 +21,10 @@ import { BackendIdentifierResolverWithFallback } from '../../../backend-identifi import { S3Client } from '@aws-sdk/client-s3'; import { AmplifyClient } from '@aws-sdk/client-amplify'; import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { + BackendOutputClientError, + BackendOutputClientErrorType, +} from '@aws-amplify/deployed-backend-client'; void describe('generate graphql-client-code command', () => { const generateApiCodeAdapter = new GenerateApiCodeAdapter({ @@ -355,4 +362,45 @@ void describe('generate graphql-client-code command', () => { ); assert.match(output, /Arguments .* are mutually exclusive/); }); + + void it('throws user error when NO_APP_FOUND_ERROR occurs', async () => { + mock.method(generateApiCodeAdapter, 'invokeGenerateApiCode', () => { + throw new BackendOutputClientError( + BackendOutputClientErrorType.NO_APP_FOUND_ERROR, + 'No app found for stack stack_name' + ); + }); + + await assert.rejects( + () => + commandRunner.runCommand( + 'graphql-client-code --app-id test-app --branch main' + ), + (error: TestCommandError) => { + assert.strictEqual(error.error.name, 'AmplifyAppNotFoundError'); + assert.strictEqual( + error.error.message, + 'No app found for stack stack_name' + ); + return true; + } + ); + }); + + void it('re-throw other types of errors', async () => { + const originalError = new Error('Some other error'); + mock.method(generateApiCodeAdapter, 'invokeGenerateApiCode', () => { + throw originalError; + }); + await assert.rejects( + () => + commandRunner.runCommand( + 'graphql-client-code --app-id test-app --branch main' + ), + (error: TestCommandError) => { + assert.strictEqual(error.error, originalError); + return true; + } + ); + }); }); diff --git a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts index ec8e4a6ed7..d4b7268a4c 100644 --- a/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts +++ b/packages/cli/src/commands/generate/graphql-client-code/generate_graphql_client_code_command.ts @@ -15,6 +15,11 @@ import { GenerateModelsOptions, } from '@aws-amplify/model-generator'; import { ArgumentsKebabCase } from '../../../kebab_case.js'; +import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { + BackendOutputClientError, + BackendOutputClientErrorType, +} from '@aws-amplify/deployed-backend-client'; type GenerateOptions = | GenerateGraphqlCodegenOptions @@ -89,20 +94,38 @@ export class GenerateGraphqlClientCodeCommand handler = async ( args: ArgumentsCamelCase ): Promise => { - const backendIdentifier = - await this.backendIdentifierResolver.resolveDeployedBackendIdentifier( - args - ); - const out = this.getOutDir(args); - const format = args.format ?? GenerateApiCodeFormat.GRAPHQL_CODEGEN; - const formatParams = this.formatParamBuilders[format](args); + try { + const backendIdentifier = + await this.backendIdentifierResolver.resolveDeployedBackendIdentifier( + args + ); + const out = this.getOutDir(args); + const format = args.format ?? GenerateApiCodeFormat.GRAPHQL_CODEGEN; + const formatParams = this.formatParamBuilders[format](args); - const result = await this.generateApiCodeAdapter.invokeGenerateApiCode({ - ...backendIdentifier, - ...formatParams, - } as unknown as InvokeGenerateApiCodeProps); + const result = await this.generateApiCodeAdapter.invokeGenerateApiCode({ + ...backendIdentifier, + ...formatParams, + } as unknown as InvokeGenerateApiCodeProps); - await result.writeToDirectory(out); + await result.writeToDirectory(out); + } catch (error) { + if ( + error instanceof BackendOutputClientError && + error.code === BackendOutputClientErrorType.NO_APP_FOUND_ERROR + ) { + throw new AmplifyUserError( + 'AmplifyAppNotFoundError', + { + message: error.message, + resolution: `Ensure that an Amplify app exists in the region.`, + }, + error + ); + } + // Re-throw any other errors + throw error; + } }; /** diff --git a/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts b/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts index 84b4a0f8e6..462f077312 100644 --- a/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts +++ b/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts @@ -2,7 +2,10 @@ import { beforeEach, describe, it, mock } from 'node:test'; import { GenerateOutputsCommand } from './generate_outputs_command.js'; import { ClientConfigFormat } from '@aws-amplify/client-config'; import yargs, { CommandModule } from 'yargs'; -import { TestCommandRunner } from '../../../test-utils/command_runner.js'; +import { + TestCommandError, + TestCommandRunner, +} from '../../../test-utils/command_runner.js'; import assert from 'node:assert'; import { AppBackendIdentifierResolver } from '../../../backend-identifier/backend_identifier_resolver.js'; import { ClientConfigGeneratorAdapter } from '../../../client-config/client_config_generator_adapter.js'; @@ -11,6 +14,10 @@ import { SandboxBackendIdResolver } from '../../sandbox/sandbox_id_resolver.js'; import { S3Client } from '@aws-sdk/client-s3'; import { AmplifyClient } from '@aws-sdk/client-amplify'; import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { + BackendOutputClientError, + BackendOutputClientErrorType, +} from '@aws-amplify/deployed-backend-client'; void describe('generate outputs command', () => { const clientConfigGeneratorAdapter = new ClientConfigGeneratorAdapter({ @@ -248,4 +255,47 @@ void describe('generate outputs command', () => { ); assert.match(output, /Arguments .* mutually exclusive/); }); + + void it('throws user error when NO_APP_FOUND_ERROR occurs', async () => { + // Mock the generator to throw NO_APP_FOUND_ERROR + mock.method( + clientConfigGeneratorAdapter, + 'generateClientConfigToFile', + () => { + throw new BackendOutputClientError( + BackendOutputClientErrorType.NO_APP_FOUND_ERROR, + 'No Amplify app found in the specified region' + ); + } + ); + + await assert.rejects( + () => commandRunner.runCommand('outputs --app-id test-app --branch main'), + (error: TestCommandError) => { + assert.strictEqual(error.error.name, 'AmplifyAppNotFoundError'); + assert.strictEqual( + error.error.message, + 'No Amplify app found in the specified region' + ); + return true; + } + ); + }); + void it('re-throw other types of errors', async () => { + const originalError = new Error('Some other error'); + mock.method( + clientConfigGeneratorAdapter, + 'generateClientConfigToFile', + () => { + throw originalError; + } + ); + await assert.rejects( + () => commandRunner.runCommand('outputs --app-id test-app --branch main'), + (error: TestCommandError) => { + assert.strictEqual(error.error, originalError); + return true; + } + ); + }); }); diff --git a/packages/cli/src/commands/generate/outputs/generate_outputs_command.ts b/packages/cli/src/commands/generate/outputs/generate_outputs_command.ts index 0edb427a00..0c9b258866 100644 --- a/packages/cli/src/commands/generate/outputs/generate_outputs_command.ts +++ b/packages/cli/src/commands/generate/outputs/generate_outputs_command.ts @@ -9,6 +9,10 @@ import { BackendIdentifierResolver } from '../../../backend-identifier/backend_i import { ClientConfigGeneratorAdapter } from '../../../client-config/client_config_generator_adapter.js'; import { ArgumentsKebabCase } from '../../../kebab_case.js'; import { AmplifyUserError } from '@aws-amplify/platform-core'; +import { + BackendOutputClientError, + BackendOutputClientErrorType, +} from '@aws-amplify/deployed-backend-client'; export type GenerateOutputsCommandOptions = ArgumentsKebabCase; @@ -55,25 +59,43 @@ export class GenerateOutputsCommand handler = async ( args: ArgumentsCamelCase ): Promise => { - const backendIdentifier = - await this.backendIdentifierResolver.resolveDeployedBackendIdentifier( - args - ); + try { + const backendIdentifier = + await this.backendIdentifierResolver.resolveDeployedBackendIdentifier( + args + ); - if (!backendIdentifier) { - throw new AmplifyUserError('BackendIdentifierResolverError', { - message: 'Could not resolve the backend identifier.', - resolution: - 'Ensure stack name or Amplify App ID and branch specified are correct and exists, then re-run this command.', - }); - } + if (!backendIdentifier) { + throw new AmplifyUserError('BackendIdentifierResolverError', { + message: 'Could not resolve the backend identifier.', + resolution: + 'Ensure stack name or Amplify App ID and branch specified are correct and exists, then re-run this command.', + }); + } - await this.clientConfigGenerator.generateClientConfigToFile( - backendIdentifier, - args.outputsVersion as ClientConfigVersion, - args.outDir, - args.format - ); + await this.clientConfigGenerator.generateClientConfigToFile( + backendIdentifier, + args.outputsVersion as ClientConfigVersion, + args.outDir, + args.format + ); + } catch (error) { + if ( + error instanceof BackendOutputClientError && + error.code === BackendOutputClientErrorType.NO_APP_FOUND_ERROR + ) { + throw new AmplifyUserError( + 'AmplifyAppNotFoundError', + { + message: error.message, + resolution: `Ensure that an Amplify app exists in the region.`, + }, + error + ); + } + // Re-throw any other errors + throw error; + } }; /** diff --git a/packages/deployed-backend-client/API.md b/packages/deployed-backend-client/API.md index c0dfc59472..5864811b91 100644 --- a/packages/deployed-backend-client/API.md +++ b/packages/deployed-backend-client/API.md @@ -102,6 +102,8 @@ export enum BackendOutputClientErrorType { // (undocumented) METADATA_RETRIEVAL_ERROR = "MetadataRetrievalError", // (undocumented) + NO_APP_FOUND_ERROR = "NoAppFoundError", + // (undocumented) NO_OUTPUTS_FOUND = "NoOutputsFound", // (undocumented) NO_STACK_FOUND = "NoStackFound" diff --git a/packages/deployed-backend-client/src/backend_output_client_factory.ts b/packages/deployed-backend-client/src/backend_output_client_factory.ts index 2dca755d66..a6cc4c3b46 100644 --- a/packages/deployed-backend-client/src/backend_output_client_factory.ts +++ b/packages/deployed-backend-client/src/backend_output_client_factory.ts @@ -13,6 +13,7 @@ export enum BackendOutputClientErrorType { NO_STACK_FOUND = 'NoStackFound', CREDENTIALS_ERROR = 'CredentialsError', ACCESS_DENIED = 'AccessDenied', + NO_APP_FOUND_ERROR = 'NoAppFoundError', } /** * Error type for BackendOutputClientError diff --git a/packages/deployed-backend-client/src/stack-name-resolvers/app_name_and_branch_main_stack_name_resolver.ts b/packages/deployed-backend-client/src/stack-name-resolvers/app_name_and_branch_main_stack_name_resolver.ts index 5e5ee7318d..41672b28c2 100644 --- a/packages/deployed-backend-client/src/stack-name-resolvers/app_name_and_branch_main_stack_name_resolver.ts +++ b/packages/deployed-backend-client/src/stack-name-resolvers/app_name_and_branch_main_stack_name_resolver.ts @@ -1,6 +1,10 @@ import { MainStackNameResolver } from '@aws-amplify/plugin-types'; import { AmplifyClient, ListAppsCommand } from '@aws-sdk/client-amplify'; import { BackendIdentifierConversions } from '@aws-amplify/platform-core'; +import { + BackendOutputClientError, + BackendOutputClientErrorType, +} from '../backend_output_client_factory.js'; /** * Tuple of Amplify App name and branch @@ -38,7 +42,8 @@ export class AppNameAndBranchMainStackNameResolver ); const region = await this.amplifyClient.config.region(); if (appMatches.length === 0) { - throw new Error( + throw new BackendOutputClientError( + BackendOutputClientErrorType.NO_APP_FOUND_ERROR, `No apps found with name ${this.appNameAndBranch.appName} in region ${region}` ); } else if (appMatches.length >= 2) {