@agile-team/robot-cli 1.0.3 → 1.0.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/bin/index.js +431 -296
- package/lib/utils.js +240 -244
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -1,341 +1,476 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import inquirer from 'inquirer';
|
|
7
|
-
import { createProject } from '../lib/create.js';
|
|
8
|
-
import { clearCache, getCacheInfo, formatSize } from '../lib/cache.js';
|
|
9
|
-
import { getAllTemplates, searchTemplates, getRecommendedTemplates } from '../lib/templates.js';
|
|
10
|
-
import { checkNetworkConnection } from '../lib/utils.js';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join, resolve } from 'path';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
11
6
|
|
|
12
|
-
|
|
7
|
+
// 获取当前文件的目录
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
/**
|
|
12
|
+
* 智能路径解析 - 兼容不同包管理器的安装路径
|
|
13
|
+
*/
|
|
14
|
+
function resolveLibPath() {
|
|
15
|
+
// 可能的 lib 目录路径
|
|
16
|
+
const possiblePaths = [
|
|
17
|
+
// 1. 标准相对路径 (开发环境 + 大多数情况)
|
|
18
|
+
join(__dirname, '..', 'lib'),
|
|
19
|
+
|
|
20
|
+
// 2. 同级目录 (某些链接情况)
|
|
21
|
+
join(__dirname, 'lib'),
|
|
22
|
+
|
|
23
|
+
// 3. 向上查找 (深度嵌套情况)
|
|
24
|
+
join(__dirname, '..', '..', 'lib'),
|
|
25
|
+
|
|
26
|
+
// 4. 从 node_modules 查找 (npm/yarn)
|
|
27
|
+
join(__dirname, '..', 'node_modules', '@agile-team', 'robot-cli', 'lib'),
|
|
28
|
+
|
|
29
|
+
// 5. 全局安装的各种可能路径
|
|
30
|
+
resolve(__dirname, '..', 'lib'),
|
|
31
|
+
resolve(__dirname, '../../lib'),
|
|
32
|
+
|
|
33
|
+
// 6. bun 特殊路径处理
|
|
34
|
+
join(__dirname, '..', '..', '@agile-team', 'robot-cli', 'lib'),
|
|
25
35
|
];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
' 🤖 Robot 项目脚手架工具 v1.0.0\n' +
|
|
32
|
-
' ',
|
|
33
|
-
{
|
|
34
|
-
padding: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
35
|
-
borderStyle: 'round',
|
|
36
|
-
borderColor: 'cyan',
|
|
37
|
-
backgroundColor: 'blackBright'
|
|
36
|
+
|
|
37
|
+
// 查找第一个存在的路径
|
|
38
|
+
for (const libPath of possiblePaths) {
|
|
39
|
+
if (existsSync(libPath)) {
|
|
40
|
+
return libPath;
|
|
38
41
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 如果都找不到,抛出详细错误
|
|
45
|
+
throw new Error(`
|
|
46
|
+
无法找到 lib 目录,已尝试以下路径:
|
|
47
|
+
${possiblePaths.map(p => ` - ${p}`).join('\n')}
|
|
48
|
+
|
|
49
|
+
当前执行路径: ${__dirname}
|
|
50
|
+
工作目录: ${process.cwd()}
|
|
51
|
+
|
|
52
|
+
可能的解决方案:
|
|
53
|
+
1. 重新安装: npm uninstall -g @agile-team/robot-cli && npm install -g @agile-team/robot-cli
|
|
54
|
+
2. 使用 npx: npx @agile-team/robot-cli
|
|
55
|
+
3. 检查包完整性: npm list -g @agile-team/robot-cli
|
|
56
|
+
`);
|
|
44
57
|
}
|
|
45
58
|
|
|
46
|
-
//
|
|
47
|
-
async function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
59
|
+
// 动态导入所需模块
|
|
60
|
+
async function loadModules() {
|
|
61
|
+
try {
|
|
62
|
+
const libPath = resolveLibPath();
|
|
63
|
+
|
|
64
|
+
// 动态导入所有需要的模块
|
|
65
|
+
const [
|
|
66
|
+
{ Command },
|
|
67
|
+
chalk,
|
|
68
|
+
boxen,
|
|
69
|
+
inquirer,
|
|
70
|
+
{ createProject },
|
|
71
|
+
{ clearCache, getCacheInfo, formatSize },
|
|
72
|
+
{ getAllTemplates, searchTemplates, getRecommendedTemplates },
|
|
73
|
+
{ checkNetworkConnection }
|
|
74
|
+
] = await Promise.all([
|
|
75
|
+
import('commander'),
|
|
76
|
+
import('chalk'),
|
|
77
|
+
import('boxen'),
|
|
78
|
+
import('inquirer'),
|
|
79
|
+
import(join(libPath, 'create.js')),
|
|
80
|
+
import(join(libPath, 'cache.js')),
|
|
81
|
+
import(join(libPath, 'templates.js')),
|
|
82
|
+
import(join(libPath, 'utils.js'))
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
Command,
|
|
87
|
+
chalk: chalk.default,
|
|
88
|
+
boxen: boxen.default,
|
|
89
|
+
inquirer: inquirer.default,
|
|
90
|
+
createProject,
|
|
91
|
+
clearCache,
|
|
92
|
+
getCacheInfo,
|
|
93
|
+
formatSize,
|
|
94
|
+
getAllTemplates,
|
|
95
|
+
searchTemplates,
|
|
96
|
+
getRecommendedTemplates,
|
|
97
|
+
checkNetworkConnection
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(`
|
|
101
|
+
❌ 模块加载失败: ${error.message}
|
|
102
|
+
|
|
103
|
+
🔧 诊断信息:
|
|
104
|
+
当前文件: ${__filename}
|
|
105
|
+
执行目录: ${__dirname}
|
|
106
|
+
工作目录: ${process.cwd()}
|
|
107
|
+
Node版本: ${process.version}
|
|
108
|
+
|
|
109
|
+
💡 解决方案:
|
|
110
|
+
1. 完全重装: npm uninstall -g @agile-team/robot-cli && npm install -g @agile-team/robot-cli
|
|
111
|
+
2. 使用npx: npx @agile-team/robot-cli
|
|
112
|
+
3. 联系支持: https://github.com/ChenyCHENYU/robot-cli/issues
|
|
113
|
+
`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
100
116
|
}
|
|
101
117
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
// 主程序
|
|
119
|
+
async function main() {
|
|
120
|
+
try {
|
|
121
|
+
const modules = await loadModules();
|
|
122
|
+
const {
|
|
123
|
+
Command,
|
|
124
|
+
chalk,
|
|
125
|
+
boxen,
|
|
126
|
+
inquirer,
|
|
127
|
+
createProject,
|
|
128
|
+
clearCache,
|
|
129
|
+
getCacheInfo,
|
|
130
|
+
formatSize,
|
|
131
|
+
getAllTemplates,
|
|
132
|
+
searchTemplates,
|
|
133
|
+
getRecommendedTemplates,
|
|
134
|
+
checkNetworkConnection
|
|
135
|
+
} = modules;
|
|
109
136
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
.option('--no-cache', '强制重新下载模板')
|
|
116
|
-
.option('--skip-install', '跳过依赖安装')
|
|
117
|
-
.action(async (projectName, options) => {
|
|
118
|
-
try {
|
|
119
|
-
// 检查网络连接
|
|
120
|
-
if (!options.cache) {
|
|
121
|
-
console.log(chalk.blue('🌐 检查网络连接...'));
|
|
122
|
-
const hasNetwork = await checkNetworkConnection();
|
|
123
|
-
if (!hasNetwork) {
|
|
124
|
-
console.log(chalk.red('❌ 网络连接失败,无法下载模板'));
|
|
125
|
-
console.log(chalk.yellow('💡 请检查网络连接后重试'));
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
137
|
+
const program = new Command();
|
|
138
|
+
|
|
139
|
+
// 现代化欢迎信息
|
|
140
|
+
function showWelcome() {
|
|
141
|
+
console.clear();
|
|
129
142
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
const logoLines = [
|
|
144
|
+
' ██████╗ ██████╗ ██████╗ ██████╗ ████████╗',
|
|
145
|
+
' ██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗╚══██╔══╝',
|
|
146
|
+
' ██████╔╝██║ ██║██████╔╝██║ ██║ ██║ ',
|
|
147
|
+
' ██╔══██╗██║ ██║██╔══██╗██║ ██║ ██║ ',
|
|
148
|
+
' ██║ ██║╚██████╔╝██████╔╝╚██████╔╝ ██║ ',
|
|
149
|
+
' ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ '
|
|
150
|
+
];
|
|
134
151
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
const logo = logoLines.map(line => chalk.cyan(line)).join('\n');
|
|
153
|
+
|
|
154
|
+
const titleBox = boxen(
|
|
155
|
+
logo + '\n\n' +
|
|
156
|
+
' 🤖 Robot 项目脚手架工具 v1.0.3\n' +
|
|
157
|
+
' 兼容 npm/yarn/pnpm/bun',
|
|
158
|
+
{
|
|
159
|
+
padding: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
160
|
+
borderStyle: 'round',
|
|
161
|
+
borderColor: 'cyan',
|
|
162
|
+
backgroundColor: 'blackBright'
|
|
163
|
+
}
|
|
164
|
+
);
|
|
143
165
|
|
|
144
166
|
console.log();
|
|
145
|
-
console.log(
|
|
146
|
-
console.log(chalk.dim(' robot --help'));
|
|
147
|
-
console.log(chalk.dim(' 联系团队技术支持'));
|
|
167
|
+
console.log(titleBox);
|
|
148
168
|
console.log();
|
|
149
|
-
process.exit(1);
|
|
150
169
|
}
|
|
151
|
-
});
|
|
152
170
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.option('-c, --category <category>', '按分类筛选')
|
|
160
|
-
.action(async (options) => {
|
|
161
|
-
try {
|
|
162
|
-
let templates;
|
|
163
|
-
let title;
|
|
171
|
+
// 显示主菜单
|
|
172
|
+
async function showMainMenu() {
|
|
173
|
+
const title = chalk.white.bold('🚀 快速开始');
|
|
174
|
+
|
|
175
|
+
console.log(' ' + title);
|
|
176
|
+
console.log();
|
|
164
177
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
templates = getAllTemplates();
|
|
170
|
-
title = '📋 所有可用模板';
|
|
171
|
-
}
|
|
178
|
+
// 获取统计信息
|
|
179
|
+
const allTemplates = getAllTemplates();
|
|
180
|
+
const templateCount = Object.keys(allTemplates).length;
|
|
181
|
+
const cacheInfo = await getCacheInfo();
|
|
172
182
|
|
|
183
|
+
console.log(chalk.dim(` 📦 可用模板: ${templateCount} 个`));
|
|
184
|
+
console.log(chalk.dim(` 💾 缓存模板: ${cacheInfo.templates.length} 个 (${formatSize(cacheInfo.size)})`));
|
|
173
185
|
console.log();
|
|
174
|
-
console.log(chalk.blue(title));
|
|
175
|
-
console.log(chalk.dim(`共 ${Object.keys(templates).length} 个模板\n`));
|
|
176
186
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
const commands = [
|
|
188
|
+
{
|
|
189
|
+
cmd: 'robot create',
|
|
190
|
+
desc: '交互式创建项目',
|
|
191
|
+
color: 'cyan'
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
cmd: 'robot create <name>',
|
|
195
|
+
desc: '快速创建项目',
|
|
196
|
+
color: 'green'
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
cmd: 'robot list',
|
|
200
|
+
desc: '查看所有可用模板',
|
|
201
|
+
color: 'blue'
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
cmd: 'robot search <keyword>',
|
|
205
|
+
desc: '搜索模板',
|
|
206
|
+
color: 'magenta'
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
cmd: 'robot cache',
|
|
210
|
+
desc: '缓存管理',
|
|
211
|
+
color: 'yellow'
|
|
184
212
|
}
|
|
185
|
-
|
|
186
|
-
});
|
|
213
|
+
];
|
|
187
214
|
|
|
188
|
-
|
|
189
|
-
console.log(chalk
|
|
190
|
-
templates.forEach(template => {
|
|
191
|
-
console.log(` ${chalk.green('●')} ${chalk.bold(template.name)}`);
|
|
192
|
-
console.log(` ${chalk.dim(template.description)}`);
|
|
193
|
-
console.log(` ${chalk.dim('功能: ' + template.features.join(', '))}`);
|
|
194
|
-
console.log(` ${chalk.dim('使用: robot create my-app --template ' + template.key)}`);
|
|
195
|
-
console.log();
|
|
196
|
-
});
|
|
215
|
+
commands.forEach(({ cmd, desc, color }) => {
|
|
216
|
+
console.log(' ' + chalk[color](cmd.padEnd(24)) + chalk.dim(desc));
|
|
197
217
|
});
|
|
198
218
|
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.log(chalk.red('❌ 获取模板列表失败:'), error.message);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// 搜索模板
|
|
205
|
-
program
|
|
206
|
-
.command('search <keyword>')
|
|
207
|
-
.description('搜索模板')
|
|
208
|
-
.action(async (keyword) => {
|
|
209
|
-
try {
|
|
210
|
-
const results = searchTemplates(keyword);
|
|
211
|
-
|
|
212
219
|
console.log();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
console.log(chalk.dim(' • 使用 robot list 查看所有模板'));
|
|
219
|
-
console.log(chalk.dim(' • 使用 robot list --recommended 查看推荐模板'));
|
|
220
|
-
} else {
|
|
221
|
-
console.log(chalk.green(`🔍 找到 ${Object.keys(results).length} 个匹配的模板:`));
|
|
222
|
-
console.log();
|
|
223
|
-
|
|
224
|
-
Object.entries(results).forEach(([key, template]) => {
|
|
225
|
-
console.log(`${chalk.green('●')} ${chalk.bold(template.name)}`);
|
|
226
|
-
console.log(` ${chalk.dim(template.description)}`);
|
|
227
|
-
console.log(` ${chalk.dim('功能: ' + template.features.join(', '))}`);
|
|
228
|
-
console.log(` ${chalk.cyan('robot create my-app --template ' + key)}`);
|
|
229
|
-
console.log();
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
} catch (error) {
|
|
233
|
-
console.log(chalk.red('❌ 搜索失败:'), error.message);
|
|
220
|
+
console.log(chalk.dim(' 示例:'));
|
|
221
|
+
console.log(chalk.dim(' robot create my-vue-admin'));
|
|
222
|
+
console.log(chalk.dim(' robot search vue'));
|
|
223
|
+
console.log(chalk.dim(' robot create my-app --template robot-admin'));
|
|
224
|
+
console.log();
|
|
234
225
|
}
|
|
235
|
-
});
|
|
236
226
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
227
|
+
program
|
|
228
|
+
.name('robot')
|
|
229
|
+
.description('🤖 Robot 项目脚手架工具 - @agile-team/robot-cli')
|
|
230
|
+
.version('1.0.3')
|
|
231
|
+
.hook('preAction', () => {
|
|
232
|
+
showWelcome();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// 创建项目命令
|
|
236
|
+
program
|
|
237
|
+
.command('create [project-name]')
|
|
238
|
+
.description('创建新项目')
|
|
239
|
+
.option('-t, --template <template>', '指定模板类型')
|
|
240
|
+
.option('--no-cache', '强制重新下载模板')
|
|
241
|
+
.option('--skip-install', '跳过依赖安装')
|
|
242
|
+
.action(async (projectName, options) => {
|
|
243
|
+
try {
|
|
244
|
+
// 检查网络连接
|
|
245
|
+
if (!options.cache) {
|
|
246
|
+
console.log(chalk.blue('🌐 检查网络连接...'));
|
|
247
|
+
const hasNetwork = await checkNetworkConnection();
|
|
248
|
+
if (!hasNetwork) {
|
|
249
|
+
console.log(chalk.red('❌ 网络连接失败,无法下载模板'));
|
|
250
|
+
console.log(chalk.yellow('💡 请检查网络连接后重试'));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
252
253
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
await clearCache();
|
|
254
|
+
|
|
255
|
+
await createProject(projectName, options);
|
|
256
|
+
} catch (error) {
|
|
257
257
|
console.log();
|
|
258
|
-
console.log(chalk.
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
console.log(chalk.red('✗'), chalk.red.bold('创建失败'));
|
|
259
|
+
|
|
260
|
+
// 根据错误类型提供不同的建议
|
|
261
|
+
if (error.message.includes('网络')) {
|
|
262
|
+
console.log(' ' + chalk.dim('网络相关问题,请检查网络连接'));
|
|
263
|
+
} else if (error.message.includes('权限')) {
|
|
264
|
+
console.log(' ' + chalk.dim('权限问题,请检查文件夹权限'));
|
|
265
|
+
} else {
|
|
266
|
+
console.log(' ' + chalk.dim(error.message));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log();
|
|
270
|
+
console.log(chalk.blue('💡 获取帮助:'));
|
|
271
|
+
console.log(chalk.dim(' robot --help'));
|
|
272
|
+
console.log(chalk.dim(' https://github.com/ChenyCHENYU/robot-cli/issues'));
|
|
273
|
+
console.log();
|
|
274
|
+
process.exit(1);
|
|
261
275
|
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// 列出所有模板
|
|
279
|
+
program
|
|
280
|
+
.command('list')
|
|
281
|
+
.alias('ls')
|
|
282
|
+
.description('列出所有可用模板')
|
|
283
|
+
.option('-r, --recommended', '只显示推荐模板')
|
|
284
|
+
.option('-c, --category <category>', '按分类筛选')
|
|
285
|
+
.action(async (options) => {
|
|
286
|
+
try {
|
|
287
|
+
let templates;
|
|
288
|
+
let title;
|
|
289
|
+
|
|
290
|
+
if (options.recommended) {
|
|
291
|
+
templates = getRecommendedTemplates();
|
|
292
|
+
title = '🎯 推荐模板';
|
|
293
|
+
} else {
|
|
294
|
+
templates = getAllTemplates();
|
|
295
|
+
title = '📋 所有可用模板';
|
|
296
|
+
}
|
|
297
|
+
|
|
276
298
|
console.log();
|
|
277
|
-
console.log(chalk.blue(
|
|
299
|
+
console.log(chalk.blue(title));
|
|
300
|
+
console.log(chalk.dim(`共 ${Object.keys(templates).length} 个模板\n`));
|
|
278
301
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
302
|
+
// 按分类显示
|
|
303
|
+
const categories = {};
|
|
304
|
+
Object.entries(templates).forEach(([key, template]) => {
|
|
305
|
+
// 简单分类逻辑,根据模板名称前缀
|
|
306
|
+
const category = key.split('-')[0];
|
|
307
|
+
if (!categories[category]) {
|
|
308
|
+
categories[category] = [];
|
|
309
|
+
}
|
|
310
|
+
categories[category].push({ key, ...template });
|
|
283
311
|
});
|
|
312
|
+
|
|
313
|
+
Object.entries(categories).forEach(([category, templates]) => {
|
|
314
|
+
console.log(chalk.cyan(`${category.toUpperCase()} 相关:`));
|
|
315
|
+
templates.forEach(template => {
|
|
316
|
+
console.log(` ${chalk.green('●')} ${chalk.bold(template.name)}`);
|
|
317
|
+
console.log(` ${chalk.dim(template.description)}`);
|
|
318
|
+
console.log(` ${chalk.dim('功能: ' + template.features.join(', '))}`);
|
|
319
|
+
console.log(` ${chalk.dim('使用: robot create my-app --template ' + template.key)}`);
|
|
320
|
+
console.log();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.log(chalk.red('❌ 获取模板列表失败:'), error.message);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// 搜索模板
|
|
330
|
+
program
|
|
331
|
+
.command('search <keyword>')
|
|
332
|
+
.description('搜索模板')
|
|
333
|
+
.action(async (keyword) => {
|
|
334
|
+
try {
|
|
335
|
+
const results = searchTemplates(keyword);
|
|
336
|
+
|
|
337
|
+
console.log();
|
|
338
|
+
if (Object.keys(results).length === 0) {
|
|
339
|
+
console.log(chalk.yellow('🔍 没有找到匹配的模板'));
|
|
340
|
+
console.log();
|
|
341
|
+
console.log(chalk.blue('💡 建议:'));
|
|
342
|
+
console.log(chalk.dim(' • 尝试其他关键词'));
|
|
343
|
+
console.log(chalk.dim(' • 使用 robot list 查看所有模板'));
|
|
344
|
+
console.log(chalk.dim(' • 使用 robot list --recommended 查看推荐模板'));
|
|
345
|
+
} else {
|
|
346
|
+
console.log(chalk.green(`🔍 找到 ${Object.keys(results).length} 个匹配的模板:`));
|
|
347
|
+
console.log();
|
|
348
|
+
|
|
349
|
+
Object.entries(results).forEach(([key, template]) => {
|
|
350
|
+
console.log(`${chalk.green('●')} ${chalk.bold(template.name)}`);
|
|
351
|
+
console.log(` ${chalk.dim(template.description)}`);
|
|
352
|
+
console.log(` ${chalk.dim('功能: ' + template.features.join(', '))}`);
|
|
353
|
+
console.log(` ${chalk.cyan('robot create my-app --template ' + key)}`);
|
|
354
|
+
console.log();
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.log(chalk.red('❌ 搜索失败:'), error.message);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// 缓存管理
|
|
363
|
+
program
|
|
364
|
+
.command('cache')
|
|
365
|
+
.description('缓存管理')
|
|
366
|
+
.option('-c, --clear', '清除所有缓存')
|
|
367
|
+
.option('-i, --info', '显示缓存信息')
|
|
368
|
+
.action(async (options) => {
|
|
369
|
+
try {
|
|
370
|
+
if (options.clear) {
|
|
371
|
+
const { confirmed } = await inquirer.prompt([
|
|
372
|
+
{
|
|
373
|
+
type: 'confirm',
|
|
374
|
+
name: 'confirmed',
|
|
375
|
+
message: '确认清除所有模板缓存?',
|
|
376
|
+
default: false
|
|
377
|
+
}
|
|
378
|
+
]);
|
|
379
|
+
|
|
380
|
+
if (confirmed) {
|
|
381
|
+
await clearCache();
|
|
382
|
+
console.log();
|
|
383
|
+
console.log(chalk.green('✓'), chalk.green.bold('缓存清除成功'));
|
|
384
|
+
} else {
|
|
385
|
+
console.log(chalk.yellow('❌ 取消清除'));
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
// 显示缓存信息
|
|
389
|
+
const cacheInfo = await getCacheInfo();
|
|
390
|
+
|
|
391
|
+
console.log();
|
|
392
|
+
console.log(chalk.blue('💾 缓存信息:'));
|
|
393
|
+
console.log();
|
|
394
|
+
|
|
395
|
+
if (!cacheInfo.exists || cacheInfo.templates.length === 0) {
|
|
396
|
+
console.log(chalk.dim(' 暂无缓存模板'));
|
|
397
|
+
} else {
|
|
398
|
+
console.log(` 缓存目录: ${chalk.dim(cacheInfo.path)}`);
|
|
399
|
+
console.log(` 模板数量: ${chalk.cyan(cacheInfo.templates.length)} 个`);
|
|
400
|
+
console.log(` 总大小: ${chalk.cyan(formatSize(cacheInfo.size))}`);
|
|
401
|
+
console.log();
|
|
402
|
+
console.log(chalk.blue(' 缓存的模板:'));
|
|
403
|
+
|
|
404
|
+
cacheInfo.templates.forEach(template => {
|
|
405
|
+
const modifiedTime = template.modifiedTime.toLocaleDateString();
|
|
406
|
+
console.log(` ${chalk.green('●')} ${template.name}`);
|
|
407
|
+
console.log(` 大小: ${formatSize(template.size)} 更新: ${modifiedTime}`);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
console.log();
|
|
412
|
+
console.log(chalk.dim(' 使用 robot cache --clear 清除缓存'));
|
|
413
|
+
}
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.log(chalk.red('❌ 缓存操作失败:'), error.message);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// 清除缓存命令 (向后兼容)
|
|
420
|
+
program
|
|
421
|
+
.command('clear-cache')
|
|
422
|
+
.description('清除模板缓存')
|
|
423
|
+
.action(async () => {
|
|
424
|
+
try {
|
|
425
|
+
await clearCache();
|
|
426
|
+
console.log();
|
|
427
|
+
console.log(chalk.green('✓'), chalk.green.bold('缓存清除成功'));
|
|
428
|
+
console.log();
|
|
429
|
+
} catch (error) {
|
|
430
|
+
console.log();
|
|
431
|
+
console.log(chalk.red('✗'), chalk.red.bold('清除缓存失败'));
|
|
432
|
+
console.log(' ' + chalk.dim(error.message));
|
|
433
|
+
console.log();
|
|
284
434
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// 如果没有参数,显示主菜单
|
|
438
|
+
if (process.argv.length === 2) {
|
|
439
|
+
showWelcome();
|
|
440
|
+
await showMainMenu();
|
|
441
|
+
process.exit(0);
|
|
291
442
|
}
|
|
292
|
-
});
|
|
293
443
|
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
.command('clear-cache')
|
|
297
|
-
.description('清除模板缓存')
|
|
298
|
-
.action(async () => {
|
|
299
|
-
try {
|
|
300
|
-
await clearCache();
|
|
301
|
-
console.log();
|
|
302
|
-
console.log(chalk.green('✓'), chalk.green.bold('缓存清除成功'));
|
|
444
|
+
// 全局错误处理
|
|
445
|
+
process.on('uncaughtException', (error) => {
|
|
303
446
|
console.log();
|
|
304
|
-
|
|
447
|
+
console.log(chalk.red('💥 程序发生未预期的错误:'));
|
|
448
|
+
console.log(chalk.dim(error.message));
|
|
305
449
|
console.log();
|
|
306
|
-
console.log(chalk.
|
|
307
|
-
console.log(
|
|
450
|
+
console.log(chalk.blue('💡 建议:'));
|
|
451
|
+
console.log(chalk.dim(' • 重启终端重试'));
|
|
452
|
+
console.log(chalk.dim(' • 检查网络连接'));
|
|
453
|
+
console.log(chalk.dim(' • 重新安装: npm install -g @agile-team/robot-cli'));
|
|
454
|
+
console.log(chalk.dim(' • 联系技术支持: https://github.com/ChenyCHENYU/robot-cli/issues'));
|
|
308
455
|
console.log();
|
|
309
|
-
|
|
310
|
-
|
|
456
|
+
process.exit(1);
|
|
457
|
+
});
|
|
311
458
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
459
|
+
process.on('unhandledRejection', (error) => {
|
|
460
|
+
console.log();
|
|
461
|
+
console.log(chalk.red('💥 程序发生未处理的异步错误:'));
|
|
462
|
+
console.log(chalk.dim(error.message));
|
|
463
|
+
console.log();
|
|
464
|
+
process.exit(1);
|
|
465
|
+
});
|
|
318
466
|
|
|
319
|
-
|
|
320
|
-
process.on('uncaughtException', (error) => {
|
|
321
|
-
console.log();
|
|
322
|
-
console.log(chalk.red('💥 程序发生未预期的错误:'));
|
|
323
|
-
console.log(chalk.dim(error.message));
|
|
324
|
-
console.log();
|
|
325
|
-
console.log(chalk.blue('💡 建议:'));
|
|
326
|
-
console.log(chalk.dim(' • 重启终端重试'));
|
|
327
|
-
console.log(chalk.dim(' • 检查网络连接'));
|
|
328
|
-
console.log(chalk.dim(' • 联系技术支持'));
|
|
329
|
-
console.log();
|
|
330
|
-
process.exit(1);
|
|
331
|
-
});
|
|
467
|
+
program.parse();
|
|
332
468
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
process.exit(1);
|
|
339
|
-
});
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error('启动失败:', error.message);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
340
474
|
|
|
341
|
-
|
|
475
|
+
// 启动程序
|
|
476
|
+
main();
|
package/lib/utils.js
CHANGED
|
@@ -1,58 +1,155 @@
|
|
|
1
|
-
// lib/utils.js -
|
|
1
|
+
// lib/utils.js - 增强版本
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { execSync } from 'child_process';
|
|
5
4
|
import chalk from 'chalk';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import fetch from 'node-fetch';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 检测当前使用的包管理器
|
|
10
|
+
*/
|
|
11
|
+
export function detectPackageManager() {
|
|
12
|
+
try {
|
|
13
|
+
// 检查各种包管理器的可用性
|
|
14
|
+
const managers = [];
|
|
15
|
+
|
|
16
|
+
// 检查 bun
|
|
17
|
+
try {
|
|
18
|
+
execSync('bun --version', { stdio: 'ignore' });
|
|
19
|
+
managers.push('bun');
|
|
20
|
+
} catch {}
|
|
21
|
+
|
|
22
|
+
// 检查 pnpm
|
|
23
|
+
try {
|
|
24
|
+
execSync('pnpm --version', { stdio: 'ignore' });
|
|
25
|
+
managers.push('pnpm');
|
|
26
|
+
} catch {}
|
|
27
|
+
|
|
28
|
+
// 检查 yarn
|
|
29
|
+
try {
|
|
30
|
+
execSync('yarn --version', { stdio: 'ignore' });
|
|
31
|
+
managers.push('yarn');
|
|
32
|
+
} catch {}
|
|
33
|
+
|
|
34
|
+
// npm 通常总是可用的
|
|
35
|
+
try {
|
|
36
|
+
execSync('npm --version', { stdio: 'ignore' });
|
|
37
|
+
managers.push('npm');
|
|
38
|
+
} catch {}
|
|
39
|
+
|
|
40
|
+
return managers;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return ['npm']; // 默认返回 npm
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 获取当前CLI的安装信息
|
|
48
|
+
*/
|
|
49
|
+
export function getInstallationInfo() {
|
|
50
|
+
try {
|
|
51
|
+
const packagePath = process.env.npm_config_global
|
|
52
|
+
? path.join(process.env.npm_config_global, 'node_modules', '@agile-team', 'robot-cli')
|
|
53
|
+
: null;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
binPath: process.argv[1],
|
|
57
|
+
packagePath,
|
|
58
|
+
nodeVersion: process.version,
|
|
59
|
+
platform: process.platform,
|
|
60
|
+
arch: process.arch
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return {
|
|
64
|
+
binPath: process.argv[1],
|
|
65
|
+
packagePath: null,
|
|
66
|
+
nodeVersion: process.version,
|
|
67
|
+
platform: process.platform,
|
|
68
|
+
arch: process.arch
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 诊断安装问题
|
|
75
|
+
*/
|
|
76
|
+
export function diagnoseInstallation() {
|
|
77
|
+
const info = getInstallationInfo();
|
|
78
|
+
const managers = detectPackageManager();
|
|
79
|
+
|
|
80
|
+
console.log(chalk.blue('🔍 安装诊断信息:'));
|
|
81
|
+
console.log();
|
|
82
|
+
console.log(` 执行文件: ${chalk.cyan(info.binPath)}`);
|
|
83
|
+
console.log(` Node版本: ${chalk.cyan(info.nodeVersion)}`);
|
|
84
|
+
console.log(` 系统平台: ${chalk.cyan(info.platform)} (${info.arch})`);
|
|
85
|
+
console.log(` 可用包管理器: ${chalk.cyan(managers.join(', '))}`);
|
|
86
|
+
|
|
87
|
+
if (info.packagePath) {
|
|
88
|
+
console.log(` 包路径: ${chalk.cyan(info.packagePath)}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log();
|
|
92
|
+
console.log(chalk.blue('💡 推荐的安装方式:'));
|
|
93
|
+
|
|
94
|
+
if (managers.includes('bun')) {
|
|
95
|
+
console.log(chalk.green(' bun install -g @agile-team/robot-cli'));
|
|
96
|
+
}
|
|
97
|
+
if (managers.includes('pnpm')) {
|
|
98
|
+
console.log(chalk.green(' pnpm install -g @agile-team/robot-cli'));
|
|
99
|
+
}
|
|
100
|
+
if (managers.includes('yarn')) {
|
|
101
|
+
console.log(chalk.green(' yarn global add @agile-team/robot-cli'));
|
|
102
|
+
}
|
|
103
|
+
if (managers.includes('npm')) {
|
|
104
|
+
console.log(chalk.green(' npm install -g @agile-team/robot-cli'));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(chalk.yellow('⚠️ 建议只使用一种包管理器来避免冲突'));
|
|
109
|
+
}
|
|
6
110
|
|
|
7
111
|
/**
|
|
8
112
|
* 验证项目名称
|
|
9
113
|
*/
|
|
10
114
|
export function validateProjectName(name) {
|
|
11
115
|
const errors = [];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!name || !name.trim()) {
|
|
116
|
+
|
|
117
|
+
if (!name || typeof name !== 'string') {
|
|
15
118
|
errors.push('项目名称不能为空');
|
|
16
119
|
return { valid: false, errors };
|
|
17
120
|
}
|
|
18
|
-
|
|
121
|
+
|
|
19
122
|
const trimmedName = name.trim();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (trimmedName.length < 1) {
|
|
123
|
+
|
|
124
|
+
if (trimmedName.length === 0) {
|
|
23
125
|
errors.push('项目名称不能为空');
|
|
24
126
|
}
|
|
25
|
-
|
|
127
|
+
|
|
26
128
|
if (trimmedName.length > 214) {
|
|
27
|
-
errors.push('
|
|
129
|
+
errors.push('项目名称不能超过214个字符');
|
|
28
130
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
errors.push('项目名称只能包含字母、数字、连字符、下划线、@和点');
|
|
131
|
+
|
|
132
|
+
if (trimmedName.toLowerCase() !== trimmedName) {
|
|
133
|
+
errors.push('项目名称只能包含小写字母');
|
|
33
134
|
}
|
|
34
|
-
|
|
35
|
-
// 检查开头字符
|
|
135
|
+
|
|
36
136
|
if (/^[._]/.test(trimmedName)) {
|
|
37
|
-
errors.push('
|
|
137
|
+
errors.push('项目名称不能以 "." 或 "_" 开头');
|
|
38
138
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
errors.push('项目名称不能包含空格');
|
|
139
|
+
|
|
140
|
+
if (!/^[a-z0-9._-]+$/.test(trimmedName)) {
|
|
141
|
+
errors.push('项目名称只能包含字母、数字、点、下划线和短横线');
|
|
43
142
|
}
|
|
44
|
-
|
|
45
|
-
// 检查保留字
|
|
143
|
+
|
|
46
144
|
const reservedNames = [
|
|
47
|
-
'node_modules', 'favicon.ico', '
|
|
48
|
-
'
|
|
49
|
-
'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9'
|
|
145
|
+
'node_modules', 'favicon.ico', '.git', '.env', 'package.json',
|
|
146
|
+
'npm', 'yarn', 'pnpm', 'bun', 'robot'
|
|
50
147
|
];
|
|
51
|
-
|
|
52
|
-
if (reservedNames.includes(trimmedName
|
|
53
|
-
errors.push(`"${trimmedName}"
|
|
148
|
+
|
|
149
|
+
if (reservedNames.includes(trimmedName)) {
|
|
150
|
+
errors.push(`"${trimmedName}" 是保留名称,请使用其他名称`);
|
|
54
151
|
}
|
|
55
|
-
|
|
152
|
+
|
|
56
153
|
return {
|
|
57
154
|
valid: errors.length === 0,
|
|
58
155
|
errors
|
|
@@ -63,208 +160,79 @@ export function validateProjectName(name) {
|
|
|
63
160
|
* 复制模板文件
|
|
64
161
|
*/
|
|
65
162
|
export async function copyTemplate(sourcePath, targetPath) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await fs.ensureDir(targetPath);
|
|
69
|
-
|
|
70
|
-
// 获取源目录中的所有文件和文件夹
|
|
71
|
-
const items = await fs.readdir(sourcePath);
|
|
72
|
-
|
|
73
|
-
for (const item of items) {
|
|
74
|
-
const sourceItemPath = path.join(sourcePath, item);
|
|
75
|
-
const targetItemPath = path.join(targetPath, item);
|
|
76
|
-
|
|
77
|
-
// 跳过不需要的文件
|
|
78
|
-
if (shouldSkipFile(item)) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const stats = await fs.stat(sourceItemPath);
|
|
83
|
-
|
|
84
|
-
if (stats.isDirectory()) {
|
|
85
|
-
// 递归复制目录
|
|
86
|
-
await copyTemplate(sourceItemPath, targetItemPath);
|
|
87
|
-
} else {
|
|
88
|
-
// 复制文件
|
|
89
|
-
await fs.copy(sourceItemPath, targetItemPath);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
} catch (error) {
|
|
93
|
-
throw new Error(`复制模板文件失败: ${error.message}`);
|
|
163
|
+
if (!fs.existsSync(sourcePath)) {
|
|
164
|
+
throw new Error(`源路径不存在: ${sourcePath}`);
|
|
94
165
|
}
|
|
95
|
-
}
|
|
96
166
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'build',
|
|
111
|
-
'*.log',
|
|
112
|
-
'.env.local',
|
|
113
|
-
'.env.*.local'
|
|
114
|
-
];
|
|
115
|
-
|
|
116
|
-
return skipPatterns.some(pattern => {
|
|
117
|
-
if (pattern.includes('*')) {
|
|
118
|
-
// 简单的通配符匹配
|
|
119
|
-
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
120
|
-
return regex.test(fileName);
|
|
167
|
+
await fs.ensureDir(targetPath);
|
|
168
|
+
await fs.copy(sourcePath, targetPath, {
|
|
169
|
+
filter: (src) => {
|
|
170
|
+
const basename = path.basename(src);
|
|
171
|
+
// 排除不需要的文件
|
|
172
|
+
return ![
|
|
173
|
+
'.git',
|
|
174
|
+
'node_modules',
|
|
175
|
+
'.DS_Store',
|
|
176
|
+
'Thumbs.db',
|
|
177
|
+
'.vscode',
|
|
178
|
+
'.idea'
|
|
179
|
+
].includes(basename);
|
|
121
180
|
}
|
|
122
|
-
return fileName === pattern;
|
|
123
181
|
});
|
|
124
182
|
}
|
|
125
183
|
|
|
126
184
|
/**
|
|
127
|
-
*
|
|
185
|
+
* 安装依赖
|
|
128
186
|
*/
|
|
129
|
-
export async function installDependencies(projectPath, spinner, packageManager) {
|
|
187
|
+
export async function installDependencies(projectPath, spinner, packageManager = 'npm') {
|
|
130
188
|
const originalCwd = process.cwd();
|
|
131
|
-
|
|
189
|
+
|
|
132
190
|
try {
|
|
133
191
|
process.chdir(projectPath);
|
|
134
|
-
|
|
135
|
-
// 如果没有指定包管理器,自动检测(优先bun和pnpm)
|
|
136
|
-
const pm = packageManager || detectPackageManager(projectPath);
|
|
137
|
-
|
|
138
|
-
if (spinner) {
|
|
139
|
-
spinner.text = `使用 ${pm} 安装依赖...`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 安装依赖
|
|
143
|
-
const installCommand = getInstallCommand(pm);
|
|
144
|
-
|
|
145
|
-
execSync(installCommand, {
|
|
146
|
-
stdio: 'pipe', // 不显示安装输出
|
|
147
|
-
timeout: 300000 // 5分钟超时
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
} catch (error) {
|
|
151
|
-
throw new Error(`安装依赖失败: ${error.message}`);
|
|
152
|
-
} finally {
|
|
153
|
-
process.chdir(originalCwd);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
192
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (fs.existsSync(path.join(projectPath, 'pnpm-lock.yaml'))) {
|
|
167
|
-
return 'pnpm';
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 2. 检查全局是否安装了bun(最优先)
|
|
171
|
-
try {
|
|
172
|
-
execSync('bun --version', { stdio: 'ignore' });
|
|
173
|
-
return 'bun';
|
|
174
|
-
} catch {
|
|
175
|
-
// bun 不可用
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 3. 检查全局是否安装了pnpm(次优先)
|
|
179
|
-
try {
|
|
180
|
-
execSync('pnpm --version', { stdio: 'ignore' });
|
|
181
|
-
return 'pnpm';
|
|
182
|
-
} catch {
|
|
183
|
-
// pnpm 不可用
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// 4. 检查yarn(兼容性)
|
|
187
|
-
if (fs.existsSync(path.join(projectPath, 'yarn.lock'))) {
|
|
188
|
-
return 'yarn';
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
execSync('yarn --version', { stdio: 'ignore' });
|
|
193
|
-
return 'yarn';
|
|
194
|
-
} catch {
|
|
195
|
-
// yarn 不可用
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// 5. 默认使用npm(最后选择)
|
|
199
|
-
return 'npm';
|
|
200
|
-
}
|
|
193
|
+
// 检查 package.json 是否存在
|
|
194
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
195
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
196
|
+
if (spinner) {
|
|
197
|
+
spinner.text = '⚠️ 跳过依赖安装 (无 package.json)';
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
yarn: 'yarn install',
|
|
210
|
-
npm: 'npm install'
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
return commands[packageManager] || commands.npm;
|
|
214
|
-
}
|
|
202
|
+
// 根据包管理器选择安装命令
|
|
203
|
+
const installCommands = {
|
|
204
|
+
bun: 'bun install',
|
|
205
|
+
pnpm: 'pnpm install',
|
|
206
|
+
yarn: 'yarn install',
|
|
207
|
+
npm: 'npm install'
|
|
208
|
+
};
|
|
215
209
|
|
|
216
|
-
|
|
217
|
-
* 检查命令是否存在
|
|
218
|
-
*/
|
|
219
|
-
export function commandExists(command) {
|
|
220
|
-
try {
|
|
221
|
-
execSync(`${command} --version`, { stdio: 'ignore' });
|
|
222
|
-
return true;
|
|
223
|
-
} catch {
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
210
|
+
const command = installCommands[packageManager] || 'npm install';
|
|
227
211
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
export function formatFileSize(bytes) {
|
|
232
|
-
if (bytes === 0) return '0 B';
|
|
233
|
-
|
|
234
|
-
const k = 1024;
|
|
235
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
236
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
237
|
-
|
|
238
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
239
|
-
}
|
|
212
|
+
if (spinner) {
|
|
213
|
+
spinner.text = `📦 使用 ${packageManager} 安装依赖...`;
|
|
214
|
+
}
|
|
240
215
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
let size = 0;
|
|
216
|
+
execSync(command, {
|
|
217
|
+
stdio: 'ignore',
|
|
218
|
+
timeout: 300000 // 5分钟超时
|
|
219
|
+
});
|
|
246
220
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
for (const item of items) {
|
|
252
|
-
const itemPath = path.join(currentPath, item);
|
|
253
|
-
const stats = await fs.stat(itemPath);
|
|
254
|
-
|
|
255
|
-
if (stats.isDirectory()) {
|
|
256
|
-
await calculateSize(itemPath);
|
|
257
|
-
} else {
|
|
258
|
-
size += stats.size;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
} catch (error) {
|
|
262
|
-
// 忽略权限错误等
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (spinner) {
|
|
223
|
+
spinner.text = `⚠️ 依赖安装失败,请手动安装`;
|
|
263
224
|
}
|
|
225
|
+
console.log();
|
|
226
|
+
console.log(chalk.yellow('⚠️ 自动安装依赖失败'));
|
|
227
|
+
console.log(chalk.dim(` 错误: ${error.message}`));
|
|
228
|
+
console.log();
|
|
229
|
+
console.log(chalk.blue('💡 请手动安装:'));
|
|
230
|
+
console.log(chalk.cyan(` cd ${path.basename(projectPath)}`));
|
|
231
|
+
console.log(chalk.cyan(` ${packageManager} install`));
|
|
232
|
+
console.log();
|
|
233
|
+
} finally {
|
|
234
|
+
process.chdir(originalCwd);
|
|
264
235
|
}
|
|
265
|
-
|
|
266
|
-
await calculateSize(folderPath);
|
|
267
|
-
return size;
|
|
268
236
|
}
|
|
269
237
|
|
|
270
238
|
/**
|
|
@@ -272,19 +240,27 @@ export async function getFolderSize(folderPath) {
|
|
|
272
240
|
*/
|
|
273
241
|
export async function checkNetworkConnection() {
|
|
274
242
|
try {
|
|
275
|
-
const
|
|
276
|
-
const response = await fetch('https://github.com', {
|
|
243
|
+
const response = await fetch('https://api.github.com', {
|
|
277
244
|
method: 'HEAD',
|
|
278
245
|
timeout: 5000
|
|
279
246
|
});
|
|
280
247
|
return response.ok;
|
|
281
|
-
} catch {
|
|
282
|
-
|
|
248
|
+
} catch (error) {
|
|
249
|
+
// 尝试备用检查
|
|
250
|
+
try {
|
|
251
|
+
const response = await fetch('https://www.npmjs.com', {
|
|
252
|
+
method: 'HEAD',
|
|
253
|
+
timeout: 5000
|
|
254
|
+
});
|
|
255
|
+
return response.ok;
|
|
256
|
+
} catch (backupError) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
283
259
|
}
|
|
284
260
|
}
|
|
285
261
|
|
|
286
262
|
/**
|
|
287
|
-
*
|
|
263
|
+
* 生成项目统计
|
|
288
264
|
*/
|
|
289
265
|
export async function generateProjectStats(projectPath) {
|
|
290
266
|
try {
|
|
@@ -292,35 +268,35 @@ export async function generateProjectStats(projectPath) {
|
|
|
292
268
|
files: 0,
|
|
293
269
|
directories: 0,
|
|
294
270
|
size: 0,
|
|
295
|
-
|
|
271
|
+
fileTypes: {}
|
|
296
272
|
};
|
|
297
273
|
|
|
298
|
-
async function
|
|
299
|
-
const items = await fs.readdir(
|
|
300
|
-
|
|
274
|
+
async function walkDir(dirPath) {
|
|
275
|
+
const items = await fs.readdir(dirPath);
|
|
276
|
+
|
|
301
277
|
for (const item of items) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
278
|
+
const itemPath = path.join(dirPath, item);
|
|
279
|
+
const stat = await fs.stat(itemPath);
|
|
280
|
+
|
|
281
|
+
if (stat.isDirectory()) {
|
|
282
|
+
// 跳过 node_modules 等目录
|
|
283
|
+
if (!['node_modules', '.git', '.DS_Store'].includes(item)) {
|
|
284
|
+
stats.directories++;
|
|
285
|
+
await walkDir(itemPath);
|
|
286
|
+
}
|
|
310
287
|
} else {
|
|
311
288
|
stats.files++;
|
|
312
|
-
stats.size +=
|
|
313
|
-
|
|
314
|
-
// 统计文件类型
|
|
289
|
+
stats.size += stat.size;
|
|
290
|
+
|
|
315
291
|
const ext = path.extname(item).toLowerCase();
|
|
316
292
|
if (ext) {
|
|
317
|
-
stats.
|
|
293
|
+
stats.fileTypes[ext] = (stats.fileTypes[ext] || 0) + 1;
|
|
318
294
|
}
|
|
319
295
|
}
|
|
320
296
|
}
|
|
321
297
|
}
|
|
322
298
|
|
|
323
|
-
await
|
|
299
|
+
await walkDir(projectPath);
|
|
324
300
|
return stats;
|
|
325
301
|
} catch (error) {
|
|
326
302
|
return null;
|
|
@@ -328,17 +304,37 @@ export async function generateProjectStats(projectPath) {
|
|
|
328
304
|
}
|
|
329
305
|
|
|
330
306
|
/**
|
|
331
|
-
*
|
|
307
|
+
* 打印项目统计
|
|
332
308
|
*/
|
|
333
309
|
export function printProjectStats(stats) {
|
|
334
310
|
if (!stats) return;
|
|
335
|
-
|
|
311
|
+
|
|
336
312
|
console.log(chalk.blue('📊 项目统计:'));
|
|
337
|
-
console.log(` 文件数量: ${chalk.cyan(stats.files)}
|
|
338
|
-
console.log(` 目录数量: ${chalk.cyan(stats.directories)}
|
|
339
|
-
console.log(`
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
313
|
+
console.log(` 文件数量: ${chalk.cyan(stats.files)} 个`);
|
|
314
|
+
console.log(` 目录数量: ${chalk.cyan(stats.directories)} 个`);
|
|
315
|
+
console.log(` 项目大小: ${chalk.cyan(formatBytes(stats.size))}`);
|
|
316
|
+
|
|
317
|
+
const topTypes = Object.entries(stats.fileTypes)
|
|
318
|
+
.sort(([,a], [,b]) => b - a)
|
|
319
|
+
.slice(0, 5);
|
|
320
|
+
|
|
321
|
+
if (topTypes.length > 0) {
|
|
322
|
+
console.log(' 主要文件类型:');
|
|
323
|
+
topTypes.forEach(([ext, count]) => {
|
|
324
|
+
console.log(` ${ext}: ${chalk.cyan(count)} 个`);
|
|
325
|
+
});
|
|
343
326
|
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 格式化字节大小
|
|
331
|
+
*/
|
|
332
|
+
function formatBytes(bytes) {
|
|
333
|
+
if (bytes === 0) return '0 B';
|
|
334
|
+
|
|
335
|
+
const k = 1024;
|
|
336
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
337
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
338
|
+
|
|
339
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
344
340
|
}
|