@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 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.0.0] - 2026-03-28
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
- # 🤖 Robot CLI
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
- ### 🎨 前端项目(Vue.js)
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
- ### 🎨 前端项目(React.js)
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
- | 🥇 | **bun** | 极速安装,现代化 |
163
- | 🥈 | **pnpm** | 快速安装,节省磁盘 |
164
- | 🥉 | **yarn** | 兼容性好 |
165
- | 4️⃣ | **npm** | Node.js 内置 |
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: "\u{1F3A8} \u524D\u7AEF\u9879\u76EE",
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: "\u{1F4F1} \u79FB\u52A8\u7AEF\u9879\u76EE",
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: "\u{1F680} \u540E\u7AEF\u9879\u76EE",
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: "\u{1F4BB} \u684C\u9762\u7AEF\u9879\u76EE",
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
- async function tryDownload(repoUrl, branch = "main", spinner) {
358
- const url = new URL(repoUrl);
359
- const host = url.hostname;
360
- const mirrors = host === "github.com" ? [repoUrl, `https://ghproxy.com/${repoUrl}`] : [repoUrl];
361
- for (let i = 0; i < mirrors.length; i++) {
362
- const current = mirrors[i];
363
- const isOriginal = current === repoUrl;
364
- const sourceName = isOriginal ? `${host} \u5B98\u65B9` : `${host} \u955C\u50CF`;
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(isOriginal ? 15e3 : 1e4),
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: ${repoUrl}`);
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 = `\u{1F4E6} \u4E0B\u8F7D\u4E2D... (${sizeInfo}from ${sourceName})`;
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 = `\u26A0\uFE0F ${sourceName} \u8BBF\u95EE\u5931\u8D25\uFF0C\u5C1D\u8BD5\u5176\u4ED6\u6E90...`;
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 = "\u{1F310} \u5F00\u59CB\u4E0B\u8F7D\u6700\u65B0\u6A21\u677F...";
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 = "\u{1F4BE} \u4FDD\u5B58\u4E0B\u8F7D\u6587\u4EF6...";
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 = "\u{1F4C2} \u89E3\u538B\u6A21\u677F\u6587\u4EF6...";
429
+ if (spinner) spinner.text = "\u89E3\u538B\u6A21\u677F\u6587\u4EF6...";
411
430
  await extract(tempZip, { dir: tempExtract });
412
- if (spinner) spinner.text = "\u{1F50D} \u67E5\u627E\u9879\u76EE\u7ED3\u6784...";
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 = "\u2705 \u9A8C\u8BC1\u6A21\u677F\u5B8C\u6574\u6027...";
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 = `\u{1F389} \u6A21\u677F\u4E0B\u8F7D\u5B8C\u6210 (via ${sourceName})`;
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 = "\u{1F4E6} \u7F51\u7EDC\u4E0D\u53EF\u7528\uFF0C\u4F7F\u7528\u7F13\u5B58\u6A21\u677F...";
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("\u26A0\uFE0F \u4F7F\u7528\u7F13\u5B58\u7248\u672C\uFF08\u975E\u6700\u65B0\uFF09")}`);
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\u{1F4A1} \u5EFA\u8BAE:\n1. \u5F53\u524D\u7F51\u7EDC\u8FDE\u63A5\u8F83\u6162\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\n2. \u5982\u679C\u5728\u56FD\u5185\uFF0C\u5C1D\u8BD5\u4F7F\u7528\u79D1\u5B66\u4E0A\u7F51\u6216\u914D\u7F6E\u4EE3\u7406\n3. \u4F7F\u7528 --no-cache \u5F3A\u5236\u91CD\u65B0\u4E0B\u8F7D";
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\u{1F4A1} \u5EFA\u8BAE:\n1. \u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\n2. \u5982\u679C\u5728\u56FD\u5185\uFF0C\u5C1D\u8BD5\u4F7F\u7528\u79D1\u5B66\u4E0A\u7F51\n3. \u7A0D\u540E\u91CD\u8BD5";
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 = "\u{1F4CA} \u7EDF\u8BA1\u6587\u4EF6\u6570\u91CF...";
660
+ if (spinner) spinner.text = "\u7EDF\u8BA1\u6587\u4EF6\u6570\u91CF...";
642
661
  const totalFiles = await countFiles(sourcePath);
643
- if (spinner) spinner.text = `\u{1F4CB} \u5F00\u59CB\u590D\u5236 ${totalFiles} \u4E2A\u6587\u4EF6...`;
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 = `\u{1F4CB} \u590D\u5236\u4E2D... ${copied}/${totalFiles} (${pct}%)`;
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 = `\u2705 \u6587\u4EF6\u590D\u5236\u5B8C\u6210 (${copied} \u4E2A\u6587\u4EF6)`;
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 = "\u26A0\uFE0F \u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5 (\u65E0 package.json)";
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 = `\u{1F4E6} \u4F7F\u7528 ${packageManager} \u5B89\u88C5\u4F9D\u8D56...`;
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 = `\u2705 \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210 (${packageManager})`;
713
+ if (spinner) spinner.text = `\u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210 (${packageManager})`;
695
714
  } catch (error) {
696
- if (spinner) spinner.text = "\u26A0\uFE0F \u4F9D\u8D56\u5B89\u88C5\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5";
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("\u26A0\uFE0F \u81EA\u52A8\u5B89\u88C5\u4F9D\u8D56\u5931\u8D25"));
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("\u{1F4A1} \u8BF7\u624B\u52A8\u5B89\u88C5:"));
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("\u{1F4CA} \u9879\u76EE\u7EDF\u8BA1:"));
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("\u{1F50D} Dry Run \u6A21\u5F0F - \u4EE5\u4E0B\u4E3A\u9884\u89C8\u4FE1\u606F\uFF0C\u672A\u5B9E\u9645\u6267\u884C\u4EFB\u4F55\u64CD\u4F5C:")
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("\u274C \u9879\u76EE\u540D\u79F0\u4E0D\u5408\u6CD5:"));
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(`\u26A0\uFE0F \u6A21\u677F "${templateOption}" \u4E0D\u5B58\u5728`));
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("\u26A0\uFE0F \u6682\u65E0\u63A8\u8350\u6A21\u677F"));
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: "\u{1F680} \u51C6\u5907\u521B\u5EFA\u9879\u76EE...",
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 = "\u{1F4C1} \u68C0\u67E5\u9879\u76EE\u76EE\u5F55...";
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("\u26A0\uFE0F \u9879\u76EE\u76EE\u5F55\u5DF2\u5B58\u5728"));
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("\u274C \u53D6\u6D88\u521B\u5EFA"));
1365
+ console.log(chalk3.yellow("\u53D6\u6D88\u521B\u5EFA"));
1347
1366
  process.exit(0);
1348
1367
  }
1349
- spinner.start("\u{1F5D1}\uFE0F \u6E05\u7406\u73B0\u6709\u76EE\u5F55...");
1368
+ spinner.start("\u6E05\u7406\u73B0\u6709\u76EE\u5F55...");
1350
1369
  await fs3.remove(projectPath);
1351
- spinner.text = "\u{1F4C1} \u51C6\u5907\u521B\u5EFA\u65B0\u76EE\u5F55...";
1370
+ spinner.text = "\u51C6\u5907\u521B\u5EFA\u65B0\u76EE\u5F55...";
1352
1371
  }
1353
- spinner.text = "\u{1F310} \u4E0B\u8F7D\u6700\u65B0\u6A21\u677F...";
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.red("\u274C \u6A21\u677F\u4E0B\u8F7D\u9519\u8BEF:"));
1365
- console.log(chalk3.dim(` ${error.message}`));
1383
+ console.log(chalk3.dim(` ${error.message}`));
1366
1384
  console.log();
1367
- throw error;
1385
+ return;
1368
1386
  }
1369
1387
  await copyTemplate(tempPath, projectPath, spinner);
1370
- spinner.text = "\u2699\uFE0F \u5904\u7406\u9879\u76EE\u914D\u7F6E...";
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 = "\u{1F4DD} \u521D\u59CB\u5316 Git \u4ED3\u5E93...";
1391
+ spinner.text = "\u521D\u59CB\u5316 Git \u4ED3\u5E93...";
1374
1392
  initializeGitRepository(projectPath);
1375
1393
  }
1376
1394
  if (config.installDeps) {
1377
- spinner.text = `\u{1F4E6} \u4F7F\u7528 ${config.packageManager} \u5B89\u88C5\u4F9D\u8D56...`;
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 = "\u{1F9F9} \u6E05\u7406\u4E34\u65F6\u6587\u4EF6...";
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("\u{1F389} \u9879\u76EE\u521B\u5EFA\u6210\u529F!"));
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("\u{1F4CA} \u7EDF\u8BA1\u9879\u76EE\u4FE1\u606F...");
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("\u26A0\uFE0F Git \u4E0D\u53EF\u7528\uFF0C\u8DF3\u8FC7\u4ED3\u5E93\u521D\u59CB\u5316"));
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("\u2705"),
1542
- warn: chalk4.yellow("\u26A0\uFE0F "),
1543
- error: chalk4.red("\u274C")
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 \u274C"));
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 \u26A0\uFE0F"));
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 \u2705"));
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 bannerLines = [
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
- console.log(chalk5.gray(` v${VERSION} \xB7 \u5DE5\u7A0B\u5316\u9879\u76EE\u811A\u624B\u67B6`));
1594
- console.log(chalk5.gray(" \u524D\u7AEF \xB7 \u540E\u7AEF \xB7 \u79FB\u52A8\u7AEF \xB7 \u684C\u9762\u7AEF"));
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.2.0",
4
- "description": "🤖 现代化项目脚手架工具,支持多技术栈快速创建项目 - 优先 bun,兼容 npm/pnpm/yarn",
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
+ }