@kubb/core 5.0.0-beta.20 → 5.0.0-beta.22
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/{KubbDriver-BXSnJ3qM.cjs → KubbDriver-DLha_xyo.cjs} +759 -108
- package/dist/KubbDriver-DLha_xyo.cjs.map +1 -0
- package/dist/{KubbDriver-Cxii_rBp.js → KubbDriver-l31wllgN.js} +737 -92
- package/dist/KubbDriver-l31wllgN.js.map +1 -0
- package/dist/{createKubb-Dcmtjqds.d.ts → createKubb-CYrw_xaR.d.ts} +91 -89
- package/dist/index.cjs +138 -762
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +136 -761
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +4 -4
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +1 -1
- package/dist/mocks.js +4 -4
- package/dist/mocks.js.map +1 -1
- package/package.json +4 -4
- package/src/FileManager.ts +65 -60
- package/src/FileProcessor.ts +11 -0
- package/src/KubbDriver.ts +368 -28
- package/src/createKubb.ts +144 -646
- package/src/createRenderer.ts +10 -0
- package/src/defineResolver.ts +7 -7
- package/src/mocks.ts +3 -3
- package/src/types.ts +2 -1
- package/dist/KubbDriver-BXSnJ3qM.cjs.map +0 -1
- package/dist/KubbDriver-Cxii_rBp.js.map +0 -1
package/src/createKubb.ts
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
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,
|
|
4
|
+
import { AsyncEventEmitter, BuildError, exists, URLPath } from '@internals/utils'
|
|
5
5
|
import type { FileNode, InputMeta, OperationNode, SchemaNode } from '@kubb/ast'
|
|
6
|
-
import { collectUsedSchemaNames, transform } from '@kubb/ast'
|
|
7
6
|
import { version as KubbVersion } from '../package.json'
|
|
8
|
-
import { DEFAULT_BANNER, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL
|
|
7
|
+
import { DEFAULT_BANNER, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
|
|
9
8
|
import type { Adapter } from './createAdapter.ts'
|
|
10
9
|
import type { RendererFactory } from './createRenderer.ts'
|
|
11
10
|
import { createStorage, type Storage } from './createStorage.ts'
|
|
12
|
-
import type { GeneratorContext
|
|
11
|
+
import type { GeneratorContext } from './defineGenerator.ts'
|
|
13
12
|
import type { Middleware } from './defineMiddleware.ts'
|
|
14
13
|
import type { Parser } from './defineParser.ts'
|
|
15
|
-
import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext,
|
|
16
|
-
import { FileProcessor } from './FileProcessor.ts'
|
|
14
|
+
import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext, Plugin } from './definePlugin.ts'
|
|
17
15
|
|
|
18
|
-
import {
|
|
16
|
+
import { KubbDriver } from './KubbDriver.ts'
|
|
19
17
|
import { fsStorage } from './storages/fsStorage.ts'
|
|
20
18
|
|
|
21
19
|
/**
|
|
@@ -517,7 +515,7 @@ export interface KubbHooks {
|
|
|
517
515
|
'kubb:warn': [ctx: KubbWarnContext]
|
|
518
516
|
'kubb:debug': [ctx: KubbDebugContext]
|
|
519
517
|
'kubb:files:processing:start': [ctx: KubbFilesProcessingStartContext]
|
|
520
|
-
'kubb:
|
|
518
|
+
'kubb:files:processing:update': [ctx: KubbFilesProcessingUpdateContext]
|
|
521
519
|
'kubb:files:processing:end': [ctx: KubbFilesProcessingEndContext]
|
|
522
520
|
'kubb:plugin:start': [ctx: KubbPluginStartContext]
|
|
523
521
|
'kubb:plugin:end': [ctx: KubbPluginEndContext]
|
|
@@ -736,7 +734,7 @@ export type KubbFilesProcessingStartContext = {
|
|
|
736
734
|
files: Array<FileNode>
|
|
737
735
|
}
|
|
738
736
|
|
|
739
|
-
export type
|
|
737
|
+
export type KubbFileProcessingUpdate = {
|
|
740
738
|
/**
|
|
741
739
|
* Number of files processed so far in this batch.
|
|
742
740
|
*/
|
|
@@ -763,6 +761,13 @@ export type KubbFileProcessingUpdateContext = {
|
|
|
763
761
|
config: Config
|
|
764
762
|
}
|
|
765
763
|
|
|
764
|
+
export type KubbFilesProcessingUpdateContext = {
|
|
765
|
+
/**
|
|
766
|
+
* All files processed in this flush chunk.
|
|
767
|
+
*/
|
|
768
|
+
files: Array<KubbFileProcessingUpdate>
|
|
769
|
+
}
|
|
770
|
+
|
|
766
771
|
export type KubbFilesProcessingEndContext = {
|
|
767
772
|
/**
|
|
768
773
|
* All files that were serialised in this batch.
|
|
@@ -836,10 +841,6 @@ export type PossibleConfig<TCliOptions = undefined> =
|
|
|
836
841
|
| PossiblePromise<Config | Config[]>
|
|
837
842
|
| ((...args: [TCliOptions] extends [undefined] ? [] : [TCliOptions]) => PossiblePromise<Config | Config[]>)
|
|
838
843
|
|
|
839
|
-
type SetupOptions = {
|
|
840
|
-
hooks?: AsyncEventEmitter<KubbHooks>
|
|
841
|
-
}
|
|
842
|
-
|
|
843
844
|
/**
|
|
844
845
|
* Full output produced by a successful or failed build.
|
|
845
846
|
*/
|
|
@@ -877,76 +878,11 @@ export type BuildOutput = {
|
|
|
877
878
|
storage: Storage
|
|
878
879
|
}
|
|
879
880
|
|
|
880
|
-
/**
|
|
881
|
-
* Kubb code generation instance returned by {@link createKubb}.
|
|
882
|
-
*
|
|
883
|
-
* Use this when orchestrating multiple builds, inspecting plugin timings, or integrating Kubb into a larger toolchain.
|
|
884
|
-
* For a single one-off build, chain directly: `await createKubb(config).build()`.
|
|
885
|
-
*/
|
|
886
|
-
export type Kubb = {
|
|
887
|
-
/**
|
|
888
|
-
* Shared event emitter for lifecycle and status events. Attach listeners before calling `setup()` or `build()`.
|
|
889
|
-
*/
|
|
890
|
-
readonly hooks: AsyncEventEmitter<KubbHooks>
|
|
891
|
-
/**
|
|
892
|
-
* Read-only view of the files from the most recent `build()` or `safeBuild()` call.
|
|
893
|
-
* Only populated after the build completes.
|
|
894
|
-
*
|
|
895
|
-
* Keys are scoped to the current run. Reads go straight to `config.storage`,
|
|
896
|
-
* so nothing extra is held in memory.
|
|
897
|
-
*
|
|
898
|
-
* @example Read a generated file
|
|
899
|
-
* ```ts
|
|
900
|
-
* const { storage } = await kubb.safeBuild()
|
|
901
|
-
* const code = await storage.getItem('/src/gen/pet.ts')
|
|
902
|
-
* ```
|
|
903
|
-
*
|
|
904
|
-
* @example Walk every generated file
|
|
905
|
-
* ```ts
|
|
906
|
-
* for (const path of await kubb.storage.getKeys()) {
|
|
907
|
-
* const code = await kubb.storage.getItem(path)
|
|
908
|
-
* }
|
|
909
|
-
* ```
|
|
910
|
-
*/
|
|
911
|
-
readonly storage: Storage
|
|
912
|
-
/**
|
|
913
|
-
* Plugin driver managing all plugins. Available after `setup()` completes.
|
|
914
|
-
*/
|
|
915
|
-
readonly driver: KubbDriver
|
|
916
|
-
/**
|
|
917
|
-
* Resolved configuration with defaults applied. Available after `setup()` completes.
|
|
918
|
-
*/
|
|
919
|
-
readonly config: Config
|
|
920
|
-
/**
|
|
921
|
-
* Resolves config and initializes the driver. `build()` calls this automatically.
|
|
922
|
-
*/
|
|
923
|
-
setup(): Promise<void>
|
|
924
|
-
/**
|
|
925
|
-
* Runs the full pipeline and throws on any plugin error. Automatically calls `setup()` if needed.
|
|
926
|
-
*/
|
|
927
|
-
build(): Promise<BuildOutput>
|
|
928
|
-
/**
|
|
929
|
-
* Runs the full pipeline and captures errors in `BuildOutput` instead of throwing. Automatically calls `setup()` if needed.
|
|
930
|
-
*/
|
|
931
|
-
safeBuild(): Promise<BuildOutput>
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
type SetupResult = {
|
|
935
|
-
hooks: AsyncEventEmitter<KubbHooks>
|
|
936
|
-
driver: KubbDriver
|
|
937
|
-
storage: Storage
|
|
938
|
-
config: Config
|
|
939
|
-
dispose: () => void
|
|
940
|
-
[Symbol.dispose](): void
|
|
941
|
-
}
|
|
942
|
-
|
|
943
881
|
/**
|
|
944
882
|
* Builds a `Storage` view scoped to the file paths produced by the current build.
|
|
945
|
-
*
|
|
946
|
-
*
|
|
947
|
-
*
|
|
948
|
-
* Writing via `setItem` stores the content in the underlying storage and registers the
|
|
949
|
-
* key so subsequent reads and `getKeys` are scoped to this build's output.
|
|
883
|
+
* Reads delegate to the underlying `storage` so source bytes stay where they were
|
|
884
|
+
* written; writes register the key so subsequent reads and `getKeys` are scoped
|
|
885
|
+
* to this build's output.
|
|
950
886
|
*/
|
|
951
887
|
function createSourcesView(storage: Storage): Storage {
|
|
952
888
|
const paths = new Set<string>()
|
|
@@ -982,9 +918,8 @@ function createSourcesView(storage: Storage): Storage {
|
|
|
982
918
|
}))()
|
|
983
919
|
}
|
|
984
920
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
const config: Config = {
|
|
921
|
+
function resolveConfig(userConfig: UserConfig): Config {
|
|
922
|
+
return {
|
|
988
923
|
...userConfig,
|
|
989
924
|
root: userConfig.root || process.cwd(),
|
|
990
925
|
parsers: userConfig.parsers ?? [],
|
|
@@ -1004,518 +939,6 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
1004
939
|
: undefined,
|
|
1005
940
|
plugins: (userConfig.plugins ?? []) as unknown as Config['plugins'],
|
|
1006
941
|
}
|
|
1007
|
-
const driver = new KubbDriver(config, {
|
|
1008
|
-
hooks,
|
|
1009
|
-
})
|
|
1010
|
-
const storage = createSourcesView(config.storage)
|
|
1011
|
-
const diagnosticInfo = getDiagnosticInfo()
|
|
1012
|
-
|
|
1013
|
-
await hooks.emit('kubb:debug', {
|
|
1014
|
-
date: new Date(),
|
|
1015
|
-
logs: [
|
|
1016
|
-
'Configuration:',
|
|
1017
|
-
` • Name: ${userConfig.name || 'unnamed'}`,
|
|
1018
|
-
` • Root: ${userConfig.root || process.cwd()}`,
|
|
1019
|
-
` • Output: ${userConfig.output?.path || 'not specified'}`,
|
|
1020
|
-
` • Plugins: ${userConfig.plugins?.length || 0}`,
|
|
1021
|
-
'Output Settings:',
|
|
1022
|
-
` • Storage: ${config.storage.name}`,
|
|
1023
|
-
` • Formatter: ${userConfig.output?.format || 'none'}`,
|
|
1024
|
-
` • Linter: ${userConfig.output?.lint || 'none'}`,
|
|
1025
|
-
`Running adapter: ${config.adapter?.name || 'none'}`,
|
|
1026
|
-
'Environment:',
|
|
1027
|
-
Object.entries(diagnosticInfo)
|
|
1028
|
-
.map(([key, value]) => ` • ${key}: ${value}`)
|
|
1029
|
-
.join('\n'),
|
|
1030
|
-
],
|
|
1031
|
-
})
|
|
1032
|
-
|
|
1033
|
-
try {
|
|
1034
|
-
if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
|
|
1035
|
-
await exists(userConfig.input.path)
|
|
1036
|
-
|
|
1037
|
-
await hooks.emit('kubb:debug', {
|
|
1038
|
-
date: new Date(),
|
|
1039
|
-
logs: [`✓ Input file validated: ${userConfig.input.path}`],
|
|
1040
|
-
})
|
|
1041
|
-
}
|
|
1042
|
-
} catch (caughtError) {
|
|
1043
|
-
if (isInputPath(userConfig)) {
|
|
1044
|
-
const error = caughtError as Error
|
|
1045
|
-
|
|
1046
|
-
throw new Error(
|
|
1047
|
-
`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`,
|
|
1048
|
-
{
|
|
1049
|
-
cause: error,
|
|
1050
|
-
},
|
|
1051
|
-
)
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
if (config.output.clean) {
|
|
1056
|
-
await hooks.emit('kubb:debug', {
|
|
1057
|
-
date: new Date(),
|
|
1058
|
-
logs: ['Cleaning output directories', ` • Output: ${config.output.path}`],
|
|
1059
|
-
})
|
|
1060
|
-
|
|
1061
|
-
await config.storage.clear(resolve(config.root, config.output.path))
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
await driver.setup()
|
|
1065
|
-
|
|
1066
|
-
return {
|
|
1067
|
-
config,
|
|
1068
|
-
hooks,
|
|
1069
|
-
driver,
|
|
1070
|
-
storage,
|
|
1071
|
-
dispose,
|
|
1072
|
-
[Symbol.dispose]: dispose,
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
function dispose() {
|
|
1076
|
-
driver.dispose()
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
type GeneratorEntry = { plugin: NormalizedPlugin; context: GeneratorContext; hrStart: ReturnType<typeof process.hrtime> }
|
|
1081
|
-
|
|
1082
|
-
async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
1083
|
-
using _cleanup = setupResult
|
|
1084
|
-
const { driver, hooks, storage } = setupResult
|
|
1085
|
-
|
|
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
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
|
|
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
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
await hooks.emit('kubb:debug', {
|
|
1108
|
-
date: new Date(),
|
|
1109
|
-
logs: [`Writing ${files.length} files...`],
|
|
1110
|
-
})
|
|
1111
|
-
|
|
1112
|
-
await hooks.emit('kubb:files:processing:start', { files })
|
|
1113
|
-
|
|
1114
|
-
const stream = fileProcessor.stream(files, { parsers: parsersMap, extension: config.output.extension })
|
|
1115
|
-
|
|
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)
|
|
1132
|
-
|
|
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
|
-
}
|
|
1139
|
-
|
|
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) })
|
|
1150
|
-
}
|
|
1151
|
-
await driver.hooks.emit('kubb:generate:operations', collectedOperations, ctx)
|
|
1152
|
-
}
|
|
1153
|
-
|
|
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
|
-
}
|
|
1181
|
-
|
|
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
|
-
})
|
|
1200
|
-
|
|
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
|
-
})
|
|
1208
|
-
|
|
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
|
-
}
|
|
1220
|
-
|
|
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
|
-
}
|
|
1230
|
-
|
|
1231
|
-
// Derive the allowed schema name set per pruning plugin.
|
|
1232
|
-
for (const state of pruningStates) {
|
|
1233
|
-
state.allowedSchemaNames = collectUsedSchemaNames(includedOpsByState.get(state) ?? [], allSchemas)
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
|
|
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
|
-
}
|
|
1240
|
-
|
|
1241
|
-
async function dispatchSchema(state: PluginState, node: SchemaNode): Promise<void> {
|
|
1242
|
-
if (state.failed) return
|
|
1243
|
-
|
|
1244
|
-
try {
|
|
1245
|
-
const { plugin, generatorContext, generators } = state
|
|
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 }
|
|
1261
|
-
|
|
1262
|
-
for (const gen of generators) {
|
|
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
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
await driver.hooks.emit('kubb:generate:schema', transformedNode, ctx)
|
|
1271
|
-
} catch (caughtError) {
|
|
1272
|
-
state.failed = true
|
|
1273
|
-
state.error = caughtError as Error
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
async function dispatchOperation(state: PluginState, node: OperationNode): Promise<void> {
|
|
1278
|
-
if (state.failed) return
|
|
1279
|
-
|
|
1280
|
-
try {
|
|
1281
|
-
const { plugin, generatorContext, generators } = state
|
|
1282
|
-
|
|
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
|
|
1289
|
-
|
|
1290
|
-
const ctx = { ...generatorContext, options }
|
|
1291
|
-
|
|
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
|
-
}
|
|
1299
|
-
|
|
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
|
-
}
|
|
1306
|
-
|
|
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
|
-
})
|
|
1314
|
-
|
|
1315
|
-
const collectedOperations: OperationNode[] = []
|
|
1316
|
-
|
|
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
|
-
)
|
|
1325
|
-
|
|
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
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
const duration = getElapsedMs(state.hrStart)
|
|
1339
|
-
pluginTimings.set(state.plugin.name, duration)
|
|
1340
|
-
|
|
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),
|
|
1351
|
-
})
|
|
1352
|
-
|
|
1353
|
-
if (state.failed && state.error) {
|
|
1354
|
-
failedPlugins.add({ plugin: state.plugin, error: state.error })
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
await driver.hooks.emit('kubb:debug', {
|
|
1358
|
-
date: new Date(),
|
|
1359
|
-
logs: [state.failed ? '✗ Plugin start failed' : `✓ Plugin started successfully (${formatMs(duration)})`],
|
|
1360
|
-
})
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
try {
|
|
1365
|
-
await driver.emitSetupHooks()
|
|
1366
|
-
|
|
1367
|
-
if (driver.adapter && driver.inputNode) {
|
|
1368
|
-
await hooks.emit('kubb:build:start', {
|
|
1369
|
-
config,
|
|
1370
|
-
adapter: driver.adapter,
|
|
1371
|
-
meta: driver.inputNode.meta,
|
|
1372
|
-
getPlugin: driver.getPlugin.bind(driver),
|
|
1373
|
-
get files() {
|
|
1374
|
-
return driver.fileManager.files
|
|
1375
|
-
},
|
|
1376
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1377
|
-
})
|
|
1378
|
-
}
|
|
1379
|
-
|
|
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> = []
|
|
1384
|
-
|
|
1385
|
-
for (const plugin of driver.plugins.values()) {
|
|
1386
|
-
const context = driver.getContext(plugin)
|
|
1387
|
-
const hrStart = process.hrtime()
|
|
1388
|
-
|
|
1389
|
-
try {
|
|
1390
|
-
await hooks.emit('kubb:plugin:start', { plugin })
|
|
1391
|
-
await hooks.emit('kubb:debug', {
|
|
1392
|
-
date: new Date(),
|
|
1393
|
-
logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
|
|
1394
|
-
})
|
|
1395
|
-
} catch (caughtError) {
|
|
1396
|
-
const error = caughtError as Error
|
|
1397
|
-
const duration = getElapsedMs(hrStart)
|
|
1398
|
-
pluginTimings.set(plugin.name, duration)
|
|
1399
|
-
await hooks.emit('kubb:plugin:end', {
|
|
1400
|
-
plugin,
|
|
1401
|
-
duration,
|
|
1402
|
-
success: false,
|
|
1403
|
-
error,
|
|
1404
|
-
config,
|
|
1405
|
-
get files() {
|
|
1406
|
-
return driver.fileManager.files
|
|
1407
|
-
},
|
|
1408
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1409
|
-
})
|
|
1410
|
-
failedPlugins.add({ plugin, error })
|
|
1411
|
-
continue
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
if (plugin.generators?.length || driver.hasEventGenerators(plugin.name)) {
|
|
1415
|
-
generatorPlugins.push({ plugin, context, hrStart })
|
|
1416
|
-
continue
|
|
1417
|
-
}
|
|
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
|
-
}
|
|
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) {
|
|
1445
|
-
const duration = getElapsedMs(hrStart)
|
|
1446
|
-
pluginTimings.set(plugin.name, duration)
|
|
1447
|
-
await hooks.emit('kubb:plugin:end', {
|
|
1448
|
-
plugin,
|
|
1449
|
-
duration,
|
|
1450
|
-
success: true,
|
|
1451
|
-
config,
|
|
1452
|
-
get files() {
|
|
1453
|
-
return driver.fileManager.files
|
|
1454
|
-
},
|
|
1455
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1456
|
-
})
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
await hooks.emit('kubb:plugins:end', {
|
|
1462
|
-
config,
|
|
1463
|
-
get files() {
|
|
1464
|
-
return driver.fileManager.files
|
|
1465
|
-
},
|
|
1466
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1467
|
-
})
|
|
1468
|
-
|
|
1469
|
-
await flushPendingFiles()
|
|
1470
|
-
|
|
1471
|
-
const files = driver.fileManager.files
|
|
1472
|
-
|
|
1473
|
-
await hooks.emit('kubb:build:end', {
|
|
1474
|
-
files,
|
|
1475
|
-
config,
|
|
1476
|
-
outputDir: resolve(config.root, config.output.path),
|
|
1477
|
-
})
|
|
1478
|
-
|
|
1479
|
-
return {
|
|
1480
|
-
failedPlugins,
|
|
1481
|
-
files,
|
|
1482
|
-
driver,
|
|
1483
|
-
pluginTimings,
|
|
1484
|
-
storage,
|
|
1485
|
-
}
|
|
1486
|
-
} catch (error) {
|
|
1487
|
-
return {
|
|
1488
|
-
failedPlugins,
|
|
1489
|
-
files: [],
|
|
1490
|
-
driver,
|
|
1491
|
-
pluginTimings,
|
|
1492
|
-
error: error as Error,
|
|
1493
|
-
storage,
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
async function build(setupResult: SetupResult): Promise<BuildOutput> {
|
|
1499
|
-
const { files, driver, failedPlugins, pluginTimings, error, storage } = await safeBuild(setupResult)
|
|
1500
|
-
|
|
1501
|
-
if (error) {
|
|
1502
|
-
throw error
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
if (failedPlugins.size > 0) {
|
|
1506
|
-
const errors = [...failedPlugins].map(({ error }) => error)
|
|
1507
|
-
|
|
1508
|
-
throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors })
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
return {
|
|
1512
|
-
failedPlugins,
|
|
1513
|
-
files,
|
|
1514
|
-
driver,
|
|
1515
|
-
pluginTimings,
|
|
1516
|
-
error: undefined,
|
|
1517
|
-
storage,
|
|
1518
|
-
}
|
|
1519
942
|
}
|
|
1520
943
|
|
|
1521
944
|
/**
|
|
@@ -1548,66 +971,141 @@ type CreateKubbOptions = {
|
|
|
1548
971
|
}
|
|
1549
972
|
|
|
1550
973
|
/**
|
|
1551
|
-
*
|
|
974
|
+
* Kubb code-generation instance bound to a single config entry. Resolves the user
|
|
975
|
+
* config during `setup()` and shares `hooks`, `storage`, `driver`, and `config` across
|
|
976
|
+
* the `setup → build` lifecycle.
|
|
1552
977
|
*
|
|
1553
|
-
*
|
|
1554
|
-
* `setup()`. The instance then holds shared state (`hooks`, `storage`, `driver`, `config`)
|
|
1555
|
-
* across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
|
|
1556
|
-
* calling `setup()` or `build()`.
|
|
978
|
+
* Attach event listeners to `.hooks` before calling `setup()` or `build()`.
|
|
1557
979
|
*
|
|
1558
980
|
* @example
|
|
1559
981
|
* ```ts
|
|
1560
982
|
* const kubb = createKubb(userConfig)
|
|
1561
|
-
*
|
|
1562
|
-
* kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
|
|
1563
|
-
* console.log(`${plugin.name} completed in ${duration}ms`)
|
|
1564
|
-
* })
|
|
1565
|
-
*
|
|
983
|
+
* kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
|
|
1566
984
|
* const { files, failedPlugins } = await kubb.safeBuild()
|
|
1567
985
|
* ```
|
|
1568
986
|
*/
|
|
1569
|
-
export
|
|
1570
|
-
|
|
1571
|
-
|
|
987
|
+
export class Kubb {
|
|
988
|
+
readonly hooks: AsyncEventEmitter<KubbHooks>
|
|
989
|
+
readonly #userConfig: UserConfig
|
|
990
|
+
#config: Config | null = null
|
|
991
|
+
#driver: KubbDriver | null = null
|
|
992
|
+
#storage: Storage | null = null
|
|
993
|
+
|
|
994
|
+
constructor(userConfig: UserConfig, options: CreateKubbOptions = {}) {
|
|
995
|
+
this.#userConfig = userConfig
|
|
996
|
+
this.hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()
|
|
997
|
+
}
|
|
1572
998
|
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
await
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
999
|
+
get storage(): Storage {
|
|
1000
|
+
if (!this.#storage) throw new Error('[kubb] setup() must be called before accessing storage')
|
|
1001
|
+
return this.#storage
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
get driver(): KubbDriver {
|
|
1005
|
+
if (!this.#driver) throw new Error('[kubb] setup() must be called before accessing driver')
|
|
1006
|
+
return this.#driver
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
get config(): Config {
|
|
1010
|
+
if (!this.#config) throw new Error('[kubb] setup() must be called before accessing config')
|
|
1011
|
+
return this.#config
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Resolves config and initializes the driver. `build()` calls this automatically.
|
|
1016
|
+
*/
|
|
1017
|
+
async setup(): Promise<void> {
|
|
1018
|
+
const config = resolveConfig(this.#userConfig)
|
|
1019
|
+
const driver = new KubbDriver(config, { hooks: this.hooks })
|
|
1020
|
+
const storage = createSourcesView(config.storage)
|
|
1021
|
+
|
|
1022
|
+
await this.hooks.emit('kubb:debug', { date: new Date(), logs: this.#configLogs(config) })
|
|
1023
|
+
|
|
1024
|
+
if (isInputPath(this.#userConfig) && !new URLPath(this.#userConfig.input.path).isURL) {
|
|
1025
|
+
try {
|
|
1026
|
+
await exists(this.#userConfig.input.path)
|
|
1027
|
+
await this.hooks.emit('kubb:debug', { date: new Date(), logs: [`✓ Input file validated: ${this.#userConfig.input.path}`] })
|
|
1028
|
+
} catch (caughtError) {
|
|
1029
|
+
throw new Error(
|
|
1030
|
+
`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${this.#userConfig.input.path}`,
|
|
1031
|
+
{ cause: caughtError as Error },
|
|
1032
|
+
)
|
|
1607
1033
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (config.output.clean) {
|
|
1037
|
+
await this.hooks.emit('kubb:debug', { date: new Date(), logs: ['Cleaning output directories', ` • Output: ${config.output.path}`] })
|
|
1038
|
+
await config.storage.clear(resolve(config.root, config.output.path))
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
await driver.setup()
|
|
1042
|
+
|
|
1043
|
+
this.#config = config
|
|
1044
|
+
this.#driver = driver
|
|
1045
|
+
this.#storage = storage
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Runs the full pipeline and throws on any plugin error.
|
|
1050
|
+
* Automatically calls `setup()` if needed.
|
|
1051
|
+
*/
|
|
1052
|
+
async build(): Promise<BuildOutput> {
|
|
1053
|
+
const out = await this.safeBuild()
|
|
1054
|
+
if (out.error) throw out.error
|
|
1055
|
+
if (out.failedPlugins.size > 0) {
|
|
1056
|
+
const errors = [...out.failedPlugins].map(({ error }) => error)
|
|
1057
|
+
throw new BuildError(`Build Error with ${out.failedPlugins.size} failed plugins`, { errors })
|
|
1058
|
+
}
|
|
1059
|
+
return out
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Runs the full pipeline and captures errors in `BuildOutput` instead of throwing.
|
|
1064
|
+
* Automatically calls `setup()` if needed.
|
|
1065
|
+
*/
|
|
1066
|
+
async safeBuild(): Promise<BuildOutput> {
|
|
1067
|
+
if (!this.#driver) await this.setup()
|
|
1068
|
+
using cleanup = this
|
|
1069
|
+
const driver = cleanup.driver
|
|
1070
|
+
const storage = cleanup.storage
|
|
1071
|
+
const { failedPlugins, pluginTimings, error } = await driver.run({ storage })
|
|
1072
|
+
return { failedPlugins, files: driver.fileManager.files, driver, pluginTimings, storage, ...(error ? { error } : {}) }
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
dispose(): void {
|
|
1076
|
+
this.#driver?.dispose()
|
|
1610
1077
|
}
|
|
1611
1078
|
|
|
1612
|
-
|
|
1079
|
+
[Symbol.dispose](): void {
|
|
1080
|
+
this.dispose()
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
#configLogs(config: Config): Array<string> {
|
|
1084
|
+
const u = this.#userConfig
|
|
1085
|
+
const diag = getDiagnosticInfo()
|
|
1086
|
+
return [
|
|
1087
|
+
'Configuration:',
|
|
1088
|
+
` • Name: ${u.name || 'unnamed'}`,
|
|
1089
|
+
` • Root: ${u.root || process.cwd()}`,
|
|
1090
|
+
` • Output: ${u.output?.path || 'not specified'}`,
|
|
1091
|
+
` • Plugins: ${u.plugins?.length || 0}`,
|
|
1092
|
+
'Output Settings:',
|
|
1093
|
+
` • Storage: ${config.storage.name}`,
|
|
1094
|
+
` • Formatter: ${u.output?.format || 'none'}`,
|
|
1095
|
+
` • Linter: ${u.output?.lint || 'none'}`,
|
|
1096
|
+
`Running adapter: ${config.adapter?.name || 'none'}`,
|
|
1097
|
+
'Environment:',
|
|
1098
|
+
Object.entries(diag)
|
|
1099
|
+
.map(([key, value]) => ` • ${key}: ${value}`)
|
|
1100
|
+
.join('\n'),
|
|
1101
|
+
]
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Factory for {@link Kubb}. Equivalent to `new Kubb(userConfig, options)` and kept
|
|
1107
|
+
* as the canonical public entry point.
|
|
1108
|
+
*/
|
|
1109
|
+
export function createKubb(userConfig: UserConfig, options: CreateKubbOptions = {}): Kubb {
|
|
1110
|
+
return new Kubb(userConfig, options)
|
|
1613
1111
|
}
|