juice-email-cli 2.3.14 → 2.3.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juice-email-cli",
3
- "version": "2.3.14",
3
+ "version": "2.3.15",
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
@@ -5,7 +5,7 @@ const path = require('path');
5
5
  const chalk = require('chalk');
6
6
  const {
7
7
  resolveEdmDir, loadMeta, findBrands, findTemplateVersions,
8
- findSeriesDirs, findSnippetVariants, findConfigs,
8
+ findSeriesDirs, filterSeries, findSnippetVariants, findConfigs,
9
9
  promptOutputName,
10
10
  } = require('./snippet');
11
11
 
@@ -92,6 +92,7 @@ async function selectWithNav(message, choices, showBack) {
92
92
  async function interactiveInit(edmDir, cwd) {
93
93
  let step = 'brand';
94
94
  let brand = null, version = null, series = null, variant = null;
95
+ let hadVariantStep = false; // track if variant step was actually shown
95
96
 
96
97
  while (true) {
97
98
  if (step === 'brand') {
@@ -122,7 +123,9 @@ async function interactiveInit(edmDir, cwd) {
122
123
  }
123
124
 
124
125
  if (step === 'series') {
125
- const allSeries = findSeriesDirs(brand.path);
126
+ let allSeries = findSeriesDirs(brand.path);
127
+ // Apply template version series filtering (allow/block in _meta.yaml)
128
+ allSeries = filterSeries(allSeries, version.meta);
126
129
  if (allSeries.length === 0) {
127
130
  series = null;
128
131
  step = 'copy';
@@ -150,6 +153,7 @@ async function interactiveInit(edmDir, cwd) {
150
153
  step = 'copy';
151
154
  continue;
152
155
  }
156
+ hadVariantStep = true;
153
157
  const choices = variants.map(v => ({
154
158
  name: formatName(v.meta, v.name) + (v.meta.description ? ' — ' + chalk.dim(v.meta.description) : ''),
155
159
  value: v,
@@ -218,7 +222,7 @@ async function interactiveInit(edmDir, cwd) {
218
222
  mainChoices,
219
223
  true
220
224
  );
221
- if (action === 'back') { step = series ? 'variant' : 'series'; break; }
225
+ if (action === 'back') { step = hadVariantStep ? 'variant' : (series ? 'series' : 'version'); break; }
222
226
  if (action === 'exit') { console.log(chalk.gray('已退出。\n')); return; }
223
227
  if (action === 'edit') {
224
228
  const { checkbox } = await import('@inquirer/prompts');
@@ -234,7 +238,7 @@ async function interactiveInit(edmDir, cwd) {
234
238
  // fall through to confirmation below
235
239
  } else if (selected.length === 0) {
236
240
  console.log(chalk.gray('未选择任何内容。\n'));
237
- step = series ? 'variant' : 'series';
241
+ step = hadVariantStep ? 'variant' : (series ? 'series' : 'version');
238
242
  break;
239
243
  }
240
244
 
package/src/snippet.js CHANGED
@@ -204,24 +204,6 @@ function findConfigs(variantDir, sourceLabel) {
204
204
  * Collect configs from multiple directories, deduplicating by path.
205
205
  * Returns { name, path, isOptimal, source }[].
206
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
-
225
207
  function findFiles(dir, regex) {
226
208
  const entries = fs.readdirSync(dir, { withFileTypes: true });
227
209
  return entries
@@ -425,11 +407,11 @@ async function assembleSnippet({ snippetPath, templatePath, config, cwd, outputB
425
407
  const outPaths = resolveSnippetOutputPaths(outputBaseName, cwd);
426
408
  const variables = Object.assign({}, config.variables || {});
427
409
 
428
- // 1. 未渲染的片段插入模板 → .raw.html(Mustache 标签保留,无 juice)
410
+ // 1. 未渲染的片段插入模板 → .raw.html
429
411
  const rawMarkup = insertIntoContent(templateHtml, snippetRaw);
430
412
  fs.writeFileSync(outPaths.raw, rawMarkup, 'utf8');
431
413
 
432
- // 2. Mustache 渲染合并 HTML → .html(已渲染,无 juice 内联)
414
+ // 2. Mustache 渲染 → .html
433
415
  const originalEscape = Mustache.escape;
434
416
  let renderedHtml;
435
417
  try {
@@ -442,34 +424,45 @@ async function assembleSnippet({ snippetPath, templatePath, config, cwd, outputB
442
424
  }
443
425
  fs.writeFileSync(outPaths.normal, renderedHtml, 'utf8');
444
426
 
445
- // 3. 收集模板目录的额外 CSS + Juice CSS 内联 → .output.html
427
+ // 3. Juice CSS 内联 → .output.html
446
428
  const templateDir = path.dirname(templatePath);
447
429
  const extraCss = collectExtraCss(templateDir, config);
448
430
  const juiceOpts = Object.assign({}, config.juice || {});
449
431
  delete juiceOpts.extraCssFiles;
450
- const processed = juice(renderedHtml, { ...juiceOpts, extraCss });
432
+ let processed;
433
+ try {
434
+ processed = juice(renderedHtml, { ...juiceOpts, extraCss });
435
+ } catch (err) {
436
+ throw new Error(`CSS 内联失败:${err.message}`);
437
+ }
451
438
  fs.writeFileSync(outPaths.output, processed, 'utf8');
452
439
 
453
440
  // 4. 压缩 → .minified.html
454
- const minified = await minifyHtml(processed, config.minify);
441
+ let minified;
442
+ try {
443
+ minified = await minifyHtml(processed, config.minify);
444
+ } catch (err) {
445
+ throw new Error(`压缩失败:${err.message}`);
446
+ }
455
447
  fs.writeFileSync(outPaths.minified, minified, 'utf8');
456
448
 
457
449
  // 5. 报告
450
+ const rel = (p) => './' + path.relative(cwd, p);
458
451
  const layers = config._layers || [];
459
- const layerLines = layers
460
- .map((l) => ` ${chalk.gray('·')} ${l.label}`)
461
- .join('\n');
452
+ const layerLines = layers.length > 0
453
+ ? layers.map((l) => ` ${chalk.gray('·')} ${l.label}`).join('\n') + '\n'
454
+ : '';
462
455
 
463
456
  console.log(
464
457
  chalk.green('\n✔ 片段组装完成') + '\n' +
465
458
  ` ${chalk.bold('片段:')} ${chalk.cyan(snippetPath)}\n` +
466
459
  ` ${chalk.bold('模板:')} ${chalk.cyan(templatePath)}\n` +
467
- ` ${chalk.bold('配置层(低→高):')}\n${layerLines}\n` +
460
+ ` ${chalk.bold('配置层(低→高):')}\n${layerLines}` +
468
461
  ` ${chalk.bold('输出:')}\n` +
469
- ` ${chalk.green('·')} 原始组装 ${chalk.cyan(outPaths.raw)} ${chalk.gray('(' + fmtSize(rawMarkup) + ')')}\n` +
470
- ` ${chalk.green('·')} 已渲染 ${chalk.cyan(outPaths.normal)} ${chalk.gray('(' + fmtSize(renderedHtml) + ')')}\n` +
471
- ` ${chalk.green('·')} 内联后 ${chalk.cyan(outPaths.output)} ${chalk.gray('(' + fmtSize(processed) + ')')}\n` +
472
- ` ${chalk.green('·')} 压缩版 ${chalk.cyan(outPaths.minified)} ${chalk.gray('(' + fmtSize(minified) + ',节省 ' + savings(processed, minified) + ')')}`
462
+ ` ${chalk.green('·')} 原始组装 ${chalk.cyan(rel(outPaths.raw))} ${chalk.gray(fmtSize(rawMarkup))}\n` +
463
+ ` ${chalk.green('·')} 已渲染 ${chalk.cyan(rel(outPaths.normal))} ${chalk.gray(fmtSize(renderedHtml))}\n` +
464
+ ` ${chalk.green('·')} 内联后 ${chalk.cyan(rel(outPaths.output))} ${chalk.gray(fmtSize(processed))}\n` +
465
+ ` ${chalk.green('·')} 压缩版 ${chalk.cyan(rel(outPaths.minified))} ${chalk.gray(fmtSize(minified) + ',节省 ' + savings(processed, minified))}`
473
466
  );
474
467
 
475
468
  return outPaths;
@@ -715,7 +708,7 @@ async function promptConfirm(summary) {
715
708
  console.log(` 片段变体: ${chalk.green(summary.variantName)} ${chalk.gray(`(${summary.variant})`)}`);
716
709
  console.log(` 配置 YAML: ${chalk.green(summary.configFile)}`);
717
710
  console.log(chalk.gray('───────────────────────────────────────────'));
718
- console.log(` 输出目录: ${chalk.cyan(summary.outputDir)}`);
711
+ console.log(` 输出目录: ${chalk.cyan('./')}`);
719
712
  console.log(` 输出文件名: ${chalk.green(summary.outputBaseName)}`);
720
713
  console.log(` 输出文件:`);
721
714
  console.log(` ${chalk.green('·')} ${path.basename(outPaths.raw)} ${chalk.gray('(未渲染,无 CSS 内联)')}`);
@@ -969,7 +962,6 @@ module.exports = {
969
962
  findHtmlFiles,
970
963
  findYamlFiles,
971
964
  findLocalConfig,
972
- collectConfigs,
973
965
  getBrand,
974
966
  filterSeries,
975
967
  // config