@agile-team/robot-cli 3.0.3 → 3.0.5

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.js +148 -42
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ 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
+ ## [3.0.4] - 2026-03-29
9
+
10
+ ### Changed (Breaking)
11
+
12
+ - **彻底重写下载策略 — `git clone --depth=1` 作为主方案**
13
+ - 根本原因分析:HTTP ZIP 下载受到国内网络封锁、Gitee 登录拦截、Content-Type 不一致等各种干扰,是错误的技术路线
14
+ - `git clone --depth=1` 是 create-vue / create-vite / degit / giget 等所有主流 CLI 的底层实现
15
+ - 自动继承用户系统级 git 代理配置 (`http.proxy` / `https.proxy`),无需 CLI 工具自己处理网络问题
16
+ - 实时显示 git 进度条(解析 git stderr 百分比输出)
17
+ - 无超时问题:git 自己管理连接,有数据在流就不断
18
+ - 无 ZIP 解析问题:直接输出目录结构
19
+
20
+ - **优先顺序**:Gitee git clone(国内快)→ GitHub git clone → HTTP ZIP 兜底(仅当系统无 git 时)
21
+
22
+ - **缓存前置**:启动时先检查缓存,命中则直接用,不发起任何网络请求
23
+
8
24
  ## [3.0.3] - 2026-03-29
9
25
 
10
26
  ### Fixed
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import { execSync as execSync2 } from "child_process";
18
18
  import fs from "fs-extra";
19
19
  import path from "path";
20
20
  import os from "os";
21
+ import { spawn } from "child_process";
21
22
  import chalk from "chalk";
22
23
  import extract from "extract-zip";
23
24
 
@@ -53,7 +54,8 @@ var TEMPLATE_CATEGORIES = {
53
54
  description: "\u57FA\u7840\u67B6\u6784\u3001\u6838\u5FC3\u529F\u80FD\u3001\u5FEB\u901F\u542F\u52A8",
54
55
  repoUrl: "https://github.com/ChenyCHENYU/Robot_Admin_Base",
55
56
  features: ["Naive UI", "Vue Router", "Pinia", "\u57FA\u7840\u5E03\u5C40"],
56
- version: "base"
57
+ version: "base",
58
+ status: "coming-soon"
57
59
  }
58
60
  }
59
61
  },
@@ -107,14 +109,16 @@ var TEMPLATE_CATEGORIES = {
107
109
  description: "Ant Design + \u5B8C\u6574\u529F\u80FD\u6F14\u793A",
108
110
  repoUrl: "https://github.com/ChenyCHENYU/Robot_React",
109
111
  features: ["Ant Design", "React Router", "Redux Toolkit"],
110
- version: "full"
112
+ version: "full",
113
+ status: "coming-soon"
111
114
  },
112
115
  "robot-react-base": {
113
116
  name: "Robot React \u7CBE\u7B80\u7248",
114
117
  description: "\u57FA\u7840React + \u6838\u5FC3\u529F\u80FD",
115
118
  repoUrl: "https://github.com/ChenyCHENYU/Robot_React_Base",
116
119
  features: ["React", "React Router", "\u57FA\u7840\u7EC4\u4EF6"],
117
- version: "base"
120
+ version: "base",
121
+ status: "coming-soon"
118
122
  }
119
123
  }
120
124
  }
@@ -144,7 +148,8 @@ var TEMPLATE_CATEGORIES = {
144
148
  description: "\u57FA\u7840\u6846\u67B6 + \u6838\u5FC3\u529F\u80FD",
145
149
  repoUrl: "https://github.com/ChenyCHENYU/Robot_Uniapp_Base",
146
150
  features: ["\u57FA\u7840\u6846\u67B6", "\u8DEF\u7531\u914D\u7F6E"],
147
- version: "base"
151
+ version: "base",
152
+ status: "coming-soon"
148
153
  }
149
154
  }
150
155
  }
@@ -173,14 +178,16 @@ var TEMPLATE_CATEGORIES = {
173
178
  "Redis",
174
179
  "\u5FAE\u670D\u52A1"
175
180
  ],
176
- version: "full"
181
+ version: "full",
182
+ status: "coming-soon"
177
183
  },
178
184
  "robot-nest-base": {
179
185
  name: "Robot NestJS \u7CBE\u7B80\u7248",
180
186
  description: "\u57FA\u7840 NestJS + \u6838\u5FC3\u6A21\u5757",
181
187
  repoUrl: "https://github.com/ChenyCHENYU/Robot_Nest_Base",
182
188
  features: ["NestJS", "\u57FA\u7840\u8DEF\u7531", "\u9519\u8BEF\u5904\u7406"],
183
- version: "base"
189
+ version: "base",
190
+ status: "coming-soon"
184
191
  }
185
192
  }
186
193
  },
@@ -192,7 +199,8 @@ var TEMPLATE_CATEGORIES = {
192
199
  description: "NestJS + \u5FAE\u670D\u52A1\u67B6\u6784 + gRPC + \u670D\u52A1\u53D1\u73B0",
193
200
  repoUrl: "https://github.com/ChenyCHENYU/Robot_Nest_Micro",
194
201
  features: ["NestJS", "\u5FAE\u670D\u52A1", "gRPC", "Redis", "\u670D\u52A1\u53D1\u73B0"],
195
- version: "micro"
202
+ version: "micro",
203
+ status: "coming-soon"
196
204
  }
197
205
  }
198
206
  }
@@ -214,14 +222,16 @@ var TEMPLATE_CATEGORIES = {
214
222
  description: "Vue3 + Electron + \u81EA\u52A8\u66F4\u65B0 + \u539F\u751F\u80FD\u529B",
215
223
  repoUrl: "https://github.com/ChenyCHENYU/Robot_Electron",
216
224
  features: ["Vue3", "Electron", "\u81EA\u52A8\u66F4\u65B0", "\u539F\u751FAPI"],
217
- version: "full"
225
+ version: "full",
226
+ status: "coming-soon"
218
227
  },
219
228
  "robot-electron-base": {
220
229
  name: "Robot Electron \u7CBE\u7B80\u7248",
221
230
  description: "\u57FA\u7840Electron + Vue\u6846\u67B6",
222
231
  repoUrl: "https://github.com/ChenyCHENYU/Robot_Electron_Base",
223
232
  features: ["Vue3", "Electron", "\u57FA\u7840\u529F\u80FD"],
224
- version: "base"
233
+ version: "base",
234
+ status: "coming-soon"
225
235
  }
226
236
  }
227
237
  }
@@ -482,6 +492,43 @@ function assertZipBuffer(buffer, sourceName) {
482
492
  );
483
493
  }
484
494
  }
495
+ async function gitCloneTemplate(repoUrl, branch, targetDir, spinner) {
496
+ return new Promise((resolve, reject) => {
497
+ if (spinner) spinner.text = `\u8FDE\u63A5\u4E2D...`;
498
+ const args = [
499
+ "clone",
500
+ "--depth=1",
501
+ "--single-branch",
502
+ "--branch",
503
+ branch,
504
+ repoUrl,
505
+ targetDir
506
+ ];
507
+ const proc = spawn("git", args, { stdio: ["ignore", "ignore", "pipe"] });
508
+ proc.stderr?.on("data", (chunk) => {
509
+ if (!spinner) return;
510
+ const text3 = chunk.toString();
511
+ const pctMatch = text3.match(/(\d+)%\s*\((\d+)\/(\d+)\)/);
512
+ if (pctMatch) {
513
+ const pct = parseInt(pctMatch[1]);
514
+ const filled = Math.round(pct / 5);
515
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
516
+ const speed = text3.match(/([\d.]+\s*[KMG]iB\/s)/)?.[1] ?? "";
517
+ spinner.text = `\u4E0B\u8F7D\u4E2D [${bar}] ${pct}%${speed ? " " + speed : ""}`;
518
+ } else {
519
+ const line = text3.split(/\r?\n/).find((l) => l.trim())?.trim() ?? "";
520
+ if (line && line.length < 80) spinner.text = `\u514B\u9686\u4E2D... ${line}`;
521
+ }
522
+ });
523
+ proc.on("error", (err) => {
524
+ reject(new Error(err.code === "ENOENT" ? "GIT_NOT_FOUND" : `git \u542F\u52A8\u5931\u8D25: ${err.message}`));
525
+ });
526
+ proc.on("close", (code) => {
527
+ if (code === 0) resolve();
528
+ else reject(new Error(`git clone \u5931\u8D25 (exit ${code})`));
529
+ });
530
+ });
531
+ }
485
532
  async function downloadTemplate(template, options = {}) {
486
533
  const { spinner, noCache, giteeUrl: optGiteeUrl } = options;
487
534
  const branch = template.branch || "main";
@@ -489,15 +536,56 @@ async function downloadTemplate(template, options = {}) {
489
536
  if (!template?.repoUrl) {
490
537
  throw new Error(`\u6A21\u677F\u914D\u7F6E\u65E0\u6548: ${JSON.stringify(template)}`);
491
538
  }
539
+ if (!noCache) {
540
+ const cached = await getCachedTemplate(template.repoUrl);
541
+ if (cached) {
542
+ if (spinner) spinner.text = "\u5DF2\u4F7F\u7528\u7F13\u5B58\u6A21\u677F\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u8FD0\u884C robot cache clear";
543
+ return cached;
544
+ }
545
+ }
546
+ const cloneSources = [];
547
+ if (giteeUrl) cloneSources.push({ url: giteeUrl, name: "Gitee" });
548
+ cloneSources.push({ url: template.repoUrl, name: "GitHub" });
549
+ let noGit = false;
550
+ const cloneErrors = [];
551
+ for (const { url: cloneUrl, name: srcName } of cloneSources) {
552
+ const tempDir = path.join(os.tmpdir(), `robot-git-${Date.now()}`);
553
+ try {
554
+ if (spinner) spinner.text = `\u8FDE\u63A5 ${srcName}...`;
555
+ await gitCloneTemplate(cloneUrl, branch, tempDir, spinner);
556
+ await fs.remove(path.join(tempDir, ".git")).catch(() => {
557
+ });
558
+ if (spinner) spinner.text = "\u9A8C\u8BC1\u6A21\u677F\u5B8C\u6574\u6027...";
559
+ if (!fs.existsSync(path.join(tempDir, "package.json"))) {
560
+ throw new Error("\u6A21\u677F\u7F3A\u5C11 package.json");
561
+ }
562
+ if (!noCache) saveToCache(template.repoUrl, tempDir, branch).catch(() => {
563
+ });
564
+ if (spinner) spinner.text = `\u6A21\u677F\u4E0B\u8F7D\u5B8C\u6210 (${srcName})`;
565
+ return tempDir;
566
+ } catch (err) {
567
+ await fs.remove(tempDir).catch(() => {
568
+ });
569
+ const msg = err.message;
570
+ if (msg === "GIT_NOT_FOUND") {
571
+ noGit = true;
572
+ break;
573
+ }
574
+ cloneErrors.push(`${srcName}: ${msg}`);
575
+ if (spinner) spinner.text = `${srcName} \u514B\u9686\u5931\u8D25\uFF0C\u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u6E90...`;
576
+ await new Promise((r) => setTimeout(r, 500));
577
+ }
578
+ }
579
+ if (noGit && spinner) spinner.text = "\u7CFB\u7EDF\u672A\u5B89\u88C5 git\uFF0C\u5207\u6362\u5230 HTTP \u4E0B\u8F7D...";
492
580
  try {
493
- if (spinner) spinner.text = "\u5F00\u59CB\u4E0B\u8F7D\u6700\u65B0\u6A21\u677F...";
581
+ if (spinner) spinner.text = "HTTP \u4E0B\u8F7D\u6A21\u677F\u4E2D...";
494
582
  const { response, sourceName } = await tryDownload(
495
583
  template.repoUrl,
496
584
  branch,
497
585
  spinner,
498
586
  giteeUrl
499
587
  );
500
- if (spinner) spinner.text = "\u4FDD\u5B58\u4E0B\u8F7D\u6587\u4EF6...";
588
+ if (spinner) spinner.text = "\u4FDD\u5B58\u6587\u4EF6...";
501
589
  const timestamp = Date.now();
502
590
  const tempZip = path.join(os.tmpdir(), `robot-template-${timestamp}.zip`);
503
591
  const tempExtract = path.join(os.tmpdir(), `robot-extract-${timestamp}`);
@@ -517,7 +605,7 @@ async function downloadTemplate(template, options = {}) {
517
605
  const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
518
606
  const sizeMB = (received / 1024 / 1024).toFixed(1);
519
607
  const totalMB = (totalSize / 1024 / 1024).toFixed(1);
520
- spinner.text = `\u4E0B\u8F7D\u4E2D [${bar}] ${pct}% ${sizeMB}MB/${totalMB}MB (${sourceName})`;
608
+ spinner.text = `\u4E0B\u8F7D\u4E2D [${bar}] ${pct}% ${sizeMB}/${totalMB}MB (${sourceName})`;
521
609
  }
522
610
  const buffer = Buffer.concat(chunks);
523
611
  assertZipBuffer(buffer, sourceName);
@@ -529,59 +617,48 @@ async function downloadTemplate(template, options = {}) {
529
617
  }
530
618
  if (spinner) spinner.text = "\u89E3\u538B\u6A21\u677F\u6587\u4EF6...";
531
619
  await extract(tempZip, { dir: tempExtract });
532
- if (spinner) spinner.text = "\u67E5\u627E\u9879\u76EE\u7ED3\u6784...";
533
620
  const extractedItems = await fs.readdir(tempExtract);
534
621
  const repoName = template.repoUrl.split("/").pop() || "";
535
622
  const projectDir = extractedItems.find(
536
623
  (item) => item === `${repoName}-${branch}` || item.endsWith(`-${branch}`) || item.endsWith("-main") || item.endsWith("-master") || item === repoName
537
624
  );
538
625
  if (!projectDir) {
539
- throw new Error(
540
- `\u89E3\u538B\u540E\u627E\u4E0D\u5230\u9879\u76EE\u76EE\u5F55\uFF0C\u53EF\u7528\u76EE\u5F55: ${extractedItems.join(", ")}`
541
- );
626
+ throw new Error(`\u89E3\u538B\u540E\u627E\u4E0D\u5230\u9879\u76EE\u76EE\u5F55\uFF0C\u53EF\u7528\u76EE\u5F55: ${extractedItems.join(", ")}`);
542
627
  }
543
628
  const sourcePath = path.join(tempExtract, projectDir);
544
- if (spinner) spinner.text = "\u9A8C\u8BC1\u6A21\u677F\u5B8C\u6574\u6027...";
545
629
  if (!fs.existsSync(path.join(sourcePath, "package.json"))) {
546
630
  throw new Error("\u6A21\u677F\u7F3A\u5C11 package.json \u6587\u4EF6");
547
631
  }
548
- if (!noCache) {
549
- saveToCache(template.repoUrl, sourcePath, branch).catch(() => {
550
- });
551
- }
552
- if (spinner) spinner.text = `\u6A21\u677F\u4E0B\u8F7D\u5B8C\u6210 (via ${sourceName})`;
632
+ if (!noCache) saveToCache(template.repoUrl, sourcePath, branch).catch(() => {
633
+ });
634
+ if (spinner) spinner.text = `\u6A21\u677F\u4E0B\u8F7D\u5B8C\u6210 (HTTP/${sourceName})`;
553
635
  await fs.remove(tempZip).catch(() => {
554
636
  });
555
637
  return sourcePath;
556
- } catch (downloadError) {
638
+ } catch (httpError) {
557
639
  if (!noCache) {
558
640
  const cached = await getCachedTemplate(template.repoUrl);
559
641
  if (cached) {
560
642
  if (spinner) spinner.text = "\u7F51\u7EDC\u4E0D\u53EF\u7528\uFF0C\u4F7F\u7528\u7F13\u5B58\u6A21\u677F...";
561
- console.log();
562
- console.log(` ${chalk.yellow(" \u6CE8\u610F: \u4F7F\u7528\u7F13\u5B58\u7248\u672C\uFF08\u975E\u6700\u65B0\uFF09")}`);
643
+ console.log(`
644
+ ${chalk.yellow("\u6CE8\u610F: \u5DF2\u4F7F\u7528\u7F13\u5B58\u7248\u672C\uFF08\u975E\u6700\u65B0\uFF09")}`);
563
645
  return cached;
564
646
  }
565
647
  }
566
648
  try {
567
649
  const tmpFiles = await fs.readdir(os.tmpdir());
568
650
  for (const f of tmpFiles.filter(
569
- (x) => x.includes("robot-template-") || x.includes("robot-extract-")
651
+ (x) => x.includes("robot-template-") || x.includes("robot-extract-") || x.includes("robot-git-")
570
652
  )) {
571
653
  await fs.remove(path.join(os.tmpdir(), f)).catch(() => {
572
654
  });
573
655
  }
574
656
  } catch {
575
657
  }
576
- const errMsg = downloadError.message;
577
- const isTimeout = errMsg.includes("aborted") || errMsg.includes("timeout") || downloadError.name === "TimeoutError";
578
- let msg = `\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25: ${errMsg}`;
579
- if (isTimeout) {
580
- 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";
581
- } else if (downloadError.code === "ENOTFOUND") {
582
- 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";
583
- }
584
- throw new Error(msg);
658
+ const errMsg = httpError.message;
659
+ const allErrors = [...cloneErrors, `HTTP: ${errMsg}`].map((e) => ` - ${e}`).join("\n");
660
+ throw new Error(`\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25\uFF0C\u6240\u6709\u65B9\u5F0F\u5747\u4E0D\u53EF\u7528:
661
+ ${allErrors}`);
585
662
  }
586
663
  }
587
664
 
@@ -880,6 +957,9 @@ function formatBytes(bytes) {
880
957
 
881
958
  // src/create.ts
882
959
  var STRIP_VERSION_RE = /\s*(完整版|精简版|微服务版)\s*$/;
960
+ function filterAvailable(map) {
961
+ return Object.fromEntries(Object.entries(map).filter(([, t]) => t.status !== "coming-soon"));
962
+ }
883
963
  async function createProject(projectName, options = {}) {
884
964
  p.intro(chalk3.bgCyan.black(" Robot CLI - \u5F00\u59CB\u521B\u5EFA\u9879\u76EE "));
885
965
  let template;
@@ -1014,6 +1094,7 @@ async function selectFromRecommended() {
1014
1094
  p.log.info(chalk3.bold("\u63A8\u8350\u6A21\u677F") + chalk3.dim(" \u2014 \u57FA\u4E8E\u56E2\u961F\u4F7F\u7528\u9891\u7387\u548C\u9879\u76EE\u6210\u719F\u5EA6\u63A8\u8350"));
1015
1095
  const options = [];
1016
1096
  for (const [key, template] of Object.entries(recommended)) {
1097
+ if (template.status === "coming-soon") continue;
1017
1098
  const ver = VERSION_LABELS[template.version] || template.version;
1018
1099
  const tags = template.features.slice(0, 3).join(", ");
1019
1100
  options.push({
@@ -1083,7 +1164,15 @@ async function selectByCategory() {
1083
1164
  if (pk === "back") continue;
1084
1165
  patternKey = pk;
1085
1166
  }
1086
- const templates = getTemplatesByCategory(catKey, stackKey, patternKey);
1167
+ const allTemplates = getTemplatesByCategory(catKey, stackKey, patternKey);
1168
+ const templates = filterAvailable(allTemplates);
1169
+ const comingSoonCount = Object.keys(allTemplates).length - Object.keys(templates).length;
1170
+ if (Object.keys(templates).length === 0) {
1171
+ const names = Object.values(allTemplates).map((t2) => t2.name).join("\u3001");
1172
+ p.log.warn(`\u8BE5\u5206\u7C7B\u4E0B\u7684\u6A21\u677F\u6B63\u5728\u5EFA\u8BBE\u4E2D\uFF0C\u656C\u8BF7\u671F\u5F85 (${names})`);
1173
+ await new Promise((r) => setTimeout(r, 600));
1174
+ continue;
1175
+ }
1087
1176
  const tplOptions = Object.entries(templates).map(([key, t2]) => {
1088
1177
  const ver = VERSION_LABELS[t2.version] || t2.version;
1089
1178
  return {
@@ -1092,6 +1181,13 @@ async function selectByCategory() {
1092
1181
  hint: t2.description
1093
1182
  };
1094
1183
  });
1184
+ if (comingSoonCount > 0) {
1185
+ tplOptions.push({
1186
+ value: "__coming_soon__",
1187
+ label: chalk3.dim(`+ ${comingSoonCount} \u4E2A\u6A21\u677F\u5F00\u53D1\u4E2D...`),
1188
+ hint: "\u656C\u8BF7\u671F\u5F85"
1189
+ });
1190
+ }
1095
1191
  tplOptions.push({ value: "back", label: "<- \u8FD4\u56DE", hint: "" });
1096
1192
  const tplKey = await p.select({
1097
1193
  message: "\u8BF7\u9009\u62E9\u6A21\u677F\u7248\u672C:",
@@ -1099,6 +1195,11 @@ async function selectByCategory() {
1099
1195
  });
1100
1196
  if (p.isCancel(tplKey)) process.exit(0);
1101
1197
  if (tplKey === "back") continue;
1198
+ if (tplKey === "__coming_soon__") {
1199
+ p.log.warn("\u8BE5\u6A21\u677F\u6B63\u5728\u5F00\u53D1\u4E2D\uFF0C\u656C\u8BF7\u671F\u5F85\uFF01");
1200
+ await new Promise((r) => setTimeout(r, 600));
1201
+ continue;
1202
+ }
1102
1203
  const t = templates[tplKey];
1103
1204
  return { key: tplKey, ...t };
1104
1205
  }
@@ -1111,7 +1212,8 @@ async function selectBySearch() {
1111
1212
  });
1112
1213
  if (p.isCancel(keyword)) return await selectTemplateMethod();
1113
1214
  const results = searchTemplates(keyword);
1114
- if (Object.keys(results).length === 0) {
1215
+ const availableResults = filterAvailable(results);
1216
+ if (Object.keys(availableResults).length === 0) {
1115
1217
  p.log.warn(`\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6A21\u677F (\u5173\u952E\u8BCD: "${keyword}")`);
1116
1218
  const action = await p.select({
1117
1219
  message: "\u8BF7\u9009\u62E9\u4E0B\u4E00\u6B65\u64CD\u4F5C:",
@@ -1123,9 +1225,9 @@ async function selectBySearch() {
1123
1225
  if (p.isCancel(action) || action === "back") return await selectTemplateMethod();
1124
1226
  continue;
1125
1227
  }
1126
- p.log.info(`\u5173\u952E\u8BCD: "${keyword}" -- \u627E\u5230 ${Object.keys(results).length} \u4E2A\u5339\u914D\u6A21\u677F`);
1228
+ p.log.info(`\u5173\u952E\u8BCD: "${keyword}" -- \u627E\u5230 ${Object.keys(availableResults).length} \u4E2A\u5339\u914D\u6A21\u677F`);
1127
1229
  const options = [];
1128
- for (const [key, t2] of Object.entries(results)) {
1230
+ for (const [key, t2] of Object.entries(availableResults)) {
1129
1231
  const ver = VERSION_LABELS[t2.version] || t2.version;
1130
1232
  const info = t2.features.slice(0, 3).join(", ");
1131
1233
  options.push({
@@ -1145,18 +1247,22 @@ async function selectBySearch() {
1145
1247
  if (p.isCancel(selected)) process.exit(0);
1146
1248
  if (selected === "search_again") continue;
1147
1249
  if (selected === "back_to_mode") return await selectTemplateMethod();
1148
- const t = results[selected];
1250
+ const t = availableResults[selected];
1149
1251
  return { key: selected, ...t };
1150
1252
  }
1151
1253
  }
1152
1254
  async function selectFromAll() {
1153
1255
  const allTemplates = getAllTemplates();
1154
- p.log.info(chalk3.bold("\u6240\u6709\u53EF\u7528\u6A21\u677F") + chalk3.dim(` -- \u5171 ${Object.keys(allTemplates).length} \u4E2A\u6A21\u677F\u53EF\u9009`));
1256
+ const availableTemplates = filterAvailable(allTemplates);
1257
+ const comingSoonCount = Object.keys(allTemplates).length - Object.keys(availableTemplates).length;
1258
+ const countInfo = comingSoonCount > 0 ? chalk3.dim(` -- \u5171 ${Object.keys(availableTemplates).length} \u4E2A\u53EF\u7528\uFF0C${comingSoonCount} \u4E2A\u5F00\u53D1\u4E2D`) : chalk3.dim(` -- \u5171 ${Object.keys(availableTemplates).length} \u4E2A\u6A21\u677F\u53EF\u9009`);
1259
+ p.log.info(chalk3.bold("\u6240\u6709\u53EF\u7528\u6A21\u677F") + countInfo);
1155
1260
  const options = [];
1156
1261
  for (const [_catKey, category] of Object.entries(TEMPLATE_CATEGORIES)) {
1157
1262
  for (const [_sKey, stack] of Object.entries(category.stacks)) {
1158
1263
  for (const _pattern of Object.values(stack.patterns)) {
1159
1264
  for (const [key, t2] of Object.entries(_pattern.templates)) {
1265
+ if (t2.status === "coming-soon") continue;
1160
1266
  const ver = VERSION_LABELS[t2.version] || t2.version;
1161
1267
  options.push({
1162
1268
  value: key,
@@ -1174,7 +1280,7 @@ async function selectFromAll() {
1174
1280
  });
1175
1281
  if (p.isCancel(selected)) process.exit(0);
1176
1282
  if (selected === "back_to_mode") return await selectTemplateMethod();
1177
- const t = allTemplates[selected];
1283
+ const t = availableTemplates[selected];
1178
1284
  return { key: selected, ...t };
1179
1285
  }
1180
1286
  async function configureProject(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agile-team/robot-cli",
3
- "version": "3.0.3",
3
+ "version": "3.0.5",
4
4
  "description": "现代化项目脚手架工具,支持多技术栈快速创建项目 - 优先 bun,兼容 npm/pnpm/yarn",
5
5
  "type": "module",
6
6
  "bin": {