NB: this is an internal RFC related to the code structure and patterns of the CLI and its dependencies. No user-facing changes are contemplated by this proposal.
- Replace usage of
figgy-pudding
with plain JavaScript option objects. - Document and define defaults for configs used by each module in the stack.
- Clone options objects if they may be mutated in the future.
- Pass options objects intact to components further down the stack.
- Canonicalize option names to match consistent
camelCase
format.
The goal of FiggyPudding was to get the npm CLI and its associated
dependencies away from having a shared npmconf
god-object with .get()
and .set()
methods, which could be mutated at any time, and contain any
arbitrary fields.
However, the use of FiggyPudding has led in practice to added complexity
and an even more entrenched reliance on a god-object pattern, albeit one
that is even more opinionated and harder to use than npmconf
was.
In one example where this has proven particularly challenging, npm ci
does not get the full set of config values, and thus it does not properly
respect config values that would be handled if the user ran npm install
.
Since all package tree generation and reification will be handled by an
external dependency in npm v7 (ie, @npmcli/arborist
), this will become an
even bigger issue.
In all of the CLI's dependencies, remove figgy-pudding
, and replace with
an options object that uses the canonical names for npm config values.
In the npm CLI, create an options object with these canonically named
fields, applying defaults and resolving aliases, as appropriate, using the
npm.config
object as its source of truth.
Use the camelCase
forms of all config fields. For example, if the user
runs npm install --prefer-online
, then the config options object would
contain { preferOnline: true }
.
Thus, the npm CLI's dependencies should use options.preferOnline
instead
of options['prefer-online']
.
It's worth noting the problem that figgy-pudding aims to solve and the value it currently provides, and ways that we will continue to solve that problem and gain that value.
- A figgy-pudding config object is a self-documenting list of the options that a given module uses, with defaults, canonical name, and so on.
- Figgy-pudding allows a module to have a different set of respected configs than it provides to the modules deeper in the stack, because they all proxy to the same underlying data object.
- Figgy-pudding prevents any accidental mutation of the options provided to a module.
- Use of figgy-pudding avoids tight coupling to an
npmconf
object withget
andset
methods.
The response to these points is:
-
It is better to document the list of options that a module uses in its documentation, and make it clear in the code what their default values are. For example, instead of:
const MyOptions = figgyPudding({ someFoo: { default: 'foo' }, myBar: {}, }) module.exports = (options) => { options = MyOptions(options) // ...options.someFoo }
We will now do:
module.exports = (options = {}) => { const { someFoo = 'foo' } = options // doSomething(someFoo) }
and document the
someFoo
field in the packageREADME.md
. -
When a module in the CLI dependency stack needs to call out to one lower down (for example, arborist calling pacote, which in turn calls npm-registry-fetch, which then calls make-fetch-happen, etc.), we can just pass the options object to the deeper dep.
It's fine, really.
-
When an options object might be mutated, it should be cloned first. For example, instead of:
class Foo { constructor (options) { this.options = options // ... later ... this.options.foo = 'foo' } }
we will now do this:
class Foo { constructor (options = {}) { this.options = { ...options } } }
-
We achieve the same benefit by passing plain old JavaScript objects as arguments rather than sharing an npmconf object. The
npm.config
object will only be used to create the config object that gets passed to dependencies, it will never be shared directly.
Some alternatives:
One alternative is to update tar, pacote v10, and arborist to use figgy-pudding as well, and fix the issues that make it a challenge in other scenarios.
It is our assessment that doing so involves more work than simply removing figgy pudding from the CLI entirely, and solving the problems in a different way.
We could update figgy-pudding to be less of a pain to work with. However, we would remain in a mode of having a very unusual way of passing around option arguments to functions and classes, which runs against the grain of JavaScript development. And, reducing the restrictions of figgy-pudding would tend to also reduce its benefits.
Ultimately, we don't need something to enforce config object and documentation discipline, we just need to be disciplined about config object usage and documentation.
Note: all of this is planned for npm v7. It has no effect on npm v6, or any versions of any deps used by npm v6 and before.
While making changes to deps and the CLI, update documentation to specify which config values are relevant, and consider adding tests to ensure that provided config objects cannot be inappropriately mutated.
- Create a list of all the config names and their canonical names.
- Update the CLI to create this object and pass it to dependencies instead of
the figgy-pudding object currently created in
lib/config/figgy-config.js
.
This will require updating some cases where css-case config keys are used rather than camelCase, but otherwise, should be relatively straightforward. Anything that takes a FiggyPudding object can also take a regular object as an argument.
- Create semver-major updates to
ssri
,npm-registry-fetch
,npm-pick-manifest
, andcacache
to take a plain old config object instead of a figgy-pudding object, and use canonical config keys. - Update direct usage of these within the CLI to provide the plain JavaScript object instead of the FiggyConfig object.
- Update
npm-profile
and all of thelibnpm*
dependencies (except forlibcipm
, which will be removed in npm v7 and replaced by arborist) to use the canonicalized pojo config object, and pass the same downstack to their deps.