netlify-cli 9.4.4 → 9.6.2
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/npm-shrinkwrap.json +9 -9
- package/package.json +2 -2
- package/src/commands/base-command.js +27 -0
- package/src/commands/dev/dev.js +126 -38
- package/src/commands/graph/graph-edit.js +7 -14
- package/src/commands/graph/graph-handler.js +69 -3
- package/src/functions-templates/javascript/scheduled-function/package.json +1 -1
- package/src/functions-templates/typescript/hello-world/package-lock.json +8 -8
- package/src/functions-templates/typescript/hello-world/package.json +1 -1
- package/src/functions-templates/typescript/scheduled-function/package.json +1 -1
- package/src/lib/functions/registry.js +1 -2
- package/src/lib/functions/server.js +1 -45
- package/src/lib/one-graph/cli-client.js +125 -23
- package/src/lib/one-graph/cli-netlify-graph.js +1 -2
- package/src/utils/command-helpers.js +33 -0
- package/src/lib/functions/watcher.js +0 -35
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.6.2",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "netlify-cli",
|
|
9
|
-
"version": "9.
|
|
9
|
+
"version": "9.6.2",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"@netlify/framework-info": "^9.0.0",
|
|
16
16
|
"@netlify/local-functions-proxy": "^1.1.1",
|
|
17
17
|
"@netlify/plugin-edge-handlers": "^3.0.6",
|
|
18
|
-
"@netlify/plugins-list": "^6.
|
|
18
|
+
"@netlify/plugins-list": "^6.11.0",
|
|
19
19
|
"@netlify/routing-local-proxy": "^0.34.1",
|
|
20
20
|
"@netlify/zip-it-and-ship-it": "^5.7.5",
|
|
21
21
|
"@octokit/rest": "^18.0.0",
|
|
@@ -4132,9 +4132,9 @@
|
|
|
4132
4132
|
}
|
|
4133
4133
|
},
|
|
4134
4134
|
"node_modules/@netlify/plugins-list": {
|
|
4135
|
-
"version": "6.
|
|
4136
|
-
"resolved": "https://registry.npmjs.org/@netlify/plugins-list/-/plugins-list-6.
|
|
4137
|
-
"integrity": "sha512-
|
|
4135
|
+
"version": "6.11.0",
|
|
4136
|
+
"resolved": "https://registry.npmjs.org/@netlify/plugins-list/-/plugins-list-6.11.0.tgz",
|
|
4137
|
+
"integrity": "sha512-b6+htKVooVnUPbq2zlCPUcWJSvR18a20cl1W3MS3EThEbXM/CCQWcCEqjwk+Q5VCH2hoJoaHlLhO/lIHpwn8Ig==",
|
|
4138
4138
|
"engines": {
|
|
4139
4139
|
"node": "^12.20.0 || ^14.14.0 || >=16.0.0"
|
|
4140
4140
|
}
|
|
@@ -26586,9 +26586,9 @@
|
|
|
26586
26586
|
}
|
|
26587
26587
|
},
|
|
26588
26588
|
"@netlify/plugins-list": {
|
|
26589
|
-
"version": "6.
|
|
26590
|
-
"resolved": "https://registry.npmjs.org/@netlify/plugins-list/-/plugins-list-6.
|
|
26591
|
-
"integrity": "sha512-
|
|
26589
|
+
"version": "6.11.0",
|
|
26590
|
+
"resolved": "https://registry.npmjs.org/@netlify/plugins-list/-/plugins-list-6.11.0.tgz",
|
|
26591
|
+
"integrity": "sha512-b6+htKVooVnUPbq2zlCPUcWJSvR18a20cl1W3MS3EThEbXM/CCQWcCEqjwk+Q5VCH2hoJoaHlLhO/lIHpwn8Ig=="
|
|
26592
26592
|
},
|
|
26593
26593
|
"@netlify/routing-local-proxy": {
|
|
26594
26594
|
"version": "0.34.1",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
3
|
"description": "Netlify command line tool",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.6.2",
|
|
5
5
|
"author": "Netlify Inc.",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Abraham Schilling <AbrahamSchilling@gmail.com> (https://gitlab.com/n4bb12)",
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
"@netlify/framework-info": "^9.0.0",
|
|
205
205
|
"@netlify/local-functions-proxy": "^1.1.1",
|
|
206
206
|
"@netlify/plugin-edge-handlers": "^3.0.6",
|
|
207
|
-
"@netlify/plugins-list": "^6.
|
|
207
|
+
"@netlify/plugins-list": "^6.11.0",
|
|
208
208
|
"@netlify/routing-local-proxy": "^0.34.1",
|
|
209
209
|
"@netlify/zip-it-and-ship-it": "^5.7.5",
|
|
210
210
|
"@octokit/rest": "^18.0.0",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
const events = require('events')
|
|
2
3
|
const process = require('process')
|
|
3
4
|
const { format } = require('util')
|
|
4
5
|
|
|
@@ -28,6 +29,7 @@ const {
|
|
|
28
29
|
pollForToken,
|
|
29
30
|
sortOptions,
|
|
30
31
|
track,
|
|
32
|
+
watchDebounced,
|
|
31
33
|
} = require('../utils')
|
|
32
34
|
|
|
33
35
|
// Netlify CLI client id. Lives in bot@netlify.com
|
|
@@ -119,6 +121,11 @@ class BaseCommand extends Command {
|
|
|
119
121
|
await this.init(actionCommand)
|
|
120
122
|
debug(`${name}:preAction`)('end')
|
|
121
123
|
})
|
|
124
|
+
.hook('postAction', async () => {
|
|
125
|
+
if (this.configWatcherHandle) {
|
|
126
|
+
await this.configWatcherHandle.close()
|
|
127
|
+
}
|
|
128
|
+
})
|
|
122
129
|
)
|
|
123
130
|
}
|
|
124
131
|
|
|
@@ -430,6 +437,24 @@ class BaseCommand extends Command {
|
|
|
430
437
|
const globalConfig = await getGlobalConfig()
|
|
431
438
|
const { NetlifyAPI } = await jsClient
|
|
432
439
|
|
|
440
|
+
const configWatcher = new events.EventEmitter()
|
|
441
|
+
|
|
442
|
+
// Only set up a watcher if we know the config path.
|
|
443
|
+
if (configPath) {
|
|
444
|
+
const configWatcherHandle = await watchDebounced(configPath, {
|
|
445
|
+
depth: 1,
|
|
446
|
+
onChange: async () => {
|
|
447
|
+
const { config: newConfig } = await actionCommand.getConfig({ cwd, state, token, ...apiUrlOpts })
|
|
448
|
+
|
|
449
|
+
const normalizedNewConfig = normalizeConfig(newConfig)
|
|
450
|
+
configWatcher.emit('change', normalizedNewConfig)
|
|
451
|
+
},
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
// chokidar handler
|
|
455
|
+
this.configWatcherHandle = configWatcherHandle
|
|
456
|
+
}
|
|
457
|
+
|
|
433
458
|
actionCommand.netlify = {
|
|
434
459
|
// api methods
|
|
435
460
|
api: new NetlifyAPI(token || '', apiOpts),
|
|
@@ -455,6 +480,8 @@ class BaseCommand extends Command {
|
|
|
455
480
|
globalConfig,
|
|
456
481
|
// state of current site dir
|
|
457
482
|
state,
|
|
483
|
+
// netlify.toml file watcher
|
|
484
|
+
configWatcher,
|
|
458
485
|
}
|
|
459
486
|
debug(`${this.name()}:init`)('end')
|
|
460
487
|
}
|
package/src/commands/dev/dev.js
CHANGED
|
@@ -14,6 +14,7 @@ const { startFunctionsServer } = require('../../lib/functions/server')
|
|
|
14
14
|
const {
|
|
15
15
|
OneGraphCliClient,
|
|
16
16
|
loadCLISession,
|
|
17
|
+
markCliSessionInactive,
|
|
17
18
|
persistNewOperationsDocForSession,
|
|
18
19
|
startOneGraphCLISession,
|
|
19
20
|
} = require('../../lib/one-graph/cli-client')
|
|
@@ -32,6 +33,7 @@ const {
|
|
|
32
33
|
detectServerSettings,
|
|
33
34
|
error,
|
|
34
35
|
exit,
|
|
36
|
+
generateNetlifyGraphJWT,
|
|
35
37
|
getSiteInformation,
|
|
36
38
|
injectEnvVariables,
|
|
37
39
|
log,
|
|
@@ -78,6 +80,30 @@ const isNonExistingCommandError = ({ command, error: commandError }) => {
|
|
|
78
80
|
)
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @type {(() => Promise<void>)[]} - array of functions to run before the process exits
|
|
85
|
+
*/
|
|
86
|
+
const cleanupWork = []
|
|
87
|
+
|
|
88
|
+
let cleanupStarted = false
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {object} input
|
|
92
|
+
* @param {number=} input.exitCode The exit code to return when exiting the process after cleanup
|
|
93
|
+
*/
|
|
94
|
+
const cleanupBeforeExit = async ({ exitCode }) => {
|
|
95
|
+
// If cleanup has started, then wherever started it will be responsible for exiting
|
|
96
|
+
if (!cleanupStarted) {
|
|
97
|
+
cleanupStarted = true
|
|
98
|
+
try {
|
|
99
|
+
// eslint-disable-next-line no-unused-vars
|
|
100
|
+
const cleanupFinished = await Promise.all(cleanupWork.map((cleanup) => cleanup()))
|
|
101
|
+
} finally {
|
|
102
|
+
process.exit(exitCode)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
/**
|
|
82
108
|
* Run a command and pipe stdout, stderr and stdin
|
|
83
109
|
* @param {string} command
|
|
@@ -100,30 +126,29 @@ const runCommand = (command, env = {}) => {
|
|
|
100
126
|
|
|
101
127
|
// we can't try->await->catch since we don't want to block on the framework server which
|
|
102
128
|
// is a long running process
|
|
103
|
-
// eslint-disable-next-line promise/catch-or-return
|
|
104
|
-
commandProcess
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
process.on(signal, () => {
|
|
124
|
-
commandProcess.kill('SIGTERM', { forceKillAfterTimeout: 500 })
|
|
125
|
-
process.exit()
|
|
129
|
+
// eslint-disable-next-line promise/catch-or-return
|
|
130
|
+
commandProcess
|
|
131
|
+
// eslint-disable-next-line promise/prefer-await-to-then
|
|
132
|
+
.then(async () => {
|
|
133
|
+
const result = await commandProcess
|
|
134
|
+
const [commandWithoutArgs] = command.split(' ')
|
|
135
|
+
if (result.failed && isNonExistingCommandError({ command: commandWithoutArgs, error: result })) {
|
|
136
|
+
log(
|
|
137
|
+
NETLIFYDEVERR,
|
|
138
|
+
`Failed running command: ${command}. Please verify ${chalk.magenta(`'${commandWithoutArgs}'`)} exists`,
|
|
139
|
+
)
|
|
140
|
+
} else {
|
|
141
|
+
const errorMessage = result.failed
|
|
142
|
+
? `${NETLIFYDEVERR} ${result.shortMessage}`
|
|
143
|
+
: `${NETLIFYDEVWARN} "${command}" exited with code ${result.exitCode}`
|
|
144
|
+
|
|
145
|
+
log(`${errorMessage}. Shutting down Netlify Dev server`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return await cleanupBeforeExit({ exitCode: 1 })
|
|
126
149
|
})
|
|
150
|
+
;['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP', 'exit'].forEach((signal) => {
|
|
151
|
+
process.on(signal, async () => await cleanupBeforeExit({}))
|
|
127
152
|
})
|
|
128
153
|
|
|
129
154
|
return commandProcess
|
|
@@ -238,6 +263,44 @@ const printBanner = ({ url }) => {
|
|
|
238
263
|
)
|
|
239
264
|
}
|
|
240
265
|
|
|
266
|
+
const startPollingForAPIAuthentication = async function (options) {
|
|
267
|
+
const { api, command, config, site, siteInfo } = options
|
|
268
|
+
const frequency = 5000
|
|
269
|
+
|
|
270
|
+
const helper = async (maybeSiteData) => {
|
|
271
|
+
const siteData = await (maybeSiteData || api.getSite({ siteId: site.id }))
|
|
272
|
+
const authlifyTokenId = siteData && siteData.authlify_token_id
|
|
273
|
+
|
|
274
|
+
const existingAuthlifyTokenId = config && config.netlifyGraphConfig && config.netlifyGraphConfig.authlifyTokenId
|
|
275
|
+
if (authlifyTokenId && authlifyTokenId !== existingAuthlifyTokenId) {
|
|
276
|
+
const netlifyToken = await command.authenticate()
|
|
277
|
+
// Only inject the authlify config if a token ID exists. This prevents
|
|
278
|
+
// calling command.authenticate() (which opens a browser window) if the
|
|
279
|
+
// user hasn't enabled API Authentication
|
|
280
|
+
const netlifyGraphConfig = {
|
|
281
|
+
netlifyToken,
|
|
282
|
+
authlifyTokenId: siteData.authlify_token_id,
|
|
283
|
+
siteId: site.id,
|
|
284
|
+
}
|
|
285
|
+
config.netlifyGraphConfig = netlifyGraphConfig
|
|
286
|
+
|
|
287
|
+
const netlifyGraphJWT = generateNetlifyGraphJWT(netlifyGraphConfig)
|
|
288
|
+
|
|
289
|
+
if (netlifyGraphJWT != null) {
|
|
290
|
+
// XXX(anmonteiro): this name is deprecated. Delete after 3/31/2022
|
|
291
|
+
process.env.ONEGRAPH_AUTHLIFY_TOKEN = netlifyGraphJWT
|
|
292
|
+
process.env.NETLIFY_GRAPH_TOKEN = netlifyGraphJWT
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
delete config.netlifyGraphConfig
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
setTimeout(helper, frequency)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
await helper(siteInfo)
|
|
302
|
+
}
|
|
303
|
+
|
|
241
304
|
/**
|
|
242
305
|
* The dev command
|
|
243
306
|
* @param {import('commander').OptionValues} options
|
|
@@ -263,7 +326,6 @@ const dev = async (options, command) => {
|
|
|
263
326
|
)
|
|
264
327
|
}
|
|
265
328
|
|
|
266
|
-
const startNetlifyGraphWatcher = Boolean(options.graph)
|
|
267
329
|
await injectEnvVariables({ env: command.netlify.cachedConfig.env, site })
|
|
268
330
|
|
|
269
331
|
const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
|
|
@@ -285,11 +347,15 @@ const dev = async (options, command) => {
|
|
|
285
347
|
|
|
286
348
|
command.setAnalyticsPayload({ projectType: settings.framework || 'custom', live: options.live })
|
|
287
349
|
|
|
350
|
+
const startNetlifyGraphWatcher = Boolean(options.graph)
|
|
351
|
+
if (startNetlifyGraphWatcher) {
|
|
352
|
+
startPollingForAPIAuthentication({ api, command, config, site, siteInfo })
|
|
353
|
+
}
|
|
354
|
+
|
|
288
355
|
await startFunctionsServer({
|
|
289
356
|
api,
|
|
290
357
|
command,
|
|
291
358
|
config,
|
|
292
|
-
isGraphEnabled: startNetlifyGraphWatcher,
|
|
293
359
|
settings,
|
|
294
360
|
site,
|
|
295
361
|
siteInfo,
|
|
@@ -322,26 +388,48 @@ const dev = async (options, command) => {
|
|
|
322
388
|
} else if (startNetlifyGraphWatcher) {
|
|
323
389
|
const netlifyToken = await command.authenticate()
|
|
324
390
|
await OneGraphCliClient.ensureAppForSite(netlifyToken, site.id)
|
|
325
|
-
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options, settings })
|
|
326
391
|
|
|
327
|
-
let
|
|
392
|
+
let stopWatchingCLISessions
|
|
328
393
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
394
|
+
const createOrResumeSession = async function () {
|
|
395
|
+
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options, settings })
|
|
332
396
|
|
|
333
|
-
|
|
397
|
+
let graphqlDocument = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
334
398
|
|
|
335
|
-
|
|
336
|
-
|
|
399
|
+
if (!graphqlDocument || graphqlDocument.trim().length === 0) {
|
|
400
|
+
graphqlDocument = defaultExampleOperationsDoc
|
|
401
|
+
}
|
|
337
402
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
403
|
+
stopWatchingCLISessions = await startOneGraphCLISession({ netlifyGraphConfig, netlifyToken, site, state })
|
|
404
|
+
|
|
405
|
+
// Should be created by startOneGraphCLISession
|
|
406
|
+
const oneGraphSessionId = loadCLISession(state)
|
|
407
|
+
|
|
408
|
+
await persistNewOperationsDocForSession({
|
|
409
|
+
netlifyGraphConfig,
|
|
410
|
+
netlifyToken,
|
|
411
|
+
oneGraphSessionId,
|
|
412
|
+
operationsDoc: graphqlDocument,
|
|
413
|
+
siteId: site.id,
|
|
414
|
+
siteRoot: site.root,
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
return oneGraphSessionId
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
//
|
|
421
|
+
// Set up a handler for config changes.
|
|
422
|
+
command.netlify.configWatcher.on('change', (newConfig) => {
|
|
423
|
+
command.netlify.config = newConfig
|
|
424
|
+
stopWatchingCLISessions()
|
|
425
|
+
createOrResumeSession()
|
|
343
426
|
})
|
|
344
427
|
|
|
428
|
+
const oneGraphSessionId = await createOrResumeSession()
|
|
429
|
+
const cleanupSession = () => markCliSessionInactive({ netlifyToken, sessionId: oneGraphSessionId, siteId: site.id })
|
|
430
|
+
|
|
431
|
+
cleanupWork.push(cleanupSession)
|
|
432
|
+
|
|
345
433
|
const graphEditUrl = getGraphEditUrlBySiteId({ siteId: site.id, oneGraphSessionId })
|
|
346
434
|
|
|
347
435
|
log(
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
const gitRepoInfo = require('git-repo-info')
|
|
3
3
|
|
|
4
|
-
const {
|
|
5
|
-
OneGraphCliClient,
|
|
6
|
-
createCLISession,
|
|
7
|
-
generateSessionName,
|
|
8
|
-
loadCLISession,
|
|
9
|
-
upsertMergeCLISessionMetadata,
|
|
10
|
-
} = require('../../lib/one-graph/cli-client')
|
|
4
|
+
const { OneGraphCliClient, ensureCLISession, upsertMergeCLISessionMetadata } = require('../../lib/one-graph/cli-client')
|
|
11
5
|
const {
|
|
12
6
|
defaultExampleOperationsDoc,
|
|
13
7
|
getGraphEditUrlBySiteId,
|
|
@@ -48,13 +42,12 @@ const graphEdit = async (options, command) => {
|
|
|
48
42
|
|
|
49
43
|
await ensureAppForSite(netlifyToken, siteId)
|
|
50
44
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
state
|
|
56
|
-
|
|
57
|
-
}
|
|
45
|
+
const oneGraphSessionId = await ensureCLISession({
|
|
46
|
+
metadata: {},
|
|
47
|
+
netlifyToken,
|
|
48
|
+
site,
|
|
49
|
+
state,
|
|
50
|
+
})
|
|
58
51
|
|
|
59
52
|
const { branch } = gitRepoInfo()
|
|
60
53
|
const persistedDoc = await createPersistedQuery(netlifyToken, {
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
const inquirer = require('inquirer')
|
|
3
|
+
const { GraphQL } = require('netlify-onegraph-internal')
|
|
4
|
+
|
|
2
5
|
const {
|
|
3
6
|
buildSchema,
|
|
7
|
+
defaultExampleOperationsDoc,
|
|
8
|
+
extractFunctionsFromOperationDoc,
|
|
4
9
|
generateHandlerByOperationName,
|
|
5
10
|
getNetlifyGraphConfig,
|
|
11
|
+
readGraphQLOperationsSourceFile,
|
|
6
12
|
readGraphQLSchemaFile,
|
|
7
13
|
} = require('../../lib/one-graph/cli-netlify-graph')
|
|
8
14
|
const { error, log } = require('../../utils')
|
|
9
15
|
|
|
16
|
+
const { parse } = GraphQL
|
|
17
|
+
|
|
10
18
|
/**
|
|
11
19
|
* Creates the `netlify graph:handler` command
|
|
12
|
-
* @param {string}
|
|
20
|
+
* @param {string} userOperationName
|
|
13
21
|
* @param {import('commander').OptionValues} options
|
|
14
22
|
* @param {import('../base-command').BaseCommand} command
|
|
15
23
|
* @returns
|
|
16
24
|
*/
|
|
17
|
-
const graphHandler = async (
|
|
25
|
+
const graphHandler = async (userOperationName, options, command) => {
|
|
18
26
|
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options })
|
|
19
27
|
|
|
20
28
|
const schemaString = readGraphQLSchemaFile(netlifyGraphConfig)
|
|
@@ -31,6 +39,64 @@ const graphHandler = async (operationName, options, command) => {
|
|
|
31
39
|
error(`Failed to parse Netlify GraphQL schema`)
|
|
32
40
|
}
|
|
33
41
|
|
|
42
|
+
let operationName = userOperationName
|
|
43
|
+
if (!operationName) {
|
|
44
|
+
try {
|
|
45
|
+
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
|
|
46
|
+
if (currentOperationsDoc.trim().length === 0) {
|
|
47
|
+
currentOperationsDoc = defaultExampleOperationsDoc
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const parsedDoc = parse(currentOperationsDoc)
|
|
51
|
+
const { functions } = extractFunctionsFromOperationDoc(parsedDoc)
|
|
52
|
+
|
|
53
|
+
const sorted = Object.values(functions).sort((aItem, bItem) =>
|
|
54
|
+
aItem.operationName.localeCompare(bItem.operationName),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const perPage = 50
|
|
58
|
+
|
|
59
|
+
const allOperationChoices = sorted.map((operation) => ({
|
|
60
|
+
name: `${operation.operationName} (${operation.kind})`,
|
|
61
|
+
value: operation.operationName,
|
|
62
|
+
}))
|
|
63
|
+
|
|
64
|
+
const filterOperationNames = (operationChoices, input) =>
|
|
65
|
+
operationChoices.filter((operation) => operation.value.toLowerCase().match(input.toLowerCase()))
|
|
66
|
+
|
|
67
|
+
// eslint-disable-next-line node/global-require
|
|
68
|
+
const inquirerAutocompletePrompt = require('inquirer-autocomplete-prompt')
|
|
69
|
+
/** multiple matching detectors, make the user choose */
|
|
70
|
+
inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
|
|
71
|
+
|
|
72
|
+
const { selectedOperationName } = await inquirer.prompt({
|
|
73
|
+
name: 'selectedOperationName',
|
|
74
|
+
message: `For which operation would you like to generate a handler?`,
|
|
75
|
+
type: 'autocomplete',
|
|
76
|
+
pageSize: perPage,
|
|
77
|
+
source(_, input) {
|
|
78
|
+
if (!input || input === '') {
|
|
79
|
+
return allOperationChoices
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const filteredChoices = filterOperationNames(allOperationChoices, input)
|
|
83
|
+
// only show filtered results
|
|
84
|
+
return filteredChoices
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (selectedOperationName) {
|
|
89
|
+
operationName = selectedOperationName
|
|
90
|
+
}
|
|
91
|
+
} catch (parseError) {
|
|
92
|
+
parseError(`Error parsing operations library: ${parseError}`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!operationName) {
|
|
97
|
+
error(`No operation name provided`)
|
|
98
|
+
}
|
|
99
|
+
|
|
34
100
|
generateHandlerByOperationName({ logger: log, netlifyGraphConfig, schema, operationName, handlerOptions: {} })
|
|
35
101
|
}
|
|
36
102
|
|
|
@@ -42,7 +108,7 @@ const graphHandler = async (operationName, options, command) => {
|
|
|
42
108
|
const createGraphHandlerCommand = (program) =>
|
|
43
109
|
program
|
|
44
110
|
.command('graph:handler')
|
|
45
|
-
.argument('
|
|
111
|
+
.argument('[name]', 'Operation name')
|
|
46
112
|
.description(
|
|
47
113
|
'Generate a handler for a Graph operation given its name. See `graph:operations` for a list of operations.',
|
|
48
114
|
)
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
"version": "1.0.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@netlify/functions": "^0.11.
|
|
13
|
-
"@types/node": "^14.
|
|
12
|
+
"@netlify/functions": "^0.11.1",
|
|
13
|
+
"@types/node": "^14.0.0",
|
|
14
14
|
"typescript": "^4.0.0"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"node_modules/@netlify/functions": {
|
|
18
|
-
"version": "0.11.
|
|
19
|
-
"resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-0.11.
|
|
20
|
-
"integrity": "sha512
|
|
18
|
+
"version": "0.11.1",
|
|
19
|
+
"resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-0.11.1.tgz",
|
|
20
|
+
"integrity": "sha512-J2QUAYRblvTrl/cOYU2RNHdv/EYy4qnKQhds0aDNB560Y83wfpaMS3F7PKpHygGWrVuLRyevenIzsV9MaQrKlQ==",
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"is-promise": "^4.0.0"
|
|
23
23
|
},
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@netlify/functions": {
|
|
53
|
-
"version": "0.11.
|
|
54
|
-
"resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-0.11.
|
|
55
|
-
"integrity": "sha512
|
|
53
|
+
"version": "0.11.1",
|
|
54
|
+
"resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-0.11.1.tgz",
|
|
55
|
+
"integrity": "sha512-J2QUAYRblvTrl/cOYU2RNHdv/EYy4qnKQhds0aDNB560Y83wfpaMS3F7PKpHygGWrVuLRyevenIzsV9MaQrKlQ==",
|
|
56
56
|
"requires": {
|
|
57
57
|
"is-promise": "^4.0.0"
|
|
58
58
|
}
|
|
@@ -5,12 +5,11 @@ const { env } = require('process')
|
|
|
5
5
|
|
|
6
6
|
const terminalLink = require('terminal-link')
|
|
7
7
|
|
|
8
|
-
const { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, log, warn } = require('../../utils')
|
|
8
|
+
const { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, log, warn, watchDebounced } = require('../../utils')
|
|
9
9
|
const { getLogMessage } = require('../log')
|
|
10
10
|
|
|
11
11
|
const { NetlifyFunction } = require('./netlify-function')
|
|
12
12
|
const runtimes = require('./runtimes')
|
|
13
|
-
const { watchDebounced } = require('./watcher')
|
|
14
13
|
|
|
15
14
|
const ZIP_EXTENSION = '.zip'
|
|
16
15
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
const process = require('process')
|
|
3
|
-
|
|
4
2
|
const jwtDecode = require('jwt-decode')
|
|
5
3
|
|
|
6
4
|
const {
|
|
@@ -47,51 +45,9 @@ const buildClientContext = function (headers) {
|
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
const startPollingForAPIAuthentication = async function (options) {
|
|
51
|
-
const { api, command, config, site, siteInfo } = options
|
|
52
|
-
const frequency = 5000
|
|
53
|
-
|
|
54
|
-
const helper = async (maybeSiteData) => {
|
|
55
|
-
const siteData = await (maybeSiteData || api.getSite({ siteId: site.id }))
|
|
56
|
-
const authlifyTokenId = siteData && siteData.authlify_token_id
|
|
57
|
-
|
|
58
|
-
const existingAuthlifyTokenId = config && config.netlifyGraphConfig && config.netlifyGraphConfig.authlifyTokenId
|
|
59
|
-
if (authlifyTokenId && authlifyTokenId !== existingAuthlifyTokenId) {
|
|
60
|
-
const netlifyToken = await command.authenticate()
|
|
61
|
-
// Only inject the authlify config if a token ID exists. This prevents
|
|
62
|
-
// calling command.authenticate() (which opens a browser window) if the
|
|
63
|
-
// user hasn't enabled API Authentication
|
|
64
|
-
const netlifyGraphConfig = {
|
|
65
|
-
netlifyToken,
|
|
66
|
-
authlifyTokenId: siteData.authlify_token_id,
|
|
67
|
-
siteId: site.id,
|
|
68
|
-
}
|
|
69
|
-
config.netlifyGraphConfig = netlifyGraphConfig
|
|
70
|
-
|
|
71
|
-
const netlifyGraphJWT = generateNetlifyGraphJWT(netlifyGraphConfig)
|
|
72
|
-
|
|
73
|
-
if (netlifyGraphJWT != null) {
|
|
74
|
-
// XXX(anmonteiro): this name is deprecated. Delete after 3/31/2022
|
|
75
|
-
process.env.ONEGRAPH_AUTHLIFY_TOKEN = netlifyGraphJWT
|
|
76
|
-
process.env.NETLIFY_GRAPH_TOKEN = netlifyGraphJWT
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
delete config.authlify
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
setTimeout(helper, frequency)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
await helper(siteInfo)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
48
|
const createHandler = function (options) {
|
|
89
49
|
const { config, functionsRegistry } = options
|
|
90
50
|
|
|
91
|
-
if (options.isGraphEnabled) {
|
|
92
|
-
startPollingForAPIAuthentication(options)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
51
|
return async function handler(request, response) {
|
|
96
52
|
// handle proxies without path re-writes (http-servr)
|
|
97
53
|
const cleanPath = request.path.replace(/^\/.netlify\/(functions|builders)/, '')
|
|
@@ -152,7 +108,7 @@ const createHandler = function (options) {
|
|
|
152
108
|
rawQuery,
|
|
153
109
|
}
|
|
154
110
|
|
|
155
|
-
if (config && config.
|
|
111
|
+
if (config && config.netlifyGraphConfig && config.netlifyGraphConfig.authlifyTokenId != null) {
|
|
156
112
|
// XXX(anmonteiro): this name is deprecated. Delete after 3/31/2022
|
|
157
113
|
const jwt = generateNetlifyGraphJWT(config.netlifyGraphConfig)
|
|
158
114
|
event.authlifyToken = jwt
|
|
@@ -10,8 +10,7 @@ const { GraphQL, InternalConsole, OneGraphClient } = require('netlify-onegraph-i
|
|
|
10
10
|
const { NetlifyGraph } = require('netlify-onegraph-internal')
|
|
11
11
|
|
|
12
12
|
// eslint-disable-next-line no-unused-vars
|
|
13
|
-
const { StateConfig, USER_AGENT, chalk, error, log, warn } = require('../../utils')
|
|
14
|
-
const { watchDebounced } = require('../functions/watcher')
|
|
13
|
+
const { StateConfig, USER_AGENT, chalk, error, log, warn, watchDebounced } = require('../../utils')
|
|
15
14
|
|
|
16
15
|
const {
|
|
17
16
|
generateFunctionsFile,
|
|
@@ -24,7 +23,13 @@ const {
|
|
|
24
23
|
|
|
25
24
|
const { parse } = GraphQL
|
|
26
25
|
const { defaultExampleOperationsDoc, extractFunctionsFromOperationDoc } = NetlifyGraph
|
|
27
|
-
const {
|
|
26
|
+
const {
|
|
27
|
+
createPersistedQuery,
|
|
28
|
+
ensureAppForSite,
|
|
29
|
+
executeMarkCliSessionActiveHeartbeat,
|
|
30
|
+
executeMarkCliSessionInactive,
|
|
31
|
+
updateCLISessionMetadata,
|
|
32
|
+
} = OneGraphClient
|
|
28
33
|
|
|
29
34
|
const internalConsole = {
|
|
30
35
|
log,
|
|
@@ -51,13 +56,31 @@ InternalConsole.registerConsole(internalConsole)
|
|
|
51
56
|
* @param {function} input.onEvents A function to call when CLI events are received and need to be processed
|
|
52
57
|
* @param {string} input.sessionId The session id to monitor CLI events for
|
|
53
58
|
* @param {StateConfig} input.state A function to call to set/get the current state of the local Netlify project
|
|
59
|
+
* @param {any} input.site The site object
|
|
54
60
|
* @returns
|
|
55
61
|
*/
|
|
56
62
|
const monitorCLISessionEvents = (input) => {
|
|
57
|
-
const { appId, netlifyGraphConfig, netlifyToken, onClose, onError, onEvents, sessionId, state } = input
|
|
63
|
+
const { appId, netlifyGraphConfig, netlifyToken, onClose, onError, onEvents, sessionId, site, state } = input
|
|
58
64
|
|
|
59
65
|
const frequency = 5000
|
|
66
|
+
// 30 minutes
|
|
67
|
+
const defaultHeartbeatFrequency = 1_800_000
|
|
60
68
|
let shouldClose = false
|
|
69
|
+
let nextMarkActiveHeartbeat = defaultHeartbeatFrequency
|
|
70
|
+
|
|
71
|
+
const markActiveHelper = async () => {
|
|
72
|
+
const fullSession = await OneGraphClient.fetchCliSession({ authToken: netlifyToken, appId, sessionId })
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
const heartbeatIntervalms = fullSession.session.cliHeartbeatIntervalMs || defaultHeartbeatFrequency
|
|
75
|
+
nextMarkActiveHeartbeat = heartbeatIntervalms
|
|
76
|
+
const markCLISessionActiveResult = await executeMarkCliSessionActiveHeartbeat(netlifyToken, site.id, sessionId)
|
|
77
|
+
if (markCLISessionActiveResult.errors && markCLISessionActiveResult.errors.length !== 0) {
|
|
78
|
+
warn(`Failed to mark CLI session active: ${markCLISessionActiveResult.errors.join(', ')}`)
|
|
79
|
+
}
|
|
80
|
+
setTimeout(markActiveHelper, nextMarkActiveHeartbeat)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setTimeout(markActiveHelper, nextMarkActiveHeartbeat)
|
|
61
84
|
|
|
62
85
|
const enabledServiceWatcher = async (innerNetlifyToken, siteId) => {
|
|
63
86
|
const enabledServices = state.get('oneGraphEnabledServices') || ['onegraph']
|
|
@@ -90,7 +113,7 @@ const monitorCLISessionEvents = (input) => {
|
|
|
90
113
|
const helper = async () => {
|
|
91
114
|
if (shouldClose) {
|
|
92
115
|
clearTimeout(handle)
|
|
93
|
-
onClose()
|
|
116
|
+
onClose && onClose()
|
|
94
117
|
}
|
|
95
118
|
|
|
96
119
|
const next = await OneGraphClient.fetchCliSessionEvents({ appId, authToken: netlifyToken, sessionId })
|
|
@@ -371,6 +394,7 @@ const upsertMergeCLISessionMetadata = async ({ netlifyToken, newMetadata, oneGra
|
|
|
371
394
|
|
|
372
395
|
const detectedMetadata = detectLocalCLISessionMetadata({ siteRoot })
|
|
373
396
|
|
|
397
|
+
// @ts-ignore
|
|
374
398
|
const finalMetadata = { ...metadata, ...detectedMetadata, ...newMetadata }
|
|
375
399
|
return OneGraphClient.updateCLISessionMetadata(netlifyToken, siteId, oneGraphSessionId, finalMetadata)
|
|
376
400
|
}
|
|
@@ -429,24 +453,18 @@ const loadCLISession = (state) => state.get('oneGraphSessionId')
|
|
|
429
453
|
const startOneGraphCLISession = async (input) => {
|
|
430
454
|
const { netlifyGraphConfig, netlifyToken, site, state } = input
|
|
431
455
|
OneGraphClient.ensureAppForSite(netlifyToken, site.id)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
sessionName,
|
|
440
|
-
metadata: sessionMetadata,
|
|
441
|
-
})
|
|
442
|
-
state.set('oneGraphSessionId', oneGraphSession.id)
|
|
443
|
-
oneGraphSessionId = state.get('oneGraphSessionId')
|
|
444
|
-
}
|
|
456
|
+
|
|
457
|
+
const oneGraphSessionId = await ensureCLISession({
|
|
458
|
+
metadata: {},
|
|
459
|
+
netlifyToken,
|
|
460
|
+
site,
|
|
461
|
+
state,
|
|
462
|
+
})
|
|
445
463
|
|
|
446
464
|
const enabledServices = []
|
|
447
465
|
const schema = await OneGraphClient.fetchOneGraphSchema(site.id, enabledServices)
|
|
448
466
|
|
|
449
|
-
monitorOperationFile({
|
|
467
|
+
const opsFileWatcher = monitorOperationFile({
|
|
450
468
|
netlifyGraphConfig,
|
|
451
469
|
onChange: async (filePath) => {
|
|
452
470
|
log('NetlifyGraph operation file changed at', filePath, 'updating function library...')
|
|
@@ -476,11 +494,12 @@ const startOneGraphCLISession = async (input) => {
|
|
|
476
494
|
},
|
|
477
495
|
})
|
|
478
496
|
|
|
479
|
-
monitorCLISessionEvents({
|
|
497
|
+
const cliEventsCloseFn = monitorCLISessionEvents({
|
|
480
498
|
appId: site.id,
|
|
481
499
|
netlifyToken,
|
|
482
500
|
netlifyGraphConfig,
|
|
483
501
|
sessionId: oneGraphSessionId,
|
|
502
|
+
site,
|
|
484
503
|
state,
|
|
485
504
|
onEvents: async (events) => {
|
|
486
505
|
for (const event of events) {
|
|
@@ -494,10 +513,27 @@ const startOneGraphCLISession = async (input) => {
|
|
|
494
513
|
onError: (fetchEventError) => {
|
|
495
514
|
error(`Netlify Graph upstream error: ${fetchEventError}`)
|
|
496
515
|
},
|
|
497
|
-
onClose: () => {
|
|
498
|
-
log('Netlify Graph upstream closed')
|
|
499
|
-
},
|
|
500
516
|
})
|
|
517
|
+
|
|
518
|
+
return async function unregisterWatchers() {
|
|
519
|
+
const watcher = await opsFileWatcher
|
|
520
|
+
watcher.close()
|
|
521
|
+
cliEventsCloseFn()
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Mark a session as inactive so it doesn't show up in any UI lists, and potentially becomes available to GC later
|
|
527
|
+
* @param {object} input
|
|
528
|
+
* @param {string} input.netlifyToken The (typically netlify) access token that is used for authentication, if any
|
|
529
|
+
* @param {string} input.siteId A function to call to set/get the current state of the local Netlify project
|
|
530
|
+
* @param {string} input.sessionId The session id to monitor CLI events for
|
|
531
|
+
*/
|
|
532
|
+
const markCliSessionInactive = async ({ netlifyToken, sessionId, siteId }) => {
|
|
533
|
+
const result = await executeMarkCliSessionInactive(netlifyToken, siteId, sessionId)
|
|
534
|
+
if (result.errors) {
|
|
535
|
+
warn(`Unable to mark CLI session ${sessionId} inactive: ${JSON.stringify(result.errors, null, 2)}`)
|
|
536
|
+
}
|
|
501
537
|
}
|
|
502
538
|
|
|
503
539
|
/**
|
|
@@ -511,6 +547,70 @@ const generateSessionName = () => {
|
|
|
511
547
|
return sessionName
|
|
512
548
|
}
|
|
513
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Ensures a cli session exists for the current checkout, or errors out if it doesn't and cannot create one.
|
|
552
|
+
*/
|
|
553
|
+
const ensureCLISession = async ({ metadata, netlifyToken, site, state }) => {
|
|
554
|
+
let oneGraphSessionId = loadCLISession(state)
|
|
555
|
+
let parentCliSessionId = null
|
|
556
|
+
|
|
557
|
+
// Validate that session still exists and we can access it
|
|
558
|
+
try {
|
|
559
|
+
if (oneGraphSessionId) {
|
|
560
|
+
const sessionEvents = await OneGraphClient.fetchCliSessionEvents({
|
|
561
|
+
appId: site.id,
|
|
562
|
+
authToken: netlifyToken,
|
|
563
|
+
sessionId: oneGraphSessionId,
|
|
564
|
+
})
|
|
565
|
+
if (sessionEvents.errors) {
|
|
566
|
+
warn(`Unable to fetch cli session: ${JSON.stringify(sessionEvents.errors, null, 2)}`)
|
|
567
|
+
log(`Creating new cli session`)
|
|
568
|
+
parentCliSessionId = oneGraphSessionId
|
|
569
|
+
oneGraphSessionId = null
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} catch (fetchSessionError) {
|
|
573
|
+
warn(`Unable to fetch cli session events: ${JSON.stringify(fetchSessionError, null, 2)}`)
|
|
574
|
+
oneGraphSessionId = null
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (!oneGraphSessionId) {
|
|
578
|
+
// If we can't access the session in the state.json or it doesn't exist, create a new one
|
|
579
|
+
const sessionName = generateSessionName()
|
|
580
|
+
const detectedMetadata = detectLocalCLISessionMetadata({ siteRoot: site.root })
|
|
581
|
+
const newSessionMetadata = parentCliSessionId ? { parentCliSessionId } : {}
|
|
582
|
+
const sessionMetadata = {
|
|
583
|
+
...detectedMetadata,
|
|
584
|
+
...newSessionMetadata,
|
|
585
|
+
...metadata,
|
|
586
|
+
}
|
|
587
|
+
const oneGraphSession = await createCLISession({
|
|
588
|
+
netlifyToken,
|
|
589
|
+
siteId: site.id,
|
|
590
|
+
sessionName,
|
|
591
|
+
metadata: sessionMetadata,
|
|
592
|
+
})
|
|
593
|
+
state.set('oneGraphSessionId', oneGraphSession.id)
|
|
594
|
+
oneGraphSessionId = state.get('oneGraphSessionId')
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (!oneGraphSessionId) {
|
|
598
|
+
error('Unable to create or access Netlify Graph CLI session')
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const { errors: markCLISessionActiveErrors } = await executeMarkCliSessionActiveHeartbeat(
|
|
602
|
+
netlifyToken,
|
|
603
|
+
site.id,
|
|
604
|
+
oneGraphSessionId,
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
if (markCLISessionActiveErrors) {
|
|
608
|
+
warn(`Unable to mark cli session active: ${JSON.stringify(markCLISessionActiveErrors, null, 2)}`)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return oneGraphSessionId
|
|
612
|
+
}
|
|
613
|
+
|
|
514
614
|
const OneGraphCliClient = {
|
|
515
615
|
ackCLISessionEvents: OneGraphClient.ackCLISessionEvents,
|
|
516
616
|
createPersistedQuery,
|
|
@@ -522,10 +622,12 @@ const OneGraphCliClient = {
|
|
|
522
622
|
module.exports = {
|
|
523
623
|
OneGraphCliClient,
|
|
524
624
|
createCLISession,
|
|
625
|
+
ensureCLISession,
|
|
525
626
|
extractFunctionsFromOperationDoc,
|
|
526
627
|
handleCliSessionEvent,
|
|
527
628
|
generateSessionName,
|
|
528
629
|
loadCLISession,
|
|
630
|
+
markCliSessionInactive,
|
|
529
631
|
monitorCLISessionEvents,
|
|
530
632
|
persistNewOperationsDocForSession,
|
|
531
633
|
refetchAndGenerateFromOneGraph,
|
|
@@ -217,8 +217,7 @@ const getNetlifyGraphConfig = async ({ command, options, settings }) => {
|
|
|
217
217
|
const moduleType =
|
|
218
218
|
(userSpecifiedConfig.moduleType && userSpecifiedConfig.moduleType.split(path.sep)) ||
|
|
219
219
|
defaultFrameworkConfig.moduleType
|
|
220
|
-
const language =
|
|
221
|
-
(userSpecifiedConfig.language && userSpecifiedConfig.language.split(path.sep)) || autodetectedLanguage
|
|
220
|
+
const language = userSpecifiedConfig.language || autodetectedLanguage
|
|
222
221
|
const webhookBasePath =
|
|
223
222
|
(userSpecifiedConfig.webhookBasePath && userSpecifiedConfig.webhookBasePath.split(path.sep)) ||
|
|
224
223
|
defaultFrameworkConfig.webhookBasePath
|
|
@@ -4,8 +4,12 @@ const process = require('process')
|
|
|
4
4
|
const { format, inspect } = require('util')
|
|
5
5
|
|
|
6
6
|
const { Instance: ChalkInstance } = require('chalk')
|
|
7
|
+
const chokidar = require('chokidar')
|
|
8
|
+
const decache = require('decache')
|
|
7
9
|
const WSL = require('is-wsl')
|
|
10
|
+
const debounce = require('lodash/debounce')
|
|
8
11
|
const { default: omit } = require('omit.js')
|
|
12
|
+
const pEvent = require('p-event')
|
|
9
13
|
|
|
10
14
|
const { name, version } = require('../../package.json')
|
|
11
15
|
const { clearSpinner, startSpinner } = require('../lib/spinner')
|
|
@@ -198,6 +202,34 @@ const normalizeConfig = (config) =>
|
|
|
198
202
|
? { ...config, build: omit(config.build, ['publish', 'publishOrigin']) }
|
|
199
203
|
: config
|
|
200
204
|
|
|
205
|
+
const DEBOUNCE_WAIT = 100
|
|
206
|
+
|
|
207
|
+
const watchDebounced = async (target, { depth, onAdd = () => {}, onChange = () => {}, onUnlink = () => {} }) => {
|
|
208
|
+
const watcher = chokidar.watch(target, { depth, ignored: /node_modules/, ignoreInitial: true })
|
|
209
|
+
|
|
210
|
+
await pEvent(watcher, 'ready')
|
|
211
|
+
|
|
212
|
+
const debouncedOnChange = debounce(onChange, DEBOUNCE_WAIT)
|
|
213
|
+
const debouncedOnUnlink = debounce(onUnlink, DEBOUNCE_WAIT)
|
|
214
|
+
const debouncedOnAdd = debounce(onAdd, DEBOUNCE_WAIT)
|
|
215
|
+
|
|
216
|
+
watcher
|
|
217
|
+
.on('change', (path) => {
|
|
218
|
+
decache(path)
|
|
219
|
+
debouncedOnChange(path)
|
|
220
|
+
})
|
|
221
|
+
.on('unlink', (path) => {
|
|
222
|
+
decache(path)
|
|
223
|
+
debouncedOnUnlink(path)
|
|
224
|
+
})
|
|
225
|
+
.on('add', (path) => {
|
|
226
|
+
decache(path)
|
|
227
|
+
debouncedOnAdd(path)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
return watcher
|
|
231
|
+
}
|
|
232
|
+
|
|
201
233
|
module.exports = {
|
|
202
234
|
BANG,
|
|
203
235
|
chalk,
|
|
@@ -217,4 +249,5 @@ module.exports = {
|
|
|
217
249
|
sortOptions,
|
|
218
250
|
USER_AGENT,
|
|
219
251
|
warn,
|
|
252
|
+
watchDebounced,
|
|
220
253
|
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
const chokidar = require('chokidar')
|
|
3
|
-
const decache = require('decache')
|
|
4
|
-
const debounce = require('lodash/debounce')
|
|
5
|
-
const pEvent = require('p-event')
|
|
6
|
-
|
|
7
|
-
const DEBOUNCE_WAIT = 100
|
|
8
|
-
|
|
9
|
-
const watchDebounced = async (target, { depth, onAdd = () => {}, onChange = () => {}, onUnlink = () => {} }) => {
|
|
10
|
-
const watcher = chokidar.watch(target, { depth, ignored: /node_modules/, ignoreInitial: true })
|
|
11
|
-
|
|
12
|
-
await pEvent(watcher, 'ready')
|
|
13
|
-
|
|
14
|
-
const debouncedOnChange = debounce(onChange, DEBOUNCE_WAIT)
|
|
15
|
-
const debouncedOnUnlink = debounce(onUnlink, DEBOUNCE_WAIT)
|
|
16
|
-
const debouncedOnAdd = debounce(onAdd, DEBOUNCE_WAIT)
|
|
17
|
-
|
|
18
|
-
watcher
|
|
19
|
-
.on('change', (path) => {
|
|
20
|
-
decache(path)
|
|
21
|
-
debouncedOnChange(path)
|
|
22
|
-
})
|
|
23
|
-
.on('unlink', (path) => {
|
|
24
|
-
decache(path)
|
|
25
|
-
debouncedOnUnlink(path)
|
|
26
|
-
})
|
|
27
|
-
.on('add', (path) => {
|
|
28
|
-
decache(path)
|
|
29
|
-
debouncedOnAdd(path)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
return watcher
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
module.exports = { watchDebounced }
|