@kubb/core 5.0.0-alpha.33 → 5.0.0-alpha.35
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/{PluginDriver-BBi_41VF.d.ts → PluginDriver-D8lWvtUg.d.ts} +743 -375
- package/dist/hooks.d.ts +1 -1
- package/dist/index.cjs +1203 -932
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +22 -141
- package/dist/index.js +1220 -942
- package/dist/index.js.map +1 -1
- package/package.json +4 -5
- package/src/FileManager.ts +1 -1
- package/src/FileProcessor.ts +1 -1
- package/src/Kubb.ts +145 -38
- package/src/PluginDriver.ts +318 -40
- package/src/constants.ts +1 -1
- package/src/{build.ts → createKubb.ts} +180 -122
- package/src/createPlugin.ts +1 -0
- package/src/createRenderer.ts +57 -0
- package/src/defineGenerator.ts +57 -84
- package/src/defineLogger.ts +2 -2
- package/src/defineParser.ts +3 -2
- package/src/definePlugin.ts +95 -0
- package/src/defineResolver.ts +1 -1
- package/src/devtools.ts +1 -1
- package/src/index.ts +5 -6
- package/src/renderNode.ts +35 -0
- package/src/types.ts +275 -209
- package/src/utils/TreeNode.ts +1 -1
- package/src/utils/getBarrelFiles.ts +3 -3
- package/src/utils/getFunctionParams.ts +14 -7
- package/src/utils/isInputPath.ts +2 -2
- package/src/utils/packageJSON.ts +2 -3
- package/src/defineConfig.ts +0 -51
- package/src/definePresets.ts +0 -16
- package/src/renderNode.tsx +0 -28
- package/src/utils/getConfigs.ts +0 -16
- package/src/utils/getPreset.ts +0 -78
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
import { dirname, resolve } from 'node:path'
|
|
2
2
|
import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, getRelativePath, URLPath } from '@internals/utils'
|
|
3
|
+
import type { ExportNode, FileNode, OperationNode } from '@kubb/ast'
|
|
3
4
|
import { createExport, createFile, transform, walk } from '@kubb/ast'
|
|
4
|
-
import type { ExportNode, FileNode, OperationNode } from '@kubb/ast/types'
|
|
5
5
|
import { BARREL_FILENAME, DEFAULT_BANNER, DEFAULT_CONCURRENCY, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
|
|
6
|
+
import type { RendererFactory } from './createRenderer.ts'
|
|
7
|
+
import type { Generator } from './defineGenerator.ts'
|
|
6
8
|
import type { Parser } from './defineParser.ts'
|
|
7
9
|
import { FileProcessor } from './FileProcessor.ts'
|
|
10
|
+
import type { Kubb } from './Kubb.ts'
|
|
8
11
|
import { PluginDriver } from './PluginDriver.ts'
|
|
9
|
-
import { applyHookResult } from './renderNode.
|
|
12
|
+
import { applyHookResult } from './renderNode.ts'
|
|
10
13
|
import { fsStorage } from './storages/fsStorage.ts'
|
|
11
|
-
import type { AdapterSource, Config,
|
|
14
|
+
import type { AdapterSource, Config, GeneratorContext, KubbHooks, Plugin, PluginContext, Storage } from './types.ts'
|
|
12
15
|
import { getDiagnosticInfo } from './utils/diagnostics.ts'
|
|
13
16
|
import type { FileMetaBase } from './utils/getBarrelFiles.ts'
|
|
14
17
|
import { getBarrelFiles } from './utils/getBarrelFiles.ts'
|
|
15
18
|
import { isInputPath } from './utils/isInputPath.ts'
|
|
16
19
|
|
|
17
20
|
type BuildOptions = {
|
|
18
|
-
config:
|
|
19
|
-
|
|
21
|
+
config: Config
|
|
22
|
+
hooks?: AsyncEventEmitter<KubbHooks>
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* Full output produced by a successful or failed build.
|
|
24
27
|
*/
|
|
25
|
-
type BuildOutput = {
|
|
28
|
+
export type BuildOutput = {
|
|
26
29
|
/**
|
|
27
30
|
* Plugins that threw during installation, paired with the caught error.
|
|
28
31
|
*/
|
|
@@ -40,39 +43,26 @@ type BuildOutput = {
|
|
|
40
43
|
sources: Map<string, string>
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
/**
|
|
44
|
-
* Intermediate result returned by {@link setup} and accepted by {@link safeBuild}.
|
|
45
|
-
*/
|
|
46
46
|
type SetupResult = {
|
|
47
|
-
|
|
47
|
+
hooks: AsyncEventEmitter<KubbHooks>
|
|
48
48
|
driver: PluginDriver
|
|
49
49
|
sources: Map<string, string>
|
|
50
50
|
config: Config
|
|
51
51
|
storage: Storage | null
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
* - Validates the input path (when applicable).
|
|
58
|
-
* - Applies config defaults (`root`, `output.*`, `devtools`).
|
|
59
|
-
* - Runs the adapter (if configured) to produce the universal `InputNode`.
|
|
60
|
-
* When no adapter is supplied and `@kubb/adapter-oas` is installed as an
|
|
61
|
-
*
|
|
62
|
-
* Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
|
|
63
|
-
* via the `overrides` argument to reuse the same infrastructure across multiple runs.
|
|
64
|
-
*/
|
|
65
|
-
export async function setup(options: BuildOptions): Promise<SetupResult> {
|
|
66
|
-
const { config: userConfig, events = new AsyncEventEmitter<KubbEvents>() } = options
|
|
54
|
+
async function setup(options: BuildOptions): Promise<SetupResult> {
|
|
55
|
+
const { config: userConfig } = options
|
|
56
|
+
const hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
|
|
67
57
|
|
|
68
58
|
const sources: Map<string, string> = new Map<string, string>()
|
|
69
59
|
const diagnosticInfo = getDiagnosticInfo()
|
|
70
60
|
|
|
71
61
|
if (Array.isArray(userConfig.input)) {
|
|
72
|
-
await
|
|
62
|
+
await hooks.emit('kubb:warn', 'This feature is still under development — use with caution')
|
|
73
63
|
}
|
|
74
64
|
|
|
75
|
-
await
|
|
65
|
+
await hooks.emit('kubb:debug', {
|
|
76
66
|
date: new Date(),
|
|
77
67
|
logs: [
|
|
78
68
|
'Configuration:',
|
|
@@ -95,7 +85,7 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
|
|
|
95
85
|
if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
|
|
96
86
|
await exists(userConfig.input.path)
|
|
97
87
|
|
|
98
|
-
await
|
|
88
|
+
await hooks.emit('kubb:debug', {
|
|
99
89
|
date: new Date(),
|
|
100
90
|
logs: [`✓ Input file validated: ${userConfig.input.path}`],
|
|
101
91
|
})
|
|
@@ -118,8 +108,8 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
|
|
|
118
108
|
}
|
|
119
109
|
|
|
120
110
|
const config: Config = {
|
|
121
|
-
root: userConfig.root || process.cwd(),
|
|
122
111
|
...userConfig,
|
|
112
|
+
root: userConfig.root || process.cwd(),
|
|
123
113
|
parsers: userConfig.parsers ?? [],
|
|
124
114
|
adapter: userConfig.adapter,
|
|
125
115
|
output: {
|
|
@@ -135,17 +125,13 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
|
|
|
135
125
|
...(typeof userConfig.devtools === 'boolean' ? {} : userConfig.devtools),
|
|
136
126
|
}
|
|
137
127
|
: undefined,
|
|
138
|
-
plugins: userConfig.plugins as Config['plugins'],
|
|
128
|
+
plugins: userConfig.plugins as unknown as Config['plugins'],
|
|
139
129
|
}
|
|
140
130
|
|
|
141
|
-
// write: false is the explicit dry-run opt-out; otherwise use the provided
|
|
142
|
-
// storage or fall back to fsStorage (backwards-compatible default).
|
|
143
|
-
// Storage keys are the absolute file.path values so fsStorage() resolves
|
|
144
|
-
// them correctly regardless of the current working directory.
|
|
145
131
|
const storage: Storage | null = config.output.write === false ? null : (config.output.storage ?? fsStorage())
|
|
146
132
|
|
|
147
133
|
if (config.output.clean) {
|
|
148
|
-
await
|
|
134
|
+
await hooks.emit('kubb:debug', {
|
|
149
135
|
date: new Date(),
|
|
150
136
|
logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
|
|
151
137
|
})
|
|
@@ -153,19 +139,17 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
|
|
|
153
139
|
}
|
|
154
140
|
|
|
155
141
|
const driver = new PluginDriver(config, {
|
|
156
|
-
|
|
142
|
+
hooks,
|
|
157
143
|
concurrency: DEFAULT_CONCURRENCY,
|
|
158
144
|
})
|
|
159
145
|
|
|
160
|
-
// Run the adapter to produce the universal InputNode.
|
|
161
|
-
|
|
162
146
|
const adapter = config.adapter
|
|
163
147
|
if (!adapter) {
|
|
164
148
|
throw new Error('No adapter configured. Please provide an adapter in your kubb.config.ts.')
|
|
165
149
|
}
|
|
166
150
|
const source = inputToAdapterSource(config)
|
|
167
151
|
|
|
168
|
-
await
|
|
152
|
+
await hooks.emit('kubb:debug', {
|
|
169
153
|
date: new Date(),
|
|
170
154
|
logs: [`Running adapter: ${adapter.name}`],
|
|
171
155
|
})
|
|
@@ -173,7 +157,7 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
|
|
|
173
157
|
driver.adapter = adapter
|
|
174
158
|
driver.inputNode = await adapter.parse(source)
|
|
175
159
|
|
|
176
|
-
await
|
|
160
|
+
await hooks.emit('kubb:debug', {
|
|
177
161
|
date: new Date(),
|
|
178
162
|
logs: [
|
|
179
163
|
`✓ Adapter '${adapter.name}' resolved InputNode`,
|
|
@@ -184,51 +168,16 @@ export async function setup(options: BuildOptions): Promise<SetupResult> {
|
|
|
184
168
|
|
|
185
169
|
return {
|
|
186
170
|
config,
|
|
187
|
-
|
|
171
|
+
hooks,
|
|
188
172
|
driver,
|
|
189
173
|
sources,
|
|
190
174
|
storage,
|
|
191
175
|
}
|
|
192
176
|
}
|
|
193
177
|
|
|
194
|
-
/**
|
|
195
|
-
* Runs a full Kubb build and throws on any error or plugin failure.
|
|
196
|
-
*
|
|
197
|
-
* Internally delegates to {@link safeBuild} and rethrows collected errors.
|
|
198
|
-
* Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
|
|
199
|
-
*/
|
|
200
|
-
export async function build(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
|
|
201
|
-
const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides)
|
|
202
|
-
|
|
203
|
-
if (error) {
|
|
204
|
-
throw error
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (failedPlugins.size > 0) {
|
|
208
|
-
const errors = [...failedPlugins].map(({ error }) => error)
|
|
209
|
-
|
|
210
|
-
throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors })
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
failedPlugins,
|
|
215
|
-
files,
|
|
216
|
-
driver,
|
|
217
|
-
pluginTimings,
|
|
218
|
-
error: undefined,
|
|
219
|
-
sources,
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
178
|
/**
|
|
224
179
|
* Walks the AST and dispatches nodes to a plugin's direct AST hooks
|
|
225
180
|
* (`schema`, `operation`, `operations`).
|
|
226
|
-
*
|
|
227
|
-
* - Each hook accepts a single handler **or an array** — all entries are called in sequence.
|
|
228
|
-
* - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
|
|
229
|
-
* - Return values are handled via `applyHookResult`: React elements are rendered,
|
|
230
|
-
* `FileNode[]` are written via upsert, and `void` is a no-op (manual handling).
|
|
231
|
-
* - Barrel files are generated automatically when `output.barrelType` is set.
|
|
232
181
|
*/
|
|
233
182
|
async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promise<void> {
|
|
234
183
|
const { adapter, inputNode, resolver, driver } = context
|
|
@@ -238,59 +187,87 @@ async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promis
|
|
|
238
187
|
throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`)
|
|
239
188
|
}
|
|
240
189
|
|
|
190
|
+
function resolveRenderer(gen: Generator<any>): RendererFactory | undefined {
|
|
191
|
+
return gen.renderer === null ? undefined : (gen.renderer ?? plugin.renderer ?? context.config.renderer)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const generators = plugin.generators ?? []
|
|
241
195
|
const collectedOperations: Array<OperationNode> = []
|
|
242
196
|
|
|
197
|
+
const baseGeneratorContext = context as GeneratorContext
|
|
198
|
+
const generatorContext = {
|
|
199
|
+
...baseGeneratorContext,
|
|
200
|
+
resolver: driver.getResolver(plugin.name),
|
|
201
|
+
}
|
|
202
|
+
|
|
243
203
|
await walk(inputNode, {
|
|
244
204
|
depth: 'shallow',
|
|
245
205
|
async schema(node) {
|
|
246
|
-
if (!plugin.schema) return
|
|
247
206
|
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
248
207
|
const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
249
208
|
if (options === null) return
|
|
250
|
-
const result = await plugin.schema.call(context, transformedNode, options)
|
|
251
209
|
|
|
252
|
-
|
|
210
|
+
const ctx = { ...generatorContext, options }
|
|
211
|
+
|
|
212
|
+
for (const gen of generators) {
|
|
213
|
+
if (!gen.schema) continue
|
|
214
|
+
const result = await gen.schema(transformedNode, ctx)
|
|
215
|
+
await applyHookResult(result, driver, resolveRenderer(gen))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
await driver.hooks.emit('kubb:generate:schema', transformedNode, ctx)
|
|
253
219
|
},
|
|
254
220
|
async operation(node) {
|
|
255
221
|
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
256
222
|
const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
257
223
|
if (options !== null) {
|
|
258
224
|
collectedOperations.push(transformedNode)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
225
|
+
|
|
226
|
+
const ctx = { ...generatorContext, options }
|
|
227
|
+
|
|
228
|
+
for (const gen of generators) {
|
|
229
|
+
if (!gen.operation) continue
|
|
230
|
+
const result = await gen.operation(transformedNode, ctx)
|
|
231
|
+
await applyHookResult(result, driver, resolveRenderer(gen))
|
|
262
232
|
}
|
|
233
|
+
|
|
234
|
+
await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
|
|
263
235
|
}
|
|
264
236
|
},
|
|
265
237
|
})
|
|
266
238
|
|
|
267
|
-
if (
|
|
268
|
-
const
|
|
239
|
+
if (collectedOperations.length > 0) {
|
|
240
|
+
const ctx = { ...generatorContext, options: plugin.options }
|
|
269
241
|
|
|
270
|
-
|
|
242
|
+
for (const gen of generators) {
|
|
243
|
+
if (!gen.operations) continue
|
|
244
|
+
const result = await gen.operations(collectedOperations, ctx)
|
|
245
|
+
await applyHookResult(result, driver, resolveRenderer(gen))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await driver.hooks.emit('kubb:generate:operations', collectedOperations, ctx)
|
|
271
249
|
}
|
|
272
250
|
}
|
|
273
251
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
*
|
|
277
|
-
* - Installs each plugin in order, recording failures in `failedPlugins`.
|
|
278
|
-
* - Generates the root barrel file when `output.barrelType` is set.
|
|
279
|
-
* - Writes all files through the driver's FileManager and FileProcessor.
|
|
280
|
-
*
|
|
281
|
-
* Returns a {@link BuildOutput} even on failure — inspect `error` and
|
|
282
|
-
* `failedPlugins` to determine whether the build succeeded.
|
|
283
|
-
*/
|
|
284
|
-
export async function safeBuild(options: BuildOptions, overrides?: SetupResult): Promise<BuildOutput> {
|
|
285
|
-
const setupResult = overrides ? overrides : await setup(options)
|
|
286
|
-
const { driver, events, sources, storage } = setupResult
|
|
252
|
+
async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
253
|
+
const { driver, hooks, sources, storage } = setupResult
|
|
287
254
|
|
|
288
255
|
const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
|
|
289
|
-
// in ms
|
|
290
256
|
const pluginTimings = new Map<string, number>()
|
|
291
257
|
const config = driver.config
|
|
292
258
|
|
|
293
259
|
try {
|
|
260
|
+
await driver.emitSetupHooks()
|
|
261
|
+
|
|
262
|
+
if (driver.adapter && driver.inputNode) {
|
|
263
|
+
await hooks.emit('kubb:build:start', {
|
|
264
|
+
config,
|
|
265
|
+
adapter: driver.adapter,
|
|
266
|
+
inputNode: driver.inputNode,
|
|
267
|
+
getPlugin: (name) => driver.getPlugin(name),
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
294
271
|
for (const plugin of driver.plugins.values()) {
|
|
295
272
|
const context = driver.getContext(plugin)
|
|
296
273
|
const hrStart = process.hrtime()
|
|
@@ -300,18 +277,16 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
300
277
|
try {
|
|
301
278
|
const timestamp = new Date()
|
|
302
279
|
|
|
303
|
-
await
|
|
280
|
+
await hooks.emit('kubb:plugin:start', plugin)
|
|
304
281
|
|
|
305
|
-
await
|
|
282
|
+
await hooks.emit('kubb:debug', {
|
|
306
283
|
date: timestamp,
|
|
307
284
|
logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
|
|
308
285
|
})
|
|
309
286
|
|
|
310
|
-
// Call buildStart() for any custom plugin logic
|
|
311
287
|
await plugin.buildStart.call(context)
|
|
312
288
|
|
|
313
|
-
|
|
314
|
-
if (plugin.schema || plugin.operation || plugin.operations) {
|
|
289
|
+
if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) {
|
|
315
290
|
await runPluginAstHooks(plugin, context)
|
|
316
291
|
}
|
|
317
292
|
|
|
@@ -328,9 +303,9 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
328
303
|
const duration = getElapsedMs(hrStart)
|
|
329
304
|
pluginTimings.set(plugin.name, duration)
|
|
330
305
|
|
|
331
|
-
await
|
|
306
|
+
await hooks.emit('kubb:plugin:end', plugin, { duration, success: true })
|
|
332
307
|
|
|
333
|
-
await
|
|
308
|
+
await hooks.emit('kubb:debug', {
|
|
334
309
|
date: new Date(),
|
|
335
310
|
logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
|
|
336
311
|
})
|
|
@@ -339,13 +314,13 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
339
314
|
const errorTimestamp = new Date()
|
|
340
315
|
const duration = getElapsedMs(hrStart)
|
|
341
316
|
|
|
342
|
-
await
|
|
317
|
+
await hooks.emit('kubb:plugin:end', plugin, {
|
|
343
318
|
duration,
|
|
344
319
|
success: false,
|
|
345
320
|
error,
|
|
346
321
|
})
|
|
347
322
|
|
|
348
|
-
await
|
|
323
|
+
await hooks.emit('kubb:debug', {
|
|
349
324
|
date: errorTimestamp,
|
|
350
325
|
logs: [
|
|
351
326
|
'✗ Plugin start failed',
|
|
@@ -365,7 +340,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
365
340
|
const rootPath = resolve(root, config.output.path, BARREL_FILENAME)
|
|
366
341
|
const rootDir = dirname(rootPath)
|
|
367
342
|
|
|
368
|
-
await
|
|
343
|
+
await hooks.emit('kubb:debug', {
|
|
369
344
|
date: new Date(),
|
|
370
345
|
logs: ['Generating barrel file', ` • Type: ${config.output.barrelType}`, ` • Path: ${rootPath}`],
|
|
371
346
|
})
|
|
@@ -374,7 +349,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
374
349
|
return file.sources.some((source) => source.isIndexable)
|
|
375
350
|
})
|
|
376
351
|
|
|
377
|
-
await
|
|
352
|
+
await hooks.emit('kubb:debug', {
|
|
378
353
|
date: new Date(),
|
|
379
354
|
logs: [`Found ${barrelFiles.length} indexable files for barrel export`],
|
|
380
355
|
})
|
|
@@ -395,7 +370,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
395
370
|
|
|
396
371
|
driver.fileManager.upsert(rootFile)
|
|
397
372
|
|
|
398
|
-
await
|
|
373
|
+
await hooks.emit('kubb:debug', {
|
|
399
374
|
date: new Date(),
|
|
400
375
|
logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`],
|
|
401
376
|
})
|
|
@@ -403,7 +378,6 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
403
378
|
|
|
404
379
|
const files = driver.fileManager.files
|
|
405
380
|
|
|
406
|
-
// Build a parsers map from config.parsers
|
|
407
381
|
const parsersMap = new Map<FileNode['extname'], Parser>()
|
|
408
382
|
for (const parser of config.parsers) {
|
|
409
383
|
if (parser.extNames) {
|
|
@@ -415,7 +389,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
415
389
|
|
|
416
390
|
const fileProcessor = new FileProcessor()
|
|
417
391
|
|
|
418
|
-
await
|
|
392
|
+
await hooks.emit('kubb:debug', {
|
|
419
393
|
date: new Date(),
|
|
420
394
|
logs: [`Writing ${files.length} files...`],
|
|
421
395
|
})
|
|
@@ -424,10 +398,10 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
424
398
|
parsers: parsersMap,
|
|
425
399
|
extension: config.output.extension,
|
|
426
400
|
onStart: async (processingFiles) => {
|
|
427
|
-
await
|
|
401
|
+
await hooks.emit('kubb:files:processing:start', processingFiles)
|
|
428
402
|
},
|
|
429
403
|
onUpdate: async ({ file, source, processed, total, percentage }) => {
|
|
430
|
-
await
|
|
404
|
+
await hooks.emit('kubb:file:processing:update', {
|
|
431
405
|
file,
|
|
432
406
|
source,
|
|
433
407
|
processed,
|
|
@@ -436,22 +410,19 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
436
410
|
config,
|
|
437
411
|
})
|
|
438
412
|
if (source) {
|
|
439
|
-
// Use the absolute file.path as the storage key so fsStorage resolves
|
|
440
|
-
// it correctly regardless of the current working directory.
|
|
441
413
|
await storage?.setItem(file.path, source)
|
|
442
414
|
sources.set(file.path, source)
|
|
443
415
|
}
|
|
444
416
|
},
|
|
445
417
|
onEnd: async (processedFiles) => {
|
|
446
|
-
await
|
|
447
|
-
await
|
|
418
|
+
await hooks.emit('kubb:files:processing:end', processedFiles)
|
|
419
|
+
await hooks.emit('kubb:debug', {
|
|
448
420
|
date: new Date(),
|
|
449
421
|
logs: [`✓ File write process completed for ${processedFiles.length} files`],
|
|
450
422
|
})
|
|
451
423
|
},
|
|
452
424
|
})
|
|
453
425
|
|
|
454
|
-
// Call buildEnd() on each plugin after all files are written
|
|
455
426
|
for (const plugin of driver.plugins.values()) {
|
|
456
427
|
if (plugin.buildEnd) {
|
|
457
428
|
const context = driver.getContext(plugin)
|
|
@@ -459,6 +430,12 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
459
430
|
}
|
|
460
431
|
}
|
|
461
432
|
|
|
433
|
+
await hooks.emit('kubb:build:end', {
|
|
434
|
+
files,
|
|
435
|
+
config,
|
|
436
|
+
outputDir: resolve(config.root, config.output.path),
|
|
437
|
+
})
|
|
438
|
+
|
|
462
439
|
return {
|
|
463
440
|
failedPlugins,
|
|
464
441
|
files,
|
|
@@ -475,6 +452,31 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
475
452
|
error: error as Error,
|
|
476
453
|
sources,
|
|
477
454
|
}
|
|
455
|
+
} finally {
|
|
456
|
+
driver.dispose()
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function build(setupResult: SetupResult): Promise<BuildOutput> {
|
|
461
|
+
const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult)
|
|
462
|
+
|
|
463
|
+
if (error) {
|
|
464
|
+
throw error
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (failedPlugins.size > 0) {
|
|
468
|
+
const errors = [...failedPlugins].map(({ error }) => error)
|
|
469
|
+
|
|
470
|
+
throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors })
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
failedPlugins,
|
|
475
|
+
files,
|
|
476
|
+
driver,
|
|
477
|
+
pluginTimings,
|
|
478
|
+
error: undefined,
|
|
479
|
+
sources,
|
|
478
480
|
}
|
|
479
481
|
}
|
|
480
482
|
|
|
@@ -524,10 +526,6 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, dri
|
|
|
524
526
|
})
|
|
525
527
|
}
|
|
526
528
|
|
|
527
|
-
/**
|
|
528
|
-
* Maps the resolved `Config['input']` shape into an `AdapterSource` that
|
|
529
|
-
* the adapter's `parse()` can consume.
|
|
530
|
-
*/
|
|
531
529
|
function inputToAdapterSource(config: Config): AdapterSource {
|
|
532
530
|
if (Array.isArray(config.input)) {
|
|
533
531
|
return {
|
|
@@ -547,3 +545,63 @@ function inputToAdapterSource(config: Config): AdapterSource {
|
|
|
547
545
|
const resolved = resolve(config.root, config.input.path)
|
|
548
546
|
return { type: 'path', path: resolved }
|
|
549
547
|
}
|
|
548
|
+
|
|
549
|
+
type KubbOptions = {
|
|
550
|
+
config: Config
|
|
551
|
+
hooks?: AsyncEventEmitter<KubbHooks>
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Creates a Kubb instance bound to a single config entry.
|
|
556
|
+
*
|
|
557
|
+
* The instance holds shared state (`hooks`, `sources`, `driver`, `config`) across the
|
|
558
|
+
* `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
|
|
559
|
+
* calling `setup()` or `build()`.
|
|
560
|
+
*
|
|
561
|
+
* @example
|
|
562
|
+
* ```ts
|
|
563
|
+
* const kubb = createKubb({ config })
|
|
564
|
+
*
|
|
565
|
+
* kubb.hooks.on('kubb:plugin:end', (plugin, { duration }) => {
|
|
566
|
+
* console.log(`${plugin.name} completed in ${duration}ms`)
|
|
567
|
+
* })
|
|
568
|
+
*
|
|
569
|
+
* const { files, failedPlugins } = await kubb.safeBuild()
|
|
570
|
+
* ```
|
|
571
|
+
*/
|
|
572
|
+
export function createKubb(options: KubbOptions): Kubb {
|
|
573
|
+
const hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
|
|
574
|
+
let setupResult: SetupResult | undefined
|
|
575
|
+
|
|
576
|
+
const instance: Kubb = {
|
|
577
|
+
get hooks() {
|
|
578
|
+
return hooks
|
|
579
|
+
},
|
|
580
|
+
get sources() {
|
|
581
|
+
return setupResult?.sources ?? new Map()
|
|
582
|
+
},
|
|
583
|
+
get driver() {
|
|
584
|
+
return setupResult?.driver
|
|
585
|
+
},
|
|
586
|
+
get config() {
|
|
587
|
+
return setupResult?.config
|
|
588
|
+
},
|
|
589
|
+
async setup() {
|
|
590
|
+
setupResult = await setup({ config: options.config, hooks })
|
|
591
|
+
},
|
|
592
|
+
async build() {
|
|
593
|
+
if (!setupResult) {
|
|
594
|
+
await instance.setup()
|
|
595
|
+
}
|
|
596
|
+
return build(setupResult!)
|
|
597
|
+
},
|
|
598
|
+
async safeBuild() {
|
|
599
|
+
if (!setupResult) {
|
|
600
|
+
await instance.setup()
|
|
601
|
+
}
|
|
602
|
+
return safeBuild(setupResult!)
|
|
603
|
+
},
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return instance
|
|
607
|
+
}
|
package/src/createPlugin.ts
CHANGED
|
@@ -22,6 +22,7 @@ type PluginBuilder<T extends PluginFactoryOptions = PluginFactoryOptions> = (opt
|
|
|
22
22
|
* // instantiate
|
|
23
23
|
* const plugin = myPlugin({ output: { path: 'src/gen' } })
|
|
24
24
|
* ```
|
|
25
|
+
* @deprecated use definePlugin instead
|
|
25
26
|
*/
|
|
26
27
|
export function createPlugin<T extends PluginFactoryOptions = PluginFactoryOptions>(
|
|
27
28
|
build: PluginBuilder<T>,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { FileNode } from '@kubb/ast'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal interface any Kubb renderer must satisfy.
|
|
5
|
+
*
|
|
6
|
+
* The generic `TElement` is the type of the element the renderer accepts —
|
|
7
|
+
* e.g. `KubbReactElement` for `@kubb/renderer-jsx`, or a custom type for
|
|
8
|
+
* your own renderer. Defaults to `unknown` so that generators which do not
|
|
9
|
+
* care about the element type continue to work without specifying it.
|
|
10
|
+
*
|
|
11
|
+
* This allows core to drive rendering without a hard dependency on
|
|
12
|
+
* `@kubb/renderer-jsx` or any specific renderer implementation.
|
|
13
|
+
*/
|
|
14
|
+
export type Renderer<TElement = unknown> = {
|
|
15
|
+
render(element: TElement): Promise<void>
|
|
16
|
+
unmount(error?: Error | number | null): void
|
|
17
|
+
readonly files: Array<FileNode>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A factory function that produces a fresh {@link Renderer} per render.
|
|
22
|
+
*
|
|
23
|
+
* Generators use this to declare which renderer handles their output.
|
|
24
|
+
*/
|
|
25
|
+
export type RendererFactory<TElement = unknown> = () => Renderer<TElement>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates a renderer factory for use in generator definitions.
|
|
29
|
+
*
|
|
30
|
+
* Wrap your renderer factory function with this helper to register it as the
|
|
31
|
+
* renderer for a generator. Core will call this factory once per render cycle
|
|
32
|
+
* to obtain a fresh renderer instance.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* // packages/renderer-jsx/src/index.ts
|
|
37
|
+
* export const jsxRenderer = createRenderer(() => {
|
|
38
|
+
* const runtime = new Runtime()
|
|
39
|
+
* return {
|
|
40
|
+
* async render(element) { await runtime.render(element) },
|
|
41
|
+
* get files() { return runtime.nodes },
|
|
42
|
+
* unmount(error) { runtime.unmount(error) },
|
|
43
|
+
* }
|
|
44
|
+
* })
|
|
45
|
+
*
|
|
46
|
+
* // packages/plugin-zod/src/generators/zodGenerator.tsx
|
|
47
|
+
* import { jsxRenderer } from '@kubb/renderer-jsx'
|
|
48
|
+
* export const zodGenerator = defineGenerator<PluginZod>({
|
|
49
|
+
* name: 'zod',
|
|
50
|
+
* renderer: jsxRenderer,
|
|
51
|
+
* schema(node, options) { return <File ...>...</File> },
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function createRenderer<TElement = unknown>(factory: RendererFactory<TElement>): RendererFactory<TElement> {
|
|
56
|
+
return factory
|
|
57
|
+
}
|