@kubb/cli 5.0.0-alpha.9 → 5.0.0-beta.10

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 (173) hide show
  1. package/README.md +180 -27
  2. package/bin/kubb.js +6 -0
  3. package/dist/agent-Bx2yllmS.js +68 -0
  4. package/dist/agent-Bx2yllmS.js.map +1 -0
  5. package/dist/agent-CeLwj5im.cjs +70 -0
  6. package/dist/agent-CeLwj5im.cjs.map +1 -0
  7. package/dist/{chunk--u3MIqq1.js → chunk-BvFE5Tac.js} +1 -0
  8. package/dist/constants-B2JTeRBb.js +42 -0
  9. package/dist/constants-B2JTeRBb.js.map +1 -0
  10. package/dist/constants-BINTA5VZ.cjs +77 -0
  11. package/dist/constants-BINTA5VZ.cjs.map +1 -0
  12. package/dist/constants-BYGmiFs0.cjs +139 -0
  13. package/dist/constants-BYGmiFs0.cjs.map +1 -0
  14. package/dist/constants-DSJ-Xrbv.js +116 -0
  15. package/dist/constants-DSJ-Xrbv.js.map +1 -0
  16. package/dist/define-Bdn8j5VM.cjs +54 -0
  17. package/dist/define-Bdn8j5VM.cjs.map +1 -0
  18. package/dist/define-m_fp-Aqm.js +43 -0
  19. package/dist/define-m_fp-Aqm.js.map +1 -0
  20. package/dist/errors-CINO1EIv.js +43 -0
  21. package/dist/errors-CINO1EIv.js.map +1 -0
  22. package/dist/{errors-DBW0N9w4.cjs → errors-CLCjoSg0.cjs} +22 -6
  23. package/dist/errors-CLCjoSg0.cjs.map +1 -0
  24. package/dist/{generate-Rly1EXBN.js → generate-BLvcvoIj.js} +12 -6
  25. package/dist/generate-BLvcvoIj.js.map +1 -0
  26. package/dist/{generate-DU5zzc54.cjs → generate-drLxTAnz.cjs} +11 -5
  27. package/dist/generate-drLxTAnz.cjs.map +1 -0
  28. package/dist/index.cjs +52 -21
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +53 -22
  32. package/dist/index.js.map +1 -1
  33. package/dist/init-CGu7JZEF.js +53 -0
  34. package/dist/init-CGu7JZEF.js.map +1 -0
  35. package/dist/init-CyCjvIEF.cjs +53 -0
  36. package/dist/init-CyCjvIEF.cjs.map +1 -0
  37. package/dist/mcp-Cq2sylQC.js +39 -0
  38. package/dist/mcp-Cq2sylQC.js.map +1 -0
  39. package/dist/mcp-DSF5gpI-.cjs +39 -0
  40. package/dist/mcp-DSF5gpI-.cjs.map +1 -0
  41. package/dist/{package-BJ6ionm6.cjs → package-DZ-6zAIO.cjs} +2 -2
  42. package/dist/package-DZ-6zAIO.cjs.map +1 -0
  43. package/dist/package-OLYIpjqw.js +6 -0
  44. package/dist/package-OLYIpjqw.js.map +1 -0
  45. package/dist/{generate-BHNyeQXl.js → run-Bfbr3RaM.js} +781 -502
  46. package/dist/run-Bfbr3RaM.js.map +1 -0
  47. package/dist/run-BzpYYOQs.js +121 -0
  48. package/dist/run-BzpYYOQs.js.map +1 -0
  49. package/dist/run-CCZ24VKk.js +51 -0
  50. package/dist/run-CCZ24VKk.js.map +1 -0
  51. package/dist/run-CF97BWVa.js +244 -0
  52. package/dist/run-CF97BWVa.js.map +1 -0
  53. package/dist/run-CQbj3ley.cjs +52 -0
  54. package/dist/run-CQbj3ley.cjs.map +1 -0
  55. package/dist/{generate-Cq5RDTBL.cjs → run-CVlrIZoW.cjs} +786 -507
  56. package/dist/run-CVlrIZoW.cjs.map +1 -0
  57. package/dist/run-D0hmRpHy.js +49 -0
  58. package/dist/run-D0hmRpHy.js.map +1 -0
  59. package/dist/run-DwdAwnLG.cjs +125 -0
  60. package/dist/run-DwdAwnLG.cjs.map +1 -0
  61. package/dist/run-Lr0Ctnu0.cjs +50 -0
  62. package/dist/run-Lr0Ctnu0.cjs.map +1 -0
  63. package/dist/run-YsoCk5we.cjs +248 -0
  64. package/dist/run-YsoCk5we.cjs.map +1 -0
  65. package/dist/{shell-7HPrTCJ5.cjs → shell-475fQKaX.cjs} +8 -3
  66. package/dist/shell-475fQKaX.cjs.map +1 -0
  67. package/dist/{shell-DqqWsHCD.js → shell-CN6DNqeC.js} +9 -4
  68. package/dist/shell-CN6DNqeC.js.map +1 -0
  69. package/dist/{telemetry-DZ7IrLAU.cjs → telemetry-B2iWkY5e.cjs} +53 -13
  70. package/dist/telemetry-B2iWkY5e.cjs.map +1 -0
  71. package/dist/{telemetry-BF3SMlH6.js → telemetry-BkektVz6.js} +52 -12
  72. package/dist/telemetry-BkektVz6.js.map +1 -0
  73. package/dist/validate-CAUqLaGt.js +26 -0
  74. package/dist/validate-CAUqLaGt.js.map +1 -0
  75. package/dist/validate-J6AEd5zK.cjs +26 -0
  76. package/dist/validate-J6AEd5zK.cjs.map +1 -0
  77. package/package.json +57 -48
  78. package/src/commands/agent/start.ts +27 -8
  79. package/src/commands/agent.ts +3 -1
  80. package/src/commands/generate.ts +39 -8
  81. package/src/commands/init.ts +40 -4
  82. package/src/commands/mcp.ts +28 -4
  83. package/src/commands/validate.ts +11 -4
  84. package/src/constants.ts +5 -80
  85. package/src/index.ts +12 -13
  86. package/src/loggers/clackLogger.ts +98 -88
  87. package/src/loggers/fileSystemLogger.ts +37 -25
  88. package/src/loggers/githubActionsLogger.ts +35 -48
  89. package/src/loggers/plainLogger.ts +33 -45
  90. package/src/loggers/types.ts +6 -0
  91. package/src/loggers/utils.ts +155 -9
  92. package/src/runners/agent/run.ts +113 -0
  93. package/src/runners/agent/utils.ts +98 -0
  94. package/src/runners/generate/run.ts +276 -0
  95. package/src/runners/generate/utils.ts +209 -0
  96. package/src/runners/init/run.ts +211 -0
  97. package/src/{utils/packageManager.ts → runners/init/utils.ts} +10 -0
  98. package/src/runners/mcp/run.ts +55 -0
  99. package/src/runners/validate/run.ts +63 -0
  100. package/src/{utils/telemetry.ts → telemetry.ts} +28 -8
  101. package/bin/kubb.cjs +0 -18
  102. package/dist/agent-5mmp7QzF.js +0 -56
  103. package/dist/agent-5mmp7QzF.js.map +0 -1
  104. package/dist/agent-BKphjOIF.cjs +0 -58
  105. package/dist/agent-BKphjOIF.cjs.map +0 -1
  106. package/dist/agent-BapvKB4r.cjs +0 -92
  107. package/dist/agent-BapvKB4r.cjs.map +0 -1
  108. package/dist/agent-CBrpIMMU.js +0 -88
  109. package/dist/agent-CBrpIMMU.js.map +0 -1
  110. package/dist/constants-D0XHAHeZ.cjs +0 -178
  111. package/dist/constants-D0XHAHeZ.cjs.map +0 -1
  112. package/dist/constants-DJM9zCXm.js +0 -131
  113. package/dist/constants-DJM9zCXm.js.map +0 -1
  114. package/dist/define--M_JMcDC.js +0 -25
  115. package/dist/define--M_JMcDC.js.map +0 -1
  116. package/dist/define-D6Kfm7-Z.cjs +0 -36
  117. package/dist/define-D6Kfm7-Z.cjs.map +0 -1
  118. package/dist/errors-6mF_WKxg.js +0 -27
  119. package/dist/errors-6mF_WKxg.js.map +0 -1
  120. package/dist/errors-DBW0N9w4.cjs.map +0 -1
  121. package/dist/generate-BHNyeQXl.js.map +0 -1
  122. package/dist/generate-Cq5RDTBL.cjs.map +0 -1
  123. package/dist/generate-DU5zzc54.cjs.map +0 -1
  124. package/dist/generate-Rly1EXBN.js.map +0 -1
  125. package/dist/init-BK6inBTR.cjs +0 -306
  126. package/dist/init-BK6inBTR.cjs.map +0 -1
  127. package/dist/init-BQ6zfsnw.js +0 -302
  128. package/dist/init-BQ6zfsnw.js.map +0 -1
  129. package/dist/init-CN1JFyGX.cjs +0 -25
  130. package/dist/init-CN1JFyGX.cjs.map +0 -1
  131. package/dist/init-iN7e1XwI.js +0 -25
  132. package/dist/init-iN7e1XwI.js.map +0 -1
  133. package/dist/jiti-Cd3S0xwr.cjs +0 -16
  134. package/dist/jiti-Cd3S0xwr.cjs.map +0 -1
  135. package/dist/jiti-e08mD2Ph.js +0 -11
  136. package/dist/jiti-e08mD2Ph.js.map +0 -1
  137. package/dist/mcp-BiGUvbWP.js +0 -41
  138. package/dist/mcp-BiGUvbWP.js.map +0 -1
  139. package/dist/mcp-CONmm_xT.cjs +0 -42
  140. package/dist/mcp-CONmm_xT.cjs.map +0 -1
  141. package/dist/mcp-T7Q4nWbT.cjs +0 -16
  142. package/dist/mcp-T7Q4nWbT.cjs.map +0 -1
  143. package/dist/mcp-eP1S40LZ.js +0 -16
  144. package/dist/mcp-eP1S40LZ.js.map +0 -1
  145. package/dist/package-BJ6ionm6.cjs.map +0 -1
  146. package/dist/package-BKZ0H3Zf.js +0 -6
  147. package/dist/package-BKZ0H3Zf.js.map +0 -1
  148. package/dist/shell-7HPrTCJ5.cjs.map +0 -1
  149. package/dist/shell-DqqWsHCD.js.map +0 -1
  150. package/dist/telemetry-BF3SMlH6.js.map +0 -1
  151. package/dist/telemetry-DZ7IrLAU.cjs.map +0 -1
  152. package/dist/validate-BImbbx1t.js +0 -41
  153. package/dist/validate-BImbbx1t.js.map +0 -1
  154. package/dist/validate-DAZdX_0i.js +0 -25
  155. package/dist/validate-DAZdX_0i.js.map +0 -1
  156. package/dist/validate-DucFMytl.cjs +0 -25
  157. package/dist/validate-DucFMytl.cjs.map +0 -1
  158. package/dist/validate-ujLCYSWU.cjs +0 -42
  159. package/dist/validate-ujLCYSWU.cjs.map +0 -1
  160. package/src/runners/agent.ts +0 -102
  161. package/src/runners/generate.ts +0 -343
  162. package/src/runners/init.ts +0 -323
  163. package/src/runners/mcp.ts +0 -32
  164. package/src/runners/validate.ts +0 -35
  165. package/src/types.ts +0 -11
  166. package/src/utils/Writables.ts +0 -17
  167. package/src/utils/executeHooks.ts +0 -45
  168. package/src/utils/flags.ts +0 -10
  169. package/src/utils/getCosmiConfig.ts +0 -71
  170. package/src/utils/getSummary.ts +0 -68
  171. package/src/utils/jiti.ts +0 -9
  172. package/src/utils/runHook.ts +0 -75
  173. package/src/utils/watcher.ts +0 -19
@@ -1,30 +1,38 @@
1
- import "./chunk--u3MIqq1.js";
2
- import { n as toCause, r as toError } from "./errors-6mF_WKxg.js";
3
- import { a as canUseTTY, i as executeIfOnline, o as isGitHubActions, r as sendTelemetry, t as buildTelemetryEvent } from "./telemetry-BF3SMlH6.js";
4
- import { n as tokenize } from "./shell-DqqWsHCD.js";
5
- import { t as version } from "./package-BKZ0H3Zf.js";
6
- import { a as WATCHER_IGNORED_PATHS, i as SUMMARY_SEPARATOR, t as KUBB_NPM_PACKAGE_URL } from "./constants-DJM9zCXm.js";
1
+ import "./chunk-BvFE5Tac.js";
2
+ import { n as toCause, r as toError } from "./errors-CINO1EIv.js";
3
+ import { a as canUseTTY, i as executeIfOnline, o as isGitHubActions, r as sendTelemetry, t as buildTelemetryEvent } from "./telemetry-BkektVz6.js";
4
+ import { n as tokenize } from "./shell-CN6DNqeC.js";
5
+ import { t as version } from "./package-OLYIpjqw.js";
6
+ import { a as WATCHER_IGNORED_PATHS, i as SUMMARY_SEPARATOR, t as KUBB_NPM_PACKAGE_URL } from "./constants-B2JTeRBb.js";
7
7
  import { styleText } from "node:util";
8
8
  import { EventEmitter } from "node:events";
9
9
  import { createHash } from "node:crypto";
10
- import "node:fs";
10
+ import { spawn } from "node:child_process";
11
+ import { readdirSync } from "node:fs";
11
12
  import { mkdir, readFile, writeFile } from "node:fs/promises";
12
13
  import path, { dirname, relative, resolve } from "node:path";
13
14
  import process$1 from "node:process";
14
15
  import * as clack from "@clack/prompts";
15
- import { defineLogger, detectFormatter, detectLinter, formatters, getConfigs, isInputPath, linters, logLevel, safeBuild, setup } from "@kubb/core";
16
- import { NonZeroExitError, x } from "tinyexec";
16
+ import { createKubb, defineLogger, isInputPath, logLevel } from "@kubb/core";
17
17
  import { Writable } from "node:stream";
18
18
  import { cosmiconfig } from "cosmiconfig";
19
19
  import { createJiti } from "jiti";
20
+ import { NonZeroExitError, x } from "tinyexec";
20
21
  //#region ../../internals/utils/src/asyncEventEmitter.ts
21
22
  /**
22
- * A typed EventEmitter that awaits all async listeners before resolving.
23
+ * Typed `EventEmitter` that awaits all async listeners before resolving.
23
24
  * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
29
+ * emitter.on('build', async (name) => { console.log(name) })
30
+ * await emitter.emit('build', 'petstore') // all listeners awaited
31
+ * ```
24
32
  */
25
33
  var AsyncEventEmitter = class {
26
34
  /**
27
- * `maxListener` controls the maximum number of listeners per event before Node emits a memory-leak warning.
35
+ * Maximum number of listeners per event before Node emits a memory-leak warning.
28
36
  * @default 10
29
37
  */
30
38
  constructor(maxListener = 10) {
@@ -32,31 +40,48 @@ var AsyncEventEmitter = class {
32
40
  }
33
41
  #emitter = new EventEmitter();
34
42
  /**
35
- * Emits an event and awaits all registered listeners in parallel.
43
+ * Emits `eventName` and awaits all registered listeners sequentially.
36
44
  * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * await emitter.emit('build', 'petstore')
49
+ * ```
37
50
  */
38
51
  async emit(eventName, ...eventArgs) {
39
52
  const listeners = this.#emitter.listeners(eventName);
40
53
  if (listeners.length === 0) return;
41
- await Promise.all(listeners.map(async (listener) => {
54
+ for (const listener of listeners) try {
55
+ await listener(...eventArgs);
56
+ } catch (err) {
57
+ let serializedArgs;
42
58
  try {
43
- return await listener(...eventArgs);
44
- } catch (err) {
45
- let serializedArgs;
46
- try {
47
- serializedArgs = JSON.stringify(eventArgs);
48
- } catch {
49
- serializedArgs = String(eventArgs);
50
- }
51
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
59
+ serializedArgs = JSON.stringify(eventArgs);
60
+ } catch {
61
+ serializedArgs = String(eventArgs);
52
62
  }
53
- }));
63
+ throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
64
+ }
54
65
  }
55
- /** Registers a persistent listener for the given event. */
66
+ /**
67
+ * Registers a persistent listener for `eventName`.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * emitter.on('build', async (name) => { console.log(name) })
72
+ * ```
73
+ */
56
74
  on(eventName, handler) {
57
75
  this.#emitter.on(eventName, handler);
58
76
  }
59
- /** Registers a one-shot listener that removes itself after the first invocation. */
77
+ /**
78
+ * Registers a one-shot listener that removes itself after the first invocation.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * emitter.onOnce('build', async (name) => { console.log(name) })
83
+ * ```
84
+ */
60
85
  onOnce(eventName, handler) {
61
86
  const wrapper = (...args) => {
62
87
  this.off(eventName, wrapper);
@@ -64,11 +89,37 @@ var AsyncEventEmitter = class {
64
89
  };
65
90
  this.on(eventName, wrapper);
66
91
  }
67
- /** Removes a previously registered listener. */
92
+ /**
93
+ * Removes a previously registered listener.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * emitter.off('build', handler)
98
+ * ```
99
+ */
68
100
  off(eventName, handler) {
69
101
  this.#emitter.off(eventName, handler);
70
102
  }
71
- /** Removes all listeners from every event channel. */
103
+ /**
104
+ * Returns the number of listeners registered for `eventName`.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * emitter.on('build', handler)
109
+ * emitter.listenerCount('build') // 1
110
+ * ```
111
+ */
112
+ listenerCount(eventName) {
113
+ return this.#emitter.listenerCount(eventName);
114
+ }
115
+ /**
116
+ * Removes all listeners from every event channel.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * emitter.removeAll()
121
+ * ```
122
+ */
72
123
  removeAll() {
73
124
  this.#emitter.removeAllListeners();
74
125
  }
@@ -76,8 +127,15 @@ var AsyncEventEmitter = class {
76
127
  //#endregion
77
128
  //#region ../../internals/utils/src/time.ts
78
129
  /**
79
- * Calculates elapsed time in milliseconds from a high-resolution start time.
80
- * Rounds to 2 decimal places to provide sub-millisecond precision without noise.
130
+ * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
131
+ * Rounds to 2 decimal places for sub-millisecond precision without noise.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * const start = process.hrtime()
136
+ * doWork()
137
+ * getElapsedMs(start) // 42.35
138
+ * ```
81
139
  */
82
140
  function getElapsedMs(hrStart) {
83
141
  const [seconds, nanoseconds] = process.hrtime(hrStart);
@@ -85,8 +143,14 @@ function getElapsedMs(hrStart) {
85
143
  return Math.round(ms * 100) / 100;
86
144
  }
87
145
  /**
88
- * Converts a millisecond duration into a human-readable string.
89
- * Adjusts units (ms, s, m s) based on the magnitude of the duration.
146
+ * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * formatMs(250) // '250ms'
151
+ * formatMs(1500) // '1.50s'
152
+ * formatMs(90000) // '1m 30.0s'
153
+ * ```
90
154
  */
91
155
  function formatMs(ms) {
92
156
  if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
@@ -94,7 +158,14 @@ function formatMs(ms) {
94
158
  return `${Math.round(ms)}ms`;
95
159
  }
96
160
  /**
97
- * Convenience helper: formats the elapsed time since `hrStart` in one step.
161
+ * Formats the elapsed time since `hrStart` as a human-readable string.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * const start = process.hrtime()
166
+ * doWork()
167
+ * formatHrtime(start) // '1.50s'
168
+ * ```
98
169
  */
99
170
  function formatHrtime(hrStart) {
100
171
  return formatMs(getElapsedMs(hrStart));
@@ -136,18 +207,46 @@ function gradient(colorStops, text) {
136
207
  return `\x1b[38;2;${Math.round(from.r + (to.r - from.r) * lt)};${Math.round(from.g + (to.g - from.g) * lt)};${Math.round(from.b + (to.b - from.b) * lt)}m${char}\x1b[0m`;
137
208
  }).join("");
138
209
  }
139
- /** ANSI color functions for each part of the Kubb mascot illustration. */
210
+ /**
211
+ * ANSI color functions for each part of the Kubb mascot illustration.
212
+ */
140
213
  const palette = {
214
+ /**
215
+ * Top cap of the skittle.
216
+ */
141
217
  lid: hex("#F55A17"),
218
+ /**
219
+ * Upper wood body.
220
+ */
142
221
  woodTop: hex("#F5A217"),
222
+ /**
223
+ * Middle wood body.
224
+ */
143
225
  woodMid: hex("#F58517"),
226
+ /**
227
+ * Base wood body.
228
+ */
144
229
  woodBase: hex("#B45309"),
230
+ /**
231
+ * Eye whites.
232
+ */
145
233
  eye: hex("#FFFFFF"),
234
+ /**
235
+ * Highlight accent.
236
+ */
146
237
  highlight: hex("#adadc6"),
238
+ /**
239
+ * Cheek blush.
240
+ */
147
241
  blush: hex("#FDA4AF")
148
242
  };
149
243
  /**
150
- * Generates the Kubb mascot welcome banner.
244
+ * Generates the Kubb mascot welcome banner as an ANSI-colored string.
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * console.log(getIntro({ title: 'kubb.config.ts', description: 'generating…', version: '5.0.0', areEyesOpen: true }))
249
+ * ```
151
250
  */
152
251
  function getIntro({ title, description, version, areEyesOpen }) {
153
252
  const kubbVersion = gradient([
@@ -165,7 +264,9 @@ function getIntro({ title, description, version, areEyesOpen }) {
165
264
  ${palette.woodBase("▀▀▀▀▀▀▀▀▀▀▀▀▀")}
166
265
  `;
167
266
  }
168
- /** ANSI color names available for terminal output. */
267
+ /**
268
+ * ANSI color names used by {@link randomCliColor} for deterministic terminal coloring.
269
+ */
169
270
  const randomColors = [
170
271
  "black",
171
272
  "red",
@@ -178,15 +279,27 @@ const randomColors = [
178
279
  "gray"
179
280
  ];
180
281
  /**
181
- * Returns the text wrapped in a deterministic ANSI color derived from the text's SHA-256 hash.
282
+ * Wraps `text` in a deterministic ANSI color derived from the text's SHA-256 hash.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * randomCliColor('petstore') // '\x1b[33m' + 'petstore' + '\x1b[39m' (always the same color for 'petstore')
287
+ * ```
182
288
  */
183
289
  function randomCliColor(text) {
184
290
  if (!text) return "";
185
291
  return styleText(randomColors[createHash("sha256").update(text).digest().readUInt32BE(0) % randomColors.length] ?? "white", text);
186
292
  }
187
293
  /**
188
- * Formats a millisecond duration with an ANSI color based on thresholds:
189
- * green 500 ms · yellow 1 000 ms · red > 1 000 ms
294
+ * Formats a millisecond duration with a threshold-based ANSI color.
295
+ * `≤ 500 ms` → green · `≤ 1 000 ms` → yellow · `> 1 000 ms` → red.
296
+ *
297
+ * @example
298
+ * ```ts
299
+ * formatMsWithColor(200) // '\x1b[32m200ms\x1b[39m'
300
+ * formatMsWithColor(800) // '\x1b[33m800ms\x1b[39m'
301
+ * formatMsWithColor(1500) // '\x1b[31m1.50s\x1b[39m'
302
+ * ```
190
303
  */
191
304
  function formatMsWithColor(ms) {
192
305
  const formatted = formatMs(ms);
@@ -195,27 +308,96 @@ function formatMsWithColor(ms) {
195
308
  return styleText("red", formatted);
196
309
  }
197
310
  //#endregion
311
+ //#region ../../internals/utils/src/formatters.ts
312
+ /**
313
+ * CLI command descriptors for each supported code formatter.
314
+ *
315
+ * Each entry contains the executable `command`, an `args` factory that maps an
316
+ * output path to the correct argument list, and an `errorMessage` shown when
317
+ * the formatter is not found.
318
+ */
319
+ const formatters = {
320
+ prettier: {
321
+ command: "prettier",
322
+ args: (outputPath) => [
323
+ "--ignore-unknown",
324
+ "--write",
325
+ outputPath
326
+ ],
327
+ errorMessage: "Prettier not found"
328
+ },
329
+ biome: {
330
+ command: "biome",
331
+ args: (outputPath) => [
332
+ "format",
333
+ "--write",
334
+ outputPath
335
+ ],
336
+ errorMessage: "Biome not found"
337
+ },
338
+ oxfmt: {
339
+ command: "oxfmt",
340
+ args: (outputPath) => [outputPath],
341
+ errorMessage: "Oxfmt not found"
342
+ }
343
+ };
344
+ async function isFormatterAvailable(formatter) {
345
+ return new Promise((resolve) => {
346
+ const child = spawn(formatter, ["--version"], { stdio: "ignore" });
347
+ child.on("close", (code) => resolve(code === 0));
348
+ child.on("error", () => resolve(false));
349
+ });
350
+ }
351
+ /**
352
+ * Detects the first available code formatter on the current system.
353
+ *
354
+ * - Checks in preference order: `oxfmt`, `biome`, `prettier`.
355
+ * - Returns `null` when none are found.
356
+ *
357
+ * @example
358
+ * ```ts
359
+ * const formatter = await detectFormatter()
360
+ * if (formatter) {
361
+ * console.log(`Using ${formatter} for formatting`)
362
+ * }
363
+ * ```
364
+ */
365
+ async function detectFormatter() {
366
+ const formatterNames = new Set([
367
+ "oxfmt",
368
+ "biome",
369
+ "prettier"
370
+ ]);
371
+ for (const formatter of formatterNames) if (await isFormatterAvailable(formatter)) return formatter;
372
+ return null;
373
+ }
374
+ //#endregion
198
375
  //#region ../../internals/utils/src/fs.ts
199
376
  /**
200
377
  * Writes `data` to `path`, trimming leading/trailing whitespace before saving.
201
- * Skips the write and returns `undefined` when the trimmed content is empty or
202
- * identical to what is already on disk.
378
+ * Skips the write when the trimmed content is empty or identical to what is already on disk.
203
379
  * Creates any missing parent directories automatically.
204
- * When `sanity` is `true`, re-reads the file after writing and throws if the
205
- * content does not match.
380
+ * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.
381
+ *
382
+ * @example
383
+ * ```ts
384
+ * await write('./src/Pet.ts', source) // writes and returns trimmed content
385
+ * await write('./src/Pet.ts', source) // null — file unchanged
386
+ * await write('./src/Pet.ts', ' ') // null — empty content skipped
387
+ * ```
206
388
  */
207
389
  async function write(path, data, options = {}) {
208
390
  const trimmed = data.trim();
209
- if (trimmed === "") return void 0;
391
+ if (trimmed === "") return null;
210
392
  const resolved = resolve(path);
211
393
  if (typeof Bun !== "undefined") {
212
394
  const file = Bun.file(resolved);
213
- if ((await file.exists() ? await file.text() : null) === trimmed) return void 0;
395
+ if ((await file.exists() ? await file.text() : null) === trimmed) return null;
214
396
  await Bun.write(resolved, trimmed);
215
397
  return trimmed;
216
398
  }
217
399
  try {
218
- if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return void 0;
400
+ if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return null;
219
401
  } catch {}
220
402
  await mkdir(dirname(resolved), { recursive: true });
221
403
  await writeFile(resolved, trimmed, { encoding: "utf-8" });
@@ -227,102 +409,88 @@ async function write(path, data, options = {}) {
227
409
  return trimmed;
228
410
  }
229
411
  //#endregion
230
- //#region src/utils/getSummary.ts
231
- function getSummary({ failedPlugins, filesCreated, status, hrStart, config, pluginTimings }) {
232
- const duration = formatHrtime(hrStart);
233
- const pluginsCount = config.plugins?.length ?? 0;
234
- const successCount = pluginsCount - failedPlugins.size;
235
- const meta = {
236
- plugins: status === "success" ? `${styleText("green", `${successCount} successful`)}, ${pluginsCount} total` : `${styleText("green", `${successCount} successful`)}, ${styleText("red", `${failedPlugins.size} failed`)}, ${pluginsCount} total`,
237
- pluginsFailed: status === "failed" ? [...failedPlugins].map(({ plugin }) => randomCliColor(plugin.name)).join(", ") : void 0,
238
- filesCreated,
239
- time: styleText("green", duration),
240
- output: path.isAbsolute(config.root) ? path.resolve(config.root, config.output.path) : config.root
241
- };
242
- const labels = {
243
- plugins: "Plugins:",
244
- failed: "Failed:",
245
- generated: "Generated:",
246
- pluginTimings: "Plugin Timings:",
247
- output: "Output:"
248
- };
249
- const maxLength = Math.max(0, ...[...Object.values(labels), ...pluginTimings ? Array.from(pluginTimings.keys()) : []].map((s) => s.length));
250
- const summaryLines = [];
251
- summaryLines.push(`${labels.plugins.padEnd(maxLength + 2)} ${meta.plugins}`);
252
- if (meta.pluginsFailed) summaryLines.push(`${labels.failed.padEnd(maxLength + 2)} ${meta.pluginsFailed}`);
253
- summaryLines.push(`${labels.generated.padEnd(maxLength + 2)} ${meta.filesCreated} files in ${meta.time}`);
254
- if (pluginTimings && pluginTimings.size > 0) {
255
- const sortedTimings = Array.from(pluginTimings.entries()).sort((a, b) => b[1] - a[1]);
256
- summaryLines.push(`${labels.pluginTimings}`);
257
- sortedTimings.forEach(([name, time]) => {
258
- const timeStr = time >= 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
259
- const barLength = Math.min(Math.ceil(time / 100), 10);
260
- const bar = styleText("dim", "█".repeat(barLength));
261
- summaryLines.push(`${styleText("dim", "•")} ${name.padEnd(maxLength + 1)}${bar} ${timeStr}`);
262
- });
412
+ //#region ../../internals/utils/src/linters.ts
413
+ /**
414
+ * Collects all files under `dir` recursively using Node's built-in fs APIs.
415
+ *
416
+ * Passing explicit file paths to oxlint (instead of a directory) bypasses
417
+ * oxlint's `.gitignore`-aware directory traversal, which would otherwise skip
418
+ * files that are listed in `.gitignore` (e.g. generated output directories).
419
+ */
420
+ function findLintableFiles(dir) {
421
+ try {
422
+ return readdirSync(dir, {
423
+ withFileTypes: true,
424
+ recursive: true
425
+ }).filter((d) => d.isFile()).map((d) => `${d.parentPath}/${d.name}`);
426
+ } catch {
427
+ return [];
263
428
  }
264
- summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`);
265
- return summaryLines;
266
429
  }
267
- //#endregion
268
- //#region src/utils/runHook.ts
269
430
  /**
270
- * Execute a hook command, emit debug/hook:end events, and forward output to
271
- * an optional HookOutputSink. All three logger adapters share this function
272
- * so the process-spawning logic lives in exactly one place.
431
+ * CLI command descriptors for each supported linter.
432
+ *
433
+ * Each entry contains the executable `command`, an `args` factory that maps an
434
+ * output path to the correct argument list, and an `errorMessage` shown when
435
+ * the linter is not found.
273
436
  */
274
- async function runHook({ id, command, args, commandWithArgs, context, stream = false, sink }) {
275
- try {
276
- const proc = x(command, [...args ?? []], {
277
- nodeOptions: { detached: true },
278
- throwOnError: true
279
- });
280
- if (stream && sink?.onLine) for await (const line of proc) sink.onLine(line);
281
- const result = await proc;
282
- await context.emit("debug", {
283
- date: /* @__PURE__ */ new Date(),
284
- logs: [result.stdout.trimEnd()]
285
- });
286
- await context.emit("hook:end", {
287
- command,
288
- args,
289
- id,
290
- success: true,
291
- error: null
292
- });
293
- } catch (err) {
294
- if (!(err instanceof NonZeroExitError)) {
295
- await context.emit("hook:end", {
296
- command,
297
- args,
298
- id,
299
- success: false,
300
- error: toError(err)
301
- });
302
- await context.emit("error", toError(err));
303
- return;
304
- }
305
- const stderr = err.output?.stderr ?? "";
306
- const stdout = err.output?.stdout ?? "";
307
- await context.emit("debug", {
308
- date: /* @__PURE__ */ new Date(),
309
- logs: [stdout, stderr].filter(Boolean)
310
- });
311
- if (stderr) sink?.onStderr?.(stderr);
312
- if (stdout) sink?.onStdout?.(stdout);
313
- const errorMessage = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
314
- await context.emit("hook:end", {
315
- command,
316
- args,
317
- id,
318
- success: false,
319
- error: errorMessage
320
- });
321
- await context.emit("error", errorMessage);
437
+ const linters = {
438
+ eslint: {
439
+ command: "eslint",
440
+ args: (outputPath) => [outputPath, "--fix"],
441
+ errorMessage: "Eslint not found"
442
+ },
443
+ biome: {
444
+ command: "biome",
445
+ args: (outputPath) => [
446
+ "lint",
447
+ "--fix",
448
+ outputPath
449
+ ],
450
+ errorMessage: "Biome not found"
451
+ },
452
+ oxlint: {
453
+ command: "oxlint",
454
+ args: (outputPath) => ["--fix", ...findLintableFiles(outputPath)],
455
+ errorMessage: "Oxlint not found"
322
456
  }
457
+ };
458
+ async function isLinterAvailable(linter) {
459
+ return new Promise((resolve) => {
460
+ const child = spawn(linter, ["--version"], { stdio: "ignore" });
461
+ child.on("close", (code) => resolve(code === 0));
462
+ child.on("error", () => resolve(false));
463
+ });
464
+ }
465
+ /**
466
+ * Detects the first available linter on the current system.
467
+ *
468
+ * - Checks in preference order: `oxlint`, `biome`, `eslint`.
469
+ * - Returns `null` when none are found.
470
+ *
471
+ * @example
472
+ * ```ts
473
+ * const linter = await detectLinter()
474
+ * if (linter) {
475
+ * console.log(`Using ${linter} for linting`)
476
+ * }
477
+ * ```
478
+ */
479
+ async function detectLinter() {
480
+ const linterNames = new Set([
481
+ "oxlint",
482
+ "biome",
483
+ "eslint"
484
+ ]);
485
+ for (const linter of linterNames) if (await isLinterAvailable(linter)) return linter;
486
+ return null;
323
487
  }
324
488
  //#endregion
325
- //#region src/utils/Writables.ts
489
+ //#region src/loggers/clackLogger.ts
490
+ /**
491
+ * Node.js `Writable` stream that forwards each chunk to a clack `taskLog` message.
492
+ * Used to pipe hook subprocess output into the clack task log UI.
493
+ */
326
494
  var ClackWritable = class extends Writable {
327
495
  taskLog;
328
496
  constructor(taskLog, opts) {
@@ -334,11 +502,8 @@ var ClackWritable = class extends Writable {
334
502
  callback();
335
503
  }
336
504
  };
337
- //#endregion
338
- //#region src/loggers/clackLogger.ts
339
505
  /**
340
- * Clack adapter for local TTY environments
341
- * Provides a beautiful CLI UI with flat structure inspired by Claude's CLI patterns
506
+ * TTY logger with beautiful UI and progress indicators for local development.
342
507
  */
343
508
  const clackLogger = defineLogger({
344
509
  name: "clack",
@@ -386,7 +551,7 @@ const clackLogger = defineLogger({
386
551
  state.spinner.stop(text);
387
552
  state.isSpinning = false;
388
553
  }
389
- context.on("info", (message, info = "") => {
554
+ context.on("kubb:info", ({ message, info = "" }) => {
390
555
  if (logLevel$8 <= logLevel.silent) return;
391
556
  const text = getMessage([
392
557
  styleText("blue", "ℹ"),
@@ -396,7 +561,7 @@ const clackLogger = defineLogger({
396
561
  if (state.isSpinning) state.spinner.message(text);
397
562
  else clack.log.info(text);
398
563
  });
399
- context.on("success", (message, info = "") => {
564
+ context.on("kubb:success", ({ message, info = "" }) => {
400
565
  if (logLevel$8 <= logLevel.silent) return;
401
566
  const text = getMessage([
402
567
  styleText("blue", "✓"),
@@ -406,7 +571,7 @@ const clackLogger = defineLogger({
406
571
  if (state.isSpinning) stopSpinner(text);
407
572
  else clack.log.success(text);
408
573
  });
409
- context.on("warn", (message, info) => {
574
+ context.on("kubb:warn", ({ message, info }) => {
410
575
  if (logLevel$8 < logLevel.warn) return;
411
576
  const text = getMessage([
412
577
  styleText("yellow", "⚠"),
@@ -415,7 +580,7 @@ const clackLogger = defineLogger({
415
580
  ].filter(Boolean).join(" "));
416
581
  clack.log.warn(text);
417
582
  });
418
- context.on("error", (error) => {
583
+ context.on("kubb:error", ({ error }) => {
419
584
  const caused = toCause(error);
420
585
  const text = [styleText("red", "✗"), error.message].join(" ");
421
586
  if (state.isSpinning) stopSpinner(getMessage(text));
@@ -430,19 +595,24 @@ const clackLogger = defineLogger({
430
595
  }
431
596
  }
432
597
  });
433
- context.on("version:new", (version, latestVersion) => {
598
+ context.on("kubb:version:new", ({ currentVersion, latestVersion }) => {
434
599
  if (logLevel$8 <= logLevel.silent) return;
435
- clack.box(`\`v${version}\` → \`v${latestVersion}\`
600
+ try {
601
+ clack.box(`\`v${currentVersion}\` → \`v${latestVersion}\`
436
602
  Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
437
- width: "auto",
438
- formatBorder: (s) => styleText("yellow", s),
439
- rounded: true,
440
- withGuide: false,
441
- contentAlign: "center",
442
- titleAlign: "center"
443
- });
603
+ width: "auto",
604
+ formatBorder: (s) => styleText("yellow", s),
605
+ rounded: true,
606
+ withGuide: false,
607
+ contentAlign: "center",
608
+ titleAlign: "center"
609
+ });
610
+ } catch {
611
+ console.log(`Update available for Kubb: v${currentVersion} → v${latestVersion}`);
612
+ console.log("Run `npm install -g @kubb/cli` to update");
613
+ }
444
614
  });
445
- context.on("lifecycle:start", async (version) => {
615
+ context.on("kubb:lifecycle:start", async ({ version }) => {
446
616
  console.log(`\n${getIntro({
447
617
  title: "The ultimate toolkit for working with APIs",
448
618
  description: "Ready to start",
@@ -451,24 +621,24 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
451
621
  })}\n`);
452
622
  reset();
453
623
  });
454
- context.on("config:start", () => {
624
+ context.on("kubb:config:start", () => {
455
625
  if (logLevel$8 <= logLevel.silent) return;
456
626
  const text = getMessage("Configuration started");
457
627
  clack.intro(text);
458
628
  startSpinner(getMessage("Configuration loading"));
459
629
  });
460
- context.on("config:end", (_configs) => {
630
+ context.on("kubb:config:end", () => {
461
631
  if (logLevel$8 <= logLevel.silent) return;
462
632
  const text = getMessage("Configuration completed");
463
633
  clack.outro(text);
464
634
  });
465
- context.on("generation:start", (config) => {
635
+ context.on("kubb:generation:start", ({ config }) => {
466
636
  reset();
467
637
  state.totalPlugins = config.plugins?.length ?? 0;
468
638
  const text = getMessage(["Generation started", config.name ? `for ${styleText("dim", config.name)}` : void 0].filter(Boolean).join(" "));
469
639
  clack.intro(text);
470
640
  });
471
- context.on("plugin:start", (plugin) => {
641
+ context.on("kubb:plugin:start", ({ plugin }) => {
472
642
  if (logLevel$8 <= logLevel.silent) return;
473
643
  stopSpinner();
474
644
  const progressBar = clack.progress({
@@ -486,7 +656,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
486
656
  interval
487
657
  });
488
658
  });
489
- context.on("plugin:end", (plugin, { duration, success }) => {
659
+ context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
490
660
  stopSpinner();
491
661
  const active = state.activeProgress.get(plugin.name);
492
662
  if (!active || logLevel$8 === logLevel.silent) return;
@@ -499,7 +669,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
499
669
  state.activeProgress.delete(plugin.name);
500
670
  showProgressStep();
501
671
  });
502
- context.on("files:processing:start", (files) => {
672
+ context.on("kubb:files:processing:start", ({ files }) => {
503
673
  if (logLevel$8 <= logLevel.silent) return;
504
674
  stopSpinner();
505
675
  state.totalFiles = files.length;
@@ -510,11 +680,11 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
510
680
  max: files.length,
511
681
  size: 30
512
682
  });
513
- context.emit("info", text);
683
+ context.emit("kubb:info", { message: text });
514
684
  progressBar.start(getMessage(text));
515
685
  state.activeProgress.set("files", { progressBar });
516
686
  });
517
- context.on("file:processing:update", ({ file, config }) => {
687
+ context.on("kubb:file:processing:update", ({ file, config }) => {
518
688
  if (logLevel$8 <= logLevel.silent) return;
519
689
  stopSpinner();
520
690
  state.processedFiles++;
@@ -523,7 +693,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
523
693
  if (!active) return;
524
694
  active.progressBar.advance(void 0, text);
525
695
  });
526
- context.on("files:processing:end", () => {
696
+ context.on("kubb:files:processing:end", () => {
527
697
  if (logLevel$8 <= logLevel.silent) return;
528
698
  stopSpinner();
529
699
  const text = getMessage("Files written successfully");
@@ -533,71 +703,41 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
533
703
  state.activeProgress.delete("files");
534
704
  showProgressStep();
535
705
  });
536
- context.on("generation:end", (config) => {
706
+ context.on("kubb:generation:end", ({ config }) => {
537
707
  const text = getMessage(config.name ? `Generation completed for ${styleText("dim", config.name)}` : "Generation completed");
538
708
  clack.outro(text);
539
709
  });
540
- context.on("format:start", () => {
710
+ context.on("kubb:format:start", () => {
541
711
  if (logLevel$8 <= logLevel.silent) return;
542
712
  const text = getMessage("Format started");
543
713
  clack.intro(text);
544
714
  });
545
- context.on("format:end", () => {
715
+ context.on("kubb:format:end", () => {
546
716
  if (logLevel$8 <= logLevel.silent) return;
547
717
  const text = getMessage("Format completed");
548
718
  clack.outro(text);
549
719
  });
550
- context.on("lint:start", () => {
720
+ context.on("kubb:lint:start", () => {
551
721
  if (logLevel$8 <= logLevel.silent) return;
552
722
  const text = getMessage("Lint started");
553
723
  clack.intro(text);
554
724
  });
555
- context.on("lint:end", () => {
725
+ context.on("kubb:lint:end", () => {
556
726
  if (logLevel$8 <= logLevel.silent) return;
557
727
  const text = getMessage("Lint completed");
558
728
  clack.outro(text);
559
729
  });
560
- context.on("hook:start", async ({ id, command, args }) => {
561
- const commandWithArgs = formatCommandWithArgs(command, args);
562
- const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} started`);
563
- if (!id) return;
564
- if (logLevel$8 <= logLevel.silent) {
565
- await runHook({
566
- id,
567
- command,
568
- args,
569
- commandWithArgs,
570
- context,
571
- sink: {
572
- onStderr: (s) => console.error(s),
573
- onStdout: (s) => console.log(s)
574
- }
575
- });
576
- return;
577
- }
730
+ context.on("kubb:hook:start", ({ command, args }) => {
731
+ if (logLevel$8 <= logLevel.silent) return;
732
+ const text = getMessage(`Hook ${styleText("dim", formatCommandWithArgs(command, args))} started`);
578
733
  clack.intro(text);
579
- const logger = clack.taskLog({ title: getMessage(["Executing hook", logLevel$8 >= logLevel.info ? styleText("dim", commandWithArgs) : void 0].filter(Boolean).join(" ")) });
580
- const writable = new ClackWritable(logger);
581
- await runHook({
582
- id,
583
- command,
584
- args,
585
- commandWithArgs,
586
- context,
587
- stream: true,
588
- sink: {
589
- onLine: (line) => writable.write(line),
590
- onStderr: (s) => logger.error(s),
591
- onStdout: (s) => logger.message(s)
592
- }
593
- });
594
734
  });
595
- context.on("hook:end", ({ command, args }) => {
735
+ context.on("kubb:hook:end", ({ command, args }) => {
596
736
  if (logLevel$8 <= logLevel.silent) return;
597
737
  const text = getMessage(`Hook ${styleText("dim", formatCommandWithArgs(command, args))} successfully executed`);
598
738
  clack.outro(text);
599
739
  });
600
- context.on("generation:summary", (config, { pluginTimings, failedPlugins, filesCreated, status, hrStart }) => {
740
+ context.on("kubb:generation:summary", ({ config, pluginTimings, failedPlugins, filesCreated, status, hrStart }) => {
601
741
  const summary = getSummary({
602
742
  failedPlugins,
603
743
  filesCreated,
@@ -610,28 +750,44 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
610
750
  summary.unshift("\n");
611
751
  summary.push("\n");
612
752
  const borderColor = status === "success" ? "green" : "red";
613
- clack.box(summary.join("\n"), getMessage(title), {
614
- width: "auto",
615
- formatBorder: (s) => styleText(borderColor, s),
616
- rounded: true,
617
- withGuide: false,
618
- contentAlign: "left",
619
- titleAlign: "center"
620
- });
753
+ try {
754
+ clack.box(summary.join("\n"), getMessage(title), {
755
+ width: "auto",
756
+ formatBorder: (s) => styleText(borderColor, s),
757
+ rounded: true,
758
+ withGuide: false,
759
+ contentAlign: "left",
760
+ titleAlign: "center"
761
+ });
762
+ } catch {
763
+ console.log(summary.join("\n"));
764
+ }
621
765
  });
622
- context.on("lifecycle:end", () => {
766
+ context.on("kubb:lifecycle:end", () => {
623
767
  reset();
624
768
  });
769
+ return (commandWithArgs) => {
770
+ if (logLevel$8 <= logLevel.silent) return {
771
+ onStdout: (s) => console.log(s),
772
+ onStderr: (s) => console.error(s)
773
+ };
774
+ const logger = clack.taskLog({ title: getMessage(["Executing hook", logLevel$8 >= logLevel.info ? styleText("dim", commandWithArgs) : void 0].filter(Boolean).join(" ")) });
775
+ const writable = new ClackWritable(logger);
776
+ return {
777
+ stream: true,
778
+ onLine: (line) => writable.write(line),
779
+ onStdout: (s) => logger.message(s),
780
+ onStderr: (s) => logger.error(s)
781
+ };
782
+ };
625
783
  }
626
784
  });
627
785
  //#endregion
628
786
  //#region src/loggers/fileSystemLogger.ts
629
787
  /**
630
- * FileSystem logger for debug log persistence
631
- * Captures debug and verbose events and writes them to files in .kubb directory
788
+ * FileSystem logger that captures debug events and writes them to `.kubb` directory files.
632
789
  *
633
- * Note: Logs are written on lifecycle:end or process exit. If the process crashes
634
- * before these events, some cached logs may be lost.
790
+ * @note Logs are written on `kubb:lifecycle:end` or process exit. Cached logs may be lost if the process crashes before either event.
635
791
  */
636
792
  const fileSystemLogger = defineLogger({
637
793
  name: "filesystem",
@@ -656,67 +812,73 @@ const fileSystemLogger = defineLogger({
656
812
  const pathName = resolve(process$1.cwd(), ".kubb", baseName);
657
813
  if (!files[pathName]) files[pathName] = [];
658
814
  if (log.logs.length > 0) {
659
- const timestamp = log.date.toLocaleString();
660
- files[pathName].push(`[${timestamp}]\n${log.logs.join("\n")}`);
815
+ const prefix = `[${log.date.toLocaleString()}] `;
816
+ const indent = " ".repeat(prefix.length);
817
+ const [first, ...rest] = log.logs;
818
+ files[pathName].push([prefix + first, ...rest.map((line) => indent + line)].join("\n"));
661
819
  }
662
820
  }
663
- await Promise.all(Object.entries(files).map(([fileName, logs]) => write(fileName, logs.join("\n\n"))));
821
+ for (const [fileName, logs] of Object.entries(files)) await write(fileName, logs.join("\n"));
664
822
  return Object.keys(files);
665
823
  }
666
- context.on("info", (message, info) => {
824
+ context.on("kubb:info", ({ message, info }) => {
667
825
  state.cachedLogs.add({
668
826
  date: /* @__PURE__ */ new Date(),
669
- logs: [`ℹ ${message} ${info}`]
827
+ logs: [`ℹ ${[message, info].filter(Boolean).join(" ")}`]
670
828
  });
671
829
  });
672
- context.on("success", (message, info) => {
830
+ context.on("kubb:success", ({ message, info }) => {
673
831
  state.cachedLogs.add({
674
832
  date: /* @__PURE__ */ new Date(),
675
- logs: [`✓ ${message} ${info}`]
833
+ logs: [`✓ ${[message, info].filter(Boolean).join(" ")}`]
676
834
  });
677
835
  });
678
- context.on("warn", (message, info) => {
836
+ context.on("kubb:warn", ({ message, info }) => {
679
837
  state.cachedLogs.add({
680
838
  date: /* @__PURE__ */ new Date(),
681
- logs: [`⚠ ${message} ${info}`]
839
+ logs: [`⚠ ${[message, info].filter(Boolean).join(" ")}`]
682
840
  });
683
841
  });
684
- context.on("error", (error) => {
842
+ context.on("kubb:error", ({ error }) => {
685
843
  state.cachedLogs.add({
686
844
  date: /* @__PURE__ */ new Date(),
687
845
  logs: [`✗ ${error.message}`, error.stack || "unknown stack"]
688
846
  });
689
847
  });
690
- context.on("debug", (message) => {
848
+ context.on("kubb:debug", ({ date, fileName, logs }) => {
691
849
  state.cachedLogs.add({
692
- date: /* @__PURE__ */ new Date(),
693
- logs: message.logs
850
+ date,
851
+ fileName,
852
+ logs
694
853
  });
695
854
  });
696
- context.on("plugin:start", (plugin) => {
855
+ context.on("kubb:plugin:start", ({ plugin }) => {
697
856
  state.cachedLogs.add({
698
857
  date: /* @__PURE__ */ new Date(),
699
- logs: [`Generating ${plugin.name}`]
858
+ logs: [`► Generating ${plugin.name}`]
700
859
  });
701
860
  });
702
- context.on("plugin:end", (plugin, { duration, success }) => {
861
+ context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
703
862
  const durationStr = formatMs(duration);
704
863
  state.cachedLogs.add({
705
864
  date: /* @__PURE__ */ new Date(),
706
- logs: [success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`]
865
+ logs: [success ? `✓ ${plugin.name} completed in ${durationStr}` : `✗ ${plugin.name} failed in ${durationStr}`]
707
866
  });
708
867
  });
709
- context.on("files:processing:start", (files) => {
868
+ context.on("kubb:files:processing:start", ({ files }) => {
710
869
  state.cachedLogs.add({
711
870
  date: /* @__PURE__ */ new Date(),
712
- logs: [`Start ${files.length} writing:`, ...files.map((file) => file.path)]
871
+ logs: [`► Writing ${files.length} files`, ...files.map((file) => ` ${file.path}`)]
713
872
  });
714
873
  });
715
- context.on("generation:end", async (config) => {
874
+ context.on("kubb:generation:end", async ({ config }) => {
716
875
  const writtenFilePaths = await writeLogs(config.name);
717
876
  if (writtenFilePaths.length > 0) {
718
877
  const files = writtenFilePaths.map((f) => relative(process$1.cwd(), f));
719
- await context.emit("info", "Debug files written to:", files.join(", "));
878
+ await context.emit("kubb:info", {
879
+ message: "Debug files written to:",
880
+ info: files.join(", ")
881
+ });
720
882
  }
721
883
  reset();
722
884
  });
@@ -731,8 +893,7 @@ const fileSystemLogger = defineLogger({
731
893
  //#endregion
732
894
  //#region src/loggers/githubActionsLogger.ts
733
895
  /**
734
- * GitHub Actions adapter for CI environments
735
- * Uses Github group annotations for collapsible sections
896
+ * GitHub Actions logger using group annotations for collapsible sections in CI.
736
897
  */
737
898
  const githubActionsLogger = defineLogger({
738
899
  name: "github-actions",
@@ -770,7 +931,7 @@ const githubActionsLogger = defineLogger({
770
931
  function closeGroup(_name) {
771
932
  console.log("::endgroup::");
772
933
  }
773
- context.on("info", (message, info = "") => {
934
+ context.on("kubb:info", ({ message, info = "" }) => {
774
935
  if (logLevel$7 <= logLevel.silent) return;
775
936
  const text = getMessage([
776
937
  styleText("blue", "ℹ"),
@@ -779,7 +940,7 @@ const githubActionsLogger = defineLogger({
779
940
  ].join(" "));
780
941
  console.log(text);
781
942
  });
782
- context.on("success", (message, info = "") => {
943
+ context.on("kubb:success", ({ message, info = "" }) => {
783
944
  if (logLevel$7 <= logLevel.silent) return;
784
945
  const text = getMessage([
785
946
  styleText("blue", "✓"),
@@ -788,7 +949,7 @@ const githubActionsLogger = defineLogger({
788
949
  ].filter(Boolean).join(" "));
789
950
  console.log(text);
790
951
  });
791
- context.on("warn", (message, info = "") => {
952
+ context.on("kubb:warn", ({ message, info = "" }) => {
792
953
  if (logLevel$7 <= logLevel.silent) return;
793
954
  const text = getMessage([
794
955
  styleText("yellow", "⚠"),
@@ -797,7 +958,7 @@ const githubActionsLogger = defineLogger({
797
958
  ].filter(Boolean).join(" "));
798
959
  console.warn(`::warning::${text}`);
799
960
  });
800
- context.on("error", (error) => {
961
+ context.on("kubb:error", ({ error }) => {
801
962
  const caused = toCause(error);
802
963
  if (logLevel$7 <= logLevel.silent) return;
803
964
  const message = error.message || String(error);
@@ -812,37 +973,37 @@ const githubActionsLogger = defineLogger({
812
973
  }
813
974
  }
814
975
  });
815
- context.on("lifecycle:start", (version) => {
976
+ context.on("kubb:lifecycle:start", ({ version }) => {
816
977
  console.log(styleText("yellow", `Kubb ${version} 🧩`));
817
978
  reset();
818
979
  });
819
- context.on("config:start", () => {
980
+ context.on("kubb:config:start", () => {
820
981
  if (logLevel$7 <= logLevel.silent) return;
821
982
  const text = getMessage("Configuration started");
822
983
  openGroup("Configuration");
823
984
  console.log(text);
824
985
  });
825
- context.on("config:end", (configs) => {
986
+ context.on("kubb:config:end", ({ configs }) => {
826
987
  state.currentConfigs = configs;
827
988
  if (logLevel$7 <= logLevel.silent) return;
828
989
  const text = getMessage("Configuration completed");
829
990
  console.log(text);
830
991
  closeGroup("Configuration");
831
992
  });
832
- context.on("generation:start", (config) => {
993
+ context.on("kubb:generation:start", ({ config }) => {
833
994
  reset();
834
995
  state.totalPlugins = config.plugins?.length ?? 0;
835
996
  const text = config.name ? `Generation for ${styleText("bold", config.name)}` : "Generation";
836
997
  if (state.currentConfigs.length > 1) openGroup(text);
837
998
  if (state.currentConfigs.length === 1) console.log(getMessage(text));
838
999
  });
839
- context.on("plugin:start", (plugin) => {
1000
+ context.on("kubb:plugin:start", ({ plugin }) => {
840
1001
  if (logLevel$7 <= logLevel.silent) return;
841
1002
  const text = getMessage(`Generating ${styleText("bold", plugin.name)}`);
842
1003
  if (state.currentConfigs.length === 1) openGroup(`Plugin: ${plugin.name}`);
843
1004
  console.log(text);
844
1005
  });
845
- context.on("plugin:end", (plugin, { duration, success }) => {
1006
+ context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
846
1007
  if (logLevel$7 <= logLevel.silent) return;
847
1008
  if (success) state.completedPlugins++;
848
1009
  else state.failedPlugins++;
@@ -853,7 +1014,7 @@ const githubActionsLogger = defineLogger({
853
1014
  if (state.currentConfigs.length === 1) closeGroup(`Plugin: ${plugin.name}`);
854
1015
  showProgressStep();
855
1016
  });
856
- context.on("files:processing:start", (files) => {
1017
+ context.on("kubb:files:processing:start", ({ files }) => {
857
1018
  if (logLevel$7 <= logLevel.silent) return;
858
1019
  state.totalFiles = files.length;
859
1020
  state.processedFiles = 0;
@@ -861,73 +1022,60 @@ const githubActionsLogger = defineLogger({
861
1022
  const text = getMessage(`Writing ${files.length} files`);
862
1023
  console.log(text);
863
1024
  });
864
- context.on("files:processing:end", () => {
1025
+ context.on("kubb:files:processing:end", () => {
865
1026
  if (logLevel$7 <= logLevel.silent) return;
866
1027
  const text = getMessage("Files written successfully");
867
1028
  console.log(text);
868
1029
  if (state.currentConfigs.length === 1) closeGroup("File Generation");
869
1030
  showProgressStep();
870
1031
  });
871
- context.on("file:processing:update", () => {
1032
+ context.on("kubb:file:processing:update", () => {
872
1033
  if (logLevel$7 <= logLevel.silent) return;
873
1034
  state.processedFiles++;
874
1035
  });
875
- context.on("generation:end", (config) => {
1036
+ context.on("kubb:generation:end", ({ config }) => {
876
1037
  const text = getMessage(config.name ? `${styleText("blue", "✓")} Generation completed for ${styleText("dim", config.name)}` : `${styleText("blue", "✓")} Generation completed`);
877
1038
  console.log(text);
878
1039
  });
879
- context.on("format:start", () => {
1040
+ context.on("kubb:format:start", () => {
880
1041
  if (logLevel$7 <= logLevel.silent) return;
881
1042
  const text = getMessage("Format started");
882
1043
  if (state.currentConfigs.length === 1) openGroup("Formatting");
883
1044
  console.log(text);
884
1045
  });
885
- context.on("format:end", () => {
1046
+ context.on("kubb:format:end", () => {
886
1047
  if (logLevel$7 <= logLevel.silent) return;
887
1048
  const text = getMessage("Format completed");
888
1049
  console.log(text);
889
1050
  if (state.currentConfigs.length === 1) closeGroup("Formatting");
890
1051
  });
891
- context.on("lint:start", () => {
1052
+ context.on("kubb:lint:start", () => {
892
1053
  if (logLevel$7 <= logLevel.silent) return;
893
1054
  const text = getMessage("Lint started");
894
1055
  if (state.currentConfigs.length === 1) openGroup("Linting");
895
1056
  console.log(text);
896
1057
  });
897
- context.on("lint:end", () => {
1058
+ context.on("kubb:lint:end", () => {
898
1059
  if (logLevel$7 <= logLevel.silent) return;
899
1060
  const text = getMessage("Lint completed");
900
1061
  console.log(text);
901
1062
  if (state.currentConfigs.length === 1) closeGroup("Linting");
902
1063
  });
903
- context.on("hook:start", async ({ id, command, args }) => {
1064
+ context.on("kubb:hook:start", ({ command, args }) => {
1065
+ if (logLevel$7 <= logLevel.silent) return;
904
1066
  const commandWithArgs = formatCommandWithArgs(command, args);
905
1067
  const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} started`);
906
- if (logLevel$7 > logLevel.silent) {
907
- if (state.currentConfigs.length === 1) openGroup(`Hook ${commandWithArgs}`);
908
- console.log(text);
909
- }
910
- if (!id) return;
911
- await runHook({
912
- id,
913
- command,
914
- args,
915
- commandWithArgs,
916
- context,
917
- sink: {
918
- onStdout: logLevel$7 > logLevel.silent ? (s) => console.log(s) : void 0,
919
- onStderr: logLevel$7 > logLevel.silent ? (s) => console.error(`::error::${s}`) : void 0
920
- }
921
- });
1068
+ if (state.currentConfigs.length === 1) openGroup(`Hook ${commandWithArgs}`);
1069
+ console.log(text);
922
1070
  });
923
- context.on("hook:end", ({ command, args }) => {
1071
+ context.on("kubb:hook:end", ({ command, args }) => {
924
1072
  if (logLevel$7 <= logLevel.silent) return;
925
1073
  const commandWithArgs = formatCommandWithArgs(command, args);
926
1074
  const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} completed`);
927
1075
  console.log(text);
928
1076
  if (state.currentConfigs.length === 1) closeGroup(`Hook ${commandWithArgs}`);
929
1077
  });
930
- context.on("generation:summary", (config, { status, hrStart, failedPlugins }) => {
1078
+ context.on("kubb:generation:summary", ({ config, status, hrStart, failedPlugins }) => {
931
1079
  const pluginsCount = config.plugins?.length ?? 0;
932
1080
  const successCount = pluginsCount - failedPlugins.size;
933
1081
  const duration = formatHrtime(hrStart);
@@ -935,16 +1083,19 @@ const githubActionsLogger = defineLogger({
935
1083
  console.log(status === "success" ? `Kubb Summary: ${styleText("blue", "✓")} ${`${successCount} successful`}, ${pluginsCount} total, ${styleText("green", duration)}` : `Kubb Summary: ${styleText("blue", "✓")} ${`${successCount} successful`}, ✗ ${`${failedPlugins.size} failed`}, ${pluginsCount} total, ${styleText("green", duration)}`);
936
1084
  if (state.currentConfigs.length > 1) closeGroup(config.name ? `Generation for ${styleText("bold", config.name)}` : "Generation");
937
1085
  });
938
- context.on("lifecycle:end", () => {
1086
+ context.on("kubb:lifecycle:end", () => {
939
1087
  reset();
940
1088
  });
1089
+ return (_commandWithArgs) => ({
1090
+ onStdout: logLevel$7 > logLevel.silent ? (s) => console.log(s) : void 0,
1091
+ onStderr: logLevel$7 > logLevel.silent ? (s) => console.error(`::error::${s}`) : void 0
1092
+ });
941
1093
  }
942
1094
  });
943
1095
  //#endregion
944
1096
  //#region src/loggers/plainLogger.ts
945
1097
  /**
946
- * Plain console adapter for non-TTY environments
947
- * Simple console.log output with indentation
1098
+ * Plain console adapter for non-TTY environments with simple `console.log` output.
948
1099
  */
949
1100
  const plainLogger = defineLogger({
950
1101
  name: "plain",
@@ -953,7 +1104,7 @@ const plainLogger = defineLogger({
953
1104
  function getMessage(message) {
954
1105
  return formatMessage(message, logLevel$6);
955
1106
  }
956
- context.on("info", (message, info) => {
1107
+ context.on("kubb:info", ({ message, info }) => {
957
1108
  if (logLevel$6 <= logLevel.silent) return;
958
1109
  const text = getMessage([
959
1110
  "ℹ",
@@ -962,7 +1113,7 @@ const plainLogger = defineLogger({
962
1113
  ].join(" "));
963
1114
  console.log(text);
964
1115
  });
965
- context.on("success", (message, info = "") => {
1116
+ context.on("kubb:success", ({ message, info = "" }) => {
966
1117
  if (logLevel$6 <= logLevel.silent) return;
967
1118
  const text = getMessage([
968
1119
  "✓",
@@ -971,7 +1122,7 @@ const plainLogger = defineLogger({
971
1122
  ].filter(Boolean).join(" "));
972
1123
  console.log(text);
973
1124
  });
974
- context.on("warn", (message, info) => {
1125
+ context.on("kubb:warn", ({ message, info }) => {
975
1126
  if (logLevel$6 < logLevel.warn) return;
976
1127
  const text = getMessage([
977
1128
  "⚠",
@@ -980,7 +1131,7 @@ const plainLogger = defineLogger({
980
1131
  ].filter(Boolean).join(" "));
981
1132
  console.log(text);
982
1133
  });
983
- context.on("error", (error) => {
1134
+ context.on("kubb:error", ({ error }) => {
984
1135
  const caused = toCause(error);
985
1136
  const text = getMessage(["✗", error.message].join(" "));
986
1137
  console.log(text);
@@ -994,96 +1145,84 @@ const plainLogger = defineLogger({
994
1145
  }
995
1146
  }
996
1147
  });
997
- context.on("lifecycle:start", () => {
1148
+ context.on("kubb:lifecycle:start", () => {
998
1149
  console.log("Kubb CLI 🧩");
999
1150
  });
1000
- context.on("config:start", () => {
1151
+ context.on("kubb:config:start", () => {
1001
1152
  if (logLevel$6 <= logLevel.silent) return;
1002
1153
  const text = getMessage("Configuration started");
1003
1154
  console.log(text);
1004
1155
  });
1005
- context.on("config:end", () => {
1156
+ context.on("kubb:config:end", () => {
1006
1157
  if (logLevel$6 <= logLevel.silent) return;
1007
1158
  const text = getMessage("Configuration completed");
1008
1159
  console.log(text);
1009
1160
  });
1010
- context.on("generation:start", () => {
1161
+ context.on("kubb:generation:start", () => {
1011
1162
  const text = getMessage("Generation started");
1012
1163
  console.log(text);
1013
1164
  });
1014
- context.on("plugin:start", (plugin) => {
1165
+ context.on("kubb:plugin:start", ({ plugin }) => {
1015
1166
  if (logLevel$6 <= logLevel.silent) return;
1016
1167
  const text = getMessage(`Generating ${plugin.name}`);
1017
1168
  console.log(text);
1018
1169
  });
1019
- context.on("plugin:end", (plugin, { duration, success }) => {
1170
+ context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
1020
1171
  if (logLevel$6 <= logLevel.silent) return;
1021
1172
  const durationStr = formatMs(duration);
1022
1173
  const text = getMessage(success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`);
1023
1174
  console.log(text);
1024
1175
  });
1025
- context.on("files:processing:start", (files) => {
1176
+ context.on("kubb:files:processing:start", ({ files }) => {
1026
1177
  if (logLevel$6 <= logLevel.silent) return;
1027
1178
  const text = getMessage(`Writing ${files.length} files`);
1028
1179
  console.log(text);
1029
1180
  });
1030
- context.on("file:processing:update", ({ file, config }) => {
1181
+ context.on("kubb:file:processing:update", ({ file, config }) => {
1031
1182
  if (logLevel$6 <= logLevel.silent) return;
1032
1183
  const text = getMessage(`Writing ${relative(config.root, file.path)}`);
1033
1184
  console.log(text);
1034
1185
  });
1035
- context.on("files:processing:end", () => {
1186
+ context.on("kubb:files:processing:end", () => {
1036
1187
  if (logLevel$6 <= logLevel.silent) return;
1037
1188
  const text = getMessage("Files written successfully");
1038
1189
  console.log(text);
1039
1190
  });
1040
- context.on("generation:end", (config) => {
1191
+ context.on("kubb:generation:end", ({ config }) => {
1041
1192
  const text = getMessage(config.name ? `Generation completed for ${config.name}` : "Generation completed");
1042
1193
  console.log(text);
1043
1194
  });
1044
- context.on("format:start", () => {
1195
+ context.on("kubb:format:start", () => {
1045
1196
  if (logLevel$6 <= logLevel.silent) return;
1046
1197
  const text = getMessage("Format started");
1047
1198
  console.log(text);
1048
1199
  });
1049
- context.on("format:end", () => {
1200
+ context.on("kubb:format:end", () => {
1050
1201
  if (logLevel$6 <= logLevel.silent) return;
1051
1202
  const text = getMessage("Format completed");
1052
1203
  console.log(text);
1053
1204
  });
1054
- context.on("lint:start", () => {
1205
+ context.on("kubb:lint:start", () => {
1055
1206
  if (logLevel$6 <= logLevel.silent) return;
1056
1207
  const text = getMessage("Lint started");
1057
1208
  console.log(text);
1058
1209
  });
1059
- context.on("lint:end", () => {
1210
+ context.on("kubb:lint:end", () => {
1060
1211
  if (logLevel$6 <= logLevel.silent) return;
1061
1212
  const text = getMessage("Lint completed");
1062
1213
  console.log(text);
1063
1214
  });
1064
- context.on("hook:start", async ({ id, command, args }) => {
1065
- const commandWithArgs = formatCommandWithArgs(command, args);
1066
- const text = getMessage(`Hook ${commandWithArgs} started`);
1067
- if (logLevel$6 > logLevel.silent) console.log(text);
1068
- if (!id) return;
1069
- await runHook({
1070
- id,
1071
- command,
1072
- args,
1073
- commandWithArgs,
1074
- context,
1075
- sink: {
1076
- onStdout: logLevel$6 > logLevel.silent ? (s) => console.log(s) : void 0,
1077
- onStderr: logLevel$6 > logLevel.silent ? (s) => console.error(s) : void 0
1078
- }
1079
- });
1215
+ context.on("kubb:hook:start", ({ command, args }) => {
1216
+ if (logLevel$6 <= logLevel.silent) return;
1217
+ const text = getMessage(`Hook ${formatCommandWithArgs(command, args)} started`);
1218
+ console.log(text);
1080
1219
  });
1081
- context.on("hook:end", ({ command, args }) => {
1220
+ context.on("kubb:hook:end", ({ command, args }) => {
1082
1221
  if (logLevel$6 <= logLevel.silent) return;
1083
1222
  const text = getMessage(`Hook ${formatCommandWithArgs(command, args)} completed`);
1084
1223
  console.log(text);
1085
1224
  });
1086
- context.on("generation:summary", (config, { pluginTimings, status, hrStart, failedPlugins, filesCreated }) => {
1225
+ context.on("kubb:generation:summary", ({ config, pluginTimings, status, hrStart, failedPlugins, filesCreated }) => {
1087
1226
  const summary = getSummary({
1088
1227
  failedPlugins,
1089
1228
  filesCreated,
@@ -1096,6 +1235,10 @@ const plainLogger = defineLogger({
1096
1235
  console.log(summary.join("\n"));
1097
1236
  console.log(SUMMARY_SEPARATOR);
1098
1237
  });
1238
+ return (_commandWithArgs) => ({
1239
+ onStdout: logLevel$6 > logLevel.silent ? (s) => console.log(s) : void 0,
1240
+ onStderr: logLevel$6 > logLevel.silent ? (s) => console.error(s) : void 0
1241
+ });
1099
1242
  }
1100
1243
  });
1101
1244
  //#endregion
@@ -1150,310 +1293,446 @@ async function setupLogger(context, { logLevel: logLevel$5 }) {
1150
1293
  const type = detectLogger();
1151
1294
  const logger = logMapper[type];
1152
1295
  if (!logger) throw new Error(`Unknown adapter type: ${type}`);
1153
- const cleanup = await logger.install(context, { logLevel: logLevel$5 });
1296
+ const makeSink = await logger.install(context, { logLevel: logLevel$5 });
1154
1297
  if (logLevel$5 >= logLevel.debug) await fileSystemLogger.install(context, { logLevel: logLevel$5 });
1155
- return cleanup;
1298
+ return typeof makeSink === "function" ? makeSink : void 0;
1156
1299
  }
1157
- //#endregion
1158
- //#region src/utils/executeHooks.ts
1159
- async function executeHooks({ hooks, events }) {
1160
- const commands = Array.isArray(hooks.done) ? hooks.done : [hooks.done].filter(Boolean);
1161
- for (const command of commands) {
1162
- const [cmd, ...args] = tokenize(command);
1163
- if (!cmd) continue;
1164
- const hookId = createHash("sha256").update(command).digest("hex");
1165
- const hookEndPromise = new Promise((resolve, reject) => {
1166
- const handler = ({ id, success, error }) => {
1167
- if (id !== hookId) return;
1168
- events.off("hook:end", handler);
1169
- if (!success) {
1170
- reject(error ?? /* @__PURE__ */ new Error(`Hook failed: ${command}`));
1171
- return;
1172
- }
1173
- events.emit("success", `${styleText("dim", command)} successfully executed`).then(resolve).catch(reject);
1174
- };
1175
- events.on("hook:end", handler);
1176
- });
1177
- await events.emit("hook:start", {
1178
- id: hookId,
1179
- command: cmd,
1180
- args
1300
+ /**
1301
+ * Builds the generation summary lines rendered in the end-of-run box.
1302
+ * Returns an array of styled strings, one per summary row.
1303
+ */
1304
+ function getSummary({ failedPlugins, filesCreated, status, hrStart, config, pluginTimings }) {
1305
+ const duration = formatHrtime(hrStart);
1306
+ const pluginsCount = config.plugins?.length ?? 0;
1307
+ const successCount = pluginsCount - failedPlugins.size;
1308
+ const meta = {
1309
+ plugins: status === "success" ? `${styleText("green", `${successCount} successful`)}, ${pluginsCount} total` : `${styleText("green", `${successCount} successful`)}, ${styleText("red", `${failedPlugins.size} failed`)}, ${pluginsCount} total`,
1310
+ pluginsFailed: status === "failed" ? [...failedPlugins].map(({ plugin }) => randomCliColor(plugin.name)).join(", ") : void 0,
1311
+ filesCreated,
1312
+ time: styleText("green", duration),
1313
+ output: path.resolve(config.root, config.output.path)
1314
+ };
1315
+ const labels = {
1316
+ plugins: "Plugins:",
1317
+ failed: "Failed:",
1318
+ generated: "Generated:",
1319
+ pluginTimings: "Plugin Timings:",
1320
+ output: "Output:"
1321
+ };
1322
+ const maxLength = Math.max(0, ...[...Object.values(labels), ...pluginTimings ? Array.from(pluginTimings.keys()) : []].map((s) => s.length));
1323
+ const summaryLines = [];
1324
+ summaryLines.push(`${labels.plugins.padEnd(maxLength + 2)} ${meta.plugins}`);
1325
+ if (meta.pluginsFailed) summaryLines.push(`${labels.failed.padEnd(maxLength + 2)} ${meta.pluginsFailed}`);
1326
+ summaryLines.push(`${labels.generated.padEnd(maxLength + 2)} ${meta.filesCreated} files in ${meta.time}`);
1327
+ if (pluginTimings && pluginTimings.size > 0) {
1328
+ const sortedTimings = Array.from(pluginTimings.entries()).sort((a, b) => b[1] - a[1]);
1329
+ summaryLines.push(`${labels.pluginTimings}`);
1330
+ sortedTimings.forEach(([name, time]) => {
1331
+ const timeStr = time >= 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
1332
+ const barLength = Math.min(Math.ceil(time / 100), 10);
1333
+ const bar = styleText("dim", "█".repeat(barLength));
1334
+ summaryLines.push(`${styleText("dim", "•")} ${name.padEnd(maxLength + 1)}${bar} ${timeStr}`);
1181
1335
  });
1182
- await hookEndPromise;
1183
1336
  }
1337
+ summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`);
1338
+ return summaryLines;
1184
1339
  }
1185
1340
  //#endregion
1186
- //#region src/utils/getCosmiConfig.ts
1341
+ //#region src/runners/generate/utils.ts
1187
1342
  const jiti = createJiti(import.meta.url, {
1188
1343
  jsx: {
1189
1344
  runtime: "automatic",
1190
- importSource: "@kubb/react-fabric"
1345
+ importSource: "@kubb/renderer-jsx"
1191
1346
  },
1192
- sourceMaps: true,
1193
- interopDefault: true
1347
+ moduleCache: false
1194
1348
  });
1195
- const tsLoader = async (configFile) => {
1196
- return await jiti.import(configFile, { default: true });
1197
- };
1198
- async function getCosmiConfig(moduleName, config) {
1199
- let result;
1200
- const searchPlaces = [
1201
- "package.json",
1202
- `.${moduleName}rc`,
1203
- `.${moduleName}rc.json`,
1204
- `.${moduleName}rc.yaml`,
1205
- `.${moduleName}rc.yml`,
1206
- `.${moduleName}rc.ts`,
1207
- `.${moduleName}rc.js`,
1208
- `.${moduleName}rc.mjs`,
1209
- `.${moduleName}rc.cjs`,
1210
- `${moduleName}.config.ts`,
1211
- `${moduleName}.config.js`,
1212
- `${moduleName}.config.mjs`,
1213
- `${moduleName}.config.cjs`
1214
- ];
1215
- const explorer = cosmiconfig(moduleName, {
1349
+ const tsLoader = (configFile) => jiti.import(configFile, { default: true });
1350
+ const MODULE_NAME = "kubb";
1351
+ const BASE_SEARCH_PLACES = [
1352
+ "package.json",
1353
+ `.${MODULE_NAME}rc`,
1354
+ `.${MODULE_NAME}rc.json`,
1355
+ `.${MODULE_NAME}rc.yaml`,
1356
+ `.${MODULE_NAME}rc.yml`,
1357
+ `.${MODULE_NAME}rc.ts`,
1358
+ `.${MODULE_NAME}rc.mts`,
1359
+ `.${MODULE_NAME}rc.cts`,
1360
+ `.${MODULE_NAME}rc.js`,
1361
+ `.${MODULE_NAME}rc.mjs`,
1362
+ `.${MODULE_NAME}rc.cjs`,
1363
+ `${MODULE_NAME}.config.ts`,
1364
+ `${MODULE_NAME}.config.mts`,
1365
+ `${MODULE_NAME}.config.cts`,
1366
+ `${MODULE_NAME}.config.js`,
1367
+ `${MODULE_NAME}.config.mjs`,
1368
+ `${MODULE_NAME}.config.cjs`
1369
+ ];
1370
+ const SEARCH_PLACES = [
1371
+ "",
1372
+ ".config/",
1373
+ "configs/"
1374
+ ].flatMap((prefix) => BASE_SEARCH_PLACES.map((p) => `${prefix}${p}`));
1375
+ async function getCosmiConfig(configFile) {
1376
+ const explorer = cosmiconfig(MODULE_NAME, {
1216
1377
  cache: false,
1217
- searchPlaces: [
1218
- ...searchPlaces.map((searchPlace) => {
1219
- return `.config/${searchPlace}`;
1220
- }),
1221
- ...searchPlaces.map((searchPlace) => {
1222
- return `configs/${searchPlace}`;
1223
- }),
1224
- ...searchPlaces
1225
- ],
1226
- loaders: { ".ts": tsLoader }
1378
+ searchPlaces: SEARCH_PLACES,
1379
+ loaders: {
1380
+ ".ts": tsLoader,
1381
+ ".mts": tsLoader,
1382
+ ".cts": tsLoader
1383
+ }
1227
1384
  });
1385
+ let result;
1228
1386
  try {
1229
- result = config ? await explorer.load(config) : await explorer.search();
1387
+ result = configFile ? await explorer.load(configFile) : await explorer.search();
1230
1388
  } catch (error) {
1231
1389
  throw new Error("Config failed loading", { cause: error });
1232
1390
  }
1233
- if (result?.isEmpty || !result || !result.config) throw new Error("Config not defined, create a kubb.config.js or pass through your config with the option --config");
1391
+ if (!result?.config || result.isEmpty) throw new Error("Config not defined, create a kubb.config.js or pass through your config with the option --config");
1234
1392
  return result;
1235
1393
  }
1236
- //#endregion
1237
- //#region src/utils/watcher.ts
1238
- async function startWatcher(path, cb) {
1394
+ /**
1395
+ * Discovers the Kubb config via cosmiconfig and resolves it into a normalized array of configs.
1396
+ * Every config in the result is guaranteed to have a `plugins` array.
1397
+ */
1398
+ async function getConfigs({ configPath, input }) {
1399
+ const result = await getCosmiConfig(configPath);
1400
+ const resolved = await (typeof result.config === "function" ? result.config({ input }) : result.config);
1401
+ const userConfigs = Array.isArray(resolved) ? resolved : [resolved];
1402
+ return {
1403
+ configPath: result.filepath,
1404
+ configs: userConfigs.map((item) => ({
1405
+ ...item,
1406
+ plugins: item.plugins ?? []
1407
+ }))
1408
+ };
1409
+ }
1410
+ /**
1411
+ * Runs the `done` hooks defined in a Kubb config in sequence.
1412
+ */
1413
+ async function executeHooks({ configHooks, hooks, makeSink }) {
1414
+ const commands = Array.isArray(configHooks.done) ? configHooks.done : [configHooks.done].filter(Boolean);
1415
+ for (const command of commands) {
1416
+ const [cmd, ...args] = tokenize(command);
1417
+ if (!cmd) continue;
1418
+ const hookId = createHash("sha256").update(command).digest("hex");
1419
+ const commandWithArgs = [cmd, ...args].join(" ");
1420
+ await hooks.emit("kubb:hook:start", {
1421
+ id: hookId,
1422
+ command: cmd,
1423
+ args
1424
+ });
1425
+ const { stream = false, onLine, onStdout, onStderr } = makeSink?.(commandWithArgs) ?? {};
1426
+ await runHook({
1427
+ id: hookId,
1428
+ command: cmd,
1429
+ args,
1430
+ commandWithArgs,
1431
+ context: hooks,
1432
+ stream,
1433
+ sink: {
1434
+ onLine,
1435
+ onStdout,
1436
+ onStderr
1437
+ }
1438
+ });
1439
+ }
1440
+ }
1441
+ async function runHook({ id, command, args, commandWithArgs, context, stream = false, sink }) {
1442
+ const emitEnd = (success, error) => context.emit("kubb:hook:end", {
1443
+ command,
1444
+ args,
1445
+ id,
1446
+ success,
1447
+ error
1448
+ });
1449
+ try {
1450
+ const proc = x(command, [...args ?? []], {
1451
+ nodeOptions: { detached: process.platform !== "win32" },
1452
+ throwOnError: true
1453
+ });
1454
+ if (stream && sink?.onLine) for await (const line of proc) sink.onLine(line);
1455
+ const result = await proc;
1456
+ await context.emit("kubb:debug", {
1457
+ date: /* @__PURE__ */ new Date(),
1458
+ logs: [result.stdout.trimEnd()]
1459
+ });
1460
+ await context.emit("kubb:success", { message: `${styleText("dim", commandWithArgs)} successfully executed` });
1461
+ await emitEnd(true, null);
1462
+ } catch (err) {
1463
+ if (!(err instanceof NonZeroExitError)) {
1464
+ const error = toError(err);
1465
+ await emitEnd(false, error);
1466
+ await context.emit("kubb:error", { error });
1467
+ return;
1468
+ }
1469
+ const stderr = err.output?.stderr ?? "";
1470
+ const stdout = err.output?.stdout ?? "";
1471
+ await context.emit("kubb:debug", {
1472
+ date: /* @__PURE__ */ new Date(),
1473
+ logs: [stdout, stderr].filter(Boolean)
1474
+ });
1475
+ if (stderr) sink?.onStderr?.(stderr);
1476
+ if (stdout) sink?.onStdout?.(stdout);
1477
+ const error = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
1478
+ await emitEnd(false, error);
1479
+ await context.emit("kubb:error", { error });
1480
+ }
1481
+ }
1482
+ /**
1483
+ * Starts a file watcher on the given paths and calls `cb` on any change.
1484
+ * Ignores `.git` and `node_modules` directories.
1485
+ */
1486
+ async function startWatcher(path, cb, log = {
1487
+ info: console.log,
1488
+ error: console.log
1489
+ }) {
1239
1490
  const { watch } = await import("chokidar");
1240
1491
  watch(path, {
1241
1492
  ignorePermissionErrors: true,
1242
1493
  ignored: WATCHER_IGNORED_PATHS
1243
1494
  }).on("all", async (type, file) => {
1244
- console.log(styleText("yellow", styleText("bold", `Change detected: ${type} ${file}`)));
1495
+ log.info(styleText("yellow", styleText("bold", `Change detected: ${type} ${file}`)));
1245
1496
  try {
1246
1497
  await cb(path);
1247
1498
  } catch (_e) {
1248
- console.log(styleText("red", "Watcher failed"));
1499
+ log.error(styleText("red", "Watcher failed"));
1249
1500
  }
1250
1501
  });
1251
1502
  }
1252
1503
  //#endregion
1253
- //#region src/runners/generate.ts
1254
- async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, configName, outputPath, logLevel: logLevel$1, events, onStart, onEnd }) {
1504
+ //#region src/runners/generate/run.ts
1505
+ /**
1506
+ * Registers a one-shot `kubb:hook:end` listener for `hookId` BEFORE the caller emits `kubb:hook:start`,
1507
+ * avoiding the race where a synchronous emitter fires end before the listener is attached.
1508
+ */
1509
+ function waitForHookEnd(hooks, hookId, onSuccess, fallbackErrorMessage) {
1510
+ return new Promise((resolve, reject) => {
1511
+ const handler = (ctx) => {
1512
+ if (ctx.id !== hookId) return;
1513
+ hooks.off("kubb:hook:end", handler);
1514
+ if (!ctx.success) {
1515
+ reject(ctx.error ?? new Error(fallbackErrorMessage));
1516
+ return;
1517
+ }
1518
+ onSuccess().then(resolve).catch(reject);
1519
+ };
1520
+ hooks.on("kubb:hook:end", handler);
1521
+ });
1522
+ }
1523
+ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, configName, outputPath, logLevel: logLevel$1, hooks, onStart, onEnd }) {
1255
1524
  await onStart();
1256
1525
  let resolvedTool = toolValue;
1257
1526
  if (resolvedTool === "auto") {
1258
1527
  const detected = await detect();
1259
- if (!detected) await events.emit("warn", noToolMessage);
1528
+ if (!detected) await hooks.emit("kubb:warn", { message: noToolMessage });
1260
1529
  else {
1261
1530
  resolvedTool = detected;
1262
- await events.emit("info", `Auto-detected ${toolLabel}: ${styleText("dim", resolvedTool)}`);
1531
+ await hooks.emit("kubb:info", { message: `Auto-detected ${toolLabel}: ${styleText("dim", resolvedTool)}` });
1263
1532
  }
1264
1533
  }
1534
+ let toolError;
1265
1535
  if (resolvedTool && resolvedTool !== "auto" && resolvedTool in toolMap) {
1266
1536
  const toolConfig = toolMap[resolvedTool];
1537
+ const hookId = createHash("sha256").update([configName, resolvedTool].filter(Boolean).join("-")).digest("hex");
1538
+ const successMessage = [
1539
+ `${successPrefix} with ${styleText("dim", resolvedTool)}`,
1540
+ logLevel$1 >= logLevel.info ? `on ${styleText("dim", outputPath)}` : void 0,
1541
+ "successfully"
1542
+ ].filter(Boolean).join(" ");
1267
1543
  try {
1268
- const hookId = createHash("sha256").update([configName, resolvedTool].filter(Boolean).join("-")).digest("hex");
1269
- const hookEndPromise = new Promise((resolve, reject) => {
1270
- const handler = ({ id, success, error }) => {
1271
- if (id !== hookId) return;
1272
- events.off("hook:end", handler);
1273
- if (!success) {
1274
- reject(error ?? /* @__PURE__ */ new Error(`${toolConfig.errorMessage}`));
1275
- return;
1276
- }
1277
- events.emit("success", [
1278
- `${successPrefix} with ${styleText("dim", resolvedTool)}`,
1279
- logLevel$1 >= logLevel.info ? `on ${styleText("dim", outputPath)}` : void 0,
1280
- "successfully"
1281
- ].filter(Boolean).join(" ")).then(resolve).catch(reject);
1282
- };
1283
- events.on("hook:end", handler);
1284
- });
1285
- await events.emit("hook:start", {
1544
+ const hookEndPromise = waitForHookEnd(hooks, hookId, () => hooks.emit("kubb:success", { message: successMessage }), toolConfig.errorMessage);
1545
+ await hooks.emit("kubb:hook:start", {
1286
1546
  id: hookId,
1287
1547
  command: toolConfig.command,
1288
1548
  args: toolConfig.args(outputPath)
1289
1549
  });
1290
1550
  await hookEndPromise;
1291
1551
  } catch (caughtError) {
1292
- const err = new Error(toolConfig.errorMessage);
1293
- err.cause = caughtError;
1294
- await events.emit("error", err);
1552
+ const err = toError(caughtError);
1553
+ await hooks.emit("kubb:error", { error: err });
1554
+ toolError = err;
1295
1555
  }
1296
1556
  }
1297
1557
  await onEnd();
1558
+ if (toolError) throw toolError;
1298
1559
  }
1299
- async function generate({ input, config: userConfig, events, logLevel: logLevel$2 }) {
1300
- const inputPath = input ?? ("path" in userConfig.input ? userConfig.input.path : void 0);
1560
+ async function generate(options) {
1561
+ const { input, hooks, logLevel: logLevel$2, makeSink } = options;
1301
1562
  const hrStart = process$1.hrtime();
1563
+ const inputPath = input ?? (options.config.input && "path" in options.config.input ? options.config.input.path : void 0);
1302
1564
  const config = {
1303
- ...userConfig,
1304
- root: userConfig.root || process$1.cwd(),
1565
+ ...options.config,
1305
1566
  input: inputPath ? {
1306
- ...userConfig.input,
1567
+ ...options.config.input,
1307
1568
  path: inputPath
1308
- } : userConfig.input,
1309
- output: {
1310
- write: true,
1311
- barrelType: "named",
1312
- extension: { ".ts": ".ts" },
1313
- format: "prettier",
1314
- ...userConfig.output
1315
- }
1569
+ } : options.config.input,
1570
+ ...options.config.output
1316
1571
  };
1317
- await events.emit("generation:start", config);
1318
- await events.emit("info", config.name ? `Setup generation ${styleText("bold", config.name)}` : "Setup generation", inputPath);
1319
- const { sources, fabric, driver } = await setup({
1320
- config,
1321
- events
1572
+ const kubb = createKubb(config, { hooks });
1573
+ await kubb.setup();
1574
+ await hooks.emit("kubb:generation:start", { config });
1575
+ await hooks.emit("kubb:info", {
1576
+ message: config.name ? `Setup generation ${styleText("bold", config.name)}` : "Setup generation",
1577
+ info: inputPath
1322
1578
  });
1323
- await events.emit("info", config.name ? `Build generation ${styleText("bold", config.name)}` : "Build generation", inputPath);
1324
- const { files, failedPlugins, pluginTimings, error } = await safeBuild({
1325
- config,
1326
- events
1327
- }, {
1328
- driver,
1329
- fabric,
1330
- events,
1331
- sources
1579
+ await hooks.emit("kubb:info", {
1580
+ message: config.name ? `Build generation ${styleText("bold", config.name)}` : "Build generation",
1581
+ info: inputPath
1332
1582
  });
1333
- await events.emit("info", "Load summary");
1583
+ const { files, failedPlugins, pluginTimings, error, driver } = await kubb.safeBuild();
1584
+ await hooks.emit("kubb:info", { message: "Load summary" });
1585
+ const telemetryPlugins = Array.from(driver.plugins.values(), (p) => ({
1586
+ name: p.name,
1587
+ options: p.options
1588
+ }));
1589
+ const reportTelemetry = (status) => sendTelemetry(buildTelemetryEvent({
1590
+ command: "generate",
1591
+ kubbVersion: version,
1592
+ plugins: telemetryPlugins,
1593
+ hrStart,
1594
+ filesCreated: files.length,
1595
+ status
1596
+ }));
1334
1597
  if (failedPlugins.size > 0 || error) {
1335
- const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
1336
- for (const err of allErrors) await events.emit("error", err);
1337
- await events.emit("generation:end", config, files, sources);
1338
- await events.emit("generation:summary", config, {
1598
+ const allErrors = [error, ...Array.from(failedPlugins, (it) => it.error)].filter(Boolean);
1599
+ for (const err of allErrors) await hooks.emit("kubb:error", { error: err });
1600
+ await hooks.emit("kubb:generation:end", {
1601
+ config,
1602
+ files,
1603
+ sources: kubb.sources
1604
+ });
1605
+ await hooks.emit("kubb:generation:summary", {
1606
+ config,
1339
1607
  failedPlugins,
1340
1608
  filesCreated: files.length,
1341
1609
  status: "failed",
1342
1610
  hrStart,
1343
1611
  pluginTimings: logLevel$2 >= logLevel.verbose ? pluginTimings : void 0
1344
1612
  });
1345
- await sendTelemetry(buildTelemetryEvent({
1346
- command: "generate",
1347
- kubbVersion: version,
1348
- plugins: driver.plugins.map((p) => ({
1349
- name: p.name,
1350
- options: p.options
1351
- })),
1352
- hrStart,
1353
- filesCreated: files.length,
1354
- status: "failed"
1355
- }));
1613
+ await reportTelemetry("failed");
1356
1614
  process$1.exit(1);
1357
1615
  }
1358
- await events.emit("success", "Generation successfully", inputPath);
1359
- await events.emit("generation:end", config, files, sources);
1616
+ await hooks.emit("kubb:success", {
1617
+ message: "Generation succeeded",
1618
+ info: inputPath
1619
+ });
1620
+ await hooks.emit("kubb:generation:end", {
1621
+ config,
1622
+ files,
1623
+ sources: kubb.sources
1624
+ });
1360
1625
  const outputPath = path.resolve(config.root, config.output.path);
1361
- if (config.output.format) await runToolPass({
1626
+ const toolPasses = [config.output.format && {
1362
1627
  toolValue: config.output.format,
1363
1628
  detect: detectFormatter,
1364
1629
  toolMap: formatters,
1365
1630
  toolLabel: "formatter",
1366
1631
  successPrefix: "Formatting",
1367
- noToolMessage: "No formatter found (biome, prettier, or oxfmt). Skipping formatting.",
1368
- configName: config.name,
1369
- outputPath,
1370
- logLevel: logLevel$2,
1371
- events,
1372
- onStart: () => events.emit("format:start"),
1373
- onEnd: () => events.emit("format:end")
1374
- });
1375
- if (config.output.lint) await runToolPass({
1632
+ noToolMessage: "No formatter found (oxfmt, biome, or prettier). Skipping formatting.",
1633
+ onStart: () => hooks.emit("kubb:format:start"),
1634
+ onEnd: () => hooks.emit("kubb:format:end")
1635
+ }, config.output.lint && {
1376
1636
  toolValue: config.output.lint,
1377
1637
  detect: detectLinter,
1378
1638
  toolMap: linters,
1379
1639
  toolLabel: "linter",
1380
1640
  successPrefix: "Linting",
1381
- noToolMessage: "No linter found (biome, oxlint, or eslint). Skipping linting.",
1641
+ noToolMessage: "No linter found (oxlint, biome, or eslint). Skipping linting.",
1642
+ onStart: () => hooks.emit("kubb:lint:start"),
1643
+ onEnd: () => hooks.emit("kubb:lint:end")
1644
+ }].filter(Boolean);
1645
+ for (const pass of toolPasses) await runToolPass({
1646
+ ...pass,
1382
1647
  configName: config.name,
1383
1648
  outputPath,
1384
1649
  logLevel: logLevel$2,
1385
- events,
1386
- onStart: () => events.emit("lint:start"),
1387
- onEnd: () => events.emit("lint:end")
1650
+ hooks
1388
1651
  });
1389
1652
  if (config.hooks) {
1390
- await events.emit("hooks:start");
1653
+ await hooks.emit("kubb:hooks:start");
1391
1654
  await executeHooks({
1392
- hooks: config.hooks,
1393
- events
1655
+ configHooks: config.hooks,
1656
+ hooks,
1657
+ makeSink
1394
1658
  });
1395
- await events.emit("hooks:end");
1659
+ await hooks.emit("kubb:hooks:end");
1396
1660
  }
1397
- await events.emit("generation:summary", config, {
1661
+ await hooks.emit("kubb:generation:summary", {
1662
+ config,
1398
1663
  failedPlugins,
1399
1664
  filesCreated: files.length,
1400
1665
  status: "success",
1401
1666
  hrStart,
1402
1667
  pluginTimings
1403
1668
  });
1404
- await sendTelemetry(buildTelemetryEvent({
1405
- command: "generate",
1406
- kubbVersion: version,
1407
- plugins: driver.plugins.map((p) => ({
1408
- name: p.name,
1409
- options: p.options
1410
- })),
1411
- hrStart,
1412
- filesCreated: files.length,
1413
- status: "success"
1414
- }));
1669
+ await reportTelemetry("success");
1415
1670
  }
1416
- async function runGenerateCommand({ input, configPath, logLevel: logLevelKey, watch }) {
1417
- const logLevel$3 = logLevel[logLevelKey] ?? logLevel.info;
1418
- const events = new AsyncEventEmitter();
1419
- await setupLogger(events, { logLevel: logLevel$3 });
1671
+ async function checkForUpdate(hooks) {
1420
1672
  await executeIfOnline(async () => {
1421
1673
  try {
1422
- const latestVersion = (await (await fetch(KUBB_NPM_PACKAGE_URL)).json()).version;
1423
- if (latestVersion && version < latestVersion) await events.emit("version:new", version, latestVersion);
1674
+ const data = await (await fetch(KUBB_NPM_PACKAGE_URL)).json();
1675
+ if (data.version && version < data.version) await hooks.emit("kubb:version:new", {
1676
+ currentVersion: version,
1677
+ latestVersion: data.version
1678
+ });
1424
1679
  } catch {}
1425
1680
  });
1681
+ }
1682
+ /**
1683
+ * Runs the full Kubb generation lifecycle for the given CLI options.
1684
+ * Sets up the logger, checks for a newer version, loads configs, and calls `generate` for each config entry.
1685
+ */
1686
+ async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1687
+ const logLevel$3 = logLevel[logLevelKey] ?? logLevel.info;
1688
+ const hooks = new AsyncEventEmitter();
1689
+ const makeSink = await setupLogger(hooks, { logLevel: logLevel$3 });
1690
+ await checkForUpdate(hooks);
1426
1691
  try {
1427
- const result = await getCosmiConfig("kubb", configPath);
1428
- const configs = await getConfigs(result.config, { input });
1429
- await events.emit("config:start");
1430
- await events.emit("info", "Config loaded", path.relative(process$1.cwd(), result.filepath));
1431
- await events.emit("success", "Config loaded successfully", path.relative(process$1.cwd(), result.filepath));
1432
- await events.emit("config:end", configs);
1433
- await events.emit("lifecycle:start", version);
1692
+ const { configs, configPath: resolvedConfigPath } = await getConfigs({
1693
+ configPath,
1694
+ input
1695
+ });
1696
+ const relativeConfigPath = path.relative(process$1.cwd(), resolvedConfigPath);
1697
+ await hooks.emit("kubb:config:start");
1698
+ await hooks.emit("kubb:info", {
1699
+ message: "Config loaded",
1700
+ info: relativeConfigPath
1701
+ });
1702
+ await hooks.emit("kubb:success", {
1703
+ message: "Config loaded successfully",
1704
+ info: relativeConfigPath
1705
+ });
1706
+ await hooks.emit("kubb:config:end", { configs });
1707
+ await hooks.emit("kubb:lifecycle:start", { version });
1434
1708
  for (const config of configs) if (isInputPath(config) && watch) await startWatcher([input || config.input.path], async (paths) => {
1435
- events.removeAll();
1709
+ hooks.removeAll();
1436
1710
  await generate({
1437
1711
  input,
1438
1712
  config,
1439
1713
  logLevel: logLevel$3,
1440
- events
1714
+ hooks,
1715
+ makeSink
1441
1716
  });
1442
1717
  clack.log.step(styleText("yellow", `Watching for changes in ${paths.join(" and ")}`));
1718
+ }, {
1719
+ info: (msg) => clack.log.info(msg),
1720
+ error: (msg) => clack.log.error(msg)
1443
1721
  });
1444
1722
  else await generate({
1445
1723
  input,
1446
1724
  config,
1447
1725
  logLevel: logLevel$3,
1448
- events
1726
+ hooks,
1727
+ makeSink
1449
1728
  });
1450
- await events.emit("lifecycle:end");
1729
+ await hooks.emit("kubb:lifecycle:end");
1451
1730
  } catch (error) {
1452
- await events.emit("error", toError(error));
1731
+ await hooks.emit("kubb:error", { error: toError(error) });
1453
1732
  process$1.exit(1);
1454
1733
  }
1455
1734
  }
1456
1735
  //#endregion
1457
- export { runGenerateCommand };
1736
+ export { run };
1458
1737
 
1459
- //# sourceMappingURL=generate-BHNyeQXl.js.map
1738
+ //# sourceMappingURL=run-Bfbr3RaM.js.map