change-image-suffix 2.1.10 → 2.1.12

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 +63 -109
  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.12](https://github.com/GuoSirius/change-image-suffix/compare/v2.1.11...v2.1.12) (2026-05-25)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * remove quotes around %1/%V to avoid double-quoting by Windows ([ac0c771](https://github.com/GuoSirius/change-image-suffix/commit/ac0c7719f8af7f3ced55a7968d926f2da80ca951))
11
+
12
+ ### [2.1.11](https://github.com/GuoSirius/change-image-suffix/compare/v2.1.10...v2.1.11) (2026-05-25)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * bypass cmd.exe entirely — call node.exe directly from context menu ([ced93c4](https://github.com/GuoSirius/change-image-suffix/commit/ced93c498bc83ed68da59b539553d5b04c0ec1eb))
18
+
19
+
20
+ ### Chores
21
+
22
+ * update local settings permissions ([f97f6f6](https://github.com/GuoSirius/change-image-suffix/commit/f97f6f6a229fc7401291c4a1175f2184bf5e7ec1))
23
+
5
24
  ### [2.1.10](https://github.com/GuoSirius/change-image-suffix/compare/v2.1.9...v2.1.10) (2026-05-25)
6
25
 
7
26
 
package/dist/index.js CHANGED
@@ -56,52 +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,不做 if exist/set 路径操作,避免 cmd.exe 对 () & 等字符解析出错 ──
61
- // %1 = 格式, %2 %3 ... = Windows 传入的文件/目录路径
62
- const batContent = `
63
- @echo off
64
- chcp 65001 >nul
65
- setlocal
66
-
67
- REM Find cis command in PATH (take first match via goto)
68
- set "CIS_CMD="
69
- for /f "delims=" %%c in ('where cis.cmd 2^>nul') do set "CIS_CMD=%%c" & goto :cis_found
70
- :cis_found
71
- if "%CIS_CMD%"=="" for /f "delims=" %%c in ('where cis 2^>nul') do set "CIS_CMD=%%c" & goto :cis_found
72
- if "%CIS_CMD%"=="" (
73
- echo [change-image-suffix] cis command not found. Run: npm install -g change-image-suffix
74
- pause
75
- exit /b 1
76
- )
77
-
78
- if "%~1"=="" (
79
- echo [change-image-suffix] Error: No format specified.
80
- pause
81
- exit /b 1
82
- )
83
-
84
- set "format=%~1"
85
- shift
86
-
87
- if "%~1"=="" (
88
- echo [change-image-suffix] No files or directories to process.
89
- pause
90
- exit /b 1
91
- )
92
-
93
- :process
94
- if "%~1"=="" goto :done
95
- "%CIS_CMD%" -t %format% "%~1"
96
- shift
97
- goto :process
98
-
99
- :done
100
- pause
101
- endlocal
102
- goto :eof
103
- `;
104
- 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');
105
62
  // ── 格式列表(webp 排第一,其他按常见程度排序)──
106
63
  const formats = [
107
64
  { verb: 'webp', label: '🌀 WebP' },
@@ -110,43 +67,28 @@ goto :eof
110
67
  { verb: 'avif', label: '📺 AVIF' },
111
68
  { verb: 'tiff', label: '📋 TIFF' },
112
69
  ];
113
- // ── 使用 ExtendedSubCommandsKey 方式 ──
114
- // 文件右键和目录右键均使用 bat 脚本,混合选择时自动分类文件和目录
70
+ // ── 使用 ExtendedSubCommandsKey,直接调用 node.exe(无 bat 中转)──
115
71
  const menuBases = [
116
- { base: 'HKCU\\Software\\Classes\\Directory\\Background\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis', arg: '-p "%V"' },
117
- { base: 'HKCU\\Software\\Classes\\Directory\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis_dir', useBat: true },
118
- { base: 'HKCU\\Software\\Classes\\*\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis_file', useBat: true },
72
+ { base: 'HKCU\\Software\\Classes\\Directory\\Background\\shell\\cis', subMenu: 'Directory\\ContextMenus\\cis', arg: '-p .' },
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' },
119
75
  ];
120
- // 1. 注册公共子菜单(每种菜单类型独立注册)
76
+ // 1. 注册格式子菜单
121
77
  const REG_ROOT = 'HKCU\\Software\\Classes\\';
122
78
  for (const menu of menuBases) {
123
79
  for (const fmt of formats) {
124
80
  const shellKey = `${REG_ROOT}${menu.subMenu}\\shell\\${fmt.verb}`;
125
- let cmd;
126
- if (menu.useBat) {
127
- // bat 脚本接收格式参数,%1 为 Windows 传入的文件/目录路径
128
- cmd = `"${batPath}" ${fmt.verb} "%1"`;
129
- }
130
- else {
131
- cmd = `"${cisCmd}" -t ${fmt.verb} ${menu.arg}`;
132
- }
81
+ const cmd = `"${nodeExe}" "${scriptPath}" --pause -t ${fmt.verb} ${menu.arg}`;
133
82
  execSync(`reg add "${shellKey}" /ve /d "${fmt.label}" /f`, { stdio: 'ignore' });
134
83
  execSync(`reg add "${shellKey}" /v Icon /d "${iconPath}" /f`, { stdio: 'ignore' });
135
84
  execSync(`reg add "${shellKey}\\command" /ve /d "${cmd}" /f`, { stdio: 'ignore' });
136
85
  }
137
86
  }
138
- // 2. 注册主菜单项(使用 ExtendedSubCommandsKey 关联各自的子菜单)
87
+ // 2. 注册主菜单项
139
88
  for (const menu of menuBases) {
140
89
  execSync(`reg add "${menu.base}" /ve /d "🖼 转换图片 (cis)" /f`, { stdio: 'ignore' });
141
90
  execSync(`reg add "${menu.base}" /v Icon /d "${iconPath}" /f`, { stdio: 'ignore' });
142
91
  execSync(`reg add "${menu.base}" /v ExtendedSubCommandsKey /d "${menu.subMenu}" /f`, { stdio: 'ignore' });
143
- // 使用 bat 的菜单(文件右键和目录右键):设置 command 接收文件路径 %1
144
- // Windows 会将父命令收到的 %1 自动传递给子命令
145
- if (menu.useBat) {
146
- execSync(`reg add "${menu.base}\\command" /ve /d "cmd /c echo %1 > nul" /f`, { stdio: 'ignore' });
147
- // 注意:不添加 AppliesTo 限制,让菜单始终显示
148
- // bat 脚本会检查文件扩展名,自动忽略非图片文件
149
- }
150
92
  }
151
93
  // 写入版本标记,用于检测 npm update 后自动刷新菜单
152
94
  const versionFile = path.join(appDataDir, 'version.json');
@@ -657,61 +599,73 @@ async function main() {
657
599
  const dirResult = await processDirs(options.multiPaths);
658
600
  console.log('\n----------------------------------------');
659
601
  console.log(`📊 转换完成!成功: ${fileResult.success + dirResult.success}, 失败: ${fileResult.fail + dirResult.fail}\n`);
660
- return;
661
602
  }
662
- // ─── 单/多文件模式 ───
663
- if (options.multiFiles && options.multiFiles.length > 0) {
603
+ else if (options.multiFiles && options.multiFiles.length > 0) {
604
+ // ─── 单/多文件模式 ───
664
605
  const result = await processFiles(options.multiFiles, '图片转换工具');
665
606
  console.log('\n----------------------------------------\n');
666
607
  console.log(`📊 转换完成!成功: ${result.success}, 失败: ${result.fail}\n`);
667
- return;
668
608
  }
669
- // ─── 多路径模式 ───
670
- if (options.multiPaths) {
609
+ else if (options.multiPaths) {
610
+ // ─── 多路径模式 ───
671
611
  console.log(`\n🖼️ change-image-suffix - 批量转换工具\n`);
672
612
  const result = await processDirs(options.multiPaths);
673
613
  console.log('\n----------------------------------------');
674
614
  console.log(`📊 转换完成!成功: ${result.success}, 失败: ${result.fail}\n`);
675
- return;
676
615
  }
677
- // ─── 目录批量模式 ───
678
- console.log(`📂 目录: ${options.directory}`);
679
- console.log(`🔁 递归: ${options.recursive ? `是 (深度: ${options.maxDepth === Infinity ? '无限制' : options.maxDepth})` : '否'}`);
680
- console.log(`📄 后缀: ${options.extensions.join(', ')}`);
681
- console.log(`🎯 目标格式: ${options.targetFormat}`);
682
- console.log('\n----------------------------------------\n');
683
- const files = getAllFiles(options.directory, options.extensions, options.recursive, 0, options.maxDepth, options.targetFormat);
684
- if (files.length === 0) {
685
- console.log('✅ 没有找到需要转换的图片文件。');
686
- return;
687
- }
688
- console.log(`📋 找到 ${files.length} 个文件,准备开始转换...\n`);
689
- let successCount = 0;
690
- let failCount = 0;
691
- const results = [];
692
- for (const file of files) {
693
- const relativePath = path.relative(options.directory, file);
694
- process.stdout.write(` 处理中: ${relativePath} ... `);
695
- const result = await convertImage(file, options.targetFormat, files);
696
- if (result.success) {
697
- const outputRelativePath = path.relative(options.directory, result.outputPath);
698
- console.log(`✅ -> ${outputRelativePath}`);
699
- results.push({ input: file, output: result.outputPath, status: 'success' });
700
- 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('✅ 没有找到需要转换的图片文件。');
701
626
  }
702
627
  else {
703
- console.log(`❌ 失败 (${result.error})`);
704
- results.push({ input: file, output: '', status: 'fail' });
705
- 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
+ }
706
656
  }
707
657
  }
708
- console.log('\n----------------------------------------');
709
- console.log(`\n📊 转换完成!成功: ${successCount}, 失败: ${failCount}\n`);
710
- if (failCount > 0) {
711
- console.log('❌ 失败的文件:');
712
- for (const r of results.filter(x => x.status === 'fail')) {
713
- console.log(` - ${r.input}`);
714
- }
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
+ });
715
669
  }
716
670
  }
717
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.10",
3
+ "version": "2.1.12",
4
4
  "type": "module",
5
5
  "description": "批量转换图片格式的CLI工具,支持递归搜索、深度限制、指定后缀、Windows右键菜单等功能",
6
6
  "main": "dist/index.js",