juice-email-cli 2.1.9 → 2.1.10
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 +13 -6
- package/bin/juice.js +9 -2
- package/edm/elabscience/elabscience-template.html +1 -1
- package/package.json +3 -4
- package/src/context-menu.js +52 -41
- package/src/index.js +21 -8
- package/src/snippet.js +19 -11
package/README.md
CHANGED
|
@@ -105,8 +105,8 @@ juice
|
|
|
105
105
|
| `--snippet <path>` | `-s` | 片段 HTML 文件路径:插入到模板 `<tbody id="content">` |
|
|
106
106
|
| `--config <path>` | `-c` | 配置文件路径,不指定时自动查找 |
|
|
107
107
|
| `--name <name>` | `-n` | 片段模式输出文件名(不含扩展名) |
|
|
108
|
-
| `--install` | | 注册 Windows
|
|
109
|
-
| `--uninstall` | | 取消 Windows
|
|
108
|
+
| `--install` | | 注册 Windows 右键菜单(当前用户,无需管理员) |
|
|
109
|
+
| `--uninstall` | | 取消 Windows 右键菜单注册 |
|
|
110
110
|
| `--version` | `-v` | 查看版本号 |
|
|
111
111
|
| `--help` | `-h` | 查看帮助 |
|
|
112
112
|
|
|
@@ -312,15 +312,22 @@ edm/
|
|
|
312
312
|
|
|
313
313
|
## Windows 右键菜单
|
|
314
314
|
|
|
315
|
-
注册后,在 `.html` / `.htm`
|
|
315
|
+
注册后,在 `.html` / `.htm` / `.yaml` / `.yml` 文件上右键可看到统一子菜单(所有文件类型共享):
|
|
316
316
|
|
|
317
317
|
```
|
|
318
318
|
📧 用 juice 生成邮件 HTML
|
|
319
|
-
├──
|
|
320
|
-
├── 🧩
|
|
321
|
-
|
|
319
|
+
├── 📄 作为模板,生成邮件 HTML → juice -f %1(后台执行)
|
|
320
|
+
├── 🧩 作为片段,拼接邮件 HTML → juice -s %1(交互选择模板)
|
|
321
|
+
├── ⚙️ 作为配置,拼接邮件 HTML → juice -c %1(交互选择品牌/模板/片段)
|
|
322
|
+
└── 📂 打开 PowerShell ← 仅已安装 pwsh 时出现
|
|
322
323
|
```
|
|
323
324
|
|
|
325
|
+
| 子命令 | 说明 |
|
|
326
|
+
|--------|------|
|
|
327
|
+
| 📄 作为模板,生成邮件 HTML | 将文件当模板处理,CSS 内联 + 变量替换 + 压缩 |
|
|
328
|
+
| 🧩 作为片段,拼接邮件 HTML | 将文件当片段,打开终端选择模板后拼接输出 |
|
|
329
|
+
| ⚙️ 作为配置,拼接邮件 HTML | 将文件当配置,打开终端选择品牌/模板/片段 |
|
|
330
|
+
|
|
324
331
|
```bash
|
|
325
332
|
juice --install # 注册(当前用户,无需管理员)
|
|
326
333
|
juice --uninstall # 卸载
|
package/bin/juice.js
CHANGED
|
@@ -63,10 +63,17 @@ program
|
|
|
63
63
|
右键菜单
|
|
64
64
|
════════════════════════════════════════════════════════════════
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
juice --install
|
|
66
|
+
注册后,在 .html / .htm / .yaml / .yml 文件上右键即可看到:
|
|
67
|
+
juice --install (当前用户,无需管理员)
|
|
68
68
|
juice --uninstall (卸载右键菜单)
|
|
69
69
|
|
|
70
|
+
菜单结构(所有文件类型统一):
|
|
71
|
+
📧 用 juice 生成邮件 HTML
|
|
72
|
+
├── 📄 作为模板,生成邮件 HTML → juice -f %1(后台执行)
|
|
73
|
+
├── 🧩 作为片段,拼接邮件 HTML → juice -s %1(交互选择模板)
|
|
74
|
+
├── ⚙️ 作为配置,拼接邮件 HTML → juice -c %1(交互选择品牌/模板/片段)
|
|
75
|
+
└── 📂 打开 PowerShell (仅已安装 pwsh 时出现)
|
|
76
|
+
|
|
70
77
|
更多信息:https://gitee.com/siriussupreme/juice-cli
|
|
71
78
|
`)
|
|
72
79
|
.action(async (options) => {
|
|
@@ -1524,7 +1524,7 @@
|
|
|
1524
1524
|
|
|
1525
1525
|
<tr>
|
|
1526
1526
|
<td>Quality Control</td>
|
|
1527
|
-
<td colspan="2">Sterilized by 0.1 μm filtration, Endotoxin Content
|
|
1527
|
+
<td colspan="2">Sterilized by 0.1 μm filtration, Endotoxin Content < 3 EU/mL.</td>
|
|
1528
1528
|
</tr>
|
|
1529
1529
|
</tbody>
|
|
1530
1530
|
</table>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juice-email-cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.10",
|
|
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": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/juice.js",
|
|
11
|
-
"test": "node
|
|
11
|
+
"test": "node test/smoke.js",
|
|
12
12
|
"postinstall": "node bin/juice.js --install",
|
|
13
13
|
"preuninstall": "node bin/juice.js --uninstall",
|
|
14
14
|
"prepare": "husky install",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"ora": "^5.4.1"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
49
|
+
"node": ">=16.0.0"
|
|
50
50
|
},
|
|
51
51
|
"files": [
|
|
52
52
|
"bin/",
|
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
"@commitlint/cli": "^21.0.1",
|
|
61
61
|
"@commitlint/config-conventional": "^21.0.1",
|
|
62
62
|
"husky": "^9.1.7",
|
|
63
|
-
"inquirer": "^13.4.3",
|
|
64
63
|
"standard-version": "^9.5.0"
|
|
65
64
|
}
|
|
66
65
|
}
|
package/src/context-menu.js
CHANGED
|
@@ -5,16 +5,13 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 使用 HKEY_CURRENT_USER,无需管理员权限,仅对当前用户生效。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* .html / .htm:
|
|
8
|
+
* 菜单结构(所有文件类型统一):
|
|
9
|
+
* .html / .htm / .yaml / .yml:
|
|
10
10
|
* 📧 用 juice 生成邮件 HTML
|
|
11
|
-
* ├──
|
|
12
|
-
* ├── 🧩
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* .yaml / .yml:
|
|
16
|
-
* 📧 用 juice 生成邮件 HTML
|
|
17
|
-
* └── 📋 交互式生成邮件(使用此配置) → juice -c
|
|
11
|
+
* ├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)
|
|
12
|
+
* ├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)
|
|
13
|
+
* ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)
|
|
14
|
+
* └── 📂 打开 PowerShell (可选)
|
|
18
15
|
*/
|
|
19
16
|
|
|
20
17
|
const { execSync } = require('child_process');
|
|
@@ -102,15 +99,30 @@ const SUBCMDS = {
|
|
|
102
99
|
pwsh: 'JuiceEmail.OpenPwsh',
|
|
103
100
|
};
|
|
104
101
|
|
|
105
|
-
// 旧版本可能残留的子命令名(升级时清理)
|
|
106
|
-
const LEGACY_SUBCMDS = [
|
|
107
|
-
'JuiceEmail.Generate',
|
|
108
|
-
'JuiceEmail.Snippet',
|
|
109
|
-
'JuiceEmail.OpenPwsh',
|
|
110
|
-
];
|
|
111
|
-
|
|
112
102
|
const PARENT_KEY_NAME = 'JuiceEmail';
|
|
113
103
|
|
|
104
|
+
/**
|
|
105
|
+
* 为交互式命令包一层 PowerShell:
|
|
106
|
+
* - 成功:窗口自动关闭
|
|
107
|
+
* - 失败:窗口保持开启,显示重新执行的命令
|
|
108
|
+
*/
|
|
109
|
+
function wrapInteractive(nodePath, scriptPath, cliArgs) {
|
|
110
|
+
const node = nodePath.replace(/'/g, "''");
|
|
111
|
+
const script = scriptPath.replace(/'/g, "''");
|
|
112
|
+
// %1 由 Windows 在 CreateProcess 前展开为实际文件路径
|
|
113
|
+
const ps = [
|
|
114
|
+
`& '${node}' '${script}' ${cliArgs}`,
|
|
115
|
+
`if ($LASTEXITCODE) {`,
|
|
116
|
+
` Write-Host ''`,
|
|
117
|
+
` Write-Host '[Failed] Exit code:' $LASTEXITCODE`,
|
|
118
|
+
` Write-Host '[Re-run] juice ${cliArgs}'`,
|
|
119
|
+
` Write-Host ''`,
|
|
120
|
+
` Read-Host 'Press Enter to close'`,
|
|
121
|
+
`}`,
|
|
122
|
+
].join('; ');
|
|
123
|
+
return `powershell.exe -Command "${ps}"`;
|
|
124
|
+
}
|
|
125
|
+
|
|
114
126
|
// ─── 注册 ─────────────────────────────────────────────────────────────────────
|
|
115
127
|
|
|
116
128
|
async function registerContextMenu() {
|
|
@@ -122,21 +134,21 @@ async function registerContextMenu() {
|
|
|
122
134
|
|
|
123
135
|
let ok = true;
|
|
124
136
|
|
|
125
|
-
// ── 子命令 1:普通模式 - juice
|
|
137
|
+
// ── 子命令 1:普通模式 - juice 生成(非交互,不需终端)─────────────────
|
|
126
138
|
const generateCmd = `"${nodePath}" "${scriptPath}" -f %1`;
|
|
127
|
-
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.generate}`, '', 'REG_SZ', '
|
|
139
|
+
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.generate}`, '', 'REG_SZ', '📄 作为模板,生成邮件 HTML') && ok;
|
|
128
140
|
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.generate}`, 'Icon', 'REG_SZ', iconPath);
|
|
129
141
|
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.generate}\\command`, '', 'REG_SZ', generateCmd);
|
|
130
142
|
|
|
131
|
-
// ── 子命令 2:片段模式 -
|
|
132
|
-
const snippetCmd =
|
|
133
|
-
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.snippet}`, '', 'REG_SZ', '🧩
|
|
143
|
+
// ── 子命令 2:片段模式 - 片段组装(交互,需要终端)────────────────────────
|
|
144
|
+
const snippetCmd = wrapInteractive(nodePath, scriptPath, '-s %1');
|
|
145
|
+
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.snippet}`, '', 'REG_SZ', '🧩 作为片段,拼接邮件 HTML') && ok;
|
|
134
146
|
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.snippet}`, 'Icon', 'REG_SZ', iconPath);
|
|
135
147
|
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.snippet}\\command`, '', 'REG_SZ', snippetCmd);
|
|
136
148
|
|
|
137
|
-
// ── 子命令 3:配置文件模式 -
|
|
138
|
-
const configCmd =
|
|
139
|
-
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.config}`, '', 'REG_SZ', '
|
|
149
|
+
// ── 子命令 3:配置文件模式 - 交互式生成(交互,需要终端)──────────────────
|
|
150
|
+
const configCmd = wrapInteractive(nodePath, scriptPath, '-c %1');
|
|
151
|
+
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.config}`, '', 'REG_SZ', '⚙️ 作为配置,拼接邮件 HTML') && ok;
|
|
140
152
|
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.config}`, 'Icon', 'REG_SZ', iconPath);
|
|
141
153
|
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.config}\\command`, '', 'REG_SZ', configCmd);
|
|
142
154
|
|
|
@@ -144,29 +156,30 @@ async function registerContextMenu() {
|
|
|
144
156
|
const pwshPath = resolvePwsh();
|
|
145
157
|
if (pwshPath) {
|
|
146
158
|
const pwshCmd = `"${pwshPath}" -NoExit -Command "Set-Location -LiteralPath (Split-Path '%1')"`;
|
|
147
|
-
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.pwsh}`, '', 'REG_SZ', '📂
|
|
159
|
+
ok = regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.pwsh}`, '', 'REG_SZ', '📂 打开 PowerShell') && ok;
|
|
148
160
|
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.pwsh}`, 'Icon', 'REG_SZ', pwshPath);
|
|
149
161
|
regAdd(`${SUBCMD_SPACE}\\${SUBCMDS.pwsh}\\command`, '', 'REG_SZ', pwshCmd);
|
|
150
162
|
}
|
|
151
163
|
|
|
152
|
-
// ──
|
|
153
|
-
const
|
|
154
|
-
? `${SUBCMDS.generate};${SUBCMDS.snippet};${SUBCMDS.pwsh}`
|
|
155
|
-
: `${SUBCMDS.generate};${SUBCMDS.snippet}`;
|
|
164
|
+
// ── 所有文件类型共用同一套子命令 ────────────────────────────────────────
|
|
165
|
+
const allSubs = pwshPath
|
|
166
|
+
? `${SUBCMDS.generate};${SUBCMDS.snippet};${SUBCMDS.config};${SUBCMDS.pwsh}`
|
|
167
|
+
: `${SUBCMDS.generate};${SUBCMDS.snippet};${SUBCMDS.config}`;
|
|
156
168
|
|
|
169
|
+
// .html / .htm
|
|
157
170
|
for (const root of HTML_ROOTS) {
|
|
158
171
|
const parentKey = `${root}\\${PARENT_KEY_NAME}`;
|
|
159
172
|
ok = regAdd(parentKey, 'MUIVerb', 'REG_SZ', '📧 用 juice 生成邮件 HTML') && ok;
|
|
160
173
|
regAdd(parentKey, 'Icon', 'REG_SZ', iconPath);
|
|
161
|
-
regAdd(parentKey, 'SubCommands', 'REG_SZ',
|
|
174
|
+
regAdd(parentKey, 'SubCommands', 'REG_SZ', allSubs);
|
|
162
175
|
}
|
|
163
176
|
|
|
164
|
-
//
|
|
177
|
+
// .yaml / .yml(与 .html 共享同一套子命令)
|
|
165
178
|
for (const root of YAML_ROOTS) {
|
|
166
179
|
const parentKey = `${root}\\${PARENT_KEY_NAME}`;
|
|
167
180
|
ok = regAdd(parentKey, 'MUIVerb', 'REG_SZ', '📧 用 juice 生成邮件 HTML') && ok;
|
|
168
181
|
regAdd(parentKey, 'Icon', 'REG_SZ', iconPath);
|
|
169
|
-
regAdd(parentKey, 'SubCommands', 'REG_SZ',
|
|
182
|
+
regAdd(parentKey, 'SubCommands', 'REG_SZ', allSubs);
|
|
170
183
|
}
|
|
171
184
|
|
|
172
185
|
// ── 输出 ──────────────────────────────────────────────────────────────────
|
|
@@ -177,13 +190,12 @@ async function registerContextMenu() {
|
|
|
177
190
|
console.log(
|
|
178
191
|
'\n' +
|
|
179
192
|
chalk.green(' ✔ 右键菜单注册完成!') + '\n\n' +
|
|
180
|
-
` ${chalk.bold('.html / .htm')} 文件右键:\n` +
|
|
193
|
+
` ${chalk.bold('.html / .htm / .yaml / .yml')} 文件右键:\n` +
|
|
181
194
|
` ${chalk.bold('📧 用 juice 生成邮件 HTML')}\n` +
|
|
182
|
-
` ├──
|
|
183
|
-
` ├── 🧩
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
` └── 📧 交互式生成邮件(使用此配置) → juice -c\n` +
|
|
195
|
+
` ├── 📄 作为模板,生成邮件 HTML → juice -f(后台执行)\n` +
|
|
196
|
+
` ├── 🧩 作为片段,拼接邮件 HTML → juice -s(交互选择模板)\n` +
|
|
197
|
+
` ├── ⚙️ 作为配置,拼接邮件 HTML → juice -c(交互选择品牌/模板/片段)\n` +
|
|
198
|
+
(pwshPath ? ` └── 📂 打开 PowerShell\n` : '') +
|
|
187
199
|
'\n' +
|
|
188
200
|
chalk.gray(' 注意:如菜单未出现,请重启文件资源管理器(explorer.exe)。\n')
|
|
189
201
|
);
|
|
@@ -206,9 +218,8 @@ async function unregisterContextMenu() {
|
|
|
206
218
|
if (regDelete(`${root}\\${PARENT_KEY_NAME}`)) removed++;
|
|
207
219
|
}
|
|
208
220
|
|
|
209
|
-
//
|
|
210
|
-
const
|
|
211
|
-
for (const name of allSubcmds) {
|
|
221
|
+
// 清理子命令
|
|
222
|
+
for (const name of Object.values(SUBCMDS)) {
|
|
212
223
|
regDelete(`${SUBCMD_SPACE}\\${name}`);
|
|
213
224
|
}
|
|
214
225
|
|
package/src/index.js
CHANGED
|
@@ -33,6 +33,10 @@ const HOME_CANDIDATES = [
|
|
|
33
33
|
path.join(os.homedir(), 'juice.yml'),
|
|
34
34
|
];
|
|
35
35
|
|
|
36
|
+
function resolveHomeConfig() {
|
|
37
|
+
return HOME_CANDIDATES.find((c) => fs.existsSync(c)) || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
// ─── 配置文件查找 ─────────────────────────────────────────────────────────────
|
|
37
41
|
|
|
38
42
|
/**
|
|
@@ -73,7 +77,7 @@ function findConfigs(configPath, inputFile) {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
return { highPriorityPath, homePath:
|
|
80
|
+
return { highPriorityPath, homePath: resolveHomeConfig() };
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
// ─── 配置文件加载 ─────────────────────────────────────────────────────────────
|
|
@@ -121,14 +125,13 @@ function deepMerge(base, ...overrides) {
|
|
|
121
125
|
* CLI 内置默认值 < 用户目录 ~/juice.yaml < 优先配置(-c/输入目录)
|
|
122
126
|
*/
|
|
123
127
|
function buildConfig(highPriorityPath, homePath) {
|
|
124
|
-
const homeExists = fs.existsSync(homePath);
|
|
125
128
|
const layers = [];
|
|
126
129
|
|
|
127
130
|
// 1. CLI 内置默认值(最低优先级)
|
|
128
131
|
layers.push({ label: 'CLI 内置默认值', data: CODE_DEFAULTS });
|
|
129
132
|
|
|
130
133
|
// 2. 用户目录配置(如果有)
|
|
131
|
-
if (
|
|
134
|
+
if (homePath) {
|
|
132
135
|
layers.push({ label: `用户目录配置 (${homePath})`, data: loadYaml(homePath) });
|
|
133
136
|
}
|
|
134
137
|
|
|
@@ -148,11 +151,15 @@ function processTemplate(inputFile, config) {
|
|
|
148
151
|
|
|
149
152
|
// 1. Mustache 变量替换(根据 rawHtml 配置决定是否转义 HTML)
|
|
150
153
|
const originalEscape = Mustache.escape;
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
let htmlWithVars;
|
|
155
|
+
try {
|
|
156
|
+
if (config.rawHtml) {
|
|
157
|
+
Mustache.escape = (text) => text;
|
|
158
|
+
}
|
|
159
|
+
htmlWithVars = Mustache.render(htmlRaw, config.variables || {});
|
|
160
|
+
} finally {
|
|
161
|
+
Mustache.escape = originalEscape;
|
|
153
162
|
}
|
|
154
|
-
const htmlWithVars = Mustache.render(htmlRaw, config.variables || {});
|
|
155
|
-
Mustache.escape = originalEscape;
|
|
156
163
|
|
|
157
164
|
// 2. 收集 extraCssFiles
|
|
158
165
|
const basePath = path.dirname(path.resolve(inputFile));
|
|
@@ -228,10 +235,16 @@ async function run({ file, config: configPath }) {
|
|
|
228
235
|
|
|
229
236
|
// 5. 写出标准版
|
|
230
237
|
spinner.text = '写出 .output.html ...';
|
|
238
|
+
if (fs.existsSync(outPaths.normal)) {
|
|
239
|
+
spinner.warn(chalk.yellow(`目标文件已存在,将覆盖:${outPaths.normal}`));
|
|
240
|
+
}
|
|
231
241
|
fs.writeFileSync(outPaths.normal, resultHtml, encoding);
|
|
232
242
|
|
|
233
243
|
// 6. 压缩并写出
|
|
234
244
|
spinner.text = '写出 .minified.html ...';
|
|
245
|
+
if (fs.existsSync(outPaths.minified)) {
|
|
246
|
+
spinner.warn(chalk.yellow(`目标文件已存在,将覆盖:${outPaths.minified}`));
|
|
247
|
+
}
|
|
235
248
|
const minified = await minifyHtml(resultHtml, config.minify);
|
|
236
249
|
fs.writeFileSync(outPaths.minified, minified, encoding);
|
|
237
250
|
|
|
@@ -266,4 +279,4 @@ function savings(original, minified) {
|
|
|
266
279
|
return (((orig - mini) / orig) * 100).toFixed(1) + '%';
|
|
267
280
|
}
|
|
268
281
|
|
|
269
|
-
module.exports = { run, findConfigs, buildConfig, processTemplate, minifyHtml, deepMerge, collectExtraCss, loadYaml, fmtSize, savings };
|
|
282
|
+
module.exports = { run, findConfigs, buildConfig, processTemplate, minifyHtml, deepMerge, collectExtraCss, loadYaml, fmtSize, savings, DEFAULT_CONFIG_PATH, resolveHomeConfig };
|
package/src/snippet.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
minifyHtml,
|
|
14
14
|
fmtSize,
|
|
15
15
|
savings,
|
|
16
|
+
DEFAULT_CONFIG_PATH,
|
|
16
17
|
} = require('./index');
|
|
17
18
|
|
|
18
19
|
// ─── EDM 目录解析 ──────────────────────────────────────────────────────────────
|
|
@@ -95,8 +96,6 @@ function getBrand(filePath, edmDir) {
|
|
|
95
96
|
|
|
96
97
|
// ─── 配置合并 ─────────────────────────────────────────────────────────────────
|
|
97
98
|
|
|
98
|
-
const DEFAULT_CONFIG_PATH = path.resolve(__dirname, '..', 'defaults', 'juice.yaml');
|
|
99
|
-
|
|
100
99
|
/**
|
|
101
100
|
* 片段模式配置合并
|
|
102
101
|
*
|
|
@@ -114,8 +113,12 @@ function buildSnippetConfig({ priorityConfigPath, cliConfigPath }) {
|
|
|
114
113
|
layers.push({ label: 'CLI 内置默认值', data: defaults });
|
|
115
114
|
|
|
116
115
|
// 2. 用户主目录
|
|
117
|
-
const
|
|
118
|
-
|
|
116
|
+
const homeCandidates = [
|
|
117
|
+
path.join(os.homedir(), 'juice.yaml'),
|
|
118
|
+
path.join(os.homedir(), 'juice.yml'),
|
|
119
|
+
];
|
|
120
|
+
const homePath = homeCandidates.find((c) => fs.existsSync(c));
|
|
121
|
+
if (homePath) {
|
|
119
122
|
layers.push({ label: `用户目录配置 (${homePath})`, data: loadYaml(homePath) });
|
|
120
123
|
}
|
|
121
124
|
|
|
@@ -246,7 +249,7 @@ function resolveSnippetOutputPaths(outputBaseName, cwd) {
|
|
|
246
249
|
* 3. Juice CSS 内联 → .output.html
|
|
247
250
|
* 4. 压缩 → .minified.html
|
|
248
251
|
*/
|
|
249
|
-
async function assembleSnippet({ snippetPath, templatePath, config,
|
|
252
|
+
async function assembleSnippet({ snippetPath, templatePath, config, cwd, outputBaseName }) {
|
|
250
253
|
const templateHtml = fs.readFileSync(templatePath, 'utf8');
|
|
251
254
|
const snippetRaw = fs.readFileSync(snippetPath, 'utf8');
|
|
252
255
|
const outPaths = resolveSnippetOutputPaths(outputBaseName, cwd);
|
|
@@ -258,11 +261,15 @@ async function assembleSnippet({ snippetPath, templatePath, config, layers, cwd,
|
|
|
258
261
|
|
|
259
262
|
// 2. Mustache 渲染合并 HTML → .html(已渲染,无 juice 内联)
|
|
260
263
|
const originalEscape = Mustache.escape;
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
let renderedHtml;
|
|
265
|
+
try {
|
|
266
|
+
if (config.rawHtml) {
|
|
267
|
+
Mustache.escape = (text) => text;
|
|
268
|
+
}
|
|
269
|
+
renderedHtml = Mustache.render(rawMarkup, variables);
|
|
270
|
+
} finally {
|
|
271
|
+
Mustache.escape = originalEscape;
|
|
263
272
|
}
|
|
264
|
-
const renderedHtml = Mustache.render(rawMarkup, variables);
|
|
265
|
-
Mustache.escape = originalEscape;
|
|
266
273
|
fs.writeFileSync(outPaths.normal, renderedHtml, 'utf8');
|
|
267
274
|
|
|
268
275
|
// 3. 收集模板目录的额外 CSS + Juice CSS 内联 → .output.html
|
|
@@ -278,6 +285,7 @@ async function assembleSnippet({ snippetPath, templatePath, config, layers, cwd,
|
|
|
278
285
|
fs.writeFileSync(outPaths.minified, minified, 'utf8');
|
|
279
286
|
|
|
280
287
|
// 5. 报告
|
|
288
|
+
const layers = config._layers || [];
|
|
281
289
|
const layerLines = layers
|
|
282
290
|
.map((l) => ` ${chalk.gray('·')} ${l.label}`)
|
|
283
291
|
.join('\n');
|
|
@@ -585,6 +593,7 @@ async function runSnippetMode({ snippet, template, config: cliConfigPath, output
|
|
|
585
593
|
}
|
|
586
594
|
|
|
587
595
|
const { config, layers } = buildSnippetConfig({ priorityConfigPath, cliConfigPath });
|
|
596
|
+
config._layers = layers;
|
|
588
597
|
|
|
589
598
|
// 确定输出文件名
|
|
590
599
|
const defaultBaseName = outputName || path.parse(templatePath).name;
|
|
@@ -621,7 +630,6 @@ async function runSnippetMode({ snippet, template, config: cliConfigPath, output
|
|
|
621
630
|
snippetPath,
|
|
622
631
|
templatePath,
|
|
623
632
|
config,
|
|
624
|
-
layers,
|
|
625
633
|
cwd: process.cwd(),
|
|
626
634
|
outputBaseName,
|
|
627
635
|
});
|
|
@@ -697,12 +705,12 @@ async function runInteractiveMode({ config: cliConfigPath }) {
|
|
|
697
705
|
|
|
698
706
|
// 8. 构建配置并执行
|
|
699
707
|
const { config, layers } = buildSnippetConfig({ priorityConfigPath, cliConfigPath });
|
|
708
|
+
config._layers = layers;
|
|
700
709
|
|
|
701
710
|
await assembleSnippet({
|
|
702
711
|
snippetPath: snippetFile.path,
|
|
703
712
|
templatePath: templateChoice.path,
|
|
704
713
|
config,
|
|
705
|
-
layers,
|
|
706
714
|
cwd: process.cwd(),
|
|
707
715
|
outputBaseName,
|
|
708
716
|
});
|