Skip to content

Commit 538e066

Browse files
authored
feat: Additional runtime support via Serverless Offline invocation endpoint (#74)
BREAKING: - removed `lambda.loadLocalEnv` option - Execute Lambda through the `serverless-offline` plugin
1 parent ec6aca4 commit 538e066

File tree

3 files changed

+62
-70
lines changed

3 files changed

+62
-70
lines changed

README.md

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ![Release](https://github.com/bboure/serverless-appsync-simulator/workflows/Release/badge.svg) <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
22
[![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors-)
3+
34
<!-- ALL-CONTRIBUTORS-BADGE:END -->
45

56
This serverless plugin is a wrapper for [amplify-appsync-simulator](https://github.com/aws-amplify/amplify-cli/tree/master/packages/amplify-appsync-simulator) made for testing AppSync APIs built with [serverless-appsync-plugin](https://github.com/sid88in/serverless-appsync-plugin).
@@ -51,24 +52,23 @@ Serverless: GraphiQl: http://localhost:20002
5152

5253
Put options under `custom.appsync-simulator` in your `serverless.yml` file
5354

54-
| option | default | description |
55-
| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
56-
| apiKey | `0123456789` | When using `API_KEY` as authentication type, the key to authenticate to the endpoint. |
57-
| port | 20002 | AppSync operations port; if using multiple APIs, the value of this option will be used as a starting point, and each other API will have a port of lastPort + 10 (e.g. 20002, 20012, 20022, etc.) |
58-
| wsPort | 20003 | AppSync subscriptions port; if using multiple APIs, the value of this option will be used as a starting point, and each other API will have a port of lastPort + 10 (e.g. 20003, 20013, 20023, etc.) |
59-
| location | . (base directory) | Location of the lambda functions handlers. |
60-
| lambda.loadLocalEnv | false | If `true`, all environment variables (`$ env`) will be accessible from the resolver function. Read more in section [Environment variables](#environment-variables). |
61-
| refMap | {} | A mapping of [resource resolutions](#resource-cloudformation-functions-resolution) for the `Ref` function |
62-
| getAttMap | {} | A mapping of [resource resolutions](#resource-cloudformation-functions-resolution) for the `GetAtt` function |
63-
| importValueMap | {} | A mapping of [resource resolutions](#resource-cloudformation-functions-resolution) for the `ImportValue` function |
64-
| functions | {} | A mapping of [external functions](#functions) for providing invoke url for external fucntions |
65-
| dynamoDb.endpoint | http://localhost:8000 | Dynamodb endpoint. Specify it if you're not using serverless-dynamodb-local. Otherwise, port is taken from dynamodb-local conf |
66-
| dynamoDb.region | localhost | Dynamodb region. Specify it if you're connecting to a remote Dynamodb intance. |
67-
| dynamoDb.accessKeyId | DEFAULT_ACCESS_KEY | AWS Access Key ID to access DynamoDB |
68-
| dynamoDb.secretAccessKey | DEFAULT_SECRET | AWS Secret Key to access DynamoDB |
69-
| dynamoDb.sessionToken | DEFAULT_ACCESS_TOKEEN | AWS Session Token to access DynamoDB, only if you have temporary security credentials configured on AWS |
70-
| dynamoDb.* | | You can add every configuration accepted by [DynamoDB SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#constructor-property) |
71-
| watch | - \*.graphql<br/> - \*.vtl | Array of glob patterns to watch for hot-reloading. |
55+
| option | default | description |
56+
| ------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57+
| apiKey | `0123456789` | When using `API_KEY` as authentication type, the key to authenticate to the endpoint. |
58+
| port | 20002 | AppSync operations port; if using multiple APIs, the value of this option will be used as a starting point, and each other API will have a port of lastPort + 10 (e.g. 20002, 20012, 20022, etc.) |
59+
| wsPort | 20003 | AppSync subscriptions port; if using multiple APIs, the value of this option will be used as a starting point, and each other API will have a port of lastPort + 10 (e.g. 20003, 20013, 20023, etc.) |
60+
| location | . (base directory) | Location of the lambda functions handlers. |
61+
| refMap | {} | A mapping of [resource resolutions](#resource-cloudformation-functions-resolution) for the `Ref` function |
62+
| getAttMap | {} | A mapping of [resource resolutions](#resource-cloudformation-functions-resolution) for the `GetAtt` function |
63+
| importValueMap | {} | A mapping of [resource resolutions](#resource-cloudformation-functions-resolution) for the `ImportValue` function |
64+
| functions | {} | A mapping of [external functions](#functions) for providing invoke url for external fucntions |
65+
| dynamoDb.endpoint | http://localhost:8000 | Dynamodb endpoint. Specify it if you're not using serverless-dynamodb-local. Otherwise, port is taken from dynamodb-local conf |
66+
| dynamoDb.region | localhost | Dynamodb region. Specify it if you're connecting to a remote Dynamodb intance. |
67+
| dynamoDb.accessKeyId | DEFAULT_ACCESS_KEY | AWS Access Key ID to access DynamoDB |
68+
| dynamoDb.secretAccessKey | DEFAULT_SECRET | AWS Secret Key to access DynamoDB |
69+
| dynamoDb.sessionToken | DEFAULT_ACCESS_TOKEEN | AWS Session Token to access DynamoDB, only if you have temporary security credentials configured on AWS |
70+
| dynamoDb.\* | | You can add every configuration accepted by [DynamoDB SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#constructor-property) |
71+
| watch | - \*.graphql<br/> - \*.vtl | Array of glob patterns to watch for hot-reloading. |
7272

7373
Example:
7474

@@ -90,6 +90,7 @@ Hot-reloading relies on [watchman](https://facebook.github.io/watchman). Make su
9090
You can change the files being watched with the `watch` option, which is then passed to watchman as [the match expression](https://facebook.github.io/watchman/docs/expr/match.html).
9191

9292
e.g.
93+
9394
```
9495
custom:
9596
appsync-simulator:
@@ -202,21 +203,6 @@ custom:
202203
value: 'https://other.api.url.com/graphql'
203204
```
204205

205-
## Environment variables
206-
207-
```yaml
208-
custom:
209-
appsync-simulator:
210-
lambda:
211-
loadLocalEnv: true
212-
```
213-
214-
If `true`, all environment variables (`$ env`) will be accessible from the resolver function.
215-
216-
If `false`, only environment variables defined in `serverless.yml` will be accessible from the resolver function.
217-
218-
> _Note: `serverless.yml` environment variables have higher priority than local environment variables. Thus some of your local environment variables, could get overridden by environment variables from `serverless.yml`._
219-
220206
## Limitations
221207

222208
This plugin only tries to resolve the following parts of the yml tree:

src/getAppSyncConfig.js

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { AmplifyAppSyncSimulatorAuthenticationType as AuthTypes } from 'amplify-appsync-simulator';
2-
import { invoke } from 'amplify-nodejs-function-runtime-provider/lib/utils/invoke';
32
import axios from 'axios';
43
import fs from 'fs';
54
import { forEach, isNil, first } from 'lodash';
@@ -37,7 +36,10 @@ export default function getAppSyncConfig(context, appSyncConfig) {
3736
};
3837

3938
const toAbsolutePosixPath = (basePath, filePath) =>
40-
(path.isAbsolute(filePath) ? filePath : path.join(basePath, filePath)).replace(/\\/g, '/');
39+
(path.isAbsolute(filePath)
40+
? filePath
41+
: path.join(basePath, filePath)
42+
).replace(/\\/g, '/');
4143

4244
const globFilePaths = (basePath, filePaths) => {
4345
return filePaths
@@ -90,48 +92,36 @@ export default function getAppSyncConfig(context, appSyncConfig) {
9092
}
9193

9294
const conf = context.options;
93-
if (conf.functions && conf.functions[functionName] !== undefined) {
94-
const func = conf.functions[functionName];
95-
return {
96-
...dataSource,
97-
invoke: async (payload) => {
98-
const result = await axios.request({
99-
url: func.url,
100-
method: func.method,
101-
data: payload,
102-
validateStatus: false,
103-
});
104-
return result.data;
105-
},
106-
};
107-
}
95+
const func =
96+
conf.functions?.[functionName] ||
97+
context.serverless.service.functions?.[functionName];
10898

109-
const func = context.serverless.service.functions[functionName];
11099
if (func === undefined) {
111100
context.plugin.log(`The ${functionName} function is not defined`, {
112101
color: 'orange',
113102
});
114103
return null;
115104
}
116105

106+
let url, method;
107+
if (func.url) {
108+
url = func.url;
109+
method = func.method;
110+
} else {
111+
url = `http://localhost:${context.options.lambdaPort}/2015-03-31/functions/${func.name}/invocations`;
112+
}
117113
return {
118114
...dataSource,
119-
invoke: (payload) =>
120-
invoke({
121-
packageFolder: path.join(
122-
context.serverless.config.servicePath,
123-
context.options.location,
124-
),
125-
handler: func.handler,
126-
event: JSON.stringify(payload),
127-
environment: {
128-
...(context.options.lambda.loadLocalEnv === true
129-
? process.env
130-
: {}),
131-
...context.serverless.service.provider.environment,
132-
...func.environment,
133-
},
134-
}),
115+
invoke: async (payload) => {
116+
const result = await axios.request({
117+
url,
118+
method: method || 'POST',
119+
data: payload,
120+
headers: payload.request?.headers,
121+
validateStatus: false,
122+
});
123+
return result.data;
124+
},
135125
};
136126
}
137127
case 'AMAZON_ELASTICSEARCH':

src/index.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@ class ServerlessAppSyncSimulator {
4545
}
4646
}
4747

48+
getLambdaPort(context) {
49+
// Default serverless-offline lambdaPort is 3002
50+
let port = 3002;
51+
const offlineConfig = context.service.custom['serverless-offline'];
52+
// Check if the user has defined a specific port as part of their serverless.yml
53+
if (offlineConfig != undefined && offlineConfig.lambdaPort != undefined) {
54+
port = offlineConfig.lambdaPort;
55+
}
56+
// Check to see if a port override was specified as part of the CLI arguments
57+
const cliOptions = context.processedInput.options;
58+
if (cliOptions != undefined && cliOptions.lambdaPort != undefined) {
59+
port = cliOptions.lambdaPort;
60+
}
61+
62+
return port;
63+
}
64+
4865
async startServers() {
4966
try {
5067
this.buildResolvedOptions();
@@ -81,6 +98,8 @@ class ServerlessAppSyncSimulator {
8198
});
8299
}
83100

101+
this.options.lambdaPort = this.getLambdaPort(this.serverless);
102+
84103
if (Array.isArray(this.options.watch) && this.options.watch.length > 0) {
85104
this.watch();
86105
} else {
@@ -246,9 +265,6 @@ class ServerlessAppSyncSimulator {
246265
port: 20002,
247266
wsPort: 20003,
248267
location: '.',
249-
lambda: {
250-
loadLocalEnv: false,
251-
},
252268
refMap: {},
253269
getAttMap: {},
254270
importValueMap: {},

0 commit comments

Comments
 (0)