| name | monorepo-management |
| description | Structure and manage monorepos with multiple packages or services. Outputs workspace configuration, build pipeline, dependency graph, code ownership rules, and CI optimisation strategy. |
| argument-hint | ["language ecosystem","number of packages","team size","CI system"] |
| allowed-tools | Read, Write, Bash |
Monorepo Management
A monorepo houses multiple packages or services in one repository. Atomic cross-package changes, shared tooling, and unified CI are the wins. The cost is build times and tooling complexity. The answer is build caching, affected-only CI, and explicit dependency management.
Process
- Choose tooling. Turborepo (JS/TS), Nx (JS/TS with plugins), Bazel (polyglot, large scale), Pants (Python), Gradle multi-project (JVM).
- Define workspace structure. Separate apps, packages (libraries), and infrastructure. Clear naming conventions.
- Declare dependencies explicitly. Every package lists its dependencies. No implicit cross-package imports.
- Configure build pipeline. Task graph: build depends on lint + type-check; test depends on build; deploy depends on test.
- Enable caching. Local cache first, remote cache for CI sharing. Cache keys based on inputs, not time.
- Set up affected-only CI. Only run pipelines for packages changed by a PR. Use dependency graph to include transitively affected packages.
- Define code ownership. CODEOWNERS file per package. Reviews required from owning team.
- Version strategy. Independent versions per package (semver) or fixed/unified versioning.
Directory Structure
monorepo/
├── apps/ # Deployable applications
│ ├── api/ # REST API service
│ ├── web/ # Frontend app
│ └── worker/ # Background job processor
├── packages/ # Shared libraries
│ ├── ui/ # Design system components
│ ├── auth/ # Authentication library
│ ├── database/ # DB client + migrations
│ ├── config/ # Shared configuration
│ └── types/ # Shared TypeScript types
├── tools/ # Internal tooling
│ ├── eslint-config/
│ └── tsconfig/
├── infrastructure/ # IaC
│ ├── terraform/
│ └── k8s/
├── turbo.json # Turborepo config
├── pnpm-workspace.yaml # Workspace definition
├── package.json
└── .github/
├── CODEOWNERS
└── workflows/
Turborepo Configuration
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [".env", "tsconfig.base.json"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"],
"cache": true
},
"lint": {
"outputs": [],
"cache": true
},
"type-check": {
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
{
"name": "@company/auth",
"version": "1.4.2",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"lint": "eslint src/",
"test": "vitest run",
"type-check": "tsc --noEmit"
},
"dependencies": {
"jsonwebtoken": "^9.0.0"
},
"devDependencies": {
"@company/tsconfig": "workspace:*"
},
"peerDependencies": {
Nx Configuration (Alternative)
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "lint", "test", "type-check"],
"remoteCache": { "enabled": true }
}
}
},
"targetDefaults": {
"build": { "dependsOn": ["^build"] },
"test": { "dependsOn": [
CI — Affected-Only Pipeline
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
affected:
runs-on: ubuntu-latest
outputs:
apps: ${{ steps.affected.outputs.apps }}
packages: ${{ steps.affected.outputs.packages }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Determine affected packages
id: affected
{ }
{ }
CODEOWNERS
# .github/CODEOWNERS
# Default owners for everything
* @company/platform-team
# App ownership
/apps/api/ @company/backend-team
/apps/web/ @company/frontend-team
/apps/worker/ @company/backend-team
# Package ownership
/packages/ui/ @company/design-system-team
/packages/auth/ @company/security-team
/packages/database/ @company/platform-team
# Infrastructure
/infrastructure/ @company/infra-team
# CI/CD changes require platform approval
/.github/ @company/platform-team
/turbo.json @company/platform-team
/pnpm-workspace.yaml @company/platform-team
Dependency Graph Management
pnpm turbo run build --graph
pnpm turbo run build --filter='...^@company/auth'
pnpm turbo run test --filter='@company/api...'
pnpm turbo run build --dry-run --filter='...[origin/main]'
npx nx graph
npx madge --circular --extensions ts apps/ packages/
Anti-Patterns to Avoid
| Anti-Pattern | Problem | Fix |
|---|
| Implicit cross-package imports | Bypasses declared dependencies; breaks build | Import only from published package entry points |
| No remote cache | Every CI run rebuilds from scratch | Set up Turborepo Remote Cache or Nx Cloud |
| Running all tests on every PR | CI takes 30min+ | Affected-only pipeline |
| Shared mutable global config | One package's change breaks others silently | Explicit config per package; extend shared base |
| Circular dependencies | Build order undefined, tests unreliable | Enforce with madge in CI |
| No CODEOWNERS | PRs merge without domain expert review | CODEOWNERS + required reviews per directory |
| Versioning confusion | Mixed independent/unified versioning | Choose one; use Changesets for independent semver |
10 Rules
- Every package is independently buildable with its explicit dependencies only.
- Remote cache is mandatory in CI — local-only cache gives no CI speedup.
- Affected-only CI is a requirement, not an optimisation, past 10 packages.
- No circular dependencies — enforce with automated detection in CI.
- Package boundaries = team ownership boundaries. Align with CODEOWNERS.
- Shared packages are products — breaking changes require migration guides and version bumps.
- Dev scripts run concurrently with watch mode —
turbo run dev should start everything needed.
- Pin internal package versions with
workspace:* — never use path imports across packages.
- Cache keys must include all inputs — env vars, config files, lock files. Missed inputs cause stale caches.
- Monorepo doesn't mean monolith — packages must have clear boundaries, separate deployable artifacts, and independent test suites.