@agile-team/robot-cli 2.2.0 → 2.3.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/CHANGELOG.md +18 -1
- package/README.md +27 -27
- package/dist/index.js +114 -98
- package/package.json +3 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
-
## [2.
|
|
8
|
+
## [2.3.0] - 2026-03-28
|
|
9
|
+
|
|
10
|
+
### Fixed (Root-Cause)
|
|
11
|
+
|
|
12
|
+
- **下载超时**:超时时间从 15s → 120s(主站)/ 60s(镜像),适配中国网络环境
|
|
13
|
+
- **下载无重试**:新增 `fetchWithRetry()`,每个源最多重试 3 次,指数退避(2s→4s)
|
|
14
|
+
- **镜像失效**:从 1 个已失效的 `ghproxy.com` → 3 个源(`github.com` + `gitmirror` + `ghproxy.net`)
|
|
15
|
+
- **双重报错**:下载失败后内外两层 catch 都打印错误 → 内层 return 终止,只输出一次
|
|
16
|
+
- **Windows emoji 乱码**:全面移除所有源文件中的 emoji,分类名 / spinner.text / doctor 输出全部改为纯文本 + chalk 着色
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Banner 从 `gradient-string` 多行 Unicode 框线 → `chalk.bold.cyan` 极简纯文本(参考 create-vue)
|
|
21
|
+
- 移除 `gradient-string` 依赖(减小包体积)
|
|
22
|
+
- doctor 诊断图标从 emoji → ASCII `[OK]` / `[!!]` / `[NO]`
|
|
23
|
+
- package.json description 移除 emoji 前缀
|
|
24
|
+
|
|
25
|
+
## [2.2.0] - 2026-03-28
|
|
9
26
|
|
|
10
27
|
### ⚠️ BREAKING CHANGES
|
|
11
28
|
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Robot CLI
|
|
2
2
|
|
|
3
3
|
> **现代化工程脚手架** — 一条命令,60 秒搭建标准化项目
|
|
4
4
|
|
|
@@ -10,17 +10,17 @@
|
|
|
10
10
|
|
|
11
11
|
## 目录
|
|
12
12
|
|
|
13
|
-
- [快速开始](
|
|
14
|
-
- [模板一览](
|
|
15
|
-
- [命令详解](
|
|
16
|
-
- [进阶用法](
|
|
17
|
-
- [项目结构](
|
|
18
|
-
- [二次开发指南](
|
|
19
|
-
- [常见问题](
|
|
13
|
+
- [快速开始](#快速开始)
|
|
14
|
+
- [模板一览](#模板一览)
|
|
15
|
+
- [命令详解](#命令详解)
|
|
16
|
+
- [进阶用法](#进阶用法)
|
|
17
|
+
- [项目结构](#项目结构)
|
|
18
|
+
- [二次开发指南](#二次开发指南)
|
|
19
|
+
- [常见问题](#常见问题)
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## 快速开始
|
|
24
24
|
|
|
25
25
|
### 安装
|
|
26
26
|
|
|
@@ -49,9 +49,9 @@ npx @agile-team/robot-cli create my-project
|
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## 模板一览
|
|
53
53
|
|
|
54
|
-
###
|
|
54
|
+
### 前端项目(Vue.js)
|
|
55
55
|
|
|
56
56
|
| 模板 Key | 名称 | 架构模式 | 说明 |
|
|
57
57
|
|---------|------|---------|------|
|
|
@@ -64,21 +64,21 @@ npx @agile-team/robot-cli create my-project
|
|
|
64
64
|
> **前端模板源码仓库**:[ChenyCHENYU/Robot_Admin](https://github.com/ChenyCHENYU/Robot_Admin)
|
|
65
65
|
> 不同架构通过分支区分:`main`(全量)、`monorepo`、`micro-app`、`module-federation`
|
|
66
66
|
|
|
67
|
-
###
|
|
67
|
+
### 前端项目(React.js)
|
|
68
68
|
|
|
69
69
|
| 模板 Key | 名称 | 说明 |
|
|
70
70
|
|---------|------|------|
|
|
71
71
|
| `robot-react` | Robot React 完整版 | Ant Design + 完整功能演示 |
|
|
72
72
|
| `robot-react-base` | Robot React 精简版 | 基础 React + 核心功能 |
|
|
73
73
|
|
|
74
|
-
###
|
|
74
|
+
### 移动端项目
|
|
75
75
|
|
|
76
76
|
| 模板 Key | 名称 | 说明 |
|
|
77
77
|
|---------|------|------|
|
|
78
78
|
| `robot-uniapp` | Robot uni-app 完整版 | 多端适配(小程序/H5/App)+ 完整示例 |
|
|
79
79
|
| `robot-uniapp-base` | Robot uni-app 精简版 | 基础框架 + 核心功能 |
|
|
80
80
|
|
|
81
|
-
###
|
|
81
|
+
### 后端项目
|
|
82
82
|
|
|
83
83
|
| 模板 Key | 名称 | 说明 |
|
|
84
84
|
|---------|------|------|
|
|
@@ -86,7 +86,7 @@ npx @agile-team/robot-cli create my-project
|
|
|
86
86
|
| `robot-nest-base` | Robot NestJS 精简版 | 基础 NestJS + 核心模块 |
|
|
87
87
|
| `robot-nest-micro` | Robot NestJS 微服务 | gRPC + 服务发现 |
|
|
88
88
|
|
|
89
|
-
###
|
|
89
|
+
### 桌面端项目
|
|
90
90
|
|
|
91
91
|
| 模板 Key | 名称 | 说明 |
|
|
92
92
|
|---------|------|------|
|
|
@@ -95,7 +95,7 @@ npx @agile-team/robot-cli create my-project
|
|
|
95
95
|
|
|
96
96
|
---
|
|
97
97
|
|
|
98
|
-
##
|
|
98
|
+
## 命令详解
|
|
99
99
|
|
|
100
100
|
### `robot create [project-name]` — 创建项目
|
|
101
101
|
|
|
@@ -151,7 +151,7 @@ robot doctor --clear-cache # 清理模板缓存
|
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
154
|
-
##
|
|
154
|
+
## 进阶用法
|
|
155
155
|
|
|
156
156
|
### 包管理器优先级
|
|
157
157
|
|
|
@@ -159,10 +159,10 @@ robot doctor --clear-cache # 清理模板缓存
|
|
|
159
159
|
|
|
160
160
|
| 优先级 | 包管理器 | 说明 |
|
|
161
161
|
|--------|---------|------|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
| 4
|
|
162
|
+
| 1 | **bun** | 极速安装,现代化 |
|
|
163
|
+
| 2 | **pnpm** | 快速安装,节省磁盘 |
|
|
164
|
+
| 3 | **yarn** | 兼容性好 |
|
|
165
|
+
| 4 | **npm** | Node.js 内置 |
|
|
166
166
|
|
|
167
167
|
### 离线缓存
|
|
168
168
|
|
|
@@ -190,7 +190,7 @@ robot create my-app --from https://gitee.com/your-org/your-template
|
|
|
190
190
|
|
|
191
191
|
---
|
|
192
192
|
|
|
193
|
-
##
|
|
193
|
+
## 项目结构
|
|
194
194
|
|
|
195
195
|
```
|
|
196
196
|
robot-cli/
|
|
@@ -225,7 +225,7 @@ robot-cli/
|
|
|
225
225
|
|
|
226
226
|
---
|
|
227
227
|
|
|
228
|
-
##
|
|
228
|
+
## 二次开发指南
|
|
229
229
|
|
|
230
230
|
### 1. 克隆 & 启动
|
|
231
231
|
|
|
@@ -305,7 +305,7 @@ npm publish --access public
|
|
|
305
305
|
|
|
306
306
|
---
|
|
307
307
|
|
|
308
|
-
##
|
|
308
|
+
## 常见问题
|
|
309
309
|
|
|
310
310
|
**Q: 提示 `command not found: robot`**
|
|
311
311
|
```bash
|
|
@@ -333,15 +333,15 @@ robot create my-app --no-cache # 跳过缓存重试
|
|
|
333
333
|
|
|
334
334
|
---
|
|
335
335
|
|
|
336
|
-
##
|
|
336
|
+
## 贡献
|
|
337
337
|
|
|
338
338
|
欢迎提交 Issues 和 Pull Requests!
|
|
339
339
|
|
|
340
|
-
##
|
|
340
|
+
## 许可证
|
|
341
341
|
|
|
342
342
|
MIT License
|
|
343
343
|
|
|
344
|
-
##
|
|
344
|
+
## 链接
|
|
345
345
|
|
|
346
346
|
- [GitHub](https://github.com/ChenyCHENYU/robot-cli)
|
|
347
347
|
- [npm](https://www.npmjs.com/package/@agile-team/robot-cli)
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,6 @@ import { readFileSync } from "fs";
|
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import { dirname, join } from "path";
|
|
5
5
|
import chalk5 from "chalk";
|
|
6
|
-
import gradient from "gradient-string";
|
|
7
6
|
import inquirer2 from "inquirer";
|
|
8
7
|
import { Command } from "commander";
|
|
9
8
|
|
|
@@ -25,7 +24,7 @@ import extract from "extract-zip";
|
|
|
25
24
|
// src/config/templates.config.ts
|
|
26
25
|
var TEMPLATE_CATEGORIES = {
|
|
27
26
|
frontend: {
|
|
28
|
-
name: "\
|
|
27
|
+
name: "\u524D\u7AEF\u9879\u76EE",
|
|
29
28
|
stacks: {
|
|
30
29
|
vue: {
|
|
31
30
|
name: "Vue.js",
|
|
@@ -65,12 +64,7 @@ var TEMPLATE_CATEGORIES = {
|
|
|
65
64
|
description: "bun workspace + Monorepo \u591A\u5305\u7BA1\u7406\u67B6\u6784",
|
|
66
65
|
repoUrl: "https://github.com/ChenyCHENYU/Robot_Admin",
|
|
67
66
|
branch: "monorepo",
|
|
68
|
-
features: [
|
|
69
|
-
"bun workspace",
|
|
70
|
-
"Monorepo",
|
|
71
|
-
"\u591A\u5305\u7BA1\u7406",
|
|
72
|
-
"\u5171\u4EAB\u7EC4\u4EF6"
|
|
73
|
-
],
|
|
67
|
+
features: ["bun workspace", "Monorepo", "\u591A\u5305\u7BA1\u7406", "\u5171\u4EAB\u7EC4\u4EF6"],
|
|
74
68
|
version: "full"
|
|
75
69
|
}
|
|
76
70
|
}
|
|
@@ -83,12 +77,7 @@ var TEMPLATE_CATEGORIES = {
|
|
|
83
77
|
description: "\u57FA\u4E8E MicroApp \u7684\u5FAE\u524D\u7AEF\u67B6\u6784\u65B9\u6848",
|
|
84
78
|
repoUrl: "https://github.com/ChenyCHENYU/Robot_Admin",
|
|
85
79
|
branch: "micro-app",
|
|
86
|
-
features: [
|
|
87
|
-
"MicroApp",
|
|
88
|
-
"\u5FAE\u524D\u7AEF",
|
|
89
|
-
"\u4E3B\u5B50\u5E94\u7528",
|
|
90
|
-
"\u8DEF\u7531\u5171\u4EAB"
|
|
91
|
-
],
|
|
80
|
+
features: ["MicroApp", "\u5FAE\u524D\u7AEF", "\u4E3B\u5B50\u5E94\u7528", "\u8DEF\u7531\u5171\u4EAB"],
|
|
92
81
|
version: "full"
|
|
93
82
|
},
|
|
94
83
|
"robot-module-federation": {
|
|
@@ -96,12 +85,7 @@ var TEMPLATE_CATEGORIES = {
|
|
|
96
85
|
description: "\u57FA\u4E8E Vite Module Federation \u7684\u6A21\u5757\u8054\u90A6\u65B9\u6848",
|
|
97
86
|
repoUrl: "https://github.com/ChenyCHENYU/Robot_Admin",
|
|
98
87
|
branch: "module-federation",
|
|
99
|
-
features: [
|
|
100
|
-
"Module Federation",
|
|
101
|
-
"\u6A21\u5757\u8054\u90A6",
|
|
102
|
-
"Vite",
|
|
103
|
-
"\u8FDC\u7A0B\u6A21\u5757"
|
|
104
|
-
],
|
|
88
|
+
features: ["Module Federation", "\u6A21\u5757\u8054\u90A6", "Vite", "\u8FDC\u7A0B\u6A21\u5757"],
|
|
105
89
|
version: "full"
|
|
106
90
|
}
|
|
107
91
|
}
|
|
@@ -135,7 +119,7 @@ var TEMPLATE_CATEGORIES = {
|
|
|
135
119
|
}
|
|
136
120
|
},
|
|
137
121
|
mobile: {
|
|
138
|
-
name: "\
|
|
122
|
+
name: "\u79FB\u52A8\u7AEF\u9879\u76EE",
|
|
139
123
|
stacks: {
|
|
140
124
|
uniapp: {
|
|
141
125
|
name: "uni-app",
|
|
@@ -164,7 +148,7 @@ var TEMPLATE_CATEGORIES = {
|
|
|
164
148
|
}
|
|
165
149
|
},
|
|
166
150
|
backend: {
|
|
167
|
-
name: "\
|
|
151
|
+
name: "\u540E\u7AEF\u9879\u76EE",
|
|
168
152
|
stacks: {
|
|
169
153
|
nestjs: {
|
|
170
154
|
name: "NestJS",
|
|
@@ -212,7 +196,7 @@ var TEMPLATE_CATEGORIES = {
|
|
|
212
196
|
}
|
|
213
197
|
},
|
|
214
198
|
desktop: {
|
|
215
|
-
name: "\
|
|
199
|
+
name: "\u684C\u9762\u7AEF\u9879\u76EE",
|
|
216
200
|
stacks: {
|
|
217
201
|
electron: {
|
|
218
202
|
name: "Electron",
|
|
@@ -354,35 +338,70 @@ async function getCacheStats() {
|
|
|
354
338
|
async function clearCache() {
|
|
355
339
|
await fs.remove(CACHE_DIR);
|
|
356
340
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
341
|
+
var TIMEOUT_PRIMARY = 12e4;
|
|
342
|
+
var TIMEOUT_MIRROR = 6e4;
|
|
343
|
+
var MAX_RETRIES = 3;
|
|
344
|
+
function getGitHubMirrors(repoUrl) {
|
|
345
|
+
return [
|
|
346
|
+
repoUrl,
|
|
347
|
+
// 1. 原始 GitHub
|
|
348
|
+
repoUrl.replace("github.com", "hub.gitmirror.com"),
|
|
349
|
+
// 2. gitmirror
|
|
350
|
+
`https://ghproxy.net/${repoUrl}`
|
|
351
|
+
// 3. ghproxy.net
|
|
352
|
+
];
|
|
353
|
+
}
|
|
354
|
+
async function fetchWithRetry(downloadUrl, timeout, retries) {
|
|
355
|
+
let lastError;
|
|
356
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
365
357
|
try {
|
|
366
|
-
if (spinner) spinner.text = `\u{1F50D} \u8FDE\u63A5\u5230 ${sourceName}...`;
|
|
367
|
-
const downloadUrl = current.endsWith(".zip") ? current : buildDownloadUrl(current, branch);
|
|
368
|
-
if (spinner) spinner.text = `\u{1F4E6} \u4ECE ${sourceName} \u4E0B\u8F7D\u6A21\u677F...`;
|
|
369
358
|
const response = await fetch(downloadUrl, {
|
|
370
|
-
signal: AbortSignal.timeout(
|
|
359
|
+
signal: AbortSignal.timeout(timeout),
|
|
371
360
|
headers: { "User-Agent": "Robot-CLI" }
|
|
372
361
|
});
|
|
373
362
|
if (!response.ok) {
|
|
374
|
-
if (response.status === 404) throw new Error(`\u4ED3\u5E93\u4E0D\u5B58\u5728
|
|
363
|
+
if (response.status === 404) throw new Error(`\u4ED3\u5E93\u4E0D\u5B58\u5728 (404)`);
|
|
375
364
|
throw new Error(`HTTP ${response.status}`);
|
|
376
365
|
}
|
|
366
|
+
return response;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
lastError = error;
|
|
369
|
+
if (lastError.message.includes("404")) throw lastError;
|
|
370
|
+
if (attempt < retries) {
|
|
371
|
+
await new Promise((r) => setTimeout(r, 2e3 * attempt));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
throw lastError;
|
|
376
|
+
}
|
|
377
|
+
async function tryDownload(repoUrl, branch = "main", spinner) {
|
|
378
|
+
const url = new URL(repoUrl);
|
|
379
|
+
const host = url.hostname;
|
|
380
|
+
const mirrors = host === "github.com" ? getGitHubMirrors(repoUrl) : [repoUrl];
|
|
381
|
+
for (let i = 0; i < mirrors.length; i++) {
|
|
382
|
+
const current = mirrors[i];
|
|
383
|
+
const isOriginal = i === 0;
|
|
384
|
+
let sourceName;
|
|
385
|
+
try {
|
|
386
|
+
sourceName = isOriginal ? host : new URL(current.replace(/^(https?:\/\/[^/]+)\/.*/, "$1")).hostname;
|
|
387
|
+
} catch {
|
|
388
|
+
sourceName = `\u955C\u50CF ${i}`;
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
if (spinner) spinner.text = `\u8FDE\u63A5 ${sourceName} ...`;
|
|
392
|
+
const downloadUrl = current.endsWith(".zip") ? current : buildDownloadUrl(current, branch);
|
|
393
|
+
const timeout = isOriginal ? TIMEOUT_PRIMARY : TIMEOUT_MIRROR;
|
|
394
|
+
if (spinner) spinner.text = `\u4ECE ${sourceName} \u4E0B\u8F7D\u6A21\u677F (\u6700\u591A\u91CD\u8BD5${MAX_RETRIES}\u6B21)...`;
|
|
395
|
+
const response = await fetchWithRetry(downloadUrl, timeout, MAX_RETRIES);
|
|
377
396
|
if (spinner) {
|
|
378
397
|
const len = response.headers.get("content-length");
|
|
379
398
|
const sizeInfo = len ? `${(parseInt(len) / 1024 / 1024).toFixed(1)}MB ` : "";
|
|
380
|
-
spinner.text = `\
|
|
399
|
+
spinner.text = `\u4E0B\u8F7D\u4E2D ${sizeInfo}(${sourceName})`;
|
|
381
400
|
}
|
|
382
401
|
return { response, sourceName };
|
|
383
402
|
} catch (error) {
|
|
384
403
|
if (i === mirrors.length - 1) throw error;
|
|
385
|
-
if (spinner) spinner.text =
|
|
404
|
+
if (spinner) spinner.text = `${sourceName} \u4E0D\u53EF\u7528\uFF0C\u5207\u6362\u4E0B\u4E00\u4E2A\u6E90...`;
|
|
386
405
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
387
406
|
}
|
|
388
407
|
}
|
|
@@ -395,21 +414,21 @@ async function downloadTemplate(template, options = {}) {
|
|
|
395
414
|
throw new Error(`\u6A21\u677F\u914D\u7F6E\u65E0\u6548: ${JSON.stringify(template)}`);
|
|
396
415
|
}
|
|
397
416
|
try {
|
|
398
|
-
if (spinner) spinner.text = "\
|
|
417
|
+
if (spinner) spinner.text = "\u5F00\u59CB\u4E0B\u8F7D\u6700\u65B0\u6A21\u677F...";
|
|
399
418
|
const { response, sourceName } = await tryDownload(
|
|
400
419
|
template.repoUrl,
|
|
401
420
|
branch,
|
|
402
421
|
spinner
|
|
403
422
|
);
|
|
404
|
-
if (spinner) spinner.text = "\
|
|
423
|
+
if (spinner) spinner.text = "\u4FDD\u5B58\u4E0B\u8F7D\u6587\u4EF6...";
|
|
405
424
|
const timestamp = Date.now();
|
|
406
425
|
const tempZip = path.join(os.tmpdir(), `robot-template-${timestamp}.zip`);
|
|
407
426
|
const tempExtract = path.join(os.tmpdir(), `robot-extract-${timestamp}`);
|
|
408
427
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
409
428
|
await fs.writeFile(tempZip, buffer);
|
|
410
|
-
if (spinner) spinner.text = "\
|
|
429
|
+
if (spinner) spinner.text = "\u89E3\u538B\u6A21\u677F\u6587\u4EF6...";
|
|
411
430
|
await extract(tempZip, { dir: tempExtract });
|
|
412
|
-
if (spinner) spinner.text = "\
|
|
431
|
+
if (spinner) spinner.text = "\u67E5\u627E\u9879\u76EE\u7ED3\u6784...";
|
|
413
432
|
const extractedItems = await fs.readdir(tempExtract);
|
|
414
433
|
const repoName = template.repoUrl.split("/").pop() || "";
|
|
415
434
|
const projectDir = extractedItems.find(
|
|
@@ -421,7 +440,7 @@ async function downloadTemplate(template, options = {}) {
|
|
|
421
440
|
);
|
|
422
441
|
}
|
|
423
442
|
const sourcePath = path.join(tempExtract, projectDir);
|
|
424
|
-
if (spinner) spinner.text = "\
|
|
443
|
+
if (spinner) spinner.text = "\u9A8C\u8BC1\u6A21\u677F\u5B8C\u6574\u6027...";
|
|
425
444
|
if (!fs.existsSync(path.join(sourcePath, "package.json"))) {
|
|
426
445
|
throw new Error("\u6A21\u677F\u7F3A\u5C11 package.json \u6587\u4EF6");
|
|
427
446
|
}
|
|
@@ -429,7 +448,7 @@ async function downloadTemplate(template, options = {}) {
|
|
|
429
448
|
saveToCache(template.repoUrl, sourcePath, branch).catch(() => {
|
|
430
449
|
});
|
|
431
450
|
}
|
|
432
|
-
if (spinner) spinner.text = `\
|
|
451
|
+
if (spinner) spinner.text = `\u6A21\u677F\u4E0B\u8F7D\u5B8C\u6210 (via ${sourceName})`;
|
|
433
452
|
await fs.remove(tempZip).catch(() => {
|
|
434
453
|
});
|
|
435
454
|
return sourcePath;
|
|
@@ -437,9 +456,9 @@ async function downloadTemplate(template, options = {}) {
|
|
|
437
456
|
if (!noCache) {
|
|
438
457
|
const cached = await getCachedTemplate(template.repoUrl);
|
|
439
458
|
if (cached) {
|
|
440
|
-
if (spinner) spinner.text = "\
|
|
459
|
+
if (spinner) spinner.text = "\u7F51\u7EDC\u4E0D\u53EF\u7528\uFF0C\u4F7F\u7528\u7F13\u5B58\u6A21\u677F...";
|
|
441
460
|
console.log();
|
|
442
|
-
console.log(` ${chalk.yellow("\
|
|
461
|
+
console.log(` ${chalk.yellow(" \u6CE8\u610F: \u4F7F\u7528\u7F13\u5B58\u7248\u672C\uFF08\u975E\u6700\u65B0\uFF09")}`);
|
|
443
462
|
return cached;
|
|
444
463
|
}
|
|
445
464
|
}
|
|
@@ -457,9 +476,9 @@ async function downloadTemplate(template, options = {}) {
|
|
|
457
476
|
const isTimeout = errMsg.includes("aborted") || errMsg.includes("timeout") || downloadError.name === "TimeoutError";
|
|
458
477
|
let msg = `\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25: ${errMsg}`;
|
|
459
478
|
if (isTimeout) {
|
|
460
|
-
msg += "\n\n\
|
|
479
|
+
msg += "\n\n \u5EFA\u8BAE:\n 1. \u5F53\u524D\u7F51\u7EDC\u8FDE\u63A5\u8F83\u6162\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\n 2. \u5982\u679C\u5728\u56FD\u5185\uFF0C\u5C1D\u8BD5\u4F7F\u7528\u4EE3\u7406\u6216\u79D1\u5B66\u4E0A\u7F51\n 3. \u4F7F\u7528 robot doctor \u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5";
|
|
461
480
|
} else if (downloadError.code === "ENOTFOUND") {
|
|
462
|
-
msg += "\n\n\
|
|
481
|
+
msg += "\n\n \u5EFA\u8BAE:\n 1. \u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u662F\u5426\u6B63\u5E38\n 2. \u5982\u679C\u5728\u56FD\u5185\uFF0C\u5C1D\u8BD5\u4F7F\u7528\u4EE3\u7406\n 3. \u7A0D\u540E\u91CD\u8BD5";
|
|
463
482
|
}
|
|
464
483
|
throw new Error(msg);
|
|
465
484
|
}
|
|
@@ -638,9 +657,9 @@ async function copyTemplate(sourcePath, targetPath, spinner) {
|
|
|
638
657
|
throw new Error(`\u6E90\u8DEF\u5F84\u4E0D\u5B58\u5728: ${sourcePath}`);
|
|
639
658
|
}
|
|
640
659
|
await fs2.ensureDir(targetPath);
|
|
641
|
-
if (spinner) spinner.text = "\
|
|
660
|
+
if (spinner) spinner.text = "\u7EDF\u8BA1\u6587\u4EF6\u6570\u91CF...";
|
|
642
661
|
const totalFiles = await countFiles(sourcePath);
|
|
643
|
-
if (spinner) spinner.text = `\
|
|
662
|
+
if (spinner) spinner.text = `\u5F00\u59CB\u590D\u5236 ${totalFiles} \u4E2A\u6587\u4EF6...`;
|
|
644
663
|
let copied = 0;
|
|
645
664
|
const skipDirs = /* @__PURE__ */ new Set([
|
|
646
665
|
"node_modules",
|
|
@@ -664,19 +683,19 @@ async function copyTemplate(sourcePath, targetPath, spinner) {
|
|
|
664
683
|
copied++;
|
|
665
684
|
if (spinner && (copied % 10 === 0 || copied === totalFiles)) {
|
|
666
685
|
const pct = Math.round(copied / totalFiles * 100);
|
|
667
|
-
spinner.text = `\
|
|
686
|
+
spinner.text = `\u590D\u5236\u4E2D ${copied}/${totalFiles} (${pct}%)`;
|
|
668
687
|
}
|
|
669
688
|
}
|
|
670
689
|
}
|
|
671
690
|
}
|
|
672
691
|
await copyDir(sourcePath, targetPath);
|
|
673
|
-
if (spinner) spinner.text = `\
|
|
692
|
+
if (spinner) spinner.text = `\u6587\u4EF6\u590D\u5236\u5B8C\u6210 (${copied} \u4E2A\u6587\u4EF6)`;
|
|
674
693
|
}
|
|
675
694
|
async function installDependencies(projectPath, spinner, packageManager = "npm") {
|
|
676
695
|
try {
|
|
677
696
|
const pkgJson = path2.join(projectPath, "package.json");
|
|
678
697
|
if (!fs2.existsSync(pkgJson)) {
|
|
679
|
-
if (spinner) spinner.text = "\
|
|
698
|
+
if (spinner) spinner.text = "\u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5 (\u65E0 package.json)";
|
|
680
699
|
return;
|
|
681
700
|
}
|
|
682
701
|
const cmds = {
|
|
@@ -685,20 +704,20 @@ async function installDependencies(projectPath, spinner, packageManager = "npm")
|
|
|
685
704
|
yarn: "yarn install",
|
|
686
705
|
npm: "npm install"
|
|
687
706
|
};
|
|
688
|
-
if (spinner) spinner.text = `\
|
|
707
|
+
if (spinner) spinner.text = `\u4F7F\u7528 ${packageManager} \u5B89\u88C5\u4F9D\u8D56...`;
|
|
689
708
|
execSync(cmds[packageManager], {
|
|
690
709
|
cwd: projectPath,
|
|
691
710
|
stdio: "ignore",
|
|
692
711
|
timeout: 3e5
|
|
693
712
|
});
|
|
694
|
-
if (spinner) spinner.text = `\
|
|
713
|
+
if (spinner) spinner.text = `\u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210 (${packageManager})`;
|
|
695
714
|
} catch (error) {
|
|
696
|
-
if (spinner) spinner.text = "\
|
|
715
|
+
if (spinner) spinner.text = "\u4F9D\u8D56\u5B89\u88C5\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5";
|
|
697
716
|
console.log();
|
|
698
|
-
console.log(chalk2.yellow("\
|
|
717
|
+
console.log(chalk2.yellow("\u81EA\u52A8\u5B89\u88C5\u4F9D\u8D56\u5931\u8D25"));
|
|
699
718
|
console.log(chalk2.dim(` \u9519\u8BEF: ${error.message}`));
|
|
700
719
|
console.log();
|
|
701
|
-
console.log(chalk2.blue("\
|
|
720
|
+
console.log(chalk2.blue("\u8BF7\u624B\u52A8\u5B89\u88C5:"));
|
|
702
721
|
console.log(chalk2.cyan(` cd ${path2.basename(projectPath)}`));
|
|
703
722
|
console.log(chalk2.cyan(` ${packageManager} install`));
|
|
704
723
|
console.log();
|
|
@@ -738,7 +757,7 @@ async function generateProjectStats(projectPath) {
|
|
|
738
757
|
}
|
|
739
758
|
}
|
|
740
759
|
function printProjectStats(stats) {
|
|
741
|
-
console.log(chalk2.blue("\
|
|
760
|
+
console.log(chalk2.blue("\u9879\u76EE\u7EDF\u8BA1:"));
|
|
742
761
|
console.log(` \u6587\u4EF6\u6570\u91CF: ${chalk2.cyan(String(stats.files))} \u4E2A`);
|
|
743
762
|
console.log(` \u76EE\u5F55\u6570\u91CF: ${chalk2.cyan(String(stats.directories))} \u4E2A`);
|
|
744
763
|
console.log(` \u9879\u76EE\u5927\u5C0F: ${chalk2.cyan(formatBytes(stats.size))}`);
|
|
@@ -791,7 +810,7 @@ async function createProject(projectName, options = {}) {
|
|
|
791
810
|
if (options.dryRun) {
|
|
792
811
|
console.log();
|
|
793
812
|
console.log(
|
|
794
|
-
chalk3.yellow("
|
|
813
|
+
chalk3.yellow("Dry Run \u6A21\u5F0F - \u4EE5\u4E0B\u4E3A\u9884\u89C8\u4FE1\u606F\uFF0C\u672A\u5B9E\u9645\u6267\u884C\u4EFB\u4F55\u64CD\u4F5C:")
|
|
795
814
|
);
|
|
796
815
|
console.log();
|
|
797
816
|
console.log(` \u9879\u76EE\u8DEF\u5F84: ${chalk3.cyan(path3.resolve(finalProjectName))}`);
|
|
@@ -823,7 +842,7 @@ async function handleProjectName(projectName, template) {
|
|
|
823
842
|
if (projectName) {
|
|
824
843
|
const v = validateProjectName(projectName);
|
|
825
844
|
if (!v.valid) {
|
|
826
|
-
console.log(chalk3.red("\
|
|
845
|
+
console.log(chalk3.red("\u9879\u76EE\u540D\u79F0\u4E0D\u5408\u6CD5:"));
|
|
827
846
|
v.errors.forEach((e) => console.log(chalk3.red(` ${e}`)));
|
|
828
847
|
console.log();
|
|
829
848
|
const { newName } = await inquirer.prompt([
|
|
@@ -869,7 +888,7 @@ async function selectTemplate(templateOption) {
|
|
|
869
888
|
if (all[templateOption]) {
|
|
870
889
|
return { key: templateOption, ...all[templateOption] };
|
|
871
890
|
}
|
|
872
|
-
console.log(chalk3.yellow(`\
|
|
891
|
+
console.log(chalk3.yellow(`\u6A21\u677F "${templateOption}" \u4E0D\u5B58\u5728`));
|
|
873
892
|
console.log();
|
|
874
893
|
}
|
|
875
894
|
return await selectTemplateMethod();
|
|
@@ -921,7 +940,7 @@ async function selectTemplateMethod() {
|
|
|
921
940
|
async function selectFromRecommended() {
|
|
922
941
|
const recommended = getRecommendedTemplates();
|
|
923
942
|
if (Object.keys(recommended).length === 0) {
|
|
924
|
-
console.log(chalk3.yellow("\
|
|
943
|
+
console.log(chalk3.yellow("\u6682\u65E0\u63A8\u8350\u6A21\u677F"));
|
|
925
944
|
return await selectTemplateMethod();
|
|
926
945
|
}
|
|
927
946
|
console.log();
|
|
@@ -1323,17 +1342,17 @@ async function executeCreation(projectName, template, config, options) {
|
|
|
1323
1342
|
if (!template?.name)
|
|
1324
1343
|
throw new Error(`\u6A21\u677F\u6570\u636E\u65E0\u6548: ${JSON.stringify(template)}`);
|
|
1325
1344
|
const spinner = ora({
|
|
1326
|
-
text: "\
|
|
1345
|
+
text: "\u51C6\u5907\u521B\u5EFA\u9879\u76EE...",
|
|
1327
1346
|
spinner: "dots",
|
|
1328
1347
|
color: "cyan"
|
|
1329
1348
|
}).start();
|
|
1330
1349
|
let tempPath;
|
|
1331
1350
|
try {
|
|
1332
|
-
spinner.text = "\
|
|
1351
|
+
spinner.text = "\u68C0\u67E5\u9879\u76EE\u76EE\u5F55...";
|
|
1333
1352
|
const projectPath = path3.resolve(projectName);
|
|
1334
1353
|
if (fs3.existsSync(projectPath)) {
|
|
1335
1354
|
spinner.stop();
|
|
1336
|
-
console.log(chalk3.yellow("\
|
|
1355
|
+
console.log(chalk3.yellow("\u9879\u76EE\u76EE\u5F55\u5DF2\u5B58\u5728"));
|
|
1337
1356
|
const { overwrite } = await inquirer.prompt([
|
|
1338
1357
|
{
|
|
1339
1358
|
type: "confirm",
|
|
@@ -1343,14 +1362,14 @@ async function executeCreation(projectName, template, config, options) {
|
|
|
1343
1362
|
}
|
|
1344
1363
|
]);
|
|
1345
1364
|
if (!overwrite) {
|
|
1346
|
-
console.log(chalk3.yellow("\
|
|
1365
|
+
console.log(chalk3.yellow("\u53D6\u6D88\u521B\u5EFA"));
|
|
1347
1366
|
process.exit(0);
|
|
1348
1367
|
}
|
|
1349
|
-
spinner.start("\
|
|
1368
|
+
spinner.start("\u6E05\u7406\u73B0\u6709\u76EE\u5F55...");
|
|
1350
1369
|
await fs3.remove(projectPath);
|
|
1351
|
-
spinner.text = "\
|
|
1370
|
+
spinner.text = "\u51C6\u5907\u521B\u5EFA\u65B0\u76EE\u5F55...";
|
|
1352
1371
|
}
|
|
1353
|
-
spinner.text = "\
|
|
1372
|
+
spinner.text = "\u4E0B\u8F7D\u6700\u65B0\u6A21\u677F...";
|
|
1354
1373
|
try {
|
|
1355
1374
|
tempPath = await downloadTemplate(template, {
|
|
1356
1375
|
spinner,
|
|
@@ -1361,28 +1380,27 @@ async function executeCreation(projectName, template, config, options) {
|
|
|
1361
1380
|
} catch (error) {
|
|
1362
1381
|
spinner.fail("\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25");
|
|
1363
1382
|
console.log();
|
|
1364
|
-
console.log(chalk3.
|
|
1365
|
-
console.log(chalk3.dim(` ${error.message}`));
|
|
1383
|
+
console.log(chalk3.dim(` ${error.message}`));
|
|
1366
1384
|
console.log();
|
|
1367
|
-
|
|
1385
|
+
return;
|
|
1368
1386
|
}
|
|
1369
1387
|
await copyTemplate(tempPath, projectPath, spinner);
|
|
1370
|
-
spinner.text = "\
|
|
1388
|
+
spinner.text = "\u5904\u7406\u9879\u76EE\u914D\u7F6E...";
|
|
1371
1389
|
await processProjectConfig(projectPath, projectName, template, config);
|
|
1372
1390
|
if (config.initGit) {
|
|
1373
|
-
spinner.text = "\
|
|
1391
|
+
spinner.text = "\u521D\u59CB\u5316 Git \u4ED3\u5E93...";
|
|
1374
1392
|
initializeGitRepository(projectPath);
|
|
1375
1393
|
}
|
|
1376
1394
|
if (config.installDeps) {
|
|
1377
|
-
spinner.text = `\
|
|
1395
|
+
spinner.text = `\u4F7F\u7528 ${config.packageManager} \u5B89\u88C5\u4F9D\u8D56...`;
|
|
1378
1396
|
await installDependencies(projectPath, spinner, config.packageManager);
|
|
1379
1397
|
}
|
|
1380
1398
|
if (tempPath) {
|
|
1381
|
-
spinner.text = "\
|
|
1399
|
+
spinner.text = "\u6E05\u7406\u4E34\u65F6\u6587\u4EF6...";
|
|
1382
1400
|
await fs3.remove(tempPath).catch(() => {
|
|
1383
1401
|
});
|
|
1384
1402
|
}
|
|
1385
|
-
spinner.succeed(chalk3.green("\
|
|
1403
|
+
spinner.succeed(chalk3.green("\u9879\u76EE\u521B\u5EFA\u6210\u529F!"));
|
|
1386
1404
|
console.log();
|
|
1387
1405
|
console.log(chalk3.green("\u9879\u76EE\u521B\u5EFA\u5B8C\u6210!"));
|
|
1388
1406
|
console.log();
|
|
@@ -1411,7 +1429,7 @@ async function executeCreation(projectName, template, config, options) {
|
|
|
1411
1429
|
chalk3.dim(" # \u6216\u4F7F\u7528 bun: bun install && bun run dev (\u5982\u5DF2\u5B89\u88C5)")
|
|
1412
1430
|
);
|
|
1413
1431
|
console.log();
|
|
1414
|
-
spinner.start("\
|
|
1432
|
+
spinner.start("\u7EDF\u8BA1\u9879\u76EE\u4FE1\u606F...");
|
|
1415
1433
|
const stats = await generateProjectStats(projectPath);
|
|
1416
1434
|
spinner.stop();
|
|
1417
1435
|
if (stats) {
|
|
@@ -1467,7 +1485,7 @@ function initializeGitRepository(projectPath) {
|
|
|
1467
1485
|
stdio: "ignore"
|
|
1468
1486
|
});
|
|
1469
1487
|
} catch {
|
|
1470
|
-
console.log(chalk3.yellow("
|
|
1488
|
+
console.log(chalk3.yellow("Git \u4E0D\u53EF\u7528\uFF0C\u8DF3\u8FC7\u4ED3\u5E93\u521D\u59CB\u5316"));
|
|
1471
1489
|
}
|
|
1472
1490
|
}
|
|
1473
1491
|
function getStartCommand(template, pm) {
|
|
@@ -1538,9 +1556,9 @@ async function runDoctor(options = {}) {
|
|
|
1538
1556
|
detail: cacheStats.count > 0 ? `${cacheStats.count} \u4E2A\u6A21\u677F (${formatBytes(cacheStats.totalSize)})` : "\u7A7A"
|
|
1539
1557
|
});
|
|
1540
1558
|
const icons = {
|
|
1541
|
-
ok: chalk4.green("
|
|
1542
|
-
warn: chalk4.yellow("
|
|
1543
|
-
error: chalk4.red("
|
|
1559
|
+
ok: chalk4.green("[OK]"),
|
|
1560
|
+
warn: chalk4.yellow("[!!]"),
|
|
1561
|
+
error: chalk4.red("[NO]")
|
|
1544
1562
|
};
|
|
1545
1563
|
for (const r of results) {
|
|
1546
1564
|
const icon = icons[r.status];
|
|
@@ -1550,11 +1568,11 @@ async function runDoctor(options = {}) {
|
|
|
1550
1568
|
const hasError = results.some((r) => r.status === "error");
|
|
1551
1569
|
const hasWarn = results.some((r) => r.status === "warn");
|
|
1552
1570
|
if (hasError) {
|
|
1553
|
-
console.log(chalk4.red(" \u8BCA\u65AD\u7ED3\u679C: \u73AF\u5883\u5B58\u5728\u95EE\u9898\uFF0C\u8BF7\u4FEE\u590D\u4E0A\u8FF0\u9519\u8BEF
|
|
1571
|
+
console.log(chalk4.red(" \u8BCA\u65AD\u7ED3\u679C: \u73AF\u5883\u5B58\u5728\u95EE\u9898\uFF0C\u8BF7\u4FEE\u590D\u4E0A\u8FF0\u9519\u8BEF"));
|
|
1554
1572
|
} else if (hasWarn) {
|
|
1555
|
-
console.log(chalk4.yellow(" \u8BCA\u65AD\u7ED3\u679C: \u73AF\u5883\u57FA\u672C\u6B63\u5E38\uFF0C\u90E8\u5206\u7EC4\u4EF6\u7F3A\u5931
|
|
1573
|
+
console.log(chalk4.yellow(" \u8BCA\u65AD\u7ED3\u679C: \u73AF\u5883\u57FA\u672C\u6B63\u5E38\uFF0C\u90E8\u5206\u7EC4\u4EF6\u7F3A\u5931"));
|
|
1556
1574
|
} else {
|
|
1557
|
-
console.log(chalk4.green(" \u8BCA\u65AD\u7ED3\u679C: \u73AF\u5883\u5065\u5EB7
|
|
1575
|
+
console.log(chalk4.green(" \u8BCA\u65AD\u7ED3\u679C: \u73AF\u5883\u5065\u5EB7"));
|
|
1558
1576
|
}
|
|
1559
1577
|
console.log();
|
|
1560
1578
|
if (options.clearCache) {
|
|
@@ -1578,20 +1596,18 @@ function getPackageVersion() {
|
|
|
1578
1596
|
}
|
|
1579
1597
|
}
|
|
1580
1598
|
var VERSION = getPackageVersion();
|
|
1581
|
-
var robotGradient = gradient(["#36d1dc", "#5b86e5"]);
|
|
1582
1599
|
function showWelcome() {
|
|
1583
|
-
const
|
|
1584
|
-
" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
1585
|
-
" \u2551 \u2551",
|
|
1586
|
-
" \u2551 R O B O T C L I \u2551",
|
|
1587
|
-
" \u2551 \u2551",
|
|
1588
|
-
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
1589
|
-
].join("\n");
|
|
1590
|
-
console.log();
|
|
1591
|
-
console.log(robotGradient.multiline(bannerLines));
|
|
1600
|
+
const supportsColor = process.stdout.isTTY && process.stdout.getColorDepth() > 1;
|
|
1592
1601
|
console.log();
|
|
1593
|
-
|
|
1594
|
-
|
|
1602
|
+
if (supportsColor) {
|
|
1603
|
+
console.log(chalk5.bold.cyan(" R O B O T C L I"));
|
|
1604
|
+
} else {
|
|
1605
|
+
console.log(" R O B O T C L I");
|
|
1606
|
+
}
|
|
1607
|
+
console.log(
|
|
1608
|
+
chalk5.dim(` v${VERSION} | ${chalk5.reset.dim("\u5DE5\u7A0B\u5316\u9879\u76EE\u811A\u624B\u67B6")}`)
|
|
1609
|
+
);
|
|
1610
|
+
console.log(chalk5.dim(" \u524D\u7AEF / \u540E\u7AEF / \u79FB\u52A8\u7AEF / \u684C\u9762\u7AEF"));
|
|
1595
1611
|
console.log();
|
|
1596
1612
|
}
|
|
1597
1613
|
async function showMainMenu() {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agile-team/robot-cli",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "现代化项目脚手架工具,支持多技术栈快速创建项目 - 优先 bun,兼容 npm/pnpm/yarn",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"robot": "bin/robot.js"
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"commander": "^11.0.0",
|
|
25
25
|
"extract-zip": "^2.0.1",
|
|
26
26
|
"fs-extra": "^11.1.0",
|
|
27
|
-
"gradient-string": "^3.0.0",
|
|
28
27
|
"inquirer": "^9.2.0",
|
|
29
28
|
"ora": "^7.0.0"
|
|
30
29
|
},
|
|
@@ -74,4 +73,4 @@
|
|
|
74
73
|
"publishConfig": {
|
|
75
74
|
"access": "public"
|
|
76
75
|
}
|
|
77
|
-
}
|
|
76
|
+
}
|