UNPKG

easy-cli-framework

Version:

A framework for building CLI applications that are robust and easy to maintain. Supports theming, configuration files, interactive prompts, and more.

309 lines (306 loc) 10.2 kB
'use strict'; /** * Removes circular references from an object recursively * * @param {Object} obj - The object to remove circular references from * @returns {Object} - The object with circular references removed */ const removeCircularReferences = (obj) => { const seen = new Map(); const recurse = (obj) => { seen.set(obj, true); if (Array.isArray(obj)) { return obj.map((v) => { if (typeof v !== 'object') { return v; } if (seen.has(v)) { return '[Circular Reference]'; } else { return recurse(v); } }); } return Object.entries(obj).reduce((acc, [k, v]) => { if (typeof v !== 'object') { acc[k] = v; return acc; } if (seen.has(v)) { acc[k] = '[Circular Reference]'; return acc; } else { acc[k] = recurse(v); return acc; } }, {}); }; return recurse(obj); }; /** * Outputs a log to the console * * @param {SupportedLogType} type - The type of log to output * @param {string} log - The log to output */ const outputLog = (type, log) => { if (type === 'success') { console.log(log); return; } console[type](log); }; /** * A response from a logger * This is used to allow for forcing a log to be output using the `force` method * * @class EasyCLILoggerResponses * @property {string} log - The log that was output * @property {SupportedLogType} type - The type of log that was output * @property {boolean} logged - Whether the log was output * @property {function} force - Forces the log to be output * * @example * ```typescript * const logger = new EasyCLILogger({ theme: new EasyCLITheme(), verbosity: 0 }); * logger.log('Hello, world!'); // Won't be logged because verbosity is 0 * logger.log('Hello, world!').force(); // Will be logged * ``` */ class EasyCLILoggerResponse { constructor(log, type, logged = false) { this.log = log; this.type = type; this.logged = logged; /** * Forces the log to be output. This is useful if you want to output a log even if the verbosity is too low. * * @example * ```typescript * const logger = new EasyCLILogger({ theme: new EasyCLITheme(), verbosity: 0 }); * logger.log('Hello, world!'); // Won't be logged because verbosity is 0 * logger.log('Hello, world!').force(); // Will be logged * ``` */ this.force = () => { if (this.logged) return; // Already logged outputLog(this.type, this.log); }; } } /** * A logger for use with CLI applicatiions. This logger allows for logging with different verbosity levels and themes * * @class EasyCLILogger * * @example * ```typescript * const logger = new EasyCLILogger({ theme: new EasyCLITheme(), verbosity: 0, timestamp: false }); * logger.log('Hello, world!'); // Won't be logged because verbosity is 0 * logger.log('Hello, world!').force(); // Will be logged due to force * logger.warn('This is a warning!'); // Won't be logged because verbosity is 0 * logger.error('This is an error!') // Will be logged * * const logs = logger.getExecutionLogs(); * * ``` */ class EasyCLILogger { /** * Instantiates a new logger with the given theme and verbosity level. * * @param {EasyCLILoggerProps} options - The configuration props for the logger * * @example * ```typescript * { * theme: new EasyCLITheme(), * verbosity?: 0, * verbosityThresholds?: { * error: 0, // Always log errors * success: 0, // Always log success * warn: 1, // Log warnings when verbosity is 1 or higher * log: 2, // Log logs when verbosity is 2 or higher * info: 3, // Log info when verbosity is 3 or higher * }, * } * ``` */ constructor({ theme, verbosity = 0, verbosityThresholds = { error: 0, success: 0, warn: 1, log: 2, info: 3, }, timestamp = true, }) { this.logs = []; this.theme = theme; this.verbosity = verbosity; this.verbosityThresholds = verbosityThresholds; this.timestamp = timestamp; } /** * Converts the arguments to strings, to be able to handle similar to how console.log works. * * @param args Converts the arguments to a string, removing circular references. * @returns The arguments as a string. */ convertArgArrayToString(args) { return args .map(arg => { if (typeof arg === 'object') { // Removes circular references so it's safe to log const cleanObj = removeCircularReferences(arg); return JSON.stringify(cleanObj, null, 2); // Pretty print } return `${arg}`; }) .join(', '); } /** * Saves a log to the internal log array for use with the `getExecutionLogs` method. * * @param {SupportedLogType} type - The type of log to save * @param {string} message - The message to save */ saveLog(type, message) { const timestamp = this.timestamp ? ` ${new Date().toISOString()}:` : ''; this.logs.push(`[${type.toLocaleUpperCase()}]${timestamp} ${message}`); } /** * An internal method to process a log, saving it and outputting it to the console if the verbosity is high enough. * * @param {SupportedLogType} type - The type of log to process * @param {unknown[]} args - The arguments to process * @returns {EasyCLILoggerResponse} - The response from the logger */ processLog(type, ...args) { const clean = this.convertArgArrayToString(args); this.saveLog(type, clean); const formatted = this.theme.formattedString(clean, type); const shouldLog = this.verbosity >= this.verbosityThresholds[type]; if (shouldLog) { outputLog(type, formatted); } return new EasyCLILoggerResponse(formatted, type, shouldLog); } /** * Writes a log to the console depending on the verbosity level, using the log display options. * * @param {unknown[]} args - The arguments to log * @returns {EasyCLILoggerResponse} - The response from the logger * * @example * ```typescript * logger.log('Hello, world!'); * ``` */ log(...args) { return this.processLog('log', ...args); } /** *Writes a warning to the console depending on the verbosity level, using the log display options. * * @param {unknown[]} args - The arguments to log * * @returns {EasyCLILoggerResponse} - The response from the logger * @example * ```typescript * logger.warn('Hello, world!'); * ``` */ warn(...args) { return this.processLog('warn', ...args); } /** * Writes an info message to the console depending on the verbosity level, using the log display options. * * @param {unknown[]} args - The arguments to log * * @returns {EasyCLILoggerResponse} - The response from the logger * * @example * ```typescript * logger.info('Hello, world!'); * ``` */ info(...args) { return this.processLog('info', ...args); } /** * Writes an error to the console depending on the verbosity level, using the log display options. * * @param {unknown[]} args - The arguments to log * * @returns {EasyCLILoggerResponse} - The response from the logger * * @example * ```typescript * logger.error('Hello, world!'); * ``` */ error(...args) { return this.processLog('error', ...args); } /** * Writes a success to the console depending on the verbosity level, using the log display options. * * @param {unknown[]} args - The arguments to log * * @returns {EasyCLILoggerResponse} - The response from the logger * * @example * ```typescript * logger.success('Hello, world!'); * ``` */ success(...args) { return this.processLog('success', ...args); } /** * Takes a list of arguments and prints them to the console in the format provided. * * @param {(string | { text: string; format: DisplayOptions })[]} args - The arguments to print * * @example * ```typescript * // Prints Hello World! in the default format and then in the info format * logger.printFormattedString('Hello, world!', { text: 'Hello, world!', format: 'info' }); * ``` */ printFormattedString(...args) { console.log(args .map(arg => typeof arg === 'string' ? this.theme.formattedString(arg, 'default') : this.theme.formattedString(arg.text, arg.format)) .join('')); } /** * Gets the execution logs, including logs that were not output due to verbosity. * This is useful for debugging and logging to a file after execution. * * @returns {string[]} - The execution logs * * @example * ```typescript * const logger = new EasyCLILogger({ theme: new EasyCLITheme(), verbosity: 0 }); * logger.log('Hello, world!'); // Won't be logged because verbosity is 0 * logger.log('Hello, world!').force(); // Will be logged * logger.warn('This is a warning!'); // Won't be logged because verbosity is 0 * logger.error('This is an error!') // Will be logged * * const logs = logger.getExecutionLogs(); * * console.log(logs); * // Will display, all logs, including those that weren't output. * ``` */ getExecutionLogs() { return this.logs; } } exports.EasyCLILogger = EasyCLILogger; exports.EasyCLILoggerResponse = EasyCLILoggerResponse;