juice-email-cli 2.3.1 → 2.3.3
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 +79 -15
- package/package.json +1 -1
- package/src/context-menu.js +65 -34
- package/src/view.js +80 -136
package/README.md
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
- **双文件输出** —— 普通模式生成 `.output.html` + `.minified.html`
|
|
16
16
|
- **四文件输出** —— 片段模式生成 `.raw.html` + `.html` + `.output.html` + `.minified.html`
|
|
17
17
|
- **交互模式** —— 无参数运行,逐步选择品牌、模板、片段、配置
|
|
18
|
-
-
|
|
18
|
+
- **资源浏览** —— `juice view` 查看 EDM 资源树,`-i` 交互式逐层翻页
|
|
19
|
+
- **资源拷贝** —— `juice init` 将模板/片段/配置拷贝到当前目录
|
|
20
|
+
- **Windows 右键菜单** —— 文件/文件夹/空白处多场景支持,一键注册/卸载
|
|
19
21
|
|
|
20
22
|
---
|
|
21
23
|
|
|
@@ -97,6 +99,41 @@ juice
|
|
|
97
99
|
|
|
98
100
|
流程:选择品牌 → 模板 → 片段文件夹 → 片段 HTML → 配置文件 → 输出文件名 → 确认执行。
|
|
99
101
|
|
|
102
|
+
### 浏览资源(查看 EDM 模板库)
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# 查看完整资源树
|
|
106
|
+
juice view
|
|
107
|
+
|
|
108
|
+
# 查看指定品牌/系列/变体
|
|
109
|
+
juice view elabscience
|
|
110
|
+
juice view elabscience/literature
|
|
111
|
+
|
|
112
|
+
# 列出所有模板 / 系列 / 片段
|
|
113
|
+
juice view --templates
|
|
114
|
+
juice view --series
|
|
115
|
+
juice view --snippets
|
|
116
|
+
|
|
117
|
+
# 交互式浏览(可上下翻层级,叶节点可直接拷贝)
|
|
118
|
+
juice view -i
|
|
119
|
+
juice view -i elabscience
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 拷贝资源到当前目录
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# 交互式选择并多选拷贝
|
|
126
|
+
juice init
|
|
127
|
+
|
|
128
|
+
# 从指定 EDM 路径拷贝
|
|
129
|
+
juice init elabscience/templates/standard
|
|
130
|
+
|
|
131
|
+
# 仅拷贝指定文件
|
|
132
|
+
juice init --template edm/elabscience/templates/standard/template.html
|
|
133
|
+
juice init --snippet edm/elabscience/series/literature/default/snippet.html
|
|
134
|
+
juice init --config edm/elabscience/series/literature/default/juice.yaml
|
|
135
|
+
```
|
|
136
|
+
|
|
100
137
|
### 参数说明
|
|
101
138
|
|
|
102
139
|
| 参数 | 简写 | 说明 |
|
|
@@ -116,7 +153,9 @@ juice
|
|
|
116
153
|
|------|------|------|----------|
|
|
117
154
|
| ✓ | * | * | 片段模式(-s 指定片段,-f 可选指定模板) |
|
|
118
155
|
| ✗ | ✓ | * | 普通模式(生成 .output.html + .minified.html) |
|
|
119
|
-
| ✗ | ✗ |
|
|
156
|
+
| ✗ | ✗ | ✗ | 交互式片段模式(逐步选择) |
|
|
157
|
+
| — | — | — | `juice view [path]` 浏览资源 / `-i` 交互浏览 |
|
|
158
|
+
| — | — | — | `juice init [path]` 拷贝资源到当前目录 |
|
|
120
159
|
|
|
121
160
|
---
|
|
122
161
|
|
|
@@ -312,21 +351,36 @@ edm/
|
|
|
312
351
|
|
|
313
352
|
## Windows 右键菜单
|
|
314
353
|
|
|
315
|
-
|
|
354
|
+
### 文件右键(.html / .htm)
|
|
316
355
|
|
|
317
356
|
```
|
|
318
357
|
📧 用 juice 生成邮件 HTML
|
|
319
358
|
├── 📄 作为模板,生成邮件 HTML → juice -f %1(后台执行)
|
|
320
359
|
├── 🧩 作为片段,拼接邮件 HTML → juice -s %1(交互选择模板)
|
|
321
|
-
├──
|
|
360
|
+
├── 📦 查看资源列表 → juice view
|
|
361
|
+
├── 📋 浏览资源库 → juice view -i
|
|
322
362
|
└── 📂 打开 PowerShell ← 仅已安装 pwsh 时出现
|
|
323
363
|
```
|
|
324
364
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
365
|
+
### 文件右键(.yaml / .yml)
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
📧 用 juice 生成邮件 HTML
|
|
369
|
+
├── ⚙️ 作为配置,拼接邮件 HTML → juice -c %1(交互选择品牌/模板/片段)
|
|
370
|
+
├── 📦 查看资源列表 → juice view
|
|
371
|
+
├── 📋 浏览资源库 → juice view -i
|
|
372
|
+
└── 📂 打开 PowerShell
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 文件夹 / 空白处右键
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
📧 juice 邮件工具
|
|
379
|
+
├── 📥 从资源库拷贝到此处 → juice init
|
|
380
|
+
├── 📦 查看资源列表 → juice view
|
|
381
|
+
├── 📋 浏览资源库 → juice view -i
|
|
382
|
+
└── 📂 在此打开终端
|
|
383
|
+
```
|
|
330
384
|
|
|
331
385
|
```bash
|
|
332
386
|
juice --install # 注册(当前用户,无需管理员)
|
|
@@ -342,21 +396,31 @@ juice --uninstall # 卸载
|
|
|
342
396
|
```
|
|
343
397
|
juice-cli/
|
|
344
398
|
├── bin/
|
|
345
|
-
│ └── juice.js # CLI
|
|
399
|
+
│ └── juice.js # CLI 入口(Commander.js)
|
|
346
400
|
├── src/
|
|
347
401
|
│ ├── index.js # 核心逻辑(配置合并、模板处理、双输出)
|
|
348
402
|
│ ├── snippet.js # 片段组装模式 + 交互式提示
|
|
403
|
+
│ ├── view.js # EDM 资源查看/浏览(juice view)
|
|
404
|
+
│ ├── init.js # EDM 资源拷贝初始化(juice init)
|
|
349
405
|
│ └── context-menu.js # Windows 右键菜单注册
|
|
350
406
|
├── defaults/
|
|
351
407
|
│ └── juice.yaml # CLI 内置默认配置
|
|
352
408
|
├── edm/ # EDM 模板库(npm 发布时包含)
|
|
353
409
|
│ ├── elabscience/
|
|
354
|
-
│ │ ├──
|
|
355
|
-
│ │
|
|
356
|
-
│ │
|
|
357
|
-
│ │ └──
|
|
410
|
+
│ │ ├── _meta.yaml
|
|
411
|
+
│ │ ├── templates/
|
|
412
|
+
│ │ │ └── standard/
|
|
413
|
+
│ │ │ └── template.html
|
|
414
|
+
│ │ └── series/
|
|
415
|
+
│ │ └── literature/
|
|
416
|
+
│ │ └── default/
|
|
417
|
+
│ │ ├── snippet.html
|
|
418
|
+
│ │ └── juice.yaml
|
|
358
419
|
│ └── procell/
|
|
359
|
-
│
|
|
420
|
+
│ ├── _meta.yaml
|
|
421
|
+
│ └── templates/
|
|
422
|
+
│ └── standard/
|
|
423
|
+
│ └── template.html
|
|
360
424
|
├── icons/
|
|
361
425
|
│ └── juice-icon.ico # 右键菜单图标
|
|
362
426
|
├── scripts/
|
package/package.json
CHANGED
package/src/context-menu.js
CHANGED
|
@@ -228,7 +228,7 @@ function registerSubCommands(containerPath, kind, nodePath, scriptPath, iconPath
|
|
|
228
228
|
const viewKey = `${containerPath}\\shell\\${SUBCMDS.viewEdm}`;
|
|
229
229
|
regAdd(viewKey, 'MUIVerb', 'REG_SZ', '📦 查看资源列表');
|
|
230
230
|
regAdd(viewKey, 'Icon', 'REG_SZ', iconPath);
|
|
231
|
-
regAdd(`${viewKey}\\command`, '', 'REG_SZ',
|
|
231
|
+
regAdd(`${viewKey}\\command`, '', 'REG_SZ', wrapWithPause(nodePath, scriptPath, 'view'));
|
|
232
232
|
|
|
233
233
|
const viewIntKey = `${containerPath}\\shell\\${SUBCMDS.viewEdmInt}`;
|
|
234
234
|
regAdd(viewIntKey, 'MUIVerb', 'REG_SZ', '📋 浏览资源库');
|
|
@@ -246,52 +246,71 @@ function registerSubCommands(containerPath, kind, nodePath, scriptPath, iconPath
|
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
// ─── 非交互命令包装(始终暂停以便用户查看输出)─────────────────────────────────
|
|
250
|
+
|
|
251
|
+
function wrapWithPause(nodePath, scriptPath, cliArgs) {
|
|
252
|
+
const node = nodePath.replace(/'/g, "''");
|
|
253
|
+
const script = scriptPath.replace(/'/g, "''");
|
|
254
|
+
const ps = [
|
|
255
|
+
`& '${node}' '${script}' ${cliArgs}`,
|
|
256
|
+
`Write-Host ''`,
|
|
257
|
+
`Read-Host 'Press Enter to close'`,
|
|
258
|
+
].join('; ');
|
|
259
|
+
return `powershell.exe -Command "${ps}"`;
|
|
260
|
+
}
|
|
261
|
+
|
|
249
262
|
// ─── Directory / Background 菜单注册 ──────────────────────────────────────────
|
|
250
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Register individual shell commands for Directory and Directory\Background.
|
|
266
|
+
* ExtendedSubCommandsKey doesn't work reliably for non-file-type associations,
|
|
267
|
+
* so we register each item as a standalone shell verb.
|
|
268
|
+
*/
|
|
251
269
|
function registerDirBgMenus(nodePath, scriptPath, iconPath, pwshPath) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
270
|
+
const roots = [
|
|
271
|
+
`${HKCU_SHELL}\\Directory\\shell`,
|
|
272
|
+
`${HKCU_SHELL}\\Directory\\Background\\shell`,
|
|
273
|
+
];
|
|
255
274
|
|
|
256
|
-
|
|
275
|
+
// First, clean up old keys from all previous versions to prevent stale entries
|
|
276
|
+
for (const root of roots) {
|
|
277
|
+
// Old parent key (ExtendedSubCommandsKey approach, v2.x commit 3fc761e)
|
|
278
|
+
regDelete(`${root}\\${PARENT_KEY_NAME}`);
|
|
279
|
+
// Old individual entries from any prior version
|
|
280
|
+
regDelete(`${root}\\JuiceEmail.Init`);
|
|
281
|
+
regDelete(`${root}\\JuiceEmail.View`);
|
|
282
|
+
regDelete(`${root}\\JuiceEmail.Browse`);
|
|
283
|
+
regDelete(`${root}\\JuiceEmail.Pwsh`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const root of roots) {
|
|
257
287
|
// 📥 从资源库拷贝到此处
|
|
258
|
-
const initKey = `${
|
|
288
|
+
const initKey = `${root}\\JuiceEmail.Init`;
|
|
259
289
|
regAdd(initKey, 'MUIVerb', 'REG_SZ', '📥 从资源库拷贝到此处');
|
|
260
290
|
regAdd(initKey, 'Icon', 'REG_SZ', iconPath);
|
|
261
291
|
regAdd(`${initKey}\\command`, '', 'REG_SZ', wrapInteractive(nodePath, scriptPath, 'init'));
|
|
262
292
|
|
|
263
293
|
// 📦 查看资源列表
|
|
264
|
-
const viewKey = `${
|
|
294
|
+
const viewKey = `${root}\\JuiceEmail.View`;
|
|
265
295
|
regAdd(viewKey, 'MUIVerb', 'REG_SZ', '📦 查看资源列表');
|
|
266
296
|
regAdd(viewKey, 'Icon', 'REG_SZ', iconPath);
|
|
267
|
-
regAdd(`${viewKey}\\command`, '', 'REG_SZ',
|
|
297
|
+
regAdd(`${viewKey}\\command`, '', 'REG_SZ', wrapWithPause(nodePath, scriptPath, 'view'));
|
|
268
298
|
|
|
269
299
|
// 📋 浏览资源库
|
|
270
|
-
const
|
|
271
|
-
regAdd(
|
|
272
|
-
regAdd(
|
|
273
|
-
regAdd(`${
|
|
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'));
|
|
274
304
|
|
|
275
305
|
// 📂 在此打开终端
|
|
276
306
|
if (pwshPath) {
|
|
277
|
-
const pwshKey = `${
|
|
307
|
+
const pwshKey = `${root}\\JuiceEmail.Pwsh`;
|
|
278
308
|
regAdd(pwshKey, 'MUIVerb', 'REG_SZ', '📂 在此打开终端');
|
|
279
309
|
regAdd(pwshKey, 'Icon', 'REG_SZ', pwshPath);
|
|
280
310
|
regAdd(`${pwshKey}\\command`, '', 'REG_SZ',
|
|
281
|
-
`"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath '%
|
|
311
|
+
`"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath '%V'"`);
|
|
282
312
|
}
|
|
283
313
|
}
|
|
284
|
-
|
|
285
|
-
// Parent keys: Directory and Directory\Background
|
|
286
|
-
const dirParent = `${HKCU_SHELL}\\Directory\\shell\\${PARENT_KEY_NAME}`;
|
|
287
|
-
regAdd(dirParent, 'MUIVerb', 'REG_SZ', '📧 juice 邮件工具');
|
|
288
|
-
regAdd(dirParent, 'Icon', 'REG_SZ', iconPath);
|
|
289
|
-
regAdd(dirParent, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_DIR);
|
|
290
|
-
|
|
291
|
-
const bgParent = `${HKCU_SHELL}\\Directory\\Background\\shell\\${PARENT_KEY_NAME}`;
|
|
292
|
-
regAdd(bgParent, 'MUIVerb', 'REG_SZ', '📧 juice 邮件工具');
|
|
293
|
-
regAdd(bgParent, 'Icon', 'REG_SZ', iconPath);
|
|
294
|
-
regAdd(bgParent, 'ExtendedSubCommandsKey', 'REG_SZ', SUB_CMDS_CONTAINER_BG);
|
|
295
314
|
}
|
|
296
315
|
|
|
297
316
|
// ─── 注册 ─────────────────────────────────────────────────────────────────────
|
|
@@ -373,11 +392,10 @@ async function registerContextMenu() {
|
|
|
373
392
|
` ├── 📋 浏览资源库 → juice view -i\n` +
|
|
374
393
|
(pwshPath ? ` └── 📂 打开 PowerShell\n` : '') +
|
|
375
394
|
`\n ${chalk.bold('文件夹 / 空白处')} 右键:\n` +
|
|
376
|
-
`
|
|
377
|
-
`
|
|
378
|
-
`
|
|
379
|
-
`
|
|
380
|
-
(pwshPath ? ` └── 📂 在此打开终端\n` : '') +
|
|
395
|
+
` 📥 从资源库拷贝到此处 → juice init\n` +
|
|
396
|
+
` 📦 查看资源列表 → juice view\n` +
|
|
397
|
+
` 📋 浏览资源库 → juice view -i\n` +
|
|
398
|
+
(pwshPath ? ` 📂 在此打开终端\n` : '') +
|
|
381
399
|
'\n' +
|
|
382
400
|
chalk.gray(' 注意:如菜单未出现,请重启文件资源管理器(explorer.exe)。\n')
|
|
383
401
|
);
|
|
@@ -395,22 +413,35 @@ async function unregisterContextMenu() {
|
|
|
395
413
|
|
|
396
414
|
let removed = 0;
|
|
397
415
|
|
|
398
|
-
// 清理 HKCU
|
|
416
|
+
// 清理 HKCU 父菜单(文件类型)
|
|
399
417
|
const allRoots = [
|
|
400
418
|
...HTML_ROOTS,
|
|
401
419
|
...YAML_ROOTS,
|
|
402
|
-
`${HKCU_SHELL}\\Directory\\shell`,
|
|
403
|
-
`${HKCU_SHELL}\\Directory\\Background\\shell`,
|
|
404
420
|
];
|
|
405
421
|
for (const root of allRoots) {
|
|
406
422
|
if (regDelete(`${root}\\${PARENT_KEY_NAME}`)) removed++;
|
|
407
423
|
}
|
|
408
424
|
|
|
409
|
-
//
|
|
425
|
+
// 清理 Directory / Background 独立菜单项 + 旧版父键
|
|
426
|
+
const dirBgRoots = [
|
|
427
|
+
`${HKCU_SHELL}\\Directory\\shell`,
|
|
428
|
+
`${HKCU_SHELL}\\Directory\\Background\\shell`,
|
|
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++;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// 清理所有子命令容器(含旧版 Dir/Bg 容器)
|
|
410
439
|
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_HTML}`)) removed++;
|
|
411
440
|
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_YAML}`)) removed++;
|
|
412
441
|
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_DIR}`)) removed++;
|
|
413
442
|
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_BG}`)) removed++;
|
|
443
|
+
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_DIR}`)) removed++;
|
|
444
|
+
if (regDelete(`${HKCU_SHELL}\\${SUB_CMDS_CONTAINER_BG}`)) removed++;
|
|
414
445
|
|
|
415
446
|
// 清理旧版共享容器(v2.1.14 之前只有一个容器)
|
|
416
447
|
regDelete(`${HKCU_SHELL}\\JuiceEmail.SubCommands`);
|
package/src/view.js
CHANGED
|
@@ -420,20 +420,6 @@ async function interactiveBrowse(edmDir, startParsed) {
|
|
|
420
420
|
description: allSeries.length + ' 个系列',
|
|
421
421
|
});
|
|
422
422
|
}
|
|
423
|
-
|
|
424
|
-
// Copy options
|
|
425
|
-
if (versions.length > 0) {
|
|
426
|
-
choices.push({
|
|
427
|
-
name: '📥 拷贝模板到当前目录',
|
|
428
|
-
value: { action: 'copy-template' },
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
if (allSeries.length > 0) {
|
|
432
|
-
choices.push({
|
|
433
|
-
name: '📥 拷贝配置到当前目录',
|
|
434
|
-
value: { action: 'copy-config-brand' },
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
423
|
break;
|
|
438
424
|
}
|
|
439
425
|
|
|
@@ -511,27 +497,15 @@ async function interactiveBrowse(edmDir, startParsed) {
|
|
|
511
497
|
if (infoLines.length > 0) {
|
|
512
498
|
console.log(chalk.dim('\n ' + infoLines.join('\n ') + '\n'));
|
|
513
499
|
}
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
choices.push({
|
|
517
|
-
name: '📥 拷贝片段到当前目录',
|
|
518
|
-
value: { action: 'copy-snippet' },
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
if (configs.length > 0) {
|
|
522
|
-
choices.push({
|
|
523
|
-
name: '📥 拷贝配置到当前目录',
|
|
524
|
-
value: { action: 'copy-config' },
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
// Also allow copying the template
|
|
500
|
+
// Multi-select copy
|
|
501
|
+
const hasResources = fs.existsSync(snipPath) || configs.length > 0;
|
|
528
502
|
let versions;
|
|
529
503
|
const brandDir = path.join(edmDir, current.brand);
|
|
530
504
|
try { versions = findTemplateVersions(brandDir); } catch (_) { versions = []; }
|
|
531
|
-
if (versions.length > 0) {
|
|
505
|
+
if (hasResources || versions.length > 0) {
|
|
532
506
|
choices.push({
|
|
533
|
-
name: '📥
|
|
534
|
-
value: { action: 'copy-
|
|
507
|
+
name: '📥 拷贝到当前目录',
|
|
508
|
+
value: { action: 'copy-multi' },
|
|
535
509
|
});
|
|
536
510
|
}
|
|
537
511
|
break;
|
|
@@ -558,125 +532,95 @@ async function interactiveBrowse(edmDir, startParsed) {
|
|
|
558
532
|
|
|
559
533
|
// Copy actions
|
|
560
534
|
if (result.action === 'copy-template') {
|
|
561
|
-
|
|
562
|
-
if (
|
|
563
|
-
const brandDir = path.join(edmDir, current.brand);
|
|
564
|
-
const versions = findTemplateVersions(brandDir);
|
|
565
|
-
if (versions.length === 1) {
|
|
566
|
-
tplPath = versions[0].templatePath;
|
|
567
|
-
} else {
|
|
568
|
-
// Let user pick which template version
|
|
569
|
-
const tplChoices = versions.map(v => ({
|
|
570
|
-
name: formatName(v.meta, v.name) + formatDesc(v.meta),
|
|
571
|
-
value: v.templatePath,
|
|
572
|
-
}));
|
|
573
|
-
tplPath = await showMenu('选择要拷贝的模板版本', tplChoices);
|
|
574
|
-
}
|
|
575
|
-
} else if (current.templatePath) {
|
|
576
|
-
tplPath = current.templatePath;
|
|
577
|
-
} else if (current.versionData) {
|
|
578
|
-
tplPath = current.versionData.templatePath;
|
|
579
|
-
} else {
|
|
580
|
-
const brandDir = path.join(edmDir, current.brand);
|
|
581
|
-
const versions = findTemplateVersions(brandDir);
|
|
582
|
-
if (versions.length === 0) continue;
|
|
583
|
-
if (versions.length === 1) {
|
|
584
|
-
tplPath = versions[0].templatePath;
|
|
585
|
-
} else {
|
|
586
|
-
const tplChoices = versions.map(v => ({
|
|
587
|
-
name: formatName(v.meta, v.name) + formatDesc(v.meta),
|
|
588
|
-
value: v.templatePath,
|
|
589
|
-
}));
|
|
590
|
-
tplPath = await showMenu('选择要拷贝的模板版本', tplChoices);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
if (tplPath) {
|
|
535
|
+
const v = current.versionData;
|
|
536
|
+
if (v && v.templatePath) {
|
|
594
537
|
console.log();
|
|
595
|
-
await copyResource('template',
|
|
538
|
+
await copyResource('template', v.templatePath, process.cwd());
|
|
596
539
|
console.log();
|
|
597
540
|
}
|
|
598
541
|
continue;
|
|
599
542
|
}
|
|
600
543
|
|
|
601
|
-
if (result.action === 'copy-
|
|
544
|
+
if (result.action === 'copy-multi') {
|
|
545
|
+
const brandDir = path.join(edmDir, current.brand);
|
|
546
|
+
const copyItems = [];
|
|
547
|
+
|
|
548
|
+
// Template
|
|
549
|
+
let versions;
|
|
550
|
+
try { versions = findTemplateVersions(brandDir); } catch (_) { versions = []; }
|
|
551
|
+
if (versions.length > 0) {
|
|
552
|
+
const tplPath = versions[0].templatePath;
|
|
553
|
+
copyItems.push({
|
|
554
|
+
name: '📋 模板 HTML',
|
|
555
|
+
value: 'template',
|
|
556
|
+
description: path.basename(tplPath),
|
|
557
|
+
checked: true,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Snippet
|
|
602
562
|
const snipPath = path.join(current.variantData.path, 'snippet.html');
|
|
603
563
|
if (fs.existsSync(snipPath)) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
564
|
+
copyItems.push({
|
|
565
|
+
name: '🧩 片段 HTML',
|
|
566
|
+
value: 'snippet',
|
|
567
|
+
description: path.basename(snipPath),
|
|
568
|
+
checked: true,
|
|
569
|
+
});
|
|
607
570
|
}
|
|
608
|
-
continue;
|
|
609
|
-
}
|
|
610
571
|
|
|
611
|
-
|
|
572
|
+
// Config
|
|
612
573
|
const configs = findConfigs(current.variantData.path);
|
|
613
|
-
if (configs.length
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
value: c.path,
|
|
621
|
-
}));
|
|
622
|
-
const cfgPath = await showMenu('选择要拷贝的配置文件', cfgChoices);
|
|
623
|
-
if (cfgPath) {
|
|
624
|
-
console.log();
|
|
625
|
-
await copyResource('config', cfgPath, process.cwd());
|
|
626
|
-
console.log();
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
continue;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
if (result.action === 'copy-config-brand') {
|
|
633
|
-
// From brand level: pick series → variant → config
|
|
634
|
-
const brandDir = path.join(edmDir, current.brand);
|
|
635
|
-
const allSeries = findSeriesDirs(brandDir);
|
|
636
|
-
if (allSeries.length === 0) continue;
|
|
637
|
-
|
|
638
|
-
let series;
|
|
639
|
-
if (allSeries.length === 1) {
|
|
640
|
-
series = allSeries[0];
|
|
641
|
-
} else {
|
|
642
|
-
const sChoices = allSeries.map(s => ({
|
|
643
|
-
name: formatName(s.meta, s.name) + formatDesc(s.meta),
|
|
644
|
-
value: s,
|
|
645
|
-
}));
|
|
646
|
-
series = await showMenu('选择系列', sChoices);
|
|
574
|
+
if (configs.length > 0) {
|
|
575
|
+
copyItems.push({
|
|
576
|
+
name: '⚙️ 配置文件',
|
|
577
|
+
value: 'config',
|
|
578
|
+
description: configs.map(c => c.name).join(', '),
|
|
579
|
+
checked: true,
|
|
580
|
+
});
|
|
647
581
|
}
|
|
648
|
-
if (!series) continue;
|
|
649
582
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const vChoices = variants.map(v => ({
|
|
657
|
-
name: formatName(v.meta, v.name) + formatDesc(v.meta),
|
|
658
|
-
value: v,
|
|
659
|
-
}));
|
|
660
|
-
variant = await showMenu('选择变体', vChoices);
|
|
661
|
-
}
|
|
662
|
-
if (!variant) continue;
|
|
583
|
+
if (copyItems.length > 0) {
|
|
584
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
585
|
+
const selected = await checkbox({
|
|
586
|
+
message: '选择要拷贝的内容:',
|
|
587
|
+
choices: copyItems,
|
|
588
|
+
});
|
|
663
589
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
590
|
+
if (selected.length > 0) {
|
|
591
|
+
const defaultBaseName = `${current.brand}-${current.series}-${current.variant}`;
|
|
592
|
+
const { promptOutputName: pn } = require('./snippet');
|
|
593
|
+
const outputBaseName = await pn(defaultBaseName, process.cwd());
|
|
594
|
+
const cwd = process.cwd();
|
|
595
|
+
const cwdRel = (p) => './' + path.relative(cwd, p);
|
|
596
|
+
|
|
597
|
+
console.log(chalk.green('\n✔ 已拷贝:'));
|
|
598
|
+
if (selected.includes('template') && versions.length > 0) {
|
|
599
|
+
const tplPath = versions[0].templatePath;
|
|
600
|
+
const dest = path.join(cwd, outputBaseName + path.extname(tplPath));
|
|
601
|
+
fs.copyFileSync(tplPath, dest);
|
|
602
|
+
console.log(` ${chalk.cyan('·')} ${cwdRel(dest)} ${chalk.gray('(模板, ' + fmtBytes(fs.statSync(dest).size) + ')')}`);
|
|
603
|
+
}
|
|
604
|
+
if (selected.includes('snippet') && fs.existsSync(snipPath)) {
|
|
605
|
+
const dest = path.join(cwd, outputBaseName + '-snippet' + path.extname(snipPath));
|
|
606
|
+
fs.copyFileSync(snipPath, dest);
|
|
607
|
+
console.log(` ${chalk.cyan('·')} ${cwdRel(dest)} ${chalk.gray('(片段, ' + fmtBytes(fs.statSync(dest).size) + ')')}`);
|
|
608
|
+
}
|
|
609
|
+
if (selected.includes('config') && configs.length > 0) {
|
|
610
|
+
const optimal = configs.find(c => c.isOptimal);
|
|
611
|
+
const cfgPath = optimal ? optimal.path : configs[0].path;
|
|
612
|
+
const dest = path.join(cwd, 'juice.yaml');
|
|
613
|
+
if (fs.existsSync(dest)) {
|
|
614
|
+
const alt = path.join(cwd, 'juice-v1.yaml');
|
|
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
|
+
}
|
|
621
|
+
}
|
|
622
|
+
console.log();
|
|
623
|
+
}
|
|
680
624
|
}
|
|
681
625
|
continue;
|
|
682
626
|
}
|