@kubb/plugin-cypress 5.0.0-beta.4 → 5.0.0-beta.56

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 CHANGED
@@ -1,19 +1,16 @@
1
1
  <div align="center">
2
- <h1>Plugin Cypress</h1>
3
2
  <a href="https://kubb.dev" target="_blank" rel="noopener noreferrer">
4
- <img width="180" src="https://raw.githubusercontent.com/kubb-labs/kubb/main/assets/logo.png" alt="Kubb logo">
3
+ <img src="https://kubb.dev/og.png" alt="Kubb banner">
5
4
  </a>
6
5
 
7
6
  [![npm version][npm-version-src]][npm-version-href]
8
7
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
9
- [![Coverage][coverage-src]][coverage-href]
8
+ [![Stars][stars-src]][stars-href]
10
9
  [![License][license-src]][license-href]
11
- [![Sponsors][sponsors-src]][sponsors-href]
10
+ [![Node][node-src]][node-href]
12
11
 
13
12
  <h4>
14
- <a href="https://codesandbox.io/s/github/kubb-labs/kubb/tree/main//examples/typescript" target="_blank">View Demo</a>
15
- <span> · </span>
16
- <a href="https://kubb.dev/" target="_blank">Documentation</a>
13
+ <a href="https://kubb.dev/plugins/cypress" target="_blank">Documentation</a>
17
14
  <span> · </span>
18
15
  <a href="https://github.com/kubb-labs/kubb/issues/" target="_blank">Report Bug</a>
19
16
  <span> · </span>
@@ -21,11 +18,31 @@
21
18
  </h4>
22
19
  </div>
23
20
 
24
- Swagger integration to create Cypress requests commands.
21
+ <br />
22
+
23
+ # @kubb/plugin-cypress
24
+
25
+ ### Generate Cypress commands from OpenAPI
26
+
27
+ `@kubb/plugin-cypress` generates Cypress request commands from your OpenAPI specification. Each operation in the spec becomes a typed Cypress command, ready to use in your test files.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ bun add @kubb/plugin-cypress
33
+ # or
34
+ pnpm add @kubb/plugin-cypress
35
+ # or
36
+ npm install @kubb/plugin-cypress
37
+ ```
38
+
39
+ ## Documentation
40
+
41
+ See the [full documentation](https://kubb.dev/plugins/cypress) for configuration options and examples.
25
42
 
26
43
  ## Supporting Kubb
27
44
 
28
- Kubb uses an MIT-licensed open source project with its ongoing development made possible entirely by the support of Sponsors. If you would like to become a sponsor, please consider:
45
+ Kubb is an open source project, and its development is funded entirely by sponsors. If you would like to become a sponsor, please consider:
29
46
 
30
47
  - [Become a Sponsor on GitHub](https://github.com/sponsors/stijnvanhulle)
31
48
 
@@ -35,19 +52,19 @@ Kubb uses an MIT-licensed open source project with its ongoing development made
35
52
  </a>
36
53
  </p>
37
54
 
55
+ ## License
56
+
57
+ [MIT](https://github.com/kubb-labs/plugins/blob/main/LICENSE)
58
+
38
59
  <!-- Badges -->
39
60
 
40
- [npm-version-src]: https://img.shields.io/npm/v/@kubb/plugin-cypress?flat&colorA=18181B&colorB=f58517
41
- [npm-version-href]: https://npmjs.com/package/@kubb/plugin-cypress
42
- [npm-downloads-src]: https://img.shields.io/npm/dm/@kubb/plugin-cypress?flat&colorA=18181B&colorB=f58517
43
- [npm-downloads-href]: https://npmjs.com/package/@kubb/plugin-cypress
44
- [license-src]: https://img.shields.io/github/license/kubb-labs/kubb.svg?flat&colorA=18181B&colorB=f58517
61
+ [npm-version-src]: https://shieldcn.dev/npm/v/@kubb/plugin-cypress.svg?variant=secondary&size=xs&theme=zinc&mode=dark
62
+ [npm-version-href]: https://npmx.dev/package/@kubb/plugin-cypress
63
+ [npm-downloads-src]: https://shieldcn.dev/npm/dm/@kubb/plugin-cypress.svg?variant=secondary&size=xs&theme=zinc&mode=dark
64
+ [npm-downloads-href]: https://npmx.dev/package/@kubb/plugin-cypress
65
+ [stars-src]: https://shieldcn.dev/github/stars/kubb-labs/kubb.svg?variant=secondary&size=xs&theme=zinc&mode=dark
66
+ [stars-href]: https://github.com/kubb-labs/kubb
67
+ [license-src]: https://shieldcn.dev/npm/license/@kubb/plugin-cypress.svg?variant=secondary&size=xs&theme=zinc
45
68
  [license-href]: https://github.com/kubb-labs/kubb/blob/main/LICENSE
46
- [build-src]: https://img.shields.io/github/actions/workflow/status/kubb-labs/kubb/ci.yaml?style=flat&colorA=18181B&colorB=f58517
47
- [build-href]: https://www.npmjs.com/package/@kubb/plugin-cypress
48
- [minified-src]: https://img.shields.io/bundlephobia/min/@kubb/plugin-cypress?style=flat&colorA=18181B&colorB=f58517
49
- [minified-href]: https://www.npmjs.com/package/@kubb/plugin-cypress
50
- [coverage-src]: https://img.shields.io/codecov/c/github/kubb-labs/kubb?style=flat&colorA=18181B&colorB=f58517
51
- [coverage-href]: https://www.npmjs.com/package/@kubb/plugin-cypress
52
- [sponsors-src]: https://img.shields.io/github/sponsors/stijnvanhulle?style=flat&colorA=18181B&colorB=f58517
53
- [sponsors-href]: https://github.com/sponsors/stijnvanhulle/
69
+ [node-src]: https://shieldcn.dev/npm/node/@kubb/plugin-cypress.svg?variant=secondary&size=xs&theme=zinc&mode=dark
70
+ [node-href]: https://npmx.dev/package/@kubb/plugin-cypress
package/dist/index.cjs CHANGED
@@ -18,36 +18,45 @@ let _kubb_renderer_jsx_jsx_runtime = require("@kubb/renderer-jsx/jsx-runtime");
18
18
  function toCamelOrPascal(text, pascal) {
19
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
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);
21
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
23
22
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
24
23
  }
25
24
  /**
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.
25
+ * Converts `text` to camelCase.
26
+ *
27
+ * @example Word boundaries
28
+ * `camelCase('hello-world') // 'helloWorld'`
29
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
+ * @example With a prefix
31
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
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 camelCase(text, { prefix = "", suffix = "" } = {}) {
34
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
36
35
  }
36
+ //#endregion
37
+ //#region ../../internals/utils/src/fs.ts
37
38
  /**
38
- * Converts `text` to camelCase.
39
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
39
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
40
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
41
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
40
42
  *
41
- * @example
42
- * camelCase('hello-world') // 'helloWorld'
43
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
43
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
44
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
45
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
46
+ * absolute path, letting generated files escape the configured output directory.
47
+ *
48
+ * @example Nested path from a dotted name
49
+ * `toFilePath('pet.petId') // 'pet/petId'`
50
+ *
51
+ * @example PascalCase the final segment
52
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
53
+ *
54
+ * @example Suffix applied to the final segment only
55
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
44
56
  */
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);
57
+ function toFilePath(name, caseLast = camelCase) {
58
+ const parts = name.split(/\.(?=[a-zA-Z])/);
59
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
51
60
  }
52
61
  //#endregion
53
62
  //#region ../../internals/utils/src/reserved.ts
@@ -153,99 +162,80 @@ function isValidVarName(name) {
153
162
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
154
163
  }
155
164
  //#endregion
156
- //#region ../../internals/utils/src/urlPath.ts
165
+ //#region ../../internals/utils/src/url.ts
166
+ function transformParam(raw, casing) {
167
+ const param = isValidVarName(raw) ? raw : camelCase(raw);
168
+ return casing === "camelcase" ? camelCase(param) : param;
169
+ }
170
+ function toParamsObject(path, { replacer, casing } = {}) {
171
+ const params = {};
172
+ for (const match of path.matchAll(/\{([^}]+)\}/g)) {
173
+ const param = transformParam(match[1], casing);
174
+ const key = replacer ? replacer(param) : param;
175
+ params[key] = key;
176
+ }
177
+ return Object.keys(params).length > 0 ? params : null;
178
+ }
157
179
  /**
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}`'
180
+ * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
164
181
  */
165
- var URLPath = class {
182
+ var Url = class Url {
166
183
  /**
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`.
184
+ * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
176
185
  *
177
186
  * @example
178
- * ```ts
179
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
180
- * ```
187
+ * Url.canParse('https://petstore.swagger.io/v2') // true
188
+ * Url.canParse('/pet/{petId}') // false
181
189
  */
182
- get URL() {
183
- return this.toURLPath();
190
+ static canParse(url, base) {
191
+ return URL.canParse(url, base);
184
192
  }
185
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
193
+ /**
194
+ * Converts an OpenAPI/Swagger path to Express-style colon syntax.
186
195
  *
187
196
  * @example
188
- * ```ts
189
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
190
- * new URLPath('/pet/{petId}').isURL // false
191
- * ```
197
+ * Url.toPath('/pet/{petId}') // '/pet/:petId'
192
198
  */
193
- get isURL() {
194
- try {
195
- return !!new URL(this.path).href;
196
- } catch {
197
- return false;
198
- }
199
+ static toPath(path) {
200
+ return path.replace(/\{([^}]+)\}/g, ":$1");
199
201
  }
200
202
  /**
201
- * Converts the OpenAPI path to a TypeScript template literal string.
203
+ * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
204
+ * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
205
+ * and `casing` controls parameter identifier casing.
202
206
  *
203
207
  * @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.
208
+ * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
211
209
  *
212
210
  * @example
213
- * ```ts
214
- * new URLPath('/pet/{petId}').object
215
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
216
- * ```
211
+ * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
217
212
  */
218
- get object() {
219
- return this.toObject();
213
+ static toTemplateString(path, { prefix, replacer, casing } = {}) {
214
+ const result = path.split(/\{([^}]+)\}/).map((part, i) => {
215
+ if (i % 2 === 0) return part;
216
+ const param = transformParam(part, casing);
217
+ return `\${${replacer ? replacer(param) : param}}`;
218
+ }).join("");
219
+ return `\`${prefix ?? ""}${result}\``;
220
220
  }
221
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
221
+ /**
222
+ * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
223
+ * expression when `stringify` is set.
222
224
  *
223
225
  * @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.getParams();
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.
226
+ * Url.toObject('/pet/{petId}')
227
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
238
228
  */
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 } = {}) {
229
+ static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
246
230
  const object = {
247
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
248
- params: this.getParams()
231
+ url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
232
+ replacer,
233
+ casing
234
+ }),
235
+ params: toParamsObject(path, {
236
+ replacer,
237
+ casing
238
+ })
249
239
  };
250
240
  if (stringify) {
251
241
  if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
@@ -254,54 +244,100 @@ var URLPath = class {
254
244
  }
255
245
  return object;
256
246
  }
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}').getParams()
279
- * // { petId: 'petId', tagId: 'tagId' }
280
- * ```
281
- */
282
- getParams(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
247
  };
301
248
  //#endregion
249
+ //#region ../../internals/shared/src/operation.ts
250
+ function getOperationParameters(node, options = {}) {
251
+ const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
252
+ return {
253
+ path: params.filter((param) => param.in === "path"),
254
+ query: params.filter((param) => param.in === "query"),
255
+ header: params.filter((param) => param.in === "header"),
256
+ cookie: params.filter((param) => param.in === "cookie")
257
+ };
258
+ }
259
+ function getStatusCodeNumber(statusCode) {
260
+ const code = Number(statusCode);
261
+ return Number.isNaN(code) ? null : code;
262
+ }
263
+ function isErrorStatusCode(statusCode) {
264
+ const code = getStatusCodeNumber(statusCode);
265
+ return code !== null && code >= 400;
266
+ }
267
+ function resolveErrorNames(node, resolver) {
268
+ return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
269
+ }
270
+ function resolveStatusCodeNames(node, resolver) {
271
+ return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
272
+ }
273
+ const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
274
+ function resolveOperationTypeNames(node, resolver, options = {}) {
275
+ const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
276
+ let byResolver = typeNamesByResolver.get(resolver);
277
+ if (byResolver) {
278
+ const cached = byResolver.get(cacheKey);
279
+ if (cached) return cached;
280
+ } else {
281
+ byResolver = /* @__PURE__ */ new Map();
282
+ typeNamesByResolver.set(resolver, byResolver);
283
+ }
284
+ const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
285
+ const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
286
+ const exclude = new Set(options.exclude ?? []);
287
+ const paramNames = [
288
+ ...path.map((param) => resolver.resolvePathParamsName(node, param)),
289
+ ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
290
+ ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
291
+ ];
292
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
293
+ const result = (options.order === "body-response-first" ? [
294
+ ...bodyAndResponseNames,
295
+ ...paramNames,
296
+ ...responseStatusNames
297
+ ] : [
298
+ ...paramNames,
299
+ ...bodyAndResponseNames,
300
+ ...responseStatusNames
301
+ ]).filter((name) => Boolean(name) && !exclude.has(name));
302
+ byResolver.set(cacheKey, result);
303
+ return result;
304
+ }
305
+ //#endregion
306
+ //#region ../../internals/shared/src/group.ts
307
+ /**
308
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
309
+ * shared default naming so every plugin groups output consistently:
310
+ *
311
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
312
+ * - other groups use the camelCased group (`pet store` → `petStore`).
313
+ *
314
+ * A user-provided `group.name` always wins over the default namer, so callers stay in
315
+ * control of their output folders. Returns `null` when grouping is disabled, matching the
316
+ * per-plugin convention.
317
+ *
318
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * createGroupConfig(group) // shared across every plugin
323
+ * ```
324
+ */
325
+ function createGroupConfig(group) {
326
+ if (!group) return null;
327
+ const defaultName = (ctx) => {
328
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
329
+ return camelCase(ctx.group);
330
+ };
331
+ return {
332
+ ...group,
333
+ name: group.name ? group.name : defaultName
334
+ };
335
+ }
336
+ //#endregion
302
337
  //#region src/components/Request.tsx
303
338
  const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
304
339
  function Request({ baseURL = "", name, dataReturnType, resolver, node, paramsType, pathParamsType, paramsCasing }) {
340
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
305
341
  const paramsNode = _kubb_core.ast.createOperationParams(node, {
306
342
  paramsType,
307
343
  pathParamsType,
@@ -319,24 +355,25 @@ function Request({ baseURL = "", name, dataReturnType, resolver, node, paramsTyp
319
355
  const paramsSignature = declarationPrinter.print(paramsNode) ?? "";
320
356
  const responseType = resolver.resolveResponseName(node);
321
357
  const returnType = dataReturnType === "data" ? `Cypress.Chainable<${responseType}>` : `Cypress.Chainable<Cypress.Response<${responseType}>>`;
322
- const casedPathParams = _kubb_core.ast.caseParams(node.parameters.filter((p) => p.in === "path"), paramsCasing);
358
+ const casedPathParams = getOperationParameters(node, { paramsCasing }).path;
323
359
  const pathParamNameMap = new Map(casedPathParams.map((p) => [camelCase(p.name), p.name]));
324
- const urlTemplate = new URLPath(node.path, { casing: paramsCasing }).toTemplateString({
360
+ const urlTemplate = Url.toTemplateString(node.path, {
325
361
  prefix: baseURL,
326
- replacer: (param) => pathParamNameMap.get(camelCase(param)) ?? param
362
+ replacer: (param) => pathParamNameMap.get(camelCase(param)) ?? param,
363
+ casing: paramsCasing
327
364
  });
328
365
  const requestOptions = [`method: '${node.method}'`, `url: ${urlTemplate}`];
329
- const queryParams = node.parameters.filter((p) => p.in === "query");
366
+ const queryParams = getOperationParameters(node).query;
330
367
  if (queryParams.length > 0) {
331
- const casedQueryParams = _kubb_core.ast.caseParams(queryParams, paramsCasing);
368
+ const casedQueryParams = getOperationParameters(node, { paramsCasing }).query;
332
369
  if (casedQueryParams.some((p, i) => p.name !== queryParams[i].name)) {
333
370
  const pairs = queryParams.map((orig, i) => `${orig.name}: params.${casedQueryParams[i].name}`).join(", ");
334
371
  requestOptions.push(`qs: params ? { ${pairs} } : undefined`);
335
372
  } else requestOptions.push("qs: params");
336
373
  }
337
- const headerParams = node.parameters.filter((p) => p.in === "header");
374
+ const headerParams = getOperationParameters(node).header;
338
375
  if (headerParams.length > 0) {
339
- const casedHeaderParams = _kubb_core.ast.caseParams(headerParams, paramsCasing);
376
+ const casedHeaderParams = getOperationParameters(node, { paramsCasing }).header;
340
377
  if (casedHeaderParams.some((p, i) => p.name !== headerParams[i].name)) {
341
378
  const pairs = headerParams.map((orig, i) => `'${orig.name}': headers.${casedHeaderParams[i].name}`).join(", ");
342
379
  requestOptions.push(`headers: headers ? { ${pairs} } : undefined`);
@@ -363,26 +400,22 @@ function Request({ baseURL = "", name, dataReturnType, resolver, node, paramsTyp
363
400
  }
364
401
  //#endregion
365
402
  //#region src/generators/cypressGenerator.tsx
403
+ /**
404
+ * Built-in generator for `@kubb/plugin-cypress`. Emits one typed
405
+ * `cy.request()` wrapper per OpenAPI operation, ready to call inside Cypress
406
+ * test specs and custom commands.
407
+ */
366
408
  const cypressGenerator = (0, _kubb_core.defineGenerator)({
367
409
  name: "cypress",
368
410
  renderer: _kubb_renderer_jsx.jsxRenderer,
369
411
  operation(node, ctx) {
370
- const { adapter, config, resolver, driver, root } = ctx;
412
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
413
+ const { config, resolver, driver, root } = ctx;
371
414
  const { output, baseURL, dataReturnType, paramsCasing, paramsType, pathParamsType, group } = ctx.options;
372
415
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
373
416
  if (!pluginTs) return null;
374
417
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
375
- const casedParams = _kubb_core.ast.caseParams(node.parameters, paramsCasing);
376
- const pathParams = casedParams.filter((p) => p.in === "path");
377
- const queryParams = casedParams.filter((p) => p.in === "query");
378
- const headerParams = casedParams.filter((p) => p.in === "header");
379
- const importedTypeNames = [
380
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
381
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
382
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
383
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
384
- tsResolver.resolveResponseName(node)
385
- ].filter(Boolean);
418
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing });
386
419
  const meta = {
387
420
  name: resolver.resolveName(node.operationId),
388
421
  file: resolver.resolveFile({
@@ -393,7 +426,7 @@ const cypressGenerator = (0, _kubb_core.defineGenerator)({
393
426
  }, {
394
427
  root,
395
428
  output,
396
- group
429
+ group: group ?? void 0
397
430
  }),
398
431
  fileTs: tsResolver.resolveFile({
399
432
  name: node.operationId,
@@ -403,23 +436,31 @@ const cypressGenerator = (0, _kubb_core.defineGenerator)({
403
436
  }, {
404
437
  root,
405
438
  output: pluginTs.options?.output ?? output,
406
- group: pluginTs.options?.group
439
+ group: pluginTs.options?.group ?? void 0
407
440
  })
408
441
  };
409
442
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
410
443
  baseName: meta.file.baseName,
411
444
  path: meta.file.path,
412
445
  meta: meta.file.meta,
413
- banner: resolver.resolveBanner(adapter.inputNode, {
446
+ banner: resolver.resolveBanner(ctx.meta, {
414
447
  output,
415
- config
448
+ config,
449
+ file: {
450
+ path: meta.file.path,
451
+ baseName: meta.file.baseName
452
+ }
416
453
  }),
417
- footer: resolver.resolveFooter(adapter.inputNode, {
454
+ footer: resolver.resolveFooter(ctx.meta, {
418
455
  output,
419
- config
456
+ config,
457
+ file: {
458
+ path: meta.file.path,
459
+ baseName: meta.file.baseName
460
+ }
420
461
  }),
421
462
  children: [meta.fileTs && importedTypeNames.length > 0 && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
422
- name: Array.from(new Set(importedTypeNames)),
463
+ name: importedTypeNames,
423
464
  root: meta.file.path,
424
465
  path: meta.fileTs.path,
425
466
  isTypeOnly: true
@@ -439,58 +480,66 @@ const cypressGenerator = (0, _kubb_core.defineGenerator)({
439
480
  //#endregion
440
481
  //#region src/resolvers/resolverCypress.ts
441
482
  /**
442
- * Naming convention resolver for Cypress plugin.
483
+ * Default resolver used by `@kubb/plugin-cypress`. Decides the names and file
484
+ * paths for every generated `cy.request()` wrapper. Functions and files use
485
+ * camelCase, matching the convention from `@kubb/plugin-client`.
443
486
  *
444
- * Provides default naming helpers using camelCase for functions and file paths.
487
+ * @example Resolve a helper name
488
+ * ```ts
489
+ * import { resolverCypress } from '@kubb/plugin-cypress'
445
490
  *
446
- * @example
447
- * `resolverCypress.default('list pets', 'function') // → 'listPets'`
491
+ * resolverCypress.default('list pets', 'function') // 'listPets'
492
+ * ```
448
493
  */
449
- const resolverCypress = (0, _kubb_core.defineResolver)((ctx) => ({
494
+ const resolverCypress = (0, _kubb_core.defineResolver)(() => ({
450
495
  name: "default",
451
496
  pluginName: "plugin-cypress",
452
497
  default(name, type) {
453
- return camelCase(name, { isFile: type === "file" });
498
+ return type === "file" ? toFilePath(name) : camelCase(name);
454
499
  },
455
500
  resolveName(name) {
456
- return ctx.default(name, "function");
501
+ return this.default(name, "function");
502
+ },
503
+ resolvePathName(name, type) {
504
+ return this.default(name, type);
457
505
  }
458
506
  }));
459
507
  //#endregion
460
508
  //#region src/plugin.ts
461
509
  /**
462
- * Canonical plugin name for `@kubb/plugin-cypress`, used to identify the plugin
463
- * in driver lookups and warnings.
510
+ * Canonical plugin name for `@kubb/plugin-cypress`. Used for driver lookups and
511
+ * cross-plugin dependency references.
464
512
  */
465
513
  const pluginCypressName = "plugin-cypress";
466
514
  /**
467
- * The `@kubb/plugin-cypress` plugin factory.
468
- *
469
- * Generates Cypress `cy.request()` test functions from an OpenAPI/AST `RootNode`.
470
- * Walks operations, delegates rendering to the active generators,
471
- * and writes barrel files based on `output.barrelType`.
515
+ * Generates one typed `cy.request()` wrapper per OpenAPI operation. Each helper
516
+ * has typed path params, body, query, and a typed response, so failing API
517
+ * calls in Cypress show up at compile time instead of inside the test runner.
472
518
  *
473
519
  * @example
474
520
  * ```ts
475
- * import pluginCypress from '@kubb/plugin-cypress'
521
+ * import { defineConfig } from 'kubb'
522
+ * import { pluginTs } from '@kubb/plugin-ts'
523
+ * import { pluginCypress } from '@kubb/plugin-cypress'
476
524
  *
477
525
  * export default defineConfig({
478
- * plugins: [pluginCypress({ output: { path: 'cypress' } })],
526
+ * input: { path: './petStore.yaml' },
527
+ * output: { path: './src/gen' },
528
+ * plugins: [
529
+ * pluginTs(),
530
+ * pluginCypress({
531
+ * output: { path: './cypress' },
532
+ * }),
533
+ * ],
479
534
  * })
480
535
  * ```
481
536
  */
482
537
  const pluginCypress = (0, _kubb_core.definePlugin)((options) => {
483
538
  const { output = {
484
539
  path: "cypress",
485
- barrelType: "named"
540
+ barrel: { type: "named" }
486
541
  }, group, exclude = [], include, override = [], dataReturnType = "data", baseURL, paramsCasing, paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
487
- const groupConfig = group ? {
488
- ...group,
489
- name: group.name ? group.name : (ctx) => {
490
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
491
- return `${camelCase(ctx.group)}Requests`;
492
- }
493
- } : void 0;
542
+ const groupConfig = createGroupConfig(group);
494
543
  return {
495
544
  name: pluginCypressName,
496
545
  options,