@kubb/plugin-msw 5.0.0-beta.56 → 5.0.0-beta.73

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.
@@ -1,70 +0,0 @@
1
- import { getPrimarySuccessResponse } from '@internals/shared'
2
- import { Url } from '@internals/utils'
3
- import { ast } from '@kubb/core'
4
- import { functionPrinter } from '@kubb/plugin-ts'
5
- import { File, Function } from '@kubb/renderer-jsx'
6
- import type { KubbReactNode } from '@kubb/renderer-jsx/types'
7
- import { getContentType, getMswMethod, getMswUrl } from '../utils.ts'
8
-
9
- type Props = {
10
- name: string
11
- typeName: string
12
- requestTypeName?: string | null
13
- fakerName: string
14
- baseURL: string | null | undefined
15
- node: ast.OperationNode
16
- }
17
-
18
- const declarationPrinter = functionPrinter({ mode: 'declaration' })
19
-
20
- export function MockWithFaker({ baseURL = '', name, fakerName, typeName, requestTypeName, node }: Props): KubbReactNode {
21
- const method = getMswMethod(node)
22
- const successResponse = getPrimarySuccessResponse(node)
23
- const statusCode = successResponse ? Number(successResponse.statusCode) : 200
24
- const contentType = getContentType(successResponse)
25
- const url = Url.toPath(getMswUrl(node))
26
-
27
- const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean)
28
-
29
- const callbackType = requestTypeName
30
- ? `HttpResponseResolver<Record<string, string>, ${requestTypeName}, any>`
31
- : `((info: Parameters<Parameters<typeof http.${method}>[1]>[0]) => Response | Promise<Response>)`
32
-
33
- const params = declarationPrinter.print(
34
- ast.createFunctionParameters({
35
- params: [
36
- ast.createFunctionParameter({
37
- name: 'data',
38
- type: ast.createParamsType({
39
- variant: 'reference',
40
- name: `${typeName} | ${callbackType}`,
41
- }),
42
- optional: true,
43
- }),
44
- ],
45
- }),
46
- )
47
-
48
- const httpCall = requestTypeName ? `http.${method}<Record<string, string>, ${requestTypeName}, any>` : `http.${method}`
49
-
50
- return (
51
- <File.Source name={name} isIndexable isExportable>
52
- <Function name={name} export params={params ?? ''}>
53
- {`return ${httpCall}('${baseURL}${url.replace(/([^/]):/g, '$1\\\\:')}', function handler(info) {
54
- if(typeof data === 'function') return data(info)
55
-
56
- return new Response(JSON.stringify(data || ${fakerName}(data)), {
57
- status: ${statusCode},
58
- ${
59
- headers.length
60
- ? ` headers: {
61
- ${headers.join(', \n')}
62
- },`
63
- : ''
64
- }
65
- })
66
- })`}
67
- </Function>
68
- </File.Source>
69
- )
70
- }
@@ -1,52 +0,0 @@
1
- import { ast } from '@kubb/core'
2
- import { functionPrinter } from '@kubb/plugin-ts'
3
- import { File, Function } from '@kubb/renderer-jsx'
4
- import type { KubbReactNode } from '@kubb/renderer-jsx/types'
5
- import { getContentType, hasResponseSchema } from '../utils.ts'
6
-
7
- type Props = {
8
- typeName: string
9
- name: string
10
- response: ast.ResponseNode
11
- key?: string | number | null
12
- }
13
-
14
- const declarationPrinter = functionPrinter({ mode: 'declaration' })
15
-
16
- export function Response({ name, typeName, response }: Props): KubbReactNode {
17
- const statusCode = Number(response.statusCode)
18
- const contentType = getContentType(response)
19
- const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean)
20
-
21
- const params = declarationPrinter.print(
22
- ast.createFunctionParameters({
23
- params: [
24
- ast.createFunctionParameter({
25
- name: 'data',
26
- type: ast.createParamsType({ variant: 'reference', name: typeName }),
27
- optional: !hasResponseSchema(response),
28
- }),
29
- ],
30
- }),
31
- )
32
-
33
- const responseName = `${name}Response${statusCode}`
34
-
35
- return (
36
- <File.Source name={responseName} isIndexable isExportable>
37
- <Function name={responseName} export params={params ?? ''}>
38
- {`
39
- return new Response(JSON.stringify(data), {
40
- status: ${statusCode},
41
- ${
42
- headers.length
43
- ? ` headers: {
44
- ${headers.join(', \n')}
45
- },`
46
- : ''
47
- }
48
- })`}
49
- </Function>
50
- </File.Source>
51
- )
52
- }
@@ -1,3 +0,0 @@
1
- export { Mock } from './Mock.tsx'
2
- export { MockWithFaker } from './MockWithFaker.tsx'
3
- export { Response } from './Response.tsx'
@@ -1,49 +0,0 @@
1
- import { ast, defineGenerator } from '@kubb/core'
2
- import type { PluginMsw } from '../types'
3
-
4
- /**
5
- * Aggregate generator enabled by `pluginMsw({ handlers: true })`. Emits a
6
- * `handlers.ts` file that re-exports every generated handler grouped by HTTP
7
- * method, ready to spread into `setupServer(...handlers)` or
8
- * `setupWorker(...handlers)`.
9
- */
10
- export const handlersGenerator = defineGenerator<PluginMsw>({
11
- name: 'plugin-msw',
12
- operations(nodes, ctx) {
13
- const { resolver, config, root } = ctx
14
- const { output, group } = ctx.options
15
-
16
- const handlersName = resolver.resolveHandlersName()
17
- const file = resolver.resolveFile({ name: resolver.resolvePathName(handlersName, 'file'), extname: '.ts' }, { root, output, group: group ?? undefined })
18
-
19
- const imports = nodes.map((node) => {
20
- const operationName = resolver.resolveHandlerName(node)
21
- const operationFile = resolver.resolveFile(
22
- { name: resolver.resolveName(node.operationId), extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
23
- { root, output, group: group ?? undefined },
24
- )
25
- return ast.createImport({ name: [operationName], root: file.path, path: operationFile.path })
26
- })
27
-
28
- const handlers = nodes.map((node) => `${resolver.resolveHandlerName(node)}()`)
29
-
30
- return [
31
- ast.createFile({
32
- baseName: file.baseName,
33
- path: file.path,
34
- meta: file.meta,
35
- banner: resolver.resolveBanner(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } }),
36
- footer: resolver.resolveFooter(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } }),
37
- imports,
38
- sources: [
39
- ast.createSource({
40
- name: handlersName,
41
- isIndexable: true,
42
- isExportable: true,
43
- nodes: [ast.createText(`export const ${handlersName} = ${JSON.stringify(handlers).replaceAll('"', '')} as const`)],
44
- }),
45
- ],
46
- }),
47
- ]
48
- },
49
- })
@@ -1,2 +0,0 @@
1
- export { handlersGenerator } from './handlersGenerator.ts'
2
- export { mswGenerator } from './mswGenerator.tsx'
@@ -1,96 +0,0 @@
1
- import { getOperationSuccessResponses, resolveResponseTypes } from '@internals/shared'
2
- import { ast, defineGenerator } from '@kubb/core'
3
- import { pluginFakerName } from '@kubb/plugin-faker'
4
- import { pluginTsName } from '@kubb/plugin-ts'
5
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
6
- import { Mock, MockWithFaker, Response } from '../components'
7
- import type { PluginMsw } from '../types'
8
- import { resolveFakerMeta } from '../utils.ts'
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
- */
16
- export const mswGenerator = defineGenerator<PluginMsw>({
17
- name: 'msw',
18
- renderer: jsxRenderer,
19
- operation(node, ctx) {
20
- if (!ast.isHttpOperationNode(node)) return null
21
- const { driver, resolver, config, root } = ctx
22
- const { output, parser, baseURL, group } = ctx.options
23
-
24
- const fileName = resolver.resolveName(node.operationId)
25
- const mock = {
26
- name: resolver.resolveHandlerName(node),
27
- file: resolver.resolveFile(
28
- { name: fileName, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
29
- { root, output, group: group ?? undefined },
30
- ),
31
- }
32
-
33
- const fakerPlugin = parser === 'faker' ? driver.getPlugin(pluginFakerName) : null
34
- const faker =
35
- parser === 'faker' && fakerPlugin
36
- ? resolveFakerMeta(node, {
37
- root,
38
- fakerResolver: driver.getResolver(pluginFakerName),
39
- fakerOutput: fakerPlugin.options?.output ?? output,
40
- fakerGroup: fakerPlugin.options?.group ?? null,
41
- })
42
- : null
43
-
44
- const pluginTs = driver.getPlugin(pluginTsName)
45
- if (!pluginTs) return null
46
- const tsResolver = driver.getResolver(pluginTsName)
47
-
48
- const type = {
49
- file: tsResolver.resolveFile(
50
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
51
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group ?? undefined },
52
- ),
53
- responseName: tsResolver.resolveResponseName(node),
54
- }
55
-
56
- const types = resolveResponseTypes(node, tsResolver)
57
- const successResponses = getOperationSuccessResponses(node)
58
- const hasSuccessSchema = successResponses.some((response) => !!response.content?.[0]?.schema)
59
-
60
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
61
-
62
- return (
63
- <File
64
- baseName={mock.file.baseName}
65
- path={mock.file.path}
66
- meta={mock.file.meta}
67
- banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: mock.file.path, baseName: mock.file.baseName } })}
68
- footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: mock.file.path, baseName: mock.file.baseName } })}
69
- >
70
- <File.Import name={['http']} path="msw" />
71
- <File.Import name={['HttpResponseResolver']} isTypeOnly path="msw" />
72
- <File.Import
73
- name={Array.from(new Set([type.responseName, ...types.map((t) => t[1]), ...(requestName ? [requestName] : [])]))}
74
- path={type.file.path}
75
- root={mock.file.path}
76
- isTypeOnly
77
- />
78
- {parser === 'faker' && faker && <File.Import name={[faker.name]} root={mock.file.path} path={faker.file.path} />}
79
-
80
- {types
81
- .filter(([code]) => code !== 'default')
82
- .map(([code, typeName]) => {
83
- const response = node.responses.find((item) => item.statusCode === String(code))
84
- if (!response) return null
85
- return <Response key={typeName} typeName={typeName} response={response} name={mock.name} />
86
- })}
87
-
88
- {parser === 'faker' && faker && hasSuccessSchema ? (
89
- <MockWithFaker name={mock.name} typeName={type.responseName} requestTypeName={requestName} fakerName={faker.name} node={node} baseURL={baseURL} />
90
- ) : (
91
- <Mock name={mock.name} typeName={type.responseName} requestTypeName={requestName} node={node} baseURL={baseURL} />
92
- )}
93
- </File>
94
- )
95
- },
96
- })
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { default, pluginMsw, pluginMswName } from './plugin.ts'
2
- export type { PluginMsw } from './types.ts'
package/src/plugin.ts DELETED
@@ -1,94 +0,0 @@
1
- import { createGroupConfig } from '@internals/shared'
2
- import { definePlugin } from '@kubb/core'
3
- import { pluginFakerName } from '@kubb/plugin-faker'
4
- import { pluginTsName } from '@kubb/plugin-ts'
5
- import { handlersGenerator, mswGenerator } from './generators'
6
- import { resolverMsw } from './resolvers/resolverMsw.ts'
7
- import type { PluginMsw } from './types.ts'
8
-
9
- /**
10
- * Canonical plugin name for `@kubb/plugin-msw`. Used for driver lookups and
11
- * cross-plugin dependency references.
12
- */
13
- export const pluginMswName = 'plugin-msw' satisfies PluginMsw['name']
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
- */
41
- export const pluginMsw = definePlugin<PluginMsw>((options) => {
42
- const {
43
- output = { path: 'handlers', barrel: { type: 'named' } },
44
- group,
45
- exclude = [],
46
- include,
47
- override = [],
48
- handlers = false,
49
- parser = 'data',
50
- baseURL,
51
- resolver: userResolver,
52
- transformer: userTransformer,
53
- generators: userGenerators = [],
54
- } = options
55
-
56
- const groupConfig = createGroupConfig(group)
57
-
58
- return {
59
- name: pluginMswName,
60
- options,
61
- dependencies: [pluginTsName, parser === 'faker' ? pluginFakerName : null].filter((dependency): dependency is string => Boolean(dependency)),
62
- hooks: {
63
- 'kubb:plugin:setup'(ctx) {
64
- const resolver = userResolver ? { ...resolverMsw, ...userResolver } : resolverMsw
65
-
66
- ctx.setOptions({
67
- output,
68
- parser,
69
- baseURL,
70
- group: groupConfig,
71
- exclude,
72
- include,
73
- override,
74
- handlers,
75
- resolver,
76
- })
77
- ctx.setResolver(resolver)
78
- if (userTransformer) {
79
- ctx.setTransformer(userTransformer)
80
- }
81
-
82
- ctx.addGenerator(mswGenerator)
83
- if (handlers) {
84
- ctx.addGenerator(handlersGenerator)
85
- }
86
- for (const gen of userGenerators) {
87
- ctx.addGenerator(gen)
88
- }
89
- },
90
- },
91
- }
92
- })
93
-
94
- export default pluginMsw
@@ -1,35 +0,0 @@
1
- import { camelCase, toFilePath } from '@internals/utils'
2
- import { defineResolver } from '@kubb/core'
3
- import type { PluginMsw } from '../types.ts'
4
-
5
- /**
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`.
9
- *
10
- * @example Resolve a handler name
11
- * ```ts
12
- * import { resolverMsw } from '@kubb/plugin-msw'
13
- *
14
- * resolverMsw.resolveName('addPet') // 'addPetHandler'
15
- * ```
16
- */
17
- export const resolverMsw = defineResolver<PluginMsw>(() => ({
18
- name: 'default',
19
- pluginName: 'plugin-msw',
20
- default(name, type) {
21
- return type === 'file' ? toFilePath(name) : camelCase(name)
22
- },
23
- resolveName(name) {
24
- return camelCase(name, { suffix: 'handler' })
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
- },
35
- }))
package/src/types.ts DELETED
@@ -1,98 +0,0 @@
1
- import type { ast, Exclude, Generator, Group, Include, Output, OutputOptions, Override, PluginFactoryOptions, Resolver } from '@kubb/core'
2
-
3
- /**
4
- * Resolver for MSW that provides naming methods for handler functions.
5
- */
6
- export type ResolverMsw = Resolver & {
7
- /**
8
- * Resolves the base handler function name for an operation.
9
- */
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
23
- }
24
-
25
- /**
26
- * Where the generated MSW handlers are written and how they are exported, plus the optional
27
- * `group` strategy. The `group` option organizes `output.mode: 'directory'` output into per-tag or per-path subdirectories.
28
- *
29
- * @default { path: 'handlers', barrel: { type: 'named' } }
30
- */
31
- export type Options = OutputOptions & {
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
- */
36
- baseURL?: string
37
- /**
38
- * Skip operations matching at least one entry in the list.
39
- */
40
- exclude?: Array<Exclude>
41
- /**
42
- * Restrict generation to operations matching at least one entry in the list.
43
- */
44
- include?: Array<Include>
45
- /**
46
- * Apply a different options object to operations matching a pattern.
47
- */
48
- override?: Array<Override<ResolvedOptions>>
49
- /**
50
- * Override how handler names and file paths are built.
51
- */
52
- resolver?: Partial<ResolverMsw> & ThisType<ResolverMsw>
53
- /**
54
- * AST visitor applied to operation nodes before printing.
55
- */
56
- transformer?: ast.Visitor
57
- /**
58
- * Emit a `handlers.ts` file that re-exports every handler grouped by HTTP method.
59
- * Drop the file into `setupServer(...handlers)` or `setupWorker(...handlers)`.
60
- *
61
- * @default false
62
- */
63
- handlers?: boolean
64
- /**
65
- * Source of the response body returned by each generated handler.
66
- * - `'data'` — typed empty/example payload, ready for you to fill in from tests.
67
- * - `'faker'` — value produced by `@kubb/plugin-faker`.
68
- *
69
- * @default 'data'
70
- */
71
- parser?: 'data' | 'faker'
72
- /**
73
- * Custom generators that run alongside the built-in MSW generators.
74
- */
75
- generators?: Array<Generator<PluginMsw>>
76
- }
77
-
78
- type ResolvedOptions = {
79
- output: Output
80
- group: Group | null
81
- exclude: NonNullable<Options['exclude']>
82
- include: Options['include']
83
- override: NonNullable<Options['override']>
84
- parser: NonNullable<Options['parser']>
85
- baseURL: Options['baseURL'] | undefined
86
- handlers: boolean
87
- resolver: ResolverMsw
88
- }
89
-
90
- export type PluginMsw = PluginFactoryOptions<'plugin-msw', Options, ResolvedOptions, ResolverMsw>
91
-
92
- declare global {
93
- namespace Kubb {
94
- interface PluginRegistry {
95
- 'plugin-msw': PluginMsw
96
- }
97
- }
98
- }
package/src/utils.ts DELETED
@@ -1,74 +0,0 @@
1
- import { ast } from '@kubb/core'
2
- import type { ResolverFaker } from '@kubb/plugin-faker'
3
- import type { PluginMsw } from './types.ts'
4
-
5
- /**
6
- * Gets the content type from a response, defaulting to 'application/json' if a schema exists.
7
- */
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'
14
- }
15
-
16
- /**
17
- * Determines if a response has a schema that is not void or any.
18
- */
19
- export function hasResponseSchema(response: ast.ResponseNode | null | undefined): boolean {
20
- const schema = response?.content?.find((entry) => entry.schema)?.schema
21
- return !!schema && schema.type !== 'void' && schema.type !== 'any'
22
- }
23
-
24
- /**
25
- * Picks the content type used for the mocked response header. When a response declares multiple
26
- * content types, JSON is preferred (the faker mock body is JSON), otherwise the first declared type.
27
- */
28
- function getResponseContentType(response: ast.ResponseNode | null | undefined): string | null {
29
- const contents = response?.content ?? []
30
- const jsonEntry = contents.find((entry) => {
31
- const baseType = entry.contentType?.split(';')[0]?.trim().toLowerCase()
32
- return baseType === 'application/json' || baseType?.endsWith('+json')
33
- })
34
- const value = (jsonEntry ?? contents[0])?.contentType
35
- return typeof value === 'string' && value.length > 0 ? value : null
36
- }
37
-
38
- /**
39
- * Converts an HTTP method to its lowercase MSW equivalent (e.g., 'POST' → 'post').
40
- */
41
- export function getMswMethod(node: ast.OperationNode): string {
42
- return ast.isHttpOperationNode(node) ? node.method.toLowerCase() : ''
43
- }
44
-
45
- /**
46
- * Converts an OpenAPI-style path to an Express/MSW-style path by replacing `{param}` with `:param`.
47
- */
48
- export function getMswUrl(node: ast.OperationNode): string {
49
- return ast.isHttpOperationNode(node) ? node.path.replaceAll('{', ':').replaceAll('}', '') : ''
50
- }
51
-
52
- /**
53
- * Resolves faker metadata for an MSW operation, including response name and file path.
54
- */
55
- export function resolveFakerMeta(
56
- node: ast.OperationNode,
57
- options: {
58
- root: string
59
- fakerResolver: ResolverFaker
60
- fakerOutput: PluginMsw['resolvedOptions']['output']
61
- fakerGroup: PluginMsw['resolvedOptions']['group']
62
- },
63
- ): { name: string; file: { path: string } } {
64
- const { root, fakerResolver, fakerOutput, fakerGroup } = options
65
- const tag = node.tags[0] ?? 'default'
66
-
67
- return {
68
- name: fakerResolver.resolveResponseName(node),
69
- file: fakerResolver.resolveFile(
70
- { name: node.operationId, extname: '.ts', tag, path: node.path },
71
- { root, output: fakerOutput, group: fakerGroup ?? undefined },
72
- ),
73
- }
74
- }