tha-cli 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/ARCHITECTURE.md +14 -14
  2. package/README.md +31 -33
  3. package/package.json +1 -1
package/ARCHITECTURE.md CHANGED
@@ -8,17 +8,17 @@
8
8
 
9
9
  ## 1. 要解决的问题
10
10
 
11
- 前端业务模块的腐化不是某一处写错,而是**每一处单看都合理、堆起来就不可维护**。典型的五种症状:
11
+ 前端业务模块的腐化不是某一处写错,而是**每一处单看都合理、累积起来就不可维护**。典型的五种症状:
12
12
 
13
13
  | 症状 | 后果 |
14
14
  |---|---|
15
15
  | 页面直接调接口 | 接口一改,页面跟着改 |
16
- | 状态层里发请求 | 状态和 IO 搅在一起,没法单独测 |
17
- | 执行器互相 import | 改一个用例,炸三个 |
16
+ | 状态层里发请求 | 状态与 IO 耦合在一起,无法独立测试 |
17
+ | 执行器互相 import | 修改一个用例,会影响多个相关模块 |
18
18
  | 执行器绕开上下文自己拿依赖 | 依赖关系散落各处,换实现要全局搜 |
19
19
  | 一个模块被创建多份 | 「私人大脑」,状态不同步 |
20
20
 
21
- 共同点是:靠 code review 抓不住,只能靠结构。所以 THA 不从「写代码的规范」入手,而是**先把一个业务模块的形状定死**。
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`,然后是一次请求,最后整个模块的业务都堆在 `index.ts` 里。所以中枢只做两件事:**单例缓存、挂载动作**。控制类逻辑(比如中断当前任务)也必须放进执行器,不许写在入口。
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` 里加——加在上下文,不加在执行器里。好处是:换实现只改一处;写用例测试时塞一个假 ctx 就能跑,不用起 store 和网络。
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
- 单向依赖的价值不是「好看」,是**改动影响范围可预测**:改 `steps/` 只影响调用它的执行器,改 `utils/` 不影响任何业务判断。
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` 只放「说给人听的话」——`id` / `level` / `title` / `basis` / `fix`;
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
- | 产线(sync) | 标准形态:`config.ts` → 5 个文件 + 注册表 | 不生成业务逻辑 |
140
- | 护栏(check) | 所有代码,包括完全手写的模块 | 不替你改代码 |
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
 
package/README.md CHANGED
@@ -1,12 +1,10 @@
1
1
  # tha-cli
2
2
 
3
- THA 架构的构建期工具。两副面孔:**产线负责生成,护栏负责守边界。**
3
+ THA 架构的构建期工具。**生成器负责产出标准代码,检查器负责保障分层边界不被破坏。**
4
4
 
5
- - **产线**:读 `domains/<域>/config.ts`,生成标准文件(基线 5 个,按 `pages` 追加详情 / 表单)和全局注册表。
6
- - **护栏**:没有 `config.ts` 的手写模块,不生成,只按层查边界。
7
- - **自带基线**:`init` 顺手补齐生成物要 import 的组件和工具,空项目也能直接跑。
8
-
9
- 产物不依赖 `tha`:上线后可以把它从 `devDependencies` 删掉。
5
+ - **生成器**:读 `domains/<域>/config.ts`,生成标准文件(基线 5 个,按 `pages` 追加详情 / 表单)和全局注册表。
6
+ - **检查器**:没有 `config.ts` 的手写模块,不生成,只按层查边界。
7
+ - **自带基线**:`init` 自动补齐生成物所需的组件和工具,空项目也可直接使用。
10
8
 
11
9
  设计思想、取舍与代价见 [ARCHITECTURE.md](./ARCHITECTURE.md);本文只讲怎么用。
12
10
 
@@ -30,7 +28,7 @@ utils 基础 纯函数、远端传输
30
28
  - **执行器** 写用例,依赖全部走 `context.ts` 注入,不直接 import 网络层、状态层、路由。
31
29
  - **零件** 是被多个执行器复用的函数,只被一个执行器用时不算零件。
32
30
 
33
- 这套约束的目的很单一:业务写在一处,边界由工具守,不靠人记。
31
+ 这套约束的目的很单一:业务逻辑集中维护,边界由工具保障,无需人工记忆。
34
32
 
35
33
  为什么这么切分、每条约束的原话和代价,见 [ARCHITECTURE.md](./ARCHITECTURE.md)。
36
34
 
@@ -40,10 +38,10 @@ utils 基础 纯函数、远端传输
40
38
 
41
39
  | 模式 | 输入 | 产物 |
42
40
  |---|---|---|
43
- | 产线 | `config.ts` | 标准 CRUD 的 api / store / context / 执行器 / 页面 / 路由 / 菜单 |
44
- | 护栏 | 任意源码 | 违规清单(10 条边界规则 + 契约比对) |
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`、遍历 AST),不参与你项目的构建——你项目里用 5.x 还是 7.x 都不影响它。
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 生成:DI + can
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` | 和 `orderBy` 一起写 |
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 要和 orderBy 一起写;只支持 asc、desc |
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
- ## 目录映射:不想放 src/ 就改这里
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` 生成头,hash 对得上 | 可以更新 | 照常查边界 |
289
+ | generated | 有 `@tha-generated` 生成头,校验值匹配 | 可以更新 | 照常查边界 |
292
290
  | handwritten | 没有生成头,且不在交接清单 | 永不覆盖 | 提示,照常查边界 |
293
- | adopted | 在 `.tha/manifest.json` 的 `manual` 里 | 永不覆盖、不比对漂移 | 不算失败,照常查边界 |
294
- | edited | 有生成头但 hash 对不上 | 拒绝,除非 `--force` | 报 drift |
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 的 schema(`{code,data}` 包装自动解一层) | error |
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
- 这条专门兜 `adopt`:生成模板里本来每个动作前都有 `ctx.can('view')`,一旦这个文件交给手写,生成器不再管它,只有这条检查还能发现「权限被悄悄删了」。
346
+ 这条专门覆盖 `adopt` 场景:生成模板里本来每个动作前都有 `ctx.can('view')`,一旦这个文件交给手写,生成器不再管理它,只有这条检查还能发现「权限判断被移除」。
349
347
 
350
348
  ---
351
349
 
352
350
  ## 退出码
353
351
 
354
- `drift` / 手写冲突 / 契约失败(响应或请求体)/ 权限守卫缺失 / 护栏 error 任一存在 → `1`;
355
- `--strict` 时契约与护栏的 warn 也算失败。
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` 等基线复制进项目。之后 CLI 升级,项目里那份**不会自动更新**:新能力(比如 `list.orderBy`)在老项目里不生效。要么删掉该文件重新 `sync`,要么手动把改动抄过去。
365
- 3. **`list.orderBy` 只排前端**。它是 `ThaCrudPage` 的本地排序,不会给后端接口加 query 参数。要后端排序,那是接口契约的事。
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tha-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "THA 架构的构建期工具:产线生成标准增删改查,护栏守住分层边界。",
5
5
  "type": "module",
6
6
  "author": "温强 <373601967@qq.com>",