diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/check-case-sensitivity.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/check-case-sensitivity.test.ts deleted file mode 100644 index b8c81c95af8..00000000000 --- a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/check-case-sensitivity.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { isNameUnique } from '../../../../provider-utils/awscloudformation/utils/check-case-sensitivity'; -import { stateManager } from 'amplify-cli-core'; - -jest.mock('amplify-cli-core'); - -const stateManager_mock = stateManager as jest.Mocked; - -stateManager_mock.getMeta.mockReturnValue({ - api: { - testBlog: {}, - }, -}); - -test('conflict exists if names differ by case only', () => { - expect(() => isNameUnique('api', 'testblog')).toThrowErrorMatchingInlineSnapshot( - `"A resource named testBlog already exists. Amplify resource names must be unique and are case-insensitive."`, - ); -}); - -test('conflict does not exist if names differ by characters', () => { - const result = isNameUnique('api', 'newname'); - expect(result).toBe(true); -}); diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts index 0b4a49a2a7e..cc421715b5d 100644 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts @@ -1,23 +1,23 @@ -import { ApiArtifactHandler } from '../api-artifact-handler'; +import { isResourceNameUnique } from 'amplify-cli-core'; import { AddApiRequest, - ConflictResolution, AppSyncServiceConfiguration, + ConflictResolution, ResolutionStrategy, UpdateApiRequest, } from 'amplify-headless-interface'; -import path from 'path'; -import fs from 'fs-extra'; -import { category } from '../../category-constants'; -import { rootAssetDir, provider, gqlSchemaFilename, cfnParametersFilename } from './aws-constants'; +import * as fs from 'fs-extra'; import { readTransformerConfiguration, TRANSFORM_CURRENT_VERSION, writeTransformerConfiguration } from 'graphql-transformer-core'; -import { conflictResolutionToResolverConfig } from './utils/resolver-config-to-conflict-resolution-bi-di-mapper'; -import { appSyncAuthTypeToAuthConfig } from './utils/auth-config-to-app-sync-auth-type-bi-di-mapper'; -import uuid from 'uuid'; import _ from 'lodash'; -import { getAppSyncResourceName, getAppSyncAuthConfig, checkIfAuthExists, authConfigHasApiKey } from './utils/amplify-meta-utils'; +import * as path from 'path'; +import uuid from 'uuid'; +import { category } from '../../category-constants'; +import { ApiArtifactHandler } from '../api-artifact-handler'; +import { cfnParametersFilename, gqlSchemaFilename, provider, rootAssetDir } from './aws-constants'; +import { authConfigHasApiKey, checkIfAuthExists, getAppSyncAuthConfig, getAppSyncResourceName } from './utils/amplify-meta-utils'; +import { appSyncAuthTypeToAuthConfig } from './utils/auth-config-to-app-sync-auth-type-bi-di-mapper'; import { printApiKeyWarnings } from './utils/print-api-key-warnings'; -import { isNameUnique } from './utils/check-case-sensitivity'; +import { conflictResolutionToResolverConfig } from './utils/resolver-config-to-conflict-resolution-bi-di-mapper'; // keep in sync with ServiceName in amplify-category-function, but probably it will not change const FunctionServiceNameLambdaFunction = 'Lambda'; @@ -50,7 +50,7 @@ class CfnApiArtifactHandler implements ApiArtifactHandler { } const serviceConfig = request.serviceConfiguration; - isNameUnique('api', serviceConfig.apiName); + isResourceNameUnique('api', serviceConfig.apiName); const resourceDir = this.getResourceDir(serviceConfig.apiName); diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-add-resource.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-add-resource.ts index ba3089e483d..ff7e33a1890 100644 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-add-resource.ts +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-add-resource.ts @@ -1,9 +1,8 @@ -import { JSONUtilities } from 'amplify-cli-core'; +import { isResourceNameUnique, JSONUtilities } from 'amplify-cli-core'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { cfnParametersFilename, parametersFileName, rootAssetDir } from './aws-constants'; import { serviceMetadataFor } from './utils/dynamic-imports'; -import { isNameUnique } from './utils/check-case-sensitivity'; -import fs from 'fs-extra'; -import path from 'path'; -import { parametersFileName, cfnParametersFilename, rootAssetDir } from './aws-constants'; // this is the old logic for generating resources in the project directory // it is still used for adding REST APIs @@ -33,7 +32,7 @@ export const legacyAddResource = async (serviceWalkthroughPromise: Promise, const parameters = { ...answers }; const resourceDirPath = path.join(projectBackendDirPath, category, parameters.resourceName); - isNameUnique(category, parameters.resourceName); + isResourceNameUnique(category, parameters.resourceName); fs.ensureDirSync(resourceDirPath); diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.ts index 3c19b13ca16..ac3390df3e3 100644 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.ts +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.ts @@ -1,12 +1,11 @@ +import { $TSContext, exitOnNextTick, isResourceNameUnique, open, ResourceDoesNotExistError, stateManager } from 'amplify-cli-core'; +import * as fs from 'fs-extra'; import inquirer from 'inquirer'; -import path from 'path'; -import fs from 'fs-extra'; import os from 'os'; +import * as path from 'path'; import uuid from 'uuid'; import { rootAssetDir } from '../aws-constants'; -import { checkForPathOverlap, validatePathName, formatCFNPathParamsForExpressJs } from '../utils/rest-api-path-utils'; -import { ResourceDoesNotExistError, exitOnNextTick, $TSContext, stateManager, open } from 'amplify-cli-core'; -import { isNameUnique } from '../utils/check-case-sensitivity'; +import { checkForPathOverlap, formatCFNPathParamsForExpressJs, validatePathName } from '../utils/rest-api-path-utils'; // keep in sync with ServiceName in amplify-category-function, but probably it will not change const FunctionServiceNameLambdaFunction = 'Lambda'; @@ -193,9 +192,16 @@ async function askApiNames(context, defaults) { }, required: true, })(input); - const uniqueCheck = isNameUnique(category, input, false); - return typeof amplifyValidatorOutput === 'string' ? amplifyValidatorOutput : typeof uniqueCheck === 'string' ? uniqueCheck : true; + + let uniqueCheck = false; + try { + uniqueCheck = isResourceNameUnique(category, input); + } catch (e) { + return e.message || e; + } + return typeof amplifyValidatorOutput === 'string' ? amplifyValidatorOutput : uniqueCheck; }; + const answer: { apiName?: string; resourceName: string } = await inquirer.prompt([ { name: 'resourceName', diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/check-case-sensitivity.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/check-case-sensitivity.ts deleted file mode 100644 index 4bbf478a689..00000000000 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/check-case-sensitivity.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { stateManager } from 'amplify-cli-core'; - -export const isNameUnique = (category: string, resourceName: string, throwOnMatch = true) => { - const resourceNames = Object.keys(stateManager.getMeta()?.[category] || {}); - const matchIdx = resourceNames.map(name => name.toLowerCase()).indexOf(resourceName.toLowerCase()); - if (matchIdx === -1) { - return true; - } - const msg = `A resource named ${resourceNames[matchIdx]} already exists. Amplify resource names must be unique and are case-insensitive.`; - if (throwOnMatch) { - throw new Error(msg); - } else { - return msg; - } -}; diff --git a/packages/amplify-cli-core/src/__tests__/utils/isResourceNameUnique.test.ts b/packages/amplify-cli-core/src/__tests__/utils/isResourceNameUnique.test.ts new file mode 100644 index 00000000000..91446026a58 --- /dev/null +++ b/packages/amplify-cli-core/src/__tests__/utils/isResourceNameUnique.test.ts @@ -0,0 +1,23 @@ +import { isResourceNameUnique } from '../../utils'; +import { stateManager } from '../../state-manager'; + +jest.mock('../../state-manager'); + +const stateManager_mock = stateManager as jest.Mocked; + +stateManager_mock.getMeta.mockReturnValue({ + api: { + testBlog: {}, + }, +}); + +test('conflict exists if names differ by case only', () => { + expect(() => isResourceNameUnique('api', 'testblog')).toThrowErrorMatchingInlineSnapshot( + `"A resource named 'testBlog' already exists. Amplify resource names must be unique and are case-insensitive."`, + ); +}); + +test('conflict does not exist if names differ by characters', () => { + const result = isResourceNameUnique('api', 'newname'); + expect(result).toBe(true); +}); diff --git a/packages/amplify-cli-core/src/utils/index.ts b/packages/amplify-cli-core/src/utils/index.ts index 2cfef28c3f5..21adb00e95e 100644 --- a/packages/amplify-cli-core/src/utils/index.ts +++ b/packages/amplify-cli-core/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './fileSize'; +export * from './isResourceNameUnique'; export * from './open'; export * from './packageManager'; export * from './recursiveOmit'; diff --git a/packages/amplify-cli-core/src/utils/isResourceNameUnique.ts b/packages/amplify-cli-core/src/utils/isResourceNameUnique.ts new file mode 100644 index 00000000000..750ef54ba67 --- /dev/null +++ b/packages/amplify-cli-core/src/utils/isResourceNameUnique.ts @@ -0,0 +1,17 @@ +import { stateManager } from '../state-manager'; + +export const isResourceNameUnique = (category: string, resourceName: string, throwOnMatch = true) => { + const meta = stateManager.getMeta(); + const resourceNames = Object.keys(meta?.[category] || {}); + const matchIdx = resourceNames.map(name => name.toLowerCase()).indexOf(resourceName.toLowerCase()); + if (matchIdx === -1) { + return true; + } + + if (throwOnMatch) { + const msg = `A resource named '${resourceNames[matchIdx]}' already exists. Amplify resource names must be unique and are case-insensitive.`; + throw new Error(msg); + } else { + return false; + } +};