netlify-cli 15.10.0 → 16.0.0-alpha.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 (47) hide show
  1. package/bin/run.mjs +6 -5
  2. package/npm-shrinkwrap.json +628 -42
  3. package/package.json +4 -5
  4. package/src/commands/base-command.mjs +295 -118
  5. package/src/commands/build/build.mjs +9 -1
  6. package/src/commands/deploy/deploy.mjs +42 -18
  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 +3 -3
  11. package/src/commands/functions/functions-serve.mjs +1 -0
  12. package/src/commands/init/init.mjs +1 -1
  13. package/src/commands/link/link.mjs +5 -5
  14. package/src/commands/serve/serve.mjs +10 -6
  15. package/src/commands/sites/sites-create-template.mjs +1 -1
  16. package/src/commands/sites/sites-create.mjs +1 -1
  17. package/src/functions-templates/javascript/google-analytics/package.json +1 -1
  18. package/src/functions-templates/typescript/scheduled-function/package.json +1 -1
  19. package/src/lib/edge-functions/deploy.mjs +11 -4
  20. package/src/lib/edge-functions/internal.mjs +5 -3
  21. package/src/lib/edge-functions/proxy.mjs +29 -5
  22. package/src/lib/functions/netlify-function.mjs +26 -1
  23. package/src/lib/functions/registry.mjs +14 -26
  24. package/src/lib/functions/runtimes/js/builders/zisi.mjs +20 -3
  25. package/src/lib/functions/runtimes/js/worker.mjs +1 -1
  26. package/src/lib/functions/server.mjs +3 -2
  27. package/src/lib/spinner.mjs +1 -1
  28. package/src/recipes/vscode/index.mjs +24 -6
  29. package/src/utils/build-info.mjs +100 -0
  30. package/src/utils/command-helpers.mjs +16 -7
  31. package/src/utils/deploy/deploy-site.mjs +4 -4
  32. package/src/utils/deploy/hash-fns.mjs +2 -2
  33. package/src/utils/detect-server-settings.mjs +133 -245
  34. package/src/utils/framework-server.mjs +6 -5
  35. package/src/utils/functions/functions.mjs +8 -5
  36. package/src/utils/get-repo-data.mjs +5 -6
  37. package/src/utils/init/config-github.mjs +2 -2
  38. package/src/utils/init/config-manual.mjs +24 -7
  39. package/src/utils/init/utils.mjs +68 -68
  40. package/src/utils/proxy-server.mjs +7 -4
  41. package/src/utils/proxy.mjs +4 -3
  42. package/src/utils/read-repo-url.mjs +4 -0
  43. package/src/utils/run-build.mjs +58 -32
  44. package/src/utils/shell.mjs +24 -7
  45. package/src/utils/state-config.mjs +5 -1
  46. package/src/utils/static-server.mjs +4 -0
  47. package/src/utils/init/frameworks.mjs +0 -23
@@ -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,8 +25,8 @@ 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)')
29
- log('See https://docs.netlify.com/configure-builds/file-based-configuration/ for more information')
28
+ log(`Please verify that 'functions.directory' is set in your Netlify configuration file ${relConfigFilePath}`)
29
+ log('Refer to https://docs.netlify.com/configure-builds/file-based-configuration/ for more information')
30
30
  exit(1)
31
31
  }
32
32
 
@@ -39,6 +39,7 @@ const functionsServe = async (options, command) => {
39
39
  await startFunctionsServer({
40
40
  config,
41
41
  debug: options.debug,
42
+ command,
42
43
  api,
43
44
  settings: { functions: functionsDir, functionsPort },
44
45
  site,
@@ -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
  }
@@ -11,11 +11,11 @@ import { track } from '../../utils/telemetry/index.mjs'
11
11
 
12
12
  /**
13
13
  *
14
- * @param {import('../base-command.mjs').NetlifyOptions} netlify
14
+ * @param {import('../base-command.mjs').default} command
15
15
  * @param {import('commander').OptionValues} options
16
16
  */
17
- const linkPrompt = async (netlify, options) => {
18
- const { api, state } = netlify
17
+ const linkPrompt = async (command, options) => {
18
+ const { api, state } = command.netlify
19
19
 
20
20
  const SITE_NAME_PROMPT = 'Search by full or partial site name'
21
21
  const SITE_LIST_PROMPT = 'Choose from a list of your recently updated sites'
@@ -24,7 +24,7 @@ const linkPrompt = async (netlify, options) => {
24
24
  let GIT_REMOTE_PROMPT = 'Use the current git remote origin URL'
25
25
  let site
26
26
  // Get git remote data if exists
27
- const repoData = await getRepoData({ remoteName: options.gitRemoteName })
27
+ const repoData = await getRepoData({ workingDir: command.workingDir, remoteName: options.gitRemoteName })
28
28
 
29
29
  let linkChoices = [SITE_NAME_PROMPT, SITE_LIST_PROMPT, SITE_ID_PROMPT]
30
30
 
@@ -326,7 +326,7 @@ export const link = async (options, command) => {
326
326
  kind: 'byName',
327
327
  })
328
328
  } else {
329
- siteData = await linkPrompt(command.netlify, options)
329
+ siteData = await linkPrompt(command, options)
330
330
  }
331
331
  return siteData
332
332
  }
@@ -38,6 +38,7 @@ const serve = async (options, command) => {
38
38
  const devConfig = {
39
39
  ...(config.functionsDirectory && { functions: config.functionsDirectory }),
40
40
  ...(config.build.publish && { publish: config.build.publish }),
41
+
41
42
  ...config.dev,
42
43
  ...options,
43
44
  // Override the `framework` value so that we start a static server and not
@@ -69,10 +70,9 @@ const serve = async (options, command) => {
69
70
  // Netlify Build are loaded.
70
71
  await getInternalFunctionsDir({ base: site.root, ensureExists: true })
71
72
 
72
- /** @type {Partial<import('../../utils/types').ServerSettings>} */
73
- let settings = {}
73
+ let settings = /** @type {import('../../utils/types.js').ServerSettings} */ ({})
74
74
  try {
75
- settings = await detectServerSettings(devConfig, options, site.root)
75
+ settings = await detectServerSettings(devConfig, options, command)
76
76
 
77
77
  cachedConfig.config = getConfigWithPlugins(cachedConfig.config, settings)
78
78
  } catch (error_) {
@@ -87,7 +87,11 @@ const serve = async (options, command) => {
87
87
  `${NETLIFYDEVWARN} Changes will not be hot-reloaded, so if you need to rebuild your site you must exit and run 'netlify serve' again`,
88
88
  )
89
89
 
90
- const { configPath: configPathOverride } = await runBuildTimeline({ cachedConfig, options, settings, site })
90
+ const { configPath: configPathOverride } = await runBuildTimeline({
91
+ command,
92
+ settings,
93
+ options,
94
+ })
91
95
 
92
96
  await startFunctionsServer({
93
97
  api,
@@ -117,8 +121,7 @@ const serve = async (options, command) => {
117
121
 
118
122
  // TODO: We should consolidate this with the existing config watcher.
119
123
  const getUpdatedConfig = async () => {
120
- const cwd = options.cwd || process.cwd()
121
- const { config: newConfig } = await command.getConfig({ cwd, offline: true, state })
124
+ const { config: newConfig } = await command.getConfig({ cwd: command.workingDir, offline: true, state })
122
125
  const normalizedNewConfig = normalizeConfig(newConfig)
123
126
 
124
127
  return normalizedNewConfig
@@ -135,6 +138,7 @@ const serve = async (options, command) => {
135
138
  getUpdatedConfig,
136
139
  inspectSettings,
137
140
  offline: options.offline,
141
+ projectDir: command.workingDir,
138
142
  settings,
139
143
  site,
140
144
  siteInfo,
@@ -197,7 +197,7 @@ const sitesCreateTemplate = async (repository, options, command) => {
197
197
 
198
198
  if (options.withCi) {
199
199
  log('Configuring CI')
200
- const repoData = await getRepoData()
200
+ const repoData = await getRepoData({ workingDir: command.workingDir })
201
201
  await configureRepo({ command, siteId: site.id, repoData, manual: options.manual })
202
202
  }
203
203
 
@@ -102,7 +102,7 @@ export const sitesCreate = async (options, command) => {
102
102
 
103
103
  if (options.withCi) {
104
104
  log('Configuring CI')
105
- const repoData = await getRepoData()
105
+ const repoData = await getRepoData({ workingDir: command.workingDir })
106
106
  await configureRepo({ command, siteId: site.id, repoData, manual: options.manual })
107
107
  }
108
108
 
@@ -7,7 +7,7 @@
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
8
8
  },
9
9
  "engines": {
10
- "node": "^14.18.0 || >=16.0.0"
10
+ "node": ">=16.16.0"
11
11
  },
12
12
  "keywords": [
13
13
  "netlify",
@@ -16,7 +16,7 @@
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
18
  "@netlify/functions": "^1.6.0",
19
- "@types/node": "^14.18.9",
19
+ "@types/node": "^18.0.0",
20
20
  "typescript": "^4.5.5"
21
21
  }
22
22
  }
@@ -8,8 +8,12 @@ import { EDGE_FUNCTIONS_FOLDER, PUBLIC_URL_PATH } from './consts.mjs'
8
8
 
9
9
  const distPath = getPathInProject([EDGE_FUNCTIONS_FOLDER])
10
10
 
11
- export const deployFileNormalizer = (rootDir, file) => {
12
- const absoluteDistPath = join(rootDir, distPath)
11
+ /**
12
+ * @param {string} workingDir
13
+ * @param {*} file
14
+ */
15
+ export const deployFileNormalizer = (workingDir, file) => {
16
+ const absoluteDistPath = join(workingDir, distPath)
13
17
  const isEdgeFunction = file.root === absoluteDistPath
14
18
  const normalizedPath = isEdgeFunction ? `${PUBLIC_URL_PATH}/${file.normalizedPath}` : file.normalizedPath
15
19
 
@@ -19,9 +23,12 @@ export const deployFileNormalizer = (rootDir, file) => {
19
23
  }
20
24
  }
21
25
 
22
- export const getDistPathIfExists = async ({ rootDir }) => {
26
+ /**
27
+ * @param {string} workingDir
28
+ */
29
+ export const getDistPathIfExists = async (workingDir) => {
23
30
  try {
24
- const absoluteDistPath = join(rootDir, distPath)
31
+ const absoluteDistPath = join(workingDir, distPath)
25
32
  const stats = await stat(absoluteDistPath)
26
33
 
27
34
  if (!stats.isDirectory()) {
@@ -1,14 +1,16 @@
1
1
  // @ts-check
2
2
  import { readFile, stat } from 'fs/promises'
3
3
  import { dirname, join, resolve } from 'path'
4
- import { cwd } from 'process'
5
4
 
6
5
  import { getPathInProject } from '../settings.mjs'
7
6
 
8
7
  import { INTERNAL_EDGE_FUNCTIONS_FOLDER } from './consts.mjs'
9
8
 
10
- export const getInternalFunctions = async () => {
11
- const path = join(cwd(), getPathInProject([INTERNAL_EDGE_FUNCTIONS_FOLDER]))
9
+ /**
10
+ * @param {string} workingDir
11
+ */
12
+ export const getInternalFunctions = async (workingDir) => {
13
+ const path = join(workingDir, getPathInProject([INTERNAL_EDGE_FUNCTIONS_FOLDER]))
12
14
 
13
15
  try {
14
16
  const stats = await stat(path)
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  import { Buffer } from 'buffer'
3
- import { relative } from 'path'
4
- import { cwd, env } from 'process'
3
+ import { join, relative } from 'path'
4
+ import { env } from 'process'
5
5
 
6
6
  // eslint-disable-next-line import/no-namespace
7
7
  import * as bundler from '@netlify/edge-bundler'
@@ -62,6 +62,26 @@ export const createAccountInfoHeader = (accountInfo = {}) => {
62
62
  return Buffer.from(accountString).toString('base64')
63
63
  }
64
64
 
65
+ /**
66
+ *
67
+ * @param {object} config
68
+ * @param {*} config.accountId
69
+ * @param {*} config.config
70
+ * @param {*} config.configPath
71
+ * @param {*} config.debug
72
+ * @param {*} config.env
73
+ * @param {*} config.geoCountry
74
+ * @param {*} config.geolocationMode
75
+ * @param {*} config.getUpdatedConfig
76
+ * @param {*} config.inspectSettings
77
+ * @param {*} config.mainPort
78
+ * @param {boolean=} config.offline
79
+ * @param {*} config.passthroughPort
80
+ * @param {*} config.projectDir
81
+ * @param {*} config.siteInfo
82
+ * @param {*} config.state
83
+ * @returns
84
+ */
65
85
  export const initializeProxy = async ({
66
86
  accountId,
67
87
  config,
@@ -79,7 +99,11 @@ export const initializeProxy = async ({
79
99
  siteInfo,
80
100
  state,
81
101
  }) => {
82
- const { functions: internalFunctions, importMap, path: internalFunctionsPath } = await getInternalFunctions()
102
+ const {
103
+ functions: internalFunctions,
104
+ importMap,
105
+ path: internalFunctionsPath,
106
+ } = await getInternalFunctions(projectDir)
83
107
  const userFunctionsPath = config.build.edge_functions
84
108
  const isolatePort = await getAvailablePort()
85
109
 
@@ -133,7 +157,7 @@ export const initializeProxy = async ({
133
157
  )} matches declaration for edge function ${chalk.yellow(
134
158
  functionName,
135
159
  )}, but there's no matching function file in ${chalk.yellow(
136
- relative(cwd(), userFunctionsPath),
160
+ relative(projectDir, userFunctionsPath),
137
161
  )}. Please visit ${chalk.blue('https://ntl.fyi/edge-create')} for more information.`,
138
162
  )
139
163
  })
@@ -193,7 +217,7 @@ const prepareServer = async ({
193
217
  ...getDownloadUpdateFunctions(),
194
218
  bootstrapURL: getBootstrapURL(),
195
219
  debug: env.NETLIFY_DENO_DEBUG === 'true',
196
- distImportMapPath,
220
+ distImportMapPath: join(projectDir, distImportMapPath),
197
221
  formatExportTypeError: (name) =>
198
222
  `${NETLIFYDEVERR} ${chalk.red('Failed')} to load Edge Function ${chalk.yellow(
199
223
  name,
@@ -1,9 +1,14 @@
1
1
  // @ts-check
2
+ import { version as nodeVersion } from 'process'
3
+
2
4
  import CronParser from 'cron-parser'
5
+ import semver from 'semver'
3
6
 
4
7
  import { error as errorExit } from '../../utils/command-helpers.mjs'
5
8
  import { BACKGROUND } from '../../utils/functions/get-functions.mjs'
6
9
 
10
+ const V2_MIN_NODE_VERSION = '18.0.0'
11
+
7
12
  // Returns a new set with all elements of `setA` that don't exist in `setB`.
8
13
  const difference = (setA, setB) => new Set([...setA].filter((item) => !setB.has(item)))
9
14
 
@@ -27,12 +32,13 @@ export default class NetlifyFunction {
27
32
  timeoutBackground,
28
33
  timeoutSynchronous,
29
34
  }) {
35
+ this.buildError = null
30
36
  this.config = config
31
37
  this.directory = directory
32
38
  this.errorExit = errorExit
33
39
  this.mainFile = mainFile
34
40
  this.name = name
35
- this.displayName = displayName
41
+ this.displayName = displayName ?? name
36
42
  this.projectRoot = projectRoot
37
43
  this.runtime = runtime
38
44
  this.timeoutBackground = timeoutBackground
@@ -63,6 +69,10 @@ export default class NetlifyFunction {
63
69
  return Boolean(this.schedule)
64
70
  }
65
71
 
72
+ isSupported() {
73
+ return !(this.buildData?.runtimeAPIVersion === 2 && semver.lt(nodeVersion, V2_MIN_NODE_VERSION))
74
+ }
75
+
66
76
  async getNextRun() {
67
77
  if (!(await this.isScheduled())) {
68
78
  return null
@@ -93,11 +103,22 @@ export default class NetlifyFunction {
93
103
  const srcFilesDiff = this.getSrcFilesDiff(srcFilesSet)
94
104
 
95
105
  this.buildData = buildData
106
+ this.buildError = null
96
107
  this.srcFiles = srcFilesSet
97
108
  this.schedule = schedule || this.schedule
98
109
 
110
+ if (!this.isSupported()) {
111
+ throw new Error(
112
+ `Function requires Node.js version ${V2_MIN_NODE_VERSION} or above, but ${nodeVersion.slice(
113
+ 1,
114
+ )} is installed. Refer to https://ntl.fyi/functions-node18 for information on how to update.`,
115
+ )
116
+ }
117
+
99
118
  return { includedFiles, srcFilesDiff }
100
119
  } catch (error) {
120
+ this.buildError = error
121
+
101
122
  return { error }
102
123
  }
103
124
  }
@@ -118,6 +139,10 @@ export default class NetlifyFunction {
118
139
  async invoke(event, context) {
119
140
  await this.buildQueue
120
141
 
142
+ if (this.buildError) {
143
+ return { result: null, error: { errorMessage: this.buildError.message } }
144
+ }
145
+
121
146
  const timeout = this.isBackground ? this.timeoutBackground : this.timeoutSynchronous
122
147
 
123
148
  try {
@@ -6,15 +6,7 @@ import { env } from 'process'
6
6
  import { listFunctions } from '@netlify/zip-it-and-ship-it'
7
7
  import extractZip from 'extract-zip'
8
8
 
9
- import {
10
- chalk,
11
- getTerminalLink,
12
- log,
13
- NETLIFYDEVERR,
14
- NETLIFYDEVLOG,
15
- warn,
16
- watchDebounced,
17
- } from '../../utils/command-helpers.mjs'
9
+ import { chalk, log, NETLIFYDEVERR, NETLIFYDEVLOG, warn, watchDebounced } from '../../utils/command-helpers.mjs'
18
10
  import { INTERNAL_FUNCTIONS_FOLDER, SERVE_FUNCTIONS_FOLDER } from '../../utils/functions/functions.mjs'
19
11
  import { BACKGROUND_FUNCTIONS_WARNING } from '../log.mjs'
20
12
  import { getPathInProject } from '../settings.mjs'
@@ -70,21 +62,23 @@ export class FunctionsRegistry {
70
62
  )
71
63
  }
72
64
 
73
- async buildFunctionAndWatchFiles(func, { verbose = false } = {}) {
74
- if (verbose) {
75
- log(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} function ${chalk.yellow(func.name)}...`)
65
+ async buildFunctionAndWatchFiles(func, firstLoad = false) {
66
+ if (!firstLoad) {
67
+ log(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} function ${chalk.yellow(func.displayName)}...`)
76
68
  }
77
69
 
78
- const { error_, includedFiles, srcFilesDiff } = await func.build({ cache: this.buildCache })
70
+ const { error: buildError, includedFiles, srcFilesDiff } = await func.build({ cache: this.buildCache })
79
71
 
80
- if (error_) {
72
+ if (buildError) {
81
73
  log(
82
- `${NETLIFYDEVERR} ${chalk.red('Failed')} reloading function ${chalk.yellow(func.name)} with error:\n${
83
- error_.message
74
+ `${NETLIFYDEVERR} ${chalk.red('Failed to load')} function ${chalk.yellow(func.displayName)}: ${
75
+ buildError.message
84
76
  }`,
85
77
  )
86
- } else if (verbose) {
87
- log(`${NETLIFYDEVLOG} ${chalk.green('Reloaded')} function ${chalk.yellow(func.name)}`)
78
+ } else {
79
+ const verb = firstLoad ? 'Loaded' : 'Reloaded'
80
+
81
+ log(`${NETLIFYDEVLOG} ${chalk.green(verb)} function ${chalk.yellow(func.displayName)}`)
88
82
  }
89
83
 
90
84
  // If the build hasn't resulted in any files being added or removed, there
@@ -116,7 +110,7 @@ export class FunctionsRegistry {
116
110
 
117
111
  const newWatcher = await watchDebounced(filesToWatch, {
118
112
  onChange: () => {
119
- this.buildFunctionAndWatchFiles(func, { verbose: true })
113
+ this.buildFunctionAndWatchFiles(func, false)
120
114
  },
121
115
  })
122
116
 
@@ -163,14 +157,8 @@ export class FunctionsRegistry {
163
157
  }
164
158
 
165
159
  this.functions.set(name, func)
166
- this.buildFunctionAndWatchFiles(func)
167
160
 
168
- log(
169
- `${NETLIFYDEVLOG} ${chalk.green('Loaded')} function ${getTerminalLink(
170
- chalk.yellow(func.displayName || name),
171
- func.url,
172
- )}.`,
173
- )
161
+ this.buildFunctionAndWatchFiles(func, true)
174
162
  }
175
163
 
176
164
  // This function is here so we can mock it in tests
@@ -105,8 +105,15 @@ const clearFunctionsCache = (functionsPath) => {
105
105
  .forEach(decache)
106
106
  }
107
107
 
108
- const getTargetDirectory = async ({ errorExit }) => {
109
- const targetDirectory = path.resolve(getPathInProject([SERVE_FUNCTIONS_FOLDER]))
108
+ /**
109
+ *
110
+ * @param {object} config
111
+ * @param {string} config.projectRoot
112
+ * @param {(msg: string) => void} config.errorExit
113
+ * @returns
114
+ */
115
+ const getTargetDirectory = async ({ errorExit, projectRoot }) => {
116
+ const targetDirectory = path.resolve(projectRoot, getPathInProject([SERVE_FUNCTIONS_FOLDER]))
110
117
 
111
118
  try {
112
119
  await mkdir(targetDirectory, { recursive: true })
@@ -120,6 +127,16 @@ const getTargetDirectory = async ({ errorExit }) => {
120
127
  const netlifyConfigToZisiConfig = ({ config, projectRoot }) =>
121
128
  addFunctionsConfigDefaults(normalizeFunctionsConfig({ functionsConfig: config.functions, projectRoot }))
122
129
 
130
+ /**
131
+ *
132
+ * @param {object} param0
133
+ * @param {*} param0.config
134
+ * @param {*} param0.directory
135
+ * @param {*} param0.errorExit
136
+ * @param {*} param0.func
137
+ * @param {*} param0.metadata
138
+ * @param {string} param0.projectRoot
139
+ */
123
140
  export default async function handler({ config, directory, errorExit, func, metadata, projectRoot }) {
124
141
  const functionsConfig = netlifyConfigToZisiConfig({ config, projectRoot })
125
142
 
@@ -153,7 +170,7 @@ export default async function handler({ config, directory, errorExit, func, meta
153
170
  // Enable source map support.
154
171
  sourceMapSupport.install()
155
172
 
156
- const targetDirectory = await getTargetDirectory({ errorExit })
173
+ const targetDirectory = await getTargetDirectory({ projectRoot, errorExit })
157
174
 
158
175
  return {
159
176
  build: ({ cache = {} }) =>
@@ -11,7 +11,7 @@ if (isMainThread) {
11
11
 
12
12
  sourceMapSupport.install()
13
13
 
14
- lambdaLocal.getLogger().level = 'warn'
14
+ lambdaLocal.getLogger().level = 'alert'
15
15
 
16
16
  const { clientContext, entryFilePath, event, timeoutMs } = workerData
17
17
 
@@ -220,7 +220,7 @@ const getFunctionsServer = (options) => {
220
220
  }
221
221
 
222
222
  export const startFunctionsServer = async (options) => {
223
- const { capabilities, config, debug, loadDistFunctions, settings, site, siteUrl, timeouts } = options
223
+ const { capabilities, command, config, debug, loadDistFunctions, settings, site, siteUrl, timeouts } = options
224
224
  const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root })
225
225
  const functionsDirectories = []
226
226
 
@@ -250,7 +250,8 @@ export const startFunctionsServer = async (options) => {
250
250
  config,
251
251
  debug,
252
252
  isConnected: Boolean(siteUrl),
253
- projectRoot: site.root,
253
+ // functions always need to be inside the packagePath if set inside a monorepo
254
+ projectRoot: command.workingDir,
254
255
  settings,
255
256
  timeouts,
256
257
  })
@@ -17,7 +17,7 @@ export const startSpinner = ({ text }) =>
17
17
  * Stops the spinner with the following text
18
18
  * @param {object} config
19
19
  * @param {ora.Ora} config.spinner
20
- * @param {object} [config.error]
20
+ * @param {boolean} [config.error]
21
21
  * @param {string} [config.text]
22
22
  * @returns {void}
23
23
  */
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  import { join } from 'path'
2
3
 
3
4
  import { DenoBridge } from '@netlify/edge-bundler'
@@ -27,15 +28,24 @@ const getPrompt = ({ fileExists, path }) => {
27
28
  const getEdgeFunctionsPath = ({ config, repositoryRoot }) =>
28
29
  config.build.edge_functions || join(repositoryRoot, 'netlify', 'edge-functions')
29
30
 
31
+ /**
32
+ * @param {string} repositoryRoot
33
+ */
30
34
  const getSettingsPath = (repositoryRoot) => join(repositoryRoot, '.vscode', 'settings.json')
31
35
 
32
- const hasDenoVSCodeExt = async () => {
33
- const { stdout: extensions } = await execa('code', ['--list-extensions'], { stderr: 'inherit' })
36
+ /**
37
+ * @param {string} repositoryRoot
38
+ */
39
+ const hasDenoVSCodeExt = async (repositoryRoot) => {
40
+ const { stdout: extensions } = await execa('code', ['--list-extensions'], { stderr: 'inherit', cwd: repositoryRoot })
34
41
  return extensions.split('\n').includes('denoland.vscode-deno')
35
42
  }
36
43
 
37
- const getDenoVSCodeExt = async () => {
38
- await execa('code', ['--install-extension', 'denoland.vscode-deno'], { stdio: 'inherit' })
44
+ /**
45
+ * @param {string} repositoryRoot
46
+ */
47
+ const getDenoVSCodeExt = async (repositoryRoot) => {
48
+ await execa('code', ['--install-extension', 'denoland.vscode-deno'], { stdio: 'inherit', cwd: repositoryRoot })
39
49
  }
40
50
 
41
51
  const getDenoExtPrompt = () => {
@@ -49,6 +59,12 @@ const getDenoExtPrompt = () => {
49
59
  })
50
60
  }
51
61
 
62
+ /**
63
+ * @param {object} params
64
+ * @param {*} params.config
65
+ * @param {string} params.repositoryRoot
66
+ * @returns
67
+ */
52
68
  export const run = async ({ config, repositoryRoot }) => {
53
69
  const deno = new DenoBridge({
54
70
  onBeforeDownload: () =>
@@ -66,9 +82,11 @@ export const run = async ({ config, repositoryRoot }) => {
66
82
  }
67
83
 
68
84
  try {
69
- if (!(await hasDenoVSCodeExt())) {
85
+ if (!(await hasDenoVSCodeExt(repositoryRoot))) {
70
86
  const { confirm: denoExtConfirm } = await getDenoExtPrompt()
71
- if (denoExtConfirm) getDenoVSCodeExt()
87
+ if (denoExtConfirm) {
88
+ getDenoVSCodeExt(repositoryRoot)
89
+ }
72
90
  }
73
91
  } catch {
74
92
  log(