cli icon indicating copy to clipboard operation
cli copied to clipboard

Create global configuration store

Open eduardoboucas opened this issue 2 years ago • 0 comments

Reading the Netlify configuration file is a very common problem throughout the CLI codebase. In some cases, it's sufficient to read the initial value of the config (i.e. the state of the config when the application starts), but in other cases it's important to watch for any updates and update the application accordingly.

We currently do this in a few places in a non-consistent way. For example, we have this watcher, using a callback pattern, that is looking for updates to headers and redirects. When netlify.toml is updated, this is printed in the console (even if there were no actual changes to headers or redirects):

◈ Reloading headers files from [ 'netlify.toml' ]
◈ Reloading redirect rules from [ 'netlify.toml' ]

Separately, the Dev command has its own config watcher, using an event-driven pattern. This is tightly coupled with the Graph functionality though, making it difficult to reuse it for more generic applications.

I propose that we create a consistent way of solving this problem by standing up a global configuration store. This store would expose an API with the following characteristics:

Returning a config value using a declarative way

The store would keep a cached version of the config, parsed and normalised, which it would update automatically when it detects a change in the configuration file.

const { config } = command.netlify

const buildCommand = await config.get("build.command")

However, certain configuration values can be set remotely (e.g. in the UI), and the config store wouldn't be able to easily receive updates of those changes.

So the .get() command accepts an optional parameter that should be used when the consumer requires a fresh value that may come from a remote API, forcing the config store to fetch it as opposed to using the cached version.

const { config } = command.netlify

const buildCommand = await config.get("build.command", { refresh: true })

Listening for changes using events

The store emits a change even every time it detects a change in the local configuration file, and it allows consumers to subscribe to them.

const { config } = command.netlify

config.on('change', () => {
  const newBuildCommand = await config.get("build.command")
})

To make user messaging more useful, the config store should compute the diff between the old config and the new config (using something like https://www.npmjs.com/package/deep-object-diff) and expose that as a parameter in the change event. With this, consumers can adjust their messaging according to what actually changed.

For example, the headers and redirects message above could be tweaked so that it's only printed if headers or redirects have changed.

const { config } = command.netlify

config.on('change', (diff) => {
  if (diff.headers) {
    console.log('◈ Reloaded headers from netlify.toml')
  }

  if (diff.redirects) {
    console.log('◈ Reloaded redirects from netlify.toml')
  }
})

eduardoboucas avatar Apr 11 '22 14:04 eduardoboucas