@wneng/create-keel 0.3.4 → 0.4.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 (94) hide show
  1. package/dist/index.js +211 -18
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/src/templates/ci-gitee/files/PULL_REQUEST_TEMPLATE.md +13 -1
  5. package/src/templates/ci-gitee/files/pipeline.yml +71 -0
  6. package/src/templates/ci-github/files/PULL_REQUEST_TEMPLATE.md +13 -1
  7. package/src/templates/ci-github/files/ci.yml +169 -0
  8. package/src/templates/contracts-events/fragment.yaml +1 -1
  9. package/src/templates/contracts-rest-events/fragment.yaml +1 -1
  10. package/src/templates/db-go-elasticsearch/files/Makefile +18 -0
  11. package/src/templates/db-go-elasticsearch/files/apply_templates.go +80 -0
  12. package/src/templates/db-go-elasticsearch/files/db-README.md +57 -0
  13. package/src/templates/db-go-elasticsearch/files/go.mod +7 -0
  14. package/src/templates/db-go-elasticsearch/files/index-template-init.json +25 -0
  15. package/src/templates/db-go-elasticsearch/fragment.yaml +22 -0
  16. package/src/templates/db-go-migrate-mysql/files/000001_init.down.sql +3 -0
  17. package/src/templates/db-go-migrate-mysql/files/000001_init.up.sql +35 -0
  18. package/src/templates/db-go-migrate-mysql/files/Makefile +33 -0
  19. package/src/templates/db-go-migrate-mysql/files/db-README.md +77 -0
  20. package/src/templates/db-go-migrate-mysql/files/go.mod +8 -0
  21. package/src/templates/db-go-migrate-mysql/fragment.yaml +22 -0
  22. package/src/templates/db-go-migrate-postgres/files/000001_init.down.sql +3 -0
  23. package/src/templates/db-go-migrate-postgres/files/000001_init.up.sql +32 -0
  24. package/src/templates/db-go-migrate-postgres/files/Makefile +31 -0
  25. package/src/templates/db-go-migrate-postgres/files/db-README.md +71 -0
  26. package/src/templates/db-go-migrate-postgres/files/go.mod +8 -0
  27. package/src/templates/db-go-migrate-postgres/fragment.yaml +22 -0
  28. package/src/templates/db-java-elasticsearch/files/EsTemplateApplier.java +86 -0
  29. package/src/templates/db-java-elasticsearch/files/db-README.md +63 -0
  30. package/src/templates/db-java-elasticsearch/files/index-template-init.json +25 -0
  31. package/src/templates/db-java-elasticsearch/files/pom.xml +134 -0
  32. package/src/templates/db-java-elasticsearch/fragment.yaml +19 -0
  33. package/src/templates/db-java-flyway-mysql/files/V1__init.sql +44 -0
  34. package/src/templates/db-java-flyway-mysql/files/application.yaml +39 -0
  35. package/src/templates/db-java-flyway-mysql/files/db-README.md +102 -0
  36. package/src/templates/db-java-flyway-mysql/files/pom.xml +172 -0
  37. package/src/templates/db-java-flyway-mysql/fragment.yaml +19 -0
  38. package/src/templates/db-java-flyway-postgres/files/V1__init.sql +40 -0
  39. package/src/templates/db-java-flyway-postgres/files/application.yaml +37 -0
  40. package/src/templates/db-java-flyway-postgres/files/db-README.md +75 -0
  41. package/src/templates/db-java-flyway-postgres/files/pom.xml +166 -0
  42. package/src/templates/db-java-flyway-postgres/fragment.yaml +19 -0
  43. package/src/templates/db-node-elasticsearch/files/apply-templates.cjs +60 -0
  44. package/src/templates/db-node-elasticsearch/files/db-README.md +76 -0
  45. package/src/templates/db-node-elasticsearch/files/index-template-init.json +26 -0
  46. package/src/templates/db-node-elasticsearch/files/package.json +26 -0
  47. package/src/templates/db-node-elasticsearch/fragment.yaml +19 -0
  48. package/src/templates/db-node-knex-mysql/files/db-README.md +90 -0
  49. package/src/templates/db-node-knex-mysql/files/knexfile.cjs +72 -0
  50. package/src/templates/db-node-knex-mysql/files/migrations-init.cjs +42 -0
  51. package/src/templates/db-node-knex-mysql/files/package.json +31 -0
  52. package/src/templates/db-node-knex-mysql/files/seeds-dev-fixtures.cjs +38 -0
  53. package/src/templates/db-node-knex-mysql/files/seeds-prod-dictionaries.cjs +25 -0
  54. package/src/templates/db-node-knex-mysql/fragment.yaml +25 -0
  55. package/src/templates/db-node-knex-postgres/files/db-README.md +81 -0
  56. package/src/templates/db-node-knex-postgres/files/knexfile.cjs +67 -0
  57. package/src/templates/db-node-knex-postgres/files/migrations-init.cjs +42 -0
  58. package/src/templates/db-node-knex-postgres/files/package.json +31 -0
  59. package/src/templates/db-node-knex-postgres/files/seeds-dev-fixtures.cjs +36 -0
  60. package/src/templates/db-node-knex-postgres/files/seeds-prod-dictionaries.cjs +26 -0
  61. package/src/templates/db-node-knex-postgres/fragment.yaml +25 -0
  62. package/src/templates/db-python-alembic-mysql/files/0001_init.py +70 -0
  63. package/src/templates/db-python-alembic-mysql/files/alembic.ini +47 -0
  64. package/src/templates/db-python-alembic-mysql/files/db-README.md +87 -0
  65. package/src/templates/db-python-alembic-mysql/files/env.py +71 -0
  66. package/src/templates/db-python-alembic-mysql/files/pyproject.toml +52 -0
  67. package/src/templates/db-python-alembic-mysql/files/script.py.mako +26 -0
  68. package/src/templates/db-python-alembic-mysql/fragment.yaml +25 -0
  69. package/src/templates/db-python-alembic-postgres/files/0001_init.py +62 -0
  70. package/src/templates/db-python-alembic-postgres/files/alembic.ini +45 -0
  71. package/src/templates/db-python-alembic-postgres/files/db-README.md +70 -0
  72. package/src/templates/db-python-alembic-postgres/files/env.py +62 -0
  73. package/src/templates/db-python-alembic-postgres/files/pyproject.toml +52 -0
  74. package/src/templates/db-python-alembic-postgres/files/script.py.mako +25 -0
  75. package/src/templates/db-python-alembic-postgres/fragment.yaml +25 -0
  76. package/src/templates/db-python-elasticsearch/files/apply_templates.py +55 -0
  77. package/src/templates/db-python-elasticsearch/files/db-README.md +57 -0
  78. package/src/templates/db-python-elasticsearch/files/index-template-init.json +25 -0
  79. package/src/templates/db-python-elasticsearch/files/pyproject.toml +50 -0
  80. package/src/templates/db-python-elasticsearch/fragment.yaml +19 -0
  81. package/src/templates/docs-skeleton/files/governance-change-tiers.md +135 -0
  82. package/src/templates/docs-skeleton/files/governance-database.md +150 -0
  83. package/src/templates/docs-skeleton/fragment.yaml +6 -0
  84. package/src/templates/root-files/files/AGENTS.md +42 -7
  85. package/src/templates/server-node/files/_eslintrc.cjs +5 -1
  86. package/src/templates/server-python/files/README.md +75 -2
  87. package/src/templates/server-python/files/app-init.py +11 -1
  88. package/src/templates/server-python/files/config.py +40 -0
  89. package/src/templates/server-python/files/main.py +62 -0
  90. package/src/templates/server-python/files/pyproject.toml +14 -1
  91. package/src/templates/server-python/files/test_healthz.py +36 -0
  92. package/src/templates/server-python/fragment.yaml +10 -1
  93. package/src/templates/web-react/files/_eslintrc.cjs +5 -1
  94. package/src/templates/web-vue/files/_eslintrc.cjs +5 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wneng/create-keel",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Scaffolder for Contract First + Vibe Coding projects (keel conventions)",
5
5
  "keywords": [
6
6
  "scaffold",
@@ -7,12 +7,24 @@
7
7
 
8
8
  <!-- 一句话讲清楚这个 PR 改了什么、为什么 -->
9
9
 
10
+ ## 变更分级(Tier)
11
+
12
+ > 完整定义见 [`docs/governance/change-tiers.md`](../docs/governance/change-tiers.md)。**勾选一项**。
13
+
14
+ - [ ] **Tier 0** trivial — typo / 格式化 / patch 升级 / 补测试。无需契约 / ADR / PRD
15
+ - [ ] **Tier 1** bugfix — 修缺陷,无新行为。本 PR 描述含:重现 + 根因 + 修复 + 回归测试
16
+ - [ ] **Tier 2** small change — 局部小改(加按钮 / 排序 / 配置)。本 PR 描述含动机 + 方案 + 影响面
17
+ - [ ] **Tier 3** contract change — 改了 `contracts/`(加 API / 字段 / 错误码 / 事件)。已附契约锚点 + CHANGELOG 已同步
18
+ - [ ] **Tier 4** architecture / breaking — 跨服务 / 鉴权变化 / 删字段。已附 ADR + 详细设计 + 迁移 / 回滚预案
19
+ - [ ] **Tier 5** spike — 探索性改动。分支为 `spike/*`,**不会**合入 `main`(合入需重新走 Tier 1-4)
20
+
10
21
  ## 关联引用
11
22
 
12
- <!-- 至少填一项;契约 / 设计 / spec 三选一 -->
23
+ <!-- Tier 0/1/2 可省略;Tier 3+ 必须填 -->
13
24
 
14
25
  - 契约锚点:`contracts/openapi/api.yaml#/paths/...` 或 `contracts/events/event-catalog.yaml#/events/...`
15
26
  - 设计文档:`docs/04-后端详细设计/<slug>.md` 或 `docs/05-前端客户端详细设计/<slug>-<platform>.md`
27
+ - ADR(Tier 4 必填):`docs/02-系统方案与架构/adr-<NNNN>-<slug>.md`
16
28
  - spec:`.kiro/specs/<feature>/`(如适用)
17
29
 
18
30
  ## 变更类型
@@ -28,6 +28,15 @@ jobs:
28
28
  else
29
29
  echo "tools/governance-lint/ not present; skipping"
30
30
  fi
31
+ - |
32
+ # CODEOWNERS @TBD gate (AGENTS.md §3.1, docs/governance/git-workflow.md).
33
+ # The CODEOWNERS file ships with @TBD placeholders; CI must reject any
34
+ # merge that still contains them so reviewers are forced to assign real owners.
35
+ if [ -f CODEOWNERS ] && grep -q '@TBD' CODEOWNERS; then
36
+ echo "ERROR: CODEOWNERS still contains @TBD placeholder(s); replace with real Git accounts before merging."
37
+ grep -n '@TBD' CODEOWNERS || true
38
+ exit 1
39
+ fi
31
40
 
32
41
  contract-lint:
33
42
  stage: lint
@@ -127,6 +136,68 @@ jobs:
127
136
  script:
128
137
  - gitleaks detect --redact --no-git -v
129
138
 
139
+ <% if (it.options.database === 'mysql' || it.options.database === 'postgres') { %> db-migrate-smoke:
140
+ stage: test
141
+ <% if (it.options.database === 'postgres') { %> image: postgres:16-alpine
142
+ services:
143
+ - name: postgres:16-alpine
144
+ alias: postgres
145
+ env:
146
+ POSTGRES_PASSWORD: postgres
147
+ POSTGRES_DB: ci_smoke
148
+ <% } else { %> image: mysql:8
149
+ services:
150
+ - name: mysql:8
151
+ alias: mysql
152
+ env:
153
+ MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
154
+ MYSQL_DATABASE: ci_smoke
155
+ <% } %> script:
156
+ - cd server
157
+ <% if (it.options.backend === 'node') { %> - apk add --no-cache nodejs npm || true
158
+ - npm ci
159
+ <% if (it.options.database === 'postgres') { %> - DB_HOST=postgres DB_PORT=5432 DB_USER=postgres DB_PASSWORD=postgres DB_NAME=ci_smoke npm run db:migrate
160
+ - DB_HOST=postgres DB_PORT=5432 DB_USER=postgres DB_PASSWORD=postgres DB_NAME=ci_smoke npm run db:rollback
161
+ <% } else { %> - DB_HOST=mysql DB_PORT=3306 DB_USER=root DB_PASSWORD= DB_NAME=ci_smoke npm run db:migrate
162
+ - DB_HOST=mysql DB_PORT=3306 DB_USER=root DB_PASSWORD= DB_NAME=ci_smoke npm run db:rollback
163
+ <% } %><% } %><% if (it.options.backend === 'java') { %> - apk add --no-cache maven openjdk21 || true
164
+ <% if (it.options.database === 'postgres') { %> - SPRING_DATASOURCE_URL='jdbc:postgresql://postgres:5432/ci_smoke' SPRING_DATASOURCE_USERNAME=postgres SPRING_DATASOURCE_PASSWORD=postgres mvn -B flyway:migrate
165
+ - SPRING_DATASOURCE_URL='jdbc:postgresql://postgres:5432/ci_smoke' SPRING_DATASOURCE_USERNAME=postgres SPRING_DATASOURCE_PASSWORD=postgres mvn -B flyway:validate
166
+ <% } else { %> - SPRING_DATASOURCE_URL='jdbc:mysql://mysql:3306/ci_smoke?useSSL=false&serverTimezone=UTC' SPRING_DATASOURCE_USERNAME=root SPRING_DATASOURCE_PASSWORD= mvn -B flyway:migrate
167
+ - SPRING_DATASOURCE_URL='jdbc:mysql://mysql:3306/ci_smoke?useSSL=false&serverTimezone=UTC' SPRING_DATASOURCE_USERNAME=root SPRING_DATASOURCE_PASSWORD= mvn -B flyway:validate
168
+ <% } %><% } %><% if (it.options.backend === 'python') { %> - apk add --no-cache python3 py3-pip || true
169
+ - pip install -e .
170
+ <% if (it.options.database === 'postgres') { %> - DATABASE_URL='postgresql+psycopg://postgres:postgres@postgres:5432/ci_smoke' alembic upgrade head
171
+ - DATABASE_URL='postgresql+psycopg://postgres:postgres@postgres:5432/ci_smoke' alembic downgrade base
172
+ <% } else { %> - DATABASE_URL='mysql+pymysql://root:@mysql:3306/ci_smoke' alembic upgrade head
173
+ - DATABASE_URL='mysql+pymysql://root:@mysql:3306/ci_smoke' alembic downgrade base
174
+ <% } %><% } %><% if (it.options.backend === 'go') { %> - apk add --no-cache go || true
175
+ - go install -tags '<%= it.options.database %>' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1
176
+ <% if (it.options.database === 'postgres') { %> - DATABASE_URL='postgres://postgres:postgres@postgres:5432/ci_smoke?sslmode=disable' migrate -path db/migrations -database "$DATABASE_URL" up
177
+ - DATABASE_URL='postgres://postgres:postgres@postgres:5432/ci_smoke?sslmode=disable' migrate -path db/migrations -database "$DATABASE_URL" down -all
178
+ <% } else { %> - DATABASE_URL='mysql://root:@tcp(mysql:3306)/ci_smoke?multiStatements=true' migrate -path db/migrations -database "$DATABASE_URL" up
179
+ - DATABASE_URL='mysql://root:@tcp(mysql:3306)/ci_smoke?multiStatements=true' migrate -path db/migrations -database "$DATABASE_URL" down -all
180
+ <% } %><% } %><% } %>
181
+ <% if (it.options.database === 'elasticsearch') { %> db-es-template-apply:
182
+ stage: test
183
+ image: <%= it.options.backend === 'node' ? 'node:20-alpine' : it.options.backend === 'java' ? 'maven:3.9-eclipse-temurin-21' : it.options.backend === 'python' ? 'python:3.12-slim' : 'golang:1.22-alpine' %>
184
+ services:
185
+ - name: elasticsearch:8.15.1
186
+ alias: elasticsearch
187
+ env:
188
+ discovery.type: single-node
189
+ xpack.security.enabled: 'false'
190
+ ES_JAVA_OPTS: '-Xms512m -Xmx512m'
191
+ script:
192
+ - cd server
193
+ <% if (it.options.backend === 'node') { %> - npm ci
194
+ - ELASTICSEARCH_URL=http://elasticsearch:9200 npm run es:apply
195
+ <% } %><% if (it.options.backend === 'java') { %> - SPRING_ELASTICSEARCH_URIS=http://elasticsearch:9200 mvn -B test -Dtest=*ApplicationTests
196
+ <% } %><% if (it.options.backend === 'python') { %> - pip install -e .
197
+ - ELASTICSEARCH_URL=http://elasticsearch:9200 python db/apply_templates.py
198
+ <% } %><% if (it.options.backend === 'go') { %> - go mod download
199
+ - ELASTICSEARCH_URL=http://elasticsearch:9200 make es-apply
200
+ <% } %><% } %>
130
201
  <% if (it.options.backend === 'node' || it.options.frontend === 'react' || it.options.frontend === 'vue' || it.options.mobile === 'react-native' || it.options.miniapp === 'wechat') { %> npm-audit:
131
202
  stage: scan
132
203
  image: node:20-alpine
@@ -7,12 +7,24 @@
7
7
 
8
8
  <!-- 一句话讲清楚这个 PR 改了什么、为什么 -->
9
9
 
10
+ ## 变更分级(Tier)
11
+
12
+ > 完整定义见 [`docs/governance/change-tiers.md`](../docs/governance/change-tiers.md)。**勾选一项**。
13
+
14
+ - [ ] **Tier 0** trivial — typo / 格式化 / patch 升级 / 补测试。无需契约 / ADR / PRD
15
+ - [ ] **Tier 1** bugfix — 修缺陷,无新行为。本 PR 描述含:重现 + 根因 + 修复 + 回归测试
16
+ - [ ] **Tier 2** small change — 局部小改(加按钮 / 排序 / 配置)。本 PR 描述含动机 + 方案 + 影响面
17
+ - [ ] **Tier 3** contract change — 改了 `contracts/`(加 API / 字段 / 错误码 / 事件)。已附契约锚点 + CHANGELOG 已同步
18
+ - [ ] **Tier 4** architecture / breaking — 跨服务 / 鉴权变化 / 删字段。已附 ADR + 详细设计 + 迁移 / 回滚预案
19
+ - [ ] **Tier 5** spike — 探索性改动。分支为 `spike/*`,**不会**合入 `main`(合入需重新走 Tier 1-4)
20
+
10
21
  ## 关联引用
11
22
 
12
- <!-- 至少填一项;契约 / 设计 / spec 三选一 -->
23
+ <!-- Tier 0/1/2 可省略;Tier 3+ 必须填 -->
13
24
 
14
25
  - 契约锚点:`contracts/openapi/api.yaml#/paths/...` 或 `contracts/events/event-catalog.yaml#/events/...`
15
26
  - 设计文档:`docs/04-后端详细设计/<slug>.md` 或 `docs/05-前端客户端详细设计/<slug>-<platform>.md`
27
+ - ADR(Tier 4 必填):`docs/02-系统方案与架构/adr-<NNNN>-<slug>.md`
16
28
  - spec:`.kiro/specs/<feature>/`(如适用)
17
29
 
18
30
  ## 变更类型
@@ -31,6 +31,15 @@ jobs:
31
31
  else
32
32
  echo "tools/governance-lint/ not present; skipping"
33
33
  fi
34
+ - name: codeowners-tbd-gate
35
+ run: |
36
+ # AGENTS.md §3.1 + docs/governance/git-workflow.md require real
37
+ # owners; @TBD placeholders must be replaced before merge.
38
+ if [ -f CODEOWNERS ] && grep -q '@TBD' CODEOWNERS; then
39
+ echo "ERROR: CODEOWNERS still contains @TBD placeholder(s); replace with real Git accounts before merging."
40
+ grep -n '@TBD' CODEOWNERS || true
41
+ exit 1
42
+ fi
34
43
 
35
44
  secret-scan:
36
45
  runs-on: ubuntu-latest
@@ -208,3 +217,163 @@ jobs:
208
217
  - run: cargo clippy --all-targets -- -D warnings
209
218
  - run: cargo test
210
219
  <% } %>
220
+ <% if (it.options.database === 'mysql' || it.options.database === 'postgres') { %> db-migrate-smoke:
221
+ runs-on: ubuntu-latest
222
+ defaults:
223
+ run:
224
+ working-directory: server
225
+ services:
226
+ <% if (it.options.database === 'postgres') { %> postgres:
227
+ image: postgres:16-alpine
228
+ env:
229
+ POSTGRES_PASSWORD: postgres
230
+ POSTGRES_DB: ci_smoke
231
+ ports: ['5432:5432']
232
+ options: >-
233
+ --health-cmd pg_isready
234
+ --health-interval 10s
235
+ --health-timeout 5s
236
+ --health-retries 5
237
+ <% } else { %> mysql:
238
+ image: mysql:8
239
+ env:
240
+ MYSQL_ROOT_PASSWORD: ''
241
+ MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
242
+ MYSQL_DATABASE: ci_smoke
243
+ ports: ['3306:3306']
244
+ options: >-
245
+ --health-cmd "mysqladmin ping -h localhost"
246
+ --health-interval 10s
247
+ --health-timeout 5s
248
+ --health-retries 5
249
+ <% } %> steps:
250
+ - uses: actions/checkout@v4
251
+ <% if (it.options.backend === 'node') { %> - uses: actions/setup-node@v4
252
+ with:
253
+ node-version: '20'
254
+ cache: npm
255
+ cache-dependency-path: server/package-lock.json
256
+ - run: npm ci
257
+ - name: migrate up
258
+ env:
259
+ <% if (it.options.database === 'postgres') { %> DB_HOST: 127.0.0.1
260
+ DB_PORT: '5432'
261
+ DB_USER: postgres
262
+ DB_PASSWORD: postgres
263
+ DB_NAME: ci_smoke
264
+ <% } else { %> DB_HOST: 127.0.0.1
265
+ DB_PORT: '3306'
266
+ DB_USER: root
267
+ DB_PASSWORD: ''
268
+ DB_NAME: ci_smoke
269
+ <% } %> run: npm run db:migrate
270
+ - name: migrate down
271
+ env:
272
+ <% if (it.options.database === 'postgres') { %> DB_HOST: 127.0.0.1
273
+ DB_PORT: '5432'
274
+ DB_USER: postgres
275
+ DB_PASSWORD: postgres
276
+ DB_NAME: ci_smoke
277
+ <% } else { %> DB_HOST: 127.0.0.1
278
+ DB_PORT: '3306'
279
+ DB_USER: root
280
+ DB_PASSWORD: ''
281
+ DB_NAME: ci_smoke
282
+ <% } %> run: npm run db:rollback
283
+ <% } %><% if (it.options.backend === 'java') { %> - uses: actions/setup-java@v4
284
+ with:
285
+ distribution: temurin
286
+ java-version: '21'
287
+ cache: maven
288
+ - name: flyway migrate + validate
289
+ env:
290
+ <% if (it.options.database === 'postgres') { %> SPRING_DATASOURCE_URL: jdbc:postgresql://127.0.0.1:5432/ci_smoke
291
+ SPRING_DATASOURCE_USERNAME: postgres
292
+ SPRING_DATASOURCE_PASSWORD: postgres
293
+ <% } else { %> SPRING_DATASOURCE_URL: jdbc:mysql://127.0.0.1:3306/ci_smoke?useSSL=false&serverTimezone=UTC
294
+ SPRING_DATASOURCE_USERNAME: root
295
+ SPRING_DATASOURCE_PASSWORD: ''
296
+ <% } %> run: |
297
+ mvn -B flyway:migrate
298
+ mvn -B flyway:validate
299
+ <% } %><% if (it.options.backend === 'python') { %> - uses: actions/setup-python@v5
300
+ with:
301
+ python-version: '3.12'
302
+ - run: pip install -e .
303
+ - name: alembic upgrade head + downgrade base
304
+ env:
305
+ <% if (it.options.database === 'postgres') { %> DATABASE_URL: postgresql+psycopg://postgres:postgres@127.0.0.1:5432/ci_smoke
306
+ <% } else { %> DATABASE_URL: mysql+pymysql://root:@127.0.0.1:3306/ci_smoke
307
+ <% } %> run: |
308
+ alembic upgrade head
309
+ alembic downgrade base
310
+ <% } %><% if (it.options.backend === 'go') { %> - uses: actions/setup-go@v5
311
+ with:
312
+ go-version: '1.22'
313
+ - name: install golang-migrate
314
+ run: |
315
+ go install -tags '<%= it.options.database %>' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1
316
+ - name: migrate up + down
317
+ env:
318
+ <% if (it.options.database === 'postgres') { %> DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/ci_smoke?sslmode=disable
319
+ <% } else { %> DATABASE_URL: mysql://root:@tcp(127.0.0.1:3306)/ci_smoke?multiStatements=true
320
+ <% } %> run: |
321
+ migrate -path db/migrations -database "$DATABASE_URL" up
322
+ migrate -path db/migrations -database "$DATABASE_URL" down -all
323
+ <% } %><% } %>
324
+ <% if (it.options.database === 'elasticsearch') { %> db-es-template-apply:
325
+ runs-on: ubuntu-latest
326
+ defaults:
327
+ run:
328
+ working-directory: server
329
+ services:
330
+ elasticsearch:
331
+ image: elasticsearch:8.15.1
332
+ env:
333
+ discovery.type: single-node
334
+ xpack.security.enabled: 'false'
335
+ ES_JAVA_OPTS: '-Xms512m -Xmx512m'
336
+ ports: ['9200:9200']
337
+ options: >-
338
+ --health-cmd "curl -fsS http://localhost:9200/_cluster/health"
339
+ --health-interval 10s
340
+ --health-timeout 5s
341
+ --health-retries 12
342
+ steps:
343
+ - uses: actions/checkout@v4
344
+ <% if (it.options.backend === 'node') { %> - uses: actions/setup-node@v4
345
+ with:
346
+ node-version: '20'
347
+ cache: npm
348
+ cache-dependency-path: server/package-lock.json
349
+ - run: npm ci
350
+ - name: apply index templates
351
+ env:
352
+ ELASTICSEARCH_URL: http://127.0.0.1:9200
353
+ run: npm run es:apply
354
+ <% } %><% if (it.options.backend === 'java') { %> - uses: actions/setup-java@v4
355
+ with:
356
+ distribution: temurin
357
+ java-version: '21'
358
+ cache: maven
359
+ - name: spring boot starts and applies templates
360
+ env:
361
+ SPRING_ELASTICSEARCH_URIS: http://127.0.0.1:9200
362
+ run: mvn -B test -Dtest=*ApplicationTests
363
+ <% } %><% if (it.options.backend === 'python') { %> - uses: actions/setup-python@v5
364
+ with:
365
+ python-version: '3.12'
366
+ - run: pip install -e .
367
+ - name: apply index templates
368
+ env:
369
+ ELASTICSEARCH_URL: http://127.0.0.1:9200
370
+ run: python db/apply_templates.py
371
+ <% } %><% if (it.options.backend === 'go') { %> - uses: actions/setup-go@v5
372
+ with:
373
+ go-version: '1.22'
374
+ - run: go mod download
375
+ - name: apply index templates
376
+ env:
377
+ ELASTICSEARCH_URL: http://127.0.0.1:9200
378
+ run: make es-apply
379
+ <% } %><% } %>
@@ -5,5 +5,5 @@ appliesWhen:
5
5
  priority: 20
6
6
  files:
7
7
  - from: files/asyncapi.yaml
8
- to: contracts/openapi/asyncapi.yaml
8
+ to: contracts/asyncapi/asyncapi.yaml
9
9
  render: true
@@ -8,5 +8,5 @@ files:
8
8
  to: contracts/openapi/api.yaml
9
9
  render: true
10
10
  - from: files/asyncapi.yaml
11
- to: contracts/openapi/asyncapi.yaml
11
+ to: contracts/asyncapi/asyncapi.yaml
12
12
  render: true
@@ -0,0 +1,18 @@
1
+ .PHONY: build test lint run es-apply
2
+
3
+ ELASTICSEARCH_URL ?= http://127.0.0.1:9200
4
+
5
+ build:
6
+ go build -o ./bin/server ./cmd/server
7
+
8
+ run:
9
+ go run ./cmd/server
10
+
11
+ test:
12
+ go test ./...
13
+
14
+ lint:
15
+ golangci-lint run
16
+
17
+ es-apply:
18
+ go run ./cmd/apply-templates
@@ -0,0 +1,80 @@
1
+ // Idempotent Elasticsearch index-template applier (Go).
2
+ //
3
+ // ES 没有事务性 DDL;"migration" 退化为把 db/index-templates/*.json 全部 PUT 一次。
4
+ // ES 自身保证 PUT 同名 template 的幂等性。
5
+ //
6
+ // 改动 tier(参见 docs/governance/change-tiers.md):
7
+ // - 新增 template 文件:Tier 3
8
+ // - 修改既有 template 的 mapping:Tier 4(破坏性 - 可能 reindex)
9
+ // - 仅改 priority / 新增 alias:Tier 2
10
+ package main
11
+
12
+ import (
13
+ "bytes"
14
+ "context"
15
+ "fmt"
16
+ "log"
17
+ "os"
18
+ "path/filepath"
19
+ "sort"
20
+ "strings"
21
+ "time"
22
+
23
+ "github.com/elastic/go-elasticsearch/v8"
24
+ )
25
+
26
+ const templateDir = "db/index-templates"
27
+
28
+ func main() {
29
+ url := os.Getenv("ELASTICSEARCH_URL")
30
+ if url == "" {
31
+ url = "http://127.0.0.1:9200"
32
+ }
33
+
34
+ cfg := elasticsearch.Config{Addresses: []string{url}}
35
+ if u := os.Getenv("ELASTICSEARCH_USERNAME"); u != "" {
36
+ cfg.Username = u
37
+ cfg.Password = os.Getenv("ELASTICSEARCH_PASSWORD")
38
+ }
39
+
40
+ es, err := elasticsearch.NewClient(cfg)
41
+ if err != nil {
42
+ log.Fatalf("✗ es client: %v", err)
43
+ }
44
+
45
+ // 简易健康检查
46
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
47
+ defer cancel()
48
+ if _, err := es.Cluster.Health(es.Cluster.Health.WithContext(ctx)); err != nil {
49
+ log.Fatalf("✗ es health: %v", err)
50
+ }
51
+
52
+ files, err := filepath.Glob(filepath.Join(templateDir, "*.json"))
53
+ if err != nil {
54
+ log.Fatalf("✗ glob: %v", err)
55
+ }
56
+ sort.Strings(files)
57
+ if len(files) == 0 {
58
+ fmt.Println("no index templates found in", templateDir)
59
+ return
60
+ }
61
+
62
+ for _, path := range files {
63
+ body, err := os.ReadFile(path)
64
+ if err != nil {
65
+ log.Fatalf("✗ read %s: %v", path, err)
66
+ }
67
+ name := strings.TrimSuffix(filepath.Base(path), ".json")
68
+ res, err := es.Indices.PutIndexTemplate(name, bytes.NewReader(body))
69
+ if err != nil {
70
+ log.Fatalf("✗ apply %s: %v", name, err)
71
+ }
72
+ if res.IsError() {
73
+ log.Fatalf("✗ apply %s: %s", name, res.String())
74
+ }
75
+ _ = res.Body.Close()
76
+ fmt.Printf("✓ applied %s\n", name)
77
+ }
78
+
79
+ fmt.Printf("done; %d template(s) applied to %s\n", len(files), url)
80
+ }
@@ -0,0 +1,57 @@
1
+ # server/db/
2
+
3
+ Elasticsearch index-template 入口(Go 后端)。
4
+
5
+ > 跨项目的数据库归属规则见 [`docs/governance/database.md`](../../docs/governance/database.md)。
6
+
7
+ ## "Migration" 的语义
8
+
9
+ **Elasticsearch 没有事务性 DDL,本目录里的 JSON 不是迁移文件。**
10
+
11
+ 每个 `db/index-templates/*.json` 是一个 [Index Template v2](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html)。`cmd/apply-templates/main.go` 按文件名字典序遍历目录,对每个文件 PUT;ES 自身保证幂等。
12
+
13
+ ## 工具与版本
14
+
15
+ | 项 | 值 |
16
+ |---|---|
17
+ | Elasticsearch | 8.15+ |
18
+ | Go 客户端 | `github.com/elastic/go-elasticsearch/v8` `8.15.0` |
19
+
20
+ ## 目录布局
21
+
22
+ ```
23
+ server/
24
+ ├── go.mod
25
+ ├── Makefile # `make es-apply` 入口
26
+ ├── cmd/apply-templates/
27
+ │ └── main.go # 幂等 applier
28
+ └── db/
29
+ ├── README.md
30
+ └── index-templates/
31
+ └── <NNNNNN>_<description>.json
32
+ ```
33
+
34
+ ## 常用命令
35
+
36
+ ```bash
37
+ make es-apply # 把 db/index-templates/*.json 全部 PUT 一次
38
+ ```
39
+
40
+ 环境变量 `ELASTICSEARCH_URL`(默认 `http://127.0.0.1:9200`)、`ELASTICSEARCH_USERNAME` / `ELASTICSEARCH_PASSWORD`(可选)。
41
+
42
+ ## 改动分级(参见 `docs/governance/change-tiers.md`)
43
+
44
+ | 改动 | Tier |
45
+ |---|---|
46
+ | 新增 template 文件 | 3 |
47
+ | 修改既有 template 的 mapping 字段 | 4(破坏性 - 可能 reindex) |
48
+ | 仅改 priority / 新增 alias / 改 settings.refresh_interval | 2 |
49
+ | 删除 template | 4 |
50
+
51
+ ## 与 `contracts/asyncapi/` 的同步
52
+
53
+ 事件 ingest 的 schema 真值在 `contracts/asyncapi/asyncapi.yaml`;index-template 的 mapping 应当与之**对齐**。当前手动同步。
54
+
55
+ ## CI 行为
56
+
57
+ CI 起一个 ES 8 service container;`make es-apply` 失败 = CI 红。
@@ -0,0 +1,7 @@
1
+ module <%= it.options.projectName %>/server
2
+
3
+ go 1.22
4
+
5
+ require (
6
+ github.com/elastic/go-elasticsearch/v8 v8.15.0
7
+ )
@@ -0,0 +1,25 @@
1
+ {
2
+ "index_patterns": ["events-*"],
3
+ "priority": 100,
4
+ "template": {
5
+ "settings": {
6
+ "number_of_shards": 1,
7
+ "number_of_replicas": 1,
8
+ "refresh_interval": "5s"
9
+ },
10
+ "mappings": {
11
+ "properties": {
12
+ "@timestamp": { "type": "date" },
13
+ "tenant_id": { "type": "keyword" },
14
+ "event_type": { "type": "keyword" },
15
+ "user_id": { "type": "keyword" },
16
+ "payload": { "type": "object", "dynamic": true },
17
+ "trace_id": { "type": "keyword" }
18
+ }
19
+ }
20
+ },
21
+ "_meta": {
22
+ "description": "Sample event-stream template. Replace mappings with your domain fields.",
23
+ "managed_by": "@wneng/create-keel"
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+ name: db-go-elasticsearch
2
+ version: 1.0.0
3
+ appliesWhen:
4
+ backend: go
5
+ database: elasticsearch
6
+ priority: 40
7
+ files:
8
+ - from: files/go.mod
9
+ to: server/go.mod
10
+ render: true
11
+ - from: files/Makefile
12
+ to: server/Makefile
13
+ render: true
14
+ - from: files/apply_templates.go
15
+ to: server/cmd/apply-templates/main.go
16
+ render: false
17
+ - from: files/index-template-init.json
18
+ to: server/db/index-templates/000001_init.json
19
+ render: false
20
+ - from: files/db-README.md
21
+ to: server/db/README.md
22
+ render: true
@@ -0,0 +1,3 @@
1
+ -- 000001_init.down.sql — 回滚 000001_init.up.sql
2
+ DROP TABLE IF EXISTS users;
3
+ DROP TABLE IF EXISTS roles;
@@ -0,0 +1,35 @@
1
+ -- 000001_init.up.sql — 初始化迁移
2
+ --
3
+ -- 命名规则:<NNNNNN>_<description>.up.sql / .down.sql。
4
+ -- 已合入 main 不可修改;改 schema 写新文件 NNNNNN+1。
5
+
6
+ CREATE TABLE IF NOT EXISTS roles (
7
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
8
+ code VARCHAR(64) NOT NULL,
9
+ name VARCHAR(128) NOT NULL,
10
+ description TEXT NULL,
11
+ created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
12
+ updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
13
+ PRIMARY KEY (id),
14
+ UNIQUE KEY uk_roles_code (code)
15
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
16
+
17
+ CREATE TABLE IF NOT EXISTS users (
18
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
19
+ email VARCHAR(255) NOT NULL,
20
+ password_hash VARCHAR(255) NOT NULL,
21
+ display_name VARCHAR(128) NULL,
22
+ role_id BIGINT UNSIGNED NOT NULL,
23
+ created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
24
+ updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
25
+ deleted_at TIMESTAMP(6) NULL,
26
+ PRIMARY KEY (id),
27
+ UNIQUE KEY uk_users_email (email),
28
+ KEY idx_users_role_id (role_id),
29
+ KEY idx_users_deleted_at (deleted_at),
30
+ CONSTRAINT fk_users_role FOREIGN KEY (role_id) REFERENCES roles (id)
31
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
32
+
33
+ INSERT IGNORE INTO roles (code, name, description) VALUES
34
+ ('admin', '系统管理员', '可执行所有管理操作'),
35
+ ('user', '普通用户', '默认注册角色');
@@ -0,0 +1,33 @@
1
+ .PHONY: build test lint run db-up db-down db-status db-create
2
+
3
+ # Default DSN: localhost MySQL with the project name as DB. Production
4
+ # overrides via DATABASE_URL env var.
5
+ DATABASE_URL ?= mysql://root:@tcp(127.0.0.1:3306)/<%= it.options.projectName %>_dev?multiStatements=true
6
+
7
+ # golang-migrate CLI: install with `go install github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1`
8
+ MIGRATE := migrate -path db/migrations -database "$(DATABASE_URL)"
9
+
10
+ build:
11
+ go build -o ./bin/server ./cmd/server
12
+
13
+ run:
14
+ go run ./cmd/server
15
+
16
+ test:
17
+ go test ./...
18
+
19
+ lint:
20
+ golangci-lint run
21
+
22
+ db-up:
23
+ $(MIGRATE) up
24
+
25
+ db-down:
26
+ $(MIGRATE) down 1
27
+
28
+ db-status:
29
+ $(MIGRATE) version
30
+
31
+ # Usage: make db-create name=create_orders
32
+ db-create:
33
+ $(MIGRATE) create -ext sql -dir db/migrations -seq $(name)