netlify-cli 17.3.2 → 17.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -138
- package/npm-shrinkwrap.json +76 -76
- package/package.json +16 -15
- package/src/commands/addons/addons-auth.mjs +27 -30
- package/src/commands/addons/addons-config.mjs +145 -154
- package/src/commands/addons/addons-create.mjs +94 -108
- package/src/commands/addons/addons-delete.mjs +36 -41
- package/src/commands/addons/addons-list.mjs +38 -42
- package/src/commands/addons/addons.mjs +26 -28
- package/src/commands/addons/index.mjs +1 -1
- package/src/commands/api/api.mjs +45 -53
- package/src/commands/api/index.mjs +1 -1
- package/src/commands/base-command.mjs +597 -684
- package/src/commands/blobs/blobs-delete.mjs +35 -0
- package/src/commands/blobs/blobs-get.mjs +44 -0
- package/src/commands/blobs/blobs-list.mjs +48 -0
- package/src/commands/blobs/blobs-set.mjs +54 -0
- package/src/commands/blobs/blobs.mjs +32 -0
- package/src/commands/blobs/index.mjs +1 -0
- package/src/commands/build/build.mjs +55 -67
- package/src/commands/build/index.mjs +1 -1
- package/src/commands/completion/completion.mjs +41 -46
- package/src/commands/completion/index.mjs +1 -1
- package/src/commands/deploy/deploy.mjs +675 -710
- package/src/commands/deploy/index.mjs +1 -1
- package/src/commands/dev/dev-exec.mjs +20 -32
- package/src/commands/dev/dev.mjs +217 -302
- package/src/commands/dev/index.mjs +1 -1
- package/src/commands/dev/types.d.ts +30 -0
- package/src/commands/env/env-clone.mjs +157 -184
- package/src/commands/env/env-get.mjs +49 -68
- package/src/commands/env/env-import.mjs +100 -119
- package/src/commands/env/env-list.mjs +104 -129
- package/src/commands/env/env-set.mjs +160 -185
- package/src/commands/env/env-unset.mjs +104 -122
- package/src/commands/env/env.mjs +28 -30
- package/src/commands/env/index.mjs +1 -1
- package/src/commands/functions/functions-build.mjs +29 -41
- package/src/commands/functions/functions-create.mjs +533 -601
- package/src/commands/functions/functions-invoke.mjs +193 -216
- package/src/commands/functions/functions-list.mjs +45 -55
- package/src/commands/functions/functions-serve.mjs +51 -61
- package/src/commands/functions/functions.mjs +26 -32
- package/src/commands/functions/index.mjs +1 -1
- package/src/commands/index.mjs +2 -2
- package/src/commands/init/index.mjs +1 -1
- package/src/commands/init/init.mjs +138 -167
- package/src/commands/integration/deploy.mjs +337 -399
- package/src/commands/integration/index.mjs +12 -13
- package/src/commands/link/index.mjs +1 -1
- package/src/commands/link/link.mjs +298 -317
- package/src/commands/lm/index.mjs +1 -1
- package/src/commands/lm/lm-info.mjs +23 -31
- package/src/commands/lm/lm-install.mjs +13 -17
- package/src/commands/lm/lm-setup.mjs +80 -84
- package/src/commands/lm/lm-uninstall.mjs +7 -12
- package/src/commands/lm/lm.mjs +18 -22
- package/src/commands/login/index.mjs +1 -1
- package/src/commands/login/login.mjs +35 -41
- package/src/commands/logout/index.mjs +1 -1
- package/src/commands/logout/logout.mjs +25 -31
- package/src/commands/main.mjs +166 -201
- package/src/commands/open/index.mjs +1 -1
- package/src/commands/open/open-admin.mjs +15 -18
- package/src/commands/open/open-site.mjs +16 -19
- package/src/commands/open/open.mjs +24 -27
- package/src/commands/recipes/common.mjs +23 -34
- package/src/commands/recipes/index.mjs +1 -1
- package/src/commands/recipes/recipes-list.mjs +13 -20
- package/src/commands/recipes/recipes.mjs +59 -72
- package/src/commands/serve/index.mjs +1 -1
- package/src/commands/serve/serve.mjs +142 -189
- package/src/commands/sites/index.mjs +2 -2
- package/src/commands/sites/sites-create-template.mjs +214 -236
- package/src/commands/sites/sites-create.mjs +145 -157
- package/src/commands/sites/sites-delete.mjs +75 -81
- package/src/commands/sites/sites-list.mjs +63 -66
- package/src/commands/sites/sites.mjs +18 -20
- package/src/commands/status/index.mjs +1 -1
- package/src/commands/status/status-hooks.mjs +32 -34
- package/src/commands/status/status.mjs +99 -106
- package/src/commands/switch/index.mjs +1 -1
- package/src/commands/switch/switch.mjs +32 -37
- package/src/commands/types.d.ts +31 -0
- package/src/commands/unlink/index.mjs +1 -1
- package/src/commands/unlink/unlink.mjs +23 -29
- package/src/commands/watch/index.mjs +1 -1
- package/src/commands/watch/watch.mjs +91 -105
- package/src/functions-templates/javascript/hello/{{name}}.js +2 -3
- package/src/lib/account.mjs +4 -5
- package/src/lib/api.mjs +22 -20
- package/src/lib/blobs/blobs.mjs +36 -45
- package/src/lib/build.mjs +82 -85
- package/src/lib/completion/constants.mjs +2 -4
- package/src/lib/completion/generate-autocompletion.mjs +33 -36
- package/src/lib/completion/get-autocompletion.mjs +31 -35
- package/src/lib/completion/index.mjs +1 -1
- package/src/lib/completion/script.mjs +12 -19
- package/src/lib/edge-functions/bootstrap.mjs +3 -5
- package/src/lib/edge-functions/consts.mjs +9 -10
- package/src/lib/edge-functions/deploy.mjs +28 -34
- package/src/lib/edge-functions/editor-helper.mjs +29 -42
- package/src/lib/edge-functions/headers.mjs +24 -26
- package/src/lib/edge-functions/internal.mjs +38 -44
- package/src/lib/edge-functions/proxy.mjs +229 -228
- package/src/lib/edge-functions/registry.mjs +473 -574
- package/src/lib/exec-fetcher.mjs +115 -122
- package/src/lib/fs.mjs +28 -27
- package/src/lib/functions/background.mjs +16 -20
- package/src/lib/functions/config.mjs +12 -9
- package/src/lib/functions/form-submissions-handler.mjs +143 -149
- package/src/lib/functions/local-proxy.mjs +40 -44
- package/src/lib/functions/memoized-build.mjs +19 -21
- package/src/lib/functions/netlify-function.mjs +269 -249
- package/src/lib/functions/registry.mjs +509 -568
- package/src/lib/functions/runtimes/go/index.mjs +62 -71
- package/src/lib/functions/runtimes/index.mjs +8 -15
- package/src/lib/functions/runtimes/js/builders/netlify-lambda.mjs +55 -64
- package/src/lib/functions/runtimes/js/builders/zisi.mjs +135 -154
- package/src/lib/functions/runtimes/js/constants.mjs +1 -1
- package/src/lib/functions/runtimes/js/index.mjs +92 -109
- package/src/lib/functions/runtimes/js/worker.mjs +43 -45
- package/src/lib/functions/runtimes/rust/index.mjs +64 -73
- package/src/lib/functions/scheduled.mjs +70 -88
- package/src/lib/functions/server.mjs +269 -327
- package/src/lib/functions/synchronous.mjs +118 -147
- package/src/lib/functions/utils.mjs +38 -46
- package/src/lib/geo-location.mjs +69 -81
- package/src/lib/http-agent.mjs +87 -90
- package/src/lib/images/proxy.mjs +97 -99
- package/src/lib/log.mjs +6 -9
- package/src/lib/path.mjs +2 -1
- package/src/lib/render-error-template.mjs +19 -20
- package/src/lib/settings.mjs +17 -19
- package/src/lib/spinner.mjs +21 -23
- package/src/lib/string.mjs +4 -2
- package/src/recipes/vscode/index.mjs +69 -85
- package/src/recipes/vscode/settings.mjs +53 -58
- package/src/utils/addons/compare.mjs +31 -32
- package/src/utils/addons/diffs/index.mjs +16 -17
- package/src/utils/addons/diffs/options.mjs +99 -101
- package/src/utils/addons/prepare.mjs +100 -97
- package/src/utils/addons/prompts.mjs +73 -76
- package/src/utils/addons/render.mjs +33 -36
- package/src/utils/addons/validation.mjs +19 -15
- package/src/utils/banner.mjs +11 -16
- package/src/utils/build-info.mjs +65 -66
- package/src/utils/command-helpers.mjs +185 -199
- package/src/utils/create-deferred.mjs +9 -12
- package/src/utils/create-stream-promise.mjs +54 -47
- package/src/utils/deploy/constants.mjs +9 -11
- package/src/utils/deploy/deploy-site.mjs +162 -182
- package/src/utils/deploy/hash-config.mjs +21 -21
- package/src/utils/deploy/hash-files.mjs +34 -38
- package/src/utils/deploy/hash-fns.mjs +149 -154
- package/src/utils/deploy/hasher-segments.mjs +58 -52
- package/src/utils/deploy/upload-files.mjs +99 -113
- package/src/utils/deploy/util.mjs +85 -91
- package/src/utils/detect-server-settings.mjs +236 -268
- package/src/utils/dev.mjs +163 -178
- package/src/utils/dot-env.mjs +37 -42
- package/src/utils/env/index.mjs +148 -148
- package/src/utils/execa.mjs +9 -13
- package/src/utils/feature-flags.mjs +6 -5
- package/src/utils/framework-server.mjs +43 -52
- package/src/utils/functions/constants.mjs +1 -1
- package/src/utils/functions/functions.mjs +30 -40
- package/src/utils/functions/get-functions.mjs +28 -29
- package/src/utils/functions/index.mjs +3 -3
- package/src/utils/get-global-config.mjs +33 -36
- package/src/utils/get-package-json.mjs +14 -15
- package/src/utils/get-repo-data.mjs +54 -64
- package/src/utils/get-site.mjs +14 -14
- package/src/utils/gh-auth.mjs +79 -100
- package/src/utils/gitignore.mjs +37 -40
- package/src/utils/headers.mjs +33 -35
- package/src/utils/hooks/requires-site-info.mjs +26 -22
- package/src/utils/init/config-github.mjs +207 -219
- package/src/utils/init/config-manual.mjs +83 -100
- package/src/utils/init/config.mjs +25 -26
- package/src/utils/init/node-version.mjs +23 -30
- package/src/utils/init/plugins.mjs +12 -8
- package/src/utils/init/utils.mjs +152 -172
- package/src/utils/live-tunnel.mjs +118 -141
- package/src/utils/lm/install.mjs +220 -259
- package/src/utils/lm/requirements.mjs +54 -63
- package/src/utils/lm/steps.mjs +31 -31
- package/src/utils/lm/ui.mjs +13 -20
- package/src/utils/open-browser.mjs +31 -32
- package/src/utils/parse-raw-flags.mjs +39 -35
- package/src/utils/proxy-server.mjs +84 -71
- package/src/utils/proxy.mjs +696 -750
- package/src/utils/read-repo-url.mjs +48 -47
- package/src/utils/redirects.mjs +49 -49
- package/src/utils/request-id.mjs +2 -4
- package/src/utils/rules-proxy.mjs +96 -100
- package/src/utils/run-build.mjs +109 -132
- package/src/utils/shell.mjs +99 -106
- package/src/utils/sign-redirect.mjs +14 -14
- package/src/utils/sites/utils.mjs +48 -55
- package/src/utils/state-config.mjs +101 -101
- package/src/utils/static-server.mjs +28 -34
- package/src/utils/telemetry/index.mjs +2 -2
- package/src/utils/telemetry/report-error.mjs +45 -49
- package/src/utils/telemetry/request.mjs +36 -43
- package/src/utils/telemetry/telemetry.mjs +90 -105
- package/src/utils/telemetry/utils.mjs +5 -6
- package/src/utils/telemetry/validation.mjs +55 -53
- package/src/utils/types.d.ts +46 -0
- package/src/utils/validation.mjs +10 -13
|
@@ -1,601 +1,542 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
37
|
-
blobsContext
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this
|
|
50
|
-
|
|
51
|
-
this
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
*
|
|
157
|
+
* Builds a function and sets up the appropriate file watchers so that any
|
|
158
|
+
* changes will trigger another build.
|
|
58
159
|
*
|
|
59
|
-
* @
|
|
160
|
+
* @param {NetlifyFunction} func
|
|
161
|
+
* @param {boolean} [firstLoad ]
|
|
162
|
+
* @returns
|
|
60
163
|
*/
|
|
61
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
231
|
+
* @param {string} name
|
|
69
232
|
*/
|
|
70
|
-
|
|
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
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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
|
-
* @
|
|
245
|
+
* @param {string} url
|
|
246
|
+
* @param {string} method
|
|
78
247
|
*/
|
|
79
|
-
|
|
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
|
-
*
|
|
278
|
+
* Logs an event associated with functions.
|
|
83
279
|
*
|
|
84
|
-
* @
|
|
280
|
+
* @param {FunctionEvent} event
|
|
281
|
+
* @param {object} data
|
|
282
|
+
* @param {NetlifyFunction} [data.func]
|
|
283
|
+
* @param {string[]} [data.warnings]
|
|
284
|
+
* @returns
|
|
85
285
|
*/
|
|
86
|
-
|
|
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
|
-
*
|
|
90
|
-
* by the `watchDebounced` utility.
|
|
328
|
+
* Adds a function to the registry
|
|
91
329
|
*
|
|
92
|
-
* @
|
|
330
|
+
* @param {string} name
|
|
331
|
+
* @param {NetlifyFunction} funcBeforeHook
|
|
332
|
+
* @param {boolean} [isReload]
|
|
333
|
+
* @returns
|
|
93
334
|
*/
|
|
94
|
-
|
|
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
|
-
*
|
|
98
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
401
|
+
* @param {string[]} relativeDirs
|
|
106
402
|
*/
|
|
107
|
-
|
|
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
|
-
*
|
|
111
|
-
*
|
|
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
|
-
* @
|
|
491
|
+
* @param {string} directory
|
|
114
492
|
*/
|
|
115
|
-
this
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
}
|