juice-email-cli 2.1.17 → 2.2.0
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/edm/elabscience/_meta.yaml +2 -0
- package/edm/elabscience/series/literature/_meta.yaml +2 -0
- package/edm/elabscience/series/literature/default/_meta.yaml +2 -0
- package/edm/elabscience/templates/standard/_meta.yaml +2 -0
- package/edm/procell/_meta.yaml +2 -0
- package/edm/procell/templates/standard/_meta.yaml +2 -0
- package/package.json +1 -1
- package/src/snippet.js +283 -118
- /package/edm/elabscience/{literature → series/literature/default}/juice.yaml +0 -0
- /package/edm/elabscience/{literature → series/literature/default}/snippet.html +0 -0
- /package/edm/elabscience/{favicon.ico → templates/standard/favicon.ico} +0 -0
- /package/edm/elabscience/{elabscience-template.html → templates/standard/template.html} +0 -0
- /package/edm/procell/{favicon.ico → templates/standard/favicon.ico} +0 -0
- /package/edm/procell/{procell-template.html → templates/standard/template.html} +0 -0
package/package.json
CHANGED
package/src/snippet.js
CHANGED
|
@@ -35,36 +35,183 @@ function resolveEdmDir() {
|
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// ─── 元数据读取 ─────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 读取目录下的 _meta.yaml,返回 { name, description, series }。
|
|
42
|
+
* 文件不存在时回退为 { name: 目录名, description: "" }。
|
|
43
|
+
*/
|
|
44
|
+
function loadMeta(dir) {
|
|
45
|
+
const metaPath = path.join(dir, '_meta.yaml');
|
|
46
|
+
if (fs.existsSync(metaPath)) {
|
|
47
|
+
try {
|
|
48
|
+
const raw = loadYaml(metaPath);
|
|
49
|
+
return {
|
|
50
|
+
name: raw.name || path.basename(dir),
|
|
51
|
+
description: raw.description || "",
|
|
52
|
+
series: raw.series || null,
|
|
53
|
+
};
|
|
54
|
+
} catch (_) {
|
|
55
|
+
// 解析失败时回退
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { name: path.basename(dir), description: "", series: null };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 根据版本 _meta.yaml 中的 series.allow / series.block 过滤系列列表。
|
|
63
|
+
* allow 和 block 互斥,同时存在时 allow 优先。
|
|
64
|
+
* 均不配置时返回全部系列。
|
|
65
|
+
*/
|
|
66
|
+
function filterSeries(allSeries, versionMeta) {
|
|
67
|
+
if (!versionMeta || !versionMeta.series) return allSeries;
|
|
68
|
+
|
|
69
|
+
const { allow, block } = versionMeta.series;
|
|
70
|
+
|
|
71
|
+
if (allow && Array.isArray(allow)) {
|
|
72
|
+
return allSeries.filter((s) => allow.includes(s.name));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (block && Array.isArray(block)) {
|
|
76
|
+
return allSeries.filter((s) => !block.includes(s.name));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return allSeries;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── EDM 目录扫描 ───────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
38
84
|
function findBrands(edmDir) {
|
|
39
85
|
const entries = fs.readdirSync(edmDir, { withFileTypes: true });
|
|
40
86
|
const brands = entries
|
|
41
87
|
.filter((e) => e.isDirectory())
|
|
42
|
-
.map((e) =>
|
|
88
|
+
.map((e) => {
|
|
89
|
+
const dir = path.join(edmDir, e.name);
|
|
90
|
+
const meta = loadMeta(dir);
|
|
91
|
+
return { name: e.name, path: dir, meta };
|
|
92
|
+
});
|
|
43
93
|
if (brands.length === 0) {
|
|
44
94
|
throw new Error(`EDM 目录下未找到任何品牌子目录:${edmDir}`);
|
|
45
95
|
}
|
|
46
96
|
return brands;
|
|
47
97
|
}
|
|
48
98
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
99
|
+
/**
|
|
100
|
+
* 扫描品牌下的模板版本:templates/<version>/
|
|
101
|
+
* 每个版本返回 { name, path, meta, templatePath }。
|
|
102
|
+
*/
|
|
103
|
+
function findTemplateVersions(brandDir) {
|
|
104
|
+
const templatesDir = path.join(brandDir, 'templates');
|
|
105
|
+
if (!fs.existsSync(templatesDir)) {
|
|
106
|
+
throw new Error(`品牌目录下未找到 templates/:${brandDir}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const entries = fs.readdirSync(templatesDir, { withFileTypes: true });
|
|
110
|
+
const versions = [];
|
|
111
|
+
for (const e of entries) {
|
|
112
|
+
if (!e.isDirectory()) continue;
|
|
113
|
+
const dir = path.join(templatesDir, e.name);
|
|
114
|
+
const meta = loadMeta(dir);
|
|
115
|
+
const htmlFiles = findFiles(dir, /\.html?$/i);
|
|
116
|
+
if (htmlFiles.length === 0) continue;
|
|
117
|
+
versions.push({
|
|
118
|
+
name: e.name,
|
|
119
|
+
path: dir,
|
|
120
|
+
meta,
|
|
121
|
+
templatePath: htmlFiles[0].path,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (versions.length === 0) {
|
|
125
|
+
throw new Error(`templates/ 下未找到任何含 .html 模板的版本子目录:${templatesDir}`);
|
|
126
|
+
}
|
|
127
|
+
return versions;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 扫描品牌下的片段系列:series/<series>/
|
|
132
|
+
* 每个系列返回 { name, path, meta }。
|
|
133
|
+
*/
|
|
134
|
+
function findSeriesDirs(brandDir) {
|
|
135
|
+
const seriesDir = path.join(brandDir, 'series');
|
|
136
|
+
if (!fs.existsSync(seriesDir)) return [];
|
|
137
|
+
|
|
138
|
+
const entries = fs.readdirSync(seriesDir, { withFileTypes: true });
|
|
139
|
+
const list = entries
|
|
52
140
|
.filter((e) => e.isDirectory())
|
|
53
|
-
.map((e) =>
|
|
141
|
+
.map((e) => {
|
|
142
|
+
const dir = path.join(seriesDir, e.name);
|
|
143
|
+
const meta = loadMeta(dir);
|
|
144
|
+
return { name: e.name, path: dir, meta };
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (list.length === 0) {
|
|
148
|
+
console.log(chalk.yellow(` ⚠ series/ 下暂无片段系列。`));
|
|
149
|
+
}
|
|
150
|
+
return list;
|
|
54
151
|
}
|
|
55
152
|
|
|
56
|
-
|
|
153
|
+
/**
|
|
154
|
+
* 检测系列目录下的片段变体:
|
|
155
|
+
* - 有子目录且含 snippet.html → 多变体模式,返回变体列表
|
|
156
|
+
* - 无子目录、自身有 snippet.html → 单变体模式,自身作为唯一变体
|
|
157
|
+
* 每个变体返回 { name, path, meta, hasSnippet }。
|
|
158
|
+
*/
|
|
159
|
+
function findSnippetVariants(seriesDir) {
|
|
160
|
+
const entries = fs.readdirSync(seriesDir, { withFileTypes: true });
|
|
161
|
+
const subDirs = entries.filter((e) => e.isDirectory());
|
|
162
|
+
|
|
163
|
+
if (subDirs.length > 0) {
|
|
164
|
+
// 潜在的多变体模式:检查是否有子目录包含 snippet.html
|
|
165
|
+
const variants = [];
|
|
166
|
+
for (const d of subDirs) {
|
|
167
|
+
const dir = path.join(seriesDir, d.name);
|
|
168
|
+
const snipPath = path.join(dir, 'snippet.html');
|
|
169
|
+
if (fs.existsSync(snipPath)) {
|
|
170
|
+
const meta = loadMeta(dir);
|
|
171
|
+
variants.push({ name: d.name, path: dir, meta, hasSnippet: true });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (variants.length > 0) return variants;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 单变体模式:系列目录自身就是变体
|
|
178
|
+
const snipPath = path.join(seriesDir, 'snippet.html');
|
|
179
|
+
if (fs.existsSync(snipPath)) {
|
|
180
|
+
const meta = loadMeta(seriesDir);
|
|
181
|
+
return [{ name: 'default', path: seriesDir, meta, hasSnippet: true }];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 列出变体目录下所有 YAML 配置,标记 juice.yaml 为最优配对。
|
|
189
|
+
* 返回 { name, path, isOptimal }。
|
|
190
|
+
*/
|
|
191
|
+
function findConfigs(variantDir) {
|
|
192
|
+
const yamlFiles = findYamlFiles(variantDir);
|
|
193
|
+
return yamlFiles
|
|
194
|
+
.filter((f) => f.name !== '_meta.yaml' && f.name !== '_meta.yml')
|
|
195
|
+
.map((f) => ({
|
|
196
|
+
name: f.name,
|
|
197
|
+
path: f.path,
|
|
198
|
+
isOptimal: f.name === 'juice.yaml',
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function findFiles(dir, regex) {
|
|
57
203
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
58
204
|
return entries
|
|
59
|
-
.filter((e) => e.isFile() &&
|
|
205
|
+
.filter((e) => e.isFile() && regex.test(e.name))
|
|
60
206
|
.map((e) => ({ name: e.name, path: path.join(dir, e.name) }));
|
|
61
207
|
}
|
|
62
208
|
|
|
209
|
+
function findHtmlFiles(dir) {
|
|
210
|
+
return findFiles(dir, /\.html?$/i);
|
|
211
|
+
}
|
|
212
|
+
|
|
63
213
|
function findYamlFiles(dir) {
|
|
64
|
-
|
|
65
|
-
return entries
|
|
66
|
-
.filter((e) => e.isFile() && /\.ya?ml$/i.test(e.name))
|
|
67
|
-
.map((e) => ({ name: e.name, path: path.join(dir, e.name) }));
|
|
214
|
+
return findFiles(dir, /\.ya?ml$/i);
|
|
68
215
|
}
|
|
69
216
|
|
|
70
217
|
/**
|
|
@@ -307,79 +454,96 @@ async function assembleSnippet({ snippetPath, templatePath, config, cwd, outputB
|
|
|
307
454
|
|
|
308
455
|
// ─── 交互式提示 ───────────────────────────────────────────────────────────────
|
|
309
456
|
|
|
457
|
+
function fmtChoice(meta, dirName) {
|
|
458
|
+
const display = meta.name || dirName;
|
|
459
|
+
const suffix = meta.name ? ` ${chalk.gray(`(${dirName})`)}` : "";
|
|
460
|
+
return { display: display + suffix, description: meta.description || "" };
|
|
461
|
+
}
|
|
462
|
+
|
|
310
463
|
async function promptBrand(brands) {
|
|
311
464
|
const { select } = await import('@inquirer/prompts');
|
|
465
|
+
const choices = brands.map((b) => ({
|
|
466
|
+
name: fmtChoice(b.meta, b.name).display,
|
|
467
|
+
value: b,
|
|
468
|
+
description: b.meta.description || undefined,
|
|
469
|
+
}));
|
|
312
470
|
return select({
|
|
313
|
-
message: '
|
|
314
|
-
choices
|
|
471
|
+
message: '请选择品牌:',
|
|
472
|
+
choices,
|
|
315
473
|
});
|
|
316
474
|
}
|
|
317
475
|
|
|
318
|
-
async function
|
|
476
|
+
async function promptTemplateVersion(versions) {
|
|
319
477
|
const { select } = await import('@inquirer/prompts');
|
|
320
|
-
|
|
478
|
+
const choices = versions.map((v) => ({
|
|
479
|
+
name: fmtChoice(v.meta, v.name).display,
|
|
480
|
+
value: v,
|
|
481
|
+
description: v.meta.description || undefined,
|
|
482
|
+
}));
|
|
321
483
|
return select({
|
|
322
|
-
message: '
|
|
323
|
-
choices
|
|
484
|
+
message: '请选择模板版本:',
|
|
485
|
+
choices,
|
|
324
486
|
});
|
|
325
487
|
}
|
|
326
488
|
|
|
327
|
-
async function
|
|
489
|
+
async function promptSeries(seriesList) {
|
|
328
490
|
const { select } = await import('@inquirer/prompts');
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
const
|
|
491
|
+
if (seriesList.length === 0) return null;
|
|
492
|
+
|
|
493
|
+
const choices = seriesList.map((s) => {
|
|
494
|
+
const variants = findSnippetVariants(s.path);
|
|
495
|
+
const extra = variants.length > 0 ? ` — 含 ${variants.length} 个片段变体` : "";
|
|
496
|
+
return {
|
|
497
|
+
name: fmtChoice(s.meta, s.name).display,
|
|
498
|
+
value: s,
|
|
499
|
+
description: (s.meta.description || "") + chalk.dim(extra),
|
|
500
|
+
};
|
|
501
|
+
});
|
|
332
502
|
|
|
333
503
|
return select({
|
|
334
|
-
message: '
|
|
335
|
-
choices
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
504
|
+
message: '请选择片段系列:',
|
|
505
|
+
choices,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function promptSnippetVariant(variants) {
|
|
510
|
+
const { select } = await import('@inquirer/prompts');
|
|
511
|
+
if (variants.length === 0) return null;
|
|
512
|
+
|
|
513
|
+
const choices = variants.map((v) => ({
|
|
514
|
+
name: fmtChoice(v.meta, v.name).display,
|
|
515
|
+
value: v,
|
|
516
|
+
description: v.meta.description || undefined,
|
|
517
|
+
}));
|
|
518
|
+
|
|
519
|
+
const defaultIdx = variants.findIndex((v) => v.name === 'default');
|
|
520
|
+
|
|
521
|
+
return select({
|
|
522
|
+
message: '请选择片段变体:',
|
|
523
|
+
choices,
|
|
339
524
|
default: defaultIdx >= 0 ? defaultIdx : 0,
|
|
340
525
|
});
|
|
341
526
|
}
|
|
342
527
|
|
|
343
|
-
|
|
344
|
-
* 交互模式的配置选择提示
|
|
345
|
-
* 优先 CWD 下的配置文件,不存在则回退到片段目录下的,也可手动输入或跳过
|
|
346
|
-
*/
|
|
347
|
-
async function promptConfigForInteractive(snippetDirYamlFiles) {
|
|
528
|
+
async function promptConfig(configs) {
|
|
348
529
|
const { select, input } = await import('@inquirer/prompts');
|
|
349
530
|
|
|
350
|
-
// 检查当前工作目录是否有配置文件
|
|
351
|
-
const cwdCandidates = [
|
|
352
|
-
{ path: path.join(process.cwd(), 'juice.yaml'), name: 'juice.yaml' },
|
|
353
|
-
{ path: path.join(process.cwd(), 'juice.yml'), name: 'juice.yml' },
|
|
354
|
-
];
|
|
355
|
-
const cwdConfig = cwdCandidates.find((c) => fs.existsSync(c.path));
|
|
356
|
-
|
|
357
531
|
const choices = [];
|
|
358
532
|
let defaultIdx = 0;
|
|
359
533
|
|
|
360
|
-
|
|
361
|
-
choices.push({
|
|
362
|
-
name: `[当前目录] ${cwdConfig.name} (优先)`,
|
|
363
|
-
value: { type: 'file', path: cwdConfig.path, name: cwdConfig.name },
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// 片段目录下的配置文件
|
|
368
|
-
const defaultName = 'juice.yaml';
|
|
369
|
-
for (const f of snippetDirYamlFiles) {
|
|
370
|
-
const isDefault = f.name === defaultName;
|
|
534
|
+
for (const c of configs) {
|
|
371
535
|
choices.push({
|
|
372
|
-
name:
|
|
373
|
-
|
|
536
|
+
name: c.isOptimal
|
|
537
|
+
? `${chalk.green('●')} ${c.name} ${chalk.green('(最优配对)')}`
|
|
538
|
+
: ` ${c.name}`,
|
|
539
|
+
value: { type: 'file', path: c.path, name: c.name },
|
|
374
540
|
});
|
|
375
|
-
if (
|
|
376
|
-
defaultIdx = choices.length - 1;
|
|
377
|
-
}
|
|
541
|
+
if (c.isOptimal) defaultIdx = choices.length - 1;
|
|
378
542
|
}
|
|
379
543
|
|
|
380
544
|
choices.push(
|
|
381
|
-
{ name: '[自定义]
|
|
382
|
-
{ name: '[跳过] 不使用项目配置', value: { type: 'skip' } },
|
|
545
|
+
{ name: ' [自定义] 输入其他路径...', value: { type: 'custom' } },
|
|
546
|
+
{ name: ' [跳过] 不使用项目配置', value: { type: 'skip' } },
|
|
383
547
|
);
|
|
384
548
|
|
|
385
549
|
const result = await select({
|
|
@@ -396,24 +560,6 @@ async function promptConfigForInteractive(snippetDirYamlFiles) {
|
|
|
396
560
|
return result;
|
|
397
561
|
}
|
|
398
562
|
|
|
399
|
-
async function promptTemplate(templateFiles) {
|
|
400
|
-
const { select } = await import('@inquirer/prompts');
|
|
401
|
-
if (templateFiles.length === 0) {
|
|
402
|
-
throw new Error('品牌目录下未找到模板 HTML 文件。');
|
|
403
|
-
}
|
|
404
|
-
const templateRe = /-template\.html?$/i;
|
|
405
|
-
const defaultIdx = templateFiles.findIndex((f) => templateRe.test(f.name));
|
|
406
|
-
|
|
407
|
-
return select({
|
|
408
|
-
message: '请选择模板 HTML 文件:',
|
|
409
|
-
choices: templateFiles.map((f, i) => ({
|
|
410
|
-
name: f.name + (i === defaultIdx ? ' (默认)' : ''),
|
|
411
|
-
value: f,
|
|
412
|
-
})),
|
|
413
|
-
default: defaultIdx >= 0 ? defaultIdx : 0,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
|
|
417
563
|
// ─── 输出文件名处理 ───────────────────────────────────────────────────────────
|
|
418
564
|
|
|
419
565
|
/**
|
|
@@ -519,10 +665,10 @@ async function promptConfirm(summary) {
|
|
|
519
665
|
console.log('\n' + chalk.cyan('═══════════════════════════════════════════'));
|
|
520
666
|
console.log(chalk.bold(' 片段组装汇总'));
|
|
521
667
|
console.log(chalk.cyan('═══════════════════════════════════════════'));
|
|
522
|
-
console.log(` 品牌: ${chalk.green(summary.brand)}`);
|
|
523
|
-
console.log(`
|
|
524
|
-
console.log(`
|
|
525
|
-
console.log(`
|
|
668
|
+
console.log(` 品牌: ${chalk.green(summary.brandName)} ${chalk.gray(`(${summary.brand})`)}`);
|
|
669
|
+
console.log(` 模板版本: ${chalk.green(summary.versionName)} ${chalk.gray(`(${summary.version})`)}`);
|
|
670
|
+
console.log(` 片段系列: ${chalk.green(summary.seriesName)} ${chalk.gray(`(${summary.series})`)}`);
|
|
671
|
+
console.log(` 片段变体: ${chalk.green(summary.variantName)} ${chalk.gray(`(${summary.variant})`)}`);
|
|
526
672
|
console.log(` 配置 YAML: ${chalk.green(summary.configFile)}`);
|
|
527
673
|
console.log(chalk.gray('───────────────────────────────────────────'));
|
|
528
674
|
console.log(` 输出目录: ${chalk.cyan(summary.outputDir)}`);
|
|
@@ -545,7 +691,7 @@ async function promptConfirm(summary) {
|
|
|
545
691
|
/**
|
|
546
692
|
* --snippet 模式:指定了片段 HTML
|
|
547
693
|
* - 如果同时指定了 -f(模板),直接使用
|
|
548
|
-
* - 如果未指定 -f,交互式选择:品牌 →
|
|
694
|
+
* - 如果未指定 -f,交互式选择:品牌 → 模板版本
|
|
549
695
|
* - 配置文件:自动检测片段目录下的 juice.yaml / juice.yml,-c 可覆盖
|
|
550
696
|
* - 合并顺序:项目默认 → 用户目录 → 片段目录配置 → CLI -c
|
|
551
697
|
*/
|
|
@@ -565,18 +711,28 @@ async function runSnippetMode({ snippet, template, config: cliConfigPath, output
|
|
|
565
711
|
process.exit(1);
|
|
566
712
|
}
|
|
567
713
|
} else {
|
|
568
|
-
// 交互式选择:品牌 →
|
|
714
|
+
// 交互式选择:品牌 → 模板版本
|
|
569
715
|
const edmDir = resolveEdmDir();
|
|
570
716
|
const brands = findBrands(edmDir);
|
|
571
717
|
const brand = await promptBrand(brands);
|
|
572
|
-
const
|
|
573
|
-
const
|
|
574
|
-
templatePath =
|
|
718
|
+
const versions = findTemplateVersions(brand.path);
|
|
719
|
+
const version = await promptTemplateVersion(versions);
|
|
720
|
+
templatePath = version.templatePath;
|
|
575
721
|
}
|
|
576
722
|
|
|
577
|
-
//
|
|
723
|
+
// 配置文件:非交互模式自动检测片段目录,交互模式提示用户选择
|
|
578
724
|
const snippetDir = path.dirname(snippetPath);
|
|
579
|
-
|
|
725
|
+
let priorityConfigPath;
|
|
726
|
+
|
|
727
|
+
if (template) {
|
|
728
|
+
// 命令行完整指定(-s + -f),自动检测
|
|
729
|
+
priorityConfigPath = findLocalConfig(snippetDir);
|
|
730
|
+
} else {
|
|
731
|
+
// 交互模式(只有 -s),提示选择配置
|
|
732
|
+
const configs = findConfigs(snippetDir);
|
|
733
|
+
const configChoice = await promptConfig(configs);
|
|
734
|
+
priorityConfigPath = (configChoice.type === 'file') ? configChoice.path : null;
|
|
735
|
+
}
|
|
580
736
|
|
|
581
737
|
// 跨品牌检查:片段和模板品牌不一致时给出警告
|
|
582
738
|
try {
|
|
@@ -600,7 +756,6 @@ async function runSnippetMode({ snippet, template, config: cliConfigPath, output
|
|
|
600
756
|
let outputBaseName;
|
|
601
757
|
|
|
602
758
|
if (outputName) {
|
|
603
|
-
// 命令行指定了 --name,检查冲突,有冲突则自动版本号
|
|
604
759
|
const conflicts = checkOutputConflicts(defaultBaseName, process.cwd());
|
|
605
760
|
if (conflicts.length > 0) {
|
|
606
761
|
outputBaseName = findNextVersion(defaultBaseName, process.cwd());
|
|
@@ -611,7 +766,6 @@ async function runSnippetMode({ snippet, template, config: cliConfigPath, output
|
|
|
611
766
|
outputBaseName = defaultBaseName;
|
|
612
767
|
}
|
|
613
768
|
} else if (template) {
|
|
614
|
-
// 非交互模式(--snippet + -f 均指定),自动版本号
|
|
615
769
|
const conflicts = checkOutputConflicts(defaultBaseName, process.cwd());
|
|
616
770
|
if (conflicts.length > 0) {
|
|
617
771
|
outputBaseName = findNextVersion(defaultBaseName, process.cwd());
|
|
@@ -622,7 +776,6 @@ async function runSnippetMode({ snippet, template, config: cliConfigPath, output
|
|
|
622
776
|
outputBaseName = defaultBaseName;
|
|
623
777
|
}
|
|
624
778
|
} else {
|
|
625
|
-
// 交互模式(--snippet 单独指定),提示用户输入
|
|
626
779
|
outputBaseName = await promptOutputName(defaultBaseName, process.cwd());
|
|
627
780
|
}
|
|
628
781
|
|
|
@@ -636,10 +789,8 @@ async function runSnippetMode({ snippet, template, config: cliConfigPath, output
|
|
|
636
789
|
}
|
|
637
790
|
|
|
638
791
|
/**
|
|
639
|
-
*
|
|
640
|
-
* 流程:品牌 →
|
|
641
|
-
* 配置文件:优先 CWD,不存在则用片段目录下 YAML,也可手动输入
|
|
642
|
-
* 合并顺序:项目默认 → 用户目录 → 项目配置(CWD/片段/手动三选一)→ CLI -c
|
|
792
|
+
* 交互模式(无 --snippet,无 -f)— 方案 B 新结构
|
|
793
|
+
* 流程:品牌 → 模板版本 → 片段系列 → 片段变体 → 配置文件 → 输出名 → 确认 → 执行
|
|
643
794
|
*/
|
|
644
795
|
async function runInteractiveMode({ config: cliConfigPath }) {
|
|
645
796
|
const edmDir = resolveEdmDir();
|
|
@@ -648,32 +799,43 @@ async function runInteractiveMode({ config: cliConfigPath }) {
|
|
|
648
799
|
const brands = findBrands(edmDir);
|
|
649
800
|
const brand = await promptBrand(brands);
|
|
650
801
|
|
|
651
|
-
// 2.
|
|
652
|
-
const
|
|
653
|
-
const
|
|
802
|
+
// 2. 选择模板版本
|
|
803
|
+
const versions = findTemplateVersions(brand.path);
|
|
804
|
+
const version = await promptTemplateVersion(versions);
|
|
654
805
|
|
|
655
|
-
// 3.
|
|
656
|
-
const
|
|
657
|
-
if (
|
|
658
|
-
console.log(chalk.yellow(`\n ⚠ 品牌「${brand.name}」下暂无片段系列。`));
|
|
659
|
-
await copyTemplateToCwd(
|
|
806
|
+
// 3. 选择片段系列(按版本 series.allow/block 过滤)
|
|
807
|
+
const allSeries = findSeriesDirs(brand.path);
|
|
808
|
+
if (allSeries.length === 0) {
|
|
809
|
+
console.log(chalk.yellow(`\n ⚠ 品牌「${brand.meta.name || brand.name}」下暂无片段系列。`));
|
|
810
|
+
await copyTemplateToCwd(version.templatePath);
|
|
660
811
|
return;
|
|
661
812
|
}
|
|
662
|
-
const
|
|
663
|
-
if (
|
|
813
|
+
const filteredSeries = filterSeries(allSeries, version.meta);
|
|
814
|
+
if (filteredSeries.length === 0) {
|
|
815
|
+
console.log(chalk.yellow(`\n ⚠ 版本「${version.meta.name || version.name}」配置的系列过滤后无可用系列。`));
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
const series = await promptSeries(filteredSeries);
|
|
819
|
+
if (!series) return;
|
|
820
|
+
|
|
821
|
+
// 4. 选择片段变体
|
|
822
|
+
const variants = findSnippetVariants(series.path);
|
|
823
|
+
if (variants.length === 0) {
|
|
824
|
+
console.log(chalk.yellow(`\n ⚠ 系列「${series.meta.name || series.name}」下暂无 snippet.html。`));
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const variant = await promptSnippetVariant(variants);
|
|
828
|
+
if (!variant) return;
|
|
664
829
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
console.log(chalk.yellow(`\n ⚠ 片段系列「${folder.name}」下暂无 HTML 文件。`));
|
|
830
|
+
const snippetPath = path.join(variant.path, 'snippet.html');
|
|
831
|
+
if (!fs.existsSync(snippetPath)) {
|
|
832
|
+
console.log(chalk.red(`\n ✘ 片段文件不存在:${snippetPath}`));
|
|
669
833
|
return;
|
|
670
834
|
}
|
|
671
|
-
const snippetFile = await promptSnippetFile(htmlFiles);
|
|
672
|
-
if (!snippetFile) return;
|
|
673
835
|
|
|
674
|
-
// 5.
|
|
675
|
-
const
|
|
676
|
-
const configChoice = await
|
|
836
|
+
// 5. 选择配置文件
|
|
837
|
+
const configs = findConfigs(variant.path);
|
|
838
|
+
const configChoice = await promptConfig(configs, variant.path);
|
|
677
839
|
|
|
678
840
|
let priorityConfigPath = null;
|
|
679
841
|
let configFileName = '(跳过)';
|
|
@@ -683,17 +845,20 @@ async function runInteractiveMode({ config: cliConfigPath }) {
|
|
|
683
845
|
}
|
|
684
846
|
|
|
685
847
|
// 6. 确定输出文件名
|
|
686
|
-
const defaultBaseName =
|
|
848
|
+
const defaultBaseName = `${brand.name}-${version.name}-${series.name}-${variant.name}-template`;
|
|
687
849
|
const outputBaseName = await promptOutputName(defaultBaseName, process.cwd());
|
|
688
850
|
|
|
689
851
|
// 7. 汇总确认
|
|
690
852
|
const confirmed = await promptConfirm({
|
|
853
|
+
brandName: brand.meta.name || brand.name,
|
|
691
854
|
brand: brand.name,
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
855
|
+
versionName: version.meta.name || version.name,
|
|
856
|
+
version: version.name,
|
|
857
|
+
seriesName: series.meta.name || series.name,
|
|
858
|
+
series: series.name,
|
|
859
|
+
variantName: variant.meta.name || variant.name,
|
|
860
|
+
variant: variant.name,
|
|
695
861
|
configFile: configFileName,
|
|
696
|
-
snippetPath: snippetFile.path,
|
|
697
862
|
outputDir: process.cwd(),
|
|
698
863
|
outputBaseName,
|
|
699
864
|
});
|
|
@@ -708,8 +873,8 @@ async function runInteractiveMode({ config: cliConfigPath }) {
|
|
|
708
873
|
config._layers = layers;
|
|
709
874
|
|
|
710
875
|
await assembleSnippet({
|
|
711
|
-
snippetPath:
|
|
712
|
-
templatePath:
|
|
876
|
+
snippetPath: snippetPath,
|
|
877
|
+
templatePath: version.templatePath,
|
|
713
878
|
config,
|
|
714
879
|
cwd: process.cwd(),
|
|
715
880
|
outputBaseName,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|