import type { CompileError } from './parser/utils/error.js'; import { bold, blue, red, grey, underline } from 'kleur/colors'; import { Writable } from 'stream'; import { format as utilFormat } from 'util'; type ConsoleStream = Writable & { fd: 1 | 2; }; export const defaultLogDestination = new Writable({ objectMode: true, write(event: LogMessage, _, callback) { let dest: ConsoleStream = process.stderr; if (levels[event.level] < levels['error']) { dest = process.stdout; } let type = event.type; if (event.level === 'info') { type = bold(blue(type)); } else if (event.level === 'error') { type = bold(red(type)); } dest.write(`[${type}] `); dest.write(utilFormat(...event.args)); dest.write('\n'); callback(); }, }); interface LogWritable extends Writable { write: (chunk: T) => boolean; } export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino export type LoggerEvent = 'debug' | 'info' | 'warn' | 'error'; export interface LogOptions { dest: LogWritable; level: LoggerLevel; } export const defaultLogOptions: LogOptions = { dest: defaultLogDestination, level: 'info', }; export interface LogMessage { type: string; level: LoggerLevel; message: string; args: Array; } const levels: Record = { debug: 20, info: 30, warn: 40, error: 50, silent: 90, }; /** Full logging API */ export function log(opts: LogOptions = defaultLogOptions, level: LoggerLevel, type: string, ...args: Array) { const event: LogMessage = { type, level, args, message: '', }; // test if this level is enabled or not if (levels[opts.level] > levels[level]) { return; // do nothing } opts.dest.write(event); } /** Emit a message only shown in debug mode */ export function debug(opts: LogOptions, type: string, ...messages: Array) { return log(opts, 'debug', type, ...messages); } /** Emit a general info message (be careful using this too much!) */ export function info(opts: LogOptions, type: string, ...messages: Array) { return log(opts, 'info', type, ...messages); } /** Emit a warning a user should be aware of */ export function warn(opts: LogOptions, type: string, ...messages: Array) { return log(opts, 'warn', type, ...messages); } /** Emit a fatal error message the user should address. */ export function error(opts: LogOptions, type: string, ...messages: Array) { return log(opts, 'error', type, ...messages); } /** Pretty format error for display */ export function parseError(opts: LogOptions, err: CompileError) { let frame = err.frame // Switch colons for pipes .replace(/^([0-9]+)(:)/gm, `${bold('$1')} │`) // Make the caret red. .replace(/(?<=^\s+)(\^)/gm, bold(red(' ^'))) // Add identation .replace(/^/gm, ' '); error( opts, 'parse-error', ` ${underline(bold(grey(`${err.filename}:${err.start.line}:${err.start.column}`)))} ${bold(red(`𝘅 ${err.message}`))} ${frame} ` ); } // A default logger for when too lazy to pass LogOptions around. export const logger = { debug: debug.bind(null, defaultLogOptions), info: info.bind(null, defaultLogOptions), warn: warn.bind(null, defaultLogOptions), error: error.bind(null, defaultLogOptions), };