From 300143e7e105bb7092926a2cf178719a1fd083f6 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Wed, 13 May 2026 00:02:35 +0200 Subject: [PATCH] feat(generators): emit feature.manifest.ts + self-asserting bind-production --- turbo/generators/config.ts | 5 ++++ .../feature/src/di/bind-production.ts.hbs | 13 +++++++++ .../feature/src/feature.manifest.ts.hbs | 29 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 turbo/generators/templates/feature/src/feature.manifest.ts.hbs diff --git a/turbo/generators/config.ts b/turbo/generators/config.ts index c8f22e2..75b2bba 100644 --- a/turbo/generators/config.ts +++ b/turbo/generators/config.ts @@ -120,6 +120,11 @@ export default function generator(plop: PlopTypes.NodePlopAPI): void { path: "packages/{{kebabCase name}}/src/index.ts", templateFile: "templates/feature/src/index.ts.hbs", }, + { + type: "add", + path: "packages/{{kebabCase name}}/src/feature.manifest.ts", + templateFile: "templates/feature/src/feature.manifest.ts.hbs", + }, // Entities — models + errors { diff --git a/turbo/generators/templates/feature/src/di/bind-production.ts.hbs b/turbo/generators/templates/feature/src/di/bind-production.ts.hbs index 0e40e01..6832e06 100644 --- a/turbo/generators/templates/feature/src/di/bind-production.ts.hbs +++ b/turbo/generators/templates/feature/src/di/bind-production.ts.hbs @@ -11,6 +11,8 @@ import { {{constantCase name}}_SYMBOLS } from "./symbols"; import { {{pascalCase entity}}Repository } from "../infrastructure/repositories/{{kebabCase entity}}.repository"; import { get{{pascalCase entity}}UseCase } from "../application/use-cases/get-{{kebabCase entity}}.use-case"; import { get{{pascalCase entity}}Controller } from "../interface-adapters/controllers/get-{{kebabCase entity}}.controller"; +import { assertFeatureConformance } from "@repo/core-shared/conformance"; +import { {{camelCase name}}Manifest } from "../feature.manifest"; export function bindProduction{{pascalCase name}}(ctx: BindProductionContext): void { const { config, tracer, logger, bus, queue, realtime, realtimeRegistry } = ctx; @@ -73,4 +75,15 @@ export function bindProduction{{pascalCase name}}(ctx: BindProductionContext): v // // // + + // Boot-time conformance check: refuses to start if any use-case binding + // is missing a required brand (withSpan / withCapture / withAudit). + assertFeatureConformance( + {{camelCase name}}Container, + {{camelCase name}}Manifest, + { + get{{pascalCase entity}}: {{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase, + }, + ctx, + ); } diff --git a/turbo/generators/templates/feature/src/feature.manifest.ts.hbs b/turbo/generators/templates/feature/src/feature.manifest.ts.hbs new file mode 100644 index 0000000..7bb3900 --- /dev/null +++ b/turbo/generators/templates/feature/src/feature.manifest.ts.hbs @@ -0,0 +1,29 @@ +import { defineFeature } from "@repo/core-shared/conformance"; + +/** + * The {{camelCase name}} feature's conformance manifest. Drives binding-slot + * types in `di/bind-production.ts` and is read by ESLint, the boot + * assertion, and the CI drift gate. + * + * Conventions: + * - `mutates: true` for any use case that creates, updates, or deletes state + * - `audits` lists every audit event the use case emits (must match calls + * to `auditLog.record(...)` in the factory body — ESLint enforces this) + * - `publishes` / `consumes` cover cross-feature events through `IEventBus` + */ +export const {{camelCase name}}Manifest = defineFeature({ + name: "{{kebabCase name}}", + requiredCores: [], + useCases: { + get{{pascalCase entity}}: { + mutates: false, + audits: [], + publishes: [], + consumes: [], + }, + }, + realtimeChannels: [], + jobs: [], +} as const); + +export type {{pascalCase name}}Manifest = typeof {{camelCase name}}Manifest;