diff --git a/.env.example b/.env.example index 1a017ef..de0e266 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ # 灵犀 · 环境变量示例(入库,禁止写真实密钥) # 使用:cp .env.example .env.local(工程规范 09 ENV-09) # 注意:本地 PostgreSQL/Redis 复用本机已有 Docker 服务(127.0.0.1:5432 / 6379); -# S3 兼容服务由 infra/docker-compose.dev.yaml 提供(19000/19001)。 +# 对象存储待接入公网 RustFS,待提供 endpoint 与凭据后填写。 # ---------- 运行环境 ---------- 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 # ---------- 对象存储(MinIO,S3 协议) ---------- -S3_ENDPOINT=http://127.0.0.1:19000 +S3_ENDPOINT= S3_REGION=us-east-1 S3_BUCKET_ASSETS=assets S3_BUCKET_RENDERS=renders S3_BUCKET_TEMP=temp -S3_ACCESS_KEY=lingxi -S3_SECRET_KEY=lingxi_dev_password +S3_ACCESS_KEY= +S3_SECRET_KEY= # ---------- 认证(本地 dev 值,生产由密钥管理注入) ---------- JWT_SECRET=dev-only-jwt-secret-change-me-32chars diff --git a/AGENTS.md b/AGENTS.md index 5615aac..da3758e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,12 +110,12 @@ docs/ PRD / 技术架构 / 立项建议 / 工程规范 | 场景 | 命令 | | --- | --- | -| 依赖启动 | `pnpm compose:up` / `pnpm compose:down` | +| 本地依赖 | `pnpm check:dev`(检查本机 PostgreSQL 5432 / Redis 6379,不启动容器) | | 开发 | `pnpm dev` | | 构建 | `pnpm build` | | 质量 | `pnpm lint` · `pnpm lint:fix` · `pnpm format` · `pnpm format:check` · `pnpm typecheck` | | 测试 | `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` | > 文档、脚本、CI 中出现的命令必须与上表一致,禁止自造别名。 diff --git a/README.md b/README.md index 2f21b45..db7ad91 100644 --- a/README.md +++ b/README.md @@ -73,15 +73,14 @@ docs/ PRD / 技术架构 / 开发计划 / 立项建议 / 工程规范 ```bash cp .env.example .env.local 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:seed # 幂等写入 system_configs 基线 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。 -- **对象存储端口**:S3 兼容服务由 `pnpm compose:up` 提供,宿主端口 `19000/19001`。 -- **对象存储**:当前 Docker 环境拉取不到 `minio/minio` 镜像(registry denied、二进制 410),A0 的 compose 使用 `adobe/s3mock` 并提供 `assets / renders / temp / archive` 四个初始桶;CI 或生产接入真实 MinIO 时只需替换 `infra/docker-compose.dev.yaml` 的 `s3` 服务。 +- **对象存储**:本地不再启动 S3 容器;待接入公网自建 RustFS,endpoint 与凭据确定后写入 `.env.local` 的 `S3_*`。 - **API 文档**:启动 api 后访问 `http://127.0.0.1:3000/api/docs`,OpenAPI JSON 在 `openapi/lingxi.openapi.json`。 - **演示登录**:A0 尚未接鉴权(A1 实现),`VITE_PUBLIC_DEMO_SESSION=true` 时登录页使用本地演示会话进入工作台;A1 接入真实登录后删除。 diff --git a/docs/开发计划/灵犀_A0工程骨架与契约基线_实施报告_V0.1.md b/docs/开发计划/灵犀_A0工程骨架与契约基线_实施报告_V0.1.md index 10b8545..57c3757 100644 --- a/docs/开发计划/灵犀_A0工程骨架与契约基线_实施报告_V0.1.md +++ b/docs/开发计划/灵犀_A0工程骨架与契约基线_实施报告_V0.1.md @@ -9,7 +9,7 @@ | 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-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-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)`、软删/时间字段约定;首版迁移可重复执行 | @@ -26,7 +26,7 @@ - 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` - 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` ## 3. 本地门禁实测 @@ -41,7 +41,7 @@ | `pnpm test:int` | 通过(真实 PostgreSQL + Redis,`/health` 200) | | `pnpm test:e2e` | 通过(4/4,Playwright/Chrome) | | `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 | | Worker 启动 | 连接 Redis 成功,收到 SIGTERM 后正常退出 | | 种子幂等 | 连续执行两次,`system_configs` 保持 7 条 | @@ -50,11 +50,11 @@ ### 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 临时) @@ -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 就绪后远程工作流可直接接管。 -本地开发库为 `lingxi` / `postgres` / `postgres`,直接连接本机已有 PostgreSQL 容器;`.env.example` 与 CI services 保持一致。 +本地开发库为 `lingxi` / `postgres` / `postgres`,直接连接本机已有 PostgreSQL 容器;`.env.example` 与 CI services 保持一致;S3 待 RustFS 信息后配置。 ### 4.8 前端视觉方向 @@ -98,7 +98,7 @@ npm 上 `oasdiff` 为占位包(0.0.1-security),A0 暂用自研 `scripts/op 1. **A1 必须移除演示登录**并接入真实 JWT / 刷新令牌 / 权限守卫。 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 按需样式。 5. 远程 CI 待 Gitea Actions runner 就绪后验证 services 与 Playwright 浏览器安装;当前以 `pnpm ci:local` 为准(已实测通过)。 diff --git a/infra/compose.dev.yaml b/infra/compose.dev.yaml deleted file mode 100644 index cbb95de..0000000 --- a/infra/compose.dev.yaml +++ /dev/null @@ -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 diff --git a/infra/docker-compose.dev.yaml b/infra/docker-compose.dev.yaml deleted file mode 100644 index 2a1ec68..0000000 --- a/infra/docker-compose.dev.yaml +++ /dev/null @@ -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: diff --git a/package.json b/package.json index ec28915..4eb57f5 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,12 @@ "lint:fix": "eslint . --fix", "format": "prettier --write .", "format:check": "prettier --check .", + "check:dev": "node scripts/check-dev-deps.mjs", "typecheck": "turbo run typecheck && tsc -p tsconfig.json --noEmit", "test": "turbo run test", "test:int": "turbo run test:int", "test:e2e": "turbo run build && node scripts/e2e.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:migrate": "prisma migrate dev", "db:reset": "prisma migrate reset --force", diff --git a/scripts/check-dev-deps.mjs b/scripts/check-dev-deps.mjs new file mode 100644 index 0000000..e22db53 --- /dev/null +++ b/scripts/check-dev-deps.mjs @@ -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); +} diff --git a/scripts/ci-local.mjs b/scripts/ci-local.mjs index e0c245e..819ff67 100644 --- a/scripts/ci-local.mjs +++ b/scripts/ci-local.mjs @@ -9,7 +9,7 @@ const pnpm = process.env.npm_execpath ? [process.execPath, process.env.npm_execp /** 每一步可包含多条 pnpm 命令,任一失败立即退出。 */ const steps = [ - { label: '启动本地依赖(PG / Redis / S3)', commands: [['compose:up']] }, + { label: '检查本地依赖(PG / Redis)', commands: [['check:dev']] }, { label: '格式化检查', commands: [['format:check']] }, { label: 'Lint', commands: [['lint']] }, { label: '类型检查', commands: [['typecheck']] },