serverless-offline 9.0.0 → 9.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 CHANGED
@@ -127,6 +127,7 @@ All CLI options are optional:
127
127
  --ignoreJWTSignature When using HttpApi with a JWT authorizer, don't check the signature of the JWT token. This should only be used for local development.
128
128
  --lambdaPort Lambda http port to listen on. Default: 3002
129
129
  --layersDir The directory layers should be stored in. Default: ${codeDir}/.serverless-offline/layers'
130
+ --localEnvironment Copy local environment variables. Default: false
130
131
  --noAuth Turns off all authorizers
131
132
  --noPrependStageInUrl Don't prepend http routes with the stage.
132
133
  --noStripTrailingSlashInUrl Don't strip trailing slash from http routes.
@@ -266,14 +267,14 @@ If you're using least-privilege principals for your AWS roles, this policy shoul
266
267
 
267
268
  ```json
268
269
  {
269
- "Version": "2012-10-17",
270
270
  "Statement": [
271
271
  {
272
- "Effect": "Allow",
273
272
  "Action": "lambda:GetLayerVersion",
273
+ "Effect": "Allow",
274
274
  "Resource": "arn:aws:lambda:*:*:layer:*:*"
275
275
  }
276
- ]
276
+ ],
277
+ "Version": "2012-10-17"
277
278
  }
278
279
  ```
279
280
 
@@ -603,13 +604,13 @@ Add a new [launch configuration](https://code.visualstudio.com/docs/editor/debug
603
604
 
604
605
  ```json
605
606
  {
606
- "type": "node",
607
- "request": "launch",
608
- "name": "Debug Serverless Offline",
609
607
  "cwd": "${workspaceFolder}",
610
- "runtimeExecutable": "npm",
608
+ "name": "Debug Serverless Offline",
609
+ "request": "launch",
611
610
  "runtimeArgs": ["run", "debug"],
612
- "sourceMaps": true
611
+ "runtimeExecutable": "npm",
612
+ "sourceMaps": true,
613
+ "type": "node"
613
614
  }
614
615
  ```
615
616
 
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": "9.0.0",
4
+ "version": "9.1.0",
5
5
  "description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
6
6
  "license": "MIT",
7
7
  "main": "./src/index.js",
@@ -194,7 +194,7 @@
194
194
  "@hapi/boom": "^10.0.0",
195
195
  "@hapi/h2o2": "^9.1.0",
196
196
  "@hapi/hapi": "^20.2.2",
197
- "aws-sdk": "^2.1176.0",
197
+ "aws-sdk": "^2.1181.0",
198
198
  "boxen": "^7.0.0",
199
199
  "chalk": "^5.0.1",
200
200
  "execa": "^6.1.0",
@@ -206,7 +206,7 @@
206
206
  "jsonwebtoken": "^8.5.1",
207
207
  "jszip": "^3.10.0",
208
208
  "luxon": "^3.0.1",
209
- "node-fetch": "^3.2.8",
209
+ "node-fetch": "^3.2.9",
210
210
  "node-schedule": "^2.1.0",
211
211
  "object.hasown": "^1.1.1",
212
212
  "p-memoize": "^7.1.0",
@@ -1,4 +1,4 @@
1
- import process, { env, exit } from 'node:process'
1
+ import process, { exit } from 'node:process'
2
2
  import { log } from '@serverless/utils/log.js'
3
3
  import chalk from 'chalk'
4
4
  import {
@@ -59,9 +59,6 @@ export default class ServerlessOffline {
59
59
 
60
60
  // Entry point for the plugin (sls offline) when running 'sls offline start'
61
61
  async start() {
62
- // Put here so available everywhere, not just in handlers
63
- env.IS_OFFLINE = true
64
-
65
62
  this.#mergeOptions()
66
63
 
67
64
  const { httpEvents, lambdas, scheduleEvents, webSocketEvents } =
@@ -91,6 +91,10 @@ export default {
91
91
  usage:
92
92
  'The directory layers should be stored in. Default: {codeDir}/.serverless-offline/layers',
93
93
  },
94
+ localEnvironment: {
95
+ type: 'boolean',
96
+ usage: 'Copy local environment variables. Default: false',
97
+ },
94
98
  noAuth: {
95
99
  type: 'boolean',
96
100
  usage: 'Turns off all authorizers',
@@ -4,7 +4,7 @@ export const BASE_URL_PLACEHOLDER = 'http://example'
4
4
 
5
5
  export const CUSTOM_OPTION = 'serverless-offline'
6
6
 
7
- export const DEFAULT_LAMBDA_RUNTIME = 'nodejs12.x'
7
+ export const DEFAULT_LAMBDA_RUNTIME = 'nodejs14.x'
8
8
 
9
9
  // https://docs.aws.amazon.com/lambda/latest/dg/limits.html
10
10
  export const DEFAULT_LAMBDA_MEMORY_SIZE = 1024
@@ -20,6 +20,7 @@ export default {
20
20
  httpsProtocol: '',
21
21
  lambdaPort: 3002,
22
22
  layersDir: null,
23
+ localEnvironment: false,
23
24
  noAuth: false,
24
25
  noPrependStageInUrl: false,
25
26
  noStripTrailingSlashInUrl: false,
@@ -2,7 +2,7 @@ import { Buffer } from 'node:buffer'
2
2
  import { readFileSync } from 'node:fs'
3
3
  import { createRequire } from 'node:module'
4
4
  import { join, resolve } from 'node:path'
5
- import process, { exit } from 'node:process'
5
+ import { exit } from 'node:process'
6
6
  import h2o2 from '@hapi/h2o2'
7
7
  import { Server } from '@hapi/hapi'
8
8
  import { log } from '@serverless/utils/log.js'
@@ -35,8 +35,6 @@ const { assign, entries, keys } = Object
35
35
  export default class HttpServer {
36
36
  #lambda = null
37
37
 
38
- #lastRequestOptions = null
39
-
40
38
  #options = null
41
39
 
42
40
  #serverless = null
@@ -198,17 +196,6 @@ export default class HttpServer {
198
196
  const server = `${httpsProtocol ? 'https' : 'http'}://${host}:${httpPort}`
199
197
 
200
198
  log.notice(`Server ready: ${server} 🚀`)
201
- log.notice()
202
- log.notice('Enter "rp" to replay the last request')
203
-
204
- process.openStdin().addListener('data', (data) => {
205
- // note: data is an object, and when converted to a string it will
206
- // end with a linefeed. so we (rather crudely) account for that
207
- // with toString() and then trim()
208
- if (data.toString().trim() === 'rp') {
209
- this.#injectLastRequest()
210
- }
211
- })
212
199
  }
213
200
 
214
201
  // stops the server
@@ -513,24 +500,11 @@ export default class HttpServer {
513
500
  hapiOptions.tags = ['api']
514
501
 
515
502
  const hapiHandler = async (request, h) => {
516
- // Here we go
517
- // Store current request as the last one
518
- this.#lastRequestOptions = {
519
- headers: request.headers,
520
- method: request.method,
521
- payload: request.payload,
522
- url: request.url.href,
523
- }
524
-
525
503
  const requestPath =
526
504
  endpoint.isHttpApi || this.#options.noPrependStageInUrl
527
505
  ? request.path
528
506
  : request.path.substr(`/${stage}`.length)
529
507
 
530
- if (request.auth.credentials && request.auth.strategy) {
531
- this.#lastRequestOptions.auth = request.auth
532
- }
533
-
534
508
  // Payload processing
535
509
  const encoding = detectEncoding(request)
536
510
 
@@ -1257,15 +1231,6 @@ export default class HttpServer {
1257
1231
  .map((line) => line.trim())
1258
1232
  }
1259
1233
 
1260
- #injectLastRequest() {
1261
- if (this.#lastRequestOptions) {
1262
- log.notice('Replaying HTTP last request')
1263
- this.#server.inject(this.#lastRequestOptions)
1264
- } else {
1265
- log.notice('No last HTTP request to replay!')
1266
- }
1267
- }
1268
-
1269
1234
  writeRoutesTerminal() {
1270
1235
  logRoutes(this.#terminalInfo)
1271
1236
  }
@@ -1,12 +1,12 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises'
2
2
  import { dirname, join, resolve } from 'node:path'
3
+ import process from 'node:process'
3
4
  import { performance } from 'node:perf_hooks'
4
5
  import { log } from '@serverless/utils/log.js'
5
6
  import { emptyDir, ensureDir, remove } from 'fs-extra'
6
7
  import jszip from 'jszip'
7
8
  import HandlerRunner from './handler-runner/index.js'
8
9
  import LambdaContext from './LambdaContext.js'
9
- import resolveJoins from '../utils/resolveJoins.js'
10
10
  import {
11
11
  DEFAULT_LAMBDA_MEMORY_SIZE,
12
12
  DEFAULT_LAMBDA_RUNTIME,
@@ -15,7 +15,7 @@ import {
15
15
  } from '../config/index.js'
16
16
  import { createUniqueId, splitHandlerPathAndName } from '../utils/index.js'
17
17
 
18
- const { entries } = Object
18
+ const { entries, fromEntries } = Object
19
19
  const { ceil } = Math
20
20
 
21
21
  export default class LambdaFunction {
@@ -35,6 +35,8 @@ export default class LambdaFunction {
35
35
 
36
36
  #functionName = null
37
37
 
38
+ #handler = null
39
+
38
40
  #handlerRunner = null
39
41
 
40
42
  #idleTimeStarted = null
@@ -86,6 +88,7 @@ export default class LambdaFunction {
86
88
  // this._executionTimeout = null
87
89
  this.#functionKey = functionKey
88
90
  this.#functionName = name
91
+ this.#handler = handler
89
92
  this.#memorySize = memorySize
90
93
  this.#region = provider.region
91
94
  this.#runtime = runtime
@@ -93,11 +96,18 @@ export default class LambdaFunction {
93
96
 
94
97
  this.#verifySupportedRuntime()
95
98
 
96
- const env = this.#getEnv(
97
- resolveJoins(provider.environment),
98
- functionDefinition.environment,
99
- handler,
100
- )
99
+ const env = {
100
+ ...(options.localEnvironment
101
+ ? process.env
102
+ : // we always copy all AWS_xxxx environment variables over from local env
103
+ fromEntries(
104
+ entries(process.env).filter(([key]) => key.startsWith('AWS_')),
105
+ )),
106
+ ...this.#getAwsEnvVars(),
107
+ ...provider.environment,
108
+ ...functionDefinition.environment,
109
+ IS_OFFLINE: 'true',
110
+ }
101
111
 
102
112
  this.#artifact = functionDefinition.package?.artifact
103
113
 
@@ -176,6 +186,7 @@ export default class LambdaFunction {
176
186
  // https://github.com/serverless/serverless/blob/v1.50.0/lib/plugins/aws/invokeLocal/index.js#L108
177
187
  #getAwsEnvVars() {
178
188
  return {
189
+ _HANDLER: this.#handler,
179
190
  AWS_DEFAULT_REGION: this.#region,
180
191
  AWS_LAMBDA_FUNCTION_MEMORY_SIZE: this.#memorySize,
181
192
  AWS_LAMBDA_FUNCTION_NAME: this.#functionName,
@@ -194,16 +205,6 @@ export default class LambdaFunction {
194
205
  }
195
206
  }
196
207
 
197
- #getEnv(providerEnv, functionDefinitionEnv, handler) {
198
- return {
199
- ...this.#getAwsEnvVars(),
200
- ...providerEnv,
201
- ...functionDefinitionEnv,
202
- _HANDLER: handler, // TODO is this available in AWS?
203
- IS_OFFLINE: true,
204
- }
205
- }
206
-
207
208
  setClientContext(clientContext) {
208
209
  this.#clientContext = clientContext
209
210
  }
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, rmdir, writeFile } from 'node:fs/promises'
1
+ import { mkdir, readFile, rm, rmdir, writeFile } from 'node:fs/promises'
2
2
  import { EOL } from 'node:os'
3
3
  import { sep, resolve, parse as pathParse } from 'node:path'
4
4
  import process, { chdir, cwd } from 'node:process'
@@ -32,6 +32,9 @@ export default class GoRunner {
32
32
 
33
33
  async cleanup() {
34
34
  try {
35
+ // refresh go.mod
36
+ await rm(this.#tmpFile)
37
+ execaSync('go', ['mod', 'tidy'])
35
38
  await rmdir(this.#tmpPath, { recursive: true })
36
39
  } catch {
37
40
  // @ignore
@@ -154,8 +157,6 @@ export default class GoRunner {
154
157
  }
155
158
 
156
159
  try {
157
- // refresh go.mod
158
- execaSync('go', ['mod', 'tidy'])
159
160
  chdir(this.#codeDir)
160
161
  } catch {
161
162
  // @ignore
@@ -1,29 +0,0 @@
1
- const { entries } = Object
2
-
3
- // Used to resolve Fn::Join in environment variables
4
- export default function resolveJoins(environment) {
5
- if (!environment) {
6
- return undefined
7
- }
8
-
9
- const newEnv = {}
10
-
11
- entries(environment).forEach(([key, value]) => {
12
- if (!value) {
13
- return
14
- }
15
-
16
- const joinArray = value['Fn::Join']
17
- const isJoin = Boolean(joinArray)
18
-
19
- if (isJoin) {
20
- const separator = joinArray[0]
21
- const joined = joinArray[1].join(separator)
22
- newEnv[key] = joined
23
- } else {
24
- newEnv[key] = value
25
- }
26
- })
27
-
28
- return newEnv
29
- }