serverless-offline 13.5.1 → 14.0.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dedicatedTo": "Blue, a great migrating bird.",
3
3
  "name": "serverless-offline",
4
- "version": "13.5.1",
4
+ "version": "14.0.0",
5
5
  "description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
6
6
  "license": "MIT",
7
7
  "exports": {
@@ -80,7 +80,7 @@
80
80
  "@aws-sdk/client-lambda": "^3.509.0",
81
81
  "@hapi/boom": "^10.0.1",
82
82
  "@hapi/h2o2": "^10.0.4",
83
- "@hapi/hapi": "^21.3.3",
83
+ "@hapi/hapi": "^21.3.10",
84
84
  "array-unflat-js": "^0.1.3",
85
85
  "boxen": "^7.1.1",
86
86
  "chalk": "^5.3.0",
@@ -89,33 +89,35 @@
89
89
  "fs-extra": "^11.2.0",
90
90
  "is-wsl": "^3.1.0",
91
91
  "java-invoke-local": "0.0.6",
92
- "jose": "^5.2.1",
92
+ "jose": "^5.6.3",
93
93
  "js-string-escape": "^1.0.1",
94
- "jsonpath-plus": "^8.0.0",
94
+ "jsonpath-plus": "^9.0.0",
95
95
  "jsonschema": "^1.4.1",
96
96
  "jszip": "^3.10.1",
97
97
  "luxon": "^3.4.4",
98
98
  "node-schedule": "^2.1.1",
99
99
  "p-memoize": "^7.1.1",
100
+ "tree-kill": "^1.2.2",
101
+ "tsx": "^4.16.2",
100
102
  "velocityjs": "^2.0.6",
101
- "ws": "^8.16.0"
103
+ "ws": "^8.18.0"
102
104
  },
103
105
  "devDependencies": {
104
106
  "@istanbuljs/esm-loader-hook": "^0.2.0",
105
- "archiver": "^6.0.1",
106
- "commit-and-tag-version": "^12.2.0",
107
- "eslint": "^8.56.0",
107
+ "archiver": "^7.0.1",
108
+ "commit-and-tag-version": "^12.4.1",
109
+ "eslint": "^8.57.0",
108
110
  "eslint-config-airbnb-base": "^15.0.0",
109
111
  "eslint-config-prettier": "^9.1.0",
110
112
  "eslint-plugin-import": "^2.29.1",
111
113
  "eslint-plugin-prettier": "^5.1.3",
112
- "eslint-plugin-unicorn": "^51.0.1",
113
- "mocha": "^10.3.0",
114
- "nyc": "^15.1.0",
115
- "prettier": "^3.2.5",
116
- "serverless": "^3.38.0"
114
+ "eslint-plugin-unicorn": "^54.0.0",
115
+ "mocha": "^10.6.0",
116
+ "nyc": "^17.0.0",
117
+ "prettier": "^3.3.2",
118
+ "serverless": "^4.1.18"
117
119
  },
118
120
  "peerDependencies": {
119
- "serverless": "^3.2.0"
121
+ "serverless": "^4.0.0"
120
122
  }
121
123
  }
@@ -31,6 +31,7 @@ export const supportedRuntimesArchitecture = {
31
31
  provided: [X86_64],
32
32
  dotnet6: [ARM64, X86_64],
33
33
  "provided.al2": [ARM64, X86_64],
34
+ "provided.al2023": [ARM64, X86_64],
34
35
  }
35
36
 
36
37
  // GO
@@ -48,7 +49,11 @@ export const supportedNodejs = new Set([
48
49
  ])
49
50
 
50
51
  // PROVIDED
51
- export const supportedProvided = new Set(["provided", "provided.al2"])
52
+ export const supportedProvided = new Set([
53
+ "provided",
54
+ "provided.al2",
55
+ "provided.al2023",
56
+ ])
52
57
 
53
58
  // PYTHON
54
59
  export const supportedPython = new Set([
@@ -57,6 +62,7 @@ export const supportedPython = new Set([
57
62
  "python3.9",
58
63
  "python3.10",
59
64
  "python3.11",
65
+ "python3.12",
60
66
  ])
61
67
 
62
68
  // RUBY
@@ -267,6 +267,8 @@ export default class HttpServer {
267
267
  override: false,
268
268
  })
269
269
 
270
+ response.headers = headers
271
+
270
272
  if (typeof result === "string") {
271
273
  response.source = stringify(result)
272
274
  } else if (result && result.body !== undefined) {
@@ -1,7 +1,8 @@
1
1
  function parseResource(resource) {
2
- const [, region, accountId, restApiId, path] = resource.match(
3
- /arn:aws:execute-api:(.*?):(.*?):(.*?)\/(.*)/,
4
- )
2
+ const [, region = "*", accountId = "*", restApiId = "*", path = "*"] =
3
+ resource.match(
4
+ /arn:aws:execute-api:([^\s:]+)(?::([^\s:]+))?(?::([^\s/:]+))?(?:\/(.*))?/,
5
+ )
5
6
 
6
7
  return {
7
8
  accountId,
@@ -26,10 +27,6 @@ export default function authMatchPolicyResource(policyResource, resource) {
26
27
  return true
27
28
  }
28
29
 
29
- if (policyResource === "arn:aws:execute-api:*:*:*") {
30
- return true
31
- }
32
-
33
30
  if (policyResource.includes("*") || policyResource.includes("?")) {
34
31
  // Policy contains a wildcard resource
35
32
 
@@ -61,7 +58,7 @@ export default function authMatchPolicyResource(policyResource, resource) {
61
58
  // for the requested resource and the resource defined in the policy
62
59
  // Need to create a regex replacing ? with one character and * with any number of characters
63
60
  const regExp = new RegExp(
64
- parsedPolicyResource.path.replaceAll("*", ".*").replaceAll("?", "."),
61
+ `${parsedPolicyResource.path.replaceAll("*", ".*").replaceAll("?", ".")}$`,
65
62
  )
66
63
 
67
64
  return regExp.test(parsedResource.path)
@@ -295,7 +295,7 @@ export default class HttpServer {
295
295
  return null
296
296
  }
297
297
 
298
- const authFunctionName = this.#extractAuthFunctionName(endpoint)
298
+ let authFunctionName = this.#extractAuthFunctionName(endpoint)
299
299
 
300
300
  if (!authFunctionName) {
301
301
  return null
@@ -303,16 +303,32 @@ export default class HttpServer {
303
303
 
304
304
  log.notice(`Configuring Authorization: ${path} ${authFunctionName}`)
305
305
 
306
+ const standardFunctionExists =
307
+ this.#serverless.service.functions &&
308
+ this.#serverless.service.functions[authFunctionName]
309
+ const serverlessAuthorizerOptions =
310
+ this.#serverless.service.provider.httpApi &&
311
+ this.#serverless.service.provider.httpApi.authorizers &&
312
+ this.#serverless.service.provider.httpApi.authorizers[authFunctionName]
313
+
314
+ if (
315
+ !standardFunctionExists &&
316
+ endpoint.isHttpApi &&
317
+ serverlessAuthorizerOptions &&
318
+ serverlessAuthorizerOptions.functionName
319
+ ) {
320
+ log.notice(
321
+ `Redirecting authorizer function: ${authFunctionName} to ${serverlessAuthorizerOptions.functionName}`,
322
+ )
323
+ authFunctionName = serverlessAuthorizerOptions.functionName
324
+ }
325
+
306
326
  const authFunction = this.#serverless.service.getFunction(authFunctionName)
307
327
 
308
328
  if (!authFunction) {
309
329
  log.error(`Authorization function ${authFunctionName} does not exist`)
310
330
  return null
311
331
  }
312
- const serverlessAuthorizerOptions =
313
- this.#serverless.service.provider.httpApi &&
314
- this.#serverless.service.provider.httpApi.authorizers &&
315
- this.#serverless.service.provider.httpApi.authorizers[authFunctionName]
316
332
 
317
333
  const authorizerOptions = {
318
334
  enableSimpleResponses:
@@ -326,7 +342,8 @@ export default class HttpServer {
326
342
  ? serverlessAuthorizerOptions?.payloadVersion || "2.0"
327
343
  : "1.0",
328
344
  resultTtlInSeconds:
329
- serverlessAuthorizerOptions?.resultTtlInSeconds || "300",
345
+ serverlessAuthorizerOptions?.resultTtlInSeconds ?? "300",
346
+ type: endpoint.isHttpApi ? serverlessAuthorizerOptions?.type : undefined,
330
347
  }
331
348
 
332
349
  if (
@@ -339,11 +356,10 @@ export default class HttpServer {
339
356
  return null
340
357
  }
341
358
 
342
- if (typeof endpoint.authorizer === "string") {
343
- authorizerOptions.name = authFunctionName
344
- } else {
359
+ if (typeof endpoint.authorizer !== "string") {
345
360
  assign(authorizerOptions, endpoint.authorizer)
346
361
  }
362
+ authorizerOptions.name = authFunctionName
347
363
 
348
364
  if (
349
365
  !authorizerOptions.identitySource &&
@@ -443,7 +459,7 @@ export default class HttpServer {
443
459
  request.payload = request.payload && request.payload.toString(encoding)
444
460
  request.rawPayload = request.payload
445
461
 
446
- // incomming request message
462
+ // incoming request message
447
463
  log.notice()
448
464
 
449
465
  log.notice()
@@ -5,7 +5,7 @@ import { log } from "../../utils/log.js"
5
5
  const { isArray } = Array
6
6
  const { now } = Date
7
7
 
8
- export default function createAuthScheme(jwtOptions) {
8
+ export default function createJWTAuthScheme(jwtOptions) {
9
9
  const authorizerName = jwtOptions.name
10
10
 
11
11
  const identitySourceMatch = /^\$request.header.((?:\w+-?)+\w+)$/.exec(
@@ -43,7 +43,7 @@ export default function createAuthScheme(jwtOptions) {
43
43
  return Boom.unauthorized("JWT Token expired")
44
44
  }
45
45
 
46
- const { aud, iss, scope, client_id: clientId } = claims
46
+ const { aud, iss, scope, scp, client_id: clientId } = claims
47
47
  if (iss !== jwtOptions.issuerUrl) {
48
48
  log.notice(`JWT Token not from correct issuer url`)
49
49
 
@@ -68,13 +68,13 @@ export default function createAuthScheme(jwtOptions) {
68
68
 
69
69
  let scopes = null
70
70
  if (jwtOptions.scopes && jwtOptions.scopes.length > 0) {
71
- if (!scope) {
71
+ if (!scope && !scp) {
72
72
  log.notice(`JWT Token missing valid scope`)
73
73
 
74
74
  return Boom.forbidden("JWT Token missing valid scope")
75
75
  }
76
76
 
77
- scopes = scope.split(" ")
77
+ scopes = scp || scope.split(" ")
78
78
  if (scopes.every((s) => !jwtOptions.scopes.includes(s))) {
79
79
  log.notice(`JWT Token missing valid scope`)
80
80
 
@@ -85,7 +85,6 @@ export default function createAuthScheme(jwtOptions) {
85
85
  log.notice(`JWT Token validated`)
86
86
 
87
87
  // Set the credentials for the rest of the pipeline
88
- // return resolve(
89
88
  return h.authenticated({
90
89
  credentials: {
91
90
  claims,
@@ -134,8 +134,8 @@ export default class LambdaProxyIntegrationEvent {
134
134
  if (token) {
135
135
  try {
136
136
  claims = decodeJwt(token)
137
- if (claims.scope) {
138
- scopes = claims.scope.split(" ")
137
+ if (claims.scp || claims.scope) {
138
+ scopes = claims.scp || claims.scope.split(" ")
139
139
  // In AWS HTTP Api the scope property is removed from the decoded JWT
140
140
  // I'm leaving this property because I'm not sure how all of the authorizers
141
141
  // for AWS REST Api handle JWT.
@@ -120,8 +120,8 @@ export default class LambdaProxyIntegrationEventV2 {
120
120
  if (token) {
121
121
  try {
122
122
  claims = decodeJwt(token)
123
- if (claims.scope) {
124
- scopes = claims.scope.split(" ")
123
+ if (claims.scp || claims.scope) {
124
+ scopes = claims.scp || claims.scope.split(" ")
125
125
  // In AWS HTTP Api the scope property is removed from the decoded JWT
126
126
  // I'm leaving this property because I'm not sure how all of the authorizers
127
127
  // for AWS REST Api handle JWT.
@@ -10,7 +10,7 @@ const { assign, entries, fromEntries } = Object
10
10
 
11
11
  function escapeJavaScript(x) {
12
12
  if (typeof x === "string") {
13
- return jsEscapeString(x).replaceAll("\\n", "\n") // See #26,
13
+ return jsEscapeString(x).replaceAll(String.raw`\n`, "\n") // See #26,
14
14
  }
15
15
 
16
16
  if (isPlainObject(x)) {
@@ -0,0 +1,32 @@
1
+ /* eslint-disable max-classes-per-file */
2
+ /**
3
+ * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4
+ *
5
+ * This code was copied from:
6
+ * https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/main/src/Errors.js
7
+ *
8
+ * Defines custom error types throwable by the runtime.
9
+ */
10
+
11
+ "use strict"
12
+
13
+ const errorClasses = [
14
+ class ImportModuleError extends Error {},
15
+ class HandlerNotFound extends Error {},
16
+ class MalformedHandlerName extends Error {},
17
+ class UserCodeSyntaxError extends Error {},
18
+ class MalformedStreamingHandler extends Error {},
19
+ class InvalidStreamingOperation extends Error {},
20
+ class UnhandledPromiseRejection extends Error {
21
+ constructor(reason, promise) {
22
+ super(reason)
23
+ this.reason = reason
24
+ this.promise = promise
25
+ }
26
+ },
27
+ ]
28
+
29
+ errorClasses.forEach((e) => {
30
+ module.exports[e.name] = e
31
+ e.prototype.name = `Runtime.${e.name}`
32
+ })
@@ -0,0 +1,38 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ /* eslint-disable no-param-reassign */
3
+ /**
4
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
+ *
6
+ * This code was copied from:
7
+ * https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/main/src/HttpResponseStream.js
8
+ *
9
+ * HttpResponseStream is NOT used by the runtime.
10
+ * It is only exposed in the `awslambda` variable for customers to use.
11
+ */
12
+
13
+ "use strict"
14
+
15
+ const METADATA_PRELUDE_CONTENT_TYPE =
16
+ "application/vnd.awslambda.http-integration-response"
17
+ const DELIMITER_LEN = 8
18
+
19
+ // Implements the application/vnd.awslambda.http-integration-response content type.
20
+ class HttpResponseStream {
21
+ static from(underlyingStream, prelude) {
22
+ underlyingStream.setContentType(METADATA_PRELUDE_CONTENT_TYPE)
23
+
24
+ // JSON.stringify is required. NULL byte is not allowed in metadataPrelude.
25
+ const metadataPrelude = JSON.stringify(prelude)
26
+
27
+ underlyingStream._onBeforeFirstWrite = (write) => {
28
+ write(metadataPrelude)
29
+
30
+ // Write 8 null bytes after the JSON prelude.
31
+ write(new Uint8Array(DELIMITER_LEN))
32
+ }
33
+
34
+ return underlyingStream
35
+ }
36
+ }
37
+
38
+ module.exports.HttpResponseStream = HttpResponseStream
@@ -1,359 +1,337 @@
1
+ /* eslint-disable import/no-dynamic-require */
2
+ /* eslint-disable global-require */
3
+ /* eslint-disable no-underscore-dangle */
4
+ /* eslint-disable func-names */
5
+ /**
6
+ * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
7
+ *
8
+ * This code was copied from:
9
+ * https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/main/src/UserFunction.js
10
+ *
11
+ * This module defines the functions for loading the user's code as specified
12
+ * in a handler string.
13
+ */
14
+
1
15
  "use strict"
2
16
 
17
+ const path = require("node:path")
3
18
  const { pathToFileURL } = require("node:url")
19
+ const fs = require("node:fs")
20
+ const process = require("node:process")
4
21
 
5
- // node_modules/lambda-runtime/dist/node16/UserFunction.js
6
- ;(function () {
7
- const __getOwnPropNames = Object.getOwnPropertyNames
8
- const __commonJS = (cb, mod) =>
9
- function __require() {
10
- return (
11
- mod ||
12
- (0, cb[__getOwnPropNames(cb)[0]])(
13
- (mod = { exports: {} }).exports,
14
- mod,
15
- ),
16
- mod.exports
17
- )
18
- }
19
- const require_Errors = __commonJS({
20
- "Errors.js": function (exports2, module2) {
21
- "use strict"
22
-
23
- const util = require("util")
24
- function _isError(obj) {
25
- return (
26
- obj &&
27
- obj.name &&
28
- obj.message &&
29
- obj.stack &&
30
- typeof obj.name === "string" &&
31
- typeof obj.message === "string" &&
32
- typeof obj.stack === "string"
33
- )
34
- }
35
- function intoError(err) {
36
- if (err instanceof Error) {
37
- return err
38
- }
39
- return new Error(err)
40
- }
41
- module2.exports.intoError = intoError
42
- function toRapidResponse(error) {
43
- try {
44
- if (util.types.isNativeError(error) || _isError(error)) {
45
- return {
46
- errorType: error.name,
47
- errorMessage: error.message,
48
- trace: error.stack.split("\n"),
49
- }
50
- }
51
- return {
52
- errorType: typeof error,
53
- errorMessage: error.toString(),
54
- trace: [],
55
- }
56
- } catch (_err) {
57
- return {
58
- errorType: "handled",
59
- errorMessage:
60
- "callback called with Error argument, but there was a problem while retrieving one or more of its message, name, and stack",
61
- }
62
- }
63
- }
64
- module2.exports.toRapidResponse = toRapidResponse
65
- module2.exports.toFormatted = (error) => {
66
- try {
67
- return ` ${JSON.stringify(error, (_k, v) =>
68
- _withEnumerableProperties(v),
69
- )}`
70
- } catch (err) {
71
- return ` ${JSON.stringify(toRapidResponse(error))}`
72
- }
73
- }
74
- function _withEnumerableProperties(error) {
75
- if (error instanceof Error) {
76
- const ret = {
77
- errorType: error.name,
78
- errorMessage: error.message,
79
- code: error.code,
80
- ...error,
81
- }
82
- if (typeof error.stack === "string") {
83
- ret.stack = error.stack.split("\n")
84
- }
85
- return ret
86
- }
87
- return error
88
- }
89
- const errorClasses = [
90
- class ImportModuleError extends Error {},
91
- class HandlerNotFound extends Error {},
92
- class MalformedHandlerName extends Error {},
93
- class UserCodeSyntaxError extends Error {},
94
- class MalformedStreamingHandler extends Error {},
95
- class InvalidStreamingOperation extends Error {},
96
- class UnhandledPromiseRejection extends Error {
97
- constructor(reason, promise) {
98
- super(reason)
99
- this.reason = reason
100
- this.promise = promise
101
- }
102
- },
103
- ]
104
- errorClasses.forEach((e) => {
105
- module2.exports[e.name] = e
106
- e.prototype.name = `Runtime.${e.name}`
107
- })
108
- },
109
- })
110
- const require_VerboseLog = __commonJS({
111
- "VerboseLog.js": function (exports2) {
112
- "use strict"
113
-
114
- const EnvVarName = "AWS_LAMBDA_RUNTIME_VERBOSE"
115
- const Tag = "RUNTIME"
116
- const Verbosity = (() => {
117
- if (!process.env[EnvVarName]) {
118
- return 0
119
- }
120
- try {
121
- const verbosity = parseInt(process.env[EnvVarName])
122
- return verbosity < 0 ? 0 : verbosity > 3 ? 3 : verbosity
123
- } catch (_) {
124
- return 0
125
- }
126
- })()
127
- exports2.logger = function (category) {
128
- return {
129
- verbose() {
130
- if (Verbosity >= 1) {
131
- console.log.apply(null, [Tag, category, ...arguments])
132
- }
133
- },
134
- vverbose() {
135
- if (Verbosity >= 2) {
136
- console.log.apply(null, [Tag, category, ...arguments])
137
- }
138
- },
139
- vvverbose() {
140
- if (Verbosity >= 3) {
141
- console.log.apply(null, [Tag, category, ...arguments])
142
- }
143
- },
144
- }
145
- }
146
- },
147
- })
148
- const require_HttpResponseStream = __commonJS({
149
- "HttpResponseStream.js": function (exports2, module2) {
150
- "use strict"
151
-
152
- const METADATA_PRELUDE_CONTENT_TYPE =
153
- "application/vnd.awslambda.http-integration-response"
154
- const DELIMITER_LEN = 8
155
- const HttpResponseStream2 = class {
156
- static from(underlyingStream, prelude) {
157
- underlyingStream.setContentType(METADATA_PRELUDE_CONTENT_TYPE)
158
- const metadataPrelude = JSON.stringify(prelude)
159
- underlyingStream._onBeforeFirstWrite = (write) => {
160
- write(metadataPrelude)
161
- write(new Uint8Array(DELIMITER_LEN))
162
- }
163
- return underlyingStream
164
- }
165
- }
166
- module2.exports.HttpResponseStream = HttpResponseStream2
167
- },
168
- })
169
- const path = require("path")
170
- const fs = require("fs")
171
- const {
172
- HandlerNotFound,
173
- MalformedHandlerName,
174
- ImportModuleError,
175
- UserCodeSyntaxError,
176
- } = require_Errors()
177
- const { verbose } = require_VerboseLog().logger("LOADER")
178
- const { HttpResponseStream } = require_HttpResponseStream()
179
- const FUNCTION_EXPR = /^([^.]*)\.(.*)$/
180
- const RELATIVE_PATH_SUBSTRING = ".."
181
- const HANDLER_STREAMING = Symbol.for("aws.lambda.runtime.handler.streaming")
182
- const STREAM_RESPONSE = "response"
183
- const NoGlobalAwsLambda =
184
- process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA === "1" ||
185
- process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA === "true"
186
- function _moduleRootAndHandler(fullHandlerString) {
187
- const handlerString = path.basename(fullHandlerString)
188
- const moduleRoot = fullHandlerString.substring(
189
- 0,
190
- fullHandlerString.indexOf(handlerString),
191
- )
192
- return [moduleRoot, handlerString]
193
- }
194
- function _splitHandlerString(handler) {
195
- const match = handler.match(FUNCTION_EXPR)
196
- if (!match || match.length != 3) {
197
- throw new MalformedHandlerName("Bad handler")
198
- }
199
- return [match[1], match[2]]
200
- }
201
- function _resolveHandler(object, nestedProperty) {
202
- return nestedProperty.split(".").reduce((nested, key) => {
203
- return nested && nested[key]
204
- }, object)
22
+ const { require: tsxRequire } = require(`tsx/cjs/api`)
23
+ const {
24
+ HandlerNotFound,
25
+ MalformedHandlerName,
26
+ ImportModuleError,
27
+ UserCodeSyntaxError,
28
+ MalformedStreamingHandler,
29
+ } = require("./Errors.js")
30
+ const { verbose } = require("./VerboseLog.js").logger("LOADER")
31
+ const { HttpResponseStream } = require("./HttpResponseStream.js")
32
+
33
+ const FUNCTION_EXPR = /^([^.]*)\.(.*)$/
34
+ const RELATIVE_PATH_SUBSTRING = ".."
35
+ const HANDLER_STREAMING = Symbol.for("aws.lambda.runtime.handler.streaming")
36
+ const HANDLER_HIGHWATERMARK = Symbol.for(
37
+ "aws.lambda.runtime.handler.streaming.highWaterMark",
38
+ )
39
+ const STREAM_RESPONSE = "response"
40
+
41
+ // `awslambda.streamifyResponse` function is provided by default.
42
+ const NoGlobalAwsLambda =
43
+ process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA === "1" ||
44
+ process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA === "true"
45
+
46
+ /**
47
+ * Break the full handler string into two pieces, the module root and the actual
48
+ * handler string.
49
+ * Given './somepath/something/module.nestedobj.handler' this returns
50
+ * ['./somepath/something', 'module.nestedobj.handler']
51
+ */
52
+ function _moduleRootAndHandler(fullHandlerString) {
53
+ const handlerString = path.basename(fullHandlerString)
54
+ const moduleRoot = fullHandlerString.substring(
55
+ 0,
56
+ fullHandlerString.indexOf(handlerString),
57
+ )
58
+ return [moduleRoot, handlerString]
59
+ }
60
+
61
+ /**
62
+ * Split the handler string into two pieces: the module name and the path to
63
+ * the handler function.
64
+ */
65
+ function _splitHandlerString(handler) {
66
+ const match = handler.match(FUNCTION_EXPR)
67
+ if (!match || match.length !== 3) {
68
+ throw new MalformedHandlerName("Bad handler")
205
69
  }
206
- function _tryRequireFile(file, extension) {
207
- const path2 = file + (extension || "")
208
- verbose("Try loading as commonjs:", path2)
209
- return fs.existsSync(path2) ? require(path2) : void 0
70
+ return [match[1], match[2]] // [module, function-path]
71
+ }
72
+
73
+ /**
74
+ * Resolve the user's handler function from the module.
75
+ */
76
+ function _resolveHandler(object, nestedProperty) {
77
+ return nestedProperty.split(".").reduce((nested, key) => {
78
+ return nested && nested[key]
79
+ }, object)
80
+ }
81
+
82
+ function _tryRequireFile(file, extension) {
83
+ const pathRequireFile = file + (extension || "")
84
+ verbose("Try loading as commonjs:", pathRequireFile)
85
+ return fs.existsSync(pathRequireFile) ? require(pathRequireFile) : undefined
86
+ }
87
+
88
+ async function _tryAwaitImport(file, extension) {
89
+ const pathAwaitImport = file + (extension || "")
90
+ verbose("Try loading as esmodule:", pathAwaitImport)
91
+
92
+ if (fs.existsSync(pathAwaitImport)) {
93
+ // eslint-disable-next-line no-return-await
94
+ return await import(pathToFileURL(pathAwaitImport).href)
210
95
  }
211
- async function _tryAwaitImport(file, extension) {
212
- const path2 = file + (extension || "")
213
- verbose("Try loading as esmodule:", path2)
214
- if (fs.existsSync(path2)) {
215
- return await import(pathToFileURL(path2).href)
216
- }
217
- return void 0
96
+
97
+ return undefined
98
+ }
99
+
100
+ function _hasFolderPackageJsonTypeModule(folder) {
101
+ // Check if package.json exists, return true if type === "module" in package json.
102
+ // If there is no package.json, and there is a node_modules, return false.
103
+ // Check parent folder otherwise, if there is one.
104
+ if (folder.endsWith("/node_modules")) {
105
+ return false
218
106
  }
219
- function _hasFolderPackageJsonTypeModule(folder) {
220
- if (folder.endsWith("/node_modules")) {
221
- return false
222
- }
223
- const pj = path.join(folder, "/package.json")
224
- if (fs.existsSync(pj)) {
225
- try {
226
- const pkg = JSON.parse(fs.readFileSync(pj))
227
- if (pkg) {
228
- if (pkg.type === "module") {
229
- verbose(`'type: module' detected in ${pj}`)
230
- return true
231
- }
232
- verbose(`'type: module' not detected in ${pj}`)
233
- return false
107
+
108
+ const pj = path.join(folder, "/package.json")
109
+ if (fs.existsSync(pj)) {
110
+ try {
111
+ const pkg = JSON.parse(fs.readFileSync(pj))
112
+ if (pkg) {
113
+ if (pkg.type === "module") {
114
+ verbose("type: module detected in", pj)
115
+ return true
234
116
  }
235
- } catch (e) {
236
- console.warn(
237
- `${pj} cannot be read, it will be ignored for ES module detection purposes.`,
238
- e,
239
- )
117
+ verbose("type: module not detected in", pj)
240
118
  return false
241
119
  }
242
- }
243
- if (folder === "/") {
120
+ } catch (e) {
121
+ console.warn(
122
+ `${pj} cannot be read, it will be ignored for ES module detection purposes.`,
123
+ e,
124
+ )
244
125
  return false
245
126
  }
246
- return _hasFolderPackageJsonTypeModule(path.resolve(folder, ".."))
247
127
  }
248
- function _hasPackageJsonTypeModule(file) {
249
- const jsPath = `${file}.js`
250
- return fs.existsSync(jsPath)
251
- ? _hasFolderPackageJsonTypeModule(path.resolve(path.dirname(jsPath)))
252
- : false
128
+
129
+ if (folder === "/") {
130
+ // We have reached root without finding either a package.json or a node_modules.
131
+ return false
253
132
  }
254
- async function _tryRequire(appRoot, moduleRoot, module2) {
255
- verbose(
256
- "Try loading as commonjs: ",
257
- module2,
258
- " with paths: ,",
259
- appRoot,
260
- moduleRoot,
261
- )
262
- const lambdaStylePath = path.resolve(appRoot, moduleRoot, module2)
263
- const extensionless = _tryRequireFile(lambdaStylePath)
264
- if (extensionless) {
265
- return extensionless
266
- }
267
- const pjHasModule = _hasPackageJsonTypeModule(lambdaStylePath)
268
- if (!pjHasModule) {
269
- const loaded2 = _tryRequireFile(lambdaStylePath, ".js")
270
- if (loaded2) {
271
- return loaded2
272
- }
273
- }
274
- const loaded =
275
- (pjHasModule && (await _tryAwaitImport(lambdaStylePath, ".js"))) ||
276
- (await _tryAwaitImport(lambdaStylePath, ".mjs")) ||
277
- _tryRequireFile(lambdaStylePath, ".cjs")
133
+
134
+ return _hasFolderPackageJsonTypeModule(path.resolve(folder, ".."))
135
+ }
136
+
137
+ function _hasPackageJsonTypeModule(file) {
138
+ // File must have a .js extension
139
+ const jsPath = `${file}.js`
140
+ return fs.existsSync(jsPath)
141
+ ? _hasFolderPackageJsonTypeModule(path.resolve(path.dirname(jsPath)))
142
+ : false
143
+ }
144
+
145
+ /**
146
+ * Attempt to load the user's module.
147
+ * Attempts to directly resolve the module relative to the application root,
148
+ * then falls back to the more general require().
149
+ */
150
+ async function _tryRequire(appRoot, moduleRoot, module) {
151
+ verbose(
152
+ "Try loading as commonjs: ",
153
+ module,
154
+ " with paths: ,",
155
+ appRoot,
156
+ moduleRoot,
157
+ )
158
+
159
+ const lambdaStylePath = path.resolve(appRoot, moduleRoot, module)
160
+
161
+ // Extensionless files are loaded via require.
162
+ const extensionless = _tryRequireFile(lambdaStylePath)
163
+ if (extensionless) {
164
+ return extensionless
165
+ }
166
+
167
+ // If package.json type != module, .js files are loaded via require.
168
+ const pjHasModule = _hasPackageJsonTypeModule(lambdaStylePath)
169
+ if (!pjHasModule) {
170
+ const loaded = _tryRequireFile(lambdaStylePath, ".js")
278
171
  if (loaded) {
279
172
  return loaded
280
173
  }
281
- verbose(
282
- "Try loading as commonjs: ",
283
- module2,
284
- " with path(s): ",
285
- appRoot,
286
- moduleRoot,
287
- )
288
- const nodeStylePath = require.resolve(module2, {
289
- paths: [appRoot, moduleRoot],
290
- })
291
- return require(nodeStylePath)
292
174
  }
293
- async function _loadUserApp(appRoot, moduleRoot, module2) {
294
- if (!NoGlobalAwsLambda) {
295
- globalThis.awslambda = {
296
- streamifyResponse: (handler) => {
297
- handler[HANDLER_STREAMING] = STREAM_RESPONSE
298
- return handler
299
- },
300
- HttpResponseStream,
301
- }
302
- }
303
- try {
304
- return await _tryRequire(appRoot, moduleRoot, module2)
305
- } catch (e) {
306
- if (e instanceof SyntaxError) {
307
- throw new UserCodeSyntaxError(e)
308
- } else if (e.code !== void 0 && e.code === "MODULE_NOT_FOUND") {
309
- verbose("globalPaths", JSON.stringify(require("module").globalPaths))
310
- throw new ImportModuleError(e)
311
- } else {
312
- throw e
313
- }
314
- }
175
+
176
+ // If still not loaded, try .js, .mjs, .cjs and .ts in that order.
177
+ // Files ending with .js are loaded as ES modules when the nearest parent package.json
178
+ // file contains a top-level field "type" with a value of "module".
179
+ // https://nodejs.org/api/packages.html#packages_type
180
+ const loaded =
181
+ (pjHasModule && (await _tryAwaitImport(lambdaStylePath, ".js"))) ||
182
+ (await _tryAwaitImport(lambdaStylePath, ".mjs")) ||
183
+ _tryRequireFile(lambdaStylePath, ".cjs") ||
184
+ tsxRequire(`${lambdaStylePath}.ts`, `${lambdaStylePath}.ts`)
185
+ if (loaded) {
186
+ return loaded
315
187
  }
316
- function _throwIfInvalidHandler(fullHandlerString) {
317
- if (fullHandlerString.includes(RELATIVE_PATH_SUBSTRING)) {
318
- throw new MalformedHandlerName(
319
- `'${fullHandlerString}' is not a valid handler name. Use absolute paths when specifying root directories in handler names.`,
320
- )
188
+
189
+ verbose(
190
+ "Try loading as commonjs: ",
191
+ module,
192
+ " with path(s): ",
193
+ appRoot,
194
+ moduleRoot,
195
+ )
196
+
197
+ // Why not just require(module)?
198
+ // Because require() is relative to __dirname, not process.cwd(). And the
199
+ // runtime implementation is not located in /var/task
200
+ // This won't work (yet) for esModules as import.meta.resolve is still experimental
201
+ // See: https://nodejs.org/api/esm.html#esm_import_meta_resolve_specifier_parent
202
+ const nodeStylePath = require.resolve(module, {
203
+ paths: [appRoot, moduleRoot],
204
+ })
205
+
206
+ return require(nodeStylePath)
207
+ }
208
+
209
+ /**
210
+ * Load the user's application or throw a descriptive error.
211
+ * @throws Runtime errors in two cases
212
+ * 1 - UserCodeSyntaxError if there's a syntax error while loading the module
213
+ * 2 - ImportModuleError if the module cannot be found
214
+ */
215
+ async function _loadUserApp(appRoot, moduleRoot, module) {
216
+ if (!NoGlobalAwsLambda) {
217
+ globalThis.awslambda = {
218
+ HttpResponseStream,
219
+ streamifyResponse: (handler, options) => {
220
+ // eslint-disable-next-line no-param-reassign
221
+ handler[HANDLER_STREAMING] = STREAM_RESPONSE
222
+ if (typeof options?.highWaterMark === "number") {
223
+ // eslint-disable-next-line no-param-reassign
224
+ handler[HANDLER_HIGHWATERMARK] = Number.parseInt(
225
+ options.highWaterMark,
226
+ 10,
227
+ )
228
+ }
229
+ return handler
230
+ },
321
231
  }
322
232
  }
323
- function _isHandlerStreaming(handler) {
324
- if (
325
- typeof handler[HANDLER_STREAMING] === "undefined" ||
326
- handler[HANDLER_STREAMING] === null ||
327
- handler[HANDLER_STREAMING] === false
328
- ) {
329
- return false
330
- }
331
- if (handler[HANDLER_STREAMING] === STREAM_RESPONSE) {
332
- return STREAM_RESPONSE
233
+
234
+ try {
235
+ return await _tryRequire(appRoot, moduleRoot, module)
236
+ } catch (e) {
237
+ if (e instanceof SyntaxError) {
238
+ throw new UserCodeSyntaxError(e)
239
+ } else if (e.code !== undefined && e.code === "MODULE_NOT_FOUND") {
240
+ verbose("globalPaths", JSON.stringify(require("node:module").globalPaths))
241
+ throw new ImportModuleError(e)
242
+ } else {
243
+ throw e
333
244
  }
334
- throw new MalformedStreamingHandler("Only response streaming is supported.")
335
245
  }
336
- module.exports.load = async function (appRoot, fullHandlerString) {
337
- _throwIfInvalidHandler(fullHandlerString)
338
- const [moduleRoot, moduleAndHandler] =
339
- _moduleRootAndHandler(fullHandlerString)
340
- const [module2, handlerPath] = _splitHandlerString(moduleAndHandler)
341
- const userApp = await _loadUserApp(appRoot, moduleRoot, module2)
342
- const handlerFunc = _resolveHandler(userApp, handlerPath)
343
- if (!handlerFunc) {
344
- throw new HandlerNotFound(
345
- `${fullHandlerString} is undefined or not exported`,
346
- )
347
- }
348
- if (typeof handlerFunc !== "function") {
349
- throw new HandlerNotFound(`${fullHandlerString} is not a function`)
350
- }
351
- return handlerFunc
246
+ }
247
+
248
+ function _throwIfInvalidHandler(fullHandlerString) {
249
+ if (fullHandlerString.includes(RELATIVE_PATH_SUBSTRING)) {
250
+ throw new MalformedHandlerName(
251
+ `'${fullHandlerString}' is not a valid handler name. Use absolute paths when specifying root directories in handler names.`,
252
+ )
352
253
  }
353
- module.exports.getHandlerMetadata = function (handlerFunc) {
354
- return {
355
- streaming: _isHandlerStreaming(handlerFunc),
356
- }
254
+ }
255
+
256
+ function _isHandlerStreaming(handler) {
257
+ if (
258
+ handler[HANDLER_STREAMING] === undefined ||
259
+ handler[HANDLER_STREAMING] === null ||
260
+ handler[HANDLER_STREAMING] === false
261
+ ) {
262
+ return false
263
+ }
264
+
265
+ if (handler[HANDLER_STREAMING] === STREAM_RESPONSE) {
266
+ return STREAM_RESPONSE
267
+ }
268
+ throw new MalformedStreamingHandler("Only response streaming is supported.")
269
+ }
270
+
271
+ function _highWaterMark(handler) {
272
+ if (
273
+ handler[HANDLER_HIGHWATERMARK] === undefined ||
274
+ handler[HANDLER_HIGHWATERMARK] === null ||
275
+ handler[HANDLER_HIGHWATERMARK] === false
276
+ ) {
277
+ return undefined
278
+ }
279
+
280
+ const hwm = Number.parseInt(handler[HANDLER_HIGHWATERMARK], 10)
281
+ return Number.isNaN(hwm) ? undefined : hwm
282
+ }
283
+
284
+ /**
285
+ * Load the user's function with the approot and the handler string.
286
+ * @param appRoot {string}
287
+ * The path to the application root.
288
+ * @param handlerString {string}
289
+ * The user-provided handler function in the form 'module.function'.
290
+ * @return userFuction {function}
291
+ * The user's handler function. This function will be passed the event body,
292
+ * the context object, and the callback function.
293
+ * @throws In five cases:-
294
+ * 1 - if the handler string is incorrectly formatted an error is thrown
295
+ * 2 - if the module referenced by the handler cannot be loaded
296
+ * 3 - if the function in the handler does not exist in the module
297
+ * 4 - if a property with the same name, but isn't a function, exists on the
298
+ * module
299
+ * 5 - the handler includes illegal character sequences (like relative paths
300
+ * for traversing up the filesystem '..')
301
+ * Errors for scenarios known by the runtime, will be wrapped by Runtime.* errors.
302
+ */
303
+ module.exports.load = async function (appRoot, fullHandlerString) {
304
+ _throwIfInvalidHandler(fullHandlerString)
305
+
306
+ const [moduleRoot, moduleAndHandler] =
307
+ _moduleRootAndHandler(fullHandlerString)
308
+ const [module, handlerPath] = _splitHandlerString(moduleAndHandler)
309
+
310
+ const userApp = await _loadUserApp(appRoot, moduleRoot, module)
311
+ const handlerFunc = _resolveHandler(userApp, handlerPath)
312
+
313
+ if (!handlerFunc) {
314
+ throw new HandlerNotFound(
315
+ `${fullHandlerString} is undefined or not exported`,
316
+ )
317
+ }
318
+
319
+ if (typeof handlerFunc !== "function") {
320
+ throw new HandlerNotFound(`${fullHandlerString} is not a function`)
321
+ }
322
+
323
+ return handlerFunc
324
+ }
325
+
326
+ module.exports.isHandlerFunction = function (value) {
327
+ return typeof value === "function"
328
+ }
329
+
330
+ module.exports.getHandlerMetadata = function (handlerFunc) {
331
+ return {
332
+ highWaterMark: _highWaterMark(handlerFunc),
333
+ streaming: _isHandlerStreaming(handlerFunc),
357
334
  }
358
- module.exports.STREAM_RESPONSE = STREAM_RESPONSE
359
- })()
335
+ }
336
+
337
+ module.exports.STREAM_RESPONSE = STREAM_RESPONSE
@@ -0,0 +1,54 @@
1
+ /* eslint-disable prefer-rest-params */
2
+ /* eslint-disable func-names */
3
+ /**
4
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
+ */
6
+
7
+ "use strict"
8
+
9
+ const process = require("node:process")
10
+
11
+ const EnvVarName = "AWS_LAMBDA_RUNTIME_VERBOSE"
12
+ const Tag = "RUNTIME"
13
+ const Verbosity = (() => {
14
+ if (!process.env[EnvVarName]) {
15
+ return 0
16
+ }
17
+
18
+ try {
19
+ const verbosity = Number.parseInt(process.env[EnvVarName], 10)
20
+ // eslint-disable-next-line unicorn/no-nested-ternary
21
+ return verbosity < 0 ? 0 : verbosity > 3 ? 3 : verbosity
22
+ } catch {
23
+ return 0
24
+ }
25
+ })()
26
+
27
+ exports.logger = function (category) {
28
+ return {
29
+ verbose() {
30
+ if (Verbosity >= 1) {
31
+ const args = [...arguments].map((arg) =>
32
+ typeof arg === "function" ? arg() : arg,
33
+ )
34
+ Reflect.apply(console.log, null, [Tag, category, ...args])
35
+ }
36
+ },
37
+ vverbose() {
38
+ if (Verbosity >= 2) {
39
+ const args = [...arguments].map((arg) =>
40
+ typeof arg === "function" ? arg() : arg,
41
+ )
42
+ Reflect.apply(console.log, null, [Tag, category, ...args])
43
+ }
44
+ },
45
+ vvverbose() {
46
+ if (Verbosity >= 3) {
47
+ const args = [...arguments].map((arg) =>
48
+ typeof arg === "function" ? arg() : arg,
49
+ )
50
+ Reflect.apply(console.log, null, [Tag, category, ...args])
51
+ }
52
+ },
53
+ }
54
+ }
@@ -21,13 +21,20 @@ export { generateAlbHapiPath } from "./generateHapiPath.js"
21
21
  const { isArray } = Array
22
22
  const { keys } = Object
23
23
 
24
+ const possibleBinaryContentTypes = [
25
+ "application/octet-stream",
26
+ "multipart/form-data",
27
+ ]
28
+
24
29
  // Detect the toString encoding from the request headers content-type
25
30
  // enhance if further content types need to be non utf8 encoded.
26
31
  export function detectEncoding(request) {
27
32
  const contentType = request.headers["content-type"]
28
33
 
29
34
  return typeof contentType === "string" &&
30
- contentType.includes("multipart/form-data")
35
+ possibleBinaryContentTypes.some((possibleBinaryContentType) =>
36
+ contentType.includes(possibleBinaryContentType),
37
+ )
31
38
  ? "binary"
32
39
  : "utf8"
33
40
  }