@kubb/core 5.0.0-beta.36 → 5.0.0-beta.37

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/src/createKubb.ts CHANGED
@@ -1,17 +1,16 @@
1
1
  import { resolve } from 'node:path'
2
- import { version as nodeVersion } from 'node:process'
3
2
  import type { PossiblePromise } from '@internals/utils'
4
- import { AsyncEventEmitter, BuildError, exists, URLPath } from '@internals/utils'
3
+ import { AsyncEventEmitter, BuildError } from '@internals/utils'
5
4
  import type { FileNode, InputMeta, OperationNode, SchemaNode } from '@kubb/ast'
6
- import { version as KubbVersion } from '../package.json'
7
- import { DEFAULT_BANNER, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
5
+ import { DEFAULT_STUDIO_URL, HOOK_LISTENERS_PER_PLUGIN } from './constants.ts'
8
6
  import type { Adapter } from './createAdapter.ts'
9
- import type { RendererFactory } from './createRenderer.ts'
7
+ import { type Diagnostic, DiagnosticError, Diagnostics, isProblemDiagnostic, type ProblemDiagnostic, type UpdateDiagnostic } from './diagnostics.ts'
10
8
  import { createStorage, type Storage } from './createStorage.ts'
11
9
  import type { GeneratorContext } from './defineGenerator.ts'
12
10
  import type { Middleware } from './defineMiddleware.ts'
13
11
  import type { Parser } from './defineParser.ts'
14
12
  import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext, Plugin } from './definePlugin.ts'
13
+ import type { ReporterName } from './createReporter.ts'
15
14
 
16
15
  import { KubbDriver } from './KubbDriver.ts'
17
16
  import { fsStorage } from './storages/fsStorage.ts'
@@ -26,10 +25,8 @@ import { fsStorage } from './storages/fsStorage.ts'
26
25
  type ExtractRegistryKey<T, K extends PropertyKey> = K extends keyof T ? T[K] : {}
27
26
 
28
27
  /**
29
- * Reference to an input file to generate code from.
30
- *
31
- * Specify an absolute path or a path relative to the config file location.
32
- * The adapter will parse this file (e.g., OpenAPI YAML or JSON) into the universal AST.
28
+ * Path to an input file to generate from, absolute or relative to the config file. The adapter
29
+ * parses it (e.g. an OpenAPI YAML or JSON spec) into the universal AST.
33
30
  */
34
31
  export type InputPath = {
35
32
  /**
@@ -45,10 +42,8 @@ export type InputPath = {
45
42
  }
46
43
 
47
44
  /**
48
- * Inline input data to generate code from.
49
- *
50
- * Useful when you want to pass the specification directly instead of from a file.
51
- * Can be a string (YAML/JSON) or a parsed object.
45
+ * Inline spec to generate from, passed directly instead of read from a file. A string
46
+ * (YAML/JSON) or a parsed object.
52
47
  */
53
48
  export type InputData = {
54
49
  /**
@@ -66,15 +61,9 @@ export type InputData = {
66
61
  type Input = InputPath | InputData
67
62
 
68
63
  /**
69
- * Build configuration for Kubb code generation.
70
- *
71
- * The Config is the main entry point for customizing how Kubb generates code. It specifies:
72
- * - What to generate from (adapter + input)
73
- * - Where to output generated code (output)
74
- * - How to generate (plugins + middleware)
75
- * - Runtime details (parsers, storage, renderer)
76
- *
77
- * See `UserConfig` for a relaxed version with sensible defaults.
64
+ * Resolved build configuration for a Kubb run: what to generate from (adapter, input), where to
65
+ * write it (output), how (plugins, middleware), and the runtime pieces (parsers, storage). See
66
+ * `UserConfig` for the relaxed form with defaults applied.
78
67
  *
79
68
  * @private
80
69
  */
@@ -91,16 +80,16 @@ export type Config<TInput = Input> = {
91
80
  name?: string
92
81
  /**
93
82
  * Project root directory, absolute or relative to the config file. Already
94
- * resolved on the `Config` instance see `UserConfig` for the optional
95
- * form that defaults to `process.cwd()`.
83
+ * resolved on the `Config` instance (see `UserConfig` for the optional
84
+ * form that defaults to `process.cwd()`).
96
85
  */
97
86
  root: string
98
87
  /**
99
88
  * Parsers that convert generated files into strings. Each parser handles a
100
- * set of file extensions; a fallback parser handles anything else.
89
+ * set of file extensions, and a fallback parser handles anything else.
101
90
  *
102
- * Already resolved on the `Config` instance see `UserConfig` for the
103
- * optional form that defaults to `[parserTs, parserTsx, parserMd]`.
91
+ * Already resolved on the `Config` instance (see `UserConfig` for the
92
+ * optional form that defaults to `[parserTs, parserTsx, parserMd]`).
104
93
  *
105
94
  * @example
106
95
  * ```ts
@@ -134,15 +123,13 @@ export type Config<TInput = Input> = {
134
123
  /**
135
124
  * Source file or data to generate code from.
136
125
  * Use `input.path` for a file path or `input.data` for inline data.
137
- * Required when an adapter is configured; omit when running in plugin-only mode.
126
+ * Required when an adapter is configured. Omit it when running in plugin-only mode.
138
127
  */
139
128
  input?: TInput
140
129
  output: {
141
130
  /**
142
- * Output directory for generated files, absolute or relative to `root`.
143
- *
144
- * All generated files will be written under this directory. Subdirectories can be created
145
- * by plugins based on grouping strategy (by tag, path, etc.).
131
+ * Output directory for generated files, absolute or relative to `root`. Plugins can nest
132
+ * subdirectories under it by grouping strategy (tag, path).
146
133
  *
147
134
  * @example
148
135
  * ```ts
@@ -153,10 +140,8 @@ export type Config<TInput = Input> = {
153
140
  */
154
141
  path: string
155
142
  /**
156
- * Remove all files from the output directory before starting the build.
157
- *
158
- * Useful to ensure old generated files aren't mixed with new ones.
159
- * Set to `true` for fresh builds, `false` to preserve manual edits in output dir.
143
+ * Remove every file in the output directory before the build, so stale output isn't mixed
144
+ * with new files. Leave `false` to preserve manual edits in the output directory.
160
145
  *
161
146
  * @default false
162
147
  * @example
@@ -166,10 +151,8 @@ export type Config<TInput = Input> = {
166
151
  */
167
152
  clean?: boolean
168
153
  /**
169
- * Auto-format generated files after code generation completes.
170
- *
171
- * Applies a code formatter to all generated files. Use `'auto'` to detect which formatter
172
- * is available on your system. Pass `false` to skip formatting (useful for CI or specific workflows).
154
+ * Format the generated files after generation. `'auto'` runs the first formatter it finds
155
+ * (oxfmt, biome, or prettier), a named tool forces that one, and `false` skips formatting.
173
156
  *
174
157
  * @default false
175
158
  * @example
@@ -181,10 +164,8 @@ export type Config<TInput = Input> = {
181
164
  */
182
165
  format?: 'auto' | 'prettier' | 'biome' | 'oxfmt' | false
183
166
  /**
184
- * Auto-lint generated files after code generation completes.
185
- *
186
- * Analyzes all generated files for style/correctness issues. Use `'auto'` to detect which linter
187
- * is available on your system. Pass `false` to skip linting.
167
+ * Lint the generated files after generation. `'auto'` runs the first linter it finds
168
+ * (oxlint, biome, or eslint), a named tool forces that one, and `false` skips linting.
188
169
  *
189
170
  * @default false
190
171
  * @example
@@ -196,10 +177,8 @@ export type Config<TInput = Input> = {
196
177
  */
197
178
  lint?: 'auto' | 'eslint' | 'biome' | 'oxlint' | false
198
179
  /**
199
- * Map file extensions to different output extensions.
200
- *
201
- * Useful when you want generated `.ts` imports to reference `.js` files or vice versa (e.g., for ESM dual packages).
202
- * Keys are the original extension, values are the output extension. Use empty string `''` to omit extension.
180
+ * Rewrite import extensions in generated files, e.g. emit `.js` imports from `.ts` sources for
181
+ * ESM dual packages. Keys are the source extension, values the output, and `''` drops it.
203
182
  *
204
183
  * @default { '.ts': '.ts' }
205
184
  * @example
@@ -210,10 +189,8 @@ export type Config<TInput = Input> = {
210
189
  */
211
190
  extension?: Record<FileNode['extname'], FileNode['extname'] | ''>
212
191
  /**
213
- * Banner text prepended to every generated file.
214
- *
215
- * Useful for auto-generation notices or license headers. Choose a preset or write custom text.
216
- * Use `'simple'` for a basic Kubb banner, `'full'` for detailed metadata, or `false` to omit.
192
+ * Banner prepended to every generated file. `'simple'` is the basic Kubb notice, `'full'` adds
193
+ * source, title, description, and API version, and `false` omits it.
217
194
  *
218
195
  * @default 'simple'
219
196
  * @example
@@ -225,10 +202,8 @@ export type Config<TInput = Input> = {
225
202
  */
226
203
  defaultBanner?: 'simple' | 'full' | false
227
204
  /**
228
- * When `true`, overwrites existing files. When `false`, skips generated files that already exist.
229
- *
230
- * Individual plugins can override this setting. This is useful for preventing accidental data loss
231
- * when re-generating while you have local edits in the output folder.
205
+ * Overwrite existing files when `true`, skip files that already exist when `false`. Individual
206
+ * plugins can override it. Keep `false` to avoid clobbering local edits in the output folder.
232
207
  *
233
208
  * @default false
234
209
  * @example
@@ -240,10 +215,8 @@ export type Config<TInput = Input> = {
240
215
  override?: boolean
241
216
  } & ExtractRegistryKey<Kubb.ConfigOptionsRegistry, 'output'>
242
217
  /**
243
- * Storage backend that controls where and how generated files are persisted.
244
- *
245
- * Defaults to `fsStorage()` which writes to the file system. Pass `memoryStorage()` to keep files in RAM,
246
- * or implement a custom `Storage` interface to write to cloud storage, databases, or other backends.
218
+ * Where generated files are persisted. Defaults to `fsStorage()` (disk). Pass `memoryStorage()`
219
+ * to keep files in RAM, or implement `Storage` for a custom backend such as cloud or a database.
247
220
  *
248
221
  * @default fsStorage()
249
222
  * @example
@@ -261,13 +234,9 @@ export type Config<TInput = Input> = {
261
234
  */
262
235
  storage: Storage
263
236
  /**
264
- * Plugins that execute during the build to generate code and transform the AST.
265
- *
266
- * Each plugin processes the AST produced by the adapter and can emit files for different
267
- * programming languages or formats (TypeScript, Zod schemas, Faker data, etc.).
268
- * Dependencies are enforced — an error is thrown if a plugin requires another plugin that isn't registered.
269
- *
270
- * Plugins can declare their own options via `PluginFactoryOptions`. See plugin documentation for details.
237
+ * Plugins that run during the build to generate code and transform the AST. Each one processes
238
+ * the adapter's AST and can emit files for a different target (TypeScript, Zod, Faker). A plugin
239
+ * that depends on another throws when that plugin isn't registered.
271
240
  *
272
241
  * @example
273
242
  * ```ts
@@ -300,22 +269,6 @@ export type Config<TInput = Input> = {
300
269
  * @see {@link defineMiddleware} to create custom middleware.
301
270
  */
302
271
  middleware?: Array<Middleware>
303
- /**
304
- * Renderer that converts generated AST nodes to code strings.
305
- *
306
- * By default, Kubb uses the JSX renderer (`rendererJsx`). Pass a custom renderer to support
307
- * different output formats (template engines, code generation DSLs, etc.).
308
- *
309
- * @default rendererJsx() // from @kubb/renderer-jsx
310
- * @example
311
- * ```ts
312
- * import { rendererJsx } from '@kubb/renderer-jsx'
313
- * renderer: rendererJsx()
314
- * ```
315
- *
316
- * @see {@link Renderer} to implement a custom renderer.
317
- */
318
- renderer?: RendererFactory
319
272
  /**
320
273
  * Kubb Studio cloud integration settings.
321
274
  *
@@ -369,6 +322,22 @@ export type Config<TInput = Input> = {
369
322
  */
370
323
  done?: string | Array<string>
371
324
  }
325
+ /**
326
+ * Reporters that render the run's output, like Vitest. List one or more by name;
327
+ * the CLI `--reporter` flag overrides this when set.
328
+ *
329
+ * - `cli` writes the end-of-run summary to the terminal.
330
+ * - `json` writes a machine-readable report to stdout, for CI.
331
+ * - `file` writes a debug log to `.kubb/<name>-<timestamp>.log`.
332
+ *
333
+ * @default ['cli']
334
+ *
335
+ * @example
336
+ * ```ts
337
+ * reporters: ['cli', 'file']
338
+ * ```
339
+ */
340
+ reporters?: Array<ReporterName>
372
341
  }
373
342
 
374
343
  /**
@@ -376,7 +345,7 @@ export type Config<TInput = Input> = {
376
345
  *
377
346
  * `UserConfig` is what you pass to `defineConfig()`. It has optional `root`, `plugins`, `parsers`, and `adapter`
378
347
  * fields (which fall back to sensible defaults). All other Config options are available, including `output`, `input`,
379
- * `storage`, `middleware`, `renderer`, `devtools`, and `hooks`.
348
+ * `storage`, `middleware`, `devtools`, and `hooks`.
380
349
  *
381
350
  * @example
382
351
  * ```ts
@@ -502,7 +471,6 @@ export interface KubbHooks {
502
471
  'kubb:config:end': [ctx: KubbConfigEndContext]
503
472
  'kubb:generation:start': [ctx: KubbGenerationStartContext]
504
473
  'kubb:generation:end': [ctx: KubbGenerationEndContext]
505
- 'kubb:generation:summary': [ctx: KubbGenerationSummaryContext]
506
474
  'kubb:format:start': []
507
475
  'kubb:format:end': []
508
476
  'kubb:lint:start': []
@@ -511,12 +479,11 @@ export interface KubbHooks {
511
479
  'kubb:hooks:end': []
512
480
  'kubb:hook:start': [ctx: KubbHookStartContext]
513
481
  'kubb:hook:end': [ctx: KubbHookEndContext]
514
- 'kubb:version:new': [ctx: KubbVersionNewContext]
515
482
  'kubb:info': [ctx: KubbInfoContext]
516
483
  'kubb:error': [ctx: KubbErrorContext]
517
484
  'kubb:success': [ctx: KubbSuccessContext]
518
485
  'kubb:warn': [ctx: KubbWarnContext]
519
- 'kubb:debug': [ctx: KubbDebugContext]
486
+ 'kubb:diagnostic': [ctx: KubbDiagnosticContext]
520
487
  'kubb:files:processing:start': [ctx: KubbFilesProcessingStartContext]
521
488
  'kubb:files:processing:update': [ctx: KubbFilesProcessingUpdateContext]
522
489
  'kubb:files:processing:end': [ctx: KubbFilesProcessingEndContext]
@@ -618,7 +585,7 @@ export type KubbGenerationEndContext = {
618
585
  config: Config
619
586
  /**
620
587
  * Read-only view of the files written during this build.
621
- * Reads go directly to `config.storage` nothing extra is held in memory.
588
+ * Reads go directly to `config.storage`, nothing extra is held in memory.
622
589
  *
623
590
  * @example Read a generated file
624
591
  * `const code = await storage.getItem('/src/gen/pet.ts')`
@@ -631,44 +598,24 @@ export type KubbGenerationEndContext = {
631
598
  * ```
632
599
  */
633
600
  storage: Storage
634
- }
635
-
636
- export type KubbGenerationSummaryContext = {
637
601
  /**
638
- * Resolved configuration for this generation run.
602
+ * Diagnostics collected during the build: error/warning/info problems plus a
603
+ * `timing` diagnostic per plugin. The end-of-run summary derives its failure counts
604
+ * and per-plugin timings from these. Set by the CLI runner, omitted by other callers.
639
605
  */
640
- config: Config
641
- /**
642
- * Plugins that threw during generation, paired with their errors.
643
- */
644
- failedPlugins: Set<{ plugin: Plugin; error: Error }>
606
+ diagnostics?: Array<Diagnostic>
645
607
  /**
646
608
  * `'success'` when all plugins completed without errors, `'failed'` otherwise.
647
609
  */
648
- status: 'success' | 'failed'
610
+ status?: 'success' | 'failed'
649
611
  /**
650
- * High-resolution start time from `process.hrtime()`.
612
+ * High-resolution start time from `process.hrtime()`, used to compute the elapsed time.
651
613
  */
652
- hrStart: [number, number]
614
+ hrStart?: [number, number]
653
615
  /**
654
616
  * Total number of files created during this run.
655
617
  */
656
- filesCreated: number
657
- /**
658
- * Elapsed milliseconds per plugin, keyed by plugin name.
659
- */
660
- pluginTimings?: Map<Plugin['name'], number>
661
- }
662
-
663
- export type KubbVersionNewContext = {
664
- /**
665
- * The installed Kubb version.
666
- */
667
- currentVersion: string
668
- /**
669
- * The newest available version on npm.
670
- */
671
- latestVersion: string
618
+ filesCreated?: number
672
619
  }
673
620
 
674
621
  export type KubbInfoContext = {
@@ -715,19 +662,11 @@ export type KubbWarnContext = {
715
662
  info?: string
716
663
  }
717
664
 
718
- export type KubbDebugContext = {
719
- /**
720
- * Timestamp when the debug entry was created.
721
- */
722
- date: Date
723
- /**
724
- * One or more log lines to emit.
725
- */
726
- logs: Array<string>
665
+ export type KubbDiagnosticContext = {
727
666
  /**
728
- * Optional source file name associated with this entry.
667
+ * The structured diagnostic to render: a build problem or a version-update notice.
729
668
  */
730
- fileName?: string
669
+ diagnostic: ProblemDiagnostic | UpdateDiagnostic
731
670
  }
732
671
 
733
672
  export type KubbFilesProcessingStartContext = {
@@ -747,7 +686,7 @@ export type KubbFileProcessingUpdate = {
747
686
  */
748
687
  total: number
749
688
  /**
750
- * Completion percentage (`0`–`100`).
689
+ * Completion percentage, `0` to `100`.
751
690
  */
752
691
  percentage: number
753
692
  /**
@@ -838,7 +777,11 @@ export type CLIOptions = {
838
777
  *
839
778
  * @default 'info'
840
779
  */
841
- logLevel?: 'silent' | 'info' | 'verbose' | 'debug'
780
+ logLevel?: 'silent' | 'info' | 'verbose'
781
+ /**
782
+ * Reporters selected on the CLI via `--reporter`, overriding `config.reporters`.
783
+ */
784
+ reporters?: Array<ReporterName>
842
785
  }
843
786
 
844
787
  /**
@@ -854,9 +797,12 @@ export type PossibleConfig<TCliOptions = undefined> =
854
797
  */
855
798
  export type BuildOutput = {
856
799
  /**
857
- * Plugins that threw during generation, paired with their errors.
800
+ * Structured diagnostics collected during the build: error/warning/info problems
801
+ * (each with a code, severity, and where known a JSON-pointer location) plus a
802
+ * `timing` diagnostic per plugin. Includes a top-level diagnostic when the build
803
+ * threw before completing. Use {@link Diagnostics.hasError} to test for failure.
858
804
  */
859
- failedPlugins: Set<{ plugin: Plugin; error: Error }>
805
+ diagnostics: Array<Diagnostic>
860
806
  /**
861
807
  * All files generated during this build.
862
808
  */
@@ -865,17 +811,9 @@ export type BuildOutput = {
865
811
  * The plugin driver that orchestrated this build.
866
812
  */
867
813
  driver: KubbDriver
868
- /**
869
- * Elapsed milliseconds per plugin, keyed by plugin name.
870
- */
871
- pluginTimings: Map<string, number>
872
- /**
873
- * Top-level error when the build threw before completing, otherwise `undefined`.
874
- */
875
- error?: Error
876
814
  /**
877
815
  * Read-only view of every file written during this build.
878
- * Reads go straight to `config.storage` nothing extra is held in memory.
816
+ * Reads go straight to `config.storage`, nothing extra is held in memory.
879
817
  *
880
818
  * @example Read a generated file
881
819
  * `const code = await buildOutput.storage.getItem('/src/gen/pet.ts')`
@@ -889,7 +827,7 @@ export type BuildOutput = {
889
827
  /**
890
828
  * Builds a `Storage` view scoped to the file paths produced by the current build.
891
829
  * Reads delegate to the underlying `storage` so source bytes stay where they were
892
- * written; writes register the key so subsequent reads and `getKeys` are scoped
830
+ * written. Writes register the key so subsequent reads and `getKeys` are scoped
893
831
  * to this build's output.
894
832
  */
895
833
  function createSourcesView(storage: Storage): Storage {
@@ -934,8 +872,8 @@ function resolveConfig(userConfig: UserConfig): Config {
934
872
  output: {
935
873
  format: false,
936
874
  lint: false,
937
- extension: DEFAULT_EXTENSION,
938
- defaultBanner: DEFAULT_BANNER,
875
+ extension: { '.ts': '.ts' },
876
+ defaultBanner: 'simple',
939
877
  ...userConfig.output,
940
878
  },
941
879
  storage: userConfig.storage ?? fsStorage(),
@@ -949,22 +887,6 @@ function resolveConfig(userConfig: UserConfig): Config {
949
887
  }
950
888
  }
951
889
 
952
- /**
953
- * Returns a snapshot of the current runtime environment.
954
- *
955
- * Useful for attaching context to debug logs and error reports so that
956
- * issues can be reproduced without manual information gathering.
957
- */
958
- export function getDiagnosticInfo() {
959
- return {
960
- nodeVersion,
961
- KubbVersion,
962
- platform: process.platform,
963
- arch: process.arch,
964
- cwd: process.cwd(),
965
- } as const
966
- }
967
-
968
890
  /**
969
891
  * Type guard to check if a given config has an `input.path`.
970
892
  */
@@ -989,7 +911,7 @@ type CreateKubbOptions = {
989
911
  * ```ts
990
912
  * const kubb = createKubb(userConfig)
991
913
  * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
992
- * const { files, failedPlugins } = await kubb.safeBuild()
914
+ * const { files, diagnostics } = await kubb.safeBuild()
993
915
  * ```
994
916
  */
995
917
  export class Kubb {
@@ -1027,22 +949,12 @@ export class Kubb {
1027
949
  const driver = new KubbDriver(config, { hooks: this.hooks })
1028
950
  const storage = createSourcesView(config.storage)
1029
951
 
1030
- await this.hooks.emit('kubb:debug', { date: new Date(), logs: this.#configLogs(config) })
1031
-
1032
- if (isInputPath(this.#userConfig) && !new URLPath(this.#userConfig.input.path).isURL) {
1033
- try {
1034
- await exists(this.#userConfig.input.path)
1035
- await this.hooks.emit('kubb:debug', { date: new Date(), logs: [`✓ Input file validated: ${this.#userConfig.input.path}`] })
1036
- } catch (caughtError) {
1037
- throw new Error(
1038
- `Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${this.#userConfig.input.path}`,
1039
- { cause: caughtError as Error },
1040
- )
1041
- }
1042
- }
952
+ // Each generator a plugin registers adds a listener to the shared hooks emitter, so size the
953
+ // ceiling to the plugin count. Without this, a multi-generator plugin set trips Node's
954
+ // EventEmitter leak warning at the default 10.
955
+ this.hooks.setMaxListeners(Math.max(10, config.plugins.length * HOOK_LISTENERS_PER_PLUGIN))
1043
956
 
1044
957
  if (config.output.clean) {
1045
- await this.hooks.emit('kubb:debug', { date: new Date(), logs: ['Cleaning output directories', ` • Output: ${config.output.path}`] })
1046
958
  await config.storage.clear(resolve(config.root, config.output.path))
1047
959
  }
1048
960
 
@@ -1059,10 +971,12 @@ export class Kubb {
1059
971
  */
1060
972
  async build(): Promise<BuildOutput> {
1061
973
  const out = await this.safeBuild()
1062
- if (out.error) throw out.error
1063
- if (out.failedPlugins.size > 0) {
1064
- const errors = [...out.failedPlugins].map(({ error }) => error)
1065
- throw new BuildError(`Build Error with ${out.failedPlugins.size} failed plugins`, { errors })
974
+ if (Diagnostics.hasError(out.diagnostics)) {
975
+ const errors = out.diagnostics
976
+ .filter(isProblemDiagnostic)
977
+ .filter((diagnostic) => diagnostic.severity === 'error')
978
+ .map((diagnostic) => diagnostic.cause ?? new DiagnosticError(diagnostic))
979
+ throw new BuildError(`Build failed with ${errors.length} ${errors.length === 1 ? 'error' : 'errors'}`, { errors })
1066
980
  }
1067
981
  return out
1068
982
  }
@@ -1076,8 +990,9 @@ export class Kubb {
1076
990
  using cleanup = this
1077
991
  const driver = cleanup.driver
1078
992
  const storage = cleanup.storage
1079
- const { failedPlugins, pluginTimings, error } = await driver.run({ storage })
1080
- return { failedPlugins, files: driver.fileManager.files, driver, pluginTimings, storage, ...(error ? { error } : {}) }
993
+ const { diagnostics } = await driver.run({ storage })
994
+
995
+ return { diagnostics, files: driver.fileManager.files, driver, storage }
1081
996
  }
1082
997
 
1083
998
  dispose(): void {
@@ -1087,27 +1002,6 @@ export class Kubb {
1087
1002
  [Symbol.dispose](): void {
1088
1003
  this.dispose()
1089
1004
  }
1090
-
1091
- #configLogs(config: Config): Array<string> {
1092
- const u = this.#userConfig
1093
- const diag = getDiagnosticInfo()
1094
- return [
1095
- 'Configuration:',
1096
- ` • Name: ${u.name || 'unnamed'}`,
1097
- ` • Root: ${u.root || process.cwd()}`,
1098
- ` • Output: ${u.output?.path || 'not specified'}`,
1099
- ` • Plugins: ${u.plugins?.length || 0}`,
1100
- 'Output Settings:',
1101
- ` • Storage: ${config.storage.name}`,
1102
- ` • Formatter: ${u.output?.format || 'none'}`,
1103
- ` • Linter: ${u.output?.lint || 'none'}`,
1104
- `Running adapter: ${config.adapter?.name || 'none'}`,
1105
- 'Environment:',
1106
- Object.entries(diag)
1107
- .map(([key, value]) => ` • ${key}: ${value}`)
1108
- .join('\n'),
1109
- ]
1110
- }
1111
1005
  }
1112
1006
 
1113
1007
  /**
@@ -11,7 +11,7 @@ import type { FileNode } from '@kubb/ast'
11
11
  export type Renderer<TElement = unknown> = {
12
12
  /**
13
13
  * Renders `element` and populates {@link files} with the resulting {@link FileNode} objects.
14
- * Called once per render cycle; must resolve before {@link files} is read.
14
+ * Called once per render cycle. Must resolve before {@link files} is read.
15
15
  */
16
16
  render(element: TElement): Promise<void>
17
17
  /**
@@ -52,7 +52,7 @@ export type RendererFactory<TElement = unknown> = () => Renderer<TElement>
52
52
  * (JSX, a template string, a tree of any shape) into `FileNode`s that get
53
53
  * written to disk.
54
54
  *
55
- * Use this to support output formats beyond JSX for instance, a Handlebars
55
+ * Use this to support output formats beyond JSX, for instance, a Handlebars
56
56
  * renderer, a string-template renderer, or a renderer that writes binary
57
57
  * files. Plugins and generators pick the renderer to use via the `renderer`
58
58
  * field on `defineGenerator`.
@@ -0,0 +1,84 @@
1
+ import type { logLevel } from './constants.ts'
2
+ import type { Config } from './createKubb.ts'
3
+ import type { Diagnostic } from './diagnostics.ts'
4
+
5
+ /**
6
+ * A built-in reporter that renders a run's output, independent of the live logger view.
7
+ *
8
+ * - `cli` renders the per-config summary to the terminal (the default).
9
+ * - `json` writes a machine-readable report to stdout, for CI.
10
+ * - `file` writes a config's diagnostics to `.kubb/kubb-<name>-<timestamp>.log`.
11
+ */
12
+ export type ReporterName = 'cli' | 'json' | 'file'
13
+
14
+ /**
15
+ * One config's outcome within a run, as handed to a {@link Reporter}.
16
+ */
17
+ export type GenerationResult = {
18
+ config: Config
19
+ /**
20
+ * Diagnostics collected while generating this config.
21
+ */
22
+ diagnostics: Array<Diagnostic>
23
+ /**
24
+ * Number of files written for this config.
25
+ */
26
+ filesCreated: number
27
+ status: 'success' | 'failed'
28
+ /**
29
+ * `process.hrtime()` snapshot taken when this config started generating.
30
+ */
31
+ hrStart: [number, number]
32
+ }
33
+
34
+ /**
35
+ * Render context passed alongside the {@link GenerationResult}, carrying knobs a reporter needs
36
+ * but that are not part of the run data (e.g. verbosity).
37
+ */
38
+ export type ReporterContext = {
39
+ /**
40
+ * Output verbosity. Use the `logLevel` constants exported from `@kubb/core`
41
+ * (`silent`, `error`, `warn`, `info`, `verbose`, `debug`).
42
+ */
43
+ logLevel: (typeof logLevel)[keyof typeof logLevel]
44
+ }
45
+
46
+ /**
47
+ * Reporter contract. Unlike a Logger (the live TUI view), a reporter never sees the event
48
+ * emitter. `report` is called once per config with its {@link GenerationResult} and produces
49
+ * output (a terminal summary, a JSON report, a log file).
50
+ */
51
+ export type Reporter = {
52
+ /**
53
+ * Display name, matching a {@link ReporterName} for the built-ins.
54
+ */
55
+ name: string
56
+ /**
57
+ * Called once per config with that config's result and the render context.
58
+ */
59
+ report: (result: GenerationResult, context: ReporterContext) => void | Promise<void>
60
+ }
61
+
62
+ export type UserReporter = Reporter
63
+
64
+ /**
65
+ * Defines a reporter. Returns the reporter unchanged at runtime. It exists for type inference and
66
+ * to mark the value as a reporter, mirroring {@link defineLogger}. Wiring the reporter onto the
67
+ * run's events is the host's job, so the reporter only ever deals with a {@link GenerationResult}.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { createReporter, Diagnostics } from '@kubb/core'
72
+ *
73
+ * export const jsonReporter = createReporter({
74
+ * name: 'json',
75
+ * report(result) {
76
+ * const status = Diagnostics.hasError(result.diagnostics) ? 'failed' : 'success'
77
+ * process.stdout.write(`${JSON.stringify({ status, diagnostics: result.diagnostics }, null, 2)}\n`)
78
+ * },
79
+ * })
80
+ * ```
81
+ */
82
+ export function createReporter(reporter: UserReporter): Reporter {
83
+ return reporter
84
+ }
@@ -43,7 +43,7 @@ export type Storage = {
43
43
  /**
44
44
  * Defines a custom storage backend. The builder receives user options and
45
45
  * returns a `Storage` implementation. Kubb ships with filesystem and
46
- * in-memory storages reach for this when you need to write generated files
46
+ * in-memory storages, reach for this when you need to write generated files
47
47
  * elsewhere (cloud storage, a database, a remote API).
48
48
  *
49
49
  * @example In-memory storage (the built-in implementation)