serverless-offline 12.0.4 → 13.1.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 -2
- package/package.json +26 -27
- package/src/ServerlessOffline.js +7 -5
- package/src/config/supportedRuntimes.js +4 -5
- package/src/events/alb/Alb.js +0 -6
- package/src/events/alb/HttpServer.js +3 -7
- package/src/events/authMatchPolicyResource.js +1 -1
- package/src/events/http/Endpoint.js +1 -1
- package/src/events/http/HttpServer.js +14 -10
- package/src/events/http/createAuthScheme.js +49 -37
- package/src/events/http/javaHelpers.js +0 -7
- package/src/events/http/lambda-events/VelocityContext.js +2 -2
- package/src/events/schedule/ScheduleEvent.js +1 -1
- package/src/index.js +0 -11
- package/src/lambda/LambdaFunction.js +1 -1
- package/src/lambda/handler-runner/docker-runner/DockerContainer.js +1 -1
- package/src/lambda/routes/invocations/InvocationsController.js +1 -1
- package/src/utils/generateHapiPath.js +1 -1
package/README.md
CHANGED
|
@@ -524,9 +524,16 @@ only enabled with the `--ignoreJWTSignature` flag. Make sure to only set this fl
|
|
|
524
524
|
|
|
525
525
|
### Serverless plugin authorizers
|
|
526
526
|
|
|
527
|
-
If your authentication needs are custom and not satisfied by the existing capabilities of the Serverless offline project, you can inject your own authentication strategy. To inject a custom strategy for Lambda invocation, you define a custom variable under `
|
|
527
|
+
If your authentication needs are custom and not satisfied by the existing capabilities of the Serverless offline project, you can inject your own authentication strategy. To inject a custom strategy for Lambda invocation, you define a custom variable under `offline` called `customAuthenticationProvider` in the serverless.yml file. The value of the custom variable will be used to `require(your customAuthenticationProvider value)` where the location is expected to return a function with the following signature.
|
|
528
|
+
|
|
529
|
+
```yaml
|
|
530
|
+
offline:
|
|
531
|
+
customAuthenticationProvider: ./path/to/custom-authentication-provider
|
|
532
|
+
```
|
|
528
533
|
|
|
529
534
|
```js
|
|
535
|
+
// ./path/to/customer-authentication-provider.js
|
|
536
|
+
|
|
530
537
|
module.exports = function (endpoint, functionKey, method, path) {
|
|
531
538
|
return {
|
|
532
539
|
getAuthenticateFunction() {
|
|
@@ -543,7 +550,7 @@ module.exports = function (endpoint, functionKey, method, path) {
|
|
|
543
550
|
}
|
|
544
551
|
```
|
|
545
552
|
|
|
546
|
-
A working example of injecting a custom authorization provider can be found in the projects integration tests under the folder `custom-authentication
|
|
553
|
+
A working example of injecting a custom authorization provider can be found in the projects integration tests under the folder [`custom-authentication`](./tests/integration/custom-authentication).
|
|
547
554
|
|
|
548
555
|
## Custom headers
|
|
549
556
|
|
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": "
|
|
4
|
+
"version": "13.1.0",
|
|
5
5
|
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"prepare-release": "standard-version && prettier --write CHANGELOG.md",
|
|
19
19
|
"prepublishOnly": "npm run lint",
|
|
20
20
|
"prettier": "prettier --check .",
|
|
21
|
+
"prettier:fix": "prettier --write .",
|
|
21
22
|
"prettier-check": "prettier -c --ignore-path .gitignore \"**/*.{css,html,js,json,md,yaml,yml}\"",
|
|
22
23
|
"prettier-check:updated": "pipe-git-updated --ext=css --ext=html --ext=js --ext=json --ext=md --ext=yaml --ext=yml -- prettier -c",
|
|
23
24
|
"prettify": "prettier --write --ignore-path .gitignore \"**/*.{css,html,js,json,md,yaml,yml}\"",
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
],
|
|
52
53
|
"author": "David Hérault <dherault@gmail.com> (https://github.com/dherault)",
|
|
53
54
|
"engines": {
|
|
54
|
-
"node": ">=
|
|
55
|
+
"node": ">=18.12.0"
|
|
55
56
|
},
|
|
56
57
|
"standard-version": {
|
|
57
58
|
"skip": {
|
|
@@ -78,49 +79,47 @@
|
|
|
78
79
|
]
|
|
79
80
|
},
|
|
80
81
|
"dependencies": {
|
|
81
|
-
"@aws-sdk/client-lambda": "^3.
|
|
82
|
-
"@hapi/boom": "^10.0.
|
|
83
|
-
"@hapi/h2o2": "^10.0.
|
|
84
|
-
"@hapi/hapi": "^21.
|
|
85
|
-
"@serverless/utils": "^6.
|
|
82
|
+
"@aws-sdk/client-lambda": "^3.418.0",
|
|
83
|
+
"@hapi/boom": "^10.0.1",
|
|
84
|
+
"@hapi/h2o2": "^10.0.4",
|
|
85
|
+
"@hapi/hapi": "^21.3.2",
|
|
86
|
+
"@serverless/utils": "^6.15.0",
|
|
86
87
|
"array-unflat-js": "^0.1.3",
|
|
87
|
-
"boxen": "^7.
|
|
88
|
-
"chalk": "^5.
|
|
88
|
+
"boxen": "^7.1.1",
|
|
89
|
+
"chalk": "^5.3.0",
|
|
89
90
|
"desm": "^1.3.0",
|
|
90
|
-
"execa": "^
|
|
91
|
-
"fs-extra": "^11.1.
|
|
92
|
-
"is-wsl": "^
|
|
91
|
+
"execa": "^8.0.1",
|
|
92
|
+
"fs-extra": "^11.1.1",
|
|
93
|
+
"is-wsl": "^3.0.0",
|
|
93
94
|
"java-invoke-local": "0.0.6",
|
|
94
|
-
"jose": "^4.
|
|
95
|
+
"jose": "^4.14.6",
|
|
95
96
|
"js-string-escape": "^1.0.1",
|
|
96
97
|
"jsonpath-plus": "^7.2.0",
|
|
97
98
|
"jsonschema": "^1.4.1",
|
|
98
99
|
"jszip": "^3.10.1",
|
|
99
100
|
"luxon": "^3.2.0",
|
|
100
|
-
"node-
|
|
101
|
-
"node-schedule": "^2.1.0",
|
|
102
|
-
"object.hasown": "^1.1.2",
|
|
101
|
+
"node-schedule": "^2.1.1",
|
|
103
102
|
"p-memoize": "^7.1.1",
|
|
104
|
-
"p-retry": "^
|
|
103
|
+
"p-retry": "^6.0.0",
|
|
105
104
|
"velocityjs": "^2.0.6",
|
|
106
|
-
"ws": "^8.
|
|
105
|
+
"ws": "^8.14.2"
|
|
107
106
|
},
|
|
108
107
|
"devDependencies": {
|
|
109
108
|
"@istanbuljs/esm-loader-hook": "^0.2.0",
|
|
110
|
-
"archiver": "^
|
|
111
|
-
"eslint": "^8.
|
|
109
|
+
"archiver": "^6.0.1",
|
|
110
|
+
"eslint": "^8.50.0",
|
|
112
111
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
113
|
-
"eslint-config-prettier": "^
|
|
114
|
-
"eslint-plugin-import": "^2.
|
|
115
|
-
"eslint-plugin-prettier": "^
|
|
116
|
-
"eslint-plugin-unicorn": "^
|
|
112
|
+
"eslint-config-prettier": "^9.0.0",
|
|
113
|
+
"eslint-plugin-import": "^2.28.1",
|
|
114
|
+
"eslint-plugin-prettier": "^5.0.0",
|
|
115
|
+
"eslint-plugin-unicorn": "^48.0.1",
|
|
117
116
|
"git-list-updated": "^1.2.1",
|
|
118
117
|
"husky": "^8.0.3",
|
|
119
|
-
"lint-staged": "^
|
|
118
|
+
"lint-staged": "^14.0.1",
|
|
120
119
|
"mocha": "^10.2.0",
|
|
121
120
|
"nyc": "^15.1.0",
|
|
122
|
-
"prettier": "^
|
|
123
|
-
"serverless": "^3.
|
|
121
|
+
"prettier": "^3.0.3",
|
|
122
|
+
"serverless": "^3.35.2",
|
|
124
123
|
"standard-version": "^9.5.0"
|
|
125
124
|
},
|
|
126
125
|
"peerDependencies": {
|
package/src/ServerlessOffline.js
CHANGED
|
@@ -107,8 +107,10 @@ export default class ServerlessOffline {
|
|
|
107
107
|
const eventModules = []
|
|
108
108
|
|
|
109
109
|
if (this.#lambda) {
|
|
110
|
-
eventModules.push(
|
|
111
|
-
|
|
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
|
-
.
|
|
264
|
+
.replaceAll(' ', '')
|
|
263
265
|
.split(',')
|
|
264
266
|
this.#options.corsAllowOrigin = this.#options.corsAllowOrigin
|
|
265
|
-
.
|
|
267
|
+
.replaceAll(' ', '')
|
|
266
268
|
.split(',')
|
|
267
269
|
this.#options.corsExposedHeaders = this.#options.corsExposedHeaders
|
|
268
|
-
.
|
|
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
|
package/src/events/alb/Alb.js
CHANGED
|
@@ -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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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.
|
|
64
|
+
parsedPolicyResource.path.replaceAll('*', '.*').replaceAll('?', '.'),
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
return regExp.test(parsedResource.path)
|
|
@@ -17,7 +17,7 @@ const defaultResponseTemplate = readFileSync(
|
|
|
17
17
|
|
|
18
18
|
function getResponseContentType(fep) {
|
|
19
19
|
if (fep.response && fep.response.headers['Content-Type']) {
|
|
20
|
-
return fep.response.headers['Content-Type'].
|
|
20
|
+
return fep.response.headers['Content-Type'].replaceAll(/'/gm, '')
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
return 'application/json'
|
|
@@ -164,13 +164,9 @@ export default class HttpServer {
|
|
|
164
164
|
if (request.method === 'options') {
|
|
165
165
|
response.statusCode = 200
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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,9 +319,7 @@ 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
325
|
payloadVersion: endpoint.isHttpApi
|
|
@@ -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}`
|
|
@@ -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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
...event,
|
|
160
|
-
type: 'TOKEN',
|
|
161
|
-
}
|
|
167
|
+
type: authorizerOptions.type === 'request' ? 'REQUEST' : 'TOKEN',
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
const lambdaFunction = lambda.get(authFunName)
|
|
@@ -270,7 +276,8 @@ export default function createAuthScheme(authorizerOptions, provider, lambda) {
|
|
|
270
276
|
authorizerOptions.type !== 'request' ||
|
|
271
277
|
authorizerOptions.identitySource
|
|
272
278
|
) {
|
|
273
|
-
|
|
279
|
+
// Only validate the first of N possible headers.
|
|
280
|
+
const headerRegExp = /^(method.|\$)request.header.((?:\w+-?)+\w+).*$/
|
|
274
281
|
const queryStringRegExp =
|
|
275
282
|
/^(method.|\$)request.querystring.((?:\w+-?)+\w+)$/
|
|
276
283
|
|
|
@@ -296,5 +303,10 @@ export default function createAuthScheme(authorizerOptions, provider, lambda) {
|
|
|
296
303
|
)
|
|
297
304
|
}
|
|
298
305
|
|
|
306
|
+
if (authorizerOptions.resultTtlInSeconds === 0) {
|
|
307
|
+
identitySourceType = IDENTITY_SOURCE_TYPE_NONE
|
|
308
|
+
return finalizeAuthScheme()
|
|
309
|
+
}
|
|
310
|
+
|
|
299
311
|
return finalizeAuthScheme()
|
|
300
312
|
}
|
|
@@ -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).
|
|
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.
|
|
139
|
+
urlDecode: (x) => decodeURIComponent(x.replaceAll('+', ' ')),
|
|
140
140
|
urlEncode: encodeURI,
|
|
141
141
|
},
|
|
142
142
|
}
|
|
@@ -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().
|
|
19
|
+
time = new Date().toISOString().replaceAll(/\.(.*)(?=Z)/g, '')
|
|
20
20
|
|
|
21
21
|
version = '0'
|
|
22
22
|
|
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
|
|
253
|
+
return undefined
|
|
254
254
|
}
|
|
255
255
|
await ensureDir(join(this.#codeDir, dirname(filename)))
|
|
256
256
|
return writeFile(join(this.#codeDir, filename), fileData, {
|
|
@@ -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
|
|
316
|
+
return undefined
|
|
317
317
|
}
|
|
318
318
|
await ensureDir(join(layerDir, dirname(filename)))
|
|
319
319
|
return writeFile(join(layerDir, filename), fileData, {
|
|
@@ -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
|
|
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
|