juice-email-cli 2.3.15 → 2.4.1
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/bin/juice.js +37 -22
- package/package.json +9 -8
- package/src/context-menu.js +8 -7
- package/src/index.js +39 -72
- package/src/init.js +10 -9
- package/src/snippet.js +12 -11
- package/src/view.js +11 -13
package/bin/juice.js
CHANGED
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, resolve } from 'path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { run } from '../src/index.js';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
|
|
3
11
|
|
|
4
|
-
const { program } = require('commander');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const pkg = require('../package.json');
|
|
7
|
-
const { run } = require('../src/index');
|
|
8
12
|
let ExitPromptError;
|
|
9
13
|
try {
|
|
10
|
-
({ ExitPromptError } =
|
|
14
|
+
({ ExitPromptError } = await import('@inquirer/core'));
|
|
11
15
|
} catch (_) {}
|
|
12
16
|
|
|
13
|
-
/**
|
|
14
|
-
* Wrap an async action handler to catch Ctrl+C and other errors gracefully.
|
|
15
|
-
*/
|
|
16
17
|
function safeAction(fn) {
|
|
17
18
|
return async (...args) => {
|
|
18
19
|
try {
|
|
19
20
|
await fn(...args);
|
|
20
21
|
} catch (err) {
|
|
21
22
|
if (ExitPromptError && err instanceof ExitPromptError) {
|
|
22
|
-
// Ctrl+C — exit silently
|
|
23
23
|
process.exit(0);
|
|
24
24
|
}
|
|
25
|
-
console.error('\n' +
|
|
25
|
+
console.error('\n' + chalk.red(` ✘ ${err.message}`));
|
|
26
26
|
if (process.env.DEBUG) console.error(err);
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
const program = new Command();
|
|
33
|
+
|
|
32
34
|
program
|
|
33
35
|
.name('juice')
|
|
34
36
|
.description('生成符合各大邮件平台的内联 CSS HTML 邮件(同时输出标准版 + 压缩版)')
|
|
@@ -68,6 +70,24 @@ program
|
|
|
68
70
|
交互模式(逐步选择品牌、模板、片段、配置):
|
|
69
71
|
juice
|
|
70
72
|
|
|
73
|
+
════════════════════════════════════════════════════════════════
|
|
74
|
+
资源浏览
|
|
75
|
+
════════════════════════════════════════════════════════════════
|
|
76
|
+
|
|
77
|
+
查看资源树:
|
|
78
|
+
juice view 查看所有品牌、模板、系列、片段
|
|
79
|
+
juice view elabscience 查看指定品牌的模板和系列
|
|
80
|
+
juice view --templates 列出所有模板
|
|
81
|
+
juice view --series 列出所有系列
|
|
82
|
+
juice view --snippets 列出所有片段
|
|
83
|
+
|
|
84
|
+
拷贝到当前目录:
|
|
85
|
+
juice init 交互式选择并拷贝模板/片段/配置
|
|
86
|
+
juice init --template <file> 仅拷贝指定模板 HTML
|
|
87
|
+
juice init --snippet <file> 仅拷贝指定片段 HTML
|
|
88
|
+
juice init --config <file> 仅拷贝指定配置 YAML
|
|
89
|
+
juice init --all [target] 拷贝整个 EDM 资源库
|
|
90
|
+
|
|
71
91
|
════════════════════════════════════════════════════════════════
|
|
72
92
|
输出文件说明
|
|
73
93
|
════════════════════════════════════════════════════════════════
|
|
@@ -111,7 +131,7 @@ program
|
|
|
111
131
|
.option('--series', '列出所有系列')
|
|
112
132
|
.option('--snippets', '列出所有片段')
|
|
113
133
|
.action(safeAction(async (viewPath, options) => {
|
|
114
|
-
const { runViewMode } =
|
|
134
|
+
const { runViewMode } = await import('../src/view.js');
|
|
115
135
|
await runViewMode({
|
|
116
136
|
viewPath: viewPath || null,
|
|
117
137
|
interactive: !!options.interactive,
|
|
@@ -130,7 +150,7 @@ program
|
|
|
130
150
|
.option('-c, --config <file-path>', '仅拷贝配置 YAML 文件')
|
|
131
151
|
.option('--all [target]', '拷贝整个 EDM 资源目录到当前或指定目录')
|
|
132
152
|
.action(safeAction(async (initPath, options) => {
|
|
133
|
-
const { runInitMode } =
|
|
153
|
+
const { runInitMode } = await import('../src/init.js');
|
|
134
154
|
await runInitMode({
|
|
135
155
|
initPath: initPath || null,
|
|
136
156
|
template: options.template || null,
|
|
@@ -143,21 +163,19 @@ program
|
|
|
143
163
|
// ─── Default action (backward compatible) ───────────────────────────────
|
|
144
164
|
program
|
|
145
165
|
.action(safeAction(async (options) => {
|
|
146
|
-
// 注册/卸载右键菜单
|
|
147
166
|
if (options.install) {
|
|
148
|
-
const { registerContextMenu } =
|
|
167
|
+
const { registerContextMenu } = await import('../src/context-menu.js');
|
|
149
168
|
await registerContextMenu();
|
|
150
169
|
return;
|
|
151
170
|
}
|
|
152
171
|
if (options.uninstall) {
|
|
153
|
-
const { unregisterContextMenu } =
|
|
172
|
+
const { unregisterContextMenu } = await import('../src/context-menu.js');
|
|
154
173
|
await unregisterContextMenu();
|
|
155
174
|
return;
|
|
156
175
|
}
|
|
157
176
|
|
|
158
|
-
// --snippet / -s 模式:片段组装
|
|
159
177
|
if (options.snippet) {
|
|
160
|
-
const { runSnippetMode } =
|
|
178
|
+
const { runSnippetMode } = await import('../src/snippet.js');
|
|
161
179
|
await runSnippetMode({
|
|
162
180
|
snippet: options.snippet,
|
|
163
181
|
template: options.file,
|
|
@@ -167,7 +185,6 @@ program
|
|
|
167
185
|
return;
|
|
168
186
|
}
|
|
169
187
|
|
|
170
|
-
// 普通模式:-f 指定文件
|
|
171
188
|
let inputFile = options.file;
|
|
172
189
|
if (!inputFile && program.args.length > 0) {
|
|
173
190
|
inputFile = program.args[0];
|
|
@@ -181,10 +198,8 @@ program
|
|
|
181
198
|
return;
|
|
182
199
|
}
|
|
183
200
|
|
|
184
|
-
|
|
185
|
-
const { runInteractiveMode } = require('../src/snippet');
|
|
201
|
+
const { runInteractiveMode } = await import('../src/snippet.js');
|
|
186
202
|
await runInteractiveMode({ config: options.config });
|
|
187
203
|
}));
|
|
188
204
|
|
|
189
|
-
program.allowUnknownOption(false);
|
|
190
205
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juice-email-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "CLI tool to generate email-client-compatible HTML with juice (CSS inlining) + Mustache templating + minification",
|
|
5
6
|
"main": "src/index.js",
|
|
6
7
|
"bin": {
|
|
@@ -36,17 +37,17 @@
|
|
|
36
37
|
"url": "https://github.com/GuoSirius/juice-cli/issues"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@inquirer/prompts": "^
|
|
40
|
-
"chalk": "^
|
|
41
|
-
"commander": "^
|
|
40
|
+
"@inquirer/prompts": "^8.4.3",
|
|
41
|
+
"chalk": "^5.6.2",
|
|
42
|
+
"commander": "^14.0.3",
|
|
42
43
|
"html-minifier-terser": "^7.2.0",
|
|
43
|
-
"js-yaml": "^4.1.
|
|
44
|
-
"juice": "^
|
|
44
|
+
"js-yaml": "^4.1.1",
|
|
45
|
+
"juice": "^11.1.1",
|
|
45
46
|
"mustache": "^4.2.0",
|
|
46
|
-
"ora": "^
|
|
47
|
+
"ora": "^9.4.0"
|
|
47
48
|
},
|
|
48
49
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
50
|
+
"node": ">=24.0.0"
|
|
50
51
|
},
|
|
51
52
|
"files": [
|
|
52
53
|
"bin/",
|
package/src/context-menu.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Windows 右键菜单注册 / 取消注册
|
|
5
3
|
*
|
|
@@ -32,10 +30,13 @@
|
|
|
32
30
|
* └── 📂 在此打开终端 (可选)
|
|
33
31
|
*/
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
import { spawnSync } from 'child_process';
|
|
34
|
+
import fs from 'fs';
|
|
35
|
+
import path from 'path';
|
|
36
|
+
import { fileURLToPath } from 'url';
|
|
37
|
+
import chalk from 'chalk';
|
|
38
|
+
|
|
39
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
39
40
|
|
|
40
41
|
// ─── 路径工具 ─────────────────────────────────────────────────────────────────
|
|
41
42
|
|
|
@@ -459,4 +460,4 @@ async function unregisterContextMenu() {
|
|
|
459
460
|
}
|
|
460
461
|
}
|
|
461
462
|
|
|
462
|
-
|
|
463
|
+
export { registerContextMenu, unregisterContextMenu };
|
package/src/index.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
import juice from 'juice';
|
|
7
|
+
import Mustache from 'mustache';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import { minify as htmlMinify } from 'html-minifier-terser';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
|
|
13
14
|
// ─── 从 defaults/juice.yaml 加载默认配置 ─────────────────────────────────────
|
|
14
15
|
|
|
15
|
-
const DEFAULT_CONFIG_PATH = path.resolve(__dirname, '..', 'defaults', 'juice.yaml');
|
|
16
|
+
export const DEFAULT_CONFIG_PATH = path.resolve(__dirname, '..', 'defaults', 'juice.yaml');
|
|
16
17
|
|
|
17
18
|
function loadDefaultConfig() {
|
|
18
19
|
try {
|
|
19
20
|
const content = fs.readFileSync(DEFAULT_CONFIG_PATH, 'utf8');
|
|
20
21
|
return yaml.load(content) || {};
|
|
21
22
|
} catch (e) {
|
|
22
|
-
// 如果 defaults/juice.yaml 不存在,使用内置最小配置
|
|
23
23
|
console.warn(chalk.yellow(` ⚠ 无法加载默认配置 ${DEFAULT_CONFIG_PATH},使用内置默认值\n`));
|
|
24
24
|
return {};
|
|
25
25
|
}
|
|
@@ -27,34 +27,20 @@ function loadDefaultConfig() {
|
|
|
27
27
|
|
|
28
28
|
const CODE_DEFAULTS = loadDefaultConfig();
|
|
29
29
|
|
|
30
|
-
// 用户主目录候选(支持 yaml/yml)
|
|
31
30
|
const HOME_CANDIDATES = [
|
|
32
31
|
path.join(os.homedir(), 'juice.yaml'),
|
|
33
32
|
path.join(os.homedir(), 'juice.yml'),
|
|
34
33
|
];
|
|
35
34
|
|
|
36
|
-
function resolveHomeConfig() {
|
|
35
|
+
export function resolveHomeConfig() {
|
|
37
36
|
return HOME_CANDIDATES.find((c) => fs.existsSync(c)) || null;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
// ─── 配置文件查找 ─────────────────────────────────────────────────────────────
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
* 查找用户配置文件
|
|
44
|
-
*
|
|
45
|
-
* 配置加载顺序(优先级从低到高):
|
|
46
|
-
* 1. CLI 内置默认值(defaults/juice.yaml)
|
|
47
|
-
* 2. 用户主目录 ~/juice.yaml(如果存在)
|
|
48
|
-
* 3. 优先配置文件(-c 指定 或 输入文件同级目录,二者互斥)
|
|
49
|
-
*
|
|
50
|
-
* 返回:
|
|
51
|
-
* - highPriorityPath: 高优先级配置路径(-c 指定 或 输入文件同级目录,二者互斥)
|
|
52
|
-
* - homePath: 用户主目录配置路径(可能不存在)
|
|
53
|
-
*/
|
|
54
|
-
function findConfigs(configPath, inputFile) {
|
|
41
|
+
export function findConfigs(configPath, inputFile) {
|
|
55
42
|
let highPriorityPath = null;
|
|
56
43
|
|
|
57
|
-
// 1. 优先使用 -c 指定的配置文件
|
|
58
44
|
if (configPath) {
|
|
59
45
|
const resolved = path.resolve(configPath);
|
|
60
46
|
if (!fs.existsSync(resolved)) {
|
|
@@ -62,7 +48,6 @@ function findConfigs(configPath, inputFile) {
|
|
|
62
48
|
}
|
|
63
49
|
highPriorityPath = resolved;
|
|
64
50
|
}
|
|
65
|
-
// 2. 否则使用输入文件同级目录下的配置(互斥)
|
|
66
51
|
else if (inputFile) {
|
|
67
52
|
const inputDir = path.dirname(path.resolve(inputFile));
|
|
68
53
|
const fileCandidates = [
|
|
@@ -82,7 +67,7 @@ function findConfigs(configPath, inputFile) {
|
|
|
82
67
|
|
|
83
68
|
// ─── 配置文件加载 ─────────────────────────────────────────────────────────────
|
|
84
69
|
|
|
85
|
-
function loadYaml(filePath) {
|
|
70
|
+
export function loadYaml(filePath) {
|
|
86
71
|
if (!filePath || !fs.existsSync(filePath)) return {};
|
|
87
72
|
try {
|
|
88
73
|
return yaml.load(fs.readFileSync(filePath, 'utf8')) || {};
|
|
@@ -91,10 +76,9 @@ function loadYaml(filePath) {
|
|
|
91
76
|
}
|
|
92
77
|
}
|
|
93
78
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
function deepMerge(base, ...overrides) {
|
|
79
|
+
// ─── 深度合并 ─────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
export function deepMerge(base, ...overrides) {
|
|
98
82
|
let result = Object.assign({}, base);
|
|
99
83
|
for (const src of overrides) {
|
|
100
84
|
if (!src || typeof src !== 'object' || Array.isArray(src)) continue;
|
|
@@ -118,24 +102,17 @@ function deepMerge(base, ...overrides) {
|
|
|
118
102
|
return result;
|
|
119
103
|
}
|
|
120
104
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
* 合并顺序(优先级从低到高):
|
|
125
|
-
* CLI 内置默认值 < 用户目录 ~/juice.yaml < 优先配置(-c/输入目录)
|
|
126
|
-
*/
|
|
127
|
-
function buildConfig(highPriorityPath, homePath) {
|
|
105
|
+
// ─── 配置合并 ─────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export function buildConfig(highPriorityPath, homePath) {
|
|
128
108
|
const layers = [];
|
|
129
109
|
|
|
130
|
-
// 1. CLI 内置默认值(最低优先级)
|
|
131
110
|
layers.push({ label: 'CLI 内置默认值', data: CODE_DEFAULTS });
|
|
132
111
|
|
|
133
|
-
// 2. 用户目录配置(如果有)
|
|
134
112
|
if (homePath) {
|
|
135
113
|
layers.push({ label: `用户目录配置 (${homePath})`, data: loadYaml(homePath) });
|
|
136
114
|
}
|
|
137
115
|
|
|
138
|
-
// 3. 优先配置(最高优先级,-c 指定 或 输入文件同级目录,互斥)
|
|
139
116
|
if (highPriorityPath) {
|
|
140
117
|
layers.push({ label: `优先配置 (${highPriorityPath})`, data: loadYaml(highPriorityPath) });
|
|
141
118
|
}
|
|
@@ -146,10 +123,9 @@ function buildConfig(highPriorityPath, homePath) {
|
|
|
146
123
|
|
|
147
124
|
// ─── HTML 模板处理 ────────────────────────────────────────────────────────────
|
|
148
125
|
|
|
149
|
-
function processTemplate(inputFile, config) {
|
|
126
|
+
export function processTemplate(inputFile, config) {
|
|
150
127
|
const htmlRaw = fs.readFileSync(inputFile, 'utf8');
|
|
151
128
|
|
|
152
|
-
// 1. Mustache 变量替换(根据 rawHtml 配置决定是否转义 HTML)
|
|
153
129
|
const originalEscape = Mustache.escape;
|
|
154
130
|
let htmlWithVars;
|
|
155
131
|
try {
|
|
@@ -161,18 +137,16 @@ function processTemplate(inputFile, config) {
|
|
|
161
137
|
Mustache.escape = originalEscape;
|
|
162
138
|
}
|
|
163
139
|
|
|
164
|
-
// 2. 收集 extraCssFiles
|
|
165
140
|
const basePath = path.dirname(path.resolve(inputFile));
|
|
166
141
|
const extraCss = collectExtraCss(basePath, config);
|
|
167
142
|
|
|
168
|
-
// 3. juice CSS 内联
|
|
169
143
|
const juiceOpts = Object.assign({}, config.juice || {});
|
|
170
144
|
delete juiceOpts.extraCssFiles;
|
|
171
145
|
|
|
172
146
|
return juice(htmlWithVars, { ...juiceOpts, extraCss });
|
|
173
147
|
}
|
|
174
148
|
|
|
175
|
-
function collectExtraCss(basePath, config) {
|
|
149
|
+
export function collectExtraCss(basePath, config) {
|
|
176
150
|
const extraFiles = (config.juice && config.juice.extraCssFiles) || [];
|
|
177
151
|
if (!extraFiles.length) return '';
|
|
178
152
|
return extraFiles
|
|
@@ -189,7 +163,7 @@ function collectExtraCss(basePath, config) {
|
|
|
189
163
|
|
|
190
164
|
// ─── 压缩 ─────────────────────────────────────────────────────────────────────
|
|
191
165
|
|
|
192
|
-
async function minifyHtml(html, minifyConfig) {
|
|
166
|
+
export async function minifyHtml(html, minifyConfig) {
|
|
193
167
|
return htmlMinify(html, {
|
|
194
168
|
removeConditionalComments: false,
|
|
195
169
|
...(minifyConfig || {}),
|
|
@@ -208,39 +182,46 @@ function resolveOutputPaths(inputFile, config) {
|
|
|
208
182
|
};
|
|
209
183
|
}
|
|
210
184
|
|
|
185
|
+
// ─── 格式化 ───────────────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
export function fmtSize(str) {
|
|
188
|
+
const b = Buffer.byteLength(str, 'utf8');
|
|
189
|
+
return b < 1024 ? `${b} B` : `${(b / 1024).toFixed(1)} KB`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function savings(original, minified) {
|
|
193
|
+
const orig = Buffer.byteLength(original, 'utf8');
|
|
194
|
+
const mini = Buffer.byteLength(minified, 'utf8');
|
|
195
|
+
return (((orig - mini) / orig) * 100).toFixed(1) + '%';
|
|
196
|
+
}
|
|
197
|
+
|
|
211
198
|
// ─── 主入口 ───────────────────────────────────────────────────────────────────
|
|
212
199
|
|
|
213
|
-
async function run({ file, config: configPath }) {
|
|
200
|
+
export async function run({ file, config: configPath }) {
|
|
214
201
|
const spinner = ora({ text: '正在处理...', color: 'cyan' }).start();
|
|
215
202
|
|
|
216
203
|
try {
|
|
217
|
-
// 1. 输入文件
|
|
218
204
|
const inputFile = path.resolve(file);
|
|
219
205
|
if (!fs.existsSync(inputFile)) {
|
|
220
206
|
spinner.fail(chalk.red(`输入文件不存在:${inputFile}`));
|
|
221
207
|
process.exit(1);
|
|
222
208
|
}
|
|
223
209
|
|
|
224
|
-
// 2. 查找配置(-c 和输入文件同级目录互斥)+ 合并
|
|
225
210
|
const { highPriorityPath, homePath } = findConfigs(configPath, inputFile);
|
|
226
211
|
const { config, layers } = buildConfig(highPriorityPath, homePath);
|
|
227
212
|
const encoding = (config.output && config.output.encoding) || 'utf8';
|
|
228
213
|
|
|
229
|
-
// 3. CSS 内联
|
|
230
214
|
spinner.text = `CSS 内联处理:${path.basename(inputFile)}`;
|
|
231
215
|
const resultHtml = processTemplate(inputFile, config);
|
|
232
216
|
|
|
233
|
-
// 4. 输出路径
|
|
234
217
|
const outPaths = resolveOutputPaths(inputFile, config);
|
|
235
218
|
|
|
236
|
-
// 5. 写出标准版
|
|
237
219
|
spinner.text = '写出 .output.html ...';
|
|
238
220
|
if (fs.existsSync(outPaths.normal)) {
|
|
239
221
|
spinner.warn(chalk.yellow(`目标文件已存在,将覆盖:${outPaths.normal}`));
|
|
240
222
|
}
|
|
241
223
|
fs.writeFileSync(outPaths.normal, resultHtml, encoding);
|
|
242
224
|
|
|
243
|
-
// 6. 压缩并写出
|
|
244
225
|
spinner.text = '写出 .minified.html ...';
|
|
245
226
|
if (fs.existsSync(outPaths.minified)) {
|
|
246
227
|
spinner.warn(chalk.yellow(`目标文件已存在,将覆盖:${outPaths.minified}`));
|
|
@@ -248,7 +229,6 @@ async function run({ file, config: configPath }) {
|
|
|
248
229
|
const minified = await minifyHtml(resultHtml, config.minify);
|
|
249
230
|
fs.writeFileSync(outPaths.minified, minified, encoding);
|
|
250
231
|
|
|
251
|
-
// 7. 汇报
|
|
252
232
|
const layerLines = layers
|
|
253
233
|
.map((l) => ` ${chalk.gray('·')} ${l.label}`)
|
|
254
234
|
.join('\n');
|
|
@@ -267,16 +247,3 @@ async function run({ file, config: configPath }) {
|
|
|
267
247
|
process.exit(1);
|
|
268
248
|
}
|
|
269
249
|
}
|
|
270
|
-
|
|
271
|
-
function fmtSize(str) {
|
|
272
|
-
const b = Buffer.byteLength(str, 'utf8');
|
|
273
|
-
return b < 1024 ? `${b} B` : `${(b / 1024).toFixed(1)} KB`;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function savings(original, minified) {
|
|
277
|
-
const orig = Buffer.byteLength(original, 'utf8');
|
|
278
|
-
const mini = Buffer.byteLength(minified, 'utf8');
|
|
279
|
-
return (((orig - mini) / orig) * 100).toFixed(1) + '%';
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
module.exports = { run, findConfigs, buildConfig, processTemplate, minifyHtml, deepMerge, collectExtraCss, loadYaml, fmtSize, savings, DEFAULT_CONFIG_PATH, resolveHomeConfig };
|
package/src/init.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const {
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import {
|
|
7
6
|
resolveEdmDir, loadMeta, findBrands, findTemplateVersions,
|
|
8
7
|
findSeriesDirs, filterSeries, findSnippetVariants, findConfigs,
|
|
9
8
|
promptOutputName,
|
|
10
|
-
}
|
|
9
|
+
} from './snippet.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
|
|
12
13
|
function fmtBytes(b) {
|
|
13
14
|
return b < 1024 ? `${b} B` : `${(b / 1024).toFixed(1)} KB`;
|
|
@@ -433,7 +434,7 @@ async function runInitMode({ initPath, template, snippet, config, all }) {
|
|
|
433
434
|
}
|
|
434
435
|
|
|
435
436
|
if (initPath) {
|
|
436
|
-
const { parseViewPath } =
|
|
437
|
+
const { parseViewPath } = await import('./view.js');
|
|
437
438
|
let edmDir;
|
|
438
439
|
try {
|
|
439
440
|
edmDir = resolveEdmDir();
|
|
@@ -526,4 +527,4 @@ async function runInitMode({ initPath, template, snippet, config, all }) {
|
|
|
526
527
|
await interactiveInit(edmDir, cwd);
|
|
527
528
|
}
|
|
528
529
|
|
|
529
|
-
|
|
530
|
+
export { runInitMode };
|
package/src/snippet.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const {
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import juice from 'juice';
|
|
6
|
+
import Mustache from 'mustache';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import {
|
|
10
9
|
loadYaml,
|
|
11
10
|
deepMerge,
|
|
12
11
|
collectExtraCss,
|
|
@@ -14,7 +13,9 @@ const {
|
|
|
14
13
|
fmtSize,
|
|
15
14
|
savings,
|
|
16
15
|
DEFAULT_CONFIG_PATH,
|
|
17
|
-
}
|
|
16
|
+
} from './index.js';
|
|
17
|
+
|
|
18
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
|
|
19
20
|
// ─── EDM 目录解析 ──────────────────────────────────────────────────────────────
|
|
20
21
|
|
|
@@ -948,7 +949,7 @@ async function runInteractiveMode({ config: cliConfigPath }) {
|
|
|
948
949
|
});
|
|
949
950
|
}
|
|
950
951
|
|
|
951
|
-
|
|
952
|
+
export {
|
|
952
953
|
runSnippetMode,
|
|
953
954
|
runInteractiveMode,
|
|
954
955
|
// EDM resolution & scanning
|
package/src/view.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const chalk = require('chalk');
|
|
6
|
-
const {
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import {
|
|
7
5
|
resolveEdmDir, loadMeta, findBrands, findTemplateVersions,
|
|
8
6
|
findSeriesDirs, findSnippetVariants, findConfigs,
|
|
9
|
-
}
|
|
7
|
+
} from './snippet.js';
|
|
10
8
|
|
|
11
9
|
function fmtBytes(b) {
|
|
12
10
|
return b < 1024 ? `${b} B` : `${(b / 1024).toFixed(1)} KB`;
|
|
@@ -349,7 +347,7 @@ async function showCheckbox(title, choices) {
|
|
|
349
347
|
*/
|
|
350
348
|
async function copyResource(type, resourcePath, cwd) {
|
|
351
349
|
try {
|
|
352
|
-
const { runInitMode } =
|
|
350
|
+
const { runInitMode } = await import('./init.js');
|
|
353
351
|
if (type === 'template') {
|
|
354
352
|
await runInitMode({ template: resourcePath });
|
|
355
353
|
} else if (type === 'snippet') {
|
|
@@ -616,7 +614,7 @@ async function interactiveBrowse(edmDir, startParsed) {
|
|
|
616
614
|
// Include template version in default name for clarity
|
|
617
615
|
const versionName = versions.length > 0 ? versions[0].name : 'template';
|
|
618
616
|
const defaultBaseName = `${current.brand}-${versionName}-${current.series}-${current.variant}`;
|
|
619
|
-
const { promptOutputName: pn } =
|
|
617
|
+
const { promptOutputName: pn } = await import('./snippet.js');
|
|
620
618
|
const outputBaseName = await pn(defaultBaseName, process.cwd());
|
|
621
619
|
const cwd = process.cwd();
|
|
622
620
|
const cwdRel = (p) => './' + path.relative(cwd, p);
|
|
@@ -714,7 +712,7 @@ async function runViewMode({ viewPath, interactive, scope }) {
|
|
|
714
712
|
});
|
|
715
713
|
if (action === 'copy') {
|
|
716
714
|
console.log();
|
|
717
|
-
const { runInitMode } =
|
|
715
|
+
const { runInitMode } = await import('./init.js');
|
|
718
716
|
await runInitMode({ template: picked.version.templatePath });
|
|
719
717
|
console.log();
|
|
720
718
|
}
|
|
@@ -792,7 +790,7 @@ async function runViewMode({ viewPath, interactive, scope }) {
|
|
|
792
790
|
actions
|
|
793
791
|
);
|
|
794
792
|
if (action === 'exit') return;
|
|
795
|
-
const { runInitMode } =
|
|
793
|
+
const { runInitMode } = await import('./init.js');
|
|
796
794
|
console.log();
|
|
797
795
|
if (action === 'snippet') {
|
|
798
796
|
await runInitMode({ snippet: snipPath });
|
|
@@ -872,7 +870,7 @@ async function runViewMode({ viewPath, interactive, scope }) {
|
|
|
872
870
|
variantData: picked.variant,
|
|
873
871
|
};
|
|
874
872
|
} else {
|
|
875
|
-
const { runInitMode } =
|
|
873
|
+
const { runInitMode } = await import('./init.js');
|
|
876
874
|
console.log();
|
|
877
875
|
if (action === 'copy-snippet') {
|
|
878
876
|
await runInitMode({ snippet: picked.snippetPath });
|
|
@@ -938,4 +936,4 @@ async function runViewMode({ viewPath, interactive, scope }) {
|
|
|
938
936
|
printFullTree(edmDir);
|
|
939
937
|
}
|
|
940
938
|
|
|
941
|
-
|
|
939
|
+
export { runViewMode, parseViewPath };
|