@agile-team/robot-cli 1.0.9 → 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 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
- repo: 'ChenyCHENYU/Robot_New_Template',
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
- repo: 'YOUR_ORG/Your_Admin_Template',
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
- "cache": {
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: 检查网络连接,尝试清除缓存:`robot cache --clear`
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(` 💾 缓存模板: ${cacheInfo.templates.length} 个 (${formatSize(cacheInfo.size)})`));
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
- if (!options.cache) {
281
- console.log(chalk.blue('🌐 检查网络连接...'));
282
- const hasNetwork = await checkNetworkConnection();
283
- if (!hasNetwork) {
284
- console.log(chalk.red(' 网络连接失败,无法下载模板'));
285
- console.log(chalk.yellow('💡 请检查网络连接后重试'));
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();
@@ -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. 下载模板 - download.js 会自动更新 spinner 状态
721
- spinner.text = "🌐 开始下载模板...";
722
- let templatePath;
723
-
716
+ // 2. 下载最新模板
717
+ spinner.text = "🌐 下载最新模板...";
724
718
  try {
725
- templatePath = await downloadTemplate(template, {
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(templatePath)) {
736
- throw new Error(`模板路径不存在: ${templatePath}`);
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. 使用 robot cache --clear 清除缓存"));
747
- console.log(chalk.dim(" 3. 重试: robot create --no-cache"));
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
- spinner.text = "📋 复制项目文件...";
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
- // GitLab: https://gitlab.com/user/repo -> /user/repo/-/archive/main/repo-main.zip
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
 
@@ -79,7 +64,7 @@ async function tryDownload(repoUrl, spinner) {
79
64
  }
80
65
 
81
66
  const response = await fetch(downloadUrl, {
82
- timeout: isOriginal ? 15000 : 10000, // 官方源给更多时间
67
+ timeout: isOriginal ? 15000 : 10000,
83
68
  headers: {
84
69
  'User-Agent': 'Robot-CLI/1.0.0'
85
70
  }
@@ -105,90 +90,48 @@ async function tryDownload(repoUrl, spinner) {
105
90
  return { response, sourceName };
106
91
 
107
92
  } catch (error) {
108
- // 如果是最后一个源,抛出错误
109
93
  if (i === mirrors.length - 1) {
110
94
  throw error;
111
95
  }
112
96
 
113
- // 否则继续尝试下一个源
114
97
  if (spinner) {
115
98
  spinner.text = `⚠️ ${sourceName} 访问失败,尝试其他源...`;
116
99
  }
117
100
 
118
- // 等待1秒再试下一个
119
101
  await new Promise(resolve => setTimeout(resolve, 1000));
120
102
  }
121
103
  }
122
104
  }
123
105
 
124
106
  /**
125
- * 下载模板 - 简化版
107
+ * 下载模板 - 简化版,总是下载最新版本
126
108
  */
127
109
  export async function downloadTemplate(template, options = {}) {
128
- const { useCache = true, spinner } = options;
110
+ const { spinner } = options;
129
111
 
130
112
  // 验证模板参数
131
- if (!template) {
132
- throw new Error('模板参数不能为空');
133
- }
134
-
135
- if (!template.key && !template.repoUrl) {
113
+ if (!template || !template.repoUrl) {
136
114
  throw new Error(`模板配置无效: ${JSON.stringify(template)}`);
137
115
  }
138
-
139
- // 获取缓存键值
140
- const cacheKey = template.key || template.repoUrl?.split('/').pop() || 'unknown-template';
141
- const cachePath = path.join(CACHE_DIR, cacheKey);
142
-
143
- // 检查缓存
144
- if (useCache && fs.existsSync(cachePath)) {
145
- if (spinner) {
146
- spinner.text = '📂 使用缓存模板...';
147
- }
148
-
149
- // 验证缓存完整性
150
- const packageJsonPath = path.join(cachePath, 'package.json');
151
- if (fs.existsSync(packageJsonPath)) {
152
- if (spinner) {
153
- spinner.text = '✅ 缓存模板验证通过';
154
- }
155
- return cachePath;
156
- } else {
157
- // 缓存损坏,删除重新下载
158
- if (spinner) {
159
- spinner.text = '🔄 缓存损坏,重新下载...';
160
- }
161
- await fs.remove(cachePath);
162
- }
163
- }
164
-
165
- // 如果是本地测试模板
166
- if (template.localTest || !template.repoUrl) {
167
- if (fs.existsSync(cachePath)) {
168
- return cachePath;
169
- } else {
170
- throw new Error(`本地测试模板不存在: ${cachePath}`);
171
- }
172
- }
173
116
 
174
- // 下载远程模板
175
117
  try {
176
118
  if (spinner) {
177
- spinner.text = '🌐 开始下载模板...';
119
+ spinner.text = '🌐 开始下载最新模板...';
178
120
  }
179
121
 
180
122
  // 尝试从不同源下载
181
123
  const { response, sourceName } = await tryDownload(template.repoUrl, spinner);
182
124
 
183
- // 显示下载完成,开始保存
184
125
  if (spinner) {
185
126
  spinner.text = '💾 保存下载文件...';
186
127
  }
187
128
 
188
- // 保存到临时文件
189
- const tempZipPath = path.join(os.tmpdir(), cacheKey + '-' + Date.now() + '.zip');
190
- const tempExtractPath = path.join(os.tmpdir(), cacheKey + '-extract-' + Date.now());
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}`);
191
133
 
134
+ // 保存下载的文件
192
135
  const buffer = await response.buffer();
193
136
  await fs.writeFile(tempZipPath, buffer);
194
137
 
@@ -227,37 +170,21 @@ export async function downloadTemplate(template, options = {}) {
227
170
  throw new Error(`模板缺少 package.json 文件`);
228
171
  }
229
172
 
230
- // 保存到缓存
231
173
  if (spinner) {
232
- spinner.text = '💾 保存到缓存目录...';
233
- }
234
-
235
- await fs.ensureDir(CACHE_DIR);
236
- if (fs.existsSync(cachePath)) {
237
- await fs.remove(cachePath);
174
+ spinner.text = `🎉 模板下载完成 (via ${sourceName})`;
238
175
  }
239
- await fs.move(sourcePath, cachePath);
240
176
 
241
- // 清理临时文件
242
- if (spinner) {
243
- spinner.text = '🧹 清理临时文件...';
244
- }
245
-
177
+ // 清理zip文件,但保留解压的源码目录供后续使用
246
178
  await fs.remove(tempZipPath).catch(() => {});
247
- await fs.remove(tempExtractPath).catch(() => {});
248
179
 
249
- if (spinner) {
250
- spinner.text = `🎉 模板下载完成 (via ${sourceName})`;
251
- }
252
-
253
- return cachePath;
180
+ return sourcePath;
254
181
 
255
182
  } catch (error) {
256
183
  // 清理临时文件
257
184
  try {
258
185
  const tempFiles = await fs.readdir(os.tmpdir());
259
186
  const robotTempFiles = tempFiles.filter(file =>
260
- file.includes(cacheKey) && (file.endsWith('.zip') || file.includes('extract'))
187
+ file.includes('robot-template-') || file.includes('robot-extract-')
261
188
  );
262
189
 
263
190
  for (const file of robotTempFiles) {
@@ -267,7 +194,6 @@ export async function downloadTemplate(template, options = {}) {
267
194
  // 忽略清理错误
268
195
  }
269
196
 
270
- // 简单的错误处理
271
197
  let errorMessage = `模板下载失败: ${error.message}`;
272
198
 
273
199
  if (error.code === 'ENOTFOUND' || error.message.includes('网络')) {
@@ -276,14 +202,4 @@ export async function downloadTemplate(template, options = {}) {
276
202
 
277
203
  throw new Error(errorMessage);
278
204
  }
279
- }
280
-
281
- /**
282
- * 清除所有缓存
283
- */
284
- export async function clearCache() {
285
- if (fs.existsSync(CACHE_DIR)) {
286
- await fs.remove(CACHE_DIR);
287
- }
288
- await fs.ensureDir(CACHE_DIR);
289
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']; // 默认返回 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
- export async function copyTemplate(sourcePath, targetPath) {
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
- 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);
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 // 5分钟超时
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.9",
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
- }