@fastcar/cli 0.0.7 → 0.0.8

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/src/init.js CHANGED
@@ -1,233 +1,604 @@
1
1
  const process = require("process");
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
- const exec = require("child_process").execSync;
4
+ const { execSync } = require("child_process");
5
5
  const inquirer = require("inquirer");
6
6
  const utils = require("./utils");
7
+ const templates = require("./templates.json");
8
+
9
+ // 可选组件配置
10
+ const optionComponents = [
11
+ {
12
+ name: "pgsql",
13
+ description: "PostgreSQL - 关系型数据库",
14
+ default: false,
15
+ package: "@fastcar/pgsql",
16
+ },
17
+ {
18
+ name: "mysql",
19
+ description: "MySQL - 关系型数据库",
20
+ default: false,
21
+ package: "@fastcar/mysql",
22
+ },
23
+ {
24
+ name: "redis",
25
+ description: "Redis - 缓存数据库",
26
+ default: false,
27
+ package: "@fastcar/redis",
28
+ },
29
+ {
30
+ name: "mongo",
31
+ description: "MongoDB - 文档数据库",
32
+ default: false,
33
+ package: "@fastcar/mongo",
34
+ },
35
+ ];
36
+
37
+ const optionComponentNames = optionComponents.map((c) => c.name);
38
+
39
+ // 包管理器配置
40
+ const packageManagers = [
41
+ {
42
+ name: "pnpm",
43
+ installCmd: "pnpm install",
44
+ description: "pnpm - 快速、节省磁盘空间的包管理器",
45
+ },
46
+ {
47
+ name: "yarn",
48
+ installCmd: "yarn install",
49
+ description: "yarn - 快速、可靠、安全的依赖管理",
50
+ },
51
+ {
52
+ name: "npm",
53
+ installCmd: "npm install",
54
+ description: "npm - Node.js 默认包管理器",
55
+ },
56
+ ];
57
+
58
+ // 交互式选择包管理器
59
+ async function selectPackageManager() {
60
+ const choices = packageManagers.map((pm) => ({
61
+ name: pm.description,
62
+ value: pm.name,
63
+ }));
64
+
65
+ const answer = await inquirer.prompt([
66
+ {
67
+ type: "list",
68
+ name: "packageManager",
69
+ message: "选择包管理器安装依赖:",
70
+ choices,
71
+ default: "npm",
72
+ },
73
+ ]);
74
+
75
+ return packageManagers.find((pm) => pm.name === answer.packageManager);
76
+ }
7
77
 
8
- const WEBTEMPLATEURL =
9
- "https://e.coding.net/william_zhong/fast-car/fastcar-boot-web.git"; //web模板
10
- const RPCTEMPLATEURL =
11
- "https://e.coding.net/william_zhong/fast-car/fastcar-boot-rpc.git"; //rpc模板
12
- const COSTEMPLATEURL =
13
- "https://e.coding.net/william_zhong/fast-car/fastcar-cos.git"; //cos模板
14
- const MICROTEMPLATE =
15
- "https://e.coding.net/william_zhong/fast-car/fastcar-microservices.git"; //微服务模板
16
- const STATICURL =
17
- "https://e.coding.net/william_zhong/fast-car/fastcar-static.git"; //静态资源模板
18
- const optionComponent = ["mysql", "redis", "mongo"];
19
-
20
- const Questions = async (defaultName) => {
21
- return new Promise((resolve) => {
22
- inquirer
23
- .prompt([
24
- {
25
- type: "input",
26
- name: "name",
27
- default: defaultName,
28
- message: `name (${defaultName}) :`,
29
- },
30
- {
31
- type: "input",
32
- name: "version",
33
- default: "1.0.0",
34
- message: "version (1.0.0) :",
35
- },
36
- {
37
- type: "input",
38
- name: "description",
39
- message: "description:",
40
- },
41
- {
42
- type: "input",
43
- name: "repositoryUrl",
44
- message: "repository url:",
45
- },
46
- {
47
- type: "input",
48
- name: "author",
49
- message: "author:",
50
- },
51
- {
52
- type: "input",
53
- name: "license",
54
- default: "MIT",
55
- message: "license (MIT) :",
56
- },
57
- {
58
- type: "confirm",
59
- name: "private",
60
- message: "private (true) :",
61
- default: true,
62
- },
63
- {
64
- type: "confirm",
65
- name: "mysql",
66
- message: "mysql (true) :",
67
- default: true,
68
- },
69
- {
70
- type: "confirm",
71
- name: "redis",
72
- message: "redis (true) :",
73
- default: true,
74
- },
75
- {
76
- type: "confirm",
77
- name: "mongo",
78
- message: "mongo (false) :",
79
- default: false,
80
- },
81
- ])
82
- .then((answers) => {
83
- resolve(answers);
84
- });
85
- });
86
- };
78
+ // 获取所有可用的模板列表
79
+ function getTemplateList() {
80
+ return Object.values(templates).map((t) => ({
81
+ name: `${t.name} - ${t.description}`,
82
+ value: t.name,
83
+ package: t.package,
84
+ }));
85
+ }
86
+
87
+ // 根据模板名称获取模板配置
88
+ function getTemplateConfig(name) {
89
+ return templates[name] || null;
90
+ }
91
+
92
+ // 交互式选择模板
93
+ async function selectTemplate() {
94
+ const templateList = getTemplateList();
95
+
96
+ const answer = await inquirer.prompt([
97
+ {
98
+ type: "list",
99
+ name: "template",
100
+ message: "请选择项目模板:",
101
+ choices: templateList,
102
+ },
103
+ ]);
104
+
105
+ return answer.template;
106
+ }
87
107
 
88
- async function init(args = ["web"]) {
89
- let currDir = process.cwd();
90
- let type = args[0];
91
-
92
- let disList = currDir.split(path.sep);
93
- let lastName = disList[disList.length - 1];
94
-
95
- //判定是否有package.json文件 若存在则跳过这一步
96
- let realPackagePath = path.join(currDir, "package.json");
97
- let packageInfo = {};
98
- let questionInfo = {
99
- mysql: false,
100
- redis: false,
101
- mongo: false,
102
- };
103
- let componentList = [];
104
- if (fs.existsSync(realPackagePath)) {
105
- packageInfo = require(realPackagePath);
106
- } else {
107
- questionInfo = await Questions(lastName);
108
- packageInfo = {
109
- name: questionInfo.name,
110
- version: questionInfo.version,
111
- description: questionInfo.description,
112
- author: questionInfo.author,
113
- license: questionInfo.license,
114
- private: questionInfo.private,
115
- };
116
-
117
- if (!!questionInfo.repositoryUrl) {
118
- let repType = questionInfo.repositoryUrl.split(".");
119
- Reflect.set(packageInfo, {
120
- repository: {
121
- type: repType,
122
- url: questionInfo.repositoryUrl,
123
- },
108
+ // npm 下载模板包
109
+ async function downloadTemplate(packageName, targetDir) {
110
+ console.log(`📦 正在下载模板 ${packageName}...`);
111
+ console.log(`📂 目标目录: ${targetDir}`);
112
+
113
+ // 使用 npm pack 下载包
114
+ const tempDir = path.join(process.cwd(), `.fastcar-temp-${Date.now()}`);
115
+ fs.mkdirSync(tempDir, { recursive: true });
116
+ console.log(`📂 临时目录: ${tempDir}`);
117
+
118
+ try {
119
+ // 下载 tarball
120
+ try {
121
+ console.log(`⬇️ 执行: npm pack ${packageName}...`);
122
+ execSync(`npm pack ${packageName} --pack-destination "${tempDir}"`, {
123
+ stdio: "pipe",
124
+ cwd: tempDir,
124
125
  });
126
+ console.log(`✅ npm pack 执行成功`);
127
+ } catch (packError) {
128
+ // 分析 npm pack 错误
129
+ const errorMsg = packError.message || "";
130
+ if (errorMsg.includes("E404") || errorMsg.includes("not found")) {
131
+ throw new Error(
132
+ `模板包 "${packageName}" 不存在\n` +
133
+ `💡 可能的原因:\n` +
134
+ ` 1. 包名拼写错误\n` +
135
+ ` 2. 该模板尚未发布到 npm\n` +
136
+ ` 3. 你没有该私有包的访问权限\n` +
137
+ `💡 解决方案:\n` +
138
+ ` - 检查模板名称是否正确\n` +
139
+ ` - 访问 https://www.npmjs.com/package/${packageName} 确认包是否存在`,
140
+ );
141
+ } else if (
142
+ errorMsg.includes("network") ||
143
+ errorMsg.includes("ECONNREFUSED")
144
+ ) {
145
+ throw new Error(
146
+ `网络连接失败,无法下载模板包 "${packageName}"\n` +
147
+ `💡 可能的原因:\n` +
148
+ ` 1. 网络连接问题\n` +
149
+ ` 2. npm registry 无法访问\n` +
150
+ ` 3. 代理设置问题\n` +
151
+ `💡 解决方案:\n` +
152
+ ` - 检查网络连接\n` +
153
+ ` - 尝试切换 npm 镜像源:npm config set registry https://registry.npmmirror.com\n` +
154
+ ` - 检查代理设置:npm config get proxy`,
155
+ );
156
+ } else {
157
+ throw new Error(
158
+ `下载模板包 "${packageName}" 失败\n` +
159
+ `📋 错误详情:${packError.message}\n` +
160
+ `💡 尝试重新执行命令,或手动检查 npm 是否正常工作`,
161
+ );
162
+ }
163
+ }
164
+
165
+ // 找到下载的 tarball 文件
166
+ console.log(`📂 读取临时目录内容...`);
167
+ const files = fs.readdirSync(tempDir);
168
+ console.log(`📄 找到文件: ${files.join(", ")}`);
169
+
170
+ const tarball = files.find((f) => f.endsWith(".tgz"));
171
+ console.log(`📦 tarball 文件: ${tarball}`);
172
+
173
+ if (!tarball) {
174
+ throw new Error(
175
+ `无法找到下载的模板包文件\n` +
176
+ `💡 可能的原因:npm pack 命令执行异常\n` +
177
+ `💡 解决方案:\n` +
178
+ ` - 检查 npm 版本:npm --version\n` +
179
+ ` - 尝试手动下载:npm pack ${packageName}`,
180
+ );
125
181
  }
182
+
183
+ const tarballPath = path.join(tempDir, tarball);
184
+
185
+ // 解压 tarball
186
+ try {
187
+ const extractDir = path.join(tempDir, "extracted");
188
+ fs.mkdirSync(extractDir, { recursive: true });
189
+
190
+ if (process.platform === "win32") {
191
+ execSync(`tar -xzf "${tarballPath}" -C "${extractDir}"`, {
192
+ stdio: "pipe",
193
+ });
194
+ } else {
195
+ execSync(`tar -xzf "${tarballPath}" -C "${extractDir}"`, {
196
+ stdio: "pipe",
197
+ });
198
+ }
199
+ } catch (extractError) {
200
+ throw new Error(
201
+ `解压模板包失败\n` +
202
+ `📋 错误详情:${extractError.message}\n` +
203
+ `💡 解决方案:\n` +
204
+ ` - 检查 tar 命令是否可用\n` +
205
+ ` - 尝试手动解压:tar -xzf ${tarballPath}`,
206
+ );
207
+ }
208
+
209
+ // npm pack 解压后会得到 package 目录
210
+ const packageDir = path.join(tempDir, "extracted", "package");
211
+ console.log(`📂 检查 package 目录: ${packageDir}`);
212
+ console.log(`📂 目录是否存在: ${fs.existsSync(packageDir)}`);
213
+
214
+ // 列出 extracted 目录内容以便调试
215
+ const extractDir = path.join(tempDir, "extracted");
216
+ if (fs.existsSync(extractDir)) {
217
+ const extractedFiles = fs.readdirSync(extractDir);
218
+ console.log(`📄 extracted 目录内容: ${extractedFiles.join(", ")}`);
219
+ }
220
+
221
+ if (!fs.existsSync(packageDir)) {
222
+ throw new Error(
223
+ `模板包结构不正确,缺少 package 目录\n` +
224
+ `💡 可能的原因:模板包打包格式不正确\n` +
225
+ `💡 解决方案:联系模板维护者检查包结构`,
226
+ );
227
+ }
228
+
229
+ // 检查模板包结构,优先使用 template 目录,否则使用整个包
230
+ const templateDir = path.join(packageDir, "template");
231
+ console.log(`📂 检查 template 目录: ${templateDir}`);
232
+ console.log(`📂 template 目录是否存在: ${fs.existsSync(templateDir)}`);
233
+
234
+ const sourceDir = fs.existsSync(templateDir) ? templateDir : packageDir;
235
+ console.log(`📂 源目录: ${sourceDir}`);
236
+ console.log(`📂 目标目录: ${targetDir}`);
237
+
238
+ // 复制模板文件到目标目录
239
+ console.log("📋 复制模板文件...");
240
+ console.log(` 从: ${sourceDir}`);
241
+ console.log(` 到: ${targetDir}`);
242
+
243
+ if (!fs.existsSync(sourceDir)) {
244
+ throw new Error(
245
+ `源目录不存在: ${sourceDir}\n` + `💡 可能原因:模板包结构不正确`,
246
+ );
247
+ }
248
+
249
+ // 检查源目录是否有内容
250
+ const sourceFiles = fs.readdirSync(sourceDir);
251
+ console.log(`📄 源目录文件数: ${sourceFiles.length}`);
252
+ if (sourceFiles.length === 0) {
253
+ throw new Error(
254
+ `源目录为空: ${sourceDir}\n` + `💡 可能原因:模板包没有正确打包`,
255
+ );
256
+ }
257
+
258
+ const copyResult = utils.copyDirectory(sourceDir, targetDir);
259
+ if (copyResult === false) {
260
+ throw new Error(
261
+ `复制模板文件失败\n` +
262
+ `💡 可能原因:\n` +
263
+ ` 1. 源目录不存在或为空\n` +
264
+ ` 2. 目标目录没有写入权限\n` +
265
+ ` 3. 磁盘空间不足`,
266
+ );
267
+ }
268
+
269
+ // 检查目标目录内容
270
+ if (fs.existsSync(targetDir)) {
271
+ const targetFiles = fs.readdirSync(targetDir);
272
+ console.log(`📄 目标目录内容: ${targetFiles.join(", ")}`);
273
+
274
+ if (targetFiles.length === 0) {
275
+ throw new Error(
276
+ `目标目录为空,复制可能失败\n` + `💡 请检查模板包内容是否正确`,
277
+ );
278
+ }
279
+ } else {
280
+ throw new Error(
281
+ `目标目录创建失败: ${targetDir}\n` + `💡 请检查是否有写入权限`,
282
+ );
283
+ }
284
+
285
+ console.log(`✅ 模板 ${packageName} 下载完成`);
286
+
287
+ // 清理临时目录
288
+ utils.delDirEctory(tempDir);
289
+
290
+ return true;
291
+ } catch (error) {
292
+ // 清理临时目录
293
+ if (fs.existsSync(tempDir)) {
294
+ utils.delDirEctory(tempDir);
295
+ }
296
+ throw error;
126
297
  }
298
+ }
127
299
 
128
- Object.keys(packageInfo).forEach((key) => {
129
- if (!packageInfo[key]) {
130
- Reflect.deleteProperty(packageInfo, key);
300
+ // 询问项目信息
301
+ // skipNamePrompt: 如果为 true,跳过项目名称询问,直接使用 defaultName
302
+ const Questions = async (defaultName, skipNamePrompt = false) => {
303
+ return new Promise((resolve) => {
304
+ const prompts = [];
305
+
306
+ // 只有在需要时才询问项目名称
307
+ if (!skipNamePrompt) {
308
+ prompts.push({
309
+ type: "input",
310
+ name: "name",
311
+ default: defaultName,
312
+ message: `项目名称 (${defaultName}):`,
313
+ });
131
314
  }
315
+
316
+ prompts.push(
317
+ {
318
+ type: "input",
319
+ name: "version",
320
+ default: "1.0.0",
321
+ message: "版本 (1.0.0):",
322
+ },
323
+ {
324
+ type: "input",
325
+ name: "description",
326
+ message: "项目描述:",
327
+ },
328
+ {
329
+ type: "input",
330
+ name: "repositoryUrl",
331
+ message: "仓库地址:",
332
+ },
333
+ {
334
+ type: "input",
335
+ name: "author",
336
+ message: "作者:",
337
+ },
338
+ {
339
+ type: "input",
340
+ name: "license",
341
+ default: "MIT",
342
+ message: "许可证 (MIT):",
343
+ },
344
+ {
345
+ type: "confirm",
346
+ name: "private",
347
+ message: "私有项目:",
348
+ default: true,
349
+ },
350
+ {
351
+ type: "checkbox",
352
+ name: "components",
353
+ message: "选择需要的数据库组件 (空格选择/取消,回车确认):",
354
+ choices: optionComponents.map((c) => ({
355
+ name: c.description,
356
+ value: c.name,
357
+ checked: c.default,
358
+ })),
359
+ },
360
+ );
361
+
362
+ inquirer.prompt(prompts).then((answers) => {
363
+ // 如果跳过了名称询问,手动设置 name 字段
364
+ if (skipNamePrompt) {
365
+ answers.name = defaultName;
366
+ }
367
+ resolve(answers);
368
+ });
132
369
  });
370
+ };
133
371
 
134
- Object.keys(questionInfo).forEach((key) => {
135
- if (questionInfo[key]) {
136
- if (optionComponent.includes(key)) {
137
- componentList.push(`@fastcar/${key}`);
138
- if (key == "mysql") {
139
- componentList.push(`@fastcar/${key}-tool`);
140
- }
372
+ async function init(args = []) {
373
+ try {
374
+ let currDir = process.cwd();
375
+ let type = null;
376
+ let projectName = null;
377
+
378
+ // 解析参数:支持以下几种格式
379
+ // 1. init -> 交互式选择模板,询问项目名,创建文件夹
380
+ // 2. init my-project -> 交互式选择模板,使用 my-project 作为项目名,创建文件夹
381
+ // 3. init web -> 使用 web 模板,询问项目名,创建文件夹
382
+ // 4. init web my-project -> 使用 web 模板,使用 my-project 作为项目名,创建文件夹
383
+
384
+ // hasProjectName 用于判断是否指定了项目名,决定是否询问项目名称
385
+ let hasProjectName = false;
386
+
387
+ if (args.length === 0) {
388
+ // 情况1:没有任何参数
389
+ type = null;
390
+ projectName = null;
391
+ hasProjectName = false;
392
+ } else if (args.length === 1) {
393
+ // 可能是情况2或情况3
394
+ if (getTemplateConfig(args[0])) {
395
+ // 情况3:args[0] 是模板名,未指定项目名,需要询问
396
+ type = args[0];
397
+ projectName = null;
398
+ hasProjectName = false;
399
+ } else {
400
+ // 情况2:args[0] 是项目名(不是模板名)
401
+ type = null;
402
+ projectName = args[0];
403
+ hasProjectName = true;
141
404
  }
405
+ } else {
406
+ // 情况4:args[0] 是模板名,args[1] 是项目名
407
+ type = args[0];
408
+ projectName = args[1];
409
+ hasProjectName = true;
142
410
  }
143
- });
144
411
 
145
- //先暂定为只有web组件
146
- let downloadUrl = "";
147
- switch (type) {
148
- case "web": {
149
- downloadUrl = WEBTEMPLATEURL;
150
- break;
412
+ // 如果没有指定模板类型,或者指定的模板不存在,则交互式选择
413
+ if (!type || !getTemplateConfig(type)) {
414
+ if (type && !getTemplateConfig(type)) {
415
+ console.log(`⚠️ 未找到模板: ${type},请从以下列表中选择:`);
416
+ }
417
+ type = await selectTemplate();
151
418
  }
152
- case "rpc": {
153
- downloadUrl = RPCTEMPLATEURL;
154
- break;
419
+
420
+ const templateConfig = getTemplateConfig(type);
421
+ if (!templateConfig) {
422
+ console.error("\n" + "=".repeat(50));
423
+ console.error("❌ 模板不存在");
424
+ console.error("=".repeat(50));
425
+ console.error(`\n📋 你输入的模板:${type}`);
426
+ console.error("\n📋 可用的模板列表:");
427
+
428
+ const availableTemplates = getTemplateList();
429
+ availableTemplates.forEach((t) => {
430
+ console.error(` • ${t.name}`);
431
+ });
432
+
433
+ console.error("\n💡 使用示例:");
434
+ console.error(` fastcar-cli init web`);
435
+ console.error(` fastcar-cli init rpc`);
436
+ console.error(` fastcar-cli init my-project`);
437
+ console.error(` fastcar-cli init web my-project`);
438
+ console.error("\n💡 或者直接执行快速交互式选择:");
439
+ console.error(` fastcar-cli init`);
440
+ console.error("=".repeat(50) + "\n");
441
+ return;
155
442
  }
156
- case "cos": {
157
- downloadUrl = COSTEMPLATEURL;
158
- break;
443
+
444
+ console.log(
445
+ `\n🚀 使用模板: ${templateConfig.name} - ${templateConfig.description}\n`,
446
+ );
447
+
448
+ // 获取默认项目名(用于询问时的默认值)
449
+ let defaultName;
450
+ if (projectName) {
451
+ defaultName = projectName;
452
+ } else {
453
+ let disList = currDir.split(path.sep);
454
+ defaultName = disList[disList.length - 1];
159
455
  }
160
- case "micro": {
161
- downloadUrl = MICROTEMPLATE;
162
- break;
456
+
457
+ // 判定是否有 package.json 文件
458
+ let realPackagePath = path.join(currDir, "package.json");
459
+ let packageInfo = {};
460
+ let questionInfo = {};
461
+ let componentList = [];
462
+
463
+ // 判断是否跳过项目名称询问
464
+ // 只有在命令行指定了项目名参数时,才跳过询问
465
+ const skipNamePrompt = hasProjectName;
466
+
467
+ if (skipNamePrompt) {
468
+ console.log(`📦 项目名称: ${defaultName}\n`);
163
469
  }
164
- case "static": {
165
- downloadUrl = STATICURL;
166
- break;
470
+
471
+ // 无论是否有 package.json,都询问项目信息和组件选择
472
+ // (如果有 package.json,则以它为基础进行修改)
473
+ if (fs.existsSync(realPackagePath)) {
474
+ const existingPackage = require(realPackagePath);
475
+ questionInfo = await Questions(
476
+ existingPackage.name || defaultName,
477
+ skipNamePrompt,
478
+ );
479
+
480
+ // 保留原有的依赖,只更新其他字段
481
+ packageInfo = {
482
+ ...existingPackage,
483
+ name: questionInfo.name,
484
+ version: questionInfo.version,
485
+ description: questionInfo.description,
486
+ author: questionInfo.author,
487
+ license: questionInfo.license,
488
+ private: questionInfo.private,
489
+ };
490
+ } else {
491
+ questionInfo = await Questions(defaultName, skipNamePrompt);
492
+ packageInfo = {
493
+ name: questionInfo.name,
494
+ version: questionInfo.version,
495
+ description: questionInfo.description,
496
+ author: questionInfo.author,
497
+ license: questionInfo.license,
498
+ private: questionInfo.private,
499
+ };
500
+
501
+ if (!!questionInfo.repositoryUrl) {
502
+ let repType = questionInfo.repositoryUrl.split(".");
503
+ Reflect.set(packageInfo, {
504
+ repository: {
505
+ type: repType,
506
+ url: questionInfo.repositoryUrl,
507
+ },
508
+ });
509
+ }
510
+ }
511
+
512
+ // 获取最终的项目名(可能是用户输入的,也可能是命令行指定的)
513
+ const finalProjectName = packageInfo.name;
514
+
515
+ // 处理项目目录:只有当项目名与当前目录名不同时,才创建新目录
516
+ // 这样如果用户在当前目录初始化,不会报错
517
+ const currentDirName = currDir.split(path.sep).pop();
518
+
519
+ if (finalProjectName !== currentDirName) {
520
+ // 需要创建项目目录
521
+ const projectDir = path.join(currDir, finalProjectName);
522
+
523
+ if (fs.existsSync(projectDir)) {
524
+ console.error("\n" + "=".repeat(50));
525
+ console.error("❌ 目录已存在");
526
+ console.error("=".repeat(50));
527
+ console.error(`\n📋 目录路径:${projectDir}`);
528
+ console.error("\n💡 解决方案:");
529
+ console.error(` 1. 更换项目名`);
530
+ console.error(` 2. 删除已存在的目录:rm -rf ${finalProjectName}`);
531
+ console.error(
532
+ ` 3. 进入目录初始化:cd ${finalProjectName} && fastcar-cli init ${type}`,
533
+ );
534
+ console.error("=".repeat(50) + "\n");
535
+ return;
536
+ }
537
+
538
+ fs.mkdirSync(projectDir, { recursive: true });
539
+ currDir = projectDir;
540
+ realPackagePath = path.join(currDir, "package.json");
541
+ console.log(`📁 创建项目目录: ${finalProjectName}\n`);
167
542
  }
168
- default: {
169
- downloadUrl = WEBTEMPLATEURL;
170
- break;
543
+
544
+ Object.keys(packageInfo).forEach((key) => {
545
+ if (!packageInfo[key]) {
546
+ Reflect.deleteProperty(packageInfo, key);
547
+ }
548
+ });
549
+
550
+ // 处理组件选择结果 (checkbox 返回的是数组)
551
+ if (questionInfo.components && Array.isArray(questionInfo.components)) {
552
+ questionInfo.components.forEach((key) => {
553
+ if (optionComponentNames.includes(key)) {
554
+ componentList.push(`@fastcar/${key}`);
555
+ if (key === "mysql") {
556
+ componentList.push(`@fastcar/${key}-tool`);
557
+ }
558
+ }
559
+ });
560
+ } else {
561
+ console.log("⚠️ 没有选择任何组件");
171
562
  }
172
- }
173
563
 
174
- if (downloadUrl) {
175
- let urlList = downloadUrl.split("/");
176
- let templateName = urlList[urlList.length - 1].replace(/.git/, "");
564
+ // npm 下载模板
565
+ await downloadTemplate(templateConfig.package, currDir);
177
566
 
178
- console.log(`Start downloading template ${downloadUrl}`);
179
- exec(`git clone ${downloadUrl} --depth=1`);
180
- console.log("Download complete");
567
+ // 检查模板是否正确复制
568
+ console.log(`\n📂 检查项目目录: ${currDir}`);
569
+ if (!fs.existsSync(currDir)) {
570
+ throw new Error(`项目目录不存在: ${currDir}`);
571
+ }
181
572
 
182
- //解压依赖
183
- //删除原先路径下的包
184
- let templateDir = path.join(currDir, templateName);
573
+ const projectFiles = fs.readdirSync(currDir);
574
+ console.log(`📄 项目文件: ${projectFiles.join(", ") || "(空)"}`);
185
575
 
186
- //删除git路径
187
- let gitPath = path.join(templateDir, ".git");
188
- if (fs.existsSync(gitPath)) {
189
- utils.delDirEctory(gitPath);
576
+ if (projectFiles.length === 0) {
577
+ throw new Error(`项目目录为空,模板复制失败`);
190
578
  }
191
579
 
192
- console.log("copy template files");
193
- //复制至项目文件下
194
- utils.copyDirectory(templateDir, currDir);
580
+ // 合并 package.json 文件
581
+ let templatePackagePath = path.join(currDir, "package.json");
582
+ console.log(`📂 检查模板 package.json: ${templatePackagePath}`);
195
583
 
196
- //合并package.json文件
197
- let templatePackagePath = path.join(templateDir, "package.json");
198
584
  if (fs.existsSync(templatePackagePath)) {
585
+ console.log(`✅ 找到模板 package.json`);
199
586
  let templatePackage = require(templatePackagePath);
200
- //替换本地包名
587
+
588
+ // 替换本地包名
201
589
  if (templatePackage.scripts) {
202
590
  templatePackage.scripts = JSON.stringify(
203
- templatePackage.scripts
591
+ templatePackage.scripts,
204
592
  ).replace(/\$npm_package_name/g, packageInfo.name);
205
593
  templatePackage.scripts = JSON.parse(templatePackage.scripts);
206
594
  }
595
+
207
596
  if (templatePackage.dependencies) {
208
597
  if (!packageInfo.dependencies) {
209
598
  packageInfo.dependencies = {};
210
599
  }
211
600
 
212
601
  let tmpDep = {};
213
- // Object.keys(templatePackage.dependencies).forEach((tmpKey) => {
214
-
215
- // let flag = optionComponent.some((o) => {
216
-
217
- // return tmpKey.indexOf(o) != -1;
218
- // });
219
-
220
- // //如果是可选组件则看有没有被包含进来
221
- // if (flag) {
222
-
223
- // if (!componentList.includes(tmpKey)) {
224
-
225
- // return;
226
- // }
227
- // }
228
-
229
- // Reflect.set(tmpDep, tmpKey, templatePackage.dependencies[tmpKey]);
230
- // });
231
602
  componentList.forEach((item) => {
232
603
  if (!packageInfo.dependencies[item]) {
233
604
  Reflect.set(tmpDep, item, `latest`);
@@ -237,7 +608,7 @@ async function init(args = ["web"]) {
237
608
  packageInfo.dependencies = Object.assign(
238
609
  packageInfo.dependencies,
239
610
  tmpDep,
240
- templatePackage.dependencies
611
+ templatePackage.dependencies,
241
612
  );
242
613
  }
243
614
 
@@ -245,7 +616,7 @@ async function init(args = ["web"]) {
245
616
  packageInfo.scripts = {};
246
617
  }
247
618
 
248
- //覆盖其脚本
619
+ // 覆盖其脚本
249
620
  if (templatePackage.scripts) {
250
621
  Object.assign(packageInfo.scripts, templatePackage.scripts);
251
622
  }
@@ -257,29 +628,72 @@ async function init(args = ["web"]) {
257
628
 
258
629
  Object.assign(
259
630
  packageInfo.devDependencies,
260
- templatePackage.devDependencies
631
+ templatePackage.devDependencies,
261
632
  );
262
633
  }
634
+ } else {
635
+ console.log(`⚠️ 模板中没有 package.json,将使用默认配置`);
263
636
  }
264
637
 
265
- console.log("wirte packageInfo");
638
+ console.log("📝 写入 package.json...");
266
639
  fs.writeFileSync(realPackagePath, JSON.stringify(packageInfo, null, "\t"));
267
640
 
268
- //更改配置的文件名
269
- let projectName = packageInfo.name;
270
- let pm2RunPath = path.join(currDir, "ecosystem.config.yml");
641
+ // 更改配置的文件名
642
+ const pm2RunPath = path.join(currDir, "ecosystem.config.yml");
271
643
 
272
644
  if (fs.existsSync(pm2RunPath)) {
273
- let pm2Config = utils.readYaml(pm2RunPath);
274
- pm2Config.apps.name = projectName;
645
+ const pm2Config = utils.readYaml(pm2RunPath);
646
+ pm2Config.apps.name = packageInfo.name;
275
647
  utils.writeYaml(pm2RunPath, pm2Config);
276
648
  }
277
649
 
278
- console.log("clean files");
279
- setTimeout(() => {
280
- utils.delDirEctory(templateDir);
281
- console.log("Please execute npm install before starting");
282
- }, 1000);
650
+ // 选择包管理器
651
+ const packageManager = await selectPackageManager();
652
+
653
+ // 获取项目文件夹名(用于显示 cd 命令)
654
+ const projectFolderName = path.basename(currDir);
655
+
656
+ console.log("\n✨ 项目初始化完成!");
657
+ console.log(`📁 项目路径: ${currDir}`);
658
+ console.log(`📦 使用模板: ${templateConfig.package}`);
659
+ console.log(`📦 包管理器: ${packageManager.name}`);
660
+ console.log(`\n👉 请执行以下命令启动项目:`);
661
+ console.log(` cd ${projectFolderName} && ${packageManager.installCmd}`);
662
+ console.log();
663
+ } catch (error) {
664
+ console.error("\n" + "=".repeat(50));
665
+ console.error("❌ 项目初始化失败");
666
+ console.error("=".repeat(50));
667
+
668
+ if (error.message) {
669
+ console.error("\n📋 错误信息:");
670
+ console.error(error.message);
671
+ }
672
+
673
+ // 如果是系统错误,显示更多技术细节
674
+ if (error.stderr) {
675
+ console.error("\n📋 详细日志:");
676
+ console.error(error.stderr.toString());
677
+ }
678
+
679
+ if (error.code) {
680
+ console.error(`\n📋 错误代码:${error.code}`);
681
+ }
682
+
683
+ console.error("\n" + "-".repeat(50));
684
+ console.error("💡 如果问题持续存在,请尝试以下操作:");
685
+ console.error(" 1. 检查网络连接是否正常");
686
+ console.error(
687
+ " 2. 更新 fastcar-cli 到最新版本:npm install -g @fastcar/cli",
688
+ );
689
+ console.error(" 3. 清除 npm 缓存:npm cache clean --force");
690
+ console.error(" 4. 使用 --verbose 参数查看详细日志(如果支持)");
691
+ console.error(
692
+ " 5. 访问 https://github.com/williamDazhangyu/fastcar-cli/issues 提交问题",
693
+ );
694
+ console.error("-".repeat(50) + "\n");
695
+
696
+ process.exit(1);
283
697
  }
284
698
  }
285
699