concurrently
Version:
Run commands concurrently
242 lines (241 loc) • 9.9 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Logger = void 0;
const chalk_1 = __importDefault(require("chalk"));
const lodash_1 = __importDefault(require("lodash"));
const Rx = __importStar(require("rxjs"));
const date_format_1 = require("./date-format");
const defaults = __importStar(require("./defaults"));
const defaultChalk = chalk_1.default;
const noColorChalk = new chalk_1.default.Instance({ level: 0 });
class Logger {
hide;
raw;
prefixFormat;
commandLength;
dateFormatter;
chalk = defaultChalk;
/**
* How many characters should a prefix have.
* Prefixes shorter than this will be padded with spaces to the right.
*/
prefixLength = 0;
/**
* Last character emitted, and from which command.
* If `undefined`, then nothing has been logged yet.
*/
lastWrite;
/**
* Observable that emits when there's been output logged.
* If `command` is is `undefined`, then the log is for a global event.
*/
output = new Rx.Subject();
constructor({ hide, prefixFormat, commandLength, raw = false, timestampFormat, }) {
this.hide = (hide || []).map(String);
this.raw = raw;
this.prefixFormat = prefixFormat;
this.commandLength = commandLength || defaults.prefixLength;
this.dateFormatter = new date_format_1.DateFormatter(timestampFormat || defaults.timestampFormat);
}
/**
* Toggles colors on/off globally.
*/
toggleColors(on) {
this.chalk = on ? defaultChalk : noColorChalk;
}
shortenText(text) {
if (!text || text.length <= this.commandLength) {
return text;
}
const ellipsis = '..';
const prefixLength = this.commandLength - ellipsis.length;
const endLength = Math.floor(prefixLength / 2);
const beginningLength = prefixLength - endLength;
const beginnning = text.slice(0, beginningLength);
const end = text.slice(text.length - endLength, text.length);
return beginnning + ellipsis + end;
}
getPrefixesFor(command) {
return {
// When there's limited concurrency, the PID might not be immediately available,
// so avoid the string 'undefined' from becoming a prefix
pid: command.pid != null ? String(command.pid) : '',
index: String(command.index),
name: command.name,
command: this.shortenText(command.command),
time: this.dateFormatter.format(new Date()),
};
}
getPrefixContent(command) {
const prefix = this.prefixFormat || (command.name ? 'name' : 'index');
if (prefix === 'none') {
return;
}
const prefixes = this.getPrefixesFor(command);
if (Object.keys(prefixes).includes(prefix)) {
return { type: 'default', value: prefixes[prefix] };
}
const value = lodash_1.default.reduce(prefixes, (prev, val, key) => {
const keyRegex = new RegExp(lodash_1.default.escapeRegExp(`{${key}}`), 'g');
return prev.replace(keyRegex, String(val));
}, prefix);
return { type: 'template', value };
}
getPrefix(command) {
const content = this.getPrefixContent(command);
if (!content) {
return '';
}
return content.type === 'template'
? content.value.padEnd(this.prefixLength, ' ')
: `[${content.value.padEnd(this.prefixLength, ' ')}]`;
}
setPrefixLength(length) {
this.prefixLength = length;
}
colorText(command, text) {
let color;
if (command.prefixColor && command.prefixColor.startsWith('#')) {
color = this.chalk.hex(command.prefixColor);
}
else {
const defaultColor = lodash_1.default.get(this.chalk, defaults.prefixColors, this.chalk.reset);
color = lodash_1.default.get(this.chalk, command.prefixColor ?? '', defaultColor);
}
return color(text);
}
/**
* Logs an event for a command (e.g. start, stop).
*
* If raw mode is on, then nothing is logged.
*/
logCommandEvent(text, command) {
if (this.raw) {
return;
}
// Last write was from this command, but it didn't end with a line feed.
// Prepend one, otherwise the event's text will be concatenated to that write.
// A line feed is otherwise inserted anyway.
let prefix = '';
if (this.lastWrite?.command === command && this.lastWrite.char !== '\n') {
prefix = '\n';
}
this.logCommandText(prefix + this.chalk.reset(text) + '\n', command);
}
logCommandText(text, command) {
if (this.hide.includes(String(command.index)) || this.hide.includes(command.name)) {
return;
}
const prefix = this.colorText(command, this.getPrefix(command));
return this.log(prefix + (prefix ? ' ' : ''), text, command);
}
/**
* Logs a global event (e.g. sending signals to processes).
*
* If raw mode is on, then nothing is logged.
*/
logGlobalEvent(text) {
if (this.raw) {
return;
}
this.log(this.chalk.reset('-->') + ' ', this.chalk.reset(text) + '\n');
}
/**
* Logs a table from an input object array, like `console.table`.
*
* Each row is a single input item, and they are presented in the input order.
*/
logTable(tableContents) {
// For now, can only print array tables with some content.
if (this.raw || !Array.isArray(tableContents) || !tableContents.length) {
return;
}
let nextColIndex = 0;
const headers = {};
const contentRows = tableContents.map((row) => {
const rowContents = [];
Object.keys(row).forEach((col) => {
if (!headers[col]) {
headers[col] = {
index: nextColIndex++,
length: col.length,
};
}
const colIndex = headers[col].index;
const formattedValue = String(row[col] == null ? '' : row[col]);
// Update the column length in case this rows value is longer than the previous length for the column.
headers[col].length = Math.max(formattedValue.length, headers[col].length);
rowContents[colIndex] = formattedValue;
return rowContents;
});
return rowContents;
});
const headersFormatted = Object.keys(headers).map((header) => header.padEnd(headers[header].length, ' '));
if (!headersFormatted.length) {
// No columns exist.
return;
}
const borderRowFormatted = headersFormatted.map((header) => '─'.padEnd(header.length, '─'));
this.logGlobalEvent(`┌─${borderRowFormatted.join('─┬─')}─┐`);
this.logGlobalEvent(`│ ${headersFormatted.join(' │ ')} │`);
this.logGlobalEvent(`├─${borderRowFormatted.join('─┼─')}─┤`);
contentRows.forEach((contentRow) => {
const contentRowFormatted = headersFormatted.map((header, colIndex) => {
// If the table was expanded after this row was processed, it won't have this column.
// Use an empty string in this case.
const col = contentRow[colIndex] || '';
return col.padEnd(header.length, ' ');
});
this.logGlobalEvent(`│ ${contentRowFormatted.join(' │ ')} │`);
});
this.logGlobalEvent(`└─${borderRowFormatted.join('─┴─')}─┘`);
}
log(prefix, text, command) {
if (this.raw) {
return this.emit(command, text);
}
// #70 - replace some ANSI code that would impact clearing lines
text = text.replace(/\u2026/g, '...');
// This write's interrupting another command, emit a line feed to start clean.
if (this.lastWrite && this.lastWrite.command !== command && this.lastWrite.char !== '\n') {
this.emit(this.lastWrite.command, '\n');
}
// Clean lines should emit a prefix
if (!this.lastWrite || this.lastWrite.char === '\n') {
this.emit(command, prefix);
}
const textToWrite = text.replaceAll('\n', (lf, i) => lf + (text[i + 1] ? prefix : ''));
this.emit(command, textToWrite);
}
emit(command, text) {
this.lastWrite = { command, char: text[text.length - 1] };
this.output.next({ command, text });
}
}
exports.Logger = Logger;