netlify-cli 8.18.1 → 8.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -160,6 +160,9 @@ Manage netlify functions
160
160
  | Subcommand | description |
161
161
  |:--------------------------- |:-----|
162
162
  | [`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 |
163
166
  | [`graph:pull`](/docs/commands/graph.md#graphpull) | Pull down your local Netlify Graph schema, and process pending Graph edit events |
164
167
 
165
168
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
- "version": "8.18.1",
3
+ "version": "8.19.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "netlify-cli",
9
- "version": "8.18.1",
9
+ "version": "8.19.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
@@ -82,7 +82,7 @@
82
82
  "multiparty": "^4.2.1",
83
83
  "netlify": "^11.0.0",
84
84
  "netlify-headers-parser": "^6.0.1",
85
- "netlify-onegraph-internal": "0.0.18",
85
+ "netlify-onegraph-internal": "0.0.28",
86
86
  "netlify-redirect-parser": "^13.0.2",
87
87
  "netlify-redirector": "^0.2.1",
88
88
  "node-fetch": "^2.6.0",
@@ -15762,9 +15762,9 @@
15762
15762
  }
15763
15763
  },
15764
15764
  "node_modules/netlify-onegraph-internal": {
15765
- "version": "0.0.18",
15766
- "resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.0.18.tgz",
15767
- "integrity": "sha512-dUODB7zQDj03gwXQZQPlxFAfB1NBAtAt0A/CvWkfieG0g3MuRFAT/TOQGWtFesN1HkRZbW7dnC3JR9S9jB66WQ==",
15765
+ "version": "0.0.28",
15766
+ "resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.0.28.tgz",
15767
+ "integrity": "sha512-csKAJ43gcdOBk1UsdVAQcfsMobDPjxqqTEZzWZOhlxjQvOZQJHXtoqnbD0eLfYl7TqCQFz2+Y/bWILIrHzYTIQ==",
15768
15768
  "dependencies": {
15769
15769
  "graphql": "16.0.0",
15770
15770
  "node-fetch": "^2.6.0",
@@ -34723,9 +34723,9 @@
34723
34723
  }
34724
34724
  },
34725
34725
  "netlify-onegraph-internal": {
34726
- "version": "0.0.18",
34727
- "resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.0.18.tgz",
34728
- "integrity": "sha512-dUODB7zQDj03gwXQZQPlxFAfB1NBAtAt0A/CvWkfieG0g3MuRFAT/TOQGWtFesN1HkRZbW7dnC3JR9S9jB66WQ==",
34726
+ "version": "0.0.28",
34727
+ "resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.0.28.tgz",
34728
+ "integrity": "sha512-csKAJ43gcdOBk1UsdVAQcfsMobDPjxqqTEZzWZOhlxjQvOZQJHXtoqnbD0eLfYl7TqCQFz2+Y/bWILIrHzYTIQ==",
34729
34729
  "requires": {
34730
34730
  "graphql": "16.0.0",
34731
34731
  "node-fetch": "^2.6.0",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
3
  "description": "Netlify command line tool",
4
- "version": "8.18.1",
4
+ "version": "8.19.0",
5
5
  "author": "Netlify Inc.",
6
6
  "contributors": [
7
7
  "Mathias Biilmann <matt@netlify.com> (https://twitter.com/biilmann)",
@@ -43,6 +43,7 @@
43
43
  "url": "https://github.com/netlify/cli/issues"
44
44
  },
45
45
  "scripts": {
46
+ "snap": "ava --verbose -u",
46
47
  "prepare": "husky install node_modules/@netlify/eslint-config-node/.husky/",
47
48
  "start": "node ./bin/run",
48
49
  "test": "run-s format test:dev",
@@ -149,7 +150,7 @@
149
150
  "multiparty": "^4.2.1",
150
151
  "netlify": "^11.0.0",
151
152
  "netlify-headers-parser": "^6.0.1",
152
- "netlify-onegraph-internal": "0.0.18",
153
+ "netlify-onegraph-internal": "0.0.28",
153
154
  "netlify-redirect-parser": "^13.0.2",
154
155
  "netlify-redirector": "^0.2.1",
155
156
  "node-fetch": "^2.6.0",
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const gitRepoInfo = require('git-repo-info')
2
3
 
3
4
  const { OneGraphCliClient, generateSessionName, loadCLISession } = require('../../lib/one-graph/cli-client')
@@ -15,7 +16,7 @@ const { createCLISession, createPersistedQuery, ensureAppForSite, updateCLISessi
15
16
  /**
16
17
  * Creates the `netlify graph:edit` command
17
18
  * @param {import('commander').OptionValues} options
18
- * @param {import('../base-command').BaseCommand} program
19
+ * @param {import('../base-command').BaseCommand} command
19
20
  * @returns
20
21
  */
21
22
  const graphEdit = async (options, command) => {
@@ -46,7 +47,7 @@ const graphEdit = async (options, command) => {
46
47
  let oneGraphSessionId = loadCLISession(state)
47
48
  if (!oneGraphSessionId) {
48
49
  const sessionName = generateSessionName()
49
- const oneGraphSession = await createCLISession(netlifyToken, site.id, sessionName)
50
+ const oneGraphSession = await createCLISession(netlifyToken, site.id, sessionName, null)
50
51
  state.set('oneGraphSessionId', oneGraphSession.id)
51
52
  oneGraphSessionId = state.get('oneGraphSessionId')
52
53
  }
@@ -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 }
@@ -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 }
@@ -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 }
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /* eslint-disable eslint-comments/disable-enable-pair */
2
3
  /* eslint-disable fp/no-loops */
3
4
  const {
@@ -12,7 +13,7 @@ const { chalk, error, warn } = require('../../utils')
12
13
  /**
13
14
  * Creates the `netlify graph:pull` command
14
15
  * @param {import('commander').OptionValues} options
15
- * @param {import('../base-command').BaseCommand} program
16
+ * @param {import('../base-command').BaseCommand} command
16
17
  * @returns
17
18
  */
18
19
  const graphPull = async (options, command) => {
@@ -60,7 +61,7 @@ const graphPull = async (options, command) => {
60
61
  })
61
62
 
62
63
  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)}`)
64
65
  }
65
66
 
66
67
  if (next.events) {
@@ -1,5 +1,8 @@
1
1
  // @ts-check
2
2
  const { createGraphEditCommand } = require('./graph-edit')
3
+ const { createGraphHandlerCommand } = require('./graph-handler')
4
+ const { createGraphLibraryCommand } = require('./graph-library')
5
+ const { createGraphOperationCommand } = require('./graph-operations')
3
6
  const { createGraphPullCommand } = require('./graph-pull')
4
7
 
5
8
  /**
@@ -18,6 +21,9 @@ const graph = (options, command) => {
18
21
  */
19
22
  const createGraphCommand = (program) => {
20
23
  createGraphEditCommand(program)
24
+ createGraphHandlerCommand(program)
25
+ createGraphLibraryCommand(program)
26
+ createGraphOperationCommand(program)
21
27
  createGraphPullCommand(program)
22
28
 
23
29
  return program
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /* eslint-disable eslint-comments/disable-enable-pair */
2
3
  /* eslint-disable fp/no-loops */
3
4
  const crypto = require('crypto')
@@ -8,12 +9,13 @@ const gitRepoInfo = require('git-repo-info')
8
9
  const { GraphQL, InternalConsole, OneGraphClient } = require('netlify-onegraph-internal')
9
10
  const { NetlifyGraph } = require('netlify-onegraph-internal')
10
11
 
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')
12
14
  const { watchDebounced } = require('../functions/watcher')
13
15
 
14
16
  const {
15
17
  generateFunctionsFile,
16
- generateHandler,
18
+ generateHandlerByOperationId,
17
19
  readGraphQLOperationsSourceFile,
18
20
  writeGraphQLOperationsSourceFile,
19
21
  writeGraphQLSchemaFile,
@@ -30,9 +32,11 @@ const internalConsole = {
30
32
  debug: console.debug,
31
33
  }
32
34
 
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
+ */
33
38
  const witnessedIncomingDocumentHashes = []
34
39
 
35
- // Keep track of which document hashes we've received from the server so we can ignore events from the filesystem based on them
36
40
  InternalConsole.registerConsole(internalConsole)
37
41
 
38
42
  /**
@@ -40,12 +44,12 @@ InternalConsole.registerConsole(internalConsole)
40
44
  * @param {object} input
41
45
  * @param {string} input.appId The app to query against, typically the siteId
42
46
  * @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
44
48
  * @param {function} input.onClose A function to call when the polling loop is closed
45
49
  * @param {function} input.onError A function to call when an error occurs
46
50
  * @param {function} input.onEvents A function to call when CLI events are received and need to be processed
47
51
  * @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
49
53
  * @returns
50
54
  */
51
55
  const monitorCLISessionEvents = (input) => {
@@ -117,15 +121,16 @@ const monitorCLISessionEvents = (input) => {
117
121
  /**
118
122
  * Monitor the operations document for changes
119
123
  * @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>}
125
129
  */
126
130
  const monitorOperationFile = async ({ netlifyGraphConfig, onAdd, onChange, onUnlink }) => {
127
131
  const filePath = path.resolve(...netlifyGraphConfig.graphQLOperationsSourceFilename)
128
132
  const newWatcher = await watchDebounced([filePath], {
133
+ depth: 1,
129
134
  onAdd,
130
135
  onChange,
131
136
  onUnlink,
@@ -139,8 +144,8 @@ const monitorOperationFile = async ({ netlifyGraphConfig, onAdd, onChange, onUnl
139
144
  * @param {object} input
140
145
  * @param {string} input.siteId The id of the site to query against
141
146
  * @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
144
149
  * @returns {Promise<void>}
145
150
  */
146
151
  const refetchAndGenerateFromOneGraph = async (input) => {
@@ -174,8 +179,8 @@ const refetchAndGenerateFromOneGraph = async (input) => {
174
179
  /**
175
180
  * Regenerate the function library based on the current operations document on disk
176
181
  * @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
179
184
  * @returns
180
185
  */
181
186
  const regenerateFunctionsFileFromOperationsFile = (input) => {
@@ -214,15 +219,15 @@ const quickHash = (input) => {
214
219
  * @param {string} input.siteId The site id to query against
215
220
  * @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
216
221
  * @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
219
224
  * @returns
220
225
  */
221
226
  const updateGraphQLOperationsFileFromPersistedDoc = async (input) => {
222
227
  const { docId, netlifyGraphConfig, netlifyToken, schema, siteId } = input
223
228
  const persistedDoc = await OneGraphClient.fetchPersistedQuery(netlifyToken, siteId, docId)
224
229
  if (!persistedDoc) {
225
- warn('No persisted doc found for:', docId)
230
+ warn(`No persisted doc found for: ${docId}`)
226
231
  return
227
232
  }
228
233
 
@@ -249,7 +254,11 @@ const handleCliSessionEvent = async ({ event, netlifyGraphConfig, netlifyToken,
249
254
  await handleCliSessionEvent({ netlifyToken, event: payload, netlifyGraphConfig, schema, siteId })
250
255
  break
251
256
  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)
253
262
  break
254
263
  case 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent':
255
264
  await updateGraphQLOperationsFileFromPersistedDoc({
@@ -261,7 +270,13 @@ const handleCliSessionEvent = async ({ event, netlifyGraphConfig, netlifyToken,
261
270
  })
262
271
  break
263
272
  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
+ )
265
280
  break
266
281
  }
267
282
  }
@@ -281,13 +296,13 @@ const persistNewOperationsDocForSession = async ({ netlifyToken, oneGraphSession
281
296
  const result = await OneGraphClient.updateCLISessionMetadata(netlifyToken, siteId, oneGraphSessionId, newMetadata)
282
297
 
283
298
  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)}`)
285
300
  }
286
301
  }
287
302
 
288
303
  /**
289
304
  * Load the CLI session id from the local state
290
- * @param {state} state
305
+ * @param {StateConfig} state
291
306
  * @returns
292
307
  */
293
308
  const loadCLISession = (state) => state.get('oneGraphSessionId')
@@ -296,9 +311,9 @@ const loadCLISession = (state) => state.get('oneGraphSessionId')
296
311
  * Idemponentially save the CLI session id to the local state and start monitoring for CLI events, upstream schema changes, and local operation file changes
297
312
  * @param {object} input
298
313
  * @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
302
317
  */
303
318
  const startOneGraphCLISession = async (input) => {
304
319
  const { netlifyGraphConfig, netlifyToken, site, state } = input
@@ -387,6 +402,7 @@ const OneGraphCliClient = {
387
402
 
388
403
  module.exports = {
389
404
  OneGraphCliClient,
405
+ extractFunctionsFromOperationDoc,
390
406
  handleCliSessionEvent,
391
407
  generateSessionName,
392
408
  loadCLISession,
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const fs = require('fs')
2
3
  const path = require('path')
3
4
  const process = require('process')
@@ -17,6 +18,8 @@ const internalConsole = {
17
18
 
18
19
  InternalConsole.registerConsole(internalConsole)
19
20
 
21
+ const { extractFunctionsFromOperationDoc } = NetlifyGraph
22
+
20
23
  /**
21
24
  * Remove any relative path components from the given path
22
25
  * @param {string[]} items Filesystem path items to filter
@@ -27,10 +30,11 @@ const filterRelativePathItems = (items) => items.filter((part) => part !== '')
27
30
  /**
28
31
  * Return the default Netlify Graph configuration for a generic site
29
32
  * @param {object} context
33
+ * @param {object} context.baseConfig
30
34
  * @param {string[]} context.detectedFunctionsPath
31
35
  * @param {string[]} context.siteRoot
32
36
  */
33
- const makeDefaultNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath }) => {
37
+ const makeDefaultNetlifyGraphConfig = ({ baseConfig, detectedFunctionsPath }) => {
34
38
  const functionsPath = filterRelativePathItems([...detectedFunctionsPath])
35
39
  const webhookBasePath = '/.netlify/functions'
36
40
  const netlifyGraphPath = [...functionsPath, 'netlifyGraph']
@@ -57,10 +61,11 @@ const makeDefaultNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath }) =>
57
61
  /**
58
62
  * Return the default Netlify Graph configuration for a Nextjs site
59
63
  * @param {object} context
64
+ * @param {object} context.baseConfig
60
65
  * @param {string[]} context.detectedFunctionsPath
61
66
  * @param {string[]} context.siteRoot
62
67
  */
63
- const makeDefaultNextJsNetlifGraphConfig = ({ baseConfig, siteRoot }) => {
68
+ const makeDefaultNextJsNetlifyGraphConfig = ({ baseConfig, siteRoot }) => {
64
69
  const functionsPath = filterRelativePathItems([...siteRoot, 'pages', 'api'])
65
70
  const webhookBasePath = '/api'
66
71
  const netlifyGraphPath = filterRelativePathItems([...siteRoot, 'lib', 'netlifyGraph'])
@@ -87,10 +92,11 @@ const makeDefaultNextJsNetlifGraphConfig = ({ baseConfig, siteRoot }) => {
87
92
  /**
88
93
  * Return the default Netlify Graph configuration for a Remix site
89
94
  * @param {object} context
95
+ * @param {object} context.baseConfig
90
96
  * @param {string[]} context.detectedFunctionsPath
91
97
  * @param {string[]} context.siteRoot
92
98
  */
93
- const makeDefaultRemixNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath, siteRoot }) => {
99
+ const makeDefaultRemixNetlifyGraphConfig = ({ baseConfig, detectedFunctionsPath, siteRoot }) => {
94
100
  const functionsPath = filterRelativePathItems([...detectedFunctionsPath])
95
101
  const webhookBasePath = '/webhooks'
96
102
  const netlifyGraphPath = filterRelativePathItems([
@@ -118,22 +124,25 @@ const makeDefaultRemixNetlifGraphConfig = ({ baseConfig, detectedFunctionsPath,
118
124
  }
119
125
 
120
126
  const defaultFrameworkLookup = {
121
- 'Next.js': makeDefaultNextJsNetlifGraphConfig,
122
- Remix: makeDefaultRemixNetlifGraphConfig,
123
- default: makeDefaultNetlifGraphConfig,
127
+ 'Next.js': makeDefaultNextJsNetlifyGraphConfig,
128
+ Remix: makeDefaultRemixNetlifyGraphConfig,
129
+ default: makeDefaultNetlifyGraphConfig,
124
130
  }
125
131
 
126
132
  /**
127
133
  * 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
130
139
  */
131
140
  const getNetlifyGraphConfig = async ({ command, options, settings }) => {
132
141
  const { config, site } = command.netlify
133
142
  config.dev = { ...config.dev }
134
143
  config.build = { ...config.build }
135
144
  const userSpecifiedConfig = (config && config.graph) || {}
136
- /** @type {import('./types').DevConfig} */
145
+ /** @type {import('../../commands/dev/types').DevConfig} */
137
146
  const devConfig = {
138
147
  framework: '#auto',
139
148
  ...(config.functionsDirectory && { functions: config.functionsDirectory }),
@@ -148,7 +157,13 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
148
157
  settings = await detectServerSettings(devConfig, options, site.root)
149
158
  } catch (detectServerSettingsError) {
150
159
  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
+ )
152
167
  }
153
168
  }
154
169
 
@@ -167,9 +182,11 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
167
182
  const baseConfig = { ...NetlifyGraph.defaultNetlifyGraphConfig, ...userSpecifiedConfig }
168
183
  const defaultFrameworkConfig = makeDefaultFrameworkConfig({ baseConfig, detectedFunctionsPath, siteRoot })
169
184
 
185
+ const userSpecifiedFunctionPath =
186
+ userSpecifiedConfig.functionsPath && userSpecifiedConfig.functionsPath.split(path.sep)
187
+
170
188
  const functionsPath =
171
- (userSpecifiedConfig.functionsPath && userSpecifiedConfig.functionsPath.split(path.sep)) ||
172
- defaultFrameworkConfig.functionsPath
189
+ (userSpecifiedFunctionPath && [...siteRoot, ...userSpecifiedFunctionPath]) || defaultFrameworkConfig.functionsPath
173
190
  const netlifyGraphPath =
174
191
  (userSpecifiedConfig.netlifyGraphPath && userSpecifiedConfig.netlifyGraphPath.split(path.sep)) ||
175
192
  defaultFrameworkConfig.netlifyGraphPath
@@ -225,7 +242,7 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
225
242
 
226
243
  /**
227
244
  * Given a NetlifyGraphConfig, ensure that the netlifyGraphPath exists
228
- * @param {NetlifyGraphConfig} netlifyGraphConfig
245
+ * @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
229
246
  */
230
247
  const ensureNetlifyGraphPath = (netlifyGraphConfig) => {
231
248
  const fullPath = path.resolve(...netlifyGraphConfig.netlifyGraphPath)
@@ -234,7 +251,7 @@ const ensureNetlifyGraphPath = (netlifyGraphConfig) => {
234
251
 
235
252
  /**
236
253
  * Given a NetlifyGraphConfig, ensure that the functionsPath exists
237
- * @param {NetlifyGraphConfig} netlifyGraphConfig
254
+ * @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
238
255
  */
239
256
  const ensureFunctionsPath = (netlifyGraphConfig) => {
240
257
  const fullPath = path.resolve(...netlifyGraphConfig.functionsPath)
@@ -248,9 +265,8 @@ const runPrettier = async (filePath) => {
248
265
  return
249
266
  }
250
267
 
251
- const command = `prettier --write ${filePath}`
252
268
  try {
253
- const commandProcess = execa.command(command, {
269
+ const commandProcess = execa('prettier', ['--write', filePath], {
254
270
  preferLocal: true,
255
271
  // windowsHide needs to be false for child process to terminate properly on Windows
256
272
  windowsHide: false,
@@ -269,11 +285,11 @@ const runPrettier = async (filePath) => {
269
285
  /**
270
286
  * Generate a library file with type definitions for a given NetlifyGraphConfig, operationsDoc, and schema, writing them to the filesystem
271
287
  * @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
274
290
  * @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
277
293
  * @returns {void} Void, effectfully writes the generated library to the filesystem
278
294
  */
279
295
  const generateFunctionsFile = ({ fragments, functions, netlifyGraphConfig, operationsDoc, schema }) => {
@@ -298,7 +314,7 @@ const generateFunctionsFile = ({ fragments, functions, netlifyGraphConfig, opera
298
314
 
299
315
  /**
300
316
  * 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
302
318
  * @returns {string} GraphQL operations doc
303
319
  */
304
320
  const readGraphQLOperationsSourceFile = (netlifyGraphConfig) => {
@@ -317,11 +333,11 @@ const readGraphQLOperationsSourceFile = (netlifyGraphConfig) => {
317
333
 
318
334
  /**
319
335
  * 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
322
338
  */
323
- const writeGraphQLOperationsSourceFile = (netlifyGraphConfig, operationDocString) => {
324
- const graphqlSource = operationDocString
339
+ const writeGraphQLOperationsSourceFile = (netlifyGraphConfig, operationsDocString) => {
340
+ const graphqlSource = operationsDocString
325
341
 
326
342
  ensureNetlifyGraphPath(netlifyGraphConfig)
327
343
  fs.writeFileSync(path.resolve(...netlifyGraphConfig.graphQLOperationsSourceFilename), graphqlSource, 'utf8')
@@ -329,8 +345,8 @@ const writeGraphQLOperationsSourceFile = (netlifyGraphConfig, operationDocString
329
345
 
330
346
  /**
331
347
  * 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
334
350
  */
335
351
  const writeGraphQLSchemaFile = (netlifyGraphConfig, schema) => {
336
352
  const graphqlSource = printSchema(schema)
@@ -341,7 +357,7 @@ const writeGraphQLSchemaFile = (netlifyGraphConfig, schema) => {
341
357
 
342
358
  /**
343
359
  * Using the given NetlifyGraphConfig, read the GraphQL schema file and return it _unparsed_
344
- * @param {NetlifyGraphConfig} netlifyGraphConfig
360
+ * @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
345
361
  * @returns {string} GraphQL schema
346
362
  */
347
363
  const readGraphQLSchemaFile = (netlifyGraphConfig) => {
@@ -351,13 +367,13 @@ const readGraphQLSchemaFile = (netlifyGraphConfig) => {
351
367
 
352
368
  /**
353
369
  * 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
356
372
  * @param {string} operationId The operationId to use when generating the handler
357
373
  * @param {object} handlerOptions The options to use when generating the handler
358
374
  * @returns
359
375
  */
360
- const generateHandler = (netlifyGraphConfig, schema, operationId, handlerOptions) => {
376
+ const generateHandlerByOperationId = (netlifyGraphConfig, schema, operationId, handlerOptions) => {
361
377
  let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
362
378
  if (currentOperationsDoc.trim().length === 0) {
363
379
  currentOperationsDoc = NetlifyGraph.defaultExampleOperationsDoc
@@ -415,6 +431,35 @@ const generateHandler = (netlifyGraphConfig, schema, operationId, handlerOptions
415
431
  })
416
432
  }
417
433
 
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
+
418
463
  // Export the minimal set of functions that are required for Netlify Graph
419
464
  const { buildSchema, parse } = GraphQL
420
465
 
@@ -455,13 +500,15 @@ module.exports = {
455
500
  generateFunctionsSource: NetlifyGraph.generateFunctionsSource,
456
501
  generateFunctionsFile,
457
502
  generateHandlerSource: NetlifyGraph.generateHandlerSource,
458
- generateHandler,
503
+ generateHandlerByOperationId,
504
+ generateHandlerByOperationName,
459
505
  getGraphEditUrlBySiteId,
460
506
  getGraphEditUrlBySiteName,
461
507
  getNetlifyGraphConfig,
462
508
  parse,
463
509
  readGraphQLOperationsSourceFile,
464
510
  readGraphQLSchemaFile,
511
+ runPrettier,
465
512
  writeGraphQLOperationsSourceFile,
466
513
  writeGraphQLSchemaFile,
467
514
  }
@@ -47,8 +47,6 @@ const USER_AGENT = `${name}/${version} ${platform}-${arch} node-${process.versio
47
47
  /** A list of base command flags that needs to be sorted down on documentation and on help pages */
48
48
  const BASE_FLAGS = new Set(['--debug', '--httpProxy', '--httpProxyCertificateFilename'])
49
49
 
50
- const { NETLIFY_AUTH_TOKEN } = process.env
51
-
52
50
  // eslint-disable-next-line no-magic-numbers
53
51
  const NETLIFY_CYAN = chalk.rgb(40, 180, 170)
54
52
 
@@ -121,6 +119,7 @@ const getToken = async (tokenFromOptions) => {
121
119
  return [tokenFromOptions, 'flag']
122
120
  }
123
121
  // 2. then Check ENV var
122
+ const { NETLIFY_AUTH_TOKEN } = process.env
124
123
  if (NETLIFY_AUTH_TOKEN && NETLIFY_AUTH_TOKEN !== 'null') {
125
124
  return [NETLIFY_AUTH_TOKEN, 'env']
126
125
  }