neex
Version:
Neex - Modern Fullstack Framework Built on Express and Next.js. Fast to Start, Easy to Build, Ready to Deploy
274 lines (273 loc) • 12.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
// src/logger.ts
const chalk_1 = __importDefault(require("chalk"));
const figures_1 = __importDefault(require("figures"));
const string_width_1 = __importDefault(require("string-width"));
const utils_1 = require("./utils");
class Logger {
constructor() {
this.prefixLength = 0;
this.outputBuffer = new Map();
this.commandColors = new Map();
this.startTimes = new Map();
this.spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
this.spinnerIndex = 0;
this.spinnerIntervals = new Map();
this.isSpinnerActive = false;
}
static getInstance() {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
getSpinnerFrame() {
const frame = this.spinnerFrames[this.spinnerIndex];
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
return frame;
}
showBanner() {
console.log('\n' + chalk_1.default.bgHex('#0066FF').black(' Neex ') + '\n');
}
setCommands(commands) {
// Clear any existing spinner intervals
this.stopAllSpinners();
// Show Neex banner
this.showBanner();
// Calculate prefix length for aligning output
this.prefixLength = Math.max(...commands.map(cmd => (0, string_width_1.default)(cmd))) + 3;
// Initialize buffers and colors for each command
commands.forEach(cmd => {
this.outputBuffer.set(cmd, []);
this.commandColors.set(cmd, this.generateColor(cmd));
});
// Log commands that will be executed
console.log(chalk_1.default.dim('» Commands to execute:'));
commands.forEach(cmd => {
const color = this.commandColors.get(cmd) || chalk_1.default.white;
console.log(chalk_1.default.dim(' ┌') + color(` ${cmd}`));
});
console.log(''); // Add a blank line after commands list
}
generateColor(command) {
// Generate distinct colors for commands based on the command string
const vibrantColors = [
'#00BFFF', // Deep Sky Blue
'#32CD32', // Lime Green
'#FF6347', // Tomato
'#9370DB', // Medium Purple
'#FF8C00', // Dark Orange
'#20B2AA', // Light Sea Green
'#0066FF', // Deep Pink
'#4169E1', // Royal Blue
'#FFD700', // Gold
'#8A2BE2' // Blue Violet
];
let hash = 0;
for (let i = 0; i < command.length; i++) {
hash = (hash << 5) - hash + command.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
const colorIndex = Math.abs(hash) % vibrantColors.length;
return chalk_1.default.hex(vibrantColors[colorIndex]);
}
formatPrefix(command) {
const color = this.commandColors.get(command) || chalk_1.default.white;
const prefix = `${command}:`.padEnd(this.prefixLength);
return color(prefix);
}
bufferOutput(output) {
const currentBuffer = this.outputBuffer.get(output.command) || [];
currentBuffer.push(output);
this.outputBuffer.set(output.command, currentBuffer);
}
printBuffer(command) {
const buffer = this.outputBuffer.get(command) || [];
const color = this.commandColors.get(command) || chalk_1.default.white;
// Stop spinner for this command if running
this.stopSpinner(command);
buffer.forEach(output => {
const prefix = this.formatPrefix(output.command);
const content = output.data.trim();
if (content) {
const lines = content.split('\n');
lines.forEach(line => {
if (line.trim()) {
const outputLine = `${prefix} ${line}`;
// Show stderr in appropriate colors
if (output.type === 'stderr') {
// Not all stderr is an error, check for warning or info patterns
if (line.toLowerCase().includes('warn') || line.toLowerCase().includes('warning')) {
console.log(`${prefix} ${chalk_1.default.yellow(line)}`);
}
else if (line.toLowerCase().includes('error')) {
console.log(`${prefix} ${chalk_1.default.red(line)}`);
}
else {
console.log(`${prefix} ${line}`);
}
}
else {
console.log(outputLine);
}
}
});
}
});
// Clear buffer after printing
this.outputBuffer.set(command, []);
}
clearBuffer(command) {
this.outputBuffer.set(command, []);
}
printLine(message, level = 'info') {
if (level === 'error') {
console.error(chalk_1.default.red(`${figures_1.default.cross} ${message}`));
}
else if (level === 'warn') {
console.warn(chalk_1.default.yellow(`${figures_1.default.warning} ${message}`));
}
else {
console.log(chalk_1.default.blue(`${figures_1.default.info} ${message}`));
}
}
printStart(command) {
// Record start time
this.startTimes.set(command, new Date());
const prefix = this.formatPrefix(command);
const color = this.commandColors.get(command) || chalk_1.default.white;
// Stop any previous spinner for this command (e.g. if retrying)
this.stopSpinner(command);
// Clear the line before printing "Starting..."
if (this.isSpinnerActive) { // Check if any spinner was active to avoid clearing unnecessarily
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
}
console.log(`${prefix} ${color('Starting...')}`);
// Start spinner for this command
this.startSpinner(command);
}
startSpinner(command) {
// Only create a spinner if one doesn't already exist for this command
if (this.spinnerIntervals.has(command)) {
return;
}
this.isSpinnerActive = true;
const color = this.commandColors.get(command) || chalk_1.default.white;
const prefix = this.formatPrefix(command);
const interval = setInterval(() => {
const frame = this.getSpinnerFrame();
process.stdout.write(`\r${prefix} ${color(frame)} ${chalk_1.default.dim('Running...')}`);
}, 80);
this.spinnerIntervals.set(command, interval);
}
stopSpinner(command) {
const interval = this.spinnerIntervals.get(command);
if (interval) {
clearInterval(interval);
this.spinnerIntervals.delete(command);
// Clear the spinner line
if (this.isSpinnerActive) {
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
}
}
}
stopAllSpinners() {
this.spinnerIntervals.forEach((interval, command) => {
clearInterval(interval);
});
this.spinnerIntervals.clear();
this.isSpinnerActive = false;
// Clear the spinner line if any spinner was active
if (this.isSpinnerActive) {
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
}
}
printSuccess(result) {
const { command, duration } = result;
this.stopSpinner(command);
const prefix = this.formatPrefix(command);
const color = this.commandColors.get(command) || chalk_1.default.white;
const durationStr = duration
? ` ${chalk_1.default.dim(`(${(duration / 1000).toFixed(2)}s)`)}`
: '';
console.log(`${prefix} ${chalk_1.default.green(figures_1.default.tick)} ${chalk_1.default.green('Completed')}${durationStr}`);
}
printError(result) {
const { command, error, code, duration } = result;
this.stopSpinner(command);
const prefix = this.formatPrefix(command);
const durationStr = duration ? ` ${chalk_1.default.dim(`(${(duration / 1000).toFixed(2)}s)`)}` : '';
const errorCode = code !== null ? ` ${chalk_1.default.red(`[code: ${code}]`)}` : '';
console.error(`${prefix} ${chalk_1.default.red(figures_1.default.cross)} ${chalk_1.default.red('Failed')}${errorCode}${durationStr}`);
if (error) {
console.error(`${prefix} ${chalk_1.default.red(error.message)}`);
}
}
printEnd(result, minimalOutput) {
this.stopSpinner(result.command);
const prefix = this.formatPrefix(result.command); // Corrected to formatPrefix
let durationDisplay = '';
if (result.duration !== null) {
// Ensure result.duration is treated as a number here
durationDisplay = `(${(0, utils_1.formatDuration)(result.duration)})`;
}
const duration = durationDisplay;
if (minimalOutput) {
if (!result.success) {
const status = result.code !== null ? `failed (code ${result.code})` : 'failed';
this.printLine(`${prefix} ${chalk_1.default.red(figures_1.default.cross)} ${result.command} ${status} ${duration}`, 'error');
}
}
else {
if (result.success) {
this.printLine(`${prefix} ${chalk_1.default.green(figures_1.default.tick)} Command "${result.command}" finished successfully ${duration}`, 'info');
}
else {
const errorCode = result.code !== null ? ` (code ${result.code})` : '';
const errorMessage = result.error ? `: ${result.error.message}` : '';
this.printLine(`${prefix} ${chalk_1.default.red(figures_1.default.cross)} Command "${result.command}" failed${errorCode}${errorMessage} ${duration}`, 'error');
}
}
}
printSummary(results) {
// Stop any remaining spinners
this.stopAllSpinners();
const successful = results.filter(r => r.success).length;
const failed = results.length - successful;
const totalDuration = results.reduce((sum, result) => sum + (result.duration || 0), 0);
const totalSeconds = (totalDuration / 1000).toFixed(2);
console.log('\n' + chalk_1.default.bgHex('#0066FF').black(' Execution Summary ') + '\n');
console.log(`${chalk_1.default.green(`${figures_1.default.tick} ${successful} succeeded`)}, ${chalk_1.default.red(`${figures_1.default.cross} ${failed} failed`)}`);
console.log(`${chalk_1.default.blue(figures_1.default.info)} ${chalk_1.default.dim(`Total execution time: ${totalSeconds}s`)}`);
if (successful > 0) {
console.log('\n' + chalk_1.default.green.bold('Successful commands:'));
results
.filter(r => r.success)
.forEach(result => {
const color = this.commandColors.get(result.command) || chalk_1.default.white;
const duration = result.duration
? chalk_1.default.dim(` (${(result.duration / 1000).toFixed(2)}s)`)
: '';
console.log(` ${chalk_1.default.green(figures_1.default.tick)} ${color(result.command)}${duration}`);
});
}
if (failed > 0) {
console.log('\n' + chalk_1.default.red.bold('Failed commands:'));
results
.filter(r => !r.success)
.forEach(result => {
const color = this.commandColors.get(result.command) || chalk_1.default.white;
const duration = result.duration
? chalk_1.default.dim(` (${(result.duration / 1000).toFixed(2)}s)`)
: '';
const code = result.code !== null ? chalk_1.default.red(` [code: ${result.code}]`) : '';
console.log(` ${chalk_1.default.red(figures_1.default.cross)} ${color(result.command)}${code}${duration}`);
});
}
}
}
exports.default = Logger.getInstance();