@kubb/plugin-msw 5.0.0-beta.3 → 5.0.0-beta.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/README.md +26 -5
- package/dist/{components-vO0FIb2i.js → components--7xwlHZU.js} +52 -59
- package/dist/components--7xwlHZU.js.map +1 -0
- package/dist/{components-CLQ77DVn.cjs → components-CHyTtokd.cjs} +55 -68
- package/dist/components-CHyTtokd.cjs.map +1 -0
- package/dist/components.cjs +1 -1
- package/dist/components.d.ts +5 -5
- package/dist/components.js +1 -1
- package/dist/{generators-BPJCs1x1.js → generators-BcGCyGBt.js} +61 -32
- package/dist/generators-BcGCyGBt.js.map +1 -0
- package/dist/{generators-CrmMwWE4.cjs → generators-DzDKzDFe.cjs} +60 -31
- package/dist/generators-DzDKzDFe.cjs.map +1 -0
- package/dist/generators.cjs +1 -1
- package/dist/generators.d.ts +13 -1
- package/dist/generators.js +1 -1
- package/dist/index.cjs +54 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +54 -9
- package/dist/index.js.map +1 -1
- package/dist/types-CLAiv8qc.d.ts +103 -0
- package/extension.yaml +680 -0
- package/package.json +11 -14
- package/src/components/Handlers.tsx +1 -1
- package/src/components/Mock.tsx +5 -4
- package/src/components/MockWithFaker.tsx +5 -4
- package/src/components/Response.tsx +1 -1
- package/src/generators/handlersGenerator.tsx +18 -12
- package/src/generators/mswGenerator.tsx +27 -17
- package/src/plugin.ts +32 -4
- package/src/resolvers/resolverMsw.ts +19 -3
- package/src/types.ts +35 -21
- package/src/utils.ts +16 -60
- package/dist/components-CLQ77DVn.cjs.map +0 -1
- package/dist/components-vO0FIb2i.js.map +0 -1
- package/dist/generators-BPJCs1x1.js.map +0 -1
- package/dist/generators-CrmMwWE4.cjs.map +0 -1
- package/dist/types-Dxu0KMQ4.d.ts +0 -89
package/package.json
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-msw",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
4
|
-
"description": "Mock Service Worker (MSW) handlers
|
|
3
|
+
"version": "5.0.0-beta.30",
|
|
4
|
+
"description": "Generate Mock Service Worker (MSW) request handlers from your OpenAPI specification. Intercept HTTP requests in the browser or Node.js for seamless frontend development and testing.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api-mocking",
|
|
7
|
-
"code-
|
|
7
|
+
"code-generation",
|
|
8
8
|
"codegen",
|
|
9
|
-
"development",
|
|
10
|
-
"interceptor",
|
|
11
9
|
"kubb",
|
|
12
10
|
"mock-service-worker",
|
|
13
11
|
"mocking",
|
|
14
12
|
"mocks",
|
|
15
13
|
"msw",
|
|
16
|
-
"oas",
|
|
17
14
|
"openapi",
|
|
18
|
-
"
|
|
15
|
+
"service-worker",
|
|
19
16
|
"swagger",
|
|
20
|
-
"testing",
|
|
21
17
|
"typescript"
|
|
22
18
|
],
|
|
23
19
|
"license": "MIT",
|
|
@@ -30,7 +26,7 @@
|
|
|
30
26
|
"files": [
|
|
31
27
|
"src",
|
|
32
28
|
"dist",
|
|
33
|
-
"
|
|
29
|
+
"extension.yaml",
|
|
34
30
|
"!/**/**.test.**",
|
|
35
31
|
"!/**/__tests__/**",
|
|
36
32
|
"!/**/__snapshots__/**"
|
|
@@ -76,16 +72,17 @@
|
|
|
76
72
|
"registry": "https://registry.npmjs.org/"
|
|
77
73
|
},
|
|
78
74
|
"dependencies": {
|
|
79
|
-
"@kubb/core": "5.0.0-beta.
|
|
80
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
81
|
-
"@kubb/plugin-faker": "5.0.0-beta.
|
|
82
|
-
"@kubb/plugin-ts": "5.0.0-beta.
|
|
75
|
+
"@kubb/core": "5.0.0-beta.29",
|
|
76
|
+
"@kubb/renderer-jsx": "5.0.0-beta.29",
|
|
77
|
+
"@kubb/plugin-faker": "5.0.0-beta.30",
|
|
78
|
+
"@kubb/plugin-ts": "5.0.0-beta.30"
|
|
83
79
|
},
|
|
84
80
|
"devDependencies": {
|
|
81
|
+
"@internals/shared": "0.0.0",
|
|
85
82
|
"@internals/utils": "0.0.0"
|
|
86
83
|
},
|
|
87
84
|
"peerDependencies": {
|
|
88
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
85
|
+
"@kubb/renderer-jsx": "5.0.0-beta.29"
|
|
89
86
|
},
|
|
90
87
|
"size-limit": [
|
|
91
88
|
{
|
package/src/components/Mock.tsx
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import { getPrimarySuccessResponse } from '@internals/shared'
|
|
1
2
|
import { URLPath } from '@internals/utils'
|
|
2
3
|
import { ast } from '@kubb/core'
|
|
3
4
|
import { functionPrinter } from '@kubb/plugin-ts'
|
|
4
5
|
import { File, Function } from '@kubb/renderer-jsx'
|
|
5
6
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
6
|
-
import { getContentType, getMswMethod, getMswUrl,
|
|
7
|
+
import { getContentType, getMswMethod, getMswUrl, hasResponseSchema } from '../utils.ts'
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
9
10
|
name: string
|
|
10
11
|
typeName: string
|
|
11
|
-
requestTypeName?: string
|
|
12
|
-
baseURL: string | undefined
|
|
12
|
+
requestTypeName?: string | null
|
|
13
|
+
baseURL: string | null | undefined
|
|
13
14
|
node: ast.OperationNode
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -22,7 +23,7 @@ export function Mock({ baseURL = '', name, typeName, requestTypeName, node }: Pr
|
|
|
22
23
|
const contentType = getContentType(successResponse)
|
|
23
24
|
const url = new URLPath(getMswUrl(node)).toURLPath()
|
|
24
25
|
|
|
25
|
-
const headers = [contentType ? `'Content-Type': '${contentType}'` :
|
|
26
|
+
const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean)
|
|
26
27
|
const responseHasSchema = hasResponseSchema(successResponse)
|
|
27
28
|
const dataType = responseHasSchema ? typeName : 'string | number | boolean | null | object'
|
|
28
29
|
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
import { getPrimarySuccessResponse } from '@internals/shared'
|
|
1
2
|
import { URLPath } from '@internals/utils'
|
|
2
3
|
import { ast } from '@kubb/core'
|
|
3
4
|
import { functionPrinter } from '@kubb/plugin-ts'
|
|
4
5
|
import { File, Function } from '@kubb/renderer-jsx'
|
|
5
6
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
6
|
-
import { getContentType, getMswMethod, getMswUrl
|
|
7
|
+
import { getContentType, getMswMethod, getMswUrl } from '../utils.ts'
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
9
10
|
name: string
|
|
10
11
|
typeName: string
|
|
11
|
-
requestTypeName?: string
|
|
12
|
+
requestTypeName?: string | null
|
|
12
13
|
fakerName: string
|
|
13
|
-
baseURL: string | undefined
|
|
14
|
+
baseURL: string | null | undefined
|
|
14
15
|
node: ast.OperationNode
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -23,7 +24,7 @@ export function MockWithFaker({ baseURL = '', name, fakerName, typeName, request
|
|
|
23
24
|
const contentType = getContentType(successResponse)
|
|
24
25
|
const url = new URLPath(getMswUrl(node)).toURLPath()
|
|
25
26
|
|
|
26
|
-
const headers = [contentType ? `'Content-Type': '${contentType}'` :
|
|
27
|
+
const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean)
|
|
27
28
|
|
|
28
29
|
const callbackType = requestTypeName
|
|
29
30
|
? `HttpResponseResolver<Record<string, string>, ${requestTypeName}, any>`
|
|
@@ -16,7 +16,7 @@ const declarationPrinter = functionPrinter({ mode: 'declaration' })
|
|
|
16
16
|
export function Response({ name, typeName, response }: Props): KubbReactNode {
|
|
17
17
|
const statusCode = Number(response.statusCode)
|
|
18
18
|
const contentType = getContentType(response)
|
|
19
|
-
const headers = [contentType ? `'Content-Type': '${contentType}'` :
|
|
19
|
+
const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean)
|
|
20
20
|
|
|
21
21
|
const params = declarationPrinter.print(
|
|
22
22
|
ast.createFunctionParameters({
|
|
@@ -1,40 +1,46 @@
|
|
|
1
1
|
import { defineGenerator } from '@kubb/core'
|
|
2
|
-
import { File,
|
|
2
|
+
import { File, jsxRendererSync } from '@kubb/renderer-jsx'
|
|
3
3
|
import { Handlers } from '../components/Handlers.tsx'
|
|
4
4
|
import type { PluginMsw } from '../types'
|
|
5
|
-
import { transformName } from '../utils.ts'
|
|
6
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Aggregate generator enabled by `pluginMsw({ handlers: true })`. Emits a
|
|
8
|
+
* `handlers.ts` file that re-exports every generated handler grouped by HTTP
|
|
9
|
+
* method, ready to spread into `setupServer(...handlers)` or
|
|
10
|
+
* `setupWorker(...handlers)`.
|
|
11
|
+
*/
|
|
7
12
|
export const handlersGenerator = defineGenerator<PluginMsw>({
|
|
8
13
|
name: 'plugin-msw',
|
|
9
|
-
renderer:
|
|
14
|
+
renderer: jsxRendererSync,
|
|
10
15
|
operations(nodes, ctx) {
|
|
11
|
-
const { resolver, config, root
|
|
12
|
-
const { output, group
|
|
16
|
+
const { resolver, config, root } = ctx
|
|
17
|
+
const { output, group } = ctx.options
|
|
13
18
|
|
|
14
|
-
const
|
|
19
|
+
const handlersName = resolver.resolveHandlersName()
|
|
20
|
+
const file = resolver.resolveFile({ name: resolver.resolvePathName(handlersName, 'file'), extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
15
21
|
|
|
16
22
|
const imports = nodes.map((node) => {
|
|
17
|
-
const operationName =
|
|
23
|
+
const operationName = resolver.resolveHandlerName(node)
|
|
18
24
|
const operationFile = resolver.resolveFile(
|
|
19
25
|
{ name: resolver.resolveName(node.operationId), extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
20
|
-
{ root, output, group },
|
|
26
|
+
{ root, output, group: group ?? undefined },
|
|
21
27
|
)
|
|
22
28
|
|
|
23
29
|
return <File.Import key={operationFile.path} name={[operationName]} root={file.path} path={operationFile.path} />
|
|
24
30
|
})
|
|
25
31
|
|
|
26
|
-
const handlers = nodes.map((node) => `${
|
|
32
|
+
const handlers = nodes.map((node) => `${resolver.resolveHandlerName(node)}()`)
|
|
27
33
|
|
|
28
34
|
return (
|
|
29
35
|
<File
|
|
30
36
|
baseName={file.baseName}
|
|
31
37
|
path={file.path}
|
|
32
38
|
meta={file.meta}
|
|
33
|
-
banner={resolver.resolveBanner(
|
|
34
|
-
footer={resolver.resolveFooter(
|
|
39
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } })}
|
|
40
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } })}
|
|
35
41
|
>
|
|
36
42
|
{imports}
|
|
37
|
-
<Handlers name={
|
|
43
|
+
<Handlers name={handlersName} handlers={handlers} />
|
|
38
44
|
</File>
|
|
39
45
|
)
|
|
40
46
|
},
|
|
@@ -1,34 +1,44 @@
|
|
|
1
|
+
import { getOperationSuccessResponses, resolveResponseTypes } from '@internals/shared'
|
|
1
2
|
import { defineGenerator } from '@kubb/core'
|
|
2
3
|
import { pluginFakerName } from '@kubb/plugin-faker'
|
|
3
4
|
import { pluginTsName } from '@kubb/plugin-ts'
|
|
4
|
-
import { File,
|
|
5
|
+
import { File, jsxRendererSync } from '@kubb/renderer-jsx'
|
|
5
6
|
import { Mock, MockWithFaker, Response } from '../components'
|
|
6
7
|
import type { PluginMsw } from '../types'
|
|
7
|
-
import {
|
|
8
|
+
import { resolveFakerMeta } from '../utils.ts'
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Built-in operation generator for `@kubb/plugin-msw`. Emits one MSW handler
|
|
12
|
+
* per OpenAPI operation. With `parser: 'faker'` the handler returns a value
|
|
13
|
+
* from `@kubb/plugin-faker`; with `parser: 'data'` it returns a typed empty
|
|
14
|
+
* payload for tests to fill in.
|
|
15
|
+
*/
|
|
9
16
|
export const mswGenerator = defineGenerator<PluginMsw>({
|
|
10
17
|
name: 'msw',
|
|
11
|
-
renderer:
|
|
18
|
+
renderer: jsxRendererSync,
|
|
12
19
|
operation(node, ctx) {
|
|
13
|
-
const { driver, resolver, config, root
|
|
14
|
-
const { output, parser, baseURL, group
|
|
20
|
+
const { driver, resolver, config, root } = ctx
|
|
21
|
+
const { output, parser, baseURL, group } = ctx.options
|
|
15
22
|
|
|
16
23
|
const fileName = resolver.resolveName(node.operationId)
|
|
17
24
|
const mock = {
|
|
18
|
-
name:
|
|
19
|
-
file: resolver.resolveFile(
|
|
25
|
+
name: resolver.resolveHandlerName(node),
|
|
26
|
+
file: resolver.resolveFile(
|
|
27
|
+
{ name: fileName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
28
|
+
{ root, output, group: group ?? undefined },
|
|
29
|
+
),
|
|
20
30
|
}
|
|
21
31
|
|
|
22
|
-
const fakerPlugin = parser === 'faker' ? driver.getPlugin(pluginFakerName) :
|
|
32
|
+
const fakerPlugin = parser === 'faker' ? driver.getPlugin(pluginFakerName) : null
|
|
23
33
|
const faker =
|
|
24
34
|
parser === 'faker' && fakerPlugin
|
|
25
35
|
? resolveFakerMeta(node, {
|
|
26
36
|
root,
|
|
27
37
|
fakerResolver: driver.getResolver(pluginFakerName),
|
|
28
38
|
fakerOutput: fakerPlugin.options?.output ?? output,
|
|
29
|
-
fakerGroup: fakerPlugin.options?.group,
|
|
39
|
+
fakerGroup: fakerPlugin.options?.group ?? null,
|
|
30
40
|
})
|
|
31
|
-
:
|
|
41
|
+
: null
|
|
32
42
|
|
|
33
43
|
const pluginTs = driver.getPlugin(pluginTsName)
|
|
34
44
|
if (!pluginTs) return null
|
|
@@ -37,24 +47,24 @@ export const mswGenerator = defineGenerator<PluginMsw>({
|
|
|
37
47
|
const type = {
|
|
38
48
|
file: tsResolver.resolveFile(
|
|
39
49
|
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
40
|
-
{ root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
|
|
50
|
+
{ root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
|
|
41
51
|
),
|
|
42
52
|
responseName: tsResolver.resolveResponseName(node),
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
const types =
|
|
46
|
-
const successResponses =
|
|
47
|
-
const hasSuccessSchema = successResponses.some((response) => !!response.schema)
|
|
55
|
+
const types = resolveResponseTypes(node, tsResolver)
|
|
56
|
+
const successResponses = getOperationSuccessResponses(node)
|
|
57
|
+
const hasSuccessSchema = successResponses.some((response) => !!response.content?.[0]?.schema)
|
|
48
58
|
|
|
49
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) :
|
|
59
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
|
|
50
60
|
|
|
51
61
|
return (
|
|
52
62
|
<File
|
|
53
63
|
baseName={mock.file.baseName}
|
|
54
64
|
path={mock.file.path}
|
|
55
65
|
meta={mock.file.meta}
|
|
56
|
-
banner={resolver.resolveBanner(
|
|
57
|
-
footer={resolver.resolveFooter(
|
|
66
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: mock.file.path, baseName: mock.file.baseName } })}
|
|
67
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: mock.file.path, baseName: mock.file.baseName } })}
|
|
58
68
|
>
|
|
59
69
|
<File.Import name={['http']} path="msw" />
|
|
60
70
|
<File.Import name={['HttpResponseResolver']} isTypeOnly path="msw" />
|
package/src/plugin.ts
CHANGED
|
@@ -6,8 +6,38 @@ import { handlersGenerator, mswGenerator } from './generators'
|
|
|
6
6
|
import { resolverMsw } from './resolvers/resolverMsw.ts'
|
|
7
7
|
import type { PluginMsw } from './types.ts'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Canonical plugin name for `@kubb/plugin-msw`. Used for driver lookups and
|
|
11
|
+
* cross-plugin dependency references.
|
|
12
|
+
*/
|
|
9
13
|
export const pluginMswName = 'plugin-msw' satisfies PluginMsw['name']
|
|
10
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Generates MSW request handlers from an OpenAPI spec. Drop them into your
|
|
17
|
+
* test setup or service worker to mock the API end-to-end. Request path,
|
|
18
|
+
* method, status, and response body all stay in sync with the spec. Combine
|
|
19
|
+
* with `@kubb/plugin-faker` (via `parser: 'faker'`) to seed handlers with
|
|
20
|
+
* realistic data.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { defineConfig } from 'kubb'
|
|
25
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
26
|
+
* import { pluginMsw } from '@kubb/plugin-msw'
|
|
27
|
+
*
|
|
28
|
+
* export default defineConfig({
|
|
29
|
+
* input: { path: './petStore.yaml' },
|
|
30
|
+
* output: { path: './src/gen' },
|
|
31
|
+
* plugins: [
|
|
32
|
+
* pluginTs(),
|
|
33
|
+
* pluginMsw({
|
|
34
|
+
* output: { path: './handlers' },
|
|
35
|
+
* handlers: true,
|
|
36
|
+
* }),
|
|
37
|
+
* ],
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
11
41
|
export const pluginMsw = definePlugin<PluginMsw>((options) => {
|
|
12
42
|
const {
|
|
13
43
|
output = { path: 'handlers', barrelType: 'named' },
|
|
@@ -15,7 +45,6 @@ export const pluginMsw = definePlugin<PluginMsw>((options) => {
|
|
|
15
45
|
exclude = [],
|
|
16
46
|
include,
|
|
17
47
|
override = [],
|
|
18
|
-
transformers = {},
|
|
19
48
|
handlers = false,
|
|
20
49
|
parser = 'data',
|
|
21
50
|
baseURL,
|
|
@@ -36,12 +65,12 @@ export const pluginMsw = definePlugin<PluginMsw>((options) => {
|
|
|
36
65
|
return `${camelCase(ctx.group)}Controller`
|
|
37
66
|
},
|
|
38
67
|
} satisfies Group)
|
|
39
|
-
:
|
|
68
|
+
: null
|
|
40
69
|
|
|
41
70
|
return {
|
|
42
71
|
name: pluginMswName,
|
|
43
72
|
options,
|
|
44
|
-
dependencies: [pluginTsName, parser === 'faker' ? pluginFakerName :
|
|
73
|
+
dependencies: [pluginTsName, parser === 'faker' ? pluginFakerName : null].filter((dependency): dependency is string => Boolean(dependency)),
|
|
45
74
|
hooks: {
|
|
46
75
|
'kubb:plugin:setup'(ctx) {
|
|
47
76
|
const resolver = userResolver ? { ...resolverMsw, ...userResolver } : resolverMsw
|
|
@@ -55,7 +84,6 @@ export const pluginMsw = definePlugin<PluginMsw>((options) => {
|
|
|
55
84
|
include,
|
|
56
85
|
override,
|
|
57
86
|
handlers,
|
|
58
|
-
transformers,
|
|
59
87
|
resolver,
|
|
60
88
|
})
|
|
61
89
|
ctx.setResolver(resolver)
|
|
@@ -3,11 +3,18 @@ import { defineResolver } from '@kubb/core'
|
|
|
3
3
|
import type { PluginMsw } from '../types.ts'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Default resolver used by `@kubb/plugin-msw`. Decides the names and file
|
|
7
|
+
* paths for every generated MSW handler. Function names get a `Handler`
|
|
8
|
+
* suffix; the aggregate export is always `handlers`.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
10
|
+
* @example Resolve a handler name
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { resolverMsw } from '@kubb/plugin-msw'
|
|
13
|
+
*
|
|
14
|
+
* resolverMsw.resolveName('addPet') // 'addPetHandler'
|
|
15
|
+
* ```
|
|
9
16
|
*/
|
|
10
|
-
export const resolverMsw = defineResolver<PluginMsw>((
|
|
17
|
+
export const resolverMsw = defineResolver<PluginMsw>(() => ({
|
|
11
18
|
name: 'default',
|
|
12
19
|
pluginName: 'plugin-msw',
|
|
13
20
|
default(name, type) {
|
|
@@ -16,4 +23,13 @@ export const resolverMsw = defineResolver<PluginMsw>((_ctx) => ({
|
|
|
16
23
|
resolveName(name) {
|
|
17
24
|
return camelCase(name, { suffix: 'handler' })
|
|
18
25
|
},
|
|
26
|
+
resolvePathName(name, type) {
|
|
27
|
+
return this.default(name, type)
|
|
28
|
+
},
|
|
29
|
+
resolveHandlerName(node) {
|
|
30
|
+
return this.resolveName(node.operationId)
|
|
31
|
+
},
|
|
32
|
+
resolveHandlersName() {
|
|
33
|
+
return 'handlers'
|
|
34
|
+
},
|
|
19
35
|
}))
|
package/src/types.ts
CHANGED
|
@@ -1,79 +1,93 @@
|
|
|
1
|
-
import type { ast, Exclude, Generator, Group, Include, Output, Override, PluginFactoryOptions,
|
|
1
|
+
import type { ast, Exclude, Generator, Group, Include, Output, Override, PluginFactoryOptions, Resolver } from '@kubb/core'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Resolver for MSW that provides naming methods for handler functions.
|
|
5
5
|
*/
|
|
6
6
|
export type ResolverMsw = Resolver & {
|
|
7
7
|
/**
|
|
8
|
-
* Resolves the handler function name for an operation.
|
|
8
|
+
* Resolves the base handler function name for an operation.
|
|
9
9
|
*/
|
|
10
10
|
resolveName(this: ResolverMsw, name: string): string
|
|
11
|
+
/**
|
|
12
|
+
* Resolves the output file name for an MSW handler module.
|
|
13
|
+
*/
|
|
14
|
+
resolvePathName(this: ResolverMsw, name: string, type?: 'file' | 'function' | 'type' | 'const'): string
|
|
15
|
+
/**
|
|
16
|
+
* Resolves the handler function name for an operation.
|
|
17
|
+
*/
|
|
18
|
+
resolveHandlerName(this: ResolverMsw, node: ast.OperationNode): string
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the exported handlers collection name.
|
|
21
|
+
*/
|
|
22
|
+
resolveHandlersName(this: ResolverMsw): string
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
export type Options = {
|
|
14
26
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
27
|
+
* Where the generated MSW handlers are written and how they are exported.
|
|
28
|
+
*
|
|
29
|
+
* @default { path: 'handlers', barrel: { type: 'named' } }
|
|
17
30
|
*/
|
|
18
31
|
output?: Output
|
|
32
|
+
/**
|
|
33
|
+
* Base URL prepended to every handler's request URL. When omitted, falls back
|
|
34
|
+
* to the adapter's server URL (typically `servers[0].url`).
|
|
35
|
+
*/
|
|
19
36
|
baseURL?: string
|
|
20
37
|
/**
|
|
21
|
-
*
|
|
38
|
+
* Split generated files into subfolders based on the operation's tag.
|
|
22
39
|
*/
|
|
23
40
|
group?: Group
|
|
24
41
|
/**
|
|
25
|
-
*
|
|
42
|
+
* Skip operations matching at least one entry in the list.
|
|
26
43
|
*/
|
|
27
44
|
exclude?: Array<Exclude>
|
|
28
45
|
/**
|
|
29
|
-
*
|
|
46
|
+
* Restrict generation to operations matching at least one entry in the list.
|
|
30
47
|
*/
|
|
31
48
|
include?: Array<Include>
|
|
32
49
|
/**
|
|
33
|
-
*
|
|
50
|
+
* Apply a different options object to operations matching a pattern.
|
|
34
51
|
*/
|
|
35
52
|
override?: Array<Override<ResolvedOptions>>
|
|
36
|
-
transformers?: {
|
|
37
|
-
/**
|
|
38
|
-
* Override the default naming for handlers.
|
|
39
|
-
*/
|
|
40
|
-
name?: (name: ResolveNameParams['name'], type?: ResolveNameParams['type']) => string
|
|
41
|
-
}
|
|
42
53
|
/**
|
|
43
|
-
* Override
|
|
54
|
+
* Override how handler names and file paths are built.
|
|
44
55
|
*/
|
|
45
56
|
resolver?: Partial<ResolverMsw> & ThisType<ResolverMsw>
|
|
46
57
|
/**
|
|
47
|
-
* AST visitor to
|
|
58
|
+
* AST visitor applied to operation nodes before printing.
|
|
48
59
|
*/
|
|
49
60
|
transformer?: ast.Visitor
|
|
50
61
|
/**
|
|
51
|
-
*
|
|
62
|
+
* Emit a `handlers.ts` file that re-exports every handler grouped by HTTP method.
|
|
63
|
+
* Drop the file into `setupServer(...handlers)` or `setupWorker(...handlers)`.
|
|
64
|
+
*
|
|
52
65
|
* @default false
|
|
53
66
|
*/
|
|
54
67
|
handlers?: boolean
|
|
55
68
|
/**
|
|
56
|
-
*
|
|
69
|
+
* Source of the response body returned by each generated handler.
|
|
70
|
+
* - `'data'` — typed empty/example payload, ready for you to fill in from tests.
|
|
71
|
+
* - `'faker'` — value produced by `@kubb/plugin-faker`.
|
|
57
72
|
*
|
|
58
73
|
* @default 'data'
|
|
59
74
|
*/
|
|
60
75
|
parser?: 'data' | 'faker'
|
|
61
76
|
/**
|
|
62
|
-
*
|
|
77
|
+
* Custom generators that run alongside the built-in MSW generators.
|
|
63
78
|
*/
|
|
64
79
|
generators?: Array<Generator<PluginMsw>>
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
type ResolvedOptions = {
|
|
68
83
|
output: Output
|
|
69
|
-
group: Group |
|
|
84
|
+
group: Group | null
|
|
70
85
|
exclude: NonNullable<Options['exclude']>
|
|
71
86
|
include: Options['include']
|
|
72
87
|
override: NonNullable<Options['override']>
|
|
73
88
|
parser: NonNullable<Options['parser']>
|
|
74
89
|
baseURL: Options['baseURL'] | undefined
|
|
75
90
|
handlers: boolean
|
|
76
|
-
transformers: NonNullable<Options['transformers']>
|
|
77
91
|
resolver: ResolverMsw
|
|
78
92
|
}
|
|
79
93
|
|
package/src/utils.ts
CHANGED
|
@@ -1,76 +1,29 @@
|
|
|
1
1
|
import type { ast } from '@kubb/core'
|
|
2
2
|
import type { ResolverFaker } from '@kubb/plugin-faker'
|
|
3
|
-
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
4
3
|
import type { PluginMsw } from './types.ts'
|
|
5
4
|
|
|
6
|
-
/**
|
|
7
|
-
* Applies a name transformer function to a name if configured, otherwise returns it unchanged.
|
|
8
|
-
*/
|
|
9
|
-
export function transformName(name: string, type: 'function' | 'type' | 'file' | 'const', transformers?: PluginMsw['resolvedOptions']['transformers']): string {
|
|
10
|
-
return transformers?.name?.(name, type) || name
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Filters responses to only those with 2xx status codes.
|
|
15
|
-
*/
|
|
16
|
-
export function getSuccessResponses(node: ast.OperationNode): ast.ResponseNode[] {
|
|
17
|
-
return node.responses.filter((response) => {
|
|
18
|
-
const code = Number.parseInt(response.statusCode, 10)
|
|
19
|
-
return !Number.isNaN(code) && code >= 200 && code < 300
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Returns the first 2xx response for an operation, if any.
|
|
25
|
-
*/
|
|
26
|
-
export function getPrimarySuccessResponse(node: ast.OperationNode): ast.ResponseNode | undefined {
|
|
27
|
-
return getSuccessResponses(node)[0]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
5
|
/**
|
|
31
6
|
* Gets the content type from a response, defaulting to 'application/json' if a schema exists.
|
|
32
7
|
*/
|
|
33
|
-
export function getContentType(response: ast.ResponseNode | undefined): string |
|
|
34
|
-
|
|
8
|
+
export function getContentType(response: ast.ResponseNode | null | undefined): string | null {
|
|
9
|
+
if (!hasResponseSchema(response)) {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return getResponseContentType(response) ?? 'application/json'
|
|
35
14
|
}
|
|
36
15
|
|
|
37
16
|
/**
|
|
38
17
|
* Determines if a response has a schema that is not void or any.
|
|
39
18
|
*/
|
|
40
|
-
export function hasResponseSchema(response: ast.ResponseNode | undefined): boolean {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
function getResponseContentType(response: ast.ResponseNode | undefined): string | undefined {
|
|
45
|
-
const contentType = response as unknown as { mediaType?: string | null; contentType?: string | null } | undefined
|
|
46
|
-
const value = contentType?.mediaType ?? contentType?.contentType
|
|
47
|
-
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
19
|
+
export function hasResponseSchema(response: ast.ResponseNode | null | undefined): boolean {
|
|
20
|
+
const schema = response?.content?.[0]?.schema
|
|
21
|
+
return !!schema && schema.type !== 'void' && schema.type !== 'any'
|
|
48
22
|
}
|
|
49
23
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export function getResponseTypes(node: ast.OperationNode, tsResolver: ResolverTs): Array<[statusCode: number | 'default', typeName: string]> {
|
|
54
|
-
const types: Array<[number | 'default', string]> = []
|
|
55
|
-
|
|
56
|
-
for (const response of node.responses) {
|
|
57
|
-
if (response.statusCode === 'default') {
|
|
58
|
-
types.push(['default', tsResolver.resolveResponseName(node)])
|
|
59
|
-
continue
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const code = Number.parseInt(response.statusCode, 10)
|
|
63
|
-
if (Number.isNaN(code)) continue
|
|
64
|
-
|
|
65
|
-
if (code >= 200 && code < 300) {
|
|
66
|
-
types.push([code, tsResolver.resolveResponseName(node)])
|
|
67
|
-
continue
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
types.push([code, tsResolver.resolveResponseStatusName(node, response.statusCode)])
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return types
|
|
24
|
+
function getResponseContentType(response: ast.ResponseNode | null | undefined): string | null {
|
|
25
|
+
const value = response?.content?.[0]?.contentType
|
|
26
|
+
return typeof value === 'string' && value.length > 0 ? value : null
|
|
74
27
|
}
|
|
75
28
|
|
|
76
29
|
/**
|
|
@@ -104,6 +57,9 @@ export function resolveFakerMeta(
|
|
|
104
57
|
|
|
105
58
|
return {
|
|
106
59
|
name: fakerResolver.resolveResponseName(node),
|
|
107
|
-
file: fakerResolver.resolveFile(
|
|
60
|
+
file: fakerResolver.resolveFile(
|
|
61
|
+
{ name: node.operationId, extname: '.ts', tag, path: node.path },
|
|
62
|
+
{ root, output: fakerOutput, group: fakerGroup ?? undefined },
|
|
63
|
+
),
|
|
108
64
|
}
|
|
109
65
|
}
|