@wneng/create-keel 0.4.1 → 0.5.0

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 (38) hide show
  1. package/dist/index.js +74 -25
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/src/standards/templates/tech-stack-server.md.eta +34 -2
  5. package/src/templates/contracts-base/files/dictionaries/event-types.yaml +16 -0
  6. package/src/templates/contracts-base/files/dictionaries/locales.yaml +12 -0
  7. package/src/templates/contracts-base/files/dictionaries/roles.yaml +12 -0
  8. package/src/templates/contracts-base/files/dictionaries/status.yaml +16 -0
  9. package/src/templates/contracts-base/files/errors/common-errors.yaml +40 -0
  10. package/src/templates/contracts-base/files/errors/server-errors.yaml +16 -0
  11. package/src/templates/contracts-base/files/events/audit-event.schema.json +47 -0
  12. package/src/templates/contracts-base/fragment.yaml +22 -10
  13. package/src/templates/contracts-rest/fragment.yaml +2 -1
  14. package/src/templates/contracts-rest-by-audience/files/_components-README.md +32 -0
  15. package/src/templates/contracts-rest-by-audience/files/_components-schemas.yaml +78 -0
  16. package/src/templates/contracts-rest-by-audience/files/admin-api.yaml +37 -0
  17. package/src/templates/contracts-rest-by-audience/files/agent-api.yaml +37 -0
  18. package/src/templates/contracts-rest-by-audience/files/user-api.yaml +33 -0
  19. package/src/templates/contracts-rest-by-audience/fragment.yaml +22 -0
  20. package/src/templates/contracts-rest-events/fragment.yaml +2 -1
  21. package/src/templates/contracts-rest-events-by-audience/files/_components-README.md +32 -0
  22. package/src/templates/contracts-rest-events-by-audience/files/_components-schemas.yaml +78 -0
  23. package/src/templates/contracts-rest-events-by-audience/files/admin-api.yaml +37 -0
  24. package/src/templates/contracts-rest-events-by-audience/files/agent-api.yaml +37 -0
  25. package/src/templates/contracts-rest-events-by-audience/files/asyncapi.yaml +8 -0
  26. package/src/templates/contracts-rest-events-by-audience/files/user-api.yaml +33 -0
  27. package/src/templates/contracts-rest-events-by-audience/fragment.yaml +25 -0
  28. package/src/templates/docs-skeleton/files/governance-contracts-layout.md +172 -0
  29. package/src/templates/docs-skeleton/fragment.yaml +3 -0
  30. package/src/templates/root-files/files/AGENTS.md +2 -0
  31. package/src/templates/welcome-html/files/WELCOME.md +39 -0
  32. package/src/templates/welcome-html/files/keel-README.md +28 -0
  33. package/src/templates/welcome-html/files/welcome.html +644 -0
  34. package/src/templates/welcome-html/fragment.yaml +14 -0
  35. package/src/templates/contracts-base/files/dictionaries/domain-models.yaml +0 -3
  36. package/src/templates/contracts-base/files/dictionaries/enums.yaml +0 -3
  37. package/src/templates/contracts-base/files/errors/error-codes.yaml +0 -6
  38. package/src/templates/contracts-base/files/events-gitkeep +0 -0
@@ -0,0 +1,37 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: <%= it.options.projectName %> Admin API
4
+ version: 0.1.0
5
+ description: |
6
+ 管理员后台 API。
7
+
8
+ 路径前缀统一为 `/api/admin/v1/`;调用方限定为内部管理员前端 + 管理工具。
9
+ schemas 通过 $ref 引用 `_components/schemas.yaml`,避免在多个 audience 文件之间重复。
10
+ contact:
11
+ name: Backend Team
12
+ servers:
13
+ - url: https://api.example.com
14
+ description: 生产环境
15
+ - url: http://127.0.0.1:8080
16
+ description: 本地开发
17
+ tags:
18
+ - name: users
19
+ description: 用户管理
20
+ paths:
21
+ /api/admin/v1/users:
22
+ get:
23
+ tags: [users]
24
+ summary: 列出所有用户(管理员)
25
+ operationId: adminListUsers
26
+ responses:
27
+ '200':
28
+ description: 用户列表
29
+ content:
30
+ application/json:
31
+ schema:
32
+ type: array
33
+ items:
34
+ $ref: '_components/schemas.yaml#/components/schemas/User'
35
+ '401':
36
+ $ref: '_components/schemas.yaml#/components/responses/Unauthorized'
37
+ components: {}
@@ -0,0 +1,37 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: <%= it.options.projectName %> Agent API
4
+ version: 0.1.0
5
+ description: |
6
+ Agent 公共接入 API。
7
+
8
+ 路径前缀统一为 `/api/agent/v1/`;调用方限定为终端常驻 agent / 桌面 / CLI 客户端。
9
+ 与管理员 API 完全隔离:agent 不能调用 admin 端点,反之亦然。
10
+ servers:
11
+ - url: https://api.example.com
12
+ description: 生产环境
13
+ - url: http://127.0.0.1:8080
14
+ description: 本地开发
15
+ tags:
16
+ - name: registration
17
+ description: 终端注册
18
+ paths:
19
+ /api/agent/v1/register:
20
+ post:
21
+ tags: [registration]
22
+ summary: 终端首次注册
23
+ operationId: agentRegister
24
+ requestBody:
25
+ required: true
26
+ content:
27
+ application/json:
28
+ schema:
29
+ $ref: '_components/schemas.yaml#/components/schemas/AgentRegistration'
30
+ responses:
31
+ '200':
32
+ description: 注册成功
33
+ content:
34
+ application/json:
35
+ schema:
36
+ $ref: '_components/schemas.yaml#/components/schemas/AgentRegistrationResult'
37
+ components: {}
@@ -0,0 +1,8 @@
1
+ asyncapi: 3.0.0
2
+ info:
3
+ title: <%= it.options.projectName %>
4
+ version: 0.1.0
5
+ description: Event-driven contract. Document publish/subscribe channels here.
6
+ channels: {}
7
+ operations: {}
8
+ components: {}
@@ -0,0 +1,33 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: <%= it.options.projectName %> User API
4
+ version: 0.1.0
5
+ description: |
6
+ 用户自助 API。
7
+
8
+ 路径前缀统一为 `/api/user/v1/`;调用方限定为终端用户的前端 / 移动端。
9
+ 所有端点必须遵守"用户只能看到自己资源"的约束。
10
+ servers:
11
+ - url: https://api.example.com
12
+ description: 生产环境
13
+ - url: http://127.0.0.1:8080
14
+ description: 本地开发
15
+ tags:
16
+ - name: profile
17
+ description: 当前用户资料
18
+ paths:
19
+ /api/user/v1/profile:
20
+ get:
21
+ tags: [profile]
22
+ summary: 获取当前用户资料
23
+ operationId: userGetProfile
24
+ responses:
25
+ '200':
26
+ description: 当前用户资料
27
+ content:
28
+ application/json:
29
+ schema:
30
+ $ref: '_components/schemas.yaml#/components/schemas/User'
31
+ '401':
32
+ $ref: '_components/schemas.yaml#/components/responses/Unauthorized'
33
+ components: {}
@@ -0,0 +1,25 @@
1
+ name: contracts-rest-events-by-audience
2
+ version: 1.0.0
3
+ appliesWhen:
4
+ contract: rest+events
5
+ openapiLayout: by-audience
6
+ priority: 20
7
+ files:
8
+ - from: files/admin-api.yaml
9
+ to: contracts/openapi/admin-api.yaml
10
+ render: true
11
+ - from: files/user-api.yaml
12
+ to: contracts/openapi/user-api.yaml
13
+ render: true
14
+ - from: files/agent-api.yaml
15
+ to: contracts/openapi/agent-api.yaml
16
+ render: true
17
+ - from: files/_components-schemas.yaml
18
+ to: contracts/openapi/_components/schemas.yaml
19
+ render: true
20
+ - from: files/_components-README.md
21
+ to: contracts/openapi/_components/README.md
22
+ render: true
23
+ - from: files/asyncapi.yaml
24
+ to: contracts/asyncapi/asyncapi.yaml
25
+ render: true
@@ -0,0 +1,172 @@
1
+ ---
2
+ last-reviewed: 2026-05-17
3
+ ---
4
+
5
+ # contracts/ 目录布局与拆分策略
6
+
7
+ > 本文档说明:契约(OpenAPI / 错误码 / 字典 / 事件)何时单文件、何时按受众拆分、何时再按业务域细拆。AGENTS.md §1(契约目录职责)与 `contracts/README.md`(SemVer、CHANGELOG、生成代码一致性)是上位文件。
8
+
9
+ ## 1. 三种 OpenAPI 布局
10
+
11
+ scaffolder 通过 `--openapi-layout` 选项选择布局;生成后的项目通过 `.scaffolder.json#options.openapiLayout` 记录。
12
+
13
+ ### 1.1 single(默认)
14
+
15
+ ```
16
+ contracts/openapi/
17
+ └── api.yaml
18
+ ```
19
+
20
+ 适用:单团队、< 30 endpoints、所有调用方共享认证模型。**这是 90% 项目的起点**。`contracts-rest` / `contracts-rest-events` fragment 在 `openapiLayout=single` 时落到此布局。
21
+
22
+ ### 1.2 by-audience(按受众拆分)
23
+
24
+ ```
25
+ contracts/openapi/
26
+ ├── admin-api.yaml # 后台管理员(admin token / 审计严格)
27
+ ├── user-api.yaml # C 端用户(user token / OIDC)
28
+ ├── agent-api.yaml # 终端 / Agent(设备签名 / mTLS)
29
+ └── _components/
30
+ ├── schemas.yaml # 共享数据模型(User、Pagination、Error)
31
+ └── README.md
32
+ ```
33
+
34
+ 适用:以下任一信号出现:
35
+
36
+ - **认证模型分裂**:admin / user / agent 三类调用方使用不同的 security scheme(JWT vs OIDC vs mTLS),同一文件内放三套 `securitySchemes` 让生成代码与 mock 都尴尬
37
+ - **多团队负责**:不同团队各自维护各自的 audience,拆开避免 PR 合并冲突
38
+ - **endpoints > 30**:单文件已超 1500 行,IDE 跳转 / spectral lint 反应慢
39
+ - **rate limit / 版本节奏不同**:admin 偶尔升 v2,user 严格 v1 永久,agent 节奏由设备版本决定
40
+
41
+ scaffolder 通过 `--openapi-layout by-audience` 选择此布局,对应 `contracts-rest-by-audience` 与 `contracts-rest-events-by-audience` fragment。
42
+
43
+ 跨文件 `$ref` 用法:
44
+
45
+ ```yaml
46
+ # admin-api.yaml
47
+ paths:
48
+ /admin/users:
49
+ post:
50
+ requestBody:
51
+ content:
52
+ application/json:
53
+ schema:
54
+ $ref: './_components/schemas.yaml#/User'
55
+ ```
56
+
57
+ `_components/schemas.yaml` 是**唯一**的共享模型来源;任何 audience 文件**禁止**重复声明 `User`、`Pagination`、`ErrorResponse`。生成代码工具(openapi-generator / oapi-codegen)必须开启多文件 bundle 模式(先 `swagger-cli bundle` 或 `redocly bundle` 合一再生成)。
58
+
59
+ ### 1.3 by-domain(按业务域,未内置)
60
+
61
+ ```
62
+ contracts/openapi/
63
+ ├── auth/
64
+ │ ├── auth-api.yaml
65
+ │ └── _components/schemas.yaml
66
+ ├── face-engine/
67
+ │ ├── face-api.yaml
68
+ │ └── _components/schemas.yaml
69
+ ├── ops/
70
+ │ └── ops-api.yaml
71
+ └── _components/
72
+ └── schemas.yaml # 跨域共享(User、Tenant)
73
+ ```
74
+
75
+ 适用:**by-audience 仍嫌大**且业务域可清晰隔离,例如:
76
+
77
+ - 同一 audience 下有 ≥ 5 个独立业务域,每个域 endpoints > 20
78
+ - 业务域有独立的发布周期(face-engine 每周一发,ops 每月一发)
79
+ - 业务域归属不同子团队,PR review 渠道完全隔离
80
+
81
+ ⚠️ **不要过早进 by-domain**:scaffolder **不内置**这个布局,需要时由项目自行创建子目录与 `$ref` 路径。`contracts-base` fragment 的 `dictionaries/` `errors/` `events/` 已经按域拆分(见 §3),通常这些就够了;OpenAPI 拆到 by-domain 是最后一步。
82
+
83
+ ## 2. 何时升级布局
84
+
85
+ | 当前 | 信号 | 下一步 |
86
+ | --- | --- | --- |
87
+ | single | endpoints > 30 或 单文件 > 1500 行 或 出现第二种 securityScheme | by-audience |
88
+ | by-audience | 单 audience 内 > 5 业务域且各自独立发布 | by-domain |
89
+
90
+ 升级是 **Tier 4**(架构变更):
91
+
92
+ 1. 写 ADR 记录"为什么从 X 升 Y"
93
+ 2. 在同一 PR 内迁移所有 endpoints + 更新所有跨文件 `$ref`
94
+ 3. `contracts/CHANGELOG.md` 记 MINOR(路径变了但模型不变)或 MAJOR(路径 + 模型同时变)
95
+ 4. 更新 `docs/06-集成对接/` 中已有的对接文档(如果有)
96
+
97
+ 不要做"逐步迁移"——契约文件结构混搭比单一臃肿更难维护。
98
+
99
+ ## 3. 字典 / 错误码 / 事件的拆分
100
+
101
+ `contracts-base` fragment 默认按域拆分,**不再**用单一聚合文件:
102
+
103
+ ```
104
+ contracts/
105
+ ├── dictionaries/
106
+ │ ├── roles.yaml # 用户角色枚举
107
+ │ ├── status.yaml # 通用状态机(active/inactive/pending/...)
108
+ │ ├── locales.yaml # 支持的语言
109
+ │ └── event-types.yaml # 事件类型索引(同时是 events/*.schema.json 的目录)
110
+ ├── errors/
111
+ │ ├── common-errors.yaml # 横切错误(auth / validation / rate-limit)
112
+ │ ├── server-errors.yaml # 后端业务错误
113
+ │ └── error-response.schema.json
114
+ └── events/
115
+ └── audit-event.schema.json # 每个事件域一份 schema,由 dictionaries/event-types.yaml 索引
116
+ ```
117
+
118
+ 新增字典 / 错误域时**直接添新文件**,不要往现有文件里塞:
119
+
120
+ - 新加一个"支付状态"枚举 → `contracts/dictionaries/payment-status.yaml`,**不要**塞进 `status.yaml`
121
+ - 新加一类"face-engine 业务错误" → `contracts/errors/face-engine-errors.yaml`
122
+ - 新加一类"face-detected 事件" → `contracts/events/face-detected.schema.json`,并在 `dictionaries/event-types.yaml` 加一行索引
123
+
124
+ 域间引用通过 `$ref` 跨文件:
125
+
126
+ ```yaml
127
+ # dictionaries/event-types.yaml
128
+ event-types:
129
+ audit:
130
+ schema: ../events/audit-event.schema.json
131
+ description: 审计事件,所有用户敏感操作必须发
132
+ face-detected:
133
+ schema: ../events/face-detected.schema.json
134
+ ```
135
+
136
+ ## 4. CI 校验
137
+
138
+ - `spectral lint contracts/openapi/**/*.yaml`:对 single 与 by-audience 都生效(递归 glob)
139
+ - 多文件 bundle:`redocly bundle contracts/openapi/admin-api.yaml -o /tmp/admin.bundled.yaml` 后再 lint,确保跨文件 `$ref` 解析无错
140
+ - `ajv` 校验 `contracts/events/*.schema.json` 与 `contracts/dictionaries/*.yaml`
141
+ - 生成代码 diff 检查(`generated/` 重生成后必须无 diff)对所有布局通用
142
+
143
+ scaffolder 生成的 `.gitee/pipelines/ci.yml` / `.github/workflows/ci.yml` 已覆盖 `contracts/openapi/**/*.yaml` 递归 glob,单文件 / by-audience 无需额外 CI 改动。
144
+
145
+ ## 5. 与生成代码 (`generated/`) 的关系
146
+
147
+ 无论哪种布局,`generated/` 都是**重新生成**的目标,禁止手改。布局变化时:
148
+
149
+ - single → by-audience:生成器命令从 `--input api.yaml` 变成多次调用(每个 audience 一次),输出到独立的 `generated/admin/`、`generated/user/`、`generated/agent/` 子目录
150
+ - by-audience → by-domain:在每个 audience 下再分子目录
151
+
152
+ CI "重新生成 diff 为空" 的检查对所有布局保持通用——只要把 `make generate` / `npm run generate` 脚本里的命令同步更新即可。
153
+
154
+ ## 6. AGENTS.md / Tier 关系
155
+
156
+ - 修改 `contracts/openapi/**/*.yaml` 任意文件 → 至少 **Tier 3**(contract-affecting),按 AGENTS.md §3.1 执行
157
+ - 拆分布局(single → by-audience / by-audience → by-domain)→ **Tier 4**(架构变更),需 ADR
158
+ - 在 by-audience 模式下加 endpoint 仅触及一个 audience 文件 → 仍是 Tier 3,PR 描述说明"只触及 admin-api"即可,不需要全文档投入
159
+
160
+ ## 7. FAQ
161
+
162
+ **Q: by-audience 下的 `_components/schemas.yaml` 算几个文件?**
163
+ A: 1 个共享模型仓库。`User` `Pagination` `ErrorResponse` 都放这里。当 schemas.yaml 自身 > 800 行时再考虑拆 `_components/user.yaml` `_components/pagination.yaml`。
164
+
165
+ **Q: events 是否也按 audience 拆?**
166
+ A: 不需要。事件天然是按业务域分的(`audit-event` / `face-detected` / `payment-completed`),`contracts/events/<domain>.schema.json` 已经满足;不要再人为加一层 audience。
167
+
168
+ **Q: AsyncAPI 文件要不要按 audience 拆?**
169
+ A: 默认 single(`contracts/asyncapi/asyncapi.yaml`)。事件总线一般是单一 broker(kafka / redis-stream),订阅端按 audience 区分是消费侧的事,不是契约侧的事。需要时再升级。
170
+
171
+ **Q: 我现在是 single,未来会不会被强制升 by-audience?**
172
+ A: 不会。AGENTS.md / governance 不强制升级;只在你**主动**遇到 §2 信号时升。single 项目永远可以保持 single。
@@ -120,6 +120,9 @@ files:
120
120
  - from: files/governance-database.md
121
121
  to: docs/governance/database.md
122
122
  render: false
123
+ - from: files/governance-contracts-layout.md
124
+ to: docs/governance/contracts-layout.md
125
+ render: false
123
126
  - from: files/governance-checklists.md
124
127
  to: docs/governance/checklists.md
125
128
  render: true
@@ -186,6 +186,7 @@
186
186
  | 修改路径 | 必读 |
187
187
  | --- | --- |
188
188
  | `contracts/**` | `contracts/README.md`、`docs/governance/integrations.md`、`docs/governance/checklists.md` |
189
+ | `contracts/openapi/**` | 上一行 + `docs/governance/contracts-layout.md`(single / by-audience 选型与升级) |
189
190
  | `docs/**`(除 `docs/governance/` 与 `docs/06-集成对接/`) | `docs/README.md`、`docs/governance/docs-references.md`、`docs/governance/checklists.md` |
190
191
  <% if (it.options.integrations) { %>| `docs/06-集成对接/**` | `docs/governance/integrations.md` |
191
192
  <% } %>| `docs/governance/**` | `docs/governance/README.md`;同一 PR 必须同步本文件中对应摘要 |
@@ -223,6 +224,7 @@
223
224
  | tools / scripts 完整规则 | [`docs/governance/tools-scripts.md`](docs/governance/tools-scripts.md) |
224
225
  | 变更分级(六档 Tier) | [`docs/governance/change-tiers.md`](docs/governance/change-tiers.md) |
225
226
  | 数据库目录 / 迁移工具 / SQL 归属 | [`docs/governance/database.md`](docs/governance/database.md) |
227
+ | 契约多文件布局(single / by-audience) | [`docs/governance/contracts-layout.md`](docs/governance/contracts-layout.md) |
226
228
  | 完整 checklist | [`docs/governance/checklists.md`](docs/governance/checklists.md) |
227
229
  | 治理细则索引 | [`docs/governance/README.md`](docs/governance/README.md) |
228
230
 
@@ -0,0 +1,39 @@
1
+ # 👋 欢迎 — <%= it.options.projectName %>
2
+
3
+ 本项目由 [keel](https://gitee.com/wangneng521/keel) 框架(@wneng/create-keel <%= it.scaffolderVersion %>)生成。
4
+
5
+ ## 🚀 第一次进项目,先看这个
6
+
7
+ 打开 [`.keel/welcome.html`](.keel/welcome.html) — 这是一份**面向新成员的可视化指南**,含:
8
+
9
+ - 目录速查 / 关键概念(Tier、Doc Weight、Contract First)
10
+ - 按任务找入口(修 bug / 加 API / 重构 / 升级依赖)
11
+ - 常用命令(按本项目实际选型生成)
12
+ - AI 协作规则
13
+ - 常见问题
14
+
15
+ > 双击文件即可在浏览器打开;IDE 里也能 Open in Browser。完全离线,不依赖外部 CDN。
16
+
17
+ ## 📚 给开发者 / AI 的入口
18
+
19
+ | 看什么 | 进哪儿 |
20
+ |---|---|
21
+ | AI 协作总纲 | [`AGENTS.md`](AGENTS.md) |
22
+ | 文档门户(人类) | [`docs/README.md`](docs/README.md) |
23
+ | 契约(机器真值) | [`contracts/README.md`](contracts/README.md) |
24
+ | 治理细则 | [`docs/governance/`](docs/governance/) |
25
+ | 变更分级 | [`docs/governance/change-tiers.md`](docs/governance/change-tiers.md) |
26
+ <% if (it.options.database !== 'none') { %>| 数据库目录 | [`docs/governance/database.md`](docs/governance/database.md) |
27
+ <% } %>
28
+
29
+ ## 🧑‍💻 想直接动手?
30
+
31
+ ```bash
32
+ <% if (it.options.backend === 'node') { %>cd server && npm install && npm test
33
+ <% } else if (it.options.backend === 'java') { %>cd server && mvn -B verify
34
+ <% } else if (it.options.backend === 'python') { %>cd server && python -m venv .venv && source .venv/bin/activate && pip install -e ".[dev]" && pytest -q
35
+ <% } else if (it.options.backend === 'go') { %>cd server && go mod download && go test ./...
36
+ <% } %><% if (it.options.frontend === 'react' || it.options.frontend === 'vue') { %>cd web && npm install && npm run dev
37
+ <% } %>```
38
+
39
+ 完整命令见 `.keel/welcome.html` §5。
@@ -0,0 +1,28 @@
1
+ # .keel/
2
+
3
+ keel 框架的**元数据 / 资料目录**,与 `.kiro/` `.github/` 等 IDE / 平台目录区分开。
4
+
5
+ ## 文件清单
6
+
7
+ | 文件 | 用途 |
8
+ |---|---|
9
+ | `welcome.html` | 面向新成员的可视化指南(双击打开)。包含目录速查、关键概念、任务入口、常用命令、FAQ |
10
+ | `README.md` | 本文件 |
11
+
12
+ > 项目根的 `.scaffolder.json` 是 keel scaffolder 的**机器元数据**,不在本目录——它要保持在仓库根才能让升级 / 校验工具找到。
13
+
14
+ ## 是否要进版本控制?
15
+
16
+ `.keel/` 目录建议**进**版本控制:
17
+
18
+ - `welcome.html` 是面向**全团队**的入门资料,不是个人偏好
19
+ - 升级 keel 后 `welcome.html` 会被新生成的版本覆盖;如果你做过本地修改要备份
20
+ - 不要把任何机密、个人 IDE 配置放进 `.keel/`
21
+
22
+ ## 想自己改 welcome.html?
23
+
24
+ `welcome.html` 是单文件、内联 CSS / JS、无外部依赖的纯静态页面,可以直接改。
25
+ 但下次跑 `npx @wneng/create-keel ...` 升级会覆盖它。如要保护本地改动:
26
+
27
+ - 选项 1:把改动写进 `docs/` 下的对应 markdown,让 welcome.html 仍是"上游版本"
28
+ - 选项 2:在文件顶部加 `<!-- keep-local -->`(未来 scaffolder 升级时会跳过覆盖;目前是约定,未自动化)