@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.
- package/CHANGELOG.md +16 -0
- package/dist/index.js +148 -42
- 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 = "\
|
|
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\
|
|
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}
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1283
|
+
const t = availableTemplates[selected];
|
|
1178
1284
|
return { key: selected, ...t };
|
|
1179
1285
|
}
|
|
1180
1286
|
async function configureProject(options) {
|