@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.
- package/README.md +2 -2
- package/dist/index.cjs +640 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +92 -2
- package/dist/index.js +635 -8
- package/dist/index.js.map +1 -1
- package/package.json +9 -18
- package/dist/components-Bi7cN-ws.js +0 -431
- package/dist/components-Bi7cN-ws.js.map +0 -1
- package/dist/components-Bp69JYOt.cjs +0 -484
- package/dist/components-Bp69JYOt.cjs.map +0 -1
- package/dist/components.cjs +0 -5
- package/dist/components.d.ts +0 -53
- package/dist/components.js +0 -2
- package/dist/generators-BnEvTy3q.js +0 -208
- package/dist/generators-BnEvTy3q.js.map +0 -1
- package/dist/generators-d22_s2UN.cjs +0 -218
- package/dist/generators-d22_s2UN.cjs.map +0 -1
- package/dist/generators.cjs +0 -4
- package/dist/generators.d.ts +0 -23
- package/dist/generators.js +0 -2
- package/dist/types-B1yTWOfj.d.ts +0 -99
- package/src/components/Mock.tsx +0 -71
- package/src/components/MockWithFaker.tsx +0 -70
- package/src/components/Response.tsx +0 -52
- package/src/components/index.ts +0 -3
- package/src/generators/handlersGenerator.ts +0 -49
- package/src/generators/index.ts +0 -2
- package/src/generators/mswGenerator.tsx +0 -96
- package/src/index.ts +0 -2
- package/src/plugin.ts +0 -94
- package/src/resolvers/resolverMsw.ts +0 -35
- package/src/types.ts +0 -98
- package/src/utils.ts +0 -74
- /package/dist/{chunk-C0LytTxp.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import {
|
|
3
|
-
import { n as handlersGenerator, t as mswGenerator } from "./generators-BnEvTy3q.js";
|
|
4
|
-
import { definePlugin, defineResolver } from "@kubb/core";
|
|
1
|
+
import "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
+
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
5
3
|
import { pluginFakerName } from "@kubb/plugin-faker";
|
|
6
|
-
import { pluginTsName } from "@kubb/plugin-ts";
|
|
4
|
+
import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
|
|
5
|
+
import { File, Function, jsxRenderer } from "@kubb/renderer-jsx";
|
|
6
|
+
import { jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
7
|
+
//#region ../../internals/utils/src/casing.ts
|
|
8
|
+
/**
|
|
9
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
10
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
11
|
+
* and capitalizes each word according to `pascal`.
|
|
12
|
+
*
|
|
13
|
+
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
14
|
+
*/
|
|
15
|
+
function toCamelOrPascal(text, pascal) {
|
|
16
|
+
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
17
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
18
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
19
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Converts `text` to camelCase.
|
|
23
|
+
*
|
|
24
|
+
* @example Word boundaries
|
|
25
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
26
|
+
*
|
|
27
|
+
* @example With a prefix
|
|
28
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
29
|
+
*/
|
|
30
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
31
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
7
34
|
//#region ../../internals/utils/src/fs.ts
|
|
8
35
|
/**
|
|
9
36
|
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
@@ -29,6 +56,241 @@ function toFilePath(name, caseLast = camelCase) {
|
|
|
29
56
|
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
30
57
|
}
|
|
31
58
|
//#endregion
|
|
59
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
60
|
+
/**
|
|
61
|
+
* JavaScript and Java reserved words.
|
|
62
|
+
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
63
|
+
*/
|
|
64
|
+
const reservedWords = new Set([
|
|
65
|
+
"abstract",
|
|
66
|
+
"arguments",
|
|
67
|
+
"boolean",
|
|
68
|
+
"break",
|
|
69
|
+
"byte",
|
|
70
|
+
"case",
|
|
71
|
+
"catch",
|
|
72
|
+
"char",
|
|
73
|
+
"class",
|
|
74
|
+
"const",
|
|
75
|
+
"continue",
|
|
76
|
+
"debugger",
|
|
77
|
+
"default",
|
|
78
|
+
"delete",
|
|
79
|
+
"do",
|
|
80
|
+
"double",
|
|
81
|
+
"else",
|
|
82
|
+
"enum",
|
|
83
|
+
"eval",
|
|
84
|
+
"export",
|
|
85
|
+
"extends",
|
|
86
|
+
"false",
|
|
87
|
+
"final",
|
|
88
|
+
"finally",
|
|
89
|
+
"float",
|
|
90
|
+
"for",
|
|
91
|
+
"function",
|
|
92
|
+
"goto",
|
|
93
|
+
"if",
|
|
94
|
+
"implements",
|
|
95
|
+
"import",
|
|
96
|
+
"in",
|
|
97
|
+
"instanceof",
|
|
98
|
+
"int",
|
|
99
|
+
"interface",
|
|
100
|
+
"let",
|
|
101
|
+
"long",
|
|
102
|
+
"native",
|
|
103
|
+
"new",
|
|
104
|
+
"null",
|
|
105
|
+
"package",
|
|
106
|
+
"private",
|
|
107
|
+
"protected",
|
|
108
|
+
"public",
|
|
109
|
+
"return",
|
|
110
|
+
"short",
|
|
111
|
+
"static",
|
|
112
|
+
"super",
|
|
113
|
+
"switch",
|
|
114
|
+
"synchronized",
|
|
115
|
+
"this",
|
|
116
|
+
"throw",
|
|
117
|
+
"throws",
|
|
118
|
+
"transient",
|
|
119
|
+
"true",
|
|
120
|
+
"try",
|
|
121
|
+
"typeof",
|
|
122
|
+
"var",
|
|
123
|
+
"void",
|
|
124
|
+
"volatile",
|
|
125
|
+
"while",
|
|
126
|
+
"with",
|
|
127
|
+
"yield",
|
|
128
|
+
"Array",
|
|
129
|
+
"Date",
|
|
130
|
+
"hasOwnProperty",
|
|
131
|
+
"Infinity",
|
|
132
|
+
"isFinite",
|
|
133
|
+
"isNaN",
|
|
134
|
+
"isPrototypeOf",
|
|
135
|
+
"length",
|
|
136
|
+
"Math",
|
|
137
|
+
"name",
|
|
138
|
+
"NaN",
|
|
139
|
+
"Number",
|
|
140
|
+
"Object",
|
|
141
|
+
"prototype",
|
|
142
|
+
"String",
|
|
143
|
+
"toString",
|
|
144
|
+
"undefined",
|
|
145
|
+
"valueOf"
|
|
146
|
+
]);
|
|
147
|
+
/**
|
|
148
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* isValidVarName('status') // true
|
|
153
|
+
* isValidVarName('class') // false (reserved word)
|
|
154
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
function isValidVarName(name) {
|
|
158
|
+
if (!name || reservedWords.has(name)) return false;
|
|
159
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region ../../internals/utils/src/url.ts
|
|
163
|
+
function transformParam(raw, casing) {
|
|
164
|
+
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
165
|
+
return casing === "camelcase" ? camelCase(param) : param;
|
|
166
|
+
}
|
|
167
|
+
function toParamsObject(path, { replacer, casing } = {}) {
|
|
168
|
+
const params = {};
|
|
169
|
+
for (const match of path.matchAll(/\{([^}]+)\}/g)) {
|
|
170
|
+
const param = transformParam(match[1], casing);
|
|
171
|
+
const key = replacer ? replacer(param) : param;
|
|
172
|
+
params[key] = key;
|
|
173
|
+
}
|
|
174
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
|
|
178
|
+
*/
|
|
179
|
+
var Url = class Url {
|
|
180
|
+
/**
|
|
181
|
+
* Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* Url.canParse('https://petstore.swagger.io/v2') // true
|
|
185
|
+
* Url.canParse('/pet/{petId}') // false
|
|
186
|
+
*/
|
|
187
|
+
static canParse(url, base) {
|
|
188
|
+
return URL.canParse(url, base);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Converts an OpenAPI/Swagger path to Express-style colon syntax.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* Url.toPath('/pet/{petId}') // '/pet/:petId'
|
|
195
|
+
*/
|
|
196
|
+
static toPath(path) {
|
|
197
|
+
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Converts an OpenAPI/Swagger path to a TypeScript template literal string.
|
|
201
|
+
* `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
|
|
202
|
+
* and `casing` controls parameter identifier casing.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
|
|
209
|
+
*/
|
|
210
|
+
static toTemplateString(path, { prefix, replacer, casing } = {}) {
|
|
211
|
+
const result = path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
212
|
+
if (i % 2 === 0) return part;
|
|
213
|
+
const param = transformParam(part, casing);
|
|
214
|
+
return `\${${replacer ? replacer(param) : param}}`;
|
|
215
|
+
}).join("");
|
|
216
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
|
|
220
|
+
* grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
|
|
221
|
+
* names are camelCased to match the generated `path` type, and `prefix` is prepended inside the
|
|
222
|
+
* literal. Shared by the client and cypress generators that pass a grouped `path` object.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
|
|
226
|
+
*/
|
|
227
|
+
static toGroupedTemplateString(path, { prefix } = {}) {
|
|
228
|
+
return Url.toTemplateString(path, {
|
|
229
|
+
prefix,
|
|
230
|
+
casing: "camelcase",
|
|
231
|
+
replacer: (name) => `path.${name}`
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Returns the path and its extracted params as a structured `URLObject`, or as a stringified
|
|
236
|
+
* expression when `stringify` is set.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* Url.toObject('/pet/{petId}')
|
|
240
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
241
|
+
*/
|
|
242
|
+
static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
|
|
243
|
+
const object = {
|
|
244
|
+
url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
|
|
245
|
+
replacer,
|
|
246
|
+
casing
|
|
247
|
+
}),
|
|
248
|
+
params: toParamsObject(path, {
|
|
249
|
+
replacer,
|
|
250
|
+
casing
|
|
251
|
+
})
|
|
252
|
+
};
|
|
253
|
+
if (stringify) {
|
|
254
|
+
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
255
|
+
if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
|
|
256
|
+
return `{ url: '${object.url}' }`;
|
|
257
|
+
}
|
|
258
|
+
return object;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region ../../internals/shared/src/operation.ts
|
|
263
|
+
function getStatusCodeNumber(statusCode) {
|
|
264
|
+
const code = Number(statusCode);
|
|
265
|
+
return Number.isNaN(code) ? null : code;
|
|
266
|
+
}
|
|
267
|
+
function isSuccessStatusCode(statusCode) {
|
|
268
|
+
const code = getStatusCodeNumber(statusCode);
|
|
269
|
+
return code !== null && code >= 200 && code < 300;
|
|
270
|
+
}
|
|
271
|
+
function getSuccessResponses(responses) {
|
|
272
|
+
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
273
|
+
}
|
|
274
|
+
function getOperationSuccessResponses(node) {
|
|
275
|
+
return getSuccessResponses(node.responses);
|
|
276
|
+
}
|
|
277
|
+
function getPrimarySuccessResponse(node) {
|
|
278
|
+
return getOperationSuccessResponses(node)[0] ?? null;
|
|
279
|
+
}
|
|
280
|
+
function resolveResponseTypes(node, resolver) {
|
|
281
|
+
const types = [];
|
|
282
|
+
for (const response of node.responses) {
|
|
283
|
+
if (response.statusCode === "default") {
|
|
284
|
+
types.push(["default", resolver.resolveResponseName(node)]);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const code = getStatusCodeNumber(response.statusCode);
|
|
288
|
+
if (code === null) continue;
|
|
289
|
+
types.push([code, isSuccessStatusCode(code) ? resolver.resolveResponseName(node) : resolver.resolveResponseStatusName(node, response.statusCode)]);
|
|
290
|
+
}
|
|
291
|
+
return types;
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
32
294
|
//#region ../../internals/shared/src/group.ts
|
|
33
295
|
/**
|
|
34
296
|
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
@@ -60,6 +322,372 @@ function createGroupConfig(group) {
|
|
|
60
322
|
};
|
|
61
323
|
}
|
|
62
324
|
//#endregion
|
|
325
|
+
//#region src/generators/handlersGenerator.ts
|
|
326
|
+
/**
|
|
327
|
+
* Aggregate generator enabled by `pluginMsw({ handlers: true })`. Emits a
|
|
328
|
+
* `handlers.ts` file that re-exports every generated handler grouped by HTTP
|
|
329
|
+
* method, ready to spread into `setupServer(...handlers)` or
|
|
330
|
+
* `setupWorker(...handlers)`.
|
|
331
|
+
*/
|
|
332
|
+
const handlersGenerator = defineGenerator({
|
|
333
|
+
name: "plugin-msw",
|
|
334
|
+
operations(nodes, ctx) {
|
|
335
|
+
const { resolver, config, root } = ctx;
|
|
336
|
+
const { output, group } = ctx.options;
|
|
337
|
+
const handlersName = resolver.resolveHandlersName();
|
|
338
|
+
const file = resolver.resolveFile({
|
|
339
|
+
name: resolver.resolvePathName(handlersName, "file"),
|
|
340
|
+
extname: ".ts"
|
|
341
|
+
}, {
|
|
342
|
+
root,
|
|
343
|
+
output,
|
|
344
|
+
group: group ?? void 0
|
|
345
|
+
});
|
|
346
|
+
const imports = nodes.map((node) => {
|
|
347
|
+
const operationName = resolver.resolveHandlerName(node);
|
|
348
|
+
const operationFile = resolver.resolveFile({
|
|
349
|
+
name: resolver.resolveName(node.operationId),
|
|
350
|
+
extname: ".ts",
|
|
351
|
+
tag: node.tags[0] ?? "default",
|
|
352
|
+
path: node.path
|
|
353
|
+
}, {
|
|
354
|
+
root,
|
|
355
|
+
output,
|
|
356
|
+
group: group ?? void 0
|
|
357
|
+
});
|
|
358
|
+
return ast.factory.createImport({
|
|
359
|
+
name: [operationName],
|
|
360
|
+
root: file.path,
|
|
361
|
+
path: operationFile.path
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
const handlers = nodes.map((node) => `${resolver.resolveHandlerName(node)}()`);
|
|
365
|
+
return [ast.factory.createFile({
|
|
366
|
+
baseName: file.baseName,
|
|
367
|
+
path: file.path,
|
|
368
|
+
meta: file.meta,
|
|
369
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
370
|
+
output,
|
|
371
|
+
config,
|
|
372
|
+
file: {
|
|
373
|
+
path: file.path,
|
|
374
|
+
baseName: file.baseName
|
|
375
|
+
}
|
|
376
|
+
}),
|
|
377
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
378
|
+
output,
|
|
379
|
+
config,
|
|
380
|
+
file: {
|
|
381
|
+
path: file.path,
|
|
382
|
+
baseName: file.baseName
|
|
383
|
+
}
|
|
384
|
+
}),
|
|
385
|
+
imports,
|
|
386
|
+
sources: [ast.factory.createSource({
|
|
387
|
+
name: handlersName,
|
|
388
|
+
isIndexable: true,
|
|
389
|
+
isExportable: true,
|
|
390
|
+
nodes: [ast.factory.createText(`export const ${handlersName} = ${JSON.stringify(handlers).replaceAll("\"", "")} as const`)]
|
|
391
|
+
})]
|
|
392
|
+
})];
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/utils.ts
|
|
397
|
+
/**
|
|
398
|
+
* Gets the content type from a response, defaulting to 'application/json' if a schema exists.
|
|
399
|
+
*/
|
|
400
|
+
function getContentType(response) {
|
|
401
|
+
if (!hasResponseSchema(response)) return null;
|
|
402
|
+
return getResponseContentType(response) ?? "application/json";
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Determines if a response has a schema that is not void or any.
|
|
406
|
+
*/
|
|
407
|
+
function hasResponseSchema(response) {
|
|
408
|
+
const schema = response?.content?.find((entry) => entry.schema)?.schema;
|
|
409
|
+
return !!schema && schema.type !== "void" && schema.type !== "any";
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Picks the content type used for the mocked response header. When a response declares multiple
|
|
413
|
+
* content types, JSON is preferred (the faker mock body is JSON), otherwise the first declared type.
|
|
414
|
+
*/
|
|
415
|
+
function getResponseContentType(response) {
|
|
416
|
+
const contents = response?.content ?? [];
|
|
417
|
+
const value = (contents.find((entry) => {
|
|
418
|
+
const baseType = entry.contentType?.split(";")[0]?.trim().toLowerCase();
|
|
419
|
+
return baseType === "application/json" || baseType?.endsWith("+json");
|
|
420
|
+
}) ?? contents[0])?.contentType;
|
|
421
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Converts an HTTP method to its lowercase MSW equivalent (e.g., 'POST' → 'post').
|
|
425
|
+
*/
|
|
426
|
+
function getMswMethod(node) {
|
|
427
|
+
return ast.isHttpOperationNode(node) ? node.method.toLowerCase() : "";
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Converts an OpenAPI-style path to an Express/MSW-style path by replacing `{param}` with `:param`.
|
|
431
|
+
*/
|
|
432
|
+
function getMswUrl(node) {
|
|
433
|
+
return ast.isHttpOperationNode(node) ? node.path.replaceAll("{", ":").replaceAll("}", "") : "";
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Resolves faker metadata for an MSW operation, including response name and file path.
|
|
437
|
+
*/
|
|
438
|
+
function resolveFakerMeta(node, options) {
|
|
439
|
+
const { root, fakerResolver, fakerOutput, fakerGroup } = options;
|
|
440
|
+
const tag = node.tags[0] ?? "default";
|
|
441
|
+
return {
|
|
442
|
+
name: fakerResolver.resolveResponseName(node),
|
|
443
|
+
file: fakerResolver.resolveFile({
|
|
444
|
+
name: node.operationId,
|
|
445
|
+
extname: ".ts",
|
|
446
|
+
tag,
|
|
447
|
+
path: node.path
|
|
448
|
+
}, {
|
|
449
|
+
root,
|
|
450
|
+
output: fakerOutput,
|
|
451
|
+
group: fakerGroup ?? void 0
|
|
452
|
+
})
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/components/Mock.tsx
|
|
457
|
+
const declarationPrinter$2 = functionPrinter({ mode: "declaration" });
|
|
458
|
+
function Mock({ baseURL = "", name, typeName, requestTypeName, node }) {
|
|
459
|
+
const method = getMswMethod(node);
|
|
460
|
+
const successResponse = getPrimarySuccessResponse(node);
|
|
461
|
+
const statusCode = successResponse ? Number(successResponse.statusCode) : 200;
|
|
462
|
+
const contentType = getContentType(successResponse);
|
|
463
|
+
const url = Url.toPath(getMswUrl(node));
|
|
464
|
+
const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean);
|
|
465
|
+
const dataType = hasResponseSchema(successResponse) ? typeName : "string | number | boolean | null | object";
|
|
466
|
+
const callbackType = requestTypeName ? `HttpResponseResolver<Record<string, string>, ${requestTypeName}, any>` : `((info: Parameters<Parameters<typeof http.${method}>[1]>[0]) => Response | Promise<Response>)`;
|
|
467
|
+
const params = declarationPrinter$2.print(createFunctionParameters({ params: [createFunctionParameter({
|
|
468
|
+
name: "data",
|
|
469
|
+
type: `${dataType} | ${callbackType}`,
|
|
470
|
+
optional: true
|
|
471
|
+
})] }));
|
|
472
|
+
const httpCall = requestTypeName ? `http.${method}<Record<string, string>, ${requestTypeName}, any>` : `http.${method}`;
|
|
473
|
+
return /* @__PURE__ */ jsx(File.Source, {
|
|
474
|
+
name,
|
|
475
|
+
isIndexable: true,
|
|
476
|
+
isExportable: true,
|
|
477
|
+
children: /* @__PURE__ */ jsx(Function, {
|
|
478
|
+
name,
|
|
479
|
+
export: true,
|
|
480
|
+
params: params ?? "",
|
|
481
|
+
children: `return ${httpCall}(\`${baseURL}${url.replace(/([^/]):/g, "$1\\\\:")}\`, function handler(info) {
|
|
482
|
+
if(typeof data === 'function') return data(info)
|
|
483
|
+
|
|
484
|
+
return new Response(JSON.stringify(data), {
|
|
485
|
+
status: ${statusCode},
|
|
486
|
+
${headers.length ? ` headers: {
|
|
487
|
+
${headers.join(", \n")}
|
|
488
|
+
},` : ""}
|
|
489
|
+
})
|
|
490
|
+
})`
|
|
491
|
+
})
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
//#region src/components/MockWithFaker.tsx
|
|
496
|
+
const declarationPrinter$1 = functionPrinter({ mode: "declaration" });
|
|
497
|
+
function MockWithFaker({ baseURL = "", name, fakerName, typeName, requestTypeName, node }) {
|
|
498
|
+
const method = getMswMethod(node);
|
|
499
|
+
const successResponse = getPrimarySuccessResponse(node);
|
|
500
|
+
const statusCode = successResponse ? Number(successResponse.statusCode) : 200;
|
|
501
|
+
const contentType = getContentType(successResponse);
|
|
502
|
+
const url = Url.toPath(getMswUrl(node));
|
|
503
|
+
const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean);
|
|
504
|
+
const callbackType = requestTypeName ? `HttpResponseResolver<Record<string, string>, ${requestTypeName}, any>` : `((info: Parameters<Parameters<typeof http.${method}>[1]>[0]) => Response | Promise<Response>)`;
|
|
505
|
+
const params = declarationPrinter$1.print(createFunctionParameters({ params: [createFunctionParameter({
|
|
506
|
+
name: "data",
|
|
507
|
+
type: `${typeName} | ${callbackType}`,
|
|
508
|
+
optional: true
|
|
509
|
+
})] }));
|
|
510
|
+
const httpCall = requestTypeName ? `http.${method}<Record<string, string>, ${requestTypeName}, any>` : `http.${method}`;
|
|
511
|
+
return /* @__PURE__ */ jsx(File.Source, {
|
|
512
|
+
name,
|
|
513
|
+
isIndexable: true,
|
|
514
|
+
isExportable: true,
|
|
515
|
+
children: /* @__PURE__ */ jsx(Function, {
|
|
516
|
+
name,
|
|
517
|
+
export: true,
|
|
518
|
+
params: params ?? "",
|
|
519
|
+
children: `return ${httpCall}('${baseURL}${url.replace(/([^/]):/g, "$1\\\\:")}', function handler(info) {
|
|
520
|
+
if(typeof data === 'function') return data(info)
|
|
521
|
+
|
|
522
|
+
return new Response(JSON.stringify(data || ${fakerName}(data)), {
|
|
523
|
+
status: ${statusCode},
|
|
524
|
+
${headers.length ? ` headers: {
|
|
525
|
+
${headers.join(", \n")}
|
|
526
|
+
},` : ""}
|
|
527
|
+
})
|
|
528
|
+
})`
|
|
529
|
+
})
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region src/components/Response.tsx
|
|
534
|
+
const declarationPrinter = functionPrinter({ mode: "declaration" });
|
|
535
|
+
function Response({ name, typeName, response }) {
|
|
536
|
+
const statusCode = Number(response.statusCode);
|
|
537
|
+
const contentType = getContentType(response);
|
|
538
|
+
const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean);
|
|
539
|
+
const params = declarationPrinter.print(createFunctionParameters({ params: [createFunctionParameter({
|
|
540
|
+
name: "data",
|
|
541
|
+
type: typeName,
|
|
542
|
+
optional: !hasResponseSchema(response)
|
|
543
|
+
})] }));
|
|
544
|
+
const responseName = `${name}Response${statusCode}`;
|
|
545
|
+
return /* @__PURE__ */ jsx(File.Source, {
|
|
546
|
+
name: responseName,
|
|
547
|
+
isIndexable: true,
|
|
548
|
+
isExportable: true,
|
|
549
|
+
children: /* @__PURE__ */ jsx(Function, {
|
|
550
|
+
name: responseName,
|
|
551
|
+
export: true,
|
|
552
|
+
params: params ?? "",
|
|
553
|
+
children: `
|
|
554
|
+
return new Response(JSON.stringify(data), {
|
|
555
|
+
status: ${statusCode},
|
|
556
|
+
${headers.length ? ` headers: {
|
|
557
|
+
${headers.join(", \n")}
|
|
558
|
+
},` : ""}
|
|
559
|
+
})`
|
|
560
|
+
})
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/generators/mswGenerator.tsx
|
|
565
|
+
/**
|
|
566
|
+
* Built-in operation generator for `@kubb/plugin-msw`. Emits one MSW handler
|
|
567
|
+
* per OpenAPI operation. With `parser: 'faker'` the handler returns a value
|
|
568
|
+
* from `@kubb/plugin-faker`; with `parser: 'data'` it returns a typed empty
|
|
569
|
+
* payload for tests to fill in.
|
|
570
|
+
*/
|
|
571
|
+
const mswGenerator = defineGenerator({
|
|
572
|
+
name: "msw",
|
|
573
|
+
renderer: jsxRenderer,
|
|
574
|
+
operation(node, ctx) {
|
|
575
|
+
if (!ast.isHttpOperationNode(node)) return null;
|
|
576
|
+
const { driver, resolver, config, root } = ctx;
|
|
577
|
+
const { output, parser, baseURL, group } = ctx.options;
|
|
578
|
+
const fileName = resolver.resolveName(node.operationId);
|
|
579
|
+
const mock = {
|
|
580
|
+
name: resolver.resolveHandlerName(node),
|
|
581
|
+
file: resolver.resolveFile({
|
|
582
|
+
name: fileName,
|
|
583
|
+
extname: ".ts",
|
|
584
|
+
tag: node.tags[0] ?? "default",
|
|
585
|
+
path: node.path
|
|
586
|
+
}, {
|
|
587
|
+
root,
|
|
588
|
+
output,
|
|
589
|
+
group: group ?? void 0
|
|
590
|
+
})
|
|
591
|
+
};
|
|
592
|
+
const fakerPlugin = parser === "faker" ? driver.getPlugin(pluginFakerName) : null;
|
|
593
|
+
const faker = parser === "faker" && fakerPlugin ? resolveFakerMeta(node, {
|
|
594
|
+
root,
|
|
595
|
+
fakerResolver: driver.getResolver(pluginFakerName),
|
|
596
|
+
fakerOutput: fakerPlugin.options?.output ?? output,
|
|
597
|
+
fakerGroup: fakerPlugin.options?.group ?? null
|
|
598
|
+
}) : null;
|
|
599
|
+
const pluginTs = driver.getPlugin(pluginTsName);
|
|
600
|
+
if (!pluginTs) return null;
|
|
601
|
+
const tsResolver = driver.getResolver(pluginTsName);
|
|
602
|
+
const type = {
|
|
603
|
+
file: tsResolver.resolveFile({
|
|
604
|
+
name: node.operationId,
|
|
605
|
+
extname: ".ts",
|
|
606
|
+
tag: node.tags[0] ?? "default",
|
|
607
|
+
path: node.path
|
|
608
|
+
}, {
|
|
609
|
+
root,
|
|
610
|
+
output: pluginTs.options?.output ?? output,
|
|
611
|
+
group: pluginTs.options?.group ?? void 0
|
|
612
|
+
}),
|
|
613
|
+
responseName: tsResolver.resolveResponseName(node)
|
|
614
|
+
};
|
|
615
|
+
const types = resolveResponseTypes(node, tsResolver);
|
|
616
|
+
const hasSuccessSchema = getOperationSuccessResponses(node).some((response) => !!response.content?.[0]?.schema);
|
|
617
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
|
|
618
|
+
return /* @__PURE__ */ jsxs(File, {
|
|
619
|
+
baseName: mock.file.baseName,
|
|
620
|
+
path: mock.file.path,
|
|
621
|
+
meta: mock.file.meta,
|
|
622
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
623
|
+
output,
|
|
624
|
+
config,
|
|
625
|
+
file: {
|
|
626
|
+
path: mock.file.path,
|
|
627
|
+
baseName: mock.file.baseName
|
|
628
|
+
}
|
|
629
|
+
}),
|
|
630
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
631
|
+
output,
|
|
632
|
+
config,
|
|
633
|
+
file: {
|
|
634
|
+
path: mock.file.path,
|
|
635
|
+
baseName: mock.file.baseName
|
|
636
|
+
}
|
|
637
|
+
}),
|
|
638
|
+
children: [
|
|
639
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
640
|
+
name: ["http"],
|
|
641
|
+
path: "msw"
|
|
642
|
+
}),
|
|
643
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
644
|
+
name: ["HttpResponseResolver"],
|
|
645
|
+
isTypeOnly: true,
|
|
646
|
+
path: "msw"
|
|
647
|
+
}),
|
|
648
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
649
|
+
name: Array.from(new Set([
|
|
650
|
+
type.responseName,
|
|
651
|
+
...types.map((t) => t[1]),
|
|
652
|
+
...requestName ? [requestName] : []
|
|
653
|
+
])),
|
|
654
|
+
path: type.file.path,
|
|
655
|
+
root: mock.file.path,
|
|
656
|
+
isTypeOnly: true
|
|
657
|
+
}),
|
|
658
|
+
parser === "faker" && faker && /* @__PURE__ */ jsx(File.Import, {
|
|
659
|
+
name: [faker.name],
|
|
660
|
+
root: mock.file.path,
|
|
661
|
+
path: faker.file.path
|
|
662
|
+
}),
|
|
663
|
+
types.filter(([code]) => code !== "default").map(([code, typeName]) => {
|
|
664
|
+
const response = node.responses.find((item) => item.statusCode === String(code));
|
|
665
|
+
if (!response) return null;
|
|
666
|
+
return /* @__PURE__ */ jsx(Response, {
|
|
667
|
+
typeName,
|
|
668
|
+
response,
|
|
669
|
+
name: mock.name
|
|
670
|
+
}, typeName);
|
|
671
|
+
}),
|
|
672
|
+
parser === "faker" && faker && hasSuccessSchema ? /* @__PURE__ */ jsx(MockWithFaker, {
|
|
673
|
+
name: mock.name,
|
|
674
|
+
typeName: type.responseName,
|
|
675
|
+
requestTypeName: requestName,
|
|
676
|
+
fakerName: faker.name,
|
|
677
|
+
node,
|
|
678
|
+
baseURL
|
|
679
|
+
}) : /* @__PURE__ */ jsx(Mock, {
|
|
680
|
+
name: mock.name,
|
|
681
|
+
typeName: type.responseName,
|
|
682
|
+
requestTypeName: requestName,
|
|
683
|
+
node,
|
|
684
|
+
baseURL
|
|
685
|
+
})
|
|
686
|
+
]
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
//#endregion
|
|
63
691
|
//#region src/resolvers/resolverMsw.ts
|
|
64
692
|
/**
|
|
65
693
|
* Default resolver used by `@kubb/plugin-msw`. Decides the names and file
|
|
@@ -129,7 +757,7 @@ const pluginMsw = definePlugin((options) => {
|
|
|
129
757
|
const { output = {
|
|
130
758
|
path: "handlers",
|
|
131
759
|
barrel: { type: "named" }
|
|
132
|
-
}, group, exclude = [], include, override = [], handlers = false, parser = "data", baseURL, resolver: userResolver,
|
|
760
|
+
}, group, exclude = [], include, override = [], handlers = false, parser = "data", baseURL, resolver: userResolver, macros: userMacros } = options;
|
|
133
761
|
const groupConfig = createGroupConfig(group);
|
|
134
762
|
return {
|
|
135
763
|
name: pluginMswName,
|
|
@@ -152,10 +780,9 @@ const pluginMsw = definePlugin((options) => {
|
|
|
152
780
|
resolver
|
|
153
781
|
});
|
|
154
782
|
ctx.setResolver(resolver);
|
|
155
|
-
if (
|
|
783
|
+
if (userMacros?.length) ctx.setMacros(userMacros);
|
|
156
784
|
ctx.addGenerator(mswGenerator);
|
|
157
785
|
if (handlers) ctx.addGenerator(handlersGenerator);
|
|
158
|
-
for (const gen of userGenerators) ctx.addGenerator(gen);
|
|
159
786
|
} }
|
|
160
787
|
};
|
|
161
788
|
});
|