UNPKG

@interaktiv/mibuilder-core

Version:

Core libraries to interact with MiBuilder projects.

503 lines (413 loc) 13.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.UX = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _dxl = require("@interaktiv/dxl"); var _mibuilderColors = require("@interaktiv/mibuilder-colors"); var _types = require("@interaktiv/types"); var _screen = require("@oclif/screen"); var _cliUx = require("cli-ux"); var _inquirer = _interopRequireDefault(require("inquirer")); var _logSymbols = _interopRequireDefault(require("log-symbols")); var _wrapAnsi = _interopRequireDefault(require("wrap-ansi")); var _logger = require("./logger/logger"); const ICONS = (0, _extends2.default)({}, _logSymbols.default, { question: _mibuilderColors.color.cyan('?') }); const JSON_STRINGIFY_SPACES_COUNT = 2; class UX { static getWarnings() { return UX.warnings; } /** * Formats a deprecation warning for display to `stderr`, `stdout`, and/or * logs. * * @param {DeprecationDefinition} def The definition for the deprecated object. * @return {string} The formatted deprecation message. */ static formatDeprecationWarning(def) { let msg; if ((0, _types.has)(def, 'version')) { const version = (0, _types.isString)(def.version) ? parseInt(def.version, 10) : def.version || 0; const { type, name } = def.type; msg = `The ${type} "${name}" has been deprecated and will be removed in v${version + 1}.0 or later.`; } else { msg = def.messageOverride; } if (def.to) msg += ` Use "${def.to}" instead.`; if (def.message) msg += ` ${def.message}`; return msg; } static setBannerEnabled(enabled) { if (enabled != null) UX.showBanner = !!enabled; return UX.showBanner; } static welcome(pkg) { if (UX.showBanner === false) { UX.showBanner = false; return; } _cliUx.cli.info(`\n${_mibuilderColors.color.blue('MiBuilder Command Line Interface')}, version ${pkg.version}\nCopyright (c) 2014-${new Date().getFullYear()}, die.interaktiven GmbH & Co. KG. All Rights Reserved.\n`); } static async create(name) { const newUx = new UX(); newUx.logger = await _logger.Logger.child(name); if (newUx.silent) newUx.setLevel(_logger.LEVEL_ERROR); return newUx; } /** * Do not directly construct instances of this class -- use {@link UX.create} * instead. * * @constructor * @param {String} _name Legacy param not used currently * @param {Object} opts The options for this instance */ constructor(_name, opts = {}) { const { logger, silent, ux } = opts; this.cli = ux || _cliUx.cli; this.logger = logger; this.setLevel(_logger.LEVEL_WARN); if ((0, _types.isBoolean)(silent)) { this.silent = silent; } else { // Respect the --json flag and MIBUILDER_CONTENT_TYPE for consumers who // don't explicitly check const isContentTypeJson = _dxl.env.getString('MIBUILDER_CONTENT_TYPE', '').toUpperCase() === 'JSON'; this._silent = process.argv.find(arg => arg === '--json') || isContentTypeJson; } } get logger() { return this._logger; } set logger(value) { if ((0, _types.isObject)(value)) this._logger = value; } get silent() { return this._silent; } set silent(value) { if ((0, _types.isBoolean)(value) === false) return; this._silent = value; } get cli() { return this._cli; } set cli(value) { if ((0, _types.isObject)(value)) this._cli = value; } setBannerEnabled(value) { return UX.setBannerEnabled(value); } welcome(pkg) { UX.welcome(pkg); } /** * Logs at `INFO` level and conditionally writes to `stdout` if stream output is enabled. * * @param {*} args The messages or objects to log. * @return {Object<UX>} this */ log(...args) { if (!this.silent) this.cli.log(...args); // Log to mibuilder.log after the console as log filtering mutates the args. this.logger.info(...args); return this; } info(...args) { return this.log(...args); } /** * Log JSON to stdout and to the log file with log level info. * * @throws {TypeError} If the object is not JSON-serializable. * @param {object} obj The object to log -- must be serializable as JSON. * @return {Object<UX>} this */ logJson(obj) { this.cli.styledJSON(obj); // Log to mibuilder.log after the console as log filtering mutates the args. this.logger.info(obj); return this; } /** * Prompt the user for input. * * @param {string} key The string that the user sees when prompted for information. * @param {Object<IPromptOptions>} options A prompt option configuration. * @return {Promise<string>} The user input to the prompt. */ prompt(key, options = {}) { if ((0, _types.isArray)(options.options)) { this.stopSpinner(`${_mibuilderColors.color.cyan(ICONS.question)}\n`); return _inquirer.default.prompt({ type: 'rawlist', name: options.name || 'list', message: key, choices: options.options, default: options.default, prefix: options.prefix || '' }); } return this.cli.prompt(key, options); } async confirm(message, options = {}) { this.stopSpinner(`${_mibuilderColors.color.cyan(ICONS.question)}\n`); const { name = 'confirmation' } = options; const answers = await _inquirer.default.prompt({ type: 'confirm', name, message, default: (0, _types.isBoolean)(options.default) ? options.default : true, prefix: options.prefix }); return !!answers[name]; } /** * Start a spinner action after displaying the given message. * * @param {string} message The message displayed to the user. */ startSpinner(message) { if (this.silent) return; this.cli.action.start(message); } /** * Pause the spinner and call the given function. * * @param {function} fn The function to be called in the pause. * @param {string} icon The string displayed to the user. * @return {*} The result returned by the passed in function. */ pauseSpinner(fn, icon) { if (this.silent) return null; return this.cli.action.pause(fn, icon); } /** * Update the spinner status. * * @param {string} status The message displayed to the user. */ setSpinnerStatus(status) { if (this.silent) return; this.cli.action.status = status; } /** * Get the spinner status. * * @return {string} The status of the spinner */ getSpinnerStatus() { if (this.silent) return null; return this.cli.action.status; } /** * Stop the spinner action. * * @param {string} [message] The message displayed to the user. */ stopSpinner(message) { if (this.silent) return; this.cli.action.stop(message || ICONS.success); } /** * Logs a warning as `WARN` level and conditionally writes to `stderr` if the log * level is `WARN` or above and stream output is enabled. The message is added * to the static {@link warnings} set if stream output is _not_ enabled, for later * consumption and manipulation. * * @see warnings * @param {string} msg The warning message to output. * @return {Object<UX>} this */ warn(msg) { // Necessarily log to log file this.logger.warn(msg); UX.warnings.add(msg); if (this.silent === false) this.cli.warn(msg); return this; } important(msg) { // Necessarily log to log file this.logger.warn(msg); if (this.silent === false) this.cli.warn(msg); return this; } /** * Logs an error at `ERROR` level and conditionally writes to `stderr` if stream * output is enabled. * * @param {*} args The errors to log. * @return {Object<UX>} this */ error(...args) { if (this.silent === false) { args.filter(Boolean).forEach(msg => this.cli.error(msg, { exit: false })); } this.logger.error(...args); return this; } /** * Logs an object as JSON at `ERROR` level and to `stderr`. * * @throws {TypeError} If the object is not JSON-serializable. * @param {object} obj The error object to log -- must be serializable as JSON. * @return {Object<UX>} this */ errorJson(obj) { const err = JSON.stringify(obj, null, JSON_STRINGIFY_SPACES_COUNT); // eslint-disable-next-line no-console if (this.silent === false) console.error(err); this.logger.error(err); return this; } /** * Logs at `INFO` level and conditionally writes to `stdout` in a table format if * stream output is enabled. * * @param {object[]} rows The rows of data to be output in table format. * @param {TableOptions} options The {@link TableOptions} to use for formatting. * @return {Object<UX>} */ // tslint:disable-next-line no-any (matches oclif) table(rows, columns = {}, options = {}) { if (this.silent === false) { // This is either an array of column names or an already built Partial<OclifTableOptions> if ((0, _types.isArray)(columns)) { const tableColumns = columns.map(col => ({ key: col, label: col.split(/(?=[A-Z])|[-_\s]/).map(w => w.toUpperCase()).join(' ') })); const columnsConfig = tableColumns.reduce((cols, current) => { return (0, _extends2.default)({}, cols, { [current.key]: { header: current.label } }); }, {}); this.cli.table(rows, columnsConfig); } else { this.cli.table(rows, columns, options); } } // Log after table output as log filtering mutates data. this.logger.info(rows); return this; } /** * Logs at `INFO` level and conditionally writes to `stdout` in a styled object format if * stream output is enabled. * * @param {object} obj The object to be styled for stdout. * @param {string[]} [keys] The object keys to be written to stdout. * @return {Object<UX>} this */ styledObject(obj, keys) { this.logger.info(obj); if (this.silent === false) this.cli.styledObject(obj, keys); return this; } /** * Log at `INFO` level and conditionally write to `stdout` in styled JSON format if * stream output is enabled. * * @param {object} obj The object to be styled for stdout. * @return {Object<UX>} this */ styledJSON(obj) { this.logger.info(obj); if (this.silent === false) this.cli.styledJSON(obj); return this; } /** * Logs at `INFO` level and conditionally writes to `stdout` in a styled header format if * stream output is enabled. * * @param {string} header The header to be styled. * @return {Object<UX>} this */ styledHeader(header) { this.logger.info(header); if (this.silent === false) this.cli.styledHeader(header); return this; } styledBanner(message) { this.logger.info(message); if (this.silent === false) { const maxWidth = _screen.stdtermwidth; const minWidth = 0; let output = '-----------------------------------------------------------------------------'.substr(minWidth, maxWidth); output += '\n'; output += (0, _wrapAnsi.default)(message, maxWidth, { trim: false, wordWrap: true }); output += '\n'; output += '-----------------------------------------------------------------------------'.substr(minWidth, maxWidth); output += '\n'; this.cli.log(output); } return this; } open(target, openOptions = {}) { if (this.silent === true) return; this.cli.open(target, openOptions); } debug(...args) { return this.logger.debug(...args); } fatal(...args) { return this.logger.fatal(...args); } silly(...args) { if (this.silent === false) this.cli.trace(...args); this.logger.silly(...args); } trace(...args) { return this.silly(...args); } wait(...args) { return this.cli.wait(...args); } setLevel(level) { if (this.logger) this.logger.setLevel(level); this.cli.config.outputLevel = level; } logCommand(message) { this.cli.info(_mibuilderColors.color.cmd(message)); } logCommandOutput(message) { message.split('\n').forEach(msg => { let prefix = ''; if (_dxl.env.getBoolean('MIBUILDER_DISABLE_OUTPUT_FORMAT', false) === false) { prefix = msg.includes('▸') ? '' : '▸ '; } this.cli.info(`${prefix}${_mibuilderColors.color.magenta(msg)}`); }); } pressToContinue(...args) { return this.cli.anykey(...args); } } exports.UX = UX; UX.Icons = ICONS; UX.LEVEL_ERROR = _logger.LEVEL_ERROR; UX.LEVEL_WARN = _logger.LEVEL_WARN; UX.LEVEL_INFO = _logger.LEVEL_INFO; UX.LEVEL_HTTP = _logger.LEVEL_HTTP; UX.LEVEL_VERBOSE = _logger.LEVEL_VERBOSE; UX.LEVEL_DEBUG = _logger.LEVEL_DEBUG; UX.LEVEL_SILLY = _logger.LEVEL_SILLY; UX.showBanner = true; UX.warnings = new Set();