juice-email-cli 2.3.3 → 2.3.5
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/README.md +26 -14
- package/bin/juice.js +8 -4
- package/package.json +1 -1
- package/src/context-menu.js +149 -147
- package/src/init.js +103 -3
- package/src/view.js +44 -23
package/README.md
CHANGED
|
@@ -351,34 +351,46 @@ edm/
|
|
|
351
351
|
|
|
352
352
|
## Windows 右键菜单
|
|
353
353
|
|
|
354
|
-
###
|
|
354
|
+
### .html / .htm 文件右键
|
|
355
355
|
|
|
356
356
|
```
|
|
357
357
|
📧 用 juice 生成邮件 HTML
|
|
358
|
-
├── 📄 作为模板,生成邮件 HTML
|
|
359
|
-
├── 🧩 作为片段,拼接邮件 HTML
|
|
360
|
-
├──
|
|
361
|
-
├──
|
|
358
|
+
├── 📄 作为模板,生成邮件 HTML → juice -f %1(后台执行)
|
|
359
|
+
├── 🧩 作为片段,拼接邮件 HTML → juice -s %1(交互选择模板)
|
|
360
|
+
├── 📋 查看可用资源 → juice view
|
|
361
|
+
├── 📦 拷贝全部资源 → juice init --all
|
|
362
|
+
├── 📥 选择资源拷贝 → juice init
|
|
362
363
|
└── 📂 打开 PowerShell ← 仅已安装 pwsh 时出现
|
|
363
364
|
```
|
|
364
365
|
|
|
365
|
-
###
|
|
366
|
+
### .yaml / .yml 文件右键
|
|
366
367
|
|
|
367
368
|
```
|
|
368
369
|
📧 用 juice 生成邮件 HTML
|
|
369
|
-
├── ⚙️ 作为配置,拼接邮件 HTML
|
|
370
|
-
├──
|
|
371
|
-
├──
|
|
370
|
+
├── ⚙️ 作为配置,拼接邮件 HTML → juice -c %1(交互选择品牌/模板/片段)
|
|
371
|
+
├── 📋 查看可用资源
|
|
372
|
+
├── 📦 拷贝全部资源
|
|
373
|
+
├── 📥 选择资源拷贝
|
|
372
374
|
└── 📂 打开 PowerShell
|
|
373
375
|
```
|
|
374
376
|
|
|
375
|
-
###
|
|
377
|
+
### 文件夹右键
|
|
376
378
|
|
|
377
379
|
```
|
|
378
|
-
📧 juice
|
|
379
|
-
├──
|
|
380
|
-
├── 📦
|
|
381
|
-
├──
|
|
380
|
+
📧 用 juice 生成邮件 HTML
|
|
381
|
+
├── 📋 查看可用资源
|
|
382
|
+
├── 📦 拷贝全部资源
|
|
383
|
+
├── 📥 选择资源拷贝
|
|
384
|
+
└── 📂 在此打开终端
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### 空白处右键
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
📧 用 juice 生成邮件 HTML
|
|
391
|
+
├── 📋 查看可用资源
|
|
392
|
+
├── 📦 拷贝全部资源
|
|
393
|
+
├── 📥 选择资源拷贝
|
|
382
394
|
└── 📂 在此打开终端
|
|
383
395
|
```
|
|
384
396
|
|
package/bin/juice.js
CHANGED
|
@@ -67,11 +67,13 @@ program
|
|
|
67
67
|
juice --install (当前用户,无需管理员)
|
|
68
68
|
juice --uninstall (卸载右键菜单)
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
菜单结构:
|
|
71
71
|
📧 用 juice 生成邮件 HTML
|
|
72
|
-
├── 📄 作为模板,生成邮件 HTML → juice -f
|
|
73
|
-
├── 🧩 作为片段,拼接邮件 HTML → juice -s
|
|
74
|
-
├──
|
|
72
|
+
├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)
|
|
73
|
+
├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)
|
|
74
|
+
├── 📋 查看可用资源 → juice view
|
|
75
|
+
├── 📦 拷贝全部资源 → juice init --all
|
|
76
|
+
├── 📥 选择资源拷贝 → juice init
|
|
75
77
|
└── 📂 打开 PowerShell (仅已安装 pwsh 时出现)
|
|
76
78
|
|
|
77
79
|
更多信息:https://gitee.com/siriussupreme/juice-cli
|
|
@@ -103,6 +105,7 @@ program
|
|
|
103
105
|
.option('-t, --template <file-path>', '仅拷贝模板 HTML 文件')
|
|
104
106
|
.option('-s, --snippet <file-path>', '仅拷贝片段 HTML 文件')
|
|
105
107
|
.option('-c, --config <file-path>', '仅拷贝配置 YAML 文件')
|
|
108
|
+
.option('--all [target]', '拷贝整个 EDM 资源目录到当前或指定目录')
|
|
106
109
|
.action(async (initPath, options) => {
|
|
107
110
|
const { runInitMode } = require('../src/init');
|
|
108
111
|
await runInitMode({
|
|
@@ -110,6 +113,7 @@ program
|
|
|
110
113
|
template: options.template || null,
|
|
111
114
|
snippet: options.snippet || null,
|
|
112
115
|
config: options.config || null,
|
|
116
|
+
all: options.all !== undefined ? (options.all || true) : null,
|
|
113
117
|
});
|
|
114
118
|
});
|
|
115
119
|
|
package/package.json
CHANGED
package/src/context-menu.js
CHANGED
|
@@ -6,20 +6,30 @@
|
|
|
6
6
|
* 使用 ExtendedSubCommandsKey 实现级联菜单(参考 WinRAR 和 Windows Defender 的实现)。
|
|
7
7
|
* 子命令存储在 Classes 根下的共享容器中,各文件类型父菜单通过 ExtendedSubCommandsKey 引用。
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* - 不依赖 CommandStore(HKCU CommandStore 在某些 Windows 版本下不被正确解析)
|
|
11
|
-
* - ExtendedSubCommandsKey 指向 Classes 根下的相对路径,子命令内联存储
|
|
12
|
-
*
|
|
13
|
-
* 菜单结构(按文件类型区分):
|
|
9
|
+
* 菜单结构:
|
|
14
10
|
* .html / .htm:
|
|
15
11
|
* 📧 用 juice 生成邮件 HTML
|
|
16
12
|
* ├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)
|
|
17
13
|
* ├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)
|
|
14
|
+
* ├── 📋 查看可用资源 → juice view
|
|
15
|
+
* ├── 📦 拷贝全部资源 → juice init --all
|
|
16
|
+
* ├── 📥 选择资源拷贝 → juice init
|
|
18
17
|
* └── 📂 打开 PowerShell (可选)
|
|
19
18
|
*
|
|
20
19
|
* .yaml / .yml:
|
|
21
20
|
* 📧 用 juice 生成邮件 HTML
|
|
22
|
-
*
|
|
21
|
+
* ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)
|
|
22
|
+
* ├── 📋 查看可用资源
|
|
23
|
+
* ├── 📦 拷贝全部资源
|
|
24
|
+
* ├── 📥 选择资源拷贝
|
|
25
|
+
* └── 📂 打开 PowerShell (可选)
|
|
26
|
+
*
|
|
27
|
+
* 文件夹 / 空白处:
|
|
28
|
+
* 📧 用 juice 生成邮件 HTML
|
|
29
|
+
* ├── 📋 查看可用资源
|
|
30
|
+
* ├── 📦 拷贝全部资源
|
|
31
|
+
* ├── 📥 选择资源拷贝
|
|
32
|
+
* └── 📂 在此打开终端 (可选)
|
|
23
33
|
*/
|
|
24
34
|
|
|
25
35
|
const { spawnSync } = require('child_process');
|
|
@@ -103,7 +113,6 @@ const SUBCMDS = {
|
|
|
103
113
|
snippet: 'JuiceEmail.Snippet',
|
|
104
114
|
config: 'JuiceEmail.WithConfig',
|
|
105
115
|
viewEdm: 'JuiceEmail.ViewEdm',
|
|
106
|
-
viewEdmInt:'JuiceEmail.ViewEdmInteractive',
|
|
107
116
|
initEdm: 'JuiceEmail.InitEdm',
|
|
108
117
|
pwsh: 'JuiceEmail.OpenPwsh',
|
|
109
118
|
};
|
|
@@ -118,7 +127,6 @@ const SUB_CMDS_CONTAINER_BG = 'JuiceEmail.SubCommands.Bg';
|
|
|
118
127
|
|
|
119
128
|
// ─── 旧版残留清理 ─────────────────────────────────────────────────────────────
|
|
120
129
|
|
|
121
|
-
// 旧版 HKLM 父菜单路径(v1 使用 HKCR/HKLM,需管理员权限写入)
|
|
122
130
|
const LEGACY_HKLM_ROOTS = [
|
|
123
131
|
'HKEY_LOCAL_MACHINE\\Software\\Classes\\SystemFileAssociations\\.html\\shell',
|
|
124
132
|
'HKEY_LOCAL_MACHINE\\Software\\Classes\\SystemFileAssociations\\.htm\\shell',
|
|
@@ -126,14 +134,9 @@ const LEGACY_HKLM_ROOTS = [
|
|
|
126
134
|
'HKEY_LOCAL_MACHINE\\Software\\Classes\\SystemFileAssociations\\.yml\\shell',
|
|
127
135
|
];
|
|
128
136
|
|
|
129
|
-
// 旧版 CommandStore 子命令路径(v1-v2.1.12 使用 SubCommands + CommandStore 方案)
|
|
130
137
|
const LEGACY_HKCU_CMDSTORE = 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell';
|
|
131
138
|
const LEGACY_HKLM_CMDSTORE = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell';
|
|
132
139
|
|
|
133
|
-
/**
|
|
134
|
-
* 清理旧版本残留在 HKLM 的父菜单(v1 使用 HKCR/HKLM,需要管理员权限)
|
|
135
|
-
* 没有管理员权限时静默失败,不影响后续 HKCU 写入
|
|
136
|
-
*/
|
|
137
140
|
function cleanLegacyHklmParents() {
|
|
138
141
|
if (!isWindows) return;
|
|
139
142
|
for (const root of LEGACY_HKLM_ROOTS) {
|
|
@@ -141,28 +144,24 @@ function cleanLegacyHklmParents() {
|
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
/**
|
|
145
|
-
* 清理旧版本 CommandStore 子命令残留(HKCU + HKLM)
|
|
146
|
-
*/
|
|
147
147
|
function cleanLegacyCommandStore() {
|
|
148
148
|
if (!isWindows) return;
|
|
149
149
|
for (const name of Object.values(SUBCMDS)) {
|
|
150
150
|
regDelete(`${LEGACY_HKCU_CMDSTORE}\\${name}`);
|
|
151
151
|
regDelete(`${LEGACY_HKLM_CMDSTORE}\\${name}`);
|
|
152
152
|
}
|
|
153
|
+
// Also clean up old Dir CommandStore entries from v2.3.x SubCommands attempt
|
|
154
|
+
for (const name of ['JuiceEmailDir.View', 'JuiceEmailDir.CopyAll', 'JuiceEmailDir.CopyPick', 'JuiceEmailDir.Pwsh']) {
|
|
155
|
+
regDelete(`${LEGACY_HKCU_CMDSTORE}\\${name}`);
|
|
156
|
+
regDelete(`${LEGACY_HKLM_CMDSTORE}\\${name}`);
|
|
157
|
+
}
|
|
153
158
|
}
|
|
154
159
|
|
|
155
160
|
// ─── PowerShell 包装 ──────────────────────────────────────────────────────────
|
|
156
161
|
|
|
157
|
-
/**
|
|
158
|
-
* 为交互式命令包一层 PowerShell:
|
|
159
|
-
* - 成功:窗口自动关闭
|
|
160
|
-
* - 失败:窗口保持开启,显示重新执行的命令
|
|
161
|
-
*/
|
|
162
162
|
function wrapInteractive(nodePath, scriptPath, cliArgs) {
|
|
163
163
|
const node = nodePath.replace(/'/g, "''");
|
|
164
164
|
const script = scriptPath.replace(/'/g, "''");
|
|
165
|
-
// %1 由 Windows Explorer 在 CreateProcess 前展开为实际文件路径
|
|
166
165
|
const ps = [
|
|
167
166
|
`& '${node}' '${script}' ${cliArgs}`,
|
|
168
167
|
`if ($LASTEXITCODE) {`,
|
|
@@ -200,115 +199,105 @@ function resolvePwsh() {
|
|
|
200
199
|
/**
|
|
201
200
|
* 向指定容器注册子命令
|
|
202
201
|
* @param {string} containerPath - 容器完整注册表路径
|
|
203
|
-
* @param {'html'|'yaml'} kind - 子命令集合类型
|
|
202
|
+
* @param {'html'|'yaml'|'dir'|'bg'} kind - 子命令集合类型
|
|
204
203
|
*/
|
|
205
204
|
function registerSubCommands(containerPath, kind, nodePath, scriptPath, iconPath, pwshPath) {
|
|
205
|
+
const node = nodePath.replace(/'/g, "''");
|
|
206
|
+
const script = scriptPath.replace(/'/g, "''");
|
|
207
|
+
|
|
208
|
+
// Shared resource commands (added to all containers)
|
|
209
|
+
function regResourceCmds(dirPrefix) {
|
|
210
|
+
const cd = dirPrefix || '';
|
|
211
|
+
|
|
212
|
+
// 📋 查看可用资源
|
|
213
|
+
const vKey = `${containerPath}\\shell\\JuiceEmail.ViewRes`;
|
|
214
|
+
regAdd(vKey, 'MUIVerb', 'REG_SZ', '📋 查看可用资源');
|
|
215
|
+
regAdd(vKey, 'Icon', 'REG_SZ', iconPath);
|
|
216
|
+
const vPs = `${cd}& '${node}' '${script}' view; Write-Host ''; Read-Host 'Press Enter to close'`;
|
|
217
|
+
regAdd(`${vKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${vPs}"`);
|
|
218
|
+
|
|
219
|
+
// 📦 拷贝全部资源
|
|
220
|
+
const aKey = `${containerPath}\\shell\\JuiceEmail.CopyAll`;
|
|
221
|
+
regAdd(aKey, 'MUIVerb', 'REG_SZ', '📦 拷贝全部资源');
|
|
222
|
+
regAdd(aKey, 'Icon', 'REG_SZ', iconPath);
|
|
223
|
+
const aPs = `${cd}& '${node}' '${script}' init --all .; Write-Host ''; Read-Host 'Press Enter to close'`;
|
|
224
|
+
regAdd(`${aKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${aPs}"`);
|
|
225
|
+
|
|
226
|
+
// 📥 选择资源拷贝
|
|
227
|
+
const pKey = `${containerPath}\\shell\\JuiceEmail.CopyPick`;
|
|
228
|
+
regAdd(pKey, 'MUIVerb', 'REG_SZ', '📥 选择资源拷贝');
|
|
229
|
+
regAdd(pKey, 'Icon', 'REG_SZ', iconPath);
|
|
230
|
+
const pPs = `${cd}& '${node}' '${script}' init; Write-Host ''; Read-Host 'Press Enter to close'`;
|
|
231
|
+
regAdd(`${pKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${pPs}"`);
|
|
232
|
+
}
|
|
233
|
+
|
|
206
234
|
if (kind === 'html') {
|
|
207
|
-
// HTML:作为模板生成
|
|
208
235
|
const genKey = `${containerPath}\\shell\\${SUBCMDS.generate}`;
|
|
209
236
|
regAdd(genKey, 'MUIVerb', 'REG_SZ', '📄 作为模板,生成邮件 HTML');
|
|
210
237
|
regAdd(genKey, 'Icon', 'REG_SZ', iconPath);
|
|
211
238
|
regAdd(`${genKey}\\command`, '', 'REG_SZ', `"${nodePath}" "${scriptPath}" -f %1`);
|
|
212
239
|
|
|
213
|
-
// HTML:作为片段拼接
|
|
214
240
|
const snipKey = `${containerPath}\\shell\\${SUBCMDS.snippet}`;
|
|
215
241
|
regAdd(snipKey, 'MUIVerb', 'REG_SZ', '🧩 作为片段,拼接邮件 HTML');
|
|
216
242
|
regAdd(snipKey, 'Icon', 'REG_SZ', iconPath);
|
|
217
243
|
regAdd(`${snipKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, '-s %1'));
|
|
218
|
-
|
|
219
|
-
|
|
244
|
+
|
|
245
|
+
regResourceCmds(`Set-Location (Split-Path '%1'); `);
|
|
246
|
+
|
|
247
|
+
if (pwshPath) {
|
|
248
|
+
const pwshKey = `${containerPath}\\shell\\${SUBCMDS.pwsh}`;
|
|
249
|
+
regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 打开 PowerShell');
|
|
250
|
+
regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
|
|
251
|
+
regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
|
|
252
|
+
`"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath (Split-Path '%1')"`);
|
|
253
|
+
}
|
|
254
|
+
} else if (kind === 'yaml') {
|
|
220
255
|
const cfgKey = `${containerPath}\\shell\\${SUBCMDS.config}`;
|
|
221
256
|
regAdd(cfgKey, 'MUIVerb', 'REG_SZ', '⚙️ 作为配置,拼接邮件 HTML');
|
|
222
257
|
regAdd(cfgKey, 'Icon', 'REG_SZ', iconPath);
|
|
223
258
|
regAdd(`${cfgKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, '-c %1'));
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Common items for file-type containers (html + yaml)
|
|
227
|
-
if (kind === 'html' || kind === 'yaml') {
|
|
228
|
-
const viewKey = `${containerPath}\\shell\\${SUBCMDS.viewEdm}`;
|
|
229
|
-
regAdd(viewKey, 'MUIVerb', 'REG_SZ', '📦 查看资源列表');
|
|
230
|
-
regAdd(viewKey, 'Icon', 'REG_SZ', iconPath);
|
|
231
|
-
regAdd(`${viewKey}\\command`, '', 'REG_SZ', wrapWithPause(nodePath, scriptPath, 'view'));
|
|
232
|
-
|
|
233
|
-
const viewIntKey = `${containerPath}\\shell\\${SUBCMDS.viewEdmInt}`;
|
|
234
|
-
regAdd(viewIntKey, 'MUIVerb', 'REG_SZ', '📋 浏览资源库');
|
|
235
|
-
regAdd(viewIntKey, 'Icon', 'REG_SZ', iconPath);
|
|
236
|
-
regAdd(`${viewIntKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, 'view -i'));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// pwsh — 始终显示(可选)
|
|
240
|
-
if (pwshPath) {
|
|
241
|
-
const pwshKey = `${containerPath}\\shell\\${SUBCMDS.pwsh}`;
|
|
242
|
-
regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 打开 PowerShell');
|
|
243
|
-
regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
|
|
244
|
-
regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
|
|
245
|
-
`"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath (Split-Path '%1')"`);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
259
|
|
|
249
|
-
|
|
260
|
+
regResourceCmds(`Set-Location (Split-Path '%1'); `);
|
|
250
261
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
`${
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
regDelete(`${root}\\JuiceEmail.Pwsh`);
|
|
284
|
-
}
|
|
262
|
+
if (pwshPath) {
|
|
263
|
+
const pwshKey = `${containerPath}\\shell\\${SUBCMDS.pwsh}`;
|
|
264
|
+
regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 打开 PowerShell');
|
|
265
|
+
regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
|
|
266
|
+
regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
|
|
267
|
+
`"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath (Split-Path '%1')"`);
|
|
268
|
+
}
|
|
269
|
+
} else if (kind === 'dir' || kind === 'bg') {
|
|
270
|
+
// Directory / Background: cd to the target directory first
|
|
271
|
+
const dirRef = kind === 'dir' ? '%1' : '%V';
|
|
272
|
+
const cd = `Set-Location '${dirRef}'; `;
|
|
273
|
+
|
|
274
|
+
// 📋 查看可用资源
|
|
275
|
+
const vKey = `${containerPath}\\shell\\JuiceEmail.ViewRes`;
|
|
276
|
+
regAdd(vKey, 'MUIVerb', 'REG_SZ', '📋 查看可用资源');
|
|
277
|
+
regAdd(vKey, 'Icon', 'REG_SZ', iconPath);
|
|
278
|
+
const vPs = `${cd}& '${node}' '${script}' view; Write-Host ''; Read-Host 'Press Enter to close'`;
|
|
279
|
+
regAdd(`${vKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${vPs}"`);
|
|
280
|
+
|
|
281
|
+
// 📦 拷贝全部资源
|
|
282
|
+
const aKey = `${containerPath}\\shell\\JuiceEmail.CopyAll`;
|
|
283
|
+
regAdd(aKey, 'MUIVerb', 'REG_SZ', '📦 拷贝全部资源');
|
|
284
|
+
regAdd(aKey, 'Icon', 'REG_SZ', iconPath);
|
|
285
|
+
const aPs = `${cd}& '${node}' '${script}' init --all .; Write-Host ''; Read-Host 'Press Enter to close'`;
|
|
286
|
+
regAdd(`${aKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${aPs}"`);
|
|
287
|
+
|
|
288
|
+
// 📥 选择资源拷贝
|
|
289
|
+
const pKey = `${containerPath}\\shell\\JuiceEmail.CopyPick`;
|
|
290
|
+
regAdd(pKey, 'MUIVerb', 'REG_SZ', '📥 选择资源拷贝');
|
|
291
|
+
regAdd(pKey, 'Icon', 'REG_SZ', iconPath);
|
|
292
|
+
const pPs = `${cd}& '${node}' '${script}' init; Write-Host ''; Read-Host 'Press Enter to close'`;
|
|
293
|
+
regAdd(`${pKey}\\command`, '', 'REG_SZ', `powershell.exe -Command "${pPs}"`);
|
|
285
294
|
|
|
286
|
-
for (const root of roots) {
|
|
287
|
-
// 📥 从资源库拷贝到此处
|
|
288
|
-
const initKey = `${root}\\JuiceEmail.Init`;
|
|
289
|
-
regAdd(initKey, 'MUIVerb', 'REG_SZ', '📥 从资源库拷贝到此处');
|
|
290
|
-
regAdd(initKey, 'Icon', 'REG_SZ', iconPath);
|
|
291
|
-
regAdd(`${initKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, 'init'));
|
|
292
|
-
|
|
293
|
-
// 📦 查看资源列表
|
|
294
|
-
const viewKey = `${root}\\JuiceEmail.View`;
|
|
295
|
-
regAdd(viewKey, 'MUIVerb', 'REG_SZ', '📦 查看资源列表');
|
|
296
|
-
regAdd(viewKey, 'Icon', 'REG_SZ', iconPath);
|
|
297
|
-
regAdd(`${viewKey}\\command`, '', 'REG_SZ', wrapWithPause(nodePath, scriptPath, 'view'));
|
|
298
|
-
|
|
299
|
-
// 📋 浏览资源库
|
|
300
|
-
const browseKey = `${root}\\JuiceEmail.Browse`;
|
|
301
|
-
regAdd(browseKey, 'MUIVerb', 'REG_SZ', '📋 浏览资源库');
|
|
302
|
-
regAdd(browseKey, 'Icon', 'REG_SZ', iconPath);
|
|
303
|
-
regAdd(`${browseKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, 'view -i'));
|
|
304
|
-
|
|
305
|
-
// 📂 在此打开终端
|
|
306
295
|
if (pwshPath) {
|
|
307
|
-
const pwshKey = `${
|
|
296
|
+
const pwshKey = `${containerPath}\\shell\\JuiceEmail.Pwsh`;
|
|
308
297
|
regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 在此打开终端');
|
|
309
298
|
regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
|
|
310
299
|
regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
|
|
311
|
-
`"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath '
|
|
300
|
+
`"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath '${dirRef}'"`);
|
|
312
301
|
}
|
|
313
302
|
}
|
|
314
303
|
}
|
|
@@ -323,7 +312,7 @@ async function registerContextMenu() {
|
|
|
323
312
|
|
|
324
313
|
console.log(chalk.cyan('\n 注册 juice 右键菜单(当前用户,无需管理员权限)...\n'));
|
|
325
314
|
|
|
326
|
-
//
|
|
315
|
+
// 清理所有旧版残留
|
|
327
316
|
cleanLegacyHklmParents();
|
|
328
317
|
cleanLegacyCommandStore();
|
|
329
318
|
|
|
@@ -334,22 +323,27 @@ async function registerContextMenu() {
|
|
|
334
323
|
|
|
335
324
|
let ok = true;
|
|
336
325
|
|
|
337
|
-
//
|
|
326
|
+
// 先删除旧容器再重建
|
|
338
327
|
console.log(chalk.gray(' 清理旧版菜单...'));
|
|
339
328
|
regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`);
|
|
340
329
|
regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`);
|
|
341
330
|
regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_DIR}`);
|
|
342
331
|
regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_BG}`);
|
|
332
|
+
// Clean up old individual Dir/Bg entries from v2.3.x
|
|
333
|
+
for (const root of [`${HKCU_SHELL}\\Directory\\shell`, `${HKCU_SHELL}\\Directory\\Background\\shell`]) {
|
|
334
|
+
regDelete(`${root}\\${PARENT_KEY_NAME}`);
|
|
335
|
+
for (const suffix of ['Init', 'View', 'Browse', 'Pwsh']) {
|
|
336
|
+
regDelete(`${root}\\JuiceEmail.${suffix}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
343
339
|
|
|
344
|
-
// HTML
|
|
340
|
+
// HTML 容器
|
|
345
341
|
console.log(chalk.gray(' 注册 .html 文件菜单...'));
|
|
346
|
-
|
|
347
|
-
registerSubCommands(htmlContainer, 'html', nodePath, scriptPath, iconPath, pwshPath);
|
|
342
|
+
registerSubCommands(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`, 'html', nodePath, scriptPath, iconPath, pwshPath);
|
|
348
343
|
|
|
349
|
-
// YAML
|
|
344
|
+
// YAML 容器
|
|
350
345
|
console.log(chalk.gray(' 注册 .yaml 文件菜单...'));
|
|
351
|
-
|
|
352
|
-
registerSubCommands(yamlContainer, 'yaml', nodePath, scriptPath, iconPath, pwshPath);
|
|
346
|
+
registerSubCommands(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`, 'yaml', nodePath, scriptPath, iconPath, pwshPath);
|
|
353
347
|
|
|
354
348
|
// .html / .htm → 引用 HTML 容器
|
|
355
349
|
for (const root of HTML_ROOTS) {
|
|
@@ -367,9 +361,22 @@ async function registerContextMenu() {
|
|
|
367
361
|
regAdd(parentKey, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_YAML);
|
|
368
362
|
}
|
|
369
363
|
|
|
370
|
-
// Directory / Background →
|
|
364
|
+
// Directory / Background → 各自独立容器
|
|
371
365
|
console.log(chalk.gray(' 注册文件夹/空白处菜单...'));
|
|
372
|
-
|
|
366
|
+
registerSubCommands(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_DIR}`, 'dir', nodePath, scriptPath, iconPath, pwshPath);
|
|
367
|
+
registerSubCommands(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_BG}`, 'bg', nodePath, scriptPath, iconPath, pwshPath);
|
|
368
|
+
|
|
369
|
+
// Directory → 引用 Dir 容器
|
|
370
|
+
const dirParent = `${HKCU_SHELL}\\Directory\\shell\\${PARENT_KEY_NAME}`;
|
|
371
|
+
ok = regAdd(dirParent, 'MUIVerb', 'REG_SZ', '📧 用 juice 生成邮件 HTML') && ok;
|
|
372
|
+
regAdd(dirParent, 'Icon', 'REG_SZ', iconPath);
|
|
373
|
+
regAdd(dirParent, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_DIR);
|
|
374
|
+
|
|
375
|
+
// Directory\Background → 引用 Bg 容器
|
|
376
|
+
const bgParent = `${HKCU_SHELL}\\Directory\\Background\\shell\\${PARENT_KEY_NAME}`;
|
|
377
|
+
ok = regAdd(bgParent, 'MUIVerb', 'REG_SZ', '📧 用 juice 生成邮件 HTML') && ok;
|
|
378
|
+
regAdd(bgParent, 'Icon', 'REG_SZ', iconPath);
|
|
379
|
+
regAdd(bgParent, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_BG);
|
|
373
380
|
|
|
374
381
|
if (!ok) {
|
|
375
382
|
console.log(chalk.yellow(' ⚠ 部分注册表项写入失败,右键菜单可能不完整。\n'));
|
|
@@ -382,20 +389,23 @@ async function registerContextMenu() {
|
|
|
382
389
|
` ${chalk.bold('📧 用 juice 生成邮件 HTML')}\n` +
|
|
383
390
|
` ├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)\n` +
|
|
384
391
|
` ├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)\n` +
|
|
385
|
-
` ├──
|
|
386
|
-
` ├──
|
|
392
|
+
` ├── 📋 查看可用资源 → juice view\n` +
|
|
393
|
+
` ├── 📦 拷贝全部资源 → juice init --all\n` +
|
|
394
|
+
` ├── 📥 选择资源拷贝 → juice init\n` +
|
|
387
395
|
(pwshPath ? ` └── 📂 打开 PowerShell\n` : '') +
|
|
388
396
|
`\n ${chalk.bold('.yaml / .yml')} 文件右键:\n` +
|
|
389
397
|
` ${chalk.bold('📧 用 juice 生成邮件 HTML')}\n` +
|
|
390
398
|
` ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)\n` +
|
|
391
|
-
` ├──
|
|
392
|
-
` ├──
|
|
399
|
+
` ├── 📋 查看可用资源\n` +
|
|
400
|
+
` ├── 📦 拷贝全部资源\n` +
|
|
401
|
+
` ├── 📥 选择资源拷贝\n` +
|
|
393
402
|
(pwshPath ? ` └── 📂 打开 PowerShell\n` : '') +
|
|
394
403
|
`\n ${chalk.bold('文件夹 / 空白处')} 右键:\n` +
|
|
395
|
-
`
|
|
396
|
-
`
|
|
397
|
-
`
|
|
398
|
-
|
|
404
|
+
` ${chalk.bold('📧 用 juice 生成邮件 HTML')}\n` +
|
|
405
|
+
` ├── 📋 查看可用资源\n` +
|
|
406
|
+
` ├── 📦 拷贝全部资源\n` +
|
|
407
|
+
` ├── 📥 选择资源拷贝\n` +
|
|
408
|
+
(pwshPath ? ` └── 📂 在此打开终端\n` : '') +
|
|
399
409
|
'\n' +
|
|
400
410
|
chalk.gray(' 注意:如菜单未出现,请重启文件资源管理器(explorer.exe)。\n')
|
|
401
411
|
);
|
|
@@ -413,40 +423,32 @@ async function unregisterContextMenu() {
|
|
|
413
423
|
|
|
414
424
|
let removed = 0;
|
|
415
425
|
|
|
416
|
-
//
|
|
426
|
+
// 清理文件类型父菜单
|
|
417
427
|
const allRoots = [
|
|
418
428
|
...HTML_ROOTS,
|
|
419
429
|
...YAML_ROOTS,
|
|
430
|
+
`${HKCU_SHELL}\\Directory\\shell`,
|
|
431
|
+
`${HKCU_SHELL}\\Directory\\Background\\shell`,
|
|
420
432
|
];
|
|
421
433
|
for (const root of allRoots) {
|
|
422
434
|
if (regDelete(`${root}\\${PARENT_KEY_NAME}`)) removed++;
|
|
423
435
|
}
|
|
424
436
|
|
|
425
|
-
//
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
];
|
|
430
|
-
const dirBgKeys = ['JuiceEmail.Init', 'JuiceEmail.View', 'JuiceEmail.Browse', 'JuiceEmail.Pwsh',
|
|
431
|
-
PARENT_KEY_NAME]; // PARENT_KEY_NAME = 'JuiceEmail' (old ExtendedSubCommandsKey parent)
|
|
432
|
-
for (const root of dirBgRoots) {
|
|
433
|
-
for (const key of dirBgKeys) {
|
|
434
|
-
if (regDelete(`${root}\\${key}`)) removed++;
|
|
437
|
+
// 清理旧版 Dir/Bg 独立条目 (v2.3.x individual entries)
|
|
438
|
+
for (const root of [`${HKCU_SHELL}\\Directory\\shell`, `${HKCU_SHELL}\\Directory\\Background\\shell`]) {
|
|
439
|
+
for (const suffix of ['Init', 'View', 'Browse', 'Pwsh']) {
|
|
440
|
+
if (regDelete(`${root}\\JuiceEmail.${suffix}`)) removed++;
|
|
435
441
|
}
|
|
436
442
|
}
|
|
437
443
|
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_DIR}`)) removed++;
|
|
444
|
-
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_BG}`)) removed++;
|
|
445
|
-
|
|
446
|
-
// 清理旧版共享容器(v2.1.14 之前只有一个容器)
|
|
444
|
+
// 清理所有容器(含旧版残留)
|
|
445
|
+
for (const container of [SUB_CMDS_CONTAINER_HTML, SUB_CMDS_CONTAINER_YAML, SUB_CMDS_CONTAINER_DIR, SUB_CMDS_CONTAINER_BG]) {
|
|
446
|
+
if (regDelete(`${HKCU_SHELL}\\${container}`)) removed++;
|
|
447
|
+
}
|
|
448
|
+
// 旧版共享容器 (v2.1.14)
|
|
447
449
|
regDelete(`${HKCU_SHELL}\\JuiceEmail.SubCommands`);
|
|
448
450
|
|
|
449
|
-
//
|
|
451
|
+
// 旧版残留(HKLM 父菜单 + CommandStore)
|
|
450
452
|
cleanLegacyHklmParents();
|
|
451
453
|
cleanLegacyCommandStore();
|
|
452
454
|
|
package/src/init.js
CHANGED
|
@@ -71,8 +71,7 @@ function deriveDefaultName(filePath) {
|
|
|
71
71
|
async function directCopy(srcPath, cwd) {
|
|
72
72
|
const resolved = path.resolve(srcPath);
|
|
73
73
|
if (!fs.existsSync(resolved)) {
|
|
74
|
-
|
|
75
|
-
process.exit(1);
|
|
74
|
+
throw new Error(`文件不存在:${resolved}`);
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
const destName = await promptOutputName(
|
|
@@ -231,9 +230,110 @@ async function interactiveInit(edmDir, cwd) {
|
|
|
231
230
|
|
|
232
231
|
// ─── Main Entry ──────────────────────────────────────────────────────────────
|
|
233
232
|
|
|
234
|
-
|
|
233
|
+
function copyDir(src, dest) {
|
|
234
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
235
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
236
|
+
const srcPath = path.join(src, entry.name);
|
|
237
|
+
const destPath = path.join(dest, entry.name);
|
|
238
|
+
if (entry.isDirectory()) {
|
|
239
|
+
copyDir(srcPath, destPath);
|
|
240
|
+
} else {
|
|
241
|
+
fs.copyFileSync(srcPath, destPath);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function dirSize(dir) {
|
|
247
|
+
let size = 0;
|
|
248
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
249
|
+
const p = path.join(dir, entry.name);
|
|
250
|
+
if (entry.isDirectory()) {
|
|
251
|
+
size += dirSize(p);
|
|
252
|
+
} else {
|
|
253
|
+
size += fs.statSync(p).size;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return size;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function countFiles(dir) {
|
|
260
|
+
let count = 0;
|
|
261
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
262
|
+
if (entry.isDirectory()) {
|
|
263
|
+
count += countFiles(path.join(dir, entry.name));
|
|
264
|
+
} else {
|
|
265
|
+
count++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return count;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function findNextEdmVersion(targetDir) {
|
|
272
|
+
let v = 1;
|
|
273
|
+
while (true) {
|
|
274
|
+
const candidate = path.join(targetDir, 'edm-v' + v);
|
|
275
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
276
|
+
v++;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function runInitMode({ initPath, template, snippet, config, all }) {
|
|
235
281
|
const cwd = process.cwd();
|
|
236
282
|
|
|
283
|
+
// --all: copy entire EDM directory
|
|
284
|
+
if (all) {
|
|
285
|
+
let edmDir;
|
|
286
|
+
try {
|
|
287
|
+
edmDir = resolveEdmDir();
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.error(chalk.red(`\n ✘ ${err.message}\n`));
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
const target = all === true ? cwd : path.resolve(all);
|
|
293
|
+
const destDir = path.join(target, 'edm');
|
|
294
|
+
|
|
295
|
+
// Prevent deleting the source when target overlaps
|
|
296
|
+
if (path.resolve(edmDir) === path.resolve(destDir)) {
|
|
297
|
+
console.log(chalk.yellow('目标目录与 EDM 资源库相同,无需拷贝。\n'));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
console.log(chalk.cyan(`\n 拷贝 EDM 资源库...`));
|
|
301
|
+
console.log(chalk.gray(` 源:${edmDir}`));
|
|
302
|
+
console.log(chalk.gray(` 目标:${destDir}`));
|
|
303
|
+
|
|
304
|
+
if (fs.existsSync(destDir)) {
|
|
305
|
+
const { select } = await import('@inquirer/prompts');
|
|
306
|
+
const vName = findNextEdmVersion(target);
|
|
307
|
+
const action = await select({
|
|
308
|
+
message: `目录 ${destDir} 已存在:`,
|
|
309
|
+
choices: [
|
|
310
|
+
{ name: '覆盖', value: 'overwrite' },
|
|
311
|
+
{ name: `自动版本号(${path.basename(vName)})`, value: 'version' },
|
|
312
|
+
{ name: '取消', value: 'cancel' },
|
|
313
|
+
],
|
|
314
|
+
});
|
|
315
|
+
if (action === 'cancel') {
|
|
316
|
+
console.log(chalk.gray('已取消。\n'));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (action === 'version') {
|
|
320
|
+
copyDir(edmDir, vName);
|
|
321
|
+
const edmSize = dirSize(vName);
|
|
322
|
+
const fileCount = countFiles(vName);
|
|
323
|
+
console.log(chalk.green(`\n✔ 已拷贝 EDM 资源库 → ${path.basename(vName)}(${fileCount} 个文件,${fmtBytes(edmSize)})\n`));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// overwrite: remove and replace
|
|
327
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
copyDir(edmDir, destDir);
|
|
331
|
+
const edmSize = dirSize(destDir);
|
|
332
|
+
const fileCount = countFiles(destDir);
|
|
333
|
+
console.log(chalk.green(`\n✔ 已拷贝 EDM 资源库(${fileCount} 个文件,${fmtBytes(edmSize)})\n`));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
237
337
|
// Direct copy modes
|
|
238
338
|
if (template) {
|
|
239
339
|
await directCopy(template, cwd);
|
package/src/view.js
CHANGED
|
@@ -12,6 +12,27 @@ function fmtBytes(b) {
|
|
|
12
12
|
return b < 1024 ? `${b} B` : `${(b / 1024).toFixed(1)} KB`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Copy src to dest, auto-versioning (-v1, -v2, ...) if dest exists.
|
|
17
|
+
* Returns the actual destination path used.
|
|
18
|
+
*/
|
|
19
|
+
function copyFileSafe(srcPath, destPath) {
|
|
20
|
+
if (!fs.existsSync(destPath)) {
|
|
21
|
+
fs.copyFileSync(srcPath, destPath);
|
|
22
|
+
return destPath;
|
|
23
|
+
}
|
|
24
|
+
const parsed = path.parse(destPath);
|
|
25
|
+
let v = 1;
|
|
26
|
+
while (true) {
|
|
27
|
+
const alt = path.join(parsed.dir, parsed.name + '-v' + v + parsed.ext);
|
|
28
|
+
if (!fs.existsSync(alt)) {
|
|
29
|
+
fs.copyFileSync(srcPath, alt);
|
|
30
|
+
return alt;
|
|
31
|
+
}
|
|
32
|
+
v++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
15
36
|
// ─── Path Parser ──────────────────────────────────────────────────────────────
|
|
16
37
|
|
|
17
38
|
/**
|
|
@@ -327,13 +348,17 @@ async function showCheckbox(title, choices) {
|
|
|
327
348
|
* Trigger copy via the init module.
|
|
328
349
|
*/
|
|
329
350
|
async function copyResource(type, resourcePath, cwd) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
351
|
+
try {
|
|
352
|
+
const { runInitMode } = require('./init');
|
|
353
|
+
if (type === 'template') {
|
|
354
|
+
await runInitMode({ template: resourcePath });
|
|
355
|
+
} else if (type === 'snippet') {
|
|
356
|
+
await runInitMode({ snippet: resourcePath });
|
|
357
|
+
} else if (type === 'config') {
|
|
358
|
+
await runInitMode({ config: resourcePath });
|
|
359
|
+
}
|
|
360
|
+
} catch (err) {
|
|
361
|
+
console.error(chalk.red(` ✘ 拷贝失败:${err.message}`));
|
|
337
362
|
}
|
|
338
363
|
}
|
|
339
364
|
|
|
@@ -551,7 +576,7 @@ async function interactiveBrowse(edmDir, startParsed) {
|
|
|
551
576
|
if (versions.length > 0) {
|
|
552
577
|
const tplPath = versions[0].templatePath;
|
|
553
578
|
copyItems.push({
|
|
554
|
-
name:
|
|
579
|
+
name: `📋 模板 HTML(${versions[0].meta.name || versions[0].name})`,
|
|
555
580
|
value: 'template',
|
|
556
581
|
description: path.basename(tplPath),
|
|
557
582
|
checked: true,
|
|
@@ -588,7 +613,9 @@ async function interactiveBrowse(edmDir, startParsed) {
|
|
|
588
613
|
});
|
|
589
614
|
|
|
590
615
|
if (selected.length > 0) {
|
|
591
|
-
|
|
616
|
+
// Include template version in default name for clarity
|
|
617
|
+
const versionName = versions.length > 0 ? versions[0].name : 'template';
|
|
618
|
+
const defaultBaseName = `${current.brand}-${versionName}-${current.series}-${current.variant}`;
|
|
592
619
|
const { promptOutputName: pn } = require('./snippet');
|
|
593
620
|
const outputBaseName = await pn(defaultBaseName, process.cwd());
|
|
594
621
|
const cwd = process.cwd();
|
|
@@ -596,28 +623,22 @@ async function interactiveBrowse(edmDir, startParsed) {
|
|
|
596
623
|
|
|
597
624
|
console.log(chalk.green('\n✔ 已拷贝:'));
|
|
598
625
|
if (selected.includes('template') && versions.length > 0) {
|
|
599
|
-
const
|
|
600
|
-
const dest = path.join(cwd, outputBaseName + path.extname(
|
|
601
|
-
|
|
602
|
-
console.log(` ${chalk.cyan('·')} ${cwdRel(
|
|
626
|
+
const src = versions[0].templatePath;
|
|
627
|
+
const dest = path.join(cwd, outputBaseName + path.extname(src));
|
|
628
|
+
const actual = copyFileSafe(src, dest);
|
|
629
|
+
console.log(` ${chalk.cyan('·')} ${cwdRel(actual)} ${chalk.gray('(模板, ' + fmtBytes(fs.statSync(actual).size) + ')')}`);
|
|
603
630
|
}
|
|
604
631
|
if (selected.includes('snippet') && fs.existsSync(snipPath)) {
|
|
605
632
|
const dest = path.join(cwd, outputBaseName + '-snippet' + path.extname(snipPath));
|
|
606
|
-
|
|
607
|
-
console.log(` ${chalk.cyan('·')} ${cwdRel(
|
|
633
|
+
const actual = copyFileSafe(snipPath, dest);
|
|
634
|
+
console.log(` ${chalk.cyan('·')} ${cwdRel(actual)} ${chalk.gray('(片段, ' + fmtBytes(fs.statSync(actual).size) + ')')}`);
|
|
608
635
|
}
|
|
609
636
|
if (selected.includes('config') && configs.length > 0) {
|
|
610
637
|
const optimal = configs.find(c => c.isOptimal);
|
|
611
638
|
const cfgPath = optimal ? optimal.path : configs[0].path;
|
|
612
639
|
const dest = path.join(cwd, 'juice.yaml');
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
fs.copyFileSync(cfgPath, alt);
|
|
616
|
-
console.log(` ${chalk.cyan('·')} ${cwdRel(alt)} ${chalk.gray('(配置, ' + fmtBytes(fs.statSync(alt).size) + ')')}`);
|
|
617
|
-
} else {
|
|
618
|
-
fs.copyFileSync(cfgPath, dest);
|
|
619
|
-
console.log(` ${chalk.cyan('·')} ${cwdRel(dest)} ${chalk.gray('(配置, ' + fmtBytes(fs.statSync(dest).size) + ')')}`);
|
|
620
|
-
}
|
|
640
|
+
const actual = copyFileSafe(cfgPath, dest);
|
|
641
|
+
console.log(` ${chalk.cyan('·')} ${cwdRel(actual)} ${chalk.gray('(配置, ' + fmtBytes(fs.statSync(actual).size) + ')')}`);
|
|
621
642
|
}
|
|
622
643
|
console.log();
|
|
623
644
|
}
|