@kubb/plugin-faker 5.0.0-alpha.9 → 5.0.0-beta.10
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/LICENSE +17 -10
- package/README.md +25 -7
- package/dist/Faker-BMgoFj8b.d.ts +27 -0
- package/dist/Faker-CWtonujy.js +334 -0
- package/dist/Faker-CWtonujy.js.map +1 -0
- package/dist/Faker-D39THFJ-.cjs +418 -0
- package/dist/Faker-D39THFJ-.cjs.map +1 -0
- package/dist/components.cjs +2 -2
- package/dist/components.d.ts +2 -31
- package/dist/components.js +1 -1
- package/dist/fakerGenerator-B-QnVz9o.d.ts +9 -0
- package/dist/fakerGenerator-B-XuVREg.cjs +570 -0
- package/dist/fakerGenerator-B-XuVREg.cjs.map +1 -0
- package/dist/fakerGenerator-DH6hN3yb.js +560 -0
- package/dist/fakerGenerator-DH6hN3yb.js.map +1 -0
- package/dist/generators.cjs +1 -1
- package/dist/generators.d.ts +2 -505
- package/dist/generators.js +1 -1
- package/dist/index.cjs +237 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +28 -4
- package/dist/index.js +229 -83
- package/dist/index.js.map +1 -1
- package/dist/printerFaker-W0pLunAj.d.ts +213 -0
- package/extension.yaml +357 -0
- package/package.json +48 -51
- package/src/components/Faker.tsx +124 -78
- package/src/generators/fakerGenerator.tsx +233 -134
- package/src/index.ts +7 -2
- package/src/plugin.ts +60 -121
- package/src/printers/printerFaker.ts +341 -0
- package/src/resolvers/resolverFaker.ts +85 -0
- package/src/types.ts +134 -81
- package/src/utils.ts +268 -0
- package/dist/components-BkBIov4R.js +0 -419
- package/dist/components-BkBIov4R.js.map +0 -1
- package/dist/components-IdP8GXXX.cjs +0 -461
- package/dist/components-IdP8GXXX.cjs.map +0 -1
- package/dist/fakerGenerator-CYUCNH3Q.cjs +0 -204
- package/dist/fakerGenerator-CYUCNH3Q.cjs.map +0 -1
- package/dist/fakerGenerator-M5oCrPmy.js +0 -200
- package/dist/fakerGenerator-M5oCrPmy.js.map +0 -1
- package/dist/types-r7BubMLO.d.ts +0 -132
- package/src/parser.ts +0 -453
package/src/plugin.ts
CHANGED
|
@@ -1,149 +1,88 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
1
|
import { camelCase } from '@internals/utils'
|
|
3
|
-
import {
|
|
4
|
-
import { OperationGenerator, pluginOasName, SchemaGenerator } from '@kubb/plugin-oas'
|
|
2
|
+
import { definePlugin, type Group } from '@kubb/core'
|
|
5
3
|
import { pluginTsName } from '@kubb/plugin-ts'
|
|
6
4
|
import { fakerGenerator } from './generators/fakerGenerator.tsx'
|
|
5
|
+
import { resolverFaker } from './resolvers/resolverFaker.ts'
|
|
7
6
|
import type { PluginFaker } from './types.ts'
|
|
8
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Canonical plugin name for `@kubb/plugin-faker`, used in driver lookups and warnings.
|
|
10
|
+
*/
|
|
9
11
|
export const pluginFakerName = 'plugin-faker' satisfies PluginFaker['name']
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Generates Faker mock data factories from OpenAPI/AST specification.
|
|
15
|
+
*
|
|
16
|
+
* Creates randomized test data and mock helpers from schema definitions.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* `import pluginFaker from '@kubb/plugin-faker'; export default defineConfig({ plugins: [pluginFaker({ output: { path: 'mocks' } })], })`
|
|
20
|
+
*/
|
|
21
|
+
export const pluginFaker = definePlugin<PluginFaker>((options) => {
|
|
12
22
|
const {
|
|
13
23
|
output = { path: 'mocks', barrelType: 'named' },
|
|
14
24
|
seed,
|
|
25
|
+
locale,
|
|
15
26
|
group,
|
|
16
27
|
exclude = [],
|
|
17
28
|
include,
|
|
18
29
|
override = [],
|
|
19
|
-
transformers = {},
|
|
20
30
|
mapper = {},
|
|
21
|
-
unknownType = 'any',
|
|
22
|
-
emptySchemaType = unknownType,
|
|
23
|
-
dateType = 'string',
|
|
24
|
-
integerType = 'number',
|
|
25
31
|
dateParser = 'faker',
|
|
26
|
-
generators = [
|
|
32
|
+
generators: userGenerators = [],
|
|
27
33
|
regexGenerator = 'faker',
|
|
28
34
|
paramsCasing,
|
|
29
|
-
|
|
35
|
+
printer,
|
|
36
|
+
resolver: userResolver,
|
|
37
|
+
transformer: userTransformer,
|
|
30
38
|
} = options
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
name: pluginFakerName,
|
|
37
|
-
options: {
|
|
38
|
-
output,
|
|
39
|
-
transformers,
|
|
40
|
-
seed,
|
|
41
|
-
dateType,
|
|
42
|
-
integerType,
|
|
43
|
-
unknownType,
|
|
44
|
-
emptySchemaType,
|
|
45
|
-
dateParser,
|
|
46
|
-
mapper,
|
|
47
|
-
override,
|
|
48
|
-
regexGenerator,
|
|
49
|
-
paramsCasing,
|
|
50
|
-
group,
|
|
51
|
-
usedEnumNames,
|
|
52
|
-
},
|
|
53
|
-
pre: [pluginOasName, pluginTsName],
|
|
54
|
-
resolvePath(baseName, pathMode, options) {
|
|
55
|
-
const root = path.resolve(this.config.root, this.config.output.path)
|
|
56
|
-
const mode = pathMode ?? getMode(path.resolve(root, output.path))
|
|
57
|
-
|
|
58
|
-
if (mode === 'single') {
|
|
59
|
-
/**
|
|
60
|
-
* when output is a file then we will always append to the same file(output file), see fileManager.addOrAppend
|
|
61
|
-
* Other plugins then need to call addOrAppend instead of just add from the fileManager class
|
|
62
|
-
*/
|
|
63
|
-
return path.resolve(root, output.path)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (group && (options?.group?.path || options?.group?.tag)) {
|
|
67
|
-
const groupName: Group['name'] = group?.name
|
|
40
|
+
const groupConfig = group
|
|
41
|
+
? ({
|
|
42
|
+
...group,
|
|
43
|
+
name: group.name
|
|
68
44
|
? group.name
|
|
69
|
-
: (ctx) => {
|
|
70
|
-
if (group
|
|
45
|
+
: (ctx: { group: string }) => {
|
|
46
|
+
if (group.type === 'path') {
|
|
71
47
|
return `${ctx.group.split('/')[1]}`
|
|
72
48
|
}
|
|
73
|
-
return `${camelCase(ctx.group)}Controller`
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return path.resolve(
|
|
77
|
-
root,
|
|
78
|
-
output.path,
|
|
79
|
-
groupName({
|
|
80
|
-
group: group.type === 'path' ? options.group.path! : options.group.tag!,
|
|
81
|
-
}),
|
|
82
|
-
baseName,
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return path.resolve(root, output.path, baseName)
|
|
87
|
-
},
|
|
88
|
-
resolveName(name, type) {
|
|
89
|
-
const resolvedName = camelCase(name, {
|
|
90
|
-
prefix: type ? 'create' : undefined,
|
|
91
|
-
isFile: type === 'file',
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
if (type) {
|
|
95
|
-
return transformers?.name?.(resolvedName, type) || resolvedName
|
|
96
|
-
}
|
|
97
49
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const mode = getMode(path.resolve(root, output.path))
|
|
103
|
-
const oas = await this.getOas()
|
|
104
|
-
|
|
105
|
-
const schemaGenerator = new SchemaGenerator(this.plugin.options, {
|
|
106
|
-
fabric: this.fabric,
|
|
107
|
-
oas,
|
|
108
|
-
driver: this.driver,
|
|
109
|
-
events: this.events,
|
|
110
|
-
plugin: this.plugin,
|
|
111
|
-
contentType,
|
|
112
|
-
include: undefined,
|
|
113
|
-
override,
|
|
114
|
-
mode,
|
|
115
|
-
output: output.path,
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
const schemaFiles = await schemaGenerator.build(...generators)
|
|
119
|
-
await this.upsertFile(...schemaFiles)
|
|
120
|
-
|
|
121
|
-
const operationGenerator = new OperationGenerator(this.plugin.options, {
|
|
122
|
-
fabric: this.fabric,
|
|
123
|
-
oas,
|
|
124
|
-
driver: this.driver,
|
|
125
|
-
events: this.events,
|
|
126
|
-
plugin: this.plugin,
|
|
127
|
-
contentType,
|
|
128
|
-
exclude,
|
|
129
|
-
include,
|
|
130
|
-
override,
|
|
131
|
-
mode,
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
const operationFiles = await operationGenerator.build(...generators)
|
|
135
|
-
await this.upsertFile(...operationFiles)
|
|
136
|
-
|
|
137
|
-
const barrelFiles = await getBarrelFiles(this.fabric.files, {
|
|
138
|
-
type: output.barrelType ?? 'named',
|
|
139
|
-
root,
|
|
140
|
-
output,
|
|
141
|
-
meta: {
|
|
142
|
-
pluginName: this.plugin.name,
|
|
143
|
-
},
|
|
144
|
-
})
|
|
50
|
+
return `${camelCase(ctx.group)}Controller`
|
|
51
|
+
},
|
|
52
|
+
} satisfies Group)
|
|
53
|
+
: undefined
|
|
145
54
|
|
|
146
|
-
|
|
55
|
+
return {
|
|
56
|
+
name: pluginFakerName,
|
|
57
|
+
options,
|
|
58
|
+
dependencies: [pluginTsName],
|
|
59
|
+
hooks: {
|
|
60
|
+
'kubb:plugin:setup'(ctx) {
|
|
61
|
+
ctx.setOptions({
|
|
62
|
+
output,
|
|
63
|
+
seed,
|
|
64
|
+
locale,
|
|
65
|
+
exclude,
|
|
66
|
+
include,
|
|
67
|
+
override,
|
|
68
|
+
group: groupConfig,
|
|
69
|
+
mapper,
|
|
70
|
+
dateParser,
|
|
71
|
+
regexGenerator,
|
|
72
|
+
paramsCasing,
|
|
73
|
+
printer,
|
|
74
|
+
})
|
|
75
|
+
ctx.setResolver(userResolver ? { ...resolverFaker, ...userResolver } : resolverFaker)
|
|
76
|
+
if (userTransformer) {
|
|
77
|
+
ctx.setTransformer(userTransformer)
|
|
78
|
+
}
|
|
79
|
+
ctx.addGenerator(fakerGenerator)
|
|
80
|
+
for (const generator of userGenerators) {
|
|
81
|
+
ctx.addGenerator(generator)
|
|
82
|
+
}
|
|
83
|
+
},
|
|
147
84
|
},
|
|
148
85
|
}
|
|
149
86
|
})
|
|
87
|
+
|
|
88
|
+
export default pluginFaker
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { stringify, toRegExpString } from '@internals/utils'
|
|
2
|
+
import { ast } from '@kubb/core'
|
|
3
|
+
import type { PluginFaker, ResolverFaker } from '../types.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Partial printer nodes for Faker generation, mapping schema types to output strings.
|
|
7
|
+
*/
|
|
8
|
+
export type PrinterFakerNodes = ast.PrinterPartial<string, PrinterFakerOptions>
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Configuration options for the Faker printer, including resolvers, mappers, and cyclic schema tracking.
|
|
12
|
+
*/
|
|
13
|
+
export type PrinterFakerOptions = {
|
|
14
|
+
dateParser?: PluginFaker['resolvedOptions']['dateParser']
|
|
15
|
+
regexGenerator?: PluginFaker['resolvedOptions']['regexGenerator']
|
|
16
|
+
mapper?: PluginFaker['resolvedOptions']['mapper']
|
|
17
|
+
resolver: ResolverFaker
|
|
18
|
+
typeName?: string
|
|
19
|
+
schemaName?: string
|
|
20
|
+
nestedInObject?: boolean
|
|
21
|
+
nodes?: PrinterFakerNodes
|
|
22
|
+
/**
|
|
23
|
+
* Names of schemas that participate in a circular dependency chain.
|
|
24
|
+
* Properties whose schema transitively references one of these are emitted
|
|
25
|
+
* as lazy getters so that user overrides via the `data` parameter prevent
|
|
26
|
+
* the recursive faker call from ever executing (avoiding stack overflow).
|
|
27
|
+
*/
|
|
28
|
+
cyclicSchemas?: ReadonlySet<string>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Factory options for the Faker printer, defining input/output types and configuration.
|
|
33
|
+
*/
|
|
34
|
+
export type PrinterFakerFactory = ast.PrinterFactoryOptions<'faker', PrinterFakerOptions, string, string>
|
|
35
|
+
|
|
36
|
+
const fakerKeywordMapper = {
|
|
37
|
+
any: () => 'undefined',
|
|
38
|
+
unknown: () => 'undefined',
|
|
39
|
+
void: () => 'undefined',
|
|
40
|
+
number: (min?: number, max?: number) => {
|
|
41
|
+
if (max !== undefined && min !== undefined) {
|
|
42
|
+
return `faker.number.float({ min: ${min}, max: ${max} })`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (max !== undefined) {
|
|
46
|
+
return `faker.number.float({ max: ${max} })`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (min !== undefined) {
|
|
50
|
+
return `faker.number.float({ min: ${min} })`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return 'faker.number.float()'
|
|
54
|
+
},
|
|
55
|
+
integer: (min?: number, max?: number) => {
|
|
56
|
+
if (max !== undefined && min !== undefined) {
|
|
57
|
+
return `faker.number.int({ min: ${min}, max: ${max} })`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (max !== undefined) {
|
|
61
|
+
return `faker.number.int({ max: ${max} })`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (min !== undefined) {
|
|
65
|
+
return `faker.number.int({ min: ${min} })`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return 'faker.number.int()'
|
|
69
|
+
},
|
|
70
|
+
bigint: () => 'faker.number.bigInt()',
|
|
71
|
+
string: (min?: number, max?: number) => {
|
|
72
|
+
if (max !== undefined && min !== undefined) {
|
|
73
|
+
return `faker.string.alpha({ length: { min: ${min}, max: ${max} } })`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (max !== undefined) {
|
|
77
|
+
return `faker.string.alpha({ length: ${max} })`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (min !== undefined) {
|
|
81
|
+
return `faker.string.alpha({ length: ${min} })`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return 'faker.string.alpha()'
|
|
85
|
+
},
|
|
86
|
+
boolean: () => 'faker.datatype.boolean()',
|
|
87
|
+
null: () => 'null',
|
|
88
|
+
array: (items: string[] = [], min?: number, max?: number) => {
|
|
89
|
+
if (items.length > 1) {
|
|
90
|
+
return `faker.helpers.arrayElements([${items.join(', ')}])`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const item = items.at(0)
|
|
94
|
+
|
|
95
|
+
if (min !== undefined && max !== undefined) {
|
|
96
|
+
return `faker.helpers.multiple(() => (${item}), { count: { min: ${min}, max: ${max} }})`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (min !== undefined) {
|
|
100
|
+
return `faker.helpers.multiple(() => (${item}), { count: ${min} })`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (max !== undefined) {
|
|
104
|
+
return `faker.helpers.multiple(() => (${item}), { count: { min: 0, max: ${max} }})`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return `faker.helpers.multiple(() => (${item}))`
|
|
108
|
+
},
|
|
109
|
+
tuple: (items: string[] = []) => `[${items.join(', ')}]`,
|
|
110
|
+
enum: (items: Array<string | number | boolean | undefined> = [], type = 'any') => `faker.helpers.arrayElement<${type}>([${items.join(', ')}])`,
|
|
111
|
+
union: (items: string[] = []) => `faker.helpers.arrayElement<any>([${items.join(', ')}])`,
|
|
112
|
+
datetime: () => 'faker.date.anytime().toISOString()',
|
|
113
|
+
date: (representation: 'date' | 'string' = 'string', parser: PluginFaker['resolvedOptions']['dateParser'] = 'faker') => {
|
|
114
|
+
if (representation === 'string') {
|
|
115
|
+
if (parser !== 'faker') {
|
|
116
|
+
return `${parser}(faker.date.anytime()).format("YYYY-MM-DD")`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return 'faker.date.anytime().toISOString().substring(0, 10)'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (parser !== 'faker') {
|
|
123
|
+
throw new Error(`type '${representation}' and parser '${parser}' can not work together`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return 'faker.date.anytime()'
|
|
127
|
+
},
|
|
128
|
+
time: (representation: 'date' | 'string' = 'string', parser: PluginFaker['resolvedOptions']['dateParser'] = 'faker') => {
|
|
129
|
+
if (representation === 'string') {
|
|
130
|
+
if (parser !== 'faker') {
|
|
131
|
+
return `${parser}(faker.date.anytime()).format("HH:mm:ss")`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return 'faker.date.anytime().toISOString().substring(11, 19)'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (parser !== 'faker') {
|
|
138
|
+
throw new Error(`type '${representation}' and parser '${parser}' can not work together`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return 'faker.date.anytime()'
|
|
142
|
+
},
|
|
143
|
+
uuid: () => 'faker.string.uuid()',
|
|
144
|
+
url: () => 'faker.internet.url()',
|
|
145
|
+
and: (items: string[] = []) => {
|
|
146
|
+
if (items.length === 0) {
|
|
147
|
+
return '{}'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (items.length === 1) {
|
|
151
|
+
return items[0] ?? '{}'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return `{...${items.join(', ...')}}`
|
|
155
|
+
},
|
|
156
|
+
matches: (value = '', regexGenerator: 'faker' | 'randexp' = 'faker') => {
|
|
157
|
+
if (regexGenerator === 'randexp') {
|
|
158
|
+
return `${toRegExpString(value, 'RandExp')}.gen()`
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return `faker.helpers.fromRegExp("${value}")`
|
|
162
|
+
},
|
|
163
|
+
email: () => 'faker.internet.email()',
|
|
164
|
+
blob: () => 'faker.image.url() as unknown as Blob',
|
|
165
|
+
} as const
|
|
166
|
+
|
|
167
|
+
function getEnumValues(node: ast.EnumSchemaNode): Array<string | number | boolean | undefined> {
|
|
168
|
+
if (node.namedEnumValues?.length) {
|
|
169
|
+
return node.namedEnumValues.map((item) => item.value as string | number | boolean | undefined)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return (node.enumValues ?? []) as Array<string | number | boolean | undefined>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function parseEnumValue(value: string | number | boolean | undefined) {
|
|
176
|
+
if (typeof value === 'string') {
|
|
177
|
+
return stringify(value)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return value
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates a Faker printer that generates mock data generation code from schema nodes.
|
|
185
|
+
* Handles circular references gracefully by emitting memoizing getters for cyclic properties.
|
|
186
|
+
*/
|
|
187
|
+
export const printerFaker: (options: PrinterFakerOptions) => ast.Printer<PrinterFakerFactory> = ast.definePrinter<PrinterFakerFactory>((options) => {
|
|
188
|
+
const printNested = (node: ast.SchemaNode, overrideOptions: Partial<PrinterFakerOptions> = {}): string => {
|
|
189
|
+
return (
|
|
190
|
+
printerFaker({
|
|
191
|
+
...options,
|
|
192
|
+
...overrideOptions,
|
|
193
|
+
nodes: options.nodes,
|
|
194
|
+
}).print(node) ?? 'undefined'
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
name: 'faker',
|
|
200
|
+
options,
|
|
201
|
+
nodes: {
|
|
202
|
+
any: () => fakerKeywordMapper.any(),
|
|
203
|
+
unknown: () => fakerKeywordMapper.unknown(),
|
|
204
|
+
void: () => fakerKeywordMapper.void(),
|
|
205
|
+
boolean: () => fakerKeywordMapper.boolean(),
|
|
206
|
+
null: () => fakerKeywordMapper.null(),
|
|
207
|
+
string(node) {
|
|
208
|
+
if (node.pattern) {
|
|
209
|
+
return fakerKeywordMapper.matches(node.pattern, this.options.regexGenerator)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return fakerKeywordMapper.string(node.min, node.max)
|
|
213
|
+
},
|
|
214
|
+
email: () => fakerKeywordMapper.email(),
|
|
215
|
+
url: () => fakerKeywordMapper.url(),
|
|
216
|
+
uuid: () => fakerKeywordMapper.uuid(),
|
|
217
|
+
number(node) {
|
|
218
|
+
return fakerKeywordMapper.number(node.min, node.max)
|
|
219
|
+
},
|
|
220
|
+
integer(node) {
|
|
221
|
+
return fakerKeywordMapper.integer(node.min, node.max)
|
|
222
|
+
},
|
|
223
|
+
bigint: () => fakerKeywordMapper.bigint(),
|
|
224
|
+
blob: () => fakerKeywordMapper.blob(),
|
|
225
|
+
datetime: () => fakerKeywordMapper.datetime(),
|
|
226
|
+
date(node) {
|
|
227
|
+
return fakerKeywordMapper.date(node.representation ?? 'string', this.options.dateParser)
|
|
228
|
+
},
|
|
229
|
+
time(node) {
|
|
230
|
+
return fakerKeywordMapper.time(node.representation ?? 'string', this.options.dateParser)
|
|
231
|
+
},
|
|
232
|
+
ref(node) {
|
|
233
|
+
// Parser-generated refs (with $ref) carry raw schema names that need resolving.
|
|
234
|
+
// Use the canonical name from the $ref path — node.name may have been overridden
|
|
235
|
+
// (e.g. by single-member allOf flatten using the property-derived child name).
|
|
236
|
+
// Inline refs (without $ref) from faker utils already carry resolved helper names.
|
|
237
|
+
const refName = node.ref ? (ast.extractRefName(node.ref) ?? node.name ?? node.schema?.name) : (node.name ?? node.schema?.name)
|
|
238
|
+
|
|
239
|
+
if (!refName) {
|
|
240
|
+
throw new Error('Name not defined for ref node')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (this.options.schemaName && refName === this.options.schemaName) {
|
|
244
|
+
return 'undefined as any'
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Internal helper refs (for generated response/data helpers) are already
|
|
248
|
+
// emitted with resolver output and should not be transformed twice.
|
|
249
|
+
const resolvedName = node.ref ? this.options.resolver.resolveName(refName) : refName
|
|
250
|
+
|
|
251
|
+
if (!this.options.nestedInObject) {
|
|
252
|
+
return `${resolvedName}(data)`
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return `${resolvedName}()`
|
|
256
|
+
},
|
|
257
|
+
enum(node) {
|
|
258
|
+
return fakerKeywordMapper.enum(getEnumValues(node).map(parseEnumValue), this.options.typeName)
|
|
259
|
+
},
|
|
260
|
+
union(node): string {
|
|
261
|
+
const items: string[] = (node.members ?? [])
|
|
262
|
+
.map((member) =>
|
|
263
|
+
printNested(member, {
|
|
264
|
+
nestedInObject: true,
|
|
265
|
+
}),
|
|
266
|
+
)
|
|
267
|
+
.filter((item): item is string => Boolean(item))
|
|
268
|
+
|
|
269
|
+
return fakerKeywordMapper.union(items)
|
|
270
|
+
},
|
|
271
|
+
intersection(node): string {
|
|
272
|
+
const items: string[] = (node.members ?? [])
|
|
273
|
+
.map((member) =>
|
|
274
|
+
printNested(member, {
|
|
275
|
+
nestedInObject: true,
|
|
276
|
+
}),
|
|
277
|
+
)
|
|
278
|
+
.filter((item): item is string => Boolean(item))
|
|
279
|
+
|
|
280
|
+
return fakerKeywordMapper.and(items)
|
|
281
|
+
},
|
|
282
|
+
array(node): string {
|
|
283
|
+
const items: string[] = (node.items ?? [])
|
|
284
|
+
.map((member) =>
|
|
285
|
+
printNested(member, {
|
|
286
|
+
typeName: this.options.typeName ? `NonNullable<${this.options.typeName}>[number]` : undefined,
|
|
287
|
+
nestedInObject: true,
|
|
288
|
+
}),
|
|
289
|
+
)
|
|
290
|
+
.filter((item): item is string => Boolean(item))
|
|
291
|
+
|
|
292
|
+
return fakerKeywordMapper.array(items, node.min, node.max)
|
|
293
|
+
},
|
|
294
|
+
tuple(node): string {
|
|
295
|
+
const items: string[] = (node.items ?? [])
|
|
296
|
+
.map((member, index) =>
|
|
297
|
+
printNested(member, {
|
|
298
|
+
typeName: this.options.typeName ? `NonNullable<${this.options.typeName}>[${index}]` : undefined,
|
|
299
|
+
nestedInObject: true,
|
|
300
|
+
}),
|
|
301
|
+
)
|
|
302
|
+
.filter((item): item is string => Boolean(item))
|
|
303
|
+
|
|
304
|
+
return fakerKeywordMapper.tuple(items)
|
|
305
|
+
},
|
|
306
|
+
object(node): string {
|
|
307
|
+
const cyclicSchemas = this.options.cyclicSchemas
|
|
308
|
+
const properties = (node.properties ?? [])
|
|
309
|
+
.map((property): string => {
|
|
310
|
+
if (this.options.mapper && Object.hasOwn(this.options.mapper, property.name)) {
|
|
311
|
+
return `"${property.name}": ${this.options.mapper[property.name]}`
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const value: string =
|
|
315
|
+
printNested(property.schema, {
|
|
316
|
+
typeName: this.options.typeName ? `NonNullable<${this.options.typeName}>[${JSON.stringify(property.name)}]` : undefined,
|
|
317
|
+
nestedInObject: true,
|
|
318
|
+
}) ?? 'undefined'
|
|
319
|
+
|
|
320
|
+
// When the property's schema transitively references a schema that is
|
|
321
|
+
// part of a circular dependency (other than the current schema itself),
|
|
322
|
+
// emit a memoizing lazy getter. On first access it computes the value,
|
|
323
|
+
// replaces itself with a plain data property via Object.defineProperty,
|
|
324
|
+
// and returns the cached value – so every subsequent read is stable.
|
|
325
|
+
if (cyclicSchemas && ast.containsCircularRef(property.schema, { circularSchemas: cyclicSchemas, excludeName: this.options.schemaName })) {
|
|
326
|
+
return `get ${property.name}() { const _value = ${value}; Object.defineProperty(this, ${JSON.stringify(property.name)}, { value: _value, configurable: true, writable: true, enumerable: true }); return _value }`
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return `"${property.name}": ${value}`
|
|
330
|
+
})
|
|
331
|
+
.join(',')
|
|
332
|
+
|
|
333
|
+
return `{${properties}}`
|
|
334
|
+
},
|
|
335
|
+
...options.nodes,
|
|
336
|
+
},
|
|
337
|
+
print(node) {
|
|
338
|
+
return this.transform(node) ?? null
|
|
339
|
+
},
|
|
340
|
+
}
|
|
341
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { camelCase, isValidVarName } from '@internals/utils'
|
|
4
|
+
import { defineResolver, PluginDriver } from '@kubb/core'
|
|
5
|
+
import type { PluginFaker } from '../types.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Naming convention resolver for Faker plugin.
|
|
9
|
+
*
|
|
10
|
+
* Provides default naming helpers using camelCase with a `create` prefix for factory functions and files.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* `resolverFaker.default('list pets', 'function') // → 'createListPets'`
|
|
14
|
+
*/
|
|
15
|
+
export const resolverFaker = defineResolver<PluginFaker>(() => {
|
|
16
|
+
return {
|
|
17
|
+
name: 'default',
|
|
18
|
+
pluginName: 'plugin-faker',
|
|
19
|
+
default(name, type) {
|
|
20
|
+
const resolvedName = camelCase(name, { isFile: type === 'file', prefix: 'create' })
|
|
21
|
+
|
|
22
|
+
if (type === 'file' || isValidVarName(resolvedName)) {
|
|
23
|
+
return resolvedName
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return `_${resolvedName}`
|
|
27
|
+
},
|
|
28
|
+
resolveName(name, type) {
|
|
29
|
+
return this.default(name, type)
|
|
30
|
+
},
|
|
31
|
+
resolvePathName(name, type) {
|
|
32
|
+
return this.default(name, type)
|
|
33
|
+
},
|
|
34
|
+
resolveFile({ name, extname, tag, path: groupPath }, context) {
|
|
35
|
+
const pathMode = PluginDriver.getMode(path.resolve(context.root, context.output.path))
|
|
36
|
+
const baseName = `${pathMode === 'single' ? '' : this.resolveName(name, 'file')}${extname}` as `${string}.${string}`
|
|
37
|
+
const filePath = this.resolvePath(
|
|
38
|
+
{
|
|
39
|
+
baseName,
|
|
40
|
+
pathMode,
|
|
41
|
+
tag,
|
|
42
|
+
path: groupPath,
|
|
43
|
+
},
|
|
44
|
+
context,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
kind: 'File',
|
|
49
|
+
id: createHash('sha256').update(filePath).digest('hex'),
|
|
50
|
+
name: path.basename(filePath, extname),
|
|
51
|
+
path: filePath,
|
|
52
|
+
baseName,
|
|
53
|
+
extname,
|
|
54
|
+
meta: { pluginName: this.pluginName },
|
|
55
|
+
sources: [],
|
|
56
|
+
imports: [],
|
|
57
|
+
exports: [],
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
resolveParamName(node, param) {
|
|
61
|
+
return this.resolveName(`${node.operationId} ${param.in} ${param.name}`)
|
|
62
|
+
},
|
|
63
|
+
resolveDataName(node) {
|
|
64
|
+
return this.resolveName(`${node.operationId} Data`)
|
|
65
|
+
},
|
|
66
|
+
resolveResponseStatusName(node, statusCode) {
|
|
67
|
+
return this.resolveName(`${node.operationId} Status ${statusCode}`)
|
|
68
|
+
},
|
|
69
|
+
resolveResponseName(node) {
|
|
70
|
+
return this.resolveName(`${node.operationId} Response`)
|
|
71
|
+
},
|
|
72
|
+
resolveResponsesName(node) {
|
|
73
|
+
return this.resolveName(`${node.operationId} Responses`)
|
|
74
|
+
},
|
|
75
|
+
resolvePathParamsName(node, param) {
|
|
76
|
+
return this.resolveParamName(node, param)
|
|
77
|
+
},
|
|
78
|
+
resolveQueryParamsName(node, param) {
|
|
79
|
+
return this.resolveParamName(node, param)
|
|
80
|
+
},
|
|
81
|
+
resolveHeaderParamsName(node, param) {
|
|
82
|
+
return this.resolveParamName(node, param)
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
})
|