diff --git a/packages/amplify-codegen/src/commands/models.js b/packages/amplify-codegen/src/commands/models.js index 4767b4197..d7e966209 100644 --- a/packages/amplify-codegen/src/commands/models.js +++ b/packages/amplify-codegen/src/commands/models.js @@ -5,6 +5,7 @@ const glob = require('glob-all'); const { FeatureFlags, pathManager } = require('@aws-amplify/amplify-cli-core'); const { generateModels: generateModelsHelper } = require('@aws-amplify/graphql-generator'); const { validateAmplifyFlutterMinSupportedVersion } = require('../utils/validateAmplifyFlutterMinSupportedVersion'); +const defaultDirectiveDefinitions = require('../utils/defaultDirectiveDefinitions'); const platformToLanguageMap = { android: 'java', @@ -85,9 +86,14 @@ async function generateModels(context, generateOptions = null) { const backendPath = await context.amplify.pathManager.getBackendDirPath(); const apiResourcePath = path.join(backendPath, 'api', apiResource.resourceName); - const directiveDefinitions = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'getTransformerDirectives', { - resourceDir: apiResourcePath, - }); + let directiveDefinitions; + try { + directiveDefinitions = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'getTransformerDirectives', { + resourceDir: apiResourcePath, + }); + } catch { + directiveDefinitions = defaultDirectiveDefinitions; + } const schemaContent = loadSchema(apiResourcePath); const baseOutputDir = overrideOutputDir || path.join(projectRoot, getModelOutputPath(context)); diff --git a/packages/amplify-codegen/src/utils/defaultDirectiveDefinitions.js b/packages/amplify-codegen/src/utils/defaultDirectiveDefinitions.js new file mode 100644 index 000000000..f0254f38f --- /dev/null +++ b/packages/amplify-codegen/src/utils/defaultDirectiveDefinitions.js @@ -0,0 +1,113 @@ +const defaultDirectiveDefinitions = ` +directive @aws_subscribe(mutations: [String!]!) on FIELD_DEFINITION + +directive @aws_auth(cognito_groups: [String!]!) on FIELD_DEFINITION + +directive @aws_api_key on FIELD_DEFINITION | OBJECT + +directive @aws_iam on FIELD_DEFINITION | OBJECT + +directive @aws_oidc on FIELD_DEFINITION | OBJECT + +directive @aws_cognito_user_pools(cognito_groups: [String!]) on FIELD_DEFINITION | OBJECT + +directive @aws_lambda on FIELD_DEFINITION | OBJECT + +directive @deprecated(reason: String) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ENUM | ENUM_VALUE + +directive @model(queries: ModelQueryMap, mutations: ModelMutationMap, subscriptions: ModelSubscriptionMap, timestamps: TimestampConfiguration) on OBJECT +input ModelMutationMap { + create: String + update: String + delete: String +} +input ModelQueryMap { + get: String + list: String +} +input ModelSubscriptionMap { + onCreate: [String] + onUpdate: [String] + onDelete: [String] + level: ModelSubscriptionLevel +} +enum ModelSubscriptionLevel { + off + public + on +} +input TimestampConfiguration { + createdAt: String + updatedAt: String +} +directive @function(name: String!, region: String, accountId: String) repeatable on FIELD_DEFINITION +directive @http(method: HttpMethod = GET, url: String!, headers: [HttpHeader] = []) on FIELD_DEFINITION +enum HttpMethod { + GET + POST + PUT + DELETE + PATCH +} +input HttpHeader { + key: String + value: String +} +directive @predictions(actions: [PredictionsActions!]!) on FIELD_DEFINITION +enum PredictionsActions { + identifyText + identifyLabels + convertTextToSpeech + translateText +} +directive @primaryKey(sortKeyFields: [String]) on FIELD_DEFINITION +directive @index(name: String, sortKeyFields: [String], queryField: String) repeatable on FIELD_DEFINITION +directive @hasMany(indexName: String, fields: [String!], limit: Int = 100) on FIELD_DEFINITION +directive @hasOne(fields: [String!]) on FIELD_DEFINITION +directive @manyToMany(relationName: String!, limit: Int = 100) on FIELD_DEFINITION +directive @belongsTo(fields: [String!]) on FIELD_DEFINITION +directive @default(value: String!) on FIELD_DEFINITION +directive @auth(rules: [AuthRule!]!) on OBJECT | FIELD_DEFINITION +input AuthRule { + allow: AuthStrategy! + provider: AuthProvider + identityClaim: String + groupClaim: String + ownerField: String + groupsField: String + groups: [String] + operations: [ModelOperation] +} +enum AuthStrategy { + owner + groups + private + public + custom +} +enum AuthProvider { + apiKey + iam + oidc + userPools + function +} +enum ModelOperation { + create + update + delete + read + list + get + sync + listen + search +} +directive @mapsTo(name: String!) on OBJECT +directive @searchable(queries: SearchableQueryMap) on OBJECT +input SearchableQueryMap { + search: String +} +`; + +module.exports = defaultDirectiveDefinitions; diff --git a/packages/amplify-codegen/src/utils/index.js b/packages/amplify-codegen/src/utils/index.js index 3843088b8..184cffd74 100644 --- a/packages/amplify-codegen/src/utils/index.js +++ b/packages/amplify-codegen/src/utils/index.js @@ -15,6 +15,7 @@ const getSDLSchemaLocation = require('./getSDLSchemaLocation'); const switchToSDLSchema = require('./switchToSDLSchema'); const ensureIntrospectionSchema = require('./ensureIntrospectionSchema'); const { readSchemaFromFile } = require('./readSchemaFromFile'); +const defaultDirectiveDefinitions = require('./defaultDirectiveDefinitions'); module.exports = { getAppSyncAPIDetails, getFrontEndHandler, @@ -33,4 +34,5 @@ module.exports = { switchToSDLSchema, ensureIntrospectionSchema, readSchemaFromFile, + defaultDirectiveDefinitions, }; diff --git a/packages/amplify-codegen/tests/commands/__snapshots__/models.test.js.snap b/packages/amplify-codegen/tests/commands/__snapshots__/models.test.js.snap index 1ddf205ef..a754bd06d 100644 --- a/packages/amplify-codegen/tests/commands/__snapshots__/models.test.js.snap +++ b/packages/amplify-codegen/tests/commands/__snapshots__/models.test.js.snap @@ -61,3 +61,12 @@ Array [ "schema.js", ] `; + +exports[`command-models-generates models in expected output path should use default directive definitions if getTransformerDirectives fails 1`] = ` +Array [ + "index.d.ts", + "index.js", + "schema.d.ts", + "schema.js", +] +`; diff --git a/packages/amplify-codegen/tests/commands/models.test.js b/packages/amplify-codegen/tests/commands/models.test.js index d2b7be990..d2a70795a 100644 --- a/packages/amplify-codegen/tests/commands/models.test.js +++ b/packages/amplify-codegen/tests/commands/models.test.js @@ -3,6 +3,7 @@ const { validateAmplifyFlutterMinSupportedVersion, MINIMUM_SUPPORTED_VERSION_CONSTRAINT, } = require('../../src/utils/validateAmplifyFlutterMinSupportedVersion'); +const defaultDirectiveDefinitions = require('../../src/utils/defaultDirectiveDefinitions'); const mockFs = require('mock-fs'); const fs = require('fs'); const path = require('path'); @@ -143,6 +144,30 @@ describe('command-models-generates models in expected output path', () => { } } + it('should use default directive definitions if getTransformerDirectives fails', async () => { + MOCK_CONTEXT.amplify.executeProviderUtils.mockRejectedValue('no amplify project'); + const frontend = 'javascript'; + // mock the input and output file structure + const schemaFilePath = path.join(MOCK_BACKEND_DIRECTORY, 'api', MOCK_PROJECT_NAME); + const outputDirectory = path.join(MOCK_PROJECT_ROOT, OUTPUT_PATHS[frontend]); + const mockedFiles = {}; + const nodeModules = path.resolve(path.join(__dirname, '../../../../node_modules')); + mockedFiles[schemaFilePath] = { + 'schema.graphql': ' type SimpleModel @model { id: ID! status: String } ', + }; + mockedFiles[outputDirectory] = {}; + mockFs(mockedFiles); + MOCK_CONTEXT.amplify.getProjectConfig.mockReturnValue({ frontend: frontend }); + + // assert empty folder before generation + expect(fs.readdirSync(outputDirectory).length).toEqual(0); + + await generateModels(MOCK_CONTEXT); + + // assert model files are generated in expected output directory + expect(fs.readdirSync(outputDirectory)).toMatchSnapshot(); + }); + afterEach(mockFs.restore); }); @@ -158,117 +183,6 @@ function addMocksToContext() { }, ], }); - MOCK_CONTEXT.amplify.executeProviderUtils.mockReturnValue(directives); + MOCK_CONTEXT.amplify.executeProviderUtils.mockReturnValue(defaultDirectiveDefinitions); MOCK_CONTEXT.amplify.pathManager.getBackendDirPath.mockReturnValue(MOCK_BACKEND_DIRECTORY); } - -const directives = ` -directive @aws_subscribe(mutations: [String!]!) on FIELD_DEFINITION - -directive @aws_auth(cognito_groups: [String!]!) on FIELD_DEFINITION - -directive @aws_api_key on FIELD_DEFINITION | OBJECT - -directive @aws_iam on FIELD_DEFINITION | OBJECT - -directive @aws_oidc on FIELD_DEFINITION | OBJECT - -directive @aws_cognito_user_pools(cognito_groups: [String!]) on FIELD_DEFINITION | OBJECT - -directive @aws_lambda on FIELD_DEFINITION | OBJECT - -directive @deprecated(reason: String) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ENUM | ENUM_VALUE - -directive @model(queries: ModelQueryMap, mutations: ModelMutationMap, subscriptions: ModelSubscriptionMap, timestamps: TimestampConfiguration) on OBJECT -input ModelMutationMap { - create: String - update: String - delete: String -} -input ModelQueryMap { - get: String - list: String -} -input ModelSubscriptionMap { - onCreate: [String] - onUpdate: [String] - onDelete: [String] - level: ModelSubscriptionLevel -} -enum ModelSubscriptionLevel { - off - public - on -} -input TimestampConfiguration { - createdAt: String - updatedAt: String -} -directive @function(name: String!, region: String, accountId: String) repeatable on FIELD_DEFINITION -directive @http(method: HttpMethod = GET, url: String!, headers: [HttpHeader] = []) on FIELD_DEFINITION -enum HttpMethod { - GET - POST - PUT - DELETE - PATCH -} -input HttpHeader { - key: String - value: String -} -directive @predictions(actions: [PredictionsActions!]!) on FIELD_DEFINITION -enum PredictionsActions { - identifyText - identifyLabels - convertTextToSpeech - translateText -} -directive @primaryKey(sortKeyFields: [String]) on FIELD_DEFINITION -directive @index(name: String, sortKeyFields: [String], queryField: String) repeatable on FIELD_DEFINITION -directive @hasMany(indexName: String, fields: [String!], limit: Int = 100) on FIELD_DEFINITION -directive @hasOne(fields: [String!]) on FIELD_DEFINITION -directive @manyToMany(relationName: String!, limit: Int = 100) on FIELD_DEFINITION -directive @belongsTo(fields: [String!]) on FIELD_DEFINITION -directive @default(value: String!) on FIELD_DEFINITION -directive @auth(rules: [AuthRule!]!) on OBJECT | FIELD_DEFINITION -input AuthRule { - allow: AuthStrategy! - provider: AuthProvider - identityClaim: String - groupClaim: String - ownerField: String - groupsField: String - groups: [String] - operations: [ModelOperation] -} -enum AuthStrategy { - owner - groups - private - public - custom -} -enum AuthProvider { - apiKey - iam - oidc - userPools - function -} -enum ModelOperation { - create - update - delete - read - list - get - sync - listen - search -} -directive @mapsTo(name: String!) on OBJECT -directive @searchable(queries: SearchableQueryMap) on OBJECT -input SearchableQueryMap { - search: String -}`;