netlify-cli 14.4.0 → 15.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/README.md +0 -16
- package/npm-shrinkwrap.json +2 -63
- package/package.json +1 -2
- package/src/commands/build/build.mjs +1 -24
- package/src/commands/dev/dev.mjs +1 -26
- package/src/commands/main.mjs +0 -2
- package/src/commands/serve/serve.mjs +1 -1
- package/src/lib/edge-functions/proxy.mjs +9 -1
- package/src/lib/functions/server.mjs +1 -10
- package/src/utils/dev.mjs +0 -22
- package/src/commands/graph/graph-config-write.mjs +0 -56
- package/src/commands/graph/graph-edit.mjs +0 -104
- package/src/commands/graph/graph-handler.mjs +0 -102
- package/src/commands/graph/graph-init.mjs +0 -163
- package/src/commands/graph/graph-library.mjs +0 -88
- package/src/commands/graph/graph-operations.mjs +0 -116
- package/src/commands/graph/graph-pull.mjs +0 -168
- package/src/commands/graph/graph.mjs +0 -45
- package/src/commands/graph/index.mjs +0 -1
- package/src/lib/one-graph/cli-client.mjs +0 -1392
- package/src/lib/one-graph/cli-netlify-graph.mjs +0 -1033
- package/src/utils/graph.mjs +0 -170
|
@@ -1,1392 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
/* eslint-disable eslint-comments/disable-enable-pair */
|
|
3
|
-
/* eslint-disable fp/no-loops */
|
|
4
|
-
/* eslint-disable no-underscore-dangle */
|
|
5
|
-
import crypto from 'crypto'
|
|
6
|
-
import { readFileSync, writeFileSync } from 'fs'
|
|
7
|
-
import os from 'os'
|
|
8
|
-
import path from 'path'
|
|
9
|
-
import process from 'process'
|
|
10
|
-
|
|
11
|
-
import { listFrameworks } from '@netlify/framework-info'
|
|
12
|
-
import gitRepoInfo from 'git-repo-info'
|
|
13
|
-
import WSL from 'is-wsl'
|
|
14
|
-
import { GraphQL, InternalConsole, NetlifyGraph, NetlifyGraphLockfile, OneGraphClient } from 'netlify-onegraph-internal'
|
|
15
|
-
|
|
16
|
-
import { chalk, error, log, warn, watchDebounced } from '../../utils/command-helpers.mjs'
|
|
17
|
-
import execa from '../../utils/execa.mjs'
|
|
18
|
-
import getPackageJson from '../../utils/get-package-json.mjs'
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
generateFunctionsFile,
|
|
22
|
-
generateHandlerByOperationId,
|
|
23
|
-
getCodegenFunctionById,
|
|
24
|
-
getCodegenModule,
|
|
25
|
-
normalizeOperationsDoc,
|
|
26
|
-
readGraphQLOperationsSourceFile,
|
|
27
|
-
setNetlifyTomlCodeGeneratorModule,
|
|
28
|
-
writeGraphQLOperationsSourceFile,
|
|
29
|
-
writeGraphQLSchemaFile,
|
|
30
|
-
} from './cli-netlify-graph.mjs'
|
|
31
|
-
|
|
32
|
-
const { parse } = GraphQL
|
|
33
|
-
const { defaultExampleOperationsDoc, extractFunctionsFromOperationDoc } = NetlifyGraph
|
|
34
|
-
|
|
35
|
-
const { version } = await getPackageJson()
|
|
36
|
-
|
|
37
|
-
const internalConsole = {
|
|
38
|
-
log,
|
|
39
|
-
warn,
|
|
40
|
-
error,
|
|
41
|
-
debug: console.debug,
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** @type {string | null} */
|
|
45
|
-
// eslint-disable-next-line import/no-mutable-exports
|
|
46
|
-
let currentPersistedDocId = null
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Keep track of which document hashes we've received from the server so we can ignore events from the filesystem based on them
|
|
50
|
-
*/
|
|
51
|
-
const witnessedIncomingDocumentHashes = []
|
|
52
|
-
|
|
53
|
-
InternalConsole.registerConsole(internalConsole)
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Start polling for CLI events for a given session to process locally
|
|
57
|
-
* @param {object} input
|
|
58
|
-
* @param {string} input.appId The app to query against, typically the siteId
|
|
59
|
-
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
60
|
-
* @param {object} input.config The parsed netlify.toml file
|
|
61
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
62
|
-
* @param {function} input.onClose A function to call when the polling loop is closed
|
|
63
|
-
* @param {function} input.onError A function to call when an error occurs
|
|
64
|
-
* @param {function} input.onEvents A function to call when CLI events are received and need to be processed
|
|
65
|
-
* @param {function} input.onSchemaIdChange A function to call when the CLI schemaId has changed
|
|
66
|
-
* @param {string} input.sessionId The session id to monitor CLI events for
|
|
67
|
-
* @param {import('../../utils/state-config.mjs').default} input.state A function to call to set/get the current state of the local Netlify project
|
|
68
|
-
* @param {any} input.site The site object
|
|
69
|
-
* @returns
|
|
70
|
-
*/
|
|
71
|
-
export const monitorCLISessionEvents = (input) => {
|
|
72
|
-
const { appId, config, netlifyGraphConfig, netlifyToken, onClose, onError, onEvents, site, state } = input
|
|
73
|
-
const currentSessionId = input.sessionId
|
|
74
|
-
// TODO (sg): Track changing schemaId for a session
|
|
75
|
-
|
|
76
|
-
const frequency = 5000
|
|
77
|
-
// 30 minutes
|
|
78
|
-
const defaultHeartbeatFrequency = 30_000
|
|
79
|
-
let shouldClose = false
|
|
80
|
-
let nextMarkActiveHeartbeat = defaultHeartbeatFrequency
|
|
81
|
-
|
|
82
|
-
const markActiveHelper = async () => {
|
|
83
|
-
try {
|
|
84
|
-
const graphJwt = await OneGraphClient.getGraphJwtForSite({ siteId: appId, nfToken: netlifyToken })
|
|
85
|
-
const fullSession = await OneGraphClient.fetchCliSession({
|
|
86
|
-
jwt: graphJwt.jwt,
|
|
87
|
-
appId,
|
|
88
|
-
sessionId: currentSessionId,
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const heartbeatIntervalms = fullSession.session.cliHeartbeatIntervalMs || defaultHeartbeatFrequency
|
|
92
|
-
nextMarkActiveHeartbeat = heartbeatIntervalms
|
|
93
|
-
|
|
94
|
-
const markCLISessionActiveResult = await OneGraphClient.executeMarkCliSessionActiveHeartbeat(
|
|
95
|
-
graphJwt.jwt,
|
|
96
|
-
site.id,
|
|
97
|
-
currentSessionId,
|
|
98
|
-
)
|
|
99
|
-
if (markCLISessionActiveResult.errors && markCLISessionActiveResult.errors.length !== 0) {
|
|
100
|
-
warn(`Failed to mark CLI session active: ${markCLISessionActiveResult.errors.join(', ')}`)
|
|
101
|
-
}
|
|
102
|
-
} catch {
|
|
103
|
-
warn(`Unable to reach Netlify Graph servers in order to mark CLI session active`)
|
|
104
|
-
}
|
|
105
|
-
setTimeout(markActiveHelper, nextMarkActiveHeartbeat)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
setTimeout(markActiveHelper, nextMarkActiveHeartbeat)
|
|
109
|
-
|
|
110
|
-
const enabledServiceWatcher = async (jwt, { appId: siteId, sessionId }) => {
|
|
111
|
-
const enabledServices = state.get('oneGraphEnabledServices') || ['onegraph']
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const graphQLSchemaInfo = await OneGraphClient.fetchGraphQLSchemaForSession(jwt, siteId, input.sessionId)
|
|
115
|
-
if (!graphQLSchemaInfo) {
|
|
116
|
-
warn('Unable to fetch enabled services for site for code generation')
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
const newEnabledServices = graphQLSchemaInfo.services.map((service) => service.graphQLField)
|
|
120
|
-
const enabledServicesCompareKey = enabledServices.sort().join(',')
|
|
121
|
-
const newEnabledServicesCompareKey = newEnabledServices.sort().join(',')
|
|
122
|
-
|
|
123
|
-
if (enabledServicesCompareKey !== newEnabledServicesCompareKey) {
|
|
124
|
-
log(
|
|
125
|
-
`${chalk.magenta(
|
|
126
|
-
'Reloading',
|
|
127
|
-
)} Netlify Graph schema..., ${enabledServicesCompareKey} => ${newEnabledServicesCompareKey}`,
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
const schemaId = graphQLSchemaInfo.id
|
|
131
|
-
|
|
132
|
-
if (!schemaId) {
|
|
133
|
-
warn(`Unable to read schemaId from Netlify Graph session, not regenerating code`)
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
mergeLockfile({ siteRoot: site.root, schemaId })
|
|
138
|
-
|
|
139
|
-
await refetchAndGenerateFromOneGraph({ config, netlifyGraphConfig, state, jwt, schemaId, siteId, sessionId })
|
|
140
|
-
log(`${chalk.green('Reloaded')} Netlify Graph schema and regenerated functions`)
|
|
141
|
-
}
|
|
142
|
-
} catch {
|
|
143
|
-
warn(`Unable to reach Netlify Graph servers in order to fetch enabled Graph services`)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const close = () => {
|
|
148
|
-
shouldClose = true
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
let handle
|
|
152
|
-
|
|
153
|
-
const helper = async () => {
|
|
154
|
-
try {
|
|
155
|
-
if (shouldClose) {
|
|
156
|
-
clearTimeout(handle)
|
|
157
|
-
onClose && onClose()
|
|
158
|
-
return
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const graphJwt = await OneGraphClient.getGraphJwtForSite({ siteId: appId, nfToken: netlifyToken })
|
|
162
|
-
const next = await OneGraphClient.fetchCliSessionEvents({ appId, jwt: graphJwt.jwt, sessionId: currentSessionId })
|
|
163
|
-
|
|
164
|
-
if (next && next.errors) {
|
|
165
|
-
next.errors.forEach((fetchEventError) => {
|
|
166
|
-
onError(fetchEventError)
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const events = (next && next.events) || []
|
|
171
|
-
|
|
172
|
-
if (events.length !== 0) {
|
|
173
|
-
let ackIds = []
|
|
174
|
-
try {
|
|
175
|
-
ackIds = await onEvents(events)
|
|
176
|
-
} catch (eventHandlerError) {
|
|
177
|
-
warn(`Error handling event: ${eventHandlerError}`)
|
|
178
|
-
} finally {
|
|
179
|
-
await OneGraphClient.ackCLISessionEvents({
|
|
180
|
-
appId,
|
|
181
|
-
jwt: graphJwt.jwt,
|
|
182
|
-
sessionId: currentSessionId,
|
|
183
|
-
eventIds: ackIds,
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
await enabledServiceWatcher(graphJwt.jwt, { appId, sessionId: currentSessionId })
|
|
189
|
-
} catch {
|
|
190
|
-
warn(`Unable to reach Netlify Graph servers in order to sync Graph session`)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
handle = setTimeout(helper, frequency)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Fire immediately to start rather than waiting the initial `frequency`
|
|
197
|
-
helper()
|
|
198
|
-
|
|
199
|
-
return close
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Monitor the operations document for changes
|
|
204
|
-
* @param {object} input
|
|
205
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
206
|
-
* @param {() => void} input.onAdd A callback function to handle when the operations document is added
|
|
207
|
-
* @param {() => void} input.onChange A callback function to handle when the operations document is changed
|
|
208
|
-
* @param {() => void=} input.onUnlink A callback function to handle when the operations document is unlinked
|
|
209
|
-
* @returns {Promise<any>}
|
|
210
|
-
*/
|
|
211
|
-
const monitorOperationFile = async ({ netlifyGraphConfig, onAdd, onChange, onUnlink }) => {
|
|
212
|
-
if (!netlifyGraphConfig.graphQLOperationsSourceFilename) {
|
|
213
|
-
error('Please configure `graphQLOperationsSourceFilename` in your `netlify.toml` [graph] section')
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const filePath = path.resolve(...(netlifyGraphConfig.graphQLOperationsSourceFilename || []))
|
|
217
|
-
const newWatcher = await watchDebounced([filePath], {
|
|
218
|
-
depth: 1,
|
|
219
|
-
onAdd,
|
|
220
|
-
onChange,
|
|
221
|
-
onUnlink,
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
return newWatcher
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Fetch the schema for a site, and regenerate all of the downstream files
|
|
229
|
-
* @param {object} input
|
|
230
|
-
* @param {string} input.siteId The id of the site to query against
|
|
231
|
-
* @param {string} input.jwt The Graph JWT
|
|
232
|
-
* @param {string} input.sessionId The session ID for the current session
|
|
233
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
234
|
-
* @param {import('../../utils/state-config.mjs').default} input.state A function to call to set/get the current state of the local Netlify project
|
|
235
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
236
|
-
* @returns {Promise<Record<string, unknown> | undefined>}
|
|
237
|
-
*/
|
|
238
|
-
const fetchCliSessionSchema = async (input) => {
|
|
239
|
-
const { jwt, siteId } = input
|
|
240
|
-
|
|
241
|
-
await OneGraphClient.ensureAppForSite(jwt, siteId)
|
|
242
|
-
|
|
243
|
-
const schemaInfo = await OneGraphClient.fetchNetlifySessionSchemaQuery(
|
|
244
|
-
{ sessionId: input.sessionId },
|
|
245
|
-
{
|
|
246
|
-
accessToken: jwt,
|
|
247
|
-
siteId,
|
|
248
|
-
},
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
if (!schemaInfo) {
|
|
252
|
-
warn('Unable to fetch schema for session')
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
const schemaMetadata = schemaInfo.data.oneGraph.netlifyCliSession.graphQLSchema
|
|
258
|
-
return schemaMetadata
|
|
259
|
-
} catch {}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Fetch the schema for a site, and regenerate all of the downstream files
|
|
264
|
-
* @param {object} input
|
|
265
|
-
* @param {string} input.siteId The id of the site to query against
|
|
266
|
-
* @param {string} input.jwt The Graph JWT
|
|
267
|
-
* @param {object} input.config The parsed netlify.toml file
|
|
268
|
-
* @param {string} input.sessionId The session ID for the current session
|
|
269
|
-
* @param {string} input.schemaId The schemaId for the current session
|
|
270
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
271
|
-
* @param {import('../../utils/state-config.mjs').default} input.state A function to call to set/get the current state of the local Netlify project
|
|
272
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
273
|
-
* @returns {Promise<void>}
|
|
274
|
-
*/
|
|
275
|
-
export const refetchAndGenerateFromOneGraph = async (input) => {
|
|
276
|
-
const { config, jwt, logger, netlifyGraphConfig, schemaId, siteId, state } = input
|
|
277
|
-
|
|
278
|
-
await OneGraphClient.ensureAppForSite(jwt, siteId)
|
|
279
|
-
|
|
280
|
-
const graphQLSchemaInfo = await OneGraphClient.fetchGraphQLSchemaForSession(jwt, siteId, input.sessionId)
|
|
281
|
-
if (!graphQLSchemaInfo) {
|
|
282
|
-
warn('Unable to fetch schema info for site for code generation')
|
|
283
|
-
return
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const enabledServices = graphQLSchemaInfo.services
|
|
287
|
-
.map((service) => service.graphQLField)
|
|
288
|
-
.sort((aString, bString) => aString.localeCompare(bString))
|
|
289
|
-
|
|
290
|
-
const schema = await OneGraphClient.fetchOneGraphSchemaById({
|
|
291
|
-
siteId,
|
|
292
|
-
schemaId: graphQLSchemaInfo.id,
|
|
293
|
-
accessToken: jwt,
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
if (!schema) {
|
|
297
|
-
error('Unable to fetch schema from Netlify Graph')
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
301
|
-
if (currentOperationsDoc.trim().length === 0) {
|
|
302
|
-
currentOperationsDoc = defaultExampleOperationsDoc
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const parsedDoc = parse(currentOperationsDoc)
|
|
306
|
-
const { fragments, functions } = extractFunctionsFromOperationDoc(GraphQL, parsedDoc)
|
|
307
|
-
|
|
308
|
-
if (!schema) {
|
|
309
|
-
warn('Unable to parse schema, please run graph:pull to update')
|
|
310
|
-
return
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
await generateFunctionsFile({
|
|
314
|
-
config,
|
|
315
|
-
logger,
|
|
316
|
-
netlifyGraphConfig,
|
|
317
|
-
schema,
|
|
318
|
-
operationsDoc: currentOperationsDoc,
|
|
319
|
-
functions,
|
|
320
|
-
fragments,
|
|
321
|
-
schemaId,
|
|
322
|
-
})
|
|
323
|
-
writeGraphQLSchemaFile({ logger, netlifyGraphConfig, schema })
|
|
324
|
-
state.set('oneGraphEnabledServices', enabledServices)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Regenerate the function library based on the current operations document on disk
|
|
329
|
-
* @param {object} input
|
|
330
|
-
* @param {object} input.config The parsed netlify.toml file
|
|
331
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
|
|
332
|
-
* @param {string} input.schemaId The GraphQL schemaId to use when generating code
|
|
333
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
334
|
-
* @returns
|
|
335
|
-
*/
|
|
336
|
-
const regenerateFunctionsFileFromOperationsFile = (input) => {
|
|
337
|
-
const { config, netlifyGraphConfig, schema, schemaId } = input
|
|
338
|
-
|
|
339
|
-
const appOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
340
|
-
|
|
341
|
-
const hash = quickHash(appOperationsDoc)
|
|
342
|
-
|
|
343
|
-
if (witnessedIncomingDocumentHashes.includes(hash)) {
|
|
344
|
-
// We've already seen this document, so don't regenerate
|
|
345
|
-
return
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const parsedDoc = parse(appOperationsDoc, {
|
|
349
|
-
noLocation: true,
|
|
350
|
-
})
|
|
351
|
-
const { fragments, functions } = extractFunctionsFromOperationDoc(GraphQL, parsedDoc)
|
|
352
|
-
generateFunctionsFile({
|
|
353
|
-
config,
|
|
354
|
-
netlifyGraphConfig,
|
|
355
|
-
schema,
|
|
356
|
-
operationsDoc: appOperationsDoc,
|
|
357
|
-
functions,
|
|
358
|
-
fragments,
|
|
359
|
-
schemaId,
|
|
360
|
-
})
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Lockfile Operations
|
|
365
|
-
*/
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Read the Netlify Graph lockfile from disk, if it exists
|
|
369
|
-
* @param {object} input
|
|
370
|
-
* @param {string} input.siteRoot The GraphQL schema to use when generating code
|
|
371
|
-
* @return {NetlifyGraphLockfile.V0_format | undefined}
|
|
372
|
-
*/
|
|
373
|
-
export const readLockfile = ({ siteRoot }) => {
|
|
374
|
-
try {
|
|
375
|
-
const buf = readFileSync(path.join(siteRoot, NetlifyGraphLockfile.defaultLockFileName))
|
|
376
|
-
return JSON.parse(buf.toString('utf8'))
|
|
377
|
-
} catch {}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Persist the Netlify Graph lockfile on disk
|
|
382
|
-
* @param {object} input
|
|
383
|
-
* @param {string} input.siteRoot The GraphQL schema to use when generating code
|
|
384
|
-
* @param {NetlifyGraphLockfile.V0_format} input.lockfile
|
|
385
|
-
*/
|
|
386
|
-
const writeLockfile = ({ lockfile, siteRoot }) => {
|
|
387
|
-
writeFileSync(path.join(siteRoot, NetlifyGraphLockfile.defaultLockFileName), JSON.stringify(lockfile, null, 2))
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Persist the Netlify Graph lockfile on disk
|
|
392
|
-
* @param {object} input
|
|
393
|
-
* @param {string} input.siteRoot The GraphQL schema to use when generating code
|
|
394
|
-
* @param {string=} input.schemaId
|
|
395
|
-
* @param {string=} input.operationsHash
|
|
396
|
-
*/
|
|
397
|
-
const mergeLockfile = ({ operationsHash, schemaId, siteRoot }) => {
|
|
398
|
-
const lockfile = readLockfile({ siteRoot })
|
|
399
|
-
if (lockfile) {
|
|
400
|
-
/** @type {NetlifyGraphLockfile.V0_format} */
|
|
401
|
-
const newLockfile = {
|
|
402
|
-
...lockfile,
|
|
403
|
-
locked: {
|
|
404
|
-
...lockfile.locked,
|
|
405
|
-
},
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (operationsHash) {
|
|
409
|
-
newLockfile.locked.operationsHash = operationsHash
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (schemaId) {
|
|
413
|
-
newLockfile.locked.schemaId = schemaId
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
writeFileSync(path.join(siteRoot, NetlifyGraphLockfile.defaultLockFileName), JSON.stringify(newLockfile, null, 2))
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Read the Netlify Graph schemaId from the lockfile on disk, if it exists
|
|
422
|
-
* @param {object} input
|
|
423
|
-
* @param {string} input.siteRoot The GraphQL schema to use when generating code
|
|
424
|
-
* @return {string | undefined}
|
|
425
|
-
*/
|
|
426
|
-
export const readSchemaIdFromLockfile = ({ siteRoot }) => {
|
|
427
|
-
try {
|
|
428
|
-
const lockfile = readLockfile({ siteRoot })
|
|
429
|
-
return lockfile && lockfile.locked.schemaId
|
|
430
|
-
} catch {}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Compute a md5 hash of a string
|
|
435
|
-
* @param {string} input String to compute a quick md5 hash for
|
|
436
|
-
* @returns hex digest of the input string
|
|
437
|
-
*/
|
|
438
|
-
const quickHash = (input) => {
|
|
439
|
-
const hashSum = crypto.createHash('md5')
|
|
440
|
-
hashSum.update(input)
|
|
441
|
-
return hashSum.digest('hex')
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Fetch a persisted operations doc by its id, normalize it for Netlify Graph
|
|
446
|
-
* and return its contents as a string
|
|
447
|
-
* @param {object} input
|
|
448
|
-
* @param {string} input.siteId The site id to query against
|
|
449
|
-
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
450
|
-
* @param {string} input.docId The GraphQL operations document id to fetch
|
|
451
|
-
* @param {object} input.config The parsed netlify.toml file
|
|
452
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
453
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
|
|
454
|
-
* @param {string} input.schemaId The GraphQL schemaId to use when generating code
|
|
455
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
456
|
-
* @returns {Promise<string | undefined>}
|
|
457
|
-
*/
|
|
458
|
-
const fetchGraphQLOperationsLibraryFromPersistedDoc = async (input) => {
|
|
459
|
-
try {
|
|
460
|
-
const { docId, netlifyToken, siteId } = input
|
|
461
|
-
const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
462
|
-
const persistedDoc = await OneGraphClient.fetchPersistedQuery(jwt, siteId, docId)
|
|
463
|
-
if (!persistedDoc) {
|
|
464
|
-
warn(`No persisted doc found for: ${docId}`)
|
|
465
|
-
return
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Sorts the operations stably, prepends the @netlify directive, etc.
|
|
469
|
-
const operationsDocString = normalizeOperationsDoc(GraphQL, persistedDoc.query)
|
|
470
|
-
|
|
471
|
-
currentPersistedDocId = docId
|
|
472
|
-
|
|
473
|
-
return operationsDocString
|
|
474
|
-
} catch {
|
|
475
|
-
warn(`Unable to reach Netlify Graph servers in order to update Graph operations file`)
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Fetch a persisted operations doc by its id, write it to the system, and regenerate the library
|
|
481
|
-
* @param {object} input
|
|
482
|
-
* @param {object} input.config The parsed netlify.toml config file
|
|
483
|
-
* @param {string} input.operationsDocString The contents of the GraphQL operations document
|
|
484
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
485
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
|
|
486
|
-
* @param {string} input.schemaId The GraphQL schemaId to use when generating code
|
|
487
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
488
|
-
* @returns
|
|
489
|
-
*/
|
|
490
|
-
const updateGraphQLOperationsFileFromPersistedDoc = (input) => {
|
|
491
|
-
const { config, logger, netlifyGraphConfig, operationsDocString, schema, schemaId } = input
|
|
492
|
-
|
|
493
|
-
writeGraphQLOperationsSourceFile({ logger, netlifyGraphConfig, operationsDocString })
|
|
494
|
-
regenerateFunctionsFileFromOperationsFile({ config, netlifyGraphConfig, schema, schemaId })
|
|
495
|
-
|
|
496
|
-
const hash = quickHash(operationsDocString)
|
|
497
|
-
|
|
498
|
-
const relevantHasLength = 10
|
|
499
|
-
|
|
500
|
-
if (witnessedIncomingDocumentHashes.length > relevantHasLength) {
|
|
501
|
-
witnessedIncomingDocumentHashes.shift()
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
witnessedIncomingDocumentHashes.push(hash)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Fetch a persisted operations doc by its id, write it to the system, and regenerate the library
|
|
509
|
-
* @param {object} input
|
|
510
|
-
* @param {object} input.config The parsed netlify.toml config file
|
|
511
|
-
* @param {string} input.siteId The site id to query against
|
|
512
|
-
* @param {string} input.schemaId The schema ID to query against
|
|
513
|
-
* @param {string} input.siteRoot Path to the root of the project
|
|
514
|
-
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
515
|
-
* @param {string} input.docId The GraphQL operations document id to fetch
|
|
516
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
517
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
|
|
518
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
519
|
-
* @returns {Promise<string | undefined>}
|
|
520
|
-
*/
|
|
521
|
-
const handleOperationsLibraryPersistedEvent = async (input) => {
|
|
522
|
-
const { schemaId, siteRoot } = input
|
|
523
|
-
const operationsFileContents = await fetchGraphQLOperationsLibraryFromPersistedDoc(input)
|
|
524
|
-
|
|
525
|
-
if (!operationsFileContents) {
|
|
526
|
-
// `fetch` already warned
|
|
527
|
-
return
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const lockfile = NetlifyGraphLockfile.createLockfile({ operationsFileContents, schemaId })
|
|
531
|
-
writeLockfile({ siteRoot, lockfile })
|
|
532
|
-
updateGraphQLOperationsFileFromPersistedDoc({ ...input, operationsDocString: operationsFileContents })
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
*
|
|
537
|
-
* @param {object} input
|
|
538
|
-
* @param {any} input.site The site object
|
|
539
|
-
* @param {import('netlify-onegraph-internal').CliEventHelper.CliEvent} input.event
|
|
540
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
|
|
541
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
542
|
-
* @param {object} input.config The parsed netlify.toml config file
|
|
543
|
-
* @param {string} input.docId The GraphQL operations document id to fetch
|
|
544
|
-
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
545
|
-
* @param {string} input.schemaId The schemaId for the current session
|
|
546
|
-
* @param {string} input.sessionId The session ID to use for this CLI session (default: read from state)
|
|
547
|
-
* @param {string} input.siteId The site id to query against
|
|
548
|
-
* @param {string} input.siteRoot Path to the root of the project
|
|
549
|
-
* @returns {Promise<void>}
|
|
550
|
-
*/
|
|
551
|
-
export const handleCliSessionEvent = async ({
|
|
552
|
-
config,
|
|
553
|
-
docId,
|
|
554
|
-
event,
|
|
555
|
-
netlifyGraphConfig,
|
|
556
|
-
netlifyToken,
|
|
557
|
-
schema,
|
|
558
|
-
schemaId,
|
|
559
|
-
sessionId,
|
|
560
|
-
site,
|
|
561
|
-
siteId,
|
|
562
|
-
siteRoot,
|
|
563
|
-
}) => {
|
|
564
|
-
switch (event.__typename) {
|
|
565
|
-
case 'OneGraphNetlifyCliSessionTestEvent': {
|
|
566
|
-
const { payload } = event
|
|
567
|
-
|
|
568
|
-
await handleCliSessionEvent({
|
|
569
|
-
config,
|
|
570
|
-
docId,
|
|
571
|
-
netlifyToken,
|
|
572
|
-
// @ts-ignore
|
|
573
|
-
event: payload,
|
|
574
|
-
netlifyGraphConfig,
|
|
575
|
-
schema,
|
|
576
|
-
schemaId,
|
|
577
|
-
sessionId,
|
|
578
|
-
siteId,
|
|
579
|
-
siteRoot,
|
|
580
|
-
site,
|
|
581
|
-
})
|
|
582
|
-
|
|
583
|
-
break
|
|
584
|
-
}
|
|
585
|
-
case 'OneGraphNetlifyCliSessionGenerateHandlerEvent': {
|
|
586
|
-
const { payload } = event
|
|
587
|
-
|
|
588
|
-
if (!payload.operationId) {
|
|
589
|
-
warn(`No operation id found in payload,
|
|
590
|
-
${JSON.stringify(payload, null, 2)}`)
|
|
591
|
-
return
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
const codegenModule = await getCodegenModule({ config })
|
|
595
|
-
if (!codegenModule) {
|
|
596
|
-
error(
|
|
597
|
-
`No Netlify Graph codegen module specified in netlify.toml under the [graph] header. Please specify 'codeGenerator' field and try again.`,
|
|
598
|
-
)
|
|
599
|
-
return
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const codeGenerator = await getCodegenFunctionById({ config, id: payload.codegenId })
|
|
603
|
-
if (!codeGenerator) {
|
|
604
|
-
warn(
|
|
605
|
-
`Unable to find Netlify Graph code generator with id "${payload.codegenId}" from ${JSON.stringify(
|
|
606
|
-
payload,
|
|
607
|
-
null,
|
|
608
|
-
2,
|
|
609
|
-
)}`,
|
|
610
|
-
)
|
|
611
|
-
return
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const files = await generateHandlerByOperationId({
|
|
615
|
-
netlifyGraphConfig,
|
|
616
|
-
schema,
|
|
617
|
-
operationId: payload.operationId,
|
|
618
|
-
handlerOptions: payload,
|
|
619
|
-
generate: codeGenerator.generateHandler,
|
|
620
|
-
})
|
|
621
|
-
|
|
622
|
-
if (!files) {
|
|
623
|
-
warn(`No files generated for operation id: ${payload.operationId}`)
|
|
624
|
-
return
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const editor = process.env.EDITOR || null
|
|
628
|
-
|
|
629
|
-
/** @type {import('netlify-onegraph-internal').CliEventHelper.OneGraphNetlifyCliSessionFilesWrittenEvent} */
|
|
630
|
-
const filesWrittenEvent = {
|
|
631
|
-
id: crypto.randomUUID(),
|
|
632
|
-
createdAt: new Date().toString(),
|
|
633
|
-
__typename: 'OneGraphNetlifyCliSessionFilesWrittenEvent',
|
|
634
|
-
sessionId,
|
|
635
|
-
payload: {
|
|
636
|
-
editor,
|
|
637
|
-
// @ts-expect-error
|
|
638
|
-
files: files.map((file) => ({
|
|
639
|
-
name: file.name,
|
|
640
|
-
filePath: file.filePath,
|
|
641
|
-
})),
|
|
642
|
-
},
|
|
643
|
-
audience: 'UI',
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
try {
|
|
647
|
-
const graphJwt = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
648
|
-
|
|
649
|
-
await OneGraphClient.executeCreateCLISessionEventMutation(
|
|
650
|
-
{
|
|
651
|
-
sessionId,
|
|
652
|
-
payload: filesWrittenEvent,
|
|
653
|
-
},
|
|
654
|
-
{ accessToken: graphJwt.jwt },
|
|
655
|
-
)
|
|
656
|
-
} catch {
|
|
657
|
-
warn(`Unable to reach Netlify Graph servers in order to notify handler files written to disk`)
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
break
|
|
661
|
-
}
|
|
662
|
-
case 'OneGraphNetlifyCliSessionOpenFileEvent': {
|
|
663
|
-
if (!event.payload.filePath) {
|
|
664
|
-
warn(`No filePath found in payload, ${JSON.stringify(event.payload, null, 2)}`)
|
|
665
|
-
return
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const editor = process.env.EDITOR || null
|
|
669
|
-
|
|
670
|
-
if (editor) {
|
|
671
|
-
log(`Opening ${editor} for ${event.payload.filePath}`)
|
|
672
|
-
execa(editor, [event.payload.filePath], {
|
|
673
|
-
preferLocal: true,
|
|
674
|
-
// windowsHide needs to be false for child process to terminate properly on Windows
|
|
675
|
-
windowsHide: false,
|
|
676
|
-
})
|
|
677
|
-
} else {
|
|
678
|
-
warn('No $EDITOR set in env vars')
|
|
679
|
-
}
|
|
680
|
-
break
|
|
681
|
-
}
|
|
682
|
-
case 'OneGraphNetlifyCliSessionSetGraphCodegenModuleEvent': {
|
|
683
|
-
setNetlifyTomlCodeGeneratorModule({ codegenModuleImportPath: event.payload.codegenModuleImportPath, siteRoot })
|
|
684
|
-
break
|
|
685
|
-
}
|
|
686
|
-
case 'OneGraphNetlifyCliSessionMetadataRequestEvent': {
|
|
687
|
-
const graphJwt = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
688
|
-
|
|
689
|
-
const { minimumCliVersionExpected } = event.payload
|
|
690
|
-
|
|
691
|
-
const cliIsOutOfDateForUI =
|
|
692
|
-
version.localeCompare(minimumCliVersionExpected, undefined, { numeric: true, sensitivity: 'base' }) === -1
|
|
693
|
-
|
|
694
|
-
if (cliIsOutOfDateForUI) {
|
|
695
|
-
warn(
|
|
696
|
-
`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.`,
|
|
697
|
-
)
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
const input = { config, docId, jwt: graphJwt.jwt, schemaId, sessionId, siteRoot }
|
|
701
|
-
await publishCliSessionMetadataPublishEvent(input)
|
|
702
|
-
break
|
|
703
|
-
}
|
|
704
|
-
case 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent': {
|
|
705
|
-
const { payload } = event
|
|
706
|
-
|
|
707
|
-
if (!payload.schemaId || !payload.docId) {
|
|
708
|
-
warn(`Malformed library update event, missing schemaId or docId in payload:
|
|
709
|
-
${JSON.stringify(event, null, 2)}`)
|
|
710
|
-
break
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
await handleOperationsLibraryPersistedEvent({
|
|
714
|
-
config,
|
|
715
|
-
netlifyToken,
|
|
716
|
-
docId: payload.docId,
|
|
717
|
-
schemaId: payload.schemaId,
|
|
718
|
-
netlifyGraphConfig,
|
|
719
|
-
schema,
|
|
720
|
-
siteId,
|
|
721
|
-
siteRoot,
|
|
722
|
-
})
|
|
723
|
-
|
|
724
|
-
break
|
|
725
|
-
}
|
|
726
|
-
default: {
|
|
727
|
-
warn(
|
|
728
|
-
`Unrecognized event received, you may need to upgrade your CLI version: ${event.__typename}: ${JSON.stringify(
|
|
729
|
-
event,
|
|
730
|
-
null,
|
|
731
|
-
2,
|
|
732
|
-
)}`,
|
|
733
|
-
)
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
*
|
|
740
|
-
* @param {object} input
|
|
741
|
-
* @param {string} input.jwt The GraphJWT string
|
|
742
|
-
* @param {string} input.oneGraphSessionId The id of the cli session to fetch the current metadata for
|
|
743
|
-
* @param {object} input.siteId The site object that contains the root file path for the site
|
|
744
|
-
*/
|
|
745
|
-
const getCLISession = async ({ jwt, oneGraphSessionId, siteId }) => {
|
|
746
|
-
const input = {
|
|
747
|
-
appId: siteId,
|
|
748
|
-
sessionId: oneGraphSessionId,
|
|
749
|
-
jwt,
|
|
750
|
-
desiredEventCount: 1,
|
|
751
|
-
}
|
|
752
|
-
return await OneGraphClient.fetchCliSession(input)
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
*
|
|
757
|
-
* @param {object} input
|
|
758
|
-
* @param {string} input.jwt The GraphJWT string
|
|
759
|
-
* @param {string} input.oneGraphSessionId The id of the cli session to fetch the current metadata for
|
|
760
|
-
* @param {string} input.siteId The site object that contains the root file path for the site
|
|
761
|
-
*/
|
|
762
|
-
const getCLISessionMetadata = async ({ jwt, oneGraphSessionId, siteId }) => {
|
|
763
|
-
const result = await getCLISession({ jwt, oneGraphSessionId, siteId })
|
|
764
|
-
if (!result) {
|
|
765
|
-
warn(`Unable to fetch CLI session metadata`)
|
|
766
|
-
}
|
|
767
|
-
const { errors, session } = result
|
|
768
|
-
return { metadata: session && session.metadata, errors }
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
/**
|
|
772
|
-
* Look at the current project, filesystem, etc. and determine relevant metadata for a cli session
|
|
773
|
-
* @param {object} input
|
|
774
|
-
* @param {string} input.siteRoot The root file path for the site
|
|
775
|
-
* @returns {Promise<import('netlify-onegraph-internal').CliEventHelper.DetectedLocalCLISessionMetadata>} Any locally detected facts that are relevant to include in the cli session metadata
|
|
776
|
-
*/
|
|
777
|
-
const detectLocalCLISessionMetadata = async ({ siteRoot }) => {
|
|
778
|
-
/** @type {string | null} */
|
|
779
|
-
let framework = null
|
|
780
|
-
|
|
781
|
-
try {
|
|
782
|
-
const frameworks = await listFrameworks({ projectDir: siteRoot })
|
|
783
|
-
framework = frameworks[0].id || null
|
|
784
|
-
} catch {}
|
|
785
|
-
|
|
786
|
-
const { branch } = gitRepoInfo()
|
|
787
|
-
const hostname = os.hostname()
|
|
788
|
-
const userInfo = os.userInfo({ encoding: 'utf-8' })
|
|
789
|
-
const { username } = userInfo
|
|
790
|
-
const cliVersion = version
|
|
791
|
-
|
|
792
|
-
const platform = WSL ? 'wsl' : os.platform()
|
|
793
|
-
const arch = os.arch() === 'ia32' ? 'x86' : os.arch()
|
|
794
|
-
|
|
795
|
-
const editor = process.env.EDITOR || null
|
|
796
|
-
|
|
797
|
-
const detectedMetadata = {
|
|
798
|
-
gitBranch: branch,
|
|
799
|
-
hostname,
|
|
800
|
-
username,
|
|
801
|
-
siteRoot,
|
|
802
|
-
cliVersion,
|
|
803
|
-
editor,
|
|
804
|
-
platform,
|
|
805
|
-
arch,
|
|
806
|
-
nodeVersion: process.version,
|
|
807
|
-
framework,
|
|
808
|
-
codegen: null,
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
return detectedMetadata
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
/**
|
|
815
|
-
*
|
|
816
|
-
* @param {object} input
|
|
817
|
-
* @param {string} input.jwt The GraphJWT string
|
|
818
|
-
* @param {string} input.sessionId The id of the cli session to fetch the current metadata for
|
|
819
|
-
* @param {string} input.siteRoot Path to the root of the project
|
|
820
|
-
* @param {object} input.config The parsed netlify.toml config file
|
|
821
|
-
* @param {string} input.docId The GraphQL operations document id to fetch
|
|
822
|
-
* @param {string} input.schemaId The GraphQL schemaId to use when generating code
|
|
823
|
-
*/
|
|
824
|
-
const publishCliSessionMetadataPublishEvent = async ({ config, docId, jwt, schemaId, sessionId, siteRoot }) => {
|
|
825
|
-
const detectedMetadata = await detectLocalCLISessionMetadata({ siteRoot })
|
|
826
|
-
|
|
827
|
-
/** @type {import('netlify-onegraph-internal').CodegenHelpers.CodegenModuleMeta | null} */
|
|
828
|
-
let codegen = null
|
|
829
|
-
|
|
830
|
-
const codegenModule = await getCodegenModule({ config })
|
|
831
|
-
|
|
832
|
-
if (codegenModule) {
|
|
833
|
-
codegen = {
|
|
834
|
-
id: codegenModule.id,
|
|
835
|
-
version: codegenModule.id,
|
|
836
|
-
generators: codegenModule.generators.map((generator) => ({
|
|
837
|
-
id: generator.id,
|
|
838
|
-
name: generator.name,
|
|
839
|
-
options: generator.generateHandlerOptions || null,
|
|
840
|
-
supportedDefinitionTypes: generator.supportedDefinitionTypes,
|
|
841
|
-
version: generator.version,
|
|
842
|
-
})),
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
/** @type {import('netlify-onegraph-internal').CliEventHelper.OneGraphNetlifyCliSessionMetadataPublishEvent} */
|
|
847
|
-
const event = {
|
|
848
|
-
__typename: 'OneGraphNetlifyCliSessionMetadataPublishEvent',
|
|
849
|
-
audience: 'UI',
|
|
850
|
-
createdAt: new Date().toString(),
|
|
851
|
-
id: crypto.randomUUID(),
|
|
852
|
-
sessionId,
|
|
853
|
-
payload: {
|
|
854
|
-
cliVersion: detectedMetadata.cliVersion,
|
|
855
|
-
editor: detectedMetadata.editor,
|
|
856
|
-
siteRoot: detectedMetadata.siteRoot,
|
|
857
|
-
siteRootFriendly: detectedMetadata.siteRoot,
|
|
858
|
-
persistedDocId: docId,
|
|
859
|
-
schemaId,
|
|
860
|
-
codegenModule: codegen,
|
|
861
|
-
arch: detectedMetadata.arch,
|
|
862
|
-
nodeVersion: detectedMetadata.nodeVersion,
|
|
863
|
-
platform: detectedMetadata.platform,
|
|
864
|
-
framework: detectedMetadata.framework,
|
|
865
|
-
},
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
try {
|
|
869
|
-
await OneGraphClient.executeCreateCLISessionEventMutation(
|
|
870
|
-
{
|
|
871
|
-
sessionId,
|
|
872
|
-
payload: event,
|
|
873
|
-
},
|
|
874
|
-
{ accessToken: jwt },
|
|
875
|
-
)
|
|
876
|
-
} catch {
|
|
877
|
-
warn(`Unable to reach Netlify Graph servers in order to publish CLI session data for the Graph UI`)
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* Fetch the existing cli session metadata if it exists, and mutate it remotely with the passed in metadata
|
|
883
|
-
* @param {object} input
|
|
884
|
-
* @param {object} input.config The parsed netlify.toml file
|
|
885
|
-
* @param {string} input.jwt The Graph JWT string
|
|
886
|
-
* @param {string} input.oneGraphSessionId The id of the cli session to fetch the current metadata for
|
|
887
|
-
* @param {string} input.siteId The site object that contains the root file path for the site
|
|
888
|
-
* @param {string} input.siteRoot The root file path for the site
|
|
889
|
-
* @param {object} input.newMetadata The metadata to merge into (with priority) the existing metadata
|
|
890
|
-
* @returns {Promise<object>}
|
|
891
|
-
*/
|
|
892
|
-
export const upsertMergeCLISessionMetadata = async ({ jwt, newMetadata, oneGraphSessionId, siteId, siteRoot }) => {
|
|
893
|
-
const { errors, metadata } = await getCLISessionMetadata({ jwt, oneGraphSessionId, siteId })
|
|
894
|
-
if (errors) {
|
|
895
|
-
warn(`Error fetching cli session metadata: ${JSON.stringify(errors, null, 2)}`)
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
const detectedMetadata = await detectLocalCLISessionMetadata({ siteRoot })
|
|
899
|
-
|
|
900
|
-
// @ts-ignore
|
|
901
|
-
const finalMetadata = { ...metadata, ...detectedMetadata, ...newMetadata }
|
|
902
|
-
|
|
903
|
-
const result = OneGraphClient.updateCLISessionMetadata(jwt, siteId, oneGraphSessionId, finalMetadata)
|
|
904
|
-
|
|
905
|
-
return result
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
export const persistNewOperationsDocForSession = async ({
|
|
909
|
-
config,
|
|
910
|
-
netlifyGraphConfig,
|
|
911
|
-
netlifyToken,
|
|
912
|
-
oneGraphSessionId,
|
|
913
|
-
operationsDoc,
|
|
914
|
-
siteId,
|
|
915
|
-
siteRoot,
|
|
916
|
-
}) => {
|
|
917
|
-
try {
|
|
918
|
-
GraphQL.parse(operationsDoc)
|
|
919
|
-
} catch (parseError) {
|
|
920
|
-
// TODO: We should send a message to the web UI that the current GraphQL operations file can't be sync because it's invalid
|
|
921
|
-
warn(
|
|
922
|
-
`Unable to sync Graph operations file. Please ensure that your GraphQL operations file is valid GraphQL. Found error: ${JSON.stringify(
|
|
923
|
-
parseError,
|
|
924
|
-
null,
|
|
925
|
-
2,
|
|
926
|
-
)}`,
|
|
927
|
-
)
|
|
928
|
-
return
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
const lockfile = readLockfile({ siteRoot })
|
|
932
|
-
|
|
933
|
-
if (!lockfile) {
|
|
934
|
-
warn(
|
|
935
|
-
`can't find a lockfile for the project while running trying to persist operations for session. To pull a remote schema (and create a lockfile), run ${chalk.yellow(
|
|
936
|
-
'netlify graph:pull',
|
|
937
|
-
)} `,
|
|
938
|
-
)
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
// NOTE(anmonteiro): We still persist a new operations document because we
|
|
942
|
-
// might be checking out someone else's branch whose session we don't have
|
|
943
|
-
// access to.
|
|
944
|
-
|
|
945
|
-
const { branch } = gitRepoInfo()
|
|
946
|
-
const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
947
|
-
const persistedResult = await OneGraphClient.executeCreatePersistedQueryMutation(
|
|
948
|
-
{
|
|
949
|
-
appId: siteId,
|
|
950
|
-
description: 'Temporary snapshot of local queries',
|
|
951
|
-
query: operationsDoc,
|
|
952
|
-
tags: ['netlify-cli', `session:${oneGraphSessionId}`, `git-branch:${branch}`, `local-change`],
|
|
953
|
-
},
|
|
954
|
-
{
|
|
955
|
-
accessToken: jwt,
|
|
956
|
-
siteId,
|
|
957
|
-
},
|
|
958
|
-
)
|
|
959
|
-
|
|
960
|
-
const persistedDoc =
|
|
961
|
-
persistedResult.data &&
|
|
962
|
-
persistedResult.data.oneGraph &&
|
|
963
|
-
persistedResult.data.oneGraph.createPersistedQuery &&
|
|
964
|
-
persistedResult.data.oneGraph.createPersistedQuery.persistedQuery
|
|
965
|
-
|
|
966
|
-
if (!persistedDoc) {
|
|
967
|
-
warn(`Failed to create persisted query for editing, ${JSON.stringify(persistedResult, null, 2)}`)
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
currentPersistedDocId = persistedDoc.id
|
|
971
|
-
|
|
972
|
-
const newMetadata = { docId: persistedDoc.id }
|
|
973
|
-
const result = await upsertMergeCLISessionMetadata({
|
|
974
|
-
config,
|
|
975
|
-
jwt,
|
|
976
|
-
siteId,
|
|
977
|
-
oneGraphSessionId,
|
|
978
|
-
newMetadata,
|
|
979
|
-
siteRoot,
|
|
980
|
-
})
|
|
981
|
-
|
|
982
|
-
if (!result || result.errors) {
|
|
983
|
-
warn(
|
|
984
|
-
`Unable to update session metadata with updated operations docId="${persistedDoc.id}": ${JSON.stringify(
|
|
985
|
-
result && result.errors,
|
|
986
|
-
null,
|
|
987
|
-
2,
|
|
988
|
-
)}`,
|
|
989
|
-
)
|
|
990
|
-
} else if (lockfile != null) {
|
|
991
|
-
// Now that we've persisted the document, lock it in the lockfile
|
|
992
|
-
const currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
993
|
-
|
|
994
|
-
/** @type {NetlifyGraphLockfile.V0_format} */
|
|
995
|
-
const newLockfile = NetlifyGraphLockfile.createLockfile({
|
|
996
|
-
schemaId: lockfile.locked.schemaId,
|
|
997
|
-
operationsFileContents: currentOperationsDoc,
|
|
998
|
-
})
|
|
999
|
-
writeLockfile({ siteRoot, lockfile: newLockfile })
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
export const createCLISession = async ({ metadata, netlifyToken, sessionName, siteId }) => {
|
|
1004
|
-
const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
1005
|
-
const result = OneGraphClient.createCLISession(jwt, siteId, sessionName, metadata)
|
|
1006
|
-
return result
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
/**
|
|
1010
|
-
* Load the CLI session id from the local state
|
|
1011
|
-
* @param {import('../../utils/state-config.mjs').default} state
|
|
1012
|
-
* @returns
|
|
1013
|
-
*/
|
|
1014
|
-
export const loadCLISession = (state) => state.get('oneGraphSessionId')
|
|
1015
|
-
|
|
1016
|
-
/**
|
|
1017
|
-
* Idemponentially save the CLI session id to the local state and start monitoring for CLI events, upstream schema changes, and local operation file changes
|
|
1018
|
-
* @param {object} input
|
|
1019
|
-
* @param {object} input.config
|
|
1020
|
-
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
1021
|
-
* @param {string | undefined} input.oneGraphSessionId The session ID to use for this CLI session (default: read from state)
|
|
1022
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
1023
|
-
* @param {import('../../utils/state-config.mjs').default} input.state A function to call to set/get the current state of the local Netlify project
|
|
1024
|
-
* @param {any} input.site The site object
|
|
1025
|
-
*/
|
|
1026
|
-
export const startOneGraphCLISession = async (input) => {
|
|
1027
|
-
const { config, netlifyGraphConfig, netlifyToken, site, state } = input
|
|
1028
|
-
const getJwt = async () => {
|
|
1029
|
-
const accessToken = await OneGraphClient.getGraphJwtForSite({ siteId: site.id, nfToken: netlifyToken })
|
|
1030
|
-
return accessToken.jwt
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
OneGraphClient.ensureAppForSite(await getJwt(), site.id)
|
|
1034
|
-
|
|
1035
|
-
const oneGraphSessionId = await ensureCLISession({
|
|
1036
|
-
config,
|
|
1037
|
-
netlifyGraphConfig,
|
|
1038
|
-
metadata: {},
|
|
1039
|
-
netlifyToken,
|
|
1040
|
-
site,
|
|
1041
|
-
state,
|
|
1042
|
-
oneGraphSessionId: input.oneGraphSessionId,
|
|
1043
|
-
})
|
|
1044
|
-
|
|
1045
|
-
const syncUIHelper = async () => {
|
|
1046
|
-
const schemaId = readSchemaIdFromLockfile({ siteRoot: site.root })
|
|
1047
|
-
|
|
1048
|
-
if (!schemaId) {
|
|
1049
|
-
warn('Unable to load schemaId from Netlify Graph lockfile, run graph:pull to update')
|
|
1050
|
-
return
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
if (!currentPersistedDocId) {
|
|
1054
|
-
warn('Unable to read current persisted Graph library doc id')
|
|
1055
|
-
return
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
const syncSessionMetadataInput = {
|
|
1059
|
-
config,
|
|
1060
|
-
docId: currentPersistedDocId,
|
|
1061
|
-
jwt: await getJwt(),
|
|
1062
|
-
schemaId,
|
|
1063
|
-
sessionId: oneGraphSessionId,
|
|
1064
|
-
siteRoot: site.root,
|
|
1065
|
-
}
|
|
1066
|
-
await publishCliSessionMetadataPublishEvent(syncSessionMetadataInput)
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
await syncUIHelper()
|
|
1070
|
-
|
|
1071
|
-
const enabledServices = []
|
|
1072
|
-
const schema = await OneGraphClient.fetchOneGraphSchemaForServices(site.id, enabledServices)
|
|
1073
|
-
|
|
1074
|
-
const opsFileWatcher = monitorOperationFile({
|
|
1075
|
-
netlifyGraphConfig,
|
|
1076
|
-
onChange: async (filePath) => {
|
|
1077
|
-
log('NetlifyGraph operation file changed at', filePath, 'updating function library...')
|
|
1078
|
-
if (!schema) {
|
|
1079
|
-
warn('Unable to load schema, run graph:pull to update')
|
|
1080
|
-
return
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
const schemaId = readSchemaIdFromLockfile({ siteRoot: site.root })
|
|
1084
|
-
|
|
1085
|
-
if (!schemaId) {
|
|
1086
|
-
warn('Unable to load schemaId from Netlify Graph lockfile, run graph:pull to update')
|
|
1087
|
-
return
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
regenerateFunctionsFileFromOperationsFile({ config, netlifyGraphConfig, schema, schemaId })
|
|
1091
|
-
const newOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
1092
|
-
await persistNewOperationsDocForSession({
|
|
1093
|
-
config,
|
|
1094
|
-
netlifyGraphConfig,
|
|
1095
|
-
netlifyToken,
|
|
1096
|
-
oneGraphSessionId,
|
|
1097
|
-
operationsDoc: newOperationsDoc,
|
|
1098
|
-
siteId: site.id,
|
|
1099
|
-
siteRoot: site.root,
|
|
1100
|
-
})
|
|
1101
|
-
},
|
|
1102
|
-
onAdd: async (filePath) => {
|
|
1103
|
-
log('NetlifyGraph operation file created at', filePath, 'creating function library...')
|
|
1104
|
-
if (!schema) {
|
|
1105
|
-
warn('Unable to load schema, run graph:pull to update')
|
|
1106
|
-
return
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
const schemaId = readSchemaIdFromLockfile({ siteRoot: site.root })
|
|
1110
|
-
|
|
1111
|
-
if (!schemaId) {
|
|
1112
|
-
warn('Unable to load schemaId from Netlify Graph lockfile, run graph:pull to update')
|
|
1113
|
-
return
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
regenerateFunctionsFileFromOperationsFile({ config, netlifyGraphConfig, schema, schemaId })
|
|
1117
|
-
const newOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
1118
|
-
await persistNewOperationsDocForSession({
|
|
1119
|
-
config,
|
|
1120
|
-
netlifyGraphConfig,
|
|
1121
|
-
netlifyToken,
|
|
1122
|
-
oneGraphSessionId,
|
|
1123
|
-
operationsDoc: newOperationsDoc,
|
|
1124
|
-
siteId: site.id,
|
|
1125
|
-
siteRoot: site.root,
|
|
1126
|
-
})
|
|
1127
|
-
},
|
|
1128
|
-
})
|
|
1129
|
-
|
|
1130
|
-
const cliEventsCloseFn = monitorCLISessionEvents({
|
|
1131
|
-
config,
|
|
1132
|
-
appId: site.id,
|
|
1133
|
-
netlifyToken,
|
|
1134
|
-
netlifyGraphConfig,
|
|
1135
|
-
sessionId: oneGraphSessionId,
|
|
1136
|
-
site,
|
|
1137
|
-
state,
|
|
1138
|
-
onClose: () => {
|
|
1139
|
-
log('CLI session closed, stopping monitor...')
|
|
1140
|
-
},
|
|
1141
|
-
onSchemaIdChange: (newSchemaId) => {
|
|
1142
|
-
log('Netlify Graph schemaId changed:', newSchemaId)
|
|
1143
|
-
},
|
|
1144
|
-
onEvents: async (events) => {
|
|
1145
|
-
const ackEventIds = []
|
|
1146
|
-
|
|
1147
|
-
for (const event of events) {
|
|
1148
|
-
try {
|
|
1149
|
-
const audience = event.audience || OneGraphClient.eventAudience(event)
|
|
1150
|
-
if (audience === 'CLI') {
|
|
1151
|
-
ackEventIds.push(event.id)
|
|
1152
|
-
const eventName = OneGraphClient.friendlyEventName(event)
|
|
1153
|
-
log(`${chalk.magenta('Handling')} Netlify Graph: ${eventName}...`)
|
|
1154
|
-
const schemaId = readSchemaIdFromLockfile({ siteRoot: site.root })
|
|
1155
|
-
|
|
1156
|
-
if (!schemaId) {
|
|
1157
|
-
warn('Unable to load schemaId from Netlify Graph lockfile, run graph:pull to update')
|
|
1158
|
-
return
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
if (!schema) {
|
|
1162
|
-
warn('Unable to load schema from for Netlify Graph, run graph:pull to update')
|
|
1163
|
-
return
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
if (!currentPersistedDocId) {
|
|
1167
|
-
warn('Unable to read current persisted Graph library doc id')
|
|
1168
|
-
return
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
await handleCliSessionEvent({
|
|
1172
|
-
config,
|
|
1173
|
-
docId: currentPersistedDocId,
|
|
1174
|
-
netlifyToken,
|
|
1175
|
-
event,
|
|
1176
|
-
netlifyGraphConfig,
|
|
1177
|
-
schema,
|
|
1178
|
-
schemaId,
|
|
1179
|
-
sessionId: oneGraphSessionId,
|
|
1180
|
-
site,
|
|
1181
|
-
siteId: site.id,
|
|
1182
|
-
siteRoot: site.root,
|
|
1183
|
-
})
|
|
1184
|
-
log(`${chalk.green('Finished handling')} Netlify Graph: ${eventName}...`)
|
|
1185
|
-
}
|
|
1186
|
-
} catch (error_) {
|
|
1187
|
-
warn(`Error processing individual Netlify Graph event, skipping:
|
|
1188
|
-
${JSON.stringify(error_, null, 2)}`)
|
|
1189
|
-
ackEventIds.push(event.id)
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
return ackEventIds
|
|
1194
|
-
},
|
|
1195
|
-
onError: (fetchEventError) => {
|
|
1196
|
-
error(`Netlify Graph upstream error: ${fetchEventError}`)
|
|
1197
|
-
},
|
|
1198
|
-
})
|
|
1199
|
-
|
|
1200
|
-
return async function unregisterWatchers() {
|
|
1201
|
-
const watcher = await opsFileWatcher
|
|
1202
|
-
watcher.close()
|
|
1203
|
-
cliEventsCloseFn()
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
/**
|
|
1208
|
-
* Mark a session as inactive so it doesn't show up in any UI lists, and potentially becomes available to GC later
|
|
1209
|
-
* @param {object} input
|
|
1210
|
-
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
1211
|
-
* @param {string} input.siteId A function to call to set/get the current state of the local Netlify project
|
|
1212
|
-
* @param {string} input.sessionId The session id to monitor CLI events for
|
|
1213
|
-
*/
|
|
1214
|
-
export const markCliSessionInactive = async ({ netlifyToken, sessionId, siteId }) => {
|
|
1215
|
-
const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
1216
|
-
const result = await OneGraphClient.executeMarkCliSessionInactive(jwt, siteId, sessionId)
|
|
1217
|
-
if (!result || result.errors) {
|
|
1218
|
-
warn(`Unable to mark CLI session ${sessionId} inactive: ${JSON.stringify(result.errors, null, 2)}`)
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
/**
|
|
1223
|
-
* Generate a session name that can be identified as belonging to the current checkout
|
|
1224
|
-
* @returns {string} The name of the session to create
|
|
1225
|
-
*/
|
|
1226
|
-
export const generateSessionName = () => {
|
|
1227
|
-
const userInfo = os.userInfo({ encoding: 'utf-8' })
|
|
1228
|
-
const sessionName = `${userInfo.username}-${Date.now()}`
|
|
1229
|
-
log(`Generated Netlify Graph session name: ${sessionName}`)
|
|
1230
|
-
return sessionName
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
/**
|
|
1234
|
-
* Mark a session as inactive so it doesn't show up in any UI lists, and potentially becomes available to GC later
|
|
1235
|
-
* @param {object} input
|
|
1236
|
-
* @param {{metadata: {schemaId:string}; id: string; appId: string; name?: string}} input.session The current session
|
|
1237
|
-
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
1238
|
-
* @param {NetlifyGraphLockfile.V0_format | undefined} input.lockfile A function to call to set/get the current state of the local Netlify project
|
|
1239
|
-
*/
|
|
1240
|
-
const idempotentlyUpdateSessionSchemaIdFromLockfile = async (input) => {
|
|
1241
|
-
const { lockfile, netlifyToken, session } = input
|
|
1242
|
-
const sessionSchemaId = session.metadata && session.metadata.schemaId
|
|
1243
|
-
const lockfileSchemaId = lockfile && lockfile.locked.schemaId
|
|
1244
|
-
|
|
1245
|
-
if (lockfileSchemaId != null && sessionSchemaId !== lockfileSchemaId) {
|
|
1246
|
-
// Local schema always wins, update the session metadata to reflect that
|
|
1247
|
-
const siteId = session.appId
|
|
1248
|
-
const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
|
|
1249
|
-
|
|
1250
|
-
log(`Found new lockfile, overwriting session ${session.name || session.id}`)
|
|
1251
|
-
return OneGraphClient.updateCLISessionMetadata(jwt, siteId, session.id, {
|
|
1252
|
-
...session.metadata,
|
|
1253
|
-
schemaId: lockfileSchemaId,
|
|
1254
|
-
})
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* Ensures a cli session exists for the current checkout, or errors out if it doesn't and cannot create one.
|
|
1260
|
-
* @param {object} input
|
|
1261
|
-
* @param {object} input.config The parsed netlify.toml config file
|
|
1262
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
1263
|
-
* @param {object} input.metadata
|
|
1264
|
-
* @param {string} input.netlifyToken
|
|
1265
|
-
* @param {import('../../utils/state-config.mjs').default} input.state
|
|
1266
|
-
* @param {string} [input.oneGraphSessionId]
|
|
1267
|
-
* @param {any} input.site The site object
|
|
1268
|
-
*/
|
|
1269
|
-
export const ensureCLISession = async (input) => {
|
|
1270
|
-
const { config, metadata, netlifyGraphConfig, netlifyToken, site, state } = input
|
|
1271
|
-
let oneGraphSessionId = input.oneGraphSessionId ? input.oneGraphSessionId : loadCLISession(state)
|
|
1272
|
-
let parentCliSessionId = null
|
|
1273
|
-
const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId: site.id, nfToken: netlifyToken })
|
|
1274
|
-
|
|
1275
|
-
const lockfile = readLockfile({ siteRoot: site.root })
|
|
1276
|
-
|
|
1277
|
-
// Validate that session still exists and we can access it
|
|
1278
|
-
try {
|
|
1279
|
-
if (oneGraphSessionId) {
|
|
1280
|
-
const { errors, session } = await OneGraphClient.fetchCliSession({
|
|
1281
|
-
appId: site.id,
|
|
1282
|
-
jwt,
|
|
1283
|
-
sessionId: oneGraphSessionId,
|
|
1284
|
-
desiredEventCount: 0,
|
|
1285
|
-
})
|
|
1286
|
-
|
|
1287
|
-
if (errors) {
|
|
1288
|
-
warn(`Unable to fetch cli session: ${JSON.stringify(errors, null, 2)}`)
|
|
1289
|
-
log(`Creating new cli session`)
|
|
1290
|
-
parentCliSessionId = oneGraphSessionId
|
|
1291
|
-
oneGraphSessionId = null
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
if (session && session.metadata && session.metadata.docId) {
|
|
1295
|
-
currentPersistedDocId = session.metadata.docId
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
// During the transition to lockfiles, write a lockfile if one isn't
|
|
1299
|
-
// found. Later, only handling a 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent'
|
|
1300
|
-
// will create or update the lockfile
|
|
1301
|
-
// TODO(anmonteiro): remove this in the future?
|
|
1302
|
-
if (lockfile == null && session.metadata.schemaId) {
|
|
1303
|
-
const currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
1304
|
-
log(`Generating Netlify Graph lockfile at ${NetlifyGraphLockfile.defaultLockFileName}`)
|
|
1305
|
-
|
|
1306
|
-
const newLockfile = NetlifyGraphLockfile.createLockfile({
|
|
1307
|
-
schemaId: session.metadata.schemaId,
|
|
1308
|
-
operationsFileContents: currentOperationsDoc,
|
|
1309
|
-
})
|
|
1310
|
-
writeLockfile({ siteRoot: site.root, lockfile: newLockfile })
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
await idempotentlyUpdateSessionSchemaIdFromLockfile({ session, lockfile, netlifyToken })
|
|
1314
|
-
}
|
|
1315
|
-
} catch (fetchSessionError) {
|
|
1316
|
-
warn(`Unable to fetch cli session events: ${JSON.stringify(fetchSessionError, null, 2)}`)
|
|
1317
|
-
oneGraphSessionId = null
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
if (oneGraphSessionId) {
|
|
1321
|
-
await upsertMergeCLISessionMetadata({
|
|
1322
|
-
jwt,
|
|
1323
|
-
config,
|
|
1324
|
-
newMetadata: {},
|
|
1325
|
-
oneGraphSessionId,
|
|
1326
|
-
siteId: site.id,
|
|
1327
|
-
siteRoot: site.root,
|
|
1328
|
-
})
|
|
1329
|
-
} else {
|
|
1330
|
-
// If we can't access the session in the state.json or it doesn't exist, create a new one
|
|
1331
|
-
const sessionName = generateSessionName()
|
|
1332
|
-
const detectedMetadata = await detectLocalCLISessionMetadata({
|
|
1333
|
-
siteRoot: site.root,
|
|
1334
|
-
})
|
|
1335
|
-
const newSessionMetadata = parentCliSessionId ? { parentCliSessionId } : {}
|
|
1336
|
-
|
|
1337
|
-
const sessionMetadata = {
|
|
1338
|
-
...detectedMetadata,
|
|
1339
|
-
...newSessionMetadata,
|
|
1340
|
-
...metadata,
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
if (lockfile != null) {
|
|
1344
|
-
log(`Creating new session "${sessionName}" from lockfile`)
|
|
1345
|
-
sessionMetadata.schemaId = lockfile.locked.schemaId
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
const oneGraphSession = await createCLISession({
|
|
1349
|
-
netlifyToken,
|
|
1350
|
-
siteId: site.id,
|
|
1351
|
-
sessionName,
|
|
1352
|
-
metadata: sessionMetadata,
|
|
1353
|
-
})
|
|
1354
|
-
|
|
1355
|
-
if (oneGraphSession) {
|
|
1356
|
-
// @ts-expect-error
|
|
1357
|
-
oneGraphSessionId = oneGraphSession.id
|
|
1358
|
-
} else {
|
|
1359
|
-
warn('Unable to load Netlify Graph session, please report this to Netlify support')
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
if (!oneGraphSessionId) {
|
|
1364
|
-
error('Unable to create or access Netlify Graph CLI session')
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
state.set('oneGraphSessionId', oneGraphSessionId)
|
|
1368
|
-
const { errors: markCLISessionActiveErrors } = await OneGraphClient.executeMarkCliSessionActiveHeartbeat(
|
|
1369
|
-
jwt,
|
|
1370
|
-
site.id,
|
|
1371
|
-
oneGraphSessionId,
|
|
1372
|
-
)
|
|
1373
|
-
|
|
1374
|
-
if (markCLISessionActiveErrors) {
|
|
1375
|
-
warn(`Unable to mark cli session active: ${JSON.stringify(markCLISessionActiveErrors, null, 2)}`)
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
return oneGraphSessionId
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
export const OneGraphCliClient = {
|
|
1382
|
-
ackCLISessionEvents: OneGraphClient.ackCLISessionEvents,
|
|
1383
|
-
executeCreatePersistedQueryMutation: OneGraphClient.executeCreatePersistedQueryMutation,
|
|
1384
|
-
executeCreateApiTokenMutation: OneGraphClient.executeCreateApiTokenMutation,
|
|
1385
|
-
fetchCliSessionEvents: OneGraphClient.fetchCliSessionEvents,
|
|
1386
|
-
fetchCliSessionSchema,
|
|
1387
|
-
ensureAppForSite: OneGraphClient.ensureAppForSite,
|
|
1388
|
-
updateCLISessionMetadata: OneGraphClient.updateCLISessionMetadata,
|
|
1389
|
-
getGraphJwtForSite: OneGraphClient.getGraphJwtForSite,
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
export { currentPersistedDocId, extractFunctionsFromOperationDoc }
|