serverless-offline 12.0.3 → 13.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.
Files changed (31) hide show
  1. package/README.md +6 -0
  2. package/package.json +31 -35
  3. package/src/ServerlessOffline.js +7 -5
  4. package/src/config/supportedRuntimes.js +4 -5
  5. package/src/events/alb/Alb.js +0 -6
  6. package/src/events/alb/HttpServer.js +3 -7
  7. package/src/events/authMatchPolicyResource.js +1 -1
  8. package/src/events/http/Endpoint.js +4 -7
  9. package/src/events/http/HttpServer.js +29 -28
  10. package/src/events/http/createAuthScheme.js +47 -36
  11. package/src/events/http/createJWTAuthScheme.js +1 -1
  12. package/src/events/http/javaHelpers.js +0 -7
  13. package/src/events/http/lambda-events/VelocityContext.js +2 -2
  14. package/src/events/http/lambda-events/renderVelocityTemplateObject.js +11 -6
  15. package/src/events/schedule/Schedule.js +8 -4
  16. package/src/events/schedule/ScheduleEvent.js +1 -1
  17. package/src/events/websocket/HttpServer.js +2 -2
  18. package/src/events/websocket/http-routes/connections/ConnectionsController.js +1 -1
  19. package/src/index.js +0 -11
  20. package/src/lambda/LambdaFunction.js +1 -1
  21. package/src/lambda/handler-runner/docker-runner/DockerContainer.js +8 -8
  22. package/src/lambda/handler-runner/go-runner/GoRunner.js +2 -2
  23. package/src/lambda/handler-runner/python-runner/PythonRunner.js +4 -6
  24. package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +3 -5
  25. package/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +14 -15
  26. package/src/lambda/routes/invocations/InvocationsController.js +1 -1
  27. package/src/lambda/routes/invocations/invocationsRoute.js +2 -2
  28. package/src/lambda/routes/invoke-async/invokeAsyncRoute.js +1 -1
  29. package/src/utils/checkGoVersion.js +1 -1
  30. package/src/utils/generateHapiPath.js +1 -1
  31. package/src/utils/splitHandlerPathAndName.js +1 -1
package/README.md CHANGED
@@ -38,6 +38,12 @@ To do so, it starts an HTTP server that handles the request's lifecycle like API
38
38
 
39
39
  This plugin is updated by its users, I just do maintenance and ensure that PRs are relevant to the community. In other words, if you [find a bug or want a new feature](https://github.com/dherault/serverless-offline/issues), please help us by becoming one of the [contributors](https://github.com/dherault/serverless-offline/graphs/contributors) :v: ! See the [contributing section](#contributing).
40
40
 
41
+ ## Looking for maintainers ⚠️
42
+
43
+ Applications welcome! This package is used by thousands of people daily. Yet we lack a maintainer! If you think you'll be a good fit for the role, please send me an email at dherault/at/gmail.com.
44
+
45
+ See https://github.com/dherault/serverless-offline/issues/1704.
46
+
41
47
  ## Documentation
42
48
 
43
49
  - [Installation](#installation)
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": "12.0.3",
4
+ "version": "13.0.0",
5
5
  "description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
6
6
  "license": "MIT",
7
7
  "exports": {
@@ -24,12 +24,8 @@
24
24
  "prettify:updated": "pipe-git-updated --ext=css --ext=html --ext=js --ext=json --ext=md --ext=yaml --ext=yml -- prettier --write",
25
25
  "test": "mocha --require ./tests/mochaHooks.cjs",
26
26
  "test:cov": "NODE_OPTIONS='--experimental-loader @istanbuljs/esm-loader-hook' nyc --reporter=html npm test",
27
- "old-test:jest": "npm run build && jest --verbose --silent --runInBand",
28
- "old-test:cov": "npm run build && jest --coverage --silent --runInBand --collectCoverageFrom=src/**/*.js",
29
- "old-test:log": "npm run build && jest --verbose",
30
- "old-test:noBuild": "jest --verbose --runInBand --bail",
31
- "old-test:unit": "jest --verbose --silent --runInBand --config jest.config.units.js",
32
- "old-test:watch": "SKIP_SETUP=true jest --verbose --watch"
27
+ "test:node": "TEST=unit mocha --require ./tests/mochaHooks.cjs",
28
+ "test:unit": "TEST=unit mocha --require ./tests/mochaHooks.cjs"
33
29
  },
34
30
  "repository": {
35
31
  "type": "git",
@@ -55,7 +51,7 @@
55
51
  ],
56
52
  "author": "David Hérault <dherault@gmail.com> (https://github.com/dherault)",
57
53
  "engines": {
58
- "node": ">=14.18.0"
54
+ "node": ">=18.12.0"
59
55
  },
60
56
  "standard-version": {
61
57
  "skip": {
@@ -82,47 +78,47 @@
82
78
  ]
83
79
  },
84
80
  "dependencies": {
85
- "@aws-sdk/client-lambda": "^3.231.0",
86
- "@hapi/boom": "^10.0.0",
87
- "@hapi/h2o2": "^10.0.0",
88
- "@hapi/hapi": "^21.1.0",
89
- "@serverless/utils": "^6.8.2",
90
- "array-unflat-js": "^0.1.0",
91
- "boxen": "^7.0.0",
92
- "chalk": "^5.2.0",
93
- "execa": "^6.1.0",
94
- "fs-extra": "^11.1.0",
95
- "is-wsl": "^2.2.0",
81
+ "@aws-sdk/client-lambda": "^3.414.0",
82
+ "@hapi/boom": "^10.0.1",
83
+ "@hapi/h2o2": "^10.0.4",
84
+ "@hapi/hapi": "^21.3.2",
85
+ "@serverless/utils": "^6.15.0",
86
+ "array-unflat-js": "^0.1.3",
87
+ "boxen": "^7.1.1",
88
+ "chalk": "^5.3.0",
89
+ "desm": "^1.3.0",
90
+ "execa": "^8.0.1",
91
+ "fs-extra": "^11.1.1",
92
+ "is-wsl": "^3.0.0",
96
93
  "java-invoke-local": "0.0.6",
97
- "jose": "^4.11.1",
94
+ "jose": "^4.14.6",
98
95
  "js-string-escape": "^1.0.1",
99
96
  "jsonpath-plus": "^7.2.0",
100
97
  "jsonschema": "^1.4.1",
101
98
  "jszip": "^3.10.1",
102
- "luxon": "^3.1.1",
103
- "node-fetch": "^3.3.0",
104
- "node-schedule": "^2.1.0",
105
- "object.hasown": "^1.1.2",
99
+ "luxon": "^3.2.0",
100
+ "node-schedule": "^2.1.1",
106
101
  "p-memoize": "^7.1.1",
107
- "p-retry": "^5.1.2",
102
+ "p-retry": "^6.0.0",
108
103
  "velocityjs": "^2.0.6",
109
- "ws": "^8.11.0"
104
+ "ws": "^8.14.2"
110
105
  },
111
106
  "devDependencies": {
112
107
  "@istanbuljs/esm-loader-hook": "^0.2.0",
113
- "archiver": "^5.3.1",
114
- "eslint": "^8.30.0",
108
+ "archiver": "^6.0.1",
109
+ "eslint": "^8.49.0",
115
110
  "eslint-config-airbnb-base": "^15.0.0",
116
- "eslint-config-prettier": "^8.5.0",
117
- "eslint-plugin-import": "^2.25.4",
118
- "eslint-plugin-prettier": "^4.2.1",
111
+ "eslint-config-prettier": "^9.0.0",
112
+ "eslint-plugin-import": "^2.28.1",
113
+ "eslint-plugin-prettier": "^5.0.0",
114
+ "eslint-plugin-unicorn": "^48.0.1",
119
115
  "git-list-updated": "^1.2.1",
120
- "husky": "^8.0.2",
121
- "lint-staged": "^13.1.0",
116
+ "husky": "^8.0.3",
117
+ "lint-staged": "^14.0.1",
122
118
  "mocha": "^10.2.0",
123
119
  "nyc": "^15.1.0",
124
- "prettier": "^2.8.1",
125
- "serverless": "^3.25.1",
120
+ "prettier": "^3.0.3",
121
+ "serverless": "^3.35.2",
126
122
  "standard-version": "^9.5.0"
127
123
  },
128
124
  "peerDependencies": {
@@ -107,8 +107,10 @@ export default class ServerlessOffline {
107
107
  const eventModules = []
108
108
 
109
109
  if (this.#lambda) {
110
- eventModules.push(this.#lambda.cleanup())
111
- eventModules.push(this.#lambda.stop(SERVER_SHUTDOWN_TIMEOUT))
110
+ eventModules.push(
111
+ this.#lambda.cleanup(),
112
+ this.#lambda.stop(SERVER_SHUTDOWN_TIMEOUT),
113
+ )
112
114
  }
113
115
 
114
116
  if (this.#alb) {
@@ -259,13 +261,13 @@ export default class ServerlessOffline {
259
261
 
260
262
  // Parse CORS options
261
263
  this.#options.corsAllowHeaders = this.#options.corsAllowHeaders
262
- .replace(/\s/g, '')
264
+ .replaceAll(' ', '')
263
265
  .split(',')
264
266
  this.#options.corsAllowOrigin = this.#options.corsAllowOrigin
265
- .replace(/\s/g, '')
267
+ .replaceAll(' ', '')
266
268
  .split(',')
267
269
  this.#options.corsExposedHeaders = this.#options.corsExposedHeaders
268
- .replace(/\s/g, '')
270
+ .replaceAll(' ', '')
269
271
  .split(',')
270
272
 
271
273
  this.#options.corsConfig = {
@@ -4,18 +4,16 @@
4
4
  // .NET CORE
5
5
  // export const supportedDotnetcore = new Set([
6
6
  // 'dotnet6',
7
- // 'dotnetcore3.1',
8
7
  // ])
9
8
 
10
9
  // GO
11
10
  export const supportedGo = new Set(['go1.x'])
12
11
 
13
12
  // JAVA
14
- export const supportedJava = new Set(['java8', 'java8.al2', 'java11'])
13
+ export const supportedJava = new Set(['java8', 'java8.al2', 'java11', 'java17'])
15
14
 
16
15
  // NODE.JS
17
16
  export const supportedNodejs = new Set([
18
- 'nodejs12.x',
19
17
  'nodejs14.x',
20
18
  'nodejs16.x',
21
19
  'nodejs18.x',
@@ -26,14 +24,15 @@ export const supportedProvided = new Set(['provided', 'provided.al2'])
26
24
 
27
25
  // PYTHON
28
26
  export const supportedPython = new Set([
29
- 'python3.6',
30
27
  'python3.7',
31
28
  'python3.8',
32
29
  'python3.9',
30
+ 'python3.10',
31
+ 'python3.11',
33
32
  ])
34
33
 
35
34
  // RUBY
36
- export const supportedRuby = new Set(['ruby2.7'])
35
+ export const supportedRuby = new Set(['ruby2.7', 'ruby3.2'])
37
36
 
38
37
  // deprecated runtimes
39
38
  // https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html
@@ -1,4 +1,3 @@
1
- import { log } from '@serverless/utils/log.js'
2
1
  import AlbEventDefinition from './AlbEventDefinition.js'
3
2
  import HttpServer from './HttpServer.js'
4
3
 
@@ -15,11 +14,6 @@ export default class Alb {
15
14
  this.#lambda = lambda
16
15
  this.#options = options
17
16
  this.#serverless = serverless
18
-
19
- log.warning(`
20
- Application Load Balancer (ALB) support in serverless-offline is experimental.
21
- Please file an issue for any bugs, missing features or other feedback: https://github.com/dherault/serverless-offline/issues
22
- `)
23
17
  }
24
18
 
25
19
  start() {
@@ -105,13 +105,9 @@ export default class HttpServer {
105
105
  if (request.method === 'options') {
106
106
  response.statusCode = 200
107
107
 
108
- if (request.headers['access-control-expose-headers']) {
109
- response.headers['access-control-expose-headers'] =
110
- request.headers['access-control-expose-headers']
111
- } else {
112
- response.headers['access-control-expose-headers'] =
113
- 'content-type, content-length, etag'
114
- }
108
+ response.headers['access-control-expose-headers'] =
109
+ request.headers['access-control-expose-headers'] ||
110
+ 'content-type, content-length, etag'
115
111
  response.headers['access-control-max-age'] = 60 * 10
116
112
 
117
113
  if (request.headers['access-control-request-headers']) {
@@ -61,7 +61,7 @@ export default function authMatchPolicyResource(policyResource, resource) {
61
61
  // for the requested resource and the resource defined in the policy
62
62
  // Need to create a regex replacing ? with one character and * with any number of characters
63
63
  const regExp = new RegExp(
64
- parsedPolicyResource.path.replace(/\*/g, '.*').replace(/\?/g, '.'),
64
+ parsedPolicyResource.path.replaceAll('*', '.*').replaceAll('?', '.'),
65
65
  )
66
66
 
67
67
  return regExp.test(parsedResource.path)
@@ -1,26 +1,23 @@
1
1
  import { existsSync, readFileSync } from 'node:fs'
2
- import { dirname, resolve } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
2
  import { log } from '@serverless/utils/log.js'
3
+ import { join } from 'desm'
5
4
  import OfflineEndpoint from './OfflineEndpoint.js'
6
5
 
7
6
  const { entries } = Object
8
7
 
9
- const __dirname = dirname(fileURLToPath(import.meta.url))
10
-
11
8
  // velocity template defaults
12
9
  const defaultRequestTemplate = readFileSync(
13
- resolve(__dirname, 'templates/offline-default.req.vm'),
10
+ join(import.meta.url, 'templates/offline-default.req.vm'),
14
11
  'utf8',
15
12
  )
16
13
  const defaultResponseTemplate = readFileSync(
17
- resolve(__dirname, 'templates/offline-default.res.vm'),
14
+ join(import.meta.url, 'templates/offline-default.res.vm'),
18
15
  'utf8',
19
16
  )
20
17
 
21
18
  function getResponseContentType(fep) {
22
19
  if (fep.response && fep.response.headers['Content-Type']) {
23
- return fep.response.headers['Content-Type'].replace(/'/gm, '')
20
+ return fep.response.headers['Content-Type'].replaceAll(/'/gm, '')
24
21
  }
25
22
 
26
23
  return 'application/json'
@@ -57,8 +57,8 @@ export default class HttpServer {
57
57
 
58
58
  async #loadCerts(httpsProtocol) {
59
59
  const [cert, key] = await Promise.all([
60
- readFile(resolve(httpsProtocol, 'cert.pem'), 'utf-8'),
61
- readFile(resolve(httpsProtocol, 'key.pem'), 'utf-8'),
60
+ readFile(resolve(httpsProtocol, 'cert.pem'), 'utf8'),
61
+ readFile(resolve(httpsProtocol, 'key.pem'), 'utf8'),
62
62
  ])
63
63
 
64
64
  return {
@@ -164,13 +164,9 @@ export default class HttpServer {
164
164
  if (request.method === 'options') {
165
165
  response.statusCode = 200
166
166
 
167
- if (request.headers['access-control-expose-headers']) {
168
- response.headers['access-control-expose-headers'] =
169
- request.headers['access-control-expose-headers']
170
- } else {
171
- response.headers['access-control-expose-headers'] =
172
- 'content-type, content-length, etag'
173
- }
167
+ response.headers['access-control-expose-headers'] =
168
+ request.headers['access-control-expose-headers'] ||
169
+ 'content-type, content-length, etag'
174
170
  response.headers['access-control-max-age'] = 60 * 10
175
171
 
176
172
  if (request.headers['access-control-request-headers']) {
@@ -323,14 +319,12 @@ export default class HttpServer {
323
319
  (endpoint.isHttpApi &&
324
320
  serverlessAuthorizerOptions?.enableSimpleResponses) ||
325
321
  false,
326
- identitySource:
327
- serverlessAuthorizerOptions?.identitySource ||
328
- 'method.request.header.Authorization',
322
+ identitySource: serverlessAuthorizerOptions?.identitySource,
329
323
  identityValidationExpression:
330
324
  serverlessAuthorizerOptions?.identityValidationExpression || '(.*)',
331
- payloadVersion: !endpoint.isHttpApi
332
- ? '1.0'
333
- : serverlessAuthorizerOptions?.payloadVersion || '2.0',
325
+ payloadVersion: endpoint.isHttpApi
326
+ ? serverlessAuthorizerOptions?.payloadVersion || '2.0'
327
+ : '1.0',
334
328
  resultTtlInSeconds:
335
329
  serverlessAuthorizerOptions?.resultTtlInSeconds || '300',
336
330
  }
@@ -351,6 +345,16 @@ export default class HttpServer {
351
345
  assign(authorizerOptions, endpoint.authorizer)
352
346
  }
353
347
 
348
+ if (
349
+ !authorizerOptions.identitySource &&
350
+ !(
351
+ authorizerOptions.type === 'request' &&
352
+ authorizerOptions.resultTtlInSeconds === 0
353
+ )
354
+ ) {
355
+ authorizerOptions.identitySource = 'method.request.header.Authorization'
356
+ }
357
+
354
358
  // Create a unique scheme per endpoint
355
359
  // This allows the methodArn on the event property to be set appropriately
356
360
  const authKey = `${functionKey}-${authFunctionName}-${method}-${path}`
@@ -502,9 +506,9 @@ export default class HttpServer {
502
506
  : ''
503
507
 
504
508
  const schemas =
505
- endpoint?.request?.schemas !== undefined
506
- ? endpoint.request.schemas[contentType]
507
- : ''
509
+ endpoint?.request?.schemas === undefined
510
+ ? ''
511
+ : endpoint.request.schemas[contentType]
508
512
 
509
513
  // https://hapijs.com/api#route-configuration doesn't seem to support selectively parsing
510
514
  // so we have to do it ourselves
@@ -519,7 +523,7 @@ export default class HttpServer {
519
523
  request.payload.length > 1
520
524
  ) {
521
525
  try {
522
- if (!request.payload || request.payload.length < 1) {
526
+ if (!request.payload || request.payload.length === 0) {
523
527
  request.payload = '{}'
524
528
  }
525
529
 
@@ -640,7 +644,7 @@ export default class HttpServer {
640
644
  for (const [key, value] of entries(endpoint.responses)) {
641
645
  if (
642
646
  key !== 'default' &&
643
- errorMessage.match(`^${value.selectionPattern || key}$`)
647
+ `^${value.selectionPattern || key}$`.test(errorMessage)
644
648
  ) {
645
649
  responseName = key
646
650
  break
@@ -685,11 +689,8 @@ export default class HttpServer {
685
689
  headerValue = valueArray[3]
686
690
  ? jsonPath(result, valueArray.slice(3).join('.'))
687
691
  : result
688
- if (headerValue === undefined || headerValue === null) {
689
- headerValue = ''
690
- } else {
691
- headerValue = headerValue.toString()
692
- }
692
+
693
+ headerValue = headerValue == null ? '' : String(headerValue)
693
694
  } else {
694
695
  log.notice()
695
696
 
@@ -702,7 +703,7 @@ export default class HttpServer {
702
703
  log.notice()
703
704
  }
704
705
  } else {
705
- headerValue = value.match(/^'.*'$/) ? value.slice(1, -1) : value // See #34
706
+ headerValue = /^'.*'$/.test(value) ? value.slice(1, -1) : value // See #34
706
707
  }
707
708
  // Applies the header;
708
709
  if (headerValue === '') {
@@ -748,7 +749,7 @@ export default class HttpServer {
748
749
  const { responseTemplates } = chosenResponse
749
750
 
750
751
  if (typeof responseTemplates === 'object') {
751
- if (keys(responseTemplates).length) {
752
+ if (keys(responseTemplates).length > 0) {
752
753
  // BAD IMPLEMENTATION: first key in responseTemplates
753
754
  const responseTemplate = responseTemplates[responseContentType]
754
755
 
@@ -1137,7 +1138,7 @@ export default class HttpServer {
1137
1138
 
1138
1139
  const resourceRoutes = parseResources(this.#serverless.service.resources)
1139
1140
 
1140
- if (!resourceRoutes || !keys(resourceRoutes).length) {
1141
+ if (!resourceRoutes || keys(resourceRoutes).length === 0) {
1141
1142
  return
1142
1143
  }
1143
1144
 
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  const IDENTITY_SOURCE_TYPE_HEADER = 'header'
15
15
  const IDENTITY_SOURCE_TYPE_QUERYSTRING = 'querystring'
16
+ const IDENTITY_SOURCE_TYPE_NONE = 'none'
16
17
 
17
18
  export default function createAuthScheme(authorizerOptions, provider, lambda) {
18
19
  const authFunName = authorizerOptions.name
@@ -65,38 +66,50 @@ export default function createAuthScheme(authorizerOptions, provider, lambda) {
65
66
  const methodArn = `arn:aws:execute-api:${provider.region}:${accountId}:${apiId}/${provider.stage}/${httpMethod}${resourcePath}`
66
67
 
67
68
  let authorization
68
- if (identitySourceType === IDENTITY_SOURCE_TYPE_HEADER) {
69
- const headers = request.raw.req.headers ?? {}
70
- authorization = headers[identitySourceField]
71
- } else if (identitySourceType === IDENTITY_SOURCE_TYPE_QUERYSTRING) {
72
- const queryStringParameters = parseQueryStringParameters(url) ?? {}
73
- authorization = queryStringParameters[identitySourceField]
74
- } else {
75
- throw new Error(
76
- `No Authorization source has been specified. This should never happen. (λ: ${authFunName})`,
77
- )
69
+ switch (identitySourceType) {
70
+ case IDENTITY_SOURCE_TYPE_HEADER: {
71
+ const headers = request.raw.req.headers ?? {}
72
+ authorization = headers[identitySourceField]
73
+ break
74
+ }
75
+ case IDENTITY_SOURCE_TYPE_QUERYSTRING: {
76
+ const queryStringParameters = parseQueryStringParameters(url) ?? {}
77
+ authorization = queryStringParameters[identitySourceField]
78
+ break
79
+ }
80
+ case IDENTITY_SOURCE_TYPE_NONE: {
81
+ break
82
+ }
83
+ default: {
84
+ throw new Error(
85
+ `No Authorization source has been specified. This should never happen. (λ: ${authFunName})`,
86
+ )
87
+ }
78
88
  }
79
89
 
80
- if (authorization === undefined) {
81
- log.error(
82
- `Identity Source is null for ${identitySourceType} ${identitySourceField} (λ: ${authFunName})`,
90
+ let finalAuthorization
91
+ if (identitySourceType !== IDENTITY_SOURCE_TYPE_NONE) {
92
+ if (authorization === undefined) {
93
+ log.error(
94
+ `Identity Source is null for ${identitySourceType} ${identitySourceField} (λ: ${authFunName})`,
95
+ )
96
+ return Boom.unauthorized(
97
+ 'User is not authorized to access this resource',
98
+ )
99
+ }
100
+
101
+ const identityValidationExpression = new RegExp(
102
+ authorizerOptions.identityValidationExpression,
83
103
  )
84
- return Boom.unauthorized(
85
- 'User is not authorized to access this resource',
104
+ const matchedAuthorization =
105
+ identityValidationExpression.test(authorization)
106
+ finalAuthorization = matchedAuthorization ? authorization : ''
107
+
108
+ log.debug(
109
+ `Retrieved ${identitySourceField} ${identitySourceType} "${finalAuthorization}"`,
86
110
  )
87
111
  }
88
112
 
89
- const identityValidationExpression = new RegExp(
90
- authorizerOptions.identityValidationExpression,
91
- )
92
- const matchedAuthorization =
93
- identityValidationExpression.test(authorization)
94
- const finalAuthorization = matchedAuthorization ? authorization : ''
95
-
96
- log.debug(
97
- `Retrieved ${identitySourceField} ${identitySourceType} "${finalAuthorization}"`,
98
- )
99
-
100
113
  if (authorizerOptions.payloadVersion === '1.0') {
101
114
  event = {
102
115
  ...event,
@@ -148,17 +161,10 @@ export default function createAuthScheme(authorizerOptions, provider, lambda) {
148
161
 
149
162
  // methodArn is the ARN of the function we are running we are authorizing access to (or not)
150
163
  // Account ID and API ID are not simulated
151
- if (authorizerOptions.type === 'request') {
152
- event = {
153
- ...event,
154
- type: 'REQUEST',
155
- }
156
- } else {
164
+ event = {
165
+ ...event,
157
166
  // This is safe since type: 'TOKEN' cannot have payload format 2.0
158
- event = {
159
- ...event,
160
- type: 'TOKEN',
161
- }
167
+ type: authorizerOptions.type === 'request' ? 'REQUEST' : 'TOKEN',
162
168
  }
163
169
 
164
170
  const lambdaFunction = lambda.get(authFunName)
@@ -296,5 +302,10 @@ export default function createAuthScheme(authorizerOptions, provider, lambda) {
296
302
  )
297
303
  }
298
304
 
305
+ if (authorizerOptions.resultTtlInSeconds === 0) {
306
+ identitySourceType = IDENTITY_SOURCE_TYPE_NONE
307
+ return finalizeAuthScheme()
308
+ }
309
+
299
310
  return finalizeAuthScheme()
300
311
  }
@@ -67,7 +67,7 @@ export default function createAuthScheme(jwtOptions) {
67
67
  }
68
68
 
69
69
  let scopes = null
70
- if (jwtOptions.scopes && jwtOptions.scopes.length) {
70
+ if (jwtOptions.scopes && jwtOptions.scopes.length > 0) {
71
71
  if (!scope) {
72
72
  log.notice(`JWT Token missing valid scope`)
73
73
 
@@ -20,10 +20,6 @@ function javaMatches(value) {
20
20
  return this.match(new RegExp(value, 'm'))
21
21
  }
22
22
 
23
- function javaReplaceAll(oldValue, newValue) {
24
- return this.replace(new RegExp(oldValue, 'gm'), newValue)
25
- }
26
-
27
23
  function javaReplaceFirst(oldValue, newValue) {
28
24
  return this.replace(new RegExp(oldValue, 'm'), newValue)
29
25
  }
@@ -74,7 +70,6 @@ const {
74
70
  equalsIgnoreCase,
75
71
  matches,
76
72
  regionMatches,
77
- replaceAll,
78
73
  replaceFirst,
79
74
  },
80
75
  } = String
@@ -85,7 +80,6 @@ export default function runInPollutedScope(runScope) {
85
80
  prototype.equalsIgnoreCase = javaEqualsIgnoreCase
86
81
  prototype.matches = javaMatches
87
82
  prototype.regionMatches = javaRegionMatches
88
- prototype.replaceAll = javaReplaceAll
89
83
  prototype.replaceFirst = javaReplaceFirst
90
84
 
91
85
  const result = runScope()
@@ -95,7 +89,6 @@ export default function runInPollutedScope(runScope) {
95
89
  prototype.equalsIgnoreCase = equalsIgnoreCase
96
90
  prototype.matches = matches
97
91
  prototype.regionMatches = regionMatches
98
- prototype.replaceAll = replaceAll
99
92
  prototype.replaceFirst = replaceFirst
100
93
 
101
94
  return result
@@ -14,7 +14,7 @@ const { assign, entries, fromEntries } = Object
14
14
 
15
15
  function escapeJavaScript(x) {
16
16
  if (typeof x === 'string') {
17
- return jsEscapeString(x).replace(/\\n/g, '\n') // See #26,
17
+ return jsEscapeString(x).replaceAll('\\n', '\n') // See #26,
18
18
  }
19
19
 
20
20
  if (isPlainObject(x)) {
@@ -136,7 +136,7 @@ export default class VelocityContext {
136
136
  Buffer.from(x.toString(), 'binary').toString('base64'),
137
137
  escapeJavaScript,
138
138
  parseJson: parse,
139
- urlDecode: (x) => decodeURIComponent(x.replace(/\+/g, ' ')),
139
+ urlDecode: (x) => decodeURIComponent(x.replaceAll('+', ' ')),
140
140
  urlEncode: encodeURI,
141
141
  },
142
142
  }
@@ -33,20 +33,25 @@ function renderVelocityString(velocityString, context) {
33
33
 
34
34
  // Haaaa Velocity... this language sure loves strings a lot
35
35
  switch (renderResult) {
36
- case 'undefined':
37
- return undefined // But we don't, we want JavaScript types
36
+ case 'undefined': {
37
+ return undefined
38
+ } // But we don't, we want JavaScript types
38
39
 
39
- case 'null':
40
+ case 'null': {
40
41
  return null
42
+ }
41
43
 
42
- case 'true':
44
+ case 'true': {
43
45
  return true
46
+ }
44
47
 
45
- case 'false':
48
+ case 'false': {
46
49
  return false
50
+ }
47
51
 
48
- default:
52
+ default: {
49
53
  return tryToParseJSON(renderResult)
54
+ }
50
55
  }
51
56
  }
52
57
 
@@ -78,21 +78,25 @@ export default class Schedule {
78
78
 
79
79
  switch (unit) {
80
80
  case 'minute':
81
- case 'minutes':
81
+ case 'minutes': {
82
82
  return `*/${number} * * * *`
83
+ }
83
84
 
84
85
  case 'hour':
85
- case 'hours':
86
+ case 'hours': {
86
87
  return `0 */${number} * * *`
88
+ }
87
89
 
88
90
  case 'day':
89
- case 'days':
91
+ case 'days': {
90
92
  return `0 0 */${number} * *`
93
+ }
91
94
 
92
- default:
95
+ default: {
93
96
  log.error(`scheduler: Invalid rate syntax '${rate}', will not schedule`)
94
97
 
95
98
  return null
99
+ }
96
100
  }
97
101
  }
98
102
 
@@ -16,7 +16,7 @@ export default class ScheduleEvent {
16
16
  source = 'aws.events'
17
17
 
18
18
  // format of aws displaying the time, e.g.: 2020-02-09T14:13:57Z
19
- time = new Date().toISOString().replace(/\.(.*)(?=Z)/g, '')
19
+ time = new Date().toISOString().replaceAll(/\.(.*)(?=Z)/g, '')
20
20
 
21
21
  version = '0'
22
22
 
@@ -19,8 +19,8 @@ export default class HttpServer {
19
19
 
20
20
  async #loadCerts(httpsProtocol) {
21
21
  const [cert, key] = await Promise.all([
22
- readFile(resolve(httpsProtocol, 'cert.pem'), 'utf-8'),
23
- readFile(resolve(httpsProtocol, 'key.pem'), 'utf-8'),
22
+ readFile(resolve(httpsProtocol, 'cert.pem'), 'utf8'),
23
+ readFile(resolve(httpsProtocol, 'key.pem'), 'utf8'),
24
24
  ])
25
25
 
26
26
  return {
@@ -14,7 +14,7 @@ export default class ConnectionsController {
14
14
  const clientExisted = this.#webSocketClients.send(
15
15
  connectionId,
16
16
  // payload is a Buffer
17
- payload.toString('utf-8'),
17
+ payload.toString('utf8'),
18
18
  )
19
19
 
20
20
  return clientExisted
package/src/index.js CHANGED
@@ -1,12 +1 @@
1
- // TODO remove with node.js v16.9+ support
2
- import 'object.hasown/auto'
3
-
4
- // install global fetch
5
- // TODO remove `node-fetch` module and use global built-in with node.js v18+ support
6
- if (globalThis.fetch === undefined) {
7
- const { default: fetch, Headers } = await import('node-fetch')
8
- globalThis.fetch = fetch
9
- globalThis.Headers = Headers
10
- }
11
-
12
1
  export { default } from './ServerlessOffline.js'
@@ -250,7 +250,7 @@ export default class LambdaFunction {
250
250
  entries(zip.files).map(async ([filename, jsZipObj]) => {
251
251
  const fileData = await jsZipObj.async('nodebuffer')
252
252
  if (filename.endsWith('/')) {
253
- return Promise.resolve()
253
+ return undefined
254
254
  }
255
255
  await ensureDir(join(this.#codeDir, dirname(filename)))
256
256
  return writeFile(join(this.#codeDir, filename), fileData, {
@@ -95,13 +95,7 @@ export default class DockerContainer {
95
95
  if (this.#layers.length > 0) {
96
96
  log.verbose(`Found layers, checking provider type`)
97
97
 
98
- if (this.#provider.name.toLowerCase() !== 'aws') {
99
- log.warning(
100
- `Provider ${
101
- this.#provider.name
102
- } is Unsupported. Layers are only supported on aws.`,
103
- )
104
- } else {
98
+ if (this.#provider.name.toLowerCase() === 'aws') {
105
99
  let layerDir = this.#dockerOptions.layersDir
106
100
 
107
101
  if (!layerDir) {
@@ -142,6 +136,12 @@ export default class DockerContainer {
142
136
  )
143
137
  }
144
138
  dockerArgs.push('-v', `${layerDir}:/opt:ro,delegated`)
139
+ } else {
140
+ log.warning(
141
+ `Provider ${
142
+ this.#provider.name
143
+ } is Unsupported. Layers are only supported on aws.`,
144
+ )
145
145
  }
146
146
  }
147
147
 
@@ -313,7 +313,7 @@ export default class DockerContainer {
313
313
  entries(zip.files).map(async ([filename, jsZipObj]) => {
314
314
  const fileData = await jsZipObj.async('nodebuffer')
315
315
  if (filename.endsWith(sep)) {
316
- return Promise.resolve()
316
+ return undefined
317
317
  }
318
318
  await ensureDir(join(layerDir, dirname(filename)))
319
319
  return writeFile(join(layerDir, filename), fileData, {
@@ -106,7 +106,7 @@ export default class GoRunner {
106
106
  // Get go env to run this locally
107
107
  if (!this.#goEnv) {
108
108
  const goEnvResponse = await execa('go', ['env'], {
109
- encoding: 'utf-8',
109
+ encoding: 'utf8',
110
110
  stdio: 'pipe',
111
111
  })
112
112
 
@@ -136,7 +136,7 @@ export default class GoRunner {
136
136
  }
137
137
 
138
138
  const { stdout, stderr } = await execa(`./tmp`, {
139
- encoding: 'utf-8',
139
+ encoding: 'utf8',
140
140
  env: {
141
141
  ...this.#env,
142
142
  ...this.#goEnv,
@@ -1,17 +1,15 @@
1
1
  import { spawn } from 'node:child_process'
2
2
  import { EOL, platform } from 'node:os'
3
- import { delimiter, dirname, join, relative, resolve } from 'node:path'
3
+ import { delimiter, join as pathJoin, relative } from 'node:path'
4
4
  import process, { cwd, nextTick } from 'node:process'
5
5
  import { createInterface } from 'node:readline'
6
- import { fileURLToPath } from 'node:url'
7
6
  import { log } from '@serverless/utils/log.js'
7
+ import { join } from 'desm'
8
8
  import { splitHandlerPathAndName } from '../../../utils/index.js'
9
9
 
10
10
  const { parse, stringify } = JSON
11
11
  const { assign, hasOwn } = Object
12
12
 
13
- const __dirname = dirname(fileURLToPath(import.meta.url))
14
-
15
13
  export default class PythonRunner {
16
14
  static #payloadIdentifier = '__offline_payload__'
17
15
 
@@ -32,7 +30,7 @@ export default class PythonRunner {
32
30
  const runtimeDir = platform() === 'win32' ? 'Scripts' : 'bin'
33
31
 
34
32
  process.env.PATH = [
35
- join(process.env.VIRTUAL_ENV, runtimeDir),
33
+ pathJoin(process.env.VIRTUAL_ENV, runtimeDir),
36
34
  delimiter,
37
35
  process.env.PATH,
38
36
  ].join('')
@@ -44,7 +42,7 @@ export default class PythonRunner {
44
42
  pythonExecutable,
45
43
  [
46
44
  '-u',
47
- resolve(__dirname, 'invoke.py'),
45
+ join(import.meta.url, 'invoke.py'),
48
46
  relative(cwd(), handlerPath),
49
47
  handlerName,
50
48
  ],
@@ -1,16 +1,14 @@
1
1
  import { EOL, platform } from 'node:os'
2
- import { dirname, relative, resolve } from 'node:path'
2
+ import { relative } from 'node:path'
3
3
  import { cwd } from 'node:process'
4
- import { fileURLToPath } from 'node:url'
5
4
  import { log } from '@serverless/utils/log.js'
5
+ import { join } from 'desm'
6
6
  import { execa } from 'execa'
7
7
  import { splitHandlerPathAndName } from '../../../utils/index.js'
8
8
 
9
9
  const { parse, stringify } = JSON
10
10
  const { hasOwn } = Object
11
11
 
12
- const __dirname = dirname(fileURLToPath(import.meta.url))
13
-
14
12
  export default class RubyRunner {
15
13
  static #payloadIdentifier = '__offline_payload__'
16
14
 
@@ -86,7 +84,7 @@ export default class RubyRunner {
86
84
  const { stderr, stdout } = await execa(
87
85
  runtime,
88
86
  [
89
- resolve(__dirname, 'invoke.rb'),
87
+ join(import.meta.url, 'invoke.rb'),
90
88
  relative(cwd(), this.#handlerPath),
91
89
  this.#handlerName,
92
90
  ],
@@ -1,9 +1,5 @@
1
- import { dirname, resolve } from 'node:path'
2
- import { fileURLToPath } from 'node:url'
3
1
  import { MessageChannel, Worker } from 'node:worker_threads'
4
-
5
- const __dirname = dirname(fileURLToPath(import.meta.url))
6
- const workerThreadHelperPath = resolve(__dirname, 'workerThreadHelper.js')
2
+ import { join } from 'desm'
7
3
 
8
4
  export default class WorkerThreadRunner {
9
5
  #workerThread = null
@@ -11,17 +7,20 @@ export default class WorkerThreadRunner {
11
7
  constructor(funOptions, env) {
12
8
  const { codeDir, functionKey, handler, servicePath, timeout } = funOptions
13
9
 
14
- this.#workerThread = new Worker(workerThreadHelperPath, {
15
- // don't pass process.env from the main process!
16
- env,
17
- workerData: {
18
- codeDir,
19
- functionKey,
20
- handler,
21
- servicePath,
22
- timeout,
10
+ this.#workerThread = new Worker(
11
+ join(import.meta.url, 'workerThreadHelper.js'),
12
+ {
13
+ // don't pass process.env from the main process!
14
+ env,
15
+ workerData: {
16
+ codeDir,
17
+ functionKey,
18
+ handler,
19
+ servicePath,
20
+ timeout,
21
+ },
23
22
  },
24
- })
23
+ )
25
24
  }
26
25
 
27
26
  // () => Promise<number>
@@ -12,7 +12,7 @@ export default class InvocationsController {
12
12
  const functionNames = this.#lambda.listFunctionNames()
13
13
  if (functionNames.length === 0 || !functionNames.includes(functionName)) {
14
14
  log.error(
15
- `Attempt to invoke function '${functionName}' failed. Function does not exists.`,
15
+ `Attempt to invoke function '${functionName}' failed. Function does not exist.`,
16
16
  )
17
17
  // Conforms to the actual response from AWS Lambda when invoking a non-existent
18
18
  // function. Details on the error are provided in the Payload.Message key
@@ -25,11 +25,11 @@ export default function invocationsRoute(lambda, options) {
25
25
  // check client context header was set
26
26
  if (clientContextHeader) {
27
27
  const clientContextBuffer = Buffer.from(clientContextHeader, 'base64')
28
- clientContext = parse(clientContextBuffer.toString('utf-8'))
28
+ clientContext = parse(clientContextBuffer.toString('utf8'))
29
29
  }
30
30
 
31
31
  // check if payload was set, if not, default event is an empty object
32
- const event = payload.length > 0 ? parse(payload.toString('utf-8')) : {}
32
+ const event = payload.length > 0 ? parse(payload.toString('utf8')) : {}
33
33
 
34
34
  const invokeResults = await invocationsController.invoke(
35
35
  functionName,
@@ -13,7 +13,7 @@ export default function invokeRoute(lambda, options) {
13
13
  payload,
14
14
  } = request
15
15
 
16
- const event = parse(payload.toString('utf-8'))
16
+ const event = parse(payload.toString('utf8'))
17
17
 
18
18
  return invokeAsyncController.invokeAsync(functionName, event)
19
19
  },
@@ -5,7 +5,7 @@ export default async function checkGoVersion() {
5
5
 
6
6
  try {
7
7
  const { stdout } = await execa('go', ['version'])
8
- if (stdout.match(/go1.\d+/g)) {
8
+ if (/go1.\d+/g.test(stdout)) {
9
9
  goVersion = '1.x'
10
10
  }
11
11
  } catch {
@@ -15,7 +15,7 @@ export default function generateHapiPath(path, options, serverless) {
15
15
  hapiPath = hapiPath.slice(0, -1)
16
16
  }
17
17
 
18
- hapiPath = hapiPath.replace(/\+}/g, '*}')
18
+ hapiPath = hapiPath.replaceAll('+}', '*}')
19
19
 
20
20
  return hapiPath
21
21
  }
@@ -8,7 +8,7 @@ export default function splitHandlerPathAndName(handler) {
8
8
  // filename: source
9
9
  // path: ./src/somefolder/source
10
10
  // name: LambdaFunctions::Handler.process
11
- if (handler.match(/::/)) {
11
+ if (/::/.test(handler)) {
12
12
  const prepathDelimiter = handler.lastIndexOf('/')
13
13
  const prepath = handler.substr(0, prepathDelimiter + 1) // include '/' for path
14
14
  const postpath = handler.substr(prepathDelimiter + 1)