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 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.1.1",
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.632.0",
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.6.3",
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.22"
118
+ "serverless": "^4.2.1"
119
119
  },
120
120
  "peerDependencies": {
121
121
  "serverless": "^4.0.0"
@@ -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.",
@@ -20,6 +20,7 @@ export default {
20
20
  noPrependStageInUrl: false,
21
21
  noTimeout: false,
22
22
  prefix: "",
23
+ preLoadModules: "",
23
24
  reloadHandler: false,
24
25
  resourceRoutes: false,
25
26
  terminateIdleLambdaTime: 60,
@@ -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 = "502"
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 = "502"
658
+ errorStatusCode = "500"
659
659
  }
660
660
 
661
661
  // Mocks Lambda errors
662
662
  result = {
663
- errorMessage,
664
- errorType: err.constructor.name,
665
- stackTrace: this.#getArrayStackTrace(err.stack),
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)
@@ -62,7 +62,7 @@ export default class HttpServer {
62
62
  .listFunctionNames()
63
63
  .map(
64
64
  (functionName) =>
65
- ` * ${funcNamePairs[functionName]}: ${functionName}`,
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,9 +104,9 @@ export default class PythonRunner {
104
104
  })
105
105
 
106
106
  const onErr = (data) => {
107
- // TODO
108
-
109
107
  log.notice(data.toString())
108
+
109
+ rej(new Error("Internal Server Error"))
110
110
  }
111
111
 
112
112
  const onLine = (line) => {
@@ -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
 
@@ -10,8 +10,7 @@ import {
10
10
  yellow,
11
11
  } from "../config/colors.js"
12
12
 
13
- const { max } = Math
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(...routeInfo.map(({ method }) => method.length))
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("POST", server, invokePath, maxLength, true),
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