import * as plugins from './smartupdate.plugins.js'; import { LOG_LEVELS, type TLogLevel, DEFAULT_MESSAGE_COLOR, MESSAGE_PREFIXES, } from './smartupdate.constants.js'; import type { INotificationOptions, IUpdateCheckResult } from './smartupdate.interfaces.js'; /** * Handles all user-facing notifications and console output */ export class UpdateNotifier { private logLevel: TLogLevel; private useColors: boolean; private customLogger?: (level: TLogLevel, message: string) => void; constructor(options: INotificationOptions) { this.logLevel = options.logLevel; this.useColors = options.useColors && !process.env.NO_COLOR; this.customLogger = options.customLogger; } /** * Check if a message at the given level should be logged */ private shouldLog(level: TLogLevel): boolean { return LOG_LEVELS[level] <= LOG_LEVELS[this.logLevel]; } /** * Colorize a string if colors are enabled */ private colorize(text: string, color: any = DEFAULT_MESSAGE_COLOR): string { if (!this.useColors) { return text; } return plugins.consolecolor.coloredString(text, color as any); } /** * Log a message at the specified level */ private log(level: TLogLevel, message: string): void { if (!this.shouldLog(level)) { return; } if (this.customLogger) { this.customLogger(level, message); } else { console.log(message); } } /** * Log a debug message */ public debug(message: string): void { this.log('DEBUG', `${MESSAGE_PREFIXES.INFO} ${message}`); } /** * Log an info message */ public info(message: string): void { this.log('INFO', `${MESSAGE_PREFIXES.SMARTUPDATE} ${message}`); } /** * Log a warning message */ public warn(message: string): void { this.log('WARN', `${MESSAGE_PREFIXES.WARN} ${message}`); } /** * Log an error message */ public error(message: string): void { this.log('ERROR', `${MESSAGE_PREFIXES.ERROR} ${message}`); } /** * Notify about checking for updates */ public notifyCheckingForUpdate(packageName: string): void { this.info( `checking for newer version of ${this.colorize(packageName)}...` ); } /** * Notify that the package is up to date */ public notifyUpToDate(packageName: string): void { this.info( `You are running the latest version of ${this.colorize(packageName)}` ); } /** * Notify that an update is available */ public notifyUpdateAvailable(packageName: string, currentVersion: string, latestVersion: string): void { this.warn(`There is a newer version of ${packageName} available on npm.`); this.warn(`Your version: ${currentVersion} | version on npm: ${latestVersion}`); } /** * Notify that a check was skipped due to rate limiting */ public notifyCheckSkipped(packageName: string, nextCheckMinutes: number): void { const minutes = Math.floor(nextCheckMinutes) + 1; this.info( `Already checked recently. Next check available in ${minutes} minute${minutes !== 1 ? 's' : ''}: ` + this.colorize(packageName) ); } /** * Notify that the changelog is being opened */ public notifyOpeningChangelog(): void { this.info('Opening changelog in browser...'); } /** * Notify about a registry error */ public notifyRegistryError(): void { this.warn('Failed to retrieve package information.'); this.info('Is the registry down?'); } /** * Notify with a complete update check result */ public notifyUpdateCheckResult(result: IUpdateCheckResult): void { switch (result.status) { case 'up-to-date': this.notifyUpToDate(result.packageName); break; case 'update-available': if (result.latestVersion) { this.notifyUpdateAvailable( result.packageName, result.currentVersion, result.latestVersion ); } break; case 'check-skipped': if (result.nextCheckTime && result.reason) { const minutesUntilNext = (result.nextCheckTime.getTime() - result.checkTime.getTime()) / 60000; this.notifyCheckSkipped(result.packageName, minutesUntilNext); } break; case 'error': if (result.error) { this.error(result.error.message); } break; } } }