Skip to content

Commit 76b6206

Browse files
authoredFeb 8, 2022
feat: additional graph commands, extra graph codegen tests, and type definitions (#4186)
* chore: add graph type defs, codegen snapshot test matrix, update codegen dependency * feat: add `graph:handler`, `graph:operations`, and `graph:library` commands
1 parent 1bf95f2 commit 76b6206

23 files changed

+1494
-636
lines changed
 

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ site/src/**/*.md
3232
tests/integration/hugo-site/resources
3333
tests/integration/hugo-site/out
3434
tests/integration/hugo-site/.hugo_build.lock
35+
_test_out/**
36+

‎README.md

+3
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ Manage netlify functions
160160
| Subcommand | description |
161161
|:--------------------------- |:-----|
162162
| [`graph:edit`](/docs/commands/graph.md#graphedit) | Launch the browser to edit your local graph functions from Netlify |
163+
| [`graph:handler`](/docs/commands/graph.md#graphhandler) | Generate a handler for a Graph operation given its name. See `graph:operations` for a list of operations. |
164+
| [`graph:library`](/docs/commands/graph.md#graphlibrary) | Generate the Graph function library |
165+
| [`graph:operations`](/docs/commands/graph.md#graphoperations) | List all of the locally available operations |
163166
| [`graph:pull`](/docs/commands/graph.md#graphpull) | Pull down your local Netlify Graph schema, and process pending Graph edit events |
164167

165168

‎docs/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ Manage netlify functions
117117
| Subcommand | description |
118118
|:--------------------------- |:-----|
119119
| [`graph:edit`](/docs/commands/graph.md#graphedit) | Launch the browser to edit your local graph functions from Netlify |
120+
| [`graph:handler`](/docs/commands/graph.md#graphhandler) | Generate a handler for a Graph operation given its name. See `graph:operations` for a list of operations. |
121+
| [`graph:library`](/docs/commands/graph.md#graphlibrary) | Generate the Graph function library |
122+
| [`graph:operations`](/docs/commands/graph.md#graphoperations) | List all of the locally available operations |
120123
| [`graph:pull`](/docs/commands/graph.md#graphpull) | Pull down your local Netlify Graph schema, and process pending Graph edit events |
121124

122125

‎docs/commands/graph.md

+58
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ netlify graph
2323
| Subcommand | description |
2424
|:--------------------------- |:-----|
2525
| [`graph:edit`](/docs/commands/graph.md#graphedit) | Launch the browser to edit your local graph functions from Netlify |
26+
| [`graph:handler`](/docs/commands/graph.md#graphhandler) | Generate a handler for a Graph operation given its name. See `graph:operations` for a list of operations. |
27+
| [`graph:library`](/docs/commands/graph.md#graphlibrary) | Generate the Graph function library |
28+
| [`graph:operations`](/docs/commands/graph.md#graphoperations) | List all of the locally available operations |
2629
| [`graph:pull`](/docs/commands/graph.md#graphpull) | Pull down your local Netlify Graph schema, and process pending Graph edit events |
2730

2831

@@ -50,6 +53,61 @@ netlify graph:edit
5053
- `httpProxy` (*string*) - Proxy server address to route requests through.
5154
- `httpProxyCertificateFilename` (*string*) - Certificate file to use when connecting using a proxy server
5255

56+
---
57+
## `graph:handler`
58+
59+
Generate a handler for a Graph operation given its name. See `graph:operations` for a list of operations.
60+
61+
**Usage**
62+
63+
```bash
64+
netlify graph:handler
65+
```
66+
67+
**Arguments**
68+
69+
- name - Operation name
70+
71+
**Flags**
72+
73+
- `debug` (*boolean*) - Print debugging information
74+
- `httpProxy` (*string*) - Proxy server address to route requests through.
75+
- `httpProxyCertificateFilename` (*string*) - Certificate file to use when connecting using a proxy server
76+
77+
---
78+
## `graph:library`
79+
80+
Generate the Graph function library
81+
82+
**Usage**
83+
84+
```bash
85+
netlify graph:library
86+
```
87+
88+
**Flags**
89+
90+
- `debug` (*boolean*) - Print debugging information
91+
- `httpProxy` (*string*) - Proxy server address to route requests through.
92+
- `httpProxyCertificateFilename` (*string*) - Certificate file to use when connecting using a proxy server
93+
94+
---
95+
## `graph:operations`
96+
97+
List all of the locally available operations
98+
99+
**Usage**
100+
101+
```bash
102+
netlify graph:operations
103+
```
104+
105+
**Flags**
106+
107+
- `debug` (*boolean*) - Print debugging information
108+
- `httpProxy` (*string*) - Proxy server address to route requests through.
109+
- `httpProxyCertificateFilename` (*string*) - Certificate file to use when connecting using a proxy server
110+
53111
---
54112
## `graph:pull`
55113

‎docs/commands/index.md

+3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ Manage netlify functions
9898
| Subcommand | description |
9999
|:--------------------------- |:-----|
100100
| [`graph:edit`](/docs/commands/graph.md#graphedit) | Launch the browser to edit your local graph functions from Netlify |
101+
| [`graph:handler`](/docs/commands/graph.md#graphhandler) | Generate a handler for a Graph operation given its name. See `graph:operations` for a list of operations. |
102+
| [`graph:library`](/docs/commands/graph.md#graphlibrary) | Generate the Graph function library |
103+
| [`graph:operations`](/docs/commands/graph.md#graphoperations) | List all of the locally available operations |
101104
| [`graph:pull`](/docs/commands/graph.md#graphpull) | Pull down your local Netlify Graph schema, and process pending Graph edit events |
102105

103106

‎npm-shrinkwrap.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"url": "https://github.com/netlify/cli/issues"
4444
},
4545
"scripts": {
46+
"snap": "ava --verbose -u",
4647
"prepare": "husky install node_modules/@netlify/eslint-config-node/.husky/",
4748
"start": "node ./bin/run",
4849
"test": "run-s format test:dev",
@@ -149,7 +150,7 @@
149150
"multiparty": "^4.2.1",
150151
"netlify": "^11.0.0",
151152
"netlify-headers-parser": "^6.0.1",
152-
"netlify-onegraph-internal": "0.0.18",
153+
"netlify-onegraph-internal": "0.0.28",
153154
"netlify-redirect-parser": "^13.0.2",
154155
"netlify-redirector": "^0.2.1",
155156
"node-fetch": "^2.6.0",

‎src/commands/graph/graph-edit.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-check
12
const gitRepoInfo = require('git-repo-info')
23

34
const { OneGraphCliClient, generateSessionName, loadCLISession } = require('../../lib/one-graph/cli-client')
@@ -15,7 +16,7 @@ const { createCLISession, createPersistedQuery, ensureAppForSite, updateCLISessi
1516
/**
1617
* Creates the `netlify graph:edit` command
1718
* @param {import('commander').OptionValues} options
18-
* @param {import('../base-command').BaseCommand} program
19+
* @param {import('../base-command').BaseCommand} command
1920
* @returns
2021
*/
2122
const graphEdit = async (options, command) => {
@@ -46,7 +47,7 @@ const graphEdit = async (options, command) => {
4647
let oneGraphSessionId = loadCLISession(state)
4748
if (!oneGraphSessionId) {
4849
const sessionName = generateSessionName()
49-
const oneGraphSession = await createCLISession(netlifyToken, site.id, sessionName)
50+
const oneGraphSession = await createCLISession(netlifyToken, site.id, sessionName, null)
5051
state.set('oneGraphSessionId', oneGraphSession.id)
5152
oneGraphSessionId = state.get('oneGraphSessionId')
5253
}

‎src/commands/graph/graph-handler.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// @ts-check
2+
const {
3+
buildSchema,
4+
generateHandlerByOperationName,
5+
getNetlifyGraphConfig,
6+
readGraphQLSchemaFile,
7+
} = require('../../lib/one-graph/cli-netlify-graph')
8+
const { error } = require('../../utils')
9+
10+
/**
11+
* Creates the `netlify graph:handler` command
12+
* @param {string} operationName
13+
* @param {import('commander').OptionValues} options
14+
* @param {import('../base-command').BaseCommand} command
15+
* @returns
16+
*/
17+
const graphHandler = async (operationName, options, command) => {
18+
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options })
19+
20+
const schemaString = readGraphQLSchemaFile(netlifyGraphConfig)
21+
22+
let schema
23+
24+
try {
25+
schema = buildSchema(schemaString)
26+
} catch (buildSchemaError) {
27+
error(`Error parsing schema: ${buildSchemaError}`)
28+
}
29+
30+
if (!schema) {
31+
error(`Failed to parse Netlify GraphQL schema`)
32+
}
33+
34+
generateHandlerByOperationName(netlifyGraphConfig, schema, operationName, {})
35+
}
36+
37+
/**
38+
* Creates the `netlify graph:handler` command
39+
* @param {import('../base-command').BaseCommand} program
40+
* @returns
41+
*/
42+
const createGraphHandlerCommand = (program) =>
43+
program
44+
.command('graph:handler')
45+
.argument('<name>', 'Operation name')
46+
.description(
47+
'Generate a handler for a Graph operation given its name. See `graph:operations` for a list of operations.',
48+
)
49+
.action(async (operationName, options, command) => {
50+
await graphHandler(operationName, options, command)
51+
})
52+
53+
module.exports = { createGraphHandlerCommand }

‎src/commands/graph/graph-library.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// @ts-check
2+
const {
3+
buildSchema,
4+
defaultExampleOperationsDoc,
5+
extractFunctionsFromOperationDoc,
6+
generateFunctionsFile,
7+
getNetlifyGraphConfig,
8+
parse,
9+
readGraphQLOperationsSourceFile,
10+
readGraphQLSchemaFile,
11+
} = require('../../lib/one-graph/cli-netlify-graph')
12+
const { error } = require('../../utils')
13+
14+
/**
15+
* Creates the `netlify graph:library` command
16+
* @param {import('commander').OptionValues} options
17+
* @param {import('../base-command').BaseCommand} command
18+
* @returns
19+
*/
20+
const graphLibrary = async (options, command) => {
21+
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options })
22+
23+
const schemaString = readGraphQLSchemaFile(netlifyGraphConfig)
24+
25+
let schema
26+
27+
try {
28+
schema = buildSchema(schemaString)
29+
} catch (buildSchemaError) {
30+
error(`Error parsing schema: ${buildSchemaError}`)
31+
}
32+
33+
if (!schema) {
34+
error(`Failed to parse Netlify GraphQL schema`)
35+
}
36+
37+
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
38+
if (currentOperationsDoc.trim().length === 0) {
39+
currentOperationsDoc = defaultExampleOperationsDoc
40+
}
41+
42+
const parsedDoc = parse(currentOperationsDoc)
43+
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
44+
45+
generateFunctionsFile({ netlifyGraphConfig, schema, operationsDoc: currentOperationsDoc, functions, fragments })
46+
}
47+
48+
/**
49+
* Creates the `netlify graph:library` command
50+
* @param {import('../base-command').BaseCommand} program
51+
* @returns
52+
*/
53+
const createGraphLibraryCommand = (program) =>
54+
program
55+
.command('graph:library')
56+
.description('Generate the Graph function library')
57+
.action(async (options, command) => {
58+
await graphLibrary(options, command)
59+
})
60+
61+
module.exports = { createGraphLibraryCommand }
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// @ts-check
2+
const { GraphQL } = require('netlify-onegraph-internal')
3+
4+
const {
5+
defaultExampleOperationsDoc,
6+
extractFunctionsFromOperationDoc,
7+
getNetlifyGraphConfig,
8+
readGraphQLOperationsSourceFile,
9+
} = require('../../lib/one-graph/cli-netlify-graph')
10+
const { log } = require('../../utils')
11+
12+
const { parse } = GraphQL
13+
14+
/**
15+
* Creates the `netlify graph:operations` command
16+
* @param {import('commander').OptionValues} options
17+
* @param {import('../base-command').BaseCommand} command
18+
* @returns
19+
*/
20+
const graphOperations = async (options, command) => {
21+
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options })
22+
try {
23+
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
24+
if (currentOperationsDoc.trim().length === 0) {
25+
currentOperationsDoc = defaultExampleOperationsDoc
26+
}
27+
28+
const parsedDoc = parse(currentOperationsDoc)
29+
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
30+
31+
const sorted = {
32+
queries: [],
33+
mutations: [],
34+
subscriptions: [],
35+
fragments: [],
36+
other: [],
37+
}
38+
39+
// Sort the operations by name and add them to the correct array under the operation type in sorted
40+
Object.values(functions)
41+
.sort((aItem, bItem) => aItem.operationName.localeCompare(bItem.operationName))
42+
.forEach((operation) => {
43+
switch (operation.kind) {
44+
case 'query': {
45+
sorted.queries.push(operation)
46+
47+
break
48+
}
49+
case 'mutation': {
50+
sorted.mutations.push(operation)
51+
52+
break
53+
}
54+
case 'subscription': {
55+
sorted.subscriptions.push(operation)
56+
57+
break
58+
}
59+
default: {
60+
sorted.other.push(operation)
61+
}
62+
}
63+
})
64+
65+
Object.values(fragments)
66+
.sort((aItem, bItem) => aItem.fragmentName.localeCompare(bItem.fragmentName))
67+
.forEach((fragment) => {
68+
sorted.fragments.push(fragment)
69+
})
70+
71+
if (sorted.queries.length !== 0) {
72+
log(`Queries:`)
73+
sorted.queries.forEach((operation) => {
74+
log(`\t${operation.operationName}`)
75+
})
76+
}
77+
if (sorted.mutations.length !== 0) {
78+
log(`Mutations:`)
79+
sorted.mutations.forEach((operation) => {
80+
log(`\t${operation.operationName}`)
81+
})
82+
}
83+
if (sorted.subscriptions.length !== 0) {
84+
log(`Subscriptions:`)
85+
sorted.subscriptions.forEach((operation) => {
86+
log(`\t${operation.operationName}`)
87+
})
88+
}
89+
if (sorted.fragments.length !== 0) {
90+
log(`Fragments:`)
91+
sorted.fragments.forEach((fragment) => {
92+
log(`\t${fragment.fragmentName}`)
93+
})
94+
}
95+
} catch (error) {
96+
error(`Error parsing operations library: ${error}`)
97+
}
98+
}
99+
100+
/**
101+
* Creates the `netlify graph:operations` command
102+
* @param {import('../base-command').BaseCommand} program
103+
* @returns
104+
*/
105+
const createGraphOperationCommand = (program) =>
106+
program
107+
.command('graph:operations')
108+
.description('List all of the locally available operations')
109+
.action(async (options, command) => {
110+
await graphOperations(options, command)
111+
})
112+
113+
module.exports = { createGraphOperationCommand }

‎src/commands/graph/graph-pull.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-check
12
/* eslint-disable eslint-comments/disable-enable-pair */
23
/* eslint-disable fp/no-loops */
34
const {
@@ -12,7 +13,7 @@ const { chalk, error, warn } = require('../../utils')
1213
/**
1314
* Creates the `netlify graph:pull` command
1415
* @param {import('commander').OptionValues} options
15-
* @param {import('../base-command').BaseCommand} program
16+
* @param {import('../base-command').BaseCommand} command
1617
* @returns
1718
*/
1819
const graphPull = async (options, command) => {
@@ -60,7 +61,7 @@ const graphPull = async (options, command) => {
6061
})
6162

6263
if (next.errors) {
63-
error(`Failed to fetch Netlify Graph cli session events`, next.errors)
64+
error(`Failed to fetch Netlify Graph cli session events: ${JSON.stringify(next.errors, null, 2)}`)
6465
}
6566

6667
if (next.events) {

‎src/commands/graph/graph.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
// @ts-check
22
const { createGraphEditCommand } = require('./graph-edit')
3+
const { createGraphHandlerCommand } = require('./graph-handler')
4+
const { createGraphLibraryCommand } = require('./graph-library')
5+
const { createGraphOperationCommand } = require('./graph-operations')
36
const { createGraphPullCommand } = require('./graph-pull')
47

58
/**
@@ -18,6 +21,9 @@ const graph = (options, command) => {
1821
*/
1922
const createGraphCommand = (program) => {
2023
createGraphEditCommand(program)
24+
createGraphHandlerCommand(program)
25+
createGraphLibraryCommand(program)
26+
createGraphOperationCommand(program)
2127
createGraphPullCommand(program)
2228

2329
return program

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

+40-24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-check
12
/* eslint-disable eslint-comments/disable-enable-pair */
23
/* eslint-disable fp/no-loops */
34
const crypto = require('crypto')
@@ -8,12 +9,13 @@ const gitRepoInfo = require('git-repo-info')
89
const { GraphQL, InternalConsole, OneGraphClient } = require('netlify-onegraph-internal')
910
const { NetlifyGraph } = require('netlify-onegraph-internal')
1011

11-
const { chalk, error, log, warn } = require('../../utils')
12+
// eslint-disable-next-line no-unused-vars
13+
const { StateConfig, chalk, error, log, warn } = require('../../utils')
1214
const { watchDebounced } = require('../functions/watcher')
1315

1416
const {
1517
generateFunctionsFile,
16-
generateHandler,
18+
generateHandlerByOperationId,
1719
readGraphQLOperationsSourceFile,
1820
writeGraphQLOperationsSourceFile,
1921
writeGraphQLSchemaFile,
@@ -30,22 +32,24 @@ const internalConsole = {
3032
debug: console.debug,
3133
}
3234

35+
/**
36+
* Keep track of which document hashes we've received from the server so we can ignore events from the filesystem based on them
37+
*/
3338
const witnessedIncomingDocumentHashes = []
3439

35-
// Keep track of which document hashes we've received from the server so we can ignore events from the filesystem based on them
3640
InternalConsole.registerConsole(internalConsole)
3741

3842
/**
3943
* Start polling for CLI events for a given session to process locally
4044
* @param {object} input
4145
* @param {string} input.appId The app to query against, typically the siteId
4246
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
43-
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
47+
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
4448
* @param {function} input.onClose A function to call when the polling loop is closed
4549
* @param {function} input.onError A function to call when an error occurs
4650
* @param {function} input.onEvents A function to call when CLI events are received and need to be processed
4751
* @param {string} input.sessionId The session id to monitor CLI events for
48-
* @param {state} input.state A function to call to set/get the current state of the local Netlify project
52+
* @param {StateConfig} input.state A function to call to set/get the current state of the local Netlify project
4953
* @returns
5054
*/
5155
const monitorCLISessionEvents = (input) => {
@@ -117,15 +121,16 @@ const monitorCLISessionEvents = (input) => {
117121
/**
118122
* Monitor the operations document for changes
119123
* @param {object} input
120-
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
121-
* @param {function} input.onAdd A callback function to handle when the operations document is added
122-
* @param {function} input.onChange A callback function to handle when the operations document is changed
123-
* @param {function} input.onUnlink A callback function to handle when the operations document is unlinked
124-
* @returns {Promise<watcher>}
124+
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
125+
* @param {() => void} input.onAdd A callback function to handle when the operations document is added
126+
* @param {() => void} input.onChange A callback function to handle when the operations document is changed
127+
* @param {() => void=} input.onUnlink A callback function to handle when the operations document is unlinked
128+
* @returns {Promise<any>}
125129
*/
126130
const monitorOperationFile = async ({ netlifyGraphConfig, onAdd, onChange, onUnlink }) => {
127131
const filePath = path.resolve(...netlifyGraphConfig.graphQLOperationsSourceFilename)
128132
const newWatcher = await watchDebounced([filePath], {
133+
depth: 1,
129134
onAdd,
130135
onChange,
131136
onUnlink,
@@ -139,8 +144,8 @@ const monitorOperationFile = async ({ netlifyGraphConfig, onAdd, onChange, onUnl
139144
* @param {object} input
140145
* @param {string} input.siteId The id of the site to query against
141146
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
142-
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
143-
* @param {state} input.state A function to call to set/get the current state of the local Netlify project
147+
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
148+
* @param {StateConfig} input.state A function to call to set/get the current state of the local Netlify project
144149
* @returns {Promise<void>}
145150
*/
146151
const refetchAndGenerateFromOneGraph = async (input) => {
@@ -174,8 +179,8 @@ const refetchAndGenerateFromOneGraph = async (input) => {
174179
/**
175180
* Regenerate the function library based on the current operations document on disk
176181
* @param {object} input
177-
* @param {string} input.schema The GraphQL schema to use when generating code
178-
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
182+
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
183+
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
179184
* @returns
180185
*/
181186
const regenerateFunctionsFileFromOperationsFile = (input) => {
@@ -214,15 +219,15 @@ const quickHash = (input) => {
214219
* @param {string} input.siteId The site id to query against
215220
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
216221
* @param {string} input.docId The GraphQL operations document id to fetch
217-
* @param {string} input.schema The GraphQL schema to use when generating code
218-
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
222+
* @param {GraphQL.GraphQLSchema} input.schema The GraphQL schema to use when generating code
223+
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
219224
* @returns
220225
*/
221226
const updateGraphQLOperationsFileFromPersistedDoc = async (input) => {
222227
const { docId, netlifyGraphConfig, netlifyToken, schema, siteId } = input
223228
const persistedDoc = await OneGraphClient.fetchPersistedQuery(netlifyToken, siteId, docId)
224229
if (!persistedDoc) {
225-
warn('No persisted doc found for:', docId)
230+
warn(`No persisted doc found for: ${docId}`)
226231
return
227232
}
228233

@@ -249,7 +254,11 @@ const handleCliSessionEvent = async ({ event, netlifyGraphConfig, netlifyToken,
249254
await handleCliSessionEvent({ netlifyToken, event: payload, netlifyGraphConfig, schema, siteId })
250255
break
251256
case 'OneGraphNetlifyCliSessionGenerateHandlerEvent':
252-
await generateHandler(netlifyGraphConfig, schema, payload.operationId, payload)
257+
if (!payload.operationId || !payload.operationId.id) {
258+
warn(`No operation id found in payload, ${JSON.stringify(payload, null, 2)}`)
259+
return
260+
}
261+
generateHandlerByOperationId(netlifyGraphConfig, schema, payload.operationId.id, payload)
253262
break
254263
case 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent':
255264
await updateGraphQLOperationsFileFromPersistedDoc({
@@ -261,7 +270,13 @@ const handleCliSessionEvent = async ({ event, netlifyGraphConfig, netlifyToken,
261270
})
262271
break
263272
default: {
264-
warn(`Unrecognized event received, you may need to upgrade your CLI version`, __typename, payload)
273+
warn(
274+
`Unrecognized event received, you may need to upgrade your CLI version: ${__typename}: ${JSON.stringify(
275+
payload,
276+
null,
277+
2,
278+
)}`,
279+
)
265280
break
266281
}
267282
}
@@ -281,13 +296,13 @@ const persistNewOperationsDocForSession = async ({ netlifyToken, oneGraphSession
281296
const result = await OneGraphClient.updateCLISessionMetadata(netlifyToken, siteId, oneGraphSessionId, newMetadata)
282297

283298
if (result.errors) {
284-
warn('Unable to update session metadata with updated operations doc', result.errors)
299+
warn(`Unable to update session metadata with updated operations doc ${JSON.stringify(result.errors, null, 2)}`)
285300
}
286301
}
287302

288303
/**
289304
* Load the CLI session id from the local state
290-
* @param {state} state
305+
* @param {StateConfig} state
291306
* @returns
292307
*/
293308
const loadCLISession = (state) => state.get('oneGraphSessionId')
@@ -296,9 +311,9 @@ const loadCLISession = (state) => state.get('oneGraphSessionId')
296311
* Idemponentially save the CLI session id to the local state and start monitoring for CLI events, upstream schema changes, and local operation file changes
297312
* @param {object} input
298313
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
299-
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
300-
* @param {state} input.state A function to call to set/get the current state of the local Netlify project
301-
* @param {site} input.site The site object
314+
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
315+
* @param {StateConfig} input.state A function to call to set/get the current state of the local Netlify project
316+
* @param {any} input.site The site object
302317
*/
303318
const startOneGraphCLISession = async (input) => {
304319
const { netlifyGraphConfig, netlifyToken, site, state } = input
@@ -387,6 +402,7 @@ const OneGraphCliClient = {
387402

388403
module.exports = {
389404
OneGraphCliClient,
405+
extractFunctionsFromOperationDoc,
390406
handleCliSessionEvent,
391407
generateSessionName,
392408
loadCLISession,

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

+79-32
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-check
12
const fs = require('fs')
23
const path = require('path')
34
const process = require('process')
@@ -17,6 +18,8 @@ const internalConsole = {
1718

1819
InternalConsole.registerConsole(internalConsole)
1920

21+
const { extractFunctionsFromOperationDoc } = NetlifyGraph
22+
2023
/**
2124
* Remove any relative path components from the given path
2225
* @param {string[]} items Filesystem path items to filter
@@ -27,10 +30,11 @@ const filterRelativePathItems = (items) => items.filter((part) => part !== '')
2730
/**
2831
* Return the default Netlify Graph configuration for a generic site
2932
* @param {object} context
33+
* @param {object} context.baseConfig
3034
* @param {string[]} context.detectedFunctionsPath
3135
* @param {string[]} context.siteRoot
3236
*/
33-
const makeDefaultNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath }) => {
37+
const makeDefaultNetlifyGraphConfig = ({ baseConfig, detectedFunctionsPath }) => {
3438
const functionsPath = filterRelativePathItems([...detectedFunctionsPath])
3539
const webhookBasePath = '/.netlify/functions'
3640
const netlifyGraphPath = [...functionsPath, 'netlifyGraph']
@@ -57,10 +61,11 @@ const makeDefaultNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath }) =>
5761
/**
5862
* Return the default Netlify Graph configuration for a Nextjs site
5963
* @param {object} context
64+
* @param {object} context.baseConfig
6065
* @param {string[]} context.detectedFunctionsPath
6166
* @param {string[]} context.siteRoot
6267
*/
63-
const makeDefaultNextJsNetlifGraphConfig = ({ baseConfig, siteRoot }) => {
68+
const makeDefaultNextJsNetlifyGraphConfig = ({ baseConfig, siteRoot }) => {
6469
const functionsPath = filterRelativePathItems([...siteRoot, 'pages', 'api'])
6570
const webhookBasePath = '/api'
6671
const netlifyGraphPath = filterRelativePathItems([...siteRoot, 'lib', 'netlifyGraph'])
@@ -87,10 +92,11 @@ const makeDefaultNextJsNetlifGraphConfig = ({ baseConfig, siteRoot }) => {
8792
/**
8893
* Return the default Netlify Graph configuration for a Remix site
8994
* @param {object} context
95+
* @param {object} context.baseConfig
9096
* @param {string[]} context.detectedFunctionsPath
9197
* @param {string[]} context.siteRoot
9298
*/
93-
const makeDefaultRemixNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath, siteRoot }) => {
99+
const makeDefaultRemixNetlifyGraphConfig = ({ baseConfig, detectedFunctionsPath, siteRoot }) => {
94100
const functionsPath = filterRelativePathItems([...detectedFunctionsPath])
95101
const webhookBasePath = '/webhooks'
96102
const netlifyGraphPath = filterRelativePathItems([
@@ -118,22 +124,25 @@ const makeDefaultRemixNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath,
118124
}
119125

120126
const defaultFrameworkLookup = {
121-
'Next.js': makeDefaultNextJsNetlifGraphConfig,
122-
Remix: makeDefaultRemixNetlifGraphConfig,
123-
default: makeDefaultNetlifGraphConfig,
127+
'Next.js': makeDefaultNextJsNetlifyGraphConfig,
128+
Remix: makeDefaultRemixNetlifyGraphConfig,
129+
default: makeDefaultNetlifyGraphConfig,
124130
}
125131

126132
/**
127133
* Return a full NetlifyGraph config with any defaults overridden by netlify.toml
128-
* @param {import('../base-command').BaseCommand} command
129-
* @return {NetlifyGraphConfig} NetlifyGraphConfig
134+
* @param {object} input
135+
* @param {import('../../commands/base-command').BaseCommand} input.command
136+
* @param {import('commander').OptionValues} input.options
137+
* @param {Partial<import('../../utils/types').ServerSettings>=} input.settings
138+
* @return {Promise<NetlifyGraph.NetlifyGraphConfig>} NetlifyGraphConfig
130139
*/
131140
const getNetlifyGraphConfig = async ({ command, options, settings }) => {
132141
const { config, site } = command.netlify
133142
config.dev = { ...config.dev }
134143
config.build = { ...config.build }
135144
const userSpecifiedConfig = (config && config.graph) || {}
136-
/** @type {import('./types').DevConfig} */
145+
/** @type {import('../../commands/dev/types').DevConfig} */
137146
const devConfig = {
138147
framework: '#auto',
139148
...(config.functionsDirectory && { functions: config.functionsDirectory }),
@@ -148,7 +157,13 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
148157
settings = await detectServerSettings(devConfig, options, site.root)
149158
} catch (detectServerSettingsError) {
150159
settings = {}
151-
warn('Error while auto-detecting project settings, Netlify Graph encounter problems', detectServerSettingsError)
160+
warn(
161+
`Error while auto-detecting project settings, Netlify Graph encounter problems: ${JSON.stringify(
162+
detectServerSettingsError,
163+
null,
164+
2,
165+
)}`,
166+
)
152167
}
153168
}
154169

@@ -167,9 +182,11 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
167182
const baseConfig = { ...NetlifyGraph.defaultNetlifyGraphConfig, ...userSpecifiedConfig }
168183
const defaultFrameworkConfig = makeDefaultFrameworkConfig({ baseConfig, detectedFunctionsPath, siteRoot })
169184

185+
const userSpecifiedFunctionPath =
186+
userSpecifiedConfig.functionsPath && userSpecifiedConfig.functionsPath.split(path.sep)
187+
170188
const functionsPath =
171-
(userSpecifiedConfig.functionsPath && userSpecifiedConfig.functionsPath.split(path.sep)) ||
172-
defaultFrameworkConfig.functionsPath
189+
(userSpecifiedFunctionPath && [...siteRoot, ...userSpecifiedFunctionPath]) || defaultFrameworkConfig.functionsPath
173190
const netlifyGraphPath =
174191
(userSpecifiedConfig.netlifyGraphPath && userSpecifiedConfig.netlifyGraphPath.split(path.sep)) ||
175192
defaultFrameworkConfig.netlifyGraphPath
@@ -225,7 +242,7 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
225242

226243
/**
227244
* Given a NetlifyGraphConfig, ensure that the netlifyGraphPath exists
228-
* @param {NetlifyGraphConfig} netlifyGraphConfig
245+
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
229246
*/
230247
const ensureNetlifyGraphPath = (netlifyGraphConfig) => {
231248
const fullPath = path.resolve(...netlifyGraphConfig.netlifyGraphPath)
@@ -234,7 +251,7 @@ const ensureNetlifyGraphPath = (netlifyGraphConfig) => {
234251

235252
/**
236253
* Given a NetlifyGraphConfig, ensure that the functionsPath exists
237-
* @param {NetlifyGraphConfig} netlifyGraphConfig
254+
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
238255
*/
239256
const ensureFunctionsPath = (netlifyGraphConfig) => {
240257
const fullPath = path.resolve(...netlifyGraphConfig.functionsPath)
@@ -248,9 +265,8 @@ const runPrettier = async (filePath) => {
248265
return
249266
}
250267

251-
const command = `prettier --write ${filePath}`
252268
try {
253-
const commandProcess = execa.command(command, {
269+
const commandProcess = execa('prettier', ['--write', filePath], {
254270
preferLocal: true,
255271
// windowsHide needs to be false for child process to terminate properly on Windows
256272
windowsHide: false,
@@ -269,11 +285,11 @@ const runPrettier = async (filePath) => {
269285
/**
270286
* Generate a library file with type definitions for a given NetlifyGraphConfig, operationsDoc, and schema, writing them to the filesystem
271287
* @param {object} context
272-
* @param {NetlifyGraphConfig} context.netlifyGraphConfig
273-
* @param {GraphQLSchema} context.schema The schema to use when generating the functions and their types
288+
* @param {NetlifyGraph.NetlifyGraphConfig} context.netlifyGraphConfig
289+
* @param {GraphQL.GraphQLSchema} context.schema The schema to use when generating the functions and their types
274290
* @param {string} context.operationsDoc The GraphQL operations doc to use when generating the functions
275-
* @param {NetlifyGraph.ParsedFunction} context.functions The parsed queries with metadata to use when generating library functions
276-
* @param {NetlifyGraph.ParsedFragment} context.fragments The parsed queries with metadata to use when generating library functions
291+
* @param {Record<string, NetlifyGraph.ExtractedFunction>} context.functions The parsed queries with metadata to use when generating library functions
292+
* @param {Record<string, NetlifyGraph.ExtractedFragment>} context.fragments The parsed queries with metadata to use when generating library functions
277293
* @returns {void} Void, effectfully writes the generated library to the filesystem
278294
*/
279295
const generateFunctionsFile = ({ fragments, functions, netlifyGraphConfig, operationsDoc, schema }) => {
@@ -298,7 +314,7 @@ const generateFunctionsFile = ({ fragments, functions, netlifyGraphConfig, opera
298314

299315
/**
300316
* Using the given NetlifyGraphConfig, read the GraphQL operations file and return the _unparsed_ GraphQL operations doc
301-
* @param {NetlifyGraphConfig} netlifyGraphConfig
317+
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
302318
* @returns {string} GraphQL operations doc
303319
*/
304320
const readGraphQLOperationsSourceFile = (netlifyGraphConfig) => {
@@ -317,20 +333,20 @@ const readGraphQLOperationsSourceFile = (netlifyGraphConfig) => {
317333

318334
/**
319335
* Write an operations doc to the filesystem using the given NetlifyGraphConfig
320-
* @param {NetlifyGraphConfig} netlifyGraphConfig
321-
* @param {string} operationsDoc The GraphQL operations doc to write
336+
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
337+
* @param {string} operationsDocString The GraphQL operations doc to write
322338
*/
323-
const writeGraphQLOperationsSourceFile = (netlifyGraphConfig, operationDocString) => {
324-
const graphqlSource = operationDocString
339+
const writeGraphQLOperationsSourceFile = (netlifyGraphConfig, operationsDocString) => {
340+
const graphqlSource = operationsDocString
325341

326342
ensureNetlifyGraphPath(netlifyGraphConfig)
327343
fs.writeFileSync(path.resolve(...netlifyGraphConfig.graphQLOperationsSourceFilename), graphqlSource, 'utf8')
328344
}
329345

330346
/**
331347
* Write a GraphQL Schema printed in SDL format to the filesystem using the given NetlifyGraphConfig
332-
* @param {NetlifyGraphConfig} netlifyGraphConfig
333-
* @param {GraphQLSchema} schema The GraphQL schema to print and write to the filesystem
348+
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
349+
* @param {GraphQL.GraphQLSchema} schema The GraphQL schema to print and write to the filesystem
334350
*/
335351
const writeGraphQLSchemaFile = (netlifyGraphConfig, schema) => {
336352
const graphqlSource = printSchema(schema)
@@ -341,7 +357,7 @@ const writeGraphQLSchemaFile = (netlifyGraphConfig, schema) => {
341357

342358
/**
343359
* Using the given NetlifyGraphConfig, read the GraphQL schema file and return it _unparsed_
344-
* @param {NetlifyGraphConfig} netlifyGraphConfig
360+
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
345361
* @returns {string} GraphQL schema
346362
*/
347363
const readGraphQLSchemaFile = (netlifyGraphConfig) => {
@@ -351,13 +367,13 @@ const readGraphQLSchemaFile = (netlifyGraphConfig) => {
351367

352368
/**
353369
* Given a NetlifyGraphConfig, read the appropriate files and write a handler for the given operationId to the filesystem
354-
* @param {NetlifyGraphConfig} netlifyGraphConfig
355-
* @param {GraphQLSchema} schema The GraphQL schema to use when generating the handler
370+
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
371+
* @param {GraphQL.GraphQLSchema} schema The GraphQL schema to use when generating the handler
356372
* @param {string} operationId The operationId to use when generating the handler
357373
* @param {object} handlerOptions The options to use when generating the handler
358374
* @returns
359375
*/
360-
const generateHandler = (netlifyGraphConfig, schema, operationId, handlerOptions) => {
376+
const generateHandlerByOperationId = (netlifyGraphConfig, schema, operationId, handlerOptions) => {
361377
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
362378
if (currentOperationsDoc.trim().length === 0) {
363379
currentOperationsDoc = NetlifyGraph.defaultExampleOperationsDoc
@@ -415,6 +431,35 @@ const generateHandler = (netlifyGraphConfig, schema, operationId, handlerOptions
415431
})
416432
}
417433

434+
/**
435+
* Given a NetlifyGraphConfig, read the appropriate files and write a handler for the given operationId to the filesystem
436+
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
437+
* @param {GraphQL.GraphQLSchema} schema The GraphQL schema to use when generating the handler
438+
* @param {string} operationName The name of the operation to use when generating the handler
439+
* @param {object} handlerOptions The options to use when generating the handler
440+
* @returns
441+
*/
442+
const generateHandlerByOperationName = (netlifyGraphConfig, schema, operationName, handlerOptions) => {
443+
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
444+
if (currentOperationsDoc.trim().length === 0) {
445+
currentOperationsDoc = NetlifyGraph.defaultExampleOperationsDoc
446+
}
447+
448+
const parsedDoc = parse(currentOperationsDoc)
449+
const { functions } = extractFunctionsFromOperationDoc(parsedDoc)
450+
451+
const operation = Object.values(functions).find(
452+
(potentialOperation) => potentialOperation.operationName === operationName,
453+
)
454+
455+
if (!operation) {
456+
warn(`No operation named ${operationName} was found in the operations doc`)
457+
return
458+
}
459+
460+
generateHandlerByOperationId(netlifyGraphConfig, schema, operation.id, handlerOptions)
461+
}
462+
418463
// Export the minimal set of functions that are required for Netlify Graph
419464
const { buildSchema, parse } = GraphQL
420465

@@ -455,13 +500,15 @@ module.exports = {
455500
generateFunctionsSource: NetlifyGraph.generateFunctionsSource,
456501
generateFunctionsFile,
457502
generateHandlerSource: NetlifyGraph.generateHandlerSource,
458-
generateHandler,
503+
generateHandlerByOperationId,
504+
generateHandlerByOperationName,
459505
getGraphEditUrlBySiteId,
460506
getGraphEditUrlBySiteName,
461507
getNetlifyGraphConfig,
462508
parse,
463509
readGraphQLOperationsSourceFile,
464510
readGraphQLSchemaFile,
511+
runPrettier,
465512
writeGraphQLOperationsSourceFile,
466513
writeGraphQLSchemaFile,
467514
}

‎src/utils/command-helpers.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ const USER_AGENT = `${name}/${version} ${platform}-${arch} node-${process.versio
4747
/** A list of base command flags that needs to be sorted down on documentation and on help pages */
4848
const BASE_FLAGS = new Set(['--debug', '--httpProxy', '--httpProxyCertificateFilename'])
4949

50-
const { NETLIFY_AUTH_TOKEN } = process.env
51-
5250
// eslint-disable-next-line no-magic-numbers
5351
const NETLIFY_CYAN = chalk.rgb(40, 180, 170)
5452

@@ -121,6 +119,7 @@ const getToken = async (tokenFromOptions) => {
121119
return [tokenFromOptions, 'flag']
122120
}
123121
// 2. then Check ENV var
122+
const { NETLIFY_AUTH_TOKEN } = process.env
124123
if (NETLIFY_AUTH_TOKEN && NETLIFY_AUTH_TOKEN !== 'null') {
125124
return [NETLIFY_AUTH_TOKEN, 'env']
126125
}
+231-38
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
/* eslint-disable eslint-comments/disable-enable-pair */
2+
/* eslint-disable no-unused-vars */
3+
// @ts-check
14
const fs = require('fs')
2-
const { join } = require('path')
5+
const path = require('path')
6+
const process = require('process')
37

48
const test = require('ava')
9+
const { GraphQL, NetlifyGraph } = require('netlify-onegraph-internal')
510

11+
const { runPrettier } = require('../../src/lib/one-graph/cli-netlify-graph')
612
const {
713
buildSchema,
814
extractFunctionsFromOperationDoc,
@@ -13,47 +19,72 @@ const {
1319

1420
const { normalize } = require('./utils/snapshots')
1521

16-
const netlifyGraphConfig = {
22+
/**
23+
* Given a path, ensure that the path exists
24+
* @param {string[]} filePath
25+
*/
26+
const ensurePath = (filePath) => {
27+
const fullPath = path.resolve(...filePath)
28+
fs.mkdirSync(fullPath, { recursive: true })
29+
}
30+
31+
/**
32+
* @constant
33+
* @type {NetlifyGraph.NetlifyGraphConfig}
34+
*/
35+
const baseNetlifyGraphConfig = {
1736
extension: 'js',
18-
netlifyGraphPath: 'netlify',
19-
moduleType: 'commonjs',
37+
netlifyGraphPath: ['netlify'],
38+
moduleType: 'esm',
2039
functionsPath: ['functions'],
21-
netlifyGraphImplementationFilename: 'dummy/index.js',
22-
netlifyGraphTypeDefinitionsFilename: 'dummy/index.d.ts',
23-
graphQLOperationsSourceFilename: 'dummy/netlifyGraphOperationsLibrary.graphql',
24-
graphQLSchemaFilename: 'dummy/netlifyGraphSchema.graphql',
40+
netlifyGraphImplementationFilename: ['dummy', 'index.js'],
41+
netlifyGraphTypeDefinitionsFilename: ['dummy', 'index.d.ts'],
42+
graphQLOperationsSourceFilename: ['dummy', 'netlifyGraphOperationsLibrary.graphql'],
43+
graphQLSchemaFilename: ['dummy', 'netlifyGraphSchema.graphql'],
44+
webhookBasePath: '/webhooks',
45+
netlifyGraphRequirePath: ['.', 'netlifyGraph'],
46+
framework: '#custom',
47+
language: 'javascript',
48+
runtimeTargetEnv: 'node',
2549
}
2650

27-
const loadAsset = (filename) => fs.readFileSync(join(__dirname, 'assets', filename), 'utf8')
28-
29-
test('netlify graph function codegen', (t) => {
30-
const schemaString = loadAsset('../assets/netlifyGraphSchema.graphql')
31-
const schema = buildSchema(schemaString)
51+
/**
52+
* @constant
53+
* @type {("esm" | "commonjs")[]}
54+
*/
55+
const moduleTypes = [
56+
'esm',
57+
/**
58+
* Restore this when we have a way to generate commonjs modules with typescript enabled
59+
*/
60+
// 'commonjs'
61+
]
3262

33-
const appOperationsDoc = loadAsset('../assets/netlifyGraphOperationsLibrary.graphql')
34-
const parsedDoc = parse(appOperationsDoc, {
35-
noLocation: true,
36-
})
37-
38-
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
39-
const generatedFunctions = generateFunctionsSource(netlifyGraphConfig, schema, appOperationsDoc, functions, fragments)
63+
const loadAsset = (filename) => fs.readFileSync(path.join(__dirname, 'assets', filename), 'utf8')
64+
const schemaString = loadAsset('../assets/netlifyGraphSchema.graphql')
65+
const commonSchema = buildSchema(schemaString)
4066

41-
t.snapshot(normalize(JSON.stringify(generatedFunctions)))
67+
const appOperationsDoc = loadAsset('../assets/netlifyGraphOperationsLibrary.graphql')
68+
const parsedDoc = parse(appOperationsDoc, {
69+
noLocation: true,
4270
})
4371

44-
test('netlify graph handler codegen', (t) => {
45-
const schemaString = loadAsset('../assets/netlifyGraphSchema.graphql')
46-
const schema = buildSchema(schemaString)
47-
48-
const appOperationsDoc = loadAsset('../assets/netlifyGraphOperationsLibrary.graphql')
49-
50-
// From the asset GraphQL file
51-
const operationId = 'd86699fb-ddfc-4833-9d9a-f3497cb7c992'
52-
const handlerOptions = {}
72+
/**
73+
*
74+
* @param {object} input
75+
* @param {Record<string, any>} input.handlerOptions
76+
* @param {string} input.operationId
77+
* @param {string} input.operationsDoc
78+
* @param {NetlifyGraph.NetlifyGraphConfig} input.netlifyGraphConfig
79+
* @param {GraphQL.GraphQLSchema} input.schema
80+
* @param {string[]} input.outDir
81+
* @returns
82+
*/
83+
const generateHandlerText = ({ handlerOptions, netlifyGraphConfig, operationId, operationsDoc, outDir, schema }) => {
5384
const result = generateHandlerSource({
5485
netlifyGraphConfig,
5586
schema,
56-
operationsDoc: appOperationsDoc,
87+
operationsDoc,
5788
operationId,
5889
handlerOptions,
5990
})
@@ -74,28 +105,190 @@ test('netlify graph handler codegen', (t) => {
74105
const { content } = exportedFile
75106
const isNamed = exportedFile.kind === 'NamedExportedFile'
76107

77-
let filenameArr
108+
let baseFilenameArr
78109

79110
if (isNamed) {
80-
filenameArr = [...exportedFile.name]
111+
baseFilenameArr = [...exportedFile.name]
81112
} else {
82113
const operationName = (operation.name && operation.name.value) || 'Unnamed'
83114
const fileExtension = netlifyGraphConfig.language === 'typescript' ? 'ts' : netlifyGraphConfig.extension
84115
const defaultBaseFilename = `${operationName}.${fileExtension}`
85116
const baseFilename = defaultBaseFilename
86117

87-
filenameArr = [...netlifyGraphConfig.functionsPath, baseFilename]
118+
baseFilenameArr = [baseFilename]
88119
}
89120

90-
const dummyPath = filenameArr.join('|')
121+
const filenameArr = [...outDir, ...baseFilenameArr]
122+
123+
const filePath = path.resolve(...filenameArr)
124+
const parentDir = filenameArr.slice(0, -1)
125+
126+
ensurePath(parentDir)
127+
fs.writeFileSync(filePath, content, 'utf8')
128+
// Run prettier to help normalize the output
129+
runPrettier(filePath)
91130

92-
sources.push([dummyPath, content])
131+
const prettierContent = fs.readFileSync(filePath, 'utf-8')
132+
133+
sources.push([filePath, baseFilenameArr, prettierContent])
93134
})
94135

136+
if (sources.length === 0) {
137+
console.warn(`No exported files found for operation ${operationId}`)
138+
}
139+
95140
const textualSource = sources
96141
.sort(([filenameA], [filenameB]) => filenameA[0].localeCompare(filenameB[0]))
97-
.map(([filename, content]) => `${filename}: ${content}`)
142+
.map(([_, baseFilenameArr, content]) => {
143+
// Strip the outDir from the filename so the output is the same regardless of where the tests are run
144+
const filename = baseFilenameArr.join('|')
145+
return `${filename}: ${content}`
146+
})
98147
.join('/-----------------/')
99148

100-
t.snapshot(normalize(JSON.stringify(textualSource)))
149+
return textualSource
150+
}
151+
152+
const testGenerateFunctionLibraryAndRuntime = ({ frameworkName, language, name, runtimeTargetEnv }) => {
153+
moduleTypes.forEach((moduleType) => {
154+
// @ts-ignore
155+
test(`netlify graph function library (+runtime) codegen [${frameworkName}-${name}-${language}-${moduleType}]`, (t) => {
156+
const outDirPath = path.join(process.cwd(), '_test_out')
157+
const outDir = [path.sep, ...outDirPath.split(path.sep), `netlify-graph-test-${frameworkName}-${moduleType}`]
158+
159+
/**
160+
* @constant
161+
* @type {NetlifyGraph.NetlifyGraphConfig}
162+
*/
163+
const netlifyGraphConfig = { ...baseNetlifyGraphConfig, runtimeTargetEnv, moduleType }
164+
165+
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)
166+
const generatedFunctions = generateFunctionsSource(
167+
netlifyGraphConfig,
168+
commonSchema,
169+
appOperationsDoc,
170+
functions,
171+
fragments,
172+
)
173+
const clientDefinitionsFilenameArr = [...outDir, 'netlifyGraph', 'index.js']
174+
const typescriptFilenameArr = [...outDir, 'netlifyGraph', 'index.d.ts']
175+
176+
const writeFile = (filenameArr, content) => {
177+
const filePath = path.resolve(...filenameArr)
178+
const parentDir = filenameArr.slice(0, -1)
179+
180+
ensurePath(parentDir)
181+
fs.writeFileSync(filePath, content, 'utf8')
182+
// Run prettier to help normalize the output (and also make sure we're generating parsable code)
183+
runPrettier(filePath)
184+
}
185+
186+
writeFile(typescriptFilenameArr, generatedFunctions.typeDefinitionsSource)
187+
writeFile(clientDefinitionsFilenameArr, generatedFunctions.clientSource)
188+
189+
const prettierGeneratedFunctions = {
190+
functionDefinitions: generatedFunctions.functionDefinitions,
191+
typeDefinitionsSource: fs.readFileSync(path.resolve(...typescriptFilenameArr), 'utf-8'),
192+
clientSource: fs.readFileSync(path.resolve(...clientDefinitionsFilenameArr), 'utf-8'),
193+
}
194+
195+
t.snapshot(normalize(JSON.stringify(prettierGeneratedFunctions)))
196+
})
197+
})
198+
}
199+
200+
const testGenerateHandlerSource = ({ frameworkName, language, name, operationId }) => {
201+
moduleTypes.forEach((moduleType) => {
202+
// @ts-ignore
203+
test(`netlify graph handler codegen [${frameworkName}-${name}-${language}-${moduleType}]`, (t) => {
204+
const outDirPath = path.join(process.cwd(), '_test_out')
205+
const outDir = [path.sep, ...outDirPath.split(path.sep), `netlify-graph-test-${frameworkName}-${moduleType}`]
206+
207+
/**
208+
* @constant
209+
* @type {NetlifyGraph.NetlifyGraphConfig}
210+
*/
211+
const netlifyGraphConfig = { ...baseNetlifyGraphConfig, framework: frameworkName, language, moduleType }
212+
213+
/**
214+
* @constant
215+
* @type Record<string, any>
216+
*/
217+
const handlerOptions = {}
218+
const textualSource = generateHandlerText({
219+
handlerOptions,
220+
netlifyGraphConfig,
221+
operationId,
222+
operationsDoc: appOperationsDoc,
223+
schema: commonSchema,
224+
outDir,
225+
})
226+
227+
t.snapshot(normalize(JSON.stringify(textualSource)))
228+
})
229+
})
230+
}
231+
232+
const frameworks = ['#custom', 'Next.js', 'Remix', 'unknown']
233+
234+
const queryWithFragmentOperationId = 'e2394c86-260c-4646-88df-7bc7370de666'
235+
frameworks.forEach((frameworkName) => {
236+
testGenerateFunctionLibraryAndRuntime({
237+
frameworkName,
238+
language: 'javascript',
239+
name: 'node',
240+
runtimeTargetEnv: 'node',
241+
})
242+
testGenerateFunctionLibraryAndRuntime({
243+
frameworkName,
244+
language: 'javascript',
245+
name: 'browser',
246+
runtimeTargetEnv: 'browser',
247+
})
248+
testGenerateHandlerSource({
249+
frameworkName,
250+
operationId: queryWithFragmentOperationId,
251+
name: 'queryWithFragment',
252+
language: 'javascript',
253+
})
254+
})
255+
256+
frameworks.forEach((frameworkName) => {
257+
testGenerateFunctionLibraryAndRuntime({
258+
frameworkName,
259+
language: 'typescript',
260+
name: 'node',
261+
runtimeTargetEnv: 'node',
262+
})
263+
testGenerateFunctionLibraryAndRuntime({
264+
frameworkName,
265+
language: 'typescript',
266+
name: 'browser',
267+
runtimeTargetEnv: 'browser',
268+
})
269+
testGenerateHandlerSource({
270+
frameworkName,
271+
operationId: queryWithFragmentOperationId,
272+
name: 'queryWithFragment',
273+
language: 'typescript',
274+
})
275+
})
276+
277+
const subscriptionWithFragmentOperationId = 'e3d4bb8b-2fb5-9898-b051-db6027224112'
278+
frameworks.forEach((frameworkName) => {
279+
testGenerateHandlerSource({
280+
frameworkName,
281+
operationId: subscriptionWithFragmentOperationId,
282+
name: 'subscriptionWithFragment',
283+
language: 'javascript',
284+
})
285+
})
286+
287+
frameworks.forEach((frameworkName) => {
288+
testGenerateHandlerSource({
289+
frameworkName,
290+
operationId: subscriptionWithFragmentOperationId,
291+
name: 'subscriptionWithFragment',
292+
language: 'typescript',
293+
})
101294
})

‎tests/integration/assets/netlifyGraphOperationsLibrary.graphql

+387-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,391 @@
1-
query ExampleQuery($package: String!) @netlify(id: "d86699fb-ddfc-4833-9d9a-f3497cb7c992", doc: "A test query to snapshot") {
2-
npm {
3-
package(name: $package) {
1+
fragment LoggedInServicesFragment on OneGraphServiceMetadata @netlify(id: """12b5bdea-9bab-4124-a731-5e697b1553be""", doc: """Subset of LoggedInServices""") {
2+
friendlyServiceName
3+
service
4+
isLoggedIn
5+
usedTestFlow
6+
serviceInfo {
7+
logoUrl
8+
availableScopes {
9+
category
10+
scope
11+
display
12+
isDefault
13+
isRequired
14+
description
15+
title
16+
}
17+
}
18+
grantedScopes {
19+
scope
20+
}
21+
foreignUserId
22+
}
23+
24+
fragment ServiceAuthFragment on OneGraphServiceAuth @netlify(id: """12b5bdea-9bab-4164-a731-5e697b1553be""", doc: """Basic info on a Service Auth""") {
25+
id
26+
service
27+
clientId
28+
revealTokens
29+
scopes
30+
}
31+
32+
fragment AppCORSOriginFragment on OneGraphApp @netlify(id: """e3d4bb8b-2fb5-48d8-b051-db6027224145""", doc: """Allowed CORS origins for calls to a site's Graph.""") {
33+
id
34+
corsOrigins
35+
customCorsOrigins {
36+
friendlyServiceName
37+
displayName
38+
encodedValue
39+
}
40+
netlifySiteNames
41+
}
42+
43+
mutation UpdateCLISessionMetadataMutation($nfToken: String!, $sessionId: String!, $metadata: JSON!) @netlify(id: """16a58acb-8188-4a47-bc93-1f4a5ef805c0""", doc: """Modify the metadata of an existing CLI session (an intentionally untype bag of `JSON`).""") {
44+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
45+
updateNetlifyCliSession(input: {id: $sessionId, metadata: $metadata}) {
46+
session {
47+
id
48+
name
49+
metadata
50+
createdAt
51+
lastEventAt
52+
}
53+
}
54+
}
55+
}
56+
57+
query AppSchemaQuery($nfToken: String!, $appId: String!) @netlify(id: """30aeff10-e743-473e-bae0-438a48074edc""", doc: """
58+
Get the _metadata_ about a site's current GraphQL schema:
59+
60+
- enabled services
61+
- schema id
62+
- creation date
63+
64+
etc.
65+
""") {
66+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
67+
app(id: $appId) {
68+
graphQLSchema {
69+
appId
70+
createdAt
71+
id
72+
services {
73+
friendlyServiceName
74+
logoUrl
75+
service
76+
slug
77+
supportsCustomRedirectUri
78+
supportsCustomServiceAuth
79+
supportsOauthLogin
80+
}
81+
updatedAt
82+
}
83+
}
84+
}
85+
}
86+
87+
mutation DestroyTokenMutation($nfToken: String!, $token: String, $authlifyTokenId: String) @netlify(id: """3d069fc8-3a03-40c8-8637-ddcf33692c34""", doc: """Delete a OneGraph personal token for a user's site""") {
88+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
89+
destroyToken(token: $token, authlifyTokenId: $authlifyTokenId)
90+
}
91+
}
92+
93+
mutation SignOutServicesMutation($services: [OneGraphServiceEnum!]!, $nfToken: String!, $authlifyTokenId: String!) @netlify(id: """3d069fc8-3a03-40c8-8637-ddcf33692c99""", doc: """Sign out of a service associated with a Authlify token""") {
94+
signoutServices(
95+
data: {services: $services, anchorAuth: {netlifyAuth: {oauthToken: $nfToken}}, authlifyTokenId: $authlifyTokenId}
96+
) {
97+
me {
98+
serviceMetadata {
99+
loggedInServices {
100+
...LoggedInServicesFragment
101+
}
102+
}
103+
}
104+
}
105+
}
106+
107+
mutation AddAuthsMutation($siteId: String! $authlifyTokenId: String, $sToken: String!, $nfToken: String!) @netlify(id: """47c6abec-7e34-4ec1-ae7d-b3303828b0ce""", doc: """Update a service's (i.e. GitHub) enabled scope permissions""") {
108+
oneGraph {
109+
addAuthsToPersonalToken(
110+
input: {anchorAuth: {netlifyAuth: {oauthToken: $nfToken}}, sacrificialToken: $sToken, authlifyTokenId: $authlifyTokenId, appId: $siteId}
111+
) {
112+
accessToken {
113+
netlifyId
114+
token
115+
}
116+
}
117+
}
118+
}
119+
120+
mutation CreateNewSchemaMutation($nfToken: String!, $input: OneGraphCreateGraphQLSchemaInput!) @netlify(id: """4fc2298a-225b-4329-b3f3-a8f8bc0513a8""", doc: """Create a new GraphQL schema for an app with a set of services enabled. Note that this just makes the schema _available_ for the app to use, it doesn't set it as the default for all queries to use.""") {
121+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
122+
createGraphQLSchema(input: $input) {
123+
app {
124+
graphQLSchema {
125+
id
126+
}
127+
}
128+
graphqlSchema {
129+
id
130+
services {
131+
friendlyServiceName
132+
logoUrl
133+
service
134+
slug
135+
supportsCustomRedirectUri
136+
supportsCustomServiceAuth
137+
supportsOauthLogin
138+
}
139+
}
140+
}
141+
}
142+
}
143+
144+
mutation DeleteServiceAuthMutation($siteId: String!, $serviceAuthId: String!, $nfToken: String!) @netlify(id: """5c7bb879-a810-4a7e-8aec-55d05fd9c172""", doc: """Delete a custom service auth""") {
145+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
146+
destroyServiceAuth(data: {appId: $siteId, serviceAuthId: $serviceAuthId}) {
147+
app {
148+
serviceAuths {
149+
...ServiceAuthFragment
150+
}
151+
}
152+
}
153+
}
154+
}
155+
156+
mutation CreatePersistedQueryMutation($nfToken: String!, $appId: String!, $query: String!, $tags: [String!]!, $description: String!, $parent: OneGraphCreatePersistedQueryParentInput) @netlify(id: """5e855574-a316-4060-955c-85b1f8898c29""", doc: """Given a document with GraphQL operations, persist them to OneGraph (with not specific metadata, e.g. cache TTL or auth) for later retrieval _or_ execution.""") {
157+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
158+
createPersistedQuery(
159+
input: {query: $query, appId: $appId, tags: $tags, description: $description, parent: $parent}
160+
) {
161+
persistedQuery {
162+
id
163+
}
164+
}
165+
}
166+
}
167+
168+
query FindLoggedInServicesQuery($nfToken: String!, $authlifyTokenId: String!) @netlify(id: """68c383e7-2e2f-4e6c-9a72-a5d095498ba3""", doc: """Fetch all logged-in OneGraph services (GitHub, Spotify, etc.) for a user's site""") {
169+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
170+
authlifyToken(authlifyTokenId: $authlifyTokenId) {
171+
serviceMetadata {
172+
loggedInServices {
173+
usedTestFlow
174+
friendlyServiceName
175+
...LoggedInServicesFragment
176+
}
177+
}
178+
}
179+
}
180+
}
181+
182+
mutation SetServiceAuthMutation($service: OneGraphCustomServiceAuthServiceEnum!, $clientId: String!, $clientSecret: String!, $siteId: String!, $nfToken: String!) @netlify(id: """694dfc01-3844-431d-9e56-7089c101fe08""", doc: """Create a custom service auth""") {
183+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
184+
createServiceAuth(
185+
data: {service: $service, clientId: $clientId, clientSecret: $clientSecret, appId: $siteId, revealTokens: true}
186+
) {
187+
app {
188+
serviceAuths {
189+
...ServiceAuthFragment
190+
}
191+
}
192+
}
193+
}
194+
}
195+
196+
mutation CreateCLISessionEventMutation($nfToken: String!, $sessionId: String!, $payload: JSON!) @netlify(id: """6f42e462-7cbf-4d95-880b-16eb55ed7a1a""", doc: """Create a new session for the Netlify CLI to communicate with the React UI via events.""") {
197+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
198+
createNetlifyCliTestEvent(
199+
input: {data: {payload: $payload}, sessionId: $sessionId}
200+
) {
201+
event {
202+
id
203+
createdAt
204+
sessionId
205+
}
206+
}
207+
}
208+
}
209+
210+
query CliSessionByIdQuery($nfToken: String!, $id: String!) @netlify(id: """6f9a0536-25f7-4b8f-ad1f-5a39edd923bb""", doc: """Get a Netlify CLI session by its id""") {
211+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
212+
netlifyCliSession(id: $id) {
4213
id
5-
readme
6-
license {
7-
url
214+
name
215+
netlifyUserId
216+
events {
217+
createdAt
218+
}
219+
createdAt
220+
lastEventAt
221+
metadata
222+
}
223+
}
224+
}
225+
226+
query Deprecated_FindLoggedInServicesQuery @netlify(id: """9ffe3872-4ae8-4f86-b5b7-ffcdfe7843fd""", doc: """(Deprecated) Find logged in services""") {
227+
me {
228+
serviceMetadata {
229+
loggedInServices {
230+
...LoggedInServicesFragment
231+
}
232+
}
233+
}
234+
}
235+
236+
mutation CreateEmptyPersonalTokenMutation($nfToken: String!, $siteId: String!) @netlify(id: """a64681f1-014c-4413-8a7d-b188c4dd5f55""", doc: """Create a new OneGraph personal token for a user's site""") {
237+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
238+
createPersonalTokenWithNetlifySiteAnchor(
239+
input: {name: "Netlify AuthManager Token", netlifySiteId: $siteId}
240+
) {
241+
accessToken {
242+
token
243+
name
244+
anchor
245+
netlifyId
246+
}
247+
}
248+
}
249+
}
250+
251+
query ServiceListQuery($logoStyle: OneGraphAppLogoStyleEnum = ROUNDED_RECTANGLE) @netlify(id: """a6969eb4-5e17-43fb-a325-11566f7d1db3""", doc: """Retrieve a list of _all_ supported services from OneGraph""") {
252+
oneGraph {
253+
services {
254+
friendlyServiceName
255+
logoUrl(style: $logoStyle)
256+
service
257+
slug
258+
supportsCustomRedirectUri
259+
supportsCustomServiceAuth
260+
supportsOauthLogin
261+
}
262+
}
263+
}
264+
265+
query AuthlifyTokenIdForPersonalToken($personalToken: String!) @netlify(id: """da5acd46-f2f1-4f24-aff9-1fe36d9c999b""", doc: null) {
266+
oneGraph {
267+
personalToken(accessToken: $personalToken) {
268+
netlifyId
269+
}
270+
}
271+
}
272+
273+
query PersistedQueryQuery($nfToken: String!, $appId: String!, $id: String!) @netlify(id: """dfbf037c-a603-46a9-8ca2-ac0069c05db2""", doc: """Retrieve a previously persisted operations doc""") {
274+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
275+
persistedQuery(appId: $appId, id: $id) {
276+
id
277+
query
278+
allowedOperationNames
279+
description
280+
freeVariables
281+
fixedVariables
282+
tags
283+
}
284+
}
285+
}
286+
287+
query CliSessionsByAppIdQuery($nfToken: String!, $appId: String!) @netlify(id: """e09d703b-468c-4c94-b098-f1ba09fdf692""", doc: """List all the CLI sessions belonging to a site""") {
288+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
289+
netlifyCliSessionsByAppId(appId: $appId, first: 10) {
290+
id
291+
name
292+
netlifyUserId
293+
events {
294+
createdAt
295+
}
296+
createdAt
297+
lastEventAt
298+
metadata
299+
}
300+
}
301+
}
302+
303+
query ListServicesQuery($nfToken: String!, $siteId: String!, $logoStyle: OneGraphAppLogoStyleEnum = ROUNDED_RECTANGLE) @netlify(id: """e2394c86-260c-4646-88df-7bc7370de666""", doc: """Fetch all available OneGraph services (GitHub, Spotify, etc.), as well as any custom service auths that may be installed for a site.""") {
304+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
305+
services(filter: {supportsOauthLogin: true}) {
306+
friendlyServiceName
307+
service
308+
slug
309+
logoUrl(style: $logoStyle)
310+
availableScopes {
311+
category
312+
scope
313+
display
314+
isDefault
315+
isRequired
316+
description
317+
title
318+
}
319+
}
320+
app(id: $siteId) {
321+
serviceAuths {
322+
...ServiceAuthFragment
323+
}
324+
}
325+
}
326+
}
327+
328+
mutation UpsertAppForSiteMutation($nfToken: String!, $siteId: String!) @netlify(id: """e3d3bb8b-2fb5-48d8-b051-db602722419f""", doc: """Ensure that an app resource exists on the OneGraph servers for a given site.""") {
329+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
330+
upsertAppForNetlifySite(input: {netlifySiteId: $siteId}) {
331+
org {
332+
id
333+
name
334+
}
335+
app {
336+
id
337+
name
338+
corsOrigins
339+
customCorsOrigins {
340+
friendlyServiceName
341+
displayName
342+
encodedValue
343+
}
344+
}
345+
}
346+
}
347+
}
348+
349+
mutation AddCORSOriginMutation($nfToken: String!, $input: OneGraphAddCORSOriginToAppInput!) @netlify(id: """e3d4bb8b-2fb5-48d8-b051-db6027224101""", doc: """Add additional allowed CORS origins for calls to a site's Graph.""") {
350+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
351+
addCORSOriginToApp(input: $input) {
352+
app {
353+
...AppCORSOriginFragment
354+
}
355+
}
356+
}
357+
}
358+
359+
mutation RemoveCORSOriginMutation($nfToken: String!, $input: OneGraphRemoveCORSOriginFromAppInput!) @netlify(id: """e3d4bb8b-2fb5-48d8-b051-db6027224112""", doc: """Remove the given CORS origins for calls to a site's Graph.""") {
360+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
361+
removeCORSOriginFromApp(input: $input) {
362+
app {
363+
...AppCORSOriginFragment
364+
}
365+
}
366+
}
367+
}
368+
369+
query CORSOriginsQuery($siteId: String!, $nfToken: String!) @netlify(id: """e3d4bb8b-2fb5-48d8-b051-db6027224190""", doc: """List the allowed CORS origins for calls to a site's Graph.""") {
370+
oneGraph(auths: {netlifyAuth: {oauthToken: $nfToken}}) {
371+
app(id: $siteId) {
372+
...AppCORSOriginFragment
373+
}
374+
}
375+
}
376+
377+
subscription TestSubscription($minutes: Int = 1) @netlify(id: """e3d4bb8b-2fb5-9898-b051-db6027224112""", doc: """A subscription with variables and a fragment to test code generation.""") {
378+
poll(
379+
schedule: {every: {minutes: $minutes}}
380+
onlyTriggerWhenPayloadChanged: true
381+
) {
382+
query {
383+
me {
384+
serviceMetadata {
385+
loggedInServices {
386+
...LoggedInServicesFragment
387+
}
388+
}
8389
}
9390
}
10391
}

‎tests/integration/assets/netlifyGraphSchema.graphql

+244-519
Large diffs are not rendered by default.

‎tests/integration/snapshots/220.command.graph.test.js.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Snapshot report for `tests/220.command.graph.test.js`
1+
# Snapshot report for `tests/integration/220.command.graph.test.js`
22

33
The actual snapshot is saved in `220.command.graph.test.js.snap`.
44

@@ -25,6 +25,9 @@ Generated by [AVA](https://avajs.dev).
2525
2626
COMMANDS␊
2727
$ graph:edit Launch the browser to edit your local graph functions from Netlify␊
28+
$ graph:handler Generate a handler for a Graph operation given its name. See \`graph:operations\` for a list of operations.␊
29+
$ graph:library Generate the Graph function library␊
30+
$ graph:operations List all of the locally available operations␊
2831
$ graph:pull Pull down your local Netlify Graph schema, and process pending Graph edit events␊
2932
`
3033

@@ -49,5 +52,8 @@ Generated by [AVA](https://avajs.dev).
4952
5053
COMMANDS␊
5154
$ graph:edit Launch the browser to edit your local graph functions from Netlify␊
55+
$ graph:handler Generate a handler for a Graph operation given its name. See \`graph:operations\` for a list of operations.␊
56+
$ graph:library Generate the Graph function library␊
57+
$ graph:operations List all of the locally available operations␊
5258
$ graph:pull Pull down your local Netlify Graph schema, and process pending Graph edit events␊
5359
`
Binary file not shown.

‎tests/integration/snapshots/530.graph-codegen.test.js.md

+188-2
Large diffs are not rendered by default.
Binary file not shown.

1 commit comments

Comments
 (1)

github-actions[bot] commented on Feb 8, 2022

@github-actions[bot]

📊 Benchmark results

Package size: 377 MB

Please sign in to comment.