netlify-cli 15.9.1 → 15.10.0-rc.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 (43) hide show
  1. package/bin/run.mjs +6 -5
  2. package/npm-shrinkwrap.json +369 -266
  3. package/package.json +8 -9
  4. package/src/commands/base-command.mjs +295 -116
  5. package/src/commands/build/build.mjs +9 -1
  6. package/src/commands/deploy/deploy.mjs +22 -9
  7. package/src/commands/dev/dev.mjs +22 -17
  8. package/src/commands/functions/functions-create.mjs +118 -89
  9. package/src/commands/functions/functions-invoke.mjs +10 -7
  10. package/src/commands/functions/functions-list.mjs +2 -2
  11. package/src/commands/init/init.mjs +1 -1
  12. package/src/commands/link/link.mjs +5 -5
  13. package/src/commands/serve/serve.mjs +10 -6
  14. package/src/commands/sites/sites-create-template.mjs +1 -1
  15. package/src/commands/sites/sites-create.mjs +1 -1
  16. package/src/functions-templates/typescript/hello-world/package-lock.json +6 -6
  17. package/src/lib/edge-functions/bootstrap.mjs +1 -1
  18. package/src/lib/edge-functions/headers.mjs +1 -0
  19. package/src/lib/edge-functions/internal.mjs +5 -3
  20. package/src/lib/edge-functions/proxy.mjs +29 -4
  21. package/src/lib/functions/runtimes/js/index.mjs +1 -1
  22. package/src/lib/functions/runtimes/js/worker.mjs +1 -1
  23. package/src/lib/functions/server.mjs +3 -2
  24. package/src/lib/spinner.mjs +1 -1
  25. package/src/recipes/vscode/index.mjs +24 -6
  26. package/src/utils/build-info.mjs +100 -0
  27. package/src/utils/command-helpers.mjs +16 -7
  28. package/src/utils/detect-server-settings.mjs +133 -245
  29. package/src/utils/framework-server.mjs +6 -5
  30. package/src/utils/functions/functions.mjs +8 -5
  31. package/src/utils/get-repo-data.mjs +5 -6
  32. package/src/utils/init/config-github.mjs +2 -2
  33. package/src/utils/init/config-manual.mjs +24 -7
  34. package/src/utils/init/utils.mjs +62 -63
  35. package/src/utils/proxy-server.mjs +7 -4
  36. package/src/utils/proxy.mjs +4 -3
  37. package/src/utils/read-repo-url.mjs +4 -0
  38. package/src/utils/run-build.mjs +58 -32
  39. package/src/utils/shell.mjs +24 -7
  40. package/src/utils/state-config.mjs +5 -1
  41. package/src/utils/static-server.mjs +4 -0
  42. package/src/utils/telemetry/report-error.mjs +8 -4
  43. package/src/utils/init/frameworks.mjs +0 -23
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  import { stat } from 'fs/promises'
3
3
  import { basename, resolve } from 'path'
4
- import { cwd, env } from 'process'
4
+ import { env } from 'process'
5
5
 
6
6
  import { runCoreSteps } from '@netlify/build'
7
7
  import { restoreConfig, updateConfig } from '@netlify/config'
@@ -64,16 +64,18 @@ const triggerDeploy = async ({ api, options, siteData, siteId }) => {
64
64
  /**
65
65
  * g
66
66
  * @param {object} config
67
+ * @param {string} config.workingDir The process working directory
67
68
  * @param {object} config.config
68
69
  * @param {import('commander').OptionValues} config.options
69
70
  * @param {object} config.site
70
71
  * @param {object} config.siteData
71
72
  * @returns {Promise<string>}
72
73
  */
73
- const getDeployFolder = async ({ config, options, site, siteData }) => {
74
+ const getDeployFolder = async ({ config, options, site, siteData, workingDir }) => {
75
+ console.log()
74
76
  let deployFolder
75
77
  if (options.dir) {
76
- deployFolder = resolve(cwd(), options.dir)
78
+ deployFolder = resolve(workingDir, options.dir)
77
79
  } else if (config?.build?.publish) {
78
80
  deployFolder = resolve(site.root, config.build.publish)
79
81
  } else if (siteData?.build_settings?.dir) {
@@ -82,14 +84,14 @@ const getDeployFolder = async ({ config, options, site, siteData }) => {
82
84
 
83
85
  if (!deployFolder) {
84
86
  log('Please provide a publish directory (e.g. "public" or "dist" or "."):')
85
- log(cwd())
87
+ log(workingDir)
86
88
  const { promptPath } = await inquirer.prompt([
87
89
  {
88
90
  type: 'input',
89
91
  name: 'promptPath',
90
92
  message: 'Publish directory',
91
93
  default: '.',
92
- filter: (input) => resolve(cwd(), input),
94
+ filter: (input) => resolve(workingDir, input),
93
95
  },
94
96
  ])
95
97
  deployFolder = promptPath
@@ -128,14 +130,15 @@ const validateDeployFolder = async ({ deployFolder }) => {
128
130
  * @param {import('commander').OptionValues} config.options
129
131
  * @param {object} config.site
130
132
  * @param {object} config.siteData
133
+ * @param {string} config.workingDir // The process working directory
131
134
  * @returns {string}
132
135
  */
133
- const getFunctionsFolder = ({ config, options, site, siteData }) => {
136
+ const getFunctionsFolder = ({ config, options, site, siteData, workingDir }) => {
134
137
  let functionsFolder
135
138
  // Support "functions" and "Functions"
136
139
  const funcConfig = config.functionsDirectory
137
140
  if (options.functions) {
138
- functionsFolder = resolve(cwd(), options.functions)
141
+ functionsFolder = resolve(workingDir, options.functions)
139
142
  } else if (funcConfig) {
140
143
  functionsFolder = resolve(site.root, funcConfig)
141
144
  } else if (siteData?.build_settings?.functions_dir) {
@@ -178,12 +181,21 @@ const validateFolders = async ({ deployFolder, functionsFolder }) => {
178
181
  return { deployFolderStat, functionsFolderStat }
179
182
  }
180
183
 
184
+ /**
185
+ * @param {object} config
186
+ * @param {string} config.deployFolder
187
+ * @param {*} config.site
188
+ * @returns
189
+ */
181
190
  const getDeployFilesFilter = ({ deployFolder, site }) => {
182
191
  // site.root === deployFolder can happen when users run `netlify deploy --dir .`
183
192
  // in that specific case we don't want to publish the repo node_modules
184
193
  // when site.root !== deployFolder the behaviour matches our buildbot
185
194
  const skipNodeModules = site.root === deployFolder
186
195
 
196
+ /**
197
+ * @param {string} filename
198
+ */
187
199
  return (filename) => {
188
200
  if (filename == null) {
189
201
  return false
@@ -496,6 +508,7 @@ const printResults = ({ deployToProduction, json, results, runBuildCommand }) =>
496
508
  * @param {import('../base-command.mjs').default} command
497
509
  */
498
510
  const deploy = async (options, command) => {
511
+ const { workingDir } = command
499
512
  const { api, site, siteInfo } = command.netlify
500
513
  const alias = options.alias || options.branch
501
514
 
@@ -566,8 +579,8 @@ const deploy = async (options, command) => {
566
579
  })
567
580
  const config = newConfig || command.netlify.config
568
581
 
569
- const deployFolder = await getDeployFolder({ options, config, site, siteData })
570
- const functionsFolder = getFunctionsFolder({ options, config, site, siteData })
582
+ const deployFolder = await getDeployFolder({ workingDir, options, config, site, siteData })
583
+ const functionsFolder = getFunctionsFolder({ workingDir, options, config, site, siteData })
571
584
  const { configPath } = site
572
585
  const edgeFunctionsConfig = command.netlify.config.edge_functions
573
586
 
@@ -9,7 +9,6 @@ import { printBanner } from '../../utils/banner.mjs'
9
9
  import {
10
10
  BANG,
11
11
  chalk,
12
- exit,
13
12
  log,
14
13
  NETLIFYDEV,
15
14
  NETLIFYDEVERR,
@@ -35,7 +34,7 @@ import { createDevExecCommand } from './dev-exec.mjs'
35
34
  * @param {object} config
36
35
  * @param {*} config.api
37
36
  * @param {import('commander').OptionValues} config.options
38
- * @param {*} config.settings
37
+ * @param {import('../../utils/types.js').ServerSettings} config.settings
39
38
  * @param {*} config.site
40
39
  * @param {*} config.state
41
40
  * @returns
@@ -68,6 +67,9 @@ const handleLiveTunnel = async ({ api, options, settings, site, state }) => {
68
67
  }
69
68
  }
70
69
 
70
+ /**
71
+ * @param {string} args
72
+ */
71
73
  const validateShortFlagArgs = (args) => {
72
74
  if (args.startsWith('=')) {
73
75
  throw new Error(
@@ -94,11 +96,13 @@ const dev = async (options, command) => {
94
96
  const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
95
97
  config.dev = { ...config.dev }
96
98
  config.build = { ...config.build }
97
- /** @type {import('./types').DevConfig} */
99
+ /** @type {import('./types.js').DevConfig} */
98
100
  const devConfig = {
99
101
  framework: '#auto',
102
+ autoLaunch: Boolean(options.open),
100
103
  ...(config.functionsDirectory && { functions: config.functionsDirectory }),
101
104
  ...(config.build.publish && { publish: config.build.publish }),
105
+ ...(config.build.base && { base: config.build.base }),
102
106
  ...config.dev,
103
107
  ...options,
104
108
  }
@@ -124,20 +128,17 @@ const dev = async (options, command) => {
124
128
  siteInfo,
125
129
  })
126
130
 
127
- /** @type {Partial<import('../../utils/types').ServerSettings>} */
128
- let settings = {}
131
+ /** @type {import('../../utils/types.js').ServerSettings} */
132
+ let settings
129
133
  try {
130
- settings = await detectServerSettings(devConfig, options, site.root, {
131
- site: {
132
- id: site.id,
133
- url: siteUrl,
134
- },
135
- })
134
+ settings = await detectServerSettings(devConfig, options, command)
136
135
 
137
136
  cachedConfig.config = getConfigWithPlugins(cachedConfig.config, settings)
138
137
  } catch (error_) {
139
- log(NETLIFYDEVERR, error_.message)
140
- exit(1)
138
+ if (error_ && typeof error_ === 'object' && 'message' in error_) {
139
+ log(NETLIFYDEVERR, error_.message)
140
+ }
141
+ process.exit(1)
141
142
  }
142
143
 
143
144
  command.setAnalyticsPayload({ live: options.live })
@@ -151,10 +152,9 @@ const dev = async (options, command) => {
151
152
  log(`${NETLIFYDEVWARN} Setting up local development server`)
152
153
 
153
154
  const { configPath: configPathOverride } = await runDevTimeline({
154
- cachedConfig,
155
+ command,
155
156
  options,
156
157
  settings,
157
- site,
158
158
  env: {
159
159
  URL: url,
160
160
  DEPLOY_URL: url,
@@ -188,8 +188,11 @@ const dev = async (options, command) => {
188
188
 
189
189
  // TODO: We should consolidate this with the existing config watcher.
190
190
  const getUpdatedConfig = async () => {
191
- const cwd = options.cwd || process.cwd()
192
- const { config: newConfig } = await command.getConfig({ cwd, offline: true, state })
191
+ const { config: newConfig } = await command.getConfig({
192
+ cwd: command.workingDir,
193
+ offline: true,
194
+ state,
195
+ })
193
196
  const normalizedNewConfig = normalizeConfig(newConfig)
194
197
 
195
198
  return normalizedNewConfig
@@ -202,6 +205,7 @@ const dev = async (options, command) => {
202
205
  config,
203
206
  configPath: configPathOverride,
204
207
  debug: options.debug,
208
+ projectDir: command.workingDir,
205
209
  env,
206
210
  getUpdatedConfig,
207
211
  inspectSettings,
@@ -248,6 +252,7 @@ export const createDevCommand = (program) => {
248
252
  .argParser((value) => Number.parseInt(value))
249
253
  .hideHelp(true),
250
254
  )
255
+ .addOption(new Option('--no-open', 'disables the automatic opening of a browser window'))
251
256
  .option('--target-port <port>', 'port of target app server', (value) => Number.parseInt(value))
252
257
  .option('--framework <name>', 'framework to use. Defaults to #auto which automatically detects a framework')
253
258
  .option('-d ,--dir <path>', 'dir with static files')
@@ -3,7 +3,7 @@ import cp from 'child_process'
3
3
  import fs from 'fs'
4
4
  import { mkdir, readdir, unlink } from 'fs/promises'
5
5
  import { createRequire } from 'module'
6
- import path, { dirname } from 'path'
6
+ import path, { dirname, join, relative } from 'path'
7
7
  import process from 'process'
8
8
  import { fileURLToPath, pathToFileURL } from 'url'
9
9
  import { promisify } from 'util'
@@ -12,7 +12,6 @@ import copyTemplateDirOriginal from 'copy-template-dir'
12
12
  import { findUp } from 'find-up'
13
13
  import fuzzy from 'fuzzy'
14
14
  import inquirer from 'inquirer'
15
- import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
16
15
  import fetch from 'node-fetch'
17
16
  import ora from 'ora'
18
17
 
@@ -31,8 +30,10 @@ const templatesDir = path.resolve(dirname(fileURLToPath(import.meta.url)), '../.
31
30
 
32
31
  const showRustTemplates = process.env.NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE === 'true'
33
32
 
34
- // Ensure that there's a sub-directory in `src/functions-templates` named after
35
- // each `value` property in this list.
33
+ /**
34
+ * Ensure that there's a sub-directory in `src/functions-templates` named after
35
+ * each `value` property in this list.
36
+ */
36
37
  const languages = [
37
38
  { name: 'JavaScript', value: 'javascript' },
38
39
  { name: 'TypeScript', value: 'typescript' },
@@ -91,23 +92,28 @@ const filterRegistry = function (registry, input) {
91
92
  })
92
93
  }
93
94
 
95
+ /**
96
+ * @param {string} lang
97
+ * @param {'edge' | 'serverless'} funcType
98
+ */
94
99
  const formatRegistryArrayForInquirer = async function (lang, funcType) {
95
- const folderNames = await readdir(path.join(templatesDir, lang))
100
+ const folders = await readdir(path.join(templatesDir, lang), { withFileTypes: true })
96
101
 
97
102
  const imports = await Promise.all(
98
- folderNames
99
- // filter out markdown files
100
- .filter((folderName) => !folderName.endsWith('.md'))
101
- .map(async (folderName) => {
102
- const templatePath = path.join(templatesDir, lang, folderName, '.netlify-function-template.mjs')
103
- const template = await import(pathToFileURL(templatePath))
104
-
105
- return template.default
103
+ folders
104
+ .filter((folder) => Boolean(folder?.isDirectory()))
105
+ .map(async ({ name }) => {
106
+ 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
112
+ }
106
113
  }),
107
114
  )
108
-
109
115
  const registry = imports
110
- .filter((template) => template.functionType === funcType)
116
+ .filter((template) => template?.functionType === funcType)
111
117
  .sort((templateA, templateB) => {
112
118
  const priorityDiff = (templateA.priority || DEFAULT_PRIORITY) - (templateB.priority || DEFAULT_PRIORITY)
113
119
 
@@ -136,7 +142,7 @@ const formatRegistryArrayForInquirer = async function (lang, funcType) {
136
142
  /**
137
143
  * pick template from our existing templates
138
144
  * @param {import('commander').OptionValues} config
139
- *
145
+ * @param {'edge' | 'serverless'} funcType
140
146
  */
141
147
  const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
142
148
  const specialCommands = [
@@ -172,8 +178,6 @@ const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
172
178
  language = languageFromPrompt
173
179
  }
174
180
 
175
- inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
176
-
177
181
  let templatesForLanguage
178
182
 
179
183
  try {
@@ -207,6 +211,7 @@ const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
207
211
 
208
212
  const DEFAULT_PRIORITY = 999
209
213
 
214
+ /** @returns {Promise<'edge' | 'serverless'>} */
210
215
  const selectTypeOfFunc = async () => {
211
216
  const functionTypes = [
212
217
  { name: 'Edge function (Deno)', value: 'edge' },
@@ -224,92 +229,100 @@ const selectTypeOfFunc = async () => {
224
229
  return functionType
225
230
  }
226
231
 
232
+ /**
233
+ * @param {import('../base-command.mjs').default} command
234
+ */
227
235
  const ensureEdgeFuncDirExists = function (command) {
228
236
  const { config, site } = command.netlify
229
237
  const siteId = site.id
230
- let functionsDirHolder = config.build.edge_functions
231
238
 
232
239
  if (!siteId) {
233
240
  error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``)
234
241
  }
235
242
 
236
- if (!functionsDirHolder) {
237
- functionsDirHolder = 'netlify/edge-functions'
238
- }
243
+ const functionsDir = config.build?.edge_functions ?? join(command.workingDir, 'netlify/edge-functions')
244
+ const relFunctionsDir = relative(command.workingDir, functionsDir)
239
245
 
240
- if (!fs.existsSync(functionsDirHolder)) {
246
+ if (!fs.existsSync(functionsDir)) {
241
247
  log(
242
248
  `${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(
243
- functionsDirHolder,
249
+ relFunctionsDir,
244
250
  )} does not exist yet, creating it...`,
245
251
  )
246
252
 
247
- fs.mkdirSync(functionsDirHolder, { recursive: true })
253
+ fs.mkdirSync(functionsDir, { recursive: true })
248
254
 
249
- log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(functionsDirHolder)} created.`)
255
+ log(`${NETLIFYDEVLOG} Edge Functions directory ${chalk.magenta.inverse(relFunctionsDir)} created.`)
250
256
  }
251
- return functionsDirHolder
257
+
258
+ return functionsDir
252
259
  }
253
260
 
254
261
  /**
255
- * Get functions directory (and make it if necessary)
262
+ * Prompts the user to choose a functions directory
256
263
  * @param {import('../base-command.mjs').default} command
257
- * @returns {Promise<string|never>} - functions directory or throws an error
264
+ * @returns {Promise<string>} - functions directory or throws an error
258
265
  */
259
- const ensureFunctionDirExists = async function (command) {
260
- const { api, config, site } = command.netlify
261
- const siteId = site.id
262
- let functionsDirHolder = config.functionsDirectory
263
-
264
- if (!functionsDirHolder) {
265
- log(`${NETLIFYDEVLOG} functions directory not specified in netlify.toml or UI settings`)
266
-
267
- if (!siteId) {
268
- error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``)
269
- }
266
+ const promptFunctionsDirectory = async (command) => {
267
+ const { api, relConfigFilePath, site } = command.netlify
268
+ log(`\n${NETLIFYDEVLOG} functions directory not specified in ${relConfigFilePath} or UI settings`)
270
269
 
271
- const { functionsDir } = await inquirer.prompt([
272
- {
273
- type: 'input',
274
- name: 'functionsDir',
275
- message:
276
- 'Enter the path, relative to your site’s base directory in your repository, where your functions should live:',
277
- default: 'netlify/functions',
278
- },
279
- ])
270
+ if (!site.id) {
271
+ error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``)
272
+ }
280
273
 
281
- functionsDirHolder = functionsDir
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
282
 
283
- try {
284
- log(`${NETLIFYDEVLOG} updating site settings with ${chalk.magenta.inverse(functionsDirHolder)}`)
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: functionsDirHolder,
292
- },
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,
293
292
  },
294
- })
293
+ },
294
+ })
295
295
 
296
- log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(functionsDirHolder)} updated in site settings`)
297
- } catch {
298
- throw error('Error updating site settings')
299
- }
296
+ log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(functionsDir)} updated in site settings`)
297
+ } catch {
298
+ throw error('Error updating site settings')
300
299
  }
300
+ return functionsDir
301
+ }
302
+
303
+ /**
304
+ * Get functions directory (and make it if necessary)
305
+ * @param {import('../base-command.mjs').default} command
306
+ * @returns {Promise<string>} - functions directory or throws an error
307
+ */
308
+ 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)
301
313
 
302
- if (!(await fileExistsAsync(functionsDirHolder))) {
314
+ if (!fs.existsSync(functionsDirHolder)) {
303
315
  log(
304
316
  `${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(
305
- functionsDirHolder,
317
+ relFunctionsDirHolder,
306
318
  )} does not exist yet, creating it...`,
307
319
  )
308
320
 
309
321
  await mkdir(functionsDirHolder, { recursive: true })
310
322
 
311
- log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(functionsDirHolder)} created`)
323
+ log(`${NETLIFYDEVLOG} functions directory ${chalk.magenta.inverse(relFunctionsDirHolder)} created`)
312
324
  }
325
+
313
326
  return functionsDirHolder
314
327
  }
315
328
 
@@ -370,20 +383,24 @@ const downloadFromURL = async function (command, options, argumentName, function
370
383
  }
371
384
  }
372
385
 
373
- // Takes a list of existing packages and a list of packages required by a
374
- // function, and returns the packages from the latter that aren't present
375
- // in the former. The packages are returned as an array of strings with the
376
- // name and version range (e.g. '@netlify/functions@0.1.0').
386
+ /**
387
+ * Takes a list of existing packages and a list of packages required by a
388
+ * function, and returns the packages from the latter that aren't present
389
+ * in the former. The packages are returned as an array of strings with the
390
+ * name and version range (e.g. '@netlify/functions@0.1.0').
391
+ */
377
392
  const getNpmInstallPackages = (existingPackages = {}, neededPackages = {}) =>
378
393
  Object.entries(neededPackages)
379
394
  .filter(([name]) => existingPackages[name] === undefined)
380
395
  .map(([name, version]) => `${name}@${version}`)
381
396
 
382
- // When installing a function's dependencies, we first try to find a site-level
383
- // `package.json` file. If we do, we look for any dependencies of the function
384
- // that aren't already listed as dependencies of the site and install them. If
385
- // we don't do this check, we may be upgrading the version of a module used in
386
- // another part of the project, which we don't want to do.
397
+ /**
398
+ * When installing a function's dependencies, we first try to find a site-level
399
+ * `package.json` file. If we do, we look for any dependencies of the function
400
+ * that aren't already listed as dependencies of the site and install them. If
401
+ * we don't do this check, we may be upgrading the version of a module used in
402
+ * another part of the project, which we don't want to do.
403
+ */
387
404
  const installDeps = async ({ functionPackageJson, functionPath, functionsDir }) => {
388
405
  const { dependencies: functionDependencies, devDependencies: functionDevDependencies } = require(functionPackageJson)
389
406
  const sitePackageJson = await findUp('package.json', { cwd: functionsDir })
@@ -430,8 +447,8 @@ const installDeps = async ({ functionPackageJson, functionPath, functionsDir })
430
447
  * @param {import('../base-command.mjs').default} command
431
448
  * @param {import('commander').OptionValues} options
432
449
  * @param {string} argumentName
433
- * @param {string} functionsDir
434
- * @param {string} funcType
450
+ * @param {string} functionsDir Absolute path of the functions directory
451
+ * @param {'edge' | 'serverless'} funcType
435
452
  */
436
453
  // eslint-disable-next-line max-params
437
454
  const scaffoldFromTemplate = async function (command, options, argumentName, functionsDir, funcType) {
@@ -443,7 +460,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun
443
460
  name: 'chosenUrl',
444
461
  message: 'URL to clone: ',
445
462
  type: 'input',
446
- validate: (val) => Boolean(validateRepoURL(val)),
463
+ validate: (/** @type {string} */ val) => Boolean(validateRepoURL(val)),
447
464
  // make sure it is not undefined and is a valid filename.
448
465
  // this has some nuance i have ignored, eg crossenv and i18n concerns
449
466
  },
@@ -506,7 +523,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun
506
523
  }
507
524
 
508
525
  if (funcType === 'edge') {
509
- registerEFInToml(name)
526
+ registerEFInToml(name, command.netlify)
510
527
  }
511
528
 
512
529
  await installAddons(command, addons, path.resolve(functionPath))
@@ -631,9 +648,15 @@ const installAddons = async function (command, functionAddons, fnPath) {
631
648
  return Promise.all(arr)
632
649
  }
633
650
 
634
- const registerEFInToml = async (funcName) => {
635
- if (!fs.existsSync('netlify.toml')) {
636
- log(`${NETLIFYDEVLOG} \`netlify.toml\` file does not exist yet. Creating it...`)
651
+ /**
652
+ *
653
+ * @param {string} funcName
654
+ * @param {import('../types.js').NetlifyOptions} options
655
+ */
656
+ 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...`)
637
660
  }
638
661
 
639
662
  let { funcPath } = await inquirer.prompt([
@@ -656,17 +679,22 @@ const registerEFInToml = async (funcName) => {
656
679
  const functionRegister = `\n\n[[edge_functions]]\nfunction = "${funcName}"\npath = "${funcPath}"`
657
680
 
658
681
  try {
659
- fs.promises.appendFile('netlify.toml', functionRegister)
682
+ fs.promises.appendFile(configFilePath, functionRegister)
660
683
  log(
661
- `${NETLIFYDEVLOG} Function '${funcName}' registered for route \`${funcPath}\`. To change, edit your \`netlify.toml\` file.`,
684
+ `${NETLIFYDEVLOG} Function '${funcName}' registered for route \`${funcPath}\`. To change, edit your \`${relConfigFilePath}\` file.`,
662
685
  )
663
686
  } catch {
664
- error(`${NETLIFYDEVERR} Unable to register function. Please check your \`netlify.toml\` file.`)
687
+ error(`${NETLIFYDEVERR} Unable to register function. Please check your \`${relConfigFilePath}\` file.`)
665
688
  }
666
689
  }
667
690
 
668
- // we used to allow for a --dir command,
669
- // but have retired that to force every scaffolded function to be a directory
691
+ /**
692
+ * we used to allow for a --dir command,
693
+ * but have retired that to force every scaffolded function to be a directory
694
+ * @param {string} functionsDir
695
+ * @param {string} name
696
+ * @returns
697
+ */
670
698
  const ensureFunctionPathIsOk = function (functionsDir, name) {
671
699
  const functionPath = path.join(functionsDir, name)
672
700
  if (fs.existsSync(functionPath)) {
@@ -678,6 +706,7 @@ const ensureFunctionPathIsOk = function (functionsDir, name) {
678
706
 
679
707
  /**
680
708
  * The functions:create command
709
+ * @param {string} name
681
710
  * @param {import('commander').OptionValues} options
682
711
  * @param {import('../base-command.mjs').default} command
683
712
  */
@@ -2,7 +2,6 @@
2
2
  import fs from 'fs'
3
3
  import { createRequire } from 'module'
4
4
  import path from 'path'
5
- import process from 'process'
6
5
 
7
6
  import inquirer from 'inquirer'
8
7
  import fetch from 'node-fetch'
@@ -56,14 +55,18 @@ const formatQstring = function (querystring) {
56
55
  return ''
57
56
  }
58
57
 
59
- /** process payloads from flag */
60
- const processPayloadFromFlag = function (payloadString) {
58
+ /**
59
+ * process payloads from flag
60
+ * @param {string} payloadString
61
+ * @param {string} workingDir
62
+ */
63
+ const processPayloadFromFlag = function (payloadString, workingDir) {
61
64
  if (payloadString) {
62
65
  // case 1: jsonstring
63
66
  let payload = tryParseJSON(payloadString)
64
67
  if (payload) return payload
65
68
  // case 2: jsonpath
66
- const payloadpath = path.join(process.cwd(), payloadString)
69
+ const payloadpath = path.join(workingDir, payloadString)
67
70
  const pathexists = fs.existsSync(payloadpath)
68
71
  if (pathexists) {
69
72
  try {
@@ -141,11 +144,11 @@ const getFunctionToTrigger = function (options, argumentName) {
141
144
  * @param {import('../base-command.mjs').default} command
142
145
  */
143
146
  const functionsInvoke = async (nameArgument, options, command) => {
144
- const { config } = command.netlify
147
+ const { config, relConfigFilePath } = command.netlify
145
148
 
146
149
  const functionsDir = options.functions || (config.dev && config.dev.functions) || config.functionsDirectory
147
150
  if (typeof functionsDir === 'undefined') {
148
- error('functions directory is undefined, did you forget to set it in netlify.toml?')
151
+ error(`functions directory is undefined, did you forget to set it in ${relConfigFilePath}?`)
149
152
  }
150
153
 
151
154
  if (!options.port)
@@ -210,7 +213,7 @@ const functionsInvoke = async (nameArgument, options, command) => {
210
213
  // }
211
214
  }
212
215
  }
213
- const payload = processPayloadFromFlag(options.payload)
216
+ const payload = processPayloadFromFlag(options.payload, command.workingDir)
214
217
  body = { ...body, ...payload }
215
218
 
216
219
  try {
@@ -16,7 +16,7 @@ const normalizeFunction = function (deployedFunctions, { name, urlPath: url }) {
16
16
  * @param {import('../base-command.mjs').default} command
17
17
  */
18
18
  const functionsList = async (options, command) => {
19
- const { config, siteInfo } = command.netlify
19
+ const { config, relConfigFilePath, siteInfo } = command.netlify
20
20
 
21
21
  const deploy = siteInfo.published_deploy || {}
22
22
  const deployedFunctions = deploy.available_functions || []
@@ -25,7 +25,7 @@ const functionsList = async (options, command) => {
25
25
 
26
26
  if (typeof functionsDir === 'undefined') {
27
27
  log('Functions directory is undefined')
28
- log('Please verify functions.directory is set in your Netlify configuration file (netlify.toml/yml/json)')
28
+ log(`Please verify functions.directory is set in your Netlify configuration file ${relConfigFilePath}`)
29
29
  log('See https://docs.netlify.com/configure-builds/file-based-configuration/ for more information')
30
30
  exit(1)
31
31
  }
@@ -196,7 +196,7 @@ export const init = async (options, command) => {
196
196
  }
197
197
 
198
198
  // Look for local repo
199
- const repoData = await getRepoData({ remoteName: options.gitRemoteName })
199
+ const repoData = await getRepoData({ workingDir: command.workingDir, remoteName: options.gitRemoteName })
200
200
  if (repoData.error) {
201
201
  await handleNoGitRemoteAndExit({ command, error: repoData.error, state })
202
202
  }