@kubb/core 5.0.0-beta.2 → 5.0.0-beta.21
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 +8 -38
- package/dist/KubbDriver-BBRa5CH2.cjs +2231 -0
- package/dist/KubbDriver-BBRa5CH2.cjs.map +1 -0
- package/dist/KubbDriver-Cq1isv2P.js +2110 -0
- package/dist/KubbDriver-Cq1isv2P.js.map +1 -0
- package/dist/{types-CC09VtBt.d.ts → createKubb-CYrw_xaR.d.ts} +1414 -1255
- package/dist/index.cjs +221 -1074
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -185
- package/dist/index.js +211 -1068
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +30 -21
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +5 -5
- package/dist/mocks.js +29 -20
- package/dist/mocks.js.map +1 -1
- package/package.json +6 -18
- package/src/FileManager.ts +75 -58
- package/src/FileProcessor.ts +48 -38
- package/src/KubbDriver.ts +915 -0
- package/src/constants.ts +11 -6
- package/src/createAdapter.ts +84 -1
- package/src/createKubb.ts +1022 -485
- package/src/createRenderer.ts +33 -22
- package/src/defineGenerator.ts +96 -7
- package/src/defineLogger.ts +42 -3
- package/src/defineMiddleware.ts +1 -1
- package/src/defineParser.ts +1 -1
- package/src/definePlugin.ts +304 -8
- package/src/defineResolver.ts +271 -150
- package/src/devtools.ts +8 -1
- package/src/index.ts +2 -2
- package/src/mocks.ts +11 -14
- package/src/storages/fsStorage.ts +13 -37
- package/src/types.ts +39 -1292
- package/dist/PluginDriver-BXibeQk-.cjs +0 -1036
- package/dist/PluginDriver-BXibeQk-.cjs.map +0 -1
- package/dist/PluginDriver-DV3p2Hky.js +0 -945
- package/dist/PluginDriver-DV3p2Hky.js.map +0 -1
- package/src/Kubb.ts +0 -300
- package/src/PluginDriver.ts +0 -424
- package/src/renderNode.ts +0 -35
- package/src/utils/diagnostics.ts +0 -18
- package/src/utils/isInputPath.ts +0 -10
- package/src/utils/packageJSON.ts +0 -99
package/src/defineResolver.ts
CHANGED
|
@@ -1,18 +1,160 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
import { camelCase, pascalCase } from '@internals/utils'
|
|
3
|
-
import type { FileNode,
|
|
3
|
+
import type { FileNode, InputMeta, Node, OperationNode, SchemaNode } from '@kubb/ast'
|
|
4
4
|
import { createFile, isOperationNode, isSchemaNode } from '@kubb/ast'
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
5
|
+
import type { PluginFactoryOptions } from './definePlugin.ts'
|
|
6
|
+
import { getMode } from './definePlugin.ts'
|
|
7
|
+
import type { Config, Group, Output } from './types.ts'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type/string pattern filter for include/exclude/override matching.
|
|
11
|
+
*/
|
|
12
|
+
type PatternFilter = {
|
|
13
|
+
type: string
|
|
14
|
+
pattern: string | RegExp
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pattern filter with partial option overrides applied when the pattern matches.
|
|
19
|
+
*/
|
|
20
|
+
type PatternOverride<TOptions> = PatternFilter & {
|
|
21
|
+
options: Omit<Partial<TOptions>, 'override'>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Context for resolving filtered options for a given operation or schema node.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export type ResolveOptionsContext<TOptions> = {
|
|
30
|
+
options: TOptions
|
|
31
|
+
exclude?: Array<PatternFilter>
|
|
32
|
+
include?: Array<PatternFilter>
|
|
33
|
+
override?: Array<PatternOverride<TOptions>>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Base constraint for all plugin resolver objects.
|
|
38
|
+
*
|
|
39
|
+
* `default`, `resolveOptions`, `resolvePath`, `resolveFile`, `resolveBanner`, and `resolveFooter`
|
|
40
|
+
* are injected automatically by `defineResolver` — extend this type to add custom resolution methods.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* type MyResolver = Resolver & {
|
|
45
|
+
* resolveName(node: SchemaNode): string
|
|
46
|
+
* resolveTypedName(node: SchemaNode): string
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export type Resolver = {
|
|
51
|
+
name: string
|
|
52
|
+
pluginName: string
|
|
53
|
+
default(name: string, type?: 'file' | 'function' | 'type' | 'const'): string
|
|
54
|
+
resolveOptions<TOptions>(node: Node, context: ResolveOptionsContext<TOptions>): TOptions | null
|
|
55
|
+
resolvePath(params: ResolverPathParams, context: ResolverContext): string
|
|
56
|
+
resolveFile(params: ResolverFileParams, context: ResolverContext): FileNode
|
|
57
|
+
resolveBanner(meta: InputMeta | undefined, context: ResolveBannerContext): string | null
|
|
58
|
+
resolveFooter(meta: InputMeta | undefined, context: ResolveBannerContext): string | null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* File-specific parameters for `Resolver.resolvePath`.
|
|
63
|
+
*
|
|
64
|
+
* Pass alongside a `ResolverContext` to identify which file to resolve.
|
|
65
|
+
* Provide `tag` for tag-based grouping or `path` for path-based grouping.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* resolver.resolvePath(
|
|
70
|
+
* { baseName: 'petTypes.ts', tag: 'pets' },
|
|
71
|
+
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
72
|
+
* )
|
|
73
|
+
* // → '/src/types/petsController/petTypes.ts'
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export type ResolverPathParams = {
|
|
77
|
+
baseName: FileNode['baseName']
|
|
78
|
+
pathMode?: 'single' | 'split'
|
|
79
|
+
/**
|
|
80
|
+
* Tag value used when `group.type === 'tag'`.
|
|
81
|
+
*/
|
|
82
|
+
tag?: string
|
|
83
|
+
/**
|
|
84
|
+
* Path value used when `group.type === 'path'`.
|
|
85
|
+
*/
|
|
86
|
+
path?: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Shared context passed as the second argument to `Resolver.resolvePath` and `Resolver.resolveFile`.
|
|
91
|
+
*
|
|
92
|
+
* Describes where on disk output is rooted, which output config is active, and the optional
|
|
93
|
+
* grouping strategy that controls subdirectory layout.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const context: ResolverContext = {
|
|
98
|
+
* root: config.root,
|
|
99
|
+
* output,
|
|
100
|
+
* group,
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export type ResolverContext = {
|
|
105
|
+
root: string
|
|
106
|
+
output: Output
|
|
107
|
+
group?: Group
|
|
108
|
+
/**
|
|
109
|
+
* Plugin name used to populate `meta.pluginName` on the resolved file.
|
|
110
|
+
*/
|
|
111
|
+
pluginName?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* File-specific parameters for `Resolver.resolveFile`.
|
|
116
|
+
*
|
|
117
|
+
* Pass alongside a `ResolverContext` to fully describe the file to resolve.
|
|
118
|
+
* `tag` and `path` are used only when a matching `group` is present in the context.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* resolver.resolveFile(
|
|
123
|
+
* { name: 'listPets', extname: '.ts', tag: 'pets' },
|
|
124
|
+
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
125
|
+
* )
|
|
126
|
+
* // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export type ResolverFileParams = {
|
|
130
|
+
name: string
|
|
131
|
+
extname: FileNode['extname']
|
|
132
|
+
/**
|
|
133
|
+
* Tag value used when `group.type === 'tag'`.
|
|
134
|
+
*/
|
|
135
|
+
tag?: string
|
|
136
|
+
/**
|
|
137
|
+
* Path value used when `group.type === 'path'`.
|
|
138
|
+
*/
|
|
139
|
+
path?: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Context passed to `Resolver.resolveBanner` and `Resolver.resolveFooter`.
|
|
144
|
+
*
|
|
145
|
+
* `output` is optional — not every plugin configures a banner/footer.
|
|
146
|
+
* `config` carries the global Kubb config, used to derive the default Kubb banner.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* resolver.resolveBanner(meta, { output: { banner: '// generated' }, config })
|
|
151
|
+
* // → '// generated'
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export type ResolveBannerContext = {
|
|
155
|
+
output?: Pick<Output, 'banner' | 'footer'>
|
|
156
|
+
config: Config
|
|
157
|
+
}
|
|
16
158
|
|
|
17
159
|
/**
|
|
18
160
|
* Builder type for the plugin-specific resolver fields.
|
|
@@ -20,19 +162,16 @@ import type {
|
|
|
20
162
|
* `default`, `resolveOptions`, `resolvePath`, `resolveFile`, `resolveBanner`, and `resolveFooter`
|
|
21
163
|
* are optional — built-in fallbacks are injected when omitted.
|
|
22
164
|
*
|
|
23
|
-
*
|
|
24
|
-
* call sibling resolver methods without using `this`. Because `ctx` is captured by the closure
|
|
25
|
-
* and the resolver is populated after the builder runs, `ctx` correctly reflects any overrides
|
|
26
|
-
* that were applied by the builder itself.
|
|
165
|
+
* Methods in the returned object can call sibling resolver methods via `this`.
|
|
27
166
|
*/
|
|
28
|
-
type ResolverBuilder<T extends PluginFactoryOptions> = (
|
|
167
|
+
type ResolverBuilder<T extends PluginFactoryOptions> = () => Omit<
|
|
29
168
|
T['resolver'],
|
|
30
169
|
'default' | 'resolveOptions' | 'resolvePath' | 'resolveFile' | 'resolveBanner' | 'resolveFooter' | 'name' | 'pluginName'
|
|
31
170
|
> &
|
|
32
171
|
Partial<Pick<T['resolver'], 'default' | 'resolveOptions' | 'resolvePath' | 'resolveFile' | 'resolveBanner' | 'resolveFooter'>> & {
|
|
33
172
|
name: string
|
|
34
173
|
pluginName: T['name']
|
|
35
|
-
}
|
|
174
|
+
} & ThisType<T['resolver']>
|
|
36
175
|
|
|
37
176
|
// String patterns are compiled lazily and cached — the same filter is reused for every node.
|
|
38
177
|
const stringPatternCache = new Map<string, RegExp>()
|
|
@@ -54,20 +193,12 @@ function testPattern(value: string, pattern: string | RegExp): boolean {
|
|
|
54
193
|
* Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
|
|
55
194
|
*/
|
|
56
195
|
function matchesOperationPattern(node: OperationNode, type: string, pattern: string | RegExp): boolean {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return testPattern(node.path, pattern)
|
|
64
|
-
case 'method':
|
|
65
|
-
return testPattern(node.method.toLowerCase(), pattern)
|
|
66
|
-
case 'contentType':
|
|
67
|
-
return node.requestBody?.content?.some((c) => testPattern(c.contentType, pattern)) ?? false
|
|
68
|
-
default:
|
|
69
|
-
return false
|
|
70
|
-
}
|
|
196
|
+
if (type === 'tag') return node.tags.some((tag) => testPattern(tag, pattern))
|
|
197
|
+
if (type === 'operationId') return testPattern(node.operationId, pattern)
|
|
198
|
+
if (type === 'path') return testPattern(node.path, pattern)
|
|
199
|
+
if (type === 'method') return testPattern(node.method.toLowerCase(), pattern)
|
|
200
|
+
if (type === 'contentType') return node.requestBody?.content?.some((c) => testPattern(c.contentType, pattern)) ?? false
|
|
201
|
+
return false
|
|
71
202
|
}
|
|
72
203
|
|
|
73
204
|
/**
|
|
@@ -76,12 +207,8 @@ function matchesOperationPattern(node: OperationNode, type: string, pattern: str
|
|
|
76
207
|
* Returns `null` when the filter type doesn't apply to schemas.
|
|
77
208
|
*/
|
|
78
209
|
function matchesSchemaPattern(node: SchemaNode, type: string, pattern: string | RegExp): boolean | null {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return node.name ? testPattern(node.name, pattern) : false
|
|
82
|
-
default:
|
|
83
|
-
return null
|
|
84
|
-
}
|
|
210
|
+
if (type === 'schemaName') return node.name ? testPattern(node.name, pattern) : false
|
|
211
|
+
return null
|
|
85
212
|
}
|
|
86
213
|
|
|
87
214
|
/**
|
|
@@ -92,19 +219,9 @@ function matchesSchemaPattern(node: SchemaNode, type: string, pattern: string |
|
|
|
92
219
|
* - `camelCase` for everything else.
|
|
93
220
|
*/
|
|
94
221
|
function defaultResolver(name: string, type?: 'file' | 'function' | 'type' | 'const'): string {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
resolvedName = camelCase(name, {
|
|
99
|
-
isFile: type === 'file',
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (type === 'type') {
|
|
104
|
-
resolvedName = pascalCase(name)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return resolvedName
|
|
222
|
+
if (type === 'file' || type === 'function') return camelCase(name, { isFile: type === 'file' })
|
|
223
|
+
if (type === 'type') return pascalCase(name)
|
|
224
|
+
return camelCase(name)
|
|
108
225
|
}
|
|
109
226
|
|
|
110
227
|
/**
|
|
@@ -130,19 +247,18 @@ function defaultResolver(name: string, type?: 'file' | 'function' | 'type' | 'co
|
|
|
130
247
|
* // → { enumType: 'enum' } when operationId matches
|
|
131
248
|
* ```
|
|
132
249
|
*/
|
|
133
|
-
|
|
250
|
+
const resolveOptionsCache = new WeakMap<object, WeakMap<Node, { value: unknown }>>()
|
|
251
|
+
|
|
252
|
+
function computeOptions<TOptions>(
|
|
134
253
|
node: Node,
|
|
135
|
-
|
|
254
|
+
options: TOptions,
|
|
255
|
+
exclude: Array<PatternFilter>,
|
|
256
|
+
include: Array<PatternFilter> | undefined,
|
|
257
|
+
override: Array<PatternOverride<TOptions>>,
|
|
136
258
|
): TOptions | null {
|
|
137
259
|
if (isOperationNode(node)) {
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
return null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) {
|
|
144
|
-
return null
|
|
145
|
-
}
|
|
260
|
+
if (exclude.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null
|
|
261
|
+
if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null
|
|
146
262
|
|
|
147
263
|
const overrideOptions = override.find(({ type, pattern }) => matchesOperationPattern(node, type, pattern))?.options
|
|
148
264
|
|
|
@@ -150,18 +266,13 @@ export function defaultResolveOptions<TOptions>(
|
|
|
150
266
|
}
|
|
151
267
|
|
|
152
268
|
if (isSchemaNode(node)) {
|
|
153
|
-
if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true))
|
|
154
|
-
return null
|
|
155
|
-
}
|
|
156
|
-
|
|
269
|
+
if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null
|
|
157
270
|
if (include) {
|
|
158
271
|
const results = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern))
|
|
159
272
|
const applicable = results.filter((r) => r !== null)
|
|
160
|
-
if (applicable.length > 0 && !applicable.includes(true)) {
|
|
161
|
-
return null
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
273
|
|
|
274
|
+
if (applicable.length > 0 && !applicable.includes(true)) return null
|
|
275
|
+
}
|
|
165
276
|
const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options
|
|
166
277
|
|
|
167
278
|
return { ...options, ...overrideOptions }
|
|
@@ -170,6 +281,26 @@ export function defaultResolveOptions<TOptions>(
|
|
|
170
281
|
return options
|
|
171
282
|
}
|
|
172
283
|
|
|
284
|
+
export function defaultResolveOptions<TOptions>(
|
|
285
|
+
node: Node,
|
|
286
|
+
{ options, exclude = [], include, override = [] }: ResolveOptionsContext<TOptions>,
|
|
287
|
+
): TOptions | null {
|
|
288
|
+
const optionsKey = options as object
|
|
289
|
+
let byOptions = resolveOptionsCache.get(optionsKey)
|
|
290
|
+
if (!byOptions) {
|
|
291
|
+
byOptions = new WeakMap()
|
|
292
|
+
resolveOptionsCache.set(optionsKey, byOptions)
|
|
293
|
+
}
|
|
294
|
+
const cached = byOptions.get(node)
|
|
295
|
+
if (cached !== undefined) return cached.value as TOptions | null
|
|
296
|
+
|
|
297
|
+
const result = computeOptions(node, options, exclude, include, override)
|
|
298
|
+
|
|
299
|
+
byOptions.set(node, { value: result })
|
|
300
|
+
|
|
301
|
+
return result
|
|
302
|
+
}
|
|
303
|
+
|
|
173
304
|
/**
|
|
174
305
|
* Default path resolver used by `defineResolver`.
|
|
175
306
|
*
|
|
@@ -215,31 +346,30 @@ export function defaultResolveOptions<TOptions>(
|
|
|
215
346
|
* ```
|
|
216
347
|
*/
|
|
217
348
|
export function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }: ResolverPathParams, { root, output, group }: ResolverContext): string {
|
|
218
|
-
const mode = pathMode ??
|
|
349
|
+
const mode = pathMode ?? getMode(path.resolve(root, output.path))
|
|
219
350
|
|
|
220
351
|
if (mode === 'single') {
|
|
221
352
|
return path.resolve(root, output.path)
|
|
222
353
|
}
|
|
223
354
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
355
|
+
const result: string = (() => {
|
|
356
|
+
if (group && (groupPath || tag)) {
|
|
357
|
+
const groupValue = group.type === 'path' ? groupPath! : tag!
|
|
358
|
+
const defaultName =
|
|
359
|
+
group.type === 'tag'
|
|
360
|
+
? ({ group: g }: { group: string }) => `${camelCase(g)}Controller`
|
|
361
|
+
: ({ group: g }: { group: string }) => {
|
|
362
|
+
// Strip traversal components (empty, '.', '..') before taking the first meaningful segment.
|
|
363
|
+
// When every segment is a traversal component (e.g. '../../') we fall back to '' so the
|
|
364
|
+
// file is placed directly in the output root — the boundary check below ensures safety.
|
|
365
|
+
const segment = g.split('/').filter((s) => s !== '' && s !== '.' && s !== '..')[0]
|
|
366
|
+
return segment ? camelCase(segment) : ''
|
|
367
|
+
}
|
|
368
|
+
const resolveName = group.name ?? defaultName
|
|
369
|
+
return path.resolve(root, output.path, resolveName({ group: groupValue }), baseName)
|
|
370
|
+
}
|
|
371
|
+
return path.resolve(root, output.path, baseName)
|
|
372
|
+
})()
|
|
243
373
|
|
|
244
374
|
// Ensure the resolved path stays within the configured output directory.
|
|
245
375
|
// This prevents path traversal from malicious OpenAPI specs or custom group.name functions.
|
|
@@ -268,35 +398,35 @@ export function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }:
|
|
|
268
398
|
*
|
|
269
399
|
* @example Resolve a schema file
|
|
270
400
|
* ```ts
|
|
271
|
-
* const file = defaultResolveFile(
|
|
401
|
+
* const file = defaultResolveFile.call(
|
|
402
|
+
* resolver,
|
|
272
403
|
* { name: 'pet', extname: '.ts' },
|
|
273
404
|
* { root: '/src', output: { path: 'types' } },
|
|
274
|
-
* resolver,
|
|
275
405
|
* )
|
|
276
406
|
* // → { baseName: 'pet.ts', path: '/src/types/pet.ts', sources: [], ... }
|
|
277
407
|
* ```
|
|
278
408
|
*
|
|
279
409
|
* @example Resolve an operation file with tag grouping
|
|
280
410
|
* ```ts
|
|
281
|
-
* const file = defaultResolveFile(
|
|
411
|
+
* const file = defaultResolveFile.call(
|
|
412
|
+
* resolver,
|
|
282
413
|
* { name: 'listPets', extname: '.ts', tag: 'pets' },
|
|
283
414
|
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
284
|
-
* resolver,
|
|
285
415
|
* )
|
|
286
416
|
* // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
|
|
287
417
|
* ```
|
|
288
418
|
*/
|
|
289
|
-
export function defaultResolveFile({ name, extname, tag, path: groupPath }: ResolverFileParams, context: ResolverContext
|
|
290
|
-
const pathMode =
|
|
291
|
-
const resolvedName = pathMode === 'single' ? '' :
|
|
419
|
+
export function defaultResolveFile(this: Resolver, { name, extname, tag, path: groupPath }: ResolverFileParams, context: ResolverContext): FileNode {
|
|
420
|
+
const pathMode = getMode(path.resolve(context.root, context.output.path))
|
|
421
|
+
const resolvedName = pathMode === 'single' ? '' : this.default(name, 'file')
|
|
292
422
|
const baseName = `${resolvedName}${extname}` as FileNode['baseName']
|
|
293
|
-
const filePath =
|
|
423
|
+
const filePath = this.resolvePath({ baseName, pathMode, tag, path: groupPath }, context)
|
|
294
424
|
|
|
295
425
|
return createFile({
|
|
296
426
|
path: filePath,
|
|
297
427
|
baseName: path.basename(filePath) as `${string}.${string}`,
|
|
298
428
|
meta: {
|
|
299
|
-
pluginName:
|
|
429
|
+
pluginName: this.pluginName,
|
|
300
430
|
},
|
|
301
431
|
sources: [],
|
|
302
432
|
imports: [],
|
|
@@ -319,17 +449,16 @@ export function buildDefaultBanner({
|
|
|
319
449
|
config: Config
|
|
320
450
|
}): string {
|
|
321
451
|
try {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
452
|
+
const source = (() => {
|
|
453
|
+
if (Array.isArray(config.input)) {
|
|
454
|
+
const first = config.input[0]
|
|
455
|
+
if (first && 'path' in first) return path.basename(first.path)
|
|
456
|
+
return ''
|
|
327
457
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
458
|
+
if (config.input && 'path' in config.input) return path.basename(config.input.path)
|
|
459
|
+
if (config.input && 'data' in config.input) return 'text content'
|
|
460
|
+
return ''
|
|
461
|
+
})()
|
|
333
462
|
|
|
334
463
|
let banner = '/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n'
|
|
335
464
|
|
|
@@ -367,10 +496,9 @@ export function buildDefaultBanner({
|
|
|
367
496
|
*
|
|
368
497
|
* A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
|
|
369
498
|
* When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
|
|
370
|
-
* from the
|
|
499
|
+
* from the document metadata when `meta` is provided).
|
|
371
500
|
*
|
|
372
|
-
* - When `output.banner` is a function
|
|
373
|
-
* - When `output.banner` is a function and `node` is absent, falls back to the Kubb notice.
|
|
501
|
+
* - When `output.banner` is a function, calls it with `meta` and returns the result.
|
|
374
502
|
* - When `output.banner` is a string, returns it directly.
|
|
375
503
|
* - When `config.output.defaultBanner` is `false`, returns `undefined`.
|
|
376
504
|
* - Otherwise returns the Kubb "Generated by Kubb" notice.
|
|
@@ -381,27 +509,27 @@ export function buildDefaultBanner({
|
|
|
381
509
|
* // → '// my banner'
|
|
382
510
|
* ```
|
|
383
511
|
*
|
|
384
|
-
* @example Function banner with
|
|
512
|
+
* @example Function banner with metadata
|
|
385
513
|
* ```ts
|
|
386
|
-
* defaultResolveBanner(
|
|
514
|
+
* defaultResolveBanner(meta, { output: { banner: (m) => `// v${m?.version}` }, config })
|
|
387
515
|
* // → '// v3.0.0'
|
|
388
516
|
* ```
|
|
389
517
|
*
|
|
390
518
|
* @example No user banner — Kubb notice with OAS metadata
|
|
391
519
|
* ```ts
|
|
392
|
-
* defaultResolveBanner(
|
|
520
|
+
* defaultResolveBanner(meta, { config })
|
|
393
521
|
* // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
|
|
394
522
|
* ```
|
|
395
523
|
*
|
|
396
524
|
* @example Disabled default banner
|
|
397
525
|
* ```ts
|
|
398
526
|
* defaultResolveBanner(undefined, { config: { output: { defaultBanner: false }, ...config } })
|
|
399
|
-
* // →
|
|
527
|
+
* // → null
|
|
400
528
|
* ```
|
|
401
529
|
*/
|
|
402
|
-
export function defaultResolveBanner(
|
|
530
|
+
export function defaultResolveBanner(meta: InputMeta | undefined, { output, config }: ResolveBannerContext): string | null {
|
|
403
531
|
if (typeof output?.banner === 'function') {
|
|
404
|
-
return output.banner(
|
|
532
|
+
return output.banner(meta)
|
|
405
533
|
}
|
|
406
534
|
|
|
407
535
|
if (typeof output?.banner === 'string') {
|
|
@@ -409,12 +537,12 @@ export function defaultResolveBanner(node: InputNode | undefined, { output, conf
|
|
|
409
537
|
}
|
|
410
538
|
|
|
411
539
|
if (config.output.defaultBanner === false) {
|
|
412
|
-
return
|
|
540
|
+
return null
|
|
413
541
|
}
|
|
414
542
|
|
|
415
543
|
return buildDefaultBanner({
|
|
416
|
-
title:
|
|
417
|
-
version:
|
|
544
|
+
title: meta?.title,
|
|
545
|
+
version: meta?.version,
|
|
418
546
|
config,
|
|
419
547
|
})
|
|
420
548
|
}
|
|
@@ -422,8 +550,7 @@ export function defaultResolveBanner(node: InputNode | undefined, { output, conf
|
|
|
422
550
|
/**
|
|
423
551
|
* Default footer resolver — returns the footer string for a generated file.
|
|
424
552
|
*
|
|
425
|
-
* - When `output.footer` is a function
|
|
426
|
-
* - When `output.footer` is a function and `node` is absent, returns `undefined`.
|
|
553
|
+
* - When `output.footer` is a function, calls it with `meta` and returns the result.
|
|
427
554
|
* - When `output.footer` is a string, returns it directly.
|
|
428
555
|
* - Otherwise returns `undefined`.
|
|
429
556
|
*
|
|
@@ -433,20 +560,20 @@ export function defaultResolveBanner(node: InputNode | undefined, { output, conf
|
|
|
433
560
|
* // → '// end of file'
|
|
434
561
|
* ```
|
|
435
562
|
*
|
|
436
|
-
* @example Function footer with
|
|
563
|
+
* @example Function footer with metadata
|
|
437
564
|
* ```ts
|
|
438
|
-
* defaultResolveFooter(
|
|
565
|
+
* defaultResolveFooter(meta, { output: { footer: (m) => `// ${m?.title}` }, config })
|
|
439
566
|
* // → '// Pet Store'
|
|
440
567
|
* ```
|
|
441
568
|
*/
|
|
442
|
-
export function defaultResolveFooter(
|
|
569
|
+
export function defaultResolveFooter(meta: InputMeta | undefined, { output }: ResolveBannerContext): string | null {
|
|
443
570
|
if (typeof output?.footer === 'function') {
|
|
444
|
-
return
|
|
571
|
+
return output.footer(meta)
|
|
445
572
|
}
|
|
446
573
|
if (typeof output?.footer === 'string') {
|
|
447
574
|
return output.footer
|
|
448
575
|
}
|
|
449
|
-
return
|
|
576
|
+
return null
|
|
450
577
|
}
|
|
451
578
|
|
|
452
579
|
/**
|
|
@@ -459,25 +586,24 @@ export function defaultResolveFooter(node: InputNode | undefined, { output }: Re
|
|
|
459
586
|
* - `resolvePath` — output path computation
|
|
460
587
|
* - `resolveFile` — full `FileNode` construction
|
|
461
588
|
*
|
|
462
|
-
*
|
|
463
|
-
* call sibling resolver methods using `ctx` instead of `this`.
|
|
589
|
+
* Methods in the returned object can call sibling resolver methods via `this`.
|
|
464
590
|
*
|
|
465
591
|
* @example Basic resolver with naming helpers
|
|
466
592
|
* ```ts
|
|
467
|
-
* export const resolver = defineResolver<PluginTs>((
|
|
593
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
468
594
|
* name: 'default',
|
|
469
595
|
* resolveName(node) {
|
|
470
|
-
* return
|
|
596
|
+
* return this.default(node.name, 'function')
|
|
471
597
|
* },
|
|
472
598
|
* resolveTypedName(node) {
|
|
473
|
-
* return
|
|
599
|
+
* return this.default(node.name, 'type')
|
|
474
600
|
* },
|
|
475
601
|
* }))
|
|
476
602
|
* ```
|
|
477
603
|
*
|
|
478
604
|
* @example Override resolvePath for a custom output structure
|
|
479
605
|
* ```ts
|
|
480
|
-
* export const resolver = defineResolver<PluginTs>((
|
|
606
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
481
607
|
* name: 'custom',
|
|
482
608
|
* resolvePath({ baseName }, { root, output }) {
|
|
483
609
|
* return path.resolve(root, output.path, 'generated', baseName)
|
|
@@ -485,37 +611,32 @@ export function defaultResolveFooter(node: InputNode | undefined, { output }: Re
|
|
|
485
611
|
* }))
|
|
486
612
|
* ```
|
|
487
613
|
*
|
|
488
|
-
* @example Use
|
|
614
|
+
* @example Use this.default inside a helper
|
|
489
615
|
* ```ts
|
|
490
|
-
* export const resolver = defineResolver<PluginTs>((
|
|
616
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
491
617
|
* name: 'default',
|
|
492
618
|
* resolveParamName(node, param) {
|
|
493
|
-
* return
|
|
619
|
+
* return this.default(`${node.operationId} ${param.in} ${param.name}`, 'type')
|
|
494
620
|
* },
|
|
495
621
|
* }))
|
|
496
622
|
* ```
|
|
497
623
|
*/
|
|
498
624
|
export function defineResolver<T extends PluginFactoryOptions>(build: ResolverBuilder<T>): T['resolver'] {
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
|
|
502
|
-
// properties (including any overrides from the builder itself).
|
|
503
|
-
const resolver = {} as T['resolver']
|
|
625
|
+
// `resolver` is kept so the default `resolveFile` wrapper can reference the fully assembled
|
|
626
|
+
// object via `.call(resolver, ...)` at call-time, after the result is assigned below.
|
|
627
|
+
let resolver: T['resolver']
|
|
504
628
|
|
|
505
|
-
|
|
629
|
+
const result = {
|
|
506
630
|
default: defaultResolver,
|
|
507
631
|
resolveOptions: defaultResolveOptions,
|
|
508
632
|
resolvePath: defaultResolvePath,
|
|
509
|
-
|
|
510
|
-
// Unlike other defaults which can be assigned directly, defaultResolveFile requires the
|
|
511
|
-
// resolver as its third parameter.
|
|
512
|
-
resolveFile: (params: ResolverFileParams, context: ResolverContext) => defaultResolveFile(params, context, resolver as Resolver),
|
|
633
|
+
resolveFile: (params: ResolverFileParams, context: ResolverContext) => defaultResolveFile.call(resolver as Resolver, params, context),
|
|
513
634
|
resolveBanner: defaultResolveBanner,
|
|
514
635
|
resolveFooter: defaultResolveFooter,
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
636
|
+
...build(),
|
|
637
|
+
} as T['resolver']
|
|
638
|
+
|
|
639
|
+
resolver = result
|
|
519
640
|
|
|
520
641
|
return resolver
|
|
521
642
|
}
|
package/src/devtools.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { InputNode } from '@kubb/ast'
|
|
2
2
|
import { deflateSync, inflateSync } from 'fflate'
|
|
3
3
|
import { x } from 'tinyexec'
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
export type DevtoolsOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Open the AST inspector in Kubb Studio (`/ast`). Defaults to the main Studio page.
|
|
8
|
+
* @default false
|
|
9
|
+
*/
|
|
10
|
+
ast?: boolean
|
|
11
|
+
}
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* Encodes an `InputNode` as a compressed, URL-safe string.
|
package/src/index.ts
CHANGED
|
@@ -13,8 +13,8 @@ export { definePlugin } from './definePlugin.ts'
|
|
|
13
13
|
export { defineResolver } from './defineResolver.ts'
|
|
14
14
|
export { FileManager } from './FileManager.ts'
|
|
15
15
|
export { FileProcessor } from './FileProcessor.ts'
|
|
16
|
-
export {
|
|
16
|
+
export { KubbDriver } from './KubbDriver.ts'
|
|
17
17
|
export { fsStorage } from './storages/fsStorage.ts'
|
|
18
18
|
export { memoryStorage } from './storages/memoryStorage.ts'
|
|
19
19
|
export * from './types.ts'
|
|
20
|
-
export { isInputPath } from './
|
|
20
|
+
export { isInputPath } from './createKubb.ts'
|