UNPKG

crapifyme

Version:

Ultra-fast developer productivity CLI tools - remove comments, logs, and more

527 lines 21.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SvgProcessor = void 0; const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const svgo_1 = require("svgo"); const fast_glob_1 = __importDefault(require("fast-glob")); const chalk_1 = __importDefault(require("chalk")); const cliProgress = __importStar(require("cli-progress")); const ora_1 = __importDefault(require("ora")); const is_svg_1 = __importDefault(require("is-svg")); const types_1 = require("./types"); const shared_1 = require("../../shared"); class SvgProcessor { constructor(logger) { this.logger = logger; } async processSvgFiles(target, options) { const startTime = Date.now(); const stats = { filesProcessed: 0, filesSkipped: 0, bytesProcessed: 0, bytesOriginal: 0, bytesOptimized: 0, bytesSaved: 0, avgCompressionRatio: 0, operationsCompleted: 0, processingTime: 0, errors: [], warnings: [] }; const results = []; const skippedFiles = []; const failedFiles = []; try { await this.performSafetyChecks(options); const files = await this.findSvgFiles(target, options); if (files.length === 0) { this.logger.warn('No SVG files found to process'); return { results, stats, skippedFiles, failedFiles }; } const config = (0, types_1.createProcessingConfig)(options); if (!options.quiet && files.length > 1) { this.setupProgressTracking(files.length, options); } if (options.parallel && files.length > 1) { await this.processFilesParallel(files, config, options, results, skippedFiles, failedFiles, stats); } else { await this.processFilesSequential(files, config, options, results, skippedFiles, failedFiles, stats); } this.cleanupProgressTracking(); this.calculateFinalStats(stats, startTime); if (options.report) { await this.generateReport(results, stats, options); } return { results, stats, skippedFiles, failedFiles }; } catch (error) { this.cleanupProgressTracking(); throw error; } } async processSvgCode(svgContent, options) { const startTime = Date.now(); try { const originalSize = Buffer.byteLength(svgContent, 'utf8'); const config = (0, types_1.createProcessingConfig)(options); if (config.validateInput) { const validation = this.validateSvg(svgContent, 'direct-input'); if (!validation.isValid) { throw new Error(`Invalid SVG: ${validation.errors.join(', ')}`); } } const svgoConfig = this.createSvgoConfig(config, options); const result = (0, svgo_1.optimize)(svgContent, svgoConfig); if ('error' in result) { throw new Error(`SVGO optimization failed: ${result.error}`); } const optimizedContent = result.data; const optimizedSize = Buffer.byteLength(optimizedContent, 'utf8'); if (config.validateOutput) { const validation = this.validateSvg(optimizedContent, 'direct-output'); if (!validation.isValid) { throw new Error(`Output validation failed: ${validation.errors.join(', ')}`); } } const bytesSaved = originalSize - optimizedSize; const compressionRatio = originalSize / optimizedSize; const processingTime = Date.now() - startTime; return { inputPath: 'direct-input', outputPath: 'stdout', originalSize, optimizedSize, compressionRatio, bytesSaved, pluginsApplied: this.getAppliedPlugins(svgoConfig), processingTime, originalContent: svgContent, optimizedContent }; } catch (error) { throw new Error(`Failed to process SVG code: ${error instanceof Error ? error.message : String(error)}`); } } async performSafetyChecks(options) { if (!options.force && !options.dryRun) { const vcs = (0, shared_1.detectVersionControl)(); if (!vcs.detected) { throw new Error('No version control system detected. Use --force to proceed without VCS, or initialize git/svn/hg/bzr first.'); } } if (options.outputDir) { const outputPath = (0, shared_1.resolvePath)(options.outputDir); try { await promises_1.default.access(outputPath); } catch { if (!options.dryRun) { await promises_1.default.mkdir(outputPath, { recursive: true }); } } } if (options.config) { const configPath = (0, shared_1.resolvePath)(options.config); try { await promises_1.default.access(configPath); } catch { throw new Error(`Configuration file not found: ${configPath}`); } } } async findSvgFiles(target, options) { const targetPath = (0, shared_1.resolvePath)(target); let patterns = []; try { const stat = await promises_1.default.stat(targetPath); if (stat.isFile()) { if (!(0, types_1.isSupportedSvgExtension)(path_1.default.extname(targetPath).slice(1))) { throw new Error(`Unsupported file type: ${path_1.default.extname(targetPath)}`); } return [targetPath]; } else if (stat.isDirectory()) { const extensions = options.extensions ? typeof options.extensions === 'string' ? options.extensions.split(',') : options.extensions : [...types_1.DEFAULT_SVG_EXTENSIONS]; if (options.glob) { patterns.push(path_1.default.join(targetPath, options.glob)); } else { if (extensions.length === 1) { patterns.push(path_1.default.join(targetPath, `**/*.${extensions[0]}`)); } else { patterns.push(path_1.default.join(targetPath, `**/*.{${extensions.join(',')}}`)); } } } } catch { patterns.push(targetPath); } const files = await (0, fast_glob_1.default)(patterns, { ignore: [ '**/node_modules/**', '**/.git/**', '**/*.min.svg', '**/*.optimized.svg', '**/*.original.svg', ...(options.exclude || []) ], absolute: true, onlyFiles: true }); return files.filter((file) => (0, types_1.isSupportedSvgExtension)(path_1.default.extname(file).slice(1))); } setupProgressTracking(totalFiles, options) { if (options.verbose) { this.progressBar = new cliProgress.SingleBar({ format: chalk_1.default.cyan('Optimizing SVGs') + ' [{bar}] {percentage}% | {value}/{total} files | ETA: {eta}s', barCompleteChar: '█', barIncompleteChar: '░', hideCursor: true }, cliProgress.Presets.shades_classic); this.progressBar.start(totalFiles, 0); } else { this.spinner = (0, ora_1.default)({ text: `Optimizing ${totalFiles} SVG files...`, color: 'cyan' }).start(); } } updateProgress(completed, total) { if (this.progressBar) { this.progressBar.update(completed); } else if (this.spinner) { this.spinner.text = `Optimizing SVG files... ${completed}/${total}`; } } cleanupProgressTracking() { if (this.progressBar) { this.progressBar.stop(); this.progressBar = undefined; } if (this.spinner) { this.spinner.stop(); this.spinner = undefined; } } async processFilesSequential(files, config, options, results, skippedFiles, failedFiles, stats) { for (let i = 0; i < files.length; i++) { const file = files[i]; try { const result = await this.processSingleFile(file, config, options); if (result) { results.push(result); this.updateStats(stats, result); } else { skippedFiles.push(file); stats.filesSkipped++; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); failedFiles.push({ file, error: errorMessage }); stats.errors.push({ file, error: errorMessage }); } this.updateProgress(i + 1, files.length); } } async processFilesParallel(files, config, options, results, skippedFiles, failedFiles, stats) { const concurrency = Math.min(options.maxConcurrency || 4, files.length); const chunks = this.chunkArray(files, Math.ceil(files.length / concurrency)); let completed = 0; const processChunk = async (chunk) => { for (const file of chunk) { try { const result = await this.processSingleFile(file, config, options); if (result) { results.push(result); this.updateStats(stats, result); } else { skippedFiles.push(file); stats.filesSkipped++; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); failedFiles.push({ file, error: errorMessage }); stats.errors.push({ file, error: errorMessage }); } completed++; this.updateProgress(completed, files.length); } }; await Promise.all(chunks.map(processChunk)); } async processSingleFile(filePath, config, options) { const startTime = Date.now(); try { const originalContent = await promises_1.default.readFile(filePath, 'utf8'); const originalSize = Buffer.byteLength(originalContent, 'utf8'); if (config.validateInput) { const validation = this.validateSvg(originalContent, filePath); if (!validation.isValid) { throw new Error(`Invalid SVG: ${validation.errors.join(', ')}`); } } if (this.isAlreadyOptimized(originalContent, options)) { return null; } const svgoConfig = this.createSvgoConfig(config, options); const result = (0, svgo_1.optimize)(originalContent, svgoConfig); if ('error' in result) { throw new Error(`SVGO optimization failed: ${result.error}`); } const optimizedContent = result.data; const optimizedSize = Buffer.byteLength(optimizedContent, 'utf8'); if (config.validateOutput) { const validation = this.validateSvg(optimizedContent, filePath); if (!validation.isValid) { throw new Error(`Output validation failed: ${validation.errors.join(', ')}`); } } const bytesSaved = originalSize - optimizedSize; const compressionRatio = originalSize / optimizedSize; const outputPath = this.getOutputPath(filePath, options); if (!options.dryRun) { await this.handleOutput(filePath, outputPath, originalContent, optimizedContent, options); } const processingTime = Date.now() - startTime; return { inputPath: filePath, outputPath, originalSize, optimizedSize, compressionRatio, bytesSaved, pluginsApplied: this.getAppliedPlugins(svgoConfig), processingTime, originalContent, optimizedContent }; } catch (error) { throw new Error(`Failed to process ${path_1.default.basename(filePath)}: ${error instanceof Error ? error.message : String(error)}`); } } validateSvg(content, filePath) { const errors = []; const warnings = []; if (!(0, is_svg_1.default)(content)) { errors.push('Not a valid SVG file'); } if (!content.includes('<svg')) { errors.push('Missing SVG root element'); } const fileSize = Buffer.byteLength(content, 'utf8'); if (fileSize === 0) { errors.push('Empty file'); } else if (fileSize > 10 * 1024 * 1024) { warnings.push('Large file size (>10MB)'); } const hasViewBox = content.includes('viewBox'); if (!hasViewBox) { warnings.push('Missing viewBox attribute'); } const hasTitle = content.includes('<title'); const hasDesc = content.includes('<desc'); const elementCount = (content.match(/<[^\/][^>]*>/g) || []).length; return { isValid: errors.length === 0, errors, warnings, fileSize, hasViewBox, hasTitle, hasDesc, elementCount }; } isAlreadyOptimized(content, options) { if (options.force) { return false; } const indicators = [ '<!-- Generated by SVGO -->', '<!-- Optimized by SVGO -->', 'data-svgo', '.min.svg', '.optimized.svg' ]; return indicators.some(indicator => content.includes(indicator)); } createSvgoConfig(config, options) { const plugins = []; const presetDefaultConfig = { name: 'preset-default', params: { overrides: {} } }; if (config.keepIds) { presetDefaultConfig.params.overrides.cleanupIds = false; } if (config.keepTitles) { presetDefaultConfig.params.overrides.removeTitle = false; presetDefaultConfig.params.overrides.removeDesc = false; } if (options.removeViewbox) { presetDefaultConfig.params.overrides.removeViewBox = true; } if (options.sortAttrs) { presetDefaultConfig.params.overrides.sortAttrs = true; } if (options.removeXmlns) { presetDefaultConfig.params.overrides.removeXMLNS = true; } if (options.convertColors) { presetDefaultConfig.params.overrides.convertColors = true; } plugins.push(presetDefaultConfig); if (config.customPlugins.length > 0) { plugins.push(...config.customPlugins); } const svgoConfig = { plugins, multipass: config.multipass, floatPrecision: config.precision }; return svgoConfig; } getOutputPath(inputPath, options) { const dir = path_1.default.dirname(inputPath); const name = path_1.default.basename(inputPath, path_1.default.extname(inputPath)); const ext = path_1.default.extname(inputPath); if (options.outputDir) { return path_1.default.join((0, shared_1.resolvePath)(options.outputDir), path_1.default.basename(inputPath)); } if (options.copy) { return path_1.default.join(dir, `${name}.optimized${ext}`); } return inputPath; } async handleOutput(inputPath, outputPath, originalContent, optimizedContent, options) { if (options.backup && outputPath === inputPath) { const backupPath = inputPath.replace(/\.svg$/, '.original.svg'); await promises_1.default.writeFile(backupPath, originalContent); } if (options.stdout && !options.outputDir) { console.log(optimizedContent); return; } await promises_1.default.writeFile(outputPath, optimizedContent); } getAppliedPlugins(config) { if (!config.plugins) return []; return config.plugins .map(plugin => (typeof plugin === 'string' ? plugin : plugin.name)) .filter(Boolean); } updateStats(stats, result) { stats.filesProcessed++; stats.bytesProcessed += result.originalSize; stats.bytesOriginal += result.originalSize; stats.bytesOptimized += result.optimizedSize; stats.bytesSaved += result.bytesSaved; stats.operationsCompleted++; } calculateFinalStats(stats, startTime) { stats.processingTime = Date.now() - startTime; stats.avgCompressionRatio = stats.filesProcessed > 0 ? stats.bytesOriginal / stats.bytesOptimized : 0; } chunkArray(array, chunkSize) { const chunks = []; for (let i = 0; i < array.length; i += chunkSize) { chunks.push(array.slice(i, i + chunkSize)); } return chunks; } async generateReport(results, stats, options) { if (!options.report) return; const reportData = { summary: stats, files: results.map(result => ({ file: path_1.default.basename(result.inputPath), originalSize: result.originalSize, optimizedSize: result.optimizedSize, bytesSaved: result.bytesSaved, compressionRatio: result.compressionRatio, processingTime: result.processingTime, plugins: result.pluginsApplied })) }; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const reportPath = `svg-optimization-report-${timestamp}.${options.report}`; if (options.report === 'json') { await promises_1.default.writeFile(reportPath, JSON.stringify(reportData, null, 2)); } else if (options.report === 'csv') { const csv = this.convertToCsv(reportData.files); await promises_1.default.writeFile(reportPath, csv); } this.logger.info(`Report saved to: ${reportPath}`); } convertToCsv(data) { if (data.length === 0) return ''; const headers = Object.keys(data[0]); const rows = data.map(row => headers.map(header => row[header]).join(',')); return [headers.join(','), ...rows].join('\n'); } } exports.SvgProcessor = SvgProcessor; //# sourceMappingURL=logic.js.map