UNPKG

image-asset-manager

Version:

A comprehensive image asset management tool for frontend projects

427 lines • 19.9 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ImageAssetManager = void 0; const config_1 = require("../config"); const FileScanner_1 = require("./FileScanner"); class ImageAssetManager { constructor(configManager) { this.configManager = configManager || new config_1.ConfigManager(); this.fileScanner = new FileScanner_1.FileScannerImpl(); console.log("Image Asset Manager initialized"); } formatBytes(bytes) { if (bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } async start(options) { console.log(`Starting Image Asset Manager for project: ${options.projectPath}`); console.log(`Port: ${options.port}`); console.log(`Watch mode: ${options.watch}`); console.log(`Optimize: ${options.optimize}`); // This will be the main entry point that combines scan and serve await this.scan(options); if (options.watch) { await this.serve(options); } } async scan(options, scanOptions) { const config = this.configManager.getConfig(); if (!scanOptions?.silent) { console.log(`šŸ“ Scanning project at: ${options.projectPath}`); console.log(`šŸ” Include patterns: ${(options.include || []).join(", ")}`); console.log(`🚫 Exclude patterns: ${(options.exclude || []).join(", ")}`); if (scanOptions?.verbose) { console.log(`āš™ļø Max depth: ${config.scan.maxDepth}`); console.log(`šŸ”— Follow symlinks: ${config.scan.followSymlinks}`); console.log(`šŸ“Š Output format: ${scanOptions.outputFormat || "table"}`); } } // å®žēŽ°åŸŗęœ¬ēš„ę–‡ä»¶ę‰«ęåŠŸčƒ½ try { const { glob } = await Promise.resolve().then(() => __importStar(require("glob"))); const fs = await Promise.resolve().then(() => __importStar(require("fs"))); const path = await Promise.resolve().then(() => __importStar(require("path"))); // ä½æē”Øé…ē½®äø­ēš„åŒ…å«å’ŒęŽ’é™¤ęØ”å¼ const includePatterns = options.include && options.include.length > 0 ? options.include : config.scan.include; const excludePatterns = options.exclude && options.exclude.length > 0 ? options.exclude : config.scan.exclude; if (!scanOptions?.silent) { console.log(`\nšŸ” Scanning with patterns:`); console.log(` Include: ${includePatterns.join(", ")}`); console.log(` Exclude: ${excludePatterns.join(", ")}`); } // ę‰«ęę–‡ä»¶ const allFiles = []; for (const pattern of includePatterns) { const files = await glob(pattern, { cwd: options.projectPath, ignore: excludePatterns, absolute: true, }); allFiles.push(...files); } // 去重 const uniqueFiles = [...new Set(allFiles)]; // čŽ·å–ę–‡ä»¶äæ”ęÆ const imageFiles = []; let totalSize = 0; for (const filePath of uniqueFiles) { try { const stats = await fs.promises.stat(filePath); const relativePath = path.relative(options.projectPath, filePath); const fileName = path.basename(filePath); const extension = path.extname(filePath).toLowerCase().slice(1); imageFiles.push({ path: filePath, relativePath, name: fileName, extension, size: stats.size, modifiedAt: stats.mtime, }); totalSize += stats.size; } catch (error) { if (scanOptions?.verbose) { console.warn(`āš ļø Could not read file: ${filePath}`); } } } // ęŒ‰ę‰©å±•ååˆ†ē»„ē»Ÿč®” const extensionStats = {}; imageFiles.forEach((file) => { if (!extensionStats[file.extension]) { extensionStats[file.extension] = { count: 0, size: 0 }; } extensionStats[file.extension].count++; extensionStats[file.extension].size += file.size; }); // č¾“å‡ŗē»“ęžœ if (scanOptions?.outputFormat === "json") { console.log(JSON.stringify({ projectPath: options.projectPath, totalFiles: imageFiles.length, totalSize, files: imageFiles, extensionStats, }, null, 2)); } else if (scanOptions?.outputFormat === "csv") { console.log("Path,RelativePath,Name,Extension,Size,ModifiedAt"); imageFiles.forEach((file) => { console.log(`"${file.path}","${file.relativePath}","${file.name}","${file.extension}",${file.size},"${file.modifiedAt.toISOString()}"`); }); } else { // č”Øę ¼ę ¼å¼č¾“å‡ŗ if (!scanOptions?.silent) { console.log(`\nšŸ“‹ Found ${imageFiles.length} image files:`); if (scanOptions?.verbose && imageFiles.length > 0) { console.log("\nšŸ“ Files by directory:"); const dirGroups = {}; imageFiles.forEach((file) => { const dir = path.dirname(file.relativePath) || "."; if (!dirGroups[dir]) dirGroups[dir] = []; dirGroups[dir].push(file); }); Object.entries(dirGroups).forEach(([dir, files]) => { console.log(`\n šŸ“‚ ${dir}/ (${files.length} files)`); files.slice(0, 10).forEach((file) => { const sizeStr = this.formatBytes(file.size); console.log(` • ${file.name} (${sizeStr})`); }); if (files.length > 10) { console.log(` ... and ${files.length - 10} more files`); } }); } } } if (scanOptions?.showStats && !scanOptions?.silent) { console.log("\nšŸ“Š Project Statistics:"); console.log(`• Total images: ${imageFiles.length}`); console.log(`• Total size: ${this.formatBytes(totalSize)}`); console.log(`• Average size: ${imageFiles.length > 0 ? this.formatBytes(Math.round(totalSize / imageFiles.length)) : "0 B"}`); console.log("\nšŸ“ˆ By file type:"); Object.entries(extensionStats) .sort(([, a], [, b]) => b.count - a.count) .forEach(([ext, stats]) => { console.log(` • ${ext.toUpperCase()}: ${stats.count} files (${this.formatBytes(stats.size)})`); }); } } catch (error) { console.error("āŒ Error during scanning:", error); if (scanOptions?.verbose) { console.error(error); } } } async serve(options, serveOptions) { const config = this.configManager.getConfig(); if (!serveOptions?.silent) { console.log(`🌐 Starting web server on port: ${options.port}`); console.log(`šŸ  Host: ${serveOptions?.host || config.server.host}`); console.log(`šŸ”’ CORS: ${serveOptions?.cors !== false ? "āœ…" : "āŒ"}`); if (serveOptions?.verbose) { console.log(`šŸ“ Static path: ${config.server.staticPath || "built-in"}`); console.log(`šŸ” Authentication: ${serveOptions?.auth ? "āœ…" : "āŒ"}`); console.log(`šŸ”’ SSL: ${serveOptions?.ssl ? "āœ…" : "āŒ"}`); } } // é¦–å…ˆę‰«ęå›¾ē‰‡ if (!serveOptions?.silent) { console.log("šŸ” Scanning images..."); } const images = await this.scanImagesForServer(options); console.log("images", images); // 启动 Web ęœåŠ”å™Ø const { WebServer } = await Promise.resolve().then(() => __importStar(require("./WebServer"))); const webServer = new WebServer(); // č®”ē®—čÆ¦ē»†ē»Ÿč®”ę•°ę® const categoryBreakdown = {}; const sizeBreakdown = {}; const formatBreakdown = {}; images.forEach((img) => { // åˆ†ē±»ē»Ÿč®” const category = img.category || "other"; categoryBreakdown[category] = (categoryBreakdown[category] || 0) + 1; // ę ¼å¼ē»Ÿč®” const format = img.extension.toLowerCase(); formatBreakdown[format] = (formatBreakdown[format] || 0) + 1; // å¤§å°åˆ†ē±»ē»Ÿč®” const sizeInKB = img.size / 1024; let sizeCategory; if (sizeInKB < 10) { sizeCategory = "small"; // < 10KB } else if (sizeInKB < 100) { sizeCategory = "medium"; // 10KB - 100KB } else if (sizeInKB < 1024) { sizeCategory = "large"; // 100KB - 1MB } else { sizeCategory = "xlarge"; // > 1MB } sizeBreakdown[sizeCategory] = (sizeBreakdown[sizeCategory] || 0) + 1; }); // ē®€å•ēš„ä½æē”Øęƒ…å†µåˆ†ęžļ¼ˆęØ”ę‹Ÿļ¼‰ const unusedFiles = []; const usedFiles = new Map(); // ē®€å•ēš„åÆå‘å¼ļ¼šå‡č®¾ęŸäŗ›ē±»åž‹ēš„ę–‡ä»¶ę›“åÆčƒ½ęœŖä½æē”Ø images.forEach((img) => { // ē®€å•ēš„åÆå‘å¼č§„åˆ™ const isLikelyUnused = img.category === "other" || img.name.includes("temp") || img.name.includes("backup") || img.name.includes("old") || img.name.includes("unused") || img.size < 1024; // å¾ˆå°ēš„ę–‡ä»¶åÆčƒ½ę˜Æęµ‹čÆ•ę–‡ä»¶ if (isLikelyUnused) { unusedFiles.push({ id: img.id, path: img.path, name: img.name }); } else { // ęØ”ę‹Ÿä½æē”Øę¬”ę•° const usageCount = Math.floor(Math.random() * 5) + 1; usedFiles.set(img.id, { references: Array(usageCount) .fill(0) .map((_, i) => ({ file: `src/component${i}.tsx`, line: Math.floor(Math.random() * 100) + 1, type: "import", })), }); } }); // å‡†å¤‡ęœåŠ”å™Øę•°ę® const serverData = { images, usage: { usedFiles, unusedFiles, totalReferences: usedFiles.size }, duplicates: [], stats: { totalImages: images.length, totalSize: images.reduce((sum, img) => sum + img.size, 0), duplicateCount: 0, unusedCount: unusedFiles.length, categoryBreakdown, sizeBreakdown, formatBreakdown, }, config: { projectPath: options.projectPath, excludePatterns: options.exclude || config.scan.exclude, includePatterns: options.include || config.scan.include, optimizeOptions: config.optimize, frameworks: config.generate.frameworks, }, }; await webServer.start(options.port, serverData); if (!serveOptions?.silent) { console.log(`āœ… Server is running at http://${serveOptions?.host || "localhost"}:${options.port}`); console.log("Press Ctrl+C to stop the server"); } // äæęŒęœåŠ”å™Øčæč”Œ return new Promise((resolve) => { process.on("SIGINT", () => { console.log("\nšŸ›‘ Shutting down server..."); webServer.stop().then(() => { resolve(); }); }); }); } async optimize(options, optimizeOptions) { const config = this.configManager.getConfig(); if (!optimizeOptions?.silent) { console.log(`⚔ Optimizing images in: ${options.projectPath}`); console.log(`šŸ“ Output: ${optimizeOptions?.outputDir || "in-place"}`); console.log(`šŸŽÆ Quality: ${optimizeOptions?.quality || config.optimize.png.quality}`); if (optimizeOptions?.verbose) { console.log(`šŸ–¼ļø PNG Quality: ${optimizeOptions?.pngQuality || config.optimize.png.quality}`); console.log(`šŸ“ø JPG Quality: ${optimizeOptions?.jpgQuality || config.optimize.jpg.quality}`); console.log(`🌐 WebP Quality: ${optimizeOptions?.webpQuality || config.optimize.webp.quality}`); console.log(`šŸ“ˆ Progressive: ${optimizeOptions?.progressive !== false ? "āœ…" : "āŒ"}`); console.log(`šŸ’¾ Keep Original: ${optimizeOptions?.keepOriginal !== false ? "āœ…" : "āŒ"}`); console.log(`šŸ“¦ Batch Size: ${optimizeOptions?.batchSize || 10}`); console.log(`⚔ Parallel: ${optimizeOptions?.parallel || 4}`); console.log(`šŸ” Dry Run: ${optimizeOptions?.dryRun ? "āœ…" : "āŒ"}`); } } // TODO: Implement optimization logic console.log("Optimization functionality will be implemented in task 4"); if (optimizeOptions?.dryRun && !optimizeOptions?.silent) { console.log("\nšŸ” Dry run completed - no files were modified"); console.log("• Estimated savings: 0 B (0%)"); console.log("• Files to optimize: 0"); } } async generate(options, generateOptions) { const config = this.configManager.getConfig(); if (!generateOptions?.silent) { console.log(`šŸ”§ Generating code for project: ${options.projectPath}`); console.log(`šŸŽÆ Framework: ${generateOptions?.framework || config.generate.frameworks[0]}`); console.log(`šŸ“ Output: ${generateOptions?.outputDir || config.generate.outputDir}`); console.log(`šŸ“ TypeScript: ${generateOptions?.typescript !== false ? "āœ…" : "āŒ"}`); if (generateOptions?.verbose) { console.log(`šŸ·ļø Component Prefix: ${generateOptions?.componentPrefix || config.generate.componentPrefix}`); console.log(`šŸ“¦ Barrel File: ${generateOptions?.barrelFile ? "āœ…" : "āŒ"}`); console.log(`šŸ‘€ Watch Mode: ${generateOptions?.watch ? "āœ…" : "āŒ"}`); } } // TODO: Implement code generation logic console.log("Code generation functionality will be implemented in task 5"); if (!generateOptions?.silent) { console.log("\nāœ… Code generation completed:"); console.log("• Type definitions: 0 files"); console.log("• Components: 0 files"); console.log("• Import files: 0 files"); } if (generateOptions?.watch && !generateOptions?.silent) { console.log("\nšŸ‘€ Watching for changes... Press Ctrl+C to stop"); } } async scanImagesForServer(options) { try { const config = this.configManager.getConfig(); const scanOptions = { extensions: [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".ico"], recursive: true, excludePatterns: options.exclude && options.exclude.length > 0 ? options.exclude : config.scan.exclude, maxDepth: 0, // No depth limit }; console.log(`šŸ” Scanning images in: ${options.projectPath}`); // Use FileScanner to properly extract metadata const imageFiles = await this.fileScanner.scanDirectory(options.projectPath, scanOptions); // Transform to server format const images = imageFiles.map((file, index) => ({ id: `img_${index}`, path: file.path, relativePath: file.relativePath, name: file.name + file.extension, extension: file.extension.slice(1), // Remove the dot size: file.size, hash: file.hash, dimensions: file.dimensions, // Now has actual dimensions! createdAt: file.createdAt, modifiedAt: file.modifiedAt, category: file.category, url: `/project-assets/${file.relativePath.replace(/\\/g, "/")}`, })); console.log(`āœ… Found ${images.length} images`); return images; } catch (error) { console.error("āŒ Error scanning images:", error); return []; } } getCategoryFromPath(filePath) { const lowerPath = filePath.toLowerCase(); if (lowerPath.includes("icon")) return "icons"; if (lowerPath.includes("logo")) return "logos"; if (lowerPath.includes("background")) return "backgrounds"; if (lowerPath.includes("avatar")) return "avatars"; if (lowerPath.includes("flag")) return "flags"; if (lowerPath.includes("brand")) return "brands"; if (lowerPath.includes("auth")) return "auth"; if (lowerPath.includes("illustration")) return "illustrations"; return "other"; } } exports.ImageAssetManager = ImageAssetManager; //# sourceMappingURL=ImageAssetManager.js.map