netlify-cli 12.7.2 → 12.9.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.
@@ -6,9 +6,9 @@ version = "0.1.0"
6
6
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7
7
 
8
8
  [dependencies]
9
- aws_lambda_events = "0.7.2"
9
+ aws_lambda_events = "0.7.3"
10
10
  http = "0.2.8"
11
- lambda_runtime = "0.7.1"
11
+ lambda_runtime = "0.7.3"
12
12
  log = "0.4.17"
13
13
  simple_logger = "1.16.0"
14
- tokio = "1.22.0"
14
+ tokio = "1.24.1"
@@ -26,9 +26,9 @@
26
26
  }
27
27
  },
28
28
  "node_modules/@types/node": {
29
- "version": "14.18.34",
30
- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.34.tgz",
31
- "integrity": "sha512-hcU9AIQVHmPnmjRK+XUUYlILlr9pQrsqSrwov/JK1pnf3GTQowVBhx54FbvM0AU/VXGH4i3+vgXS5EguR7fysA=="
29
+ "version": "14.18.36",
30
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz",
31
+ "integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ=="
32
32
  },
33
33
  "node_modules/is-promise": {
34
34
  "version": "4.0.0",
@@ -36,9 +36,9 @@
36
36
  "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
37
37
  },
38
38
  "node_modules/typescript": {
39
- "version": "4.9.3",
40
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
41
- "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
39
+ "version": "4.9.4",
40
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
41
+ "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
42
42
  "bin": {
43
43
  "tsc": "bin/tsc",
44
44
  "tsserver": "bin/tsserver"
@@ -58,9 +58,9 @@
58
58
  }
59
59
  },
60
60
  "@types/node": {
61
- "version": "14.18.34",
62
- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.34.tgz",
63
- "integrity": "sha512-hcU9AIQVHmPnmjRK+XUUYlILlr9pQrsqSrwov/JK1pnf3GTQowVBhx54FbvM0AU/VXGH4i3+vgXS5EguR7fysA=="
61
+ "version": "14.18.36",
62
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz",
63
+ "integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ=="
64
64
  },
65
65
  "is-promise": {
66
66
  "version": "4.0.0",
@@ -68,9 +68,9 @@
68
68
  "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
69
69
  },
70
70
  "typescript": {
71
- "version": "4.9.3",
72
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
73
- "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA=="
71
+ "version": "4.9.4",
72
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
73
+ "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg=="
74
74
  }
75
75
  }
76
76
  }
@@ -1,12 +1,12 @@
1
1
  const headers = {
2
2
  ForwardedHost: 'x-forwarded-host',
3
- ForwardedProtocol: 'x-forwarded-proto',
4
- Functions: 'x-deno-functions',
3
+ Functions: 'x-nf-edge-functions',
5
4
  Geo: 'x-nf-geo',
6
- Passthrough: 'x-deno-pass',
5
+ Passthrough: 'x-nf-passthrough',
7
6
  RequestID: 'X-NF-Request-ID',
8
7
  IP: 'x-nf-client-connection-ip',
9
8
  Site: 'X-NF-Site-Info',
9
+ DebugLogging: 'x-nf-debug-logging',
10
10
  }
11
11
 
12
12
  export default headers
@@ -56,19 +56,20 @@ export const createSiteInfoHeader = (siteInfo = {}) => {
56
56
  export const initializeProxy = async ({
57
57
  config,
58
58
  configPath,
59
+ debug,
59
60
  env: configEnv,
60
61
  geoCountry,
61
62
  geolocationMode,
62
63
  getUpdatedConfig,
63
64
  inspectSettings,
65
+ mainPort,
64
66
  offline,
67
+ passthroughPort,
65
68
  projectDir,
66
- settings,
67
69
  siteInfo,
68
70
  state,
69
71
  }) => {
70
72
  const { functions: internalFunctions, importMap, path: internalFunctionsPath } = await getInternalFunctions()
71
- const { port: mainPort } = settings
72
73
  const userFunctionsPath = config.build.edge_functions
73
74
  const isolatePort = await getAvailablePort()
74
75
 
@@ -76,7 +77,6 @@ export const initializeProxy = async ({
76
77
  // the network if needed. We don't want to wait for that to be completed, or
77
78
  // the command will be left hanging.
78
79
  const server = prepareServer({
79
- certificatePath: settings.https ? settings.https.certFilePath : undefined,
80
80
  config,
81
81
  configPath,
82
82
  directories: [internalFunctionsPath, userFunctionsPath].filter(Boolean),
@@ -131,14 +131,14 @@ export const initializeProxy = async ({
131
131
 
132
132
  req[headersSymbol] = {
133
133
  [headers.Functions]: functionNames.join(','),
134
- [headers.ForwardedHost]: `localhost:${mainPort}`,
134
+ [headers.ForwardedHost]: `localhost:${passthroughPort}`,
135
135
  [headers.Passthrough]: 'passthrough',
136
136
  [headers.RequestID]: generateUUID(),
137
137
  [headers.IP]: LOCAL_HOST,
138
138
  }
139
139
 
140
- if (settings.https) {
141
- req[headersSymbol][headers.ForwardedProtocol] = 'https'
140
+ if (debug) {
141
+ req[headersSymbol][headers.DebugLogging] = '1'
142
142
  }
143
143
 
144
144
  return `http://${LOCAL_HOST}:${isolatePort}`
@@ -148,7 +148,6 @@ export const initializeProxy = async ({
148
148
  export const isEdgeFunctionsRequest = (req) => req[headersSymbol] !== undefined
149
149
 
150
150
  const prepareServer = async ({
151
- certificatePath,
152
151
  config,
153
152
  configPath,
154
153
  directories,
@@ -168,7 +167,6 @@ const prepareServer = async ({
168
167
  const distImportMapPath = getPathInProject([DIST_IMPORT_MAP_PATH])
169
168
  const runIsolate = await bundler.serve({
170
169
  ...getDownloadUpdateFunctions(),
171
- certificatePath,
172
170
  debug: env.NETLIFY_DENO_DEBUG === 'true',
173
171
  distImportMapPath,
174
172
  formatExportTypeError: (name) =>
@@ -295,7 +295,15 @@ export class EdgeFunctionsRegistry {
295
295
  ...route,
296
296
  pattern: new RegExp(route.pattern),
297
297
  }))
298
- const functionNames = routes.filter(({ pattern }) => pattern.test(urlPath)).map((route) => route.function)
298
+ const functionNames = routes
299
+ .filter(({ pattern }) => pattern.test(urlPath))
300
+ .filter(({ function: name }) => {
301
+ const isExcluded = manifest.function_config[name]?.excluded_patterns.some((pattern) =>
302
+ new RegExp(pattern).test(urlPath),
303
+ )
304
+ return !isExcluded
305
+ })
306
+ .map((route) => route.function)
299
307
  const orphanedDeclarations = await this.matchURLPathAgainstOrphanedDeclarations(urlPath)
300
308
 
301
309
  return { functionNames, orphanedDeclarations }
@@ -168,7 +168,7 @@ export const createHandler = function (options) {
168
168
  return
169
169
  }
170
170
 
171
- handleSynchronousFunction(error, result, request, response)
171
+ handleSynchronousFunction({ error, functionName: func.name, result, request, response })
172
172
  }
173
173
  }
174
174
  }
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  import { Buffer } from 'buffer'
3
3
 
4
- import { NETLIFYDEVERR } from '../../utils/command-helpers.mjs'
4
+ import { chalk, log, NETLIFYDEVERR } from '../../utils/command-helpers.mjs'
5
5
  import renderErrorTemplate from '../render-error-template.mjs'
6
6
 
7
7
  import { detectAwsSdkError } from './utils.mjs'
@@ -16,20 +16,35 @@ const addHeaders = (headers, response) => {
16
16
  })
17
17
  }
18
18
 
19
- export const handleSynchronousFunction = function (err, result, request, response) {
20
- if (err) {
21
- return handleErr(err, request, response)
19
+ export const handleSynchronousFunction = function ({
20
+ error: invocationError,
21
+ functionName,
22
+ request,
23
+ response,
24
+ result,
25
+ }) {
26
+ if (invocationError) {
27
+ return handleErr(invocationError, request, response)
22
28
  }
23
29
 
24
30
  const { error } = validateLambdaResponse(result)
25
31
  if (error) {
26
- console.log(`${NETLIFYDEVERR} ${error}`)
32
+ log(`${NETLIFYDEVERR} ${error}`)
27
33
  return handleErr(error, request, response)
28
34
  }
29
35
 
30
36
  response.statusCode = result.statusCode
31
- addHeaders(result.headers, response)
32
- addHeaders(result.multiValueHeaders, response)
37
+
38
+ try {
39
+ addHeaders(result.headers, response)
40
+ addHeaders(result.multiValueHeaders, response)
41
+ } catch (headersError) {
42
+ formatError(headersError)
43
+
44
+ log(`${NETLIFYDEVERR} Failed to set header in function ${chalk.yellow(functionName)}: ${headersError.message}`)
45
+
46
+ return handleErr(headersError, request, response)
47
+ }
33
48
 
34
49
  if (result.body) {
35
50
  response.write(result.isBase64Encoded ? Buffer.from(result.body, 'base64') : result.body)
@@ -37,6 +52,12 @@ export const handleSynchronousFunction = function (err, result, request, respons
37
52
  response.end()
38
53
  }
39
54
 
55
+ const formatError = (err) => {
56
+ err.errorType = err.code
57
+ err.errorMessage = err.message
58
+ err.stackTrace = err.trace
59
+ }
60
+
40
61
  const formatLambdaLocalError = (err, acceptsHtml) =>
41
62
  acceptsHtml
42
63
  ? JSON.stringify({
@@ -44,7 +44,7 @@ const getRepoData = async function ({ remoteName } = {}) {
44
44
  Object.keys(gitConfig.remote[remoteName]).length === 0
45
45
  ) {
46
46
  throw new Error(
47
- `The specified remote "${remoteName}" is not defined in Git repo. Please use --gitRemoteName flag to specify a remote.`,
47
+ `The specified remote "${remoteName}" is not defined in Git repo. Please use --git-remote-name flag to specify a remote.`,
48
48
  )
49
49
  }
50
50
 
@@ -39,6 +39,7 @@ export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => {
39
39
  * @param {*} params.addonsUrls
40
40
  * @param {import('../commands/base-command.mjs').NetlifyOptions["config"]} params.config
41
41
  * @param {string} [params.configPath] An override for the Netlify config path
42
+ * @param {boolean} params.debug
42
43
  * @param {import('../commands/base-command.mjs').NetlifyOptions["cachedConfig"]['env']} params.env
43
44
  * @param {InspectSettings} params.inspectSettings
44
45
  * @param {() => Promise<object>} params.getUpdatedConfig
@@ -55,6 +56,7 @@ export const startProxyServer = async ({
55
56
  addonsUrls,
56
57
  config,
57
58
  configPath,
59
+ debug,
58
60
  env,
59
61
  geoCountry,
60
62
  geolocationMode,
@@ -70,6 +72,7 @@ export const startProxyServer = async ({
70
72
  addonsUrls,
71
73
  config,
72
74
  configPath: configPath || site.configPath,
75
+ debug,
73
76
  env,
74
77
  geolocationMode,
75
78
  geoCountry,
@@ -13,6 +13,7 @@ import contentType from 'content-type'
13
13
  import cookie from 'cookie'
14
14
  import { get } from 'dot-prop'
15
15
  import generateETag from 'etag'
16
+ import getAvailablePort from 'get-port'
16
17
  import httpProxy from 'http-proxy'
17
18
  import { createProxyMiddleware } from 'http-proxy-middleware'
18
19
  import jwtDecode from 'jwt-decode'
@@ -32,6 +33,7 @@ import { NETLIFYDEVLOG, NETLIFYDEVWARN } from './command-helpers.mjs'
32
33
  import createStreamPromise from './create-stream-promise.mjs'
33
34
  import { headersForPath, parseHeaders } from './headers.mjs'
34
35
  import { createRewriter, onChanges } from './rules-proxy.mjs'
36
+ import { signRedirect } from './sign-redirect.mjs'
35
37
 
36
38
  const decompress = util.promisify(zlib.gunzip)
37
39
  const shouldGenerateETag = Symbol('Internal: response should generate ETag')
@@ -142,7 +144,7 @@ const alternativePathsFor = function (url) {
142
144
  return paths
143
145
  }
144
146
 
145
- const serveRedirect = async function ({ match, options, proxy, req, res }) {
147
+ const serveRedirect = async function ({ match, options, proxy, req, res, siteInfo }) {
146
148
  if (!match) return proxy.web(req, res, options)
147
149
 
148
150
  options = options || req.proxyOptions || {}
@@ -154,6 +156,15 @@ const serveRedirect = async function ({ match, options, proxy, req, res }) {
154
156
  })
155
157
  }
156
158
 
159
+ if (match.signingSecret) {
160
+ req.headers['x-nf-sign'] = signRedirect({
161
+ deployContext: 'dev',
162
+ secret: match.signingSecret,
163
+ siteID: siteInfo.id,
164
+ siteURL: siteInfo.url,
165
+ })
166
+ }
167
+
157
168
  if (isFunction(options.functionsPort, req.url)) {
158
169
  return proxy.web(req, res, { target: options.functionsServer })
159
170
  }
@@ -305,7 +316,7 @@ const reqToURL = function (req, pathname) {
305
316
 
306
317
  const MILLISEC_TO_SEC = 1e3
307
318
 
308
- const initializeProxy = async function ({ configPath, distDir, host, port, projectDir }) {
319
+ const initializeProxy = async function ({ configPath, distDir, host, port, projectDir, siteInfo }) {
309
320
  const proxy = httpProxy.createProxyServer({
310
321
  selfHandleResponse: true,
311
322
  target: {
@@ -369,13 +380,20 @@ const initializeProxy = async function ({ configPath, distDir, host, port, proje
369
380
  return proxy.web(req, res, req.proxyOptions)
370
381
  }
371
382
  if (req.proxyOptions && req.proxyOptions.match) {
372
- return serveRedirect({ req, res, proxy: handlers, match: req.proxyOptions.match, options: req.proxyOptions })
383
+ return serveRedirect({
384
+ req,
385
+ res,
386
+ proxy: handlers,
387
+ match: req.proxyOptions.match,
388
+ options: req.proxyOptions,
389
+ siteInfo,
390
+ })
373
391
  }
374
392
  }
375
393
 
376
394
  if (req.proxyOptions.staticFile && isRedirect({ status: proxyRes.statusCode }) && proxyRes.headers.location) {
377
395
  req.url = proxyRes.headers.location
378
- return serveRedirect({ req, res, proxy: handlers, match: null, options: req.proxyOptions })
396
+ return serveRedirect({ req, res, proxy: handlers, match: null, options: req.proxyOptions, siteInfo })
379
397
  }
380
398
 
381
399
  const responseData = []
@@ -471,7 +489,11 @@ const initializeProxy = async function ({ configPath, distDir, host, port, proje
471
489
  return handlers
472
490
  }
473
491
 
474
- const onRequest = async ({ addonsUrls, edgeFunctionsProxy, functionsServer, proxy, rewriter, settings }, req, res) => {
492
+ const onRequest = async (
493
+ { addonsUrls, edgeFunctionsProxy, functionsServer, proxy, rewriter, settings, siteInfo },
494
+ req,
495
+ res,
496
+ ) => {
475
497
  req.originalBody = ['GET', 'OPTIONS', 'HEAD'].includes(req.method)
476
498
  ? null
477
499
  : await createStreamPromise(req, BYTES_LIMIT)
@@ -508,7 +530,7 @@ const onRequest = async ({ addonsUrls, edgeFunctionsProxy, functionsServer, prox
508
530
  // We don't want to generate an ETag for 3xx redirects.
509
531
  req[shouldGenerateETag] = ({ statusCode }) => statusCode < 300 || statusCode >= 400
510
532
 
511
- return serveRedirect({ req, res, proxy, match, options })
533
+ return serveRedirect({ req, res, proxy, match, options, siteInfo })
512
534
  }
513
535
 
514
536
  // The request will be served by the framework server, which means we want to
@@ -533,6 +555,7 @@ export const startProxy = async function ({
533
555
  addonsUrls,
534
556
  config,
535
557
  configPath,
558
+ debug,
536
559
  env,
537
560
  geoCountry,
538
561
  geolocationMode,
@@ -544,18 +567,21 @@ export const startProxy = async function ({
544
567
  siteInfo,
545
568
  state,
546
569
  }) {
570
+ const secondaryServerPort = settings.https ? await getAvailablePort() : null
547
571
  const functionsServer = settings.functionsPort ? `http://127.0.0.1:${settings.functionsPort}` : null
548
572
  const edgeFunctionsProxy = await initializeEdgeFunctionsProxy({
549
573
  config,
550
574
  configPath,
575
+ debug,
551
576
  env,
552
577
  geolocationMode,
553
578
  geoCountry,
554
579
  getUpdatedConfig,
555
580
  inspectSettings,
581
+ mainPort: settings.port,
556
582
  offline,
583
+ passthroughPort: secondaryServerPort || settings.port,
557
584
  projectDir,
558
- settings,
559
585
  siteInfo,
560
586
  state,
561
587
  })
@@ -565,6 +591,7 @@ export const startProxy = async function ({
565
591
  distDir: settings.dist,
566
592
  projectDir,
567
593
  configPath,
594
+ siteInfo,
568
595
  })
569
596
 
570
597
  const rewriter = await createRewriter({
@@ -583,17 +610,34 @@ export const startProxy = async function ({
583
610
  addonsUrls,
584
611
  functionsServer,
585
612
  edgeFunctionsProxy,
613
+ siteInfo,
586
614
  })
587
- const server = settings.https
615
+ const primaryServer = settings.https
588
616
  ? https.createServer({ cert: settings.https.cert, key: settings.https.key }, onRequestWithOptions)
589
617
  : http.createServer(onRequestWithOptions)
590
-
591
- server.on('upgrade', function onUpgrade(req, socket, head) {
618
+ const onUpgrade = function onUpgrade(req, socket, head) {
592
619
  proxy.ws(req, socket, head)
593
- })
620
+ }
621
+
622
+ primaryServer.on('upgrade', onUpgrade)
623
+ primaryServer.listen({ port: settings.port })
624
+
625
+ const eventQueue = [once(primaryServer, 'listening')]
626
+
627
+ // If we're running the main server on HTTPS, we need to start a secondary
628
+ // server on HTTP for receiving passthrough requests from edge functions.
629
+ // This lets us run the Deno server on HTTP and avoid the complications of
630
+ // Deno talking to Node on HTTPS with potentially untrusted certificates.
631
+ if (secondaryServerPort) {
632
+ const secondaryServer = http.createServer(onRequestWithOptions)
633
+
634
+ secondaryServer.on('upgrade', onUpgrade)
635
+ secondaryServer.listen({ port: secondaryServerPort })
636
+
637
+ eventQueue.push(once(secondaryServer, 'listening'))
638
+ }
594
639
 
595
- server.listen({ port: settings.port })
596
- await once(server, 'listening')
640
+ await Promise.all(eventQueue)
597
641
 
598
642
  const scheme = settings.https ? 'https' : 'http'
599
643
  return `${scheme}://localhost:${settings.port}`
@@ -36,6 +36,7 @@ const normalizeRedirect = function ({
36
36
  conditions: { country, language, role, ...conditions },
37
37
  from,
38
38
  query,
39
+ signed,
39
40
  ...redirect
40
41
  }) {
41
42
  return {
@@ -48,5 +49,10 @@ const normalizeRedirect = function ({
48
49
  ...(country && { Country: country }),
49
50
  ...(language && { Language: language }),
50
51
  },
52
+ ...(signed && {
53
+ sign: {
54
+ jwt_secret: signed,
55
+ },
56
+ }),
51
57
  }
52
58
  }
@@ -0,0 +1,16 @@
1
+ import jwt from 'jsonwebtoken'
2
+
3
+ // https://docs.netlify.com/routing/redirects/rewrites-proxies/#signed-proxy-redirects
4
+ export const signRedirect = ({ deployContext, secret, siteID, siteURL }) => {
5
+ const claims = {
6
+ deploy_context: deployContext,
7
+ netlify_id: siteID,
8
+ site_url: siteURL,
9
+ }
10
+ const options = {
11
+ expiresIn: '5 minutes',
12
+ issuer: 'netlify',
13
+ }
14
+
15
+ return jwt.sign(claims, secret, options)
16
+ }