@kubb/core 5.0.0-beta.62 → 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.
- package/dist/{diagnostics-D0G07LHG.d.ts → diagnostics-BqiNAWVS.d.ts} +47 -40
- package/dist/index.cjs +47 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.js +49 -54
- package/dist/index.js.map +1 -1
- package/dist/{memoryStorage-CWFzAz4o.js → memoryStorage-DWnhqUf2.js} +3 -3
- package/dist/memoryStorage-DWnhqUf2.js.map +1 -0
- package/dist/{memoryStorage-CUj1hrxa.cjs → memoryStorage-mojU6pbA.cjs} +2 -2
- package/dist/memoryStorage-mojU6pbA.cjs.map +1 -0
- package/dist/mocks.cjs +2 -2
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +3 -3
- package/dist/mocks.js +3 -3
- package/dist/mocks.js.map +1 -1
- package/package.json +4 -5
- package/dist/memoryStorage-CUj1hrxa.cjs.map +0 -1
- package/dist/memoryStorage-CWFzAz4o.js.map +0 -1
- package/src/FileManager.ts +0 -137
- package/src/FileProcessor.ts +0 -212
- package/src/KubbDriver.ts +0 -893
- package/src/Transform.ts +0 -105
- package/src/constants.ts +0 -126
- package/src/createAdapter.ts +0 -127
- package/src/createKubb.ts +0 -196
- package/src/createRenderer.ts +0 -72
- package/src/createReporter.ts +0 -134
- package/src/createStorage.ts +0 -83
- package/src/defineGenerator.ts +0 -210
- package/src/defineParser.ts +0 -62
- package/src/definePlugin.ts +0 -437
- package/src/defineResolver.ts +0 -711
- package/src/diagnostics.ts +0 -662
- package/src/index.ts +0 -20
- package/src/mocks.ts +0 -249
- package/src/reporters/cliReporter.ts +0 -89
- package/src/reporters/fileReporter.ts +0 -103
- package/src/reporters/jsonReporter.ts +0 -20
- package/src/reporters/report.ts +0 -85
- package/src/storages/fsStorage.ts +0 -82
- package/src/storages/memoryStorage.ts +0 -55
- package/src/types.ts +0 -829
- /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 `PluginDriver` 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,103 +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']`), replacing the
|
|
78
|
-
* old `--debug` flag.
|
|
79
|
-
*
|
|
80
|
-
* @note Unlike the streaming logger it replaced, it captures the collected diagnostics once a
|
|
81
|
-
* config finishes, not the live `kubb:info`/`kubb:plugin` event stream. Color is stripped so the
|
|
82
|
-
* file stays plain text even when the run is attached to a TTY.
|
|
83
|
-
*/
|
|
84
|
-
export const fileReporter = createReporter({
|
|
85
|
-
name: 'file',
|
|
86
|
-
async report(result) {
|
|
87
|
-
const { diagnostics, config } = result
|
|
88
|
-
if (diagnostics.length === 0) {
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const report = buildReport(result)
|
|
93
|
-
const header = config.name ? `# ${config.name} — ${new Date().toISOString()}` : `# ${new Date().toISOString()}`
|
|
94
|
-
const sections = [buildSummarySection(report), buildProblemSection(diagnostics), buildTimingSection(report)].filter((section) => section.length > 0)
|
|
95
|
-
const content = stripVTControlCharacters([header, ...sections.map((section) => section.join('\n'))].join('\n\n'))
|
|
96
|
-
|
|
97
|
-
const baseName = `${['kubb', config.name, Date.now()].filter(Boolean).join('-')}.log`
|
|
98
|
-
const pathName = resolve(process.cwd(), '.kubb', baseName)
|
|
99
|
-
|
|
100
|
-
await write(pathName, `${content}\n`)
|
|
101
|
-
console.error(`Debug log written to ${relative(process.cwd(), pathName)}`)
|
|
102
|
-
},
|
|
103
|
-
})
|
|
@@ -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
|
-
})
|
package/src/reporters/report.ts
DELETED
|
@@ -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
|
-
})
|