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.
Files changed (210) hide show
  1. package/README.md +2 -138
  2. package/npm-shrinkwrap.json +76 -76
  3. package/package.json +16 -15
  4. package/src/commands/addons/addons-auth.mjs +27 -30
  5. package/src/commands/addons/addons-config.mjs +145 -154
  6. package/src/commands/addons/addons-create.mjs +94 -108
  7. package/src/commands/addons/addons-delete.mjs +36 -41
  8. package/src/commands/addons/addons-list.mjs +38 -42
  9. package/src/commands/addons/addons.mjs +26 -28
  10. package/src/commands/addons/index.mjs +1 -1
  11. package/src/commands/api/api.mjs +45 -53
  12. package/src/commands/api/index.mjs +1 -1
  13. package/src/commands/base-command.mjs +597 -684
  14. package/src/commands/blobs/blobs-delete.mjs +35 -0
  15. package/src/commands/blobs/blobs-get.mjs +44 -0
  16. package/src/commands/blobs/blobs-list.mjs +48 -0
  17. package/src/commands/blobs/blobs-set.mjs +54 -0
  18. package/src/commands/blobs/blobs.mjs +32 -0
  19. package/src/commands/blobs/index.mjs +1 -0
  20. package/src/commands/build/build.mjs +55 -67
  21. package/src/commands/build/index.mjs +1 -1
  22. package/src/commands/completion/completion.mjs +41 -46
  23. package/src/commands/completion/index.mjs +1 -1
  24. package/src/commands/deploy/deploy.mjs +675 -710
  25. package/src/commands/deploy/index.mjs +1 -1
  26. package/src/commands/dev/dev-exec.mjs +20 -32
  27. package/src/commands/dev/dev.mjs +217 -302
  28. package/src/commands/dev/index.mjs +1 -1
  29. package/src/commands/dev/types.d.ts +30 -0
  30. package/src/commands/env/env-clone.mjs +157 -184
  31. package/src/commands/env/env-get.mjs +49 -68
  32. package/src/commands/env/env-import.mjs +100 -119
  33. package/src/commands/env/env-list.mjs +104 -129
  34. package/src/commands/env/env-set.mjs +160 -185
  35. package/src/commands/env/env-unset.mjs +104 -122
  36. package/src/commands/env/env.mjs +28 -30
  37. package/src/commands/env/index.mjs +1 -1
  38. package/src/commands/functions/functions-build.mjs +29 -41
  39. package/src/commands/functions/functions-create.mjs +533 -601
  40. package/src/commands/functions/functions-invoke.mjs +193 -216
  41. package/src/commands/functions/functions-list.mjs +45 -55
  42. package/src/commands/functions/functions-serve.mjs +51 -61
  43. package/src/commands/functions/functions.mjs +26 -32
  44. package/src/commands/functions/index.mjs +1 -1
  45. package/src/commands/index.mjs +2 -2
  46. package/src/commands/init/index.mjs +1 -1
  47. package/src/commands/init/init.mjs +138 -167
  48. package/src/commands/integration/deploy.mjs +337 -399
  49. package/src/commands/integration/index.mjs +12 -13
  50. package/src/commands/link/index.mjs +1 -1
  51. package/src/commands/link/link.mjs +298 -317
  52. package/src/commands/lm/index.mjs +1 -1
  53. package/src/commands/lm/lm-info.mjs +23 -31
  54. package/src/commands/lm/lm-install.mjs +13 -17
  55. package/src/commands/lm/lm-setup.mjs +80 -84
  56. package/src/commands/lm/lm-uninstall.mjs +7 -12
  57. package/src/commands/lm/lm.mjs +18 -22
  58. package/src/commands/login/index.mjs +1 -1
  59. package/src/commands/login/login.mjs +35 -41
  60. package/src/commands/logout/index.mjs +1 -1
  61. package/src/commands/logout/logout.mjs +25 -31
  62. package/src/commands/main.mjs +166 -201
  63. package/src/commands/open/index.mjs +1 -1
  64. package/src/commands/open/open-admin.mjs +15 -18
  65. package/src/commands/open/open-site.mjs +16 -19
  66. package/src/commands/open/open.mjs +24 -27
  67. package/src/commands/recipes/common.mjs +23 -34
  68. package/src/commands/recipes/index.mjs +1 -1
  69. package/src/commands/recipes/recipes-list.mjs +13 -20
  70. package/src/commands/recipes/recipes.mjs +59 -72
  71. package/src/commands/serve/index.mjs +1 -1
  72. package/src/commands/serve/serve.mjs +142 -189
  73. package/src/commands/sites/index.mjs +2 -2
  74. package/src/commands/sites/sites-create-template.mjs +214 -236
  75. package/src/commands/sites/sites-create.mjs +145 -157
  76. package/src/commands/sites/sites-delete.mjs +75 -81
  77. package/src/commands/sites/sites-list.mjs +63 -66
  78. package/src/commands/sites/sites.mjs +18 -20
  79. package/src/commands/status/index.mjs +1 -1
  80. package/src/commands/status/status-hooks.mjs +32 -34
  81. package/src/commands/status/status.mjs +99 -106
  82. package/src/commands/switch/index.mjs +1 -1
  83. package/src/commands/switch/switch.mjs +32 -37
  84. package/src/commands/types.d.ts +31 -0
  85. package/src/commands/unlink/index.mjs +1 -1
  86. package/src/commands/unlink/unlink.mjs +23 -29
  87. package/src/commands/watch/index.mjs +1 -1
  88. package/src/commands/watch/watch.mjs +91 -105
  89. package/src/functions-templates/javascript/hello/{{name}}.js +2 -3
  90. package/src/lib/account.mjs +4 -5
  91. package/src/lib/api.mjs +22 -20
  92. package/src/lib/blobs/blobs.mjs +36 -45
  93. package/src/lib/build.mjs +82 -85
  94. package/src/lib/completion/constants.mjs +2 -4
  95. package/src/lib/completion/generate-autocompletion.mjs +33 -36
  96. package/src/lib/completion/get-autocompletion.mjs +31 -35
  97. package/src/lib/completion/index.mjs +1 -1
  98. package/src/lib/completion/script.mjs +12 -19
  99. package/src/lib/edge-functions/bootstrap.mjs +3 -5
  100. package/src/lib/edge-functions/consts.mjs +9 -10
  101. package/src/lib/edge-functions/deploy.mjs +28 -34
  102. package/src/lib/edge-functions/editor-helper.mjs +29 -42
  103. package/src/lib/edge-functions/headers.mjs +24 -26
  104. package/src/lib/edge-functions/internal.mjs +38 -44
  105. package/src/lib/edge-functions/proxy.mjs +229 -228
  106. package/src/lib/edge-functions/registry.mjs +473 -574
  107. package/src/lib/exec-fetcher.mjs +115 -122
  108. package/src/lib/fs.mjs +28 -27
  109. package/src/lib/functions/background.mjs +16 -20
  110. package/src/lib/functions/config.mjs +12 -9
  111. package/src/lib/functions/form-submissions-handler.mjs +143 -149
  112. package/src/lib/functions/local-proxy.mjs +40 -44
  113. package/src/lib/functions/memoized-build.mjs +19 -21
  114. package/src/lib/functions/netlify-function.mjs +269 -249
  115. package/src/lib/functions/registry.mjs +509 -568
  116. package/src/lib/functions/runtimes/go/index.mjs +62 -71
  117. package/src/lib/functions/runtimes/index.mjs +8 -15
  118. package/src/lib/functions/runtimes/js/builders/netlify-lambda.mjs +55 -64
  119. package/src/lib/functions/runtimes/js/builders/zisi.mjs +135 -154
  120. package/src/lib/functions/runtimes/js/constants.mjs +1 -1
  121. package/src/lib/functions/runtimes/js/index.mjs +92 -109
  122. package/src/lib/functions/runtimes/js/worker.mjs +43 -45
  123. package/src/lib/functions/runtimes/rust/index.mjs +64 -73
  124. package/src/lib/functions/scheduled.mjs +70 -88
  125. package/src/lib/functions/server.mjs +269 -327
  126. package/src/lib/functions/synchronous.mjs +118 -147
  127. package/src/lib/functions/utils.mjs +38 -46
  128. package/src/lib/geo-location.mjs +69 -81
  129. package/src/lib/http-agent.mjs +87 -90
  130. package/src/lib/images/proxy.mjs +97 -99
  131. package/src/lib/log.mjs +6 -9
  132. package/src/lib/path.mjs +2 -1
  133. package/src/lib/render-error-template.mjs +19 -20
  134. package/src/lib/settings.mjs +17 -19
  135. package/src/lib/spinner.mjs +21 -23
  136. package/src/lib/string.mjs +4 -2
  137. package/src/recipes/vscode/index.mjs +69 -85
  138. package/src/recipes/vscode/settings.mjs +53 -58
  139. package/src/utils/addons/compare.mjs +31 -32
  140. package/src/utils/addons/diffs/index.mjs +16 -17
  141. package/src/utils/addons/diffs/options.mjs +99 -101
  142. package/src/utils/addons/prepare.mjs +100 -97
  143. package/src/utils/addons/prompts.mjs +73 -76
  144. package/src/utils/addons/render.mjs +33 -36
  145. package/src/utils/addons/validation.mjs +19 -15
  146. package/src/utils/banner.mjs +11 -16
  147. package/src/utils/build-info.mjs +65 -66
  148. package/src/utils/command-helpers.mjs +185 -199
  149. package/src/utils/create-deferred.mjs +9 -12
  150. package/src/utils/create-stream-promise.mjs +54 -47
  151. package/src/utils/deploy/constants.mjs +9 -11
  152. package/src/utils/deploy/deploy-site.mjs +162 -182
  153. package/src/utils/deploy/hash-config.mjs +21 -21
  154. package/src/utils/deploy/hash-files.mjs +34 -38
  155. package/src/utils/deploy/hash-fns.mjs +149 -154
  156. package/src/utils/deploy/hasher-segments.mjs +58 -52
  157. package/src/utils/deploy/upload-files.mjs +99 -113
  158. package/src/utils/deploy/util.mjs +85 -91
  159. package/src/utils/detect-server-settings.mjs +236 -268
  160. package/src/utils/dev.mjs +163 -178
  161. package/src/utils/dot-env.mjs +37 -42
  162. package/src/utils/env/index.mjs +148 -148
  163. package/src/utils/execa.mjs +9 -13
  164. package/src/utils/feature-flags.mjs +6 -5
  165. package/src/utils/framework-server.mjs +43 -52
  166. package/src/utils/functions/constants.mjs +1 -1
  167. package/src/utils/functions/functions.mjs +30 -40
  168. package/src/utils/functions/get-functions.mjs +28 -29
  169. package/src/utils/functions/index.mjs +3 -3
  170. package/src/utils/get-global-config.mjs +33 -36
  171. package/src/utils/get-package-json.mjs +14 -15
  172. package/src/utils/get-repo-data.mjs +54 -64
  173. package/src/utils/get-site.mjs +14 -14
  174. package/src/utils/gh-auth.mjs +79 -100
  175. package/src/utils/gitignore.mjs +37 -40
  176. package/src/utils/headers.mjs +33 -35
  177. package/src/utils/hooks/requires-site-info.mjs +26 -22
  178. package/src/utils/init/config-github.mjs +207 -219
  179. package/src/utils/init/config-manual.mjs +83 -100
  180. package/src/utils/init/config.mjs +25 -26
  181. package/src/utils/init/node-version.mjs +23 -30
  182. package/src/utils/init/plugins.mjs +12 -8
  183. package/src/utils/init/utils.mjs +152 -172
  184. package/src/utils/live-tunnel.mjs +118 -141
  185. package/src/utils/lm/install.mjs +220 -259
  186. package/src/utils/lm/requirements.mjs +54 -63
  187. package/src/utils/lm/steps.mjs +31 -31
  188. package/src/utils/lm/ui.mjs +13 -20
  189. package/src/utils/open-browser.mjs +31 -32
  190. package/src/utils/parse-raw-flags.mjs +39 -35
  191. package/src/utils/proxy-server.mjs +84 -71
  192. package/src/utils/proxy.mjs +696 -750
  193. package/src/utils/read-repo-url.mjs +48 -47
  194. package/src/utils/redirects.mjs +49 -49
  195. package/src/utils/request-id.mjs +2 -4
  196. package/src/utils/rules-proxy.mjs +96 -100
  197. package/src/utils/run-build.mjs +109 -132
  198. package/src/utils/shell.mjs +99 -106
  199. package/src/utils/sign-redirect.mjs +14 -14
  200. package/src/utils/sites/utils.mjs +48 -55
  201. package/src/utils/state-config.mjs +101 -101
  202. package/src/utils/static-server.mjs +28 -34
  203. package/src/utils/telemetry/index.mjs +2 -2
  204. package/src/utils/telemetry/report-error.mjs +45 -49
  205. package/src/utils/telemetry/request.mjs +36 -43
  206. package/src/utils/telemetry/telemetry.mjs +90 -105
  207. package/src/utils/telemetry/utils.mjs +5 -6
  208. package/src/utils/telemetry/validation.mjs +55 -53
  209. package/src/utils/types.d.ts +46 -0
  210. package/src/utils/validation.mjs +10 -13
@@ -1,244 +1,203 @@
1
- // @ts-check
2
- import { Buffer } from 'buffer'
3
- import { promises as fs } from 'fs'
4
- import path from 'path'
5
-
6
- import express from 'express'
7
- import expressLogging from 'express-logging'
8
- import jwtDecode from 'jwt-decode'
9
-
10
- import { NETLIFYDEVERR, NETLIFYDEVLOG, error as errorExit, log } from '../../utils/command-helpers.mjs'
11
- import { isFeatureFlagEnabled } from '../../utils/feature-flags.mjs'
12
- import {
13
- CLOCKWORK_USERAGENT,
14
- getFunctionsDistPath,
15
- getFunctionsServePath,
16
- getInternalFunctionsDir,
17
- } from '../../utils/functions/index.mjs'
18
- import { NFFunctionName, NFFunctionRoute } from '../../utils/headers.mjs'
19
- import { headers as efHeaders } from '../edge-functions/headers.mjs'
20
- import { getGeoLocation } from '../geo-location.mjs'
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
- // inject a client context based on auth header, ported over from netlify-lambda (https://github.com/netlify/netlify-lambda/pull/57)
31
- if (!headers.authorization) return
32
-
33
- const parts = headers.authorization.split(' ')
34
- if (parts.length !== 2 || parts[0] !== 'Bearer') return
35
-
36
- try {
37
- return {
38
- identity: {
39
- url: 'https://netlify-dev-locally-emulated-identity.netlify.com/.netlify/identity',
40
- token:
41
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb3VyY2UiOiJuZXRsaWZ5IGRldiIsInRlc3REYXRhIjoiTkVUTElGWV9ERVZfTE9DQUxMWV9FTVVMQVRFRF9JREVOVElUWSJ9.2eSDqUOZAOBsx39FHFePjYj12k0LrxldvGnlvDu3GMI',
42
- // you can decode this with https://jwt.io/
43
- // just says
44
- // {
45
- // "source": "netlify dev",
46
- // "testData": "NETLIFY_DEV_LOCALLY_EMULATED_IDENTITY"
47
- // }
48
- },
49
- user: jwtDecode(parts[1]),
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
- } catch {
52
- // Ignore errors - bearer token is not a JWT, probably not intended for us
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
- const clientContext = buildClientContext(request.headers) || {}
166
-
167
- if (func.isBackground) {
168
- handleBackgroundFunction(functionName, response)
169
-
170
- // background functions do not receive a clientContext
171
- const { error } = await func.invoke(event)
172
-
173
- handleBackgroundFunctionResult(functionName, error)
174
- } else if (await func.isScheduled()) {
175
- const { error, result } = await func.invoke(
176
- {
177
- ...event,
178
- body: JSON.stringify({
179
- next_run: await func.getNextRun(),
180
- }),
181
- isBase64Encoded: false,
182
- headers: {
183
- ...event.headers,
184
- 'user-agent': CLOCKWORK_USERAGENT,
185
- 'X-NF-Event': 'schedule',
186
- },
187
- },
188
- clientContext,
189
- )
190
-
191
- handleScheduledFunction({
192
- error,
193
- result,
194
- request,
195
- response,
196
- })
197
- } else {
198
- const { error, result } = await func.invoke(event, clientContext)
199
-
200
- // check for existence of metadata if this is a builder function
201
- if (/^\/.netlify\/(builders)/.test(request.path) && (!result.metadata || !result.metadata.builder_function)) {
202
- response.status(400).send({
203
- message:
204
- 'Function is not an on-demand builder. See https://ntl.fyi/create-builder for how to convert a function to a builder.',
205
- })
206
- response.end()
207
- return
208
- }
209
-
210
- handleSynchronousFunction({ error, functionName: func.name, result, request, response })
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
- const { buildersPrefix = '', functionsPrefix = '', functionsRegistry, siteUrl } = options
217
- const app = express()
218
- const functionHandler = createHandler(options)
219
-
220
- app.set('query parser', 'simple')
221
-
222
- app.use(
223
- express.text({
224
- limit: '6mb',
225
- type: ['text/*', 'application/json'],
226
- }),
227
- )
228
- app.use(express.raw({ limit: '6mb', type: '*/*' }))
229
- app.use(createFormSubmissionHandler({ functionsRegistry, siteUrl }))
230
- app.use(
231
- expressLogging(console, {
232
- blacklist: ['/favicon.ico'],
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
- const {
260
- blobsContext,
261
- capabilities,
262
- command,
263
- config,
264
- debug,
265
- loadDistFunctions,
266
- settings,
267
- site,
268
- siteInfo,
269
- siteUrl,
270
- timeouts,
271
- } = options
272
- const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root })
273
- const functionsDirectories = []
274
- let manifest
275
-
276
- // If the `loadDistFunctions` parameter is sent, the functions server will
277
- // use the built functions created by zip-it-and-ship-it rather than building
278
- // them from source.
279
- if (loadDistFunctions) {
280
- const distPath = await getFunctionsDistPath({ base: site.root })
281
-
282
- if (distPath) {
283
- functionsDirectories.push(distPath)
284
-
285
- // When using built functions, read the manifest file so that we can
286
- // extract metadata such as routes and API version.
287
- try {
288
- const manifestPath = path.join(distPath, 'manifest.json')
289
- // eslint-disable-next-line unicorn/prefer-json-parse-buffer
290
- const data = await fs.readFile(manifestPath, 'utf8')
291
-
292
- manifest = JSON.parse(data)
293
- } catch {
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
- } else {
298
- // The order of the function directories matters. Rightmost directories take
299
- // precedence.
300
- const sourceDirectories = [internalFunctionsDir, settings.functions].filter(Boolean)
301
-
302
- functionsDirectories.push(...sourceDirectories)
303
- }
304
-
305
- try {
306
- const functionsServePath = getFunctionsServePath({ base: site.root })
307
-
308
- await fs.rm(functionsServePath, { force: true, recursive: true })
309
- } catch {
310
- // no-op
311
- }
312
-
313
- if (functionsDirectories.length === 0) {
314
- return
315
- }
316
-
317
- const functionsRegistry = new FunctionsRegistry({
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
- await new Promise((/** @type {(resolve: void) => void} */ resolve) => {
349
- server.listen(settings.functionsPort, (/** @type {unknown} */ err) => {
350
- if (err) {
351
- errorExit(`${NETLIFYDEVERR} Unable to start functions server: ${err}`)
352
- } else if (debug) {
353
- log(`${NETLIFYDEVLOG} Functions server is listening on ${settings.functionsPort}`)
354
- }
355
- resolve()
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
+ };