--- name: monorepo description: Monorepo best practices — tooling, workspace configuration, shared dependencies, CI/CD, and dependency management. Use when working in monorepos (pnpm workspaces, moon, turborepo, Nx), managing shared packages. Triggers from project files and configuration, not just keyword matching." --- # Monorepo Best Practices ## Tool Selection | Tool | Best For | Why | |------|----------|-----| | **pnpm workspaces** | Package management | Strict, fast, disk-efficient | | **moon** | Monorepo orchestration | Task orchestration + caching | | **turborepo** | Task orchestration | Simple caching, good for JS/TS | | **Nx** | Full monorepo framework | Generators, dependency graph, affected commands | | **Git submodules** | Multi-repo coordination | Separate repos imported together (this repo's pattern) | ## Workspace Structure (pnpm + moon) ``` ├── apps/ │ ├── hub/ # Next.js app (submodule) │ └── scraper/ # Rust API (submodule) ├── packages/ # Shared libraries (when not submodules) ├── infra/ # Shared infra config ├── pnpm-workspace.yaml ├── moon.yml └── package.json ``` ### pnpm-workspace.yaml ```yaml packages: - 'apps/*' - 'packages/*' ``` ### moon.yml (root) ```yaml $schema: 'https://moonrepo.dev/schemas/project.json' language: 'typescript' type: 'application' ``` ## Shared Dependencies ```bash # Install a shared dependency pnpm add -w typescript # Install in a specific package pnpm add --filter @scope/package zod # Run in all packages pnpm -r run build ``` **Rules:** - **One version of a dependency across the monorepo** — use `pnpm overrides` or `resolution`. - **Root `devDependencies`** for shared tooling (TypeScript, Biome, ESLint). - **Explicit `dependencies`** — never rely on hoisting. - **Lock file** (`pnpm-lock.yaml`) committed — immutable installs. ## Git Submodules (this repo's pattern) ``` asepharyana-hub/ ├── apps/ │ ├── hub/ → asepharyana/asepharyana-hub-hub │ └── scraper/ → asepharyana/asepharyana-hub-scraper ``` ### Submodule Workflow ```bash # Init after clone git submodule update --init --recursive # Update all submodules to latest git submodule foreach git pull origin main # Update one submodule cd apps/hub && git checkout main && git pull cd ../.. && git add apps/hub && git commit -m "chore(deps): update hub submodule" git push ``` **State indicators:** - `(HEAD)` — detached at committed pointer (normal state). - `(main)` — on a branch (you've done `cd apps/name && git checkout main`). - Dirty — uncommitted changes inside submodule. ### When to Use Submodules vs Workspaces | Need | Use | |------|-----| | Independent repos, separate deploy | Submodules | | Shared code within one repo | Workspaces | | Tightly coupled, always deploy together | Workspaces | | Loosely coupled, different teams | Submodules | ## CI/CD for Monorepos ### Selective Builds ```yaml # Only run relevant workflows based on changed paths on: push: branches: [main] paths: - 'apps/hub/**' - 'infra/docker/hub.Dockerfile' ``` ### Affected Commands (Nx/Turborepo/Moon) ```bash moon ci # Runs affected tasks based on changes npx nx affected:test # Nx style turbo run build # Turborepo — leverages cache ``` ### Caching - **moon/turborepo** cache task outputs by file hash + env. - **pnpm** caches node_modules. - **Docker layer caching** — Registry-based caching for Docker builds. ## Shared Configuration ### TypeScript ```jsonc // tsconfig.base.json at root — extended by all packages { "compilerOptions": { "strict": true, "declaration": true, "declarationMap": true, "sourceMap": true, "moduleResolution": "bundler" } } ``` ### ESLint / Biome ```jsonc // biome.json at root — shared config for all packages { "formatter": { "indentStyle": "tab", "lineWidth": 120 }, "linter": { "rules": { "recommended": true } } } ``` ## Dependency Management - **Dependabot / Renovate** — automate dependency updates. - **`pnpm dedupe`** — deduplicate after updates. - **Check for duplicates** — `pnpm ls -r` or `pnpm why `. - **When to upgrade:** - Patch: auto-merge. - Minor: update weekly. - Major: scheduled migration, document breaking changes. ## Anti-patterns - ❌ Different dependency versions across packages — inconsistent builds - ❌ Hoisting assumptions — code works in dev but not in CI because of missing deps - ❌ Monolithic `package.json` — each package declares its own dependencies - ❌ No `.npmrc` with `shamefully-hoist=true` — defeats pnpm's strictness - ❌ Circular dependencies between packages — extract shared code - ❌ Every change rebuilds everything — use affected commands and caching - ❌ Submodule pointer drift — always commit after updating submodules