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>
4.4 KiB
4.4 KiB
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):
pnpm turbo gen feature
Non-interactive (positional bypass — order matches the prompts in
turbo/generators/config.ts):
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:
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 intracer.startSpan; the real repo also callslogger.captureExceptionon errors. The real repo body is a Phase-1 stub that returnsnulluntil you wire a Payload collection. - DI:
symbols.ts,module.ts,container.ts, plusbind-production.tsandbind-dev-seed.tsthat composewithSpan(tracer, opts, withCapture(logger, tags, factory(deps)))at bind time (post-R44 sandwich pattern) - tRPC integration:
procedures.ts(feature-scoped error middleware) androuter.tsexposingget<Entity>with full router tests includingBAD_REQUEST/NOT_FOUNDmapping - Contract suite (
__contracts__/), dev seed (__seeds__/dev.ts), and empty stubs for__factories__/andui/
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:
apps/web-next/src/server/bind-production.ts— importbindProduction<Name>andbindDevSeed<Name>and call them from thebindAll()dispatcher (production branch + dev-seed branch).packages/core-api/src/root.ts— import<name>Routerfrom@repo/<name>/apiand mount it on the app router.packages/core-api/package.json— add"@repo/<name>": "workspace:*"to dependencies.apps/web-next/package.json— add"@repo/<name>": "workspace:*"to dependencies.- (Later) Payload CMS — add a collection at
packages/<name>/src/integrations/cms/collections/<entities-plural>.tsand register it inpackages/core-cms/.... - 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 mirrordocs/architecture/vertical-feature-spec.md— design rationale for the layoutdocs/decisions/adr-012-lazar-conformance.md— file naming + factory patterndocs/decisions/adr-013-input-output-unification.md— schemas-in-use-case + presenterdocs/decisions/adr-014-instrumentation-sentry.md— span + capture wiring (R41–R44)