@kubb/plugin-zod 5.0.0-alpha.8 → 5.0.0-beta.3

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 (45) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +1 -3
  3. package/dist/index.cjs +1061 -105
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +369 -4
  6. package/dist/index.js +1053 -104
  7. package/dist/index.js.map +1 -1
  8. package/package.json +44 -70
  9. package/src/components/Operations.tsx +25 -18
  10. package/src/components/Zod.tsx +21 -121
  11. package/src/constants.ts +5 -0
  12. package/src/generators/zodGenerator.tsx +174 -160
  13. package/src/index.ts +11 -2
  14. package/src/plugin.ts +67 -156
  15. package/src/printers/printerZod.ts +339 -0
  16. package/src/printers/printerZodMini.ts +295 -0
  17. package/src/resolvers/resolverZod.ts +57 -0
  18. package/src/types.ts +130 -115
  19. package/src/utils.ts +222 -0
  20. package/dist/components-B7zUFnAm.cjs +0 -890
  21. package/dist/components-B7zUFnAm.cjs.map +0 -1
  22. package/dist/components-eECfXVou.js +0 -842
  23. package/dist/components-eECfXVou.js.map +0 -1
  24. package/dist/components.cjs +0 -4
  25. package/dist/components.d.ts +0 -56
  26. package/dist/components.js +0 -2
  27. package/dist/generators-BjPDdJUz.cjs +0 -301
  28. package/dist/generators-BjPDdJUz.cjs.map +0 -1
  29. package/dist/generators-lTWPS6oN.js +0 -290
  30. package/dist/generators-lTWPS6oN.js.map +0 -1
  31. package/dist/generators.cjs +0 -4
  32. package/dist/generators.d.ts +0 -479
  33. package/dist/generators.js +0 -2
  34. package/dist/templates/ToZod.source.cjs +0 -7
  35. package/dist/templates/ToZod.source.cjs.map +0 -1
  36. package/dist/templates/ToZod.source.d.ts +0 -7
  37. package/dist/templates/ToZod.source.js +0 -6
  38. package/dist/templates/ToZod.source.js.map +0 -1
  39. package/dist/types-CoCoOc2u.d.ts +0 -172
  40. package/src/components/index.ts +0 -2
  41. package/src/generators/index.ts +0 -2
  42. package/src/generators/operationsGenerator.tsx +0 -50
  43. package/src/parser.ts +0 -909
  44. package/src/templates/ToZod.source.ts +0 -4
  45. package/templates/ToZod.ts +0 -61
@@ -1,201 +1,215 @@
1
- import path from 'node:path'
2
- import { useMode, usePluginDriver } from '@kubb/core/hooks'
3
- import { type OperationSchema as OperationSchemaType, SchemaGenerator, schemaKeywords } from '@kubb/plugin-oas'
4
- import { createReactGenerator } from '@kubb/plugin-oas/generators'
5
- import { useOas, useOperationManager, useSchemaManager } from '@kubb/plugin-oas/hooks'
6
- import { getBanner, getFooter, getImports } from '@kubb/plugin-oas/utils'
7
- import { pluginTsName } from '@kubb/plugin-ts'
8
- import { File } from '@kubb/react-fabric'
9
- import { Zod } from '../components'
1
+ import type { Adapter } from '@kubb/core'
2
+ import { ast, defineGenerator } from '@kubb/core'
3
+ import type { AdapterOas } from '@kubb/adapter-oas'
4
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
5
+ import { Operations } from '../components/Operations.tsx'
6
+ import { Zod } from '../components/Zod.tsx'
7
+ import { ZOD_NAMESPACE_IMPORTS } from '../constants.ts'
8
+ import { printerZod } from '../printers/printerZod.ts'
9
+ import { printerZodMini } from '../printers/printerZodMini.ts'
10
10
  import type { PluginZod } from '../types'
11
+ import { buildSchemaNames } from '../utils.ts'
11
12
 
12
- export const zodGenerator = createReactGenerator<PluginZod>({
13
+ export const zodGenerator = defineGenerator<PluginZod>({
13
14
  name: 'zod',
14
- Operation({ config, operation, generator, plugin }) {
15
- const {
16
- options,
17
- options: { coercion: globalCoercion, inferred, typed, mapper, wrapOutput, version, guidType, mini },
18
- } = plugin
19
-
20
- const mode = useMode()
21
- const driver = usePluginDriver()
22
-
23
- const oas = useOas()
24
- const { getSchemas, getFile, getGroup } = useOperationManager(generator)
25
- const schemaManager = useSchemaManager()
26
-
27
- const file = getFile(operation)
28
- const schemas = getSchemas(operation)
29
- const schemaGenerator = new SchemaGenerator(options, {
30
- fabric: generator.context.fabric,
31
- oas,
32
- plugin,
33
- driver,
34
- events: generator.context.events,
35
- mode,
36
- override: options.override,
37
- })
15
+ renderer: jsxRenderer,
16
+ schema(node, ctx) {
17
+ const { adapter, config, resolver, root } = ctx
18
+ const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options
19
+ const dateType = (adapter as Adapter<AdapterOas>).options.dateType
20
+
21
+ if (!node.name) {
22
+ return
23
+ }
38
24
 
39
- const operationSchemas = [schemas.pathParams, schemas.queryParams, schemas.headerParams, schemas.statusCodes, schemas.request, schemas.response]
40
- .flat()
41
- .filter(Boolean)
42
- const toZodPath = path.resolve(config.root, config.output.path, '.kubb/ToZod.ts')
25
+ const mode = ctx.getMode(output)
26
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
43
27
 
44
- const mapOperationSchema = ({ name, schema: schemaOriginal, description, keysToOmit: keysToOmitOriginal, ...options }: OperationSchemaType) => {
45
- let schemaObject = schemaOriginal
46
- let keysToOmit = keysToOmitOriginal
28
+ const imports = adapter.getImports(node, (schemaName) => ({
29
+ name: resolver.resolveSchemaName(schemaName),
30
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
31
+ }))
47
32
 
48
- if ((schemaOriginal.anyOf || schemaOriginal.oneOf) && keysToOmitOriginal && keysToOmitOriginal.length > 0) {
49
- schemaObject = structuredClone(schemaOriginal)
33
+ const meta = {
34
+ name: resolver.resolveSchemaName(node.name),
35
+ file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
36
+ } as const
50
37
 
51
- // Remove $ref so the schema parser generates inline schema instead of a reference
52
- delete schemaObject.$ref
38
+ const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : undefined
53
39
 
54
- for (const key of keysToOmitOriginal) {
55
- delete schemaObject.properties?.[key]
56
- }
40
+ const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : undefined
57
41
 
58
- if (Array.isArray(schemaObject.required)) {
59
- schemaObject.required = schemaObject.required.filter((key) => !keysToOmitOriginal.includes(key))
60
- }
42
+ const schemaPrinter = mini
43
+ ? printerZodMini({ guidType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
44
+ : printerZod({ coercion, guidType, dateType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
61
45
 
62
- keysToOmit = undefined
63
- }
46
+ return (
47
+ <File
48
+ baseName={meta.file.baseName}
49
+ path={meta.file.path}
50
+ meta={meta.file.meta}
51
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
52
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
53
+ >
54
+ <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
55
+ {mode === 'split' && imports.map((imp) => <File.Import key={[node.name, imp.path].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
64
56
 
65
- const hasProperties = Object.keys(schemaObject || {}).length > 0
66
- const hasDefaults = Object.values(schemaObject.properties || {}).some((prop) => prop && Object.hasOwn(prop, 'default'))
57
+ <Zod name={meta.name} node={node} printer={schemaPrinter} inferTypeName={inferTypeName} />
58
+ </File>
59
+ )
60
+ },
61
+ operation(node, ctx) {
62
+ const { adapter, config, resolver, root } = ctx
63
+ const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options
64
+ const dateType = (adapter as Adapter<AdapterOas>).options.dateType
67
65
 
68
- const required = Array.isArray(schemaObject?.required) ? schemaObject.required.length > 0 : !!schemaObject?.required
69
- const optional = !required && !hasDefaults && hasProperties && name.includes('Params')
66
+ const mode = ctx.getMode(output)
67
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
70
68
 
71
- if (!optional && Array.isArray(schemaObject.required) && !schemaObject.required.length) {
72
- schemaObject.required = Object.entries(schemaObject.properties || {})
73
- .filter(([_key, value]) => value && Object.hasOwn(value, 'default'))
74
- .map(([key]) => key)
75
- }
69
+ const params = ast.caseParams(node.parameters, paramsCasing)
76
70
 
77
- const tree = [
78
- ...schemaGenerator.parse({ schema: schemaObject, name, parentName: null }),
79
- optional ? { keyword: schemaKeywords.optional } : undefined,
80
- ].filter(Boolean)
81
- const imports = getImports(tree)
82
- const group = options.operation ? getGroup(options.operation) : undefined
71
+ const meta = {
72
+ file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
73
+ } as const
83
74
 
84
- const coercion = name.includes('Params') ? { numbers: true, strings: false, dates: true } : globalCoercion
75
+ const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : undefined
85
76
 
86
- const zod = {
87
- name: schemaManager.getName(name, { type: 'function' }),
88
- inferTypeName: schemaManager.getName(name, { type: 'type' }),
89
- file: schemaManager.getFile(name),
90
- }
77
+ function renderSchemaEntry({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
78
+ if (!schema) return null
91
79
 
92
- const type = {
93
- name: schemaManager.getName(name, {
94
- type: 'type',
95
- pluginName: pluginTsName,
96
- }),
97
- file: schemaManager.getFile(options.operationName || name, {
98
- pluginName: pluginTsName,
99
- group,
100
- }),
101
- }
80
+ const inferTypeName = inferred ? resolver.resolveTypeName(name) : undefined
81
+
82
+ const imports = adapter.getImports(schema, (schemaName) => ({
83
+ name: resolver.resolveSchemaName(schemaName),
84
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
85
+ }))
86
+
87
+ const schemaPrinter = mini
88
+ ? printerZodMini({ guidType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes })
89
+ : printerZod({ coercion, guidType, dateType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes })
102
90
 
103
91
  return (
104
92
  <>
105
- {typed && <File.Import isTypeOnly root={file.path} path={type.file.path} name={[type.name]} />}
106
- {imports.map((imp) => (
107
- <File.Import key={[imp.path, imp.name, imp.isTypeOnly].join('-')} root={file.path} path={imp.path} name={imp.name} />
108
- ))}
109
- <Zod
110
- name={zod.name}
111
- typeName={typed ? type.name : undefined}
112
- inferTypeName={inferred ? zod.inferTypeName : undefined}
113
- description={description}
114
- tree={tree}
115
- schema={schemaObject}
116
- mapper={mapper}
117
- coercion={coercion}
118
- keysToOmit={keysToOmit}
119
- wrapOutput={wrapOutput}
120
- version={plugin.options.version}
121
- guidType={guidType}
122
- emptySchemaType={plugin.options.emptySchemaType}
123
- mini={mini}
124
- />
93
+ {mode === 'split' &&
94
+ imports.map((imp) => <File.Import key={[name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
95
+ <Zod name={name} node={schema} printer={schemaPrinter} inferTypeName={inferTypeName} />
125
96
  </>
126
97
  )
127
98
  }
128
99
 
129
- const isZodImport = plugin.options.importPath === 'zod' || plugin.options.importPath === 'zod/mini'
100
+ const paramSchemas = params.map((param) => renderSchemaEntry({ schema: param.schema, name: resolver.resolveParamName(node, param) }))
101
+
102
+ const responseSchemas = node.responses.map((res) =>
103
+ renderSchemaEntry({
104
+ schema: res.schema,
105
+ name: resolver.resolveResponseStatusName(node, res.statusCode),
106
+ keysToOmit: res.keysToOmit,
107
+ }),
108
+ )
109
+
110
+ const responsesWithSchema = node.responses.filter((res) => res.schema)
111
+ const responseUnionSchema =
112
+ responsesWithSchema.length > 0
113
+ ? (() => {
114
+ const responseUnionName = resolver.resolveResponseName(node)
115
+
116
+ // Collect all import names from response schemas to detect naming collisions.
117
+ // When a response is a $ref to a component schema whose resolved name matches
118
+ // the response union name, skip generation to avoid redeclaration errors.
119
+ const importedNames = new Set(
120
+ responsesWithSchema.flatMap((res) =>
121
+ res.schema
122
+ ? adapter
123
+ .getImports(res.schema, (schemaName) => ({
124
+ name: resolver.resolveSchemaName(schemaName),
125
+ path: '',
126
+ }))
127
+ .flatMap((imp) => (Array.isArray(imp.name) ? imp.name : [imp.name]))
128
+ : [],
129
+ ),
130
+ )
131
+
132
+ if (importedNames.has(responseUnionName)) {
133
+ return null
134
+ }
135
+
136
+ const members = responsesWithSchema.map((res) => ast.createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, res.statusCode) }))
137
+ const unionNode = members.length === 1 ? members[0]! : ast.createSchema({ type: 'union', members })
138
+
139
+ return renderSchemaEntry({
140
+ schema: unionNode,
141
+ name: responseUnionName,
142
+ })
143
+ })()
144
+ : null
145
+
146
+ const requestSchema = node.requestBody?.content?.[0]?.schema
147
+ ? renderSchemaEntry({
148
+ schema: {
149
+ ...node.requestBody.content![0]!.schema!,
150
+ description: node.requestBody.description ?? node.requestBody.content![0]!.schema!.description,
151
+ },
152
+ name: resolver.resolveDataName(node),
153
+ keysToOmit: node.requestBody.content![0]!.keysToOmit,
154
+ })
155
+ : null
130
156
 
131
157
  return (
132
158
  <File
133
- baseName={file.baseName}
134
- path={file.path}
135
- meta={file.meta}
136
- banner={getBanner({ oas, output: plugin.options.output, config: driver.config })}
137
- footer={getFooter({ oas, output: plugin.options.output })}
159
+ baseName={meta.file.baseName}
160
+ path={meta.file.path}
161
+ meta={meta.file.meta}
162
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
163
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
138
164
  >
139
- <File.Import name={isZodImport ? 'z' : ['z']} path={plugin.options.importPath} isNameSpace={isZodImport} />
140
- {typed && version === '3' && <File.Import name={['ToZod']} isTypeOnly root={file.path} path={toZodPath} />}
141
- {operationSchemas.map(mapOperationSchema)}
165
+ <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
166
+ {paramSchemas}
167
+ {responseSchemas}
168
+ {responseUnionSchema}
169
+ {requestSchema}
142
170
  </File>
143
171
  )
144
172
  },
145
- Schema({ config, schema, plugin }) {
146
- const { getName, getFile } = useSchemaManager()
147
- const {
148
- options: { output, emptySchemaType, coercion, inferred, typed, mapper, importPath, wrapOutput, version, guidType, mini },
149
- } = plugin
150
- const driver = usePluginDriver()
151
- const oas = useOas()
152
-
153
- const imports = getImports(schema.tree)
154
-
155
- const zod = {
156
- name: getName(schema.name, { type: 'function' }),
157
- inferTypeName: getName(schema.name, { type: 'type' }),
158
- file: getFile(schema.name),
159
- }
173
+ operations(nodes, ctx) {
174
+ const { adapter, config, resolver, root } = ctx
175
+ const { output, importPath, group, operations, paramsCasing } = ctx.options
160
176
 
161
- const type = {
162
- name: getName(schema.name, { type: 'type', pluginName: pluginTsName }),
163
- file: getFile(schema.name, { pluginName: pluginTsName }),
177
+ if (!operations) {
178
+ return
164
179
  }
180
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
181
+
182
+ const meta = {
183
+ file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group }),
184
+ } as const
165
185
 
166
- const isZodImport = importPath === 'zod' || importPath === 'zod/mini'
167
- const toZodPath = path.resolve(config.root, config.output.path, '.kubb/ToZod.ts')
186
+ const transformedOperations = nodes.map((node) => {
187
+ const params = ast.caseParams(node.parameters, paramsCasing)
188
+
189
+ return {
190
+ node,
191
+ data: buildSchemaNames(node, { params, resolver }),
192
+ }
193
+ })
194
+
195
+ const imports = transformedOperations.flatMap(({ node, data }) => {
196
+ const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as string[]
197
+ const opFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
198
+
199
+ return names.map((name) => <File.Import key={[name, opFile.path].join('-')} name={[name]} root={meta.file.path} path={opFile.path} />)
200
+ })
168
201
 
169
202
  return (
170
203
  <File
171
- baseName={zod.file.baseName}
172
- path={zod.file.path}
173
- meta={zod.file.meta}
174
- banner={getBanner({ oas, output, config: driver.config })}
175
- footer={getFooter({ oas, output })}
204
+ baseName={meta.file.baseName}
205
+ path={meta.file.path}
206
+ meta={meta.file.meta}
207
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
208
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
176
209
  >
177
- <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
178
- {typed && <File.Import isTypeOnly root={zod.file.path} path={type.file.path} name={[type.name]} />}
179
- {typed && version === '3' && <File.Import name={['ToZod']} isTypeOnly root={zod.file.path} path={toZodPath} />}
180
- {imports.map((imp) => (
181
- <File.Import key={[imp.path, imp.name, imp.isTypeOnly].join('-')} root={zod.file.path} path={imp.path} name={imp.name} />
182
- ))}
183
-
184
- <Zod
185
- name={zod.name}
186
- typeName={typed ? type.name : undefined}
187
- inferTypeName={inferred ? zod.inferTypeName : undefined}
188
- description={schema.value.description}
189
- tree={schema.tree}
190
- schema={schema.value}
191
- mapper={mapper}
192
- coercion={coercion}
193
- wrapOutput={wrapOutput}
194
- version={version}
195
- guidType={guidType}
196
- emptySchemaType={emptySchemaType}
197
- mini={mini}
198
- />
210
+ <File.Import isTypeOnly name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
211
+ {imports}
212
+ <Operations name="operations" operations={transformedOperations} />
199
213
  </File>
200
214
  )
201
215
  },
package/src/index.ts CHANGED
@@ -1,2 +1,11 @@
1
- export { pluginZod, pluginZodName } from './plugin.ts'
2
- export type { PluginZod } from './types.ts'
1
+ export { zodGenerator } from './generators/zodGenerator.tsx'
2
+
3
+ export { default, pluginZod, pluginZodName } from './plugin.ts'
4
+ export type { PrinterZodFactory, PrinterZodNodes, PrinterZodOptions } from './printers/printerZod.ts'
5
+ export { printerZod } from './printers/printerZod.ts'
6
+ export type { PrinterZodMiniFactory, PrinterZodMiniNodes, PrinterZodMiniOptions } from './printers/printerZodMini.ts'
7
+ export { printerZodMini } from './printers/printerZodMini.ts'
8
+
9
+ export { resolverZod } from './resolvers/resolverZod.ts'
10
+
11
+ export type { PluginZod, ResolverZod } from './types.ts'
package/src/plugin.ts CHANGED
@@ -1,183 +1,94 @@
1
- import path from 'node:path'
2
- import { camelCase, pascalCase } from '@internals/utils'
3
- import { createPlugin, type Group, getBarrelFiles, getMode, PackageManager } from '@kubb/core'
4
- import { OperationGenerator, pluginOasName, SchemaGenerator } from '@kubb/plugin-oas'
5
- import { pluginTsName } from '@kubb/plugin-ts'
6
- import { operationsGenerator } from './generators'
1
+ import { camelCase } from '@internals/utils'
2
+ import { definePlugin, type Group } from '@kubb/core'
7
3
  import { zodGenerator } from './generators/zodGenerator.tsx'
8
- import { source as toZodSource } from './templates/ToZod.source.ts'
4
+ import { resolverZod } from './resolvers/resolverZod.ts'
9
5
  import type { PluginZod } from './types.ts'
10
6
 
7
+ /**
8
+ * Canonical plugin name for `@kubb/plugin-zod`, used in driver lookups and warnings.
9
+ */
11
10
  export const pluginZodName = 'plugin-zod' satisfies PluginZod['name']
12
11
 
13
- export const pluginZod = createPlugin<PluginZod>((options) => {
12
+ /**
13
+ * Generates Zod validation schemas from an OpenAPI specification.
14
+ * Walks schemas and operations, delegates to generators, and writes barrel files
15
+ * based on the configured `barrelType`.
16
+ *
17
+ * @example Zod schema generator
18
+ * ```ts
19
+ * import pluginZod from '@kubb/plugin-zod'
20
+ * export default defineConfig({
21
+ * plugins: [pluginZod({ output: { path: 'zod' } })]
22
+ * })
23
+ * ```
24
+ */
25
+ export const pluginZod = definePlugin<PluginZod>((options) => {
14
26
  const {
15
27
  output = { path: 'zod', barrelType: 'named' },
16
28
  group,
17
29
  exclude = [],
18
30
  include,
19
31
  override = [],
20
- transformers = {},
21
- dateType = 'string',
22
- unknownType = 'any',
23
- emptySchemaType = unknownType,
24
- integerType = 'number',
25
32
  typed = false,
26
- mapper = {},
27
33
  operations = false,
28
34
  mini = false,
29
- version = mini ? '4' : new PackageManager().isValidSync('zod', '>=4') ? '4' : '3',
30
35
  guidType = 'uuid',
31
- importPath = mini ? 'zod/mini' : version === '4' ? 'zod/v4' : 'zod',
36
+ importPath = mini ? 'zod/mini' : 'zod',
32
37
  coercion = false,
33
38
  inferred = false,
34
- generators = [zodGenerator, operations ? operationsGenerator : undefined].filter(Boolean),
35
39
  wrapOutput = undefined,
36
- contentType,
40
+ paramsCasing,
41
+ printer,
42
+ resolver: userResolver,
43
+ transformer: userTransformer,
44
+ generators: userGenerators = [],
37
45
  } = options
38
46
 
39
- // @deprecated Will be removed in v5 when collisionDetection defaults to true
40
- const usedEnumNames = {}
47
+ const groupConfig = group
48
+ ? ({
49
+ ...group,
50
+ name: (ctx) => {
51
+ if (group.type === 'path') {
52
+ return `${ctx.group.split('/')[1]}`
53
+ }
54
+ return `${camelCase(ctx.group)}Controller`
55
+ },
56
+ } satisfies Group)
57
+ : undefined
41
58
 
42
59
  return {
43
60
  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
- },
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)
78
- }
79
-
80
- if (group && (options?.group?.path || options?.group?.tag)) {
81
- const groupName: Group['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
- )
98
- }
99
-
100
- return path.resolve(root, output.path, baseName)
101
- },
102
- 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
114
- }
115
-
116
- return resolvedName
117
- },
118
- 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: [],
61
+ options,
62
+ hooks: {
63
+ 'kubb:plugin:setup'(ctx) {
64
+ ctx.setOptions({
65
+ output,
66
+ exclude,
67
+ include,
68
+ override,
69
+ group: groupConfig,
70
+ typed,
71
+ importPath,
72
+ coercion,
73
+ operations,
74
+ inferred,
75
+ guidType,
76
+ mini,
77
+ wrapOutput,
78
+ paramsCasing,
79
+ printer,
136
80
  })
137
- }
138
-
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
- })
151
-
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,
166
- })
167
-
168
- const operationFiles = await operationGenerator.build(...generators)
169
- await this.upsertFile(...operationFiles)
170
-
171
- const barrelFiles = await getBarrelFiles(this.fabric.files, {
172
- type: output.barrelType ?? 'named',
173
- root,
174
- output,
175
- meta: {
176
- pluginName: this.plugin.name,
177
- },
178
- })
179
-
180
- await this.upsertFile(...barrelFiles)
81
+ ctx.setResolver(userResolver ? { ...resolverZod, ...userResolver } : resolverZod)
82
+ if (userTransformer) {
83
+ ctx.setTransformer(userTransformer)
84
+ }
85
+ ctx.addGenerator(zodGenerator)
86
+ for (const gen of userGenerators) {
87
+ ctx.addGenerator(gen)
88
+ }
89
+ },
181
90
  },
182
91
  }
183
92
  })
93
+
94
+ export default pluginZod