change-image-suffix 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +15 -13
- package/dist/index.js +110 -23
- package/package.json +1 -1
- package/scripts/preuninstall.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [2.1.4](https://github.com/GuoSirius/change-image-suffix/compare/v2.1.3...v2.1.4) (2026-05-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* output to <format>/ dir, command injection, recursive nesting, and other issues ([32da38a](https://github.com/GuoSirius/change-image-suffix/commit/32da38aef36a7d736e91e0375573a905d565d864))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Chores
|
|
14
|
+
|
|
15
|
+
* update local settings permissions ([1a86130](https://github.com/GuoSirius/change-image-suffix/commit/1a86130de6005348d6ba8aaba26500d7688be996))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
|
|
20
|
+
* update README output convention, add .claude/ commit policy memory ([b689bc6](https://github.com/GuoSirius/change-image-suffix/commit/b689bc60ed397eb826954e289c3f89488bd97449))
|
|
21
|
+
|
|
22
|
+
### [2.1.3](https://github.com/GuoSirius/change-image-suffix/compare/v2.1.2...v2.1.3) (2026-05-21)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* **context-menu:** auto-refresh menu on version change, gate preuninstall on global ([396dc45](https://github.com/GuoSirius/change-image-suffix/commit/396dc45f1d0606a16828f8e9467194ec933487d8))
|
|
28
|
+
|
|
5
29
|
### [2.1.2](https://github.com/GuoSirius/change-image-suffix/compare/v2.1.1...v2.1.2) (2026-05-21)
|
|
6
30
|
|
|
7
31
|
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- 📏 支持递归深度限制
|
|
10
10
|
- 🎯 支持指定源文件后缀(png, jpg, gif 等)
|
|
11
11
|
- 🎨 支持多种目标格式(webp, jpg, png, avif, tiff)
|
|
12
|
-
- 📤 输出到 `
|
|
12
|
+
- 📤 输出到 `<目标格式>/` 子目录(如 `webp/`, `jpg/`)
|
|
13
13
|
- 🔢 同名不同后缀文件自动编号(`_01`, `_02`)
|
|
14
14
|
- 🖱️ Windows 右键菜单集成
|
|
15
15
|
- ⚡ 基于 [sharp](https://sharp.pixel.glass/) 高性能图片处理
|
|
@@ -101,7 +101,7 @@ cis uninstall-menu
|
|
|
101
101
|
|
|
102
102
|
## 文件命名规范
|
|
103
103
|
|
|
104
|
-
转换后的文件输出到
|
|
104
|
+
转换后的文件输出到 **`<目标格式>/`** 子目录下(例如转换为 webp 则输出到 `webp/`,转换为 jpg 则输出到 `jpg/`),命名规则如下:
|
|
105
105
|
|
|
106
106
|
### 同名不同后缀 → 自动编号
|
|
107
107
|
|
|
@@ -109,34 +109,36 @@ cis uninstall-menu
|
|
|
109
109
|
|
|
110
110
|
```
|
|
111
111
|
源目录: photo.png + photo.jpg + photo.gif
|
|
112
|
-
输出:
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
输出: webp/photo_01.webp
|
|
113
|
+
webp/photo_02.webp
|
|
114
|
+
webp/photo_03.webp
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
### 不同名或不同文件 → 直接覆盖
|
|
118
118
|
|
|
119
119
|
```
|
|
120
120
|
源目录: banner.png + logo.jpg
|
|
121
|
-
输出:
|
|
122
|
-
|
|
121
|
+
输出: webp/banner.webp
|
|
122
|
+
webp/logo.webp
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
### 同格式转换 → 直接覆盖
|
|
126
126
|
|
|
127
127
|
```
|
|
128
128
|
源目录: photo.webp
|
|
129
|
-
输出:
|
|
129
|
+
输出: webp/photo.webp (直接复制,无双重后缀)
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
### 输出目录结构
|
|
133
133
|
|
|
134
134
|
```
|
|
135
135
|
📁 原目录/
|
|
136
|
-
├── 📁
|
|
136
|
+
├── 📁 webp/ ← 转换后的 webp 文件
|
|
137
137
|
│ ├── photo.webp
|
|
138
|
-
│ ├── banner.
|
|
139
|
-
│ └── photo_01.
|
|
138
|
+
│ ├── banner.webp
|
|
139
|
+
│ └── photo_01.webp
|
|
140
|
+
├── 📁 jpg/ ← 转换后的 jpg 文件
|
|
141
|
+
│ └── ...
|
|
140
142
|
├── photo.png
|
|
141
143
|
├── banner.jpg
|
|
142
144
|
└── logo.gif
|
|
@@ -193,14 +195,14 @@ cis -f ./avatar.png -t webp
|
|
|
193
195
|
### 示例 6:多选文件/目录批量转换
|
|
194
196
|
|
|
195
197
|
```bash
|
|
196
|
-
# 多选文件(空格分隔),每个文件的输出在其所在目录的
|
|
198
|
+
# 多选文件(空格分隔),每个文件的输出在其所在目录的 <目标格式>/
|
|
197
199
|
cis ./photo1.png ./photo2.jpg ./folder3
|
|
198
200
|
|
|
199
201
|
# 多选多个目录
|
|
200
202
|
cis ./images ./icons ./logos
|
|
201
203
|
```
|
|
202
204
|
|
|
203
|
-
> 💡 **多选时**:每个文件/目录的输出结果放在**各自所在目录的 `
|
|
205
|
+
> 💡 **多选时**:每个文件/目录的输出结果放在**各自所在目录的 `<目标格式>/` 子目录**中(如 `webp/`, `jpg/`),互不干扰。
|
|
204
206
|
|
|
205
207
|
---
|
|
206
208
|
|
package/dist/index.js
CHANGED
|
@@ -195,11 +195,15 @@ endlocal
|
|
|
195
195
|
// bat 脚本会检查文件扩展名,自动忽略非图片文件
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
+
// 写入版本标记,用于检测 npm update 后自动刷新菜单
|
|
199
|
+
const versionFile = path.join(appDataDir, 'version.json');
|
|
200
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
201
|
+
fs.writeFileSync(versionFile, JSON.stringify({ version: pkg.version }), 'utf8');
|
|
198
202
|
console.log('✅ 右键菜单安装成功!');
|
|
199
203
|
console.log(' 📁 文件夹空白处/图标右键 → 悬停展开格式子菜单');
|
|
200
204
|
console.log(' 🖼 图片文件上右键 → 悬停展开格式子菜单');
|
|
201
205
|
console.log(' ⚠️ 非图片文件右键 → 菜单显示但不处理');
|
|
202
|
-
console.log(` 📂 输出目录:
|
|
206
|
+
console.log(` 📂 输出目录: <原目录>/<目标格式>/`);
|
|
203
207
|
console.log('\n💡 提示:如需卸载,执行 cis uninstall-menu');
|
|
204
208
|
}
|
|
205
209
|
function uninstallContextMenu() {
|
|
@@ -228,15 +232,55 @@ function uninstallContextMenu() {
|
|
|
228
232
|
}
|
|
229
233
|
catch { /* ignore */ }
|
|
230
234
|
}
|
|
231
|
-
//
|
|
235
|
+
// 删除批处理文件、图标和版本标记
|
|
232
236
|
const appDataDir = path.join(os.homedir(), 'AppData', 'Roaming', 'change-image-suffix');
|
|
233
237
|
const batPath = path.join(appDataDir, 'cis_file.bat');
|
|
234
238
|
try {
|
|
235
239
|
fs.unlinkSync(batPath);
|
|
236
240
|
}
|
|
237
241
|
catch { /* ignore */ }
|
|
242
|
+
const iconPath = path.join(appDataDir, 'icon.ico');
|
|
243
|
+
try {
|
|
244
|
+
fs.unlinkSync(iconPath);
|
|
245
|
+
}
|
|
246
|
+
catch { /* ignore */ }
|
|
247
|
+
const versionFile = path.join(appDataDir, 'version.json');
|
|
248
|
+
try {
|
|
249
|
+
fs.unlinkSync(versionFile);
|
|
250
|
+
}
|
|
251
|
+
catch { /* ignore */ }
|
|
252
|
+
// 尝试删除目录(仅当为空时),失败也不影响
|
|
253
|
+
try {
|
|
254
|
+
fs.rmdirSync(appDataDir);
|
|
255
|
+
}
|
|
256
|
+
catch { /* ignore */ }
|
|
238
257
|
console.log('✅ 右键菜单已卸载');
|
|
239
258
|
}
|
|
259
|
+
// 自动检测版本变化并更新右键菜单(解决 npm update 不触发 postinstall 的问题)
|
|
260
|
+
function autoUpdateContextMenu() {
|
|
261
|
+
if (os.platform() !== 'win32')
|
|
262
|
+
return;
|
|
263
|
+
const appDataDir = path.join(os.homedir(), 'AppData', 'Roaming', 'change-image-suffix');
|
|
264
|
+
const versionFile = path.join(appDataDir, 'version.json');
|
|
265
|
+
// 如果从未安装过右键菜单,跳过自动更新(postinstall 负责首次安装)
|
|
266
|
+
if (!fs.existsSync(versionFile))
|
|
267
|
+
return;
|
|
268
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
269
|
+
const currentVersion = pkg.version;
|
|
270
|
+
let installedVersion = '';
|
|
271
|
+
try {
|
|
272
|
+
installedVersion = JSON.parse(fs.readFileSync(versionFile, 'utf8')).version || '';
|
|
273
|
+
}
|
|
274
|
+
catch { /* ignore */ }
|
|
275
|
+
if (installedVersion !== currentVersion) {
|
|
276
|
+
try {
|
|
277
|
+
installContextMenu();
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
console.warn('⚠️ 右键菜单自动更新失败,请手动执行 cis install-menu');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
240
284
|
// ─────────────────────────────────────────
|
|
241
285
|
// 参数解析
|
|
242
286
|
// ─────────────────────────────────────────
|
|
@@ -263,27 +307,40 @@ function parseArgs() {
|
|
|
263
307
|
continue;
|
|
264
308
|
}
|
|
265
309
|
if (arg === '-d' || arg === '--depth') {
|
|
266
|
-
if (i + 1 < args.length) {
|
|
267
|
-
|
|
268
|
-
if (isNaN(
|
|
310
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
311
|
+
const val = parseInt(args[++i], 10);
|
|
312
|
+
if (isNaN(val) || val < 1) {
|
|
269
313
|
console.error('❌ 深度必须是正整数');
|
|
270
314
|
process.exit(1);
|
|
271
315
|
}
|
|
316
|
+
options.maxDepth = val;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
console.error('❌ -d/--depth 需要指定一个正整数参数');
|
|
320
|
+
process.exit(1);
|
|
272
321
|
}
|
|
273
322
|
i++;
|
|
274
323
|
continue;
|
|
275
324
|
}
|
|
276
325
|
if (arg === '-e' || arg === '--extensions') {
|
|
277
|
-
if (i + 1 < args.length) {
|
|
326
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
278
327
|
options.extensions = args[++i].split(',').map(e => e.trim().toLowerCase().replace(/^\./, ''));
|
|
279
328
|
}
|
|
329
|
+
else {
|
|
330
|
+
console.error('❌ -e/--extensions 需要指定后缀参数');
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
280
333
|
i++;
|
|
281
334
|
continue;
|
|
282
335
|
}
|
|
283
336
|
if (arg === '-t' || arg === '--to') {
|
|
284
|
-
if (i + 1 < args.length) {
|
|
337
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
285
338
|
options.targetFormat = args[++i].trim().toLowerCase().replace(/^\./, '');
|
|
286
339
|
}
|
|
340
|
+
else {
|
|
341
|
+
console.error('❌ -t/--to 需要指定目标格式');
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
287
344
|
i++;
|
|
288
345
|
continue;
|
|
289
346
|
}
|
|
@@ -312,10 +369,15 @@ function parseArgs() {
|
|
|
312
369
|
}
|
|
313
370
|
if (arg === '-f' || arg === '--file') {
|
|
314
371
|
// 收集 -f 后的所有文件
|
|
372
|
+
const start = i + 1;
|
|
315
373
|
while (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
316
374
|
i++;
|
|
317
375
|
filesFromFlag.push(path.resolve(args[i]));
|
|
318
376
|
}
|
|
377
|
+
if (start > i) {
|
|
378
|
+
console.error('❌ -f/--file 需要指定至少一个文件路径');
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
319
381
|
i++;
|
|
320
382
|
continue;
|
|
321
383
|
}
|
|
@@ -403,14 +465,16 @@ function printHelp() {
|
|
|
403
465
|
// ─────────────────────────────────────────
|
|
404
466
|
// 图片处理
|
|
405
467
|
// ─────────────────────────────────────────
|
|
406
|
-
function getAllFiles(dir, extensions, recursive, currentDepth, maxDepth) {
|
|
468
|
+
function getAllFiles(dir, extensions, recursive, currentDepth, maxDepth, excludeDirName) {
|
|
407
469
|
const files = [];
|
|
408
470
|
try {
|
|
409
471
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
410
472
|
for (const entry of entries) {
|
|
411
473
|
const fullPath = path.join(dir, entry.name);
|
|
412
474
|
if (entry.isDirectory() && recursive && currentDepth < maxDepth) {
|
|
413
|
-
|
|
475
|
+
if (excludeDirName && entry.name === excludeDirName)
|
|
476
|
+
continue;
|
|
477
|
+
files.push(...getAllFiles(fullPath, extensions, recursive, currentDepth + 1, maxDepth, excludeDirName));
|
|
414
478
|
}
|
|
415
479
|
else if (entry.isFile()) {
|
|
416
480
|
const ext = path.extname(entry.name).slice(1).toLowerCase();
|
|
@@ -431,7 +495,7 @@ function getOutputPath(inputPath, targetFormat, allInputFiles) {
|
|
|
431
495
|
const basename = path.basename(inputPath, ext);
|
|
432
496
|
const targetExt = targetFormat;
|
|
433
497
|
let coreName = basename;
|
|
434
|
-
const outputDir = path.join(dir,
|
|
498
|
+
const outputDir = path.join(dir, targetFormat);
|
|
435
499
|
// 检查输入目录中是否有同名(不含扩展名)但不同后缀的文件
|
|
436
500
|
// 如 photo.png 和 photo.jpg 会被判定为同名冲突
|
|
437
501
|
const hasNameConflict = allInputFiles.some(f => {
|
|
@@ -440,7 +504,7 @@ function getOutputPath(inputPath, targetFormat, allInputFiles) {
|
|
|
440
504
|
const fDir = path.dirname(f);
|
|
441
505
|
const fExt = path.extname(f);
|
|
442
506
|
const fBasename = path.basename(f, fExt);
|
|
443
|
-
return fDir === dir && fBasename === basename && fExt.toLowerCase() !== ext.toLowerCase();
|
|
507
|
+
return fDir === dir && fBasename.toLowerCase() === basename.toLowerCase() && fExt.toLowerCase() !== ext.toLowerCase();
|
|
444
508
|
});
|
|
445
509
|
if (hasNameConflict) {
|
|
446
510
|
// 找所有同basename的文件的序号
|
|
@@ -450,7 +514,7 @@ function getOutputPath(inputPath, targetFormat, allInputFiles) {
|
|
|
450
514
|
const fDir = path.dirname(f);
|
|
451
515
|
const fExt = path.extname(f);
|
|
452
516
|
const fBasename = path.basename(f, fExt);
|
|
453
|
-
return fDir === dir && fBasename === basename;
|
|
517
|
+
return fDir === dir && fBasename.toLowerCase() === basename.toLowerCase();
|
|
454
518
|
});
|
|
455
519
|
// 当前文件在所有同名文件中的索引(从1开始)
|
|
456
520
|
const sortedMatches = [...allBasenameMatches, inputPath].sort();
|
|
@@ -462,22 +526,23 @@ function getOutputPath(inputPath, targetFormat, allInputFiles) {
|
|
|
462
526
|
return path.join(outputDir, filename);
|
|
463
527
|
}
|
|
464
528
|
async function convertImage(inputPath, targetFormat, allInputFiles) {
|
|
529
|
+
const outputPath = getOutputPath(inputPath, targetFormat, allInputFiles);
|
|
530
|
+
const srcExt = path.extname(inputPath).slice(1).toLowerCase();
|
|
531
|
+
const fmt = targetFormat.toLowerCase();
|
|
532
|
+
// 先验证格式,避免在无效路径上创建目录
|
|
533
|
+
if (!SUPPORTED_OUTPUT_FORMATS.includes(fmt)) {
|
|
534
|
+
return { success: false, outputPath, error: `不支持的目标格式: ${targetFormat},支持: ${SUPPORTED_OUTPUT_FORMATS.join(', ')}` };
|
|
535
|
+
}
|
|
465
536
|
try {
|
|
466
|
-
const outputPath = getOutputPath(inputPath, targetFormat, allInputFiles);
|
|
467
537
|
const outputDir = path.dirname(outputPath);
|
|
468
538
|
if (!fs.existsSync(outputDir)) {
|
|
469
539
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
470
540
|
}
|
|
471
|
-
const srcExt = path.extname(inputPath).slice(1).toLowerCase();
|
|
472
|
-
const fmt = targetFormat.toLowerCase();
|
|
473
541
|
// 同格式直接复制,避免重新编码导致质量损失
|
|
474
542
|
if (srcExt === fmt || (srcExt === 'jpeg' && fmt === 'jpg') || (srcExt === 'jpg' && fmt === 'jpeg') || (srcExt === 'tif' && fmt === 'tiff') || (srcExt === 'tiff' && fmt === 'tif')) {
|
|
475
543
|
fs.copyFileSync(inputPath, outputPath);
|
|
476
544
|
return { success: true, outputPath };
|
|
477
545
|
}
|
|
478
|
-
if (!SUPPORTED_OUTPUT_FORMATS.includes(fmt)) {
|
|
479
|
-
return { success: false, outputPath: inputPath, error: `不支持的目标格式: ${targetFormat},支持: ${SUPPORTED_OUTPUT_FORMATS.join(', ')}` };
|
|
480
|
-
}
|
|
481
546
|
const image = (0, sharp_1.default)(inputPath);
|
|
482
547
|
switch (fmt) {
|
|
483
548
|
case 'webp':
|
|
@@ -503,7 +568,7 @@ async function convertImage(inputPath, targetFormat, allInputFiles) {
|
|
|
503
568
|
catch (err) {
|
|
504
569
|
return {
|
|
505
570
|
success: false,
|
|
506
|
-
outputPath
|
|
571
|
+
outputPath,
|
|
507
572
|
error: err instanceof Error ? err.message : '未知错误'
|
|
508
573
|
};
|
|
509
574
|
}
|
|
@@ -522,6 +587,8 @@ async function main() {
|
|
|
522
587
|
uninstallContextMenu();
|
|
523
588
|
return;
|
|
524
589
|
}
|
|
590
|
+
// 自动检测版本变化并刷新右键菜单(npm update 不触发 postinstall)
|
|
591
|
+
autoUpdateContextMenu();
|
|
525
592
|
console.log('\n🖼️ change-image-suffix - 图片格式批量转换工具\n');
|
|
526
593
|
const options = parseArgs();
|
|
527
594
|
// ─── 辅助函数:处理文件列表 ───
|
|
@@ -532,16 +599,18 @@ async function main() {
|
|
|
532
599
|
console.log('----------------------------------------\n');
|
|
533
600
|
let totalSuccess = 0;
|
|
534
601
|
let totalFail = 0;
|
|
602
|
+
const failures = [];
|
|
535
603
|
for (const filePath of files) {
|
|
536
604
|
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
537
605
|
if (!SUPPORTED_INPUT_EXTENSIONS.includes(ext)) {
|
|
538
606
|
console.log(` ⚠️ 跳过(不支持格式): ${filePath}`);
|
|
539
607
|
totalFail++;
|
|
608
|
+
failures.push(filePath);
|
|
540
609
|
continue;
|
|
541
610
|
}
|
|
542
611
|
console.log(` 📄 文件: ${filePath}`);
|
|
543
612
|
process.stdout.write(` 处理中: ${path.basename(filePath)} ... `);
|
|
544
|
-
const result = await convertImage(filePath, options.targetFormat,
|
|
613
|
+
const result = await convertImage(filePath, options.targetFormat, files);
|
|
545
614
|
if (result.success) {
|
|
546
615
|
console.log(`✅ -> ${path.relative(path.dirname(filePath), result.outputPath)}`);
|
|
547
616
|
totalSuccess++;
|
|
@@ -549,6 +618,13 @@ async function main() {
|
|
|
549
618
|
else {
|
|
550
619
|
console.log(`❌ 失败 (${result.error})`);
|
|
551
620
|
totalFail++;
|
|
621
|
+
failures.push(filePath);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (failures.length > 0) {
|
|
625
|
+
console.log('\n❌ 失败的文件:');
|
|
626
|
+
for (const f of failures) {
|
|
627
|
+
console.log(` - ${f}`);
|
|
552
628
|
}
|
|
553
629
|
}
|
|
554
630
|
return { success: totalSuccess, fail: totalFail };
|
|
@@ -560,11 +636,13 @@ async function main() {
|
|
|
560
636
|
console.log('----------------------------------------\n');
|
|
561
637
|
let totalSuccess = 0;
|
|
562
638
|
let totalFail = 0;
|
|
639
|
+
const failures = [];
|
|
563
640
|
for (const inputPath of dirs) {
|
|
564
641
|
const stat = fs.existsSync(inputPath) ? fs.statSync(inputPath) : null;
|
|
565
642
|
if (!stat) {
|
|
566
643
|
console.log(` ⚠️ 跳过(不存在): ${inputPath}`);
|
|
567
644
|
totalFail++;
|
|
645
|
+
failures.push(inputPath);
|
|
568
646
|
continue;
|
|
569
647
|
}
|
|
570
648
|
if (stat.isFile()) {
|
|
@@ -572,11 +650,12 @@ async function main() {
|
|
|
572
650
|
if (!SUPPORTED_INPUT_EXTENSIONS.includes(ext)) {
|
|
573
651
|
console.log(` ⚠️ 跳过(不支持格式): ${inputPath}`);
|
|
574
652
|
totalFail++;
|
|
653
|
+
failures.push(inputPath);
|
|
575
654
|
continue;
|
|
576
655
|
}
|
|
577
656
|
console.log(` 📄 文件: ${inputPath}`);
|
|
578
657
|
process.stdout.write(` 处理中: ${path.basename(inputPath)} ... `);
|
|
579
|
-
const result = await convertImage(inputPath, options.targetFormat,
|
|
658
|
+
const result = await convertImage(inputPath, options.targetFormat, dirs);
|
|
580
659
|
if (result.success) {
|
|
581
660
|
console.log(`✅ -> ${path.relative(path.dirname(inputPath), result.outputPath)}`);
|
|
582
661
|
totalSuccess++;
|
|
@@ -584,10 +663,11 @@ async function main() {
|
|
|
584
663
|
else {
|
|
585
664
|
console.log(`❌ 失败 (${result.error})`);
|
|
586
665
|
totalFail++;
|
|
666
|
+
failures.push(inputPath);
|
|
587
667
|
}
|
|
588
668
|
}
|
|
589
669
|
else {
|
|
590
|
-
const files = getAllFiles(inputPath, options.extensions, options.recursive, 0, options.maxDepth);
|
|
670
|
+
const files = getAllFiles(inputPath, options.extensions, options.recursive, 0, options.maxDepth, options.targetFormat);
|
|
591
671
|
console.log(` 📁 目录: ${inputPath} (${files.length} 个文件)`);
|
|
592
672
|
if (files.length === 0) {
|
|
593
673
|
console.log(' ✅ 没有找到图片文件');
|
|
@@ -603,10 +683,17 @@ async function main() {
|
|
|
603
683
|
else {
|
|
604
684
|
console.log(`❌ (${result.error})`);
|
|
605
685
|
totalFail++;
|
|
686
|
+
failures.push(file);
|
|
606
687
|
}
|
|
607
688
|
}
|
|
608
689
|
}
|
|
609
690
|
}
|
|
691
|
+
if (failures.length > 0) {
|
|
692
|
+
console.log('\n❌ 失败的文件:');
|
|
693
|
+
for (const f of failures) {
|
|
694
|
+
console.log(` - ${f}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
610
697
|
return { success: totalSuccess, fail: totalFail };
|
|
611
698
|
}
|
|
612
699
|
// ─── 混合模式:同时有文件和目录 ───
|
|
@@ -640,7 +727,7 @@ async function main() {
|
|
|
640
727
|
console.log(`📄 后缀: ${options.extensions.join(', ')}`);
|
|
641
728
|
console.log(`🎯 目标格式: ${options.targetFormat}`);
|
|
642
729
|
console.log('\n----------------------------------------\n');
|
|
643
|
-
const files = getAllFiles(options.directory, options.extensions, options.recursive, 0, options.maxDepth);
|
|
730
|
+
const files = getAllFiles(options.directory, options.extensions, options.recursive, 0, options.maxDepth, options.targetFormat);
|
|
644
731
|
if (files.length === 0) {
|
|
645
732
|
console.log('✅ 没有找到需要转换的图片文件。');
|
|
646
733
|
return;
|
package/package.json
CHANGED
package/scripts/preuninstall.js
CHANGED
|
@@ -4,7 +4,7 @@ const os = require('os');
|
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
|
-
if (os.platform() === 'win32') {
|
|
7
|
+
if (os.platform() === 'win32' && process.env.npm_config_global === 'true') {
|
|
8
8
|
try {
|
|
9
9
|
const indexJs = path.join(__dirname, '..', 'dist', 'index.js');
|
|
10
10
|
execSync(`node "${indexJs}" uninstall-menu`, { stdio: 'inherit' });
|