feat: 添加清理未使用图片资源的脚本

添加 clean-unused-images.js 脚本用于自动扫描并删除未被引用的图片文件
更新 package.json 添加 clean 命令
更新 README.md 文档说明新功能
This commit is contained in:
二叉树树
2025-09-05 03:56:31 +08:00
parent 52720591c3
commit 25b2021a2d
40 changed files with 469 additions and 46 deletions

205
README.md
View File

@@ -1,60 +1,173 @@
# 🍥Fuwari # Fuwari
A static blog template built with [Astro](https://astro.build). 一个基于 Astro 构建的现代化个人博客主题,专注于技术分享与实践。
[**🖥️ Live Demo (Vercel)**](https://fuwari.vercel.app)   /    ## ✨ 特性
[**📦 Old Hexo Version**](https://github.com/saicaca/hexo-theme-vivia)   /   
- 🚀 基于 Astro 4.0+ 构建,性能卓越
- 📱 完全响应式设计,支持移动端
- 🌙 支持深色/浅色主题切换
- 📝 支持 Markdown 和 MDX 格式
- 🔍 内置搜索功能
- 📊 文章阅读时间统计
- 🏷️ 标签和分类系统
- 📈 SEO 优化
- 🎨 可自定义配置
- 💬 评论系统支持
- 📡 RSS 订阅支持
> README version: `2024-09-10` ## 🛠️ 技术栈
![Preview Image](https://raw.githubusercontent.com/saicaca/resource/main/fuwari/home.png) - **框架**: Astro
- **样式**: Tailwind CSS + Stylus
- **交互**: Svelte
- **构建工具**: Vite
- **包管理**: pnpm
- **代码规范**: Biome
## ✨ Features ## 🚀 快速开始
- [x] Built with [Astro](https://astro.build) and [Tailwind CSS](https://tailwindcss.com) ### 环境要求
- [x] Smooth animations and page transitions
- [x] Light / dark mode
- [x] Customizable theme colors & banner
- [x] Responsive design
- [ ] Comments
- [x] Search
- [ ] TOC
## 🚀 How to Use - Node.js 18+
- pnpm
1. [Generate a new repository](https://github.com/saicaca/fuwari/generate) from this template or fork this repository. ### 安装依赖
2. To edit your blog locally, clone your repository, run `pnpm install` AND `pnpm add sharp` to install dependencies.
- Install [pnpm](https://pnpm.io) `npm install -g pnpm` if you haven't.
3. Edit the config file `src/config.ts` to customize your blog.
4. Run `pnpm new-post <filename>` to create a new post and edit it in `src/content/posts/`.
5. Deploy your blog to Vercel, Netlify, GitHub Pages, etc. following [the guides](https://docs.astro.build/en/guides/deploy/). You need to edit the site configuration in `astro.config.mjs` before deployment.
## ⚙️ Frontmatter of Posts ```bash
pnpm install
```yaml
---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: ./cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
lang: jp # Set only if the post's language differs from the site's language in `config.ts`
---
``` ```
## 🧞 Commands ### 开发模式
All commands are run from the root of the project, from a terminal: ```bash
pnpm dev
```
| Command | Action | ### 构建生产版本
|:------------------------------------|:-------------------------------------------------|
| `pnpm install` AND `pnpm add sharp` | Installs dependencies | ```bash
| `pnpm dev` | Starts local dev server at `localhost:4321` | pnpm build
| `pnpm build` | Build your production site to `./dist/` | ```
| `pnpm preview` | Preview your build locally, before deploying |
| `pnpm new-post <filename>` | Create a new post | ### 预览构建结果
| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
| `pnpm astro --help` | Get help using the Astro CLI | ```bash
pnpm preview
```
## 📝 使用指南
### 创建新文章
使用内置脚本快速创建新文章:
```bash
pnpm new-post helloword
```
### 清理未使用的图片
清理 `src/content/assets` 目录下未被引用的图片文件:
```bash
pnpm clean
```
### 配置博客
编辑 `src/config.ts` 文件来自定义博客配置:
```typescript
export const siteConfig: SiteConfig = {
title: "Fuwari",
subtitle: "技术分享与实践",
lang: "zh_CN",
themeColor: {
hue: 250,
fixed: false,
},
banner: {
enable: false,
src: "assets/images/demo-banner.png",
position: "center",
},
favicon: [
{
src: "/favicon/icon.png",
}
]
}
```
### 文章格式
文章使用 Markdown 格式,支持 frontmatter
```markdown
---
title: 文章标题
published: 2024-01-01
description: 文章描述
image: ./cover.jpg
tags: [标签1, 标签2]
category: 分类
draft: false
---
# 文章内容
这里是文章正文...
```
## 📁 项目结构
```
├── public/ # 静态资源
├── src/
│ ├── components/ # 组件
│ ├── content/ # 内容
│ │ ├── posts/ # 博客文章
│ │ └── assets/ # 资源文件
│ ├── layouts/ # 布局
│ ├── pages/ # 页面
│ ├── styles/ # 样式
│ └── config.ts # 配置文件
├── scripts/ # 脚本工具
└── package.json
```
## 🎨 自定义
### 主题颜色
`src/config.ts` 中修改 `themeColor` 配置:
```typescript
themeColor: {
hue: 250, // 主色调 (0-360)
fixed: false, // 是否固定颜色
}
```
### 样式定制
- 全局样式:`src/styles/main.css`
- Markdown 样式:`src/styles/markdown.css`
- 变量定义:`src/styles/variables.styl`
## 📦 部署
构建后的静态文件位于 `dist/` 目录,可部署到任何静态托管平台。
## 🤝 贡献
欢迎提交 Issue 和 Pull Request
## 📄 许可证
[MIT License](LICENSE)
## 🙏 致谢
感谢所有为这个项目做出贡献的开发者们!尤其感谢[上游仓库](https://github.com/saicaca/fuwari)

View File

@@ -10,6 +10,7 @@
"astro": "astro", "astro": "astro",
"type-check": "tsc --noEmit --isolatedDeclarations", "type-check": "tsc --noEmit --isolatedDeclarations",
"new-post": "node scripts/new-post.js", "new-post": "node scripts/new-post.js",
"clean": "node scripts/clean-unused-images.js",
"format": "biome format --write ./src", "format": "biome format --write ./src",
"lint": "biome check --write ./src" "lint": "biome check --write ./src"
}, },
@@ -37,6 +38,7 @@
"astro": "5.7.9", "astro": "5.7.9",
"astro-expressive-code": "^0.41.3", "astro-expressive-code": "^0.41.3",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"glob": "^11.0.3",
"hastscript": "^9.0.1", "hastscript": "^9.0.1",
"katex": "^0.16.22", "katex": "^0.16.22",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",

62
pnpm-lock.yaml generated
View File

@@ -77,6 +77,9 @@ importers:
astro-icon: astro-icon:
specifier: ^1.1.5 specifier: ^1.1.5
version: 1.1.5 version: 1.1.5
glob:
specifier: ^11.0.3
version: 11.0.3
hastscript: hastscript:
specifier: ^9.0.1 specifier: ^9.0.1
version: 9.0.1 version: 9.0.1
@@ -1385,6 +1388,14 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@isaacs/balanced-match@4.0.1':
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
engines: {node: 20 || >=22}
'@isaacs/brace-expansion@5.0.0':
resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
engines: {node: 20 || >=22}
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -2908,6 +2919,11 @@ packages:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true hasBin: true
glob@11.0.3:
resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==}
engines: {node: 20 || >=22}
hasBin: true
glob@7.2.3: glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported deprecated: Glob versions prior to v9 are no longer supported
@@ -3280,6 +3296,10 @@ packages:
jackspeak@3.4.3: jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jackspeak@4.1.1:
resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
engines: {node: 20 || >=22}
jake@10.9.2: jake@10.9.2:
resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -3482,6 +3502,10 @@ packages:
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
lru-cache@11.2.1:
resolution: {integrity: sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==}
engines: {node: 20 || >=22}
lru-cache@5.1.1: lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -3686,6 +3710,10 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
minimatch@10.0.3:
resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==}
engines: {node: 20 || >=22}
minimatch@3.1.2: minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -3973,6 +4001,10 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'} engines: {node: '>=16 || 14 >=14.18'}
path-scurry@2.0.0:
resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
engines: {node: 20 || >=22}
path-to-regexp@6.1.0: path-to-regexp@6.1.0:
resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==} resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==}
@@ -6745,6 +6777,12 @@ snapshots:
'@img/sharp-win32-x64@0.34.1': '@img/sharp-win32-x64@0.34.1':
optional: true optional: true
'@isaacs/balanced-match@4.0.1': {}
'@isaacs/brace-expansion@5.0.0':
dependencies:
'@isaacs/balanced-match': 4.0.1
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
dependencies: dependencies:
string-width: 5.1.2 string-width: 5.1.2
@@ -8523,6 +8561,15 @@ snapshots:
package-json-from-dist: 1.0.1 package-json-from-dist: 1.0.1
path-scurry: 1.11.1 path-scurry: 1.11.1
glob@11.0.3:
dependencies:
foreground-child: 3.3.1
jackspeak: 4.1.1
minimatch: 10.0.3
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 2.0.0
glob@7.2.3: glob@7.2.3:
dependencies: dependencies:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
@@ -8979,6 +9026,10 @@ snapshots:
optionalDependencies: optionalDependencies:
'@pkgjs/parseargs': 0.11.0 '@pkgjs/parseargs': 0.11.0
jackspeak@4.1.1:
dependencies:
'@isaacs/cliui': 8.0.2
jake@10.9.2: jake@10.9.2:
dependencies: dependencies:
async: 3.2.6 async: 3.2.6
@@ -9132,6 +9183,8 @@ snapshots:
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
lru-cache@11.2.1: {}
lru-cache@5.1.1: lru-cache@5.1.1:
dependencies: dependencies:
yallist: 3.1.1 yallist: 3.1.1
@@ -9606,6 +9659,10 @@ snapshots:
dependencies: dependencies:
mime-db: 1.52.0 mime-db: 1.52.0
minimatch@10.0.3:
dependencies:
'@isaacs/brace-expansion': 5.0.0
minimatch@3.1.2: minimatch@3.1.2:
dependencies: dependencies:
brace-expansion: 1.1.11 brace-expansion: 1.1.11
@@ -9877,6 +9934,11 @@ snapshots:
lru-cache: 10.4.3 lru-cache: 10.4.3
minipass: 7.1.2 minipass: 7.1.2
path-scurry@2.0.0:
dependencies:
lru-cache: 11.2.1
minipass: 7.1.2
path-to-regexp@6.1.0: {} path-to-regexp@6.1.0: {}
path-to-regexp@6.3.0: {} path-to-regexp@6.3.0: {}

View File

@@ -0,0 +1,246 @@
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* 清理未使用的图片资源脚本
* 扫描 src/content/posts 下的所有 markdown 文件,
* 查找 src/content/assets 中未被引用的图片并删除
*/
const CONTENT_DIR = path.join(process.cwd(), 'src/content');
const POSTS_DIR = path.join(CONTENT_DIR, 'posts');
const ASSETS_DIR = path.join(CONTENT_DIR, 'assets');
// 支持的图片格式
const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.avif'];
/**
* 获取所有 markdown 文件
*/
async function getAllMarkdownFiles() {
try {
const pattern = path.join(POSTS_DIR, '**/*.md').replace(/\\/g, '/');
return await glob(pattern);
} catch (error) {
console.error('获取 markdown 文件失败:', error.message);
return [];
}
}
/**
* 获取所有图片文件
*/
async function getAllImageFiles() {
try {
const extensions = IMAGE_EXTENSIONS.join(',');
const pattern = path.join(ASSETS_DIR, `**/*{${extensions}}`).replace(/\\/g, '/');
return await glob(pattern);
} catch (error) {
console.error('获取图片文件失败:', error.message);
return [];
}
}
/**
* 从 markdown 内容中提取图片引用
*/
function extractImageReferences(content) {
const references = new Set();
// 匹配 YAML frontmatter 中的 image 字段(支持带引号和不带引号的值)
const yamlImageRegex = /^---[\s\S]*?image:\s*(?:['"]([^'"]+)['"]|([^\s\n]+))[\s\S]*?^---/m;
let match = yamlImageRegex.exec(content);
if (match) {
// match[1] 是带引号的值match[2] 是不带引号的值
references.add(match[1] || match[2]);
}
// 匹配 markdown 图片语法: ![alt](path)
const markdownImageRegex = /!\[.*?\]\(([^)]+)\)/g;
while ((match = markdownImageRegex.exec(content)) !== null) {
references.add(match[1]);
}
// 匹配 HTML img 标签: <img src="path">
const htmlImageRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
while ((match = htmlImageRegex.exec(content)) !== null) {
references.add(match[1]);
}
// 匹配 Astro Image 组件引用
const astroImageRegex = /import\s+.*?\s+from\s+["']([^"']+\.(jpg|jpeg|png|gif|webp|svg|avif))["']/gi;
while ((match = astroImageRegex.exec(content)) !== null) {
references.add(match[1]);
}
return Array.from(references);
}
/**
* 规范化路径,处理相对路径和绝对路径
*/
function normalizePath(imagePath, markdownFilePath) {
// 跳过外部 URL
if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
return null;
}
// 跳过以 / 开头的绝对路径(通常指向 public 目录)
if (imagePath.startsWith('/')) {
return null;
}
// 处理相对路径
if (imagePath.startsWith('./') || imagePath.startsWith('../')) {
const markdownDir = path.dirname(markdownFilePath);
return path.resolve(markdownDir, imagePath);
}
// 处理直接的文件名或相对路径
const markdownDir = path.dirname(markdownFilePath);
return path.resolve(markdownDir, imagePath);
}
/**
* 主函数
*/
async function cleanUnusedImages() {
console.log('🔍 开始扫描未使用的图片资源...');
// 检查目录是否存在
if (!fs.existsSync(POSTS_DIR)) {
console.error(`❌ Posts 目录不存在: ${POSTS_DIR}`);
return;
}
if (!fs.existsSync(ASSETS_DIR)) {
console.log(` Assets 目录不存在: ${ASSETS_DIR}`);
return;
}
// 获取所有文件
const markdownFiles = await getAllMarkdownFiles();
const imageFiles = await getAllImageFiles();
console.log(`📄 找到 ${markdownFiles.length} 个 markdown 文件`);
console.log(`🖼️ 找到 ${imageFiles.length} 个图片文件`);
if (imageFiles.length === 0) {
console.log('✅ 没有找到图片文件,无需清理');
return;
}
// 收集所有被引用的图片
const referencedImages = new Set();
for (const mdFile of markdownFiles) {
try {
const content = fs.readFileSync(mdFile, 'utf-8');
const references = extractImageReferences(content);
for (const ref of references) {
const normalizedPath = normalizePath(ref, mdFile);
if (normalizedPath) {
const resolvedPath = path.resolve(normalizedPath);
referencedImages.add(resolvedPath);
}
}
} catch (error) {
console.warn(`⚠️ 读取文件失败: ${mdFile} - ${error.message}`);
}
}
console.log(`🔗 找到 ${referencedImages.size} 个被引用的图片`);
// 找出未被引用的图片
const unusedImages = [];
for (const imageFile of imageFiles) {
const resolvedImagePath = path.resolve(imageFile);
const isReferenced = referencedImages.has(resolvedImagePath);
if (!isReferenced) {
unusedImages.push(imageFile);
}
}
console.log(`🗑️ 找到 ${unusedImages.length} 个未使用的图片`);
if (unusedImages.length === 0) {
console.log('✅ 所有图片都在使用中,无需清理');
return;
}
// 删除未使用的图片
let deletedCount = 0;
for (const unusedImage of unusedImages) {
try {
fs.unlinkSync(unusedImage);
console.log(`🗑️ 已删除: ${path.relative(process.cwd(), unusedImage)}`);
deletedCount++;
} catch (error) {
console.error(`❌ 删除失败: ${unusedImage} - ${error.message}`);
}
}
// 清理空目录
try {
cleanEmptyDirectories(ASSETS_DIR);
} catch (error) {
console.warn(`⚠️ 清理空目录时出错: ${error.message}`);
}
console.log(`\n✅ 清理完成!删除了 ${deletedCount} 个未使用的图片文件`);
}
/**
* 递归清理空目录
*/
function cleanEmptyDirectories(dir) {
if (!fs.existsSync(dir)) return;
const files = fs.readdirSync(dir);
if (files.length === 0) {
fs.rmdirSync(dir);
console.log(`🗑️ 已删除空目录: ${path.relative(process.cwd(), dir)}`);
return;
}
for (const file of files) {
const filePath = path.join(dir, file);
if (fs.statSync(filePath).isDirectory()) {
cleanEmptyDirectories(filePath);
}
}
// 再次检查目录是否为空
const remainingFiles = fs.readdirSync(dir);
if (remainingFiles.length === 0) {
fs.rmdirSync(dir);
console.log(`🗑️ 已删除空目录: ${path.relative(process.cwd(), dir)}`);
}
}
// 运行脚本
// 检查是否直接运行此脚本
const scriptPath = fileURLToPath(import.meta.url);
const isMainModule = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(scriptPath);
if (isMainModule) {
cleanUnusedImages().catch(error => {
console.error('❌ 脚本执行失败:', error.message);
console.error(error.stack);
process.exit(1);
});
}
export { cleanUnusedImages };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB