@kubb/mcp 5.0.0-beta.4 → 5.0.0-beta.41

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.
@@ -1,210 +1,147 @@
1
1
  import { AsyncEventEmitter } from '@internals/utils'
2
- import { type Config, createKubb, type KubbHooks } from '@kubb/core'
3
- import type { CallToolResult } from '@modelcontextprotocol/sdk/types.d.ts'
4
- import type { z } from 'zod'
5
- import type { generateSchema } from '../schemas/generateSchema.ts'
2
+ import { type Config, createKubb, type Diagnostic, Diagnostics, type KubbHooks } from '@kubb/core'
3
+ import { defineTool } from 'tmcp/tool'
4
+ import { tool } from 'tmcp/utils'
5
+ import type * as v from 'valibot'
6
+ import { generateSchema } from '../schemas/generateSchema.ts'
7
+ import { formatDiagnostics } from '../utils/formatDiagnostics.ts'
6
8
  import { NotifyTypes } from '../types.ts'
7
9
  import { loadUserConfig } from '../utils/loadUserConfig.ts'
8
10
  import { resolveCwd } from '../utils/resolveCwd.ts'
9
11
  import { resolveUserConfig } from '../utils/resolveUserConfig.ts'
10
12
 
11
- interface NotificationHandler {
12
- sendNotification(method: string, params: unknown): Promise<void>
13
- }
14
-
15
- /**
16
- * Build tool that generates code from OpenAPI specs using Kubb.
17
- * Sends real-time notifications of build progress and events.
18
- */
19
- export async function generate(schema: z.infer<typeof generateSchema>, handler: NotificationHandler): Promise<CallToolResult> {
20
- const { config: configPath, input, output, logLevel } = schema
21
-
22
- try {
23
- const hooks = new AsyncEventEmitter<KubbHooks>()
24
- const messages: string[] = []
25
-
26
- // Helper to send notifications
27
- const notify = async (type: string, message: string, data?: Record<string, unknown>) => {
28
- messages.push(`${type}: ${message}`)
29
-
30
- await handler.sendNotification('kubb/progress', {
31
- type,
32
- message,
33
- timestamp: new Date().toISOString(),
34
- ...data,
35
- })
36
- }
37
-
38
- // Capture events for output and send notifications
39
- hooks.on('kubb:info', async ({ message }: { message: string }) => {
40
- await notify(NotifyTypes.INFO, message)
41
- })
42
-
43
- hooks.on('kubb:success', async ({ message }: { message: string }) => {
44
- await notify(NotifyTypes.SUCCESS, message)
45
- })
13
+ export const generateTool = defineTool(
14
+ {
15
+ name: 'generate',
16
+ description: 'Generate OpenAPI spec helpers using Kubb configuration',
17
+ schema: generateSchema,
18
+ },
19
+ async function generate(schema: v.InferInput<typeof generateSchema>) {
20
+ const { config: configPath, input, output, logLevel } = schema
46
21
 
47
- hooks.on('kubb:error', async ({ error }: { error: Error }) => {
48
- await notify(NotifyTypes.ERROR, error.message, { stack: error.stack })
49
- })
22
+ try {
23
+ const hooks = new AsyncEventEmitter<KubbHooks>()
24
+ const messages: Array<string> = []
50
25
 
51
- hooks.on('kubb:warn', async ({ message }: { message: string }) => {
52
- await notify(NotifyTypes.WARN, message)
53
- })
26
+ const notify = async (type: string, message: string, data?: Record<string, unknown>) => {
27
+ messages.push(data ? `${type}: ${message} ${JSON.stringify(data)}` : `${type}: ${message}`)
28
+ }
54
29
 
55
- // Plugin lifecycle events
56
- hooks.on('kubb:plugin:start', async ({ plugin }) => {
57
- await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`)
58
- })
30
+ hooks.on('kubb:info', async ({ message }: { message: string }) => {
31
+ await notify(NotifyTypes.INFO, message)
32
+ })
59
33
 
60
- hooks.on('kubb:plugin:end', async ({ plugin, duration }) => {
61
- await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${plugin.name}`, {
62
- duration,
34
+ hooks.on('kubb:success', async ({ message }: { message: string }) => {
35
+ await notify(NotifyTypes.SUCCESS, message)
63
36
  })
64
- })
65
37
 
66
- // File processing events
67
- hooks.on('kubb:files:processing:start', async () => {
68
- await notify(NotifyTypes.FILES_START, 'Starting file processing')
69
- })
38
+ hooks.on('kubb:error', async ({ error }: { error: Error }) => {
39
+ await notify(NotifyTypes.ERROR, error.message)
40
+ })
70
41
 
71
- hooks.on('kubb:file:processing:update', async ({ file }: { file: { name: string } }) => {
72
- await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`)
73
- })
42
+ hooks.on('kubb:warn', async ({ message }: { message: string }) => {
43
+ await notify(NotifyTypes.WARN, message)
44
+ })
74
45
 
75
- hooks.on('kubb:files:processing:end', async () => {
76
- await notify(NotifyTypes.FILES_END, 'File processing complete')
77
- })
46
+ hooks.on('kubb:diagnostic', async ({ diagnostic }: { diagnostic: Diagnostic }) => {
47
+ await notify(NotifyTypes.DIAGNOSTIC, diagnostic.message, Diagnostics.serialize(diagnostic))
48
+ })
78
49
 
79
- // Generation events
80
- hooks.on('kubb:generation:start', async () => {
81
- await notify(NotifyTypes.GENERATION_START, 'Generation started')
82
- })
50
+ hooks.on('kubb:plugin:start', async ({ plugin }) => {
51
+ await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`)
52
+ })
83
53
 
84
- hooks.on('kubb:generation:end', async () => {
85
- await notify(NotifyTypes.GENERATION_END, 'Generation ended')
86
- })
54
+ hooks.on('kubb:plugin:end', async ({ plugin, duration }) => {
55
+ await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${plugin.name}`, { duration })
56
+ })
87
57
 
88
- // Load and process configuration
89
- let userConfig: Config
90
- let cwd: string
58
+ hooks.on('kubb:files:processing:start', async () => {
59
+ await notify(NotifyTypes.FILES_START, 'Starting file processing')
60
+ })
91
61
 
92
- try {
93
- const configResult = await loadUserConfig(configPath, { notify })
94
- userConfig = configResult.userConfig
95
- cwd = configResult.cwd
62
+ hooks.on('kubb:files:processing:update', async ({ files }: { files: Array<{ file: { name: string } }> }) => {
63
+ await notify(NotifyTypes.FILES_UPDATE, `Processing ${files.length} files`)
64
+ })
96
65
 
97
- if (Array.isArray(userConfig) && userConfig.length) {
98
- throw new Error('Array type in kubb.config.ts is not supported in this tool. Please provide a single configuration object.')
99
- }
66
+ hooks.on('kubb:files:processing:end', async () => {
67
+ await notify(NotifyTypes.FILES_END, 'File processing complete')
68
+ })
100
69
 
101
- userConfig = await resolveUserConfig(userConfig, {
102
- configPath,
103
- logLevel,
70
+ hooks.on('kubb:generation:start', async () => {
71
+ await notify(NotifyTypes.GENERATION_START, 'Generation started')
104
72
  })
105
- } catch (error) {
106
- const errorMessage = error instanceof Error ? error.message : String(error)
107
- await notify(NotifyTypes.CONFIG_ERROR, errorMessage)
108
- return {
109
- content: [
110
- {
111
- type: 'text',
112
- text: errorMessage,
113
- },
114
- ],
115
- isError: true,
116
- }
117
- }
118
73
 
119
- const inputPath = input ?? (userConfig.input && 'path' in userConfig.input ? userConfig.input.path : undefined)
120
-
121
- // Override config with CLI options
122
- const config: Config = {
123
- ...userConfig,
124
- root: resolveCwd(userConfig, cwd),
125
- input: inputPath
126
- ? {
127
- ...userConfig.input,
128
- path: inputPath,
129
- }
130
- : userConfig.input,
131
- output: output
132
- ? {
133
- ...userConfig.output,
134
- path: output,
135
- }
136
- : userConfig.output,
137
- }
74
+ hooks.on('kubb:generation:end', async () => {
75
+ await notify(NotifyTypes.GENERATION_END, 'Generation ended')
76
+ })
138
77
 
139
- await notify(NotifyTypes.CONFIG_READY, 'Configuration ready', {
140
- root: config.root,
141
- })
78
+ let userConfig: Config
79
+ let cwd: string
80
+
81
+ try {
82
+ const configResult = await loadUserConfig(configPath, { notify })
83
+ userConfig = configResult.userConfig
84
+ cwd = configResult.cwd
85
+
86
+ if (Array.isArray(userConfig)) {
87
+ throw new Error('Array type in kubb.config.ts is not supported in this tool. Please provide a single configuration object.')
88
+ }
89
+
90
+ userConfig = await resolveUserConfig(userConfig, {
91
+ configPath,
92
+ logLevel,
93
+ })
94
+ } catch (error) {
95
+ const errorMessage = error instanceof Error ? error.message : String(error)
96
+ await notify(NotifyTypes.CONFIG_ERROR, errorMessage)
97
+ return tool.error(errorMessage)
98
+ }
142
99
 
143
- // Setup and build
144
- await notify(NotifyTypes.SETUP_START, 'Setting up Kubb')
100
+ const inputPath = input ?? (userConfig.input && 'path' in userConfig.input ? userConfig.input.path : undefined)
101
+
102
+ const config: Config = {
103
+ ...userConfig,
104
+ root: resolveCwd(userConfig, cwd),
105
+ input: inputPath
106
+ ? {
107
+ ...userConfig.input,
108
+ path: inputPath,
109
+ }
110
+ : userConfig.input,
111
+ output: output
112
+ ? {
113
+ ...userConfig.output,
114
+ path: output,
115
+ }
116
+ : userConfig.output,
117
+ }
145
118
 
146
- const kubb = createKubb(config, { hooks })
147
- await kubb.setup()
148
- await notify(NotifyTypes.SETUP_END, 'Kubb setup complete')
119
+ await notify(NotifyTypes.CONFIG_READY, 'Configuration ready')
120
+ await notify(NotifyTypes.SETUP_START, 'Setting up Kubb')
149
121
 
150
- await notify(NotifyTypes.BUILD_START, 'Starting build')
151
- const { files, failedPlugins, error } = await kubb.safeBuild()
152
- await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`)
122
+ const kubb = createKubb(config, { hooks })
123
+ await kubb.setup()
124
+ await notify(NotifyTypes.SETUP_END, 'Kubb setup complete')
153
125
 
154
- if (error || failedPlugins.size > 0) {
155
- const allErrors: Error[] = [
156
- error,
157
- ...Array.from(failedPlugins)
158
- .filter((it) => it.error)
159
- .map((it) => it.error),
160
- ].filter(Boolean)
126
+ await notify(NotifyTypes.BUILD_START, 'Starting build')
127
+ const { files, diagnostics } = await kubb.safeBuild()
128
+ await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`)
161
129
 
162
- await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${allErrors.length} error(s)`, {
163
- errorCount: allErrors.length,
164
- errors: allErrors.map((err) => err.message),
165
- })
130
+ const problems = diagnostics.filter(Diagnostics.isProblem)
131
+ const errors = problems.filter((diagnostic) => diagnostic.severity === 'error')
132
+ if (errors.length > 0) {
133
+ await notify(NotifyTypes.BUILD_FAILED, `Build failed with ${errors.length} diagnostic(s)`)
166
134
 
167
- return {
168
- content: [
169
- {
170
- type: 'text',
171
- text: `Build failed:\n${allErrors.map((err) => err.message).join('\n')}\n\n${messages.join('\n')}`,
172
- },
173
- ],
174
- isError: true,
135
+ const serialized = problems.map((diagnostic) => Diagnostics.serialize(diagnostic))
136
+ return tool.error(`Build failed:\n${formatDiagnostics(serialized)}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``)
175
137
  }
176
- }
177
138
 
178
- await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`, {
179
- filesCount: files.length,
180
- })
181
-
182
- return {
183
- content: [
184
- {
185
- type: 'text',
186
- text: `Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join('\n')}`,
187
- },
188
- ],
189
- }
190
- } catch (caughtError) {
191
- const error = caughtError as Error
192
-
193
- await handler.sendNotification('kubb/progress', {
194
- type: NotifyTypes.FATAL_ERROR,
195
- message: error.message,
196
- stack: error.stack,
197
- timestamp: new Date().toISOString(),
198
- })
199
-
200
- return {
201
- content: [
202
- {
203
- type: 'text',
204
- text: `Build error: ${error.message}\n${error.stack || ''}`,
205
- },
206
- ],
207
- isError: true,
139
+ await notify(NotifyTypes.BUILD_SUCCESS, `Build completed successfully - Generated ${files.length} files`)
140
+
141
+ return tool.text(`Build completed successfully!\n\nGenerated ${files.length} files\n\n${messages.join('\n')}`)
142
+ } catch (caughtError) {
143
+ const serialized = Diagnostics.serialize(Diagnostics.from(caughtError))
144
+ return tool.error(`Build error:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``)
208
145
  }
209
- }
210
- }
146
+ },
147
+ )
@@ -0,0 +1,37 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+ import { availablePlugins, generateConfigFile, KUBB_CONFIG_FILENAME, type PluginOption } from '@internals/shared'
5
+ import { defineTool } from 'tmcp/tool'
6
+ import { tool } from 'tmcp/utils'
7
+ import { initSchema } from '../schemas/initSchema.ts'
8
+
9
+ export function resolvePlugins(pluginsFlag: string | undefined): Array<PluginOption> {
10
+ if (!pluginsFlag) {
11
+ return []
12
+ }
13
+ const requested = pluginsFlag
14
+ .split(',')
15
+ .map((v) => v.trim())
16
+ .filter(Boolean)
17
+ return availablePlugins.filter((p) => requested.includes(p.value))
18
+ }
19
+
20
+ export const initTool = defineTool(
21
+ {
22
+ name: 'init',
23
+ description: 'Scaffold a kubb.config.ts in the current directory (non-interactive). Does not install packages.',
24
+ schema: initSchema,
25
+ },
26
+ async ({ input = './openapi.yaml', output = './src/gen', plugins }) => {
27
+ const selected = resolvePlugins(plugins)
28
+ const content = generateConfigFile({ selectedPlugins: selected, inputPath: input, outputPath: output })
29
+ const dest = path.join(process.cwd(), KUBB_CONFIG_FILENAME)
30
+ if (fs.existsSync(dest)) {
31
+ return tool.error(`${KUBB_CONFIG_FILENAME} already exists at ${dest}. Delete it first before running init again.`)
32
+ }
33
+ fs.writeFileSync(dest, content, 'utf-8')
34
+ const packageList = ['kubb', ...selected.map((p) => p.packageName)].join(' ')
35
+ return tool.text(`Created kubb.config.ts\n\nInstall packages:\n npm install ${packageList}\n\nThen run:\n npx kubb generate`)
36
+ },
37
+ )
@@ -0,0 +1,28 @@
1
+ import { Diagnostics } from '@kubb/core'
2
+ import { defineTool } from 'tmcp/tool'
3
+ import { tool } from 'tmcp/utils'
4
+ import { validateSchema } from '../schemas/validateSchema.ts'
5
+ import { formatDiagnostics } from '../utils/formatDiagnostics.ts'
6
+
7
+ export const validateTool = defineTool(
8
+ {
9
+ name: 'validate',
10
+ description: 'Validate an OpenAPI/Swagger specification file or URL',
11
+ schema: validateSchema,
12
+ },
13
+ async ({ input }) => {
14
+ let mod: typeof import('@kubb/adapter-oas')
15
+ try {
16
+ mod = await import('@kubb/adapter-oas')
17
+ } catch {
18
+ return tool.error('The validate tool requires @kubb/adapter-oas.\nInstall: npm install @kubb/adapter-oas')
19
+ }
20
+ try {
21
+ await mod.adapterOas().validate(input, { throwOnError: true })
22
+ return tool.text(`Validation successful: ${input}`)
23
+ } catch (err) {
24
+ const serialized = Diagnostics.serialize(Diagnostics.from(err))
25
+ return tool.error(`Validation failed:\n${formatDiagnostics([serialized])}\n\n\`\`\`json\n${JSON.stringify(serialized, null, 2)}\n\`\`\``)
26
+ }
27
+ },
28
+ )
package/src/types.ts CHANGED
@@ -3,10 +3,11 @@ export const NotifyTypes = {
3
3
  SUCCESS: 'SUCCESS',
4
4
  ERROR: 'ERROR',
5
5
  WARN: 'WARN',
6
+ DIAGNOSTIC: 'DIAGNOSTIC',
6
7
  PLUGIN_START: 'PLUGIN_START',
7
8
  PLUGIN_END: 'PLUGIN_END',
8
9
  FILES_START: 'FILES_START',
9
- FILE_UPDATE: 'FILE_UPDATE',
10
+ FILES_UPDATE: 'FILES_UPDATE',
10
11
  FILES_END: 'FILES_END',
11
12
  GENERATION_START: 'GENERATION_START',
12
13
  GENERATION_END: 'GENERATION_END',
@@ -0,0 +1,29 @@
1
+ import type { SerializedDiagnostic } from '@kubb/core'
2
+
3
+ /**
4
+ * Renders serialized diagnostics as a plain-text block for an AI assistant. Each entry
5
+ * keeps the stable `code`, the source pointer, the suggested fix, and the docs link, so
6
+ * the agent can act on the problem rather than parsing a bare message. No ANSI styling,
7
+ * unlike the CLI renderer.
8
+ */
9
+ export function formatDiagnostics(diagnostics: ReadonlyArray<SerializedDiagnostic>): string {
10
+ return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic)).join('\n\n')
11
+ }
12
+
13
+ function formatDiagnostic(diagnostic: SerializedDiagnostic): string {
14
+ const { code, severity, message, location, help, plugin, docsUrl } = diagnostic
15
+ const rule = plugin ? `${plugin}(${code})` : code
16
+ const lines = [`${severity} ${rule}: ${message}`]
17
+
18
+ if (location && 'pointer' in location) {
19
+ lines.push(` at ${location.pointer}`)
20
+ }
21
+ if (help) {
22
+ lines.push(` help: ${help}`)
23
+ }
24
+ if (docsUrl) {
25
+ lines.push(` docs: ${docsUrl}`)
26
+ }
27
+
28
+ return lines.join('\n')
29
+ }
@@ -1,12 +1,20 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import type { Config } from '@kubb/core'
4
- import { unrun } from 'unrun'
4
+ import { createJiti } from 'jiti'
5
5
  import { ALLOWED_CONFIG_EXTENSIONS } from '../constants.ts'
6
6
  import { NotifyTypes } from '../types.ts'
7
7
 
8
8
  type NotifyFunction = (type: string, message: string, data?: Record<string, unknown>) => Promise<void>
9
9
 
10
+ const jiti = createJiti(import.meta.url, {
11
+ jsx: {
12
+ runtime: 'automatic',
13
+ importSource: '@kubb/renderer-jsx',
14
+ },
15
+ moduleCache: false,
16
+ })
17
+
10
18
  const loadedModules = new Map<string, unknown>()
11
19
 
12
20
  async function loadModule(filePath: string): Promise<unknown> {
@@ -17,15 +25,12 @@ async function loadModule(filePath: string): Promise<unknown> {
17
25
  if (loadedModules.has(filePath)) {
18
26
  return loadedModules.get(filePath)
19
27
  }
20
- const { module } = await unrun({ path: filePath })
21
- loadedModules.set(filePath, module)
22
- return module
28
+ const mod = await jiti.import(filePath, { default: true })
29
+ loadedModules.set(filePath, mod)
30
+ return mod
23
31
  }
24
32
 
25
33
  export async function loadUserConfig(configPath: string | undefined, { notify }: { notify: NotifyFunction }): Promise<{ userConfig: Config; cwd: string }> {
26
- let userConfig: Config | undefined
27
- let cwd: string
28
-
29
34
  if (configPath) {
30
35
  const ext = path.extname(configPath)
31
36
  if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
@@ -41,37 +46,33 @@ export async function loadUserConfig(configPath: string | undefined, { notify }:
41
46
  await notify(NotifyTypes.CONFIG_ERROR, msg)
42
47
  throw new Error(msg)
43
48
  }
44
- cwd = path.dirname(resolvedConfigPath)
45
-
49
+ const cwd = path.dirname(resolvedConfigPath)
46
50
  try {
47
- userConfig = (await loadModule(resolvedConfigPath)) as Config
51
+ const userConfig = (await loadModule(resolvedConfigPath)) as Config
48
52
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`)
53
+ return { userConfig, cwd }
49
54
  } catch (error) {
50
- await notify(NotifyTypes.CONFIG_ERROR, `Failed to load config: ${error instanceof Error ? error.message : String(error)}`)
51
- throw new Error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`)
52
- }
53
- } else {
54
- cwd = process.cwd()
55
- const configFileNames = ['kubb.config.ts', 'kubb.config.mts', 'kubb.config.cts', 'kubb.config.js', 'kubb.config.cjs']
56
-
57
- for (const configFileName of configFileNames) {
58
- const configFilePath = path.resolve(process.cwd(), configFileName)
59
- if (!existsSync(configFilePath)) continue
60
- try {
61
- userConfig = (await loadModule(configFilePath)) as Config
62
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`)
63
- break
64
- } catch {
65
- // Continue trying next config file
66
- }
55
+ const msg = `Failed to load config: ${error instanceof Error ? error.message : String(error)}`
56
+ await notify(NotifyTypes.CONFIG_ERROR, msg)
57
+ throw new Error(msg)
67
58
  }
59
+ }
68
60
 
69
- if (!userConfig) {
70
- await notify(NotifyTypes.CONFIG_ERROR, 'No config file found')
61
+ const cwd = process.cwd()
62
+ const configFileNames = ['kubb.config.ts', 'kubb.config.mts', 'kubb.config.cts', 'kubb.config.js', 'kubb.config.cjs']
71
63
 
72
- throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(', ')}`)
64
+ for (const configFileName of configFileNames) {
65
+ const configFilePath = path.resolve(process.cwd(), configFileName)
66
+ if (!existsSync(configFilePath)) continue
67
+ try {
68
+ const userConfig = (await loadModule(configFilePath)) as Config
69
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`)
70
+ return { userConfig, cwd }
71
+ } catch (err) {
72
+ await notify(NotifyTypes.CONFIG_ERROR, `Failed to load ${configFileName}: ${err instanceof Error ? err.message : String(err)}`)
73
73
  }
74
74
  }
75
75
 
76
- return { userConfig: userConfig!, cwd }
76
+ await notify(NotifyTypes.CONFIG_ERROR, 'No config file found')
77
+ throw new Error(`No config file found. Please provide a config path or create one of: ${configFileNames.join(', ')}`)
77
78
  }
@@ -1,28 +1,13 @@
1
1
  import { isPromise } from '@internals/utils'
2
- import type { CLIOptions, Config } from '@kubb/core'
2
+ import type { CLIOptions, Config, PossibleConfig } from '@kubb/core'
3
3
 
4
4
  export type ResolveUserConfigOptions = {
5
5
  configPath?: string
6
6
  logLevel?: string
7
7
  }
8
8
 
9
- /**
10
- * Resolve the config by handling function configs and returning the final configuration
11
- */
12
- export async function resolveUserConfig(config: Config, options: ResolveUserConfigOptions): Promise<Config> {
13
- let kubbUserConfig = Promise.resolve(config) as Promise<Config>
14
-
15
- if (typeof config === 'function') {
16
- const possiblePromise = (config as any)({
17
- logLevel: options.logLevel,
18
- config: options.configPath,
19
- } as CLIOptions)
20
- if (isPromise(possiblePromise)) {
21
- kubbUserConfig = possiblePromise
22
- } else {
23
- kubbUserConfig = Promise.resolve(possiblePromise)
24
- }
25
- }
26
-
27
- return (await kubbUserConfig) as Config
9
+ export async function resolveUserConfig(config: PossibleConfig<CLIOptions>, options: ResolveUserConfigOptions): Promise<Config> {
10
+ const result = typeof config === 'function' ? config({ logLevel: options.logLevel as CLIOptions['logLevel'], config: options.configPath }) : config
11
+ const resolved = isPromise(result) ? await result : result
12
+ return (Array.isArray(resolved) ? resolved[0] : resolved) as Config
28
13
  }
File without changes