netlify-cli 8.17.4 → 8.19.1
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 +4 -0
- package/npm-shrinkwrap.json +289 -45
- package/package.json +131 -9
- package/src/commands/graph/graph-edit.js +3 -2
- package/src/commands/graph/graph-handler.js +53 -0
- package/src/commands/graph/graph-library.js +61 -0
- package/src/commands/graph/graph-operations.js +113 -0
- package/src/commands/graph/graph-pull.js +3 -2
- package/src/commands/graph/graph.js +6 -0
- package/src/commands/sites/sites-create-template.js +212 -0
- package/src/commands/sites/sites-create.js +48 -41
- package/src/commands/sites/sites.js +2 -0
- package/src/lib/one-graph/cli-client.js +40 -24
- package/src/lib/one-graph/cli-netlify-graph.js +79 -32
- package/src/utils/command-helpers.js +1 -2
- package/src/utils/sites/utils.js +30 -0
|
@@ -13,6 +13,49 @@ const { link } = require('../link')
|
|
|
13
13
|
|
|
14
14
|
const SITE_NAME_SUGGESTION_SUFFIX_LENGTH = 5
|
|
15
15
|
|
|
16
|
+
const getSiteNameInput = async (name, user, api) => {
|
|
17
|
+
let siteSuggestion
|
|
18
|
+
if (!user) user = await api.getCurrentUser()
|
|
19
|
+
|
|
20
|
+
if (!name) {
|
|
21
|
+
let { slug } = user
|
|
22
|
+
let suffix = ''
|
|
23
|
+
|
|
24
|
+
// If the user doesn't have a slug, we'll compute one. Because `full_name` is not guaranteed to be unique, we
|
|
25
|
+
// append a short randomly-generated ID to reduce the likelihood of a conflict.
|
|
26
|
+
if (!slug) {
|
|
27
|
+
slug = slugify(user.full_name || user.email)
|
|
28
|
+
suffix = `-${uuidv4().slice(0, SITE_NAME_SUGGESTION_SUFFIX_LENGTH)}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const suggestions = [
|
|
32
|
+
`super-cool-site-by-${slug}${suffix}`,
|
|
33
|
+
`the-awesome-${slug}-site${suffix}`,
|
|
34
|
+
`${slug}-makes-great-sites${suffix}`,
|
|
35
|
+
`netlify-thinks-${slug}-is-great${suffix}`,
|
|
36
|
+
`the-great-${slug}-site${suffix}`,
|
|
37
|
+
`isnt-${slug}-awesome${suffix}`,
|
|
38
|
+
]
|
|
39
|
+
siteSuggestion = sample(suggestions)
|
|
40
|
+
|
|
41
|
+
console.log(
|
|
42
|
+
`Choose a unique site name (e.g. ${siteSuggestion}.netlify.app) or leave it blank for a random name. You can update the site name later.`,
|
|
43
|
+
)
|
|
44
|
+
const { name: nameInput } = await inquirer.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: 'input',
|
|
47
|
+
name: 'name',
|
|
48
|
+
message: 'Site name (optional):',
|
|
49
|
+
filter: (val) => (val === '' ? undefined : val),
|
|
50
|
+
validate: (input) => /^[a-zA-Z\d-]+$/.test(input) || 'Only alphanumeric characters and hyphens are allowed',
|
|
51
|
+
},
|
|
52
|
+
])
|
|
53
|
+
name = nameInput
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { name, siteSuggestion }
|
|
57
|
+
}
|
|
58
|
+
|
|
16
59
|
/**
|
|
17
60
|
* The sites:create command
|
|
18
61
|
* @param {import('commander').OptionValues} options
|
|
@@ -47,47 +90,11 @@ const sitesCreate = async (options, command) => {
|
|
|
47
90
|
|
|
48
91
|
// Allow the user to reenter site name if selected one isn't available
|
|
49
92
|
const inputSiteName = async (name) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!name) {
|
|
53
|
-
let { slug } = user
|
|
54
|
-
let suffix = ''
|
|
55
|
-
|
|
56
|
-
// If the user doesn't have a slug, we'll compute one. Because `full_name` is not guaranteed to be unique, we
|
|
57
|
-
// append a short randomly-generated ID to reduce the likelihood of a conflict.
|
|
58
|
-
if (!slug) {
|
|
59
|
-
slug = slugify(user.full_name || user.email)
|
|
60
|
-
suffix = `-${uuidv4().slice(0, SITE_NAME_SUGGESTION_SUFFIX_LENGTH)}`
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const suggestions = [
|
|
64
|
-
`super-cool-site-by-${slug}${suffix}`,
|
|
65
|
-
`the-awesome-${slug}-site${suffix}`,
|
|
66
|
-
`${slug}-makes-great-sites${suffix}`,
|
|
67
|
-
`netlify-thinks-${slug}-is-great${suffix}`,
|
|
68
|
-
`the-great-${slug}-site${suffix}`,
|
|
69
|
-
`isnt-${slug}-awesome${suffix}`,
|
|
70
|
-
]
|
|
71
|
-
const siteSuggestion = sample(suggestions)
|
|
72
|
-
|
|
73
|
-
console.log(
|
|
74
|
-
`Choose a unique site name (e.g. ${siteSuggestion}.netlify.app) or leave it blank for a random name. You can update the site name later.`,
|
|
75
|
-
)
|
|
76
|
-
const { name: nameInput } = await inquirer.prompt([
|
|
77
|
-
{
|
|
78
|
-
type: 'input',
|
|
79
|
-
name: 'name',
|
|
80
|
-
message: 'Site name (optional):',
|
|
81
|
-
filter: (val) => (val === '' ? undefined : val),
|
|
82
|
-
validate: (input) => /^[a-zA-Z\d-]+$/.test(input) || 'Only alphanumeric characters and hyphens are allowed',
|
|
83
|
-
},
|
|
84
|
-
])
|
|
85
|
-
name = nameInput
|
|
86
|
-
}
|
|
93
|
+
const { name: siteName } = await getSiteNameInput(name, user, api)
|
|
87
94
|
|
|
88
95
|
const body = {}
|
|
89
|
-
if (typeof
|
|
90
|
-
body.name =
|
|
96
|
+
if (typeof siteName === 'string') {
|
|
97
|
+
body.name = siteName.trim()
|
|
91
98
|
}
|
|
92
99
|
try {
|
|
93
100
|
site = await api.createSiteInTeam({
|
|
@@ -96,7 +103,7 @@ const sitesCreate = async (options, command) => {
|
|
|
96
103
|
})
|
|
97
104
|
} catch (error_) {
|
|
98
105
|
if (error_.status === 422) {
|
|
99
|
-
warn(`${
|
|
106
|
+
warn(`${siteName}.netlify.app already exists. Please try a different slug.`)
|
|
100
107
|
await inputSiteName()
|
|
101
108
|
} else {
|
|
102
109
|
error(`createSiteInTeam error: ${error_.status}: ${error_.message}`)
|
|
@@ -191,4 +198,4 @@ Create a blank site that isn't associated with any git remote. Will link the sit
|
|
|
191
198
|
)
|
|
192
199
|
.action(sitesCreate)
|
|
193
200
|
|
|
194
|
-
module.exports = { createSitesCreateCommand, sitesCreate }
|
|
201
|
+
module.exports = { createSitesCreateCommand, sitesCreate, getSiteNameInput }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
const { createSitesCreateCommand } = require('./sites-create')
|
|
3
|
+
const { createSitesFromTemplateCommand } = require('./sites-create-template')
|
|
3
4
|
const { createSitesDeleteCommand } = require('./sites-delete')
|
|
4
5
|
const { createSitesListCommand } = require('./sites-list')
|
|
5
6
|
|
|
@@ -19,6 +20,7 @@ const sites = (options, command) => {
|
|
|
19
20
|
*/
|
|
20
21
|
const createSitesCommand = (program) => {
|
|
21
22
|
createSitesCreateCommand(program)
|
|
23
|
+
createSitesFromTemplateCommand(program)
|
|
22
24
|
createSitesListCommand(program)
|
|
23
25
|
createSitesDeleteCommand(program)
|
|
24
26
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
122
|
-
* @param {
|
|
123
|
-
* @param {
|
|
124
|
-
* @returns {Promise<
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 {
|
|
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 {
|
|
301
|
-
* @param {
|
|
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
|
|
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
|
|
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
|
|
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':
|
|
122
|
-
Remix:
|
|
123
|
-
default:
|
|
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 {
|
|
129
|
-
* @
|
|
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('
|
|
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(
|
|
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
|
-
(
|
|
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
|
|
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.
|
|
276
|
-
* @param {NetlifyGraph.
|
|
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}
|
|
336
|
+
* @param {NetlifyGraph.NetlifyGraphConfig} netlifyGraphConfig
|
|
337
|
+
* @param {string} operationsDocString The GraphQL operations doc to write
|
|
322
338
|
*/
|
|
323
|
-
const writeGraphQLOperationsSourceFile = (netlifyGraphConfig,
|
|
324
|
-
const graphqlSource =
|
|
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
|
|
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
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fetch = require('node-fetch')
|
|
2
|
+
|
|
3
|
+
const getTemplatesFromGitHub = async (token) => {
|
|
4
|
+
const templates = await fetch(`https://api.github.com/orgs/netlify-templates/repos`, {
|
|
5
|
+
method: 'GET',
|
|
6
|
+
headers: {
|
|
7
|
+
Authorization: `token ${token}`,
|
|
8
|
+
},
|
|
9
|
+
})
|
|
10
|
+
const allTemplates = await templates.json()
|
|
11
|
+
|
|
12
|
+
return allTemplates
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const createRepo = async (templateUrl, ghToken, siteName) => {
|
|
16
|
+
const resp = await fetch(`https://api.github.com/repos/${templateUrl.templateName}/generate`, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `token ${ghToken}`,
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify({
|
|
22
|
+
name: siteName,
|
|
23
|
+
}),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const data = await resp.json()
|
|
27
|
+
return data
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { getTemplatesFromGitHub, createRepo }
|