@kubb/plugin-ts 5.0.0-beta.30 → 5.0.0-beta.33
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 +124 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +124 -61
- package/dist/index.js.map +1 -1
- package/extension.yaml +2 -0
- package/package.json +6 -6
- package/src/generators/typeGenerator.tsx +58 -69
- package/src/plugin.ts +3 -13
- package/src/utils.ts +1 -1
package/extension.yaml
CHANGED
|
@@ -707,7 +707,9 @@ options:
|
|
|
707
707
|
| --- | --- |
|
|
708
708
|
| `@kubb/plugin-ts` | `resolverTs` |
|
|
709
709
|
| `@kubb/plugin-zod` | `resolverZod` |
|
|
710
|
+
| `@kubb/plugin-faker` | `resolverFaker` |
|
|
710
711
|
| `@kubb/plugin-cypress` | `resolverCypress` |
|
|
712
|
+
| `@kubb/plugin-msw` | `resolverMsw` |
|
|
711
713
|
| `@kubb/plugin-mcp` | `resolverMcp` |
|
|
712
714
|
| `@kubb/plugin-client` | `resolverClient` |
|
|
713
715
|
codeBlock:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-ts",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.33",
|
|
4
4
|
"description": "Generate TypeScript types, interfaces, and enums from your OpenAPI specification. The foundational plugin that powers type safety across the entire Kubb ecosystem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"code-generation",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"registry": "https://registry.npmjs.org/"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@kubb/core": "5.0.0-beta.
|
|
50
|
-
"@kubb/parser-ts": "5.0.0-beta.
|
|
51
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
52
|
-
"remeda": "^2.
|
|
49
|
+
"@kubb/core": "5.0.0-beta.33",
|
|
50
|
+
"@kubb/parser-ts": "5.0.0-beta.33",
|
|
51
|
+
"@kubb/renderer-jsx": "5.0.0-beta.33",
|
|
52
|
+
"remeda": "^2.36.0",
|
|
53
53
|
"typescript": "^6.0.3"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@internals/utils": "0.0.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
60
|
+
"@kubb/renderer-jsx": "5.0.0-beta.33"
|
|
61
61
|
},
|
|
62
62
|
"size-limit": [
|
|
63
63
|
{
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveContentTypeVariants } from '@internals/shared'
|
|
1
2
|
import { ast, defineGenerator } from '@kubb/core'
|
|
2
3
|
import { File, jsxRendererSync } from '@kubb/renderer-jsx'
|
|
3
4
|
import { Type } from '../components/Type.tsx'
|
|
@@ -6,24 +7,6 @@ import { printerTs } from '../printers/printerTs.ts'
|
|
|
6
7
|
import type { PluginTs } from '../types'
|
|
7
8
|
import { buildData, buildResponses, buildResponseUnion } from '../utils.ts'
|
|
8
9
|
|
|
9
|
-
function getContentTypeSuffix(contentType: string): string {
|
|
10
|
-
const baseType = contentType.split(';')[0]!.trim()
|
|
11
|
-
if (baseType === 'application/json') return 'Json'
|
|
12
|
-
if (baseType === 'multipart/form-data') return 'FormData'
|
|
13
|
-
if (baseType === 'application/x-www-form-urlencoded') return 'FormUrlEncoded'
|
|
14
|
-
const subtype = baseType.split('/').pop() ?? baseType
|
|
15
|
-
const parts = subtype.split(/[^a-zA-Z0-9]+/).filter(Boolean)
|
|
16
|
-
if (parts.length === 0) return 'Unknown'
|
|
17
|
-
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function getPerContentTypeName(dataName: string, suffix: string): string {
|
|
21
|
-
if (dataName.endsWith('Data')) {
|
|
22
|
-
return suffix.endsWith('Data') ? dataName.slice(0, -4) + suffix : `${dataName.slice(0, -4)}${suffix}Data`
|
|
23
|
-
}
|
|
24
|
-
return dataName + suffix
|
|
25
|
-
}
|
|
26
|
-
|
|
27
10
|
/**
|
|
28
11
|
* Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
|
|
29
12
|
* schema in the spec plus per-operation request, response, and parameter
|
|
@@ -168,6 +151,34 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
168
151
|
)
|
|
169
152
|
}
|
|
170
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Emits an individual type per content type plus a union alias under `baseName`.
|
|
156
|
+
* Shared by the request body and multi-content-type responses.
|
|
157
|
+
*/
|
|
158
|
+
function buildContentTypeVariants(
|
|
159
|
+
entries: Array<{ contentType: string; schema?: ast.SchemaNode | null; keysToOmit?: Array<string> | null }>,
|
|
160
|
+
baseName: string,
|
|
161
|
+
decorate?: (schema: ast.SchemaNode) => ast.SchemaNode,
|
|
162
|
+
) {
|
|
163
|
+
const variants = resolveContentTypeVariants(entries, baseName)
|
|
164
|
+
const unionSchema = ast.createSchema({
|
|
165
|
+
type: 'union',
|
|
166
|
+
members: variants.map((variant) => ast.createSchema({ type: 'ref', name: variant.name })),
|
|
167
|
+
})
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
{variants.map((variant) =>
|
|
171
|
+
renderSchemaType({
|
|
172
|
+
schema: decorate ? decorate(variant.schema) : variant.schema,
|
|
173
|
+
name: variant.name,
|
|
174
|
+
keysToOmit: variant.keysToOmit,
|
|
175
|
+
}),
|
|
176
|
+
)}
|
|
177
|
+
{renderSchemaType({ schema: unionSchema, name: baseName })}
|
|
178
|
+
</>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
171
182
|
const paramTypes = params.map((param) =>
|
|
172
183
|
renderSchemaType({
|
|
173
184
|
schema: param.schema,
|
|
@@ -192,52 +203,27 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
192
203
|
})
|
|
193
204
|
}
|
|
194
205
|
// Multiple content types — generate individual types + union alias
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
.map((entry) => {
|
|
200
|
-
const baseSuffix = getContentTypeSuffix(entry.contentType)
|
|
201
|
-
let individualName = getPerContentTypeName(dataName, baseSuffix)
|
|
202
|
-
let counter = 2
|
|
203
|
-
while (usedNames.has(individualName)) {
|
|
204
|
-
individualName = getPerContentTypeName(dataName, `${baseSuffix}${counter++}`)
|
|
205
|
-
}
|
|
206
|
-
usedNames.add(individualName)
|
|
207
|
-
return {
|
|
208
|
-
name: individualName,
|
|
209
|
-
rendered: renderSchemaType({
|
|
210
|
-
schema: {
|
|
211
|
-
...entry.schema!,
|
|
212
|
-
description: node.requestBody!.description ?? entry.schema!.description,
|
|
213
|
-
},
|
|
214
|
-
name: individualName,
|
|
215
|
-
keysToOmit: entry.keysToOmit,
|
|
216
|
-
}),
|
|
217
|
-
}
|
|
218
|
-
})
|
|
219
|
-
const unionSchema = ast.createSchema({
|
|
220
|
-
type: 'union',
|
|
221
|
-
members: individualItems.map((item) => ast.createSchema({ type: 'ref', name: item.name })),
|
|
222
|
-
})
|
|
223
|
-
const unionType = renderSchemaType({ schema: unionSchema, name: dataName })
|
|
224
|
-
return (
|
|
225
|
-
<>
|
|
226
|
-
{individualItems.map((item) => item.rendered)}
|
|
227
|
-
{unionType}
|
|
228
|
-
</>
|
|
229
|
-
)
|
|
206
|
+
return buildContentTypeVariants(requestBodyContent, resolver.resolveDataName(node), (schema) => ({
|
|
207
|
+
...schema,
|
|
208
|
+
description: node.requestBody!.description ?? schema.description,
|
|
209
|
+
}))
|
|
230
210
|
}
|
|
231
211
|
|
|
232
212
|
const requestType = buildRequestType()
|
|
233
213
|
|
|
234
|
-
const responseTypes = node.responses.map((res) =>
|
|
235
|
-
|
|
236
|
-
|
|
214
|
+
const responseTypes = node.responses.map((res) => {
|
|
215
|
+
const variants = (res.content ?? []).filter((entry) => entry.schema)
|
|
216
|
+
// Multiple content types for a single status code — generate a union of the variants.
|
|
217
|
+
if (variants.length > 1) {
|
|
218
|
+
return buildContentTypeVariants(variants, resolver.resolveResponseStatusName(node, res.statusCode))
|
|
219
|
+
}
|
|
220
|
+
const primary = variants[0] ?? res.content?.[0]
|
|
221
|
+
return renderSchemaType({
|
|
222
|
+
schema: primary?.schema ?? null,
|
|
237
223
|
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
238
|
-
keysToOmit:
|
|
239
|
-
})
|
|
240
|
-
)
|
|
224
|
+
keysToOmit: primary?.keysToOmit,
|
|
225
|
+
})
|
|
226
|
+
})
|
|
241
227
|
|
|
242
228
|
const dataType = renderSchemaType({
|
|
243
229
|
schema: buildData({ ...node, parameters: params }, { resolver }),
|
|
@@ -250,23 +236,26 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
250
236
|
})
|
|
251
237
|
|
|
252
238
|
function buildResponseType() {
|
|
253
|
-
|
|
239
|
+
const hasSchema = (res: ast.ResponseNode) => (res.content ?? []).some((entry) => entry.schema)
|
|
240
|
+
if (!node.responses.some(hasSchema)) {
|
|
254
241
|
return null
|
|
255
242
|
}
|
|
256
243
|
|
|
257
244
|
const responseName = resolver.resolveResponseName(node)
|
|
258
245
|
|
|
259
|
-
const responsesWithSchema = node.responses.filter(
|
|
246
|
+
const responsesWithSchema = node.responses.filter(hasSchema)
|
|
260
247
|
const importedNames = new Set(
|
|
261
248
|
responsesWithSchema.flatMap((res) =>
|
|
262
|
-
res.content
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
249
|
+
(res.content ?? []).flatMap((entry) =>
|
|
250
|
+
entry.schema
|
|
251
|
+
? adapter
|
|
252
|
+
.getImports(entry.schema, (schemaName) => ({
|
|
253
|
+
name: resolveImportName(schemaName),
|
|
254
|
+
path: '',
|
|
255
|
+
}))
|
|
256
|
+
.flatMap((imp) => (Array.isArray(imp.name) ? imp.name : [imp.name]))
|
|
257
|
+
: [],
|
|
258
|
+
),
|
|
270
259
|
),
|
|
271
260
|
)
|
|
272
261
|
|
package/src/plugin.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { definePlugin
|
|
1
|
+
import { createGroupConfig } from '@internals/shared'
|
|
2
|
+
import { definePlugin } from '@kubb/core'
|
|
3
3
|
import { typeGenerator } from './generators/typeGenerator.tsx'
|
|
4
4
|
import { resolverTs } from './resolvers/resolverTs.ts'
|
|
5
5
|
import type { PluginTs } from './types.ts'
|
|
@@ -54,17 +54,7 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
|
|
|
54
54
|
generators: userGenerators = [],
|
|
55
55
|
} = options
|
|
56
56
|
|
|
57
|
-
const groupConfig = group
|
|
58
|
-
? ({
|
|
59
|
-
...group,
|
|
60
|
-
name: (ctx) => {
|
|
61
|
-
if (group.type === 'path') {
|
|
62
|
-
return `${ctx.group.split('/')[1]}`
|
|
63
|
-
}
|
|
64
|
-
return `${camelCase(ctx.group)}Controller`
|
|
65
|
-
},
|
|
66
|
-
} satisfies Group)
|
|
67
|
-
: null
|
|
57
|
+
const groupConfig = createGroupConfig(group, { suffix: 'Controller' })
|
|
68
58
|
|
|
69
59
|
return {
|
|
70
60
|
name: pluginTsName,
|
package/src/utils.ts
CHANGED
|
@@ -127,7 +127,7 @@ export function buildResponses(node: ast.OperationNode, { resolver }: BuildOpera
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
export function buildResponseUnion(node: ast.OperationNode, { resolver }: BuildOperationSchemaOptions): ast.SchemaNode | null {
|
|
130
|
-
const responsesWithSchema = node.responses.filter((res) => res.content?.
|
|
130
|
+
const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema))
|
|
131
131
|
|
|
132
132
|
if (responsesWithSchema.length === 0) {
|
|
133
133
|
return null
|