tha-cli 0.1.0 → 0.1.2
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/ARCHITECTURE.md +17 -17
- package/README.md +33 -35
- package/package.json +1 -1
package/ARCHITECTURE.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
# THA 架构设计
|
|
2
2
|
|
|
3
3
|
> 本文讲**为什么这么设计**:要解决什么、靠什么结构解决、代价是什么。
|
|
4
|
-
> 怎么安装、`config.ts` 怎么写、命令怎么用,见 [README.md](
|
|
4
|
+
> 怎么安装、`config.ts` 怎么写、命令怎么用,见 [README.md](https://gitee.com/JonnyWen6699/tha-cli/blob/main/README.md)。
|
|
5
5
|
> 每条约束的原话在 `tha/verify/rules.mjs`,本文引用的都是那里的 `basis`。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## 1. 要解决的问题
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
前端业务模块的腐化不是某一处写错,而是**每一处单看都合理、累积起来就不可维护**。典型的五种症状:
|
|
12
12
|
|
|
13
13
|
| 症状 | 后果 |
|
|
14
14
|
|---|---|
|
|
15
15
|
| 页面直接调接口 | 接口一改,页面跟着改 |
|
|
16
|
-
| 状态层里发请求 |
|
|
17
|
-
| 执行器互相 import |
|
|
16
|
+
| 状态层里发请求 | 状态与 IO 耦合在一起,无法独立测试 |
|
|
17
|
+
| 执行器互相 import | 修改一个用例,会影响多个相关模块 |
|
|
18
18
|
| 执行器绕开上下文自己拿依赖 | 依赖关系散落各处,换实现要全局搜 |
|
|
19
19
|
| 一个模块被创建多份 | 「私人大脑」,状态不同步 |
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
共同点是:code review 无法有效识别这类问题,只能依赖结构约束。因此 THA 不从「编码规范」入手,而是**先定义业务模块的固定结构**。
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -52,7 +52,7 @@ step 被第二个用例复用时才下沉
|
|
|
52
52
|
|
|
53
53
|
> 「中枢只做分发:把意图交给执行器;禁止直接依赖执行器内部步骤」(HUB-01)
|
|
54
54
|
|
|
55
|
-
入口一旦开始写逻辑,就会长成上帝对象:先是一个 `if
|
|
55
|
+
入口一旦开始写逻辑,就会长成上帝对象:先是一个 `if`,然后是一次请求,最后整个模块的业务都集中在 `index.ts` 里。所以中枢只做两件事:**单例缓存、挂载动作**。控制类逻辑(比如中断当前任务)也必须放进执行器,不允许写在入口。
|
|
56
56
|
|
|
57
57
|
### 3.2 执行器只认 ctx
|
|
58
58
|
|
|
@@ -70,13 +70,13 @@ interface Context {
|
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
这是**最小集**,不够就往 `context.ts`
|
|
73
|
+
这是**最小集**,不够就往 `context.ts` 里加——加在上下文,不加在执行器里。好处是:换实现只改一处;写用例测试时注入一个模拟的 ctx 就能运行,无需启动 store 和网络。
|
|
74
74
|
|
|
75
75
|
### 3.3 执行器互不引用
|
|
76
76
|
|
|
77
77
|
> 「执行器之间互不引用:共用逻辑下沉成零件,用例顺序留在各自执行器里」(EXE-01)
|
|
78
78
|
|
|
79
|
-
两个用例要共用一段逻辑,正确做法是抽到 `steps/`,不是 A import B
|
|
79
|
+
两个用例要共用一段逻辑,正确做法是抽到 `steps/`,不是 A import B。执行器之间的横向引用会形成网状结构,而非树形结构——网状结构的改动影响范围无法预估。
|
|
80
80
|
|
|
81
81
|
### 3.4 零件按需下沉,不预判
|
|
82
82
|
|
|
@@ -99,7 +99,7 @@ view → hub → executor → step
|
|
|
99
99
|
- **状态层只存状态,不发请求**(STORE-01)。请求属于用例,状态只负责被写入。
|
|
100
100
|
- **传输层只上报不越权**(UTIL-01)。错误总线只管把错误抛上来,不决定怎么展示、不感知业务模块。
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
单向依赖的价值并非为了代码美观,而是**改动影响范围可预测**:改 `steps/` 只影响调用它的执行器,改 `utils/` 不影响任何业务判断。
|
|
103
103
|
|
|
104
104
|
---
|
|
105
105
|
|
|
@@ -109,7 +109,7 @@ view → hub → executor → step
|
|
|
109
109
|
|
|
110
110
|
### 5.1 规则表与判断逻辑分离
|
|
111
111
|
|
|
112
|
-
`tha/verify/rules.mjs`
|
|
112
|
+
`tha/verify/rules.mjs` 只保留规则的自然语言描述——`id` / `level` / `title` / `basis` / `fix`;
|
|
113
113
|
判断逻辑在 `tha/verify/boundary.mjs`。改文案不动逻辑,改逻辑不动文案。
|
|
114
114
|
|
|
115
115
|
### 5.2 每条规则带 `basis`
|
|
@@ -134,12 +134,12 @@ src/domains/doc/index.ts:1 HUB-03
|
|
|
134
134
|
|
|
135
135
|
## 6. 生成与检查分离
|
|
136
136
|
|
|
137
|
-
| |
|
|
137
|
+
| | 负责什么 | 不负责什么 |
|
|
138
138
|
|---|---|---|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
139
|
+
| 生成器(sync) | 标准形态:`config.ts` → 5 个文件 + 注册表 | 不生成业务逻辑 |
|
|
140
|
+
| 检查器(check) | 所有代码,包括完全手写的模块 | 不自动修改代码 |
|
|
141
141
|
|
|
142
|
-
`adopt`
|
|
142
|
+
`adopt` 的语义最能说明这个分工:**停止生成,但持续校验**。文件移交后,生成器不再写入,边界检查仍然执行。这样「生成」和「约束」就是两件可以独立演进的事——生成器可以不处理某个文件,但架构约束不能有盲区。
|
|
143
143
|
|
|
144
144
|
---
|
|
145
145
|
|
|
@@ -147,7 +147,7 @@ src/domains/doc/index.ts:1 HUB-03
|
|
|
147
147
|
|
|
148
148
|
`config.ts` 顶层只有 9 个键,多写一个直接报错,当次一个文件都不生成。
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
逻辑:**配置项每多一个,生成器要处理的组合就翻一倍,边界就越难保障**。宁可让人在代码里写,也不让配置去表达——代码里的复杂度是局部的,配置里的复杂度是全局的。
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
@@ -180,7 +180,7 @@ src/domains/doc/index.ts:1 HUB-03
|
|
|
180
180
|
| STORE-01 | error | 状态层只存状态,不发请求 |
|
|
181
181
|
| UTIL-01 | error | 传输层只上报不越权:不感知业务模块,不做展示 |
|
|
182
182
|
|
|
183
|
-
规则真源:[`tha/verify/rules.mjs`](
|
|
183
|
+
规则真源:[`tha/verify/rules.mjs`](https://gitee.com/JonnyWen6699/tha-cli/blob/main/tha/verify/rules.mjs) 判断逻辑:[`tha/verify/boundary.mjs`](https://gitee.com/JonnyWen6699/tha-cli/blob/main/tha/verify/boundary.mjs)
|
|
184
184
|
|
|
185
185
|
`EXE-03` 提到的 `ctx.ports` / `ctx.errors` 是架构原话里的完整形态;生成器当前的 `context.ts` 只给最小集(`state` / `api` / `ui` / `run` / `can`),需要时按 3.2 往上下文里加。
|
|
186
186
|
|
|
@@ -188,6 +188,6 @@ src/domains/doc/index.ts:1 HUB-03
|
|
|
188
188
|
|
|
189
189
|
## 10. 相关文档
|
|
190
190
|
|
|
191
|
-
- [README.md](
|
|
191
|
+
- [README.md](https://gitee.com/JonnyWen6699/tha-cli/blob/main/README.md)——安装、命令、`config.ts` 全量配置项、契约与退出码
|
|
192
192
|
- `tha/generate/domain/`——每一类生成物的模板,是「形状」的可执行版本
|
|
193
193
|
- `tha/verify/rules.mjs`——规则真源,改架构先改这里
|
package/README.md
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# tha-cli
|
|
2
2
|
|
|
3
|
-
THA
|
|
3
|
+
THA 架构的构建期工具。**生成器负责产出标准代码,检查器负责保障分层边界不被破坏。**
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
- **自带基线**:`init`
|
|
5
|
+
- **生成器**:读 `domains/<域>/config.ts`,生成标准文件(基线 5 个,按 `pages` 追加详情 / 表单)和全局注册表。
|
|
6
|
+
- **检查器**:没有 `config.ts` 的手写模块,不生成,只按层查边界。
|
|
7
|
+
- **自带基线**:`init` 自动补齐生成物所需的组件和工具,空项目也可直接使用。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
设计思想、取舍与代价见 [ARCHITECTURE.md](./ARCHITECTURE.md);本文只讲怎么用。
|
|
9
|
+
设计思想、取舍与代价见 [ARCHITECTURE.md](https://gitee.com/JonnyWen6699/tha-cli/blob/main/ARCHITECTURE.md);本文只讲怎么用。
|
|
12
10
|
|
|
13
11
|
---
|
|
14
12
|
|
|
@@ -30,9 +28,9 @@ utils 基础 纯函数、远端传输
|
|
|
30
28
|
- **执行器** 写用例,依赖全部走 `context.ts` 注入,不直接 import 网络层、状态层、路由。
|
|
31
29
|
- **零件** 是被多个执行器复用的函数,只被一个执行器用时不算零件。
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
这套约束的目的很单一:业务逻辑集中维护,边界由工具保障,无需人工记忆。
|
|
34
32
|
|
|
35
|
-
为什么这么切分、每条约束的原话和代价,见 [ARCHITECTURE.md](
|
|
33
|
+
为什么这么切分、每条约束的原话和代价,见 [ARCHITECTURE.md](https://gitee.com/JonnyWen6699/tha-cli/blob/main/ARCHITECTURE.md)。
|
|
36
34
|
|
|
37
35
|
---
|
|
38
36
|
|
|
@@ -40,10 +38,10 @@ utils 基础 纯函数、远端传输
|
|
|
40
38
|
|
|
41
39
|
| 模式 | 输入 | 产物 |
|
|
42
40
|
|---|---|---|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
41
|
+
| 生成器 | `config.ts` | 标准 CRUD 的 api / store / context / 执行器 / 页面 / 路由 / 菜单 |
|
|
42
|
+
| 检查器 | 任意源码 | 违规清单(10 条边界规则 + 契约比对) |
|
|
45
43
|
|
|
46
|
-
它不生成业务逻辑。`config.ts`
|
|
44
|
+
它不生成业务逻辑。`config.ts` 声明模块的字段与动作范围,工具据此生成标准代码;复杂逻辑由开发者编写,置于自定义执行器中,工具不覆盖。
|
|
47
45
|
|
|
48
46
|
---
|
|
49
47
|
|
|
@@ -57,7 +55,7 @@ npx tha-cli check
|
|
|
57
55
|
前置条件:
|
|
58
56
|
|
|
59
57
|
- **TypeScript 项目**(`.ts` / `.vue`)。纯 JS 项目不支持——模块靠 `config.ts` 识别,生成物也全是 TS。
|
|
60
|
-
- 不需要自己装 `typescript`。CLI 自带一份 5.x,只用于解析源码(读 `config.ts
|
|
58
|
+
- 不需要自己装 `typescript`。CLI 自带一份 5.x,只用于解析源码(读 `config.ts`、遍历语法结构),不参与你项目的构建——你项目里用 5.x 还是 7.x 都不影响它。
|
|
61
59
|
|
|
62
60
|
---
|
|
63
61
|
|
|
@@ -67,7 +65,7 @@ npx tha-cli check
|
|
|
67
65
|
tha init doc # 建 config 模板,不生成代码
|
|
68
66
|
# 编辑 src/domains/doc/config.ts
|
|
69
67
|
tha sync # 生成 5 个文件;中枢缺失时补一次骨架
|
|
70
|
-
tha check # 检查:配置 + 契约 +
|
|
68
|
+
tha check # 检查:配置 + 契约 + 边界检查 + 文件一致性
|
|
71
69
|
```
|
|
72
70
|
|
|
73
71
|
CI:
|
|
@@ -86,7 +84,7 @@ tha check --json # 机器可消费
|
|
|
86
84
|
|---|---|
|
|
87
85
|
| `tha init <域>` | 建 config 模板 + 补齐基线(`--bare` 只写 config) |
|
|
88
86
|
| `tha sync [<域>]` | 生成 / 更新标准 CRUD 与注册表;补一次中枢骨架 |
|
|
89
|
-
| `tha check` | 只检查不写:配置 + 契约(响应与请求体)+
|
|
87
|
+
| `tha check` | 只检查不写:配置 + 契约(响应与请求体)+ 边界检查 + 文件一致性 |
|
|
90
88
|
| `tha diff` | 显示将要发生的文件变化 |
|
|
91
89
|
| `tha remove <域>` | 删模块:生成物清掉,手写文件保留;保留的文件若还引用已删生成物会明确报出来 |
|
|
92
90
|
| `tha adopt <文件...>` | 这个文件以后你自己维护:不再自动生成,但分层规则照样查 |
|
|
@@ -111,7 +109,7 @@ tha check --json # 机器可消费
|
|
|
111
109
|
├── config.ts 手写:唯一真源
|
|
112
110
|
├── index.ts 手写:中枢(单例 + 只挂载动作;缺失时补骨架)
|
|
113
111
|
├── permissions.ts 按需生成:权限码 + canXxx 骨架(声明了权限码才有)
|
|
114
|
-
├── context.ts
|
|
112
|
+
├── context.ts 生成:依赖注入 + 权限判定
|
|
115
113
|
├── executors/
|
|
116
114
|
│ ├── crud.generated.ts 生成:refresh / load / create / update / remove(按 crud 裁剪)
|
|
117
115
|
│ └── <用例>.ts 手写:自定义用例(可选)
|
|
@@ -218,7 +216,7 @@ export const docConfig = {
|
|
|
218
216
|
| `pages` | 否 | `list` / `detail` / `form` 的数组 | `['list','detail']` | 要生成哪几个页面 |
|
|
219
217
|
| `list.pageSize` | 否 | 1–100 的整数 | `20` | 每页条数 |
|
|
220
218
|
| `list.orderBy` | 否 | `fields` 里声明过的字段名 | 无 | 列表默认按这个字段排序 |
|
|
221
|
-
| `list.order` | 否 | `asc` / `desc` | `desc` |
|
|
219
|
+
| `list.order` | 否 | `asc` / `desc` | `desc` | 与 `orderBy` 配合使用 |
|
|
222
220
|
| `crud` | 否 | `create` / `update` / `remove` 布尔 | 全 `true` | 关掉的动作整条链都不生成 |
|
|
223
221
|
| `permissions` | 否 | `view` / `create` / `update` / `remove` 权限码 | `{}` | 非空才生成 `permissions.ts` |
|
|
224
222
|
|
|
@@ -254,7 +252,7 @@ export const docConfig = {
|
|
|
254
252
|
| `name: 'views'` 这种撞目录名的 | 不能使用保留名(api / stores / domains / views / router / layouts / components / utils) |
|
|
255
253
|
| `list.pageSize: 0` 或 `500` | 必须是 1–100 的整数 |
|
|
256
254
|
| `list.orderBy` 指向 `fields` 里没有的字段,或 enum / boolean | 排序字段必须已声明,且类型可排序 |
|
|
257
|
-
| 只写 `list.order` 不写 `orderBy`,或 `order` 不是 asc / desc | order
|
|
255
|
+
| 只写 `list.order` 不写 `orderBy`,或 `order` 不是 asc / desc | order 需与 orderBy 配合使用;仅支持 asc、desc |
|
|
258
256
|
|
|
259
257
|
### 常见组合
|
|
260
258
|
|
|
@@ -268,7 +266,7 @@ api: { base: '/docs', contract: 'docs/openapi.json' } // 接后端契约比对
|
|
|
268
266
|
|
|
269
267
|
---
|
|
270
268
|
|
|
271
|
-
##
|
|
269
|
+
## 目录映射:自定义源码目录
|
|
272
270
|
|
|
273
271
|
项目根建 `tha.config.json`:
|
|
274
272
|
|
|
@@ -284,19 +282,19 @@ api: { base: '/docs', contract: 'docs/openapi.json' } // 接后端契约比对
|
|
|
284
282
|
|
|
285
283
|
---
|
|
286
284
|
|
|
287
|
-
##
|
|
285
|
+
## 所有权:四种文件,四种处理策略
|
|
288
286
|
|
|
289
287
|
| 所有权 | 怎么识别 | sync | check |
|
|
290
288
|
|---|---|---|---|
|
|
291
|
-
| generated | 有 `@tha-generated`
|
|
289
|
+
| generated | 有 `@tha-generated` 生成头,校验值匹配 | 可以更新 | 照常查边界 |
|
|
292
290
|
| handwritten | 没有生成头,且不在交接清单 | 永不覆盖 | 提示,照常查边界 |
|
|
293
|
-
| adopted | 在 `.tha/manifest.json` 的 `manual` 里 |
|
|
294
|
-
| edited |
|
|
291
|
+
| adopted | 在 `.tha/manifest.json` 的 `manual` 里 | 永不覆盖、不校验文件一致性 | 不算失败,照常查边界 |
|
|
292
|
+
| edited | 有生成头但校验值不匹配 | 拒绝,除非 `--force` | 报告文件与生成器不一致 |
|
|
295
293
|
|
|
296
294
|
`adopt` 只接受构建期会生成的文件(用 `tha diff` 查看)。清单写在
|
|
297
295
|
`.tha/manifest.json`,建议提交到版本库。
|
|
298
296
|
|
|
299
|
-
**adopt
|
|
297
|
+
**adopt 的语义**:工具不再生成该文件,但持续校验其边界合规性。
|
|
300
298
|
|
|
301
299
|
---
|
|
302
300
|
|
|
@@ -326,15 +324,15 @@ api: { base: '/docs', contract: 'docs/openapi.json' } // 接后端契约比对
|
|
|
326
324
|
|
|
327
325
|
---
|
|
328
326
|
|
|
329
|
-
##
|
|
327
|
+
## 契约与权限:声明即生效
|
|
330
328
|
|
|
331
|
-
|
|
329
|
+
这两项最容易出现「配置已声明、产物已生成,但对应功能未生效」的情况,因此检查口径需明确。
|
|
332
330
|
|
|
333
331
|
**字段契约**对两个方向都比(`api.contract` 指向的 OpenAPI,`.json` / `.yaml` / `.yml` 都认):
|
|
334
332
|
|
|
335
333
|
| 方向 | 取自 | 级别 |
|
|
336
334
|
|---|---|---|
|
|
337
|
-
| 响应:config 字段后端没有 | GET 200 / POST 201
|
|
335
|
+
| 响应:config 字段后端没有 | GET 200 / POST 201 的返回数据结构(`{code,data}` 包装自动解一层) | error |
|
|
338
336
|
| 响应:类型不一致 | 同上 | error |
|
|
339
337
|
| 响应:`format` / 必填 / 后端多字段 | 同上 | warn |
|
|
340
338
|
| 请求体:表单会提交的字段(`form !== false`)后端不收 | POST 请求体 → `create`;PUT / PATCH → `update` | error |
|
|
@@ -345,28 +343,28 @@ api: { base: '/docs', contract: 'docs/openapi.json' } // 接后端契约比对
|
|
|
345
343
|
|
|
346
344
|
**权限守卫**:`permissions` 非空的模块,执行器里必须至少有**一处** `can(...)` 判断(注释不算)。一处都没有 → error,提示「声明了 permissions,但执行器里没有任何 can(...) 判断」。
|
|
347
345
|
|
|
348
|
-
|
|
346
|
+
这条专门覆盖 `adopt` 场景:生成模板里本来每个动作前都有 `ctx.can('view')`,一旦这个文件交给手写,生成器不再管理它,只有这条检查还能发现「权限判断被移除」。
|
|
349
347
|
|
|
350
348
|
---
|
|
351
349
|
|
|
352
350
|
## 退出码
|
|
353
351
|
|
|
354
|
-
|
|
355
|
-
`--strict`
|
|
352
|
+
文件与生成器不一致 / 手写冲突 / 契约失败(响应或请求体)/ 权限守卫缺失 / 检查器 error 任一存在 → `1`;
|
|
353
|
+
`--strict` 模式下契约与检查器的 warn 也视为失败。
|
|
356
354
|
|
|
357
355
|
---
|
|
358
356
|
|
|
359
357
|
## 注意事项
|
|
360
358
|
|
|
361
|
-
|
|
359
|
+
实际使用中需注意以下事项:
|
|
362
360
|
|
|
363
361
|
1. **只覆盖标准 CRUD**。列表 / 详情 / 表单、增删改查、搜索分页是标准形态;工作流、嵌套表单、自定义布局这类复杂模块仍然人写,工具只保证不覆盖、不误报。
|
|
364
|
-
2. **自带组件是复制,不是引用**。`init` / `sync` 会把 `ThaCrudPage.vue`
|
|
365
|
-
3. **`list.orderBy` 只排前端**。它是 `ThaCrudPage`
|
|
362
|
+
2. **自带组件是复制,不是引用**。`init` / `sync` 会把 `ThaCrudPage.vue` 等基线组件复制进项目。之后 CLI 升级,项目里那份**不会自动更新**:新能力(比如 `list.orderBy`)在老项目里不生效。要么删除该文件后重新执行 `sync`,要么手动将更新同步过去。
|
|
363
|
+
3. **`list.orderBy` 只排前端**。它是 `ThaCrudPage` 的本地排序,不会给后端接口加查询参数。要后端排序,那是接口契约的事。
|
|
366
364
|
4. **`api.contract` 的 YAML 解析依赖 `yaml` 包**,随 CLI 一起装,不需要单独安装。
|
|
367
|
-
5. **`config.ts` 顶层只有 9 个键**。多写一个直接报错,该次 `sync`
|
|
365
|
+
5. **`config.ts` 顶层只有 9 个键**。多写一个直接报错,该次 `sync` 一个文件都不生成。收窄配置面是有意的,否则无法保障边界完整性。
|
|
368
366
|
6. **非交互不写盘**。脚本里不加 `--yes`,`sync` / `remove` 只打印计划。
|
|
369
|
-
7. **adopt
|
|
367
|
+
7. **adopt 后工具不再生成该文件,但持续校验分层规则**。文件移交后,工具停止生成,边界检查仍然生效。
|
|
370
368
|
|
|
371
369
|
---
|
|
372
370
|
|