fix(infra): 移除本地 S3 容器并改用 check:dev
ci / 质量门禁 (push) Canceled after 0s

背景:本地 PG/Redis 已按要求复用 docker-postgres-1 / docker-redis-1;
S3 容器按用户要求删除,后续接入公网自建 RustFS。

改动:
- 删除 infra/docker-compose.dev.yaml、infra/compose.dev.yaml
- 移除 compose:up / compose:down 脚本,改用 check:dev 预检本机 PG/Redis
- 新增 scripts/check-dev-deps.mjs;.env.example 的 S3_* 改为待填
- README / AGENTS / A0 报告同步本地依赖与对象存储说明

影响面:本地开发依赖与 CI 本地脚本、环境变量样例、文档。
回滚方式:恢复被删除的两个 compose 文件与 package.json 脚本即可。
验证:format:check、lint、pnpm ci:local(含 e2e 4/4)全部通过,用时 106.3s。
This commit is contained in:
Stev_Wang committed 2026-11-02 18:26:24 +08:00
1 parent 620cb93a46
commit 5628e158c0
9 files changed
+85 -57

No files matched your search

+4 -4
View File
@@ -1,7 +1,7 @@
# 灵犀 · 环境变量示例(入库,禁止写真实密钥) # 灵犀 · 环境变量示例(入库,禁止写真实密钥)
# 使用:cp .env.example .env.local(工程规范 09 ENV-09) # 使用:cp .env.example .env.local(工程规范 09 ENV-09)
# 注意:本地 PostgreSQL/Redis 复用本机已有 Docker 服务(127.0.0.1:5432 / 6379); # 注意:本地 PostgreSQL/Redis 复用本机已有 Docker 服务(127.0.0.1:5432 / 6379);
# S3 兼容服务由 infra/docker-compose.dev.yaml 提供(19000/19001)。 # 对象存储待接入公网 RustFS,待提供 endpoint 与凭据后填写。
# ---------- 运行环境 ---------- # ---------- 运行环境 ----------
NODE_ENV=development NODE_ENV=development
@@ -14,13 +14,13 @@ DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/lingxi?schema=public
REDIS_URL=redis://127.0.0.1:6379 REDIS_URL=redis://127.0.0.1:6379
# ---------- 对象存储(MinIO,S3 协议) ---------- # ---------- 对象存储(MinIO,S3 协议) ----------
S3_ENDPOINT=http://127.0.0.1:19000 S3_ENDPOINT=
S3_REGION=us-east-1 S3_REGION=us-east-1
S3_BUCKET_ASSETS=assets S3_BUCKET_ASSETS=assets
S3_BUCKET_RENDERS=renders S3_BUCKET_RENDERS=renders
S3_BUCKET_TEMP=temp S3_BUCKET_TEMP=temp
S3_ACCESS_KEY=lingxi S3_ACCESS_KEY=
S3_SECRET_KEY=lingxi_dev_password S3_SECRET_KEY=
# ---------- 认证(本地 dev 值,生产由密钥管理注入) ---------- # ---------- 认证(本地 dev 值,生产由密钥管理注入) ----------
JWT_SECRET=dev-only-jwt-secret-change-me-32chars JWT_SECRET=dev-only-jwt-secret-change-me-32chars
+2 -2
View File
@@ -110,12 +110,12 @@ docs/ PRD / 技术架构 / 立项建议 / 工程规范
| 场景 | 命令 | | 场景 | 命令 |
| --- | --- | | --- | --- |
| 依赖启动 | `pnpm compose:up` / `pnpm compose:down` | | 本地依赖 | `pnpm check:dev`(检查本机 PostgreSQL 5432 / Redis 6379,不启动容器) |
| 开发 | `pnpm dev` | | 开发 | `pnpm dev` |
| 构建 | `pnpm build` | | 构建 | `pnpm build` |
| 质量 | `pnpm lint` · `pnpm lint:fix` · `pnpm format` · `pnpm format:check` · `pnpm typecheck` | | 质量 | `pnpm lint` · `pnpm lint:fix` · `pnpm format` · `pnpm format:check` · `pnpm typecheck` |
| 测试 | `pnpm test` · `pnpm test:int` · `pnpm test:e2e` · `pnpm ci:local` | | 测试 | `pnpm test` · `pnpm test:int` · `pnpm test:e2e` · `pnpm ci:local` |
| 数据库 | `pnpm db:migrate` · `pnpm db:reset`(禁止 `docker compose down -v`) | | 数据库 | `pnpm db:migrate` · `pnpm db:reset`(禁止手工删库 / 删数据卷) |
| 契约 | `pnpm openapi:diff` | | 契约 | `pnpm openapi:diff` |
> 文档、脚本、CI 中出现的命令必须与上表一致,禁止自造别名。 > 文档、脚本、CI 中出现的命令必须与上表一致,禁止自造别名。
+2 -3
View File
@@ -73,15 +73,14 @@ docs/ PRD / 技术架构 / 开发计划 / 立项建议 / 工程规范
```bash ```bash
cp .env.example .env.local cp .env.example .env.local
cp .env.local .env # Prisma CLI 只读取 .env(两者均已 gitignore) cp .env.local .env # Prisma CLI 只读取 .env(两者均已 gitignore)
pnpm compose:up # 启动 S3 兼容存储;PG / Redis 复用本机已有 Docker pnpm check:dev # 检查本机 PostgreSQL / Redis 是否可用
pnpm db:migrate # 首次建表;后续由 Prisma 迁移管理 pnpm db:migrate # 首次建表;后续由 Prisma 迁移管理
pnpm db:seed # 幂等写入 system_configs 基线 pnpm db:seed # 幂等写入 system_configs 基线
pnpm dev # 并行启动 api(3000) / web(5173) / worker pnpm dev # 并行启动 api(3000) / web(5173) / worker
``` ```
- **本地数据库**:直接复用本机已有 Docker 服务 `docker-postgres-1`(PostgreSQL 18,`127.0.0.1:5432`),库名 `lingxi`,用户 `postgres`,密码 `postgres`;Redis 复用 `docker-redis-1`(`127.0.0.1:6379`)。请先确认这两个容器已启动,本仓库不会另起 PG/Redis。 - **本地数据库**:直接复用本机已有 Docker 服务 `docker-postgres-1`(PostgreSQL 18,`127.0.0.1:5432`),库名 `lingxi`,用户 `postgres`,密码 `postgres`;Redis 复用 `docker-redis-1`(`127.0.0.1:6379`)。请先确认这两个容器已启动,本仓库不会另起 PG/Redis。
- **对象存储端口**:S3 兼容服务由 `pnpm compose:up` 提供,宿主端口 `19000/19001`。 - **对象存储**:本地不再启动 S3 容器;待接入公网自建 RustFS,endpoint 与凭据确定后写入 `.env.local` 的 `S3_*`。
- **对象存储**:当前 Docker 环境拉取不到 `minio/minio` 镜像(registry denied、二进制 410),A0 的 compose 使用 `adobe/s3mock` 并提供 `assets / renders / temp / archive` 四个初始桶;CI 或生产接入真实 MinIO 时只需替换 `infra/docker-compose.dev.yaml` 的 `s3` 服务。
- **API 文档**:启动 api 后访问 `http://127.0.0.1:3000/api/docs`,OpenAPI JSON 在 `openapi/lingxi.openapi.json`。 - **API 文档**:启动 api 后访问 `http://127.0.0.1:3000/api/docs`,OpenAPI JSON 在 `openapi/lingxi.openapi.json`。
- **演示登录**:A0 尚未接鉴权(A1 实现),`VITE_PUBLIC_DEMO_SESSION=true` 时登录页使用本地演示会话进入工作台;A1 接入真实登录后删除。 - **演示登录**:A0 尚未接鉴权(A1 实现),`VITE_PUBLIC_DEMO_SESSION=true` 时登录页使用本地演示会话进入工作台;A1 接入真实登录后删除。
@@ -9,7 +9,7 @@
| WBS | 状态 | 落地内容 | | WBS | 状态 | 落地内容 |
| --- | --- | --- | | --- | --- | --- |
| A0-INF-01 Monorepo / 工具链 | 完成 | pnpm workspace + Turborepo;TS strict、ESLint 9 flat + Prettier + commitlint + lint-staged + husky;`.nvmrc` / `engines` / `packageManager` / preinstall 仅允许 pnpm;目录与 AGENTS.md §5 一致 | | A0-INF-01 Monorepo / 工具链 | 完成 | pnpm workspace + Turborepo;TS strict、ESLint 9 flat + Prettier + commitlint + lint-staged + husky;`.nvmrc` / `engines` / `packageManager` / preinstall 仅允许 pnpm;目录与 AGENTS.md §5 一致 |
| A0-INF-02 本地依赖与迁移种子 | 完成(种子范围受限,见 §4.4) | 复用本机 `docker-postgres-1`(PostgreSQL 18,5432)与 `docker-redis-1`(6379);`infra/docker-compose.dev.yaml` 仅管理 S3 兼容服务;`.env.example`;Prisma 初始化与首版迁移;`system_configs` 幂等种子 | | A0-INF-02 本地依赖与迁移种子 | 完成(种子范围受限,见 §4.4) | 复用本机 `docker-postgres-1`(PostgreSQL 18,5432)与 `docker-redis-1`(6379);`pnpm check:dev` 预检;`.env.example`;Prisma 初始化与首版迁移;`system_configs` 幂等种子 |
| A0-INF-03 CI 与质量门禁 | 完成 | `.gitea/workflows/ci.yml`:install → format → lint → typecheck → test → build → migrate → test:int → openapi:export/diff → e2e;新增 `pnpm ci:local` 在无 runner 时本地执行同一门禁;失败阻断 | | A0-INF-03 CI 与质量门禁 | 完成 | `.gitea/workflows/ci.yml`:install → format → lint → typecheck → test → build → migrate → test:int → openapi:export/diff → e2e;新增 `pnpm ci:local` 在无 runner 时本地执行同一门禁;失败阻断 |
| A0-BE-01 API 骨架 | 完成 | NestJS 11 + Fastify;Pino + `trace_id`;统一错误体 `{code,message,details,trace_id}`;`GET /health`;`/api/docs` 与 OpenAPI JSON;Zod 管道;CORS / Helmet | | A0-BE-01 API 骨架 | 完成 | NestJS 11 + Fastify;Pino + `trace_id`;统一错误体 `{code,message,details,trace_id}`;`GET /health`;`/api/docs` 与 OpenAPI JSON;Zod 管道;CORS / Helmet |
| A0-BE-02 Prisma 基线 | 完成 | `PrismaService` 封装;`outbox_events` / `idempotency_keys` / `system_configs`;ULID `char(26)`、软删/时间字段约定;首版迁移可重复执行 | | A0-BE-02 Prisma 基线 | 完成 | `PrismaService` 封装;`outbox_events` / `idempotency_keys` / `system_configs`;ULID `char(26)`、软删/时间字段约定;首版迁移可重复执行 |
@@ -26,7 +26,7 @@
- API:`apps/api/src/`(`bootstrap.ts`、`common/filters`、`common/pipes`、`modules/platform/health/`、`openapi/document.ts`、`scripts/export-openapi.ts`) - API:`apps/api/src/`(`bootstrap.ts`、`common/filters`、`common/pipes`、`modules/platform/health/`、`openapi/document.ts`、`scripts/export-openapi.ts`)
- 数据:`prisma/schema.prisma`、`prisma/migrations/20261102083445_init_platform_baseline/`、`prisma/seed.ts`、`prisma/SEED.md` - 数据:`prisma/schema.prisma`、`prisma/migrations/20261102083445_init_platform_baseline/`、`prisma/seed.ts`、`prisma/SEED.md`
- Web:`apps/web/src/`(`layout/`、`navigation/nav-config.ts`、`api/client.ts`、`pages/`、`stores/auth-store.ts`、`styles/`) - Web:`apps/web/src/`(`layout/`、`navigation/nav-config.ts`、`api/client.ts`、`pages/`、`stores/auth-store.ts`、`styles/`)
- 基础设施:`infra/docker-compose.dev.yaml`、`infra/compose.dev.yaml`、`.env.example` - 本地依赖:`scripts/check-dev-deps.mjs`、`.env.example`
- 契约快照:`openapi/lingxi.openapi.json`、`apps/web/src/api/generated/openapi.d.ts` - 契约快照:`openapi/lingxi.openapi.json`、`apps/web/src/api/generated/openapi.d.ts`
## 3. 本地门禁实测 ## 3. 本地门禁实测
@@ -41,7 +41,7 @@
| `pnpm test:int` | 通过(真实 PostgreSQL + Redis,`/health` 200) | | `pnpm test:int` | 通过(真实 PostgreSQL + Redis,`/health` 200) |
| `pnpm test:e2e` | 通过(4/4,Playwright/Chrome) | | `pnpm test:e2e` | 通过(4/4,Playwright/Chrome) |
| `pnpm openapi:export` + `pnpm openapi:diff` | 通过;快照含 20 个错误码,OpenAPI `3.1.0` | | `pnpm openapi:export` + `pnpm openapi:diff` | 通过;快照含 20 个错误码,OpenAPI `3.1.0` |
| `pnpm compose:up` | 通过;`lingxi-s3` healthy;PG / Redis 使用本机已运行容器,`/health` 与 `test:int` 验证连通 | | `pnpm check:dev` | 通过;PostgreSQL `5432/lingxi` 与 Redis `6379` 均可连接,`/health` 与 `test:int` 验证连通 |
| API 启动 | `/health` 200、`/api/docs` 200、`/api/openapi.json` 200 | | API 启动 | `/health` 200、`/api/docs` 200、`/api/openapi.json` 200 |
| Worker 启动 | 连接 Redis 成功,收到 SIGTERM 后正常退出 | | Worker 启动 | 连接 Redis 成功,收到 SIGTERM 后正常退出 |
| 种子幂等 | 连续执行两次,`system_configs` 保持 7 条 | | 种子幂等 | 连续执行两次,`system_configs` 保持 7 条 |
@@ -50,11 +50,11 @@
### 4.1 本地依赖来源(环境适配) ### 4.1 本地依赖来源(环境适配)
本地 PostgreSQL 与 Redis **复用本机已有 Docker 服务**,不另起容器:`docker-postgres-1`(PostgreSQL 18,`127.0.0.1:5432`,库 `lingxi`、用户 `postgres`、密码 `postgres`)与 `docker-redis-1`(`127.0.0.1:6379`)。`infra/docker-compose.dev.yaml` 仅保留 S3 兼容服务(宿主 `19000/19001`)。CI services 使用标准 5432/6379,凭据同为 `postgres/postgres`。 本地 PostgreSQL 与 Redis **复用本机已有 Docker 服务**,不另起容器:`docker-postgres-1`(PostgreSQL 18,`127.0.0.1:5432`,库 `lingxi`、用户 `postgres`、密码 `postgres`)与 `docker-redis-1`(`127.0.0.1:6379`)。仓库不包含任何数据库/缓存容器编排。CI services 使用标准 5432/6379,凭据同为 `postgres/postgres`。
### 4.2 对象存储镜像不可得(环境适配) ### 4.2 对象存储待接入公网 RustFS
当前 Docker 环境无法拉取 `minio/minio` 与 `minio/mc`(registry denied),`dl.min.io` 二进制返回 410。A0 使用 **`adobe/s3mock`** 并在启动时创建 `assets / renders / temp / archive` 四个桶,仅用于本地开发;CI / 生产接入真实 MinIO 时替换 compose 中 `s3` 服务即可,应用侧仍只依赖 S3 协议与环境变量。 本地 Docker 不提供 S3 服务,`minio/minio` 镜像在当前网络也不可拉取。A0 代码不依赖对象存储(`/health` 只检查 DB 与 Redis),`.env.example` 保留 `S3_*` 占位;后续由项目方提供公网自建 RustFS 的 endpoint 与凭据后填写,应用侧继续只依赖 S3 协议。
### 4.3 演示登录(A0 临时) ### 4.3 演示登录(A0 临时)
@@ -80,7 +80,7 @@ npm 上 `oasdiff` 为占位包(0.0.1-security),A0 暂用自研 `scripts/op
当前离线 Gitea 未部署 Actions runner,推送后远程 CI 会停留在 `queued`。A0 增加 `pnpm ci:local`:按 `.gitea/workflows/ci.yml` 的顺序在本地执行 compose、格式、Lint、类型、单测、构建、迁移与种子、集成、OpenAPI 契约与 e2e,全部通过才退出 0;runner 就绪后远程工作流可直接接管。 当前离线 Gitea 未部署 Actions runner,推送后远程 CI 会停留在 `queued`。A0 增加 `pnpm ci:local`:按 `.gitea/workflows/ci.yml` 的顺序在本地执行 compose、格式、Lint、类型、单测、构建、迁移与种子、集成、OpenAPI 契约与 e2e,全部通过才退出 0;runner 就绪后远程工作流可直接接管。
本地开发库为 `lingxi` / `postgres` / `postgres`,直接连接本机已有 PostgreSQL 容器;`.env.example` 与 CI services 保持一致。 本地开发库为 `lingxi` / `postgres` / `postgres`,直接连接本机已有 PostgreSQL 容器;`.env.example` 与 CI services 保持一致;S3 待 RustFS 信息后配置。
### 4.8 前端视觉方向 ### 4.8 前端视觉方向
@@ -98,7 +98,7 @@ npm 上 `oasdiff` 为占位包(0.0.1-security),A0 暂用自研 `scripts/op
1. **A1 必须移除演示登录**并接入真实 JWT / 刷新令牌 / 权限守卫。 1. **A1 必须移除演示登录**并接入真实 JWT / 刷新令牌 / 权限守卫。
2. 种子业务数据待 A1 / A3 / A4 / B0 表就绪后补齐(Q-A0-01)。 2. 种子业务数据待 A1 / A3 / A4 / B0 表就绪后补齐(Q-A0-01)。
3. MinIO 本地替代方案需在具备镜像的环境回归;生产 compose 与 CI 镜像另行处理。 3. 对象存储待接入公网 RustFS;接入前不使用本地 S3 容器。
4. Web 主包约 599 kB(Semi 全量 CSS 689 kB),A0 接受;M1 前做路由级拆分与 Semi 按需样式。 4. Web 主包约 599 kB(Semi 全量 CSS 689 kB),A0 接受;M1 前做路由级拆分与 Semi 按需样式。
5. 远程 CI 待 Gitea Actions runner 就绪后验证 services 与 Playwright 浏览器安装;当前以 `pnpm ci:local` 为准(已实测通过)。 5. 远程 CI 待 Gitea Actions runner 就绪后验证 services 与 Playwright 浏览器安装;当前以 `pnpm ci:local` 为准(已实测通过)。
-4
View File
@@ -1,4 +0,0 @@
# 技术架构 §4.1.1 引用的路径为 infra/compose.dev.yaml,这里做 include 兼容。
# 实际定义在 infra/docker-compose.dev.yaml(开发计划 §6.1 与工程规范 README 的命名)。
include:
- docker-compose.dev.yaml
-33
View File
@@ -1,33 +0,0 @@
# 灵犀 · 本地开发对象存储(S3 协议兼容)
# 用法:pnpm compose:up / pnpm compose:down
#
# 数据库与 Redis 复用本机已有 Docker 服务,不再由本文件创建:
# PostgreSQL → docker-postgres-1,127.0.0.1:5432,库 lingxi / 用户 postgres / 密码 postgres
# Redis → docker-redis-1,127.0.0.1:6379
# 本文件只管理 S3 兼容服务:2026-11 实测 minio/minio 与 minio/mc 镜像在当前网络不可拉取,
# 本地改用 Adobe S3Mock 提供 assets/renders/temp/archive 四个桶;接入真实 MinIO 时仅替换本服务。
name: lingxi-dev
services:
s3:
image: adobe/s3mock:latest
container_name: lingxi-s3
environment:
COM_ADOBE_TESTING_S3MOCK_STORE_INITIAL_BUCKETS: assets,renders,temp,archive
COM_ADOBE_TESTING_S3MOCK_STORE_REGION: us-east-1
COM_ADOBE_TESTING_S3MOCK_STORE_ROOT: /s3mockroot
COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT: 'true'
ports:
- '19000:9090'
- '19001:9191'
volumes:
- lingxi-s3-data:/s3mockroot
healthcheck:
test: ['CMD-SHELL', 'wget -q -O /dev/null http://127.0.0.1:9090/ || exit 1']
interval: 5s
timeout: 3s
retries: 20
start_period: 20s
volumes:
lingxi-s3-data:
+1 -2
View File
@@ -17,13 +17,12 @@
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check .", "format:check": "prettier --check .",
"check:dev": "node scripts/check-dev-deps.mjs",
"typecheck": "turbo run typecheck && tsc -p tsconfig.json --noEmit", "typecheck": "turbo run typecheck && tsc -p tsconfig.json --noEmit",
"test": "turbo run test", "test": "turbo run test",
"test:int": "turbo run test:int", "test:int": "turbo run test:int",
"test:e2e": "turbo run build && node scripts/e2e.mjs", "test:e2e": "turbo run build && node scripts/e2e.mjs",
"ci:local": "node scripts/ci-local.mjs", "ci:local": "node scripts/ci-local.mjs",
"compose:up": "docker compose -f infra/docker-compose.dev.yaml up -d --wait",
"compose:down": "docker compose -f infra/docker-compose.dev.yaml down",
"db:generate": "prisma generate", "db:generate": "prisma generate",
"db:migrate": "prisma migrate dev", "db:migrate": "prisma migrate dev",
"db:reset": "prisma migrate reset --force", "db:reset": "prisma migrate reset --force",
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env node
/**
* 检查本地已有依赖是否可连接:PostgreSQL(DATABASE_URL)与 Redis(REDIS_URL)。
* 仓库不再启动任何数据库/缓存容器;依赖由本机 Docker 服务提供。
*/
import { createConnection } from 'node:net';
import { loadEnvFile } from 'node:process';
// Node 24 支持 process.loadEnvFile;两个文件都不存在时忽略。
for (const file of ['.env.local', '.env']) {
try {
loadEnvFile(file);
} catch {
// 文件不存在时跳过。
}
}
const targets = [
{ name: 'PostgreSQL', value: process.env.DATABASE_URL, fallbackPort: 5432 },
{ name: 'Redis', value: process.env.REDIS_URL, fallbackPort: 6379 },
];
function parseTarget({ name, value, fallbackPort }) {
if (!value) {
throw new Error(`${name} 未配置连接串`);
}
const url = new URL(value);
return {
name,
host: url.hostname,
port: Number(url.port || fallbackPort),
database: url.pathname.replace(/^\//, '') || undefined,
};
}
function probe({ name, host, port, database }) {
return new Promise((resolve, reject) => {
const socket = createConnection({ host, port });
const timer = setTimeout(() => {
socket.destroy();
reject(new Error(`${name} 连接超时:${host}:${port}`));
}, 3_000);
socket.once('connect', () => {
clearTimeout(timer);
socket.end();
const suffix = database ? `/${database}` : '';
resolve(`${name} 可用:${host}:${port}${suffix}`);
});
socket.once('error', (error) => {
clearTimeout(timer);
reject(new Error(`${name} 不可用:${host}:${port}(${error.message})`));
});
});
}
try {
for (const target of targets) {
const info = await probe(parseTarget(target));
console.log(`✓ ${info}`);
}
console.log('本地依赖检查通过(PostgreSQL / Redis 使用本机已有 Docker 服务)。');
} catch (error) {
console.error(`✗ ${error instanceof Error ? error.message : String(error)}`);
console.error('请确认 docker-postgres-1(5432)与 docker-redis-1(6379)已启动。');
process.exit(1);
}
+1 -1
View File
@@ -9,7 +9,7 @@ const pnpm = process.env.npm_execpath ? [process.execPath, process.env.npm_execp
/** 每一步可包含多条 pnpm 命令,任一失败立即退出。 */ /** 每一步可包含多条 pnpm 命令,任一失败立即退出。 */
const steps = [ const steps = [
{ label: '启动本地依赖(PG / Redis / S3)', commands: [['compose:up']] }, { label: '检查本地依赖(PG / Redis)', commands: [['check:dev']] },
{ label: '格式化检查', commands: [['format:check']] }, { label: '格式化检查', commands: [['format:check']] },
{ label: 'Lint', commands: [['lint']] }, { label: 'Lint', commands: [['lint']] },
{ label: '类型检查', commands: [['typecheck']] }, { label: '类型检查', commands: [['typecheck']] },