@agile-team/robot-cli 1.0.9 → 1.1.1

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/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.1",
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
- }