@interaktiv/mibuilder-core
Version:
Core libraries to interact with MiBuilder projects.
503 lines (413 loc) • 13.1 kB
JavaScript
"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();