@rafeekpro/stackwizard
Version:
š§āāļø Magical full-stack project generator with FastAPI, React, PostgreSQL, and Docker
962 lines (847 loc) ⢠27 kB
JavaScript
/**
* Error handling utilities for StackWizard CLI
* Provides comprehensive error handling, logging, and user-friendly messages
*/
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import os from 'os';
import { exec } from 'child_process';
import { promisify } from 'util';
import net from 'net';
const execAsync = promisify(exec);
/**
* Error codes for different types of failures
*/
export const ErrorCodes = {
// File system errors
DIR_EXISTS: 'DIR_EXISTS',
DIR_CREATE_FAILED: 'DIR_CREATE_FAILED',
FILE_COPY_FAILED: 'FILE_COPY_FAILED',
FILE_WRITE_FAILED: 'FILE_WRITE_FAILED',
FILE_READ_FAILED: 'FILE_READ_FAILED',
TEMPLATE_NOT_FOUND: 'TEMPLATE_NOT_FOUND',
TEMPLATE_CORRUPTED: 'TEMPLATE_CORRUPTED',
PERMISSION_DENIED: 'PERMISSION_DENIED',
PATH_TOO_LONG: 'PATH_TOO_LONG',
// Dependency errors
GIT_NOT_INSTALLED: 'GIT_NOT_INSTALLED',
GIT_INIT_FAILED: 'GIT_INIT_FAILED',
NPM_NOT_INSTALLED: 'NPM_NOT_INSTALLED',
PIP_NOT_INSTALLED: 'PIP_NOT_INSTALLED',
DOCKER_NOT_INSTALLED: 'DOCKER_NOT_INSTALLED',
NODE_VERSION_MISMATCH: 'NODE_VERSION_MISMATCH',
// Network errors
NPM_INSTALL_FAILED: 'NPM_INSTALL_FAILED',
PIP_INSTALL_FAILED: 'PIP_INSTALL_FAILED',
NETWORK_ERROR: 'NETWORK_ERROR',
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
DNS_ERROR: 'DNS_ERROR',
// Validation errors
INVALID_PROJECT_NAME: 'INVALID_PROJECT_NAME',
INVALID_PORT: 'INVALID_PORT',
INVALID_DB_CONFIG: 'INVALID_DB_CONFIG',
PORT_IN_USE: 'PORT_IN_USE',
RESERVED_NAME: 'RESERVED_NAME',
// System errors
OUT_OF_MEMORY: 'OUT_OF_MEMORY',
DISK_FULL: 'DISK_FULL',
CPU_OVERLOAD: 'CPU_OVERLOAD',
SIGNAL_TERMINATED: 'SIGNAL_TERMINATED',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
// Process errors
PROCESS_SPAWN_FAILED: 'PROCESS_SPAWN_FAILED',
PROCESS_EXIT_ERROR: 'PROCESS_EXIT_ERROR',
COMMAND_NOT_FOUND: 'COMMAND_NOT_FOUND',
};
/**
* Custom error class with additional context
*/
export class StackWizardError extends Error {
constructor(code, message, details = {}) {
super(message);
this.name = 'StackWizardError';
this.code = code;
this.details = details;
this.timestamp = new Date().toISOString();
}
}
/**
* Error messages with user-friendly explanations and solutions
*/
export const ErrorMessages = {
[ErrorCodes.DIR_EXISTS]: {
message: 'Directory already exists',
solution:
'Choose a different project name or remove the existing directory',
recoverable: true,
autoRecovery: false,
userAction: ['rename', 'delete', 'overwrite'],
},
[ErrorCodes.DIR_CREATE_FAILED]: {
message: 'Failed to create project directory',
solution: 'Check if you have write permissions in the current directory',
recoverable: false,
},
[ErrorCodes.FILE_COPY_FAILED]: {
message: 'Failed to copy template files',
solution: 'Ensure the StackWizard package is properly installed',
recoverable: false,
},
[ErrorCodes.FILE_WRITE_FAILED]: {
message: 'Failed to write configuration files',
solution: 'Check disk space and file permissions',
recoverable: false,
},
[ErrorCodes.TEMPLATE_NOT_FOUND]: {
message: 'Template files not found',
solution: 'Reinstall StackWizard: npm install -g @rafeekpro/stackwizard',
recoverable: false,
},
[ErrorCodes.PERMISSION_DENIED]: {
message: 'Permission denied',
solution:
'Run the command with appropriate permissions or choose a different directory',
recoverable: true,
},
[ErrorCodes.GIT_NOT_INSTALLED]: {
message: 'Git is not installed',
solution:
'Install Git from https://git-scm.com/ or skip Git initialization',
recoverable: true,
},
[ErrorCodes.NPM_NOT_INSTALLED]: {
message: 'npm is not installed',
solution: 'Install Node.js and npm from https://nodejs.org/',
recoverable: true,
},
[ErrorCodes.PIP_NOT_INSTALLED]: {
message: 'pip is not installed',
solution: 'Install Python and pip from https://www.python.org/',
recoverable: true,
},
[ErrorCodes.DOCKER_NOT_INSTALLED]: {
message: 'Docker is not installed',
solution: 'Install Docker from https://www.docker.com/get-started',
recoverable: true,
},
[ErrorCodes.NPM_INSTALL_FAILED]: {
message: 'Failed to install npm dependencies',
solution: 'Try running "npm install" manually in the frontend directory',
recoverable: true,
},
[ErrorCodes.PIP_INSTALL_FAILED]: {
message: 'Failed to install Python dependencies',
solution:
'Try running "pip install -r requirements.txt" manually in the backend directory',
recoverable: true,
},
[ErrorCodes.NETWORK_ERROR]: {
message: 'Network connection error',
solution: 'Check your internet connection and try again',
recoverable: true,
},
[ErrorCodes.INVALID_PROJECT_NAME]: {
message: 'Invalid project name',
solution: 'Use only lowercase letters, numbers, and hyphens',
recoverable: true,
},
[ErrorCodes.INVALID_PORT]: {
message: 'Invalid port number',
solution: 'Use a port number between 1 and 65535',
recoverable: true,
},
[ErrorCodes.INVALID_DB_CONFIG]: {
message: 'Invalid database configuration',
solution: 'Check your database settings and ensure they are valid',
recoverable: true,
},
[ErrorCodes.OUT_OF_MEMORY]: {
message: 'Out of memory',
solution: 'Close other applications and try again',
recoverable: false,
},
[ErrorCodes.DISK_FULL]: {
message: 'Disk is full',
solution: 'Free up disk space and try again',
recoverable: false,
},
[ErrorCodes.UNKNOWN_ERROR]: {
message: 'An unexpected error occurred',
solution:
'Please report this issue at https://github.com/rafeekpro/stackwizard/issues',
recoverable: false,
},
[ErrorCodes.FILE_READ_FAILED]: {
message: 'Failed to read file',
solution: 'Check if the file exists and you have read permissions',
recoverable: false,
},
[ErrorCodes.TEMPLATE_CORRUPTED]: {
message: 'Template files are corrupted',
solution: 'Reinstall StackWizard to restore templates',
recoverable: false,
},
[ErrorCodes.PATH_TOO_LONG]: {
message: 'Path is too long for the operating system',
solution: 'Use a shorter project name or change to a different directory',
recoverable: true,
},
[ErrorCodes.GIT_INIT_FAILED]: {
message: 'Failed to initialize Git repository',
solution: 'Check Git installation and permissions',
recoverable: true,
},
[ErrorCodes.NODE_VERSION_MISMATCH]: {
message: 'Node.js version requirement not met',
solution: 'Update Node.js to version 14.0.0 or higher',
recoverable: false,
},
[ErrorCodes.TIMEOUT_ERROR]: {
message: 'Operation timed out',
solution: 'Check your internet connection and try again',
recoverable: true,
},
[ErrorCodes.DNS_ERROR]: {
message: 'DNS resolution failed',
solution: 'Check your internet connection and DNS settings',
recoverable: true,
},
[ErrorCodes.PORT_IN_USE]: {
message: 'Port is already in use',
solution: 'Choose a different port or stop the service using this port',
recoverable: true,
},
[ErrorCodes.RESERVED_NAME]: {
message: 'Project name is reserved',
solution: 'Choose a different project name',
recoverable: true,
},
[ErrorCodes.CPU_OVERLOAD]: {
message: 'System CPU is overloaded',
solution: 'Close other applications and try again',
recoverable: true,
},
[ErrorCodes.SIGNAL_TERMINATED]: {
message: 'Process was terminated',
solution: 'Operation was cancelled',
recoverable: false,
},
[ErrorCodes.PROCESS_SPAWN_FAILED]: {
message: 'Failed to start process',
solution: 'Check if the command exists and you have execution permissions',
recoverable: false,
},
[ErrorCodes.PROCESS_EXIT_ERROR]: {
message: 'Process exited with error',
solution: 'Check the command output for details',
recoverable: true,
},
[ErrorCodes.COMMAND_NOT_FOUND]: {
message: 'Command not found',
solution: 'Install the required tool or check your PATH',
recoverable: true,
},
};
/**
* Logger class for debug mode and error tracking
*/
export class Logger {
constructor(debugMode = false) {
this.debugMode = debugMode || process.env.DEBUG === 'true';
this.logFile = null;
this.initLogFile();
}
initLogFile() {
if (this.debugMode) {
const logDir = path.join(os.homedir(), '.stackwizard', 'logs');
fs.ensureDirSync(logDir);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
this.logFile = path.join(logDir, `stackwizard-${timestamp}.log`);
// Clean up old log files (keep last 10)
this.cleanupOldLogs(logDir);
}
}
cleanupOldLogs(logDir) {
try {
const files = fs
.readdirSync(logDir)
.filter((f) => f.startsWith('stackwizard-') && f.endsWith('.log'))
.map((f) => ({
name: f,
path: path.join(logDir, f),
time: fs.statSync(path.join(logDir, f)).mtime,
}))
.sort((a, b) => b.time - a.time);
// Keep only the 10 most recent logs
if (files.length > 10) {
files.slice(10).forEach((f) => {
try {
fs.unlinkSync(f.path);
} catch {
// Ignore cleanup errors
}
});
}
} catch {
// Ignore cleanup errors
}
}
debug(message, data = {}) {
if (this.debugMode) {
const logEntry = {
timestamp: new Date().toISOString(),
level: 'DEBUG',
message,
data,
};
console.log(chalk.gray(`[DEBUG] ${message}`));
if (Object.keys(data).length > 0) {
console.log(chalk.gray(JSON.stringify(data, null, 2)));
}
if (this.logFile) {
fs.appendFileSync(this.logFile, JSON.stringify(logEntry) + '\n');
}
}
}
info(message) {
const logEntry = {
timestamp: new Date().toISOString(),
level: 'INFO',
message,
};
if (this.debugMode && this.logFile) {
fs.appendFileSync(this.logFile, JSON.stringify(logEntry) + '\n');
}
}
warn(message) {
const logEntry = {
timestamp: new Date().toISOString(),
level: 'WARN',
message,
};
console.log(chalk.yellow(`ā ļø ${message}`));
if (this.debugMode && this.logFile) {
fs.appendFileSync(this.logFile, JSON.stringify(logEntry) + '\n');
}
}
error(message, error = null) {
const logEntry = {
timestamp: new Date().toISOString(),
level: 'ERROR',
message,
error: error
? {
message: error.message,
stack: error.stack,
code: error.code,
}
: null,
};
console.log(chalk.red(`ā ${message}`));
if (this.debugMode) {
if (error && error.stack) {
console.log(chalk.red(error.stack));
}
if (this.logFile) {
fs.appendFileSync(this.logFile, JSON.stringify(logEntry) + '\n');
}
}
}
getLogPath() {
return this.logFile;
}
}
/**
* Error handler with recovery mechanisms
*/
export class ErrorHandler {
constructor(logger) {
this.logger = logger;
this.errorCount = 0;
this.recoveryAttempts = {};
}
/**
* Handle error with appropriate user messaging and recovery
*/
async handle(error, context = {}) {
this.errorCount++;
this.logger.error(`Error #${this.errorCount}: ${error.message}`, error);
// Capture error context for better debugging
const errorContext = {
...context,
timestamp: new Date().toISOString(),
platform: process.platform,
nodeVersion: process.version,
cwd: process.cwd(),
memoryUsage: process.memoryUsage(),
cpuUsage: process.cpuUsage(),
};
this.logger.debug('Error context', errorContext);
// Determine error code
const errorCode = this.determineErrorCode(error);
const errorInfo =
ErrorMessages[errorCode] || ErrorMessages[ErrorCodes.UNKNOWN_ERROR];
// Display user-friendly error message
console.log('\n' + chalk.red.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
console.log(chalk.red.bold(' šØ Error Occurred'));
console.log(chalk.red.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
console.log(chalk.white(`\n ${chalk.bold('What happened:')}`));
console.log(chalk.white(` ${errorInfo.message}`));
if (context.details) {
console.log(chalk.white(`\n ${chalk.bold('Details:')}`));
console.log(chalk.gray(` ${context.details}`));
}
console.log(chalk.white(`\n ${chalk.bold('How to fix:')}`));
console.log(chalk.cyan(` ${errorInfo.solution}`));
if (this.logger.debugMode) {
console.log(chalk.white(`\n ${chalk.bold('Debug Information:')}`));
console.log(chalk.gray(` Error Code: ${errorCode}`));
console.log(chalk.gray(` Log File: ${this.logger.getLogPath()}`));
}
console.log(chalk.red.bold('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
// Attempt recovery if possible
if (errorInfo.recoverable) {
return await this.attemptRecovery(errorCode, error, context);
}
return false;
}
/**
* Determine error code from error object
*/
determineErrorCode(error) {
if (error instanceof StackWizardError) {
return error.code;
}
// Check error code if present
if (error.code) {
const codeMap = {
EEXIST: ErrorCodes.DIR_EXISTS,
ENOENT: ErrorCodes.TEMPLATE_NOT_FOUND,
EACCES: ErrorCodes.PERMISSION_DENIED,
EPERM: ErrorCodes.PERMISSION_DENIED,
ENOSPC: ErrorCodes.DISK_FULL,
ENOMEM: ErrorCodes.OUT_OF_MEMORY,
ETIMEDOUT: ErrorCodes.TIMEOUT_ERROR,
ENOTFOUND: ErrorCodes.DNS_ERROR,
EADDRINUSE: ErrorCodes.PORT_IN_USE,
ENAMETOOLONG: ErrorCodes.PATH_TOO_LONG,
};
if (codeMap[error.code]) {
return codeMap[error.code];
}
}
// Check for common error patterns
const message = error.message.toLowerCase();
if (message.includes('eexist')) return ErrorCodes.DIR_EXISTS;
if (message.includes('eacces') || message.includes('permission'))
return ErrorCodes.PERMISSION_DENIED;
if (message.includes('enoent')) return ErrorCodes.TEMPLATE_NOT_FOUND;
if (message.includes('enospc')) return ErrorCodes.DISK_FULL;
if (message.includes('enomem')) return ErrorCodes.OUT_OF_MEMORY;
if (message.includes('git')) return ErrorCodes.GIT_NOT_INSTALLED;
if (message.includes('npm')) return ErrorCodes.NPM_INSTALL_FAILED;
if (message.includes('pip')) return ErrorCodes.PIP_INSTALL_FAILED;
if (message.includes('network') || message.includes('enotfound'))
return ErrorCodes.NETWORK_ERROR;
return ErrorCodes.UNKNOWN_ERROR;
}
/**
* Attempt to recover from recoverable errors
*/
async attemptRecovery(errorCode, error, context) {
const recoveryKey = `${errorCode}_${context.projectName || 'default'}`;
if (!this.recoveryAttempts[recoveryKey]) {
this.recoveryAttempts[recoveryKey] = 0;
}
this.recoveryAttempts[recoveryKey]++;
if (this.recoveryAttempts[recoveryKey] > 3) {
this.logger.warn('Maximum recovery attempts reached');
return false;
}
this.logger.info(
`Attempting recovery (attempt ${this.recoveryAttempts[recoveryKey]}/3)`
);
switch (errorCode) {
case ErrorCodes.DIR_EXISTS:
return await this.recoverFromDirExists(context);
case ErrorCodes.GIT_NOT_INSTALLED:
case ErrorCodes.NPM_NOT_INSTALLED:
case ErrorCodes.PIP_NOT_INSTALLED:
return await this.recoverFromMissingDependency(errorCode, context);
case ErrorCodes.NETWORK_ERROR:
return await this.recoverFromNetworkError(context);
default:
return false;
}
}
/**
* Recovery strategies for specific error types
*/
async recoverFromDirExists() {
console.log(chalk.yellow('\nš” Recovery Options:'));
console.log(chalk.white(' 1. Choose a different project name'));
console.log(chalk.white(' 2. Remove the existing directory'));
console.log(chalk.white(' 3. Cancel the operation\n'));
// Return true to indicate recovery options were presented
return true;
}
async recoverFromMissingDependency(errorCode, context) {
console.log(chalk.yellow('\nš” Continuing without this feature...'));
// Mark the feature as skipped
if (context.skipFeatures) {
context.skipFeatures.push(errorCode);
}
return true;
}
async recoverFromNetworkError() {
console.log(chalk.yellow('\nš” Network issue detected'));
console.log(chalk.white(' You can install dependencies manually later:'));
console.log(
chalk.gray(' ⢠Backend: cd backend && pip install -r requirements.txt')
);
console.log(chalk.gray(' ⢠Frontend: cd frontend && npm install\n'));
return true;
}
/**
* Generate error report for debugging
*/
generateErrorReport() {
const report = {
timestamp: new Date().toISOString(),
platform: process.platform,
nodeVersion: process.version,
stackwizardVersion: process.env.npm_package_version || 'unknown',
errorCount: this.errorCount,
logFile: this.logger.getLogPath(),
};
if (this.logger.debugMode) {
const reportPath = path.join(
os.homedir(),
'.stackwizard',
'error-report.json'
);
fs.writeJsonSync(reportPath, report, { spaces: 2 });
console.log(chalk.gray(`\nError report saved to: ${reportPath}`));
}
return report;
}
}
/**
* Input validators with detailed error messages
*/
export const Validators = {
projectName: (input) => {
if (!input || input.trim() === '') {
return 'Project name cannot be empty';
}
if (!/^[a-z0-9-]+$/.test(input)) {
return 'Project name can only contain lowercase letters, numbers, and hyphens';
}
if (input.startsWith('-') || input.endsWith('-')) {
return 'Project name cannot start or end with a hyphen';
}
if (input.length > 50) {
return 'Project name is too long (max 50 characters)';
}
// Check for reserved names
const reserved = [
'node_modules',
'dist',
'build',
'test',
'tests',
'.git',
'.env',
'src',
'lib',
'bin',
];
if (reserved.includes(input.toLowerCase())) {
return `"${input}" is a reserved name. Please choose another`;
}
// Check for npm package naming conventions
if (input.includes('--')) {
return 'Project name cannot contain consecutive hyphens';
}
return true;
},
port: (input) => {
const port = parseInt(input);
if (isNaN(port)) {
return 'Port must be a number';
}
if (port < 1 || port > 65535) {
return 'Port must be between 1 and 65535';
}
// Check for commonly used ports
const commonPorts = [80, 443, 22, 21, 25, 3306, 5432, 27017];
if (commonPorts.includes(port)) {
return `Port ${port} is commonly used by other services. Consider using a different port`;
}
return true;
},
databaseName: (input) => {
if (!input || input.trim() === '') {
return 'Database name cannot be empty';
}
if (!/^[a-z0-9_]+$/.test(input)) {
return 'Database name can only contain lowercase letters, numbers, and underscores';
}
if (input.startsWith('_') || input.endsWith('_')) {
return 'Database name cannot start or end with an underscore';
}
return true;
},
databaseUser: (input) => {
if (!input || input.trim() === '') {
return 'Database user cannot be empty';
}
if (!/^[a-zA-Z0-9_]+$/.test(input)) {
return 'Database user can only contain letters, numbers, and underscores';
}
return true;
},
databasePassword: (input) => {
if (!input || input.trim() === '') {
return 'Database password cannot be empty';
}
if (input.length < 8) {
return 'Password should be at least 8 characters long for security';
}
return true;
},
};
/**
* Check system requirements
*/
export async function checkSystemRequirements(logger) {
const requirements = {
node: {
command: 'node --version',
name: 'Node.js',
optional: false,
minVersion: '14.0.0',
},
npm: {
command: 'npm --version',
name: 'npm',
optional: false,
minVersion: '6.0.0',
},
git: { command: 'git --version', name: 'Git', optional: true },
docker: { command: 'docker --version', name: 'Docker', optional: true },
python: {
commands: ['python3 --version', 'python --version'],
name: 'Python',
optional: true,
minVersion: '3.8.0',
},
pip: {
commands: ['pip3 --version', 'pip --version'],
name: 'pip',
optional: true,
},
};
const results = {};
for (const [key, req] of Object.entries(requirements)) {
try {
let stdout, version, usedCommand;
// Handle multiple command options (for Python and pip)
if (req.commands) {
let found = false;
for (const cmd of req.commands) {
try {
const result = await execAsync(cmd);
stdout = result.stdout;
version = extractVersion(stdout);
usedCommand = cmd.split(' ')[0]; // Extract just the command name
found = true;
break;
} catch {
// Try next command
continue;
}
}
if (!found) {
throw new Error(`${req.name} not found`);
}
} else {
// Handle single command
const result = await execAsync(req.command);
stdout = result.stdout;
version = extractVersion(stdout);
usedCommand = req.command.split(' ')[0];
}
results[key] = {
installed: true,
name: req.name,
version,
optional: req.optional,
command: usedCommand,
};
// Check minimum version if specified
if (req.minVersion && version) {
const meetsRequirement = compareVersions(version, req.minVersion) >= 0;
if (!meetsRequirement && !req.optional) {
throw new StackWizardError(
ErrorCodes.NODE_VERSION_MISMATCH,
`${req.name} version ${req.minVersion} or higher is required (found: ${version})`
);
}
results[key].meetsRequirement = meetsRequirement;
}
logger.debug(
`${req.name} is installed (version: ${version}, command: ${usedCommand})`
);
} catch (error) {
if (error instanceof StackWizardError) {
throw error;
}
results[key] = {
installed: false,
name: req.name,
optional: req.optional,
version: null,
};
if (!req.optional) {
throw new StackWizardError(
ErrorCodes[`${key.toUpperCase()}_NOT_INSTALLED`] ||
ErrorCodes.COMMAND_NOT_FOUND,
`${req.name} is required but not installed`
);
} else {
logger.warn(`${req.name} is not installed (optional)`);
}
}
}
return results;
}
/**
* Clean up on error
*/
export async function cleanup(projectPath, logger) {
if (projectPath && fs.existsSync(projectPath)) {
try {
logger.info(`Cleaning up incomplete project at ${projectPath}`);
// Create backup before cleanup if project has substantial content
const stats = await fs.stat(projectPath);
if (stats.size > 1024 * 1024) {
// If > 1MB, create backup
const backupPath = `${projectPath}.backup-${Date.now()}`;
logger.info(`Creating backup at ${backupPath}`);
await fs.copy(projectPath, backupPath);
console.log(chalk.yellow(`Backup created at: ${backupPath}`));
}
await fs.remove(projectPath);
console.log(chalk.yellow('ā Cleaned up incomplete project files'));
} catch (error) {
logger.error('Failed to clean up project files', error);
console.log(
chalk.red(
'ā ļø Failed to clean up. Please manually remove: ' + projectPath
)
);
}
}
}
/**
* Extract version from command output
*/
function extractVersion(output) {
const versionMatch = output.match(/(\d+\.\d+\.\d+)/);
return versionMatch ? versionMatch[1] : null;
}
/**
* Compare semantic versions
*/
function compareVersions(current, required) {
const currentParts = current.split('.').map(Number);
const requiredParts = required.split('.').map(Number);
for (
let i = 0;
i < Math.max(currentParts.length, requiredParts.length);
i++
) {
const currentPart = currentParts[i] || 0;
const requiredPart = requiredParts[i] || 0;
if (currentPart > requiredPart) return 1;
if (currentPart < requiredPart) return -1;
}
return 0;
}
/**
* Check if port is available
*/
export async function isPortAvailable(port) {
return new Promise((resolve) => {
const server = net.createServer();
server.once('error', (err) => {
if (err.code === 'EADDRINUSE') {
resolve(false);
} else {
resolve(false);
}
});
server.once('listening', () => {
server.close();
resolve(true);
});
server.listen(port, '127.0.0.1');
});
}
/**
* Get system information for debugging
*/
export function getSystemInfo() {
return {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
npmVersion: process.env.npm_version || 'unknown',
memory: {
total: os.totalmem(),
free: os.freemem(),
used: process.memoryUsage(),
},
cpu: {
cores: os.cpus().length,
model: os.cpus()[0]?.model || 'unknown',
usage: process.cpuUsage(),
},
user: os.userInfo(),
cwd: process.cwd(),
env: {
PATH: process.env.PATH,
NODE_ENV: process.env.NODE_ENV,
DEBUG: process.env.DEBUG,
},
};
}
/**
* Format bytes to human readable size
*/
export function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
/**
* Create error context for debugging
*/
export function createErrorContext(error, additionalContext = {}) {
return {
error: {
name: error.name,
message: error.message,
code: error.code,
stack: error.stack,
},
system: getSystemInfo(),
context: additionalContext,
timestamp: new Date().toISOString(),
};
}