serverless-offline 14.5.0 → 14.7.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/LICENSE +1 -1
- package/README.md +106 -104
- package/package.json +25 -44
- package/src/config/commandOptions.js +5 -0
- package/src/config/defaultOptions.js +1 -0
- package/src/config/supportedRuntimes.js +7 -1
- package/src/events/alb/HttpServer.js +43 -40
- package/src/lambda/LambdaFunction.js +5 -5
- package/src/lambda/handler-runner/HandlerRunner.js +5 -7
- package/src/lambda/handler-runner/docker-runner/DockerContainer.js +6 -7
- package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +332 -49
- package/src/lambda/handler-runner/ruby-runner/invoke.rb +46 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-offline",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.7.0",
|
|
4
4
|
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"code-quality": "npm run prettier && npm run lint",
|
|
14
14
|
"lint": "eslint .",
|
|
15
15
|
"lint:fix": "eslint . --fix",
|
|
16
|
-
"list-contributors": "echo 'clone https://github.com/mgechev/github-contributors-list.git first, then run npm install' && cd ../github-contributors-list && node bin/githubcontrib --owner dherault --repo serverless-offline --sortBy contributions --
|
|
17
|
-
"prepare-release": "commit-and-tag-version && prettier --write CHANGELOG.md",
|
|
16
|
+
"list-contributors": "echo 'clone https://github.com/mgechev/github-contributors-list.git first, then run npm install' && cd ../github-contributors-list && node bin/githubcontrib --owner dherault --repo serverless-offline --sortBy contributions --sortOrder desc > contributors.md",
|
|
18
17
|
"prepublishOnly": "npm run lint",
|
|
18
|
+
"changelog": "auto-changelog",
|
|
19
19
|
"prettier": "prettier --check .",
|
|
20
20
|
"prettier:fix": "prettier --write .",
|
|
21
21
|
"test": "mocha --require ./tests/mochaHooks.cjs",
|
|
@@ -51,72 +51,53 @@
|
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": ">=20.0.0"
|
|
53
53
|
},
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"types": [
|
|
60
|
-
{
|
|
61
|
-
"type": "feat",
|
|
62
|
-
"section": "Features"
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
"type": "fix",
|
|
66
|
-
"section": "Bug Fixes"
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"type": "perf",
|
|
70
|
-
"section": "Performance Improvements"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"type": "refactor",
|
|
74
|
-
"section": "Maintenance Improvements"
|
|
75
|
-
}
|
|
76
|
-
]
|
|
54
|
+
"auto-changelog": {
|
|
55
|
+
"output": "CHANGELOG.md",
|
|
56
|
+
"unreleased": false,
|
|
57
|
+
"commitLimit": false,
|
|
58
|
+
"hideCredit": true
|
|
77
59
|
},
|
|
78
60
|
"dependencies": {
|
|
79
|
-
"@aws-sdk/client-lambda": "^3.
|
|
61
|
+
"@aws-sdk/client-lambda": "^3.1052.0",
|
|
80
62
|
"@hapi/boom": "^10.0.1",
|
|
81
63
|
"@hapi/h2o2": "^10.0.4",
|
|
82
|
-
"@hapi/hapi": "^21.
|
|
64
|
+
"@hapi/hapi": "^21.4.9",
|
|
83
65
|
"array-unflat-js": "^0.1.3",
|
|
84
66
|
"boxen": "^7.1.1",
|
|
85
|
-
"chalk": "^5.
|
|
67
|
+
"chalk": "^5.6.2",
|
|
86
68
|
"desm": "^1.3.1",
|
|
87
69
|
"execa": "^8.0.1",
|
|
88
|
-
"
|
|
89
|
-
"is-wsl": "^3.1.0",
|
|
70
|
+
"is-wsl": "^3.1.1",
|
|
90
71
|
"java-invoke-local": "0.0.6",
|
|
91
72
|
"jose": "^5.7.0",
|
|
92
73
|
"js-string-escape": "^1.0.1",
|
|
93
|
-
"jsonpath-plus": "^10.
|
|
94
|
-
"jsonschema": "^1.
|
|
74
|
+
"jsonpath-plus": "^10.4.0",
|
|
75
|
+
"jsonschema": "^1.5.0",
|
|
95
76
|
"jszip": "^3.10.1",
|
|
96
|
-
"luxon": "^3.
|
|
77
|
+
"luxon": "^3.7.2",
|
|
97
78
|
"nock": "^13.5.6",
|
|
98
79
|
"node-fetch": "^3.3.2",
|
|
99
80
|
"node-schedule": "^2.1.1",
|
|
100
81
|
"p-memoize": "^7.1.1",
|
|
101
82
|
"tree-kill": "^1.2.2",
|
|
102
|
-
"tsx": "^4.
|
|
103
|
-
"velocityjs": "^2.
|
|
104
|
-
"ws": "^8.
|
|
83
|
+
"tsx": "^4.22.3",
|
|
84
|
+
"velocityjs": "^2.1.6",
|
|
85
|
+
"ws": "^8.21.0"
|
|
105
86
|
},
|
|
106
87
|
"devDependencies": {
|
|
107
|
-
"@istanbuljs/esm-loader-hook": "^0.
|
|
88
|
+
"@istanbuljs/esm-loader-hook": "^0.3.0",
|
|
108
89
|
"archiver": "^7.0.1",
|
|
109
|
-
"
|
|
90
|
+
"auto-changelog": "^2.5.1",
|
|
110
91
|
"eslint": "^8.57.0",
|
|
111
92
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
112
93
|
"eslint-config-prettier": "^9.1.0",
|
|
113
|
-
"eslint-plugin-import": "^2.
|
|
114
|
-
"eslint-plugin-prettier": "^5.
|
|
94
|
+
"eslint-plugin-import": "^2.32.0",
|
|
95
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
115
96
|
"eslint-plugin-unicorn": "^54.0.0",
|
|
116
|
-
"mocha": "^
|
|
97
|
+
"mocha": "^11.7.6",
|
|
117
98
|
"nyc": "^17.0.0",
|
|
118
|
-
"prettier": "^3.
|
|
119
|
-
"serverless": "^4.
|
|
99
|
+
"prettier": "^3.8.3",
|
|
100
|
+
"serverless": "^4.36.1"
|
|
120
101
|
},
|
|
121
102
|
"peerDependencies": {
|
|
122
103
|
"serverless": "^4.0.0"
|
|
@@ -116,6 +116,11 @@ export default {
|
|
|
116
116
|
type: "boolean",
|
|
117
117
|
usage: "Turns on loading of your HTTP proxy settings from serverless.yml.",
|
|
118
118
|
},
|
|
119
|
+
rubyWatchDirs: {
|
|
120
|
+
type: "string",
|
|
121
|
+
usage:
|
|
122
|
+
"Comma-separated list of directories to watch for Ruby (.rb) file changes. When set, the persistent Ruby process is automatically restarted on change for hot reload.",
|
|
123
|
+
},
|
|
119
124
|
terminateIdleLambdaTime: {
|
|
120
125
|
type: "string",
|
|
121
126
|
usage:
|
|
@@ -24,6 +24,7 @@ export const supportedRuntimesArchitecture = {
|
|
|
24
24
|
"ruby2.7": [ARM64, X86_64],
|
|
25
25
|
"ruby3.2": [ARM64, X86_64],
|
|
26
26
|
"ruby3.3": [ARM64, X86_64],
|
|
27
|
+
"ruby3.4": [ARM64, X86_64],
|
|
27
28
|
java8: [X86_64],
|
|
28
29
|
"java8.al2": [ARM64, X86_64],
|
|
29
30
|
java11: [ARM64, X86_64],
|
|
@@ -68,7 +69,12 @@ export const supportedPython = new Set([
|
|
|
68
69
|
])
|
|
69
70
|
|
|
70
71
|
// RUBY
|
|
71
|
-
export const supportedRuby = new Set([
|
|
72
|
+
export const supportedRuby = new Set([
|
|
73
|
+
"ruby2.7",
|
|
74
|
+
"ruby3.2",
|
|
75
|
+
"ruby3.3",
|
|
76
|
+
"ruby3.4",
|
|
77
|
+
])
|
|
72
78
|
|
|
73
79
|
export const supportedRuntimes = new Set([
|
|
74
80
|
// ...supportedDotnetcore,
|
|
@@ -294,10 +294,11 @@ export default class HttpServer {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
createRoutes(functionKey, albEvent) {
|
|
297
|
-
let
|
|
297
|
+
let methods = ["ANY"]
|
|
298
298
|
if ((albEvent.conditions.method || []).length > 0) {
|
|
299
|
-
|
|
299
|
+
methods = albEvent.conditions.method.map((m) => m.toUpperCase())
|
|
300
300
|
}
|
|
301
|
+
methods = methods.includes("ANY") ? ["ANY"] : methods
|
|
301
302
|
|
|
302
303
|
const path = albEvent.conditions.path[0]
|
|
303
304
|
const hapiPath = generateAlbHapiPath(path, this.#options, this.#serverless)
|
|
@@ -306,51 +307,53 @@ export default class HttpServer {
|
|
|
306
307
|
const { host, albPort, httpsProtocol } = this.#options
|
|
307
308
|
const server = `${httpsProtocol ? "https" : "http"}://${host}:${albPort}`
|
|
308
309
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
310
|
+
methods.forEach((method) => {
|
|
311
|
+
this.#terminalInfo.push({
|
|
312
|
+
invokePath: `/2015-03-31/functions/${functionKey}/invocations`,
|
|
313
|
+
method,
|
|
314
|
+
path: hapiPath,
|
|
315
|
+
server,
|
|
316
|
+
stage: this.#options.noPrependStageInUrl ? null : stage,
|
|
317
|
+
})
|
|
316
318
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
const hapiMethod = method === "ANY" ? "*" : method
|
|
320
|
+
const hapiOptions = {
|
|
321
|
+
response: {
|
|
322
|
+
emptyStatusCode: 200,
|
|
323
|
+
},
|
|
324
|
+
}
|
|
323
325
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
326
|
+
// skip HEAD routes as hapi will fail with 'Method name not allowed: HEAD ...'
|
|
327
|
+
// for more details, check https://github.com/dherault/serverless-offline/issues/204
|
|
328
|
+
if (hapiMethod === "HEAD") {
|
|
329
|
+
log.notice(
|
|
330
|
+
"HEAD method event detected. Skipping HAPI server route mapping",
|
|
331
|
+
)
|
|
330
332
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
+
return
|
|
334
|
+
}
|
|
333
335
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
if (hapiMethod !== "HEAD" && hapiMethod !== "GET") {
|
|
337
|
+
// maxBytes: Increase request size from 1MB default limit to 10MB.
|
|
338
|
+
// Cf AWS API GW payload limits.
|
|
339
|
+
hapiOptions.payload = {
|
|
340
|
+
maxBytes: 1024 * 1024 * 10,
|
|
341
|
+
parse: false,
|
|
342
|
+
}
|
|
340
343
|
}
|
|
341
|
-
}
|
|
342
344
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
345
|
+
const hapiHandler = this.#createHapiHandler({
|
|
346
|
+
functionKey,
|
|
347
|
+
method,
|
|
348
|
+
stage,
|
|
349
|
+
})
|
|
348
350
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
this.#server.route({
|
|
352
|
+
handler: hapiHandler,
|
|
353
|
+
method: hapiMethod,
|
|
354
|
+
options: hapiOptions,
|
|
355
|
+
path: hapiPath,
|
|
356
|
+
})
|
|
354
357
|
})
|
|
355
358
|
}
|
|
356
359
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import crypto from "node:crypto"
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises"
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises"
|
|
3
3
|
import { dirname, join, resolve } from "node:path"
|
|
4
4
|
import process from "node:process"
|
|
5
5
|
import { performance } from "node:perf_hooks"
|
|
6
6
|
import { setTimeout } from "node:timers/promises"
|
|
7
|
-
import { emptyDir, ensureDir, remove } from "fs-extra"
|
|
8
7
|
import jszip from "jszip"
|
|
9
8
|
import { log } from "../utils/log.js"
|
|
10
9
|
import renderIntrinsicFunction from "../utils/renderIntrinsicFunction.js"
|
|
@@ -226,7 +225,7 @@ export default class LambdaFunction {
|
|
|
226
225
|
// TODO console.log('lambda cleanup')
|
|
227
226
|
await this.#handlerRunner.cleanup()
|
|
228
227
|
if (this.#lambdaDir) {
|
|
229
|
-
await
|
|
228
|
+
await rm(this.#lambdaDir, { force: true, recursive: true })
|
|
230
229
|
}
|
|
231
230
|
}
|
|
232
231
|
|
|
@@ -246,7 +245,8 @@ export default class LambdaFunction {
|
|
|
246
245
|
return
|
|
247
246
|
}
|
|
248
247
|
|
|
249
|
-
await
|
|
248
|
+
await rm(this.#codeDir, { force: true, recursive: true })
|
|
249
|
+
await mkdir(this.#codeDir, { recursive: true })
|
|
250
250
|
|
|
251
251
|
const data = await readFile(this.#artifact)
|
|
252
252
|
const zip = await jszip.loadAsync(data)
|
|
@@ -257,7 +257,7 @@ export default class LambdaFunction {
|
|
|
257
257
|
if (filename.endsWith("/")) {
|
|
258
258
|
return undefined
|
|
259
259
|
}
|
|
260
|
-
await
|
|
260
|
+
await mkdir(join(this.#codeDir, dirname(filename)), { recursive: true })
|
|
261
261
|
return writeFile(join(this.#codeDir, filename), fileData, {
|
|
262
262
|
mode: jsZipObj.unixPermissions,
|
|
263
263
|
})
|
|
@@ -52,16 +52,14 @@ export default class HandlerRunner {
|
|
|
52
52
|
|
|
53
53
|
if (supportedNodejs.has(runtime)) {
|
|
54
54
|
if (useInProcess) {
|
|
55
|
-
const { default: InProcessRunner } =
|
|
56
|
-
"./in-process-runner/index.js"
|
|
57
|
-
)
|
|
55
|
+
const { default: InProcessRunner } =
|
|
56
|
+
await import("./in-process-runner/index.js")
|
|
58
57
|
|
|
59
58
|
return new InProcessRunner(this.#funOptions, this.#env)
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
const { default: WorkerThreadRunner } =
|
|
63
|
-
"./worker-thread-runner/index.js"
|
|
64
|
-
)
|
|
61
|
+
const { default: WorkerThreadRunner } =
|
|
62
|
+
await import("./worker-thread-runner/index.js")
|
|
65
63
|
|
|
66
64
|
return new WorkerThreadRunner(this.#funOptions, this.#env)
|
|
67
65
|
}
|
|
@@ -81,7 +79,7 @@ export default class HandlerRunner {
|
|
|
81
79
|
if (supportedRuby.has(runtime)) {
|
|
82
80
|
const { default: RubyRunner } = await import("./ruby-runner/index.js")
|
|
83
81
|
|
|
84
|
-
return new RubyRunner(this.#funOptions, this.#env)
|
|
82
|
+
return new RubyRunner(this.#funOptions, this.#env, this.#options)
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
if (supportedJava.has(runtime)) {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto"
|
|
2
|
-
import { createWriteStream } from "node:fs"
|
|
3
|
-
import { readFile, unlink, writeFile } from "node:fs/promises"
|
|
2
|
+
import { createWriteStream, existsSync } from "node:fs"
|
|
3
|
+
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises"
|
|
4
4
|
import { platform } from "node:os"
|
|
5
5
|
import { dirname, join, sep } from "node:path"
|
|
6
6
|
import { LambdaClient, GetLayerVersionCommand } from "@aws-sdk/client-lambda"
|
|
7
7
|
import { execa } from "execa"
|
|
8
|
-
import { ensureDir, pathExists } from "fs-extra"
|
|
9
8
|
import isWsl from "is-wsl"
|
|
10
9
|
import jszip from "jszip"
|
|
11
10
|
import { log, progress } from "../../../utils/log.js"
|
|
@@ -103,7 +102,7 @@ export default class DockerContainer {
|
|
|
103
102
|
|
|
104
103
|
layerDir = join(layerDir, this.#getLayersSha256())
|
|
105
104
|
|
|
106
|
-
if (
|
|
105
|
+
if (existsSync(layerDir)) {
|
|
107
106
|
log.verbose(
|
|
108
107
|
`Layers already exist for this function. Skipping download.`,
|
|
109
108
|
)
|
|
@@ -143,7 +142,7 @@ export default class DockerContainer {
|
|
|
143
142
|
} else {
|
|
144
143
|
log.debug("Looking for bootstrap file")
|
|
145
144
|
const bootstrapDir = join(this.#servicePath, "bootstrap")
|
|
146
|
-
if (
|
|
145
|
+
if (existsSync(bootstrapDir)) {
|
|
147
146
|
log.debug(`Found bootstrap file at ${bootstrapDir}`)
|
|
148
147
|
dockerArgs.push(
|
|
149
148
|
"-v",
|
|
@@ -262,7 +261,7 @@ export default class DockerContainer {
|
|
|
262
261
|
const { CodeSize: layerSize, Location: layerUrl } = layer.Content
|
|
263
262
|
// const layerSha = layer.Content.CodeSha256
|
|
264
263
|
|
|
265
|
-
await
|
|
264
|
+
await mkdir(layerDir, { recursive: true })
|
|
266
265
|
|
|
267
266
|
log.verbose(
|
|
268
267
|
`Retrieving "${layerName}": Downloading ${this.#formatBytes(
|
|
@@ -311,7 +310,7 @@ export default class DockerContainer {
|
|
|
311
310
|
if (filename.endsWith(sep)) {
|
|
312
311
|
return undefined
|
|
313
312
|
}
|
|
314
|
-
await
|
|
313
|
+
await mkdir(join(layerDir, dirname(filename)), { recursive: true })
|
|
315
314
|
return writeFile(join(layerDir, filename), fileData, {
|
|
316
315
|
mode: zip.files[filename].unixPermissions,
|
|
317
316
|
})
|