netlify-cli 16.1.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/README.md +0 -12
- package/npm-shrinkwrap.json +2124 -577
- package/package.json +9 -9
- package/src/commands/dev/dev.mjs +2 -1
- package/src/commands/lm/lm-info.mjs +1 -1
- package/src/commands/lm/lm-install.mjs +1 -1
- package/src/commands/lm/lm-setup.mjs +1 -1
- package/src/commands/lm/lm.mjs +4 -2
- package/src/commands/serve/serve.mjs +3 -1
- package/src/lib/edge-functions/bootstrap.mjs +1 -1
- package/src/lib/edge-functions/headers.mjs +3 -0
- package/src/lib/edge-functions/proxy.mjs +6 -10
- package/src/lib/edge-functions/registry.mjs +34 -14
- package/src/lib/functions/netlify-function.mjs +30 -0
- package/src/lib/functions/registry.mjs +10 -0
- package/src/lib/functions/runtimes/js/builders/zisi.mjs +2 -1
- package/src/lib/functions/server.mjs +22 -5
- package/src/utils/headers.mjs +2 -0
- package/src/utils/proxy-server.mjs +3 -0
- package/src/utils/proxy.mjs +56 -8
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.
|
|
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.
|
|
48
|
-
"@netlify/build-info": "7.
|
|
49
|
-
"@netlify/config": "20.8.
|
|
50
|
-
"@netlify/edge-bundler": "8.
|
|
47
|
+
"@netlify/build": "29.20.12",
|
|
48
|
+
"@netlify/build-info": "7.8.0",
|
|
49
|
+
"@netlify/config": "20.8.1",
|
|
50
|
+
"@netlify/edge-bundler": "8.19.0",
|
|
51
51
|
"@netlify/local-functions-proxy": "1.1.1",
|
|
52
|
-
"@netlify/serverless-functions-api": "1.
|
|
53
|
-
"@netlify/zip-it-and-ship-it": "9.
|
|
52
|
+
"@netlify/serverless-functions-api": "1.7.3",
|
|
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",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"isexe": "2.0.0",
|
|
107
107
|
"jsonwebtoken": "9.0.1",
|
|
108
108
|
"jwt-decode": "3.1.2",
|
|
109
|
-
"lambda-local": "2.1.
|
|
109
|
+
"lambda-local": "2.1.2",
|
|
110
110
|
"listr": "0.14.3",
|
|
111
111
|
"locate-path": "7.2.0",
|
|
112
112
|
"lodash": "4.17.21",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"multiparty": "4.2.3",
|
|
117
117
|
"netlify": "13.1.10",
|
|
118
118
|
"netlify-headers-parser": "7.1.2",
|
|
119
|
-
"netlify-redirect-parser": "14.
|
|
119
|
+
"netlify-redirect-parser": "14.2.0",
|
|
120
120
|
"netlify-redirector": "0.4.0",
|
|
121
121
|
"node-fetch": "2.6.12",
|
|
122
122
|
"node-version-alias": "3.4.1",
|
package/src/commands/dev/dev.mjs
CHANGED
|
@@ -161,7 +161,7 @@ const dev = async (options, command) => {
|
|
|
161
161
|
},
|
|
162
162
|
})
|
|
163
163
|
|
|
164
|
-
await startFunctionsServer({
|
|
164
|
+
const functionsRegistry = await startFunctionsServer({
|
|
165
165
|
api,
|
|
166
166
|
command,
|
|
167
167
|
config,
|
|
@@ -217,6 +217,7 @@ const dev = async (options, command) => {
|
|
|
217
217
|
geolocationMode: options.geo,
|
|
218
218
|
geoCountry: options.country,
|
|
219
219
|
accountId,
|
|
220
|
+
functionsRegistry,
|
|
220
221
|
})
|
|
221
222
|
|
|
222
223
|
if (devConfig.autoLaunch !== false) {
|
|
@@ -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')
|
package/src/commands/lm/lm.mjs
CHANGED
|
@@ -25,8 +25,10 @@ export const createLmCommand = (program) => {
|
|
|
25
25
|
createLmUninstallCommand(program)
|
|
26
26
|
|
|
27
27
|
program
|
|
28
|
-
.command('lm')
|
|
29
|
-
.description(
|
|
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
|
}
|
|
@@ -93,7 +93,7 @@ const serve = async (options, command) => {
|
|
|
93
93
|
options,
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
-
await startFunctionsServer({
|
|
96
|
+
const functionsRegistry = await startFunctionsServer({
|
|
97
97
|
api,
|
|
98
98
|
command,
|
|
99
99
|
config,
|
|
@@ -132,7 +132,9 @@ const serve = async (options, command) => {
|
|
|
132
132
|
addonsUrls,
|
|
133
133
|
config,
|
|
134
134
|
configPath: configPathOverride,
|
|
135
|
+
debug: options.debug,
|
|
135
136
|
env,
|
|
137
|
+
functionsRegistry,
|
|
136
138
|
geolocationMode: options.geo,
|
|
137
139
|
geoCountry: options.country,
|
|
138
140
|
getUpdatedConfig,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { env } from 'process'
|
|
2
2
|
|
|
3
|
-
const latestBootstrapURL = 'https://
|
|
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.
|
|
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,
|
|
@@ -318,23 +319,42 @@ export class EdgeFunctionsRegistry {
|
|
|
318
319
|
functions: this.#functions,
|
|
319
320
|
featureFlags,
|
|
320
321
|
})
|
|
321
|
-
const invocationMetadata = {
|
|
322
|
-
function_config: manifest.function_config,
|
|
323
|
-
routes: manifest.routes.map((route) => ({ function: route.function, pattern: route.pattern })),
|
|
324
|
-
}
|
|
325
322
|
const routes = [...manifest.routes, ...manifest.post_cache_routes].map((route) => ({
|
|
326
323
|
...route,
|
|
327
324
|
pattern: new RegExp(route.pattern),
|
|
328
325
|
}))
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
326
|
+
|
|
327
|
+
/** @type string[] */
|
|
328
|
+
const functionNames = []
|
|
329
|
+
|
|
330
|
+
/** @type number[] */
|
|
331
|
+
const routeIndexes = []
|
|
332
|
+
|
|
333
|
+
routes.forEach((route, index) => {
|
|
334
|
+
if (route.methods && route.methods.length !== 0 && !route.methods.includes(method)) {
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!route.pattern.test(urlPath)) {
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const isExcluded = manifest.function_config[route.function]?.excluded_patterns?.some((pattern) =>
|
|
343
|
+
new RegExp(pattern).test(urlPath),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
if (isExcluded) {
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
functionNames.push(route.function)
|
|
351
|
+
routeIndexes.push(index)
|
|
352
|
+
})
|
|
353
|
+
const invocationMetadata = {
|
|
354
|
+
function_config: manifest.function_config,
|
|
355
|
+
req_routes: routeIndexes,
|
|
356
|
+
routes: manifest.routes.map((route) => ({ function: route.function, path: route.path, pattern: route.pattern })),
|
|
357
|
+
}
|
|
338
358
|
const orphanedDeclarations = this.#matchURLPathAgainstOrphanedDeclarations(urlPath)
|
|
339
359
|
|
|
340
360
|
return { functionNames, invocationMetadata, orphanedDeclarations }
|
|
@@ -158,6 +158,36 @@ export default class NetlifyFunction {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
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) {
|
|
168
|
+
await this.buildQueue
|
|
169
|
+
|
|
170
|
+
const path = (rawPath.endsWith('/') ? rawPath.slice(0, -1) : rawPath).toLowerCase()
|
|
171
|
+
const { routes = [] } = this.buildData
|
|
172
|
+
return routes.find(({ expression, literal, methods }) => {
|
|
173
|
+
if (methods.length !== 0 && !methods.includes(method)) {
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (literal !== undefined) {
|
|
178
|
+
return path === literal
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (expression !== undefined) {
|
|
182
|
+
const regex = new RegExp(expression)
|
|
183
|
+
|
|
184
|
+
return regex.test(path)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return false
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
161
191
|
get url() {
|
|
162
192
|
// This line fixes the issue here https://github.com/netlify/cli/issues/4116
|
|
163
193
|
// Not sure why `settings.port` was used here nor does a valid reference exist.
|
|
@@ -122,6 +122,16 @@ export class FunctionsRegistry {
|
|
|
122
122
|
return this.functions.get(name)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
async getFunctionForURLPath(urlPath, method) {
|
|
126
|
+
for (const func of this.functions.values()) {
|
|
127
|
+
const route = await func.matchURLPath(urlPath, method)
|
|
128
|
+
|
|
129
|
+
if (route) {
|
|
130
|
+
return { func, route }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
125
135
|
async registerFunction(name, funcBeforeHook) {
|
|
126
136
|
const { runtime } = funcBeforeHook
|
|
127
137
|
|
|
@@ -58,6 +58,7 @@ const buildFunction = async ({
|
|
|
58
58
|
includedFiles,
|
|
59
59
|
inputs,
|
|
60
60
|
path: functionPath,
|
|
61
|
+
routes,
|
|
61
62
|
runtimeAPIVersion,
|
|
62
63
|
schedule,
|
|
63
64
|
} = await memoizedBuild({
|
|
@@ -81,7 +82,7 @@ const buildFunction = async ({
|
|
|
81
82
|
|
|
82
83
|
clearFunctionsCache(targetDirectory)
|
|
83
84
|
|
|
84
|
-
return { buildPath, includedFiles, runtimeAPIVersion, srcFiles, schedule }
|
|
85
|
+
return { buildPath, includedFiles, routes, runtimeAPIVersion, srcFiles, schedule }
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
/**
|
|
@@ -7,6 +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, NFFunctionRoute } from '../../utils/headers.mjs'
|
|
10
11
|
import { headers as efHeaders } from '../edge-functions/headers.mjs'
|
|
11
12
|
import { getGeoLocation } from '../geo-location.mjs'
|
|
12
13
|
|
|
@@ -55,9 +56,22 @@ export const createHandler = function (options) {
|
|
|
55
56
|
const { functionsRegistry } = options
|
|
56
57
|
|
|
57
58
|
return async function handler(request, response) {
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// If these headers are set, it means we've already matched a function and we
|
|
60
|
+
// can just grab its name directly. We delete the header from the request
|
|
61
|
+
// because we don't want to expose it to user code.
|
|
62
|
+
let functionName = request.header(NFFunctionName)
|
|
63
|
+
delete request.headers[NFFunctionName]
|
|
64
|
+
const functionRoute = request.header(NFFunctionRoute)
|
|
65
|
+
delete request.headers[NFFunctionRoute]
|
|
66
|
+
|
|
67
|
+
// If we didn't match a function with a custom route, let's try to match
|
|
68
|
+
// using the fixed URL format.
|
|
69
|
+
if (!functionName) {
|
|
70
|
+
const cleanPath = request.path.replace(/^\/.netlify\/(functions|builders)/, '')
|
|
71
|
+
|
|
72
|
+
functionName = cleanPath.split('/').find(Boolean)
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
const func = functionsRegistry.get(functionName)
|
|
62
76
|
|
|
63
77
|
if (func === undefined) {
|
|
@@ -136,6 +150,7 @@ export const createHandler = function (options) {
|
|
|
136
150
|
isBase64Encoded,
|
|
137
151
|
rawUrl,
|
|
138
152
|
rawQuery,
|
|
153
|
+
route: functionRoute,
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
const clientContext = buildClientContext(request.headers) || {}
|
|
@@ -231,7 +246,7 @@ const getFunctionsServer = (options) => {
|
|
|
231
246
|
* @param {*} options.site
|
|
232
247
|
* @param {string} options.siteUrl
|
|
233
248
|
* @param {*} options.timeouts
|
|
234
|
-
* @returns
|
|
249
|
+
* @returns {Promise<import('./registry.mjs').FunctionsRegistry | undefined>}
|
|
235
250
|
*/
|
|
236
251
|
export const startFunctionsServer = async (options) => {
|
|
237
252
|
const { capabilities, command, config, debug, loadDistFunctions, settings, site, siteUrl, timeouts } = options
|
|
@@ -272,9 +287,11 @@ export const startFunctionsServer = async (options) => {
|
|
|
272
287
|
|
|
273
288
|
await functionsRegistry.scan(functionsDirectories)
|
|
274
289
|
|
|
275
|
-
const server =
|
|
290
|
+
const server = getFunctionsServer(Object.assign(options, { functionsRegistry }))
|
|
276
291
|
|
|
277
292
|
await startWebServer({ server, settings, debug })
|
|
293
|
+
|
|
294
|
+
return functionsRegistry
|
|
278
295
|
}
|
|
279
296
|
|
|
280
297
|
/**
|
package/src/utils/headers.mjs
CHANGED
|
@@ -52,6 +52,7 @@ export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => {
|
|
|
52
52
|
* @param {*} params.siteInfo
|
|
53
53
|
* @param {string} params.projectDir
|
|
54
54
|
* @param {import('./state-config.mjs').default} params.state
|
|
55
|
+
* @param {import('../lib/functions/registry.mjs').FunctionsRegistry=} params.functionsRegistry
|
|
55
56
|
* @returns
|
|
56
57
|
*/
|
|
57
58
|
export const startProxyServer = async ({
|
|
@@ -61,6 +62,7 @@ export const startProxyServer = async ({
|
|
|
61
62
|
configPath,
|
|
62
63
|
debug,
|
|
63
64
|
env,
|
|
65
|
+
functionsRegistry,
|
|
64
66
|
geoCountry,
|
|
65
67
|
geolocationMode,
|
|
66
68
|
getUpdatedConfig,
|
|
@@ -78,6 +80,7 @@ export const startProxyServer = async ({
|
|
|
78
80
|
configPath: configPath || site.configPath,
|
|
79
81
|
debug,
|
|
80
82
|
env,
|
|
83
|
+
functionsRegistry,
|
|
81
84
|
geolocationMode,
|
|
82
85
|
geoCountry,
|
|
83
86
|
getUpdatedConfig,
|
package/src/utils/proxy.mjs
CHANGED
|
@@ -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, 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'
|
|
@@ -181,7 +181,7 @@ const alternativePathsFor = function (url) {
|
|
|
181
181
|
return paths
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
const serveRedirect = async function ({ env, match, options, proxy, req, res, siteInfo }) {
|
|
184
|
+
const serveRedirect = async function ({ env, functionsRegistry, match, options, proxy, req, res, siteInfo }) {
|
|
185
185
|
if (!match) return proxy.web(req, res, options)
|
|
186
186
|
|
|
187
187
|
options = options || req.proxyOptions || {}
|
|
@@ -214,6 +214,7 @@ const serveRedirect = async function ({ env, match, options, proxy, req, res, si
|
|
|
214
214
|
if (isFunction(options.functionsPort, req.url)) {
|
|
215
215
|
return proxy.web(req, res, { target: options.functionsServer })
|
|
216
216
|
}
|
|
217
|
+
|
|
217
218
|
const urlForAddons = getAddonUrl(options.addonsUrls, req)
|
|
218
219
|
if (urlForAddons) {
|
|
219
220
|
return handleAddonUrl({ req, res, addonUrl: urlForAddons })
|
|
@@ -327,22 +328,31 @@ const serveRedirect = async function ({ env, match, options, proxy, req, res, si
|
|
|
327
328
|
return proxy.web(req, res, { target: options.functionsServer })
|
|
328
329
|
}
|
|
329
330
|
|
|
331
|
+
const functionWithCustomRoute =
|
|
332
|
+
functionsRegistry && (await functionsRegistry.getFunctionForURLPath(destURL, req.method))
|
|
330
333
|
const destStaticFile = await getStatic(dest.pathname, options.publicFolder)
|
|
331
334
|
let statusValue
|
|
332
|
-
if (
|
|
335
|
+
if (
|
|
336
|
+
match.force ||
|
|
337
|
+
(!staticFile && ((!options.framework && destStaticFile) || isInternal(destURL) || functionWithCustomRoute))
|
|
338
|
+
) {
|
|
333
339
|
req.url = destStaticFile ? destStaticFile + dest.search : destURL
|
|
334
340
|
const { status } = match
|
|
335
341
|
statusValue = status
|
|
336
342
|
console.log(`${NETLIFYDEVLOG} Rewrote URL to`, req.url)
|
|
337
343
|
}
|
|
338
344
|
|
|
339
|
-
if (isFunction(options.functionsPort, req.url)) {
|
|
345
|
+
if (isFunction(options.functionsPort, req.url) || functionWithCustomRoute) {
|
|
346
|
+
const functionHeaders = functionWithCustomRoute
|
|
347
|
+
? { [NFFunctionName]: functionWithCustomRoute.func.name, [NFFunctionRoute]: functionWithCustomRoute.route }
|
|
348
|
+
: {}
|
|
340
349
|
const url = reqToURL(req, originalURL)
|
|
341
350
|
req.headers['x-netlify-original-pathname'] = url.pathname
|
|
342
351
|
req.headers['x-netlify-original-search'] = url.search
|
|
343
352
|
|
|
344
|
-
return proxy.web(req, res, { target: options.functionsServer })
|
|
353
|
+
return proxy.web(req, res, { headers: functionHeaders, target: options.functionsServer })
|
|
345
354
|
}
|
|
355
|
+
|
|
346
356
|
const addonUrl = getAddonUrl(options.addonsUrls, req)
|
|
347
357
|
if (addonUrl) {
|
|
348
358
|
return handleAddonUrl({ req, res, addonUrl })
|
|
@@ -434,12 +444,22 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
|
|
|
434
444
|
}
|
|
435
445
|
|
|
436
446
|
if (proxyRes.statusCode === 404 || proxyRes.statusCode === 403) {
|
|
447
|
+
// If a request for `/path` has failed, we'll a few variations like
|
|
448
|
+
// `/path/index.html` to mimic the CDN behavior.
|
|
437
449
|
if (req.alternativePaths && req.alternativePaths.length !== 0) {
|
|
438
450
|
req.url = req.alternativePaths.shift()
|
|
439
451
|
return proxy.web(req, res, req.proxyOptions)
|
|
440
452
|
}
|
|
453
|
+
|
|
454
|
+
// The request has failed but we might still have a matching redirect
|
|
455
|
+
// rule (without `force`) that should kick in. This is how we mimic the
|
|
456
|
+
// file shadowing behavior from the CDN.
|
|
441
457
|
if (req.proxyOptions && req.proxyOptions.match) {
|
|
442
458
|
return serveRedirect({
|
|
459
|
+
// We don't want to match functions at this point because any redirects
|
|
460
|
+
// to functions will have already been processed, so we don't supply a
|
|
461
|
+
// functions registry to `serveRedirect`.
|
|
462
|
+
functionsRegistry: null,
|
|
443
463
|
req,
|
|
444
464
|
res,
|
|
445
465
|
proxy: handlers,
|
|
@@ -453,7 +473,19 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
|
|
|
453
473
|
|
|
454
474
|
if (req.proxyOptions.staticFile && isRedirect({ status: proxyRes.statusCode }) && proxyRes.headers.location) {
|
|
455
475
|
req.url = proxyRes.headers.location
|
|
456
|
-
return serveRedirect({
|
|
476
|
+
return serveRedirect({
|
|
477
|
+
// We don't want to match functions at this point because any redirects
|
|
478
|
+
// to functions will have already been processed, so we don't supply a
|
|
479
|
+
// functions registry to `serveRedirect`.
|
|
480
|
+
functionsRegistry: null,
|
|
481
|
+
req,
|
|
482
|
+
res,
|
|
483
|
+
proxy: handlers,
|
|
484
|
+
match: null,
|
|
485
|
+
options: req.proxyOptions,
|
|
486
|
+
siteInfo,
|
|
487
|
+
env,
|
|
488
|
+
})
|
|
457
489
|
}
|
|
458
490
|
|
|
459
491
|
const responseData = []
|
|
@@ -551,7 +583,7 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
|
|
|
551
583
|
}
|
|
552
584
|
|
|
553
585
|
const onRequest = async (
|
|
554
|
-
{ addonsUrls, edgeFunctionsProxy, env, functionsServer, proxy, rewriter, settings, siteInfo },
|
|
586
|
+
{ addonsUrls, edgeFunctionsProxy, env, functionsRegistry, functionsServer, proxy, rewriter, settings, siteInfo },
|
|
555
587
|
req,
|
|
556
588
|
res,
|
|
557
589
|
) => {
|
|
@@ -565,9 +597,22 @@ const onRequest = async (
|
|
|
565
597
|
return proxy.web(req, res, { target: edgeFunctionsProxyURL })
|
|
566
598
|
}
|
|
567
599
|
|
|
600
|
+
// Does the request match a function on the fixed URL path?
|
|
568
601
|
if (isFunction(settings.functionsPort, req.url)) {
|
|
569
602
|
return proxy.web(req, res, { target: functionsServer })
|
|
570
603
|
}
|
|
604
|
+
|
|
605
|
+
// Does the request match a function on a custom URL path?
|
|
606
|
+
const functionMatch = functionsRegistry ? await functionsRegistry.getFunctionForURLPath(req.url, req.method) : null
|
|
607
|
+
|
|
608
|
+
if (functionMatch) {
|
|
609
|
+
// Setting an internal header with the function name so that we don't
|
|
610
|
+
// have to match the URL again in the functions server.
|
|
611
|
+
const headers = { [NFFunctionName]: functionMatch.func.name, [NFFunctionRoute]: functionMatch.route.pattern }
|
|
612
|
+
|
|
613
|
+
return proxy.web(req, res, { headers, target: functionsServer })
|
|
614
|
+
}
|
|
615
|
+
|
|
571
616
|
const addonUrl = getAddonUrl(addonsUrls, req)
|
|
572
617
|
if (addonUrl) {
|
|
573
618
|
return handleAddonUrl({ req, res, addonUrl })
|
|
@@ -591,7 +636,7 @@ const onRequest = async (
|
|
|
591
636
|
// We don't want to generate an ETag for 3xx redirects.
|
|
592
637
|
req[shouldGenerateETag] = ({ statusCode }) => statusCode < 300 || statusCode >= 400
|
|
593
638
|
|
|
594
|
-
return serveRedirect({ req, res, proxy, match, options, siteInfo, env })
|
|
639
|
+
return serveRedirect({ req, res, proxy, match, options, siteInfo, env, functionsRegistry })
|
|
595
640
|
}
|
|
596
641
|
|
|
597
642
|
// The request will be served by the framework server, which means we want to
|
|
@@ -628,6 +673,7 @@ export const startProxy = async function ({
|
|
|
628
673
|
configPath,
|
|
629
674
|
debug,
|
|
630
675
|
env,
|
|
676
|
+
functionsRegistry,
|
|
631
677
|
geoCountry,
|
|
632
678
|
geolocationMode,
|
|
633
679
|
getUpdatedConfig,
|
|
@@ -652,6 +698,7 @@ export const startProxy = async function ({
|
|
|
652
698
|
mainPort: settings.port,
|
|
653
699
|
offline,
|
|
654
700
|
passthroughPort: secondaryServerPort || settings.port,
|
|
701
|
+
settings,
|
|
655
702
|
projectDir,
|
|
656
703
|
siteInfo,
|
|
657
704
|
accountId,
|
|
@@ -681,6 +728,7 @@ export const startProxy = async function ({
|
|
|
681
728
|
rewriter,
|
|
682
729
|
settings,
|
|
683
730
|
addonsUrls,
|
|
731
|
+
functionsRegistry,
|
|
684
732
|
functionsServer,
|
|
685
733
|
edgeFunctionsProxy,
|
|
686
734
|
siteInfo,
|