juice-email-cli 2.1.13 → 2.1.15
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/package.json +1 -1
- package/src/context-menu.js +147 -117
package/package.json
CHANGED
package/src/context-menu.js
CHANGED
|
@@ -3,15 +3,23 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Windows 右键菜单注册 / 取消注册
|
|
5
5
|
*
|
|
6
|
-
* 使用
|
|
6
|
+
* 使用 ExtendedSubCommandsKey 实现级联菜单(参考 WinRAR 和 Windows Defender 的实现)。
|
|
7
|
+
* 子命令存储在 Classes 根下的共享容器中,各文件类型父菜单通过 ExtendedSubCommandsKey 引用。
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* 与旧版 SubCommands + CommandStore 方案的区别:
|
|
10
|
+
* - 不依赖 CommandStore(HKCU CommandStore 在某些 Windows 版本下不被正确解析)
|
|
11
|
+
* - ExtendedSubCommandsKey 指向 Classes 根下的相对路径,子命令内联存储
|
|
12
|
+
*
|
|
13
|
+
* 菜单结构(按文件类型区分):
|
|
14
|
+
* .html / .htm:
|
|
10
15
|
* 📧 用 juice 生成邮件 HTML
|
|
11
16
|
* ├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)
|
|
12
17
|
* ├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)
|
|
13
|
-
* ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)
|
|
14
18
|
* └── 📂 打开 PowerShell (可选)
|
|
19
|
+
*
|
|
20
|
+
* .yaml / .yml:
|
|
21
|
+
* 📧 用 juice 生成邮件 HTML
|
|
22
|
+
* └── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)
|
|
15
23
|
*/
|
|
16
24
|
|
|
17
25
|
const { spawnSync } = require('child_process');
|
|
@@ -37,9 +45,6 @@ function getIconPath() {
|
|
|
37
45
|
|
|
38
46
|
const isWindows = process.platform === 'win32';
|
|
39
47
|
|
|
40
|
-
/**
|
|
41
|
-
* 执行 reg add — 使用 spawnSync 直接调用 reg.exe,避免 cmd.exe 解析特殊字符
|
|
42
|
-
*/
|
|
43
48
|
function regAdd(key, valueName, type, data) {
|
|
44
49
|
if (!isWindows) return false;
|
|
45
50
|
const args = ['add', key, '/f'];
|
|
@@ -60,38 +65,12 @@ function regAdd(key, valueName, type, data) {
|
|
|
60
65
|
return true;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
/**
|
|
64
|
-
* 执行 reg delete — 键不存在时静默忽略
|
|
65
|
-
*/
|
|
66
68
|
function regDelete(key) {
|
|
67
69
|
if (!isWindows) return false;
|
|
68
70
|
const result = spawnSync('reg', ['delete', key, '/f'], { stdio: 'pipe' });
|
|
69
71
|
return result.status === 0;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
/**
|
|
73
|
-
* 清理旧版本残留在 HKLM 的注册表项(v1 使用 HKCR/HKLM,需要管理员权限)
|
|
74
|
-
*
|
|
75
|
-
* 旧版本用管理员权限安装时写入 HKLM,升级到 HKCU 版本后这些残留不会被自动清理。
|
|
76
|
-
* Windows 合并 HKLM + HKCU 注册表视图时,HKLM 里的旧菜单名可能覆盖 HKCU 的新菜单名,
|
|
77
|
-
* 导致用户更新后重启电脑仍然看到旧版菜单。
|
|
78
|
-
*
|
|
79
|
-
* 没有管理员权限时 reg delete 静默失败,不影响后续 HKCU 写入。
|
|
80
|
-
*/
|
|
81
|
-
function cleanLegacyHklmEntries() {
|
|
82
|
-
if (!isWindows) return;
|
|
83
|
-
|
|
84
|
-
// 清理旧版父菜单(HKLM\Software\Classes\SystemFileAssociations\.*\shell\JuiceEmail)
|
|
85
|
-
for (const root of LEGACY_HKLM_ROOTS) {
|
|
86
|
-
regDelete(`${root}\\${PARENT_KEY_NAME}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// 清理旧版子命令(HKLM\...\CommandStore\shell\JuiceEmail.*)
|
|
90
|
-
for (const name of Object.values(SUBCMDS)) {
|
|
91
|
-
regDelete(`${LEGACY_HKLM_SUBCMD_SPACE}\\${name}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
74
|
// ─── 菜单结构常量 ─────────────────────────────────────────────────────────────
|
|
96
75
|
|
|
97
76
|
// 使用 HKCU,无需管理员权限
|
|
@@ -109,11 +88,23 @@ const YAML_ROOTS = [
|
|
|
109
88
|
`${HKCU_SHELL}\\SystemFileAssociations\\.yml\\shell`,
|
|
110
89
|
];
|
|
111
90
|
|
|
112
|
-
//
|
|
113
|
-
const
|
|
91
|
+
// 子命令名称常量
|
|
92
|
+
const SUBCMDS = {
|
|
93
|
+
generate: 'JuiceEmail.Generate',
|
|
94
|
+
snippet: 'JuiceEmail.Snippet',
|
|
95
|
+
config: 'JuiceEmail.WithConfig',
|
|
96
|
+
pwsh: 'JuiceEmail.OpenPwsh',
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const PARENT_KEY_NAME = 'JuiceEmail';
|
|
100
|
+
|
|
101
|
+
// ExtendedSubCommandsKey 指向的容器路径(相对于 HKCU\Software\Classes)
|
|
102
|
+
const SUB_CMDS_CONTAINER_HTML = 'JuiceEmail.SubCommands';
|
|
103
|
+
const SUB_CMDS_CONTAINER_YAML = 'JuiceEmail.SubCommands.Yaml';
|
|
114
104
|
|
|
115
|
-
//
|
|
116
|
-
|
|
105
|
+
// ─── 旧版残留清理 ─────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
// 旧版 HKLM 父菜单路径(v1 使用 HKCR/HKLM,需管理员权限写入)
|
|
117
108
|
const LEGACY_HKLM_ROOTS = [
|
|
118
109
|
'HKEY_LOCAL_MACHINE\\Software\\Classes\\SystemFileAssociations\\.html\\shell',
|
|
119
110
|
'HKEY_LOCAL_MACHINE\\Software\\Classes\\SystemFileAssociations\\.htm\\shell',
|
|
@@ -121,17 +112,33 @@ const LEGACY_HKLM_ROOTS = [
|
|
|
121
112
|
'HKEY_LOCAL_MACHINE\\Software\\Classes\\SystemFileAssociations\\.yml\\shell',
|
|
122
113
|
];
|
|
123
114
|
|
|
124
|
-
|
|
115
|
+
// 旧版 CommandStore 子命令路径(v1-v2.1.12 使用 SubCommands + CommandStore 方案)
|
|
116
|
+
const LEGACY_HKCU_CMDSTORE = 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell';
|
|
117
|
+
const LEGACY_HKLM_CMDSTORE = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell';
|
|
125
118
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
119
|
+
/**
|
|
120
|
+
* 清理旧版本残留在 HKLM 的父菜单(v1 使用 HKCR/HKLM,需要管理员权限)
|
|
121
|
+
* 没有管理员权限时静默失败,不影响后续 HKCU 写入
|
|
122
|
+
*/
|
|
123
|
+
function cleanLegacyHklmParents() {
|
|
124
|
+
if (!isWindows) return;
|
|
125
|
+
for (const root of LEGACY_HKLM_ROOTS) {
|
|
126
|
+
regDelete(`${root}\\${PARENT_KEY_NAME}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
133
129
|
|
|
134
|
-
|
|
130
|
+
/**
|
|
131
|
+
* 清理旧版本 CommandStore 子命令残留(HKCU + HKLM)
|
|
132
|
+
*/
|
|
133
|
+
function cleanLegacyCommandStore() {
|
|
134
|
+
if (!isWindows) return;
|
|
135
|
+
for (const name of Object.values(SUBCMDS)) {
|
|
136
|
+
regDelete(`${LEGACY_HKCU_CMDSTORE}\\${name}`);
|
|
137
|
+
regDelete(`${LEGACY_HKLM_CMDSTORE}\\${name}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── PowerShell 包装 ──────────────────────────────────────────────────────────
|
|
135
142
|
|
|
136
143
|
/**
|
|
137
144
|
* 为交互式命令包一层 PowerShell:
|
|
@@ -141,7 +148,7 @@ const PARENT_KEY_NAME = 'JuiceEmail';
|
|
|
141
148
|
function wrapInteractive(nodePath, scriptPath, cliArgs) {
|
|
142
149
|
const node = nodePath.replace(/'/g, "''");
|
|
143
150
|
const script = scriptPath.replace(/'/g, "''");
|
|
144
|
-
// %1 由 Windows 在 CreateProcess 前展开为实际文件路径
|
|
151
|
+
// %1 由 Windows Explorer 在 CreateProcess 前展开为实际文件路径
|
|
145
152
|
const ps = [
|
|
146
153
|
`& '${node}' '${script}' ${cliArgs}`,
|
|
147
154
|
`if ($LASTEXITCODE) {`,
|
|
@@ -155,6 +162,63 @@ function wrapInteractive(nodePath, scriptPath, cliArgs) {
|
|
|
155
162
|
return `powershell.exe -Command "${ps}"`;
|
|
156
163
|
}
|
|
157
164
|
|
|
165
|
+
// ─── 工具:查找 PowerShell 7 ─────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
function resolvePwsh() {
|
|
168
|
+
const candidates = [
|
|
169
|
+
'C:\\Program Files\\PowerShell\\7\\pwsh.exe',
|
|
170
|
+
'C:\\Program Files (x86)\\PowerShell\\7\\pwsh.exe',
|
|
171
|
+
path.join(process.env['LOCALAPPDATA'] || '', 'Microsoft', 'PowerShell', 'pwsh.exe'),
|
|
172
|
+
];
|
|
173
|
+
for (const p of candidates) {
|
|
174
|
+
if (fs.existsSync(p)) return p;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const result = spawnSync('where', ['pwsh'], { stdio: 'pipe' });
|
|
178
|
+
const line = result.stdout.toString().trim().split('\n')[0].trim();
|
|
179
|
+
if (line && fs.existsSync(line)) return line;
|
|
180
|
+
} catch (_) {}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ─── 注册子命令到容器 ────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 向指定容器注册子命令
|
|
188
|
+
* @param {string} containerPath - 容器完整注册表路径
|
|
189
|
+
* @param {'html'|'yaml'} kind - 子命令集合类型
|
|
190
|
+
*/
|
|
191
|
+
function registerSubCommands(containerPath, kind, nodePath, scriptPath, iconPath, pwshPath) {
|
|
192
|
+
if (kind === 'html') {
|
|
193
|
+
// HTML:作为模板生成
|
|
194
|
+
const genKey = `${containerPath}\\shell\\${SUBCMDS.generate}`;
|
|
195
|
+
regAdd(genKey, 'MUIVerb', 'REG_SZ', '📄 作为模板,生成邮件 HTML');
|
|
196
|
+
regAdd(genKey, 'Icon', 'REG_SZ', iconPath);
|
|
197
|
+
regAdd(`${genKey}\\command`, '', 'REG_SZ', `"${nodePath}" "${scriptPath}" -f %1`);
|
|
198
|
+
|
|
199
|
+
// HTML:作为片段拼接
|
|
200
|
+
const snipKey = `${containerPath}\\shell\\${SUBCMDS.snippet}`;
|
|
201
|
+
regAdd(snipKey, 'MUIVerb', 'REG_SZ', '🧩 作为片段,拼接邮件 HTML');
|
|
202
|
+
regAdd(snipKey, 'Icon', 'REG_SZ', iconPath);
|
|
203
|
+
regAdd(`${snipKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, '-s %1'));
|
|
204
|
+
} else {
|
|
205
|
+
// YAML:作为配置拼接
|
|
206
|
+
const cfgKey = `${containerPath}\\shell\\${SUBCMDS.config}`;
|
|
207
|
+
regAdd(cfgKey, 'MUIVerb', 'REG_SZ', '⚙️ 作为配置,拼接邮件 HTML');
|
|
208
|
+
regAdd(cfgKey, 'Icon', 'REG_SZ', iconPath);
|
|
209
|
+
regAdd(`${cfgKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, '-c %1'));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// pwsh — 始终显示(可选)
|
|
213
|
+
if (pwshPath) {
|
|
214
|
+
const pwshKey = `${containerPath}\\shell\\${SUBCMDS.pwsh}`;
|
|
215
|
+
regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 打开 PowerShell');
|
|
216
|
+
regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
|
|
217
|
+
regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
|
|
218
|
+
`"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath (Split-Path '%1')"`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
158
222
|
// ─── 注册 ─────────────────────────────────────────────────────────────────────
|
|
159
223
|
|
|
160
224
|
async function registerContextMenu() {
|
|
@@ -165,64 +229,45 @@ async function registerContextMenu() {
|
|
|
165
229
|
|
|
166
230
|
console.log(chalk.cyan('\n 注册 juice 右键菜单(当前用户,无需管理员权限)...\n'));
|
|
167
231
|
|
|
168
|
-
//
|
|
169
|
-
|
|
232
|
+
// 清理所有旧版残留,避免新旧方案冲突
|
|
233
|
+
cleanLegacyHklmParents();
|
|
234
|
+
cleanLegacyCommandStore();
|
|
170
235
|
|
|
171
236
|
const nodePath = getNodePath();
|
|
172
237
|
const scriptPath = getJuiceScript();
|
|
173
238
|
const iconPath = getIconPath();
|
|
239
|
+
const pwshPath = resolvePwsh();
|
|
174
240
|
|
|
175
241
|
let ok = true;
|
|
176
242
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const snippetCmd = wrapInteractive(nodePath, scriptPath, '-s %1');
|
|
185
|
-
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.snippet}`, '', 'REG_SZ', '🧩 作为片段,拼接邮件 HTML') && ok;
|
|
186
|
-
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.snippet}`, 'Icon', 'REG_SZ', iconPath);
|
|
187
|
-
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.snippet}\\command`, '', 'REG_SZ', snippetCmd);
|
|
188
|
-
|
|
189
|
-
// ── 子命令 3:配置文件模式 - 交互式生成(交互,需要终端)──────────────────
|
|
190
|
-
const configCmd = wrapInteractive(nodePath, scriptPath, '-c %1');
|
|
191
|
-
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.config}`, '', 'REG_SZ', '⚙️ 作为配置,拼接邮件 HTML') && ok;
|
|
192
|
-
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.config}`, 'Icon', 'REG_SZ', iconPath);
|
|
193
|
-
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.config}\\command`, '', 'REG_SZ', configCmd);
|
|
194
|
-
|
|
195
|
-
// ── 子命令 4:在文件目录打开 pwsh(可选)──────────────────────────────────
|
|
196
|
-
const pwshPath = resolvePwsh();
|
|
197
|
-
if (pwshPath) {
|
|
198
|
-
const pwshCmd = `"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath (Split-Path '%1')"`;
|
|
199
|
-
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.pwsh}`, '', 'REG_SZ', '📂 打开 PowerShell') && ok;
|
|
200
|
-
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.pwsh}`, 'Icon', 'REG_SZ', pwshPath);
|
|
201
|
-
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.pwsh}\\command`, '', 'REG_SZ', pwshCmd);
|
|
202
|
-
}
|
|
243
|
+
// 先删除旧容器再重建,避免注册残留旧子命令(如旧版 HTML 容器的 WithConfig)
|
|
244
|
+
regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`);
|
|
245
|
+
regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`);
|
|
246
|
+
|
|
247
|
+
// HTML 容器:generate + snippet + pwsh
|
|
248
|
+
const htmlContainer = `${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`;
|
|
249
|
+
registerSubCommands(htmlContainer, 'html', nodePath, scriptPath, iconPath, pwshPath);
|
|
203
250
|
|
|
204
|
-
//
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
: `${SUBCMDS.generate};${SUBCMDS.snippet};${SUBCMDS.config}`;
|
|
251
|
+
// YAML 容器:config + pwsh
|
|
252
|
+
const yamlContainer = `${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`;
|
|
253
|
+
registerSubCommands(yamlContainer, 'yaml', nodePath, scriptPath, iconPath, pwshPath);
|
|
208
254
|
|
|
209
|
-
// .html / .htm
|
|
255
|
+
// .html / .htm → 引用 HTML 容器
|
|
210
256
|
for (const root of HTML_ROOTS) {
|
|
211
257
|
const parentKey = `${root}\\${PARENT_KEY_NAME}`;
|
|
212
258
|
ok = regAdd(parentKey, 'MUIVerb', 'REG_SZ', '📧 用 juice 生成邮件 HTML') && ok;
|
|
213
259
|
regAdd(parentKey, 'Icon', 'REG_SZ', iconPath);
|
|
214
|
-
regAdd(parentKey, '
|
|
260
|
+
regAdd(parentKey, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_HTML);
|
|
215
261
|
}
|
|
216
262
|
|
|
217
|
-
// .yaml / .yml
|
|
263
|
+
// .yaml / .yml → 引用 YAML 容器
|
|
218
264
|
for (const root of YAML_ROOTS) {
|
|
219
265
|
const parentKey = `${root}\\${PARENT_KEY_NAME}`;
|
|
220
266
|
ok = regAdd(parentKey, 'MUIVerb', 'REG_SZ', '📧 用 juice 生成邮件 HTML') && ok;
|
|
221
267
|
regAdd(parentKey, 'Icon', 'REG_SZ', iconPath);
|
|
222
|
-
regAdd(parentKey, '
|
|
268
|
+
regAdd(parentKey, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_YAML);
|
|
223
269
|
}
|
|
224
270
|
|
|
225
|
-
// ── 输出 ──────────────────────────────────────────────────────────────────
|
|
226
271
|
if (!ok) {
|
|
227
272
|
console.log(chalk.yellow(' ⚠ 部分注册表项写入失败,右键菜单可能不完整。\n'));
|
|
228
273
|
}
|
|
@@ -230,12 +275,17 @@ async function registerContextMenu() {
|
|
|
230
275
|
console.log(
|
|
231
276
|
'\n' +
|
|
232
277
|
chalk.green(' ✔ 右键菜单注册完成!') + '\n\n' +
|
|
233
|
-
` ${chalk.bold('.html / .htm
|
|
278
|
+
` ${chalk.bold('.html / .htm')} 文件右键:\n` +
|
|
234
279
|
` ${chalk.bold('📧 用 juice 生成邮件 HTML')}\n` +
|
|
235
280
|
` ├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)\n` +
|
|
236
281
|
` ├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)\n` +
|
|
237
|
-
` ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)\n` +
|
|
238
282
|
(pwshPath ? ` └── 📂 打开 PowerShell\n` : '') +
|
|
283
|
+
`\n ${chalk.bold('.yaml / .yml')} 文件右键:\n` +
|
|
284
|
+
` ${chalk.bold('📧 用 juice 生成邮件 HTML')}\n` +
|
|
285
|
+
(pwshPath
|
|
286
|
+
? ` ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)\n` +
|
|
287
|
+
` └── 📂 打开 PowerShell\n`
|
|
288
|
+
: ` └── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)\n`) +
|
|
239
289
|
'\n' +
|
|
240
290
|
chalk.gray(' 注意:如菜单未出现,请重启文件资源管理器(explorer.exe)。\n')
|
|
241
291
|
);
|
|
@@ -253,23 +303,22 @@ async function unregisterContextMenu() {
|
|
|
253
303
|
|
|
254
304
|
let removed = 0;
|
|
255
305
|
|
|
256
|
-
// 清理 HKCU
|
|
257
|
-
|
|
306
|
+
// 清理 HKCU 父菜单(4 个文件类型)
|
|
307
|
+
const allRoots = [...HTML_ROOTS, ...YAML_ROOTS];
|
|
308
|
+
for (const root of allRoots) {
|
|
258
309
|
if (regDelete(`${root}\\${PARENT_KEY_NAME}`)) removed++;
|
|
259
310
|
}
|
|
260
311
|
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
312
|
+
// 清理两个子命令容器
|
|
313
|
+
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`)) removed++;
|
|
314
|
+
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`)) removed++;
|
|
265
315
|
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
regDelete(`${SUBCMD_SPACE}\\${name}`);
|
|
269
|
-
}
|
|
316
|
+
// 清理旧版共享容器(v2.1.14 之前只有一个容器)
|
|
317
|
+
regDelete(`${HKCU_SHELL}\\JuiceEmail.SubCommands`);
|
|
270
318
|
|
|
271
|
-
//
|
|
272
|
-
|
|
319
|
+
// 清理旧版残留(HKLM 父菜单 + CommandStore)
|
|
320
|
+
cleanLegacyHklmParents();
|
|
321
|
+
cleanLegacyCommandStore();
|
|
273
322
|
|
|
274
323
|
if (removed > 0) {
|
|
275
324
|
console.log(chalk.green(` ✔ 已移除 ${removed} 个右键菜单项。\n`));
|
|
@@ -278,23 +327,4 @@ async function unregisterContextMenu() {
|
|
|
278
327
|
}
|
|
279
328
|
}
|
|
280
329
|
|
|
281
|
-
// ─── 工具:查找 PowerShell 7 ─────────────────────────────────────────────────
|
|
282
|
-
|
|
283
|
-
function resolvePwsh() {
|
|
284
|
-
const candidates = [
|
|
285
|
-
'C:\\Program Files\\PowerShell\\7\\pwsh.exe',
|
|
286
|
-
'C:\\Program Files (x86)\\PowerShell\\7\\pwsh.exe',
|
|
287
|
-
path.join(process.env['LOCALAPPDATA'] || '', 'Microsoft', 'PowerShell', 'pwsh.exe'),
|
|
288
|
-
];
|
|
289
|
-
for (const p of candidates) {
|
|
290
|
-
if (fs.existsSync(p)) return p;
|
|
291
|
-
}
|
|
292
|
-
try {
|
|
293
|
-
const result = spawnSync('where', ['pwsh'], { stdio: 'pipe' });
|
|
294
|
-
const line = result.stdout.toString().trim().split('\n')[0].trim();
|
|
295
|
-
if (line && fs.existsSync(line)) return line;
|
|
296
|
-
} catch (_) {}
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
330
|
module.exports = { registerContextMenu, unregisterContextMenu };
|