netlify-cli 16.2.0 → 16.3.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
  "name": "netlify-cli",
3
3
  "description": "Netlify command line tool",
4
- "version": "16.2.0",
4
+ "version": "16.3.0",
5
5
  "author": "Netlify Inc.",
6
6
  "type": "module",
7
7
  "engines": {
@@ -44,13 +44,13 @@
44
44
  "dependencies": {
45
45
  "@bugsnag/js": "7.20.2",
46
46
  "@fastify/static": "6.10.2",
47
- "@netlify/build": "29.20.8",
48
- "@netlify/build-info": "7.7.4",
49
- "@netlify/config": "20.8.0",
47
+ "@netlify/build": "29.20.12",
48
+ "@netlify/build-info": "7.8.0",
49
+ "@netlify/config": "20.8.1",
50
50
  "@netlify/edge-bundler": "8.19.0",
51
51
  "@netlify/local-functions-proxy": "1.1.1",
52
52
  "@netlify/serverless-functions-api": "1.7.3",
53
- "@netlify/zip-it-and-ship-it": "9.17.0",
53
+ "@netlify/zip-it-and-ship-it": "9.17.3",
54
54
  "@octokit/rest": "19.0.13",
55
55
  "ansi-escapes": "6.2.0",
56
56
  "ansi-styles": "6.2.1",
@@ -37,4 +37,4 @@ const lmInfo = async () => {
37
37
  * @returns
38
38
  */
39
39
  export const createLmInfoCommand = (program) =>
40
- program.command('lm:info').description('Show large media requirements information.').action(lmInfo)
40
+ program.command('lm:info', { hidden: true }).description('Show large media requirements information.').action(lmInfo)
@@ -20,7 +20,7 @@ const lmInstall = async ({ force }) => {
20
20
  */
21
21
  export const createLmInstallCommand = (program) =>
22
22
  program
23
- .command('lm:install')
23
+ .command('lm:install', { hidden: true })
24
24
  .alias('lm:init')
25
25
  .description(
26
26
  `Configures your computer to use Netlify Large Media
@@ -97,7 +97,7 @@ const lmSetup = async (options, command) => {
97
97
  */
98
98
  export const createLmSetupCommand = (program) =>
99
99
  program
100
- .command('lm:setup')
100
+ .command('lm:setup', { hidden: true })
101
101
  .description('Configures your site to use Netlify Large Media')
102
102
  .option('-s, --skip-install', 'Skip the credentials helper installation check')
103
103
  .option('-f, --force-install', 'Force the credentials helper installation')
@@ -25,8 +25,10 @@ export const createLmCommand = (program) => {
25
25
  createLmUninstallCommand(program)
26
26
 
27
27
  program
28
- .command('lm')
29
- .description('Handle Netlify Large Media operations\nThe lm command will help you manage large media for a site')
28
+ .command('lm', { hidden: true })
29
+ .description(
30
+ '[Deprecated and will be removed from future versions] Handle Netlify Large Media operations\nThe lm command will help you manage large media for a site',
31
+ )
30
32
  .addExamples(['netlify lm:info', 'netlify lm:install', 'netlify lm:setup'])
31
33
  .action(lm)
32
34
  }
@@ -1,5 +1,5 @@
1
1
  import { env } from 'process'
2
2
 
3
- const latestBootstrapURL = 'https://64e7783fce8cfe0008496c72--edge.netlify.com/bootstrap/index-combined.ts'
3
+ const latestBootstrapURL = 'https://64f73321fdd56900083fa618--edge.netlify.com/bootstrap/index-combined.ts'
4
4
 
5
5
  export const getBootstrapURL = () => env.NETLIFY_EDGE_BOOTSTRAP || latestBootstrapURL
@@ -5,10 +5,13 @@ export const headers = {
5
5
  DeployID: 'x-nf-deploy-id',
6
6
  FeatureFlags: 'x-nf-feature-flags',
7
7
  ForwardedHost: 'x-forwarded-host',
8
+ ForwardedProtocol: 'x-forwarded-proto',
8
9
  Functions: 'x-nf-edge-functions',
9
10
  InvocationMetadata: 'x-nf-edge-functions-metadata',
10
11
  Geo: 'x-nf-geo',
11
12
  Passthrough: 'x-nf-passthrough',
13
+ PassthroughHost: 'x-nf-passthrough-host',
14
+ PassthroughProtocol: 'x-nf-passthrough-proto',
12
15
  IP: 'x-nf-client-connection-ip',
13
16
  Site: 'X-NF-Site-Info',
14
17
  DebugLogging: 'x-nf-debug-logging',
@@ -78,6 +78,7 @@ export const createAccountInfoHeader = (accountInfo = {}) => {
78
78
  * @param {boolean=} config.offline
79
79
  * @param {*} config.passthroughPort
80
80
  * @param {*} config.projectDir
81
+ * @param {*} config.settings
81
82
  * @param {*} config.siteInfo
82
83
  * @param {*} config.state
83
84
  * @returns
@@ -96,6 +97,7 @@ export const initializeProxy = async ({
96
97
  offline,
97
98
  passthroughPort,
98
99
  projectDir,
100
+ settings,
99
101
  siteInfo,
100
102
  state,
101
103
  }) => {
@@ -146,7 +148,7 @@ export const initializeProxy = async ({
146
148
  await registry.initialize()
147
149
 
148
150
  const url = new URL(req.url, `http://${LOCAL_HOST}:${mainPort}`)
149
- const { functionNames, invocationMetadata, orphanedDeclarations } = registry.matchURLPath(url.pathname)
151
+ const { functionNames, invocationMetadata, orphanedDeclarations } = registry.matchURLPath(url.pathname, req.method)
150
152
 
151
153
  // If the request matches a config declaration for an Edge Function without
152
154
  // a matching function file, we warn the user.
@@ -167,28 +169,22 @@ export const initializeProxy = async ({
167
169
  }
168
170
 
169
171
  const featureFlags = ['edge_functions_bootstrap_failure_mode']
170
- const forwardedHost = `localhost:${passthroughPort}`
171
172
 
172
173
  req[headersSymbol] = {
173
174
  [headers.FeatureFlags]: getFeatureFlagsHeader(featureFlags),
174
- [headers.ForwardedHost]: forwardedHost,
175
+ [headers.ForwardedProtocol]: settings.https ? 'https:' : 'http:',
175
176
  [headers.Functions]: functionNames.join(','),
176
177
  [headers.InvocationMetadata]: getInvocationMetadataHeader(invocationMetadata),
177
178
  [headers.IP]: LOCAL_HOST,
178
179
  [headers.Passthrough]: 'passthrough',
180
+ [headers.PassthroughHost]: `localhost:${passthroughPort}`,
181
+ [headers.PassthroughProtocol]: 'http:',
179
182
  }
180
183
 
181
184
  if (debug) {
182
185
  req[headersSymbol][headers.DebugLogging] = '1'
183
186
  }
184
187
 
185
- // If we're using a different port for passthrough requests, which is the
186
- // case when the CLI is running on HTTPS, use it on the Host header so
187
- // that the request URL inside the edge function is something accessible.
188
- if (mainPort !== passthroughPort) {
189
- req[headersSymbol].host = forwardedHost
190
- }
191
-
192
188
  return `http://${LOCAL_HOST}:${isolatePort}`
193
189
  }
194
190
  }
@@ -302,8 +302,9 @@ export class EdgeFunctionsRegistry {
302
302
 
303
303
  /**
304
304
  * @param {string} urlPath
305
+ * @param {string} method
305
306
  */
306
- matchURLPath(urlPath) {
307
+ matchURLPath(urlPath, method) {
307
308
  const declarations = this.#bundler.mergeDeclarations(
308
309
  this.#declarationsFromTOML,
309
310
  this.#userFunctionConfigs,
@@ -330,6 +331,10 @@ export class EdgeFunctionsRegistry {
330
331
  const routeIndexes = []
331
332
 
332
333
  routes.forEach((route, index) => {
334
+ if (route.methods && route.methods.length !== 0 && !route.methods.includes(method)) {
335
+ return
336
+ }
337
+
333
338
  if (!route.pattern.test(urlPath)) {
334
339
  return
335
340
  }
@@ -158,12 +158,22 @@ export default class NetlifyFunction {
158
158
  }
159
159
  }
160
160
 
161
- async matchURLPath(rawPath) {
161
+ /**
162
+ * Matches all routes agains the incoming request. If a match is found, then the matched route is returned.
163
+ * @param {string} rawPath
164
+ * @param {string} method
165
+ * @returns matched route
166
+ */
167
+ async matchURLPath(rawPath, method) {
162
168
  await this.buildQueue
163
169
 
164
170
  const path = (rawPath.endsWith('/') ? rawPath.slice(0, -1) : rawPath).toLowerCase()
165
171
  const { routes = [] } = this.buildData
166
- const isMatch = routes.some(({ expression, literal }) => {
172
+ return routes.find(({ expression, literal, methods }) => {
173
+ if (methods.length !== 0 && !methods.includes(method)) {
174
+ return false
175
+ }
176
+
167
177
  if (literal !== undefined) {
168
178
  return path === literal
169
179
  }
@@ -176,8 +186,6 @@ export default class NetlifyFunction {
176
186
 
177
187
  return false
178
188
  })
179
-
180
- return isMatch
181
189
  }
182
190
 
183
191
  get url() {
@@ -122,12 +122,12 @@ export class FunctionsRegistry {
122
122
  return this.functions.get(name)
123
123
  }
124
124
 
125
- async getFunctionForURLPath(urlPath) {
125
+ async getFunctionForURLPath(urlPath, method) {
126
126
  for (const func of this.functions.values()) {
127
- const isMatch = await func.matchURLPath(urlPath)
127
+ const route = await func.matchURLPath(urlPath, method)
128
128
 
129
- if (isMatch) {
130
- return func
129
+ if (route) {
130
+ return { func, route }
131
131
  }
132
132
  }
133
133
  }
@@ -7,7 +7,7 @@ import jwtDecode from 'jwt-decode'
7
7
 
8
8
  import { NETLIFYDEVERR, NETLIFYDEVLOG, error as errorExit, log } from '../../utils/command-helpers.mjs'
9
9
  import { CLOCKWORK_USERAGENT, getFunctionsDistPath, getInternalFunctionsDir } from '../../utils/functions/index.mjs'
10
- import { NFFunctionName } from '../../utils/headers.mjs'
10
+ import { NFFunctionName, NFFunctionRoute } from '../../utils/headers.mjs'
11
11
  import { headers as efHeaders } from '../edge-functions/headers.mjs'
12
12
  import { getGeoLocation } from '../geo-location.mjs'
13
13
 
@@ -56,11 +56,13 @@ export const createHandler = function (options) {
56
56
  const { functionsRegistry } = options
57
57
 
58
58
  return async function handler(request, response) {
59
- // If this header is set, it means we've already matched a function and we
59
+ // If these headers are set, it means we've already matched a function and we
60
60
  // can just grab its name directly. We delete the header from the request
61
61
  // because we don't want to expose it to user code.
62
62
  let functionName = request.header(NFFunctionName)
63
63
  delete request.headers[NFFunctionName]
64
+ const functionRoute = request.header(NFFunctionRoute)
65
+ delete request.headers[NFFunctionRoute]
64
66
 
65
67
  // If we didn't match a function with a custom route, let's try to match
66
68
  // using the fixed URL format.
@@ -148,6 +150,7 @@ export const createHandler = function (options) {
148
150
  isBase64Encoded,
149
151
  rawUrl,
150
152
  rawQuery,
153
+ route: functionRoute,
151
154
  }
152
155
 
153
156
  const clientContext = buildClientContext(request.headers) || {}
@@ -47,4 +47,5 @@ const getErrorMessage = function ({ message }) {
47
47
  }
48
48
 
49
49
  export const NFFunctionName = 'x-nf-function-name'
50
+ export const NFFunctionRoute = 'x-nf-function-route'
50
51
  export const NFRequestID = 'x-nf-request-id'
@@ -31,7 +31,7 @@ import renderErrorTemplate from '../lib/render-error-template.mjs'
31
31
 
32
32
  import { NETLIFYDEVLOG, NETLIFYDEVWARN, log, chalk } from './command-helpers.mjs'
33
33
  import createStreamPromise from './create-stream-promise.mjs'
34
- import { headersForPath, parseHeaders, NFFunctionName, NFRequestID } from './headers.mjs'
34
+ import { headersForPath, parseHeaders, NFFunctionName, NFRequestID, NFFunctionRoute } from './headers.mjs'
35
35
  import { generateRequestID } from './request-id.mjs'
36
36
  import { createRewriter, onChanges } from './rules-proxy.mjs'
37
37
  import { signRedirect } from './sign-redirect.mjs'
@@ -328,7 +328,8 @@ const serveRedirect = async function ({ env, functionsRegistry, match, options,
328
328
  return proxy.web(req, res, { target: options.functionsServer })
329
329
  }
330
330
 
331
- const functionWithCustomRoute = functionsRegistry && (await functionsRegistry.getFunctionForURLPath(destURL))
331
+ const functionWithCustomRoute =
332
+ functionsRegistry && (await functionsRegistry.getFunctionForURLPath(destURL, req.method))
332
333
  const destStaticFile = await getStatic(dest.pathname, options.publicFolder)
333
334
  let statusValue
334
335
  if (
@@ -342,7 +343,9 @@ const serveRedirect = async function ({ env, functionsRegistry, match, options,
342
343
  }
343
344
 
344
345
  if (isFunction(options.functionsPort, req.url) || functionWithCustomRoute) {
345
- const functionHeaders = functionWithCustomRoute ? { [NFFunctionName]: functionWithCustomRoute.name } : {}
346
+ const functionHeaders = functionWithCustomRoute
347
+ ? { [NFFunctionName]: functionWithCustomRoute.func.name, [NFFunctionRoute]: functionWithCustomRoute.route }
348
+ : {}
346
349
  const url = reqToURL(req, originalURL)
347
350
  req.headers['x-netlify-original-pathname'] = url.pathname
348
351
  req.headers['x-netlify-original-search'] = url.search
@@ -600,12 +603,12 @@ const onRequest = async (
600
603
  }
601
604
 
602
605
  // Does the request match a function on a custom URL path?
603
- const functionMatch = functionsRegistry ? await functionsRegistry.getFunctionForURLPath(req.url) : null
606
+ const functionMatch = functionsRegistry ? await functionsRegistry.getFunctionForURLPath(req.url, req.method) : null
604
607
 
605
608
  if (functionMatch) {
606
609
  // Setting an internal header with the function name so that we don't
607
610
  // have to match the URL again in the functions server.
608
- const headers = { [NFFunctionName]: functionMatch.name }
611
+ const headers = { [NFFunctionName]: functionMatch.func.name, [NFFunctionRoute]: functionMatch.route.pattern }
609
612
 
610
613
  return proxy.web(req, res, { headers, target: functionsServer })
611
614
  }
@@ -695,6 +698,7 @@ export const startProxy = async function ({
695
698
  mainPort: settings.port,
696
699
  offline,
697
700
  passthroughPort: secondaryServerPort || settings.port,
701
+ settings,
698
702
  projectDir,
699
703
  siteInfo,
700
704
  accountId,