@kubb/plugin-msw 5.0.0-beta.98 → 5.0.0

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.js CHANGED
@@ -1,267 +1,49 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
2
+ import { Resolver, Url, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
3
3
  import { pluginFakerName } from "@kubb/plugin-faker";
4
4
  import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
5
5
  import { File, Function, jsxRenderer } from "kubb/jsx";
6
6
  import { jsx, jsxs } from "kubb/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
34
- //#region ../../internals/utils/src/reserved.ts
35
- /**
36
- * JavaScript and Java reserved words.
37
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
38
- */
39
- const reservedWords = /* @__PURE__ */ new Set([
40
- "abstract",
41
- "arguments",
42
- "boolean",
43
- "break",
44
- "byte",
45
- "case",
46
- "catch",
47
- "char",
48
- "class",
49
- "const",
50
- "continue",
51
- "debugger",
52
- "default",
53
- "delete",
54
- "do",
55
- "double",
56
- "else",
57
- "enum",
58
- "eval",
59
- "export",
60
- "extends",
61
- "false",
62
- "final",
63
- "finally",
64
- "float",
65
- "for",
66
- "function",
67
- "goto",
68
- "if",
69
- "implements",
70
- "import",
71
- "in",
72
- "instanceof",
73
- "int",
74
- "interface",
75
- "let",
76
- "long",
77
- "native",
78
- "new",
79
- "null",
80
- "package",
81
- "private",
82
- "protected",
83
- "public",
84
- "return",
85
- "short",
86
- "static",
87
- "super",
88
- "switch",
89
- "synchronized",
90
- "this",
91
- "throw",
92
- "throws",
93
- "transient",
94
- "true",
95
- "try",
96
- "typeof",
97
- "var",
98
- "void",
99
- "volatile",
100
- "while",
101
- "with",
102
- "yield",
103
- "Array",
104
- "Date",
105
- "hasOwnProperty",
106
- "Infinity",
107
- "isFinite",
108
- "isNaN",
109
- "isPrototypeOf",
110
- "length",
111
- "Math",
112
- "name",
113
- "NaN",
114
- "Number",
115
- "Object",
116
- "prototype",
117
- "String",
118
- "toString",
119
- "undefined",
120
- "valueOf"
121
- ]);
7
+ //#region ../../internals/shared/src/operation.ts
122
8
  /**
123
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
9
+ * Builds the `ResolverFileParams` every operation generator passes to
10
+ * `resolver.file`: a file named `name`, tagged by the operation's first
11
+ * tag (or `'default'`), at the operation's path. Centralizes the entry object
12
+ * that was repeated at dozens of call sites across the client and query plugins.
124
13
  *
125
14
  * @example
126
15
  * ```ts
127
- * isValidVarName('status') // true
128
- * isValidVarName('class') // false (reserved word)
129
- * isValidVarName('42foo') // false (starts with digit)
16
+ * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
130
17
  * ```
131
18
  */
132
- function isValidVarName(name) {
133
- if (!name || reservedWords.has(name)) return false;
134
- return isIdentifier(name);
19
+ function operationFileEntry(node, name, extname = ".ts") {
20
+ return {
21
+ name,
22
+ extname,
23
+ tag: node.tags[0] ?? "default",
24
+ path: node.path
25
+ };
135
26
  }
136
27
  /**
137
- * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
28
+ * Resolves a dependency plugin's generated file for `node.operationId`, cached in `cache` (the
29
+ * current node's `ctx.cache`) under the resolver's own plugin name. Several dependents reading the
30
+ * same dependency for the same operation in one pass (a query plugin's several hook generators, the
31
+ * MCP handler, ...) share one computed name and path instead of each calling `resolver.file` again.
138
32
  *
139
- * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
140
- * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
141
- * deciding whether an object key needs quoting.
142
- *
143
- * @example
33
+ * @example Cache `plugin-ts`'s file for the current operation
144
34
  * ```ts
145
- * isIdentifier('name') // true
146
- * isIdentifier('x-total')// false
35
+ * const fileTs = resolveDependencyOperationFile({ cache: ctx.cache, node, resolver: tsResolver, root, output })
147
36
  * ```
148
37
  */
149
- function isIdentifier(name) {
150
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
151
- }
152
- //#endregion
153
- //#region ../../internals/utils/src/url.ts
154
- function transformParam(raw, casing) {
155
- const param = isValidVarName(raw) ? raw : camelCase(raw);
156
- return casing === "camelcase" ? camelCase(param) : param;
157
- }
158
- function toParamsObject(path, { replacer, casing } = {}) {
159
- const params = {};
160
- for (const match of path.matchAll(/\{([^}]+)\}/g)) {
161
- const param = transformParam(match[1], casing);
162
- const key = replacer ? replacer(param) : param;
163
- params[key] = key;
164
- }
165
- return Object.keys(params).length > 0 ? params : null;
38
+ function resolveDependencyOperationFile(options) {
39
+ const { cache, node, resolver, root, output, group } = options;
40
+ return cache.ensureItem(`${resolver.pluginName}:operationFile`, () => resolver.file({
41
+ ...operationFileEntry(node, node.operationId),
42
+ root,
43
+ output,
44
+ group: group ?? void 0
45
+ }));
166
46
  }
167
- /**
168
- * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
169
- */
170
- var Url = class Url {
171
- /**
172
- * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
173
- *
174
- * @example
175
- * Url.canParse('https://petstore.swagger.io/v2') // true
176
- * Url.canParse('/pet/{petId}') // false
177
- */
178
- static canParse(url, base) {
179
- return URL.canParse(url, base);
180
- }
181
- /**
182
- * Converts an OpenAPI/Swagger path to Express-style colon syntax.
183
- *
184
- * @example
185
- * Url.toPath('/pet/{petId}') // '/pet/:petId'
186
- */
187
- static toPath(path) {
188
- return path.replace(/\{([^}]+)\}/g, ":$1");
189
- }
190
- /**
191
- * Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
192
- * literal aligns with the grouped `path` request option that the runtime client interpolates by
193
- * key.
194
- *
195
- * @example
196
- * Url.toCasedTemplate('/projects/{project_id}', { casing: 'camelcase' }) // '/projects/{projectId}'
197
- */
198
- static toCasedTemplate(path, { casing } = {}) {
199
- return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name, casing)}}`);
200
- }
201
- /**
202
- * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
203
- * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
204
- * and `casing` controls parameter identifier casing.
205
- *
206
- * @example
207
- * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
208
- *
209
- * @example
210
- * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
211
- */
212
- static toTemplateString(path, { prefix, replacer, casing } = {}) {
213
- const result = path.split(/\{([^}]+)\}/).map((part, i) => {
214
- if (i % 2 === 0) return part;
215
- const param = transformParam(part, casing);
216
- return `\${${replacer ? replacer(param) : param}}`;
217
- }).join("");
218
- return `\`${prefix ?? ""}${result}\``;
219
- }
220
- /**
221
- * Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
222
- * grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
223
- * names are camelCased to match the generated `path` type, and `prefix` is prepended inside the
224
- * literal. Shared by the client and cypress generators that pass a grouped `path` object.
225
- *
226
- * @example
227
- * Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
228
- */
229
- static toGroupedTemplateString(path, { prefix } = {}) {
230
- return Url.toTemplateString(path, {
231
- prefix,
232
- casing: "camelcase",
233
- replacer: (name) => `path.${name}`
234
- });
235
- }
236
- /**
237
- * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
238
- * expression when `stringify` is set.
239
- *
240
- * @example
241
- * Url.toObject('/pet/{petId}')
242
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
243
- */
244
- static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
245
- const object = {
246
- url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
247
- replacer,
248
- casing
249
- }),
250
- params: toParamsObject(path, {
251
- replacer,
252
- casing
253
- })
254
- };
255
- if (stringify) {
256
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
257
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
258
- return `{ url: '${object.url}' }`;
259
- }
260
- return object;
261
- }
262
- };
263
- //#endregion
264
- //#region ../../internals/shared/src/operation.ts
265
47
  function getStatusCodeNumber(statusCode) {
266
48
  const code = Number(statusCode);
267
49
  return Number.isNaN(code) ? null : code;
@@ -293,6 +75,33 @@ function resolveResponseTypes(node, resolver) {
293
75
  return types;
294
76
  }
295
77
  //#endregion
78
+ //#region ../../internals/utils/src/casing.ts
79
+ /**
80
+ * Shared implementation for camelCase and PascalCase conversion.
81
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
82
+ * and capitalizes each word according to `pascal`.
83
+ *
84
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
85
+ */
86
+ function toCamelOrPascal(text, pascal) {
87
+ 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) => {
88
+ if (word.length > 1 && word === word.toUpperCase()) return word;
89
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
90
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
91
+ }
92
+ /**
93
+ * Converts `text` to camelCase.
94
+ *
95
+ * @example Word boundaries
96
+ * `camelCase('hello-world') // 'helloWorld'`
97
+ *
98
+ * @example With a prefix
99
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
100
+ */
101
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
102
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
103
+ }
104
+ //#endregion
296
105
  //#region ../../internals/shared/src/group.ts
297
106
  /**
298
107
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
@@ -427,12 +236,6 @@ function getMswMethod(node) {
427
236
  return ast.isHttpOperationNode(node) ? node.method.toLowerCase() : "";
428
237
  }
429
238
  /**
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
239
  * Resolves faker metadata for an MSW operation, including response name and file path.
437
240
  */
438
241
  function resolveFakerMeta(node, options) {
@@ -459,7 +262,7 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
459
262
  const successResponse = getPrimarySuccessResponse(node);
460
263
  const statusCode = successResponse ? Number(successResponse.statusCode) : 200;
461
264
  const contentType = getContentType(successResponse);
462
- const url = Url.toPath(getMswUrl(node));
265
+ const url = ast.isHttpOperationNode(node) ? Url.toPath(node.path) : "";
463
266
  const headers = [contentType ? `'Content-Type': '${contentType}'` : null].filter(Boolean);
464
267
  const dataType = hasResponseSchema(successResponse) ? typeName : "string | number | boolean | null | object";
465
268
  const paramType = fakerName ? typeName : dataType;
@@ -473,6 +276,7 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
473
276
  const requestUrl = `${baseURL}${url.replace(/([^/]):/g, "$1\\\\:")}`;
474
277
  const urlLiteral = fakerName ? `'${requestUrl}'` : `\`${requestUrl}\``;
475
278
  const responseBody = fakerName ? `JSON.stringify(data || ${fakerName}(data))` : "JSON.stringify(data)";
279
+ const headersBlock = headers.length ? `\n headers: {\n ${headers.join(", \n")}\n },` : "";
476
280
  return /* @__PURE__ */ jsx(File.Source, {
477
281
  name,
478
282
  isIndexable: true,
@@ -485,10 +289,7 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
485
289
  if(typeof data === 'function') return data(info)
486
290
 
487
291
  return new Response(${responseBody}, {
488
- status: ${statusCode},
489
- ${headers.length ? ` headers: {
490
- ${headers.join(", \n")}
491
- },` : ""}
292
+ status: ${statusCode},${headersBlock}
492
293
  })
493
294
  })`
494
295
  })
@@ -507,6 +308,7 @@ function Response({ name, typeName, response }) {
507
308
  optional: !hasResponseSchema(response)
508
309
  })] }));
509
310
  const responseName = `${name}Response${statusCode}`;
311
+ const headersBlock = headers.length ? `\n headers: {\n ${headers.join(", \n")}\n },` : "";
510
312
  return /* @__PURE__ */ jsx(File.Source, {
511
313
  name: responseName,
512
314
  isIndexable: true,
@@ -517,10 +319,7 @@ function Response({ name, typeName, response }) {
517
319
  params: params ?? "",
518
320
  children: `
519
321
  return new Response(JSON.stringify(data), {
520
- status: ${statusCode},
521
- ${headers.length ? ` headers: {
522
- ${headers.join(", \n")}
523
- },` : ""}
322
+ status: ${statusCode},${headersBlock}
524
323
  })`
525
324
  })
526
325
  });
@@ -564,14 +363,13 @@ const mswGenerator = defineGenerator({
564
363
  if (!pluginTs) return null;
565
364
  const tsResolver = driver.getResolver(pluginTsName);
566
365
  const type = {
567
- file: tsResolver.file({
568
- name: node.operationId,
569
- extname: ".ts",
570
- tag: node.tags[0] ?? "default",
571
- path: node.path,
366
+ file: resolveDependencyOperationFile({
367
+ cache: ctx.cache,
368
+ node,
369
+ resolver: tsResolver,
572
370
  root,
573
371
  output: pluginTs.options?.output ?? output,
574
- group: pluginTs.options?.group ?? void 0
372
+ group: pluginTs.options?.group
575
373
  }),
576
374
  responseName: tsResolver.response.response(node)
577
375
  };