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,1033 +0,0 @@
|
|
|
1
|
-
/* eslint-disable eslint-comments/disable-enable-pair */
|
|
2
|
-
|
|
3
|
-
// @ts-check
|
|
4
|
-
import fs from 'fs'
|
|
5
|
-
import path from 'path'
|
|
6
|
-
import process from 'process'
|
|
7
|
-
import { pathToFileURL } from 'url'
|
|
8
|
-
|
|
9
|
-
import inquirer from 'inquirer'
|
|
10
|
-
import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
|
|
11
|
-
import { GraphQL, GraphQLHelpers, IncludedCodegen, InternalConsole, NetlifyGraph } from 'netlify-onegraph-internal'
|
|
12
|
-
|
|
13
|
-
import { chalk, error, log, warn } from '../../utils/command-helpers.mjs'
|
|
14
|
-
import detectServerSettings from '../../utils/detect-server-settings.mjs'
|
|
15
|
-
import execa from '../../utils/execa.mjs'
|
|
16
|
-
import { getFunctionsDir } from '../../utils/functions/index.mjs'
|
|
17
|
-
|
|
18
|
-
const { printSchema } = GraphQL
|
|
19
|
-
|
|
20
|
-
const internalConsole = {
|
|
21
|
-
log,
|
|
22
|
-
warn,
|
|
23
|
-
error,
|
|
24
|
-
debug: console.debug,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
InternalConsole.registerConsole(internalConsole)
|
|
28
|
-
|
|
29
|
-
const { defaultExampleOperationsDoc, extractFunctionsFromOperationDoc, generateHandlerSource } = NetlifyGraph
|
|
30
|
-
const { normalizeOperationsDoc } = GraphQLHelpers
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Updates the netlify.toml in-place with the `graph.codeGenerator` key set to `codegenModuleImportPath
|
|
34
|
-
* It's a very hacky, string-based implementation for because
|
|
35
|
-
* 1. There isn't a good toml parser/updater/pretty-printer that preserves formatting and comments
|
|
36
|
-
* 2. We want to make minimal changes to `netlify.toml`
|
|
37
|
-
* @param {object} input
|
|
38
|
-
* @param {string} input.siteRoot
|
|
39
|
-
* @param {string} input.codegenModuleImportPath
|
|
40
|
-
* @returns void
|
|
41
|
-
*/
|
|
42
|
-
const setNetlifyTomlCodeGeneratorModule = ({ codegenModuleImportPath, siteRoot }) => {
|
|
43
|
-
let toml
|
|
44
|
-
let filepath
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const filepathArr = ['/', ...siteRoot.split(path.sep), 'netlify.toml']
|
|
48
|
-
filepath = path.resolve(...filepathArr)
|
|
49
|
-
const configText = fs.readFileSync(filepath, 'utf-8')
|
|
50
|
-
|
|
51
|
-
toml = configText
|
|
52
|
-
} catch (error_) {
|
|
53
|
-
warn(`Error reading netlify.toml for Netlify Graph codegenModule update: ${error_}`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!filepath) {
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const entry = ` codeGenerator = "${codegenModuleImportPath}"
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
const fullEntry = `
|
|
64
|
-
[graph]
|
|
65
|
-
${entry}`
|
|
66
|
-
|
|
67
|
-
if (!toml) {
|
|
68
|
-
fs.writeFileSync(filepath, fullEntry, 'utf-8')
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const EOL = '\n'
|
|
73
|
-
let lines = toml.split(EOL)
|
|
74
|
-
const graphKeyLine = lines.findIndex((line) => line.trim().startsWith('[graph]'))
|
|
75
|
-
const hasGraphKey = graphKeyLine !== -1
|
|
76
|
-
const codegenKeyLine = lines.findIndex((line) => line.trim().startsWith('codeGenerator'))
|
|
77
|
-
const hasCodegenKeyLine = codegenKeyLine !== 1
|
|
78
|
-
|
|
79
|
-
if (hasGraphKey && hasCodegenKeyLine) {
|
|
80
|
-
lines.splice(codegenKeyLine, 1, entry)
|
|
81
|
-
} else if (hasGraphKey) {
|
|
82
|
-
lines.splice(graphKeyLine, 0, entry)
|
|
83
|
-
} else {
|
|
84
|
-
lines = [...lines, ...fullEntry.split(EOL)]
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const newToml = lines.join(EOL)
|
|
88
|
-
|
|
89
|
-
fs.writeFileSync(filepath, newToml, 'utf-8')
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Remove any relative path components from the given path
|
|
94
|
-
* @param {string[]} items Filesystem path items to filter
|
|
95
|
-
* @return {string[]} Filtered filesystem path items
|
|
96
|
-
*/
|
|
97
|
-
const filterRelativePathItems = (items) => items.filter((part) => part !== '')
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Return the default Netlify Graph configuration for a generic site
|
|
101
|
-
* @param {object} input
|
|
102
|
-
* @param {object} input.baseConfig
|
|
103
|
-
* @param {string[]} input.detectedFunctionsPath
|
|
104
|
-
* @param {string[]} input.siteRoot
|
|
105
|
-
*/
|
|
106
|
-
const makeDefaultNetlifyGraphConfig = ({ baseConfig, detectedFunctionsPath }) => {
|
|
107
|
-
const functionsPath = filterRelativePathItems([...detectedFunctionsPath])
|
|
108
|
-
const webhookBasePath = '/.netlify/functions'
|
|
109
|
-
const netlifyGraphPath = [...functionsPath, 'netlifyGraph']
|
|
110
|
-
const netlifyGraphImplementationFilename = [...netlifyGraphPath, `index.${baseConfig.extension}`]
|
|
111
|
-
const netlifyGraphTypeDefinitionsFilename = [...netlifyGraphPath, `index.d.ts`]
|
|
112
|
-
const graphQLOperationsSourceFilename = [...netlifyGraphPath, NetlifyGraph.defaultSourceOperationsFilename]
|
|
113
|
-
const graphQLSchemaFilename = [...netlifyGraphPath, NetlifyGraph.defaultGraphQLSchemaFilename]
|
|
114
|
-
const netlifyGraphRequirePath = [`./netlifyGraph`]
|
|
115
|
-
const moduleType = baseConfig.moduleType || 'esm'
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
functionsPath,
|
|
119
|
-
webhookBasePath,
|
|
120
|
-
netlifyGraphPath,
|
|
121
|
-
netlifyGraphImplementationFilename,
|
|
122
|
-
netlifyGraphTypeDefinitionsFilename,
|
|
123
|
-
graphQLOperationsSourceFilename,
|
|
124
|
-
graphQLSchemaFilename,
|
|
125
|
-
netlifyGraphRequirePath,
|
|
126
|
-
moduleType,
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Return the default Netlify Graph configuration for a Nextjs site
|
|
132
|
-
* @param {object} input
|
|
133
|
-
* @param {object} input.baseConfig
|
|
134
|
-
* @param {string[]} input.detectedFunctionsPath
|
|
135
|
-
* @param {string[]} input.siteRoot
|
|
136
|
-
*/
|
|
137
|
-
const makeDefaultNextJsNetlifyGraphConfig = ({ baseConfig, siteRoot }) => {
|
|
138
|
-
const functionsPath = filterRelativePathItems([...siteRoot, 'pages', 'api'])
|
|
139
|
-
const webhookBasePath = '/api'
|
|
140
|
-
const netlifyGraphPath = filterRelativePathItems([...siteRoot, 'lib', 'netlifyGraph'])
|
|
141
|
-
const netlifyGraphImplementationFilename = [...netlifyGraphPath, `index.${baseConfig.extension}`]
|
|
142
|
-
const netlifyGraphTypeDefinitionsFilename = [...netlifyGraphPath, `index.d.ts`]
|
|
143
|
-
const graphQLOperationsSourceFilename = [...netlifyGraphPath, NetlifyGraph.defaultSourceOperationsFilename]
|
|
144
|
-
const graphQLSchemaFilename = [...netlifyGraphPath, NetlifyGraph.defaultGraphQLSchemaFilename]
|
|
145
|
-
const netlifyGraphRequirePath = ['..', '..', 'lib', 'netlifyGraph']
|
|
146
|
-
const moduleType = baseConfig.moduleType || 'esm'
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
functionsPath,
|
|
150
|
-
webhookBasePath,
|
|
151
|
-
netlifyGraphPath,
|
|
152
|
-
netlifyGraphImplementationFilename,
|
|
153
|
-
netlifyGraphTypeDefinitionsFilename,
|
|
154
|
-
graphQLOperationsSourceFilename,
|
|
155
|
-
graphQLSchemaFilename,
|
|
156
|
-
netlifyGraphRequirePath,
|
|
157
|
-
moduleType,
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Return the default Netlify Graph configuration for a Remix site
|
|
163
|
-
* @param {object} input
|
|
164
|
-
* @param {object} input.baseConfig
|
|
165
|
-
* @param {string[]} input.detectedFunctionsPath
|
|
166
|
-
* @param {string[]} input.siteRoot
|
|
167
|
-
*/
|
|
168
|
-
const makeDefaultRemixNetlifyGraphConfig = ({ baseConfig, detectedFunctionsPath, siteRoot }) => {
|
|
169
|
-
const functionsPath = filterRelativePathItems([...detectedFunctionsPath])
|
|
170
|
-
const webhookBasePath = '/webhooks'
|
|
171
|
-
const netlifyGraphPath = filterRelativePathItems([
|
|
172
|
-
...siteRoot,
|
|
173
|
-
...NetlifyGraph.defaultNetlifyGraphConfig.netlifyGraphPath,
|
|
174
|
-
])
|
|
175
|
-
const netlifyGraphImplementationFilename = [...netlifyGraphPath, `index.${baseConfig.extension}`]
|
|
176
|
-
const netlifyGraphTypeDefinitionsFilename = [...netlifyGraphPath, `index.d.ts`]
|
|
177
|
-
const graphQLOperationsSourceFilename = [...netlifyGraphPath, NetlifyGraph.defaultSourceOperationsFilename]
|
|
178
|
-
const graphQLSchemaFilename = [...netlifyGraphPath, NetlifyGraph.defaultGraphQLSchemaFilename]
|
|
179
|
-
const netlifyGraphRequirePath = [`../../netlify/functions/netlifyGraph`]
|
|
180
|
-
const moduleType = 'esm'
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
functionsPath,
|
|
184
|
-
webhookBasePath,
|
|
185
|
-
netlifyGraphPath,
|
|
186
|
-
netlifyGraphImplementationFilename,
|
|
187
|
-
netlifyGraphTypeDefinitionsFilename,
|
|
188
|
-
graphQLOperationsSourceFilename,
|
|
189
|
-
graphQLSchemaFilename,
|
|
190
|
-
netlifyGraphRequirePath,
|
|
191
|
-
moduleType,
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const defaultFrameworkLookup = {
|
|
196
|
-
'Next.js': makeDefaultNextJsNetlifyGraphConfig,
|
|
197
|
-
Remix: makeDefaultRemixNetlifyGraphConfig,
|
|
198
|
-
default: makeDefaultNetlifyGraphConfig,
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Return a full NetlifyGraph config with any defaults overridden by netlify.toml
|
|
203
|
-
* @param {object} input
|
|
204
|
-
* @param {import('../../commands/base-command.mjs').default} input.command
|
|
205
|
-
* @param {import('commander').CommandOptions} input.options
|
|
206
|
-
* @param {Partial<import('../../utils/types').ServerSettings>=} input.settings
|
|
207
|
-
* @return {Promise<NetlifyGraph.NetlifyGraphConfig>} NetlifyGraphConfig
|
|
208
|
-
*/
|
|
209
|
-
const getNetlifyGraphConfig = async ({ command, options, settings }) => {
|
|
210
|
-
const { config, site } = command.netlify
|
|
211
|
-
config.dev = { ...config.dev }
|
|
212
|
-
config.build = { ...config.build }
|
|
213
|
-
const userSpecifiedConfig = (config && config.graph) || {}
|
|
214
|
-
/** @type {import('../../commands/dev/types').DevConfig} */
|
|
215
|
-
const devConfig = {
|
|
216
|
-
framework: '#auto',
|
|
217
|
-
...(config.functionsDirectory && { functions: config.functionsDirectory }),
|
|
218
|
-
...(config.build.publish && { publish: config.build.publish }),
|
|
219
|
-
...config.dev,
|
|
220
|
-
...options,
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** @type {Partial<import('../../utils/types').ServerSettings>} */
|
|
224
|
-
if (!settings) {
|
|
225
|
-
try {
|
|
226
|
-
settings = await detectServerSettings(devConfig, options, site.root)
|
|
227
|
-
} catch (detectServerSettingsError) {
|
|
228
|
-
settings = {}
|
|
229
|
-
warn(
|
|
230
|
-
`Error while auto-detecting project settings, Netlify Graph encountered problems: ${JSON.stringify(
|
|
231
|
-
detectServerSettingsError,
|
|
232
|
-
null,
|
|
233
|
-
2,
|
|
234
|
-
)}`,
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const defaulFunctionsPath = ['netlify', 'functions']
|
|
240
|
-
|
|
241
|
-
const siteRoot = [path.sep, ...filterRelativePathItems(site.root.split(path.sep))]
|
|
242
|
-
|
|
243
|
-
const tsConfig = 'tsconfig.json'
|
|
244
|
-
const autodetectedLanguage = fs.existsSync(tsConfig) ? 'typescript' : 'javascript'
|
|
245
|
-
|
|
246
|
-
const framework = settings.framework || userSpecifiedConfig.framework
|
|
247
|
-
const makeDefaultFrameworkConfig = defaultFrameworkLookup[framework] || defaultFrameworkLookup.default
|
|
248
|
-
|
|
249
|
-
const detectedFunctionsPathString = getFunctionsDir({ config, options })
|
|
250
|
-
const detectedFunctionsPath = detectedFunctionsPathString
|
|
251
|
-
? [path.sep, ...detectedFunctionsPathString.split(path.sep)]
|
|
252
|
-
: defaulFunctionsPath
|
|
253
|
-
const baseConfig = { ...NetlifyGraph.defaultNetlifyGraphConfig, ...userSpecifiedConfig }
|
|
254
|
-
const defaultFrameworkConfig = makeDefaultFrameworkConfig({ baseConfig, detectedFunctionsPath, siteRoot })
|
|
255
|
-
|
|
256
|
-
const userSpecifiedFunctionPath =
|
|
257
|
-
userSpecifiedConfig.functionsPath && userSpecifiedConfig.functionsPath.split(path.sep)
|
|
258
|
-
|
|
259
|
-
const functionsPath =
|
|
260
|
-
(userSpecifiedFunctionPath && [...siteRoot, ...userSpecifiedFunctionPath]) || defaultFrameworkConfig.functionsPath
|
|
261
|
-
const netlifyGraphPath =
|
|
262
|
-
(userSpecifiedConfig.netlifyGraphPath && userSpecifiedConfig.netlifyGraphPath.split(path.sep)) ||
|
|
263
|
-
defaultFrameworkConfig.netlifyGraphPath
|
|
264
|
-
const netlifyGraphImplementationFilename =
|
|
265
|
-
(userSpecifiedConfig.netlifyGraphImplementationFilename &&
|
|
266
|
-
userSpecifiedConfig.netlifyGraphImplementationFilename.split(path.sep)) ||
|
|
267
|
-
defaultFrameworkConfig.netlifyGraphImplementationFilename
|
|
268
|
-
const netlifyGraphTypeDefinitionsFilename =
|
|
269
|
-
(userSpecifiedConfig.netlifyGraphTypeDefinitionsFilename &&
|
|
270
|
-
userSpecifiedConfig.netlifyGraphTypeDefinitionsFilename.split(path.sep)) ||
|
|
271
|
-
defaultFrameworkConfig.netlifyGraphTypeDefinitionsFilename
|
|
272
|
-
const graphQLOperationsSourceFilename =
|
|
273
|
-
(userSpecifiedConfig.graphQLOperationsSourceFilename &&
|
|
274
|
-
userSpecifiedConfig.graphQLOperationsSourceFilename.split(path.sep)) ||
|
|
275
|
-
defaultFrameworkConfig.graphQLOperationsSourceFilename
|
|
276
|
-
const graphQLConfigJsonFilename =
|
|
277
|
-
(userSpecifiedConfig.graphQLConfigJsonFilename && userSpecifiedConfig.graphQLConfigJsonFilename.split(path.sep)) ||
|
|
278
|
-
defaultFrameworkConfig.graphQLConfigJsonFilename ||
|
|
279
|
-
baseConfig.graphQLConfigJsonFilename
|
|
280
|
-
const graphQLSchemaFilename =
|
|
281
|
-
(userSpecifiedConfig.graphQLSchemaFilename && userSpecifiedConfig.graphQLSchemaFilename.split(path.sep)) ||
|
|
282
|
-
defaultFrameworkConfig.graphQLSchemaFilename
|
|
283
|
-
const netlifyGraphRequirePath =
|
|
284
|
-
(userSpecifiedConfig.netlifyGraphRequirePath && userSpecifiedConfig.netlifyGraphRequirePath.split(path.sep)) ||
|
|
285
|
-
defaultFrameworkConfig.netlifyGraphRequirePath
|
|
286
|
-
const moduleType =
|
|
287
|
-
(userSpecifiedConfig.moduleType && userSpecifiedConfig.moduleType.split(path.sep)) ||
|
|
288
|
-
defaultFrameworkConfig.moduleType
|
|
289
|
-
const language = userSpecifiedConfig.language || autodetectedLanguage
|
|
290
|
-
const webhookBasePath =
|
|
291
|
-
(userSpecifiedConfig.webhookBasePath && userSpecifiedConfig.webhookBasePath.split(path.sep)) ||
|
|
292
|
-
defaultFrameworkConfig.webhookBasePath
|
|
293
|
-
const customGeneratorFile =
|
|
294
|
-
userSpecifiedConfig.customGeneratorFile && userSpecifiedConfig.customGeneratorFile.split(path.sep)
|
|
295
|
-
const runtimeTargetEnv = userSpecifiedConfig.runtimeTargetEnv || defaultFrameworkConfig.runtimeTargetEnv || 'node'
|
|
296
|
-
|
|
297
|
-
const fullConfig = {
|
|
298
|
-
...baseConfig,
|
|
299
|
-
functionsPath,
|
|
300
|
-
webhookBasePath,
|
|
301
|
-
netlifyGraphPath,
|
|
302
|
-
netlifyGraphImplementationFilename,
|
|
303
|
-
netlifyGraphTypeDefinitionsFilename,
|
|
304
|
-
graphQLOperationsSourceFilename,
|
|
305
|
-
graphQLSchemaFilename,
|
|
306
|
-
graphQLConfigJsonFilename,
|
|
307
|
-
netlifyGraphRequirePath,
|
|
308
|
-
framework,
|
|
309
|
-
language,
|
|
310
|
-
moduleType,
|
|
311
|
-
customGeneratorFile,
|
|
312
|
-
runtimeTargetEnv,
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return fullConfig
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Given a NetlifyGraphConfig, ensure that the netlifyGraphPath exists
|
|
320
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
|
|
321
|
-
*/
|
|
322
|
-
const ensureNetlifyGraphPath = (netlifyGraphConfig) => {
|
|
323
|
-
const fullPath = path.resolve(...netlifyGraphConfig.netlifyGraphPath)
|
|
324
|
-
fs.mkdirSync(fullPath, { recursive: true })
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Given a NetlifyGraphConfig, ensure that the functionsPath exists
|
|
329
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
|
|
330
|
-
*/
|
|
331
|
-
const ensureFunctionsPath = (netlifyGraphConfig) => {
|
|
332
|
-
const fullPath = path.resolve(...netlifyGraphConfig.functionsPath)
|
|
333
|
-
fs.mkdirSync(fullPath, { recursive: true })
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
let disablePrettierDueToPreviousError = false
|
|
337
|
-
|
|
338
|
-
const runPrettier = async (filePath) => {
|
|
339
|
-
if (disablePrettierDueToPreviousError) {
|
|
340
|
-
return
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
const commandProcess = execa('prettier', ['--write', filePath], {
|
|
345
|
-
preferLocal: true,
|
|
346
|
-
// windowsHide needs to be false for child process to terminate properly on Windows
|
|
347
|
-
windowsHide: false,
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
await commandProcess
|
|
351
|
-
// eslint-disable-next-line unicorn/prefer-optional-catch-binding
|
|
352
|
-
} catch (prettierError) {
|
|
353
|
-
// It would be nice to log this error to help debugging, but it's potentially a bit scary for the dev to see it
|
|
354
|
-
if (!disablePrettierDueToPreviousError) {
|
|
355
|
-
disablePrettierDueToPreviousError = true
|
|
356
|
-
warn("Error while running prettier, make sure you have installed it globally with 'npm i -g prettier'")
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Generate a library file with type definitions for a given NetlifyGraphConfig, operationsDoc, and schema, returning the in-memory source
|
|
363
|
-
* @param {object} input
|
|
364
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
|
|
365
|
-
* @param {GraphQL.GraphQLSchema} input.schema The schema to use when generating the functions and their types
|
|
366
|
-
* @param {string} input.schemaId The id of the schema to use when fetching Graph data
|
|
367
|
-
* @param {string} input.operationsDoc The GraphQL operations doc to use when generating the functions
|
|
368
|
-
* @param {Record<string, NetlifyGraph.ExtractedFunction>} input.functions The parsed queries with metadata to use when generating library functions
|
|
369
|
-
* @param {Record<string, NetlifyGraph.ExtractedFragment>} input.fragments The parsed queries with metadata to use when generating library functions
|
|
370
|
-
* @param {import('netlify-onegraph-internal').CodegenHelpers.GenerateRuntimeFunction} input.generate
|
|
371
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
372
|
-
* @returns {Promise<import('netlify-onegraph-internal').CodegenHelpers.NamedExportedFile[]>} In-memory files
|
|
373
|
-
*/
|
|
374
|
-
const generateRuntimeSource = async ({
|
|
375
|
-
fragments,
|
|
376
|
-
functions,
|
|
377
|
-
generate,
|
|
378
|
-
netlifyGraphConfig,
|
|
379
|
-
operationsDoc,
|
|
380
|
-
schema,
|
|
381
|
-
schemaId,
|
|
382
|
-
}) => {
|
|
383
|
-
const runtime = await NetlifyGraph.generateRuntime({
|
|
384
|
-
GraphQL,
|
|
385
|
-
netlifyGraphConfig,
|
|
386
|
-
schema,
|
|
387
|
-
operationsDoc,
|
|
388
|
-
operations: functions,
|
|
389
|
-
fragments,
|
|
390
|
-
generate,
|
|
391
|
-
schemaId,
|
|
392
|
-
})
|
|
393
|
-
|
|
394
|
-
return runtime
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Generate a library file with type definitions for a given NetlifyGraphConfig, operationsDoc, and schema, writing them to the filesystem
|
|
399
|
-
* @param {object} input
|
|
400
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
|
|
401
|
-
* @param {GraphQL.GraphQLSchema} input.schema The schema to use when generating the functions and their types
|
|
402
|
-
* @param {string} input.schemaId The id of the schema to use when fetching Graph data
|
|
403
|
-
* @param {string} input.operationsDoc The GraphQL operations doc to use when generating the functions
|
|
404
|
-
* @param {Record<string, NetlifyGraph.ExtractedFunction>} input.functions The parsed queries with metadata to use when generating library functions
|
|
405
|
-
* @param {Record<string, NetlifyGraph.ExtractedFragment>} input.fragments The parsed queries with metadata to use when generating library functions
|
|
406
|
-
* @param {import('netlify-onegraph-internal').CodegenHelpers.GenerateRuntimeFunction} input.generate
|
|
407
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
408
|
-
* @returns {Promise<void>} Void, effectfully writes the generated library to the filesystem
|
|
409
|
-
*/
|
|
410
|
-
const generateRuntime = async ({
|
|
411
|
-
fragments,
|
|
412
|
-
functions,
|
|
413
|
-
generate,
|
|
414
|
-
logger,
|
|
415
|
-
netlifyGraphConfig,
|
|
416
|
-
operationsDoc,
|
|
417
|
-
schema,
|
|
418
|
-
schemaId,
|
|
419
|
-
}) => {
|
|
420
|
-
const runtime = await generateRuntimeSource({
|
|
421
|
-
netlifyGraphConfig,
|
|
422
|
-
schema,
|
|
423
|
-
operationsDoc,
|
|
424
|
-
functions,
|
|
425
|
-
fragments,
|
|
426
|
-
generate,
|
|
427
|
-
schemaId,
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
runtime.forEach((file) => {
|
|
431
|
-
const implementationResolvedPath = path.resolve(...file.name)
|
|
432
|
-
fs.writeFileSync(implementationResolvedPath, file.content, 'utf8')
|
|
433
|
-
const implementationRelativePath = path.relative(process.cwd(), implementationResolvedPath)
|
|
434
|
-
logger && logger(`Wrote ${chalk.cyan(implementationRelativePath)}`)
|
|
435
|
-
runPrettier(path.resolve(...file.name))
|
|
436
|
-
})
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Generate a library file with type definitions for a given NetlifyGraphConfig, operationsDoc, and schema, writing them to the filesystem
|
|
441
|
-
* @param {object} input
|
|
442
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
|
|
443
|
-
* @param {object} input.config The parsed netlify.toml file
|
|
444
|
-
* @param {string} input.schemaId
|
|
445
|
-
* @param {GraphQL.GraphQLSchema} input.schema The schema to use when generating the functions and their types
|
|
446
|
-
* @param {string} input.operationsDoc The GraphQL operations doc to use when generating the functions
|
|
447
|
-
* @param {Record<string, NetlifyGraph.ExtractedFunction>} input.functions The parsed queries with metadata to use when generating library functions
|
|
448
|
-
* @param {Record<string, NetlifyGraph.ExtractedFragment>} input.fragments The parsed queries with metadata to use when generating library functions
|
|
449
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
450
|
-
* @returns {Promise<void>} Void, effectfully writes the generated library to the filesystem
|
|
451
|
-
*/
|
|
452
|
-
const generateFunctionsFile = async ({ config, netlifyGraphConfig, operationsDoc, schema, schemaId }) => {
|
|
453
|
-
const parsedDoc = GraphQL.parse(operationsDoc)
|
|
454
|
-
|
|
455
|
-
const extracted = extractFunctionsFromOperationDoc(GraphQL, parsedDoc)
|
|
456
|
-
|
|
457
|
-
const codegenModule = await getCodegenModule({ config })
|
|
458
|
-
if (!codegenModule) {
|
|
459
|
-
warn(
|
|
460
|
-
`No Netlify Graph codegen module specified in netlify.toml under the [graph] header. Please specify 'codeGenerator' field and try again.`,
|
|
461
|
-
)
|
|
462
|
-
return
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
await generateRuntime({
|
|
466
|
-
generate: codegenModule.generateRuntime,
|
|
467
|
-
schema,
|
|
468
|
-
schemaId,
|
|
469
|
-
netlifyGraphConfig,
|
|
470
|
-
logger: log,
|
|
471
|
-
fragments: extracted.fragments,
|
|
472
|
-
functions: extracted.functions,
|
|
473
|
-
operationsDoc,
|
|
474
|
-
})
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Using the given NetlifyGraphConfig, read the GraphQL operations file and return the _unparsed_ GraphQL operations doc
|
|
479
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
|
|
480
|
-
* @returns {string} GraphQL operations doc
|
|
481
|
-
*/
|
|
482
|
-
const readGraphQLOperationsSourceFile = (netlifyGraphConfig) => {
|
|
483
|
-
ensureNetlifyGraphPath(netlifyGraphConfig)
|
|
484
|
-
|
|
485
|
-
const fullFilename = path.resolve(...(netlifyGraphConfig.graphQLOperationsSourceFilename || []))
|
|
486
|
-
if (!fs.existsSync(fullFilename)) {
|
|
487
|
-
fs.writeFileSync(fullFilename, '')
|
|
488
|
-
fs.closeSync(fs.openSync(fullFilename, 'w'))
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const source = fs.readFileSync(fullFilename, 'utf8')
|
|
492
|
-
|
|
493
|
-
return source
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Write an operations doc to the filesystem using the given NetlifyGraphConfig
|
|
498
|
-
* @param {object} input
|
|
499
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
500
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
|
|
501
|
-
* @param {string} input.operationsDocString The GraphQL operations doc to write
|
|
502
|
-
*/
|
|
503
|
-
const writeGraphQLOperationsSourceFile = ({ logger, netlifyGraphConfig, operationsDocString }) => {
|
|
504
|
-
const graphqlSource = operationsDocString
|
|
505
|
-
|
|
506
|
-
ensureNetlifyGraphPath(netlifyGraphConfig)
|
|
507
|
-
const resolvedPath = path.resolve(...(netlifyGraphConfig.graphQLOperationsSourceFilename || []))
|
|
508
|
-
fs.writeFileSync(resolvedPath, graphqlSource, 'utf8')
|
|
509
|
-
const relativePath = path.relative(process.cwd(), resolvedPath)
|
|
510
|
-
logger && logger(`Wrote ${chalk.cyan(relativePath)}`)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* Write a GraphQL Schema printed in SDL format to the filesystem using the given NetlifyGraphConfig
|
|
515
|
-
* @param {object} input
|
|
516
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
517
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
|
|
518
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to print and write to the filesystem
|
|
519
|
-
*/
|
|
520
|
-
const writeGraphQLSchemaFile = ({ logger, netlifyGraphConfig, schema }) => {
|
|
521
|
-
const graphqlSource = printSchema(schema)
|
|
522
|
-
|
|
523
|
-
ensureNetlifyGraphPath(netlifyGraphConfig)
|
|
524
|
-
const resolvedPath = path.resolve(...netlifyGraphConfig.graphQLSchemaFilename)
|
|
525
|
-
fs.writeFileSync(resolvedPath, graphqlSource, 'utf8')
|
|
526
|
-
const relativePath = path.relative(process.cwd(), resolvedPath)
|
|
527
|
-
logger && logger(`Wrote ${chalk.cyan(relativePath)}`)
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Using the given NetlifyGraphConfig, read the GraphQL schema file and return it _unparsed_
|
|
532
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
|
|
533
|
-
* @returns {string} GraphQL schema
|
|
534
|
-
*/
|
|
535
|
-
const readGraphQLSchemaFile = (netlifyGraphConfig) => {
|
|
536
|
-
ensureNetlifyGraphPath(netlifyGraphConfig)
|
|
537
|
-
return fs.readFileSync(path.resolve(...netlifyGraphConfig.graphQLSchemaFilename), 'utf8')
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* Given a NetlifyGraphConfig, read the appropriate files and write a handler for the given operationId to the filesystem
|
|
542
|
-
* @param {object} input
|
|
543
|
-
* @param {import('netlify-onegraph-internal').CodegenHelpers.GenerateHandlerFunction} input.generate
|
|
544
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
|
|
545
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating the handler
|
|
546
|
-
* @param {string} input.operationId The operationId to use when generating the handler
|
|
547
|
-
* @param {string} input.operationsDoc The document containing the operation with operationId and any fragment dependency to use when generating the handler
|
|
548
|
-
* @param {object} input.handlerOptions The options to use when generating the handler
|
|
549
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
550
|
-
* @returns {Promise<{exportedFiles: import('netlify-onegraph-internal').CodegenHelpers.ExportedFile[]; operation: GraphQL.OperationDefinitionNode;} | undefined>} The generated files
|
|
551
|
-
*/
|
|
552
|
-
const generateHandlerSourceByOperationId = async ({
|
|
553
|
-
generate,
|
|
554
|
-
handlerOptions,
|
|
555
|
-
netlifyGraphConfig,
|
|
556
|
-
operationId,
|
|
557
|
-
operationsDoc,
|
|
558
|
-
schema,
|
|
559
|
-
}) => {
|
|
560
|
-
const generateHandlerPayload = {
|
|
561
|
-
GraphQL,
|
|
562
|
-
generate,
|
|
563
|
-
handlerOptions,
|
|
564
|
-
schema,
|
|
565
|
-
schemaString: GraphQL.printSchema(schema),
|
|
566
|
-
netlifyGraphConfig,
|
|
567
|
-
operationId,
|
|
568
|
-
operationsDoc,
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
const result = await NetlifyGraph.generateCustomHandlerSource(generateHandlerPayload)
|
|
572
|
-
|
|
573
|
-
return result
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Given a NetlifyGraphConfig, read the appropriate files and write a handler for the given operationId to the filesystem
|
|
578
|
-
* @param {object} input
|
|
579
|
-
* @param {import('netlify-onegraph-internal').CodegenHelpers.GenerateHandlerFunction} input.generate
|
|
580
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
|
|
581
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating the handler
|
|
582
|
-
* @param {string} input.operationId The operationId to use when generating the handler
|
|
583
|
-
* @param {object} input.handlerOptions The options to use when generating the handler
|
|
584
|
-
* @param {(message: string) => void=} input.logger A function that if provided will be used to log messages
|
|
585
|
-
* @returns {Promise<Array<{filePath: string, name:string, prettierSuccess: boolean}> | undefined>} An array of the generated handler filepaths
|
|
586
|
-
*/
|
|
587
|
-
const generateHandlerByOperationId = async ({ generate, handlerOptions, netlifyGraphConfig, operationId, schema }) => {
|
|
588
|
-
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
589
|
-
if (currentOperationsDoc.trim().length === 0) {
|
|
590
|
-
currentOperationsDoc = NetlifyGraph.defaultExampleOperationsDoc
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const result = await generateHandlerSourceByOperationId({
|
|
594
|
-
generate,
|
|
595
|
-
handlerOptions,
|
|
596
|
-
netlifyGraphConfig,
|
|
597
|
-
operationId,
|
|
598
|
-
operationsDoc: currentOperationsDoc,
|
|
599
|
-
schema,
|
|
600
|
-
})
|
|
601
|
-
|
|
602
|
-
if (!result) {
|
|
603
|
-
warn(`No handler was generated for operationId ${operationId}`)
|
|
604
|
-
return
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const { exportedFiles, operation } = result
|
|
608
|
-
|
|
609
|
-
log('Ensure destination path exists...')
|
|
610
|
-
|
|
611
|
-
ensureFunctionsPath(netlifyGraphConfig)
|
|
612
|
-
|
|
613
|
-
if (!exportedFiles) {
|
|
614
|
-
warn(`No exported files from Netlify Graph code generator`)
|
|
615
|
-
return
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
/** @type {Array<{filePath: string, name:string, prettierSuccess: boolean}>} */
|
|
619
|
-
const results = []
|
|
620
|
-
|
|
621
|
-
exportedFiles.forEach((exportedFile) => {
|
|
622
|
-
const { content } = exportedFile
|
|
623
|
-
const isNamed = exportedFile.kind === 'NamedExportedFile'
|
|
624
|
-
|
|
625
|
-
let filenameArr
|
|
626
|
-
|
|
627
|
-
if (isNamed) {
|
|
628
|
-
filenameArr = [...exportedFile.name]
|
|
629
|
-
} else {
|
|
630
|
-
const operationName = (operation.name && operation.name.value) || 'Unnamed'
|
|
631
|
-
const fileExtension = netlifyGraphConfig.language === 'typescript' ? 'ts' : netlifyGraphConfig.extension
|
|
632
|
-
const defaultBaseFilename = `${operationName}.${fileExtension}`
|
|
633
|
-
const baseFilename = defaultBaseFilename
|
|
634
|
-
|
|
635
|
-
filenameArr = [path.sep, ...netlifyGraphConfig.functionsPath, baseFilename]
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const parentDir = path.resolve(...filterRelativePathItems(filenameArr.slice(0, -1)))
|
|
639
|
-
|
|
640
|
-
// Make sure the parent directory exists
|
|
641
|
-
fs.mkdirSync(parentDir, { recursive: true })
|
|
642
|
-
|
|
643
|
-
const absoluteFilename = path.resolve(...filenameArr)
|
|
644
|
-
|
|
645
|
-
fs.writeFileSync(absoluteFilename, content)
|
|
646
|
-
const relativePath = path.relative(process.cwd(), absoluteFilename)
|
|
647
|
-
log(`Wrote ${chalk.cyan(relativePath)}`)
|
|
648
|
-
runPrettier(absoluteFilename)
|
|
649
|
-
|
|
650
|
-
results.push({
|
|
651
|
-
name: filenameArr.slice(-1)[0],
|
|
652
|
-
filePath: absoluteFilename,
|
|
653
|
-
prettierSuccess: true,
|
|
654
|
-
})
|
|
655
|
-
})
|
|
656
|
-
|
|
657
|
-
return results
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/**
|
|
661
|
-
* Given a NetlifyGraphConfig, read the appropriate files and write a handler for the given operationId to the filesystem
|
|
662
|
-
* @param {object} input
|
|
663
|
-
* @param {import('netlify-onegraph-internal').CodegenHelpers.GenerateHandlerFunction} input.generate
|
|
664
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
|
|
665
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating the handler
|
|
666
|
-
* @param {string} input.operationName The name of the operation to use when generating the handler
|
|
667
|
-
* @param {object} input.handlerOptions The options to use when generating the handler
|
|
668
|
-
* @param {(message: string) => void} input.logger A function that if provided will be used to log messages
|
|
669
|
-
* @returns {Promise<void>}
|
|
670
|
-
*/
|
|
671
|
-
const generateHandlerByOperationName = async ({
|
|
672
|
-
generate,
|
|
673
|
-
handlerOptions,
|
|
674
|
-
logger,
|
|
675
|
-
netlifyGraphConfig,
|
|
676
|
-
operationName,
|
|
677
|
-
schema,
|
|
678
|
-
}) => {
|
|
679
|
-
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
680
|
-
if (currentOperationsDoc.trim().length === 0) {
|
|
681
|
-
currentOperationsDoc = NetlifyGraph.defaultExampleOperationsDoc
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
const parsedDoc = parse(currentOperationsDoc)
|
|
685
|
-
const { fragments, functions } = extractFunctionsFromOperationDoc(GraphQL, parsedDoc)
|
|
686
|
-
|
|
687
|
-
const functionDefinition = Object.values(functions).find(
|
|
688
|
-
(potentialDefinition) => potentialDefinition.operationName === operationName,
|
|
689
|
-
)
|
|
690
|
-
|
|
691
|
-
const fragmentDefinition = Object.values(fragments).find(
|
|
692
|
-
(potentialDefinition) => potentialDefinition.fragmentName === operationName,
|
|
693
|
-
)
|
|
694
|
-
|
|
695
|
-
const definition = functionDefinition || fragmentDefinition
|
|
696
|
-
|
|
697
|
-
if (!definition) {
|
|
698
|
-
warn(`No operation named ${operationName} was found in the operations doc`)
|
|
699
|
-
return
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
await generateHandlerByOperationId({
|
|
703
|
-
logger,
|
|
704
|
-
generate,
|
|
705
|
-
netlifyGraphConfig,
|
|
706
|
-
schema,
|
|
707
|
-
operationId: definition.id,
|
|
708
|
-
handlerOptions,
|
|
709
|
-
})
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* Given a NetlifyGraphConfig, read the appropriate files and write a handler for the given operationId to the filesystem
|
|
714
|
-
* @param {object} input
|
|
715
|
-
* @param {import('netlify-onegraph-internal').CodegenHelpers.GenerateHandlerPreviewFunction} input.generate
|
|
716
|
-
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
|
|
717
|
-
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating the handler
|
|
718
|
-
* @param {string} input.operationName The name of the operation to use when generating the handler
|
|
719
|
-
* @param {object} input.handlerOptions The options to use when generating the handler
|
|
720
|
-
* @param {(message: string) => void} input.logger A function that if provided will be used to log messages
|
|
721
|
-
* @returns {import('netlify-onegraph-internal').CodegenHelpers.ExportedFile | undefined}
|
|
722
|
-
*/
|
|
723
|
-
const generateHandlerPreviewByOperationName = ({
|
|
724
|
-
generate,
|
|
725
|
-
handlerOptions,
|
|
726
|
-
netlifyGraphConfig,
|
|
727
|
-
operationName,
|
|
728
|
-
schema,
|
|
729
|
-
}) => {
|
|
730
|
-
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
731
|
-
if (currentOperationsDoc.trim().length === 0) {
|
|
732
|
-
currentOperationsDoc = NetlifyGraph.defaultExampleOperationsDoc
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
const parsedDoc = parse(currentOperationsDoc)
|
|
736
|
-
const { functions } = extractFunctionsFromOperationDoc(GraphQL, parsedDoc)
|
|
737
|
-
|
|
738
|
-
const operation = Object.values(functions).find(
|
|
739
|
-
(potentialOperation) => potentialOperation.operationName === operationName,
|
|
740
|
-
)
|
|
741
|
-
|
|
742
|
-
if (!operation) {
|
|
743
|
-
warn(`No operation named ${operationName} was found in the operations doc`)
|
|
744
|
-
return
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
const generateHandlerPreviewPayload = {
|
|
748
|
-
GraphQL,
|
|
749
|
-
generate,
|
|
750
|
-
handlerOptions,
|
|
751
|
-
schema,
|
|
752
|
-
schemaString: GraphQL.printSchema(schema),
|
|
753
|
-
netlifyGraphConfig,
|
|
754
|
-
operationId: operation.id,
|
|
755
|
-
operationsDoc: currentOperationsDoc,
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
const preview = NetlifyGraph.generatePreview(generateHandlerPreviewPayload)
|
|
759
|
-
|
|
760
|
-
if (!preview) {
|
|
761
|
-
return
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
return preview.exportedFile
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Export the minimal set of functions that are required for Netlify Graph
|
|
768
|
-
const { buildSchema, parse } = GraphQL
|
|
769
|
-
|
|
770
|
-
/**
|
|
771
|
-
*
|
|
772
|
-
* @param {object} options
|
|
773
|
-
* @param {string} options.siteName The name of the site as used in the Netlify UI url scheme
|
|
774
|
-
* @param {string} options.oneGraphSessionId The oneGraph session id to use when generating the graph-edit link
|
|
775
|
-
* @returns {string} The url to the Netlify Graph UI for the current session
|
|
776
|
-
*/
|
|
777
|
-
const getGraphEditUrlBySiteName = ({ oneGraphSessionId, siteName }) => {
|
|
778
|
-
const host = process.env.NETLIFY_APP_HOST || 'app.netlify.com'
|
|
779
|
-
// http because app.netlify.com will redirect to https, and localhost will still work for development
|
|
780
|
-
const url = `http://${host}/sites/app/${siteName}/graph/explorer/${oneGraphSessionId}`
|
|
781
|
-
|
|
782
|
-
return url
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* Get a url to the Netlify Graph UI for the current session by a site's id
|
|
787
|
-
* @param {object} options
|
|
788
|
-
* @param {string} options.siteId The name of the site as used in the Netlify UI url scheme
|
|
789
|
-
* @param {string} options.oneGraphSessionId The oneGraph session id to use when generating the graph-edit link
|
|
790
|
-
* @returns {string} The url to the Netlify Graph UI for the current session
|
|
791
|
-
*/
|
|
792
|
-
const getGraphEditUrlBySiteId = ({ oneGraphSessionId, siteId }) => {
|
|
793
|
-
const host = process.env.NETLIFY_APP_HOST || 'app.netlify.com'
|
|
794
|
-
// http because app.netlify.com will redirect to https, and localhost will still work for development
|
|
795
|
-
const url = `http://${host}/site-redirect/${siteId}/graph/explorer/${oneGraphSessionId}`
|
|
796
|
-
|
|
797
|
-
return url
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Load `netlifyGraph.json` from the appropriate location
|
|
802
|
-
* @param {string} siteRoot The root directory of the site
|
|
803
|
-
* @returns {import('netlify-onegraph-internal').NetlifyGraphJsonConfig.JsonConfig}
|
|
804
|
-
*/
|
|
805
|
-
const loadNetlifyGraphConfig = (siteRoot) => {
|
|
806
|
-
const configPath = path.join(siteRoot, 'netlifyGraph.json')
|
|
807
|
-
if (fs.existsSync(configPath)) {
|
|
808
|
-
// eslint-disable-next-line unicorn/prefer-json-parse-buffer
|
|
809
|
-
const file = fs.readFileSync(configPath, 'utf-8')
|
|
810
|
-
return JSON.parse(file)
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
return {}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
const autocompleteOperationNames = async ({ netlifyGraphConfig }) => {
|
|
817
|
-
try {
|
|
818
|
-
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
819
|
-
if (currentOperationsDoc.trim().length === 0) {
|
|
820
|
-
currentOperationsDoc = NetlifyGraph.defaultExampleOperationsDoc
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
const parsedDoc = parse(currentOperationsDoc)
|
|
824
|
-
const extracted = extractFunctionsFromOperationDoc(GraphQL, parsedDoc)
|
|
825
|
-
|
|
826
|
-
const { functions } = extracted
|
|
827
|
-
|
|
828
|
-
const sorted = Object.values(functions).sort((aItem, bItem) =>
|
|
829
|
-
aItem.operationName.localeCompare(bItem.operationName),
|
|
830
|
-
)
|
|
831
|
-
|
|
832
|
-
const perPage = 50
|
|
833
|
-
|
|
834
|
-
const allOperationChoices = sorted.map((operation) => ({
|
|
835
|
-
name: `${operation.operationName} (${operation.kind})`,
|
|
836
|
-
value: operation.operationName,
|
|
837
|
-
}))
|
|
838
|
-
|
|
839
|
-
const filterOperationNames = (operationChoices, input) =>
|
|
840
|
-
operationChoices.filter((operation) => operation.value.toLowerCase().match(input.toLowerCase()))
|
|
841
|
-
|
|
842
|
-
/** multiple matching detectors, make the user choose */
|
|
843
|
-
// @ts-ignore
|
|
844
|
-
inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
|
|
845
|
-
|
|
846
|
-
// @ts-ignore
|
|
847
|
-
const { selectedOperationName } = await inquirer.prompt({
|
|
848
|
-
name: 'selectedOperationName',
|
|
849
|
-
message: `For which operation would you like to generate a handler?`,
|
|
850
|
-
type: 'autocomplete',
|
|
851
|
-
pageSize: perPage,
|
|
852
|
-
source(_, input) {
|
|
853
|
-
if (!input || input === '') {
|
|
854
|
-
return allOperationChoices
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
const filteredChoices = filterOperationNames(allOperationChoices, input)
|
|
858
|
-
// only show filtered results
|
|
859
|
-
return filteredChoices
|
|
860
|
-
},
|
|
861
|
-
})
|
|
862
|
-
|
|
863
|
-
return selectedOperationName
|
|
864
|
-
} catch (parseError) {
|
|
865
|
-
error(`Error parsing operations library: ${parseError}`)
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
/** @type {string | undefined} */
|
|
870
|
-
let lastWarnedFailedCodegenModule
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
* @param {object} input
|
|
874
|
-
* @param {object} input.config The parsed netlify.toml file
|
|
875
|
-
* @param {string=} input.cwd The optional directory to use as a base path when resolving codegen modules
|
|
876
|
-
* @returns {Promise<import('netlify-onegraph-internal').CodegenHelpers.CodegenModule | void>} codegenModule
|
|
877
|
-
*/
|
|
878
|
-
const dynamicallyLoadCodegenModule = async ({ config, cwd }) => {
|
|
879
|
-
const basePath = cwd || process.cwd()
|
|
880
|
-
const importPath = config.graph && config.graph.codeGenerator
|
|
881
|
-
|
|
882
|
-
if (!importPath) {
|
|
883
|
-
return
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// We currently include some default code generators for the most common framework
|
|
887
|
-
// use-cases. We still require it to be explicitly configured in netlify.toml,
|
|
888
|
-
// but we don't require an additional package install for it.
|
|
889
|
-
const includedCodegenModule = IncludedCodegen.includedCodegenModules.find(
|
|
890
|
-
(codegenModule) => codegenModule.sigil === importPath,
|
|
891
|
-
)
|
|
892
|
-
|
|
893
|
-
if (includedCodegenModule) {
|
|
894
|
-
return includedCodegenModule
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
try {
|
|
898
|
-
if (!importPath) {
|
|
899
|
-
warn(
|
|
900
|
-
`No Netlify Graph codegen module specified in netlify.toml under the [graph] header. Please specify 'codeGenerator' field and try again.`,
|
|
901
|
-
)
|
|
902
|
-
return
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const absolute = [basePath, 'node_modules', ...importPath.split('/'), 'index.js']
|
|
906
|
-
const relativePath = path.join(basePath, importPath)
|
|
907
|
-
const absoluteOrNodePath = path.resolve(...absolute)
|
|
908
|
-
|
|
909
|
-
const finalPath = fs.existsSync(relativePath) ? relativePath : pathToFileURL(absoluteOrNodePath).href
|
|
910
|
-
|
|
911
|
-
/** @type {import('netlify-onegraph-internal').CodegenHelpers.CodegenModule | undefined} */
|
|
912
|
-
// eslint-disable-next-line import/no-dynamic-require
|
|
913
|
-
const newModule = await import(finalPath)
|
|
914
|
-
|
|
915
|
-
if (newModule) {
|
|
916
|
-
const hasGenerators = Array.isArray(newModule.generators)
|
|
917
|
-
let generatorsConform = true
|
|
918
|
-
if (hasGenerators) {
|
|
919
|
-
newModule.generators.forEach((generator) => {
|
|
920
|
-
const hasId = Boolean(generator.id)
|
|
921
|
-
const hasName = Boolean(generator.name)
|
|
922
|
-
const hasVersion = Boolean(generator.version)
|
|
923
|
-
const hasGenerator = Boolean(generator.generateHandler)
|
|
924
|
-
|
|
925
|
-
if (!hasId || !hasName || !hasVersion || !hasGenerator) {
|
|
926
|
-
generatorsConform = false
|
|
927
|
-
}
|
|
928
|
-
})
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (!generatorsConform) {
|
|
932
|
-
warn(`Generator ${importPath} does not conform to expected module declaration type, ignoring...`)
|
|
933
|
-
return
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
return newModule
|
|
938
|
-
} catch (error_) {
|
|
939
|
-
if (lastWarnedFailedCodegenModule !== importPath) {
|
|
940
|
-
warn(`Failed to load Graph code generator, please make sure that "${importPath}" either exists as a local file or is listed in your package.json under devDependencies, and can be 'require'd or 'import'ed.
|
|
941
|
-
|
|
942
|
-
${error_}`)
|
|
943
|
-
|
|
944
|
-
lastWarnedFailedCodegenModule = importPath
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
const getCodegenModule = ({ config }) => dynamicallyLoadCodegenModule({ config })
|
|
950
|
-
|
|
951
|
-
const getCodegenFunctionById = async ({ config, id }) => {
|
|
952
|
-
const codegenModule = await getCodegenModule({ config })
|
|
953
|
-
|
|
954
|
-
return codegenModule && codegenModule.generators && codegenModule.generators.find((generator) => generator.id === id)
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
const autocompleteCodegenModules = async ({ config }) => {
|
|
958
|
-
const codegenModule = await getCodegenModule({ config })
|
|
959
|
-
if (!codegenModule || !codegenModule.generators) {
|
|
960
|
-
return null
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
log(`Using Graph Codegen module ${codegenModule.id} [${codegenModule.version}] from '${config.graph.codeGenerator}'`)
|
|
964
|
-
|
|
965
|
-
const allGeneratorChoices = codegenModule.generators
|
|
966
|
-
// eslint-disable-next-line id-length
|
|
967
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
968
|
-
.map((codeGen) => ({
|
|
969
|
-
name: `${codeGen.name} [${codeGen.id}]`,
|
|
970
|
-
value: codeGen.name,
|
|
971
|
-
}))
|
|
972
|
-
|
|
973
|
-
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
974
|
-
const filterModuleNames = (moduleChoices, input) =>
|
|
975
|
-
moduleChoices.filter((moduleChoice) => moduleChoice.name.toLowerCase().match(input.toLowerCase()))
|
|
976
|
-
|
|
977
|
-
/** multiple matching detectors, make the user choose */
|
|
978
|
-
// @ts-ignore
|
|
979
|
-
inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
|
|
980
|
-
|
|
981
|
-
const perPage = 50
|
|
982
|
-
|
|
983
|
-
// @ts-ignore
|
|
984
|
-
const { selectedCodeGen } = await inquirer.prompt({
|
|
985
|
-
name: 'selectedCodeGen',
|
|
986
|
-
message: `Which codegen would you like to use?`,
|
|
987
|
-
type: 'autocomplete',
|
|
988
|
-
pageSize: perPage,
|
|
989
|
-
source(_, input) {
|
|
990
|
-
if (!input || input === '') {
|
|
991
|
-
return allGeneratorChoices
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
const filteredChoices = filterModuleNames(allGeneratorChoices, input)
|
|
995
|
-
// only show filtered results
|
|
996
|
-
return filteredChoices
|
|
997
|
-
},
|
|
998
|
-
})
|
|
999
|
-
|
|
1000
|
-
return codegenModule.generators.find(
|
|
1001
|
-
(dynamicallyImportedModule) => dynamicallyImportedModule.name === selectedCodeGen,
|
|
1002
|
-
)
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
export {
|
|
1006
|
-
autocompleteCodegenModules,
|
|
1007
|
-
autocompleteOperationNames,
|
|
1008
|
-
buildSchema,
|
|
1009
|
-
defaultExampleOperationsDoc,
|
|
1010
|
-
extractFunctionsFromOperationDoc,
|
|
1011
|
-
generateFunctionsFile,
|
|
1012
|
-
generateHandlerSource,
|
|
1013
|
-
generateHandlerByOperationId,
|
|
1014
|
-
generateHandlerByOperationName,
|
|
1015
|
-
generateHandlerPreviewByOperationName,
|
|
1016
|
-
generateHandlerSourceByOperationId,
|
|
1017
|
-
generateRuntime,
|
|
1018
|
-
generateRuntimeSource,
|
|
1019
|
-
getCodegenFunctionById,
|
|
1020
|
-
getCodegenModule,
|
|
1021
|
-
getGraphEditUrlBySiteId,
|
|
1022
|
-
getGraphEditUrlBySiteName,
|
|
1023
|
-
getNetlifyGraphConfig,
|
|
1024
|
-
loadNetlifyGraphConfig,
|
|
1025
|
-
normalizeOperationsDoc,
|
|
1026
|
-
parse,
|
|
1027
|
-
readGraphQLOperationsSourceFile,
|
|
1028
|
-
readGraphQLSchemaFile,
|
|
1029
|
-
setNetlifyTomlCodeGeneratorModule,
|
|
1030
|
-
runPrettier,
|
|
1031
|
-
writeGraphQLOperationsSourceFile,
|
|
1032
|
-
writeGraphQLSchemaFile,
|
|
1033
|
-
}
|