development-monorepo-pnpm skill: initial import
Full-stack pnpm + Turborepo monorepo skill, independent of the separate backend and frontend monorepo skills it draws on. Repository shape is split four ways at the top level: applications-backend, applications-frontend, packages-backend, packages-frontend — so a path states which runtime a file ends up in. Defaults: NestJS for backend services, React with IBM Carbon Design for browser applications; the project's stated architecture overrides both. Covers workspace globs, the Turborepo pipeline, filtered validation, blast-radius checks, Module Federation, turbo prune image builds, and Helm. Documents one sanctioned cross-boundary dependency: API contract types owned by the backend, imported type-only by the frontend. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
---
|
||||
name: development-monorepo-pnpm
|
||||
description: >
|
||||
Create, review, refactor, debug, and maintain full-stack monorepos built on pnpm workspaces
|
||||
and Turborepo, split four ways — applications-backend, applications-frontend,
|
||||
packages-backend, packages-frontend. Backend services default to NestJS; browser
|
||||
applications default to React with IBM Carbon Design, unless the project architecture states
|
||||
otherwise. Use when adding or changing a service, controller, module, React page, component
|
||||
or hook; assigning ports and API base paths; sharing API contract types across the
|
||||
front/back boundary; wiring Module Federation; editing shared eslint or typescript presets;
|
||||
cutting changesets; editing Dockerfiles, Helm charts, or Turborepo tasks; running filtered
|
||||
validation or blast-radius checks; or fixing workspace, dependency, lockfile, or
|
||||
build-pipeline problems — even when the user only names their repository instead of saying
|
||||
"monorepo".
|
||||
license: Proprietary
|
||||
metadata:
|
||||
author: workspace-skills-code-agent
|
||||
version: "1.0"
|
||||
spec: agentskills.io/specification
|
||||
compatibility: Requires pnpm (via corepack) and Node. Designed for Cline, Claude Code, GitHub Copilot, OpenAI Codex, and other compatible agent environments
|
||||
---
|
||||
|
||||
# Full-Stack pnpm + Turborepo Monorepo
|
||||
|
||||
Work rules for monorepos holding **both** backend services and browser applications, using
|
||||
**pnpm workspaces** for dependency resolution and **Turborepo** for task orchestration.
|
||||
|
||||
The defining choice in this layout is that the front/back split is expressed in the
|
||||
**directory names themselves**, not in a nested convention. Four top-level workspace roots,
|
||||
each with one job.
|
||||
|
||||
## Scope routing
|
||||
|
||||
Pick the reference that matches the change; do not load all of them.
|
||||
|
||||
| The change touches | Read |
|
||||
|---|---|
|
||||
| A service under `applications-backend/*` — modules, controllers, providers, config | `references/applications-backend.md` |
|
||||
| An app under `applications-frontend/*` — components, routing, Carbon, Module Federation | `references/applications-frontend.md` |
|
||||
| Anything under `packages-backend/*` or `packages-frontend/*` — exports, presets, changesets | `references/packages.md` |
|
||||
| `pnpm-workspace.yaml`, `turbo.json`, root scripts, formatting, dependency overrides | `references/workspace-and-tooling.md` |
|
||||
| Dockerfiles, images, Helm charts, values, deploy automation | `references/containers-and-helm.md` |
|
||||
|
||||
## 1. Canonical repository shape
|
||||
|
||||
```text
|
||||
<repo>/
|
||||
├── applications-backend/ # deployable services — NestJS by default
|
||||
│ └── <service-name>/
|
||||
├── applications-frontend/ # deployable browser apps — React + Carbon by default
|
||||
│ └── <app-name>/
|
||||
├── packages-backend/ # libraries consumed by services
|
||||
│ └── <package-name>/
|
||||
├── packages-frontend/ # libraries consumed by browser apps
|
||||
│ └── <package-name>/
|
||||
├── helm-charts/<chart-name>/ # deployment assets
|
||||
├── .scripts/ # shell modules: docker, images, helm, pipelines
|
||||
├── package.json # root scripts + packageManager + engines + overrides
|
||||
├── pnpm-workspace.yaml # the four globs below
|
||||
├── turbo.json # task pipeline
|
||||
├── pnpm-lock.yaml
|
||||
├── .prettierrc # single formatting authority
|
||||
└── Taskfile.yml # thin delegator — includes .scripts/ modules only
|
||||
```
|
||||
|
||||
```yaml
|
||||
# pnpm-workspace.yaml
|
||||
packages:
|
||||
- "applications-backend/*"
|
||||
- "applications-frontend/*"
|
||||
- "packages-backend/*"
|
||||
- "packages-frontend/*"
|
||||
```
|
||||
|
||||
Applications sit **directly** under their root — `applications-backend/<name>`, not
|
||||
`applications-backend/<name>/core`. The four-way split already gives each side its own
|
||||
namespace, so no extra nesting layer is needed.
|
||||
|
||||
**A workspace exists only if a glob matches it.** Creating a directory is not enough: confirm
|
||||
the glob, then run `pnpm install`. A directory outside these four roots is not part of the
|
||||
build graph, and Turborepo will silently ignore it.
|
||||
|
||||
### Why the split is in the directory name
|
||||
|
||||
One glance at a path tells you which runtime a file ends up in. That matters because the two
|
||||
sides have genuinely different constraints — a browser bundle ships to untrusted clients and
|
||||
pays for every kilobyte, a service does not; a service holds credentials, a bundle must never.
|
||||
When both live under one `packages/` root, the only thing standing between a secret-handling
|
||||
utility and a browser bundle is someone reading the import path carefully.
|
||||
|
||||
## 2. Default stacks
|
||||
|
||||
| Layer | Default | Applies unless |
|
||||
|---|---|---|
|
||||
| `applications-backend/*` | **NestJS** — modules, controllers, providers, DI | the project architecture specifies another framework |
|
||||
| `applications-frontend/*` | **React** with **IBM Carbon Design** (`@carbon/react`) | the project architecture specifies another framework or design system |
|
||||
|
||||
These are defaults, not mandates. **The project's stated architecture always wins** — read the
|
||||
paired code-agent repository's `memory-bank/` (§9) and any architecture decision record
|
||||
*before* scaffolding. If the architecture is silent, use the defaults above and record the
|
||||
choice; if it disagrees, follow it and do not "correct" it toward these defaults.
|
||||
|
||||
Do not mix design systems inside one frontend application. Carbon components and a second
|
||||
component library in the same app produce two type scales, two grid systems, and a token set
|
||||
that fights itself.
|
||||
|
||||
## 3. The one sanctioned cross-boundary dependency
|
||||
|
||||
Frontend workspaces must not import from `packages-backend/*`, and backend workspaces must not
|
||||
import from `packages-frontend/*` — with exactly one exception:
|
||||
|
||||
**API contract types.** The service that defines an endpoint owns its contract, so contract
|
||||
packages live under `packages-backend/<domain>-contracts` and expose **types only** — no
|
||||
runtime code, no dependencies, no side effects. Frontend consumers import them type-only:
|
||||
|
||||
```ts
|
||||
import type { UserDto } from "@<scope>/user-contracts";
|
||||
```
|
||||
|
||||
`import type` is erased at compile time, so nothing from the backend package reaches the
|
||||
browser bundle. A plain `import` from the same package does reach it — that is the failure
|
||||
this rule exists to prevent, and it will not show up as a type error.
|
||||
|
||||
A contract package that acquires a runtime dependency has stopped being a contract package.
|
||||
Split it before it ships anything into a bundle.
|
||||
|
||||
## 4. Tooling baseline
|
||||
|
||||
Root `package.json` pins the contract for every contributor and CI runner: `packageManager`
|
||||
(exact pnpm version, read by corepack), `engines.node`, `pnpm.overrides` for security pins,
|
||||
and root scripts that always delegate to Turborepo — never to a single workspace.
|
||||
|
||||
```bash
|
||||
pnpm build # turbo run build
|
||||
pnpm dev # turbo run dev
|
||||
pnpm lint # turbo run lint
|
||||
pnpm check-types # turbo run check-types
|
||||
pnpm format # prettier --write
|
||||
pnpm publish-packages # build:ci + lint, then changeset version && changeset publish
|
||||
pnpm prepush # turbo build --filter=...[HEAD^] — only what the branch touched
|
||||
```
|
||||
|
||||
`build` depends on `^build`, `check-types`, and `lint`, so a type error or lint error is a
|
||||
build failure for every downstream workspace, not a warning. Full pipeline, cache outputs, and
|
||||
override rules → `references/workspace-and-tooling.md`.
|
||||
|
||||
## 5. Before making changes
|
||||
|
||||
1. Identify the exact workspace path, and therefore which of the four roots you are in.
|
||||
2. Read that workspace's `package.json` — scripts, dependencies, whether it is publishable.
|
||||
3. Inspect its `tsconfig.json`, lint config, build config, and source layout before adding.
|
||||
4. Map what it consumes from shared packages, and what consumes it.
|
||||
5. For Module Federation changes, map the host↔remote contract before editing either side.
|
||||
6. Read the paired code-agent repository's `rules/` and `memory-bank/` when one exists (§9) —
|
||||
it carries architecture decisions the code does not.
|
||||
|
||||
```bash
|
||||
find applications-backend applications-frontend -maxdepth 2 -name package.json -print
|
||||
find packages-backend packages-frontend -maxdepth 2 -name package.json -print
|
||||
pnpm list --depth 0
|
||||
task --list
|
||||
```
|
||||
|
||||
## 6. Implementation rules
|
||||
|
||||
### Workspace boundaries
|
||||
|
||||
- Application logic stays inside its own workspace.
|
||||
- Promote into a shared package only when **more than one** application needs it *and* the
|
||||
abstraction has stopped changing. Config presets are the exception — shared from day one.
|
||||
Premature promotion is the most expensive mistake here: every consumer inherits the churn.
|
||||
- A shared package goes in the root matching its **consumers**, never its author.
|
||||
- Never cross a workspace boundary with a relative import (`../../other-app/src`). Depend on
|
||||
the workspace package, or the Module Federation contract, or not at all.
|
||||
|
||||
### TypeScript
|
||||
|
||||
- Every workspace extends a shared config from the `typescript-config` preset. Do not restate
|
||||
`compilerOptions` locally beyond `outDir`, `rootDir`, `include`, `exclude`.
|
||||
- `check-types` is `tsc --noEmit`, named identically in every workspace so Turborepo can run
|
||||
it across the graph.
|
||||
- Keep types explicit at public boundaries; avoid broad `any`.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Add a dependency to the **smallest** workspace that needs it.
|
||||
- Root dependencies are monorepo tooling only — turbo, prettier, husky, changesets,
|
||||
typescript. Nothing an application imports at runtime.
|
||||
- Inter-workspace dependencies use `workspace:*`.
|
||||
- Run `pnpm install` after every `package.json` edit and commit the lockfile change with the
|
||||
edit that caused it.
|
||||
- Never remove a `pnpm.overrides` entry without a security review — removing one silently
|
||||
reintroduces a vulnerable transitive version and nothing in the build complains.
|
||||
|
||||
### Formatting
|
||||
|
||||
Root `.prettierrc` is the single authority. Do not add per-package Prettier config; a second
|
||||
config produces churn diffs every time someone formats a file from the other side.
|
||||
|
||||
## 7. Validation
|
||||
|
||||
Validate the narrowest scope covering the change, then widen if it touched shared code.
|
||||
|
||||
```bash
|
||||
# Single workspace
|
||||
pnpm check-types --filter="@<scope>/<name>"
|
||||
pnpm lint --filter="@<scope>/<name>"
|
||||
pnpm build --filter="@<scope>/<name>"
|
||||
|
||||
# Shared package or cross-workspace change — unfiltered, plus each consumer
|
||||
pnpm check-types && pnpm lint && pnpm build
|
||||
pnpm build --filter="@<scope>/<consumer>"
|
||||
|
||||
# Only what the branch changed
|
||||
pnpm prepush
|
||||
```
|
||||
|
||||
Module Federation changes additionally require **host and remote running together** — a green
|
||||
build on each side separately proves nothing about a runtime contract:
|
||||
|
||||
```bash
|
||||
pnpm dev --filter="@<scope>/<host>"
|
||||
pnpm dev --filter="@<scope>/<remote>"
|
||||
```
|
||||
|
||||
Record the commands run and their outcome in the task record (§9). "It builds", without the
|
||||
filter you used, is not a validation result.
|
||||
|
||||
## 8. Blast-radius checks
|
||||
|
||||
Before changing anything another workspace can observe — an exported symbol, a REST base path,
|
||||
a port, an environment variable name, an MFE `exposes` key:
|
||||
|
||||
1. Enumerate the surface.
|
||||
2. Search every consumer across all four roots.
|
||||
3. Decide explicitly whether the change is backward-compatible.
|
||||
4. If it is not: update consumers in the same change, or ship a compatible wrapper first.
|
||||
5. Validate each affected consumer with its own `--filter` build.
|
||||
|
||||
```bash
|
||||
grep -Rn "from '@<scope>/<package>'" applications-backend applications-frontend \
|
||||
packages-backend packages-frontend
|
||||
grep -Rn "api/<name>" applications-frontend # base-path callers
|
||||
```
|
||||
|
||||
A port or base path change is never local: it also lands in the Helm chart values, the gateway
|
||||
routing, and every frontend caller. See `references/containers-and-helm.md`.
|
||||
|
||||
## 9. Durable records
|
||||
|
||||
Monorepos in this family pair with a `<repo>-code-agent/` repository holding `rules/`,
|
||||
`memory-bank/`, and `tasks/`. Update `memory-bank/` when durable facts change — application
|
||||
inventory, ports, base paths, MFE topology, chosen frameworks and design system, chart
|
||||
structure. Create a task record under `tasks/` for substantial work.
|
||||
|
||||
Never place task records inside the target repository unless explicitly asked.
|
||||
|
||||
## 10. Common mistakes to avoid
|
||||
|
||||
- Importing a `packages-backend/*` package from a frontend workspace with a value import
|
||||
instead of `import type` — it silently pulls server code into the browser bundle.
|
||||
- Putting a shared package in the root that matches its author rather than its consumers.
|
||||
- Adding a workspace outside the four roots, or without updating `pnpm-workspace.yaml`.
|
||||
- Re-introducing a `core/` nesting layer under an application directory.
|
||||
- Changing a service port or base path without updating Helm values, gateway routing, and
|
||||
every caller.
|
||||
- Changing an MFE `exposes` key, remote name, or remote URL on one side only.
|
||||
- Removing a `pnpm.overrides` entry without a security review.
|
||||
- Adding a root-level dependency for a need local to one workspace.
|
||||
- Skipping `--filter` validation for the exact workspace you edited.
|
||||
- Mixing a second component library into a Carbon application.
|
||||
- Overriding the project's stated architecture because it differs from the defaults in §2.
|
||||
- Adding tasks directly to root `Taskfile.yml` instead of a `.scripts/` module.
|
||||
- Editing generated artifacts (`dist/`, rendered manifests, lockfiles) by hand.
|
||||
- Committing `.env`, `key.pem`, `cert.pem`, registry tokens, or `.npmrc` secrets — including
|
||||
into memory-bank or task documentation.
|
||||
|
||||
## Reference files
|
||||
|
||||
| File | Contents | Read when |
|
||||
|---|---|---|
|
||||
| `references/applications-backend.md` | NestJS service layout, modules/controllers/providers, config and env, ports and base paths, health checks, service Dockerfile | Touching `applications-backend/*` |
|
||||
| `references/applications-frontend.md` | React app layout, IBM Carbon setup and theming, routing, state, Module Federation wiring and failure modes, nginx image | Touching `applications-frontend/*` |
|
||||
| `references/packages.md` | Package tiers per root, contract packages, public-API safety, export and dependency rules, changesets and publishing | Touching `packages-backend/*` or `packages-frontend/*` |
|
||||
| `references/workspace-and-tooling.md` | Workspace globs, `turbo.json` pipeline and caching, root scripts, Prettier, `pnpm.overrides`, lockfile discipline | Touching root config or the task pipeline |
|
||||
| `references/containers-and-helm.md` | `turbo prune` image builds for both sides, chart layout, values and secrets, port/base-path propagation | Touching Dockerfiles, images, or charts |
|
||||
| `assets/example-shared-package/` | Minimal shared TypeScript package — ESM, barrel export, shared tsconfig and eslint | Scaffolding a package under either packages root |
|
||||
Reference in New Issue
Block a user