@kubb/plugin-zod 5.0.0-alpha.3 → 5.0.0-alpha.30
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/dist/index.cjs +1619 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +418 -4
- package/dist/index.js +1614 -100
- package/dist/index.js.map +1 -1
- package/package.json +6 -34
- package/src/components/Operations.tsx +22 -15
- package/src/components/Zod.tsx +20 -119
- package/src/constants.ts +5 -0
- package/src/generators/zodGenerator.tsx +129 -159
- package/src/generators/zodGeneratorLegacy.tsx +365 -0
- package/src/index.ts +12 -1
- package/src/plugin.ts +102 -148
- package/src/presets.ts +30 -0
- package/src/printers/printerZod.ts +298 -0
- package/src/printers/printerZodMini.ts +273 -0
- package/src/resolvers/resolverZod.ts +61 -0
- package/src/resolvers/resolverZodLegacy.ts +60 -0
- package/src/types.ts +172 -93
- package/src/utils.ts +248 -0
- package/dist/components-B7zUFnAm.cjs +0 -890
- package/dist/components-B7zUFnAm.cjs.map +0 -1
- package/dist/components-eECfXVou.js +0 -842
- package/dist/components-eECfXVou.js.map +0 -1
- package/dist/components.cjs +0 -4
- package/dist/components.d.ts +0 -56
- package/dist/components.js +0 -2
- package/dist/generators-CRKtFRi1.js +0 -290
- package/dist/generators-CRKtFRi1.js.map +0 -1
- package/dist/generators-CzSLRVqQ.cjs +0 -301
- package/dist/generators-CzSLRVqQ.cjs.map +0 -1
- package/dist/generators.cjs +0 -4
- package/dist/generators.d.ts +0 -503
- package/dist/generators.js +0 -2
- package/dist/templates/ToZod.source.cjs +0 -7
- package/dist/templates/ToZod.source.cjs.map +0 -1
- package/dist/templates/ToZod.source.d.ts +0 -7
- package/dist/templates/ToZod.source.js +0 -6
- package/dist/templates/ToZod.source.js.map +0 -1
- package/dist/types-D0wsPC6Y.d.ts +0 -172
- package/src/components/index.ts +0 -2
- package/src/generators/index.ts +0 -2
- package/src/generators/operationsGenerator.tsx +0 -50
- package/src/parser.ts +0 -909
- package/src/templates/ToZod.source.ts +0 -4
- package/templates/ToZod.ts +0 -61
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { caseParams, createProperty, createSchema } from '@kubb/ast'
|
|
2
|
+
import type { OperationNode, ParameterNode, SchemaNode } from '@kubb/ast/types'
|
|
3
|
+
import { defineGenerator } from '@kubb/core'
|
|
4
|
+
import { File } from '@kubb/react-fabric'
|
|
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
|
+
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
|
+
schema(node, options) {
|
|
170
|
+
const { adapter, config, resolver, root } = this
|
|
171
|
+
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = options
|
|
172
|
+
|
|
173
|
+
if (!node.name) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const mode = this.getMode(output)
|
|
178
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
179
|
+
|
|
180
|
+
const imports = adapter.getImports(node, (schemaName) => ({
|
|
181
|
+
name: resolver.resolveSchemaName(schemaName),
|
|
182
|
+
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
183
|
+
}))
|
|
184
|
+
|
|
185
|
+
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : undefined
|
|
186
|
+
|
|
187
|
+
const meta = {
|
|
188
|
+
name: resolver.resolveSchemaName(node.name),
|
|
189
|
+
file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
|
|
190
|
+
} as const
|
|
191
|
+
|
|
192
|
+
const schemaPrinter = mini
|
|
193
|
+
? printerZodMini({ guidType, wrapOutput, resolver, schemaName: meta.name, nodes: printer?.nodes })
|
|
194
|
+
: printerZod({ coercion, guidType, wrapOutput, resolver, schemaName: meta.name, nodes: printer?.nodes })
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<File
|
|
198
|
+
baseName={meta.file.baseName}
|
|
199
|
+
path={meta.file.path}
|
|
200
|
+
meta={meta.file.meta}
|
|
201
|
+
banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
|
|
202
|
+
footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
|
|
203
|
+
>
|
|
204
|
+
<File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
205
|
+
{mode === 'split' && imports.map((imp) => <File.Import key={[node.name, imp.path].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
|
|
206
|
+
|
|
207
|
+
<Zod name={meta.name} node={node} printer={schemaPrinter} inferTypeName={inferTypeName} />
|
|
208
|
+
</File>
|
|
209
|
+
)
|
|
210
|
+
},
|
|
211
|
+
operation(node, options) {
|
|
212
|
+
const { adapter, config, resolver, root } = this
|
|
213
|
+
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = options
|
|
214
|
+
|
|
215
|
+
const mode = this.getMode(output)
|
|
216
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
217
|
+
|
|
218
|
+
const params = caseParams(node.parameters, paramsCasing)
|
|
219
|
+
|
|
220
|
+
const meta = {
|
|
221
|
+
file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
|
|
222
|
+
} as const
|
|
223
|
+
|
|
224
|
+
function renderSchemaEntry({ schema, name, keysToOmit }: { schema: SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
|
|
225
|
+
if (!schema) return null
|
|
226
|
+
|
|
227
|
+
const inferTypeName = inferred ? resolver.resolveTypeName(name) : undefined
|
|
228
|
+
|
|
229
|
+
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
230
|
+
name: resolver.resolveSchemaName(schemaName),
|
|
231
|
+
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
232
|
+
}))
|
|
233
|
+
|
|
234
|
+
const schemaPrinter = mini
|
|
235
|
+
? printerZodMini({ guidType, wrapOutput, resolver, schemaName: name, keysToOmit, nodes: printer?.nodes })
|
|
236
|
+
: printerZod({ coercion, guidType, wrapOutput, resolver, schemaName: name, keysToOmit, nodes: printer?.nodes })
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<>
|
|
240
|
+
{mode === 'split' &&
|
|
241
|
+
imports.map((imp) => <File.Import key={[name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
|
|
242
|
+
<Zod name={name} node={schema} printer={schemaPrinter} inferTypeName={inferTypeName} />
|
|
243
|
+
</>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const pathParams = params.filter((p) => p.in === 'path')
|
|
248
|
+
const queryParams = params.filter((p) => p.in === 'query')
|
|
249
|
+
const headerParams = params.filter((p) => p.in === 'header')
|
|
250
|
+
|
|
251
|
+
const responseSchemas = node.responses.map((res) => {
|
|
252
|
+
const responseName = resolver.resolveResponseStatusName(node, res.statusCode)
|
|
253
|
+
return renderSchemaEntry({
|
|
254
|
+
schema: {
|
|
255
|
+
...res.schema,
|
|
256
|
+
description: res.description ?? res.schema.description,
|
|
257
|
+
},
|
|
258
|
+
name: responseName,
|
|
259
|
+
keysToOmit: res.keysToOmit,
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const requestSchema = node.requestBody?.schema
|
|
264
|
+
? renderSchemaEntry({
|
|
265
|
+
schema: {
|
|
266
|
+
...node.requestBody.schema,
|
|
267
|
+
description: node.requestBody.description ?? node.requestBody.schema.description,
|
|
268
|
+
},
|
|
269
|
+
name: resolver.resolveDataName(node),
|
|
270
|
+
keysToOmit: node.requestBody.keysToOmit,
|
|
271
|
+
})
|
|
272
|
+
: null
|
|
273
|
+
|
|
274
|
+
const legacyParamTypes = [
|
|
275
|
+
pathParams.length > 0
|
|
276
|
+
? renderSchemaEntry({
|
|
277
|
+
schema: buildGroupedParamsSchema({ params: pathParams, optional: pathParams.every((p) => !p.required) }),
|
|
278
|
+
name: resolver.resolvePathParamsName(node, pathParams[0]!),
|
|
279
|
+
})
|
|
280
|
+
: null,
|
|
281
|
+
queryParams.length > 0
|
|
282
|
+
? renderSchemaEntry({
|
|
283
|
+
schema: buildGroupedParamsSchema({ params: queryParams, optional: queryParams.every((p) => !p.required) }),
|
|
284
|
+
name: resolver.resolveQueryParamsName(node, queryParams[0]!),
|
|
285
|
+
})
|
|
286
|
+
: null,
|
|
287
|
+
headerParams.length > 0
|
|
288
|
+
? renderSchemaEntry({
|
|
289
|
+
schema: buildGroupedParamsSchema({ params: headerParams, optional: headerParams.every((p) => !p.required) }),
|
|
290
|
+
name: resolver.resolveHeaderParamsName(node, headerParams[0]!),
|
|
291
|
+
})
|
|
292
|
+
: null,
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
const legacyResponsesSchema = renderSchemaEntry({
|
|
296
|
+
schema: buildLegacyResponsesSchemaNode(node, { resolver }),
|
|
297
|
+
name: resolver.resolveResponsesName(node),
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
const legacyResponseSchema = renderSchemaEntry({
|
|
301
|
+
schema: buildLegacyResponseUnionSchemaNode(node, { resolver }),
|
|
302
|
+
name: resolver.resolveResponseName(node),
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<File
|
|
307
|
+
baseName={meta.file.baseName}
|
|
308
|
+
path={meta.file.path}
|
|
309
|
+
meta={meta.file.meta}
|
|
310
|
+
banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
|
|
311
|
+
footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
|
|
312
|
+
>
|
|
313
|
+
<File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
314
|
+
{legacyParamTypes}
|
|
315
|
+
{responseSchemas}
|
|
316
|
+
{requestSchema}
|
|
317
|
+
{legacyResponseSchema}
|
|
318
|
+
{legacyResponsesSchema}
|
|
319
|
+
</File>
|
|
320
|
+
)
|
|
321
|
+
},
|
|
322
|
+
operations(nodes, options) {
|
|
323
|
+
const { adapter, config, resolver, root } = this
|
|
324
|
+
const { output, importPath, group, operations, paramsCasing } = options
|
|
325
|
+
|
|
326
|
+
if (!operations) {
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
330
|
+
|
|
331
|
+
const meta = {
|
|
332
|
+
file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group }),
|
|
333
|
+
} as const
|
|
334
|
+
|
|
335
|
+
const transformedOperations = nodes.map((node) => {
|
|
336
|
+
const params = caseParams(node.parameters, paramsCasing)
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
node,
|
|
340
|
+
data: buildLegacySchemaNames(node, params, resolver),
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const imports = transformedOperations.flatMap(({ node, data }) => {
|
|
345
|
+
const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as string[]
|
|
346
|
+
const opFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
|
|
347
|
+
|
|
348
|
+
return names.map((name) => <File.Import key={[name, opFile.path].join('-')} name={[name]} root={meta.file.path} path={opFile.path} />)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<File
|
|
353
|
+
baseName={meta.file.baseName}
|
|
354
|
+
path={meta.file.path}
|
|
355
|
+
meta={meta.file.meta}
|
|
356
|
+
banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
|
|
357
|
+
footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
|
|
358
|
+
>
|
|
359
|
+
<File.Import isTypeOnly name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
360
|
+
{imports}
|
|
361
|
+
<Operations name="operations" operations={transformedOperations} />
|
|
362
|
+
</File>
|
|
363
|
+
)
|
|
364
|
+
},
|
|
365
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
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 {
|
|
5
|
+
export type { PrinterZodFactory, PrinterZodNodes, PrinterZodOptions } from './printers/printerZod.ts'
|
|
6
|
+
export { printerZod } from './printers/printerZod.ts'
|
|
7
|
+
export type { PrinterZodMiniFactory, PrinterZodMiniNodes, PrinterZodMiniOptions } from './printers/printerZodMini.ts'
|
|
8
|
+
export { printerZodMini } from './printers/printerZodMini.ts'
|
|
9
|
+
|
|
10
|
+
export { resolverZod } from './resolvers/resolverZod.ts'
|
|
11
|
+
export { resolverZodLegacy } from './resolvers/resolverZodLegacy.ts'
|
|
12
|
+
|
|
13
|
+
export type { PluginZod, ResolverZod } from './types.ts'
|
package/src/plugin.ts
CHANGED
|
@@ -1,183 +1,137 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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'
|
|
1
|
+
import { camelCase } from '@internals/utils'
|
|
2
|
+
import { createPlugin, type Group, getPreset, mergeGenerators } from '@kubb/core'
|
|
3
|
+
import { version } from '../package.json'
|
|
4
|
+
import { presets } from './presets.ts'
|
|
9
5
|
import type { PluginZod } from './types.ts'
|
|
10
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Canonical plugin name for `@kubb/plugin-zod`, used to identify the plugin in driver lookups and warnings.
|
|
9
|
+
*/
|
|
11
10
|
export const pluginZodName = 'plugin-zod' satisfies PluginZod['name']
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The `@kubb/plugin-zod` plugin factory.
|
|
14
|
+
*
|
|
15
|
+
* Generates Zod validation schemas from an OpenAPI/AST `RootNode`.
|
|
16
|
+
* Walks schemas and operations, delegates rendering to the active generators,
|
|
17
|
+
* and writes barrel files based on `output.barrelType`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { pluginZod } from '@kubb/plugin-zod'
|
|
22
|
+
*
|
|
23
|
+
* export default defineConfig({
|
|
24
|
+
* plugins: [pluginZod({ output: { path: 'zod' } })],
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const pluginZod = createPlugin<PluginZod>((options) => {
|
|
14
29
|
const {
|
|
15
30
|
output = { path: 'zod', barrelType: 'named' },
|
|
16
31
|
group,
|
|
17
32
|
exclude = [],
|
|
18
33
|
include,
|
|
19
34
|
override = [],
|
|
20
|
-
transformers = {},
|
|
21
35
|
dateType = 'string',
|
|
22
|
-
unknownType = 'any',
|
|
23
|
-
emptySchemaType = unknownType,
|
|
24
|
-
integerType = 'number',
|
|
25
36
|
typed = false,
|
|
26
|
-
mapper = {},
|
|
27
37
|
operations = false,
|
|
28
38
|
mini = false,
|
|
29
|
-
version = mini ? '4' : new PackageManager().isValidSync('zod', '>=4') ? '4' : '3',
|
|
30
39
|
guidType = 'uuid',
|
|
31
|
-
importPath = mini ? 'zod/mini' :
|
|
40
|
+
importPath = mini ? 'zod/mini' : 'zod',
|
|
32
41
|
coercion = false,
|
|
33
42
|
inferred = false,
|
|
34
|
-
generators = [zodGenerator, operations ? operationsGenerator : undefined].filter(Boolean),
|
|
35
43
|
wrapOutput = undefined,
|
|
36
|
-
|
|
44
|
+
paramsCasing,
|
|
45
|
+
printer,
|
|
46
|
+
compatibilityPreset = 'default',
|
|
47
|
+
resolver: userResolver,
|
|
48
|
+
transformer: userTransformer,
|
|
49
|
+
generators: userGenerators = [],
|
|
37
50
|
} = options
|
|
38
51
|
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
const preset = getPreset({
|
|
53
|
+
preset: compatibilityPreset,
|
|
54
|
+
presets: presets,
|
|
55
|
+
resolver: userResolver,
|
|
56
|
+
transformer: userTransformer,
|
|
57
|
+
generators: userGenerators,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const generators = preset.generators ?? []
|
|
61
|
+
const mergedGenerator = mergeGenerators(generators)
|
|
62
|
+
|
|
63
|
+
let resolveNameWarning = false
|
|
64
|
+
let resolvePathWarning = false
|
|
41
65
|
|
|
42
66
|
return {
|
|
43
67
|
name: pluginZodName,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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,
|
|
68
|
+
version,
|
|
69
|
+
get resolver() {
|
|
70
|
+
return preset.resolver
|
|
66
71
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
get transformer() {
|
|
73
|
+
return preset.transformer
|
|
74
|
+
},
|
|
75
|
+
get options() {
|
|
76
|
+
return {
|
|
77
|
+
output,
|
|
78
|
+
exclude,
|
|
79
|
+
include,
|
|
80
|
+
override,
|
|
81
|
+
group: group
|
|
82
|
+
? ({
|
|
83
|
+
...group,
|
|
84
|
+
name: (ctx) => {
|
|
85
|
+
if (group.type === 'path') {
|
|
86
|
+
return `${ctx.group.split('/')[1]}`
|
|
87
|
+
}
|
|
88
|
+
return `${camelCase(ctx.group)}Controller`
|
|
89
|
+
},
|
|
90
|
+
} satisfies Group)
|
|
91
|
+
: undefined,
|
|
92
|
+
dateType,
|
|
93
|
+
typed,
|
|
94
|
+
importPath,
|
|
95
|
+
coercion,
|
|
96
|
+
operations,
|
|
97
|
+
inferred,
|
|
98
|
+
guidType,
|
|
99
|
+
mini,
|
|
100
|
+
wrapOutput,
|
|
101
|
+
paramsCasing,
|
|
102
|
+
printer,
|
|
78
103
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
)
|
|
104
|
+
},
|
|
105
|
+
resolvePath(baseName, pathMode, options) {
|
|
106
|
+
if (!resolvePathWarning) {
|
|
107
|
+
this.warn('Do not use resolvePath for pluginZod, use resolverZod.resolvePath instead')
|
|
108
|
+
resolvePathWarning = true
|
|
98
109
|
}
|
|
99
110
|
|
|
100
|
-
return
|
|
111
|
+
return this.plugin.resolver.resolvePath(
|
|
112
|
+
{ baseName, pathMode, tag: options?.group?.tag, path: options?.group?.path },
|
|
113
|
+
{ root: this.root, output, group: this.plugin.options.group },
|
|
114
|
+
)
|
|
101
115
|
},
|
|
102
116
|
resolveName(name, type) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
if (type === 'type') {
|
|
109
|
-
resolvedName = pascalCase(resolvedName)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (type) {
|
|
113
|
-
return transformers?.name?.(resolvedName, type) || resolvedName
|
|
117
|
+
if (!resolveNameWarning) {
|
|
118
|
+
this.warn('Do not use resolveName for pluginZod, use resolverZod.default instead')
|
|
119
|
+
resolveNameWarning = true
|
|
114
120
|
}
|
|
115
121
|
|
|
116
|
-
return
|
|
122
|
+
return this.plugin.resolver.default(name, type)
|
|
117
123
|
},
|
|
118
|
-
async
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{
|
|
130
|
-
name: 'ToZod',
|
|
131
|
-
value: toZodSource,
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
imports: [],
|
|
135
|
-
exports: [],
|
|
136
|
-
})
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const schemaGenerator = new SchemaGenerator(this.plugin.options, {
|
|
140
|
-
fabric: this.fabric,
|
|
141
|
-
oas,
|
|
142
|
-
pluginManager: this.pluginManager,
|
|
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
|
-
pluginManager: this.pluginManager,
|
|
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)
|
|
124
|
+
async schema(node, options) {
|
|
125
|
+
return mergedGenerator.schema?.call(this, node, options)
|
|
126
|
+
},
|
|
127
|
+
async operation(node, options) {
|
|
128
|
+
return mergedGenerator.operation?.call(this, node, options)
|
|
129
|
+
},
|
|
130
|
+
async operations(nodes, options) {
|
|
131
|
+
return mergedGenerator.operations?.call(this, nodes, options)
|
|
132
|
+
},
|
|
133
|
+
async buildStart() {
|
|
134
|
+
await this.openInStudio({ ast: true })
|
|
181
135
|
},
|
|
182
136
|
}
|
|
183
137
|
})
|