fix(ci): 依据 Actions 真实日志修复三处失败(runner 无 pnpm / 门禁缺依赖 / 生产配置未入库)
上一轮已把 CI 跑起来并读到真实结果:7 个 job 中 Mini Program 与 Test Shell Guard 通过,Admin Web、Backend、UI Contracts 三个失败,migration 与 e2e 因依赖 backend 被跳过。本轮全部依据 Actions 原始日志定位,而不是本地推测 —— 三者都不是业务代码问题。 1. Admin Web Build:runner 已不再预装 pnpm 日志:Unable to locate executable file: pnpm runner 镜像(ubuntu-24.04 / 2.337.0)的 PATH 里没有 pnpm,而 setup-node 的 cache: 'pnpm' 在 restore 阶段就需要调用 pnpm 可执行文件,于是 job 在第一步失败、 后续步骤全部 skipped。同批的 miniapp job 没配 cache 所以正常通过,正好反向印证。 改为先用 corepack 启用并钉住 pnpm@9.15.9,store 缓存改由 actions/cache 手动配置 (此时 pnpm 才存在于 PATH)。 2. UI Contracts:门禁依赖没装 日志:Cannot find module '.../miniapp/node_modules/typescript/lib/typescript.js' phase04 守卫会用 TypeScript 编译器 API 解析小程序源码,但本 job 此前不装依赖。 本地能跑通只因为工作区早已装过;CI 的每个 job 都在独立 runner 上, miniapp job 安装的依赖不会共享过来。补上 npm install,并修正 workflow 中 「零 npm 依赖、故无需装依赖」的错误注释 —— 与实际不符的声明本身就是缺陷来源。 3. Backend:断言了一个永远不会入库的文件 日志:DeploymentTests.testDockerComposeProdExists 期望 true 实际 false infrastructure/docker-compose.prod.yml 被 .gitignore 列在 「Secrets and certificates」段而排除出版本控制,但这是**误分类**: 该文件不含任何明文密钥,敏感值全部走 ${VAR} 注入、db 密码走 Docker secrets, 文件头注释本身也写着「敏感信息通过 .env 文件注入,不进 Git」。 后果是该测试「本地恒绿(文件在工作区)、CI 恒红(checkout 拿不到)」, 且从本地永远发现不了。现修正 .gitignore 把它纳入版本控制; 真正的密钥仍由 secrets/、certs/、backups/ 等规则挡住。 修复后,此前因 backend 失败而被跳过的 migration 与 e2e 两个 job 也会真正跑起来。
This commit is contained in:
+40
-10
@@ -42,16 +42,36 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '20'
|
||||||
cache: 'pnpm'
|
# 这里不能写 cache: 'pnpm'。
|
||||||
cache-dependency-path: admin-web/pnpm-lock.yaml
|
# 2026-09-22 实测:runner 镜像(ubuntu-24.04 / runner 2.337.0)**已不再预装
|
||||||
|
# pnpm**,而 setup-node 的 cache 步骤在 restore 阶段就需要调用 pnpm 可执行文件,
|
||||||
|
# 于是直接报 "Unable to locate executable file: pnpm",整个 job 在第一步失败、
|
||||||
|
# 后续步骤全部 skipped —— 表现为 admin-web job 秒红,且与业务代码无关。
|
||||||
|
# (同批的 miniapp job 没配 cache 所以活了下来,正好反向印证了这一点。)
|
||||||
|
# 因此:先让 pnpm 存在于 PATH,缓存改为手动配置。
|
||||||
|
|
||||||
# 必须钉版本:runner 预装的 pnpm 会随 GitHub 镜像升级而漂移,而本仓库的
|
- name: 启用并锁定 pnpm
|
||||||
# pnpm-workspace.yaml 含有 onlyBuiltDependencies 等版本敏感配置,
|
# corepack 随 Node 分发,runner 上没有 pnpm 但有 corepack。
|
||||||
# 且 pnpm 9 与 10 对「是否默认放行依赖 build 脚本」的语义相反
|
# 钉 9.15.9 的理由:pnpm 9 与 10 对「是否默认放行依赖 build 脚本」语义相反,
|
||||||
# (esbuild / vue-demi 的 postinstall 若被静默阻断,install 能过、build 会红,
|
# 而 pnpm-workspace.yaml 里的 onlyBuiltDependencies 正是版本敏感项
|
||||||
# 且错误信息不指向真正原因)。钉死后 CI 语义不再随 runner 变化。
|
# (esbuild / vue-demi 的 postinstall 若被静默阻断,会出现 install 能过、
|
||||||
- name: 锁定 pnpm 版本(消除 runner 预装版本漂移)
|
# build 变红,且错误信息不指向真正原因)。钉死后不再随 runner 镜像升级而变化。
|
||||||
run: npm i -g pnpm@9.15.9
|
run: |
|
||||||
|
corepack enable pnpm
|
||||||
|
corepack prepare pnpm@9.15.9 --activate
|
||||||
|
pnpm --version
|
||||||
|
|
||||||
|
- name: 解析 pnpm store 路径
|
||||||
|
id: pnpm-store
|
||||||
|
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: 缓存 pnpm store
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-store.outputs.path }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('admin-web/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
working-directory: admin-web
|
working-directory: admin-web
|
||||||
@@ -162,7 +182,7 @@ jobs:
|
|||||||
# 第 16/17 项:`this._x()` 无定义(运行期 TypeError,曾致所有请求 Promise 永不 settle)
|
# 第 16/17 项:`this._x()` 无定义(运行期 TypeError,曾致所有请求 Promise 永不 settle)
|
||||||
# 与组件 WXSS 违规选择器(标签/ID/属性选择器);
|
# 与组件 WXSS 违规选择器(标签/ID/属性选择器);
|
||||||
# 第 18 项:api.js 请求层**行为**契约(桩 wx.request 真跑,非源码断言)。
|
# 第 18 项:api.js 请求层**行为**契约(桩 wx.request 真跑,非源码断言)。
|
||||||
# 守卫是纯 node 内建模块、零 npm 依赖,故无需装依赖;统一执行器负责逐个跑 + 汇总 + 非零退出,
|
# 统一执行器负责逐个跑 + 汇总 + 非零退出,
|
||||||
# 并校验守卫数量下限(防止守卫被删除/改名后仍然报绿)。
|
# 并校验守卫数量下限(防止守卫被删除/改名后仍然报绿)。
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -172,6 +192,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '20'
|
||||||
|
|
||||||
|
# 2026-09-22 修正:本 job 此前注释称「零 npm 依赖,故无需装依赖」,这个声明是错的。
|
||||||
|
# 至少 phase04 守卫会 import miniapp/node_modules/typescript/lib/typescript.js
|
||||||
|
# (用 TS 编译器 API 解析小程序源码)。本地能跑通只因为工作区早已装过 miniapp 依赖;
|
||||||
|
# CI 的每个 job 都在**独立 runner**上,miniapp job 装的东西不会共享过来,
|
||||||
|
# 于是这里直接 ERR_MODULE_NOT_FOUND。
|
||||||
|
# 「依赖声明与实际不符」正是本地绿 / CI 红最典型的一类来源。
|
||||||
|
- name: 安装 miniapp 依赖(phase04 守卫需要 typescript 编译器 API)
|
||||||
|
working-directory: miniapp
|
||||||
|
run: npm install
|
||||||
|
|
||||||
- name: 运行 UI 契约门禁
|
- name: 运行 UI 契约门禁
|
||||||
run: node scripts/run-ui-contracts.mjs
|
run: node scripts/run-ui-contracts.mjs
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -70,7 +70,17 @@ Thumbs.db
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Secrets and certificates
|
# Secrets and certificates
|
||||||
docker-compose.prod.yml
|
#
|
||||||
|
# 2026-09-22 修正:`docker-compose.prod.yml` 原先被列在本段(当作密钥)而排除出
|
||||||
|
# 版本控制,这是**误分类** —— 该文件不含任何明文密钥:所有敏感值都通过
|
||||||
|
# `${VAR:?请设置...}` 注入,db 密码走 Docker secrets(./secrets/db_password.txt),
|
||||||
|
# 文件头注释本身也写着「敏感信息通过 .env 文件注入,不进 Git」。
|
||||||
|
#
|
||||||
|
# 后果:backend 的 `DeploymentTests.testDockerComposeProdExists` 断言该文件存在,
|
||||||
|
# 于是「本地恒绿(文件在工作区)、CI 恒红(checkout 拿不到)」——
|
||||||
|
# 一次典型的本地绿/CI 红假绿,且从本地永远发现不了。
|
||||||
|
# 生产部署配置需要可复现、可评审,应与 staging 版一致纳入版本控制。
|
||||||
|
# 真正的密钥仍由下面三条挡住。
|
||||||
infrastructure/secrets/
|
infrastructure/secrets/
|
||||||
infrastructure/certs/
|
infrastructure/certs/
|
||||||
infrastructure/backups/
|
infrastructure/backups/
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# Production Docker Compose
|
||||||
|
# 生产环境配置,敏感信息通过 .env 文件注入,不进 Git
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: danyang-match-postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${DB_NAME:-danyang_match}
|
||||||
|
POSTGRES_USER: ${DB_USER:-danyang}
|
||||||
|
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
- ./backups:/backups
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-danyang} -d ${DB_NAME:-danyang_match}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- danyang-net
|
||||||
|
secrets:
|
||||||
|
- db_password
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: danyang-match-redis
|
||||||
|
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:?请设置REDIS_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:?请设置REDIS_PASSWORD}", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- danyang-net
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
container_name: danyang-match-minio
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?请设置MINIO_ACCESS_KEY}
|
||||||
|
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:?请设置MINIO_SECRET_KEY}
|
||||||
|
volumes:
|
||||||
|
- minio_data:/data
|
||||||
|
networks:
|
||||||
|
- danyang-net
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ../backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: danyang-match-backend
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILES_ACTIVE: docker,production
|
||||||
|
SPRING_CONFIG_IMPORT: configtree:/run/secrets/
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_NAME: ${DB_NAME:-danyang_match}
|
||||||
|
DB_USER: ${DB_USER:-danyang}
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
REDIS_PASSWORD: ${REDIS_PASSWORD:?请设置REDIS_PASSWORD}
|
||||||
|
MINIO_ENDPOINT: http://minio:9000
|
||||||
|
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?请设置MINIO_ACCESS_KEY}
|
||||||
|
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?请设置MINIO_SECRET_KEY}
|
||||||
|
JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET:?请设置JWT_ACCESS_SECRET}
|
||||||
|
JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:?请设置JWT_REFRESH_SECRET}
|
||||||
|
CHECKIN_TOKEN_SECRET: ${CHECKIN_TOKEN_SECRET:?请设置CHECKIN_TOKEN_SECRET}
|
||||||
|
ENCRYPTION_KEY: ${ENCRYPTION_KEY:?请设置ENCRYPTION_KEY}
|
||||||
|
WECHAT_APP_ID: ${WECHAT_APP_ID:?请设置WECHAT_APP_ID}
|
||||||
|
WECHAT_APP_SECRET: ${WECHAT_APP_SECRET:?请设置WECHAT_APP_SECRET}
|
||||||
|
VERIFICATION_PROVIDER: ${VERIFICATION_PROVIDER:?请设置VERIFICATION_PROVIDER}
|
||||||
|
SPRING_SECURITY_FALLBACK_PASSWORD: ${SPRING_SECURITY_FALLBACK_PASSWORD:?请设置SPRING_SECURITY_FALLBACK_PASSWORD}
|
||||||
|
FEATURES_APPEAL_ENABLED: ${FEATURES_APPEAL_ENABLED:-false}
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8080:8080"
|
||||||
|
networks:
|
||||||
|
- danyang-net
|
||||||
|
secrets:
|
||||||
|
- source: db_password
|
||||||
|
target: spring.datasource.password
|
||||||
|
restart: unless-stopped
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512M
|
||||||
|
reservations:
|
||||||
|
memory: 256M
|
||||||
|
|
||||||
|
admin-web:
|
||||||
|
build:
|
||||||
|
context: ../admin-web
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: danyang-match-admin
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
ports:
|
||||||
|
# 管理端端口统一 13520(与 admin-web/vite.config.ts 的开发端口一致,
|
||||||
|
# 由 ui-tests/ui-document-baseline-contracts.mjs 钉住三处一致)。
|
||||||
|
# 仍绑定 127.0.0.1:对外访问走 nginx 的 80/443 + /admin/,此处仅供本机直连排查。
|
||||||
|
- "127.0.0.1:13520:80"
|
||||||
|
networks:
|
||||||
|
- danyang-net
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: danyang-match-nginx
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
- admin-web
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx-production.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
- ./certs:/etc/nginx/certs:ro
|
||||||
|
networks:
|
||||||
|
- danyang-net
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
|
minio_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
danyang-net:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
db_password:
|
||||||
|
file: ./secrets/db_password.txt
|
||||||
Reference in New Issue
Block a user