code:
'use strict';

const Types = require('./types');

const internals = {
    mismatched: null
};

module.exports = function (obj, ref, options) {

    options = Object.assign({ prototype: true }, options);

    return !!internals.isDeepEqual(obj, ref, options, []);
};

internals.isDeepEqual = function (obj, ref, options, seen) {

    if (obj === ref) {                                                      // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
        return obj !== 0 || 1 / obj === 1 / ref;
    }

    const type = typeof obj;

    if (type !== typeof ref) {
        return false;
    }

    if (obj === null ||
        ref === null) {

        return false;
    }

    if (type === 'function') {
        if (!options.deepFunction ||
            obj.toString() !== ref.toString()) {

            return false;
        }

        // Continue as object
    }
    else if (type !== 'object') {
        return obj !== obj && ref !== ref;                                  // NaN
    }

    const instanceType = internals.getSharedType(obj, ref, !!options.prototype);
    switch (instanceType) {
        case Types.buffer:
            return Buffer && Buffer.prototype.equals.call(obj, ref);        // $lab:coverage:ignore$
        case Types.promise:
            return obj === ref;
        case Types.regex:
            return obj.toString() === ref.toString();
        case internals.mismatched:
            return false;
    }

    for (let i = seen.length - 1; i >= 0; --i) {
        if (seen[i].isSame(obj, ref)) {
            return true;                                                    // If previous comparison failed, it would have stopped execution
        }
    }

    seen.push(new internals.SeenEntry(obj, ref));

    try {
        return !!internals.isDeepEqualObj(instanceType, obj, ref, options, seen);
    }
    finally {
        seen.pop();
    }
};

internals.getSharedType = function (obj, ref, checkPrototype) {

    if (checkPrototype) {
        if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
            return internals.mismatched;
        }

        return Types.getInternalProto(obj);
    }

    const type = Types.getInternalProto(obj);
    if (type !== Types.getInternalProto(ref)) {
        return internals.mismatched;
    }

    return type;
};

internals.valueOf = function (obj) {

    const objValueOf = obj.valueOf;
    if (objValueOf === undefined) {
        return obj;
    }

    try {
        return objValueOf.call(obj);
    }
    catch (err) {
        return err;
    }
};

internals.hasOwnEnumerableProperty = function (obj, key) {

    return Object.prototype.propertyIsEnumerable.call(obj, key);
};

internals.isSetSimpleEqual = function (obj, ref) {

    for (const entry of Set.prototype.values.call(obj)) {
        if (!Set.prototype.has.call(ref, entry)) {
            return false;
        }
    }

    return true;
};

internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {

    const { isDeepEqual, valueOf, hasOwnEnumerableProperty } = internals;
    const { keys, getOwnPropertySymbols } = Object;

    if (instanceType === Types.array) {
        if (options.part) {

            // Check if any index match any other index

            for (const objValue of obj) {
                for (const refValue of ref) {
                    if (isDeepEqual(objValue, refValue, options, seen)) {
                        return true;
                    }
                }
            }
        }
        else {
            if (obj.length !== ref.length) {
                return false;
            }

            for (let i = 0; i < obj.length; ++i) {
                if (!isDeepEqual(obj[i], ref[i], options, seen)) {
                    return false;
                }
            }

            return true;
        }
    }
    else if (instanceType === Types.set) {
        if (obj.size !== ref.size) {
            return false;
        }

        if (!internals.isSetSimpleEqual(obj, ref)) {

            // Check for deep equality

            const ref2 = new Set(Set.prototype.values.call(ref));
            for (const objEntry of Set.prototype.values.call(obj)) {
                if (ref2.delete(objEntry)) {
                    continue;
                }

                let found = false;
                for (const refEntry of ref2) {
                    if (isDeepEqual(objEntry, refEntry, options, seen)) {
                        ref2.delete(refEntry);
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    return false;
                }
            }
        }
    }
    else if (instanceType === Types.map) {
        if (obj.size !== ref.size) {
            return false;
        }

        for (const [key, value] of Map.prototype.entries.call(obj)) {
            if (value === undefined && !Map.prototype.has.call(ref, key)) {
                return false;
            }

            if (!isDeepEqual(value, Map.prototype.get.call(ref, key), options, seen)) {
                return false;
            }
        }
    }
    else if (instanceType === Types.error) {

        // Always check name and message

        if (obj.name !== ref.name ||
            obj.message !== ref.message) {

            return false;
        }
    }

    // Check .valueOf()

    const valueOfObj = valueOf(obj);
    const valueOfRef = valueOf(ref);
    if ((obj !== valueOfObj || ref !== valueOfRef) &&
        !isDeepEqual(valueOfObj, valueOfRef, options, seen)) {

        return false;
    }

    // Check properties

    const objKeys = keys(obj);
    if (!options.part &&
        objKeys.length !== keys(ref).length &&
        !options.skip) {

        return false;
    }

    let skipped = 0;
    for (const key of objKeys) {
        if (options.skip &&
            options.skip.includes(key)) {

            if (ref[key] === undefined) {
                ++skipped;
            }

            continue;
        }

        if (!hasOwnEnumerableProperty(ref, key)) {
            return false;
        }

        if (!isDeepEqual(obj[key], ref[key], options, seen)) {
            return false;
        }
    }

    if (!options.part &&
        objKeys.length - skipped !== keys(ref).length) {

        return false;
    }

    // Check symbols

    if (options.symbols !== false) {                                // Defaults to true
        const objSymbols = getOwnPropertySymbols(obj);
        const refSymbols = new Set(getOwnPropertySymbols(ref));

        for (const key of objSymbols) {
            if (!options.skip ||
                !options.skip.includes(key)) {

                if (hasOwnEnumerableProperty(obj, key)) {
                    if (!hasOwnEnumerableProperty(ref, key)) {
                        return false;
                    }

                    if (!isDeepEqual(obj[key], ref[key], options, seen)) {
                        return false;
                    }
                }
                else if (hasOwnEnumerableProperty(ref, key)) {
                    return false;
                }
            }

            refSymbols.delete(key);
        }

        for (const key of refSymbols) {
            if (hasOwnEnumerableProperty(ref, key)) {
                return false;
            }
        }
    }

    return true;
};

internals.SeenEntry = class {

    constructor(obj, ref) {

        this.obj = obj;
        this.ref = ref;
    }

    isSame(obj, ref) {

        return this.obj === obj && this.ref === ref;
    }
};


code:
'use strict';

const Stringify = require('./stringify');

const internals = {};

module.exports = class extends Error {

    constructor(args) {

        const msgs = args
            .filter((arg) => arg !== '')
            .map((arg) => {

                return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);
            });

        super(msgs.join(' ') || 'Unknown error');

        if (typeof Error.captureStackTrace === 'function') {            // $lab:coverage:ignore$
            Error.captureStackTrace(this, exports.assert);
        }
    }
};

license: mit


code:
'use strict';

const Assert = require('./assert');

const internals = {};

module.exports = function (attribute) {

    // Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "

    Assert(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), 'Bad attribute value (' + attribute + ')');

    return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"');                             // Escape quotes and slash
};


code:
'use strict';

const internals = {};

module.exports = function (input) {

    if (!input) {
        return '';
    }

    let escaped = '';

    for (let i = 0; i < input.length; ++i) {

        const charCode = input.charCodeAt(i);

        if (internals.isSafe(charCode)) {
            escaped += input[i];
        }
        else {
            escaped += internals.escapeHtmlChar(charCode);
        }
    }

    return escaped;
};

internals.escapeHtmlChar = function (charCode) {

    const namedEscape = internals.namedHtml.get(charCode);
    if (namedEscape) {
        return namedEscape;
    }

    if (charCode >= 256) {
        return '&#' + charCode + ';';
    }

    const hexValue = charCode.toString(16).padStart(2, '0');
    return `&#x${hexValue};`;
};

internals.isSafe = function (charCode) {

    return internals.safeCharCodes.has(charCode);
};

internals.namedHtml = new Map([
    [38, '&'],
    [60, '<'],
    [62, '>'],
    [34, '"'],
    [160, ' '],
    [162, '¢'],
    [163, '£'],
    [164, '¤'],
    [169, '©'],
    [174, '®']
]);

internals.safeCharCodes = (function () {

    const safe = new Set();

    for (let i = 32; i < 123; ++i) {

        if ((i >= 97) ||                    // a-z
            (i >= 65 && i <= 90) ||         // A-Z
            (i >= 48 && i <= 57) ||         // 0-9
            i === 32 ||                     // space
            i === 46 ||                     // .
            i === 44 ||                     // ,
            i === 45 ||                     // -
            i === 58 ||                     // :
            i === 95) {                     // _

            safe.add(i);
        }
    }

    return safe;
}());


code:
'use strict';

const internals = {};

module.exports = function (input) {

    if (!input) {
        return '';
    }

    return input.replace(/[<>&\u2028\u2029]/g, internals.escape);
};

internals.escape = function (char) {

    return internals.replacements.get(char);
};

internals.replacements = new Map([
    ['<', '\\u003c'],
    ['>', '\\u003e'],
    ['&', '\\u0026'],
    ['\u2028', '\\u2028'],
    ['\u2029', '\\u2029']
]);


code:
'use strict';

const internals = {};

module.exports = internals.flatten = function (array, target) {

    const result = target || [];

    for (const entry of array) {
        if (Array.isArray(entry)) {
            internals.flatten(entry, result);
        }
        else {
            result.push(entry);
        }
    }

    return result;
};


code:
'use strict';

const internals = {};

module.exports = function () { };


code:
/// <reference types="node" />

/**
 * Performs a deep comparison of the two values including support for circular dependencies, prototype, and enumerable properties.
 *
 * @param obj - The value being compared.
 * @param ref - The reference value used for comparison.
 *
 * @return true when the two values are equal, otherwise false.
 */
export function deepEqual(obj: any, ref: any, options?: deepEqual.Options): boolean;

export namespace deepEqual {

    interface Options {

        /**
         * Compare functions with difference references by comparing their internal code and properties.
         *
         * @default false
         */
        readonly deepFunction?: boolean;

        /**
         * Allow partial match.
         *
         * @default false
         */
        readonly part?: boolean;

        /**
         * Compare the objects' prototypes.
         *
         * @default true
         */
        readonly prototype?: boolean;

        /**
         * List of object keys to ignore different values of.
         *
         * @default null
         */
        readonly skip?: (string | symbol)[];

        /**
         * Compare symbol properties.
         *
         * @default true
         */
        readonly symbols?: boolean;
    }
}

/**
 * Clone any value, object, or array.
 *
 * @param obj - The value being cloned.
 * @param options - Optional settings.
 *
 * @returns A deep clone of `obj`.
 */
export function clone<T>(obj: T, options?: clone.Options): T;

export namespace clone {

    interface Options {

        /**
         * Clone the object's prototype.
         *
         * @default true
         */
        readonly prototype?: boolean;

        /**
         * Include symbol properties.
         *
         * @default true
         */
        readonly symbols?: boolean;

        /**
         * Shallow clone the specified keys.
         *
         * @default undefined
         */
        readonly shallow?: string[] | string[][] | boolean;
    }
}

/**
 * Merge all the properties of source into target.
 *
 * @param target - The object being modified.
 * @param source - The object used to copy properties from.
 * @param options - Optional settings.
 *
 * @returns The `target` object.
 */
export function merge<T1 extends object, T2 extends object>(target: T1, source: T2, options?: merge.Options): T1 & T2;

export namespace merge {

    interface Options {

        /**
         * When true, null value from `source` overrides existing value in `target`.
         *
         * @default true
         */
        readonly nullOverride?: boolean;

        /**
         * When true, array value from `source` is merged with the existing value in `target`.
         *
         * @default false
         */
        readonly mergeArrays?: boolean;

        /**
         * Compare symbol properties.
         *
         * @default true
         */
        readonly symbols?: boolean;
    }
}

/**
 * Apply source to a copy of the defaults.
 *
 * @param defaults - An object with the default values to use of `options` does not contain the same keys.
 * @param source - The source used to override the `defaults`.
 * @param options - Optional settings.
 *
 * @returns A copy of `defaults` with `source` keys overriding any conflicts.
 */
export function applyToDefaults<T extends object>(defaults: Partial<T>, source: Partial<T> | boolean | null, options?: applyToDefaults.Options): Partial<T>;

export namespace applyToDefaults {

    interface Options {

        /**
         * When true, null value from `source` overrides existing value in `target`.
         *
         * @default true
         */
        readonly nullOverride?: boolean;

        /**
         * Shallow clone the specified keys.
         *
         * @default undefined
         */
        readonly shallow?: string[] | string[][];
    }
}

/**
 * Find the common unique items in two arrays.
 *
 * @param array1 - The first array to compare.
 * @param array2 - The second array to compare.
 * @param options - Optional settings.
 *
 * @return - An array of the common items. If `justFirst` is true, returns the first common item.
 */
export function intersect<T1, T2>(array1: intersect.Array<T1>, array2: intersect.Array<T2>, options?: intersect.Options): Array<T1 | T2>;
export function intersect<T1, T2>(array1: intersect.Array<T1>, array2: intersect.Array<T2>, options?: intersect.Options): T1 | T2;

export namespace intersect {

    type Array<T> = ArrayLike<T> | Set<T> | null;

    interface Options {

        /**
         * When true, return the first overlapping value.
         *
         * @default false
         */
        readonly first?: boolean;
    }
}

/**
 * Checks if the reference value contains the provided values.
 *
 * @param ref - The reference string, array, or object.
 * @param values - A single or array of values to find within `ref`. If `ref` is an object, `values` can be a key name, an array of key names, or an object with key-value pairs to compare.
 *
 * @return true if the value contains the provided values, otherwise false.
 */
export function contain(ref: string, values: string | string[], options?: contain.Options): boolean;
export function contain(ref: any[], values: any, options?: contain.Options): boolean;
export function contain(ref: object, values: string | string[] | object, options?: Omit<contain.Options, 'once'>): boolean;

export namespace contain {

    interface Options {

        /**
         * Perform a deep comparison.
         *
         * @default false
         */
        readonly deep?: boolean;

        /**
         * Allow only one occurrence of each value.
         *
         * @default false
         */
        readonly once?: boolean;

        /**
         * Allow only values explicitly listed.
         *
         * @default false
         */
        readonly only?: boolean;

        /**
         * Allow partial match.
         *
         * @default false
         */
        readonly part?: boolean;

        /**
         * Include symbol properties.
         *
         * @default true
         */
        readonly symbols?: boolean;
    }
}

/**
 * Flatten an array with sub arrays
 *
 * @param array - an array of items or other arrays to flatten.
 * @param target - if provided, an array to shallow copy the flattened `array` items to
 *
 * @return a flat array of the provided values (appended to `target` is provided).
 */
export function flatten<T>(array: ArrayLike<T | ReadonlyArray<T>>, target?: ArrayLike<T | ReadonlyArray<T>>): T[];

/**
 * Convert an object key chain string to reference.
 *
 * @param obj - the object from which to look up the value.
 * @param chain - the string path of the requested value. The chain string is split into key names using `options.separator`, or an array containing each individual key name. A chain including negative numbers will work like a negative index on an array.
 *
 * @return The value referenced by the chain if found, otherwise undefined. If chain is null, undefined, or false, the object itself will be returned.
 */
export function reach(obj: object | null, chain: string | (string | number)[] | false | null | undefined, options?: reach.Options): any;

export namespace reach {

    interface Options {

        /**
         * String to split chain path on. Defaults to '.'.
         *
         * @default false
         */
        readonly separator?: string;

        /**
         * Value to return if the path or value is not present. No default value.
         *
         * @default false
         */
        readonly default?: any;

        /**
         * If true, will throw an error on missing member in the chain. Default to false.
         *
         * @default false
         */
        readonly strict?: boolean;

        /**
         * If true, allows traversing functions for properties. false will throw an error if a function is part of the chain.
         *
         * @default true
         */
        readonly functions?: boolean;

        /**
         * If true, allows traversing Set and Map objects for properties. false will return undefined regardless of the Set or Map passed.
         *
         * @default false
         */
        readonly iterables?: boolean;
    }
}

/**
 * Replace string parameters (using format "{path.to.key}") with their corresponding object key values using `Hoek.reach()`.
 *
 * @param obj - the object from which to look up the value.
 * @param template - the string containing {} enclosed key paths to be replaced.
 *
 * @return The template string with the {} enclosed keys replaced with looked-up values.
 */
export function reachTemplate(obj: object | null, template: string, options?: reach.Options): string;

/**
 * Throw an error if condition is falsy.
 *
 * @param condition - If `condition` is not truthy, an exception is thrown.
 * @param error - The error thrown if the condition fails.
 *
 * @return Does not return a value but throws if the `condition` is falsy.
 */
export function assert(condition: any, error: Error): void;

/**
 * Throw an error if condition is falsy.
 *
 * @param condition - If `condition` is not truthy, an exception is thrown.
 * @param args - Any number of values, concatenated together (space separated) to create the error message.
 *
 * @return Does not return a value but throws if the `condition` is falsy.
 */
export function assert(condition: any, ...args: any): void;

/**
 * A benchmarking timer, using the internal node clock for maximum accuracy.
 */
export class Bench {

    constructor();

    /** The starting timestamp expressed in the number of milliseconds since the epoch. */
    ts: number;

    /** The time in milliseconds since the object was created. */
    elapsed(): number;

    /** Reset the `ts` value to now. */
    reset(): void;

    /** The current time in milliseconds since the epoch. */
    static now(): number;
}

/**
 * Escape string for Regex construction by prefixing all reserved characters with a backslash.
 *
 * @param string - The string to be escaped.
 *
 * @return The escaped string.
 */
export function escapeRegex(string: string): string;

/**
 * Escape string for usage as an attribute value in HTTP headers.
 *
 * @param attribute - The string to be escaped.
 *
 * @return The escaped string. Will throw on invalid characters that are not supported to be escaped.
 */
export function escapeHeaderAttribute(attribute: string): string;

/**
 * Escape string for usage in HTML.
 *
 * @param string - The string to be escaped.
 *
 * @return The escaped string.
 */
export function escapeHtml(string: string): string;

/**
 * Escape string for usage in JSON.
 *
 * @param string - The string to be escaped.
 *
 * @return The escaped string.
 */
export function escapeJson(string: string): string;

/**
 * Wraps a function to ensure it can only execute once.
 *
 * @param method - The function to be wrapped.
 *
 * @return The wrapped function.
 */
export function once<T extends Function>(method: T): T;

/**
 * A reusable no-op function.
 */
export function ignore(...ignore: any): void;

/**
 * Converts a JavaScript value to a JavaScript Object Notation (JSON) string with protection against thrown errors.
 *
 * @param value A JavaScript value, usually an object or array, to be converted.
 * @param replacer The JSON.stringify() `replacer` argument.
 * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
 *
 * @return The JSON string. If the operation fails, an error string value is returned (no exception thrown).
 */
export function stringify(value: any, replacer?: any, space?: string | number): string;

/**
 * Returns a Promise that resolves after the requested timeout.
 *
 * @param timeout - The number of milliseconds to wait before resolving the Promise.
 * @param returnValue - The value that the Promise will resolve to.
 *
 * @return A Promise that resolves with `returnValue`.
 */
export function wait<T>(timeout?: number, returnValue?: T): Promise<T>;

/**
 * Returns a Promise that never resolves.
 */
export function block(): Promise<void>;

/**
 * Determines if an object is a promise.
 *
 * @param promise - the object tested.
 *
 * @returns true if the object is a promise, otherwise false.
 */
export function isPromise(promise: any): boolean;

export namespace ts {

    /**
     * Defines a type that can must be one of T or U but not both.
     */
    type XOR<T, U> = (T | U) extends object ? (internals.Without<T, U> & U) | (internals.Without<U, T> & T) : T | U;
}

declare namespace internals {

    type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
}


code:
'use strict';

exports.applyToDefaults = require('./applyToDefaults');

exports.assert = require('./assert');

exports.Bench = require('./bench');

exports.block = require('./block');

exports.clone = require('./clone');

exports.contain = require('./contain');

exports.deepEqual = require('./deepEqual');

exports.Error = require('./error');

exports.escapeHeaderAttribute = require('./escapeHeaderAttribute');

exports.escapeHtml = require('./escapeHtml');

exports.escapeJson = require('./escapeJson');

exports.escapeRegex = require('./escapeRegex');

exports.flatten = require('./flatten');

exports.ignore = require('./ignore');

exports.intersect = require('./intersect');

exports.isPromise = require('./isPromise');

exports.merge = require('./merge');

exports.once = require('./once');

exports.reach = require('./reach');

exports.reachTemplate = require('./reachTemplate');

exports.stringify = require('./stringify');

exports.wait = require('./wait');


code:
'use strict';

const internals = {};

module.exports = function (array1, array2, options = {}) {

    if (!array1 ||
        !array2) {

        return (options.first ? null : []);
    }

    const common = [];
    const hash = (Array.isArray(array1) ? new Set(array1) : array1);
    const found = new Set();
    for (const value of array2) {
        if (internals.has(hash, value) &&
            !found.has(value)) {

            if (options.first) {
                return value;
            }

            common.push(value);
            found.add(value);
        }
    }

    return (options.first ? null : common);
};

internals.has = function (ref, key) {

    if (typeof ref.has === 'function') {
        return ref.has(key);
    }

    return ref[key] !== undefined;
};


code:
'use strict';

const internals = {};

module.exports = function (promise) {

    return !!promise && typeof promise.then === 'function';
};


code:
'use strict';

const Assert = require('./assert');
const Clone = require('./clone');
const Utils = require('./utils');

const internals = {};

module.exports = internals.merge = function (target, source, options) {

    Assert(target && typeof target === 'object', 'Invalid target value: must be an object');
    Assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object');

    if (!source) {
        return target;
    }

    options = Object.assign({ nullOverride: true, mergeArrays: true }, options);

    if (Array.isArray(source)) {
        Assert(Array.isArray(target), 'Cannot merge array onto an object');
        if (!options.mergeArrays) {
            target.length = 0;                                                          // Must not change target assignment
        }

        for (let i = 0; i < source.length; ++i) {
            target.push(Clone(source[i], { symbols: options.symbols }));
        }

        return target;
    }

    const keys = Utils.keys(source, options);
    for (let i = 0; i < keys.length; ++i) {
        const key = keys[i];
        if (key === '__proto__' ||
            !Object.prototype.propertyIsEnumerable.call(source, key)) {

            continue;
        }

        const value = source[key];
        if (value &&
            typeof value === 'object') {

            if (target[key] === value) {
                continue;                                           // Can occur for shallow merges
            }

            if (!target[key] ||
                typeof target[key] !== 'object' ||
                (Array.isArray(target[key]) !== Array.isArray(value)) ||
                value instanceof Date ||
                (Buffer && Buffer.isBuffer(value)) ||               // $lab:coverage:ignore$
                value instanceof RegExp) {

                target[key] = Clone(value, { symbols: options.symbols });
            }
            else {
                internals.merge(target[key], value, options);
            }
        }
        else {
            if (value !== null &&
                value !== undefined) {                              // Explicit to preserve empty strings

                target[key] = value;
            }
            else if (options.nullOverride) {
                target[key] = value;
            }
        }
    }

    return target;
};


code:
'use strict';

const internals = {
    wrapped: Symbol('wrapped')
};

module.exports = function (method) {

    if (method[internals.wrapped]) {
        return method;
    }

    let once = false;
    const wrappedFn = function (...args) {

        if (!once) {
            once = true;
            method(...args);
        }
    };

    wrappedFn[internals.wrapped] = true;
    return wrappedFn;
};


code:
'use strict';

const Assert = require('./assert');

const internals = {};

module.exports = function (obj, chain, options) {

    if (chain === false ||
        chain === null ||
        chain === undefined) {

        return obj;
    }

    options = options || {};
    if (typeof options === 'string') {
        options = { separator: options };
    }

    const isChainArray = Array.isArray(chain);

    Assert(!isChainArray || !options.separator, 'Separator option is not valid for array-based chain');

    const path = isChainArray ? chain : chain.split(options.separator || '.');
    let ref = obj;
    for (let i = 0; i < path.length; ++i) {
        let key = path[i];
        const type = options.iterables && internals.iterables(ref);

        if (Array.isArray(ref) ||
            type === 'set') {

            const number = Number(key);
            if (Number.isInteger(number)) {
                key = number < 0 ? ref.length + number : number;
            }
        }

        if (!ref ||
            typeof ref === 'function' && options.functions === false ||         // Defaults to true
            !type && ref[key] === undefined) {

            Assert(!options.strict || i + 1 === path.length, 'Missing segment', key, 'in reach path ', chain);
            Assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain);
            ref = options.default;
            break;
        }

        if (!type) {
            ref = ref[key];
        }
        else if (type === 'set') {
            ref = [...ref][key];
        }
        else {  // type === 'map'
            ref = ref.get(key);
        }
    }

    return ref;
};

internals.iterables = function (ref) {

    if (ref instanceof Set) {
        return 'set';
    }

    if (ref instanceof Map) {
        return 'map';
    }
};


code:
'use strict';

const Reach = require('./reach');

const internals = {};

module.exports = function (obj, template, options) {

    return template.replace(/{([^{}]+)}/g, ($0, chain) => {

        const value = Reach(obj, chain, options);
        return (value === undefined || value === null ? '' : value);
    });
};


code:
'use strict';

const internals = {};

module.exports = function (...args) {

    try {
        return JSON.stringify(...args);
    }
    catch (err) {
        return '[Cannot display object: ' + err.message + ']';
    }
};


code:
'use strict';

const internals = {};

exports = module.exports = {
    array: Array.prototype,
    buffer: Buffer && Buffer.prototype,             // $lab:coverage:ignore$
    date: Date.prototype,
    error: Error.prototype,
    generic: Object.prototype,
    map: Map.prototype,
    promise: Promise.prototype,
    regex: RegExp.prototype,
    set: Set.prototype,
    weakMap: WeakMap.prototype,
    weakSet: WeakSet.prototype
};

internals.typeMap = new Map([
    ['[object Error]', exports.error],
    ['[object Map]', exports.map],
    ['[object Promise]', exports.promise],
    ['[object Set]', exports.set],
    ['[object WeakMap]', exports.weakMap],
    ['[object WeakSet]', exports.weakSet]
]);

exports.getInternalProto = function (obj) {

    if (Array.isArray(obj)) {
        return exports.array;
    }

    if (Buffer && obj instanceof Buffer) {          // $lab:coverage:ignore$
        return exports.buffer;
    }

    if (obj instanceof Date) {
        return exports.date;
    }

    if (obj instanceof RegExp) {
        return exports.regex;
    }

    if (obj instanceof Error) {
        return exports.error;
    }

    const objName = Object.prototype.toString.call(obj);
    return internals.typeMap.get(objName) || exports.generic;
};


code:
'use strict';

const internals = {};

exports.keys = function (obj, options = {}) {

    return options.symbols !== false ? Reflect.ownKeys(obj) : Object.getOwnPropertyNames(obj);  // Defaults to true
};


code:
'use strict';

const internals = {
    maxTimer: 2 ** 31 - 1              // ~25 days
};

module.exports = function (timeout, returnValue, options) {

    if (typeof timeout === 'bigint') {
        timeout = Number(timeout);
    }

    if (timeout >= Number.MAX_SAFE_INTEGER) {         // Thousands of years
        timeout = Infinity;
    }

    if (typeof timeout !== 'number' && timeout !== undefined) {
        throw new TypeError('Timeout must be a number or bigint');
    }

    return new Promise((resolve) => {

        const _setTimeout = options ? options.setTimeout : setTimeout;

        const activate = () => {

            const time = Math.min(timeout, internals.maxTimer);
            timeout -= time;
            _setTimeout(() => (timeout > 0 ? activate() : resolve(returnValue)), time);
        };

        if (timeout !== Infinity) {
            activate();
        }
    });
};


@hapi/hoek

description: General purpose node utilities

test: lab -a @hapi/code -t 100 -L -Y

test-cov-html: lab -a @hapi/code -t 100 -L -r html -o coverage.html


code:
# @hapi/topo

#### Topological sorting with grouping support.

**topo** is part of the **hapi** ecosystem and was designed to work seamlessly with the [hapi web framework](https://hapi.dev) and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out [hapi](https://hapi.dev) – they work even better together.

### Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support

## Useful resources

- [Documentation and API](https://hapi.dev/family/topo/)
- [Version status](https://hapi.dev/resources/status/#topo) (builds, dependencies, node versions, licenses, eol)
- [Changelog](https://hapi.dev/family/topo/changelog/)
- [Project policies](https://hapi.dev/policies/)
- [Free and commercial support options](https://hapi.dev/support/)


code:
export class Sorter<T> {

    /**
     * An array of the topologically sorted nodes. This list is renewed upon each call to topo.add().
     */
    nodes: T[];

    /**
     * Adds a node or list of nodes to be added and topologically sorted
     *
     * @param nodes - A mixed value or array of mixed values to be added as nodes to the topologically sorted list.
     * @param options - Optional sorting information about the nodes.
     *
     * @returns Returns an array of the topologically sorted nodes.
     */
    add(nodes: T | T[], options?: Options): T[];

    /**
     * Merges another Sorter object into the current object.
     *
     * @param others - The other object or array of objects to be merged into the current one.
     *
     * @returns Returns an array of the topologically sorted nodes.
     */
    merge(others: Sorter<T> | Sorter<T>[]): T[];

    /**
     * Sorts the nodes array (only required if the manual option is used when adding items)
     */
    sort(): T[];
}

export interface Options {

    /**
     * The sorting group the added items belong to
     */
    readonly group?: string;

    /**
     * The group or groups the added items must come before
     */
    readonly before?: string | string[];

    /**
     * The group or groups the added items must come after
     */
    readonly after?: string | string[];

    /**
     * A number used to sort items with equal before/after requirements
     */
    readonly sort?: number;

    /**
     * If true, the array is not sorted automatically until sort() is called
     */
    readonly manual?: boolean;
}


code:
'use strict';

const Assert = require('@hapi/hoek/lib/assert');

const internals = {};

exports.Sorter = class {

    constructor() {

        this._items = [];
        this.nodes = [];
    }

    add(nodes, options) {

        options = options || {};

        // Validate rules

        const before = [].concat(options.before || []);
        const after = [].concat(options.after || []);
        const group = options.group || '?';
        const sort = options.sort || 0;                   // Used for merging only

        Assert(!before.includes(group), `Item cannot come before itself: ${group}`);
        Assert(!before.includes('?'), 'Item cannot come before unassociated items');
        Assert(!after.includes(group), `Item cannot come after itself: ${group}`);
        Assert(!after.includes('?'), 'Item cannot come after unassociated items');

        if (!Array.isArray(nodes)) {
            nodes = [nodes];
        }

        for (const node of nodes) {
            const item = {
                seq: this._items.length,
                sort,
                before,
                after,
                group,
                node
            };

            this._items.push(item);
        }

        // Insert event

        if (!options.manual) {
            const valid = this._sort();
            Assert(valid, 'item', group !== '?' ? `added into group ${group}` : '', 'created a dependencies error');
        }

        return this.nodes;
    }

    merge(others) {

        if (!Array.isArray(others)) {
            others = [others];
        }

        for (const other of others) {
            if (other) {
                for (const item of other._items) {
                    this._items.push(Object.assign({}, item));      // Shallow cloned
                }
            }
        }

        // Sort items

        this._items.sort(internals.mergeSort);
        for (let i = 0; i < this._items.length; ++i) {
            this._items[i].seq = i;
        }

        const valid = this._sort();
        Assert(valid, 'merge created a dependencies error');

        return this.nodes;
    }

    sort() {

        const valid = this._sort();
        Assert(valid, 'sort created a dependencies error');

        return this.nodes;
    }

    _sort() {

        // Construct graph

        const graph = {};
        const graphAfters = Object.create(null);            // A prototype can bungle lookups w/ false positives
        const groups = Object.create(null);

        for (const item of this._items) {
            const seq = item.seq;                           // Unique across all items
            const group = item.group;

            // Determine Groups

            groups[group] = groups[group] || [];
            groups[group].push(seq);

            // Build intermediary graph using 'before'

            graph[seq] = item.before;

            // Build second intermediary graph with 'after'

            for (const after of item.after) {
                graphAfters[after] = graphAfters[after] || [];
                graphAfters[after].push(seq);
            }
        }

        // Expand intermediary graph

        for (const node in graph) {
            const expandedGroups = [];

            for (const graphNodeItem in graph[node]) {
                const group = graph[node][graphNodeItem];
                groups[group] = groups[group] || [];
                expandedGroups.push(...groups[group]);
            }

            graph[node] = expandedGroups;
        }

        // Merge intermediary graph using graphAfters into final graph

        for (const group in graphAfters) {
            if (groups[group]) {
                for (const node of groups[group]) {
                    graph[node].push(...graphAfters[group]);
                }
            }
        }

        // Compile ancestors

        const ancestors = {};
        for (const node in graph) {
            const children = graph[node];
            for (const child of children) {
                ancestors[child] = ancestors[child] || [];
                ancestors[child].push(node);
            }
        }

        // Topo sort

        const visited = {};
        const sorted = [];

        for (let i = 0; i < this._items.length; ++i) {          // Looping through item.seq values out of order
            let next = i;

            if (ancestors[i]) {
                next = null;
                for (let j = 0; j < this._items.length; ++j) {  // As above, these are item.seq values
                    if (visited[j] === true) {
                        continue;
                    }

                    if (!ancestors[j]) {
                        ancestors[j] = [];
                    }

                    const shouldSeeCount = ancestors[j].length;
                    let seenCount = 0;
                    for (let k = 0; k < shouldSeeCount; ++k) {
                        if (visited[ancestors[j][k]]) {
                            ++seenCount;
                        }
                    }

                    if (seenCount === shouldSeeCount) {
                        next = j;
                        break;
                    }
                }
            }

            if (next !== null) {
                visited[next] = true;
                sorted.push(next);
            }
        }

        if (sorted.length !== this._items.length) {
            return false;
        }

        const seqIndex = {};
        for (const item of this._items) {
            seqIndex[item.seq] = item;
        }

        this._items = [];
        this.nodes = [];

        for (const value of sorted) {
            const sortedItem = seqIndex[value];
            this.nodes.push(sortedItem.node);
            this._items.push(sortedItem);
        }

        return true;
    }
};

internals.mergeSort = (a, b) => {

    return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);
};


@hapi/topo

description: Topological sorting with grouping support

keywords: topological sort toposort topsort


code:
# Config Array

by [Nicholas C. Zakas](https://humanwhocodes.com)

If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).

## Description

A config array is a way of managing configurations that are based on glob pattern matching of filenames. Each config array contains the information needed to determine the correct configuration for any file based on the filename.

## Background

In 2019, I submitted an [ESLint RFC](https://github.com/eslint/rfcs/pull/9) proposing a new way of configuring ESLint. The goal was to streamline what had become an increasingly complicated configuration process. Over several iterations, this proposal was eventually born.

The basic idea is that all configuration, including overrides, can be represented by a single array where each item in the array is a config object. Config objects appearing later in the array override config objects appearing earlier in the array. You can calculate a config for a given file by traversing all config objects in the array to find the ones that match the filename. Matching is done by specifying glob patterns in `files` and `ignores` properties on each config object. Here's an example:

```js
export default [

    // match all JSON files
    {
        name: "JSON Handler",
        files: ["**/*.json"],
        handler: jsonHandler
    },

    // match only package.json
    {
        name: "package.json Handler",
        files: ["package.json"],
        handler: packageJsonHandler
    }
];
```

In this example, there are two config objects: the first matches all JSON files in all directories and the second matches just `package.json` in the base path directory (all the globs are evaluated as relative to a base path that can be specified). When you retrieve a configuration for `foo.json`, only the first config object matches so `handler` is equal to `jsonHandler`; when you retrieve a configuration for `package.json`, `handler` is equal to `packageJsonHandler` (because both config objects match, the second one wins).

## Installation

You can install the package using npm or Yarn:

```bash
npm install @humanwhocodes/config-array --save

# or

yarn add @humanwhocodes/config-array
```

## Usage

First, import the `ConfigArray` constructor:

```js
import { ConfigArray } from "@humanwhocodes/config-array";

// or using CommonJS

const { ConfigArray } = require("@humanwhocodes/config-array");
```

When you create a new instance of `ConfigArray`, you must pass in two arguments: an array of configs and an options object. The array of configs is most likely read in from a configuration file, so here's a typical example:

```js
const configFilename = path.resolve(process.cwd(), "my.config.js");
const { default: rawConfigs } = await import(configFilename);
const configs = new ConfigArray(rawConfigs, {

    // the path to match filenames from
    basePath: process.cwd(),

    // additional items in each config
    schema: mySchema
});
```

This example reads in an object or array from `my.config.js` and passes it into the `ConfigArray` constructor as the first argument. The second argument is an object specifying the `basePath` (the directory in which `my.config.js` is found) and a `schema` to define the additional properties of a config object beyond `files`, `ignores`, and `name`.

### Specifying a Schema

The `schema` option is required for you to use additional properties in config objects. The schema is an object that follows the format of an [`ObjectSchema`](https://npmjs.com/package/@humanwhocodes/object-schema). The schema specifies both validation and merge rules that the `ConfigArray` instance needs to combine configs when there are multiple matches. Here's an example:

```js
const configFilename = path.resolve(process.cwd(), "my.config.js");
const { default: rawConfigs } = await import(configFilename);

const mySchema = {

    // define the handler key in configs
    handler: {
        required: true,
        merge(a, b) {
            if (!b) return a;
            if (!a) return b;
        },
        validate(value) {
            if (typeof value !== "function") {
                throw new TypeError("Function expected.");
            }
        }
    }
};

const configs = new ConfigArray(rawConfigs, {

    // the path to match filenames from
    basePath: process.cwd(),

    // additional item schemas in each config
    schema: mySchema,

    // additional config types supported (default: [])
    extraConfigTypes: ["array", "function"];
});
```

### Config Arrays

Config arrays can be multidimensional, so it's possible for a config array to contain another config array when `extraConfigTypes` contains `"array"`, such as:

```js
export default [

    // JS config
    {
        files: ["**/*.js"],
        handler: jsHandler
    },

    // JSON configs
    [

        // match all JSON files
        {
            name: "JSON Handler",
            files: ["**/*.json"],
            handler: jsonHandler
        },

        // match only package.json
        {
            name: "package.json Handler",
            files: ["package.json"],
            handler: packageJsonHandler
        }
    ],

    // filename must match function
    {
        files: [ filePath => filePath.endsWith(".md") ],
        handler: markdownHandler
    },

    // filename must match all patterns in subarray
    {
        files: [ ["*.test.*", "*.js"] ],
        handler: jsTestHandler
    },

    // filename must not match patterns beginning with !
    {
        name: "Non-JS files",
        files: ["!*.js"],
        settings: {
            js: false
        }
    }
];
```

In this example, the array contains both config objects and a config array. When a config array is normalized (see details below), it is flattened so only config objects remain. However, the order of evaluation remains the same.

If the `files` array contains a function, then that function is called with the absolute path of the file and is expected to return `true` if there is a match and `false` if not. (The `ignores` array can also contain functions.)

If the `files` array contains an item that is an array of strings and functions, then all patterns must match in order for the config to match. In the preceding examples, both `*.test.*` and `*.js` must match in order for the config object to be used.

If a pattern in the files array begins with `!` then it excludes that pattern. In the preceding example, any filename that doesn't end with `.js` will automatically get a `settings.js` property set to `false`.

You can also specify an `ignores` key that will force files matching those patterns to not be included. If the `ignores` key is in a config object without any other keys, then those ignores will always be applied; otherwise those ignores act as exclusions. Here's an example:

```js
export default [

    // Always ignored
    {
        ignores: ["**/.git/**", "**/node_modules/**"]
    },

    // .eslintrc.js file is ignored only when .js file matches
    {
        files: ["**/*.js"],
        ignores: [".eslintrc.js"]
        handler: jsHandler
    }
];
```

You can use negated patterns in `ignores` to exclude a file that was already ignored, such as:

```js
export default [

    // Ignore all JSON files except tsconfig.json
    {
        files: ["**/*"],
        ignores: ["**/*.json", "!tsconfig.json"]
    },

];
```

### Config Functions

Config arrays can also include config functions when `extraConfigTypes` contains `"function"`. A config function accepts a single parameter, `context` (defined by you), and must return either a config object or a config array (it cannot return another function). Config functions allow end users to execute code in the creation of appropriate config objects. Here's an example:

```js
export default [

    // JS config
    {
        files: ["**/*.js"],
        handler: jsHandler
    },

    // JSON configs
    function (context) {
        return [

            // match all JSON files
            {
                name: context.name + " JSON Handler",
                files: ["**/*.json"],
                handler: jsonHandler
            },

            // match only package.json
            {
                name: context.name + " package.json Handler",
                files: ["package.json"],
                handler: packageJsonHandler
            }
        ];
    }
];
```

When a config array is normalized, each function is executed and replaced in the config array with the return value.

**Note:** Config functions can also be async.

### Normalizing Config Arrays

Once a config array has been created and loaded with all of the raw config data, it must be normalized before it can be used. The normalization process goes through and flattens the config array as well as executing all config functions to get their final values.

To normalize a config array, call the `normalize()` method and pass in a context object:

```js
await configs.normalize({
    name: "MyApp"
});
```

The `normalize()` method returns a promise, so be sure to use the `await` operator. The config array instance is normalized in-place, so you don't need to create a new variable.

If you want to disallow async config functions, you can call `normalizeSync()` instead. This method is completely synchronous and does not require using the `await` operator as it does not return a promise:

```js
await configs.normalizeSync({
    name: "MyApp"
});
```

**Important:** Once a `ConfigArray` is normalized, it cannot be changed further. You can, however, create a new `ConfigArray` and pass in the normalized instance to create an unnormalized copy.

### Getting Config for a File

To get the config for a file, use the `getConfig()` method on a normalized config array and pass in the filename to get a config for:

```js
// pass in absolute filename
const fileConfig = configs.getConfig(path.resolve(process.cwd(), "package.json"));
```

The config array always returns an object, even if there are no configs matching the given filename. You can then inspect the returned config object to determine how to proceed.

A few things to keep in mind:

* You must pass in the absolute filename to get a config for.
* The returned config object never has `files`, `ignores`, or `name` properties; the only properties on the object will be the other configuration options specified.
* The config array caches configs, so subsequent calls to `getConfig()` with the same filename will return in a fast lookup rather than another calculation.
* A config will only be generated if the filename matches an entry in a `files` key. A config will not be generated without matching a `files` key (configs without a `files` key are only applied when another config with a `files` key is applied; configs without `files` are never applied on their own). Any config with a `files` key entry ending with `/**` or `/*` will only be applied if another entry in the same `files` key matches or another config matches.

## Determining Ignored Paths

You can determine if a file is ignored by using the `isFileIgnored()` method and passing in the absolute path of any file, as in this example:

```js
const ignored = configs.isFileIgnored('/foo/bar/baz.txt');
```

A file is considered ignored if any of the following is true:

* **It's parent directory is ignored.** For example, if `foo` is in `ignores`, then `foo/a.js` is considered ignored.
* **It has an ancestor directory that is ignored.** For example, if `foo` is in `ignores`, then `foo/baz/a.js` is considered ignored.
* **It matches an ignored file pattern.** For example, if `**/a.js` is in `ignores`, then `foo/a.js` and `foo/baz/a.js` are considered ignored.
* **If it matches an entry in `files` and also in `ignores`.** For example, if `**/*.js` is in `files` and `**/a.js` is in `ignores`, then `foo/a.js` and `foo/baz/a.js` are considered ignored.
* **The file is outside the `basePath`.** If the `basePath` is `/usr/me`, then `/foo/a.js` is considered ignored.

For directories, use the `isDirectoryIgnored()` method and pass in the absolute path of any directory, as in this example:

```js
const ignored = configs.isDirectoryIgnored('/foo/bar/');
```

A directory is considered ignored if any of the following is true:

* **It's parent directory is ignored.** For example, if `foo` is in `ignores`, then `foo/baz` is considered ignored.
* **It has an ancestor directory that is ignored.** For example, if `foo` is in `ignores`, then `foo/bar/baz/a.js` is considered ignored.
* **It matches and ignored file pattern.** For example, if `**/a.js` is in `ignores`, then `foo/a.js` and `foo/baz/a.js` are considered ignored.
* **If it matches an entry in `files` and also in `ignores`.** For example, if `**/*.js` is in `files` and `**/a.js` is in `ignores`, then `foo/a.js` and `foo/baz/a.js` are considered ignored.
* **The file is outside the `basePath`.** If the `basePath` is `/usr/me`, then `/foo/a.js` is considered ignored.

**Important:** A pattern such as `foo/**` means that `foo` and `foo/` are *not* ignored whereas `foo/bar` is ignored. If you want to ignore `foo` and all of its subdirectories, use the pattern `foo` or `foo/` in `ignores`.

## Caching Mechanisms

Each `ConfigArray` aggressively caches configuration objects to avoid unnecessary work. This caching occurs in two ways:

1. **File-based Caching.** For each filename that is passed into a method, the resulting config is cached against that filename so you're always guaranteed to get the same object returned from `getConfig()` whenever you pass the same filename in.
2. **Index-based Caching.** Whenever a config is calculated, the config elements that were used to create the config are also cached. So if a given filename matches elements 1, 5, and 7, the resulting config is cached with a key of `1,5,7`. That way, if another file is passed that matches the same config elements, the result is already known and doesn't have to be recalculated. That means two files that match all the same elements will return the same config from `getConfig()`.

## Acknowledgements

The design of this project was influenced by feedback on the ESLint RFC, and incorporates ideas from:

* Teddy Katz (@not-an-aardvark)
* Toru Nagashima (@mysticatea)
* Kai Cataldo (@kaicataldo)

## License

Apache 2.0


code:
'use strict';

var path = require('path');
var minimatch = require('minimatch');
var createDebug = require('debug');
var objectSchema = require('@humanwhocodes/object-schema');

/**
 * @fileoverview ConfigSchema
 * @author Nicholas C. Zakas
 */

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const NOOP_STRATEGY = {
	required: false,
	merge() {
		return undefined;
	},
	validate() { }
};

//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------

/**
 * The base schema that every ConfigArray uses.
 * @type Object
 */
const baseSchema = Object.freeze({
	name: {
		required: false,
		merge() {
			return undefined;
		},
		validate(value) {
			if (typeof value !== 'string') {
				throw new TypeError('Property must be a string.');
			}
		}
	},
	files: NOOP_STRATEGY,
	ignores: NOOP_STRATEGY
});

/**
 * @fileoverview ConfigSchema
 * @author Nicholas C. Zakas
 */

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Asserts that a given value is an array.
 * @param {*} value The value to check.
 * @returns {void}
 * @throws {TypeError} When the value is not an array.
 */
function assertIsArray(value) {
	if (!Array.isArray(value)) {
		throw new TypeError('Expected value to be an array.');
	}
}

/**
 * Asserts that a given value is an array containing only strings and functions.
 * @param {*} value The value to check.
 * @returns {void}
 * @throws {TypeError} When the value is not an array of strings and functions.
 */
function assertIsArrayOfStringsAndFunctions(value, name) {
	assertIsArray(value);

	if (value.some(item => typeof item !== 'string' && typeof item !== 'function')) {
		throw new TypeError('Expected array to only contain strings and functions.');
	}
}

/**
 * Asserts that a given value is a non-empty array.
 * @param {*} value The value to check.
 * @returns {void}
 * @throws {TypeError} When the value is not an array or an empty array.
 */
function assertIsNonEmptyArray(value) {
	if (!Array.isArray(value) || value.length === 0) {
		throw new TypeError('Expected value to be a non-empty array.');
	}
}

//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------

/**
 * The schema for `files` and `ignores` that every ConfigArray uses.
 * @type Object
 */
const filesAndIgnoresSchema = Object.freeze({
	files: {
		required: false,
		merge() {
			return undefined;
		},
		validate(value) {

			// first check if it's an array
			assertIsNonEmptyArray(value);

			// then check each member
			value.forEach(item => {
				if (Array.isArray(item)) {
					assertIsArrayOfStringsAndFunctions(item);
				} else if (typeof item !== 'string' && typeof item !== 'function') {
					throw new TypeError('Items must be a string, a function, or an array of strings and functions.');
				}
			});

		}
	},
	ignores: {
		required: false,
		merge() {
			return undefined;
		},
		validate: assertIsArrayOfStringsAndFunctions
	}
});

/**
 * @fileoverview ConfigArray
 * @author Nicholas C. Zakas
 */

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const Minimatch = minimatch.Minimatch;
const minimatchCache = new Map();
const negatedMinimatchCache = new Map();
const debug = createDebug('@hwc/config-array');

const MINIMATCH_OPTIONS = {
	// matchBase: true,
	dot: true
};

const CONFIG_TYPES = new Set(['array', 'function']);

const FILES_AND_IGNORES_SCHEMA = new objectSchema.ObjectSchema(filesAndIgnoresSchema);

/**
 * Shorthand for checking if a value is a string.
 * @param {any} value The value to check.
 * @returns {boolean} True if a string, false if not.
 */
function isString(value) {
	return typeof value === 'string';
}

/**
 * Asserts that the files and ignores keys of a config object are valid as per base schema.
 * @param {object} config The config object to check.
 * @returns {void}
 * @throws {TypeError} If the files and ignores keys of a config object are not valid.
 */
function assertValidFilesAndIgnores(config) {
	if (!config || typeof config !== 'object') {
		return;
	}
	const validateConfig = { };
	if ('files' in config) {
		validateConfig.files = config.files;
	}
	if ('ignores' in config) {
		validateConfig.ignores = config.ignores;
	}
	FILES_AND_IGNORES_SCHEMA.validate(validateConfig);
}

/**
 * Wrapper around minimatch that caches minimatch patterns for
 * faster matching speed over multiple file path evaluations.
 * @param {string} filepath The file path to match.
 * @param {string} pattern The glob pattern to match against.
 * @param {object} options The minimatch options to use.
 * @returns
 */
function doMatch(filepath, pattern, options = {}) {

	let cache = minimatchCache;

	if (options.flipNegate) {
		cache = negatedMinimatchCache;
	}

	let matcher = cache.get(pattern);

	if (!matcher) {
		matcher = new Minimatch(pattern, Object.assign({}, MINIMATCH_OPTIONS, options));
		cache.set(pattern, matcher);
	}

	return matcher.match(filepath);
}

/**
 * Normalizes a `ConfigArray` by flattening it and executing any functions
 * that are found inside.
 * @param {Array} items The items in a `ConfigArray`.
 * @param {Object} context The context object to pass into any function
 *      found.
 * @param {Array<string>} extraConfigTypes The config types to check.
 * @returns {Promise<Array>} A flattened array containing only config objects.
 * @throws {TypeError} When a config function returns a function.
 */
async function normalize(items, context, extraConfigTypes) {

	const allowFunctions = extraConfigTypes.includes('function');
	const allowArrays = extraConfigTypes.includes('array');

	async function* flatTraverse(array) {
		for (let item of array) {
			if (typeof item === 'function') {
				if (!allowFunctions) {
					throw new TypeError('Unexpected function.');
				}

				item = item(context);
				if (item.then) {
					item = await item;
				}
			}

			if (Array.isArray(item)) {
				if (!allowArrays) {
					throw new TypeError('Unexpected array.');
				}
				yield* flatTraverse(item);
			} else if (typeof item === 'function') {
				throw new TypeError('A config function can only return an object or array.');
			} else {
				yield item;
			}
		}
	}

	/*
	 * Async iterables cannot be used with the spread operator, so we need to manually
	 * create the array to return.
	 */
	const asyncIterable = await flatTraverse(items);
	const configs = [];

	for await (const config of asyncIterable) {
		configs.push(config);
	}

	return configs;
}

/**
 * Normalizes a `ConfigArray` by flattening it and executing any functions
 * that are found inside.
 * @param {Array} items The items in a `ConfigArray`.
 * @param {Object} context The context object to pass into any function
 *      found.
 * @param {Array<string>} extraConfigTypes The config types to check.
 * @returns {Array} A flattened array containing only config objects.
 * @throws {TypeError} When a config function returns a function.
 */
function normalizeSync(items, context, extraConfigTypes) {

	const allowFunctions = extraConfigTypes.includes('function');
	const allowArrays = extraConfigTypes.includes('array');

	function* flatTraverse(array) {
		for (let item of array) {
			if (typeof item === 'function') {

				if (!allowFunctions) {
					throw new TypeError('Unexpected function.');
				}

				item = item(context);
				if (item.then) {
					throw new TypeError('Async config functions are not supported.');
				}
			}

			if (Array.isArray(item)) {

				if (!allowArrays) {
					throw new TypeError('Unexpected array.');
				}

				yield* flatTraverse(item);
			} else if (typeof item === 'function') {
				throw new TypeError('A config function can only return an object or array.');
			} else {
				yield item;
			}
		}
	}

	return [...flatTraverse(items)];
}

/**
 * Determines if a given file path should be ignored based on the given
 * matcher.
 * @param {Array<string|() => boolean>} ignores The ignore patterns to check.
 * @param {string} filePath The absolute path of the file to check.
 * @param {string} relativeFilePath The relative path of the file to check.
 * @returns {boolean} True if the path should be ignored and false if not.
 */
function shouldIgnorePath(ignores, filePath, relativeFilePath) {

	// all files outside of the basePath are ignored
	if (relativeFilePath.startsWith('..')) {
		return true;
	}

	return ignores.reduce((ignored, matcher) => {

		if (!ignored) {

			if (typeof matcher === 'function') {
				return matcher(filePath);
			}

			// don't check negated patterns because we're not ignored yet
			if (!matcher.startsWith('!')) {
				return doMatch(relativeFilePath, matcher);
			}

			// otherwise we're still not ignored
			return false;

		}

		// only need to check negated patterns because we're ignored
		if (typeof matcher === 'string' && matcher.startsWith('!')) {
			return !doMatch(relativeFilePath, matcher, {
				flipNegate: true
			});
		}

		return ignored;

	}, false);

}

/**
 * Determines if a given file path is matched by a config based on
 * `ignores` only.
 * @param {string} filePath The absolute file path to check.
 * @param {string} basePath The base path for the config.
 * @param {Object} config The config object to check.
 * @returns {boolean} True if the file path is matched by the config,
 *      false if not.
 */
function pathMatchesIgnores(filePath, basePath, config) {

	/*
	 * For both files and ignores, functions are passed the absolute
	 * file path while strings are compared against the relative
	 * file path.
	 */
	const relativeFilePath = path.relative(basePath, filePath);

	return Object.keys(config).length > 1 &&
		!shouldIgnorePath(config.ignores, filePath, relativeFilePath);
}

/**
 * Determines if a given file path is matched by a config. If the config
 * has no `files` field, then it matches; otherwise, if a `files` field
 * is present then we match the globs in `files` and exclude any globs in
 * `ignores`.
 * @param {string} filePath The absolute file path to check.
 * @param {string} basePath The base path for the config.
 * @param {Object} config The config object to check.
 * @returns {boolean} True if the file path is matched by the config,
 *      false if not.
 */
function pathMatches(filePath, basePath, config) {

	/*
	 * For both files and ignores, functions are passed the absolute
	 * file path while strings are compared against the relative
	 * file path.
	 */
	const relativeFilePath = path.relative(basePath, filePath);

	// match both strings and functions
	const match = pattern => {

		if (isString(pattern)) {
			return doMatch(relativeFilePath, pattern);
		}

		if (typeof pattern === 'function') {
			return pattern(filePath);
		}

		throw new TypeError(`Unexpected matcher type ${pattern}.`);
	};

	// check for all matches to config.files
	let filePathMatchesPattern = config.files.some(pattern => {
		if (Array.isArray(pattern)) {
			return pattern.every(match);
		}

		return match(pattern);
	});

	/*
	 * If the file path matches the config.files patterns, then check to see
	 * if there are any files to ignore.
	 */
	if (filePathMatchesPattern && config.ignores) {
		filePathMatchesPattern = !shouldIgnorePath(config.ignores, filePath, relativeFilePath);
	}

	return filePathMatchesPattern;
}

/**
 * Ensures that a ConfigArray has been normalized.
 * @param {ConfigArray} configArray The ConfigArray to check.
 * @returns {void}
 * @throws {Error} When the `ConfigArray` is not normalized.
 */
function assertNormalized(configArray) {
	// TODO: Throw more verbose error
	if (!configArray.isNormalized()) {
		throw new Error('ConfigArray must be normalized to perform this operation.');
	}
}

/**
 * Ensures that config types are valid.
 * @param {Array<string>} extraConfigTypes The config types to check.
 * @returns {void}
 * @throws {Error} When the config types array is invalid.
 */
function assertExtraConfigTypes(extraConfigTypes) {
	if (extraConfigTypes.length > 2) {
		throw new TypeError('configTypes must be an array with at most two items.');
	}

	for (const configType of extraConfigTypes) {
		if (!CONFIG_TYPES.has(configType)) {
			throw new TypeError(`Unexpected config type "${configType}" found. Expected one of: "object", "array", "function".`);
		}
	}
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

const ConfigArraySymbol = {
	isNormalized: Symbol('isNormalized'),
	configCache: Symbol('configCache'),
	schema: Symbol('schema'),
	finalizeConfig: Symbol('finalizeConfig'),
	preprocessConfig: Symbol('preprocessConfig')
};

// used to store calculate data for faster lookup
const dataCache = new WeakMap();

/**
 * Represents an array of config objects and provides method for working with
 * those config objects.
 */
class ConfigArray extends Array {

	/**
	 * Creates a new instance of ConfigArray.
	 * @param {Iterable|Function|Object} configs An iterable yielding config
	 *      objects, or a config function, or a config object.
	 * @param {string} [options.basePath=""] The path of the config file
	 * @param {boolean} [options.normalized=false] Flag indicating if the
	 *      configs have already been normalized.
	 * @param {Object} [options.schema] The additional schema
	 *      definitions to use for the ConfigArray schema.
	 * @param {Array<string>} [options.configTypes] List of config types supported.
	 */
	constructor(configs, {
		basePath = '',
		normalized = false,
		schema: customSchema,
		extraConfigTypes = []
	} = {}
	) {
		super();

		/**
		 * Tracks if the array has been normalized.
		 * @property isNormalized
		 * @type boolean
		 * @private
		 */
		this[ConfigArraySymbol.isNormalized] = normalized;

		/**
		 * The schema used for validating and merging configs.
		 * @property schema
		 * @type ObjectSchema
		 * @private
		 */
		this[ConfigArraySymbol.schema] = new objectSchema.ObjectSchema(
			Object.assign({}, customSchema, baseSchema)
		);

		/**
		 * The path of the config file that this array was loaded from.
		 * This is used to calculate filename matches.
		 * @property basePath
		 * @type string
		 */
		this.basePath = basePath;

		assertExtraConfigTypes(extraConfigTypes);

		/**
		 * The supported config types.
		 * @property configTypes
		 * @type Array<string>
		 */
		this.extraConfigTypes = Object.freeze([...extraConfigTypes]);

		/**
		 * A cache to store calculated configs for faster repeat lookup.
		 * @property configCache
		 * @type Map
		 * @private
		 */
		this[ConfigArraySymbol.configCache] = new Map();

		// init cache
		dataCache.set(this, {
			explicitMatches: new Map(),
			directoryMatches: new Map(),
			files: undefined,
			ignores: undefined
		});

		// load the configs into this array
		if (Array.isArray(configs)) {
			this.push(...configs);
		} else {
			this.push(configs);
		}

	}

	/**
	 * Prevent normal array methods from creating a new `ConfigArray` instance.
	 * This is to ensure that methods such as `slice()` won't try to create a
	 * new instance of `ConfigArray` behind the scenes as doing so may throw
	 * an error due to the different constructor signature.
	 * @returns {Function} The `Array` constructor.
	 */
	static get [Symbol.species]() {
		return Array;
	}

	/**
	 * Returns the `files` globs from every config object in the array.
	 * This can be used to determine which files will be matched by a
	 * config array or to use as a glob pattern when no patterns are provided
	 * for a command line interface.
	 * @returns {Array<string|Function>} An array of matchers.
	 */
	get files() {

		assertNormalized(this);

		// if this data has been cached, retrieve it
		const cache = dataCache.get(this);

		if (cache.files) {
			return cache.files;
		}

		// otherwise calculate it

		const result = [];

		for (const config of this) {
			if (config.files) {
				config.files.forEach(filePattern => {
					result.push(filePattern);
				});
			}
		}

		// store result
		cache.files = result;
		dataCache.set(this, cache);

		return result;
	}

	/**
	 * Returns ignore matchers that should always be ignored regardless of
	 * the matching `files` fields in any configs. This is necessary to mimic
	 * the behavior of things like .gitignore and .eslintignore, allowing a
	 * globbing operation to be faster.
	 * @returns {string[]} An array of string patterns and functions to be ignored.
	 */
	get ignores() {

		assertNormalized(this);

		// if this data has been cached, retrieve it
		const cache = dataCache.get(this);

		if (cache.ignores) {
			return cache.ignores;
		}

		// otherwise calculate it

		const result = [];

		for (const config of this) {

			/*
			 * We only count ignores if there are no other keys in the object.
			 * In this case, it acts list a globally ignored pattern. If there
			 * are additional keys, then ignores act like exclusions.
			 */
			if (config.ignores && Object.keys(config).length === 1) {
				result.push(...config.ignores);
			}
		}

		// store result
		cache.ignores = result;
		dataCache.set(this, cache);

		return result;
	}

	/**
	 * Indicates if the config array has been normalized.
	 * @returns {boolean} True if the config array is normalized, false if not.
	 */
	isNormalized() {
		return this[ConfigArraySymbol.isNormalized];
	}

	/**
	 * Normalizes a config array by flattening embedded arrays and executing
	 * config functions.
	 * @param {ConfigContext} context The context object for config functions.
	 * @returns {Promise<ConfigArray>} The current ConfigArray instance.
	 */
	async normalize(context = {}) {

		if (!this.isNormalized()) {
			const normalizedConfigs = await normalize(this, context, this.extraConfigTypes);
			this.length = 0;
			this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this)));
			this.forEach(assertValidFilesAndIgnores);
			this[ConfigArraySymbol.isNormalized] = true;

			// prevent further changes
			Object.freeze(this);
		}

		return this;
	}

	/**
	 * Normalizes a config array by flattening embedded arrays and executing
	 * config functions.
	 * @param {ConfigContext} context The context object for config functions.
	 * @returns {ConfigArray} The current ConfigArray instance.
	 */
	normalizeSync(context = {}) {

		if (!this.isNormalized()) {
			const normalizedConfigs = normalizeSync(this, context, this.extraConfigTypes);
			this.length = 0;
			this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this)));
			this.forEach(assertValidFilesAndIgnores);
			this[ConfigArraySymbol.isNormalized] = true;

			// prevent further changes
			Object.freeze(this);
		}

		return this;
	}

	/**
	 * Finalizes the state of a config before being cached and returned by
	 * `getConfig()`. Does nothing by default but is provided to be
	 * overridden by subclasses as necessary.
	 * @param {Object} config The config to finalize.
	 * @returns {Object} The finalized config.
	 */
	[ConfigArraySymbol.finalizeConfig](config) {
		return config;
	}

	/**
	 * Preprocesses a config during the normalization process. This is the
	 * method to override if you want to convert an array item before it is
	 * validated for the first time. For example, if you want to replace a
	 * string with an object, this is the method to override.
	 * @param {Object} config The config to preprocess.
	 * @returns {Object} The config to use in place of the argument.
	 */
	[ConfigArraySymbol.preprocessConfig](config) {
		return config;
	}

	/**
	 * Determines if a given file path explicitly matches a `files` entry
	 * and also doesn't match an `ignores` entry. Configs that don't have
	 * a `files` property are not considered an explicit match.
	 * @param {string} filePath The complete path of a file to check.
	 * @returns {boolean} True if the file path matches a `files` entry
	 * 		or false if not.
	 */
	isExplicitMatch(filePath) {

		assertNormalized(this);

		const cache = dataCache.get(this);

		// first check the cache to avoid duplicate work
		let result = cache.explicitMatches.get(filePath);

		if (typeof result == 'boolean') {
			return result;
		}

		// TODO: Maybe move elsewhere? Maybe combine with getConfig() logic?
		const relativeFilePath = path.relative(this.basePath, filePath);

		if (shouldIgnorePath(this.ignores, filePath, relativeFilePath)) {
			debug(`Ignoring ${filePath}`);

			// cache and return result
			cache.explicitMatches.set(filePath, false);
			return false;
		}

		// filePath isn't automatically ignored, so try to find a match

		for (const config of this) {

			if (!config.files) {
				continue;
			}

			if (pathMatches(filePath, this.basePath, config)) {
				debug(`Matching config found for ${filePath}`);
				cache.explicitMatches.set(filePath, true);
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns the config object for a given file path.
	 * @param {string} filePath The complete path of a file to get a config for.
	 * @returns {Object} The config object for this file.
	 */
	getConfig(filePath) {

		assertNormalized(this);

		const cache = this[ConfigArraySymbol.configCache];

		// first check the cache for a filename match to avoid duplicate work
		if (cache.has(filePath)) {
			return cache.get(filePath);
		}

		let finalConfig;

		// next check to see if the file should be ignored

		// check if this should be ignored due to its directory
		if (this.isDirectoryIgnored(path.dirname(filePath))) {
			debug(`Ignoring ${filePath} based on directory pattern`);

			// cache and return result - finalConfig is undefined at this point
			cache.set(filePath, finalConfig);
			return finalConfig;
		}

		// TODO: Maybe move elsewhere?
		const relativeFilePath = path.relative(this.basePath, filePath);

		if (shouldIgnorePath(this.ignores, filePath, relativeFilePath)) {
			debug(`Ignoring ${filePath} based on file pattern`);

			// cache and return result - finalConfig is undefined at this point
			cache.set(filePath, finalConfig);
			return finalConfig;
		}

		// filePath isn't automatically ignored, so try to construct config

		const matchingConfigIndices = [];
		let matchFound = false;
		const universalPattern = /\/\*{1,2}$/;

		this.forEach((config, index) => {

			if (!config.files) {

				if (!config.ignores) {
					debug(`Anonymous universal config found for ${filePath}`);
					matchingConfigIndices.push(index);
					return;
				}

				if (pathMatchesIgnores(filePath, this.basePath, config)) {
					debug(`Matching config found for ${filePath} (based on ignores: ${config.ignores})`);
					matchingConfigIndices.push(index);
					return;
				}

				debug(`Skipped config found for ${filePath} (based on ignores: ${config.ignores})`);
				return;
			}

			/*
			 * If a config has a files pattern ending in /** or /*, and the
			 * filePath only matches those patterns, then the config is only
			 * applied if there is another config where the filePath matches
			 * a file with a specific extensions such as *.js.
			 */

			const universalFiles = config.files.filter(
				pattern => universalPattern.test(pattern)
			);

			// universal patterns were found so we need to check the config twice
			if (universalFiles.length) {

				debug('Universal files patterns found. Checking carefully.');

				const nonUniversalFiles = config.files.filter(
					pattern => !universalPattern.test(pattern)
				);

				// check that the config matches without the non-universal files first
				if (
					nonUniversalFiles.length &&
					pathMatches(
						filePath, this.basePath,
						{ files: nonUniversalFiles, ignores: config.ignores }
					)
				) {
					debug(`Matching config found for ${filePath}`);
					matchingConfigIndices.push(index);
					matchFound = true;
					return;
				}

				// if there wasn't a match then check if it matches with universal files
				if (
					universalFiles.length &&
					pathMatches(
						filePath, this.basePath,
						{ files: universalFiles, ignores: config.ignores }
					)
				) {
					debug(`Matching config found for ${filePath}`);
					matchingConfigIndices.push(index);
					return;
				}

				// if we make here, then there was no match
				return;
			}

			// the normal case
			if (pathMatches(filePath, this.basePath, config)) {
				debug(`Matching config found for ${filePath}`);
				matchingConfigIndices.push(index);
				matchFound = true;
				return;
			}

		});

		// if matching both files and ignores, there will be no config to create
		if (!matchFound) {
			debug(`No matching configs found for ${filePath}`);

			// cache and return result - finalConfig is undefined at this point
			cache.set(filePath, finalConfig);
			return finalConfig;
		}

		// check to see if there is a config cached by indices
		finalConfig = cache.get(matchingConfigIndices.toString());

		if (finalConfig) {

			// also store for filename for faster lookup next time
			cache.set(filePath, finalConfig);

			return finalConfig;
		}

		// otherwise construct the config

		finalConfig = matchingConfigIndices.reduce((result, index) => {
			return this[ConfigArraySymbol.schema].merge(result, this[index]);
		}, {}, this);

		finalConfig = this[ConfigArraySymbol.finalizeConfig](finalConfig);

		cache.set(filePath, finalConfig);
		cache.set(matchingConfigIndices.toString(), finalConfig);

		return finalConfig;
	}

	/**
	 * Determines if the given filepath is ignored based on the configs.
	 * @param {string} filePath The complete path of a file to check.
	 * @returns {boolean} True if the path is ignored, false if not.
	 * @deprecated Use `isFileIgnored` instead.
	 */
	isIgnored(filePath) {
		return this.isFileIgnored(filePath);
	}

	/**
	 * Determines if the given filepath is ignored based on the configs.
	 * @param {string} filePath The complete path of a file to check.
	 * @returns {boolean} True if the path is ignored, false if not.
	 */
	isFileIgnored(filePath) {
		return this.getConfig(filePath) === undefined;
	}

	/**
	 * Determines if the given directory is ignored based on the configs.
	 * This checks only default `ignores` that don't have `files` in the
	 * same config. A pattern such as `/foo` be considered to ignore the directory
	 * while a pattern such as `/foo/**` is not considered to ignore the
	 * directory because it is matching files.
	 * @param {string} directoryPath The complete path of a directory to check.
	 * @returns {boolean} True if the directory is ignored, false if not. Will
	 * 		return true for any directory that is not inside of `basePath`.
	 * @throws {Error} When the `ConfigArray` is not normalized.
	 */
	isDirectoryIgnored(directoryPath) {

		assertNormalized(this);

		const relativeDirectoryPath = path.relative(this.basePath, directoryPath)
			.replace(/\\/g, '/');

		if (relativeDirectoryPath.startsWith('..')) {
			return true;
		}

		// first check the cache
		const cache = dataCache.get(this).directoryMatches;

		if (cache.has(relativeDirectoryPath)) {
			return cache.get(relativeDirectoryPath);
		}

		const directoryParts = relativeDirectoryPath.split('/');
		let relativeDirectoryToCheck = '';
		let result = false;

		/*
		 * In order to get the correct gitignore-style ignores, where an
		 * ignored parent directory cannot have any descendants unignored,
		 * we need to check every directory starting at the parent all
		 * the way down to the actual requested directory.
		 *
		 * We aggressively cache all of this info to make sure we don't
		 * have to recalculate everything for every call.
		 */
		do {

			relativeDirectoryToCheck += directoryParts.shift() + '/';

			result = shouldIgnorePath(
				this.ignores,
				path.join(this.basePath, relativeDirectoryToCheck),
				relativeDirectoryToCheck
			);

			cache.set(relativeDirectoryToCheck, result);

		} while (!result && directoryParts.length);

		// also cache the result for the requested path
		cache.set(relativeDirectoryPath, result);

		return result;
	}

}

exports.ConfigArray = ConfigArray;
exports.ConfigArraySymbol = ConfigArraySymbol;


@humanwhocodes/config-array

description: Glob-based configuration matching.

author: Nicholas C. Zakas

type: git

build: rollup -c

format: nitpik

lint: eslint *.config.js src/*.js tests/*.js

lint:fix: eslint --fix *.config.js src/*.js tests/*.js

prepublish: npm run build

test:coverage: nyc --include src/*.js npm run test

test: mocha -r esm tests/ --recursive

pre-commit: lint-staged

*.js: eslint --fix --ignore-pattern '!.eslintrc.js'

keywords:
configuration
configarray
config file


code:
# Changelog

## [1.0.1](https://github.com/humanwhocodes/module-importer/compare/v1.0.0...v1.0.1) (2022-08-18)

### Bug Fixes

* Ensure CommonJS mode works correctly. ([cf54a0b](https://github.com/humanwhocodes/module-importer/commit/cf54a0b998085066fbe1776dd0b4cacd808cc192)), closes [#6](https://github.com/humanwhocodes/module-importer/issues/6)

## 1.0.0 (2022-08-17)

### Features

* Implement ModuleImporter ([3ce4e82](https://www.github.com/humanwhocodes/module-importer/commit/3ce4e820c30c114e787bfed00a0966ac4772f563))


code:
# ModuleImporter

by [Nicholas C. Zakas](https://humanwhocodes.com)

If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).

## Description

A utility for seamlessly importing modules in Node.js regardless if they are CommonJS or ESM format. Under the hood, this uses `import()` and relies on Node.js's CommonJS compatibility to work correctly. This ensures that the correct locations and formats are used for CommonJS so you can call one method and not worry about any compatibility issues.

The problem with the default `import()` is that it always resolves relative to the file location in which it is called. If you want to resolve from a different location, you need to jump through a few hoops to achieve that. This package makes it easy to both resolve and import modules from any directory.

## Usage

### Node.js

Install using [npm][npm] or [yarn][yarn]:

```
npm install @humanwhocodes/module-importer

# or

yarn add @humanwhocodes/module-importer
```

Import into your Node.js project:

```js
// CommonJS
const { ModuleImporter } = require("@humanwhocodes/module-importer");

// ESM
import { ModuleImporter } from "@humanwhocodes/module-importer";
```

### Bun

Install using this command:

```
bun add @humanwhocodes/module-importer
```

Import into your Bun project:

```js
import { ModuleImporter } from "@humanwhocodes/module-importer";
```

## API

After importing, create a new instance of `ModuleImporter` to start emitting events:

```js
// cwd can be omitted to use process.cwd()
const importer = new ModuleImporter(cwd);

// you can resolve the location of any package
const location = importer.resolve("./some-file.cjs");

// you can also import directly
const module = importer.import("./some-file.cjs");
```

For both `resolve()` and `import()`, you can pass in package names and filenames.

## Developer Setup

1. Fork the repository
2. Clone your fork
3. Run `npm install` to setup dependencies
4. Run `npm test` to run tests

## License

Apache 2.0

[npm]: https://npmjs.com/
[yarn]: https://yarnpkg.com/


code:
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var module$1 = require('module');
var url = require('url');
var path = require('path');

/**
 * @fileoverview Universal module importer
 */

//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------

const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('module-importer.cjs', document.baseURI).href)));
const __dirname$1 = path.dirname(__filename$1);
const require$1 = module$1.createRequire(__dirname$1 + "/");
const { ModuleImporter } = require$1("./module-importer.cjs");

exports.ModuleImporter = ModuleImporter;


code:
export class ModuleImporter {
    /**
     * Creates a new instance.
     * @param {string} [cwd] The current working directory to resolve from.
     */
    constructor(cwd?: string);
    /**
     * The base directory from which paths should be resolved.
     * @type {string}
     */
    cwd: string;
    /**
     * Resolves a module based on its name or location.
     * @param {string} specifier Either an npm package name or
     *      relative file path.
     * @returns {string|undefined} The location of the import.
     * @throws {Error} If specifier cannot be located.
     */
    resolve(specifier: string): string | undefined;
    /**
     * Imports a module based on its name or location.
     * @param {string} specifier Either an npm package name or
     *      relative file path.
     * @returns {Promise<object>} The module's object.
     */
    import(specifier: string): Promise<object>;
}


code:
export { ModuleImporter };
import { ModuleImporter } from "./module-importer.cjs";


code:
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

/**
 * @fileoverview Universal module importer
 */

//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const require = createRequire(__dirname + "/");
const { ModuleImporter } = require("./module-importer.cjs");

export { ModuleImporter };


@humanwhocodes/module-importer

description: Universal module importer for Node.js

type: module

files: dist src

access: public

type: github

build: rollup -c && tsc

prepare: npm run build

lint: eslint src/ tests/

test:unit: c8 mocha tests/module-importer.test.js

test:build: node tests/pkg.test.cjs && node tests/pkg.test.mjs

test: npm run test:unit && npm run test:build

keywords: modules esm commonjs

author: Nicholas C. Zaks


code:
/**
 * @fileoverview Universal module importer
 */

//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------

const { createRequire } = require("module");
const { pathToFileURL }  = require("url");

//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------

const SLASHES = new Set(["/", "\\"]);

/**
 * Normalizes directories to have a trailing slash.
 * Resolve is pretty finicky -- if the directory name doesn't have
 * a trailing slash then it tries to look in the parent directory.
 * i.e., if the directory is "/usr/nzakas/foo" it will start the
 * search in /usr/nzakas. However, if the directory is "/user/nzakas/foo/",
 * then it will start the search in /user/nzakas/foo.
 * @param {string} directory The directory to check.
 * @returns {string} The normalized directory.
 */
function normalizeDirectory(directory) {
    if (!SLASHES.has(directory[directory.length-1])) {
        return directory + "/";
    }

    return directory;
}

//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------

/**
 * Class for importing both CommonJS and ESM modules in Node.js.
 */
exports.ModuleImporter = class ModuleImporter {

    /**
     * Creates a new instance.
     * @param {string} [cwd] The current working directory to resolve from.
     */
    constructor(cwd = process.cwd()) {

        /**
         * The base directory from which paths should be resolved.
         * @type {string}
         */
        this.cwd = normalizeDirectory(cwd);
    }

    /**
     * Resolves a module based on its name or location.
     * @param {string} specifier Either an npm package name or
     *      relative file path.
     * @returns {string|undefined} The location of the import.
     * @throws {Error} If specifier cannot be located.
     */
    resolve(specifier) {
        const require = createRequire(this.cwd);
        return require.resolve(specifier);
    }

    /**
     * Imports a module based on its name or location.
     * @param {string} specifier Either an npm package name or
     *      relative file path.
     * @returns {Promise<object>} The module's object.
     */
    import(specifier) {
        const location = this.resolve(specifier);
        return import(pathToFileURL(location).href);
    }

}


code:
# Changelog

## [2.0.3](https://github.com/humanwhocodes/object-schema/compare/v2.0.2...v2.0.3) (2024-04-01)

### Bug Fixes

* Ensure test files are not including in package ([6eeb32c](https://github.com/humanwhocodes/object-schema/commit/6eeb32cc76a3e37d76b2990bd603d72061c816e0)), closes [#19](https://github.com/humanwhocodes/object-schema/issues/19)

## [2.0.2](https://github.com/humanwhocodes/object-schema/compare/v2.0.1...v2.0.2) (2024-01-10)

### Bug Fixes

* WrapperError should be an actual error ([2523f01](https://github.com/humanwhocodes/object-schema/commit/2523f014168167e5a40bb63e0cc03231b2c0f1bf))

## [2.0.1](https://github.com/humanwhocodes/object-schema/compare/v2.0.0...v2.0.1) (2023-10-20)

### Bug Fixes

* Custom properties should be available on thrown errors ([6ca80b0](https://github.com/humanwhocodes/object-schema/commit/6ca80b001a4ffb678b9b5544fc53322117374376))

## [2.0.0](https://github.com/humanwhocodes/object-schema/compare/v1.2.1...v2.0.0) (2023-10-18)

### ⚠ BREAKING CHANGES

* Throw custom errors instead of generics.

### Features

* Throw custom errors instead of generics. ([c6c01d7](https://github.com/humanwhocodes/object-schema/commit/c6c01d71eb354bf7b1fb3e883c40f7bd9b61647c))

### [1.2.1](https://www.github.com/humanwhocodes/object-schema/compare/v1.2.0...v1.2.1) (2021-11-02)

### Bug Fixes

* Never return original object from individual config ([5463c5c](https://www.github.com/humanwhocodes/object-schema/commit/5463c5c6d2cb35a7b7948dffc37c899a41d1775f))