serverless-offline 13.3.3 → 13.4.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.3.3",
4
+ "version": "13.4.0",
5
5
  "description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
6
6
  "license": "MIT",
7
7
  "exports": {
@@ -20,8 +20,10 @@
20
20
  "prettier:fix": "prettier --write .",
21
21
  "test": "mocha --require ./tests/mochaHooks.cjs",
22
22
  "test:cov": "NODE_OPTIONS='--experimental-loader @istanbuljs/esm-loader-hook' nyc --reporter=html npm test",
23
- "test:node": "TEST=unit mocha --require ./tests/mochaHooks.cjs",
24
- "test:unit": "TEST=unit mocha --require ./tests/mochaHooks.cjs"
23
+ "test:node": "TEST=node mocha --require ./tests/mochaHooks.cjs",
24
+ "test:unit": "TEST=unit mocha --require ./tests/mochaHooks.cjs",
25
+ "test:e2e": "TEST=e2e mocha --require ./tests/mochaHooks.cjs",
26
+ "test:docker": "TEST=docker mocha --require ./tests/mochaHooks.cjs"
25
27
  },
26
28
  "repository": {
27
29
  "type": "git",
@@ -74,10 +76,10 @@
74
76
  ]
75
77
  },
76
78
  "dependencies": {
77
- "@aws-sdk/client-lambda": "^3.496.0",
79
+ "@aws-sdk/client-lambda": "^3.509.0",
78
80
  "@hapi/boom": "^10.0.1",
79
81
  "@hapi/h2o2": "^10.0.4",
80
- "@hapi/hapi": "^21.3.2",
82
+ "@hapi/hapi": "^21.3.3",
81
83
  "@serverless/utils": "^6.15.0",
82
84
  "array-unflat-js": "^0.1.3",
83
85
  "boxen": "^7.1.1",
@@ -87,7 +89,7 @@
87
89
  "fs-extra": "^11.2.0",
88
90
  "is-wsl": "^3.1.0",
89
91
  "java-invoke-local": "0.0.6",
90
- "jose": "^5.2.0",
92
+ "jose": "^5.2.1",
91
93
  "js-string-escape": "^1.0.1",
92
94
  "jsonpath-plus": "^8.0.0",
93
95
  "jsonschema": "^1.4.1",
@@ -95,7 +97,6 @@
95
97
  "luxon": "^3.4.4",
96
98
  "node-schedule": "^2.1.1",
97
99
  "p-memoize": "^7.1.1",
98
- "p-retry": "^6.2.0",
99
100
  "velocityjs": "^2.0.6",
100
101
  "ws": "^8.16.0"
101
102
  },
@@ -108,10 +109,10 @@
108
109
  "eslint-config-prettier": "^9.1.0",
109
110
  "eslint-plugin-import": "^2.29.1",
110
111
  "eslint-plugin-prettier": "^5.1.3",
111
- "eslint-plugin-unicorn": "^50.0.1",
112
- "mocha": "^10.2.0",
112
+ "eslint-plugin-unicorn": "^51.0.1",
113
+ "mocha": "^10.3.0",
113
114
  "nyc": "^15.1.0",
114
- "prettier": "^3.2.4",
115
+ "prettier": "^3.2.5",
115
116
  "serverless": "^3.38.0"
116
117
  },
117
118
  "peerDependencies": {
@@ -6,6 +6,8 @@ export const CUSTOM_OPTION = "serverless-offline"
6
6
 
7
7
  export const DEFAULT_LAMBDA_RUNTIME = "nodejs14.x"
8
8
 
9
+ export const DEFAULT_LAMBDA_ARCHITECTURE = "arm64"
10
+
9
11
  // https://docs.aws.amazon.com/lambda/latest/dg/limits.html
10
12
  export const DEFAULT_LAMBDA_MEMORY_SIZE = 1024
11
13
  // default function timeout in seconds
@@ -1,3 +1,4 @@
1
+ /* eslint-disable sort-keys */
1
2
  // native runtime support for AWS
2
3
  // https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
3
4
 
@@ -6,6 +7,32 @@
6
7
  // 'dotnet6',
7
8
  // ])
8
9
 
10
+ const X86_64 = "x86_64"
11
+ const ARM64 = "arm64"
12
+
13
+ export const supportedRuntimesArchitecture = {
14
+ "nodejs14.x": [ARM64, X86_64],
15
+ "nodejs16.x": [ARM64, X86_64],
16
+ "nodejs18.x": [ARM64, X86_64],
17
+ "nodejs20.x": [ARM64, X86_64],
18
+ "python3.7": [X86_64],
19
+ "python3.8": [ARM64, X86_64],
20
+ "python3.9": [ARM64, X86_64],
21
+ "python3.10": [ARM64, X86_64],
22
+ "python3.11": [ARM64, X86_64],
23
+ "ruby2.7": [ARM64, X86_64],
24
+ "ruby3.2": [ARM64, X86_64],
25
+ java8: [X86_64],
26
+ "java8.al2": [ARM64, X86_64],
27
+ java11: [ARM64, X86_64],
28
+ java17: [ARM64, X86_64],
29
+ "go1.x": [X86_64],
30
+ "dotnetcore3.1": [ARM64, X86_64],
31
+ provided: [X86_64],
32
+ dotnet6: [ARM64, X86_64],
33
+ "provided.al2": [ARM64, X86_64],
34
+ }
35
+
9
36
  // GO
10
37
  export const supportedGo = new Set(["go1.x"])
11
38
 
@@ -35,8 +62,6 @@ export const supportedPython = new Set([
35
62
  // RUBY
36
63
  export const supportedRuby = new Set(["ruby2.7", "ruby3.2"])
37
64
 
38
- // deprecated runtimes
39
- // https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html
40
65
  export const supportedRuntimes = new Set([
41
66
  // ...supportedDotnetcore,
42
67
  ...supportedGo,
@@ -47,10 +72,6 @@ export const supportedRuntimes = new Set([
47
72
  ...supportedRuby,
48
73
  ])
49
74
 
50
- export const unsupportedDockerRuntimes = new Set([
51
- "nodejs14.x",
52
- "nodejs16.x",
53
- "nodejs18.x",
54
- "nodejs20.x",
55
- "python3.9",
56
- ])
75
+ // deprecated runtimes
76
+ // https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html
77
+ export const unsupportedDockerRuntimes = new Set([])
@@ -654,8 +654,7 @@ export default class HttpServer {
654
654
 
655
655
  log.debug(`Using response '${responseName}'`)
656
656
 
657
- const chosenResponse = endpoint.responses[responseName]
658
-
657
+ const chosenResponse = endpoint.responses?.[responseName] ?? {}
659
658
  /* RESPONSE PARAMETERS PROCCESSING */
660
659
 
661
660
  const { responseParameters } = chosenResponse
@@ -10,6 +10,7 @@ import jszip from "jszip"
10
10
  import HandlerRunner from "./handler-runner/index.js"
11
11
  import LambdaContext from "./LambdaContext.js"
12
12
  import {
13
+ DEFAULT_LAMBDA_ARCHITECTURE,
13
14
  DEFAULT_LAMBDA_MEMORY_SIZE,
14
15
  DEFAULT_LAMBDA_RUNTIME,
15
16
  DEFAULT_LAMBDA_TIMEOUT,
@@ -57,6 +58,8 @@ export default class LambdaFunction {
57
58
 
58
59
  #runtime = null
59
60
 
61
+ #architecture = null
62
+
60
63
  #status = "IDLE" // can be 'BUSY' or 'IDLE'
61
64
 
62
65
  #timeout = null
@@ -91,6 +94,10 @@ export default class LambdaFunction {
91
94
 
92
95
  this.#runtime =
93
96
  functionDefinition.runtime ?? provider.runtime ?? DEFAULT_LAMBDA_RUNTIME
97
+ this.#architecture =
98
+ functionDefinition.architecture ??
99
+ provider.architecture ??
100
+ DEFAULT_LAMBDA_ARCHITECTURE
94
101
 
95
102
  this.#timeout =
96
103
  (functionDefinition.timeout ??
@@ -136,6 +143,7 @@ export default class LambdaFunction {
136
143
 
137
144
  // TEMP
138
145
  const funOptions = {
146
+ architecture: this.#architecture,
139
147
  codeDir: this.#codeDir,
140
148
  functionKey,
141
149
  functionName: name,
@@ -32,7 +32,7 @@ export default class HandlerRunner {
32
32
  if (useDocker) {
33
33
  if (unsupportedDockerRuntimes.has(runtime)) {
34
34
  log.warning(
35
- `"${runtime}" runtime is not supported with docker. See https://github.com/lambci/docker-lambda`,
35
+ `"${runtime}" runtime is not supported with docker. See https://github.com/aws/aws-lambda-base-images`,
36
36
  )
37
37
  throw new Error("Unsupported runtime")
38
38
  }
@@ -9,8 +9,8 @@ import { execa } from "execa"
9
9
  import { ensureDir, pathExists } from "fs-extra"
10
10
  import isWsl from "is-wsl"
11
11
  import jszip from "jszip"
12
- import pRetry from "p-retry"
13
12
  import DockerImage from "./DockerImage.js"
13
+ import Runtime from "./DockerRuntime.js"
14
14
 
15
15
  const { stringify } = JSON
16
16
  const { floor, log: mathLog } = Math
@@ -24,8 +24,6 @@ export default class DockerContainer {
24
24
 
25
25
  #env = null
26
26
 
27
- #functionKey = null
28
-
29
27
  #handler = null
30
28
 
31
29
  #image = null
@@ -42,13 +40,15 @@ export default class DockerContainer {
42
40
 
43
41
  #runtime = null
44
42
 
43
+ #architecture = null
44
+
45
45
  #servicePath = null
46
46
 
47
47
  constructor(
48
48
  env,
49
- functionKey,
50
49
  handler,
51
50
  runtime,
51
+ architecture,
52
52
  layers,
53
53
  provider,
54
54
  servicePath,
@@ -56,23 +56,24 @@ export default class DockerContainer {
56
56
  ) {
57
57
  this.#dockerOptions = dockerOptions
58
58
  this.#env = env
59
- this.#functionKey = functionKey
60
59
  this.#handler = handler
61
- this.#imageNameTag = this.#baseImage(runtime)
60
+ this.#imageNameTag = this.#baseImage(runtime, architecture)
62
61
  this.#image = new DockerImage(this.#imageNameTag)
63
62
  this.#layers = layers
64
63
  this.#provider = provider
65
64
  this.#runtime = runtime
65
+ this.#architecture = architecture
66
66
  this.#servicePath = servicePath
67
67
  }
68
68
 
69
- #baseImage(runtime) {
70
- return `lambci/lambda:${runtime}`
69
+ #baseImage(runtime, architecture) {
70
+ const runtimeImageTag = new Runtime().getImageNameTag(runtime, architecture)
71
+ // # Gets the ECR image format like `python:3.7` or `nodejs:16-x86_64`
72
+ return `public.ecr.aws/lambda/${runtimeImageTag}`
71
73
  }
72
74
 
73
75
  async start(codeDir) {
74
76
  await this.#image.pull()
75
-
76
77
  log.debug("Run Docker container...")
77
78
 
78
79
  let permissions = "ro"
@@ -85,19 +86,17 @@ export default class DockerContainer {
85
86
  "-v",
86
87
  `${codeDir}:/var/task:${permissions},delegated`,
87
88
  "-p",
88
- 9001,
89
+ 8080,
89
90
  "-e",
90
91
  "DOCKER_LAMBDA_STAY_OPEN=1", // API mode
91
92
  "-e",
92
93
  "DOCKER_LAMBDA_WATCH=1", // Watch mode
93
94
  ]
94
-
95
95
  if (this.#layers.length > 0) {
96
96
  log.verbose(`Found layers, checking provider type`)
97
97
 
98
98
  if (this.#provider.name.toLowerCase() === "aws") {
99
99
  let layerDir = this.#dockerOptions.layersDir
100
-
101
100
  if (!layerDir) {
102
101
  layerDir = join(this.#servicePath, ".serverless-offline", "layers")
103
102
  }
@@ -135,12 +134,22 @@ export default class DockerContainer {
135
134
  this.#dockerOptions.hostServicePath,
136
135
  )
137
136
  }
138
- dockerArgs.push("-v", `${layerDir}:/opt:ro,delegated`)
137
+ dockerArgs.push("-v", `${layerDir}:/var/runtime:ro,delegated`)
139
138
  } else {
140
139
  log.warning(
141
140
  `Provider ${this.#provider.name} is Unsupported. Layers are only supported on aws.`,
142
141
  )
143
142
  }
143
+ } else {
144
+ log.debug("Looking for bootstrap file")
145
+ const bootstrapDir = join(this.#servicePath, "bootstrap")
146
+ if (await pathExists(bootstrapDir)) {
147
+ log.debug(`Found bootstrap file at ${bootstrapDir}`)
148
+ dockerArgs.push(
149
+ "-v",
150
+ `${bootstrapDir}:/var/runtime/bootstrap:ro,delegated`,
151
+ )
152
+ }
144
153
  }
145
154
 
146
155
  entries(this.#env).forEach(([key, value]) => {
@@ -176,7 +185,7 @@ export default class DockerContainer {
176
185
  const str = String(data)
177
186
  log.error(str)
178
187
 
179
- if (str.includes("Lambda API listening on port")) {
188
+ if (str.includes("(cwd=/var/task, handler=)")) {
180
189
  resolve()
181
190
  }
182
191
  })
@@ -195,12 +204,12 @@ export default class DockerContainer {
195
204
  // NOTE: `docker port` may output multiple lines.
196
205
  //
197
206
  // e.g.:
198
- // 9001/tcp -> 0.0.0.0:49153
199
- // 9001/tcp -> :::49153
207
+ // 8080/tcp -> 0.0.0.0:49153
208
+ // 8080/tcp -> :::49153
200
209
  //
201
210
  // Parse each line until it finds the mapped port.
202
211
  for (const line of dockerPortOutput.split("\n")) {
203
- const result = line.match(/^9001\/tcp -> (.*):(\d+)$/)
212
+ const result = line.match(/^8080\/tcp -> (.*):(\d+)$/)
204
213
  if (result && result.length > 2) {
205
214
  ;[, , containerPort] = result
206
215
  break
@@ -212,15 +221,6 @@ export default class DockerContainer {
212
221
 
213
222
  this.#containerId = containerId
214
223
  this.#port = containerPort
215
-
216
- await pRetry(() => this.#ping(), {
217
- // default,
218
- factor: 2,
219
- // milliseconds
220
- minTimeout: 10,
221
- // default
222
- retries: 10,
223
- })
224
224
  }
225
225
 
226
226
  async #downloadLayer(layerArn, layerDir) {
@@ -344,19 +344,8 @@ export default class DockerContainer {
344
344
  return gateway.split("/")[0]
345
345
  }
346
346
 
347
- async #ping() {
348
- const url = `http://${this.#dockerOptions.host}:${this.#port}/2018-06-01/ping`
349
- const res = await fetch(url)
350
-
351
- if (!res.ok) {
352
- throw new Error(`Failed to fetch from ${url} with ${res.statusText}`)
353
- }
354
-
355
- return res.text()
356
- }
357
-
358
347
  async request(event) {
359
- const url = `http://${this.#dockerOptions.host}:${this.#port}/2015-03-31/functions/${this.#functionKey}/invocations`
348
+ const url = `http://${this.#dockerOptions.host}:${this.#port}/2015-03-31/functions/function/invocations`
360
349
 
361
350
  const res = await fetch(url, {
362
351
  body: stringify(event),
@@ -17,11 +17,7 @@ export default class DockerImage {
17
17
  log.debug(`Downloading base Docker image... (${imageNameTag})`)
18
18
 
19
19
  try {
20
- await execa("docker", [
21
- "pull",
22
- "--disable-content-trust=false",
23
- imageNameTag,
24
- ])
20
+ await execa("docker", ["pull", imageNameTag])
25
21
  } catch (err) {
26
22
  log.error(err.stderr)
27
23
 
@@ -9,9 +9,9 @@ export default class DockerRunner {
9
9
  constructor(funOptions, env, dockerOptions) {
10
10
  const {
11
11
  codeDir,
12
- functionKey,
13
12
  handler,
14
13
  runtime,
14
+ architecture,
15
15
  layers,
16
16
  provider,
17
17
  servicePath,
@@ -31,9 +31,9 @@ export default class DockerRunner {
31
31
 
32
32
  this.#container = new DockerContainer(
33
33
  env,
34
- functionKey,
35
34
  handler,
36
35
  runtime,
36
+ architecture,
37
37
  layers,
38
38
  provider,
39
39
  servicePath,
@@ -0,0 +1,33 @@
1
+ import { supportedRuntimesArchitecture } from "../../../config/supportedRuntimes.js"
2
+
3
+ export default class Runtime {
4
+ getImageNameTag(runtime, architecture) {
5
+ let runtimeImageTag = ""
6
+
7
+ if (runtime === this.provided) {
8
+ runtimeImageTag = "provided:alami"
9
+ } else if (runtime.startsWith("provided")) {
10
+ runtimeImageTag = runtime.replace(".", ":")
11
+ } else if (runtime.startsWith("dotnet")) {
12
+ runtimeImageTag = runtime.replace("dotnet", "dotnet:")
13
+ } else {
14
+ const match = /^([a-z]+)(\d[\d.a-z]*)$/.exec(runtime)
15
+ runtimeImageTag = match ? `${match[1]}:${match[2]}` : runtime
16
+ runtimeImageTag = runtimeImageTag.replace(".x", "")
17
+ }
18
+
19
+ if (this.hasRuntimeMultiArchImage(runtime)) {
20
+ runtimeImageTag = `${runtimeImageTag}-${architecture}`
21
+ }
22
+
23
+ return runtimeImageTag
24
+ }
25
+
26
+ hasRuntimeMultiArchImage(runtime) {
27
+ return (
28
+ (supportedRuntimesArchitecture[runtime] &&
29
+ supportedRuntimesArchitecture[runtime].length > 1) ||
30
+ false
31
+ )
32
+ }
33
+ }