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,601 +1,542 @@
1
- // @ts-check
2
- import { mkdir, stat } from 'fs/promises'
3
- import { createRequire } from 'module'
4
- import { basename, extname, isAbsolute, join, resolve } from 'path'
5
- import { env } from 'process'
6
-
7
- import { listFunctions } from '@netlify/zip-it-and-ship-it'
8
- import extractZip from 'extract-zip'
9
-
10
- import {
11
- chalk,
12
- log,
13
- getTerminalLink,
14
- NETLIFYDEVERR,
15
- NETLIFYDEVLOG,
16
- NETLIFYDEVWARN,
17
- warn,
18
- watchDebounced,
19
- } from '../../utils/command-helpers.mjs'
20
- import { INTERNAL_FUNCTIONS_FOLDER, SERVE_FUNCTIONS_FOLDER } from '../../utils/functions/functions.mjs'
21
- import { BACKGROUND_FUNCTIONS_WARNING } from '../log.mjs'
22
- import { getPathInProject } from '../settings.mjs'
23
-
24
- import NetlifyFunction from './netlify-function.mjs'
25
- import runtimes from './runtimes/index.mjs'
26
-
27
- export const DEFAULT_FUNCTION_URL_EXPRESSION = /^\/.netlify\/(functions|builders)\/([^/]+).*/
28
- const TYPES_PACKAGE = '@netlify/functions'
29
- const ZIP_EXTENSION = '.zip'
30
-
1
+ import { mkdir, stat } from 'fs/promises';
2
+ import { createRequire } from 'module';
3
+ import { basename, extname, isAbsolute, join, resolve } from 'path';
4
+ import { env } from 'process';
5
+ import { listFunctions } from '@netlify/zip-it-and-ship-it';
6
+ import extractZip from 'extract-zip';
7
+ import { chalk, log, getTerminalLink, NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, warn, watchDebounced, } from '../../utils/command-helpers.mjs';
8
+ import { INTERNAL_FUNCTIONS_FOLDER, SERVE_FUNCTIONS_FOLDER } from '../../utils/functions/functions.mjs';
9
+ import { BACKGROUND_FUNCTIONS_WARNING } from '../log.mjs';
10
+ import { getPathInProject } from '../settings.mjs';
11
+ import NetlifyFunction from './netlify-function.mjs';
12
+ import runtimes from './runtimes/index.mjs';
13
+ export const DEFAULT_FUNCTION_URL_EXPRESSION = /^\/.netlify\/(functions|builders)\/([^/]+).*/;
14
+ const TYPES_PACKAGE = '@netlify/functions';
15
+ const ZIP_EXTENSION = '.zip';
31
16
  /**
32
17
  * @typedef {"buildError" | "extracted" | "loaded" | "missing-types-package" | "reloaded" | "reloading" | "removed"} FunctionEvent
33
18
  */
34
-
35
19
  export class FunctionsRegistry {
36
- constructor({
37
- blobsContext,
38
- capabilities,
39
- config,
40
- debug = false,
41
- isConnected = false,
42
- logLambdaCompat,
43
- manifest,
44
- projectRoot,
45
- settings,
46
- timeouts,
47
- }) {
48
- this.capabilities = capabilities
49
- this.config = config
50
- this.debug = debug
51
- this.isConnected = isConnected
52
- this.projectRoot = projectRoot
53
- this.timeouts = timeouts
54
- this.settings = settings
55
-
20
+ constructor({
21
+ // @ts-expect-error TS(7031) FIXME: Binding element 'blobsContext' implicitly has an '... Remove this comment to see the full error message
22
+ blobsContext,
23
+ // @ts-expect-error TS(7031) FIXME: Binding element 'capabilities' implicitly has an '... Remove this comment to see the full error message
24
+ capabilities,
25
+ // @ts-expect-error TS(7031) FIXME: Binding element 'config' implicitly has an 'any' t... Remove this comment to see the full error message
26
+ config, debug = false, isConnected = false,
27
+ // @ts-expect-error TS(7031) FIXME: Binding element 'logLambdaCompat' implicitly has a... Remove this comment to see the full error message
28
+ logLambdaCompat,
29
+ // @ts-expect-error TS(7031) FIXME: Binding element 'manifest' implicitly has an 'any'... Remove this comment to see the full error message
30
+ manifest,
31
+ // @ts-expect-error TS(7031) FIXME: Binding element 'projectRoot' implicitly has an 'a... Remove this comment to see the full error message
32
+ projectRoot,
33
+ // @ts-expect-error TS(7031) FIXME: Binding element 'settings' implicitly has an 'any'... Remove this comment to see the full error message
34
+ settings,
35
+ // @ts-expect-error TS(7031) FIXME: Binding element 'timeouts' implicitly has an 'any'... Remove this comment to see the full error message
36
+ timeouts, }) {
37
+ // @ts-expect-error TS(2339) FIXME: Property 'capabilities' does not exist on type 'Fu... Remove this comment to see the full error message
38
+ this.capabilities = capabilities;
39
+ // @ts-expect-error TS(2339) FIXME: Property 'config' does not exist on type 'Function... Remove this comment to see the full error message
40
+ this.config = config;
41
+ // @ts-expect-error TS(2339) FIXME: Property 'debug' does not exist on type 'Functions... Remove this comment to see the full error message
42
+ this.debug = debug;
43
+ // @ts-expect-error TS(2339) FIXME: Property 'isConnected' does not exist on type 'Fun... Remove this comment to see the full error message
44
+ this.isConnected = isConnected;
45
+ // @ts-expect-error TS(2339) FIXME: Property 'projectRoot' does not exist on type 'Fun... Remove this comment to see the full error message
46
+ this.projectRoot = projectRoot;
47
+ // @ts-expect-error TS(2339) FIXME: Property 'timeouts' does not exist on type 'Functi... Remove this comment to see the full error message
48
+ this.timeouts = timeouts;
49
+ // @ts-expect-error TS(2339) FIXME: Property 'settings' does not exist on type 'Functi... Remove this comment to see the full error message
50
+ this.settings = settings;
51
+ /**
52
+ * Context object for Netlify Blobs
53
+ *
54
+ * @type {import("../blobs/blobs.mjs").BlobsContext}
55
+ */
56
+ // @ts-expect-error TS(2339) FIXME: Property 'blobsContext' does not exist on type 'Fu... Remove this comment to see the full error message
57
+ this.blobsContext = blobsContext;
58
+ /**
59
+ * An object to be shared among all functions in the registry. It can be
60
+ * used to cache the results of the build function — e.g. it's used in
61
+ * the `memoizedBuild` method in the JavaScript runtime.
62
+ *
63
+ * @type {Record<string, unknown>}
64
+ */
65
+ // @ts-expect-error TS(2339) FIXME: Property 'buildCache' does not exist on type 'Func... Remove this comment to see the full error message
66
+ this.buildCache = {};
67
+ /**
68
+ * File watchers for parent directories where functions live — i.e. the
69
+ * ones supplied to `scan()`. This is a Map because in the future we
70
+ * might have several function directories.
71
+ *
72
+ * @type {Map<string, Awaited<ReturnType<watchDebounced>>>}
73
+ */
74
+ // @ts-expect-error TS(2339) FIXME: Property 'directoryWatchers' does not exist on typ... Remove this comment to see the full error message
75
+ this.directoryWatchers = new Map();
76
+ /**
77
+ * The functions held by the registry
78
+ *
79
+ * @type {Map<string, NetlifyFunction>}
80
+ */
81
+ // @ts-expect-error TS(2339) FIXME: Property 'functions' does not exist on type 'Funct... Remove this comment to see the full error message
82
+ this.functions = new Map();
83
+ /**
84
+ * File watchers for function files. Maps function names to objects built
85
+ * by the `watchDebounced` utility.
86
+ *
87
+ * @type {Map<string, Awaited<ReturnType<watchDebounced>>>}
88
+ */
89
+ // @ts-expect-error TS(2339) FIXME: Property 'functionWatchers' does not exist on type... Remove this comment to see the full error message
90
+ this.functionWatchers = new Map();
91
+ /**
92
+ * Keeps track of whether we've checked whether `TYPES_PACKAGE` is
93
+ * installed.
94
+ */
95
+ // @ts-expect-error TS(2339) FIXME: Property 'hasCheckedTypesPackage' does not exist o... Remove this comment to see the full error message
96
+ this.hasCheckedTypesPackage = false;
97
+ /**
98
+ * Whether to log V1 functions as using the "Lambda compatibility mode"
99
+ *
100
+ * @type {boolean}
101
+ */
102
+ // @ts-expect-error TS(2339) FIXME: Property 'logLambdaCompat' does not exist on type ... Remove this comment to see the full error message
103
+ this.logLambdaCompat = Boolean(logLambdaCompat);
104
+ /**
105
+ * Contents of a `manifest.json` file that can be looked up when dealing
106
+ * with built functions.
107
+ *
108
+ * @type {object}
109
+ */
110
+ // @ts-expect-error TS(2339) FIXME: Property 'manifest' does not exist on type 'Functi... Remove this comment to see the full error message
111
+ this.manifest = manifest;
112
+ }
113
+ checkTypesPackage() {
114
+ // @ts-expect-error TS(2339) FIXME: Property 'hasCheckedTypesPackage' does not exist o... Remove this comment to see the full error message
115
+ if (this.hasCheckedTypesPackage) {
116
+ return;
117
+ }
118
+ // @ts-expect-error TS(2339) FIXME: Property 'hasCheckedTypesPackage' does not exist o... Remove this comment to see the full error message
119
+ this.hasCheckedTypesPackage = true;
120
+ // @ts-expect-error TS(2339) FIXME: Property 'projectRoot' does not exist on type 'Fun... Remove this comment to see the full error message
121
+ const require = createRequire(this.projectRoot);
122
+ try {
123
+ // @ts-expect-error TS(2339) FIXME: Property 'projectRoot' does not exist on type 'Fun... Remove this comment to see the full error message
124
+ require.resolve(TYPES_PACKAGE, { paths: [this.projectRoot] });
125
+ }
126
+ catch (error) {
127
+ // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
128
+ if (error?.code === 'MODULE_NOT_FOUND') {
129
+ // @ts-expect-error TS(2345) FIXME: Argument of type '{}' is not assignable to paramet... Remove this comment to see the full error message
130
+ FunctionsRegistry.logEvent('missing-types-package', {});
131
+ }
132
+ }
133
+ }
134
+ /**
135
+ * Runs before `scan` and calls any `onDirectoryScan` hooks defined by the
136
+ * runtime before the directory is read. This gives runtime the opportunity
137
+ * to run additional logic when a directory is scanned.
138
+ *
139
+ * @param {string} directory
140
+ */
141
+ // @ts-expect-error TS(7006) FIXME: Parameter 'directory' implicitly has an 'any' type... Remove this comment to see the full error message
142
+ static async prepareDirectoryScan(directory) {
143
+ await mkdir(directory, { recursive: true });
144
+ // We give runtimes the opportunity to react to a directory scan and run
145
+ // additional logic before the directory is read. So if they implement a
146
+ // `onDirectoryScan` hook, we run it.
147
+ await Promise.all(Object.values(runtimes).map((runtime) => {
148
+ // @ts-expect-error TS(2339) FIXME: Property 'onDirectoryScan' does not exist on type ... Remove this comment to see the full error message
149
+ if (typeof runtime.onDirectoryScan !== 'function') {
150
+ return null;
151
+ }
152
+ // @ts-expect-error TS(2339) FIXME: Property 'onDirectoryScan' does not exist on type ... Remove this comment to see the full error message
153
+ return runtime.onDirectoryScan({ directory });
154
+ }));
155
+ }
56
156
  /**
57
- * Context object for Netlify Blobs
157
+ * Builds a function and sets up the appropriate file watchers so that any
158
+ * changes will trigger another build.
58
159
  *
59
- * @type {import("../blobs/blobs.mjs").BlobsContext}
160
+ * @param {NetlifyFunction} func
161
+ * @param {boolean} [firstLoad ]
162
+ * @returns
60
163
  */
61
- this.blobsContext = blobsContext
62
-
164
+ // @ts-expect-error TS(7006) FIXME: Parameter 'func' implicitly has an 'any' type.
165
+ async buildFunctionAndWatchFiles(func, firstLoad = false) {
166
+ if (!firstLoad) {
167
+ FunctionsRegistry.logEvent('reloading', { func });
168
+ }
169
+ // @ts-expect-error TS(2339) FIXME: Property 'buildCache' does not exist on type 'Func... Remove this comment to see the full error message
170
+ const { error: buildError, includedFiles, srcFilesDiff } = await func.build({ cache: this.buildCache });
171
+ if (buildError) {
172
+ FunctionsRegistry.logEvent('buildError', { func });
173
+ }
174
+ else {
175
+ const event = firstLoad ? 'loaded' : 'reloaded';
176
+ const recommendedExtension = func.getRecommendedExtension();
177
+ if (recommendedExtension) {
178
+ const { filename } = func;
179
+ const newFilename = filename ? `${basename(filename, extname(filename))}${recommendedExtension}` : null;
180
+ const action = newFilename
181
+ ? `rename the function file to ${chalk.underline(newFilename)}. Refer to https://ntl.fyi/functions-runtime for more information`
182
+ : `refer to https://ntl.fyi/functions-runtime`;
183
+ const warning = `The function is using the legacy CommonJS format. To start using ES modules, ${action}.`;
184
+ // @ts-expect-error TS(2322) FIXME: Type 'string' is not assignable to type 'never'.
185
+ FunctionsRegistry.logEvent(event, { func, warnings: [warning] });
186
+ }
187
+ else {
188
+ FunctionsRegistry.logEvent(event, { func });
189
+ }
190
+ }
191
+ if (func.isTypeScript()) {
192
+ this.checkTypesPackage();
193
+ }
194
+ // If the build hasn't resulted in any files being added or removed, there
195
+ // is nothing else we need to do.
196
+ if (!srcFilesDiff) {
197
+ return;
198
+ }
199
+ // @ts-expect-error TS(2339) FIXME: Property 'functionWatchers' does not exist on type... Remove this comment to see the full error message
200
+ const watcher = this.functionWatchers.get(func.name);
201
+ // If there is already a watcher for this function, we need to unwatch any
202
+ // files that have been removed and watch any files that have been added.
203
+ if (watcher) {
204
+ // @ts-expect-error TS(7006) FIXME: Parameter 'path' implicitly has an 'any' type.
205
+ srcFilesDiff.deleted.forEach((path) => {
206
+ watcher.unwatch(path);
207
+ });
208
+ // @ts-expect-error TS(7006) FIXME: Parameter 'path' implicitly has an 'any' type.
209
+ srcFilesDiff.added.forEach((path) => {
210
+ watcher.add(path);
211
+ });
212
+ return;
213
+ }
214
+ // If there is no watcher for this function but the build produced files,
215
+ // we create a new watcher and watch them.
216
+ if (srcFilesDiff.added.size !== 0) {
217
+ const filesToWatch = [...srcFilesDiff.added, ...includedFiles];
218
+ // @ts-expect-error TS(2345) FIXME: Argument of type '{ onChange: () => void; }' is no... Remove this comment to see the full error message
219
+ const newWatcher = await watchDebounced(filesToWatch, {
220
+ onChange: () => {
221
+ this.buildFunctionAndWatchFiles(func, false);
222
+ },
223
+ });
224
+ // @ts-expect-error TS(2339) FIXME: Property 'functionWatchers' does not exist on type... Remove this comment to see the full error message
225
+ this.functionWatchers.set(func.name, newWatcher);
226
+ }
227
+ }
63
228
  /**
64
- * An object to be shared among all functions in the registry. It can be
65
- * used to cache the results of the build function — e.g. it's used in
66
- * the `memoizedBuild` method in the JavaScript runtime.
229
+ * Returns a function by name.
67
230
  *
68
- * @type {Record<string, unknown>}
231
+ * @param {string} name
69
232
  */
70
- this.buildCache = {}
71
-
233
+ // @ts-expect-error TS(7006) FIXME: Parameter 'name' implicitly has an 'any' type.
234
+ get(name) {
235
+ // @ts-expect-error TS(2339) FIXME: Property 'functions' does not exist on type 'Funct... Remove this comment to see the full error message
236
+ return this.functions.get(name);
237
+ }
72
238
  /**
73
- * File watchers for parent directories where functions live i.e. the
74
- * ones supplied to `scan()`. This is a Map because in the future we
75
- * might have several function directories.
239
+ * Looks for the first function that matches a given URL path. If a match is
240
+ * found, returns an object with the function and the route. If the URL path
241
+ * matches the default functions URL (i.e. can only be for a function) but no
242
+ * function with the given name exists, returns an object with the function
243
+ * and the route set to `null`. Otherwise, `undefined` is returned,
76
244
  *
77
- * @type {Map<string, Awaited<ReturnType<watchDebounced>>>}
245
+ * @param {string} url
246
+ * @param {string} method
78
247
  */
79
- this.directoryWatchers = new Map()
80
-
248
+ // @ts-expect-error TS(7006) FIXME: Parameter 'url' implicitly has an 'any' type.
249
+ async getFunctionForURLPath(url, method) {
250
+ // We're constructing a URL object just so that we can extract the path from
251
+ // the incoming URL. It doesn't really matter that we don't have the actual
252
+ // local URL with the correct port.
253
+ const urlPath = new URL(url, 'http://localhost').pathname;
254
+ const defaultURLMatch = urlPath.match(DEFAULT_FUNCTION_URL_EXPRESSION);
255
+ if (defaultURLMatch) {
256
+ const func = this.get(defaultURLMatch[2]);
257
+ if (!func) {
258
+ return { func: null, route: null };
259
+ }
260
+ const { routes = [] } = await func.getBuildData();
261
+ if (routes.length !== 0) {
262
+ // @ts-expect-error TS(7006) FIXME: Parameter 'route' implicitly has an 'any' type.
263
+ const paths = routes.map((route) => chalk.underline(route.pattern)).join(', ');
264
+ warn(`Function ${chalk.yellow(func.name)} cannot be invoked on ${chalk.underline(urlPath)}, because the function has the following URL paths defined: ${paths}`);
265
+ return;
266
+ }
267
+ return { func, route: null };
268
+ }
269
+ // @ts-expect-error TS(2339) FIXME: Property 'functions' does not exist on type 'Funct... Remove this comment to see the full error message
270
+ for (const func of this.functions.values()) {
271
+ const route = await func.matchURLPath(urlPath, method);
272
+ if (route) {
273
+ return { func, route };
274
+ }
275
+ }
276
+ }
81
277
  /**
82
- * The functions held by the registry
278
+ * Logs an event associated with functions.
83
279
  *
84
- * @type {Map<string, NetlifyFunction>}
280
+ * @param {FunctionEvent} event
281
+ * @param {object} data
282
+ * @param {NetlifyFunction} [data.func]
283
+ * @param {string[]} [data.warnings]
284
+ * @returns
85
285
  */
86
- this.functions = new Map()
87
-
286
+ // @ts-expect-error TS(7006) FIXME: Parameter 'event' implicitly has an 'any' type.
287
+ static logEvent(event, { func, warnings = [] }) {
288
+ let warningsText = '';
289
+ if (warnings.length !== 0) {
290
+ warningsText = ` with warnings:\n${warnings.map((warning) => ` - ${warning}`).join('\n')}`;
291
+ }
292
+ if (event === 'buildError') {
293
+ log(`${NETLIFYDEVERR} ${chalk.red('Failed to load')} function ${chalk.yellow(func?.displayName)}: ${func?.buildError?.message}`);
294
+ }
295
+ if (event === 'extracted') {
296
+ log(`${NETLIFYDEVLOG} ${chalk.green('Extracted')} function ${chalk.yellow(func?.displayName)} from ${func?.mainFile}.`);
297
+ return;
298
+ }
299
+ if (event === 'loaded') {
300
+ const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG;
301
+ const color = warningsText ? chalk.yellow : chalk.green;
302
+ const mode =
303
+ // @ts-expect-error TS(2339) FIXME: Property 'logLambdaCompat' does not exist on type ... Remove this comment to see the full error message
304
+ func?.runtimeAPIVersion === 1 && this.logLambdaCompat
305
+ ? ` in ${getTerminalLink('Lambda compatibility mode', 'https://ntl.fyi/lambda-compat')}`
306
+ : '';
307
+ log(`${icon} ${color('Loaded')} function ${chalk.yellow(func?.displayName)}${mode}${warningsText}`);
308
+ return;
309
+ }
310
+ if (event === 'missing-types-package') {
311
+ log(`${NETLIFYDEVWARN} For a better experience with TypeScript functions, consider installing the ${chalk.underline(TYPES_PACKAGE)} package. Refer to https://ntl-fyi/function-types for more information.`);
312
+ }
313
+ if (event === 'reloaded') {
314
+ const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG;
315
+ const color = warningsText ? chalk.yellow : chalk.green;
316
+ log(`${icon} ${color('Reloaded')} function ${chalk.yellow(func?.displayName)}${warningsText}`);
317
+ return;
318
+ }
319
+ if (event === 'reloading') {
320
+ log(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} function ${chalk.yellow(func?.displayName)}...`);
321
+ return;
322
+ }
323
+ if (event === 'removed') {
324
+ log(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} function ${chalk.yellow(func?.displayName)}`);
325
+ }
326
+ }
88
327
  /**
89
- * File watchers for function files. Maps function names to objects built
90
- * by the `watchDebounced` utility.
328
+ * Adds a function to the registry
91
329
  *
92
- * @type {Map<string, Awaited<ReturnType<watchDebounced>>>}
330
+ * @param {string} name
331
+ * @param {NetlifyFunction} funcBeforeHook
332
+ * @param {boolean} [isReload]
333
+ * @returns
93
334
  */
94
- this.functionWatchers = new Map()
95
-
335
+ // @ts-expect-error TS(7006) FIXME: Parameter 'name' implicitly has an 'any' type.
336
+ async registerFunction(name, funcBeforeHook, isReload = false) {
337
+ const { runtime } = funcBeforeHook;
338
+ // The `onRegister` hook allows runtimes to modify the function before it's
339
+ // registered, or to prevent it from being registered altogether if the
340
+ // hook returns `null`.
341
+ const func = typeof runtime.onRegister === 'function' ? runtime.onRegister(funcBeforeHook) : funcBeforeHook;
342
+ if (func === null) {
343
+ return;
344
+ }
345
+ // @ts-expect-error TS(2339) FIXME: Property 'isConnected' does not exist on type 'Fun... Remove this comment to see the full error message
346
+ if (func.isBackground && this.isConnected && !this.capabilities.backgroundFunctions) {
347
+ warn(BACKGROUND_FUNCTIONS_WARNING);
348
+ }
349
+ if (!func.hasValidName()) {
350
+ warn(`Function name '${func.name}' is invalid. It should consist only of alphanumeric characters, hyphen & underscores.`);
351
+ }
352
+ // If the function file is a ZIP, we extract it and rewire its main file to
353
+ // the new location.
354
+ if (extname(func.mainFile) === ZIP_EXTENSION) {
355
+ const unzippedDirectory = await this.unzipFunction(func);
356
+ // @ts-expect-error TS(2339) FIXME: Property 'debug' does not exist on type 'Functions... Remove this comment to see the full error message
357
+ if (this.debug) {
358
+ FunctionsRegistry.logEvent('extracted', { func });
359
+ }
360
+ // If there's a manifest file, look up the function in order to extract
361
+ // the build data.
362
+ // @ts-expect-error TS(2339) FIXME: Property 'manifest' does not exist on type 'Functi... Remove this comment to see the full error message
363
+ const manifestEntry = (this.manifest?.functions || []).find((manifestFunc) => manifestFunc.name === func.name);
364
+ func.buildData = manifestEntry?.buildData || {};
365
+ // When we look at an unzipped function, we don't know whether it uses
366
+ // the legacy entry file format (i.e. `[function name].js`) or the new
367
+ // one (i.e. `___netlify-entry-point.mjs`). Let's look for the new one
368
+ // and use it if it exists, otherwise use the old one.
369
+ try {
370
+ const v2EntryPointPath = join(unzippedDirectory, '___netlify-entry-point.mjs');
371
+ await stat(v2EntryPointPath);
372
+ func.mainFile = v2EntryPointPath;
373
+ }
374
+ catch {
375
+ func.mainFile = join(unzippedDirectory, `${func.name}.js`);
376
+ }
377
+ }
378
+ else {
379
+ this.buildFunctionAndWatchFiles(func, !isReload);
380
+ }
381
+ // @ts-expect-error TS(2339) FIXME: Property 'functions' does not exist on type 'Funct... Remove this comment to see the full error message
382
+ this.functions.set(name, func);
383
+ }
96
384
  /**
97
- * Keeps track of whether we've checked whether `TYPES_PACKAGE` is
98
- * installed.
385
+ * A proxy to zip-it-and-ship-it's `listFunctions` method. It exists just so
386
+ * that we can mock it in tests.
387
+ * @param {Parameters<listFunctions>} args
388
+ * @returns
99
389
  */
100
- this.hasCheckedTypesPackage = false
101
-
390
+ // @ts-expect-error TS(7019) FIXME: Rest parameter 'args' implicitly has an 'any[]' ty... Remove this comment to see the full error message
391
+ // eslint-disable-next-line class-methods-use-this
392
+ async listFunctions(...args) {
393
+ // @ts-expect-error TS(2556) FIXME: A spread argument must either have a tuple type or... Remove this comment to see the full error message
394
+ return await listFunctions(...args);
395
+ }
102
396
  /**
103
- * Whether to log V1 functions as using the "Lambda compatibility mode"
397
+ * Takes a list of directories and scans for functions. It keeps tracks of
398
+ * any functions in those directories that we've previously seen, and takes
399
+ * care of registering and unregistering functions as they come and go.
104
400
  *
105
- * @type {boolean}
401
+ * @param {string[]} relativeDirs
106
402
  */
107
- this.logLambdaCompat = Boolean(logLambdaCompat)
108
-
403
+ // @ts-expect-error TS(7006) FIXME: Parameter 'relativeDirs' implicitly has an 'any' t... Remove this comment to see the full error message
404
+ async scan(relativeDirs) {
405
+ // @ts-expect-error TS(7006) FIXME: Parameter 'dir' implicitly has an 'any' type.
406
+ const directories = relativeDirs.filter(Boolean).map((dir) => (isAbsolute(dir) ? dir : join(this.projectRoot, dir)));
407
+ // check after filtering to filter out [undefined] for example
408
+ if (directories.length === 0) {
409
+ return;
410
+ }
411
+ // @ts-expect-error TS(7006) FIXME: Parameter 'path' implicitly has an 'any' type.
412
+ await Promise.all(directories.map((path) => FunctionsRegistry.prepareDirectoryScan(path)));
413
+ const functions = await this.listFunctions(directories, {
414
+ featureFlags: {
415
+ buildRustSource: env.NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE === 'true',
416
+ },
417
+ configFileDirectories: [getPathInProject([INTERNAL_FUNCTIONS_FOLDER])],
418
+ // @ts-expect-error TS(2339) FIXME: Property 'config' does not exist on type 'Function... Remove this comment to see the full error message
419
+ config: this.config.functions,
420
+ });
421
+ // Before registering any functions, we look for any functions that were on
422
+ // the previous list but are missing from the new one. We unregister them.
423
+ // @ts-expect-error TS(2339) FIXME: Property 'functions' does not exist on type 'Funct... Remove this comment to see the full error message
424
+ const deletedFunctions = [...this.functions.values()].filter((oldFunc) => {
425
+ const isFound = functions.some((newFunc) => newFunc.name === oldFunc.name && newFunc.mainFile === oldFunc.mainFile);
426
+ return !isFound;
427
+ });
428
+ await Promise.all(deletedFunctions.map((func) => this.unregisterFunction(func)));
429
+ const deletedFunctionNames = new Set(deletedFunctions.map((func) => func.name));
430
+ const addedFunctions = await Promise.all(
431
+ // zip-it-and-ship-it returns an array sorted based on which extension should have precedence,
432
+ // where the last ones precede the previous ones. This is why
433
+ // we reverse the array so we get the right functions precedence in the CLI.
434
+ functions.reverse().map(async ({ displayName, mainFile, name, runtime: runtimeName }) => {
435
+ const runtime = runtimes[runtimeName];
436
+ // If there is no matching runtime, it means this function is not yet
437
+ // supported in Netlify Dev.
438
+ if (runtime === undefined) {
439
+ return;
440
+ }
441
+ // If this function has already been registered, we skip it.
442
+ // @ts-expect-error TS(2339) FIXME: Property 'functions' does not exist on type 'Funct... Remove this comment to see the full error message
443
+ if (this.functions.has(name)) {
444
+ return;
445
+ }
446
+ const func = new NetlifyFunction({
447
+ // @ts-expect-error TS(2339) FIXME: Property 'blobsContext' does not exist on type 'Fu... Remove this comment to see the full error message
448
+ blobsContext: this.blobsContext,
449
+ // @ts-expect-error TS(2339) FIXME: Property 'config' does not exist on type 'Function... Remove this comment to see the full error message
450
+ config: this.config,
451
+ // @ts-expect-error TS(7006) FIXME: Parameter 'directory' implicitly has an 'any' type... Remove this comment to see the full error message
452
+ directory: directories.find((directory) => mainFile.startsWith(directory)),
453
+ mainFile,
454
+ name,
455
+ displayName,
456
+ // @ts-expect-error TS(2339) FIXME: Property 'projectRoot' does not exist on type 'Fun... Remove this comment to see the full error message
457
+ projectRoot: this.projectRoot,
458
+ runtime,
459
+ // @ts-expect-error TS(2339) FIXME: Property 'timeouts' does not exist on type 'Functi... Remove this comment to see the full error message
460
+ timeoutBackground: this.timeouts.backgroundFunctions,
461
+ // @ts-expect-error TS(2339) FIXME: Property 'timeouts' does not exist on type 'Functi... Remove this comment to see the full error message
462
+ timeoutSynchronous: this.timeouts.syncFunctions,
463
+ // @ts-expect-error TS(2339) FIXME: Property 'settings' does not exist on type 'Functi... Remove this comment to see the full error message
464
+ settings: this.settings,
465
+ });
466
+ // If a function we're registering was also unregistered in this run,
467
+ // then it was a rename. Let's flag it as such so that the messaging
468
+ // is adjusted accordingly.
469
+ const isReload = deletedFunctionNames.has(name);
470
+ await this.registerFunction(name, func, isReload);
471
+ return func;
472
+ }));
473
+ // @ts-expect-error TS(2339) FIXME: Property 'name' does not exist on type 'NetlifyFun... Remove this comment to see the full error message
474
+ const addedFunctionNames = new Set(addedFunctions.filter(Boolean).map((func) => func?.name));
475
+ deletedFunctions.forEach((func) => {
476
+ // If a function we've unregistered was also registered in this run, then
477
+ // it was a rename that we've already logged. Nothing to do in this case.
478
+ if (addedFunctionNames.has(func.name)) {
479
+ return;
480
+ }
481
+ FunctionsRegistry.logEvent('removed', { func });
482
+ });
483
+ // @ts-expect-error TS(7006) FIXME: Parameter 'path' implicitly has an 'any' type.
484
+ await Promise.all(directories.map((path) => this.setupDirectoryWatcher(path)));
485
+ }
109
486
  /**
110
- * Contents of a `manifest.json` file that can be looked up when dealing
111
- * with built functions.
487
+ * Creates a watcher that looks at files being added or removed from a
488
+ * functions directory. It doesn't care about files being changed, because
489
+ * those will be handled by each functions' watcher.
112
490
  *
113
- * @type {object}
491
+ * @param {string} directory
114
492
  */
115
- this.manifest = manifest
116
- }
117
-
118
- checkTypesPackage() {
119
- if (this.hasCheckedTypesPackage) {
120
- return
121
- }
122
-
123
- this.hasCheckedTypesPackage = true
124
-
125
- const require = createRequire(this.projectRoot)
126
-
127
- try {
128
- require.resolve(TYPES_PACKAGE, { paths: [this.projectRoot] })
129
- } catch (error) {
130
- if (error?.code === 'MODULE_NOT_FOUND') {
131
- FunctionsRegistry.logEvent('missing-types-package', {})
132
- }
133
- }
134
- }
135
-
136
- /**
137
- * Runs before `scan` and calls any `onDirectoryScan` hooks defined by the
138
- * runtime before the directory is read. This gives runtime the opportunity
139
- * to run additional logic when a directory is scanned.
140
- *
141
- * @param {string} directory
142
- */
143
- static async prepareDirectoryScan(directory) {
144
- await mkdir(directory, { recursive: true })
145
-
146
- // We give runtimes the opportunity to react to a directory scan and run
147
- // additional logic before the directory is read. So if they implement a
148
- // `onDirectoryScan` hook, we run it.
149
- await Promise.all(
150
- Object.values(runtimes).map((runtime) => {
151
- if (typeof runtime.onDirectoryScan !== 'function') {
152
- return null
493
+ // @ts-expect-error TS(7006) FIXME: Parameter 'directory' implicitly has an 'any' type... Remove this comment to see the full error message
494
+ async setupDirectoryWatcher(directory) {
495
+ // @ts-expect-error TS(2339) FIXME: Property 'directoryWatchers' does not exist on typ... Remove this comment to see the full error message
496
+ if (this.directoryWatchers.has(directory)) {
497
+ return;
153
498
  }
154
-
155
- return runtime.onDirectoryScan({ directory })
156
- }),
157
- )
158
- }
159
-
160
- /**
161
- * Builds a function and sets up the appropriate file watchers so that any
162
- * changes will trigger another build.
163
- *
164
- * @param {NetlifyFunction} func
165
- * @param {boolean} [firstLoad ]
166
- * @returns
167
- */
168
- async buildFunctionAndWatchFiles(func, firstLoad = false) {
169
- if (!firstLoad) {
170
- FunctionsRegistry.logEvent('reloading', { func })
171
- }
172
-
173
- const { error: buildError, includedFiles, srcFilesDiff } = await func.build({ cache: this.buildCache })
174
-
175
- if (buildError) {
176
- FunctionsRegistry.logEvent('buildError', { func })
177
- } else {
178
- const event = firstLoad ? 'loaded' : 'reloaded'
179
- const recommendedExtension = func.getRecommendedExtension()
180
-
181
- if (recommendedExtension) {
182
- const { filename } = func
183
- const newFilename = filename ? `${basename(filename, extname(filename))}${recommendedExtension}` : null
184
- const action = newFilename
185
- ? `rename the function file to ${chalk.underline(
186
- newFilename,
187
- )}. Refer to https://ntl.fyi/functions-runtime for more information`
188
- : `refer to https://ntl.fyi/functions-runtime`
189
- const warning = `The function is using the legacy CommonJS format. To start using ES modules, ${action}.`
190
-
191
- FunctionsRegistry.logEvent(event, { func, warnings: [warning] })
192
- } else {
193
- FunctionsRegistry.logEvent(event, { func })
194
- }
195
- }
196
-
197
- if (func.isTypeScript()) {
198
- this.checkTypesPackage()
199
- }
200
-
201
- // If the build hasn't resulted in any files being added or removed, there
202
- // is nothing else we need to do.
203
- if (!srcFilesDiff) {
204
- return
205
- }
206
-
207
- const watcher = this.functionWatchers.get(func.name)
208
-
209
- // If there is already a watcher for this function, we need to unwatch any
210
- // files that have been removed and watch any files that have been added.
211
- if (watcher) {
212
- srcFilesDiff.deleted.forEach((path) => {
213
- watcher.unwatch(path)
214
- })
215
-
216
- srcFilesDiff.added.forEach((path) => {
217
- watcher.add(path)
218
- })
219
-
220
- return
221
- }
222
-
223
- // If there is no watcher for this function but the build produced files,
224
- // we create a new watcher and watch them.
225
- if (srcFilesDiff.added.size !== 0) {
226
- const filesToWatch = [...srcFilesDiff.added, ...includedFiles]
227
- const newWatcher = await watchDebounced(filesToWatch, {
228
- onChange: () => {
229
- this.buildFunctionAndWatchFiles(func, false)
230
- },
231
- })
232
-
233
- this.functionWatchers.set(func.name, newWatcher)
234
- }
235
- }
236
-
237
- /**
238
- * Returns a function by name.
239
- *
240
- * @param {string} name
241
- */
242
- get(name) {
243
- return this.functions.get(name)
244
- }
245
-
246
- /**
247
- * Looks for the first function that matches a given URL path. If a match is
248
- * found, returns an object with the function and the route. If the URL path
249
- * matches the default functions URL (i.e. can only be for a function) but no
250
- * function with the given name exists, returns an object with the function
251
- * and the route set to `null`. Otherwise, `undefined` is returned,
252
- *
253
- * @param {string} url
254
- * @param {string} method
255
- */
256
- async getFunctionForURLPath(url, method) {
257
- // We're constructing a URL object just so that we can extract the path from
258
- // the incoming URL. It doesn't really matter that we don't have the actual
259
- // local URL with the correct port.
260
- const urlPath = new URL(url, 'http://localhost').pathname
261
- const defaultURLMatch = urlPath.match(DEFAULT_FUNCTION_URL_EXPRESSION)
262
-
263
- if (defaultURLMatch) {
264
- const func = this.get(defaultURLMatch[2])
265
-
266
- if (!func) {
267
- return { func: null, route: null }
268
- }
269
-
270
- const { routes = [] } = await func.getBuildData()
271
-
272
- if (routes.length !== 0) {
273
- const paths = routes.map((route) => chalk.underline(route.pattern)).join(', ')
274
-
275
- warn(
276
- `Function ${chalk.yellow(func.name)} cannot be invoked on ${chalk.underline(
277
- urlPath,
278
- )}, because the function has the following URL paths defined: ${paths}`,
279
- )
280
-
281
- return
282
- }
283
-
284
- return { func, route: null }
285
- }
286
-
287
- for (const func of this.functions.values()) {
288
- const route = await func.matchURLPath(urlPath, method)
289
-
290
- if (route) {
291
- return { func, route }
292
- }
293
- }
294
- }
295
-
296
- /**
297
- * Logs an event associated with functions.
298
- *
299
- * @param {FunctionEvent} event
300
- * @param {object} data
301
- * @param {NetlifyFunction} [data.func]
302
- * @param {string[]} [data.warnings]
303
- * @returns
304
- */
305
- static logEvent(event, { func, warnings = [] }) {
306
- let warningsText = ''
307
-
308
- if (warnings.length !== 0) {
309
- warningsText = ` with warnings:\n${warnings.map((warning) => ` - ${warning}`).join('\n')}`
310
- }
311
-
312
- if (event === 'buildError') {
313
- log(
314
- `${NETLIFYDEVERR} ${chalk.red('Failed to load')} function ${chalk.yellow(func?.displayName)}: ${
315
- func?.buildError?.message
316
- }`,
317
- )
318
- }
319
-
320
- if (event === 'extracted') {
321
- log(
322
- `${NETLIFYDEVLOG} ${chalk.green('Extracted')} function ${chalk.yellow(func?.displayName)} from ${
323
- func?.mainFile
324
- }.`,
325
- )
326
-
327
- return
499
+ const watcher = await watchDebounced(directory, {
500
+ depth: 1,
501
+ onAdd: () => {
502
+ this.scan([directory]);
503
+ },
504
+ onUnlink: () => {
505
+ this.scan([directory]);
506
+ },
507
+ });
508
+ // @ts-expect-error TS(2339) FIXME: Property 'directoryWatchers' does not exist on typ... Remove this comment to see the full error message
509
+ this.directoryWatchers.set(directory, watcher);
328
510
  }
329
-
330
- if (event === 'loaded') {
331
- const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG
332
- const color = warningsText ? chalk.yellow : chalk.green
333
- const mode =
334
- func?.runtimeAPIVersion === 1 && this.logLambdaCompat
335
- ? ` in ${getTerminalLink('Lambda compatibility mode', 'https://ntl.fyi/lambda-compat')}`
336
- : ''
337
-
338
- log(`${icon} ${color('Loaded')} function ${chalk.yellow(func?.displayName)}${mode}${warningsText}`)
339
-
340
- return
341
- }
342
-
343
- if (event === 'missing-types-package') {
344
- log(
345
- `${NETLIFYDEVWARN} For a better experience with TypeScript functions, consider installing the ${chalk.underline(
346
- TYPES_PACKAGE,
347
- )} package. Refer to https://ntl-fyi/function-types for more information.`,
348
- )
349
- }
350
-
351
- if (event === 'reloaded') {
352
- const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG
353
- const color = warningsText ? chalk.yellow : chalk.green
354
-
355
- log(`${icon} ${color('Reloaded')} function ${chalk.yellow(func?.displayName)}${warningsText}`)
356
-
357
- return
358
- }
359
-
360
- if (event === 'reloading') {
361
- log(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} function ${chalk.yellow(func?.displayName)}...`)
362
-
363
- return
364
- }
365
-
366
- if (event === 'removed') {
367
- log(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} function ${chalk.yellow(func?.displayName)}`)
368
- }
369
- }
370
-
371
- /**
372
- * Adds a function to the registry
373
- *
374
- * @param {string} name
375
- * @param {NetlifyFunction} funcBeforeHook
376
- * @param {boolean} [isReload]
377
- * @returns
378
- */
379
- async registerFunction(name, funcBeforeHook, isReload = false) {
380
- const { runtime } = funcBeforeHook
381
-
382
- // The `onRegister` hook allows runtimes to modify the function before it's
383
- // registered, or to prevent it from being registered altogether if the
384
- // hook returns `null`.
385
- const func = typeof runtime.onRegister === 'function' ? runtime.onRegister(funcBeforeHook) : funcBeforeHook
386
-
387
- if (func === null) {
388
- return
389
- }
390
-
391
- if (func.isBackground && this.isConnected && !this.capabilities.backgroundFunctions) {
392
- warn(BACKGROUND_FUNCTIONS_WARNING)
393
- }
394
-
395
- if (!func.hasValidName()) {
396
- warn(
397
- `Function name '${func.name}' is invalid. It should consist only of alphanumeric characters, hyphen & underscores.`,
398
- )
399
- }
400
-
401
- // If the function file is a ZIP, we extract it and rewire its main file to
402
- // the new location.
403
- if (extname(func.mainFile) === ZIP_EXTENSION) {
404
- const unzippedDirectory = await this.unzipFunction(func)
405
-
406
- if (this.debug) {
407
- FunctionsRegistry.logEvent('extracted', { func })
408
- }
409
-
410
- // If there's a manifest file, look up the function in order to extract
411
- // the build data.
412
- const manifestEntry = (this.manifest?.functions || []).find((manifestFunc) => manifestFunc.name === func.name)
413
-
414
- func.buildData = manifestEntry?.buildData || {}
415
-
416
- // When we look at an unzipped function, we don't know whether it uses
417
- // the legacy entry file format (i.e. `[function name].js`) or the new
418
- // one (i.e. `___netlify-entry-point.mjs`). Let's look for the new one
419
- // and use it if it exists, otherwise use the old one.
420
- try {
421
- const v2EntryPointPath = join(unzippedDirectory, '___netlify-entry-point.mjs')
422
-
423
- await stat(v2EntryPointPath)
424
-
425
- func.mainFile = v2EntryPointPath
426
- } catch {
427
- func.mainFile = join(unzippedDirectory, `${func.name}.js`)
428
- }
429
- } else {
430
- this.buildFunctionAndWatchFiles(func, !isReload)
431
- }
432
-
433
- this.functions.set(name, func)
434
- }
435
-
436
- /**
437
- * A proxy to zip-it-and-ship-it's `listFunctions` method. It exists just so
438
- * that we can mock it in tests.
439
- * @param {Parameters<listFunctions>} args
440
- * @returns
441
- */
442
- // eslint-disable-next-line class-methods-use-this
443
- async listFunctions(...args) {
444
- return await listFunctions(...args)
445
- }
446
-
447
- /**
448
- * Takes a list of directories and scans for functions. It keeps tracks of
449
- * any functions in those directories that we've previously seen, and takes
450
- * care of registering and unregistering functions as they come and go.
451
- *
452
- * @param {string[]} relativeDirs
453
- */
454
- async scan(relativeDirs) {
455
- const directories = relativeDirs.filter(Boolean).map((dir) => (isAbsolute(dir) ? dir : join(this.projectRoot, dir)))
456
-
457
- // check after filtering to filter out [undefined] for example
458
- if (directories.length === 0) {
459
- return
460
- }
461
-
462
- await Promise.all(directories.map((path) => FunctionsRegistry.prepareDirectoryScan(path)))
463
-
464
- const functions = await this.listFunctions(directories, {
465
- featureFlags: {
466
- buildRustSource: env.NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE === 'true',
467
- },
468
- configFileDirectories: [getPathInProject([INTERNAL_FUNCTIONS_FOLDER])],
469
- config: this.config.functions,
470
- })
471
-
472
- // Before registering any functions, we look for any functions that were on
473
- // the previous list but are missing from the new one. We unregister them.
474
- const deletedFunctions = [...this.functions.values()].filter((oldFunc) => {
475
- const isFound = functions.some(
476
- (newFunc) => newFunc.name === oldFunc.name && newFunc.mainFile === oldFunc.mainFile,
477
- )
478
-
479
- return !isFound
480
- })
481
-
482
- await Promise.all(deletedFunctions.map((func) => this.unregisterFunction(func)))
483
-
484
- const deletedFunctionNames = new Set(deletedFunctions.map((func) => func.name))
485
- const addedFunctions = await Promise.all(
486
- // zip-it-and-ship-it returns an array sorted based on which extension should have precedence,
487
- // where the last ones precede the previous ones. This is why
488
- // we reverse the array so we get the right functions precedence in the CLI.
489
- functions.reverse().map(async ({ displayName, mainFile, name, runtime: runtimeName }) => {
490
- const runtime = runtimes[runtimeName]
491
-
492
- // If there is no matching runtime, it means this function is not yet
493
- // supported in Netlify Dev.
494
- if (runtime === undefined) {
495
- return
496
- }
497
-
498
- // If this function has already been registered, we skip it.
499
- if (this.functions.has(name)) {
500
- return
511
+ /**
512
+ * Removes a function from the registry and closes its file watchers.
513
+ *
514
+ * @param {NetlifyFunction} func
515
+ */
516
+ // @ts-expect-error TS(7006) FIXME: Parameter 'func' implicitly has an 'any' type.
517
+ async unregisterFunction(func) {
518
+ const { name } = func;
519
+ // @ts-expect-error TS(2339) FIXME: Property 'functions' does not exist on type 'Funct... Remove this comment to see the full error message
520
+ this.functions.delete(name);
521
+ // @ts-expect-error TS(2339) FIXME: Property 'functionWatchers' does not exist on type... Remove this comment to see the full error message
522
+ const watcher = this.functionWatchers.get(name);
523
+ if (watcher) {
524
+ await watcher.close();
501
525
  }
502
-
503
- const func = new NetlifyFunction({
504
- blobsContext: this.blobsContext,
505
- config: this.config,
506
- directory: directories.find((directory) => mainFile.startsWith(directory)),
507
- mainFile,
508
- name,
509
- displayName,
510
- projectRoot: this.projectRoot,
511
- runtime,
512
- timeoutBackground: this.timeouts.backgroundFunctions,
513
- timeoutSynchronous: this.timeouts.syncFunctions,
514
- settings: this.settings,
515
- })
516
-
517
- // If a function we're registering was also unregistered in this run,
518
- // then it was a rename. Let's flag it as such so that the messaging
519
- // is adjusted accordingly.
520
- const isReload = deletedFunctionNames.has(name)
521
-
522
- await this.registerFunction(name, func, isReload)
523
-
524
- return func
525
- }),
526
- )
527
- const addedFunctionNames = new Set(addedFunctions.filter(Boolean).map((func) => func?.name))
528
-
529
- deletedFunctions.forEach((func) => {
530
- // If a function we've unregistered was also registered in this run, then
531
- // it was a rename that we've already logged. Nothing to do in this case.
532
- if (addedFunctionNames.has(func.name)) {
533
- return
534
- }
535
-
536
- FunctionsRegistry.logEvent('removed', { func })
537
- })
538
-
539
- await Promise.all(directories.map((path) => this.setupDirectoryWatcher(path)))
540
- }
541
-
542
- /**
543
- * Creates a watcher that looks at files being added or removed from a
544
- * functions directory. It doesn't care about files being changed, because
545
- * those will be handled by each functions' watcher.
546
- *
547
- * @param {string} directory
548
- */
549
- async setupDirectoryWatcher(directory) {
550
- if (this.directoryWatchers.has(directory)) {
551
- return
526
+ // @ts-expect-error TS(2339) FIXME: Property 'functionWatchers' does not exist on type... Remove this comment to see the full error message
527
+ this.functionWatchers.delete(name);
552
528
  }
553
-
554
- const watcher = await watchDebounced(directory, {
555
- depth: 1,
556
- onAdd: () => {
557
- this.scan([directory])
558
- },
559
- onUnlink: () => {
560
- this.scan([directory])
561
- },
562
- })
563
-
564
- this.directoryWatchers.set(directory, watcher)
565
- }
566
-
567
- /**
568
- * Removes a function from the registry and closes its file watchers.
569
- *
570
- * @param {NetlifyFunction} func
571
- */
572
- async unregisterFunction(func) {
573
- const { name } = func
574
-
575
- this.functions.delete(name)
576
-
577
- const watcher = this.functionWatchers.get(name)
578
-
579
- if (watcher) {
580
- await watcher.close()
529
+ /**
530
+ * Takes a zipped function and extracts its contents to an internal directory.
531
+ *
532
+ * @param {NetlifyFunction} func
533
+ */
534
+ // @ts-expect-error TS(7006) FIXME: Parameter 'func' implicitly has an 'any' type.
535
+ async unzipFunction(func) {
536
+ const targetDirectory = resolve(
537
+ // @ts-expect-error TS(2339) FIXME: Property 'projectRoot' does not exist on type 'Fun... Remove this comment to see the full error message
538
+ this.projectRoot, getPathInProject([SERVE_FUNCTIONS_FOLDER, '.unzipped', func.name]));
539
+ await extractZip(func.mainFile, { dir: targetDirectory });
540
+ return targetDirectory;
581
541
  }
582
-
583
- this.functionWatchers.delete(name)
584
- }
585
-
586
- /**
587
- * Takes a zipped function and extracts its contents to an internal directory.
588
- *
589
- * @param {NetlifyFunction} func
590
- */
591
- async unzipFunction(func) {
592
- const targetDirectory = resolve(
593
- this.projectRoot,
594
- getPathInProject([SERVE_FUNCTIONS_FOLDER, '.unzipped', func.name]),
595
- )
596
-
597
- await extractZip(func.mainFile, { dir: targetDirectory })
598
-
599
- return targetDirectory
600
- }
601
542
  }