@kubb/cli 5.0.0-beta.2 → 5.0.0-beta.20

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 (151) hide show
  1. package/README.md +177 -26
  2. package/dist/agent-CjtFcQSP.js +68 -0
  3. package/dist/agent-CjtFcQSP.js.map +1 -0
  4. package/dist/agent-ImlBMmqx.cjs +70 -0
  5. package/dist/agent-ImlBMmqx.cjs.map +1 -0
  6. package/dist/{chunk--u3MIqq1.js → chunk-BvFE5Tac.js} +1 -0
  7. package/dist/constants-B2JTeRBb.js +42 -0
  8. package/dist/constants-B2JTeRBb.js.map +1 -0
  9. package/dist/constants-BINTA5VZ.cjs +77 -0
  10. package/dist/constants-BINTA5VZ.cjs.map +1 -0
  11. package/dist/constants-BYGmiFs0.cjs +139 -0
  12. package/dist/constants-BYGmiFs0.cjs.map +1 -0
  13. package/dist/constants-DSJ-Xrbv.js +116 -0
  14. package/dist/constants-DSJ-Xrbv.js.map +1 -0
  15. package/dist/define-Bdn8j5VM.cjs.map +1 -1
  16. package/dist/{define-Ctii4bel.js → define-m_fp-Aqm.js} +2 -2
  17. package/dist/{define-Ctii4bel.js.map → define-m_fp-Aqm.js.map} +1 -1
  18. package/dist/{errors-CjPmyZHy.js → errors-CINO1EIv.js} +2 -2
  19. package/dist/{errors-CjPmyZHy.js.map → errors-CINO1EIv.js.map} +1 -1
  20. package/dist/errors-CLCjoSg0.cjs.map +1 -1
  21. package/dist/{generate-DL_7a7Wd.cjs → generate-BzD-zvO8.cjs} +10 -4
  22. package/dist/generate-BzD-zvO8.cjs.map +1 -0
  23. package/dist/{generate-B3PZ6Dp-.js → generate-DMjDB40_.js} +12 -6
  24. package/dist/generate-DMjDB40_.js.map +1 -0
  25. package/dist/index.cjs +20 -11
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +22 -13
  29. package/dist/index.js.map +1 -1
  30. package/dist/init-DSUkgcQb.js +53 -0
  31. package/dist/init-DSUkgcQb.js.map +1 -0
  32. package/dist/init-DTtYizXX.cjs +53 -0
  33. package/dist/init-DTtYizXX.cjs.map +1 -0
  34. package/dist/mcp-WXRUaZnw.cjs +39 -0
  35. package/dist/mcp-WXRUaZnw.cjs.map +1 -0
  36. package/dist/mcp-mW5bt29k.js +39 -0
  37. package/dist/mcp-mW5bt29k.js.map +1 -0
  38. package/dist/package-B1xAdhcK.js +6 -0
  39. package/dist/package-B1xAdhcK.js.map +1 -0
  40. package/dist/{package-D8wlStAg.cjs → package-CxbQOn_j.cjs} +2 -2
  41. package/dist/package-CxbQOn_j.cjs.map +1 -0
  42. package/dist/run-B11-UaUs.cjs +33 -0
  43. package/dist/run-B11-UaUs.cjs.map +1 -0
  44. package/dist/{init-eNRlotJK.js → run-BNqMQygv.js} +107 -149
  45. package/dist/run-BNqMQygv.js.map +1 -0
  46. package/dist/{generate-B3jl4ukb.cjs → run-BP_t4Z6z.cjs} +472 -397
  47. package/dist/run-BP_t4Z6z.cjs.map +1 -0
  48. package/dist/{init-CZ5Xq2Hd.cjs → run-BnGfi7Cp.cjs} +105 -147
  49. package/dist/run-BnGfi7Cp.cjs.map +1 -0
  50. package/dist/{agent-Ev5hU5hH.js → run-BzpYYOQs.js} +53 -44
  51. package/dist/run-BzpYYOQs.js.map +1 -0
  52. package/dist/run-CCZ24VKk.js +51 -0
  53. package/dist/run-CCZ24VKk.js.map +1 -0
  54. package/dist/{generate-Dt_r0ELY.js → run-CM9IkB_6.js} +475 -400
  55. package/dist/run-CM9IkB_6.js.map +1 -0
  56. package/dist/run-CQbj3ley.cjs +52 -0
  57. package/dist/run-CQbj3ley.cjs.map +1 -0
  58. package/dist/{agent-B_pirbeB.cjs → run-DwdAwnLG.cjs} +51 -42
  59. package/dist/run-DwdAwnLG.cjs.map +1 -0
  60. package/dist/run-PSA9X7ci.js +32 -0
  61. package/dist/run-PSA9X7ci.js.map +1 -0
  62. package/dist/shell-475fQKaX.cjs.map +1 -1
  63. package/dist/{shell-DLzN4fRo.js → shell-CN6DNqeC.js} +2 -2
  64. package/dist/{shell-DLzN4fRo.js.map → shell-CN6DNqeC.js.map} +1 -1
  65. package/dist/{telemetry-DN95_2pF.cjs → telemetry-B2iWkY5e.cjs} +5 -7
  66. package/dist/telemetry-B2iWkY5e.cjs.map +1 -0
  67. package/dist/{telemetry-LgT_sdPe.js → telemetry-BkektVz6.js} +6 -8
  68. package/dist/telemetry-BkektVz6.js.map +1 -0
  69. package/dist/validate-BQxNrR5z.js +26 -0
  70. package/dist/validate-BQxNrR5z.js.map +1 -0
  71. package/dist/validate-CvFVXmSa.cjs +26 -0
  72. package/dist/validate-CvFVXmSa.cjs.map +1 -0
  73. package/package.json +23 -15
  74. package/src/commands/agent/start.ts +10 -7
  75. package/src/commands/agent.ts +3 -1
  76. package/src/commands/generate.ts +5 -3
  77. package/src/commands/init.ts +34 -3
  78. package/src/commands/mcp.ts +28 -4
  79. package/src/commands/validate.ts +6 -4
  80. package/src/constants.ts +2 -58
  81. package/src/index.ts +5 -3
  82. package/src/loggers/clackLogger.ts +66 -72
  83. package/src/loggers/fileSystemLogger.ts +26 -13
  84. package/src/loggers/githubActionsLogger.ts +72 -26
  85. package/src/loggers/plainLogger.ts +51 -26
  86. package/src/loggers/types.ts +6 -0
  87. package/src/loggers/utils.ts +158 -9
  88. package/src/runners/agent/run.ts +113 -0
  89. package/src/runners/agent/utils.ts +98 -0
  90. package/src/runners/generate/run.ts +316 -0
  91. package/src/runners/generate/utils.ts +216 -0
  92. package/src/runners/init/run.ts +212 -0
  93. package/src/{utils/packageManager.ts → runners/init/utils.ts} +10 -0
  94. package/src/runners/mcp/run.ts +37 -0
  95. package/src/runners/validate/run.ts +63 -0
  96. package/src/{utils/telemetry.ts → telemetry.ts} +12 -5
  97. package/dist/agent-0Nk--lcr.cjs +0 -58
  98. package/dist/agent-0Nk--lcr.cjs.map +0 -1
  99. package/dist/agent-B_pirbeB.cjs.map +0 -1
  100. package/dist/agent-DKeVuiUC.js +0 -56
  101. package/dist/agent-DKeVuiUC.js.map +0 -1
  102. package/dist/agent-Ev5hU5hH.js.map +0 -1
  103. package/dist/constants-CnDXa1R6.cjs +0 -148
  104. package/dist/constants-CnDXa1R6.cjs.map +0 -1
  105. package/dist/constants-aL3CP_Wq.js +0 -95
  106. package/dist/constants-aL3CP_Wq.js.map +0 -1
  107. package/dist/generate-B3PZ6Dp-.js.map +0 -1
  108. package/dist/generate-B3jl4ukb.cjs.map +0 -1
  109. package/dist/generate-DL_7a7Wd.cjs.map +0 -1
  110. package/dist/generate-Dt_r0ELY.js.map +0 -1
  111. package/dist/init-Bj94Nvt8.js +0 -25
  112. package/dist/init-Bj94Nvt8.js.map +0 -1
  113. package/dist/init-CZ5Xq2Hd.cjs.map +0 -1
  114. package/dist/init-CyN1oyTF.cjs +0 -25
  115. package/dist/init-CyN1oyTF.cjs.map +0 -1
  116. package/dist/init-eNRlotJK.js.map +0 -1
  117. package/dist/mcp-BzW703d7.js +0 -16
  118. package/dist/mcp-BzW703d7.js.map +0 -1
  119. package/dist/mcp-CLcDV4Jm.cjs +0 -41
  120. package/dist/mcp-CLcDV4Jm.cjs.map +0 -1
  121. package/dist/mcp-D7EIR9fR.js +0 -40
  122. package/dist/mcp-D7EIR9fR.js.map +0 -1
  123. package/dist/mcp-ZY-ONTOp.cjs +0 -16
  124. package/dist/mcp-ZY-ONTOp.cjs.map +0 -1
  125. package/dist/package-D8wlStAg.cjs.map +0 -1
  126. package/dist/package-Yo-9_m5C.js +0 -6
  127. package/dist/package-Yo-9_m5C.js.map +0 -1
  128. package/dist/telemetry-DN95_2pF.cjs.map +0 -1
  129. package/dist/telemetry-LgT_sdPe.js.map +0 -1
  130. package/dist/validate-Dplr99xO.js +0 -25
  131. package/dist/validate-Dplr99xO.js.map +0 -1
  132. package/dist/validate-_8mBa63G.cjs +0 -25
  133. package/dist/validate-_8mBa63G.cjs.map +0 -1
  134. package/dist/validate-kLJoT_hi.js +0 -33
  135. package/dist/validate-kLJoT_hi.js.map +0 -1
  136. package/dist/validate-yKKzqEZ5.cjs +0 -34
  137. package/dist/validate-yKKzqEZ5.cjs.map +0 -1
  138. package/src/runners/agent.ts +0 -149
  139. package/src/runners/generate.ts +0 -333
  140. package/src/runners/init.ts +0 -296
  141. package/src/runners/mcp.ts +0 -45
  142. package/src/runners/validate.ts +0 -39
  143. package/src/types.ts +0 -11
  144. package/src/utils/Writables.ts +0 -17
  145. package/src/utils/executeHooks.ts +0 -45
  146. package/src/utils/flags.ts +0 -9
  147. package/src/utils/getConfig.ts +0 -10
  148. package/src/utils/getCosmiConfig.ts +0 -80
  149. package/src/utils/getSummary.ts +0 -68
  150. package/src/utils/runHook.ts +0 -91
  151. package/src/utils/watcher.ts +0 -19
@@ -2,11 +2,9 @@ import { relative } from 'node:path'
2
2
  import process from 'node:process'
3
3
  import { styleText } from 'node:util'
4
4
  import * as clack from '@clack/prompts'
5
- import { formatMs, formatMsWithColor, getIntro, toCause } from '@internals/utils'
5
+ import { formatMs, formatMsWithColor, getElapsedMs, getIntro, toCause } from '@internals/utils'
6
6
  import { defineLogger, logLevel as logLevelMap } from '@kubb/core'
7
- import { getSummary } from '../utils/getSummary.ts'
8
- import { runHook } from '../utils/runHook.ts'
9
- import { ClackWritable } from '../utils/Writables.ts'
7
+ import { getSummary } from './utils.ts'
10
8
  import { buildProgressLine, formatCommandWithArgs, formatMessage } from './utils.ts'
11
9
 
12
10
  /**
@@ -26,6 +24,7 @@ export const clackLogger = defineLogger({
26
24
  spinner: clack.spinner(),
27
25
  isSpinning: false,
28
26
  activeProgress: new Map<string, { interval?: NodeJS.Timeout; progressBar: clack.ProgressResult }>(),
27
+ activeHookLogs: new Map<string, { taskLog: ReturnType<typeof clack.taskLog>; hrStart: [number, number] }>(),
29
28
  }
30
29
 
31
30
  function reset() {
@@ -45,6 +44,7 @@ export const clackLogger = defineLogger({
45
44
  state.spinner = clack.spinner()
46
45
  state.isSpinning = false
47
46
  state.activeProgress.clear()
47
+ state.activeHookLogs.clear()
48
48
  }
49
49
 
50
50
  function showProgressStep() {
@@ -68,6 +68,9 @@ export const clackLogger = defineLogger({
68
68
  }
69
69
 
70
70
  function stopSpinner(text?: string) {
71
+ if (!state.isSpinning) {
72
+ return
73
+ }
71
74
  state.spinner.stop(text)
72
75
  state.isSpinning = false
73
76
  }
@@ -81,9 +84,9 @@ export const clackLogger = defineLogger({
81
84
 
82
85
  if (state.isSpinning) {
83
86
  state.spinner.message(text)
84
- } else {
85
- clack.log.info(text)
87
+ return
86
88
  }
89
+ clack.log.info(text)
87
90
  })
88
91
 
89
92
  context.on('kubb:success', ({ message, info = '' }) => {
@@ -95,9 +98,9 @@ export const clackLogger = defineLogger({
95
98
 
96
99
  if (state.isSpinning) {
97
100
  stopSpinner(text)
98
- } else {
99
- clack.log.success(text)
101
+ return
100
102
  }
103
+ clack.log.success(text)
101
104
  })
102
105
 
103
106
  context.on('kubb:warn', ({ message, info }) => {
@@ -119,9 +122,9 @@ export const clackLogger = defineLogger({
119
122
 
120
123
  if (state.isSpinning) {
121
124
  stopSpinner(getMessage(text))
122
- } else {
123
- clack.log.error(getMessage(text))
125
+ return
124
126
  }
127
+ clack.log.error(getMessage(text))
125
128
 
126
129
  // Show stack trace in debug mode (first 3 frames)
127
130
  if (logLevel >= logLevelMap.debug && error.stack) {
@@ -199,6 +202,10 @@ Run \`npm install -g @kubb/cli\` to update`,
199
202
  // Initialize progress tracking for this generation
200
203
  state.totalPlugins = config.plugins?.length ?? 0
201
204
 
205
+ if (logLevel <= logLevelMap.silent) {
206
+ return
207
+ }
208
+
202
209
  const text = getMessage(['Generation started', config.name ? `for ${styleText('dim', config.name)}` : undefined].filter(Boolean).join(' '))
203
210
 
204
211
  clack.intro(text)
@@ -319,6 +326,8 @@ Run \`npm install -g @kubb/cli\` to update`,
319
326
  })
320
327
 
321
328
  context.on('kubb:generation:end', ({ config }) => {
329
+ stopSpinner()
330
+
322
331
  const text = getMessage(config.name ? `Generation completed for ${styleText('dim', config.name)}` : 'Generation completed')
323
332
 
324
333
  clack.outro(text)
@@ -329,97 +338,59 @@ Run \`npm install -g @kubb/cli\` to update`,
329
338
  return
330
339
  }
331
340
 
332
- const text = getMessage('Format started')
333
-
334
- clack.intro(text)
341
+ clack.log.step(getMessage('Formatting'))
335
342
  })
336
343
 
337
- context.on('kubb:format:end', () => {
344
+ context.on('kubb:lint:start', () => {
338
345
  if (logLevel <= logLevelMap.silent) {
339
346
  return
340
347
  }
341
348
 
342
- const text = getMessage('Format completed')
343
-
344
- clack.outro(text)
349
+ clack.log.step(getMessage('Linting'))
345
350
  })
346
351
 
347
- context.on('kubb:lint:start', () => {
352
+ context.on('kubb:hooks:start', () => {
348
353
  if (logLevel <= logLevelMap.silent) {
349
354
  return
350
355
  }
351
356
 
352
- const text = getMessage('Lint started')
353
-
354
- clack.intro(text)
357
+ clack.log.step(getMessage('Running hooks'))
355
358
  })
356
359
 
357
- context.on('kubb:lint:end', () => {
358
- if (logLevel <= logLevelMap.silent) {
360
+ context.on('kubb:hook:start', ({ id, command, args }) => {
361
+ if (logLevel <= logLevelMap.silent || !id) {
359
362
  return
360
363
  }
361
364
 
362
- const text = getMessage('Lint completed')
363
-
364
- clack.outro(text)
365
- })
365
+ stopSpinner()
366
366
 
367
- context.on('kubb:hook:start', async ({ id, command, args }) => {
368
367
  const commandWithArgs = formatCommandWithArgs(command, args)
369
- const text = getMessage(`Hook ${styleText('dim', commandWithArgs)} started`)
368
+ const title = getMessage(`Running ${styleText('dim', commandWithArgs)}`)
369
+ const taskLog = clack.taskLog({ title })
370
370
 
371
- // Skip hook execution if no id is provided (e.g., during benchmarks or tests)
372
- if (!id) {
373
- return
374
- }
371
+ state.activeHookLogs.set(id, { taskLog, hrStart: process.hrtime() })
372
+ })
375
373
 
376
- if (logLevel <= logLevelMap.silent) {
377
- await runHook({
378
- id,
379
- command,
380
- args,
381
- commandWithArgs,
382
- context,
383
- sink: {
384
- onStderr: (s) => console.error(s),
385
- onStdout: (s) => console.log(s),
386
- },
387
- })
374
+ context.on('kubb:hook:end', ({ id, command, args, success, error }) => {
375
+ if (logLevel <= logLevelMap.silent || !id) {
388
376
  return
389
377
  }
390
378
 
391
- clack.intro(text)
392
-
393
- const logger = clack.taskLog({
394
- title: getMessage(['Executing hook', logLevel >= logLevelMap.info ? styleText('dim', commandWithArgs) : undefined].filter(Boolean).join(' ')),
395
- })
396
-
397
- const writable = new ClackWritable(logger)
398
-
399
- await runHook({
400
- id,
401
- command,
402
- args,
403
- commandWithArgs,
404
- context,
405
- stream: true,
406
- sink: {
407
- onLine: (line) => writable.write(line),
408
- onStderr: (s) => logger.error(s),
409
- onStdout: (s) => logger.message(s),
410
- },
411
- })
412
- })
413
-
414
- context.on('kubb:hook:end', ({ command, args }) => {
415
- if (logLevel <= logLevelMap.silent) {
379
+ const active = state.activeHookLogs.get(id)
380
+ if (!active) {
416
381
  return
417
382
  }
383
+ state.activeHookLogs.delete(id)
418
384
 
419
385
  const commandWithArgs = formatCommandWithArgs(command, args)
420
- const text = getMessage(`Hook ${styleText('dim', commandWithArgs)} successfully executed`)
386
+ const duration = formatMsWithColor(getElapsedMs(active.hrStart))
421
387
 
422
- clack.outro(text)
388
+ if (success) {
389
+ active.taskLog.success(getMessage(`${styleText('dim', commandWithArgs)} completed in ${duration}`))
390
+ } else {
391
+ const reason = error?.message ? ` (${error.message})` : ''
392
+ active.taskLog.error(getMessage(`${styleText('dim', commandWithArgs)} failed${reason}`), { showLog: true })
393
+ }
423
394
  })
424
395
 
425
396
  context.on('kubb:generation:summary', ({ config, pluginTimings, failedPlugins, filesCreated, status, hrStart }) => {
@@ -454,5 +425,28 @@ Run \`npm install -g @kubb/cli\` to update`,
454
425
  context.on('kubb:lifecycle:end', () => {
455
426
  reset()
456
427
  })
428
+
429
+ return (_commandWithArgs: string, hookId: string) => {
430
+ if (logLevel <= logLevelMap.silent) {
431
+ return {
432
+ onStdout: (s: string) => console.log(s),
433
+ onStderr: (s: string) => console.error(s),
434
+ }
435
+ }
436
+
437
+ const active = state.activeHookLogs.get(hookId)
438
+ if (!active) {
439
+ return undefined
440
+ }
441
+
442
+ const { taskLog } = active
443
+
444
+ return {
445
+ stream: true,
446
+ onLine: (line: string) => taskLog.message(styleText('dim', line)),
447
+ onStdout: (s: string) => taskLog.message(s),
448
+ onStderr: (s: string) => taskLog.message(styleText('red', s)),
449
+ }
450
+ }
457
451
  },
458
452
  })
@@ -4,14 +4,24 @@ import { formatMs, write } from '@internals/utils'
4
4
  import { defineLogger } from '@kubb/core'
5
5
 
6
6
  type CachedEvent = {
7
+ /**
8
+ * Timestamp when this event was captured, used to derive the log filename.
9
+ */
7
10
  date: Date
11
+ /**
12
+ * Accumulated log lines for this event.
13
+ */
8
14
  logs: string[]
15
+ /**
16
+ * Optional override for the output filename inside `.kubb/`. When omitted, the filename is derived from `date`.
17
+ */
9
18
  fileName?: string
10
19
  }
11
20
 
12
21
  /**
13
22
  * FileSystem logger that captures debug events and writes them to `.kubb` directory files.
14
- * Note: Logs write on `lifecycle:end` or process exit. Cached logs may be lost if the process crashes before these events.
23
+ *
24
+ * @note Logs are written on `kubb:lifecycle:end` or process exit. Cached logs may be lost if the process crashes before either event.
15
25
  */
16
26
  export const fileSystemLogger = defineLogger({
17
27
  name: 'filesystem',
@@ -42,13 +52,15 @@ export const fileSystemLogger = defineLogger({
42
52
  }
43
53
 
44
54
  if (log.logs.length > 0) {
45
- const timestamp = log.date.toLocaleString()
46
- files[pathName].push(`[${timestamp}]\n${log.logs.join('\n')}`)
55
+ const prefix = `[${log.date.toLocaleString()}] `
56
+ const indent = ' '.repeat(prefix.length)
57
+ const [first, ...rest] = log.logs
58
+ files[pathName].push([prefix + first, ...rest.map((line) => indent + line)].join('\n'))
47
59
  }
48
60
  }
49
61
 
50
62
  for (const [fileName, logs] of Object.entries(files)) {
51
- await write(fileName, logs.join('\n\n'))
63
+ await write(fileName, logs.join('\n'))
52
64
  }
53
65
 
54
66
  return Object.keys(files)
@@ -57,21 +69,21 @@ export const fileSystemLogger = defineLogger({
57
69
  context.on('kubb:info', ({ message, info }) => {
58
70
  state.cachedLogs.add({
59
71
  date: new Date(),
60
- logs: [`ℹ ${message} ${info}`],
72
+ logs: [`ℹ ${[message, info].filter(Boolean).join(' ')}`],
61
73
  })
62
74
  })
63
75
 
64
76
  context.on('kubb:success', ({ message, info }) => {
65
77
  state.cachedLogs.add({
66
78
  date: new Date(),
67
- logs: [`✓ ${message} ${info}`],
79
+ logs: [`✓ ${[message, info].filter(Boolean).join(' ')}`],
68
80
  })
69
81
  })
70
82
 
71
83
  context.on('kubb:warn', ({ message, info }) => {
72
84
  state.cachedLogs.add({
73
85
  date: new Date(),
74
- logs: [`⚠ ${message} ${info}`],
86
+ logs: [`⚠ ${[message, info].filter(Boolean).join(' ')}`],
75
87
  })
76
88
  })
77
89
 
@@ -82,17 +94,18 @@ export const fileSystemLogger = defineLogger({
82
94
  })
83
95
  })
84
96
 
85
- context.on('kubb:debug', (message) => {
97
+ context.on('kubb:debug', ({ date, fileName, logs }) => {
86
98
  state.cachedLogs.add({
87
- date: new Date(),
88
- logs: message.logs,
99
+ date,
100
+ fileName,
101
+ logs,
89
102
  })
90
103
  })
91
104
 
92
105
  context.on('kubb:plugin:start', ({ plugin }) => {
93
106
  state.cachedLogs.add({
94
107
  date: new Date(),
95
- logs: [`Generating ${plugin.name}`],
108
+ logs: [`► Generating ${plugin.name}`],
96
109
  })
97
110
  })
98
111
 
@@ -101,14 +114,14 @@ export const fileSystemLogger = defineLogger({
101
114
 
102
115
  state.cachedLogs.add({
103
116
  date: new Date(),
104
- logs: [success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`],
117
+ logs: [success ? `✓ ${plugin.name} completed in ${durationStr}` : `✗ ${plugin.name} failed in ${durationStr}`],
105
118
  })
106
119
  })
107
120
 
108
121
  context.on('kubb:files:processing:start', ({ files }) => {
109
122
  state.cachedLogs.add({
110
123
  date: new Date(),
111
- logs: [`Start ${files.length} writing:`, ...files.map((file) => file.path)],
124
+ logs: [`► Writing ${files.length} files`, ...files.map((file) => ` ${file.path}`)],
112
125
  })
113
126
  })
114
127
 
@@ -1,7 +1,7 @@
1
+ import process from 'node:process'
1
2
  import { styleText } from 'node:util'
2
- import { formatHrtime, formatMs, formatMsWithColor, toCause } from '@internals/utils'
3
+ import { formatHrtime, formatMs, formatMsWithColor, getElapsedMs, toCause } from '@internals/utils'
3
4
  import { type Config, defineLogger, logLevel as logLevelMap } from '@kubb/core'
4
- import { runHook } from '../utils/runHook.ts'
5
5
  import { buildProgressLine, formatCommandWithArgs, formatMessage } from './utils.ts'
6
6
 
7
7
  /**
@@ -19,9 +19,12 @@ export const githubActionsLogger = defineLogger({
19
19
  processedFiles: 0,
20
20
  hrStart: process.hrtime(),
21
21
  currentConfigs: [] as Array<Config>,
22
+ hookStarts: new Map<string, [number, number]>(),
23
+ openGroupDepth: 0,
22
24
  }
23
25
 
24
26
  function reset() {
27
+ closeAllGroups()
25
28
  state.totalPlugins = 0
26
29
  state.completedPlugins = 0
27
30
  state.failedPlugins = 0
@@ -29,6 +32,7 @@ export const githubActionsLogger = defineLogger({
29
32
  state.processedFiles = 0
30
33
  state.hrStart = process.hrtime()
31
34
  state.currentConfigs = []
35
+ state.hookStarts.clear()
32
36
  }
33
37
 
34
38
  function showProgressStep() {
@@ -48,10 +52,19 @@ export const githubActionsLogger = defineLogger({
48
52
 
49
53
  function openGroup(name: string) {
50
54
  console.log(`::group::${name}`)
55
+ state.openGroupDepth++
51
56
  }
52
57
 
53
58
  function closeGroup(_name: string) {
54
59
  console.log('::endgroup::')
60
+ if (state.openGroupDepth > 0) state.openGroupDepth--
61
+ }
62
+
63
+ function closeAllGroups() {
64
+ while (state.openGroupDepth > 0) {
65
+ console.log('::endgroup::')
66
+ state.openGroupDepth--
67
+ }
55
68
  }
56
69
 
57
70
  context.on('kubb:info', ({ message, info = '' }) => {
@@ -87,6 +100,10 @@ export const githubActionsLogger = defineLogger({
87
100
  context.on('kubb:error', ({ error }) => {
88
101
  const caused = toCause(error)
89
102
 
103
+ // Always release any unclosed groups so a thrown :start without a matching :end
104
+ // (e.g., when getConfigs or kubb.setup throws) doesn't leak an open section.
105
+ closeAllGroups()
106
+
90
107
  if (logLevel <= logLevelMap.silent) {
91
108
  return
92
109
  }
@@ -116,6 +133,10 @@ export const githubActionsLogger = defineLogger({
116
133
  reset()
117
134
  })
118
135
 
136
+ context.on('kubb:version:new', ({ currentVersion, latestVersion }) => {
137
+ console.log(`::notice::Update available for Kubb: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`)
138
+ })
139
+
119
140
  context.on('kubb:config:start', () => {
120
141
  if (logLevel <= logLevelMap.silent) {
121
142
  return
@@ -307,45 +328,65 @@ export const githubActionsLogger = defineLogger({
307
328
  }
308
329
  })
309
330
 
310
- context.on('kubb:hook:start', async ({ id, command, args }) => {
311
- const commandWithArgs = formatCommandWithArgs(command, args)
312
- const text = getMessage(`Hook ${styleText('dim', commandWithArgs)} started`)
331
+ context.on('kubb:hooks:start', () => {
332
+ if (logLevel <= logLevelMap.silent) {
333
+ return
334
+ }
313
335
 
314
- if (logLevel > logLevelMap.silent) {
315
- if (state.currentConfigs.length === 1) {
316
- openGroup(`Hook ${commandWithArgs}`)
317
- }
318
- console.log(text)
336
+ if (state.currentConfigs.length === 1) {
337
+ openGroup('Hooks')
319
338
  }
320
339
 
321
- // Skip hook execution if no id is provided (e.g., during benchmarks or tests)
322
- if (!id) {
340
+ console.log(getMessage('Hooks started'))
341
+ })
342
+
343
+ context.on('kubb:hooks:end', () => {
344
+ if (logLevel <= logLevelMap.silent) {
323
345
  return
324
346
  }
325
347
 
326
- await runHook({
327
- id,
328
- command,
329
- args,
330
- commandWithArgs,
331
- context,
332
- sink: {
333
- // GHA formats errors with the ::error:: annotation
334
- onStdout: logLevel > logLevelMap.silent ? (s) => console.log(s) : undefined,
335
- onStderr: logLevel > logLevelMap.silent ? (s) => console.error(`::error::${s}`) : undefined,
336
- },
337
- })
348
+ console.log(getMessage('Hooks completed'))
349
+
350
+ if (state.currentConfigs.length === 1) {
351
+ closeGroup('Hooks')
352
+ }
338
353
  })
339
354
 
340
- context.on('kubb:hook:end', ({ command, args }) => {
355
+ context.on('kubb:hook:start', ({ id, command, args }) => {
341
356
  if (logLevel <= logLevelMap.silent) {
342
357
  return
343
358
  }
344
359
 
360
+ if (id) {
361
+ state.hookStarts.set(id, process.hrtime())
362
+ }
363
+
345
364
  const commandWithArgs = formatCommandWithArgs(command, args)
346
- const text = getMessage(`Hook ${styleText('dim', commandWithArgs)} completed`)
365
+ const text = getMessage(`Hook ${styleText('dim', commandWithArgs)} started`)
347
366
 
367
+ if (state.currentConfigs.length === 1) {
368
+ openGroup(`Hook ${commandWithArgs}`)
369
+ }
348
370
  console.log(text)
371
+ })
372
+
373
+ context.on('kubb:hook:end', ({ id, command, args, success, error }) => {
374
+ if (logLevel <= logLevelMap.silent) {
375
+ return
376
+ }
377
+
378
+ const hrStart = id ? state.hookStarts.get(id) : undefined
379
+ if (id) state.hookStarts.delete(id)
380
+ const durationStr = hrStart ? ` in ${formatMsWithColor(getElapsedMs(hrStart))}` : ''
381
+
382
+ const commandWithArgs = formatCommandWithArgs(command, args)
383
+
384
+ if (success) {
385
+ console.log(getMessage(`${styleText('green', '✓')} Hook ${styleText('dim', commandWithArgs)} completed${durationStr}`))
386
+ } else {
387
+ const reason = error?.message ? ` (${error.message})` : ''
388
+ console.log(`::error::Hook ${commandWithArgs} failed${durationStr}${reason}`)
389
+ }
349
390
 
350
391
  if (state.currentConfigs.length === 1) {
351
392
  closeGroup(`Hook ${commandWithArgs}`)
@@ -375,5 +416,10 @@ export const githubActionsLogger = defineLogger({
375
416
  context.on('kubb:lifecycle:end', () => {
376
417
  reset()
377
418
  })
419
+
420
+ return (_commandWithArgs: string, _hookId: string) => ({
421
+ onStdout: logLevel > logLevelMap.silent ? (s: string) => console.log(s) : undefined,
422
+ onStderr: logLevel > logLevelMap.silent ? (s: string) => console.error(`::error::${s}`) : undefined,
423
+ })
378
424
  },
379
425
  })
@@ -1,9 +1,9 @@
1
1
  import { relative } from 'node:path'
2
- import { formatMs, toCause } from '@internals/utils'
2
+ import process from 'node:process'
3
+ import { formatMs, getElapsedMs, toCause } from '@internals/utils'
3
4
  import { defineLogger, logLevel as logLevelMap } from '@kubb/core'
4
5
  import { SUMMARY_SEPARATOR } from '../constants.ts'
5
- import { getSummary } from '../utils/getSummary.ts'
6
- import { runHook } from '../utils/runHook.ts'
6
+ import { getSummary } from './utils.ts'
7
7
  import { formatCommandWithArgs, formatMessage } from './utils.ts'
8
8
 
9
9
  /**
@@ -13,6 +13,7 @@ export const plainLogger = defineLogger({
13
13
  name: 'plain',
14
14
  install(context, options) {
15
15
  const logLevel = options?.logLevel ?? logLevelMap.info
16
+ const hookStarts = new Map<string, [number, number]>()
16
17
 
17
18
  function getMessage(message: string): string {
18
19
  return formatMessage(message, logLevel)
@@ -73,8 +74,16 @@ export const plainLogger = defineLogger({
73
74
  }
74
75
  })
75
76
 
76
- context.on('kubb:lifecycle:start', () => {
77
- console.log('Kubb CLI 🧩')
77
+ context.on('kubb:lifecycle:start', ({ version }) => {
78
+ console.log(`Kubb CLI v${version}`)
79
+ })
80
+
81
+ context.on('kubb:version:new', ({ currentVersion, latestVersion }) => {
82
+ if (logLevel <= logLevelMap.silent) {
83
+ return
84
+ }
85
+
86
+ console.log(getMessage(`Update available: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`))
78
87
  })
79
88
 
80
89
  context.on('kubb:config:start', () => {
@@ -199,41 +208,52 @@ export const plainLogger = defineLogger({
199
208
  console.log(text)
200
209
  })
201
210
 
202
- context.on('kubb:hook:start', async ({ id, command, args }) => {
203
- const commandWithArgs = formatCommandWithArgs(command, args)
204
- const text = getMessage(`Hook ${commandWithArgs} started`)
211
+ context.on('kubb:hooks:start', () => {
212
+ if (logLevel <= logLevelMap.silent) {
213
+ return
214
+ }
205
215
 
206
- if (logLevel > logLevelMap.silent) {
207
- console.log(text)
216
+ console.log(getMessage('Hooks started'))
217
+ })
218
+
219
+ context.on('kubb:hooks:end', () => {
220
+ if (logLevel <= logLevelMap.silent) {
221
+ return
208
222
  }
209
223
 
210
- // Skip hook execution if no id is provided (e.g., during benchmarks or tests)
211
- if (!id) {
224
+ console.log(getMessage('Hooks completed'))
225
+ })
226
+
227
+ context.on('kubb:hook:start', ({ id, command, args }) => {
228
+ if (logLevel <= logLevelMap.silent) {
212
229
  return
213
230
  }
214
231
 
215
- await runHook({
216
- id,
217
- command,
218
- args,
219
- commandWithArgs,
220
- context,
221
- sink: {
222
- onStdout: logLevel > logLevelMap.silent ? (s) => console.log(s) : undefined,
223
- onStderr: logLevel > logLevelMap.silent ? (s) => console.error(s) : undefined,
224
- },
225
- })
232
+ if (id) {
233
+ hookStarts.set(id, process.hrtime())
234
+ }
235
+
236
+ const commandWithArgs = formatCommandWithArgs(command, args)
237
+ console.log(getMessage(`Hook ${commandWithArgs} started`))
226
238
  })
227
239
 
228
- context.on('kubb:hook:end', ({ command, args }) => {
240
+ context.on('kubb:hook:end', ({ id, command, args, success, error }) => {
229
241
  if (logLevel <= logLevelMap.silent) {
230
242
  return
231
243
  }
232
244
 
245
+ const hrStart = id ? hookStarts.get(id) : undefined
246
+ if (id) hookStarts.delete(id)
247
+ const durationStr = hrStart ? ` in ${formatMs(getElapsedMs(hrStart))}` : ''
248
+
233
249
  const commandWithArgs = formatCommandWithArgs(command, args)
234
- const text = getMessage(`Hook ${commandWithArgs} completed`)
235
250
 
236
- console.log(text)
251
+ if (success) {
252
+ console.log(getMessage(`✓ Hook ${commandWithArgs} completed${durationStr}`))
253
+ } else {
254
+ const reason = error?.message ? ` (${error.message})` : ''
255
+ console.log(getMessage(`✗ Hook ${commandWithArgs} failed${durationStr}${reason}`))
256
+ }
237
257
  })
238
258
 
239
259
  context.on('kubb:generation:summary', ({ config, pluginTimings, status, hrStart, failedPlugins, filesCreated }) => {
@@ -250,5 +270,10 @@ export const plainLogger = defineLogger({
250
270
  console.log(summary.join('\n'))
251
271
  console.log(SUMMARY_SEPARATOR)
252
272
  })
273
+
274
+ return (_commandWithArgs: string, _hookId: string) => ({
275
+ onStdout: logLevel > logLevelMap.silent ? (s: string) => console.log(s) : undefined,
276
+ onStderr: logLevel > logLevelMap.silent ? (s: string) => console.error(s) : undefined,
277
+ })
253
278
  },
254
279
  })
@@ -1 +1,7 @@
1
+ /**
2
+ * Logger adapter selected by `setupLogger` based on the runtime environment.
3
+ * - `'clack'`: TTY-aware output with spinners and progress bars.
4
+ * - `'github-actions'`: CI output using `::group::` annotations.
5
+ * - `'plain'`: Plain `console.log` output for non-TTY environments.
6
+ */
1
7
  export type LoggerType = 'clack' | 'github-actions' | 'plain'