@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 +39 -22
- package/dist/index.cjs +234 -185
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +66 -43
- package/dist/index.js +235 -186
- package/dist/index.js.map +1 -1
- package/package.json +11 -23
- package/src/components/Request.tsx +12 -13
- package/src/generators/cypressGenerator.tsx +17 -21
- package/src/plugin.ts +20 -25
- package/src/resolvers/resolverCypress.ts +15 -8
- package/src/types.ts +33 -25
- package/extension.yaml +0 -441
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
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
|
|
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
|
-
[![
|
|
8
|
+
[![Stars][stars-src]][stars-href]
|
|
10
9
|
[![License][license-src]][license-href]
|
|
11
|
-
[![
|
|
10
|
+
[![Node][node-src]][node-href]
|
|
12
11
|
|
|
13
12
|
<h4>
|
|
14
|
-
<a href="https://
|
|
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
|
-
|
|
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
|
|
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://
|
|
41
|
-
[npm-version-href]: https://
|
|
42
|
-
[npm-downloads-src]: https://
|
|
43
|
-
[npm-downloads-href]: https://
|
|
44
|
-
[
|
|
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
|
-
[
|
|
47
|
-
[
|
|
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
|
-
|
|
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
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* Converts `text` to camelCase.
|
|
26
|
+
*
|
|
27
|
+
* @example Word boundaries
|
|
28
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* @example With a prefix
|
|
31
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
32
32
|
*/
|
|
33
|
-
function
|
|
34
|
-
|
|
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
|
-
*
|
|
39
|
-
*
|
|
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
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
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/
|
|
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
|
-
*
|
|
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
|
|
182
|
+
var Url = class Url {
|
|
166
183
|
/**
|
|
167
|
-
*
|
|
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
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* ```
|
|
187
|
+
* Url.canParse('https://petstore.swagger.io/v2') // true
|
|
188
|
+
* Url.canParse('/pet/{petId}') // false
|
|
181
189
|
*/
|
|
182
|
-
|
|
183
|
-
return
|
|
190
|
+
static canParse(url, base) {
|
|
191
|
+
return URL.canParse(url, base);
|
|
184
192
|
}
|
|
185
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* Converts an OpenAPI/Swagger path to Express-style colon syntax.
|
|
186
195
|
*
|
|
187
196
|
* @example
|
|
188
|
-
*
|
|
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
|
-
|
|
194
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
225
|
-
*
|
|
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
|
-
|
|
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" ?
|
|
248
|
-
|
|
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 =
|
|
358
|
+
const casedPathParams = getOperationParameters(node, { paramsCasing }).path;
|
|
323
359
|
const pathParamNameMap = new Map(casedPathParams.map((p) => [camelCase(p.name), p.name]));
|
|
324
|
-
const urlTemplate =
|
|
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
|
|
366
|
+
const queryParams = getOperationParameters(node).query;
|
|
330
367
|
if (queryParams.length > 0) {
|
|
331
|
-
const casedQueryParams =
|
|
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
|
|
374
|
+
const headerParams = getOperationParameters(node).header;
|
|
338
375
|
if (headerParams.length > 0) {
|
|
339
|
-
const casedHeaderParams =
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
-
*
|
|
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
|
-
*
|
|
487
|
+
* @example Resolve a helper name
|
|
488
|
+
* ```ts
|
|
489
|
+
* import { resolverCypress } from '@kubb/plugin-cypress'
|
|
445
490
|
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
491
|
+
* resolverCypress.default('list pets', 'function') // 'listPets'
|
|
492
|
+
* ```
|
|
448
493
|
*/
|
|
449
|
-
const resolverCypress = (0, _kubb_core.defineResolver)((
|
|
494
|
+
const resolverCypress = (0, _kubb_core.defineResolver)(() => ({
|
|
450
495
|
name: "default",
|
|
451
496
|
pluginName: "plugin-cypress",
|
|
452
497
|
default(name, type) {
|
|
453
|
-
return
|
|
498
|
+
return type === "file" ? toFilePath(name) : camelCase(name);
|
|
454
499
|
},
|
|
455
500
|
resolveName(name) {
|
|
456
|
-
return
|
|
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
|
|
463
|
-
*
|
|
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
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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,
|