MeshiTrack/docs/instructions/turborepo.md

291 lines
6.5 KiB
Markdown

# Turborepo Monorepo Best Practices — MeshiTrack
> Instruction file for managing the monorepo workspace.
## Workspace Structure
```
MeshiTrack/
├── packages/
│ ├── api/ # NestJS backend → @meshitrack/api
│ ├── web/ # Next.js frontend → @meshitrack/web
│ └── shared/ # Shared types & DTOs → @meshitrack/shared
├── docker/ # Docker configs (not a package)
├── docs/ # Documentation (not a package)
├── turbo.json # Turborepo pipeline config
├── package.json # Root workspace config
├── tsconfig.base.json
├── .eslintrc.js
├── .prettierrc
└── .gitignore
```
## Root package.json
```json
{
"name": "meshitrack",
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"lint": "turbo run lint",
"test": "turbo run test",
"typecheck": "turbo run typecheck",
"clean": "turbo run clean"
},
"devDependencies": {
"turbo": "^2.x",
"typescript": "^5.x",
"eslint": "^9.x",
"prettier": "^3.x"
}
}
```
## turbo.json Configuration
```json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["^build"]
},
"typecheck": {
"dependsOn": ["^build"]
},
"clean": {
"cache": false
}
}
}
```
### Key concepts
- **`dependsOn: ["^build"]`**: Before running a task in a package, first build all its dependencies. This ensures `shared` is built before `api` or `web` run.
- **`outputs`**: What Turborepo caches. If outputs haven't changed, Turborepo replays from cache.
- **`persistent: true`**: For long-running dev servers that shouldn't be cached.
- **`cache: false`**: Disables caching for tasks that should always run.
## Package Dependencies
### Shared package is the foundation
```
@meshitrack/shared ← @meshitrack/api
← @meshitrack/web
```
Both `api` and `web` depend on `shared`, but never on each other.
### Reference shared in package.json
```json
// packages/api/package.json
{
"name": "@meshitrack/api",
"dependencies": {
"@meshitrack/shared": "workspace:*"
}
}
// packages/web/package.json
{
"name": "@meshitrack/web",
"dependencies": {
"@meshitrack/shared": "workspace:*"
}
}
```
### Import from shared
```typescript
// In packages/api or packages/web
import { Product, NutritionInfo, ProductCategory } from '@meshitrack/shared';
import { CreateProductSchema } from '@meshitrack/shared/validation';
```
## Shared Package Setup
### packages/shared/package.json
```json
{
"name": "@meshitrack/shared",
"version": "0.0.1",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./validation": {
"types": "./dist/validation/index.d.ts",
"default": "./dist/validation/index.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"zod": "^3.x"
},
"devDependencies": {
"typescript": "^5.x"
}
}
```
### packages/shared/tsconfig.json
```json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"]
}
```
### Shared package must be platform-agnostic
Rules for code in `packages/shared`:
- **No** Node.js imports (`fs`, `path`, `http`, etc.)
- **No** NestJS decorators or imports
- **No** Next.js imports
- **No** DOM/browser APIs
- Only pure TypeScript: types, interfaces, enums, Zod schemas, utility functions
## TypeScript Configuration
### Root tsconfig.base.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "commonjs",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"incremental": true
},
"exclude": ["node_modules", "dist"]
}
```
Each package extends this and overrides as needed (e.g., `web` uses `"jsx": "preserve"`, `"module": "esnext"`).
## Development Workflow
### Start all packages in dev mode
```bash
npm run dev
# Turborepo runs: shared (build) → then api (dev) + web (dev) in parallel
```
### Build all packages
```bash
npm run build
# Turborepo builds shared first, then api and web in parallel
```
### Run tasks for a specific package
```bash
npx turbo run dev --filter=@meshitrack/api
npx turbo run test --filter=@meshitrack/web
```
### Add a dependency to a specific package
```bash
cd packages/api
npm install @nestjs/schedule
# Or from root:
npm install @nestjs/schedule --workspace=packages/api
```
## Caching
### Turborepo remote caching (optional)
For CI, consider enabling remote caching to share build cache across machines:
```bash
npx turbo login
npx turbo link
```
Or use a self-hosted cache server for full self-hosted setup.
### Local caching
Turborepo caches locally in `node_modules/.cache/turbo` by default. It's fast and requires no setup.
## CI Integration
```yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npx turbo run build lint test typecheck
```
## Common Pitfalls
1. **Forgetting to build shared**: If `api` or `web` can't find types from `shared`, run `npm run build` from root. The `dependsOn: ["^build"]` config handles this in Turborepo tasks.
2. **Circular dependencies**: Never import from `api` in `web` or vice versa. Only import from `shared`.
3. **Version mismatches**: Keep TypeScript versions aligned across all packages. Pin in root `devDependencies`.
4. **Large `node_modules`**: Use `npm` workspaces hoisting. Most dependencies are installed at root level. Only package-specific versions go in package-level `node_modules`.