@gadmin2n/schematics 0.0.88 → 0.0.90

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 (101) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +16 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.codegen +40 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +76 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.web +53 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +219 -33
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +250 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +4 -1
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/dev/postgres/init.sql +12 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +170 -0
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +254 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +7 -6
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/lib/page-helpers.ts +1 -1
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/prismaModels.ts +1 -1
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/agenda.seed.ts +39 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/audit.seed.ts +40 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/bootstrap.ts +56 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/canvas.seed.ts +39 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/{scripts/sync-data-mngt-pages.ts → seed/data-mngt.seed.ts} +36 -20
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/game.seed.ts +44 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +30 -6
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +130 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-event-trigger.ts +60 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +11 -25
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +108 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +1 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.controller.spec.ts +31 -2
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +31 -2
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +41 -57
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +31 -2
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +309 -1
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +31 -2
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +315 -1
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +31 -2
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +312 -2
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +31 -2
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +317 -1
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +31 -2
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +309 -1
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +31 -2
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +299 -1
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +31 -2
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +307 -1
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +31 -2
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +309 -1
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.spec.ts +205 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.ts +116 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.spec.ts +158 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +110 -1
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.spec.ts +79 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.ts +54 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +34 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +457 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +241 -4
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.spec.ts +34 -2
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.spec.ts +24 -30
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.spec.ts +34 -2
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.spec.ts +36 -36
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.spec.ts +34 -2
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.spec.ts +48 -24
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +312 -3
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +152 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +12 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +79 -0
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +270 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +1 -1
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +23 -0
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +70 -5
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +246 -90
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +20 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +96 -8
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/nginx.conf +74 -0
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/ElementInspector.tsx +18 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/promptGenerator.ts +1 -1
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/form.tsx +1 -1
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -3
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -3
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +4 -1
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +9 -0
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +156 -139
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +14 -2
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +62 -0
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/PublishModal.tsx +4 -6
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +18 -27
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasDefaults.ts +32 -11
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/demos.ts +48 -61
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas-page/index.tsx +3 -6
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +16 -16
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +28 -35
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +1 -0
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -3
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +1 -1
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -1
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +6 -0
  94. package/package.json +1 -1
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/.gitattributes +0 -2
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +0 -63
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/sync-resources.ts +0 -100
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +0 -302
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.spec.ts +0 -20
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/sql/create-event-trigger.sql +0 -87
  101. /package/dist/lib/application/files/gadmin2-game-angle-demo/{GRACEFUL-DEPLOYMENT.md → server/GRACEFUL-DEPLOYMENT.md} +0 -0
@@ -1,5 +1,4 @@
1
1
  .DS_Store
2
- web/
3
2
  **/node_modules
4
3
  **/dist
5
4
  **/generated
@@ -11,6 +10,11 @@ npm-debug.log
11
10
  .coverage
12
11
  .coverage.*
13
12
  .aws
13
+
14
+ .agent/
15
+ .claude/
16
+ .claude-internal/
17
+
14
18
  # local env files
15
19
  .env.local
16
20
  .env.*.local
@@ -30,4 +34,14 @@ yarn-error.log*
30
34
  *.sw?
31
35
 
32
36
  run.cmd
33
- todo.md
37
+ todo.md
38
+
39
+ sync-to-template.sh
40
+ CLAUDE.md
41
+
42
+ # Build/CI orchestration files — read directly by `docker build -f` / Jenkins / docker compose,
43
+ # 不需要进入任何镜像的 build context
44
+ Dockerfile.*
45
+ Jenkinsfile
46
+ docker-compose.yml
47
+ docker-compose.md
@@ -0,0 +1,40 @@
1
+ # syntax=docker/dockerfile:1
2
+ #
3
+ # 代码生成专用镜像(方案 C)。
4
+ # Jenkins 流水线在构建 web/server 业务镜像之前,先 docker build 本镜像;
5
+ # 后续 Dockerfile.web / Dockerfile.server 通过 ARG + FROM 引用本镜像,
6
+ # 直接 COPY --from=gen 拿到 gadmin2 g prisma 已生成的源码。
7
+ #
8
+ # 本镜像不会被推送到 registry,仅在 Jenkins agent 本地 docker daemon 中存活,
9
+ # 流水线末尾会清理。
10
+
11
+ FROM node:20-alpine
12
+
13
+ WORKDIR /app
14
+
15
+ # Prisma 在 alpine 上需要 openssl 才能加载原生引擎
16
+ RUN apk add --no-cache openssl
17
+
18
+ # 禁止 husky 在无 .git 环境下报错
19
+ ENV HUSKY=0
20
+
21
+ # 配置腾讯软件源
22
+ RUN yarn config set registry https://mirrors.cloud.tencent.com/npm/
23
+
24
+ # 安装 gadmin2 CLI
25
+ RUN yarn global add @gadmin2n/cli@latest
26
+
27
+ # 先复制 server 依赖描述文件,源码未变时此层 layer cache 可复用
28
+ # (prisma generators 是 server 的 devDependencies,必须先 yarn install)
29
+ COPY server/package.json server/yarn.lock ./server/
30
+
31
+ RUN --mount=type=cache,target=/root/.yarn \
32
+ cd server && yarn install --frozen-lockfile
33
+
34
+ # 复制全部 codegen 输入:上层 config/、server/(含 schema.prisma 与 gadmin-cli.json)、web/
35
+ COPY config/ ./config/
36
+ COPY server/ ./server/
37
+ COPY web/ ./web/
38
+
39
+ # 执行代码生成;产物会写入 server/src 与 web/src
40
+ RUN gadmin2 g prisma
@@ -0,0 +1,76 @@
1
+ # syntax=docker/dockerfile:1
2
+ #
3
+ # 通过 ARG CODEGEN_IMAGE + FROM ${CODEGEN_IMAGE} 引用 Jenkins 流水线先行构建的 codegen 镜像。
4
+ # 直接 COPY --from=gen /app/server,复用 codegen 已经装好的 server/node_modules
5
+ # (含 prisma client 与 gadmin2 generators),避免重复 yarn install。
6
+
7
+ ARG CODEGEN_IMAGE
8
+ FROM ${CODEGEN_IMAGE} AS gen
9
+
10
+ # ─── 阶段 1:构建 ────────────────────────────────────────────
11
+ FROM node:20-alpine AS builder
12
+
13
+ WORKDIR /app
14
+
15
+ # Prisma 在 alpine 上需要 openssl
16
+ RUN apk add --no-cache openssl
17
+
18
+ # 禁止 husky 在无 .git 环境下报错
19
+ ENV HUSKY=0
20
+
21
+ # 从 codegen 镜像取完整 server/(含已生成代码 + 已安装的 node_modules)
22
+ COPY --from=gen /app/server ./server
23
+
24
+ # 编译
25
+ RUN cd server && yarn build
26
+
27
+ # ─── 阶段 2:运行 ────────────────────────────────────────────
28
+ FROM node:20-alpine AS runner
29
+
30
+ # Prisma 运行时需要 openssl;中国项目默认时区上海
31
+ RUN apk add --no-cache openssl tzdata && \
32
+ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
33
+ echo "Asia/Shanghai" > /etc/timezone
34
+ ENV TZ=Asia/Shanghai
35
+
36
+ WORKDIR /app/server
37
+
38
+ STOPSIGNAL SIGQUIT
39
+
40
+ # 禁止 husky 在无 .git 环境下报错;显式声明生产环境(NestJS / 第三方库行为优化)
41
+ ENV HUSKY=0 \
42
+ NODE_ENV=production
43
+
44
+ # 从 builder 复制依赖描述文件(其源头是 codegen 镜像)
45
+ COPY --from=builder --chown=node:node /app/server/package.json /app/server/yarn.lock ./
46
+
47
+ # 只安装 production 依赖,大幅减少镜像体积
48
+ RUN --mount=type=cache,target=/root/.yarn \
49
+ yarn install --production --frozen-lockfile
50
+
51
+ # 从构建阶段复制 Prisma 生成的 Client(含平台原生二进制)
52
+ COPY --from=builder --chown=node:node /app/server/node_modules/.prisma ./node_modules/.prisma
53
+
54
+ # 从构建阶段复制编译产物
55
+ COPY --from=builder --chown=node:node /app/server/dist ./dist
56
+
57
+ # 复制 Prisma schema(feature 分支部署时 prisma db push 需要)
58
+ COPY --from=builder --chown=node:node /app/server/prisma ./prisma
59
+
60
+ # 复制启动脚本和数据库迁移脚本(codegen 不会修改这些文件,但统一从 builder 拿,避免 COPY 源混用)
61
+ COPY --from=builder --chown=node:node /app/server/start-prod.sh /app/server/migrate-between-pg-schemas.js ./
62
+ RUN chmod +x start-prod.sh
63
+
64
+ # 复制环境变量配置(作为默认值,K8s 注入的同名变量优先级更高)
65
+ COPY --from=builder --chown=node:node /app/server/.env ./.env
66
+
67
+ # 切换到非 root 用户(node 镜像内置 uid/gid 1000)
68
+ USER node
69
+
70
+ EXPOSE 8000
71
+
72
+ # 容器健康检查:调用 NestJS 的 /health/live 端点
73
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
74
+ CMD wget -qO- http://127.0.0.1:8000/health/live || exit 1
75
+
76
+ CMD ["yarn", "start:prod"]
@@ -0,0 +1,53 @@
1
+ # syntax=docker/dockerfile:1
2
+ #
3
+ # 通过 ARG CODEGEN_IMAGE + FROM ${CODEGEN_IMAGE} 引用 Jenkins 流水线先行构建的 codegen 镜像,
4
+ # 直接 COPY --from=gen 拿到 gadmin2 g prisma 已生成的 web/ 源码。
5
+ # 本镜像不再安装 gadmin2 CLI,构建上下文也不再需要 web/ 源码。
6
+ #
7
+ # 本地手工调试用:先 `docker build -f Dockerfile.codegen -t gadmin2-codegen:dev .`
8
+ # 再 `docker build -f Dockerfile.web --build-arg CODEGEN_IMAGE=gadmin2-codegen:dev -t gadmin2-web:dev .`
9
+
10
+ ARG CODEGEN_IMAGE
11
+ FROM ${CODEGEN_IMAGE} AS gen
12
+
13
+ # ─── 阶段 1:构建 ────────────────────────────────────────────
14
+ FROM node:20-alpine AS builder
15
+
16
+ WORKDIR /app
17
+
18
+ # 禁止 husky 在无 .git 环境下报错
19
+ ENV HUSKY=0
20
+
21
+ # 配置腾讯软件源
22
+ RUN yarn config set registry https://mirrors.cloud.tencent.com/npm/
23
+
24
+ # 从 codegen 镜像取完整 web/(含生成代码、不含 node_modules)
25
+ COPY --from=gen /app/web ./web
26
+
27
+ # 移除 yarn.lock 中的 resolved 地址,强制使用新的 registry
28
+ RUN sed -i 's|https://registry.npmmirror.com/|https://mirrors.cloud.tencent.com/npm/|g' web/yarn.lock
29
+
30
+ # 安装依赖
31
+ RUN --mount=type=cache,target=/root/.yarn \
32
+ cd web && yarn install --frozen-lockfile
33
+
34
+ # 声明构建参数
35
+ ARG NODE_ENV=production
36
+ ARG DEPLOY_NAME
37
+ # 设置环境变量,让构建命令可以读取到(src 中通过 import.meta.env.VITE_DEPLOY_NAME 使用)
38
+ ENV VITE_DEPLOY_NAME=${DEPLOY_NAME}
39
+
40
+ # 编译
41
+ RUN --mount=type=cache,target=/root/.yarn \
42
+ cd web && yarn build --mode ${NODE_ENV}
43
+
44
+ # ─── 阶段 2:运行 ────────────────────────────────────────────
45
+ FROM nginx:alpine
46
+
47
+ # nginx 配置和构建产物均来自 builder 阶段(其源头是 codegen 镜像)
48
+ COPY --from=builder /app/web/nginx.conf /etc/nginx/nginx.conf
49
+ COPY --from=builder /app/web/dist /var/www/html
50
+
51
+ EXPOSE 80
52
+
53
+ CMD ["nginx", "-g", "daemon off;"]
@@ -1,70 +1,256 @@
1
1
  // see https://www.jenkins.io/doc/book/pipeline/syntax/
2
+ //
3
+ // Monorepo pipeline for gadmin-test: 3 deployable modules (web / server / worker).
4
+ // 路径触发:哪个模块代码有改动就构建哪个,纯文档改动跳过所有构建。
5
+ // 风格沿用 erp-frontend / erp-backend 的 Jenkinsfile(声明式、Coding CI 环境变量、Spinnaker webhook)。
6
+
2
7
  pipeline {
3
8
  agent any
9
+
4
10
  environment {
5
11
  CODING_DOCKER_REG_HOST = "${CCI_CURRENT_TEAM}-${CCI_DOCKER_REGISTRY_DOMAIN}"
6
12
  IMAGE_VERSION = "build-${env.CI_BUILD_NUMBER}"
7
- CODING_DOCKER_IMAGE_NAME = "${PROJECT_NAME.toLowerCase()}/${env.BRANCH_NAME == 'test'? 'test' : 'prod'}/${CCI_JOB_NAME}"
8
- IMAGE_FULL_NAME = "${CODING_DOCKER_REG_HOST}/${env.CODING_DOCKER_IMAGE_NAME}:${IMAGE_VERSION}"
13
+ DEPLOY_ENV = "${env.BRANCH_NAME == 'test' ? 'test' : 'prod'}"
14
+ IMAGE_NAME_PREFIX = "${PROJECT_NAME.toLowerCase()}/${DEPLOY_ENV}"
15
+
16
+ // 镜像名后缀(Coding CI 通常用 ${CCI_JOB_NAME},但 monorepo 需要 3 个不同名字,这里固定写死)
17
+ // TODO: 确认这三个 image name 与 Spinnaker 应用 / K8s deployment 命名一致
18
+ WEB_IMAGE_SHORT = "gadmin2-web"
19
+ SERVER_IMAGE_SHORT = "gadmin2-server"
20
+ WORKER_IMAGE_SHORT = "gadmin2-worker"
21
+
22
+ // 代码生成中间镜像(不推送到 registry,仅本地 docker daemon 临时存活)
23
+ CODEGEN_IMAGE = "gadmin2-codegen:${IMAGE_VERSION}"
24
+ }
25
+
26
+ options {
27
+ timeout(time: 30, unit: 'MINUTES')
9
28
  }
10
29
 
11
30
  stages {
31
+
12
32
  stage('检出Git') {
13
- when {
14
- anyOf {
15
- branch 'test';
16
- branch 'master'
17
- }
18
- }
33
+ when { anyOf { branch 'test'; branch 'master' } }
19
34
  steps {
20
- checkout([$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]], userRemoteConfigs: [[url: env.GIT_REPO_URL, credentialsId: env.CREDENTIALS_ID]]])
35
+ checkout([$class: 'GitSCM',
36
+ branches: [[name: env.GIT_BUILD_REF]],
37
+ userRemoteConfigs: [[url: env.GIT_REPO_URL, credentialsId: env.CREDENTIALS_ID]]
38
+ ])
21
39
  }
22
40
  }
23
41
 
24
- stage('构建推送镜像') {
25
- when {
26
- anyOf {
27
- branch 'test';
28
- branch 'master'
29
- }
30
- }
42
+ stage('检测变更模块') {
43
+ when { anyOf { branch 'test'; branch 'master' } }
31
44
  steps {
32
45
  script {
33
- docker.withRegistry(
34
- "${CCI_CURRENT_WEB_PROTOCOL}://${env.CODING_DOCKER_REG_HOST}",
35
- "${env.CODING_ARTIFACTS_CREDENTIALS_ID}"
36
- ) {
37
- docker.build("${env.CODING_DOCKER_IMAGE_NAME}:${IMAGE_VERSION}").push()
38
- sh "docker image rm ${env.IMAGE_FULL_NAME}"
46
+ // 对比基准:优先使用 Jenkins git plugin 的 GIT_PREVIOUS_SUCCESSFUL_COMMIT
47
+ // 兜底:基准缺失或 commit 在当前 clone 中找不到 → 全量构建(保险策略)
48
+ def base = env.GIT_PREVIOUS_SUCCESSFUL_COMMIT ?: ''
49
+ def baseValid = false
50
+ if (base) {
51
+ // shallow clone 时旧 commit 可能不在本地,检测一下
52
+ def rc = sh(script: "git cat-file -e ${base}^{commit} 2>/dev/null", returnStatus: true)
53
+ baseValid = (rc == 0)
54
+ if (!baseValid) {
55
+ echo "GIT_PREVIOUS_SUCCESSFUL_COMMIT=${base} 在当前 clone 中不可达(可能是 shallow clone),全量构建"
56
+ }
57
+ } else {
58
+ echo "无 GIT_PREVIOUS_SUCCESSFUL_COMMIT(首次构建?),全量构建"
59
+ }
60
+
61
+ def changedFiles = []
62
+ if (baseValid) {
63
+ def diffOut = sh(script: "git diff --name-only ${base} HEAD", returnStdout: true).trim()
64
+ changedFiles = diffOut ? diffOut.split('\n') as List : []
65
+ echo "对比基准 ${base}..HEAD,变更文件 ${changedFiles.size()} 个"
39
66
  }
67
+
68
+ // 路径规则
69
+ def matches = { List files, String prefix -> files.any { it.startsWith(prefix) } }
70
+ def isDocOnly = { String f ->
71
+ f.startsWith('docs/') || f == 'CLAUDE.md' || f == 'readme.md' ||
72
+ f == '.gitignore' || f == 'Jenkinsfile' || f == 'sync-to-template.sh' ||
73
+ f == 'sync-to-template.md' || f.startsWith('.claude/')
74
+ }
75
+
76
+ def buildWeb = false
77
+ def buildServer = false
78
+ def buildWorker = false
79
+
80
+ if (!baseValid) {
81
+ buildWeb = true; buildServer = true; buildWorker = true
82
+ } else if (changedFiles.isEmpty()) {
83
+ echo '没有文件变更,全部跳过'
84
+ } else {
85
+ // 纯文档变更 → 全部跳过
86
+ def nonDoc = changedFiles.findAll { !isDocOnly(it) }
87
+ if (nonDoc.isEmpty()) {
88
+ echo '仅文档/配置文件变更,全部跳过'
89
+ } else {
90
+ // 路径触发规则:
91
+ // web/** → web
92
+ // config/ui/** → web(前端 CRUD 代码由 config/ui 生成)
93
+ // config/prisma/** → server + web(后端模型由 prisma 生成;前端 enums 通过 yarn sync:code 与后端同步)
94
+ // server/** → server
95
+ // temporal/worker/**→ worker
96
+ buildWeb = matches(nonDoc, 'web/') || matches(nonDoc, 'config/ui/') || matches(nonDoc, 'config/prisma/')
97
+ buildServer = matches(nonDoc, 'server/') || matches(nonDoc, 'config/prisma/')
98
+ buildWorker = matches(nonDoc, 'temporal/worker/')
99
+ // 注:本项目根目录没有共享的 package.json / tsconfig.json,
100
+ // 各模块依赖独立在 web/ server/ temporal/worker/ 下,无需"全量兜底"逻辑。
101
+ }
102
+ }
103
+
104
+ // 用 env 暴露给后续 stage(when 表达式只能读 env / params)
105
+ env.BUILD_WEB = buildWeb.toString()
106
+ env.BUILD_SERVER = buildServer.toString()
107
+ env.BUILD_WORKER = buildWorker.toString()
108
+
109
+ echo "buildWeb=${buildWeb}, buildServer=${buildServer}, buildWorker=${buildWorker}"
40
110
  }
41
111
  }
42
112
  }
43
113
 
44
- stage('测试环境部署') {
114
+ stage('代码生成') {
115
+ // 仅当需要构建 web 或 server 时执行;worker 模块与 gadmin2 codegen 无关
45
116
  when {
46
117
  anyOf {
47
- branch 'test'
118
+ environment name: 'BUILD_WEB', value: 'true'
119
+ environment name: 'BUILD_SERVER', value: 'true'
48
120
  }
49
121
  }
50
122
  steps {
51
- sh 'curl https://api-spinnaker.devops.intlgame.com/webhooks/webhook/oit-erp-backend-test -X POST -H "content-type: application/json" -d \'{"parameters": {"image_tag": "\'"${IMAGE_VERSION}"\'"}}\''
52
- echo '部署已经触发,可以在Spinnaker查看状态: https://api-spinnaker.devops.intlgame.com'
123
+ script {
124
+ // 构建一个本地代码生成镜像:内部装好 gadmin2 CLI + server devDeps,
125
+ // 并已执行 `gadmin2 g prisma`,产出包含生成代码的 /app/web 与 /app/server。
126
+ //
127
+ // 关键点:
128
+ // - 镜像不 push 到 registry,仅在本 Jenkins agent 的 docker daemon 中存活
129
+ // - 后续 Dockerfile.web / Dockerfile.server 通过 ARG CODEGEN_IMAGE + FROM ${CODEGEN_IMAGE}
130
+ // 直接引用本地镜像,无需 registry 周转
131
+ // - 流水线末尾会清理本镜像
132
+ docker.build(env.CODEGEN_IMAGE, "-f Dockerfile.codegen .")
133
+ }
53
134
  }
54
135
  }
55
136
 
56
- stage('正式环境自动部署') {
57
- when {
58
- anyOf {
59
- branch 'master'
137
+ stage('并行构建推送镜像') {
138
+ when { anyOf { branch 'test'; branch 'master' } }
139
+ parallel {
140
+
141
+ stage('构建 Web') {
142
+ when { environment name: 'BUILD_WEB', value: 'true' }
143
+ steps {
144
+ script {
145
+ def imageName = "${env.IMAGE_NAME_PREFIX}/${env.WEB_IMAGE_SHORT}"
146
+ def imageFull = "${env.CODING_DOCKER_REG_HOST}/${imageName}:${env.IMAGE_VERSION}"
147
+ def buildArg = (env.BRANCH_NAME == 'test') ? '--build-arg NODE_ENV=test' : '--build-arg NODE_ENV=production'
148
+
149
+ docker.withRegistry(
150
+ "${CCI_CURRENT_WEB_PROTOCOL}://${env.CODING_DOCKER_REG_HOST}",
151
+ "${env.CODING_ARTIFACTS_CREDENTIALS_ID}"
152
+ ) {
153
+ // FROM ${CODEGEN_IMAGE} 在本地 docker daemon 解析,不会触发 registry 拉取
154
+ docker.build(
155
+ "${imageName}:${env.IMAGE_VERSION}",
156
+ "${buildArg} --build-arg CODEGEN_IMAGE=${env.CODEGEN_IMAGE} -f Dockerfile.web ."
157
+ ).push()
158
+ sh "docker image rm ${imageFull}"
159
+ }
160
+ env.WEB_IMAGE_FULL = imageFull
161
+ }
162
+ }
163
+ }
164
+
165
+ stage('构建 Server') {
166
+ when { environment name: 'BUILD_SERVER', value: 'true' }
167
+ steps {
168
+ script {
169
+ def imageName = "${env.IMAGE_NAME_PREFIX}/${env.SERVER_IMAGE_SHORT}"
170
+ def imageFull = "${env.CODING_DOCKER_REG_HOST}/${imageName}:${env.IMAGE_VERSION}"
171
+
172
+ docker.withRegistry(
173
+ "${CCI_CURRENT_WEB_PROTOCOL}://${env.CODING_DOCKER_REG_HOST}",
174
+ "${env.CODING_ARTIFACTS_CREDENTIALS_ID}"
175
+ ) {
176
+ // FROM ${CODEGEN_IMAGE} 在本地 docker daemon 解析,不会触发 registry 拉取
177
+ docker.build(
178
+ "${imageName}:${env.IMAGE_VERSION}",
179
+ "--build-arg CODEGEN_IMAGE=${env.CODEGEN_IMAGE} -f Dockerfile.server ."
180
+ ).push()
181
+ sh "docker image rm ${imageFull}"
182
+ }
183
+ env.SERVER_IMAGE_FULL = imageFull
184
+ }
185
+ }
186
+ }
187
+
188
+ stage('构建 Worker') {
189
+ when { environment name: 'BUILD_WORKER', value: 'true' }
190
+ steps {
191
+ script {
192
+ def imageName = "${env.IMAGE_NAME_PREFIX}/${env.WORKER_IMAGE_SHORT}"
193
+ def imageFull = "${env.CODING_DOCKER_REG_HOST}/${imageName}:${env.IMAGE_VERSION}"
194
+
195
+ docker.withRegistry(
196
+ "${CCI_CURRENT_WEB_PROTOCOL}://${env.CODING_DOCKER_REG_HOST}",
197
+ "${env.CODING_ARTIFACTS_CREDENTIALS_ID}"
198
+ ) {
199
+ // worker 的构建上下文是 temporal/worker/
200
+ docker.build("${imageName}:${env.IMAGE_VERSION}", "-f temporal/worker/Dockerfile temporal/worker").push()
201
+ sh "docker image rm ${imageFull}"
202
+ }
203
+ env.WORKER_IMAGE_FULL = imageFull
204
+ }
205
+ }
60
206
  }
61
207
  }
208
+ }
209
+
210
+ stage('触发 Spinnaker 部署') {
211
+ when { anyOf { branch 'test'; branch 'master' } }
62
212
  steps {
63
213
  script {
64
- sh 'curl https://api-spinnaker.devops.intlgame.com/webhooks/webhook/oit-erp-backend-prod -X POST -H "content-type: application/json" -d \'{"parameters": {"image_tag": "\'"${IMAGE_VERSION}"\'"}}\''
65
- echo '部署已经触发,可以在Spinnaker查看状态: https://api-spinnaker.devops.intlgame.com'
214
+ // 任一模块构建过就触发对应的 Spinnaker webhook
215
+ // TODO: 确认 Spinnaker 上是按"每模块一个 application"还是"一个 application 接收所有模块"
216
+ // 下面按"每模块一个 application"实现,与 erp-frontend / erp-backend 保持一致
217
+ def env_ = env.DEPLOY_ENV // test 或 prod
218
+ def webhookBase = 'https://api-spinnaker.devops.intlgame.com/webhooks/webhook'
219
+
220
+ def triggerOne = { String moduleSlug, String imageFull ->
221
+ // TODO: 确认 webhook 路径名 oit-gadmin2-${moduleSlug}-${env_} 已在 Spinnaker 注册
222
+ def url = "${webhookBase}/oit-gadmin2-${moduleSlug}-${env_}"
223
+ def payload = """{"parameters": {"image_tag": "${env.IMAGE_VERSION}", "repository": "${imageFull}"}}"""
224
+ sh "curl -sf '${url}' -X POST -H 'content-type: application/json' -d '${payload}'"
225
+ echo "已触发 ${moduleSlug} 部署:${url}"
226
+ }
227
+
228
+ if (env.BUILD_WEB == 'true' && env.WEB_IMAGE_FULL) {
229
+ triggerOne('web', env.WEB_IMAGE_FULL)
230
+ }
231
+ if (env.BUILD_SERVER == 'true' && env.SERVER_IMAGE_FULL) {
232
+ triggerOne('server', env.SERVER_IMAGE_FULL)
233
+ }
234
+ if (env.BUILD_WORKER == 'true' && env.WORKER_IMAGE_FULL) {
235
+ triggerOne('worker', env.WORKER_IMAGE_FULL)
236
+ }
237
+
238
+ if (env.BUILD_WEB != 'true' && env.BUILD_SERVER != 'true' && env.BUILD_WORKER != 'true') {
239
+ echo '本次构建无模块更新,跳过 Spinnaker 触发'
240
+ } else {
241
+ echo '部署已经触发,可以在 Spinnaker 查看状态: https://api-spinnaker.devops.intlgame.com'
242
+ }
66
243
  }
67
244
  }
68
245
  }
69
246
  }
70
- }
247
+
248
+ post {
249
+ // 清理本地 codegen 中间镜像(不影响 web/server 已 push 的产物镜像)
250
+ always {
251
+ script {
252
+ sh "docker image rm ${env.CODEGEN_IMAGE} || true"
253
+ }
254
+ }
255
+ }
256
+ }