juice-email-cli 2.3.11 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juice-email-cli",
3
- "version": "2.3.11",
3
+ "version": "2.3.12",
4
4
  "description": "CLI tool to generate email-client-compatible HTML with juice (CSS inlining) + Mustache templating + minification",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/init.js CHANGED
@@ -231,10 +231,8 @@ async function interactiveInit(edmDir, cwd) {
231
231
  }
232
232
  if (action === 'snippet-config') {
233
233
  selected = ['snippet', 'config'];
234
- continue;
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
- choices.push({
536
- name: c.isOptimal
537
- ? `${chalk.green('')} ${c.name} ${chalk.green('(最优配对)')}`
538
- : ` ${c.name}`,
539
- value: { type: 'file', path: c.path, name: c.name },
540
- });
541
- if (c.isOptimal) defaultIdx = choices.length - 1;
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 = findConfigs(snippetDir);
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, variant.path);
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