netlify-cli 13.2.2 → 14.0.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.
@@ -8,15 +8,75 @@ import { NETLIFYDEVERR, NETLIFYDEVLOG, chalk, log, warn, watchDebounced } from '
8
8
  /** @typedef {import('@netlify/edge-bundler').FunctionConfig} FunctionConfig */
9
9
  /** @typedef {Awaited<ReturnType<typeof import('@netlify/edge-bundler').serve>>} RunIsolate */
10
10
 
11
+ const featureFlags = { edge_functions_correct_order: true }
12
+
11
13
  export class EdgeFunctionsRegistry {
14
+ /** @type {import('@netlify/edge-bundler')} */
15
+ #bundler
16
+
17
+ /** @type {string} */
18
+ #configPath
19
+
20
+ /** @type {string[]} */
21
+ #directories
22
+
23
+ /** @type {string[]} */
24
+ #internalDirectories
25
+
26
+ /** @type {() => Promise<object>} */
27
+ #getUpdatedConfig
28
+
29
+ /** @type {RunIsolate} */
30
+ #runIsolate
31
+
32
+ /** @type {Error | null} */
33
+ #buildError = null
34
+
35
+ /** @type {Declaration[]} */
36
+ #declarationsFromDeployConfig
37
+
38
+ /** @type {Record<string, FunctionConfig>} */
39
+ #userFunctionConfigs = {}
40
+
41
+ /** @type {Record<string, FunctionConfig>} */
42
+ #internalFunctionConfigs = {}
43
+
44
+ /** @type {Declaration[]} */
45
+ #declarationsFromTOML
46
+
47
+ /** @type {Record<string, string>} */
48
+ #env
49
+
50
+ /** @type {import('chokidar').FSWatcher} */
51
+ #configWatcher
52
+
53
+ /** @type {Map<string, import('chokidar').FSWatcher>} */
54
+ #directoryWatchers = new Map()
55
+
56
+ /** @type {Map<string, string[]>} */
57
+ #dependencyPaths = new Map()
58
+
59
+ /** @type {Map<string, string>} */
60
+ #functionPaths = new Map()
61
+
62
+ /** @type {EdgeFunction[]} */
63
+ #userFunctions = []
64
+
65
+ /** @type {EdgeFunction[]} */
66
+ #internalFunctions = []
67
+
68
+ /** @type {Promise<void>} */
69
+ #initialScan
70
+
12
71
  /**
13
72
  * @param {Object} opts
14
73
  * @param {import('@netlify/edge-bundler')} opts.bundler
15
74
  * @param {object} opts.config
16
75
  * @param {string} opts.configPath
17
76
  * @param {string[]} opts.directories
18
- * @param {Record<string, string>} opts.env
77
+ * @param {Record<string, { sources: string[], value: string}>} opts.env
19
78
  * @param {() => Promise<object>} opts.getUpdatedConfig
79
+ * @param {string[]} opts.internalDirectories
20
80
  * @param {Declaration[]} opts.internalFunctions
21
81
  * @param {string} opts.projectDir
22
82
  * @param {RunIsolate} opts.runIsolate
@@ -28,91 +88,71 @@ export class EdgeFunctionsRegistry {
28
88
  directories,
29
89
  env,
30
90
  getUpdatedConfig,
91
+ internalDirectories,
31
92
  internalFunctions,
32
93
  projectDir,
33
94
  runIsolate,
34
95
  }) {
35
- this.bundler = bundler
36
-
37
- /**
38
- * @type {string}
39
- */
40
- this.configPath = configPath
41
-
42
- /**
43
- * @type {string[]}
44
- */
45
- this.directories = directories
46
-
47
- /**
48
- * @type {() => Promise<object>}
49
- */
50
- this.getUpdatedConfig = getUpdatedConfig
51
-
52
- /**
53
- * @type {RunIsolate}
54
- */
55
- this.runIsolate = runIsolate
56
-
57
- /**
58
- * @type {Error | null}
59
- */
60
- this.buildError = null
61
-
62
- /**
63
- * @type {Declaration[]}
64
- */
65
- this.declarationsFromDeployConfig = internalFunctions
66
-
67
- /**
68
- * @type {Record<string, FunctionConfig>}
69
- */
70
- this.declarationsFromSource = {}
71
-
72
- /**
73
- * @type {Declaration[]}
74
- */
75
- this.declarationsFromTOML = EdgeFunctionsRegistry.getDeclarationsFromTOML(config)
76
-
77
- /**
78
- * @type {Record<string, string>}
79
- */
80
- this.env = EdgeFunctionsRegistry.getEnvironmentVariables(env)
81
-
82
- /**
83
- * @type {Map<string, import('chokidar').FSWatcher>}
84
- */
85
- this.directoryWatchers = new Map()
86
-
87
- /**
88
- * @type {Map<string, string[]>}
89
- */
90
- this.dependencyPaths = new Map()
91
-
92
- /**
93
- * @type {Map<string, string>}
94
- */
95
- this.functionPaths = new Map()
96
-
97
- /**
98
- * @type {EdgeFunction[]}
99
- */
100
- this.functions = []
101
-
102
- /**
103
- * @type {Promise<EdgeFunction[]>}
104
- */
105
- this.initialScan = this.scan(directories)
106
-
107
- this.setupWatchers({ projectDir })
96
+ this.#bundler = bundler
97
+ this.#configPath = configPath
98
+ this.#directories = directories
99
+ this.#internalDirectories = internalDirectories
100
+ this.#getUpdatedConfig = getUpdatedConfig
101
+ this.#runIsolate = runIsolate
102
+
103
+ this.#declarationsFromDeployConfig = internalFunctions
104
+ this.#declarationsFromTOML = EdgeFunctionsRegistry.#getDeclarationsFromTOML(config)
105
+ this.#env = EdgeFunctionsRegistry.#getEnvironmentVariables(env)
106
+
107
+ this.#buildError = null
108
+ this.#userFunctionConfigs = {}
109
+ this.#internalFunctionConfigs = {}
110
+ this.#directoryWatchers = new Map()
111
+ this.#dependencyPaths = new Map()
112
+ this.#functionPaths = new Map()
113
+ this.#userFunctions = []
114
+ this.#internalFunctions = []
115
+
116
+ this.#initialScan = this.#doInitialScan()
117
+
118
+ this.#setupWatchers(projectDir)
119
+ }
120
+
121
+ /**
122
+ * @returns {Promise<void>}
123
+ */
124
+ async #doInitialScan() {
125
+ const [internalFunctions, userFunctions] = await Promise.all([
126
+ this.#scanForFunctions(this.#internalDirectories),
127
+ this.#scanForFunctions(this.#directories),
128
+ ])
129
+ this.#internalFunctions = internalFunctions.all
130
+ this.#userFunctions = userFunctions.all
131
+
132
+ this.#functions.forEach((func) => {
133
+ this.#logAddedFunction(func)
134
+ })
135
+
136
+ try {
137
+ await this.#build()
138
+ } catch {
139
+ // no-op
140
+ }
108
141
  }
109
142
 
110
143
  /**
111
- * @param {EdgeFunction[]} functions
144
+ * @return {EdgeFunction[]}
112
145
  */
113
- async build(functions) {
146
+ get #functions() {
147
+ return [...this.#internalFunctions, ...this.#userFunctions]
148
+ }
149
+
150
+ /**
151
+ * @return {Promise<void>}
152
+ */
153
+ async #build() {
114
154
  try {
115
- const { functionsConfig, graph, success } = await this.runIsolate(functions, this.env, {
155
+ const { functionsConfig, graph, success } = await this.#runIsolate(this.#functions, this.#env, {
116
156
  getFunctionsConfig: true,
117
157
  })
118
158
 
@@ -120,72 +160,89 @@ export class EdgeFunctionsRegistry {
120
160
  throw new Error('Build error')
121
161
  }
122
162
 
123
- this.buildError = null
124
- this.declarationsFromSource = functions.reduce(
125
- (acc, func, index) => ({ ...acc, [func.name]: functionsConfig[index] }),
163
+ this.#buildError = null
164
+
165
+ // We use one index to loop over both internal and user function, because we know that this.#functions has internalFunctions first.
166
+ // functionsConfig therefore contains first all internal functionConfigs and then user functionConfigs
167
+ let index = 0
168
+
169
+ this.#internalFunctionConfigs = this.#internalFunctions.reduce(
170
+ // eslint-disable-next-line no-plusplus
171
+ (acc, func) => ({ ...acc, [func.name]: functionsConfig[index++] }),
172
+ {},
173
+ )
174
+
175
+ this.#userFunctionConfigs = this.#userFunctions.reduce(
176
+ // eslint-disable-next-line no-plusplus
177
+ (acc, func) => ({ ...acc, [func.name]: functionsConfig[index++] }),
126
178
  {},
127
179
  )
128
180
 
129
- this.processGraph(graph)
181
+ this.#processGraph(graph)
130
182
  } catch (error) {
131
- this.buildError = error
183
+ this.#buildError = error
132
184
 
133
185
  throw error
134
186
  }
135
187
  }
136
188
 
137
- async checkForAddedOrDeletedFunctions() {
138
- const functionsFound = await this.bundler.find(this.directories)
139
- const newFunctions = functionsFound.filter((func) => {
140
- const functionExists = this.functions.some(
141
- (existingFunc) => func.name === existingFunc.name && func.path === existingFunc.path,
142
- )
143
-
144
- return !functionExists
145
- })
146
- const deletedFunctions = this.functions.filter((existingFunc) => {
147
- const functionExists = functionsFound.some(
148
- (func) => func.name === existingFunc.name && func.path === existingFunc.path,
149
- )
189
+ /**
190
+ * @returns {Promise<void>}
191
+ */
192
+ async #checkForAddedOrDeletedFunctions() {
193
+ const [internalFunctions, userFunctions] = await Promise.all([
194
+ this.#scanForFunctions(this.#internalDirectories),
195
+ this.#scanForFunctions(this.#directories),
196
+ ])
150
197
 
151
- return !functionExists
152
- })
198
+ this.#internalFunctions = internalFunctions.all
199
+ this.#userFunctions = userFunctions.all
153
200
 
154
- this.functions = functionsFound
201
+ const newFunctions = [...internalFunctions.new, ...userFunctions.new]
202
+ const deletedFunctions = [...internalFunctions.deleted, ...userFunctions.deleted]
155
203
 
156
204
  if (newFunctions.length === 0 && deletedFunctions.length === 0) {
157
205
  return
158
206
  }
159
207
 
160
208
  try {
161
- await this.build(functionsFound)
209
+ await this.#build()
162
210
 
163
211
  deletedFunctions.forEach((func) => {
164
- EdgeFunctionsRegistry.logDeletedFunction(func, this.findDisplayName(func.name))
212
+ this.#logDeletedFunction(func)
165
213
  })
166
214
 
167
215
  newFunctions.forEach((func) => {
168
- EdgeFunctionsRegistry.logAddedFunction(func, this.findDisplayName(func.name))
216
+ this.#logAddedFunction(func)
169
217
  })
170
218
  } catch {
171
219
  // no-op
172
220
  }
173
221
  }
174
222
 
175
- static getDeclarationsFromTOML(config) {
223
+ /**
224
+ * @param {any} config
225
+ * @returns {Declaration[]}
226
+ */
227
+ static #getDeclarationsFromTOML(config) {
176
228
  const { edge_functions: edgeFunctions = [] } = config
177
229
 
178
230
  return edgeFunctions
179
231
  }
180
232
 
181
- static getEnvironmentVariables(envConfig) {
233
+ /**
234
+ * @param {Record<string, { sources:string[], value:string }>} envConfig
235
+ * @returns {Record<string, string>}
236
+ */
237
+ static #getEnvironmentVariables(envConfig) {
182
238
  const env = Object.create(null)
183
239
  Object.entries(envConfig).forEach(([key, variable]) => {
184
240
  if (
185
241
  variable.sources.includes('ui') ||
186
242
  variable.sources.includes('account') ||
187
243
  variable.sources.includes('addons') ||
188
- variable.sources.includes('internal')
244
+ variable.sources.includes('internal') ||
245
+ variable.sources.some((source) => source.startsWith('.env'))
189
246
  ) {
190
247
  env[key] = variable.value
191
248
  }
@@ -196,23 +253,27 @@ export class EdgeFunctionsRegistry {
196
253
  return env
197
254
  }
198
255
 
199
- async handleFileChange(path) {
256
+ /**
257
+ * @param {string} path
258
+ * @returns {Promise<void>}
259
+ */
260
+ async #handleFileChange(path) {
200
261
  const matchingFunctions = new Set(
201
- [this.functionPaths.get(path), ...(this.dependencyPaths.get(path) || [])].filter(Boolean),
262
+ [this.#functionPaths.get(path), ...(this.#dependencyPaths.get(path) || [])].filter(Boolean),
202
263
  )
203
264
 
204
265
  // If the file is not associated with any function, there's no point in
205
266
  // building. However, it might be that the path is in fact associated with
206
267
  // a function but we just haven't registered it due to a build error. So if
207
268
  // there was a build error, let's always build.
208
- if (matchingFunctions.size === 0 && this.buildError === null) {
269
+ if (matchingFunctions.size === 0 && this.#buildError === null) {
209
270
  return
210
271
  }
211
272
 
212
273
  log(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} edge functions...`)
213
274
 
214
275
  try {
215
- await this.build(this.functions)
276
+ await this.#build()
216
277
 
217
278
  const functionNames = [...matchingFunctions]
218
279
 
@@ -220,8 +281,11 @@ export class EdgeFunctionsRegistry {
220
281
  log(`${NETLIFYDEVLOG} ${chalk.green('Reloaded')} edge functions`)
221
282
  } else {
222
283
  functionNames.forEach((functionName) => {
223
- const displayName = this.findDisplayName(functionName)
224
- log(`${NETLIFYDEVLOG} ${chalk.green('Reloaded')} edge function ${chalk.yellow(displayName || functionName)}`)
284
+ log(
285
+ `${NETLIFYDEVLOG} ${chalk.green('Reloaded')} edge function ${chalk.yellow(
286
+ this.#getDisplayName(functionName),
287
+ )}`,
288
+ )
225
289
  })
226
290
  }
227
291
  } catch {
@@ -229,46 +293,44 @@ export class EdgeFunctionsRegistry {
229
293
  }
230
294
  }
231
295
 
232
- initialize() {
233
- this.initialization =
234
- this.initialization ||
235
- // eslint-disable-next-line promise/prefer-await-to-then
236
- this.initialScan.then(async (functions) => {
237
- try {
238
- await this.build(functions)
239
- } catch {
240
- // no-op
241
- }
242
-
243
- return null
244
- })
245
-
246
- return this.initialization
296
+ /**
297
+ * @return {Promise<void>}
298
+ */
299
+ async initialize() {
300
+ return await this.#initialScan
247
301
  }
248
302
 
249
- static logAddedFunction(func, displayName) {
250
- log(`${NETLIFYDEVLOG} ${chalk.green('Loaded')} edge function ${chalk.yellow(displayName || func.name)}`)
303
+ /**
304
+ * @param {EdgeFunction} func
305
+ */
306
+ #logAddedFunction(func) {
307
+ log(`${NETLIFYDEVLOG} ${chalk.green('Loaded')} edge function ${chalk.yellow(this.#getDisplayName(func.name))}`)
251
308
  }
252
309
 
253
- static logDeletedFunction(func, displayName) {
254
- log(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} edge function ${chalk.yellow(displayName || func.name)}`)
310
+ /**
311
+ * @param {EdgeFunction} func
312
+ */
313
+ #logDeletedFunction(func) {
314
+ log(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} edge function ${chalk.yellow(this.#getDisplayName(func.name))}`)
255
315
  }
256
316
 
257
317
  /**
258
318
  * @param {string} urlPath
259
319
  */
260
320
  matchURLPath(urlPath) {
261
- const declarations = this.bundler.mergeDeclarations(
262
- this.declarationsFromTOML,
263
- this.declarationsFromSource,
264
- {},
265
- this.declarationsFromDeployConfig,
321
+ const declarations = this.#bundler.mergeDeclarations(
322
+ this.#declarationsFromTOML,
323
+ this.#userFunctionConfigs,
324
+ this.#internalFunctionConfigs,
325
+ this.#declarationsFromDeployConfig,
326
+ featureFlags,
266
327
  )
267
- const manifest = this.bundler.generateManifest({
328
+ const manifest = this.#bundler.generateManifest({
268
329
  declarations,
269
- userFunctionConfig: this.declarationsFromSource,
270
- internalFunctionConfig: {},
271
- functions: this.functions,
330
+ userFunctionConfig: this.#userFunctionConfigs,
331
+ internalFunctionConfig: this.#internalFunctionConfigs,
332
+ functions: this.#functions,
333
+ featureFlags,
272
334
  })
273
335
  const invocationMetadata = {
274
336
  function_config: manifest.function_config,
@@ -287,22 +349,29 @@ export class EdgeFunctionsRegistry {
287
349
  return !isExcluded
288
350
  })
289
351
  .map((route) => route.function)
290
- const orphanedDeclarations = this.matchURLPathAgainstOrphanedDeclarations(urlPath)
352
+ const orphanedDeclarations = this.#matchURLPathAgainstOrphanedDeclarations(urlPath)
291
353
 
292
354
  return { functionNames, invocationMetadata, orphanedDeclarations }
293
355
  }
294
356
 
295
- matchURLPathAgainstOrphanedDeclarations(urlPath) {
357
+ /**
358
+ *
359
+ * @param {string} urlPath
360
+ * @returns {string[]}
361
+ */
362
+ #matchURLPathAgainstOrphanedDeclarations(urlPath) {
296
363
  // `generateManifest` will only include functions for which there is both a
297
364
  // function file and a config declaration, but we want to catch cases where
298
365
  // a config declaration exists without a matching function file. To do that
299
366
  // we compute a list of functions from the declarations (the `path` doesn't
300
367
  // really matter).
301
- const functions = this.declarationsFromTOML.map((declaration) => ({ name: declaration.function, path: '' }))
302
- const manifest = this.bundler.generateManifest({
303
- declarations: this.declarationsFromTOML,
304
- functionConfig: this.declarationsFromSource,
368
+ const functions = this.#declarationsFromTOML.map((declaration) => ({ name: declaration.function, path: '' }))
369
+ const manifest = this.#bundler.generateManifest({
370
+ declarations: this.#declarationsFromTOML,
371
+ userFunctionConfig: this.#userFunctionConfigs,
372
+ internalFunctionConfig: this.#internalFunctionConfigs,
305
373
  functions,
374
+ featureFlags,
306
375
  })
307
376
 
308
377
  const routes = [...manifest.routes, ...manifest.post_cache_routes].map((route) => ({
@@ -312,7 +381,7 @@ export class EdgeFunctionsRegistry {
312
381
 
313
382
  const functionNames = routes
314
383
  .filter((route) => {
315
- const hasFunctionFile = this.functions.some((func) => func.name === route.function)
384
+ const hasFunctionFile = this.#functions.some((func) => func.name === route.function)
316
385
 
317
386
  if (hasFunctionFile) {
318
387
  return false
@@ -325,18 +394,18 @@ export class EdgeFunctionsRegistry {
325
394
  return functionNames
326
395
  }
327
396
 
328
- processGraph(graph) {
397
+ #processGraph(graph) {
329
398
  if (!graph) {
330
399
  warn('Could not process edge functions dependency graph. Live reload will not be available.')
331
400
 
332
401
  return
333
402
  }
334
403
 
335
- // Creating a Map from `this.functions` that map function paths to function
404
+ // Creating a Map from `this.#functions` that map function paths to function
336
405
  // names. This allows us to match modules against functions in O(1) time as
337
406
  // opposed to O(n).
338
407
  // eslint-disable-next-line unicorn/prefer-spread
339
- const functionPaths = new Map(Array.from(this.functions, (func) => [func.path, func.name]))
408
+ const functionPaths = new Map(Array.from(this.#functions, (func) => [func.path, func.name]))
340
409
 
341
410
  // Mapping file URLs to names of functions that use them as dependencies.
342
411
  const dependencyPaths = new Map()
@@ -372,32 +441,48 @@ export class EdgeFunctionsRegistry {
372
441
  })
373
442
  })
374
443
 
375
- this.dependencyPaths = dependencyPaths
376
- this.functionPaths = functionPaths
444
+ this.#dependencyPaths = dependencyPaths
445
+ this.#functionPaths = functionPaths
377
446
  }
378
447
 
379
- async scan(directories) {
380
- const functions = await this.bundler.find(directories)
448
+ /**
449
+ *
450
+ * @param {string[]} directories
451
+ * @returns {Promise<{all: EdgeFunction[], new: EdgeFunction[], deleted: EdgeFunction[]}>}
452
+ */
453
+ async #scanForFunctions(directories) {
454
+ const functions = await this.#bundler.find(directories)
455
+ const newFunctions = functions.filter((func) => {
456
+ const functionExists = this.#functions.some(
457
+ (existingFunc) => func.name === existingFunc.name && func.path === existingFunc.path,
458
+ )
381
459
 
382
- functions.forEach((func) => {
383
- EdgeFunctionsRegistry.logAddedFunction(func, this.findDisplayName(func.name))
460
+ return !functionExists
384
461
  })
462
+ const deletedFunctions = this.#functions.filter((existingFunc) => {
463
+ const functionExists = functions.some(
464
+ (func) => func.name === existingFunc.name && func.path === existingFunc.path,
465
+ )
385
466
 
386
- this.functions = functions
467
+ return !functionExists
468
+ })
387
469
 
388
- return functions
470
+ return { all: functions, new: newFunctions, deleted: deletedFunctions }
389
471
  }
390
472
 
391
- async setupWatchers({ projectDir }) {
473
+ /**
474
+ * @param {string} projectDir
475
+ */
476
+ async #setupWatchers(projectDir) {
392
477
  // Creating a watcher for the config file. When it changes, we update the
393
478
  // declarations and see if we need to register or unregister any functions.
394
- this.configWatcher = await watchDebounced(this.configPath, {
479
+ this.#configWatcher = await watchDebounced(this.#configPath, {
395
480
  onChange: async () => {
396
- const newConfig = await this.getUpdatedConfig()
481
+ const newConfig = await this.#getUpdatedConfig()
397
482
 
398
- this.declarationsFromTOML = EdgeFunctionsRegistry.getDeclarationsFromTOML(newConfig)
483
+ this.#declarationsFromTOML = EdgeFunctionsRegistry.#getDeclarationsFromTOML(newConfig)
399
484
 
400
- await this.checkForAddedOrDeletedFunctions()
485
+ await this.#checkForAddedOrDeletedFunctions()
401
486
  },
402
487
  })
403
488
 
@@ -405,22 +490,30 @@ export class EdgeFunctionsRegistry {
405
490
  // directories, they might be importing files that are located in
406
491
  // parent directories. So we watch the entire project directory for
407
492
  // changes.
408
- await this.setupWatcherForDirectory(projectDir)
493
+ await this.#setupWatcherForDirectory(projectDir)
409
494
  }
410
495
 
411
- async setupWatcherForDirectory(directory) {
496
+ /**
497
+ * @param {string} directory
498
+ * @returns {Promise<void>}
499
+ */
500
+ async #setupWatcherForDirectory(directory) {
412
501
  const watcher = await watchDebounced(directory, {
413
- onAdd: () => this.checkForAddedOrDeletedFunctions(),
414
- onChange: (path) => this.handleFileChange(path),
415
- onUnlink: () => this.checkForAddedOrDeletedFunctions(),
502
+ onAdd: () => this.#checkForAddedOrDeletedFunctions(),
503
+ onChange: (path) => this.#handleFileChange(path),
504
+ onUnlink: () => this.#checkForAddedOrDeletedFunctions(),
416
505
  })
417
506
 
418
- this.directoryWatchers.set(directory, watcher)
507
+ this.#directoryWatchers.set(directory, watcher)
419
508
  }
420
509
 
421
- findDisplayName(func) {
422
- const declarations = [...this.declarationsFromTOML, ...this.declarationsFromDeployConfig]
510
+ /**
511
+ * @param {string} func
512
+ * @returns {string | undefined}
513
+ */
514
+ #getDisplayName(func) {
515
+ const declarations = [...this.#declarationsFromTOML, ...this.#declarationsFromDeployConfig]
423
516
 
424
- return declarations.find((declaration) => declaration.function === func)?.name
517
+ return declarations.find((declaration) => declaration.function === func)?.name ?? func
425
518
  }
426
519
  }
@@ -212,7 +212,10 @@ export class FunctionsRegistry {
212
212
  await Promise.all(deletedFunctions.map((func) => this.unregisterFunction(func.name)))
213
213
 
214
214
  await Promise.all(
215
- functions.map(async ({ displayName, mainFile, name, runtime: runtimeName }) => {
215
+ // zip-it-and-ship-it returns an array sorted based on which extension should have precedence,
216
+ // where the last ones precede the previous ones. This is why
217
+ // we reverse the array so we get the right functions precedence in the CLI.
218
+ functions.reverse().map(async ({ displayName, mainFile, name, runtime: runtimeName }) => {
216
219
  const runtime = runtimes[runtimeName]
217
220
 
218
221
  // If there is no matching runtime, it means this function is not yet
@@ -222,9 +222,9 @@ export const startFunctionsServer = async (options) => {
222
222
  functionsDirectories.push(distPath)
223
223
  }
224
224
  } else {
225
- // The order of the function directories matters. Leftmost directories take
225
+ // The order of the function directories matters. Rightmost directories take
226
226
  // precedence.
227
- const sourceDirectories = [settings.functions, internalFunctionsDir].filter(Boolean)
227
+ const sourceDirectories = [internalFunctionsDir, settings.functions].filter(Boolean)
228
228
 
229
229
  functionsDirectories.push(...sourceDirectories)
230
230
  }
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  import { sep } from 'path'
2
3
 
3
4
  import pWaitFor from 'p-wait-for'
@@ -43,8 +44,10 @@ export const waitForDiff = async (api, deployId, siteId, timeout) => {
43
44
 
44
45
  await pWaitFor(loadDeploy, {
45
46
  interval: DEPLOY_POLL,
46
- timeout,
47
- message: 'Timeout while waiting for deploy',
47
+ timeout: {
48
+ milliseconds: timeout,
49
+ message: 'Timeout while waiting for deploy',
50
+ },
48
51
  })
49
52
 
50
53
  return deploy
@@ -80,8 +83,10 @@ export const waitForDeploy = async (api, deployId, siteId, timeout) => {
80
83
 
81
84
  await pWaitFor(loadDeploy, {
82
85
  interval: DEPLOY_POLL,
83
- timeout,
84
- message: 'Timeout while waiting for deploy',
86
+ timeout: {
87
+ milliseconds: timeout,
88
+ message: 'Timeout while waiting for deploy',
89
+ },
85
90
  })
86
91
 
87
92
  return deploy