UNPKG

ai-github-changelog-generator-cli-mcp

Version:

AI-powered changelog generator with MCP server support - works with OpenAI GPT-4.1 and Azure OpenAI with reasoning models

362 lines (304 loc) 10.1 kB
/** * Color utility for consistent styled output across the application * Uses ANSI escape codes for maximum compatibility */ // ANSI escape codes for colors and styles const ansiColors = { // Reset reset: '\x1b[0m', // Text colors black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', gray: '\x1b[90m', grey: '\x1b[90m', // Bright colors redBright: '\x1b[91m', greenBright: '\x1b[92m', yellowBright: '\x1b[93m', blueBright: '\x1b[94m', magentaBright: '\x1b[95m', cyanBright: '\x1b[96m', whiteBright: '\x1b[97m', // Styles bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m', underline: '\x1b[4m', inverse: '\x1b[7m', strikethrough: '\x1b[9m' }; /** * Color utility class */ class Colors { constructor() { this.enabled = this.shouldEnableColors(); this.setupColors(); } shouldEnableColors() { // Don't use colors if explicitly disabled if (process.env.NO_COLOR || process.env.NODE_DISABLE_COLORS) { return false; } // Don't use colors if not in a TTY (unless forced) if (process.stdout && process.stdout.isTTY === false) { return false; } // Don't use colors in dumb terminals if (process.env.TERM === 'dumb') { return false; } // Enable colors by default return true; } colorize(color, text) { if (!this.enabled || !text) return text; return `${ansiColors[color] || ''}${text}${ansiColors.reset}`; } setupColors() { if (!this.enabled) { this.setupFallbackColors(); return; } // Status colors this.success = (text) => this.colorize('green', text); this.error = (text) => this.colorize('red', text); this.warning = (text) => this.colorize('yellow', text); this.info = (text) => this.colorize('blue', text); this.secondary = (text) => this.colorize('gray', text); this.highlight = (text) => this.colorize('cyan', text); this.bold = (text) => this.colorize('bold', text); this.dim = (text) => this.colorize('dim', text); // Semantic colors for changelog this.feature = (text) => this.colorize('greenBright', text); this.fix = (text) => this.colorize('redBright', text); this.security = (text) => this.colorize('magentaBright', text); this.breaking = (text) => this.colorize('bold', this.colorize('red', text)); this.docs = (text) => this.colorize('blueBright', text); this.style = (text) => this.colorize('magenta', text); this.refactor = (text) => this.colorize('yellow', text); this.perf = (text) => this.colorize('cyan', text); this.test = (text) => this.colorize('blue', text); this.chore = (text) => this.colorize('gray', text); // UI elements this.header = (text) => this.colorize('underline', this.colorize('bold', text)); this.subheader = (text) => this.colorize('bold', text); this.label = (text) => this.colorize('cyan', text); this.value = (text) => this.colorize('white', text); this.code = (text) => this.colorize('inverse', text); this.file = (text) => this.colorize('yellowBright', text); this.path = (text) => this.colorize('green', text); this.hash = (text) => this.colorize('magenta', text); // Metrics and stats this.metric = (text) => this.colorize('cyan', text); this.number = (text) => this.colorize('yellowBright', text); this.percentage = (text) => this.colorize('green', text); // Risk levels this.riskLow = (text) => this.colorize('green', text); this.riskMedium = (text) => this.colorize('yellow', text); this.riskHigh = (text) => this.colorize('red', text); this.riskCritical = (text) => this.colorize('inverse', this.colorize('bold', this.colorize('red', text))); // Impact levels this.impactMinimal = (text) => this.colorize('gray', text); this.impactLow = (text) => this.colorize('blue', text); this.impactMedium = (text) => this.colorize('yellow', text); this.impactHigh = (text) => this.colorize('red', text); this.impactCritical = (text) => this.colorize('bold', this.colorize('red', text)); } setupFallbackColors() { // Fallback to identity functions when colors are disabled const identity = (text) => text; // Status colors this.success = identity; this.error = identity; this.warning = identity; this.info = identity; this.secondary = identity; this.highlight = identity; this.bold = identity; this.dim = identity; // Semantic colors for changelog this.feature = identity; this.fix = identity; this.security = identity; this.breaking = identity; this.docs = identity; this.style = identity; this.refactor = identity; this.perf = identity; this.test = identity; this.chore = identity; // UI elements this.header = identity; this.subheader = identity; this.label = identity; this.value = identity; this.code = identity; this.file = identity; this.path = identity; this.hash = identity; // Metrics and stats this.metric = identity; this.number = identity; this.percentage = identity; // Risk levels this.riskLow = identity; this.riskMedium = identity; this.riskHigh = identity; this.riskCritical = identity; // Impact levels this.impactMinimal = identity; this.impactLow = identity; this.impactMedium = identity; this.impactHigh = identity; this.impactCritical = identity; } disable() { this.enabled = false; this.setupFallbackColors(); } enable() { this.enabled = true; this.setupColors(); } // Utility methods for common patterns emoji(text) { return text; // Emojis work without colors } status(type, message) { const colorMap = { success: this.success, error: this.error, warning: this.warning, info: this.info }; const color = colorMap[type] || this.info; return color(message); } commitType(type) { const colorMap = { feat: this.feature, fix: this.fix, security: this.security, breaking: this.breaking, docs: this.docs, style: this.style, refactor: this.refactor, perf: this.perf, test: this.test, chore: this.chore }; return (colorMap[type] || this.secondary)(type); } risk(level) { const colorMap = { low: this.riskLow, medium: this.riskMedium, high: this.riskHigh, critical: this.riskCritical }; return (colorMap[level] || this.secondary)(level.toUpperCase()); } impact(level) { const colorMap = { minimal: this.impactMinimal, low: this.impactLow, medium: this.impactMedium, high: this.impactHigh, critical: this.impactCritical }; return (colorMap[level] || this.secondary)(level); } // Diff highlighting diffAdd(text) { return this.success(`+ ${text}`); } diffRemove(text) { return this.error(`- ${text}`); } diffContext(text) { return this.dim(` ${text}`); } // Progress indicators progress(current, total, label = '') { const percentage = Math.round((current / total) * 100); const bar = '█'.repeat(Math.round(percentage / 5)) + '░'.repeat(20 - Math.round(percentage / 5)); return `${this.info(`[${bar}]`)} ${this.percentage(`${percentage}%`)} ${label}`; } // Box drawing for sections box(title, content, width = 80) { const topBorder = '┌' + '─'.repeat(width - 2) + '┐'; const bottomBorder = '└' + '─'.repeat(width - 2) + '┘'; const titleLine = `│ ${this.header(title)}${' '.repeat(Math.max(0, width - title.length - 3))}│`; const contentLines = content.split('\n').map(line => { const paddedLine = line.padEnd(width - 4); return `│ ${paddedLine} │`; }); return [ this.secondary(topBorder), titleLine, this.secondary('├' + '─'.repeat(width - 2) + '┤'), ...contentLines, this.secondary(bottomBorder) ].join('\n'); } // Quick access to common formatted messages successMessage(message) { return `${this.success('✅')} ${message}`; } errorMessage(message) { return `${this.error('❌')} ${message}`; } warningMessage(message) { return `${this.warning('⚠️')} ${message}`; } infoMessage(message) { return `${this.info('ℹ️')} ${message}`; } processingMessage(message) { return `${this.highlight('🔍')} ${message}`; } aiMessage(message) { return `${this.highlight('🤖')} ${message}`; } metricsMessage(message) { return `${this.metric('📊')} ${message}`; } // Format file lists with syntax highlighting formatFileList(files, maxDisplay = 10) { const displayed = files.slice(0, maxDisplay); const result = displayed.map(file => { const ext = file.split('.').pop()?.toLowerCase(); let color = this.file; // Color by file type if (['ts', 'tsx', 'js', 'jsx'].includes(ext)) color = this.feature; else if (['css', 'scss', 'sass'].includes(ext)) color = this.style; else if (['md', 'txt'].includes(ext)) color = this.docs; else if (['json', 'yaml', 'yml'].includes(ext)) color = this.warning; else if (['sql'].includes(ext)) color = this.fix; return ` - ${color(file)}`; }); if (files.length > maxDisplay) { result.push(` ${this.dim(`... and ${files.length - maxDisplay} more`)}`); } return result.join('\n'); } // Format metrics table formatMetrics(metrics) { const entries = Object.entries(metrics); const maxKeyLength = Math.max(...entries.map(([k]) => k.length)); return entries.map(([key, value]) => { const paddedKey = key.padEnd(maxKeyLength); return `${this.label(paddedKey)}: ${this.value(value)}`; }).join('\n'); } } // Export singleton instance const colors = new Colors(); module.exports = colors;