juice-email-cli 2.3.10 → 2.3.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.
- package/README.md +11 -1
- package/package.json +1 -1
- package/src/init.js +5 -7
- package/src/snippet.js +64 -16
package/README.md
CHANGED
|
@@ -122,9 +122,12 @@ juice view -i elabscience
|
|
|
122
122
|
### 拷贝资源到当前目录
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
|
-
#
|
|
125
|
+
# 交互式选择(每步支持返回/退出)
|
|
126
126
|
juice init
|
|
127
127
|
|
|
128
|
+
# 拷贝整个 EDM 资源库
|
|
129
|
+
juice init --all
|
|
130
|
+
|
|
128
131
|
# 从指定 EDM 路径拷贝
|
|
129
132
|
juice init elabscience/templates/standard
|
|
130
133
|
|
|
@@ -134,6 +137,13 @@ juice init --snippet edm/elabscience/series/literature/default/snippet.html
|
|
|
134
137
|
juice init --config edm/elabscience/series/literature/default/juice.yaml
|
|
135
138
|
```
|
|
136
139
|
|
|
140
|
+
交互拷贝进入变体后提供三个操作:
|
|
141
|
+
- `🧩 仅片段+配置` — 快捷拷贝片段 HTML + juice.yaml(跳过模板)
|
|
142
|
+
- `✅ 确认拷贝` — 按当前选择执行
|
|
143
|
+
- `🔄 自定义选择` — 打开多选 checkbox 调整
|
|
144
|
+
|
|
145
|
+
拷贝模板时自动附带 `favicon.ico`。配置文件冲突自动加版本号。
|
|
146
|
+
|
|
137
147
|
### 参数说明
|
|
138
148
|
|
|
139
149
|
| 参数 | 简写 | 说明 |
|
package/package.json
CHANGED
package/src/init.js
CHANGED
|
@@ -204,13 +204,13 @@ async function interactiveInit(edmDir, cwd) {
|
|
|
204
204
|
.map(c => c.name.replace(/^[^\s]+\s/, ''));
|
|
205
205
|
const summary = picked.length > 0 ? picked.join(' + ') : '(未选择)';
|
|
206
206
|
const mainChoices = [];
|
|
207
|
-
if (picked.length > 0) {
|
|
208
|
-
mainChoices.push({ name: `✅ 确认拷贝(${summary})`, value: 'confirm' });
|
|
209
|
-
}
|
|
210
207
|
// Quick shortcut: snippet + config only (skip template)
|
|
211
208
|
if (variant && copyItems.some(c => c.value === 'snippet') && copyItems.some(c => c.value === 'config')) {
|
|
212
209
|
mainChoices.push({ name: '🧩 仅片段 + 配置', value: 'snippet-config' });
|
|
213
210
|
}
|
|
211
|
+
if (picked.length > 0) {
|
|
212
|
+
mainChoices.push({ name: `✅ 确认拷贝(${summary})`, value: 'confirm' });
|
|
213
|
+
}
|
|
214
214
|
mainChoices.push({ name: `🔄 自定义选择(${summary})`, value: 'edit' });
|
|
215
215
|
|
|
216
216
|
const action = await selectWithNav(
|
|
@@ -231,10 +231,8 @@ async function interactiveInit(edmDir, cwd) {
|
|
|
231
231
|
}
|
|
232
232
|
if (action === 'snippet-config') {
|
|
233
233
|
selected = ['snippet', 'config'];
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (selected.length === 0) {
|
|
234
|
+
// fall through to confirmation below
|
|
235
|
+
} else if (selected.length === 0) {
|
|
238
236
|
console.log(chalk.gray('未选择任何内容。\n'));
|
|
239
237
|
step = series ? 'variant' : 'series';
|
|
240
238
|
break;
|
package/src/snippet.js
CHANGED
|
@@ -186,9 +186,9 @@ function findSnippetVariants(seriesDir) {
|
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
188
|
* 列出变体目录下所有 YAML 配置,标记 juice.yaml 为最优配对。
|
|
189
|
-
* 返回 { name, path, isOptimal }。
|
|
189
|
+
* 返回 { name, path, isOptimal, source }。
|
|
190
190
|
*/
|
|
191
|
-
function findConfigs(variantDir) {
|
|
191
|
+
function findConfigs(variantDir, sourceLabel) {
|
|
192
192
|
const yamlFiles = findYamlFiles(variantDir);
|
|
193
193
|
return yamlFiles
|
|
194
194
|
.filter((f) => f.name !== '_meta.yaml' && f.name !== '_meta.yml')
|
|
@@ -196,9 +196,32 @@ function findConfigs(variantDir) {
|
|
|
196
196
|
name: f.name,
|
|
197
197
|
path: f.path,
|
|
198
198
|
isOptimal: f.name === 'juice.yaml',
|
|
199
|
+
source: sourceLabel || path.basename(path.dirname(f.path)),
|
|
199
200
|
}));
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Collect configs from multiple directories, deduplicating by path.
|
|
205
|
+
* Returns { name, path, isOptimal, source }[].
|
|
206
|
+
*/
|
|
207
|
+
function collectConfigs(dirs) {
|
|
208
|
+
const seen = new Set();
|
|
209
|
+
const result = [];
|
|
210
|
+
for (const { dir, label, markOptimal } of dirs) {
|
|
211
|
+
if (!fs.existsSync(dir)) continue;
|
|
212
|
+
const configs = findConfigs(dir, label);
|
|
213
|
+
for (const c of configs) {
|
|
214
|
+
if (seen.has(c.path)) continue;
|
|
215
|
+
seen.add(c.path);
|
|
216
|
+
if (markOptimal && c.name === 'juice.yaml') {
|
|
217
|
+
c.isOptimal = true;
|
|
218
|
+
}
|
|
219
|
+
result.push(c);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
202
225
|
function findFiles(dir, regex) {
|
|
203
226
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
204
227
|
return entries
|
|
@@ -525,21 +548,42 @@ async function promptSnippetVariant(variants) {
|
|
|
525
548
|
});
|
|
526
549
|
}
|
|
527
550
|
|
|
528
|
-
async function promptConfig(configs) {
|
|
551
|
+
async function promptConfig(configs, preferredPath) {
|
|
529
552
|
const { select, input } = await import('@inquirer/prompts');
|
|
530
553
|
|
|
554
|
+
// Check if any names collide — if so, show source in the label
|
|
555
|
+
const nameCounts = {};
|
|
556
|
+
for (const c of configs) {
|
|
557
|
+
nameCounts[c.name] = (nameCounts[c.name] || 0) + 1;
|
|
558
|
+
}
|
|
559
|
+
const hasCollisions = Object.values(nameCounts).some(n => n > 1);
|
|
560
|
+
|
|
531
561
|
const choices = [];
|
|
532
562
|
let defaultIdx = 0;
|
|
563
|
+
let preferredIdx = -1;
|
|
533
564
|
|
|
534
565
|
for (const c of configs) {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (c.isOptimal)
|
|
566
|
+
let label = c.name;
|
|
567
|
+
if (hasCollisions && c.source) {
|
|
568
|
+
label += ` ${chalk.dim('(' + c.source + ')')}`;
|
|
569
|
+
}
|
|
570
|
+
const isPreferred = preferredPath && c.path === preferredPath;
|
|
571
|
+
if (isPreferred) preferredIdx = choices.length;
|
|
572
|
+
if (isPreferred || c.isOptimal) {
|
|
573
|
+
const tag = isPreferred ? '指定' : '最优配对';
|
|
574
|
+
choices.push({
|
|
575
|
+
name: `${chalk.green('●')} ${label} ${chalk.green('(' + tag + ')')}`,
|
|
576
|
+
value: { type: 'file', path: c.path, name: c.name },
|
|
577
|
+
});
|
|
578
|
+
} else {
|
|
579
|
+
choices.push({
|
|
580
|
+
name: ` ${label}`,
|
|
581
|
+
value: { type: 'file', path: c.path, name: c.name },
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (!isPreferred && c.isOptimal) defaultIdx = choices.length - 1;
|
|
542
585
|
}
|
|
586
|
+
if (preferredIdx >= 0) defaultIdx = preferredIdx;
|
|
543
587
|
|
|
544
588
|
choices.push(
|
|
545
589
|
{ name: ' [自定义] 输入其他路径...', value: { type: 'custom' } },
|
|
@@ -720,16 +764,19 @@ async function runSnippetMode({ snippet, template, config: cliConfigPath, output
|
|
|
720
764
|
templatePath = version.templatePath;
|
|
721
765
|
}
|
|
722
766
|
|
|
723
|
-
//
|
|
767
|
+
// 配置文件:收集当前目录和片段目录的配置,片段目录优先
|
|
724
768
|
const snippetDir = path.dirname(snippetPath);
|
|
725
769
|
let priorityConfigPath;
|
|
726
770
|
|
|
727
771
|
if (template) {
|
|
728
772
|
// 命令行完整指定(-s + -f),自动检测
|
|
729
|
-
priorityConfigPath = findLocalConfig(snippetDir);
|
|
773
|
+
priorityConfigPath = findLocalConfig(snippetDir) || findLocalConfig(process.cwd());
|
|
730
774
|
} else {
|
|
731
|
-
// 交互模式(只有 -s
|
|
732
|
-
const configs =
|
|
775
|
+
// 交互模式(只有 -s),收集 CWD + 片段目录配置
|
|
776
|
+
const configs = collectConfigs([
|
|
777
|
+
{ dir: snippetDir, label: path.basename(snippetDir), markOptimal: true },
|
|
778
|
+
{ dir: process.cwd(), label: '当前目录' },
|
|
779
|
+
]);
|
|
733
780
|
const configChoice = await promptConfig(configs);
|
|
734
781
|
priorityConfigPath = (configChoice.type === 'file') ? configChoice.path : null;
|
|
735
782
|
}
|
|
@@ -834,8 +881,8 @@ async function runInteractiveMode({ config: cliConfigPath }) {
|
|
|
834
881
|
}
|
|
835
882
|
|
|
836
883
|
// 5. 选择配置文件
|
|
837
|
-
const configs = findConfigs(variant.path);
|
|
838
|
-
const configChoice = await promptConfig(configs,
|
|
884
|
+
const configs = findConfigs(variant.path, path.basename(variant.path));
|
|
885
|
+
const configChoice = await promptConfig(configs, cliConfigPath);
|
|
839
886
|
|
|
840
887
|
let priorityConfigPath = null;
|
|
841
888
|
let configFileName = '(跳过)';
|
|
@@ -895,6 +942,7 @@ module.exports = {
|
|
|
895
942
|
findHtmlFiles,
|
|
896
943
|
findYamlFiles,
|
|
897
944
|
findLocalConfig,
|
|
945
|
+
collectConfigs,
|
|
898
946
|
getBrand,
|
|
899
947
|
filterSeries,
|
|
900
948
|
// config
|