@kubb/core 5.0.0-alpha.9 → 5.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +13 -40
  2. package/dist/PluginDriver-Cu1Kj9S-.cjs +1075 -0
  3. package/dist/PluginDriver-Cu1Kj9S-.cjs.map +1 -0
  4. package/dist/PluginDriver-D8Z0Htid.js +978 -0
  5. package/dist/PluginDriver-D8Z0Htid.js.map +1 -0
  6. package/dist/createKubb-ALdb8lmq.d.ts +2082 -0
  7. package/dist/index.cjs +747 -1667
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +175 -269
  10. package/dist/index.js +734 -1638
  11. package/dist/index.js.map +1 -1
  12. package/dist/mocks.cjs +145 -0
  13. package/dist/mocks.cjs.map +1 -0
  14. package/dist/mocks.d.ts +80 -0
  15. package/dist/mocks.js +140 -0
  16. package/dist/mocks.js.map +1 -0
  17. package/package.json +47 -60
  18. package/src/FileManager.ts +115 -0
  19. package/src/FileProcessor.ts +86 -0
  20. package/src/PluginDriver.ts +355 -561
  21. package/src/constants.ts +21 -48
  22. package/src/createAdapter.ts +88 -5
  23. package/src/createKubb.ts +1266 -0
  24. package/src/createRenderer.ts +57 -0
  25. package/src/createStorage.ts +13 -1
  26. package/src/defineGenerator.ts +160 -119
  27. package/src/defineLogger.ts +46 -5
  28. package/src/defineMiddleware.ts +62 -0
  29. package/src/defineParser.ts +44 -0
  30. package/src/definePlugin.ts +379 -0
  31. package/src/defineResolver.ts +548 -25
  32. package/src/devtools.ts +22 -15
  33. package/src/index.ts +13 -15
  34. package/src/mocks.ts +177 -0
  35. package/src/storages/fsStorage.ts +13 -8
  36. package/src/storages/memoryStorage.ts +4 -2
  37. package/src/types.ts +40 -547
  38. package/dist/PluginDriver-BkFepPdm.d.ts +0 -1054
  39. package/dist/chunk-ByKO4r7w.cjs +0 -38
  40. package/dist/hooks.cjs +0 -103
  41. package/dist/hooks.cjs.map +0 -1
  42. package/dist/hooks.d.ts +0 -77
  43. package/dist/hooks.js +0 -98
  44. package/dist/hooks.js.map +0 -1
  45. package/src/Kubb.ts +0 -224
  46. package/src/build.ts +0 -418
  47. package/src/config.ts +0 -56
  48. package/src/createPlugin.ts +0 -28
  49. package/src/hooks/index.ts +0 -4
  50. package/src/hooks/useKubb.ts +0 -143
  51. package/src/hooks/useMode.ts +0 -11
  52. package/src/hooks/usePlugin.ts +0 -11
  53. package/src/hooks/usePluginDriver.ts +0 -11
  54. package/src/utils/FunctionParams.ts +0 -155
  55. package/src/utils/TreeNode.ts +0 -215
  56. package/src/utils/diagnostics.ts +0 -15
  57. package/src/utils/executeStrategies.ts +0 -81
  58. package/src/utils/formatters.ts +0 -56
  59. package/src/utils/getBarrelFiles.ts +0 -141
  60. package/src/utils/getConfigs.ts +0 -12
  61. package/src/utils/linters.ts +0 -25
  62. package/src/utils/packageJSON.ts +0 -61
@@ -0,0 +1,1266 @@
1
+ import { resolve } from 'node:path'
2
+ import { version as nodeVersion } from 'node:process'
3
+ import type { PossiblePromise } from '@internals/utils'
4
+ import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, URLPath } from '@internals/utils'
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'
8
+ import { DEFAULT_BANNER, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
9
+ import type { Adapter, AdapterSource } from './createAdapter.ts'
10
+ import type { RendererFactory } from './createRenderer.ts'
11
+ import type { Storage } from './createStorage.ts'
12
+ import type { GeneratorContext, Generator } from './defineGenerator.ts'
13
+ import type { Middleware } from './defineMiddleware.ts'
14
+ import type { Parser } from './defineParser.ts'
15
+ import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext, NormalizedPlugin, Plugin } from './definePlugin.ts'
16
+ import { FileProcessor } from './FileProcessor.ts'
17
+ import { applyHookResult, PluginDriver } from './PluginDriver.ts'
18
+ import { fsStorage } from './storages/fsStorage.ts'
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[]>)
665
+
666
+ type SetupOptions = {
667
+ hooks?: AsyncEventEmitter<KubbHooks>
668
+ }
669
+
670
+ /**
671
+ * Full output produced by a successful or failed build.
672
+ */
673
+ export type BuildOutput = {
674
+ /**
675
+ * Plugins that threw during installation, paired with the caught error.
676
+ */
677
+ failedPlugins: Set<{ plugin: Plugin; error: Error }>
678
+ files: Array<FileNode>
679
+ driver: PluginDriver
680
+ /**
681
+ * Elapsed time in milliseconds for each plugin, keyed by plugin name.
682
+ */
683
+ pluginTimings: Map<string, number>
684
+ error?: Error
685
+ /**
686
+ * Raw generated source, keyed by absolute file path.
687
+ */
688
+ sources: Map<string, string>
689
+ }
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
+
728
+ type SetupResult = {
729
+ hooks: AsyncEventEmitter<KubbHooks>
730
+ driver: PluginDriver
731
+ sources: Map<string, string>
732
+ config: Config
733
+ }
734
+
735
+ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promise<SetupResult> {
736
+ const hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
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
+ })
761
+ const sources: Map<string, string> = new Map<string, string>()
762
+ const diagnosticInfo = getDiagnosticInfo()
763
+
764
+ await hooks.emit('kubb:debug', {
765
+ date: new Date(),
766
+ logs: [
767
+ 'Configuration:',
768
+ ` • Name: ${userConfig.name || 'unnamed'}`,
769
+ ` • Root: ${userConfig.root || process.cwd()}`,
770
+ ` • Output: ${userConfig.output?.path || 'not specified'}`,
771
+ ` • Plugins: ${userConfig.plugins?.length || 0}`,
772
+ 'Output Settings:',
773
+ ` • Storage: ${config.storage.name}`,
774
+ ` • Formatter: ${userConfig.output?.format || 'none'}`,
775
+ ` • Linter: ${userConfig.output?.lint || 'none'}`,
776
+ 'Environment:',
777
+ Object.entries(diagnosticInfo)
778
+ .map(([key, value]) => ` • ${key}: ${value}`)
779
+ .join('\n'),
780
+ ],
781
+ })
782
+
783
+ try {
784
+ if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
785
+ await exists(userConfig.input.path)
786
+
787
+ await hooks.emit('kubb:debug', {
788
+ date: new Date(),
789
+ logs: [`✓ Input file validated: ${userConfig.input.path}`],
790
+ })
791
+ }
792
+ } catch (caughtError) {
793
+ if (isInputPath(userConfig)) {
794
+ const error = caughtError as Error
795
+
796
+ throw new Error(
797
+ `Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`,
798
+ {
799
+ cause: error,
800
+ },
801
+ )
802
+ }
803
+ }
804
+
805
+ if (config.output.clean) {
806
+ await hooks.emit('kubb:debug', {
807
+ date: new Date(),
808
+ logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
809
+ })
810
+ await config.storage.clear(resolve(config.root, config.output.path))
811
+ }
812
+
813
+ // Register middleware hooks after all plugin hooks are registered.
814
+ // Because AsyncEventEmitter calls listeners in registration order,
815
+ // middleware hooks for any event fire after all plugin hooks for that event.
816
+ function registerMiddlewareHook<K extends keyof KubbHooks & string>(event: K, middlewareHooks: Middleware['hooks']) {
817
+ const handler = middlewareHooks[event]
818
+ if (handler) {
819
+ hooks.on(event, handler)
820
+ }
821
+ }
822
+
823
+ for (const middleware of config.middleware ?? []) {
824
+ for (const event of Object.keys(middleware.hooks) as Array<keyof KubbHooks & string>) {
825
+ registerMiddlewareHook(event, middleware.hooks)
826
+ }
827
+ }
828
+
829
+ if (config.adapter) {
830
+ const source = inputToAdapterSource(config)
831
+
832
+ await hooks.emit('kubb:debug', {
833
+ date: new Date(),
834
+ logs: [`Running adapter: ${config.adapter.name}`],
835
+ })
836
+
837
+ driver.adapter = config.adapter
838
+ driver.inputNode = await config.adapter.parse(source)
839
+
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
+ }
849
+
850
+ return {
851
+ config,
852
+ hooks,
853
+ driver,
854
+ sources,
855
+ }
856
+ }
857
+
858
+ /**
859
+ * Walks the AST and dispatches nodes to a plugin's direct AST hooks
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.
867
+ */
868
+ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorContext): Promise<void> {
869
+ const { adapter, inputNode, resolver, driver } = context
870
+ const { exclude, include, override } = plugin.options
871
+
872
+ if (!adapter || !inputNode) {
873
+ throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. adapterOas()) before this plugin in your Kubb config.`)
874
+ }
875
+
876
+ function resolveRenderer(gen: Generator): RendererFactory | undefined {
877
+ return gen.renderer === null ? undefined : (gen.renderer ?? plugin.renderer ?? context.config.renderer)
878
+ }
879
+
880
+ const generators = plugin.generators ?? []
881
+ const collectedOperations: Array<OperationNode> = []
882
+
883
+ const generatorContext = {
884
+ ...context,
885
+ resolver: driver.getResolver(plugin.name),
886
+ }
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
+
902
+ await walk(inputNode, {
903
+ depth: 'shallow',
904
+ async schema(node) {
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
+
912
+ const options = resolver.resolveOptions(transformedNode, {
913
+ options: plugin.options,
914
+ exclude,
915
+ include,
916
+ override,
917
+ })
918
+ if (options === null) return
919
+
920
+ const ctx = { ...generatorContext, options }
921
+
922
+ for (const gen of generators) {
923
+ if (!gen.schema) continue
924
+ const result = await gen.schema(transformedNode, ctx)
925
+ await applyHookResult(result, driver, resolveRenderer(gen))
926
+ }
927
+
928
+ await driver.hooks.emit('kubb:generate:schema', transformedNode, ctx)
929
+ },
930
+ async operation(node) {
931
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
932
+ const options = resolver.resolveOptions(transformedNode, {
933
+ options: plugin.options,
934
+ exclude,
935
+ include,
936
+ override,
937
+ })
938
+ if (options !== null) {
939
+ collectedOperations.push(transformedNode)
940
+
941
+ const ctx = { ...generatorContext, options }
942
+
943
+ for (const gen of generators) {
944
+ if (!gen.operation) continue
945
+ const result = await gen.operation(transformedNode, ctx)
946
+ await applyHookResult(result, driver, resolveRenderer(gen))
947
+ }
948
+
949
+ await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
950
+ }
951
+ },
952
+ })
953
+
954
+ if (collectedOperations.length > 0) {
955
+ const ctx = { ...generatorContext, options: plugin.options }
956
+
957
+ for (const gen of generators) {
958
+ if (!gen.operations) continue
959
+ const result = await gen.operations(collectedOperations, ctx)
960
+ await applyHookResult(result, driver, resolveRenderer(gen))
961
+ }
962
+
963
+ await driver.hooks.emit('kubb:generate:operations', collectedOperations, ctx)
964
+ }
965
+ }
966
+
967
+ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
968
+ const { driver, hooks, sources } = setupResult
969
+
970
+ const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
971
+ const pluginTimings = new Map<string, number>()
972
+ const config = driver.config
973
+
974
+ try {
975
+ await driver.emitSetupHooks()
976
+
977
+ if (driver.adapter && driver.inputNode) {
978
+ await hooks.emit('kubb:build:start', {
979
+ config,
980
+ adapter: driver.adapter,
981
+ inputNode: driver.inputNode,
982
+ getPlugin: driver.getPlugin.bind(driver),
983
+ get files() {
984
+ return driver.fileManager.files
985
+ },
986
+ upsertFile: (...files) => driver.fileManager.upsert(...files),
987
+ })
988
+ }
989
+
990
+ for (const plugin of driver.plugins.values()) {
991
+ const context = driver.getContext(plugin)
992
+ const hrStart = process.hrtime()
993
+
994
+ try {
995
+ const timestamp = new Date()
996
+
997
+ await hooks.emit('kubb:plugin:start', { plugin })
998
+ await hooks.emit('kubb:debug', {
999
+ date: timestamp,
1000
+ logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
1001
+ })
1002
+
1003
+ if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) {
1004
+ await runPluginAstHooks(plugin, context)
1005
+ }
1006
+
1007
+ const duration = getElapsedMs(hrStart)
1008
+ pluginTimings.set(plugin.name, duration)
1009
+
1010
+ await hooks.emit('kubb:plugin:end', {
1011
+ plugin,
1012
+ duration,
1013
+ success: true,
1014
+ config,
1015
+ get files() {
1016
+ return driver.fileManager.files
1017
+ },
1018
+ upsertFile: (...files) => driver.fileManager.upsert(...files),
1019
+ })
1020
+
1021
+ await hooks.emit('kubb:debug', {
1022
+ date: new Date(),
1023
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
1024
+ })
1025
+ } catch (caughtError) {
1026
+ const error = caughtError as Error
1027
+ const errorTimestamp = new Date()
1028
+ const duration = getElapsedMs(hrStart)
1029
+
1030
+ await hooks.emit('kubb:plugin:end', {
1031
+ plugin,
1032
+ duration,
1033
+ success: false,
1034
+ error,
1035
+ config,
1036
+ get files() {
1037
+ return driver.fileManager.files
1038
+ },
1039
+ upsertFile: (...files) => driver.fileManager.upsert(...files),
1040
+ })
1041
+
1042
+ await hooks.emit('kubb:debug', {
1043
+ date: errorTimestamp,
1044
+ logs: [
1045
+ '✗ Plugin start failed',
1046
+ ` • Plugin Name: ${plugin.name}`,
1047
+ ` • Error: ${error.constructor.name} - ${error.message}`,
1048
+ ' • Stack Trace:',
1049
+ error.stack || 'No stack trace available',
1050
+ ],
1051
+ })
1052
+
1053
+ failedPlugins.add({ plugin, error })
1054
+ }
1055
+ }
1056
+
1057
+ await hooks.emit('kubb:plugins:end', {
1058
+ config,
1059
+ get files() {
1060
+ return driver.fileManager.files
1061
+ },
1062
+ upsertFile: (...files) => driver.fileManager.upsert(...files),
1063
+ })
1064
+
1065
+ const files = driver.fileManager.files
1066
+
1067
+ const parsersMap = new Map<FileNode['extname'], Parser>()
1068
+ for (const parser of config.parsers) {
1069
+ if (parser.extNames) {
1070
+ for (const extname of parser.extNames) {
1071
+ parsersMap.set(extname, parser)
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ const fileProcessor = new FileProcessor()
1077
+
1078
+ await hooks.emit('kubb:debug', {
1079
+ date: new Date(),
1080
+ logs: [`Writing ${files.length} files...`],
1081
+ })
1082
+
1083
+ await fileProcessor.run(files, {
1084
+ parsers: parsersMap,
1085
+ mode: 'parallel',
1086
+ extension: config.output.extension,
1087
+ onStart: async (processingFiles) => {
1088
+ await hooks.emit('kubb:files:processing:start', { files: processingFiles })
1089
+ },
1090
+ onUpdate: async ({ file, source, processed, total, percentage }) => {
1091
+ await hooks.emit('kubb:file:processing:update', {
1092
+ file,
1093
+ source,
1094
+ processed,
1095
+ total,
1096
+ percentage,
1097
+ config,
1098
+ })
1099
+ if (source) {
1100
+ await config.storage.setItem(file.path, source)
1101
+
1102
+ sources.set(file.path, source)
1103
+ }
1104
+ },
1105
+ onEnd: async (processedFiles) => {
1106
+ await hooks.emit('kubb:files:processing:end', { files: processedFiles })
1107
+ await hooks.emit('kubb:debug', {
1108
+ date: new Date(),
1109
+ logs: [`✓ File write process completed for ${processedFiles.length} files`],
1110
+ })
1111
+ },
1112
+ })
1113
+
1114
+ await hooks.emit('kubb:build:end', {
1115
+ files,
1116
+ config,
1117
+ outputDir: resolve(config.root, config.output.path),
1118
+ })
1119
+
1120
+ return {
1121
+ failedPlugins,
1122
+ files,
1123
+ driver,
1124
+ pluginTimings,
1125
+ sources,
1126
+ }
1127
+ } catch (error) {
1128
+ return {
1129
+ failedPlugins,
1130
+ files: [],
1131
+ driver,
1132
+ pluginTimings,
1133
+ error: error as Error,
1134
+ sources,
1135
+ }
1136
+ } finally {
1137
+ driver.dispose()
1138
+ }
1139
+ }
1140
+
1141
+ async function build(setupResult: SetupResult): Promise<BuildOutput> {
1142
+ const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult)
1143
+
1144
+ if (error) {
1145
+ throw error
1146
+ }
1147
+
1148
+ if (failedPlugins.size > 0) {
1149
+ const errors = [...failedPlugins].map(({ error }) => error)
1150
+
1151
+ throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors })
1152
+ }
1153
+
1154
+ return {
1155
+ failedPlugins,
1156
+ files,
1157
+ driver,
1158
+ pluginTimings,
1159
+ error: undefined,
1160
+ sources,
1161
+ }
1162
+ }
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
+
1189
+ function inputToAdapterSource(config: Config): AdapterSource {
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.')
1193
+ }
1194
+
1195
+ if ('data' in input) {
1196
+ return { type: 'data', data: input.data }
1197
+ }
1198
+
1199
+ if (new URLPath(input.path).isURL) {
1200
+ return { type: 'path', path: input.path }
1201
+ }
1202
+
1203
+ const resolved = resolve(config.root, input.path)
1204
+
1205
+ return { type: 'path', path: resolved }
1206
+ }
1207
+
1208
+ type CreateKubbOptions = {
1209
+ hooks?: AsyncEventEmitter<KubbHooks>
1210
+ }
1211
+
1212
+ /**
1213
+ * Creates a Kubb instance bound to a single config entry.
1214
+ *
1215
+ * Accepts a user-facing config shape and resolves it to a full {@link Config} during
1216
+ * `setup()`. The instance then holds shared state (`hooks`, `sources`, `driver`, `config`)
1217
+ * across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
1218
+ * calling `setup()` or `build()`.
1219
+ *
1220
+ * @example
1221
+ * ```ts
1222
+ * const kubb = createKubb(userConfig)
1223
+ *
1224
+ * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
1225
+ * console.log(`${plugin.name} completed in ${duration}ms`)
1226
+ * })
1227
+ *
1228
+ * const { files, failedPlugins } = await kubb.safeBuild()
1229
+ * ```
1230
+ */
1231
+ export function createKubb(userConfig: UserConfig, options: CreateKubbOptions = {}): Kubb {
1232
+ const hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
1233
+ let setupResult: SetupResult | undefined
1234
+
1235
+ const instance: Kubb = {
1236
+ get hooks() {
1237
+ return hooks
1238
+ },
1239
+ get sources() {
1240
+ return setupResult?.sources ?? new Map()
1241
+ },
1242
+ get driver() {
1243
+ return setupResult?.driver
1244
+ },
1245
+ get config() {
1246
+ return setupResult?.config
1247
+ },
1248
+ async setup() {
1249
+ setupResult = await setup(userConfig, { hooks })
1250
+ },
1251
+ async build() {
1252
+ if (!setupResult) {
1253
+ await instance.setup()
1254
+ }
1255
+ return build(setupResult!)
1256
+ },
1257
+ async safeBuild() {
1258
+ if (!setupResult) {
1259
+ await instance.setup()
1260
+ }
1261
+ return safeBuild(setupResult!)
1262
+ },
1263
+ }
1264
+
1265
+ return instance
1266
+ }