@wix-pilot/core
Version:
A flexible plugin that drives your tests with human-written commands, enhanced by the power of large language models (LLMs)
341 lines (340 loc) • 12.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultLoggerDelegate = void 0;
const chalk_1 = __importDefault(require("chalk"));
const path_1 = __importDefault(require("path"));
const testEnvUtils_1 = require("../../common/cacheHandler/testEnvUtils");
const winston_1 = require("winston");
const stream_1 = require("stream");
const pathUtils_1 = require("./pathUtils");
/**
* Default logger delegate implementation using Winston
* Provides clean, formatted output to the console
*/
class DefaultLoggerDelegate {
logger;
constructor() {
// Create a custom output stream that writes directly to stdout
// This completely bypasses console.log
const outputStream = new stream_1.Writable({
write(chunk, encoding, callback) {
process.stdout.write(chunk);
callback();
},
});
// Create a custom transport using our direct stdout stream
const directStdoutTransport = new winston_1.transports.Stream({
stream: outputStream,
format: winston_1.format.printf(({ message }) => String(message)),
});
// Create the Winston logger with minimal formatting
this.logger = (0, winston_1.createLogger)({
level: "info",
format: winston_1.format.printf(({ message }) => String(message)),
transports: [directStdoutTransport],
});
}
/**
* Log a message at the specified level
* @param level The log level (info, warn, error, debug)
* @param message The formatted message to log
*/
log(level, message) {
this.logger[level](message);
}
}
exports.DefaultLoggerDelegate = DefaultLoggerDelegate;
/**
* Core Logger implementation with singleton pattern
*/
class Logger {
static instance;
delegate;
projectRootCache = new Map();
logLevels = [
"info",
"warn",
"error",
"debug",
];
colorMap = {
info: "whiteBright",
warn: "yellow",
error: "red",
debug: "gray",
};
statusColors = {
inProgress: "yellow",
success: "green",
failure: "red",
warning: "yellow",
info: "gray",
};
/**
* Private constructor to enforce singleton pattern
*/
constructor() {
this.delegate = new DefaultLoggerDelegate();
}
/**
* Get the singleton instance of the Logger
* @returns The Logger instance
*/
static getInstance() {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
/**
* Set a custom logger delegate
* @param delegate The delegate implementation to use for logging
*/
setDelegate(delegate) {
this.delegate = delegate;
}
/**
* Get the current logger delegate
* @returns Current delegate implementation
*/
getDelegate() {
return this.delegate;
}
/**
* Convert message components to a colorized string
* Applies colors and formatting to log components
* @param components Array of message components to colorize
* @returns Formatted string with ANSI color codes
*/
colorizeMessage(...components) {
return components
.map((component) => {
if (typeof component === "string") {
return chalk_1.default.white(component);
}
const { message, isBold, color } = component;
const colorFn = chalk_1.default[color || "white"];
return isBold ? colorFn.bold(message) : colorFn(message);
})
.join("");
}
/**
* Format a message for log file output (plain text without colors)
* @param level Log level
* @param components Message components
* @returns Plain text formatted message for file output
*/
/**
* Core logging method that all other log methods use
* Creates a formatted log component from a text string
* @param text The message text
* @param level Log level to determine text color
* @param isBold Whether the text should be bold
* @returns Formatted log component
*/
createComponent(text, level, isBold = false) {
return {
message: text,
isBold,
color: this.colorMap[level],
};
}
/**
* Creates components for the test file label if in a test environment
* @returns Array of components for the test file label or empty array if not in a test
*/
createTestFileComponents() {
const filePath = (0, testEnvUtils_1.getCurrentTestFilePath)();
if (!filePath) {
return [];
}
let projectRoot = this.projectRootCache.get(filePath);
if (projectRoot === undefined) {
projectRoot = (0, pathUtils_1.findProjectRoot)(filePath);
this.projectRootCache.set(filePath, projectRoot);
}
if (projectRoot) {
const relativePath = (0, pathUtils_1.getRelativePath)(filePath, projectRoot);
const dirname = path_1.default.dirname(relativePath);
const basename = path_1.default.basename(relativePath);
const dirLabel = dirname === "." ? "" : `${dirname}${path_1.default.sep}`;
return [
{ message: dirLabel, color: "gray" },
{ message: basename, color: "white", isBold: true },
"\n",
];
}
else {
const basename = path_1.default.basename(filePath);
return [{ message: basename, color: "white", isBold: true }, "\n"];
}
}
/**
* Converts string messages to properly formatted log components
* @param components Raw message components (strings or formatted components)
* @param level Log level to apply styling
* @returns Array of properly formatted log components
*/
normalizeComponents(components, level) {
return components.map((component) => typeof component === "string"
? this.createComponent(component, level)
: component);
}
/**
* Formats and sends log message to the delegate
* This is the core logging implementation used by all logging methods
* @param level Log level
* @param components Message components
* @param prefix Optional prefix to prepend to the message
*/
formatAndSend(level, components, prefix) {
const normalizedComponents = this.normalizeComponents(components, level);
const messageComponents = [];
if (prefix) {
messageComponents.push({
message: `${prefix} `,
color: "white",
});
}
const fileComponents = this.createTestFileComponents();
if (fileComponents.length > 0) {
messageComponents.push(...fileComponents);
}
messageComponents.push(...normalizedComponents);
const formattedMessage = this.colorizeMessage(...messageComponents);
this.delegate.log(level, formattedMessage);
}
log(level, ...components) {
this.formatAndSend(level, components);
}
/**
* Log an informational message
* @param components Message components
*/
info(...components) {
this.log("info", ...components);
}
warn(...components) {
this.log("warn", ...components);
}
error(...components) {
this.log("error", ...components);
}
debug(...components) {
this.log("debug", ...components);
}
labeled(label) {
const createMethod = (level) => (...c) => this.logWithLabel(level, label, ...c);
return {
info: createMethod("info"),
warn: createMethod("warn"),
error: createMethod("error"),
debug: createMethod("debug"),
progress: () => {
this.logWithLabel("info", label, "Starting");
return {
update: createMethod("info"),
complete: createMethod("info"),
fail: createMethod("error"),
};
},
};
}
/**
* Log a message with a formatted label prefix
* @param level Log level
* @param label The label to display
* @param components Message components
*/
logWithLabel(level, label, ...components) {
const bgColorMap = {
info: "gray",
warn: "yellow",
error: "red",
debug: "gray",
};
const displayLabel = this.createLabel(label, bgColorMap[level]);
this.formatAndSend(level, components, displayLabel);
}
/**
* Create a styled background label
* @param text The label text
* @param bgColor Background color for the label
* @returns Formatted label string with ANSI color codes
*/
createLabel(text, bgColor) {
const colorMap = {
gray: chalk_1.default.bgGray,
green: chalk_1.default.bgGreen,
red: chalk_1.default.bgRed,
yellow: chalk_1.default.bgYellow,
blue: chalk_1.default.bgBlue,
cyan: chalk_1.default.bgCyan,
};
return (colorMap[bgColor] || chalk_1.default.bgWhite).black.bold(` ${text} `);
}
/**
* Start a progress tracking operation
* @param options Progress configuration options
* @param components Initial message components
* @returns Progress control object
*/
startProgress(options, ...components) {
let currentActionLabel = options.actionLabel || "Progress";
this.logProgress(currentActionLabel, this.statusColors.inProgress, "info", components);
return {
update: (...components) => {
return this.logProgress(currentActionLabel, this.statusColors.inProgress, "info", components);
},
updateLabel: (label, ...components) => {
currentActionLabel = label;
this.logProgress(currentActionLabel, this.statusColors.inProgress, "info", components);
},
stop: (result, ...components) => {
const resultMap = {
success: {
label: options.successLabel || `${currentActionLabel} completed`,
color: this.statusColors.success,
},
failure: {
label: options.failureLabel || `${currentActionLabel} failed`,
color: this.statusColors.failure,
},
warn: {
label: options.warnLabel || `${currentActionLabel} warning`,
color: this.statusColors.warning,
},
};
const { label, color } = resultMap[result] || resultMap.warn;
const logLevel = this.getLogMethodForResult(result);
this.logProgress(label, color, logLevel, components);
},
};
}
/**
* Log a progress message with a styled label
* @param labelText Text for the progress label
* @param labelColor Background color for the label
* @param level Log level
* @param components Message components
*/
logProgress(labelText, labelColor, level, components = []) {
const displayLabel = this.createLabel(labelText, labelColor);
this.formatAndSend(level, components, displayLabel);
}
/**
* Map operation result type to appropriate log level
* @param result The operation result type
* @returns The corresponding log level
*/
getLogMethodForResult(result) {
return result === "failure" ? "error" : result === "warn" ? "warn" : "info";
}
}
// Export the singleton instance
const logger = Logger.getInstance();
// Export the logger object and the default delegate class for users to extend
exports.default = logger;