@kubb/plugin-faker 5.0.0-alpha.9 → 5.0.0-beta.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import "./chunk--u3MIqq1.js";
2
- import { t as fakerGenerator } from "./fakerGenerator-M5oCrPmy.js";
3
- import path from "node:path";
4
- import { createPlugin, getBarrelFiles, getMode } from "@kubb/core";
5
- import { OperationGenerator, SchemaGenerator, pluginOasName } from "@kubb/plugin-oas";
6
- import { pluginTsName } from "@kubb/plugin-ts";
1
+ import "./rolldown-runtime-C0LytTxp.js";
2
+ import { posix } from "node:path";
3
+ import { Resolver, ast, containsCircularRef, createResolver, defineGenerator, definePlugin } from "kubb/kit";
4
+ import { buildParams, createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
5
+ import { File, Function, jsxRenderer } from "kubb/jsx";
6
+ import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
7
7
  //#region ../../internals/utils/src/casing.ts
8
8
  /**
9
9
  * Shared implementation for camelCase and PascalCase conversion.
@@ -15,127 +15,1460 @@ import { pluginTsName } from "@kubb/plugin-ts";
15
15
  function toCamelOrPascal(text, pascal) {
16
16
  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) => {
17
17
  if (word.length > 1 && word === word.toUpperCase()) return word;
18
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
19
- return word.charAt(0).toUpperCase() + word.slice(1);
18
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
20
19
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
21
20
  }
22
21
  /**
23
- * Splits `text` on `.` and applies `transformPart` to each segment.
24
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
25
- * Segments are joined with `/` to form a file path.
22
+ * Converts `text` to camelCase.
23
+ *
24
+ * @example Word boundaries
25
+ * `camelCase('hello-world') // 'helloWorld'`
26
+ *
27
+ * @example With a prefix
28
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
26
29
  */
27
- function applyToFileParts(text, transformPart) {
28
- const parts = text.split(".");
29
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
30
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
31
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
30
32
  }
33
+ //#endregion
34
+ //#region ../../internals/utils/src/reserved.ts
31
35
  /**
32
- * Converts `text` to camelCase.
33
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
36
+ * JavaScript and Java reserved words.
37
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
38
+ */
39
+ const reservedWords = /* @__PURE__ */ new Set([
40
+ "abstract",
41
+ "arguments",
42
+ "boolean",
43
+ "break",
44
+ "byte",
45
+ "case",
46
+ "catch",
47
+ "char",
48
+ "class",
49
+ "const",
50
+ "continue",
51
+ "debugger",
52
+ "default",
53
+ "delete",
54
+ "do",
55
+ "double",
56
+ "else",
57
+ "enum",
58
+ "eval",
59
+ "export",
60
+ "extends",
61
+ "false",
62
+ "final",
63
+ "finally",
64
+ "float",
65
+ "for",
66
+ "function",
67
+ "goto",
68
+ "if",
69
+ "implements",
70
+ "import",
71
+ "in",
72
+ "instanceof",
73
+ "int",
74
+ "interface",
75
+ "let",
76
+ "long",
77
+ "native",
78
+ "new",
79
+ "null",
80
+ "package",
81
+ "private",
82
+ "protected",
83
+ "public",
84
+ "return",
85
+ "short",
86
+ "static",
87
+ "super",
88
+ "switch",
89
+ "synchronized",
90
+ "this",
91
+ "throw",
92
+ "throws",
93
+ "transient",
94
+ "true",
95
+ "try",
96
+ "typeof",
97
+ "var",
98
+ "void",
99
+ "volatile",
100
+ "while",
101
+ "with",
102
+ "yield",
103
+ "Array",
104
+ "Date",
105
+ "hasOwnProperty",
106
+ "Infinity",
107
+ "isFinite",
108
+ "isNaN",
109
+ "isPrototypeOf",
110
+ "length",
111
+ "Math",
112
+ "name",
113
+ "NaN",
114
+ "Number",
115
+ "Object",
116
+ "prototype",
117
+ "String",
118
+ "toString",
119
+ "undefined",
120
+ "valueOf"
121
+ ]);
122
+ /**
123
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
34
124
  *
35
125
  * @example
36
- * camelCase('hello-world') // 'helloWorld'
37
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
126
+ * ```ts
127
+ * isValidVarName('status') // true
128
+ * isValidVarName('class') // false (reserved word)
129
+ * isValidVarName('42foo') // false (starts with digit)
130
+ * ```
38
131
  */
39
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
40
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
41
- prefix,
42
- suffix
43
- } : {}));
44
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
132
+ function isValidVarName(name) {
133
+ if (!name || reservedWords.has(name)) return false;
134
+ return isIdentifier(name);
135
+ }
136
+ /**
137
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
138
+ * otherwise prefixes it with `_` so the result is a valid identifier.
139
+ *
140
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
141
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
142
+ * variable, type, or function names.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * ensureValidVarName('409') // '_409'
147
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
148
+ * ensureValidVarName('Pet') // 'Pet'
149
+ * ensureValidVarName('class') // '_class'
150
+ * ```
151
+ */
152
+ function ensureValidVarName(name) {
153
+ if (!name || isValidVarName(name)) return name;
154
+ return `_${name}`;
155
+ }
156
+ /**
157
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
158
+ *
159
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
160
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
161
+ * deciding whether an object key needs quoting.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * isIdentifier('name') // true
166
+ * isIdentifier('x-total')// false
167
+ * ```
168
+ */
169
+ function isIdentifier(name) {
170
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
45
171
  }
46
172
  //#endregion
173
+ //#region ../../internals/utils/src/strings.ts
174
+ /**
175
+ * Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping
176
+ * any backslash or single quote in the content.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * singleQuote('foo') // "'foo'"
181
+ * singleQuote("o'clock") // "'o\\'clock'"
182
+ * ```
183
+ */
184
+ function singleQuote(value) {
185
+ if (value === void 0 || value === null) return "''";
186
+ return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
187
+ }
188
+ /**
189
+ * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
190
+ * Returns the string unchanged when no balanced quote pair is found.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * trimQuotes('"hello"') // 'hello'
195
+ * trimQuotes('hello') // 'hello'
196
+ * ```
197
+ */
198
+ function trimQuotes(text) {
199
+ if (text.length >= 2) {
200
+ const first = text[0];
201
+ const last = text[text.length - 1];
202
+ if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
203
+ }
204
+ return text;
205
+ }
206
+ /**
207
+ * Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
208
+ *
209
+ * Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
210
+ * code matches the repo style without a formatter.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * stringify('hello') // "'hello'"
215
+ * stringify('"hello"') // "'hello'"
216
+ * ```
217
+ */
218
+ function stringify(value) {
219
+ if (value === void 0 || value === null) return "''";
220
+ return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
221
+ }
222
+ /**
223
+ * Escapes characters that are not allowed inside JS string literals, covering quotes, backslashes,
224
+ * and the Unicode line terminators U+2028 and U+2029.
225
+ *
226
+ * @see http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * jsStringEscape('say "hi"\nbye') // 'say \\"hi\\"\\nbye'
231
+ * ```
232
+ */
233
+ function jsStringEscape(input) {
234
+ return `${input}`.replace(/["'\\\n\r\u2028\u2029]/g, (character) => {
235
+ switch (character) {
236
+ case "\"":
237
+ case "'":
238
+ case "\\": return `\\${character}`;
239
+ case "\n": return "\\n";
240
+ case "\r": return "\\r";
241
+ case "\u2028": return "\\u2028";
242
+ case "\u2029": return "\\u2029";
243
+ default: return "";
244
+ }
245
+ });
246
+ }
247
+ /**
248
+ * Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
249
+ * Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
250
+ * Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
255
+ * toRegExpString('^(?im)foo', null) // '/^foo/im'
256
+ * ```
257
+ */
258
+ function toRegExpString(text, func = "RegExp") {
259
+ const raw = trimQuotes(text);
260
+ const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
261
+ const replacementTarget = match?.[1] ?? "";
262
+ const matchedFlags = match?.[2];
263
+ const cleaned = raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, "");
264
+ const { source, flags } = new RegExp(cleaned, matchedFlags);
265
+ if (func === null) return `/${source}/${flags}`;
266
+ return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
267
+ }
268
+ //#endregion
269
+ //#region ../../internals/utils/src/codegen.ts
270
+ const INDENT = " ";
271
+ /**
272
+ * Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
273
+ */
274
+ function indentLines(text) {
275
+ if (!text) return "";
276
+ return text.split("\n").map((line) => line.trim() ? `${INDENT}${line}` : "").join("\n");
277
+ }
278
+ /**
279
+ * Renders an object key, quoting it with single quotes only when it is not a valid identifier.
280
+ * Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * objectKey('name') // 'name'
285
+ * objectKey('x-total') // "'x-total'"
286
+ * ```
287
+ */
288
+ function objectKey(name) {
289
+ return isIdentifier(name) ? name : singleQuote(name);
290
+ }
291
+ /**
292
+ * Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
293
+ * level and closing the brace at column zero. Entries that are themselves multi-line objects indent
294
+ * cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * buildObject(['id: z.number()', 'name: z.string()'])
299
+ * // '{\n id: z.number(),\n name: z.string(),\n}'
300
+ * ```
301
+ */
302
+ function buildObject(entries) {
303
+ if (entries.length === 0) return "{}";
304
+ return `{\n${entries.map((entry) => `${indentLines(entry)},`).join("\n")}\n}`;
305
+ }
306
+ //#endregion
307
+ //#region ../../internals/utils/src/fs.ts
308
+ /**
309
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
310
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
311
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
312
+ *
313
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
314
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
315
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
316
+ * absolute path, letting generated files escape the configured output directory.
317
+ *
318
+ * @example Nested path from a dotted name
319
+ * `toFilePath('pet.petId') // 'pet/petId'`
320
+ *
321
+ * @example PascalCase the final segment
322
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
323
+ *
324
+ * @example Suffix applied to the final segment only
325
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
326
+ */
327
+ function toFilePath(name, caseLast = camelCase) {
328
+ const parts = name.split(/\.(?=[a-zA-Z])/);
329
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
330
+ }
331
+ //#endregion
332
+ //#region ../../internals/utils/src/imports.ts
333
+ function escapeRegExp(value) {
334
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
335
+ }
336
+ function getImportNames(entry) {
337
+ return (Array.isArray(entry.name) ? entry.name : [entry.name]).map((name) => {
338
+ if (typeof name === "string") return name;
339
+ return name.name ?? name.propertyName;
340
+ }).filter((name) => Boolean(name));
341
+ }
342
+ function filterUsedImports(imports, text, skipImportNames = []) {
343
+ const skip = new Set(skipImportNames);
344
+ return imports.filter((entry) => {
345
+ return getImportNames(entry).some((name) => {
346
+ if (skip.has(name)) return false;
347
+ return new RegExp(`\\b${escapeRegExp(name)}\\b(?=\\s*\\()`).test(text);
348
+ });
349
+ });
350
+ }
351
+ function aliasConflictingImports(imports, reservedNames) {
352
+ const reservedNameSet = new Set(reservedNames);
353
+ const aliases = /* @__PURE__ */ new Map();
354
+ return {
355
+ imports: imports.map((entry) => {
356
+ const aliasedNames = (Array.isArray(entry.name) ? entry.name : [entry.name]).map((item) => {
357
+ if (typeof item !== "string" || !reservedNameSet.has(item)) return item;
358
+ const alias = `${item}Schema`;
359
+ aliases.set(item, alias);
360
+ return {
361
+ propertyName: item,
362
+ name: alias
363
+ };
364
+ });
365
+ return aliasedNames.some((item) => typeof item === "object" && item.name) ? {
366
+ ...entry,
367
+ name: aliasedNames
368
+ } : entry;
369
+ }),
370
+ aliases
371
+ };
372
+ }
373
+ function rewriteAliasedImports(text, aliases) {
374
+ return Array.from(aliases).reduce((acc, [name, alias]) => acc.replace(new RegExp(`\\b${escapeRegExp(name)}\\b`, "g"), alias), text);
375
+ }
376
+ //#endregion
377
+ //#region src/utils.ts
378
+ /**
379
+ * Returns the `@faker-js/faker` named export for a locale code.
380
+ *
381
+ * Without a locale, returns `'faker'` for the default English instance.
382
+ * With a locale, the language code is converted to upper case and joined with any region suffix.
383
+ *
384
+ * @example Default
385
+ * `localeToFakerImport() // 'faker'`
386
+ *
387
+ * @example Simple locale
388
+ * `localeToFakerImport('de') // 'fakerDE'`
389
+ *
390
+ * @example Compound locale
391
+ * `localeToFakerImport('de_AT') // 'fakerDE_AT'`
392
+ */
393
+ function localeToFakerImport(locale) {
394
+ if (!locale) return "faker";
395
+ const parts = locale.split("_");
396
+ parts[0] = parts[0].toUpperCase();
397
+ return `faker${parts.join("_")}`;
398
+ }
399
+ /**
400
+ * Determines if a schema node can be overridden during faker generation.
401
+ */
402
+ function canOverrideSchema(node) {
403
+ return (/* @__PURE__ */ new Set([
404
+ "array",
405
+ "tuple",
406
+ "object",
407
+ "intersection",
408
+ "union",
409
+ "enum",
410
+ "ref",
411
+ "string",
412
+ "email",
413
+ "url",
414
+ "uuid",
415
+ "number",
416
+ "integer",
417
+ "bigint",
418
+ "boolean",
419
+ "date",
420
+ "time",
421
+ "datetime",
422
+ "blob"
423
+ ])).has(node.type);
424
+ }
425
+ function shouldInlineSingleResponseSchema(schema) {
426
+ return (/* @__PURE__ */ new Set([
427
+ "any",
428
+ "unknown",
429
+ "void",
430
+ "null",
431
+ "array",
432
+ "tuple",
433
+ "string",
434
+ "email",
435
+ "url",
436
+ "uuid",
437
+ "number",
438
+ "integer",
439
+ "bigint",
440
+ "boolean",
441
+ "date",
442
+ "time",
443
+ "datetime",
444
+ "blob",
445
+ "enum",
446
+ "union"
447
+ ])).has(schema.type);
448
+ }
449
+ /**
450
+ * Builds a response schema as a union of all response statuses.
451
+ * Returns null if no responses are provided, or embeds single simple responses inline.
452
+ */
453
+ function buildResponseUnionSchema(node, resolver) {
454
+ const responses = node.responses.filter((response) => response.content?.[0]?.schema);
455
+ if (!responses.length) return null;
456
+ if (responses.length === 1) {
457
+ const schema = responses[0].content?.[0]?.schema;
458
+ if (schema && shouldInlineSingleResponseSchema(schema)) return schema;
459
+ return ast.factory.createSchema({
460
+ type: "ref",
461
+ name: resolver.response.status(node, responses[0].statusCode)
462
+ });
463
+ }
464
+ return ast.factory.createSchema({
465
+ type: "union",
466
+ members: responses.map((response) => ast.factory.createSchema({
467
+ type: "ref",
468
+ name: resolver.response.status(node, response.statusCode)
469
+ }))
470
+ });
471
+ }
472
+ const SCALAR_TYPES$1 = /* @__PURE__ */ new Set([
473
+ "string",
474
+ "email",
475
+ "url",
476
+ "uuid",
477
+ "number",
478
+ "integer",
479
+ "bigint",
480
+ "boolean",
481
+ "date",
482
+ "time",
483
+ "datetime",
484
+ "blob",
485
+ "enum"
486
+ ]);
487
+ function toRelativeImportPath(from, to) {
488
+ const relativePath = posix.relative(posix.dirname(from), to);
489
+ return relativePath.startsWith("../") ? relativePath : `./${relativePath}`;
490
+ }
491
+ /**
492
+ * Resolves a type reference, determining if it needs an import statement or inline type reference.
493
+ * Takes into account whether the type can be overridden and the file paths.
494
+ */
495
+ function resolveTypeReference({ node, canOverride, name, typeName, filePath, typeFilePath }) {
496
+ const { usesTypeName } = resolveFakerTypeUsage(node, typeName, canOverride);
497
+ if (!usesTypeName) return { typeName };
498
+ if (name === typeName) return { typeName: `import('${toRelativeImportPath(filePath, typeFilePath)}').${typeName}` };
499
+ return {
500
+ importPath: typeFilePath,
501
+ typeName
502
+ };
503
+ }
504
+ /**
505
+ * Maps a schema node type to its corresponding scalar type representation.
506
+ * Returns the type name for enums or the base type (string, number, etc.) for primitives.
507
+ */
508
+ function getScalarType(node, typeName) {
509
+ switch (node.type) {
510
+ case "string":
511
+ case "email":
512
+ case "url":
513
+ case "uuid": return "string";
514
+ case "number":
515
+ case "integer": return "number";
516
+ case "bigint": return "bigint";
517
+ case "boolean": return "boolean";
518
+ case "date":
519
+ case "time": return node.representation === "date" ? "Date" : "string";
520
+ case "datetime": return "string";
521
+ case "blob": return "Blob";
522
+ case "enum": return typeName;
523
+ default: return typeName;
524
+ }
525
+ }
526
+ /**
527
+ * Resolves faker type usage information for a schema.
528
+ * Determines the data type, return type, and whether it uses the type name.
529
+ */
530
+ function resolveFakerTypeUsage(node, typeName, canOverride) {
531
+ const isArray = node.type === "array";
532
+ const isTuple = node.type === "tuple";
533
+ const isScalar = SCALAR_TYPES$1.has(node.type);
534
+ let dataType = `Partial<${typeName}>`;
535
+ if (isArray || isTuple || node.type === "union" || node.type === "enum") dataType = typeName;
536
+ if (isScalar) dataType = getScalarType(node, typeName);
537
+ let returnType = canOverride ? typeName : null;
538
+ if (isScalar) returnType = getScalarType(node, typeName);
539
+ return {
540
+ dataType,
541
+ returnType,
542
+ usesTypeName: dataType.includes(typeName) || Boolean(returnType?.includes(typeName))
543
+ };
544
+ }
545
+ //#endregion
546
+ //#region src/components/Faker.tsx
547
+ const OBJECT_TYPES = /* @__PURE__ */ new Set(["object", "intersection"]);
548
+ const SCALAR_TYPES = /* @__PURE__ */ new Set([
549
+ "string",
550
+ "email",
551
+ "url",
552
+ "uuid",
553
+ "number",
554
+ "integer",
555
+ "bigint",
556
+ "boolean",
557
+ "date",
558
+ "time",
559
+ "datetime",
560
+ "blob",
561
+ "enum"
562
+ ]);
563
+ const declarationPrinter = functionPrinter({ mode: "declaration" });
564
+ function Faker({ node, description, name, typeName, printer, seed, canOverride }) {
565
+ const fakerText = printer.print(node) ?? "undefined";
566
+ const isArray = node.type === "array";
567
+ const isObject = OBJECT_TYPES.has(node.type);
568
+ const isTuple = node.type === "tuple";
569
+ const isScalar = SCALAR_TYPES.has(node.type);
570
+ const useGenericOverride = canOverride && isObject;
571
+ const fakerTextWithOverride = (() => {
572
+ if (canOverride && isTuple) return `data || ${fakerText}`;
573
+ if (canOverride && isArray) return `[\n ...${fakerText},\n ...(data || [])\n]`;
574
+ if (canOverride && isScalar) return `data ?? ${fakerText}`;
575
+ return fakerText;
576
+ })();
577
+ const { dataType, returnType: resolvedReturnType } = resolveFakerTypeUsage(node, typeName, canOverride);
578
+ if (!useGenericOverride) {
579
+ const params = createFunctionParameters({ params: [createFunctionParameter({
580
+ name: /\bdata\b/.test(fakerTextWithOverride) ? "data" : "_data",
581
+ type: dataType,
582
+ optional: true
583
+ })] });
584
+ const paramsSignature = declarationPrinter.print(params) ?? "";
585
+ const returnType = resolvedReturnType;
586
+ const returnExpression = node.type === "ref" && canOverride && returnType ? `${fakerTextWithOverride} as ${returnType}` : fakerTextWithOverride;
587
+ return /* @__PURE__ */ jsx(File.Source, {
588
+ name,
589
+ isExportable: true,
590
+ isIndexable: true,
591
+ children: /* @__PURE__ */ jsxs(Function, {
592
+ export: true,
593
+ name,
594
+ JSDoc: { comments: description ? [`@description ${jsStringEscape(description)}`] : [] },
595
+ params: canOverride ? paramsSignature : void 0,
596
+ returnType: returnType ?? void 0,
597
+ children: [seed ? /* @__PURE__ */ jsxs(Fragment, { children: [`faker.seed(${JSON.stringify(seed)})`, /* @__PURE__ */ jsx("br", {})] }) : void 0, `return ${returnExpression}`]
598
+ })
599
+ });
600
+ }
601
+ const functionSignature = `${description ? `/**\n * @description ${jsStringEscape(description)}\n */\n ` : ""}export function ${name}<TData extends Partial<${typeName}> = object>(data?: TData)`;
602
+ const seedCode = seed ? `faker.seed(${JSON.stringify(seed)})\n ` : "";
603
+ const { cyclicSchemas, schemaName } = printer.options;
604
+ const functionBody = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => containsCircularRef(p.schema, {
605
+ circularSchemas: cyclicSchemas,
606
+ excludeName: schemaName
607
+ })) ? `{
608
+ ${seedCode}const defaultFakeData = ${fakerText}
609
+ if (data) {
610
+ for (const [key, value] of Object.entries(data)) {
611
+ Object.defineProperty(defaultFakeData, key, { value, configurable: true, writable: true, enumerable: true })
612
+ }
613
+ }
614
+ return defaultFakeData as Omit<typeof defaultFakeData, keyof TData> & TData
615
+ }` : `{
616
+ ${seedCode}const defaultFakeData = ${fakerText}
617
+ return {
618
+ ...defaultFakeData,
619
+ ...(data || {}),
620
+ } as Omit<typeof defaultFakeData, keyof TData> & TData
621
+ }`;
622
+ return /* @__PURE__ */ jsxs(File.Source, {
623
+ name,
624
+ isExportable: true,
625
+ isIndexable: true,
626
+ children: [functionSignature, functionBody]
627
+ });
628
+ }
629
+ //#endregion
630
+ //#region ../../internals/shared/src/params.ts
631
+ /**
632
+ * Drops parameters that share the same name, keeping the first.
633
+ *
634
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
635
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
636
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
637
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
638
+ */
639
+ function dedupeParams(params) {
640
+ const seen = /* @__PURE__ */ new Set();
641
+ return params.filter((param) => {
642
+ if (seen.has(param.name)) return false;
643
+ seen.add(param.name);
644
+ return true;
645
+ });
646
+ }
647
+ //#endregion
648
+ //#region ../../internals/shared/src/operation.ts
649
+ /**
650
+ * Maps a content type to the PascalCase suffix used to name per-content-type variants
651
+ * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
652
+ */
653
+ function getContentTypeSuffix(contentType) {
654
+ const baseType = contentType.split(";")[0].trim();
655
+ if (baseType === "application/json") return "Json";
656
+ if (baseType === "multipart/form-data") return "FormData";
657
+ if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
658
+ const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
659
+ if (parts.length === 0) return "Unknown";
660
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
661
+ }
662
+ /**
663
+ * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
664
+ * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
665
+ */
666
+ function getPerContentTypeName(baseName, suffix) {
667
+ if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
668
+ return baseName + suffix;
669
+ }
670
+ /**
671
+ * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
672
+ * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
673
+ * the final (possibly counter-augmented) value, so callers can derive parallel names in another
674
+ * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
675
+ */
676
+ function resolveContentTypeVariants(entries, baseName) {
677
+ const usedNames = /* @__PURE__ */ new Set();
678
+ return entries.filter((entry) => entry.schema).map((entry) => {
679
+ const baseSuffix = getContentTypeSuffix(entry.contentType);
680
+ let suffix = baseSuffix;
681
+ let name = getPerContentTypeName(baseName, suffix);
682
+ let counter = 2;
683
+ while (usedNames.has(name)) {
684
+ suffix = `${baseSuffix}${counter++}`;
685
+ name = getPerContentTypeName(baseName, suffix);
686
+ }
687
+ usedNames.add(name);
688
+ return {
689
+ name,
690
+ suffix,
691
+ schema: entry.schema,
692
+ keysToOmit: entry.keysToOmit,
693
+ contentType: entry.contentType
694
+ };
695
+ });
696
+ }
697
+ function getOperationParameters(node) {
698
+ return {
699
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
700
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
701
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
702
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
703
+ };
704
+ }
705
+ //#endregion
706
+ //#region ../../internals/shared/src/resolver.ts
707
+ /**
708
+ * Resolves a single operation parameter name with the
709
+ * `<operationId> <in> <name>` template.
710
+ *
711
+ * @example
712
+ * `operationParamName.call(resolver, node, param) // → 'DeletePetPathPetId'`
713
+ */
714
+ function operationParamName(node, param) {
715
+ return this.name(`${node.operationId} ${param.in} ${param.name}`);
716
+ }
717
+ /**
718
+ * Builds the shared `param` namespace. Spread the result into `createResolver`
719
+ * and override individual methods next to it when a plugin deviates.
720
+ *
721
+ * @example
722
+ * ```ts
723
+ * createResolver<PluginTs>({ param: createOperationParamResolver(), ... })
724
+ * ```
725
+ */
726
+ function createOperationParamResolver() {
727
+ return {
728
+ name: operationParamName,
729
+ path(node) {
730
+ return this.name(`${node.operationId} Path`);
731
+ },
732
+ query(node) {
733
+ return this.name(`${node.operationId} Query`);
734
+ },
735
+ headers(node) {
736
+ return this.name(`${node.operationId} Headers`);
737
+ }
738
+ };
739
+ }
740
+ /**
741
+ * Builds the shared `response` namespace. Spread the result into
742
+ * `createResolver` and add plugin-specific methods (`options`, `error`) next
743
+ * to it.
744
+ *
745
+ * @example
746
+ * ```ts
747
+ * createResolver<PluginTs>({ response: { ...createOperationResponseResolver(), options(node) {...} }, ... })
748
+ * ```
749
+ */
750
+ function createOperationResponseResolver() {
751
+ return {
752
+ status(node, statusCode) {
753
+ return this.name(`${node.operationId} Status ${statusCode}`);
754
+ },
755
+ body(node) {
756
+ return this.name(`${node.operationId} Body`);
757
+ },
758
+ responses(node) {
759
+ return this.name(`${node.operationId} Responses`);
760
+ },
761
+ response(node) {
762
+ return this.name(`${node.operationId} Response`);
763
+ }
764
+ };
765
+ }
766
+ /**
767
+ * Builds a resolver `file` override whose base name runs every path segment
768
+ * through `toFilePath`, casing the final segment with `caseLast`.
769
+ *
770
+ * @example
771
+ * ```ts
772
+ * createResolver<PluginTs>({ file: createCasedFile(pascalCase), ... })
773
+ * ```
774
+ */
775
+ function createCasedFile(caseLast) {
776
+ return { baseName({ name, extname }) {
777
+ return `${toFilePath(name, caseLast)}${extname}`;
778
+ } };
779
+ }
780
+ //#endregion
781
+ //#region ../../internals/shared/src/group.ts
782
+ /**
783
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
784
+ * shared default naming so every plugin groups output consistently:
785
+ *
786
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
787
+ * - other groups use the camelCased group (`pet store` → `petStore`).
788
+ *
789
+ * A user-provided `group.name` always wins over the default namer, so callers stay in
790
+ * control of their output folders. Returns `null` when grouping is disabled, matching the
791
+ * per-plugin convention.
792
+ *
793
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
794
+ *
795
+ * @example
796
+ * ```ts
797
+ * createGroupConfig(group) // shared across every plugin
798
+ * ```
799
+ */
800
+ function createGroupConfig(group) {
801
+ if (!group) return null;
802
+ const defaultName = (ctx) => {
803
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
804
+ return camelCase(ctx.group);
805
+ };
806
+ return {
807
+ ...group,
808
+ name: group.name ? group.name : defaultName
809
+ };
810
+ }
811
+ //#endregion
812
+ //#region ../../internals/shared/src/schemaTraversal.ts
813
+ /**
814
+ * Maps each member of a union or intersection schema to its transformed output, pairing every
815
+ * result with the original member.
816
+ */
817
+ function mapSchemaMembers(node, transform) {
818
+ return (node.members ?? []).map((schema) => ({
819
+ schema,
820
+ output: transform(schema)
821
+ }));
822
+ }
823
+ /**
824
+ * Maps each item of an array or tuple schema to its transformed output, pairing every result with
825
+ * the original item.
826
+ */
827
+ function mapSchemaItems(node, transform) {
828
+ return (node.items ?? []).map((schema) => ({
829
+ schema,
830
+ output: transform(schema)
831
+ }));
832
+ }
833
+ //#endregion
834
+ //#region src/printers/printerFaker.ts
835
+ const fakerKeywordMapper = {
836
+ any: () => "undefined",
837
+ unknown: () => "undefined",
838
+ void: () => "undefined",
839
+ number: (min, max) => {
840
+ if (max !== void 0 && min !== void 0) return `faker.number.float({ min: ${min}, max: ${max} })`;
841
+ if (max !== void 0) return `faker.number.float({ max: ${max} })`;
842
+ if (min !== void 0) return `faker.number.float({ min: ${min} })`;
843
+ return "faker.number.float()";
844
+ },
845
+ integer: (min, max) => {
846
+ if (max !== void 0 && min !== void 0) return `faker.number.int({ min: ${min}, max: ${max} })`;
847
+ if (max !== void 0) return `faker.number.int({ max: ${max} })`;
848
+ if (min !== void 0) return `faker.number.int({ min: ${min} })`;
849
+ return "faker.number.int()";
850
+ },
851
+ bigint: () => "faker.number.bigInt()",
852
+ string: (min, max) => {
853
+ if (max !== void 0 && min !== void 0) return `faker.string.alpha({ length: { min: ${min}, max: ${max} } })`;
854
+ if (max !== void 0) return `faker.string.alpha({ length: ${max} })`;
855
+ if (min !== void 0) return `faker.string.alpha({ length: ${min} })`;
856
+ return "faker.string.alpha()";
857
+ },
858
+ boolean: () => "faker.datatype.boolean()",
859
+ null: () => "null",
860
+ array: (items = [], min, max) => {
861
+ if (items.length > 1) return `faker.helpers.arrayElements([${items.join(", ")}])`;
862
+ const item = items.at(0);
863
+ if (min !== void 0 && max !== void 0) return `faker.helpers.multiple(() => (${item}), { count: { min: ${min}, max: ${max} }})`;
864
+ if (min !== void 0) return `faker.helpers.multiple(() => (${item}), { count: ${min} })`;
865
+ if (max !== void 0) return `faker.helpers.multiple(() => (${item}), { count: { min: 0, max: ${max} }})`;
866
+ return `faker.helpers.multiple(() => (${item}))`;
867
+ },
868
+ tuple: (items = []) => `[${items.join(", ")}]`,
869
+ enum: (items = [], type) => `faker.helpers.arrayElement${type ? `<${type}>` : ""}([${items.join(", ")}])`,
870
+ union: (items = []) => `faker.helpers.arrayElement([${items.join(", ")}])`,
871
+ datetime: () => "faker.date.anytime().toISOString()",
872
+ date: (representation = "string", parser = "faker") => {
873
+ if (representation === "string") {
874
+ if (parser !== "faker") return `${parser}(faker.date.anytime()).format("YYYY-MM-DD")`;
875
+ return "faker.date.anytime().toISOString().substring(0, 10)";
876
+ }
877
+ if (parser !== "faker") throw new Error(`type '${representation}' and parser '${parser}' can not work together`);
878
+ return "faker.date.anytime()";
879
+ },
880
+ time: (representation = "string", parser = "faker") => {
881
+ if (representation === "string") {
882
+ if (parser !== "faker") return `${parser}(faker.date.anytime()).format("HH:mm:ss")`;
883
+ return "faker.date.anytime().toISOString().substring(11, 19)";
884
+ }
885
+ if (parser !== "faker") throw new Error(`type '${representation}' and parser '${parser}' can not work together`);
886
+ return "faker.date.anytime()";
887
+ },
888
+ uuid: () => "faker.string.uuid()",
889
+ url: () => "faker.internet.url()",
890
+ and: (items = []) => {
891
+ if (items.length === 0) return "{}";
892
+ if (items.length === 1) return items[0] ?? "{}";
893
+ return `{...${items.join(", ...")}}`;
894
+ },
895
+ matches: (value = "", regexGenerator = "faker") => {
896
+ if (regexGenerator === "randexp") return `${toRegExpString(value, "RandExp")}.gen()`;
897
+ return `faker.helpers.fromRegExp("${value}")`;
898
+ },
899
+ email: () => "faker.internet.email()",
900
+ blob: () => "faker.image.url() as unknown as Blob"
901
+ };
902
+ function getEnumValues(node) {
903
+ if (node.namedEnumValues?.length) return node.namedEnumValues.map((item) => item.value);
904
+ return node.enumValues ?? [];
905
+ }
906
+ function parseEnumValue(value) {
907
+ if (typeof value === "string") return stringify(value);
908
+ return value;
909
+ }
910
+ /**
911
+ * Reads the discriminator literal off a variant, or `undefined` when it can't be determined.
912
+ */
913
+ function getDiscriminatorValue(member, discriminatorPropertyName) {
914
+ const prop = ast.narrowSchema(member, "object")?.properties?.find((p) => p.name === discriminatorPropertyName);
915
+ const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
916
+ return enumNode ? getEnumValues(enumNode)[0] : void 0;
917
+ }
918
+ /**
919
+ * Type expression for an object property's value, indexed off the parent `typeName`.
920
+ *
921
+ * In a union (`oneOf`), a key that only some branches declare turns a plain `NonNullable<T>[K]`
922
+ * into a TS2339 error, so union members guard the access. The breakdown is below.
923
+ */
924
+ function indexedTypeName(typeName, propertyName, nestedInUnion) {
925
+ const key = JSON.stringify(propertyName);
926
+ return nestedInUnion ? `(NonNullable<${typeName}> & Record<${key}, unknown>)[${key}]` : `NonNullable<${typeName}>[${key}]`;
927
+ }
928
+ /**
929
+ * Creates a Faker printer that generates mock data generation code from schema nodes.
930
+ * Handles circular references gracefully by emitting memoizing getters for cyclic properties.
931
+ */
932
+ const printerFaker = ast.createPrinter((options) => {
933
+ const printNested = (node, overrideOptions = {}) => {
934
+ return printerFaker({
935
+ ...options,
936
+ ...overrideOptions,
937
+ nodes: options.nodes
938
+ }).print(node) ?? "undefined";
939
+ };
940
+ return {
941
+ name: "faker",
942
+ options,
943
+ nodes: {
944
+ any: () => fakerKeywordMapper.any(),
945
+ unknown: () => fakerKeywordMapper.unknown(),
946
+ void: () => fakerKeywordMapper.void(),
947
+ boolean: () => fakerKeywordMapper.boolean(),
948
+ null: () => fakerKeywordMapper.null(),
949
+ string(node) {
950
+ if (node.pattern) return fakerKeywordMapper.matches(node.pattern, this.options.regexGenerator);
951
+ return fakerKeywordMapper.string(node.min, node.max);
952
+ },
953
+ email: () => fakerKeywordMapper.email(),
954
+ url: () => fakerKeywordMapper.url(),
955
+ uuid: () => fakerKeywordMapper.uuid(),
956
+ number(node) {
957
+ return fakerKeywordMapper.number(node.min, node.max);
958
+ },
959
+ integer(node) {
960
+ return fakerKeywordMapper.integer(node.min, node.max);
961
+ },
962
+ bigint: () => fakerKeywordMapper.bigint(),
963
+ blob: () => fakerKeywordMapper.blob(),
964
+ datetime: () => fakerKeywordMapper.datetime(),
965
+ date(node) {
966
+ return fakerKeywordMapper.date(node.representation ?? "string", this.options.dateParser);
967
+ },
968
+ time(node) {
969
+ return fakerKeywordMapper.time(node.representation ?? "string", this.options.dateParser);
970
+ },
971
+ ref(node) {
972
+ const refName = ast.resolveRefName(node);
973
+ if (!refName) throw new Error("Name not defined for ref node");
974
+ if (this.options.schemaName && refName === this.options.schemaName) return this.options.typeName ? `undefined as unknown as ${this.options.typeName}` : "undefined as unknown";
975
+ const resolvedName = node.ref ? this.options.resolver.name(refName) : refName;
976
+ if (!this.options.nestedInObject) return `${resolvedName}(data)`;
977
+ return `${resolvedName}()`;
978
+ },
979
+ enum(node) {
980
+ return fakerKeywordMapper.enum(getEnumValues(node).map(parseEnumValue), this.options.typeName);
981
+ },
982
+ union(node) {
983
+ const { discriminatorPropertyName } = node;
984
+ const baseTypeName = this.options.typeName;
985
+ const items = mapSchemaMembers(node, (member) => {
986
+ const value = discriminatorPropertyName ? getDiscriminatorValue(member, discriminatorPropertyName) : void 0;
987
+ if (baseTypeName && value !== void 0) {
988
+ const typeName = `Extract<NonNullable<${baseTypeName}>, { ${JSON.stringify(discriminatorPropertyName)}: ${parseEnumValue(value)} }>`;
989
+ return printNested(member, {
990
+ typeName,
991
+ nestedInObject: true
992
+ });
993
+ }
994
+ return printNested(member, {
995
+ typeName: baseTypeName,
996
+ nestedInObject: true,
997
+ nestedInUnion: true
998
+ });
999
+ }).map(({ output }) => output).filter((item) => Boolean(item));
1000
+ return fakerKeywordMapper.union(items);
1001
+ },
1002
+ intersection(node) {
1003
+ const items = mapSchemaMembers(node, (member) => printNested(member, { nestedInObject: true })).map(({ output }) => output).filter((item) => Boolean(item) && item !== "undefined");
1004
+ return fakerKeywordMapper.and(items);
1005
+ },
1006
+ array(node) {
1007
+ const items = mapSchemaItems(node, (member) => printNested(member, {
1008
+ typeName: this.options.typeName ? `NonNullable<${this.options.typeName}>[number]` : void 0,
1009
+ nestedInObject: true
1010
+ })).map(({ output }) => output).filter((item) => Boolean(item));
1011
+ return fakerKeywordMapper.array(items, node.min, node.max);
1012
+ },
1013
+ tuple(node) {
1014
+ const items = (node.items ?? []).map((member, index) => printNested(member, {
1015
+ typeName: this.options.typeName ? `NonNullable<${this.options.typeName}>[${index}]` : void 0,
1016
+ nestedInObject: true
1017
+ })).filter((item) => Boolean(item));
1018
+ return fakerKeywordMapper.tuple(items);
1019
+ },
1020
+ object(node) {
1021
+ const cyclicSchemas = this.options.cyclicSchemas;
1022
+ return buildObject((node.properties ?? []).map((property) => {
1023
+ const value = printNested(property.schema, {
1024
+ typeName: this.options.typeName ? indexedTypeName(this.options.typeName, property.name, this.options.nestedInUnion) : void 0,
1025
+ nestedInObject: true
1026
+ }) ?? "undefined";
1027
+ if (cyclicSchemas && containsCircularRef(property.schema, {
1028
+ circularSchemas: cyclicSchemas,
1029
+ excludeName: this.options.schemaName
1030
+ })) return `get ${objectKey(property.name)}() { const _value = ${value}; Object.defineProperty(this, ${JSON.stringify(property.name)}, { value: _value, configurable: true, writable: true, enumerable: true }); return _value }`;
1031
+ return `${objectKey(property.name)}: ${value}`;
1032
+ }));
1033
+ },
1034
+ ...options.nodes
1035
+ },
1036
+ print(node) {
1037
+ return this.transform(node) ?? null;
1038
+ }
1039
+ };
1040
+ });
1041
+ //#endregion
1042
+ //#region src/generators/fakerGenerator.tsx
1043
+ /**
1044
+ * Built-in generator for `@kubb/plugin-faker`. Emits one `createX` factory
1045
+ * per schema in the spec plus per-operation request/response factories. Each
1046
+ * factory returns a value matching the corresponding TypeScript type from
1047
+ * `@kubb/plugin-ts`.
1048
+ */
1049
+ const fakerGenerator = defineGenerator({
1050
+ name: "faker",
1051
+ renderer: jsxRenderer,
1052
+ schema(node, ctx) {
1053
+ const { config, resolver, root } = ctx;
1054
+ const { output, group, dateParser, regexGenerator, seed, locale, printer } = ctx.options;
1055
+ const pluginTs = ctx.driver.getPlugin(pluginTsName);
1056
+ if (!node.name || !pluginTs) return;
1057
+ const tsResolver = ctx.driver.getResolver(pluginTsName);
1058
+ const schemaName = node.name;
1059
+ const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
1060
+ const tsEnumType = pluginTs.options?.enum?.type;
1061
+ const tsEnumTypeSuffix = pluginTs.options?.enum?.typeSuffix ?? "Key";
1062
+ const schemaTypeName = isEnumSchema && tsEnumType === "asConst" ? tsResolver.enum.keyName({ name: schemaName }, tsEnumTypeSuffix) : tsResolver.name(schemaName);
1063
+ const meta = {
1064
+ name: resolver.name(schemaName),
1065
+ file: resolver.file({
1066
+ name: schemaName,
1067
+ extname: ".ts",
1068
+ root,
1069
+ output,
1070
+ group: group ?? void 0
1071
+ }),
1072
+ typeName: schemaTypeName,
1073
+ typeFile: tsResolver.file({
1074
+ name: schemaName,
1075
+ extname: ".ts",
1076
+ root,
1077
+ output: pluginTs.options?.output ?? output,
1078
+ group: pluginTs.options?.group ?? void 0
1079
+ })
1080
+ };
1081
+ const canOverride = canOverrideSchema(node);
1082
+ const cyclicSchemas = new Set(ctx.meta.circularNames);
1083
+ const printerInstance = printerFaker({
1084
+ resolver,
1085
+ schemaName,
1086
+ typeName: meta.typeName,
1087
+ dateParser,
1088
+ regexGenerator,
1089
+ nodes: printer?.nodes,
1090
+ cyclicSchemas
1091
+ });
1092
+ const fakerText = printerInstance.print(node) ?? "undefined";
1093
+ const typeReference = resolveTypeReference({
1094
+ node,
1095
+ canOverride,
1096
+ name: meta.name,
1097
+ typeName: meta.typeName,
1098
+ filePath: meta.file.path,
1099
+ typeFilePath: meta.typeFile.path
1100
+ });
1101
+ const usedImports = filterUsedImports(resolver.imports({
1102
+ node,
1103
+ root,
1104
+ output,
1105
+ group: group ?? void 0
1106
+ }), fakerText);
1107
+ return /* @__PURE__ */ jsxs(File, {
1108
+ baseName: meta.file.baseName,
1109
+ path: meta.file.path,
1110
+ meta: meta.file.meta,
1111
+ banner: resolver.default.banner(ctx.meta, {
1112
+ output,
1113
+ config,
1114
+ file: {
1115
+ path: meta.file.path,
1116
+ baseName: meta.file.baseName
1117
+ }
1118
+ }),
1119
+ footer: resolver.default.footer(ctx.meta, {
1120
+ output,
1121
+ config,
1122
+ file: {
1123
+ path: meta.file.path,
1124
+ baseName: meta.file.baseName
1125
+ }
1126
+ }),
1127
+ children: [
1128
+ /* @__PURE__ */ jsx(File.Import, {
1129
+ name: locale ? [{
1130
+ propertyName: localeToFakerImport(locale),
1131
+ name: "faker"
1132
+ }] : ["faker"],
1133
+ path: "@faker-js/faker"
1134
+ }),
1135
+ regexGenerator === "randexp" && /* @__PURE__ */ jsx(File.Import, {
1136
+ name: "RandExp",
1137
+ path: "randexp"
1138
+ }),
1139
+ dateParser !== "faker" && /* @__PURE__ */ jsx(File.Import, {
1140
+ path: dateParser,
1141
+ name: dateParser
1142
+ }),
1143
+ typeReference.importPath && /* @__PURE__ */ jsx(File.Import, {
1144
+ isTypeOnly: true,
1145
+ root: meta.file.path,
1146
+ path: typeReference.importPath,
1147
+ name: [meta.typeName]
1148
+ }),
1149
+ usedImports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1150
+ root: meta.file.path,
1151
+ path: imp.path,
1152
+ name: imp.name
1153
+ }, [
1154
+ schemaName,
1155
+ imp.path,
1156
+ imp.name
1157
+ ].join("-"))),
1158
+ /* @__PURE__ */ jsx(Faker, {
1159
+ name: meta.name,
1160
+ typeName: typeReference.typeName,
1161
+ description: node.description,
1162
+ node,
1163
+ printer: printerInstance,
1164
+ seed,
1165
+ canOverride
1166
+ })
1167
+ ]
1168
+ });
1169
+ },
1170
+ operation(node, ctx) {
1171
+ const { config, resolver, root } = ctx;
1172
+ const { output, group, dateParser, regexGenerator, seed, locale, printer } = ctx.options;
1173
+ const pluginTs = ctx.driver.getPlugin(pluginTsName);
1174
+ if (!pluginTs) return;
1175
+ const tsResolver = ctx.driver.getResolver(pluginTsName);
1176
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
1177
+ const paramGroups = [
1178
+ {
1179
+ params: pathParams,
1180
+ name: resolver.param.path,
1181
+ typeName: tsResolver.param.path
1182
+ },
1183
+ {
1184
+ params: queryParams,
1185
+ name: resolver.param.query,
1186
+ typeName: tsResolver.param.query
1187
+ },
1188
+ {
1189
+ params: headerParams,
1190
+ name: resolver.param.headers,
1191
+ typeName: tsResolver.param.headers
1192
+ }
1193
+ ].filter((group) => group.params.length > 0).map((group) => ({
1194
+ schema: buildParams({ params: group.params }),
1195
+ name: group.name(node, group.params[0]),
1196
+ typeName: group.typeName(node, group.params[0])
1197
+ }));
1198
+ function expandContentUnits(entries, baseName, tsBaseName, description, decorate) {
1199
+ const withSchema = entries.filter((entry) => entry.schema);
1200
+ if (withSchema.length <= 1) {
1201
+ const primary = withSchema[0] ?? entries[0];
1202
+ if (!primary?.schema) return [];
1203
+ return [{
1204
+ schema: decorate ? decorate(primary.schema) : primary.schema,
1205
+ name: baseName,
1206
+ typeName: tsBaseName,
1207
+ description,
1208
+ skipImportNames: []
1209
+ }];
1210
+ }
1211
+ const variants = resolveContentTypeVariants(entries, baseName);
1212
+ const unionSchema = ast.factory.createSchema({
1213
+ type: "union",
1214
+ members: variants.map((variant) => ast.factory.createSchema({
1215
+ type: "ref",
1216
+ name: variant.name
1217
+ }))
1218
+ });
1219
+ return [...variants.map((variant) => ({
1220
+ schema: decorate ? decorate(variant.schema) : variant.schema,
1221
+ name: variant.name,
1222
+ typeName: getPerContentTypeName(tsBaseName, variant.suffix),
1223
+ description,
1224
+ skipImportNames: []
1225
+ })), {
1226
+ schema: unionSchema,
1227
+ name: baseName,
1228
+ typeName: tsBaseName,
1229
+ description,
1230
+ skipImportNames: variants.map((variant) => variant.name)
1231
+ }];
1232
+ }
1233
+ const responseUnits = node.responses.flatMap((response) => expandContentUnits(response.content ?? [], resolver.response.status(node, response.statusCode), tsResolver.response.status(node, response.statusCode), response.description));
1234
+ const dataUnits = expandContentUnits(node.requestBody?.content ?? [], resolver.response.body(node), tsResolver.response.body(node), node.requestBody?.description, (schema) => ({
1235
+ ...schema,
1236
+ description: node.requestBody?.description ?? schema.description
1237
+ }));
1238
+ const responseName = resolver.response.response(node);
1239
+ const localHelperNames = /* @__PURE__ */ new Set([
1240
+ ...paramGroups.map((group) => group.name),
1241
+ ...responseUnits.map((unit) => unit.name),
1242
+ ...dataUnits.map((unit) => unit.name),
1243
+ responseName
1244
+ ]);
1245
+ const cyclicSchemas = new Set(ctx.meta.circularNames);
1246
+ const meta = {
1247
+ file: resolver.file({
1248
+ name: node.operationId,
1249
+ extname: ".ts",
1250
+ tag: node.tags[0] ?? "default",
1251
+ path: node.path,
1252
+ root,
1253
+ output,
1254
+ group: group ?? void 0
1255
+ }),
1256
+ typeFile: tsResolver.file({
1257
+ name: node.operationId,
1258
+ extname: ".ts",
1259
+ tag: node.tags[0] ?? "default",
1260
+ path: node.path,
1261
+ root,
1262
+ output: pluginTs.options?.output ?? output,
1263
+ group: pluginTs.options?.group ?? void 0
1264
+ })
1265
+ };
1266
+ function resolveMockImports(schema) {
1267
+ return resolver.imports({
1268
+ node: schema,
1269
+ root,
1270
+ output,
1271
+ group: group ?? void 0
1272
+ }).filter((entry) => entry.path !== meta.file.path);
1273
+ }
1274
+ function renderEntry({ schema, name, typeName, description, skipImportNames = [] }) {
1275
+ if (!schema) return null;
1276
+ const canOverride = canOverrideSchema(schema);
1277
+ const printerInstance = printerFaker({
1278
+ resolver,
1279
+ schemaName: name,
1280
+ typeName,
1281
+ dateParser,
1282
+ regexGenerator,
1283
+ nodes: printer?.nodes,
1284
+ cyclicSchemas
1285
+ });
1286
+ const fakerText = printerInstance.print(schema) ?? "undefined";
1287
+ const { imports, aliases } = aliasConflictingImports(filterUsedImports(resolveMockImports(schema), fakerText, skipImportNames), localHelperNames);
1288
+ const rewrittenFakerText = rewriteAliasedImports(fakerText, aliases);
1289
+ const typeReference = resolveTypeReference({
1290
+ node: schema,
1291
+ canOverride,
1292
+ name,
1293
+ typeName,
1294
+ filePath: meta.file.path,
1295
+ typeFilePath: meta.typeFile.path
1296
+ });
1297
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1298
+ typeReference.importPath && /* @__PURE__ */ jsx(File.Import, {
1299
+ isTypeOnly: true,
1300
+ root: meta.file.path,
1301
+ path: typeReference.importPath,
1302
+ name: [typeName]
1303
+ }),
1304
+ imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1305
+ root: meta.file.path,
1306
+ path: imp.path,
1307
+ name: imp.name
1308
+ }, [
1309
+ name,
1310
+ imp.path,
1311
+ imp.name
1312
+ ].join("-"))),
1313
+ /* @__PURE__ */ jsx(Faker, {
1314
+ name,
1315
+ typeName: typeReference.typeName,
1316
+ description,
1317
+ node: schema,
1318
+ printer: {
1319
+ ...printerInstance,
1320
+ print: () => rewrittenFakerText
1321
+ },
1322
+ seed,
1323
+ canOverride
1324
+ })
1325
+ ] });
1326
+ }
1327
+ return /* @__PURE__ */ jsxs(File, {
1328
+ baseName: meta.file.baseName,
1329
+ path: meta.file.path,
1330
+ meta: meta.file.meta,
1331
+ banner: resolver.default.banner(ctx.meta, {
1332
+ output,
1333
+ config,
1334
+ file: {
1335
+ path: meta.file.path,
1336
+ baseName: meta.file.baseName
1337
+ }
1338
+ }),
1339
+ footer: resolver.default.footer(ctx.meta, {
1340
+ output,
1341
+ config,
1342
+ file: {
1343
+ path: meta.file.path,
1344
+ baseName: meta.file.baseName
1345
+ }
1346
+ }),
1347
+ children: [
1348
+ /* @__PURE__ */ jsx(File.Import, {
1349
+ name: locale ? [{
1350
+ propertyName: localeToFakerImport(locale),
1351
+ name: "faker"
1352
+ }] : ["faker"],
1353
+ path: "@faker-js/faker"
1354
+ }),
1355
+ regexGenerator === "randexp" && /* @__PURE__ */ jsx(File.Import, {
1356
+ name: "RandExp",
1357
+ path: "randexp"
1358
+ }),
1359
+ dateParser !== "faker" && /* @__PURE__ */ jsx(File.Import, {
1360
+ path: dateParser,
1361
+ name: dateParser
1362
+ }),
1363
+ paramGroups.map((group) => renderEntry(group)),
1364
+ responseUnits.map((unit) => renderEntry({
1365
+ schema: unit.schema,
1366
+ name: unit.name,
1367
+ typeName: unit.typeName,
1368
+ description: unit.description,
1369
+ skipImportNames: unit.skipImportNames
1370
+ })),
1371
+ dataUnits.map((unit) => renderEntry({
1372
+ schema: unit.schema,
1373
+ name: unit.name,
1374
+ typeName: unit.typeName,
1375
+ description: unit.description,
1376
+ skipImportNames: unit.skipImportNames
1377
+ })),
1378
+ renderEntry({
1379
+ schema: buildResponseUnionSchema(node, resolver),
1380
+ name: responseName,
1381
+ typeName: tsResolver.response.response(node),
1382
+ skipImportNames: responseUnits.map((unit) => unit.name)
1383
+ })
1384
+ ]
1385
+ });
1386
+ }
1387
+ });
1388
+ //#endregion
1389
+ //#region src/resolvers/resolverFaker.ts
1390
+ /**
1391
+ * Default resolver used by `@kubb/plugin-faker`. Decides the names and file
1392
+ * paths for every generated mock factory. Functions and files are prefixed
1393
+ * with `create` so `Pet` becomes `createPet`.
1394
+ *
1395
+ * @example Resolve a factory name
1396
+ * ```ts
1397
+ * import { resolverFaker } from '@kubb/plugin-faker'
1398
+ *
1399
+ * resolverFaker.name('list pets') // 'createListPets'
1400
+ * ```
1401
+ */
1402
+ const resolverFaker = createResolver({
1403
+ pluginName: "plugin-faker",
1404
+ name(name) {
1405
+ return ensureValidVarName(camelCase(name, { prefix: "create" }));
1406
+ },
1407
+ file: createCasedFile((part) => camelCase(part, { prefix: "create" })),
1408
+ param: createOperationParamResolver(),
1409
+ response: createOperationResponseResolver()
1410
+ });
1411
+ //#endregion
47
1412
  //#region src/plugin.ts
1413
+ /**
1414
+ * Canonical plugin name for `@kubb/plugin-faker`. Used for driver lookups and
1415
+ * cross-plugin dependency references.
1416
+ */
48
1417
  const pluginFakerName = "plugin-faker";
49
- const pluginFaker = createPlugin((options) => {
1418
+ /**
1419
+ * Generates one mock-data factory per OpenAPI schema using Faker.js. Call
1420
+ * `createPet()` to get a realistic `Pet` object. Useful for tests, Storybook,
1421
+ * and local development without a running backend.
1422
+ *
1423
+ * @example
1424
+ * ```ts
1425
+ * import { defineConfig } from 'kubb/config'
1426
+ * import { pluginTs } from '@kubb/plugin-ts'
1427
+ * import { pluginFaker } from '@kubb/plugin-faker'
1428
+ *
1429
+ * export default defineConfig({
1430
+ * input: './petStore.yaml',
1431
+ * output: { path: './src/gen' },
1432
+ * plugins: [
1433
+ * pluginTs(),
1434
+ * pluginFaker({
1435
+ * output: { path: './mocks' },
1436
+ * seed: [100],
1437
+ * }),
1438
+ * ],
1439
+ * })
1440
+ * ```
1441
+ */
1442
+ const pluginFaker = definePlugin((options) => {
50
1443
  const { output = {
51
1444
  path: "mocks",
52
- barrelType: "named"
53
- }, seed, group, exclude = [], include, override = [], transformers = {}, mapper = {}, unknownType = "any", emptySchemaType = unknownType, dateType = "string", integerType = "number", dateParser = "faker", generators = [fakerGenerator].filter(Boolean), regexGenerator = "faker", paramsCasing, contentType } = options;
1445
+ barrel: { type: "named" }
1446
+ }, seed, locale = "en", group, exclude = [], include, override = [], dateParser = "faker", regexGenerator = "faker", printer, resolver: userResolver, macros: userMacros } = options;
1447
+ const groupConfig = createGroupConfig(group);
54
1448
  return {
55
1449
  name: pluginFakerName,
56
- options: {
57
- output,
58
- transformers,
59
- seed,
60
- dateType,
61
- integerType,
62
- unknownType,
63
- emptySchemaType,
64
- dateParser,
65
- mapper,
66
- override,
67
- regexGenerator,
68
- paramsCasing,
69
- group,
70
- usedEnumNames: {}
71
- },
72
- pre: [pluginOasName, pluginTsName],
73
- resolvePath(baseName, pathMode, options) {
74
- const root = path.resolve(this.config.root, this.config.output.path);
75
- if ((pathMode ?? getMode(path.resolve(root, output.path))) === "single")
76
- /**
77
- * when output is a file then we will always append to the same file(output file), see fileManager.addOrAppend
78
- * Other plugins then need to call addOrAppend instead of just add from the fileManager class
79
- */
80
- return path.resolve(root, output.path);
81
- if (group && (options?.group?.path || options?.group?.tag)) {
82
- const groupName = group?.name ? group.name : (ctx) => {
83
- if (group?.type === "path") return `${ctx.group.split("/")[1]}`;
84
- return `${camelCase(ctx.group)}Controller`;
85
- };
86
- return path.resolve(root, output.path, groupName({ group: group.type === "path" ? options.group.path : options.group.tag }), baseName);
87
- }
88
- return path.resolve(root, output.path, baseName);
89
- },
90
- resolveName(name, type) {
91
- const resolvedName = camelCase(name, {
92
- prefix: type ? "create" : void 0,
93
- isFile: type === "file"
94
- });
95
- if (type) return transformers?.name?.(resolvedName, type) || resolvedName;
96
- return resolvedName;
97
- },
98
- async install() {
99
- const root = path.resolve(this.config.root, this.config.output.path);
100
- const mode = getMode(path.resolve(root, output.path));
101
- const oas = await this.getOas();
102
- const schemaFiles = await new SchemaGenerator(this.plugin.options, {
103
- fabric: this.fabric,
104
- oas,
105
- driver: this.driver,
106
- events: this.events,
107
- plugin: this.plugin,
108
- contentType,
109
- include: void 0,
110
- override,
111
- mode,
112
- output: output.path
113
- }).build(...generators);
114
- await this.upsertFile(...schemaFiles);
115
- const operationFiles = await new OperationGenerator(this.plugin.options, {
116
- fabric: this.fabric,
117
- oas,
118
- driver: this.driver,
119
- events: this.events,
120
- plugin: this.plugin,
121
- contentType,
1450
+ options,
1451
+ dependencies: [pluginTsName],
1452
+ hooks: { "kubb:plugin:setup"(ctx) {
1453
+ ctx.setOptions({
1454
+ output,
1455
+ seed,
1456
+ locale,
122
1457
  exclude,
123
1458
  include,
124
1459
  override,
125
- mode
126
- }).build(...generators);
127
- await this.upsertFile(...operationFiles);
128
- const barrelFiles = await getBarrelFiles(this.fabric.files, {
129
- type: output.barrelType ?? "named",
130
- root,
131
- output,
132
- meta: { pluginName: this.plugin.name }
1460
+ group: groupConfig,
1461
+ dateParser,
1462
+ regexGenerator,
1463
+ printer
133
1464
  });
134
- await this.upsertFile(...barrelFiles);
135
- }
1465
+ ctx.setResolver(userResolver ? Resolver.merge(resolverFaker, userResolver) : resolverFaker);
1466
+ if (userMacros?.length) ctx.setMacros(userMacros);
1467
+ ctx.addGenerator(fakerGenerator);
1468
+ } }
136
1469
  };
137
1470
  });
138
1471
  //#endregion
139
- export { pluginFaker, pluginFakerName };
1472
+ export { Faker, pluginFaker as default, pluginFaker, fakerGenerator, pluginFakerName, printerFaker, resolverFaker };
140
1473
 
141
1474
  //# sourceMappingURL=index.js.map