UNPKG

@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
"use strict"; 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;