serverless-offline 9.1.0 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,8 +7,8 @@
7
7
  <a href="https://www.npmjs.com/package/serverless-offline">
8
8
  <img src="https://img.shields.io/npm/v/serverless-offline.svg?style=flat-square">
9
9
  </a>
10
- <a href="https://github.com/dherault/serverless-offline/actions?query=workflow%3ACI">
11
- <img src="https://img.shields.io/github/workflow/status/dherault/serverless-offline/CI?style=flat-square">
10
+ <a href="https://github.com/dherault/serverless-offline/actions/workflows/integrate.yml">
11
+ <img src="https://img.shields.io/github/workflow/status/dherault/serverless-offline/Integrate">
12
12
  </a>
13
13
  <img src="https://img.shields.io/node/v/serverless-offline.svg?style=flat-square">
14
14
  <a href="https://github.com/serverless/serverless">
@@ -29,9 +29,9 @@
29
29
  This [Serverless](https://github.com/serverless/serverless) plugin emulates [AWS λ](https://aws.amazon.com/lambda) and [API Gateway](https://aws.amazon.com/api-gateway) on your local machine to speed up your development cycles.
30
30
  To do so, it starts an HTTP server that handles the request's lifecycle like APIG does and invokes your handlers.
31
31
 
32
- **Features:**
32
+ **Features**
33
33
 
34
- - [Node.js](https://nodejs.org), [Python](https://www.python.org), [Ruby](https://www.ruby-lang.org) and [Go](https://golang.org) λ runtimes.
34
+ - [Node.js](https://nodejs.org), [Python](https://www.python.org), [Ruby](https://www.ruby-lang.org), [Go](https://golang.org), [Java](https://www.java.com) (incl. [Kotlin](https://kotlinlang.org), [Groovy](https://groovy-lang.org), [Scala](https://www.scala-lang.org)) λ runtimes.
35
35
  - Velocity templates support.
36
36
  - Lazy loading of your handler files.
37
37
  - And more: integrations, authorizers, proxies, timeouts, responseParameters, HTTPS, CORS, etc...
@@ -42,6 +42,7 @@ This plugin is updated by its users, I just do maintenance and ensure that PRs a
42
42
 
43
43
  - [Installation](#installation)
44
44
  - [Usage and command line options](#usage-and-command-line-options)
45
+ - [Run modes](#run-modes)
45
46
  - [Usage with `invoke`](#usage-with-invoke)
46
47
  - [The `process.env.IS_OFFLINE` variable](#the-processenvis_offline-variable)
47
48
  - [Docker and Layers](#docker-and-layers)
@@ -84,7 +85,7 @@ Then inside your project's `serverless.yml` file add following entry to the plug
84
85
 
85
86
  It should look something like this:
86
87
 
87
- ```YAML
88
+ ```yml
88
89
  plugins:
89
90
  - serverless-offline
90
91
  ```
@@ -146,13 +147,13 @@ All CLI options are optional:
146
147
 
147
148
  Any of the CLI options can be added to your `serverless.yml`. For example:
148
149
 
149
- ```
150
+ ```yml
150
151
  custom:
151
152
  serverless-offline:
152
- httpsProtocol: "dev-certs"
153
+ httpsProtocol: 'dev-certs'
153
154
  httpPort: 4000
154
155
  stageVariables:
155
- foo: "bar"
156
+ foo: 'bar'
156
157
  ```
157
158
 
158
159
  Options passed on the command line override YAML options.
@@ -164,18 +165,64 @@ By default you can send your requests to `http://localhost:3000/`. Please note t
164
165
  But if you send an `application/x-www-form-urlencoded` or a `multipart/form-data` body with an `application/json` (or no) Content-Type, API Gateway won't parse your data (you'll get the ugly raw as input), whereas the plugin will answer 400 (malformed JSON).
165
166
  Please consider explicitly setting your requests' Content-Type and using separate templates.
166
167
 
168
+ ## Run modes
169
+
170
+ ### node.js
171
+
172
+ Lambda handlers for the `node.js` runtime can run in different execution modes with `serverless-offline` and they have subtle differences with a variety of pros and cons. they are mutually exclusive and it is planned to combine the flags into one single flag in the future.
173
+
174
+ #### worker-threads (default)
175
+
176
+ - handlers run in their own context
177
+ - memory is not being shared between handlers, memory consumption is therefore higher
178
+ - memory is being released when handlers reload or after usage
179
+ - environment (process.env) is not being shared across handlers
180
+ - global state is not being shared across handlers
181
+ - easy debugging
182
+
183
+ #### in-process
184
+
185
+ - handlers run in the same context (instance) as `serverless` and `serverless-offline`
186
+ - memory is being shared across lambda handlers as well as with `serverless` and `serverless-offline`
187
+ - no reloading capabilities as it is [currently] not possible to implement for commonjs handlers (without memory leaks) and for esm handlers
188
+ - environment (process.env) is being shared across handlers as well as with `serverless` and `serverless-offline`
189
+ - global state is being shared across lambda handlers as well as with `serverless` and `serverless-offline`
190
+ - easy debugging
191
+
192
+ #### child-processes
193
+
194
+ - handlers run in a separate node.js instance
195
+ - memory is not being shared between handlers, memory consumption is therefore higher
196
+ - memory is being released when handlers reload or after usage
197
+ - environment (process.env) is not being shared across handlers
198
+ - global state is not being shared across handlers
199
+ - debugging more complicated
200
+
201
+ #### docker
202
+
203
+ - handlers run in a docker container
204
+ - memory is not being shared between handlers, memory consumption is therefore higher
205
+ - memory is being released when handlers reload or after usage
206
+ - environment (process.env) is not being shared across handlers
207
+ - global state is not being shared across handlers
208
+ - debugging more complicated
209
+
210
+ ### Python, Ruby, Go, Java (incl. Kotlin, Groovy, Scala)
211
+
212
+ the Lambda handler process is running in a child process.
213
+
167
214
  ## Usage with `invoke`
168
215
 
169
- To use `Lambda.invoke` you need to set the lambda endpoint to the serverless-offline endpoint:
216
+ To use `Lambda.invoke` you need to set the lambda endpoint to the `serverless-offline` endpoint:
170
217
 
171
218
  ```js
219
+ const { env } = require('node:process')
172
220
  const { Lambda } = require('aws-sdk')
173
221
 
174
222
  const lambda = new Lambda({
175
223
  apiVersion: '2015-03-31',
176
- // endpoint needs to be set only if it deviates from the default, e.g. in a dev environment
177
- // process.env.SOME_VARIABLE could be set in e.g. serverless.yml for provider.environment or function.environment
178
- endpoint: process.env.SOME_VARIABLE
224
+ // endpoint needs to be set only if it deviates from the default
225
+ endpoint: env.IS_OFFLINE
179
226
  ? 'http://localhost:3002'
180
227
  : 'https://lambda.us-east-1.amazonaws.com',
181
228
  })
@@ -184,15 +231,33 @@ const lambda = new Lambda({
184
231
  All your lambdas can then be invoked in a handler using
185
232
 
186
233
  ```js
234
+ const { Buffer } = require('node:buffer')
235
+ const { Lambda } = require('aws-sdk')
236
+
237
+ const { stringify } = JSON
238
+
239
+ const lambda = new Lambda({
240
+ apiVersion: '2015-03-31',
241
+ endpoint: 'http://localhost:3002',
242
+ })
243
+
187
244
  exports.handler = async function () {
245
+ const clientContextData = stringify({ foo: 'foo' })
246
+
188
247
  const params = {
248
+ ClientContext: Buffer.from(clientContextData).toString('base64'),
189
249
  // FunctionName is composed of: service name - stage - function name, e.g.
190
250
  FunctionName: 'myServiceName-dev-invokedHandler',
191
251
  InvocationType: 'RequestResponse',
192
- Payload: JSON.stringify({ data: 'foo' }),
252
+ Payload: stringify({ data: 'foo' }),
193
253
  }
194
254
 
195
255
  const response = await lambda.invoke(params).promise()
256
+
257
+ return {
258
+ body: stringify(response),
259
+ statusCode: 200,
260
+ }
196
261
  }
197
262
  ```
198
263
 
@@ -244,7 +309,7 @@ to calling it via `aws-sdk`.
244
309
 
245
310
  ## The `process.env.IS_OFFLINE` variable
246
311
 
247
- Will be `"true"` in your handlers and throughout the plugin.
312
+ Will be `"true"` in your handlers when using `serverless-offline`.
248
313
 
249
314
  ## Docker and Layers
250
315
 
@@ -326,11 +391,11 @@ Only [custom authorizers](https://aws.amazon.com/blogs/compute/introducing-custo
326
391
 
327
392
  The Custom authorizer is passed an `event` object as below:
328
393
 
329
- ```javascript
394
+ ```js
330
395
  {
331
- "type": "TOKEN",
332
396
  "authorizationToken": "<Incoming bearer token>",
333
- "methodArn": "arn:aws:execute-api:<Region id>:<Account id>:<API id>/<Stage>/<Method>/<Resource path>"
397
+ "methodArn": "arn:aws:execute-api:<Region id>:<Account id>:<API id>/<Stage>/<Method>/<Resource path>",
398
+ "type": "TOKEN"
334
399
  }
335
400
  ```
336
401
 
@@ -338,11 +403,11 @@ The `methodArn` does not include the Account id or API id.
338
403
 
339
404
  The plugin only supports retrieving Tokens from headers. You can configure the header as below:
340
405
 
341
- ```javascript
406
+ ```js
342
407
  "authorizer": {
343
- "type": "TOKEN",
408
+ "authorizerResultTtlInSeconds": "0",
344
409
  "identitySource": "method.request.header.Authorization", // or method.request.header.SomeOtherHeader
345
- "authorizerResultTtlInSeconds": "0"
410
+ "type": "TOKEN"
346
411
  }
347
412
  ```
348
413
 
@@ -370,14 +435,14 @@ If your authentication needs are custom and not satisfied by the existing capabi
370
435
  ```js
371
436
  module.exports = function (endpoint, functionKey, method, path) {
372
437
  return {
373
- name: 'your strategy name',
374
- scheme: 'your scheme name',
375
-
376
438
  getAuthenticateFunction: () => ({
377
439
  async authenticate(request, h) {
378
440
  // your implementation
379
441
  },
380
442
  }),
443
+
444
+ name: 'your strategy name',
445
+ scheme: 'your scheme name',
381
446
  }
382
447
  }
383
448
  ```
@@ -441,7 +506,7 @@ Now let's make a request with this body: `{ "id": 1 }`
441
506
 
442
507
  AWS parses the event as such:
443
508
 
444
- ```javascript
509
+ ```js
445
510
  {
446
511
  "payload": {
447
512
  "id": 1
@@ -453,7 +518,7 @@ AWS parses the event as such:
453
518
 
454
519
  Whereas Offline parses:
455
520
 
456
- ```javascript
521
+ ```js
457
522
  {
458
523
  "payload": {
459
524
  "id": 1
@@ -505,7 +570,7 @@ Works out of the box. See examples in the manual_test directory.
505
570
 
506
571
  Example of enabling proxy:
507
572
 
508
- ```
573
+ ```yml
509
574
  custom:
510
575
  serverless-offline:
511
576
  resourceRoutes: true
@@ -513,18 +578,18 @@ custom:
513
578
 
514
579
  or
515
580
 
516
- ```
581
+ ```yml
517
582
  YourCloudFormationMethodId:
518
- Type: AWS::ApiGateway::Method
519
583
  Properties:
520
584
  ......
521
585
  Integration:
522
586
  Type: HTTP_PROXY
523
587
  Uri: 'https://s3-${self:custom.region}.amazonaws.com/${self:custom.yourBucketName}/{proxy}'
524
588
  ......
589
+ Type: AWS::ApiGateway::Method
525
590
  ```
526
591
 
527
- ```
592
+ ```yml
528
593
  custom:
529
594
  serverless-offline:
530
595
  resourceRoutes:
@@ -542,7 +607,7 @@ May not work properly. Please PR. (Difficulty: hard?)
542
607
 
543
608
  Example response velocity template:
544
609
 
545
- ```javascript
610
+ ```js
546
611
  "responseParameters": {
547
612
  "method.response.header.X-Powered-By": "Serverless", // a string
548
613
  "method.response.header.Warning": "integration.response.body", // the whole response
@@ -559,7 +624,9 @@ Usage in order to send messages back to clients:
559
624
  Or,
560
625
 
561
626
  ```js
562
- const apiGatewayManagementApi = new AWS.ApiGatewayManagementApi({
627
+ const { ApiGatewayManagementApi } = require('aws-sdk')
628
+
629
+ const apiGatewayManagementApi = new ApiGatewayManagementApi({
563
630
  apiVersion: '2018-11-29',
564
631
  endpoint: 'http://localhost:3001',
565
632
  });
@@ -648,7 +715,7 @@ You can change this profile directly in the code or by setting proper environmen
648
715
  ## Simulation quality
649
716
 
650
717
  This plugin simulates API Gateway for many practical purposes, good enough for development - but is not a perfect simulator.
651
- Specifically, Lambda currently runs on Node.js v10.x, v12.x and v14.x ([AWS Docs](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html)), whereas _Offline_ runs on your own runtime where no memory limits are enforced.
718
+ Specifically, Lambda currently runs on Node.js v12.x, v14.x and v16.x ([AWS Docs](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html)), whereas _Offline_ runs on your own runtime where no memory limits are enforced.
652
719
 
653
720
  ## Usage with other plugins
654
721
 
@@ -661,7 +728,7 @@ Plugins are executed in order, so plugins that process your code or add resource
661
728
 
662
729
  For example:
663
730
 
664
- ```yaml
731
+ ```yml
665
732
  plugins:
666
733
  - serverless-middleware # modifies some of your handler based on configuration
667
734
  - serverless-webpack # package your javascript handlers using webpack
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dedicatedTo": "Blue, a great migrating bird.",
3
3
  "name": "serverless-offline",
4
- "version": "9.1.0",
4
+ "version": "9.1.1",
5
5
  "description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
6
6
  "license": "MIT",
7
7
  "main": "./src/index.js",
@@ -194,7 +194,7 @@
194
194
  "@hapi/boom": "^10.0.0",
195
195
  "@hapi/h2o2": "^9.1.0",
196
196
  "@hapi/hapi": "^20.2.2",
197
- "aws-sdk": "^2.1181.0",
197
+ "aws-sdk": "^2.1184.0",
198
198
  "boxen": "^7.0.0",
199
199
  "chalk": "^5.0.1",
200
200
  "execa": "^6.1.0",
@@ -9,7 +9,7 @@ export const DEFAULT_LAMBDA_RUNTIME = 'nodejs14.x'
9
9
  // https://docs.aws.amazon.com/lambda/latest/dg/limits.html
10
10
  export const DEFAULT_LAMBDA_MEMORY_SIZE = 1024
11
11
  // default function timeout in seconds
12
- export const DEFAULT_LAMBDA_TIMEOUT = 900 // 15 min
12
+ export const DEFAULT_LAMBDA_TIMEOUT = 6 // 6 seconds
13
13
 
14
14
  // timeout for all connections to be closed
15
15
  export const SERVER_SHUTDOWN_TIMEOUT = 5000
@@ -15,8 +15,8 @@ import {
15
15
  } from '../config/index.js'
16
16
  import { createUniqueId, splitHandlerPathAndName } from '../utils/index.js'
17
17
 
18
- const { entries, fromEntries } = Object
19
18
  const { ceil } = Math
19
+ const { entries, fromEntries } = Object
20
20
 
21
21
  export default class LambdaFunction {
22
22
  #artifact = null
@@ -41,14 +41,18 @@ export default class ChildProcessRunner {
41
41
  },
42
42
  )
43
43
 
44
- const message = new Promise((res, rej) => {
45
- childProcess.on('message', (data) => {
46
- if (data.error) rej(data.error)
47
- else res(data)
44
+ let message
45
+
46
+ try {
47
+ message = new Promise((res, rej) => {
48
+ childProcess.on('message', (data) => {
49
+ if (data.error) rej(data.error)
50
+ else res(data)
51
+ })
48
52
  })
49
- }).finally(() => {
53
+ } finally {
50
54
  childProcess.kill()
51
- })
55
+ }
52
56
 
53
57
  childProcess.send({
54
58
  context,
@@ -12,6 +12,8 @@ import pRetry from 'p-retry'
12
12
  import DockerImage from './DockerImage.js'
13
13
 
14
14
  const { stringify } = JSON
15
+ const { floor, log: mathLog } = Math
16
+ const { parseFloat } = Number
15
17
  const { entries, hasOwn } = Object
16
18
 
17
19
  export default class DockerContainer {
@@ -402,7 +404,7 @@ export default class DockerContainer {
402
404
  const dm = decimals < 0 ? 0 : decimals
403
405
  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
404
406
 
405
- const i = Math.floor(Math.log(bytes) / Math.log(k))
407
+ const i = floor(mathLog(bytes) / mathLog(k))
406
408
 
407
409
  return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`
408
410
  }
@@ -1,9 +1,9 @@
1
1
  import { mkdir, readFile, rm, rmdir, writeFile } from 'node:fs/promises'
2
2
  import { EOL } from 'node:os'
3
- import { sep, resolve, parse as pathParse } from 'node:path'
4
3
  import process, { chdir, cwd } from 'node:process'
4
+ import { parse as pathParse, resolve, sep } from 'node:path'
5
5
  import { log } from '@serverless/utils/log.js'
6
- import { execa, execaSync } from 'execa'
6
+ import { execa } from 'execa'
7
7
 
8
8
  const { parse, stringify } = JSON
9
9
 
@@ -18,10 +18,10 @@ export default class GoRunner {
18
18
 
19
19
  #handlerPath = null
20
20
 
21
- #tmpPath = null
22
-
23
21
  #tmpFile = null
24
22
 
23
+ #tmpPath = null
24
+
25
25
  constructor(funOptions, env) {
26
26
  const { handlerPath, codeDir } = funOptions
27
27
 
@@ -34,8 +34,10 @@ export default class GoRunner {
34
34
  try {
35
35
  // refresh go.mod
36
36
  await rm(this.#tmpFile)
37
- execaSync('go', ['mod', 'tidy'])
38
- await rmdir(this.#tmpPath, { recursive: true })
37
+ await execa('go', ['mod', 'tidy'])
38
+ await rmdir(this.#tmpPath, {
39
+ recursive: true,
40
+ })
39
41
  } catch {
40
42
  // @ignore
41
43
  }
@@ -122,8 +124,11 @@ export default class GoRunner {
122
124
  chdir(cwdPath.substring(0, cwdPath.indexOf('main.go')))
123
125
 
124
126
  // Make sure we have the mock-lambda runner
125
- execaSync('go', ['get', 'github.com/icarus-sullivan/mock-lambda@e065469'])
126
- execaSync('go', ['build'])
127
+ await execa('go', [
128
+ 'get',
129
+ 'github.com/icarus-sullivan/mock-lambda@e065469',
130
+ ])
131
+ await execa('go', ['build'])
127
132
  } catch {
128
133
  // @ignore
129
134
  }
@@ -3,6 +3,7 @@ import { performance } from 'node:perf_hooks'
3
3
  import process from 'node:process'
4
4
  import { log } from '@serverless/utils/log.js'
5
5
 
6
+ const { floor } = Math
6
7
  const { assign } = Object
7
8
 
8
9
  const require = createRequire(import.meta.url)
@@ -88,11 +89,11 @@ export default class InProcessRunner {
88
89
  ...context,
89
90
  done: (err, data) => callback(err, data),
90
91
  fail: (err) => callback(err),
91
- getRemainingTimeInMillis: () => {
92
+ getRemainingTimeInMillis() {
92
93
  const timeLeft = executionTimeout - performance.now()
93
94
 
94
95
  // just return 0 for now if we are beyond alotted time (timeout)
95
- return timeLeft > 0 ? timeLeft : 0
96
+ return timeLeft > 0 ? floor(timeLeft) : 0
96
97
  },
97
98
  succeed: (res) => callback(null, res),
98
99
  }
@@ -2,10 +2,10 @@ import { env } from 'node:process'
2
2
  import { parentPort, workerData } from 'node:worker_threads'
3
3
  import InProcessRunner from '../in-process-runner/index.js'
4
4
 
5
- const { functionKey, handlerName, handlerPath } = workerData
5
+ const { functionKey, handlerName, handlerPath, timeout } = workerData
6
6
 
7
7
  parentPort.on('message', async (messageData) => {
8
- const { context, event, port, timeout } = messageData
8
+ const { context, event, port } = messageData
9
9
 
10
10
  // TODO we could probably cache this in the module scope?
11
11
  const inProcessRunner = new InProcessRunner(