@kubb/core 5.0.0-beta.6 → 5.0.0-beta.60
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/LICENSE +17 -10
- package/README.md +25 -158
- package/dist/diagnostics-B-UZnFqP.d.ts +2906 -0
- package/dist/index.cjs +2497 -1071
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +80 -273
- package/dist/index.js +2487 -1067
- package/dist/index.js.map +1 -1
- package/dist/memoryStorage-CUj1hrxa.cjs +823 -0
- package/dist/memoryStorage-CUj1hrxa.cjs.map +1 -0
- package/dist/memoryStorage-CWFzAz4o.js +714 -0
- package/dist/memoryStorage-CWFzAz4o.js.map +1 -0
- package/dist/mocks.cjs +79 -19
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +35 -9
- package/dist/mocks.js +80 -22
- package/dist/mocks.js.map +1 -1
- package/package.json +8 -28
- package/src/FileManager.ts +86 -64
- package/src/FileProcessor.ts +170 -44
- package/src/KubbDriver.ts +908 -0
- package/src/Transform.ts +75 -0
- package/src/constants.ts +111 -20
- package/src/createAdapter.ts +112 -17
- package/src/createKubb.ts +140 -517
- package/src/createRenderer.ts +43 -28
- package/src/createReporter.ts +134 -0
- package/src/createStorage.ts +36 -23
- package/src/defineGenerator.ts +147 -17
- package/src/defineParser.ts +30 -12
- package/src/definePlugin.ts +370 -21
- package/src/defineResolver.ts +402 -212
- package/src/diagnostics.ts +662 -0
- package/src/index.ts +8 -8
- package/src/mocks.ts +91 -20
- package/src/reporters/cliReporter.ts +89 -0
- package/src/reporters/fileReporter.ts +103 -0
- package/src/reporters/jsonReporter.ts +20 -0
- package/src/reporters/report.ts +85 -0
- package/src/storages/fsStorage.ts +23 -55
- package/src/types.ts +411 -887
- package/dist/PluginDriver-BkTRD2H2.js +0 -946
- package/dist/PluginDriver-BkTRD2H2.js.map +0 -1
- package/dist/PluginDriver-Cadu4ORh.cjs +0 -1037
- package/dist/PluginDriver-Cadu4ORh.cjs.map +0 -1
- package/dist/types-DVPKmzw_.d.ts +0 -2159
- package/src/Kubb.ts +0 -300
- package/src/PluginDriver.ts +0 -426
- package/src/defineLogger.ts +0 -19
- package/src/defineMiddleware.ts +0 -62
- package/src/devtools.ts +0 -59
- package/src/renderNode.ts +0 -35
- package/src/utils/diagnostics.ts +0 -18
- package/src/utils/isInputPath.ts +0 -10
- package/src/utils/packageJSON.ts +0 -99
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
package/src/createKubb.ts
CHANGED
|
@@ -1,573 +1,196 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
|
-
import { AsyncEventEmitter, BuildError
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import type { Generator } from './defineGenerator.ts'
|
|
8
|
-
import type { Parser } from './defineParser.ts'
|
|
9
|
-
import type { Plugin } from './definePlugin.ts'
|
|
10
|
-
import { FileProcessor } from './FileProcessor.ts'
|
|
11
|
-
import type { Kubb } from './Kubb.ts'
|
|
12
|
-
import { PluginDriver } from './PluginDriver.ts'
|
|
13
|
-
import { applyHookResult } from './renderNode.ts'
|
|
2
|
+
import { AsyncEventEmitter, BuildError } from '@internals/utils'
|
|
3
|
+
import { HOOK_LISTENERS_PER_PLUGIN } from './constants.ts'
|
|
4
|
+
import { Diagnostics } from './diagnostics.ts'
|
|
5
|
+
import { createStorage, type Storage } from './createStorage.ts'
|
|
6
|
+
import { KubbDriver } from './KubbDriver.ts'
|
|
14
7
|
import { fsStorage } from './storages/fsStorage.ts'
|
|
15
|
-
import type {
|
|
16
|
-
import { getDiagnosticInfo } from './utils/diagnostics.ts'
|
|
17
|
-
import { isInputPath } from './utils/isInputPath.ts'
|
|
18
|
-
|
|
19
|
-
type SetupOptions = {
|
|
20
|
-
hooks?: AsyncEventEmitter<KubbHooks>
|
|
21
|
-
}
|
|
8
|
+
import type { BuildOutput, Config, KubbHooks, UserConfig } from './types.ts'
|
|
22
9
|
|
|
23
10
|
/**
|
|
24
|
-
*
|
|
11
|
+
* Builds a `Storage` view scoped to the file paths produced by the current build.
|
|
12
|
+
* Reads delegate to the underlying `storage` so source bytes stay where they were
|
|
13
|
+
* written. Writes register the key so subsequent reads and `getKeys` are scoped
|
|
14
|
+
* to this build's output.
|
|
25
15
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* Plugins that threw during installation, paired with the caught error.
|
|
29
|
-
*/
|
|
30
|
-
failedPlugins: Set<{ plugin: Plugin; error: Error }>
|
|
31
|
-
files: Array<FileNode>
|
|
32
|
-
driver: PluginDriver
|
|
33
|
-
/**
|
|
34
|
-
* Elapsed time in milliseconds for each plugin, keyed by plugin name.
|
|
35
|
-
*/
|
|
36
|
-
pluginTimings: Map<string, number>
|
|
37
|
-
error?: Error
|
|
38
|
-
/**
|
|
39
|
-
* Raw generated source, keyed by absolute file path.
|
|
40
|
-
*/
|
|
41
|
-
sources: Map<string, string>
|
|
42
|
-
}
|
|
16
|
+
function createSourcesView(storage: Storage): Storage {
|
|
17
|
+
const paths = new Set<string>()
|
|
43
18
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
19
|
+
return createStorage(() => ({
|
|
20
|
+
name: `${storage.name}:sources`,
|
|
21
|
+
async hasItem(key: string) {
|
|
22
|
+
return paths.has(key) && (await storage.hasItem(key))
|
|
23
|
+
},
|
|
24
|
+
async getItem(key: string) {
|
|
25
|
+
return paths.has(key) ? storage.getItem(key) : null
|
|
26
|
+
},
|
|
27
|
+
async setItem(key: string, value: string) {
|
|
28
|
+
paths.add(key)
|
|
29
|
+
await storage.setItem(key, value)
|
|
30
|
+
},
|
|
31
|
+
async removeItem(key: string) {
|
|
32
|
+
paths.delete(key)
|
|
33
|
+
await storage.removeItem(key)
|
|
34
|
+
},
|
|
35
|
+
async getKeys(base?: string) {
|
|
36
|
+
if (!base) return [...paths]
|
|
37
|
+
const result: Array<string> = []
|
|
38
|
+
for (const key of paths) {
|
|
39
|
+
if (key.startsWith(base)) result.push(key)
|
|
40
|
+
}
|
|
41
|
+
return result
|
|
42
|
+
},
|
|
43
|
+
async clear() {
|
|
44
|
+
paths.clear()
|
|
45
|
+
await storage.clear()
|
|
46
|
+
},
|
|
47
|
+
}))()
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const sources: Map<string, string> = new Map<string, string>()
|
|
56
|
-
const diagnosticInfo = getDiagnosticInfo()
|
|
57
|
-
|
|
58
|
-
if (Array.isArray(userConfig.input)) {
|
|
59
|
-
await hooks.emit('kubb:warn', { message: 'This feature is still under development — use with caution' })
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
await hooks.emit('kubb:debug', {
|
|
63
|
-
date: new Date(),
|
|
64
|
-
logs: [
|
|
65
|
-
'Configuration:',
|
|
66
|
-
` • Name: ${userConfig.name || 'unnamed'}`,
|
|
67
|
-
` • Root: ${userConfig.root || process.cwd()}`,
|
|
68
|
-
` • Output: ${userConfig.output?.path || 'not specified'}`,
|
|
69
|
-
` • Plugins: ${userConfig.plugins?.length || 0}`,
|
|
70
|
-
'Output Settings:',
|
|
71
|
-
` • Storage: ${userConfig.storage ? `custom(${userConfig.storage.name})` : userConfig.output?.write === false ? 'disabled' : 'filesystem (default)'}`,
|
|
72
|
-
` • Formatter: ${userConfig.output?.format || 'none'}`,
|
|
73
|
-
` • Linter: ${userConfig.output?.lint || 'none'}`,
|
|
74
|
-
'Environment:',
|
|
75
|
-
Object.entries(diagnosticInfo)
|
|
76
|
-
.map(([key, value]) => ` • ${key}: ${value}`)
|
|
77
|
-
.join('\n'),
|
|
78
|
-
],
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
|
|
83
|
-
await exists(userConfig.input.path)
|
|
84
|
-
|
|
85
|
-
await hooks.emit('kubb:debug', {
|
|
86
|
-
date: new Date(),
|
|
87
|
-
logs: [`✓ Input file validated: ${userConfig.input.path}`],
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
} catch (caughtError) {
|
|
91
|
-
if (isInputPath(userConfig)) {
|
|
92
|
-
const error = caughtError as Error
|
|
93
|
-
|
|
94
|
-
throw new Error(
|
|
95
|
-
`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`,
|
|
96
|
-
{
|
|
97
|
-
cause: error,
|
|
98
|
-
},
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const config: Config = {
|
|
50
|
+
function resolveConfig(userConfig: UserConfig): Config {
|
|
51
|
+
return {
|
|
104
52
|
...userConfig,
|
|
105
53
|
root: userConfig.root || process.cwd(),
|
|
106
54
|
parsers: userConfig.parsers ?? [],
|
|
107
|
-
adapter: userConfig.adapter,
|
|
108
55
|
output: {
|
|
109
56
|
format: false,
|
|
110
57
|
lint: false,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
defaultBanner: DEFAULT_BANNER,
|
|
58
|
+
extension: { '.ts': '.ts' },
|
|
59
|
+
defaultBanner: 'simple',
|
|
114
60
|
...userConfig.output,
|
|
115
61
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
...(typeof userConfig.devtools === 'boolean' ? {} : userConfig.devtools),
|
|
120
|
-
}
|
|
121
|
-
: undefined,
|
|
122
|
-
plugins: (userConfig.plugins ?? []) as unknown as Config['plugins'],
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const storage: Storage | null = config.output.write === false ? null : (config.storage ?? fsStorage())
|
|
126
|
-
|
|
127
|
-
if (config.output.clean) {
|
|
128
|
-
await hooks.emit('kubb:debug', {
|
|
129
|
-
date: new Date(),
|
|
130
|
-
logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
|
|
131
|
-
})
|
|
132
|
-
await storage?.clear(resolve(config.root, config.output.path))
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const driver = new PluginDriver(config, {
|
|
136
|
-
hooks,
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
// Register middleware hooks after all plugin hooks are registered.
|
|
140
|
-
// Because AsyncEventEmitter calls listeners in registration order,
|
|
141
|
-
// middleware hooks for any event fire after all plugin hooks for that event.
|
|
142
|
-
function registerMiddlewareHook<K extends keyof KubbHooks & string>(event: K, middlewareHooks: Middleware['hooks']) {
|
|
143
|
-
const handler = middlewareHooks[event]
|
|
144
|
-
if (handler) {
|
|
145
|
-
hooks.on(event, handler)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
for (const middleware of config.middleware ?? []) {
|
|
150
|
-
for (const event of Object.keys(middleware.hooks) as Array<keyof KubbHooks & string>) {
|
|
151
|
-
registerMiddlewareHook(event, middleware.hooks)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (config.adapter) {
|
|
156
|
-
const source = inputToAdapterSource(config)
|
|
157
|
-
|
|
158
|
-
await hooks.emit('kubb:debug', {
|
|
159
|
-
date: new Date(),
|
|
160
|
-
logs: [`Running adapter: ${config.adapter.name}`],
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
driver.adapter = config.adapter
|
|
164
|
-
driver.inputNode = await config.adapter.parse(source)
|
|
165
|
-
|
|
166
|
-
await hooks.emit('kubb:debug', {
|
|
167
|
-
date: new Date(),
|
|
168
|
-
logs: [
|
|
169
|
-
`✓ Adapter '${config.adapter.name}' resolved InputNode`,
|
|
170
|
-
` • Schemas: ${driver.inputNode.schemas.length}`,
|
|
171
|
-
` • Operations: ${driver.inputNode.operations.length}`,
|
|
172
|
-
],
|
|
173
|
-
})
|
|
62
|
+
storage: userConfig.storage ?? fsStorage(),
|
|
63
|
+
reporters: userConfig.reporters ?? [],
|
|
64
|
+
plugins: userConfig.plugins ?? [],
|
|
174
65
|
}
|
|
66
|
+
}
|
|
175
67
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
hooks,
|
|
179
|
-
driver,
|
|
180
|
-
sources,
|
|
181
|
-
storage,
|
|
182
|
-
}
|
|
68
|
+
type CreateKubbOptions = {
|
|
69
|
+
hooks?: AsyncEventEmitter<KubbHooks>
|
|
183
70
|
}
|
|
184
71
|
|
|
185
72
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
73
|
+
* Kubb code-generation instance bound to a single config entry. Resolves the user
|
|
74
|
+
* config in the constructor, so `config` is available right away, and shares `hooks`,
|
|
75
|
+
* `storage`, and `driver` across the `setup → build` lifecycle.
|
|
76
|
+
*
|
|
77
|
+
* `createKubb` takes a plain, serializable config object (the shape `defineConfig`
|
|
78
|
+
* produces), not a fluent builder. Config stays plain data so it can be cache
|
|
79
|
+
* fingerprinted and validated against the shipped JSON schema.
|
|
188
80
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
81
|
+
* Attach event listeners to `.hooks` before calling `setup()` or `build()`.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const kubb = createKubb(userConfig)
|
|
86
|
+
* kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
|
|
87
|
+
* const { files, diagnostics } = await kubb.safeBuild()
|
|
88
|
+
* ```
|
|
194
89
|
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
90
|
+
export class Kubb {
|
|
91
|
+
readonly hooks: AsyncEventEmitter<KubbHooks>
|
|
92
|
+
readonly config: Config
|
|
93
|
+
#driver: KubbDriver | null = null
|
|
94
|
+
#storage: Storage | null = null
|
|
198
95
|
|
|
199
|
-
|
|
200
|
-
|
|
96
|
+
constructor(userConfig: UserConfig, options: CreateKubbOptions = {}) {
|
|
97
|
+
this.config = resolveConfig(userConfig)
|
|
98
|
+
this.hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
|
|
201
99
|
}
|
|
202
100
|
|
|
203
|
-
|
|
204
|
-
|
|
101
|
+
get storage(): Storage {
|
|
102
|
+
if (!this.#storage) throw new Error('[kubb] setup() must be called before accessing storage')
|
|
103
|
+
return this.#storage
|
|
205
104
|
}
|
|
206
105
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const generatorContext = {
|
|
211
|
-
...context,
|
|
212
|
-
resolver: driver.getResolver(plugin.name),
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// When `include` has operation-based filters (tag, operationId, path, method, contentType)
|
|
216
|
-
// but no schema-level filters (schemaName), pre-compute the set of top-level schema names
|
|
217
|
-
// that are transitively referenced by the included operations. Schemas outside that set are
|
|
218
|
-
// skipped so that types belonging exclusively to excluded operations are not generated.
|
|
219
|
-
const operationFilterTypes = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])
|
|
220
|
-
const hasOperationBasedIncludes = include?.some(({ type }) => operationFilterTypes.has(type)) ?? false
|
|
221
|
-
const hasSchemaNameIncludes = include?.some(({ type }) => type === 'schemaName') ?? false
|
|
222
|
-
|
|
223
|
-
let allowedSchemaNames: Set<string> | undefined
|
|
224
|
-
if (hasOperationBasedIncludes && !hasSchemaNameIncludes) {
|
|
225
|
-
const includedOps = inputNode.operations.filter((op) => resolver.resolveOptions(op, { options: plugin.options, exclude, include, override }) !== null)
|
|
226
|
-
allowedSchemaNames = collectUsedSchemaNames(includedOps, inputNode.schemas)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
await walk(inputNode, {
|
|
230
|
-
depth: 'shallow',
|
|
231
|
-
async schema(node) {
|
|
232
|
-
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
233
|
-
|
|
234
|
-
// Skip named top-level schemas that are not reachable from any included operation.
|
|
235
|
-
if (allowedSchemaNames !== undefined && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) {
|
|
236
|
-
return
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const options = resolver.resolveOptions(transformedNode, {
|
|
240
|
-
options: plugin.options,
|
|
241
|
-
exclude,
|
|
242
|
-
include,
|
|
243
|
-
override,
|
|
244
|
-
})
|
|
245
|
-
if (options === null) return
|
|
246
|
-
|
|
247
|
-
const ctx = { ...generatorContext, options }
|
|
248
|
-
|
|
249
|
-
for (const gen of generators) {
|
|
250
|
-
if (!gen.schema) continue
|
|
251
|
-
const result = await gen.schema(transformedNode, ctx)
|
|
252
|
-
await applyHookResult(result, driver, resolveRenderer(gen))
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
await driver.hooks.emit('kubb:generate:schema', transformedNode, ctx)
|
|
256
|
-
},
|
|
257
|
-
async operation(node) {
|
|
258
|
-
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
259
|
-
const options = resolver.resolveOptions(transformedNode, {
|
|
260
|
-
options: plugin.options,
|
|
261
|
-
exclude,
|
|
262
|
-
include,
|
|
263
|
-
override,
|
|
264
|
-
})
|
|
265
|
-
if (options !== null) {
|
|
266
|
-
collectedOperations.push(transformedNode)
|
|
267
|
-
|
|
268
|
-
const ctx = { ...generatorContext, options }
|
|
269
|
-
|
|
270
|
-
for (const gen of generators) {
|
|
271
|
-
if (!gen.operation) continue
|
|
272
|
-
const result = await gen.operation(transformedNode, ctx)
|
|
273
|
-
await applyHookResult(result, driver, resolveRenderer(gen))
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
|
|
277
|
-
}
|
|
278
|
-
},
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
if (collectedOperations.length > 0) {
|
|
282
|
-
const ctx = { ...generatorContext, options: plugin.options }
|
|
283
|
-
|
|
284
|
-
for (const gen of generators) {
|
|
285
|
-
if (!gen.operations) continue
|
|
286
|
-
const result = await gen.operations(collectedOperations, ctx)
|
|
287
|
-
await applyHookResult(result, driver, resolveRenderer(gen))
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
await driver.hooks.emit('kubb:generate:operations', collectedOperations, ctx)
|
|
106
|
+
get driver(): KubbDriver {
|
|
107
|
+
if (!this.#driver) throw new Error('[kubb] setup() must be called before accessing driver')
|
|
108
|
+
return this.#driver
|
|
291
109
|
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
295
|
-
const { driver, hooks, sources, storage } = setupResult
|
|
296
|
-
|
|
297
|
-
const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
|
|
298
|
-
const pluginTimings = new Map<string, number>()
|
|
299
|
-
const config = driver.config
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
await driver.emitSetupHooks()
|
|
303
|
-
|
|
304
|
-
if (driver.adapter && driver.inputNode) {
|
|
305
|
-
await hooks.emit('kubb:build:start', {
|
|
306
|
-
config,
|
|
307
|
-
adapter: driver.adapter,
|
|
308
|
-
inputNode: driver.inputNode,
|
|
309
|
-
getPlugin: driver.getPlugin.bind(driver),
|
|
310
|
-
get files() {
|
|
311
|
-
return driver.fileManager.files
|
|
312
|
-
},
|
|
313
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
314
|
-
})
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
for (const plugin of driver.plugins.values()) {
|
|
318
|
-
const context = driver.getContext(plugin)
|
|
319
|
-
const hrStart = process.hrtime()
|
|
320
|
-
|
|
321
|
-
try {
|
|
322
|
-
const timestamp = new Date()
|
|
323
|
-
|
|
324
|
-
await hooks.emit('kubb:plugin:start', { plugin })
|
|
325
|
-
|
|
326
|
-
await hooks.emit('kubb:debug', {
|
|
327
|
-
date: timestamp,
|
|
328
|
-
logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) {
|
|
332
|
-
await runPluginAstHooks(plugin, context)
|
|
333
|
-
}
|
|
334
110
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
date: new Date(),
|
|
351
|
-
logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
|
|
352
|
-
})
|
|
353
|
-
} catch (caughtError) {
|
|
354
|
-
const error = caughtError as Error
|
|
355
|
-
const errorTimestamp = new Date()
|
|
356
|
-
const duration = getElapsedMs(hrStart)
|
|
357
|
-
|
|
358
|
-
await hooks.emit('kubb:plugin:end', {
|
|
359
|
-
plugin,
|
|
360
|
-
duration,
|
|
361
|
-
success: false,
|
|
362
|
-
error,
|
|
363
|
-
config,
|
|
364
|
-
get files() {
|
|
365
|
-
return driver.fileManager.files
|
|
366
|
-
},
|
|
367
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
await hooks.emit('kubb:debug', {
|
|
371
|
-
date: errorTimestamp,
|
|
372
|
-
logs: [
|
|
373
|
-
'✗ Plugin start failed',
|
|
374
|
-
` • Plugin Name: ${plugin.name}`,
|
|
375
|
-
` • Error: ${error.constructor.name} - ${error.message}`,
|
|
376
|
-
' • Stack Trace:',
|
|
377
|
-
error.stack || 'No stack trace available',
|
|
378
|
-
],
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
failedPlugins.add({ plugin, error })
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
await hooks.emit('kubb:plugins:end', {
|
|
386
|
-
config,
|
|
387
|
-
get files() {
|
|
388
|
-
return driver.fileManager.files
|
|
389
|
-
},
|
|
390
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
const files = driver.fileManager.files
|
|
394
|
-
|
|
395
|
-
const parsersMap = new Map<FileNode['extname'], Parser>()
|
|
396
|
-
for (const parser of config.parsers) {
|
|
397
|
-
if (parser.extNames) {
|
|
398
|
-
for (const extname of parser.extNames) {
|
|
399
|
-
parsersMap.set(extname, parser)
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const fileProcessor = new FileProcessor()
|
|
405
|
-
|
|
406
|
-
await hooks.emit('kubb:debug', {
|
|
407
|
-
date: new Date(),
|
|
408
|
-
logs: [`Writing ${files.length} files...`],
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
await fileProcessor.run(files, {
|
|
412
|
-
parsers: parsersMap,
|
|
413
|
-
extension: config.output.extension,
|
|
414
|
-
onStart: async (processingFiles) => {
|
|
415
|
-
await hooks.emit('kubb:files:processing:start', { files: processingFiles })
|
|
416
|
-
},
|
|
417
|
-
onUpdate: async ({ file, source, processed, total, percentage }) => {
|
|
418
|
-
await hooks.emit('kubb:file:processing:update', {
|
|
419
|
-
file,
|
|
420
|
-
source,
|
|
421
|
-
processed,
|
|
422
|
-
total,
|
|
423
|
-
percentage,
|
|
424
|
-
config,
|
|
425
|
-
})
|
|
426
|
-
if (source) {
|
|
427
|
-
await storage?.setItem(file.path, source)
|
|
428
|
-
sources.set(file.path, source)
|
|
429
|
-
}
|
|
430
|
-
},
|
|
431
|
-
onEnd: async (processedFiles) => {
|
|
432
|
-
await hooks.emit('kubb:files:processing:end', { files: processedFiles })
|
|
433
|
-
await hooks.emit('kubb:debug', {
|
|
434
|
-
date: new Date(),
|
|
435
|
-
logs: [`✓ File write process completed for ${processedFiles.length} files`],
|
|
436
|
-
})
|
|
437
|
-
},
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
await hooks.emit('kubb:build:end', {
|
|
441
|
-
files,
|
|
442
|
-
config,
|
|
443
|
-
outputDir: resolve(config.root, config.output.path),
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
return {
|
|
447
|
-
failedPlugins,
|
|
448
|
-
files,
|
|
449
|
-
driver,
|
|
450
|
-
pluginTimings,
|
|
451
|
-
sources,
|
|
452
|
-
}
|
|
453
|
-
} catch (error) {
|
|
454
|
-
return {
|
|
455
|
-
failedPlugins,
|
|
456
|
-
files: [],
|
|
457
|
-
driver,
|
|
458
|
-
pluginTimings,
|
|
459
|
-
error: error as Error,
|
|
460
|
-
sources,
|
|
111
|
+
/**
|
|
112
|
+
* Initializes the driver and storage. `build()` calls this automatically.
|
|
113
|
+
*/
|
|
114
|
+
async setup(): Promise<void> {
|
|
115
|
+
const config = this.config
|
|
116
|
+
const driver = new KubbDriver(config, { hooks: this.hooks })
|
|
117
|
+
const storage = createSourcesView(config.storage)
|
|
118
|
+
|
|
119
|
+
// Each generator a plugin registers adds a listener to the shared hooks emitter, so size the
|
|
120
|
+
// ceiling to the plugin count. Without this, a multi-generator plugin set trips Node's
|
|
121
|
+
// EventEmitter leak warning at the default 10.
|
|
122
|
+
this.hooks.setMaxListeners(Math.max(10, config.plugins.length * HOOK_LISTENERS_PER_PLUGIN))
|
|
123
|
+
|
|
124
|
+
if (config.output.clean) {
|
|
125
|
+
await config.storage.clear(resolve(config.root, config.output.path))
|
|
461
126
|
}
|
|
462
|
-
} finally {
|
|
463
|
-
driver.dispose()
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
127
|
|
|
467
|
-
|
|
468
|
-
const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult)
|
|
128
|
+
await driver.setup()
|
|
469
129
|
|
|
470
|
-
|
|
471
|
-
|
|
130
|
+
this.#driver = driver
|
|
131
|
+
this.#storage = storage
|
|
472
132
|
}
|
|
473
133
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Runs the full pipeline and throws on any plugin error.
|
|
136
|
+
* Automatically calls `setup()` if needed.
|
|
137
|
+
*/
|
|
138
|
+
async build(): Promise<BuildOutput> {
|
|
139
|
+
const out = await this.safeBuild()
|
|
140
|
+
if (Diagnostics.hasError(out.diagnostics)) {
|
|
141
|
+
const errors = out.diagnostics
|
|
142
|
+
.filter(Diagnostics.isProblem)
|
|
143
|
+
.filter((diagnostic) => diagnostic.severity === 'error')
|
|
144
|
+
.map((diagnostic) => diagnostic.cause ?? new Diagnostics.Error(diagnostic))
|
|
145
|
+
throw new BuildError(`Build failed with ${errors.length} ${errors.length === 1 ? 'error' : 'errors'}`, { errors })
|
|
146
|
+
}
|
|
147
|
+
return out
|
|
487
148
|
}
|
|
488
|
-
}
|
|
489
149
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Runs the full pipeline and captures errors in `BuildOutput` instead of throwing.
|
|
152
|
+
* Automatically calls `setup()` if needed. This is the canonical call: it never throws on
|
|
153
|
+
* plugin errors, so callers stay in control of how failures surface.
|
|
154
|
+
*/
|
|
155
|
+
async safeBuild(): Promise<BuildOutput> {
|
|
156
|
+
if (!this.#driver) await this.setup()
|
|
157
|
+
using cleanup = this
|
|
158
|
+
const driver = cleanup.driver
|
|
159
|
+
const storage = cleanup.storage
|
|
160
|
+
const { diagnostics } = await driver.run({ storage })
|
|
495
161
|
|
|
496
|
-
|
|
497
|
-
return {
|
|
498
|
-
type: 'paths',
|
|
499
|
-
paths: input.map((i) => (new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))),
|
|
500
|
-
}
|
|
162
|
+
return { diagnostics, files: driver.fileManager.files, driver, storage }
|
|
501
163
|
}
|
|
502
164
|
|
|
503
|
-
|
|
504
|
-
|
|
165
|
+
dispose(): void {
|
|
166
|
+
this.#driver?.dispose()
|
|
505
167
|
}
|
|
506
168
|
|
|
507
|
-
|
|
508
|
-
|
|
169
|
+
[Symbol.dispose](): void {
|
|
170
|
+
this.dispose()
|
|
509
171
|
}
|
|
510
|
-
|
|
511
|
-
const resolved = resolve(config.root, input.path)
|
|
512
|
-
return { type: 'path', path: resolved }
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
type CreateKubbOptions = {
|
|
516
|
-
hooks?: AsyncEventEmitter<KubbHooks>
|
|
517
172
|
}
|
|
518
173
|
|
|
519
174
|
/**
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
* Accepts a user-facing config shape and resolves it to a full {@link Config} during
|
|
523
|
-
* `setup()`. The instance then holds shared state (`hooks`, `sources`, `driver`, `config`)
|
|
524
|
-
* across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
|
|
525
|
-
* calling `setup()` or `build()`.
|
|
175
|
+
* Constructs a {@link Kubb} build orchestrator from a user config. Equivalent
|
|
176
|
+
* to `new Kubb(userConfig, options)` and the canonical public entry point.
|
|
526
177
|
*
|
|
527
178
|
* @example
|
|
528
179
|
* ```ts
|
|
529
|
-
*
|
|
180
|
+
* import { createKubb } from '@kubb/core'
|
|
181
|
+
* import { adapterOas } from '@kubb/adapter-oas'
|
|
182
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
530
183
|
*
|
|
531
|
-
* kubb
|
|
532
|
-
*
|
|
184
|
+
* const kubb = createKubb({
|
|
185
|
+
* input: { path: './petStore.yaml' },
|
|
186
|
+
* output: { path: './src/gen' },
|
|
187
|
+
* adapter: adapterOas(),
|
|
188
|
+
* plugins: [pluginTs()],
|
|
533
189
|
* })
|
|
534
190
|
*
|
|
535
|
-
*
|
|
191
|
+
* await kubb.build()
|
|
536
192
|
* ```
|
|
537
193
|
*/
|
|
538
194
|
export function createKubb(userConfig: UserConfig, options: CreateKubbOptions = {}): Kubb {
|
|
539
|
-
|
|
540
|
-
let setupResult: SetupResult | undefined
|
|
541
|
-
|
|
542
|
-
const instance: Kubb = {
|
|
543
|
-
get hooks() {
|
|
544
|
-
return hooks
|
|
545
|
-
},
|
|
546
|
-
get sources() {
|
|
547
|
-
return setupResult?.sources ?? new Map()
|
|
548
|
-
},
|
|
549
|
-
get driver() {
|
|
550
|
-
return setupResult?.driver
|
|
551
|
-
},
|
|
552
|
-
get config() {
|
|
553
|
-
return setupResult?.config
|
|
554
|
-
},
|
|
555
|
-
async setup() {
|
|
556
|
-
setupResult = await setup(userConfig, { hooks })
|
|
557
|
-
},
|
|
558
|
-
async build() {
|
|
559
|
-
if (!setupResult) {
|
|
560
|
-
await instance.setup()
|
|
561
|
-
}
|
|
562
|
-
return build(setupResult!)
|
|
563
|
-
},
|
|
564
|
-
async safeBuild() {
|
|
565
|
-
if (!setupResult) {
|
|
566
|
-
await instance.setup()
|
|
567
|
-
}
|
|
568
|
-
return safeBuild(setupResult!)
|
|
569
|
-
},
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
return instance
|
|
195
|
+
return new Kubb(userConfig, options)
|
|
573
196
|
}
|