@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/README.md +17 -21
- package/bin/index.js +14 -128
- package/lib/create.js +1124 -957
- package/lib/download.js +17 -101
- package/lib/utils.js +86 -92
- package/package.json +4 -6
- package/lib/cache.js +0 -120
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
|
|
|
@@ -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 {
|
|
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
|
|
190
|
-
const
|
|
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
|
-
|
|
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(
|
|
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'];
|
|
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.
|
|
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
|
-
}
|