@pattern-stack/codegen 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +2 -1
  2. package/dist/runtime/shared/openapi/error-response.dto.d.ts +33 -0
  3. package/dist/runtime/shared/openapi/error-response.dto.js +13 -0
  4. package/dist/runtime/shared/openapi/error-response.dto.js.map +1 -0
  5. package/dist/runtime/shared/openapi/errors.d.ts +30 -0
  6. package/dist/runtime/shared/openapi/errors.js +24 -0
  7. package/dist/runtime/shared/openapi/errors.js.map +1 -0
  8. package/dist/runtime/shared/openapi/index.d.ts +5 -0
  9. package/dist/runtime/shared/openapi/index.js +115 -0
  10. package/dist/runtime/shared/openapi/index.js.map +1 -0
  11. package/dist/runtime/shared/openapi/registry.d.ts +82 -0
  12. package/dist/runtime/shared/openapi/registry.js +107 -0
  13. package/dist/runtime/shared/openapi/registry.js.map +1 -0
  14. package/dist/runtime/shared/openapi/registry.tokens.d.ts +15 -0
  15. package/dist/runtime/shared/openapi/registry.tokens.js +6 -0
  16. package/dist/runtime/shared/openapi/registry.tokens.js.map +1 -0
  17. package/dist/src/cli/index.js +281 -7
  18. package/dist/src/cli/index.js.map +1 -1
  19. package/package.json +9 -1
  20. package/templates/entity/new/backend/application/schemas/dto.ejs.t +31 -0
  21. package/templates/entity/new/backend/modules/core/module.ejs.t +21 -2
  22. package/templates/entity/new/backend/presentation/controller.ejs.t +74 -0
  23. package/templates/entity/new/clean-lite-ps/controller.ejs.t +48 -0
  24. package/templates/entity/new/clean-lite-ps/module.ejs.t +24 -2
  25. package/templates/entity/new/prompt.js +2 -0
  26. package/templates/subsystem/openapi-config/codegen-config-openapi-block.ejs.t +35 -0
  27. package/templates/subsystem/openapi-config/prompt.js +23 -0
@@ -218304,6 +218304,7 @@ async function detectInstalledSubsystems(ctx) {
218304
218304
  if (!fs8.existsSync(root)) continue;
218305
218305
  for (const name of KNOWN_NAMES) {
218306
218306
  if (seen.has(name)) continue;
218307
+ if (name === "openapi-config") continue;
218307
218308
  const dir = path16.join(root, name);
218308
218309
  if (!fs8.existsSync(dir) || !fs8.statSync(dir).isDirectory()) continue;
218309
218310
  const files = fs8.readdirSync(dir);
@@ -218317,6 +218318,25 @@ async function detectInstalledSubsystems(ctx) {
218317
218318
  });
218318
218319
  }
218319
218320
  }
218321
+ if (!seen.has("openapi-config")) {
218322
+ const configPath = path16.resolve(
218323
+ ctx.cwd,
218324
+ ctx.config ? "codegen.config.yaml" : "codegen.config.yaml"
218325
+ );
218326
+ if (fs8.existsSync(configPath)) {
218327
+ try {
218328
+ const source = fs8.readFileSync(configPath, "utf-8");
218329
+ if (/^openapi\s*:/m.test(source)) {
218330
+ found.push({
218331
+ name: "openapi-config",
218332
+ path: configPath,
218333
+ backend: "config-only"
218334
+ });
218335
+ }
218336
+ } catch {
218337
+ }
218338
+ }
218339
+ }
218320
218340
  return found;
218321
218341
  }
218322
218342
  var SUBSYSTEMS, KNOWN_NAMES;
@@ -218359,6 +218379,16 @@ var init_subsystem_detect = __esm({
218359
218379
  description: "Event-to-job bridge (durable async fanout via @JobHandler.triggers)",
218360
218380
  backends: ["drizzle", "memory"],
218361
218381
  defaultBackend: "drizzle"
218382
+ },
218383
+ {
218384
+ // OPENAPI-4. "Config-only" pseudo-subsystem — the runtime helpers
218385
+ // (OpenApiRegistry, ErrorResponseDto) are already vendored by
218386
+ // `codegen project init`. Installing this subsystem just injects the
218387
+ // `openapi:` block into codegen.config.yaml.
218388
+ name: "openapi-config",
218389
+ description: "OpenAPI/Swagger config block (registry is vendored at init)",
218390
+ backends: ["config-only"],
218391
+ defaultBackend: "config-only"
218362
218392
  }
218363
218393
  ];
218364
218394
  KNOWN_NAMES = SUBSYSTEMS.map((s) => s.name);
@@ -218377,7 +218407,10 @@ import fs9 from "fs";
218377
218407
  import path17 from "path";
218378
218408
  import { Command as Command3, Option as Option3 } from "clipanion";
218379
218409
  function runtimeRoot() {
218380
- return path17.resolve(import.meta.dirname, "..", "..", "..", "runtime");
218410
+ const pkgRoot = path17.resolve(import.meta.dirname, "..", "..", "..");
218411
+ const topLevel = path17.join(pkgRoot, "runtime");
218412
+ if (fs9.existsSync(topLevel)) return topLevel;
218413
+ return path17.join(pkgRoot, "dist", "runtime");
218381
218414
  }
218382
218415
  function subsystemSource(name) {
218383
218416
  return path17.join(runtimeRoot(), "subsystems", name);
@@ -218827,6 +218860,9 @@ var init_subsystem = __esm({
218827
218860
  );
218828
218861
  return 2;
218829
218862
  }
218863
+ if (desc.name === "openapi-config") {
218864
+ return this.executeOpenApiConfig(ctx);
218865
+ }
218830
218866
  const installed = await detectInstalledSubsystems(ctx);
218831
218867
  const already = installed.find((i) => i.name === desc.name);
218832
218868
  if (already && !this.force) {
@@ -219038,6 +219074,72 @@ var init_subsystem = __esm({
219038
219074
  }
219039
219075
  return 0;
219040
219076
  }
219077
+ /**
219078
+ * OPENAPI-4: install flow for the config-only `openapi-config`
219079
+ * pseudo-subsystem.
219080
+ *
219081
+ * Nothing to copy — `src/shared/openapi/*` was already vendored by
219082
+ * `codegen project init`. This method just invokes the
219083
+ * `subsystem/openapi-config` Hygen action to inject the `openapi:`
219084
+ * block into `codegen.config.yaml`, honoring the same `--force-config`
219085
+ * semantics as jobs/events/sync/bridge.
219086
+ */
219087
+ async executeOpenApiConfig(ctx) {
219088
+ const configPath = path17.join(ctx.cwd, "codegen.config.yaml");
219089
+ const outcome = planConfigBlockAction(configPath, "openapi", this.forceConfig);
219090
+ if (outcome === "parse-error") {
219091
+ printError(
219092
+ "codegen.config.yaml is not valid YAML: refusing to inject openapi config block. Fix the YAML and re-run."
219093
+ );
219094
+ return 1;
219095
+ }
219096
+ if (this.dryRun) {
219097
+ if (isJsonMode()) {
219098
+ printJson({
219099
+ command: "subsystem install",
219100
+ subsystem: "openapi-config",
219101
+ dryRun: true,
219102
+ configBlockOutcome: outcome,
219103
+ planned: [configPath]
219104
+ });
219105
+ } else {
219106
+ printInfo(`Dry run \u2014 openapi config block would be ${outcome}`);
219107
+ console.log(` ${theme.muted(icons.arrow)} ${path17.relative(ctx.cwd, configPath) || configPath}`);
219108
+ }
219109
+ return 0;
219110
+ }
219111
+ const configResult = runConfigBlockAction({
219112
+ cwd: ctx.cwd,
219113
+ actionFolder: "openapi-config",
219114
+ configPath,
219115
+ subsystem: "openapi",
219116
+ outcome,
219117
+ json: isJsonMode()
219118
+ });
219119
+ if (!configResult.ok) {
219120
+ printError(
219121
+ `openapi-config install failed: ${configResult.error ?? "unknown error"}`
219122
+ );
219123
+ return 1;
219124
+ }
219125
+ if (isJsonMode()) {
219126
+ printJson({
219127
+ command: "subsystem install",
219128
+ subsystem: "openapi-config",
219129
+ configBlockOutcome: outcome,
219130
+ configPath
219131
+ });
219132
+ return 0;
219133
+ }
219134
+ printSuccess(`openapi config block ${outcome === "skipped" ? "already present" : "installed"}.`);
219135
+ printInfo(
219136
+ "Install the peer deps: bun add @nestjs/swagger @anatine/zod-openapi"
219137
+ );
219138
+ printInfo(
219139
+ "Swagger UI mounts at /docs once main.ts calls SwaggerModule.setup(...) \u2014 see CONSUMER-SETUP.md \xA7OpenAPI."
219140
+ );
219141
+ return 0;
219142
+ }
219041
219143
  };
219042
219144
  SubsystemListCommand = class extends Command3 {
219043
219145
  static paths = [["subsystem", "list"]];
@@ -219131,7 +219233,10 @@ import fs10 from "fs";
219131
219233
  import path18 from "path";
219132
219234
  import { stringify as stringifyYaml } from "yaml";
219133
219235
  function runtimeRoot2() {
219134
- return path18.resolve(import.meta.dirname, "..", "..", "..", "runtime");
219236
+ const pkgRoot = path18.resolve(import.meta.dirname, "..", "..", "..");
219237
+ const topLevel = path18.join(pkgRoot, "runtime");
219238
+ if (fs10.existsSync(topLevel)) return topLevel;
219239
+ return path18.join(pkgRoot, "dist", "runtime");
219135
219240
  }
219136
219241
  function resolveRuntimePath(cwd) {
219137
219242
  const shimDir = path18.join(cwd, "src", "shared", "base-classes");
@@ -219173,22 +219278,158 @@ export class DatabaseModule {}
219173
219278
  `;
219174
219279
  }
219175
219280
  function appModuleContent() {
219176
- return `import { Module } from '@nestjs/common';
219281
+ return `import { Global, Module } from '@nestjs/common';
219177
219282
  import { DatabaseModule } from './shared/database/database.module';
219178
219283
  import { GENERATED_MODULES } from './generated/modules';
219284
+ import { OPENAPI_REGISTRY, OpenApiRegistry } from './shared/openapi';
219179
219285
 
219180
219286
  /**
219181
- * AppModule \u2014 wires DatabaseModule (global) + the GENERATED_MODULES barrel.
219287
+ * OpenApiModule \u2014 @Global() wrapper around the OPENAPI_REGISTRY singleton.
219288
+ *
219289
+ * OPENAPI-4: every generated entity module \`@Inject(OPENAPI_REGISTRY)\` to
219290
+ * register its Zod DTOs at onModuleInit (OPENAPI-2). NestJS's DI scoping
219291
+ * means providers declared in AppModule are NOT automatically visible
219292
+ * inside imported feature modules \u2014 exports from AppModule only flow to
219293
+ * modules that explicitly import AppModule, which feature modules don't.
219294
+ * Making the provider module \`@Global()\` broadcasts the token to every
219295
+ * module in the application graph; each generated module picks it up
219296
+ * without needing to import anything extra.
219297
+ *
219298
+ * \`useValue: new OpenApiRegistry()\` locks the instance to one per
219299
+ * process. Never instantiate \`new OpenApiRegistry()\` elsewhere \u2014 a
219300
+ * forked registry forks the schema table and produces a partial
219301
+ * /docs-json at boot.
219302
+ */
219303
+ @Global()
219304
+ @Module({
219305
+ providers: [{ provide: OPENAPI_REGISTRY, useValue: new OpenApiRegistry() }],
219306
+ exports: [OPENAPI_REGISTRY],
219307
+ })
219308
+ class OpenApiModule {}
219309
+
219310
+ /**
219311
+ * AppModule \u2014 wires DatabaseModule (global) + the OpenApiModule (global)
219312
+ * + the GENERATED_MODULES barrel.
219182
219313
  *
219183
219314
  * DatabaseModule must come first \u2014 it provides the DRIZZLE token that every
219184
- * generated repository depends on.
219315
+ * generated repository depends on. OpenApiModule follows so the registry
219316
+ * is in the global injector before any generated module's onModuleInit
219317
+ * fires. main.ts reads the built registry at boot to produce the Swagger
219318
+ * document (OPENAPI-4).
219185
219319
  */
219186
219320
  @Module({
219187
- imports: [DatabaseModule, ...GENERATED_MODULES],
219321
+ imports: [DatabaseModule, OpenApiModule, ...GENERATED_MODULES],
219188
219322
  })
219189
219323
  export class AppModule {}
219190
219324
  `;
219191
219325
  }
219326
+ function mainTsContent() {
219327
+ return `import 'reflect-metadata';
219328
+ import fs from 'node:fs';
219329
+ import path from 'node:path';
219330
+ import { NestFactory } from '@nestjs/core';
219331
+ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
219332
+ import { parse as parseYaml } from 'yaml';
219333
+ import { AppModule } from './app.module';
219334
+ import { OPENAPI_REGISTRY, OpenApiRegistry } from './shared/openapi';
219335
+
219336
+ interface OpenApiConfig {
219337
+ enabled?: boolean;
219338
+ path?: string;
219339
+ title?: string;
219340
+ version?: string;
219341
+ description?: string;
219342
+ auth?: 'bearer' | 'none';
219343
+ }
219344
+
219345
+ interface CodegenConfig {
219346
+ openapi?: OpenApiConfig;
219347
+ }
219348
+
219349
+ /**
219350
+ * Load \`codegen.config.yaml\` to pick up the \`openapi:\` block. Missing or
219351
+ * malformed \u2192 no config (Swagger disabled). The registry is the source of
219352
+ * truth for the document content; this just toggles whether Swagger UI
219353
+ * mounts + which metadata the header shows.
219354
+ */
219355
+ function loadConfig(): CodegenConfig {
219356
+ const configPath = path.resolve(process.cwd(), 'codegen.config.yaml');
219357
+ if (!fs.existsSync(configPath)) return {};
219358
+ try {
219359
+ const raw = fs.readFileSync(configPath, 'utf-8');
219360
+ const parsed = parseYaml(raw);
219361
+ return (parsed && typeof parsed === 'object' ? parsed : {}) as CodegenConfig;
219362
+ } catch {
219363
+ return {};
219364
+ }
219365
+ }
219366
+
219367
+ async function bootstrap(): Promise<void> {
219368
+ const config = loadConfig();
219369
+ const app = await NestFactory.create(AppModule);
219370
+ app.enableShutdownHooks();
219371
+
219372
+ if (config.openapi?.enabled) {
219373
+ // OPENAPI-4: build the document in two passes.
219374
+ //
219375
+ // 1. Our vendored \`OpenApiRegistry\` owns the component schemas
219376
+ // (Zod-derived DTOs registered by every generated module at
219377
+ // onModuleInit \u2014 OPENAPI-2).
219378
+ // 2. \`SwaggerModule.createDocument\` scans controller decorators
219379
+ // (@Api* \u2014 OPENAPI-3) and produces the \`paths\` map. Our
219380
+ // schemas then get merged into the \`components.schemas\` it
219381
+ // emits by reference.
219382
+ //
219383
+ // Both passes are needed: Nest's scanner is the source of truth for
219384
+ // paths (it knows the routes); the registry is the source of truth
219385
+ // for schemas (Zod can't be inferred from reflection metadata).
219386
+ const registry = app.get<OpenApiRegistry>(OPENAPI_REGISTRY);
219387
+ const registryDocument = await registry.build({
219388
+ title: config.openapi.title ?? 'API',
219389
+ version: config.openapi.version ?? '0.0.0',
219390
+ description: config.openapi.description,
219391
+ });
219392
+
219393
+ const docBuilder = new DocumentBuilder()
219394
+ .setTitle(config.openapi.title ?? 'API')
219395
+ .setVersion(config.openapi.version ?? '0.0.0');
219396
+ if (config.openapi.description) docBuilder.setDescription(config.openapi.description);
219397
+ if ((config.openapi.auth ?? 'bearer') === 'bearer') docBuilder.addBearerAuth();
219398
+
219399
+ const nestDocument = SwaggerModule.createDocument(app, docBuilder.build());
219400
+
219401
+ // Merge registry-owned component schemas on top of whatever Nest's
219402
+ // decorator scanner produced. Controllers reference schemas by
219403
+ // \`$ref\` (OPENAPI-3), so this merge is what actually resolves the
219404
+ // refs consumers see in /docs-json.
219405
+ // Registry schemas are typed Record<string, unknown> locally
219406
+ // (avoids depending on openapi3-ts); the runtime shape matches Nest's
219407
+ // SchemaObject \u2014 generateSchema(zodRef, false, '3.0') emits valid
219408
+ // OpenAPI 3.0 schema objects. Cast to satisfy Nest's stricter typing.
219409
+ nestDocument.components = {
219410
+ ...nestDocument.components,
219411
+ schemas: {
219412
+ ...(nestDocument.components?.schemas ?? {}),
219413
+ ...registryDocument.components.schemas,
219414
+ } as NonNullable<typeof nestDocument.components>['schemas'],
219415
+ };
219416
+
219417
+ SwaggerModule.setup(config.openapi.path ?? '/docs', app, nestDocument);
219418
+ }
219419
+
219420
+ const port = Number(process.env.PORT ?? 3000);
219421
+ await app.listen(port);
219422
+ // eslint-disable-next-line no-console
219423
+ console.log(\`Application listening on http://localhost:\${port}\`);
219424
+ }
219425
+
219426
+ bootstrap().catch((err) => {
219427
+ // eslint-disable-next-line no-console
219428
+ console.error('Failed to start application:', err);
219429
+ process.exit(1);
219430
+ });
219431
+ `;
219432
+ }
219192
219433
  function rootSchemaContent() {
219193
219434
  return `/**
219194
219435
  * Drizzle schema root.
@@ -219516,6 +219757,24 @@ async function buildInitPlan(ctx, options) {
219516
219757
  });
219517
219758
  }
219518
219759
  }
219760
+ {
219761
+ const mainPath = path18.join(cwd, "src", "main.ts");
219762
+ if (!fs10.existsSync(mainPath)) {
219763
+ entries.push({
219764
+ path: mainPath,
219765
+ relPath: relOf(cwd, mainPath),
219766
+ action: "create",
219767
+ content: mainTsContent()
219768
+ });
219769
+ } else {
219770
+ entries.push({
219771
+ path: mainPath,
219772
+ relPath: relOf(cwd, mainPath),
219773
+ action: "skip",
219774
+ reason: "exists \u2014 copy the OpenAPI bootstrap block from CONSUMER-SETUP \xA7OpenAPI manually"
219775
+ });
219776
+ }
219777
+ }
219519
219778
  {
219520
219779
  const schemaPath = path18.join(cwd, "src", "schema.ts");
219521
219780
  if (!fs10.existsSync(schemaPath)) {
@@ -219633,7 +219892,22 @@ var init_init_scaffold = __esm({
219633
219892
  // @Body() to give runtime Zod validation at the controller boundary.
219634
219893
  { runtime: "pipes/zod-validation.pipe.ts", target: "src/shared/pipes/zod-validation.pipe.ts" },
219635
219894
  // EAV helpers — referenced by generated services on `eav_value_table` entities
219636
- { runtime: "eav-helpers.ts", target: "src/shared/eav-helpers.ts" }
219895
+ { runtime: "eav-helpers.ts", target: "src/shared/eav-helpers.ts" },
219896
+ // OpenAPI registry (OPENAPI-1/2) — generated modules register Zod DTOs
219897
+ // here at onModuleInit; OPENAPI-4 mounts Swagger UI off the same registry.
219898
+ // `@anatine/zod-openapi` is an optional peer dep (lazy-imported on build).
219899
+ { runtime: "shared/openapi/registry.ts", target: "src/shared/openapi/registry.ts" },
219900
+ { runtime: "shared/openapi/registry.tokens.ts", target: "src/shared/openapi/registry.tokens.ts" },
219901
+ { runtime: "shared/openapi/errors.ts", target: "src/shared/openapi/errors.ts" },
219902
+ // OPENAPI-3: shared error response schema referenced by every generated
219903
+ // controller's 4xx `@ApiResponse` `$ref`. Auto-registered in the registry
219904
+ // constructor so consumer projects expose `ErrorResponseDto` on
219905
+ // `/docs-json` without per-entity duplication.
219906
+ {
219907
+ runtime: "shared/openapi/error-response.dto.ts",
219908
+ target: "src/shared/openapi/error-response.dto.ts"
219909
+ },
219910
+ { runtime: "shared/openapi/index.ts", target: "src/shared/openapi/index.ts" }
219637
219911
  ];
219638
219912
  REQUIRED_ALIASES = {
219639
219913
  "@shared/*": ["./src/shared/*"],