Skip to content

Commit 38b6b34

Browse files
committed
feat: 重构 create-tona 项目创建流程
实现模板方法模式重构项目创建流程,将核心逻辑拆分为多个职责单一模块 添加策略模式处理不同包管理器的命令生成 新增测试用例覆盖核心功能 完善类型定义和文档
1 parent 14f5614 commit 38b6b34

29 files changed

+2153
-5292
lines changed
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
# create-tona 重构计划
2+
3+
## 设计模式选择
4+
5+
### 推荐模式:模板方法模式 (Template Method Pattern)
6+
7+
**选择理由:**
8+
9+
1. **流程固定性**:项目创建流程有明确的步骤骨架:
10+
11+
* 解析参数 → 获取项目名 → 处理目录 → 选择模板 → 初始化Git → 安装依赖 → 启动服务
12+
13+
2. **步骤可变性**:每个步骤的具体实现可能变化(如不同包管理器的命令),但整体流程不变
14+
15+
3. **代码复用**:将公共流程抽取到基类,避免重复代码
16+
17+
4. **易于扩展**:未来添加新模板或新步骤时,只需扩展子类
18+
19+
### 辅助模式:策略模式 (Strategy Pattern)
20+
21+
用于处理不同包管理器的命令生成逻辑。
22+
23+
***
24+
25+
## 重构目标
26+
27+
1. 将 300+ 行的 `init()` 函数拆分为职责单一的模块
28+
2. 提高代码可测试性和可维护性
29+
3. 消除重复代码
30+
4. 保持功能完全不变
31+
32+
***
33+
34+
## 目录结构设计
35+
36+
```
37+
packages/create-tona/src/
38+
├── index.ts # 入口文件
39+
├── types.ts # 类型定义
40+
├── consts.ts # 常量定义(已存在,需扩展)
41+
├── core/
42+
│ ├── creator.ts # 项目创建器基类(模板方法)
43+
│ └── context.ts # 项目上下文
44+
├── prompts/
45+
│ ├── index.ts # 交互流程编排
46+
│ ├── project-name.ts # 项目名称交互
47+
│ ├── overwrite.ts # 目录覆盖交互
48+
│ ├── package-name.ts # 包名交互
49+
│ ├── language.ts # 语言选择交互
50+
│ ├── template.ts # 模板选择交互
51+
│ ├── git.ts # Git 初始化交互
52+
│ ├── install.ts # 安装选项交互
53+
│ └── start.ts # 启动选项交互
54+
├── actions/
55+
│ ├── scaffold.ts # 脚手架生成
56+
│ ├── git.ts # Git 操作
57+
│ └── package-manager.ts # 包管理器操作
58+
├── strategies/
59+
│ └── package-manager.ts # 包管理器策略
60+
└── utils/
61+
├── fs.ts # 文件系统工具
62+
├── package.ts # 包名处理
63+
└── command.ts # 命令执行
64+
```
65+
66+
***
67+
68+
## 实现步骤
69+
70+
### 阶段一:基础设施(类型与常量)
71+
72+
#### 1.1 创建 `types.ts`
73+
74+
* 定义 `CliArgs` 接口
75+
76+
* 定义 `ProjectContext` 接口
77+
78+
* 定义 `PkgInfo` 接口
79+
80+
* 定义 `PackageManager` 类型
81+
82+
* 定义 `Template` 类型
83+
84+
* 定义 `Registry` 类型
85+
86+
#### 1.2 扩展 `consts.ts`
87+
88+
* 添加 `TEMPLATES` 常量
89+
90+
* 添加 `REGISTRIES` 常量
91+
92+
* 添加 `PACKAGE_MANAGERS` 常量
93+
94+
***
95+
96+
### 阶段二:工具函数模块
97+
98+
#### 2.1 创建 `utils/fs.ts`
99+
100+
* 迁移 `formatTargetDir()`
101+
102+
* 迁移 `copy()`
103+
104+
* 迁移 `copyDir()`
105+
106+
* 迁移 `isEmpty()`
107+
108+
* 迁移 `emptyDir()`
109+
110+
#### 2.2 创建 `utils/package.ts`
111+
112+
* 迁移 `isValidPackageName()`
113+
114+
* 迁移 `toValidPackageName()`
115+
116+
* 迁移 `pkgFromUserAgent()`
117+
118+
#### 2.3 创建 `utils/command.ts`
119+
120+
* 迁移 `run()` 函数
121+
122+
***
123+
124+
### 阶段三:策略模式实现
125+
126+
#### 3.1 创建 `strategies/package-manager.ts`
127+
128+
* 定义 `PackageManagerStrategy` 接口
129+
130+
* 实现 `NpmStrategy`
131+
132+
* 实现 `PnpmStrategy`
133+
134+
* 实现 `YarnStrategy`
135+
136+
* 实现 `BunStrategy`
137+
138+
* 实现 `DenoStrategy`
139+
140+
* 创建策略工厂 `getPackageManagerStrategy()`
141+
142+
***
143+
144+
### 阶段四:上下文与核心类
145+
146+
#### 4.1 创建 `core/context.ts`
147+
148+
* 定义 `ProjectContext`
149+
150+
* 实现上下文状态管理
151+
152+
#### 4.2 创建 `core/creator.ts`
153+
154+
* 定义 `ProjectCreator` 抽象类
155+
156+
* 实现模板方法 `create()`
157+
158+
* 定义抽象方法:
159+
160+
* `collectProjectName()`
161+
162+
* `handleDirectory()`
163+
164+
* `collectPackageName()`
165+
166+
* `collectLanguage()`
167+
168+
* `collectTemplate()`
169+
170+
* `collectGitOption()`
171+
172+
* `collectInstallOption()`
173+
174+
* `collectStartOption()`
175+
176+
* 实现具体方法:
177+
178+
* `scaffoldProject()`
179+
180+
* `initializeGit()`
181+
182+
* `installDependencies()`
183+
184+
* `startDevServer()`
185+
186+
* `showCompletionMessage()`
187+
188+
***
189+
190+
### 阶段五:交互模块
191+
192+
#### 5.1 创建 `prompts/index.ts`
193+
194+
* 实现交互流程编排
195+
196+
* 实现 `collectUserInput()` 函数
197+
198+
#### 5.2 创建各交互子模块
199+
200+
* `prompts/project-name.ts` - 项目名称交互
201+
202+
* `prompts/overwrite.ts` - 目录覆盖交互
203+
204+
* `prompts/package-name.ts` - 包名交互
205+
206+
* `prompts/language.ts` - 语言选择交互
207+
208+
* `prompts/template.ts` - 模板选择交互
209+
210+
* `prompts/git.ts` - Git 初始化交互
211+
212+
* `prompts/install.ts` - 安装选项交互
213+
214+
* `prompts/start.ts` - 启动选项交互
215+
216+
***
217+
218+
### 阶段六:操作模块
219+
220+
#### 6.1 创建 `actions/scaffold.ts`
221+
222+
* 实现脚手架生成逻辑
223+
224+
* 封装文件复制和模板处理
225+
226+
#### 6.2 创建 `actions/git.ts`
227+
228+
* 实现 Git 初始化逻辑
229+
230+
* 实现文件暂存逻辑
231+
232+
#### 6.3 创建 `actions/package-manager.ts`
233+
234+
* 实现依赖安装逻辑
235+
236+
* 实现开发服务器启动逻辑
237+
238+
***
239+
240+
### 阶段七:重构入口文件
241+
242+
#### 7.1 重构 `index.ts`
243+
244+
* 导入所有模块
245+
246+
* 实现简化的 `init()` 函数
247+
248+
* 使用模板方法模式调用创建流程
249+
250+
***
251+
252+
## 关键代码示例
253+
254+
### 模板方法模式核心
255+
256+
```typescript
257+
// core/creator.ts
258+
export abstract class ProjectCreator {
259+
protected context: ProjectContext
260+
261+
constructor(argv: CliArgs) {
262+
this.context = new ProjectContext(argv)
263+
}
264+
265+
async create(): Promise<void> {
266+
await this.collectProjectName()
267+
await this.handleDirectory()
268+
await this.collectPackageName()
269+
await this.collectLanguage()
270+
await this.collectTemplate()
271+
await this.collectGitOption()
272+
await this.collectInstallOption()
273+
await this.collectStartOption()
274+
275+
await this.scaffoldProject()
276+
277+
if (this.context.initGit) {
278+
await this.initializeGit()
279+
}
280+
281+
if (this.context.shouldInstall) {
282+
await this.installDependencies()
283+
284+
if (this.context.shouldStart) {
285+
await this.startDevServer()
286+
}
287+
}
288+
289+
this.showCompletionMessage()
290+
}
291+
292+
protected abstract collectProjectName(): Promise<void>
293+
protected abstract handleDirectory(): Promise<void>
294+
// ... 其他抽象方法
295+
296+
protected async scaffoldProject(): Promise<void> {
297+
// 具体实现
298+
}
299+
}
300+
```
301+
302+
### 策略模式核心
303+
304+
```typescript
305+
// strategies/package-manager.ts
306+
export interface PackageManagerStrategy {
307+
getInstallCommand(registry?: string): string[]
308+
getRunCommand(script: string): string[]
309+
}
310+
311+
export class NpmStrategy implements PackageManagerStrategy {
312+
getInstallCommand(registry?: string): string[] {
313+
const cmd = ['npm', 'install']
314+
if (registry) cmd.push('--registry', registry)
315+
return cmd
316+
}
317+
318+
getRunCommand(script: string): string[] {
319+
return ['npm', 'run', script]
320+
}
321+
}
322+
323+
export class PnpmStrategy implements PackageManagerStrategy {
324+
getInstallCommand(registry?: string): string[] {
325+
const cmd = ['pnpm', 'install']
326+
if (registry) cmd.push('--registry', registry)
327+
return cmd
328+
}
329+
330+
getRunCommand(script: string): string[] {
331+
return ['pnpm', script]
332+
}
333+
}
334+
335+
// ... 其他策略实现
336+
```
337+
338+
***
339+
340+
## 验证清单
341+
342+
* [ ] 所有原有功能保持不变
343+
344+
* [ ] 命令行参数解析正确
345+
346+
* [ ] 交互式流程正常
347+
348+
* [ ] 非交互式流程正常
349+
350+
* [ ] 文件复制正确
351+
352+
* [ ] Git 初始化正常
353+
354+
* [ ] 依赖安装正常
355+
356+
* [ ] 开发服务器启动正常
357+
358+
* [ ] 错误处理完善
359+
360+
* [ ] 用户取消操作处理正确
361+
362+
***
363+
364+
## 风险与缓解
365+
366+
| 风险 | 缓解措施 |
367+
| ------------ | --------------------- |
368+
| 重构过程中引入 bug | 每个阶段完成后进行功能测试 |
369+
| 文件拆分过多导致导入复杂 | 使用桶文件 (index.ts) 统一导出 |
370+
| 模板方法模式增加理解成本 | 添加详细注释和文档 |
371+
| 保持向后兼容 | 确保所有公开 API 不变 |
372+

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"singlefile",
5656
"sonner",
5757
"taglist",
58+
"taobao",
5859
"Tona",
5960
"trae",
6061
"tsdown",

0 commit comments

Comments
 (0)