UNPKG

@curvenote/cli

Version:
123 lines (122 loc) 5.08 kB
import chalk from 'chalk'; import { LogLevel, chalkLogger, silentLogger } from 'myst-cli-utils'; import { sep } from 'node:path'; /** * Builds a composite {@link Logger} that fans out each log call to both a * chalk-styled terminal logger and, optionally, a websocket logger. * * For every level (`debug`, `info`, `warn`, `error`), the returned logger: * - Short-circuits when the message is below the configured {@link LogLevel}. * - Delegates first to {@link chalkLogger} for terminal output. * - Delegates to {@link websocketLogger} when `sendJson` is provided, otherwise * falls back to {@link silentLogger} so websocket calls are no-ops. * * @param level - Minimum {@link LogLevel} to emit; lower-severity messages are * discarded before either underlying logger is invoked. * @param cwd - Optional working directory whose prefix is stripped from string * arguments so logged paths are displayed relative to it. * @param sendJson - Optional transport for forwarding log envelopes over a * websocket (typically `AppServer.contentServer.sendJson`). * When omitted, only terminal logging is active. * @returns A {@link Logger} that multiplexes log output to the terminal and * (when configured) to websocket clients. */ export function compositeLoggerFactory(levels = { websocket: LogLevel.debug, terminal: LogLevel.info, }, cwd, sendJson) { const logChalk = chalkLogger(levels.terminal, cwd); const logWebsocket = sendJson ? websocketLogger(sendJson, levels.websocket, cwd) : silentLogger(); return { debug(...args) { logChalk.debug(...args); logWebsocket.debug(...args); }, info(...args) { logChalk.info(...args); logWebsocket.info(...args); }, warn(...args) { logChalk.warn(...args); logWebsocket.warn(...args); }, error(...args) { logChalk.error(...args); logWebsocket.error(...args); }, }; } /** * Strips the current working directory prefix from string arguments passed to * the logger, producing paths that are relative to `cwd` instead of absolute. * * Non-string arguments are returned untouched. If `cwd` is undefined, the * original arguments are returned as-is. * * @param cwd - The absolute working directory to strip from string args. When * provided, occurrences of `cwd + sep` are removed from each string. * @param args - The array of log arguments (mixed types) to normalize. * @returns A new array with `cwd` prefixes removed from any string entries. * * @example * replaceCwd('/Users/me/proj', ['/Users/me/proj/src/a.ts', 42]); * // => ['src/a.ts', 42] */ function replaceCwd(cwd, args) { if (!cwd) return args; return args.map((a) => { if (typeof a === 'string') { return a.replaceAll(cwd + sep, ''); } return a; }); } /** * Creates a {@link Logger} that forwards log messages over a websocket by * emitting `LOG` envelopes through the provided `sendJson` function. * * Each log method: * - Respects the configured {@link LogLevel}, dropping messages below it. * - Normalizes arguments with {@link replaceCwd} so absolute paths are shown * relative to `cwd`. * - Applies a chalk style per level (`dim` for debug, `reset` for info, * `yellow` for warn, `red` for error) before sending the serialized message. * * The emitted payload shape is: * `{ type: 'LOG', level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR', message: string }`. * * @param sendJson - Transport function used to deliver JSON log envelopes to * connected websocket clients (typically * `AppServer.contentServer.sendJson`). * @param level - Minimum {@link LogLevel} to forward; lower-severity messages * are ignored. * @param cwd - Optional working directory whose prefix is stripped from string * arguments before formatting. * @returns A {@link Logger} whose `debug`, `info`, `warn`, and `error` methods * publish styled log messages over the websocket. */ export function websocketLogger(sendJson, level, cwd) { return { debug(...args) { if (level > LogLevel.debug) return; sendJson({ type: 'LOG', level: 'DEBUG', message: chalk.dim(...replaceCwd(cwd, args)) }); }, info(...args) { if (level > LogLevel.info) return; sendJson({ type: 'LOG', level: 'INFO', message: chalk.reset(...replaceCwd(cwd, args)) }); }, warn(...args) { if (level > LogLevel.warn) return; sendJson({ type: 'LOG', level: 'WARN', message: chalk.yellow(...replaceCwd(cwd, args)) }); }, error(...args) { if (level > LogLevel.error) return; sendJson({ type: 'LOG', level: 'ERROR', message: chalk.red(...replaceCwd(cwd, args)) }); }, }; }