@kubb/core 5.0.0-beta.63 → 5.0.0-beta.64

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 (39) hide show
  1. package/dist/{diagnostics-IjkPEgAO.d.ts → diagnostics-BqiNAWVS.d.ts} +9 -10
  2. package/dist/index.cjs +24 -34
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.js +25 -35
  6. package/dist/index.js.map +1 -1
  7. package/dist/{memoryStorage-CWFzAz4o.js → memoryStorage-DWnhqUf2.js} +3 -3
  8. package/dist/{memoryStorage-CWFzAz4o.js.map → memoryStorage-DWnhqUf2.js.map} +1 -1
  9. package/dist/{memoryStorage-CUj1hrxa.cjs → memoryStorage-mojU6pbA.cjs} +2 -2
  10. package/dist/{memoryStorage-CUj1hrxa.cjs.map → memoryStorage-mojU6pbA.cjs.map} +1 -1
  11. package/dist/mocks.cjs +1 -1
  12. package/dist/mocks.d.ts +2 -2
  13. package/dist/mocks.js +2 -2
  14. package/package.json +4 -5
  15. package/src/FileManager.ts +0 -137
  16. package/src/FileProcessor.ts +0 -212
  17. package/src/KubbDriver.ts +0 -900
  18. package/src/Transform.ts +0 -105
  19. package/src/constants.ts +0 -126
  20. package/src/createAdapter.ts +0 -127
  21. package/src/createKubb.ts +0 -195
  22. package/src/createRenderer.ts +0 -72
  23. package/src/createReporter.ts +0 -134
  24. package/src/createStorage.ts +0 -83
  25. package/src/defineGenerator.ts +0 -211
  26. package/src/defineParser.ts +0 -63
  27. package/src/definePlugin.ts +0 -438
  28. package/src/defineResolver.ts +0 -711
  29. package/src/diagnostics.ts +0 -662
  30. package/src/index.ts +0 -20
  31. package/src/mocks.ts +0 -249
  32. package/src/reporters/cliReporter.ts +0 -89
  33. package/src/reporters/fileReporter.ts +0 -102
  34. package/src/reporters/jsonReporter.ts +0 -20
  35. package/src/reporters/report.ts +0 -85
  36. package/src/storages/fsStorage.ts +0 -82
  37. package/src/storages/memoryStorage.ts +0 -55
  38. package/src/types.ts +0 -829
  39. /package/dist/{chunk-C0LytTxp.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/src/mocks.ts DELETED
@@ -1,249 +0,0 @@
1
- import path, { resolve } from 'node:path'
2
- import { camelCase } from '@internals/utils'
3
- import type { FileNode, InputMeta, Macro, OperationNode, SchemaNode } from '@kubb/ast'
4
- import { applyMacros } from '@kubb/ast'
5
- import { expect } from 'vitest'
6
- import type { Parser } from './defineParser.ts'
7
- import { FileManager } from './FileManager.ts'
8
- import { FileProcessor } from './FileProcessor.ts'
9
- import type { KubbDriver } from './KubbDriver.ts'
10
- import { memoryStorage } from './storages/memoryStorage.ts'
11
- import type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'
12
-
13
- /**
14
- * Creates a minimal `KubbDriver` mock for unit tests.
15
- */
16
- export function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {
17
- const fileManager = new FileManager()
18
-
19
- return {
20
- config: options?.config ?? {
21
- root: '.',
22
- output: {
23
- path: './path',
24
- },
25
- },
26
- getPlugin(_pluginName: string): NormalizedPlugin | undefined {
27
- return options?.plugin
28
- },
29
- getResolver: (_pluginName: string) => options?.plugin?.resolver,
30
- fileManager,
31
- async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {
32
- if (!result) return
33
-
34
- if (Array.isArray(result)) {
35
- fileManager.upsert(...(result as Array<FileNode>))
36
- return
37
- }
38
-
39
- if (!renderer) return
40
-
41
- using instance = renderer()
42
- if (instance.stream) {
43
- for (const file of instance.stream(result)) fileManager.upsert(file)
44
- return
45
- }
46
-
47
- await instance.render(result)
48
- fileManager.upsert(...instance.files)
49
- },
50
- } as unknown as KubbDriver
51
- }
52
-
53
- /**
54
- * Creates a minimal `Adapter` mock for unit tests.
55
- * `parse` returns an empty `InputNode` by default. Override via `options.parse`.
56
- * `getImports` returns `[]` by default.
57
- */
58
- export function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(
59
- options: {
60
- name?: TOptions['name']
61
- resolvedOptions?: TOptions['resolvedOptions']
62
- parse?: Adapter<TOptions>['parse']
63
- getImports?: Adapter<TOptions>['getImports']
64
- } = {},
65
- ): Adapter<TOptions> {
66
- return {
67
- name: (options.name ?? 'oas') as TOptions['name'],
68
- options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],
69
- parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),
70
- getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),
71
- } as Adapter<TOptions>
72
- }
73
-
74
- /**
75
- * Creates a minimal plugin mock for unit tests.
76
- *
77
- * @example
78
- * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`
79
- */
80
- export function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {
81
- name: TOptions['name']
82
- options: TOptions['resolvedOptions']
83
- resolver?: TOptions['resolver']
84
- macros?: Array<Macro>
85
- dependencies?: Array<string>
86
- }): NormalizedPlugin<TOptions> {
87
- return {
88
- name: params.name,
89
- options: params.options,
90
- resolver: params.resolver,
91
- macros: params.macros,
92
- dependencies: params.dependencies,
93
- hooks: {},
94
- } as unknown as NormalizedPlugin<TOptions>
95
- }
96
-
97
- type RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {
98
- config: Config
99
- adapter: Adapter
100
- meta?: InputMeta
101
- driver: KubbDriver
102
- plugin: NormalizedPlugin<TOptions>
103
- options: TOptions['resolvedOptions']
104
- resolver: TOptions['resolver']
105
- }
106
-
107
- function createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {
108
- const root = resolve(opts.config.root, opts.config.output.path)
109
-
110
- return {
111
- config: opts.config,
112
- root,
113
- adapter: opts.adapter,
114
- resolver: opts.resolver,
115
- plugin: opts.plugin,
116
- driver: opts.driver,
117
- getResolver: (name: string) => opts.driver.getResolver(name),
118
- meta: opts.meta ?? { circularNames: [], enumNames: [] },
119
- addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),
120
- upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),
121
- hooks: opts.driver.hooks ?? ({} as never),
122
- warn: (msg: string) => console.warn(msg),
123
- error: (msg: string) => console.error(msg),
124
- info: (msg: string) => console.info(msg),
125
- } as unknown as Omit<GeneratorContext<TOptions>, 'options'>
126
- }
127
-
128
- /**
129
- * Renders a generator's `schema` method in a test context.
130
- *
131
- * @example
132
- * ```ts
133
- * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })
134
- * await matchFiles(driver.fileManager.files)
135
- * ```
136
- */
137
- export async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(
138
- generator: Generator<TOptions>,
139
- node: SchemaNode,
140
- opts: RenderGeneratorOptions<TOptions>,
141
- ): Promise<void> {
142
- if (!generator.schema) return
143
- const context = createMockedPluginContext(opts)
144
- const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node
145
- const result = await generator.schema(transformedNode, {
146
- ...context,
147
- options: opts.options,
148
- })
149
- await opts.driver.dispatch({ result, renderer: generator.renderer })
150
- }
151
-
152
- /**
153
- * Renders a generator's `operation` method in a test context.
154
- *
155
- * @example
156
- * ```ts
157
- * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })
158
- * await matchFiles(driver.fileManager.files)
159
- * ```
160
- */
161
- export async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(
162
- generator: Generator<TOptions>,
163
- node: OperationNode,
164
- opts: RenderGeneratorOptions<TOptions>,
165
- ): Promise<void> {
166
- if (!generator.operation) return
167
- const context = createMockedPluginContext(opts)
168
- const transformedNode = opts.plugin.macros?.length ? applyMacros(node, opts.plugin.macros) : node
169
- const result = await generator.operation(transformedNode, {
170
- ...context,
171
- options: opts.options,
172
- })
173
- await opts.driver.dispatch({ result, renderer: generator.renderer })
174
- }
175
-
176
- /**
177
- * Renders a generator's `operations` method in a test context.
178
- *
179
- * @example
180
- * ```ts
181
- * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })
182
- * await matchFiles(driver.fileManager.files)
183
- * ```
184
- */
185
- export async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(
186
- generator: Generator<TOptions>,
187
- nodes: Array<OperationNode>,
188
- opts: RenderGeneratorOptions<TOptions>,
189
- ): Promise<void> {
190
- if (!generator.operations) return
191
- const context = createMockedPluginContext(opts)
192
- const transformedNodes = opts.plugin.macros?.length ? nodes.map((n) => applyMacros(n, opts.plugin.macros!)) : nodes
193
- const result = await generator.operations(transformedNodes, {
194
- ...context,
195
- options: opts.options,
196
- })
197
- await opts.driver.dispatch({ result, renderer: generator.renderer })
198
- }
199
-
200
- type MatchFilesOptions = {
201
- /**
202
- * Parsers indexed by file extension, used to render each `FileNode` to source.
203
- * Without a matching parser the file's raw content is used.
204
- */
205
- parsers?: Map<FileNode['extname'], Parser>
206
- /**
207
- * Formatter applied to non-JSON output before snapshotting, e.g. prettier. When
208
- * omitted the parsed source is snapshotted as-is.
209
- */
210
- format?: (source?: string) => string | Promise<string>
211
- /**
212
- * Subfolder under `__snapshots__`, camelCased. Useful to keep variant snapshots apart.
213
- */
214
- pre?: string
215
- }
216
-
217
- /**
218
- * Renders the driver's collected `FileNode`s to source and asserts each against a file snapshot.
219
- * Pair it with the `renderGenerator*` helpers to snapshot a generator's output.
220
- *
221
- * @example
222
- * ```ts
223
- * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })
224
- * await matchFiles(driver.fileManager.files, { parsers, format })
225
- * ```
226
- */
227
- export async function matchFiles(files: Array<FileNode> | undefined, options: MatchFilesOptions = {}): Promise<Map<string, string> | undefined> {
228
- if (!files?.length) return
229
-
230
- const { parsers = new Map(), format, pre } = options
231
- const fileProcessor = new FileProcessor({ storage: memoryStorage(), parsers })
232
- const processed = new Map<string, string>()
233
-
234
- for (const file of files) {
235
- if (!file?.path || processed.has(file.path)) {
236
- continue
237
- }
238
-
239
- const parsed = fileProcessor.parse(file)
240
- const code = file.baseName.endsWith('.json') || !format ? parsed : await format(parsed)
241
-
242
- processed.set(file.path, code)
243
-
244
- const snapshotPath = path.join('__snapshots__', ...(pre ? [camelCase(pre)] : []), file.baseName)
245
- await expect(code).toMatchFileSnapshot(snapshotPath)
246
- }
247
-
248
- return processed
249
- }
@@ -1,89 +0,0 @@
1
- import { styleText } from 'node:util'
2
- import { formatMs, randomCliColor } from '@internals/utils'
3
- import { SUMMARY_MAX_BAR_LENGTH, SUMMARY_TIME_SCALE_DIVISOR } from '../constants.ts'
4
- import { createReporter, logLevel as logLevelMap } from '../createReporter.ts'
5
- import { buildReport, type Report } from './report.ts'
6
-
7
- /**
8
- * Builds the vitest/jest-style summary for one {@link Report}: right-aligned dim labels with
9
- * `N passed (total)` counts, and a per-plugin `Timings` section when `showTimings`.
10
- */
11
- function buildSummaryLines(report: Report, { showTimings }: { showTimings: boolean }): Array<string> {
12
- const { status, plugins, counts, filesCreated, durationMs, output, timings } = report
13
-
14
- const rows: Array<[label: string, value: string]> = []
15
-
16
- rows.push([
17
- 'Plugins',
18
- status === 'success'
19
- ? `${styleText('green', `${plugins.passed} passed`)} (${plugins.total})`
20
- : `${styleText('green', `${plugins.passed} passed`)} | ${styleText('red', `${plugins.failed.length} failed`)} (${plugins.total})`,
21
- ])
22
-
23
- if (status === 'failed' && plugins.failed.length > 0) {
24
- rows.push(['Failed', plugins.failed.map((name) => randomCliColor(name)).join(', ')])
25
- }
26
-
27
- if (counts.errors > 0 || counts.warnings > 0) {
28
- const issues = [
29
- counts.errors > 0 ? styleText('red', `${counts.errors} ${counts.errors === 1 ? 'error' : 'errors'}`) : undefined,
30
- counts.warnings > 0 ? styleText('yellow', `${counts.warnings} ${counts.warnings === 1 ? 'warning' : 'warnings'}`) : undefined,
31
- ]
32
- .filter(Boolean)
33
- .join(' | ')
34
- rows.push(['Issues', issues])
35
- }
36
-
37
- rows.push(['Files', `${styleText('green', String(filesCreated))} generated`])
38
- rows.push(['Duration', styleText('green', formatMs(durationMs))])
39
- rows.push(['Output', output])
40
-
41
- const labelWidth = Math.max(...rows.map(([label]) => label.length), timings.length > 0 ? 'Timings'.length : 0)
42
- const lines = rows.map(([label, value]) => `${styleText('dim', label.padStart(labelWidth))} ${value}`)
43
-
44
- if (showTimings && timings.length > 0) {
45
- const nameWidth = Math.max(0, ...timings.map((timing) => timing.plugin.length))
46
- const indent = ' '.repeat(labelWidth + 2)
47
-
48
- lines.push(styleText('dim', 'Timings'.padStart(labelWidth)))
49
- for (const timing of timings) {
50
- const timeStr = formatMs(timing.durationMs)
51
- const barLength = Math.min(Math.ceil(timing.durationMs / SUMMARY_TIME_SCALE_DIVISOR), SUMMARY_MAX_BAR_LENGTH)
52
- const bar = styleText('dim', '█'.repeat(barLength))
53
- lines.push(`${indent}${styleText('dim', '•')} ${timing.plugin.padEnd(nameWidth)} ${bar} ${timeStr}`)
54
- }
55
- }
56
-
57
- return lines
58
- }
59
-
60
- /**
61
- * Renders the summary as plain `console.log` lines so it works in every CLI (no clack/TTY
62
- * dependency): a blank line, the config name colored by status, then the summary rows.
63
- */
64
- function renderSummary(lines: ReadonlyArray<string>, { title, status }: { title: string; status: 'success' | 'failed' }): void {
65
- console.log('')
66
- if (title) {
67
- console.log(styleText(status === 'failed' ? 'red' : 'green', title))
68
- }
69
- for (const line of lines) {
70
- console.log(line)
71
- }
72
- }
73
-
74
- /**
75
- * The default `cli` reporter. Renders the {@link Report} for each config as it finishes, independent
76
- * of the live logger view. Suppressed at `silent`. The `verbose` level adds the per-plugin timings.
77
- */
78
- export const cliReporter = createReporter({
79
- name: 'cli',
80
- report(result, { logLevel }) {
81
- if (logLevel <= logLevelMap.silent) {
82
- return
83
- }
84
-
85
- const report = buildReport(result)
86
- const lines = buildSummaryLines(report, { showTimings: logLevel >= logLevelMap.verbose })
87
- renderSummary(lines, { title: report.name, status: report.status })
88
- },
89
- })
@@ -1,102 +0,0 @@
1
- import { relative, resolve } from 'node:path'
2
- import process from 'node:process'
3
- import { stripVTControlCharacters } from 'node:util'
4
- import { formatMs, write } from '@internals/utils'
5
- import { createReporter } from '../createReporter.ts'
6
- import { type Diagnostic, Diagnostics } from '../diagnostics.ts'
7
- import { buildReport, type Report } from './report.ts'
8
-
9
- /**
10
- * Builds the `## Summary` section: the same counts the cli and json reporters expose, as a list of
11
- * `label value` rows with the labels padded to a common width.
12
- */
13
- function buildSummarySection(report: Report): Array<string> {
14
- const { status, plugins, counts, filesCreated, durationMs, output } = report
15
-
16
- const rows: Array<[label: string, value: string]> = [
17
- ['Status', status],
18
- [
19
- 'Plugins',
20
- status === 'success' ? `${plugins.passed} passed (${plugins.total})` : `${plugins.passed} passed | ${plugins.failed.length} failed (${plugins.total})`,
21
- ],
22
- ]
23
-
24
- if (plugins.failed.length > 0) {
25
- rows.push(['Failed', plugins.failed.join(', ')])
26
- }
27
-
28
- rows.push(['Issues', `${counts.errors} errors | ${counts.warnings} warnings | ${counts.infos} infos`])
29
- rows.push(['Files', `${filesCreated} generated`])
30
- rows.push(['Duration', formatMs(durationMs)])
31
- rows.push(['Output', output])
32
-
33
- const labelWidth = Math.max(...rows.map(([label]) => label.length))
34
- const lines = rows.map(([label, value]) => ` ${label.padEnd(labelWidth)} ${value}`)
35
-
36
- return ['## Summary', '', ...lines]
37
- }
38
-
39
- /**
40
- * Builds the `## Problems` section: each problem rendered in the miette block format, blocks
41
- * separated by a blank line. Returns an empty array when there are no problems, so the caller
42
- * can drop the heading.
43
- */
44
- function buildProblemSection(diagnostics: ReadonlyArray<Diagnostic>): Array<string> {
45
- const problems = diagnostics.filter(Diagnostics.isProblem)
46
- if (problems.length === 0) {
47
- return []
48
- }
49
-
50
- const blocks = problems.map((diagnostic) => Diagnostics.formatLines(diagnostic).join('\n'))
51
- return ['## Problems', '', blocks.join('\n\n')]
52
- }
53
-
54
- /**
55
- * Builds the `## Timings` section from a {@link Report}: one `plugin duration` row per record,
56
- * slowest first with the plugin names left-aligned and the durations right-aligned. Returns an
57
- * empty array when there are no timings.
58
- */
59
- function buildTimingSection(report: Report): Array<string> {
60
- const { timings } = report
61
- if (timings.length === 0) {
62
- return []
63
- }
64
-
65
- const nameWidth = Math.max(...timings.map((timing) => timing.plugin.length))
66
- const durations = timings.map((timing) => formatMs(timing.durationMs))
67
- const durationWidth = Math.max(...durations.map((duration) => duration.length))
68
- const rows = timings.map((timing, index) => ` ${timing.plugin.padEnd(nameWidth)} ${durations[index]!.padStart(durationWidth)}`)
69
-
70
- return ['## Timings', '', ...rows]
71
- }
72
-
73
- /**
74
- * The `file` reporter. Writes a config's {@link Report} to `.kubb/kubb-<name>-<timestamp>.log` as a
75
- * plain-text document: a `# <name> — <timestamp>` header, a `## Summary` with the same counts the
76
- * cli and json reporters expose, a `## Problems` section in the miette block format, and a
77
- * `## Timings` section. Selected with `--reporter file` (or `reporters: ['file']`).
78
- *
79
- * @note It captures the collected diagnostics once a config finishes, not the live
80
- * `kubb:info`/`kubb:plugin` event stream. Color is stripped so the file stays plain text even when
81
- * the run is attached to a TTY.
82
- */
83
- export const fileReporter = createReporter({
84
- name: 'file',
85
- async report(result) {
86
- const { diagnostics, config } = result
87
- if (diagnostics.length === 0) {
88
- return
89
- }
90
-
91
- const report = buildReport(result)
92
- const header = config.name ? `# ${config.name} — ${new Date().toISOString()}` : `# ${new Date().toISOString()}`
93
- const sections = [buildSummarySection(report), buildProblemSection(diagnostics), buildTimingSection(report)].filter((section) => section.length > 0)
94
- const content = stripVTControlCharacters([header, ...sections.map((section) => section.join('\n'))].join('\n\n'))
95
-
96
- const baseName = `${['kubb', config.name, Date.now()].filter(Boolean).join('-')}.log`
97
- const pathName = resolve(process.cwd(), '.kubb', baseName)
98
-
99
- await write(pathName, `${content}\n`)
100
- console.error(`Debug log written to ${relative(process.cwd(), pathName)}`)
101
- },
102
- })
@@ -1,20 +0,0 @@
1
- import process from 'node:process'
2
- import { createReporter } from '../createReporter.ts'
3
- import { buildReport } from './report.ts'
4
-
5
- /**
6
- * The `json` reporter. `report` returns one config's {@link Report}, which {@link createReporter}
7
- * buffers, and `drain` writes them as a single pretty-printed JSON array on `kubb:lifecycle:end`.
8
- * Buffering keeps a multi-config run one valid JSON document on stdout instead of concatenated
9
- * objects that would break `jq .`. The terminal reporter is suppressed while `json` is active so
10
- * stdout stays valid JSON.
11
- */
12
- export const jsonReporter = createReporter({
13
- name: 'json',
14
- report(result) {
15
- return buildReport(result)
16
- },
17
- drain(_context, reports) {
18
- process.stdout.write(`${JSON.stringify(reports, null, 2)}\n`)
19
- },
20
- })
@@ -1,85 +0,0 @@
1
- import { resolve } from 'node:path'
2
- import { getElapsedMs } from '@internals/utils'
3
- import type { GenerationResult } from '../createReporter.ts'
4
- import { Diagnostics, type SerializedDiagnostic } from '../diagnostics.ts'
5
-
6
- /**
7
- * One plugin's elapsed time, derived from a `performance` diagnostic.
8
- */
9
- type ReportTiming = {
10
- plugin: string
11
- durationMs: number
12
- }
13
-
14
- /**
15
- * The normalized result of generating one config, shared by every reporter. Each reporter renders
16
- * the same {@link Report} in its own format (the `cli` summary, the `json` document, the `file`
17
- * log), so they always agree on the numbers. Build it with {@link buildReport}.
18
- */
19
- export type Report = {
20
- /**
21
- * The config name, or an empty string when it is unnamed.
22
- */
23
- name: string
24
- status: 'success' | 'failed'
25
- plugins: {
26
- passed: number
27
- /**
28
- * Names of the plugins that failed.
29
- */
30
- failed: Array<string>
31
- total: number
32
- }
33
- counts: {
34
- errors: number
35
- warnings: number
36
- infos: number
37
- }
38
- filesCreated: number
39
- /**
40
- * Wall-clock time spent generating this config, in milliseconds.
41
- */
42
- durationMs: number
43
- /**
44
- * Absolute output directory the files were written to.
45
- */
46
- output: string
47
- /**
48
- * Per-plugin durations, slowest first.
49
- */
50
- timings: Array<ReportTiming>
51
- /**
52
- * The build problems, serialized to their JSON-safe fields plus a `docsUrl`.
53
- */
54
- diagnostics: Array<SerializedDiagnostic>
55
- }
56
-
57
- /**
58
- * Builds the normalized {@link Report} for one config from its {@link GenerationResult}. Splits the
59
- * diagnostics into problems and per-plugin timings (slowest first) and derives the plugin and issue
60
- * counts, so every reporter renders the same data.
61
- */
62
- export function buildReport(result: GenerationResult): Report {
63
- const { config, diagnostics, filesCreated, status, hrStart } = result
64
-
65
- const failed = Diagnostics.failedPlugins(diagnostics)
66
- const total = config.plugins?.length ?? 0
67
- const counts = Diagnostics.count(diagnostics)
68
- const problems = diagnostics.filter(Diagnostics.isProblem)
69
- const timings = diagnostics
70
- .filter(Diagnostics.isPerformance)
71
- .sort((a, b) => b.duration - a.duration)
72
- .map((diagnostic) => ({ plugin: diagnostic.plugin, durationMs: diagnostic.duration }))
73
-
74
- return {
75
- name: config.name ?? '',
76
- status,
77
- plugins: { passed: total - failed.length, failed, total },
78
- counts,
79
- filesCreated,
80
- durationMs: getElapsedMs(hrStart),
81
- output: resolve(config.root, config.output.path),
82
- timings,
83
- diagnostics: problems.map((diagnostic) => Diagnostics.serialize(diagnostic)),
84
- }
85
- }
@@ -1,82 +0,0 @@
1
- import { access, glob, readFile, rm } from 'node:fs/promises'
2
- import { join, relative, resolve } from 'node:path'
3
- import { clean, runtime, toPosixPath, write } from '@internals/utils'
4
- import { createStorage } from '../createStorage.ts'
5
-
6
- /**
7
- * Built-in filesystem storage driver.
8
- *
9
- * This is the default storage when no `storage` option is configured in the root config.
10
- * Keys are resolved against `process.cwd()`, so root-relative paths such as
11
- * `src/gen/api/getPets.ts` are written to the correct location without extra configuration.
12
- *
13
- * Writes are deduplicated and directory-safe:
14
- * - leading and trailing whitespace is trimmed before writing
15
- * - the write is skipped when the file content is already identical
16
- * - missing parent directories are created automatically
17
- * - Bun's native file API is used when running under Bun
18
- *
19
- * @example
20
- * ```ts
21
- * import { fsStorage } from '@kubb/core'
22
- * import { defineConfig } from 'kubb'
23
- *
24
- * export default defineConfig({
25
- * input: { path: './petStore.yaml' },
26
- * output: { path: './src/gen' },
27
- * storage: fsStorage(),
28
- * })
29
- * ```
30
- */
31
- export const fsStorage = createStorage(() => ({
32
- name: 'fs',
33
- async hasItem(key: string) {
34
- try {
35
- await access(resolve(key))
36
- return true
37
- } catch (_error) {
38
- return false
39
- }
40
- },
41
- async getItem(key: string) {
42
- try {
43
- return await readFile(resolve(key), 'utf8')
44
- } catch (_error) {
45
- return null
46
- }
47
- },
48
- async setItem(key: string, value: string) {
49
- await write(resolve(key), value, { sanity: false })
50
- },
51
- async removeItem(key: string) {
52
- await rm(resolve(key), { force: true })
53
- },
54
- async getKeys(base?: string) {
55
- const resolvedBase = resolve(base ?? process.cwd())
56
-
57
- if (runtime.isBun) {
58
- const bunGlob = new Bun.Glob('**/*')
59
- return Array.fromAsync(bunGlob.scan({ cwd: resolvedBase, onlyFiles: true, dot: true }))
60
- }
61
-
62
- const keys: Array<string> = []
63
- try {
64
- for await (const entry of glob('**/*', { cwd: resolvedBase, withFileTypes: true })) {
65
- if (entry.isFile()) {
66
- keys.push(toPosixPath(relative(resolvedBase, join(entry.parentPath, entry.name))))
67
- }
68
- }
69
- } catch (_error) {
70
- // base directory does not exist yet
71
- }
72
-
73
- return keys
74
- },
75
- async clear(base?: string) {
76
- if (!base) {
77
- return
78
- }
79
-
80
- await clean(resolve(base))
81
- },
82
- }))
@@ -1,55 +0,0 @@
1
- import { createStorage } from '../createStorage.ts'
2
-
3
- /**
4
- * In-memory storage driver. Useful for testing and dry-run scenarios where
5
- * generated output should be captured without touching the filesystem.
6
- *
7
- * All data lives in a `Map` scoped to the storage instance and is discarded
8
- * when the instance is garbage-collected.
9
- *
10
- * @example
11
- * ```ts
12
- * import { memoryStorage } from '@kubb/core'
13
- * import { defineConfig } from 'kubb'
14
- *
15
- * export default defineConfig({
16
- * input: { path: './petStore.yaml' },
17
- * output: { path: './src/gen' },
18
- * storage: memoryStorage(),
19
- * })
20
- * ```
21
- */
22
- export const memoryStorage = createStorage(() => {
23
- const store = new Map<string, string>()
24
-
25
- return {
26
- name: 'memory',
27
- async hasItem(key: string) {
28
- return store.has(key)
29
- },
30
- async getItem(key: string) {
31
- return store.get(key) ?? null
32
- },
33
- async setItem(key: string, value: string) {
34
- store.set(key, value)
35
- },
36
- async removeItem(key: string) {
37
- store.delete(key)
38
- },
39
- async getKeys(base?: string) {
40
- const keys = [...store.keys()]
41
- return base ? keys.filter((k) => k.startsWith(base)) : keys
42
- },
43
- async clear(base?: string) {
44
- if (!base) {
45
- store.clear()
46
- return
47
- }
48
- for (const key of store.keys()) {
49
- if (key.startsWith(base)) {
50
- store.delete(key)
51
- }
52
- }
53
- },
54
- }
55
- })