@kubb/cli 5.0.0-alpha.6 → 5.0.0-alpha.61

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.
Files changed (137) hide show
  1. package/README.md +4 -2
  2. package/bin/kubb.js +6 -0
  3. package/dist/{agent-CCb1mJmy.cjs → agent-Bzvmh47F.cjs} +5 -5
  4. package/dist/agent-Bzvmh47F.cjs.map +1 -0
  5. package/dist/agent-CouS-H2S.js +112 -0
  6. package/dist/agent-CouS-H2S.js.map +1 -0
  7. package/dist/agent-Zo8CQwxZ.cjs +116 -0
  8. package/dist/agent-Zo8CQwxZ.cjs.map +1 -0
  9. package/dist/{agent-Dq1OWkeN.js → agent-byFj-XQv.js} +5 -5
  10. package/dist/agent-byFj-XQv.js.map +1 -0
  11. package/dist/{constants-BTUap0zs.cjs → constants-BgLUQ1nx.cjs} +50 -9
  12. package/dist/constants-BgLUQ1nx.cjs.map +1 -0
  13. package/dist/{constants-CM3dJzjK.js → constants-mIdg9ls2.js} +39 -10
  14. package/dist/constants-mIdg9ls2.js.map +1 -0
  15. package/dist/define-Bdn8j5VM.cjs +54 -0
  16. package/dist/define-Bdn8j5VM.cjs.map +1 -0
  17. package/dist/define-Ctii4bel.js +43 -0
  18. package/dist/define-Ctii4bel.js.map +1 -0
  19. package/dist/{errors-DBW0N9w4.cjs → errors-CLCjoSg0.cjs} +22 -6
  20. package/dist/errors-CLCjoSg0.cjs.map +1 -0
  21. package/dist/errors-CjPmyZHy.js +43 -0
  22. package/dist/errors-CjPmyZHy.js.map +1 -0
  23. package/dist/{generate-DM1NsxI1.cjs → generate-CdZ9NOlM.cjs} +555 -262
  24. package/dist/generate-CdZ9NOlM.cjs.map +1 -0
  25. package/dist/{generate-D2nVnbYA.js → generate-CpiY2Cu5.js} +548 -255
  26. package/dist/generate-CpiY2Cu5.js.map +1 -0
  27. package/dist/{generate-B9sA0jDk.js → generate-DCKEQWHR.js} +3 -3
  28. package/dist/generate-DCKEQWHR.js.map +1 -0
  29. package/dist/{generate-CH1EAyW4.cjs → generate-hv4f8gXv.cjs} +3 -3
  30. package/dist/generate-hv4f8gXv.cjs.map +1 -0
  31. package/dist/index.cjs +49 -21
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +49 -21
  35. package/dist/index.js.map +1 -1
  36. package/dist/{init-Dpm2lzZO.js → init-B5h9W-ng.js} +4 -4
  37. package/dist/init-B5h9W-ng.js.map +1 -0
  38. package/dist/{init-hmolV6B4.cjs → init-CmrUd26x.cjs} +31 -21
  39. package/dist/init-CmrUd26x.cjs.map +1 -0
  40. package/dist/{init-B80nQshO.cjs → init-D9yt-80C.cjs} +4 -4
  41. package/dist/init-D9yt-80C.cjs.map +1 -0
  42. package/dist/{init-C-InrmSY.js → init-DEC1gCsk.js} +27 -17
  43. package/dist/init-DEC1gCsk.js.map +1 -0
  44. package/dist/{mcp-ChHFPRzD.cjs → mcp-BCLUiocM.cjs} +12 -6
  45. package/dist/mcp-BCLUiocM.cjs.map +1 -0
  46. package/dist/{mcp-BDZI-Jjk.js → mcp-BUrpBKk3.js} +4 -4
  47. package/dist/{mcp-BDZI-Jjk.js.map → mcp-BUrpBKk3.js.map} +1 -1
  48. package/dist/{mcp-D2SHEg_d.js → mcp-C-HbwyVA.js} +11 -4
  49. package/dist/mcp-C-HbwyVA.js.map +1 -0
  50. package/dist/{mcp-gFMKRdfg.cjs → mcp-h74MvJ3J.cjs} +4 -4
  51. package/dist/{mcp-gFMKRdfg.cjs.map → mcp-h74MvJ3J.cjs.map} +1 -1
  52. package/dist/{package-BTEeDglN.cjs → package-Bf1bbyZj.cjs} +2 -2
  53. package/dist/package-Bf1bbyZj.cjs.map +1 -0
  54. package/dist/package-DR__6vbM.js +6 -0
  55. package/dist/package-DR__6vbM.js.map +1 -0
  56. package/dist/{shell-7HPrTCJ5.cjs → shell-475fQKaX.cjs} +8 -3
  57. package/dist/shell-475fQKaX.cjs.map +1 -0
  58. package/dist/{shell-DqqWsHCD.js → shell-DLzN4fRo.js} +8 -3
  59. package/dist/shell-DLzN4fRo.js.map +1 -0
  60. package/dist/{telemetry-DxiR7clS.js → telemetry-CS5ZE0cN.js} +48 -6
  61. package/dist/telemetry-CS5ZE0cN.js.map +1 -0
  62. package/dist/{telemetry-Cn9X1I5B.cjs → telemetry-kmnc5OAT.cjs} +50 -8
  63. package/dist/telemetry-kmnc5OAT.cjs.map +1 -0
  64. package/dist/{validate-l8vLmwKA.js → validate-BR_0eveE.js} +5 -13
  65. package/dist/validate-BR_0eveE.js.map +1 -0
  66. package/dist/{validate-PAYtxnS6.js → validate-Cm2CHYG_.js} +4 -4
  67. package/dist/validate-Cm2CHYG_.js.map +1 -0
  68. package/dist/{validate-BYzj0qVB.cjs → validate-D4FmUN0k.cjs} +4 -4
  69. package/dist/validate-D4FmUN0k.cjs.map +1 -0
  70. package/dist/{validate-Bbrn3Q-A.cjs → validate-Db7vfaU9.cjs} +6 -14
  71. package/dist/validate-Db7vfaU9.cjs.map +1 -0
  72. package/package.json +47 -46
  73. package/src/commands/agent/start.ts +20 -4
  74. package/src/commands/generate.ts +35 -6
  75. package/src/commands/init.ts +6 -1
  76. package/src/commands/validate.ts +6 -1
  77. package/src/constants.ts +40 -11
  78. package/src/index.ts +10 -12
  79. package/src/loggers/clackLogger.ts +54 -46
  80. package/src/loggers/fileSystemLogger.ts +13 -11
  81. package/src/loggers/githubActionsLogger.ts +22 -22
  82. package/src/loggers/plainLogger.ts +21 -21
  83. package/src/runners/agent.ts +81 -34
  84. package/src/runners/generate.ts +98 -116
  85. package/src/runners/init.ts +9 -9
  86. package/src/runners/mcp.ts +19 -3
  87. package/src/runners/validate.ts +19 -15
  88. package/src/types.ts +11 -0
  89. package/src/utils/executeHooks.ts +15 -15
  90. package/src/utils/flags.ts +10 -0
  91. package/src/utils/getConfig.ts +10 -0
  92. package/src/utils/getCosmiConfig.ts +9 -3
  93. package/src/utils/getSummary.ts +1 -1
  94. package/src/utils/runHook.ts +27 -9
  95. package/src/utils/telemetry.ts +16 -3
  96. package/bin/kubb.cjs +0 -18
  97. package/dist/agent-C6o_6GSJ.cjs +0 -92
  98. package/dist/agent-C6o_6GSJ.cjs.map +0 -1
  99. package/dist/agent-CCb1mJmy.cjs.map +0 -1
  100. package/dist/agent-Dq1OWkeN.js.map +0 -1
  101. package/dist/agent-L50VNhXv.js +0 -88
  102. package/dist/agent-L50VNhXv.js.map +0 -1
  103. package/dist/constants-BTUap0zs.cjs.map +0 -1
  104. package/dist/constants-CM3dJzjK.js.map +0 -1
  105. package/dist/define--M_JMcDC.js +0 -25
  106. package/dist/define--M_JMcDC.js.map +0 -1
  107. package/dist/define-D6Kfm7-Z.cjs +0 -36
  108. package/dist/define-D6Kfm7-Z.cjs.map +0 -1
  109. package/dist/errors-6mF_WKxg.js +0 -27
  110. package/dist/errors-6mF_WKxg.js.map +0 -1
  111. package/dist/errors-DBW0N9w4.cjs.map +0 -1
  112. package/dist/generate-B9sA0jDk.js.map +0 -1
  113. package/dist/generate-CH1EAyW4.cjs.map +0 -1
  114. package/dist/generate-D2nVnbYA.js.map +0 -1
  115. package/dist/generate-DM1NsxI1.cjs.map +0 -1
  116. package/dist/init-B80nQshO.cjs.map +0 -1
  117. package/dist/init-C-InrmSY.js.map +0 -1
  118. package/dist/init-Dpm2lzZO.js.map +0 -1
  119. package/dist/init-hmolV6B4.cjs.map +0 -1
  120. package/dist/jiti-Cd3S0xwr.cjs +0 -16
  121. package/dist/jiti-Cd3S0xwr.cjs.map +0 -1
  122. package/dist/jiti-e08mD2Ph.js +0 -11
  123. package/dist/jiti-e08mD2Ph.js.map +0 -1
  124. package/dist/mcp-ChHFPRzD.cjs.map +0 -1
  125. package/dist/mcp-D2SHEg_d.js.map +0 -1
  126. package/dist/package-BTEeDglN.cjs.map +0 -1
  127. package/dist/package-BZMTT_6s.js +0 -6
  128. package/dist/package-BZMTT_6s.js.map +0 -1
  129. package/dist/shell-7HPrTCJ5.cjs.map +0 -1
  130. package/dist/shell-DqqWsHCD.js.map +0 -1
  131. package/dist/telemetry-Cn9X1I5B.cjs.map +0 -1
  132. package/dist/telemetry-DxiR7clS.js.map +0 -1
  133. package/dist/validate-BYzj0qVB.cjs.map +0 -1
  134. package/dist/validate-Bbrn3Q-A.cjs.map +0 -1
  135. package/dist/validate-PAYtxnS6.js.map +0 -1
  136. package/dist/validate-l8vLmwKA.js.map +0 -1
  137. package/src/utils/getIntro.ts +0 -1
@@ -4,26 +4,13 @@ import process from 'node:process'
4
4
  import { styleText } from 'node:util'
5
5
  import * as clack from '@clack/prompts'
6
6
  import type { AsyncEventEmitter } from '@internals/utils'
7
- import { AsyncEventEmitter as AsyncEventEmitterClass, executeIfOnline, toError } from '@internals/utils'
8
- import {
9
- type CLIOptions,
10
- type Config,
11
- detectFormatter,
12
- detectLinter,
13
- formatters,
14
- getConfigs,
15
- isInputPath,
16
- type KubbEvents,
17
- linters,
18
- logLevel as logLevelMap,
19
- PromiseManager,
20
- safeBuild,
21
- setup,
22
- } from '@kubb/core'
7
+ import { AsyncEventEmitter as AsyncEventEmitterClass, detectFormatter, detectLinter, executeIfOnline, formatters, linters, toError } from '@internals/utils'
8
+ import { type CLIOptions, type Config, createKubb, isInputPath, type KubbHooks, logLevel as logLevelMap } from '@kubb/core'
23
9
  import { version } from '../../package.json'
24
10
  import { KUBB_NPM_PACKAGE_URL } from '../constants.ts'
25
11
  import { setupLogger } from '../loggers/utils.ts'
26
12
  import { executeHooks } from '../utils/executeHooks.ts'
13
+ import { getConfigs } from '../utils/getConfig.ts'
27
14
  import { getCosmiConfig } from '../utils/getCosmiConfig.ts'
28
15
  import { buildTelemetryEvent, sendTelemetry } from '../utils/telemetry.ts'
29
16
  import { startWatcher } from '../utils/watcher.ts'
@@ -31,7 +18,7 @@ import { startWatcher } from '../utils/watcher.ts'
31
18
  type GenerateProps = {
32
19
  input?: string
33
20
  config: Config
34
- events: AsyncEventEmitter<KubbEvents>
21
+ hooks: AsyncEventEmitter<KubbHooks>
35
22
  logLevel: number
36
23
  }
37
24
 
@@ -39,7 +26,7 @@ type ToolMap = typeof formatters | typeof linters
39
26
 
40
27
  type RunToolPassOptions = {
41
28
  toolValue: string
42
- detect: () => Promise<string | undefined>
29
+ detect: () => Promise<string | null>
43
30
  toolMap: ToolMap
44
31
  /** Short noun used in "Auto-detected <toolLabel>:" message, e.g. "formatter" or "linter". */
45
32
  toolLabel: string
@@ -49,7 +36,7 @@ type RunToolPassOptions = {
49
36
  configName: string | undefined
50
37
  outputPath: string
51
38
  logLevel: number
52
- events: AsyncEventEmitter<KubbEvents>
39
+ hooks: AsyncEventEmitter<KubbHooks>
53
40
  onStart: () => Promise<void>
54
41
  onEnd: () => Promise<void>
55
42
  }
@@ -64,7 +51,7 @@ async function runToolPass({
64
51
  configName,
65
52
  outputPath,
66
53
  logLevel,
67
- events,
54
+ hooks,
68
55
  onStart,
69
56
  onEnd,
70
57
  }: RunToolPassOptions) {
@@ -74,13 +61,15 @@ async function runToolPass({
74
61
  if (resolvedTool === 'auto') {
75
62
  const detected = await detect()
76
63
  if (!detected) {
77
- await events.emit('warn', noToolMessage)
64
+ await hooks.emit('kubb:warn', { message: noToolMessage })
78
65
  } else {
79
66
  resolvedTool = detected
80
- await events.emit('info', `Auto-detected ${toolLabel}: ${styleText('dim', resolvedTool)}`)
67
+ await hooks.emit('kubb:info', { message: `Auto-detected ${toolLabel}: ${styleText('dim', resolvedTool)}` })
81
68
  }
82
69
  }
83
70
 
71
+ let toolError: Error | undefined
72
+
84
73
  if (resolvedTool && resolvedTool !== 'auto' && resolvedTool in toolMap) {
85
74
  const toolConfig = toolMap[resolvedTool as keyof ToolMap]
86
75
 
@@ -88,33 +77,32 @@ async function runToolPass({
88
77
  const hookId = createHash('sha256').update([configName, resolvedTool].filter(Boolean).join('-')).digest('hex')
89
78
 
90
79
  // Wire up the hook:end listener BEFORE emitting hook:start to avoid the race condition
91
- // where hook:end fires synchronously inside emit('hook:start') before the listener is registered.
80
+ // where hook:end fires synchronously inside emit('kubb:hook:start') before the listener is registered.
92
81
  const hookEndPromise = new Promise<void>((resolve, reject) => {
93
- const handler = ({ id, success, error }: { id?: string; command: string; args?: readonly string[]; success: boolean; error: Error | null }) => {
94
- if (id !== hookId) return
95
- events.off('hook:end', handler)
96
- if (!success) {
97
- reject(error ?? new Error(`${toolConfig.errorMessage}`))
82
+ const handler = (ctx: { id?: string; command: string; args?: readonly string[]; success: boolean; error: Error | null }) => {
83
+ if (ctx.id !== hookId) return
84
+ hooks.off('kubb:hook:end', handler)
85
+ if (!ctx.success) {
86
+ reject(ctx.error ?? new Error(`${toolConfig.errorMessage}`))
98
87
  return
99
88
  }
100
- events
101
- .emit(
102
- 'success',
103
- [
89
+ hooks
90
+ .emit('kubb:success', {
91
+ message: [
104
92
  `${successPrefix} with ${styleText('dim', resolvedTool)}`,
105
93
  logLevel >= logLevelMap.info ? `on ${styleText('dim', outputPath)}` : undefined,
106
94
  'successfully',
107
95
  ]
108
96
  .filter(Boolean)
109
97
  .join(' '),
110
- )
98
+ })
111
99
  .then(resolve)
112
100
  .catch(reject)
113
101
  }
114
- events.on('hook:end', handler)
102
+ hooks.on('kubb:hook:end', handler)
115
103
  })
116
104
 
117
- await events.emit('hook:start', {
105
+ await hooks.emit('kubb:hook:start', {
118
106
  id: hookId,
119
107
  command: toolConfig.command,
120
108
  args: toolConfig.args(outputPath),
@@ -122,59 +110,52 @@ async function runToolPass({
122
110
 
123
111
  await hookEndPromise
124
112
  } catch (caughtError) {
125
- const err = new Error(toolConfig.errorMessage)
126
- err.cause = caughtError
127
- await events.emit('error', err)
113
+ // Use the actual error from the hook. toolConfig.errorMessage (e.g. "Oxlint not found")
114
+ // is misleading when the binary was found and ran but exited with a non-zero code.
115
+ // runHook already emitted kubb:error for binary-not-found cases; here we surface the
116
+ // real reason (e.g. "Hook execute failed: oxlint --fix …") for non-zero exits.
117
+ const err = toError(caughtError)
118
+ await hooks.emit('kubb:error', { error: err })
119
+ toolError = err
128
120
  }
129
121
  }
130
122
 
131
123
  await onEnd()
124
+
125
+ if (toolError) {
126
+ throw toolError
127
+ }
132
128
  }
133
129
 
134
- async function generate({ input, config: userConfig, events, logLevel }: GenerateProps): Promise<void> {
135
- const inputPath = input ?? ('path' in userConfig.input ? userConfig.input.path : undefined)
130
+ async function generate(options: GenerateProps): Promise<void> {
131
+ const { input, hooks, logLevel } = options
132
+
136
133
  const hrStart = process.hrtime()
134
+ const inputPath = input ?? ('path' in options.config.input ? options.config.input.path : undefined)
137
135
 
138
136
  const config: Config = {
139
- ...userConfig,
140
- root: userConfig.root || process.cwd(),
137
+ ...options.config,
141
138
  input: inputPath
142
139
  ? {
143
- ...userConfig.input,
140
+ ...options.config.input,
144
141
  path: inputPath,
145
142
  }
146
- : userConfig.input,
147
- output: {
148
- write: true,
149
- barrelType: 'named',
150
- extension: {
151
- '.ts': '.ts',
152
- },
153
- format: 'prettier',
154
- ...userConfig.output,
155
- },
156
- }
143
+ : options.config.input,
144
+ ...options.config.output,
145
+ } satisfies Config
157
146
 
158
- await events.emit('generation:start', config)
147
+ const kubb = createKubb(config, { hooks })
148
+ await kubb.setup()
159
149
 
160
- await events.emit('info', config.name ? `Setup generation ${styleText('bold', config.name)}` : 'Setup generation', inputPath)
150
+ await hooks.emit('kubb:generation:start', { config })
161
151
 
162
- const { sources, fabric, pluginManager } = await setup({
163
- config,
164
- events,
165
- })
152
+ await hooks.emit('kubb:info', { message: config.name ? `Setup generation ${styleText('bold', config.name)}` : 'Setup generation', info: inputPath })
166
153
 
167
- await events.emit('info', config.name ? `Build generation ${styleText('bold', config.name)}` : 'Build generation', inputPath)
154
+ await hooks.emit('kubb:info', { message: config.name ? `Build generation ${styleText('bold', config.name)}` : 'Build generation', info: inputPath })
168
155
 
169
- const { files, failedPlugins, pluginTimings, error } = await safeBuild(
170
- {
171
- config,
172
- events,
173
- },
174
- { pluginManager, fabric, events, sources },
175
- )
156
+ const { files, failedPlugins, pluginTimings, error, driver } = await kubb.safeBuild()
176
157
 
177
- await events.emit('info', 'Load summary')
158
+ await hooks.emit('kubb:info', { message: 'Load summary' })
178
159
 
179
160
  // Handle build failures (either from failed plugins or general errors)
180
161
 
@@ -189,12 +170,13 @@ async function generate({ input, config: userConfig, events, logLevel }: Generat
189
170
  ].filter(Boolean)
190
171
 
191
172
  for (const err of allErrors) {
192
- await events.emit('error', err)
173
+ await hooks.emit('kubb:error', { error: err })
193
174
  }
194
175
 
195
- await events.emit('generation:end', config, files, sources)
176
+ await hooks.emit('kubb:generation:end', { config, files, sources: kubb.sources })
196
177
 
197
- await events.emit('generation:summary', config, {
178
+ await hooks.emit('kubb:generation:summary', {
179
+ config,
198
180
  failedPlugins,
199
181
  filesCreated: files.length,
200
182
  status: 'failed',
@@ -206,7 +188,10 @@ async function generate({ input, config: userConfig, events, logLevel }: Generat
206
188
  buildTelemetryEvent({
207
189
  command: 'generate',
208
190
  kubbVersion: version,
209
- plugins: pluginManager.plugins.map((p) => ({ name: p.name, options: p.options as Record<string, unknown> })),
191
+ plugins: Array.from(driver.plugins.values(), (p) => ({
192
+ name: p.name,
193
+ options: p.options as Record<string, unknown>,
194
+ })),
210
195
  hrStart,
211
196
  filesCreated: files.length,
212
197
  status: 'failed',
@@ -216,8 +201,8 @@ async function generate({ input, config: userConfig, events, logLevel }: Generat
216
201
  process.exit(1)
217
202
  }
218
203
 
219
- await events.emit('success', 'Generation successfully', inputPath)
220
- await events.emit('generation:end', config, files, sources)
204
+ await hooks.emit('kubb:success', { message: 'Generation successfully', info: inputPath })
205
+ await hooks.emit('kubb:generation:end', { config, files, sources: kubb.sources })
221
206
 
222
207
  const outputPath = path.resolve(config.root, config.output.path)
223
208
 
@@ -228,13 +213,13 @@ async function generate({ input, config: userConfig, events, logLevel }: Generat
228
213
  toolMap: formatters,
229
214
  toolLabel: 'formatter',
230
215
  successPrefix: 'Formatting',
231
- noToolMessage: 'No formatter found (biome, prettier, or oxfmt). Skipping formatting.',
216
+ noToolMessage: 'No formatter found (oxfmt, biome, or prettier). Skipping formatting.',
232
217
  configName: config.name,
233
218
  outputPath,
234
219
  logLevel,
235
- events,
236
- onStart: () => events.emit('format:start'),
237
- onEnd: () => events.emit('format:end'),
220
+ hooks,
221
+ onStart: () => hooks.emit('kubb:format:start'),
222
+ onEnd: () => hooks.emit('kubb:format:end'),
238
223
  })
239
224
  }
240
225
 
@@ -245,25 +230,26 @@ async function generate({ input, config: userConfig, events, logLevel }: Generat
245
230
  toolMap: linters,
246
231
  toolLabel: 'linter',
247
232
  successPrefix: 'Linting',
248
- noToolMessage: 'No linter found (biome, oxlint, or eslint). Skipping linting.',
233
+ noToolMessage: 'No linter found (oxlint, biome, or eslint). Skipping linting.',
249
234
  configName: config.name,
250
235
  outputPath,
251
236
  logLevel,
252
- events,
253
- onStart: () => events.emit('lint:start'),
254
- onEnd: () => events.emit('lint:end'),
237
+ hooks,
238
+ onStart: () => hooks.emit('kubb:lint:start'),
239
+ onEnd: () => hooks.emit('kubb:lint:end'),
255
240
  })
256
241
  }
257
242
 
258
243
  if (config.hooks) {
259
- await events.emit('hooks:start')
260
- await executeHooks({ hooks: config.hooks, events })
244
+ await hooks.emit('kubb:hooks:start')
245
+ await executeHooks({ configHooks: config.hooks, hooks })
261
246
 
262
- await events.emit('hooks:end')
247
+ await hooks.emit('kubb:hooks:end')
263
248
  }
264
249
 
265
250
  // Only reached when there are no failures (process.exit(1) is called above otherwise)
266
- await events.emit('generation:summary', config, {
251
+ await hooks.emit('kubb:generation:summary', {
252
+ config,
267
253
  failedPlugins,
268
254
  filesCreated: files.length,
269
255
  status: 'success',
@@ -274,7 +260,10 @@ async function generate({ input, config: userConfig, events, logLevel }: Generat
274
260
  const telemetryEvent = buildTelemetryEvent({
275
261
  command: 'generate',
276
262
  kubbVersion: version,
277
- plugins: pluginManager.plugins.map((p) => ({ name: p.name, options: p.options as Record<string, unknown> })),
263
+ plugins: Array.from(driver.plugins.values(), (p) => ({
264
+ name: p.name,
265
+ options: p.options as Record<string, unknown>,
266
+ })),
278
267
  hrStart,
279
268
  filesCreated: files.length,
280
269
  status: 'success',
@@ -292,10 +281,9 @@ type GenerateCommandOptions = {
292
281
 
293
282
  export async function runGenerateCommand({ input, configPath, logLevel: logLevelKey, watch }: GenerateCommandOptions): Promise<void> {
294
283
  const logLevel = logLevelMap[logLevelKey as keyof typeof logLevelMap] ?? logLevelMap.info
295
- const events = new AsyncEventEmitterClass<KubbEvents>()
296
- const promiseManager = new PromiseManager()
284
+ const hooks = new AsyncEventEmitterClass<KubbHooks>()
297
285
 
298
- await setupLogger(events, { logLevel })
286
+ await setupLogger(hooks, { logLevel })
299
287
 
300
288
  await executeIfOnline(async () => {
301
289
  try {
@@ -304,7 +292,7 @@ export async function runGenerateCommand({ input, configPath, logLevel: logLevel
304
292
  const latestVersion = data.version
305
293
 
306
294
  if (latestVersion && version < latestVersion) {
307
- await events.emit('version:new', version, latestVersion)
295
+ await hooks.emit('kubb:version:new', { currentVersion: version, latestVersion })
308
296
  }
309
297
  } catch {
310
298
  // Ignore network errors for version check
@@ -315,37 +303,31 @@ export async function runGenerateCommand({ input, configPath, logLevel: logLevel
315
303
  const result = await getCosmiConfig('kubb', configPath)
316
304
  const configs = await getConfigs(result.config, { input } as CLIOptions)
317
305
 
318
- await events.emit('config:start')
319
- await events.emit('info', 'Config loaded', path.relative(process.cwd(), result.filepath))
320
- await events.emit('success', 'Config loaded successfully', path.relative(process.cwd(), result.filepath))
321
- await events.emit('config:end', configs)
322
-
323
- await events.emit('lifecycle:start', version)
324
-
325
- const promises = configs.map((config) => {
326
- return async () => {
327
- if (isInputPath(config) && watch) {
328
- await startWatcher([input || config.input.path], async (paths) => {
329
- // remove to avoid duplicate listeners after each change
330
- events.removeAll()
306
+ await hooks.emit('kubb:config:start')
307
+ await hooks.emit('kubb:info', { message: 'Config loaded', info: path.relative(process.cwd(), result.filepath) })
308
+ await hooks.emit('kubb:success', { message: 'Config loaded successfully', info: path.relative(process.cwd(), result.filepath) })
309
+ await hooks.emit('kubb:config:end', { configs })
331
310
 
332
- await generate({ input, config, logLevel, events })
311
+ await hooks.emit('kubb:lifecycle:start', { version })
333
312
 
334
- clack.log.step(styleText('yellow', `Watching for changes in ${paths.join(' and ')}`))
335
- })
313
+ for (const config of configs) {
314
+ if (isInputPath(config) && watch) {
315
+ await startWatcher([input || config.input.path], async (paths) => {
316
+ // remove to avoid duplicate listeners after each change
317
+ hooks.removeAll()
336
318
 
337
- return
338
- }
319
+ await generate({ input, config, logLevel, hooks })
339
320
 
340
- await generate({ input, config, logLevel, events })
321
+ clack.log.step(styleText('yellow', `Watching for changes in ${paths.join(' and ')}`))
322
+ })
323
+ } else {
324
+ await generate({ input, config, logLevel, hooks })
341
325
  }
342
- })
343
-
344
- await promiseManager.run('seq', promises)
326
+ }
345
327
 
346
- await events.emit('lifecycle:end')
328
+ await hooks.emit('kubb:lifecycle:end')
347
329
  } catch (error) {
348
- await events.emit('error', toError(error))
330
+ await hooks.emit('kubb:error', { error: toError(error) })
349
331
  process.exit(1)
350
332
  }
351
333
  }
@@ -5,7 +5,7 @@ import { styleText } from 'node:util'
5
5
  import * as clack from '@clack/prompts'
6
6
  import type { PackageManagerInfo } from '@internals/utils'
7
7
  import { detectPackageManager } from '@internals/utils'
8
- import { initDefaults, pluginDefaultConfigs } from '../constants.ts'
8
+ import { initDefaults, KUBB_CONFIG_FILENAME, pluginDefaultConfigs } from '../constants.ts'
9
9
  import { hasPackageJson, initPackageJson, installPackages } from '../utils/packageManager.ts'
10
10
 
11
11
  type PluginOption = {
@@ -123,7 +123,7 @@ function generateConfigFile(selectedPlugins: PluginOption[], inputPath: string,
123
123
  })
124
124
  .join('\n')
125
125
 
126
- return `import { defineConfig } from '@kubb/core'
126
+ return `import { defineConfig } from 'kubb'
127
127
  ${imports}
128
128
 
129
129
  export default defineConfig({
@@ -257,7 +257,7 @@ export async function runInit({ yes, version }: InitOptions): Promise<void> {
257
257
  }
258
258
 
259
259
  // Install packages
260
- const packagesToInstall = ['@kubb/core', '@kubb/cli', '@kubb/agent', ...selectedPlugins.map((p) => p.packageName)]
260
+ const packagesToInstall = ['kubb', ...selectedPlugins.map((p) => p.packageName)]
261
261
 
262
262
  const spinner = clack.spinner()
263
263
  spinner.start(`Installing ${packagesToInstall.length} packages with ${packageManager.name}`)
@@ -272,17 +272,17 @@ export async function runInit({ yes, version }: InitOptions): Promise<void> {
272
272
 
273
273
  // Generate config file
274
274
  const configSpinner = clack.spinner()
275
- configSpinner.start('Creating kubb.config.ts')
275
+ configSpinner.start(`Creating ${KUBB_CONFIG_FILENAME}`)
276
276
 
277
277
  const configContent = generateConfigFile(selectedPlugins, inputPath, outputPath)
278
- const configPath = path.join(cwd, 'kubb.config.ts')
278
+ const configPath = path.join(cwd, KUBB_CONFIG_FILENAME)
279
279
 
280
280
  if (fs.existsSync(configPath)) {
281
- configSpinner.stop('kubb.config.ts already exists')
281
+ configSpinner.stop(`${KUBB_CONFIG_FILENAME} already exists`)
282
282
 
283
283
  if (!yes) {
284
284
  const shouldOverwrite = await clack.confirm({
285
- message: 'kubb.config.ts already exists. Overwrite?',
285
+ message: `${KUBB_CONFIG_FILENAME} already exists. Overwrite?`,
286
286
  initialValue: false,
287
287
  })
288
288
 
@@ -291,12 +291,12 @@ export async function runInit({ yes, version }: InitOptions): Promise<void> {
291
291
  }
292
292
  }
293
293
 
294
- configSpinner.start('Overwriting kubb.config.ts')
294
+ configSpinner.start(`Overwriting ${KUBB_CONFIG_FILENAME}`)
295
295
  }
296
296
 
297
297
  fs.writeFileSync(configPath, configContent, 'utf-8')
298
298
 
299
- configSpinner.stop('Created kubb.config.ts')
299
+ configSpinner.stop(`Created ${KUBB_CONFIG_FILENAME}`)
300
300
 
301
301
  clack.outro(
302
302
  styleText('green', '✓ All set!') +
@@ -12,7 +12,9 @@ type McpOptions = {
12
12
  export async function runMcp({ version }: McpOptions): Promise<void> {
13
13
  let mod: typeof McpModule
14
14
  try {
15
- mod = (await jiti.import('@kubb/mcp', { default: true })) as typeof McpModule
15
+ mod = (await jiti.import('@kubb/mcp', {
16
+ default: true,
17
+ })) as typeof McpModule
16
18
  } catch (_e) {
17
19
  console.error(`Import of '@kubb/mcp' is required to start the MCP server`)
18
20
  process.exit(1)
@@ -24,9 +26,23 @@ export async function runMcp({ version }: McpOptions): Promise<void> {
24
26
  console.log('⏳ Starting MCP server...')
25
27
  console.warn(styleText('yellow', 'This feature is still under development — use with caution'))
26
28
  run()
27
- await sendTelemetry(buildTelemetryEvent({ command: 'mcp', kubbVersion: version, hrStart, status: 'success' }))
29
+ await sendTelemetry(
30
+ buildTelemetryEvent({
31
+ command: 'mcp',
32
+ kubbVersion: version,
33
+ hrStart,
34
+ status: 'success',
35
+ }),
36
+ )
28
37
  } catch (error) {
29
- await sendTelemetry(buildTelemetryEvent({ command: 'mcp', kubbVersion: version, hrStart, status: 'failed' }))
38
+ await sendTelemetry(
39
+ buildTelemetryEvent({
40
+ command: 'mcp',
41
+ kubbVersion: version,
42
+ hrStart,
43
+ status: 'failed',
44
+ }),
45
+ )
30
46
  console.error(getErrorMessage(error))
31
47
  }
32
48
  }
@@ -1,7 +1,6 @@
1
1
  import process from 'node:process'
2
2
  import { getErrorMessage } from '@internals/utils'
3
- import type * as OasModule from '@kubb/oas'
4
- import { jiti } from '../utils/jiti.ts'
3
+ import { parseDocument, validateDocument } from '@kubb/adapter-oas'
5
4
  import { buildTelemetryEvent, sendTelemetry } from '../utils/telemetry.ts'
6
5
 
7
6
  type ValidateOptions = {
@@ -10,24 +9,29 @@ type ValidateOptions = {
10
9
  }
11
10
 
12
11
  export async function runValidate({ input, version }: ValidateOptions): Promise<void> {
13
- let mod: typeof OasModule
14
- try {
15
- mod = (await jiti.import('@kubb/oas', { default: true })) as typeof OasModule
16
- } catch (_e) {
17
- console.error(`Import of '@kubb/oas' is required to do validation`)
18
- process.exit(1)
19
- }
20
-
21
- const { parse } = mod
22
12
  const hrStart = process.hrtime()
23
13
  try {
24
- const oas = await parse(input)
25
- await oas.validate()
14
+ const document = await parseDocument(input)
15
+ await validateDocument(document, { throwOnError: true })
26
16
 
27
- await sendTelemetry(buildTelemetryEvent({ command: 'validate', kubbVersion: version, hrStart, status: 'success' }))
17
+ await sendTelemetry(
18
+ buildTelemetryEvent({
19
+ command: 'validate',
20
+ kubbVersion: version,
21
+ hrStart,
22
+ status: 'success',
23
+ }),
24
+ )
28
25
  console.log('✅ Validation success')
29
26
  } catch (error) {
30
- await sendTelemetry(buildTelemetryEvent({ command: 'validate', kubbVersion: version, hrStart, status: 'failed' }))
27
+ await sendTelemetry(
28
+ buildTelemetryEvent({
29
+ command: 'validate',
30
+ kubbVersion: version,
31
+ hrStart,
32
+ status: 'failed',
33
+ }),
34
+ )
31
35
  console.error('❌ Validation failed')
32
36
  console.error(getErrorMessage(error))
33
37
  process.exit(1)
package/src/types.ts ADDED
@@ -0,0 +1,11 @@
1
+ export type QuiteFlag = '--help' | '-h' | '--version' | '-v'
2
+
3
+ export type GenerateFlag = '--config' | '-c' | '--log-level' | '-l' | '--watch' | '-w' | '--debug' | '-d' | '--verbose' | '-v' | '--silent' | '-s'
4
+
5
+ export type ValidateFlag = '--input' | '-i'
6
+
7
+ export type InitFlag = '--yes' | '-y'
8
+
9
+ export type AgentStartFlag = '--config' | '-c' | '--port' | '-p' | '--host' | '--allow-write' | '--allow-all'
10
+
11
+ export type Arg = QuiteFlag | GenerateFlag | ValidateFlag | InitFlag | AgentStartFlag
@@ -2,15 +2,15 @@ import { createHash } from 'node:crypto'
2
2
  import { styleText } from 'node:util'
3
3
  import type { AsyncEventEmitter } from '@internals/utils'
4
4
  import { tokenize } from '@internals/utils'
5
- import type { Config, KubbEvents } from '@kubb/core'
5
+ import type { Config, KubbHookEndContext, KubbHooks } from '@kubb/core'
6
6
 
7
7
  type ExecutingHooksProps = {
8
- hooks: NonNullable<Config['hooks']>
9
- events: AsyncEventEmitter<KubbEvents>
8
+ configHooks: NonNullable<Config['hooks']>
9
+ hooks: AsyncEventEmitter<KubbHooks>
10
10
  }
11
11
 
12
- export async function executeHooks({ hooks, events }: ExecutingHooksProps): Promise<void> {
13
- const commands = Array.isArray(hooks.done) ? hooks.done : [hooks.done].filter(Boolean)
12
+ export async function executeHooks({ configHooks, hooks }: ExecutingHooksProps): Promise<void> {
13
+ const commands = Array.isArray(configHooks.done) ? configHooks.done : [configHooks.done].filter(Boolean)
14
14
 
15
15
  for (const command of commands) {
16
16
  const [cmd, ...args] = tokenize(command)
@@ -22,24 +22,24 @@ export async function executeHooks({ hooks, events }: ExecutingHooksProps): Prom
22
22
  const hookId = createHash('sha256').update(command).digest('hex')
23
23
 
24
24
  // Wire up the hook:end listener BEFORE emitting hook:start to avoid the race condition
25
- // where hook:end fires synchronously inside emit('hook:start') before the listener is registered.
25
+ // where hook:end fires synchronously inside emit('kubb:hook:start') before the listener is registered.
26
26
  const hookEndPromise = new Promise<void>((resolve, reject) => {
27
- const handler = ({ id, success, error }: { id?: string; command: string; args?: readonly string[]; success: boolean; error: Error | null }) => {
28
- if (id !== hookId) return
29
- events.off('hook:end', handler)
30
- if (!success) {
31
- reject(error ?? new Error(`Hook failed: ${command}`))
27
+ const handler = (ctx: KubbHookEndContext) => {
28
+ if (ctx.id !== hookId) return
29
+ hooks.off('kubb:hook:end', handler)
30
+ if (!ctx.success) {
31
+ reject(ctx.error ?? new Error(`Hook failed: ${command}`))
32
32
  return
33
33
  }
34
- events
35
- .emit('success', `${styleText('dim', command)} successfully executed`)
34
+ hooks
35
+ .emit('kubb:success', { message: `${styleText('dim', command)} successfully executed` })
36
36
  .then(resolve)
37
37
  .catch(reject)
38
38
  }
39
- events.on('hook:end', handler)
39
+ hooks.on('kubb:hook:end', handler)
40
40
  })
41
41
 
42
- await events.emit('hook:start', { id: hookId, command: cmd, args })
42
+ await hooks.emit('kubb:hook:start', { id: hookId, command: cmd, args })
43
43
  await hookEndPromise
44
44
  }
45
45
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Type guard that checks whether a raw string is a member of a typed flag set.
3
+ * Avoids the need for type assertions when working with `Set<T extends string>`.
4
+ */
5
+ export function isFlag<T extends string>(set: ReadonlySet<T>, value: string): value is T {
6
+ for (const flag of set) {
7
+ if (flag === value) return true
8
+ }
9
+ return false
10
+ }
@@ -0,0 +1,10 @@
1
+ import type { CLIOptions, Config, PossibleConfig } from '@kubb/core'
2
+
3
+ type ConfigInput = PossibleConfig<CLIOptions>
4
+
5
+ export async function getConfigs(config: ConfigInput, args: CLIOptions): Promise<Array<Config>> {
6
+ const resolved = await (typeof config === 'function' ? config(args as CLIOptions) : config)
7
+ const userConfigs = Array.isArray(resolved) ? resolved : [resolved]
8
+
9
+ return userConfigs.map((item) => ({ ...item, plugins: item.plugins ?? [] }) as Config)
10
+ }