diff --git a/.claude/skills/d8d-dev/phase-00-setup.md b/.claude/skills/d8d-dev/phase-00-setup.md index 135e812..ff5c193 100644 --- a/.claude/skills/d8d-dev/phase-00-setup.md +++ b/.claude/skills/d8d-dev/phase-00-setup.md @@ -77,7 +77,11 @@ Admin: http://admin-custom-{project-slug}.dev.d8d.fun → 127.0.0.1:3002 Mini-H5: http://mini-custom-{project-slug}.dev.d8d.fun → 127.0.0.1:5173 ``` - - 缺失的域名通过 `create_proxy_host` 创建(SSL 由 K8S ingress 处理,不需要在 NPM 中配置) + - 缺失的域名通过 `create_proxy_host` 创建(⛔ 参数要求见 `reference/platform-nextjs.md` §7 或 `reference/platform-uni-app.md`) + - `block_exploits: false`(默认 true 阻断 WebSocket) + - 创建后 `update_proxy_host` 启用 WebSocket Support + - SSL 由 K8S ingress 处理,不需要在 NPM 中配置 + - 创建后验证:`get_proxy_host` 确认 `block_exploits: false` 且 `allow_websocket_upgrade: true` - 将外网域名写入 `pipeline-context.md` 的"外网域名"部分 - **E2E 测试优先使用外网域名**,localhost 作为最后 fallback @@ -226,6 +230,20 @@ - 不一致时以 `pipeline-state.yaml` 为准,同步更新 `sprint-status.yaml` - 同步后输出一行:`📋 状态同步:pipeline-state X done ↔ sprint-status Y done` +7.5. **平台检测与自动配置(⛔ 强制)** + - 检测项目类型,加载对应平台方案集: + ```bash + # 检测 Next.js 项目 + [ -f "next.config.ts" ] || [ -f "next.config.js" ] && echo "PLATFORM=nextjs" + # 检测 uni-app 项目 + [ -f "app/mini-program/src/pages.json" ] && echo "PLATFORM=uniapp" + ``` + - **Next.js 平台**:按 `./reference/platform-nextjs.md` 逐项检测 + - NextAuth trustHost 配置(第 1 节) + - Prisma 7 adapter 配置(第 2 节) + - 检测不通过 → 自动修复并记录到 `pipeline-context.md` 修复记录 + - **uni-app 平台**:按 `./reference/platform-uni-app.md` 逐项检测(已有流程不变) + --- diff --git a/.claude/skills/d8d-dev/reference/platform-nextjs.md b/.claude/skills/d8d-dev/reference/platform-nextjs.md new file mode 100644 index 0000000..6285b41 --- /dev/null +++ b/.claude/skills/d8d-dev/reference/platform-nextjs.md @@ -0,0 +1,232 @@ +# Next.js 平台方案集 + +> **⛔ 主流程文件统一引用此文件**:所有 Next.js 平台特定内容集中于此。 +> 主流程文件中不再内联平台特定方案,改为引用本文件对应章节。 + +--- + +## 1. 外网域名认证(⛔ 强制) + +### 问题描述 + +NextAuth v5 (5.0.0-beta.x) 默认使用 `AUTH_URL` 环境变量的固定值作为 cookie domain 和重定向 URL。 +当通过外网域名(如 `api-custom-xxx.dev.d8d.fun`)访问时,NextAuth 生成的 cookie domain 与请求域名不匹配, +导致浏览器拒绝发送 cookie,认证失败。 + +**症状**: +- 外网域名登录后立即跳回登录页(cookie 未保存) +- API 测试返回 401 Unauthorized +- localhost 登录正常,外网域名登录失败 + +### 检测命令 + +```bash +# 检查 NextAuth 配置是否有 trustHost +grep -q "trustHost" src/lib/auth.ts && echo "PASS: trustHost 已配置" || echo "FAIL: 缺少 trustHost" + +# 检查环境变量 +grep -q "AUTH_TRUST_HOST" .env && echo "PASS: AUTH_TRUST_HOST 已设置" || echo "WARN: 建议添加 AUTH_TRUST_HOST=true" +``` + +### 自动修复 + +在 NextAuth 配置对象顶部添加 `trustHost: true`: + +```typescript +// src/lib/auth.ts +export const { handlers, auth, signIn, signOut } = NextAuth({ + trustHost: true, // ← 关键:让 NextAuth 使用请求的 host 动态推导 URL + providers: [ + Credentials({ ... }) + ], + // ... +}); +``` + +同时在 `.env` 中添加(双重保险): + +``` +AUTH_TRUST_HOST=true +``` + +### E2E 测试 Auth 策略 + +- **外网域名 E2E 测试**:使用 Playwright 浏览器登录(trustHost=true 后正常工作) +- **API 测试**:curl + cookie jar 方式(先获取 CSRF token,再 POST 登录) +- **禁止硬编码 localhost 做 E2E 测试**,应从 `pipeline-context.md` 读取 URL + +--- + +## 2. Prisma 7 配置(⛔ 强制) + +### 问题描述 + +Prisma 7 使用 `@prisma/adapter-pg` driver adapter,PrismaClient 需要 `adapter` 参数。 +`prisma generate` 的输出路径为 `src/generated/prisma/client`(非 `src/generated/prisma`)。 + +### 检测命令 + +```bash +# 检查 Prisma adapter 是否已配置 +grep -q "@prisma/adapter-pg" src/lib/prisma.ts && echo "PASS" || echo "FAIL: 缺少 adapter-pg" +``` + +### 已知问题与修复 + +1. **seed.ts 编译错误**:`prisma/seed.ts` 不应参与 `next build` + - 修复:在 `tsconfig.json` 的 `exclude` 数组中添加 `"prisma/seed.ts"` + +2. **import 路径错误**: + - ❌ `import { PrismaClient } from '../src/generated/prisma'` + - ✅ `import { PrismaClient } from '../src/generated/prisma/client'` + +3. **PrismaClient 初始化**: + ```typescript + import { PrismaClient } from '@/generated/prisma/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! }) + const prisma = new PrismaClient({ adapter }) + ``` + +--- + +## 3. NextAuth v5 类型问题 + +### 问题描述 + +next-auth v5 beta 的 `session.user` 类型不完整,直接添加自定义属性(如 id、role)会报 TypeScript 错误: +``` +Conversion of type 'AdapterUser & User' to type '{ role: string }' is not possible +``` + +### 修复模式 + +在 callbacks 中使用 `as any` 类型断言 + eslint-disable 注释: + +```typescript +callbacks: { + async session({ session, token }) { + if (session.user) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (session.user as any).id = token.id as string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (session.user as any).role = token.role as string; + } + return session; + }, +}, +``` + +--- + +## 4. Playwright + React 兼容性 + +### 4.1 React 表单填写 + +**问题**:Playwright 的 `fill()` 方法在 React 18+ 应用中可能不触发 `onChange` 事件, +导致 React state 不更新,表单提交时值为空。 + +**修复**:使用 `nativeInputValueSetter` 设置值并手动派发事件: + +```javascript +await page.evaluate(() => { + const el = document.querySelector('input[type="email"]'); + const nativeSetter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, 'value' + ).set; + nativeSetter.call(el, 'test@example.com'); + el.dispatchEvent(new Event('input', { bubbles: true })); + el.dispatchEvent(new Event('change', { bubbles: true })); +}); +``` + +### 4.2 React 隐藏文件输入 + +**问题**:隐藏的 `` 在 React 中,`setInputFiles` 不会触发 `onChange` 事件, +导致上传流程无法启动。 + +**建议**: +- 优先通过 API (curl) 验证上传功能 +- E2E 测试时通过 `browser_run_code` 触发可见的 UI 元素而非直接操作隐藏 input + +--- + +## 5. 单 Next.js 应用模式 + +### 适用场景 + +本项目中 API 后端和前端共用一个 Next.js 应用,与 pipeline 默认的多应用模式不同。 + +### 关键差异 + +| 项目 | 多应用模式 | 单应用模式(本项目) | +|------|-----------|-------------------| +| API | `api/` 子目录 | `src/app/api/` 路由 | +| 前端 | `app/admin/` 子目录 | `src/app/` 页面 | +| 端口 | 3001 (API) + 3002 (Admin) | 3001 (单一端口) | +| 环境变量 | 需注入跨域 API URL | 无需注入(同源) | +| pm2 进程 | api + admin | vividspark (单进程) | + +### pipeline-context.md 调整 + +``` +## 前端环境变量注入状态 +- 无需注入(单 Next.js 应用,API 和前端同源) +``` + +--- + +## 6. 中间件(middleware.ts) + +### 已知警告 + +Next.js 16 中 "middleware" file convention is deprecated,应使用 "proxy"。 +但目前仍可工作,属于 warning 级别,不影响功能。 + +### middleware 中的 Auth 检查 + +在 Edge Runtime 中不能使用完整的 `auth()` 函数,应使用 `getToken()` 进行 JWT-only 检查: + +```typescript +import { getToken } from "next-auth/jwt" +import { NextResponse } from "next/server" + +export async function middleware(request) { + const token = await getToken({ req: request }) + if (!token) { + return NextResponse.redirect(new URL('/login', request.url)) + } + return NextResponse.next() +} +``` + +### 需要公开的路由 + +确保以下路由在 middleware 中被排除: +- `/api/auth/*` — NextAuth 默认路由(CSRF、session、callback) +- `/login` — 登录页 +- `/register` — 注册页 +- `/_next/*` — Next.js 静态资源 + +--- + +## 7. NPM 代理主机配置(⛔ 强制) + +### 问题描述 + +`next dev` 的 HMR(热模块替换)依赖 WebSocket 连接。 +NPM 代理主机默认配置会阻断 WebSocket,导致: +- 浏览器控制台持续 `WebSocket connection to 'ws://.../_next/webpack-hmr' failed` +- 页面反复重载,React 状态丢失 +- Playwright E2E 测试中表单值丢失 + +### 修复 + +`create_proxy_host` 创建代理主机时 ⛔ 必须设置: +- `block_exploits: false`(默认 true 会阻断 WebSocket 握手) +- 创建后通过 `update_proxy_host` 启用 WebSocket Support 开关 + +### 验证 + +`get_proxy_host` 确认:`block_exploits: false` + `allow_websocket_upgrade: true`