netlify-cli 17.3.2 → 17.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +2 -138
  2. package/npm-shrinkwrap.json +76 -76
  3. package/package.json +16 -15
  4. package/src/commands/addons/addons-auth.mjs +27 -30
  5. package/src/commands/addons/addons-config.mjs +145 -154
  6. package/src/commands/addons/addons-create.mjs +94 -108
  7. package/src/commands/addons/addons-delete.mjs +36 -41
  8. package/src/commands/addons/addons-list.mjs +38 -42
  9. package/src/commands/addons/addons.mjs +26 -28
  10. package/src/commands/addons/index.mjs +1 -1
  11. package/src/commands/api/api.mjs +45 -53
  12. package/src/commands/api/index.mjs +1 -1
  13. package/src/commands/base-command.mjs +597 -684
  14. package/src/commands/blobs/blobs-delete.mjs +35 -0
  15. package/src/commands/blobs/blobs-get.mjs +44 -0
  16. package/src/commands/blobs/blobs-list.mjs +48 -0
  17. package/src/commands/blobs/blobs-set.mjs +54 -0
  18. package/src/commands/blobs/blobs.mjs +32 -0
  19. package/src/commands/blobs/index.mjs +1 -0
  20. package/src/commands/build/build.mjs +55 -67
  21. package/src/commands/build/index.mjs +1 -1
  22. package/src/commands/completion/completion.mjs +41 -46
  23. package/src/commands/completion/index.mjs +1 -1
  24. package/src/commands/deploy/deploy.mjs +675 -710
  25. package/src/commands/deploy/index.mjs +1 -1
  26. package/src/commands/dev/dev-exec.mjs +20 -32
  27. package/src/commands/dev/dev.mjs +217 -302
  28. package/src/commands/dev/index.mjs +1 -1
  29. package/src/commands/dev/types.d.ts +30 -0
  30. package/src/commands/env/env-clone.mjs +157 -184
  31. package/src/commands/env/env-get.mjs +49 -68
  32. package/src/commands/env/env-import.mjs +100 -119
  33. package/src/commands/env/env-list.mjs +104 -129
  34. package/src/commands/env/env-set.mjs +160 -185
  35. package/src/commands/env/env-unset.mjs +104 -122
  36. package/src/commands/env/env.mjs +28 -30
  37. package/src/commands/env/index.mjs +1 -1
  38. package/src/commands/functions/functions-build.mjs +29 -41
  39. package/src/commands/functions/functions-create.mjs +533 -601
  40. package/src/commands/functions/functions-invoke.mjs +193 -216
  41. package/src/commands/functions/functions-list.mjs +45 -55
  42. package/src/commands/functions/functions-serve.mjs +51 -61
  43. package/src/commands/functions/functions.mjs +26 -32
  44. package/src/commands/functions/index.mjs +1 -1
  45. package/src/commands/index.mjs +2 -2
  46. package/src/commands/init/index.mjs +1 -1
  47. package/src/commands/init/init.mjs +138 -167
  48. package/src/commands/integration/deploy.mjs +337 -399
  49. package/src/commands/integration/index.mjs +12 -13
  50. package/src/commands/link/index.mjs +1 -1
  51. package/src/commands/link/link.mjs +298 -317
  52. package/src/commands/lm/index.mjs +1 -1
  53. package/src/commands/lm/lm-info.mjs +23 -31
  54. package/src/commands/lm/lm-install.mjs +13 -17
  55. package/src/commands/lm/lm-setup.mjs +80 -84
  56. package/src/commands/lm/lm-uninstall.mjs +7 -12
  57. package/src/commands/lm/lm.mjs +18 -22
  58. package/src/commands/login/index.mjs +1 -1
  59. package/src/commands/login/login.mjs +35 -41
  60. package/src/commands/logout/index.mjs +1 -1
  61. package/src/commands/logout/logout.mjs +25 -31
  62. package/src/commands/main.mjs +166 -201
  63. package/src/commands/open/index.mjs +1 -1
  64. package/src/commands/open/open-admin.mjs +15 -18
  65. package/src/commands/open/open-site.mjs +16 -19
  66. package/src/commands/open/open.mjs +24 -27
  67. package/src/commands/recipes/common.mjs +23 -34
  68. package/src/commands/recipes/index.mjs +1 -1
  69. package/src/commands/recipes/recipes-list.mjs +13 -20
  70. package/src/commands/recipes/recipes.mjs +59 -72
  71. package/src/commands/serve/index.mjs +1 -1
  72. package/src/commands/serve/serve.mjs +142 -189
  73. package/src/commands/sites/index.mjs +2 -2
  74. package/src/commands/sites/sites-create-template.mjs +214 -236
  75. package/src/commands/sites/sites-create.mjs +145 -157
  76. package/src/commands/sites/sites-delete.mjs +75 -81
  77. package/src/commands/sites/sites-list.mjs +63 -66
  78. package/src/commands/sites/sites.mjs +18 -20
  79. package/src/commands/status/index.mjs +1 -1
  80. package/src/commands/status/status-hooks.mjs +32 -34
  81. package/src/commands/status/status.mjs +99 -106
  82. package/src/commands/switch/index.mjs +1 -1
  83. package/src/commands/switch/switch.mjs +32 -37
  84. package/src/commands/types.d.ts +31 -0
  85. package/src/commands/unlink/index.mjs +1 -1
  86. package/src/commands/unlink/unlink.mjs +23 -29
  87. package/src/commands/watch/index.mjs +1 -1
  88. package/src/commands/watch/watch.mjs +91 -105
  89. package/src/functions-templates/javascript/hello/{{name}}.js +2 -3
  90. package/src/lib/account.mjs +4 -5
  91. package/src/lib/api.mjs +22 -20
  92. package/src/lib/blobs/blobs.mjs +36 -45
  93. package/src/lib/build.mjs +82 -85
  94. package/src/lib/completion/constants.mjs +2 -4
  95. package/src/lib/completion/generate-autocompletion.mjs +33 -36
  96. package/src/lib/completion/get-autocompletion.mjs +31 -35
  97. package/src/lib/completion/index.mjs +1 -1
  98. package/src/lib/completion/script.mjs +12 -19
  99. package/src/lib/edge-functions/bootstrap.mjs +3 -5
  100. package/src/lib/edge-functions/consts.mjs +9 -10
  101. package/src/lib/edge-functions/deploy.mjs +28 -34
  102. package/src/lib/edge-functions/editor-helper.mjs +29 -42
  103. package/src/lib/edge-functions/headers.mjs +24 -26
  104. package/src/lib/edge-functions/internal.mjs +38 -44
  105. package/src/lib/edge-functions/proxy.mjs +229 -228
  106. package/src/lib/edge-functions/registry.mjs +473 -574
  107. package/src/lib/exec-fetcher.mjs +115 -122
  108. package/src/lib/fs.mjs +28 -27
  109. package/src/lib/functions/background.mjs +16 -20
  110. package/src/lib/functions/config.mjs +12 -9
  111. package/src/lib/functions/form-submissions-handler.mjs +143 -149
  112. package/src/lib/functions/local-proxy.mjs +40 -44
  113. package/src/lib/functions/memoized-build.mjs +19 -21
  114. package/src/lib/functions/netlify-function.mjs +269 -249
  115. package/src/lib/functions/registry.mjs +509 -568
  116. package/src/lib/functions/runtimes/go/index.mjs +62 -71
  117. package/src/lib/functions/runtimes/index.mjs +8 -15
  118. package/src/lib/functions/runtimes/js/builders/netlify-lambda.mjs +55 -64
  119. package/src/lib/functions/runtimes/js/builders/zisi.mjs +135 -154
  120. package/src/lib/functions/runtimes/js/constants.mjs +1 -1
  121. package/src/lib/functions/runtimes/js/index.mjs +92 -109
  122. package/src/lib/functions/runtimes/js/worker.mjs +43 -45
  123. package/src/lib/functions/runtimes/rust/index.mjs +64 -73
  124. package/src/lib/functions/scheduled.mjs +70 -88
  125. package/src/lib/functions/server.mjs +269 -327
  126. package/src/lib/functions/synchronous.mjs +118 -147
  127. package/src/lib/functions/utils.mjs +38 -46
  128. package/src/lib/geo-location.mjs +69 -81
  129. package/src/lib/http-agent.mjs +87 -90
  130. package/src/lib/images/proxy.mjs +97 -99
  131. package/src/lib/log.mjs +6 -9
  132. package/src/lib/path.mjs +2 -1
  133. package/src/lib/render-error-template.mjs +19 -20
  134. package/src/lib/settings.mjs +17 -19
  135. package/src/lib/spinner.mjs +21 -23
  136. package/src/lib/string.mjs +4 -2
  137. package/src/recipes/vscode/index.mjs +69 -85
  138. package/src/recipes/vscode/settings.mjs +53 -58
  139. package/src/utils/addons/compare.mjs +31 -32
  140. package/src/utils/addons/diffs/index.mjs +16 -17
  141. package/src/utils/addons/diffs/options.mjs +99 -101
  142. package/src/utils/addons/prepare.mjs +100 -97
  143. package/src/utils/addons/prompts.mjs +73 -76
  144. package/src/utils/addons/render.mjs +33 -36
  145. package/src/utils/addons/validation.mjs +19 -15
  146. package/src/utils/banner.mjs +11 -16
  147. package/src/utils/build-info.mjs +65 -66
  148. package/src/utils/command-helpers.mjs +185 -199
  149. package/src/utils/create-deferred.mjs +9 -12
  150. package/src/utils/create-stream-promise.mjs +54 -47
  151. package/src/utils/deploy/constants.mjs +9 -11
  152. package/src/utils/deploy/deploy-site.mjs +162 -182
  153. package/src/utils/deploy/hash-config.mjs +21 -21
  154. package/src/utils/deploy/hash-files.mjs +34 -38
  155. package/src/utils/deploy/hash-fns.mjs +149 -154
  156. package/src/utils/deploy/hasher-segments.mjs +58 -52
  157. package/src/utils/deploy/upload-files.mjs +99 -113
  158. package/src/utils/deploy/util.mjs +85 -91
  159. package/src/utils/detect-server-settings.mjs +236 -268
  160. package/src/utils/dev.mjs +163 -178
  161. package/src/utils/dot-env.mjs +37 -42
  162. package/src/utils/env/index.mjs +148 -148
  163. package/src/utils/execa.mjs +9 -13
  164. package/src/utils/feature-flags.mjs +6 -5
  165. package/src/utils/framework-server.mjs +43 -52
  166. package/src/utils/functions/constants.mjs +1 -1
  167. package/src/utils/functions/functions.mjs +30 -40
  168. package/src/utils/functions/get-functions.mjs +28 -29
  169. package/src/utils/functions/index.mjs +3 -3
  170. package/src/utils/get-global-config.mjs +33 -36
  171. package/src/utils/get-package-json.mjs +14 -15
  172. package/src/utils/get-repo-data.mjs +54 -64
  173. package/src/utils/get-site.mjs +14 -14
  174. package/src/utils/gh-auth.mjs +79 -100
  175. package/src/utils/gitignore.mjs +37 -40
  176. package/src/utils/headers.mjs +33 -35
  177. package/src/utils/hooks/requires-site-info.mjs +26 -22
  178. package/src/utils/init/config-github.mjs +207 -219
  179. package/src/utils/init/config-manual.mjs +83 -100
  180. package/src/utils/init/config.mjs +25 -26
  181. package/src/utils/init/node-version.mjs +23 -30
  182. package/src/utils/init/plugins.mjs +12 -8
  183. package/src/utils/init/utils.mjs +152 -172
  184. package/src/utils/live-tunnel.mjs +118 -141
  185. package/src/utils/lm/install.mjs +220 -259
  186. package/src/utils/lm/requirements.mjs +54 -63
  187. package/src/utils/lm/steps.mjs +31 -31
  188. package/src/utils/lm/ui.mjs +13 -20
  189. package/src/utils/open-browser.mjs +31 -32
  190. package/src/utils/parse-raw-flags.mjs +39 -35
  191. package/src/utils/proxy-server.mjs +84 -71
  192. package/src/utils/proxy.mjs +696 -750
  193. package/src/utils/read-repo-url.mjs +48 -47
  194. package/src/utils/redirects.mjs +49 -49
  195. package/src/utils/request-id.mjs +2 -4
  196. package/src/utils/rules-proxy.mjs +96 -100
  197. package/src/utils/run-build.mjs +109 -132
  198. package/src/utils/shell.mjs +99 -106
  199. package/src/utils/sign-redirect.mjs +14 -14
  200. package/src/utils/sites/utils.mjs +48 -55
  201. package/src/utils/state-config.mjs +101 -101
  202. package/src/utils/static-server.mjs +28 -34
  203. package/src/utils/telemetry/index.mjs +2 -2
  204. package/src/utils/telemetry/report-error.mjs +45 -49
  205. package/src/utils/telemetry/request.mjs +36 -43
  206. package/src/utils/telemetry/telemetry.mjs +90 -105
  207. package/src/utils/telemetry/utils.mjs +5 -6
  208. package/src/utils/telemetry/validation.mjs +55 -53
  209. package/src/utils/types.d.ts +46 -0
  210. package/src/utils/validation.mjs +10 -13
@@ -1,46 +1,40 @@
1
- // @ts-check
2
- import cp from 'child_process'
3
- import fs from 'fs'
4
- import { mkdir, readdir, unlink } from 'fs/promises'
5
- import { createRequire } from 'module'
6
- import path, { dirname, join, relative } from 'path'
7
- import process from 'process'
8
- import { fileURLToPath, pathToFileURL } from 'url'
9
- import { promisify } from 'util'
10
-
11
- import copyTemplateDirOriginal from 'copy-template-dir'
12
- import { findUp } from 'find-up'
13
- import fuzzy from 'fuzzy'
14
- import inquirer from 'inquirer'
15
- import fetch from 'node-fetch'
16
- import ora from 'ora'
17
-
18
- import { fileExistsAsync } from '../../lib/fs.mjs'
19
- import { getAddons, getCurrentAddon, getSiteData } from '../../utils/addons/prepare.mjs'
20
- import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, error, log } from '../../utils/command-helpers.mjs'
21
- import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.mjs'
22
- import execa from '../../utils/execa.mjs'
23
- import { readRepoURL, validateRepoURL } from '../../utils/read-repo-url.mjs'
24
-
25
- const copyTemplateDir = promisify(copyTemplateDirOriginal)
26
-
27
- const require = createRequire(import.meta.url)
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
- { name: 'JavaScript', value: 'javascript' },
39
- { name: 'TypeScript', value: 'typescript' },
40
- { name: 'Go', value: 'go' },
41
- showRustTemplates && { name: 'Rust', value: 'rust' },
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
- if (options.name) {
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
- throw new Error('function name specified in both flag and arg format, pick one')
55
- }
56
- return options.name
57
- }
58
-
59
- if (argumentName) {
60
- return argumentName
61
- }
62
-
63
- const { name } = await inquirer.prompt([
64
- {
65
- name: 'name',
66
- message: 'Name your function:',
67
- default: defaultName,
68
- type: 'input',
69
- validate: (val) => Boolean(val) && /^[\w.-]+$/i.test(val),
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
- const temp = registry.map((value) => value.name + value.description)
79
- // TODO: remove once https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1394 is fixed
80
- // eslint-disable-next-line unicorn/no-array-method-this-argument
81
- const filteredTemplates = fuzzy.filter(input, temp)
82
- const filteredTemplateNames = new Set(
83
- filteredTemplates.map((filteredTemplate) => (input ? filteredTemplate.string : filteredTemplate)),
84
- )
85
- return registry
86
- .filter((t) => filteredTemplateNames.has(t.name + t.description))
87
- .map((t) => {
88
- // add the score
89
- const { score } = filteredTemplates.find((filteredTemplate) => filteredTemplate.string === t.name + t.description)
90
- t.score = score
91
- return t
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
- const folders = await readdir(path.join(templatesDir, lang), { withFileTypes: true })
101
-
102
- const imports = await Promise.all(
103
- folders
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
- const templatePath = path.join(templatesDir, lang, name, '.netlify-function-template.mjs')
108
- const template = await import(pathToFileURL(templatePath))
109
- return template.default
110
- } catch {
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
- const registry = imports
116
- .filter((template) => template?.functionType === funcType)
117
- .sort((templateA, templateB) => {
118
- const priorityDiff = (templateA.priority || DEFAULT_PRIORITY) - (templateB.priority || DEFAULT_PRIORITY)
119
-
120
- if (priorityDiff !== 0) {
121
- return priorityDiff
122
- }
123
-
124
- // This branch is needed because `Array.prototype.sort` was not stable
125
- // until Node 11, so the original sorting order from `fs.readdirSync`
126
- // was not respected. We can simplify this once we drop support for
127
- // Node 10.
128
- return templateA - templateB
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
- return registry
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
- const specialCommands = [
149
- new inquirer.Separator(),
150
- {
151
- name: `Clone template from GitHub URL`,
152
- value: 'url',
153
- short: 'gh-url',
154
- },
155
- {
156
- name: `Report issue with, or suggest a new template`,
157
- value: 'report',
158
- short: 'gh-report',
159
- },
160
- new inquirer.Separator(),
161
- ]
162
-
163
- let language = languageFromFlag
164
-
165
- if (language === undefined) {
166
- const langs =
167
- funcType === 'edge'
168
- ? languages.filter((lang) => lang.value === 'javascript' || lang.value === 'typescript')
169
- : languages.filter(Boolean)
170
-
171
- const { language: languageFromPrompt } = await inquirer.prompt({
172
- choices: langs,
173
- message: 'Select the language of your function',
174
- name: 'language',
175
- type: 'list',
176
- })
177
-
178
- language = languageFromPrompt
179
- }
180
-
181
- let templatesForLanguage
182
-
183
- try {
184
- templatesForLanguage = await formatRegistryArrayForInquirer(language, funcType)
185
- } catch {
186
- throw error(`Invalid language: ${language}`)
187
- }
188
-
189
- const { chosenTemplate } = await inquirer.prompt({
190
- name: 'chosenTemplate',
191
- message: 'Pick a template',
192
- type: 'autocomplete',
193
- source(answersSoFar, input) {
194
- // if Edge Functions template, don't show url option
195
- const edgeCommands = specialCommands.filter((val) => val.value !== 'url')
196
- const parsedSpecialCommands = funcType === 'edge' ? edgeCommands : specialCommands
197
-
198
- if (!input || input === '') {
199
- // show separators
200
- return [...templatesForLanguage, ...parsedSpecialCommands]
201
- }
202
- // only show filtered results sorted by score
203
- const answers = [...filterRegistry(templatesForLanguage, input), ...parsedSpecialCommands].sort(
204
- (answerA, answerB) => answerB.score - answerA.score,
205
- )
206
- return answers
207
- },
208
- })
209
- return chosenTemplate
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
- const functionTypes = [
217
- { name: 'Edge function (Deno)', value: 'edge' },
218
- { name: 'Serverless function (Node/Go)', value: 'serverless' },
219
- ]
220
-
221
- const { functionType } = await inquirer.prompt([
222
- {
223
- name: 'functionType',
224
- message: "Select the type of function you'd like to create",
225
- type: 'list',
226
- choices: functionTypes,
227
- },
228
- ])
229
- return functionType
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
- const { config, site } = command.netlify
237
- const siteId = site.id
238
-
239
- if (!siteId) {
240
- error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``)
241
- }
242
-
243
- const functionsDir = config.build?.edge_functions ?? join(command.workingDir, 'netlify/edge-functions')
244
- const relFunctionsDir = relative(command.workingDir, functionsDir)
245
-
246
- if (!fs.existsSync(functionsDir)) {
247
- log(
248
- `${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(
249
- relFunctionsDir,
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
- const { api, relConfigFilePath, site } = command.netlify
268
- log(`\n${NETLIFYDEVLOG} functions directory not specified in ${relConfigFilePath} or UI settings`)
269
-
270
- if (!site.id) {
271
- error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``)
272
- }
273
-
274
- const { functionsDir } = await inquirer.prompt([
275
- {
276
- type: 'input',
277
- name: 'functionsDir',
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
- log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(functionsDir)} updated in site settings`)
297
- } catch {
298
- throw error('Error updating site settings')
299
- }
300
- return functionsDir
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
- const { config } = command.netlify
310
- const functionsDirHolder =
311
- config.functionsDirectory || join(command.workingDir, await promptFunctionsDirectory(command))
312
- const relFunctionsDirHolder = relative(command.workingDir, functionsDirHolder)
313
-
314
- if (!fs.existsSync(functionsDirHolder)) {
315
- log(
316
- `${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(
317
- relFunctionsDirHolder,
318
- )} does not exist yet, creating it...`,
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
- const folderContents = await readRepoURL(options.url)
338
- const [functionName] = options.url.split('/').slice(-1)
339
- const nameToUse = await getNameFromArgs(argumentName, options, functionName)
340
-
341
- const fnFolder = path.join(functionsDir, nameToUse)
342
- if (fs.existsSync(`${fnFolder}.js`) && fs.lstatSync(`${fnFolder}.js`).isFile()) {
343
- log(
344
- `${NETLIFYDEVWARN}: A single file version of the function ${nameToUse} already exists at ${fnFolder}.js. Terminating without further action.`,
345
- )
346
- process.exit(1)
347
- }
348
-
349
- try {
350
- await mkdir(fnFolder, { recursive: true })
351
- } catch {
352
- // Ignore
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
- try {
357
- const res = await fetch(downloadUrl)
358
- const finalName = path.basename(name, '.js') === functionName ? `${nameToUse}.js` : name
359
- const dest = fs.createWriteStream(path.join(fnFolder, finalName))
360
- res.body.pipe(dest)
361
- } catch (error_) {
362
- throw new Error(`Error while retrieving ${downloadUrl} ${error_}`)
363
- }
364
- }),
365
- )
366
-
367
- log(`${NETLIFYDEVLOG} Installing dependencies for ${nameToUse}...`)
368
- cp.exec('npm i', { cwd: path.join(functionsDir, nameToUse) }, () => {
369
- log(`${NETLIFYDEVLOG} Installing dependencies for ${nameToUse} complete `)
370
- })
371
-
372
- // read, execute, and delete function template file if exists
373
- const fnTemplateFile = path.join(fnFolder, '.netlify-function-template.mjs')
374
- if (await fileExistsAsync(fnTemplateFile)) {
375
- const {
376
- default: { addons = [], onComplete },
377
- } = await import(pathToFileURL(fnTemplateFile).href)
378
-
379
- await installAddons(command, addons, path.resolve(fnFolder))
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
- Object.entries(neededPackages)
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
- const { dependencies: functionDependencies, devDependencies: functionDevDependencies } = require(functionPackageJson)
406
- const sitePackageJson = await findUp('package.json', { cwd: functionsDir })
407
- const npmInstallFlags = ['--no-audit', '--no-fund']
408
-
409
- // If there is no site-level `package.json`, we fall back to the old behavior
410
- // of keeping that file in the function directory and running `npm install`
411
- // from there.
412
- if (!sitePackageJson) {
413
- await execa('npm', ['i', ...npmInstallFlags], { cwd: functionPath })
414
-
415
- return
416
- }
417
-
418
- const { dependencies: siteDependencies, devDependencies: siteDevDependencies } = require(sitePackageJson)
419
- const dependencies = getNpmInstallPackages(siteDependencies, functionDependencies)
420
- const devDependencies = getNpmInstallPackages(siteDevDependencies, functionDevDependencies)
421
- const npmInstallPath = path.dirname(sitePackageJson)
422
-
423
- if (dependencies.length !== 0) {
424
- await execa('npm', ['i', ...dependencies, '--save', ...npmInstallFlags], { cwd: npmInstallPath })
425
- }
426
-
427
- if (devDependencies.length !== 0) {
428
- await execa('npm', ['i', ...devDependencies, '--save-dev', ...npmInstallFlags], { cwd: npmInstallPath })
429
- }
430
-
431
- // We installed the function's dependencies in the site-level `package.json`,
432
- // so there's no reason to keep the one copied over from the template.
433
- fs.unlinkSync(functionPackageJson)
434
-
435
- // Similarly, if the template has a `package-lock.json` file, we delete it.
436
- try {
437
- const functionPackageLock = path.join(functionPath, 'package-lock.json')
438
-
439
- fs.unlinkSync(functionPackageLock)
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
- // pull the rest of the metadata from the template
456
- const chosenTemplate = await pickTemplate(options, funcType)
457
- if (chosenTemplate === 'url') {
458
- const { chosenUrl } = await inquirer.prompt([
459
- {
460
- name: 'chosenUrl',
461
- message: 'URL to clone: ',
462
- type: 'input',
463
- validate: (/** @type {string} */ val) => Boolean(validateRepoURL(val)),
464
- // make sure it is not undefined and is a valid filename.
465
- // this has some nuance i have ignored, eg crossenv and i18n concerns
466
- },
467
- ])
468
- options.url = chosenUrl.trim()
469
- try {
470
- await downloadFromURL(command, options, argumentName, functionsDir)
471
- } catch (error_) {
472
- error(`$${NETLIFYDEVERR} Error downloading from URL: ${options.url}`)
473
- error(error_)
474
- process.exit(1)
475
- }
476
- } else if (chosenTemplate === 'report') {
477
- log(`${NETLIFYDEVLOG} Open in browser: https://github.com/netlify/cli/issues/new`)
478
- } else {
479
- const { addons = [], lang, name: templateName, onComplete } = chosenTemplate
480
- const pathToTemplate = path.join(templatesDir, lang, templateName)
481
- if (!fs.existsSync(pathToTemplate)) {
482
- throw new Error(
483
- `There isn't a corresponding directory to the selected name. Template '${templateName}' is misconfigured`,
484
- )
485
- }
486
-
487
- const name = await getNameFromArgs(argumentName, options, templateName)
488
-
489
- log(`${NETLIFYDEVLOG} Creating function ${chalk.cyan.inverse(name)}`)
490
- const functionPath = ensureFunctionPathIsOk(functionsDir, name)
491
-
492
- const vars = { name }
493
- let functionPackageJson
494
-
495
- // These files will not be part of the log message because they'll likely
496
- // be removed before the command finishes.
497
- const omittedFromOutput = new Set(['.netlify-function-template.mjs', 'package.json', 'package-lock.json'])
498
- const createdFiles = await copyTemplateDir(pathToTemplate, functionPath, vars)
499
- createdFiles.forEach((filePath) => {
500
- const filename = path.basename(filePath)
501
-
502
- if (!omittedFromOutput.has(filename)) {
503
- log(`${NETLIFYDEVLOG} ${chalk.greenBright('Created')} ${filePath}`)
504
- }
505
-
506
- fs.chmodSync(path.resolve(filePath), TEMPLATE_PERMISSIONS)
507
- if (filePath.includes('package.json')) {
508
- functionPackageJson = path.resolve(filePath)
509
- }
510
- })
511
-
512
- // delete function template file that was copied over by copydir
513
- await unlink(path.join(functionPath, '.netlify-function-template.mjs'))
514
-
515
- // npm install
516
- if (functionPackageJson !== undefined) {
517
- const spinner = ora({
518
- text: `Installing dependencies for ${name}`,
519
- spinner: 'moon',
520
- }).start()
521
- await installDeps({ functionPackageJson, functionPath, functionsDir })
522
- spinner.succeed(`Installed dependencies for ${name}`)
523
- }
524
-
525
- if (funcType === 'edge') {
526
- registerEFInToml(name, command.netlify)
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
- try {
538
- const addon = getCurrentAddon({ addons, addonName })
539
- if (addon && addon.id) {
540
- log(`The "${addonName} add-on" already exists for ${siteData.name}`)
541
- return false
542
- }
543
- await api.createServiceInstance({
544
- siteId,
545
- addon: addonName,
546
- body: { config: {} },
547
- })
548
- log(`Add-on "${addonName}" created for ${siteData.name}`)
549
- return true
550
- } catch (error_) {
551
- error(error_.message)
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
- const { config } = command.netlify
563
-
564
- if (onComplete) {
565
- const env = await getDotEnvVariables({
566
- devConfig: { ...config.dev },
567
- env: command.netlify.cachedConfig.env,
568
- site: command.netlify.site,
569
- })
570
- injectEnvVariables(env)
571
- await onComplete.call(command)
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
- const { config } = command.netlify
584
-
585
- if (!addonCreated || !addonDidInstall) {
586
- return
587
- }
588
-
589
- const { confirmPostInstall } = await inquirer.prompt([
590
- {
591
- type: 'confirm',
592
- name: 'confirmPostInstall',
593
- 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?`,
594
- default: false,
595
- },
596
- ])
597
-
598
- if (!confirmPostInstall) {
599
- return
600
- }
601
-
602
- await injectEnvVariables({
603
- devConfig: { ...config.dev },
604
- env: command.netlify.cachedConfig.env,
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
- if (functionAddons.length === 0) {
619
- return
620
- }
621
-
622
- const { api, site } = command.netlify
623
- const siteId = site.id
624
- if (!siteId) {
625
- log('No site id found, please run inside a site directory or `netlify link`')
626
- return false
627
- }
628
- log(`${NETLIFYDEVLOG} checking Netlify APIs...`)
629
-
630
- const [siteData, siteAddons] = await Promise.all([getSiteData({ api, siteId }), getAddons({ api, siteId })])
631
-
632
- const arr = functionAddons.map(async ({ addonDidInstall, addonName }) => {
633
- log(`${NETLIFYDEVLOG} installing addon: ${chalk.yellow.inverse(addonName)}`)
634
- try {
635
- const addonCreated = await createFunctionAddon({
636
- api,
637
- addons: siteAddons,
638
- siteId,
639
- addonName,
640
- siteData,
641
- })
642
-
643
- await handleAddonDidInstall({ addonCreated, addonDidInstall, command, fnPath })
644
- } catch (error_) {
645
- error(`${NETLIFYDEVERR} Error installing addon: `, error_)
646
- }
647
- })
648
- return Promise.all(arr)
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
- const { configFilePath, relConfigFilePath } = options
658
- if (!fs.existsSync(configFilePath)) {
659
- log(`${NETLIFYDEVLOG} \`${relConfigFilePath}\` file does not exist yet. Creating it...`)
660
- }
661
-
662
- let { funcPath } = await inquirer.prompt([
663
- {
664
- type: 'input',
665
- name: 'funcPath',
666
- message: `What route do you want your edge function to be invoked on?`,
667
- default: '/test',
668
- validate: (val) => Boolean(val),
669
- // Make sure route isn't undefined and is valid
670
- // Todo: add more validation?
671
- },
672
- ])
673
-
674
- // Make sure path begins with a '/'
675
- if (funcPath[0] !== '/') {
676
- funcPath = `/${funcPath}`
677
- }
678
-
679
- const functionRegister = `\n\n[[edge_functions]]\nfunction = "${funcName}"\npath = "${funcPath}"`
680
-
681
- try {
682
- fs.promises.appendFile(configFilePath, functionRegister)
683
- log(
684
- `${NETLIFYDEVLOG} Function '${funcName}' registered for route \`${funcPath}\`. To change, edit your \`${relConfigFilePath}\` file.`,
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
- const functionPath = path.join(functionsDir, name)
700
- if (fs.existsSync(functionPath)) {
701
- log(`${NETLIFYDEVLOG} Function ${functionPath} already exists, cancelling...`)
702
- process.exit(1)
703
- }
704
- return functionPath
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
- const functionType = await selectTypeOfFunc()
715
- const functionsDir =
716
- functionType === 'edge' ? await ensureEdgeFuncDirExists(command) : await ensureFunctionDirExists(command)
717
-
718
- /* either download from URL or scaffold from template */
719
- const mainFunc = options.url ? downloadFromURL : scaffoldFromTemplate
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
- export const createFunctionsCreateCommand = (program) =>
729
- program
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
- 'netlify functions:create',
739
- 'netlify functions:create hello-world',
740
- 'netlify functions:create --name hello-world',
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);