@kubb/core 5.0.0-beta.1 → 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/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 -84
- 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 -31
- 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,14 @@ 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
|
-
|
|
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
|
-
|
|
131
805
|
if (config.output.clean) {
|
|
132
806
|
await hooks.emit('kubb:debug', {
|
|
133
807
|
date: new Date(),
|
|
134
808
|
logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
|
|
135
809
|
})
|
|
136
|
-
await storage
|
|
810
|
+
await config.storage.clear(resolve(config.root, config.output.path))
|
|
137
811
|
}
|
|
138
812
|
|
|
139
|
-
const driver = new PluginDriver(config, {
|
|
140
|
-
hooks,
|
|
141
|
-
})
|
|
142
|
-
|
|
143
813
|
// Register middleware hooks after all plugin hooks are registered.
|
|
144
814
|
// Because AsyncEventEmitter calls listeners in registration order,
|
|
145
815
|
// middleware hooks for any event fire after all plugin hooks for that event.
|
|
@@ -156,48 +826,51 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
156
826
|
}
|
|
157
827
|
}
|
|
158
828
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
throw new Error('No adapter configured. Please provide an adapter in your kubb.config.ts.')
|
|
162
|
-
}
|
|
163
|
-
const source = inputToAdapterSource(config)
|
|
829
|
+
if (config.adapter) {
|
|
830
|
+
const source = inputToAdapterSource(config)
|
|
164
831
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
832
|
+
await hooks.emit('kubb:debug', {
|
|
833
|
+
date: new Date(),
|
|
834
|
+
logs: [`Running adapter: ${config.adapter.name}`],
|
|
835
|
+
})
|
|
169
836
|
|
|
170
|
-
|
|
171
|
-
|
|
837
|
+
driver.adapter = config.adapter
|
|
838
|
+
driver.inputNode = await config.adapter.parse(source)
|
|
172
839
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
840
|
+
await hooks.emit('kubb:debug', {
|
|
841
|
+
date: new Date(),
|
|
842
|
+
logs: [
|
|
843
|
+
`✓ Adapter '${config.adapter.name}' resolved InputNode`,
|
|
844
|
+
` • Schemas: ${driver.inputNode.schemas.length}`,
|
|
845
|
+
` • Operations: ${driver.inputNode.operations.length}`,
|
|
846
|
+
],
|
|
847
|
+
})
|
|
848
|
+
}
|
|
181
849
|
|
|
182
850
|
return {
|
|
183
851
|
config,
|
|
184
852
|
hooks,
|
|
185
853
|
driver,
|
|
186
854
|
sources,
|
|
187
|
-
storage,
|
|
188
855
|
}
|
|
189
856
|
}
|
|
190
857
|
|
|
191
858
|
/**
|
|
192
859
|
* Walks the AST and dispatches nodes to a plugin's direct AST hooks
|
|
193
860
|
* (`schema`, `operation`, `operations`).
|
|
861
|
+
*
|
|
862
|
+
* When `include` contains only operation-scoped filters (`tag`, `operationId`, `path`,
|
|
863
|
+
* `method`, `contentType`) and no `schemaName` filter, the function pre-computes the set
|
|
864
|
+
* of top-level schema names transitively reachable from the included operations and skips
|
|
865
|
+
* schemas that fall outside that set. This ensures that component schemas referenced
|
|
866
|
+
* exclusively by excluded operations are not generated.
|
|
194
867
|
*/
|
|
195
868
|
async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorContext): Promise<void> {
|
|
196
869
|
const { adapter, inputNode, resolver, driver } = context
|
|
197
870
|
const { exclude, include, override } = plugin.options
|
|
198
871
|
|
|
199
872
|
if (!adapter || !inputNode) {
|
|
200
|
-
throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g.
|
|
873
|
+
throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. adapterOas()) before this plugin in your Kubb config.`)
|
|
201
874
|
}
|
|
202
875
|
|
|
203
876
|
function resolveRenderer(gen: Generator): RendererFactory | undefined {
|
|
@@ -212,10 +885,30 @@ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorCon
|
|
|
212
885
|
resolver: driver.getResolver(plugin.name),
|
|
213
886
|
}
|
|
214
887
|
|
|
888
|
+
// When `include` has operation-based filters (tag, operationId, path, method, contentType)
|
|
889
|
+
// but no schema-level filters (schemaName), pre-compute the set of top-level schema names
|
|
890
|
+
// that are transitively referenced by the included operations. Schemas outside that set are
|
|
891
|
+
// skipped so that types belonging exclusively to excluded operations are not generated.
|
|
892
|
+
const operationFilterTypes = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])
|
|
893
|
+
const hasOperationBasedIncludes = include?.some(({ type }) => operationFilterTypes.has(type)) ?? false
|
|
894
|
+
const hasSchemaNameIncludes = include?.some(({ type }) => type === 'schemaName') ?? false
|
|
895
|
+
|
|
896
|
+
let allowedSchemaNames: Set<string> | undefined
|
|
897
|
+
if (hasOperationBasedIncludes && !hasSchemaNameIncludes) {
|
|
898
|
+
const includedOps = inputNode.operations.filter((op) => resolver.resolveOptions(op, { options: plugin.options, exclude, include, override }) !== null)
|
|
899
|
+
allowedSchemaNames = collectUsedSchemaNames(includedOps, inputNode.schemas)
|
|
900
|
+
}
|
|
901
|
+
|
|
215
902
|
await walk(inputNode, {
|
|
216
903
|
depth: 'shallow',
|
|
217
904
|
async schema(node) {
|
|
218
905
|
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
906
|
+
|
|
907
|
+
// Skip named top-level schemas that are not reachable from any included operation.
|
|
908
|
+
if (allowedSchemaNames !== undefined && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) {
|
|
909
|
+
return
|
|
910
|
+
}
|
|
911
|
+
|
|
219
912
|
const options = resolver.resolveOptions(transformedNode, {
|
|
220
913
|
options: plugin.options,
|
|
221
914
|
exclude,
|
|
@@ -272,7 +965,7 @@ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorCon
|
|
|
272
965
|
}
|
|
273
966
|
|
|
274
967
|
async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
275
|
-
const { driver, hooks, sources
|
|
968
|
+
const { driver, hooks, sources } = setupResult
|
|
276
969
|
|
|
277
970
|
const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
|
|
278
971
|
const pluginTimings = new Map<string, number>()
|
|
@@ -302,7 +995,6 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
302
995
|
const timestamp = new Date()
|
|
303
996
|
|
|
304
997
|
await hooks.emit('kubb:plugin:start', { plugin })
|
|
305
|
-
|
|
306
998
|
await hooks.emit('kubb:debug', {
|
|
307
999
|
date: timestamp,
|
|
308
1000
|
logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
|
|
@@ -390,6 +1082,7 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
390
1082
|
|
|
391
1083
|
await fileProcessor.run(files, {
|
|
392
1084
|
parsers: parsersMap,
|
|
1085
|
+
mode: 'parallel',
|
|
393
1086
|
extension: config.output.extension,
|
|
394
1087
|
onStart: async (processingFiles) => {
|
|
395
1088
|
await hooks.emit('kubb:files:processing:start', { files: processingFiles })
|
|
@@ -404,7 +1097,8 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
404
1097
|
config,
|
|
405
1098
|
})
|
|
406
1099
|
if (source) {
|
|
407
|
-
await storage
|
|
1100
|
+
await config.storage.setItem(file.path, source)
|
|
1101
|
+
|
|
408
1102
|
sources.set(file.path, source)
|
|
409
1103
|
}
|
|
410
1104
|
},
|
|
@@ -467,23 +1161,47 @@ async function build(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
467
1161
|
}
|
|
468
1162
|
}
|
|
469
1163
|
|
|
1164
|
+
/**
|
|
1165
|
+
* Returns a snapshot of the current runtime environment.
|
|
1166
|
+
*
|
|
1167
|
+
* Useful for attaching context to debug logs and error reports so that
|
|
1168
|
+
* issues can be reproduced without manual information gathering.
|
|
1169
|
+
*/
|
|
1170
|
+
export function getDiagnosticInfo() {
|
|
1171
|
+
return {
|
|
1172
|
+
nodeVersion,
|
|
1173
|
+
KubbVersion,
|
|
1174
|
+
platform: process.platform,
|
|
1175
|
+
arch: process.arch,
|
|
1176
|
+
cwd: process.cwd(),
|
|
1177
|
+
} as const
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* Type guard to check if a given config has an `input.path`.
|
|
1182
|
+
*/
|
|
1183
|
+
export function isInputPath(config: UserConfig | undefined): config is UserConfig<InputPath> & { input: InputPath }
|
|
1184
|
+
export function isInputPath(config: Config | undefined): config is Config<InputPath> & { input: InputPath }
|
|
1185
|
+
export function isInputPath(config: Config | UserConfig | undefined): config is (Config<InputPath> | UserConfig<InputPath>) & { input: InputPath } {
|
|
1186
|
+
return typeof config?.input === 'object' && config.input !== null && 'path' in config.input
|
|
1187
|
+
}
|
|
1188
|
+
|
|
470
1189
|
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
|
-
}
|
|
1190
|
+
const input = config.input
|
|
1191
|
+
if (!input) {
|
|
1192
|
+
throw new Error('[kubb] input is required when using an adapter. Provide input.path or input.data in your config.')
|
|
476
1193
|
}
|
|
477
1194
|
|
|
478
|
-
if ('data' in
|
|
479
|
-
return { type: 'data', data:
|
|
1195
|
+
if ('data' in input) {
|
|
1196
|
+
return { type: 'data', data: input.data }
|
|
480
1197
|
}
|
|
481
1198
|
|
|
482
|
-
if (new URLPath(
|
|
483
|
-
return { type: 'path', path:
|
|
1199
|
+
if (new URLPath(input.path).isURL) {
|
|
1200
|
+
return { type: 'path', path: input.path }
|
|
484
1201
|
}
|
|
485
1202
|
|
|
486
|
-
const resolved = resolve(config.root,
|
|
1203
|
+
const resolved = resolve(config.root, input.path)
|
|
1204
|
+
|
|
487
1205
|
return { type: 'path', path: resolved }
|
|
488
1206
|
}
|
|
489
1207
|
|