diff --git a/package.json b/package.json index 9f1aea8..441ec98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-appsync-simulator", - "version": "0.0.0-development", + "version": "0.20.0", "main": "lib/index.js", "author": "bboure", "license": "MIT", @@ -22,7 +22,7 @@ ], "dependencies": { "@graphql-tools/merge": "^8.2.1", - "amplify-appsync-simulator": "^1.27.4", + "@aws-amplify/amplify-appsync-simulator": "^2.8.0", "amplify-nodejs-function-runtime-provider": "^1.1.6", "aws-sdk": "^2.792.0", "axios": "^0.21.0", @@ -42,7 +42,6 @@ "@babel/core": "^7.12.3", "@babel/plugin-transform-modules-commonjs": "^7.12.1", "@babel/preset-env": "^7.12.1", - "@semantic-release/git": "^9.0.0", "all-contributors-cli": "^6.19.0", "babel-eslint": "^10.1.0", "babel-plugin-inline-import": "^3.0.0", diff --git a/src/__tests__/__snapshots__/getAppSyncConfig.js.snap b/src/__tests__/__snapshots__/getAppSyncConfig.js.snap index bf2a18a..7eb332a 100644 --- a/src/__tests__/__snapshots__/getAppSyncConfig.js.snap +++ b/src/__tests__/__snapshots__/getAppSyncConfig.js.snap @@ -192,3 +192,66 @@ $util.toJson($ctx.result) }, ] `; + +exports[`getAppSyncConfig should generate a valid config even with serverless-appsync-plugin >= 2.2.0 1`] = ` +Object { + "additionalAuthenticationProviders": Array [], + "apiKey": "123456789", + "defaultAuthenticationType": Object { + "authenticationType": undefined, + }, + "name": undefined, +} +`; + +exports[`getAppSyncConfig should generate a valid config even with serverless-appsync-plugin >= 2.2.0 2`] = ` +Object { + "content": "type Post { + userId: Int! + id: Int! + title: String! + body: String! +} + +type Query { + getPost: Post + getPosts: [Post]! +} + +schema { + query: Query +}", + "path": "schema.graphql", +} +`; + +exports[`getAppSyncConfig should generate a valid config even with serverless-appsync-plugin >= 2.2.0 3`] = `Array []`; + +exports[`getAppSyncConfig should generate a valid config even with serverless-appsync-plugin >= 2.2.0 4`] = ` +Array [ + Object { + "invoke": [Function], + "name": "lambda", + "type": "AWS_LAMBDA", + }, + Object { + "config": Object { + "accessKeyId": "DEFAULT_ACCESS_KEY", + "endpoint": "http://localhost:8000", + "region": "localhost", + "secretAccessKey": "DEFAULT_SECRET", + "sessionToken": "DEFAULT_SESSION_TOKEN", + "tableName": "myTable", + }, + "name": "dynamodb", + "type": "AMAZON_DYNAMODB", + }, + Object { + "endpoint": "http://127.0.0.1", + "name": "http", + "type": "HTTP", + }, +] +`; + +exports[`getAppSyncConfig should generate a valid config even with serverless-appsync-plugin >= 2.2.0 5`] = `Array []`; diff --git a/src/__tests__/getAppSyncConfig.js b/src/__tests__/getAppSyncConfig.js index 6fa7481..1304015 100644 --- a/src/__tests__/getAppSyncConfig.js +++ b/src/__tests__/getAppSyncConfig.js @@ -2,6 +2,68 @@ import path from 'path'; import getAppSyncConfig from '../getAppSyncConfig'; describe('getAppSyncConfig', () => { + it('should generate a valid config even with serverless-appsync-plugin >= 2.2.0', () => { + const config = { + dataSources: { + lambda: { + type: 'AWS_LAMBDA', + name: 'lambda', + config: { + functionName: 'getPosts', + }, + }, + dynamodb: { + type: 'AMAZON_DYNAMODB', + name: 'dynamodb', + config: { + tableName: 'myTable', + }, + }, + http: { + type: 'HTTP', + name: 'http', + config: { + endpoint: 'http://127.0.0.1', + }, + }, + }, + }; + + const result = getAppSyncConfig( + { + options: { + apiKey: '123456789', + dynamoDb: { + endpoint: `http://localhost:8000`, + region: 'localhost', + accessKeyId: 'DEFAULT_ACCESS_KEY', + secretAccessKey: 'DEFAULT_SECRET', + sessionToken: 'DEFAULT_SESSION_TOKEN', + }, + }, + serverless: { + config: { servicePath: path.join(__dirname, 'files') }, + service: { + functions: { + getPost: { + hndler: 'index.handler', + }, + getPosts: { + hndler: 'index.handler', + }, + }, + }, + }, + }, + config, + ); + expect(result.appSync).toMatchSnapshot(); + expect(result.schema).toMatchSnapshot(); + expect(result.resolvers).toMatchSnapshot(); + expect(result.dataSources).toMatchSnapshot(); + expect(result.functions).toMatchSnapshot(); + }); + it('should generate a valid config', () => { const config = { name: 'myAPI', diff --git a/src/getAppSyncConfig.js b/src/getAppSyncConfig.js index 4dc1b97..19981f7 100644 --- a/src/getAppSyncConfig.js +++ b/src/getAppSyncConfig.js @@ -1,4 +1,4 @@ -import { AmplifyAppSyncSimulatorAuthenticationType as AuthTypes } from 'amplify-appsync-simulator'; +import { AmplifyAppSyncSimulatorAuthenticationType as AuthTypes } from '@aws-amplify/amplify-appsync-simulator'; import axios from 'axios'; import fs from 'fs'; import { forEach, isNil, first } from 'lodash'; @@ -23,25 +23,119 @@ const directLambdaMappingTemplates = { response: directLambdaResponse, }; +const convertAppSyncConfigBackToOldVersion = (appSyncConfig) => { + if ( + appSyncConfig.authentication && + appSyncConfig.authentication.hasOwnProperty('type') + ) { + appSyncConfig.authenticationType = appSyncConfig.authentication.type; + } + + if (appSyncConfig.dataSources && !Array.isArray(appSyncConfig.dataSources)) { + console.log('AppSync Simulator: Convert new configuration to old one'); + const arrayDataSources = []; + for (const [key, value] of Object.entries(appSyncConfig.dataSources)) { + const name = value.hasOwnProperty('name') ? value.name : key; + + arrayDataSources.push({ + ...value, + name: name, + }); + } + appSyncConfig.dataSources = arrayDataSources; + appSyncConfig.version = '2.2.0'; + } + + if ( + appSyncConfig.pipelineFunctions && + !Array.isArray(appSyncConfig.pipelineFunctions) + ) { + console.log('AppSync Simulator: Convert new configuration to old one'); + const arrayPipelineFunctions = []; + for (const [key, value] of Object.entries( + appSyncConfig.pipelineFunctions, + )) { + const name = value.hasOwnProperty('name') ? value.name : key; + const pipelineFunction = { + ...value, + name: name, + }; + + if ( + !pipelineFunction.hasOwnProperty('code') && + !( + pipelineFunction.hasOwnProperty('request') || + pipelineFunction.hasOwnProperty('response') + ) + ) { + if (!pipelineFunction.hasOwnProperty('request')) { + pipelineFunction['request'] = false; + } + if (!pipelineFunction.hasOwnProperty('response')) { + pipelineFunction['response'] = false; + } + } + + arrayPipelineFunctions.push(pipelineFunction); + } + appSyncConfig.functionConfigurations = arrayPipelineFunctions; + appSyncConfig.version = '2.2.0'; + } + + if (appSyncConfig.resolvers) { + console.log('AppSync Simulator: Convert new configuration to old one'); + const arrayMappingTemplates = []; + + for (const [key, value] of Object.entries(appSyncConfig.resolvers)) { + const keySplitted = key.split('.'); + const type = value.hasOwnProperty('type') ? value.type : keySplitted[0]; + const field = value.hasOwnProperty('field') + ? value.field + : keySplitted[1]; + + arrayMappingTemplates.push({ + ...value, + type: type, + field: field, + }); + } + // console.log('Mapping Config', arrayMappingTemplates); + appSyncConfig.mappingTemplates = arrayMappingTemplates; + appSyncConfig.version = '2.2.0'; + } + + return appSyncConfig; +}; + export default function getAppSyncConfig(context, appSyncConfig) { // Flattening params + + const modifiedAppSyncConfig = + convertAppSyncConfigBackToOldVersion(appSyncConfig); + const cfg = { - ...appSyncConfig, - mappingTemplates: (appSyncConfig.mappingTemplates || []).flat(), - functionConfigurations: (appSyncConfig.functionConfigurations || []).flat(), - dataSources: (appSyncConfig.dataSources || []).flat(), + ...modifiedAppSyncConfig, + mappingTemplates: (modifiedAppSyncConfig.mappingTemplates || []).flat(), + functionConfigurations: ( + modifiedAppSyncConfig.functionConfigurations || [] + ).flat(), + dataSources: (modifiedAppSyncConfig.dataSources || []).flat(), }; const mappingTemplatesLocation = path.join( context.serverless.config.servicePath, - cfg.mappingTemplatesLocation || DEFAULT_MAPPING_TEMPLATE_LOCATION, + modifiedAppSyncConfig.hasOwnProperty('version') + ? '' + : cfg.mappingTemplatesLocation || DEFAULT_MAPPING_TEMPLATE_LOCATION, ); const functionConfigurationsLocation = path.join( context.serverless.config.servicePath, - cfg.functionConfigurationsLocation || - cfg.mappingTemplatesLocation || - DEFAULT_MAPPING_TEMPLATE_LOCATION, + modifiedAppSyncConfig.hasOwnProperty('version') + ? '' + : cfg.functionConfigurationsLocation || + cfg.mappingTemplatesLocation || + DEFAULT_MAPPING_TEMPLATE_LOCATION, ); const { defaultMappingTemplates = {} } = cfg; @@ -143,7 +237,7 @@ export default function getAppSyncConfig(context, appSyncConfig) { url = func.url; method = func.method; } else { - url = `http://localhost:${context.options.lambdaPort}/2015-03-31/functions/${func.name}/invocations`; + url = `${context.options.httpProtocol}://${context.options.httpHost}:${context.options.lambdaPort}/2015-03-31/functions/${func.name}/invocations`; } return { ...dataSource, diff --git a/src/index.js b/src/index.js index c00227c..f6e1fcc 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import { AmplifyAppSyncSimulator, addDataLoader, -} from 'amplify-appsync-simulator'; +} from '@aws-amplify/amplify-appsync-simulator'; import { inspect } from 'util'; import { defaults, get, merge, reduce } from 'lodash'; import NodeEvaluator from 'cfn-resolver-lib'; @@ -27,7 +27,10 @@ class ServerlessAppSyncSimulator { this.simulators = null; addDataLoader('HTTP', HttpDataLoader); - addDataLoader('AMAZON_ELASTICSEARCH', ElasticDataLoader); + + // not needed anymore, this loader is registered by the new version of @aws-amplify/amplify-appsync-simulator + // addDataLoader('AMAZON_ELASTICSEARCH', ElasticDataLoader); + addDataLoader('RELATIONAL_DATABASE', RelationalDataLoader); this.hooks = { @@ -46,6 +49,31 @@ class ServerlessAppSyncSimulator { } } + getHttpProtocol(context) { + // Default serverless-offline httpsProtocol is empty + let protocol = 'http'; + const offlineConfig = context.service.custom['serverless-offline']; + // Check if the user has defined a certificate for https as part of their serverless.yml + if (offlineConfig != undefined && offlineConfig.httpsProtocol != undefined) { + protocol = 'https' + } + + return protocol; + } + + + getHttpHost(context) { + // Default serverless-offline httpHost is localhost + let host = 'localhost'; + const offlineConfig = context.service.custom['serverless-offline']; + // Check if the user has defined a specific host as part of their serverless.yml + if (offlineConfig != undefined && offlineConfig.host != undefined) { + host = offlineConfig.host; + } + + return host; + } + getLambdaPort(context) { // Default serverless-offline lambdaPort is 3002 let port = 3002; @@ -100,6 +128,8 @@ class ServerlessAppSyncSimulator { } this.options.lambdaPort = this.getLambdaPort(this.serverless); + this.options.httpHost = this.getHttpHost(this.serverless); + this.options.httpProtocol = this.getHttpProtocol(this.serverless); if (Array.isArray(this.options.watch) && this.options.watch.length > 0) { this.watch();