254 lines
8.2 KiB
JavaScript
254 lines
8.2 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const glob = require('glob');
|
||
|
||
/**
|
||
* 分析未使用的图片资源
|
||
* 扫描 src/blogs/ 目录下的 .assets 文件夹中的图片文件
|
||
* 检查这些图片是否在对应的 markdown 文件中被引用
|
||
*/
|
||
|
||
// 支持的图片格式
|
||
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg', '.webp'];
|
||
|
||
// 博客目录
|
||
const blogsDir = './src/blogs';
|
||
|
||
// 获取所有assets目录中的图片文件
|
||
function getAllAssetImages() {
|
||
const assetImages = [];
|
||
|
||
try {
|
||
// 使用 glob 匹配所有 .assets 目录下的文件
|
||
const assetFiles = glob.sync(`${blogsDir}/**/*.assets/**/*`, { nodir: true });
|
||
|
||
assetFiles.forEach(filePath => {
|
||
const ext = path.extname(filePath).toLowerCase();
|
||
if (imageExtensions.includes(ext)) {
|
||
assetImages.push({
|
||
fullPath: filePath,
|
||
fileName: path.basename(filePath),
|
||
blogName: path.dirname(filePath).split('/').pop().replace('.assets', ''),
|
||
relativePath: path.relative(blogsDir, filePath)
|
||
});
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('读取assets目录时出错:', error.message);
|
||
}
|
||
|
||
return assetImages;
|
||
}
|
||
|
||
// 获取所有markdown文件
|
||
function getAllMarkdownFiles() {
|
||
const markdownFiles = [];
|
||
|
||
try {
|
||
const mdFiles = glob.sync(`${blogsDir}/*.md`);
|
||
|
||
mdFiles.forEach(filePath => {
|
||
markdownFiles.push({
|
||
fullPath: filePath,
|
||
fileName: path.basename(filePath),
|
||
blogName: path.basename(filePath, '.md')
|
||
});
|
||
});
|
||
} catch (error) {
|
||
console.error('读取markdown文件时出错:', error.message);
|
||
}
|
||
|
||
return markdownFiles;
|
||
}
|
||
|
||
// 读取markdown文件内容并提取图片引用
|
||
function extractImageReferences(mdFilePath) {
|
||
try {
|
||
const content = fs.readFileSync(mdFilePath, 'utf8');
|
||
const imageRefs = new Set();
|
||
|
||
// 匹配  格式的图片引用
|
||
const markdownImageRegex = /!\[[^\]]*\]\(([^)]+)\)/g;
|
||
let match;
|
||
while ((match = markdownImageRegex.exec(content)) !== null) {
|
||
imageRefs.add(match[1]);
|
||
}
|
||
|
||
// 匹配 HTML img 标签
|
||
const htmlImageRegex = /<img[^>]+src=['"]*([^'">\s]+)['"]*[^>]*>/g;
|
||
while ((match = htmlImageRegex.exec(content)) !== null) {
|
||
imageRefs.add(match[1]);
|
||
}
|
||
|
||
return Array.from(imageRefs);
|
||
} catch (error) {
|
||
console.error(`读取文件 ${mdFilePath} 时出错:`, error.message);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
// 检查图片是否被使用
|
||
function isImageUsed(imagePath, imageFileName, blogName, allMarkdownFiles) {
|
||
// 首先检查对应的markdown文件
|
||
const correspondingMdFile = allMarkdownFiles.find(md => md.blogName === blogName);
|
||
|
||
if (correspondingMdFile) {
|
||
const imageRefs = extractImageReferences(correspondingMdFile.fullPath);
|
||
|
||
// 检查是否有任何引用匹配当前图片
|
||
const isUsed = imageRefs.some(ref => {
|
||
// 处理相对路径和绝对路径
|
||
const normalizedRef = ref.replace(/^\.\//, '');
|
||
const fileName = path.basename(ref);
|
||
|
||
return (
|
||
fileName === imageFileName ||
|
||
normalizedRef.includes(imageFileName) ||
|
||
ref.includes(imageFileName) ||
|
||
normalizedRef === imagePath ||
|
||
ref === imagePath
|
||
);
|
||
});
|
||
|
||
if (isUsed) {
|
||
return { used: true, foundIn: correspondingMdFile.fileName };
|
||
}
|
||
}
|
||
|
||
// 如果在对应的markdown文件中没找到,检查所有其他markdown文件
|
||
for (const mdFile of allMarkdownFiles) {
|
||
if (mdFile.blogName === blogName) continue; // 已经检查过了
|
||
|
||
const imageRefs = extractImageReferences(mdFile.fullPath);
|
||
const isUsed = imageRefs.some(ref => {
|
||
const normalizedRef = ref.replace(/^\.\//, '');
|
||
const fileName = path.basename(ref);
|
||
|
||
return (
|
||
fileName === imageFileName ||
|
||
normalizedRef.includes(imageFileName) ||
|
||
ref.includes(imageFileName)
|
||
);
|
||
});
|
||
|
||
if (isUsed) {
|
||
return { used: true, foundIn: mdFile.fileName };
|
||
}
|
||
}
|
||
|
||
return { used: false, foundIn: null };
|
||
}
|
||
|
||
// 主函数
|
||
function analyzeUnusedImages() {
|
||
console.log('🔍 开始分析未使用的图片资源...\n');
|
||
|
||
const assetImages = getAllAssetImages();
|
||
const markdownFiles = getAllMarkdownFiles();
|
||
|
||
console.log(`📊 统计信息:`);
|
||
console.log(` - 找到 ${assetImages.length} 个图片文件`);
|
||
console.log(` - 找到 ${markdownFiles.length} 个markdown文件\n`);
|
||
|
||
const unusedImages = [];
|
||
const usedImages = [];
|
||
|
||
console.log('📝 分析结果:\n');
|
||
|
||
assetImages.forEach(image => {
|
||
const usage = isImageUsed(image.relativePath, image.fileName, image.blogName, markdownFiles);
|
||
|
||
if (usage.used) {
|
||
usedImages.push({
|
||
...image,
|
||
foundIn: usage.foundIn
|
||
});
|
||
console.log(`✅ ${image.fileName} (在 ${usage.foundIn} 中使用)`);
|
||
} else {
|
||
unusedImages.push(image);
|
||
console.log(`❌ ${image.fileName} (未使用)`);
|
||
}
|
||
});
|
||
|
||
// 输出汇总
|
||
console.log('\n📋 汇总报告:');
|
||
console.log(` ✅ 已使用的图片: ${usedImages.length} 个`);
|
||
console.log(` ❌ 未使用的图片: ${unusedImages.length} 个`);
|
||
|
||
if (unusedImages.length > 0) {
|
||
console.log('\n🗑️ 未使用的图片列表:');
|
||
unusedImages.forEach(image => {
|
||
console.log(` - ${image.relativePath}`);
|
||
});
|
||
|
||
// 计算未使用图片的总大小
|
||
let totalSize = 0;
|
||
unusedImages.forEach(image => {
|
||
try {
|
||
const stats = fs.statSync(image.fullPath);
|
||
totalSize += stats.size;
|
||
} catch (error) {
|
||
console.error(`无法获取文件大小: ${image.fullPath}`);
|
||
}
|
||
});
|
||
|
||
console.log(`\n💾 未使用图片总大小: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
|
||
|
||
// 生成删除脚本(包含git移除操作)
|
||
const deleteScript = unusedImages.map(image => {
|
||
return `# 删除文件: ${image.relativePath}
|
||
if [ -f "${image.fullPath}" ]; then
|
||
# 检查文件是否被git跟踪
|
||
if git ls-files --error-unmatch "${image.fullPath}" >/dev/null 2>&1; then
|
||
echo "从git中移除: ${image.relativePath}"
|
||
git rm "${image.fullPath}"
|
||
else
|
||
echo "删除未跟踪文件: ${image.relativePath}"
|
||
rm "${image.fullPath}"
|
||
fi
|
||
else
|
||
echo "文件不存在,跳过: ${image.relativePath}"
|
||
fi`;
|
||
}).join('\n\n');
|
||
|
||
const scriptContent = `#!/bin/bash
|
||
# 删除未使用的图片文件并从git中移除(如果被跟踪)
|
||
# 生成时间: ${new Date().toLocaleString()}
|
||
|
||
set -e # 遇到错误时退出
|
||
|
||
echo "🗑️ 开始删除未使用的图片文件..."
|
||
echo "总共需要处理 ${unusedImages.length} 个文件"
|
||
echo ""
|
||
|
||
${deleteScript}
|
||
|
||
echo ""
|
||
echo "✅ 删除操作完成!"
|
||
echo "如果有文件从git中移除,请记得提交这些更改:"
|
||
echo "git commit -m 'Remove unused images'"
|
||
`;
|
||
|
||
fs.writeFileSync('./delete-unused-images.sh', scriptContent);
|
||
console.log('\n📜 已生成删除脚本: delete-unused-images.sh');
|
||
console.log(' 脚本将自动检查文件是否被git跟踪,并相应地删除或从git中移除');
|
||
console.log(' 运行 chmod +x delete-unused-images.sh && ./delete-unused-images.sh 来删除这些文件');
|
||
} else {
|
||
console.log('\n🎉 太棒了!所有图片都在使用中!');
|
||
}
|
||
}
|
||
|
||
// 检查是否安装了 glob 包
|
||
try {
|
||
require('glob');
|
||
} catch (error) {
|
||
console.error('❌ 缺少依赖包 "glob"');
|
||
console.log('请运行: npm install glob');
|
||
process.exit(1);
|
||
}
|
||
|
||
// 运行分析
|
||
analyzeUnusedImages();
|