netlify-cli 8.11.1 → 8.13.2
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 +12 -1
- package/npm-shrinkwrap.json +1658 -2482
- package/package.json +8 -5
- package/src/commands/dev/dev.js +37 -7
- package/src/commands/graph/graph-edit.js +91 -0
- package/src/commands/graph/graph-pull.js +95 -0
- package/src/commands/graph/graph.js +30 -0
- package/src/commands/graph/index.js +5 -0
- package/src/commands/main.js +2 -0
- package/src/functions-templates/javascript/hasura-event-triggered/package.json +1 -1
- package/src/functions-templates/javascript/stripe-charge/package-lock.json +7 -7
- package/src/functions-templates/javascript/stripe-subscription/package-lock.json +7 -7
- package/src/functions-templates/javascript/token-hider/package-lock.json +16 -16
- package/src/functions-templates/javascript/token-hider/package.json +1 -1
- package/src/functions-templates/typescript/hello-world/package-lock.json +13 -13
- package/src/lib/one-graph/cli-client.js +278 -0
- package/src/lib/one-graph/cli-netlify-graph.js +278 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/* eslint-disable eslint-comments/disable-enable-pair */
|
|
2
|
+
/* eslint-disable fp/no-loops */
|
|
3
|
+
const os = require('os')
|
|
4
|
+
|
|
5
|
+
const { GraphQL, InternalConsole, OneGraphClient } = require('netlify-onegraph-internal')
|
|
6
|
+
const { NetlifyGraph } = require('netlify-onegraph-internal')
|
|
7
|
+
|
|
8
|
+
const { chalk, error, log, warn } = require('../../utils')
|
|
9
|
+
|
|
10
|
+
const { createCLISession, createPersistedQuery, ensureAppForSite, updateCLISessionMetadata } = OneGraphClient
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
generateFunctionsFile,
|
|
14
|
+
generateHandler,
|
|
15
|
+
readGraphQLOperationsSourceFile,
|
|
16
|
+
writeGraphQLOperationsSourceFile,
|
|
17
|
+
writeGraphQLSchemaFile,
|
|
18
|
+
} = require('./cli-netlify-graph')
|
|
19
|
+
|
|
20
|
+
const { parse } = GraphQL
|
|
21
|
+
const { defaultExampleOperationsDoc, extractFunctionsFromOperationDoc } = NetlifyGraph
|
|
22
|
+
|
|
23
|
+
const internalConsole = {
|
|
24
|
+
log,
|
|
25
|
+
warn,
|
|
26
|
+
error,
|
|
27
|
+
debug: console.debug,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
InternalConsole.registerConsole(internalConsole)
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Start polling for CLI events for a given session to process locally
|
|
34
|
+
* @param {object} input
|
|
35
|
+
* @param {string} input.appId The app to query against, typically the siteId
|
|
36
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
37
|
+
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
38
|
+
* @param {function} input.onClose A function to call when the polling loop is closed
|
|
39
|
+
* @param {function} input.onError A function to call when an error occurs
|
|
40
|
+
* @param {function} input.onEvents A function to call when CLI events are received and need to be processed
|
|
41
|
+
* @param {string} input.sessionId The session id to monitor CLI events for
|
|
42
|
+
* @param {state} input.state A function to call to set/get the current state of the local Netlify project
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
const monitorCLISessionEvents = (input) => {
|
|
46
|
+
const { appId, netlifyGraphConfig, netlifyToken, onClose, onError, onEvents, sessionId, state } = input
|
|
47
|
+
|
|
48
|
+
const frequency = 5000
|
|
49
|
+
let shouldClose = false
|
|
50
|
+
|
|
51
|
+
const enabledServiceWatcher = async (innerNetlifyToken, siteId) => {
|
|
52
|
+
const enabledServices = state.get('oneGraphEnabledServices') || ['onegraph']
|
|
53
|
+
const enabledServicesInfo = await OneGraphClient.fetchEnabledServices(innerNetlifyToken, siteId)
|
|
54
|
+
if (!enabledServicesInfo) {
|
|
55
|
+
warn('Unable to fetch enabled services for site for code generation')
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
const newEnabledServices = enabledServicesInfo.map((service) => service.service)
|
|
59
|
+
const enabledServicesCompareKey = enabledServices.sort().join(',')
|
|
60
|
+
const newEnabledServicesCompareKey = newEnabledServices.sort().join(',')
|
|
61
|
+
|
|
62
|
+
if (enabledServicesCompareKey !== newEnabledServicesCompareKey) {
|
|
63
|
+
log(
|
|
64
|
+
`${chalk.magenta(
|
|
65
|
+
'Reloading',
|
|
66
|
+
)} Netlify Graph schema..., ${enabledServicesCompareKey} => ${newEnabledServicesCompareKey}`,
|
|
67
|
+
)
|
|
68
|
+
await refetchAndGenerateFromOneGraph({ netlifyGraphConfig, state, netlifyToken: innerNetlifyToken, siteId })
|
|
69
|
+
log(`${chalk.green('Reloaded')} Netlify Graph schema and regenerated functions`)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const close = () => {
|
|
74
|
+
shouldClose = true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let handle
|
|
78
|
+
|
|
79
|
+
const helper = async () => {
|
|
80
|
+
if (shouldClose) {
|
|
81
|
+
clearTimeout(handle)
|
|
82
|
+
onClose()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const next = await OneGraphClient.fetchCliSessionEvents({ appId, authToken: netlifyToken, sessionId })
|
|
86
|
+
|
|
87
|
+
if (next.errors) {
|
|
88
|
+
next.errors.forEach((fetchEventError) => {
|
|
89
|
+
onError(fetchEventError)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { events } = next
|
|
94
|
+
|
|
95
|
+
if (events.length !== 0) {
|
|
96
|
+
const ackIds = await onEvents(events)
|
|
97
|
+
await OneGraphClient.ackCLISessionEvents({ appId, authToken: netlifyToken, sessionId, eventIds: ackIds })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await enabledServiceWatcher(netlifyToken, appId)
|
|
101
|
+
|
|
102
|
+
handle = setTimeout(helper, frequency)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Fire immediately to start rather than waiting the initial `frequency`
|
|
106
|
+
helper()
|
|
107
|
+
|
|
108
|
+
return close
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Fetch the schema for a site, and regenerate all of the downstream files
|
|
113
|
+
* @param {object} input
|
|
114
|
+
* @param {string} input.siteId The id of the site to query against
|
|
115
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
116
|
+
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
117
|
+
* @param {state} input.state A function to call to set/get the current state of the local Netlify project
|
|
118
|
+
* @returns {Promise<void>}
|
|
119
|
+
*/
|
|
120
|
+
const refetchAndGenerateFromOneGraph = async (input) => {
|
|
121
|
+
const { netlifyGraphConfig, netlifyToken, siteId, state } = input
|
|
122
|
+
await OneGraphClient.ensureAppForSite(netlifyToken, siteId)
|
|
123
|
+
|
|
124
|
+
const enabledServicesInfo = await OneGraphClient.fetchEnabledServices(netlifyToken, siteId)
|
|
125
|
+
if (!enabledServicesInfo) {
|
|
126
|
+
warn('Unable to fetch enabled services for site for code generation')
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const enabledServices = enabledServicesInfo
|
|
131
|
+
.map((service) => service.service)
|
|
132
|
+
.sort((aString, bString) => aString.localeCompare(bString))
|
|
133
|
+
const schema = await OneGraphClient.fetchOneGraphSchema(siteId, enabledServices)
|
|
134
|
+
|
|
135
|
+
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
136
|
+
if (currentOperationsDoc.trim().length === 0) {
|
|
137
|
+
currentOperationsDoc = defaultExampleOperationsDoc
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parsedDoc = parse(currentOperationsDoc)
|
|
141
|
+
const operations = extractFunctionsFromOperationDoc(parsedDoc)
|
|
142
|
+
|
|
143
|
+
generateFunctionsFile(netlifyGraphConfig, schema, currentOperationsDoc, operations)
|
|
144
|
+
writeGraphQLSchemaFile(netlifyGraphConfig, schema)
|
|
145
|
+
state.set('oneGraphEnabledServices', enabledServices)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
* @param {object} input
|
|
151
|
+
* @param {string} input.siteId The site id to query against
|
|
152
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
153
|
+
* @param {string} input.docId The GraphQL operations document id to fetch
|
|
154
|
+
* @param {string} input.schema The GraphQL schema to use when generating code
|
|
155
|
+
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
156
|
+
* @returns
|
|
157
|
+
*/
|
|
158
|
+
const updateGraphQLOperationsFile = async (input) => {
|
|
159
|
+
const { docId, netlifyGraphConfig, netlifyToken, schema, siteId } = input
|
|
160
|
+
const persistedDoc = await OneGraphClient.fetchPersistedQuery(netlifyToken, siteId, docId)
|
|
161
|
+
if (!persistedDoc) {
|
|
162
|
+
warn('No persisted doc found for:', docId)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const doc = persistedDoc.query
|
|
167
|
+
|
|
168
|
+
writeGraphQLOperationsSourceFile(netlifyGraphConfig, doc)
|
|
169
|
+
const appOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
170
|
+
const parsedDoc = parse(appOperationsDoc, {
|
|
171
|
+
noLocation: true,
|
|
172
|
+
})
|
|
173
|
+
const operations = extractFunctionsFromOperationDoc(parsedDoc)
|
|
174
|
+
generateFunctionsFile(netlifyGraphConfig, schema, appOperationsDoc, operations)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const handleCliSessionEvent = async ({ event, netlifyGraphConfig, netlifyToken, schema, siteId }) => {
|
|
178
|
+
const { __typename, payload } = await event
|
|
179
|
+
switch (__typename) {
|
|
180
|
+
case 'OneGraphNetlifyCliSessionTestEvent':
|
|
181
|
+
await handleCliSessionEvent({ netlifyToken, event: payload, netlifyGraphConfig, schema, siteId })
|
|
182
|
+
break
|
|
183
|
+
case 'OneGraphNetlifyCliSessionGenerateHandlerEvent':
|
|
184
|
+
await generateHandler(netlifyGraphConfig, schema, payload.operationId, payload)
|
|
185
|
+
break
|
|
186
|
+
case 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent':
|
|
187
|
+
await updateGraphQLOperationsFile({ netlifyToken, docId: payload.docId, netlifyGraphConfig, schema, siteId })
|
|
188
|
+
break
|
|
189
|
+
default: {
|
|
190
|
+
warn(`Unrecognized event received, you may need to upgrade your CLI version`, __typename, payload)
|
|
191
|
+
break
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Load the CLI session id from the local state
|
|
198
|
+
* @param {state} state
|
|
199
|
+
* @returns
|
|
200
|
+
*/
|
|
201
|
+
const loadCLISession = (state) => state.get('oneGraphSessionId')
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Idemponentially save the CLI session id to the local state and start monitoring for CLI events and upstream schema changes
|
|
205
|
+
* @param {object} input
|
|
206
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
207
|
+
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
208
|
+
* @param {state} input.state A function to call to set/get the current state of the local Netlify project
|
|
209
|
+
* @param {site} input.site The site object
|
|
210
|
+
*/
|
|
211
|
+
const startOneGraphCLISession = async (input) => {
|
|
212
|
+
const { netlifyGraphConfig, netlifyToken, site, state } = input
|
|
213
|
+
OneGraphClient.ensureAppForSite(netlifyToken, site.id)
|
|
214
|
+
let oneGraphSessionId = loadCLISession(state)
|
|
215
|
+
if (!oneGraphSessionId) {
|
|
216
|
+
const sessionName = generateSessionName()
|
|
217
|
+
const sessionMetadata = {}
|
|
218
|
+
const oneGraphSession = await OneGraphClient.createCLISession(netlifyToken, site.id, sessionName, sessionMetadata)
|
|
219
|
+
state.set('oneGraphSessionId', oneGraphSession.id)
|
|
220
|
+
oneGraphSessionId = state.get('oneGraphSessionId')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const enabledServices = []
|
|
224
|
+
const schema = await OneGraphClient.fetchOneGraphSchema(site.id, enabledServices)
|
|
225
|
+
|
|
226
|
+
monitorCLISessionEvents({
|
|
227
|
+
appId: site.id,
|
|
228
|
+
netlifyToken,
|
|
229
|
+
netlifyGraphConfig,
|
|
230
|
+
sessionId: oneGraphSessionId,
|
|
231
|
+
state,
|
|
232
|
+
onEvents: async (events) => {
|
|
233
|
+
for (const event of events) {
|
|
234
|
+
const eventName = OneGraphClient.friendlyEventName(event)
|
|
235
|
+
log(`${chalk.magenta('Handling')} Netlify Graph: ${eventName}...`)
|
|
236
|
+
await handleCliSessionEvent({ netlifyToken, event, netlifyGraphConfig, schema, siteId: site.id })
|
|
237
|
+
log(`${chalk.green('Finished handling')} Netlify Graph: ${eventName}...`)
|
|
238
|
+
}
|
|
239
|
+
return events.map((event) => event.id)
|
|
240
|
+
},
|
|
241
|
+
onError: (fetchEventError) => {
|
|
242
|
+
error(`Netlify Graph upstream error: ${fetchEventError}`)
|
|
243
|
+
},
|
|
244
|
+
onClose: () => {
|
|
245
|
+
log('Netlify Graph upstream closed')
|
|
246
|
+
},
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Generate a session name that can be identified as belonging to the current checkout
|
|
252
|
+
* @returns {string} The name of the session to create
|
|
253
|
+
*/
|
|
254
|
+
const generateSessionName = () => {
|
|
255
|
+
const userInfo = os.userInfo({ encoding: 'utf-8' })
|
|
256
|
+
const sessionName = `${userInfo.username}-${Date.now()}`
|
|
257
|
+
log(`Generated Netlify Graph session name: ${sessionName}`)
|
|
258
|
+
return sessionName
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const OneGraphCliClient = {
|
|
262
|
+
ackCLISessionEvents: OneGraphClient.ackCLISessionEvents,
|
|
263
|
+
createCLISession,
|
|
264
|
+
createPersistedQuery,
|
|
265
|
+
fetchCliSessionEvents: OneGraphClient.fetchCliSessionEvents,
|
|
266
|
+
ensureAppForSite,
|
|
267
|
+
updateCLISessionMetadata,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = {
|
|
271
|
+
OneGraphCliClient,
|
|
272
|
+
handleCliSessionEvent,
|
|
273
|
+
generateSessionName,
|
|
274
|
+
loadCLISession,
|
|
275
|
+
monitorCLISessionEvents,
|
|
276
|
+
refetchAndGenerateFromOneGraph,
|
|
277
|
+
startOneGraphCLISession,
|
|
278
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const process = require('process')
|
|
4
|
+
|
|
5
|
+
const { GraphQL, InternalConsole, NetlifyGraph } = require('netlify-onegraph-internal')
|
|
6
|
+
|
|
7
|
+
const { detectServerSettings, error, getFunctionsDir, log, warn } = require('../../utils')
|
|
8
|
+
|
|
9
|
+
const { printSchema } = GraphQL
|
|
10
|
+
|
|
11
|
+
const internalConsole = {
|
|
12
|
+
log,
|
|
13
|
+
warn,
|
|
14
|
+
error,
|
|
15
|
+
debug: console.debug,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
InternalConsole.registerConsole(internalConsole)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Remove any relative path components from the given path
|
|
22
|
+
* @param {string[]} items Filesystem path items to filter
|
|
23
|
+
* @return {string[]} Filtered filesystem path items
|
|
24
|
+
*/
|
|
25
|
+
const filterRelativePathItems = (items) => items.filter((part) => part !== '')
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Return a full NetlifyGraph config with any defaults overridden by netlify.toml
|
|
29
|
+
* @param {import('../base-command').BaseCommand} command
|
|
30
|
+
* @return {NetlifyGraphConfig} NetlifyGraphConfig
|
|
31
|
+
*/
|
|
32
|
+
const getNetlifyGraphConfig = async ({ command, options }) => {
|
|
33
|
+
const { config, site } = command.netlify
|
|
34
|
+
config.dev = { ...config.dev }
|
|
35
|
+
config.build = { ...config.build }
|
|
36
|
+
const userSpecifiedConfig = (config && config.graph) || {}
|
|
37
|
+
/** @type {import('./types').DevConfig} */
|
|
38
|
+
const devConfig = {
|
|
39
|
+
framework: '#auto',
|
|
40
|
+
...(config.functionsDirectory && { functions: config.functionsDirectory }),
|
|
41
|
+
...(config.build.publish && { publish: config.build.publish }),
|
|
42
|
+
...config.dev,
|
|
43
|
+
...options,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @type {Partial<import('../../utils/types').ServerSettings>} */
|
|
47
|
+
let settings = {}
|
|
48
|
+
try {
|
|
49
|
+
settings = await detectServerSettings(devConfig, options, site.root)
|
|
50
|
+
} catch (detectServerSettingsError) {
|
|
51
|
+
error(detectServerSettingsError)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const siteRoot = [path.sep, ...filterRelativePathItems(site.root.split(path.sep))]
|
|
55
|
+
|
|
56
|
+
const tsConfig = 'tsconfig.json'
|
|
57
|
+
const autodetectedLanguage = fs.existsSync(tsConfig) ? 'typescript' : 'javascript'
|
|
58
|
+
|
|
59
|
+
const framework = settings.framework || userSpecifiedConfig.framework
|
|
60
|
+
const isNextjs = framework === 'Next.js'
|
|
61
|
+
const detectedFunctionsPathString = getFunctionsDir({ config, options })
|
|
62
|
+
const detectedFunctionsPath = detectedFunctionsPathString ? detectedFunctionsPathString.split(path.sep) : null
|
|
63
|
+
const functionsPath = filterRelativePathItems(isNextjs ? [...siteRoot, 'pages', 'api'] : [...detectedFunctionsPath])
|
|
64
|
+
const netlifyGraphPath = filterRelativePathItems(
|
|
65
|
+
isNextjs
|
|
66
|
+
? [...siteRoot, 'lib', 'netlifyGraph']
|
|
67
|
+
: [...siteRoot, ...NetlifyGraph.defaultNetlifyGraphConfig.netlifyGraphPath],
|
|
68
|
+
)
|
|
69
|
+
const baseConfig = { ...NetlifyGraph.defaultNetlifyGraphConfig, ...userSpecifiedConfig }
|
|
70
|
+
const netlifyGraphImplementationFilename = [...netlifyGraphPath, `index.${baseConfig.extension}`]
|
|
71
|
+
const netlifyGraphTypeDefinitionsFilename = [...netlifyGraphPath, `index.d.ts`]
|
|
72
|
+
const graphQLOperationsSourceFilename = [...netlifyGraphPath, NetlifyGraph.defaultSourceOperationsFilename]
|
|
73
|
+
const graphQLSchemaFilename = [...netlifyGraphPath, NetlifyGraph.defaultGraphQLSchemaFilename]
|
|
74
|
+
const netlifyGraphRequirePath = isNextjs ? ['..', '..', 'lib', 'netlifyGraph'] : [`./netlifyGraph`]
|
|
75
|
+
const language = userSpecifiedConfig.language || autodetectedLanguage
|
|
76
|
+
const moduleType = baseConfig.moduleType || isNextjs ? 'esm' : 'commonjs'
|
|
77
|
+
const fullConfig = {
|
|
78
|
+
...baseConfig,
|
|
79
|
+
functionsPath,
|
|
80
|
+
netlifyGraphPath,
|
|
81
|
+
netlifyGraphImplementationFilename,
|
|
82
|
+
netlifyGraphTypeDefinitionsFilename,
|
|
83
|
+
graphQLOperationsSourceFilename,
|
|
84
|
+
graphQLSchemaFilename,
|
|
85
|
+
netlifyGraphRequirePath,
|
|
86
|
+
framework,
|
|
87
|
+
language,
|
|
88
|
+
moduleType,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return fullConfig
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Given a NetlifyGraphConfig, ensure that the netlifyGraphPath exists
|
|
96
|
+
* @param {NetlifyGraphConfig} netlifyGraphConfig
|
|
97
|
+
*/
|
|
98
|
+
const ensureNetlifyGraphPath = (netlifyGraphConfig) => {
|
|
99
|
+
fs.mkdirSync(path.resolve(...netlifyGraphConfig.netlifyGraphPath), { recursive: true })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Given a NetlifyGraphConfig, ensure that the functionsPath exists
|
|
104
|
+
* @param {NetlifyGraphConfig} netlifyGraphConfig
|
|
105
|
+
*/
|
|
106
|
+
const ensureFunctionsPath = (netlifyGraphConfig) => {
|
|
107
|
+
fs.mkdirSync(path.resolve(...netlifyGraphConfig.functionsPath), { recursive: true })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generate a library file with type definitions for a given NetlifyGraphConfig, operationsDoc, and schema, writing them to the filesystem
|
|
112
|
+
* @param {NetlifyGraphConfig} netlifyGraphConfig
|
|
113
|
+
* @param {GraphQLSchema} schema The schema to use when generating the functions and their types
|
|
114
|
+
* @param {string} operationsDoc The GraphQL operations doc to use when generating the functions
|
|
115
|
+
* @param {NetlifyGraph.ParsedFunction} queries The parsed queries with metadata to use when generating library functions
|
|
116
|
+
*/
|
|
117
|
+
const generateFunctionsFile = (netlifyGraphConfig, schema, operationsDoc, queries) => {
|
|
118
|
+
const { clientSource, typeDefinitionsSource } = NetlifyGraph.generateFunctionsSource(
|
|
119
|
+
netlifyGraphConfig,
|
|
120
|
+
schema,
|
|
121
|
+
operationsDoc,
|
|
122
|
+
queries,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
ensureNetlifyGraphPath(netlifyGraphConfig)
|
|
126
|
+
fs.writeFileSync(path.resolve(...netlifyGraphConfig.netlifyGraphImplementationFilename), clientSource, 'utf8')
|
|
127
|
+
fs.writeFileSync(
|
|
128
|
+
path.resolve(...netlifyGraphConfig.netlifyGraphTypeDefinitionsFilename),
|
|
129
|
+
typeDefinitionsSource,
|
|
130
|
+
'utf8',
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Using the given NetlifyGraphConfig, read the GraphQL operations file and return the _unparsed_ GraphQL operations doc
|
|
136
|
+
* @param {NetlifyGraphConfig} netlifyGraphConfig
|
|
137
|
+
* @returns {string} GraphQL operations doc
|
|
138
|
+
*/
|
|
139
|
+
const readGraphQLOperationsSourceFile = (netlifyGraphConfig) => {
|
|
140
|
+
ensureNetlifyGraphPath(netlifyGraphConfig)
|
|
141
|
+
|
|
142
|
+
const fullFilename = path.resolve(...netlifyGraphConfig.graphQLOperationsSourceFilename)
|
|
143
|
+
if (!fs.existsSync(fullFilename)) {
|
|
144
|
+
fs.writeFileSync(fullFilename, '')
|
|
145
|
+
fs.closeSync(fs.openSync(fullFilename, 'w'))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const source = fs.readFileSync(fullFilename, 'utf8')
|
|
149
|
+
|
|
150
|
+
return source
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Write an operations doc to the filesystem using the given NetlifyGraphConfig
|
|
155
|
+
* @param {NetlifyGraphConfig} netlifyGraphConfig
|
|
156
|
+
* @param {string} operationsDoc The GraphQL operations doc to write
|
|
157
|
+
*/
|
|
158
|
+
const writeGraphQLOperationsSourceFile = (netlifyGraphConfig, operationDocString) => {
|
|
159
|
+
const graphqlSource = operationDocString
|
|
160
|
+
|
|
161
|
+
ensureNetlifyGraphPath(netlifyGraphConfig)
|
|
162
|
+
fs.writeFileSync(path.resolve(...netlifyGraphConfig.graphQLOperationsSourceFilename), graphqlSource, 'utf8')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Write a GraphQL Schema printed in SDL format to the filesystem using the given NetlifyGraphConfig
|
|
167
|
+
* @param {NetlifyGraphConfig} netlifyGraphConfig
|
|
168
|
+
* @param {GraphQLSchema} schema The GraphQL schema to print and write to the filesystem
|
|
169
|
+
*/
|
|
170
|
+
const writeGraphQLSchemaFile = (netlifyGraphConfig, schema) => {
|
|
171
|
+
const graphqlSource = printSchema(schema)
|
|
172
|
+
|
|
173
|
+
ensureNetlifyGraphPath(netlifyGraphConfig)
|
|
174
|
+
fs.writeFileSync(path.resolve(...netlifyGraphConfig.graphQLSchemaFilename), graphqlSource, 'utf8')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Using the given NetlifyGraphConfig, read the GraphQL schema file and return it _unparsed_
|
|
179
|
+
* @param {NetlifyGraphConfig} netlifyGraphConfig
|
|
180
|
+
* @returns {string} GraphQL schema
|
|
181
|
+
*/
|
|
182
|
+
const readGraphQLSchemaFile = (netlifyGraphConfig) => {
|
|
183
|
+
ensureNetlifyGraphPath(netlifyGraphConfig)
|
|
184
|
+
return fs.readFileSync(path.resolve(...netlifyGraphConfig.graphQLSchemaFilename), 'utf8')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Given a NetlifyGraphConfig, read the appropriate files and write a handler for the given operationId to the filesystem
|
|
189
|
+
* @param {NetlifyGraphConfig} netlifyGraphConfig
|
|
190
|
+
* @param {GraphQLSchema} schema The GraphQL schema to use when generating the handler
|
|
191
|
+
* @param {string} operationId The operationId to use when generating the handler
|
|
192
|
+
* @param {object} handlerOptions The options to use when generating the handler
|
|
193
|
+
* @returns
|
|
194
|
+
*/
|
|
195
|
+
const generateHandler = (netlifyGraphConfig, schema, operationId, handlerOptions) => {
|
|
196
|
+
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
197
|
+
if (currentOperationsDoc.trim().length === 0) {
|
|
198
|
+
currentOperationsDoc = NetlifyGraph.defaultExampleOperationsDoc
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const result = NetlifyGraph.generateHandlerSource({
|
|
202
|
+
handlerOptions,
|
|
203
|
+
schema,
|
|
204
|
+
netlifyGraphConfig,
|
|
205
|
+
operationId,
|
|
206
|
+
operationsDoc: currentOperationsDoc,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
if (!result) {
|
|
210
|
+
warn(`No handler was generated for operationId ${operationId}`)
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const { exportedFiles, operation } = result
|
|
215
|
+
|
|
216
|
+
ensureFunctionsPath(netlifyGraphConfig)
|
|
217
|
+
|
|
218
|
+
if (!exportedFiles) {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
exportedFiles.forEach((exportedFile) => {
|
|
223
|
+
const { content } = exportedFile
|
|
224
|
+
const isNamed = exportedFile.kind === 'NamedExportedFile'
|
|
225
|
+
|
|
226
|
+
let filenameArr
|
|
227
|
+
|
|
228
|
+
if (isNamed) {
|
|
229
|
+
filenameArr = [...exportedFile.name]
|
|
230
|
+
} else {
|
|
231
|
+
const operationName = (operation.name && operation.name.value) || 'Unnamed'
|
|
232
|
+
const fileExtension = netlifyGraphConfig.language === 'typescript' ? 'ts' : netlifyGraphConfig.extension
|
|
233
|
+
const defaultBaseFilename = `${operationName}.${fileExtension}`
|
|
234
|
+
const baseFilename = defaultBaseFilename
|
|
235
|
+
|
|
236
|
+
filenameArr = [path.sep, ...netlifyGraphConfig.functionsPath, baseFilename]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const absoluteFilename = path.resolve(...filenameArr)
|
|
240
|
+
|
|
241
|
+
fs.writeFileSync(absoluteFilename, content)
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Export the minimal set of functions that are required for Netlify Graph
|
|
246
|
+
const { buildSchema, parse } = GraphQL
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
*
|
|
250
|
+
* @param {object} options
|
|
251
|
+
* @param {string} options.siteName The name of the site as used in the Netlify UI url scheme
|
|
252
|
+
* @param {string} options.oneGraphSessionId The oneGraph session id to use when generating the graph-edit link
|
|
253
|
+
* @returns {string} The url to the Netlify Graph UI for the current session
|
|
254
|
+
*/
|
|
255
|
+
const getGraphEditUrlBySiteName = ({ oneGraphSessionId, siteName }) => {
|
|
256
|
+
const host = 'app.netlify.com' || process.env.NETLIFY_APP_HOST
|
|
257
|
+
// http because app.netlify.com will redirect to https, and localhost will still work for development
|
|
258
|
+
const url = `http://${host}/sites/${siteName}/graph/explorer?cliSessionId=${oneGraphSessionId}`
|
|
259
|
+
|
|
260
|
+
return url
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = {
|
|
264
|
+
buildSchema,
|
|
265
|
+
defaultExampleOperationsDoc: NetlifyGraph.defaultExampleOperationsDoc,
|
|
266
|
+
extractFunctionsFromOperationDoc: NetlifyGraph.extractFunctionsFromOperationDoc,
|
|
267
|
+
generateFunctionsSource: NetlifyGraph.generateFunctionsSource,
|
|
268
|
+
generateFunctionsFile,
|
|
269
|
+
generateHandlerSource: NetlifyGraph.generateHandlerSource,
|
|
270
|
+
generateHandler,
|
|
271
|
+
getGraphEditUrlBySiteName,
|
|
272
|
+
getNetlifyGraphConfig,
|
|
273
|
+
parse,
|
|
274
|
+
readGraphQLOperationsSourceFile,
|
|
275
|
+
readGraphQLSchemaFile,
|
|
276
|
+
writeGraphQLOperationsSourceFile,
|
|
277
|
+
writeGraphQLSchemaFile,
|
|
278
|
+
}
|