@kubb/core 5.0.0-beta.75 → 5.0.0-beta.9
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 +9 -39
- package/dist/{PluginDriver-BXibeQk-.cjs → PluginDriver-Cu1Kj9S-.cjs} +95 -56
- package/dist/PluginDriver-Cu1Kj9S-.cjs.map +1 -0
- package/dist/{PluginDriver-DV3p2Hky.js → PluginDriver-D8Z0Htid.js} +90 -57
- package/dist/PluginDriver-D8Z0Htid.js.map +1 -0
- package/dist/{types-CuNocrbJ.d.ts → createKubb-ALdb8lmq.d.ts} +1427 -1493
- package/dist/index.cjs +119 -127
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -143
- package/dist/index.js +119 -127
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +1 -1
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +1 -1
- package/dist/mocks.js +1 -1
- package/dist/mocks.js.map +1 -1
- package/package.json +5 -12
- package/src/PluginDriver.ts +40 -7
- package/src/constants.ts +1 -1
- package/src/createAdapter.ts +77 -1
- package/src/createKubb.ts +802 -82
- package/src/defineGenerator.ts +92 -4
- package/src/defineLogger.ts +42 -3
- package/src/defineMiddleware.ts +1 -1
- package/src/definePlugin.ts +304 -8
- package/src/defineResolver.ts +185 -52
- package/src/devtools.ts +8 -1
- package/src/index.ts +1 -1
- package/src/mocks.ts +1 -2
- package/src/storages/fsStorage.ts +6 -30
- package/src/types.ts +37 -1292
- package/dist/PluginDriver-BXibeQk-.cjs.map +0 -1
- package/dist/PluginDriver-DV3p2Hky.js.map +0 -1
- package/src/Kubb.ts +0 -300
- 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/createKubb.ts
CHANGED
|
@@ -1,20 +1,667 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
|
+
import { version as nodeVersion } from 'node:process'
|
|
3
|
+
import type { PossiblePromise } from '@internals/utils'
|
|
2
4
|
import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, URLPath } from '@internals/utils'
|
|
3
|
-
import type { FileNode, OperationNode } from '@kubb/ast'
|
|
4
|
-
import { transform, walk } from '@kubb/ast'
|
|
5
|
+
import type { FileNode, InputNode, OperationNode, SchemaNode } from '@kubb/ast'
|
|
6
|
+
import { collectUsedSchemaNames, transform, walk } from '@kubb/ast'
|
|
7
|
+
import { version as KubbVersion } from '../package.json'
|
|
5
8
|
import { DEFAULT_BANNER, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
|
|
9
|
+
import type { Adapter, AdapterSource } from './createAdapter.ts'
|
|
6
10
|
import type { RendererFactory } from './createRenderer.ts'
|
|
7
|
-
import type {
|
|
11
|
+
import type { Storage } from './createStorage.ts'
|
|
12
|
+
import type { GeneratorContext, Generator } from './defineGenerator.ts'
|
|
13
|
+
import type { Middleware } from './defineMiddleware.ts'
|
|
8
14
|
import type { Parser } from './defineParser.ts'
|
|
9
|
-
import type { Plugin } from './definePlugin.ts'
|
|
15
|
+
import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext, NormalizedPlugin, Plugin } from './definePlugin.ts'
|
|
10
16
|
import { FileProcessor } from './FileProcessor.ts'
|
|
11
|
-
import
|
|
12
|
-
import { PluginDriver } from './PluginDriver.ts'
|
|
13
|
-
import { applyHookResult } from './renderNode.ts'
|
|
17
|
+
import { applyHookResult, PluginDriver } from './PluginDriver.ts'
|
|
14
18
|
import { fsStorage } from './storages/fsStorage.ts'
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Safely extracts a type from a registry, returning `{}` if the key doesn't exist.
|
|
22
|
+
* Enables optional interface augmentation for `Kubb.ConfigOptionsRegistry` and `Kubb.PluginOptionsRegistry`
|
|
23
|
+
* without requiring changes to core.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
type ExtractRegistryKey<T, K extends PropertyKey> = K extends keyof T ? T[K] : {}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reference to an input file to generate code from.
|
|
31
|
+
*
|
|
32
|
+
* Specify an absolute path or a path relative to the config file location.
|
|
33
|
+
* The adapter will parse this file (e.g., OpenAPI YAML or JSON) into the universal AST.
|
|
34
|
+
*/
|
|
35
|
+
export type InputPath = {
|
|
36
|
+
/**
|
|
37
|
+
* Path to your Swagger/OpenAPI file, absolute or relative to the config file location.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* { path: './petstore.yaml' }
|
|
42
|
+
* { path: '/absolute/path/to/openapi.json' }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
path: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Inline input data to generate code from.
|
|
50
|
+
*
|
|
51
|
+
* Useful when you want to pass the specification directly instead of from a file.
|
|
52
|
+
* Can be a string (YAML/JSON) or a parsed object.
|
|
53
|
+
*/
|
|
54
|
+
export type InputData = {
|
|
55
|
+
/**
|
|
56
|
+
* Swagger/OpenAPI data as a string (YAML/JSON) or a parsed object.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* { data: fs.readFileSync('./openapi.yaml', 'utf8') }
|
|
61
|
+
* { data: { openapi: '3.1.0', info: { ... } } }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
data: string | unknown
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type Input = InputPath | InputData
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Build configuration for Kubb code generation.
|
|
71
|
+
*
|
|
72
|
+
* The Config is the main entry point for customizing how Kubb generates code. It specifies:
|
|
73
|
+
* - What to generate from (adapter + input)
|
|
74
|
+
* - Where to output generated code (output)
|
|
75
|
+
* - How to generate (plugins + middleware)
|
|
76
|
+
* - Runtime details (parsers, storage, renderer)
|
|
77
|
+
*
|
|
78
|
+
* See `UserConfig` for a relaxed version with sensible defaults.
|
|
79
|
+
*
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
export type Config<TInput = Input> = {
|
|
83
|
+
/**
|
|
84
|
+
* Display name for this configuration in CLI output and logs.
|
|
85
|
+
* Useful when running multiple builds with `defineConfig` arrays.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* name: 'api-client'
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
name?: string
|
|
93
|
+
/**
|
|
94
|
+
* Project root directory, absolute or relative to the config file.
|
|
95
|
+
* @default process.cwd()
|
|
96
|
+
*/
|
|
97
|
+
root: string
|
|
98
|
+
/**
|
|
99
|
+
* Parsers that convert generated files to strings.
|
|
100
|
+
* Each parser handles specific extensions (e.g. `.ts`, `.tsx`).
|
|
101
|
+
* A fallback parser is appended for unhandled extensions.
|
|
102
|
+
* When omitted, defaults to `parserTs` from `@kubb/parser-ts`.
|
|
103
|
+
*
|
|
104
|
+
* @default [parserTs] from `@kubb/parser-ts`
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* import { parserTs, tsxParser } from '@kubb/parser-ts'
|
|
108
|
+
* export default defineConfig({
|
|
109
|
+
* parsers: [parserTs, tsxParser],
|
|
110
|
+
* })
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
parsers: Array<Parser>
|
|
114
|
+
/**
|
|
115
|
+
* Adapter that parses input files into the universal `InputNode` representation.
|
|
116
|
+
* Use `@kubb/adapter-oas` for OpenAPI/Swagger or `@kubb/adapter-asyncapi` for other formats.
|
|
117
|
+
*
|
|
118
|
+
* When omitted, Kubb runs in plugin-only mode: `kubb:plugin:setup` fires and files
|
|
119
|
+
* injected via `injectFile` are written, but no AST walk occurs and generator hooks
|
|
120
|
+
* (`kubb:generate:schema`, `kubb:generate:operation`) are never emitted.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* import { adapterOas } from '@kubb/adapter-oas'
|
|
125
|
+
* export default defineConfig({
|
|
126
|
+
* adapter: adapterOas(),
|
|
127
|
+
* input: { path: './petstore.yaml' },
|
|
128
|
+
* })
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
adapter?: Adapter
|
|
132
|
+
/**
|
|
133
|
+
* Source file or data to generate code from.
|
|
134
|
+
* Use `input.path` for a file path or `input.data` for inline data.
|
|
135
|
+
* Required when an adapter is configured; omit when running in plugin-only mode.
|
|
136
|
+
*/
|
|
137
|
+
input?: TInput
|
|
138
|
+
output: {
|
|
139
|
+
/**
|
|
140
|
+
* Output directory for generated files, absolute or relative to `root`.
|
|
141
|
+
*
|
|
142
|
+
* All generated files will be written under this directory. Subdirectories can be created
|
|
143
|
+
* by plugins based on grouping strategy (by tag, path, etc.).
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* output: {
|
|
148
|
+
* path: './src/gen', // generates ./src/gen/api.ts, ./src/gen/types.ts, etc.
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
path: string
|
|
153
|
+
/**
|
|
154
|
+
* Remove all files from the output directory before starting the build.
|
|
155
|
+
*
|
|
156
|
+
* Useful to ensure old generated files aren't mixed with new ones.
|
|
157
|
+
* Set to `true` for fresh builds, `false` to preserve manual edits in output dir.
|
|
158
|
+
*
|
|
159
|
+
* @default false
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* clean: true // wipes ./src/gen/* before generating
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
clean?: boolean
|
|
166
|
+
/**
|
|
167
|
+
* Auto-format generated files after code generation completes.
|
|
168
|
+
*
|
|
169
|
+
* Applies a code formatter to all generated files. Use `'auto'` to detect which formatter
|
|
170
|
+
* is available on your system. Pass `false` to skip formatting (useful for CI or specific workflows).
|
|
171
|
+
*
|
|
172
|
+
* @default false
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* format: 'auto' // auto-detect prettier, biome, or oxfmt
|
|
176
|
+
* format: 'prettier' // force prettier
|
|
177
|
+
* format: false // skip formatting
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
format?: 'auto' | 'prettier' | 'biome' | 'oxfmt' | false
|
|
181
|
+
/**
|
|
182
|
+
* Auto-lint generated files after code generation completes.
|
|
183
|
+
*
|
|
184
|
+
* Analyzes all generated files for style/correctness issues. Use `'auto'` to detect which linter
|
|
185
|
+
* is available on your system. Pass `false` to skip linting.
|
|
186
|
+
*
|
|
187
|
+
* @default false
|
|
188
|
+
* @example
|
|
189
|
+
* ```ts
|
|
190
|
+
* lint: 'auto' // auto-detect oxlint, biome, or eslint
|
|
191
|
+
* lint: 'eslint' // force eslint
|
|
192
|
+
* lint: false // skip linting
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
lint?: 'auto' | 'eslint' | 'biome' | 'oxlint' | false
|
|
196
|
+
/**
|
|
197
|
+
* Map file extensions to different output extensions.
|
|
198
|
+
*
|
|
199
|
+
* Useful when you want generated `.ts` imports to reference `.js` files or vice versa (e.g., for ESM dual packages).
|
|
200
|
+
* Keys are the original extension, values are the output extension. Use empty string `''` to omit extension.
|
|
201
|
+
*
|
|
202
|
+
* @default { '.ts': '.ts' }
|
|
203
|
+
* @example
|
|
204
|
+
* ```ts
|
|
205
|
+
* extension: { '.ts': '.js' } // generates import './api.js' instead of './api.ts'
|
|
206
|
+
* extension: { '.ts': '', '.tsx': '.jsx' }
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
extension?: Record<FileNode['extname'], FileNode['extname'] | ''>
|
|
210
|
+
/**
|
|
211
|
+
* Banner text prepended to every generated file.
|
|
212
|
+
*
|
|
213
|
+
* Useful for auto-generation notices or license headers. Choose a preset or write custom text.
|
|
214
|
+
* Use `'simple'` for a basic Kubb banner, `'full'` for detailed metadata, or `false` to omit.
|
|
215
|
+
*
|
|
216
|
+
* @default 'simple'
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* defaultBanner: 'simple' // "This file was autogenerated by Kubb"
|
|
220
|
+
* defaultBanner: 'full' // adds source, title, description, API version
|
|
221
|
+
* defaultBanner: false // no banner
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
defaultBanner?: 'simple' | 'full' | false
|
|
225
|
+
/**
|
|
226
|
+
* When `true`, overwrites existing files. When `false`, skips generated files that already exist.
|
|
227
|
+
*
|
|
228
|
+
* Individual plugins can override this setting. This is useful for preventing accidental data loss
|
|
229
|
+
* when re-generating while you have local edits in the output folder.
|
|
230
|
+
*
|
|
231
|
+
* @default false
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* override: true // regenerate everything, even existing files
|
|
235
|
+
* override: false // skip files that already exist
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
override?: boolean
|
|
239
|
+
} & ExtractRegistryKey<Kubb.ConfigOptionsRegistry, 'output'>
|
|
240
|
+
/**
|
|
241
|
+
* Storage backend that controls where and how generated files are persisted.
|
|
242
|
+
*
|
|
243
|
+
* Defaults to `fsStorage()` which writes to the file system. Pass `memoryStorage()` to keep files in RAM,
|
|
244
|
+
* or implement a custom `Storage` interface to write to cloud storage, databases, or other backends.
|
|
245
|
+
*
|
|
246
|
+
* @default fsStorage()
|
|
247
|
+
* @example
|
|
248
|
+
* ```ts
|
|
249
|
+
* import { memoryStorage } from '@kubb/core'
|
|
250
|
+
*
|
|
251
|
+
* // Keep generated files in memory (useful for testing, CI pipelines)
|
|
252
|
+
* storage: memoryStorage()
|
|
253
|
+
*
|
|
254
|
+
* // Use custom S3 storage
|
|
255
|
+
* storage: myS3Storage()
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* @see {@link Storage} interface for implementing custom backends.
|
|
259
|
+
*/
|
|
260
|
+
storage: Storage
|
|
261
|
+
/**
|
|
262
|
+
* Plugins that execute during the build to generate code and transform the AST.
|
|
263
|
+
*
|
|
264
|
+
* Each plugin processes the AST produced by the adapter and can emit files for different
|
|
265
|
+
* programming languages or formats (TypeScript, Zod schemas, Faker data, etc.).
|
|
266
|
+
* Dependencies are enforced — an error is thrown if a plugin requires another plugin that isn't registered.
|
|
267
|
+
*
|
|
268
|
+
* Plugins can declare their own options via `PluginFactoryOptions`. See plugin documentation for details.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```ts
|
|
272
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
273
|
+
* import { pluginZod } from '@kubb/plugin-zod'
|
|
274
|
+
*
|
|
275
|
+
* plugins: [
|
|
276
|
+
* pluginTs({ output: { path: './src/gen' } }),
|
|
277
|
+
* pluginZod({ output: { path: './src/gen' } }),
|
|
278
|
+
* ]
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
plugins: Array<Plugin>
|
|
282
|
+
/**
|
|
283
|
+
* Middleware instances that observe build events and post-process generated code.
|
|
284
|
+
*
|
|
285
|
+
* Middleware fires AFTER all plugins for each event. Perfect for tasks like:
|
|
286
|
+
* - Auditing what was generated
|
|
287
|
+
* - Adding barrel/index files
|
|
288
|
+
* - Validating output
|
|
289
|
+
* - Running custom transformations
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* import { middlewareBarrel } from '@kubb/middleware-barrel'
|
|
294
|
+
*
|
|
295
|
+
* middleware: [middlewareBarrel()]
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
298
|
+
* @see {@link defineMiddleware} to create custom middleware.
|
|
299
|
+
*/
|
|
300
|
+
middleware?: Array<Middleware>
|
|
301
|
+
/**
|
|
302
|
+
* Renderer that converts generated AST nodes to code strings.
|
|
303
|
+
*
|
|
304
|
+
* By default, Kubb uses the JSX renderer (`rendererJsx`). Pass a custom renderer to support
|
|
305
|
+
* different output formats (template engines, code generation DSLs, etc.).
|
|
306
|
+
*
|
|
307
|
+
* @default rendererJsx() // from @kubb/renderer-jsx
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* import { rendererJsx } from '@kubb/renderer-jsx'
|
|
311
|
+
* renderer: rendererJsx()
|
|
312
|
+
* ```
|
|
313
|
+
*
|
|
314
|
+
* @see {@link Renderer} to implement a custom renderer.
|
|
315
|
+
*/
|
|
316
|
+
renderer?: RendererFactory
|
|
317
|
+
/**
|
|
318
|
+
* Kubb Studio cloud integration settings.
|
|
319
|
+
*
|
|
320
|
+
* Kubb Studio (https://kubb.studio) is a web-based IDE for managing API specs and generated code.
|
|
321
|
+
* Set to `true` to enable with default settings, or pass an object to customize the Studio URL.
|
|
322
|
+
*
|
|
323
|
+
* @default false // disabled by default
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* devtools: true // use default Kubb Studio
|
|
327
|
+
* devtools: { studioUrl: 'https://my-studio.dev' } // custom Studio instance
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
devtools?:
|
|
331
|
+
| true
|
|
332
|
+
| {
|
|
333
|
+
/**
|
|
334
|
+
* Override the Kubb Studio base URL.
|
|
335
|
+
* @default 'https://kubb.studio'
|
|
336
|
+
*/
|
|
337
|
+
studioUrl?: typeof DEFAULT_STUDIO_URL | (string & {})
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Lifecycle hooks that execute during or after the build process.
|
|
341
|
+
*
|
|
342
|
+
* Hooks allow you to run external tools (prettier, eslint, custom scripts) based on build events.
|
|
343
|
+
* Currently supports the `done` hook which fires after all plugins and middleware complete.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```ts
|
|
347
|
+
* hooks: {
|
|
348
|
+
* done: 'prettier --write "./src/gen"', // auto-format generated files
|
|
349
|
+
* // or multiple commands:
|
|
350
|
+
* done: ['prettier --write "./src/gen"', 'eslint --fix "./src/gen"']
|
|
351
|
+
* }
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
hooks?: {
|
|
355
|
+
/**
|
|
356
|
+
* Command(s) to run after all plugins and middleware complete generation.
|
|
357
|
+
*
|
|
358
|
+
* Useful for post-processing: formatting, linting, copying files, or custom validation.
|
|
359
|
+
* Pass a single command string or array of command strings to run sequentially.
|
|
360
|
+
* Commands are executed relative to the `root` directory.
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```ts
|
|
364
|
+
* done: 'prettier --write "./src/gen"'
|
|
365
|
+
* done: ['prettier --write "./src/gen"', 'eslint --fix "./src/gen"']
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
done?: string | Array<string>
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Partial `Config` for user-facing entry points with sensible defaults.
|
|
374
|
+
*
|
|
375
|
+
* `UserConfig` is what you pass to `defineConfig()`. It has optional `root`, `plugins`, `parsers`, and `adapter`
|
|
376
|
+
* fields (which fall back to sensible defaults). All other Config options are available, including `output`, `input`,
|
|
377
|
+
* `storage`, `middleware`, `renderer`, `devtools`, and `hooks`.
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* ```ts
|
|
381
|
+
* export default defineConfig({
|
|
382
|
+
* input: { path: './petstore.yaml' },
|
|
383
|
+
* output: { path: './src/gen' },
|
|
384
|
+
* plugins: [pluginTs(), pluginZod()],
|
|
385
|
+
* })
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
export type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins' | 'parsers' | 'adapter'| 'storage'> & {
|
|
389
|
+
/**
|
|
390
|
+
* Project root directory, absolute or relative to the config file location.
|
|
391
|
+
* @default process.cwd()
|
|
392
|
+
*/
|
|
393
|
+
root?: string
|
|
394
|
+
/**
|
|
395
|
+
* Custom parsers that convert generated AST nodes to strings (TypeScript, JSON, markdown, etc.).
|
|
396
|
+
* @default [parserTs] // from `@kubb/parser-ts`
|
|
397
|
+
*/
|
|
398
|
+
parsers?: Array<Parser>
|
|
399
|
+
/**
|
|
400
|
+
* Adapter that parses your API specification into Kubb's universal AST.
|
|
401
|
+
* When omitted, Kubb runs in plugin-only mode.
|
|
402
|
+
*/
|
|
403
|
+
adapter?: Adapter
|
|
404
|
+
/**
|
|
405
|
+
* Plugins that execute during the build to generate code and transform the AST.
|
|
406
|
+
* @default []
|
|
407
|
+
*/
|
|
408
|
+
plugins?: Array<Plugin>
|
|
409
|
+
/**
|
|
410
|
+
* Storage backend that controls where and how generated files are persisted.
|
|
411
|
+
* @default fsStorage()
|
|
412
|
+
*/
|
|
413
|
+
storage?: Storage
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
declare global {
|
|
417
|
+
namespace Kubb {
|
|
418
|
+
/**
|
|
419
|
+
* Registry that maps plugin names to their `PluginFactoryOptions`.
|
|
420
|
+
* Augment this interface in each plugin's `types.ts` to enable automatic
|
|
421
|
+
* typing for `getPlugin` and `requirePlugin`.
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```ts
|
|
425
|
+
* // packages/plugin-ts/src/types.ts
|
|
426
|
+
* declare global {
|
|
427
|
+
* namespace Kubb {
|
|
428
|
+
* interface PluginRegistry {
|
|
429
|
+
* 'plugin-ts': PluginTs
|
|
430
|
+
* }
|
|
431
|
+
* }
|
|
432
|
+
* }
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
interface PluginRegistry {}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Extension point for root `Config['output']` options.
|
|
439
|
+
* Augment the `output` key in middleware or plugin packages to add extra fields
|
|
440
|
+
* to the global output configuration without touching core types.
|
|
441
|
+
*
|
|
442
|
+
* @example
|
|
443
|
+
* ```ts
|
|
444
|
+
* // packages/middleware-barrel/src/types.ts
|
|
445
|
+
* declare global {
|
|
446
|
+
* namespace Kubb {
|
|
447
|
+
* interface ConfigOptionsRegistry {
|
|
448
|
+
* output: {
|
|
449
|
+
* barrel?: import('./types.ts').BarrelConfig | false
|
|
450
|
+
* }
|
|
451
|
+
* }
|
|
452
|
+
* }
|
|
453
|
+
* }
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
interface ConfigOptionsRegistry {}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Extension point for per-plugin `Output` options.
|
|
460
|
+
* Augment the `output` key in middleware or plugin packages to add extra fields
|
|
461
|
+
* to the per-plugin output configuration without touching core types.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* // packages/middleware-barrel/src/types.ts
|
|
466
|
+
* declare global {
|
|
467
|
+
* namespace Kubb {
|
|
468
|
+
* interface PluginOptionsRegistry {
|
|
469
|
+
* output: {
|
|
470
|
+
* barrel?: import('./types.ts').PluginBarrelConfig | false
|
|
471
|
+
* }
|
|
472
|
+
* }
|
|
473
|
+
* }
|
|
474
|
+
* }
|
|
475
|
+
* ```
|
|
476
|
+
*/
|
|
477
|
+
interface PluginOptionsRegistry {}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Lifecycle events emitted during Kubb code generation.
|
|
483
|
+
* Use these for logging, progress tracking, and custom integrations.
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* ```typescript
|
|
487
|
+
* import type { AsyncEventEmitter } from '@internals/utils'
|
|
488
|
+
* import type { KubbHooks } from '@kubb/core'
|
|
489
|
+
*
|
|
490
|
+
* const hooks: AsyncEventEmitter<KubbHooks> = new AsyncEventEmitter()
|
|
491
|
+
*
|
|
492
|
+
* hooks.on('kubb:lifecycle:start', () => {
|
|
493
|
+
* console.log('Starting Kubb generation')
|
|
494
|
+
* })
|
|
495
|
+
*
|
|
496
|
+
* hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
|
|
497
|
+
* console.log(`Plugin ${plugin.name} completed in ${duration}ms`)
|
|
498
|
+
* })
|
|
499
|
+
* ```
|
|
500
|
+
*/
|
|
501
|
+
export interface KubbHooks {
|
|
502
|
+
'kubb:lifecycle:start': [ctx: KubbLifecycleStartContext]
|
|
503
|
+
'kubb:lifecycle:end': []
|
|
504
|
+
'kubb:config:start': []
|
|
505
|
+
'kubb:config:end': [ctx: KubbConfigEndContext]
|
|
506
|
+
'kubb:generation:start': [ctx: KubbGenerationStartContext]
|
|
507
|
+
'kubb:generation:end': [ctx: KubbGenerationEndContext]
|
|
508
|
+
'kubb:generation:summary': [ctx: KubbGenerationSummaryContext]
|
|
509
|
+
'kubb:format:start': []
|
|
510
|
+
'kubb:format:end': []
|
|
511
|
+
'kubb:lint:start': []
|
|
512
|
+
'kubb:lint:end': []
|
|
513
|
+
'kubb:hooks:start': []
|
|
514
|
+
'kubb:hooks:end': []
|
|
515
|
+
'kubb:hook:start': [ctx: KubbHookStartContext]
|
|
516
|
+
'kubb:hook:end': [ctx: KubbHookEndContext]
|
|
517
|
+
'kubb:version:new': [ctx: KubbVersionNewContext]
|
|
518
|
+
'kubb:info': [ctx: KubbInfoContext]
|
|
519
|
+
'kubb:error': [ctx: KubbErrorContext]
|
|
520
|
+
'kubb:success': [ctx: KubbSuccessContext]
|
|
521
|
+
'kubb:warn': [ctx: KubbWarnContext]
|
|
522
|
+
'kubb:debug': [ctx: KubbDebugContext]
|
|
523
|
+
'kubb:files:processing:start': [ctx: KubbFilesProcessingStartContext]
|
|
524
|
+
'kubb:file:processing:update': [ctx: KubbFileProcessingUpdateContext]
|
|
525
|
+
'kubb:files:processing:end': [ctx: KubbFilesProcessingEndContext]
|
|
526
|
+
'kubb:plugin:start': [ctx: KubbPluginStartContext]
|
|
527
|
+
'kubb:plugin:end': [ctx: KubbPluginEndContext]
|
|
528
|
+
'kubb:plugin:setup': [ctx: KubbPluginSetupContext]
|
|
529
|
+
'kubb:build:start': [ctx: KubbBuildStartContext]
|
|
530
|
+
'kubb:plugins:end': [ctx: KubbPluginsEndContext]
|
|
531
|
+
'kubb:build:end': [ctx: KubbBuildEndContext]
|
|
532
|
+
'kubb:generate:schema': [node: SchemaNode, ctx: GeneratorContext]
|
|
533
|
+
'kubb:generate:operation': [node: OperationNode, ctx: GeneratorContext]
|
|
534
|
+
'kubb:generate:operations': [nodes: Array<OperationNode>, ctx: GeneratorContext]
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export type KubbBuildStartContext = {
|
|
538
|
+
config: Config
|
|
539
|
+
adapter: Adapter
|
|
540
|
+
inputNode: InputNode
|
|
541
|
+
getPlugin<TName extends keyof Kubb.PluginRegistry>(name: TName): Plugin<Kubb.PluginRegistry[TName]> | undefined
|
|
542
|
+
getPlugin(name: string): Plugin | undefined
|
|
543
|
+
readonly files: ReadonlyArray<FileNode>
|
|
544
|
+
upsertFile: (...files: Array<FileNode>) => void
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export type KubbPluginsEndContext = {
|
|
548
|
+
config: Config
|
|
549
|
+
readonly files: ReadonlyArray<FileNode>
|
|
550
|
+
upsertFile: (...files: Array<FileNode>) => void
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export type KubbBuildEndContext = {
|
|
554
|
+
files: Array<FileNode>
|
|
555
|
+
config: Config
|
|
556
|
+
outputDir: string
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export type KubbLifecycleStartContext = {
|
|
560
|
+
version: string
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export type KubbConfigEndContext = {
|
|
564
|
+
configs: Array<Config>
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export type KubbGenerationStartContext = {
|
|
568
|
+
config: Config
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export type KubbGenerationEndContext = {
|
|
572
|
+
config: Config
|
|
573
|
+
files: Array<FileNode>
|
|
574
|
+
sources: Map<string, string>
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export type KubbGenerationSummaryContext = {
|
|
578
|
+
config: Config
|
|
579
|
+
failedPlugins: Set<{ plugin: Plugin; error: Error }>
|
|
580
|
+
status: 'success' | 'failed'
|
|
581
|
+
hrStart: [number, number]
|
|
582
|
+
filesCreated: number
|
|
583
|
+
pluginTimings?: Map<Plugin['name'], number>
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export type KubbVersionNewContext = {
|
|
587
|
+
currentVersion: string
|
|
588
|
+
latestVersion: string
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export type KubbInfoContext = {
|
|
592
|
+
message: string
|
|
593
|
+
info?: string
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export type KubbErrorContext = {
|
|
597
|
+
error: Error
|
|
598
|
+
meta?: Record<string, unknown>
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export type KubbSuccessContext = {
|
|
602
|
+
message: string
|
|
603
|
+
info?: string
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export type KubbWarnContext = {
|
|
607
|
+
message: string
|
|
608
|
+
info?: string
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export type KubbDebugContext = {
|
|
612
|
+
date: Date
|
|
613
|
+
logs: Array<string>
|
|
614
|
+
fileName?: string
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export type KubbFilesProcessingStartContext = {
|
|
618
|
+
files: Array<FileNode>
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export type KubbFileProcessingUpdateContext = {
|
|
622
|
+
processed: number
|
|
623
|
+
total: number
|
|
624
|
+
percentage: number
|
|
625
|
+
source?: string
|
|
626
|
+
file: FileNode
|
|
627
|
+
config: Config
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
export type KubbFilesProcessingEndContext = {
|
|
631
|
+
files: Array<FileNode>
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
export type KubbHookStartContext = {
|
|
635
|
+
id?: string
|
|
636
|
+
command: string
|
|
637
|
+
args?: readonly string[]
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export type KubbHookEndContext = {
|
|
641
|
+
id?: string
|
|
642
|
+
command: string
|
|
643
|
+
args?: readonly string[]
|
|
644
|
+
success: boolean
|
|
645
|
+
error: Error | null
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* CLI options derived from command-line flags.
|
|
650
|
+
*/
|
|
651
|
+
export type CLIOptions = {
|
|
652
|
+
config?: string
|
|
653
|
+
watch?: boolean
|
|
654
|
+
/** @default 'silent' */
|
|
655
|
+
logLevel?: 'silent' | 'info' | 'debug'
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* All accepted forms of a Kubb configuration.
|
|
660
|
+
* Accepts `Config`/`Config[]`/promise or a factory (optionally receiving `TCliOptions`).
|
|
661
|
+
*/
|
|
662
|
+
export type PossibleConfig<TCliOptions = undefined> =
|
|
663
|
+
| PossiblePromise<Config | Config[]>
|
|
664
|
+
| ((...args: [TCliOptions] extends [undefined] ? [] : [TCliOptions]) => PossiblePromise<Config | Config[]>)
|
|
18
665
|
|
|
19
666
|
type SetupOptions = {
|
|
20
667
|
hooks?: AsyncEventEmitter<KubbHooks>
|
|
@@ -41,24 +688,79 @@ export type BuildOutput = {
|
|
|
41
688
|
sources: Map<string, string>
|
|
42
689
|
}
|
|
43
690
|
|
|
691
|
+
/**
|
|
692
|
+
* Kubb code generation instance returned by {@link createKubb}.
|
|
693
|
+
*
|
|
694
|
+
* Use this when orchestrating multiple builds, inspecting plugin timings, or integrating Kubb into a larger toolchain.
|
|
695
|
+
* For a single one-off build, chain directly: `await createKubb(config).build()`.
|
|
696
|
+
*/
|
|
697
|
+
export type Kubb = {
|
|
698
|
+
/**
|
|
699
|
+
* Shared event emitter for lifecycle and status events. Attach listeners before calling `setup()` or `build()`.
|
|
700
|
+
*/
|
|
701
|
+
readonly hooks: AsyncEventEmitter<KubbHooks>
|
|
702
|
+
/**
|
|
703
|
+
* Generated source code keyed by absolute file path. Available after `build()` or `safeBuild()` completes.
|
|
704
|
+
*/
|
|
705
|
+
readonly sources: Map<string, string>
|
|
706
|
+
/**
|
|
707
|
+
* Plugin driver managing all plugins. Available after `setup()` completes.
|
|
708
|
+
*/
|
|
709
|
+
readonly driver: PluginDriver | undefined
|
|
710
|
+
/**
|
|
711
|
+
* Resolved configuration with defaults applied. Available after `setup()` completes.
|
|
712
|
+
*/
|
|
713
|
+
readonly config: Config | undefined
|
|
714
|
+
/**
|
|
715
|
+
* Resolves config and initializes the driver. `build()` calls this automatically.
|
|
716
|
+
*/
|
|
717
|
+
setup(): Promise<void>
|
|
718
|
+
/**
|
|
719
|
+
* Runs the full pipeline and throws on any plugin error. Automatically calls `setup()` if needed.
|
|
720
|
+
*/
|
|
721
|
+
build(): Promise<BuildOutput>
|
|
722
|
+
/**
|
|
723
|
+
* Runs the full pipeline and captures errors in `BuildOutput` instead of throwing. Automatically calls `setup()` if needed.
|
|
724
|
+
*/
|
|
725
|
+
safeBuild(): Promise<BuildOutput>
|
|
726
|
+
}
|
|
727
|
+
|
|
44
728
|
type SetupResult = {
|
|
45
729
|
hooks: AsyncEventEmitter<KubbHooks>
|
|
46
730
|
driver: PluginDriver
|
|
47
731
|
sources: Map<string, string>
|
|
48
732
|
config: Config
|
|
49
|
-
storage: Storage | null
|
|
50
733
|
}
|
|
51
734
|
|
|
52
735
|
async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promise<SetupResult> {
|
|
53
736
|
const hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
|
|
54
|
-
|
|
737
|
+
const config: Config = {
|
|
738
|
+
...userConfig,
|
|
739
|
+
root: userConfig.root || process.cwd(),
|
|
740
|
+
parsers: userConfig.parsers ?? [],
|
|
741
|
+
adapter: userConfig.adapter,
|
|
742
|
+
output: {
|
|
743
|
+
format: false,
|
|
744
|
+
lint: false,
|
|
745
|
+
extension: DEFAULT_EXTENSION,
|
|
746
|
+
defaultBanner: DEFAULT_BANNER,
|
|
747
|
+
...userConfig.output,
|
|
748
|
+
},
|
|
749
|
+
storage: userConfig.storage ?? fsStorage(),
|
|
750
|
+
devtools: userConfig.devtools
|
|
751
|
+
? {
|
|
752
|
+
studioUrl: DEFAULT_STUDIO_URL,
|
|
753
|
+
...(typeof userConfig.devtools === 'boolean' ? {} : userConfig.devtools),
|
|
754
|
+
}
|
|
755
|
+
: undefined,
|
|
756
|
+
plugins: (userConfig.plugins ?? []) as unknown as Config['plugins'],
|
|
757
|
+
}
|
|
758
|
+
const driver = new PluginDriver(config, {
|
|
759
|
+
hooks,
|
|
760
|
+
})
|
|
55
761
|
const sources: Map<string, string> = new Map<string, string>()
|
|
56
762
|
const diagnosticInfo = getDiagnosticInfo()
|
|
57
763
|
|
|
58
|
-
if (Array.isArray(userConfig.input)) {
|
|
59
|
-
await hooks.emit('kubb:warn', { message: 'This feature is still under development — use with caution' })
|
|
60
|
-
}
|
|
61
|
-
|
|
62
764
|
await hooks.emit('kubb:debug', {
|
|
63
765
|
date: new Date(),
|
|
64
766
|
logs: [
|
|
@@ -68,7 +770,7 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
68
770
|
` • Output: ${userConfig.output?.path || 'not specified'}`,
|
|
69
771
|
` • Plugins: ${userConfig.plugins?.length || 0}`,
|
|
70
772
|
'Output Settings:',
|
|
71
|
-
` • Storage: ${
|
|
773
|
+
` • Storage: ${config.storage.name}`,
|
|
72
774
|
` • Formatter: ${userConfig.output?.format || 'none'}`,
|
|
73
775
|
` • Linter: ${userConfig.output?.lint || 'none'}`,
|
|
74
776
|
'Environment:',
|
|
@@ -100,46 +802,16 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
100
802
|
}
|
|
101
803
|
}
|
|
102
804
|
|
|
103
|
-
if (!userConfig.adapter) {
|
|
104
|
-
throw new Error('Adapter should be defined')
|
|
105
|
-
}
|
|
106
805
|
|
|
107
|
-
const config: Config = {
|
|
108
|
-
...userConfig,
|
|
109
|
-
root: userConfig.root || process.cwd(),
|
|
110
|
-
parsers: userConfig.parsers ?? [],
|
|
111
|
-
adapter: userConfig.adapter,
|
|
112
|
-
output: {
|
|
113
|
-
format: false,
|
|
114
|
-
lint: false,
|
|
115
|
-
write: true,
|
|
116
|
-
extension: DEFAULT_EXTENSION,
|
|
117
|
-
defaultBanner: DEFAULT_BANNER,
|
|
118
|
-
...userConfig.output,
|
|
119
|
-
},
|
|
120
|
-
devtools: userConfig.devtools
|
|
121
|
-
? {
|
|
122
|
-
studioUrl: DEFAULT_STUDIO_URL,
|
|
123
|
-
...(typeof userConfig.devtools === 'boolean' ? {} : userConfig.devtools),
|
|
124
|
-
}
|
|
125
|
-
: undefined,
|
|
126
|
-
plugins: userConfig.plugins as unknown as Config['plugins'],
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const storage: Storage | null = config.output.write === false ? null : (config.storage ?? fsStorage())
|
|
130
806
|
|
|
131
807
|
if (config.output.clean) {
|
|
132
808
|
await hooks.emit('kubb:debug', {
|
|
133
809
|
date: new Date(),
|
|
134
810
|
logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
|
|
135
811
|
})
|
|
136
|
-
await storage
|
|
812
|
+
await config.storage.clear(resolve(config.root, config.output.path))
|
|
137
813
|
}
|
|
138
814
|
|
|
139
|
-
const driver = new PluginDriver(config, {
|
|
140
|
-
hooks,
|
|
141
|
-
})
|
|
142
|
-
|
|
143
815
|
// Register middleware hooks after all plugin hooks are registered.
|
|
144
816
|
// Because AsyncEventEmitter calls listeners in registration order,
|
|
145
817
|
// middleware hooks for any event fire after all plugin hooks for that event.
|
|
@@ -156,48 +828,51 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
156
828
|
}
|
|
157
829
|
}
|
|
158
830
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
throw new Error('No adapter configured. Please provide an adapter in your kubb.config.ts.')
|
|
162
|
-
}
|
|
163
|
-
const source = inputToAdapterSource(config)
|
|
831
|
+
if (config.adapter) {
|
|
832
|
+
const source = inputToAdapterSource(config)
|
|
164
833
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
834
|
+
await hooks.emit('kubb:debug', {
|
|
835
|
+
date: new Date(),
|
|
836
|
+
logs: [`Running adapter: ${config.adapter.name}`],
|
|
837
|
+
})
|
|
169
838
|
|
|
170
|
-
|
|
171
|
-
|
|
839
|
+
driver.adapter = config.adapter
|
|
840
|
+
driver.inputNode = await config.adapter.parse(source)
|
|
172
841
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
842
|
+
await hooks.emit('kubb:debug', {
|
|
843
|
+
date: new Date(),
|
|
844
|
+
logs: [
|
|
845
|
+
`✓ Adapter '${config.adapter.name}' resolved InputNode`,
|
|
846
|
+
` • Schemas: ${driver.inputNode.schemas.length}`,
|
|
847
|
+
` • Operations: ${driver.inputNode.operations.length}`,
|
|
848
|
+
],
|
|
849
|
+
})
|
|
850
|
+
}
|
|
181
851
|
|
|
182
852
|
return {
|
|
183
853
|
config,
|
|
184
854
|
hooks,
|
|
185
855
|
driver,
|
|
186
856
|
sources,
|
|
187
|
-
storage,
|
|
188
857
|
}
|
|
189
858
|
}
|
|
190
859
|
|
|
191
860
|
/**
|
|
192
861
|
* Walks the AST and dispatches nodes to a plugin's direct AST hooks
|
|
193
862
|
* (`schema`, `operation`, `operations`).
|
|
863
|
+
*
|
|
864
|
+
* When `include` contains only operation-scoped filters (`tag`, `operationId`, `path`,
|
|
865
|
+
* `method`, `contentType`) and no `schemaName` filter, the function pre-computes the set
|
|
866
|
+
* of top-level schema names transitively reachable from the included operations and skips
|
|
867
|
+
* schemas that fall outside that set. This ensures that component schemas referenced
|
|
868
|
+
* exclusively by excluded operations are not generated.
|
|
194
869
|
*/
|
|
195
870
|
async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorContext): Promise<void> {
|
|
196
871
|
const { adapter, inputNode, resolver, driver } = context
|
|
197
872
|
const { exclude, include, override } = plugin.options
|
|
198
873
|
|
|
199
874
|
if (!adapter || !inputNode) {
|
|
200
|
-
throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g.
|
|
875
|
+
throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. adapterOas()) before this plugin in your Kubb config.`)
|
|
201
876
|
}
|
|
202
877
|
|
|
203
878
|
function resolveRenderer(gen: Generator): RendererFactory | undefined {
|
|
@@ -212,10 +887,30 @@ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorCon
|
|
|
212
887
|
resolver: driver.getResolver(plugin.name),
|
|
213
888
|
}
|
|
214
889
|
|
|
890
|
+
// When `include` has operation-based filters (tag, operationId, path, method, contentType)
|
|
891
|
+
// but no schema-level filters (schemaName), pre-compute the set of top-level schema names
|
|
892
|
+
// that are transitively referenced by the included operations. Schemas outside that set are
|
|
893
|
+
// skipped so that types belonging exclusively to excluded operations are not generated.
|
|
894
|
+
const operationFilterTypes = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])
|
|
895
|
+
const hasOperationBasedIncludes = include?.some(({ type }) => operationFilterTypes.has(type)) ?? false
|
|
896
|
+
const hasSchemaNameIncludes = include?.some(({ type }) => type === 'schemaName') ?? false
|
|
897
|
+
|
|
898
|
+
let allowedSchemaNames: Set<string> | undefined
|
|
899
|
+
if (hasOperationBasedIncludes && !hasSchemaNameIncludes) {
|
|
900
|
+
const includedOps = inputNode.operations.filter((op) => resolver.resolveOptions(op, { options: plugin.options, exclude, include, override }) !== null)
|
|
901
|
+
allowedSchemaNames = collectUsedSchemaNames(includedOps, inputNode.schemas)
|
|
902
|
+
}
|
|
903
|
+
|
|
215
904
|
await walk(inputNode, {
|
|
216
905
|
depth: 'shallow',
|
|
217
906
|
async schema(node) {
|
|
218
907
|
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
908
|
+
|
|
909
|
+
// Skip named top-level schemas that are not reachable from any included operation.
|
|
910
|
+
if (allowedSchemaNames !== undefined && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) {
|
|
911
|
+
return
|
|
912
|
+
}
|
|
913
|
+
|
|
219
914
|
const options = resolver.resolveOptions(transformedNode, {
|
|
220
915
|
options: plugin.options,
|
|
221
916
|
exclude,
|
|
@@ -272,7 +967,7 @@ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorCon
|
|
|
272
967
|
}
|
|
273
968
|
|
|
274
969
|
async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
275
|
-
const { driver, hooks, sources
|
|
970
|
+
const { driver, hooks, sources } = setupResult
|
|
276
971
|
|
|
277
972
|
const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
|
|
278
973
|
const pluginTimings = new Map<string, number>()
|
|
@@ -302,7 +997,6 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
302
997
|
const timestamp = new Date()
|
|
303
998
|
|
|
304
999
|
await hooks.emit('kubb:plugin:start', { plugin })
|
|
305
|
-
|
|
306
1000
|
await hooks.emit('kubb:debug', {
|
|
307
1001
|
date: timestamp,
|
|
308
1002
|
logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
|
|
@@ -390,6 +1084,7 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
390
1084
|
|
|
391
1085
|
await fileProcessor.run(files, {
|
|
392
1086
|
parsers: parsersMap,
|
|
1087
|
+
mode: 'parallel',
|
|
393
1088
|
extension: config.output.extension,
|
|
394
1089
|
onStart: async (processingFiles) => {
|
|
395
1090
|
await hooks.emit('kubb:files:processing:start', { files: processingFiles })
|
|
@@ -404,7 +1099,8 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
404
1099
|
config,
|
|
405
1100
|
})
|
|
406
1101
|
if (source) {
|
|
407
|
-
await storage
|
|
1102
|
+
await config.storage.setItem(file.path, source)
|
|
1103
|
+
|
|
408
1104
|
sources.set(file.path, source)
|
|
409
1105
|
}
|
|
410
1106
|
},
|
|
@@ -467,23 +1163,47 @@ async function build(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
467
1163
|
}
|
|
468
1164
|
}
|
|
469
1165
|
|
|
1166
|
+
/**
|
|
1167
|
+
* Returns a snapshot of the current runtime environment.
|
|
1168
|
+
*
|
|
1169
|
+
* Useful for attaching context to debug logs and error reports so that
|
|
1170
|
+
* issues can be reproduced without manual information gathering.
|
|
1171
|
+
*/
|
|
1172
|
+
export function getDiagnosticInfo() {
|
|
1173
|
+
return {
|
|
1174
|
+
nodeVersion,
|
|
1175
|
+
KubbVersion,
|
|
1176
|
+
platform: process.platform,
|
|
1177
|
+
arch: process.arch,
|
|
1178
|
+
cwd: process.cwd(),
|
|
1179
|
+
} as const
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* Type guard to check if a given config has an `input.path`.
|
|
1184
|
+
*/
|
|
1185
|
+
export function isInputPath(config: UserConfig | undefined): config is UserConfig<InputPath> & { input: InputPath }
|
|
1186
|
+
export function isInputPath(config: Config | undefined): config is Config<InputPath> & { input: InputPath }
|
|
1187
|
+
export function isInputPath(config: Config | UserConfig | undefined): config is (Config<InputPath> | UserConfig<InputPath>) & { input: InputPath } {
|
|
1188
|
+
return typeof config?.input === 'object' && config.input !== null && 'path' in config.input
|
|
1189
|
+
}
|
|
1190
|
+
|
|
470
1191
|
function inputToAdapterSource(config: Config): AdapterSource {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
paths: config.input.map((i) => (new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))),
|
|
475
|
-
}
|
|
1192
|
+
const input = config.input
|
|
1193
|
+
if (!input) {
|
|
1194
|
+
throw new Error('[kubb] input is required when using an adapter. Provide input.path or input.data in your config.')
|
|
476
1195
|
}
|
|
477
1196
|
|
|
478
|
-
if ('data' in
|
|
479
|
-
return { type: 'data', data:
|
|
1197
|
+
if ('data' in input) {
|
|
1198
|
+
return { type: 'data', data: input.data }
|
|
480
1199
|
}
|
|
481
1200
|
|
|
482
|
-
if (new URLPath(
|
|
483
|
-
return { type: 'path', path:
|
|
1201
|
+
if (new URLPath(input.path).isURL) {
|
|
1202
|
+
return { type: 'path', path: input.path }
|
|
484
1203
|
}
|
|
485
1204
|
|
|
486
|
-
const resolved = resolve(config.root,
|
|
1205
|
+
const resolved = resolve(config.root, input.path)
|
|
1206
|
+
|
|
487
1207
|
return { type: 'path', path: resolved }
|
|
488
1208
|
}
|
|
489
1209
|
|