Package detail

ex-config

chrisblossom83MIT5.0.0

Extendable configuration processor.

config, configuration, extend, extendable

readme

ex-config

npm Linux Build Status Windows Build Status Code Coverage

About

ex-config is an extendable configuration processor. It is used to merge multiple configurations together like babel and eslint configurations do.

Installation

npm install --save ex-config

Usage

'use strict';

const cosmiconfig = require('cosmiconfig');
const {
    /* async: */ exConfig,
    /* sync: */ exConfigSync,
} = require('ex-config');

/**
 * Example using cosmiconfig to load the base config from disk
 */
const explorer = cosmiconfig('example');
const baseConfig = await explorer.load();

/**
 * Original configs are not mutated
 *
 * all options are optional
 *
 * preprocessor, validator, processor, and postProcessor lifecycles all
 *   are given one argument object containing:
 * {
 *   value: new value
 *   current: current value, can be undefined
 *   config: current config
 *   dirname: directory where the preset was loaded from
 * }
 */
const config = await exConfig(
    /* initial config: */ baseConfig,
    /* options: */ {
        /**
         * directory to initially resolve presets/plugins from
         *
         * default: process.cwd()
         */
        baseDirectory: process.cwd(),

        /**
         * Add an API accessible to all lifecycles/presets/plugins
         *
         * default: {}
         */
        api: new (class ApiExample {
            someCustomApiExample() {
                return this;
            }
        })(),

        /**
         * key id that extends configurations via resolve
         * See also, overrides.presets.resolve
         *
         * default: 'presets'
         * set to false to disable
         */
        presets: 'presets',

        /**
         * key id that resolves plugins
         *
         * default: 'plugins'
         * set to false to disable
         */
        plugins: 'plugins',

        /**
         * Pre-process the preset before validation
         *
         * Whatever is returned will be the new value of the current preset
         *
         * Runs once with each preset
         */
        preprocessor: async ({ value, current, config, dirname, api }) => {
            // returned value will be the updated preset value
            return value;
        },

        /**
         * Used to validate a preset. if preset is invalid, throw with an error
         *
         * Runs once with each preset
         */
        validator: async ({ value, current, config, dirname, api }) => {
            if (value !== 'valid') {
                throw new Error('value is invalid');
            }

            // does not expect a return value
            return undefined;
        },

        /**
         * Post Processor runs once after all configurations
         *   have been successfully processed.
         */
        postProcessor: async ({ config, dirname, api }) => {
            // returned value will equal the final config
            return value;
        },

        /**
         * processor runs once per key per preset
         *
         * used merge the value with the overall current value
         *
         * by default, this will deep merge any objects,
         *   push new value onto array, or replace current value
         *
         * other built-in processors are:
         * arrayPush: all values are an array that are appended
         *   to the end of the current array
         *
         * arrayConcat: all values are an array that are added
         *   to the front of the current array
         *
         * mergeDeep: all values are treated as an object
         *
         * Pass a function to use a custom processor
         */
        processor: async ({ value, current = [], config, dirname, api }) => {
            const val = Array.isArray(value) ? value : [value];

            // returned value will be the new value of the key
            return [
                ...current,
                ...val,
            ];
        },

        /**
         * overrides can override/change the settings above
         */
        overrides: {
            presets: {
                resolve: {
                    /**
                     * Prefix to add to packageId
                     *
                     * example: one -> example-preset-one
                     *
                     * Optional
                     *
                     * Accepts a single or an array of prefixes
                     */
                    prefix: 'example-preset',

                    /**
                     * NPM Scope of organization to override prefix
                     *
                     * Optional
                     */
                    org: '@example',

                    /**
                     * Org prefixes
                     *
                     * example: @example/one -> @example/preset-one
                     *
                     * Optional
                     *
                     * Accepts a single or an array of prefixes
                     */
                    orgPrefix: 'preset',

                    /**
                     * Only allow prefixed module resolution.
                     * Explicit modules can be required by prepending module:
                     * For example, module:local-module
                     *
                     * Default: true
                     * Optional
                     */
                    strict: true,
                },
            },

            files: {
                /**
                 * preprocessor override is in addition to the base preprocessor
                 */
                preprocessor: async ({
                    value,
                    current,
                    config,
                    dirname,
                    api,
                }) => {
                    return value;
                },

                /**
                 * validator override will validate in addition to the base validator
                 */
                validator: async ({ value, current, config, dirname, api }) => {
                    if (typeof value.source !== 'string') {
                        throw new Error('files source must be a string');
                    }
                },

                /**
                 * processor override will override the base processor
                 */
                processor: async ({
                    value,
                    current = [],
                    config,
                    dirname,
                    api,
                }) => {
                    const val = Array.isArray(value) ? value : [value];

                    return [
                        ...val,
                        ...current,
                    ];
                },
            },
        },
    },
);

Creating Presets and Plugins

A preset or plugin needs to export an object or function.

Only export default is supported when using es modules.

If a function is used it will be invoked with the following argument:

// preset-example.js
function presetExample(context) {
    // options given as second index when calling preset/plugin
    const options = context.options;
    // dirname package was found from
    const dirname = context.dirname;
    // api provided in options
    const api = context.api;

    return {
        verbose: options.verbose,
    };
}

module.exports = presetExample;

// config.js
const exampleConfig = {
    presets: [
        [
            // packageId
            'preset-example',
            // options for preset/plugin
            { verbose: true },
        ],
    ],
};

module.exports = exampleConfig;

Thanks To

This package was created with the great work / lessons learned from:

changelog

Change Log

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

[Unreleased]

[4.0.0] - 2019-05-18

  • Breaking: drop node 6 support

[3.0.0] - 2019-05-02

Added

  • Added sync export exConfigSync
  • Added api option

Changed

  • Breaking: exConfig is now async

[2.1.0] - 2019-04-29

Added

  • Added baseDirectory option

[2.0.0] - 2019-04-27

  • Add named export: exConfig
  • Breaking: remove default export
  • Breaking: Remove class and new ExConfig
  • Internal refactor

[1.1.0] - 2019-04-05

Changed

[1.0.12] - 2019-01-02

Changed

  • Internal: Use backtrack to manage build environment
  • package updates

[1.0.9] - 2018-05-17

Fixed

  • when base config is a function, options is now an empty object

[1.0.8] - 2018-05-15

Fixed

  • handle base config as function

[1.0.6] - 2018-05-09

Changed

  • package updates

Fixed

[1.0.5] - 2018-04-02

Added

  • presets and plugins can now be expressed as ['PACKAGE_ID', OPTIONS].
  • handle es module default exports

[1.0.4] - 2018-04-02

  • flow type updates

[1.0.3] - 2018-03-31

Changed

  • cherry pick lodash functions
  • refactor tests to remove normalizeRootPath

[1.0.2] - 2018-03-18

Changed

  • package updates