change-image-suffix 2.1.9 → 2.1.11
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 +19 -0
- package/dist/index.js +62 -127
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
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.11](https://github.com/GuoSirius/change-image-suffix/compare/v2.1.10...v2.1.11) (2026-05-25)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* bypass cmd.exe entirely — call node.exe directly from context menu ([ced93c4](https://github.com/GuoSirius/change-image-suffix/commit/ced93c498bc83ed68da59b539553d5b04c0ec1eb))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Chores
|
|
14
|
+
|
|
15
|
+
* update local settings permissions ([f97f6f6](https://github.com/GuoSirius/change-image-suffix/commit/f97f6f6a229fc7401291c4a1175f2184bf5e7ec1))
|
|
16
|
+
|
|
17
|
+
### [2.1.10](https://github.com/GuoSirius/change-image-suffix/compare/v2.1.9...v2.1.10) (2026-05-25)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* rewrite bat to avoid all path manipulation, pass directly to cis ([6c262fa](https://github.com/GuoSirius/change-image-suffix/commit/6c262fa190c8a7f380c29b0319ef860364bf90af))
|
|
23
|
+
|
|
5
24
|
### [2.1.9](https://github.com/GuoSirius/change-image-suffix/compare/v2.1.8...v2.1.9) (2026-05-25)
|
|
6
25
|
|
|
7
26
|
|
package/dist/index.js
CHANGED
|
@@ -56,71 +56,9 @@ function installContextMenu() {
|
|
|
56
56
|
fs.copyFileSync(icoSource, icoTarget);
|
|
57
57
|
}
|
|
58
58
|
const iconPath = fs.existsSync(icoTarget) ? icoTarget : cisCmd;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const batContent = `
|
|
63
|
-
@echo off
|
|
64
|
-
chcp 65001 >nul
|
|
65
|
-
setlocal enabledelayedexpansion
|
|
66
|
-
|
|
67
|
-
REM Find cis command in PATH
|
|
68
|
-
set "CIS_CMD="
|
|
69
|
-
for /f "delims=" %%c in ('where cis.cmd 2^>nul') do (
|
|
70
|
-
if "!CIS_CMD!"=="" set "CIS_CMD=%%c"
|
|
71
|
-
)
|
|
72
|
-
if "!CIS_CMD!"=="" (
|
|
73
|
-
for /f "delims=" %%c in ('where cis 2^>nul') do (
|
|
74
|
-
if "!CIS_CMD!"=="" set "CIS_CMD=%%c"
|
|
75
|
-
)
|
|
76
|
-
)
|
|
77
|
-
if "!CIS_CMD!"=="" (
|
|
78
|
-
echo [change-image-suffix] cis command not found. Run: npm install -g change-image-suffix
|
|
79
|
-
pause
|
|
80
|
-
exit /b 1
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
if "%~1"=="" (
|
|
84
|
-
echo [change-image-suffix] Error: No format specified.
|
|
85
|
-
pause
|
|
86
|
-
exit /b 1
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
set "format=%~1"
|
|
90
|
-
shift
|
|
91
|
-
|
|
92
|
-
set args=
|
|
93
|
-
:parse
|
|
94
|
-
if "%~1"=="" goto :run
|
|
95
|
-
if exist "%~1\\*" goto :is_dir
|
|
96
|
-
goto :is_file
|
|
97
|
-
|
|
98
|
-
:is_dir
|
|
99
|
-
set args=!args! -p "%~1"
|
|
100
|
-
goto :next_arg
|
|
101
|
-
|
|
102
|
-
:is_file
|
|
103
|
-
set args=!args! -f "%~1"
|
|
104
|
-
|
|
105
|
-
:next_arg
|
|
106
|
-
shift
|
|
107
|
-
goto :parse
|
|
108
|
-
|
|
109
|
-
:run
|
|
110
|
-
if "!args!"=="" goto :no_args
|
|
111
|
-
|
|
112
|
-
"!CIS_CMD!" -t !format! !args!
|
|
113
|
-
pause
|
|
114
|
-
endlocal
|
|
115
|
-
goto :eof
|
|
116
|
-
|
|
117
|
-
:no_args
|
|
118
|
-
echo [change-image-suffix] No files or directories to process.
|
|
119
|
-
pause
|
|
120
|
-
endlocal
|
|
121
|
-
exit /b 1
|
|
122
|
-
`;
|
|
123
|
-
fs.writeFileSync(batPath, batContent, 'utf8');
|
|
59
|
+
// 绕过 cmd.exe 编码问题:直接用 node.exe 调用,避免 bat/cmd 的 GBK/Unicode 转换
|
|
60
|
+
const nodeExe = process.execPath;
|
|
61
|
+
const scriptPath = path.join(__dirname, 'index.js');
|
|
124
62
|
// ── 格式列表(webp 排第一,其他按常见程度排序)──
|
|
125
63
|
const formats = [
|
|
126
64
|
{ verb: 'webp', label: '🌀 WebP' },
|
|
@@ -129,43 +67,28 @@ exit /b 1
|
|
|
129
67
|
{ verb: 'avif', label: '📺 AVIF' },
|
|
130
68
|
{ verb: 'tiff', label: '📋 TIFF' },
|
|
131
69
|
];
|
|
132
|
-
// ── 使用 ExtendedSubCommandsKey
|
|
133
|
-
// 文件右键和目录右键均使用 bat 脚本,混合选择时自动分类文件和目录
|
|
70
|
+
// ── 使用 ExtendedSubCommandsKey,直接调用 node.exe(无 bat 中转)──
|
|
134
71
|
const menuBases = [
|
|
135
72
|
{ base: 'HKCU\\Software\\Classes\\Directory\\Background\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis', arg: '-p "%V"' },
|
|
136
|
-
{ base: 'HKCU\\Software\\Classes\\Directory\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis_dir',
|
|
137
|
-
{ base: 'HKCU\\Software\\Classes\\*\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis_file',
|
|
73
|
+
{ base: 'HKCU\\Software\\Classes\\Directory\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis_dir', arg: '-p "%1"' },
|
|
74
|
+
{ base: 'HKCU\\Software\\Classes\\*\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis_file', arg: '-f "%1"' },
|
|
138
75
|
];
|
|
139
|
-
// 1.
|
|
76
|
+
// 1. 注册格式子菜单
|
|
140
77
|
const REG_ROOT = 'HKCU\\Software\\Classes\\';
|
|
141
78
|
for (const menu of menuBases) {
|
|
142
79
|
for (const fmt of formats) {
|
|
143
80
|
const shellKey = `${REG_ROOT}${menu.subMenu}\\shell\\${fmt.verb}`;
|
|
144
|
-
|
|
145
|
-
if (menu.useBat) {
|
|
146
|
-
// bat 脚本接收格式参数,%1 为 Windows 传入的文件/目录路径
|
|
147
|
-
cmd = `"${batPath}" ${fmt.verb} "%1"`;
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
cmd = `"${cisCmd}" -t ${fmt.verb} ${menu.arg}`;
|
|
151
|
-
}
|
|
81
|
+
const cmd = `"${nodeExe}" "${scriptPath}" --pause -t ${fmt.verb} ${menu.arg}`;
|
|
152
82
|
execSync(`reg add "${shellKey}" /ve /d "${fmt.label}" /f`, { stdio: 'ignore' });
|
|
153
83
|
execSync(`reg add "${shellKey}" /v Icon /d "${iconPath}" /f`, { stdio: 'ignore' });
|
|
154
84
|
execSync(`reg add "${shellKey}\\command" /ve /d "${cmd}" /f`, { stdio: 'ignore' });
|
|
155
85
|
}
|
|
156
86
|
}
|
|
157
|
-
// 2.
|
|
87
|
+
// 2. 注册主菜单项
|
|
158
88
|
for (const menu of menuBases) {
|
|
159
89
|
execSync(`reg add "${menu.base}" /ve /d "🖼 转换图片 (cis)" /f`, { stdio: 'ignore' });
|
|
160
90
|
execSync(`reg add "${menu.base}" /v Icon /d "${iconPath}" /f`, { stdio: 'ignore' });
|
|
161
91
|
execSync(`reg add "${menu.base}" /v ExtendedSubCommandsKey /d "${menu.subMenu}" /f`, { stdio: 'ignore' });
|
|
162
|
-
// 使用 bat 的菜单(文件右键和目录右键):设置 command 接收文件路径 %1
|
|
163
|
-
// Windows 会将父命令收到的 %1 自动传递给子命令
|
|
164
|
-
if (menu.useBat) {
|
|
165
|
-
execSync(`reg add "${menu.base}\\command" /ve /d "cmd /c echo %1 > nul" /f`, { stdio: 'ignore' });
|
|
166
|
-
// 注意:不添加 AppliesTo 限制,让菜单始终显示
|
|
167
|
-
// bat 脚本会检查文件扩展名,自动忽略非图片文件
|
|
168
|
-
}
|
|
169
92
|
}
|
|
170
93
|
// 写入版本标记,用于检测 npm update 后自动刷新菜单
|
|
171
94
|
const versionFile = path.join(appDataDir, 'version.json');
|
|
@@ -676,61 +599,73 @@ async function main() {
|
|
|
676
599
|
const dirResult = await processDirs(options.multiPaths);
|
|
677
600
|
console.log('\n----------------------------------------');
|
|
678
601
|
console.log(`📊 转换完成!成功: ${fileResult.success + dirResult.success}, 失败: ${fileResult.fail + dirResult.fail}\n`);
|
|
679
|
-
return;
|
|
680
602
|
}
|
|
681
|
-
|
|
682
|
-
|
|
603
|
+
else if (options.multiFiles && options.multiFiles.length > 0) {
|
|
604
|
+
// ─── 单/多文件模式 ───
|
|
683
605
|
const result = await processFiles(options.multiFiles, '图片转换工具');
|
|
684
606
|
console.log('\n----------------------------------------\n');
|
|
685
607
|
console.log(`📊 转换完成!成功: ${result.success}, 失败: ${result.fail}\n`);
|
|
686
|
-
return;
|
|
687
608
|
}
|
|
688
|
-
|
|
689
|
-
|
|
609
|
+
else if (options.multiPaths) {
|
|
610
|
+
// ─── 多路径模式 ───
|
|
690
611
|
console.log(`\n🖼️ change-image-suffix - 批量转换工具\n`);
|
|
691
612
|
const result = await processDirs(options.multiPaths);
|
|
692
613
|
console.log('\n----------------------------------------');
|
|
693
614
|
console.log(`📊 转换完成!成功: ${result.success}, 失败: ${result.fail}\n`);
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
// ─── 目录批量模式 ───
|
|
697
|
-
console.log(`📂 目录: ${options.directory}`);
|
|
698
|
-
console.log(`🔁 递归: ${options.recursive ? `是 (深度: ${options.maxDepth === Infinity ? '无限制' : options.maxDepth})` : '否'}`);
|
|
699
|
-
console.log(`📄 后缀: ${options.extensions.join(', ')}`);
|
|
700
|
-
console.log(`🎯 目标格式: ${options.targetFormat}`);
|
|
701
|
-
console.log('\n----------------------------------------\n');
|
|
702
|
-
const files = getAllFiles(options.directory, options.extensions, options.recursive, 0, options.maxDepth, options.targetFormat);
|
|
703
|
-
if (files.length === 0) {
|
|
704
|
-
console.log('✅ 没有找到需要转换的图片文件。');
|
|
705
|
-
return;
|
|
706
615
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
const
|
|
715
|
-
if (
|
|
716
|
-
|
|
717
|
-
console.log(`✅ -> ${outputRelativePath}`);
|
|
718
|
-
results.push({ input: file, output: result.outputPath, status: 'success' });
|
|
719
|
-
successCount++;
|
|
616
|
+
else {
|
|
617
|
+
// ─── 目录批量模式 ───
|
|
618
|
+
console.log(`📂 目录: ${options.directory}`);
|
|
619
|
+
console.log(`🔁 递归: ${options.recursive ? `是 (深度: ${options.maxDepth === Infinity ? '无限制' : options.maxDepth})` : '否'}`);
|
|
620
|
+
console.log(`📄 后缀: ${options.extensions.join(', ')}`);
|
|
621
|
+
console.log(`🎯 目标格式: ${options.targetFormat}`);
|
|
622
|
+
console.log('\n----------------------------------------\n');
|
|
623
|
+
const files = getAllFiles(options.directory, options.extensions, options.recursive, 0, options.maxDepth, options.targetFormat);
|
|
624
|
+
if (files.length === 0) {
|
|
625
|
+
console.log('✅ 没有找到需要转换的图片文件。');
|
|
720
626
|
}
|
|
721
627
|
else {
|
|
722
|
-
console.log(
|
|
723
|
-
|
|
724
|
-
failCount
|
|
628
|
+
console.log(`📋 找到 ${files.length} 个文件,准备开始转换...\n`);
|
|
629
|
+
let successCount = 0;
|
|
630
|
+
let failCount = 0;
|
|
631
|
+
const results = [];
|
|
632
|
+
for (const file of files) {
|
|
633
|
+
const relativePath = path.relative(options.directory, file);
|
|
634
|
+
process.stdout.write(` 处理中: ${relativePath} ... `);
|
|
635
|
+
const result = await convertImage(file, options.targetFormat, files);
|
|
636
|
+
if (result.success) {
|
|
637
|
+
const outputRelativePath = path.relative(options.directory, result.outputPath);
|
|
638
|
+
console.log(`✅ -> ${outputRelativePath}`);
|
|
639
|
+
results.push({ input: file, output: result.outputPath, status: 'success' });
|
|
640
|
+
successCount++;
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
console.log(`❌ 失败 (${result.error})`);
|
|
644
|
+
results.push({ input: file, output: '', status: 'fail' });
|
|
645
|
+
failCount++;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
console.log('\n----------------------------------------');
|
|
649
|
+
console.log(`\n📊 转换完成!成功: ${successCount}, 失败: ${failCount}\n`);
|
|
650
|
+
if (failCount > 0) {
|
|
651
|
+
console.log('❌ 失败的文件:');
|
|
652
|
+
for (const r of results.filter(x => x.status === 'fail')) {
|
|
653
|
+
console.log(` - ${r.input}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
725
656
|
}
|
|
726
657
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
658
|
+
// 右键菜单调用时暂停,让用户看到输出
|
|
659
|
+
if (process.argv.includes('--pause')) {
|
|
660
|
+
console.log('\n按任意键退出...');
|
|
661
|
+
process.stdin.setRawMode(true);
|
|
662
|
+
process.stdin.resume();
|
|
663
|
+
await new Promise(resolve => {
|
|
664
|
+
process.stdin.once('data', () => {
|
|
665
|
+
process.stdin.setRawMode(false);
|
|
666
|
+
resolve();
|
|
667
|
+
});
|
|
668
|
+
});
|
|
734
669
|
}
|
|
735
670
|
}
|
|
736
671
|
main().catch(console.error);
|