chore: migrate to pnpm workspaces monorepo with Turborepo

- Restructure directories: apps/api, apps/admin, apps/website
- Add root pnpm-workspace.yaml, turbo.json, .prettierrc, .gitignore
- Rename packages to @inkreach/api, @inkreach/admin, @inkreach/website
- Add shared packages: packages/tsconfig, packages/shared-types
- Add pnpm.onlyBuiltDependencies for native builds
- Update docs: README.md, structs.md
- All three projects build successfully
This commit is contained in:
yeuimu
2026-07-11 16:54:05 +08:00
parent 69945b8749
commit 7e04877bb6
155 changed files with 20134 additions and 14393 deletions
+48 -35
View File
@@ -1,45 +1,49 @@
# InkReach Product Center — Monorepo Structure
> 全局根目录结构说明。三个子项目独立维护各自 `package.json` 与技术栈,通过约定的端口协同工作
> pnpm workspaces + Turborepo 统一管理三个子项目与共享包。所有子项目位于 `apps/`,共享代码位于 `packages/`
## 根目录
```
inkreach-official/
├── .agents/ # AI Agent 共享技能(brainstorming / TDD / git-spec / ...
├── apps/
│ ├── api/ # NestJS 后端 (package: @inkreach/api)
│ ├── admin/ # Vue 3 后台 (package: @inkreach/admin)
│ └── website/ # Nuxt 4 官网 (package: @inkreach/website)
├── packages/
│ ├── tsconfig/ # 共享 TypeScript 配置 (base / api / vue)
│ └── shared-types/ # 共享类型定义(PaginatedResult, BackendEnvelope, 实体接口)
├── .agents/ # AI Agent 共享技能
├── docs/ # 跨子项目文档
│ ├── dev/ # 设计文档(数据库表设计、PRD、原型图)
│ │ ├── database-table-design.md
│ │ ├── product-center-prd.md
│ │ └── product-center-{1,2,3}.png
│ ├── references/
│ │ └── structs.md # 本文件
│ └── superpowers/specs/ # superpowers 规格说明
├── plans/ # 跨子项目计划
│ └── feature/
├── AGENTS.md # 根级 AI Agent 开发规范
├── skills-lock.json # 技能版本锁定
├── package.json # 根 workspace 配置(scripts + devDependencies
├── pnpm-workspace.yaml # pnpm workspace 定义(apps/*, packages/*
├── turbo.json # Turborepo 任务流水线
├── .prettierrc # 统一代码格式化
├── .gitignore # 全局忽略规则
├── .env # 根级环境变量(DATABASE_URL 等)
├── .git/ # 单一 Git 仓库,trunk-based
── inkreach-official-nestjs/ # 子项目 1NestJS 后端
├── inkreach-official-admin/ # 子项目 2Vue 3 后台
└── inkreach-official-website/ # 子项目 3Nuxt 4 官网
├── AGENTS.md # 根级 AI Agent 开发规范
── README.md
```
## 端口与子项目
| 子项目 | 端口 | 启动命令 | 说明 |
|--------|------|----------|------|
| inkreach-official-nestjs | 3001 | `npm run start:dev` | NestJS + Prisma + PostgreSQL 后端 APISwagger 文档 `/api/docs` |
| inkreach-official-admin | 5173 | `npm run dev` | Vue 3 + Element Plus 后台管理;通过 Vite proxy `/api → :3001` |
| inkreach-official-website | 3000 | `npm run dev` | Nuxt 4 官网;通过 Nitro `server/api/backend/*` 代理 `:3001` |
| 子项目 | 包名 | 端口 | 启动命令 | 说明 |
|--------|------|------|----------|------|
| `apps/api` | `@inkreach/api` | 3001 | `pnpm --filter @inkreach/api start:dev` | NestJS + Prisma + PostgreSQL 后端 APISwagger 文档 `/api/docs` |
| `apps/admin` | `@inkreach/admin` | 5173 | `pnpm --filter @inkreach/admin dev` | Vue 3 + Element Plus 后台管理;通过 Vite proxy `/api → :3001` |
| `apps/website` | `@inkreach/website` | 3000 | `pnpm --filter @inkreach/website dev` | Nuxt 4 官网;通过 Nitro `server/api/backend/*` 代理 `:3001` |
启动顺序:先启动 `inkreach-official-nestjs`,再启动另两个。
启动顺序:先启动 `apps/api`,再启动另两个。
## 子项目 1inkreach-official-nestjs(后端
## 子项目 1apps/api(后端,@inkreach/api
```
inkreach-official-nestjs/
apps/api/
├── prisma/
│ ├── schema.prisma # 数据模型(OriginGood/Country/Category/Tag/Position/Good/User/SyncLog
│ └── migrations/ # Prisma migrate 历史
@@ -51,6 +55,7 @@ inkreach-official-nestjs/
│ ├── countries/ # 国家 CRUD(受 JWT 保护)
│ ├── categories/ # 品类 CRUD(受 JWT 保护,自引用树)
│ ├── tags/ # 标签 CRUD(受 JWT 保护)
│ ├── tag-groups/ # 标签分组 CRUD(受 JWT 保护,含批量排序)
│ ├── positions/ # 坑位 CRUD(受 JWT 保护)
│ ├── origin-goods/ # SDS 原始商品快照(只读分页)
│ ├── goods/ # 商品 CRUD + 批量优先级 + 批量创建
@@ -75,7 +80,8 @@ inkreach-official-nestjs/
| `OriginGood` | SDS 原始商品缓存,关联 `sds_good_id`(唯一) |
| `Country` | 国家,关联 goods / positions |
| `Category` | 自引用树形品类,可选 `sds_category_id` |
| `Tag` | 标签,含 `tagColor``timing` |
| `Tag` | 标签,含 `tagColor``tagFontColor``tagGroupId``sortOrder``timing` |
| `TagGroup` | 标签分组(含 `groupName``groupColor``groupIcon``sortOrder`),删除分组时组内 tag 的 `tagGroupId` 通过 `onDelete: SetNull` 自动置空 |
| `Position` | 坑位:`(country, category)` 维度,关联多个 goods |
| `Good` | 商品:`originGood × country × category × tag? × position?`,含 `goodPriority` |
| `User` | 后台用户(bcrypt 哈希) |
@@ -98,9 +104,13 @@ inkreach-official-nestjs/
| `/auth/login` `POST` | 登录获取 JWT | 公开 |
| `/public/categories` `GET` | 公开品类树(仅含已挂商品的品类) | 公开 |
| `/public/countries` `GET` | 公开国家列表(仅含已挂商品的国家) | 公开 |
| `/public/goods` `GET` | 分页商品(支持 `countryId/categoryId/tagId/keyword/page/pageSize` | 公开 |
| `/public/tags` `GET` | 公开标签列表(带 `group` 字段,按 group 排序 | 公开 |
| `/public/tag-groups` `GET` | 公开标签分组列表 | 公开 |
| `/public/goods` `GET` | 分页商品(支持 `countryId/categoryId/tagIds(逗号分隔)/keyword/page/pageSize``tagIds` 为 AND 关系) | 公开 |
| `/public/goods/:id` `GET` | 商品详情 | 公开 |
| `/categories` `/tags` `/countries` `/positions` | 后台 CRUD | JWT |
| `/categories` `/tags` `/tag-groups` `/countries` `/positions` | 后台 CRUD | JWT |
| `/tags/sort` `PATCH` | 批量更新 tag 排序和分组归属 | JWT |
| `/tag-groups/sort` `PATCH` | 批量更新分组排序 | JWT |
| `/origin-goods` `GET` | SDS 原始商品快照分页 | JWT |
| `/goods` | 后台商品 CRUD + `POST /goods/batch` + `PATCH /goods/batch-priority` | JWT |
| `/sync/categories` `POST` | 手动触发分类同步 | JWT |
@@ -108,10 +118,10 @@ inkreach-official-nestjs/
| `/sync/status` `GET` | 最近同步日志(`?limit=20` | JWT |
| `/api/docs` | Swagger UI | 公开 |
## 子项目 2inkreach-official-admin(后台
## 子项目 2apps/admin(后台,@inkreach/admin
```
inkreach-official-admin/
apps/admin/
├── public/ # 静态资源
├── src/
│ ├── main.ts # 入口:Pinia + Vue Router + ElementPlus
@@ -123,7 +133,8 @@ inkreach-official-admin/
│ │ ├── goods.ts # 商品 CRUD + 批量
│ │ ├── categories.ts # 品类 CRUD
│ │ ├── countries.ts # 国家 CRUD
│ │ ├── tags.ts # 标签 CRUD
│ │ ├── tags.ts # 标签 CRUD(含批量排序)
│ │ ├── tag-groups.ts # 标签分组 CRUD
│ │ ├── positions.ts # 坑位 CRUD
│ │ ├── origin-goods.ts # 原始商品快照
│ │ └── sync.ts # 同步触发 + 日志
@@ -170,10 +181,10 @@ inkreach-official-admin/
- **路由守卫**:未登录访问受保护路由跳 `/login`;已登录访问 `/login``/`
- **Pinia 持久化**`auth` store 主动读写 `localStorage``token` + `user`)。
## 子项目 3inkreach-official-website(官网
## 子项目 3apps/website(官网,@inkreach/website
```
inkreach-official-website/
apps/website/
├── app/
│ ├── app.vue # 根容器:<NuxtPage />
│ ├── pages/
@@ -212,6 +223,8 @@ inkreach-official-website/
│ │ └── backend/ # NestJS 后端代理(同源 + Nitro 缓存 60s
│ │ ├── categories.get.ts # → GET :3001/public/categories
│ │ ├── countries.get.ts # → GET :3001/public/countries
│ │ ├── tags.get.ts # → GET :3001/public/tags
│ │ ├── tag-groups.get.ts # → GET :3001/public/tag-groups
│ │ ├── goods.get.ts # → GET :3001/public/goods
│ │ └── goods/[id].get.ts # → GET :3001/public/goods/:id
│ └── utils/pod-api.ts
@@ -227,7 +240,7 @@ inkreach-official-website/
└── package.json
```
> 子项目内部的页面、组件、composable、代理路由、组件响应式断点等详细信息见 `inkreach-official-website/docs/references/structs.md`。
> 子项目内部的页面、组件、composable、代理路由、组件响应式断点等详细信息见 `apps/website/docs/references/structs.md`。
## 数据库
@@ -236,15 +249,15 @@ inkreach-official-website/
- 所有 `TIMESTAMPTZ` 列:`@db.Timestamptz(6)`
- 所有主键:`BigInt @default(autoincrement())`
- 表名与列名通过 `@map` / `@@map` 映射为 `snake_case`
- 迁移位于 `inkreach-official-nestjs/prisma/migrations/`
- 迁移位于 `apps/api/prisma/migrations/`
## 环境变量总览
| 变量 | 位置 | 用途 | 默认 |
|------|------|------|------|
| `DATABASE_URL` | 根 `.env` | PostgreSQL 连接串 | — |
| `JWT_SECRET` | inkreach-official-nestjs | JWT 签名密钥 | — |
| `PORT` | inkreach-official-nestjs | 后端端口 | `3001` |
| `SDS_API_*` | inkreach-official-nestjs | 同步上游 SDS 接口凭据 | — |
| `VITE_API_BASE` | inkreach-official-admin | axios baseURL | `/api` |
| `NUXT_PUBLIC_BACKEND_URL` | inkreach-official-website | NestJS 后端地址 | `http://localhost:3001` |
| `JWT_SECRET` | `apps/api` | JWT 签名密钥 | — |
| `PORT` | `apps/api` | 后端端口 | `3001` |
| `SDS_API_*` | `apps/api` | 同步上游 SDS 接口凭据 | — |
| `VITE_API_BASE` | `apps/admin` | axios baseURL | `/api` |
| `NUXT_PUBLIC_BACKEND_URL` | `apps/website` | NestJS 后端地址 | `http://localhost:3001` |