serverless-offline 14.1.1 → 14.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 -4
- package/package.json +4 -4
- package/src/ServerlessOffline.js +25 -2
- package/src/config/commandOptions.js +8 -0
- package/src/config/defaultOptions.js +1 -0
- package/src/events/http/HttpServer.js +8 -5
- package/src/lambda/HttpServer.js +3 -5
- package/src/lambda/handler-runner/python-runner/PythonRunner.js +2 -2
- package/src/lambda/handler-runner/python-runner/invoke.py +1 -1
- package/src/utils/logRoutes.js +6 -7
- package/src/utils/logSponsor.js +69 -0
package/README.md
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# Serverless Offline
|
|
2
2
|
|
|
3
|
-
⚠️
|
|
4
|
-
We are looking for maintainers! This package is entirely community-driven. Please send an email to dherault to start helping now.
|
|
5
|
-
⚠️
|
|
6
|
-
|
|
7
3
|
<p>
|
|
8
4
|
<a href="https://www.serverless.com">
|
|
9
5
|
<img src="http://public.serverless.com/badges/v3.svg">
|
|
@@ -199,6 +195,10 @@ Turns off all authorizers.
|
|
|
199
195
|
|
|
200
196
|
Don't prepend http routes with the stage.
|
|
201
197
|
|
|
198
|
+
#### noSponsor
|
|
199
|
+
|
|
200
|
+
Remove sponsor message from the output.
|
|
201
|
+
|
|
202
202
|
#### noTimeout
|
|
203
203
|
|
|
204
204
|
-t Disables the timeout feature.
|
|
@@ -243,6 +243,11 @@ Default: 600 (10 minutes)
|
|
|
243
243
|
WebSocket port to listen on.<br />
|
|
244
244
|
Default: 3001
|
|
245
245
|
|
|
246
|
+
#### preLoadModules
|
|
247
|
+
|
|
248
|
+
Pre-load specified modules in the main thread to avoid crashes when importing in worker threads. Provide module names as a comma-separated list (e.g., "sharp,canvas").<br />
|
|
249
|
+
Default: ''
|
|
250
|
+
|
|
246
251
|
Any of the CLI options can be added to your `serverless.yml`. For example:
|
|
247
252
|
|
|
248
253
|
```yml
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-offline",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.3.0",
|
|
4
4
|
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
]
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@aws-sdk/client-lambda": "^3.
|
|
79
|
+
"@aws-sdk/client-lambda": "^3.636.0",
|
|
80
80
|
"@hapi/boom": "^10.0.1",
|
|
81
81
|
"@hapi/h2o2": "^10.0.4",
|
|
82
82
|
"@hapi/hapi": "^21.3.10",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"fs-extra": "^11.2.0",
|
|
89
89
|
"is-wsl": "^3.1.0",
|
|
90
90
|
"java-invoke-local": "0.0.6",
|
|
91
|
-
"jose": "^5.
|
|
91
|
+
"jose": "^5.7.0",
|
|
92
92
|
"js-string-escape": "^1.0.1",
|
|
93
93
|
"jsonpath-plus": "^9.0.0",
|
|
94
94
|
"jsonschema": "^1.4.1",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"mocha": "^10.7.3",
|
|
116
116
|
"nyc": "^17.0.0",
|
|
117
117
|
"prettier": "^3.3.3",
|
|
118
|
-
"serverless": "^4.1
|
|
118
|
+
"serverless": "^4.2.1"
|
|
119
119
|
},
|
|
120
120
|
"peerDependencies": {
|
|
121
121
|
"serverless": "^4.0.0"
|
package/src/ServerlessOffline.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import process, { exit } from "node:process"
|
|
2
2
|
import { log, setLogUtils } from "./utils/log.js"
|
|
3
|
+
import logSponsor from "./utils/logSponsor.js"
|
|
3
4
|
import {
|
|
4
5
|
commandOptions,
|
|
5
6
|
CUSTOM_OPTION,
|
|
@@ -64,6 +65,14 @@ export default class ServerlessOffline {
|
|
|
64
65
|
async start() {
|
|
65
66
|
this.#mergeOptions()
|
|
66
67
|
|
|
68
|
+
this.#preLoadModules()
|
|
69
|
+
|
|
70
|
+
if (this.#cliOptions.noSponsor) {
|
|
71
|
+
log.notice()
|
|
72
|
+
} else {
|
|
73
|
+
logSponsor()
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
const {
|
|
68
77
|
albEvents,
|
|
69
78
|
httpEvents,
|
|
@@ -278,13 +287,11 @@ export default class ServerlessOffline {
|
|
|
278
287
|
origin: this.#options.corsAllowOrigin,
|
|
279
288
|
}
|
|
280
289
|
|
|
281
|
-
log.notice()
|
|
282
290
|
log.notice(
|
|
283
291
|
`Starting Offline at stage ${
|
|
284
292
|
this.#options.stage || provider.stage
|
|
285
293
|
} ${gray(`(${this.#options.region || provider.region})`)}`,
|
|
286
294
|
)
|
|
287
|
-
log.notice()
|
|
288
295
|
log.debug("options:", this.#options)
|
|
289
296
|
}
|
|
290
297
|
|
|
@@ -418,6 +425,18 @@ export default class ServerlessOffline {
|
|
|
418
425
|
}
|
|
419
426
|
}
|
|
420
427
|
|
|
428
|
+
#preLoadModules() {
|
|
429
|
+
const modules = this.#options.preLoadModules.split(",")
|
|
430
|
+
|
|
431
|
+
modules.forEach((module) => {
|
|
432
|
+
try {
|
|
433
|
+
import(module)
|
|
434
|
+
} catch (error) {
|
|
435
|
+
log.error(`Error importing module ${module}: ${error}`)
|
|
436
|
+
}
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
|
|
421
440
|
// TODO FIXME
|
|
422
441
|
// TEMP quick fix to expose for testing, look for better solution
|
|
423
442
|
internals() {
|
|
@@ -441,6 +460,10 @@ export default class ServerlessOffline {
|
|
|
441
460
|
mergeOptions: () => {
|
|
442
461
|
this.#mergeOptions()
|
|
443
462
|
},
|
|
463
|
+
|
|
464
|
+
preLoadModules: () => {
|
|
465
|
+
this.#preLoadModules()
|
|
466
|
+
},
|
|
444
467
|
}
|
|
445
468
|
}
|
|
446
469
|
}
|
|
@@ -89,6 +89,10 @@ export default {
|
|
|
89
89
|
type: "boolean",
|
|
90
90
|
usage: "Don't prepend http routes with the stage.",
|
|
91
91
|
},
|
|
92
|
+
noSponsor: {
|
|
93
|
+
type: "boolean",
|
|
94
|
+
usage: "Remove sponsor message from the output.",
|
|
95
|
+
},
|
|
92
96
|
noTimeout: {
|
|
93
97
|
shortcut: "t",
|
|
94
98
|
type: "boolean",
|
|
@@ -100,6 +104,10 @@ export default {
|
|
|
100
104
|
usage:
|
|
101
105
|
"Adds a prefix to every path, to send your requests to http://localhost:3000/prefix/[your_path] instead.",
|
|
102
106
|
},
|
|
107
|
+
preLoadModules: {
|
|
108
|
+
type: "string",
|
|
109
|
+
usage: "A comma separated list of modules to preload on the main thread",
|
|
110
|
+
},
|
|
103
111
|
reloadHandler: {
|
|
104
112
|
type: "boolean",
|
|
105
113
|
usage: "Reloads handler with each request.",
|
|
@@ -645,7 +645,7 @@ export default class HttpServer {
|
|
|
645
645
|
/* RESPONSE SELECTION (among endpoint's possible responses) */
|
|
646
646
|
|
|
647
647
|
// Failure handling
|
|
648
|
-
let errorStatusCode = "
|
|
648
|
+
let errorStatusCode = "500"
|
|
649
649
|
|
|
650
650
|
if (err) {
|
|
651
651
|
const errorMessage = (err.message || err).toString()
|
|
@@ -655,14 +655,17 @@ export default class HttpServer {
|
|
|
655
655
|
if (found && found.length > 1) {
|
|
656
656
|
;[, errorStatusCode] = found
|
|
657
657
|
} else {
|
|
658
|
-
errorStatusCode = "
|
|
658
|
+
errorStatusCode = "500"
|
|
659
659
|
}
|
|
660
660
|
|
|
661
661
|
// Mocks Lambda errors
|
|
662
662
|
result = {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
663
|
+
body: JSON.stringify({
|
|
664
|
+
message: errorMessage,
|
|
665
|
+
stackTrace: this.#getArrayStackTrace(err.stack),
|
|
666
|
+
type: err.constructor.name,
|
|
667
|
+
}),
|
|
668
|
+
statusCode: errorStatusCode,
|
|
666
669
|
}
|
|
667
670
|
|
|
668
671
|
log.error(errorMessage)
|
package/src/lambda/HttpServer.js
CHANGED
|
@@ -62,7 +62,7 @@ export default class HttpServer {
|
|
|
62
62
|
.listFunctionNames()
|
|
63
63
|
.map(
|
|
64
64
|
(functionName) =>
|
|
65
|
-
`
|
|
65
|
+
` * ${funcNamePairs[functionName]}: ${functionName}`,
|
|
66
66
|
),
|
|
67
67
|
].join("\n"),
|
|
68
68
|
)
|
|
@@ -73,9 +73,7 @@ export default class HttpServer {
|
|
|
73
73
|
.listFunctionNames()
|
|
74
74
|
.map(
|
|
75
75
|
(functionName) =>
|
|
76
|
-
`
|
|
77
|
-
invRoute.method
|
|
78
|
-
} ${basePath}${invRoute.path.replace(
|
|
76
|
+
` * ${invRoute.method} ${basePath}${invRoute.path.replace(
|
|
79
77
|
"{functionName}",
|
|
80
78
|
functionName,
|
|
81
79
|
)}`,
|
|
@@ -90,7 +88,7 @@ export default class HttpServer {
|
|
|
90
88
|
.listFunctionNames()
|
|
91
89
|
.map(
|
|
92
90
|
(functionName) =>
|
|
93
|
-
`
|
|
91
|
+
` * ${
|
|
94
92
|
invAsyncRoute.method
|
|
95
93
|
} ${basePath}${invAsyncRoute.path.replace(
|
|
96
94
|
"{functionName}",
|
|
@@ -104,7 +104,7 @@ if __name__ == '__main__':
|
|
|
104
104
|
'__offline_payload__': result
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
if isinstance(result['body'], bytes):
|
|
107
|
+
if hasattr(result, 'body') and isinstance(result['body'], bytes):
|
|
108
108
|
data['__offline_payload__']['body'] = base64.b64encode(result['body']).decode('utf-8')
|
|
109
109
|
data['isBase64Encoded'] = True
|
|
110
110
|
|
package/src/utils/logRoutes.js
CHANGED
|
@@ -10,8 +10,7 @@ import {
|
|
|
10
10
|
yellow,
|
|
11
11
|
} from "../config/colors.js"
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
|
|
13
|
+
const post = "POST"
|
|
15
14
|
const colorMethodMapping = new Map([
|
|
16
15
|
["DELETE", red],
|
|
17
16
|
["GET", dodgerblue],
|
|
@@ -28,13 +27,13 @@ function logRoute(method, server, path, maxLength, dimPath = false) {
|
|
|
28
27
|
const methodColor = colorMethodMapping.get(method) ?? peachpuff
|
|
29
28
|
const methodFormatted = method.padEnd(maxLength, " ")
|
|
30
29
|
|
|
31
|
-
return `${methodColor(methodFormatted)} ${yellow.dim("|")} ${gray.dim(
|
|
32
|
-
server,
|
|
33
|
-
)}${dimPath ? gray.dim(path) : lime(path)}`
|
|
30
|
+
return `${methodColor(methodFormatted)} ${yellow.dim("|")} ${gray.dim(server)}${dimPath ? gray.dim(path) : lime(path)}`
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
function getMaxHttpMethodNameLength(routeInfo) {
|
|
37
|
-
return max(
|
|
34
|
+
return Math.max(
|
|
35
|
+
...routeInfo.map(({ method }) => Math.max(method.length, post.length)),
|
|
36
|
+
)
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
export default function logRoutes(routeInfo) {
|
|
@@ -55,7 +54,7 @@ export default function logRoutes(routeInfo) {
|
|
|
55
54
|
// eslint-disable-next-line prefer-template
|
|
56
55
|
logRoute(method, server, path, maxLength) +
|
|
57
56
|
"\n" +
|
|
58
|
-
logRoute(
|
|
57
|
+
logRoute(post, server, invokePath, maxLength, true),
|
|
59
58
|
)
|
|
60
59
|
.join("\n"),
|
|
61
60
|
boxenOptions,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* eslint-disable no-use-before-define */
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
import process from "node:process"
|
|
4
|
+
|
|
5
|
+
import boxen from "boxen"
|
|
6
|
+
import { gray, dodgerblue } from "../config/colors.js"
|
|
7
|
+
|
|
8
|
+
const boxenOptions = {
|
|
9
|
+
borderColor: "blue",
|
|
10
|
+
margin: 1,
|
|
11
|
+
padding: 1,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Promotion starts on August 22, 2024
|
|
15
|
+
const startAt = new Date("2024-08-22T00:00:00.000Z")
|
|
16
|
+
// By October 22, 2024, the promotion will be displayed to 100% of users
|
|
17
|
+
const endAt = new Date("2024-10-22T00:00:00.000Z")
|
|
18
|
+
const nDays = diffDays(startAt, endAt)
|
|
19
|
+
|
|
20
|
+
function logSponsor() {
|
|
21
|
+
if (!shouldDisplaySponsor()) {
|
|
22
|
+
console.log()
|
|
23
|
+
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(
|
|
28
|
+
boxen(
|
|
29
|
+
`Sponsored by ${dodgerblue("Arccode, the RPG for developers")}\nhttps://arccode.dev?ref=so\n${gray.dim(
|
|
30
|
+
"Disable with --noSponsor",
|
|
31
|
+
)}`,
|
|
32
|
+
boxenOptions,
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Display the message progressively over time to 100% of users
|
|
38
|
+
function shouldDisplaySponsor() {
|
|
39
|
+
const ratio = diffDays(startAt, new Date()) / nDays
|
|
40
|
+
|
|
41
|
+
if (ratio >= 1) return true
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const nonce = Number(
|
|
45
|
+
encodeStringToNumber(process.cwd()).toString().padStart(2, "0").slice(-2),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return nonce <= ratio * 100
|
|
49
|
+
} catch {
|
|
50
|
+
//
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function encodeStringToNumber(string) {
|
|
57
|
+
let sum = 0
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < string.length; i += 1) {
|
|
60
|
+
sum += Number(string.codePointAt(i).toString(10))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return sum
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function diffDays(a, b) {
|
|
67
|
+
return Math.round((b - a) / (1000 * 60 * 60 * 24))
|
|
68
|
+
}
|
|
69
|
+
export default logSponsor
|