feat(turbo): turbo gen feature generator (Phase 1, single-entity)
Adds `pnpm turbo gen feature` to scaffold a Lazar-conformant feature package matching the navigation reference shape: entity + Zod schema, single use case (`get<Entity>`), controller, mock + Payload-stub real repository (with span + capture), DI module/container/symbols, and tRPC router with full BAD_REQUEST/NOT_FOUND error mapping. The generated `bind-production.ts` and `bind-dev-seed.ts` compose the post-R44 `withSpan(tracer, opts, withCapture(logger, tags, factory(deps)))` sandwich at bind time. Verified by generating a sample `packages/example/` feature and running `pnpm --filter @repo/example lint typecheck test` — all three pass (9 test files, 25 tests). Cleaned up after verification so no example package is committed. Phase-1 limitations (documented in `docs/guides/scaffolding-a-feature.md` and printed by the generator on success): no Payload CMS templates, no React Query helpers, faker-driven factories left as stubs, single entity / single use case, and aggregator wiring (core-api/root, apps/web-next bindAll) is left as a manual checklist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
101
docs/guides/scaffolding-a-feature.md
Normal file
101
docs/guides/scaffolding-a-feature.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Scaffolding a feature
|
||||
|
||||
`turbo gen feature` produces a Lazar-conformant feature package under
|
||||
`packages/<name>/` matching the shape of the reference `navigation` feature.
|
||||
|
||||
## Invoking the generator
|
||||
|
||||
Interactive (recommended for first runs — Plop will prompt for each value):
|
||||
|
||||
```bash
|
||||
pnpm turbo gen feature
|
||||
```
|
||||
|
||||
Non-interactive (positional bypass — order matches the prompts in
|
||||
`turbo/generators/config.ts`):
|
||||
|
||||
```bash
|
||||
pnpm turbo gen feature --args <name> <Entity> <entities-plural>
|
||||
```
|
||||
|
||||
| Position | Prompt | Example | Conventions |
|
||||
|---|---|---|---|
|
||||
| `<name>` | Feature package name | `widgets` | `kebab-case`, becomes `@repo/<name>` and `packages/<name>/` |
|
||||
| `<Entity>` | Entity name | `Widget` | `PascalCase` singular, drives class/symbol/use-case names |
|
||||
| `<entities-plural>` | Entity plural slug | `widgets` | `kebab-case`, used for the future Payload collection slug |
|
||||
|
||||
Example end-to-end:
|
||||
|
||||
```bash
|
||||
pnpm turbo gen feature --args widgets Widget widgets
|
||||
pnpm install # link the new workspace package
|
||||
pnpm --filter @repo/widgets lint typecheck test
|
||||
```
|
||||
|
||||
## What it generates
|
||||
|
||||
- Package files: `package.json`, `tsconfig.json`, `vitest.config.ts`,
|
||||
`eslint.config.js`, `turbo.json`, `AGENTS.md`
|
||||
- One entity (`src/entities/models/<entity>.ts`) with a Zod schema +
|
||||
unit test
|
||||
- One use case (`src/application/use-cases/get-<entity>.use-case.ts`) with
|
||||
exported input/output schemas, factory function, and tests
|
||||
- One controller (`src/interface-adapters/controllers/get-<entity>.controller.ts`)
|
||||
with the canonical safeParse → presenter shape
|
||||
- Mock + real repository (`src/infrastructure/repositories/<entity>.repository{,.mock}.ts`).
|
||||
Both wrap calls in `tracer.startSpan`; the real repo also calls
|
||||
`logger.captureException` on errors. The real repo body is a Phase-1
|
||||
stub that returns `null` until you wire a Payload collection.
|
||||
- DI: `symbols.ts`, `module.ts`, `container.ts`, plus
|
||||
`bind-production.ts` and `bind-dev-seed.ts` that compose
|
||||
`withSpan(tracer, opts, withCapture(logger, tags, factory(deps)))` at
|
||||
bind time (post-R44 sandwich pattern)
|
||||
- tRPC integration: `procedures.ts` (feature-scoped error middleware) and
|
||||
`router.ts` exposing `get<Entity>` with full router tests including
|
||||
`BAD_REQUEST` / `NOT_FOUND` mapping
|
||||
- Contract suite (`__contracts__/`), dev seed (`__seeds__/dev.ts`), and
|
||||
empty stubs for `__factories__/` and `ui/`
|
||||
|
||||
## Phase-1 scope (intentionally limited)
|
||||
|
||||
The generator does NOT yet emit:
|
||||
|
||||
- Payload CMS collection / global templates (`integrations/cms/**`)
|
||||
- React Query option builders (`ui/query.ts`)
|
||||
- Faker-driven `defineFactory<Entity>` factories (only stubs)
|
||||
- Multi-entity / multi-use-case (one `get<Entity>` only)
|
||||
- Aggregator wiring across the monorepo
|
||||
|
||||
Add these by hand once the entity stabilises. The generator's stub files
|
||||
clearly mark each `TODO`.
|
||||
|
||||
## Manual aggregator wiring (printed on success)
|
||||
|
||||
After running the generator, hand-edit these files to mount the new
|
||||
feature on the app's runtime composition graph:
|
||||
|
||||
1. **`apps/web-next/src/server/bind-production.ts`** — import
|
||||
`bindProduction<Name>` and `bindDevSeed<Name>` and call them from the
|
||||
`bindAll()` dispatcher (production branch + dev-seed branch).
|
||||
2. **`packages/core-api/src/root.ts`** — import `<name>Router` from
|
||||
`@repo/<name>/api` and mount it on the app router.
|
||||
3. **`packages/core-api/package.json`** — add `"@repo/<name>": "workspace:*"`
|
||||
to dependencies.
|
||||
4. **`apps/web-next/package.json`** — add `"@repo/<name>": "workspace:*"`
|
||||
to dependencies.
|
||||
5. **(Later) Payload CMS** — add a collection at
|
||||
`packages/<name>/src/integrations/cms/collections/<entities-plural>.ts`
|
||||
and register it in `packages/core-cms/...`.
|
||||
6. **Verify**: `pnpm --filter @repo/<name> lint typecheck test`
|
||||
|
||||
The same checklist is printed by the generator when it finishes.
|
||||
|
||||
## Cross-links
|
||||
|
||||
- `CLAUDE.md` — Key Conventions (factory-style use cases, `.toDynamicValue()`,
|
||||
schemas-in-use-case, three binding modes per feature, span + capture sandwich)
|
||||
- `packages/navigation/AGENTS.md` — canonical reference shape the templates mirror
|
||||
- `docs/architecture/vertical-feature-spec.md` — design rationale for the layout
|
||||
- `docs/decisions/adr-012-lazar-conformance.md` — file naming + factory pattern
|
||||
- `docs/decisions/adr-013-input-output-unification.md` — schemas-in-use-case + presenter
|
||||
- `docs/decisions/adr-014-instrumentation-sentry.md` — span + capture wiring (R41–R44)
|
||||
Reference in New Issue
Block a user