serverless-offline 9.2.5 → 9.3.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
@@ -215,10 +215,10 @@ the Lambda handler process is running in a child process.
215
215
  To use `Lambda.invoke` you need to set the lambda endpoint to the `serverless-offline` endpoint:
216
216
 
217
217
  ```js
218
- const { env } = require('node:process')
219
- const { Lambda } = require('aws-sdk')
218
+ import { env } from 'node:process'
219
+ import aws from 'aws-sdk'
220
220
 
221
- const lambda = new Lambda({
221
+ const lambda = new aws.Lambda({
222
222
  apiVersion: '2015-03-31',
223
223
  // endpoint needs to be set only if it deviates from the default
224
224
  endpoint: env.IS_OFFLINE
@@ -230,17 +230,17 @@ const lambda = new Lambda({
230
230
  All your lambdas can then be invoked in a handler using
231
231
 
232
232
  ```js
233
- const { Buffer } = require('node:buffer')
234
- const { Lambda } = require('aws-sdk')
233
+ import { Buffer } from 'node:buffer'
234
+ import aws from 'aws-sdk'
235
235
 
236
236
  const { stringify } = JSON
237
237
 
238
- const lambda = new Lambda({
238
+ const lambda = new aws.Lambda({
239
239
  apiVersion: '2015-03-31',
240
240
  endpoint: 'http://localhost:3002',
241
241
  })
242
242
 
243
- exports.handler = async function () {
243
+ export async function handler() {
244
244
  const clientContextData = stringify({ foo: 'foo' })
245
245
 
246
246
  const params = {
@@ -623,9 +623,9 @@ Usage in order to send messages back to clients:
623
623
  Or,
624
624
 
625
625
  ```js
626
- const { ApiGatewayManagementApi } = require('aws-sdk')
626
+ import aws from 'aws-sdk'
627
627
 
628
- const apiGatewayManagementApi = new ApiGatewayManagementApi({
628
+ const apiGatewayManagementApi = new aws.ApiGatewayManagementApi({
629
629
  apiVersion: '2018-11-29',
630
630
  endpoint: 'http://localhost:3001',
631
631
  });
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "dedicatedTo": "Blue, a great migrating bird.",
3
3
  "name": "serverless-offline",
4
- "version": "9.2.5",
4
+ "version": "9.3.1",
5
5
  "description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
6
6
  "license": "MIT",
7
- "main": "./src/index.js",
8
7
  "exports": "./src/index.js",
9
8
  "type": "module",
10
9
  "scripts": {
@@ -189,18 +188,18 @@
189
188
  "@hapi/h2o2": "^9.1.0",
190
189
  "@hapi/hapi": "^20.2.2",
191
190
  "@serverless/utils": "^6.7.0",
192
- "aws-sdk": "^2.1195.0",
191
+ "aws-sdk": "^2.1205.0",
193
192
  "boxen": "^7.0.0",
194
193
  "chalk": "^5.0.1",
195
194
  "execa": "^6.1.0",
196
195
  "fs-extra": "^10.1.0",
197
196
  "java-invoke-local": "0.0.6",
198
197
  "js-string-escape": "^1.0.1",
199
- "jsonpath-plus": "^7.0.0",
198
+ "jsonpath-plus": "^7.1.0",
200
199
  "jsonschema": "^1.4.1",
201
200
  "jsonwebtoken": "^8.5.1",
202
201
  "jszip": "^3.10.1",
203
- "luxon": "^3.0.1",
202
+ "luxon": "^3.0.3",
204
203
  "node-fetch": "^3.2.10",
205
204
  "node-schedule": "^2.1.0",
206
205
  "object.hasown": "^1.1.1",
@@ -211,7 +210,7 @@
211
210
  },
212
211
  "devDependencies": {
213
212
  "archiver": "^5.3.1",
214
- "eslint": "^8.22.0",
213
+ "eslint": "^8.23.0",
215
214
  "eslint-config-airbnb-base": "^15.0.0",
216
215
  "eslint-config-prettier": "^8.5.0",
217
216
  "eslint-plugin-import": "^2.25.4",
@@ -83,6 +83,15 @@ export default class ServerlessOffline {
83
83
  }
84
84
 
85
85
  await Promise.all(eventModules)
86
+
87
+ if (this.#options.useChildProcesses) {
88
+ log.notice()
89
+ log.warning(
90
+ orange(`'--useChildProcesses' is deprecated and will be removed in the next major version. Worker threads, the current default, should provide the same if not an even better developer experience.
91
+ If you are experiencing any issues please let us know: https://github.com/dherault/serverless-offline/issues`),
92
+ )
93
+ log.notice()
94
+ }
86
95
  }
87
96
 
88
97
  async #ready() {
@@ -225,14 +234,6 @@ export default class ServerlessOffline {
225
234
  ...this.#cliOptions,
226
235
  }
227
236
 
228
- if (this.#options.useChildProcesses) {
229
- log.notice()
230
- log.warning(
231
- orange(`'--useChildProcesses' is deprecated and will be removed in the next major version. Worker threads, the current default, should provide the same if not an even better developer experience.
232
- If you are experiencing any issues please let us know: https://github.com/dherault/serverless-offline/issues`),
233
- )
234
- }
235
-
236
237
  // Parse CORS options
237
238
  this.#options.corsAllowHeaders = this.#options.corsAllowHeaders
238
239
  .replace(/\s/g, '')
@@ -2,10 +2,10 @@
2
2
  // https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
3
3
 
4
4
  // .NET CORE
5
- export const supportedDotnetcore = new Set([
6
- // 'dotnet6',
7
- // 'dotnetcore3.1',
8
- ])
5
+ // export const supportedDotnetcore = new Set([
6
+ // 'dotnet6',
7
+ // 'dotnetcore3.1',
8
+ // ])
9
9
 
10
10
  // GO
11
11
  export const supportedGo = new Set(['go1.x'])
@@ -37,7 +37,7 @@ export const supportedRuby = new Set(['ruby2.7'])
37
37
  // deprecated runtimes
38
38
  // https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html
39
39
  export const supportedRuntimes = new Set([
40
- ...supportedDotnetcore,
40
+ // ...supportedDotnetcore,
41
41
  ...supportedGo,
42
42
  ...supportedJava,
43
43
  ...supportedNodejs,
@@ -2,6 +2,7 @@ import { readFile, writeFile } from 'node:fs/promises'
2
2
  import { dirname, join, resolve } from 'node:path'
3
3
  import process from 'node:process'
4
4
  import { performance } from 'node:perf_hooks'
5
+ import { promisify } from 'node:util'
5
6
  import { log } from '@serverless/utils/log.js'
6
7
  import { emptyDir, ensureDir, remove } from 'fs-extra'
7
8
  import jszip from 'jszip'
@@ -18,6 +19,8 @@ import { createUniqueId } from '../utils/index.js'
18
19
  const { ceil } = Math
19
20
  const { entries, fromEntries } = Object
20
21
 
22
+ const setTimeoutPromise = promisify(setTimeout)
23
+
21
24
  export default class LambdaFunction {
22
25
  #artifact = null
23
26
 
@@ -37,6 +40,8 @@ export default class LambdaFunction {
37
40
 
38
41
  #handler = null
39
42
 
43
+ #handlerRunDone = false
44
+
40
45
  #handlerRunner = null
41
46
 
42
47
  #idleTimeStarted = null
@@ -49,13 +54,15 @@ export default class LambdaFunction {
49
54
 
50
55
  #memorySize = null
51
56
 
57
+ #noTimeout = null
58
+
52
59
  #region = null
53
60
 
54
61
  #runtime = null
55
62
 
56
- #timeout = null
63
+ #status = 'IDLE' // can be 'BUSY' or 'IDLE'
57
64
 
58
- status = 'IDLE' // can be 'BUSY' or 'IDLE'
65
+ #timeout = null
59
66
 
60
67
  constructor(functionKey, functionDefinition, serverless, options) {
61
68
  const {
@@ -71,28 +78,28 @@ export default class LambdaFunction {
71
78
 
72
79
  const { handler, name, package: functionPackage = {} } = functionDefinition
73
80
 
74
- const memorySize =
81
+ // this._executionTimeout = null
82
+ this.#functionKey = functionKey
83
+ this.#functionName = name
84
+ this.#handler = handler
85
+
86
+ this.#memorySize =
75
87
  functionDefinition.memorySize ||
76
88
  provider.memorySize ||
77
89
  DEFAULT_LAMBDA_MEMORY_SIZE
78
90
 
79
- const runtime =
91
+ this.#noTimeout = options.noTimeout
92
+
93
+ this.#region = provider.region
94
+
95
+ this.#runtime =
80
96
  functionDefinition.runtime || provider.runtime || DEFAULT_LAMBDA_RUNTIME
81
97
 
82
- const timeout =
98
+ this.#timeout =
83
99
  (functionDefinition.timeout ||
84
100
  provider.timeout ||
85
101
  DEFAULT_LAMBDA_TIMEOUT) * 1000
86
102
 
87
- // this._executionTimeout = null
88
- this.#functionKey = functionKey
89
- this.#functionName = name
90
- this.#handler = handler
91
- this.#memorySize = memorySize
92
- this.#region = provider.region
93
- this.#runtime = runtime
94
- this.#timeout = timeout
95
-
96
103
  this.#verifySupportedRuntime()
97
104
 
98
105
  const env = {
@@ -141,17 +148,17 @@ export default class LambdaFunction {
141
148
  handler,
142
149
  layers: functionDefinition.layers || [],
143
150
  provider,
144
- runtime,
151
+ runtime: this.#runtime,
145
152
  serverlessPath,
146
153
  servicePackage: servicePackage.artifact
147
154
  ? resolve(servicepath, servicePackage.artifact)
148
155
  : undefined,
149
156
  servicePath: servicepath,
150
- timeout,
157
+ timeout: this.#timeout,
151
158
  }
152
159
 
153
160
  this.#handlerRunner = new HandlerRunner(funOptions, options, env)
154
- this.#lambdaContext = new LambdaContext(name, memorySize)
161
+ this.#lambdaContext = new LambdaContext(name, this.#memorySize)
155
162
  }
156
163
 
157
164
  #startExecutionTimer() {
@@ -259,16 +266,33 @@ export default class LambdaFunction {
259
266
  this.#initialized = true
260
267
  }
261
268
 
262
- get idleTimeInMinutes() {
263
- return (performance.now() - this.#idleTimeStarted) / 1000 / 60
269
+ get idleTimeInMillis() {
270
+ return performance.now() - this.#idleTimeStarted
264
271
  }
265
272
 
266
273
  get functionName() {
267
274
  return this.#functionName
268
275
  }
269
276
 
277
+ get status() {
278
+ return this.#status
279
+ }
280
+
281
+ async #timeoutAndTerminate() {
282
+ await setTimeoutPromise(this.#timeout)
283
+
284
+ // if the handler has finished before the timeout don't terminate
285
+ if (this.#handlerRunDone) {
286
+ return
287
+ }
288
+
289
+ await this.#handlerRunner.cleanup()
290
+
291
+ throw new Error('Lambda timeout.')
292
+ }
293
+
270
294
  async runHandler() {
271
- this.status = 'BUSY'
295
+ this.#status = 'BUSY'
272
296
 
273
297
  if (!this.#initialized) {
274
298
  await this.#initialize()
@@ -283,7 +307,14 @@ export default class LambdaFunction {
283
307
 
284
308
  this.#startExecutionTimer()
285
309
 
286
- const result = await this.#handlerRunner.run(this.#event, context)
310
+ this.#handlerRunDone = false
311
+
312
+ const result = await Promise.race([
313
+ this.#handlerRunner.run(this.#event, context),
314
+ ...(this.#noTimeout ? [] : [this.#timeoutAndTerminate()]),
315
+ ])
316
+
317
+ this.#handlerRunDone = true
287
318
 
288
319
  this.#stopExecutionTimer()
289
320
 
@@ -298,7 +329,7 @@ export default class LambdaFunction {
298
329
  )
299
330
  }
300
331
 
301
- this.status = 'IDLE'
332
+ this.#status = 'IDLE'
302
333
  this.#startIdleTimer()
303
334
 
304
335
  return result
@@ -20,19 +20,20 @@ export default class LambdaFunctionPool {
20
20
  }
21
21
 
22
22
  #startCleanTimer() {
23
+ const functionCleanupIdleTimeInMillis =
24
+ this.#options.functionCleanupIdleTimeSeconds * 1000
25
+
23
26
  // NOTE: don't use setInterval, as it would schedule always a new run,
24
27
  // regardless of function processing time and e.g. user action (debugging)
25
28
  this.#timerRef = setTimeout(() => {
26
29
  // console.log('run cleanup')
27
30
  this.#pool.forEach((lambdaFunctions) => {
28
31
  lambdaFunctions.forEach((lambdaFunction) => {
29
- const { idleTimeInMinutes, status } = lambdaFunction
30
- // console.log(idleTimeInMinutes, status)
32
+ const { idleTimeInMillis, status } = lambdaFunction
31
33
 
32
34
  if (
33
35
  status === 'IDLE' &&
34
- idleTimeInMinutes >=
35
- this.#options.functionCleanupIdleTimeSeconds / 60
36
+ idleTimeInMillis >= functionCleanupIdleTimeInMillis
36
37
  ) {
37
38
  // console.log(`removed Lambda Function ${lambdaFunction.functionName}`)
38
39
  lambdaFunction.cleanup()
@@ -43,7 +44,7 @@ export default class LambdaFunctionPool {
43
44
 
44
45
  // schedule new timer
45
46
  this.#startCleanTimer()
46
- }, (this.#options.functionCleanupIdleTimeSeconds * 1000) / 2)
47
+ }, functionCleanupIdleTimeInMillis)
47
48
  }
48
49
 
49
50
  #cleanupPool() {
@@ -123,6 +123,13 @@ export default class HandlerRunner {
123
123
  this.#runner = await this.#loadRunner()
124
124
  }
125
125
 
126
- return this.#runner.run(event, context)
126
+ try {
127
+ return await this.#runner.run(event, context)
128
+ } catch (err) {
129
+ log.error(
130
+ `Unhandled exception in handler '${this.#funOptions.functionKey}'.`,
131
+ )
132
+ throw err
133
+ }
127
134
  }
128
135
  }
@@ -11,8 +11,6 @@ export default class InProcessRunner {
11
11
 
12
12
  #env = null
13
13
 
14
- #functionKey = null
15
-
16
14
  #handler = null
17
15
 
18
16
  #servicePath = null
@@ -20,11 +18,10 @@ export default class InProcessRunner {
20
18
  #timeout = null
21
19
 
22
20
  constructor(funOptions, env) {
23
- const { codeDir, functionKey, handler, servicePath, timeout } = funOptions
21
+ const { codeDir, handler, servicePath, timeout } = funOptions
24
22
 
25
23
  this.#codeDir = codeDir
26
24
  this.#env = env
27
- this.#functionKey = functionKey
28
25
  this.#handler = handler
29
26
  this.#servicePath = servicePath
30
27
  this.#timeout = timeout
@@ -48,7 +45,7 @@ export default class InProcessRunner {
48
45
 
49
46
  let callback
50
47
 
51
- const callbackCalled = new Promise((res, rej) => {
48
+ const callbackWrapper = new Promise((res, rej) => {
52
49
  callback = (err, data) => {
53
50
  if (err === 'Unauthorized') {
54
51
  res('Unauthorized')
@@ -85,29 +82,17 @@ export default class InProcessRunner {
85
82
  },
86
83
  }
87
84
 
88
- let result
89
-
90
85
  // execute (run) handler
91
- try {
92
- result = handler(event, lambdaContext, callback)
93
- } catch {
94
- throw new Error(`Uncaught error in '${this.#functionKey}' handler.`)
95
- }
86
+ // no try/catch so that errors bubble up and are logged with root stack traces
87
+ const result = handler(event, lambdaContext, callback)
96
88
 
97
- // // not a Promise, which is not supported by aws
98
- // if (result == null || typeof result.then !== 'function') {
99
- // throw new Error(`Synchronous function execution is not supported.`)
100
- // }
101
-
102
- const callbacks = [callbackCalled]
89
+ const responses = [callbackWrapper]
103
90
 
104
91
  // Promise was returned
105
92
  if (result != null && typeof result.then === 'function') {
106
- callbacks.push(result)
93
+ responses.push(result)
107
94
  }
108
95
 
109
- const callbackResult = await Promise.race(callbacks)
110
-
111
- return callbackResult
96
+ return Promise.race(responses)
112
97
  }
113
98
  }
@@ -83,7 +83,7 @@ export default class RubyRunner {
83
83
 
84
84
  // console.log(input)
85
85
 
86
- const ruby = execa(
86
+ const { stderr, stdout } = await execa(
87
87
  runtime,
88
88
  [
89
89
  resolve(__dirname, 'invoke.rb'),
@@ -97,10 +97,6 @@ export default class RubyRunner {
97
97
  },
98
98
  )
99
99
 
100
- const result = await ruby
101
-
102
- const { stderr, stdout } = result
103
-
104
100
  if (stderr) {
105
101
  // TODO
106
102
 
@@ -1,7 +1,10 @@
1
+ export { default as checkDockerDaemon } from './checkDockerDaemon.js'
2
+ export { default as checkGoVersion } from './checkGoVersion.js'
1
3
  export { default as createApiKey } from './createApiKey.js'
2
4
  export { default as createUniqueId } from './createUniqueId.js'
3
5
  export { default as detectExecutable } from './detectExecutable.js'
4
6
  export { default as formatToClfTime } from './formatToClfTime.js'
7
+ export { default as generateHapiPath } from './generateHapiPath.js'
5
8
  export { default as getHttpApiCorsConfig } from './getHttpApiCorsConfig.js'
6
9
  export { default as jsonPath } from './jsonPath.js'
7
10
  export { default as lowerCaseKeys } from './lowerCaseKeys.js'
@@ -10,9 +13,7 @@ export { default as parseMultiValueHeaders } from './parseMultiValueHeaders.js'
10
13
  export { default as parseMultiValueQueryStringParameters } from './parseMultiValueQueryStringParameters.js'
11
14
  export { default as parseQueryStringParameters } from './parseQueryStringParameters.js'
12
15
  export { default as splitHandlerPathAndName } from './splitHandlerPathAndName.js'
13
- export { default as checkDockerDaemon } from './checkDockerDaemon.js'
14
- export { default as checkGoVersion } from './checkGoVersion.js'
15
- export { default as generateHapiPath } from './generateHapiPath.js'
16
+
16
17
  // export { default as baseImage } from './baseImage.js'
17
18
 
18
19
  const { isArray } = Array