- Add principle 27: 'Language-Agnostic Auto-Trigger' to engineering-principles - Remove hardcoded English-only keyword lists from all 26 skill descriptions - Replace with concept-based detection: triggers from code context, project files, and file types regardless of spoken language - Update detect-project.sh hook output with language-agnostic message
170 lines
4.8 KiB
Markdown
170 lines
4.8 KiB
Markdown
---
|
|
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 <package>`.
|
|
- **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
|