@agile-team/robot-cli 1.1.12 → 2.1.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/dist/index.js ADDED
@@ -0,0 +1,1720 @@
1
+ // src/index.ts
2
+ import { readFileSync } from "fs";
3
+ import { fileURLToPath } from "url";
4
+ import { dirname, join } from "path";
5
+ import chalk4 from "chalk";
6
+ import boxen from "boxen";
7
+ import inquirer2 from "inquirer";
8
+ import { Command } from "commander";
9
+
10
+ // src/create.ts
11
+ import fs3 from "fs-extra";
12
+ import path3 from "path";
13
+ import chalk2 from "chalk";
14
+ import inquirer from "inquirer";
15
+ import ora from "ora";
16
+ import { execSync as execSync2 } from "child_process";
17
+
18
+ // src/download.ts
19
+ import fs from "fs-extra";
20
+ import path from "path";
21
+ import os from "os";
22
+ import extract from "extract-zip";
23
+
24
+ // src/config/templates.config.ts
25
+ var TEMPLATE_CATEGORIES = {
26
+ frontend: {
27
+ name: "\u{1F3A8} \u524D\u7AEF\u9879\u76EE",
28
+ stacks: {
29
+ vue: {
30
+ name: "Vue.js",
31
+ patterns: {
32
+ monolith: {
33
+ name: "\u5355\u4F53\u5E94\u7528",
34
+ templates: {
35
+ "robot-admin": {
36
+ name: "Robot Admin \u5B8C\u6574\u7248",
37
+ description: "\u5305\u542B50+\u5B8C\u6574\u793A\u4F8B\u3001\u6743\u9650\u7BA1\u7406\u3001\u56FE\u8868\u7EC4\u4EF6\u3001\u6700\u4F73\u5B9E\u8DF5\u7B49\u7B49",
38
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Admin",
39
+ features: [
40
+ "Naive UI",
41
+ "Vue Router",
42
+ "Pinia",
43
+ "\u6743\u9650\u7BA1\u7406",
44
+ "\u52A8\u6001\u8DEF\u7531",
45
+ "\u56FE\u8868\u7EC4\u4EF6",
46
+ "\u6027\u80FD\u4F18\u5316\u7B49\u7B49"
47
+ ],
48
+ version: "full"
49
+ },
50
+ "robot-admin-base": {
51
+ name: "Robot Admin \u7CBE\u7B80\u7248",
52
+ description: "\u57FA\u7840\u67B6\u6784\u3001\u6838\u5FC3\u529F\u80FD\u3001\u5FEB\u901F\u542F\u52A8",
53
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Admin_Base",
54
+ features: ["Naive UI", "Vue Router", "Pinia", "\u57FA\u7840\u5E03\u5C40"],
55
+ version: "base"
56
+ }
57
+ }
58
+ },
59
+ monorepo: {
60
+ name: "Monorepo \u67B6\u6784",
61
+ templates: {
62
+ "robot-monorepo": {
63
+ name: "Robot Monorepo",
64
+ description: "bun workspace + Monorepo \u591A\u5305\u7BA1\u7406\u67B6\u6784",
65
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Admin",
66
+ branch: "monorepo",
67
+ features: [
68
+ "bun workspace",
69
+ "Monorepo",
70
+ "\u591A\u5305\u7BA1\u7406",
71
+ "\u5171\u4EAB\u7EC4\u4EF6"
72
+ ],
73
+ version: "full"
74
+ }
75
+ }
76
+ },
77
+ microfrontend: {
78
+ name: "\u5FAE\u524D\u7AEF\u67B6\u6784",
79
+ templates: {
80
+ "robot-micro-app": {
81
+ name: "Robot MicroApp \u5FAE\u524D\u7AEF",
82
+ description: "\u57FA\u4E8E MicroApp \u7684\u5FAE\u524D\u7AEF\u67B6\u6784\u65B9\u6848",
83
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Admin",
84
+ branch: "micro-app",
85
+ features: [
86
+ "MicroApp",
87
+ "\u5FAE\u524D\u7AEF",
88
+ "\u4E3B\u5B50\u5E94\u7528",
89
+ "\u8DEF\u7531\u5171\u4EAB"
90
+ ],
91
+ version: "full"
92
+ },
93
+ "robot-module-federation": {
94
+ name: "Robot Module Federation \u6A21\u5757\u8054\u90A6",
95
+ description: "\u57FA\u4E8E Vite Module Federation \u7684\u6A21\u5757\u8054\u90A6\u65B9\u6848",
96
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Admin",
97
+ branch: "module-federation",
98
+ features: [
99
+ "Module Federation",
100
+ "\u6A21\u5757\u8054\u90A6",
101
+ "Vite",
102
+ "\u8FDC\u7A0B\u6A21\u5757"
103
+ ],
104
+ version: "full"
105
+ }
106
+ }
107
+ }
108
+ }
109
+ },
110
+ react: {
111
+ name: "React.js",
112
+ patterns: {
113
+ monolith: {
114
+ name: "\u5355\u4F53\u5E94\u7528",
115
+ templates: {
116
+ "robot-react": {
117
+ name: "Robot React \u5B8C\u6574\u7248",
118
+ description: "Ant Design + \u5B8C\u6574\u529F\u80FD\u6F14\u793A",
119
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_React",
120
+ features: ["Ant Design", "React Router", "Redux Toolkit"],
121
+ version: "full"
122
+ },
123
+ "robot-react-base": {
124
+ name: "Robot React \u7CBE\u7B80\u7248",
125
+ description: "\u57FA\u7840React + \u6838\u5FC3\u529F\u80FD",
126
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_React_Base",
127
+ features: ["React", "React Router", "\u57FA\u7840\u7EC4\u4EF6"],
128
+ version: "base"
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ },
136
+ mobile: {
137
+ name: "\u{1F4F1} \u79FB\u52A8\u7AEF\u9879\u76EE",
138
+ stacks: {
139
+ uniapp: {
140
+ name: "uni-app",
141
+ patterns: {
142
+ multiplatform: {
143
+ name: "\u591A\u7AEF\u5E94\u7528",
144
+ templates: {
145
+ "robot-uniapp": {
146
+ name: "Robot uni-app \u5B8C\u6574\u7248",
147
+ description: "\u591A\u7AEF\u9002\u914D + \u63D2\u4EF6\u5E02\u573A + \u5B8C\u6574\u793A\u4F8B",
148
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Uniapp",
149
+ features: ["\u591A\u7AEF\u53D1\u5E03", "uView UI", "\u63D2\u4EF6\u96C6\u6210"],
150
+ version: "full"
151
+ },
152
+ "robot-uniapp-base": {
153
+ name: "Robot uni-app \u7CBE\u7B80\u7248",
154
+ description: "\u57FA\u7840\u6846\u67B6 + \u6838\u5FC3\u529F\u80FD",
155
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Uniapp_Base",
156
+ features: ["\u57FA\u7840\u6846\u67B6", "\u8DEF\u7531\u914D\u7F6E"],
157
+ version: "base"
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ },
165
+ backend: {
166
+ name: "\u{1F680} \u540E\u7AEF\u9879\u76EE",
167
+ stacks: {
168
+ nestjs: {
169
+ name: "NestJS",
170
+ patterns: {
171
+ api: {
172
+ name: "API\u670D\u52A1",
173
+ templates: {
174
+ "robot-nest": {
175
+ name: "Robot NestJS \u5B8C\u6574\u7248",
176
+ description: "NestJS + TypeORM + JWT + Swagger + Redis + \u5B8C\u6574\u751F\u6001",
177
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Nest",
178
+ features: [
179
+ "NestJS",
180
+ "TypeORM",
181
+ "JWT\u8BA4\u8BC1",
182
+ "ApiFox\u6587\u6863",
183
+ "Redis",
184
+ "\u5FAE\u670D\u52A1"
185
+ ],
186
+ version: "full"
187
+ },
188
+ "robot-nest-base": {
189
+ name: "Robot NestJS \u7CBE\u7B80\u7248",
190
+ description: "\u57FA\u7840 NestJS + \u6838\u5FC3\u6A21\u5757",
191
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Nest_Base",
192
+ features: ["NestJS", "\u57FA\u7840\u8DEF\u7531", "\u9519\u8BEF\u5904\u7406"],
193
+ version: "base"
194
+ }
195
+ }
196
+ },
197
+ microservice: {
198
+ name: "\u5FAE\u670D\u52A1\u67B6\u6784",
199
+ templates: {
200
+ "robot-nest-micro": {
201
+ name: "Robot NestJS\u5FAE\u670D\u52A1\u7248",
202
+ description: "NestJS + \u5FAE\u670D\u52A1\u67B6\u6784 + gRPC + \u670D\u52A1\u53D1\u73B0",
203
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Nest_Micro",
204
+ features: ["NestJS", "\u5FAE\u670D\u52A1", "gRPC", "Redis", "\u670D\u52A1\u53D1\u73B0"],
205
+ version: "micro"
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+ },
213
+ desktop: {
214
+ name: "\u{1F4BB} \u684C\u9762\u7AEF\u9879\u76EE",
215
+ stacks: {
216
+ electron: {
217
+ name: "Electron",
218
+ patterns: {
219
+ desktop: {
220
+ name: "\u684C\u9762\u5E94\u7528",
221
+ templates: {
222
+ "robot-electron": {
223
+ name: "Robot Electron \u5B8C\u6574\u7248",
224
+ description: "Vue3 + Electron + \u81EA\u52A8\u66F4\u65B0 + \u539F\u751F\u80FD\u529B",
225
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Electron",
226
+ features: ["Vue3", "Electron", "\u81EA\u52A8\u66F4\u65B0", "\u539F\u751FAPI"],
227
+ version: "full"
228
+ },
229
+ "robot-electron-base": {
230
+ name: "Robot Electron \u7CBE\u7B80\u7248",
231
+ description: "\u57FA\u7840Electron + Vue\u6846\u67B6",
232
+ repoUrl: "https://github.com/ChenyCHENYU/Robot_Electron_Base",
233
+ features: ["Vue3", "Electron", "\u57FA\u7840\u529F\u80FD"],
234
+ version: "base"
235
+ }
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
241
+ }
242
+ };
243
+
244
+ // src/config/cli.config.ts
245
+ var CACHE_DIR_NAME = "cache";
246
+ var RECOMMENDED_TEMPLATE_KEYS = [
247
+ "robot-admin",
248
+ "robot-monorepo",
249
+ "robot-micro-app",
250
+ "robot-module-federation",
251
+ "robot-nest",
252
+ "robot-react"
253
+ ];
254
+ var START_COMMAND_MAP = {
255
+ "robot-admin": "dev",
256
+ "robot-admin-base": "dev",
257
+ "robot-monorepo": "dev",
258
+ "robot-micro-app": "dev",
259
+ "robot-module-federation": "dev",
260
+ "robot-react": "start",
261
+ "robot-react-base": "start",
262
+ "robot-uniapp": "dev:h5",
263
+ "robot-uniapp-base": "dev:h5",
264
+ "robot-nest": "start:dev",
265
+ "robot-nest-base": "start:dev",
266
+ "robot-nest-micro": "start:dev",
267
+ "robot-electron": "electron:dev",
268
+ "robot-electron-base": "electron:dev"
269
+ };
270
+
271
+ // src/download.ts
272
+ var CACHE_DIR = path.join(os.homedir(), ".robot-cli", CACHE_DIR_NAME);
273
+ var CACHE_INDEX_PATH = path.join(CACHE_DIR, "index.json");
274
+ function buildDownloadUrl(repoUrl, branch = "main") {
275
+ try {
276
+ const url = new URL(repoUrl);
277
+ const host = url.hostname;
278
+ const cleanUrl = repoUrl.replace(/\/+$/, "");
279
+ if (host === "github.com") return `${cleanUrl}/archive/refs/heads/${branch}.zip`;
280
+ if (host === "gitee.com")
281
+ return `${cleanUrl}/repository/archive/${branch}.zip`;
282
+ if (host === "gitlab.com") {
283
+ const name = cleanUrl.split("/").pop();
284
+ return `${cleanUrl}/-/archive/${branch}/${name}-${branch}.zip`;
285
+ }
286
+ return `${cleanUrl}/archive/refs/heads/${branch}.zip`;
287
+ } catch {
288
+ return `${repoUrl}/archive/refs/heads/${branch}.zip`;
289
+ }
290
+ }
291
+ function getCacheKey(repoUrl) {
292
+ let hash = 0;
293
+ for (let i = 0; i < repoUrl.length; i++) {
294
+ hash = (hash << 5) - hash + repoUrl.charCodeAt(i) | 0;
295
+ }
296
+ return Math.abs(hash).toString(36);
297
+ }
298
+ async function loadCacheIndex() {
299
+ try {
300
+ if (await fs.pathExists(CACHE_INDEX_PATH)) {
301
+ return await fs.readJson(CACHE_INDEX_PATH);
302
+ }
303
+ } catch {
304
+ }
305
+ return { version: 1, entries: {} };
306
+ }
307
+ async function saveCacheIndex(index) {
308
+ await fs.ensureDir(CACHE_DIR);
309
+ await fs.writeJson(CACHE_INDEX_PATH, index, { spaces: 2 });
310
+ }
311
+ async function getCachedTemplate(repoUrl) {
312
+ const key = getCacheKey(repoUrl);
313
+ const cachePath = path.join(CACHE_DIR, key);
314
+ if (await fs.pathExists(cachePath)) {
315
+ const pkgPath = path.join(cachePath, "package.json");
316
+ if (await fs.pathExists(pkgPath)) return cachePath;
317
+ }
318
+ return null;
319
+ }
320
+ async function saveToCache(repoUrl, sourcePath) {
321
+ try {
322
+ const key = getCacheKey(repoUrl);
323
+ const cachePath = path.join(CACHE_DIR, key);
324
+ await fs.ensureDir(CACHE_DIR);
325
+ await fs.remove(cachePath).catch(() => {
326
+ });
327
+ await fs.copy(sourcePath, cachePath);
328
+ const index = await loadCacheIndex();
329
+ const stat = await fs.stat(sourcePath);
330
+ index.entries[key] = {
331
+ repoUrl,
332
+ downloadedAt: (/* @__PURE__ */ new Date()).toISOString(),
333
+ branch: "main",
334
+ size: stat.size
335
+ };
336
+ await saveCacheIndex(index);
337
+ } catch {
338
+ }
339
+ }
340
+ async function getCacheStats() {
341
+ const index = await loadCacheIndex();
342
+ const entries = Object.values(index.entries);
343
+ return {
344
+ count: entries.length,
345
+ totalSize: entries.reduce((sum, e) => sum + (e.size || 0), 0)
346
+ };
347
+ }
348
+ async function clearCache() {
349
+ await fs.remove(CACHE_DIR);
350
+ }
351
+ async function tryDownload(repoUrl, branch = "main", spinner) {
352
+ const url = new URL(repoUrl);
353
+ const host = url.hostname;
354
+ const mirrors = host === "github.com" ? [repoUrl, `https://ghproxy.com/${repoUrl}`] : [repoUrl];
355
+ for (let i = 0; i < mirrors.length; i++) {
356
+ const current = mirrors[i];
357
+ const isOriginal = current === repoUrl;
358
+ const sourceName = isOriginal ? `${host} \u5B98\u65B9` : `${host} \u955C\u50CF`;
359
+ try {
360
+ if (spinner) spinner.text = `\u{1F50D} \u8FDE\u63A5\u5230 ${sourceName}...`;
361
+ const downloadUrl = current.endsWith(".zip") ? current : buildDownloadUrl(current, branch);
362
+ if (spinner) spinner.text = `\u{1F4E6} \u4ECE ${sourceName} \u4E0B\u8F7D\u6A21\u677F...`;
363
+ const response = await fetch(downloadUrl, {
364
+ signal: AbortSignal.timeout(isOriginal ? 15e3 : 1e4),
365
+ headers: { "User-Agent": "Robot-CLI/2.0.0" }
366
+ });
367
+ if (!response.ok) {
368
+ if (response.status === 404) throw new Error(`\u4ED3\u5E93\u4E0D\u5B58\u5728: ${repoUrl}`);
369
+ throw new Error(`HTTP ${response.status}`);
370
+ }
371
+ if (spinner) {
372
+ const len = response.headers.get("content-length");
373
+ const sizeInfo = len ? `${(parseInt(len) / 1024 / 1024).toFixed(1)}MB ` : "";
374
+ spinner.text = `\u{1F4E6} \u4E0B\u8F7D\u4E2D... (${sizeInfo}from ${sourceName})`;
375
+ }
376
+ return { response, sourceName };
377
+ } catch (error) {
378
+ if (i === mirrors.length - 1) throw error;
379
+ if (spinner) spinner.text = `\u26A0\uFE0F ${sourceName} \u8BBF\u95EE\u5931\u8D25\uFF0C\u5C1D\u8BD5\u5176\u4ED6\u6E90...`;
380
+ await new Promise((r) => setTimeout(r, 1e3));
381
+ }
382
+ }
383
+ throw new Error("\u6240\u6709\u4E0B\u8F7D\u6E90\u5747\u4E0D\u53EF\u7528");
384
+ }
385
+ async function downloadTemplate(template, options = {}) {
386
+ const { spinner, noCache } = options;
387
+ const branch = template.branch || "main";
388
+ if (!template?.repoUrl) {
389
+ throw new Error(`\u6A21\u677F\u914D\u7F6E\u65E0\u6548: ${JSON.stringify(template)}`);
390
+ }
391
+ try {
392
+ if (spinner) spinner.text = "\u{1F310} \u5F00\u59CB\u4E0B\u8F7D\u6700\u65B0\u6A21\u677F...";
393
+ const { response, sourceName } = await tryDownload(
394
+ template.repoUrl,
395
+ branch,
396
+ spinner
397
+ );
398
+ if (spinner) spinner.text = "\u{1F4BE} \u4FDD\u5B58\u4E0B\u8F7D\u6587\u4EF6...";
399
+ const timestamp = Date.now();
400
+ const tempZip = path.join(os.tmpdir(), `robot-template-${timestamp}.zip`);
401
+ const tempExtract = path.join(os.tmpdir(), `robot-extract-${timestamp}`);
402
+ const buffer = Buffer.from(await response.arrayBuffer());
403
+ await fs.writeFile(tempZip, buffer);
404
+ if (spinner) spinner.text = "\u{1F4C2} \u89E3\u538B\u6A21\u677F\u6587\u4EF6...";
405
+ await extract(tempZip, { dir: tempExtract });
406
+ if (spinner) spinner.text = "\u{1F50D} \u67E5\u627E\u9879\u76EE\u7ED3\u6784...";
407
+ const extractedItems = await fs.readdir(tempExtract);
408
+ const repoName = template.repoUrl.split("/").pop() || "";
409
+ const projectDir = extractedItems.find(
410
+ (item) => item === `${repoName}-${branch}` || item.endsWith(`-${branch}`) || item.endsWith("-main") || item.endsWith("-master") || item === repoName
411
+ );
412
+ if (!projectDir) {
413
+ throw new Error(
414
+ `\u89E3\u538B\u540E\u627E\u4E0D\u5230\u9879\u76EE\u76EE\u5F55\uFF0C\u53EF\u7528\u76EE\u5F55: ${extractedItems.join(", ")}`
415
+ );
416
+ }
417
+ const sourcePath = path.join(tempExtract, projectDir);
418
+ if (spinner) spinner.text = "\u2705 \u9A8C\u8BC1\u6A21\u677F\u5B8C\u6574\u6027...";
419
+ if (!fs.existsSync(path.join(sourcePath, "package.json"))) {
420
+ throw new Error("\u6A21\u677F\u7F3A\u5C11 package.json \u6587\u4EF6");
421
+ }
422
+ if (!noCache) {
423
+ saveToCache(template.repoUrl, sourcePath).catch(() => {
424
+ });
425
+ }
426
+ if (spinner) spinner.text = `\u{1F389} \u6A21\u677F\u4E0B\u8F7D\u5B8C\u6210 (via ${sourceName})`;
427
+ await fs.remove(tempZip).catch(() => {
428
+ });
429
+ return sourcePath;
430
+ } catch (downloadError) {
431
+ if (!noCache) {
432
+ const cached = await getCachedTemplate(template.repoUrl);
433
+ if (cached) {
434
+ if (spinner) spinner.text = "\u{1F4E6} \u7F51\u7EDC\u4E0D\u53EF\u7528\uFF0C\u4F7F\u7528\u7F13\u5B58\u6A21\u677F...";
435
+ console.log();
436
+ console.log(
437
+ ` ${import("chalk").then((c) => c.default.yellow("\u26A0\uFE0F \u4F7F\u7528\u7F13\u5B58\u7248\u672C\uFF08\u975E\u6700\u65B0\uFF09"))}`
438
+ );
439
+ return cached;
440
+ }
441
+ }
442
+ try {
443
+ const tmpFiles = await fs.readdir(os.tmpdir());
444
+ for (const f of tmpFiles.filter(
445
+ (x) => x.includes("robot-template-") || x.includes("robot-extract-")
446
+ )) {
447
+ await fs.remove(path.join(os.tmpdir(), f)).catch(() => {
448
+ });
449
+ }
450
+ } catch {
451
+ }
452
+ let msg = `\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25: ${downloadError.message}`;
453
+ if (downloadError.code === "ENOTFOUND") {
454
+ 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";
455
+ }
456
+ throw new Error(msg);
457
+ }
458
+ }
459
+
460
+ // src/templates.ts
461
+ var remoteTemplates = null;
462
+ function getAllTemplates() {
463
+ const templates = {};
464
+ for (const category of Object.values(TEMPLATE_CATEGORIES)) {
465
+ for (const stack of Object.values(category.stacks)) {
466
+ for (const pattern of Object.values(stack.patterns)) {
467
+ Object.assign(templates, pattern.templates);
468
+ }
469
+ }
470
+ }
471
+ if (remoteTemplates) {
472
+ for (const [key, template] of Object.entries(remoteTemplates)) {
473
+ if (!templates[key]) {
474
+ templates[key] = template;
475
+ }
476
+ }
477
+ }
478
+ return templates;
479
+ }
480
+ function getTemplatesByCategory(categoryKey, stackKey, patternKey) {
481
+ const category = TEMPLATE_CATEGORIES[categoryKey];
482
+ if (!category) return {};
483
+ const stack = category.stacks[stackKey];
484
+ if (!stack) return {};
485
+ const pattern = stack.patterns[patternKey];
486
+ if (!pattern) return {};
487
+ return pattern.templates;
488
+ }
489
+ function searchTemplates(keyword) {
490
+ const all = getAllTemplates();
491
+ const results = {};
492
+ const lower = keyword.toLowerCase();
493
+ for (const [key, t] of Object.entries(all)) {
494
+ const haystack = `${t.name} ${t.description} ${t.features.join(" ")}`.toLowerCase();
495
+ if (haystack.includes(lower)) {
496
+ results[key] = t;
497
+ }
498
+ }
499
+ return results;
500
+ }
501
+ function getRecommendedTemplates() {
502
+ const all = getAllTemplates();
503
+ const recommended = {};
504
+ const keys = RECOMMENDED_TEMPLATE_KEYS;
505
+ for (const key of keys) {
506
+ if (all[key]) recommended[key] = all[key];
507
+ }
508
+ if (Object.keys(recommended).length < 4) {
509
+ for (const key of Object.keys(all)) {
510
+ if (!recommended[key] && Object.keys(recommended).length < 6) {
511
+ recommended[key] = all[key];
512
+ }
513
+ }
514
+ }
515
+ return recommended;
516
+ }
517
+ function getCategoryForTemplate(templateKey) {
518
+ for (const category of Object.values(TEMPLATE_CATEGORIES)) {
519
+ for (const stack of Object.values(category.stacks)) {
520
+ for (const pattern of Object.values(stack.patterns)) {
521
+ if (templateKey in pattern.templates) return category;
522
+ }
523
+ }
524
+ }
525
+ return void 0;
526
+ }
527
+
528
+ // src/utils.ts
529
+ import fs2 from "fs-extra";
530
+ import path2 from "path";
531
+ import chalk from "chalk";
532
+ import { execSync } from "child_process";
533
+ function detectPackageManager() {
534
+ const managers = [];
535
+ const checks = ["bun", "pnpm", "yarn", "npm"];
536
+ for (const pm of checks) {
537
+ try {
538
+ execSync(`${pm} --version`, { stdio: "ignore" });
539
+ managers.push(pm);
540
+ } catch {
541
+ }
542
+ }
543
+ return managers.length > 0 ? managers : ["npm"];
544
+ }
545
+ function getGitUser() {
546
+ try {
547
+ return execSync("git config user.name", {
548
+ stdio: ["pipe", "pipe", "ignore"]
549
+ }).toString().trim();
550
+ } catch {
551
+ return "";
552
+ }
553
+ }
554
+ function validateProjectName(name) {
555
+ const errors = [];
556
+ if (!name || typeof name !== "string") {
557
+ return { valid: false, errors: ["\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A"] };
558
+ }
559
+ const t = name.trim();
560
+ if (t.length === 0) errors.push("\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
561
+ if (t.length > 214) errors.push("\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u8D85\u8FC7214\u4E2A\u5B57\u7B26");
562
+ if (t.toLowerCase() !== t) errors.push("\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD");
563
+ if (/^[._]/.test(t)) errors.push('\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4EE5 "." \u6216 "_" \u5F00\u5934');
564
+ if (!/^[a-z0-9._-]+$/.test(t))
565
+ errors.push("\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u70B9\u3001\u4E0B\u5212\u7EBF\u548C\u77ED\u6A2A\u7EBF");
566
+ const reserved = [
567
+ "node_modules",
568
+ "favicon.ico",
569
+ ".git",
570
+ ".env",
571
+ "package.json",
572
+ "npm",
573
+ "yarn",
574
+ "pnpm",
575
+ "bun",
576
+ "robot"
577
+ ];
578
+ if (reserved.includes(t)) errors.push(`"${t}" \u662F\u4FDD\u7559\u540D\u79F0\uFF0C\u8BF7\u4F7F\u7528\u5176\u4ED6\u540D\u79F0`);
579
+ return { valid: errors.length === 0, errors };
580
+ }
581
+ async function checkNetworkConnection(testUrl) {
582
+ const urls = testUrl ? [testUrl] : ["https://api.github.com", "https://www.npmjs.com"];
583
+ for (const url of urls) {
584
+ try {
585
+ const res = await fetch(url, {
586
+ method: "HEAD",
587
+ signal: AbortSignal.timeout(5e3)
588
+ });
589
+ if (res.ok) return true;
590
+ } catch {
591
+ }
592
+ }
593
+ return false;
594
+ }
595
+ async function checkForUpdates(packageName, currentVersion) {
596
+ try {
597
+ const res = await fetch(
598
+ `https://registry.npmjs.org/${packageName}/latest`,
599
+ {
600
+ signal: AbortSignal.timeout(3e3)
601
+ }
602
+ );
603
+ if (!res.ok) return null;
604
+ const data = await res.json();
605
+ return data.version !== currentVersion ? data.version : null;
606
+ } catch {
607
+ return null;
608
+ }
609
+ }
610
+ async function countFiles(dirPath) {
611
+ let count = 0;
612
+ const skip = /* @__PURE__ */ new Set(["node_modules", ".git", ".DS_Store"]);
613
+ async function walk(dir) {
614
+ const items = await fs2.readdir(dir);
615
+ for (const item of items) {
616
+ const full = path2.join(dir, item);
617
+ const stat = await fs2.stat(full);
618
+ if (stat.isDirectory()) {
619
+ if (!skip.has(item)) await walk(full);
620
+ } else {
621
+ count++;
622
+ }
623
+ }
624
+ }
625
+ await walk(dirPath);
626
+ return count;
627
+ }
628
+ async function copyTemplate(sourcePath, targetPath, spinner) {
629
+ if (!fs2.existsSync(sourcePath)) {
630
+ throw new Error(`\u6E90\u8DEF\u5F84\u4E0D\u5B58\u5728: ${sourcePath}`);
631
+ }
632
+ await fs2.ensureDir(targetPath);
633
+ if (spinner) spinner.text = "\u{1F4CA} \u7EDF\u8BA1\u6587\u4EF6\u6570\u91CF...";
634
+ const totalFiles = await countFiles(sourcePath);
635
+ if (spinner) spinner.text = `\u{1F4CB} \u5F00\u59CB\u590D\u5236 ${totalFiles} \u4E2A\u6587\u4EF6...`;
636
+ let copied = 0;
637
+ const skipDirs = /* @__PURE__ */ new Set([
638
+ "node_modules",
639
+ ".git",
640
+ ".DS_Store",
641
+ ".vscode",
642
+ ".idea"
643
+ ]);
644
+ async function copyDir(src, dest) {
645
+ const items = await fs2.readdir(src);
646
+ for (const item of items) {
647
+ const s = path2.join(src, item);
648
+ const d = path2.join(dest, item);
649
+ const stat = await fs2.stat(s);
650
+ if (stat.isDirectory()) {
651
+ if (skipDirs.has(item)) continue;
652
+ await fs2.ensureDir(d);
653
+ await copyDir(s, d);
654
+ } else {
655
+ await fs2.copy(s, d);
656
+ copied++;
657
+ if (spinner && (copied % 10 === 0 || copied === totalFiles)) {
658
+ const pct = Math.round(copied / totalFiles * 100);
659
+ spinner.text = `\u{1F4CB} \u590D\u5236\u4E2D... ${copied}/${totalFiles} (${pct}%)`;
660
+ }
661
+ }
662
+ }
663
+ }
664
+ await copyDir(sourcePath, targetPath);
665
+ if (spinner) spinner.text = `\u2705 \u6587\u4EF6\u590D\u5236\u5B8C\u6210 (${copied} \u4E2A\u6587\u4EF6)`;
666
+ }
667
+ async function installDependencies(projectPath, spinner, packageManager = "npm") {
668
+ try {
669
+ const pkgJson = path2.join(projectPath, "package.json");
670
+ if (!fs2.existsSync(pkgJson)) {
671
+ if (spinner) spinner.text = "\u26A0\uFE0F \u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5 (\u65E0 package.json)";
672
+ return;
673
+ }
674
+ const cmds = {
675
+ bun: "bun install",
676
+ pnpm: "pnpm install",
677
+ yarn: "yarn install",
678
+ npm: "npm install"
679
+ };
680
+ if (spinner) spinner.text = `\u{1F4E6} \u4F7F\u7528 ${packageManager} \u5B89\u88C5\u4F9D\u8D56...`;
681
+ execSync(cmds[packageManager], {
682
+ cwd: projectPath,
683
+ stdio: "ignore",
684
+ timeout: 3e5
685
+ });
686
+ if (spinner) spinner.text = `\u2705 \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210 (${packageManager})`;
687
+ } catch (error) {
688
+ if (spinner) spinner.text = "\u26A0\uFE0F \u4F9D\u8D56\u5B89\u88C5\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5";
689
+ console.log();
690
+ console.log(chalk.yellow("\u26A0\uFE0F \u81EA\u52A8\u5B89\u88C5\u4F9D\u8D56\u5931\u8D25"));
691
+ console.log(chalk.dim(` \u9519\u8BEF: ${error.message}`));
692
+ console.log();
693
+ console.log(chalk.blue("\u{1F4A1} \u8BF7\u624B\u52A8\u5B89\u88C5:"));
694
+ console.log(chalk.cyan(` cd ${path2.basename(projectPath)}`));
695
+ console.log(chalk.cyan(` ${packageManager} install`));
696
+ console.log();
697
+ }
698
+ }
699
+ async function generateProjectStats(projectPath) {
700
+ try {
701
+ const stats = {
702
+ files: 0,
703
+ directories: 0,
704
+ size: 0,
705
+ fileTypes: {}
706
+ };
707
+ const skip = /* @__PURE__ */ new Set(["node_modules", ".git", ".DS_Store"]);
708
+ async function walk(dir) {
709
+ const items = await fs2.readdir(dir);
710
+ for (const item of items) {
711
+ const full = path2.join(dir, item);
712
+ const stat = await fs2.stat(full);
713
+ if (stat.isDirectory()) {
714
+ if (!skip.has(item)) {
715
+ stats.directories++;
716
+ await walk(full);
717
+ }
718
+ } else {
719
+ stats.files++;
720
+ stats.size += stat.size;
721
+ const ext = path2.extname(item).toLowerCase();
722
+ if (ext) stats.fileTypes[ext] = (stats.fileTypes[ext] || 0) + 1;
723
+ }
724
+ }
725
+ }
726
+ await walk(projectPath);
727
+ return stats;
728
+ } catch {
729
+ return null;
730
+ }
731
+ }
732
+ function printProjectStats(stats) {
733
+ console.log(chalk.blue("\u{1F4CA} \u9879\u76EE\u7EDF\u8BA1:"));
734
+ console.log(` \u6587\u4EF6\u6570\u91CF: ${chalk.cyan(String(stats.files))} \u4E2A`);
735
+ console.log(` \u76EE\u5F55\u6570\u91CF: ${chalk.cyan(String(stats.directories))} \u4E2A`);
736
+ console.log(` \u9879\u76EE\u5927\u5C0F: ${chalk.cyan(formatBytes(stats.size))}`);
737
+ const top = Object.entries(stats.fileTypes).sort(([, a], [, b]) => b - a).slice(0, 5);
738
+ if (top.length > 0) {
739
+ console.log(" \u4E3B\u8981\u6587\u4EF6\u7C7B\u578B:");
740
+ for (const [ext, count] of top) {
741
+ console.log(` ${ext}: ${chalk.cyan(String(count))} \u4E2A`);
742
+ }
743
+ }
744
+ }
745
+ function formatBytes(bytes) {
746
+ if (bytes === 0) return "0 B";
747
+ const k = 1024;
748
+ const sizes = ["B", "KB", "MB", "GB"];
749
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
750
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
751
+ }
752
+
753
+ // src/create.ts
754
+ async function createProject(projectName, options = {}) {
755
+ console.log();
756
+ console.log(chalk2.cyan("\u{1F680} Robot CLI - \u5F00\u59CB\u521B\u5EFA\u9879\u76EE"));
757
+ console.log();
758
+ let template;
759
+ if (options.from) {
760
+ template = {
761
+ key: "custom",
762
+ name: "Custom Template",
763
+ description: `\u6765\u81EA ${options.from}`,
764
+ repoUrl: options.from,
765
+ features: [],
766
+ version: "full"
767
+ };
768
+ console.log(chalk2.blue(`\u{1F4E6} \u4F7F\u7528\u81EA\u5B9A\u4E49\u6A21\u677F: ${chalk2.dim(options.from)}`));
769
+ console.log();
770
+ } else {
771
+ template = await selectTemplate(options.template);
772
+ }
773
+ const finalProjectName = await handleProjectName(projectName, template);
774
+ const projectConfig = await configureProject(options);
775
+ await confirmCreation(finalProjectName, template, projectConfig);
776
+ if (options.dryRun) {
777
+ console.log();
778
+ console.log(
779
+ chalk2.yellow("\u{1F50D} Dry Run \u6A21\u5F0F - \u4EE5\u4E0B\u4E3A\u9884\u89C8\u4FE1\u606F\uFF0C\u672A\u5B9E\u9645\u6267\u884C\u4EFB\u4F55\u64CD\u4F5C:")
780
+ );
781
+ console.log();
782
+ console.log(` \u9879\u76EE\u8DEF\u5F84: ${chalk2.cyan(path3.resolve(finalProjectName))}`);
783
+ console.log(` \u4E0B\u8F7D\u6765\u6E90: ${chalk2.dim(template.repoUrl)}`);
784
+ console.log(" \u6267\u884C\u6B65\u9AA4:");
785
+ console.log(chalk2.dim(` 1. \u4ECE ${template.repoUrl} \u4E0B\u8F7D\u6700\u65B0\u6A21\u677F`));
786
+ console.log(chalk2.dim(` 2. \u89E3\u538B\u5E76\u590D\u5236\u5230 ./${finalProjectName}`));
787
+ console.log(
788
+ chalk2.dim(" 3. \u66F4\u65B0 package.json (name, description, author)")
789
+ );
790
+ let step = 4;
791
+ if (projectConfig.initGit) {
792
+ console.log(chalk2.dim(` ${step}. \u521D\u59CB\u5316 Git \u4ED3\u5E93\u5E76\u63D0\u4EA4\u521D\u59CB\u4EE3\u7801`));
793
+ step++;
794
+ }
795
+ if (projectConfig.installDeps) {
796
+ console.log(
797
+ chalk2.dim(` ${step}. \u4F7F\u7528 ${projectConfig.packageManager} \u5B89\u88C5\u4F9D\u8D56`)
798
+ );
799
+ }
800
+ console.log();
801
+ console.log(chalk2.dim("\u79FB\u9664 --dry-run \u53C2\u6570\u4EE5\u6267\u884C\u521B\u5EFA\u3002"));
802
+ console.log();
803
+ return;
804
+ }
805
+ await executeCreation(finalProjectName, template, projectConfig, options);
806
+ }
807
+ async function handleProjectName(projectName, template) {
808
+ if (projectName) {
809
+ const v = validateProjectName(projectName);
810
+ if (!v.valid) {
811
+ console.log(chalk2.red("\u274C \u9879\u76EE\u540D\u79F0\u4E0D\u5408\u6CD5:"));
812
+ v.errors.forEach((e) => console.log(chalk2.red(` ${e}`)));
813
+ console.log();
814
+ const { newName } = await inquirer.prompt([
815
+ {
816
+ type: "input",
817
+ name: "newName",
818
+ message: "\u8BF7\u8F93\u5165\u65B0\u7684\u9879\u76EE\u540D\u79F0:",
819
+ validate: (input) => {
820
+ const r = validateProjectName(input);
821
+ return r.valid || r.errors[0];
822
+ }
823
+ }
824
+ ]);
825
+ return newName;
826
+ }
827
+ return projectName;
828
+ }
829
+ const defaultName = generateDefaultProjectName(template);
830
+ const { name } = await inquirer.prompt([
831
+ {
832
+ type: "input",
833
+ name: "name",
834
+ message: "\u8BF7\u8F93\u5165\u9879\u76EE\u540D\u79F0:",
835
+ default: defaultName,
836
+ validate: (input) => {
837
+ if (!input.trim()) return "\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
838
+ const r = validateProjectName(input);
839
+ return r.valid || r.errors[0];
840
+ }
841
+ }
842
+ ]);
843
+ return name;
844
+ }
845
+ function generateDefaultProjectName(template) {
846
+ const key = template.key || "project";
847
+ const ts = Date.now().toString().slice(-4);
848
+ const base = key.replace(/-(full|lite|base)$/, "");
849
+ return `my-${base}-${ts}`;
850
+ }
851
+ async function selectTemplate(templateOption) {
852
+ if (templateOption) {
853
+ const all = getAllTemplates();
854
+ if (all[templateOption]) {
855
+ return { key: templateOption, ...all[templateOption] };
856
+ }
857
+ console.log(chalk2.yellow(`\u26A0\uFE0F \u6A21\u677F "${templateOption}" \u4E0D\u5B58\u5728`));
858
+ console.log();
859
+ }
860
+ return await selectTemplateMethod();
861
+ }
862
+ async function selectTemplateMethod() {
863
+ console.log();
864
+ console.log(chalk2.blue.bold("\u{1F3AF} \u9009\u62E9\u6A21\u677F\u521B\u5EFA\u65B9\u5F0F"));
865
+ console.log(chalk2.dim("\u8BF7\u9009\u62E9\u6700\u9002\u5408\u4F60\u7684\u6A21\u677F\u6D4F\u89C8\u65B9\u5F0F"));
866
+ console.log();
867
+ const { selectionMode } = await inquirer.prompt([
868
+ {
869
+ type: "list",
870
+ name: "selectionMode",
871
+ message: "\u6A21\u677F\u9009\u62E9\u65B9\u5F0F:",
872
+ choices: [
873
+ {
874
+ name: `\u25CF ${chalk2.bold("\u63A8\u8350\u6A21\u677F")} ${chalk2.dim("(\u5E38\u7528\u6A21\u677F\u5FEB\u901F\u9009\u62E9) - \u57FA\u4E8E\u56E2\u961F\u4F7F\u7528\u9891\u7387\u63A8\u8350\u7684\u70ED\u95E8\u6A21\u677F")}`,
875
+ value: "recommended"
876
+ },
877
+ { name: chalk2.dim("\u2500".repeat(70)), value: "sep1", disabled: true },
878
+ {
879
+ name: `\u25CF ${chalk2.bold("\u5206\u7C7B\u6A21\u677F")} ${chalk2.dim("(\u6309\u9879\u76EE\u7C7B\u578B\u5206\u7C7B\u9009\u62E9) - \u524D\u7AEF\u3001\u540E\u7AEF\u3001\u79FB\u52A8\u7AEF\u3001\u684C\u9762\u7AEF\u5206\u7C7B\u6D4F\u89C8")}`,
880
+ value: "category"
881
+ },
882
+ { name: chalk2.dim("\u2500".repeat(70)), value: "sep2", disabled: true },
883
+ {
884
+ name: `\u25CF ${chalk2.bold("\u641C\u7D22\u6A21\u677F")} ${chalk2.dim("(\u5173\u952E\u8BCD\u641C\u7D22) - \u901A\u8FC7\u6280\u672F\u6808\u3001\u529F\u80FD\u7279\u6027\u7B49\u5173\u952E\u8BCD\u5FEB\u901F\u67E5\u627E")}`,
885
+ value: "search"
886
+ },
887
+ { name: chalk2.dim("\u2500".repeat(70)), value: "sep3", disabled: true },
888
+ {
889
+ name: `\u25CF ${chalk2.bold("\u5168\u90E8\u6A21\u677F")} ${chalk2.dim("(\u67E5\u770B\u6240\u6709\u53EF\u7528\u6A21\u677F) - \u6309\u5206\u7C7B\u5C55\u793A\u6240\u6709\u53EF\u7528\u7684\u9879\u76EE\u6A21\u677F")}`,
890
+ value: "all"
891
+ }
892
+ ],
893
+ pageSize: 10
894
+ }
895
+ ]);
896
+ switch (selectionMode) {
897
+ case "recommended":
898
+ return await selectFromRecommended();
899
+ case "category":
900
+ return await selectByCategory();
901
+ case "search":
902
+ return await selectBySearch();
903
+ case "all":
904
+ return await selectFromAll();
905
+ default:
906
+ return await selectByCategory();
907
+ }
908
+ }
909
+ async function selectFromRecommended() {
910
+ const recommended = getRecommendedTemplates();
911
+ if (Object.keys(recommended).length === 0) {
912
+ console.log(chalk2.yellow("\u26A0\uFE0F \u6682\u65E0\u63A8\u8350\u6A21\u677F"));
913
+ return await selectTemplateMethod();
914
+ }
915
+ console.log();
916
+ console.log(chalk2.blue.bold("\u{1F3AF} \u63A8\u8350\u6A21\u677F"));
917
+ console.log(chalk2.dim("\u57FA\u4E8E\u56E2\u961F\u4F7F\u7528\u9891\u7387\u548C\u9879\u76EE\u6210\u719F\u5EA6\u63A8\u8350"));
918
+ console.log();
919
+ const choices = [];
920
+ const entries = Object.entries(recommended);
921
+ entries.forEach(([key, template], index) => {
922
+ const tags = template.features.slice(0, 3).map((f) => chalk2.dim(`[${f}]`)).join(" ");
923
+ const ver = template.version === "full" ? chalk2.green("[\u5B8C\u6574\u7248]") : chalk2.yellow("[\u7CBE\u7B80\u7248]");
924
+ choices.push({
925
+ name: `${chalk2.bold.white(template.name.replace(/\s*(完整版|精简版)\s*$/, ""))} ${ver} - ${chalk2.dim(template.description)}
926
+ ${tags}${template.features.length > 3 ? chalk2.dim(` +${template.features.length - 3}more`) : ""}`,
927
+ value: { key, ...template },
928
+ short: template.name
929
+ });
930
+ if (index < entries.length - 1) {
931
+ choices.push({
932
+ name: chalk2.dim("\u2500".repeat(70)),
933
+ value: `sep_${index}`,
934
+ disabled: true
935
+ });
936
+ }
937
+ });
938
+ choices.push({ name: chalk2.dim("\u2B05\uFE0F \u8FD4\u56DE\u9009\u62E9\u5176\u4ED6\u65B9\u5F0F"), value: "back" });
939
+ const { selectedTemplate } = await inquirer.prompt([
940
+ {
941
+ type: "list",
942
+ name: "selectedTemplate",
943
+ message: "\u9009\u62E9\u63A8\u8350\u6A21\u677F:",
944
+ choices,
945
+ pageSize: 15,
946
+ loop: false
947
+ }
948
+ ]);
949
+ if (selectedTemplate === "back") return await selectTemplateMethod();
950
+ return selectedTemplate;
951
+ }
952
+ async function selectByCategory() {
953
+ while (true) {
954
+ const cat = await selectCategory();
955
+ if (cat === "back_to_method") return await selectTemplateMethod();
956
+ const stack = await selectStack(cat);
957
+ if (stack === "back_to_category") continue;
958
+ if (stack === "back_to_method") return await selectTemplateMethod();
959
+ const pattern = await selectPattern(cat, stack);
960
+ if (pattern === "back_to_stack" || pattern === "back_to_category") continue;
961
+ if (pattern === "back_to_method") return await selectTemplateMethod();
962
+ const tpl = await selectSpecificTemplate(cat, stack, pattern);
963
+ if (typeof tpl === "string") continue;
964
+ return tpl;
965
+ }
966
+ }
967
+ async function selectCategory() {
968
+ const choices = Object.entries(TEMPLATE_CATEGORIES).map(([key, c]) => ({
969
+ name: c.name,
970
+ value: key
971
+ }));
972
+ choices.push({
973
+ name: chalk2.dim("\u2190 \u8FD4\u56DE\u6A21\u677F\u9009\u62E9\u65B9\u5F0F"),
974
+ value: "back_to_method"
975
+ });
976
+ const { categoryKey } = await inquirer.prompt([
977
+ { type: "list", name: "categoryKey", message: "\u8BF7\u9009\u62E9\u9879\u76EE\u7C7B\u578B:", choices }
978
+ ]);
979
+ return categoryKey;
980
+ }
981
+ async function selectStack(categoryKey) {
982
+ const category = TEMPLATE_CATEGORIES[categoryKey];
983
+ const choices = Object.entries(category.stacks).map(([key, s]) => ({
984
+ name: s.name,
985
+ value: key
986
+ }));
987
+ choices.push(
988
+ {
989
+ name: chalk2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
990
+ value: "separator",
991
+ disabled: true
992
+ },
993
+ { name: chalk2.dim("\u2190 \u8FD4\u56DE\u9879\u76EE\u7C7B\u578B\u9009\u62E9"), value: "back_to_category" },
994
+ { name: chalk2.dim("\u2190 \u8FD4\u56DE\u6A21\u677F\u9009\u62E9\u65B9\u5F0F"), value: "back_to_method" }
995
+ );
996
+ if (choices.length === 4) return choices[0].value;
997
+ const { stackKey } = await inquirer.prompt([
998
+ { type: "list", name: "stackKey", message: "\u8BF7\u9009\u62E9\u6280\u672F\u6808:", choices }
999
+ ]);
1000
+ return stackKey;
1001
+ }
1002
+ async function selectPattern(catKey, stackKey) {
1003
+ if (["back_to_category", "back_to_method"].includes(stackKey))
1004
+ return stackKey;
1005
+ const stack = TEMPLATE_CATEGORIES[catKey].stacks[stackKey];
1006
+ const choices = Object.entries(stack.patterns).map(([key, p]) => ({
1007
+ name: p.name,
1008
+ value: key
1009
+ }));
1010
+ choices.push(
1011
+ {
1012
+ name: chalk2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1013
+ value: "separator",
1014
+ disabled: true
1015
+ },
1016
+ { name: chalk2.dim("\u2190 \u8FD4\u56DE\u6280\u672F\u6808\u9009\u62E9"), value: "back_to_stack" },
1017
+ { name: chalk2.dim("\u2190 \u8FD4\u56DE\u9879\u76EE\u7C7B\u578B\u9009\u62E9"), value: "back_to_category" },
1018
+ { name: chalk2.dim("\u2190 \u8FD4\u56DE\u6A21\u677F\u9009\u62E9\u65B9\u5F0F"), value: "back_to_method" }
1019
+ );
1020
+ if (choices.length === 5) return choices[0].value;
1021
+ const { patternKey } = await inquirer.prompt([
1022
+ { type: "list", name: "patternKey", message: "\u8BF7\u9009\u62E9\u67B6\u6784\u6A21\u5F0F:", choices }
1023
+ ]);
1024
+ return patternKey;
1025
+ }
1026
+ async function selectSpecificTemplate(catKey, stackKey, patternKey) {
1027
+ if (["back_to_stack", "back_to_category", "back_to_method"].includes(patternKey)) {
1028
+ return patternKey;
1029
+ }
1030
+ const templates = getTemplatesByCategory(catKey, stackKey, patternKey);
1031
+ const choices = Object.entries(templates).map(([key, t]) => ({
1032
+ name: `${t.name} - ${chalk2.dim(t.description)}`,
1033
+ value: { key, ...t },
1034
+ short: t.name
1035
+ }));
1036
+ choices.push(
1037
+ {
1038
+ name: chalk2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1039
+ value: "separator",
1040
+ disabled: true
1041
+ },
1042
+ { name: chalk2.dim("\u2190 \u8FD4\u56DE\u67B6\u6784\u6A21\u5F0F\u9009\u62E9"), value: "back_to_pattern" },
1043
+ { name: chalk2.dim("\u2190 \u8FD4\u56DE\u6280\u672F\u6808\u9009\u62E9"), value: "back_to_stack" },
1044
+ { name: chalk2.dim("\u2190 \u8FD4\u56DE\u9879\u76EE\u7C7B\u578B\u9009\u62E9"), value: "back_to_category" },
1045
+ { name: chalk2.dim("\u2190 \u8FD4\u56DE\u6A21\u677F\u9009\u62E9\u65B9\u5F0F"), value: "back_to_method" }
1046
+ );
1047
+ const { selectedTemplate } = await inquirer.prompt([
1048
+ {
1049
+ type: "list",
1050
+ name: "selectedTemplate",
1051
+ message: "\u8BF7\u9009\u62E9\u6A21\u677F\u7248\u672C:",
1052
+ choices
1053
+ }
1054
+ ]);
1055
+ return selectedTemplate;
1056
+ }
1057
+ async function selectBySearch() {
1058
+ while (true) {
1059
+ const { keyword } = await inquirer.prompt([
1060
+ {
1061
+ type: "input",
1062
+ name: "keyword",
1063
+ message: "\u8BF7\u8F93\u5165\u641C\u7D22\u5173\u952E\u8BCD (\u540D\u79F0\u3001\u63CF\u8FF0\u3001\u6280\u672F\u6808):",
1064
+ validate: (input) => input.trim() ? true : "\u5173\u952E\u8BCD\u4E0D\u80FD\u4E3A\u7A7A"
1065
+ }
1066
+ ]);
1067
+ const results = searchTemplates(keyword);
1068
+ if (Object.keys(results).length === 0) {
1069
+ console.log();
1070
+ console.log(chalk2.yellow("\u{1F50D} \u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6A21\u677F"));
1071
+ console.log(chalk2.dim(`\u641C\u7D22\u5173\u952E\u8BCD: "${keyword}"`));
1072
+ console.log();
1073
+ const { action } = await inquirer.prompt([
1074
+ {
1075
+ type: "list",
1076
+ name: "action",
1077
+ message: "\u8BF7\u9009\u62E9\u4E0B\u4E00\u6B65\u64CD\u4F5C:",
1078
+ choices: [
1079
+ { name: "\u{1F50D} \u91CD\u65B0\u641C\u7D22", value: "retry" },
1080
+ { name: "\u2B05\uFE0F \u8FD4\u56DE\u6A21\u677F\u9009\u62E9\u65B9\u5F0F", value: "back" }
1081
+ ]
1082
+ }
1083
+ ]);
1084
+ if (action === "retry") continue;
1085
+ return await selectTemplateMethod();
1086
+ }
1087
+ console.log();
1088
+ console.log(chalk2.green.bold("\u{1F50D} \u641C\u7D22\u7ED3\u679C"));
1089
+ console.log(
1090
+ chalk2.dim(
1091
+ `\u5173\u952E\u8BCD: "${keyword}" \u2022 \u627E\u5230 ${Object.keys(results).length} \u4E2A\u5339\u914D\u6A21\u677F`
1092
+ )
1093
+ );
1094
+ console.log();
1095
+ const choices = Object.entries(results).map(([key, t]) => {
1096
+ const hl = (text) => text.replace(
1097
+ new RegExp(`(${keyword})`, "gi"),
1098
+ chalk2.bgYellow.black("$1")
1099
+ );
1100
+ const ver = t.version === "full" ? chalk2.green("[\u5B8C\u6574\u7248]") : chalk2.yellow("[\u7CBE\u7B80\u7248]");
1101
+ const info = t.features.slice(0, 2).join(" \u2022 ");
1102
+ return {
1103
+ name: `${chalk2.bold(hl(t.name.replace(/\s*(完整版|精简版)\s*$/, "")))} ${ver}
1104
+ ${chalk2.dim(hl(t.description))}
1105
+ ${chalk2.dim(`${info} \u2022 key: ${key}`)}
1106
+ ${chalk2.dim("\u2500".repeat(60))}`,
1107
+ value: { key, ...t },
1108
+ short: t.name
1109
+ };
1110
+ });
1111
+ choices.push(
1112
+ { name: chalk2.dim("\u2501".repeat(70)), value: "separator", disabled: true },
1113
+ { name: "\u{1F50D} \u91CD\u65B0\u641C\u7D22", value: "search_again" },
1114
+ { name: "\u2B05\uFE0F \u8FD4\u56DE\u6A21\u677F\u9009\u62E9\u65B9\u5F0F", value: "back_to_mode" }
1115
+ );
1116
+ const { selectedTemplate } = await inquirer.prompt([
1117
+ {
1118
+ type: "list",
1119
+ name: "selectedTemplate",
1120
+ message: "\u9009\u62E9\u6A21\u677F:",
1121
+ choices,
1122
+ pageSize: 15,
1123
+ loop: false
1124
+ }
1125
+ ]);
1126
+ if (selectedTemplate === "search_again") continue;
1127
+ if (selectedTemplate === "back_to_mode")
1128
+ return await selectTemplateMethod();
1129
+ return selectedTemplate;
1130
+ }
1131
+ }
1132
+ async function selectFromAll() {
1133
+ const allTemplates = getAllTemplates();
1134
+ console.log();
1135
+ console.log(chalk2.blue.bold("\u{1F4CB} \u6240\u6709\u53EF\u7528\u6A21\u677F"));
1136
+ console.log(chalk2.dim(`\u5171 ${Object.keys(allTemplates).length} \u4E2A\u6A21\u677F\u53EF\u9009`));
1137
+ console.log();
1138
+ const categorizedChoices = [];
1139
+ for (const [_catKey, category] of Object.entries(TEMPLATE_CATEGORIES)) {
1140
+ categorizedChoices.push({
1141
+ name: chalk2.yellow.bold(category.name),
1142
+ value: `${_catKey}_header`,
1143
+ disabled: true
1144
+ });
1145
+ for (const [_sKey, stack] of Object.entries(category.stacks)) {
1146
+ for (const _pattern of Object.values(stack.patterns)) {
1147
+ for (const [key, t] of Object.entries(_pattern.templates)) {
1148
+ const ver = t.version === "full" ? chalk2.green("[\u5B8C\u6574\u7248]") : chalk2.yellow("[\u7CBE\u7B80\u7248]");
1149
+ categorizedChoices.push({
1150
+ name: ` \u25CF ${chalk2.bold(t.name.replace(/\s*(完整版|精简版)\s*$/, ""))} ${ver} - ${chalk2.dim(t.description)}
1151
+ ${chalk2.dim(`\u6280\u672F\u6808: ${stack.name} \u2022 \u547D\u4EE4: robot create my-app -t ${key}`)}`,
1152
+ value: { key, ...t },
1153
+ short: t.name
1154
+ });
1155
+ categorizedChoices.push({
1156
+ name: chalk2.dim(" " + "\u2500".repeat(66)),
1157
+ value: `sep_${key}`,
1158
+ disabled: true
1159
+ });
1160
+ }
1161
+ }
1162
+ }
1163
+ }
1164
+ categorizedChoices.push(
1165
+ { name: chalk2.dim("\u2501".repeat(70)), value: "separator", disabled: true },
1166
+ { name: chalk2.dim("\u2B05\uFE0F \u8FD4\u56DE\u6A21\u677F\u9009\u62E9\u65B9\u5F0F"), value: "back_to_mode" }
1167
+ );
1168
+ const { selectedTemplate } = await inquirer.prompt([
1169
+ {
1170
+ type: "list",
1171
+ name: "selectedTemplate",
1172
+ message: "\u9009\u62E9\u6A21\u677F:",
1173
+ choices: categorizedChoices,
1174
+ pageSize: 25,
1175
+ loop: false
1176
+ }
1177
+ ]);
1178
+ if (selectedTemplate === "back_to_mode") return await selectTemplateMethod();
1179
+ return selectedTemplate;
1180
+ }
1181
+ async function configureProject(options) {
1182
+ console.log();
1183
+ console.log(chalk2.blue("\u2699\uFE0F \u9879\u76EE\u914D\u7F6E"));
1184
+ console.log();
1185
+ const available = detectPackageManager();
1186
+ const hasBun = available.includes("bun");
1187
+ const hasPnpm = available.includes("pnpm");
1188
+ const managerChoices = [];
1189
+ if (available.includes("bun"))
1190
+ managerChoices.push({
1191
+ name: "bun (\u63A8\u8350 - \u6781\u901F\u5B89\u88C5\uFF0C\u73B0\u4EE3\u5316\uFF0C\u6027\u80FD\u6700\u4F73)",
1192
+ value: "bun"
1193
+ });
1194
+ if (available.includes("pnpm"))
1195
+ managerChoices.push({
1196
+ name: "pnpm (\u63A8\u8350 - \u5FEB\u901F\u5B89\u88C5\uFF0C\u8282\u7701\u78C1\u76D8\u7A7A\u95F4)",
1197
+ value: "pnpm"
1198
+ });
1199
+ if (available.includes("yarn"))
1200
+ managerChoices.push({
1201
+ name: "yarn (\u517C\u5BB9\u6027\u597D - \u9002\u7528\u4E8E\u73B0\u6709yarn\u9879\u76EE)",
1202
+ value: "yarn"
1203
+ });
1204
+ if (available.includes("npm"))
1205
+ managerChoices.push({
1206
+ name: "npm (\u9ED8\u8BA4 - Node.js\u5185\u7F6E\uFF0C\u517C\u5BB9\u6027\u6700\u597D)",
1207
+ value: "npm"
1208
+ });
1209
+ if (managerChoices.length === 0) {
1210
+ managerChoices.push(
1211
+ { name: "npm (\u9ED8\u8BA4)", value: "npm" },
1212
+ { name: "bun (\u5982\u5DF2\u5B89\u88C5)", value: "bun" },
1213
+ { name: "pnpm (\u5982\u5DF2\u5B89\u88C5)", value: "pnpm" },
1214
+ { name: "yarn (\u5982\u5DF2\u5B89\u88C5)", value: "yarn" }
1215
+ );
1216
+ }
1217
+ const config = await inquirer.prompt([
1218
+ {
1219
+ type: "confirm",
1220
+ name: "initGit",
1221
+ message: "\u662F\u5426\u521D\u59CB\u5316 Git \u4ED3\u5E93?",
1222
+ default: true
1223
+ },
1224
+ {
1225
+ type: "confirm",
1226
+ name: "installDeps",
1227
+ message: "\u662F\u5426\u7ACB\u5373\u5B89\u88C5\u4F9D\u8D56?",
1228
+ default: !options.skipInstall
1229
+ },
1230
+ {
1231
+ type: "list",
1232
+ name: "packageManager",
1233
+ message: "\u9009\u62E9\u5305\u7BA1\u7406\u5668:",
1234
+ choices: managerChoices,
1235
+ default: hasBun ? "bun" : hasPnpm ? "pnpm" : "npm",
1236
+ when: (a) => a.installDeps
1237
+ },
1238
+ {
1239
+ type: "input",
1240
+ name: "description",
1241
+ message: "\u9879\u76EE\u63CF\u8FF0 (\u53EF\u9009):",
1242
+ default: ""
1243
+ },
1244
+ {
1245
+ type: "input",
1246
+ name: "author",
1247
+ message: "\u4F5C\u8005 (\u53EF\u9009):",
1248
+ default: getGitUser()
1249
+ },
1250
+ {
1251
+ type: "confirm",
1252
+ name: "confirmConfig",
1253
+ message: "\u786E\u8BA4\u4EE5\u4E0A\u914D\u7F6E?",
1254
+ default: true
1255
+ }
1256
+ ]);
1257
+ if (!config.confirmConfig) {
1258
+ const { action } = await inquirer.prompt([
1259
+ {
1260
+ type: "list",
1261
+ name: "action",
1262
+ message: "\u8BF7\u9009\u62E9\u64CD\u4F5C:",
1263
+ choices: [
1264
+ { name: "\u{1F504} \u91CD\u65B0\u914D\u7F6E", value: "reconfigure" },
1265
+ { name: "\u274C \u53D6\u6D88\u521B\u5EFA", value: "cancel" }
1266
+ ]
1267
+ }
1268
+ ]);
1269
+ if (action === "reconfigure") return await configureProject(options);
1270
+ console.log(chalk2.yellow("\u274C \u53D6\u6D88\u521B\u5EFA\u9879\u76EE"));
1271
+ process.exit(0);
1272
+ }
1273
+ return config;
1274
+ }
1275
+ async function confirmCreation(projectName, template, config) {
1276
+ console.log();
1277
+ console.log(chalk2.blue("\u{1F4CB} \u9879\u76EE\u521B\u5EFA\u4FE1\u606F\u786E\u8BA4:"));
1278
+ console.log();
1279
+ console.log(` \u9879\u76EE\u540D\u79F0: ${chalk2.cyan(projectName)}`);
1280
+ console.log(` \u9009\u62E9\u6A21\u677F: ${chalk2.cyan(template.name)}`);
1281
+ console.log(` \u6A21\u677F\u63CF\u8FF0: ${chalk2.dim(template.description)}`);
1282
+ console.log(
1283
+ ` \u5305\u542B\u529F\u80FD: ${chalk2.dim(template.features.join(", ") || "\u81EA\u5B9A\u4E49\u6A21\u677F")}`
1284
+ );
1285
+ if (config.description)
1286
+ console.log(` \u9879\u76EE\u63CF\u8FF0: ${chalk2.dim(config.description)}`);
1287
+ if (config.author) console.log(` \u4F5C\u3000\u3000\u8005: ${chalk2.dim(config.author)}`);
1288
+ console.log(
1289
+ ` \u521D\u59CB\u5316Git: ${config.initGit ? chalk2.green("\u662F") : chalk2.dim("\u5426")}`
1290
+ );
1291
+ console.log(
1292
+ ` \u5B89\u88C5\u4F9D\u8D56: ${config.installDeps ? chalk2.green("\u662F") + chalk2.dim(` (${config.packageManager})`) : chalk2.dim("\u5426")}`
1293
+ );
1294
+ console.log(` \u6E90\u7801\u4ED3\u5E93: ${chalk2.dim(template.repoUrl)}`);
1295
+ console.log();
1296
+ const { confirmed } = await inquirer.prompt([
1297
+ {
1298
+ type: "confirm",
1299
+ name: "confirmed",
1300
+ message: "\u786E\u8BA4\u521B\u5EFA\u9879\u76EE?",
1301
+ default: true
1302
+ }
1303
+ ]);
1304
+ if (!confirmed) {
1305
+ console.log(chalk2.yellow("\u274C \u53D6\u6D88\u521B\u5EFA"));
1306
+ process.exit(0);
1307
+ }
1308
+ }
1309
+ async function executeCreation(projectName, template, config, options) {
1310
+ if (!projectName) throw new Error(`\u9879\u76EE\u540D\u79F0\u65E0\u6548: ${projectName}`);
1311
+ if (!template?.name)
1312
+ throw new Error(`\u6A21\u677F\u6570\u636E\u65E0\u6548: ${JSON.stringify(template)}`);
1313
+ const spinner = ora({
1314
+ text: "\u{1F680} \u51C6\u5907\u521B\u5EFA\u9879\u76EE...",
1315
+ spinner: "dots",
1316
+ color: "cyan"
1317
+ }).start();
1318
+ let tempPath;
1319
+ try {
1320
+ spinner.text = "\u{1F4C1} \u68C0\u67E5\u9879\u76EE\u76EE\u5F55...";
1321
+ const projectPath = path3.resolve(projectName);
1322
+ if (fs3.existsSync(projectPath)) {
1323
+ spinner.stop();
1324
+ console.log(chalk2.yellow("\u26A0\uFE0F \u9879\u76EE\u76EE\u5F55\u5DF2\u5B58\u5728"));
1325
+ const { overwrite } = await inquirer.prompt([
1326
+ {
1327
+ type: "confirm",
1328
+ name: "overwrite",
1329
+ message: "\u76EE\u5F55\u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6?",
1330
+ default: false
1331
+ }
1332
+ ]);
1333
+ if (!overwrite) {
1334
+ console.log(chalk2.yellow("\u274C \u53D6\u6D88\u521B\u5EFA"));
1335
+ process.exit(0);
1336
+ }
1337
+ spinner.start("\u{1F5D1}\uFE0F \u6E05\u7406\u73B0\u6709\u76EE\u5F55...");
1338
+ await fs3.remove(projectPath);
1339
+ spinner.text = "\u{1F4C1} \u51C6\u5907\u521B\u5EFA\u65B0\u76EE\u5F55...";
1340
+ }
1341
+ spinner.text = "\u{1F310} \u4E0B\u8F7D\u6700\u65B0\u6A21\u677F...";
1342
+ try {
1343
+ tempPath = await downloadTemplate(template, {
1344
+ spinner,
1345
+ noCache: options.noCache
1346
+ });
1347
+ if (!tempPath || !fs3.existsSync(tempPath))
1348
+ throw new Error(`\u6A21\u677F\u8DEF\u5F84\u65E0\u6548: ${tempPath}`);
1349
+ } catch (error) {
1350
+ spinner.fail("\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25");
1351
+ console.log();
1352
+ console.log(chalk2.red("\u274C \u6A21\u677F\u4E0B\u8F7D\u9519\u8BEF:"));
1353
+ console.log(chalk2.dim(` ${error.message}`));
1354
+ console.log();
1355
+ throw error;
1356
+ }
1357
+ await copyTemplate(tempPath, projectPath, spinner);
1358
+ spinner.text = "\u2699\uFE0F \u5904\u7406\u9879\u76EE\u914D\u7F6E...";
1359
+ await processProjectConfig(projectPath, projectName, template, config);
1360
+ if (config.initGit) {
1361
+ spinner.text = "\u{1F4DD} \u521D\u59CB\u5316 Git \u4ED3\u5E93...";
1362
+ initializeGitRepository(projectPath);
1363
+ }
1364
+ if (config.installDeps) {
1365
+ spinner.text = `\u{1F4E6} \u4F7F\u7528 ${config.packageManager} \u5B89\u88C5\u4F9D\u8D56...`;
1366
+ await installDependencies(projectPath, spinner, config.packageManager);
1367
+ }
1368
+ if (tempPath) {
1369
+ spinner.text = "\u{1F9F9} \u6E05\u7406\u4E34\u65F6\u6587\u4EF6...";
1370
+ await fs3.remove(tempPath).catch(() => {
1371
+ });
1372
+ }
1373
+ spinner.succeed(chalk2.green("\u{1F389} \u9879\u76EE\u521B\u5EFA\u6210\u529F!"));
1374
+ console.log();
1375
+ console.log(chalk2.green("\u{1F389} \u9879\u76EE\u521B\u5EFA\u5B8C\u6210!"));
1376
+ console.log();
1377
+ console.log(chalk2.blue("\u{1F4C1} \u9879\u76EE\u4FE1\u606F:"));
1378
+ console.log(` \u4F4D\u7F6E: ${chalk2.cyan(projectPath)}`);
1379
+ console.log(` \u6A21\u677F: ${chalk2.cyan(template.name)}`);
1380
+ console.log(
1381
+ ` Git\u4ED3\u5E93: ${config.initGit ? chalk2.green("\u5DF2\u521D\u59CB\u5316") : chalk2.dim("\u672A\u521D\u59CB\u5316")}`
1382
+ );
1383
+ console.log(
1384
+ ` \u4F9D\u8D56\u5B89\u88C5: ${config.installDeps ? chalk2.green("\u5DF2\u5B8C\u6210") : chalk2.dim("\u9700\u624B\u52A8\u5B89\u88C5")}`
1385
+ );
1386
+ console.log();
1387
+ console.log(chalk2.blue("\u{1F680} \u5FEB\u901F\u5F00\u59CB:"));
1388
+ console.log(chalk2.cyan(` cd ${projectName}`));
1389
+ const pm = config.packageManager || "bun";
1390
+ if (!config.installDeps) {
1391
+ console.log(chalk2.cyan(` ${pm} install`));
1392
+ }
1393
+ const cmd = getStartCommand(template, pm);
1394
+ if (cmd) console.log(chalk2.cyan(` ${cmd}`));
1395
+ if (pm === "bun")
1396
+ console.log(chalk2.dim(" # \u6216\u4F7F\u7528 npm: npm install && npm run dev"));
1397
+ else if (pm === "npm")
1398
+ console.log(
1399
+ chalk2.dim(" # \u6216\u4F7F\u7528 bun: bun install && bun run dev (\u5982\u5DF2\u5B89\u88C5)")
1400
+ );
1401
+ console.log();
1402
+ spinner.start("\u{1F4CA} \u7EDF\u8BA1\u9879\u76EE\u4FE1\u606F...");
1403
+ const stats = await generateProjectStats(projectPath);
1404
+ spinner.stop();
1405
+ if (stats) {
1406
+ printProjectStats(stats);
1407
+ console.log();
1408
+ }
1409
+ } catch (error) {
1410
+ if (tempPath) await fs3.remove(tempPath).catch(() => {
1411
+ });
1412
+ spinner.fail("\u521B\u5EFA\u9879\u76EE\u5931\u8D25");
1413
+ throw error;
1414
+ }
1415
+ }
1416
+ async function processProjectConfig(projectPath, projectName, template, config) {
1417
+ const pkgPath = path3.join(projectPath, "package.json");
1418
+ if (fs3.existsSync(pkgPath)) {
1419
+ const pkg = await fs3.readJson(pkgPath);
1420
+ pkg.name = projectName;
1421
+ pkg.description = config.description || `\u57FA\u4E8E ${template.name} \u521B\u5EFA\u7684\u9879\u76EE`;
1422
+ if (config.author) pkg.author = config.author;
1423
+ await fs3.writeJson(pkgPath, pkg, { spaces: 2 });
1424
+ }
1425
+ const readmePath = path3.join(projectPath, "README.md");
1426
+ if (fs3.existsSync(readmePath)) {
1427
+ let readme = await fs3.readFile(readmePath, "utf8");
1428
+ readme = readme.replace(/# .+/, `# ${projectName}`);
1429
+ const desc = config.description || `\u57FA\u4E8E ${template.name} \u521B\u5EFA\u7684\u9879\u76EE`;
1430
+ readme = readme.replace(/项目描述.*/, desc);
1431
+ if (config.author) readme += `
1432
+
1433
+ ## \u4F5C\u8005
1434
+
1435
+ ${config.author}
1436
+ `;
1437
+ await fs3.writeFile(readmePath, readme);
1438
+ }
1439
+ for (const [from, to] of [
1440
+ ["_gitignore", ".gitignore"],
1441
+ ["_env.example", ".env.example"]
1442
+ ]) {
1443
+ const src = path3.join(projectPath, from);
1444
+ const dest = path3.join(projectPath, to);
1445
+ if (fs3.existsSync(src)) await fs3.move(src, dest);
1446
+ }
1447
+ }
1448
+ function initializeGitRepository(projectPath) {
1449
+ try {
1450
+ execSync2("git --version", { stdio: "ignore" });
1451
+ execSync2("git init", { cwd: projectPath, stdio: "ignore" });
1452
+ execSync2("git add .", { cwd: projectPath, stdio: "ignore" });
1453
+ execSync2('git commit -m "feat: \u521D\u59CB\u5316\u9879\u76EE"', {
1454
+ cwd: projectPath,
1455
+ stdio: "ignore"
1456
+ });
1457
+ } catch {
1458
+ console.log(chalk2.yellow("\u26A0\uFE0F Git \u4E0D\u53EF\u7528\uFF0C\u8DF3\u8FC7\u4ED3\u5E93\u521D\u59CB\u5316"));
1459
+ }
1460
+ }
1461
+ function getStartCommand(template, pm) {
1462
+ const script = START_COMMAND_MAP[template.key] || "dev";
1463
+ return `${pm} run ${script}`;
1464
+ }
1465
+
1466
+ // src/doctor.ts
1467
+ import chalk3 from "chalk";
1468
+ import ora2 from "ora";
1469
+ import { execSync as execSync3 } from "child_process";
1470
+ async function runDoctor(options = {}) {
1471
+ console.log();
1472
+ console.log(chalk3.blue.bold("\u{1F3E5} Robot CLI \u73AF\u5883\u8BCA\u65AD"));
1473
+ console.log();
1474
+ const results = [];
1475
+ const nodeVersion = process.version;
1476
+ const nodeMajor = parseInt(nodeVersion.slice(1), 10);
1477
+ results.push({
1478
+ name: "Node.js",
1479
+ status: nodeMajor >= 20 ? "ok" : "error",
1480
+ detail: nodeMajor >= 20 ? `${nodeVersion} (\u8981\u6C42 >= 20)` : `${nodeVersion} ${chalk3.red("(\u9700\u8981\u5347\u7EA7\u5230 Node.js 20+)")}`
1481
+ });
1482
+ try {
1483
+ const gitVer = execSync3("git --version", { encoding: "utf8" }).trim().replace("git version ", "");
1484
+ results.push({ name: "Git", status: "ok", detail: gitVer });
1485
+ } catch {
1486
+ results.push({ name: "Git", status: "error", detail: "\u672A\u5B89\u88C5" });
1487
+ }
1488
+ for (const pm of ["bun", "pnpm", "yarn", "npm"]) {
1489
+ try {
1490
+ const ver = execSync3(`${pm} --version`, {
1491
+ encoding: "utf8",
1492
+ stdio: ["pipe", "pipe", "ignore"]
1493
+ }).trim();
1494
+ const rec = pm === "bun" ? " (\u63A8\u8350)" : "";
1495
+ results.push({
1496
+ name: pm,
1497
+ status: "ok",
1498
+ detail: `v${ver.replace(/^v/, "")}${rec}`
1499
+ });
1500
+ } catch {
1501
+ results.push({
1502
+ name: pm,
1503
+ status: pm === "npm" ? "error" : "warn",
1504
+ detail: "\u672A\u5B89\u88C5"
1505
+ });
1506
+ }
1507
+ }
1508
+ const spinner = ora2({ text: "\u68C0\u67E5\u7F51\u7EDC...", spinner: "dots" }).start();
1509
+ const githubOk = await checkNetworkConnection("https://github.com");
1510
+ results.push({
1511
+ name: "GitHub",
1512
+ status: githubOk ? "ok" : "warn",
1513
+ detail: githubOk ? "\u8FDE\u63A5\u6B63\u5E38" : "\u65E0\u6CD5\u8BBF\u95EE (\u53EF\u80FD\u9700\u8981\u4EE3\u7406)"
1514
+ });
1515
+ const npmOk = await checkNetworkConnection("https://registry.npmjs.org");
1516
+ results.push({
1517
+ name: "npm Registry",
1518
+ status: npmOk ? "ok" : "warn",
1519
+ detail: npmOk ? "\u8FDE\u63A5\u6B63\u5E38" : "\u65E0\u6CD5\u8BBF\u95EE"
1520
+ });
1521
+ spinner.stop();
1522
+ const cacheStats = await getCacheStats();
1523
+ results.push({
1524
+ name: "\u7F13\u5B58",
1525
+ status: "ok",
1526
+ detail: cacheStats.count > 0 ? `${cacheStats.count} \u4E2A\u6A21\u677F (${formatBytes(cacheStats.totalSize)})` : "\u7A7A"
1527
+ });
1528
+ const icons = {
1529
+ ok: chalk3.green("\u2705"),
1530
+ warn: chalk3.yellow("\u26A0\uFE0F "),
1531
+ error: chalk3.red("\u274C")
1532
+ };
1533
+ for (const r of results) {
1534
+ const icon = icons[r.status];
1535
+ console.log(` ${icon} ${chalk3.bold(r.name.padEnd(14))} ${r.detail}`);
1536
+ }
1537
+ console.log();
1538
+ const hasError = results.some((r) => r.status === "error");
1539
+ const hasWarn = results.some((r) => r.status === "warn");
1540
+ if (hasError) {
1541
+ console.log(chalk3.red(" \u8BCA\u65AD\u7ED3\u679C: \u73AF\u5883\u5B58\u5728\u95EE\u9898\uFF0C\u8BF7\u4FEE\u590D\u4E0A\u8FF0\u9519\u8BEF \u274C"));
1542
+ } else if (hasWarn) {
1543
+ console.log(chalk3.yellow(" \u8BCA\u65AD\u7ED3\u679C: \u73AF\u5883\u57FA\u672C\u6B63\u5E38\uFF0C\u90E8\u5206\u7EC4\u4EF6\u7F3A\u5931 \u26A0\uFE0F"));
1544
+ } else {
1545
+ console.log(chalk3.green(" \u8BCA\u65AD\u7ED3\u679C: \u73AF\u5883\u5065\u5EB7 \u2705"));
1546
+ }
1547
+ console.log();
1548
+ if (options.clearCache) {
1549
+ const cSpinner = ora2("\u6E05\u7406\u6A21\u677F\u7F13\u5B58...").start();
1550
+ await clearCache();
1551
+ cSpinner.succeed("\u6A21\u677F\u7F13\u5B58\u5DF2\u6E05\u7406");
1552
+ console.log();
1553
+ }
1554
+ }
1555
+
1556
+ // src/index.ts
1557
+ var __dirname = dirname(fileURLToPath(import.meta.url));
1558
+ function getPackageVersion() {
1559
+ try {
1560
+ const pkg = JSON.parse(
1561
+ readFileSync(join(__dirname, "..", "package.json"), "utf8")
1562
+ );
1563
+ return pkg.version || "2.0.0";
1564
+ } catch {
1565
+ return "2.0.0";
1566
+ }
1567
+ }
1568
+ var VERSION = getPackageVersion();
1569
+ function showWelcome() {
1570
+ const banner = boxen(
1571
+ [
1572
+ "",
1573
+ chalk4.bold.cyan(" \u{1F916} R O B O T - C L I "),
1574
+ "",
1575
+ chalk4.dim(` v${VERSION} \u2014 \u5DE5\u7A0B\u5316\u9879\u76EE\u811A\u624B\u67B6`),
1576
+ "",
1577
+ chalk4.dim(" \u5FEB\u901F\u521B\u5EFA\u6807\u51C6\u5316\u524D\u7AEF/\u540E\u7AEF/\u79FB\u52A8\u7AEF/\u684C\u9762\u7AEF\u9879\u76EE"),
1578
+ ""
1579
+ ].join("\n"),
1580
+ {
1581
+ padding: 1,
1582
+ margin: { top: 1, bottom: 1, left: 2, right: 2 },
1583
+ borderStyle: "round",
1584
+ borderColor: "cyan"
1585
+ }
1586
+ );
1587
+ console.log(banner);
1588
+ }
1589
+ async function showMainMenu() {
1590
+ const allTemplates = getAllTemplates();
1591
+ const count = Object.keys(allTemplates).length;
1592
+ console.log(
1593
+ chalk4.dim(
1594
+ ` \u{1F4E6} ${count} \u4E2A\u6A21\u677F\u53EF\u7528 \u2022 Node ${process.version} \u2022 v${VERSION}`
1595
+ )
1596
+ );
1597
+ console.log();
1598
+ const { action } = await inquirer2.prompt([
1599
+ {
1600
+ type: "list",
1601
+ name: "action",
1602
+ message: "\u8BF7\u9009\u62E9\u64CD\u4F5C:",
1603
+ choices: [
1604
+ {
1605
+ name: `\u{1F680} ${chalk4.bold("\u521B\u5EFA\u65B0\u9879\u76EE")} ${chalk4.dim("\u2014 \u9009\u62E9\u6A21\u677F\u521B\u5EFA\u9879\u76EE")}`,
1606
+ value: "create"
1607
+ },
1608
+ {
1609
+ name: `\u{1F4CB} ${chalk4.bold("\u67E5\u770B\u6A21\u677F\u5217\u8868")} ${chalk4.dim("\u2014 \u6D4F\u89C8\u6240\u6709\u53EF\u7528\u6A21\u677F")}`,
1610
+ value: "list"
1611
+ },
1612
+ {
1613
+ name: `\u{1F50D} ${chalk4.bold("\u641C\u7D22\u6A21\u677F")} ${chalk4.dim("\u2014 \u6309\u5173\u952E\u8BCD\u641C\u7D22\u6A21\u677F")}`,
1614
+ value: "search"
1615
+ },
1616
+ {
1617
+ name: `\u{1F3E5} ${chalk4.bold("\u73AF\u5883\u8BCA\u65AD")} ${chalk4.dim("\u2014 \u68C0\u67E5\u5F00\u53D1\u73AF\u5883")}`,
1618
+ value: "doctor"
1619
+ },
1620
+ { name: chalk4.dim("\u2500".repeat(50)), value: "sep", disabled: true },
1621
+ { name: `\u274C ${chalk4.dim("\u9000\u51FA")}`, value: "exit" }
1622
+ ]
1623
+ }
1624
+ ]);
1625
+ switch (action) {
1626
+ case "create":
1627
+ await createProject(void 0, {});
1628
+ break;
1629
+ case "list":
1630
+ showTemplateList(false);
1631
+ break;
1632
+ case "search":
1633
+ await searchInteractive();
1634
+ break;
1635
+ case "doctor":
1636
+ await runDoctor();
1637
+ break;
1638
+ case "exit":
1639
+ console.log(chalk4.dim("\u{1F44B} \u518D\u89C1!"));
1640
+ process.exit(0);
1641
+ }
1642
+ }
1643
+ function showTemplateList(recommended) {
1644
+ const templates = recommended ? getRecommendedTemplates() : getAllTemplates();
1645
+ const title = recommended ? "\u{1F3AF} \u63A8\u8350\u6A21\u677F" : "\u{1F4CB} \u6240\u6709\u6A21\u677F";
1646
+ const entries = Object.entries(templates);
1647
+ console.log();
1648
+ console.log(chalk4.blue.bold(title));
1649
+ console.log(chalk4.dim(`\u5171 ${entries.length} \u4E2A\u6A21\u677F`));
1650
+ console.log();
1651
+ for (const [key, t] of entries) {
1652
+ const ver = t.version === "full" ? chalk4.green("[\u5B8C\u6574\u7248]") : chalk4.yellow("[\u7CBE\u7B80\u7248]");
1653
+ const cat = getCategoryForTemplate(key);
1654
+ const catLabel = cat ? chalk4.dim(`[${cat}]`) : "";
1655
+ console.log(` ${chalk4.bold(t.name)} ${ver} ${catLabel}`);
1656
+ console.log(` ${chalk4.dim(t.description)}`);
1657
+ console.log(` ${chalk4.dim(`\u547D\u4EE4: robot create my-app -t ${key}`)}`);
1658
+ console.log(` ${chalk4.dim(`features: ${t.features.join(", ")}`)}`);
1659
+ console.log();
1660
+ }
1661
+ }
1662
+ async function searchInteractive() {
1663
+ const { keyword } = await inquirer2.prompt([
1664
+ {
1665
+ type: "input",
1666
+ name: "keyword",
1667
+ message: "\u641C\u7D22\u5173\u952E\u8BCD:",
1668
+ validate: (i) => i.trim() ? true : "\u8BF7\u8F93\u5165\u5173\u952E\u8BCD"
1669
+ }
1670
+ ]);
1671
+ showSearchResults(keyword);
1672
+ }
1673
+ function showSearchResults(keyword) {
1674
+ const results = searchTemplates(keyword);
1675
+ const entries = Object.entries(results);
1676
+ console.log();
1677
+ if (entries.length === 0) {
1678
+ console.log(chalk4.yellow(`\u{1F50D} \u6CA1\u6709\u627E\u5230\u300C${keyword}\u300D\u76F8\u5173\u6A21\u677F`));
1679
+ console.log();
1680
+ return;
1681
+ }
1682
+ console.log(chalk4.green.bold(`\u{1F50D} \u641C\u7D22\u7ED3\u679C: "${keyword}"`));
1683
+ console.log(chalk4.dim(`\u627E\u5230 ${entries.length} \u4E2A\u5339\u914D`));
1684
+ console.log();
1685
+ for (const [key, t] of entries) {
1686
+ const ver = t.version === "full" ? chalk4.green("[\u5B8C\u6574\u7248]") : chalk4.yellow("[\u7CBE\u7B80\u7248]");
1687
+ console.log(` ${chalk4.bold(t.name)} ${ver}`);
1688
+ console.log(` ${chalk4.dim(t.description)}`);
1689
+ console.log(` ${chalk4.dim(`robot create my-app -t ${key}`)}`);
1690
+ console.log();
1691
+ }
1692
+ }
1693
+ async function main() {
1694
+ const program = new Command();
1695
+ program.name("robot").description("\u{1F916} Robot CLI - \u5DE5\u7A0B\u5316\u9879\u76EE\u811A\u624B\u67B6").version(VERSION, "-v, --version");
1696
+ program.command("create [project-name]").description("\u521B\u5EFA\u65B0\u9879\u76EE").option("-t, --template <name>", "\u6307\u5B9A\u6A21\u677F\u540D\u79F0").option("--skip-install", "\u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5").option("--dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4E0D\u5B9E\u9645\u521B\u5EFA").option("--from <url>", "\u4ECE\u81EA\u5B9A\u4E49 Git \u4ED3\u5E93\u521B\u5EFA").option("--no-cache", "\u4E0D\u4F7F\u7528\u7F13\u5B58").action(async (projectName, opts) => {
1697
+ showWelcome();
1698
+ await createProject(projectName, opts);
1699
+ checkForUpdates("@agile-team/robot-cli", VERSION).catch(() => {
1700
+ });
1701
+ });
1702
+ program.command("list").description("\u67E5\u770B\u6A21\u677F\u5217\u8868").option("-r, --recommended", "\u53EA\u663E\u793A\u63A8\u8350\u6A21\u677F").action((opts) => {
1703
+ showTemplateList(!!opts.recommended);
1704
+ });
1705
+ program.command("search <keyword>").description("\u641C\u7D22\u6A21\u677F").action((keyword) => {
1706
+ showSearchResults(keyword);
1707
+ });
1708
+ program.command("doctor").description("\u8BCA\u65AD\u5F00\u53D1\u73AF\u5883").option("--clear-cache", "\u6E05\u7406\u6A21\u677F\u7F13\u5B58").action(async (opts) => {
1709
+ await runDoctor(opts);
1710
+ });
1711
+ if (process.argv.length <= 2) {
1712
+ showWelcome();
1713
+ await showMainMenu();
1714
+ return;
1715
+ }
1716
+ await program.parseAsync(process.argv);
1717
+ }
1718
+ export {
1719
+ main
1720
+ };