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,46 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
import fetch from 'node-fetch'
|
|
16
|
-
import ora from 'ora'
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
import execa from '../../utils/execa.mjs'
|
|
23
|
-
import { readRepoURL, validateRepoURL } from '../../utils/read-repo-url.mjs'
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const templatesDir = path.resolve(dirname(fileURLToPath(import.meta.url)), '../../functions-templates')
|
|
30
|
-
|
|
31
|
-
const showRustTemplates = process.env.NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE === 'true'
|
|
32
|
-
|
|
1
|
+
import cp from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { mkdir, readdir, unlink } from 'fs/promises';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
import path, { dirname, join, relative } from 'path';
|
|
6
|
+
import process from 'process';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'copy... Remove this comment to see the full error message
|
|
10
|
+
import copyTemplateDirOriginal from 'copy-template-dir';
|
|
11
|
+
import { findUp } from 'find-up';
|
|
12
|
+
import fuzzy from 'fuzzy';
|
|
13
|
+
import inquirer from 'inquirer';
|
|
14
|
+
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'node... Remove this comment to see the full error message
|
|
15
|
+
import fetch from 'node-fetch';
|
|
16
|
+
import ora from 'ora';
|
|
17
|
+
import { fileExistsAsync } from '../../lib/fs.mjs';
|
|
18
|
+
import { getAddons, getCurrentAddon, getSiteData } from '../../utils/addons/prepare.mjs';
|
|
19
|
+
import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, error, log } from '../../utils/command-helpers.mjs';
|
|
20
|
+
import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.mjs';
|
|
21
|
+
// @ts-expect-error TS(7034) FIXME: Variable 'execa' implicitly has type 'any' in some... Remove this comment to see the full error message
|
|
22
|
+
import execa from '../../utils/execa.mjs';
|
|
23
|
+
import { readRepoURL, validateRepoURL } from '../../utils/read-repo-url.mjs';
|
|
24
|
+
const copyTemplateDir = promisify(copyTemplateDirOriginal);
|
|
25
|
+
const require = createRequire(import.meta.url);
|
|
26
|
+
const templatesDir = path.resolve(dirname(fileURLToPath(import.meta.url)), '../../functions-templates');
|
|
27
|
+
const showRustTemplates = process.env.NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE === 'true';
|
|
33
28
|
/**
|
|
34
29
|
* Ensure that there's a sub-directory in `src/functions-templates` named after
|
|
35
30
|
* each `value` property in this list.
|
|
36
31
|
*/
|
|
37
32
|
const languages = [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
]
|
|
43
|
-
|
|
33
|
+
{ name: 'JavaScript', value: 'javascript' },
|
|
34
|
+
{ name: 'TypeScript', value: 'typescript' },
|
|
35
|
+
{ name: 'Go', value: 'go' },
|
|
36
|
+
showRustTemplates && { name: 'Rust', value: 'rust' },
|
|
37
|
+
];
|
|
44
38
|
/**
|
|
45
39
|
* prompt for a name if name not supplied
|
|
46
40
|
* @param {string} argumentName
|
|
@@ -48,284 +42,252 @@ const languages = [
|
|
|
48
42
|
* @param {string} [defaultName]
|
|
49
43
|
* @returns
|
|
50
44
|
*/
|
|
45
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'argumentName' implicitly has an 'any' t... Remove this comment to see the full error message
|
|
51
46
|
const getNameFromArgs = async function (argumentName, options, defaultName) {
|
|
52
|
-
|
|
47
|
+
if (options.name) {
|
|
48
|
+
if (argumentName) {
|
|
49
|
+
throw new Error('function name specified in both flag and arg format, pick one');
|
|
50
|
+
}
|
|
51
|
+
return options.name;
|
|
52
|
+
}
|
|
53
53
|
if (argumentName) {
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// make sure it is not undefined and is a valid filename.
|
|
71
|
-
// this has some nuance i have ignored, eg crossenv and i18n concerns
|
|
72
|
-
},
|
|
73
|
-
])
|
|
74
|
-
return name
|
|
75
|
-
}
|
|
76
|
-
|
|
54
|
+
return argumentName;
|
|
55
|
+
}
|
|
56
|
+
const { name } = await inquirer.prompt([
|
|
57
|
+
{
|
|
58
|
+
name: 'name',
|
|
59
|
+
message: 'Name your function:',
|
|
60
|
+
default: defaultName,
|
|
61
|
+
type: 'input',
|
|
62
|
+
validate: (val) => Boolean(val) && /^[\w.-]+$/i.test(val),
|
|
63
|
+
// make sure it is not undefined and is a valid filename.
|
|
64
|
+
// this has some nuance i have ignored, eg crossenv and i18n concerns
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
return name;
|
|
68
|
+
};
|
|
69
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'registry' implicitly has an 'any' type.
|
|
77
70
|
const filterRegistry = function (registry, input) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
filteredTemplates.map((filteredTemplate) => (input ? filteredTemplate.string : filteredTemplate))
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
71
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'value' implicitly has an 'any' type.
|
|
72
|
+
const temp = registry.map((value) => value.name + value.description);
|
|
73
|
+
// TODO: remove once https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1394 is fixed
|
|
74
|
+
// eslint-disable-next-line unicorn/no-array-method-this-argument
|
|
75
|
+
const filteredTemplates = fuzzy.filter(input, temp);
|
|
76
|
+
const filteredTemplateNames = new Set(filteredTemplates.map((filteredTemplate) => (input ? filteredTemplate.string : filteredTemplate)));
|
|
77
|
+
return registry
|
|
78
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 't' implicitly has an 'any' type.
|
|
79
|
+
.filter((t) => filteredTemplateNames.has(t.name + t.description))
|
|
80
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 't' implicitly has an 'any' type.
|
|
81
|
+
.map((t) => {
|
|
82
|
+
// add the score
|
|
83
|
+
// @ts-expect-error TS(2339) FIXME: Property 'score' does not exist on type 'FilterRes... Remove this comment to see the full error message
|
|
84
|
+
const { score } = filteredTemplates.find((filteredTemplate) => filteredTemplate.string === t.name + t.description);
|
|
85
|
+
t.score = score;
|
|
86
|
+
return t;
|
|
87
|
+
});
|
|
88
|
+
};
|
|
95
89
|
/**
|
|
96
90
|
* @param {string} lang
|
|
97
91
|
* @param {'edge' | 'serverless'} funcType
|
|
98
92
|
*/
|
|
93
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'lang' implicitly has an 'any' type.
|
|
99
94
|
const formatRegistryArrayForInquirer = async function (lang, funcType) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.filter((folder) => Boolean(folder?.isDirectory()))
|
|
105
|
-
.map(async ({ name }) => {
|
|
95
|
+
const folders = await readdir(path.join(templatesDir, lang), { withFileTypes: true });
|
|
96
|
+
const imports = await Promise.all(folders
|
|
97
|
+
.filter((folder) => Boolean(folder?.isDirectory()))
|
|
98
|
+
.map(async ({ name }) => {
|
|
106
99
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// noop if import fails we don't break the whole inquirer
|
|
100
|
+
const templatePath = path.join(templatesDir, lang, name, '.netlify-function-template.mjs');
|
|
101
|
+
// @ts-expect-error TS(7036) FIXME: Dynamic import's specifier must be of type 'string... Remove this comment to see the full error message
|
|
102
|
+
const template = await import(pathToFileURL(templatePath));
|
|
103
|
+
return template.default;
|
|
112
104
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
})
|
|
130
|
-
.map((t) => {
|
|
131
|
-
t.lang = lang
|
|
132
|
-
return {
|
|
133
|
-
// confusing but this is the format inquirer wants
|
|
134
|
-
name: `[${t.name}] ${t.description}`,
|
|
135
|
-
value: t,
|
|
136
|
-
short: `${lang}-${t.name}`,
|
|
137
|
-
}
|
|
105
|
+
catch {
|
|
106
|
+
// noop if import fails we don't break the whole inquirer
|
|
107
|
+
}
|
|
108
|
+
}));
|
|
109
|
+
const registry = imports
|
|
110
|
+
.filter((template) => template?.functionType === funcType)
|
|
111
|
+
.sort((templateA, templateB) => {
|
|
112
|
+
const priorityDiff = (templateA.priority || DEFAULT_PRIORITY) - (templateB.priority || DEFAULT_PRIORITY);
|
|
113
|
+
if (priorityDiff !== 0) {
|
|
114
|
+
return priorityDiff;
|
|
115
|
+
}
|
|
116
|
+
// This branch is needed because `Array.prototype.sort` was not stable
|
|
117
|
+
// until Node 11, so the original sorting order from `fs.readdirSync`
|
|
118
|
+
// was not respected. We can simplify this once we drop support for
|
|
119
|
+
// Node 10.
|
|
120
|
+
return templateA - templateB;
|
|
138
121
|
})
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
122
|
+
.map((t) => {
|
|
123
|
+
t.lang = lang;
|
|
124
|
+
return {
|
|
125
|
+
// confusing but this is the format inquirer wants
|
|
126
|
+
name: `[${t.name}] ${t.description}`,
|
|
127
|
+
value: t,
|
|
128
|
+
short: `${lang}-${t.name}`,
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
return registry;
|
|
132
|
+
};
|
|
142
133
|
/**
|
|
143
134
|
* pick template from our existing templates
|
|
144
135
|
* @param {import('commander').OptionValues} config
|
|
145
136
|
* @param {'edge' | 'serverless'} funcType
|
|
146
137
|
*/
|
|
138
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'languageFromFlag' implicitly has ... Remove this comment to see the full error message
|
|
147
139
|
const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const DEFAULT_PRIORITY = 999
|
|
213
|
-
|
|
140
|
+
const specialCommands = [
|
|
141
|
+
new inquirer.Separator(),
|
|
142
|
+
{
|
|
143
|
+
name: `Clone template from GitHub URL`,
|
|
144
|
+
value: 'url',
|
|
145
|
+
short: 'gh-url',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: `Report issue with, or suggest a new template`,
|
|
149
|
+
value: 'report',
|
|
150
|
+
short: 'gh-report',
|
|
151
|
+
},
|
|
152
|
+
new inquirer.Separator(),
|
|
153
|
+
];
|
|
154
|
+
let language = languageFromFlag;
|
|
155
|
+
if (language === undefined) {
|
|
156
|
+
const langs = funcType === 'edge'
|
|
157
|
+
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'false | {... Remove this comment to see the full error message
|
|
158
|
+
? languages.filter((lang) => lang.value === 'javascript' || lang.value === 'typescript')
|
|
159
|
+
: languages.filter(Boolean);
|
|
160
|
+
const { language: languageFromPrompt } = await inquirer.prompt({
|
|
161
|
+
// @ts-expect-error
|
|
162
|
+
choices: langs,
|
|
163
|
+
message: 'Select the language of your function',
|
|
164
|
+
name: 'language',
|
|
165
|
+
type: 'list',
|
|
166
|
+
});
|
|
167
|
+
language = languageFromPrompt;
|
|
168
|
+
}
|
|
169
|
+
// @ts-expect-error TS(7034) FIXME: Variable 'templatesForLanguage' implicitly has typ... Remove this comment to see the full error message
|
|
170
|
+
let templatesForLanguage;
|
|
171
|
+
try {
|
|
172
|
+
templatesForLanguage = await formatRegistryArrayForInquirer(language, funcType);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
throw error(`Invalid language: ${language}`);
|
|
176
|
+
}
|
|
177
|
+
const { chosenTemplate } = await inquirer.prompt({
|
|
178
|
+
name: 'chosenTemplate',
|
|
179
|
+
message: 'Pick a template',
|
|
180
|
+
// @ts-expect-error TS(2769) FIXME: No overload matches this call.
|
|
181
|
+
type: 'autocomplete',
|
|
182
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'answersSoFar' implicitly has an 'any' t... Remove this comment to see the full error message
|
|
183
|
+
source(answersSoFar, input) {
|
|
184
|
+
// if Edge Functions template, don't show url option
|
|
185
|
+
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'Separator... Remove this comment to see the full error message
|
|
186
|
+
const edgeCommands = specialCommands.filter((val) => val.value !== 'url');
|
|
187
|
+
const parsedSpecialCommands = funcType === 'edge' ? edgeCommands : specialCommands;
|
|
188
|
+
if (!input || input === '') {
|
|
189
|
+
// show separators
|
|
190
|
+
// @ts-expect-error TS(7005) FIXME: Variable 'templatesForLanguage' implicitly has an ... Remove this comment to see the full error message
|
|
191
|
+
return [...templatesForLanguage, ...parsedSpecialCommands];
|
|
192
|
+
}
|
|
193
|
+
// only show filtered results sorted by score
|
|
194
|
+
// @ts-expect-error TS(7005) FIXME: Variable 'templatesForLanguage' implicitly has an ... Remove this comment to see the full error message
|
|
195
|
+
const answers = [...filterRegistry(templatesForLanguage, input), ...parsedSpecialCommands].sort((answerA, answerB) => answerB.score - answerA.score);
|
|
196
|
+
return answers;
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
return chosenTemplate;
|
|
200
|
+
};
|
|
201
|
+
const DEFAULT_PRIORITY = 999;
|
|
214
202
|
/** @returns {Promise<'edge' | 'serverless'>} */
|
|
215
203
|
const selectTypeOfFunc = async () => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
204
|
+
const functionTypes = [
|
|
205
|
+
{ name: 'Edge function (Deno)', value: 'edge' },
|
|
206
|
+
{ name: 'Serverless function (Node/Go)', value: 'serverless' },
|
|
207
|
+
];
|
|
208
|
+
const { functionType } = await inquirer.prompt([
|
|
209
|
+
{
|
|
210
|
+
name: 'functionType',
|
|
211
|
+
message: "Select the type of function you'd like to create",
|
|
212
|
+
type: 'list',
|
|
213
|
+
choices: functionTypes,
|
|
214
|
+
},
|
|
215
|
+
]);
|
|
216
|
+
return functionType;
|
|
217
|
+
};
|
|
232
218
|
/**
|
|
233
219
|
* @param {import('../base-command.mjs').default} command
|
|
234
220
|
*/
|
|
221
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
|
|
235
222
|
const ensureEdgeFuncDirExists = function (command) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
)} does not exist yet, creating it...`,
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
fs.mkdirSync(functionsDir, { recursive: true })
|
|
254
|
-
|
|
255
|
-
log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(relFunctionsDir)} created.`)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return functionsDir
|
|
259
|
-
}
|
|
260
|
-
|
|
223
|
+
const { config, site } = command.netlify;
|
|
224
|
+
const siteId = site.id;
|
|
225
|
+
if (!siteId) {
|
|
226
|
+
error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``);
|
|
227
|
+
}
|
|
228
|
+
const functionsDir = config.build?.edge_functions ?? join(command.workingDir, 'netlify/edge-functions');
|
|
229
|
+
const relFunctionsDir = relative(command.workingDir, functionsDir);
|
|
230
|
+
if (!fs.existsSync(functionsDir)) {
|
|
231
|
+
log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(relFunctionsDir)} does not exist yet, creating it...`);
|
|
232
|
+
fs.mkdirSync(functionsDir, { recursive: true });
|
|
233
|
+
log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(relFunctionsDir)} created.`);
|
|
234
|
+
}
|
|
235
|
+
return functionsDir;
|
|
236
|
+
};
|
|
261
237
|
/**
|
|
262
238
|
* Prompts the user to choose a functions directory
|
|
263
239
|
* @param {import('../base-command.mjs').default} command
|
|
264
240
|
* @returns {Promise<string>} - functions directory or throws an error
|
|
265
241
|
*/
|
|
242
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
|
|
266
243
|
const promptFunctionsDirectory = async (command) => {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
message: 'Enter the path, relative to your site, where your functions should live:',
|
|
279
|
-
default: 'netlify/functions',
|
|
280
|
-
},
|
|
281
|
-
])
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
log(`${NETLIFYDEVLOG} updating site settings with ${chalk.magenta.inverse(functionsDir)}`)
|
|
285
|
-
|
|
286
|
-
// @ts-ignore Typings of API are not correct
|
|
287
|
-
await api.updateSite({
|
|
288
|
-
siteId: site.id,
|
|
289
|
-
body: {
|
|
290
|
-
build_settings: {
|
|
291
|
-
functions_dir: functionsDir,
|
|
244
|
+
const { api, relConfigFilePath, site } = command.netlify;
|
|
245
|
+
log(`\n${NETLIFYDEVLOG} functions directory not specified in ${relConfigFilePath} or UI settings`);
|
|
246
|
+
if (!site.id) {
|
|
247
|
+
error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``);
|
|
248
|
+
}
|
|
249
|
+
const { functionsDir } = await inquirer.prompt([
|
|
250
|
+
{
|
|
251
|
+
type: 'input',
|
|
252
|
+
name: 'functionsDir',
|
|
253
|
+
message: 'Enter the path, relative to your site, where your functions should live:',
|
|
254
|
+
default: 'netlify/functions',
|
|
292
255
|
},
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
256
|
+
]);
|
|
257
|
+
try {
|
|
258
|
+
log(`${NETLIFYDEVLOG} updating site settings with ${chalk.magenta.inverse(functionsDir)}`);
|
|
259
|
+
await api.updateSite({
|
|
260
|
+
siteId: site.id,
|
|
261
|
+
body: {
|
|
262
|
+
build_settings: {
|
|
263
|
+
functions_dir: functionsDir,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(functionsDir)} updated in site settings`);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
throw error('Error updating site settings');
|
|
271
|
+
}
|
|
272
|
+
return functionsDir;
|
|
273
|
+
};
|
|
303
274
|
/**
|
|
304
275
|
* Get functions directory (and make it if necessary)
|
|
305
276
|
* @param {import('../base-command.mjs').default} command
|
|
306
277
|
* @returns {Promise<string>} - functions directory or throws an error
|
|
307
278
|
*/
|
|
279
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
|
|
308
280
|
const ensureFunctionDirExists = async function (command) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
await mkdir(functionsDirHolder, { recursive: true })
|
|
322
|
-
|
|
323
|
-
log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(relFunctionsDirHolder)} created`)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return functionsDirHolder
|
|
327
|
-
}
|
|
328
|
-
|
|
281
|
+
const { config } = command.netlify;
|
|
282
|
+
const functionsDirHolder = config.functionsDirectory || join(command.workingDir, await promptFunctionsDirectory(command));
|
|
283
|
+
const relFunctionsDirHolder = relative(command.workingDir, functionsDirHolder);
|
|
284
|
+
if (!fs.existsSync(functionsDirHolder)) {
|
|
285
|
+
log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(relFunctionsDirHolder)} does not exist yet, creating it...`);
|
|
286
|
+
await mkdir(functionsDirHolder, { recursive: true });
|
|
287
|
+
log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(relFunctionsDirHolder)} created`);
|
|
288
|
+
}
|
|
289
|
+
return functionsDirHolder;
|
|
290
|
+
};
|
|
329
291
|
/**
|
|
330
292
|
* Download files from a given GitHub URL
|
|
331
293
|
* @param {import('../base-command.mjs').default} command
|
|
@@ -333,67 +295,59 @@ const ensureFunctionDirExists = async function (command) {
|
|
|
333
295
|
* @param {string} argumentName
|
|
334
296
|
* @param {string} functionsDir
|
|
335
297
|
*/
|
|
298
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
|
|
336
299
|
const downloadFromURL = async function (command, options, argumentName, functionsDir) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
//
|
|
353
|
-
}
|
|
354
|
-
await Promise.all(
|
|
300
|
+
const folderContents = await readRepoURL(options.url);
|
|
301
|
+
const [functionName] = options.url.split('/').slice(-1);
|
|
302
|
+
const nameToUse = await getNameFromArgs(argumentName, options, functionName);
|
|
303
|
+
const fnFolder = path.join(functionsDir, nameToUse);
|
|
304
|
+
if (fs.existsSync(`${fnFolder}.js`) && fs.lstatSync(`${fnFolder}.js`).isFile()) {
|
|
305
|
+
log(`${NETLIFYDEVWARN}: A single file version of the function ${nameToUse} already exists at ${fnFolder}.js. Terminating without further action.`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
await mkdir(fnFolder, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// Ignore
|
|
313
|
+
}
|
|
314
|
+
await Promise.all(
|
|
315
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'downloadUrl' implicitly has an 'a... Remove this comment to see the full error message
|
|
355
316
|
folderContents.map(async ({ download_url: downloadUrl, name }) => {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
await handleOnComplete({ command, onComplete })
|
|
381
|
-
// delete
|
|
382
|
-
await unlink(fnTemplateFile)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
317
|
+
try {
|
|
318
|
+
const res = await fetch(downloadUrl);
|
|
319
|
+
const finalName = path.basename(name, '.js') === functionName ? `${nameToUse}.js` : name;
|
|
320
|
+
const dest = fs.createWriteStream(path.join(fnFolder, finalName));
|
|
321
|
+
res.body.pipe(dest);
|
|
322
|
+
}
|
|
323
|
+
catch (error_) {
|
|
324
|
+
throw new Error(`Error while retrieving ${downloadUrl} ${error_}`);
|
|
325
|
+
}
|
|
326
|
+
}));
|
|
327
|
+
log(`${NETLIFYDEVLOG} Installing dependencies for ${nameToUse}...`);
|
|
328
|
+
cp.exec('npm i', { cwd: path.join(functionsDir, nameToUse) }, () => {
|
|
329
|
+
log(`${NETLIFYDEVLOG} Installing dependencies for ${nameToUse} complete `);
|
|
330
|
+
});
|
|
331
|
+
// read, execute, and delete function template file if exists
|
|
332
|
+
const fnTemplateFile = path.join(fnFolder, '.netlify-function-template.mjs');
|
|
333
|
+
if (await fileExistsAsync(fnTemplateFile)) {
|
|
334
|
+
const { default: { addons = [], onComplete }, } = await import(pathToFileURL(fnTemplateFile).href);
|
|
335
|
+
await installAddons(command, addons, path.resolve(fnFolder));
|
|
336
|
+
await handleOnComplete({ command, onComplete });
|
|
337
|
+
// delete
|
|
338
|
+
await unlink(fnTemplateFile);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
386
341
|
/**
|
|
387
342
|
* Takes a list of existing packages and a list of packages required by a
|
|
388
343
|
* function, and returns the packages from the latter that aren't present
|
|
389
344
|
* in the former. The packages are returned as an array of strings with the
|
|
390
345
|
* name and version range (e.g. '@netlify/functions@0.1.0').
|
|
391
346
|
*/
|
|
392
|
-
const getNpmInstallPackages = (existingPackages = {}, neededPackages = {}) =>
|
|
393
|
-
|
|
347
|
+
const getNpmInstallPackages = (existingPackages = {}, neededPackages = {}) => Object.entries(neededPackages)
|
|
348
|
+
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
394
349
|
.filter(([name]) => existingPackages[name] === undefined)
|
|
395
|
-
.map(([name, version]) => `${name}@${version}`)
|
|
396
|
-
|
|
350
|
+
.map(([name, version]) => `${name}@${version}`);
|
|
397
351
|
/**
|
|
398
352
|
* When installing a function's dependencies, we first try to find a site-level
|
|
399
353
|
* `package.json` file. If we do, we look for any dependencies of the function
|
|
@@ -401,47 +355,43 @@ const getNpmInstallPackages = (existingPackages = {}, neededPackages = {}) =>
|
|
|
401
355
|
* we don't do this check, we may be upgrading the version of a module used in
|
|
402
356
|
* another part of the project, which we don't want to do.
|
|
403
357
|
*/
|
|
358
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'functionPackageJson' implicitly h... Remove this comment to see the full error message
|
|
404
359
|
const installDeps = async ({ functionPackageJson, functionPath, functionsDir }) => {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
} catch {
|
|
441
|
-
// no-op
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
360
|
+
const { dependencies: functionDependencies, devDependencies: functionDevDependencies } = require(functionPackageJson);
|
|
361
|
+
const sitePackageJson = await findUp('package.json', { cwd: functionsDir });
|
|
362
|
+
const npmInstallFlags = ['--no-audit', '--no-fund'];
|
|
363
|
+
// If there is no site-level `package.json`, we fall back to the old behavior
|
|
364
|
+
// of keeping that file in the function directory and running `npm install`
|
|
365
|
+
// from there.
|
|
366
|
+
if (!sitePackageJson) {
|
|
367
|
+
// @ts-expect-error TS(7005) FIXME: Variable 'execa' implicitly has an 'any' type.
|
|
368
|
+
await execa('npm', ['i', ...npmInstallFlags], { cwd: functionPath });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const { dependencies: siteDependencies, devDependencies: siteDevDependencies } = require(sitePackageJson);
|
|
372
|
+
const dependencies = getNpmInstallPackages(siteDependencies, functionDependencies);
|
|
373
|
+
const devDependencies = getNpmInstallPackages(siteDevDependencies, functionDevDependencies);
|
|
374
|
+
const npmInstallPath = path.dirname(sitePackageJson);
|
|
375
|
+
if (dependencies.length !== 0) {
|
|
376
|
+
// @ts-expect-error TS(7005) FIXME: Variable 'execa' implicitly has an 'any' type.
|
|
377
|
+
await execa('npm', ['i', ...dependencies, '--save', ...npmInstallFlags], { cwd: npmInstallPath });
|
|
378
|
+
}
|
|
379
|
+
if (devDependencies.length !== 0) {
|
|
380
|
+
// @ts-expect-error TS(7005) FIXME: Variable 'execa' implicitly has an 'any' type.
|
|
381
|
+
await execa('npm', ['i', ...devDependencies, '--save-dev', ...npmInstallFlags], { cwd: npmInstallPath });
|
|
382
|
+
}
|
|
383
|
+
// We installed the function's dependencies in the site-level `package.json`,
|
|
384
|
+
// so there's no reason to keep the one copied over from the template.
|
|
385
|
+
fs.unlinkSync(functionPackageJson);
|
|
386
|
+
// Similarly, if the template has a `package-lock.json` file, we delete it.
|
|
387
|
+
try {
|
|
388
|
+
const functionPackageLock = path.join(functionPath, 'package-lock.json');
|
|
389
|
+
fs.unlinkSync(functionPackageLock);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// no-op
|
|
393
|
+
}
|
|
394
|
+
};
|
|
445
395
|
/**
|
|
446
396
|
* no --url flag specified, pick from a provided template
|
|
447
397
|
* @param {import('../base-command.mjs').default} command
|
|
@@ -450,127 +400,121 @@ const installDeps = async ({ functionPackageJson, functionPath, functionsDir })
|
|
|
450
400
|
* @param {string} functionsDir Absolute path of the functions directory
|
|
451
401
|
* @param {'edge' | 'serverless'} funcType
|
|
452
402
|
*/
|
|
403
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
|
|
453
404
|
// eslint-disable-next-line max-params
|
|
454
405
|
const scaffoldFromTemplate = async function (command, options, argumentName, functionsDir, funcType) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
await installAddons(command, addons, path.resolve(functionPath))
|
|
530
|
-
await handleOnComplete({ command, onComplete })
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const TEMPLATE_PERMISSIONS = 0o777
|
|
535
|
-
|
|
406
|
+
// pull the rest of the metadata from the template
|
|
407
|
+
const chosenTemplate = await pickTemplate(options, funcType);
|
|
408
|
+
if (chosenTemplate === 'url') {
|
|
409
|
+
const { chosenUrl } = await inquirer.prompt([
|
|
410
|
+
{
|
|
411
|
+
name: 'chosenUrl',
|
|
412
|
+
message: 'URL to clone: ',
|
|
413
|
+
type: 'input',
|
|
414
|
+
validate: (/** @type {string} */ val) => Boolean(validateRepoURL(val)),
|
|
415
|
+
// make sure it is not undefined and is a valid filename.
|
|
416
|
+
// this has some nuance i have ignored, eg crossenv and i18n concerns
|
|
417
|
+
},
|
|
418
|
+
]);
|
|
419
|
+
options.url = chosenUrl.trim();
|
|
420
|
+
try {
|
|
421
|
+
await downloadFromURL(command, options, argumentName, functionsDir);
|
|
422
|
+
}
|
|
423
|
+
catch (error_) {
|
|
424
|
+
error(`$${NETLIFYDEVERR} Error downloading from URL: ${options.url}`);
|
|
425
|
+
// @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
|
|
426
|
+
error(error_);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
else if (chosenTemplate === 'report') {
|
|
431
|
+
log(`${NETLIFYDEVLOG} Open in browser: https://github.com/netlify/cli/issues/new`);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
const { addons = [], lang, name: templateName, onComplete } = chosenTemplate;
|
|
435
|
+
const pathToTemplate = path.join(templatesDir, lang, templateName);
|
|
436
|
+
if (!fs.existsSync(pathToTemplate)) {
|
|
437
|
+
throw new Error(`There isn't a corresponding directory to the selected name. Template '${templateName}' is misconfigured`);
|
|
438
|
+
}
|
|
439
|
+
const name = await getNameFromArgs(argumentName, options, templateName);
|
|
440
|
+
log(`${NETLIFYDEVLOG} Creating function ${chalk.cyan.inverse(name)}`);
|
|
441
|
+
const functionPath = ensureFunctionPathIsOk(functionsDir, name);
|
|
442
|
+
const vars = { name };
|
|
443
|
+
let functionPackageJson;
|
|
444
|
+
// These files will not be part of the log message because they'll likely
|
|
445
|
+
// be removed before the command finishes.
|
|
446
|
+
const omittedFromOutput = new Set(['.netlify-function-template.mjs', 'package.json', 'package-lock.json']);
|
|
447
|
+
const createdFiles = await copyTemplateDir(pathToTemplate, functionPath, vars);
|
|
448
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'filePath' implicitly has an 'any' type.
|
|
449
|
+
createdFiles.forEach((filePath) => {
|
|
450
|
+
const filename = path.basename(filePath);
|
|
451
|
+
if (!omittedFromOutput.has(filename)) {
|
|
452
|
+
log(`${NETLIFYDEVLOG} ${chalk.greenBright('Created')} ${filePath}`);
|
|
453
|
+
}
|
|
454
|
+
fs.chmodSync(path.resolve(filePath), TEMPLATE_PERMISSIONS);
|
|
455
|
+
if (filePath.includes('package.json')) {
|
|
456
|
+
functionPackageJson = path.resolve(filePath);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
// delete function template file that was copied over by copydir
|
|
460
|
+
await unlink(path.join(functionPath, '.netlify-function-template.mjs'));
|
|
461
|
+
// npm install
|
|
462
|
+
if (functionPackageJson !== undefined) {
|
|
463
|
+
const spinner = ora({
|
|
464
|
+
text: `Installing dependencies for ${name}`,
|
|
465
|
+
spinner: 'moon',
|
|
466
|
+
}).start();
|
|
467
|
+
await installDeps({ functionPackageJson, functionPath, functionsDir });
|
|
468
|
+
spinner.succeed(`Installed dependencies for ${name}`);
|
|
469
|
+
}
|
|
470
|
+
if (funcType === 'edge') {
|
|
471
|
+
registerEFInToml(name, command.netlify);
|
|
472
|
+
}
|
|
473
|
+
await installAddons(command, addons, path.resolve(functionPath));
|
|
474
|
+
await handleOnComplete({ command, onComplete });
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
const TEMPLATE_PERMISSIONS = 0o777;
|
|
478
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'addonName' implicitly has an 'any... Remove this comment to see the full error message
|
|
536
479
|
const createFunctionAddon = async function ({ addonName, addons, api, siteData, siteId }) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
480
|
+
try {
|
|
481
|
+
const addon = getCurrentAddon({ addons, addonName });
|
|
482
|
+
if (addon && addon.id) {
|
|
483
|
+
log(`The "${addonName} add-on" already exists for ${siteData.name}`);
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
await api.createServiceInstance({
|
|
487
|
+
siteId,
|
|
488
|
+
addon: addonName,
|
|
489
|
+
body: { config: {} },
|
|
490
|
+
});
|
|
491
|
+
log(`Add-on "${addonName}" created for ${siteData.name}`);
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
catch (error_) {
|
|
495
|
+
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
|
|
496
|
+
error(error_.message);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
555
499
|
/**
|
|
556
500
|
*
|
|
557
501
|
* @param {object} config
|
|
558
502
|
* @param {import('../base-command.mjs').default} config.command
|
|
559
503
|
* @param {(command: import('../base-command.mjs').default) => any} config.onComplete
|
|
560
504
|
*/
|
|
505
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'command' implicitly has an 'any' ... Remove this comment to see the full error message
|
|
561
506
|
const handleOnComplete = async ({ command, onComplete }) => {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
507
|
+
const { config } = command.netlify;
|
|
508
|
+
if (onComplete) {
|
|
509
|
+
const env = await getDotEnvVariables({
|
|
510
|
+
devConfig: { ...config.dev },
|
|
511
|
+
env: command.netlify.cachedConfig.env,
|
|
512
|
+
site: command.netlify.site,
|
|
513
|
+
});
|
|
514
|
+
injectEnvVariables(env);
|
|
515
|
+
await onComplete.call(command);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
574
518
|
/**
|
|
575
519
|
*
|
|
576
520
|
* @param {object} config
|
|
@@ -579,34 +523,30 @@ const handleOnComplete = async ({ command, onComplete }) => {
|
|
|
579
523
|
* @param {import('../base-command.mjs').default} config.command
|
|
580
524
|
* @param {string} config.fnPath
|
|
581
525
|
*/
|
|
526
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'addonCreated' implicitly has an '... Remove this comment to see the full error message
|
|
582
527
|
const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, fnPath }) => {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
site: command.netlify.site,
|
|
606
|
-
})
|
|
607
|
-
addonDidInstall(fnPath)
|
|
608
|
-
}
|
|
609
|
-
|
|
528
|
+
const { config } = command.netlify;
|
|
529
|
+
if (!addonCreated || !addonDidInstall) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const { confirmPostInstall } = await inquirer.prompt([
|
|
533
|
+
{
|
|
534
|
+
type: 'confirm',
|
|
535
|
+
name: 'confirmPostInstall',
|
|
536
|
+
message: `This template has an optional setup script that runs after addon install. This can be helpful for first time users to try out templates. Run the script?`,
|
|
537
|
+
default: false,
|
|
538
|
+
},
|
|
539
|
+
]);
|
|
540
|
+
if (!confirmPostInstall) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
await injectEnvVariables({
|
|
544
|
+
devConfig: { ...config.dev },
|
|
545
|
+
env: command.netlify.cachedConfig.env,
|
|
546
|
+
site: command.netlify.site,
|
|
547
|
+
});
|
|
548
|
+
addonDidInstall(fnPath);
|
|
549
|
+
};
|
|
610
550
|
/**
|
|
611
551
|
*
|
|
612
552
|
* @param {import('../base-command.mjs').default} command
|
|
@@ -614,80 +554,74 @@ const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, f
|
|
|
614
554
|
* @param {*} fnPath
|
|
615
555
|
* @returns
|
|
616
556
|
*/
|
|
557
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
|
|
617
558
|
const installAddons = async function (command, functionAddons, fnPath) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
|
|
559
|
+
if (functionAddons.length === 0) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const { api, site } = command.netlify;
|
|
563
|
+
const siteId = site.id;
|
|
564
|
+
if (!siteId) {
|
|
565
|
+
log('No site id found, please run inside a site directory or `netlify link`');
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
log(`${NETLIFYDEVLOG} checking Netlify APIs...`);
|
|
569
|
+
const [siteData, siteAddons] = await Promise.all([getSiteData({ api, siteId }), getAddons({ api, siteId })]);
|
|
570
|
+
// @ts-expect-error TS(7031) FIXME: Binding element 'addonDidInstall' implicitly has a... Remove this comment to see the full error message
|
|
571
|
+
const arr = functionAddons.map(async ({ addonDidInstall, addonName }) => {
|
|
572
|
+
log(`${NETLIFYDEVLOG} installing addon: ${chalk.yellow.inverse(addonName)}`);
|
|
573
|
+
try {
|
|
574
|
+
const addonCreated = await createFunctionAddon({
|
|
575
|
+
api,
|
|
576
|
+
addons: siteAddons,
|
|
577
|
+
siteId,
|
|
578
|
+
addonName,
|
|
579
|
+
siteData,
|
|
580
|
+
});
|
|
581
|
+
await handleAddonDidInstall({ addonCreated, addonDidInstall, command, fnPath });
|
|
582
|
+
}
|
|
583
|
+
catch (error_) {
|
|
584
|
+
// @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
|
|
585
|
+
error(`${NETLIFYDEVERR} Error installing addon: `, error_);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
return Promise.all(arr);
|
|
589
|
+
};
|
|
651
590
|
/**
|
|
652
591
|
*
|
|
653
592
|
* @param {string} funcName
|
|
654
593
|
* @param {import('../types.js').NetlifyOptions} options
|
|
655
594
|
*/
|
|
595
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'funcName' implicitly has an 'any' type.
|
|
656
596
|
const registerEFInToml = async (funcName, options) => {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
)
|
|
686
|
-
} catch {
|
|
687
|
-
error(`${NETLIFYDEVERR} Unable to register function. Please check your \`${relConfigFilePath}\` file.`)
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
597
|
+
const { configFilePath, relConfigFilePath } = options;
|
|
598
|
+
if (!fs.existsSync(configFilePath)) {
|
|
599
|
+
log(`${NETLIFYDEVLOG} \`${relConfigFilePath}\` file does not exist yet. Creating it...`);
|
|
600
|
+
}
|
|
601
|
+
let { funcPath } = await inquirer.prompt([
|
|
602
|
+
{
|
|
603
|
+
type: 'input',
|
|
604
|
+
name: 'funcPath',
|
|
605
|
+
message: `What route do you want your edge function to be invoked on?`,
|
|
606
|
+
default: '/test',
|
|
607
|
+
validate: (val) => Boolean(val),
|
|
608
|
+
// Make sure route isn't undefined and is valid
|
|
609
|
+
// Todo: add more validation?
|
|
610
|
+
},
|
|
611
|
+
]);
|
|
612
|
+
// Make sure path begins with a '/'
|
|
613
|
+
if (funcPath[0] !== '/') {
|
|
614
|
+
funcPath = `/${funcPath}`;
|
|
615
|
+
}
|
|
616
|
+
const functionRegister = `\n\n[[edge_functions]]\nfunction = "${funcName}"\npath = "${funcPath}"`;
|
|
617
|
+
try {
|
|
618
|
+
fs.promises.appendFile(configFilePath, functionRegister);
|
|
619
|
+
log(`${NETLIFYDEVLOG} Function '${funcName}' registered for route \`${funcPath}\`. To change, edit your \`${relConfigFilePath}\` file.`);
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
error(`${NETLIFYDEVERR} Unable to register function. Please check your \`${relConfigFilePath}\` file.`);
|
|
623
|
+
}
|
|
624
|
+
};
|
|
691
625
|
/**
|
|
692
626
|
* we used to allow for a --dir command,
|
|
693
627
|
* but have retired that to force every scaffolded function to be a directory
|
|
@@ -695,38 +629,36 @@ const registerEFInToml = async (funcName, options) => {
|
|
|
695
629
|
* @param {string} name
|
|
696
630
|
* @returns
|
|
697
631
|
*/
|
|
632
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'functionsDir' implicitly has an 'any' t... Remove this comment to see the full error message
|
|
698
633
|
const ensureFunctionPathIsOk = function (functionsDir, name) {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
|
|
634
|
+
const functionPath = path.join(functionsDir, name);
|
|
635
|
+
if (fs.existsSync(functionPath)) {
|
|
636
|
+
log(`${NETLIFYDEVLOG} Function ${functionPath} already exists, cancelling...`);
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
return functionPath;
|
|
640
|
+
};
|
|
707
641
|
/**
|
|
708
642
|
* The functions:create command
|
|
709
643
|
* @param {string} name
|
|
710
644
|
* @param {import('commander').OptionValues} options
|
|
711
645
|
* @param {import('../base-command.mjs').default} command
|
|
712
646
|
*/
|
|
647
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'name' implicitly has an 'any' type.
|
|
713
648
|
const functionsCreate = async (name, options, command) => {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
await mainFunc(command, options, name, functionsDir, functionType)
|
|
721
|
-
}
|
|
722
|
-
|
|
649
|
+
const functionType = await selectTypeOfFunc();
|
|
650
|
+
const functionsDir = functionType === 'edge' ? await ensureEdgeFuncDirExists(command) : await ensureFunctionDirExists(command);
|
|
651
|
+
/* either download from URL or scaffold from template */
|
|
652
|
+
const mainFunc = options.url ? downloadFromURL : scaffoldFromTemplate;
|
|
653
|
+
await mainFunc(command, options, name, functionsDir, functionType);
|
|
654
|
+
};
|
|
723
655
|
/**
|
|
724
656
|
* Creates the `netlify functions:create` command
|
|
725
657
|
* @param {import('../base-command.mjs').default} program
|
|
726
658
|
* @returns
|
|
727
659
|
*/
|
|
728
|
-
|
|
729
|
-
|
|
660
|
+
// @ts-expect-error TS(7006) FIXME: Parameter 'program' implicitly has an 'any' type.
|
|
661
|
+
export const createFunctionsCreateCommand = (program) => program
|
|
730
662
|
.command('functions:create')
|
|
731
663
|
.alias('function:create')
|
|
732
664
|
.argument('[name]', 'name of your new function file inside your functions directory')
|
|
@@ -735,8 +667,8 @@ export const createFunctionsCreateCommand = (program) =>
|
|
|
735
667
|
.option('-u, --url <url>', 'pull template from URL')
|
|
736
668
|
.option('-l, --language <lang>', 'function language')
|
|
737
669
|
.addExamples([
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
.action(functionsCreate)
|
|
670
|
+
'netlify functions:create',
|
|
671
|
+
'netlify functions:create hello-world',
|
|
672
|
+
'netlify functions:create --name hello-world',
|
|
673
|
+
])
|
|
674
|
+
.action(functionsCreate);
|