netlify-cli 14.0.0-rc → 14.1.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,66 @@ 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)
108
119
  }
109
120
 
110
121
  /**
111
- * @param {EdgeFunction[]} functions
122
+ * @returns {Promise<void>}
112
123
  */
113
- async build(functions) {
124
+ async #doInitialScan() {
125
+ await this.#scanForFunctions()
126
+
127
+ this.#functions.forEach((func) => {
128
+ this.#logAddedFunction(func)
129
+ })
130
+
114
131
  try {
115
- const { functionsConfig, graph, success } = await this.runIsolate(functions, this.env, {
132
+ await this.#build()
133
+ } catch {
134
+ // no-op
135
+ }
136
+ }
137
+
138
+ /**
139
+ * @return {EdgeFunction[]}
140
+ */
141
+ get #functions() {
142
+ return [...this.#internalFunctions, ...this.#userFunctions]
143
+ }
144
+
145
+ /**
146
+ * @return {Promise<void>}
147
+ */
148
+ async #build() {
149
+ try {
150
+ const { functionsConfig, graph, success } = await this.#runIsolate(this.#functions, this.#env, {
116
151
  getFunctionsConfig: true,
117
152
  })
118
153
 
@@ -120,72 +155,80 @@ export class EdgeFunctionsRegistry {
120
155
  throw new Error('Build error')
121
156
  }
122
157
 
123
- this.buildError = null
124
- this.declarationsFromSource = functions.reduce(
125
- (acc, func, index) => ({ ...acc, [func.name]: functionsConfig[index] }),
158
+ this.#buildError = null
159
+
160
+ // We use one index to loop over both internal and user function, because we know that this.#functions has internalFunctions first.
161
+ // functionsConfig therefore contains first all internal functionConfigs and then user functionConfigs
162
+ let index = 0
163
+
164
+ this.#internalFunctionConfigs = this.#internalFunctions.reduce(
165
+ // eslint-disable-next-line no-plusplus
166
+ (acc, func) => ({ ...acc, [func.name]: functionsConfig[index++] }),
167
+ {},
168
+ )
169
+
170
+ this.#userFunctionConfigs = this.#userFunctions.reduce(
171
+ // eslint-disable-next-line no-plusplus
172
+ (acc, func) => ({ ...acc, [func.name]: functionsConfig[index++] }),
126
173
  {},
127
174
  )
128
175
 
129
- this.processGraph(graph)
176
+ this.#processGraph(graph)
130
177
  } catch (error) {
131
- this.buildError = error
178
+ this.#buildError = error
132
179
 
133
180
  throw error
134
181
  }
135
182
  }
136
183
 
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
- )
150
-
151
- return !functionExists
152
- })
153
-
154
- this.functions = functionsFound
184
+ /**
185
+ * @returns {Promise<void>}
186
+ */
187
+ async #checkForAddedOrDeletedFunctions() {
188
+ const { deleted: deletedFunctions, new: newFunctions } = await this.#scanForFunctions()
155
189
 
156
190
  if (newFunctions.length === 0 && deletedFunctions.length === 0) {
157
191
  return
158
192
  }
159
193
 
160
194
  try {
161
- await this.build(functionsFound)
195
+ await this.#build()
162
196
 
163
197
  deletedFunctions.forEach((func) => {
164
- EdgeFunctionsRegistry.logDeletedFunction(func, this.findDisplayName(func.name))
198
+ this.#logDeletedFunction(func)
165
199
  })
166
200
 
167
201
  newFunctions.forEach((func) => {
168
- EdgeFunctionsRegistry.logAddedFunction(func, this.findDisplayName(func.name))
202
+ this.#logAddedFunction(func)
169
203
  })
170
204
  } catch {
171
205
  // no-op
172
206
  }
173
207
  }
174
208
 
175
- static getDeclarationsFromTOML(config) {
209
+ /**
210
+ * @param {any} config
211
+ * @returns {Declaration[]}
212
+ */
213
+ static #getDeclarationsFromTOML(config) {
176
214
  const { edge_functions: edgeFunctions = [] } = config
177
215
 
178
216
  return edgeFunctions
179
217
  }
180
218
 
181
- static getEnvironmentVariables(envConfig) {
219
+ /**
220
+ * @param {Record<string, { sources:string[], value:string }>} envConfig
221
+ * @returns {Record<string, string>}
222
+ */
223
+ static #getEnvironmentVariables(envConfig) {
182
224
  const env = Object.create(null)
183
225
  Object.entries(envConfig).forEach(([key, variable]) => {
184
226
  if (
185
227
  variable.sources.includes('ui') ||
186
228
  variable.sources.includes('account') ||
187
229
  variable.sources.includes('addons') ||
188
- variable.sources.includes('internal')
230
+ variable.sources.includes('internal') ||
231
+ variable.sources.some((source) => source.startsWith('.env'))
189
232
  ) {
190
233
  env[key] = variable.value
191
234
  }
@@ -196,23 +239,27 @@ export class EdgeFunctionsRegistry {
196
239
  return env
197
240
  }
198
241
 
199
- async handleFileChange(path) {
242
+ /**
243
+ * @param {string} path
244
+ * @returns {Promise<void>}
245
+ */
246
+ async #handleFileChange(path) {
200
247
  const matchingFunctions = new Set(
201
- [this.functionPaths.get(path), ...(this.dependencyPaths.get(path) || [])].filter(Boolean),
248
+ [this.#functionPaths.get(path), ...(this.#dependencyPaths.get(path) || [])].filter(Boolean),
202
249
  )
203
250
 
204
251
  // If the file is not associated with any function, there's no point in
205
252
  // building. However, it might be that the path is in fact associated with
206
253
  // a function but we just haven't registered it due to a build error. So if
207
254
  // there was a build error, let's always build.
208
- if (matchingFunctions.size === 0 && this.buildError === null) {
255
+ if (matchingFunctions.size === 0 && this.#buildError === null) {
209
256
  return
210
257
  }
211
258
 
212
259
  log(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} edge functions...`)
213
260
 
214
261
  try {
215
- await this.build(this.functions)
262
+ await this.#build()
216
263
 
217
264
  const functionNames = [...matchingFunctions]
218
265
 
@@ -220,8 +267,11 @@ export class EdgeFunctionsRegistry {
220
267
  log(`${NETLIFYDEVLOG} ${chalk.green('Reloaded')} edge functions`)
221
268
  } else {
222
269
  functionNames.forEach((functionName) => {
223
- const displayName = this.findDisplayName(functionName)
224
- log(`${NETLIFYDEVLOG} ${chalk.green('Reloaded')} edge function ${chalk.yellow(displayName || functionName)}`)
270
+ log(
271
+ `${NETLIFYDEVLOG} ${chalk.green('Reloaded')} edge function ${chalk.yellow(
272
+ this.#getDisplayName(functionName),
273
+ )}`,
274
+ )
225
275
  })
226
276
  }
227
277
  } catch {
@@ -229,46 +279,44 @@ export class EdgeFunctionsRegistry {
229
279
  }
230
280
  }
231
281
 
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
282
+ /**
283
+ * @return {Promise<void>}
284
+ */
285
+ async initialize() {
286
+ return await this.#initialScan
247
287
  }
248
288
 
249
- static logAddedFunction(func, displayName) {
250
- log(`${NETLIFYDEVLOG} ${chalk.green('Loaded')} edge function ${chalk.yellow(displayName || func.name)}`)
289
+ /**
290
+ * @param {EdgeFunction} func
291
+ */
292
+ #logAddedFunction(func) {
293
+ log(`${NETLIFYDEVLOG} ${chalk.green('Loaded')} edge function ${chalk.yellow(this.#getDisplayName(func.name))}`)
251
294
  }
252
295
 
253
- static logDeletedFunction(func, displayName) {
254
- log(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} edge function ${chalk.yellow(displayName || func.name)}`)
296
+ /**
297
+ * @param {EdgeFunction} func
298
+ */
299
+ #logDeletedFunction(func) {
300
+ log(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} edge function ${chalk.yellow(this.#getDisplayName(func.name))}`)
255
301
  }
256
302
 
257
303
  /**
258
304
  * @param {string} urlPath
259
305
  */
260
306
  matchURLPath(urlPath) {
261
- const declarations = this.bundler.mergeDeclarations(
262
- this.declarationsFromTOML,
263
- this.declarationsFromSource,
264
- {},
265
- this.declarationsFromDeployConfig,
307
+ const declarations = this.#bundler.mergeDeclarations(
308
+ this.#declarationsFromTOML,
309
+ this.#userFunctionConfigs,
310
+ this.#internalFunctionConfigs,
311
+ this.#declarationsFromDeployConfig,
312
+ featureFlags,
266
313
  )
267
- const manifest = this.bundler.generateManifest({
314
+ const manifest = this.#bundler.generateManifest({
268
315
  declarations,
269
- userFunctionConfig: this.declarationsFromSource,
270
- internalFunctionConfig: {},
271
- functions: this.functions,
316
+ userFunctionConfig: this.#userFunctionConfigs,
317
+ internalFunctionConfig: this.#internalFunctionConfigs,
318
+ functions: this.#functions,
319
+ featureFlags,
272
320
  })
273
321
  const invocationMetadata = {
274
322
  function_config: manifest.function_config,
@@ -287,22 +335,29 @@ export class EdgeFunctionsRegistry {
287
335
  return !isExcluded
288
336
  })
289
337
  .map((route) => route.function)
290
- const orphanedDeclarations = this.matchURLPathAgainstOrphanedDeclarations(urlPath)
338
+ const orphanedDeclarations = this.#matchURLPathAgainstOrphanedDeclarations(urlPath)
291
339
 
292
340
  return { functionNames, invocationMetadata, orphanedDeclarations }
293
341
  }
294
342
 
295
- matchURLPathAgainstOrphanedDeclarations(urlPath) {
343
+ /**
344
+ *
345
+ * @param {string} urlPath
346
+ * @returns {string[]}
347
+ */
348
+ #matchURLPathAgainstOrphanedDeclarations(urlPath) {
296
349
  // `generateManifest` will only include functions for which there is both a
297
350
  // function file and a config declaration, but we want to catch cases where
298
351
  // a config declaration exists without a matching function file. To do that
299
352
  // we compute a list of functions from the declarations (the `path` doesn't
300
353
  // 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,
354
+ const functions = this.#declarationsFromTOML.map((declaration) => ({ name: declaration.function, path: '' }))
355
+ const manifest = this.#bundler.generateManifest({
356
+ declarations: this.#declarationsFromTOML,
357
+ userFunctionConfig: this.#userFunctionConfigs,
358
+ internalFunctionConfig: this.#internalFunctionConfigs,
305
359
  functions,
360
+ featureFlags,
306
361
  })
307
362
 
308
363
  const routes = [...manifest.routes, ...manifest.post_cache_routes].map((route) => ({
@@ -312,7 +367,7 @@ export class EdgeFunctionsRegistry {
312
367
 
313
368
  const functionNames = routes
314
369
  .filter((route) => {
315
- const hasFunctionFile = this.functions.some((func) => func.name === route.function)
370
+ const hasFunctionFile = this.#functions.some((func) => func.name === route.function)
316
371
 
317
372
  if (hasFunctionFile) {
318
373
  return false
@@ -325,18 +380,18 @@ export class EdgeFunctionsRegistry {
325
380
  return functionNames
326
381
  }
327
382
 
328
- processGraph(graph) {
383
+ #processGraph(graph) {
329
384
  if (!graph) {
330
385
  warn('Could not process edge functions dependency graph. Live reload will not be available.')
331
386
 
332
387
  return
333
388
  }
334
389
 
335
- // Creating a Map from `this.functions` that map function paths to function
390
+ // Creating a Map from `this.#functions` that map function paths to function
336
391
  // names. This allows us to match modules against functions in O(1) time as
337
392
  // opposed to O(n).
338
393
  // eslint-disable-next-line unicorn/prefer-spread
339
- const functionPaths = new Map(Array.from(this.functions, (func) => [func.path, func.name]))
394
+ const functionPaths = new Map(Array.from(this.#functions, (func) => [func.path, func.name]))
340
395
 
341
396
  // Mapping file URLs to names of functions that use them as dependencies.
342
397
  const dependencyPaths = new Map()
@@ -372,32 +427,55 @@ export class EdgeFunctionsRegistry {
372
427
  })
373
428
  })
374
429
 
375
- this.dependencyPaths = dependencyPaths
376
- this.functionPaths = functionPaths
430
+ this.#dependencyPaths = dependencyPaths
431
+ this.#functionPaths = functionPaths
377
432
  }
378
433
 
379
- async scan(directories) {
380
- const functions = await this.bundler.find(directories)
434
+ /**
435
+ * @returns {Promise<{all: EdgeFunction[], new: EdgeFunction[], deleted: EdgeFunction[]}>}
436
+ */
437
+ async #scanForFunctions() {
438
+ const [internalFunctions, userFunctions] = await Promise.all([
439
+ this.#bundler.find(this.#internalDirectories),
440
+ this.#bundler.find(this.#directories),
441
+ ])
442
+
443
+ const functions = [...internalFunctions, ...userFunctions]
444
+
445
+ const newFunctions = functions.filter((func) => {
446
+ const functionExists = this.#functions.some(
447
+ (existingFunc) => func.name === existingFunc.name && func.path === existingFunc.path,
448
+ )
449
+
450
+ return !functionExists
451
+ })
452
+ const deletedFunctions = this.#functions.filter((existingFunc) => {
453
+ const functionExists = functions.some(
454
+ (func) => func.name === existingFunc.name && func.path === existingFunc.path,
455
+ )
381
456
 
382
- functions.forEach((func) => {
383
- EdgeFunctionsRegistry.logAddedFunction(func, this.findDisplayName(func.name))
457
+ return !functionExists
384
458
  })
385
459
 
386
- this.functions = functions
460
+ this.#internalFunctions = internalFunctions
461
+ this.#userFunctions = userFunctions
387
462
 
388
- return functions
463
+ return { all: functions, new: newFunctions, deleted: deletedFunctions }
389
464
  }
390
465
 
391
- async setupWatchers({ projectDir }) {
466
+ /**
467
+ * @param {string} projectDir
468
+ */
469
+ async #setupWatchers(projectDir) {
392
470
  // Creating a watcher for the config file. When it changes, we update the
393
471
  // declarations and see if we need to register or unregister any functions.
394
- this.configWatcher = await watchDebounced(this.configPath, {
472
+ this.#configWatcher = await watchDebounced(this.#configPath, {
395
473
  onChange: async () => {
396
- const newConfig = await this.getUpdatedConfig()
474
+ const newConfig = await this.#getUpdatedConfig()
397
475
 
398
- this.declarationsFromTOML = EdgeFunctionsRegistry.getDeclarationsFromTOML(newConfig)
476
+ this.#declarationsFromTOML = EdgeFunctionsRegistry.#getDeclarationsFromTOML(newConfig)
399
477
 
400
- await this.checkForAddedOrDeletedFunctions()
478
+ await this.#checkForAddedOrDeletedFunctions()
401
479
  },
402
480
  })
403
481
 
@@ -405,22 +483,30 @@ export class EdgeFunctionsRegistry {
405
483
  // directories, they might be importing files that are located in
406
484
  // parent directories. So we watch the entire project directory for
407
485
  // changes.
408
- await this.setupWatcherForDirectory(projectDir)
486
+ await this.#setupWatcherForDirectory(projectDir)
409
487
  }
410
488
 
411
- async setupWatcherForDirectory(directory) {
489
+ /**
490
+ * @param {string} directory
491
+ * @returns {Promise<void>}
492
+ */
493
+ async #setupWatcherForDirectory(directory) {
412
494
  const watcher = await watchDebounced(directory, {
413
- onAdd: () => this.checkForAddedOrDeletedFunctions(),
414
- onChange: (path) => this.handleFileChange(path),
415
- onUnlink: () => this.checkForAddedOrDeletedFunctions(),
495
+ onAdd: () => this.#checkForAddedOrDeletedFunctions(),
496
+ onChange: (path) => this.#handleFileChange(path),
497
+ onUnlink: () => this.#checkForAddedOrDeletedFunctions(),
416
498
  })
417
499
 
418
- this.directoryWatchers.set(directory, watcher)
500
+ this.#directoryWatchers.set(directory, watcher)
419
501
  }
420
502
 
421
- findDisplayName(func) {
422
- const declarations = [...this.declarationsFromTOML, ...this.declarationsFromDeployConfig]
503
+ /**
504
+ * @param {string} func
505
+ * @returns {string | undefined}
506
+ */
507
+ #getDisplayName(func) {
508
+ const declarations = [...this.#declarationsFromTOML, ...this.#declarationsFromDeployConfig]
423
509
 
424
- return declarations.find((declaration) => declaration.function === func)?.name
510
+ return declarations.find((declaration) => declaration.function === func)?.name ?? func
425
511
  }
426
512
  }
@@ -16,6 +16,7 @@ import { clearSpinner, startSpinner } from '../lib/spinner.mjs'
16
16
 
17
17
  import getGlobalConfig from './get-global-config.mjs'
18
18
  import getPackageJson from './get-package-json.mjs'
19
+ import { reportError } from './telemetry/report-error.mjs'
19
20
 
20
21
  /** The parsed process argv without the binary only arguments and flags */
21
22
  const argv = process.argv.slice(2)
@@ -179,14 +180,16 @@ export const warn = (message = '') => {
179
180
  */
180
181
  export const error = (message = '', options = {}) => {
181
182
  const err = message instanceof Error ? message : new Error(message)
183
+
182
184
  if (options.exit === false) {
183
185
  const bang = chalk.red(BANG)
184
186
  if (process.env.DEBUG) {
185
- process.stderr.write(` ${bang} Warning: ${err.stack.split('\n').join(`\n ${bang} `)}\n`)
187
+ process.stderr.write(` ${bang} Warning: ${err.stack?.split('\n').join(`\n ${bang} `)}\n`)
186
188
  } else {
187
189
  process.stderr.write(` ${bang} ${chalk.red(`${err.name}:`)} ${err.message}\n`)
188
190
  }
189
191
  } else {
192
+ reportError(err, { severity: 'error' })
190
193
  throw err
191
194
  }
192
195
  }
@@ -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