UNPKG

@tw-enigma/core

Version:

CSS optimization engine for tw-enigma

1,375 lines (1,370 loc) 1.24 MB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/utils/logger.ts function parseLogLevel(level) { if (!level) return LogLevel.INFO; const upperLevel = level.toUpperCase(); switch (upperLevel) { case "TRACE": return LogLevel.TRACE; case "DEBUG": return LogLevel.DEBUG; case "INFO": return LogLevel.INFO; case "WARN": return LogLevel.WARN; case "ERROR": return LogLevel.ERROR; case "FATAL": return LogLevel.FATAL; default: return LogLevel.INFO; } } function createFileOutputFromEnv() { const filePath = process.env.ENIGMA_LOG_FILE; if (!filePath) return void 0; return { filePath, format: process.env.ENIGMA_LOG_FORMAT || "human", maxSize: process.env.ENIGMA_LOG_MAX_SIZE ? parseInt(process.env.ENIGMA_LOG_MAX_SIZE) : void 0, maxFiles: process.env.ENIGMA_LOG_MAX_FILES ? parseInt(process.env.ENIGMA_LOG_MAX_FILES) : void 0, compress: process.env.ENIGMA_LOG_COMPRESS === "true" }; } function createLogger(component, _options) { return logger.child(component, _options); } var import_chalk, import_fs, import_path, import_zlib, LogLevel, LogLevelNames, LogLevelColors, Logger, logger; var init_logger = __esm({ "src/utils/logger.ts"() { "use strict"; import_chalk = __toESM(require("chalk")); import_fs = require("fs"); import_path = require("path"); import_zlib = require("zlib"); LogLevel = { TRACE: 0, DEBUG: 1, INFO: 2, WARN: 3, ERROR: 4, FATAL: 5 }; LogLevelNames = { [LogLevel.TRACE]: "TRACE", [LogLevel.DEBUG]: "DEBUG", [LogLevel.INFO]: "INFO", [LogLevel.WARN]: "WARN", [LogLevel.ERROR]: "ERROR", [LogLevel.FATAL]: "FATAL" }; LogLevelColors = { [LogLevel.TRACE]: import_chalk.default.gray, [LogLevel.DEBUG]: import_chalk.default.cyan, [LogLevel.INFO]: import_chalk.default.blue, [LogLevel.WARN]: import_chalk.default.yellow, [LogLevel.ERROR]: import_chalk.default.red, [LogLevel.FATAL]: import_chalk.default.magenta }; Logger = class _Logger { constructor(options = {}) { this.progressStates = /* @__PURE__ */ new Map(); this.level = options.level ?? LogLevel.INFO; this.verbose = options.verbose ?? false; this.veryVerbose = options.veryVerbose ?? false; this.quiet = options.quiet ?? false; this.silent = options.silent ?? false; this.outputFormat = options.outputFormat ?? "human"; this.colorize = options.colorize ?? true; this.timestamp = options.timestamp ?? true; this.component = options.component; this.fileOutput = options.fileOutput; this.enableProgressTracking = options.enableProgressTracking ?? false; if (this.veryVerbose) { this.verbose = true; if (this.level > LogLevel.TRACE) { this.level = LogLevel.TRACE; } } else if (this.verbose && this.level > LogLevel.DEBUG) { this.level = LogLevel.DEBUG; } if (this.quiet) { this.verbose = false; this.veryVerbose = false; if (this.level < LogLevel.WARN) { this.level = LogLevel.WARN; } } if (this.fileOutput) { this.initializeFileOutput(); } } /** * Initialize file output with rotation support */ initializeFileOutput() { if (!this.fileOutput) return; try { const dir = (0, import_path.dirname)(this.fileOutput.filePath); if (!(0, import_fs.existsSync)(dir)) { (0, import_fs.mkdirSync)(dir, { recursive: true }); } this.rotateLogsIfNeeded(); this.fileStream = (0, import_fs.createWriteStream)(this.fileOutput.filePath, { flags: "a" }); this.fileStream.on("error", (error) => { console.error(`Logger file stream error: ${error.message}`); this.fileStream = void 0; }); } catch (error) { console.error( `Failed to initialize file output: ${error instanceof Error ? error.message : String(error)}` ); this.fileStream = void 0; } } /** * Rotate log files if size limit exceeded */ rotateLogsIfNeeded() { if (!this.fileOutput || !(0, import_fs.existsSync)(this.fileOutput.filePath)) return; const maxSize = this.fileOutput.maxSize ?? 10 * 1024 * 1024; const maxFiles = this.fileOutput.maxFiles ?? 5; const stats = (0, import_fs.statSync)(this.fileOutput.filePath); if (stats.size >= maxSize) { this.rotateLogFiles(maxFiles); } } /** * Perform log file rotation with optional compression */ rotateLogFiles(maxFiles) { if (!this.fileOutput) return; const basePath = this.fileOutput.filePath; const compress = this.fileOutput.compress ?? false; if (this.fileStream) { this.fileStream.end(); this.fileStream = void 0; } for (let i = maxFiles - 1; i >= 1; i--) { const oldPath = `${basePath}.${i}${compress ? ".gz" : ""}`; const newPath = `${basePath}.${i + 1}${compress ? ".gz" : ""}`; if ((0, import_fs.existsSync)(oldPath)) { if (i === maxFiles - 1) { (0, import_fs.unlinkSync)(oldPath); } else { (0, import_fs.unlinkSync)(newPath); require("fs").renameSync(oldPath, newPath); } } } if ((0, import_fs.existsSync)(basePath)) { const rotatedPath = `${basePath}.1`; if (compress) { const content = require("fs").readFileSync(basePath); const compressed = (0, import_zlib.gzipSync)(content); require("fs").writeFileSync(`${rotatedPath}.gz`, compressed); (0, import_fs.unlinkSync)(basePath); } else { require("fs").renameSync(basePath, rotatedPath); } } } /** * Set the minimum log level */ setLevel(level) { this.level = level; } /** * Enable or disable verbose logging */ setVerbose(verbose) { this.verbose = verbose; if (verbose && this.level > LogLevel.DEBUG) { this.level = LogLevel.DEBUG; } } /** * Enable or disable very verbose logging */ setVeryVerbose(veryVerbose) { this.veryVerbose = veryVerbose; if (veryVerbose) { this.verbose = true; if (this.level > LogLevel.TRACE) { this.level = LogLevel.TRACE; } } } /** * Enable or disable quiet mode */ setQuiet(quiet) { this.quiet = quiet; if (quiet) { this.verbose = false; this.veryVerbose = false; if (this.level < LogLevel.WARN) { this.level = LogLevel.WARN; } } } /** * Enable or disable silent mode */ setSilent(silent) { this.silent = silent; } /** * Set output format */ setOutputFormat(format) { this.outputFormat = format; } /** * Configure file output */ setFileOutput(options) { if (this.fileStream) { this.fileStream.end(); this.fileStream = void 0; } this.fileOutput = options; this.initializeFileOutput(); } /** * Disable file output */ disableFileOutput() { if (this.fileStream) { this.fileStream.end(); this.fileStream = void 0; } this.fileOutput = void 0; } /** * Start progress tracking for an operation */ startProgress(id, options) { if (!this.enableProgressTracking) return; this.progressStates.set(id, { ...options, startTime: Date.now() }); if (this.verbose) { const label = options.label || id; this.info(`\u{1F4CA} Starting ${label} (0/${options.total})`); } } /** * Update progress for an operation */ updateProgress(id, current, additionalInfo) { if (!this.enableProgressTracking) return; const progress = this.progressStates.get(id); if (!progress) return; progress.current = current; const percentage = Math.round(current / progress.total * 100); const elapsed = Date.now() - progress.startTime; let message = `\u{1F4C8} ${progress.label || id}: ${current}/${progress.total}`; if (progress.showPercentage !== false) { message += ` (${percentage}%)`; } if (progress.showETA !== false && current > 0) { const estimatedTotal = elapsed / current * progress.total; const eta = Math.round((estimatedTotal - elapsed) / 1e3); message += ` ETA: ${eta}s`; } if (additionalInfo) { message += ` - ${additionalInfo}`; } if (this.verbose) { this.debug(message); } } /** * Complete progress tracking for an operation */ completeProgress(id, summary) { if (!this.enableProgressTracking) return; const progress = this.progressStates.get(id); if (!progress) return; const elapsed = Date.now() - progress.startTime; const duration = Math.round(elapsed / 1e3); let message = `\u2705 Completed ${progress.label || id} (${progress.total} items in ${duration}s)`; if (summary) { message += ` - ${summary}`; } if (this.verbose) { this.info(message); } this.progressStates.delete(id); } /** * Log performance metrics */ performanceMetrics(operation, metrics, context) { const extendedContext = { ...context, operation, processingTime: metrics.processingTime, memoryUsage: metrics.memoryUsage.heapUsed, fileCount: metrics.fileCount, totalFileSize: metrics.totalFileSize, optimizationRatio: metrics.optimizationRatio }; const heapMB = Math.round(metrics.memoryUsage.heapUsed / 1024 / 1024); let message = `\u26A1 ${operation} completed in ${metrics.processingTime}ms (heap: ${heapMB}MB)`; if (metrics.fileCount) { message += `, processed ${metrics.fileCount} files`; } if (metrics.totalFileSize) { const sizeMB = Math.round(metrics.totalFileSize / 1024 / 1024 * 100) / 100; message += `, total size: ${sizeMB}MB`; } if (metrics.optimizationRatio) { const ratio = Math.round(metrics.optimizationRatio * 100); message += `, optimization: ${ratio}%`; } if (this.veryVerbose) { this.trace(message, extendedContext); } else if (this.verbose) { this.debug(message, extendedContext); } } /** * Log detailed file operation */ fileOperation(operation, filePath, details) { if (!this.veryVerbose) return; let message = `\u{1F4C1} ${operation}: ${filePath}`; const context = { operation, filePath }; if (details?.size) { const sizeKB = Math.round(details.size / 1024); message += ` (${sizeKB}KB)`; context.fileSize = details.size; } if (details?.processingTime) { message += ` - ${details.processingTime}ms`; context.processingTime = details.processingTime; } if (details?.result) { message += ` \u2192 ${details.result}`; } this.trace(message, context); } /** * Log step-by-step process details */ processStep(step, details, context) { if (!this.verbose) return; let message = `\u{1F504} ${step}`; if (details) { message += `: ${details}`; } this.debug(message, context); } /** * Create a child logger with additional context */ child(component, options = {}) { return new _Logger({ level: this.level, verbose: this.verbose, veryVerbose: this.veryVerbose, quiet: this.quiet, silent: this.silent, outputFormat: this.outputFormat, colorize: this.colorize, timestamp: this.timestamp, component, fileOutput: this.fileOutput, enableProgressTracking: this.enableProgressTracking, ...options }); } /** * Check if a log level should be output */ shouldLog(level) { if (this.silent) return false; return level >= this.level; } /** * Format timestamp */ getTimestamp() { return (/* @__PURE__ */ new Date()).toISOString(); } /** * Create a structured log entry */ createLogEntry(level, message, context, error) { const entry = { level: LogLevelNames[level], message, timestamp: this.getTimestamp() }; if (this.component) { entry.component = this.component; } if (context && Object.keys(context).length > 0) { entry.context = { ...context }; } if (error) { entry.error = { name: error.name, message: error.message, stack: error.stack, code: error.code }; } return entry; } /** * Format log entry for human-readable output */ formatHuman(entry) { const levelName = entry.level.padEnd(5); const colorFn = LogLevelColors[LogLevel[entry.level]]; let output = ""; if (this.timestamp) { output += import_chalk.default.gray(`[${entry.timestamp}] `); } if (this.colorize) { output += colorFn(`${levelName} `); } else { output += `${levelName} `; } if (entry.component) { output += import_chalk.default.gray(`[${entry.component}] `); } output += entry.message; if (entry.context && Object.keys(entry.context).length > 0) { output += import_chalk.default.gray(` ${JSON.stringify(entry.context)}`); } if (entry.error) { output += "\n" + (entry.error.stack || `${entry.error.name}: ${entry.error.message}`); } return output; } /** * Format log entry for CSV output */ formatCSV(entry) { const timestamp = entry.timestamp; const level = entry.level; const component = entry.component || ""; const message = entry.message.replace(/"/g, '""'); const context = entry.context ? JSON.stringify(entry.context).replace(/"/g, '""') : ""; const errorMessage = entry.error ? `${entry.error.name}: ${entry.error.message}`.replace(/"/g, '""') : ""; return `"${timestamp}","${level}","${component}","${message}","${context}","${errorMessage}"`; } /** * Output a log entry to console and/or file */ output(entry) { if (this.outputFormat === "json") { if (entry.level === "ERROR" || entry.level === "FATAL") { console.error(JSON.stringify(entry)); } else { console.log(JSON.stringify(entry)); } } else { const formattedMessage = this.formatHuman(entry); if (entry.level === "ERROR" || entry.level === "FATAL") { console.error(formattedMessage); } else { console.log(formattedMessage); } } if (this.fileStream && !this.fileStream.destroyed) { try { let fileContent; const fileFormat = this.fileOutput?.format || "human"; switch (fileFormat) { case "json": fileContent = JSON.stringify(entry) + "\n"; break; case "csv": fileContent = this.formatCSV(entry) + "\n"; break; default: fileContent = this.formatHuman(entry) + "\n"; } this.fileStream.write(fileContent); this.rotateLogsIfNeeded(); } catch (error) { console.error( `Failed to write to log file: ${error instanceof Error ? error.message : String(error)}` ); } } } /** * Core logging method */ log(level, message, context, _error) { if (!this.shouldLog(level)) return; const entry = this.createLogEntry(level, message, context, _error); this.output(entry); } /** * Log a trace message (most verbose) */ trace(message, context) { this.log(LogLevel.TRACE, message, context); } /** * Log a debug message */ debug(message, context) { this.log(LogLevel.DEBUG, message, context); } /** * Log an info message */ info(message, context) { this.log(LogLevel.INFO, message, context); } /** * Log a warning message */ warn(message, context) { this.log(LogLevel.WARN, message, context); } /** * Log an error message */ error(messageOrError, context) { if (messageOrError instanceof Error) { this.log(LogLevel.ERROR, messageOrError.message, context, messageOrError); } else { this.log(LogLevel.ERROR, messageOrError, context); } } /** * Log a fatal error message */ fatal(messageOrError, context) { if (messageOrError instanceof Error) { this.log(LogLevel.FATAL, messageOrError.message, context, messageOrError); } else { this.log(LogLevel.FATAL, messageOrError, context); } } /** * Log performance timing */ timing(operation, duration, context) { const extendedContext = { ...context, operation, processingTime: duration }; this.debug(`Operation "${operation}" completed in ${duration}ms`, extendedContext); } /** * Clean up resources (close file streams) */ cleanup() { if (this.fileStream) { this.fileStream.end(); this.fileStream = void 0; } this.progressStates.clear(); } /** * Get current logger state for debugging */ getState() { return { level: this.level, verbose: this.verbose, veryVerbose: this.veryVerbose, quiet: this.quiet, silent: this.silent, fileOutputEnabled: !!this.fileOutput, progressTrackingEnabled: this.enableProgressTracking, activeProgressCount: this.progressStates.size }; } }; logger = new Logger({ level: process.env.ENIGMA_LOG_LEVEL ? parseLogLevel(process.env.ENIGMA_LOG_LEVEL) : process.env.NODE_ENV === "development" ? LogLevel.DEBUG : LogLevel.INFO, verbose: process.env.ENIGMA_VERBOSE === "true", veryVerbose: process.env.ENIGMA_VERY_VERBOSE === "true", quiet: process.env.ENIGMA_QUIET === "true", colorize: process.stdout.isTTY, timestamp: true, fileOutput: createFileOutputFromEnv(), enableProgressTracking: process.env.ENIGMA_PROGRESS_TRACKING !== "false" }); } }); // src/utils/pathUtils.ts function createPathUtils(options = {}) { return new PathUtils(options); } function calculateRelativePath(fromPath, toPath, options = {}) { const utils = createPathUtils(options); const result = utils.calculateRelativePath(fromPath, toPath); return result.relativePath; } function validatePath(inputPath, context = "path") { const utils = createPathUtils(); return utils.validatePath(inputPath, context); } function normalizePath(inputPath, forWeb = false) { const utils = createPathUtils(); return utils.normalizePath(inputPath, forWeb); } function isPathSafe(inputPath) { const validation = validatePath(inputPath); return validation.isValid && !validation.security.hasTraversal; } function calculateRelativePathsBatch(pairs, options = {}) { const utils = createPathUtils(options); return pairs.map(({ from, to }) => utils.calculateRelativePath(from, to)); } var path, import_zod10, PathCalculationOptionsSchema, PathUtilsError, PathSecurityError, PathValidationError, PathUtils; var init_pathUtils = __esm({ "src/utils/pathUtils.ts"() { "use strict"; path = __toESM(require("path")); import_zod10 = require("zod"); PathCalculationOptionsSchema = import_zod10.z.object({ /** Use relative paths instead of absolute paths */ useRelativePaths: import_zod10.z.boolean().default(true), /** Base path for resolving relative paths */ basePath: import_zod10.z.string().optional(), /** Whether to normalize paths for web use (forward slashes) */ normalizeForWeb: import_zod10.z.boolean().default(true), /** Maximum allowed path depth to prevent excessive nesting */ maxDepth: import_zod10.z.number().min(1).max(100).default(50), /** Whether to resolve symbolic links */ resolveSymlinks: import_zod10.z.boolean().default(false), /** Enable path traversal protection */ enableSecurity: import_zod10.z.boolean().default(true) }); PathUtilsError = class extends Error { constructor(message, code, cause) { super(message); this.name = "PathUtilsError"; this.code = code; this.cause = cause; } }; PathSecurityError = class extends PathUtilsError { constructor(message, path8, cause) { super(message, "PATH_SECURITY_ERROR", cause); this.name = "PathSecurityError"; this.path = path8; } }; PathValidationError = class extends PathUtilsError { constructor(message, path8, cause) { super(message, "PATH_VALIDATION_ERROR", cause); this.name = "PathValidationError"; this.path = path8; } }; PathUtils = class { constructor(options = {}) { this.pathCache = /* @__PURE__ */ new Map(); this.validationCache = /* @__PURE__ */ new Map(); this.maxCacheSize = 1e3; this.options = PathCalculationOptionsSchema.parse(options); } /** * Calculate relative path from one file to another */ calculateRelativePath(fromPath, toPath, options) { const mergedOptions = { ...this.options, ...options }; const cacheKey = `${fromPath}::${toPath}::${JSON.stringify(mergedOptions, Object.keys(mergedOptions).sort())}`; if (this.pathCache.has(cacheKey)) { return this.pathCache.get(cacheKey); } try { const fromValidation = this.validatePath(fromPath, "fromPath"); if (!fromValidation.isValid) { throw new PathValidationError( `Invalid fromPath: ${fromValidation.errors.join(", ")}`, fromPath ); } const toValidation = this.validatePath(toPath, "toPath"); if (!toValidation.isValid) { throw new PathValidationError(`Invalid toPath: ${toValidation.errors.join(", ")}`, toPath); } if (!mergedOptions.useRelativePaths) { let normalizedPath = toPath; if (mergedOptions.normalizeForWeb) { normalizedPath = this.normalizeForWeb(toPath); if (process.platform === "win32") { normalizedPath = normalizedPath.toLowerCase(); } } const result2 = { relativePath: normalizedPath, isValid: true, normalizedPath, metadata: { fromPath, toPath, basePath: mergedOptions.basePath, platformSeparators: path.sep, webPath: normalizedPath, depth: this.calculatePathDepth(normalizedPath) } }; this.cacheResult(cacheKey, result2); return result2; } let normalizedFromPath = this.normalizePlatformPath(fromPath); let normalizedToPath = this.normalizePlatformPath(toPath); if (mergedOptions.basePath) { if (!path.isAbsolute(normalizedFromPath)) { normalizedFromPath = path.resolve(mergedOptions.basePath, normalizedFromPath); } if (!path.isAbsolute(normalizedToPath)) { normalizedToPath = path.resolve(mergedOptions.basePath, normalizedToPath); } } const fromDir = path.dirname(normalizedFromPath); const relativePath = path.relative(fromDir, normalizedToPath); const webPath = mergedOptions.normalizeForWeb ? this.normalizeForWeb(relativePath) : relativePath; if (mergedOptions.enableSecurity) { this.performSecurityCheck(webPath, fromPath, toPath); } const result = { relativePath: webPath, isValid: true, normalizedPath: webPath, metadata: { fromPath: normalizedFromPath, toPath: normalizedToPath, basePath: mergedOptions.basePath, platformSeparators: path.sep, webPath, depth: this.calculatePathDepth(webPath) } }; this.cacheResult(cacheKey, result); return result; } catch (error) { throw new PathUtilsError( `Failed to calculate relative path: ${error instanceof Error ? error.message : String(error)}`, "CALCULATION_ERROR", error instanceof Error ? error : void 0 ); } } /** * Validate a path for security and correctness */ validatePath(inputPath, context = "path") { const cacheKey = `validate::${inputPath}::${context}`; if (this.validationCache.has(cacheKey)) { return this.validationCache.get(cacheKey); } const result = { isValid: true, normalizedPath: inputPath, errors: [], warnings: [], security: { hasTraversal: false, isAbsolute: false, depth: 0 } }; try { if (!inputPath || typeof inputPath !== "string") { result.isValid = false; result.errors.push(`${context} must be a non-empty string`); return result; } const trimmedPath = inputPath.trim(); if (trimmedPath !== inputPath) { result.warnings.push(`${context} has leading/trailing whitespace`); } if (this.options.enableSecurity) { if (trimmedPath.includes("..")) { result.security.hasTraversal = true; result.warnings.push(`${context} contains path traversal sequences`); } if (trimmedPath.includes("\0")) { result.isValid = false; result.errors.push(`${context} contains null bytes (security risk)`); return result; } const suspiciousPatterns = [ /\.(\.)+/, // Multiple dots /[<>:"|?*]/, // Invalid Windows characters /^\s*$/ // Whitespace only ]; for (const pattern of suspiciousPatterns) { if (pattern.test(trimmedPath)) { result.warnings.push(`${context} contains potentially problematic characters`); break; } } } result.normalizedPath = this.normalizePath(trimmedPath, true); result.security.isAbsolute = path.isAbsolute(trimmedPath); result.security.depth = this.calculatePathDepth(result.normalizedPath); if (result.security.depth > this.options.maxDepth) { result.isValid = false; result.errors.push(`${context} exceeds maximum depth of ${this.options.maxDepth}`); } this.cacheValidationResult(cacheKey, result); return result; } catch (error) { result.isValid = false; result.errors.push( `Validation failed: ${error instanceof Error ? error.message : String(error)}` ); return result; } } /** * Normalize path for comparison and consistency */ normalizePath(inputPath, forWeb = false) { if (!inputPath) return ""; let normalized = inputPath; if (forWeb) { normalized = normalized.toLowerCase(); } else if (process.platform === "win32") { normalized = normalized.toLowerCase(); } if (forWeb) { normalized = normalized.replace(/\\/g, "/"); } else { normalized = path.normalize(normalized); if (process.platform === "win32" && inputPath.includes("/") && !inputPath.includes("\\")) { normalized = normalized.replace(/\\/g, "/"); } } normalized = normalized.replace(/^\.\//, ""); normalized = normalized.replace(/^\.\\/, ""); if (normalized === "/" || normalized === "\\") { return "/"; } if (forWeb && normalized.startsWith("/") && normalized.length > 1) { normalized = normalized.substring(1); } if (forWeb && normalized.startsWith("\\") && normalized.length > 1) { normalized = normalized.substring(1); } const separator = forWeb ? "/" : normalized.includes("/") ? "/" : path.sep; if (separator === "/") { normalized = normalized.replace(/\/+/g, "/"); } else { normalized = normalized.replace(/\\+/g, "\\"); } if (normalized.length > 1) { normalized = normalized.replace(/\/$/, ""); normalized = normalized.replace(/\\$/, ""); } return normalized; } /** * Normalize path for web use (forward slashes only) */ normalizeForWeb(inputPath) { return inputPath.replace(/\\/g, "/"); } /** * Normalize path using platform-specific separators */ normalizePlatformPath(inputPath) { return inputPath.replace(/[/\\]/g, path.sep); } /** * Calculate the depth of a path (number of directory levels) */ calculatePathDepth(inputPath) { if (!inputPath || inputPath === "." || inputPath === "/") return 0; const normalizedPath = this.normalizePath(inputPath, true); const segments = normalizedPath.split("/").filter((segment) => segment && segment !== "."); return segments.length; } /** * Perform security checks on calculated paths */ performSecurityCheck(calculatedPath, _fromPath, _toPath) { if (calculatedPath.includes("..")) { const depth = (calculatedPath.match(/\.\./g) || []).length; if (depth > 10) { throw new PathSecurityError( `Excessive path traversal detected (${depth} levels up)`, calculatedPath ); } } if (this.options.useRelativePaths && path.isAbsolute(calculatedPath)) { throw new PathSecurityError( "Unexpected absolute path in relative calculation result", calculatedPath ); } } /** * Cache management */ cacheResult(key, result) { if (this.pathCache.size >= this.maxCacheSize) { const firstKey = this.pathCache.keys().next().value; if (firstKey) this.pathCache.delete(firstKey); } this.pathCache.set(key, result); } cacheValidationResult(key, result) { if (this.validationCache.size >= this.maxCacheSize) { const firstKey = this.validationCache.keys().next().value; if (firstKey) this.validationCache.delete(firstKey); } this.validationCache.set(key, result); } /** * Clear all caches */ clearCache() { this.pathCache.clear(); this.validationCache.clear(); } /** * Get cache statistics */ getCacheStats() { return { paths: this.pathCache.size, validations: this.validationCache.size, maxSize: this.maxCacheSize }; } }; } }); // src/processors/htmlExtractor.ts function createHtmlExtractor(options = {}) { return new HtmlExtractor(options); } async function extractClassesFromHtml(html, options = {}) { const extractor = new HtmlExtractor(options); return extractor.extractFromString(html); } async function extractClassesFromFile(filePath, options = {}) { const extractor = new HtmlExtractor(options); return extractor.extractFromFile(filePath); } var cheerio2, fs3, import_zod12, HtmlExtractionOptionsSchema, HtmlParsingError, FileReadError, HtmlExtractor; var init_htmlExtractor = __esm({ "src/processors/htmlExtractor.ts"() { "use strict"; cheerio2 = __toESM(require("cheerio")); fs3 = __toESM(require("fs/promises")); import_zod12 = require("zod"); HtmlExtractionOptionsSchema = import_zod12.z.object({ preserveWhitespace: import_zod12.z.boolean().default(false), caseSensitive: import_zod12.z.boolean().default(true), ignoreEmpty: import_zod12.z.boolean().default(true), maxFileSize: import_zod12.z.number().min(1).default(10 * 1024 * 1024), // 10MB timeout: import_zod12.z.number().min(1).default(5e3) // 5 seconds }); HtmlParsingError = class extends Error { constructor(message, source, cause) { super(message); this.name = "HtmlParsingError"; this.source = source; this.cause = cause; } }; FileReadError = class extends Error { constructor(message, filePath, cause) { super(message); this.name = "FileReadError"; this.filePath = filePath; this.cause = cause; } }; HtmlExtractor = class { constructor(options = {}) { this.options = HtmlExtractionOptionsSchema.parse(options); } /** * Extract classes from HTML string */ async extractFromString(html, source = "string") { const startTime = Date.now(); const metadata = { source, processedAt: /* @__PURE__ */ new Date(), processingTime: 0, errors: [] }; try { const $ = cheerio2.load(html, { xml: { xmlMode: false, decodeEntities: true, withStartIndices: false, withEndIndices: false } }); const classes = /* @__PURE__ */ new Map(); let totalElements = 0; let totalClasses = 0; $("[class]").each((index, element) => { totalElements++; const $element = $(element); const classAttr = $element.attr("class"); if (!classAttr) return; const elementClasses = this.parseClassAttribute(classAttr); totalClasses += elementClasses.length; const tagName = element.tagName?.toLowerCase() || "unknown"; const attributes = element.attribs || {}; const depth = this.calculateDepth($element); elementClasses.forEach((className) => { if (!this.options.caseSensitive) { className = className.toLowerCase(); } if (!classes.has(className)) { classes.set(className, { name: className, frequency: 0, contexts: [] }); } const classData = classes.get(className); classData.frequency++; if (classData.contexts.length < 10) { classData.contexts.push({ tagName, attributes: this.sanitizeAttributes(attributes), depth }); } }); }); metadata.processingTime = Date.now() - startTime; return { classes, totalElements, totalClasses, uniqueClasses: classes.size, metadata }; } catch (error) { metadata.errors.push(error instanceof Error ? error.message : String(error)); metadata.processingTime = Date.now() - startTime; throw new HtmlParsingError( `Failed to parse HTML: ${error instanceof Error ? error.message : String(error)}`, source, error instanceof Error ? error : void 0 ); } } /** * Extract classes from HTML file */ async extractFromFile(filePath) { try { const stats = await fs3.stat(filePath); if (stats.size > this.options.maxFileSize) { throw new FileReadError( `File size (${stats.size} bytes) exceeds maximum allowed size (${this.options.maxFileSize} bytes)`, filePath ); } const html = await this.readFileWithTimeout(filePath, this.options.timeout); const result = await this.extractFromString(html, filePath); result.metadata.fileSize = stats.size; return result; } catch (error) { if (error instanceof HtmlParsingError || error instanceof FileReadError) { throw error; } throw new FileReadError( `Failed to read file: ${error instanceof Error ? error.message : String(error)}`, filePath, error instanceof Error ? error : void 0 ); } } /** * Extract classes from multiple HTML files */ async extractFromFiles(filePaths) { const results = []; for (const filePath of filePaths) { try { const result = await this.extractFromFile(filePath); results.push(result); } catch (error) { results.push({ classes: /* @__PURE__ */ new Map(), totalElements: 0, totalClasses: 0, uniqueClasses: 0, metadata: { source: filePath, processedAt: /* @__PURE__ */ new Date(), processingTime: 0, errors: [error instanceof Error ? error.message : String(error)] } }); } } return results; } /** * Parse class attribute string into individual class names */ parseClassAttribute(classAttr) { if (!classAttr || !this.options.preserveWhitespace && !classAttr.trim()) { return []; } const classes = classAttr.split(/\s+/).map((cls) => this.options.preserveWhitespace ? cls : cls.trim()).filter((cls) => this.options.ignoreEmpty ? cls.length > 0 : true); return classes; } /** * Calculate the depth of an element in the DOM tree */ calculateDepth($element) { let depth = 0; let current = $element.parent(); while (current.length > 0 && current.prop("tagName") !== "HTML") { depth++; current = current.parent(); } return depth; } /** * Sanitize element attributes to avoid sensitive data exposure */ sanitizeAttributes(attributes) { const sanitized = {}; const allowedAttributes = ["id", "class", "data-", "aria-"]; Object.entries(attributes).forEach(([key, value]) => { if (allowedAttributes.some((allowed) => key.startsWith(allowed))) { sanitized[key] = value.length > 100 ? value.substring(0, 100) + "..." : value; } }); return sanitized; } /** * Read file with timeout protection */ async readFileWithTimeout(filePath, timeout) { return new Promise((resolve9, reject) => { const timer = setTimeout(() => { reject(new Error(`File read timeout after ${timeout}ms`)); }, timeout); fs3.readFile(filePath, "utf8").then((content) => { clearTimeout(timer); resolve9(content); }).catch((error) => { clearTimeout(timer); reject(error); }); }); } }; } }); // src/processors/jsExtractor.ts function createJsExtractor(options = {}) { return new JsExtractor(options); } async function extractClassesFromJs(code, options = {}) { const extractor = createJsExtractor(options); return extractor.extractFromString(code); } async function extractClassesFromJsFile(filePath, options = {}) { const extractor = createJsExtractor(options); return extractor.extractFromFile(filePath); } var fs5, import_zod14, JsExtractionOptionsSchema, JsParsingError, JsFileReadError, RegexPatterns, JsExtractor; var init_jsExtractor = __esm({ "src/processors/jsExtractor.ts"() { "use strict"; fs5 = __toESM(require("fs/promises")); import_zod14 = require("zod"); JsExtractionOptionsSchema = import_zod14.z.object({ enableFrameworkDetection: import_zod14.z.boolean().default(true), includeDynamicClasses: import_zod14.z.boolean().default(true), caseSensitive: import_zod14.z.boolean().default(true), ignoreEmpty: import_zod14.z.boolean().default(true), maxFileSize: import_zod14.z.number().min(1).default(10 * 1024 * 1024), // 10MB timeout: import_zod14.z.number().min(1).default(1e4), // 10 seconds supportedFrameworks: import_zod14.z.array(import_zod14.z.string()).default(["react", "preact", "solid", "vue", "angular"]) }); JsParsingError = class extends Error { constructor(message, source, cause) { super(message); this.name = "JsParsingError"; this.source = source; this.cause = cause; } }; JsFileReadError = class extends Error { constructor(message, filePath, cause) { super(message); this.name = "JsFileReadError"; this.filePath = filePath; this.cause = cause; } }; RegexPatterns = class { }; // Static className/class patterns RegexPatterns.STATIC_CLASSNAME = /(?:className|class)\s*=\s*["'`]([^"'`]*?)["'`]/g; // Template literal patterns with simple content RegexPatterns.TEMPLATE_SIMPLE = /(?:className|class)\s*=\s*\{`([^`]*?)`\}/g; // Dynamic expression patterns (basic) RegexPatterns.DYNAMIC_EXPRESSION = /(?:className|class)\s*=\s*\{([^}]*?)\}/g; // Utility function patterns (clsx, classnames, cn) RegexPatterns.UTILITY_FUNCTIONS = /(?:clsx|classnames|cn)\s*\(([^)]*)\)/g; // JavaScript variable assignments with quoted strings (for extracting class strings from variables) RegexPatterns.JS_STRING_LITERALS = /(?:const|let|var)\s+\w+\s*=\s*["'`]([^"'`]*?)["'`]/g; // Object property values with quoted strings (for extracting classes from object literals) RegexPatterns.OBJECT_PROPERTY_STRINGS = /\w+\s*:\s*["'`]([^"'`]*?)["'`]/g; // Framework detection patterns RegexPatterns.REACT_IMPORT = /(?:import.*?from\s+['"]react['"]|import\s+React|from\s+['"]react['"])/; RegexPatterns.PREACT_IMPORT = /(?:import.*?from\s+['"]preact['"]|from\s+['"]preact['"])/; RegexPatterns.SOLID_IMPORT = /(?:import.*?from\s+['"]solid-js['"]|from\s+['"]solid-js['"])/; RegexPatterns.VUE_IMPORT = /(?:import.*?from\s+['"]vue['"]|from\s+['"]vue['"])/; RegexPatterns.ANGULAR_IMPORT = /(?:import.*?from\s+['"]@angular|from\s+['"]@angular)/; // JSX syntax detection RegexPatterns.JSX_SYNTAX = /<[A-Z][A-Za-z0-9]*|<[a-z][a-zA-Z0-9-]*(?:\s+[a-zA-Z][a-zA-Z0-9-]*(?:=(?:"[^"]*"|'[^']*'|{[^}]*}))?)*\s*\/?>/; JsExtractor = class { constructor(options = {}) { this.options = JsExtractionOptionsSchema.parse(options); } /** * Extract classes from JavaScript/JSX string */ async extractFromString(code, source = "string") { const startTime = Date.now(); const metadata = { source, processedAt: /* @__PURE__ */ new Date(), processingTime: 0, errors: [], extractionStats: { staticMatches: 0, dynamicMatches: 0, templateMatches: 0, utilityMatches: 0 } }; try { const framework = this.options.enableFrameworkDetection ? this.detectFramework(code) : "unknown"; const classes = /* @__PURE__ */ new Map(); let totalMatches = 0; let totalClasses = 0; const staticMatches = this.extractStaticClasses(code); this.processMatches(staticMatches, "static", classes, framework); const jsStringMatches = this.extractJsStringLiterals(code); this.processMatches(jsStringMatches, "static", classes, framework); const objectPropertyMatches = this.extractObjectPropertyStrings(code); this.processMatches(objectPropertyMatches, "static", classes, framework); const totalStaticMatches = staticMatches.length + jsStringMatches.length + objectPropertyMatches.length; totalMatches += totalStaticMatches; metadata.extractionStats.staticMatches = totalStaticMatches; const templateMatches = this.extractTemplateClasses(code); this.processMatches(templateMatches, "template", classes, framework); totalMatches += templateMatches.length; metadata.extractionStats.templateMatches = templateMatches.length; const utilityMatches = this.extractUtilityClasses(code); this.processMatches(utilityMatches, "utility", classes, framework); totalMatches += utilityMatches.length; metadata.extractionStats.utilityMatches = utilityMatches.length; if (this.options.includeDynamicClasses) { const dynamicMatches = this.extractDynamicClasses(code); this.processMatches(dynamicMatches, "dynamic", classes, framework); totalMatches += dynamicMatches.length; metadata.extractionStats.dynamicMatches = dynamicMatches.length; } classes.forEach((classData) => { totalClasses += classData.frequency; }); metadata.processingTime = Date.now() - startTime; return { classes, totalMatches, totalClasses, uniqueClasses: classes.size, framework, metadata }; } catch (error) { metadata.errors.push(error instanceof Error ? error.message : String(error)); metadata.processingTime = Date.now() - startTime; throw new JsParsingError( `Failed to parse JavaScript/JSX: ${error instanceof Error ? error.message : String(error)}`, source, error instanceof Error ? error : void 0 ); } } /** * Extract classes from JavaScript/JSX file */ async extractFromFile(filePath) { try { const stats = await fs5.stat(filePath); if (stats.size > this.options.maxFileSize) { throw new JsFileReadError( `File size (${stats.size} bytes) exceeds maximum allowed size (${this.options.maxFileSize} bytes)`, filePath ); } const code = await this.readFileWithTimeout(filePath, this.options.timeout); const result = await this.extractFromString(code, filePath); result.metadata.fileSize = stats.size; return result; } catch (error) {