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.
- package/npm-shrinkwrap.json +575 -793
- package/package.json +7 -6
- package/src/commands/dev/dev-exec.mjs +3 -2
- package/src/commands/dev/dev.mjs +3 -2
- package/src/commands/functions/functions-create.mjs +3 -2
- package/src/commands/functions/functions-serve.mjs +4 -3
- package/src/commands/serve/serve.mjs +3 -2
- package/src/commands/watch/watch.mjs +4 -2
- package/src/lib/edge-functions/proxy.mjs +6 -3
- package/src/lib/edge-functions/registry.mjs +258 -172
- package/src/utils/command-helpers.mjs +4 -1
- package/src/utils/deploy/util.mjs +9 -4
- package/src/utils/dev.mjs +20 -10
- package/src/utils/execa.mjs +4 -0
- package/src/utils/init/node-version.mjs +2 -3
- package/src/utils/live-tunnel.mjs +1 -1
- package/src/utils/telemetry/index.mjs +1 -0
- package/src/utils/telemetry/report-error.mjs +44 -0
- package/src/utils/telemetry/request.mjs +13 -3
- package/src/utils/telemetry/telemetry.mjs +1 -7
- package/src/utils/telemetry/utils.mjs +7 -0
|
@@ -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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this
|
|
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
|
-
* @
|
|
122
|
+
* @returns {Promise<void>}
|
|
112
123
|
*/
|
|
113
|
-
async
|
|
124
|
+
async #doInitialScan() {
|
|
125
|
+
await this.#scanForFunctions()
|
|
126
|
+
|
|
127
|
+
this.#functions.forEach((func) => {
|
|
128
|
+
this.#logAddedFunction(func)
|
|
129
|
+
})
|
|
130
|
+
|
|
114
131
|
try {
|
|
115
|
-
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
176
|
+
this.#processGraph(graph)
|
|
130
177
|
} catch (error) {
|
|
131
|
-
this
|
|
178
|
+
this.#buildError = error
|
|
132
179
|
|
|
133
180
|
throw error
|
|
134
181
|
}
|
|
135
182
|
}
|
|
136
183
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
195
|
+
await this.#build()
|
|
162
196
|
|
|
163
197
|
deletedFunctions.forEach((func) => {
|
|
164
|
-
|
|
198
|
+
this.#logDeletedFunction(func)
|
|
165
199
|
})
|
|
166
200
|
|
|
167
201
|
newFunctions.forEach((func) => {
|
|
168
|
-
|
|
202
|
+
this.#logAddedFunction(func)
|
|
169
203
|
})
|
|
170
204
|
} catch {
|
|
171
205
|
// no-op
|
|
172
206
|
}
|
|
173
207
|
}
|
|
174
208
|
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
+
/**
|
|
243
|
+
* @param {string} path
|
|
244
|
+
* @returns {Promise<void>}
|
|
245
|
+
*/
|
|
246
|
+
async #handleFileChange(path) {
|
|
200
247
|
const matchingFunctions = new Set(
|
|
201
|
-
[this
|
|
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
|
|
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
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
|
262
|
-
this
|
|
263
|
-
this
|
|
264
|
-
|
|
265
|
-
this
|
|
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
|
|
314
|
+
const manifest = this.#bundler.generateManifest({
|
|
268
315
|
declarations,
|
|
269
|
-
userFunctionConfig: this
|
|
270
|
-
internalFunctionConfig:
|
|
271
|
-
functions: this
|
|
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
|
|
338
|
+
const orphanedDeclarations = this.#matchURLPathAgainstOrphanedDeclarations(urlPath)
|
|
291
339
|
|
|
292
340
|
return { functionNames, invocationMetadata, orphanedDeclarations }
|
|
293
341
|
}
|
|
294
342
|
|
|
295
|
-
|
|
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
|
|
302
|
-
const manifest = this
|
|
303
|
-
declarations: this
|
|
304
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
376
|
-
this
|
|
430
|
+
this.#dependencyPaths = dependencyPaths
|
|
431
|
+
this.#functionPaths = functionPaths
|
|
377
432
|
}
|
|
378
433
|
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
383
|
-
EdgeFunctionsRegistry.logAddedFunction(func, this.findDisplayName(func.name))
|
|
457
|
+
return !functionExists
|
|
384
458
|
})
|
|
385
459
|
|
|
386
|
-
this
|
|
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
|
-
|
|
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
|
|
472
|
+
this.#configWatcher = await watchDebounced(this.#configPath, {
|
|
395
473
|
onChange: async () => {
|
|
396
|
-
const newConfig = await this
|
|
474
|
+
const newConfig = await this.#getUpdatedConfig()
|
|
397
475
|
|
|
398
|
-
this
|
|
476
|
+
this.#declarationsFromTOML = EdgeFunctionsRegistry.#getDeclarationsFromTOML(newConfig)
|
|
399
477
|
|
|
400
|
-
await this
|
|
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
|
|
486
|
+
await this.#setupWatcherForDirectory(projectDir)
|
|
409
487
|
}
|
|
410
488
|
|
|
411
|
-
|
|
489
|
+
/**
|
|
490
|
+
* @param {string} directory
|
|
491
|
+
* @returns {Promise<void>}
|
|
492
|
+
*/
|
|
493
|
+
async #setupWatcherForDirectory(directory) {
|
|
412
494
|
const watcher = await watchDebounced(directory, {
|
|
413
|
-
onAdd: () => this
|
|
414
|
-
onChange: (path) => this
|
|
415
|
-
onUnlink: () => this
|
|
495
|
+
onAdd: () => this.#checkForAddedOrDeletedFunctions(),
|
|
496
|
+
onChange: (path) => this.#handleFileChange(path),
|
|
497
|
+
onUnlink: () => this.#checkForAddedOrDeletedFunctions(),
|
|
416
498
|
})
|
|
417
499
|
|
|
418
|
-
this
|
|
500
|
+
this.#directoryWatchers.set(directory, watcher)
|
|
419
501
|
}
|
|
420
502
|
|
|
421
|
-
|
|
422
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
86
|
+
timeout: {
|
|
87
|
+
milliseconds: timeout,
|
|
88
|
+
message: 'Timeout while waiting for deploy',
|
|
89
|
+
},
|
|
85
90
|
})
|
|
86
91
|
|
|
87
92
|
return deploy
|