@kubb/mcp 5.0.0-alpha.9 → 5.0.0-beta.2

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,5 +1,5 @@
1
1
  import { AsyncEventEmitter } from '@internals/utils'
2
- import { type Config, type KubbEvents, safeBuild, setup } from '@kubb/core'
2
+ import { type Config, createKubb, type KubbHooks } from '@kubb/core'
3
3
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.d.ts'
4
4
  import type { z } from 'zod'
5
5
  import type { generateSchema } from '../schemas/generateSchema.ts'
@@ -20,7 +20,7 @@ export async function generate(schema: z.infer<typeof generateSchema>, handler:
20
20
  const { config: configPath, input, output, logLevel } = schema
21
21
 
22
22
  try {
23
- const events = new AsyncEventEmitter<KubbEvents>()
23
+ const hooks = new AsyncEventEmitter<KubbHooks>()
24
24
  const messages: string[] = []
25
25
 
26
26
  // Helper to send notifications
@@ -36,50 +36,52 @@ export async function generate(schema: z.infer<typeof generateSchema>, handler:
36
36
  }
37
37
 
38
38
  // Capture events for output and send notifications
39
- events.on('info', async (message: string) => {
39
+ hooks.on('kubb:info', async ({ message }: { message: string }) => {
40
40
  await notify(NotifyTypes.INFO, message)
41
41
  })
42
42
 
43
- events.on('success', async (message: string) => {
43
+ hooks.on('kubb:success', async ({ message }: { message: string }) => {
44
44
  await notify(NotifyTypes.SUCCESS, message)
45
45
  })
46
46
 
47
- events.on('error', async (error: Error) => {
47
+ hooks.on('kubb:error', async ({ error }: { error: Error }) => {
48
48
  await notify(NotifyTypes.ERROR, error.message, { stack: error.stack })
49
49
  })
50
50
 
51
- events.on('warn', async (message: string) => {
51
+ hooks.on('kubb:warn', async ({ message }: { message: string }) => {
52
52
  await notify(NotifyTypes.WARN, message)
53
53
  })
54
54
 
55
55
  // Plugin lifecycle events
56
- events.on('plugin:start', async ({ name }: { name: string }) => {
57
- await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${name}`)
56
+ hooks.on('kubb:plugin:start', async ({ plugin }) => {
57
+ await notify(NotifyTypes.PLUGIN_START, `Plugin starting: ${plugin.name}`)
58
58
  })
59
59
 
60
- events.on('plugin:end', async ({ name, duration }: { name: string; duration?: number }) => {
61
- await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${name}`, { duration })
60
+ hooks.on('kubb:plugin:end', async ({ plugin, duration }) => {
61
+ await notify(NotifyTypes.PLUGIN_END, `Plugin finished: ${plugin.name}`, {
62
+ duration,
63
+ })
62
64
  })
63
65
 
64
66
  // File processing events
65
- events.on('files:processing:start', async () => {
67
+ hooks.on('kubb:files:processing:start', async () => {
66
68
  await notify(NotifyTypes.FILES_START, 'Starting file processing')
67
69
  })
68
70
 
69
- events.on('file:processing:update', async ({ file }: { file: { name: string } }) => {
71
+ hooks.on('kubb:file:processing:update', async ({ file }: { file: { name: string } }) => {
70
72
  await notify(NotifyTypes.FILE_UPDATE, `Processing file: ${file.name}`)
71
73
  })
72
74
 
73
- events.on('files:processing:end', async () => {
75
+ hooks.on('kubb:files:processing:end', async () => {
74
76
  await notify(NotifyTypes.FILES_END, 'File processing complete')
75
77
  })
76
78
 
77
79
  // Generation events
78
- events.on('generation:start', async () => {
80
+ hooks.on('kubb:generation:start', async () => {
79
81
  await notify(NotifyTypes.GENERATION_START, 'Generation started')
80
82
  })
81
83
 
82
- events.on('generation:end', async () => {
84
+ hooks.on('kubb:generation:end', async () => {
83
85
  await notify(NotifyTypes.GENERATION_END, 'Generation ended')
84
86
  })
85
87
 
@@ -96,7 +98,10 @@ export async function generate(schema: z.infer<typeof generateSchema>, handler:
96
98
  throw new Error('Array type in kubb.config.ts is not supported in this tool. Please provide a single configuration object.')
97
99
  }
98
100
 
99
- userConfig = await resolveUserConfig(userConfig, { configPath, logLevel })
101
+ userConfig = await resolveUserConfig(userConfig, {
102
+ configPath,
103
+ logLevel,
104
+ })
100
105
  } catch (error) {
101
106
  const errorMessage = error instanceof Error ? error.message : String(error)
102
107
  await notify(NotifyTypes.CONFIG_ERROR, errorMessage)
@@ -138,20 +143,12 @@ export async function generate(schema: z.infer<typeof generateSchema>, handler:
138
143
  // Setup and build
139
144
  await notify(NotifyTypes.SETUP_START, 'Setting up Kubb')
140
145
 
141
- const { fabric, driver, sources } = await setup({
142
- config,
143
- events,
144
- })
146
+ const kubb = createKubb(config, { hooks })
147
+ await kubb.setup()
145
148
  await notify(NotifyTypes.SETUP_END, 'Kubb setup complete')
146
149
 
147
150
  await notify(NotifyTypes.BUILD_START, 'Starting build')
148
- const { files, failedPlugins, error } = await safeBuild(
149
- {
150
- config,
151
- events,
152
- },
153
- { driver, fabric, events, sources },
154
- )
151
+ const { files, failedPlugins, error } = await kubb.safeBuild()
155
152
  await notify(NotifyTypes.BUILD_END, `Build complete - Generated ${files.length} files`)
156
153
 
157
154
  if (error || failedPlugins.size > 0) {
@@ -1,42 +1,64 @@
1
+ import { existsSync } from 'node:fs'
1
2
  import path from 'node:path'
2
3
  import type { Config } from '@kubb/core'
3
- import createJiti from 'jiti'
4
+ import { unrun } from 'unrun'
5
+ import { ALLOWED_CONFIG_EXTENSIONS } from '../constants.ts'
4
6
  import { NotifyTypes } from '../types.ts'
5
7
 
6
8
  type NotifyFunction = (type: string, message: string, data?: Record<string, unknown>) => Promise<void>
7
9
 
8
- const jiti = createJiti(import.meta.url, {
9
- sourceMaps: true,
10
- })
10
+ const loadedModules = new Map<string, unknown>()
11
+
12
+ async function loadModule(filePath: string): Promise<unknown> {
13
+ const ext = path.extname(filePath)
14
+ if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
15
+ throw new Error(`Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(', ')}`)
16
+ }
17
+ if (loadedModules.has(filePath)) {
18
+ return loadedModules.get(filePath)
19
+ }
20
+ const { module } = await unrun({ path: filePath })
21
+ loadedModules.set(filePath, module)
22
+ return module
23
+ }
11
24
 
12
- /**
13
- * Load the user configuration from the specified path or current directory
14
- */
15
25
  export async function loadUserConfig(configPath: string | undefined, { notify }: { notify: NotifyFunction }): Promise<{ userConfig: Config; cwd: string }> {
16
26
  let userConfig: Config | undefined
17
27
  let cwd: string
18
28
 
19
29
  if (configPath) {
20
- // Resolve the config path to absolute path and get its directory
21
- cwd = path.dirname(path.resolve(configPath))
30
+ const ext = path.extname(configPath)
31
+ if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {
32
+ const msg = `Invalid config file extension "${ext}". Allowed: ${[...ALLOWED_CONFIG_EXTENSIONS].join(', ')}`
33
+ await notify(NotifyTypes.CONFIG_ERROR, msg)
34
+ throw new Error(msg)
35
+ }
36
+ const base = path.resolve(process.cwd())
37
+ const resolvedConfigPath = path.resolve(base, configPath)
38
+ const relative = path.relative(base, resolvedConfigPath)
39
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
40
+ const msg = 'Invalid config file path: must be within the current working directory'
41
+ await notify(NotifyTypes.CONFIG_ERROR, msg)
42
+ throw new Error(msg)
43
+ }
44
+ cwd = path.dirname(resolvedConfigPath)
22
45
 
23
- // Try to load from path
24
46
  try {
25
- userConfig = await jiti.import(configPath, { default: true })
26
- await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${configPath}`)
47
+ userConfig = (await loadModule(resolvedConfigPath)) as Config
48
+ await notify(NotifyTypes.CONFIG_LOADED, `Loaded config from ${resolvedConfigPath}`)
27
49
  } catch (error) {
28
50
  await notify(NotifyTypes.CONFIG_ERROR, `Failed to load config: ${error instanceof Error ? error.message : String(error)}`)
29
51
  throw new Error(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`)
30
52
  }
31
53
  } else {
32
- // Look for kubb.config in current directory with various extensions
33
54
  cwd = process.cwd()
34
- const configFileNames = ['kubb.config.ts', 'kubb.config.js', 'kubb.config.cjs']
55
+ const configFileNames = ['kubb.config.ts', 'kubb.config.mts', 'kubb.config.cts', 'kubb.config.js', 'kubb.config.cjs']
35
56
 
36
57
  for (const configFileName of configFileNames) {
58
+ const configFilePath = path.resolve(process.cwd(), configFileName)
59
+ if (!existsSync(configFilePath)) continue
37
60
  try {
38
- const configFilePath = path.resolve(process.cwd(), configFileName)
39
- userConfig = await jiti.import(configFilePath, { default: true })
61
+ userConfig = (await loadModule(configFilePath)) as Config
40
62
  await notify(NotifyTypes.CONFIG_LOADED, `Loaded ${configFileName} from current directory`)
41
63
  break
42
64
  } catch {
@@ -1,5 +1,5 @@
1
1
  import { isPromise } from '@internals/utils'
2
- import type { CLIOptions, Config, UserConfig } from '@kubb/core'
2
+ import type { CLIOptions, Config } from '@kubb/core'
3
3
 
4
4
  export type ResolveUserConfigOptions = {
5
5
  configPath?: string
@@ -9,11 +9,14 @@ export type ResolveUserConfigOptions = {
9
9
  /**
10
10
  * Resolve the config by handling function configs and returning the final configuration
11
11
  */
12
- export async function resolveUserConfig(userConfig: UserConfig, options: ResolveUserConfigOptions): Promise<Config> {
13
- let kubbUserConfig = Promise.resolve(userConfig) as Promise<UserConfig>
12
+ export async function resolveUserConfig(config: Config, options: ResolveUserConfigOptions): Promise<Config> {
13
+ let kubbUserConfig = Promise.resolve(config) as Promise<Config>
14
14
 
15
- if (typeof userConfig === 'function') {
16
- const possiblePromise = (userConfig as any)({ logLevel: options.logLevel, config: options.configPath } as CLIOptions)
15
+ if (typeof config === 'function') {
16
+ const possiblePromise = (config as any)({
17
+ logLevel: options.logLevel,
18
+ config: options.configPath,
19
+ } as CLIOptions)
17
20
  if (isPromise(possiblePromise)) {
18
21
  kubbUserConfig = possiblePromise
19
22
  } else {