Skip to content

Commit c2f4f1d

Browse files
authored
Graph improvements (#4169)
* feat: graph: refactor per-framework config and netlify.toml options * chore: update snapshots * chore: remove optional chaining and fix formatting * chore: fix jsdoc description
1 parent cc2c6dd commit c2f4f1d

File tree

7 files changed

+186
-48
lines changed

7 files changed

+186
-48
lines changed

npm-shrinkwrap.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@
148148
"multiparty": "^4.2.1",
149149
"netlify": "^10.1.2",
150150
"netlify-headers-parser": "^6.0.1",
151-
"netlify-onegraph-internal": "0.0.16",
151+
"netlify-onegraph-internal": "0.0.18",
152152
"netlify-redirect-parser": "^13.0.1",
153153
"netlify-redirector": "^0.2.1",
154154
"node-fetch": "^2.6.0",

src/lib/one-graph/cli-client.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ const refetchAndGenerateFromOneGraph = async (input) => {
138138
}
139139

140140
const parsedDoc = parse(currentOperationsDoc)
141-
const operations = extractFunctionsFromOperationDoc(parsedDoc)
141+
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
142142

143-
generateFunctionsFile(netlifyGraphConfig, schema, currentOperationsDoc, operations)
143+
generateFunctionsFile({ netlifyGraphConfig, schema, operationsDoc: currentOperationsDoc, functions, fragments })
144144
writeGraphQLSchemaFile(netlifyGraphConfig, schema)
145145
state.set('oneGraphEnabledServices', enabledServices)
146146
}
@@ -170,8 +170,8 @@ const updateGraphQLOperationsFile = async (input) => {
170170
const parsedDoc = parse(appOperationsDoc, {
171171
noLocation: true,
172172
})
173-
const operations = extractFunctionsFromOperationDoc(parsedDoc)
174-
generateFunctionsFile(netlifyGraphConfig, schema, appOperationsDoc, operations)
173+
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
174+
generateFunctionsFile({ netlifyGraphConfig, schema, operationsDoc: appOperationsDoc, functions, fragments })
175175
}
176176

177177
const handleCliSessionEvent = async ({ event, netlifyGraphConfig, netlifyToken, schema, siteId }) => {

src/lib/one-graph/cli-netlify-graph.js

Lines changed: 171 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,105 @@ InternalConsole.registerConsole(internalConsole)
2424
*/
2525
const filterRelativePathItems = (items) => items.filter((part) => part !== '')
2626

27+
/**
28+
* Return the default Netlify Graph configuration for a generic site
29+
* @param {object} context
30+
* @param {string[]} context.detectedFunctionsPath
31+
* @param {string[]} context.siteRoot
32+
*/
33+
const makeDefaultNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath }) => {
34+
const functionsPath = filterRelativePathItems([...detectedFunctionsPath])
35+
const webhookBasePath = '/.netlify/functions'
36+
const netlifyGraphPath = [...functionsPath, 'netlifyGraph']
37+
const netlifyGraphImplementationFilename = [...netlifyGraphPath, `index.${baseConfig.extension}`]
38+
const netlifyGraphTypeDefinitionsFilename = [...netlifyGraphPath, `index.d.ts`]
39+
const graphQLOperationsSourceFilename = [...netlifyGraphPath, NetlifyGraph.defaultSourceOperationsFilename]
40+
const graphQLSchemaFilename = [...netlifyGraphPath, NetlifyGraph.defaultGraphQLSchemaFilename]
41+
const netlifyGraphRequirePath = [`./netlifyGraph`]
42+
const moduleType = baseConfig.moduleType || 'esm'
43+
44+
return {
45+
functionsPath,
46+
webhookBasePath,
47+
netlifyGraphPath,
48+
netlifyGraphImplementationFilename,
49+
netlifyGraphTypeDefinitionsFilename,
50+
graphQLOperationsSourceFilename,
51+
graphQLSchemaFilename,
52+
netlifyGraphRequirePath,
53+
moduleType,
54+
}
55+
}
56+
57+
/**
58+
* Return the default Netlify Graph configuration for a Nextjs site
59+
* @param {object} context
60+
* @param {string[]} context.detectedFunctionsPath
61+
* @param {string[]} context.siteRoot
62+
*/
63+
const makeDefaultNextJsNetlifGraphConfig = ({ baseConfig, siteRoot }) => {
64+
const functionsPath = filterRelativePathItems([...siteRoot, 'pages', 'api'])
65+
const webhookBasePath = '/api'
66+
const netlifyGraphPath = filterRelativePathItems([...siteRoot, 'lib', 'netlifyGraph'])
67+
const netlifyGraphImplementationFilename = [...netlifyGraphPath, `index.${baseConfig.extension}`]
68+
const netlifyGraphTypeDefinitionsFilename = [...netlifyGraphPath, `index.d.ts`]
69+
const graphQLOperationsSourceFilename = [...netlifyGraphPath, NetlifyGraph.defaultSourceOperationsFilename]
70+
const graphQLSchemaFilename = [...netlifyGraphPath, NetlifyGraph.defaultGraphQLSchemaFilename]
71+
const netlifyGraphRequirePath = ['..', '..', 'lib', 'netlifyGraph']
72+
const moduleType = baseConfig.moduleType || 'esm'
73+
74+
return {
75+
functionsPath,
76+
webhookBasePath,
77+
netlifyGraphPath,
78+
netlifyGraphImplementationFilename,
79+
netlifyGraphTypeDefinitionsFilename,
80+
graphQLOperationsSourceFilename,
81+
graphQLSchemaFilename,
82+
netlifyGraphRequirePath,
83+
moduleType,
84+
}
85+
}
86+
87+
/**
88+
* Return the default Netlify Graph configuration for a Remix site
89+
* @param {object} context
90+
* @param {string[]} context.detectedFunctionsPath
91+
* @param {string[]} context.siteRoot
92+
*/
93+
const makeDefaultRemixNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath, siteRoot }) => {
94+
const functionsPath = filterRelativePathItems([...detectedFunctionsPath])
95+
const webhookBasePath = '/webhooks'
96+
const netlifyGraphPath = filterRelativePathItems([
97+
...siteRoot,
98+
...NetlifyGraph.defaultNetlifyGraphConfig.netlifyGraphPath,
99+
])
100+
const netlifyGraphImplementationFilename = [...netlifyGraphPath, `index.${baseConfig.extension}`]
101+
const netlifyGraphTypeDefinitionsFilename = [...netlifyGraphPath, `index.d.ts`]
102+
const graphQLOperationsSourceFilename = [...netlifyGraphPath, NetlifyGraph.defaultSourceOperationsFilename]
103+
const graphQLSchemaFilename = [...netlifyGraphPath, NetlifyGraph.defaultGraphQLSchemaFilename]
104+
const netlifyGraphRequirePath = [`../../netlify/functions/netlifyGraph`]
105+
const moduleType = 'esm'
106+
107+
return {
108+
functionsPath,
109+
webhookBasePath,
110+
netlifyGraphPath,
111+
netlifyGraphImplementationFilename,
112+
netlifyGraphTypeDefinitionsFilename,
113+
graphQLOperationsSourceFilename,
114+
graphQLSchemaFilename,
115+
netlifyGraphRequirePath,
116+
moduleType,
117+
}
118+
}
119+
120+
const defaultFrameworkLookup = {
121+
'Next.js': makeDefaultNextJsNetlifGraphConfig,
122+
Remix: makeDefaultRemixNetlifGraphConfig,
123+
default: makeDefaultNetlifGraphConfig,
124+
}
125+
27126
/**
28127
* Return a full NetlifyGraph config with any defaults overridden by netlify.toml
29128
* @param {import('../base-command').BaseCommand} command
@@ -48,7 +147,8 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
48147
try {
49148
settings = await detectServerSettings(devConfig, options, site.root)
50149
} catch (detectServerSettingsError) {
51-
error(detectServerSettingsError)
150+
settings = {}
151+
warn('Error while auto-detecting project settings, Netlify Graph encounter problems', detectServerSettingsError)
52152
}
53153
}
54154

@@ -58,26 +158,55 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
58158
const autodetectedLanguage = fs.existsSync(tsConfig) ? 'typescript' : 'javascript'
59159

60160
const framework = settings.framework || userSpecifiedConfig.framework
61-
const isNextjs = framework === 'Next.js'
161+
const makeDefaultFrameworkConfig = defaultFrameworkLookup[framework] || defaultFrameworkLookup.default
162+
62163
const detectedFunctionsPathString = getFunctionsDir({ config, options })
63-
const detectedFunctionsPath = detectedFunctionsPathString ? detectedFunctionsPathString.split(path.sep) : null
64-
const functionsPath = filterRelativePathItems(isNextjs ? [...siteRoot, 'pages', 'api'] : [...detectedFunctionsPath])
65-
const netlifyGraphPath = filterRelativePathItems(
66-
isNextjs
67-
? [...siteRoot, 'lib', 'netlifyGraph']
68-
: [...siteRoot, ...NetlifyGraph.defaultNetlifyGraphConfig.netlifyGraphPath],
69-
)
164+
const detectedFunctionsPath = detectedFunctionsPathString
165+
? [path.sep, ...detectedFunctionsPathString.split(path.sep)]
166+
: null
70167
const baseConfig = { ...NetlifyGraph.defaultNetlifyGraphConfig, ...userSpecifiedConfig }
71-
const netlifyGraphImplementationFilename = [...netlifyGraphPath, `index.${baseConfig.extension}`]
72-
const netlifyGraphTypeDefinitionsFilename = [...netlifyGraphPath, `index.d.ts`]
73-
const graphQLOperationsSourceFilename = [...netlifyGraphPath, NetlifyGraph.defaultSourceOperationsFilename]
74-
const graphQLSchemaFilename = [...netlifyGraphPath, NetlifyGraph.defaultGraphQLSchemaFilename]
75-
const netlifyGraphRequirePath = isNextjs ? ['..', '..', 'lib', 'netlifyGraph'] : [`./netlifyGraph`]
76-
const language = userSpecifiedConfig.language || autodetectedLanguage
77-
const moduleType = baseConfig.moduleType || isNextjs ? 'esm' : 'commonjs'
168+
const defaultFrameworkConfig = makeDefaultFrameworkConfig({ baseConfig, detectedFunctionsPath, siteRoot })
169+
170+
const functionsPath =
171+
(userSpecifiedConfig.functionsPath && userSpecifiedConfig.functionsPath.split(path.sep)) ||
172+
defaultFrameworkConfig.functionsPath
173+
const netlifyGraphPath =
174+
(userSpecifiedConfig.netlifyGraphPath && userSpecifiedConfig.netlifyGraphPath.split(path.sep)) ||
175+
defaultFrameworkConfig.netlifyGraphPath
176+
const netlifyGraphImplementationFilename =
177+
(userSpecifiedConfig.netlifyGraphImplementationFilename &&
178+
userSpecifiedConfig.netlifyGraphImplementationFilename.split(path.sep)) ||
179+
defaultFrameworkConfig.netlifyGraphImplementationFilename
180+
const netlifyGraphTypeDefinitionsFilename =
181+
(userSpecifiedConfig.netlifyGraphTypeDefinitionsFilename &&
182+
userSpecifiedConfig.netlifyGraphTypeDefinitionsFilename.split(path.sep)) ||
183+
defaultFrameworkConfig.netlifyGraphTypeDefinitionsFilename
184+
const graphQLOperationsSourceFilename =
185+
(userSpecifiedConfig.graphQLOperationsSourceFilename &&
186+
userSpecifiedConfig.graphQLOperationsSourceFilename.split(path.sep)) ||
187+
defaultFrameworkConfig.graphQLOperationsSourceFilename
188+
const graphQLSchemaFilename =
189+
(userSpecifiedConfig.graphQLSchemaFilename && userSpecifiedConfig.graphQLSchemaFilename.split(path.sep)) ||
190+
defaultFrameworkConfig.graphQLSchemaFilename
191+
const netlifyGraphRequirePath =
192+
(userSpecifiedConfig.netlifyGraphRequirePath && userSpecifiedConfig.netlifyGraphRequirePath.split(path.sep)) ||
193+
defaultFrameworkConfig.netlifyGraphRequirePath
194+
const moduleType =
195+
(userSpecifiedConfig.moduleType && userSpecifiedConfig.moduleType.split(path.sep)) ||
196+
defaultFrameworkConfig.moduleType
197+
const language =
198+
(userSpecifiedConfig.language && userSpecifiedConfig.language.split(path.sep)) || autodetectedLanguage
199+
const webhookBasePath =
200+
(userSpecifiedConfig.webhookBasePath && userSpecifiedConfig.webhookBasePath.split(path.sep)) ||
201+
defaultFrameworkConfig.webhookBasePath
202+
const customGeneratorFile =
203+
userSpecifiedConfig.customGeneratorFile && userSpecifiedConfig.customGeneratorFile.split(path.sep)
204+
const runtimeTargetEnv = userSpecifiedConfig.runtimeTargetEnv || defaultFrameworkConfig.runtimeTargetEnv || 'node'
205+
78206
const fullConfig = {
79207
...baseConfig,
80208
functionsPath,
209+
webhookBasePath,
81210
netlifyGraphPath,
82211
netlifyGraphImplementationFilename,
83212
netlifyGraphTypeDefinitionsFilename,
@@ -87,6 +216,8 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
87216
framework,
88217
language,
89218
moduleType,
219+
customGeneratorFile,
220+
runtimeTargetEnv,
90221
}
91222

92223
return fullConfig
@@ -97,30 +228,36 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
97228
* @param {NetlifyGraphConfig} netlifyGraphConfig
98229
*/
99230
const ensureNetlifyGraphPath = (netlifyGraphConfig) => {
100-
fs.mkdirSync(path.resolve(...netlifyGraphConfig.netlifyGraphPath), { recursive: true })
231+
const fullPath = path.resolve(...netlifyGraphConfig.netlifyGraphPath)
232+
fs.mkdirSync(fullPath, { recursive: true })
101233
}
102234

103235
/**
104236
* Given a NetlifyGraphConfig, ensure that the functionsPath exists
105237
* @param {NetlifyGraphConfig} netlifyGraphConfig
106238
*/
107239
const ensureFunctionsPath = (netlifyGraphConfig) => {
108-
fs.mkdirSync(path.resolve(...netlifyGraphConfig.functionsPath), { recursive: true })
240+
const fullPath = path.resolve(...netlifyGraphConfig.functionsPath)
241+
fs.mkdirSync(fullPath, { recursive: true })
109242
}
110243

111244
/**
112245
* Generate a library file with type definitions for a given NetlifyGraphConfig, operationsDoc, and schema, writing them to the filesystem
113-
* @param {NetlifyGraphConfig} netlifyGraphConfig
114-
* @param {GraphQLSchema} schema The schema to use when generating the functions and their types
115-
* @param {string} operationsDoc The GraphQL operations doc to use when generating the functions
116-
* @param {NetlifyGraph.ParsedFunction} queries The parsed queries with metadata to use when generating library functions
246+
* @param {object} context
247+
* @param {NetlifyGraphConfig} context.netlifyGraphConfig
248+
* @param {GraphQLSchema} context.schema The schema to use when generating the functions and their types
249+
* @param {string} context.operationsDoc The GraphQL operations doc to use when generating the functions
250+
* @param {NetlifyGraph.ParsedFunction} context.functions The parsed queries with metadata to use when generating library functions
251+
* @param {NetlifyGraph.ParsedFragment} context.fragments The parsed queries with metadata to use when generating library functions
252+
* @returns {void} Void, effectfully writes the generated library to the filesystem
117253
*/
118-
const generateFunctionsFile = (netlifyGraphConfig, schema, operationsDoc, queries) => {
254+
const generateFunctionsFile = ({ fragments, functions, netlifyGraphConfig, operationsDoc, schema }) => {
119255
const { clientSource, typeDefinitionsSource } = NetlifyGraph.generateFunctionsSource(
120256
netlifyGraphConfig,
121257
schema,
122258
operationsDoc,
123-
queries,
259+
functions,
260+
fragments,
124261
)
125262

126263
ensureNetlifyGraphPath(netlifyGraphConfig)
@@ -199,13 +336,15 @@ const generateHandler = (netlifyGraphConfig, schema, operationId, handlerOptions
199336
currentOperationsDoc = NetlifyGraph.defaultExampleOperationsDoc
200337
}
201338

202-
const result = NetlifyGraph.generateHandlerSource({
339+
const payload = {
203340
handlerOptions,
204341
schema,
205342
netlifyGraphConfig,
206343
operationId,
207344
operationsDoc: currentOperationsDoc,
208-
})
345+
}
346+
347+
const result = NetlifyGraph.generateHandlerSource(payload)
209348

210349
if (!result) {
211350
warn(`No handler was generated for operationId ${operationId}`)
@@ -237,6 +376,11 @@ const generateHandler = (netlifyGraphConfig, schema, operationId, handlerOptions
237376
filenameArr = [path.sep, ...netlifyGraphConfig.functionsPath, baseFilename]
238377
}
239378

379+
const parentDir = path.resolve(...filterRelativePathItems(filenameArr.slice(0, -1)))
380+
381+
// Make sure the parent directory exists
382+
fs.mkdirSync(parentDir, { recursive: true })
383+
240384
const absoluteFilename = path.resolve(...filenameArr)
241385

242386
fs.writeFileSync(absoluteFilename, content)
@@ -254,7 +398,7 @@ const { buildSchema, parse } = GraphQL
254398
* @returns {string} The url to the Netlify Graph UI for the current session
255399
*/
256400
const getGraphEditUrlBySiteName = ({ oneGraphSessionId, siteName }) => {
257-
const host = 'app.netlify.com' || process.env.NETLIFY_APP_HOST
401+
const host = process.env.NETLIFY_APP_HOST || 'app.netlify.com'
258402
// http because app.netlify.com will redirect to https, and localhost will still work for development
259403
const url = `http://${host}/sites/${siteName}/graph/explorer?cliSessionId=${oneGraphSessionId}`
260404

tests/integration/530.graph-codegen.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ test('netlify graph function codegen', (t) => {
3535
noLocation: true,
3636
})
3737

38-
const operations = extractFunctionsFromOperationDoc(parsedDoc)
39-
const generatedFunctions = generateFunctionsSource(netlifyGraphConfig, schema, appOperationsDoc, operations)
38+
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
39+
const generatedFunctions = generateFunctionsSource(netlifyGraphConfig, schema, appOperationsDoc, functions, fragments)
4040

4141
t.snapshot(normalize(JSON.stringify(generatedFunctions)))
4242
})

0 commit comments

Comments
 (0)