serverless-offline 9.2.4 → 9.3.0
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 +9 -9
- package/package.json +5 -5
- package/src/ServerlessOffline.js +9 -8
- package/src/events/http/HttpServer.js +1 -6
- package/src/lambda/LambdaFunction.js +51 -20
- package/src/lambda/handler-runner/HandlerRunner.js +8 -1
- package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +7 -17
- package/src/utils/index.js +4 -3
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
|
-
|
|
219
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dedicatedTo": "Blue, a great migrating bird.",
|
|
3
3
|
"name": "serverless-offline",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.3.0",
|
|
5
5
|
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "./src/index.js",
|
|
@@ -189,18 +189,18 @@
|
|
|
189
189
|
"@hapi/h2o2": "^9.1.0",
|
|
190
190
|
"@hapi/hapi": "^20.2.2",
|
|
191
191
|
"@serverless/utils": "^6.7.0",
|
|
192
|
-
"aws-sdk": "^2.
|
|
192
|
+
"aws-sdk": "^2.1204.0",
|
|
193
193
|
"boxen": "^7.0.0",
|
|
194
194
|
"chalk": "^5.0.1",
|
|
195
195
|
"execa": "^6.1.0",
|
|
196
196
|
"fs-extra": "^10.1.0",
|
|
197
197
|
"java-invoke-local": "0.0.6",
|
|
198
198
|
"js-string-escape": "^1.0.1",
|
|
199
|
-
"jsonpath-plus": "^7.
|
|
199
|
+
"jsonpath-plus": "^7.1.0",
|
|
200
200
|
"jsonschema": "^1.4.1",
|
|
201
201
|
"jsonwebtoken": "^8.5.1",
|
|
202
202
|
"jszip": "^3.10.1",
|
|
203
|
-
"luxon": "
|
|
203
|
+
"luxon": "3.0.1",
|
|
204
204
|
"node-fetch": "^3.2.10",
|
|
205
205
|
"node-schedule": "^2.1.0",
|
|
206
206
|
"object.hasown": "^1.1.1",
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
},
|
|
212
212
|
"devDependencies": {
|
|
213
213
|
"archiver": "^5.3.1",
|
|
214
|
-
"eslint": "^8.
|
|
214
|
+
"eslint": "^8.23.0",
|
|
215
215
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
216
216
|
"eslint-config-prettier": "^8.5.0",
|
|
217
217
|
"eslint-plugin-import": "^2.25.4",
|
package/src/ServerlessOffline.js
CHANGED
|
@@ -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, '')
|
|
@@ -788,12 +788,6 @@ export default class HttpServer {
|
|
|
788
788
|
response.variety = 'buffer'
|
|
789
789
|
} else if (typeof result === 'string') {
|
|
790
790
|
response.source = stringify(result)
|
|
791
|
-
} else if (result && result.body && typeof result.body !== 'string') {
|
|
792
|
-
return this.#reply502(
|
|
793
|
-
response,
|
|
794
|
-
'According to the API Gateway specs, the body content must be stringified. Check your Lambda response and make sure you are invoking JSON.stringify(YOUR_CONTENT) on your body object',
|
|
795
|
-
{},
|
|
796
|
-
)
|
|
797
791
|
} else {
|
|
798
792
|
response.source = result
|
|
799
793
|
}
|
|
@@ -888,6 +882,7 @@ export default class HttpServer {
|
|
|
888
882
|
response.variety = 'buffer'
|
|
889
883
|
} else {
|
|
890
884
|
if (result && result.body && typeof result.body !== 'string') {
|
|
885
|
+
// FIXME TODO we should probably just write to console instead of returning a payload
|
|
891
886
|
return this.#reply502(
|
|
892
887
|
response,
|
|
893
888
|
'According to the API Gateway specs, the body content must be stringified. Check your Lambda response and make sure you are invoking JSON.stringify(YOUR_CONTENT) on your body object',
|
|
@@ -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
|
-
#
|
|
63
|
+
#status = 'IDLE' // can be 'BUSY' or 'IDLE'
|
|
57
64
|
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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() {
|
|
@@ -267,8 +274,25 @@ export default class LambdaFunction {
|
|
|
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
|
|
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
|
-
|
|
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
|
|
332
|
+
this.#status = 'IDLE'
|
|
302
333
|
this.#startIdleTimer()
|
|
303
334
|
|
|
304
335
|
return result
|
|
@@ -123,6 +123,13 @@ export default class HandlerRunner {
|
|
|
123
123
|
this.#runner = await this.#loadRunner()
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
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,
|
|
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
|
|
48
|
+
const callbackWrapper = new Promise((res, rej) => {
|
|
52
49
|
callback = (err, data) => {
|
|
53
50
|
if (err === 'Unauthorized') {
|
|
54
51
|
res('Unauthorized')
|
|
@@ -85,29 +82,22 @@ export default class InProcessRunner {
|
|
|
85
82
|
},
|
|
86
83
|
}
|
|
87
84
|
|
|
88
|
-
let result
|
|
89
|
-
|
|
90
85
|
// execute (run) handler
|
|
91
|
-
try
|
|
92
|
-
|
|
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
89
|
// // not a Promise, which is not supported by aws
|
|
98
90
|
// if (result == null || typeof result.then !== 'function') {
|
|
99
91
|
// throw new Error(`Synchronous function execution is not supported.`)
|
|
100
92
|
// }
|
|
101
93
|
|
|
102
|
-
const
|
|
94
|
+
const responses = [callbackWrapper]
|
|
103
95
|
|
|
104
96
|
// Promise was returned
|
|
105
97
|
if (result != null && typeof result.then === 'function') {
|
|
106
|
-
|
|
98
|
+
responses.push(result)
|
|
107
99
|
}
|
|
108
100
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return callbackResult
|
|
101
|
+
return Promise.race(responses)
|
|
112
102
|
}
|
|
113
103
|
}
|
package/src/utils/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|