@kubb/plugin-cypress 5.0.0-beta.10 → 5.0.0-beta.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3,337 +3,167 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  //#endregion
6
- let _kubb_core = require("@kubb/core");
6
+ let kubb_kit = require("kubb/kit");
7
+ let kubb_jsx = require("kubb/jsx");
8
+ let kubb_jsx_jsx_runtime = require("kubb/jsx/jsx-runtime");
7
9
  let _kubb_plugin_ts = require("@kubb/plugin-ts");
8
- let _kubb_renderer_jsx = require("@kubb/renderer-jsx");
9
- let _kubb_renderer_jsx_jsx_runtime = require("@kubb/renderer-jsx/jsx-runtime");
10
- //#region ../../internals/utils/src/casing.ts
10
+ //#region ../../internals/shared/src/params.ts
11
11
  /**
12
- * Shared implementation for camelCase and PascalCase conversion.
13
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
14
- * and capitalizes each word according to `pascal`.
12
+ * Drops parameters that share the same name, keeping the first.
15
13
  *
16
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
14
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
15
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
16
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
17
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
17
18
  */
18
- function toCamelOrPascal(text, pascal) {
19
- 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) => {
20
- if (word.length > 1 && word === word.toUpperCase()) return word;
21
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
22
- return word.charAt(0).toUpperCase() + word.slice(1);
23
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
19
+ function dedupeParams(params) {
20
+ const seen = /* @__PURE__ */ new Set();
21
+ return params.filter((param) => {
22
+ if (seen.has(param.name)) return false;
23
+ seen.add(param.name);
24
+ return true;
25
+ });
24
26
  }
27
+ //#endregion
28
+ //#region ../../internals/shared/src/operation.ts
25
29
  /**
26
- * Splits `text` on `.` and applies `transformPart` to each segment.
27
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
28
- * Segments are joined with `/` to form a file path.
29
- *
30
- * Only splits on dots followed by a letter so that version numbers
31
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
30
+ * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
31
+ * are present and the union, default, and form-data flags the client uses to pick one.
32
32
  */
33
- function applyToFileParts(text, transformPart) {
34
- const parts = text.split(/\.(?=[a-zA-Z])/);
35
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
33
+ function buildContentTypeInfo(contentTypes) {
34
+ const isMultipleContentTypes = contentTypes.length > 1;
35
+ return {
36
+ contentTypes,
37
+ isMultipleContentTypes,
38
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
39
+ defaultContentType: contentTypes[0] ?? "application/json",
40
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
41
+ };
42
+ }
43
+ function getContentTypeInfo(node) {
44
+ return buildContentTypeInfo(node.requestBody?.content?.map((e) => e.contentType) ?? []);
36
45
  }
37
46
  /**
38
- * Converts `text` to camelCase.
39
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
40
- *
41
- * @example
42
- * camelCase('hello-world') // 'helloWorld'
43
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
47
+ * The request-body counterpart for the primary success response: the content types it documents and
48
+ * whether several are present, so the client can let a caller pick which one to accept.
44
49
  */
45
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
46
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
47
- prefix,
48
- suffix
49
- } : {}));
50
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
50
+ function getResponseContentTypeInfo(node) {
51
+ return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
52
+ }
53
+ function buildRequestConfigType(node) {
54
+ const request = getContentTypeInfo(node);
55
+ const response = getResponseContentTypeInfo(node);
56
+ const configType = `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers' | 'url'>>`;
57
+ const members = [request.isMultipleContentTypes ? `request?: ${request.contentTypeUnion}` : null, response.isMultipleContentTypes ? `response?: ${response.contentTypeUnion}` : null].filter(Boolean);
58
+ return members.length ? `${configType} & { contentType?: { ${members.join("; ")} } }` : configType;
51
59
  }
52
- //#endregion
53
- //#region ../../internals/utils/src/reserved.ts
54
60
  /**
55
- * JavaScript and Java reserved words.
56
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
61
+ * Which of the grouped request options an operation carries.
57
62
  */
58
- const reservedWords = new Set([
59
- "abstract",
60
- "arguments",
61
- "boolean",
62
- "break",
63
- "byte",
64
- "case",
65
- "catch",
66
- "char",
67
- "class",
68
- "const",
69
- "continue",
70
- "debugger",
71
- "default",
72
- "delete",
73
- "do",
74
- "double",
75
- "else",
76
- "enum",
77
- "eval",
78
- "export",
79
- "extends",
80
- "false",
81
- "final",
82
- "finally",
83
- "float",
84
- "for",
85
- "function",
86
- "goto",
87
- "if",
88
- "implements",
89
- "import",
90
- "in",
91
- "instanceof",
92
- "int",
93
- "interface",
94
- "let",
95
- "long",
96
- "native",
97
- "new",
98
- "null",
99
- "package",
100
- "private",
101
- "protected",
102
- "public",
103
- "return",
104
- "short",
105
- "static",
106
- "super",
107
- "switch",
108
- "synchronized",
109
- "this",
110
- "throw",
111
- "throws",
112
- "transient",
113
- "true",
114
- "try",
115
- "typeof",
116
- "var",
117
- "void",
118
- "volatile",
119
- "while",
120
- "with",
121
- "yield",
122
- "Array",
123
- "Date",
124
- "hasOwnProperty",
125
- "Infinity",
126
- "isFinite",
127
- "isNaN",
128
- "isPrototypeOf",
129
- "length",
130
- "Math",
131
- "name",
132
- "NaN",
133
- "Number",
134
- "Object",
135
- "prototype",
136
- "String",
137
- "toString",
138
- "undefined",
139
- "valueOf"
140
- ]);
63
+ function getRequestGroups(node) {
64
+ const { path, query, header } = getOperationParameters(node);
65
+ return {
66
+ path: path.length > 0,
67
+ query: query.length > 0,
68
+ body: Boolean(node.requestBody?.content?.[0]?.schema),
69
+ headers: header.length > 0
70
+ };
71
+ }
141
72
  /**
142
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
143
- *
144
- * @example
145
- * ```ts
146
- * isValidVarName('status') // true
147
- * isValidVarName('class') // false (reserved word)
148
- * isValidVarName('42foo') // false (starts with digit)
149
- * ```
73
+ * Resolves which grouped request options an operation carries together with whether each group
74
+ * holds a required member. The grouped parameter stays optional only when nothing inside it is
75
+ * required, matching the generated `RequestConfig` type.
150
76
  */
151
- function isValidVarName(name) {
152
- if (!name || reservedWords.has(name)) return false;
153
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
77
+ function getRequestGroupOptionality(node) {
78
+ const groups = getRequestGroups(node);
79
+ const { path, query, header } = getOperationParameters(node);
80
+ const hasRequiredPath = path.some((param) => param.required);
81
+ const hasRequiredQuery = query.some((param) => param.required);
82
+ const hasRequiredHeader = header.some((param) => param.required);
83
+ return {
84
+ groups,
85
+ hasRequiredPath,
86
+ hasRequiredQuery,
87
+ hasRequiredHeader,
88
+ isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !groups.body
89
+ };
154
90
  }
155
- //#endregion
156
- //#region ../../internals/utils/src/urlPath.ts
157
91
  /**
158
- * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
159
- *
160
- * @example
161
- * const p = new URLPath('/pet/{petId}')
162
- * p.URL // '/pet/:petId'
163
- * p.template // '`/pet/${petId}`'
92
+ * Builds the grouped `{ path, query, body, headers }` parameter for a generated client
93
+ * function, typed from the operation's `Options` (minus `url`). Only the groups the
94
+ * operation actually has are destructured. The trailing `config` parameter carries the
95
+ * runtime `RequestConfig` overrides plus `client`.
164
96
  */
165
- var URLPath = class {
166
- /**
167
- * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
168
- */
169
- path;
170
- #options;
171
- constructor(path, options = {}) {
172
- this.path = path;
173
- this.#options = options;
174
- }
175
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` `/pet/:petId`.
176
- *
177
- * @example
178
- * ```ts
179
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
180
- * ```
181
- */
182
- get URL() {
183
- return this.toURLPath();
184
- }
185
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
186
- *
187
- * @example
188
- * ```ts
189
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
190
- * new URLPath('/pet/{petId}').isURL // false
191
- * ```
192
- */
193
- get isURL() {
194
- try {
195
- return !!new URL(this.path).href;
196
- } catch {
197
- return false;
198
- }
199
- }
200
- /**
201
- * Converts the OpenAPI path to a TypeScript template literal string.
202
- *
203
- * @example
204
- * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
205
- * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
206
- */
207
- get template() {
208
- return this.toTemplateString();
209
- }
210
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
211
- *
212
- * @example
213
- * ```ts
214
- * new URLPath('/pet/{petId}').object
215
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
216
- * ```
217
- */
218
- get object() {
219
- return this.toObject();
220
- }
221
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
222
- *
223
- * @example
224
- * ```ts
225
- * new URLPath('/pet/{petId}').params // { petId: 'petId' }
226
- * new URLPath('/pet').params // undefined
227
- * ```
228
- */
229
- get params() {
230
- return this.toParamsObject();
231
- }
232
- #transformParam(raw) {
233
- const param = isValidVarName(raw) ? raw : camelCase(raw);
234
- return this.#options.casing === "camelcase" ? camelCase(param) : param;
235
- }
236
- /**
237
- * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
238
- */
239
- #eachParam(fn) {
240
- for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
241
- const raw = match[1];
242
- fn(raw, this.#transformParam(raw));
243
- }
244
- }
245
- toObject({ type = "path", replacer, stringify } = {}) {
246
- const object = {
247
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
248
- params: this.toParamsObject()
249
- };
250
- if (stringify) {
251
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
252
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
253
- return `{ url: '${object.url}' }`;
254
- }
255
- return object;
256
- }
257
- /**
258
- * Converts the OpenAPI path to a TypeScript template literal string.
259
- * An optional `replacer` can transform each extracted parameter name before interpolation.
260
- *
261
- * @example
262
- * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
263
- */
264
- toTemplateString({ prefix = "", replacer } = {}) {
265
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
266
- if (i % 2 === 0) return part;
267
- const param = this.#transformParam(part);
268
- return `\${${replacer ? replacer(param) : param}}`;
269
- }).join("")}\``;
270
- }
271
- /**
272
- * Extracts all `{param}` segments from the path and returns them as a key-value map.
273
- * An optional `replacer` transforms each parameter name in both key and value positions.
274
- * Returns `undefined` when no path parameters are found.
275
- *
276
- * @example
277
- * ```ts
278
- * new URLPath('/pet/{petId}/tag/{tagId}').toParamsObject()
279
- * // { petId: 'petId', tagId: 'tagId' }
280
- * ```
281
- */
282
- toParamsObject(replacer) {
283
- const params = {};
284
- this.#eachParam((_raw, param) => {
285
- const key = replacer ? replacer(param) : param;
286
- params[key] = key;
287
- });
288
- return Object.keys(params).length > 0 ? params : void 0;
289
- }
290
- /** Converts the OpenAPI path to Express-style colon syntax.
291
- *
292
- * @example
293
- * ```ts
294
- * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
295
- * ```
296
- */
297
- toURLPath() {
298
- return this.path.replace(/\{([^}]+)\}/g, ":$1");
299
- }
300
- };
301
- //#endregion
302
- //#region ../../internals/shared/src/operation.ts
303
- function getOperationParameters(node, options = {}) {
304
- const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
97
+ function buildRequestParamsSignature(node, resolver, options = {}) {
98
+ const { isConfigurable = true } = options;
99
+ const { groups, isOptional } = getRequestGroupOptionality(node);
100
+ const names = [
101
+ "path",
102
+ "query",
103
+ "body",
104
+ "headers"
105
+ ].filter((key) => groups[key]);
106
+ return {
107
+ signature: [names.length > 0 ? `{ ${names.join(", ")} }: ${resolver.response.options(node)}${isOptional ? " = {}" : ""}` : null, isConfigurable ? `config: ${buildRequestConfigType(node)} = {}` : null].filter(Boolean).join(", "),
108
+ groups
109
+ };
110
+ }
111
+ function getOperationParameters(node) {
305
112
  return {
306
- path: params.filter((param) => param.in === "path"),
307
- query: params.filter((param) => param.in === "query"),
308
- header: params.filter((param) => param.in === "header"),
309
- cookie: params.filter((param) => param.in === "cookie")
113
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
114
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
115
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
116
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
310
117
  };
311
118
  }
312
119
  function getStatusCodeNumber(statusCode) {
313
120
  const code = Number(statusCode);
314
- return Number.isNaN(code) ? void 0 : code;
121
+ return Number.isNaN(code) ? null : code;
122
+ }
123
+ function isSuccessStatusCode(statusCode) {
124
+ const code = getStatusCodeNumber(statusCode);
125
+ return code !== null && code >= 200 && code < 300;
315
126
  }
316
127
  function isErrorStatusCode(statusCode) {
317
128
  const code = getStatusCodeNumber(statusCode);
318
- return code !== void 0 && code >= 400;
129
+ return code !== null && code >= 400;
130
+ }
131
+ function getSuccessResponses(responses) {
132
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
133
+ }
134
+ function getOperationSuccessResponses(node) {
135
+ return getSuccessResponses(node.responses);
136
+ }
137
+ function getPrimarySuccessResponse(node) {
138
+ return getOperationSuccessResponses(node)[0] ?? null;
319
139
  }
320
140
  function resolveErrorNames(node, resolver) {
321
- return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
141
+ return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.response.status(node, response.statusCode));
322
142
  }
323
143
  function resolveStatusCodeNames(node, resolver) {
324
- return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
144
+ return node.responses.map((response) => resolver.response.status(node, response.statusCode));
325
145
  }
146
+ const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
326
147
  function resolveOperationTypeNames(node, resolver, options = {}) {
327
- const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
148
+ const cacheKey = `${node.operationId}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${options.includeParams === false ? "noparams" : ""}\0${(options.exclude ?? []).join(",")}`;
149
+ let byResolver = typeNamesByResolver.get(resolver);
150
+ if (byResolver) {
151
+ const cached = byResolver.get(cacheKey);
152
+ if (cached) return cached;
153
+ } else {
154
+ byResolver = /* @__PURE__ */ new Map();
155
+ typeNamesByResolver.set(resolver, byResolver);
156
+ }
157
+ const { path, query, header } = getOperationParameters(node);
328
158
  const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
329
159
  const exclude = new Set(options.exclude ?? []);
330
- const paramNames = [
331
- ...path.map((param) => resolver.resolvePathParamsName(node, param)),
332
- ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
333
- ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
160
+ const paramNames = options.includeParams === false ? [] : [
161
+ ...path.map((param) => resolver.param.path(node, param)),
162
+ ...query.map((param) => resolver.param.query(node, param)),
163
+ ...header.map((param) => resolver.param.headers(node, param))
334
164
  ];
335
- const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0, resolver.resolveResponseName(node)];
336
- return (options.order === "body-response-first" ? [
165
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.response.body(node) : null, resolver.response.response(node)];
166
+ const result = (options.order === "body-response-first" ? [
337
167
  ...bodyAndResponseNames,
338
168
  ...paramNames,
339
169
  ...responseStatusNames
@@ -342,130 +172,165 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
342
172
  ...bodyAndResponseNames,
343
173
  ...responseStatusNames
344
174
  ]).filter((name) => Boolean(name) && !exclude.has(name));
175
+ byResolver.set(cacheKey, result);
176
+ return result;
177
+ }
178
+ //#endregion
179
+ //#region ../../internals/utils/src/casing.ts
180
+ /**
181
+ * Shared implementation for camelCase and PascalCase conversion.
182
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
183
+ * and capitalizes each word according to `pascal`.
184
+ *
185
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
186
+ */
187
+ function toCamelOrPascal(text, pascal) {
188
+ 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) => {
189
+ if (word.length > 1 && word === word.toUpperCase()) return word;
190
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
191
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
192
+ }
193
+ /**
194
+ * Converts `text` to camelCase.
195
+ *
196
+ * @example Word boundaries
197
+ * `camelCase('hello-world') // 'helloWorld'`
198
+ *
199
+ * @example With a prefix
200
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
201
+ */
202
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
203
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
204
+ }
205
+ //#endregion
206
+ //#region ../../internals/shared/src/group.ts
207
+ /**
208
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
209
+ * shared default naming so every plugin groups output consistently:
210
+ *
211
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
212
+ * - other groups use the camelCased group (`pet store` → `petStore`).
213
+ *
214
+ * A user-provided `group.name` always wins over the default namer, so callers stay in
215
+ * control of their output folders. Returns `null` when grouping is disabled, matching the
216
+ * per-plugin convention.
217
+ *
218
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * createGroupConfig(group) // shared across every plugin
223
+ * ```
224
+ */
225
+ function createGroupConfig(group) {
226
+ if (!group) return null;
227
+ const defaultName = (ctx) => {
228
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
229
+ return camelCase(ctx.group);
230
+ };
231
+ return {
232
+ ...group,
233
+ name: group.name ? group.name : defaultName
234
+ };
345
235
  }
346
236
  //#endregion
347
237
  //#region src/components/Request.tsx
348
- const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
349
- function Request({ baseURL = "", name, dataReturnType, resolver, node, paramsType, pathParamsType, paramsCasing }) {
350
- const paramsNode = _kubb_core.ast.createOperationParams(node, {
351
- paramsType,
352
- pathParamsType,
353
- paramsCasing,
354
- resolver,
355
- extraParams: [_kubb_core.ast.createFunctionParameter({
356
- name: "options",
357
- type: _kubb_core.ast.createParamsType({
358
- variant: "reference",
359
- name: "Partial<Cypress.RequestOptions>"
360
- }),
361
- default: "{}"
362
- })]
363
- });
364
- const paramsSignature = declarationPrinter.print(paramsNode) ?? "";
365
- const responseType = resolver.resolveResponseName(node);
366
- const returnType = dataReturnType === "data" ? `Cypress.Chainable<${responseType}>` : `Cypress.Chainable<Cypress.Response<${responseType}>>`;
367
- const casedPathParams = getOperationParameters(node, { paramsCasing }).path;
368
- const pathParamNameMap = new Map(casedPathParams.map((p) => [camelCase(p.name), p.name]));
369
- const urlTemplate = new URLPath(node.path, { casing: paramsCasing }).toTemplateString({
370
- prefix: baseURL,
371
- replacer: (param) => pathParamNameMap.get(camelCase(param)) ?? param
372
- });
238
+ function Request({ baseURL = "", name, resolver, node }) {
239
+ if (!kubb_kit.ast.isHttpOperationNode(node)) return null;
240
+ const { signature, groups } = buildRequestParamsSignature(node, resolver, { isConfigurable: false });
241
+ const paramsSignature = [signature, "options: Partial<Cypress.RequestOptions> = {}"].filter(Boolean).join(", ");
242
+ const responseType = resolver.response.response(node);
243
+ const returnType = `Cypress.Chainable<${responseType}>`;
244
+ const urlTemplate = kubb_kit.Url.toGroupedTemplateString(node.path, { prefix: baseURL });
373
245
  const requestOptions = [`method: '${node.method}'`, `url: ${urlTemplate}`];
374
- const queryParams = getOperationParameters(node).query;
375
- if (queryParams.length > 0) {
376
- const casedQueryParams = getOperationParameters(node, { paramsCasing }).query;
377
- if (casedQueryParams.some((p, i) => p.name !== queryParams[i].name)) {
378
- const pairs = queryParams.map((orig, i) => `${orig.name}: params.${casedQueryParams[i].name}`).join(", ");
379
- requestOptions.push(`qs: params ? { ${pairs} } : undefined`);
380
- } else requestOptions.push("qs: params");
381
- }
382
- const headerParams = getOperationParameters(node).header;
383
- if (headerParams.length > 0) {
384
- const casedHeaderParams = getOperationParameters(node, { paramsCasing }).header;
385
- if (casedHeaderParams.some((p, i) => p.name !== headerParams[i].name)) {
386
- const pairs = headerParams.map((orig, i) => `'${orig.name}': headers.${casedHeaderParams[i].name}`).join(", ");
387
- requestOptions.push(`headers: headers ? { ${pairs} } : undefined`);
388
- } else requestOptions.push("headers");
389
- }
390
- if (node.requestBody?.content?.[0]?.schema) requestOptions.push("body: data");
246
+ if (groups.query) requestOptions.push("qs: query");
247
+ if (groups.headers) requestOptions.push("headers");
248
+ if (groups.body) requestOptions.push("body");
391
249
  requestOptions.push("...options");
392
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
250
+ const requestCall = `return cy.request<${responseType}>({
251
+ ${requestOptions.join(",\n ")}
252
+ }).then((res) => res.body)`;
253
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Source, {
393
254
  name,
394
255
  isIndexable: true,
395
256
  isExportable: true,
396
- children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Function, {
257
+ children: /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.Function, {
397
258
  name,
398
259
  export: true,
399
260
  params: paramsSignature,
400
261
  returnType,
401
- children: dataReturnType === "data" ? `return cy.request<${responseType}>({
402
- ${requestOptions.join(",\n ")}
403
- }).then((res) => res.body)` : `return cy.request<${responseType}>({
404
- ${requestOptions.join(",\n ")}
405
- })`
262
+ children: requestCall
406
263
  })
407
264
  });
408
265
  }
409
266
  //#endregion
410
267
  //#region src/generators/cypressGenerator.tsx
411
- const cypressGenerator = (0, _kubb_core.defineGenerator)({
268
+ /**
269
+ * Built-in generator for `@kubb/plugin-cypress`. Emits one typed
270
+ * `cy.request()` wrapper per OpenAPI operation, ready to call inside Cypress
271
+ * test specs and custom commands.
272
+ */
273
+ const cypressGenerator = (0, kubb_kit.defineGenerator)({
412
274
  name: "cypress",
413
- renderer: _kubb_renderer_jsx.jsxRenderer,
275
+ renderer: kubb_jsx.jsxRenderer,
414
276
  operation(node, ctx) {
415
- const { adapter, config, resolver, driver, root } = ctx;
416
- const { output, baseURL, dataReturnType, paramsCasing, paramsType, pathParamsType, group } = ctx.options;
277
+ if (!kubb_kit.ast.isHttpOperationNode(node)) return null;
278
+ const { config, resolver, driver, root } = ctx;
279
+ const { output, baseURL, group } = ctx.options;
417
280
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
418
281
  if (!pluginTs) return null;
419
282
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
420
- const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing });
283
+ const importedTypeNames = [tsResolver.response.options(node), ...resolveOperationTypeNames(node, tsResolver, { includeParams: false })];
421
284
  const meta = {
422
- name: resolver.resolveName(node.operationId),
423
- file: resolver.resolveFile({
285
+ name: resolver.name(node.operationId),
286
+ file: resolver.file({
424
287
  name: node.operationId,
425
288
  extname: ".ts",
426
289
  tag: node.tags[0] ?? "default",
427
- path: node.path
428
- }, {
290
+ path: node.path,
429
291
  root,
430
292
  output,
431
- group
293
+ group: group ?? void 0
432
294
  }),
433
- fileTs: tsResolver.resolveFile({
295
+ fileTs: tsResolver.file({
434
296
  name: node.operationId,
435
297
  extname: ".ts",
436
298
  tag: node.tags[0] ?? "default",
437
- path: node.path
438
- }, {
299
+ path: node.path,
439
300
  root,
440
301
  output: pluginTs.options?.output ?? output,
441
- group: pluginTs.options?.group
302
+ group: pluginTs.options?.group ?? void 0
442
303
  })
443
304
  };
444
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
305
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.File, {
445
306
  baseName: meta.file.baseName,
446
307
  path: meta.file.path,
447
308
  meta: meta.file.meta,
448
- banner: resolver.resolveBanner(adapter.inputNode, {
309
+ banner: resolver.default.banner(ctx.meta, {
449
310
  output,
450
- config
311
+ config,
312
+ file: {
313
+ path: meta.file.path,
314
+ baseName: meta.file.baseName
315
+ }
451
316
  }),
452
- footer: resolver.resolveFooter(adapter.inputNode, {
317
+ footer: resolver.default.footer(ctx.meta, {
453
318
  output,
454
- config
319
+ config,
320
+ file: {
321
+ path: meta.file.path,
322
+ baseName: meta.file.baseName
323
+ }
455
324
  }),
456
- children: [meta.fileTs && importedTypeNames.length > 0 && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
325
+ children: [importedTypeNames.length > 0 && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
457
326
  name: importedTypeNames,
458
327
  root: meta.file.path,
459
328
  path: meta.fileTs.path,
460
329
  isTypeOnly: true
461
- }), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Request, {
330
+ }), /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(Request, {
462
331
  name: meta.name,
463
332
  node,
464
333
  resolver: tsResolver,
465
- dataReturnType,
466
- paramsCasing,
467
- paramsType,
468
- pathParamsType,
469
334
  baseURL
470
335
  })]
471
336
  });
@@ -474,87 +339,72 @@ const cypressGenerator = (0, _kubb_core.defineGenerator)({
474
339
  //#endregion
475
340
  //#region src/resolvers/resolverCypress.ts
476
341
  /**
477
- * Naming convention resolver for Cypress plugin.
342
+ * Default resolver used by `@kubb/plugin-cypress`. Decides the names and file
343
+ * paths for every generated `cy.request()` wrapper. Functions and files use
344
+ * camelCase, matching the convention from `@kubb/plugin-axios` and `@kubb/plugin-fetch`.
478
345
  *
479
- * Provides default naming helpers using camelCase for functions and file paths.
346
+ * @example Resolve a helper name
347
+ * ```ts
348
+ * import { resolverCypress } from '@kubb/plugin-cypress'
480
349
  *
481
- * @example
482
- * `resolverCypress.default('list pets', 'function') // → 'listPets'`
350
+ * resolverCypress.name('list pets') // 'listPets'
351
+ * ```
483
352
  */
484
- const resolverCypress = (0, _kubb_core.defineResolver)(() => ({
485
- name: "default",
486
- pluginName: "plugin-cypress",
487
- default(name, type) {
488
- return camelCase(name, { isFile: type === "file" });
489
- },
490
- resolveName(name) {
491
- return this.default(name, "function");
492
- },
493
- resolvePathName(name, type) {
494
- return this.default(name, type);
495
- }
496
- }));
353
+ const resolverCypress = (0, kubb_kit.createResolver)({ pluginName: "plugin-cypress" });
497
354
  //#endregion
498
355
  //#region src/plugin.ts
499
356
  /**
500
- * Canonical plugin name for `@kubb/plugin-cypress`, used to identify the plugin
501
- * in driver lookups and warnings.
357
+ * Canonical plugin name for `@kubb/plugin-cypress`. Used for driver lookups and
358
+ * cross-plugin dependency references.
502
359
  */
503
360
  const pluginCypressName = "plugin-cypress";
504
361
  /**
505
- * The `@kubb/plugin-cypress` plugin factory.
506
- *
507
- * Generates Cypress `cy.request()` test functions from an OpenAPI/AST `RootNode`.
508
- * Walks operations, delegates rendering to the active generators,
509
- * and writes barrel files based on `output.barrelType`.
362
+ * Generates one typed `cy.request()` wrapper per OpenAPI operation. Each helper
363
+ * has typed path params, body, query, and a typed response, so failing API
364
+ * calls in Cypress show up at compile time instead of inside the test runner.
510
365
  *
511
366
  * @example
512
367
  * ```ts
513
- * import pluginCypress from '@kubb/plugin-cypress'
368
+ * import { defineConfig } from 'kubb/config'
369
+ * import { pluginTs } from '@kubb/plugin-ts'
370
+ * import { pluginCypress } from '@kubb/plugin-cypress'
514
371
  *
515
372
  * export default defineConfig({
516
- * plugins: [pluginCypress({ output: { path: 'cypress' } })],
373
+ * input: './petStore.yaml',
374
+ * output: { path: './src/gen' },
375
+ * plugins: [
376
+ * pluginTs(),
377
+ * pluginCypress({
378
+ * output: { path: './cypress' },
379
+ * }),
380
+ * ],
517
381
  * })
518
382
  * ```
519
383
  */
520
- const pluginCypress = (0, _kubb_core.definePlugin)((options) => {
384
+ const pluginCypress = (0, kubb_kit.definePlugin)((options) => {
521
385
  const { output = {
522
386
  path: "cypress",
523
- barrelType: "named"
524
- }, group, exclude = [], include, override = [], dataReturnType = "data", baseURL, paramsCasing, paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
525
- const groupConfig = group ? {
526
- ...group,
527
- name: group.name ? group.name : (ctx) => {
528
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
529
- return `${camelCase(ctx.group)}Requests`;
530
- }
531
- } : void 0;
387
+ barrel: { type: "named" }
388
+ }, group, exclude = [], include, override = [], baseURL, resolver: userResolver, macros: userMacros } = options;
389
+ const groupConfig = createGroupConfig(group);
532
390
  return {
533
391
  name: pluginCypressName,
534
392
  options,
535
393
  dependencies: [_kubb_plugin_ts.pluginTsName],
536
394
  hooks: { "kubb:plugin:setup"(ctx) {
537
- const resolver = userResolver ? {
538
- ...resolverCypress,
539
- ...userResolver
540
- } : resolverCypress;
395
+ const resolver = userResolver ? kubb_kit.Resolver.merge(resolverCypress, userResolver) : resolverCypress;
541
396
  ctx.setOptions({
542
397
  output,
543
398
  exclude,
544
399
  include,
545
400
  override,
546
- dataReturnType,
547
401
  group: groupConfig,
548
402
  baseURL,
549
- paramsCasing,
550
- paramsType,
551
- pathParamsType,
552
403
  resolver
553
404
  });
554
405
  ctx.setResolver(resolver);
555
- if (userTransformer) ctx.setTransformer(userTransformer);
406
+ if (userMacros?.length) ctx.setMacros(userMacros);
556
407
  ctx.addGenerator(cypressGenerator);
557
- for (const gen of userGenerators) ctx.addGenerator(gen);
558
408
  } }
559
409
  };
560
410
  });