netlify-cli 8.12.0 → 8.14.0-scheduled-functions
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 +12 -1
- package/npm-shrinkwrap.json +1669 -2534
- package/package.json +9 -5
- package/src/commands/dev/dev.js +31 -6
- package/src/commands/functions/functions-invoke.js +17 -2
- package/src/commands/graph/graph-edit.js +91 -0
- package/src/commands/graph/graph-pull.js +95 -0
- package/src/commands/graph/graph.js +30 -0
- package/src/commands/graph/index.js +5 -0
- package/src/commands/main.js +2 -0
- package/src/functions-templates/javascript/hasura-event-triggered/package.json +1 -1
- package/src/functions-templates/javascript/stripe-charge/package-lock.json +7 -7
- package/src/functions-templates/javascript/stripe-subscription/package-lock.json +7 -7
- package/src/functions-templates/javascript/token-hider/package-lock.json +16 -16
- package/src/functions-templates/javascript/token-hider/package.json +1 -1
- package/src/functions-templates/typescript/hello-world/package-lock.json +13 -13
- package/src/lib/functions/netlify-function.js +9 -1
- package/src/lib/functions/runtimes/js/builders/zisi.js +25 -6
- package/src/lib/functions/runtimes/js/index.js +2 -1
- package/src/lib/functions/scheduled.js +78 -0
- package/src/lib/functions/server.js +10 -0
- package/src/lib/one-graph/cli-client.js +278 -0
- package/src/lib/one-graph/cli-netlify-graph.js +278 -0
- package/src/utils/functions/constants.js +5 -0
- package/src/utils/functions/get-functions.js +5 -3
- package/src/utils/functions/index.js +2 -0
|
@@ -32,6 +32,7 @@ class NetlifyFunction {
|
|
|
32
32
|
// Determines whether this is a background function based on the function
|
|
33
33
|
// name.
|
|
34
34
|
this.isBackground = name.endsWith(BACKGROUND_SUFFIX)
|
|
35
|
+
this.schedule = null
|
|
35
36
|
|
|
36
37
|
// List of the function's source files. This starts out as an empty set
|
|
37
38
|
// and will get populated on every build.
|
|
@@ -44,6 +45,12 @@ class NetlifyFunction {
|
|
|
44
45
|
return /^[A-Za-z0-9_-]+$/.test(this.name)
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
async isScheduled() {
|
|
49
|
+
await this.buildQueue
|
|
50
|
+
|
|
51
|
+
return Boolean(this.schedule)
|
|
52
|
+
}
|
|
53
|
+
|
|
47
54
|
// The `build` method transforms source files into invocable functions. Its
|
|
48
55
|
// return value is an object with:
|
|
49
56
|
//
|
|
@@ -61,12 +68,13 @@ class NetlifyFunction {
|
|
|
61
68
|
this.buildQueue = buildFunction({ cache })
|
|
62
69
|
|
|
63
70
|
try {
|
|
64
|
-
const { srcFiles, ...buildData } = await this.buildQueue
|
|
71
|
+
const { schedule, srcFiles, ...buildData } = await this.buildQueue
|
|
65
72
|
const srcFilesSet = new Set(srcFiles)
|
|
66
73
|
const srcFilesDiff = this.getSrcFilesDiff(srcFilesSet)
|
|
67
74
|
|
|
68
75
|
this.buildData = buildData
|
|
69
76
|
this.srcFiles = srcFilesSet
|
|
77
|
+
this.schedule = schedule
|
|
70
78
|
|
|
71
79
|
return { srcFilesDiff }
|
|
72
80
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { mkdir, writeFile } = require('fs').promises
|
|
2
2
|
const path = require('path')
|
|
3
3
|
|
|
4
|
-
const { zipFunction } = require('@netlify/zip-it-and-ship-it')
|
|
4
|
+
const { listFunction, zipFunction } = require('@netlify/zip-it-and-ship-it')
|
|
5
5
|
const decache = require('decache')
|
|
6
6
|
const readPkgUp = require('read-pkg-up')
|
|
7
7
|
const sourceMapSupport = require('source-map-support')
|
|
@@ -35,7 +35,11 @@ const buildFunction = async ({ cache, config, directory, func, hasTypeModule, pr
|
|
|
35
35
|
// root of the functions directory (e.g. `functions/my-func.js`). In
|
|
36
36
|
// this case, we use `mainFile` as the function path of `zipFunction`.
|
|
37
37
|
const entryPath = functionDirectory === directory ? func.mainFile : functionDirectory
|
|
38
|
-
const {
|
|
38
|
+
const {
|
|
39
|
+
inputs,
|
|
40
|
+
path: functionPath,
|
|
41
|
+
schedule,
|
|
42
|
+
} = await memoizedBuild({
|
|
39
43
|
cache,
|
|
40
44
|
cacheKey: `zisi-${entryPath}`,
|
|
41
45
|
command: () => zipFunction(entryPath, targetDirectory, zipOptions),
|
|
@@ -56,7 +60,19 @@ const buildFunction = async ({ cache, config, directory, func, hasTypeModule, pr
|
|
|
56
60
|
|
|
57
61
|
clearFunctionsCache(targetDirectory)
|
|
58
62
|
|
|
59
|
-
return { buildPath, srcFiles }
|
|
63
|
+
return { buildPath, srcFiles, schedule }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} mainFile
|
|
68
|
+
*/
|
|
69
|
+
const parseForSchedule = async ({ config, mainFile, projectRoot }) => {
|
|
70
|
+
const listedFunction = await listFunction(mainFile, {
|
|
71
|
+
config: netlifyConfigToZisiConfig({ config, projectRoot }),
|
|
72
|
+
parseISC: true,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return listedFunction && listedFunction.schedule
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
// Clears the cache for any files inside the directory from which functions are
|
|
@@ -79,10 +95,11 @@ const getTargetDirectory = async ({ errorExit }) => {
|
|
|
79
95
|
return targetDirectory
|
|
80
96
|
}
|
|
81
97
|
|
|
98
|
+
const netlifyConfigToZisiConfig = ({ config, projectRoot }) =>
|
|
99
|
+
addFunctionsConfigDefaults(normalizeFunctionsConfig({ functionsConfig: config.functions, projectRoot }))
|
|
100
|
+
|
|
82
101
|
module.exports = async ({ config, directory, errorExit, func, projectRoot }) => {
|
|
83
|
-
const functionsConfig =
|
|
84
|
-
normalizeFunctionsConfig({ functionsConfig: config.functions, projectRoot }),
|
|
85
|
-
)
|
|
102
|
+
const functionsConfig = netlifyConfigToZisiConfig({ config, projectRoot })
|
|
86
103
|
|
|
87
104
|
const packageJson = await readPkgUp(func.mainFile)
|
|
88
105
|
const hasTypeModule = packageJson && packageJson.packageJson.type === 'module'
|
|
@@ -115,3 +132,5 @@ module.exports = async ({ config, directory, errorExit, func, projectRoot }) =>
|
|
|
115
132
|
target: targetDirectory,
|
|
116
133
|
}
|
|
117
134
|
}
|
|
135
|
+
|
|
136
|
+
module.exports.parseForSchedule = parseForSchedule
|
|
@@ -46,8 +46,9 @@ const getBuildFunction = async ({ config, directory, errorExit, func, projectRoo
|
|
|
46
46
|
// main file otherwise.
|
|
47
47
|
const functionDirectory = dirname(func.mainFile)
|
|
48
48
|
const srcFiles = functionDirectory === directory ? [func.mainFile] : [functionDirectory]
|
|
49
|
+
const schedule = await detectZisiBuilder.parseForSchedule({ mainFile: func.mainFile, config, projectRoot })
|
|
49
50
|
|
|
50
|
-
return () => ({ srcFiles })
|
|
51
|
+
return () => ({ schedule, srcFiles })
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
const invokeFunction = async ({ context, event, func, timeout }) => {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const ansi2html = require('ansi2html')
|
|
2
|
+
|
|
3
|
+
const { CLOCKWORK_USERAGENT } = require('../../utils')
|
|
4
|
+
|
|
5
|
+
const { formatLambdaError } = require('./utils')
|
|
6
|
+
|
|
7
|
+
const handleScheduledFunction = ({ error, request, response, result }) => {
|
|
8
|
+
const acceptsHtml = request.headers.accept && request.headers.accept.includes('text/html')
|
|
9
|
+
const paragraph = (text) => {
|
|
10
|
+
text = text.trim()
|
|
11
|
+
|
|
12
|
+
if (acceptsHtml) {
|
|
13
|
+
return ansi2html(`<p>${text}</p>`)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
text = text
|
|
17
|
+
.replaceAll('<pre><code>', '```\n')
|
|
18
|
+
.replaceAll('</code></pre>', '\n```')
|
|
19
|
+
.replaceAll(`<code>`, '`')
|
|
20
|
+
.replaceAll(`</code>`, '`')
|
|
21
|
+
|
|
22
|
+
return `${text}\n\n`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const isSimulatedRequest = request.headers['user-agent'] === CLOCKWORK_USERAGENT
|
|
26
|
+
|
|
27
|
+
let message = ''
|
|
28
|
+
|
|
29
|
+
if (!isSimulatedRequest) {
|
|
30
|
+
message += paragraph(`
|
|
31
|
+
You performed an HTTP request to <code>${request.path}</code>, which is a scheduled function.
|
|
32
|
+
You can do this to test your functions locally, but it won't work in production.
|
|
33
|
+
`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (error) {
|
|
37
|
+
message += paragraph(`
|
|
38
|
+
There was an error during execution of your scheduled function:
|
|
39
|
+
|
|
40
|
+
<pre><code>${formatLambdaError(error)}</code></pre>`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (result) {
|
|
44
|
+
const { statusCode } = result
|
|
45
|
+
if (statusCode >= 500) {
|
|
46
|
+
message += paragraph(`
|
|
47
|
+
Your function returned a status code of <code>${statusCode}</code>.
|
|
48
|
+
At the moment, Netlify does nothing about that. In the future, there might be a retry mechanism based on this.
|
|
49
|
+
`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const allowedKeys = new Set(['statusCode'])
|
|
53
|
+
const returnedKeys = Object.keys(result)
|
|
54
|
+
const ignoredKeys = returnedKeys.filter((key) => !allowedKeys.has(key))
|
|
55
|
+
|
|
56
|
+
if (ignoredKeys.length !== 0) {
|
|
57
|
+
message += paragraph(
|
|
58
|
+
`Your function returned ${ignoredKeys
|
|
59
|
+
.map((key) => `<code>${key}</code>`)
|
|
60
|
+
.join(', ')}. Is this an accident? It won't be interpreted by Netlify.`,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
response.status(error ? 500 : 200)
|
|
66
|
+
if (acceptsHtml) {
|
|
67
|
+
response.set('Content-Type', 'text/html')
|
|
68
|
+
response.send(
|
|
69
|
+
`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">\n
|
|
70
|
+
${message}`,
|
|
71
|
+
)
|
|
72
|
+
} else {
|
|
73
|
+
response.set('Content-Type', 'text/plain')
|
|
74
|
+
response.send(message)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { handleScheduledFunction }
|
|
@@ -6,6 +6,7 @@ const { NETLIFYDEVERR, NETLIFYDEVLOG, error: errorExit, getInternalFunctionsDir,
|
|
|
6
6
|
const { handleBackgroundFunction, handleBackgroundFunctionResult } = require('./background')
|
|
7
7
|
const { createFormSubmissionHandler } = require('./form-submissions-handler')
|
|
8
8
|
const { FunctionsRegistry } = require('./registry')
|
|
9
|
+
const { handleScheduledFunction } = require('./scheduled')
|
|
9
10
|
const { handleSynchronousFunction } = require('./synchronous')
|
|
10
11
|
const { shouldBase64Encode } = require('./utils')
|
|
11
12
|
|
|
@@ -105,6 +106,15 @@ const createHandler = function ({ functionsRegistry }) {
|
|
|
105
106
|
const { error } = await func.invoke(event, clientContext)
|
|
106
107
|
|
|
107
108
|
handleBackgroundFunctionResult(functionName, error)
|
|
109
|
+
} else if (await func.isScheduled()) {
|
|
110
|
+
const { error, result } = await func.invoke(event, clientContext)
|
|
111
|
+
|
|
112
|
+
handleScheduledFunction({
|
|
113
|
+
error,
|
|
114
|
+
result,
|
|
115
|
+
request,
|
|
116
|
+
response,
|
|
117
|
+
})
|
|
108
118
|
} else {
|
|
109
119
|
const { error, result } = await func.invoke(event, clientContext)
|
|
110
120
|
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/* eslint-disable eslint-comments/disable-enable-pair */
|
|
2
|
+
/* eslint-disable fp/no-loops */
|
|
3
|
+
const os = require('os')
|
|
4
|
+
|
|
5
|
+
const { GraphQL, InternalConsole, OneGraphClient } = require('netlify-onegraph-internal')
|
|
6
|
+
const { NetlifyGraph } = require('netlify-onegraph-internal')
|
|
7
|
+
|
|
8
|
+
const { chalk, error, log, warn } = require('../../utils')
|
|
9
|
+
|
|
10
|
+
const { createCLISession, createPersistedQuery, ensureAppForSite, updateCLISessionMetadata } = OneGraphClient
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
generateFunctionsFile,
|
|
14
|
+
generateHandler,
|
|
15
|
+
readGraphQLOperationsSourceFile,
|
|
16
|
+
writeGraphQLOperationsSourceFile,
|
|
17
|
+
writeGraphQLSchemaFile,
|
|
18
|
+
} = require('./cli-netlify-graph')
|
|
19
|
+
|
|
20
|
+
const { parse } = GraphQL
|
|
21
|
+
const { defaultExampleOperationsDoc, extractFunctionsFromOperationDoc } = NetlifyGraph
|
|
22
|
+
|
|
23
|
+
const internalConsole = {
|
|
24
|
+
log,
|
|
25
|
+
warn,
|
|
26
|
+
error,
|
|
27
|
+
debug: console.debug,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
InternalConsole.registerConsole(internalConsole)
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Start polling for CLI events for a given session to process locally
|
|
34
|
+
* @param {object} input
|
|
35
|
+
* @param {string} input.appId The app to query against, typically the siteId
|
|
36
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
37
|
+
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
38
|
+
* @param {function} input.onClose A function to call when the polling loop is closed
|
|
39
|
+
* @param {function} input.onError A function to call when an error occurs
|
|
40
|
+
* @param {function} input.onEvents A function to call when CLI events are received and need to be processed
|
|
41
|
+
* @param {string} input.sessionId The session id to monitor CLI events for
|
|
42
|
+
* @param {state} input.state A function to call to set/get the current state of the local Netlify project
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
const monitorCLISessionEvents = (input) => {
|
|
46
|
+
const { appId, netlifyGraphConfig, netlifyToken, onClose, onError, onEvents, sessionId, state } = input
|
|
47
|
+
|
|
48
|
+
const frequency = 5000
|
|
49
|
+
let shouldClose = false
|
|
50
|
+
|
|
51
|
+
const enabledServiceWatcher = async (innerNetlifyToken, siteId) => {
|
|
52
|
+
const enabledServices = state.get('oneGraphEnabledServices') || ['onegraph']
|
|
53
|
+
const enabledServicesInfo = await OneGraphClient.fetchEnabledServices(innerNetlifyToken, siteId)
|
|
54
|
+
if (!enabledServicesInfo) {
|
|
55
|
+
warn('Unable to fetch enabled services for site for code generation')
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
const newEnabledServices = enabledServicesInfo.map((service) => service.service)
|
|
59
|
+
const enabledServicesCompareKey = enabledServices.sort().join(',')
|
|
60
|
+
const newEnabledServicesCompareKey = newEnabledServices.sort().join(',')
|
|
61
|
+
|
|
62
|
+
if (enabledServicesCompareKey !== newEnabledServicesCompareKey) {
|
|
63
|
+
log(
|
|
64
|
+
`${chalk.magenta(
|
|
65
|
+
'Reloading',
|
|
66
|
+
)} Netlify Graph schema..., ${enabledServicesCompareKey} => ${newEnabledServicesCompareKey}`,
|
|
67
|
+
)
|
|
68
|
+
await refetchAndGenerateFromOneGraph({ netlifyGraphConfig, state, netlifyToken: innerNetlifyToken, siteId })
|
|
69
|
+
log(`${chalk.green('Reloaded')} Netlify Graph schema and regenerated functions`)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const close = () => {
|
|
74
|
+
shouldClose = true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let handle
|
|
78
|
+
|
|
79
|
+
const helper = async () => {
|
|
80
|
+
if (shouldClose) {
|
|
81
|
+
clearTimeout(handle)
|
|
82
|
+
onClose()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const next = await OneGraphClient.fetchCliSessionEvents({ appId, authToken: netlifyToken, sessionId })
|
|
86
|
+
|
|
87
|
+
if (next.errors) {
|
|
88
|
+
next.errors.forEach((fetchEventError) => {
|
|
89
|
+
onError(fetchEventError)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { events } = next
|
|
94
|
+
|
|
95
|
+
if (events.length !== 0) {
|
|
96
|
+
const ackIds = await onEvents(events)
|
|
97
|
+
await OneGraphClient.ackCLISessionEvents({ appId, authToken: netlifyToken, sessionId, eventIds: ackIds })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await enabledServiceWatcher(netlifyToken, appId)
|
|
101
|
+
|
|
102
|
+
handle = setTimeout(helper, frequency)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Fire immediately to start rather than waiting the initial `frequency`
|
|
106
|
+
helper()
|
|
107
|
+
|
|
108
|
+
return close
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Fetch the schema for a site, and regenerate all of the downstream files
|
|
113
|
+
* @param {object} input
|
|
114
|
+
* @param {string} input.siteId The id of the site to query against
|
|
115
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
116
|
+
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
117
|
+
* @param {state} input.state A function to call to set/get the current state of the local Netlify project
|
|
118
|
+
* @returns {Promise<void>}
|
|
119
|
+
*/
|
|
120
|
+
const refetchAndGenerateFromOneGraph = async (input) => {
|
|
121
|
+
const { netlifyGraphConfig, netlifyToken, siteId, state } = input
|
|
122
|
+
await OneGraphClient.ensureAppForSite(netlifyToken, siteId)
|
|
123
|
+
|
|
124
|
+
const enabledServicesInfo = await OneGraphClient.fetchEnabledServices(netlifyToken, siteId)
|
|
125
|
+
if (!enabledServicesInfo) {
|
|
126
|
+
warn('Unable to fetch enabled services for site for code generation')
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const enabledServices = enabledServicesInfo
|
|
131
|
+
.map((service) => service.service)
|
|
132
|
+
.sort((aString, bString) => aString.localeCompare(bString))
|
|
133
|
+
const schema = await OneGraphClient.fetchOneGraphSchema(siteId, enabledServices)
|
|
134
|
+
|
|
135
|
+
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
136
|
+
if (currentOperationsDoc.trim().length === 0) {
|
|
137
|
+
currentOperationsDoc = defaultExampleOperationsDoc
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parsedDoc = parse(currentOperationsDoc)
|
|
141
|
+
const operations = extractFunctionsFromOperationDoc(parsedDoc)
|
|
142
|
+
|
|
143
|
+
generateFunctionsFile(netlifyGraphConfig, schema, currentOperationsDoc, operations)
|
|
144
|
+
writeGraphQLSchemaFile(netlifyGraphConfig, schema)
|
|
145
|
+
state.set('oneGraphEnabledServices', enabledServices)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
* @param {object} input
|
|
151
|
+
* @param {string} input.siteId The site id to query against
|
|
152
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
153
|
+
* @param {string} input.docId The GraphQL operations document id to fetch
|
|
154
|
+
* @param {string} input.schema The GraphQL schema to use when generating code
|
|
155
|
+
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
156
|
+
* @returns
|
|
157
|
+
*/
|
|
158
|
+
const updateGraphQLOperationsFile = async (input) => {
|
|
159
|
+
const { docId, netlifyGraphConfig, netlifyToken, schema, siteId } = input
|
|
160
|
+
const persistedDoc = await OneGraphClient.fetchPersistedQuery(netlifyToken, siteId, docId)
|
|
161
|
+
if (!persistedDoc) {
|
|
162
|
+
warn('No persisted doc found for:', docId)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const doc = persistedDoc.query
|
|
167
|
+
|
|
168
|
+
writeGraphQLOperationsSourceFile(netlifyGraphConfig, doc)
|
|
169
|
+
const appOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
170
|
+
const parsedDoc = parse(appOperationsDoc, {
|
|
171
|
+
noLocation: true,
|
|
172
|
+
})
|
|
173
|
+
const operations = extractFunctionsFromOperationDoc(parsedDoc)
|
|
174
|
+
generateFunctionsFile(netlifyGraphConfig, schema, appOperationsDoc, operations)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const handleCliSessionEvent = async ({ event, netlifyGraphConfig, netlifyToken, schema, siteId }) => {
|
|
178
|
+
const { __typename, payload } = await event
|
|
179
|
+
switch (__typename) {
|
|
180
|
+
case 'OneGraphNetlifyCliSessionTestEvent':
|
|
181
|
+
await handleCliSessionEvent({ netlifyToken, event: payload, netlifyGraphConfig, schema, siteId })
|
|
182
|
+
break
|
|
183
|
+
case 'OneGraphNetlifyCliSessionGenerateHandlerEvent':
|
|
184
|
+
await generateHandler(netlifyGraphConfig, schema, payload.operationId, payload)
|
|
185
|
+
break
|
|
186
|
+
case 'OneGraphNetlifyCliSessionPersistedLibraryUpdatedEvent':
|
|
187
|
+
await updateGraphQLOperationsFile({ netlifyToken, docId: payload.docId, netlifyGraphConfig, schema, siteId })
|
|
188
|
+
break
|
|
189
|
+
default: {
|
|
190
|
+
warn(`Unrecognized event received, you may need to upgrade your CLI version`, __typename, payload)
|
|
191
|
+
break
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Load the CLI session id from the local state
|
|
198
|
+
* @param {state} state
|
|
199
|
+
* @returns
|
|
200
|
+
*/
|
|
201
|
+
const loadCLISession = (state) => state.get('oneGraphSessionId')
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Idemponentially save the CLI session id to the local state and start monitoring for CLI events and upstream schema changes
|
|
205
|
+
* @param {object} input
|
|
206
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
207
|
+
* @param {NetlifyGraphConfig} input.netlifyGraphConfig A standalone config object that contains all the information necessary for Netlify Graph to process events
|
|
208
|
+
* @param {state} input.state A function to call to set/get the current state of the local Netlify project
|
|
209
|
+
* @param {site} input.site The site object
|
|
210
|
+
*/
|
|
211
|
+
const startOneGraphCLISession = async (input) => {
|
|
212
|
+
const { netlifyGraphConfig, netlifyToken, site, state } = input
|
|
213
|
+
OneGraphClient.ensureAppForSite(netlifyToken, site.id)
|
|
214
|
+
let oneGraphSessionId = loadCLISession(state)
|
|
215
|
+
if (!oneGraphSessionId) {
|
|
216
|
+
const sessionName = generateSessionName()
|
|
217
|
+
const sessionMetadata = {}
|
|
218
|
+
const oneGraphSession = await OneGraphClient.createCLISession(netlifyToken, site.id, sessionName, sessionMetadata)
|
|
219
|
+
state.set('oneGraphSessionId', oneGraphSession.id)
|
|
220
|
+
oneGraphSessionId = state.get('oneGraphSessionId')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const enabledServices = []
|
|
224
|
+
const schema = await OneGraphClient.fetchOneGraphSchema(site.id, enabledServices)
|
|
225
|
+
|
|
226
|
+
monitorCLISessionEvents({
|
|
227
|
+
appId: site.id,
|
|
228
|
+
netlifyToken,
|
|
229
|
+
netlifyGraphConfig,
|
|
230
|
+
sessionId: oneGraphSessionId,
|
|
231
|
+
state,
|
|
232
|
+
onEvents: async (events) => {
|
|
233
|
+
for (const event of events) {
|
|
234
|
+
const eventName = OneGraphClient.friendlyEventName(event)
|
|
235
|
+
log(`${chalk.magenta('Handling')} Netlify Graph: ${eventName}...`)
|
|
236
|
+
await handleCliSessionEvent({ netlifyToken, event, netlifyGraphConfig, schema, siteId: site.id })
|
|
237
|
+
log(`${chalk.green('Finished handling')} Netlify Graph: ${eventName}...`)
|
|
238
|
+
}
|
|
239
|
+
return events.map((event) => event.id)
|
|
240
|
+
},
|
|
241
|
+
onError: (fetchEventError) => {
|
|
242
|
+
error(`Netlify Graph upstream error: ${fetchEventError}`)
|
|
243
|
+
},
|
|
244
|
+
onClose: () => {
|
|
245
|
+
log('Netlify Graph upstream closed')
|
|
246
|
+
},
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Generate a session name that can be identified as belonging to the current checkout
|
|
252
|
+
* @returns {string} The name of the session to create
|
|
253
|
+
*/
|
|
254
|
+
const generateSessionName = () => {
|
|
255
|
+
const userInfo = os.userInfo({ encoding: 'utf-8' })
|
|
256
|
+
const sessionName = `${userInfo.username}-${Date.now()}`
|
|
257
|
+
log(`Generated Netlify Graph session name: ${sessionName}`)
|
|
258
|
+
return sessionName
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const OneGraphCliClient = {
|
|
262
|
+
ackCLISessionEvents: OneGraphClient.ackCLISessionEvents,
|
|
263
|
+
createCLISession,
|
|
264
|
+
createPersistedQuery,
|
|
265
|
+
fetchCliSessionEvents: OneGraphClient.fetchCliSessionEvents,
|
|
266
|
+
ensureAppForSite,
|
|
267
|
+
updateCLISessionMetadata,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = {
|
|
271
|
+
OneGraphCliClient,
|
|
272
|
+
handleCliSessionEvent,
|
|
273
|
+
generateSessionName,
|
|
274
|
+
loadCLISession,
|
|
275
|
+
monitorCLISessionEvents,
|
|
276
|
+
refetchAndGenerateFromOneGraph,
|
|
277
|
+
startOneGraphCLISession,
|
|
278
|
+
}
|