The Problem
eniem.dev is a Next.js SaaS boilerplate that ships with a CLI and a docs site built with Fumadocs. Until recently, it lived across 3 separate repos:
- eniem-boilerplate: the customer-facing boilerplate
- eniem-cli: the npm-published CLI
- eniem-docs: the documentation site
This worked fine when I was writing code manually. But once I started building with AI agents, the multi-repo setup became a real bottleneck.
The main pain point: agents can't see across repo boundaries. Building a feature on the boilerplate that needed matching docs meant switching repos, spinning up a new agent session, and re-feeding all the context. Tightly coupled CLI and boilerplate features couldn't be implemented in one session. I was constantly context-switching, and so was the AI.
On top of that, I had duplicated CI workflows across repos and conventions that were slowly diverging. Three repos for what is essentially one product didn't make sense anymore.
The Decision
I went with Turborepo + pnpm workspaces. It's what I know, it's fast, and it handles the filtering and caching I need. No comparison shopping needed, I just wanted something that works.
The constraints were clear:
- Preserve full git history for all packages (no squash-and-start-fresh)
- Keep the customer-facing boilerplate repo accessible. Customers shouldn't see the monorepo internals, they just need the boilerplate
- No collaborator migration on GitHub
- Consolidate CI across all projects
The Migration
The whole migration happened in 4 phases, each as a separate PR.
Phase 1 — Repo Consolidation
The first step was getting all the code into one place. I created a fresh repo and used git subtree add for each of the 3 existing repos:
git subtree add --prefix=apps/boilerplate <boilerplate-remote> main
git subtree add --prefix=apps/docs <docs-remote> main
git subtree add --prefix=packages/cli <cli-remote> main
The beauty of git subtree is that it preserves the full commit history. Running git log -- apps/boilerplate/ shows every commit from the original repo. No history lost.
The resulting structure:
apps/
├── boilerplate/ # The Next.js SaaS boilerplate
├── docs/ # Fumadocs documentation site
packages/
└── cli/ # npm-published CLI
Phase 2 — Post-Migration Config
With everything in one repo, the packages needed new names to avoid conflicts. I renamed eniem to @eniem/boilerplate and eniem-docs to @eniem/docs. The CLI stayed as eniem-cli since that's what's published to npm.
I also cleaned up the duplicate .github/workflows/ directories that came over with each subtree, and added semantic-release-monorepo so CLI releases would only trigger from commits touching packages/cli/.
Phase 3 — Unified GitHub Actions
This was the most involved phase. Three workflows to rule them all:
ci.yml: Runs on every PR to main. One command does it all:
pnpm turbo build lint typecheck test
Turborepo handles the dependency graph and caching. If only the docs changed, only docs tasks run.
release-cli.yml: Triggers on push to main when files in packages/cli/ change. Runs semantic-release with npm OIDC trusted publishing. semantic-release-monorepo ensures it only analyzes commits touching the CLI package.
sync-boilerplate.yml: This is the clever one. Customers clone the eniem-boilerplate repo, not the monorepo. So I needed a way to keep that repo in sync. Here's how it works:
- Triggers on
boilerplate@*tags - Rsyncs
apps/boilerplate/to a temp directory - Restores the standalone package name (
@eniem/boilerplate→eniem) with asedswap inpackage.json - Generates a standalone
pnpm-lock.yamlwithpnpm install --lockfile-only - Clones the customer repo, swaps the
.gitdirectory, commits with a changelog, and pushes with the version tag
The customer repo gets a clean, standalone package with its own lock file. No trace of the monorepo.
Phase 4 — AI Workflow Setup
The whole reason for the migration. I set up monorepo-aware .eni/ prompts at the root so agents can plan and build across all packages in one session. The standalone .eni/ in apps/boilerplate/ stays for customers who use the boilerplate on its own.
Tricky Bits
A few things that weren't obvious:
React version mismatch. The CLI uses React 18 (it builds templates, not a running app), while the boilerplate and docs run React 19. pnpm workspaces handles this fine — no forced version unification needed. Each package gets its own dependency tree.
Standalone lock file. The customer repo needs its own pnpm-lock.yaml that doesn't reference monorepo workspace packages. The trick is restoring the original package name first (sed to swap @eniem/boilerplate back to eniem), then running pnpm install --lockfile-only in the extracted directory. pnpm resolves everything as if it's a standalone project.
Scoped releases. semantic-release-monorepo plugs into semantic-release and filters the commit analysis to only consider commits that touch the package's directory. So a docs commit doesn't bump the CLI version.
Done With AI in a Day
The entire migration:
- repo consolidation
- config wiring
- three CI workflows
- AI workflow setup
All was done in a single day with AI assistance.
Without AI this would have easily been a multi-day project. The CI workflows alone would have taken hours of trial and error debugging. Instead, I described what I wanted, the agent wrote the workflows, I reviewed, and we iterated until everything passed.
The Result
One repo, one CI, one place to work.
- Turborepo filtering for targeted builds:
pnpm turbo build --filter=@eniem/boilerplate - Customer repo still gets a clean standalone package via the sync workflow
- Full git history preserved for every package
- AI agents now have full context across boilerplate, CLI, and docs in a single session
The multi-repo setup was holding back the AI workflow. Now an agent can implement a boilerplate feature, update the CLI to support it, and write the docs. All in one session, with full context. That's the real win.