@fastcar/cli 0.1.3 → 0.1.4

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,700 +1,708 @@
1
- const process = require("process");
2
- const fs = require("fs");
3
- const path = require("path");
4
- const { execSync } = require("child_process");
5
- const inquirer = require("inquirer");
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
- }
77
-
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
- }
107
-
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,
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
- );
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;
297
- }
298
- }
299
-
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
- });
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
- });
369
- });
370
- };
371
-
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;
404
- }
405
- } else {
406
- // 情况4:args[0] 是模板名,args[1] 是项目名
407
- type = args[0];
408
- projectName = args[1];
409
- hasProjectName = true;
410
- }
411
-
412
- // 如果没有指定模板类型,或者指定的模板不存在,则交互式选择
413
- if (!type || !getTemplateConfig(type)) {
414
- if (type && !getTemplateConfig(type)) {
415
- console.log(`⚠️ 未找到模板: ${type},请从以下列表中选择:`);
416
- }
417
- type = await selectTemplate();
418
- }
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;
442
- }
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];
455
- }
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`);
469
- }
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`);
542
- }
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("⚠️ 没有选择任何组件");
562
- }
563
-
564
- // 从 npm 下载模板
565
- await downloadTemplate(templateConfig.package, currDir);
566
-
567
- // 检查模板是否正确复制
568
- console.log(`\n📂 检查项目目录: ${currDir}`);
569
- if (!fs.existsSync(currDir)) {
570
- throw new Error(`项目目录不存在: ${currDir}`);
571
- }
572
-
573
- const projectFiles = fs.readdirSync(currDir);
574
- console.log(`📄 项目文件: ${projectFiles.join(", ") || "(空)"}`);
575
-
576
- if (projectFiles.length === 0) {
577
- throw new Error(`项目目录为空,模板复制失败`);
578
- }
579
-
580
- // 合并 package.json 文件
581
- let templatePackagePath = path.join(currDir, "package.json");
582
- console.log(`📂 检查模板 package.json: ${templatePackagePath}`);
583
-
584
- if (fs.existsSync(templatePackagePath)) {
585
- console.log(`✅ 找到模板 package.json`);
586
- let templatePackage = require(templatePackagePath);
587
-
588
- // 替换本地包名
589
- if (templatePackage.scripts) {
590
- templatePackage.scripts = JSON.stringify(
591
- templatePackage.scripts,
592
- ).replace(/\$npm_package_name/g, packageInfo.name);
593
- templatePackage.scripts = JSON.parse(templatePackage.scripts);
594
- }
595
-
596
- if (templatePackage.dependencies) {
597
- if (!packageInfo.dependencies) {
598
- packageInfo.dependencies = {};
599
- }
600
-
601
- let tmpDep = {};
602
- componentList.forEach((item) => {
603
- if (!packageInfo.dependencies[item]) {
604
- Reflect.set(tmpDep, item, `latest`);
605
- }
606
- });
607
-
608
- packageInfo.dependencies = Object.assign(
609
- packageInfo.dependencies,
610
- tmpDep,
611
- templatePackage.dependencies,
612
- );
613
- }
614
-
615
- if (!packageInfo.scripts) {
616
- packageInfo.scripts = {};
617
- }
618
-
619
- // 覆盖其脚本
620
- if (templatePackage.scripts) {
621
- Object.assign(packageInfo.scripts, templatePackage.scripts);
622
- }
623
-
624
- if (templatePackage.devDependencies) {
625
- if (!packageInfo.devDependencies) {
626
- packageInfo.devDependencies = {};
627
- }
628
-
629
- Object.assign(
630
- packageInfo.devDependencies,
631
- templatePackage.devDependencies,
632
- );
633
- }
634
- } else {
635
- console.log(`⚠️ 模板中没有 package.json,将使用默认配置`);
636
- }
637
-
638
- console.log("📝 写入 package.json...");
639
- fs.writeFileSync(realPackagePath, JSON.stringify(packageInfo, null, "\t"));
640
-
641
- // 更改配置的文件名
642
- const pm2RunPath = path.join(currDir, "ecosystem.config.yml");
643
-
644
- if (fs.existsSync(pm2RunPath)) {
645
- const pm2Config = utils.readYaml(pm2RunPath);
646
- pm2Config.apps.name = packageInfo.name;
647
- utils.writeYaml(pm2RunPath, pm2Config);
648
- }
649
-
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);
697
- }
698
- }
699
-
700
- module.exports = init;
1
+ const process = require("process");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const { execSync } = require("child_process");
5
+ const inquirer = require("inquirer");
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
+ }
77
+
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
+ }
107
+
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,
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
+ );
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;
297
+ }
298
+ }
299
+
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
+ });
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
+ });
369
+ });
370
+ };
371
+
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;
404
+ }
405
+ } else {
406
+ // 情况4:args[0] 是模板名,args[1] 是项目名
407
+ type = args[0];
408
+ projectName = args[1];
409
+ hasProjectName = true;
410
+ }
411
+
412
+ // 如果没有指定模板类型,或者指定的模板不存在,则交互式选择
413
+ if (!type || !getTemplateConfig(type)) {
414
+ if (type && !getTemplateConfig(type)) {
415
+ console.log(`⚠️ 未找到模板: ${type},请从以下列表中选择:`);
416
+ }
417
+ type = await selectTemplate();
418
+ }
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;
442
+ }
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];
455
+ }
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`);
469
+ }
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`);
542
+ }
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("⚠️ 没有选择任何组件");
562
+ }
563
+
564
+ // 从 npm 下载模板
565
+ await downloadTemplate(templateConfig.package, currDir);
566
+
567
+ // 检查模板是否正确复制
568
+ console.log(`\n📂 检查项目目录: ${currDir}`);
569
+ if (!fs.existsSync(currDir)) {
570
+ throw new Error(`项目目录不存在: ${currDir}`);
571
+ }
572
+
573
+ const projectFiles = fs.readdirSync(currDir);
574
+ console.log(`📄 项目文件: ${projectFiles.join(", ") || "(空)"}`);
575
+
576
+ if (projectFiles.length === 0) {
577
+ throw new Error(`项目目录为空,模板复制失败`);
578
+ }
579
+
580
+ // 合并 package.json 文件
581
+ let templatePackagePath = path.join(currDir, "package.json");
582
+ console.log(`📂 检查模板 package.json: ${templatePackagePath}`);
583
+
584
+ if (fs.existsSync(templatePackagePath)) {
585
+ console.log(`✅ 找到模板 package.json`);
586
+ let templatePackage = require(templatePackagePath);
587
+
588
+ // 替换本地包名
589
+ if (templatePackage.scripts) {
590
+ templatePackage.scripts = JSON.stringify(
591
+ templatePackage.scripts,
592
+ ).replace(/\$npm_package_name/g, packageInfo.name);
593
+ templatePackage.scripts = JSON.parse(templatePackage.scripts);
594
+ }
595
+
596
+ if (templatePackage.dependencies) {
597
+ if (!packageInfo.dependencies) {
598
+ packageInfo.dependencies = {};
599
+ }
600
+
601
+ let tmpDep = {};
602
+ componentList.forEach((item) => {
603
+ if (!packageInfo.dependencies[item]) {
604
+ Reflect.set(tmpDep, item, `latest`);
605
+ }
606
+ });
607
+
608
+ packageInfo.dependencies = Object.assign(
609
+ packageInfo.dependencies,
610
+ tmpDep,
611
+ templatePackage.dependencies,
612
+ );
613
+ }
614
+
615
+ if (!packageInfo.scripts) {
616
+ packageInfo.scripts = {};
617
+ }
618
+
619
+ // 覆盖其脚本
620
+ if (templatePackage.scripts) {
621
+ Object.assign(packageInfo.scripts, templatePackage.scripts);
622
+ }
623
+
624
+ if (templatePackage.devDependencies) {
625
+ if (!packageInfo.devDependencies) {
626
+ packageInfo.devDependencies = {};
627
+ }
628
+
629
+ Object.assign(
630
+ packageInfo.devDependencies,
631
+ templatePackage.devDependencies,
632
+ );
633
+ }
634
+ } else {
635
+ console.log(`⚠️ 模板中没有 package.json,将使用默认配置`);
636
+ }
637
+
638
+ console.log("📝 写入 package.json...");
639
+ fs.writeFileSync(realPackagePath, JSON.stringify(packageInfo, null, "\t"));
640
+
641
+ // 复制 AGENTS.md 到项目根目录(如果模板没有自带)
642
+ const agentsSourcePath = path.join(__dirname, "..", "skills", "AGENTS.md");
643
+ const agentsTargetPath = path.join(currDir, "AGENTS.md");
644
+ if (fs.existsSync(agentsSourcePath) && !fs.existsSync(agentsTargetPath)) {
645
+ console.log("📝 复制 AGENTS.md 到项目根目录...");
646
+ fs.copyFileSync(agentsSourcePath, agentsTargetPath);
647
+ }
648
+
649
+ // 更改配置的文件名
650
+ const pm2RunPath = path.join(currDir, "ecosystem.config.yml");
651
+
652
+ if (fs.existsSync(pm2RunPath)) {
653
+ const pm2Config = utils.readYaml(pm2RunPath);
654
+ pm2Config.apps.name = packageInfo.name;
655
+ utils.writeYaml(pm2RunPath, pm2Config);
656
+ }
657
+
658
+ // 选择包管理器
659
+ const packageManager = await selectPackageManager();
660
+
661
+ // 获取项目文件夹名(用于显示 cd 命令)
662
+ const projectFolderName = path.basename(currDir);
663
+
664
+ console.log("\n 项目初始化完成!");
665
+ console.log(`📁 项目路径: ${currDir}`);
666
+ console.log(`📦 使用模板: ${templateConfig.package}`);
667
+ console.log(`📦 包管理器: ${packageManager.name}`);
668
+ console.log(`\n👉 请执行以下命令启动项目:`);
669
+ console.log(` cd ${projectFolderName} && ${packageManager.installCmd}`);
670
+ console.log();
671
+ } catch (error) {
672
+ console.error("\n" + "=".repeat(50));
673
+ console.error("❌ 项目初始化失败");
674
+ console.error("=".repeat(50));
675
+
676
+ if (error.message) {
677
+ console.error("\n📋 错误信息:");
678
+ console.error(error.message);
679
+ }
680
+
681
+ // 如果是系统错误,显示更多技术细节
682
+ if (error.stderr) {
683
+ console.error("\n📋 详细日志:");
684
+ console.error(error.stderr.toString());
685
+ }
686
+
687
+ if (error.code) {
688
+ console.error(`\n📋 错误代码:${error.code}`);
689
+ }
690
+
691
+ console.error("\n" + "-".repeat(50));
692
+ console.error("💡 如果问题持续存在,请尝试以下操作:");
693
+ console.error(" 1. 检查网络连接是否正常");
694
+ console.error(
695
+ " 2. 更新 fastcar-cli 到最新版本:npm install -g @fastcar/cli",
696
+ );
697
+ console.error(" 3. 清除 npm 缓存:npm cache clean --force");
698
+ console.error(" 4. 使用 --verbose 参数查看详细日志(如果支持)");
699
+ console.error(
700
+ " 5. 访问 https://github.com/williamDazhangyu/fastcar-cli/issues 提交问题",
701
+ );
702
+ console.error("-".repeat(50) + "\n");
703
+
704
+ process.exit(1);
705
+ }
706
+ }
707
+
708
+ module.exports = init;