@kubb/core 5.0.0-beta.19 → 5.0.0-beta.20
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-DXp767s2.cjs → KubbDriver-BXSnJ3qM.cjs} +546 -102
- package/dist/KubbDriver-BXSnJ3qM.cjs.map +1 -0
- package/dist/{PluginDriver-uNex0SAr.js → KubbDriver-Cxii_rBp.js} +522 -96
- package/dist/KubbDriver-Cxii_rBp.js.map +1 -0
- package/dist/{createKubb-BJGymYhe.d.ts → createKubb-Dcmtjqds.d.ts} +32 -62
- package/dist/index.cjs +257 -684
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +248 -675
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +9 -10
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +5 -5
- package/dist/mocks.js +5 -6
- package/dist/mocks.js.map +1 -1
- package/package.json +4 -4
- package/src/{PluginDriver.ts → KubbDriver.ts} +175 -88
- package/src/constants.ts +4 -4
- package/src/createAdapter.ts +0 -8
- package/src/createKubb.ts +293 -469
- package/src/defineGenerator.ts +9 -8
- package/src/definePlugin.ts +5 -5
- package/src/defineResolver.ts +26 -40
- package/src/index.ts +1 -1
- package/src/mocks.ts +8 -8
- package/dist/PluginDriver-DXp767s2.cjs.map +0 -1
- package/dist/PluginDriver-uNex0SAr.js.map +0 -1
package/src/createKubb.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
2
|
import { version as nodeVersion } from 'node:process'
|
|
3
3
|
import type { PossiblePromise } from '@internals/utils'
|
|
4
|
-
import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, URLPath, isPromise } from '@internals/utils'
|
|
5
|
-
import type { FileNode,
|
|
6
|
-
import { collectUsedSchemaNames, transform
|
|
4
|
+
import { AsyncEventEmitter, BuildError, exists, forBatches, formatMs, getElapsedMs, URLPath, isPromise, withDrain } from '@internals/utils'
|
|
5
|
+
import type { FileNode, InputMeta, OperationNode, SchemaNode } from '@kubb/ast'
|
|
6
|
+
import { collectUsedSchemaNames, transform } from '@kubb/ast'
|
|
7
7
|
import { version as KubbVersion } from '../package.json'
|
|
8
|
-
import { DEFAULT_BANNER, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL,
|
|
9
|
-
import type { Adapter
|
|
8
|
+
import { DEFAULT_BANNER, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL, SCHEMA_PARALLEL, STREAM_FLUSH_EVERY } from './constants.ts'
|
|
9
|
+
import type { Adapter } from './createAdapter.ts'
|
|
10
10
|
import type { RendererFactory } from './createRenderer.ts'
|
|
11
11
|
import { createStorage, type Storage } from './createStorage.ts'
|
|
12
12
|
import type { GeneratorContext, Generator } from './defineGenerator.ts'
|
|
@@ -15,7 +15,7 @@ import type { Parser } from './defineParser.ts'
|
|
|
15
15
|
import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext, NormalizedPlugin, Plugin } from './definePlugin.ts'
|
|
16
16
|
import { FileProcessor } from './FileProcessor.ts'
|
|
17
17
|
|
|
18
|
-
import { applyHookResult,
|
|
18
|
+
import { applyHookResult, KubbDriver } from './KubbDriver.ts'
|
|
19
19
|
import { fsStorage } from './storages/fsStorage.ts'
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -113,7 +113,7 @@ export type Config<TInput = Input> = {
|
|
|
113
113
|
*/
|
|
114
114
|
parsers: Array<Parser>
|
|
115
115
|
/**
|
|
116
|
-
* Adapter that parses input files into the universal
|
|
116
|
+
* Adapter that parses input files into the universal AST representation.
|
|
117
117
|
* Use `@kubb/adapter-oas` for OpenAPI/Swagger or `@kubb/adapter-asyncapi` for other formats.
|
|
118
118
|
*
|
|
119
119
|
* When omitted, Kubb runs in plugin-only mode: `kubb:plugin:setup` fires and files
|
|
@@ -540,10 +540,10 @@ export type KubbBuildStartContext = {
|
|
|
540
540
|
*/
|
|
541
541
|
adapter: Adapter
|
|
542
542
|
/**
|
|
543
|
-
*
|
|
544
|
-
*
|
|
543
|
+
* Metadata about the parsed document (title, version, base URL, circular schema names, enum names).
|
|
544
|
+
* To observe individual schemas and operations use the `kubb:generate:schema` / `kubb:generate:operation` hooks.
|
|
545
545
|
*/
|
|
546
|
-
|
|
546
|
+
meta: InputMeta | undefined
|
|
547
547
|
/**
|
|
548
548
|
* Looks up a registered plugin by name, typed by the plugin registry.
|
|
549
549
|
*/
|
|
@@ -855,7 +855,7 @@ export type BuildOutput = {
|
|
|
855
855
|
/**
|
|
856
856
|
* The plugin driver that orchestrated this build.
|
|
857
857
|
*/
|
|
858
|
-
driver:
|
|
858
|
+
driver: KubbDriver
|
|
859
859
|
/**
|
|
860
860
|
* Elapsed milliseconds per plugin, keyed by plugin name.
|
|
861
861
|
*/
|
|
@@ -912,7 +912,7 @@ export type Kubb = {
|
|
|
912
912
|
/**
|
|
913
913
|
* Plugin driver managing all plugins. Available after `setup()` completes.
|
|
914
914
|
*/
|
|
915
|
-
readonly driver:
|
|
915
|
+
readonly driver: KubbDriver
|
|
916
916
|
/**
|
|
917
917
|
* Resolved configuration with defaults applied. Available after `setup()` completes.
|
|
918
918
|
*/
|
|
@@ -933,7 +933,7 @@ export type Kubb = {
|
|
|
933
933
|
|
|
934
934
|
type SetupResult = {
|
|
935
935
|
hooks: AsyncEventEmitter<KubbHooks>
|
|
936
|
-
driver:
|
|
936
|
+
driver: KubbDriver
|
|
937
937
|
storage: Storage
|
|
938
938
|
config: Config
|
|
939
939
|
dispose: () => void
|
|
@@ -950,6 +950,7 @@ type SetupResult = {
|
|
|
950
950
|
*/
|
|
951
951
|
function createSourcesView(storage: Storage): Storage {
|
|
952
952
|
const paths = new Set<string>()
|
|
953
|
+
|
|
953
954
|
return createStorage(() => ({
|
|
954
955
|
name: `${storage.name}:sources`,
|
|
955
956
|
async hasItem(key: string) {
|
|
@@ -987,7 +988,6 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
987
988
|
...userConfig,
|
|
988
989
|
root: userConfig.root || process.cwd(),
|
|
989
990
|
parsers: userConfig.parsers ?? [],
|
|
990
|
-
adapter: userConfig.adapter,
|
|
991
991
|
output: {
|
|
992
992
|
format: false,
|
|
993
993
|
lint: false,
|
|
@@ -1004,10 +1004,10 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
1004
1004
|
: undefined,
|
|
1005
1005
|
plugins: (userConfig.plugins ?? []) as unknown as Config['plugins'],
|
|
1006
1006
|
}
|
|
1007
|
-
const driver = new
|
|
1007
|
+
const driver = new KubbDriver(config, {
|
|
1008
1008
|
hooks,
|
|
1009
1009
|
})
|
|
1010
|
-
const storage
|
|
1010
|
+
const storage = createSourcesView(config.storage)
|
|
1011
1011
|
const diagnosticInfo = getDiagnosticInfo()
|
|
1012
1012
|
|
|
1013
1013
|
await hooks.emit('kubb:debug', {
|
|
@@ -1022,6 +1022,7 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
1022
1022
|
` • Storage: ${config.storage.name}`,
|
|
1023
1023
|
` • Formatter: ${userConfig.output?.format || 'none'}`,
|
|
1024
1024
|
` • Linter: ${userConfig.output?.lint || 'none'}`,
|
|
1025
|
+
`Running adapter: ${config.adapter?.name || 'none'}`,
|
|
1025
1026
|
'Environment:',
|
|
1026
1027
|
Object.entries(diagnosticInfo)
|
|
1027
1028
|
.map(([key, value]) => ` • ${key}: ${value}`)
|
|
@@ -1056,70 +1057,11 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
1056
1057
|
date: new Date(),
|
|
1057
1058
|
logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
|
|
1058
1059
|
})
|
|
1059
|
-
await config.storage.clear(resolve(config.root, config.output.path))
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// Register middleware hooks after all plugin hooks are registered.
|
|
1063
|
-
// Because AsyncEventEmitter calls listeners in registration order,
|
|
1064
|
-
// middleware hooks for any event fire after all plugin hooks for that event.
|
|
1065
|
-
// Handlers are tracked so they can be removed after each build (disposeMiddleware),
|
|
1066
|
-
// preventing accumulation when multiple configs share the same hooks instance.
|
|
1067
|
-
const middlewareListeners: Array<[keyof KubbHooks & string, (...args: never[]) => void | Promise<void>]> = []
|
|
1068
|
-
|
|
1069
|
-
function registerMiddlewareHook<K extends keyof KubbHooks & string>(event: K, middlewareHooks: Middleware['hooks']) {
|
|
1070
|
-
const handler = middlewareHooks[event]
|
|
1071
|
-
if (handler) {
|
|
1072
|
-
hooks.on(event, handler)
|
|
1073
|
-
middlewareListeners.push([event, handler as (...args: never[]) => void | Promise<void>])
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
1060
|
|
|
1077
|
-
|
|
1078
|
-
for (const event of Object.keys(middleware.hooks) as Array<keyof KubbHooks & string>) {
|
|
1079
|
-
registerMiddlewareHook(event, middleware.hooks)
|
|
1080
|
-
}
|
|
1061
|
+
await config.storage.clear(resolve(config.root, config.output.path))
|
|
1081
1062
|
}
|
|
1082
1063
|
|
|
1083
|
-
|
|
1084
|
-
const source = inputToAdapterSource(config)
|
|
1085
|
-
|
|
1086
|
-
await hooks.emit('kubb:debug', {
|
|
1087
|
-
date: new Date(),
|
|
1088
|
-
logs: [`Running adapter: ${config.adapter.name}`],
|
|
1089
|
-
})
|
|
1090
|
-
|
|
1091
|
-
driver.adapter = config.adapter
|
|
1092
|
-
|
|
1093
|
-
if (config.adapter.count && config.adapter.stream) {
|
|
1094
|
-
const { schemas: schemaCount, operations: operationCount } = await config.adapter.count(source)
|
|
1095
|
-
|
|
1096
|
-
if (schemaCount > STREAM_SCHEMA_THRESHOLD) {
|
|
1097
|
-
driver.inputStreamNode = await config.adapter.stream(source)
|
|
1098
|
-
|
|
1099
|
-
await hooks.emit('kubb:debug', {
|
|
1100
|
-
date: new Date(),
|
|
1101
|
-
logs: [
|
|
1102
|
-
`✓ Adapter '${config.adapter.name}' streaming InputStreamNode`,
|
|
1103
|
-
` • Schemas: ${schemaCount} (threshold: ${STREAM_SCHEMA_THRESHOLD})`,
|
|
1104
|
-
` • Operations: ${operationCount}`,
|
|
1105
|
-
],
|
|
1106
|
-
})
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
if (!driver.inputStreamNode) {
|
|
1111
|
-
driver.inputNode = await config.adapter.parse(source)
|
|
1112
|
-
|
|
1113
|
-
await hooks.emit('kubb:debug', {
|
|
1114
|
-
date: new Date(),
|
|
1115
|
-
logs: [
|
|
1116
|
-
`✓ Adapter '${config.adapter.name}' resolved InputNode`,
|
|
1117
|
-
` • Schemas: ${driver.inputNode.schemas.length}`,
|
|
1118
|
-
` • Operations: ${driver.inputNode.operations.length}`,
|
|
1119
|
-
],
|
|
1120
|
-
})
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1064
|
+
await driver.setup()
|
|
1123
1065
|
|
|
1124
1066
|
return {
|
|
1125
1067
|
config,
|
|
@@ -1132,368 +1074,301 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
1132
1074
|
|
|
1133
1075
|
function dispose() {
|
|
1134
1076
|
driver.dispose()
|
|
1135
|
-
for (const [event, handler] of middlewareListeners) {
|
|
1136
|
-
hooks.off(event, handler as never)
|
|
1137
|
-
}
|
|
1138
1077
|
}
|
|
1139
1078
|
}
|
|
1140
1079
|
|
|
1141
|
-
type
|
|
1142
|
-
plugin: NormalizedPlugin
|
|
1143
|
-
context: GeneratorContext
|
|
1144
|
-
hrStart: ReturnType<typeof process.hrtime>
|
|
1145
|
-
}
|
|
1080
|
+
type GeneratorEntry = { plugin: NormalizedPlugin; context: GeneratorContext; hrStart: ReturnType<typeof process.hrtime> }
|
|
1146
1081
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
generators: Generator[]
|
|
1151
|
-
hrStart: ReturnType<typeof process.hrtime>
|
|
1152
|
-
failed: boolean
|
|
1153
|
-
error: Error | undefined
|
|
1154
|
-
/**
|
|
1155
|
-
* `true` when the plugin's options have no `include`, `exclude`, or `override`
|
|
1156
|
-
* filters. The per-node `resolveOptions` call always returns the same `options`
|
|
1157
|
-
* reference in that case, so the inner loop can skip it entirely.
|
|
1158
|
-
*/
|
|
1159
|
-
optionsAreStatic: boolean
|
|
1160
|
-
}
|
|
1082
|
+
async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
1083
|
+
using _cleanup = setupResult
|
|
1084
|
+
const { driver, hooks, storage } = setupResult
|
|
1161
1085
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
driver,
|
|
1176
|
-
pluginTimings,
|
|
1177
|
-
failedPlugins,
|
|
1178
|
-
}: {
|
|
1179
|
-
entries: PluginStreamEntry[]
|
|
1180
|
-
driver: PluginDriver
|
|
1181
|
-
pluginTimings: Map<string, number>
|
|
1182
|
-
failedPlugins: Set<{ plugin: Plugin; error: Error }>
|
|
1183
|
-
}): Promise<void> {
|
|
1184
|
-
const inputStreamNode = driver.inputStreamNode!
|
|
1185
|
-
function resolveRendererFor(gen: Generator, state: PluginState): RendererFactory | undefined {
|
|
1186
|
-
return gen.renderer === null ? undefined : (gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer)
|
|
1086
|
+
const failedPlugins = new Set<{ plugin: Plugin; error: Error }>()
|
|
1087
|
+
const pluginTimings = new Map<string, number>()
|
|
1088
|
+
const config = driver.config
|
|
1089
|
+
const writtenPaths = new Set<string>()
|
|
1090
|
+
const parsersMap = new Map<FileNode['extname'], Parser>()
|
|
1091
|
+
const fileProcessor = new FileProcessor()
|
|
1092
|
+
|
|
1093
|
+
for (const parser of config.parsers) {
|
|
1094
|
+
if (parser.extNames) {
|
|
1095
|
+
for (const extname of parser.extNames) {
|
|
1096
|
+
parsersMap.set(extname, parser)
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1187
1099
|
}
|
|
1188
1100
|
|
|
1189
|
-
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
const hasOverride = Array.isArray(override) && override.length > 0
|
|
1194
|
-
return {
|
|
1195
|
-
plugin,
|
|
1196
|
-
generatorContext: { ...context, resolver: driver.getResolver(plugin.name) },
|
|
1197
|
-
generators: plugin.generators ?? [],
|
|
1198
|
-
hrStart,
|
|
1199
|
-
failed: false,
|
|
1200
|
-
error: undefined,
|
|
1201
|
-
optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
|
|
1101
|
+
async function flushPendingFiles(): Promise<void> {
|
|
1102
|
+
const files = driver.fileManager.files.filter((f) => !writtenPaths.has(f.path))
|
|
1103
|
+
if (files.length === 0) {
|
|
1104
|
+
return
|
|
1202
1105
|
}
|
|
1203
|
-
})
|
|
1204
1106
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1107
|
+
await hooks.emit('kubb:debug', {
|
|
1108
|
+
date: new Date(),
|
|
1109
|
+
logs: [`Writing ${files.length} files...`],
|
|
1110
|
+
})
|
|
1207
1111
|
|
|
1208
|
-
|
|
1209
|
-
const { plugin, generatorContext, generators } = state
|
|
1210
|
-
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
1211
|
-
const { exclude, include, override } = plugin.options
|
|
1212
|
-
const options: typeof plugin.options | null = state.optionsAreStatic
|
|
1213
|
-
? plugin.options
|
|
1214
|
-
: generatorContext.resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
1112
|
+
await hooks.emit('kubb:files:processing:start', { files })
|
|
1215
1113
|
|
|
1216
|
-
|
|
1114
|
+
const stream = fileProcessor.stream(files, { parsers: parsersMap, extension: config.output.extension })
|
|
1217
1115
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1116
|
+
const queue: Array<Promise<void>> = []
|
|
1117
|
+
for (const { file, source, processed, total, percentage } of stream) {
|
|
1118
|
+
writtenPaths.add(file.path)
|
|
1119
|
+
queue.push(
|
|
1120
|
+
(async () => {
|
|
1121
|
+
await hooks.emit('kubb:file:processing:update', { file, source, processed, total, percentage, config })
|
|
1122
|
+
if (source) {
|
|
1123
|
+
await storage.setItem(file.path, source)
|
|
1124
|
+
}
|
|
1125
|
+
})(),
|
|
1126
|
+
)
|
|
1127
|
+
if (queue.length >= STREAM_FLUSH_EVERY) {
|
|
1128
|
+
await Promise.all(queue.splice(0))
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
await Promise.all(queue)
|
|
1221
1132
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1133
|
+
await hooks.emit('kubb:files:processing:end', { files })
|
|
1134
|
+
await hooks.emit('kubb:debug', {
|
|
1135
|
+
date: new Date(),
|
|
1136
|
+
logs: [`✓ File write process completed for ${files.length} files`],
|
|
1137
|
+
})
|
|
1138
|
+
}
|
|
1225
1139
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1140
|
+
async function dispatchOperationsToGenerators(
|
|
1141
|
+
generators: Generator[],
|
|
1142
|
+
collectedOperations: OperationNode[],
|
|
1143
|
+
ctx: GeneratorContext,
|
|
1144
|
+
rendererFor: (gen: Generator) => RendererFactory | undefined,
|
|
1145
|
+
): Promise<void> {
|
|
1146
|
+
for (const gen of generators) {
|
|
1147
|
+
if (!gen.operations) continue
|
|
1148
|
+
const result = await gen.operations(collectedOperations, ctx)
|
|
1149
|
+
await applyHookResult({ result, driver, rendererFactory: rendererFor(gen) })
|
|
1232
1150
|
}
|
|
1151
|
+
await driver.hooks.emit('kubb:generate:operations', collectedOperations, ctx)
|
|
1233
1152
|
}
|
|
1234
1153
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1154
|
+
/**
|
|
1155
|
+
* Single-pass fan-out: iterates all schemas and operations once, distributing each node
|
|
1156
|
+
* to every generator-plugin in parallel. This replaces the N-pass-per-plugin pattern
|
|
1157
|
+
* (each plugin getting its own iterator) with one parse pass fanned to all plugins,
|
|
1158
|
+
* eliminating the N×parse-time overhead for multi-plugin builds.
|
|
1159
|
+
*/
|
|
1160
|
+
async function runPlugins(entries: Array<GeneratorEntry>): Promise<void> {
|
|
1161
|
+
type PluginState = {
|
|
1162
|
+
plugin: NormalizedPlugin
|
|
1163
|
+
generatorContext: GeneratorContext
|
|
1164
|
+
generators: Generator[]
|
|
1165
|
+
hrStart: ReturnType<typeof process.hrtime>
|
|
1166
|
+
failed: boolean
|
|
1167
|
+
error: Error | undefined
|
|
1168
|
+
/**
|
|
1169
|
+
* `true` when the plugin's options have no `include`, `exclude`, or `override`
|
|
1170
|
+
* filters. The per-node `resolveOptions` call always returns the same `options`
|
|
1171
|
+
* reference in that case, so the inner loop can skip it entirely.
|
|
1172
|
+
*/
|
|
1173
|
+
optionsAreStatic: boolean
|
|
1174
|
+
/**
|
|
1175
|
+
* Set when the plugin has operation-based includes (tag, operationId, path, method, contentType)
|
|
1176
|
+
* but no schemaName includes. Schema nodes whose name is not in this set are skipped,
|
|
1177
|
+
* matching the pruning behavior of the eager path.
|
|
1178
|
+
*/
|
|
1179
|
+
allowedSchemaNames: Set<string> | undefined
|
|
1180
|
+
}
|
|
1244
1181
|
|
|
1245
|
-
|
|
1182
|
+
const { schemas, operations } = driver.inputNode!
|
|
1183
|
+
const operationFilterTypes = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])
|
|
1184
|
+
const states: PluginState[] = entries.map(({ plugin, context, hrStart }) => {
|
|
1185
|
+
const { exclude, include, override } = plugin.options
|
|
1186
|
+
const hasExclude = Array.isArray(exclude) && exclude.length > 0
|
|
1187
|
+
const hasInclude = Array.isArray(include) && include.length > 0
|
|
1188
|
+
const hasOverride = Array.isArray(override) && override.length > 0
|
|
1189
|
+
return {
|
|
1190
|
+
plugin,
|
|
1191
|
+
generatorContext: { ...context, resolver: driver.getResolver(plugin.name) },
|
|
1192
|
+
generators: plugin.generators ?? [],
|
|
1193
|
+
hrStart,
|
|
1194
|
+
failed: false,
|
|
1195
|
+
error: undefined,
|
|
1196
|
+
optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
|
|
1197
|
+
allowedSchemaNames: undefined,
|
|
1198
|
+
}
|
|
1199
|
+
})
|
|
1246
1200
|
|
|
1247
|
-
|
|
1201
|
+
// Pre-scan: compute allowedSchemaNames for plugins that use operation-based includes
|
|
1202
|
+
// without schemaName filters. Each AsyncIterable yields a fresh iterator on every call,
|
|
1203
|
+
// so consuming them here does not affect the main dispatch passes below.
|
|
1204
|
+
const pruningStates = states.filter(({ plugin }) => {
|
|
1205
|
+
const { include } = plugin.options
|
|
1206
|
+
return (include?.some(({ type }) => operationFilterTypes.has(type)) ?? false) && !(include?.some(({ type }) => type === 'schemaName') ?? false)
|
|
1207
|
+
})
|
|
1248
1208
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1209
|
+
if (pruningStates.length > 0) {
|
|
1210
|
+
// Known trade-off: computing the reachable-schema set for operation-based includes
|
|
1211
|
+
// requires the full schema graph in memory at once — there is no way to determine
|
|
1212
|
+
// transitive reachability from a single schema node in isolation.
|
|
1213
|
+
// `allSchemas` is released as soon as this block exits; it is never held past
|
|
1214
|
+
// the pruning pre-scan. The main dispatch passes below each get their own
|
|
1215
|
+
// fresh iterator from the AsyncIterable, so this consumption does not affect them.
|
|
1216
|
+
const allSchemas: SchemaNode[] = []
|
|
1217
|
+
for await (const schema of schemas) {
|
|
1218
|
+
allSchemas.push(schema)
|
|
1219
|
+
}
|
|
1251
1220
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1221
|
+
// Collect the included operations for each pruning plugin in one shared pass.
|
|
1222
|
+
const includedOpsByState = new Map<PluginState, OperationNode[]>(pruningStates.map((s) => [s, []]))
|
|
1223
|
+
for await (const operation of operations) {
|
|
1224
|
+
for (const state of pruningStates) {
|
|
1225
|
+
const { exclude, include, override } = state.plugin.options
|
|
1226
|
+
const options = state.generatorContext.resolver.resolveOptions(operation, { options: state.plugin.options, exclude, include, override })
|
|
1227
|
+
if (options !== null) includedOpsByState.get(state)?.push(operation)
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1255
1230
|
|
|
1256
|
-
|
|
1231
|
+
// Derive the allowed schema name set per pruning plugin.
|
|
1232
|
+
for (const state of pruningStates) {
|
|
1233
|
+
state.allowedSchemaNames = collectUsedSchemaNames(includedOpsByState.get(state) ?? [], allSchemas)
|
|
1257
1234
|
}
|
|
1258
|
-
await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
|
|
1259
|
-
} catch (caughtError) {
|
|
1260
|
-
state.failed = true
|
|
1261
|
-
state.error = caughtError as Error
|
|
1262
1235
|
}
|
|
1263
|
-
}
|
|
1264
1236
|
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
// plugin chain remains deterministic.
|
|
1269
|
-
await Promise.all(states.map((state) => dispatchSchema(state, node)))
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
const collectedOperations: OperationNode[] = []
|
|
1273
|
-
|
|
1274
|
-
for await (const node of inputStreamNode.operations) {
|
|
1275
|
-
collectedOperations.push(node)
|
|
1237
|
+
function resolveRendererFor(gen: Generator, state: PluginState): RendererFactory | undefined {
|
|
1238
|
+
return gen.renderer === null ? undefined : (gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer)
|
|
1239
|
+
}
|
|
1276
1240
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1241
|
+
async function dispatchSchema(state: PluginState, node: SchemaNode): Promise<void> {
|
|
1242
|
+
if (state.failed) return
|
|
1279
1243
|
|
|
1280
|
-
// After stream: gen.operations for each plugin, then emit plugin:end
|
|
1281
|
-
for (const state of states) {
|
|
1282
|
-
if (!state.failed) {
|
|
1283
1244
|
try {
|
|
1284
1245
|
const { plugin, generatorContext, generators } = state
|
|
1285
|
-
|
|
1246
|
+
|
|
1247
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
1248
|
+
|
|
1249
|
+
// Skip named top-level schemas not reachable from any included operation.
|
|
1250
|
+
if (state.allowedSchemaNames !== undefined && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) {
|
|
1251
|
+
return
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
const { exclude, include, override } = plugin.options
|
|
1255
|
+
const options = state.optionsAreStatic
|
|
1256
|
+
? plugin.options
|
|
1257
|
+
: generatorContext.resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
1258
|
+
if (options === null) return
|
|
1259
|
+
|
|
1260
|
+
const ctx = { ...generatorContext, options }
|
|
1286
1261
|
|
|
1287
1262
|
for (const gen of generators) {
|
|
1288
|
-
if (!gen.
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1263
|
+
if (!gen.schema) continue
|
|
1264
|
+
const raw = gen.schema(transformedNode, ctx)
|
|
1265
|
+
const result = isPromise(raw) ? await raw : raw
|
|
1266
|
+
const applied = applyHookResult({ result, driver, rendererFactory: resolveRendererFor(gen, state) })
|
|
1267
|
+
if (isPromise(applied)) await applied
|
|
1291
1268
|
}
|
|
1292
1269
|
|
|
1293
|
-
await driver.hooks.emit('kubb:generate:
|
|
1270
|
+
await driver.hooks.emit('kubb:generate:schema', transformedNode, ctx)
|
|
1294
1271
|
} catch (caughtError) {
|
|
1295
1272
|
state.failed = true
|
|
1296
1273
|
state.error = caughtError as Error
|
|
1297
1274
|
}
|
|
1298
1275
|
}
|
|
1299
1276
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1277
|
+
async function dispatchOperation(state: PluginState, node: OperationNode): Promise<void> {
|
|
1278
|
+
if (state.failed) return
|
|
1302
1279
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
duration,
|
|
1306
|
-
success: !state.failed,
|
|
1307
|
-
...(state.failed && state.error ? { error: state.error } : {}),
|
|
1308
|
-
config: driver.config,
|
|
1309
|
-
get files() {
|
|
1310
|
-
return driver.fileManager.files
|
|
1311
|
-
},
|
|
1312
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1313
|
-
})
|
|
1280
|
+
try {
|
|
1281
|
+
const { plugin, generatorContext, generators } = state
|
|
1314
1282
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1283
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
1284
|
+
const { exclude, include, override } = plugin.options
|
|
1285
|
+
const options = state.optionsAreStatic
|
|
1286
|
+
? plugin.options
|
|
1287
|
+
: generatorContext.resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
1288
|
+
if (options === null) return
|
|
1318
1289
|
|
|
1319
|
-
|
|
1320
|
-
date: new Date(),
|
|
1321
|
-
logs: [state.failed ? '✗ Plugin start failed' : `✓ Plugin started successfully (${formatMs(duration)})`],
|
|
1322
|
-
})
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1290
|
+
const ctx = { ...generatorContext, options }
|
|
1325
1291
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
* schemas that fall outside that set. This ensures that component schemas referenced
|
|
1334
|
-
* exclusively by excluded operations are not generated.
|
|
1335
|
-
*/
|
|
1336
|
-
async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorContext): Promise<void> {
|
|
1337
|
-
const { adapter, inputNode, resolver, driver } = context
|
|
1338
|
-
const { exclude, include, override } = plugin.options
|
|
1292
|
+
for (const gen of generators) {
|
|
1293
|
+
if (!gen.operation) continue
|
|
1294
|
+
const raw = gen.operation(transformedNode, ctx)
|
|
1295
|
+
const result = isPromise(raw) ? await raw : raw
|
|
1296
|
+
const applied = applyHookResult({ result, driver, rendererFactory: resolveRendererFor(gen, state) })
|
|
1297
|
+
if (isPromise(applied)) await applied
|
|
1298
|
+
}
|
|
1339
1299
|
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1300
|
+
await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
|
|
1301
|
+
} catch (caughtError) {
|
|
1302
|
+
state.failed = true
|
|
1303
|
+
state.error = caughtError as Error
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1343
1306
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1307
|
+
// Batch schemas: SCHEMA_PARALLEL nodes dispatched across all plugins concurrently.
|
|
1308
|
+
// Per-plugin work inside dispatchSchema stays sequential so FileManager.upsert
|
|
1309
|
+
// ordering for any single plugin chain remains deterministic.
|
|
1310
|
+
await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((n) => states.map((state) => dispatchSchema(state, n)))), {
|
|
1311
|
+
concurrency: SCHEMA_PARALLEL,
|
|
1312
|
+
flush: flushPendingFiles,
|
|
1313
|
+
})
|
|
1347
1314
|
|
|
1348
|
-
|
|
1349
|
-
const collectedOperations: Array<OperationNode> = []
|
|
1315
|
+
const collectedOperations: OperationNode[] = []
|
|
1350
1316
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1317
|
+
await forBatches(
|
|
1318
|
+
operations,
|
|
1319
|
+
(nodes) => {
|
|
1320
|
+
collectedOperations.push(...nodes)
|
|
1321
|
+
return Promise.all(nodes.flatMap((n) => states.map((state) => dispatchOperation(state, n))))
|
|
1322
|
+
},
|
|
1323
|
+
{ concurrency: SCHEMA_PARALLEL, flush: flushPendingFiles },
|
|
1324
|
+
)
|
|
1355
1325
|
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
const includedOps = inputNode!.operations.filter((op) => resolver.resolveOptions(op, { options: plugin.options, exclude, include, override }) !== null)
|
|
1367
|
-
return collectUsedSchemaNames(includedOps, inputNode!.schemas)
|
|
1368
|
-
})()
|
|
1369
|
-
|
|
1370
|
-
await walk(inputNode!, {
|
|
1371
|
-
depth: 'shallow',
|
|
1372
|
-
async schema(node) {
|
|
1373
|
-
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
1374
|
-
|
|
1375
|
-
// Skip named top-level schemas that are not reachable from any included operation.
|
|
1376
|
-
if (allowedSchemaNames !== undefined && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) {
|
|
1377
|
-
return
|
|
1326
|
+
for (const state of states) {
|
|
1327
|
+
if (!state.failed) {
|
|
1328
|
+
try {
|
|
1329
|
+
const { plugin, generatorContext, generators } = state
|
|
1330
|
+
const ctx = { ...generatorContext, options: plugin.options }
|
|
1331
|
+
await dispatchOperationsToGenerators(generators, collectedOperations, ctx, (gen) => resolveRendererFor(gen, state))
|
|
1332
|
+
} catch (caughtError) {
|
|
1333
|
+
state.failed = true
|
|
1334
|
+
state.error = caughtError as Error
|
|
1335
|
+
}
|
|
1378
1336
|
}
|
|
1379
1337
|
|
|
1380
|
-
const
|
|
1381
|
-
|
|
1382
|
-
exclude,
|
|
1383
|
-
include,
|
|
1384
|
-
override,
|
|
1385
|
-
})
|
|
1386
|
-
if (options === null) return
|
|
1387
|
-
|
|
1388
|
-
const ctx = { ...generatorContext, options }
|
|
1389
|
-
|
|
1390
|
-
await Promise.all(
|
|
1391
|
-
generators
|
|
1392
|
-
.filter((gen) => gen.schema)
|
|
1393
|
-
.map(async (gen) => {
|
|
1394
|
-
const result = await gen.schema!(transformedNode, ctx)
|
|
1395
|
-
return applyHookResult({ result, driver, rendererFactory: resolveRenderer(gen) })
|
|
1396
|
-
}),
|
|
1397
|
-
)
|
|
1338
|
+
const duration = getElapsedMs(state.hrStart)
|
|
1339
|
+
pluginTimings.set(state.plugin.name, duration)
|
|
1398
1340
|
|
|
1399
|
-
await driver.hooks.emit('kubb:
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1341
|
+
await driver.hooks.emit('kubb:plugin:end', {
|
|
1342
|
+
plugin: state.plugin,
|
|
1343
|
+
duration,
|
|
1344
|
+
success: !state.failed,
|
|
1345
|
+
...(state.failed && state.error ? { error: state.error } : {}),
|
|
1346
|
+
config: driver.config,
|
|
1347
|
+
get files() {
|
|
1348
|
+
return driver.fileManager.files
|
|
1349
|
+
},
|
|
1350
|
+
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1408
1351
|
})
|
|
1409
|
-
if (options === null) return
|
|
1410
|
-
|
|
1411
|
-
collectedOperations.push(transformedNode)
|
|
1412
|
-
|
|
1413
|
-
const ctx = { ...generatorContext, options }
|
|
1414
|
-
|
|
1415
|
-
await Promise.all(
|
|
1416
|
-
generators
|
|
1417
|
-
.filter((gen) => gen.operation)
|
|
1418
|
-
.map(async (gen) => {
|
|
1419
|
-
const result = await gen.operation!(transformedNode, ctx)
|
|
1420
|
-
return applyHookResult({ result, driver, rendererFactory: resolveRenderer(gen) })
|
|
1421
|
-
}),
|
|
1422
|
-
)
|
|
1423
|
-
|
|
1424
|
-
await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
|
|
1425
|
-
},
|
|
1426
|
-
})
|
|
1427
|
-
|
|
1428
|
-
if (collectedOperations.length > 0) {
|
|
1429
|
-
const ctx = { ...generatorContext, options: plugin.options }
|
|
1430
|
-
|
|
1431
|
-
for (const gen of generators) {
|
|
1432
|
-
if (!gen.operations) continue
|
|
1433
|
-
const result = await gen.operations(collectedOperations, ctx)
|
|
1434
|
-
await applyHookResult({ result, driver, rendererFactory: resolveRenderer(gen) })
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
await driver.hooks.emit('kubb:generate:operations', collectedOperations, ctx)
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
1442
|
-
using _cleanup = setupResult
|
|
1443
|
-
const { driver, hooks, storage } = setupResult
|
|
1444
1352
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
const config = driver.config
|
|
1448
|
-
const writtenPaths = new Set<string>()
|
|
1449
|
-
const parsersMap = new Map<FileNode['extname'], Parser>()
|
|
1450
|
-
for (const parser of config.parsers) {
|
|
1451
|
-
if (parser.extNames) {
|
|
1452
|
-
for (const extname of parser.extNames) {
|
|
1453
|
-
parsersMap.set(extname, parser)
|
|
1353
|
+
if (state.failed && state.error) {
|
|
1354
|
+
failedPlugins.add({ plugin: state.plugin, error: state.error })
|
|
1454
1355
|
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
const fileProcessor = new FileProcessor()
|
|
1458
|
-
|
|
1459
|
-
async function flushPendingFiles(): Promise<void> {
|
|
1460
|
-
const files = driver.fileManager.files.filter((f) => !writtenPaths.has(f.path))
|
|
1461
|
-
if (files.length === 0) {
|
|
1462
|
-
return
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
await hooks.emit('kubb:debug', {
|
|
1466
|
-
date: new Date(),
|
|
1467
|
-
logs: [`Writing ${files.length} files...`],
|
|
1468
|
-
})
|
|
1469
|
-
|
|
1470
|
-
await hooks.emit('kubb:files:processing:start', { files })
|
|
1471
1356
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
if (source) {
|
|
1477
|
-
await storage.setItem(file.path, source)
|
|
1478
|
-
}
|
|
1479
|
-
writtenPaths.add(file.path)
|
|
1357
|
+
await driver.hooks.emit('kubb:debug', {
|
|
1358
|
+
date: new Date(),
|
|
1359
|
+
logs: [state.failed ? '✗ Plugin start failed' : `✓ Plugin started successfully (${formatMs(duration)})`],
|
|
1360
|
+
})
|
|
1480
1361
|
}
|
|
1481
|
-
|
|
1482
|
-
await hooks.emit('kubb:files:processing:end', { files })
|
|
1483
|
-
await hooks.emit('kubb:debug', {
|
|
1484
|
-
date: new Date(),
|
|
1485
|
-
logs: [`✓ File write process completed for ${files.length} files`],
|
|
1486
|
-
})
|
|
1487
1362
|
}
|
|
1488
1363
|
|
|
1489
1364
|
try {
|
|
1490
1365
|
await driver.emitSetupHooks()
|
|
1491
1366
|
|
|
1492
|
-
if (driver.adapter &&
|
|
1367
|
+
if (driver.adapter && driver.inputNode) {
|
|
1493
1368
|
await hooks.emit('kubb:build:start', {
|
|
1494
1369
|
config,
|
|
1495
1370
|
adapter: driver.adapter,
|
|
1496
|
-
|
|
1371
|
+
meta: driver.inputNode.meta,
|
|
1497
1372
|
getPlugin: driver.getPlugin.bind(driver),
|
|
1498
1373
|
get files() {
|
|
1499
1374
|
return driver.fileManager.files
|
|
@@ -1502,70 +1377,73 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
1502
1377
|
})
|
|
1503
1378
|
}
|
|
1504
1379
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
const streamPluginEntries: PluginStreamEntry[] = []
|
|
1380
|
+
// Always run the plugin lifecycle so middleware hooks (kubb:plugin:start,
|
|
1381
|
+
// kubb:plugin:end) fire even when no adapter is configured.
|
|
1382
|
+
// Generator-plugins are collected for the stream fan-out pass below.
|
|
1383
|
+
const generatorPlugins: Array<GeneratorEntry> = []
|
|
1510
1384
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1385
|
+
for (const plugin of driver.plugins.values()) {
|
|
1386
|
+
const context = driver.getContext(plugin)
|
|
1387
|
+
const hrStart = process.hrtime()
|
|
1514
1388
|
|
|
1389
|
+
try {
|
|
1515
1390
|
await hooks.emit('kubb:plugin:start', { plugin })
|
|
1516
1391
|
await hooks.emit('kubb:debug', {
|
|
1517
1392
|
date: new Date(),
|
|
1518
1393
|
logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
|
|
1519
1394
|
})
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
streamPluginEntries.push({ plugin, context, hrStart })
|
|
1523
|
-
continue
|
|
1524
|
-
}
|
|
1525
|
-
// No generators: plugin ran via setup hooks; finish it now.
|
|
1395
|
+
} catch (caughtError) {
|
|
1396
|
+
const error = caughtError as Error
|
|
1526
1397
|
const duration = getElapsedMs(hrStart)
|
|
1527
1398
|
pluginTimings.set(plugin.name, duration)
|
|
1528
1399
|
await hooks.emit('kubb:plugin:end', {
|
|
1529
1400
|
plugin,
|
|
1530
1401
|
duration,
|
|
1531
|
-
success:
|
|
1402
|
+
success: false,
|
|
1403
|
+
error,
|
|
1532
1404
|
config,
|
|
1533
1405
|
get files() {
|
|
1534
1406
|
return driver.fileManager.files
|
|
1535
1407
|
},
|
|
1536
1408
|
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1537
1409
|
})
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
|
|
1541
|
-
})
|
|
1410
|
+
failedPlugins.add({ plugin, error })
|
|
1411
|
+
continue
|
|
1542
1412
|
}
|
|
1543
1413
|
|
|
1544
|
-
if (
|
|
1545
|
-
|
|
1546
|
-
|
|
1414
|
+
if (plugin.generators?.length || driver.hasEventGenerators(plugin.name)) {
|
|
1415
|
+
generatorPlugins.push({ plugin, context, hrStart })
|
|
1416
|
+
continue
|
|
1547
1417
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1418
|
+
// No generators: plugin ran via setup hooks; finish it now.
|
|
1419
|
+
const duration = getElapsedMs(hrStart)
|
|
1420
|
+
pluginTimings.set(plugin.name, duration)
|
|
1421
|
+
await hooks.emit('kubb:plugin:end', {
|
|
1422
|
+
plugin,
|
|
1423
|
+
duration,
|
|
1424
|
+
success: true,
|
|
1425
|
+
config,
|
|
1426
|
+
get files() {
|
|
1427
|
+
return driver.fileManager.files
|
|
1428
|
+
},
|
|
1429
|
+
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1430
|
+
})
|
|
1431
|
+
await hooks.emit('kubb:debug', {
|
|
1432
|
+
date: new Date(),
|
|
1433
|
+
logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
|
|
1434
|
+
})
|
|
1435
|
+
}
|
|
1565
1436
|
|
|
1437
|
+
if (generatorPlugins.length > 0) {
|
|
1438
|
+
if (driver.inputNode) {
|
|
1439
|
+
// Normal path: fan-out schemas and operations to all generator-plugins in one pass.
|
|
1440
|
+
await withDrain(() => runPlugins(generatorPlugins), flushPendingFiles)
|
|
1441
|
+
} else {
|
|
1442
|
+
// No adapter configured — generator-plugins have nothing to process.
|
|
1443
|
+
// Still emit plugin:end so middleware hooks (e.g. barrel) complete their lifecycle.
|
|
1444
|
+
for (const { plugin, hrStart } of generatorPlugins) {
|
|
1566
1445
|
const duration = getElapsedMs(hrStart)
|
|
1567
1446
|
pluginTimings.set(plugin.name, duration)
|
|
1568
|
-
|
|
1569
1447
|
await hooks.emit('kubb:plugin:end', {
|
|
1570
1448
|
plugin,
|
|
1571
1449
|
duration,
|
|
@@ -1576,42 +1454,7 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
1576
1454
|
},
|
|
1577
1455
|
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1578
1456
|
})
|
|
1579
|
-
|
|
1580
|
-
await hooks.emit('kubb:debug', {
|
|
1581
|
-
date: new Date(),
|
|
1582
|
-
logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
|
|
1583
|
-
})
|
|
1584
|
-
} catch (caughtError) {
|
|
1585
|
-
const error = caughtError as Error
|
|
1586
|
-
const errorTimestamp = new Date()
|
|
1587
|
-
const duration = getElapsedMs(hrStart)
|
|
1588
|
-
|
|
1589
|
-
await hooks.emit('kubb:plugin:end', {
|
|
1590
|
-
plugin,
|
|
1591
|
-
duration,
|
|
1592
|
-
success: false,
|
|
1593
|
-
error,
|
|
1594
|
-
config,
|
|
1595
|
-
get files() {
|
|
1596
|
-
return driver.fileManager.files
|
|
1597
|
-
},
|
|
1598
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1599
|
-
})
|
|
1600
|
-
|
|
1601
|
-
await hooks.emit('kubb:debug', {
|
|
1602
|
-
date: errorTimestamp,
|
|
1603
|
-
logs: [
|
|
1604
|
-
'✗ Plugin start failed',
|
|
1605
|
-
` • Plugin Name: ${plugin.name}`,
|
|
1606
|
-
` • Error: ${error.constructor.name} - ${error.message}`,
|
|
1607
|
-
' • Stack Trace:',
|
|
1608
|
-
error.stack || 'No stack trace available',
|
|
1609
|
-
],
|
|
1610
|
-
})
|
|
1611
|
-
|
|
1612
|
-
failedPlugins.add({ plugin, error })
|
|
1613
1457
|
}
|
|
1614
|
-
await flushPendingFiles()
|
|
1615
1458
|
}
|
|
1616
1459
|
}
|
|
1617
1460
|
|
|
@@ -1700,25 +1543,6 @@ export function isInputPath(config: Config | UserConfig | undefined): config is
|
|
|
1700
1543
|
return typeof config?.input === 'object' && config.input !== null && 'path' in config.input
|
|
1701
1544
|
}
|
|
1702
1545
|
|
|
1703
|
-
function inputToAdapterSource(config: Config): AdapterSource {
|
|
1704
|
-
const input = config.input
|
|
1705
|
-
if (!input) {
|
|
1706
|
-
throw new Error('[kubb] input is required when using an adapter. Provide input.path or input.data in your config.')
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
if ('data' in input) {
|
|
1710
|
-
return { type: 'data', data: input.data }
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
if (new URLPath(input.path).isURL) {
|
|
1714
|
-
return { type: 'path', path: input.path }
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
const resolved = resolve(config.root, input.path)
|
|
1718
|
-
|
|
1719
|
-
return { type: 'path', path: resolved }
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
1546
|
type CreateKubbOptions = {
|
|
1723
1547
|
hooks?: AsyncEventEmitter<KubbHooks>
|
|
1724
1548
|
}
|