@kubb/plugin-zod 5.0.0-alpha.24 → 5.0.0-alpha.26

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 (47) hide show
  1. package/dist/index.cjs +1673 -88
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.ts +317 -2
  4. package/dist/index.js +1646 -88
  5. package/dist/index.js.map +1 -1
  6. package/package.json +5 -33
  7. package/src/components/Operations.tsx +22 -15
  8. package/src/components/Zod.tsx +18 -118
  9. package/src/components/ZodMini.tsx +41 -0
  10. package/src/constants.ts +5 -0
  11. package/src/generators/zodGenerator.tsx +165 -158
  12. package/src/generators/zodGeneratorLegacy.tsx +401 -0
  13. package/src/index.ts +11 -1
  14. package/src/plugin.ts +105 -129
  15. package/src/presets.ts +25 -0
  16. package/src/printers/printerZod.ts +271 -0
  17. package/src/printers/printerZodMini.ts +246 -0
  18. package/src/resolvers/resolverZod.ts +71 -0
  19. package/src/resolvers/resolverZodLegacy.ts +60 -0
  20. package/src/types.ts +121 -92
  21. package/src/utils.ts +248 -0
  22. package/dist/components-DW4Q2yVq.js +0 -868
  23. package/dist/components-DW4Q2yVq.js.map +0 -1
  24. package/dist/components-qFUGs0vU.cjs +0 -916
  25. package/dist/components-qFUGs0vU.cjs.map +0 -1
  26. package/dist/components.cjs +0 -4
  27. package/dist/components.d.ts +0 -56
  28. package/dist/components.js +0 -2
  29. package/dist/generators-C9BCTLXg.cjs +0 -301
  30. package/dist/generators-C9BCTLXg.cjs.map +0 -1
  31. package/dist/generators-maqx12yN.js +0 -290
  32. package/dist/generators-maqx12yN.js.map +0 -1
  33. package/dist/generators.cjs +0 -4
  34. package/dist/generators.d.ts +0 -12
  35. package/dist/generators.js +0 -2
  36. package/dist/templates/ToZod.source.cjs +0 -7
  37. package/dist/templates/ToZod.source.cjs.map +0 -1
  38. package/dist/templates/ToZod.source.d.ts +0 -7
  39. package/dist/templates/ToZod.source.js +0 -6
  40. package/dist/templates/ToZod.source.js.map +0 -1
  41. package/dist/types-CClg-ikj.d.ts +0 -172
  42. package/src/components/index.ts +0 -2
  43. package/src/generators/index.ts +0 -2
  44. package/src/generators/operationsGenerator.tsx +0 -50
  45. package/src/parser.ts +0 -952
  46. package/src/templates/ToZod.source.ts +0 -4
  47. package/templates/ToZod.ts +0 -61
@@ -0,0 +1,401 @@
1
+ import path from 'node:path'
2
+ import { caseParams, composeTransformers, createProperty, createSchema, transform } from '@kubb/ast'
3
+ import type { OperationNode, ParameterNode, SchemaNode } from '@kubb/ast/types'
4
+ import { defineGenerator, getMode } from '@kubb/core'
5
+ import { File } from '@kubb/react-fabric'
6
+ import { Operations } from '../components/Operations.tsx'
7
+ import { Zod } from '../components/Zod.tsx'
8
+ import { ZodMini } from '../components/ZodMini.tsx'
9
+ import { ZOD_NAMESPACE_IMPORTS } from '../constants.ts'
10
+ import type { PluginZod, ResolverZod } from '../types'
11
+
12
+ type BuildGroupedParamsSchemaOptions = {
13
+ params: Array<ParameterNode>
14
+ }
15
+
16
+ function buildGroupedParamsSchema({ params, optional }: BuildGroupedParamsSchemaOptions & { optional?: boolean }): SchemaNode {
17
+ return createSchema({
18
+ type: 'object',
19
+ optional,
20
+ primitive: 'object',
21
+ properties: params.map((param) => {
22
+ return createProperty({
23
+ name: param.name,
24
+ required: param.required,
25
+ schema: param.schema,
26
+ })
27
+ }),
28
+ })
29
+ }
30
+
31
+ type BuildOperationSchemaOptions = {
32
+ resolver: ResolverZod
33
+ }
34
+
35
+ function buildLegacyResponsesSchemaNode(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode | null {
36
+ const isGet = node.method.toLowerCase() === 'get'
37
+ const successResponses = node.responses.filter((res) => {
38
+ const code = Number(res.statusCode)
39
+ return !Number.isNaN(code) && code >= 200 && code < 300
40
+ })
41
+ const errorResponses = node.responses.filter((res) => res.statusCode === 'default' || Number(res.statusCode) >= 400)
42
+
43
+ const responseSchema =
44
+ successResponses.length > 0
45
+ ? successResponses.length === 1
46
+ ? createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, successResponses[0]!.statusCode) })
47
+ : createSchema({
48
+ type: 'union',
49
+ members: successResponses.map((res) => createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, res.statusCode) })),
50
+ })
51
+ : createSchema({ type: 'any' })
52
+
53
+ const errorsSchema =
54
+ errorResponses.length > 0
55
+ ? errorResponses.length === 1
56
+ ? createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, errorResponses[0]!.statusCode) })
57
+ : createSchema({
58
+ type: 'union',
59
+ members: errorResponses.map((res) => createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, res.statusCode) })),
60
+ })
61
+ : createSchema({ type: 'any' })
62
+
63
+ const properties = [createProperty({ name: 'Response', required: true, schema: responseSchema })]
64
+
65
+ if (!isGet && node.requestBody?.schema) {
66
+ properties.push(
67
+ createProperty({
68
+ name: 'Request',
69
+ required: true,
70
+ schema: createSchema({ type: 'ref', name: resolver.resolveDataName(node) }),
71
+ }),
72
+ )
73
+ }
74
+
75
+ const queryParam = node.parameters.find((p) => p.in === 'query')
76
+ if (queryParam) {
77
+ properties.push(
78
+ createProperty({
79
+ name: 'QueryParams',
80
+ required: true,
81
+ schema: createSchema({ type: 'ref', name: resolver.resolveQueryParamsName(node, queryParam) }),
82
+ }),
83
+ )
84
+ }
85
+
86
+ const pathParam = node.parameters.find((p) => p.in === 'path')
87
+ if (pathParam) {
88
+ properties.push(
89
+ createProperty({
90
+ name: 'PathParams',
91
+ required: true,
92
+ schema: createSchema({ type: 'ref', name: resolver.resolvePathParamsName(node, pathParam) }),
93
+ }),
94
+ )
95
+ }
96
+
97
+ const headerParam = node.parameters.find((p) => p.in === 'header')
98
+ if (headerParam) {
99
+ properties.push(
100
+ createProperty({
101
+ name: 'HeaderParams',
102
+ required: true,
103
+ schema: createSchema({ type: 'ref', name: resolver.resolveHeaderParamsName(node, headerParam) }),
104
+ }),
105
+ )
106
+ }
107
+
108
+ properties.push(createProperty({ name: 'Errors', required: true, schema: errorsSchema }))
109
+
110
+ return createSchema({ type: 'object', primitive: 'object', properties })
111
+ }
112
+
113
+ function buildLegacyResponseUnionSchemaNode(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode {
114
+ const successResponses = node.responses.filter((res) => {
115
+ const code = Number(res.statusCode)
116
+ return !Number.isNaN(code) && code >= 200 && code < 300
117
+ })
118
+
119
+ if (successResponses.length === 0) {
120
+ return createSchema({ type: 'any' })
121
+ }
122
+
123
+ if (successResponses.length === 1) {
124
+ return createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, successResponses[0]!.statusCode) })
125
+ }
126
+
127
+ return createSchema({
128
+ type: 'union',
129
+ members: successResponses.map((res) => createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, res.statusCode) })),
130
+ })
131
+ }
132
+
133
+ function buildLegacySchemaNames(node: OperationNode, params: Array<ParameterNode>, resolver: ResolverZod) {
134
+ const pathParam = params.find((p) => p.in === 'path')
135
+ const queryParam = params.find((p) => p.in === 'query')
136
+ const headerParam = params.find((p) => p.in === 'header')
137
+
138
+ const responses: Record<number | string, string> = {}
139
+ const errors: Record<number | string, string> = {}
140
+
141
+ for (const res of node.responses) {
142
+ const name = resolver.resolveResponseStatusName(node, res.statusCode)
143
+ const statusNum = Number(res.statusCode)
144
+
145
+ if (!Number.isNaN(statusNum)) {
146
+ responses[statusNum] = name
147
+ if (statusNum >= 400) {
148
+ errors[statusNum] = name
149
+ }
150
+ }
151
+ }
152
+
153
+ responses['default'] = resolver.resolveResponseName(node)
154
+
155
+ return {
156
+ request: node.requestBody?.schema ? resolver.resolveDataName(node) : undefined,
157
+ parameters: {
158
+ path: pathParam ? resolver.resolvePathParamsName(node, pathParam) : undefined,
159
+ query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) : undefined,
160
+ header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) : undefined,
161
+ },
162
+ responses,
163
+ errors,
164
+ }
165
+ }
166
+
167
+ export const zodGeneratorLegacy = defineGenerator<PluginZod>({
168
+ name: 'zod-legacy',
169
+ type: 'react',
170
+ Schema({ node, adapter, options, config, resolver }) {
171
+ const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, transformers = [] } = options
172
+ const transformedNode = transform(node, composeTransformers(...transformers))
173
+
174
+ if (!transformedNode.name) {
175
+ return
176
+ }
177
+
178
+ const root = path.resolve(config.root, config.output.path)
179
+ const mode = getMode(path.resolve(root, output.path))
180
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
181
+
182
+ const imports = adapter.getImports(transformedNode, (schemaName) => ({
183
+ name: resolver.default(schemaName, 'function'),
184
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
185
+ }))
186
+
187
+ const inferTypeName = inferred ? resolver.resolveInferName(resolver.resolveName(transformedNode.name)) : undefined
188
+
189
+ const meta = {
190
+ name: resolver.default(transformedNode.name, 'function'),
191
+ file: resolver.resolveFile({ name: transformedNode.name, extname: '.ts' }, { root, output, group }),
192
+ } as const
193
+
194
+ return (
195
+ <File
196
+ baseName={meta.file.baseName}
197
+ path={meta.file.path}
198
+ meta={meta.file.meta}
199
+ banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
200
+ footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
201
+ >
202
+ <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
203
+ {mode === 'split' &&
204
+ imports.map((imp) => <File.Import key={[transformedNode.name, imp.path].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
205
+
206
+ {mini ? (
207
+ <ZodMini name={meta.name} node={transformedNode} guidType={guidType} wrapOutput={wrapOutput} inferTypeName={inferTypeName} resolver={resolver} />
208
+ ) : (
209
+ <Zod
210
+ name={meta.name}
211
+ node={transformedNode}
212
+ coercion={coercion}
213
+ guidType={guidType}
214
+ wrapOutput={wrapOutput}
215
+ inferTypeName={inferTypeName}
216
+ resolver={resolver}
217
+ />
218
+ )}
219
+ </File>
220
+ )
221
+ },
222
+ Operation({ node, adapter, options, config, resolver }) {
223
+ const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, transformers } = options
224
+
225
+ const transformedNode = transform(node, composeTransformers(...transformers))
226
+
227
+ const root = path.resolve(config.root, config.output.path)
228
+ const mode = getMode(path.resolve(root, output.path))
229
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
230
+
231
+ const params = caseParams(transformedNode.parameters, paramsCasing)
232
+
233
+ const meta = {
234
+ file: resolver.resolveFile(
235
+ { name: transformedNode.operationId, extname: '.ts', tag: transformedNode.tags[0] ?? 'default', path: transformedNode.path },
236
+ { root, output, group },
237
+ ),
238
+ } as const
239
+
240
+ function renderSchemaEntry({ schema, name, keysToOmit }: { schema: SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
241
+ if (!schema) return null
242
+
243
+ const inferTypeName = inferred ? resolver.resolveInferName(name) : undefined
244
+
245
+ const imports = adapter.getImports(schema, (schemaName) => ({
246
+ name: resolver.default(schemaName, 'function'),
247
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
248
+ }))
249
+
250
+ return (
251
+ <>
252
+ {mode === 'split' &&
253
+ imports.map((imp) => <File.Import key={[name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
254
+ {mini ? (
255
+ <ZodMini
256
+ name={name}
257
+ node={schema}
258
+ guidType={guidType}
259
+ wrapOutput={wrapOutput}
260
+ inferTypeName={inferTypeName}
261
+ resolver={resolver}
262
+ keysToOmit={keysToOmit}
263
+ />
264
+ ) : (
265
+ <Zod
266
+ name={name}
267
+ node={schema}
268
+ coercion={coercion}
269
+ guidType={guidType}
270
+ wrapOutput={wrapOutput}
271
+ inferTypeName={inferTypeName}
272
+ resolver={resolver}
273
+ keysToOmit={keysToOmit}
274
+ />
275
+ )}
276
+ </>
277
+ )
278
+ }
279
+
280
+ const pathParams = params.filter((p) => p.in === 'path')
281
+ const queryParams = params.filter((p) => p.in === 'query')
282
+ const headerParams = params.filter((p) => p.in === 'header')
283
+
284
+ const responseSchemas = transformedNode.responses.map((res) => {
285
+ const responseName = resolver.resolveResponseStatusName(transformedNode, res.statusCode)
286
+ return renderSchemaEntry({
287
+ schema: {
288
+ ...res.schema,
289
+ description: res.description ?? res.schema.description,
290
+ },
291
+ name: responseName,
292
+ keysToOmit: res.keysToOmit,
293
+ })
294
+ })
295
+
296
+ const requestSchema = transformedNode.requestBody?.schema
297
+ ? renderSchemaEntry({
298
+ schema: {
299
+ ...transformedNode.requestBody.schema,
300
+ description: transformedNode.requestBody.description ?? transformedNode.requestBody.schema.description,
301
+ },
302
+ name: resolver.resolveDataName(transformedNode),
303
+ keysToOmit: transformedNode.requestBody.keysToOmit,
304
+ })
305
+ : null
306
+
307
+ const legacyParamTypes = [
308
+ pathParams.length > 0
309
+ ? renderSchemaEntry({
310
+ schema: buildGroupedParamsSchema({ params: pathParams, optional: pathParams.every((p) => !p.required) }),
311
+ name: resolver.resolvePathParamsName(transformedNode, pathParams[0]!),
312
+ })
313
+ : null,
314
+ queryParams.length > 0
315
+ ? renderSchemaEntry({
316
+ schema: buildGroupedParamsSchema({ params: queryParams, optional: queryParams.every((p) => !p.required) }),
317
+ name: resolver.resolveQueryParamsName(transformedNode, queryParams[0]!),
318
+ })
319
+ : null,
320
+ headerParams.length > 0
321
+ ? renderSchemaEntry({
322
+ schema: buildGroupedParamsSchema({ params: headerParams, optional: headerParams.every((p) => !p.required) }),
323
+ name: resolver.resolveHeaderParamsName(transformedNode, headerParams[0]!),
324
+ })
325
+ : null,
326
+ ]
327
+
328
+ const legacyResponsesSchema = renderSchemaEntry({
329
+ schema: buildLegacyResponsesSchemaNode(transformedNode, { resolver }),
330
+ name: resolver.resolveResponsesName(transformedNode),
331
+ })
332
+
333
+ const legacyResponseSchema = renderSchemaEntry({
334
+ schema: buildLegacyResponseUnionSchemaNode(transformedNode, { resolver }),
335
+ name: resolver.resolveResponseName(transformedNode),
336
+ })
337
+
338
+ return (
339
+ <File
340
+ baseName={meta.file.baseName}
341
+ path={meta.file.path}
342
+ meta={meta.file.meta}
343
+ banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
344
+ footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
345
+ >
346
+ <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
347
+ {legacyParamTypes}
348
+ {responseSchemas}
349
+ {requestSchema}
350
+ {legacyResponseSchema}
351
+ {legacyResponsesSchema}
352
+ </File>
353
+ )
354
+ },
355
+ Operations({ nodes, adapter, options, config, resolver }) {
356
+ const { output, importPath, group, operations, paramsCasing, transformers } = options
357
+
358
+ if (!operations) {
359
+ return
360
+ }
361
+
362
+ const root = path.resolve(config.root, config.output.path)
363
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
364
+
365
+ const meta = {
366
+ file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group }),
367
+ } as const
368
+
369
+ const transformedOperations = nodes.map((node) => {
370
+ const transformedNode = transform(node, composeTransformers(...transformers))
371
+
372
+ const params = caseParams(transformedNode.parameters, paramsCasing)
373
+
374
+ return {
375
+ node: transformedNode,
376
+ data: buildLegacySchemaNames(transformedNode, params, resolver),
377
+ }
378
+ })
379
+
380
+ const imports = transformedOperations.flatMap(({ node, data }) => {
381
+ const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as string[]
382
+ const opFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
383
+
384
+ return names.map((name) => <File.Import key={[name, opFile.path].join('-')} name={[name]} root={meta.file.path} path={opFile.path} />)
385
+ })
386
+
387
+ return (
388
+ <File
389
+ baseName={meta.file.baseName}
390
+ path={meta.file.path}
391
+ meta={meta.file.meta}
392
+ banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
393
+ footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
394
+ >
395
+ <File.Import isTypeOnly name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
396
+ {imports}
397
+ <Operations name="operations" operations={transformedOperations} />
398
+ </File>
399
+ )
400
+ },
401
+ })
package/src/index.ts CHANGED
@@ -1,2 +1,12 @@
1
+ export { zodGenerator } from './generators/zodGenerator.tsx'
2
+ export { zodGeneratorLegacy } from './generators/zodGeneratorLegacy.tsx'
3
+
1
4
  export { pluginZod, pluginZodName } from './plugin.ts'
2
- export type { PluginZod } from './types.ts'
5
+
6
+ export { printerZod } from './printers/printerZod.ts'
7
+ export { printerZodMini } from './printers/printerZodMini.ts'
8
+
9
+ export { resolverZod } from './resolvers/resolverZod.ts'
10
+ export { resolverZodLegacy } from './resolvers/resolverZodLegacy.ts'
11
+
12
+ export type { PluginZod, ResolverZod } from './types.ts'
package/src/plugin.ts CHANGED
@@ -1,15 +1,32 @@
1
1
  import path from 'node:path'
2
- import { camelCase, pascalCase } from '@internals/utils'
3
- import { createPlugin, getBarrelFiles, getMode, satisfiesDependency, type UserGroup } from '@kubb/core'
4
- import { OperationGenerator, pluginOasName, SchemaGenerator } from '@kubb/plugin-oas'
5
- import { pluginTsName } from '@kubb/plugin-ts'
6
- import { operationsGenerator } from './generators'
7
- import { zodGenerator } from './generators/zodGenerator.tsx'
8
- import { source as toZodSource } from './templates/ToZod.source.ts'
2
+ import { camelCase } from '@internals/utils'
3
+ import { walk } from '@kubb/ast'
4
+ import type { OperationNode } from '@kubb/ast/types'
5
+ import { createPlugin, type Group, getBarrelFiles, getPreset, runGeneratorOperation, runGeneratorOperations, runGeneratorSchema } from '@kubb/core'
6
+ import { presets } from './presets.ts'
9
7
  import type { PluginZod } from './types.ts'
10
8
 
9
+ /**
10
+ * Canonical plugin name for `@kubb/plugin-zod`, used to identify the plugin in driver lookups and warnings.
11
+ */
11
12
  export const pluginZodName = 'plugin-zod' satisfies PluginZod['name']
12
13
 
14
+ /**
15
+ * The `@kubb/plugin-zod` plugin factory.
16
+ *
17
+ * Generates Zod validation schemas from an OpenAPI/AST `RootNode`.
18
+ * Walks schemas and operations, delegates rendering to the active generators,
19
+ * and writes barrel files based on `output.barrelType`.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { pluginZod } from '@kubb/plugin-zod'
24
+ *
25
+ * export default defineConfig({
26
+ * plugins: [pluginZod({ output: { path: 'zod' } })],
27
+ * })
28
+ * ```
29
+ */
13
30
  export const pluginZod = createPlugin<PluginZod>((options) => {
14
31
  const {
15
32
  output = { path: 'zod', barrelType: 'named' },
@@ -17,156 +34,115 @@ export const pluginZod = createPlugin<PluginZod>((options) => {
17
34
  exclude = [],
18
35
  include,
19
36
  override = [],
20
- transformers = {},
21
37
  dateType = 'string',
22
- unknownType = 'any',
23
- emptySchemaType = unknownType,
24
- integerType = 'number',
25
38
  typed = false,
26
- mapper = {},
27
39
  operations = false,
28
40
  mini = false,
29
- version = mini ? '4' : satisfiesDependency('zod', '>=4') ? '4' : '3',
30
41
  guidType = 'uuid',
31
- importPath = mini ? 'zod/mini' : version === '4' ? 'zod/v4' : 'zod',
42
+ importPath = mini ? 'zod/mini' : 'zod',
32
43
  coercion = false,
33
44
  inferred = false,
34
- generators = [zodGenerator, operations ? operationsGenerator : undefined].filter(Boolean),
35
45
  wrapOutput = undefined,
36
- contentType,
46
+ paramsCasing,
47
+ compatibilityPreset = 'default',
48
+ resolvers: userResolvers = [],
49
+ transformers: userTransformers = [],
50
+ generators: userGenerators = [],
37
51
  } = options
38
52
 
39
- // @deprecated Will be removed in v5 when collisionDetection defaults to true
40
- const usedEnumNames = {}
53
+ const preset = getPreset({
54
+ preset: compatibilityPreset,
55
+ presets: presets,
56
+ resolvers: userResolvers,
57
+ transformers: userTransformers,
58
+ generators: userGenerators,
59
+ })
60
+
61
+ let resolveNameWarning = false
62
+ let resolvePathWarning = false
41
63
 
42
64
  return {
43
65
  name: pluginZodName,
44
- options: {
45
- output,
46
- transformers,
47
- include,
48
- exclude,
49
- override,
50
- typed,
51
- dateType,
52
- unknownType,
53
- emptySchemaType,
54
- integerType,
55
- mapper,
56
- importPath,
57
- coercion,
58
- operations,
59
- inferred,
60
- group,
61
- wrapOutput,
62
- version,
63
- guidType,
64
- mini,
65
- usedEnumNames,
66
+ get resolver() {
67
+ return preset.resolver
66
68
  },
67
- pre: [pluginOasName, typed ? pluginTsName : undefined].filter(Boolean),
68
- resolvePath(baseName, pathMode, options) {
69
- const root = path.resolve(this.config.root, this.config.output.path)
70
- const mode = pathMode ?? getMode(path.resolve(root, output.path))
71
-
72
- if (mode === 'single') {
73
- /**
74
- * when output is a file then we will always append to the same file(output file), see fileManager.addOrAppend
75
- * Other plugins then need to call addOrAppend instead of just add from the fileManager class
76
- */
77
- return path.resolve(root, output.path)
69
+ get options() {
70
+ return {
71
+ output,
72
+ group: group
73
+ ? ({
74
+ ...group,
75
+ name: (ctx) => {
76
+ if (group.type === 'path') {
77
+ return `${ctx.group.split('/')[1]}`
78
+ }
79
+ return `${camelCase(ctx.group)}Controller`
80
+ },
81
+ } satisfies Group)
82
+ : undefined,
83
+ dateType,
84
+ typed,
85
+ importPath,
86
+ coercion,
87
+ operations,
88
+ inferred,
89
+ guidType,
90
+ mini,
91
+ wrapOutput,
92
+ paramsCasing,
93
+ transformers: preset.transformers,
78
94
  }
79
-
80
- if (group && (options?.group?.path || options?.group?.tag)) {
81
- const groupName: UserGroup['name'] = group?.name
82
- ? group.name
83
- : (ctx) => {
84
- if (group?.type === 'path') {
85
- return `${ctx.group.split('/')[1]}`
86
- }
87
- return `${camelCase(ctx.group)}Controller`
88
- }
89
-
90
- return path.resolve(
91
- root,
92
- output.path,
93
- groupName({
94
- group: group.type === 'path' ? options.group.path! : options.group.tag!,
95
- }),
96
- baseName,
97
- )
95
+ },
96
+ resolvePath(baseName, pathMode, options) {
97
+ if (!resolvePathWarning) {
98
+ this.events.emit('warn', 'Do not use resolvePath for pluginZod, use resolverZod.resolvePath instead')
99
+ resolvePathWarning = true
98
100
  }
99
101
 
100
- return path.resolve(root, output.path, baseName)
102
+ return this.plugin.resolver.resolvePath(
103
+ { baseName, pathMode, tag: options?.group?.tag, path: options?.group?.path },
104
+ { root: path.resolve(this.config.root, this.config.output.path), output, group: this.plugin.options.group },
105
+ )
101
106
  },
102
107
  resolveName(name, type) {
103
- let resolvedName = camelCase(name, {
104
- suffix: type ? 'schema' : undefined,
105
- isFile: type === 'file',
106
- })
107
-
108
- if (type === 'type') {
109
- resolvedName = pascalCase(resolvedName)
110
- }
111
-
112
- if (type) {
113
- return transformers?.name?.(resolvedName, type) || resolvedName
108
+ if (!resolveNameWarning) {
109
+ this.events.emit('warn', 'Do not use resolveName for pluginZod, use resolverZod.default instead')
110
+ resolveNameWarning = true
114
111
  }
115
112
 
116
- return resolvedName
113
+ return this.plugin.resolver.default(name, type)
117
114
  },
118
115
  async install() {
119
- const root = path.resolve(this.config.root, this.config.output.path)
120
- const mode = getMode(path.resolve(root, output.path))
121
- const oas = await this.getOas()
122
-
123
- if (this.plugin.options.typed && this.plugin.options.version === '3') {
124
- // pre add bundled
125
- await this.addFile({
126
- baseName: 'ToZod.ts',
127
- path: path.resolve(root, '.kubb/ToZod.ts'),
128
- sources: [
129
- {
130
- name: 'ToZod',
131
- value: toZodSource,
132
- },
133
- ],
134
- imports: [],
135
- exports: [],
136
- })
116
+ const { config, fabric, plugin, adapter, rootNode, driver, openInStudio, resolver } = this
117
+
118
+ const root = path.resolve(config.root, config.output.path)
119
+
120
+ if (!adapter) {
121
+ throw new Error(`[${pluginZodName}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`)
137
122
  }
138
123
 
139
- const schemaGenerator = new SchemaGenerator(this.plugin.options, {
140
- fabric: this.fabric,
141
- oas,
142
- driver: this.driver,
143
- events: this.events,
144
- plugin: this.plugin,
145
- contentType,
146
- include: undefined,
147
- override,
148
- mode,
149
- output: output.path,
150
- })
124
+ await openInStudio({ ast: true })
125
+
126
+ const collectedOperations: Array<OperationNode> = []
127
+ const generatorContext = { generators: preset.generators, plugin, resolver, exclude, include, override, fabric, adapter, config, driver }
151
128
 
152
- const schemaFiles = await schemaGenerator.build(...generators)
153
- await this.upsertFile(...schemaFiles)
154
-
155
- const operationGenerator = new OperationGenerator(this.plugin.options, {
156
- fabric: this.fabric,
157
- oas,
158
- driver: this.driver,
159
- events: this.events,
160
- plugin: this.plugin,
161
- contentType,
162
- exclude,
163
- include,
164
- override,
165
- mode,
129
+ await walk(rootNode, {
130
+ depth: 'shallow',
131
+ async schema(schemaNode) {
132
+ await runGeneratorSchema(schemaNode, generatorContext)
133
+ },
134
+ async operation(operationNode) {
135
+ const baseOptions = resolver.resolveOptions(operationNode, { options: plugin.options, exclude, include, override })
136
+
137
+ if (baseOptions !== null) {
138
+ collectedOperations.push(operationNode)
139
+ }
140
+
141
+ await runGeneratorOperation(operationNode, generatorContext)
142
+ },
166
143
  })
167
144
 
168
- const operationFiles = await operationGenerator.build(...generators)
169
- await this.upsertFile(...operationFiles)
145
+ await runGeneratorOperations(collectedOperations, generatorContext)
170
146
 
171
147
  const barrelFiles = await getBarrelFiles(this.fabric.files, {
172
148
  type: output.barrelType ?? 'named',