Skip to content

Commit

Permalink
feat: netlify dev --graph: watch config.toml and reload automatical…
Browse files Browse the repository at this point in the history
…ly (#4268)

* feat: `netlify dev --graph`: watch config.toml and reload automatically

* chore: update contributors field

* fix: don't set `netlify.config` globally

* fix: set the incoming config when running `ntl dev --graph`

* chore: move `watchDebounced` to utils/command-helpers

* chore: give return function a name for explicitness

* fix: close the config watcher so that Node can exit safely

* fix: not all commands will have configPath

Co-authored-by: Erez Rokah <erezrokah@users.noreply.github.com>
  • Loading branch information
anmonteiro and erezrokah authored Feb 14, 2022
1 parent fa3980d commit d961855
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 60 deletions.
27 changes: 27 additions & 0 deletions src/commands/base-command.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @ts-check
const events = require('events')
const process = require('process')
const { format } = require('util')

Expand Down Expand Up @@ -28,6 +29,7 @@ const {
pollForToken,
sortOptions,
track,
watchDebounced,
} = require('../utils')

// Netlify CLI client id. Lives in bot@netlify.com
Expand Down Expand Up @@ -119,6 +121,11 @@ class BaseCommand extends Command {
await this.init(actionCommand)
debug(`${name}:preAction`)('end')
})
.hook('postAction', async () => {
if (this.configWatcherHandle) {
await this.configWatcherHandle.close()
}
})
)
}

Expand Down Expand Up @@ -430,6 +437,24 @@ class BaseCommand extends Command {
const globalConfig = await getGlobalConfig()
const { NetlifyAPI } = await jsClient

const configWatcher = new events.EventEmitter()

// Only set up a watcher if we know the config path.
if (configPath) {
const configWatcherHandle = await watchDebounced(configPath, {
depth: 1,
onChange: async () => {
const { config: newConfig } = await actionCommand.getConfig({ cwd, state, token, ...apiUrlOpts })

const normalizedNewConfig = normalizeConfig(newConfig)
configWatcher.emit('change', normalizedNewConfig)
},
})

// chokidar handler
this.configWatcherHandle = configWatcherHandle
}

actionCommand.netlify = {
// api methods
api: new NetlifyAPI(token || '', apiOpts),
Expand All @@ -455,6 +480,8 @@ class BaseCommand extends Command {
globalConfig,
// state of current site dir
state,
// netlify.toml file watcher
configWatcher,
}
debug(`${this.name()}:init`)('end')
}
Expand Down
46 changes: 31 additions & 15 deletions src/commands/dev/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,28 +346,44 @@ const dev = async (options, command) => {
} else if (startNetlifyGraphWatcher) {
const netlifyToken = await command.authenticate()
await OneGraphCliClient.ensureAppForSite(netlifyToken, site.id)
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options, settings })

let graphqlDocument = readGraphQLOperationsSourceFile(netlifyGraphConfig)
let stopWatchingCLISessions

if (!graphqlDocument || graphqlDocument.trim().length === 0) {
graphqlDocument = defaultExampleOperationsDoc
}
const createOrResumeSession = async function () {
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options, settings })

let graphqlDocument = readGraphQLOperationsSourceFile(netlifyGraphConfig)

await startOneGraphCLISession({ netlifyGraphConfig, netlifyToken, site, state })
if (!graphqlDocument || graphqlDocument.trim().length === 0) {
graphqlDocument = defaultExampleOperationsDoc
}

// Should be created by startOneGraphCLISession
const oneGraphSessionId = loadCLISession(state)
stopWatchingCLISessions = await startOneGraphCLISession({ netlifyGraphConfig, netlifyToken, site, state })

await persistNewOperationsDocForSession({
netlifyGraphConfig,
netlifyToken,
oneGraphSessionId,
operationsDoc: graphqlDocument,
siteId: site.id,
siteRoot: site.root,
// Should be created by startOneGraphCLISession
const oneGraphSessionId = loadCLISession(state)

await persistNewOperationsDocForSession({
netlifyGraphConfig,
netlifyToken,
oneGraphSessionId,
operationsDoc: graphqlDocument,
siteId: site.id,
siteRoot: site.root,
})

return oneGraphSessionId
}

//
// Set up a handler for config changes.
command.netlify.configWatcher.on('change', (newConfig) => {
command.netlify.config = newConfig
stopWatchingCLISessions()
createOrResumeSession()
})

const oneGraphSessionId = await createOrResumeSession()
const cleanupSession = () => markCliSessionInactive({ netlifyToken, sessionId: oneGraphSessionId, siteId: site.id })

cleanupWork.push(cleanupSession)
Expand Down
3 changes: 1 addition & 2 deletions src/lib/functions/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ const { env } = require('process')

const terminalLink = require('terminal-link')

const { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, log, warn } = require('../../utils')
const { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, log, warn, watchDebounced } = require('../../utils')
const { getLogMessage } = require('../log')

const { NetlifyFunction } = require('./netlify-function')
const runtimes = require('./runtimes')
const { watchDebounced } = require('./watcher')

const ZIP_EXTENSION = '.zip'

Expand Down
35 changes: 0 additions & 35 deletions src/lib/functions/watcher.js

This file was deleted.

18 changes: 10 additions & 8 deletions src/lib/one-graph/cli-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ const { GraphQL, InternalConsole, OneGraphClient } = require('netlify-onegraph-i
const { NetlifyGraph } = require('netlify-onegraph-internal')

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

const {
generateFunctionsFile,
Expand Down Expand Up @@ -114,7 +113,7 @@ const monitorCLISessionEvents = (input) => {
const helper = async () => {
if (shouldClose) {
clearTimeout(handle)
onClose()
onClose && onClose()
}

const next = await OneGraphClient.fetchCliSessionEvents({ appId, authToken: netlifyToken, sessionId })
Expand Down Expand Up @@ -465,7 +464,7 @@ const startOneGraphCLISession = async (input) => {
const enabledServices = []
const schema = await OneGraphClient.fetchOneGraphSchema(site.id, enabledServices)

monitorOperationFile({
const opsFileWatcher = monitorOperationFile({
netlifyGraphConfig,
onChange: async (filePath) => {
log('NetlifyGraph operation file changed at', filePath, 'updating function library...')
Expand Down Expand Up @@ -495,7 +494,7 @@ const startOneGraphCLISession = async (input) => {
},
})

monitorCLISessionEvents({
const cliEventsCloseFn = monitorCLISessionEvents({
appId: site.id,
netlifyToken,
netlifyGraphConfig,
Expand All @@ -514,10 +513,13 @@ const startOneGraphCLISession = async (input) => {
onError: (fetchEventError) => {
error(`Netlify Graph upstream error: ${fetchEventError}`)
},
onClose: () => {
log('Netlify Graph upstream closed')
},
})

return async function unregisterWatchers() {
const watcher = await opsFileWatcher
watcher.close()
cliEventsCloseFn()
}
}

/**
Expand Down
33 changes: 33 additions & 0 deletions src/utils/command-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ const process = require('process')
const { format, inspect } = require('util')

const { Instance: ChalkInstance } = require('chalk')
const chokidar = require('chokidar')
const decache = require('decache')
const WSL = require('is-wsl')
const debounce = require('lodash/debounce')
const { default: omit } = require('omit.js')
const pEvent = require('p-event')

const { name, version } = require('../../package.json')
const { clearSpinner, startSpinner } = require('../lib/spinner')
Expand Down Expand Up @@ -198,6 +202,34 @@ const normalizeConfig = (config) =>
? { ...config, build: omit(config.build, ['publish', 'publishOrigin']) }
: config

const DEBOUNCE_WAIT = 100

const watchDebounced = async (target, { depth, onAdd = () => {}, onChange = () => {}, onUnlink = () => {} }) => {
const watcher = chokidar.watch(target, { depth, ignored: /node_modules/, ignoreInitial: true })

await pEvent(watcher, 'ready')

const debouncedOnChange = debounce(onChange, DEBOUNCE_WAIT)
const debouncedOnUnlink = debounce(onUnlink, DEBOUNCE_WAIT)
const debouncedOnAdd = debounce(onAdd, DEBOUNCE_WAIT)

watcher
.on('change', (path) => {
decache(path)
debouncedOnChange(path)
})
.on('unlink', (path) => {
decache(path)
debouncedOnUnlink(path)
})
.on('add', (path) => {
decache(path)
debouncedOnAdd(path)
})

return watcher
}

module.exports = {
BANG,
chalk,
Expand All @@ -217,4 +249,5 @@ module.exports = {
sortOptions,
USER_AGENT,
warn,
watchDebounced,
}

1 comment on commit d961855

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Benchmark results

Package size: 442 MB

Please sign in to comment.