@guchenyo/create-temp 0.0.1 → 0.0.3

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/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ UNLICENSED
2
+
3
+ Copyright (c) GuChena
4
+
5
+ All rights reserved.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # create-temp
2
2
 
3
3
  Local template scaffolding CLI for this repository. It clones the latest
4
- templates from GitHub on each run (SSH access required).
4
+ templates from GitHub on each run (HTTPS by default).
5
5
 
6
6
  ## Usage
7
7
 
@@ -20,19 +20,19 @@ create-temp front/web/vue3/vue3-no-ts my-web
20
20
  Use with npm create:
21
21
 
22
22
  ```bash
23
- npm create @guchen_0521/temp@latest -- backend/express/express-no-ts my-api
23
+ npm create @guchenyo/temp@latest -- backend/express/express-no-ts my-api
24
24
  ```
25
25
 
26
26
  Interactive selection (TUI):
27
27
 
28
28
  ```bash
29
- npm create @guchen_0521/temp@latest
29
+ npm create @guchenyo/temp@latest
30
30
  ```
31
31
 
32
32
  ## Requirements
33
33
 
34
34
  - Git installed and available in PATH
35
- - SSH access to `ssh://git@ssh.github.com:443/GuChena/template.git`
35
+ - Access to `https://github.com/GuChena/template.git` (or override `TEMPLATE_REPO_URL`)
36
36
 
37
37
  ## Publish
38
38
 
package/lib/scaffold.js CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  } from "@clack/prompts";
25
25
  import gradient from "gradient-string";
26
26
  import ora from "ora";
27
+ import pc from "picocolors";
27
28
 
28
29
  const IGNORE_NAMES = new Set([
29
30
  ".git",
@@ -43,7 +44,7 @@ const PROMPT_CANCELLED_MESSAGE = "已取消操作。";
43
44
  const PROMPT_BACK_VALUE = "__back";
44
45
  const OPTIONAL_PACKAGES_PATH = new URL(
45
46
  "../optional-packages.json",
46
- import.meta.url
47
+ import.meta.url,
47
48
  );
48
49
 
49
50
  function assertInteractive() {
@@ -71,6 +72,90 @@ function applyGradientPerLine(text, gradientFn) {
71
72
  .join("\n");
72
73
  }
73
74
 
75
+ function renderHero() {
76
+ const headerGradient = gradient(["#4285f4", "#9b72cb", "#d96570"]);
77
+ const bodyGradient = gradient(["#8ec5ff", "#c9a1f0", "#ffb3ba"]);
78
+ const logo = [
79
+ " ██████╗██████╗ ███████╗ █████╗ ████████╗███████╗ ████████╗███████╗███╗ ███╗██████╗ ",
80
+ " ██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔════╝ ╚══██╔══╝██╔════╝████╗ ████║██╔══██╗ ",
81
+ " ██║ ██████╔╝█████╗ ███████║ ██║ █████╗ █████╗ ██║ █████╗ ██╔████╔██║██████╔╝ ",
82
+ " ██║ ██╔══██╗██╔══╝ ██╔══██║ ██║ ██╔══╝ ╚════╝ ██║ ██╔══╝ ██║╚██╔╝██║██╔═══╝ ",
83
+ " ╚██████╗██║ ██║███████╗██║ ██║ ██║ ███████╗ ██║ ███████╗██║ ╚═╝ ██║██║ ",
84
+ " ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ "
85
+ ].join("\n");
86
+
87
+ const header = applyGradientPerLine(
88
+ [
89
+ logo,
90
+ " ✨ 一站式项目模板脚手架 ✨",
91
+ "",
92
+ " 按步骤选择模板、可选依赖和项目目录。",
93
+ ].join("\n"),
94
+ headerGradient,
95
+ );
96
+ const hints = applyGradientPerLine(
97
+ [
98
+ " 流程: 1) 模板 2) 可选依赖 3) 项目名称",
99
+ " 支持: backend / front",
100
+ ].join("\n"),
101
+ bodyGradient,
102
+ );
103
+ return `${header}\n\n${hints}`;
104
+ }
105
+
106
+ function buildTemplateOverview(templates) {
107
+ const grouped = new Map();
108
+
109
+ templates.forEach((template) => {
110
+ const [root, ...rest] = template.key.split("/");
111
+ if (!grouped.has(root)) {
112
+ grouped.set(root, []);
113
+ }
114
+ grouped.get(root).push(rest.join("/"));
115
+ });
116
+
117
+ const lines = [];
118
+ const roots = Array.from(grouped.keys()).sort((a, b) => a.localeCompare(b));
119
+ roots.forEach((root) => {
120
+ const items = grouped.get(root).sort((a, b) => a.localeCompare(b));
121
+ lines.push(`${root}/ (${items.length})`);
122
+ items.slice(0, 6).forEach((item) => {
123
+ lines.push(` - ${item}`);
124
+ });
125
+ if (items.length > 6) {
126
+ lines.push(` - ... +${items.length - 6} 更多`);
127
+ }
128
+ });
129
+
130
+ return lines.join("\n");
131
+ }
132
+
133
+ function buildNextStepsBlock({
134
+ selectedTemplateKey,
135
+ selectedOptionalPackages,
136
+ resolvedTargetDir,
137
+ packageManager,
138
+ }) {
139
+ const lines = [
140
+ `📂 模板: ${pc.cyan(selectedTemplateKey)}`,
141
+ `📁 目录: ${pc.cyan(resolvedTargetDir)}`,
142
+ "",
143
+ "🚀 下一步命令:",
144
+ ` ${pc.green(`cd ${resolvedTargetDir}`)}`,
145
+ ` ${pc.green(`${packageManager} install`)}`,
146
+ ` ${pc.green(`${packageManager} run dev`)}`,
147
+ ];
148
+
149
+ if (selectedOptionalPackages.length > 0) {
150
+ const installed = selectedOptionalPackages
151
+ .map((pkg) => pkg.name)
152
+ .join(", ");
153
+ lines.push("", `📦 已预装可选依赖: ${pc.magenta(installed)}`);
154
+ }
155
+
156
+ return lines.join("\n");
157
+ }
158
+
74
159
  async function cloneTemplateRepo() {
75
160
  const tempDir = await mkdtemp(path.join(os.tmpdir(), "template-cli-"));
76
161
  const repoDir = path.join(tempDir, `repo-${Date.now().toString(36)}`);
@@ -85,7 +170,7 @@ async function cloneTemplateRepo() {
85
170
  await execFileAsync(
86
171
  "git",
87
172
  ["clone", "--depth", "1", TEMPLATE_REPO_URL, repoDir],
88
- { env }
173
+ { env },
89
174
  );
90
175
  return { repoDir, tempDir };
91
176
  } catch (error) {
@@ -227,30 +312,24 @@ async function promptOptionalPackages(optionalPackages) {
227
312
 
228
313
  const choices = optionalPackages.map((pkg) => ({
229
314
  value: pkg.name,
230
- label: pkg.description ? `${pkg.name} - ${pkg.description}` : pkg.name,
315
+ label: pkg.description
316
+ ? `${pkg.name}${pkg.dev ? " (dev)" : ""} - ${pkg.description}`
317
+ : `${pkg.name}${pkg.dev ? " (dev)" : ""}`,
231
318
  }));
232
319
 
233
320
  const selected = handleCancel(
234
321
  await multiselect({
235
- message: "选择可选依赖:",
322
+ message: "🧩 第 2 步:选择可选预装依赖(空格多选,回车确认)",
236
323
  options: choices,
237
324
  required: false,
238
- })
325
+ }),
239
326
  );
240
327
 
241
328
  if (!Array.isArray(selected) || selected.length === 0) {
242
329
  return [];
243
330
  }
244
331
 
245
- if (isInteractive() && process.stdout.isTTY) {
246
- const gray = "\x1b[90m";
247
- const dim = "\x1b[2m";
248
- const reset = "\x1b[0m";
249
- process.stdout.write("\x1b[1A\x1b[2K");
250
- process.stdout.write(
251
- `${gray}│${reset} ${dim}${selected.join(", ")}${reset}\n`
252
- );
253
- }
332
+ note(selected.join("\n"), "已选择可选依赖");
254
333
 
255
334
  const selectedSet = new Set(selected);
256
335
  return optionalPackages.filter((pkg) => selectedSet.has(pkg.name));
@@ -301,10 +380,10 @@ async function promptSelectTemplateTree(templates) {
301
380
  await select({
302
381
  message:
303
382
  parents.length === 0
304
- ? "选择模板类别:"
305
- : `选择模板目录: ${pathStack.join("/")}`,
383
+ ? "📁 第 1 步:选择模板类别"
384
+ : `📂 第 1 步:选择模板目录 (${pc.cyan(pathStack.join("/"))})`,
306
385
  options: choices,
307
- })
386
+ }),
308
387
  );
309
388
 
310
389
  if (!selection || typeof selection !== "string") {
@@ -336,11 +415,11 @@ async function promptTargetDir() {
336
415
  assertInteractive();
337
416
  const response = handleCancel(
338
417
  await text({
339
- message: "项目名称:",
418
+ message: "🎯 第 3 步:输入项目名称",
340
419
  placeholder: "my-app",
341
420
  validate: (value) =>
342
421
  value && value.trim().length > 0 ? undefined : "请输入项目名称。",
343
- })
422
+ }),
344
423
  );
345
424
  return response.trim();
346
425
  }
@@ -392,11 +471,11 @@ async function applyOptionalPackages(targetPath, packages) {
392
471
 
393
472
  const hadDependencies = Object.prototype.hasOwnProperty.call(
394
473
  pkgJson,
395
- "dependencies"
474
+ "dependencies",
396
475
  );
397
476
  const hadDevDependencies = Object.prototype.hasOwnProperty.call(
398
477
  pkgJson,
399
- "devDependencies"
478
+ "devDependencies",
400
479
  );
401
480
 
402
481
  const dependencies = hadDependencies ? { ...pkgJson.dependencies } : {};
@@ -436,27 +515,9 @@ async function applyOptionalPackages(targetPath, packages) {
436
515
  export async function scaffold({ templateKey, targetDir }) {
437
516
  const interactive = isInteractive();
438
517
  if (interactive) {
439
- intro("create-temp");
440
- console.log("");
441
- console.log(
442
- applyGradientPerLine(
443
- ["轻量模板脚手架", "按提示选择模板并设置项目名称"].join("\n"),
444
- gradient(["#8ec5ff", "#b6e3ff"])
445
- )
446
- );
518
+ intro(gradient(["#8ec5ff", "#b6e3ff"])("create-temp"));
447
519
  console.log("");
448
- console.log(
449
- applyGradientPerLine(
450
- [
451
- "可用模板与用途:",
452
- " backend/",
453
- " - express/express-no-ts: Node.js + Express API 服务",
454
- " front/",
455
- " - web/vue3/vue3-no-ts: Vue3 前端站点",
456
- ].join("\n"),
457
- gradient(["#9de7ff", "#b7f7d4"])
458
- )
459
- );
520
+ console.log(renderHero());
460
521
  console.log("");
461
522
  }
462
523
 
@@ -464,7 +525,7 @@ export async function scaffold({ templateKey, targetDir }) {
464
525
  let tempDir = null;
465
526
  const cloneSpinner = interactive
466
527
  ? ora({
467
- text: "正在获取模板...",
528
+ text: pc.cyan("正在获取模板..."),
468
529
  spinner: "dots",
469
530
  interval: 50,
470
531
  }).start()
@@ -474,12 +535,11 @@ export async function scaffold({ templateKey, targetDir }) {
474
535
  repoDir = cloneResult.repoDir;
475
536
  tempDir = cloneResult.tempDir;
476
537
  if (cloneSpinner) {
477
- cloneSpinner.stop();
478
- console.log(`T ${gradient(["#8ec5ff", "#b6e3ff"])("加载完成")}`);
538
+ cloneSpinner.succeed(pc.green("✨ 模板索引加载完成"));
479
539
  }
480
540
  } catch (error) {
481
541
  if (cloneSpinner) {
482
- cloneSpinner.fail("模板获取失败");
542
+ cloneSpinner.fail(pc.red("模板获取失败"));
483
543
  }
484
544
  const message = error instanceof Error ? error.message : String(error);
485
545
  throw new Error(`模板仓库拉取失败,请检查网络或权限。${message}`);
@@ -509,11 +569,15 @@ export async function scaffold({ templateKey, targetDir }) {
509
569
  throw new Error("未在 backend/ 或 front/ 下找到模板。");
510
570
  }
511
571
 
572
+ if (interactive) {
573
+ note(buildTemplateOverview(templates), `模板分类总览(共 ${templates.length} 个)`);
574
+ }
575
+
512
576
  let selectedTemplate = null;
513
577
  if (templateKey) {
514
578
  const normalizedKey = normalizeTemplateKey(templateKey);
515
579
  selectedTemplate = templates.find(
516
- (template) => normalizeTemplateKey(template.key) === normalizedKey
580
+ (template) => normalizeTemplateKey(template.key) === normalizedKey,
517
581
  );
518
582
  if (!selectedTemplate) {
519
583
  const keys = templates.map((template) => template.key).join(", ");
@@ -526,7 +590,7 @@ export async function scaffold({ templateKey, targetDir }) {
526
590
  const optionalRules = await loadOptionalPackageRules();
527
591
  const optionalPackages = collectOptionalPackages(
528
592
  optionalRules,
529
- selectedTemplate.key
593
+ selectedTemplate.key,
530
594
  );
531
595
  const selectedOptionalPackages =
532
596
  optionalPackages.length > 0
@@ -540,29 +604,29 @@ export async function scaffold({ templateKey, targetDir }) {
540
604
  try {
541
605
  if (interactive) {
542
606
  buildSpinner = ora({
543
- text: "正在生成项目...",
607
+ text: pc.cyan("🚀 正在生成项目..."),
544
608
  spinner: "dots",
545
609
  interval: 50,
546
610
  }).start();
547
611
  }
548
612
  if (buildSpinner) {
549
- buildSpinner.text = "项目初始化中 (1/3)";
613
+ buildSpinner.text = pc.cyan("📦 正在初始化项目目录 (1/3)");
550
614
  }
551
615
  await ensureEmptyDir(targetPath);
552
616
  if (buildSpinner) {
553
- buildSpinner.text = "项目初始化中 (2/3)";
617
+ buildSpinner.text = pc.cyan("🚚 正在复制模板文件 (2/3)");
554
618
  }
555
619
  await copyDir(selectedTemplate.dir, targetPath);
556
620
  if (buildSpinner) {
557
- buildSpinner.text = "项目初始化中 (3/3)";
621
+ buildSpinner.text = pc.cyan("🔧 正在应用可选依赖 (3/3)");
558
622
  }
559
623
  await applyOptionalPackages(targetPath, selectedOptionalPackages);
560
624
  if (buildSpinner) {
561
- buildSpinner.succeed(" 项目生成完成,可执行以下指令");
625
+ buildSpinner.succeed(pc.green("🎉 项目生成完成"));
562
626
  }
563
627
  } catch (error) {
564
628
  if (buildSpinner) {
565
- buildSpinner.fail("项目生成失败");
629
+ buildSpinner.fail(pc.red("项目生成失败"));
566
630
  }
567
631
  throw error;
568
632
  }
@@ -582,12 +646,16 @@ export async function scaffold({ templateKey, targetDir }) {
582
646
  }
583
647
 
584
648
  if (interactive) {
585
- const cyan = "\x1b[36m";
586
- const bold = "\x1b[1m";
587
- const reset = "\x1b[0m";
588
- console.log(
589
- `${cyan} cd ${resolvedTargetDir}\n ${packageManager} install\n ${packageManager} run dev${reset}`
649
+ note(
650
+ buildNextStepsBlock({
651
+ selectedTemplateKey: selectedTemplate.key,
652
+ selectedOptionalPackages,
653
+ resolvedTargetDir,
654
+ packageManager,
655
+ }),
656
+ "✨ 下一步",
590
657
  );
658
+ outro(pc.green(`✅ 完成:${selectedTemplate.key}`));
591
659
  } else {
592
660
  console.log(`Created project in ${targetPath}`);
593
661
  console.log("Next steps:");
@@ -11,7 +11,52 @@
11
11
  {
12
12
  "name": "mysql2",
13
13
  "dev": false,
14
- "description": "MySQL 驱动"
14
+ "description": "MySQL驱动"
15
+ }
16
+ ]
17
+ },
18
+ {
19
+ "match": "backend/nest",
20
+ "packages": [
21
+ {
22
+ "name": "prisma",
23
+ "dev": true,
24
+ "description": "Prisma CLI"
25
+ },
26
+ {
27
+ "name": "@prisma/client",
28
+ "dev": false,
29
+ "description": "Prisma runtime client"
30
+ },
31
+ {
32
+ "name": "ioredis",
33
+ "dev": false,
34
+ "description": "Redis client"
35
+ },
36
+ {
37
+ "name": "amqplib",
38
+ "dev": false,
39
+ "description": "RabbitMQ client"
40
+ },
41
+ {
42
+ "name": "@types/amqplib",
43
+ "dev": true,
44
+ "description": "Type definitions for amqplib"
45
+ },
46
+ {
47
+ "name": "class-validator",
48
+ "dev": false,
49
+ "description": "Validation decorators"
50
+ },
51
+ {
52
+ "name": "class-transformer",
53
+ "dev": false,
54
+ "description": "Class object transformation"
55
+ },
56
+ {
57
+ "name": "axios",
58
+ "dev": false,
59
+ "description": "HTTP client"
15
60
  }
16
61
  ]
17
62
  },
@@ -21,7 +66,7 @@
21
66
  {
22
67
  "name": "axios",
23
68
  "dev": false,
24
- "description": "HTTP 请求库"
69
+ "description": "HTTP请求库"
25
70
  },
26
71
  {
27
72
  "name": "eslint",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guchenyo/create-temp",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Project scaffolding CLI (templates pulled from GitHub)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "dependencies": {
20
20
  "@clack/prompts": "^0.7.0",
21
21
  "gradient-string": "^2.0.2",
22
- "ora": "^8.0.1"
22
+ "ora": "^8.0.1",
23
+ "picocolors": "^1.1.1"
23
24
  }
24
25
  }