@pattern-stack/codegen 0.3.2 → 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.
- package/README.md +2 -1
- package/dist/runtime/shared/openapi/error-response.dto.d.ts +33 -0
- package/dist/runtime/shared/openapi/error-response.dto.js +13 -0
- package/dist/runtime/shared/openapi/error-response.dto.js.map +1 -0
- package/dist/runtime/shared/openapi/errors.d.ts +30 -0
- package/dist/runtime/shared/openapi/errors.js +24 -0
- package/dist/runtime/shared/openapi/errors.js.map +1 -0
- package/dist/runtime/shared/openapi/index.d.ts +5 -0
- package/dist/runtime/shared/openapi/index.js +115 -0
- package/dist/runtime/shared/openapi/index.js.map +1 -0
- package/dist/runtime/shared/openapi/registry.d.ts +82 -0
- package/dist/runtime/shared/openapi/registry.js +107 -0
- package/dist/runtime/shared/openapi/registry.js.map +1 -0
- package/dist/runtime/shared/openapi/registry.tokens.d.ts +15 -0
- package/dist/runtime/shared/openapi/registry.tokens.js +6 -0
- package/dist/runtime/shared/openapi/registry.tokens.js.map +1 -0
- package/dist/runtime/subsystems/sync/sync-audit.schema.d.ts +2 -2
- package/dist/src/cli/index.js +273 -5
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +9 -1
- package/templates/entity/new/backend/application/schemas/dto.ejs.t +31 -0
- package/templates/entity/new/backend/modules/core/module.ejs.t +21 -2
- package/templates/entity/new/backend/presentation/controller.ejs.t +74 -0
- package/templates/entity/new/clean-lite-ps/controller.ejs.t +48 -0
- package/templates/entity/new/clean-lite-ps/module.ejs.t +24 -2
- package/templates/entity/new/prompt.js +2 -0
- package/templates/subsystem/openapi-config/codegen-config-openapi-block.ejs.t +35 -0
- package/templates/subsystem/openapi-config/prompt.js +23 -0
package/dist/src/cli/index.js
CHANGED
|
@@ -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);
|
|
@@ -218830,6 +218860,9 @@ var init_subsystem = __esm({
|
|
|
218830
218860
|
);
|
|
218831
218861
|
return 2;
|
|
218832
218862
|
}
|
|
218863
|
+
if (desc.name === "openapi-config") {
|
|
218864
|
+
return this.executeOpenApiConfig(ctx);
|
|
218865
|
+
}
|
|
218833
218866
|
const installed = await detectInstalledSubsystems(ctx);
|
|
218834
218867
|
const already = installed.find((i) => i.name === desc.name);
|
|
218835
218868
|
if (already && !this.force) {
|
|
@@ -219041,6 +219074,72 @@ var init_subsystem = __esm({
|
|
|
219041
219074
|
}
|
|
219042
219075
|
return 0;
|
|
219043
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
|
+
}
|
|
219044
219143
|
};
|
|
219045
219144
|
SubsystemListCommand = class extends Command3 {
|
|
219046
219145
|
static paths = [["subsystem", "list"]];
|
|
@@ -219179,22 +219278,158 @@ export class DatabaseModule {}
|
|
|
219179
219278
|
`;
|
|
219180
219279
|
}
|
|
219181
219280
|
function appModuleContent() {
|
|
219182
|
-
return `import { Module } from '@nestjs/common';
|
|
219281
|
+
return `import { Global, Module } from '@nestjs/common';
|
|
219183
219282
|
import { DatabaseModule } from './shared/database/database.module';
|
|
219184
219283
|
import { GENERATED_MODULES } from './generated/modules';
|
|
219284
|
+
import { OPENAPI_REGISTRY, OpenApiRegistry } from './shared/openapi';
|
|
219185
219285
|
|
|
219186
219286
|
/**
|
|
219187
|
-
*
|
|
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.
|
|
219188
219313
|
*
|
|
219189
219314
|
* DatabaseModule must come first \u2014 it provides the DRIZZLE token that every
|
|
219190
|
-
* 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).
|
|
219191
219319
|
*/
|
|
219192
219320
|
@Module({
|
|
219193
|
-
imports: [DatabaseModule, ...GENERATED_MODULES],
|
|
219321
|
+
imports: [DatabaseModule, OpenApiModule, ...GENERATED_MODULES],
|
|
219194
219322
|
})
|
|
219195
219323
|
export class AppModule {}
|
|
219196
219324
|
`;
|
|
219197
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
|
+
}
|
|
219198
219433
|
function rootSchemaContent() {
|
|
219199
219434
|
return `/**
|
|
219200
219435
|
* Drizzle schema root.
|
|
@@ -219522,6 +219757,24 @@ async function buildInitPlan(ctx, options) {
|
|
|
219522
219757
|
});
|
|
219523
219758
|
}
|
|
219524
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
|
+
}
|
|
219525
219778
|
{
|
|
219526
219779
|
const schemaPath = path18.join(cwd, "src", "schema.ts");
|
|
219527
219780
|
if (!fs10.existsSync(schemaPath)) {
|
|
@@ -219639,7 +219892,22 @@ var init_init_scaffold = __esm({
|
|
|
219639
219892
|
// @Body() to give runtime Zod validation at the controller boundary.
|
|
219640
219893
|
{ runtime: "pipes/zod-validation.pipe.ts", target: "src/shared/pipes/zod-validation.pipe.ts" },
|
|
219641
219894
|
// EAV helpers — referenced by generated services on `eav_value_table` entities
|
|
219642
|
-
{ 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" }
|
|
219643
219911
|
];
|
|
219644
219912
|
REQUIRED_ALIASES = {
|
|
219645
219913
|
"@shared/*": ["./src/shared/*"],
|