image-asset-manager
Version:
A comprehensive image asset management tool for frontend projects
427 lines ⢠19.9 kB
JavaScript
;
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