@agile-team/robot-cli 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -21
- package/bin/index.js +14 -128
- package/lib/create.js +30 -36
- package/lib/download.js +31 -94
- package/lib/utils.js +86 -92
- package/package.json +4 -6
- package/lib/cache.js +0 -120
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- **🎯 智能模板分类** - 推荐模板、分类浏览、关键词搜索
|
|
10
10
|
- **⚡ 极速项目创建** - 一条命令创建完整项目架构
|
|
11
11
|
- **📦 现代包管理器** - 优先推荐 bun/pnpm,智能检测最佳选择
|
|
12
|
-
- **🌐
|
|
12
|
+
- **🌐 总是最新版本** - 每次创建都下载最新模板,无缓存困扰
|
|
13
13
|
- **🎨 友好用户界面** - 现代化终端交互,清晰进度提示
|
|
14
14
|
|
|
15
15
|
## 🚀 快速开始
|
|
@@ -70,9 +70,6 @@ robot create my-project --template robot-admin
|
|
|
70
70
|
|
|
71
71
|
# 跳过依赖安装
|
|
72
72
|
robot create my-project --skip-install
|
|
73
|
-
|
|
74
|
-
# 强制重新下载
|
|
75
|
-
robot create my-project --no-cache
|
|
76
73
|
```
|
|
77
74
|
|
|
78
75
|
### 模板管理
|
|
@@ -88,15 +85,6 @@ robot search vue
|
|
|
88
85
|
robot search admin
|
|
89
86
|
```
|
|
90
87
|
|
|
91
|
-
### 缓存管理
|
|
92
|
-
```bash
|
|
93
|
-
# 查看缓存信息
|
|
94
|
-
robot cache
|
|
95
|
-
|
|
96
|
-
# 清除缓存
|
|
97
|
-
robot cache --clear
|
|
98
|
-
```
|
|
99
|
-
|
|
100
88
|
## 📦 包管理器优先级
|
|
101
89
|
|
|
102
90
|
Robot CLI 智能选择最佳包管理器:
|
|
@@ -106,6 +94,15 @@ Robot CLI 智能选择最佳包管理器:
|
|
|
106
94
|
3. **yarn** ⚖️ - 兼容现有项目
|
|
107
95
|
4. **npm** ⚖️ - Node.js默认
|
|
108
96
|
|
|
97
|
+
## 🌐 为什么选择"总是最新"
|
|
98
|
+
|
|
99
|
+
Robot CLI 不使用本地缓存,每次创建项目都会下载最新版本的模板:
|
|
100
|
+
|
|
101
|
+
- ✅ **确保最新**: 总是获得最新的代码和依赖
|
|
102
|
+
- ✅ **避免冲突**: 无需担心缓存过期或版本不一致
|
|
103
|
+
- ✅ **简化维护**: 用户无需管理缓存,开发者无需考虑缓存策略
|
|
104
|
+
- ✅ **减少错误**: 避免因缓存损坏导致的创建失败
|
|
105
|
+
|
|
109
106
|
## 🛠 开发指南
|
|
110
107
|
|
|
111
108
|
### 项目结构
|
|
@@ -116,7 +113,6 @@ robot-cli/
|
|
|
116
113
|
│ ├── templates.js # 模板配置
|
|
117
114
|
│ ├── create.js # 创建流程
|
|
118
115
|
│ ├── download.js # 下载逻辑
|
|
119
|
-
│ ├── cache.js # 缓存管理
|
|
120
116
|
│ └── utils.js # 工具函数
|
|
121
117
|
└── test/local-test.js # 测试脚本
|
|
122
118
|
```
|
|
@@ -128,7 +124,7 @@ robot-cli/
|
|
|
128
124
|
'robot-new-template': {
|
|
129
125
|
name: 'Robot新模板',
|
|
130
126
|
description: '模板描述',
|
|
131
|
-
|
|
127
|
+
repoUrl: 'https://github.com/ChenyCHENYU/Robot_New_Template',
|
|
132
128
|
features: ['特性1', '特性2'],
|
|
133
129
|
version: 'full'
|
|
134
130
|
}
|
|
@@ -200,7 +196,7 @@ export const TEMPLATE_CATEGORIES = {
|
|
|
200
196
|
templates: {
|
|
201
197
|
'your-admin': {
|
|
202
198
|
name: '你的后台模板',
|
|
203
|
-
|
|
199
|
+
repoUrl: 'https://github.com/YOUR_ORG/Your_Admin_Template',
|
|
204
200
|
// ...
|
|
205
201
|
}
|
|
206
202
|
}
|
|
@@ -319,10 +315,7 @@ your-cli generate component MyComponent
|
|
|
319
315
|
{
|
|
320
316
|
"templateSource": "github",
|
|
321
317
|
"defaultOrg": "YourOrg",
|
|
322
|
-
"
|
|
323
|
-
"enabled": true,
|
|
324
|
-
"ttl": 86400000
|
|
325
|
-
}
|
|
318
|
+
"alwaysLatest": true
|
|
326
319
|
}
|
|
327
320
|
```
|
|
328
321
|
|
|
@@ -401,7 +394,7 @@ analytics.track('template_used', {
|
|
|
401
394
|
A: 全局安装CLI:`npm install -g @agile-team/robot-cli`
|
|
402
395
|
|
|
403
396
|
**Q: 模板下载失败?**
|
|
404
|
-
A:
|
|
397
|
+
A: 检查网络连接,确保能访问GitHub
|
|
405
398
|
|
|
406
399
|
**Q: 如何添加自定义模板?**
|
|
407
400
|
A: 创建模板仓库 → 添加配置 → 测试功能
|
|
@@ -409,6 +402,9 @@ A: 创建模板仓库 → 添加配置 → 测试功能
|
|
|
409
402
|
**Q: 支持私有仓库吗?**
|
|
410
403
|
A: 目前仅支持公开GitHub仓库
|
|
411
404
|
|
|
405
|
+
**Q: 为什么不使用缓存?**
|
|
406
|
+
A: 为了确保总是获得最新版本的模板,避免缓存相关的问题
|
|
407
|
+
|
|
412
408
|
## 🎉 快速体验
|
|
413
409
|
|
|
414
410
|
```bash
|
package/bin/index.js
CHANGED
|
@@ -14,7 +14,6 @@ const __dirname = dirname(__filename);
|
|
|
14
14
|
*/
|
|
15
15
|
function getPackageVersion() {
|
|
16
16
|
try {
|
|
17
|
-
// 尝试从多个位置读取 package.json
|
|
18
17
|
const possiblePaths = [
|
|
19
18
|
join(__dirname, '..', 'package.json'),
|
|
20
19
|
join(__dirname, 'package.json'),
|
|
@@ -28,49 +27,34 @@ function getPackageVersion() {
|
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
return '1.0.0';
|
|
30
|
+
return '1.0.0';
|
|
32
31
|
} catch (error) {
|
|
33
|
-
return '1.0.0';
|
|
32
|
+
return '1.0.0';
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
// 获取版本号
|
|
38
36
|
const PACKAGE_VERSION = getPackageVersion();
|
|
39
37
|
|
|
40
38
|
/**
|
|
41
|
-
* 智能路径解析
|
|
39
|
+
* 智能路径解析
|
|
42
40
|
*/
|
|
43
41
|
function resolveLibPath() {
|
|
44
|
-
// 可能的 lib 目录路径
|
|
45
42
|
const possiblePaths = [
|
|
46
|
-
// 1. 标准相对路径 (开发环境 + 大多数情况)
|
|
47
43
|
join(__dirname, '..', 'lib'),
|
|
48
|
-
|
|
49
|
-
// 2. 同级目录 (某些链接情况)
|
|
50
44
|
join(__dirname, 'lib'),
|
|
51
|
-
|
|
52
|
-
// 3. 向上查找 (深度嵌套情况)
|
|
53
45
|
join(__dirname, '..', '..', 'lib'),
|
|
54
|
-
|
|
55
|
-
// 4. 从 node_modules 查找 (npm/yarn)
|
|
56
46
|
join(__dirname, '..', 'node_modules', '@agile-team', 'robot-cli', 'lib'),
|
|
57
|
-
|
|
58
|
-
// 5. 全局安装的各种可能路径
|
|
59
47
|
resolve(__dirname, '..', 'lib'),
|
|
60
48
|
resolve(__dirname, '../../lib'),
|
|
61
|
-
|
|
62
|
-
// 6. bun 特殊路径处理
|
|
63
49
|
join(__dirname, '..', '..', '@agile-team', 'robot-cli', 'lib'),
|
|
64
50
|
];
|
|
65
51
|
|
|
66
|
-
// 查找第一个存在的路径
|
|
67
52
|
for (const libPath of possiblePaths) {
|
|
68
53
|
if (existsSync(libPath)) {
|
|
69
54
|
return libPath;
|
|
70
55
|
}
|
|
71
56
|
}
|
|
72
57
|
|
|
73
|
-
// 如果都找不到,抛出详细错误
|
|
74
58
|
throw new Error(`
|
|
75
59
|
无法找到 lib 目录,已尝试以下路径:
|
|
76
60
|
${possiblePaths.map(p => ` - ${p}`).join('\n')}
|
|
@@ -85,25 +69,21 @@ ${possiblePaths.map(p => ` - ${p}`).join('\n')}
|
|
|
85
69
|
`);
|
|
86
70
|
}
|
|
87
71
|
|
|
88
|
-
// 动态导入所需模块
|
|
72
|
+
// 动态导入所需模块 - 移除缓存相关模块
|
|
89
73
|
async function loadModules() {
|
|
90
74
|
try {
|
|
91
75
|
const libPath = resolveLibPath();
|
|
92
76
|
|
|
93
|
-
// 将路径转换为 file:// URL 格式(Windows 兼容)
|
|
94
77
|
const createUrl = pathToFileURL(join(libPath, 'create.js')).href;
|
|
95
|
-
const cacheUrl = pathToFileURL(join(libPath, 'cache.js')).href;
|
|
96
78
|
const templatesUrl = pathToFileURL(join(libPath, 'templates.js')).href;
|
|
97
79
|
const utilsUrl = pathToFileURL(join(libPath, 'utils.js')).href;
|
|
98
80
|
|
|
99
|
-
// 动态导入所有需要的模块
|
|
100
81
|
const [
|
|
101
82
|
{ Command },
|
|
102
83
|
chalk,
|
|
103
84
|
boxen,
|
|
104
85
|
inquirer,
|
|
105
86
|
{ createProject },
|
|
106
|
-
{ clearCache, getCacheInfo, formatSize },
|
|
107
87
|
{ getAllTemplates, searchTemplates, getRecommendedTemplates },
|
|
108
88
|
{ checkNetworkConnection }
|
|
109
89
|
] = await Promise.all([
|
|
@@ -112,7 +92,6 @@ async function loadModules() {
|
|
|
112
92
|
import('boxen'),
|
|
113
93
|
import('inquirer'),
|
|
114
94
|
import(createUrl),
|
|
115
|
-
import(cacheUrl),
|
|
116
95
|
import(templatesUrl),
|
|
117
96
|
import(utilsUrl)
|
|
118
97
|
]);
|
|
@@ -123,9 +102,6 @@ async function loadModules() {
|
|
|
123
102
|
boxen: boxen.default,
|
|
124
103
|
inquirer: inquirer.default,
|
|
125
104
|
createProject,
|
|
126
|
-
clearCache,
|
|
127
|
-
getCacheInfo,
|
|
128
|
-
formatSize,
|
|
129
105
|
getAllTemplates,
|
|
130
106
|
searchTemplates,
|
|
131
107
|
getRecommendedTemplates,
|
|
@@ -160,9 +136,6 @@ async function main() {
|
|
|
160
136
|
boxen,
|
|
161
137
|
inquirer,
|
|
162
138
|
createProject,
|
|
163
|
-
clearCache,
|
|
164
|
-
getCacheInfo,
|
|
165
|
-
formatSize,
|
|
166
139
|
getAllTemplates,
|
|
167
140
|
searchTemplates,
|
|
168
141
|
getRecommendedTemplates,
|
|
@@ -203,20 +176,18 @@ async function main() {
|
|
|
203
176
|
console.log();
|
|
204
177
|
}
|
|
205
178
|
|
|
206
|
-
// 显示主菜单
|
|
179
|
+
// 显示主菜单 - 移除缓存信息
|
|
207
180
|
async function showMainMenu() {
|
|
208
181
|
const title = chalk.white.bold('🚀 快速开始');
|
|
209
182
|
|
|
210
183
|
console.log(' ' + title);
|
|
211
184
|
console.log();
|
|
212
185
|
|
|
213
|
-
// 获取统计信息
|
|
214
186
|
const allTemplates = getAllTemplates();
|
|
215
187
|
const templateCount = Object.keys(allTemplates).length;
|
|
216
|
-
const cacheInfo = await getCacheInfo();
|
|
217
188
|
|
|
218
189
|
console.log(chalk.dim(` 📦 可用模板: ${templateCount} 个`));
|
|
219
|
-
console.log(chalk.dim(`
|
|
190
|
+
console.log(chalk.dim(` 🌐 总是下载最新版本`));
|
|
220
191
|
console.log();
|
|
221
192
|
|
|
222
193
|
const commands = [
|
|
@@ -239,11 +210,6 @@ async function main() {
|
|
|
239
210
|
cmd: 'robot search <keyword>',
|
|
240
211
|
desc: '搜索模板',
|
|
241
212
|
color: 'magenta'
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
cmd: 'robot cache',
|
|
245
|
-
desc: '缓存管理',
|
|
246
|
-
color: 'yellow'
|
|
247
213
|
}
|
|
248
214
|
];
|
|
249
215
|
|
|
@@ -262,29 +228,26 @@ async function main() {
|
|
|
262
228
|
program
|
|
263
229
|
.name('robot')
|
|
264
230
|
.description('🤖 Robot 项目脚手架工具 - @agile-team/robot-cli')
|
|
265
|
-
.version(PACKAGE_VERSION)
|
|
231
|
+
.version(PACKAGE_VERSION)
|
|
266
232
|
.hook('preAction', () => {
|
|
267
233
|
showWelcome();
|
|
268
234
|
});
|
|
269
235
|
|
|
270
|
-
// 创建项目命令
|
|
236
|
+
// 创建项目命令 - 移除缓存相关选项
|
|
271
237
|
program
|
|
272
238
|
.command('create [project-name]')
|
|
273
239
|
.description('创建新项目')
|
|
274
240
|
.option('-t, --template <template>', '指定模板类型')
|
|
275
|
-
.option('--no-cache', '强制重新下载模板')
|
|
276
241
|
.option('--skip-install', '跳过依赖安装')
|
|
277
242
|
.action(async (projectName, options) => {
|
|
278
243
|
try {
|
|
279
244
|
// 检查网络连接
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
process.exit(1);
|
|
287
|
-
}
|
|
245
|
+
console.log(chalk.blue('🌐 检查网络连接...'));
|
|
246
|
+
const hasNetwork = await checkNetworkConnection();
|
|
247
|
+
if (!hasNetwork) {
|
|
248
|
+
console.log(chalk.red('❌ 网络连接失败,无法下载模板'));
|
|
249
|
+
console.log(chalk.yellow('💡 请检查网络连接后重试'));
|
|
250
|
+
process.exit(1);
|
|
288
251
|
}
|
|
289
252
|
|
|
290
253
|
await createProject(projectName, options);
|
|
@@ -292,7 +255,6 @@ async function main() {
|
|
|
292
255
|
console.log();
|
|
293
256
|
console.log(chalk.red('✗'), chalk.red.bold('创建失败'));
|
|
294
257
|
|
|
295
|
-
// 根据错误类型提供不同的建议
|
|
296
258
|
if (error.message.includes('网络')) {
|
|
297
259
|
console.log(' ' + chalk.dim('网络相关问题,请检查网络连接'));
|
|
298
260
|
} else if (error.message.includes('权限')) {
|
|
@@ -337,7 +299,6 @@ async function main() {
|
|
|
337
299
|
// 按分类显示
|
|
338
300
|
const categories = {};
|
|
339
301
|
Object.entries(templates).forEach(([key, template]) => {
|
|
340
|
-
// 简单分类逻辑,根据模板名称前缀
|
|
341
302
|
const category = key.split('-')[0];
|
|
342
303
|
if (!categories[category]) {
|
|
343
304
|
categories[category] = [];
|
|
@@ -394,81 +355,6 @@ async function main() {
|
|
|
394
355
|
}
|
|
395
356
|
});
|
|
396
357
|
|
|
397
|
-
// 缓存管理
|
|
398
|
-
program
|
|
399
|
-
.command('cache')
|
|
400
|
-
.description('缓存管理')
|
|
401
|
-
.option('-c, --clear', '清除所有缓存')
|
|
402
|
-
.option('-i, --info', '显示缓存信息')
|
|
403
|
-
.action(async (options) => {
|
|
404
|
-
try {
|
|
405
|
-
if (options.clear) {
|
|
406
|
-
const { confirmed } = await inquirer.prompt([
|
|
407
|
-
{
|
|
408
|
-
type: 'confirm',
|
|
409
|
-
name: 'confirmed',
|
|
410
|
-
message: '确认清除所有模板缓存?',
|
|
411
|
-
default: false
|
|
412
|
-
}
|
|
413
|
-
]);
|
|
414
|
-
|
|
415
|
-
if (confirmed) {
|
|
416
|
-
await clearCache();
|
|
417
|
-
console.log();
|
|
418
|
-
console.log(chalk.green('✓'), chalk.green.bold('缓存清除成功'));
|
|
419
|
-
} else {
|
|
420
|
-
console.log(chalk.yellow('❌ 取消清除'));
|
|
421
|
-
}
|
|
422
|
-
} else {
|
|
423
|
-
// 显示缓存信息
|
|
424
|
-
const cacheInfo = await getCacheInfo();
|
|
425
|
-
|
|
426
|
-
console.log();
|
|
427
|
-
console.log(chalk.blue('💾 缓存信息:'));
|
|
428
|
-
console.log();
|
|
429
|
-
|
|
430
|
-
if (!cacheInfo.exists || cacheInfo.templates.length === 0) {
|
|
431
|
-
console.log(chalk.dim(' 暂无缓存模板'));
|
|
432
|
-
} else {
|
|
433
|
-
console.log(` 缓存目录: ${chalk.dim(cacheInfo.path)}`);
|
|
434
|
-
console.log(` 模板数量: ${chalk.cyan(cacheInfo.templates.length)} 个`);
|
|
435
|
-
console.log(` 总大小: ${chalk.cyan(formatSize(cacheInfo.size))}`);
|
|
436
|
-
console.log();
|
|
437
|
-
console.log(chalk.blue(' 缓存的模板:'));
|
|
438
|
-
|
|
439
|
-
cacheInfo.templates.forEach(template => {
|
|
440
|
-
const modifiedTime = template.modifiedTime.toLocaleDateString();
|
|
441
|
-
console.log(` ${chalk.green('●')} ${template.name}`);
|
|
442
|
-
console.log(` 大小: ${formatSize(template.size)} 更新: ${modifiedTime}`);
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
console.log();
|
|
447
|
-
console.log(chalk.dim(' 使用 robot cache --clear 清除缓存'));
|
|
448
|
-
}
|
|
449
|
-
} catch (error) {
|
|
450
|
-
console.log(chalk.red('❌ 缓存操作失败:'), error.message);
|
|
451
|
-
}
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
// 清除缓存命令 (向后兼容)
|
|
455
|
-
program
|
|
456
|
-
.command('clear-cache')
|
|
457
|
-
.description('清除模板缓存')
|
|
458
|
-
.action(async () => {
|
|
459
|
-
try {
|
|
460
|
-
await clearCache();
|
|
461
|
-
console.log();
|
|
462
|
-
console.log(chalk.green('✓'), chalk.green.bold('缓存清除成功'));
|
|
463
|
-
console.log();
|
|
464
|
-
} catch (error) {
|
|
465
|
-
console.log();
|
|
466
|
-
console.log(chalk.red('✗'), chalk.red.bold('清除缓存失败'));
|
|
467
|
-
console.log(' ' + chalk.dim(error.message));
|
|
468
|
-
console.log();
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
|
|
472
358
|
// 如果没有参数,显示主菜单
|
|
473
359
|
if (process.argv.length === 2) {
|
|
474
360
|
showWelcome();
|
package/lib/create.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// lib/create.js -
|
|
1
|
+
// lib/create.js - 简化版,移除缓存功能
|
|
2
2
|
import fs from "fs-extra";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import chalk from "chalk";
|
|
@@ -539,7 +539,7 @@ async function selectFromAll() {
|
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
/**
|
|
542
|
-
* 项目配置选项
|
|
542
|
+
* 项目配置选项 - 移除缓存相关配置
|
|
543
543
|
*/
|
|
544
544
|
async function configureProject(options) {
|
|
545
545
|
console.log();
|
|
@@ -592,7 +592,6 @@ async function configureProject(options) {
|
|
|
592
592
|
},
|
|
593
593
|
]);
|
|
594
594
|
|
|
595
|
-
// 如果用户不确认配置,提供重新配置或退出选项
|
|
596
595
|
if (!config.confirmConfig) {
|
|
597
596
|
const { action } = await inquirer.prompt([
|
|
598
597
|
{
|
|
@@ -607,21 +606,18 @@ async function configureProject(options) {
|
|
|
607
606
|
]);
|
|
608
607
|
|
|
609
608
|
if (action === "reconfigure") {
|
|
610
|
-
return await configureProject(options);
|
|
609
|
+
return await configureProject(options);
|
|
611
610
|
} else {
|
|
612
611
|
console.log(chalk.yellow("❌ 取消创建项目"));
|
|
613
612
|
process.exit(0);
|
|
614
613
|
}
|
|
615
614
|
}
|
|
616
615
|
|
|
617
|
-
return
|
|
618
|
-
...config,
|
|
619
|
-
useCache: options.cache !== false,
|
|
620
|
-
};
|
|
616
|
+
return config;
|
|
621
617
|
}
|
|
622
618
|
|
|
623
619
|
/**
|
|
624
|
-
* 确认创建
|
|
620
|
+
* 确认创建 - 移除缓存相关信息显示
|
|
625
621
|
*/
|
|
626
622
|
async function confirmCreation(projectName, template, projectConfig) {
|
|
627
623
|
console.log();
|
|
@@ -652,7 +648,7 @@ async function confirmCreation(projectName, template, projectConfig) {
|
|
|
652
648
|
: chalk.dim("否")
|
|
653
649
|
}`
|
|
654
650
|
);
|
|
655
|
-
console.log(` 源码仓库: ${chalk.dim(template.
|
|
651
|
+
console.log(` 源码仓库: ${chalk.dim(template.repoUrl)}`);
|
|
656
652
|
console.log();
|
|
657
653
|
|
|
658
654
|
const { confirmed } = await inquirer.prompt([
|
|
@@ -671,10 +667,9 @@ async function confirmCreation(projectName, template, projectConfig) {
|
|
|
671
667
|
}
|
|
672
668
|
|
|
673
669
|
/**
|
|
674
|
-
* 执行创建流程 -
|
|
670
|
+
* 执行创建流程 - 简化版本,总是下载最新模板
|
|
675
671
|
*/
|
|
676
672
|
async function executeCreation(projectName, template, projectConfig) {
|
|
677
|
-
// 调试信息 - 确保参数正确
|
|
678
673
|
if (!projectName || typeof projectName !== "string") {
|
|
679
674
|
throw new Error(`项目名称无效: ${projectName}`);
|
|
680
675
|
}
|
|
@@ -683,13 +678,14 @@ async function executeCreation(projectName, template, projectConfig) {
|
|
|
683
678
|
throw new Error(`模板数据无效: ${JSON.stringify(template)}`);
|
|
684
679
|
}
|
|
685
680
|
|
|
686
|
-
// 使用更详细的 spinner 配置
|
|
687
681
|
const spinner = ora({
|
|
688
682
|
text: "🚀 准备创建项目...",
|
|
689
683
|
spinner: 'dots',
|
|
690
684
|
color: 'cyan'
|
|
691
685
|
}).start();
|
|
692
686
|
|
|
687
|
+
let tempTemplatePath; // 用于清理临时文件
|
|
688
|
+
|
|
693
689
|
try {
|
|
694
690
|
// 1. 检查目录是否存在
|
|
695
691
|
spinner.text = "📁 检查项目目录...";
|
|
@@ -717,23 +713,13 @@ async function executeCreation(projectName, template, projectConfig) {
|
|
|
717
713
|
spinner.text = "📁 准备创建新目录...";
|
|
718
714
|
}
|
|
719
715
|
|
|
720
|
-
// 2.
|
|
721
|
-
spinner.text = "🌐
|
|
722
|
-
let templatePath;
|
|
723
|
-
|
|
716
|
+
// 2. 下载最新模板
|
|
717
|
+
spinner.text = "🌐 下载最新模板...";
|
|
724
718
|
try {
|
|
725
|
-
|
|
726
|
-
useCache: projectConfig.useCache,
|
|
727
|
-
spinner, // 传递 spinner,让 download 函数更新进度
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
// 验证模板路径
|
|
731
|
-
if (!templatePath || typeof templatePath !== "string") {
|
|
732
|
-
throw new Error(`模板路径无效: ${templatePath}`);
|
|
733
|
-
}
|
|
719
|
+
tempTemplatePath = await downloadTemplate(template, { spinner });
|
|
734
720
|
|
|
735
|
-
if (!fs.existsSync(
|
|
736
|
-
throw new Error(
|
|
721
|
+
if (!tempTemplatePath || !fs.existsSync(tempTemplatePath)) {
|
|
722
|
+
throw new Error(`模板路径无效: ${tempTemplatePath}`);
|
|
737
723
|
}
|
|
738
724
|
} catch (error) {
|
|
739
725
|
spinner.fail("模板下载失败");
|
|
@@ -743,16 +729,14 @@ async function executeCreation(projectName, template, projectConfig) {
|
|
|
743
729
|
console.log();
|
|
744
730
|
console.log(chalk.blue("💡 可能的解决方案:"));
|
|
745
731
|
console.log(chalk.dim(" 1. 检查网络连接"));
|
|
746
|
-
console.log(chalk.dim(" 2.
|
|
747
|
-
console.log(chalk.dim(" 3.
|
|
748
|
-
console.log(chalk.dim(" 4. 检查仓库地址是否正确"));
|
|
732
|
+
console.log(chalk.dim(" 2. 重试命令"));
|
|
733
|
+
console.log(chalk.dim(" 3. 检查仓库地址是否正确"));
|
|
749
734
|
console.log();
|
|
750
735
|
throw error;
|
|
751
736
|
}
|
|
752
737
|
|
|
753
|
-
// 3. 复制模板文件
|
|
754
|
-
|
|
755
|
-
await copyTemplate(templatePath, projectPath);
|
|
738
|
+
// 3. 复制模板文件 - 带详细进度
|
|
739
|
+
await copyTemplate(tempTemplatePath, projectPath, spinner);
|
|
756
740
|
|
|
757
741
|
// 4. 处理项目配置
|
|
758
742
|
spinner.text = "⚙️ 处理项目配置...";
|
|
@@ -779,7 +763,13 @@ async function executeCreation(projectName, template, projectConfig) {
|
|
|
779
763
|
);
|
|
780
764
|
}
|
|
781
765
|
|
|
782
|
-
// 7.
|
|
766
|
+
// 7. 清理临时文件
|
|
767
|
+
if (tempTemplatePath) {
|
|
768
|
+
spinner.text = "🧹 清理临时文件...";
|
|
769
|
+
await fs.remove(tempTemplatePath).catch(() => {});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// 8. 创建成功
|
|
783
773
|
spinner.succeed(chalk.green("🎉 项目创建成功!"));
|
|
784
774
|
|
|
785
775
|
console.log();
|
|
@@ -809,7 +799,6 @@ async function executeCreation(projectName, template, projectConfig) {
|
|
|
809
799
|
);
|
|
810
800
|
}
|
|
811
801
|
|
|
812
|
-
// 根据模板类型提供启动命令
|
|
813
802
|
const startCommand = getStartCommand(template);
|
|
814
803
|
if (startCommand) {
|
|
815
804
|
console.log(chalk.cyan(` ${startCommand}`));
|
|
@@ -826,6 +815,11 @@ async function executeCreation(projectName, template, projectConfig) {
|
|
|
826
815
|
console.log();
|
|
827
816
|
}
|
|
828
817
|
} catch (error) {
|
|
818
|
+
// 清理临时文件
|
|
819
|
+
if (tempTemplatePath) {
|
|
820
|
+
await fs.remove(tempTemplatePath).catch(() => {});
|
|
821
|
+
}
|
|
822
|
+
|
|
829
823
|
spinner.fail("创建项目失败");
|
|
830
824
|
throw error;
|
|
831
825
|
}
|
package/lib/download.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
// lib/download.js -
|
|
1
|
+
// lib/download.js - 简化版,移除缓存功能
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import fetch from 'node-fetch';
|
|
6
6
|
import extract from 'extract-zip';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = path.dirname(__filename);
|
|
12
|
-
|
|
13
|
-
// 缓存目录
|
|
14
|
-
const CACHE_DIR = path.join(os.homedir(), '.robot-cli', 'cache');
|
|
15
7
|
|
|
16
8
|
/**
|
|
17
9
|
* 解析仓库URL,构建下载链接
|
|
@@ -20,30 +12,24 @@ function buildDownloadUrl(repoUrl) {
|
|
|
20
12
|
try {
|
|
21
13
|
const url = new URL(repoUrl);
|
|
22
14
|
const hostname = url.hostname;
|
|
23
|
-
const pathname = url.pathname;
|
|
24
15
|
|
|
25
16
|
if (hostname === 'github.com') {
|
|
26
|
-
// GitHub: https://github.com/user/repo -> /user/repo/archive/refs/heads/main.zip
|
|
27
17
|
return `${repoUrl}/archive/refs/heads/main.zip`;
|
|
28
18
|
} else if (hostname === 'gitee.com') {
|
|
29
|
-
// Gitee: https://gitee.com/user/repo -> /user/repo/repository/archive/master.zip
|
|
30
19
|
return `${repoUrl}/repository/archive/master.zip`;
|
|
31
20
|
} else if (hostname === 'gitlab.com') {
|
|
32
|
-
|
|
33
|
-
const repoName = pathname.split('/').pop();
|
|
21
|
+
const repoName = repoUrl.split('/').pop();
|
|
34
22
|
return `${repoUrl}/-/archive/main/${repoName}-main.zip`;
|
|
35
23
|
} else {
|
|
36
|
-
// 其他平台,默认使用 GitHub 格式
|
|
37
24
|
return `${repoUrl}/archive/refs/heads/main.zip`;
|
|
38
25
|
}
|
|
39
26
|
} catch (error) {
|
|
40
|
-
// URL 解析失败,返回原始URL + 默认后缀
|
|
41
27
|
return `${repoUrl}/archive/refs/heads/main.zip`;
|
|
42
28
|
}
|
|
43
29
|
}
|
|
44
30
|
|
|
45
31
|
/**
|
|
46
|
-
*
|
|
32
|
+
* 尝试从多个源下载
|
|
47
33
|
*/
|
|
48
34
|
async function tryDownload(repoUrl, spinner) {
|
|
49
35
|
const url = new URL(repoUrl);
|
|
@@ -58,7 +44,6 @@ async function tryDownload(repoUrl, spinner) {
|
|
|
58
44
|
`https://ghproxy.com/${repoUrl}` // GitHub 代理
|
|
59
45
|
];
|
|
60
46
|
} else {
|
|
61
|
-
// 其他平台直接使用原始URL
|
|
62
47
|
mirrors = [repoUrl];
|
|
63
48
|
}
|
|
64
49
|
|
|
@@ -69,13 +54,17 @@ async function tryDownload(repoUrl, spinner) {
|
|
|
69
54
|
|
|
70
55
|
try {
|
|
71
56
|
if (spinner) {
|
|
72
|
-
spinner.text =
|
|
57
|
+
spinner.text = `🔍 连接到 ${sourceName}...`;
|
|
73
58
|
}
|
|
74
59
|
|
|
75
60
|
const downloadUrl = buildDownloadUrl(currentUrl);
|
|
76
61
|
|
|
62
|
+
if (spinner) {
|
|
63
|
+
spinner.text = `📦 从 ${sourceName} 下载模板...`;
|
|
64
|
+
}
|
|
65
|
+
|
|
77
66
|
const response = await fetch(downloadUrl, {
|
|
78
|
-
timeout: isOriginal ? 15000 : 10000,
|
|
67
|
+
timeout: isOriginal ? 15000 : 10000,
|
|
79
68
|
headers: {
|
|
80
69
|
'User-Agent': 'Robot-CLI/1.0.0'
|
|
81
70
|
}
|
|
@@ -101,81 +90,48 @@ async function tryDownload(repoUrl, spinner) {
|
|
|
101
90
|
return { response, sourceName };
|
|
102
91
|
|
|
103
92
|
} catch (error) {
|
|
104
|
-
// 如果是最后一个源,抛出错误
|
|
105
93
|
if (i === mirrors.length - 1) {
|
|
106
94
|
throw error;
|
|
107
95
|
}
|
|
108
96
|
|
|
109
|
-
// 否则继续尝试下一个源
|
|
110
97
|
if (spinner) {
|
|
111
98
|
spinner.text = `⚠️ ${sourceName} 访问失败,尝试其他源...`;
|
|
112
99
|
}
|
|
113
100
|
|
|
114
|
-
// 等待1秒再试下一个
|
|
115
101
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
116
102
|
}
|
|
117
103
|
}
|
|
118
104
|
}
|
|
119
105
|
|
|
120
106
|
/**
|
|
121
|
-
* 下载模板 -
|
|
107
|
+
* 下载模板 - 简化版,总是下载最新版本
|
|
122
108
|
*/
|
|
123
109
|
export async function downloadTemplate(template, options = {}) {
|
|
124
|
-
const {
|
|
110
|
+
const { spinner } = options;
|
|
125
111
|
|
|
126
112
|
// 验证模板参数
|
|
127
|
-
if (!template) {
|
|
128
|
-
throw new Error('模板参数不能为空');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!template.key && !template.repoUrl) {
|
|
113
|
+
if (!template || !template.repoUrl) {
|
|
132
114
|
throw new Error(`模板配置无效: ${JSON.stringify(template)}`);
|
|
133
115
|
}
|
|
134
|
-
|
|
135
|
-
// 获取缓存键值
|
|
136
|
-
const cacheKey = template.key || template.repoUrl?.split('/').pop() || 'unknown-template';
|
|
137
|
-
const cachePath = path.join(CACHE_DIR, cacheKey);
|
|
138
116
|
|
|
139
|
-
|
|
140
|
-
if (useCache && fs.existsSync(cachePath)) {
|
|
117
|
+
try {
|
|
141
118
|
if (spinner) {
|
|
142
|
-
spinner.text = '
|
|
119
|
+
spinner.text = '🌐 开始下载最新模板...';
|
|
143
120
|
}
|
|
144
121
|
|
|
145
|
-
// 验证缓存完整性
|
|
146
|
-
const packageJsonPath = path.join(cachePath, 'package.json');
|
|
147
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
148
|
-
if (spinner) {
|
|
149
|
-
spinner.text = '✅ 缓存模板验证通过';
|
|
150
|
-
}
|
|
151
|
-
return cachePath;
|
|
152
|
-
} else {
|
|
153
|
-
// 缓存损坏,删除重新下载
|
|
154
|
-
if (spinner) {
|
|
155
|
-
spinner.text = '🔄 缓存损坏,重新下载...';
|
|
156
|
-
}
|
|
157
|
-
await fs.remove(cachePath);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// 如果是本地测试模板
|
|
162
|
-
if (template.localTest || !template.repoUrl) {
|
|
163
|
-
if (fs.existsSync(cachePath)) {
|
|
164
|
-
return cachePath;
|
|
165
|
-
} else {
|
|
166
|
-
throw new Error(`本地测试模板不存在: ${cachePath}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 下载远程模板
|
|
171
|
-
try {
|
|
172
122
|
// 尝试从不同源下载
|
|
173
123
|
const { response, sourceName } = await tryDownload(template.repoUrl, spinner);
|
|
174
124
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
125
|
+
if (spinner) {
|
|
126
|
+
spinner.text = '💾 保存下载文件...';
|
|
127
|
+
}
|
|
178
128
|
|
|
129
|
+
// 创建临时目录和文件
|
|
130
|
+
const timestamp = Date.now();
|
|
131
|
+
const tempZipPath = path.join(os.tmpdir(), `robot-template-${timestamp}.zip`);
|
|
132
|
+
const tempExtractPath = path.join(os.tmpdir(), `robot-extract-${timestamp}`);
|
|
133
|
+
|
|
134
|
+
// 保存下载的文件
|
|
179
135
|
const buffer = await response.buffer();
|
|
180
136
|
await fs.writeFile(tempZipPath, buffer);
|
|
181
137
|
|
|
@@ -186,6 +142,10 @@ export async function downloadTemplate(template, options = {}) {
|
|
|
186
142
|
// 解压文件
|
|
187
143
|
await extract(tempZipPath, { dir: tempExtractPath });
|
|
188
144
|
|
|
145
|
+
if (spinner) {
|
|
146
|
+
spinner.text = '🔍 查找项目结构...';
|
|
147
|
+
}
|
|
148
|
+
|
|
189
149
|
// 查找项目目录
|
|
190
150
|
const extractedItems = await fs.readdir(tempExtractPath);
|
|
191
151
|
const projectDir = extractedItems.find(item =>
|
|
@@ -210,33 +170,21 @@ export async function downloadTemplate(template, options = {}) {
|
|
|
210
170
|
throw new Error(`模板缺少 package.json 文件`);
|
|
211
171
|
}
|
|
212
172
|
|
|
213
|
-
// 保存到缓存
|
|
214
173
|
if (spinner) {
|
|
215
|
-
spinner.text =
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
await fs.ensureDir(CACHE_DIR);
|
|
219
|
-
if (fs.existsSync(cachePath)) {
|
|
220
|
-
await fs.remove(cachePath);
|
|
174
|
+
spinner.text = `🎉 模板下载完成 (via ${sourceName})`;
|
|
221
175
|
}
|
|
222
|
-
await fs.move(sourcePath, cachePath);
|
|
223
176
|
|
|
224
|
-
//
|
|
177
|
+
// 清理zip文件,但保留解压的源码目录供后续使用
|
|
225
178
|
await fs.remove(tempZipPath).catch(() => {});
|
|
226
|
-
await fs.remove(tempExtractPath).catch(() => {});
|
|
227
|
-
|
|
228
|
-
if (spinner) {
|
|
229
|
-
spinner.text = `🎉 模板下载完成 (via ${sourceName})`;
|
|
230
|
-
}
|
|
231
179
|
|
|
232
|
-
return
|
|
180
|
+
return sourcePath;
|
|
233
181
|
|
|
234
182
|
} catch (error) {
|
|
235
183
|
// 清理临时文件
|
|
236
184
|
try {
|
|
237
185
|
const tempFiles = await fs.readdir(os.tmpdir());
|
|
238
186
|
const robotTempFiles = tempFiles.filter(file =>
|
|
239
|
-
file.includes(
|
|
187
|
+
file.includes('robot-template-') || file.includes('robot-extract-')
|
|
240
188
|
);
|
|
241
189
|
|
|
242
190
|
for (const file of robotTempFiles) {
|
|
@@ -246,7 +194,6 @@ export async function downloadTemplate(template, options = {}) {
|
|
|
246
194
|
// 忽略清理错误
|
|
247
195
|
}
|
|
248
196
|
|
|
249
|
-
// 简单的错误处理
|
|
250
197
|
let errorMessage = `模板下载失败: ${error.message}`;
|
|
251
198
|
|
|
252
199
|
if (error.code === 'ENOTFOUND' || error.message.includes('网络')) {
|
|
@@ -255,14 +202,4 @@ export async function downloadTemplate(template, options = {}) {
|
|
|
255
202
|
|
|
256
203
|
throw new Error(errorMessage);
|
|
257
204
|
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* 清除所有缓存
|
|
262
|
-
*/
|
|
263
|
-
export async function clearCache() {
|
|
264
|
-
if (fs.existsSync(CACHE_DIR)) {
|
|
265
|
-
await fs.remove(CACHE_DIR);
|
|
266
|
-
}
|
|
267
|
-
await fs.ensureDir(CACHE_DIR);
|
|
268
205
|
}
|
package/lib/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// lib/utils.js -
|
|
1
|
+
// lib/utils.js - 增强版本,添加详细进度展示
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import chalk from 'chalk';
|
|
@@ -10,28 +10,23 @@ import fetch from 'node-fetch';
|
|
|
10
10
|
*/
|
|
11
11
|
export function detectPackageManager() {
|
|
12
12
|
try {
|
|
13
|
-
// 检查各种包管理器的可用性
|
|
14
13
|
const managers = [];
|
|
15
14
|
|
|
16
|
-
// 检查 bun
|
|
17
15
|
try {
|
|
18
16
|
execSync('bun --version', { stdio: 'ignore' });
|
|
19
17
|
managers.push('bun');
|
|
20
18
|
} catch {}
|
|
21
19
|
|
|
22
|
-
// 检查 pnpm
|
|
23
20
|
try {
|
|
24
21
|
execSync('pnpm --version', { stdio: 'ignore' });
|
|
25
22
|
managers.push('pnpm');
|
|
26
23
|
} catch {}
|
|
27
24
|
|
|
28
|
-
// 检查 yarn
|
|
29
25
|
try {
|
|
30
26
|
execSync('yarn --version', { stdio: 'ignore' });
|
|
31
27
|
managers.push('yarn');
|
|
32
28
|
} catch {}
|
|
33
29
|
|
|
34
|
-
// npm 通常总是可用的
|
|
35
30
|
try {
|
|
36
31
|
execSync('npm --version', { stdio: 'ignore' });
|
|
37
32
|
managers.push('npm');
|
|
@@ -39,75 +34,10 @@ export function detectPackageManager() {
|
|
|
39
34
|
|
|
40
35
|
return managers;
|
|
41
36
|
} catch (error) {
|
|
42
|
-
return ['npm'];
|
|
37
|
+
return ['npm'];
|
|
43
38
|
}
|
|
44
39
|
}
|
|
45
40
|
|
|
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
|
-
}
|
|
110
|
-
|
|
111
41
|
/**
|
|
112
42
|
* 验证项目名称
|
|
113
43
|
*/
|
|
@@ -157,28 +87,92 @@ export function validateProjectName(name) {
|
|
|
157
87
|
}
|
|
158
88
|
|
|
159
89
|
/**
|
|
160
|
-
*
|
|
90
|
+
* 统计目录中的文件数量
|
|
161
91
|
*/
|
|
162
|
-
|
|
92
|
+
async function countFiles(dirPath) {
|
|
93
|
+
let count = 0;
|
|
94
|
+
|
|
95
|
+
async function walkDir(currentPath) {
|
|
96
|
+
const items = await fs.readdir(currentPath);
|
|
97
|
+
|
|
98
|
+
for (const item of items) {
|
|
99
|
+
const itemPath = path.join(currentPath, item);
|
|
100
|
+
const stat = await fs.stat(itemPath);
|
|
101
|
+
|
|
102
|
+
if (stat.isDirectory()) {
|
|
103
|
+
// 跳过不需要的目录
|
|
104
|
+
if (!['node_modules', '.git', '.DS_Store'].includes(item)) {
|
|
105
|
+
await walkDir(itemPath);
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
count++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await walkDir(dirPath);
|
|
114
|
+
return count;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 复制模板文件 - 带详细进度展示
|
|
119
|
+
*/
|
|
120
|
+
export async function copyTemplate(sourcePath, targetPath, spinner) {
|
|
163
121
|
if (!fs.existsSync(sourcePath)) {
|
|
164
122
|
throw new Error(`源路径不存在: ${sourcePath}`);
|
|
165
123
|
}
|
|
166
124
|
|
|
167
125
|
await fs.ensureDir(targetPath);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
126
|
+
|
|
127
|
+
// 1. 统计文件数量
|
|
128
|
+
if (spinner) {
|
|
129
|
+
spinner.text = '📊 统计文件数量...';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const totalFiles = await countFiles(sourcePath);
|
|
133
|
+
|
|
134
|
+
if (spinner) {
|
|
135
|
+
spinner.text = `📋 开始复制 ${totalFiles} 个文件...`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let copiedFiles = 0;
|
|
139
|
+
|
|
140
|
+
// 2. 递归复制文件,带进度更新
|
|
141
|
+
async function copyWithProgress(srcDir, destDir) {
|
|
142
|
+
const items = await fs.readdir(srcDir);
|
|
143
|
+
|
|
144
|
+
for (const item of items) {
|
|
145
|
+
const srcPath = path.join(srcDir, item);
|
|
146
|
+
const destPath = path.join(destDir, item);
|
|
147
|
+
const stat = await fs.stat(srcPath);
|
|
148
|
+
|
|
149
|
+
if (stat.isDirectory()) {
|
|
150
|
+
// 排除不需要的文件夹
|
|
151
|
+
if (['node_modules', '.git', '.DS_Store', '.vscode', '.idea'].includes(item)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await fs.ensureDir(destPath);
|
|
156
|
+
await copyWithProgress(srcPath, destPath);
|
|
157
|
+
} else {
|
|
158
|
+
// 复制文件
|
|
159
|
+
await fs.copy(srcPath, destPath);
|
|
160
|
+
copiedFiles++;
|
|
161
|
+
|
|
162
|
+
// 更新进度 (每10个文件或重要节点更新一次)
|
|
163
|
+
if (spinner && (copiedFiles % 10 === 0 || copiedFiles === totalFiles)) {
|
|
164
|
+
const percentage = Math.round((copiedFiles / totalFiles) * 100);
|
|
165
|
+
spinner.text = `📋 复制中... ${copiedFiles}/${totalFiles} (${percentage}%)`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
180
168
|
}
|
|
181
|
-
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await copyWithProgress(sourcePath, targetPath);
|
|
172
|
+
|
|
173
|
+
if (spinner) {
|
|
174
|
+
spinner.text = `✅ 文件复制完成 (${copiedFiles} 个文件)`;
|
|
175
|
+
}
|
|
182
176
|
}
|
|
183
177
|
|
|
184
178
|
/**
|
|
@@ -190,7 +184,6 @@ export async function installDependencies(projectPath, spinner, packageManager =
|
|
|
190
184
|
try {
|
|
191
185
|
process.chdir(projectPath);
|
|
192
186
|
|
|
193
|
-
// 检查 package.json 是否存在
|
|
194
187
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
195
188
|
if (!fs.existsSync(packageJsonPath)) {
|
|
196
189
|
if (spinner) {
|
|
@@ -199,7 +192,6 @@ export async function installDependencies(projectPath, spinner, packageManager =
|
|
|
199
192
|
return;
|
|
200
193
|
}
|
|
201
194
|
|
|
202
|
-
// 根据包管理器选择安装命令
|
|
203
195
|
const installCommands = {
|
|
204
196
|
bun: 'bun install',
|
|
205
197
|
pnpm: 'pnpm install',
|
|
@@ -215,9 +207,13 @@ export async function installDependencies(projectPath, spinner, packageManager =
|
|
|
215
207
|
|
|
216
208
|
execSync(command, {
|
|
217
209
|
stdio: 'ignore',
|
|
218
|
-
timeout: 300000
|
|
210
|
+
timeout: 300000
|
|
219
211
|
});
|
|
220
212
|
|
|
213
|
+
if (spinner) {
|
|
214
|
+
spinner.text = `✅ 依赖安装完成 (${packageManager})`;
|
|
215
|
+
}
|
|
216
|
+
|
|
221
217
|
} catch (error) {
|
|
222
218
|
if (spinner) {
|
|
223
219
|
spinner.text = `⚠️ 依赖安装失败,请手动安装`;
|
|
@@ -246,7 +242,6 @@ export async function checkNetworkConnection() {
|
|
|
246
242
|
});
|
|
247
243
|
return response.ok;
|
|
248
244
|
} catch (error) {
|
|
249
|
-
// 尝试备用检查
|
|
250
245
|
try {
|
|
251
246
|
const response = await fetch('https://www.npmjs.com', {
|
|
252
247
|
method: 'HEAD',
|
|
@@ -279,7 +274,6 @@ export async function generateProjectStats(projectPath) {
|
|
|
279
274
|
const stat = await fs.stat(itemPath);
|
|
280
275
|
|
|
281
276
|
if (stat.isDirectory()) {
|
|
282
|
-
// 跳过 node_modules 等目录
|
|
283
277
|
if (!['node_modules', '.git', '.DS_Store'].includes(item)) {
|
|
284
278
|
stats.directories++;
|
|
285
279
|
await walkDir(itemPath);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agile-team/robot-cli",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "🤖 现代化项目脚手架工具,支持多技术栈快速创建项目",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "🤖 现代化项目脚手架工具,支持多技术栈快速创建项目 - 总是下载最新版本",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"robot": "bin/index.js"
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
"robot:create": "node bin/index.js create",
|
|
12
12
|
"robot:list": "node bin/index.js list",
|
|
13
13
|
"robot:search": "node bin/index.js search",
|
|
14
|
-
"robot:cache": "node bin/index.js cache --info",
|
|
15
|
-
"robot:cache:clear": "node bin/index.js cache --clear",
|
|
16
14
|
"test": "node test/local-test.js",
|
|
17
15
|
"test:setup": "node test/local-test.js --setup",
|
|
18
16
|
"test:clean": "node test/local-test.js --clean",
|
|
@@ -20,8 +18,6 @@
|
|
|
20
18
|
"dev:create": "node bin/index.js create",
|
|
21
19
|
"dev:list": "node bin/index.js list",
|
|
22
20
|
"dev:search": "node bin/index.js search",
|
|
23
|
-
"dev:cache": "node bin/index.js cache --info",
|
|
24
|
-
"dev:cache:clear": "node bin/index.js cache --clear",
|
|
25
21
|
"install:deps": "bun install || npm install",
|
|
26
22
|
"clean:modules": "rm -rf node_modules",
|
|
27
23
|
"reinstall": "npm run clean:modules && npm run install:deps",
|
|
@@ -61,6 +57,8 @@
|
|
|
61
57
|
"electron",
|
|
62
58
|
"project-generator",
|
|
63
59
|
"boilerplate",
|
|
60
|
+
"always-latest",
|
|
61
|
+
"no-cache",
|
|
64
62
|
"cheny"
|
|
65
63
|
],
|
|
66
64
|
"engines": {
|
package/lib/cache.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
// lib/cache.js
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
|
|
7
|
-
const CACHE_DIR = path.join(os.homedir(), '.robot-cli', 'cache');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 清除缓存
|
|
11
|
-
*/
|
|
12
|
-
export async function clearCache() {
|
|
13
|
-
try {
|
|
14
|
-
if (fs.existsSync(CACHE_DIR)) {
|
|
15
|
-
const cacheItems = await fs.readdir(CACHE_DIR);
|
|
16
|
-
|
|
17
|
-
if (cacheItems.length === 0) {
|
|
18
|
-
console.log(chalk.yellow('📭 缓存目录为空'));
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
await fs.remove(CACHE_DIR);
|
|
23
|
-
await fs.ensureDir(CACHE_DIR);
|
|
24
|
-
|
|
25
|
-
console.log(chalk.green(`🗑️ 已清除 ${cacheItems.length} 个缓存模板:`));
|
|
26
|
-
cacheItems.forEach(item => {
|
|
27
|
-
console.log(chalk.gray(` - ${item}`));
|
|
28
|
-
});
|
|
29
|
-
} else {
|
|
30
|
-
console.log(chalk.yellow('📭 缓存目录不存在'));
|
|
31
|
-
}
|
|
32
|
-
} catch (error) {
|
|
33
|
-
throw new Error(`清除缓存失败: ${error.message}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 获取缓存信息
|
|
39
|
-
*/
|
|
40
|
-
export async function getCacheInfo() {
|
|
41
|
-
try {
|
|
42
|
-
if (!fs.existsSync(CACHE_DIR)) {
|
|
43
|
-
return {
|
|
44
|
-
exists: false,
|
|
45
|
-
templates: [],
|
|
46
|
-
size: 0,
|
|
47
|
-
path: CACHE_DIR
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const templates = await fs.readdir(CACHE_DIR);
|
|
52
|
-
let totalSize = 0;
|
|
53
|
-
|
|
54
|
-
const templateInfos = await Promise.all(
|
|
55
|
-
templates.map(async (template) => {
|
|
56
|
-
const templatePath = path.join(CACHE_DIR, template);
|
|
57
|
-
const stats = await fs.stat(templatePath);
|
|
58
|
-
const size = await getFolderSize(templatePath);
|
|
59
|
-
totalSize += size;
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
name: template,
|
|
63
|
-
modifiedTime: stats.mtime,
|
|
64
|
-
size: size
|
|
65
|
-
};
|
|
66
|
-
})
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
exists: true,
|
|
71
|
-
templates: templateInfos,
|
|
72
|
-
size: totalSize,
|
|
73
|
-
path: CACHE_DIR
|
|
74
|
-
};
|
|
75
|
-
} catch (error) {
|
|
76
|
-
throw new Error(`获取缓存信息失败: ${error.message}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 计算文件夹大小
|
|
82
|
-
*/
|
|
83
|
-
async function getFolderSize(folderPath) {
|
|
84
|
-
let size = 0;
|
|
85
|
-
|
|
86
|
-
async function calculateSize(currentPath) {
|
|
87
|
-
try {
|
|
88
|
-
const items = await fs.readdir(currentPath);
|
|
89
|
-
|
|
90
|
-
for (const item of items) {
|
|
91
|
-
const itemPath = path.join(currentPath, item);
|
|
92
|
-
const stats = await fs.stat(itemPath);
|
|
93
|
-
|
|
94
|
-
if (stats.isDirectory()) {
|
|
95
|
-
await calculateSize(itemPath);
|
|
96
|
-
} else {
|
|
97
|
-
size += stats.size;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
} catch (error) {
|
|
101
|
-
// 忽略权限错误等
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
await calculateSize(folderPath);
|
|
106
|
-
return size;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 格式化文件大小
|
|
111
|
-
*/
|
|
112
|
-
export function formatSize(bytes) {
|
|
113
|
-
if (bytes === 0) return '0 B';
|
|
114
|
-
|
|
115
|
-
const k = 1024;
|
|
116
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
117
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
118
|
-
|
|
119
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
120
|
-
}
|