--- name: monorepo-patterns description: Monorepo workspace patterns for multi-package projects with shared dependencies, testing strategies, and CI/CD. Use when working in monorepo structures. --- # Monorepo Patterns Skill ## Recommended Structure ``` project/ ├── apps/ │ ├── backend/ # Python FastAPI │ │ ├── src/ │ │ ├── tests/ │ │ └── pyproject.toml │ └── frontend/ # React TypeScript │ ├── src/ │ ├── tests/ │ └── package.json ├── packages/ │ ├── shared-types/ # Shared TypeScript types │ │ ├── src/ │ │ └── package.json │ └── ui-components/ # Shared React components │ ├── src/ │ └── package.json ├── infrastructure/ │ ├── terraform/ │ │ ├── environments/ │ │ └── modules/ │ └── ansible/ │ ├── playbooks/ │ └── roles/ ├── scripts/ # Shared scripts ├── docs/ # Documentation ├── .github/ │ └── workflows/ ├── package.json # Root (workspaces config) ├── pyproject.toml # Python workspace config └── CLAUDE.md # Project-level guidance ``` ## Workspace Configuration ### npm Workspaces (Node.js) ```json // package.json (root) { "name": "my-monorepo", "private": true, "workspaces": [ "apps/*", "packages/*" ], "scripts": { "dev": "npm run dev --workspaces --if-present", "build": "npm run build --workspaces --if-present", "test": "npm run test --workspaces --if-present", "lint": "npm run lint --workspaces --if-present", "typecheck": "npm run typecheck --workspaces --if-present" }, "devDependencies": { "typescript": "^5.6.0", "vitest": "^3.2.0", "@types/node": "^22.0.0" } } ``` ### UV Workspace (Python) ```toml # pyproject.toml (root) [project] name = "my-monorepo" version = "0.0.0" requires-python = ">=3.11" [tool.uv.workspace] members = ["apps/*", "packages/*"] [tool.uv.sources] shared-utils = { workspace = true } ``` ## Package References ### TypeScript Internal Packages ```json // packages/shared-types/package.json { "name": "@myorg/shared-types", "version": "0.0.0", "private": true, "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } }, "scripts": { "build": "tsc", "dev": "tsc --watch" } } // apps/frontend/package.json { "name": "@myorg/frontend", "dependencies": { "@myorg/shared-types": "workspace:*" } } ``` ### Python Internal Packages ```toml # packages/shared-utils/pyproject.toml [project] name = "shared-utils" version = "0.1.0" dependencies = [] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" # apps/backend/pyproject.toml [project] name = "backend" dependencies = [ "shared-utils", # Resolved via workspace ] ``` ## Testing Strategies ### Run All Tests ```bash # From root npm test # All Node packages uv run pytest # All Python packages # Specific workspace npm test --workspace=@myorg/frontend uv run pytest apps/backend/ ``` ### Test Dependencies Between Packages ```typescript // packages/shared-types/src/user.ts export type User = { id: string; email: string; name: string; }; // apps/frontend/src/features/users/types.ts // Import from workspace package import type { User } from '@myorg/shared-types'; export type UserListProps = { users: User[]; onSelect: (user: User) => void; }; ``` ### Integration Tests Across Packages ```typescript // apps/frontend/tests/integration/api.test.ts import { User } from '@myorg/shared-types'; import { renderWithProviders } from '../utils/render'; describe('Frontend-Backend Integration', () => { it('should display user from API', async () => { const mockUser: User = { id: 'user-1', email: 'test@example.com', name: 'Test User', }; // Mock API response with shared type server.use( http.get('/api/users/user-1', () => HttpResponse.json(mockUser)) ); render(); await expect(screen.findByText('Test User')).resolves.toBeInTheDocument(); }); }); ``` ## CI/CD Patterns ### Change Detection ```yaml # .github/workflows/ci.yml name: CI on: push: branches: [main] pull_request: branches: [main] jobs: detect-changes: runs-on: ubuntu-latest outputs: frontend: ${{ steps.changes.outputs.frontend }} backend: ${{ steps.changes.outputs.backend }} infrastructure: ${{ steps.changes.outputs.infrastructure }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: changes with: filters: | frontend: - 'apps/frontend/**' - 'packages/shared-types/**' - 'packages/ui-components/**' backend: - 'apps/backend/**' - 'packages/shared-utils/**' infrastructure: - 'infrastructure/**' frontend: needs: detect-changes if: needs.detect-changes.outputs.frontend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' cache: 'npm' - run: npm ci - run: npm run typecheck --workspace=@myorg/frontend - run: npm run lint --workspace=@myorg/frontend - run: npm run test --workspace=@myorg/frontend backend: needs: detect-changes if: needs.detect-changes.outputs.backend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v4 - run: uv sync - run: uv run ruff check apps/backend/ - run: uv run mypy apps/backend/ - run: uv run pytest apps/backend/ --cov --cov-fail-under=80 ``` ### Jenkinsfile for Monorepo ```groovy // Jenkinsfile pipeline { agent any stages { stage('Detect Changes') { steps { script { def changes = sh( script: 'git diff --name-only HEAD~1', returnStdout: true ).trim().split('\n') env.FRONTEND_CHANGED = changes.any { it.startsWith('apps/frontend/') || it.startsWith('packages/') } env.BACKEND_CHANGED = changes.any { it.startsWith('apps/backend/') } env.INFRA_CHANGED = changes.any { it.startsWith('infrastructure/') } } } } stage('Frontend') { when { expression { env.FRONTEND_CHANGED == 'true' } } steps { dir('apps/frontend') { sh 'npm ci' sh 'npm run typecheck' sh 'npm run lint' sh 'npm run test' } } } stage('Backend') { when { expression { env.BACKEND_CHANGED == 'true' } } steps { sh 'uv sync' sh 'uv run ruff check apps/backend/' sh 'uv run pytest apps/backend/ --cov --cov-fail-under=80' } } stage('Infrastructure') { when { expression { env.INFRA_CHANGED == 'true' } } steps { dir('infrastructure/terraform') { sh 'terraform init' sh 'terraform validate' sh 'terraform fmt -check -recursive' } } } } } ``` ## Dependency Management ### Shared Dependencies at Root ```json // package.json (root) { "devDependencies": { // Shared dev dependencies "typescript": "^5.6.0", "vitest": "^3.2.0", "eslint": "^9.0.0", "@types/node": "^22.0.0" } } ``` ### Package-Specific Dependencies ```json // apps/frontend/package.json { "dependencies": { // App-specific dependencies "react": "^18.3.0", "@tanstack/react-query": "^5.0.0" } } ``` ## Commands Quick Reference ```bash # Install all dependencies npm install # Node (from root) uv sync # Python # Run in specific workspace npm run dev --workspace=@myorg/frontend npm run test --workspace=@myorg/shared-types # Run in all workspaces npm run build --workspaces npm run test --workspaces --if-present # Add dependency to specific package npm install lodash --workspace=@myorg/frontend uv add requests --package backend # Add shared dependency to root npm install -D prettier ``` ## CLAUDE.md Placement ### Root CLAUDE.md (Project-Wide) ```markdown # Project Standards [Core standards that apply everywhere] ``` ### Package-Specific CLAUDE.md ```markdown # apps/frontend/CLAUDE.md ## Frontend-Specific Standards - Use React Testing Library for component tests - Prefer Radix UI primitives - Use TanStack Query for server state ``` ```markdown # apps/backend/CLAUDE.md ## Backend-Specific Standards - Use pytest-asyncio for async tests - Pydantic v2 for all schemas - SQLAlchemy 2.0 async patterns ``` Skills in `~/.claude/skills/` are automatically available across all packages in the monorepo.