0
\$\begingroup\$

I just finished creating my first npm package (for learning, not competition) and published it. Now I want to ask some questions, for example: Is my method of writing code good (for later maintenance and error handling)? Is there something better I can do to improve my package (and to improve my upcoming packages as well)?

I would be happy if someone with experience in this matter could leave me an explanatory comment.

Code:

index.js

/**
 * @file                                index
 * @description                         Exports the module
 *
 * @version                             0.0.1
 *
 * @note                                Follows the `MECS` code style
*/


/* ---------------------------------- PACK ---------------------------------- */

    const get                           = require('./src/modules/get');
    const set                           = require('./src/modules/set');
    const prop                          = require('./src/modules/prop');

/* ---------------------------------- ---- ---------------------------------- */


/* ---------------------------------- CORE ---------------------------------- */

    const ansi                          =
    {
        get,
        set,
        prop,
    };

    module.exports                      = ansi;

/* ---------------------------------- ---- ---------------------------------- */

get.js

/**
 * @file                                get
 * @description                         Get anything about ANSI
 *
 * @version                             0.0.1
 *
 * @note                                Follows the `MECS` code style
*/


/* ---------------------------------- PACK ---------------------------------- */

    const M_codes                       = require('../json/ansi-escape-list.json');

/* ---------------------------------- ---- ---------------------------------- */


/* ---------------------------------- CORE ---------------------------------- */

    /**
     * Gets anything about ANSI !
    */
    const get                           =
    {
        /**
         * Returns the object containing the ANSI escape codes.
        */
        codeOf                          : M_codes,

        /**
         * Retrieves the ANSI escape code for the specified attribute.
         *
         * @param {string} ref - The name of the attribute.
         * @throws {Error} If the input is not a string.
         * @return {string|undefined} The ANSI escape code for the specified attribute, or undefined if the attribute is not found.
        */
        attr
        : (ref) =>
        {
            // accept : string only
            if (!ref || typeof ref !== 'string')
            throw new Error('ref must be a not empty string');

            // output : string || undefined
            return get.codeOf.attr[ref];
        },

        /**
         * Retrieves the ANSI escape code for the specified color.
         *
         * @param {string|Array<number>} ref - The color name or RGB values.
         * @param {string} [_for='fg'] - The type of color ('fg' for foreground, 'bg' for background).
         * @throws {Error} If the input is not a valid color name or RGB values.
         * @return {string} The ANSI escape code for the specified color.
        */
        fg
        : (ref, _for = 'fg') =>
        {
            if(!ref)
            throw new Error('ref must be a not empty string/array');

            if(!_for)
            throw new Error('_for must be fg|bg');

            if (typeof ref === 'string')
            {
                // Check if the input is a predefined color name
                if (get.codeOf.colors[_for].hasOwnProperty(ref))
                {
                  return get.codeOf.colors[_for][ref];
                }

                // Check if the hex color code has valid length
                if (ref.length === 7 || ref.length === 4)
                {
                    let hex = ref.replace('#', '');

                    // Expand shorthand hex color codes
                    if (ref.length === 4)
                    {
                        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
                    }

                    const bigint = parseInt(hex, 16);
                    const r = (bigint >> 16) & 255;
                    const g = (bigint >> 8) & 255;
                    const b = bigint & 255;
                    return _for == 'fg' ? `\u001b[38;2;${r};${g};${b}m` : `\u001b[48;2;${r};${g};${b}m`;
                }

                else
                {
                    throw new Error('Invalid hex color code');
                }
            }

            else if (Array.isArray(ref) && ref.length === 3)
            {
                // Check if the input is an array representing RGB values
                const [r, g, b] = ref;
                if (typeof r === 'number' && typeof g === 'number' && typeof b === 'number')
                {
                  return _for == 'fg' ? `\u001b[38;2;${r};${g};${b}m` : `\u001b[48;2;${r};${g};${b}m`;
                }

                else
                {
                  throw new Error('RGB values must be numbers');
                }
            }

            else
            {
                throw new Error('ref must be a not empty string or array');
            }
        },

        /**
         * Retrieves the ANSI escape code for the specified background color.
         *
         * @param {string|Array<number>} ref - The color name or RGB values.
         * @throws {Error} If the input is not a valid color name or RGB values.
         * @return {string} The ANSI escape code for the specified color.
        */
        bg
        : (ref) =>
        {
            return get.fg(ref, 'bg');
        }
    };

    module.exports                      = get;

/* ---------------------------------- ---- ---------------------------------- */

set.js

/**
 * @file                                set
 * @description                         Set text to anything with ANSI
 *
 * @version                             0.0.1
 *
 * @note                                Follows the `MECS` code style
*/


/* ---------------------------------- PACK ---------------------------------- */

    const M_codes                       = require('../json/ansi-escape-list.json');
    const get                           = require('./get');

/* ---------------------------------- ---- ---------------------------------- */


/* ---------------------------------- CORE ---------------------------------- */

    /**
     * Sets text to anything with ANSI !
    */
    const set                           =
    {
        /**
         * accept : str = string only, ref = string|array
         *
         * @param {string} str - description of parameter
         * @param {string|array} ref - description of parameter
         * @return {string} color + str + color reset
        */
        fg
        : (str, ref) =>
        {
            // accept : str = string only, ref = string|array
            if (!str || typeof str !== 'string')
            throw new Error('str must be a not empty string');

            if (!ref || (typeof ref !== 'string' ) && !Array.isArray(ref))
            throw new Error('ref must be a not empty string/array');

            // output : color + str + color reset
            return get.fg(ref) + str + get.codeOf.attr.reset;
        },

        /**
         * Sets the background color of a string and returns the modified string.
         *
         * @param {string} str - The string to modify.
         * @param {string|array} ref - The reference to the background color.
         * @return {string} The modified string with the background color applied.
        */
        bg
        : (str, ref) =>
        {
            // accept : str = string only, ref = string|array
            if (!str || typeof str !== 'string')
            throw new Error('str must be a not empty string');

            if (!ref || (typeof ref !== 'string' ) && !Array.isArray(ref))
            throw new Error('ref must be a not empty string/array');

            // output : bg color + str + bg color reset
            return get.bg(ref) + str + get.codeOf.attr.reset;
        },

        /**
         * Applies the specified attribute to a string and returns the modified string.
         *
         * @param {string} str - The string to modify.
         * @param {string|array} ref - The reference to the attribute.
         * @throws {Error} If str is not a non-empty string or ref is not a non-empty string or array.
         * @return {string} The modified string with the attribute applied.
        */
        attr
        : (str, ref) =>
        {
            // accept : str = string only, ref = string|array
            if (!str || typeof str !== 'string')
            throw new Error('str must be a not empty string');

            if (!ref || (typeof ref !== 'string' ) && !Array.isArray(ref))
            throw new Error('ref must be a not empty string/array');

            // output : attr + str + attr reset
            return get.attr(ref) + str + get.codeOf.attr.reset;
        },

        /**
         * Applies styles to a given string.
         *
         * @param {string} str - The string to apply styles to.
         * @param {object} styles - The styles to apply.
         * @param {string} styles.attr - The attribute style to apply.
         * @param {string} styles.bg - The background color style to apply.
         * @param {string} styles.fg - The foreground color style to apply.
         * @throws {Error} If str is not a non-empty string or styles is not an object.
         * @throws {Error} If styles.attr is not a string or styles.bg is not a string or styles.fg is not a string.
         * @return {string} The string with styles applied.
        */
        style
        : (str, styles) =>
        {
            if(!str || typeof str !== "string")
            throw new Error('str must be a not empty string');

            if(!styles || typeof styles !== "object")
            throw new Error('styles must be an object');

            // res
            let res = '';
            {
                // attr
                if(styles.attr)
                res += get.attr(styles.attr);

                // bg
                if(styles.bg)
                res += get.bg(styles.bg);

                // fg
                if(styles.fg || styles.fg)
                res += get.fg(styles.fg);

                // str
                res += str;

                // reset
                res += get.codeOf.attr.reset;
            }

            // output : res
            return res;
        },
    };

    module.exports                      = set;

/* ---------------------------------- ---- ---------------------------------- */

prop.js

/**
 * @file                                prop
 * @description                         properties
 *
 * @version                             0.0.1
 *
 * @note                                Follows the `MECS` code style
*/


/* ---------------------------------- PACK ---------------------------------- */

    const M_codes                       = require('../json/ansi-escape-list.json');
    const get                           = require('./get');
    const set                           = require('./set');

    const segmenter                     = new Intl.Segmenter();

/* ---------------------------------- ---- ---------------------------------- */


/* ---------------------------------- CORE ---------------------------------- */

    /**
     * Properties
    */
    const prop                          =
    {
        /**
         * Function to align the str in the line with a lot of options
         *
         * @param {string} str - The string to align.
         * @param {object} options - Options for alignment.
         * @param {number} options.length - The length of the line.
         * @param {string} options.fill - The character to fill the line with.
         * @param {string} options.align - The character to align with.
         * @param {number} options.padding - The space between the str and the line.
         * @param {number} options.steps - The space between the beginning and end of the line and the terminal itself (right-left).
         * @param {object} options.style - The style to apply.
         * @param {fg} options.style.fg - The foreground color to apply.
         * @param {bg} options.style.bg - The background color to apply.
         * @param {attr} options.style.attr - The attribute to apply.
         * @param {cfg} options.style.fg - The foreground color (for fill characters) to apply.
         * @throws {Error} If str is not a non-empty string or options is not an object.
         * @return {string} The aligned string.
        */
        align
        : (str, options) =>
        {
            const applyStyle = (_str, cond = false, mode = 'fg', value = null) =>
            {
                if(!cond || options.disableStyle)
                return _str;

                if(mode == 'attr')
                _str = set.attr(_str, value);

                if(mode == 'bg')
                _str = set.bg(_str, value);

                if(mode == 'fg')
                _str = set.fg(_str, value);

                return _str
            }

            // accept : str = string only, options = object
            if (!str || typeof str !== 'string')
            throw new Error('str must be a not empty string');

            if (!options || (typeof options !== 'object' ))
            throw new Error('options must be a not empty object');

            // defaults
            {
                if(!options.length)
                options.length = 80

                if(!options.fill)
                options.fill = ' '

                if(!options.align)
                options.align = 'left'

                if(!options.padding)
                options.padding = 0

                if(!options.steps)
                options.steps = 0

                if(options.style === false || options.style === null)
                {
                    options.disableStyle = true
                }
                else
                {
                    options.disableStyle = false
                }

                if(!options.style)
                options.style =
                {
                    fg: options.disableStyle ? '' : 'white',
                    bg: options.disableStyle ? '' : 'black',
                    attr: options.disableStyle ? '' : 'normal',
                    cfg: options.disableStyle ? '' : 'gray'
                }
            }

            // process
            {
                let alignedStr = '';

                if(str.length >= options.length)
                return str

                if(options.padding)
                {
                    const padd = ' '.repeat(Math.floor((options.padding) / 2));
                    str = padd + str + padd;
                }

                if(options.align == 'left')
                {
                    alignedStr
                    = applyStyle(str, options.style.fg, 'fg', options.style.fg)
                    + applyStyle(options.fill.repeat(options.length - str.length), options.style.cfg, 'fg', options.style.cfg);
                }

                else if(options.align == 'right')
                {
                    alignedStr
                    = applyStyle(options.fill.repeat(options.length - str.length), options.style.cfg, 'fg', options.style.cfg)
                    + applyStyle (str, options.style.fg, 'fg', options.style.fg);
                }

                else
                {
                    const len
                    = Math.floor((options.length - str.length) / 2);
                    alignedStr
                    = applyStyle(options.fill.repeat(len), options.style.cfg, 'fg', options.style.cfg)
                    + applyStyle (str, options.style.fg, 'fg', options.style.fg)
                    + applyStyle(options.fill.repeat(len), options.style.cfg, 'fg', options.style.cfg);
                }

                if(options.steps)
                {
                    options.steps = Math.floor(options.steps / 2);
                    alignedStr
                    = ' '.repeat(options.steps)
                    + alignedStr
                    + ' '.repeat(options.steps);
                }
                return alignedStr;
            }
        },

        /**
         * Generates a regular expression for matching ANSI escape codes.
         * @param onlyFirst Whether to match only the first occurrence.
         * @returns {RegExp} A regular expression object.
        */
        getRegex
        : (onlyFirst) =>
        {
            const reg =
            [
                '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
                '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))'
            ]
            .join('|');

            return new RegExp(reg, onlyFirst ? undefined : 'g');
        },

        /**
         * Strips ANSI escape codes from a given string.
         *
         * @param {string} string - The input string to strip ANSI escape codes from.
         * @return {string} The input string with ANSI escape codes removed.
         * @throws {TypeError} If the input is not a string.
        */
        strip
        : (string) =>
        {
            const reg = prop.getRegex();

            if (typeof string !== 'string')
            throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);

            // Even though the regex is global, we don't need to reset the `.lastIndex`
            // because unlike `.exec()` and `.test()`, `.replace()` does it automatically
            // and doing it manually has a performance penalty.
            return string.replace(reg, '');
        },

        /**
         * Calculates the length of a given string, optionally excluding ANSI escape codes.
         *
         * @param {string} string - The input string to calculate the length of.
         * @param {boolean} countAnsiEscapeCodes - Whether to include ANSI escape codes in the length calculation.
         * @return {number} The length of the input string, excluding ANSI escape codes if `countAnsiEscapeCodes` is false.
        */
        length
        : (string, countAnsiEscapeCodes = false) =>
        {
            if (string === '')
            return 0;

            if (!countAnsiEscapeCodes)
            string = prop.strip(string);

            if (string === '')
            return 0;

            let length = 0;

            for (const _ of segmenter.segment(string))
            // eslint-disable-line no-unused-vars
            length++;

            return length;
        },

    };

    module.exports                      = prop;

/* ---------------------------------- ---- ---------------------------------- */

For more information and examples see the repo on GitHub and the package on npm

Also, for more information, you can read this post: The Wacky World of ANSI Escape Sequences: A Comedic Guide

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.