@kubb/cli 4.11.3 → 4.12.1

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 (52) hide show
  1. package/dist/generate-BfIvo80v.js +1249 -0
  2. package/dist/generate-BfIvo80v.js.map +1 -0
  3. package/dist/generate-BoZaeXSl.cjs +1256 -0
  4. package/dist/generate-BoZaeXSl.cjs.map +1 -0
  5. package/dist/index.cjs +9 -35
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.js +7 -30
  8. package/dist/index.js.map +1 -1
  9. package/dist/{mcp-BIRDY8xn.js → mcp-LLlOFV3c.js} +5 -6
  10. package/dist/mcp-LLlOFV3c.js.map +1 -0
  11. package/dist/{mcp-BQjDRDXR.cjs → mcp-N1IVyiXf.cjs} +5 -7
  12. package/dist/mcp-N1IVyiXf.cjs.map +1 -0
  13. package/dist/package-B1rQiz1w.js +6 -0
  14. package/dist/package-B1rQiz1w.js.map +1 -0
  15. package/dist/package-CngLazMY.cjs +12 -0
  16. package/dist/package-CngLazMY.cjs.map +1 -0
  17. package/dist/{validate-0i6Q9eIy.js → validate-BptoQ-63.js} +5 -6
  18. package/dist/validate-BptoQ-63.js.map +1 -0
  19. package/dist/{validate-6F-VPZR7.cjs → validate-Dn6hZ7Z4.cjs} +5 -7
  20. package/dist/validate-Dn6hZ7Z4.cjs.map +1 -0
  21. package/package.json +7 -7
  22. package/src/commands/generate.ts +52 -65
  23. package/src/commands/mcp.ts +4 -5
  24. package/src/commands/validate.ts +4 -5
  25. package/src/index.ts +8 -23
  26. package/src/loggers/clackLogger.ts +433 -0
  27. package/src/loggers/envDetection.ts +28 -0
  28. package/src/loggers/fileSystemLogger.ts +79 -0
  29. package/src/loggers/githubActionsLogger.ts +310 -0
  30. package/src/loggers/index.ts +5 -0
  31. package/src/loggers/plainLogger.ts +274 -0
  32. package/src/loggers/types.ts +1 -0
  33. package/src/loggers/utils.ts +43 -0
  34. package/src/runners/generate.ts +196 -208
  35. package/src/utils/Writables.ts +12 -8
  36. package/src/utils/executeHooks.ts +11 -18
  37. package/src/utils/getCosmiConfig.ts +6 -1
  38. package/src/utils/getSummary.ts +20 -42
  39. package/src/utils/randomColour.ts +26 -0
  40. package/src/utils/watcher.ts +2 -4
  41. package/dist/generate-CYBFB3tU.js +0 -221
  42. package/dist/generate-CYBFB3tU.js.map +0 -1
  43. package/dist/generate-CpBJ2Y-n.js +0 -342
  44. package/dist/generate-CpBJ2Y-n.js.map +0 -1
  45. package/dist/generate-DpHvARzf.cjs +0 -345
  46. package/dist/generate-DpHvARzf.cjs.map +0 -1
  47. package/dist/generate-KUqCSnZp.cjs +0 -225
  48. package/dist/generate-KUqCSnZp.cjs.map +0 -1
  49. package/dist/mcp-BIRDY8xn.js.map +0 -1
  50. package/dist/mcp-BQjDRDXR.cjs.map +0 -1
  51. package/dist/validate-0i6Q9eIy.js.map +0 -1
  52. package/dist/validate-6F-VPZR7.cjs.map +0 -1
@@ -0,0 +1,433 @@
1
+ import { relative } from 'node:path'
2
+ import * as clack from '@clack/prompts'
3
+ import { defineLogger, LogLevel } from '@kubb/core'
4
+ import { execa } from 'execa'
5
+ import { default as gradientString } from 'gradient-string'
6
+ import pc from 'picocolors'
7
+
8
+ import { getSummary } from '../utils/getSummary.ts'
9
+ import { ClackWritable } from '../utils/Writables.ts'
10
+
11
+ /**
12
+ * Clack adapter for local TTY environments
13
+ * Provides a beautiful CLI UI with flat structure inspired by Claude's CLI patterns
14
+ */
15
+ export const clackLogger = defineLogger({
16
+ name: 'clack',
17
+ install(context, options) {
18
+ const logLevel = options?.logLevel || LogLevel.info
19
+ const activeProgress = new Map<string, { interval?: NodeJS.Timeout; progressBar: clack.ProgressResult }>()
20
+ const spinner = clack.spinner()
21
+ let isSpinning = false
22
+
23
+ function getMessage(message: string): string {
24
+ if (logLevel >= LogLevel.verbose) {
25
+ const timestamp = new Date().toLocaleTimeString('en-US', {
26
+ hour12: false,
27
+ hour: '2-digit',
28
+ minute: '2-digit',
29
+ second: '2-digit',
30
+ })
31
+
32
+ return [pc.dim(`[${timestamp}]`), message].join(' ')
33
+ }
34
+
35
+ return message
36
+ }
37
+
38
+ function startSpinner(text?: string) {
39
+ spinner.start(text)
40
+ isSpinning = true
41
+ }
42
+
43
+ function stopSpinner(text?: string) {
44
+ spinner.stop(text)
45
+ isSpinning = false
46
+ }
47
+
48
+ context.on('info', (message, info = '') => {
49
+ if (logLevel <= LogLevel.silent) {
50
+ return
51
+ }
52
+
53
+ const text = getMessage([pc.blue('ℹ'), message, pc.dim(info)].join(' '))
54
+
55
+ if (isSpinning) {
56
+ spinner.message(text)
57
+ } else {
58
+ clack.log.info(text)
59
+ }
60
+ })
61
+
62
+ context.on('success', (message, info = '') => {
63
+ if (logLevel <= LogLevel.silent) {
64
+ return
65
+ }
66
+
67
+ const text = getMessage([pc.blue('✓'), message, logLevel >= LogLevel.info ? pc.dim(info) : undefined].filter(Boolean).join(' '))
68
+
69
+ if (isSpinning) {
70
+ stopSpinner(text)
71
+ } else {
72
+ clack.log.success(text)
73
+ }
74
+ })
75
+
76
+ context.on('warn', (message, info) => {
77
+ if (logLevel < LogLevel.warn) {
78
+ return
79
+ }
80
+
81
+ const text = getMessage([pc.yellow('⚠'), message, logLevel >= LogLevel.info ? pc.dim(info) : undefined].filter(Boolean).join(' '))
82
+
83
+ clack.log.warn(text)
84
+ })
85
+
86
+ context.on('error', (error) => {
87
+ const caused = error.cause as Error
88
+
89
+ const text = [pc.red('✗'), error.message].join(' ')
90
+
91
+ if (isSpinning) {
92
+ stopSpinner(getMessage(text))
93
+ } else {
94
+ clack.log.error(getMessage(text))
95
+ }
96
+
97
+ // Show stack trace in debug mode (first 3 frames)
98
+ if (logLevel >= LogLevel.debug && error.stack) {
99
+ const frames = error.stack.split('\n').slice(1, 4)
100
+ for (const frame of frames) {
101
+ clack.log.message(getMessage(pc.dim(frame.trim())))
102
+ }
103
+
104
+ if (caused?.stack) {
105
+ clack.log.message(pc.dim(`└─ caused by ${caused.message}`))
106
+
107
+ const frames = caused.stack.split('\n').slice(1, 4)
108
+ for (const frame of frames) {
109
+ clack.log.message(getMessage(` ${pc.dim(frame.trim())}`))
110
+ }
111
+ }
112
+ }
113
+ })
114
+
115
+ context.on('version:new', (version, latestVersion) => {
116
+ if (logLevel <= LogLevel.silent) {
117
+ return
118
+ }
119
+
120
+ clack.box(
121
+ `\`v${version}\` → \`v${latestVersion}\`
122
+ Run \`npm install -g @kubb/cli\` to update`,
123
+ 'Update available for `Kubb`',
124
+ {
125
+ width: 'auto',
126
+ formatBorder: pc.yellow,
127
+ rounded: true,
128
+ withGuide: false,
129
+ contentAlign: 'center',
130
+ titleAlign: 'center',
131
+ },
132
+ )
133
+ })
134
+
135
+ context.on('lifecycle:start', (version) => {
136
+ console.log(gradientString(['#F58517', '#F5A217', '#F55A17'])(`Kubb ${version} 🧩`))
137
+ })
138
+
139
+ context.on('config:start', () => {
140
+ if (logLevel <= LogLevel.silent) {
141
+ return
142
+ }
143
+
144
+ const text = getMessage('Configuration started')
145
+
146
+ clack.intro(text)
147
+ startSpinner(getMessage('Configuration loading'))
148
+ })
149
+
150
+ context.on('config:end', () => {
151
+ if (logLevel <= LogLevel.silent) {
152
+ return
153
+ }
154
+
155
+ const text = getMessage('Configuration completed')
156
+
157
+ clack.outro(text)
158
+ })
159
+
160
+ context.on('generation:start', (config) => {
161
+ const text = getMessage(['Generation started', config.name ? `for ${pc.dim(config.name)}` : undefined].filter(Boolean).join(' '))
162
+
163
+ clack.intro(text)
164
+ })
165
+
166
+ context.on('plugin:start', (plugin) => {
167
+ if (logLevel <= LogLevel.silent) {
168
+ return
169
+ }
170
+
171
+ stopSpinner()
172
+
173
+ const progressBar = clack.progress({
174
+ style: 'block',
175
+ max: 100,
176
+ size: 30,
177
+ })
178
+ const text = getMessage(`Generating ${pc.bold(plugin.name)}`)
179
+ progressBar.start(text)
180
+
181
+ const interval = setInterval(() => {
182
+ progressBar.advance()
183
+ }, 50)
184
+
185
+ activeProgress.set(plugin.name, { progressBar, interval })
186
+ })
187
+
188
+ context.on('plugin:end', (plugin, duration) => {
189
+ stopSpinner()
190
+
191
+ const active = activeProgress.get(plugin.name)
192
+
193
+ if (!active || logLevel === LogLevel.silent) {
194
+ return
195
+ }
196
+
197
+ clearInterval(active.interval)
198
+
199
+ const durationStr = duration >= 1000 ? `${(duration / 1000).toFixed(2)}s` : `${duration}ms`
200
+ const text = getMessage(`${pc.bold(plugin.name)} completed in ${pc.green(durationStr)}`)
201
+
202
+ active.progressBar.stop(text)
203
+ activeProgress.delete(plugin.name)
204
+ })
205
+
206
+ context.on('files:processing:start', (files) => {
207
+ if (logLevel <= LogLevel.silent) {
208
+ return
209
+ }
210
+
211
+ stopSpinner()
212
+
213
+ const text = getMessage(`Writing ${files.length} files`)
214
+ const progressBar = clack.progress({
215
+ style: 'block',
216
+ max: files.length,
217
+ size: 30,
218
+ })
219
+
220
+ context.emit('info', text)
221
+ progressBar.start(text)
222
+ activeProgress.set('files', { progressBar })
223
+ })
224
+
225
+ context.on('file:processing:update', ({ file, config }) => {
226
+ if (logLevel <= LogLevel.silent) {
227
+ return
228
+ }
229
+
230
+ stopSpinner()
231
+
232
+ const text = `Writing ${relative(config.root, file.path)}`
233
+ const active = activeProgress.get('files')
234
+
235
+ if (!active) {
236
+ return
237
+ }
238
+
239
+ active.progressBar.advance(undefined, text)
240
+ })
241
+ context.on('files:processing:end', () => {
242
+ if (logLevel <= LogLevel.silent) {
243
+ return
244
+ }
245
+
246
+ stopSpinner()
247
+
248
+ const text = getMessage('Files written successfully')
249
+ const active = activeProgress.get('files')
250
+
251
+ if (!active) {
252
+ return
253
+ }
254
+
255
+ active.progressBar.stop(text)
256
+ activeProgress.delete('files')
257
+ })
258
+
259
+ context.on('generation:end', (config) => {
260
+ const text = getMessage(config.name ? `Generation completed for ${pc.dim(config.name)}` : 'Generation completed')
261
+
262
+ clack.outro(text)
263
+ })
264
+
265
+ context.on('hook:execute', async ({ command, args }, cb) => {
266
+ if (logLevel <= LogLevel.silent) {
267
+ try {
268
+ const result = await execa(command, args, {
269
+ detached: true,
270
+ stripFinalNewline: true,
271
+ })
272
+
273
+ await context.emit('debug', {
274
+ date: new Date(),
275
+ logs: [result.stdout],
276
+ })
277
+
278
+ cb()
279
+ } catch (err) {
280
+ const error = new Error('Hook execute failed')
281
+ error.cause = err
282
+
283
+ await context.emit('debug', {
284
+ date: new Date(),
285
+ logs: [(err as any).stdout],
286
+ })
287
+
288
+ await context.emit('error', error)
289
+ }
290
+
291
+ return
292
+ }
293
+
294
+ const logger = clack.taskLog({
295
+ title: getMessage(['Executing hook', logLevel >= LogLevel.info ? pc.dim(`${command} ${args?.join(' ')}`) : undefined].filter(Boolean).join(' ')),
296
+ })
297
+
298
+ const writable = new ClackWritable(logger)
299
+
300
+ try {
301
+ const result = await execa(command, args, {
302
+ detached: true,
303
+ stdout: ['pipe', writable],
304
+ stripFinalNewline: true,
305
+ })
306
+
307
+ await context.emit('debug', {
308
+ date: new Date(),
309
+ logs: [result.stdout],
310
+ })
311
+
312
+ cb()
313
+ } catch (err) {
314
+ const error = new Error('Hook execute failed')
315
+ error.cause = err
316
+
317
+ await context.emit('debug', {
318
+ date: new Date(),
319
+ logs: [(err as any).stdout],
320
+ })
321
+
322
+ await context.emit('error', error)
323
+ }
324
+ })
325
+
326
+ context.on('format:start', () => {
327
+ if (logLevel <= LogLevel.silent) {
328
+ return
329
+ }
330
+
331
+ const text = getMessage('Format started')
332
+
333
+ clack.intro(text)
334
+ })
335
+
336
+ context.on('format:end', () => {
337
+ if (logLevel <= LogLevel.silent) {
338
+ return
339
+ }
340
+
341
+ const text = getMessage('Format completed')
342
+
343
+ clack.outro(text)
344
+ })
345
+
346
+ context.on('lint:start', () => {
347
+ if (logLevel <= LogLevel.silent) {
348
+ return
349
+ }
350
+
351
+ const text = getMessage('Lint started')
352
+
353
+ clack.intro(text)
354
+ })
355
+
356
+ context.on('lint:end', () => {
357
+ if (logLevel <= LogLevel.silent) {
358
+ return
359
+ }
360
+
361
+ const text = getMessage('Lint completed')
362
+
363
+ clack.outro(text)
364
+ })
365
+
366
+ context.on('hook:start', (command) => {
367
+ if (logLevel <= LogLevel.silent) {
368
+ return
369
+ }
370
+
371
+ const text = getMessage(`Hook ${pc.dim(command)} started`)
372
+
373
+ clack.intro(text)
374
+ })
375
+
376
+ context.on('hook:end', (command) => {
377
+ if (logLevel <= LogLevel.silent) {
378
+ return
379
+ }
380
+
381
+ const text = getMessage(`Hook ${pc.dim(command)} completed`)
382
+
383
+ clack.outro(text)
384
+ })
385
+
386
+ context.on('generation:summary', (config, { pluginTimings, failedPlugins, filesCreated, status, hrStart }) => {
387
+ const summary = getSummary({
388
+ failedPlugins,
389
+ filesCreated,
390
+ config,
391
+ status,
392
+ hrStart,
393
+ pluginTimings: logLevel >= LogLevel.verbose ? pluginTimings : undefined,
394
+ })
395
+ const title = config.name || ''
396
+
397
+ summary.unshift('\n')
398
+ summary.push('\n')
399
+
400
+ if (status === 'success') {
401
+ clack.box(summary.join('\n'), getMessage(title), {
402
+ width: 'auto',
403
+ formatBorder: pc.green,
404
+ rounded: true,
405
+ withGuide: false,
406
+ contentAlign: 'left',
407
+ titleAlign: 'center',
408
+ })
409
+
410
+ return
411
+ }
412
+
413
+ clack.box(summary.join('\n'), getMessage(title), {
414
+ width: 'auto',
415
+ formatBorder: pc.red,
416
+ rounded: true,
417
+ withGuide: false,
418
+ contentAlign: 'left',
419
+ titleAlign: 'center',
420
+ })
421
+ })
422
+
423
+ context.on('lifecycle:end', () => {
424
+ for (const [_key, active] of activeProgress) {
425
+ if (active.interval) {
426
+ clearInterval(active.interval)
427
+ }
428
+ active.progressBar?.stop()
429
+ }
430
+ activeProgress.clear()
431
+ })
432
+ },
433
+ })
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Check if running in GitHub Actions environment
3
+ */
4
+ export function isGitHubActions(): boolean {
5
+ return !!process.env.GITHUB_ACTIONS
6
+ }
7
+
8
+ /**
9
+ * Check if running in any CI environment
10
+ */
11
+ export function isCIEnvironment(): boolean {
12
+ return !!(
13
+ process.env.CI ||
14
+ process.env.GITHUB_ACTIONS ||
15
+ process.env.GITLAB_CI ||
16
+ process.env.CIRCLECI ||
17
+ process.env.TRAVIS ||
18
+ process.env.JENKINS_URL ||
19
+ process.env.BUILDKITE
20
+ )
21
+ }
22
+
23
+ /**
24
+ * Check if TTY is available for interactive output
25
+ */
26
+ export function canUseTTY(): boolean {
27
+ return !!process.stdout.isTTY && !isCIEnvironment()
28
+ }
@@ -0,0 +1,79 @@
1
+ import { resolve } from 'node:path'
2
+ import { defineLogger } from '@kubb/core'
3
+ import { write } from '@kubb/core/fs'
4
+
5
+ type CachedEvent = {
6
+ date: Date
7
+ logs: string[]
8
+ fileName?: string
9
+ }
10
+
11
+ /**
12
+ * FileSystem logger for debug log persistence
13
+ * Captures debug and verbose events and writes them to files in .kubb directory
14
+ *
15
+ * Note: Logs are written on lifecycle:end or process exit. If the process crashes
16
+ * before these events, some cached logs may be lost.
17
+ */
18
+ export const fileSystemLogger = defineLogger({
19
+ name: 'filesystem',
20
+ install(context) {
21
+ const cachedLogs: Set<CachedEvent> = new Set()
22
+ const startDate = Date.now()
23
+
24
+ async function writeLogs() {
25
+ if (cachedLogs.size === 0) {
26
+ return
27
+ }
28
+
29
+ const files: Record<string, string[]> = {}
30
+
31
+ for (const log of cachedLogs) {
32
+ const fileName = resolve(process.cwd(), '.kubb', log.fileName || `kubb-${startDate}.log`)
33
+
34
+ if (!files[fileName]) {
35
+ files[fileName] = []
36
+ }
37
+
38
+ if (log.logs.length > 0) {
39
+ const timestamp = log.date.toLocaleString()
40
+ files[fileName].push(`[${timestamp}]\n${log.logs.join('\n')}`)
41
+ }
42
+ }
43
+
44
+ await Promise.all(
45
+ Object.entries(files).map(async ([fileName, logs]) => {
46
+ return write(fileName, logs.join('\n\n'))
47
+ }),
48
+ )
49
+
50
+ cachedLogs.clear()
51
+ }
52
+
53
+ context.on('debug', (message) => {
54
+ cachedLogs.add({
55
+ date: new Date(),
56
+ logs: message.logs,
57
+ fileName: undefined,
58
+ })
59
+ })
60
+
61
+ context.on('lifecycle:end', async () => {
62
+ await writeLogs()
63
+ })
64
+
65
+ // Fallback: Write logs on process exit to handle crashes
66
+ const exitHandler = () => {
67
+ // Synchronous write on exit - best effort
68
+ if (cachedLogs.size > 0) {
69
+ writeLogs().catch(() => {
70
+ // Ignore errors on exit
71
+ })
72
+ }
73
+ }
74
+
75
+ process.once('exit', exitHandler)
76
+ process.once('SIGINT', exitHandler)
77
+ process.once('SIGTERM', exitHandler)
78
+ },
79
+ })