netlify-cli 10.15.0 → 10.17.1
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 +812 -1571
- package/package.json +7 -6
- package/src/commands/dev/dev.js +31 -4
- package/src/commands/env/env-get.js +31 -4
- package/src/commands/env/env-list.js +59 -16
- package/src/commands/graph/graph-edit.js +3 -2
- package/src/commands/graph/graph-handler.js +41 -63
- package/src/commands/graph/graph-init.js +2 -1
- package/src/commands/graph/graph-library.js +20 -12
- package/src/commands/graph/graph-operations.js +6 -1
- package/src/commands/graph/graph-pull.js +72 -16
- package/src/commands/init/init.js +1 -1
- package/src/functions-templates/go/hello-world/go.mod +1 -1
- package/src/lib/edge-functions/proxy.js +4 -0
- package/src/lib/edge-functions/registry.js +29 -3
- package/src/lib/one-graph/cli-client.js +555 -108
- package/src/lib/one-graph/cli-netlify-graph.js +519 -50
- package/src/utils/dev.js +2 -2
- package/src/utils/env/index.js +156 -1
- package/src/utils/proxy.js +2 -0
|
@@ -1,24 +1,41 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
/* eslint-disable eslint-comments/disable-enable-pair */
|
|
3
3
|
/* eslint-disable fp/no-loops */
|
|
4
|
+
/* eslint-disable no-underscore-dangle */
|
|
4
5
|
const crypto = require('crypto')
|
|
5
6
|
const { readFileSync, writeFileSync } = require('fs')
|
|
6
7
|
const os = require('os')
|
|
7
8
|
const path = require('path')
|
|
9
|
+
const process = require('process')
|
|
8
10
|
|
|
9
11
|
const gitRepoInfo = require('git-repo-info')
|
|
10
|
-
const
|
|
11
|
-
const {
|
|
12
|
-
|
|
12
|
+
const WSL = require('is-wsl')
|
|
13
|
+
const {
|
|
14
|
+
// eslint-disable-next-line no-unused-vars
|
|
15
|
+
CliEventHelper,
|
|
16
|
+
// eslint-disable-next-line no-unused-vars
|
|
17
|
+
CodegenHelpers,
|
|
18
|
+
GraphQL,
|
|
19
|
+
InternalConsole,
|
|
20
|
+
NetlifyGraph,
|
|
21
|
+
NetlifyGraphLockfile,
|
|
22
|
+
OneGraphClient,
|
|
23
|
+
} = require('netlify-onegraph-internal')
|
|
24
|
+
|
|
25
|
+
const frameworkInfoPromise = import('@netlify/framework-info')
|
|
26
|
+
|
|
27
|
+
const { version } = require('../../../package.json')
|
|
13
28
|
// eslint-disable-next-line no-unused-vars
|
|
14
29
|
const { StateConfig, USER_AGENT, chalk, error, execa, log, warn, watchDebounced } = require('../../utils')
|
|
15
30
|
|
|
16
31
|
const {
|
|
17
32
|
generateFunctionsFile,
|
|
18
33
|
generateHandlerByOperationId,
|
|
19
|
-
|
|
34
|
+
getCodegenFunctionById,
|
|
35
|
+
getCodegenModule,
|
|
20
36
|
normalizeOperationsDoc,
|
|
21
37
|
readGraphQLOperationsSourceFile,
|
|
38
|
+
setNetlifyTomlCodeGeneratorModule,
|
|
22
39
|
writeGraphQLOperationsSourceFile,
|
|
23
40
|
writeGraphQLSchemaFile,
|
|
24
41
|
} = require('./cli-netlify-graph')
|
|
@@ -33,6 +50,9 @@ const internalConsole = {
|
|
|
33
50
|
debug: console.debug,
|
|
34
51
|
}
|
|
35
52
|
|
|
53
|
+
/** @type {string | null} */
|
|
54
|
+
let currentPersistedDocId = null
|
|
55
|
+
|
|
36
56
|
/**
|
|
37
57
|
* Keep track of which document hashes we've received from the server so we can ignore events from the filesystem based on them
|
|
38
58
|
*/
|
|
@@ -45,22 +65,25 @@ InternalConsole.registerConsole(internalConsole)
|
|
|
45
65
|
* @param {object} input
|
|
46
66
|
* @param {string} input.appId The app to query against, typically the siteId
|
|
47
67
|
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
68
|
+
* @param {object} input.config The parsed netlify.toml file
|
|
48
69
|
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
49
70
|
* @param {function} input.onClose A function to call when the polling loop is closed
|
|
50
71
|
* @param {function} input.onError A function to call when an error occurs
|
|
51
72
|
* @param {function} input.onEvents A function to call when CLI events are received and need to be processed
|
|
73
|
+
* @param {function} input.onSchemaIdChange A function to call when the CLI schemaId has changed
|
|
52
74
|
* @param {string} input.sessionId The session id to monitor CLI events for
|
|
53
75
|
* @param {StateConfig} input.state A function to call to set/get the current state of the local Netlify project
|
|
54
76
|
* @param {any} input.site The site object
|
|
55
77
|
* @returns
|
|
56
78
|
*/
|
|
57
79
|
const monitorCLISessionEvents = (input) => {
|
|
58
|
-
const { appId, netlifyGraphConfig, netlifyToken, onClose, onError, onEvents, site, state } = input
|
|
80
|
+
const { appId, config, netlifyGraphConfig, netlifyToken, onClose, onError, onEvents, site, state } = input
|
|
59
81
|
const currentSessionId = input.sessionId
|
|
82
|
+
// TODO (sg): Track changing schemaId for a session
|
|
60
83
|
|
|
61
84
|
const frequency = 5000
|
|
62
85
|
// 30 minutes
|
|
63
|
-
const defaultHeartbeatFrequency =
|
|
86
|
+
const defaultHeartbeatFrequency = 30_000
|
|
64
87
|
let shouldClose = false
|
|
65
88
|
let nextMarkActiveHeartbeat = defaultHeartbeatFrequency
|
|
66
89
|
|
|
@@ -72,8 +95,10 @@ const monitorCLISessionEvents = (input) => {
|
|
|
72
95
|
appId,
|
|
73
96
|
sessionId: currentSessionId,
|
|
74
97
|
})
|
|
98
|
+
|
|
75
99
|
const heartbeatIntervalms = fullSession.session.cliHeartbeatIntervalMs || defaultHeartbeatFrequency
|
|
76
100
|
nextMarkActiveHeartbeat = heartbeatIntervalms
|
|
101
|
+
|
|
77
102
|
const markCLISessionActiveResult = await OneGraphClient.executeMarkCliSessionActiveHeartbeat(
|
|
78
103
|
graphJwt.jwt,
|
|
79
104
|
site.id,
|
|
@@ -94,12 +119,12 @@ const monitorCLISessionEvents = (input) => {
|
|
|
94
119
|
const enabledServices = state.get('oneGraphEnabledServices') || ['onegraph']
|
|
95
120
|
|
|
96
121
|
try {
|
|
97
|
-
const
|
|
98
|
-
if (!
|
|
122
|
+
const graphQLSchemaInfo = await OneGraphClient.fetchGraphQLSchemaForSession(jwt, siteId, input.sessionId)
|
|
123
|
+
if (!graphQLSchemaInfo) {
|
|
99
124
|
warn('Unable to fetch enabled services for site for code generation')
|
|
100
125
|
return
|
|
101
126
|
}
|
|
102
|
-
const newEnabledServices =
|
|
127
|
+
const newEnabledServices = graphQLSchemaInfo.services.map((service) => service.graphQLField)
|
|
103
128
|
const enabledServicesCompareKey = enabledServices.sort().join(',')
|
|
104
129
|
const newEnabledServicesCompareKey = newEnabledServices.sort().join(',')
|
|
105
130
|
|
|
@@ -109,7 +134,17 @@ const monitorCLISessionEvents = (input) => {
|
|
|
109
134
|
'Reloading',
|
|
110
135
|
)} Netlify Graph schema..., ${enabledServicesCompareKey} => ${newEnabledServicesCompareKey}`,
|
|
111
136
|
)
|
|
112
|
-
|
|
137
|
+
|
|
138
|
+
const schemaId = graphQLSchemaInfo.id
|
|
139
|
+
|
|
140
|
+
if (!schemaId) {
|
|
141
|
+
warn(`Unable to read schemaId from Netlify Graph session, not regenerating code`)
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
mergeLockfile({ siteRoot: site.root, schemaId })
|
|
146
|
+
|
|
147
|
+
await refetchAndGenerateFromOneGraph({ config, netlifyGraphConfig, state, jwt, schemaId, siteId, sessionId })
|
|
113
148
|
log(`${chalk.green('Reloaded')} Netlify Graph schema and regenerated functions`)
|
|
114
149
|
}
|
|
115
150
|
} catch {
|
|
@@ -128,6 +163,7 @@ const monitorCLISessionEvents = (input) => {
|
|
|
128
163
|
if (shouldClose) {
|
|
129
164
|
clearTimeout(handle)
|
|
130
165
|
onClose && onClose()
|
|
166
|
+
return
|
|
131
167
|
}
|
|
132
168
|
|
|
133
169
|
const graphJwt = await OneGraphClient.getGraphJwtForSite({ siteId: appId, nfToken: netlifyToken })
|
|
@@ -205,24 +241,65 @@ const monitorOperationFile = async ({ netlifyGraphConfig, onAdd, onChange, onUnl
|
|
|
205
241
|
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
206
242
|
* @param {StateConfig} input.state A function to call to set/get the current state of the local Netlify project
|
|
207
243
|
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
244
|
+
* @returns {Promise<Record<string, unknown> | undefined>}
|
|
245
|
+
*/
|
|
246
|
+
const fetchCliSessionSchema = async (input) => {
|
|
247
|
+
const { jwt, siteId } = input
|
|
248
|
+
|
|
249
|
+
await OneGraphClient.ensureAppForSite(jwt, siteId)
|
|
250
|
+
|
|
251
|
+
const schemaInfo = await OneGraphClient.fetchNetlifySessionSchemaQuery(
|
|
252
|
+
{ sessionId: input.sessionId },
|
|
253
|
+
{
|
|
254
|
+
accessToken: jwt,
|
|
255
|
+
siteId,
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if (!schemaInfo) {
|
|
260
|
+
warn('Unable to fetch schema for session')
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const schemaMetadata = schemaInfo.data.oneGraph.netlifyCliSession.graphQLSchema
|
|
266
|
+
return schemaMetadata
|
|
267
|
+
} catch {}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Fetch the schema for a site, and regenerate all of the downstream files
|
|
272
|
+
* @param {object} input
|
|
273
|
+
* @param {string} input.siteId The id of the site to query against
|
|
274
|
+
* @param {string} input.jwt The Graph JWT
|
|
275
|
+
* @param {object} input.config The parsed netlify.toml file
|
|
276
|
+
* @param {string} input.sessionId The session ID for the current session
|
|
277
|
+
* @param {string} input.schemaId The schemaId for the current session
|
|
278
|
+
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
279
|
+
* @param {StateConfig} input.state A function to call to set/get the current state of the local Netlify project
|
|
280
|
+
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
208
281
|
* @returns {Promise<void>}
|
|
209
282
|
*/
|
|
210
283
|
const refetchAndGenerateFromOneGraph = async (input) => {
|
|
211
|
-
const { jwt, logger, netlifyGraphConfig, siteId, state } = input
|
|
284
|
+
const { config, jwt, logger, netlifyGraphConfig, schemaId, siteId, state } = input
|
|
212
285
|
|
|
213
286
|
await OneGraphClient.ensureAppForSite(jwt, siteId)
|
|
214
287
|
|
|
215
|
-
const
|
|
216
|
-
if (!
|
|
217
|
-
warn('Unable to fetch
|
|
288
|
+
const graphQLSchemaInfo = await OneGraphClient.fetchGraphQLSchemaForSession(jwt, siteId, input.sessionId)
|
|
289
|
+
if (!graphQLSchemaInfo) {
|
|
290
|
+
warn('Unable to fetch schema info for site for code generation')
|
|
218
291
|
return
|
|
219
292
|
}
|
|
220
293
|
|
|
221
|
-
const enabledServices =
|
|
294
|
+
const enabledServices = graphQLSchemaInfo.services
|
|
222
295
|
.map((service) => service.graphQLField)
|
|
223
296
|
.sort((aString, bString) => aString.localeCompare(bString))
|
|
224
297
|
|
|
225
|
-
const schema = await OneGraphClient.
|
|
298
|
+
const schema = await OneGraphClient.fetchOneGraphSchemaById({
|
|
299
|
+
siteId,
|
|
300
|
+
schemaId: graphQLSchemaInfo.id,
|
|
301
|
+
accessToken: jwt,
|
|
302
|
+
})
|
|
226
303
|
|
|
227
304
|
if (!schema) {
|
|
228
305
|
error('Unable to fetch schema from Netlify Graph')
|
|
@@ -234,15 +311,22 @@ const refetchAndGenerateFromOneGraph = async (input) => {
|
|
|
234
311
|
}
|
|
235
312
|
|
|
236
313
|
const parsedDoc = parse(currentOperationsDoc)
|
|
237
|
-
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
|
|
314
|
+
const { fragments, functions } = extractFunctionsFromOperationDoc(GraphQL, parsedDoc)
|
|
315
|
+
|
|
316
|
+
if (!schema) {
|
|
317
|
+
warn('Unable to parse schema, please run graph:pull to update')
|
|
318
|
+
return
|
|
319
|
+
}
|
|
238
320
|
|
|
239
321
|
generateFunctionsFile({
|
|
322
|
+
config,
|
|
240
323
|
logger,
|
|
241
324
|
netlifyGraphConfig,
|
|
242
325
|
schema,
|
|
243
326
|
operationsDoc: currentOperationsDoc,
|
|
244
327
|
functions,
|
|
245
328
|
fragments,
|
|
329
|
+
schemaId,
|
|
246
330
|
})
|
|
247
331
|
writeGraphQLSchemaFile({ logger, netlifyGraphConfig, schema })
|
|
248
332
|
state.set('oneGraphEnabledServices', enabledServices)
|
|
@@ -251,12 +335,14 @@ const refetchAndGenerateFromOneGraph = async (input) => {
|
|
|
251
335
|
/**
|
|
252
336
|
* Regenerate the function library based on the current operations document on disk
|
|
253
337
|
* @param {object} input
|
|
338
|
+
* @param {object} input.config The parsed netlify.toml file
|
|
254
339
|
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
|
|
340
|
+
* @param {string} input.schemaId The GraphQL schemaId to use when generating code
|
|
255
341
|
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
256
342
|
* @returns
|
|
257
343
|
*/
|
|
258
344
|
const regenerateFunctionsFileFromOperationsFile = (input) => {
|
|
259
|
-
const { netlifyGraphConfig, schema } = input
|
|
345
|
+
const { config, netlifyGraphConfig, schema, schemaId } = input
|
|
260
346
|
|
|
261
347
|
const appOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
262
348
|
|
|
@@ -270,14 +356,35 @@ const regenerateFunctionsFileFromOperationsFile = (input) => {
|
|
|
270
356
|
const parsedDoc = parse(appOperationsDoc, {
|
|
271
357
|
noLocation: true,
|
|
272
358
|
})
|
|
273
|
-
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
|
|
274
|
-
generateFunctionsFile({
|
|
359
|
+
const { fragments, functions } = extractFunctionsFromOperationDoc(GraphQL, parsedDoc)
|
|
360
|
+
generateFunctionsFile({
|
|
361
|
+
config,
|
|
362
|
+
netlifyGraphConfig,
|
|
363
|
+
schema,
|
|
364
|
+
operationsDoc: appOperationsDoc,
|
|
365
|
+
functions,
|
|
366
|
+
fragments,
|
|
367
|
+
schemaId,
|
|
368
|
+
})
|
|
275
369
|
}
|
|
276
370
|
|
|
277
371
|
/**
|
|
278
372
|
* Lockfile Operations
|
|
279
373
|
*/
|
|
280
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Read the Netlify Graph lockfile from disk, if it exists
|
|
377
|
+
* @param {object} input
|
|
378
|
+
* @param {string} input.siteRoot The GraphQL schema to use when generating code
|
|
379
|
+
* @return {NetlifyGraphLockfile.V0_format | undefined}
|
|
380
|
+
*/
|
|
381
|
+
const readLockfile = ({ siteRoot }) => {
|
|
382
|
+
try {
|
|
383
|
+
const buf = readFileSync(path.join(siteRoot, NetlifyGraphLockfile.defaultLockFileName))
|
|
384
|
+
return JSON.parse(buf.toString('utf8'))
|
|
385
|
+
} catch {}
|
|
386
|
+
}
|
|
387
|
+
|
|
281
388
|
/**
|
|
282
389
|
* Persist the Netlify Graph lockfile on disk
|
|
283
390
|
* @param {object} input
|
|
@@ -289,15 +396,45 @@ const writeLockfile = ({ lockfile, siteRoot }) => {
|
|
|
289
396
|
}
|
|
290
397
|
|
|
291
398
|
/**
|
|
292
|
-
*
|
|
399
|
+
* Persist the Netlify Graph lockfile on disk
|
|
293
400
|
* @param {object} input
|
|
294
401
|
* @param {string} input.siteRoot The GraphQL schema to use when generating code
|
|
295
|
-
* @
|
|
402
|
+
* @param {string=} input.schemaId
|
|
403
|
+
* @param {string=} input.operationsHash
|
|
296
404
|
*/
|
|
297
|
-
const
|
|
405
|
+
const mergeLockfile = ({ operationsHash, schemaId, siteRoot }) => {
|
|
406
|
+
const lockfile = readLockfile({ siteRoot })
|
|
407
|
+
if (lockfile) {
|
|
408
|
+
/** @type {NetlifyGraphLockfile.V0_format} */
|
|
409
|
+
const newLockfile = {
|
|
410
|
+
...lockfile,
|
|
411
|
+
locked: {
|
|
412
|
+
...lockfile.locked,
|
|
413
|
+
},
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (operationsHash) {
|
|
417
|
+
newLockfile.locked.operationsHash = operationsHash
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (schemaId) {
|
|
421
|
+
newLockfile.locked.schemaId = schemaId
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
writeFileSync(path.join(siteRoot, NetlifyGraphLockfile.defaultLockFileName), JSON.stringify(newLockfile, null, 2))
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Read the Netlify Graph schemaId from the lockfile on disk, if it exists
|
|
430
|
+
* @param {object} input
|
|
431
|
+
* @param {string} input.siteRoot The GraphQL schema to use when generating code
|
|
432
|
+
* @return {string | undefined}
|
|
433
|
+
*/
|
|
434
|
+
const readSchemaIdFromLockfile = ({ siteRoot }) => {
|
|
298
435
|
try {
|
|
299
|
-
const
|
|
300
|
-
return
|
|
436
|
+
const lockfile = readLockfile({ siteRoot })
|
|
437
|
+
return lockfile && lockfile.locked.schemaId
|
|
301
438
|
} catch {}
|
|
302
439
|
}
|
|
303
440
|
|
|
@@ -319,8 +456,10 @@ const quickHash = (input) => {
|
|
|
319
456
|
* @param {string} input.siteId The site id to query against
|
|
320
457
|
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
321
458
|
* @param {string} input.docId The GraphQL operations document id to fetch
|
|
459
|
+
* @param {object} input.config The parsed netlify.toml file
|
|
322
460
|
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
323
461
|
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
|
|
462
|
+
* @param {string} input.schemaId The GraphQL schemaId to use when generating code
|
|
324
463
|
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
325
464
|
* @returns {Promise<string | undefined>}
|
|
326
465
|
*/
|
|
@@ -335,7 +474,9 @@ const fetchGraphQLOperationsLibraryFromPersistedDoc = async (input) => {
|
|
|
335
474
|
}
|
|
336
475
|
|
|
337
476
|
// Sorts the operations stably, prepends the @netlify directive, etc.
|
|
338
|
-
const operationsDocString = normalizeOperationsDoc(persistedDoc.query)
|
|
477
|
+
const operationsDocString = normalizeOperationsDoc(GraphQL, persistedDoc.query)
|
|
478
|
+
|
|
479
|
+
currentPersistedDocId = docId
|
|
339
480
|
|
|
340
481
|
return operationsDocString
|
|
341
482
|
} catch {
|
|
@@ -346,17 +487,19 @@ const fetchGraphQLOperationsLibraryFromPersistedDoc = async (input) => {
|
|
|
346
487
|
/**
|
|
347
488
|
* Fetch a persisted operations doc by its id, write it to the system, and regenerate the library
|
|
348
489
|
* @param {object} input
|
|
490
|
+
* @param {object} input.config The parsed netlify.toml config file
|
|
349
491
|
* @param {string} input.operationsDocString The contents of the GraphQL operations document
|
|
350
492
|
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
351
493
|
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
|
|
494
|
+
* @param {string} input.schemaId The GraphQL schemaId to use when generating code
|
|
352
495
|
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
353
496
|
* @returns
|
|
354
497
|
*/
|
|
355
498
|
const updateGraphQLOperationsFileFromPersistedDoc = (input) => {
|
|
356
|
-
const { logger, netlifyGraphConfig, operationsDocString, schema } = input
|
|
499
|
+
const { config, logger, netlifyGraphConfig, operationsDocString, schema, schemaId } = input
|
|
357
500
|
|
|
358
501
|
writeGraphQLOperationsSourceFile({ logger, netlifyGraphConfig, operationsDocString })
|
|
359
|
-
regenerateFunctionsFileFromOperationsFile({ netlifyGraphConfig, schema })
|
|
502
|
+
regenerateFunctionsFileFromOperationsFile({ config, netlifyGraphConfig, schema, schemaId })
|
|
360
503
|
|
|
361
504
|
const hash = quickHash(operationsDocString)
|
|
362
505
|
|
|
@@ -372,6 +515,7 @@ const updateGraphQLOperationsFileFromPersistedDoc = (input) => {
|
|
|
372
515
|
/**
|
|
373
516
|
* Fetch a persisted operations doc by its id, write it to the system, and regenerate the library
|
|
374
517
|
* @param {object} input
|
|
518
|
+
* @param {object} input.config The parsed netlify.toml config file
|
|
375
519
|
* @param {string} input.siteId The site id to query against
|
|
376
520
|
* @param {string} input.schemaId The schema ID to query against
|
|
377
521
|
* @param {string} input.siteRoot Path to the root of the project
|
|
@@ -396,94 +540,186 @@ const handleOperationsLibraryPersistedEvent = async (input) => {
|
|
|
396
540
|
updateGraphQLOperationsFileFromPersistedDoc({ ...input, operationsDocString: operationsFileContents })
|
|
397
541
|
}
|
|
398
542
|
|
|
543
|
+
/**
|
|
544
|
+
*
|
|
545
|
+
* @param {object} input
|
|
546
|
+
* @param {any} input.site The site object
|
|
547
|
+
* @param {CliEventHelper.CliEvent} input.event
|
|
548
|
+
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
|
|
549
|
+
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
550
|
+
* @param {object} input.config The parsed netlify.toml config file
|
|
551
|
+
* @param {string} input.docId The GraphQL operations document id to fetch
|
|
552
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
553
|
+
* @param {string} input.schemaId The schemaId for the current session
|
|
554
|
+
* @param {string} input.sessionId The session ID to use for this CLI session (default: read from state)
|
|
555
|
+
* @param {string} input.siteId The site id to query against
|
|
556
|
+
* @param {string} input.siteRoot Path to the root of the project
|
|
557
|
+
* @returns {Promise<void>}
|
|
558
|
+
*/
|
|
399
559
|
const handleCliSessionEvent = async ({
|
|
560
|
+
config,
|
|
561
|
+
docId,
|
|
400
562
|
event,
|
|
401
563
|
netlifyGraphConfig,
|
|
402
564
|
netlifyToken,
|
|
403
565
|
schema,
|
|
566
|
+
schemaId,
|
|
404
567
|
sessionId,
|
|
568
|
+
site,
|
|
405
569
|
siteId,
|
|
406
570
|
siteRoot,
|
|
407
571
|
}) => {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
572
|
+
switch (event.__typename) {
|
|
573
|
+
case 'OneGraphNetlifyCliSessionTestEvent': {
|
|
574
|
+
const { payload } = event
|
|
575
|
+
|
|
411
576
|
await handleCliSessionEvent({
|
|
577
|
+
config,
|
|
578
|
+
docId,
|
|
412
579
|
netlifyToken,
|
|
580
|
+
// @ts-ignore
|
|
413
581
|
event: payload,
|
|
414
582
|
netlifyGraphConfig,
|
|
415
583
|
schema,
|
|
584
|
+
schemaId,
|
|
416
585
|
sessionId,
|
|
417
586
|
siteId,
|
|
418
587
|
siteRoot,
|
|
588
|
+
site,
|
|
419
589
|
})
|
|
590
|
+
|
|
420
591
|
break
|
|
592
|
+
}
|
|
421
593
|
case 'OneGraphNetlifyCliSessionGenerateHandlerEvent': {
|
|
422
|
-
|
|
594
|
+
const { payload } = event
|
|
595
|
+
|
|
596
|
+
if (!payload.operationId) {
|
|
423
597
|
warn(`No operation id found in payload,
|
|
424
|
-
|
|
598
|
+
${JSON.stringify(payload, null, 2)}`)
|
|
425
599
|
return
|
|
426
600
|
}
|
|
601
|
+
|
|
602
|
+
const codegenModule = await getCodegenModule({ config })
|
|
603
|
+
if (!codegenModule) {
|
|
604
|
+
error(
|
|
605
|
+
`No Netlify Graph codegen module specified in netlify.toml under the [graph] header. Please specify 'codeGenerator' field and try again.`,
|
|
606
|
+
)
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const codeGenerator = await getCodegenFunctionById({ config, id: payload.codegenId })
|
|
611
|
+
if (!codeGenerator) {
|
|
612
|
+
warn(
|
|
613
|
+
`Unable to find Netlify Graph code generator with id "${payload.codegenId}" from ${JSON.stringify(
|
|
614
|
+
payload,
|
|
615
|
+
null,
|
|
616
|
+
2,
|
|
617
|
+
)}`,
|
|
618
|
+
)
|
|
619
|
+
return
|
|
620
|
+
}
|
|
621
|
+
|
|
427
622
|
const files = generateHandlerByOperationId({
|
|
428
623
|
netlifyGraphConfig,
|
|
429
624
|
schema,
|
|
430
|
-
operationId: payload.operationId
|
|
625
|
+
operationId: payload.operationId,
|
|
431
626
|
handlerOptions: payload,
|
|
627
|
+
generate: codeGenerator.generateHandler,
|
|
432
628
|
})
|
|
433
629
|
|
|
434
630
|
if (!files) {
|
|
435
|
-
warn(`No files generated for operation id: ${payload.operationId
|
|
631
|
+
warn(`No files generated for operation id: ${payload.operationId}`)
|
|
436
632
|
return
|
|
437
633
|
}
|
|
438
634
|
|
|
439
|
-
const
|
|
440
|
-
for (const file of files) {
|
|
441
|
-
const fileWrittenEvent = {
|
|
442
|
-
__typename: 'OneGraphNetlifyCliSessionFileWrittenEvent',
|
|
443
|
-
cliSessionId: sessionId,
|
|
444
|
-
payload: {
|
|
445
|
-
editor: config.editor,
|
|
446
|
-
filepath: file.filePath,
|
|
447
|
-
audience: 'ui',
|
|
448
|
-
},
|
|
449
|
-
}
|
|
635
|
+
const editor = process.env.EDITOR || null
|
|
450
636
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
637
|
+
/** @type {CliEventHelper.OneGraphNetlifyCliSessionFilesWrittenEvent} */
|
|
638
|
+
const filesWrittenEvent = {
|
|
639
|
+
id: crypto.randomUUID(),
|
|
640
|
+
createdAt: new Date().toString(),
|
|
641
|
+
__typename: 'OneGraphNetlifyCliSessionFilesWrittenEvent',
|
|
642
|
+
sessionId,
|
|
643
|
+
payload: {
|
|
644
|
+
editor,
|
|
645
|
+
// @ts-expect-error
|
|
646
|
+
files: files.map((file) => ({
|
|
647
|
+
name: file.name,
|
|
648
|
+
filePath: file.filePath,
|
|
649
|
+
})),
|
|
650
|
+
},
|
|
651
|
+
audience: 'UI',
|
|
464
652
|
}
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
const graphJwt = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
656
|
+
|
|
657
|
+
await OneGraphClient.executeCreateCLISessionEventMutation(
|
|
658
|
+
{
|
|
659
|
+
sessionId,
|
|
660
|
+
payload: filesWrittenEvent,
|
|
661
|
+
},
|
|
662
|
+
{ accessToken: graphJwt.jwt },
|
|
663
|
+
)
|
|
664
|
+
} catch {
|
|
665
|
+
warn(`Unable to reach Netlify Graph servers in order to notify handler files written to disk`)
|
|
666
|
+
}
|
|
667
|
+
|
|
465
668
|
break
|
|
466
669
|
}
|
|
467
670
|
case 'OneGraphNetlifyCliSessionOpenFileEvent': {
|
|
468
|
-
if (!payload.filePath) {
|
|
469
|
-
warn(`No filePath found in payload, ${JSON.stringify(payload, null, 2)}`)
|
|
671
|
+
if (!event.payload.filePath) {
|
|
672
|
+
warn(`No filePath found in payload, ${JSON.stringify(event.payload, null, 2)}`)
|
|
470
673
|
return
|
|
471
674
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
675
|
+
|
|
676
|
+
const editor = process.env.EDITOR || null
|
|
677
|
+
|
|
678
|
+
if (editor) {
|
|
679
|
+
log(`Opening ${editor} for ${event.payload.filePath}`)
|
|
680
|
+
execa(editor, [event.payload.filePath], {
|
|
476
681
|
preferLocal: true,
|
|
477
682
|
// windowsHide needs to be false for child process to terminate properly on Windows
|
|
478
683
|
windowsHide: false,
|
|
479
684
|
})
|
|
480
685
|
} else {
|
|
481
|
-
warn('No
|
|
686
|
+
warn('No $EDITOR set in env vars')
|
|
482
687
|
}
|
|
483
688
|
break
|
|
484
689
|
}
|
|
485
|
-
case '
|
|
690
|
+
case 'OneGraphNetlifyCliSessionSetGraphCodegenModuleEvent': {
|
|
691
|
+
setNetlifyTomlCodeGeneratorModule({ codegenModuleImportPath: event.payload.codegenModuleImportPath, siteRoot })
|
|
692
|
+
break
|
|
693
|
+
}
|
|
694
|
+
case 'OneGraphNetlifyCliSessionMetadataRequestEvent': {
|
|
695
|
+
const graphJwt = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
696
|
+
|
|
697
|
+
const { minimumCliVersionExpected } = event.payload
|
|
698
|
+
|
|
699
|
+
const cliIsOutOfDateForUI =
|
|
700
|
+
version.localeCompare(minimumCliVersionExpected, undefined, { numeric: true, sensitivity: 'base' }) === -1
|
|
701
|
+
|
|
702
|
+
if (cliIsOutOfDateForUI) {
|
|
703
|
+
warn(
|
|
704
|
+
`The Netlify Graph UI expects the netlify-cli to be at least at version "${minimumCliVersionExpected}", but you're running ${version}. You may need to upgrade for a stable experience.`,
|
|
705
|
+
)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const input = { config, docId, jwt: graphJwt.jwt, schemaId, sessionId, siteRoot }
|
|
709
|
+
await publishCliSessionMetadataPublishEvent(input)
|
|
710
|
+
break
|
|
711
|
+
}
|
|
712
|
+
case 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent': {
|
|
713
|
+
const { payload } = event
|
|
714
|
+
|
|
715
|
+
if (!payload.schemaId || !payload.docId) {
|
|
716
|
+
warn(`Malformed library update event, missing schemaId or docId in payload:
|
|
717
|
+
${JSON.stringify(event, null, 2)}`)
|
|
718
|
+
break
|
|
719
|
+
}
|
|
720
|
+
|
|
486
721
|
await handleOperationsLibraryPersistedEvent({
|
|
722
|
+
config,
|
|
487
723
|
netlifyToken,
|
|
488
724
|
docId: payload.docId,
|
|
489
725
|
schemaId: payload.schemaId,
|
|
@@ -492,16 +728,17 @@ const handleCliSessionEvent = async ({
|
|
|
492
728
|
siteId,
|
|
493
729
|
siteRoot,
|
|
494
730
|
})
|
|
731
|
+
|
|
495
732
|
break
|
|
733
|
+
}
|
|
496
734
|
default: {
|
|
497
735
|
warn(
|
|
498
|
-
`Unrecognized event received, you may need to upgrade your CLI version: ${__typename}: ${JSON.stringify(
|
|
499
|
-
|
|
736
|
+
`Unrecognized event received, you may need to upgrade your CLI version: ${event.__typename}: ${JSON.stringify(
|
|
737
|
+
event,
|
|
500
738
|
null,
|
|
501
739
|
2,
|
|
502
740
|
)}`,
|
|
503
741
|
)
|
|
504
|
-
break
|
|
505
742
|
}
|
|
506
743
|
}
|
|
507
744
|
}
|
|
@@ -531,7 +768,11 @@ const getCLISession = async ({ jwt, oneGraphSessionId, siteId }) => {
|
|
|
531
768
|
* @param {string} input.siteId The site object that contains the root file path for the site
|
|
532
769
|
*/
|
|
533
770
|
const getCLISessionMetadata = async ({ jwt, oneGraphSessionId, siteId }) => {
|
|
534
|
-
const
|
|
771
|
+
const result = await getCLISession({ jwt, oneGraphSessionId, siteId })
|
|
772
|
+
if (!result) {
|
|
773
|
+
warn(`Unable to fetch CLI session metadata`)
|
|
774
|
+
}
|
|
775
|
+
const { errors, session } = result
|
|
535
776
|
return { metadata: session && session.metadata, errors }
|
|
536
777
|
}
|
|
537
778
|
|
|
@@ -539,18 +780,50 @@ const getCLISessionMetadata = async ({ jwt, oneGraphSessionId, siteId }) => {
|
|
|
539
780
|
* Look at the current project, filesystem, etc. and determine relevant metadata for a cli session
|
|
540
781
|
* @param {object} input
|
|
541
782
|
* @param {string} input.siteRoot The root file path for the site
|
|
542
|
-
* @
|
|
783
|
+
* @param {object} input.config The parsed netlify.toml config file
|
|
784
|
+
* @returns {Promise<CliEventHelper.DetectedLocalCLISessionMetadata>} Any locally detected facts that are relevant to include in the cli session metadata
|
|
543
785
|
*/
|
|
544
|
-
const detectLocalCLISessionMetadata = ({ siteRoot }) => {
|
|
786
|
+
const detectLocalCLISessionMetadata = async ({ config, siteRoot }) => {
|
|
787
|
+
// @ts-ignore
|
|
788
|
+
const { listFrameworks } = await frameworkInfoPromise
|
|
789
|
+
|
|
790
|
+
/** @type {string | null} */
|
|
791
|
+
let framework = null
|
|
792
|
+
|
|
793
|
+
try {
|
|
794
|
+
const frameworks = await listFrameworks({ projectDir: siteRoot })
|
|
795
|
+
framework = frameworks[0].id || null
|
|
796
|
+
} catch {}
|
|
797
|
+
|
|
545
798
|
const { branch } = gitRepoInfo()
|
|
546
799
|
const hostname = os.hostname()
|
|
547
800
|
const userInfo = os.userInfo({ encoding: 'utf-8' })
|
|
548
801
|
const { username } = userInfo
|
|
549
|
-
const cliVersion =
|
|
802
|
+
const cliVersion = version
|
|
803
|
+
|
|
804
|
+
const platform = WSL ? 'wsl' : os.platform()
|
|
805
|
+
const arch = os.arch() === 'ia32' ? 'x86' : os.arch()
|
|
806
|
+
|
|
807
|
+
const editor = process.env.EDITOR || null
|
|
550
808
|
|
|
551
|
-
|
|
809
|
+
/** @type {CodegenHelpers.CodegenModuleMeta | null} */
|
|
810
|
+
let codegen = null
|
|
552
811
|
|
|
553
|
-
const
|
|
812
|
+
const codegenModule = await getCodegenModule({ config })
|
|
813
|
+
|
|
814
|
+
if (codegenModule) {
|
|
815
|
+
codegen = {
|
|
816
|
+
id: codegenModule.id,
|
|
817
|
+
version: codegenModule.id,
|
|
818
|
+
generators: codegenModule.generators.map((generator) => ({
|
|
819
|
+
id: generator.id,
|
|
820
|
+
name: generator.name,
|
|
821
|
+
options: generator.generateHandlerOptions || null,
|
|
822
|
+
supportedDefinitionTypes: generator.supportedDefinitionTypes,
|
|
823
|
+
version: generator.version,
|
|
824
|
+
})),
|
|
825
|
+
}
|
|
826
|
+
}
|
|
554
827
|
|
|
555
828
|
const detectedMetadata = {
|
|
556
829
|
gitBranch: branch,
|
|
@@ -559,15 +832,68 @@ const detectLocalCLISessionMetadata = ({ siteRoot }) => {
|
|
|
559
832
|
siteRoot,
|
|
560
833
|
cliVersion,
|
|
561
834
|
editor,
|
|
835
|
+
platform,
|
|
836
|
+
arch,
|
|
837
|
+
nodeVersion: process.version,
|
|
838
|
+
codegen,
|
|
839
|
+
framework,
|
|
562
840
|
}
|
|
563
841
|
|
|
564
842
|
return detectedMetadata
|
|
565
843
|
}
|
|
566
844
|
|
|
845
|
+
/**
|
|
846
|
+
*
|
|
847
|
+
* @param {object} input
|
|
848
|
+
* @param {string} input.jwt The GraphJWT string
|
|
849
|
+
* @param {string} input.sessionId The id of the cli session to fetch the current metadata for
|
|
850
|
+
* @param {object} input.config The parsed netlify.toml config file
|
|
851
|
+
* @param {string} input.siteRoot Path to the root of the project
|
|
852
|
+
* @param {string} input.docId The GraphQL operations document id to fetch
|
|
853
|
+
* @param {string} input.schemaId The GraphQL schemaId to use when generating code
|
|
854
|
+
*/
|
|
855
|
+
const publishCliSessionMetadataPublishEvent = async ({ config, docId, jwt, schemaId, sessionId, siteRoot }) => {
|
|
856
|
+
const detectedMetadata = await detectLocalCLISessionMetadata({ config, siteRoot })
|
|
857
|
+
|
|
858
|
+
/** @type {CliEventHelper.OneGraphNetlifyCliSessionMetadataPublishEvent} */
|
|
859
|
+
const event = {
|
|
860
|
+
__typename: 'OneGraphNetlifyCliSessionMetadataPublishEvent',
|
|
861
|
+
audience: 'UI',
|
|
862
|
+
createdAt: new Date().toString(),
|
|
863
|
+
id: crypto.randomUUID(),
|
|
864
|
+
sessionId,
|
|
865
|
+
payload: {
|
|
866
|
+
cliVersion: detectedMetadata.cliVersion,
|
|
867
|
+
editor: detectedMetadata.editor,
|
|
868
|
+
siteRoot: detectedMetadata.siteRoot,
|
|
869
|
+
siteRootFriendly: detectedMetadata.siteRoot,
|
|
870
|
+
persistedDocId: docId,
|
|
871
|
+
schemaId,
|
|
872
|
+
codegenModule: detectedMetadata.codegen,
|
|
873
|
+
arch: detectedMetadata.arch,
|
|
874
|
+
nodeVersion: detectedMetadata.nodeVersion,
|
|
875
|
+
platform: detectedMetadata.platform,
|
|
876
|
+
framework: detectedMetadata.framework,
|
|
877
|
+
},
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
try {
|
|
881
|
+
await OneGraphClient.executeCreateCLISessionEventMutation(
|
|
882
|
+
{
|
|
883
|
+
sessionId,
|
|
884
|
+
payload: event,
|
|
885
|
+
},
|
|
886
|
+
{ accessToken: jwt },
|
|
887
|
+
)
|
|
888
|
+
} catch {
|
|
889
|
+
warn(`Unable to reach Netlify Graph servers in order to publish CLI session data for the Graph UI`)
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
567
893
|
/**
|
|
568
894
|
* Fetch the existing cli session metadata if it exists, and mutate it remotely with the passed in metadata
|
|
569
895
|
* @param {object} input
|
|
570
|
-
* @param {
|
|
896
|
+
* @param {object} input.config The parsed netlify.toml file
|
|
571
897
|
* @param {string} input.jwt The Graph JWT string
|
|
572
898
|
* @param {string} input.oneGraphSessionId The id of the cli session to fetch the current metadata for
|
|
573
899
|
* @param {string} input.siteId The site object that contains the root file path for the site
|
|
@@ -575,21 +901,24 @@ const detectLocalCLISessionMetadata = ({ siteRoot }) => {
|
|
|
575
901
|
* @param {object} input.newMetadata The metadata to merge into (with priority) the existing metadata
|
|
576
902
|
* @returns {Promise<object>}
|
|
577
903
|
*/
|
|
578
|
-
const upsertMergeCLISessionMetadata = async ({ jwt, newMetadata, oneGraphSessionId, siteId, siteRoot }) => {
|
|
904
|
+
const upsertMergeCLISessionMetadata = async ({ config, jwt, newMetadata, oneGraphSessionId, siteId, siteRoot }) => {
|
|
579
905
|
const { errors, metadata } = await getCLISessionMetadata({ jwt, oneGraphSessionId, siteId })
|
|
580
906
|
if (errors) {
|
|
581
907
|
warn(`Error fetching cli session metadata: ${JSON.stringify(errors, null, 2)}`)
|
|
582
908
|
}
|
|
583
909
|
|
|
584
|
-
const detectedMetadata = detectLocalCLISessionMetadata({ siteRoot })
|
|
910
|
+
const detectedMetadata = await detectLocalCLISessionMetadata({ config, siteRoot })
|
|
585
911
|
|
|
586
912
|
// @ts-ignore
|
|
587
913
|
const finalMetadata = { ...metadata, ...detectedMetadata, ...newMetadata }
|
|
588
914
|
|
|
589
|
-
|
|
915
|
+
const result = OneGraphClient.updateCLISessionMetadata(jwt, siteId, oneGraphSessionId, finalMetadata)
|
|
916
|
+
|
|
917
|
+
return result
|
|
590
918
|
}
|
|
591
919
|
|
|
592
920
|
const persistNewOperationsDocForSession = async ({
|
|
921
|
+
config,
|
|
593
922
|
netlifyGraphConfig,
|
|
594
923
|
netlifyToken,
|
|
595
924
|
oneGraphSessionId,
|
|
@@ -650,9 +979,11 @@ const persistNewOperationsDocForSession = async ({
|
|
|
650
979
|
warn(`Failed to create persisted query for editing, ${JSON.stringify(persistedResult, null, 2)}`)
|
|
651
980
|
}
|
|
652
981
|
|
|
982
|
+
currentPersistedDocId = persistedDoc.id
|
|
983
|
+
|
|
653
984
|
const newMetadata = { docId: persistedDoc.id }
|
|
654
985
|
const result = await upsertMergeCLISessionMetadata({
|
|
655
|
-
|
|
986
|
+
config,
|
|
656
987
|
jwt,
|
|
657
988
|
siteId,
|
|
658
989
|
oneGraphSessionId,
|
|
@@ -660,12 +991,13 @@ const persistNewOperationsDocForSession = async ({
|
|
|
660
991
|
siteRoot,
|
|
661
992
|
})
|
|
662
993
|
|
|
663
|
-
if (result.errors) {
|
|
994
|
+
if (!result || result.errors) {
|
|
664
995
|
warn(`Unable to update session metadata with updated operations doc ${JSON.stringify(result.errors, null, 2)}`)
|
|
665
996
|
} else if (lockfile != null) {
|
|
666
997
|
// Now that we've persisted the document, lock it in the lockfile
|
|
667
998
|
const currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
668
999
|
|
|
1000
|
+
/** @type {NetlifyGraphLockfile.V0_format} */
|
|
669
1001
|
const newLockfile = NetlifyGraphLockfile.createLockfile({
|
|
670
1002
|
schemaId: lockfile.locked.schemaId,
|
|
671
1003
|
operationsFileContents: currentOperationsDoc,
|
|
@@ -690,6 +1022,7 @@ const loadCLISession = (state) => state.get('oneGraphSessionId')
|
|
|
690
1022
|
/**
|
|
691
1023
|
* Idemponentially save the CLI session id to the local state and start monitoring for CLI events, upstream schema changes, and local operation file changes
|
|
692
1024
|
* @param {object} input
|
|
1025
|
+
* @param {object} input.config
|
|
693
1026
|
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
694
1027
|
* @param {string | undefined} input.oneGraphSessionId The session ID to use for this CLI session (default: read from state)
|
|
695
1028
|
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
@@ -697,19 +1030,50 @@ const loadCLISession = (state) => state.get('oneGraphSessionId')
|
|
|
697
1030
|
* @param {any} input.site The site object
|
|
698
1031
|
*/
|
|
699
1032
|
const startOneGraphCLISession = async (input) => {
|
|
700
|
-
const { netlifyGraphConfig, netlifyToken, site, state } = input
|
|
701
|
-
const
|
|
702
|
-
|
|
1033
|
+
const { config, netlifyGraphConfig, netlifyToken, site, state } = input
|
|
1034
|
+
const getJwt = async () => {
|
|
1035
|
+
const accessToken = await OneGraphClient.getGraphJwtForSite({ siteId: site.id, nfToken: netlifyToken })
|
|
1036
|
+
return accessToken.jwt
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
OneGraphClient.ensureAppForSite(await getJwt(), site.id)
|
|
703
1040
|
|
|
704
1041
|
const oneGraphSessionId = await ensureCLISession({
|
|
1042
|
+
config,
|
|
1043
|
+
netlifyGraphConfig,
|
|
705
1044
|
metadata: {},
|
|
706
1045
|
netlifyToken,
|
|
707
1046
|
site,
|
|
708
1047
|
state,
|
|
709
1048
|
oneGraphSessionId: input.oneGraphSessionId,
|
|
710
|
-
netlifyGraphConfig,
|
|
711
1049
|
})
|
|
712
1050
|
|
|
1051
|
+
const syncUIHelper = async () => {
|
|
1052
|
+
const schemaId = readSchemaIdFromLockfile({ siteRoot: site.root })
|
|
1053
|
+
|
|
1054
|
+
if (!schemaId) {
|
|
1055
|
+
warn('Unable to load schemaId from Netlify Graph lockfile, run graph:pull to update')
|
|
1056
|
+
return
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (!currentPersistedDocId) {
|
|
1060
|
+
warn('Unable to read current persisted Graph library doc id')
|
|
1061
|
+
return
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const syncSessionMetadataInput = {
|
|
1065
|
+
config,
|
|
1066
|
+
docId: currentPersistedDocId,
|
|
1067
|
+
jwt: await getJwt(),
|
|
1068
|
+
schemaId,
|
|
1069
|
+
sessionId: oneGraphSessionId,
|
|
1070
|
+
siteRoot: site.root,
|
|
1071
|
+
}
|
|
1072
|
+
await publishCliSessionMetadataPublishEvent(syncSessionMetadataInput)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
await syncUIHelper()
|
|
1076
|
+
|
|
713
1077
|
const enabledServices = []
|
|
714
1078
|
const schema = await OneGraphClient.fetchOneGraphSchemaForServices(site.id, enabledServices)
|
|
715
1079
|
|
|
@@ -717,9 +1081,22 @@ const startOneGraphCLISession = async (input) => {
|
|
|
717
1081
|
netlifyGraphConfig,
|
|
718
1082
|
onChange: async (filePath) => {
|
|
719
1083
|
log('NetlifyGraph operation file changed at', filePath, 'updating function library...')
|
|
720
|
-
|
|
1084
|
+
if (!schema) {
|
|
1085
|
+
warn('Unable to load schema, run graph:pull to update')
|
|
1086
|
+
return
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const schemaId = readSchemaIdFromLockfile({ siteRoot: site.root })
|
|
1090
|
+
|
|
1091
|
+
if (!schemaId) {
|
|
1092
|
+
warn('Unable to load schemaId from Netlify Graph lockfile, run graph:pull to update')
|
|
1093
|
+
return
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
regenerateFunctionsFileFromOperationsFile({ config, netlifyGraphConfig, schema, schemaId })
|
|
721
1097
|
const newOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
722
1098
|
await persistNewOperationsDocForSession({
|
|
1099
|
+
config,
|
|
723
1100
|
netlifyGraphConfig,
|
|
724
1101
|
netlifyToken,
|
|
725
1102
|
oneGraphSessionId,
|
|
@@ -730,9 +1107,22 @@ const startOneGraphCLISession = async (input) => {
|
|
|
730
1107
|
},
|
|
731
1108
|
onAdd: async (filePath) => {
|
|
732
1109
|
log('NetlifyGraph operation file created at', filePath, 'creating function library...')
|
|
733
|
-
|
|
1110
|
+
if (!schema) {
|
|
1111
|
+
warn('Unable to load schema, run graph:pull to update')
|
|
1112
|
+
return
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const schemaId = readSchemaIdFromLockfile({ siteRoot: site.root })
|
|
1116
|
+
|
|
1117
|
+
if (!schemaId) {
|
|
1118
|
+
warn('Unable to load schemaId from Netlify Graph lockfile, run graph:pull to update')
|
|
1119
|
+
return
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
regenerateFunctionsFileFromOperationsFile({ config, netlifyGraphConfig, schema, schemaId })
|
|
734
1123
|
const newOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
735
1124
|
await persistNewOperationsDocForSession({
|
|
1125
|
+
config,
|
|
736
1126
|
netlifyGraphConfig,
|
|
737
1127
|
netlifyToken,
|
|
738
1128
|
oneGraphSessionId,
|
|
@@ -744,6 +1134,7 @@ const startOneGraphCLISession = async (input) => {
|
|
|
744
1134
|
})
|
|
745
1135
|
|
|
746
1136
|
const cliEventsCloseFn = monitorCLISessionEvents({
|
|
1137
|
+
config,
|
|
747
1138
|
appId: site.id,
|
|
748
1139
|
netlifyToken,
|
|
749
1140
|
netlifyGraphConfig,
|
|
@@ -751,26 +1142,56 @@ const startOneGraphCLISession = async (input) => {
|
|
|
751
1142
|
site,
|
|
752
1143
|
state,
|
|
753
1144
|
onClose: () => {
|
|
754
|
-
log('CLI session closed, stopping
|
|
1145
|
+
log('CLI session closed, stopping monitor...')
|
|
1146
|
+
},
|
|
1147
|
+
onSchemaIdChange: (newSchemaId) => {
|
|
1148
|
+
log('Netlify Graph schemaId changed:', newSchemaId)
|
|
755
1149
|
},
|
|
756
1150
|
onEvents: async (events) => {
|
|
757
1151
|
const ackEventIds = []
|
|
758
1152
|
|
|
759
1153
|
for (const event of events) {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1154
|
+
try {
|
|
1155
|
+
const audience = event.audience || OneGraphClient.eventAudience(event)
|
|
1156
|
+
if (audience === 'CLI') {
|
|
1157
|
+
ackEventIds.push(event.id)
|
|
1158
|
+
const eventName = OneGraphClient.friendlyEventName(event)
|
|
1159
|
+
log(`${chalk.magenta('Handling')} Netlify Graph: ${eventName}...`)
|
|
1160
|
+
const schemaId = readSchemaIdFromLockfile({ siteRoot: site.root })
|
|
1161
|
+
|
|
1162
|
+
if (!schemaId) {
|
|
1163
|
+
warn('Unable to load schemaId from Netlify Graph lockfile, run graph:pull to update')
|
|
1164
|
+
return
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
if (!schema) {
|
|
1168
|
+
warn('Unable to load schema from for Netlify Graph, run graph:pull to update')
|
|
1169
|
+
return
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
if (!currentPersistedDocId) {
|
|
1173
|
+
warn('Unable to read current persisted Graph library doc id')
|
|
1174
|
+
return
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
await handleCliSessionEvent({
|
|
1178
|
+
config,
|
|
1179
|
+
docId: currentPersistedDocId,
|
|
1180
|
+
netlifyToken,
|
|
1181
|
+
event,
|
|
1182
|
+
netlifyGraphConfig,
|
|
1183
|
+
schema,
|
|
1184
|
+
schemaId,
|
|
1185
|
+
sessionId: oneGraphSessionId,
|
|
1186
|
+
site,
|
|
1187
|
+
siteId: site.id,
|
|
1188
|
+
siteRoot: site.root,
|
|
1189
|
+
})
|
|
1190
|
+
log(`${chalk.green('Finished handling')} Netlify Graph: ${eventName}...`)
|
|
1191
|
+
}
|
|
1192
|
+
} catch (error_) {
|
|
1193
|
+
warn(`Error processing individual Netlify Graph event, skipping:
|
|
1194
|
+
${JSON.stringify(error_, null, 2)}`)
|
|
774
1195
|
ackEventIds.push(event.id)
|
|
775
1196
|
}
|
|
776
1197
|
}
|
|
@@ -799,7 +1220,7 @@ const startOneGraphCLISession = async (input) => {
|
|
|
799
1220
|
const markCliSessionInactive = async ({ netlifyToken, sessionId, siteId }) => {
|
|
800
1221
|
const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
801
1222
|
const result = await OneGraphClient.executeMarkCliSessionInactive(jwt, siteId, sessionId)
|
|
802
|
-
if (result.errors) {
|
|
1223
|
+
if (!result || result.errors) {
|
|
803
1224
|
warn(`Unable to mark CLI session ${sessionId} inactive: ${JSON.stringify(result.errors, null, 2)}`)
|
|
804
1225
|
}
|
|
805
1226
|
}
|
|
@@ -843,6 +1264,7 @@ const idempotentlyUpdateSessionSchemaIdFromLockfile = async (input) => {
|
|
|
843
1264
|
/**
|
|
844
1265
|
* Ensures a cli session exists for the current checkout, or errors out if it doesn't and cannot create one.
|
|
845
1266
|
* @param {object} input
|
|
1267
|
+
* @param {object} input.config The parsed netlify.toml config file
|
|
846
1268
|
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
847
1269
|
* @param {object} input.metadata
|
|
848
1270
|
* @param {string} input.netlifyToken
|
|
@@ -851,7 +1273,7 @@ const idempotentlyUpdateSessionSchemaIdFromLockfile = async (input) => {
|
|
|
851
1273
|
* @param {any} input.site The site object
|
|
852
1274
|
*/
|
|
853
1275
|
const ensureCLISession = async (input) => {
|
|
854
|
-
const { metadata, netlifyGraphConfig, netlifyToken, site, state } = input
|
|
1276
|
+
const { config, metadata, netlifyGraphConfig, netlifyToken, site, state } = input
|
|
855
1277
|
let oneGraphSessionId = input.oneGraphSessionId ? input.oneGraphSessionId : loadCLISession(state)
|
|
856
1278
|
let parentCliSessionId = null
|
|
857
1279
|
const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId: site.id, nfToken: netlifyToken })
|
|
@@ -867,6 +1289,7 @@ const ensureCLISession = async (input) => {
|
|
|
867
1289
|
sessionId: oneGraphSessionId,
|
|
868
1290
|
desiredEventCount: 0,
|
|
869
1291
|
})
|
|
1292
|
+
|
|
870
1293
|
if (errors) {
|
|
871
1294
|
warn(`Unable to fetch cli session: ${JSON.stringify(errors, null, 2)}`)
|
|
872
1295
|
log(`Creating new cli session`)
|
|
@@ -874,6 +1297,10 @@ const ensureCLISession = async (input) => {
|
|
|
874
1297
|
oneGraphSessionId = null
|
|
875
1298
|
}
|
|
876
1299
|
|
|
1300
|
+
if (session && session.metadata && session.metadata.docId) {
|
|
1301
|
+
currentPersistedDocId = session.metadata.docId
|
|
1302
|
+
}
|
|
1303
|
+
|
|
877
1304
|
// During the transition to lockfiles, write a lockfile if one isn't
|
|
878
1305
|
// found. Later, only handling a 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent'
|
|
879
1306
|
// will create or update the lockfile
|
|
@@ -896,10 +1323,22 @@ const ensureCLISession = async (input) => {
|
|
|
896
1323
|
oneGraphSessionId = null
|
|
897
1324
|
}
|
|
898
1325
|
|
|
899
|
-
if (
|
|
1326
|
+
if (oneGraphSessionId) {
|
|
1327
|
+
await upsertMergeCLISessionMetadata({
|
|
1328
|
+
jwt,
|
|
1329
|
+
config,
|
|
1330
|
+
newMetadata: {},
|
|
1331
|
+
oneGraphSessionId,
|
|
1332
|
+
siteId: site.id,
|
|
1333
|
+
siteRoot: site.root,
|
|
1334
|
+
})
|
|
1335
|
+
} else {
|
|
900
1336
|
// If we can't access the session in the state.json or it doesn't exist, create a new one
|
|
901
1337
|
const sessionName = generateSessionName()
|
|
902
|
-
const detectedMetadata = detectLocalCLISessionMetadata({
|
|
1338
|
+
const detectedMetadata = await detectLocalCLISessionMetadata({
|
|
1339
|
+
config,
|
|
1340
|
+
siteRoot: site.root,
|
|
1341
|
+
})
|
|
903
1342
|
const newSessionMetadata = parentCliSessionId ? { parentCliSessionId } : {}
|
|
904
1343
|
|
|
905
1344
|
const sessionMetadata = {
|
|
@@ -920,7 +1359,12 @@ const ensureCLISession = async (input) => {
|
|
|
920
1359
|
metadata: sessionMetadata,
|
|
921
1360
|
})
|
|
922
1361
|
|
|
923
|
-
|
|
1362
|
+
if (oneGraphSession) {
|
|
1363
|
+
// @ts-expect-error
|
|
1364
|
+
oneGraphSessionId = oneGraphSession.id
|
|
1365
|
+
} else {
|
|
1366
|
+
warn('Unable to load Netlify Graph session, please report this to Netlify support')
|
|
1367
|
+
}
|
|
924
1368
|
}
|
|
925
1369
|
|
|
926
1370
|
if (!oneGraphSessionId) {
|
|
@@ -946,6 +1390,7 @@ const OneGraphCliClient = {
|
|
|
946
1390
|
executeCreatePersistedQueryMutation: OneGraphClient.executeCreatePersistedQueryMutation,
|
|
947
1391
|
executeCreateApiTokenMutation: OneGraphClient.executeCreateApiTokenMutation,
|
|
948
1392
|
fetchCliSessionEvents: OneGraphClient.fetchCliSessionEvents,
|
|
1393
|
+
fetchCliSessionSchema,
|
|
949
1394
|
ensureAppForSite: OneGraphClient.ensureAppForSite,
|
|
950
1395
|
updateCLISessionMetadata: OneGraphClient.updateCLISessionMetadata,
|
|
951
1396
|
getGraphJwtForSite: OneGraphClient.getGraphJwtForSite,
|
|
@@ -954,6 +1399,7 @@ const OneGraphCliClient = {
|
|
|
954
1399
|
module.exports = {
|
|
955
1400
|
OneGraphCliClient,
|
|
956
1401
|
createCLISession,
|
|
1402
|
+
currentPersistedDocId,
|
|
957
1403
|
ensureCLISession,
|
|
958
1404
|
extractFunctionsFromOperationDoc,
|
|
959
1405
|
handleCliSessionEvent,
|
|
@@ -962,6 +1408,7 @@ module.exports = {
|
|
|
962
1408
|
markCliSessionInactive,
|
|
963
1409
|
monitorCLISessionEvents,
|
|
964
1410
|
persistNewOperationsDocForSession,
|
|
1411
|
+
readSchemaIdFromLockfile,
|
|
965
1412
|
refetchAndGenerateFromOneGraph,
|
|
966
1413
|
startOneGraphCLISession,
|
|
967
1414
|
upsertMergeCLISessionMetadata,
|