netlify-cli 16.5.0 → 16.6.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/npm-shrinkwrap.json +12864 -11321
- package/package.json +5 -5
- package/src/lib/functions/form-submissions-handler.mjs +6 -1
- package/src/lib/functions/netlify-function.mjs +52 -2
- package/src/lib/functions/registry.mjs +308 -37
- package/src/lib/functions/runtimes/js/builders/zisi.mjs +3 -1
- package/src/lib/functions/runtimes/js/index.mjs +1 -7
- package/src/lib/functions/server.mjs +20 -2
- package/src/lib/functions/synchronous.mjs +72 -16
- package/src/utils/command-helpers.mjs +7 -1
- package/src/utils/functions/functions.mjs +6 -0
- package/src/utils/proxy.mjs +16 -16
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { Buffer } from 'buffer'
|
|
3
|
+
import { promises as fs } from 'fs'
|
|
3
4
|
|
|
4
5
|
import express from 'express'
|
|
5
6
|
import expressLogging from 'express-logging'
|
|
6
7
|
import jwtDecode from 'jwt-decode'
|
|
7
8
|
|
|
8
9
|
import { NETLIFYDEVERR, NETLIFYDEVLOG, error as errorExit, log } from '../../utils/command-helpers.mjs'
|
|
9
|
-
import {
|
|
10
|
+
import { isFeatureFlagEnabled } from '../../utils/feature-flags.mjs'
|
|
11
|
+
import {
|
|
12
|
+
CLOCKWORK_USERAGENT,
|
|
13
|
+
getFunctionsDistPath,
|
|
14
|
+
getFunctionsServePath,
|
|
15
|
+
getInternalFunctionsDir,
|
|
16
|
+
} from '../../utils/functions/index.mjs'
|
|
10
17
|
import { NFFunctionName, NFFunctionRoute } from '../../utils/headers.mjs'
|
|
11
18
|
import { headers as efHeaders } from '../edge-functions/headers.mjs'
|
|
12
19
|
import { getGeoLocation } from '../geo-location.mjs'
|
|
@@ -244,12 +251,14 @@ const getFunctionsServer = (options) => {
|
|
|
244
251
|
* @param {*} options.loadDistFunctions
|
|
245
252
|
* @param {*} options.settings
|
|
246
253
|
* @param {*} options.site
|
|
254
|
+
* @param {*} options.siteInfo
|
|
247
255
|
* @param {string} options.siteUrl
|
|
248
256
|
* @param {*} options.timeouts
|
|
249
257
|
* @returns {Promise<import('./registry.mjs').FunctionsRegistry | undefined>}
|
|
250
258
|
*/
|
|
251
259
|
export const startFunctionsServer = async (options) => {
|
|
252
|
-
const { capabilities, command, config, debug, loadDistFunctions, settings, site, siteUrl, timeouts } =
|
|
260
|
+
const { capabilities, command, config, debug, loadDistFunctions, settings, site, siteInfo, siteUrl, timeouts } =
|
|
261
|
+
options
|
|
253
262
|
const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root })
|
|
254
263
|
const functionsDirectories = []
|
|
255
264
|
|
|
@@ -270,6 +279,14 @@ export const startFunctionsServer = async (options) => {
|
|
|
270
279
|
functionsDirectories.push(...sourceDirectories)
|
|
271
280
|
}
|
|
272
281
|
|
|
282
|
+
try {
|
|
283
|
+
const functionsServePath = getFunctionsServePath({ base: site.root })
|
|
284
|
+
|
|
285
|
+
await fs.rm(functionsServePath, { force: true, recursive: true })
|
|
286
|
+
} catch {
|
|
287
|
+
// no-op
|
|
288
|
+
}
|
|
289
|
+
|
|
273
290
|
if (functionsDirectories.length === 0) {
|
|
274
291
|
return
|
|
275
292
|
}
|
|
@@ -279,6 +296,7 @@ export const startFunctionsServer = async (options) => {
|
|
|
279
296
|
config,
|
|
280
297
|
debug,
|
|
281
298
|
isConnected: Boolean(siteUrl),
|
|
299
|
+
logLambdaCompat: isFeatureFlagEnabled('cli_log_lambda_compat', siteInfo),
|
|
282
300
|
// functions always need to be inside the packagePath if set inside a monorepo
|
|
283
301
|
projectRoot: command.workingDir,
|
|
284
302
|
settings,
|
|
@@ -3,11 +3,18 @@ import { Buffer } from 'buffer'
|
|
|
3
3
|
|
|
4
4
|
import { isStream } from 'is-stream'
|
|
5
5
|
|
|
6
|
-
import { chalk,
|
|
6
|
+
import { chalk, logPadded, NETLIFYDEVERR } from '../../utils/command-helpers.mjs'
|
|
7
7
|
import renderErrorTemplate from '../render-error-template.mjs'
|
|
8
8
|
|
|
9
9
|
import { detectAwsSdkError } from './utils.mjs'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @typedef InvocationError
|
|
13
|
+
* @property {string} errorType
|
|
14
|
+
* @property {string} errorMessage
|
|
15
|
+
* @property {Array<string>} stackTrace
|
|
16
|
+
*/
|
|
17
|
+
|
|
11
18
|
const addHeaders = (headers, response) => {
|
|
12
19
|
if (!headers) {
|
|
13
20
|
return
|
|
@@ -26,12 +33,21 @@ export const handleSynchronousFunction = function ({
|
|
|
26
33
|
result,
|
|
27
34
|
}) {
|
|
28
35
|
if (invocationError) {
|
|
36
|
+
const error = getNormalizedError(invocationError)
|
|
37
|
+
|
|
38
|
+
logPadded(
|
|
39
|
+
`${NETLIFYDEVERR} Function ${chalk.yellow(functionName)} has returned an error: ${
|
|
40
|
+
error.errorMessage
|
|
41
|
+
}\n${chalk.dim(error.stackTrace.join('\n'))}`,
|
|
42
|
+
)
|
|
43
|
+
|
|
29
44
|
return handleErr(invocationError, request, response)
|
|
30
45
|
}
|
|
31
46
|
|
|
32
47
|
const { error } = validateLambdaResponse(result)
|
|
33
48
|
if (error) {
|
|
34
|
-
|
|
49
|
+
logPadded(`${NETLIFYDEVERR} ${error}`)
|
|
50
|
+
|
|
35
51
|
return handleErr(error, request, response)
|
|
36
52
|
}
|
|
37
53
|
|
|
@@ -41,9 +57,13 @@ export const handleSynchronousFunction = function ({
|
|
|
41
57
|
addHeaders(result.headers, response)
|
|
42
58
|
addHeaders(result.multiValueHeaders, response)
|
|
43
59
|
} catch (headersError) {
|
|
44
|
-
|
|
60
|
+
const normalizedError = getNormalizedError(headersError)
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
logPadded(
|
|
63
|
+
`${NETLIFYDEVERR} Failed to set header in function ${chalk.yellow(functionName)}: ${
|
|
64
|
+
normalizedError.errorMessage
|
|
65
|
+
}`,
|
|
66
|
+
)
|
|
47
67
|
|
|
48
68
|
return handleErr(headersError, request, response)
|
|
49
69
|
}
|
|
@@ -60,20 +80,56 @@ export const handleSynchronousFunction = function ({
|
|
|
60
80
|
response.end()
|
|
61
81
|
}
|
|
62
82
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Accepts an error generated by `lambda-local` or an instance of `Error` and
|
|
85
|
+
* returns a normalized error that we can treat in the same way.
|
|
86
|
+
*
|
|
87
|
+
* @param {InvocationError|Error} error
|
|
88
|
+
* @returns {InvocationError}
|
|
89
|
+
*/
|
|
90
|
+
const getNormalizedError = (error) => {
|
|
91
|
+
if (error instanceof Error) {
|
|
92
|
+
const normalizedError = {
|
|
93
|
+
errorMessage: error.message,
|
|
94
|
+
errorType: error.name,
|
|
95
|
+
stackTrace: error.stack ? error.stack.split('\n') : [],
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if ('code' in error && error.code === 'ERR_REQUIRE_ESM') {
|
|
99
|
+
return {
|
|
100
|
+
...normalizedError,
|
|
101
|
+
errorMessage:
|
|
102
|
+
'a CommonJS file cannot import ES modules. Consider switching your function to ES modules. For more information, refer to https://ntl.fyi/functions-runtime.',
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return normalizedError
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Formatting stack trace lines in the same way that Node.js formats native
|
|
110
|
+
// errors.
|
|
111
|
+
const stackTrace = error.stackTrace.map((line) => ` at ${line}`)
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
errorType: error.errorType,
|
|
115
|
+
errorMessage: error.errorMessage,
|
|
116
|
+
stackTrace,
|
|
117
|
+
}
|
|
67
118
|
}
|
|
68
119
|
|
|
69
|
-
const formatLambdaLocalError = (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
120
|
+
const formatLambdaLocalError = (rawError, acceptsHTML) => {
|
|
121
|
+
const error = getNormalizedError(rawError)
|
|
122
|
+
|
|
123
|
+
if (acceptsHTML) {
|
|
124
|
+
return JSON.stringify({
|
|
125
|
+
...error,
|
|
126
|
+
stackTrace: undefined,
|
|
127
|
+
trace: error.stackTrace,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return `${error.errorType}: ${error.errorMessage}\n ${error.stackTrace.join('\n')}`
|
|
132
|
+
}
|
|
77
133
|
|
|
78
134
|
const handleErr = async (err, request, response) => {
|
|
79
135
|
detectAwsSdkError({ err })
|
|
@@ -163,6 +163,12 @@ export const log = (message = '', ...args) => {
|
|
|
163
163
|
process.stdout.write(`${format(message, ...args)}\n`)
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
export const logPadded = (message = '', ...args) => {
|
|
167
|
+
log('')
|
|
168
|
+
log(message, ...args)
|
|
169
|
+
log('')
|
|
170
|
+
}
|
|
171
|
+
|
|
166
172
|
/**
|
|
167
173
|
* logs a warning message
|
|
168
174
|
* @param {string} message
|
|
@@ -262,4 +268,4 @@ export const watchDebounced = async (
|
|
|
262
268
|
return watcher
|
|
263
269
|
}
|
|
264
270
|
|
|
265
|
-
export const getTerminalLink = (text, url) => terminalLink(text, url, { fallback: () => `${text} ${url}` })
|
|
271
|
+
export const getTerminalLink = (text, url) => terminalLink(text, url, { fallback: () => `${text} (${url})` })
|
|
@@ -33,6 +33,12 @@ export const getFunctionsDistPath = async ({ base, packagePath = '' }) => {
|
|
|
33
33
|
return isDirectory ? path : null
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export const getFunctionsServePath = ({ base, packagePath = '' }) => {
|
|
37
|
+
const path = resolve(base, packagePath, getPathInProject([SERVE_FUNCTIONS_FOLDER]))
|
|
38
|
+
|
|
39
|
+
return path
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* Retrieves the internal functions directory and creates it if ensureExists is provided
|
|
38
44
|
* @param {object} config
|
package/src/utils/proxy.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
isEdgeFunctionsRequest,
|
|
28
28
|
} from '../lib/edge-functions/proxy.mjs'
|
|
29
29
|
import { fileExistsAsync, isFileAsync } from '../lib/fs.mjs'
|
|
30
|
+
import { DEFAULT_FUNCTION_URL_EXPRESSION } from '../lib/functions/registry.mjs'
|
|
30
31
|
import renderErrorTemplate from '../lib/render-error-template.mjs'
|
|
31
32
|
|
|
32
33
|
import { NETLIFYDEVLOG, NETLIFYDEVWARN, log, chalk } from './command-helpers.mjs'
|
|
@@ -87,7 +88,7 @@ function isInternal(url) {
|
|
|
87
88
|
* @param {string} url
|
|
88
89
|
*/
|
|
89
90
|
function isFunction(functionsPort, url) {
|
|
90
|
-
return functionsPort && url.match(
|
|
91
|
+
return functionsPort && url.match(DEFAULT_FUNCTION_URL_EXPRESSION)
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
/**
|
|
@@ -328,13 +329,12 @@ const serveRedirect = async function ({ env, functionsRegistry, match, options,
|
|
|
328
329
|
return proxy.web(req, res, { target: options.functionsServer })
|
|
329
330
|
}
|
|
330
331
|
|
|
331
|
-
const
|
|
332
|
-
functionsRegistry && (await functionsRegistry.getFunctionForURLPath(destURL, req.method))
|
|
332
|
+
const matchingFunction = functionsRegistry && (await functionsRegistry.getFunctionForURLPath(destURL, req.method))
|
|
333
333
|
const destStaticFile = await getStatic(dest.pathname, options.publicFolder)
|
|
334
334
|
let statusValue
|
|
335
335
|
if (
|
|
336
336
|
match.force ||
|
|
337
|
-
(!staticFile && ((!options.framework && destStaticFile) || isInternal(destURL) ||
|
|
337
|
+
(!staticFile && ((!options.framework && destStaticFile) || isInternal(destURL) || matchingFunction))
|
|
338
338
|
) {
|
|
339
339
|
req.url = destStaticFile ? destStaticFile + dest.search : destURL
|
|
340
340
|
const { status } = match
|
|
@@ -342,10 +342,11 @@ const serveRedirect = async function ({ env, functionsRegistry, match, options,
|
|
|
342
342
|
console.log(`${NETLIFYDEVLOG} Rewrote URL to`, req.url)
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
if (
|
|
346
|
-
const functionHeaders =
|
|
347
|
-
|
|
348
|
-
:
|
|
345
|
+
if (matchingFunction) {
|
|
346
|
+
const functionHeaders = {
|
|
347
|
+
[NFFunctionName]: matchingFunction.func.name,
|
|
348
|
+
[NFFunctionRoute]: matchingFunction.route,
|
|
349
|
+
}
|
|
349
350
|
const url = reqToURL(req, originalURL)
|
|
350
351
|
req.headers['x-netlify-original-pathname'] = url.pathname
|
|
351
352
|
req.headers['x-netlify-original-search'] = url.search
|
|
@@ -597,18 +598,17 @@ const onRequest = async (
|
|
|
597
598
|
return proxy.web(req, res, { target: edgeFunctionsProxyURL })
|
|
598
599
|
}
|
|
599
600
|
|
|
600
|
-
|
|
601
|
-
if (isFunction(settings.functionsPort, req.url)) {
|
|
602
|
-
return proxy.web(req, res, { target: functionsServer })
|
|
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
|
|
601
|
+
const functionMatch = functionsRegistry && (await functionsRegistry.getFunctionForURLPath(req.url, req.method))
|
|
607
602
|
|
|
608
603
|
if (functionMatch) {
|
|
609
604
|
// Setting an internal header with the function name so that we don't
|
|
610
605
|
// have to match the URL again in the functions server.
|
|
611
|
-
|
|
606
|
+
/** @type {Record<string, string>} */
|
|
607
|
+
const headers = { [NFFunctionName]: functionMatch.func.name }
|
|
608
|
+
|
|
609
|
+
if (functionMatch.route) {
|
|
610
|
+
headers[NFFunctionRoute] = functionMatch.route.pattern
|
|
611
|
+
}
|
|
612
612
|
|
|
613
613
|
return proxy.web(req, res, { headers, target: functionsServer })
|
|
614
614
|
}
|