Skip to content

Commit 4789031

Browse files
committed
✨refactor(core): 添加拉取模板命令
1 parent 94f3faf commit 4789031

File tree

5 files changed

+263
-2
lines changed

5 files changed

+263
-2
lines changed

src/command/template.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import type { CAC } from "cac";
2+
3+
import path from "node:path";
4+
import { randomUUID } from "node:crypto";
5+
6+
import enquirer from "enquirer";
7+
8+
import {
9+
getVariantByFramework,
10+
isEmptyDir,
11+
isValidFramework,
12+
isValidPackageName,
13+
isValidVariant,
14+
loggerInfo,
15+
toValidPackageName,
16+
} from "@/shared/index";
17+
import { ACTIVATION, FRAMEWORKS } from "@/shared/config";
18+
import { TemplateOptions } from "@/shared/types";
19+
20+
interface PromptResult {
21+
projectName: string;
22+
framework: string;
23+
variant: string;
24+
overwrite: boolean;
25+
packageName: string;
26+
}
27+
28+
export const template = async (options: TemplateOptions) => {
29+
if (ACTIVATION) {
30+
loggerInfo("template 参数信息: \n");
31+
console.table(options);
32+
}
33+
34+
const { projectName } = await enquirer.prompt<PromptResult>({
35+
name: "projectName",
36+
type: "text",
37+
message: "请输入项目名称",
38+
initial: options.projectName,
39+
});
40+
41+
const { overwrite } = await enquirer.prompt<PromptResult>({
42+
name: "overwrite",
43+
type: "confirm",
44+
skip: () => isEmptyDir(projectName),
45+
message: `${path.join(
46+
process.cwd(),
47+
projectName,
48+
)} \n 文件夹非空,继续操作会丢失现有的文件?`,
49+
result: (value) => {
50+
if (!isEmptyDir(projectName) && !value) {
51+
process.exit(1);
52+
}
53+
return value;
54+
},
55+
});
56+
57+
const { packageName } = await enquirer.prompt<PromptResult>({
58+
name: "packageName",
59+
type: "text",
60+
skip: () => isValidPackageName(projectName),
61+
message: "请输入 package name",
62+
initial: projectName,
63+
validate: (value) => {
64+
return isValidPackageName(value) || "输入的 package name 不符合规范";
65+
},
66+
result: (value) => {
67+
return toValidPackageName(value);
68+
},
69+
});
70+
71+
const { framework } = await enquirer.prompt<PromptResult>({
72+
name: "framework",
73+
type: "select",
74+
skip: () => isValidFramework(options.framework),
75+
message: "请选择下列的有效模板",
76+
choices: FRAMEWORKS.map((framework) => {
77+
const frameworkColor = framework.color;
78+
return {
79+
message: frameworkColor(framework.display || framework.name),
80+
name: framework.name,
81+
};
82+
}),
83+
});
84+
85+
const { variant } = await enquirer.prompt<PromptResult>({
86+
name: "variant",
87+
type: "select",
88+
skip: () => !isValidVariant(framework),
89+
message: "请选择下列的有效变体",
90+
choices: getVariantByFramework(framework).map((variant) => {
91+
const variantColor = variant.color;
92+
return {
93+
message: variantColor(variant.display || variant.name),
94+
name: variant.name,
95+
};
96+
}),
97+
});
98+
99+
console.log(projectName, framework, variant, overwrite, packageName);
100+
101+
const root = path.join(process.cwd(), projectName);
102+
// if (overwrite) emptyDir(root);
103+
// const template: string = variant || framework;
104+
// const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
105+
// const pkgManager = pkgInfo ? pkgInfo.name : "npm";
106+
107+
console.log(`\nScaffolding project in ${root}...`);
108+
// const templateDir = path.resolve(
109+
// fileURLToPath(import.meta.url),
110+
// "../../../template",
111+
// `template-${template}`
112+
// );
113+
// console.log(templateDir);
114+
// fs.copySync(templateDir, projectName, {
115+
// filter: (src: string) => {
116+
// return !fileIgnore.find(
117+
// (f) => f === `${path.parse(src).name}${path.parse(src).ext}`
118+
// );
119+
// },
120+
// });
121+
};
122+
123+
export default function templateInstaller(cli: CAC) {
124+
return {
125+
name: "templateInstaller",
126+
setup: () => {
127+
cli
128+
.command("template", "快速创建CodeGenius基础项目")
129+
.option("-n, --project-name <project-name>", "项目名称", {
130+
default: `project-${randomUUID().slice(0, 8)}`,
131+
})
132+
.option("-f, --framework <framework>", "项目框架")
133+
.action(async (options) => {
134+
const { projectName, framework } = options;
135+
await template({
136+
projectName,
137+
framework,
138+
});
139+
});
140+
},
141+
};
142+
}

src/setup.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import npmRunInstaller from "./command/npm-run";
1010
import npmRegistryInstaller from "./command/npm-registry";
1111
import eslintFixInstaller from "./command/eslint-fix";
1212
import prettierFormatInstaller from "./command/prettier-format";
13+
import templateInstaller from "./command/template";
1314

1415
export function cmdInstaller(cli: CAC) {
1516
gitCommitInstaller(cli).setup();
@@ -19,8 +20,9 @@ export function cmdInstaller(cli: CAC) {
1920
npmDepCheckInstaller(cli).setup();
2021
npmRunInstaller(cli).setup();
2122
npmRegistryInstaller(cli).setup();
22-
eslintFixInstaller(cli).setup;
23+
eslintFixInstaller(cli).setup();
2324
prettierFormatInstaller(cli).setup();
2425
clearInstaller(cli).setup();
2526
createProjectInstaller(cli).setup();
27+
templateInstaller(cli).setup();
2628
}

src/shared/config.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import {
22
CommitScope,
33
CommitType,
4+
Framework,
45
KeyValue,
56
ProjectSource,
67
} from "@/shared/types";
78

9+
import { blue, green, yellow } from "kolorist";
10+
811
export const clearGlob = ["./dist/"];
912

1013
export const formatGlob = ["./src"];
@@ -142,3 +145,31 @@ export const projectSources: Array<ProjectSource> = [
142145
description: "创建由 Vite 驱动的 Vue2 项目(支持 IE11)",
143146
},
144147
];
148+
149+
export const FRAMEWORKS: Framework[] = [
150+
{
151+
name: "vue",
152+
display: "Vue",
153+
color: green,
154+
variants: [
155+
{
156+
framework: "vue",
157+
name: "vue",
158+
display: "JavaScript",
159+
color: yellow,
160+
},
161+
{
162+
framework: "vue",
163+
name: "vue-ts",
164+
display: "TypeScript",
165+
color: blue,
166+
},
167+
],
168+
},
169+
];
170+
171+
export const TEMPLATES = FRAMEWORKS.map(
172+
(f) => (f.variants && f.variants.map((v) => v.name)) || [f.name],
173+
).reduce((a, b) => a.concat(b), []);
174+
175+
export const fileIgnore = ["package.json", "_gitignore"];

src/shared/index.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { Options } from "execa";
77
import execa from "execa";
88
import { green, lightYellow, lightGreen, lightRed } from "kolorist";
99

10-
import { ACTIVATION } from "@/shared/config";
10+
import { ACTIVATION, FRAMEWORKS, TEMPLATES } from "@/shared/config";
1111

1212
export const execCommand = async (
1313
cmd: string,
@@ -159,3 +159,67 @@ export const getEveryFilesBySuffixes = async (
159159
}
160160
return getFiilesBySuffixes(files, suffix);
161161
};
162+
163+
export function formatTargetDir(targetDir: string | undefined) {
164+
return targetDir?.trim().replace(/\/+$/g, "");
165+
}
166+
167+
export function isValidPackageName(projectName: string) {
168+
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
169+
projectName,
170+
);
171+
}
172+
173+
export function toValidPackageName(projectName: string) {
174+
return projectName
175+
.trim()
176+
.toLowerCase()
177+
.replace(/\s+/g, "-")
178+
.replace(/^[._]/, "")
179+
.replace(/[^a-z0-9-~]+/g, "-");
180+
}
181+
182+
export function isEmpty(path: string) {
183+
const files = fs.readdirSync(path);
184+
return files.length === 0 || (files.length === 1 && files[0] === ".git");
185+
}
186+
187+
export function pkgFromUserAgent(userAgent: string | undefined) {
188+
if (!userAgent) return undefined;
189+
const pkgSpec = userAgent.split(" ")[0];
190+
const pkgSpecArr = pkgSpec.split("/");
191+
return {
192+
name: pkgSpecArr[0],
193+
version: pkgSpecArr[1],
194+
};
195+
}
196+
197+
export function emptyDir(dir: string) {
198+
if (!fs.existsSync(dir)) {
199+
return;
200+
}
201+
for (const file of fs.readdirSync(dir)) {
202+
if (file === ".git") {
203+
continue;
204+
}
205+
fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
206+
}
207+
}
208+
209+
export function isEmptyDir(projectName: string) {
210+
const targetDir = path.join(process.cwd(), projectName);
211+
return !fs.existsSync(targetDir) || isEmpty(targetDir);
212+
}
213+
214+
export function isValidFramework(framework: string) {
215+
return typeof framework === "string" && TEMPLATES.includes(framework);
216+
}
217+
218+
export function getVariantByFramework(framework: string) {
219+
return FRAMEWORKS.find((v) => v.name === framework)?.variants || [];
220+
}
221+
222+
export function isValidVariant(framework: string) {
223+
const variants = getVariantByFramework(framework);
224+
return variants.length > 0;
225+
}

src/shared/types.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,25 @@ export interface ProjectSource {
2424
name: string;
2525
description: string;
2626
}
27+
28+
export interface TemplateOptions {
29+
projectName: string;
30+
framework: string;
31+
}
32+
33+
export type ColorFunc = (str: string | number) => string;
34+
35+
export interface FrameworkVariant {
36+
framework: string;
37+
name: string;
38+
display: string;
39+
color: ColorFunc;
40+
customCommand?: string;
41+
}
42+
43+
export interface Framework {
44+
name: string;
45+
display: string;
46+
color: ColorFunc;
47+
variants: FrameworkVariant[];
48+
}

0 commit comments

Comments
 (0)