netlify-cli 8.12.0 → 8.13.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.
@@ -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
+ }