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.
- package/npm-shrinkwrap.json +14737 -30481
- package/package.json +12 -85
- package/src/commands/dev/dev-exec.mjs +3 -2
- package/src/commands/dev/dev.mjs +20 -10
- package/src/commands/functions/functions-create.mjs +3 -2
- package/src/commands/functions/functions-serve.mjs +4 -3
- package/src/commands/functions/functions.mjs +1 -1
- package/src/commands/serve/serve.mjs +3 -2
- package/src/commands/watch/watch.mjs +4 -2
- package/src/functions-templates/javascript/google-analytics/package.json +1 -1
- package/src/functions-templates/javascript/token-hider/package-lock.json +6 -6
- package/src/functions-templates/typescript/hello-world/package-lock.json +6 -6
- package/src/lib/edge-functions/bootstrap.mjs +1 -1
- package/src/lib/edge-functions/proxy.mjs +6 -3
- package/src/lib/edge-functions/registry.mjs +263 -170
- package/src/lib/functions/registry.mjs +4 -1
- package/src/lib/functions/server.mjs +2 -2
- package/src/utils/deploy/util.mjs +9 -4
- package/src/utils/dev.mjs +20 -10
- package/src/utils/init/node-version.mjs +2 -3
- package/src/utils/live-tunnel.mjs +1 -1
- package/src/utils/proxy.mjs +6 -2
- package/src/utils/run-build.mjs +2 -1
|
@@ -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
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
* @
|
|
144
|
+
* @return {EdgeFunction[]}
|
|
112
145
|
*/
|
|
113
|
-
|
|
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
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
181
|
+
this.#processGraph(graph)
|
|
130
182
|
} catch (error) {
|
|
131
|
-
this
|
|
183
|
+
this.#buildError = error
|
|
132
184
|
|
|
133
185
|
throw error
|
|
134
186
|
}
|
|
135
187
|
}
|
|
136
188
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
152
|
-
|
|
198
|
+
this.#internalFunctions = internalFunctions.all
|
|
199
|
+
this.#userFunctions = userFunctions.all
|
|
153
200
|
|
|
154
|
-
|
|
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
|
|
209
|
+
await this.#build()
|
|
162
210
|
|
|
163
211
|
deletedFunctions.forEach((func) => {
|
|
164
|
-
|
|
212
|
+
this.#logDeletedFunction(func)
|
|
165
213
|
})
|
|
166
214
|
|
|
167
215
|
newFunctions.forEach((func) => {
|
|
168
|
-
|
|
216
|
+
this.#logAddedFunction(func)
|
|
169
217
|
})
|
|
170
218
|
} catch {
|
|
171
219
|
// no-op
|
|
172
220
|
}
|
|
173
221
|
}
|
|
174
222
|
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
256
|
+
/**
|
|
257
|
+
* @param {string} path
|
|
258
|
+
* @returns {Promise<void>}
|
|
259
|
+
*/
|
|
260
|
+
async #handleFileChange(path) {
|
|
200
261
|
const matchingFunctions = new Set(
|
|
201
|
-
[this
|
|
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
|
|
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
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
|
296
|
+
/**
|
|
297
|
+
* @return {Promise<void>}
|
|
298
|
+
*/
|
|
299
|
+
async initialize() {
|
|
300
|
+
return await this.#initialScan
|
|
247
301
|
}
|
|
248
302
|
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
|
262
|
-
this
|
|
263
|
-
this
|
|
264
|
-
|
|
265
|
-
this
|
|
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
|
|
328
|
+
const manifest = this.#bundler.generateManifest({
|
|
268
329
|
declarations,
|
|
269
|
-
userFunctionConfig: this
|
|
270
|
-
internalFunctionConfig:
|
|
271
|
-
functions: this
|
|
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
|
|
352
|
+
const orphanedDeclarations = this.#matchURLPathAgainstOrphanedDeclarations(urlPath)
|
|
291
353
|
|
|
292
354
|
return { functionNames, invocationMetadata, orphanedDeclarations }
|
|
293
355
|
}
|
|
294
356
|
|
|
295
|
-
|
|
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
|
|
302
|
-
const manifest = this
|
|
303
|
-
declarations: this
|
|
304
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
376
|
-
this
|
|
444
|
+
this.#dependencyPaths = dependencyPaths
|
|
445
|
+
this.#functionPaths = functionPaths
|
|
377
446
|
}
|
|
378
447
|
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
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
|
-
|
|
467
|
+
return !functionExists
|
|
468
|
+
})
|
|
387
469
|
|
|
388
|
-
return functions
|
|
470
|
+
return { all: functions, new: newFunctions, deleted: deletedFunctions }
|
|
389
471
|
}
|
|
390
472
|
|
|
391
|
-
|
|
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
|
|
479
|
+
this.#configWatcher = await watchDebounced(this.#configPath, {
|
|
395
480
|
onChange: async () => {
|
|
396
|
-
const newConfig = await this
|
|
481
|
+
const newConfig = await this.#getUpdatedConfig()
|
|
397
482
|
|
|
398
|
-
this
|
|
483
|
+
this.#declarationsFromTOML = EdgeFunctionsRegistry.#getDeclarationsFromTOML(newConfig)
|
|
399
484
|
|
|
400
|
-
await this
|
|
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
|
|
493
|
+
await this.#setupWatcherForDirectory(projectDir)
|
|
409
494
|
}
|
|
410
495
|
|
|
411
|
-
|
|
496
|
+
/**
|
|
497
|
+
* @param {string} directory
|
|
498
|
+
* @returns {Promise<void>}
|
|
499
|
+
*/
|
|
500
|
+
async #setupWatcherForDirectory(directory) {
|
|
412
501
|
const watcher = await watchDebounced(directory, {
|
|
413
|
-
onAdd: () => this
|
|
414
|
-
onChange: (path) => this
|
|
415
|
-
onUnlink: () => this
|
|
502
|
+
onAdd: () => this.#checkForAddedOrDeletedFunctions(),
|
|
503
|
+
onChange: (path) => this.#handleFileChange(path),
|
|
504
|
+
onUnlink: () => this.#checkForAddedOrDeletedFunctions(),
|
|
416
505
|
})
|
|
417
506
|
|
|
418
|
-
this
|
|
507
|
+
this.#directoryWatchers.set(directory, watcher)
|
|
419
508
|
}
|
|
420
509
|
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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.
|
|
225
|
+
// The order of the function directories matters. Rightmost directories take
|
|
226
226
|
// precedence.
|
|
227
|
-
const sourceDirectories = [settings.functions
|
|
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
|
-
|
|
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
|