@agile-team/robot-cli 1.1.9 → 2.1.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/CHANGELOG.md +77 -0
- package/README.md +240 -323
- package/bin/robot.js +3 -0
- package/dist/index.js +1720 -0
- package/package.json +77 -80
- package/bin/index.js +0 -397
- package/lib/create.js +0 -1146
- package/lib/download.js +0 -205
- package/lib/templates.js +0 -353
- package/lib/utils.js +0 -334
package/lib/create.js
DELETED
|
@@ -1,1146 +0,0 @@
|
|
|
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: true
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
name: `● ${chalk.bold('分类模板')} ${chalk.dim('(按项目类型分类选择) - 前端、后端、移动端、桌面端分类浏览')}`,
|
|
157
|
-
value: "category"
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
name: chalk.dim("─".repeat(70)),
|
|
161
|
-
value: "sep2",
|
|
162
|
-
disabled: true
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
name: `● ${chalk.bold('搜索模板')} ${chalk.dim('(关键词搜索) - 通过技术栈、功能特性等关键词快速查找')}`,
|
|
166
|
-
value: "search"
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: chalk.dim("─".repeat(70)),
|
|
170
|
-
value: "sep3",
|
|
171
|
-
disabled: true
|
|
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: true
|
|
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: true,
|
|
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: true
|
|
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: true
|
|
615
|
-
});
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
// 移动端模板
|
|
619
|
-
categorizedChoices.push({
|
|
620
|
-
name: chalk.yellow.bold("📱 移动端项目"),
|
|
621
|
-
value: "mobile_header",
|
|
622
|
-
disabled: true
|
|
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: true
|
|
643
|
-
});
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
// 后端模板
|
|
647
|
-
categorizedChoices.push({
|
|
648
|
-
name: chalk.yellow.bold("🚀 后端项目"),
|
|
649
|
-
value: "backend_header",
|
|
650
|
-
disabled: true
|
|
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: true
|
|
672
|
-
});
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
// 桌面端模板
|
|
676
|
-
categorizedChoices.push({
|
|
677
|
-
name: chalk.yellow.bold("💻 桌面端项目"),
|
|
678
|
-
value: "desktop_header",
|
|
679
|
-
disabled: true
|
|
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: true
|
|
701
|
-
});
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
// 添加返回选项
|
|
705
|
-
categorizedChoices.push(
|
|
706
|
-
{
|
|
707
|
-
name: chalk.dim("━".repeat(70)),
|
|
708
|
-
value: "separator",
|
|
709
|
-
disabled: true,
|
|
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";
|
|
1146
|
-
}
|