prisma-generator-express 1.65.2 → 1.67.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 (106) hide show
  1. package/README.md +51 -3480
  2. package/dist/client/encodeQueryParams.js +3 -1
  3. package/dist/client/encodeQueryParams.js.map +1 -1
  4. package/dist/copy/misc.d.ts +1 -0
  5. package/dist/copy/misc.js +14 -0
  6. package/dist/copy/misc.js.map +1 -1
  7. package/dist/copy/operationDefinitions.js +69 -6
  8. package/dist/copy/operationDefinitions.js.map +1 -1
  9. package/dist/generators/generateFastifyHandler.d.ts +2 -2
  10. package/dist/generators/generateFastifyHandler.js +15 -5
  11. package/dist/generators/generateFastifyHandler.js.map +1 -1
  12. package/dist/generators/generateHonoHandler.d.ts +2 -2
  13. package/dist/generators/generateHonoHandler.js +12 -5
  14. package/dist/generators/generateHonoHandler.js.map +1 -1
  15. package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
  16. package/dist/generators/generateImportPrismaStatement.js +6 -0
  17. package/dist/generators/generateImportPrismaStatement.js.map +1 -1
  18. package/dist/generators/generateModelMetadata.js +20 -10
  19. package/dist/generators/generateModelMetadata.js.map +1 -1
  20. package/dist/generators/generateOperationCore.d.ts +3 -3
  21. package/dist/generators/generateOperationCore.js +51 -16
  22. package/dist/generators/generateOperationCore.js.map +1 -1
  23. package/dist/generators/generateQueryBuilderHelper.d.ts +1 -1
  24. package/dist/generators/generateQueryBuilderHelper.js +2 -4
  25. package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
  26. package/dist/generators/generateRelationMeta.js +2 -2
  27. package/dist/generators/generateRelationMeta.js.map +1 -1
  28. package/dist/generators/generateRouteConfigType.d.ts +2 -2
  29. package/dist/generators/generateRouteConfigType.js +29 -11
  30. package/dist/generators/generateRouteConfigType.js.map +1 -1
  31. package/dist/generators/generateRouter.d.ts +7 -4
  32. package/dist/generators/generateRouter.js +107 -14
  33. package/dist/generators/generateRouter.js.map +1 -1
  34. package/dist/generators/generateRouterFastify.d.ts +7 -4
  35. package/dist/generators/generateRouterFastify.js +56 -28
  36. package/dist/generators/generateRouterFastify.js.map +1 -1
  37. package/dist/generators/generateRouterHono.d.ts +7 -4
  38. package/dist/generators/generateRouterHono.js +84 -53
  39. package/dist/generators/generateRouterHono.js.map +1 -1
  40. package/dist/generators/generateUnifiedDocs.d.ts +1 -1
  41. package/dist/generators/generateUnifiedDocs.js +15 -8
  42. package/dist/generators/generateUnifiedDocs.js.map +1 -1
  43. package/dist/generators/generateUnifiedHandler.d.ts +2 -2
  44. package/dist/generators/generateUnifiedHandler.js +11 -3
  45. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  46. package/dist/guardOptions.js +12 -7
  47. package/dist/guardOptions.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +22 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/utils/copyFiles.js +22 -6
  52. package/dist/utils/copyFiles.js.map +1 -1
  53. package/dist/utils/importExt.d.ts +1 -1
  54. package/dist/utils/pathCasing.d.ts +4 -0
  55. package/dist/utils/pathCasing.js +40 -0
  56. package/dist/utils/pathCasing.js.map +1 -0
  57. package/dist/utils/resolveImportStyle.js +13 -5
  58. package/dist/utils/resolveImportStyle.js.map +1 -1
  59. package/dist/utils/writeFileSafely.js.map +1 -1
  60. package/package.json +16 -3
  61. package/src/bin.ts +1 -1
  62. package/src/client/encodeQueryParams.ts +39 -35
  63. package/src/constants.ts +1 -1
  64. package/src/copy/autoIncludePlanner.ts +93 -27
  65. package/src/copy/autoIncludePlannerGuarded.ts +171 -48
  66. package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
  67. package/src/copy/buildModelOpenApi.ts +153 -50
  68. package/src/copy/concurrency.ts +10 -8
  69. package/src/copy/errorMapper.ts +68 -19
  70. package/src/copy/guardHelpers.ts +30 -9
  71. package/src/copy/materializedCount.ts +17 -5
  72. package/src/copy/misc.ts +88 -68
  73. package/src/copy/ndjson.ts +72 -0
  74. package/src/copy/operationDefinitions.ts +99 -21
  75. package/src/copy/operationRuntime.ts +100 -12
  76. package/src/copy/pagination.ts +25 -11
  77. package/src/copy/parseQueryParams.ts +47 -48
  78. package/src/copy/projectionDefaults.ts +6 -4
  79. package/src/copy/routeConfig.express.ts +16 -4
  80. package/src/copy/routeConfig.fastify.ts +16 -2
  81. package/src/copy/routeConfig.hono.ts +19 -9
  82. package/src/copy/routeConfig.ts +174 -39
  83. package/src/copy/scalarTypes.ts +7 -2
  84. package/src/copy/sse.ts +72 -19
  85. package/src/generators/generateFastifyHandler.ts +24 -13
  86. package/src/generators/generateHonoHandler.ts +21 -13
  87. package/src/generators/generateImportPrismaStatement.ts +93 -81
  88. package/src/generators/generateModelMetadata.ts +24 -12
  89. package/src/generators/generateOperationCore.ts +81 -33
  90. package/src/generators/generateQueryBuilderHelper.ts +20 -4
  91. package/src/generators/generateRelationMeta.ts +23 -11
  92. package/src/generators/generateRouteConfigType.ts +39 -13
  93. package/src/generators/generateRouter.ts +114 -17
  94. package/src/generators/generateRouterFastify.ts +67 -31
  95. package/src/generators/generateRouterHono.ts +108 -60
  96. package/src/generators/generateUnifiedDocs.ts +29 -8
  97. package/src/generators/generateUnifiedHandler.ts +16 -8
  98. package/src/generators/generateUnifiedScalarUI.ts +1 -1
  99. package/src/guardOptions.ts +105 -92
  100. package/src/index.ts +46 -4
  101. package/src/utils/copyFiles.ts +199 -168
  102. package/src/utils/importExt.ts +2 -2
  103. package/src/utils/pathCasing.ts +42 -0
  104. package/src/utils/resolveImportStyle.ts +37 -15
  105. package/src/utils/strings.ts +2 -2
  106. package/src/utils/writeFileSafely.ts +6 -2
@@ -98,90 +98,94 @@ export type GuardOptionMetadata = {
98
98
  * them; declaring them on the shared base made Express and Fastify accept them in
99
99
  * TypeScript and ignore them at runtime, which is a false public API.
100
100
  */
101
- export const GUARD_OPTION_METADATA: readonly GuardOptionMetadata[] = Object.freeze([
102
- {
103
- name: 'requireGuardShape',
104
- type: 'boolean',
105
- default: UPSTREAM_GUARD_DEFAULTS.requireGuardShape,
106
- hardened: HARDENED_GUARD_PROFILE.requireGuardShape,
107
- label: 'Require a guard on every enabled operation',
108
- description:
109
- 'Refuses at router construction when an operation defines neither "shape" nor "variants".',
110
- warning:
111
- "Off, an operation nobody configured passes the caller's own where/select/include straight to Prisma.",
112
- target: 'hono',
113
- },
114
- {
115
- name: 'validateGuardShapes',
116
- type: 'boolean',
117
- default: UPSTREAM_GUARD_DEFAULTS.validateGuardShapes,
118
- hardened: HARDENED_GUARD_PROFILE.validateGuardShapes,
119
- label: 'Validate guard shapes',
120
- description:
121
- 'Refuses an empty shape, and one mixing guard keys with non-guard keys, at the top level and inside variants.',
122
- warning:
123
- 'Off, "shape: {}" reads as guarded and constrains nothing. On, strict validation may reject existing route configs.',
124
- target: 'hono',
125
- },
126
- {
127
- name: 'requireDefaultVariantOptIn',
128
- type: 'boolean',
129
- default: UPSTREAM_GUARD_DEFAULTS.requireDefaultVariantOptIn,
130
- hardened: HARDENED_GUARD_PROFILE.requireDefaultVariantOptIn,
131
- label: 'Require confirmation for a "default" variant',
132
- description:
133
- 'A variant named "default" must set allowDefaultVariant: true to be accepted.',
134
- warning: 'A "default" variant answers every unrecognised, blank and missing caller.',
135
- target: 'hono',
136
- },
137
- {
138
- name: 'enableUpdateEach',
139
- type: 'boolean',
140
- default: UPSTREAM_GUARD_DEFAULTS.enableUpdateEach,
141
- hardened: HARDENED_GUARD_PROFILE.enableUpdateEach,
142
- label: 'Register the updateEach batch route',
143
- description: 'Mounts POST <base>/each. Set false to refuse it at router construction.',
144
- warning:
145
- 'updateEach bypasses guard shapes entirely; only a development-only warning stands between it and an unguarded mass update.',
146
- target: 'hono',
147
- },
148
- {
149
- name: 'guardResolutionOrder',
150
- type: 'enum',
151
- values: Object.freeze(['after-hooks', 'before-hooks']),
152
- default: UPSTREAM_GUARD_DEFAULTS.guardResolutionOrder,
153
- hardened: HARDENED_GUARD_PROFILE.guardResolutionOrder,
154
- label: 'When guard failures are raised',
155
- description:
156
- 'after-hooks is the upstream behaviour; before-hooks settles variant and shape resolution before any operation hook runs.',
157
- warning:
158
- 'With after-hooks, a hook that returns a Response can answer a request whose guard was never established.',
159
- target: 'hono',
160
- },
161
- {
162
- name: 'allowE2EGuardBypass',
163
- type: 'boolean',
164
- default: UPSTREAM_GUARD_DEFAULTS.allowE2EGuardBypass,
165
- hardened: HARDENED_GUARD_PROFILE.allowE2EGuardBypass,
166
- label: 'Honour the E2E environment guard bypass',
167
- description: 'Treats E2E=true in the environment as a request to drop the guard.',
168
- warning:
169
- 'A runtime variable can weaken a deployed artifact after generation. On an edge runtime this is an ordinary config var.',
170
- target: 'hono',
171
- },
172
- {
173
- name: 'validateResolvedShapes',
174
- type: 'boolean',
175
- default: UPSTREAM_GUARD_DEFAULTS.validateResolvedShapes,
176
- hardened: HARDENED_GUARD_PROFILE.validateResolvedShapes,
177
- label: 'Validate what a shape function returns',
178
- description:
179
- 'Checks a dynamically resolved shape before use and fails the request with 500 if it is unusable. Also resolves it exactly once.',
180
- warning:
181
- 'Off, a shape function returning {} or undefined runs the operation unguarded, once per request.',
182
- target: 'hono',
183
- },
184
- ])
101
+ export const GUARD_OPTION_METADATA: readonly GuardOptionMetadata[] =
102
+ Object.freeze([
103
+ {
104
+ name: 'requireGuardShape',
105
+ type: 'boolean',
106
+ default: UPSTREAM_GUARD_DEFAULTS.requireGuardShape,
107
+ hardened: HARDENED_GUARD_PROFILE.requireGuardShape,
108
+ label: 'Require a guard on every enabled operation',
109
+ description:
110
+ 'Refuses at router construction when an operation defines neither "shape" nor "variants".',
111
+ warning:
112
+ "Off, an operation nobody configured passes the caller's own where/select/include straight to Prisma.",
113
+ target: 'hono',
114
+ },
115
+ {
116
+ name: 'validateGuardShapes',
117
+ type: 'boolean',
118
+ default: UPSTREAM_GUARD_DEFAULTS.validateGuardShapes,
119
+ hardened: HARDENED_GUARD_PROFILE.validateGuardShapes,
120
+ label: 'Validate guard shapes',
121
+ description:
122
+ 'Refuses an empty shape, and one mixing guard keys with non-guard keys, at the top level and inside variants.',
123
+ warning:
124
+ 'Off, "shape: {}" reads as guarded and constrains nothing. On, strict validation may reject existing route configs.',
125
+ target: 'hono',
126
+ },
127
+ {
128
+ name: 'requireDefaultVariantOptIn',
129
+ type: 'boolean',
130
+ default: UPSTREAM_GUARD_DEFAULTS.requireDefaultVariantOptIn,
131
+ hardened: HARDENED_GUARD_PROFILE.requireDefaultVariantOptIn,
132
+ label: 'Require confirmation for a "default" variant',
133
+ description:
134
+ 'A variant named "default" must set allowDefaultVariant: true to be accepted.',
135
+ warning:
136
+ 'A "default" variant answers every unrecognised, blank and missing caller.',
137
+ target: 'hono',
138
+ },
139
+ {
140
+ name: 'enableUpdateEach',
141
+ type: 'boolean',
142
+ default: UPSTREAM_GUARD_DEFAULTS.enableUpdateEach,
143
+ hardened: HARDENED_GUARD_PROFILE.enableUpdateEach,
144
+ label: 'Register the updateEach batch route',
145
+ description:
146
+ 'Mounts POST <base>/each. Set false to refuse it at router construction.',
147
+ warning:
148
+ 'updateEach bypasses guard shapes entirely; only a development-only warning stands between it and an unguarded mass update.',
149
+ target: 'hono',
150
+ },
151
+ {
152
+ name: 'guardResolutionOrder',
153
+ type: 'enum',
154
+ values: Object.freeze(['after-hooks', 'before-hooks']),
155
+ default: UPSTREAM_GUARD_DEFAULTS.guardResolutionOrder,
156
+ hardened: HARDENED_GUARD_PROFILE.guardResolutionOrder,
157
+ label: 'When guard failures are raised',
158
+ description:
159
+ 'after-hooks is the upstream behaviour; before-hooks settles variant and shape resolution before any operation hook runs.',
160
+ warning:
161
+ 'With after-hooks, a hook that returns a Response can answer a request whose guard was never established.',
162
+ target: 'hono',
163
+ },
164
+ {
165
+ name: 'allowE2EGuardBypass',
166
+ type: 'boolean',
167
+ default: UPSTREAM_GUARD_DEFAULTS.allowE2EGuardBypass,
168
+ hardened: HARDENED_GUARD_PROFILE.allowE2EGuardBypass,
169
+ label: 'Honour the PGE_DROP_GUARD environment guard bypass',
170
+ description:
171
+ 'Treats PGE_DROP_GUARD=true in the environment as a request to drop the guard. Deprecated alias: E2E=true.',
172
+ warning:
173
+ 'A runtime variable can weaken a deployed artifact after generation. On an edge runtime this is an ordinary config var.',
174
+ target: 'hono',
175
+ },
176
+ {
177
+ name: 'validateResolvedShapes',
178
+ type: 'boolean',
179
+ default: UPSTREAM_GUARD_DEFAULTS.validateResolvedShapes,
180
+ hardened: HARDENED_GUARD_PROFILE.validateResolvedShapes,
181
+ label: 'Validate what a shape function returns',
182
+ description:
183
+ 'Checks a dynamically resolved shape before use and fails the request with 500 if it is unusable. Also resolves it exactly once.',
184
+ warning:
185
+ 'Off, a shape function returning {} or undefined runs the operation unguarded, once per request.',
186
+ target: 'hono',
187
+ },
188
+ ])
185
189
 
186
190
  /**
187
191
  * What this metadata does NOT cover.
@@ -208,19 +212,28 @@ export const GUARD_METADATA_SCOPE = Object.freeze({
208
212
  })
209
213
 
210
214
  /** Fill in the defaults. Absent means upstream, never an opinion. */
211
- export function resolveGuardPolicy(config: Partial<GuardPolicy> | undefined): GuardPolicy {
215
+ export function resolveGuardPolicy(
216
+ config: Partial<GuardPolicy> | undefined,
217
+ ): GuardPolicy {
212
218
  return {
213
- requireGuardShape: config?.requireGuardShape ?? UPSTREAM_GUARD_DEFAULTS.requireGuardShape,
219
+ requireGuardShape:
220
+ config?.requireGuardShape ?? UPSTREAM_GUARD_DEFAULTS.requireGuardShape,
214
221
  validateGuardShapes:
215
- config?.validateGuardShapes ?? UPSTREAM_GUARD_DEFAULTS.validateGuardShapes,
222
+ config?.validateGuardShapes ??
223
+ UPSTREAM_GUARD_DEFAULTS.validateGuardShapes,
216
224
  requireDefaultVariantOptIn:
217
- config?.requireDefaultVariantOptIn ?? UPSTREAM_GUARD_DEFAULTS.requireDefaultVariantOptIn,
218
- enableUpdateEach: config?.enableUpdateEach ?? UPSTREAM_GUARD_DEFAULTS.enableUpdateEach,
225
+ config?.requireDefaultVariantOptIn ??
226
+ UPSTREAM_GUARD_DEFAULTS.requireDefaultVariantOptIn,
227
+ enableUpdateEach:
228
+ config?.enableUpdateEach ?? UPSTREAM_GUARD_DEFAULTS.enableUpdateEach,
219
229
  guardResolutionOrder:
220
- config?.guardResolutionOrder ?? UPSTREAM_GUARD_DEFAULTS.guardResolutionOrder,
230
+ config?.guardResolutionOrder ??
231
+ UPSTREAM_GUARD_DEFAULTS.guardResolutionOrder,
221
232
  allowE2EGuardBypass:
222
- config?.allowE2EGuardBypass ?? UPSTREAM_GUARD_DEFAULTS.allowE2EGuardBypass,
233
+ config?.allowE2EGuardBypass ??
234
+ UPSTREAM_GUARD_DEFAULTS.allowE2EGuardBypass,
223
235
  validateResolvedShapes:
224
- config?.validateResolvedShapes ?? UPSTREAM_GUARD_DEFAULTS.validateResolvedShapes,
236
+ config?.validateResolvedShapes ??
237
+ UPSTREAM_GUARD_DEFAULTS.validateResolvedShapes,
225
238
  }
226
239
  }
package/src/index.ts CHANGED
@@ -15,7 +15,11 @@ export {
15
15
  HARDENED_GUARD_PROFILE,
16
16
  resolveGuardPolicy,
17
17
  } from './guardOptions'
18
- export type { GuardPolicy, GuardOptionMetadata, GuardResolutionOrder } from './guardOptions'
18
+ export type {
19
+ GuardPolicy,
20
+ GuardOptionMetadata,
21
+ GuardResolutionOrder,
22
+ } from './guardOptions'
19
23
 
20
24
  import {
21
25
  generatorHandler,
@@ -40,11 +44,13 @@ import {
40
44
  import { generateModelMetadata } from './generators/generateModelMetadata'
41
45
  import {
42
46
  getRelativeClientPath,
47
+ getRelativeClientTypeImport,
43
48
  getGuardShapesImport,
44
49
  } from './generators/generateImportPrismaStatement'
45
50
  import { writeFileSafely } from './utils/writeFileSafely'
46
51
  import { copyFiles } from './utils/copyFiles'
47
52
  import { resolveImportStyle, ImportStyle } from './utils/resolveImportStyle'
53
+ import { parsePathCase, modelPathSegment, PathCase } from './utils/pathCasing'
48
54
  import {
49
55
  GENERATOR_NAME,
50
56
  Target,
@@ -99,6 +105,12 @@ function getFindManyPaginatedMode(
99
105
  )
100
106
  }
101
107
 
108
+ function getPathCase(options: GeneratorOptions): PathCase {
109
+ return parsePathCase(
110
+ (options.generator.config as Record<string, unknown>).pathCase,
111
+ )
112
+ }
113
+
102
114
  function getDropGuard(options: GeneratorOptions): boolean {
103
115
  const raw = (options.generator.config as Record<string, unknown>).dropGuard
104
116
  if (raw === undefined || raw === null) return false
@@ -109,7 +121,8 @@ function getDropGuard(options: GeneratorOptions): boolean {
109
121
  const s = raw.trim()
110
122
  const lower = s.toLowerCase()
111
123
  if (lower === 'true' || lower === '1' || lower === 'yes') return true
112
- if (lower === 'false' || lower === '0' || lower === 'no' || s === '') return false
124
+ if (lower === 'false' || lower === '0' || lower === 'no' || s === '')
125
+ return false
113
126
  const envVal = process.env[s]
114
127
  if (envVal === undefined) return false
115
128
  const el = envVal.trim().toLowerCase()
@@ -140,6 +153,7 @@ generatorHandler({
140
153
  const writeStrategy = getWriteStrategy(options)
141
154
  const findManyPaginatedMode = getFindManyPaginatedMode(options)
142
155
  const dropGuard = getDropGuard(options)
156
+ const pathCase = getPathCase(options)
143
157
 
144
158
  const manifestDefaultAbs = path.resolve(
145
159
  __dirname,
@@ -165,6 +179,7 @@ generatorHandler({
165
179
  console.log(` Import style: ${importStyle}`)
166
180
  console.log(` Write strategy: ${writeStrategy}`)
167
181
  console.log(` findManyPaginated mode: ${findManyPaginatedMode}`)
182
+ console.log(` Path case: ${pathCase}`)
168
183
 
169
184
  if (dropGuard) {
170
185
  console.log('')
@@ -181,6 +196,7 @@ generatorHandler({
181
196
  await copyFiles(options, target, importStyle)
182
197
 
183
198
  const modelNames: string[] = []
199
+ let guardArtifactsSeen = false
184
200
  const generateHandler: (opts: {
185
201
  model: DMMF.Model
186
202
  importStyle: ImportStyle
@@ -201,6 +217,7 @@ generatorHandler({
201
217
  modelNames.push(model.name)
202
218
 
203
219
  const guardShapesImport = getGuardShapesImport(options, model.name)
220
+ if (guardShapesImport) guardArtifactsSeen = true
204
221
 
205
222
  await writeFileSafely({
206
223
  content: generateModelMetadata({
@@ -238,28 +255,34 @@ generatorHandler({
238
255
  model: model as DMMF.Model,
239
256
  enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
240
257
  guardShapesImport,
258
+ clientImport: getRelativeClientTypeImport(options, model.name),
241
259
  importStyle,
242
260
  writeStrategy,
243
261
  findManyPaginatedMode,
244
262
  dropGuard,
263
+ pathCase,
245
264
  })
246
265
  : target === 'hono'
247
266
  ? generateHonoRouterFunction({
248
267
  model: model as DMMF.Model,
249
268
  enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
250
269
  guardShapesImport,
270
+ clientImport: getRelativeClientTypeImport(options, model.name),
251
271
  importStyle,
252
272
  writeStrategy,
253
273
  dropGuard,
274
+ pathCase,
254
275
  })
255
276
  : generateRouterFunction({
256
277
  model: model as DMMF.Model,
257
278
  enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
258
279
  guardShapesImport,
280
+ clientImport: getRelativeClientTypeImport(options, model.name),
259
281
  importStyle,
260
282
  writeStrategy,
261
283
  findManyPaginatedMode,
262
284
  dropGuard,
285
+ pathCase,
263
286
  })
264
287
 
265
288
  await writeFileSafely({
@@ -304,8 +327,17 @@ generatorHandler({
304
327
  })
305
328
  }
306
329
 
330
+ const pathSegments = Object.fromEntries(
331
+ modelNames.map((n) => [n, modelPathSegment(n, pathCase)]),
332
+ )
333
+
307
334
  await writeFileSafely({
308
- content: generateUnifiedDocs(modelNames, target, importStyle),
335
+ content: generateUnifiedDocs(
336
+ modelNames,
337
+ pathSegments,
338
+ target,
339
+ importStyle,
340
+ ),
309
341
  options,
310
342
  operation: 'combinedDocs',
311
343
  })
@@ -316,8 +348,18 @@ generatorHandler({
316
348
  operation: 'queryBuilder',
317
349
  })
318
350
 
351
+ if (modelNames.length > 0 && !guardArtifactsSeen) {
352
+ console.log('')
353
+ console.log(
354
+ ' Note: prisma-guard not detected. Generated routes call Prisma directly',
355
+ )
356
+ console.log(
357
+ ' unless your route configs define guard shapes or variants.',
358
+ )
359
+ }
360
+
319
361
  console.log('\n═══ Generation Complete ═══')
320
362
  console.log(`✓ ${modelNames.length} models (${target})`)
321
363
  console.log('')
322
364
  },
323
- })
365
+ })