Files
WanJieDaoyou/docs/local-development.md
2026-10-04 23:05:46 +08:00

119 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本地开发
需要 pnpm 10.34.6、Node.js 24.18+ 和 Docker。独立运行维护命令前先执行 `pnpm exec turbo run build --filter="./packages/*"`;根 dev、build、typecheck、test 会自动按依赖图安排库构建。pnpm管理workspace依赖,Turborepo编排任务,Node运行Nest API与Vite。先执行 `pnpm install --frozen-lockfile`。首次将 `env/local.example.env` 复制为 `env/local.env`;已有本地配置保留。模板只含专用本地容器凭据,实际文件被Git忽略。
启动本地服务:
```bash
pnpm run services up -d --wait # 启动 PostgreSQL、Redis、NATS、Mailpit
pnpm run db:migrate # 执行认证和业务迁移
pnpm run dev # 启动 API 与 Vite
```
环境文件不再由脚本自动生成。启动和迁移命令重复执行不会清空数据;停止容器使用 `pnpm run services down`,数据保留在专用卷中。
| 服务 | 地址 |
| --- | --- |
| 页面 | http://127.0.0.1:5174 |
| API | http://127.0.0.1:3001 |
| PostgreSQL | `127.0.0.1:15432`,数据库 `daoyou_local` |
| Redis | `127.0.0.1:16379` |
| NATS / 监控 | `127.0.0.1:14222` / http://127.0.0.1:18222 |
| SMTP / Mailpit | `127.0.0.1:11025` / http://127.0.0.1:18025 |
## Workspace 与配置边界
- `apps/api`:Nest 功能模块、服务端基础设施与维护命令;运行依赖、Nest CLI 和 Oxlint 在本包声明;使用默认 tsc 构建。
- `apps/web`:React SPA、静态资源与 Vite 配置;React、Vite 和 Tailwind 依赖在本包声明。
- `packages/*`:由 tsc 编译为 `dist` 中的 JavaScript 与声明文件,JSON 内容随编译复制。API 在运行时加载,Web 由 Vite 打包,不独立部署。常量、领域模型、战斗 core、内容、玩法规则与契约分别位于 `constants`、`game-domain`、`combat-core`、`game-content`、`game-rules`、`contracts`;旧 shared 已清退,依赖方向和验收进度见 [应用边界](architecture-boundaries.md)。宿主使用明确的 `exports`,不以TS/Vite源码别名绕过边界;包内使用相对源码导入,避免类型检查读取自身陈旧 dist。公开叶子入口需单独注册。
- 根目录保留 `pnpm-workspace.yaml`、单一 `pnpm-lock.yaml`、`turbo.json`、lint/test/typecheck、迁移配置和部署入口。内部依赖使用 `workspace:*`,各包自行声明依赖。
Web/packages/根工具使用 ESLint 的 JS、TypeScript、React Hooks 推荐规则及 Vite Fast Refresh 配置,API 使用 `oxlint --type-aware`。
Oxlint 以 TypeScript 插件的 `correctness: error` 为基线,补充原有推荐规则中未被基线覆盖的检查;保留全 API 的 `no-floating-promises` 和入口、HTTP、Runtime、Realtime 的 `no-misused-promises`。新增的 `await-thenable`、`no-base-to-string`、`no-misused-spread`、`no-redundant-type-constituents`、`no-useless-default-assignment`、`restrict-template-expressions`、`unbound-method` 暂不启用,避免配置整理扩大为既有业务代码的类型规则整改。
迁移完成后已删除包边界检查脚本、`check:boundaries` 命令及两套 linter 的导入限制;包依赖方向、公开入口和数据库注入仍遵循架构约定。战斗内核继续禁止直接使用 `Math.random()` 和 `Date.now()`。
LLM生成和提示词位于API的 `lib/generation`;共同的修为与突破计算位于共享包。
Nest `ConfigurationModule` 使用 `@nestjs/config`、关闭dotenv自动发现,并提供一次Zod校验后的不可变环境快照。Nest服务注入 `AppConfigService`,使用类型化的 `get()` 读取配置,独立仓储/基础库使用 `getRuntimeEnvironment()`。端口、数据库URL、连接池上限、认证配置和消息配置在启动时校验;production还要求Redis地址与cron密钥且禁止 `APP_ENV=local`。错误只报告字段与约束,不输出凭据。
`DatabaseModule`以 `DRIZZLE_DATABASE` Provider 导出既有Drizzle客户端和 `DatabaseService`,Nest SQL/资源读取入口显式注入并向下传递客户端,与独立仓储共用一个pg.Pool,事务继续使用 `DbExecutor`/`DbTransaction`。Runtime在请求及消息排空后关闭连接;健康检查同时探测PostgreSQL、Redis、NATS及消息设施。
业务实现归所属feature的 `application/`(宗门组织为 `sects/organization/`),实时广播归 `realtime/infrastructure/`。`player/application/state`拥有跨领域资源读取、提交和命令协调;秘境/蜃楼归所属feature,调度和业务消息组合归`runtime`,`lib`只保留共享技术设施。避免新增集中式业务实现。浏览器业务HTTP请求使用 `apps/web/src/lib/api/fetch.ts` 的 `apiFetch`,不修改全局fetch。完整边界和发布顺序见 [架构审查与规范](monorepo-architecture.md)。
## NestJS API
`dev:api`、`prd:api`、`build:server`和Docker均使用Nest;旧Hono入口、路由和依赖已移除。框架代码与工具链迁移完成;特殊数据/发布验收见迁移记录。
```bash
pnpm run dev:api # 首次构建 Nest,随后监听源码并重建、重启 Node.js
pnpm run dev:web # 另一个终端启动前端
pnpm run build:server # 类型检查并生成 apps/api/dist/main.js
```
Nest 使用 `nest start --no-shell --env-file ...` 和默认 tsc 编译器。根 `dev`/`prd` 命令通过 `turbo watch` 先按依赖图构建库,再启动应用;修改工作区源码时重建依赖并重启受影响的开发任务,CLI 通过 SIGTERM 排空旧 API。直接执行 API 包内的 dev 命令只启动 Nest,不替代根工作区 watch。类型检查随 Nest 构建执行。提示词通过 Nest CLI assets 复制到 `dist/prompts`,启动时读取和解析一次。API只清理 `apps/api/dist`,Vite只清理 `apps/web/dist`,两者可独立构建。Docker使用同一pnpm锁文件构建,再通过 `pnpm --filter @daoyou/api deploy --prod /out` 生成独立运行目录;最终镜像仅复制该目录,入口为 `dist/main.js`。Web及根开发工具不会进入运行镜像。workspace 禁用自动安装 peer 依赖;`syncInjectedDepsAfterScripts: [build]` 在 workspace 构建后同步注入依赖,确保 API、Web 和 deploy 使用当前产物。
当前HTTP入口已迁入Nest,特殊数据与生产验收仍有未完成项。长流程验证需要关闭watch时,先执行`pnpm run build:server`,再执行`APP_ENV=local NODE_ENV=development node --env-file=env/local.env apps/api/dist/main.js`。详情见 [迁移进度](nestjs-migration.md)。
workspace 使用 pnpm 原生的 `resolvePeersFromWorkspaceRoot: false` 和 `dedupePeerDependents: false`,禁止应用借用根开发依赖或合并不同 peer 环境下的包实例,避免 Better Auth 的可选 peer 将 `vitest`、`drizzle-kit` 带入 `deploy --prod`。API/Web 各自声明插件必需的运行 peer,根开发工具仍供测试和迁移使用;不修改第三方包元数据。调整 peer 配置时,使用 `pnpm install --lockfile-only --no-prefer-frozen-lockfile` 重新解析并核对锁文件版本,再执行 frozen install、完整构建及部署目录检查。运行镜像保留 Node 24/Debian bookworm 文件系统及非 root 用户,清理 npm、Corepack、Yarn 后重新复制基础层;部署目录仅删除本项目 API 与六个库的 source map、声明文件,保留运行 JavaScript、JSON 和提示词。宿主构建产物仍保留调试与类型文件。
Nest收到SIGTERM/SIGINT后拒绝新请求,停止定时发布并排空HTTP;30秒后关闭剩余HTTP连接,触发流式请求取消,继续等待处理器结束。随后停止消息消费者/发布任务、关闭数据库与Redis。启动期间收到信号会先等待初始化结束,再统一清理。整个停机过程超过60秒则以失败状态强制退出;生产Compose设置75秒停机宽限期。请求取消不能撤销已经提交的事务,恢复仍依赖现有幂等与outbox机制。
## 选择环境
两套开发命令分别显式加载一个文件:
| 用途 | 本地 `env/local.env` | 预发布 `env/staging.env` |
| --- | --- | --- |
| 同时启动 | `pnpm run dev` | `pnpm run prd` |
| 仅 API | `pnpm run dev:api` | `pnpm run prd:api` |
| 仅 Web | `pnpm run dev:web` | `pnpm run prd:web` |
`prd` 指预发布调试,两组都使用 `NODE_ENV=development`,API 启用 watch,Web 使用 Node.js 运行 Vite。API 和 Web 分别读取同一份环境文件;Vite 根据 `HOST`、`WEB_PORT` 监听,并把 API/WebSocket 代理到 `PORT`。本地端口为 3001/5174;预发布调试默认 3000/5173。
`env/staging.env` 从 `env/example.env` 准备,已有文件继续使用;它连接远程服务,不能用于自动化模拟测试写入。仅 `env/local.example.env` 和 `env/example.env` 纳入Git,实际 `env/` 配置默认忽略。原根目录 `.env` 原样移至 `env/legacy.env` 保留,不被任何命令加载;不将真实凭据放入本地配置或 `VITE_*`。整个 `env/` 排除在 Docker 构建上下文之外。
Node的 `--env-file` 显式选择文件;Nest ConfigModule关闭dotenv自动发现。Vite使用 `envDir: false`,避免叠加另一份配置;公开的 `VITE_*` 由进程环境注入。不要在Shell中预先导出另一环境的业务凭据。
Turbo开发任务不缓存,使用loose环境模式向子进程传递开发配置。构建使用默认strict模式,只传入声明的 `NODE_ENV`、`VITE_*`、`CF_PAGES_COMMIT_SHA`、`GITHUB_SHA`;这些值参与缓存键。共享包源码和根tsconfig变化会使依赖任务失效。`.turbo/` 不入Git或Docker上下文。
终端Ctrl-C终止开发任务及子进程。浏览器长流程测试使用上述构建后的无watch命令和 `pnpm run dev:web`,不增加专用脚本。
## 构建与迁移
`build:client` 和 `build:server` 保留为 CI/CD 稳定入口,不要求本地环境文件或本地数据库启动:
- `build:client`:`apps/web/vite.config.ts` 构建 SPA 至 `apps/web/dist`,保留 `version.json` 和构建 ID。
- `build:server`:`nest build` 根据 `apps/api/nest-cli.json` 构建Nest服务`apps/api/dist/main.js`,由Node运行并使用外部生产依赖。Docker调用此命令;旧resolver Worker已退役,没有额外Worker构建。
- `build`:Turbo按依赖图构建两个独立包;各目标会清理自己的输出,生产仍按原有独立构建方式发布。
前端公开配置由 CI 环境变量注入;需要文件时显式使用 `node --env-file=env/staging.env node_modules/turbo/bin/turbo run build --filter=@daoyou/web`。服务端密钥和连接串在部署启动时注入,不在构建时绑定环境。根命令分别进入对应 workspace;Vite只负责Web,Nest CLI只负责API。
`pnpm run db:migrate` 仅加载 `env/local.env`,依次执行认证和业务迁移。预发布/生产迁移沿用原子工具并显式注入环境,例如 `node --env-file=env/staging.env node_modules/drizzle-kit/bin.cjs migrate --config drizzle.auth.config.ts` 和 `node --env-file=env/staging.env node_modules/drizzle-kit/bin.cjs migrate`;不会随 `prd` 启动自动迁移。
## 注册与测试
`ALTCHA_HMAC_SECRET` 是人机验证唯一开关:未配置或为空时,服务端跳过验证,前端读取 `/api/captcha/config` 后不显示验证码组件;配置后强制校验。配置接口失败不会视作关闭验证。前端不读取密钥,也没有本地认证开关或伪造验证码 token。
邮箱激活、密码重置和邮箱 OTP 使用所有环境共用的正常流程;本地邮件送到 Mailpit。账号通过真实页面注册,验证码和链接从 Mailpit 获取,不再用脚本绕过账号/角色创建。已有本地测试账号可复用;若未验证邮箱,按登录页提示完成验证。多人操作应使用独立浏览器配置文件/上下文,避免共享 Cookie。
项目只有两层测试:`packages/*/src` 纯单元测试,以及 Codex 使用浏览器/Playwright 的真实用户流程模拟。操作规范见 [测试规范](./testing.md)。不保留一次性冒烟、故障注入、种子或性能采样脚本;需要的数据与步骤由当次任务按实际页面准备,结果记录在任务或相关设计文档中。
已有测试账号的查询方式、统一密码和登录要求统一维护在[本地测试账号与密码](./testing.md#3-本地测试账号与密码),测试前按该规范复用账号。
本地未配置 LLM、OAuth、支付凭据;涉及这些外部能力时需明确准备条件,不能把无法走通的功能记为已验证。需要准备特殊角色数据时,在当次任务中明确本地范围并执行必要操作,不将临时准备过程扩展成长期脚本。
## 检查与迁移
```bash
pnpm run lint
pnpm run typecheck
pnpm run test
pnpm run build
```
新库安装已修正 `0000` 中唯一索引与外键的创建顺序。`0036_unified_v6_replay` 针对尚未上线的 v6 清空旧开发回放并建立统一角色关联表。迁移与清理仅在本地验证,未向预发布执行。
认证整理时已通过 Codex 浏览器验证无验证码注册、Mailpit 激活和密码登录,以及配置密钥后启用验证码、移除后关闭。本轮原生启动改造已验证 `dev` 组合启动、Node 承载 Vite、页面与登录会话读取、`prd:web` 独立启动、本地重复迁移、Lint、`build:client`、完整 `build`,以及原 Dockerfile 的实际镜像构建。未启动连接远程服务的 `prd:api`,未发布镜像;本轮不涉及共享业务逻辑,未重复共享单元测试。