netlify-cli 17.3.2 → 17.4.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 +2 -138
- package/npm-shrinkwrap.json +76 -76
- package/package.json +16 -15
- package/src/commands/addons/addons-auth.mjs +27 -30
- package/src/commands/addons/addons-config.mjs +145 -154
- package/src/commands/addons/addons-create.mjs +94 -108
- package/src/commands/addons/addons-delete.mjs +36 -41
- package/src/commands/addons/addons-list.mjs +38 -42
- package/src/commands/addons/addons.mjs +26 -28
- package/src/commands/addons/index.mjs +1 -1
- package/src/commands/api/api.mjs +45 -53
- package/src/commands/api/index.mjs +1 -1
- package/src/commands/base-command.mjs +597 -684
- package/src/commands/blobs/blobs-delete.mjs +35 -0
- package/src/commands/blobs/blobs-get.mjs +44 -0
- package/src/commands/blobs/blobs-list.mjs +48 -0
- package/src/commands/blobs/blobs-set.mjs +54 -0
- package/src/commands/blobs/blobs.mjs +32 -0
- package/src/commands/blobs/index.mjs +1 -0
- package/src/commands/build/build.mjs +55 -67
- package/src/commands/build/index.mjs +1 -1
- package/src/commands/completion/completion.mjs +41 -46
- package/src/commands/completion/index.mjs +1 -1
- package/src/commands/deploy/deploy.mjs +675 -710
- package/src/commands/deploy/index.mjs +1 -1
- package/src/commands/dev/dev-exec.mjs +20 -32
- package/src/commands/dev/dev.mjs +217 -302
- package/src/commands/dev/index.mjs +1 -1
- package/src/commands/dev/types.d.ts +30 -0
- package/src/commands/env/env-clone.mjs +157 -184
- package/src/commands/env/env-get.mjs +49 -68
- package/src/commands/env/env-import.mjs +100 -119
- package/src/commands/env/env-list.mjs +104 -129
- package/src/commands/env/env-set.mjs +160 -185
- package/src/commands/env/env-unset.mjs +104 -122
- package/src/commands/env/env.mjs +28 -30
- package/src/commands/env/index.mjs +1 -1
- package/src/commands/functions/functions-build.mjs +29 -41
- package/src/commands/functions/functions-create.mjs +533 -601
- package/src/commands/functions/functions-invoke.mjs +193 -216
- package/src/commands/functions/functions-list.mjs +45 -55
- package/src/commands/functions/functions-serve.mjs +51 -61
- package/src/commands/functions/functions.mjs +26 -32
- package/src/commands/functions/index.mjs +1 -1
- package/src/commands/index.mjs +2 -2
- package/src/commands/init/index.mjs +1 -1
- package/src/commands/init/init.mjs +138 -167
- package/src/commands/integration/deploy.mjs +337 -399
- package/src/commands/integration/index.mjs +12 -13
- package/src/commands/link/index.mjs +1 -1
- package/src/commands/link/link.mjs +298 -317
- package/src/commands/lm/index.mjs +1 -1
- package/src/commands/lm/lm-info.mjs +23 -31
- package/src/commands/lm/lm-install.mjs +13 -17
- package/src/commands/lm/lm-setup.mjs +80 -84
- package/src/commands/lm/lm-uninstall.mjs +7 -12
- package/src/commands/lm/lm.mjs +18 -22
- package/src/commands/login/index.mjs +1 -1
- package/src/commands/login/login.mjs +35 -41
- package/src/commands/logout/index.mjs +1 -1
- package/src/commands/logout/logout.mjs +25 -31
- package/src/commands/main.mjs +166 -201
- package/src/commands/open/index.mjs +1 -1
- package/src/commands/open/open-admin.mjs +15 -18
- package/src/commands/open/open-site.mjs +16 -19
- package/src/commands/open/open.mjs +24 -27
- package/src/commands/recipes/common.mjs +23 -34
- package/src/commands/recipes/index.mjs +1 -1
- package/src/commands/recipes/recipes-list.mjs +13 -20
- package/src/commands/recipes/recipes.mjs +59 -72
- package/src/commands/serve/index.mjs +1 -1
- package/src/commands/serve/serve.mjs +142 -189
- package/src/commands/sites/index.mjs +2 -2
- package/src/commands/sites/sites-create-template.mjs +214 -236
- package/src/commands/sites/sites-create.mjs +145 -157
- package/src/commands/sites/sites-delete.mjs +75 -81
- package/src/commands/sites/sites-list.mjs +63 -66
- package/src/commands/sites/sites.mjs +18 -20
- package/src/commands/status/index.mjs +1 -1
- package/src/commands/status/status-hooks.mjs +32 -34
- package/src/commands/status/status.mjs +99 -106
- package/src/commands/switch/index.mjs +1 -1
- package/src/commands/switch/switch.mjs +32 -37
- package/src/commands/types.d.ts +31 -0
- package/src/commands/unlink/index.mjs +1 -1
- package/src/commands/unlink/unlink.mjs +23 -29
- package/src/commands/watch/index.mjs +1 -1
- package/src/commands/watch/watch.mjs +91 -105
- package/src/functions-templates/javascript/hello/{{name}}.js +2 -3
- package/src/lib/account.mjs +4 -5
- package/src/lib/api.mjs +22 -20
- package/src/lib/blobs/blobs.mjs +36 -45
- package/src/lib/build.mjs +82 -85
- package/src/lib/completion/constants.mjs +2 -4
- package/src/lib/completion/generate-autocompletion.mjs +33 -36
- package/src/lib/completion/get-autocompletion.mjs +31 -35
- package/src/lib/completion/index.mjs +1 -1
- package/src/lib/completion/script.mjs +12 -19
- package/src/lib/edge-functions/bootstrap.mjs +3 -5
- package/src/lib/edge-functions/consts.mjs +9 -10
- package/src/lib/edge-functions/deploy.mjs +28 -34
- package/src/lib/edge-functions/editor-helper.mjs +29 -42
- package/src/lib/edge-functions/headers.mjs +24 -26
- package/src/lib/edge-functions/internal.mjs +38 -44
- package/src/lib/edge-functions/proxy.mjs +229 -228
- package/src/lib/edge-functions/registry.mjs +473 -574
- package/src/lib/exec-fetcher.mjs +115 -122
- package/src/lib/fs.mjs +28 -27
- package/src/lib/functions/background.mjs +16 -20
- package/src/lib/functions/config.mjs +12 -9
- package/src/lib/functions/form-submissions-handler.mjs +143 -149
- package/src/lib/functions/local-proxy.mjs +40 -44
- package/src/lib/functions/memoized-build.mjs +19 -21
- package/src/lib/functions/netlify-function.mjs +269 -249
- package/src/lib/functions/registry.mjs +509 -568
- package/src/lib/functions/runtimes/go/index.mjs +62 -71
- package/src/lib/functions/runtimes/index.mjs +8 -15
- package/src/lib/functions/runtimes/js/builders/netlify-lambda.mjs +55 -64
- package/src/lib/functions/runtimes/js/builders/zisi.mjs +135 -154
- package/src/lib/functions/runtimes/js/constants.mjs +1 -1
- package/src/lib/functions/runtimes/js/index.mjs +92 -109
- package/src/lib/functions/runtimes/js/worker.mjs +43 -45
- package/src/lib/functions/runtimes/rust/index.mjs +64 -73
- package/src/lib/functions/scheduled.mjs +70 -88
- package/src/lib/functions/server.mjs +269 -327
- package/src/lib/functions/synchronous.mjs +118 -147
- package/src/lib/functions/utils.mjs +38 -46
- package/src/lib/geo-location.mjs +69 -81
- package/src/lib/http-agent.mjs +87 -90
- package/src/lib/images/proxy.mjs +97 -99
- package/src/lib/log.mjs +6 -9
- package/src/lib/path.mjs +2 -1
- package/src/lib/render-error-template.mjs +19 -20
- package/src/lib/settings.mjs +17 -19
- package/src/lib/spinner.mjs +21 -23
- package/src/lib/string.mjs +4 -2
- package/src/recipes/vscode/index.mjs +69 -85
- package/src/recipes/vscode/settings.mjs +53 -58
- package/src/utils/addons/compare.mjs +31 -32
- package/src/utils/addons/diffs/index.mjs +16 -17
- package/src/utils/addons/diffs/options.mjs +99 -101
- package/src/utils/addons/prepare.mjs +100 -97
- package/src/utils/addons/prompts.mjs +73 -76
- package/src/utils/addons/render.mjs +33 -36
- package/src/utils/addons/validation.mjs +19 -15
- package/src/utils/banner.mjs +11 -16
- package/src/utils/build-info.mjs +65 -66
- package/src/utils/command-helpers.mjs +185 -199
- package/src/utils/create-deferred.mjs +9 -12
- package/src/utils/create-stream-promise.mjs +54 -47
- package/src/utils/deploy/constants.mjs +9 -11
- package/src/utils/deploy/deploy-site.mjs +162 -182
- package/src/utils/deploy/hash-config.mjs +21 -21
- package/src/utils/deploy/hash-files.mjs +34 -38
- package/src/utils/deploy/hash-fns.mjs +149 -154
- package/src/utils/deploy/hasher-segments.mjs +58 -52
- package/src/utils/deploy/upload-files.mjs +99 -113
- package/src/utils/deploy/util.mjs +85 -91
- package/src/utils/detect-server-settings.mjs +236 -268
- package/src/utils/dev.mjs +163 -178
- package/src/utils/dot-env.mjs +37 -42
- package/src/utils/env/index.mjs +148 -148
- package/src/utils/execa.mjs +9 -13
- package/src/utils/feature-flags.mjs +6 -5
- package/src/utils/framework-server.mjs +43 -52
- package/src/utils/functions/constants.mjs +1 -1
- package/src/utils/functions/functions.mjs +30 -40
- package/src/utils/functions/get-functions.mjs +28 -29
- package/src/utils/functions/index.mjs +3 -3
- package/src/utils/get-global-config.mjs +33 -36
- package/src/utils/get-package-json.mjs +14 -15
- package/src/utils/get-repo-data.mjs +54 -64
- package/src/utils/get-site.mjs +14 -14
- package/src/utils/gh-auth.mjs +79 -100
- package/src/utils/gitignore.mjs +37 -40
- package/src/utils/headers.mjs +33 -35
- package/src/utils/hooks/requires-site-info.mjs +26 -22
- package/src/utils/init/config-github.mjs +207 -219
- package/src/utils/init/config-manual.mjs +83 -100
- package/src/utils/init/config.mjs +25 -26
- package/src/utils/init/node-version.mjs +23 -30
- package/src/utils/init/plugins.mjs +12 -8
- package/src/utils/init/utils.mjs +152 -172
- package/src/utils/live-tunnel.mjs +118 -141
- package/src/utils/lm/install.mjs +220 -259
- package/src/utils/lm/requirements.mjs +54 -63
- package/src/utils/lm/steps.mjs +31 -31
- package/src/utils/lm/ui.mjs +13 -20
- package/src/utils/open-browser.mjs +31 -32
- package/src/utils/parse-raw-flags.mjs +39 -35
- package/src/utils/proxy-server.mjs +84 -71
- package/src/utils/proxy.mjs +696 -750
- package/src/utils/read-repo-url.mjs +48 -47
- package/src/utils/redirects.mjs +49 -49
- package/src/utils/request-id.mjs +2 -4
- package/src/utils/rules-proxy.mjs +96 -100
- package/src/utils/run-build.mjs +109 -132
- package/src/utils/shell.mjs +99 -106
- package/src/utils/sign-redirect.mjs +14 -14
- package/src/utils/sites/utils.mjs +48 -55
- package/src/utils/state-config.mjs +101 -101
- package/src/utils/static-server.mjs +28 -34
- package/src/utils/telemetry/index.mjs +2 -2
- package/src/utils/telemetry/report-error.mjs +45 -49
- package/src/utils/telemetry/request.mjs +36 -43
- package/src/utils/telemetry/telemetry.mjs +90 -105
- package/src/utils/telemetry/utils.mjs +5 -6
- package/src/utils/telemetry/validation.mjs +55 -53
- package/src/utils/types.d.ts +46 -0
- package/src/utils/validation.mjs +10 -13
|
@@ -1,244 +1,203 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from '
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import { handleBackgroundFunction, handleBackgroundFunctionResult } from './background.mjs'
|
|
23
|
-
import { createFormSubmissionHandler } from './form-submissions-handler.mjs'
|
|
24
|
-
import { FunctionsRegistry } from './registry.mjs'
|
|
25
|
-
import { handleScheduledFunction } from './scheduled.mjs'
|
|
26
|
-
import { handleSynchronousFunction } from './synchronous.mjs'
|
|
27
|
-
import { shouldBase64Encode } from './utils.mjs'
|
|
28
|
-
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'expr... Remove this comment to see the full error message
|
|
6
|
+
import expressLogging from 'express-logging';
|
|
7
|
+
import jwtDecode from 'jwt-decode';
|
|
8
|
+
import { NETLIFYDEVERR, NETLIFYDEVLOG, error as errorExit, log } from '../../utils/command-helpers.mjs';
|
|
9
|
+
import { isFeatureFlagEnabled } from '../../utils/feature-flags.mjs';
|
|
10
|
+
import { CLOCKWORK_USERAGENT, getFunctionsDistPath, getFunctionsServePath, getInternalFunctionsDir, } from '../../utils/functions/index.mjs';
|
|
11
|
+
import { NFFunctionName, NFFunctionRoute } from '../../utils/headers.mjs';
|
|
12
|
+
import { headers as efHeaders } from '../edge-functions/headers.mjs';
|
|
13
|
+
import { getGeoLocation } from '../geo-location.mjs';
|
|
14
|
+
import { handleBackgroundFunction, handleBackgroundFunctionResult } from './background.mjs';
|
|
15
|
+
import { createFormSubmissionHandler } from './form-submissions-handler.mjs';
|
|
16
|
+
import { FunctionsRegistry } from './registry.mjs';
|
|
17
|
+
import { handleScheduledFunction } from './scheduled.mjs';
|
|
18
|
+
import { handleSynchronousFunction } from './synchronous.mjs';
|
|
19
|
+
import { shouldBase64Encode } from './utils.mjs';
|
|
20
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'headers' implicitly has an 'any' type.
|
|
29
21
|
const buildClientContext = function (headers) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
22
|
+
// inject a client context based on auth header, ported over from netlify-lambda (https://github.com/netlify/netlify-lambda/pull/57)
|
|
23
|
+
if (!headers.authorization)
|
|
24
|
+
return;
|
|
25
|
+
const parts = headers.authorization.split(' ');
|
|
26
|
+
if (parts.length !== 2 || parts[0] !== 'Bearer')
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
29
|
+
return {
|
|
30
|
+
identity: {
|
|
31
|
+
url: 'https://netlify-dev-locally-emulated-identity.netlify.com/.netlify/identity',
|
|
32
|
+
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb3VyY2UiOiJuZXRsaWZ5IGRldiIsInRlc3REYXRhIjoiTkVUTElGWV9ERVZfTE9DQUxMWV9FTVVMQVRFRF9JREVOVElUWSJ9.2eSDqUOZAOBsx39FHFePjYj12k0LrxldvGnlvDu3GMI',
|
|
33
|
+
// you can decode this with https://jwt.io/
|
|
34
|
+
// just says
|
|
35
|
+
// {
|
|
36
|
+
// "source": "netlify dev",
|
|
37
|
+
// "testData": "NETLIFY_DEV_LOCALLY_EMULATED_IDENTITY"
|
|
38
|
+
// }
|
|
39
|
+
},
|
|
40
|
+
// @ts-expect-error
|
|
41
|
+
user: jwtDecode(parts[1]),
|
|
42
|
+
};
|
|
50
43
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const hasBody = (req) =>
|
|
57
|
-
// copied from is-type package
|
|
58
|
-
// eslint-disable-next-line unicorn/prefer-number-properties
|
|
59
|
-
(req.header('transfer-encoding') !== undefined || !isNaN(req.header('content-length'))) &&
|
|
60
|
-
// we expect a string or a buffer, because we use the two bodyParsers(text, raw) from express
|
|
61
|
-
(typeof req.body === 'string' || Buffer.isBuffer(req.body))
|
|
62
|
-
|
|
63
|
-
export const createHandler = function (options) {
|
|
64
|
-
const { functionsRegistry } = options
|
|
65
|
-
|
|
66
|
-
return async function handler(request, response) {
|
|
67
|
-
// If these headers are set, it means we've already matched a function and we
|
|
68
|
-
// can just grab its name directly. We delete the header from the request
|
|
69
|
-
// because we don't want to expose it to user code.
|
|
70
|
-
let functionName = request.header(NFFunctionName)
|
|
71
|
-
delete request.headers[NFFunctionName]
|
|
72
|
-
const functionRoute = request.header(NFFunctionRoute)
|
|
73
|
-
delete request.headers[NFFunctionRoute]
|
|
74
|
-
|
|
75
|
-
// If we didn't match a function with a custom route, let's try to match
|
|
76
|
-
// using the fixed URL format.
|
|
77
|
-
if (!functionName) {
|
|
78
|
-
const cleanPath = request.path.replace(/^\/.netlify\/(functions|builders)/, '')
|
|
79
|
-
|
|
80
|
-
functionName = cleanPath.split('/').find(Boolean)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const func = functionsRegistry.get(functionName)
|
|
84
|
-
|
|
85
|
-
if (func === undefined) {
|
|
86
|
-
response.statusCode = 404
|
|
87
|
-
response.end('Function not found...')
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!func.hasValidName()) {
|
|
92
|
-
response.statusCode = 400
|
|
93
|
-
response.end('Function name should consist only of alphanumeric characters, hyphen & underscores.')
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const isBase64Encoded = shouldBase64Encode(request.header('content-type'))
|
|
98
|
-
let body
|
|
99
|
-
if (hasBody(request)) {
|
|
100
|
-
body = request.body.toString(isBase64Encoded ? 'base64' : 'utf8')
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let remoteAddress = request.header('x-forwarded-for') || request.connection.remoteAddress || ''
|
|
104
|
-
remoteAddress = remoteAddress
|
|
105
|
-
.split(remoteAddress.includes('.') ? ':' : ',')
|
|
106
|
-
.pop()
|
|
107
|
-
.trim()
|
|
108
|
-
|
|
109
|
-
let requestPath = request.path
|
|
110
|
-
if (request.header('x-netlify-original-pathname')) {
|
|
111
|
-
requestPath = request.header('x-netlify-original-pathname')
|
|
112
|
-
delete request.headers['x-netlify-original-pathname']
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
let requestQuery = request.query
|
|
116
|
-
if (request.header('x-netlify-original-search')) {
|
|
117
|
-
const newRequestQuery = {}
|
|
118
|
-
const searchParams = new URLSearchParams(request.header('x-netlify-original-search'))
|
|
119
|
-
|
|
120
|
-
for (const key of searchParams.keys()) {
|
|
121
|
-
newRequestQuery[key] = searchParams.getAll(key)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
requestQuery = newRequestQuery
|
|
125
|
-
delete request.headers['x-netlify-original-search']
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const queryParams = Object.entries(requestQuery).reduce(
|
|
129
|
-
(prev, [key, value]) => ({ ...prev, [key]: Array.isArray(value) ? value : [value] }),
|
|
130
|
-
{},
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
const geoLocation = await getGeoLocation({ ...options, mode: options.geo })
|
|
134
|
-
|
|
135
|
-
const headers = Object.entries({
|
|
136
|
-
...request.headers,
|
|
137
|
-
'client-ip': [remoteAddress],
|
|
138
|
-
'x-nf-client-connection-ip': [remoteAddress],
|
|
139
|
-
'x-nf-account-id': [options.accountId],
|
|
140
|
-
'x-nf-site-id': [options?.siteInfo?.id],
|
|
141
|
-
[efHeaders.Geo]: Buffer.from(JSON.stringify(geoLocation)).toString('base64'),
|
|
142
|
-
}).reduce((prev, [key, value]) => ({ ...prev, [key]: Array.isArray(value) ? value : [value] }), {})
|
|
143
|
-
const rawQuery = new URLSearchParams(requestQuery).toString()
|
|
144
|
-
const protocol = options.config?.dev?.https ? 'https' : 'http'
|
|
145
|
-
const url = new URL(requestPath, `${protocol}://${request.get('host') || 'localhost'}`)
|
|
146
|
-
url.search = rawQuery
|
|
147
|
-
const rawUrl = url.toString()
|
|
148
|
-
const event = {
|
|
149
|
-
path: requestPath,
|
|
150
|
-
httpMethod: request.method,
|
|
151
|
-
queryStringParameters: Object.entries(queryParams).reduce(
|
|
152
|
-
(prev, [key, value]) => ({ ...prev, [key]: value.join(', ') }),
|
|
153
|
-
{},
|
|
154
|
-
),
|
|
155
|
-
multiValueQueryStringParameters: queryParams,
|
|
156
|
-
headers: Object.entries(headers).reduce((prev, [key, value]) => ({ ...prev, [key]: value.join(', ') }), {}),
|
|
157
|
-
multiValueHeaders: headers,
|
|
158
|
-
body,
|
|
159
|
-
isBase64Encoded,
|
|
160
|
-
rawUrl,
|
|
161
|
-
rawQuery,
|
|
162
|
-
route: functionRoute,
|
|
44
|
+
catch {
|
|
45
|
+
// Ignore errors - bearer token is not a JWT, probably not intended for us
|
|
163
46
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
47
|
+
};
|
|
48
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'req' implicitly has an 'any' type.
|
|
49
|
+
const hasBody = (req) =>
|
|
50
|
+
// copied from is-type package
|
|
51
|
+
// eslint-disable-next-line unicorn/prefer-number-properties
|
|
52
|
+
(req.header('transfer-encoding') !== undefined || !isNaN(req.header('content-length'))) &&
|
|
53
|
+
// we expect a string or a buffer, because we use the two bodyParsers(text, raw) from express
|
|
54
|
+
(typeof req.body === 'string' || Buffer.isBuffer(req.body));
|
|
55
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'options' implicitly has an 'any' type.
|
|
56
|
+
export const createHandler = function (options) {
|
|
57
|
+
const { functionsRegistry } = options;
|
|
58
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'request' implicitly has an 'any' type.
|
|
59
|
+
return async function handler(request, response) {
|
|
60
|
+
// If these headers are set, it means we've already matched a function and we
|
|
61
|
+
// can just grab its name directly. We delete the header from the request
|
|
62
|
+
// because we don't want to expose it to user code.
|
|
63
|
+
let functionName = request.header(NFFunctionName);
|
|
64
|
+
delete request.headers[NFFunctionName];
|
|
65
|
+
const functionRoute = request.header(NFFunctionRoute);
|
|
66
|
+
delete request.headers[NFFunctionRoute];
|
|
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
|
+
functionName = cleanPath.split('/').find(Boolean);
|
|
72
|
+
}
|
|
73
|
+
const func = functionsRegistry.get(functionName);
|
|
74
|
+
if (func === undefined) {
|
|
75
|
+
response.statusCode = 404;
|
|
76
|
+
response.end('Function not found...');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!func.hasValidName()) {
|
|
80
|
+
response.statusCode = 400;
|
|
81
|
+
response.end('Function name should consist only of alphanumeric characters, hyphen & underscores.');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const isBase64Encoded = shouldBase64Encode(request.header('content-type'));
|
|
85
|
+
let body;
|
|
86
|
+
if (hasBody(request)) {
|
|
87
|
+
body = request.body.toString(isBase64Encoded ? 'base64' : 'utf8');
|
|
88
|
+
}
|
|
89
|
+
let remoteAddress = request.header('x-forwarded-for') || request.connection.remoteAddress || '';
|
|
90
|
+
remoteAddress = remoteAddress
|
|
91
|
+
.split(remoteAddress.includes('.') ? ':' : ',')
|
|
92
|
+
.pop()
|
|
93
|
+
.trim();
|
|
94
|
+
let requestPath = request.path;
|
|
95
|
+
if (request.header('x-netlify-original-pathname')) {
|
|
96
|
+
requestPath = request.header('x-netlify-original-pathname');
|
|
97
|
+
delete request.headers['x-netlify-original-pathname'];
|
|
98
|
+
}
|
|
99
|
+
let requestQuery = request.query;
|
|
100
|
+
if (request.header('x-netlify-original-search')) {
|
|
101
|
+
const newRequestQuery = {};
|
|
102
|
+
const searchParams = new URLSearchParams(request.header('x-netlify-original-search'));
|
|
103
|
+
for (const key of searchParams.keys()) {
|
|
104
|
+
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
105
|
+
newRequestQuery[key] = searchParams.getAll(key);
|
|
106
|
+
}
|
|
107
|
+
requestQuery = newRequestQuery;
|
|
108
|
+
delete request.headers['x-netlify-original-search'];
|
|
109
|
+
}
|
|
110
|
+
const queryParams = Object.entries(requestQuery).reduce((prev, [key, value]) => ({ ...prev, [key]: Array.isArray(value) ? value : [value] }), {});
|
|
111
|
+
const geoLocation = await getGeoLocation({ ...options, mode: options.geo });
|
|
112
|
+
const headers = Object.entries({
|
|
113
|
+
...request.headers,
|
|
114
|
+
'client-ip': [remoteAddress],
|
|
115
|
+
'x-nf-client-connection-ip': [remoteAddress],
|
|
116
|
+
'x-nf-account-id': [options.accountId],
|
|
117
|
+
'x-nf-site-id': [options?.siteInfo?.id] ?? 'unlinked',
|
|
118
|
+
[efHeaders.Geo]: Buffer.from(JSON.stringify(geoLocation)).toString('base64'),
|
|
119
|
+
}).reduce((prev, [key, value]) => ({ ...prev, [key]: Array.isArray(value) ? value : [value] }), {});
|
|
120
|
+
const rawQuery = new URLSearchParams(requestQuery).toString();
|
|
121
|
+
const protocol = options.config?.dev?.https ? 'https' : 'http';
|
|
122
|
+
const url = new URL(requestPath, `${protocol}://${request.get('host') || 'localhost'}`);
|
|
123
|
+
url.search = rawQuery;
|
|
124
|
+
const rawUrl = url.toString();
|
|
125
|
+
const event = {
|
|
126
|
+
path: requestPath,
|
|
127
|
+
httpMethod: request.method,
|
|
128
|
+
queryStringParameters: Object.entries(queryParams).reduce(
|
|
129
|
+
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
|
|
130
|
+
(prev, [key, value]) => ({ ...prev, [key]: value.join(', ') }), {}),
|
|
131
|
+
multiValueQueryStringParameters: queryParams,
|
|
132
|
+
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
|
|
133
|
+
headers: Object.entries(headers).reduce((prev, [key, value]) => ({ ...prev, [key]: value.join(', ') }), {}),
|
|
134
|
+
multiValueHeaders: headers,
|
|
135
|
+
body,
|
|
136
|
+
isBase64Encoded,
|
|
137
|
+
rawUrl,
|
|
138
|
+
rawQuery,
|
|
139
|
+
route: functionRoute,
|
|
140
|
+
};
|
|
141
|
+
const clientContext = buildClientContext(request.headers) || {};
|
|
142
|
+
if (func.isBackground) {
|
|
143
|
+
handleBackgroundFunction(functionName, response);
|
|
144
|
+
// background functions do not receive a clientContext
|
|
145
|
+
const { error } = await func.invoke(event);
|
|
146
|
+
handleBackgroundFunctionResult(functionName, error);
|
|
147
|
+
}
|
|
148
|
+
else if (await func.isScheduled()) {
|
|
149
|
+
const { error, result } = await func.invoke({
|
|
150
|
+
...event,
|
|
151
|
+
body: JSON.stringify({
|
|
152
|
+
next_run: await func.getNextRun(),
|
|
153
|
+
}),
|
|
154
|
+
isBase64Encoded: false,
|
|
155
|
+
headers: {
|
|
156
|
+
...event.headers,
|
|
157
|
+
'user-agent': CLOCKWORK_USERAGENT,
|
|
158
|
+
'X-NF-Event': 'schedule',
|
|
159
|
+
},
|
|
160
|
+
}, clientContext);
|
|
161
|
+
handleScheduledFunction({
|
|
162
|
+
error,
|
|
163
|
+
result,
|
|
164
|
+
request,
|
|
165
|
+
response,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const { error, result } = await func.invoke(event, clientContext);
|
|
170
|
+
// check for existence of metadata if this is a builder function
|
|
171
|
+
if (/^\/.netlify\/(builders)/.test(request.path) && (!result.metadata || !result.metadata.builder_function)) {
|
|
172
|
+
response.status(400).send({
|
|
173
|
+
message: 'Function is not an on-demand builder. See https://ntl.fyi/create-builder for how to convert a function to a builder.',
|
|
174
|
+
});
|
|
175
|
+
response.end();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
handleSynchronousFunction({ error, functionName: func.name, result, request, response });
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'options' implicitly has an 'any' type.
|
|
215
183
|
const getFunctionsServer = (options) => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}),
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
app.all(`${functionsPrefix}*`, functionHandler)
|
|
237
|
-
app.all(`${buildersPrefix}*`, functionHandler)
|
|
238
|
-
|
|
239
|
-
return app
|
|
240
|
-
}
|
|
241
|
-
|
|
184
|
+
const { buildersPrefix = '', functionsPrefix = '', functionsRegistry, siteUrl } = options;
|
|
185
|
+
const app = express();
|
|
186
|
+
const functionHandler = createHandler(options);
|
|
187
|
+
app.set('query parser', 'simple');
|
|
188
|
+
app.use(express.text({
|
|
189
|
+
limit: '6mb',
|
|
190
|
+
type: ['text/*', 'application/json'],
|
|
191
|
+
}));
|
|
192
|
+
app.use(express.raw({ limit: '6mb', type: '*/*' }));
|
|
193
|
+
app.use(createFormSubmissionHandler({ functionsRegistry, siteUrl }));
|
|
194
|
+
app.use(expressLogging(console, {
|
|
195
|
+
blacklist: ['/favicon.ico'],
|
|
196
|
+
}));
|
|
197
|
+
app.all(`${functionsPrefix}*`, functionHandler);
|
|
198
|
+
app.all(`${buildersPrefix}*`, functionHandler);
|
|
199
|
+
return app;
|
|
200
|
+
};
|
|
242
201
|
/**
|
|
243
202
|
*
|
|
244
203
|
* @param {object} options
|
|
@@ -255,88 +214,67 @@ const getFunctionsServer = (options) => {
|
|
|
255
214
|
* @param {*} options.timeouts
|
|
256
215
|
* @returns {Promise<import('./registry.mjs').FunctionsRegistry | undefined>}
|
|
257
216
|
*/
|
|
217
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'options' implicitly has an 'any' type.
|
|
258
218
|
export const startFunctionsServer = async (options) => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
219
|
+
const { blobsContext, capabilities, command, config, debug, loadDistFunctions, settings, site, siteInfo, siteUrl, timeouts, } = options;
|
|
220
|
+
// @ts-expect-error TS(2345) FIXME: Argument of type '{ base: any; }' is not assignabl... Remove this comment to see the full error message
|
|
221
|
+
const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root });
|
|
222
|
+
const functionsDirectories = [];
|
|
223
|
+
let manifest;
|
|
224
|
+
// If the `loadDistFunctions` parameter is sent, the functions server will
|
|
225
|
+
// use the built functions created by zip-it-and-ship-it rather than building
|
|
226
|
+
// them from source.
|
|
227
|
+
if (loadDistFunctions) {
|
|
228
|
+
const distPath = await getFunctionsDistPath({ base: site.root });
|
|
229
|
+
if (distPath) {
|
|
230
|
+
functionsDirectories.push(distPath);
|
|
231
|
+
// When using built functions, read the manifest file so that we can
|
|
232
|
+
// extract metadata such as routes and API version.
|
|
233
|
+
try {
|
|
234
|
+
const manifestPath = path.join(distPath, 'manifest.json');
|
|
235
|
+
// eslint-disable-next-line unicorn/prefer-json-parse-buffer
|
|
236
|
+
const data = await fs.readFile(manifestPath, 'utf8');
|
|
237
|
+
manifest = JSON.parse(data);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// no-op
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// The order of the function directories matters. Rightmost directories take
|
|
246
|
+
// precedence.
|
|
247
|
+
const sourceDirectories = [internalFunctionsDir, settings.functions].filter(Boolean);
|
|
248
|
+
functionsDirectories.push(...sourceDirectories);
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const functionsServePath = getFunctionsServePath({ base: site.root });
|
|
252
|
+
await fs.rm(functionsServePath, { force: true, recursive: true });
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
294
255
|
// no-op
|
|
295
|
-
}
|
|
296
256
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
blobsContext,
|
|
319
|
-
capabilities,
|
|
320
|
-
config,
|
|
321
|
-
debug,
|
|
322
|
-
isConnected: Boolean(siteUrl),
|
|
323
|
-
logLambdaCompat: isFeatureFlagEnabled('cli_log_lambda_compat', siteInfo),
|
|
324
|
-
manifest,
|
|
325
|
-
// functions always need to be inside the packagePath if set inside a monorepo
|
|
326
|
-
projectRoot: command.workingDir,
|
|
327
|
-
settings,
|
|
328
|
-
timeouts,
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
await functionsRegistry.scan(functionsDirectories)
|
|
332
|
-
|
|
333
|
-
const server = getFunctionsServer(Object.assign(options, { functionsRegistry }))
|
|
334
|
-
|
|
335
|
-
await startWebServer({ server, settings, debug })
|
|
336
|
-
|
|
337
|
-
return functionsRegistry
|
|
338
|
-
}
|
|
339
|
-
|
|
257
|
+
if (functionsDirectories.length === 0) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const functionsRegistry = new FunctionsRegistry({
|
|
261
|
+
blobsContext,
|
|
262
|
+
capabilities,
|
|
263
|
+
config,
|
|
264
|
+
debug,
|
|
265
|
+
isConnected: Boolean(siteUrl),
|
|
266
|
+
logLambdaCompat: isFeatureFlagEnabled('cli_log_lambda_compat', siteInfo),
|
|
267
|
+
manifest,
|
|
268
|
+
// functions always need to be inside the packagePath if set inside a monorepo
|
|
269
|
+
projectRoot: command.workingDir,
|
|
270
|
+
settings,
|
|
271
|
+
timeouts,
|
|
272
|
+
});
|
|
273
|
+
await functionsRegistry.scan(functionsDirectories);
|
|
274
|
+
const server = getFunctionsServer(Object.assign(options, { functionsRegistry }));
|
|
275
|
+
await startWebServer({ server, settings, debug });
|
|
276
|
+
return functionsRegistry;
|
|
277
|
+
};
|
|
340
278
|
/**
|
|
341
279
|
*
|
|
342
280
|
* @param {object} config
|
|
@@ -344,15 +282,19 @@ export const startFunctionsServer = async (options) => {
|
|
|
344
282
|
* @param {ReturnType<Awaited<typeof getFunctionsServer>>} config.server
|
|
345
283
|
* @param {*} config.settings
|
|
346
284
|
*/
|
|
285
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'debug' implicitly has an 'any' ty... Remove this comment to see the full error message
|
|
347
286
|
const startWebServer = async ({ debug, server, settings }) => {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
287
|
+
await new Promise((/** @type {(resolve: void) => void} */ resolve) => {
|
|
288
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'err' implicitly has an 'any' type.
|
|
289
|
+
server.listen(settings.functionsPort, (/** @type {unknown} */ err) => {
|
|
290
|
+
if (err) {
|
|
291
|
+
errorExit(`${NETLIFYDEVERR} Unable to start functions server: ${err}`);
|
|
292
|
+
}
|
|
293
|
+
else if (debug) {
|
|
294
|
+
log(`${NETLIFYDEVLOG} Functions server is listening on ${settings.functionsPort}`);
|
|
295
|
+
}
|
|
296
|
+
// @ts-expect-error TS(2794) FIXME: Expected 1 arguments, but got 0. Did you forget to... Remove this comment to see the full error message
|
|
297
|
+
resolve();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
};
|