@kubb/cli 5.0.0-beta.3 → 5.0.0-beta.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +177 -26
  2. package/dist/agent-BAAO2W7u.cjs +70 -0
  3. package/dist/agent-BAAO2W7u.cjs.map +1 -0
  4. package/dist/agent-WLRLgsEM.js +68 -0
  5. package/dist/agent-WLRLgsEM.js.map +1 -0
  6. package/dist/{chunk--u3MIqq1.js → chunk-BvFE5Tac.js} +1 -0
  7. package/dist/constants-B2JTeRBb.js +42 -0
  8. package/dist/constants-B2JTeRBb.js.map +1 -0
  9. package/dist/constants-BINTA5VZ.cjs +77 -0
  10. package/dist/constants-BINTA5VZ.cjs.map +1 -0
  11. package/dist/constants-BYGmiFs0.cjs +139 -0
  12. package/dist/constants-BYGmiFs0.cjs.map +1 -0
  13. package/dist/constants-DSJ-Xrbv.js +116 -0
  14. package/dist/constants-DSJ-Xrbv.js.map +1 -0
  15. package/dist/define-Bdn8j5VM.cjs.map +1 -1
  16. package/dist/{define-Ctii4bel.js → define-m_fp-Aqm.js} +2 -2
  17. package/dist/{define-Ctii4bel.js.map → define-m_fp-Aqm.js.map} +1 -1
  18. package/dist/{errors-CjPmyZHy.js → errors-CINO1EIv.js} +2 -2
  19. package/dist/{errors-CjPmyZHy.js.map → errors-CINO1EIv.js.map} +1 -1
  20. package/dist/errors-CLCjoSg0.cjs.map +1 -1
  21. package/dist/{generate-CTdVvIaP.js → generate-C4iw5Nou.js} +12 -6
  22. package/dist/generate-C4iw5Nou.js.map +1 -0
  23. package/dist/{generate-BzCMyyNN.cjs → generate-DKtBY8eR.cjs} +10 -4
  24. package/dist/generate-DKtBY8eR.cjs.map +1 -0
  25. package/dist/index.cjs +20 -11
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +22 -13
  29. package/dist/index.js.map +1 -1
  30. package/dist/init-DE_judaK.js +53 -0
  31. package/dist/init-DE_judaK.js.map +1 -0
  32. package/dist/init-berpsF2G.cjs +53 -0
  33. package/dist/init-berpsF2G.cjs.map +1 -0
  34. package/dist/mcp-Ce6errt_.js +39 -0
  35. package/dist/mcp-Ce6errt_.js.map +1 -0
  36. package/dist/mcp-DcohdQTl.cjs +39 -0
  37. package/dist/mcp-DcohdQTl.cjs.map +1 -0
  38. package/dist/package-C0vNpFXU.js +6 -0
  39. package/dist/package-C0vNpFXU.js.map +1 -0
  40. package/dist/{package-DcmDg_mw.cjs → package-DZDnoPgZ.cjs} +2 -2
  41. package/dist/package-DZDnoPgZ.cjs.map +1 -0
  42. package/dist/run-B11-UaUs.cjs +33 -0
  43. package/dist/run-B11-UaUs.cjs.map +1 -0
  44. package/dist/{init-eNRlotJK.js → run-BNqMQygv.js} +107 -149
  45. package/dist/run-BNqMQygv.js.map +1 -0
  46. package/dist/{generate-BL-Kp5GY.js → run-BgM41TQT.js} +561 -493
  47. package/dist/run-BgM41TQT.js.map +1 -0
  48. package/dist/{init-CZ5Xq2Hd.cjs → run-BnGfi7Cp.cjs} +105 -147
  49. package/dist/run-BnGfi7Cp.cjs.map +1 -0
  50. package/dist/{agent-sdYBBgrd.js → run-BzpYYOQs.js} +46 -43
  51. package/dist/run-BzpYYOQs.js.map +1 -0
  52. package/dist/run-CCZ24VKk.js +51 -0
  53. package/dist/run-CCZ24VKk.js.map +1 -0
  54. package/dist/run-CQbj3ley.cjs +52 -0
  55. package/dist/run-CQbj3ley.cjs.map +1 -0
  56. package/dist/{generate-DMqdAYqy.cjs → run-DeWgpA6S.cjs} +558 -490
  57. package/dist/run-DeWgpA6S.cjs.map +1 -0
  58. package/dist/{agent-B4cAAab2.cjs → run-DwdAwnLG.cjs} +44 -41
  59. package/dist/run-DwdAwnLG.cjs.map +1 -0
  60. package/dist/run-PSA9X7ci.js +32 -0
  61. package/dist/run-PSA9X7ci.js.map +1 -0
  62. package/dist/shell-475fQKaX.cjs.map +1 -1
  63. package/dist/{shell-DLzN4fRo.js → shell-CN6DNqeC.js} +2 -2
  64. package/dist/{shell-DLzN4fRo.js.map → shell-CN6DNqeC.js.map} +1 -1
  65. package/dist/{telemetry-DN95_2pF.cjs → telemetry-B2iWkY5e.cjs} +5 -7
  66. package/dist/telemetry-B2iWkY5e.cjs.map +1 -0
  67. package/dist/{telemetry-LgT_sdPe.js → telemetry-BkektVz6.js} +6 -8
  68. package/dist/telemetry-BkektVz6.js.map +1 -0
  69. package/dist/validate-DVeCYyIS.js +26 -0
  70. package/dist/validate-DVeCYyIS.js.map +1 -0
  71. package/dist/validate-ymG_XDSU.cjs +26 -0
  72. package/dist/validate-ymG_XDSU.cjs.map +1 -0
  73. package/package.json +14 -14
  74. package/src/commands/agent/start.ts +10 -7
  75. package/src/commands/agent.ts +3 -1
  76. package/src/commands/generate.ts +5 -3
  77. package/src/commands/init.ts +34 -3
  78. package/src/commands/mcp.ts +28 -4
  79. package/src/commands/validate.ts +6 -4
  80. package/src/constants.ts +2 -58
  81. package/src/index.ts +5 -3
  82. package/src/loggers/clackLogger.ts +85 -118
  83. package/src/loggers/fileSystemLogger.ts +28 -15
  84. package/src/loggers/githubActionsLogger.ts +87 -96
  85. package/src/loggers/plainLogger.ts +48 -81
  86. package/src/loggers/types.ts +6 -0
  87. package/src/loggers/utils.ts +235 -11
  88. package/src/runners/agent/run.ts +113 -0
  89. package/src/runners/agent/utils.ts +98 -0
  90. package/src/runners/generate/run.ts +321 -0
  91. package/src/runners/generate/utils.ts +225 -0
  92. package/src/runners/init/run.ts +212 -0
  93. package/src/{utils/packageManager.ts → runners/init/utils.ts} +12 -2
  94. package/src/runners/mcp/run.ts +37 -0
  95. package/src/runners/validate/run.ts +63 -0
  96. package/src/{utils/telemetry.ts → telemetry.ts} +27 -20
  97. package/dist/agent-B4cAAab2.cjs.map +0 -1
  98. package/dist/agent-BFACosbG.cjs +0 -58
  99. package/dist/agent-BFACosbG.cjs.map +0 -1
  100. package/dist/agent-s7TqqoTg.js +0 -56
  101. package/dist/agent-s7TqqoTg.js.map +0 -1
  102. package/dist/agent-sdYBBgrd.js.map +0 -1
  103. package/dist/constants-CnDXa1R6.cjs +0 -148
  104. package/dist/constants-CnDXa1R6.cjs.map +0 -1
  105. package/dist/constants-aL3CP_Wq.js +0 -95
  106. package/dist/constants-aL3CP_Wq.js.map +0 -1
  107. package/dist/generate-BL-Kp5GY.js.map +0 -1
  108. package/dist/generate-BzCMyyNN.cjs.map +0 -1
  109. package/dist/generate-CTdVvIaP.js.map +0 -1
  110. package/dist/generate-DMqdAYqy.cjs.map +0 -1
  111. package/dist/init-BHMGbly9.cjs +0 -25
  112. package/dist/init-BHMGbly9.cjs.map +0 -1
  113. package/dist/init-CZ5Xq2Hd.cjs.map +0 -1
  114. package/dist/init-eNRlotJK.js.map +0 -1
  115. package/dist/init-qgpg-iRW.js +0 -25
  116. package/dist/init-qgpg-iRW.js.map +0 -1
  117. package/dist/mcp-BRp-2Rdc.js +0 -16
  118. package/dist/mcp-BRp-2Rdc.js.map +0 -1
  119. package/dist/mcp-CYOgxB82.cjs +0 -47
  120. package/dist/mcp-CYOgxB82.cjs.map +0 -1
  121. package/dist/mcp-DmJm3TrU.js +0 -46
  122. package/dist/mcp-DmJm3TrU.js.map +0 -1
  123. package/dist/mcp-N3mRyVuO.cjs +0 -16
  124. package/dist/mcp-N3mRyVuO.cjs.map +0 -1
  125. package/dist/package-DcmDg_mw.cjs.map +0 -1
  126. package/dist/package-DtuyzAVW.js +0 -6
  127. package/dist/package-DtuyzAVW.js.map +0 -1
  128. package/dist/telemetry-DN95_2pF.cjs.map +0 -1
  129. package/dist/telemetry-LgT_sdPe.js.map +0 -1
  130. package/dist/validate-CJpTOzKA.js +0 -25
  131. package/dist/validate-CJpTOzKA.js.map +0 -1
  132. package/dist/validate-DyTbv7Bc.cjs +0 -25
  133. package/dist/validate-DyTbv7Bc.cjs.map +0 -1
  134. package/dist/validate-kLJoT_hi.js +0 -33
  135. package/dist/validate-kLJoT_hi.js.map +0 -1
  136. package/dist/validate-yKKzqEZ5.cjs +0 -34
  137. package/dist/validate-yKKzqEZ5.cjs.map +0 -1
  138. package/src/runners/agent.ts +0 -155
  139. package/src/runners/generate.ts +0 -333
  140. package/src/runners/init.ts +0 -296
  141. package/src/runners/mcp.ts +0 -51
  142. package/src/runners/validate.ts +0 -39
  143. package/src/types.ts +0 -11
  144. package/src/utils/Writables.ts +0 -17
  145. package/src/utils/executeHooks.ts +0 -45
  146. package/src/utils/flags.ts +0 -9
  147. package/src/utils/getConfig.ts +0 -10
  148. package/src/utils/getCosmiConfig.ts +0 -80
  149. package/src/utils/getSummary.ts +0 -68
  150. package/src/utils/runHook.ts +0 -91
  151. package/src/utils/watcher.ts +0 -19
@@ -1,9 +1,9 @@
1
- import "./chunk--u3MIqq1.js";
2
- import { n as toCause, r as toError } from "./errors-CjPmyZHy.js";
3
- import { a as canUseTTY, i as executeIfOnline, o as isGitHubActions, r as sendTelemetry, t as buildTelemetryEvent } from "./telemetry-LgT_sdPe.js";
4
- import { n as tokenize } from "./shell-DLzN4fRo.js";
5
- import { t as version } from "./package-DtuyzAVW.js";
6
- import { a as SUMMARY_SEPARATOR, n as KUBB_NPM_PACKAGE_URL, o as WATCHER_IGNORED_PATHS } from "./constants-aL3CP_Wq.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-C0vNpFXU.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";
@@ -14,10 +14,9 @@ import path, { dirname, relative, resolve } from "node:path";
14
14
  import process$1 from "node:process";
15
15
  import * as clack from "@clack/prompts";
16
16
  import { createKubb, defineLogger, isInputPath, logLevel } from "@kubb/core";
17
- import { NonZeroExitError, x } from "tinyexec";
18
- import { Writable } from "node:stream";
19
17
  import { cosmiconfig } from "cosmiconfig";
20
- import { unrun } from "unrun";
18
+ import { createJiti } from "jiti";
19
+ import { NonZeroExitError, x } from "tinyexec";
21
20
  //#region ../../internals/utils/src/asyncEventEmitter.ts
22
21
  /**
23
22
  * Typed `EventEmitter` that awaits all async listeners before resolving.
@@ -48,9 +47,12 @@ var AsyncEventEmitter = class {
48
47
  * await emitter.emit('build', 'petstore')
49
48
  * ```
50
49
  */
51
- async emit(eventName, ...eventArgs) {
50
+ emit(eventName, ...eventArgs) {
52
51
  const listeners = this.#emitter.listeners(eventName);
53
52
  if (listeners.length === 0) return;
53
+ return this.#emitAll(eventName, listeners, eventArgs);
54
+ }
55
+ async #emitAll(eventName, listeners, eventArgs) {
54
56
  for (const listener of listeners) try {
55
57
  await listener(...eventArgs);
56
58
  } catch (err) {
@@ -486,112 +488,6 @@ async function detectLinter() {
486
488
  return null;
487
489
  }
488
490
  //#endregion
489
- //#region src/utils/getSummary.ts
490
- function getSummary({ failedPlugins, filesCreated, status, hrStart, config, pluginTimings }) {
491
- const duration = formatHrtime(hrStart);
492
- const pluginsCount = config.plugins?.length ?? 0;
493
- const successCount = pluginsCount - failedPlugins.size;
494
- const meta = {
495
- plugins: status === "success" ? `${styleText("green", `${successCount} successful`)}, ${pluginsCount} total` : `${styleText("green", `${successCount} successful`)}, ${styleText("red", `${failedPlugins.size} failed`)}, ${pluginsCount} total`,
496
- pluginsFailed: status === "failed" ? [...failedPlugins].map(({ plugin }) => randomCliColor(plugin.name)).join(", ") : void 0,
497
- filesCreated,
498
- time: styleText("green", duration),
499
- output: path.resolve(config.root, config.output.path)
500
- };
501
- const labels = {
502
- plugins: "Plugins:",
503
- failed: "Failed:",
504
- generated: "Generated:",
505
- pluginTimings: "Plugin Timings:",
506
- output: "Output:"
507
- };
508
- const maxLength = Math.max(0, ...[...Object.values(labels), ...pluginTimings ? Array.from(pluginTimings.keys()) : []].map((s) => s.length));
509
- const summaryLines = [];
510
- summaryLines.push(`${labels.plugins.padEnd(maxLength + 2)} ${meta.plugins}`);
511
- if (meta.pluginsFailed) summaryLines.push(`${labels.failed.padEnd(maxLength + 2)} ${meta.pluginsFailed}`);
512
- summaryLines.push(`${labels.generated.padEnd(maxLength + 2)} ${meta.filesCreated} files in ${meta.time}`);
513
- if (pluginTimings && pluginTimings.size > 0) {
514
- const sortedTimings = Array.from(pluginTimings.entries()).sort((a, b) => b[1] - a[1]);
515
- summaryLines.push(`${labels.pluginTimings}`);
516
- sortedTimings.forEach(([name, time]) => {
517
- const timeStr = time >= 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
518
- const barLength = Math.min(Math.ceil(time / 100), 10);
519
- const bar = styleText("dim", "█".repeat(barLength));
520
- summaryLines.push(`${styleText("dim", "•")} ${name.padEnd(maxLength + 1)}${bar} ${timeStr}`);
521
- });
522
- }
523
- summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`);
524
- return summaryLines;
525
- }
526
- //#endregion
527
- //#region src/utils/runHook.ts
528
- /**
529
- * Executes a hook command, emits debug and completion events, and forwards output to an optional sink.
530
- */
531
- async function runHook({ id, command, args, commandWithArgs, context, stream = false, sink }) {
532
- try {
533
- const proc = x(command, [...args ?? []], {
534
- nodeOptions: { detached: process.platform !== "win32" },
535
- throwOnError: true
536
- });
537
- if (stream && sink?.onLine) for await (const line of proc) sink.onLine(line);
538
- const result = await proc;
539
- await context.emit("kubb:debug", {
540
- date: /* @__PURE__ */ new Date(),
541
- logs: [result.stdout.trimEnd()]
542
- });
543
- await context.emit("kubb:hook:end", {
544
- command,
545
- args,
546
- id,
547
- success: true,
548
- error: null
549
- });
550
- } catch (err) {
551
- if (!(err instanceof NonZeroExitError)) {
552
- await context.emit("kubb:hook:end", {
553
- command,
554
- args,
555
- id,
556
- success: false,
557
- error: toError(err)
558
- });
559
- await context.emit("kubb:error", { error: toError(err) });
560
- return;
561
- }
562
- const stderr = err.output?.stderr ?? "";
563
- const stdout = err.output?.stdout ?? "";
564
- await context.emit("kubb:debug", {
565
- date: /* @__PURE__ */ new Date(),
566
- logs: [stdout, stderr].filter(Boolean)
567
- });
568
- if (stderr) sink?.onStderr?.(stderr);
569
- if (stdout) sink?.onStdout?.(stdout);
570
- const errorMessage = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
571
- await context.emit("kubb:hook:end", {
572
- command,
573
- args,
574
- id,
575
- success: false,
576
- error: errorMessage
577
- });
578
- await context.emit("kubb:error", { error: errorMessage });
579
- }
580
- }
581
- //#endregion
582
- //#region src/utils/Writables.ts
583
- var ClackWritable = class extends Writable {
584
- taskLog;
585
- constructor(taskLog, opts) {
586
- super(opts);
587
- this.taskLog = taskLog;
588
- }
589
- _write(chunk, _encoding, callback) {
590
- this.taskLog.message(`${styleText("dim", chunk.toString())}`);
591
- callback();
592
- }
593
- };
594
- //#endregion
595
491
  //#region src/loggers/clackLogger.ts
596
492
  /**
597
493
  * TTY logger with beautiful UI and progress indicators for local development.
@@ -601,30 +497,22 @@ const clackLogger = defineLogger({
601
497
  install(context, options) {
602
498
  const logLevel$8 = options?.logLevel ?? logLevel.info;
603
499
  const state = {
604
- totalPlugins: 0,
605
- completedPlugins: 0,
606
- failedPlugins: 0,
607
- totalFiles: 0,
608
- processedFiles: 0,
609
- hrStart: process$1.hrtime(),
500
+ ...createProgressCounters(),
610
501
  spinner: clack.spinner(),
611
502
  isSpinning: false,
612
- activeProgress: /* @__PURE__ */ new Map()
503
+ activeProgress: /* @__PURE__ */ new Map(),
504
+ activeHookLogs: /* @__PURE__ */ new Map()
613
505
  };
614
506
  function reset() {
615
507
  for (const [_key, active] of state.activeProgress) {
616
508
  if (active.interval) clearInterval(active.interval);
617
509
  active.progressBar?.stop();
618
510
  }
619
- state.totalPlugins = 0;
620
- state.completedPlugins = 0;
621
- state.failedPlugins = 0;
622
- state.totalFiles = 0;
623
- state.processedFiles = 0;
624
- state.hrStart = process$1.hrtime();
511
+ resetProgressCounters(state);
625
512
  state.spinner = clack.spinner();
626
513
  state.isSpinning = false;
627
514
  state.activeProgress.clear();
515
+ state.activeHookLogs.clear();
628
516
  }
629
517
  function showProgressStep() {
630
518
  if (logLevel$8 <= logLevel.silent) return;
@@ -634,11 +522,18 @@ const clackLogger = defineLogger({
634
522
  function getMessage(message) {
635
523
  return formatMessage(message, logLevel$8);
636
524
  }
525
+ function onStep(event, message) {
526
+ context.on(event, () => {
527
+ if (logLevel$8 <= logLevel.silent) return;
528
+ clack.log.step(getMessage(message));
529
+ });
530
+ }
637
531
  function startSpinner(text) {
638
532
  state.spinner.start(text);
639
533
  state.isSpinning = true;
640
534
  }
641
535
  function stopSpinner(text) {
536
+ if (!state.isSpinning) return;
642
537
  state.spinner.stop(text);
643
538
  state.isSpinning = false;
644
539
  }
@@ -649,8 +544,11 @@ const clackLogger = defineLogger({
649
544
  message,
650
545
  styleText("dim", info)
651
546
  ].join(" "));
652
- if (state.isSpinning) state.spinner.message(text);
653
- else clack.log.info(text);
547
+ if (state.isSpinning) {
548
+ state.spinner.message(text);
549
+ return;
550
+ }
551
+ clack.log.info(text);
654
552
  });
655
553
  context.on("kubb:success", ({ message, info = "" }) => {
656
554
  if (logLevel$8 <= logLevel.silent) return;
@@ -659,8 +557,11 @@ const clackLogger = defineLogger({
659
557
  message,
660
558
  logLevel$8 >= logLevel.info ? styleText("dim", info) : void 0
661
559
  ].filter(Boolean).join(" "));
662
- if (state.isSpinning) stopSpinner(text);
663
- else clack.log.success(text);
560
+ if (state.isSpinning) {
561
+ stopSpinner(text);
562
+ return;
563
+ }
564
+ clack.log.success(text);
664
565
  });
665
566
  context.on("kubb:warn", ({ message, info }) => {
666
567
  if (logLevel$8 < logLevel.warn) return;
@@ -674,8 +575,11 @@ const clackLogger = defineLogger({
674
575
  context.on("kubb:error", ({ error }) => {
675
576
  const caused = toCause(error);
676
577
  const text = [styleText("red", "✗"), error.message].join(" ");
677
- if (state.isSpinning) stopSpinner(getMessage(text));
678
- else clack.log.error(getMessage(text));
578
+ if (state.isSpinning) {
579
+ stopSpinner(getMessage(text));
580
+ return;
581
+ }
582
+ clack.log.error(getMessage(text));
679
583
  if (logLevel$8 >= logLevel.debug && error.stack) {
680
584
  const frames = error.stack.split("\n").slice(1, 4);
681
585
  for (const frame of frames) clack.log.message(getMessage(styleText("dim", frame.trim())));
@@ -726,6 +630,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
726
630
  context.on("kubb:generation:start", ({ config }) => {
727
631
  reset();
728
632
  state.totalPlugins = config.plugins?.length ?? 0;
633
+ if (logLevel$8 <= logLevel.silent) return;
729
634
  const text = getMessage(["Generation started", config.name ? `for ${styleText("dim", config.name)}` : void 0].filter(Boolean).join(" "));
730
635
  clack.intro(text);
731
636
  });
@@ -752,8 +657,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
752
657
  const active = state.activeProgress.get(plugin.name);
753
658
  if (!active || logLevel$8 === logLevel.silent) return;
754
659
  clearInterval(active.interval);
755
- if (success) state.completedPlugins++;
756
- else state.failedPlugins++;
660
+ recordPluginResult(state, success);
757
661
  const durationStr = formatMsWithColor(duration);
758
662
  const text = getMessage(success ? `${styleText("bold", plugin.name)} completed in ${durationStr}` : `${styleText("bold", plugin.name)} failed in ${styleText("red", formatMs(duration))}`);
759
663
  active.progressBar.stop(text);
@@ -775,14 +679,14 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
775
679
  progressBar.start(getMessage(text));
776
680
  state.activeProgress.set("files", { progressBar });
777
681
  });
778
- context.on("kubb:file:processing:update", ({ file, config }) => {
682
+ context.on("kubb:files:processing:update", ({ files }) => {
779
683
  if (logLevel$8 <= logLevel.silent) return;
780
684
  stopSpinner();
781
- state.processedFiles++;
782
- const text = `Writing ${relative(config.root, file.path)}`;
783
685
  const active = state.activeProgress.get("files");
784
- if (!active) return;
785
- active.progressBar.advance(void 0, text);
686
+ for (const { file, config } of files) {
687
+ state.processedFiles++;
688
+ if (active) active.progressBar.advance(void 0, `Writing ${relative(config.root, file.path)}`);
689
+ }
786
690
  });
787
691
  context.on("kubb:files:processing:end", () => {
788
692
  if (logLevel$8 <= logLevel.silent) return;
@@ -795,68 +699,35 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
795
699
  showProgressStep();
796
700
  });
797
701
  context.on("kubb:generation:end", ({ config }) => {
702
+ stopSpinner();
798
703
  const text = getMessage(config.name ? `Generation completed for ${styleText("dim", config.name)}` : "Generation completed");
799
704
  clack.outro(text);
800
705
  });
801
- context.on("kubb:format:start", () => {
802
- if (logLevel$8 <= logLevel.silent) return;
803
- const text = getMessage("Format started");
804
- clack.intro(text);
805
- });
806
- context.on("kubb:format:end", () => {
807
- if (logLevel$8 <= logLevel.silent) return;
808
- const text = getMessage("Format completed");
809
- clack.outro(text);
810
- });
811
- context.on("kubb:lint:start", () => {
812
- if (logLevel$8 <= logLevel.silent) return;
813
- const text = getMessage("Lint started");
814
- clack.intro(text);
815
- });
816
- context.on("kubb:lint:end", () => {
817
- if (logLevel$8 <= logLevel.silent) return;
818
- const text = getMessage("Lint completed");
819
- clack.outro(text);
706
+ onStep("kubb:format:start", "Formatting");
707
+ onStep("kubb:lint:start", "Linting");
708
+ onStep("kubb:hooks:start", "Running hooks");
709
+ context.on("kubb:hook:start", ({ id, command, args }) => {
710
+ if (logLevel$8 <= logLevel.silent || !id) return;
711
+ stopSpinner();
712
+ const title = getMessage(`Running ${styleText("dim", formatCommandWithArgs(command, args))}`);
713
+ const taskLog = clack.taskLog({ title });
714
+ state.activeHookLogs.set(id, {
715
+ taskLog,
716
+ hrStart: process$1.hrtime()
717
+ });
820
718
  });
821
- context.on("kubb:hook:start", async ({ id, command, args }) => {
719
+ context.on("kubb:hook:end", ({ id, command, args, success, error }) => {
720
+ if (logLevel$8 <= logLevel.silent || !id) return;
721
+ const active = state.activeHookLogs.get(id);
722
+ if (!active) return;
723
+ state.activeHookLogs.delete(id);
822
724
  const commandWithArgs = formatCommandWithArgs(command, args);
823
- const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} started`);
824
- if (!id) return;
825
- if (logLevel$8 <= logLevel.silent) {
826
- await runHook({
827
- id,
828
- command,
829
- args,
830
- commandWithArgs,
831
- context,
832
- sink: {
833
- onStderr: (s) => console.error(s),
834
- onStdout: (s) => console.log(s)
835
- }
836
- });
837
- return;
725
+ const duration = formatMsWithColor(getElapsedMs(active.hrStart));
726
+ if (success) active.taskLog.success(getMessage(`${styleText("dim", commandWithArgs)} completed in ${duration}`));
727
+ else {
728
+ const reason = error?.message ? ` (${error.message})` : "";
729
+ active.taskLog.error(getMessage(`${styleText("dim", commandWithArgs)} failed${reason}`), { showLog: true });
838
730
  }
839
- clack.intro(text);
840
- const logger = clack.taskLog({ title: getMessage(["Executing hook", logLevel$8 >= logLevel.info ? styleText("dim", commandWithArgs) : void 0].filter(Boolean).join(" ")) });
841
- const writable = new ClackWritable(logger);
842
- await runHook({
843
- id,
844
- command,
845
- args,
846
- commandWithArgs,
847
- context,
848
- stream: true,
849
- sink: {
850
- onLine: (line) => writable.write(line),
851
- onStderr: (s) => logger.error(s),
852
- onStdout: (s) => logger.message(s)
853
- }
854
- });
855
- });
856
- context.on("kubb:hook:end", ({ command, args }) => {
857
- if (logLevel$8 <= logLevel.silent) return;
858
- const text = getMessage(`Hook ${styleText("dim", formatCommandWithArgs(command, args))} successfully executed`);
859
- clack.outro(text);
860
731
  });
861
732
  context.on("kubb:generation:summary", ({ config, pluginTimings, failedPlugins, filesCreated, status, hrStart }) => {
862
733
  const summary = getSummary({
@@ -887,13 +758,29 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
887
758
  context.on("kubb:lifecycle:end", () => {
888
759
  reset();
889
760
  });
761
+ return (_commandWithArgs, hookId) => {
762
+ if (logLevel$8 <= logLevel.silent) return {
763
+ onStdout: (s) => console.log(s),
764
+ onStderr: (s) => console.error(s)
765
+ };
766
+ const active = state.activeHookLogs.get(hookId);
767
+ if (!active) return null;
768
+ const { taskLog } = active;
769
+ return {
770
+ stream: true,
771
+ onLine: (line) => taskLog.message(styleText("dim", line)),
772
+ onStdout: (s) => taskLog.message(s),
773
+ onStderr: (s) => taskLog.message(styleText("red", s))
774
+ };
775
+ };
890
776
  }
891
777
  });
892
778
  //#endregion
893
779
  //#region src/loggers/fileSystemLogger.ts
894
780
  /**
895
781
  * FileSystem logger that captures debug events and writes them to `.kubb` directory files.
896
- * Note: Logs write on `lifecycle:end` or process exit. Cached logs may be lost if the process crashes before these events.
782
+ *
783
+ * @note Logs are written on `kubb:lifecycle:end` or process exit. Cached logs may be lost if the process crashes before either event.
897
784
  */
898
785
  const fileSystemLogger = defineLogger({
899
786
  name: "filesystem",
@@ -918,29 +805,31 @@ const fileSystemLogger = defineLogger({
918
805
  const pathName = resolve(process$1.cwd(), ".kubb", baseName);
919
806
  if (!files[pathName]) files[pathName] = [];
920
807
  if (log.logs.length > 0) {
921
- const timestamp = log.date.toLocaleString();
922
- files[pathName].push(`[${timestamp}]\n${log.logs.join("\n")}`);
808
+ const prefix = `[${log.date.toLocaleString()}] `;
809
+ const indent = " ".repeat(prefix.length);
810
+ const [first, ...rest] = log.logs;
811
+ files[pathName].push([prefix + first, ...rest.map((line) => indent + line)].join("\n"));
923
812
  }
924
813
  }
925
- for (const [fileName, logs] of Object.entries(files)) await write(fileName, logs.join("\n\n"));
814
+ for (const [fileName, logs] of Object.entries(files)) await write(fileName, logs.join("\n"));
926
815
  return Object.keys(files);
927
816
  }
928
817
  context.on("kubb:info", ({ message, info }) => {
929
818
  state.cachedLogs.add({
930
819
  date: /* @__PURE__ */ new Date(),
931
- logs: [`ℹ ${message} ${info}`]
820
+ logs: [`ℹ ${[message, info].filter(Boolean).join(" ")}`]
932
821
  });
933
822
  });
934
823
  context.on("kubb:success", ({ message, info }) => {
935
824
  state.cachedLogs.add({
936
825
  date: /* @__PURE__ */ new Date(),
937
- logs: [`✓ ${message} ${info}`]
826
+ logs: [`✓ ${[message, info].filter(Boolean).join(" ")}`]
938
827
  });
939
828
  });
940
829
  context.on("kubb:warn", ({ message, info }) => {
941
830
  state.cachedLogs.add({
942
831
  date: /* @__PURE__ */ new Date(),
943
- logs: [`⚠ ${message} ${info}`]
832
+ logs: [`⚠ ${[message, info].filter(Boolean).join(" ")}`]
944
833
  });
945
834
  });
946
835
  context.on("kubb:error", ({ error }) => {
@@ -949,29 +838,30 @@ const fileSystemLogger = defineLogger({
949
838
  logs: [`✗ ${error.message}`, error.stack || "unknown stack"]
950
839
  });
951
840
  });
952
- context.on("kubb:debug", (message) => {
841
+ context.on("kubb:debug", ({ date, fileName, logs }) => {
953
842
  state.cachedLogs.add({
954
- date: /* @__PURE__ */ new Date(),
955
- logs: message.logs
843
+ date,
844
+ fileName,
845
+ logs
956
846
  });
957
847
  });
958
848
  context.on("kubb:plugin:start", ({ plugin }) => {
959
849
  state.cachedLogs.add({
960
850
  date: /* @__PURE__ */ new Date(),
961
- logs: [`Generating ${plugin.name}`]
851
+ logs: [`► Generating ${plugin.name}`]
962
852
  });
963
853
  });
964
854
  context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
965
855
  const durationStr = formatMs(duration);
966
856
  state.cachedLogs.add({
967
857
  date: /* @__PURE__ */ new Date(),
968
- logs: [success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`]
858
+ logs: [success ? `✓ ${plugin.name} completed in ${durationStr}` : `✗ ${plugin.name} failed in ${durationStr}`]
969
859
  });
970
860
  });
971
861
  context.on("kubb:files:processing:start", ({ files }) => {
972
862
  state.cachedLogs.add({
973
863
  date: /* @__PURE__ */ new Date(),
974
- logs: [`Start ${files.length} writing:`, ...files.map((file) => file.path)]
864
+ logs: [`► Writing ${files.length} files`, ...files.map((file) => ` ${file.path}`)]
975
865
  });
976
866
  });
977
867
  context.on("kubb:generation:end", async ({ config }) => {
@@ -1003,22 +893,16 @@ const githubActionsLogger = defineLogger({
1003
893
  install(context, options) {
1004
894
  const logLevel$7 = options?.logLevel ?? logLevel.info;
1005
895
  const state = {
1006
- totalPlugins: 0,
1007
- completedPlugins: 0,
1008
- failedPlugins: 0,
1009
- totalFiles: 0,
1010
- processedFiles: 0,
1011
- hrStart: process.hrtime(),
1012
- currentConfigs: []
896
+ ...createProgressCounters(),
897
+ currentConfigs: [],
898
+ openGroupDepth: 0
1013
899
  };
900
+ const hookTimer = createHookTimer();
1014
901
  function reset() {
1015
- state.totalPlugins = 0;
1016
- state.completedPlugins = 0;
1017
- state.failedPlugins = 0;
1018
- state.totalFiles = 0;
1019
- state.processedFiles = 0;
1020
- state.hrStart = process.hrtime();
902
+ closeAllGroups();
903
+ resetProgressCounters(state);
1021
904
  state.currentConfigs = [];
905
+ hookTimer.clear();
1022
906
  }
1023
907
  function showProgressStep() {
1024
908
  if (logLevel$7 <= logLevel.silent) return;
@@ -1030,9 +914,31 @@ const githubActionsLogger = defineLogger({
1030
914
  }
1031
915
  function openGroup(name) {
1032
916
  console.log(`::group::${name}`);
917
+ state.openGroupDepth++;
1033
918
  }
1034
919
  function closeGroup(_name) {
1035
920
  console.log("::endgroup::");
921
+ if (state.openGroupDepth > 0) state.openGroupDepth--;
922
+ }
923
+ function closeAllGroups() {
924
+ while (state.openGroupDepth > 0) {
925
+ console.log("::endgroup::");
926
+ state.openGroupDepth--;
927
+ }
928
+ }
929
+ function onGroupStart(event, message, group) {
930
+ context.on(event, () => {
931
+ if (logLevel$7 <= logLevel.silent) return;
932
+ if (state.currentConfigs.length === 1) openGroup(group);
933
+ console.log(getMessage(message));
934
+ });
935
+ }
936
+ function onGroupEnd(event, message, group) {
937
+ context.on(event, () => {
938
+ if (logLevel$7 <= logLevel.silent) return;
939
+ console.log(getMessage(message));
940
+ if (state.currentConfigs.length === 1) closeGroup(group);
941
+ });
1036
942
  }
1037
943
  context.on("kubb:info", ({ message, info = "" }) => {
1038
944
  if (logLevel$7 <= logLevel.silent) return;
@@ -1063,6 +969,7 @@ const githubActionsLogger = defineLogger({
1063
969
  });
1064
970
  context.on("kubb:error", ({ error }) => {
1065
971
  const caused = toCause(error);
972
+ closeAllGroups();
1066
973
  if (logLevel$7 <= logLevel.silent) return;
1067
974
  const message = error.message || String(error);
1068
975
  console.error(`::error::${message}`);
@@ -1080,6 +987,9 @@ const githubActionsLogger = defineLogger({
1080
987
  console.log(styleText("yellow", `Kubb ${version} 🧩`));
1081
988
  reset();
1082
989
  });
990
+ context.on("kubb:version:new", ({ currentVersion, latestVersion }) => {
991
+ console.log(`::notice::Update available for Kubb: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`);
992
+ });
1083
993
  context.on("kubb:config:start", () => {
1084
994
  if (logLevel$7 <= logLevel.silent) return;
1085
995
  const text = getMessage("Configuration started");
@@ -1108,8 +1018,7 @@ const githubActionsLogger = defineLogger({
1108
1018
  });
1109
1019
  context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
1110
1020
  if (logLevel$7 <= logLevel.silent) return;
1111
- if (success) state.completedPlugins++;
1112
- else state.failedPlugins++;
1021
+ recordPluginResult(state, success);
1113
1022
  const durationStr = formatMsWithColor(duration);
1114
1023
  const text = getMessage(success ? `${styleText("bold", plugin.name)} completed in ${durationStr}` : `${styleText("bold", plugin.name)} failed in ${styleText("red", formatMs(duration))}`);
1115
1024
  console.log(text);
@@ -1132,63 +1041,38 @@ const githubActionsLogger = defineLogger({
1132
1041
  if (state.currentConfigs.length === 1) closeGroup("File Generation");
1133
1042
  showProgressStep();
1134
1043
  });
1135
- context.on("kubb:file:processing:update", () => {
1044
+ context.on("kubb:files:processing:update", ({ files }) => {
1136
1045
  if (logLevel$7 <= logLevel.silent) return;
1137
- state.processedFiles++;
1046
+ state.processedFiles += files.length;
1138
1047
  });
1139
1048
  context.on("kubb:generation:end", ({ config }) => {
1140
1049
  const text = getMessage(config.name ? `${styleText("blue", "✓")} Generation completed for ${styleText("dim", config.name)}` : `${styleText("blue", "✓")} Generation completed`);
1141
1050
  console.log(text);
1142
1051
  });
1143
- context.on("kubb:format:start", () => {
1144
- if (logLevel$7 <= logLevel.silent) return;
1145
- const text = getMessage("Format started");
1146
- if (state.currentConfigs.length === 1) openGroup("Formatting");
1147
- console.log(text);
1148
- });
1149
- context.on("kubb:format:end", () => {
1150
- if (logLevel$7 <= logLevel.silent) return;
1151
- const text = getMessage("Format completed");
1152
- console.log(text);
1153
- if (state.currentConfigs.length === 1) closeGroup("Formatting");
1154
- });
1155
- context.on("kubb:lint:start", () => {
1156
- if (logLevel$7 <= logLevel.silent) return;
1157
- const text = getMessage("Lint started");
1158
- if (state.currentConfigs.length === 1) openGroup("Linting");
1159
- console.log(text);
1160
- });
1161
- context.on("kubb:lint:end", () => {
1052
+ onGroupStart("kubb:format:start", "Format started", "Formatting");
1053
+ onGroupEnd("kubb:format:end", "Format completed", "Formatting");
1054
+ onGroupStart("kubb:lint:start", "Lint started", "Linting");
1055
+ onGroupEnd("kubb:lint:end", "Lint completed", "Linting");
1056
+ onGroupStart("kubb:hooks:start", "Hooks started", "Hooks");
1057
+ onGroupEnd("kubb:hooks:end", "Hooks completed", "Hooks");
1058
+ context.on("kubb:hook:start", ({ id, command, args }) => {
1162
1059
  if (logLevel$7 <= logLevel.silent) return;
1163
- const text = getMessage("Lint completed");
1164
- console.log(text);
1165
- if (state.currentConfigs.length === 1) closeGroup("Linting");
1166
- });
1167
- context.on("kubb:hook:start", async ({ id, command, args }) => {
1060
+ if (id) hookTimer.start(id);
1168
1061
  const commandWithArgs = formatCommandWithArgs(command, args);
1169
1062
  const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} started`);
1170
- if (logLevel$7 > logLevel.silent) {
1171
- if (state.currentConfigs.length === 1) openGroup(`Hook ${commandWithArgs}`);
1172
- console.log(text);
1173
- }
1174
- if (!id) return;
1175
- await runHook({
1176
- id,
1177
- command,
1178
- args,
1179
- commandWithArgs,
1180
- context,
1181
- sink: {
1182
- onStdout: logLevel$7 > logLevel.silent ? (s) => console.log(s) : void 0,
1183
- onStderr: logLevel$7 > logLevel.silent ? (s) => console.error(`::error::${s}`) : void 0
1184
- }
1185
- });
1063
+ if (state.currentConfigs.length === 1) openGroup(`Hook ${commandWithArgs}`);
1064
+ console.log(text);
1186
1065
  });
1187
- context.on("kubb:hook:end", ({ command, args }) => {
1066
+ context.on("kubb:hook:end", ({ id, command, args, success, error }) => {
1188
1067
  if (logLevel$7 <= logLevel.silent) return;
1068
+ const ms = id ? hookTimer.end(id) : void 0;
1069
+ const durationStr = ms !== void 0 ? ` in ${formatMsWithColor(ms)}` : "";
1189
1070
  const commandWithArgs = formatCommandWithArgs(command, args);
1190
- const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} completed`);
1191
- console.log(text);
1071
+ if (success) console.log(getMessage(`${styleText("green", "✓")} Hook ${styleText("dim", commandWithArgs)} completed${durationStr}`));
1072
+ else {
1073
+ const reason = error?.message ? ` (${error.message})` : "";
1074
+ console.log(`::error::Hook ${commandWithArgs} failed${durationStr}${reason}`);
1075
+ }
1192
1076
  if (state.currentConfigs.length === 1) closeGroup(`Hook ${commandWithArgs}`);
1193
1077
  });
1194
1078
  context.on("kubb:generation:summary", ({ config, status, hrStart, failedPlugins }) => {
@@ -1202,6 +1086,10 @@ const githubActionsLogger = defineLogger({
1202
1086
  context.on("kubb:lifecycle:end", () => {
1203
1087
  reset();
1204
1088
  });
1089
+ return (_commandWithArgs, _hookId) => ({
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
+ });
1205
1093
  }
1206
1094
  });
1207
1095
  //#endregion
@@ -1213,9 +1101,16 @@ const plainLogger = defineLogger({
1213
1101
  name: "plain",
1214
1102
  install(context, options) {
1215
1103
  const logLevel$6 = options?.logLevel ?? logLevel.info;
1104
+ const hookTimer = createHookTimer();
1216
1105
  function getMessage(message) {
1217
1106
  return formatMessage(message, logLevel$6);
1218
1107
  }
1108
+ function onStep(event, message) {
1109
+ context.on(event, () => {
1110
+ if (logLevel$6 <= logLevel.silent) return;
1111
+ console.log(getMessage(message));
1112
+ });
1113
+ }
1219
1114
  context.on("kubb:info", ({ message, info }) => {
1220
1115
  if (logLevel$6 <= logLevel.silent) return;
1221
1116
  const text = getMessage([
@@ -1257,19 +1152,15 @@ const plainLogger = defineLogger({
1257
1152
  }
1258
1153
  }
1259
1154
  });
1260
- context.on("kubb:lifecycle:start", () => {
1261
- console.log("Kubb CLI 🧩");
1262
- });
1263
- context.on("kubb:config:start", () => {
1264
- if (logLevel$6 <= logLevel.silent) return;
1265
- const text = getMessage("Configuration started");
1266
- console.log(text);
1155
+ context.on("kubb:lifecycle:start", ({ version }) => {
1156
+ console.log(`Kubb CLI v${version}`);
1267
1157
  });
1268
- context.on("kubb:config:end", () => {
1158
+ context.on("kubb:version:new", ({ currentVersion, latestVersion }) => {
1269
1159
  if (logLevel$6 <= logLevel.silent) return;
1270
- const text = getMessage("Configuration completed");
1271
- console.log(text);
1160
+ console.log(getMessage(`Update available: v${currentVersion} v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`));
1272
1161
  });
1162
+ onStep("kubb:config:start", "Configuration started");
1163
+ onStep("kubb:config:end", "Configuration completed");
1273
1164
  context.on("kubb:generation:start", () => {
1274
1165
  const text = getMessage("Generation started");
1275
1166
  console.log(text);
@@ -1290,10 +1181,9 @@ const plainLogger = defineLogger({
1290
1181
  const text = getMessage(`Writing ${files.length} files`);
1291
1182
  console.log(text);
1292
1183
  });
1293
- context.on("kubb:file:processing:update", ({ file, config }) => {
1184
+ context.on("kubb:files:processing:update", ({ files }) => {
1294
1185
  if (logLevel$6 <= logLevel.silent) return;
1295
- const text = getMessage(`Writing ${relative(config.root, file.path)}`);
1296
- console.log(text);
1186
+ for (const { file, config } of files) console.log(getMessage(`Writing ${relative(config.root, file.path)}`));
1297
1187
  });
1298
1188
  context.on("kubb:files:processing:end", () => {
1299
1189
  if (logLevel$6 <= logLevel.silent) return;
@@ -1304,47 +1194,28 @@ const plainLogger = defineLogger({
1304
1194
  const text = getMessage(config.name ? `Generation completed for ${config.name}` : "Generation completed");
1305
1195
  console.log(text);
1306
1196
  });
1307
- context.on("kubb:format:start", () => {
1308
- if (logLevel$6 <= logLevel.silent) return;
1309
- const text = getMessage("Format started");
1310
- console.log(text);
1311
- });
1312
- context.on("kubb:format:end", () => {
1313
- if (logLevel$6 <= logLevel.silent) return;
1314
- const text = getMessage("Format completed");
1315
- console.log(text);
1316
- });
1317
- context.on("kubb:lint:start", () => {
1318
- if (logLevel$6 <= logLevel.silent) return;
1319
- const text = getMessage("Lint started");
1320
- console.log(text);
1321
- });
1322
- context.on("kubb:lint:end", () => {
1197
+ onStep("kubb:format:start", "Format started");
1198
+ onStep("kubb:format:end", "Format completed");
1199
+ onStep("kubb:lint:start", "Lint started");
1200
+ onStep("kubb:lint:end", "Lint completed");
1201
+ onStep("kubb:hooks:start", "Hooks started");
1202
+ onStep("kubb:hooks:end", "Hooks completed");
1203
+ context.on("kubb:hook:start", ({ id, command, args }) => {
1323
1204
  if (logLevel$6 <= logLevel.silent) return;
1324
- const text = getMessage("Lint completed");
1325
- console.log(text);
1326
- });
1327
- context.on("kubb:hook:start", async ({ id, command, args }) => {
1205
+ if (id) hookTimer.start(id);
1328
1206
  const commandWithArgs = formatCommandWithArgs(command, args);
1329
- const text = getMessage(`Hook ${commandWithArgs} started`);
1330
- if (logLevel$6 > logLevel.silent) console.log(text);
1331
- if (!id) return;
1332
- await runHook({
1333
- id,
1334
- command,
1335
- args,
1336
- commandWithArgs,
1337
- context,
1338
- sink: {
1339
- onStdout: logLevel$6 > logLevel.silent ? (s) => console.log(s) : void 0,
1340
- onStderr: logLevel$6 > logLevel.silent ? (s) => console.error(s) : void 0
1341
- }
1342
- });
1207
+ console.log(getMessage(`Hook ${commandWithArgs} started`));
1343
1208
  });
1344
- context.on("kubb:hook:end", ({ command, args }) => {
1209
+ context.on("kubb:hook:end", ({ id, command, args, success, error }) => {
1345
1210
  if (logLevel$6 <= logLevel.silent) return;
1346
- const text = getMessage(`Hook ${formatCommandWithArgs(command, args)} completed`);
1347
- console.log(text);
1211
+ const ms = id ? hookTimer.end(id) : void 0;
1212
+ const durationStr = ms !== void 0 ? ` in ${formatMs(ms)}` : "";
1213
+ const commandWithArgs = formatCommandWithArgs(command, args);
1214
+ if (success) console.log(getMessage(`✓ Hook ${commandWithArgs} completed${durationStr}`));
1215
+ else {
1216
+ const reason = error?.message ? ` (${error.message})` : "";
1217
+ console.log(getMessage(`✗ Hook ${commandWithArgs} failed${durationStr}${reason}`));
1218
+ }
1348
1219
  });
1349
1220
  context.on("kubb:generation:summary", ({ config, pluginTimings, status, hrStart, failedPlugins, filesCreated }) => {
1350
1221
  const summary = getSummary({
@@ -1359,6 +1230,10 @@ const plainLogger = defineLogger({
1359
1230
  console.log(summary.join("\n"));
1360
1231
  console.log(SUMMARY_SEPARATOR);
1361
1232
  });
1233
+ return (_commandWithArgs, _hookId) => ({
1234
+ onStdout: logLevel$6 > logLevel.silent ? (s) => console.log(s) : void 0,
1235
+ onStderr: logLevel$6 > logLevel.silent ? (s) => console.error(s) : void 0
1236
+ });
1362
1237
  }
1363
1238
  });
1364
1239
  //#endregion
@@ -1393,6 +1268,57 @@ function buildProgressLine(state) {
1393
1268
  return parts.join(styleText("dim", " | "));
1394
1269
  }
1395
1270
  /**
1271
+ * Creates the per-run progress counters shared by the clack and GitHub Actions loggers.
1272
+ */
1273
+ function createProgressCounters() {
1274
+ return {
1275
+ totalPlugins: 0,
1276
+ completedPlugins: 0,
1277
+ failedPlugins: 0,
1278
+ totalFiles: 0,
1279
+ processedFiles: 0,
1280
+ hrStart: process$1.hrtime()
1281
+ };
1282
+ }
1283
+ /**
1284
+ * Resets the progress counters in place at the start/end of a generation run.
1285
+ */
1286
+ function resetProgressCounters(state) {
1287
+ state.totalPlugins = 0;
1288
+ state.completedPlugins = 0;
1289
+ state.failedPlugins = 0;
1290
+ state.totalFiles = 0;
1291
+ state.processedFiles = 0;
1292
+ state.hrStart = process$1.hrtime();
1293
+ }
1294
+ /**
1295
+ * Records a finished plugin against the progress counters.
1296
+ */
1297
+ function recordPluginResult(state, success) {
1298
+ if (success) state.completedPlugins++;
1299
+ else state.failedPlugins++;
1300
+ }
1301
+ /**
1302
+ * Creates a {@link HookTimer} backed by a private `id → hrtime` map.
1303
+ */
1304
+ function createHookTimer() {
1305
+ const starts = /* @__PURE__ */ new Map();
1306
+ return {
1307
+ start(id) {
1308
+ starts.set(id, process$1.hrtime());
1309
+ },
1310
+ end(id) {
1311
+ const hrStart = starts.get(id);
1312
+ if (!hrStart) return;
1313
+ starts.delete(id);
1314
+ return getElapsedMs(hrStart);
1315
+ },
1316
+ clear() {
1317
+ starts.clear();
1318
+ }
1319
+ };
1320
+ }
1321
+ /**
1396
1322
  * Join a command and its optional args into a single display string.
1397
1323
  * e.g. ("prettier", ["--write", "."]) → "prettier --write ."
1398
1324
  */
@@ -1413,125 +1339,247 @@ async function setupLogger(context, { logLevel: logLevel$5 }) {
1413
1339
  const type = detectLogger();
1414
1340
  const logger = logMapper[type];
1415
1341
  if (!logger) throw new Error(`Unknown adapter type: ${type}`);
1416
- const cleanup = await logger.install(context, { logLevel: logLevel$5 });
1342
+ const makeSink = await logger.install(context, { logLevel: logLevel$5 });
1417
1343
  if (logLevel$5 >= logLevel.debug) await fileSystemLogger.install(context, { logLevel: logLevel$5 });
1418
- return cleanup;
1344
+ return typeof makeSink === "function" ? makeSink : null;
1419
1345
  }
1420
- //#endregion
1421
- //#region src/utils/executeHooks.ts
1422
- async function executeHooks({ configHooks, hooks }) {
1423
- const commands = Array.isArray(configHooks.done) ? configHooks.done : [configHooks.done].filter(Boolean);
1424
- for (const command of commands) {
1425
- const [cmd, ...args] = tokenize(command);
1426
- if (!cmd) continue;
1427
- const hookId = createHash("sha256").update(command).digest("hex");
1428
- const hookEndPromise = new Promise((resolve, reject) => {
1429
- const handler = (ctx) => {
1430
- if (ctx.id !== hookId) return;
1431
- hooks.off("kubb:hook:end", handler);
1432
- if (!ctx.success) {
1433
- reject(ctx.error ?? /* @__PURE__ */ new Error(`Hook failed: ${command}`));
1434
- return;
1435
- }
1436
- hooks.emit("kubb:success", { message: `${styleText("dim", command)} successfully executed` }).then(resolve).catch(reject);
1437
- };
1438
- hooks.on("kubb:hook:end", handler);
1439
- });
1440
- await hooks.emit("kubb:hook:start", {
1441
- id: hookId,
1442
- command: cmd,
1443
- args
1346
+ /**
1347
+ * Builds the generation summary lines rendered in the end-of-run box.
1348
+ * Returns an array of styled strings, one per summary row.
1349
+ */
1350
+ function getSummary({ failedPlugins, filesCreated, status, hrStart, config, pluginTimings }) {
1351
+ const duration = formatHrtime(hrStart);
1352
+ const pluginsCount = config.plugins?.length ?? 0;
1353
+ const successCount = pluginsCount - failedPlugins.size;
1354
+ const meta = {
1355
+ plugins: status === "success" ? `${styleText("green", `${successCount} successful`)}, ${pluginsCount} total` : `${styleText("green", `${successCount} successful`)}, ${styleText("red", `${failedPlugins.size} failed`)}, ${pluginsCount} total`,
1356
+ pluginsFailed: status === "failed" ? [...failedPlugins].map(({ plugin }) => randomCliColor(plugin.name)).join(", ") : void 0,
1357
+ filesCreated,
1358
+ time: styleText("green", duration),
1359
+ output: path.resolve(config.root, config.output.path)
1360
+ };
1361
+ const labels = {
1362
+ plugins: "Plugins:",
1363
+ failed: "Failed:",
1364
+ generated: "Generated:",
1365
+ pluginTimings: "Plugin Timings:",
1366
+ output: "Output:"
1367
+ };
1368
+ const maxLength = Math.max(0, ...[...Object.values(labels), ...pluginTimings ? Array.from(pluginTimings.keys()) : []].map((s) => s.length));
1369
+ const summaryLines = [];
1370
+ summaryLines.push(`${labels.plugins.padEnd(maxLength + 2)} ${meta.plugins}`);
1371
+ if (meta.pluginsFailed) summaryLines.push(`${labels.failed.padEnd(maxLength + 2)} ${meta.pluginsFailed}`);
1372
+ summaryLines.push(`${labels.generated.padEnd(maxLength + 2)} ${meta.filesCreated} files in ${meta.time}`);
1373
+ if (pluginTimings && pluginTimings.size > 0) {
1374
+ const sortedTimings = Array.from(pluginTimings.entries()).sort((a, b) => b[1] - a[1]);
1375
+ summaryLines.push(`${labels.pluginTimings}`);
1376
+ sortedTimings.forEach(([name, time]) => {
1377
+ const timeStr = time >= 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
1378
+ const barLength = Math.min(Math.ceil(time / 100), 10);
1379
+ const bar = styleText("dim", "█".repeat(barLength));
1380
+ summaryLines.push(`${styleText("dim", "•")} ${name.padEnd(maxLength + 1)}${bar} ${timeStr}`);
1444
1381
  });
1445
- await hookEndPromise;
1446
1382
  }
1383
+ summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`);
1384
+ return summaryLines;
1447
1385
  }
1448
1386
  //#endregion
1449
- //#region src/utils/getConfig.ts
1450
- async function getConfigs(config, args) {
1451
- const resolved = await (typeof config === "function" ? config(args) : config);
1452
- return (Array.isArray(resolved) ? resolved : [resolved]).map((item) => ({
1453
- ...item,
1454
- plugins: item.plugins ?? []
1455
- }));
1456
- }
1457
- //#endregion
1458
- //#region src/utils/getCosmiConfig.ts
1459
- const unrunInputOptions = { transform: { jsx: {
1460
- runtime: "automatic",
1461
- importSource: "@kubb/renderer-jsx"
1462
- } } };
1463
- const tsLoader = async (configFile) => {
1464
- const { module } = await unrun({
1465
- path: configFile,
1466
- inputOptions: unrunInputOptions
1467
- });
1468
- return module;
1469
- };
1470
- async function getCosmiConfig(moduleName, config) {
1471
- let result;
1472
- const searchPlaces = [
1473
- "package.json",
1474
- `.${moduleName}rc`,
1475
- `.${moduleName}rc.json`,
1476
- `.${moduleName}rc.yaml`,
1477
- `.${moduleName}rc.yml`,
1478
- `.${moduleName}rc.ts`,
1479
- `.${moduleName}rc.mts`,
1480
- `.${moduleName}rc.cts`,
1481
- `.${moduleName}rc.js`,
1482
- `.${moduleName}rc.mjs`,
1483
- `.${moduleName}rc.cjs`,
1484
- `${moduleName}.config.ts`,
1485
- `${moduleName}.config.mts`,
1486
- `${moduleName}.config.cts`,
1487
- `${moduleName}.config.js`,
1488
- `${moduleName}.config.mjs`,
1489
- `${moduleName}.config.cjs`
1490
- ];
1491
- const explorer = cosmiconfig(moduleName, {
1387
+ //#region src/runners/generate/utils.ts
1388
+ const jiti = createJiti(import.meta.url, {
1389
+ jsx: {
1390
+ runtime: "automatic",
1391
+ importSource: "@kubb/renderer-jsx"
1392
+ },
1393
+ moduleCache: false
1394
+ });
1395
+ const tsLoader = (configFile) => jiti.import(configFile, { default: true });
1396
+ const MODULE_NAME = "kubb";
1397
+ const BASE_SEARCH_PLACES = [
1398
+ "package.json",
1399
+ `.${MODULE_NAME}rc`,
1400
+ `.${MODULE_NAME}rc.json`,
1401
+ `.${MODULE_NAME}rc.yaml`,
1402
+ `.${MODULE_NAME}rc.yml`,
1403
+ `.${MODULE_NAME}rc.ts`,
1404
+ `.${MODULE_NAME}rc.mts`,
1405
+ `.${MODULE_NAME}rc.cts`,
1406
+ `.${MODULE_NAME}rc.js`,
1407
+ `.${MODULE_NAME}rc.mjs`,
1408
+ `.${MODULE_NAME}rc.cjs`,
1409
+ `${MODULE_NAME}.config.ts`,
1410
+ `${MODULE_NAME}.config.mts`,
1411
+ `${MODULE_NAME}.config.cts`,
1412
+ `${MODULE_NAME}.config.js`,
1413
+ `${MODULE_NAME}.config.mjs`,
1414
+ `${MODULE_NAME}.config.cjs`
1415
+ ];
1416
+ const SEARCH_PLACES = [
1417
+ "",
1418
+ ".config/",
1419
+ "configs/"
1420
+ ].flatMap((prefix) => BASE_SEARCH_PLACES.map((p) => `${prefix}${p}`));
1421
+ async function getCosmiConfig(configFile) {
1422
+ const explorer = cosmiconfig(MODULE_NAME, {
1492
1423
  cache: false,
1493
- searchPlaces: [
1494
- ...searchPlaces.map((searchPlace) => {
1495
- return `.config/${searchPlace}`;
1496
- }),
1497
- ...searchPlaces.map((searchPlace) => {
1498
- return `configs/${searchPlace}`;
1499
- }),
1500
- ...searchPlaces
1501
- ],
1424
+ searchPlaces: SEARCH_PLACES,
1502
1425
  loaders: {
1503
1426
  ".ts": tsLoader,
1504
1427
  ".mts": tsLoader,
1505
1428
  ".cts": tsLoader
1506
1429
  }
1507
1430
  });
1431
+ let result;
1508
1432
  try {
1509
- result = config ? await explorer.load(config) : await explorer.search();
1433
+ result = configFile ? await explorer.load(configFile) : await explorer.search();
1510
1434
  } catch (error) {
1511
1435
  throw new Error("Config failed loading", { cause: error });
1512
1436
  }
1513
- 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");
1437
+ 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");
1514
1438
  return result;
1515
1439
  }
1516
- //#endregion
1517
- //#region src/utils/watcher.ts
1518
- async function startWatcher(path, cb) {
1440
+ /**
1441
+ * Discovers the Kubb config via cosmiconfig and resolves it into a normalized array of configs.
1442
+ * Every config in the result is guaranteed to have a `plugins` array.
1443
+ */
1444
+ async function getConfigs({ configPath, input, watch, logLevel }) {
1445
+ const result = await getCosmiConfig(configPath);
1446
+ const cli = {
1447
+ config: configPath,
1448
+ input,
1449
+ watch,
1450
+ logLevel
1451
+ };
1452
+ const resolved = await (typeof result.config === "function" ? result.config(cli) : result.config);
1453
+ const userConfigs = Array.isArray(resolved) ? resolved : [resolved];
1454
+ return {
1455
+ configPath: result.filepath,
1456
+ configs: userConfigs.map((item) => ({
1457
+ ...item,
1458
+ plugins: item.plugins ?? []
1459
+ }))
1460
+ };
1461
+ }
1462
+ /**
1463
+ * Runs the `done` hooks defined in a Kubb config in sequence.
1464
+ */
1465
+ async function executeHooks({ configHooks, hooks, makeSink }) {
1466
+ const commands = Array.isArray(configHooks.done) ? configHooks.done : [configHooks.done].filter(Boolean);
1467
+ for (const command of commands) {
1468
+ const [cmd, ...args] = tokenize(command);
1469
+ if (!cmd) continue;
1470
+ const hookId = createHash("sha256").update(command).digest("hex");
1471
+ const commandWithArgs = [cmd, ...args].join(" ");
1472
+ await hooks.emit("kubb:hook:start", {
1473
+ id: hookId,
1474
+ command: cmd,
1475
+ args
1476
+ });
1477
+ const { stream = false, onLine, onStdout, onStderr } = makeSink?.(commandWithArgs, hookId) ?? {};
1478
+ await runHook({
1479
+ id: hookId,
1480
+ command: cmd,
1481
+ args,
1482
+ commandWithArgs,
1483
+ context: hooks,
1484
+ stream,
1485
+ sink: {
1486
+ onLine,
1487
+ onStdout,
1488
+ onStderr
1489
+ }
1490
+ });
1491
+ }
1492
+ }
1493
+ async function runHook({ id, command, args, commandWithArgs, context, stream = false, sink }) {
1494
+ const emitEnd = (success, error) => context.emit("kubb:hook:end", {
1495
+ command,
1496
+ args,
1497
+ id,
1498
+ success,
1499
+ error
1500
+ });
1501
+ try {
1502
+ const proc = x(command, [...args ?? []], {
1503
+ nodeOptions: { detached: process.platform !== "win32" },
1504
+ throwOnError: true
1505
+ });
1506
+ if (stream && sink?.onLine) for await (const line of proc) sink.onLine(line);
1507
+ const result = await proc;
1508
+ await context.emit("kubb:debug", {
1509
+ date: /* @__PURE__ */ new Date(),
1510
+ logs: [result.stdout.trimEnd()]
1511
+ });
1512
+ await context.emit("kubb:success", { message: `${styleText("dim", commandWithArgs)} successfully executed` });
1513
+ await emitEnd(true, null);
1514
+ } catch (err) {
1515
+ if (!(err instanceof NonZeroExitError)) {
1516
+ const error = toError(err);
1517
+ await emitEnd(false, error);
1518
+ await context.emit("kubb:error", { error });
1519
+ return;
1520
+ }
1521
+ const stderr = err.output?.stderr ?? "";
1522
+ const stdout = err.output?.stdout ?? "";
1523
+ await context.emit("kubb:debug", {
1524
+ date: /* @__PURE__ */ new Date(),
1525
+ logs: [stdout, stderr].filter(Boolean)
1526
+ });
1527
+ if (stderr) sink?.onStderr?.(stderr);
1528
+ if (stdout) sink?.onStdout?.(stdout);
1529
+ const error = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
1530
+ await emitEnd(false, error);
1531
+ await context.emit("kubb:error", { error });
1532
+ }
1533
+ }
1534
+ /**
1535
+ * Starts a file watcher on the given paths and calls `cb` on any change.
1536
+ * Ignores `.git` and `node_modules` directories.
1537
+ */
1538
+ async function startWatcher(path, cb, log = {
1539
+ info: console.log,
1540
+ error: console.log
1541
+ }) {
1519
1542
  const { watch } = await import("chokidar");
1520
- watch(path, {
1543
+ const watcher = watch(path, {
1521
1544
  ignorePermissionErrors: true,
1522
1545
  ignored: WATCHER_IGNORED_PATHS
1523
- }).on("all", async (type, file) => {
1524
- console.log(styleText("yellow", styleText("bold", `Change detected: ${type} ${file}`)));
1546
+ });
1547
+ process.once("SIGINT", () => {
1548
+ watcher.close();
1549
+ });
1550
+ process.once("SIGTERM", () => {
1551
+ watcher.close();
1552
+ });
1553
+ watcher.on("all", async (type, file) => {
1554
+ log.info(styleText("yellow", styleText("bold", `Change detected: ${type} ${file}`)));
1525
1555
  try {
1526
1556
  await cb(path);
1527
1557
  } catch (_e) {
1528
- console.log(styleText("red", "Watcher failed"));
1558
+ log.error(styleText("red", "Watcher failed"));
1529
1559
  }
1530
1560
  });
1531
1561
  }
1532
1562
  //#endregion
1533
- //#region src/runners/generate.ts
1534
- async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, configName, outputPath, logLevel: logLevel$1, hooks, onStart, onEnd }) {
1563
+ //#region src/runners/generate/run.ts
1564
+ /**
1565
+ * Registers a one-shot `kubb:hook:end` listener for `hookId` BEFORE the caller emits `kubb:hook:start`,
1566
+ * avoiding the race where a synchronous emitter fires end before the listener is attached.
1567
+ */
1568
+ function waitForHookEnd(hooks, hookId, onSuccess, fallbackErrorMessage) {
1569
+ return new Promise((resolve, reject) => {
1570
+ const handler = (ctx) => {
1571
+ if (ctx.id !== hookId) return;
1572
+ hooks.off("kubb:hook:end", handler);
1573
+ if (!ctx.success) {
1574
+ reject(ctx.error ?? new Error(fallbackErrorMessage));
1575
+ return;
1576
+ }
1577
+ Promise.resolve(onSuccess()).then(resolve).catch(reject);
1578
+ };
1579
+ hooks.on("kubb:hook:end", handler);
1580
+ });
1581
+ }
1582
+ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, configName, outputPath, logLevel: logLevel$1, hooks, makeSink, onStart, onEnd }) {
1535
1583
  await onStart();
1536
1584
  let resolvedTool = toolValue;
1537
1585
  if (resolvedTool === "auto") {
@@ -1545,29 +1593,35 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1545
1593
  let toolError;
1546
1594
  if (resolvedTool && resolvedTool !== "auto" && resolvedTool in toolMap) {
1547
1595
  const toolConfig = toolMap[resolvedTool];
1596
+ const hookId = createHash("sha256").update([configName, resolvedTool].filter(Boolean).join("-")).digest("hex");
1597
+ const successMessage = [
1598
+ `${successPrefix} with ${styleText("dim", resolvedTool)}`,
1599
+ logLevel$1 >= logLevel.info ? `on ${styleText("dim", outputPath)}` : void 0,
1600
+ "successfully"
1601
+ ].filter(Boolean).join(" ");
1548
1602
  try {
1549
- const hookId = createHash("sha256").update([configName, resolvedTool].filter(Boolean).join("-")).digest("hex");
1550
- const hookEndPromise = new Promise((resolve, reject) => {
1551
- const handler = (ctx) => {
1552
- if (ctx.id !== hookId) return;
1553
- hooks.off("kubb:hook:end", handler);
1554
- if (!ctx.success) {
1555
- reject(ctx.error ?? /* @__PURE__ */ new Error(`${toolConfig.errorMessage}`));
1556
- return;
1557
- }
1558
- hooks.emit("kubb:success", { message: [
1559
- `${successPrefix} with ${styleText("dim", resolvedTool)}`,
1560
- logLevel$1 >= logLevel.info ? `on ${styleText("dim", outputPath)}` : void 0,
1561
- "successfully"
1562
- ].filter(Boolean).join(" ") }).then(resolve).catch(reject);
1563
- };
1564
- hooks.on("kubb:hook:end", handler);
1565
- });
1603
+ const hookArgs = toolConfig.args(outputPath);
1604
+ const commandWithArgs = [toolConfig.command, ...hookArgs].join(" ");
1605
+ const hookEndPromise = waitForHookEnd(hooks, hookId, () => hooks.emit("kubb:success", { message: successMessage }), toolConfig.errorMessage);
1566
1606
  await hooks.emit("kubb:hook:start", {
1567
1607
  id: hookId,
1568
1608
  command: toolConfig.command,
1569
- args: toolConfig.args(outputPath)
1609
+ args: hookArgs
1570
1610
  });
1611
+ const { stream = false, onLine, onStdout, onStderr } = makeSink?.(commandWithArgs, hookId) ?? {};
1612
+ runHook({
1613
+ id: hookId,
1614
+ command: toolConfig.command,
1615
+ args: hookArgs,
1616
+ commandWithArgs,
1617
+ context: hooks,
1618
+ stream,
1619
+ sink: {
1620
+ onLine,
1621
+ onStdout,
1622
+ onStderr
1623
+ }
1624
+ }).catch(() => {});
1571
1625
  await hookEndPromise;
1572
1626
  } catch (caughtError) {
1573
1627
  const err = toError(caughtError);
@@ -1579,9 +1633,9 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1579
1633
  if (toolError) throw toolError;
1580
1634
  }
1581
1635
  async function generate(options) {
1582
- const { input, hooks, logLevel: logLevel$2 } = options;
1636
+ const { input, hooks, logLevel: logLevel$2, makeSink } = options;
1583
1637
  const hrStart = process$1.hrtime();
1584
- const inputPath = input ?? ("path" in options.config.input ? options.config.input.path : void 0);
1638
+ const inputPath = input ?? (options.config.input && "path" in options.config.input ? options.config.input.path : void 0);
1585
1639
  const config = {
1586
1640
  ...options.config,
1587
1641
  input: inputPath ? {
@@ -1591,25 +1645,36 @@ async function generate(options) {
1591
1645
  ...options.config.output
1592
1646
  };
1593
1647
  const kubb = createKubb(config, { hooks });
1594
- await kubb.setup();
1595
1648
  await hooks.emit("kubb:generation:start", { config });
1596
1649
  await hooks.emit("kubb:info", {
1597
1650
  message: config.name ? `Setup generation ${styleText("bold", config.name)}` : "Setup generation",
1598
1651
  info: inputPath
1599
1652
  });
1653
+ await kubb.setup();
1600
1654
  await hooks.emit("kubb:info", {
1601
1655
  message: config.name ? `Build generation ${styleText("bold", config.name)}` : "Build generation",
1602
1656
  info: inputPath
1603
1657
  });
1604
1658
  const { files, failedPlugins, pluginTimings, error, driver } = await kubb.safeBuild();
1605
1659
  await hooks.emit("kubb:info", { message: "Load summary" });
1660
+ const telemetryPlugins = Array.from(driver.plugins.values(), (p) => ({
1661
+ name: p.name,
1662
+ options: p.options
1663
+ }));
1664
+ const reportTelemetry = (status) => sendTelemetry(buildTelemetryEvent({
1665
+ command: "generate",
1666
+ kubbVersion: version,
1667
+ plugins: telemetryPlugins,
1668
+ hrStart,
1669
+ filesCreated: files.length,
1670
+ status
1671
+ }));
1606
1672
  if (failedPlugins.size > 0 || error) {
1607
- const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
1673
+ const allErrors = [error, ...Array.from(failedPlugins, (it) => it.error)].filter(Boolean);
1608
1674
  for (const err of allErrors) await hooks.emit("kubb:error", { error: err });
1609
1675
  await hooks.emit("kubb:generation:end", {
1610
1676
  config,
1611
- files,
1612
- sources: kubb.sources
1677
+ storage: kubb.storage
1613
1678
  });
1614
1679
  await hooks.emit("kubb:generation:summary", {
1615
1680
  config,
@@ -1619,62 +1684,51 @@ async function generate(options) {
1619
1684
  hrStart,
1620
1685
  pluginTimings: logLevel$2 >= logLevel.verbose ? pluginTimings : void 0
1621
1686
  });
1622
- await sendTelemetry(buildTelemetryEvent({
1623
- command: "generate",
1624
- kubbVersion: version,
1625
- plugins: Array.from(driver.plugins.values(), (p) => ({
1626
- name: p.name,
1627
- options: p.options
1628
- })),
1629
- hrStart,
1630
- filesCreated: files.length,
1631
- status: "failed"
1632
- }));
1633
- process$1.exit(1);
1687
+ await reportTelemetry("failed");
1688
+ return false;
1634
1689
  }
1635
1690
  await hooks.emit("kubb:success", {
1636
- message: "Generation successfully",
1691
+ message: "Generation succeeded",
1637
1692
  info: inputPath
1638
1693
  });
1639
1694
  await hooks.emit("kubb:generation:end", {
1640
1695
  config,
1641
- files,
1642
- sources: kubb.sources
1696
+ storage: kubb.storage
1643
1697
  });
1644
1698
  const outputPath = path.resolve(config.root, config.output.path);
1645
- if (config.output.format) await runToolPass({
1699
+ const toolPasses = [config.output.format && {
1646
1700
  toolValue: config.output.format,
1647
1701
  detect: detectFormatter,
1648
1702
  toolMap: formatters,
1649
1703
  toolLabel: "formatter",
1650
1704
  successPrefix: "Formatting",
1651
1705
  noToolMessage: "No formatter found (oxfmt, biome, or prettier). Skipping formatting.",
1652
- configName: config.name,
1653
- outputPath,
1654
- logLevel: logLevel$2,
1655
- hooks,
1656
1706
  onStart: () => hooks.emit("kubb:format:start"),
1657
1707
  onEnd: () => hooks.emit("kubb:format:end")
1658
- });
1659
- if (config.output.lint) await runToolPass({
1708
+ }, config.output.lint && {
1660
1709
  toolValue: config.output.lint,
1661
1710
  detect: detectLinter,
1662
1711
  toolMap: linters,
1663
1712
  toolLabel: "linter",
1664
1713
  successPrefix: "Linting",
1665
1714
  noToolMessage: "No linter found (oxlint, biome, or eslint). Skipping linting.",
1715
+ onStart: () => hooks.emit("kubb:lint:start"),
1716
+ onEnd: () => hooks.emit("kubb:lint:end")
1717
+ }].filter(Boolean);
1718
+ for (const pass of toolPasses) await runToolPass({
1719
+ ...pass,
1666
1720
  configName: config.name,
1667
1721
  outputPath,
1668
1722
  logLevel: logLevel$2,
1669
1723
  hooks,
1670
- onStart: () => hooks.emit("kubb:lint:start"),
1671
- onEnd: () => hooks.emit("kubb:lint:end")
1724
+ makeSink
1672
1725
  });
1673
1726
  if (config.hooks) {
1674
1727
  await hooks.emit("kubb:hooks:start");
1675
1728
  await executeHooks({
1676
1729
  configHooks: config.hooks,
1677
- hooks
1730
+ hooks,
1731
+ makeSink
1678
1732
  });
1679
1733
  await hooks.emit("kubb:hooks:end");
1680
1734
  }
@@ -1686,68 +1740,82 @@ async function generate(options) {
1686
1740
  hrStart,
1687
1741
  pluginTimings
1688
1742
  });
1689
- await sendTelemetry(buildTelemetryEvent({
1690
- command: "generate",
1691
- kubbVersion: version,
1692
- plugins: Array.from(driver.plugins.values(), (p) => ({
1693
- name: p.name,
1694
- options: p.options
1695
- })),
1696
- hrStart,
1697
- filesCreated: files.length,
1698
- status: "success"
1699
- }));
1743
+ await reportTelemetry("success");
1744
+ return true;
1700
1745
  }
1701
- async function runGenerateCommand({ input, configPath, logLevel: logLevelKey, watch }) {
1702
- const logLevel$3 = logLevel[logLevelKey] ?? logLevel.info;
1703
- const hooks = new AsyncEventEmitter();
1704
- await setupLogger(hooks, { logLevel: logLevel$3 });
1746
+ async function checkForUpdate(hooks) {
1705
1747
  await executeIfOnline(async () => {
1706
1748
  try {
1707
- const latestVersion = (await (await fetch(KUBB_NPM_PACKAGE_URL)).json()).version;
1708
- if (latestVersion && version < latestVersion) await hooks.emit("kubb:version:new", {
1749
+ const data = await (await fetch(KUBB_NPM_PACKAGE_URL)).json();
1750
+ if (data.version && version < data.version) await hooks.emit("kubb:version:new", {
1709
1751
  currentVersion: version,
1710
- latestVersion
1752
+ latestVersion: data.version
1711
1753
  });
1712
1754
  } catch {}
1713
1755
  });
1756
+ }
1757
+ /**
1758
+ * Runs the full Kubb generation lifecycle for the given CLI options.
1759
+ * Sets up the logger, checks for a newer version, loads configs, and calls `generate` for each config entry.
1760
+ */
1761
+ async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1762
+ const logLevel$3 = logLevel[logLevelKey] ?? logLevel.info;
1763
+ const hooks = new AsyncEventEmitter();
1764
+ const makeSink = await setupLogger(hooks, { logLevel: logLevel$3 });
1765
+ await hooks.emit("kubb:lifecycle:start", { version });
1766
+ await checkForUpdate(hooks);
1714
1767
  try {
1715
- const result = await getCosmiConfig("kubb", configPath);
1716
- const configs = await getConfigs(result.config, { input });
1717
1768
  await hooks.emit("kubb:config:start");
1769
+ const { configs, configPath: resolvedConfigPath } = await getConfigs({
1770
+ configPath,
1771
+ input,
1772
+ watch,
1773
+ logLevel: logLevelKey
1774
+ });
1775
+ const relativeConfigPath = path.relative(process$1.cwd(), resolvedConfigPath);
1718
1776
  await hooks.emit("kubb:info", {
1719
1777
  message: "Config loaded",
1720
- info: path.relative(process$1.cwd(), result.filepath)
1778
+ info: relativeConfigPath
1721
1779
  });
1722
1780
  await hooks.emit("kubb:success", {
1723
1781
  message: "Config loaded successfully",
1724
- info: path.relative(process$1.cwd(), result.filepath)
1782
+ info: relativeConfigPath
1725
1783
  });
1726
1784
  await hooks.emit("kubb:config:end", { configs });
1727
- await hooks.emit("kubb:lifecycle:start", { version });
1785
+ let anyFailed = false;
1728
1786
  for (const config of configs) if (isInputPath(config) && watch) await startWatcher([input || config.input.path], async (paths) => {
1729
- hooks.removeAll();
1730
1787
  await generate({
1731
1788
  input,
1732
1789
  config,
1733
1790
  logLevel: logLevel$3,
1734
- hooks
1791
+ hooks,
1792
+ makeSink
1735
1793
  });
1736
1794
  clack.log.step(styleText("yellow", `Watching for changes in ${paths.join(" and ")}`));
1795
+ }, {
1796
+ info: (msg) => clack.log.info(msg),
1797
+ error: (msg) => clack.log.error(msg)
1737
1798
  });
1738
- else await generate({
1739
- input,
1740
- config,
1741
- logLevel: logLevel$3,
1742
- hooks
1743
- });
1799
+ else try {
1800
+ if (!await generate({
1801
+ input,
1802
+ config,
1803
+ logLevel: logLevel$3,
1804
+ hooks,
1805
+ makeSink
1806
+ })) anyFailed = true;
1807
+ } catch (configError) {
1808
+ await hooks.emit("kubb:error", { error: toError(configError) });
1809
+ anyFailed = true;
1810
+ }
1744
1811
  await hooks.emit("kubb:lifecycle:end");
1812
+ if (anyFailed) process$1.exit(1);
1745
1813
  } catch (error) {
1746
1814
  await hooks.emit("kubb:error", { error: toError(error) });
1747
1815
  process$1.exit(1);
1748
1816
  }
1749
1817
  }
1750
1818
  //#endregion
1751
- export { runGenerateCommand };
1819
+ export { run };
1752
1820
 
1753
- //# sourceMappingURL=generate-BL-Kp5GY.js.map
1821
+ //# sourceMappingURL=run-BgM41TQT.js.map