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