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.
Files changed (3) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.js +62 -127
  3. 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
- const batPath = path.join(appDataDir, 'cis_file.bat');
60
- // ── bat 脚本:接收格式 + 文件/目录路径,直接调用 cis ──
61
- // %1 = 格式, %2 %3 ... = Windows 传入的文件/目录路径
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', useBat: true },
137
- { base: 'HKCU\\Software\\Classes\\*\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis_file', useBat: true },
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
- let cmd;
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. 注册主菜单项(使用 ExtendedSubCommandsKey 关联各自的子菜单)
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
- if (options.multiFiles && options.multiFiles.length > 0) {
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
- if (options.multiPaths) {
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
- console.log(`📋 找到 ${files.length} 个文件,准备开始转换...\n`);
708
- let successCount = 0;
709
- let failCount = 0;
710
- const results = [];
711
- for (const file of files) {
712
- const relativePath = path.relative(options.directory, file);
713
- process.stdout.write(` 处理中: ${relativePath} ... `);
714
- const result = await convertImage(file, options.targetFormat, files);
715
- if (result.success) {
716
- const outputRelativePath = path.relative(options.directory, result.outputPath);
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(`❌ 失败 (${result.error})`);
723
- results.push({ input: file, output: '', status: 'fail' });
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
- console.log('\n----------------------------------------');
728
- console.log(`\n📊 转换完成!成功: ${successCount}, 失败: ${failCount}\n`);
729
- if (failCount > 0) {
730
- console.log('❌ 失败的文件:');
731
- for (const r of results.filter(x => x.status === 'fail')) {
732
- console.log(` - ${r.input}`);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "change-image-suffix",
3
- "version": "2.1.9",
3
+ "version": "2.1.11",
4
4
  "type": "module",
5
5
  "description": "批量转换图片格式的CLI工具,支持递归搜索、深度限制、指定后缀、Windows右键菜单等功能",
6
6
  "main": "dist/index.js",