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

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-C1bIl1nH.js} +34 -24
  6. package/dist/generate-C1bIl1nH.js.map +1 -0
  7. package/dist/{generate-Diob_zqj.cjs → generate-Cgmty90l.cjs} +34 -23
  8. package/dist/generate-Cgmty90l.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-DqgB69HY.cjs} +9 -10
  14. package/dist/init-DqgB69HY.cjs.map +1 -0
  15. package/dist/{init-Cn3jZVkf.js → init-EnhDxlBB.js} +10 -10
  16. package/dist/init-EnhDxlBB.js.map +1 -0
  17. package/dist/{mcp-n-IOoFZL.js → mcp-5DDTiwIA.js} +6 -6
  18. package/dist/mcp-5DDTiwIA.js.map +1 -0
  19. package/dist/{mcp-BxhOXHGD.cjs → mcp-DYmjmXQb.cjs} +5 -6
  20. package/dist/mcp-DYmjmXQb.cjs.map +1 -0
  21. package/dist/{package-BBBOfxZz.cjs → package-B57sF1X0.cjs} +2 -2
  22. package/dist/package-B57sF1X0.cjs.map +1 -0
  23. package/dist/package-CQbxcw7I.js +6 -0
  24. package/dist/package-CQbxcw7I.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-CCsw-Y-v.js} +226 -268
  32. package/dist/run-CCsw-Y-v.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-ChSqYuc1.cjs → run-DT9pRBLX.cjs} +228 -269
  36. package/dist/run-DT9pRBLX.cjs.map +1 -0
  37. package/dist/{run-DYkciQ4u.cjs → run-DYzUPE7Z.cjs} +4 -10
  38. package/dist/run-DYzUPE7Z.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-BTVjxR4L.cjs} +8 -9
  46. package/dist/validate-BTVjxR4L.cjs.map +1 -0
  47. package/dist/{validate-BoG8ZZgE.js → validate-CZFmahWd.js} +9 -9
  48. package/dist/validate-CZFmahWd.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,129 +1,21 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-Bx3C2hgW.cjs");
2
+ const require_Telemetry = require("./Telemetry-CwQYBgsg.cjs");
2
3
  const require_errors = require("./errors-gxFK0vrp.cjs");
3
- const require_Telemetry = require("./Telemetry-jtbchpQE.cjs");
4
- const require_tools = require("./tools-CZT9wSA6.cjs");
5
- const require_package = require("./package-BBBOfxZz.cjs");
4
+ const require_package = require("./package-B57sF1X0.cjs");
5
+ const require_tools = require("./tools-CeIABV0T.cjs");
6
6
  let node_util = require("node:util");
7
- let node_events = require("node:events");
8
- let node_crypto = require("node:crypto");
7
+ require("node:crypto");
8
+ let node_process = require("node:process");
9
+ node_process = require_rolldown_runtime.__toESM(node_process, 1);
9
10
  let node_fs = require("node:fs");
10
11
  let node_path = require("node:path");
11
12
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
12
- let node_process = require("node:process");
13
- node_process = require_rolldown_runtime.__toESM(node_process, 1);
14
13
  let _clack_prompts = require("@clack/prompts");
15
14
  _clack_prompts = require_rolldown_runtime.__toESM(_clack_prompts, 1);
16
15
  let _kubb_core = require("@kubb/core");
17
16
  let jiti = require("jiti");
18
17
  let tinyexec = require("tinyexec");
19
18
  let unconfig = require("unconfig");
20
- //#region ../../internals/utils/src/asyncEventEmitter.ts
21
- /**
22
- * Typed `EventEmitter` that awaits all async listeners before resolving.
23
- * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
24
- *
25
- * @example
26
- * ```ts
27
- * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
28
- * emitter.on('build', async (name) => { console.log(name) })
29
- * await emitter.emit('build', 'petstore') // all listeners awaited
30
- * ```
31
- */
32
- var AsyncEventEmitter = class {
33
- /**
34
- * Maximum number of listeners per event before Node emits a memory-leak warning.
35
- * @default 10
36
- */
37
- constructor(maxListener = 10) {
38
- this.#emitter.setMaxListeners(maxListener);
39
- }
40
- #emitter = new node_events.EventEmitter();
41
- /**
42
- * Emits `eventName` and awaits all registered listeners sequentially.
43
- * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
44
- *
45
- * @example
46
- * ```ts
47
- * await emitter.emit('build', 'petstore')
48
- * ```
49
- */
50
- emit(eventName, ...eventArgs) {
51
- const listeners = this.#emitter.listeners(eventName);
52
- if (listeners.length === 0) return;
53
- return this.#emitAll(eventName, listeners, eventArgs);
54
- }
55
- async #emitAll(eventName, listeners, eventArgs) {
56
- for (const listener of listeners) try {
57
- await listener(...eventArgs);
58
- } catch (err) {
59
- let serializedArgs;
60
- try {
61
- serializedArgs = JSON.stringify(eventArgs);
62
- } catch {
63
- serializedArgs = String(eventArgs);
64
- }
65
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: require_errors.toError(err) });
66
- }
67
- }
68
- /**
69
- * Registers a persistent listener for `eventName`.
70
- *
71
- * @example
72
- * ```ts
73
- * emitter.on('build', async (name) => { console.log(name) })
74
- * ```
75
- */
76
- on(eventName, handler) {
77
- this.#emitter.on(eventName, handler);
78
- }
79
- /**
80
- * Removes a previously registered listener.
81
- *
82
- * @example
83
- * ```ts
84
- * emitter.off('build', handler)
85
- * ```
86
- */
87
- off(eventName, handler) {
88
- this.#emitter.off(eventName, handler);
89
- }
90
- /**
91
- * Returns the number of listeners registered for `eventName`.
92
- *
93
- * @example
94
- * ```ts
95
- * emitter.on('build', handler)
96
- * emitter.listenerCount('build') // 1
97
- * ```
98
- */
99
- listenerCount(eventName) {
100
- return this.#emitter.listenerCount(eventName);
101
- }
102
- /**
103
- * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
104
- * Set this above the expected listener count when many listeners attach by design.
105
- *
106
- * @example
107
- * ```ts
108
- * emitter.setMaxListeners(40)
109
- * ```
110
- */
111
- setMaxListeners(max) {
112
- this.#emitter.setMaxListeners(max);
113
- }
114
- /**
115
- * Removes all listeners from every event channel.
116
- *
117
- * @example
118
- * ```ts
119
- * emitter.removeAll()
120
- * ```
121
- */
122
- removeAll() {
123
- this.#emitter.removeAllListeners();
124
- }
125
- };
126
- //#endregion
127
19
  //#region ../../internals/utils/src/time.ts
128
20
  /**
129
21
  * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
@@ -268,6 +160,43 @@ function formatMsWithColor(ms) {
268
160
  return (0, node_util.styleText)("red", formatted);
269
161
  }
270
162
  //#endregion
163
+ //#region ../../internals/utils/src/promise.ts
164
+ /**
165
+ * Wraps `run` so invocations never overlap: a trigger that lands while a run is in flight
166
+ * marks it dirty and runs once more after it finishes, no matter how many triggers arrived.
167
+ * Useful for event-driven reruns (a file watcher, a queue drain) where bursts should
168
+ * coalesce into a single trailing run.
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * const rebuild = createSerialRunner({
173
+ * run: () => build(),
174
+ * onError: (error) => log.error(error.message),
175
+ * })
176
+ * watcher.on('change', () => void rebuild())
177
+ * ```
178
+ */
179
+ function createSerialRunner({ run, onError }) {
180
+ let running = false;
181
+ let dirty = false;
182
+ return async () => {
183
+ if (running) {
184
+ dirty = true;
185
+ return;
186
+ }
187
+ running = true;
188
+ do {
189
+ dirty = false;
190
+ try {
191
+ await run();
192
+ } catch (error) {
193
+ onError(require_errors.toError(error));
194
+ }
195
+ } while (dirty);
196
+ running = false;
197
+ };
198
+ }
199
+ //#endregion
271
200
  //#region src/loggers/defineLogger.ts
272
201
  /**
273
202
  * Type-safe helper for authoring a {@link Logger}.
@@ -376,20 +305,18 @@ const clackLogger = defineLogger({
376
305
  _clack_prompts.log.warn(text);
377
306
  });
378
307
  context.on("kubb:error", ({ error }) => {
379
- const caused = require_errors.toCause(error);
380
308
  const text = [(0, node_util.styleText)("red", "✗"), error.message].join(" ");
381
309
  if (state.isSpinning) {
382
310
  stopSpinner(getMessage(text));
383
311
  return;
384
312
  }
385
313
  _clack_prompts.log.error(getMessage(text));
386
- if (logLevel >= _kubb_core.logLevel.verbose && error.stack) {
387
- const frames = error.stack.split("\n").slice(1, 4);
388
- for (const frame of frames) _clack_prompts.log.message(getMessage((0, node_util.styleText)("dim", frame.trim())));
389
- if (caused?.stack) {
390
- _clack_prompts.log.message((0, node_util.styleText)("dim", `└─ caused by ${caused.message}`));
391
- const frames = caused.stack.split("\n").slice(1, 4);
392
- for (const frame of frames) _clack_prompts.log.message(getMessage(` ${(0, node_util.styleText)("dim", frame.trim())}`));
314
+ const frames = logLevel >= _kubb_core.logLevel.verbose ? formatErrorFrames(error) : null;
315
+ if (frames) {
316
+ for (const frame of frames.frames) _clack_prompts.log.message(getMessage((0, node_util.styleText)("dim", frame)));
317
+ if (frames.cause) {
318
+ _clack_prompts.log.message((0, node_util.styleText)("dim", frames.cause.header));
319
+ for (const frame of frames.cause.frames) _clack_prompts.log.message(getMessage(` ${(0, node_util.styleText)("dim", frame)}`));
393
320
  }
394
321
  }
395
322
  });
@@ -589,16 +516,14 @@ const plainLogger = defineLogger({
589
516
  console.log(text);
590
517
  });
591
518
  context.on("kubb:error", ({ error }) => {
592
- const caused = require_errors.toCause(error);
593
519
  const text = getMessage(["✗", error.message].join(" "));
594
520
  console.log(text);
595
- if (logLevel >= _kubb_core.logLevel.verbose && error.stack) {
596
- const frames = error.stack.split("\n").slice(1, 4);
597
- for (const frame of frames) console.log(getMessage(frame.trim()));
598
- if (caused?.stack) {
599
- console.log(`└─ caused by ${caused.message}`);
600
- const frames = caused.stack.split("\n").slice(1, 4);
601
- for (const frame of frames) console.log(getMessage(` ${frame.trim()}`));
521
+ const frames = logLevel >= _kubb_core.logLevel.verbose ? formatErrorFrames(error) : null;
522
+ if (frames) {
523
+ for (const frame of frames.frames) console.log(getMessage(frame));
524
+ if (frames.cause) {
525
+ console.log(frames.cause.header);
526
+ for (const frame of frames.cause.frames) console.log(getMessage(` ${frame}`));
602
527
  }
603
528
  }
604
529
  });
@@ -685,6 +610,22 @@ function formatMessage(message, logLevel) {
685
610
  return message;
686
611
  }
687
612
  /**
613
+ * First stack frames for verbose error output, including an optional `cause` chain.
614
+ */
615
+ function formatErrorFrames(error) {
616
+ if (!error.stack) return null;
617
+ const frames = error.stack.split("\n").slice(1, 4).map((frame) => frame.trim());
618
+ const caused = require_errors.toCause(error);
619
+ if (!caused?.stack) return { frames };
620
+ return {
621
+ frames,
622
+ cause: {
623
+ header: `└─ caused by ${caused.message}`,
624
+ frames: caused.stack.split("\n").slice(1, 4).map((frame) => frame.trim())
625
+ }
626
+ };
627
+ }
628
+ /**
688
629
  * Build the progress summary line shown by the clack logger.
689
630
  * Returns null when there is nothing to display.
690
631
  */
@@ -745,9 +686,6 @@ function createHookTimer() {
745
686
  if (!hrStart) return;
746
687
  starts.delete(id);
747
688
  return getElapsedMs(hrStart);
748
- },
749
- clear() {
750
- starts.clear();
751
689
  }
752
690
  };
753
691
  }
@@ -760,14 +698,6 @@ function createHookTimer() {
760
698
  function formatCommandWithArgs(command, args) {
761
699
  return args?.length ? `${command} ${args.join(" ")}` : command;
762
700
  }
763
- function detectLogger() {
764
- if (require_Telemetry.canUseTTY()) return "clack";
765
- return "plain";
766
- }
767
- const logMapper = {
768
- clack: clackLogger,
769
- plain: plainLogger
770
- };
771
701
  /**
772
702
  * Bridges a {@link Reporter} onto the run's event emitter: calls `report` with each config's
773
703
  * {@link GenerationResult} on `kubb:generation:end`. The reporter never touches the emitter.
@@ -801,10 +731,7 @@ async function setupReporters(context, { logLevel, reporters }) {
801
731
  for (const reporter of reporters) {
802
732
  if (reporter.name === "cli") {
803
733
  if (hasJson) continue;
804
- const type = detectLogger();
805
- const logger = logMapper[type];
806
- if (!logger) throw new Error(`Unknown adapter type: ${type}`);
807
- await logger.install(context, { logLevel });
734
+ await (require_Telemetry.canUseTTY() ? clackLogger : plainLogger).install(context, { logLevel });
808
735
  }
809
736
  installReporter(context, reporter, ctx);
810
737
  }
@@ -915,6 +842,34 @@ async function getConfigs({ configPath, input, watch, logLevel }) {
915
842
  }))
916
843
  };
917
844
  }
845
+ let hookSequence = 0;
846
+ /**
847
+ * Returns a process-unique id that correlates a hook's `kubb:hook:start` and `kubb:hook:end`
848
+ * events, which loggers use to key their per-hook state.
849
+ */
850
+ function createHookId() {
851
+ hookSequence += 1;
852
+ return `hook-${hookSequence}`;
853
+ }
854
+ /**
855
+ * Returns `true` when `latest` is a newer semver version than `current`. Compares each numeric
856
+ * part, so `5.10.0` beats `5.9.0` where a plain string comparison would not. Prerelease
857
+ * suffixes are ignored, and a malformed version never reports an update.
858
+ *
859
+ * @example Double-digit minor
860
+ * `isNewerVersion('5.9.0', '5.10.0') // true`
861
+ *
862
+ * @example String comparison would get this wrong the other way
863
+ * `isNewerVersion('5.10.0', '5.9.0') // false`
864
+ */
865
+ function isNewerVersion(current, latest) {
866
+ const release = (value) => value.split("-")[0] ?? "";
867
+ const isNumeric = (value) => /^\d+(\.\d+)*$/.test(value);
868
+ const currentRelease = release(current);
869
+ const latestRelease = release(latest);
870
+ if (!isNumeric(currentRelease) || !isNumeric(latestRelease)) return false;
871
+ return currentRelease.localeCompare(latestRelease, void 0, { numeric: true }) < 0;
872
+ }
918
873
  /**
919
874
  * Tokenizes a shell command string, respecting single and double quotes.
920
875
  *
@@ -928,44 +883,48 @@ function tokenize(command) {
928
883
  return (command.match(/[^\s"']+|"([^"]*)"|'([^']*)'/g) ?? []).map((token) => token.replace(/^["']|["']$/g, ""));
929
884
  }
930
885
  /**
931
- * Runs the `done` hooks defined in a Kubb config in sequence.
886
+ * Runs the `done` hooks defined in a Kubb config in sequence and returns each hook's outcome,
887
+ * so the caller can turn failures into diagnostics.
932
888
  */
933
889
  async function executeHooks({ configHooks, hooks }) {
934
890
  const commands = Array.isArray(configHooks.done) ? configHooks.done : [configHooks.done].filter(Boolean);
891
+ const results = [];
935
892
  for (const command of commands) {
936
893
  const [cmd, ...args] = tokenize(command);
937
894
  if (!cmd) continue;
938
- const hookId = (0, node_crypto.hash)("sha256", command, "hex");
895
+ const hookId = createHookId();
939
896
  const commandWithArgs = [cmd, ...args].join(" ");
940
897
  await hooks.emit("kubb:hook:start", {
941
898
  id: hookId,
942
899
  command: cmd,
943
900
  args
944
901
  });
945
- await runHook({
902
+ results.push(await runHook({
946
903
  id: hookId,
947
904
  command: cmd,
948
905
  args,
949
906
  commandWithArgs,
950
907
  hooks
951
- });
908
+ }));
952
909
  }
910
+ return results;
953
911
  }
954
912
  /**
955
- * Spawns a hook command and reports its outcome through `kubb:hook:end`.
956
- * A non-zero exit signals failure via `kubb:hook:end` rather than throwing, so the caller can turn
913
+ * Spawns a hook command and returns its outcome, mirroring it through `kubb:hook:end` for the
914
+ * loggers. A non-zero exit returns `success: false` rather than throwing, so the caller can turn
957
915
  * it into a diagnostic. Other spawn errors do the same. Output is streamed through `kubb:hook:line`
958
916
  * only while a listener is attached.
959
917
  */
960
918
  async function runHook({ id, command, args, commandWithArgs, hooks }) {
961
- const emitEnd = (success, error, output) => hooks.emit("kubb:hook:end", {
962
- command,
963
- args,
964
- id,
965
- success,
966
- error,
967
- ...output
968
- });
919
+ const emitEnd = async (result) => {
920
+ await hooks.emit("kubb:hook:end", {
921
+ command,
922
+ args,
923
+ id,
924
+ ...result
925
+ });
926
+ return result;
927
+ };
969
928
  const stream = hooks.listenerCount("kubb:hook:line") > 0;
970
929
  try {
971
930
  const proc = (0, tinyexec.x)(command, [...args ?? []], {
@@ -978,15 +937,20 @@ async function runHook({ id, command, args, commandWithArgs, hooks }) {
978
937
  });
979
938
  await proc;
980
939
  await hooks.emit("kubb:success", { message: `${(0, node_util.styleText)("dim", commandWithArgs)} successfully executed` });
981
- await emitEnd(true, null);
940
+ return emitEnd({
941
+ success: true,
942
+ error: null
943
+ });
982
944
  } catch (err) {
983
- if (!(err instanceof tinyexec.NonZeroExitError)) {
984
- await emitEnd(false, require_errors.toError(err));
985
- return;
986
- }
945
+ if (!(err instanceof tinyexec.NonZeroExitError)) return emitEnd({
946
+ success: false,
947
+ error: require_errors.toError(err)
948
+ });
987
949
  const stderr = err.output?.stderr ?? "";
988
950
  const stdout = err.output?.stdout ?? "";
989
- await emitEnd(false, /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`), {
951
+ return emitEnd({
952
+ success: false,
953
+ error: /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`),
990
954
  stdout,
991
955
  stderr
992
956
  });
@@ -994,7 +958,9 @@ async function runHook({ id, command, args, commandWithArgs, hooks }) {
994
958
  }
995
959
  /**
996
960
  * Starts a file watcher on the given paths and calls `cb` on any change.
997
- * Ignores `.git` and `node_modules` directories.
961
+ * Ignores `.git` and `node_modules` directories. Event bursts (an editor save emits several)
962
+ * are debounced into one build, and builds never overlap: changes during a build queue exactly
963
+ * one rebuild.
998
964
  */
999
965
  async function startWatcher(path, cb, log = {
1000
966
  info: console.log,
@@ -1003,7 +969,8 @@ async function startWatcher(path, cb, log = {
1003
969
  const { watch } = await import("chokidar");
1004
970
  const watcher = watch(path, {
1005
971
  ignorePermissionErrors: true,
1006
- ignored: require_Telemetry.WATCHER_IGNORED_PATHS
972
+ ignored: require_Telemetry.WATCHER_IGNORED_PATHS,
973
+ ignoreInitial: true
1007
974
  });
1008
975
  node_process.default.once("SIGINT", () => {
1009
976
  watcher.close();
@@ -1011,36 +978,28 @@ async function startWatcher(path, cb, log = {
1011
978
  node_process.default.once("SIGTERM", () => {
1012
979
  watcher.close();
1013
980
  });
1014
- watcher.on("all", async (type, file) => {
981
+ const runBuild = createSerialRunner({
982
+ run: () => cb(path),
983
+ onError: () => log.error((0, node_util.styleText)("red", "Watcher failed"))
984
+ });
985
+ let debounceTimer = null;
986
+ watcher.on("all", (type, file) => {
1015
987
  log.info((0, node_util.styleText)("yellow", (0, node_util.styleText)("bold", `Change detected: ${type} ${file}`)));
1016
- try {
1017
- await cb(path);
1018
- } catch (_e) {
1019
- log.error((0, node_util.styleText)("red", "Watcher failed"));
1020
- }
988
+ if (debounceTimer) clearTimeout(debounceTimer);
989
+ debounceTimer = setTimeout(() => {
990
+ debounceTimer = null;
991
+ runBuild();
992
+ }, 100);
1021
993
  });
1022
994
  }
1023
995
  //#endregion
1024
996
  //#region src/runners/generate/run.ts
1025
997
  /**
1026
- * Registers a one-shot `kubb:hook:end` listener for `hookId` before the caller emits `kubb:hook:start`,
1027
- * so a synchronous emitter cannot fire end before the listener is attached.
998
+ * Runs one formatter or linter pass over the output directory. Returns the failure instead of
999
+ * throwing, so the caller can turn it into a coded diagnostic. Failures never render here:
1000
+ * the caller emits them through `Diagnostics.emit`, like every other diagnostic.
1028
1001
  */
1029
- function waitForHookEnd(hooks, hookId, onSuccess, fallbackErrorMessage) {
1030
- return new Promise((resolve, reject) => {
1031
- const handler = (ctx) => {
1032
- if (ctx.id !== hookId) return;
1033
- hooks.off("kubb:hook:end", handler);
1034
- if (!ctx.success) {
1035
- reject(ctx.error ?? new Error(fallbackErrorMessage));
1036
- return;
1037
- }
1038
- Promise.resolve(onSuccess()).then(resolve).catch(reject);
1039
- };
1040
- hooks.on("kubb:hook:end", handler);
1041
- });
1042
- }
1043
- async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, configName, outputPath, logLevel, hooks, onStart, onEnd }) {
1002
+ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, outputPath, logLevel, hooks, onStart, onEnd }) {
1044
1003
  await onStart();
1045
1004
  let resolvedTool = toolValue;
1046
1005
  if (resolvedTool === "auto") {
@@ -1051,38 +1010,38 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1051
1010
  await hooks.emit("kubb:info", { message: `Auto-detected ${toolLabel}: ${(0, node_util.styleText)("dim", resolvedTool)}` });
1052
1011
  }
1053
1012
  }
1054
- let toolError;
1013
+ let toolError = null;
1055
1014
  if (resolvedTool && resolvedTool !== "auto" && resolvedTool in toolMap && (0, node_fs.existsSync)(outputPath)) {
1056
1015
  const toolConfig = toolMap[resolvedTool];
1057
- const hookId = (0, node_crypto.hash)("sha256", [configName, resolvedTool].filter(Boolean).join("-"), "hex");
1058
1016
  const successMessage = [
1059
1017
  `${successPrefix} with ${(0, node_util.styleText)("dim", resolvedTool)}`,
1060
1018
  logLevel >= _kubb_core.logLevel.info ? `on ${(0, node_util.styleText)("dim", outputPath)}` : void 0,
1061
1019
  "successfully"
1062
1020
  ].filter(Boolean).join(" ");
1063
1021
  try {
1022
+ const hookId = createHookId();
1064
1023
  const hookArgs = toolConfig.args(outputPath);
1065
1024
  const commandWithArgs = [toolConfig.command, ...hookArgs].join(" ");
1066
- const hookEndPromise = waitForHookEnd(hooks, hookId, () => hooks.emit("kubb:success", { message: successMessage }), toolConfig.errorMessage);
1067
1025
  await hooks.emit("kubb:hook:start", {
1068
1026
  id: hookId,
1069
1027
  command: toolConfig.command,
1070
1028
  args: hookArgs
1071
1029
  });
1072
- runHook({
1030
+ const result = await runHook({
1073
1031
  id: hookId,
1074
1032
  command: toolConfig.command,
1075
1033
  args: hookArgs,
1076
1034
  commandWithArgs,
1077
1035
  hooks
1078
- }).catch(() => {});
1079
- await hookEndPromise;
1036
+ });
1037
+ if (result.success) await hooks.emit("kubb:success", { message: successMessage });
1038
+ else toolError = result.error ?? new Error(toolConfig.errorMessage);
1080
1039
  } catch (caughtError) {
1081
1040
  toolError = require_errors.toError(caughtError);
1082
1041
  }
1083
1042
  }
1084
1043
  await onEnd();
1085
- if (toolError) throw toolError;
1044
+ return toolError;
1086
1045
  }
1087
1046
  async function generate(options) {
1088
1047
  const { input, hooks, logLevel } = options;
@@ -1093,8 +1052,7 @@ async function generate(options) {
1093
1052
  input: inputPath ? {
1094
1053
  ...options.config.input,
1095
1054
  path: inputPath
1096
- } : options.config.input,
1097
- ...options.config.output
1055
+ } : options.config.input
1098
1056
  };
1099
1057
  const kubb = (0, _kubb_core.createKubb)(config, { hooks });
1100
1058
  await hooks.emit("kubb:generation:start", { config });
@@ -1141,67 +1099,60 @@ async function generate(options) {
1141
1099
  }
1142
1100
  const outputPath = node_path.default.resolve(config.root, config.output.path);
1143
1101
  const outputDiagnostics = [];
1144
- const toolPasses = [config.output.format && {
1145
- code: _kubb_core.Diagnostics.code.formatFailed,
1146
- toolValue: config.output.format,
1147
- detect: () => require_tools.detectTool([
1148
- "oxfmt",
1149
- "biome",
1150
- "prettier"
1151
- ]),
1152
- toolMap: require_tools.formatters,
1153
- toolLabel: "formatter",
1154
- successPrefix: "Formatting",
1155
- noToolMessage: "No formatter found (oxfmt, biome, or prettier). Skipping formatting.",
1156
- onStart: () => hooks.emit("kubb:format:start"),
1157
- onEnd: () => hooks.emit("kubb:format:end")
1158
- }, config.output.lint && {
1159
- code: _kubb_core.Diagnostics.code.lintFailed,
1160
- toolValue: config.output.lint,
1161
- detect: () => require_tools.detectTool([
1162
- "oxlint",
1163
- "biome",
1164
- "eslint"
1165
- ]),
1166
- toolMap: require_tools.linters,
1167
- toolLabel: "linter",
1168
- successPrefix: "Linting",
1169
- noToolMessage: "No linter found (oxlint, biome, or eslint). Skipping linting.",
1170
- onStart: () => hooks.emit("kubb:lint:start"),
1171
- onEnd: () => hooks.emit("kubb:lint:end")
1172
- }].filter(Boolean);
1173
- for (const { code, ...pass } of toolPasses) try {
1174
- await runToolPass({
1175
- ...pass,
1176
- configName: config.name,
1102
+ const reportOutputFailure = async (code, label, error) => {
1103
+ const diagnostic = outputDiagnostic(code, label, error);
1104
+ outputDiagnostics.push(diagnostic);
1105
+ await _kubb_core.Diagnostics.emit(hooks, diagnostic);
1106
+ };
1107
+ if (config.output.format) {
1108
+ const error = await runToolPass({
1109
+ toolValue: config.output.format,
1110
+ detect: () => require_tools.detectTool([
1111
+ "oxfmt",
1112
+ "biome",
1113
+ "prettier"
1114
+ ]),
1115
+ toolMap: require_tools.formatters,
1116
+ toolLabel: "formatter",
1117
+ successPrefix: "Formatting",
1118
+ noToolMessage: "No formatter found (oxfmt, biome, or prettier). Skipping formatting.",
1119
+ onStart: () => hooks.emit("kubb:format:start"),
1120
+ onEnd: () => hooks.emit("kubb:format:end"),
1177
1121
  outputPath,
1178
1122
  logLevel,
1179
1123
  hooks
1180
1124
  });
1181
- } catch (caughtError) {
1182
- const diagnostic = outputDiagnostic(code, pass.toolLabel, caughtError);
1183
- outputDiagnostics.push(diagnostic);
1184
- await _kubb_core.Diagnostics.emit(hooks, diagnostic);
1125
+ if (error) await reportOutputFailure(_kubb_core.Diagnostics.code.formatFailed, "formatter", error);
1126
+ }
1127
+ if (config.output.lint) {
1128
+ const error = await runToolPass({
1129
+ toolValue: config.output.lint,
1130
+ detect: () => require_tools.detectTool([
1131
+ "oxlint",
1132
+ "biome",
1133
+ "eslint"
1134
+ ]),
1135
+ toolMap: require_tools.linters,
1136
+ toolLabel: "linter",
1137
+ successPrefix: "Linting",
1138
+ noToolMessage: "No linter found (oxlint, biome, or eslint). Skipping linting.",
1139
+ onStart: () => hooks.emit("kubb:lint:start"),
1140
+ onEnd: () => hooks.emit("kubb:lint:end"),
1141
+ outputPath,
1142
+ logLevel,
1143
+ hooks
1144
+ });
1145
+ if (error) await reportOutputFailure(_kubb_core.Diagnostics.code.lintFailed, "linter", error);
1185
1146
  }
1186
1147
  if (config.hooks) {
1187
1148
  await hooks.emit("kubb:hooks:start");
1188
- const hookFailures = [];
1189
- const onHookEnd = (ctx) => {
1190
- if (!ctx.success) hookFailures.push(ctx.error ?? /* @__PURE__ */ new Error("Post-generate hook failed"));
1191
- };
1192
- hooks.on("kubb:hook:end", onHookEnd);
1193
- try {
1194
- await executeHooks({
1195
- configHooks: config.hooks,
1196
- hooks
1197
- });
1198
- } finally {
1199
- hooks.off("kubb:hook:end", onHookEnd);
1200
- }
1201
- for (const error of hookFailures) {
1202
- const diagnostic = outputDiagnostic(_kubb_core.Diagnostics.code.hookFailed, "Post-generate hook", error);
1203
- outputDiagnostics.push(diagnostic);
1204
- await _kubb_core.Diagnostics.emit(hooks, diagnostic);
1149
+ const hookResults = await executeHooks({
1150
+ configHooks: config.hooks,
1151
+ hooks
1152
+ });
1153
+ for (const result of hookResults) {
1154
+ if (result.success) continue;
1155
+ await reportOutputFailure(_kubb_core.Diagnostics.code.hookFailed, "Post-generate hook", result.error ?? /* @__PURE__ */ new Error("Post-generate hook failed"));
1205
1156
  }
1206
1157
  await hooks.emit("kubb:hooks:end");
1207
1158
  }
@@ -1238,8 +1189,8 @@ function outputDiagnostic(code, label, caughtError) {
1238
1189
  }
1239
1190
  async function checkForUpdate(hooks) {
1240
1191
  try {
1241
- const data = await (await fetch(require_Telemetry.KUBB_NPM_PACKAGE_URL)).json();
1242
- if (data.version && require_package.version < data.version) await _kubb_core.Diagnostics.emit(hooks, _kubb_core.Diagnostics.update({
1192
+ const data = await (await fetch(require_Telemetry.KUBB_NPM_PACKAGE_URL, { signal: AbortSignal.timeout(require_Telemetry.UPDATE_CHECK_TIMEOUT_MS) })).json();
1193
+ if (data.version && isNewerVersion(require_package.version, data.version)) await _kubb_core.Diagnostics.emit(hooks, _kubb_core.Diagnostics.update({
1243
1194
  currentVersion: require_package.version,
1244
1195
  latestVersion: data.version
1245
1196
  }));
@@ -1252,7 +1203,7 @@ async function checkForUpdate(hooks) {
1252
1203
  */
1253
1204
  async function run({ input, configPath, logLevel: logLevelKey, watch, reporters: cliReporters }) {
1254
1205
  const logLevel = _kubb_core.logLevel[logLevelKey] ?? _kubb_core.logLevel.info;
1255
- const hooks = new AsyncEventEmitter();
1206
+ const hooks = new _kubb_core.AsyncEventEmitter();
1256
1207
  let configs;
1257
1208
  let resolvedConfigPath;
1258
1209
  try {
@@ -1290,19 +1241,27 @@ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters:
1290
1241
  info: relativeConfigPath
1291
1242
  });
1292
1243
  let anyFailed = false;
1293
- for (const config of configs) if (config.input && "path" in config.input && watch) await startWatcher([input || config.input.path], async (paths) => {
1294
- await generate({
1295
- input,
1296
- config,
1297
- logLevel,
1298
- hooks
1244
+ for (const config of configs) if (config.input && "path" in config.input && watch) {
1245
+ const watchedPaths = [input || config.input.path];
1246
+ const build = async (paths) => {
1247
+ await generate({
1248
+ input,
1249
+ config,
1250
+ logLevel,
1251
+ hooks
1252
+ });
1253
+ _clack_prompts.log.step((0, node_util.styleText)("yellow", `Watching for changes in ${paths.join(" and ")}`));
1254
+ };
1255
+ try {
1256
+ await build(watchedPaths);
1257
+ } catch (buildError) {
1258
+ await hooks.emit("kubb:error", { error: require_errors.toError(buildError) });
1259
+ }
1260
+ await startWatcher(watchedPaths, build, {
1261
+ info: (msg) => _clack_prompts.log.info(msg),
1262
+ error: (msg) => _clack_prompts.log.error(msg)
1299
1263
  });
1300
- _clack_prompts.log.step((0, node_util.styleText)("yellow", `Watching for changes in ${paths.join(" and ")}`));
1301
- }, {
1302
- info: (msg) => _clack_prompts.log.info(msg),
1303
- error: (msg) => _clack_prompts.log.error(msg)
1304
- });
1305
- else try {
1264
+ } else try {
1306
1265
  if (!await generate({
1307
1266
  input,
1308
1267
  config,
@@ -1323,4 +1282,4 @@ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters:
1323
1282
  //#endregion
1324
1283
  exports.run = run;
1325
1284
 
1326
- //# sourceMappingURL=run-ChSqYuc1.cjs.map
1285
+ //# sourceMappingURL=run-DT9pRBLX.cjs.map