@kubb/core 5.0.0-alpha.4 → 5.0.0-alpha.40
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-BQwm8hDd.cjs +1729 -0
- package/dist/PluginDriver-BQwm8hDd.cjs.map +1 -0
- package/dist/PluginDriver-CgXFtmNP.js +1617 -0
- package/dist/PluginDriver-CgXFtmNP.js.map +1 -0
- package/dist/index.cjs +918 -1898
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +267 -264
- package/dist/index.js +897 -1860
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +164 -0
- package/dist/mocks.cjs.map +1 -0
- package/dist/mocks.d.ts +74 -0
- package/dist/mocks.js +159 -0
- package/dist/mocks.js.map +1 -0
- package/dist/types-PLXOrxov.d.ts +2151 -0
- package/package.json +11 -14
- package/src/FileManager.ts +131 -0
- package/src/FileProcessor.ts +84 -0
- package/src/Kubb.ts +174 -85
- package/src/PluginDriver.ts +941 -0
- package/src/constants.ts +33 -43
- package/src/createAdapter.ts +25 -0
- package/src/createKubb.ts +607 -0
- package/src/createPlugin.ts +31 -0
- package/src/createRenderer.ts +57 -0
- package/src/createStorage.ts +58 -0
- package/src/defineGenerator.ts +88 -100
- package/src/defineLogger.ts +13 -3
- package/src/defineParser.ts +45 -0
- package/src/definePlugin.ts +90 -7
- package/src/defineResolver.ts +453 -0
- package/src/devtools.ts +14 -14
- package/src/index.ts +12 -17
- package/src/mocks.ts +234 -0
- package/src/renderNode.ts +35 -0
- package/src/storages/fsStorage.ts +29 -9
- package/src/storages/memoryStorage.ts +2 -2
- package/src/types.ts +821 -152
- package/src/utils/TreeNode.ts +47 -9
- package/src/utils/diagnostics.ts +4 -1
- package/src/utils/executeStrategies.ts +16 -13
- package/src/utils/getBarrelFiles.ts +88 -15
- package/src/utils/isInputPath.ts +8 -0
- package/src/utils/packageJSON.ts +75 -0
- package/dist/chunk-ByKO4r7w.cjs +0 -38
- package/dist/hooks.cjs +0 -50
- package/dist/hooks.cjs.map +0 -1
- package/dist/hooks.d.ts +0 -49
- package/dist/hooks.js +0 -46
- package/dist/hooks.js.map +0 -1
- package/dist/types-Bbh1o0yW.d.ts +0 -1057
- package/src/BarrelManager.ts +0 -74
- package/src/PackageManager.ts +0 -180
- package/src/PluginManager.ts +0 -668
- package/src/PromiseManager.ts +0 -40
- package/src/build.ts +0 -420
- package/src/config.ts +0 -56
- package/src/defineAdapter.ts +0 -22
- package/src/defineStorage.ts +0 -56
- package/src/errors.ts +0 -1
- package/src/hooks/index.ts +0 -8
- package/src/hooks/useKubb.ts +0 -22
- package/src/hooks/useMode.ts +0 -11
- package/src/hooks/usePlugin.ts +0 -11
- package/src/hooks/usePluginManager.ts +0 -11
- package/src/utils/FunctionParams.ts +0 -155
- package/src/utils/formatters.ts +0 -56
- package/src/utils/getConfigs.ts +0 -30
- package/src/utils/getPlugins.ts +0 -23
- package/src/utils/linters.ts +0 -25
- package/src/utils/resolveOptions.ts +0 -93
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path'
|
|
2
|
+
import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, getRelativePath, URLPath } from '@internals/utils'
|
|
3
|
+
import type { ExportNode, FileNode, OperationNode } from '@kubb/ast'
|
|
4
|
+
import { createExport, createFile, transform, walk } from '@kubb/ast'
|
|
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'
|
|
8
|
+
import type { Parser } from './defineParser.ts'
|
|
9
|
+
import { FileProcessor } from './FileProcessor.ts'
|
|
10
|
+
import type { Kubb } from './Kubb.ts'
|
|
11
|
+
import { PluginDriver } from './PluginDriver.ts'
|
|
12
|
+
import { applyHookResult } from './renderNode.ts'
|
|
13
|
+
import { fsStorage } from './storages/fsStorage.ts'
|
|
14
|
+
import type { AdapterSource, Config, GeneratorContext, KubbHooks, Plugin, PluginContext, Storage } from './types.ts'
|
|
15
|
+
import { getDiagnosticInfo } from './utils/diagnostics.ts'
|
|
16
|
+
import type { FileMetaBase } from './utils/getBarrelFiles.ts'
|
|
17
|
+
import { getBarrelFiles } from './utils/getBarrelFiles.ts'
|
|
18
|
+
import { isInputPath } from './utils/isInputPath.ts'
|
|
19
|
+
|
|
20
|
+
type BuildOptions = {
|
|
21
|
+
config: Config
|
|
22
|
+
hooks?: AsyncEventEmitter<KubbHooks>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Full output produced by a successful or failed build.
|
|
27
|
+
*/
|
|
28
|
+
export type BuildOutput = {
|
|
29
|
+
/**
|
|
30
|
+
* Plugins that threw during installation, paired with the caught error.
|
|
31
|
+
*/
|
|
32
|
+
failedPlugins: Set<{ plugin: Plugin; error: Error }>
|
|
33
|
+
files: Array<FileNode>
|
|
34
|
+
driver: PluginDriver
|
|
35
|
+
/**
|
|
36
|
+
* Elapsed time in milliseconds for each plugin, keyed by plugin name.
|
|
37
|
+
*/
|
|
38
|
+
pluginTimings: Map<string, number>
|
|
39
|
+
error?: Error
|
|
40
|
+
/**
|
|
41
|
+
* Raw generated source, keyed by absolute file path.
|
|
42
|
+
*/
|
|
43
|
+
sources: Map<string, string>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type SetupResult = {
|
|
47
|
+
hooks: AsyncEventEmitter<KubbHooks>
|
|
48
|
+
driver: PluginDriver
|
|
49
|
+
sources: Map<string, string>
|
|
50
|
+
config: Config
|
|
51
|
+
storage: Storage | null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function setup(options: BuildOptions): Promise<SetupResult> {
|
|
55
|
+
const { config: userConfig } = options
|
|
56
|
+
const hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
|
|
57
|
+
|
|
58
|
+
const sources: Map<string, string> = new Map<string, string>()
|
|
59
|
+
const diagnosticInfo = getDiagnosticInfo()
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(userConfig.input)) {
|
|
62
|
+
await hooks.emit('kubb:warn', 'This feature is still under development — use with caution')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await hooks.emit('kubb:debug', {
|
|
66
|
+
date: new Date(),
|
|
67
|
+
logs: [
|
|
68
|
+
'Configuration:',
|
|
69
|
+
` • Name: ${userConfig.name || 'unnamed'}`,
|
|
70
|
+
` • Root: ${userConfig.root || process.cwd()}`,
|
|
71
|
+
` • Output: ${userConfig.output?.path || 'not specified'}`,
|
|
72
|
+
` • Plugins: ${userConfig.plugins?.length || 0}`,
|
|
73
|
+
'Output Settings:',
|
|
74
|
+
` • Storage: ${userConfig.output?.storage ? `custom(${userConfig.output.storage.name})` : userConfig.output?.write === false ? 'disabled' : 'filesystem (default)'}`,
|
|
75
|
+
` • Formatter: ${userConfig.output?.format || 'none'}`,
|
|
76
|
+
` • Linter: ${userConfig.output?.lint || 'none'}`,
|
|
77
|
+
'Environment:',
|
|
78
|
+
Object.entries(diagnosticInfo)
|
|
79
|
+
.map(([key, value]) => ` • ${key}: ${value}`)
|
|
80
|
+
.join('\n'),
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
|
|
86
|
+
await exists(userConfig.input.path)
|
|
87
|
+
|
|
88
|
+
await hooks.emit('kubb:debug', {
|
|
89
|
+
date: new Date(),
|
|
90
|
+
logs: [`✓ Input file validated: ${userConfig.input.path}`],
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
} catch (caughtError) {
|
|
94
|
+
if (isInputPath(userConfig)) {
|
|
95
|
+
const error = caughtError as Error
|
|
96
|
+
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`,
|
|
99
|
+
{
|
|
100
|
+
cause: error,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!userConfig.adapter) {
|
|
107
|
+
throw new Error('Adapter should be defined')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const config: Config = {
|
|
111
|
+
...userConfig,
|
|
112
|
+
root: userConfig.root || process.cwd(),
|
|
113
|
+
parsers: userConfig.parsers ?? [],
|
|
114
|
+
adapter: userConfig.adapter,
|
|
115
|
+
output: {
|
|
116
|
+
write: true,
|
|
117
|
+
barrelType: 'named',
|
|
118
|
+
extension: DEFAULT_EXTENSION,
|
|
119
|
+
defaultBanner: DEFAULT_BANNER,
|
|
120
|
+
...userConfig.output,
|
|
121
|
+
},
|
|
122
|
+
devtools: userConfig.devtools
|
|
123
|
+
? {
|
|
124
|
+
studioUrl: DEFAULT_STUDIO_URL,
|
|
125
|
+
...(typeof userConfig.devtools === 'boolean' ? {} : userConfig.devtools),
|
|
126
|
+
}
|
|
127
|
+
: undefined,
|
|
128
|
+
plugins: userConfig.plugins as unknown as Config['plugins'],
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const storage: Storage | null = config.output.write === false ? null : (config.output.storage ?? fsStorage())
|
|
132
|
+
|
|
133
|
+
if (config.output.clean) {
|
|
134
|
+
await hooks.emit('kubb:debug', {
|
|
135
|
+
date: new Date(),
|
|
136
|
+
logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
|
|
137
|
+
})
|
|
138
|
+
await storage?.clear(resolve(config.root, config.output.path))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const driver = new PluginDriver(config, {
|
|
142
|
+
hooks,
|
|
143
|
+
concurrency: DEFAULT_CONCURRENCY,
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const adapter = config.adapter
|
|
147
|
+
if (!adapter) {
|
|
148
|
+
throw new Error('No adapter configured. Please provide an adapter in your kubb.config.ts.')
|
|
149
|
+
}
|
|
150
|
+
const source = inputToAdapterSource(config)
|
|
151
|
+
|
|
152
|
+
await hooks.emit('kubb:debug', {
|
|
153
|
+
date: new Date(),
|
|
154
|
+
logs: [`Running adapter: ${adapter.name}`],
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
driver.adapter = adapter
|
|
158
|
+
driver.inputNode = await adapter.parse(source)
|
|
159
|
+
|
|
160
|
+
await hooks.emit('kubb:debug', {
|
|
161
|
+
date: new Date(),
|
|
162
|
+
logs: [
|
|
163
|
+
`✓ Adapter '${adapter.name}' resolved InputNode`,
|
|
164
|
+
` • Schemas: ${driver.inputNode.schemas.length}`,
|
|
165
|
+
` • Operations: ${driver.inputNode.operations.length}`,
|
|
166
|
+
],
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
config,
|
|
171
|
+
hooks,
|
|
172
|
+
driver,
|
|
173
|
+
sources,
|
|
174
|
+
storage,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Walks the AST and dispatches nodes to a plugin's direct AST hooks
|
|
180
|
+
* (`schema`, `operation`, `operations`).
|
|
181
|
+
*/
|
|
182
|
+
async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promise<void> {
|
|
183
|
+
const { adapter, inputNode, resolver, driver } = context
|
|
184
|
+
const { exclude, include, override } = plugin.options
|
|
185
|
+
|
|
186
|
+
if (!adapter || !inputNode) {
|
|
187
|
+
throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function resolveRenderer(gen: Generator): RendererFactory | undefined {
|
|
191
|
+
return gen.renderer === null ? undefined : (gen.renderer ?? plugin.renderer ?? context.config.renderer)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const generators = plugin.generators ?? []
|
|
195
|
+
const collectedOperations: Array<OperationNode> = []
|
|
196
|
+
|
|
197
|
+
const baseGeneratorContext = context as GeneratorContext
|
|
198
|
+
const generatorContext = {
|
|
199
|
+
...baseGeneratorContext,
|
|
200
|
+
resolver: driver.getResolver(plugin.name),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await walk(inputNode, {
|
|
204
|
+
depth: 'shallow',
|
|
205
|
+
async schema(node) {
|
|
206
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
207
|
+
const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
208
|
+
if (options === null) return
|
|
209
|
+
|
|
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)
|
|
219
|
+
},
|
|
220
|
+
async operation(node) {
|
|
221
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
222
|
+
const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
223
|
+
if (options !== null) {
|
|
224
|
+
collectedOperations.push(transformedNode)
|
|
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))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
if (collectedOperations.length > 0) {
|
|
240
|
+
const ctx = { ...generatorContext, options: plugin.options }
|
|
241
|
+
|
|
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)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
253
|
+
const { driver, hooks, sources, storage } = setupResult
|
|
254
|
+
|
|
255
|
+
const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
|
|
256
|
+
const pluginTimings = new Map<string, number>()
|
|
257
|
+
const config = driver.config
|
|
258
|
+
|
|
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
|
+
|
|
271
|
+
for (const plugin of driver.plugins.values()) {
|
|
272
|
+
const context = driver.getContext(plugin)
|
|
273
|
+
const hrStart = process.hrtime()
|
|
274
|
+
const { output } = plugin.options ?? {}
|
|
275
|
+
const root = resolve(config.root, config.output.path)
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const timestamp = new Date()
|
|
279
|
+
|
|
280
|
+
await hooks.emit('kubb:plugin:start', plugin)
|
|
281
|
+
|
|
282
|
+
await hooks.emit('kubb:debug', {
|
|
283
|
+
date: timestamp,
|
|
284
|
+
logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
await plugin.buildStart.call(context)
|
|
288
|
+
|
|
289
|
+
if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) {
|
|
290
|
+
await runPluginAstHooks(plugin, context)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (output) {
|
|
294
|
+
const barrelFiles = await getBarrelFiles(driver.fileManager.files, {
|
|
295
|
+
type: output.barrelType ?? 'named',
|
|
296
|
+
root,
|
|
297
|
+
output,
|
|
298
|
+
meta: { pluginName: plugin.name },
|
|
299
|
+
})
|
|
300
|
+
await context.upsertFile(...barrelFiles)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const duration = getElapsedMs(hrStart)
|
|
304
|
+
pluginTimings.set(plugin.name, duration)
|
|
305
|
+
|
|
306
|
+
await hooks.emit('kubb:plugin:end', plugin, { duration, success: true })
|
|
307
|
+
|
|
308
|
+
await hooks.emit('kubb:debug', {
|
|
309
|
+
date: new Date(),
|
|
310
|
+
logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
|
|
311
|
+
})
|
|
312
|
+
} catch (caughtError) {
|
|
313
|
+
const error = caughtError as Error
|
|
314
|
+
const errorTimestamp = new Date()
|
|
315
|
+
const duration = getElapsedMs(hrStart)
|
|
316
|
+
|
|
317
|
+
await hooks.emit('kubb:plugin:end', plugin, {
|
|
318
|
+
duration,
|
|
319
|
+
success: false,
|
|
320
|
+
error,
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
await hooks.emit('kubb:debug', {
|
|
324
|
+
date: errorTimestamp,
|
|
325
|
+
logs: [
|
|
326
|
+
'✗ Plugin start failed',
|
|
327
|
+
` • Plugin Name: ${plugin.name}`,
|
|
328
|
+
` • Error: ${error.constructor.name} - ${error.message}`,
|
|
329
|
+
' • Stack Trace:',
|
|
330
|
+
error.stack || 'No stack trace available',
|
|
331
|
+
],
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
failedPlugins.add({ plugin, error })
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (config.output.barrelType) {
|
|
339
|
+
const root = resolve(config.root)
|
|
340
|
+
const rootPath = resolve(root, config.output.path, BARREL_FILENAME)
|
|
341
|
+
const rootDir = dirname(rootPath)
|
|
342
|
+
|
|
343
|
+
await hooks.emit('kubb:debug', {
|
|
344
|
+
date: new Date(),
|
|
345
|
+
logs: ['Generating barrel file', ` • Type: ${config.output.barrelType}`, ` • Path: ${rootPath}`],
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
const barrelFiles = driver.fileManager.files.filter((file) => {
|
|
349
|
+
return file.sources.some((source) => source.isIndexable)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
await hooks.emit('kubb:debug', {
|
|
353
|
+
date: new Date(),
|
|
354
|
+
logs: [`Found ${barrelFiles.length} indexable files for barrel export`],
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
const existingBarrel = driver.fileManager.files.find((f) => f.path === rootPath)
|
|
358
|
+
const existingExports = new Set(
|
|
359
|
+
existingBarrel?.exports?.flatMap((e) => (Array.isArray(e.name) ? e.name : [e.name])).filter((n): n is string => Boolean(n)) ?? [],
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
const rootFile = createFile<object>({
|
|
363
|
+
path: rootPath,
|
|
364
|
+
baseName: BARREL_FILENAME,
|
|
365
|
+
exports: buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }).map((e) => createExport(e)),
|
|
366
|
+
sources: [],
|
|
367
|
+
imports: [],
|
|
368
|
+
meta: {},
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
driver.fileManager.upsert(rootFile)
|
|
372
|
+
|
|
373
|
+
await hooks.emit('kubb:debug', {
|
|
374
|
+
date: new Date(),
|
|
375
|
+
logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`],
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const files = driver.fileManager.files
|
|
380
|
+
|
|
381
|
+
const parsersMap = new Map<FileNode['extname'], Parser>()
|
|
382
|
+
for (const parser of config.parsers) {
|
|
383
|
+
if (parser.extNames) {
|
|
384
|
+
for (const extname of parser.extNames) {
|
|
385
|
+
parsersMap.set(extname, parser)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const fileProcessor = new FileProcessor()
|
|
391
|
+
|
|
392
|
+
await hooks.emit('kubb:debug', {
|
|
393
|
+
date: new Date(),
|
|
394
|
+
logs: [`Writing ${files.length} files...`],
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
await fileProcessor.run(files, {
|
|
398
|
+
parsers: parsersMap,
|
|
399
|
+
extension: config.output.extension,
|
|
400
|
+
onStart: async (processingFiles) => {
|
|
401
|
+
await hooks.emit('kubb:files:processing:start', processingFiles)
|
|
402
|
+
},
|
|
403
|
+
onUpdate: async ({ file, source, processed, total, percentage }) => {
|
|
404
|
+
await hooks.emit('kubb:file:processing:update', {
|
|
405
|
+
file,
|
|
406
|
+
source,
|
|
407
|
+
processed,
|
|
408
|
+
total,
|
|
409
|
+
percentage,
|
|
410
|
+
config,
|
|
411
|
+
})
|
|
412
|
+
if (source) {
|
|
413
|
+
await storage?.setItem(file.path, source)
|
|
414
|
+
sources.set(file.path, source)
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
onEnd: async (processedFiles) => {
|
|
418
|
+
await hooks.emit('kubb:files:processing:end', processedFiles)
|
|
419
|
+
await hooks.emit('kubb:debug', {
|
|
420
|
+
date: new Date(),
|
|
421
|
+
logs: [`✓ File write process completed for ${processedFiles.length} files`],
|
|
422
|
+
})
|
|
423
|
+
},
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
for (const plugin of driver.plugins.values()) {
|
|
427
|
+
if (plugin.buildEnd) {
|
|
428
|
+
const context = driver.getContext(plugin)
|
|
429
|
+
await plugin.buildEnd.call(context)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
await hooks.emit('kubb:build:end', {
|
|
434
|
+
files,
|
|
435
|
+
config,
|
|
436
|
+
outputDir: resolve(config.root, config.output.path),
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
failedPlugins,
|
|
441
|
+
files,
|
|
442
|
+
driver,
|
|
443
|
+
pluginTimings,
|
|
444
|
+
sources,
|
|
445
|
+
}
|
|
446
|
+
} catch (error) {
|
|
447
|
+
return {
|
|
448
|
+
failedPlugins,
|
|
449
|
+
files: [],
|
|
450
|
+
driver,
|
|
451
|
+
pluginTimings,
|
|
452
|
+
error: error as Error,
|
|
453
|
+
sources,
|
|
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,
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
type BuildBarrelExportsParams = {
|
|
484
|
+
barrelFiles: FileNode[]
|
|
485
|
+
rootDir: string
|
|
486
|
+
existingExports: Set<string>
|
|
487
|
+
config: Config
|
|
488
|
+
driver: PluginDriver
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }: BuildBarrelExportsParams): ExportNode[] {
|
|
492
|
+
const pluginNameMap = new Map<string, Plugin>()
|
|
493
|
+
for (const plugin of driver.plugins.values()) {
|
|
494
|
+
pluginNameMap.set(plugin.name, plugin)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return barrelFiles.flatMap((file) => {
|
|
498
|
+
const containsOnlyTypes = file.sources?.every((source) => source.isTypeOnly)
|
|
499
|
+
|
|
500
|
+
return (file.sources ?? []).flatMap((source) => {
|
|
501
|
+
if (!file.path || !source.isIndexable) {
|
|
502
|
+
return []
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const meta = file.meta as FileMetaBase | undefined
|
|
506
|
+
const plugin = meta?.pluginName ? pluginNameMap.get(meta.pluginName) : undefined
|
|
507
|
+
const pluginOptions = plugin?.options
|
|
508
|
+
|
|
509
|
+
if (!pluginOptions || pluginOptions.output?.barrelType === false) {
|
|
510
|
+
return []
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const exportName = config.output.barrelType === 'all' ? undefined : source.name ? [source.name] : undefined
|
|
514
|
+
if (exportName?.some((n) => existingExports.has(n))) {
|
|
515
|
+
return []
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return [
|
|
519
|
+
createExport({
|
|
520
|
+
name: exportName,
|
|
521
|
+
path: getRelativePath(rootDir, file.path),
|
|
522
|
+
isTypeOnly: config.output.barrelType === 'all' ? containsOnlyTypes : source.isTypeOnly,
|
|
523
|
+
}),
|
|
524
|
+
]
|
|
525
|
+
})
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function inputToAdapterSource(config: Config): AdapterSource {
|
|
530
|
+
if (Array.isArray(config.input)) {
|
|
531
|
+
return {
|
|
532
|
+
type: 'paths',
|
|
533
|
+
paths: config.input.map((i) => (new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))),
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if ('data' in config.input) {
|
|
538
|
+
return { type: 'data', data: config.input.data }
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (new URLPath(config.input.path).isURL) {
|
|
542
|
+
return { type: 'path', path: config.input.path }
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const resolved = resolve(config.root, config.input.path)
|
|
546
|
+
return { type: 'path', path: resolved }
|
|
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
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { PluginFactoryOptions, UserPluginWithLifeCycle } from './types.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builder type for a {@link UserPluginWithLifeCycle} — takes options and returns the plugin instance.
|
|
5
|
+
*/
|
|
6
|
+
type PluginBuilder<T extends PluginFactoryOptions = PluginFactoryOptions> = (options: T['options']) => UserPluginWithLifeCycle<T>
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* export const myPlugin = createPlugin<MyPlugin>((options) => {
|
|
14
|
+
* return {
|
|
15
|
+
* name: 'my-plugin',
|
|
16
|
+
* get options() { return options },
|
|
17
|
+
* resolvePath(baseName) { ... },
|
|
18
|
+
* resolveName(name, type) { ... },
|
|
19
|
+
* }
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* // instantiate
|
|
23
|
+
* const plugin = myPlugin({ output: { path: 'src/gen' } })
|
|
24
|
+
* ```
|
|
25
|
+
* @deprecated use definePlugin instead
|
|
26
|
+
*/
|
|
27
|
+
export function createPlugin<T extends PluginFactoryOptions = PluginFactoryOptions>(
|
|
28
|
+
build: PluginBuilder<T>,
|
|
29
|
+
): (options?: T['options']) => UserPluginWithLifeCycle<T> {
|
|
30
|
+
return (options) => build(options ?? ({} as T['options']))
|
|
31
|
+
}
|
|
@@ -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
|
+
}
|