@agile-team/robot-cli 1.1.8 → 1.1.12

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/lib/create.js CHANGED
@@ -1,1146 +1,1201 @@
1
- // lib/create.js - 完整优化版,移除缓存功能 + 美化展示
2
- import fs from "fs-extra";
3
- import path from "path";
4
- import chalk from "chalk";
5
- import inquirer from "inquirer";
6
- import ora from "ora";
7
- import { execSync } from "child_process";
8
- import { downloadTemplate } from "./download.js";
9
- import {
10
- TEMPLATE_CATEGORIES,
11
- getAllTemplates,
12
- getTemplatesByCategory,
13
- searchTemplates,
14
- getRecommendedTemplates,
15
- } from "./templates.js";
16
- import {
17
- validateProjectName,
18
- copyTemplate,
19
- installDependencies,
20
- generateProjectStats,
21
- printProjectStats,
22
- } from "./utils.js";
23
-
24
- /**
25
- * 创建项目主函数
26
- */
27
- export async function createProject(projectName, options = {}) {
28
- console.log();
29
- console.log(chalk.cyan("🚀 Robot CLI - 开始创建项目"));
30
- console.log();
31
-
32
- // 1. 选择模板
33
- const template = await selectTemplate(options.template);
34
-
35
- // 2. 处理项目名称
36
- const finalProjectName = await handleProjectName(projectName, template);
37
-
38
- // 3. 项目配置选项
39
- const projectConfig = await configureProject(options);
40
-
41
- // 4. 确认创建
42
- await confirmCreation(finalProjectName, template, projectConfig);
43
-
44
- // 5. 创建项目
45
- await executeCreation(finalProjectName, template, projectConfig);
46
- }
47
-
48
- /**
49
- * 处理项目名称
50
- */
51
- async function handleProjectName(projectName, template) {
52
- if (projectName) {
53
- // 验证项目名称
54
- const validation = validateProjectName(projectName);
55
- if (!validation.valid) {
56
- console.log(chalk.red("❌ 项目名称不合法:"));
57
- validation.errors.forEach((error) => {
58
- console.log(chalk.red(` ${error}`));
59
- });
60
- console.log();
61
-
62
- const { newName } = await inquirer.prompt([
63
- {
64
- type: "input",
65
- name: "newName",
66
- message: "请输入新的项目名称:",
67
- validate: (input) => {
68
- const result = validateProjectName(input);
69
- return result.valid || result.errors[0];
70
- },
71
- },
72
- ]);
73
- return newName;
74
- }
75
- return projectName;
76
- } else {
77
- // 根据模板名称生成默认项目名称
78
- const defaultName = generateDefaultProjectName(template);
79
-
80
- const { name } = await inquirer.prompt([
81
- {
82
- type: "input",
83
- name: "name",
84
- message: "请输入项目名称:",
85
- default: defaultName,
86
- validate: (input) => {
87
- if (!input.trim()) return "项目名称不能为空";
88
- const result = validateProjectName(input);
89
- return result.valid || result.errors[0];
90
- },
91
- },
92
- ]);
93
- return name;
94
- }
95
- }
96
-
97
- /**
98
- * 根据模板生成默认项目名称
99
- */
100
- function generateDefaultProjectName(template) {
101
- if (!template) return "my-project";
102
-
103
- const templateKey = template.key || "project";
104
- const timestamp = Date.now().toString().slice(-4);
105
-
106
- // 移除版本后缀 (-full, -lite)
107
- const baseName = templateKey.replace(/-(full|lite|base)$/, "");
108
-
109
- return `my-${baseName}-${timestamp}`;
110
- }
111
-
112
- /**
113
- * 选择模板 - 多种方式(带返回功能)
114
- */
115
- async function selectTemplate(templateOption) {
116
- if (templateOption) {
117
- // 命令行指定了模板
118
- const allTemplates = getAllTemplates();
119
- if (allTemplates[templateOption]) {
120
- return { key: templateOption, ...allTemplates[templateOption] };
121
- } else {
122
- console.log(chalk.yellow(`⚠️ 模板 "${templateOption}" 不存在`));
123
- console.log();
124
- }
125
- }
126
-
127
- // 交互式选择 - 主选择方式
128
- return await selectTemplateMethod();
129
- }
130
-
131
- /**
132
- * 选择模板方式 - 优化主菜单
133
- */
134
- async function selectTemplateMethod() {
135
- console.log();
136
- console.log(chalk.blue.bold("🎯 选择模板创建方式"));
137
- console.log(chalk.dim("请选择最适合你的模板浏览方式"));
138
- console.log();
139
-
140
- const { selectionMode } = await inquirer.prompt([
141
- {
142
- type: "list",
143
- name: "selectionMode",
144
- message: "模板选择方式:",
145
- choices: [
146
- {
147
- name: `● ${chalk.bold('推荐模板')} ${chalk.dim('(常用模板快速选择) - 基于团队使用频率推荐的热门模板')}`,
148
- value: "recommended"
149
- },
150
- {
151
- name: chalk.dim("─".repeat(70)),
152
- value: "sep1",
153
- disabled: ""
154
- },
155
- {
156
- name: `● ${chalk.bold('分类模板')} ${chalk.dim('(按项目类型分类选择) - 前端、后端、移动端、桌面端分类浏览')}`,
157
- value: "category"
158
- },
159
- {
160
- name: chalk.dim("─".repeat(70)),
161
- value: "sep2",
162
- disabled: ""
163
- },
164
- {
165
- name: `● ${chalk.bold('搜索模板')} ${chalk.dim('(关键词搜索) - 通过技术栈、功能特性等关键词快速查找')}`,
166
- value: "search"
167
- },
168
- {
169
- name: chalk.dim("─".repeat(70)),
170
- value: "sep3",
171
- disabled: ""
172
- },
173
- {
174
- name: `● ${chalk.bold('全部模板')} ${chalk.dim('(查看所有可用模板) - 按分类展示所有可用的项目模板')}`,
175
- value: "all"
176
- },
177
- ],
178
- pageSize: 10
179
- },
180
- ]);
181
-
182
- switch (selectionMode) {
183
- case "recommended":
184
- return await selectFromRecommended();
185
- case "category":
186
- return await selectByCategory();
187
- case "search":
188
- return await selectBySearch();
189
- case "all":
190
- return await selectFromAll();
191
- default:
192
- return await selectByCategory();
193
- }
194
- }
195
-
196
- /**
197
- * 从推荐模板中选择 - 优化后的展示
198
- */
199
- async function selectFromRecommended() {
200
- const recommended = getRecommendedTemplates();
201
-
202
- if (Object.keys(recommended).length === 0) {
203
- console.log(chalk.yellow("⚠️ 暂无推荐模板"));
204
- return await selectTemplateMethod();
205
- }
206
-
207
- console.log();
208
- console.log(chalk.blue.bold("🎯 推荐模板"));
209
- console.log(chalk.dim("基于团队使用频率和项目成熟度推荐"));
210
- console.log();
211
-
212
- // 创建更美观的选择项
213
- const choices = Object.entries(recommended).map(([key, template]) => {
214
- // 构建特性标签
215
- const featureTags = template.features.slice(0, 3).map(f =>
216
- chalk.dim(`[${f}]`)
217
- ).join(' ');
218
-
219
- // 版本标签 - 只保留方括号内的版本
220
- const versionTag = template.version === 'full' ?
221
- chalk.green('[完整版]') :
222
- chalk.yellow('[精简版]');
223
-
224
- return {
225
- name: `${chalk.bold.white(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
226
- ${chalk.dim(featureTags)}${template.features.length > 3 ? chalk.dim(` +${template.features.length - 3}more`) : ''}`,
227
- value: { key, ...template },
228
- short: template.name,
229
- };
230
- });
231
-
232
- // 为每个推荐模板添加分隔符
233
- const choicesWithSeparators = [];
234
- choices.forEach((choice, index) => {
235
- choicesWithSeparators.push(choice);
236
- if (index < choices.length - 1) {
237
- choicesWithSeparators.push({
238
- name: chalk.dim("─".repeat(70)),
239
- value: `sep_${index}`,
240
- disabled: ""
241
- });
242
- }
243
- });
244
-
245
- choicesWithSeparators.push({
246
- name: chalk.dim("⬅️ 返回选择其他方式"),
247
- value: "back",
248
- });
249
-
250
- const { selectedTemplate } = await inquirer.prompt([
251
- {
252
- type: "list",
253
- name: "selectedTemplate",
254
- message: "选择推荐模板:",
255
- choices: choicesWithSeparators,
256
- pageSize: 15,
257
- loop: false
258
- },
259
- ]);
260
-
261
- if (selectedTemplate === "back") {
262
- return await selectTemplateMethod();
263
- }
264
-
265
- return selectedTemplate;
266
- }
267
-
268
- /**
269
- * 按分类选择模板(完整的返回功能)
270
- */
271
- async function selectByCategory() {
272
- // 1. 选择项目类型
273
- while (true) {
274
- const categoryResult = await selectCategory();
275
- if (categoryResult === "back_to_method") {
276
- return await selectTemplateMethod();
277
- }
278
-
279
- // 2. 选择技术栈
280
- const stackResult = await selectStack(categoryResult);
281
- if (stackResult === "back_to_category") {
282
- continue; // 返回到项目类型选择
283
- }
284
- if (stackResult === "back_to_method") {
285
- return await selectTemplateMethod();
286
- }
287
-
288
- // 3. 选择架构模式
289
- const patternResult = await selectPattern(categoryResult, stackResult);
290
- if (patternResult === "back_to_stack") {
291
- continue; // 返回到技术栈选择,会重新开始while循环
292
- }
293
- if (patternResult === "back_to_category") {
294
- continue; // 返回到项目类型选择
295
- }
296
- if (patternResult === "back_to_method") {
297
- return await selectTemplateMethod();
298
- }
299
-
300
- // 4. 选择具体模板
301
- const templateResult = await selectSpecificTemplate(
302
- categoryResult,
303
- stackResult,
304
- patternResult
305
- );
306
- if (templateResult === "back_to_pattern") {
307
- continue; // 返回到架构模式选择
308
- }
309
- if (templateResult === "back_to_stack") {
310
- continue; // 返回到技术栈选择
311
- }
312
- if (templateResult === "back_to_category") {
313
- continue; // 返回到项目类型选择
314
- }
315
- if (templateResult === "back_to_method") {
316
- return await selectTemplateMethod();
317
- }
318
-
319
- // 成功选择了模板
320
- return templateResult;
321
- }
322
- }
323
-
324
- /**
325
- * 选择项目类型
326
- */
327
- async function selectCategory() {
328
- const categoryChoices = Object.entries(TEMPLATE_CATEGORIES).map(
329
- ([key, category]) => ({
330
- name: category.name,
331
- value: key,
332
- })
333
- );
334
-
335
- categoryChoices.push({
336
- name: chalk.dim("← 返回模板选择方式"),
337
- value: "back_to_method",
338
- });
339
-
340
- const { categoryKey } = await inquirer.prompt([
341
- {
342
- type: "list",
343
- name: "categoryKey",
344
- message: "请选择项目类型:",
345
- choices: categoryChoices,
346
- },
347
- ]);
348
-
349
- return categoryKey;
350
- }
351
-
352
- /**
353
- * 选择技术栈
354
- */
355
- async function selectStack(categoryKey) {
356
- if (categoryKey === "back_to_method") return categoryKey;
357
-
358
- const category = TEMPLATE_CATEGORIES[categoryKey];
359
- const stackChoices = Object.entries(category.stacks).map(([key, stack]) => ({
360
- name: stack.name,
361
- value: key,
362
- }));
363
-
364
- stackChoices.push(
365
- {
366
- name: chalk.dim("─────────────────────"),
367
- value: "separator",
368
- disabled: true,
369
- },
370
- { name: chalk.dim("← 返回项目类型选择"), value: "back_to_category" },
371
- { name: chalk.dim("← 返回模板选择方式"), value: "back_to_method" }
372
- );
373
-
374
- if (stackChoices.length === 3) {
375
- // 只有一个技术栈 + 分隔线 + 返回选项
376
- return stackChoices[0].value;
377
- }
378
-
379
- const { stackKey } = await inquirer.prompt([
380
- {
381
- type: "list",
382
- name: "stackKey",
383
- message: "请选择技术栈:",
384
- choices: stackChoices,
385
- },
386
- ]);
387
-
388
- return stackKey;
389
- }
390
-
391
- /**
392
- * 选择架构模式
393
- */
394
- async function selectPattern(categoryKey, stackKey) {
395
- if (["back_to_category", "back_to_method"].includes(stackKey))
396
- return stackKey;
397
-
398
- const category = TEMPLATE_CATEGORIES[categoryKey];
399
- const stack = category.stacks[stackKey];
400
- const patternChoices = Object.entries(stack.patterns).map(
401
- ([key, pattern]) => ({
402
- name: pattern.name,
403
- value: key,
404
- })
405
- );
406
-
407
- patternChoices.push(
408
- {
409
- name: chalk.dim("─────────────────────"),
410
- value: "separator",
411
- disabled: true,
412
- },
413
- { name: chalk.dim("← 返回技术栈选择"), value: "back_to_stack" },
414
- { name: chalk.dim("← 返回项目类型选择"), value: "back_to_category" },
415
- { name: chalk.dim("← 返回模板选择方式"), value: "back_to_method" }
416
- );
417
-
418
- if (patternChoices.length === 4) {
419
- // 只有一个模式 + 分隔线 + 返回选项
420
- return patternChoices[0].value;
421
- }
422
-
423
- const { patternKey } = await inquirer.prompt([
424
- {
425
- type: "list",
426
- name: "patternKey",
427
- message: "请选择架构模式:",
428
- choices: patternChoices,
429
- },
430
- ]);
431
-
432
- return patternKey;
433
- }
434
-
435
- /**
436
- * 选择具体模板
437
- */
438
- async function selectSpecificTemplate(categoryKey, stackKey, patternKey) {
439
- if (
440
- ["back_to_stack", "back_to_category", "back_to_method"].includes(patternKey)
441
- ) {
442
- return patternKey;
443
- }
444
-
445
- const templates = getTemplatesByCategory(categoryKey, stackKey, patternKey);
446
- const templateChoices = Object.entries(templates).map(([key, template]) => ({
447
- name: `${template.name} - ${chalk.dim(template.description)}`,
448
- value: { key, ...template },
449
- short: template.name,
450
- }));
451
-
452
- templateChoices.push(
453
- {
454
- name: chalk.dim("─────────────────────"),
455
- value: "separator",
456
- disabled: true,
457
- },
458
- { name: chalk.dim("← 返回架构模式选择"), value: "back_to_pattern" },
459
- { name: chalk.dim("← 返回技术栈选择"), value: "back_to_stack" },
460
- { name: chalk.dim("← 返回项目类型选择"), value: "back_to_category" },
461
- { name: chalk.dim("← 返回模板选择方式"), value: "back_to_method" }
462
- );
463
-
464
- const { selectedTemplate } = await inquirer.prompt([
465
- {
466
- type: "list",
467
- name: "selectedTemplate",
468
- message: "请选择模板版本:",
469
- choices: templateChoices,
470
- },
471
- ]);
472
-
473
- return selectedTemplate;
474
- }
475
-
476
- /**
477
- * 搜索选择模板 - 优化结果展示
478
- */
479
- async function selectBySearch() {
480
- while (true) {
481
- const { keyword } = await inquirer.prompt([
482
- {
483
- type: "input",
484
- name: "keyword",
485
- message: "请输入搜索关键词 (名称、描述、技术栈):",
486
- validate: (input) => (input.trim() ? true : "关键词不能为空"),
487
- },
488
- ]);
489
-
490
- const results = searchTemplates(keyword);
491
-
492
- if (Object.keys(results).length === 0) {
493
- console.log();
494
- console.log(chalk.yellow("🔍 没有找到匹配的模板"));
495
- console.log(chalk.dim(`搜索关键词: "${keyword}"`));
496
- console.log();
497
-
498
- const { action } = await inquirer.prompt([
499
- {
500
- type: "list",
501
- name: "action",
502
- message: "请选择下一步操作:",
503
- choices: [
504
- { name: "🔍 重新搜索", value: "retry" },
505
- { name: "⬅️ 返回模板选择方式", value: "back" },
506
- ],
507
- },
508
- ]);
509
-
510
- if (action === "retry") {
511
- continue;
512
- } else {
513
- return await selectTemplateMethod();
514
- }
515
- }
516
-
517
- console.log();
518
- console.log(chalk.green.bold(`🔍 搜索结果`));
519
- console.log(chalk.dim(`关键词: "${keyword}" • 找到 ${Object.keys(results).length} 个匹配模板`));
520
- console.log();
521
-
522
- const choices = Object.entries(results).map(([key, template]) => {
523
- // 高亮匹配的关键词
524
- const highlightText = (text) => {
525
- const regex = new RegExp(`(${keyword})`, 'gi');
526
- return text.replace(regex, chalk.bgYellow.black('$1'));
527
- };
528
-
529
- const techInfo = template.features.slice(0, 2).join(' • ');
530
- const versionTag = template.version === 'full' ?
531
- chalk.green('[完整版]') : chalk.yellow('[精简版]');
532
-
533
- return {
534
- name: `${chalk.bold(highlightText(template.name.replace(/\s*(完整版|精简版)\s*$/, '')))} ${versionTag}
535
- ${chalk.dim(highlightText(template.description))}
536
- ${chalk.dim(`${techInfo} • 模板key: ${key}`)}
537
- ${chalk.dim('─'.repeat(60))}`,
538
- value: { key, ...template },
539
- short: template.name,
540
- };
541
- });
542
-
543
- choices.push(
544
- {
545
- name: chalk.dim("━".repeat(70)),
546
- value: "separator",
547
- disabled: "",
548
- },
549
- { name: "🔍 重新搜索", value: "search_again" },
550
- { name: "⬅️ 返回模板选择方式", value: "back_to_mode" }
551
- );
552
-
553
- const { selectedTemplate } = await inquirer.prompt([
554
- {
555
- type: "list",
556
- name: "selectedTemplate",
557
- message: "选择模板:",
558
- choices,
559
- pageSize: 15,
560
- loop: false
561
- },
562
- ]);
563
-
564
- if (selectedTemplate === "search_again") {
565
- continue;
566
- } else if (selectedTemplate === "back_to_mode") {
567
- return await selectTemplateMethod();
568
- } else {
569
- return selectedTemplate;
570
- }
571
- }
572
- }
573
-
574
- /**
575
- * 从全部模板中选择 - 优化后的展示
576
- */
577
- async function selectFromAll() {
578
- const allTemplates = getAllTemplates();
579
-
580
- console.log();
581
- console.log(chalk.blue.bold(`📋 所有可用模板`));
582
- console.log(chalk.dim(`共 ${Object.keys(allTemplates).length} 个模板可选`));
583
- console.log();
584
-
585
- // 按分类组织模板
586
- const categorizedChoices = [];
587
-
588
- // 前端模板
589
- categorizedChoices.push({
590
- name: chalk.yellow.bold("🎨 前端项目"),
591
- value: "frontend_header",
592
- disabled: ""
593
- });
594
-
595
- Object.entries(allTemplates)
596
- .filter(([key]) => key.includes('admin') || key.includes('react') || key.includes('micro'))
597
- .forEach(([key, template]) => {
598
- const techStack = key.includes('admin') ? 'Vue3' :
599
- key.includes('react') ? 'React' : 'Vue3';
600
- const versionTag = template.version === 'full' ?
601
- chalk.green('[完整版]') : chalk.yellow('[精简版]');
602
-
603
- categorizedChoices.push({
604
- name: ` ● ${chalk.bold(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
605
- ${chalk.dim(`技术栈: ${techStack} • 命令: robot create my-app -t ${key}`)}`,
606
- value: { key, ...template },
607
- short: template.name
608
- });
609
-
610
- // 添加分隔符
611
- categorizedChoices.push({
612
- name: chalk.dim(" " + "─".repeat(66)),
613
- value: `sep_${key}`,
614
- disabled: ""
615
- });
616
- });
617
-
618
- // 移动端模板
619
- categorizedChoices.push({
620
- name: chalk.yellow.bold("📱 移动端项目"),
621
- value: "mobile_header",
622
- disabled: ""
623
- });
624
-
625
- Object.entries(allTemplates)
626
- .filter(([key]) => key.includes('uniapp') || key.includes('tarao'))
627
- .forEach(([key, template]) => {
628
- const versionTag = template.version === 'full' ?
629
- chalk.green('[完整版]') : chalk.yellow('[精简版]');
630
-
631
- categorizedChoices.push({
632
- name: ` ● ${chalk.bold(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
633
- ${chalk.dim(`跨平台: 小程序/H5/App 命令: robot create my-app -t ${key}`)}`,
634
- value: { key, ...template },
635
- short: template.name
636
- });
637
-
638
- // 添加分隔符
639
- categorizedChoices.push({
640
- name: chalk.dim(" " + "─".repeat(66)),
641
- value: `sep_${key}`,
642
- disabled: ""
643
- });
644
- });
645
-
646
- // 后端模板
647
- categorizedChoices.push({
648
- name: chalk.yellow.bold("🚀 后端项目"),
649
- value: "backend_header",
650
- disabled: ""
651
- });
652
-
653
- Object.entries(allTemplates)
654
- .filter(([key]) => key.includes('nest') || key.includes('koa'))
655
- .forEach(([key, template]) => {
656
- const framework = key.includes('nest') ? 'NestJS' : 'Koa3';
657
- const versionTag = template.version === 'full' ?
658
- chalk.green('[完整版]') : chalk.yellow('[精简版]');
659
-
660
- categorizedChoices.push({
661
- name: ` ● ${chalk.bold(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
662
- ${chalk.dim(`框架: ${framework} • 命令: robot create my-app -t ${key}`)}`,
663
- value: { key, ...template },
664
- short: template.name
665
- });
666
-
667
- // 添加分隔符
668
- categorizedChoices.push({
669
- name: chalk.dim(" " + "─".repeat(66)),
670
- value: `sep_${key}`,
671
- disabled: ""
672
- });
673
- });
674
-
675
- // 桌面端模板
676
- categorizedChoices.push({
677
- name: chalk.yellow.bold("💻 桌面端项目"),
678
- value: "desktop_header",
679
- disabled: ""
680
- });
681
-
682
- Object.entries(allTemplates)
683
- .filter(([key]) => key.includes('electron') || key.includes('tauri'))
684
- .forEach(([key, template]) => {
685
- const framework = key.includes('electron') ? 'Electron' : 'Tauri';
686
- const versionTag = template.version === 'full' ?
687
- chalk.green('[完整版]') : chalk.yellow('[精简版]');
688
-
689
- categorizedChoices.push({
690
- name: ` ● ${chalk.bold(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
691
- ${chalk.dim(`框架: ${framework} • 命令: robot create my-app -t ${key}`)}`,
692
- value: { key, ...template },
693
- short: template.name
694
- });
695
-
696
- // 添加分隔符
697
- categorizedChoices.push({
698
- name: chalk.dim(" " + "─".repeat(66)),
699
- value: `sep_${key}`,
700
- disabled: ""
701
- });
702
- });
703
-
704
- // 添加返回选项
705
- categorizedChoices.push(
706
- {
707
- name: chalk.dim("━".repeat(70)),
708
- value: "separator",
709
- disabled: "",
710
- },
711
- {
712
- name: chalk.dim("⬅️ 返回模板选择方式"),
713
- value: "back_to_mode"
714
- }
715
- );
716
-
717
- const { selectedTemplate } = await inquirer.prompt([
718
- {
719
- type: "list",
720
- name: "selectedTemplate",
721
- message: "选择模板:",
722
- choices: categorizedChoices,
723
- pageSize: 25,
724
- loop: false
725
- },
726
- ]);
727
-
728
- if (selectedTemplate === "back_to_mode") {
729
- return await selectTemplateMethod();
730
- }
731
-
732
- return selectedTemplate;
733
- }
734
-
735
- /**
736
- * 项目配置选项 - 移除缓存相关配置
737
- */
738
- async function configureProject(options) {
739
- console.log();
740
- console.log(chalk.blue("⚙️ 项目配置"));
741
- console.log();
742
-
743
- const config = await inquirer.prompt([
744
- {
745
- type: "confirm",
746
- name: "initGit",
747
- message: "是否初始化 Git 仓库?",
748
- default: true,
749
- },
750
- {
751
- type: "confirm",
752
- name: "installDeps",
753
- message: "是否立即安装依赖?",
754
- default: !options.skipInstall,
755
- },
756
- {
757
- type: "list",
758
- name: "packageManager",
759
- message: "选择包管理器:",
760
- choices: [
761
- { name: "bun (推荐 - 极速安装,现代化)", value: "bun" },
762
- { name: "pnpm (推荐 - 快速安装,节省空间)", value: "pnpm" },
763
- { name: "yarn (兼容 - 适用于现有yarn项目)", value: "yarn" },
764
- { name: "npm (兼容 - Node.js默认)", value: "npm" },
765
- ],
766
- default: "bun",
767
- when: (answers) => answers.installDeps,
768
- },
769
- {
770
- type: "input",
771
- name: "description",
772
- message: "项目描述 (可选):",
773
- default: "",
774
- },
775
- {
776
- type: "input",
777
- name: "author",
778
- message: "作者 (可选):",
779
- default: "CHENY",
780
- },
781
- {
782
- type: "confirm",
783
- name: "confirmConfig",
784
- message: "确认以上配置?",
785
- default: true,
786
- },
787
- ]);
788
-
789
- if (!config.confirmConfig) {
790
- const { action } = await inquirer.prompt([
791
- {
792
- type: "list",
793
- name: "action",
794
- message: "请选择操作:",
795
- choices: [
796
- { name: "🔄 重新配置", value: "reconfigure" },
797
- { name: "❌ 取消创建", value: "cancel" },
798
- ],
799
- },
800
- ]);
801
-
802
- if (action === "reconfigure") {
803
- return await configureProject(options);
804
- } else {
805
- console.log(chalk.yellow("❌ 取消创建项目"));
806
- process.exit(0);
807
- }
808
- }
809
-
810
- return config;
811
- }
812
-
813
- /**
814
- * 确认创建 - 移除缓存相关信息显示
815
- */
816
- async function confirmCreation(projectName, template, projectConfig) {
817
- console.log();
818
- console.log(chalk.blue("📋 项目创建信息确认:"));
819
- console.log();
820
- console.log(` 项目名称: ${chalk.cyan(projectName)}`);
821
- console.log(` 选择模板: ${chalk.cyan(template.name)}`);
822
- console.log(` 模板描述: ${chalk.dim(template.description)}`);
823
- console.log(` 包含功能: ${chalk.dim(template.features.join(", "))}`);
824
-
825
- if (projectConfig.description) {
826
- console.log(` 项目描述: ${chalk.dim(projectConfig.description)}`);
827
- }
828
-
829
- if (projectConfig.author) {
830
- console.log(` 作  者: ${chalk.dim(projectConfig.author)}`);
831
- }
832
-
833
- console.log(
834
- ` 初始化Git: ${
835
- projectConfig.initGit ? chalk.green("是") : chalk.dim("否")
836
- }`
837
- );
838
- console.log(
839
- ` 安装依赖: ${
840
- projectConfig.installDeps
841
- ? chalk.green("") + chalk.dim(` (${projectConfig.packageManager})`)
842
- : chalk.dim("否")
843
- }`
844
- );
845
- console.log(` 源码仓库: ${chalk.dim(template.repoUrl)}`);
846
- console.log();
847
-
848
- const { confirmed } = await inquirer.prompt([
849
- {
850
- type: "confirm",
851
- name: "confirmed",
852
- message: "确认创建项目?",
853
- default: true,
854
- },
855
- ]);
856
-
857
- if (!confirmed) {
858
- console.log(chalk.yellow("❌ 取消创建"));
859
- process.exit(0);
860
- }
861
- }
862
-
863
- /**
864
- * 执行创建流程 - 简化版本,总是下载最新模板
865
- */
866
- async function executeCreation(projectName, template, projectConfig) {
867
- if (!projectName || typeof projectName !== "string") {
868
- throw new Error(`项目名称无效: ${projectName}`);
869
- }
870
-
871
- if (!template || !template.name) {
872
- throw new Error(`模板数据无效: ${JSON.stringify(template)}`);
873
- }
874
-
875
- const spinner = ora({
876
- text: "🚀 准备创建项目...",
877
- spinner: 'dots',
878
- color: 'cyan'
879
- }).start();
880
-
881
- let tempTemplatePath; // 用于清理临时文件
882
-
883
- try {
884
- // 1. 检查目录是否存在
885
- spinner.text = "📁 检查项目目录...";
886
- const projectPath = path.resolve(projectName);
887
- if (fs.existsSync(projectPath)) {
888
- spinner.stop();
889
- console.log(chalk.yellow("⚠️ 项目目录已存在"));
890
-
891
- const { overwrite } = await inquirer.prompt([
892
- {
893
- type: "confirm",
894
- name: "overwrite",
895
- message: "目录已存在,是否覆盖?",
896
- default: false,
897
- },
898
- ]);
899
-
900
- if (!overwrite) {
901
- console.log(chalk.yellow("❌ 取消创建"));
902
- process.exit(0);
903
- }
904
-
905
- spinner.start("🗑️ 清理现有目录...");
906
- await fs.remove(projectPath);
907
- spinner.text = "📁 准备创建新目录...";
908
- }
909
-
910
- // 2. 下载最新模板
911
- spinner.text = "🌐 下载最新模板...";
912
- try {
913
- tempTemplatePath = await downloadTemplate(template, { spinner });
914
-
915
- if (!tempTemplatePath || !fs.existsSync(tempTemplatePath)) {
916
- throw new Error(`模板路径无效: ${tempTemplatePath}`);
917
- }
918
- } catch (error) {
919
- spinner.fail("模板下载失败");
920
- console.log();
921
- console.log(chalk.red("❌ 模板下载错误:"));
922
- console.log(chalk.dim(` ${error.message}`));
923
- console.log();
924
- console.log(chalk.blue("💡 可能的解决方案:"));
925
- console.log(chalk.dim(" 1. 检查网络连接"));
926
- console.log(chalk.dim(" 2. 重试命令"));
927
- console.log(chalk.dim(" 3. 检查仓库地址是否正确"));
928
- console.log();
929
- throw error;
930
- }
931
-
932
- // 3. 复制模板文件 - 带详细进度
933
- await copyTemplate(tempTemplatePath, projectPath, spinner);
934
-
935
- // 4. 处理项目配置
936
- spinner.text = "⚙️ 处理项目配置...";
937
- await processProjectConfig(
938
- projectPath,
939
- projectName,
940
- template,
941
- projectConfig
942
- );
943
-
944
- // 5. 初始化Git仓库
945
- if (projectConfig.initGit) {
946
- spinner.text = "📝 初始化 Git 仓库...";
947
- await initializeGitRepository(projectPath);
948
- }
949
-
950
- // 6. 安装依赖
951
- if (projectConfig.installDeps) {
952
- spinner.text = `📦 使用 ${projectConfig.packageManager} 安装依赖...`;
953
- await installDependencies(
954
- projectPath,
955
- spinner,
956
- projectConfig.packageManager
957
- );
958
- }
959
-
960
- // 7. 清理临时文件
961
- if (tempTemplatePath) {
962
- spinner.text = "🧹 清理临时文件...";
963
- await fs.remove(tempTemplatePath).catch(() => {});
964
- }
965
-
966
- // 8. 创建成功
967
- spinner.succeed(chalk.green("🎉 项目创建成功!"));
968
-
969
- console.log();
970
- console.log(chalk.green("🎉 项目创建完成!"));
971
- console.log();
972
- console.log(chalk.blue("📁 项目信息:"));
973
- console.log(` 位置: ${chalk.cyan(projectPath)}`);
974
- console.log(` 模板: ${chalk.cyan(template.name)}`);
975
- console.log(
976
- ` Git仓库: ${
977
- projectConfig.initGit ? chalk.green("已初始化") : chalk.dim("未初始化")
978
- }`
979
- );
980
- console.log(
981
- ` 依赖安装: ${
982
- projectConfig.installDeps
983
- ? chalk.green("已完成")
984
- : chalk.dim("需手动安装")
985
- }`
986
- );
987
- console.log();
988
- console.log(chalk.blue("🚀 快速开始:"));
989
- console.log(chalk.cyan(` cd ${projectName}`));
990
- if (!projectConfig.installDeps) {
991
- console.log(
992
- chalk.cyan(` ${projectConfig.packageManager || "npm"} install`)
993
- );
994
- }
995
-
996
- const startCommand = getStartCommand(template);
997
- if (startCommand) {
998
- console.log(chalk.cyan(` ${startCommand}`));
999
- }
1000
- console.log();
1001
-
1002
- // 显示项目统计
1003
- spinner.start("📊 统计项目信息...");
1004
- const stats = await generateProjectStats(projectPath);
1005
- spinner.stop();
1006
-
1007
- if (stats) {
1008
- printProjectStats(stats);
1009
- console.log();
1010
- }
1011
- } catch (error) {
1012
- // 清理临时文件
1013
- if (tempTemplatePath) {
1014
- await fs.remove(tempTemplatePath).catch(() => {});
1015
- }
1016
-
1017
- spinner.fail("创建项目失败");
1018
- throw error;
1019
- }
1020
- }
1021
-
1022
- /**
1023
- * 处理项目配置
1024
- */
1025
- async function processProjectConfig(
1026
- projectPath,
1027
- projectName,
1028
- template,
1029
- projectConfig
1030
- ) {
1031
- // 更新 package.json
1032
- const packageJsonPath = path.join(projectPath, "package.json");
1033
- if (fs.existsSync(packageJsonPath)) {
1034
- const packageJson = await fs.readJson(packageJsonPath);
1035
- packageJson.name = projectName;
1036
-
1037
- if (projectConfig.description) {
1038
- packageJson.description = projectConfig.description;
1039
- } else {
1040
- packageJson.description = `基于 ${template.name} 创建的项目`;
1041
- }
1042
-
1043
- if (projectConfig.author) {
1044
- packageJson.author = projectConfig.author;
1045
- }
1046
-
1047
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
1048
- }
1049
-
1050
- // 处理 README.md
1051
- const readmePath = path.join(projectPath, "README.md");
1052
- if (fs.existsSync(readmePath)) {
1053
- let readme = await fs.readFile(readmePath, "utf8");
1054
- readme = readme.replace(/# .+/, `# ${projectName}`);
1055
-
1056
- const description =
1057
- projectConfig.description || `基于 ${template.name} 创建的项目`;
1058
- readme = readme.replace(/项目描述.*/, description);
1059
-
1060
- if (projectConfig.author) {
1061
- readme += `\n\n## 作者\n\n${projectConfig.author}\n`;
1062
- }
1063
-
1064
- await fs.writeFile(readmePath, readme);
1065
- }
1066
-
1067
- // 处理 .gitignore (如果是 _gitignore)
1068
- const gitignoreSource = path.join(projectPath, "_gitignore");
1069
- const gitignoreDest = path.join(projectPath, ".gitignore");
1070
- if (fs.existsSync(gitignoreSource)) {
1071
- await fs.move(gitignoreSource, gitignoreDest);
1072
- }
1073
-
1074
- // 处理 .env.example
1075
- const envSource = path.join(projectPath, "_env.example");
1076
- const envDest = path.join(projectPath, ".env.example");
1077
- if (fs.existsSync(envSource)) {
1078
- await fs.move(envSource, envDest);
1079
- }
1080
- }
1081
-
1082
- /**
1083
- * 初始化Git仓库
1084
- */
1085
- async function initializeGitRepository(projectPath) {
1086
- const originalCwd = process.cwd();
1087
-
1088
- try {
1089
- process.chdir(projectPath);
1090
-
1091
- // 检查是否安装了git
1092
- execSync("git --version", { stdio: "ignore" });
1093
-
1094
- // 初始化git仓库
1095
- execSync("git init", { stdio: "ignore" });
1096
- execSync("git add .", { stdio: "ignore" });
1097
- execSync('git commit -m "feat: 初始化项目"', { stdio: "ignore" });
1098
- } catch (error) {
1099
- // Git不可用,跳过初始化
1100
- console.log(chalk.yellow("⚠️ Git 不可用,跳过仓库初始化"));
1101
- } finally {
1102
- process.chdir(originalCwd);
1103
- }
1104
- }
1105
-
1106
- /**
1107
- * 获取启动命令
1108
- */
1109
- function getStartCommand(template) {
1110
- if (!template.key) return "bun run dev";
1111
-
1112
- const startCommands = {
1113
- // Vue前端项目
1114
- "robot-admin": "bun run dev",
1115
- "robot-admin-base": "bun run dev",
1116
- "robot-monorepo": "bun run dev:packages",
1117
- "robot-monorepo-base": "bun run dev",
1118
- "robot-micro": "bun run dev:main",
1119
- "robot-micro-base": "bun run dev",
1120
-
1121
- // React前端项目
1122
- "robot-react": "bun run start",
1123
- "robot-react-base": "bun run start",
1124
-
1125
- // 移动端项目
1126
- "robot-uniapp": "bun run dev:h5",
1127
- "robot-uniapp-base": "bun run dev:h5",
1128
- "robot-tarao": "bun run android",
1129
- "robot-tarao-base": "bun run android",
1130
-
1131
- // 后端项目
1132
- "robot-nest": "bun run start:dev",
1133
- "robot-nest-base": "bun run start:dev",
1134
- "robot-nest-micro": "bun run start:dev",
1135
- "robot-koa": "bun run dev",
1136
- "robot-koa-base": "bun run dev",
1137
-
1138
- // 桌面端项目
1139
- "robot-electron": "bun run electron:dev",
1140
- "robot-electron-base": "bun run electron:dev",
1141
- "robot-tauri": "bun run tauri dev",
1142
- "robot-tauri-base": "bun run tauri dev",
1143
- };
1144
-
1145
- return startCommands[template.key] || "bun run dev";
1
+ // lib/create.js - 统一包管理器版本,优先 bun
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ import chalk from "chalk";
5
+ import inquirer from "inquirer";
6
+ import ora from "ora";
7
+ import { execSync } from "child_process";
8
+ import { downloadTemplate } from "./download.js";
9
+ import {
10
+ TEMPLATE_CATEGORIES,
11
+ getAllTemplates,
12
+ getTemplatesByCategory,
13
+ searchTemplates,
14
+ getRecommendedTemplates,
15
+ } from "./templates.js";
16
+ import {
17
+ validateProjectName,
18
+ copyTemplate,
19
+ installDependencies,
20
+ generateProjectStats,
21
+ printProjectStats,
22
+ detectPackageManager,
23
+ } from "./utils.js";
24
+
25
+ /**
26
+ * 创建项目主函数
27
+ */
28
+ export async function createProject(projectName, options = {}) {
29
+ console.log();
30
+ console.log(chalk.cyan("🚀 Robot CLI - 开始创建项目"));
31
+ console.log();
32
+
33
+ // 1. 选择模板
34
+ const template = await selectTemplate(options.template);
35
+
36
+ // 2. 处理项目名称
37
+ const finalProjectName = await handleProjectName(projectName, template);
38
+
39
+ // 3. 项目配置选项
40
+ const projectConfig = await configureProject(options);
41
+
42
+ // 4. 确认创建
43
+ await confirmCreation(finalProjectName, template, projectConfig);
44
+
45
+ // 5. 创建项目
46
+ await executeCreation(finalProjectName, template, projectConfig);
47
+ }
48
+
49
+ /**
50
+ * 处理项目名称
51
+ */
52
+ async function handleProjectName(projectName, template) {
53
+ if (projectName) {
54
+ // 验证项目名称
55
+ const validation = validateProjectName(projectName);
56
+ if (!validation.valid) {
57
+ console.log(chalk.red("❌ 项目名称不合法:"));
58
+ validation.errors.forEach((error) => {
59
+ console.log(chalk.red(` ${error}`));
60
+ });
61
+ console.log();
62
+
63
+ const { newName } = await inquirer.prompt([
64
+ {
65
+ type: "input",
66
+ name: "newName",
67
+ message: "请输入新的项目名称:",
68
+ validate: (input) => {
69
+ const result = validateProjectName(input);
70
+ return result.valid || result.errors[0];
71
+ },
72
+ },
73
+ ]);
74
+ return newName;
75
+ }
76
+ return projectName;
77
+ } else {
78
+ // 根据模板名称生成默认项目名称
79
+ const defaultName = generateDefaultProjectName(template);
80
+
81
+ const { name } = await inquirer.prompt([
82
+ {
83
+ type: "input",
84
+ name: "name",
85
+ message: "请输入项目名称:",
86
+ default: defaultName,
87
+ validate: (input) => {
88
+ if (!input.trim()) return "项目名称不能为空";
89
+ const result = validateProjectName(input);
90
+ return result.valid || result.errors[0];
91
+ },
92
+ },
93
+ ]);
94
+ return name;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * 根据模板生成默认项目名称
100
+ */
101
+ function generateDefaultProjectName(template) {
102
+ if (!template) return "my-project";
103
+
104
+ const templateKey = template.key || "project";
105
+ const timestamp = Date.now().toString().slice(-4);
106
+
107
+ // 移除版本后缀 (-full, -lite)
108
+ const baseName = templateKey.replace(/-(full|lite|base)$/, "");
109
+
110
+ return `my-${baseName}-${timestamp}`;
111
+ }
112
+
113
+ /**
114
+ * 选择模板 - 多种方式(带返回功能)
115
+ */
116
+ async function selectTemplate(templateOption) {
117
+ if (templateOption) {
118
+ // 命令行指定了模板
119
+ const allTemplates = getAllTemplates();
120
+ if (allTemplates[templateOption]) {
121
+ return { key: templateOption, ...allTemplates[templateOption] };
122
+ } else {
123
+ console.log(chalk.yellow(`⚠️ 模板 "${templateOption}" 不存在`));
124
+ console.log();
125
+ }
126
+ }
127
+
128
+ // 交互式选择 - 主选择方式
129
+ return await selectTemplateMethod();
130
+ }
131
+
132
+ /**
133
+ * 选择模板方式 - 优化主菜单
134
+ */
135
+ async function selectTemplateMethod() {
136
+ console.log();
137
+ console.log(chalk.blue.bold("🎯 选择模板创建方式"));
138
+ console.log(chalk.dim("请选择最适合你的模板浏览方式"));
139
+ console.log();
140
+
141
+ const { selectionMode } = await inquirer.prompt([
142
+ {
143
+ type: "list",
144
+ name: "selectionMode",
145
+ message: "模板选择方式:",
146
+ choices: [
147
+ {
148
+ name: `● ${chalk.bold('推荐模板')} ${chalk.dim('(常用模板快速选择) - 基于团队使用频率推荐的热门模板')}`,
149
+ value: "recommended"
150
+ },
151
+ {
152
+ name: chalk.dim("".repeat(70)),
153
+ value: "sep1",
154
+ disabled: true
155
+ },
156
+ {
157
+ name: `● ${chalk.bold('分类模板')} ${chalk.dim('(按项目类型分类选择) - 前端、后端、移动端、桌面端分类浏览')}`,
158
+ value: "category"
159
+ },
160
+ {
161
+ name: chalk.dim("".repeat(70)),
162
+ value: "sep2",
163
+ disabled: true
164
+ },
165
+ {
166
+ name: `● ${chalk.bold('搜索模板')} ${chalk.dim('(关键词搜索) - 通过技术栈、功能特性等关键词快速查找')}`,
167
+ value: "search"
168
+ },
169
+ {
170
+ name: chalk.dim("".repeat(70)),
171
+ value: "sep3",
172
+ disabled: true
173
+ },
174
+ {
175
+ name: `● ${chalk.bold('全部模板')} ${chalk.dim('(查看所有可用模板) - 按分类展示所有可用的项目模板')}`,
176
+ value: "all"
177
+ },
178
+ ],
179
+ pageSize: 10
180
+ },
181
+ ]);
182
+
183
+ switch (selectionMode) {
184
+ case "recommended":
185
+ return await selectFromRecommended();
186
+ case "category":
187
+ return await selectByCategory();
188
+ case "search":
189
+ return await selectBySearch();
190
+ case "all":
191
+ return await selectFromAll();
192
+ default:
193
+ return await selectByCategory();
194
+ }
195
+ }
196
+
197
+ /**
198
+ * 从推荐模板中选择 - 优化后的展示
199
+ */
200
+ async function selectFromRecommended() {
201
+ const recommended = getRecommendedTemplates();
202
+
203
+ if (Object.keys(recommended).length === 0) {
204
+ console.log(chalk.yellow("⚠️ 暂无推荐模板"));
205
+ return await selectTemplateMethod();
206
+ }
207
+
208
+ console.log();
209
+ console.log(chalk.blue.bold("🎯 推荐模板"));
210
+ console.log(chalk.dim("基于团队使用频率和项目成熟度推荐"));
211
+ console.log();
212
+
213
+ // 创建更美观的选择项
214
+ const choices = Object.entries(recommended).map(([key, template]) => {
215
+ // 构建特性标签
216
+ const featureTags = template.features.slice(0, 3).map(f =>
217
+ chalk.dim(`[${f}]`)
218
+ ).join(' ');
219
+
220
+ // 版本标签 - 只保留方括号内的版本
221
+ const versionTag = template.version === 'full' ?
222
+ chalk.green('[完整版]') :
223
+ chalk.yellow('[精简版]');
224
+
225
+ return {
226
+ name: `${chalk.bold.white(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
227
+ ${chalk.dim(featureTags)}${template.features.length > 3 ? chalk.dim(` +${template.features.length - 3}more`) : ''}`,
228
+ value: { key, ...template },
229
+ short: template.name,
230
+ };
231
+ });
232
+
233
+ // 为每个推荐模板添加分隔符
234
+ const choicesWithSeparators = [];
235
+ choices.forEach((choice, index) => {
236
+ choicesWithSeparators.push(choice);
237
+ if (index < choices.length - 1) {
238
+ choicesWithSeparators.push({
239
+ name: chalk.dim("─".repeat(70)),
240
+ value: `sep_${index}`,
241
+ disabled: true
242
+ });
243
+ }
244
+ });
245
+
246
+ choicesWithSeparators.push({
247
+ name: chalk.dim("⬅️ 返回选择其他方式"),
248
+ value: "back",
249
+ });
250
+
251
+ const { selectedTemplate } = await inquirer.prompt([
252
+ {
253
+ type: "list",
254
+ name: "selectedTemplate",
255
+ message: "选择推荐模板:",
256
+ choices: choicesWithSeparators,
257
+ pageSize: 15,
258
+ loop: false
259
+ },
260
+ ]);
261
+
262
+ if (selectedTemplate === "back") {
263
+ return await selectTemplateMethod();
264
+ }
265
+
266
+ return selectedTemplate;
267
+ }
268
+
269
+ /**
270
+ * 按分类选择模板(完整的返回功能)
271
+ */
272
+ async function selectByCategory() {
273
+ // 1. 选择项目类型
274
+ while (true) {
275
+ const categoryResult = await selectCategory();
276
+ if (categoryResult === "back_to_method") {
277
+ return await selectTemplateMethod();
278
+ }
279
+
280
+ // 2. 选择技术栈
281
+ const stackResult = await selectStack(categoryResult);
282
+ if (stackResult === "back_to_category") {
283
+ continue; // 返回到项目类型选择
284
+ }
285
+ if (stackResult === "back_to_method") {
286
+ return await selectTemplateMethod();
287
+ }
288
+
289
+ // 3. 选择架构模式
290
+ const patternResult = await selectPattern(categoryResult, stackResult);
291
+ if (patternResult === "back_to_stack") {
292
+ continue; // 返回到技术栈选择,会重新开始while循环
293
+ }
294
+ if (patternResult === "back_to_category") {
295
+ continue; // 返回到项目类型选择
296
+ }
297
+ if (patternResult === "back_to_method") {
298
+ return await selectTemplateMethod();
299
+ }
300
+
301
+ // 4. 选择具体模板
302
+ const templateResult = await selectSpecificTemplate(
303
+ categoryResult,
304
+ stackResult,
305
+ patternResult
306
+ );
307
+ if (templateResult === "back_to_pattern") {
308
+ continue; // 返回到架构模式选择
309
+ }
310
+ if (templateResult === "back_to_stack") {
311
+ continue; // 返回到技术栈选择
312
+ }
313
+ if (templateResult === "back_to_category") {
314
+ continue; // 返回到项目类型选择
315
+ }
316
+ if (templateResult === "back_to_method") {
317
+ return await selectTemplateMethod();
318
+ }
319
+
320
+ // 成功选择了模板
321
+ return templateResult;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * 选择项目类型
327
+ */
328
+ async function selectCategory() {
329
+ const categoryChoices = Object.entries(TEMPLATE_CATEGORIES).map(
330
+ ([key, category]) => ({
331
+ name: category.name,
332
+ value: key,
333
+ })
334
+ );
335
+
336
+ categoryChoices.push({
337
+ name: chalk.dim("← 返回模板选择方式"),
338
+ value: "back_to_method",
339
+ });
340
+
341
+ const { categoryKey } = await inquirer.prompt([
342
+ {
343
+ type: "list",
344
+ name: "categoryKey",
345
+ message: "请选择项目类型:",
346
+ choices: categoryChoices,
347
+ },
348
+ ]);
349
+
350
+ return categoryKey;
351
+ }
352
+
353
+ /**
354
+ * 选择技术栈
355
+ */
356
+ async function selectStack(categoryKey) {
357
+ if (categoryKey === "back_to_method") return categoryKey;
358
+
359
+ const category = TEMPLATE_CATEGORIES[categoryKey];
360
+ const stackChoices = Object.entries(category.stacks).map(([key, stack]) => ({
361
+ name: stack.name,
362
+ value: key,
363
+ }));
364
+
365
+ stackChoices.push(
366
+ {
367
+ name: chalk.dim("─────────────────────"),
368
+ value: "separator",
369
+ disabled: true,
370
+ },
371
+ { name: chalk.dim("← 返回项目类型选择"), value: "back_to_category" },
372
+ { name: chalk.dim("← 返回模板选择方式"), value: "back_to_method" }
373
+ );
374
+
375
+ if (stackChoices.length === 3) {
376
+ // 只有一个技术栈 + 分隔线 + 返回选项
377
+ return stackChoices[0].value;
378
+ }
379
+
380
+ const { stackKey } = await inquirer.prompt([
381
+ {
382
+ type: "list",
383
+ name: "stackKey",
384
+ message: "请选择技术栈:",
385
+ choices: stackChoices,
386
+ },
387
+ ]);
388
+
389
+ return stackKey;
390
+ }
391
+
392
+ /**
393
+ * 选择架构模式
394
+ */
395
+ async function selectPattern(categoryKey, stackKey) {
396
+ if (["back_to_category", "back_to_method"].includes(stackKey))
397
+ return stackKey;
398
+
399
+ const category = TEMPLATE_CATEGORIES[categoryKey];
400
+ const stack = category.stacks[stackKey];
401
+ const patternChoices = Object.entries(stack.patterns).map(
402
+ ([key, pattern]) => ({
403
+ name: pattern.name,
404
+ value: key,
405
+ })
406
+ );
407
+
408
+ patternChoices.push(
409
+ {
410
+ name: chalk.dim("─────────────────────"),
411
+ value: "separator",
412
+ disabled: true,
413
+ },
414
+ { name: chalk.dim("← 返回技术栈选择"), value: "back_to_stack" },
415
+ { name: chalk.dim("← 返回项目类型选择"), value: "back_to_category" },
416
+ { name: chalk.dim("← 返回模板选择方式"), value: "back_to_method" }
417
+ );
418
+
419
+ if (patternChoices.length === 4) {
420
+ // 只有一个模式 + 分隔线 + 返回选项
421
+ return patternChoices[0].value;
422
+ }
423
+
424
+ const { patternKey } = await inquirer.prompt([
425
+ {
426
+ type: "list",
427
+ name: "patternKey",
428
+ message: "请选择架构模式:",
429
+ choices: patternChoices,
430
+ },
431
+ ]);
432
+
433
+ return patternKey;
434
+ }
435
+
436
+ /**
437
+ * 选择具体模板
438
+ */
439
+ async function selectSpecificTemplate(categoryKey, stackKey, patternKey) {
440
+ if (
441
+ ["back_to_stack", "back_to_category", "back_to_method"].includes(patternKey)
442
+ ) {
443
+ return patternKey;
444
+ }
445
+
446
+ const templates = getTemplatesByCategory(categoryKey, stackKey, patternKey);
447
+ const templateChoices = Object.entries(templates).map(([key, template]) => ({
448
+ name: `${template.name} - ${chalk.dim(template.description)}`,
449
+ value: { key, ...template },
450
+ short: template.name,
451
+ }));
452
+
453
+ templateChoices.push(
454
+ {
455
+ name: chalk.dim("─────────────────────"),
456
+ value: "separator",
457
+ disabled: true,
458
+ },
459
+ { name: chalk.dim("← 返回架构模式选择"), value: "back_to_pattern" },
460
+ { name: chalk.dim("← 返回技术栈选择"), value: "back_to_stack" },
461
+ { name: chalk.dim("← 返回项目类型选择"), value: "back_to_category" },
462
+ { name: chalk.dim("← 返回模板选择方式"), value: "back_to_method" }
463
+ );
464
+
465
+ const { selectedTemplate } = await inquirer.prompt([
466
+ {
467
+ type: "list",
468
+ name: "selectedTemplate",
469
+ message: "请选择模板版本:",
470
+ choices: templateChoices,
471
+ },
472
+ ]);
473
+
474
+ return selectedTemplate;
475
+ }
476
+
477
+ /**
478
+ * 搜索选择模板 - 优化结果展示
479
+ */
480
+ async function selectBySearch() {
481
+ while (true) {
482
+ const { keyword } = await inquirer.prompt([
483
+ {
484
+ type: "input",
485
+ name: "keyword",
486
+ message: "请输入搜索关键词 (名称、描述、技术栈):",
487
+ validate: (input) => (input.trim() ? true : "关键词不能为空"),
488
+ },
489
+ ]);
490
+
491
+ const results = searchTemplates(keyword);
492
+
493
+ if (Object.keys(results).length === 0) {
494
+ console.log();
495
+ console.log(chalk.yellow("🔍 没有找到匹配的模板"));
496
+ console.log(chalk.dim(`搜索关键词: "${keyword}"`));
497
+ console.log();
498
+
499
+ const { action } = await inquirer.prompt([
500
+ {
501
+ type: "list",
502
+ name: "action",
503
+ message: "请选择下一步操作:",
504
+ choices: [
505
+ { name: "🔍 重新搜索", value: "retry" },
506
+ { name: "⬅️ 返回模板选择方式", value: "back" },
507
+ ],
508
+ },
509
+ ]);
510
+
511
+ if (action === "retry") {
512
+ continue;
513
+ } else {
514
+ return await selectTemplateMethod();
515
+ }
516
+ }
517
+
518
+ console.log();
519
+ console.log(chalk.green.bold(`🔍 搜索结果`));
520
+ console.log(chalk.dim(`关键词: "${keyword}" • 找到 ${Object.keys(results).length} 个匹配模板`));
521
+ console.log();
522
+
523
+ const choices = Object.entries(results).map(([key, template]) => {
524
+ // 高亮匹配的关键词
525
+ const highlightText = (text) => {
526
+ const regex = new RegExp(`(${keyword})`, 'gi');
527
+ return text.replace(regex, chalk.bgYellow.black('$1'));
528
+ };
529
+
530
+ const techInfo = template.features.slice(0, 2).join(' ');
531
+ const versionTag = template.version === 'full' ?
532
+ chalk.green('[完整版]') : chalk.yellow('[精简版]');
533
+
534
+ return {
535
+ name: `${chalk.bold(highlightText(template.name.replace(/\s*(完整版|精简版)\s*$/, '')))} ${versionTag}
536
+ ${chalk.dim(highlightText(template.description))}
537
+ ${chalk.dim(`${techInfo} • 模板key: ${key}`)}
538
+ ${chalk.dim('─'.repeat(60))}`,
539
+ value: { key, ...template },
540
+ short: template.name,
541
+ };
542
+ });
543
+
544
+ choices.push(
545
+ {
546
+ name: chalk.dim("".repeat(70)),
547
+ value: "separator",
548
+ disabled: true,
549
+ },
550
+ { name: "🔍 重新搜索", value: "search_again" },
551
+ { name: "⬅️ 返回模板选择方式", value: "back_to_mode" }
552
+ );
553
+
554
+ const { selectedTemplate } = await inquirer.prompt([
555
+ {
556
+ type: "list",
557
+ name: "selectedTemplate",
558
+ message: "选择模板:",
559
+ choices,
560
+ pageSize: 15,
561
+ loop: false
562
+ },
563
+ ]);
564
+
565
+ if (selectedTemplate === "search_again") {
566
+ continue;
567
+ } else if (selectedTemplate === "back_to_mode") {
568
+ return await selectTemplateMethod();
569
+ } else {
570
+ return selectedTemplate;
571
+ }
572
+ }
573
+ }
574
+
575
+ /**
576
+ * 从全部模板中选择 - 优化后的展示
577
+ */
578
+ async function selectFromAll() {
579
+ const allTemplates = getAllTemplates();
580
+
581
+ console.log();
582
+ console.log(chalk.blue.bold(`📋 所有可用模板`));
583
+ console.log(chalk.dim(`共 ${Object.keys(allTemplates).length} 个模板可选`));
584
+ console.log();
585
+
586
+ // 按分类组织模板
587
+ const categorizedChoices = [];
588
+
589
+ // 前端模板
590
+ categorizedChoices.push({
591
+ name: chalk.yellow.bold("🎨 前端项目"),
592
+ value: "frontend_header",
593
+ disabled: true
594
+ });
595
+
596
+ Object.entries(allTemplates)
597
+ .filter(([key]) => key.includes('admin') || key.includes('react') || key.includes('micro'))
598
+ .forEach(([key, template]) => {
599
+ const techStack = key.includes('admin') ? 'Vue3' :
600
+ key.includes('react') ? 'React' : 'Vue3';
601
+ const versionTag = template.version === 'full' ?
602
+ chalk.green('[完整版]') : chalk.yellow('[精简版]');
603
+
604
+ categorizedChoices.push({
605
+ name: ` ● ${chalk.bold(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
606
+ ${chalk.dim(`技术栈: ${techStack} 命令: robot create my-app -t ${key}`)}`,
607
+ value: { key, ...template },
608
+ short: template.name
609
+ });
610
+
611
+ // 添加分隔符
612
+ categorizedChoices.push({
613
+ name: chalk.dim(" " + "─".repeat(66)),
614
+ value: `sep_${key}`,
615
+ disabled: true
616
+ });
617
+ });
618
+
619
+ // 移动端模板
620
+ categorizedChoices.push({
621
+ name: chalk.yellow.bold("📱 移动端项目"),
622
+ value: "mobile_header",
623
+ disabled: true
624
+ });
625
+
626
+ Object.entries(allTemplates)
627
+ .filter(([key]) => key.includes('uniapp') || key.includes('tarao'))
628
+ .forEach(([key, template]) => {
629
+ const versionTag = template.version === 'full' ?
630
+ chalk.green('[完整版]') : chalk.yellow('[精简版]');
631
+
632
+ categorizedChoices.push({
633
+ name: ` ● ${chalk.bold(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
634
+ ${chalk.dim(`跨平台: 小程序/H5/App 命令: robot create my-app -t ${key}`)}`,
635
+ value: { key, ...template },
636
+ short: template.name
637
+ });
638
+
639
+ // 添加分隔符
640
+ categorizedChoices.push({
641
+ name: chalk.dim(" " + "─".repeat(66)),
642
+ value: `sep_${key}`,
643
+ disabled: true
644
+ });
645
+ });
646
+
647
+ // 后端模板
648
+ categorizedChoices.push({
649
+ name: chalk.yellow.bold("🚀 后端项目"),
650
+ value: "backend_header",
651
+ disabled: true
652
+ });
653
+
654
+ Object.entries(allTemplates)
655
+ .filter(([key]) => key.includes('nest') || key.includes('koa'))
656
+ .forEach(([key, template]) => {
657
+ const framework = key.includes('nest') ? 'NestJS' : 'Koa3';
658
+ const versionTag = template.version === 'full' ?
659
+ chalk.green('[完整版]') : chalk.yellow('[精简版]');
660
+
661
+ categorizedChoices.push({
662
+ name: ` ● ${chalk.bold(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
663
+ ${chalk.dim(`框架: ${framework} 命令: robot create my-app -t ${key}`)}`,
664
+ value: { key, ...template },
665
+ short: template.name
666
+ });
667
+
668
+ // 添加分隔符
669
+ categorizedChoices.push({
670
+ name: chalk.dim(" " + "─".repeat(66)),
671
+ value: `sep_${key}`,
672
+ disabled: true
673
+ });
674
+ });
675
+
676
+ // 桌面端模板
677
+ categorizedChoices.push({
678
+ name: chalk.yellow.bold("💻 桌面端项目"),
679
+ value: "desktop_header",
680
+ disabled: true
681
+ });
682
+
683
+ Object.entries(allTemplates)
684
+ .filter(([key]) => key.includes('electron') || key.includes('tauri'))
685
+ .forEach(([key, template]) => {
686
+ const framework = key.includes('electron') ? 'Electron' : 'Tauri';
687
+ const versionTag = template.version === 'full' ?
688
+ chalk.green('[完整版]') : chalk.yellow('[精简版]');
689
+
690
+ categorizedChoices.push({
691
+ name: ` ● ${chalk.bold(template.name.replace(/\s*(完整版|精简版)\s*$/, ''))} ${versionTag} - ${chalk.dim(template.description)}
692
+ ${chalk.dim(`框架: ${framework} 命令: robot create my-app -t ${key}`)}`,
693
+ value: { key, ...template },
694
+ short: template.name
695
+ });
696
+
697
+ // 添加分隔符
698
+ categorizedChoices.push({
699
+ name: chalk.dim(" " + "─".repeat(66)),
700
+ value: `sep_${key}`,
701
+ disabled: true
702
+ });
703
+ });
704
+
705
+ // 添加返回选项
706
+ categorizedChoices.push(
707
+ {
708
+ name: chalk.dim("".repeat(70)),
709
+ value: "separator",
710
+ disabled: true,
711
+ },
712
+ {
713
+ name: chalk.dim("⬅️ 返回模板选择方式"),
714
+ value: "back_to_mode"
715
+ }
716
+ );
717
+
718
+ const { selectedTemplate } = await inquirer.prompt([
719
+ {
720
+ type: "list",
721
+ name: "selectedTemplate",
722
+ message: "选择模板:",
723
+ choices: categorizedChoices,
724
+ pageSize: 25,
725
+ loop: false
726
+ },
727
+ ]);
728
+
729
+ if (selectedTemplate === "back_to_mode") {
730
+ return await selectTemplateMethod();
731
+ }
732
+
733
+ return selectedTemplate;
734
+ }
735
+
736
+ /**
737
+ * 项目配置选项 - 智能检测并优先推荐 bun
738
+ */
739
+ async function configureProject(options) {
740
+ console.log();
741
+ console.log(chalk.blue("⚙️ 项目配置"));
742
+ console.log();
743
+
744
+ // 检测可用的包管理器
745
+ const availableManagers = detectPackageManager();
746
+ const hasBun = availableManagers.includes('bun');
747
+ const hasPnpm = availableManagers.includes('pnpm');
748
+ const hasYarn = availableManagers.includes('yarn');
749
+ const hasNpm = availableManagers.includes('npm');
750
+
751
+ // 构建包管理器选择列表
752
+ const managerChoices = [];
753
+
754
+ if (hasBun) {
755
+ managerChoices.push({
756
+ name: "bun (推荐 - 极速安装,现代化,性能最佳)",
757
+ value: "bun"
758
+ });
759
+ }
760
+
761
+ if (hasPnpm) {
762
+ managerChoices.push({
763
+ name: "pnpm (推荐 - 快速安装,节省磁盘空间)",
764
+ value: "pnpm"
765
+ });
766
+ }
767
+
768
+ if (hasYarn) {
769
+ managerChoices.push({
770
+ name: "yarn (兼容性好 - 适用于现有yarn项目)",
771
+ value: "yarn"
772
+ });
773
+ }
774
+
775
+ if (hasNpm) {
776
+ managerChoices.push({
777
+ name: "npm (默认 - Node.js内置,兼容性最好)",
778
+ value: "npm"
779
+ });
780
+ }
781
+
782
+ // 如果没有检测到任何包管理器,使用默认列表
783
+ if (managerChoices.length === 0) {
784
+ managerChoices.push(
785
+ { name: "npm (默认)", value: "npm" },
786
+ { name: "bun (如已安装)", value: "bun" },
787
+ { name: "pnpm (如已安装)", value: "pnpm" },
788
+ { name: "yarn (如已安装)", value: "yarn" }
789
+ );
790
+ }
791
+
792
+ const config = await inquirer.prompt([
793
+ {
794
+ type: "confirm",
795
+ name: "initGit",
796
+ message: "是否初始化 Git 仓库?",
797
+ default: true,
798
+ },
799
+ {
800
+ type: "confirm",
801
+ name: "installDeps",
802
+ message: "是否立即安装依赖?",
803
+ default: !options.skipInstall,
804
+ },
805
+ {
806
+ type: "list",
807
+ name: "packageManager",
808
+ message: "选择包管理器:",
809
+ choices: managerChoices,
810
+ default: hasBun ? "bun" : (hasPnpm ? "pnpm" : "npm"),
811
+ when: (answers) => answers.installDeps,
812
+ },
813
+ {
814
+ type: "input",
815
+ name: "description",
816
+ message: "项目描述 (可选):",
817
+ default: "",
818
+ },
819
+ {
820
+ type: "input",
821
+ name: "author",
822
+ message: "作者 (可选):",
823
+ default: "CHENY",
824
+ },
825
+ {
826
+ type: "confirm",
827
+ name: "confirmConfig",
828
+ message: "确认以上配置?",
829
+ default: true,
830
+ },
831
+ ]);
832
+
833
+ if (!config.confirmConfig) {
834
+ const { action } = await inquirer.prompt([
835
+ {
836
+ type: "list",
837
+ name: "action",
838
+ message: "请选择操作:",
839
+ choices: [
840
+ { name: "🔄 重新配置", value: "reconfigure" },
841
+ { name: "❌ 取消创建", value: "cancel" },
842
+ ],
843
+ },
844
+ ]);
845
+
846
+ if (action === "reconfigure") {
847
+ return await configureProject(options);
848
+ } else {
849
+ console.log(chalk.yellow("❌ 取消创建项目"));
850
+ process.exit(0);
851
+ }
852
+ }
853
+
854
+ return config;
855
+ }
856
+
857
+ /**
858
+ * 确认创建 - 统一显示包管理器
859
+ */
860
+ async function confirmCreation(projectName, template, projectConfig) {
861
+ console.log();
862
+ console.log(chalk.blue("📋 项目创建信息确认:"));
863
+ console.log();
864
+ console.log(` 项目名称: ${chalk.cyan(projectName)}`);
865
+ console.log(` 选择模板: ${chalk.cyan(template.name)}`);
866
+ console.log(` 模板描述: ${chalk.dim(template.description)}`);
867
+ console.log(` 包含功能: ${chalk.dim(template.features.join(", "))}`);
868
+
869
+ if (projectConfig.description) {
870
+ console.log(` 项目描述: ${chalk.dim(projectConfig.description)}`);
871
+ }
872
+
873
+ if (projectConfig.author) {
874
+ console.log(` 作  者: ${chalk.dim(projectConfig.author)}`);
875
+ }
876
+
877
+ console.log(
878
+ ` 初始化Git: ${
879
+ projectConfig.initGit ? chalk.green("是") : chalk.dim("否")
880
+ }`
881
+ );
882
+ console.log(
883
+ ` 安装依赖: ${
884
+ projectConfig.installDeps
885
+ ? chalk.green("是") + chalk.dim(` (${projectConfig.packageManager})`)
886
+ : chalk.dim("否")
887
+ }`
888
+ );
889
+ console.log(` 源码仓库: ${chalk.dim(template.repoUrl)}`);
890
+ console.log();
891
+
892
+ const { confirmed } = await inquirer.prompt([
893
+ {
894
+ type: "confirm",
895
+ name: "confirmed",
896
+ message: "确认创建项目?",
897
+ default: true,
898
+ },
899
+ ]);
900
+
901
+ if (!confirmed) {
902
+ console.log(chalk.yellow("❌ 取消创建"));
903
+ process.exit(0);
904
+ }
905
+ }
906
+
907
+ /**
908
+ * 执行创建流程 - 统一包管理器使用
909
+ */
910
+ async function executeCreation(projectName, template, projectConfig) {
911
+ if (!projectName || typeof projectName !== "string") {
912
+ throw new Error(`项目名称无效: ${projectName}`);
913
+ }
914
+
915
+ if (!template || !template.name) {
916
+ throw new Error(`模板数据无效: ${JSON.stringify(template)}`);
917
+ }
918
+
919
+ const spinner = ora({
920
+ text: "🚀 准备创建项目...",
921
+ spinner: 'dots',
922
+ color: 'cyan'
923
+ }).start();
924
+
925
+ let tempTemplatePath; // 用于清理临时文件
926
+
927
+ try {
928
+ // 1. 检查目录是否存在
929
+ spinner.text = "📁 检查项目目录...";
930
+ const projectPath = path.resolve(projectName);
931
+ if (fs.existsSync(projectPath)) {
932
+ spinner.stop();
933
+ console.log(chalk.yellow("⚠️ 项目目录已存在"));
934
+
935
+ const { overwrite } = await inquirer.prompt([
936
+ {
937
+ type: "confirm",
938
+ name: "overwrite",
939
+ message: "目录已存在,是否覆盖?",
940
+ default: false,
941
+ },
942
+ ]);
943
+
944
+ if (!overwrite) {
945
+ console.log(chalk.yellow("❌ 取消创建"));
946
+ process.exit(0);
947
+ }
948
+
949
+ spinner.start("🗑️ 清理现有目录...");
950
+ await fs.remove(projectPath);
951
+ spinner.text = "📁 准备创建新目录...";
952
+ }
953
+
954
+ // 2. 下载最新模板
955
+ spinner.text = "🌐 下载最新模板...";
956
+ try {
957
+ tempTemplatePath = await downloadTemplate(template, { spinner });
958
+
959
+ if (!tempTemplatePath || !fs.existsSync(tempTemplatePath)) {
960
+ throw new Error(`模板路径无效: ${tempTemplatePath}`);
961
+ }
962
+ } catch (error) {
963
+ spinner.fail("模板下载失败");
964
+ console.log();
965
+ console.log(chalk.red("❌ 模板下载错误:"));
966
+ console.log(chalk.dim(` ${error.message}`));
967
+ console.log();
968
+ console.log(chalk.blue("💡 可能的解决方案:"));
969
+ console.log(chalk.dim(" 1. 检查网络连接"));
970
+ console.log(chalk.dim(" 2. 重试命令"));
971
+ console.log(chalk.dim(" 3. 检查仓库地址是否正确"));
972
+ console.log();
973
+ throw error;
974
+ }
975
+
976
+ // 3. 复制模板文件 - 带详细进度
977
+ await copyTemplate(tempTemplatePath, projectPath, spinner);
978
+
979
+ // 4. 处理项目配置
980
+ spinner.text = "⚙️ 处理项目配置...";
981
+ await processProjectConfig(
982
+ projectPath,
983
+ projectName,
984
+ template,
985
+ projectConfig
986
+ );
987
+
988
+ // 5. 初始化Git仓库
989
+ if (projectConfig.initGit) {
990
+ spinner.text = "📝 初始化 Git 仓库...";
991
+ await initializeGitRepository(projectPath);
992
+ }
993
+
994
+ // 6. 安装依赖
995
+ if (projectConfig.installDeps) {
996
+ spinner.text = `📦 使用 ${projectConfig.packageManager} 安装依赖...`;
997
+ await installDependencies(
998
+ projectPath,
999
+ spinner,
1000
+ projectConfig.packageManager
1001
+ );
1002
+ }
1003
+
1004
+ // 7. 清理临时文件
1005
+ if (tempTemplatePath) {
1006
+ spinner.text = "🧹 清理临时文件...";
1007
+ await fs.remove(tempTemplatePath).catch(() => {});
1008
+ }
1009
+
1010
+ // 8. 创建成功
1011
+ spinner.succeed(chalk.green("🎉 项目创建成功!"));
1012
+
1013
+ console.log();
1014
+ console.log(chalk.green("🎉 项目创建完成!"));
1015
+ console.log();
1016
+ console.log(chalk.blue("📁 项目信息:"));
1017
+ console.log(` 位置: ${chalk.cyan(projectPath)}`);
1018
+ console.log(` 模板: ${chalk.cyan(template.name)}`);
1019
+ console.log(
1020
+ ` Git仓库: ${
1021
+ projectConfig.initGit ? chalk.green("已初始化") : chalk.dim("未初始化")
1022
+ }`
1023
+ );
1024
+ console.log(
1025
+ ` 依赖安装: ${
1026
+ projectConfig.installDeps
1027
+ ? chalk.green("已完成")
1028
+ : chalk.dim("需手动安装")
1029
+ }`
1030
+ );
1031
+ console.log();
1032
+ console.log(chalk.blue("🚀 快速开始:"));
1033
+ console.log(chalk.cyan(` cd ${projectName}`));
1034
+
1035
+ // 🎯 修复:统一使用选择的包管理器
1036
+ if (!projectConfig.installDeps) {
1037
+ const packageManager = projectConfig.packageManager || "bun";
1038
+ console.log(chalk.cyan(` ${packageManager} install`));
1039
+ }
1040
+
1041
+ const startCommand = getStartCommand(template, projectConfig.packageManager);
1042
+ if (startCommand) {
1043
+ console.log(chalk.cyan(` ${startCommand}`));
1044
+ }
1045
+
1046
+ // 添加替代方案提示
1047
+ const selectedManager = projectConfig.packageManager || "bun";
1048
+ if (selectedManager === "bun") {
1049
+ console.log(chalk.dim(` ${chalk.gray("# 或使用 npm: npm install && npm run dev")}`));
1050
+ } else if (selectedManager === "npm") {
1051
+ console.log(chalk.dim(` ${chalk.gray("# 或使用 bun: bun install && bun run dev (如已安装)")}`));
1052
+ }
1053
+
1054
+ console.log();
1055
+
1056
+ // 显示项目统计
1057
+ spinner.start("📊 统计项目信息...");
1058
+ const stats = await generateProjectStats(projectPath);
1059
+ spinner.stop();
1060
+
1061
+ if (stats) {
1062
+ printProjectStats(stats);
1063
+ console.log();
1064
+ }
1065
+ } catch (error) {
1066
+ // 清理临时文件
1067
+ if (tempTemplatePath) {
1068
+ await fs.remove(tempTemplatePath).catch(() => {});
1069
+ }
1070
+
1071
+ spinner.fail("创建项目失败");
1072
+ throw error;
1073
+ }
1074
+ }
1075
+
1076
+ /**
1077
+ * 处理项目配置
1078
+ */
1079
+ async function processProjectConfig(
1080
+ projectPath,
1081
+ projectName,
1082
+ template,
1083
+ projectConfig
1084
+ ) {
1085
+ // 更新 package.json
1086
+ const packageJsonPath = path.join(projectPath, "package.json");
1087
+ if (fs.existsSync(packageJsonPath)) {
1088
+ const packageJson = await fs.readJson(packageJsonPath);
1089
+ packageJson.name = projectName;
1090
+
1091
+ if (projectConfig.description) {
1092
+ packageJson.description = projectConfig.description;
1093
+ } else {
1094
+ packageJson.description = `基于 ${template.name} 创建的项目`;
1095
+ }
1096
+
1097
+ if (projectConfig.author) {
1098
+ packageJson.author = projectConfig.author;
1099
+ }
1100
+
1101
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
1102
+ }
1103
+
1104
+ // 处理 README.md
1105
+ const readmePath = path.join(projectPath, "README.md");
1106
+ if (fs.existsSync(readmePath)) {
1107
+ let readme = await fs.readFile(readmePath, "utf8");
1108
+ readme = readme.replace(/# .+/, `# ${projectName}`);
1109
+
1110
+ const description =
1111
+ projectConfig.description || `基于 ${template.name} 创建的项目`;
1112
+ readme = readme.replace(/项目描述.*/, description);
1113
+
1114
+ if (projectConfig.author) {
1115
+ readme += `\n\n## 作者\n\n${projectConfig.author}\n`;
1116
+ }
1117
+
1118
+ await fs.writeFile(readmePath, readme);
1119
+ }
1120
+
1121
+ // 处理 .gitignore (如果是 _gitignore)
1122
+ const gitignoreSource = path.join(projectPath, "_gitignore");
1123
+ const gitignoreDest = path.join(projectPath, ".gitignore");
1124
+ if (fs.existsSync(gitignoreSource)) {
1125
+ await fs.move(gitignoreSource, gitignoreDest);
1126
+ }
1127
+
1128
+ // 处理 .env.example
1129
+ const envSource = path.join(projectPath, "_env.example");
1130
+ const envDest = path.join(projectPath, ".env.example");
1131
+ if (fs.existsSync(envSource)) {
1132
+ await fs.move(envSource, envDest);
1133
+ }
1134
+ }
1135
+
1136
+ /**
1137
+ * 初始化Git仓库
1138
+ */
1139
+ async function initializeGitRepository(projectPath) {
1140
+ const originalCwd = process.cwd();
1141
+
1142
+ try {
1143
+ process.chdir(projectPath);
1144
+
1145
+ // 检查是否安装了git
1146
+ execSync("git --version", { stdio: "ignore" });
1147
+
1148
+ // 初始化git仓库
1149
+ execSync("git init", { stdio: "ignore" });
1150
+ execSync("git add .", { stdio: "ignore" });
1151
+ execSync('git commit -m "feat: 初始化项目"', { stdio: "ignore" });
1152
+ } catch (error) {
1153
+ // Git不可用,跳过初始化
1154
+ console.log(chalk.yellow("⚠️ Git 不可用,跳过仓库初始化"));
1155
+ } finally {
1156
+ process.chdir(originalCwd);
1157
+ }
1158
+ }
1159
+
1160
+ /**
1161
+ * 获取启动命令 - 🎯 修复:统一使用包管理器
1162
+ */
1163
+ function getStartCommand(template, packageManager = "bun") {
1164
+ if (!template.key) return `${packageManager} run dev`;
1165
+
1166
+ const startCommands = {
1167
+ // Vue前端项目
1168
+ "robot-admin": "dev",
1169
+ "robot-admin-base": "dev",
1170
+ "robot-monorepo": "dev:packages",
1171
+ "robot-monorepo-base": "dev",
1172
+ "robot-micro": "dev:main",
1173
+ "robot-micro-base": "dev",
1174
+
1175
+ // React前端项目
1176
+ "robot-react": "start",
1177
+ "robot-react-base": "start",
1178
+
1179
+ // 移动端项目
1180
+ "robot-uniapp": "dev:h5",
1181
+ "robot-uniapp-base": "dev:h5",
1182
+ "robot-tarao": "android",
1183
+ "robot-tarao-base": "android",
1184
+
1185
+ // 后端项目
1186
+ "robot-nest": "start:dev",
1187
+ "robot-nest-base": "start:dev",
1188
+ "robot-nest-micro": "start:dev",
1189
+ "robot-koa": "dev",
1190
+ "robot-koa-base": "dev",
1191
+
1192
+ // 桌面端项目
1193
+ "robot-electron": "electron:dev",
1194
+ "robot-electron-base": "electron:dev",
1195
+ "robot-tauri": "tauri dev",
1196
+ "robot-tauri-base": "tauri dev",
1197
+ };
1198
+
1199
+ const scriptName = startCommands[template.key] || "dev";
1200
+ return `${packageManager} run ${scriptName}`;
1146
1201
  }