Scaffold a production monorepo: Nx vs Turborepo decision, workspace structure, shared packages, CI matrix builds, versioning strategy (fixed vs. independent), and dependency graph visualization.
From sanpx claudepluginhub javimontano/jm-adk --plugin sovereign-architectThis skill is limited to using the following tools:
agents/scaffold-monorepo-agent.mdevals/evals.jsonexamples/sample-output.mdprompts/use-case-prompts.mdreferences/body-of-knowledge.mdreferences/knowledge-graph.mmdreferences/state-of-the-art.md"A monorepo is an organizational decision before it's a tooling decision — get the ownership model right first."
Five-step procedure to scaffold a monorepo with correct workspace tooling, shared package conventions, CI matrix strategy, and version management — with an explicit Nx vs Turborepo decision framework based on team size and ecosystem fit.
| Criterion | Choose Nx | Choose Turborepo |
|---|---|---|
| Codebase size | Large (20+ packages) | Any size |
| Code generation needs | Yes (generators) | No |
| Angular or Nest.js present | Yes | No |
| Distributed task execution | Yes (Nx Cloud) | Yes (Vercel Remote Cache) |
| Learning curve tolerance | Medium | Low |
| Plugin ecosystem depth | High | Growing |
| Full repo visualization | Yes (graph command) | Limited |
| Independent version per package | Yes | Yes (via Changesets) |
Lean toward Nx when: multiple app types, need generators, enterprise scale. Lean toward Turborepo when: mostly JS/TS, simpler mental model preferred, Vercel hosting.
my-monorepo/
├── apps/
│ ├── web/ # Next.js / React app
│ ├── api/ # Express / Fastify / NestJS API
│ └── mobile/ # React Native / Expo
├── packages/
│ ├── ui/ # Shared component library
│ ├── types/ # Shared TypeScript types (no runtime)
│ ├── utils/ # Shared utility functions
│ ├── config/ # Shared configs (ESLint, tsconfig, tailwind)
│ └── database/ # Prisma schema + generated client
├── tools/
│ └── scripts/ # Repo-wide automation scripts
├── .changeset/ # Changesets for versioning
├── pnpm-workspace.yaml
├── package.json # Root — devDependencies only
├── turbo.json OR nx.json
└── tsconfig.base.json
pnpm-workspace.yamlpackages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
package.json{
"name": "my-monorepo",
"private": true,
"engines": { "node": ">=20", "pnpm": ">=9" },
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"test": "turbo test",
"lint": "turbo lint",
"typecheck": "turbo typecheck",
"clean": "turbo clean && rm -rf node_modules"
},
"devDependencies": {
"turbo": "^2.0.0",
"@changesets/cli": "^2.27.0",
"typescript": "^5.4.0"
},
"packageManager": "pnpm@9.0.0"
}
packages/types — Zero-runtime shared types// packages/types/src/index.ts
export interface User {
id: string;
email: string;
createdAt: Date;
}
export interface ApiResponse<T> {
data: T;
error?: string;
meta?: { page: number; total: number };
}
// packages/types/package.json
{
"name": "@myrepo/types",
"version": "0.0.1",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": { "import": "./src/index.ts" }
},
"scripts": {
"typecheck": "tsc --noEmit"
}
}
tsconfig.base.json{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"paths": {
"@myrepo/types": ["./packages/types/src/index.ts"],
"@myrepo/utils": ["./packages/utils/src/index.ts"],
"@myrepo/ui": ["./packages/ui/src/index.ts"]
}
}
}
turbo.json — Pipeline Configuration{
"$schema": "https://turbo.build/schema.json",
"globalEnv": ["NODE_ENV", "DATABASE_URL"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**", "!dist/cache/**"],
"env": ["NEXT_PUBLIC_*"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"env": ["DATABASE_URL_TEST"]
},
"lint": { "outputs": [] },
"typecheck": { "dependsOn": ["^typecheck"], "outputs": [] },
"clean": { "cache": false }
}
}
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 2 }
- uses: pnpm/action-setup@v4
with: { version: 9 }
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Restore Turbo Cache
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- run: pnpm turbo build test lint typecheck
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
pnpm add -D @changesets/cli -w
pnpm changeset init
// .changeset/config.json
{
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["@myrepo/config"]
}
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'pnpm' }
- run: pnpm install --frozen-lockfile
- uses: changesets/action@v1
with:
publish: pnpm changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
^build dependency in turbo — Packages build in dependency order, never stale outputs.packageManager field in root package.json — Enforces consistent package manager version.--frozen-lockfile in CI — Prevents silent lockfile mutations in CI.tsconfig.base.json — All packages inherit base config; no per-package duplication.private: true in package root — Prevents accidental publish of root workspace.packages/*, never from other apps.packages/ui importing from packages/database while packages/database imports types from packages/ui deadlocks the build graph.workspace:* version without resolution — Publishing packages with unresolved workspace:* versions breaks consumers outside the monorepo.packages/ as a publishable package — Config-only packages (ESLint config, tsconfig) belong in packages/config, not as publishable packages.outputs in turbo.json — Turbo doesn't know what to cache; every task re-runs on cache hit.apps/web importing from apps/api creates tight coupling; share through packages/ instead..npmrc.Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.