@kubb/cli 5.0.0-beta.84 → 5.0.0-beta.86

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 (76) hide show
  1. package/dist/{Telemetry-ugO-B77y.js → Telemetry-Cuzoes9Y.js} +6 -2
  2. package/dist/Telemetry-Cuzoes9Y.js.map +1 -0
  3. package/dist/{Telemetry-jtbchpQE.cjs → Telemetry-CwQYBgsg.cjs} +11 -1
  4. package/dist/Telemetry-CwQYBgsg.cjs.map +1 -0
  5. package/dist/{generate-DDgZnlMs.js → generate-BO6xVUEL.js} +34 -24
  6. package/dist/generate-BO6xVUEL.js.map +1 -0
  7. package/dist/{generate-Diob_zqj.cjs → generate-BW9yIfgS.cjs} +34 -23
  8. package/dist/generate-BW9yIfgS.cjs.map +1 -0
  9. package/dist/index.cjs +28 -192
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.js +29 -193
  12. package/dist/index.js.map +1 -1
  13. package/dist/{init-uOQx7jSE.cjs → init-Cx4n_O5X.cjs} +9 -10
  14. package/dist/init-Cx4n_O5X.cjs.map +1 -0
  15. package/dist/{init-Cn3jZVkf.js → init-_NdYuc6u.js} +10 -10
  16. package/dist/init-_NdYuc6u.js.map +1 -0
  17. package/dist/{mcp-n-IOoFZL.js → mcp-BuhCkjE_.js} +6 -6
  18. package/dist/mcp-BuhCkjE_.js.map +1 -0
  19. package/dist/{mcp-BxhOXHGD.cjs → mcp-DJd2cm9I.cjs} +5 -6
  20. package/dist/mcp-DJd2cm9I.cjs.map +1 -0
  21. package/dist/{package-BBBOfxZz.cjs → package-Bar6zPkT.cjs} +2 -2
  22. package/dist/package-Bar6zPkT.cjs.map +1 -0
  23. package/dist/package-D24TC3pS.js +6 -0
  24. package/dist/package-D24TC3pS.js.map +1 -0
  25. package/dist/{run-DXEMoDFa.cjs → run-3qq_Q4Tg.cjs} +4 -4
  26. package/dist/run-3qq_Q4Tg.cjs.map +1 -0
  27. package/dist/{run-Bz6ZUrhp.cjs → run-B9FN0sHW.cjs} +2 -2
  28. package/dist/run-B9FN0sHW.cjs.map +1 -0
  29. package/dist/{run-CPBFfbI7.js → run-BFhNcn8b.js} +4 -10
  30. package/dist/run-BFhNcn8b.js.map +1 -0
  31. package/dist/{run-D8TRnpBB.js → run-BxMfDivH.js} +291 -333
  32. package/dist/run-BxMfDivH.js.map +1 -0
  33. package/dist/{run-BBLhhWZu.js → run-D0Gs_UzJ.js} +2 -2
  34. package/dist/run-D0Gs_UzJ.js.map +1 -0
  35. package/dist/{run-DYkciQ4u.cjs → run-DYzUPE7Z.cjs} +4 -10
  36. package/dist/run-DYzUPE7Z.cjs.map +1 -0
  37. package/dist/{run-ChSqYuc1.cjs → run-lAxAF-4t.cjs} +292 -333
  38. package/dist/run-lAxAF-4t.cjs.map +1 -0
  39. package/dist/{run-44Zxh8Ec.js → run-yMRylOST.js} +3 -3
  40. package/dist/run-yMRylOST.js.map +1 -0
  41. package/dist/{tools-D0UogMU3.js → tools-B23ltmWP.js} +3 -3
  42. package/dist/tools-B23ltmWP.js.map +1 -0
  43. package/dist/{tools-CZT9wSA6.cjs → tools-CeIABV0T.cjs} +3 -3
  44. package/dist/tools-CeIABV0T.cjs.map +1 -0
  45. package/dist/{validate-CYIieoc4.cjs → validate-D0doE06k.cjs} +8 -9
  46. package/dist/validate-D0doE06k.cjs.map +1 -0
  47. package/dist/{validate-BoG8ZZgE.js → validate-DZXin3M6.js} +9 -9
  48. package/dist/validate-DZXin3M6.js.map +1 -0
  49. package/package.json +8 -7
  50. package/dist/Telemetry-jtbchpQE.cjs.map +0 -1
  51. package/dist/Telemetry-ugO-B77y.js.map +0 -1
  52. package/dist/defineCommand-Bo3yZTWI.cjs +0 -36
  53. package/dist/defineCommand-Bo3yZTWI.cjs.map +0 -1
  54. package/dist/defineCommand-DMEeqliP.js +0 -31
  55. package/dist/defineCommand-DMEeqliP.js.map +0 -1
  56. package/dist/generate-DDgZnlMs.js.map +0 -1
  57. package/dist/generate-Diob_zqj.cjs.map +0 -1
  58. package/dist/init-Cn3jZVkf.js.map +0 -1
  59. package/dist/init-uOQx7jSE.cjs.map +0 -1
  60. package/dist/mcp-BxhOXHGD.cjs.map +0 -1
  61. package/dist/mcp-n-IOoFZL.js.map +0 -1
  62. package/dist/package-BBBOfxZz.cjs.map +0 -1
  63. package/dist/package-DKwwXnAz.js +0 -6
  64. package/dist/package-DKwwXnAz.js.map +0 -1
  65. package/dist/run-44Zxh8Ec.js.map +0 -1
  66. package/dist/run-BBLhhWZu.js.map +0 -1
  67. package/dist/run-Bz6ZUrhp.cjs.map +0 -1
  68. package/dist/run-CPBFfbI7.js.map +0 -1
  69. package/dist/run-ChSqYuc1.cjs.map +0 -1
  70. package/dist/run-D8TRnpBB.js.map +0 -1
  71. package/dist/run-DXEMoDFa.cjs.map +0 -1
  72. package/dist/run-DYkciQ4u.cjs.map +0 -1
  73. package/dist/tools-CZT9wSA6.cjs.map +0 -1
  74. package/dist/tools-D0UogMU3.js.map +0 -1
  75. package/dist/validate-BoG8ZZgE.js.map +0 -1
  76. package/dist/validate-CYIieoc4.cjs.map +0 -1
@@ -1,126 +1,17 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
+ import { a as WATCHER_IGNORED_PATHS, i as UPDATE_CHECK_TIMEOUT_MS, n as KUBB_NPM_PACKAGE_URL, o as canUseTTY, t as Telemetry } from "./Telemetry-Cuzoes9Y.js";
2
3
  import { n as toCause, r as toError } from "./errors-Dc_d7BfX.js";
3
- import { a as canUseTTY, i as WATCHER_IGNORED_PATHS, n as KUBB_NPM_PACKAGE_URL, t as Telemetry } from "./Telemetry-ugO-B77y.js";
4
- import { i as linters, n as detectTool, r as formatters } from "./tools-D0UogMU3.js";
5
- import { t as version } from "./package-DKwwXnAz.js";
4
+ import { t as version } from "./package-D24TC3pS.js";
5
+ import { i as linters, n as detectTool, r as formatters } from "./tools-B23ltmWP.js";
6
6
  import { styleText } from "node:util";
7
- import { EventEmitter } from "node:events";
8
- import { hash } from "node:crypto";
7
+ import process$1 from "node:process";
9
8
  import { existsSync } from "node:fs";
10
9
  import path, { basename, dirname, relative, resolve } from "node:path";
11
- import process$1 from "node:process";
12
10
  import * as clack from "@clack/prompts";
13
- import { Diagnostics, cliReporter, createKubb, logLevel } from "@kubb/core";
11
+ import { Diagnostics, Hookable, cliReporter, createKubb, logLevel } from "@kubb/core";
14
12
  import { createJiti } from "jiti";
15
13
  import { NonZeroExitError, x } from "tinyexec";
16
14
  import { loadConfig } from "unconfig";
17
- //#region ../../internals/utils/src/asyncEventEmitter.ts
18
- /**
19
- * Typed `EventEmitter` that awaits all async listeners before resolving.
20
- * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
21
- *
22
- * @example
23
- * ```ts
24
- * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
25
- * emitter.on('build', async (name) => { console.log(name) })
26
- * await emitter.emit('build', 'petstore') // all listeners awaited
27
- * ```
28
- */
29
- var AsyncEventEmitter = class {
30
- /**
31
- * Maximum number of listeners per event before Node emits a memory-leak warning.
32
- * @default 10
33
- */
34
- constructor(maxListener = 10) {
35
- this.#emitter.setMaxListeners(maxListener);
36
- }
37
- #emitter = new EventEmitter();
38
- /**
39
- * Emits `eventName` and awaits all registered listeners sequentially.
40
- * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
41
- *
42
- * @example
43
- * ```ts
44
- * await emitter.emit('build', 'petstore')
45
- * ```
46
- */
47
- emit(eventName, ...eventArgs) {
48
- const listeners = this.#emitter.listeners(eventName);
49
- if (listeners.length === 0) return;
50
- return this.#emitAll(eventName, listeners, eventArgs);
51
- }
52
- async #emitAll(eventName, listeners, eventArgs) {
53
- for (const listener of listeners) try {
54
- await listener(...eventArgs);
55
- } catch (err) {
56
- let serializedArgs;
57
- try {
58
- serializedArgs = JSON.stringify(eventArgs);
59
- } catch {
60
- serializedArgs = String(eventArgs);
61
- }
62
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
63
- }
64
- }
65
- /**
66
- * Registers a persistent listener for `eventName`.
67
- *
68
- * @example
69
- * ```ts
70
- * emitter.on('build', async (name) => { console.log(name) })
71
- * ```
72
- */
73
- on(eventName, handler) {
74
- this.#emitter.on(eventName, handler);
75
- }
76
- /**
77
- * Removes a previously registered listener.
78
- *
79
- * @example
80
- * ```ts
81
- * emitter.off('build', handler)
82
- * ```
83
- */
84
- off(eventName, handler) {
85
- this.#emitter.off(eventName, handler);
86
- }
87
- /**
88
- * Returns the number of listeners registered for `eventName`.
89
- *
90
- * @example
91
- * ```ts
92
- * emitter.on('build', handler)
93
- * emitter.listenerCount('build') // 1
94
- * ```
95
- */
96
- listenerCount(eventName) {
97
- return this.#emitter.listenerCount(eventName);
98
- }
99
- /**
100
- * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
101
- * Set this above the expected listener count when many listeners attach by design.
102
- *
103
- * @example
104
- * ```ts
105
- * emitter.setMaxListeners(40)
106
- * ```
107
- */
108
- setMaxListeners(max) {
109
- this.#emitter.setMaxListeners(max);
110
- }
111
- /**
112
- * Removes all listeners from every event channel.
113
- *
114
- * @example
115
- * ```ts
116
- * emitter.removeAll()
117
- * ```
118
- */
119
- removeAll() {
120
- this.#emitter.removeAllListeners();
121
- }
122
- };
123
- //#endregion
124
15
  //#region ../../internals/utils/src/time.ts
125
16
  /**
126
17
  * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
@@ -265,6 +156,43 @@ function formatMsWithColor(ms) {
265
156
  return styleText("red", formatted);
266
157
  }
267
158
  //#endregion
159
+ //#region ../../internals/utils/src/promise.ts
160
+ /**
161
+ * Wraps `run` so invocations never overlap: a trigger that lands while a run is in flight
162
+ * marks it dirty and runs once more after it finishes, no matter how many triggers arrived.
163
+ * Useful for event-driven reruns (a file watcher, a queue drain) where bursts should
164
+ * coalesce into a single trailing run.
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * const rebuild = createSerialRunner({
169
+ * run: () => build(),
170
+ * onError: (error) => log.error(error.message),
171
+ * })
172
+ * watcher.on('change', () => void rebuild())
173
+ * ```
174
+ */
175
+ function createSerialRunner({ run, onError }) {
176
+ let running = false;
177
+ let dirty = false;
178
+ return async () => {
179
+ if (running) {
180
+ dirty = true;
181
+ return;
182
+ }
183
+ running = true;
184
+ do {
185
+ dirty = false;
186
+ try {
187
+ await run();
188
+ } catch (error) {
189
+ onError(toError(error));
190
+ }
191
+ } while (dirty);
192
+ running = false;
193
+ };
194
+ }
195
+ //#endregion
268
196
  //#region src/loggers/defineLogger.ts
269
197
  /**
270
198
  * Type-safe helper for authoring a {@link Logger}.
@@ -274,7 +202,7 @@ function formatMsWithColor(ms) {
274
202
  * export const myLogger = defineLogger({
275
203
  * name: 'my-logger',
276
204
  * install(context) {
277
- * context.on('kubb:info', ({ message }) => console.log('ℹ', message))
205
+ * context.hook('kubb:info', ({ message }) => console.log('ℹ', message))
278
206
  * },
279
207
  * })
280
208
  * ```
@@ -326,8 +254,8 @@ const clackLogger = defineLogger({
326
254
  function getMessage(message) {
327
255
  return formatMessage(message, logLevel$5);
328
256
  }
329
- function onStep(event, message) {
330
- context.on(event, () => {
257
+ function onStep(hook, message) {
258
+ context.hook(hook, () => {
331
259
  if (logLevel$5 <= logLevel.silent) return;
332
260
  clack.log.step(getMessage(message));
333
261
  });
@@ -337,7 +265,7 @@ const clackLogger = defineLogger({
337
265
  state.spinner.stop(text);
338
266
  state.isSpinning = false;
339
267
  }
340
- context.on("kubb:info", ({ message, info = "" }) => {
268
+ context.hook("kubb:info", ({ message, info = "" }) => {
341
269
  if (logLevel$5 <= logLevel.silent) return;
342
270
  const text = getMessage([
343
271
  styleText("blue", "ℹ"),
@@ -350,7 +278,7 @@ const clackLogger = defineLogger({
350
278
  }
351
279
  clack.log.info(text);
352
280
  });
353
- context.on("kubb:success", ({ message, info = "" }) => {
281
+ context.hook("kubb:success", ({ message, info = "" }) => {
354
282
  if (logLevel$5 <= logLevel.silent) return;
355
283
  const text = getMessage([
356
284
  styleText("blue", "✓"),
@@ -363,7 +291,7 @@ const clackLogger = defineLogger({
363
291
  }
364
292
  clack.log.success(text);
365
293
  });
366
- context.on("kubb:warn", ({ message, info }) => {
294
+ context.hook("kubb:warn", ({ message, info }) => {
367
295
  if (logLevel$5 < logLevel.warn) return;
368
296
  const text = getMessage([
369
297
  styleText("yellow", "⚠"),
@@ -372,25 +300,23 @@ const clackLogger = defineLogger({
372
300
  ].filter(Boolean).join(" "));
373
301
  clack.log.warn(text);
374
302
  });
375
- context.on("kubb:error", ({ error }) => {
376
- const caused = toCause(error);
303
+ context.hook("kubb:error", ({ error }) => {
377
304
  const text = [styleText("red", "✗"), error.message].join(" ");
378
305
  if (state.isSpinning) {
379
306
  stopSpinner(getMessage(text));
380
307
  return;
381
308
  }
382
309
  clack.log.error(getMessage(text));
383
- if (logLevel$5 >= logLevel.verbose && error.stack) {
384
- const frames = error.stack.split("\n").slice(1, 4);
385
- for (const frame of frames) clack.log.message(getMessage(styleText("dim", frame.trim())));
386
- if (caused?.stack) {
387
- clack.log.message(styleText("dim", `└─ caused by ${caused.message}`));
388
- const frames = caused.stack.split("\n").slice(1, 4);
389
- for (const frame of frames) clack.log.message(getMessage(` ${styleText("dim", frame.trim())}`));
310
+ const frames = logLevel$5 >= logLevel.verbose ? formatErrorFrames(error) : null;
311
+ if (frames) {
312
+ for (const frame of frames.frames) clack.log.message(getMessage(styleText("dim", frame)));
313
+ if (frames.cause) {
314
+ clack.log.message(styleText("dim", frames.cause.header));
315
+ for (const frame of frames.cause.frames) clack.log.message(getMessage(` ${styleText("dim", frame)}`));
390
316
  }
391
317
  }
392
318
  });
393
- context.on("kubb:diagnostic", ({ diagnostic }) => {
319
+ context.hook("kubb:diagnostic", ({ diagnostic }) => {
394
320
  if (logLevel$5 <= logLevel.silent && diagnostic.severity !== "error") return;
395
321
  stopSpinner();
396
322
  stopActiveProgress();
@@ -412,7 +338,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
412
338
  secondarySymbol: ""
413
339
  });
414
340
  });
415
- context.on("kubb:lifecycle:start", async ({ version }) => {
341
+ context.hook("kubb:lifecycle:start", async ({ version }) => {
416
342
  console.log(`\n${getIntro({
417
343
  title: "The meta framework for code generation",
418
344
  description: "Ready to start",
@@ -421,14 +347,14 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
421
347
  })}\n`);
422
348
  reset();
423
349
  });
424
- context.on("kubb:generation:start", ({ config }) => {
350
+ context.hook("kubb:generation:start", ({ config }) => {
425
351
  reset();
426
352
  state.totalPlugins = config.plugins?.length ?? 0;
427
353
  if (logLevel$5 <= logLevel.silent) return;
428
354
  const text = getMessage(["Generation started", config.name ? `for ${styleText("dim", config.name)}` : void 0].filter(Boolean).join(" "));
429
355
  clack.intro(text);
430
356
  });
431
- context.on("kubb:plugin:start", ({ plugin }) => {
357
+ context.hook("kubb:plugin:start", ({ plugin }) => {
432
358
  if (logLevel$5 <= logLevel.silent) return;
433
359
  stopSpinner();
434
360
  state.runningPlugins.add(plugin.name);
@@ -446,7 +372,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
446
372
  progressBar.advance(state.completedPlugins + state.failedPlugins, pluginProgressText());
447
373
  state.activeProgress.set("plugins", { progressBar });
448
374
  });
449
- context.on("kubb:plugin:end", ({ plugin, success }) => {
375
+ context.hook("kubb:plugin:end", ({ plugin, success }) => {
450
376
  stopSpinner();
451
377
  const active = state.activeProgress.get("plugins");
452
378
  if (!active || logLevel$5 === logLevel.silent) return;
@@ -459,7 +385,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
459
385
  showProgressStep();
460
386
  }
461
387
  });
462
- context.on("kubb:files:processing:start", ({ files }) => {
388
+ context.hook("kubb:files:processing:start", ({ files }) => {
463
389
  if (logLevel$5 <= logLevel.silent) return;
464
390
  stopSpinner();
465
391
  state.totalFiles = files.length;
@@ -470,11 +396,11 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
470
396
  max: files.length,
471
397
  size: 30
472
398
  });
473
- context.emit("kubb:info", { message: text });
399
+ context.callHook("kubb:info", { message: text });
474
400
  progressBar.start(getMessage(text));
475
401
  state.activeProgress.set("files", { progressBar });
476
402
  });
477
- context.on("kubb:files:processing:update", ({ files }) => {
403
+ context.hook("kubb:files:processing:update", ({ files }) => {
478
404
  if (logLevel$5 <= logLevel.silent) return;
479
405
  stopSpinner();
480
406
  const active = state.activeProgress.get("files");
@@ -483,7 +409,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
483
409
  if (active) active.progressBar.advance(void 0, `Writing ${relative(config.root, file.path)}`);
484
410
  }
485
411
  });
486
- context.on("kubb:files:processing:end", () => {
412
+ context.hook("kubb:files:processing:end", () => {
487
413
  if (logLevel$5 <= logLevel.silent) return;
488
414
  stopSpinner();
489
415
  const text = getMessage("Files written successfully");
@@ -493,7 +419,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
493
419
  state.activeProgress.delete("files");
494
420
  showProgressStep();
495
421
  });
496
- context.on("kubb:generation:end", ({ config }) => {
422
+ context.hook("kubb:generation:end", ({ config }) => {
497
423
  stopSpinner();
498
424
  const text = getMessage(config.name ? `Generation completed for ${styleText("dim", config.name)}` : "Generation completed");
499
425
  clack.outro(text);
@@ -501,7 +427,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
501
427
  onStep("kubb:format:start", "Formatting");
502
428
  onStep("kubb:lint:start", "Linting");
503
429
  onStep("kubb:hooks:start", "Running hooks");
504
- context.on("kubb:hook:start", ({ id, command, args }) => {
430
+ context.hook("kubb:hook:start", ({ id, command, args }) => {
505
431
  if (logLevel$5 <= logLevel.silent || !id) return;
506
432
  stopSpinner();
507
433
  const title = getMessage(`Running ${styleText("dim", formatCommandWithArgs(command, args))}`);
@@ -511,10 +437,10 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
511
437
  hrStart: process$1.hrtime()
512
438
  });
513
439
  });
514
- if (logLevel$5 > logLevel.silent) context.on("kubb:hook:line", ({ id, line }) => {
440
+ if (logLevel$5 > logLevel.silent) context.hook("kubb:hook:line", ({ id, line }) => {
515
441
  state.activeHookLogs.get(id)?.taskLog.message(styleText("dim", line));
516
442
  });
517
- context.on("kubb:hook:end", ({ id, command, args, success, error, stdout, stderr }) => {
443
+ context.hook("kubb:hook:end", ({ id, command, args, success, error, stdout, stderr }) => {
518
444
  if (!id) return;
519
445
  if (logLevel$5 <= logLevel.silent) {
520
446
  if (!success) {
@@ -534,7 +460,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
534
460
  active.taskLog.error(getMessage(`${styleText("dim", commandWithArgs)} failed${reason}`), { showLog: true });
535
461
  }
536
462
  });
537
- context.on("kubb:lifecycle:end", () => {
463
+ context.hook("kubb:lifecycle:end", () => {
538
464
  reset();
539
465
  });
540
466
  }
@@ -552,13 +478,13 @@ const plainLogger = defineLogger({
552
478
  function getMessage(message) {
553
479
  return formatMessage(message, logLevel$4);
554
480
  }
555
- function onStep(event, message) {
556
- context.on(event, () => {
481
+ function onStep(hook, message) {
482
+ context.hook(hook, () => {
557
483
  if (logLevel$4 <= logLevel.silent) return;
558
484
  console.log(getMessage(message));
559
485
  });
560
486
  }
561
- context.on("kubb:info", ({ message, info }) => {
487
+ context.hook("kubb:info", ({ message, info }) => {
562
488
  if (logLevel$4 <= logLevel.silent) return;
563
489
  const text = getMessage([
564
490
  "ℹ",
@@ -567,7 +493,7 @@ const plainLogger = defineLogger({
567
493
  ].join(" "));
568
494
  console.log(text);
569
495
  });
570
- context.on("kubb:success", ({ message, info = "" }) => {
496
+ context.hook("kubb:success", ({ message, info = "" }) => {
571
497
  if (logLevel$4 <= logLevel.silent) return;
572
498
  const text = getMessage([
573
499
  "✓",
@@ -576,7 +502,7 @@ const plainLogger = defineLogger({
576
502
  ].filter(Boolean).join(" "));
577
503
  console.log(text);
578
504
  });
579
- context.on("kubb:warn", ({ message, info }) => {
505
+ context.hook("kubb:warn", ({ message, info }) => {
580
506
  if (logLevel$4 < logLevel.warn) return;
581
507
  const text = getMessage([
582
508
  "⚠",
@@ -585,57 +511,55 @@ const plainLogger = defineLogger({
585
511
  ].filter(Boolean).join(" "));
586
512
  console.log(text);
587
513
  });
588
- context.on("kubb:error", ({ error }) => {
589
- const caused = toCause(error);
514
+ context.hook("kubb:error", ({ error }) => {
590
515
  const text = getMessage(["✗", error.message].join(" "));
591
516
  console.log(text);
592
- if (logLevel$4 >= logLevel.verbose && error.stack) {
593
- const frames = error.stack.split("\n").slice(1, 4);
594
- for (const frame of frames) console.log(getMessage(frame.trim()));
595
- if (caused?.stack) {
596
- console.log(`└─ caused by ${caused.message}`);
597
- const frames = caused.stack.split("\n").slice(1, 4);
598
- for (const frame of frames) console.log(getMessage(` ${frame.trim()}`));
517
+ const frames = logLevel$4 >= logLevel.verbose ? formatErrorFrames(error) : null;
518
+ if (frames) {
519
+ for (const frame of frames.frames) console.log(getMessage(frame));
520
+ if (frames.cause) {
521
+ console.log(frames.cause.header);
522
+ for (const frame of frames.cause.frames) console.log(getMessage(` ${frame}`));
599
523
  }
600
524
  }
601
525
  });
602
- context.on("kubb:diagnostic", ({ diagnostic }) => {
526
+ context.hook("kubb:diagnostic", ({ diagnostic }) => {
603
527
  if (logLevel$4 <= logLevel.silent && diagnostic.severity !== "error") return;
604
528
  console.log(getMessage(Diagnostics.formatLines(diagnostic).join("\n")));
605
529
  });
606
- context.on("kubb:lifecycle:start", ({ version }) => {
530
+ context.hook("kubb:lifecycle:start", ({ version }) => {
607
531
  console.log(`Kubb CLI v${version}`);
608
532
  });
609
- context.on("kubb:generation:start", () => {
533
+ context.hook("kubb:generation:start", () => {
610
534
  const text = getMessage("Generation started");
611
535
  console.log(text);
612
536
  });
613
- context.on("kubb:plugin:start", ({ plugin }) => {
537
+ context.hook("kubb:plugin:start", ({ plugin }) => {
614
538
  if (logLevel$4 <= logLevel.silent) return;
615
539
  const text = getMessage(`Generating ${plugin.name}`);
616
540
  console.log(text);
617
541
  });
618
- context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
542
+ context.hook("kubb:plugin:end", ({ plugin, duration, success }) => {
619
543
  if (logLevel$4 <= logLevel.silent) return;
620
544
  const durationStr = formatMs(duration);
621
545
  const text = getMessage(success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`);
622
546
  console.log(text);
623
547
  });
624
- context.on("kubb:files:processing:start", ({ files }) => {
548
+ context.hook("kubb:files:processing:start", ({ files }) => {
625
549
  if (logLevel$4 <= logLevel.silent) return;
626
550
  const text = getMessage(`Writing ${files.length} files`);
627
551
  console.log(text);
628
552
  });
629
- context.on("kubb:files:processing:update", ({ files }) => {
553
+ context.hook("kubb:files:processing:update", ({ files }) => {
630
554
  if (logLevel$4 <= logLevel.silent) return;
631
555
  for (const { file, config } of files) console.log(getMessage(`Writing ${relative(config.root, file.path)}`));
632
556
  });
633
- context.on("kubb:files:processing:end", () => {
557
+ context.hook("kubb:files:processing:end", () => {
634
558
  if (logLevel$4 <= logLevel.silent) return;
635
559
  const text = getMessage("Files written successfully");
636
560
  console.log(text);
637
561
  });
638
- context.on("kubb:generation:end", ({ config }) => {
562
+ context.hook("kubb:generation:end", ({ config }) => {
639
563
  const text = getMessage(config.name ? `Generation completed for ${config.name}` : "Generation completed");
640
564
  console.log(text);
641
565
  });
@@ -645,13 +569,13 @@ const plainLogger = defineLogger({
645
569
  onStep("kubb:lint:end", "Lint completed");
646
570
  onStep("kubb:hooks:start", "Hooks started");
647
571
  onStep("kubb:hooks:end", "Hooks completed");
648
- context.on("kubb:hook:start", ({ id, command, args }) => {
572
+ context.hook("kubb:hook:start", ({ id, command, args }) => {
649
573
  if (logLevel$4 <= logLevel.silent) return;
650
574
  if (id) hookTimer.start(id);
651
575
  const commandWithArgs = formatCommandWithArgs(command, args);
652
576
  console.log(getMessage(`Hook ${commandWithArgs} started`));
653
577
  });
654
- context.on("kubb:hook:end", ({ id, command, args, success, error, stdout, stderr }) => {
578
+ context.hook("kubb:hook:end", ({ id, command, args, success, error, stdout, stderr }) => {
655
579
  if (logLevel$4 <= logLevel.silent) return;
656
580
  const ms = id ? hookTimer.end(id) : void 0;
657
581
  const durationStr = ms !== void 0 ? ` in ${formatMs(ms)}` : "";
@@ -682,6 +606,22 @@ function formatMessage(message, logLevel$3) {
682
606
  return message;
683
607
  }
684
608
  /**
609
+ * First stack frames for verbose error output, including an optional `cause` chain.
610
+ */
611
+ function formatErrorFrames(error) {
612
+ if (!error.stack) return null;
613
+ const frames = error.stack.split("\n").slice(1, 4).map((frame) => frame.trim());
614
+ const caused = toCause(error);
615
+ if (!caused?.stack) return { frames };
616
+ return {
617
+ frames,
618
+ cause: {
619
+ header: `└─ caused by ${caused.message}`,
620
+ frames: caused.stack.split("\n").slice(1, 4).map((frame) => frame.trim())
621
+ }
622
+ };
623
+ }
624
+ /**
685
625
  * Build the progress summary line shown by the clack logger.
686
626
  * Returns null when there is nothing to display.
687
627
  */
@@ -742,9 +682,6 @@ function createHookTimer() {
742
682
  if (!hrStart) return;
743
683
  starts.delete(id);
744
684
  return getElapsedMs(hrStart);
745
- },
746
- clear() {
747
- starts.clear();
748
685
  }
749
686
  };
750
687
  }
@@ -757,20 +694,12 @@ function createHookTimer() {
757
694
  function formatCommandWithArgs(command, args) {
758
695
  return args?.length ? `${command} ${args.join(" ")}` : command;
759
696
  }
760
- function detectLogger() {
761
- if (canUseTTY()) return "clack";
762
- return "plain";
763
- }
764
- const logMapper = {
765
- clack: clackLogger,
766
- plain: plainLogger
767
- };
768
697
  /**
769
- * Bridges a {@link Reporter} onto the run's event emitter: calls `report` with each config's
698
+ * Bridges a {@link Reporter} onto the run's hook emitter: calls `report` with each config's
770
699
  * {@link GenerationResult} on `kubb:generation:end`. The reporter never touches the emitter.
771
700
  */
772
701
  function installReporter(context, reporter, ctx) {
773
- context.on("kubb:generation:end", async ({ config, diagnostics = [], filesCreated = 0, status = "success", hrStart = process$1.hrtime() }) => {
702
+ context.hook("kubb:generation:end", async ({ config, diagnostics = [], filesCreated = 0, status = "success", hrStart = process$1.hrtime() }) => {
774
703
  await reporter.report({
775
704
  config,
776
705
  diagnostics,
@@ -779,7 +708,7 @@ function installReporter(context, reporter, ctx) {
779
708
  hrStart
780
709
  }, ctx);
781
710
  });
782
- if (reporter.drain) context.on("kubb:lifecycle:end", () => reporter.drain?.(ctx));
711
+ if (reporter.drain) context.hook("kubb:lifecycle:end", () => reporter.drain?.(ctx));
783
712
  }
784
713
  /**
785
714
  * Installs the live logger (the TUI view) and the given reporters (the output). The reporters are
@@ -798,10 +727,7 @@ async function setupReporters(context, { logLevel, reporters }) {
798
727
  for (const reporter of reporters) {
799
728
  if (reporter.name === "cli") {
800
729
  if (hasJson) continue;
801
- const type = detectLogger();
802
- const logger = logMapper[type];
803
- if (!logger) throw new Error(`Unknown adapter type: ${type}`);
804
- await logger.install(context, { logLevel });
730
+ await (canUseTTY() ? clackLogger : plainLogger).install(context, { logLevel });
805
731
  }
806
732
  installReporter(context, reporter, ctx);
807
733
  }
@@ -912,6 +838,34 @@ async function getConfigs({ configPath, input, watch, logLevel }) {
912
838
  }))
913
839
  };
914
840
  }
841
+ let hookSequence = 0;
842
+ /**
843
+ * Returns a process-unique id that correlates a hook's `kubb:hook:start` and `kubb:hook:end`
844
+ * emissions, which loggers use to key their per-hook state.
845
+ */
846
+ function createHookId() {
847
+ hookSequence += 1;
848
+ return `hook-${hookSequence}`;
849
+ }
850
+ /**
851
+ * Returns `true` when `latest` is a newer semver version than `current`. Compares each numeric
852
+ * part, so `5.10.0` beats `5.9.0` where a plain string comparison would not. Prerelease
853
+ * suffixes are ignored, and a malformed version never reports an update.
854
+ *
855
+ * @example Double-digit minor
856
+ * `isNewerVersion('5.9.0', '5.10.0') // true`
857
+ *
858
+ * @example String comparison would get this wrong the other way
859
+ * `isNewerVersion('5.10.0', '5.9.0') // false`
860
+ */
861
+ function isNewerVersion(current, latest) {
862
+ const release = (value) => value.split("-")[0] ?? "";
863
+ const isNumeric = (value) => /^\d+(\.\d+)*$/.test(value);
864
+ const currentRelease = release(current);
865
+ const latestRelease = release(latest);
866
+ if (!isNumeric(currentRelease) || !isNumeric(latestRelease)) return false;
867
+ return currentRelease.localeCompare(latestRelease, void 0, { numeric: true }) < 0;
868
+ }
915
869
  /**
916
870
  * Tokenizes a shell command string, respecting single and double quotes.
917
871
  *
@@ -925,65 +879,74 @@ function tokenize(command) {
925
879
  return (command.match(/[^\s"']+|"([^"]*)"|'([^']*)'/g) ?? []).map((token) => token.replace(/^["']|["']$/g, ""));
926
880
  }
927
881
  /**
928
- * Runs the `done` hooks defined in a Kubb config in sequence.
882
+ * Runs the `done` hooks defined in a Kubb config in sequence and returns each hook's outcome,
883
+ * so the caller can turn failures into diagnostics.
929
884
  */
930
885
  async function executeHooks({ configHooks, hooks }) {
931
886
  const commands = Array.isArray(configHooks.done) ? configHooks.done : [configHooks.done].filter(Boolean);
887
+ const results = [];
932
888
  for (const command of commands) {
933
889
  const [cmd, ...args] = tokenize(command);
934
890
  if (!cmd) continue;
935
- const hookId = hash("sha256", command, "hex");
891
+ const hookId = createHookId();
936
892
  const commandWithArgs = [cmd, ...args].join(" ");
937
- await hooks.emit("kubb:hook:start", {
893
+ await hooks.callHook("kubb:hook:start", {
938
894
  id: hookId,
939
895
  command: cmd,
940
896
  args
941
897
  });
942
- await runHook({
898
+ results.push(await runHook({
943
899
  id: hookId,
944
900
  command: cmd,
945
901
  args,
946
902
  commandWithArgs,
947
903
  hooks
948
- });
904
+ }));
949
905
  }
906
+ return results;
950
907
  }
951
908
  /**
952
- * Spawns a hook command and reports its outcome through `kubb:hook:end`.
953
- * A non-zero exit signals failure via `kubb:hook:end` rather than throwing, so the caller can turn
909
+ * Spawns a hook command and returns its outcome, mirroring it through `kubb:hook:end` for the
910
+ * loggers. A non-zero exit returns `success: false` rather than throwing, so the caller can turn
954
911
  * it into a diagnostic. Other spawn errors do the same. Output is streamed through `kubb:hook:line`
955
912
  * only while a listener is attached.
956
913
  */
957
914
  async function runHook({ id, command, args, commandWithArgs, hooks }) {
958
- const emitEnd = (success, error, output) => hooks.emit("kubb:hook:end", {
959
- command,
960
- args,
961
- id,
962
- success,
963
- error,
964
- ...output
965
- });
915
+ const emitEnd = async (result) => {
916
+ await hooks.callHook("kubb:hook:end", {
917
+ command,
918
+ args,
919
+ id,
920
+ ...result
921
+ });
922
+ return result;
923
+ };
966
924
  const stream = hooks.listenerCount("kubb:hook:line") > 0;
967
925
  try {
968
926
  const proc = x(command, [...args ?? []], {
969
927
  nodeOptions: { detached: process$1.platform !== "win32" },
970
928
  throwOnError: true
971
929
  });
972
- if (stream) for await (const line of proc) await hooks.emit("kubb:hook:line", {
930
+ if (stream) for await (const line of proc) await hooks.callHook("kubb:hook:line", {
973
931
  id,
974
932
  line
975
933
  });
976
934
  await proc;
977
- await hooks.emit("kubb:success", { message: `${styleText("dim", commandWithArgs)} successfully executed` });
978
- await emitEnd(true, null);
935
+ await hooks.callHook("kubb:success", { message: `${styleText("dim", commandWithArgs)} successfully executed` });
936
+ return emitEnd({
937
+ success: true,
938
+ error: null
939
+ });
979
940
  } catch (err) {
980
- if (!(err instanceof NonZeroExitError)) {
981
- await emitEnd(false, toError(err));
982
- return;
983
- }
941
+ if (!(err instanceof NonZeroExitError)) return emitEnd({
942
+ success: false,
943
+ error: toError(err)
944
+ });
984
945
  const stderr = err.output?.stderr ?? "";
985
946
  const stdout = err.output?.stdout ?? "";
986
- await emitEnd(false, /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`), {
947
+ return emitEnd({
948
+ success: false,
949
+ error: /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`),
987
950
  stdout,
988
951
  stderr
989
952
  });
@@ -991,7 +954,9 @@ async function runHook({ id, command, args, commandWithArgs, hooks }) {
991
954
  }
992
955
  /**
993
956
  * Starts a file watcher on the given paths and calls `cb` on any change.
994
- * Ignores `.git` and `node_modules` directories.
957
+ * Ignores `.git` and `node_modules` directories. Event bursts (an editor save emits several)
958
+ * are debounced into one build, and builds never overlap: changes during a build queue exactly
959
+ * one rebuild.
995
960
  */
996
961
  async function startWatcher(path, cb, log = {
997
962
  info: console.log,
@@ -1000,7 +965,8 @@ async function startWatcher(path, cb, log = {
1000
965
  const { watch } = await import("chokidar");
1001
966
  const watcher = watch(path, {
1002
967
  ignorePermissionErrors: true,
1003
- ignored: WATCHER_IGNORED_PATHS
968
+ ignored: WATCHER_IGNORED_PATHS,
969
+ ignoreInitial: true
1004
970
  });
1005
971
  process$1.once("SIGINT", () => {
1006
972
  watcher.close();
@@ -1008,78 +974,70 @@ async function startWatcher(path, cb, log = {
1008
974
  process$1.once("SIGTERM", () => {
1009
975
  watcher.close();
1010
976
  });
1011
- watcher.on("all", async (type, file) => {
977
+ const runBuild = createSerialRunner({
978
+ run: () => cb(path),
979
+ onError: () => log.error(styleText("red", "Watcher failed"))
980
+ });
981
+ let debounceTimer = null;
982
+ watcher.on("all", (type, file) => {
1012
983
  log.info(styleText("yellow", styleText("bold", `Change detected: ${type} ${file}`)));
1013
- try {
1014
- await cb(path);
1015
- } catch (_e) {
1016
- log.error(styleText("red", "Watcher failed"));
1017
- }
984
+ if (debounceTimer) clearTimeout(debounceTimer);
985
+ debounceTimer = setTimeout(() => {
986
+ debounceTimer = null;
987
+ runBuild();
988
+ }, 100);
1018
989
  });
1019
990
  }
1020
991
  //#endregion
1021
992
  //#region src/runners/generate/run.ts
1022
993
  /**
1023
- * Registers a one-shot `kubb:hook:end` listener for `hookId` before the caller emits `kubb:hook:start`,
1024
- * so a synchronous emitter cannot fire end before the listener is attached.
994
+ * Runs one formatter or linter pass over the output directory. Returns the failure instead of
995
+ * throwing, so the caller can turn it into a coded diagnostic. Failures never render here:
996
+ * the caller emits them through `Diagnostics.emit`, like every other diagnostic.
1025
997
  */
1026
- function waitForHookEnd(hooks, hookId, onSuccess, fallbackErrorMessage) {
1027
- return new Promise((resolve, reject) => {
1028
- const handler = (ctx) => {
1029
- if (ctx.id !== hookId) return;
1030
- hooks.off("kubb:hook:end", handler);
1031
- if (!ctx.success) {
1032
- reject(ctx.error ?? new Error(fallbackErrorMessage));
1033
- return;
1034
- }
1035
- Promise.resolve(onSuccess()).then(resolve).catch(reject);
1036
- };
1037
- hooks.on("kubb:hook:end", handler);
1038
- });
1039
- }
1040
- async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, configName, outputPath, logLevel: logLevel$1, hooks, onStart, onEnd }) {
998
+ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, outputPath, logLevel: logLevel$1, hooks, onStart, onEnd }) {
1041
999
  await onStart();
1042
1000
  let resolvedTool = toolValue;
1043
1001
  if (resolvedTool === "auto") {
1044
1002
  const detected = await detect();
1045
- if (!detected) await hooks.emit("kubb:warn", { message: noToolMessage });
1003
+ if (!detected) await hooks.callHook("kubb:warn", { message: noToolMessage });
1046
1004
  else {
1047
1005
  resolvedTool = detected;
1048
- await hooks.emit("kubb:info", { message: `Auto-detected ${toolLabel}: ${styleText("dim", resolvedTool)}` });
1006
+ await hooks.callHook("kubb:info", { message: `Auto-detected ${toolLabel}: ${styleText("dim", resolvedTool)}` });
1049
1007
  }
1050
1008
  }
1051
- let toolError;
1009
+ let toolError = null;
1052
1010
  if (resolvedTool && resolvedTool !== "auto" && resolvedTool in toolMap && existsSync(outputPath)) {
1053
1011
  const toolConfig = toolMap[resolvedTool];
1054
- const hookId = hash("sha256", [configName, resolvedTool].filter(Boolean).join("-"), "hex");
1055
1012
  const successMessage = [
1056
1013
  `${successPrefix} with ${styleText("dim", resolvedTool)}`,
1057
1014
  logLevel$1 >= logLevel.info ? `on ${styleText("dim", outputPath)}` : void 0,
1058
1015
  "successfully"
1059
1016
  ].filter(Boolean).join(" ");
1060
1017
  try {
1018
+ const hookId = createHookId();
1061
1019
  const hookArgs = toolConfig.args(outputPath);
1062
1020
  const commandWithArgs = [toolConfig.command, ...hookArgs].join(" ");
1063
- const hookEndPromise = waitForHookEnd(hooks, hookId, () => hooks.emit("kubb:success", { message: successMessage }), toolConfig.errorMessage);
1064
- await hooks.emit("kubb:hook:start", {
1021
+ await hooks.callHook("kubb:hook:start", {
1065
1022
  id: hookId,
1066
1023
  command: toolConfig.command,
1067
1024
  args: hookArgs
1068
1025
  });
1069
- runHook({
1026
+ const result = await runHook({
1070
1027
  id: hookId,
1071
1028
  command: toolConfig.command,
1072
1029
  args: hookArgs,
1073
1030
  commandWithArgs,
1074
1031
  hooks
1075
- }).catch(() => {});
1076
- await hookEndPromise;
1032
+ });
1033
+ if (result.success) await hooks.callHook("kubb:success", { message: successMessage });
1034
+ else toolError = result.error ?? new Error(toolConfig.errorMessage);
1077
1035
  } catch (caughtError) {
1078
1036
  toolError = toError(caughtError);
1079
1037
  }
1080
1038
  }
1081
1039
  await onEnd();
1082
- if (toolError) throw toolError;
1040
+ return toolError;
1083
1041
  }
1084
1042
  async function generate(options) {
1085
1043
  const { input, hooks, logLevel } = options;
@@ -1090,22 +1048,21 @@ async function generate(options) {
1090
1048
  input: inputPath ? {
1091
1049
  ...options.config.input,
1092
1050
  path: inputPath
1093
- } : options.config.input,
1094
- ...options.config.output
1051
+ } : options.config.input
1095
1052
  };
1096
1053
  const kubb = createKubb(config, { hooks });
1097
- await hooks.emit("kubb:generation:start", { config });
1098
- await hooks.emit("kubb:info", {
1054
+ await hooks.callHook("kubb:generation:start", { config });
1055
+ await hooks.callHook("kubb:info", {
1099
1056
  message: config.name ? `Setup generation ${styleText("bold", config.name)}` : "Setup generation",
1100
1057
  info: inputPath
1101
1058
  });
1102
1059
  await kubb.setup();
1103
- await hooks.emit("kubb:info", {
1060
+ await hooks.callHook("kubb:info", {
1104
1061
  message: config.name ? `Build generation ${styleText("bold", config.name)}` : "Build generation",
1105
1062
  info: inputPath
1106
1063
  });
1107
1064
  const { files, diagnostics, driver } = await kubb.safeBuild();
1108
- await hooks.emit("kubb:info", { message: "Load summary" });
1065
+ await hooks.callHook("kubb:info", { message: "Load summary" });
1109
1066
  const telemetryPlugins = Array.from(driver.plugins.values(), (p) => ({
1110
1067
  name: p.name,
1111
1068
  options: p.options
@@ -1121,11 +1078,11 @@ async function generate(options) {
1121
1078
  for (const diagnostic of diagnostics) {
1122
1079
  if (!Diagnostics.isProblem(diagnostic)) continue;
1123
1080
  const unknown = Diagnostics.narrow(diagnostic, Diagnostics.code.unknown);
1124
- if (unknown) await hooks.emit("kubb:error", { error: unknown.cause ?? new Error(unknown.message) });
1081
+ if (unknown) await hooks.callHook("kubb:error", { error: unknown.cause ?? new Error(unknown.message) });
1125
1082
  else await Diagnostics.emit(hooks, diagnostic);
1126
1083
  }
1127
1084
  if (Diagnostics.hasError(diagnostics)) {
1128
- await hooks.emit("kubb:generation:end", {
1085
+ await hooks.callHook("kubb:generation:end", {
1129
1086
  config,
1130
1087
  storage: kubb.storage,
1131
1088
  diagnostics,
@@ -1138,77 +1095,70 @@ async function generate(options) {
1138
1095
  }
1139
1096
  const outputPath = path.resolve(config.root, config.output.path);
1140
1097
  const outputDiagnostics = [];
1141
- const toolPasses = [config.output.format && {
1142
- code: Diagnostics.code.formatFailed,
1143
- toolValue: config.output.format,
1144
- detect: () => detectTool([
1145
- "oxfmt",
1146
- "biome",
1147
- "prettier"
1148
- ]),
1149
- toolMap: formatters,
1150
- toolLabel: "formatter",
1151
- successPrefix: "Formatting",
1152
- noToolMessage: "No formatter found (oxfmt, biome, or prettier). Skipping formatting.",
1153
- onStart: () => hooks.emit("kubb:format:start"),
1154
- onEnd: () => hooks.emit("kubb:format:end")
1155
- }, config.output.lint && {
1156
- code: Diagnostics.code.lintFailed,
1157
- toolValue: config.output.lint,
1158
- detect: () => detectTool([
1159
- "oxlint",
1160
- "biome",
1161
- "eslint"
1162
- ]),
1163
- toolMap: linters,
1164
- toolLabel: "linter",
1165
- successPrefix: "Linting",
1166
- noToolMessage: "No linter found (oxlint, biome, or eslint). Skipping linting.",
1167
- onStart: () => hooks.emit("kubb:lint:start"),
1168
- onEnd: () => hooks.emit("kubb:lint:end")
1169
- }].filter(Boolean);
1170
- for (const { code, ...pass } of toolPasses) try {
1171
- await runToolPass({
1172
- ...pass,
1173
- configName: config.name,
1098
+ const reportOutputFailure = async (code, label, error) => {
1099
+ const diagnostic = outputDiagnostic(code, label, error);
1100
+ outputDiagnostics.push(diagnostic);
1101
+ await Diagnostics.emit(hooks, diagnostic);
1102
+ };
1103
+ if (config.output.format) {
1104
+ const error = await runToolPass({
1105
+ toolValue: config.output.format,
1106
+ detect: () => detectTool([
1107
+ "oxfmt",
1108
+ "biome",
1109
+ "prettier"
1110
+ ]),
1111
+ toolMap: formatters,
1112
+ toolLabel: "formatter",
1113
+ successPrefix: "Formatting",
1114
+ noToolMessage: "No formatter found (oxfmt, biome, or prettier). Skipping formatting.",
1115
+ onStart: () => hooks.callHook("kubb:format:start"),
1116
+ onEnd: () => hooks.callHook("kubb:format:end"),
1174
1117
  outputPath,
1175
1118
  logLevel,
1176
1119
  hooks
1177
1120
  });
1178
- } catch (caughtError) {
1179
- const diagnostic = outputDiagnostic(code, pass.toolLabel, caughtError);
1180
- outputDiagnostics.push(diagnostic);
1181
- await Diagnostics.emit(hooks, diagnostic);
1121
+ if (error) await reportOutputFailure(Diagnostics.code.formatFailed, "formatter", error);
1122
+ }
1123
+ if (config.output.lint) {
1124
+ const error = await runToolPass({
1125
+ toolValue: config.output.lint,
1126
+ detect: () => detectTool([
1127
+ "oxlint",
1128
+ "biome",
1129
+ "eslint"
1130
+ ]),
1131
+ toolMap: linters,
1132
+ toolLabel: "linter",
1133
+ successPrefix: "Linting",
1134
+ noToolMessage: "No linter found (oxlint, biome, or eslint). Skipping linting.",
1135
+ onStart: () => hooks.callHook("kubb:lint:start"),
1136
+ onEnd: () => hooks.callHook("kubb:lint:end"),
1137
+ outputPath,
1138
+ logLevel,
1139
+ hooks
1140
+ });
1141
+ if (error) await reportOutputFailure(Diagnostics.code.lintFailed, "linter", error);
1182
1142
  }
1183
1143
  if (config.hooks) {
1184
- await hooks.emit("kubb:hooks:start");
1185
- const hookFailures = [];
1186
- const onHookEnd = (ctx) => {
1187
- if (!ctx.success) hookFailures.push(ctx.error ?? /* @__PURE__ */ new Error("Post-generate hook failed"));
1188
- };
1189
- hooks.on("kubb:hook:end", onHookEnd);
1190
- try {
1191
- await executeHooks({
1192
- configHooks: config.hooks,
1193
- hooks
1194
- });
1195
- } finally {
1196
- hooks.off("kubb:hook:end", onHookEnd);
1197
- }
1198
- for (const error of hookFailures) {
1199
- const diagnostic = outputDiagnostic(Diagnostics.code.hookFailed, "Post-generate hook", error);
1200
- outputDiagnostics.push(diagnostic);
1201
- await Diagnostics.emit(hooks, diagnostic);
1144
+ await hooks.callHook("kubb:hooks:start");
1145
+ const hookResults = await executeHooks({
1146
+ configHooks: config.hooks,
1147
+ hooks
1148
+ });
1149
+ for (const result of hookResults) {
1150
+ if (result.success) continue;
1151
+ await reportOutputFailure(Diagnostics.code.hookFailed, "Post-generate hook", result.error ?? /* @__PURE__ */ new Error("Post-generate hook failed"));
1202
1152
  }
1203
- await hooks.emit("kubb:hooks:end");
1153
+ await hooks.callHook("kubb:hooks:end");
1204
1154
  }
1205
1155
  const finalDiagnostics = [...diagnostics, ...outputDiagnostics];
1206
1156
  const failed = Diagnostics.hasError(outputDiagnostics);
1207
- if (!failed) await hooks.emit("kubb:success", {
1157
+ if (!failed) await hooks.callHook("kubb:success", {
1208
1158
  message: "Generation succeeded",
1209
1159
  info: inputPath
1210
1160
  });
1211
- await hooks.emit("kubb:generation:end", {
1161
+ await hooks.callHook("kubb:generation:end", {
1212
1162
  config,
1213
1163
  storage: kubb.storage,
1214
1164
  diagnostics: finalDiagnostics,
@@ -1235,8 +1185,8 @@ function outputDiagnostic(code, label, caughtError) {
1235
1185
  }
1236
1186
  async function checkForUpdate(hooks) {
1237
1187
  try {
1238
- const data = await (await fetch(KUBB_NPM_PACKAGE_URL)).json();
1239
- if (data.version && version < data.version) await Diagnostics.emit(hooks, Diagnostics.update({
1188
+ const data = await (await fetch(KUBB_NPM_PACKAGE_URL, { signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS) })).json();
1189
+ if (data.version && isNewerVersion(version, data.version)) await Diagnostics.emit(hooks, Diagnostics.update({
1240
1190
  currentVersion: version,
1241
1191
  latestVersion: data.version
1242
1192
  }));
@@ -1249,7 +1199,7 @@ async function checkForUpdate(hooks) {
1249
1199
  */
1250
1200
  async function run({ input, configPath, logLevel: logLevelKey, watch, reporters: cliReporters }) {
1251
1201
  const logLevel$2 = logLevel[logLevelKey] ?? logLevel.info;
1252
- const hooks = new AsyncEventEmitter();
1202
+ const hooks = new Hookable();
1253
1203
  let configs;
1254
1204
  let resolvedConfigPath;
1255
1205
  try {
@@ -1266,7 +1216,7 @@ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters:
1266
1216
  logLevel: logLevel$2,
1267
1217
  reporters: [cliReporter]
1268
1218
  });
1269
- await hooks.emit("kubb:error", { error: toError(error) });
1219
+ await hooks.callHook("kubb:error", { error: toError(error) });
1270
1220
  process$1.exit(1);
1271
1221
  }
1272
1222
  const requestedNames = cliReporters?.length ? cliReporters : ["cli"];
@@ -1274,32 +1224,40 @@ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters:
1274
1224
  logLevel: logLevel$2,
1275
1225
  reporters: selectReporters(configs[0]?.reporters ?? [], requestedNames)
1276
1226
  });
1277
- await hooks.emit("kubb:lifecycle:start", { version });
1227
+ await hooks.callHook("kubb:lifecycle:start", { version });
1278
1228
  await checkForUpdate(hooks);
1279
1229
  try {
1280
1230
  const relativeConfigPath = path.relative(process$1.cwd(), resolvedConfigPath);
1281
- await hooks.emit("kubb:info", {
1231
+ await hooks.callHook("kubb:info", {
1282
1232
  message: "Config loaded",
1283
1233
  info: relativeConfigPath
1284
1234
  });
1285
- await hooks.emit("kubb:success", {
1235
+ await hooks.callHook("kubb:success", {
1286
1236
  message: "Config loaded successfully",
1287
1237
  info: relativeConfigPath
1288
1238
  });
1289
1239
  let anyFailed = false;
1290
- for (const config of configs) if (config.input && "path" in config.input && watch) await startWatcher([input || config.input.path], async (paths) => {
1291
- await generate({
1292
- input,
1293
- config,
1294
- logLevel: logLevel$2,
1295
- hooks
1240
+ for (const config of configs) if (config.input && "path" in config.input && watch) {
1241
+ const watchedPaths = [input || config.input.path];
1242
+ const build = async (paths) => {
1243
+ await generate({
1244
+ input,
1245
+ config,
1246
+ logLevel: logLevel$2,
1247
+ hooks
1248
+ });
1249
+ clack.log.step(styleText("yellow", `Watching for changes in ${paths.join(" and ")}`));
1250
+ };
1251
+ try {
1252
+ await build(watchedPaths);
1253
+ } catch (buildError) {
1254
+ await hooks.callHook("kubb:error", { error: toError(buildError) });
1255
+ }
1256
+ await startWatcher(watchedPaths, build, {
1257
+ info: (msg) => clack.log.info(msg),
1258
+ error: (msg) => clack.log.error(msg)
1296
1259
  });
1297
- clack.log.step(styleText("yellow", `Watching for changes in ${paths.join(" and ")}`));
1298
- }, {
1299
- info: (msg) => clack.log.info(msg),
1300
- error: (msg) => clack.log.error(msg)
1301
- });
1302
- else try {
1260
+ } else try {
1303
1261
  if (!await generate({
1304
1262
  input,
1305
1263
  config,
@@ -1307,17 +1265,17 @@ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters:
1307
1265
  hooks
1308
1266
  })) anyFailed = true;
1309
1267
  } catch (configError) {
1310
- await hooks.emit("kubb:error", { error: toError(configError) });
1268
+ await hooks.callHook("kubb:error", { error: toError(configError) });
1311
1269
  anyFailed = true;
1312
1270
  }
1313
- await hooks.emit("kubb:lifecycle:end");
1271
+ await hooks.callHook("kubb:lifecycle:end");
1314
1272
  if (anyFailed) process$1.exit(1);
1315
1273
  } catch (error) {
1316
- await hooks.emit("kubb:error", { error: toError(error) });
1274
+ await hooks.callHook("kubb:error", { error: toError(error) });
1317
1275
  process$1.exit(1);
1318
1276
  }
1319
1277
  }
1320
1278
  //#endregion
1321
1279
  export { run };
1322
1280
 
1323
- //# sourceMappingURL=run-D8TRnpBB.js.map
1281
+ //# sourceMappingURL=run-BxMfDivH.js.map