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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/core",
3
- "version": "5.0.0-beta.37",
3
+ "version": "5.0.0-beta.38",
4
4
  "description": "Core engine for Kubb's plugin-based code generation system. Provides the plugin driver, file manager, defineConfig, and build orchestration used by every Kubb plugin.",
5
5
  "keywords": [
6
6
  "code-generator",
@@ -58,14 +58,14 @@
58
58
  "dependencies": {
59
59
  "fflate": "^0.8.3",
60
60
  "tinyexec": "~1.1.2",
61
- "@kubb/ast": "5.0.0-beta.37"
61
+ "@kubb/ast": "5.0.0-beta.38"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@internals/utils": "0.0.0",
65
- "@kubb/renderer-jsx": "5.0.0-beta.37"
65
+ "@kubb/renderer-jsx": "5.0.0-beta.38"
66
66
  },
67
67
  "peerDependencies": {
68
- "@kubb/renderer-jsx": "5.0.0-beta.37"
68
+ "@kubb/renderer-jsx": "5.0.0-beta.38"
69
69
  },
70
70
  "size-limit": [
71
71
  {
package/src/KubbDriver.ts CHANGED
@@ -802,17 +802,11 @@ export class KubbDriver {
802
802
  getContext<TOptions extends PluginFactoryOptions>(plugin: NormalizedPlugin<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {
803
803
  const driver = this
804
804
 
805
+ // Collect into the active build only. The host renders each collected diagnostic once after the
806
+ // build (the CLI via `Diagnostics.emit`, the agent via its post-build loop), so emitting a live
807
+ // `kubb:error`/`kubb:warn`/`kubb:info` here would render it twice.
805
808
  const report = (diagnostic: Omit<ProblemDiagnostic, 'plugin'>): void => {
806
809
  Diagnostics.report({ ...diagnostic, plugin: plugin.name })
807
- if (diagnostic.severity === 'error') {
808
- driver.hooks.emit('kubb:error', { error: diagnostic.cause ?? new Error(diagnostic.message) })
809
- return
810
- }
811
- if (diagnostic.severity === 'warning') {
812
- driver.hooks.emit('kubb:warn', { message: diagnostic.message })
813
- return
814
- }
815
- driver.hooks.emit('kubb:info', { message: diagnostic.message })
816
810
  }
817
811
 
818
812
  return {
@@ -44,9 +44,9 @@ export type ReporterContext = {
44
44
  }
45
45
 
46
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).
47
+ * Host-facing reporter, as installed onto a run. Unlike a Logger (the live TUI view), a reporter
48
+ * never sees the event emitter. `report` runs once per config; `flush`, when present, runs once
49
+ * after the last config.
50
50
  */
51
51
  export type Reporter = {
52
52
  /**
@@ -57,14 +57,29 @@ export type Reporter = {
57
57
  * Called once per config with that config's result and the render context.
58
58
  */
59
59
  report: (result: GenerationResult, context: ReporterContext) => void | Promise<void>
60
+ /**
61
+ * Optional finalizer called once after the run's last config. The host wires it to
62
+ * `kubb:lifecycle:end`. {@link createReporter} closes it over the reports `report` returned.
63
+ */
64
+ flush?: (context: ReporterContext) => void | Promise<void>
60
65
  }
61
66
 
62
- export type UserReporter = Reporter
67
+ /**
68
+ * Reporter definition passed to {@link createReporter}. `report` returns the value to collect for
69
+ * this config (e.g. a built report), and the optional `flush` receives the collected reports to
70
+ * emit as one document. `T` is inferred from `report`'s return type.
71
+ */
72
+ export type UserReporter<T = void> = {
73
+ name: string
74
+ report: (result: GenerationResult, context: ReporterContext) => T | Promise<T>
75
+ flush?: (context: ReporterContext, reports: Array<T>) => void | Promise<void>
76
+ }
63
77
 
64
78
  /**
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}.
79
+ * Defines a reporter. When the definition has a `flush`, the returned reporter buffers each value
80
+ * `report` returns and hands the array to `flush` once, then clears it. Without a `flush`, nothing
81
+ * is buffered. Wiring the reporter onto the run's events is the host's job, so the reporter only
82
+ * ever deals with a {@link GenerationResult}.
68
83
  *
69
84
  * @example
70
85
  * ```ts
@@ -73,12 +88,30 @@ export type UserReporter = Reporter
73
88
  * export const jsonReporter = createReporter({
74
89
  * name: 'json',
75
90
  * report(result) {
76
- * const status = Diagnostics.hasError(result.diagnostics) ? 'failed' : 'success'
77
- * process.stdout.write(`${JSON.stringify({ status, diagnostics: result.diagnostics }, null, 2)}\n`)
91
+ * return { status: Diagnostics.hasError(result.diagnostics) ? 'failed' : 'success', diagnostics: result.diagnostics }
92
+ * },
93
+ * flush(context, reports) {
94
+ * process.stdout.write(`${JSON.stringify(reports, null, 2)}\n`)
78
95
  * },
79
96
  * })
80
97
  * ```
81
98
  */
82
- export function createReporter(reporter: UserReporter): Reporter {
83
- return reporter
99
+ export function createReporter<T = void>(reporter: UserReporter<T>): Reporter {
100
+ const flush = reporter.flush
101
+ if (!flush) {
102
+ return { name: reporter.name, report: reporter.report }
103
+ }
104
+
105
+ const reports: Array<T> = []
106
+
107
+ return {
108
+ name: reporter.name,
109
+ async report(result, context) {
110
+ reports.push(await reporter.report(result, context))
111
+ },
112
+ async flush(context) {
113
+ await flush(context, reports)
114
+ reports.length = 0
115
+ },
116
+ }
84
117
  }
package/src/mocks.ts CHANGED
@@ -3,13 +3,15 @@ import type { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from '@k
3
3
  import { transform } from '@kubb/ast'
4
4
  import { FileManager } from './FileManager.ts'
5
5
  import { KubbDriver } from './KubbDriver.ts'
6
- import type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions } from './types.ts'
6
+ import type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'
7
7
 
8
8
  /**
9
9
 
10
10
  * Creates a minimal `PluginDriver` mock for unit tests.
11
11
  */
12
12
  export function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {
13
+ const fileManager = new FileManager()
14
+
13
15
  return {
14
16
  config: options?.config ?? {
15
17
  root: '.',
@@ -21,7 +23,26 @@ export function createMockedPluginDriver(options: { name?: string; plugin?: Norm
21
23
  return options?.plugin
22
24
  },
23
25
  getResolver: (_pluginName: string) => options?.plugin?.resolver,
24
- fileManager: new FileManager(),
26
+ fileManager,
27
+ async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {
28
+ if (!result) return
29
+
30
+ if (Array.isArray(result)) {
31
+ fileManager.upsert(...(result as Array<FileNode>))
32
+ return
33
+ }
34
+
35
+ if (!renderer) return
36
+
37
+ using instance = renderer()
38
+ if (instance.stream) {
39
+ for (const file of instance.stream(result)) fileManager.upsert(file)
40
+ return
41
+ }
42
+
43
+ await instance.render(result)
44
+ fileManager.upsert(...instance.files)
45
+ },
25
46
  } as unknown as KubbDriver
26
47
  }
27
48