From 742aa94eda274268c81f7bfcafe2359be9e35089 Mon Sep 17 00:00:00 2001 From: Albert Winberg Date: Fri, 30 Jun 2023 05:52:13 +0000 Subject: [PATCH] feat: generate components using graphql --- package.json | 2 +- .../amplify-appsync-simulator/package.json | 2 +- packages/amplify-category-auth/package.json | 2 +- .../amplify-category-function/package.json | 2 +- packages/amplify-category-geo/package.json | 2 +- .../package.json | 2 +- .../amplify-category-predictions/package.json | 2 +- .../amplify-category-storage/package.json | 2 +- packages/amplify-cli/package.json | 4 +- .../package.json | 2 +- .../amplify-dynamodb-simulator/package.json | 2 +- packages/amplify-e2e-core/package.json | 2 +- packages/amplify-e2e-tests/package.json | 2 +- .../package.json | 2 +- .../amplify-opensearch-simulator/package.json | 2 +- .../package.json | 4 +- .../amplify-storage-simulator/package.json | 2 +- packages/amplify-util-import/package.json | 2 +- packages/amplify-util-mock/package.json | 4 +- packages/amplify-util-uibuilder/package.json | 4 +- .../src/__tests__/generateComponents.test.ts | 247 +++++++++++------- .../src/clients/amplify-studio-client.ts | 3 + .../src/commands/generateComponents.ts | 70 ++++- .../types/amplify-codegen.d.ts | 12 + yarn.lock | 92 +++---- 25 files changed, 307 insertions(+), 165 deletions(-) create mode 100644 packages/amplify-util-uibuilder/types/amplify-codegen.d.ts diff --git a/package.json b/package.json index 3310d56f365..66a9a7cf501 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ }, "packageManager": "yarn@3.5.0", "resolutions": { - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "cross-fetch": "^2.2.6", "glob-parent": "^6.0.2", "got": "^11.8.5", diff --git a/packages/amplify-appsync-simulator/package.json b/packages/amplify-appsync-simulator/package.json index a9a01295423..3d6e2c0e294 100644 --- a/packages/amplify-appsync-simulator/package.json +++ b/packages/amplify-appsync-simulator/package.json @@ -35,7 +35,7 @@ "@graphql-tools/schema": "^8.3.1", "@graphql-tools/utils": "^8.5.1", "amplify-velocity-template": "1.4.12", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "chalk": "^4.1.1", "cors": "^2.8.5", "dataloader": "^2.0.0", diff --git a/packages/amplify-category-auth/package.json b/packages/amplify-category-auth/package.json index 548960bea50..9ea11806922 100644 --- a/packages/amplify-category-auth/package.json +++ b/packages/amplify-category-auth/package.json @@ -37,7 +37,7 @@ "amplify-headless-interface": "1.17.4", "amplify-util-headless-input": "1.9.13", "aws-cdk-lib": "~2.80.0", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "axios": "^0.26.0", "chalk": "^4.1.1", "change-case": "^4.1.1", diff --git a/packages/amplify-category-function/package.json b/packages/amplify-category-function/package.json index 95b09832c1c..6c4c45e074c 100644 --- a/packages/amplify-category-function/package.json +++ b/packages/amplify-category-function/package.json @@ -31,7 +31,7 @@ "@aws-amplify/amplify-function-plugin-interface": "1.11.0", "@aws-amplify/amplify-prompts": "2.8.0", "archiver": "^5.3.0", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "chalk": "^4.1.1", "cloudform-types": "^4.2.0", "enquirer": "^2.3.6", diff --git a/packages/amplify-category-geo/package.json b/packages/amplify-category-geo/package.json index 8d60734eaea..51031a65bbf 100644 --- a/packages/amplify-category-geo/package.json +++ b/packages/amplify-category-geo/package.json @@ -32,7 +32,7 @@ "amplify-headless-interface": "1.17.4", "amplify-util-headless-input": "1.9.13", "aws-cdk-lib": "~2.80.0", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "constructs": "^10.0.5", "fs-extra": "^8.1.0", "lodash": "^4.17.21", diff --git a/packages/amplify-category-notifications/package.json b/packages/amplify-category-notifications/package.json index 8397c85fb0b..0fc5ef34bc6 100644 --- a/packages/amplify-category-notifications/package.json +++ b/packages/amplify-category-notifications/package.json @@ -30,7 +30,7 @@ "@aws-amplify/amplify-environment-parameters": "1.7.3", "@aws-amplify/amplify-prompts": "2.8.0", "@aws-amplify/amplify-provider-awscloudformation": "8.3.3", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "chalk": "^4.1.1", "fs-extra": "^8.1.0", "lodash": "^4.17.21", diff --git a/packages/amplify-category-predictions/package.json b/packages/amplify-category-predictions/package.json index 927018c4f4a..5fa2c93e5d1 100644 --- a/packages/amplify-category-predictions/package.json +++ b/packages/amplify-category-predictions/package.json @@ -27,7 +27,7 @@ "dependencies": { "@aws-amplify/amplify-cli-core": "4.2.3", "@aws-amplify/amplify-prompts": "2.8.0", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "chalk": "^4.1.1", "fs-extra": "^8.1.0", "uuid": "^8.3.2" diff --git a/packages/amplify-category-storage/package.json b/packages/amplify-category-storage/package.json index b61a21f0cc8..344a793d94d 100644 --- a/packages/amplify-category-storage/package.json +++ b/packages/amplify-category-storage/package.json @@ -35,7 +35,7 @@ "amplify-headless-interface": "1.17.4", "amplify-util-headless-input": "1.9.13", "aws-cdk-lib": "~2.80.0", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "chalk": "^4.1.1", "constructs": "^10.0.5", "enquirer": "^2.3.6", diff --git a/packages/amplify-cli/package.json b/packages/amplify-cli/package.json index 5df348b9bfc..fbbd2447b78 100644 --- a/packages/amplify-cli/package.json +++ b/packages/amplify-cli/package.json @@ -66,7 +66,7 @@ "@aws-amplify/amplify-util-mock": "5.4.3", "@aws-amplify/amplify-util-uibuilder": "1.9.3", "@aws-cdk/cloudformation-diff": "~2.68.0", - "amplify-codegen": "^4.1.4", + "amplify-codegen": "^4.1.5", "amplify-dotnet-function-runtime-provider": "2.0.8", "amplify-go-function-runtime-provider": "2.3.26", "amplify-java-function-runtime-provider": "2.3.26", @@ -74,7 +74,7 @@ "amplify-nodejs-function-runtime-provider": "2.5.3", "amplify-python-function-runtime-provider": "2.4.26", "aws-cdk-lib": "~2.80.0", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "chalk": "^4.1.1", "ci-info": "^3.8.0", "cli-table3": "^0.6.0", diff --git a/packages/amplify-console-integration-tests/package.json b/packages/amplify-console-integration-tests/package.json index b46c2af79bc..2dece8a57dd 100644 --- a/packages/amplify-console-integration-tests/package.json +++ b/packages/amplify-console-integration-tests/package.json @@ -24,7 +24,7 @@ "@aws-amplify/amplify-cli-core": "4.2.3", "@aws-amplify/amplify-e2e-core": "5.1.3", "@types/ini": "^1.3.30", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "dotenv": "^8.2.0", "execa": "^5.1.1", "fs-extra": "^8.1.0", diff --git a/packages/amplify-dynamodb-simulator/package.json b/packages/amplify-dynamodb-simulator/package.json index d6f8bca6923..96490c1ed1a 100644 --- a/packages/amplify-dynamodb-simulator/package.json +++ b/packages/amplify-dynamodb-simulator/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@aws-amplify/amplify-cli-core": "4.2.3", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "detect-port": "^1.3.0", "execa": "^5.1.1", "fs-extra": "^8.1.0", diff --git a/packages/amplify-e2e-core/package.json b/packages/amplify-e2e-core/package.json index 7be041e8a32..87cb1a0f42f 100644 --- a/packages/amplify-e2e-core/package.json +++ b/packages/amplify-e2e-core/package.json @@ -26,7 +26,7 @@ "amplify-headless-interface": "1.17.4", "aws-amplify": "^4.2.8", "aws-appsync": "^4.1.1", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "chalk": "^4.1.1", "dotenv": "^8.2.0", "execa": "^5.1.1", diff --git a/packages/amplify-e2e-tests/package.json b/packages/amplify-e2e-tests/package.json index 5fb3a86b3c9..3f46f4e7011 100644 --- a/packages/amplify-e2e-tests/package.json +++ b/packages/amplify-e2e-tests/package.json @@ -40,7 +40,7 @@ "aws-amplify": "^4.2.8", "aws-appsync": "^4.1.1", "aws-cdk-lib": "~2.80.0", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "axios": "^0.26.0", "circleci-api": "^4.1.4", "constructs": "^10.0.5", diff --git a/packages/amplify-environment-parameters/package.json b/packages/amplify-environment-parameters/package.json index 90520de53d2..3c254d14276 100644 --- a/packages/amplify-environment-parameters/package.json +++ b/packages/amplify-environment-parameters/package.json @@ -32,7 +32,7 @@ "lodash": "^4.17.21" }, "devDependencies": { - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "mkdirp": "^1.0.4", "ts-json-schema-generator": "~1.1.2" }, diff --git a/packages/amplify-opensearch-simulator/package.json b/packages/amplify-opensearch-simulator/package.json index e729e7fa57f..8fe17fee1c5 100644 --- a/packages/amplify-opensearch-simulator/package.json +++ b/packages/amplify-opensearch-simulator/package.json @@ -27,7 +27,7 @@ "dependencies": { "@aws-amplify/amplify-cli-core": "4.2.3", "@aws-amplify/amplify-prompts": "2.8.0", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "detect-port": "^1.3.0", "execa": "^5.1.1", "fs-extra": "^8.1.0", diff --git a/packages/amplify-provider-awscloudformation/package.json b/packages/amplify-provider-awscloudformation/package.json index 54304f02ed3..dd1318e7313 100644 --- a/packages/amplify-provider-awscloudformation/package.json +++ b/packages/amplify-provider-awscloudformation/package.json @@ -37,10 +37,10 @@ "@aws-amplify/cli-extensibility-helper": "3.0.13", "@aws-amplify/graphql-transformer-core": "^1.4.0", "@aws-amplify/graphql-transformer-interfaces": "^2.3.0", - "amplify-codegen": "^4.1.4", + "amplify-codegen": "^4.1.5", "archiver": "^5.3.0", "aws-cdk-lib": "~2.80.0", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "bottleneck": "2.19.5", "chalk": "^4.1.1", "cloudform-types": "^4.2.0", diff --git a/packages/amplify-storage-simulator/package.json b/packages/amplify-storage-simulator/package.json index 06e043c42e3..8af60b5609f 100644 --- a/packages/amplify-storage-simulator/package.json +++ b/packages/amplify-storage-simulator/package.json @@ -44,7 +44,7 @@ "@types/serve-static": "^1.13.3", "@types/uuid": "^8.3.1", "@types/xml": "^1.0.4", - "aws-sdk": "^2.1405.0" + "aws-sdk": "^2.1426.0" }, "berry": { "plugins": [ diff --git a/packages/amplify-util-import/package.json b/packages/amplify-util-import/package.json index abff2c5e9b8..937b66c75a4 100644 --- a/packages/amplify-util-import/package.json +++ b/packages/amplify-util-import/package.json @@ -20,7 +20,7 @@ "author": "Amazon Web Services", "license": "Apache-2.0", "dependencies": { - "aws-sdk": "^2.1405.0" + "aws-sdk": "^2.1426.0" }, "devDependencies": { "@types/node": "^12.12.6" diff --git a/packages/amplify-util-mock/package.json b/packages/amplify-util-mock/package.json index 1e76a1f9c54..34ac5cba8b2 100644 --- a/packages/amplify-util-mock/package.json +++ b/packages/amplify-util-mock/package.json @@ -39,7 +39,7 @@ "@aws-amplify/amplify-prompts": "2.8.0", "@aws-amplify/amplify-provider-awscloudformation": "8.3.3", "@hapi/topo": "^5.0.0", - "amplify-codegen": "^4.1.4", + "amplify-codegen": "^4.1.5", "amplify-dynamodb-simulator": "2.8.3", "amplify-storage-simulator": "1.10.0", "chokidar": "^3.5.3", @@ -76,7 +76,7 @@ "@types/which": "^1.3.2", "amplify-nodejs-function-runtime-provider": "2.5.3", "aws-appsync": "^4.1.4", - "aws-sdk": "^2.1405.0", + "aws-sdk": "^2.1426.0", "aws-sdk-mock": "^5.8.0", "axios": "^0.26.0", "graphql": "^15.5.0", diff --git a/packages/amplify-util-uibuilder/package.json b/packages/amplify-util-uibuilder/package.json index 3b336d19df5..c8fe7525ce1 100644 --- a/packages/amplify-util-uibuilder/package.json +++ b/packages/amplify-util-uibuilder/package.json @@ -19,8 +19,8 @@ "@aws-amplify/amplify-prompts": "2.8.0", "@aws-amplify/codegen-ui": "2.14.2", "@aws-amplify/codegen-ui-react": "2.14.2", - "amplify-codegen": "^4.1.4", - "aws-sdk": "^2.1405.0", + "amplify-codegen": "^4.1.5", + "aws-sdk": "^2.1426.0", "fs-extra": "^8.1.0", "node-fetch": "^2.6.7", "ora": "^4.0.3", diff --git a/packages/amplify-util-uibuilder/src/__tests__/generateComponents.test.ts b/packages/amplify-util-uibuilder/src/__tests__/generateComponents.test.ts index 69d937987c4..779fb70cff4 100644 --- a/packages/amplify-util-uibuilder/src/__tests__/generateComponents.test.ts +++ b/packages/amplify-util-uibuilder/src/__tests__/generateComponents.test.ts @@ -3,6 +3,7 @@ import * as utils from '../commands/utils'; import { run } from '../commands/generateComponents'; import { isDataStoreEnabled } from '@aws-amplify/amplify-category-api'; import { getTransformerVersion } from '../commands/utils/featureFlags'; +import { getCodegenConfig } from 'amplify-codegen'; jest.mock('../commands/utils'); jest.mock('@aws-amplify/amplify-cli-core'); @@ -14,11 +15,17 @@ jest.mock('../commands/utils/featureFlags', () => ({ ...jest.requireActual('../commands/utils/featureFlags'), getTransformerVersion: jest.fn(), })); +jest.mock('amplify-codegen', () => ({ + ...jest.requireActual('amplify-codegen'), + getCodegenConfig: jest.fn(), +})); + const awsMock = aws as any; const utilsMock = utils as any; +const isDataStoreEnabledMocked = isDataStoreEnabled as any; +const getTransformerVersionMocked = getTransformerVersion as any; +const getCodegenConfigMocked = getCodegenConfig as any; -const isDataStoreEnabledMocked = jest.mocked(isDataStoreEnabled); -const getTransformerVersionMocked = jest.mocked(getTransformerVersion); utilsMock.shouldRenderComponents = jest.fn().mockReturnValue(true); utilsMock.notifyMissingPackages = jest.fn().mockReturnValue(true); utilsMock.getAmplifyDataSchema = jest.fn().mockReturnValue({}); @@ -32,18 +39,23 @@ jest.mock('../commands/utils/featureFlags', () => ({ getTransformerVersion: jest.fn().mockReturnValue(2), })); +const defaultStudioFeatureFlags = { + autoGenerateForms: 'true', + autoGenerateViews: 'true', + isRelationshipSupported: 'false', + isNonModelSupported: 'false', + isGraphQLEnabled: 'true', +}; + +const projectPath = '/usr/test/test-project'; + describe('can generate components', () => { let context: any; let schemas: any; let mockedExport: jest.Mock; const getMetadataPromise = jest.fn().mockReturnValue({ features: { - autoGenerateForms: 'true', - autoGenerateViews: 'true', - formFeatureFlags: { - isRelationshipSupported: 'false', - isNonModelSupported: 'false', - }, + ...defaultStudioFeatureFlags, }, }); const startCodegenJobPromise = jest.fn().mockReturnValue({ @@ -53,6 +65,7 @@ describe('can generate components', () => { promise: startCodegenJobPromise, }); beforeEach(() => { + jest.clearAllMocks(); isDataStoreEnabledMocked.mockResolvedValue(true); getTransformerVersionMocked.mockResolvedValue(2); context = { @@ -65,6 +78,11 @@ describe('can generate components', () => { envName: 'testEnvName', }, }, + exeInfo: { + localEnvInfo: { + projectPath, + }, + }, }; schemas = { entities: [ @@ -81,6 +99,15 @@ describe('can generate components', () => { }, ], }; + + getCodegenConfigMocked.mockReturnValue({ + getGeneratedTypesPath: jest.fn().mockReturnValue(undefined), + getGeneratedQueriesPath: jest.fn().mockReturnValue(projectPath + '/src/graphql/queries.js'), + getGeneratedMutationsPath: jest.fn().mockReturnValue(projectPath + '/src/graphql/mutations.js'), + getGeneratedSubscriptionsPath: jest.fn().mockReturnValue(projectPath + '/src/graphql/subscriptions.js'), + getGeneratedFragmentsPath: jest.fn().mockReturnValue(projectPath + '/src/graphql/fragments.js'), + }); + mockedExport = jest.fn().mockReturnValue({ entities: schemas.entities, }); @@ -105,6 +132,7 @@ describe('can generate components', () => { promise: jest.fn().mockReturnValue({ status: 'succeeded' }), }), }); + utilsMock.getUiBuilderComponentsPath = jest.fn().mockReturnValue(projectPath + '/src/ui-components'); utilsMock.generateUiBuilderComponents = jest.fn().mockReturnValue(schemas.entities); utilsMock.generateUiBuilderThemes = jest.fn().mockReturnValue(schemas.entities); utilsMock.generateUiBuilderForms = jest.fn().mockReturnValue(schemas.entities); @@ -130,34 +158,14 @@ describe('can generate components', () => { getTransformerVersionMocked.mockResolvedValue(2); getMetadataPromise.mockReturnValue({ features: { - autoGenerateForms: 'true', - autoGenerateViews: 'true', - formFeatureFlags: { - isRelationshipSupported: 'false', - isNonModelSupported: 'false', - }, + ...defaultStudioFeatureFlags, }, }); await run(context, 'PostPull'); expect(mockStartCodegenJob).toHaveBeenCalledWith({ appId: 'testAppId', environmentName: 'testEnvName', - codegenJobToCreate: { - renderConfig: { - react: { - module: 'es2020', - target: 'es2020', - script: 'jsx', - renderTypeDeclarations: true, - }, - }, - genericDataSchema: undefined, - autoGenerateForms: true, - features: { - isNonModelSupported: false, - isRelationshipSupported: false, - }, - }, + codegenJobToCreate: expect.objectContaining({ autoGenerateForms: true }), }); }); @@ -166,104 +174,163 @@ describe('can generate components', () => { getTransformerVersionMocked.mockResolvedValue(1); getMetadataPromise.mockReturnValue({ features: { - autoGenerateForms: 'true', - autoGenerateViews: 'true', - formFeatureFlags: { - isRelationshipSupported: 'false', - isNonModelSupported: 'false', - }, + ...defaultStudioFeatureFlags, }, }); await run(context, 'PostPull'); expect(mockStartCodegenJob).toHaveBeenCalledWith({ appId: 'testAppId', environmentName: 'testEnvName', - codegenJobToCreate: { - renderConfig: { - react: { - module: 'es2020', - target: 'es2020', - script: 'jsx', - renderTypeDeclarations: true, - }, - }, - genericDataSchema: undefined, - autoGenerateForms: false, - features: { - isNonModelSupported: false, - isRelationshipSupported: false, - }, + codegenJobToCreate: expect.objectContaining({ autoGenerateForms: false }), + }); + }); + + it('should not autogenerate forms if datastore is not enabled and GraphQL is not enabled', async () => { + isDataStoreEnabledMocked.mockResolvedValue(false); + getMetadataPromise.mockReturnValue({ + features: { + ...defaultStudioFeatureFlags, + isGraphQLEnabled: 'false', }, }); + await run(context, 'PostPull'); + expect(mockStartCodegenJob).toHaveBeenCalledWith({ + appId: 'testAppId', + environmentName: 'testEnvName', + codegenJobToCreate: expect.objectContaining({ autoGenerateForms: false }), + }); }); - it('should not autogenerate forms if datastore is not enabled', async () => { + it('should not autogenerate forms if datastore is not enabled and GraphQL is enabled with invalid config', async () => { isDataStoreEnabledMocked.mockResolvedValue(false); getMetadataPromise.mockReturnValue({ features: { - autoGenerateForms: 'true', - autoGenerateViews: 'true', - formFeatureFlags: { - isRelationshipSupported: 'false', - isNonModelSupported: 'false', - }, + ...defaultStudioFeatureFlags, + isGraphQLEnabled: 'true', }, }); + getCodegenConfigMocked.mockImplementation(() => { + throw new Error(); + }); await run(context, 'PostPull'); expect(mockStartCodegenJob).toHaveBeenCalledWith({ appId: 'testAppId', environmentName: 'testEnvName', - codegenJobToCreate: { - renderConfig: { - react: { - module: 'es2020', - target: 'es2020', - script: 'jsx', - renderTypeDeclarations: true, - }, - }, - genericDataSchema: undefined, - autoGenerateForms: false, - features: { - isNonModelSupported: false, - isRelationshipSupported: false, - }, + codegenJobToCreate: expect.objectContaining({ autoGenerateForms: false }), + }); + }); + + it('should autogenerate forms if datastore is not enabled and GraphQL is enabled with valid config', async () => { + isDataStoreEnabledMocked.mockResolvedValue(false); + getMetadataPromise.mockReturnValue({ + features: { + ...defaultStudioFeatureFlags, + isGraphQLEnabled: 'true', }, }); + await run(context, 'PostPull'); + expect(mockStartCodegenJob).toHaveBeenCalledWith({ + appId: 'testAppId', + environmentName: 'testEnvName', + codegenJobToCreate: expect.objectContaining({ autoGenerateForms: true }), + }); }); it('should not autogenerate forms if feature flag is not enabled', async () => { isDataStoreEnabledMocked.mockResolvedValue(true); getMetadataPromise.mockReturnValue({ features: { + ...defaultStudioFeatureFlags, autoGenerateForms: 'false', - autoGenerateViews: 'true', - formFeatureFlags: { - isRelationshipSupported: 'false', - isNonModelSupported: 'false', - }, }, }); await run(context, 'PostPull'); expect(mockStartCodegenJob).toHaveBeenCalledWith({ appId: 'testAppId', environmentName: 'testEnvName', - codegenJobToCreate: { - renderConfig: { - react: { - module: 'es2020', - target: 'es2020', - script: 'jsx', - renderTypeDeclarations: true, + codegenJobToCreate: expect.objectContaining({ autoGenerateForms: false }), + }); + }); + + describe('codegen job creation', () => { + it('should inclue dataStore configuration when dataStore is enabled', async () => { + isDataStoreEnabledMocked.mockResolvedValue(true); + getMetadataPromise.mockReturnValue({ + features: { + ...defaultStudioFeatureFlags, + }, + }); + await run(context, 'PostPull'); + expect(mockStartCodegenJob).toHaveBeenCalledWith({ + appId: 'testAppId', + environmentName: 'testEnvName', + codegenJobToCreate: expect.objectContaining({ + renderConfig: { + react: expect.objectContaining({ + apiConfiguration: { + datastoreConfig: {}, + }, + }), }, + }), + }); + }); + + it('should inclue GraphQL configuration when dataStore is disabled and valid api configuration is found', async () => { + isDataStoreEnabledMocked.mockResolvedValue(false); + getMetadataPromise.mockReturnValue({ + features: { + ...defaultStudioFeatureFlags, + isGraphQLEnabled: 'true', }, - genericDataSchema: undefined, - autoGenerateForms: false, + }); + await run(context, 'PostPull'); + expect(mockStartCodegenJob).toHaveBeenCalledWith({ + appId: 'testAppId', + environmentName: 'testEnvName', + codegenJobToCreate: expect.objectContaining({ + renderConfig: { + react: expect.objectContaining({ + apiConfiguration: { + graphQlConfig: { + fragmentsFilePath: '../graphql/fragments.js', + mutationsFilePath: '../graphql/mutations.js', + queriesFilePath: '../graphql/queries.js', + subscriptionsFilePath: '../graphql/subscriptions.js', + typesFilePath: undefined, + }, + }, + }), + }, + }), + }); + }); + + it('should inclue noApi configuration when dataStore is disabled and no valid GraphQL Api', async () => { + isDataStoreEnabledMocked.mockResolvedValue(false); + getMetadataPromise.mockReturnValue({ features: { - isNonModelSupported: false, - isRelationshipSupported: false, + ...defaultStudioFeatureFlags, + isGraphQLEnabled: 'true', }, - }, + }); + getCodegenConfigMocked.mockImplementation(() => { + throw new Error(); + }); + await run(context, 'PostPull'); + expect(mockStartCodegenJob).toHaveBeenCalledWith({ + appId: 'testAppId', + environmentName: 'testEnvName', + codegenJobToCreate: expect.objectContaining({ + renderConfig: { + react: expect.objectContaining({ + apiConfiguration: { + noApiConfig: {}, + }, + }), + }, + }), + }); }); }); }); diff --git a/packages/amplify-util-uibuilder/src/clients/amplify-studio-client.ts b/packages/amplify-util-uibuilder/src/clients/amplify-studio-client.ts index 4ced90235ea..ef68d228d8a 100644 --- a/packages/amplify-util-uibuilder/src/clients/amplify-studio-client.ts +++ b/packages/amplify-util-uibuilder/src/clients/amplify-studio-client.ts @@ -17,6 +17,7 @@ export type StudioMetadata = { isRelationshipSupported: boolean; isNonModelSupported: boolean; }; + isGraphQLEnabled: boolean; }; /** @@ -122,6 +123,7 @@ export default class AmplifyStudioClient { isRelationshipSupported: false, isNonModelSupported: false, }, + isGraphQLEnabled: false, }; } @@ -145,6 +147,7 @@ export default class AmplifyStudioClient { isRelationshipSupported: response.features?.isRelationshipSupported === 'true', isNonModelSupported: response.features?.isNonModelSupported === 'true', }, + isGraphQLEnabled: response.features?.isGraphQLEnabled === 'true', }; } catch (err) { throw new Error(`Failed to load metadata: ${err.message}`); diff --git a/packages/amplify-util-uibuilder/src/commands/generateComponents.ts b/packages/amplify-util-uibuilder/src/commands/generateComponents.ts index 323c4076852..1bf47cbdd49 100644 --- a/packages/amplify-util-uibuilder/src/commands/generateComponents.ts +++ b/packages/amplify-util-uibuilder/src/commands/generateComponents.ts @@ -16,6 +16,9 @@ import { extractUIComponents, } from './utils'; import { AmplifyUIBuilder } from 'aws-sdk'; +import { getCodegenConfig } from 'amplify-codegen'; +import path from 'path'; +import { ApiConfiguration } from 'aws-sdk/clients/amplifyuibuilder'; /** * Pulls ui components from Studio backend and generates the code in the user's file system @@ -34,10 +37,66 @@ export const run = async (context: $TSContext, eventType: 'PostPush' | 'PostPull studioClient.isGraphQLSupported ? getAmplifyDataSchema(context) : Promise.resolve(undefined), ]); - const nothingWouldAutogenerate = - !dataSchema || !studioClient.metadata.autoGenerateForms || !studioClient.isGraphQLSupported || !studioClient.isDataStoreEnabled; + let canCodegenGraphqlComponents = false; + let apiConfiguration: ApiConfiguration = { + dataStoreConfig: {}, + }; + + if (!studioClient.isDataStoreEnabled && studioClient.metadata.isGraphQLEnabled) { + printer.debug('building graphql config'); + // attempt to get api codegen info + const projectPath = context.exeInfo.localEnvInfo.projectPath; + const componentsPath = getUiBuilderComponentsPath(context); + let promptForUpdateCodegen = false; + const relativeToComponentsPath = (importPath: string) => { + return path.relative(componentsPath, importPath).split(path.sep).join('/'); + }; + + try { + const codegenConfig = getCodegenConfig(projectPath); + const typesPath = codegenConfig.getGeneratedTypesPath(); + apiConfiguration = { + graphQLConfig: { + typesFilePath: (typesPath && relativeToComponentsPath(typesPath)) || '', + queriesFilePath: relativeToComponentsPath(codegenConfig.getGeneratedQueriesPath()), + mutationsFilePath: relativeToComponentsPath(codegenConfig.getGeneratedMutationsPath()), + subscriptionsFilePath: relativeToComponentsPath(codegenConfig.getGeneratedSubscriptionsPath()), + fragmentsFilePath: relativeToComponentsPath(codegenConfig.getGeneratedFragmentsPath()), + }, + }; + + const minQueryDepth = 3; + const isQueryingTooShallow = (codegenConfig.getQueryMaxDepth() || 0) < minQueryDepth; + + if (studioClient.metadata.formFeatureFlags.isRelationshipSupported && isQueryingTooShallow) { + promptForUpdateCodegen = true; + printer.warn(`Forms with relationships require a maximum query depth of at least ${minQueryDepth}.`); + } + canCodegenGraphqlComponents = true; + } catch { + canCodegenGraphqlComponents = false; + promptForUpdateCodegen = true; + printer.warn( + 'Error encountered when accessing GraphQL configurations. This will impact generating forms and components bound to your data models.', + ); + } finally { + if (promptForUpdateCodegen) { + printer.warn(`Run 'amplify update codegen' to ensure GraphQL configurations for your project are correct.`); + } + } + } + + const hasDataAPI = studioClient.isDataStoreEnabled || canCodegenGraphqlComponents; + + const willAutogenerateItems = dataSchema && studioClient.metadata.autoGenerateForms && studioClient.isGraphQLSupported && hasDataAPI; + + if (!hasDataAPI) { + apiConfiguration = { + noApiConfig: {}, + }; + } - if (nothingWouldAutogenerate && [componentSchemas, themeSchemas, formSchemas].every((group) => !group.entities.length)) { + if (!willAutogenerateItems && [componentSchemas, themeSchemas, formSchemas].every((group) => !group.entities.length)) { printer.debug('Skipping UI component generation since none are found.'); return; } @@ -52,9 +111,10 @@ export const run = async (context: $TSContext, eventType: 'PostPush' | 'PostPull target: 'es2020', script: 'jsx', renderTypeDeclarations: true, - }, + apiConfiguration, + } as AmplifyUIBuilder.ReactStartCodegenJobData, }, - autoGenerateForms: studioClient.metadata.autoGenerateForms && studioClient.isGraphQLSupported, + autoGenerateForms: studioClient.metadata.autoGenerateForms && studioClient.isGraphQLSupported && hasDataAPI, features: studioClient.metadata.formFeatureFlags, }; // SDK will throw if this is undefined diff --git a/packages/amplify-util-uibuilder/types/amplify-codegen.d.ts b/packages/amplify-util-uibuilder/types/amplify-codegen.d.ts new file mode 100644 index 00000000000..9301b96ea77 --- /dev/null +++ b/packages/amplify-util-uibuilder/types/amplify-codegen.d.ts @@ -0,0 +1,12 @@ +declare module 'amplify-codegen' { + export function getCodegenConfig(projectPath: string | undefined): CodegenConfigHelper; + + export type CodegenConfigHelper = { + getGeneratedTypesPath: () => string | undefined; + getGeneratedQueriesPath: () => string; + getGeneratedMutationsPath: () => string; + getGeneratedSubscriptionsPath: () => string; + getGeneratedFragmentsPath: () => string; + getQueryMaxDepth: () => number | undefined; + }; +} diff --git a/yarn.lock b/yarn.lock index ba2707e87ee..e0e83bef4d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -105,7 +105,7 @@ __metadata: "@types/node": ^12.12.6 "@types/ws": ^8.2.2 amplify-velocity-template: 1.4.12 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 chalk: ^4.1.1 cors: ^2.8.5 dataloader: ^2.0.0 @@ -213,7 +213,7 @@ __metadata: amplify-headless-interface: 1.17.4 amplify-util-headless-input: 1.9.13 aws-cdk-lib: ~2.80.0 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 axios: ^0.26.0 chalk: ^4.1.1 change-case: ^4.1.1 @@ -260,7 +260,7 @@ __metadata: "@aws-amplify/amplify-prompts": 2.8.0 "@types/folder-hash": ^4.0.1 archiver: ^5.3.0 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 chalk: ^4.1.1 cloudform-types: ^4.2.0 enquirer: ^2.3.6 @@ -289,7 +289,7 @@ __metadata: amplify-headless-interface: 1.17.4 amplify-util-headless-input: 1.9.13 aws-cdk-lib: ~2.80.0 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 constructs: ^10.0.5 fs-extra: ^8.1.0 lodash: ^4.17.21 @@ -333,7 +333,7 @@ __metadata: "@aws-amplify/amplify-environment-parameters": 1.7.3 "@aws-amplify/amplify-prompts": 2.8.0 "@aws-amplify/amplify-provider-awscloudformation": 8.3.3 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 chalk: ^4.1.1 fs-extra: ^8.1.0 lodash: ^4.17.21 @@ -350,7 +350,7 @@ __metadata: "@aws-amplify/amplify-cli-core": 4.2.3 "@aws-amplify/amplify-prompts": 2.8.0 "@aws-sdk/client-rekognition": ^3.303.0 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 chalk: ^4.1.1 fs-extra: ^8.1.0 uuid: ^8.3.2 @@ -369,7 +369,7 @@ __metadata: amplify-headless-interface: 1.17.4 amplify-util-headless-input: 1.9.13 aws-cdk-lib: ~2.80.0 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 chalk: ^4.1.1 cloudform-types: ^4.2.0 constructs: ^10.0.5 @@ -475,7 +475,7 @@ __metadata: "@aws-amplify/amplify-cli-core": 4.2.3 "@aws-amplify/amplify-e2e-core": 5.1.3 "@types/ini": ^1.3.30 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 dotenv: ^8.2.0 execa: ^5.1.1 fs-extra: ^8.1.0 @@ -524,7 +524,7 @@ __metadata: amplify-headless-interface: 1.17.4 aws-amplify: ^4.2.8 aws-appsync: ^4.1.1 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 chalk: ^4.1.1 dotenv: ^8.2.0 execa: ^5.1.1 @@ -551,7 +551,7 @@ __metadata: dependencies: "@aws-amplify/amplify-cli-core": 4.2.3 ajv: ^6.12.6 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 lodash: ^4.17.21 mkdirp: ^1.0.4 ts-json-schema-generator: ~1.1.2 @@ -752,7 +752,7 @@ __metadata: "@aws-amplify/amplify-prompts": 2.8.0 "@types/node": ^12.12.6 "@types/openpgp": ^4.4.18 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 detect-port: ^1.3.0 execa: ^5.1.1 fs-extra: ^8.1.0 @@ -797,10 +797,10 @@ __metadata: "@types/lodash.throttle": ^4.1.6 "@types/node": ^12.12.6 "@types/uuid": ^8.0.0 - amplify-codegen: ^4.1.4 + amplify-codegen: ^4.1.5 archiver: ^5.3.0 aws-cdk-lib: ~2.80.0 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 bottleneck: 2.19.5 chalk: ^4.1.1 cloudform-types: ^4.2.0 @@ -849,7 +849,7 @@ __metadata: resolution: "@aws-amplify/amplify-util-import@workspace:packages/amplify-util-import" dependencies: "@types/node": ^12.12.6 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 languageName: unknown linkType: soft @@ -885,12 +885,12 @@ __metadata: "@types/node": ^12.12.6 "@types/semver": ^7.1.0 "@types/which": ^1.3.2 - amplify-codegen: ^4.1.4 + amplify-codegen: ^4.1.5 amplify-dynamodb-simulator: 2.8.3 amplify-nodejs-function-runtime-provider: 2.5.3 amplify-storage-simulator: 1.10.0 aws-appsync: ^4.1.4 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 aws-sdk-mock: ^5.8.0 axios: ^0.26.0 chokidar: ^3.5.3 @@ -934,8 +934,8 @@ __metadata: "@types/jest": ^29.5.1 "@types/semver": ^7.1.0 "@types/tiny-async-pool": ^2.0.0 - amplify-codegen: ^4.1.4 - aws-sdk: ^2.1405.0 + amplify-codegen: ^4.1.5 + aws-sdk: ^2.1426.0 fs-extra: ^8.1.0 node-fetch: ^2.6.7 ora: ^4.0.3 @@ -997,9 +997,9 @@ __metadata: languageName: node linkType: hard -"@aws-amplify/appsync-modelgen-plugin@npm:2.5.1, @aws-amplify/appsync-modelgen-plugin@npm:^2.4.4": - version: 2.5.1 - resolution: "@aws-amplify/appsync-modelgen-plugin@npm:2.5.1" +"@aws-amplify/appsync-modelgen-plugin@npm:2.5.2, @aws-amplify/appsync-modelgen-plugin@npm:^2.4.4": + version: 2.5.2 + resolution: "@aws-amplify/appsync-modelgen-plugin@npm:2.5.2" dependencies: "@graphql-codegen/plugin-helpers": ^1.18.8 "@graphql-codegen/visitor-plugin-common": ^1.22.0 @@ -1014,7 +1014,7 @@ __metadata: ts-dedent: ^1.1.0 peerDependencies: graphql: ^15.5.0 - checksum: 07fb5825ad89d95650f832a52cef82bc54a1cae3a421e3b0ccca29a75b7c859df8624c68c50aa66d30217f73a731e9d1651be403039bac8c5ddda01fe395d142 + checksum: dd88838b9ad451e0688e171653282cd9b3b59d2fc43a9aaf9f19b99f13a0520e171824aebc87379d1748123ca5c7cb13bc4efbd6842334ef9fd8a0fc73c67857 languageName: node linkType: hard @@ -1099,7 +1099,7 @@ __metadata: "@types/tar-fs": ^2.0.0 "@types/treeify": ^1.0.0 "@types/update-notifier": ^5.1.0 - amplify-codegen: ^4.1.4 + amplify-codegen: ^4.1.5 amplify-dotnet-function-runtime-provider: 2.0.8 amplify-go-function-runtime-provider: 2.3.26 amplify-java-function-runtime-provider: 2.3.26 @@ -1107,7 +1107,7 @@ __metadata: amplify-nodejs-function-runtime-provider: 2.5.3 amplify-python-function-runtime-provider: 2.4.26 aws-cdk-lib: ~2.80.0 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 chalk: ^4.1.1 ci-info: ^3.8.0 cli-table3: ^0.6.0 @@ -1266,16 +1266,16 @@ __metadata: languageName: node linkType: hard -"@aws-amplify/graphql-docs-generator@npm:4.0.3": - version: 4.0.3 - resolution: "@aws-amplify/graphql-docs-generator@npm:4.0.3" +"@aws-amplify/graphql-docs-generator@npm:4.0.4": + version: 4.0.4 + resolution: "@aws-amplify/graphql-docs-generator@npm:4.0.4" dependencies: graphql: ^15.5.0 handlebars: 4.7.7 yargs: ^15.1.0 bin: graphql-docs-generator: bin/cli - checksum: 5486426ecc726b69820ecc610c36a61a8ee37477c05e2ef073264431ecb783e4ac63714fda000ae2c3e8568f0e3e00534b317c05698b31430c13d709f1cd8e09 + checksum: 8e49072d340b659a12a3687c1b6dd9acf12f9ec17bbdba440c541ba1cf3699fc11d0968ff5cd5313002f110c64911455bec13597b3a0f11d1c282dd641b431d6 languageName: node linkType: hard @@ -1500,9 +1500,9 @@ __metadata: languageName: node linkType: hard -"@aws-amplify/graphql-types-generator@npm:3.0.3": - version: 3.0.3 - resolution: "@aws-amplify/graphql-types-generator@npm:3.0.3" +"@aws-amplify/graphql-types-generator@npm:3.0.4": + version: 3.0.4 + resolution: "@aws-amplify/graphql-types-generator@npm:3.0.4" dependencies: "@babel/generator": 7.0.0-beta.4 "@babel/types": 7.0.0-beta.4 @@ -1525,7 +1525,7 @@ __metadata: yargs: ^15.1.0 bin: graphql-types-generator: lib/cli.js - checksum: 513e648bf98e27803bc0b1a9807cdf8db98b5c81f9b3ee8f822aaa02f8320fe043ee43b5de2a2a1f9a6bf91a2fbb211b0f0efb7dd1942288ef360139654d9936 + checksum: 4b944a578b84b38a61566c49a9a86adff3db06f29fde60b77838d78bf7eea65e674c73c64f2d18d5368254721b6f3cba0c8e68f70d6205aec3ce5818ad4c344c languageName: node linkType: hard @@ -12983,13 +12983,13 @@ __metadata: languageName: unknown linkType: soft -"amplify-codegen@npm:^4.1.4": - version: 4.1.4 - resolution: "amplify-codegen@npm:4.1.4" +"amplify-codegen@npm:^4.1.5": + version: 4.1.5 + resolution: "amplify-codegen@npm:4.1.5" dependencies: - "@aws-amplify/appsync-modelgen-plugin": 2.5.1 - "@aws-amplify/graphql-docs-generator": 4.0.3 - "@aws-amplify/graphql-types-generator": 3.0.3 + "@aws-amplify/appsync-modelgen-plugin": 2.5.2 + "@aws-amplify/graphql-docs-generator": 4.0.4 + "@aws-amplify/graphql-types-generator": 3.0.4 "@graphql-codegen/core": 2.6.6 chalk: ^3.0.0 fs-extra: ^8.1.0 @@ -13006,7 +13006,7 @@ __metadata: peerDependencies: "@aws-amplify/amplify-cli-core": "*" graphql-transformer-core: ^8.0.0 - checksum: 7f47747ae825817401f331f4f135b7c1ea01e9d1e2f2fed68436ce0292b9d990af29711fd4ff87ce70d20ab66c49b7b10e3c73419907408e0708bcb2a54091f3 + checksum: 1470d918252f28482d5668c83b0da51de2a93140a955a917550a189327e7b9a50951dba3daa9e446692b2f631efa1f82f9b88b3f1a36436ff24e81151642a636 languageName: node linkType: hard @@ -13031,7 +13031,7 @@ __metadata: resolution: "amplify-dynamodb-simulator@workspace:packages/amplify-dynamodb-simulator" dependencies: "@aws-amplify/amplify-cli-core": 4.2.3 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 detect-port: ^1.3.0 execa: ^5.1.1 fs-extra: ^8.1.0 @@ -13071,7 +13071,7 @@ __metadata: aws-amplify: ^4.2.8 aws-appsync: ^4.1.1 aws-cdk-lib: ~2.80.0 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 axios: ^0.26.0 circleci-api: ^4.1.4 constructs: ^10.0.5 @@ -13230,7 +13230,7 @@ __metadata: "@types/serve-static": ^1.13.3 "@types/uuid": ^8.3.1 "@types/xml": ^1.0.4 - aws-sdk: ^2.1405.0 + aws-sdk: ^2.1426.0 body-parser: ^1.19.2 cors: ^2.8.5 etag: ^1.8.1 @@ -14006,9 +14006,9 @@ __metadata: languageName: node linkType: hard -"aws-sdk@npm:^2.1405.0": - version: 2.1405.0 - resolution: "aws-sdk@npm:2.1405.0" +"aws-sdk@npm:^2.1426.0": + version: 2.1426.0 + resolution: "aws-sdk@npm:2.1426.0" dependencies: buffer: 4.9.2 events: 1.1.1 @@ -14020,7 +14020,7 @@ __metadata: util: ^0.12.4 uuid: 8.0.0 xml2js: 0.5.0 - checksum: e7e06ba1d4b37cd10dae568bc17526c3d5d17532d37c1743f8589420ce9a4a489ff87cdb5ff53baa6138dbca79048d8b2f26d1a2fce8d2b11eb4eec6e9b12cb8 + checksum: 4d79be6a7ea7436989d8dc183f4a5a881a8935e8ab93270b6c1d5caac7f93e640b979c48a0af2569a9b063175ec3137427ba8a6545cada1f858b938d3cbab46b languageName: node linkType: hard