@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/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./chunk
|
|
1
|
+
import "./chunk-C0LytTxp.js";
|
|
2
2
|
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
3
3
|
import { functionPrinter, pluginTsName } from "@kubb/plugin-ts";
|
|
4
4
|
import { File, Function, jsxRenderer } from "@kubb/renderer-jsx";
|
|
@@ -14,36 +14,45 @@ import { jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
|
14
14
|
function toCamelOrPascal(text, pascal) {
|
|
15
15
|
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) => {
|
|
16
16
|
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
17
|
-
|
|
18
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
17
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
19
18
|
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* Converts `text` to camelCase.
|
|
22
|
+
*
|
|
23
|
+
* @example Word boundaries
|
|
24
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* @example With a prefix
|
|
27
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
28
28
|
*/
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
29
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
30
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
32
31
|
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region ../../internals/utils/src/fs.ts
|
|
33
34
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
35
|
+
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
36
|
+
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
37
|
+
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
36
38
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
39
|
+
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
40
|
+
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
41
|
+
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
42
|
+
* absolute path, letting generated files escape the configured output directory.
|
|
43
|
+
*
|
|
44
|
+
* @example Nested path from a dotted name
|
|
45
|
+
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
46
|
+
*
|
|
47
|
+
* @example PascalCase the final segment
|
|
48
|
+
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
49
|
+
*
|
|
50
|
+
* @example Suffix applied to the final segment only
|
|
51
|
+
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
40
52
|
*/
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
suffix
|
|
45
|
-
} : {}));
|
|
46
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
53
|
+
function toFilePath(name, caseLast = camelCase) {
|
|
54
|
+
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
55
|
+
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
47
56
|
}
|
|
48
57
|
//#endregion
|
|
49
58
|
//#region ../../internals/utils/src/reserved.ts
|
|
@@ -149,99 +158,80 @@ function isValidVarName(name) {
|
|
|
149
158
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
150
159
|
}
|
|
151
160
|
//#endregion
|
|
152
|
-
//#region ../../internals/utils/src/
|
|
161
|
+
//#region ../../internals/utils/src/url.ts
|
|
162
|
+
function transformParam(raw, casing) {
|
|
163
|
+
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
164
|
+
return casing === "camelcase" ? camelCase(param) : param;
|
|
165
|
+
}
|
|
166
|
+
function toParamsObject(path, { replacer, casing } = {}) {
|
|
167
|
+
const params = {};
|
|
168
|
+
for (const match of path.matchAll(/\{([^}]+)\}/g)) {
|
|
169
|
+
const param = transformParam(match[1], casing);
|
|
170
|
+
const key = replacer ? replacer(param) : param;
|
|
171
|
+
params[key] = key;
|
|
172
|
+
}
|
|
173
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
174
|
+
}
|
|
153
175
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* @example
|
|
157
|
-
* const p = new URLPath('/pet/{petId}')
|
|
158
|
-
* p.URL // '/pet/:petId'
|
|
159
|
-
* p.template // '`/pet/${petId}`'
|
|
176
|
+
* Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
|
|
160
177
|
*/
|
|
161
|
-
var
|
|
178
|
+
var Url = class Url {
|
|
162
179
|
/**
|
|
163
|
-
*
|
|
164
|
-
*/
|
|
165
|
-
path;
|
|
166
|
-
#options;
|
|
167
|
-
constructor(path, options = {}) {
|
|
168
|
-
this.path = path;
|
|
169
|
-
this.#options = options;
|
|
170
|
-
}
|
|
171
|
-
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
180
|
+
* Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
|
|
172
181
|
*
|
|
173
182
|
* @example
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* ```
|
|
183
|
+
* Url.canParse('https://petstore.swagger.io/v2') // true
|
|
184
|
+
* Url.canParse('/pet/{petId}') // false
|
|
177
185
|
*/
|
|
178
|
-
|
|
179
|
-
return
|
|
186
|
+
static canParse(url, base) {
|
|
187
|
+
return URL.canParse(url, base);
|
|
180
188
|
}
|
|
181
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Converts an OpenAPI/Swagger path to Express-style colon syntax.
|
|
182
191
|
*
|
|
183
192
|
* @example
|
|
184
|
-
*
|
|
185
|
-
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
186
|
-
* new URLPath('/pet/{petId}').isURL // false
|
|
187
|
-
* ```
|
|
193
|
+
* Url.toPath('/pet/{petId}') // '/pet/:petId'
|
|
188
194
|
*/
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return !!new URL(this.path).href;
|
|
192
|
-
} catch {
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
+
static toPath(path) {
|
|
196
|
+
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
195
197
|
}
|
|
196
198
|
/**
|
|
197
|
-
* Converts
|
|
199
|
+
* Converts an OpenAPI/Swagger path to a TypeScript template literal string.
|
|
200
|
+
* `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
|
|
201
|
+
* and `casing` controls parameter identifier casing.
|
|
198
202
|
*
|
|
199
203
|
* @example
|
|
200
|
-
*
|
|
201
|
-
* new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
|
|
202
|
-
*/
|
|
203
|
-
get template() {
|
|
204
|
-
return this.toTemplateString();
|
|
205
|
-
}
|
|
206
|
-
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
204
|
+
* Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
|
|
207
205
|
*
|
|
208
206
|
* @example
|
|
209
|
-
*
|
|
210
|
-
* new URLPath('/pet/{petId}').object
|
|
211
|
-
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
212
|
-
* ```
|
|
207
|
+
* Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
|
|
213
208
|
*/
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
static toTemplateString(path, { prefix, replacer, casing } = {}) {
|
|
210
|
+
const result = path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
211
|
+
if (i % 2 === 0) return part;
|
|
212
|
+
const param = transformParam(part, casing);
|
|
213
|
+
return `\${${replacer ? replacer(param) : param}}`;
|
|
214
|
+
}).join("");
|
|
215
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
216
216
|
}
|
|
217
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* Returns the path and its extracted params as a structured `URLObject`, or as a stringified
|
|
219
|
+
* expression when `stringify` is set.
|
|
218
220
|
*
|
|
219
221
|
* @example
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* new URLPath('/pet').params // undefined
|
|
223
|
-
* ```
|
|
224
|
-
*/
|
|
225
|
-
get params() {
|
|
226
|
-
return this.getParams();
|
|
227
|
-
}
|
|
228
|
-
#transformParam(raw) {
|
|
229
|
-
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
230
|
-
return this.#options.casing === "camelcase" ? camelCase(param) : param;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
222
|
+
* Url.toObject('/pet/{petId}')
|
|
223
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
234
224
|
*/
|
|
235
|
-
|
|
236
|
-
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
237
|
-
const raw = match[1];
|
|
238
|
-
fn(raw, this.#transformParam(raw));
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
toObject({ type = "path", replacer, stringify } = {}) {
|
|
225
|
+
static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
|
|
242
226
|
const object = {
|
|
243
|
-
url: type === "path" ?
|
|
244
|
-
|
|
227
|
+
url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
|
|
228
|
+
replacer,
|
|
229
|
+
casing
|
|
230
|
+
}),
|
|
231
|
+
params: toParamsObject(path, {
|
|
232
|
+
replacer,
|
|
233
|
+
casing
|
|
234
|
+
})
|
|
245
235
|
};
|
|
246
236
|
if (stringify) {
|
|
247
237
|
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
@@ -250,54 +240,100 @@ var URLPath = class {
|
|
|
250
240
|
}
|
|
251
241
|
return object;
|
|
252
242
|
}
|
|
253
|
-
/**
|
|
254
|
-
* Converts the OpenAPI path to a TypeScript template literal string.
|
|
255
|
-
* An optional `replacer` can transform each extracted parameter name before interpolation.
|
|
256
|
-
*
|
|
257
|
-
* @example
|
|
258
|
-
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
259
|
-
*/
|
|
260
|
-
toTemplateString({ prefix = "", replacer } = {}) {
|
|
261
|
-
return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
262
|
-
if (i % 2 === 0) return part;
|
|
263
|
-
const param = this.#transformParam(part);
|
|
264
|
-
return `\${${replacer ? replacer(param) : param}}`;
|
|
265
|
-
}).join("")}\``;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
269
|
-
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
270
|
-
* Returns `undefined` when no path parameters are found.
|
|
271
|
-
*
|
|
272
|
-
* @example
|
|
273
|
-
* ```ts
|
|
274
|
-
* new URLPath('/pet/{petId}/tag/{tagId}').getParams()
|
|
275
|
-
* // { petId: 'petId', tagId: 'tagId' }
|
|
276
|
-
* ```
|
|
277
|
-
*/
|
|
278
|
-
getParams(replacer) {
|
|
279
|
-
const params = {};
|
|
280
|
-
this.#eachParam((_raw, param) => {
|
|
281
|
-
const key = replacer ? replacer(param) : param;
|
|
282
|
-
params[key] = key;
|
|
283
|
-
});
|
|
284
|
-
return Object.keys(params).length > 0 ? params : void 0;
|
|
285
|
-
}
|
|
286
|
-
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
287
|
-
*
|
|
288
|
-
* @example
|
|
289
|
-
* ```ts
|
|
290
|
-
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
291
|
-
* ```
|
|
292
|
-
*/
|
|
293
|
-
toURLPath() {
|
|
294
|
-
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
295
|
-
}
|
|
296
243
|
};
|
|
297
244
|
//#endregion
|
|
245
|
+
//#region ../../internals/shared/src/operation.ts
|
|
246
|
+
function getOperationParameters(node, options = {}) {
|
|
247
|
+
const params = ast.caseParams(node.parameters, options.paramsCasing);
|
|
248
|
+
return {
|
|
249
|
+
path: params.filter((param) => param.in === "path"),
|
|
250
|
+
query: params.filter((param) => param.in === "query"),
|
|
251
|
+
header: params.filter((param) => param.in === "header"),
|
|
252
|
+
cookie: params.filter((param) => param.in === "cookie")
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function getStatusCodeNumber(statusCode) {
|
|
256
|
+
const code = Number(statusCode);
|
|
257
|
+
return Number.isNaN(code) ? null : code;
|
|
258
|
+
}
|
|
259
|
+
function isErrorStatusCode(statusCode) {
|
|
260
|
+
const code = getStatusCodeNumber(statusCode);
|
|
261
|
+
return code !== null && code >= 400;
|
|
262
|
+
}
|
|
263
|
+
function resolveErrorNames(node, resolver) {
|
|
264
|
+
return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
|
|
265
|
+
}
|
|
266
|
+
function resolveStatusCodeNames(node, resolver) {
|
|
267
|
+
return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
|
|
268
|
+
}
|
|
269
|
+
const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
|
|
270
|
+
function resolveOperationTypeNames(node, resolver, options = {}) {
|
|
271
|
+
const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
|
|
272
|
+
let byResolver = typeNamesByResolver.get(resolver);
|
|
273
|
+
if (byResolver) {
|
|
274
|
+
const cached = byResolver.get(cacheKey);
|
|
275
|
+
if (cached) return cached;
|
|
276
|
+
} else {
|
|
277
|
+
byResolver = /* @__PURE__ */ new Map();
|
|
278
|
+
typeNamesByResolver.set(resolver, byResolver);
|
|
279
|
+
}
|
|
280
|
+
const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
|
|
281
|
+
const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
|
|
282
|
+
const exclude = new Set(options.exclude ?? []);
|
|
283
|
+
const paramNames = [
|
|
284
|
+
...path.map((param) => resolver.resolvePathParamsName(node, param)),
|
|
285
|
+
...query.map((param) => resolver.resolveQueryParamsName(node, param)),
|
|
286
|
+
...header.map((param) => resolver.resolveHeaderParamsName(node, param))
|
|
287
|
+
];
|
|
288
|
+
const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
|
|
289
|
+
const result = (options.order === "body-response-first" ? [
|
|
290
|
+
...bodyAndResponseNames,
|
|
291
|
+
...paramNames,
|
|
292
|
+
...responseStatusNames
|
|
293
|
+
] : [
|
|
294
|
+
...paramNames,
|
|
295
|
+
...bodyAndResponseNames,
|
|
296
|
+
...responseStatusNames
|
|
297
|
+
]).filter((name) => Boolean(name) && !exclude.has(name));
|
|
298
|
+
byResolver.set(cacheKey, result);
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region ../../internals/shared/src/group.ts
|
|
303
|
+
/**
|
|
304
|
+
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
305
|
+
* shared default naming so every plugin groups output consistently:
|
|
306
|
+
*
|
|
307
|
+
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
308
|
+
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
309
|
+
*
|
|
310
|
+
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
311
|
+
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
312
|
+
* per-plugin convention.
|
|
313
|
+
*
|
|
314
|
+
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* createGroupConfig(group) // shared across every plugin
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
function createGroupConfig(group) {
|
|
322
|
+
if (!group) return null;
|
|
323
|
+
const defaultName = (ctx) => {
|
|
324
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
325
|
+
return camelCase(ctx.group);
|
|
326
|
+
};
|
|
327
|
+
return {
|
|
328
|
+
...group,
|
|
329
|
+
name: group.name ? group.name : defaultName
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
298
333
|
//#region src/components/Request.tsx
|
|
299
334
|
const declarationPrinter = functionPrinter({ mode: "declaration" });
|
|
300
335
|
function Request({ baseURL = "", name, dataReturnType, resolver, node, paramsType, pathParamsType, paramsCasing }) {
|
|
336
|
+
if (!ast.isHttpOperationNode(node)) return null;
|
|
301
337
|
const paramsNode = ast.createOperationParams(node, {
|
|
302
338
|
paramsType,
|
|
303
339
|
pathParamsType,
|
|
@@ -315,24 +351,25 @@ function Request({ baseURL = "", name, dataReturnType, resolver, node, paramsTyp
|
|
|
315
351
|
const paramsSignature = declarationPrinter.print(paramsNode) ?? "";
|
|
316
352
|
const responseType = resolver.resolveResponseName(node);
|
|
317
353
|
const returnType = dataReturnType === "data" ? `Cypress.Chainable<${responseType}>` : `Cypress.Chainable<Cypress.Response<${responseType}>>`;
|
|
318
|
-
const casedPathParams =
|
|
354
|
+
const casedPathParams = getOperationParameters(node, { paramsCasing }).path;
|
|
319
355
|
const pathParamNameMap = new Map(casedPathParams.map((p) => [camelCase(p.name), p.name]));
|
|
320
|
-
const urlTemplate =
|
|
356
|
+
const urlTemplate = Url.toTemplateString(node.path, {
|
|
321
357
|
prefix: baseURL,
|
|
322
|
-
replacer: (param) => pathParamNameMap.get(camelCase(param)) ?? param
|
|
358
|
+
replacer: (param) => pathParamNameMap.get(camelCase(param)) ?? param,
|
|
359
|
+
casing: paramsCasing
|
|
323
360
|
});
|
|
324
361
|
const requestOptions = [`method: '${node.method}'`, `url: ${urlTemplate}`];
|
|
325
|
-
const queryParams = node
|
|
362
|
+
const queryParams = getOperationParameters(node).query;
|
|
326
363
|
if (queryParams.length > 0) {
|
|
327
|
-
const casedQueryParams =
|
|
364
|
+
const casedQueryParams = getOperationParameters(node, { paramsCasing }).query;
|
|
328
365
|
if (casedQueryParams.some((p, i) => p.name !== queryParams[i].name)) {
|
|
329
366
|
const pairs = queryParams.map((orig, i) => `${orig.name}: params.${casedQueryParams[i].name}`).join(", ");
|
|
330
367
|
requestOptions.push(`qs: params ? { ${pairs} } : undefined`);
|
|
331
368
|
} else requestOptions.push("qs: params");
|
|
332
369
|
}
|
|
333
|
-
const headerParams = node
|
|
370
|
+
const headerParams = getOperationParameters(node).header;
|
|
334
371
|
if (headerParams.length > 0) {
|
|
335
|
-
const casedHeaderParams =
|
|
372
|
+
const casedHeaderParams = getOperationParameters(node, { paramsCasing }).header;
|
|
336
373
|
if (casedHeaderParams.some((p, i) => p.name !== headerParams[i].name)) {
|
|
337
374
|
const pairs = headerParams.map((orig, i) => `'${orig.name}': headers.${casedHeaderParams[i].name}`).join(", ");
|
|
338
375
|
requestOptions.push(`headers: headers ? { ${pairs} } : undefined`);
|
|
@@ -359,26 +396,22 @@ function Request({ baseURL = "", name, dataReturnType, resolver, node, paramsTyp
|
|
|
359
396
|
}
|
|
360
397
|
//#endregion
|
|
361
398
|
//#region src/generators/cypressGenerator.tsx
|
|
399
|
+
/**
|
|
400
|
+
* Built-in generator for `@kubb/plugin-cypress`. Emits one typed
|
|
401
|
+
* `cy.request()` wrapper per OpenAPI operation, ready to call inside Cypress
|
|
402
|
+
* test specs and custom commands.
|
|
403
|
+
*/
|
|
362
404
|
const cypressGenerator = defineGenerator({
|
|
363
405
|
name: "cypress",
|
|
364
406
|
renderer: jsxRenderer,
|
|
365
407
|
operation(node, ctx) {
|
|
366
|
-
|
|
408
|
+
if (!ast.isHttpOperationNode(node)) return null;
|
|
409
|
+
const { config, resolver, driver, root } = ctx;
|
|
367
410
|
const { output, baseURL, dataReturnType, paramsCasing, paramsType, pathParamsType, group } = ctx.options;
|
|
368
411
|
const pluginTs = driver.getPlugin(pluginTsName);
|
|
369
412
|
if (!pluginTs) return null;
|
|
370
413
|
const tsResolver = driver.getResolver(pluginTsName);
|
|
371
|
-
const
|
|
372
|
-
const pathParams = casedParams.filter((p) => p.in === "path");
|
|
373
|
-
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
374
|
-
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
375
|
-
const importedTypeNames = [
|
|
376
|
-
...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
|
|
377
|
-
...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
|
|
378
|
-
...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
|
|
379
|
-
node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
|
|
380
|
-
tsResolver.resolveResponseName(node)
|
|
381
|
-
].filter(Boolean);
|
|
414
|
+
const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing });
|
|
382
415
|
const meta = {
|
|
383
416
|
name: resolver.resolveName(node.operationId),
|
|
384
417
|
file: resolver.resolveFile({
|
|
@@ -389,7 +422,7 @@ const cypressGenerator = defineGenerator({
|
|
|
389
422
|
}, {
|
|
390
423
|
root,
|
|
391
424
|
output,
|
|
392
|
-
group
|
|
425
|
+
group: group ?? void 0
|
|
393
426
|
}),
|
|
394
427
|
fileTs: tsResolver.resolveFile({
|
|
395
428
|
name: node.operationId,
|
|
@@ -399,23 +432,31 @@ const cypressGenerator = defineGenerator({
|
|
|
399
432
|
}, {
|
|
400
433
|
root,
|
|
401
434
|
output: pluginTs.options?.output ?? output,
|
|
402
|
-
group: pluginTs.options?.group
|
|
435
|
+
group: pluginTs.options?.group ?? void 0
|
|
403
436
|
})
|
|
404
437
|
};
|
|
405
438
|
return /* @__PURE__ */ jsxs(File, {
|
|
406
439
|
baseName: meta.file.baseName,
|
|
407
440
|
path: meta.file.path,
|
|
408
441
|
meta: meta.file.meta,
|
|
409
|
-
banner: resolver.resolveBanner(
|
|
442
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
410
443
|
output,
|
|
411
|
-
config
|
|
444
|
+
config,
|
|
445
|
+
file: {
|
|
446
|
+
path: meta.file.path,
|
|
447
|
+
baseName: meta.file.baseName
|
|
448
|
+
}
|
|
412
449
|
}),
|
|
413
|
-
footer: resolver.resolveFooter(
|
|
450
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
414
451
|
output,
|
|
415
|
-
config
|
|
452
|
+
config,
|
|
453
|
+
file: {
|
|
454
|
+
path: meta.file.path,
|
|
455
|
+
baseName: meta.file.baseName
|
|
456
|
+
}
|
|
416
457
|
}),
|
|
417
458
|
children: [meta.fileTs && importedTypeNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
|
|
418
|
-
name:
|
|
459
|
+
name: importedTypeNames,
|
|
419
460
|
root: meta.file.path,
|
|
420
461
|
path: meta.fileTs.path,
|
|
421
462
|
isTypeOnly: true
|
|
@@ -435,58 +476,66 @@ const cypressGenerator = defineGenerator({
|
|
|
435
476
|
//#endregion
|
|
436
477
|
//#region src/resolvers/resolverCypress.ts
|
|
437
478
|
/**
|
|
438
|
-
*
|
|
479
|
+
* Default resolver used by `@kubb/plugin-cypress`. Decides the names and file
|
|
480
|
+
* paths for every generated `cy.request()` wrapper. Functions and files use
|
|
481
|
+
* camelCase, matching the convention from `@kubb/plugin-client`.
|
|
439
482
|
*
|
|
440
|
-
*
|
|
483
|
+
* @example Resolve a helper name
|
|
484
|
+
* ```ts
|
|
485
|
+
* import { resolverCypress } from '@kubb/plugin-cypress'
|
|
441
486
|
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
487
|
+
* resolverCypress.default('list pets', 'function') // 'listPets'
|
|
488
|
+
* ```
|
|
444
489
|
*/
|
|
445
|
-
const resolverCypress = defineResolver((
|
|
490
|
+
const resolverCypress = defineResolver(() => ({
|
|
446
491
|
name: "default",
|
|
447
492
|
pluginName: "plugin-cypress",
|
|
448
493
|
default(name, type) {
|
|
449
|
-
return
|
|
494
|
+
return type === "file" ? toFilePath(name) : camelCase(name);
|
|
450
495
|
},
|
|
451
496
|
resolveName(name) {
|
|
452
|
-
return
|
|
497
|
+
return this.default(name, "function");
|
|
498
|
+
},
|
|
499
|
+
resolvePathName(name, type) {
|
|
500
|
+
return this.default(name, type);
|
|
453
501
|
}
|
|
454
502
|
}));
|
|
455
503
|
//#endregion
|
|
456
504
|
//#region src/plugin.ts
|
|
457
505
|
/**
|
|
458
|
-
* Canonical plugin name for `@kubb/plugin-cypress
|
|
459
|
-
*
|
|
506
|
+
* Canonical plugin name for `@kubb/plugin-cypress`. Used for driver lookups and
|
|
507
|
+
* cross-plugin dependency references.
|
|
460
508
|
*/
|
|
461
509
|
const pluginCypressName = "plugin-cypress";
|
|
462
510
|
/**
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
* Walks operations, delegates rendering to the active generators,
|
|
467
|
-
* and writes barrel files based on `output.barrelType`.
|
|
511
|
+
* Generates one typed `cy.request()` wrapper per OpenAPI operation. Each helper
|
|
512
|
+
* has typed path params, body, query, and a typed response, so failing API
|
|
513
|
+
* calls in Cypress show up at compile time instead of inside the test runner.
|
|
468
514
|
*
|
|
469
515
|
* @example
|
|
470
516
|
* ```ts
|
|
471
|
-
* import
|
|
517
|
+
* import { defineConfig } from 'kubb'
|
|
518
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
519
|
+
* import { pluginCypress } from '@kubb/plugin-cypress'
|
|
472
520
|
*
|
|
473
521
|
* export default defineConfig({
|
|
474
|
-
*
|
|
522
|
+
* input: { path: './petStore.yaml' },
|
|
523
|
+
* output: { path: './src/gen' },
|
|
524
|
+
* plugins: [
|
|
525
|
+
* pluginTs(),
|
|
526
|
+
* pluginCypress({
|
|
527
|
+
* output: { path: './cypress' },
|
|
528
|
+
* }),
|
|
529
|
+
* ],
|
|
475
530
|
* })
|
|
476
531
|
* ```
|
|
477
532
|
*/
|
|
478
533
|
const pluginCypress = definePlugin((options) => {
|
|
479
534
|
const { output = {
|
|
480
535
|
path: "cypress",
|
|
481
|
-
|
|
536
|
+
barrel: { type: "named" }
|
|
482
537
|
}, group, exclude = [], include, override = [], dataReturnType = "data", baseURL, paramsCasing, paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
|
|
483
|
-
const groupConfig = group
|
|
484
|
-
...group,
|
|
485
|
-
name: group.name ? group.name : (ctx) => {
|
|
486
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
487
|
-
return `${camelCase(ctx.group)}Requests`;
|
|
488
|
-
}
|
|
489
|
-
} : void 0;
|
|
538
|
+
const groupConfig = createGroupConfig(group);
|
|
490
539
|
return {
|
|
491
540
|
name: pluginCypressName,
|
|
492
541
|
options,
|