juice-email-cli 2.3.6 → 2.3.8

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.
Files changed (3) hide show
  1. package/bin/juice.js +29 -6
  2. package/package.json +1 -1
  3. package/src/init.js +13 -11
package/bin/juice.js CHANGED
@@ -5,6 +5,29 @@ const { program } = require('commander');
5
5
  const path = require('path');
6
6
  const pkg = require('../package.json');
7
7
  const { run } = require('../src/index');
8
+ let ExitPromptError;
9
+ try {
10
+ ({ ExitPromptError } = require('@inquirer/core'));
11
+ } catch (_) {}
12
+
13
+ /**
14
+ * Wrap an async action handler to catch Ctrl+C and other errors gracefully.
15
+ */
16
+ function safeAction(fn) {
17
+ return async (...args) => {
18
+ try {
19
+ await fn(...args);
20
+ } catch (err) {
21
+ if (ExitPromptError && err instanceof ExitPromptError) {
22
+ // Ctrl+C — exit silently
23
+ process.exit(0);
24
+ }
25
+ console.error('\n' + require('chalk').red(` ✘ ${err.message}`));
26
+ if (process.env.DEBUG) console.error(err);
27
+ process.exit(1);
28
+ }
29
+ };
30
+ }
8
31
 
9
32
  program
10
33
  .name('juice')
@@ -87,7 +110,7 @@ program
87
110
  .option('--templates', '列出所有模板')
88
111
  .option('--series', '列出所有系列')
89
112
  .option('--snippets', '列出所有片段')
90
- .action(async (viewPath, options) => {
113
+ .action(safeAction(async (viewPath, options) => {
91
114
  const { runViewMode } = require('../src/view');
92
115
  await runViewMode({
93
116
  viewPath: viewPath || null,
@@ -96,7 +119,7 @@ program
96
119
  : (options.series ? 'series'
97
120
  : (options.snippets ? 'snippets' : null)),
98
121
  });
99
- });
122
+ }));
100
123
 
101
124
  // ─── Subcommand: juice init ─────────────────────────────────────────────
102
125
  program
@@ -106,7 +129,7 @@ program
106
129
  .option('-s, --snippet <file-path>', '仅拷贝片段 HTML 文件')
107
130
  .option('-c, --config <file-path>', '仅拷贝配置 YAML 文件')
108
131
  .option('--all [target]', '拷贝整个 EDM 资源目录到当前或指定目录')
109
- .action(async (initPath, options) => {
132
+ .action(safeAction(async (initPath, options) => {
110
133
  const { runInitMode } = require('../src/init');
111
134
  await runInitMode({
112
135
  initPath: initPath || null,
@@ -115,11 +138,11 @@ program
115
138
  config: options.config || null,
116
139
  all: options.all !== undefined ? (options.all || true) : null,
117
140
  });
118
- });
141
+ }));
119
142
 
120
143
  // ─── Default action (backward compatible) ───────────────────────────────
121
144
  program
122
- .action(async (options) => {
145
+ .action(safeAction(async (options) => {
123
146
  // 注册/卸载右键菜单
124
147
  if (options.install) {
125
148
  const { registerContextMenu } = require('../src/context-menu');
@@ -161,7 +184,7 @@ program
161
184
  // 全交互模式:无 --snippet,无 -f
162
185
  const { runInteractiveMode } = require('../src/snippet');
163
186
  await runInteractiveMode({ config: options.config });
164
- });
187
+ }));
165
188
 
166
189
  program.allowUnknownOption(false);
167
190
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juice-email-cli",
3
- "version": "2.3.6",
3
+ "version": "2.3.8",
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
@@ -93,13 +93,13 @@ async function directCopy(srcPath, cwd) {
93
93
  * Wrap @inquirer/prompts select with back/exit navigation.
94
94
  * Returns the chosen value, 'back', or 'exit'.
95
95
  */
96
- async function selectWithNav(choices, showBack) {
96
+ async function selectWithNav(message, choices, showBack) {
97
97
  const { select } = await import('@inquirer/prompts');
98
98
  const all = [];
99
99
  if (showBack) all.push({ name: '.. 返回上级', value: 'back' });
100
100
  all.push(...choices);
101
101
  all.push({ name: '✕ 退出', value: 'exit' });
102
- return select({ message: choices[0] ? undefined : '', choices: all, loop: false });
102
+ return select({ message, choices: all, loop: false });
103
103
  }
104
104
 
105
105
  async function interactiveInit(edmDir, cwd) {
@@ -113,7 +113,7 @@ async function interactiveInit(edmDir, cwd) {
113
113
  name: formatName(b.meta, b.name) + (b.meta.description ? ' — ' + chalk.dim(b.meta.description) : ''),
114
114
  value: b,
115
115
  }));
116
- const result = await selectWithNav(choices, false);
116
+ const result = await selectWithNav('选择品牌:', choices, false);
117
117
  if (result === 'exit') { console.log(chalk.gray('已退出。\n')); return; }
118
118
  brand = result;
119
119
  step = 'version';
@@ -126,7 +126,7 @@ async function interactiveInit(edmDir, cwd) {
126
126
  name: formatName(v.meta, v.name) + (v.meta.description ? ' — ' + chalk.dim(v.meta.description) : ''),
127
127
  value: v,
128
128
  }));
129
- const result = await selectWithNav(choices, true);
129
+ const result = await selectWithNav('选择模板版本:', choices, true);
130
130
  if (result === 'back') { step = 'brand'; continue; }
131
131
  if (result === 'exit') { console.log(chalk.gray('已退出。\n')); return; }
132
132
  version = result;
@@ -148,7 +148,7 @@ async function interactiveInit(edmDir, cwd) {
148
148
  value: s,
149
149
  })),
150
150
  ];
151
- const result = await selectWithNav(choices, true);
151
+ const result = await selectWithNav('选择片段系列:', choices, true);
152
152
  if (result === 'back') { step = 'version'; continue; }
153
153
  if (result === 'exit') { console.log(chalk.gray('已退出。\n')); return; }
154
154
  series = result;
@@ -167,7 +167,7 @@ async function interactiveInit(edmDir, cwd) {
167
167
  name: formatName(v.meta, v.name) + (v.meta.description ? ' — ' + chalk.dim(v.meta.description) : ''),
168
168
  value: v,
169
169
  }));
170
- const result = await selectWithNav(choices, true);
170
+ const result = await selectWithNav('选择片段变体:', choices, true);
171
171
  if (result === 'back') { step = 'series'; continue; }
172
172
  if (result === 'exit') { console.log(chalk.gray('已退出。\n')); return; }
173
173
  variant = result;
@@ -367,11 +367,13 @@ async function runInitMode({ initPath, template, snippet, config, all }) {
367
367
 
368
368
  // --all: copy entire EDM directory
369
369
  if (all) {
370
- let edmDir;
371
- try {
372
- edmDir = resolveEdmDir();
373
- } catch (err) {
374
- console.error(chalk.red(`\n ✘ ${err.message}\n`));
370
+ // Always use package built-in EDM for --all (not CWD's local copy)
371
+ const pkgEdm = path.resolve(__dirname, '..', 'edm');
372
+ const edmDir = fs.existsSync(pkgEdm) ? pkgEdm : (() => {
373
+ try { return resolveEdmDir(); } catch (_) { return null; }
374
+ })();
375
+ if (!edmDir) {
376
+ console.error(chalk.red('\n ✘ EDM 资源库不存在,无法拷贝。\n'));
375
377
  process.exit(1);
376
378
  }
377
379
  const target = all === true ? cwd : path.resolve(all);