@kubb/cli 5.0.0-beta.35 → 5.0.0-beta.37

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 (81) hide show
  1. package/README.md +14 -11
  2. package/dist/{agent-CY4CGQxI.js → agent-B8oJFhcN.js} +4 -4
  3. package/dist/{agent-CY4CGQxI.js.map → agent-B8oJFhcN.js.map} +1 -1
  4. package/dist/{agent-CX220hJL.cjs → agent-DtuTV_Qk.cjs} +4 -4
  5. package/dist/{agent-CX220hJL.cjs.map → agent-DtuTV_Qk.cjs.map} +1 -1
  6. package/dist/{constants-FhPsMOdo.cjs → constants-CAKUpLcQ.cjs} +3 -12
  7. package/dist/{constants-FhPsMOdo.cjs.map → constants-CAKUpLcQ.cjs.map} +1 -1
  8. package/dist/{constants-Co6NWt3U.js → constants-CYxk4aNm.js} +4 -7
  9. package/dist/{constants-Co6NWt3U.js.map → constants-CYxk4aNm.js.map} +1 -1
  10. package/dist/{generate-DYPz-3vT.cjs → generate-BvaMqrBk.cjs} +20 -14
  11. package/dist/generate-BvaMqrBk.cjs.map +1 -0
  12. package/dist/{generate-DuzUhvOd.js → generate-CzTjeiji.js} +20 -14
  13. package/dist/generate-CzTjeiji.js.map +1 -0
  14. package/dist/index.cjs +8 -8
  15. package/dist/index.js +8 -8
  16. package/dist/{init-D6916j9S.cjs → init-C59u3T68.cjs} +2 -2
  17. package/dist/{init-D6916j9S.cjs.map → init-C59u3T68.cjs.map} +1 -1
  18. package/dist/{init-lIh2Ooks.js → init-CaMeuE1-.js} +2 -2
  19. package/dist/{init-lIh2Ooks.js.map → init-CaMeuE1-.js.map} +1 -1
  20. package/dist/{mcp-tiR3udeu.js → mcp-Ca3ZcpKB.js} +3 -3
  21. package/dist/{mcp-tiR3udeu.js.map → mcp-Ca3ZcpKB.js.map} +1 -1
  22. package/dist/{mcp-Cl02BXV5.cjs → mcp-D4NMV9lk.cjs} +3 -3
  23. package/dist/{mcp-Cl02BXV5.cjs.map → mcp-D4NMV9lk.cjs.map} +1 -1
  24. package/dist/{package-B121qOXj.cjs → package-DQFf9DB2.cjs} +2 -2
  25. package/dist/package-DQFf9DB2.cjs.map +1 -0
  26. package/dist/package-DUwUSFeL.js +6 -0
  27. package/dist/package-DUwUSFeL.js.map +1 -0
  28. package/dist/{run-v-75bcU1.js → run-BFEK9md9.js} +2 -2
  29. package/dist/{run-v-75bcU1.js.map → run-BFEK9md9.js.map} +1 -1
  30. package/dist/{run-CCgNPz0F.cjs → run-BFv6avA_.cjs} +3 -3
  31. package/dist/{run-CCgNPz0F.cjs.map → run-BFv6avA_.cjs.map} +1 -1
  32. package/dist/{run-DpDKN_rb.cjs → run-BQZyg7If.cjs} +2 -2
  33. package/dist/{run-DpDKN_rb.cjs.map → run-BQZyg7If.cjs.map} +1 -1
  34. package/dist/{run-CPimpDgO.js → run-BvXxelGR.js} +2 -2
  35. package/dist/{run-CPimpDgO.js.map → run-BvXxelGR.js.map} +1 -1
  36. package/dist/{run-Lnupy7qb.cjs → run-Bz9IFMWg.cjs} +2 -2
  37. package/dist/{run-Lnupy7qb.cjs.map → run-Bz9IFMWg.cjs.map} +1 -1
  38. package/dist/{run-tnqS6GZS.cjs → run-CK8Cvq6n.cjs} +485 -310
  39. package/dist/run-CK8Cvq6n.cjs.map +1 -0
  40. package/dist/run-C_NMctua.cjs.map +1 -1
  41. package/dist/{run-zuPIKTwa.js → run-Ca2h07rN.js} +551 -376
  42. package/dist/run-Ca2h07rN.js.map +1 -0
  43. package/dist/run-D8dCWepS.js.map +1 -1
  44. package/dist/{run-BRrNHp24.js → run-DJxYClJV.js} +3 -3
  45. package/dist/{run-BRrNHp24.js.map → run-DJxYClJV.js.map} +1 -1
  46. package/dist/{telemetry-DRhd3joO.cjs → telemetry-B80oJfxR.cjs} +2 -2
  47. package/dist/telemetry-B80oJfxR.cjs.map +1 -0
  48. package/dist/{telemetry-ne1IOrz1.js → telemetry-ueaMzs_c.js} +2 -2
  49. package/dist/telemetry-ueaMzs_c.js.map +1 -0
  50. package/dist/{validate-Cjvn6prf.js → validate-BEEerg2-.js} +3 -3
  51. package/dist/{validate-Cjvn6prf.js.map → validate-BEEerg2-.js.map} +1 -1
  52. package/dist/{validate-3ROToMMX.cjs → validate-B_wfDSHQ.cjs} +3 -3
  53. package/dist/{validate-3ROToMMX.cjs.map → validate-B_wfDSHQ.cjs.map} +1 -1
  54. package/package.json +7 -7
  55. package/src/commands/generate.ts +17 -10
  56. package/src/constants.ts +1 -1
  57. package/src/loggers/clackLogger.ts +68 -71
  58. package/src/loggers/diagnostics.ts +77 -0
  59. package/src/loggers/githubActionsLogger.ts +38 -31
  60. package/src/loggers/plainLogger.ts +10 -26
  61. package/src/loggers/types.ts +1 -1
  62. package/src/loggers/utils.ts +43 -96
  63. package/src/reporters/cliReporter.ts +89 -0
  64. package/src/reporters/fileReporter.ts +103 -0
  65. package/src/reporters/jsonReporter.ts +15 -0
  66. package/src/reporters/report.ts +84 -0
  67. package/src/runners/agent/run.ts +2 -2
  68. package/src/runners/generate/run.ts +130 -44
  69. package/src/runners/generate/utils.ts +8 -11
  70. package/src/runners/init/run.ts +1 -1
  71. package/src/telemetry.ts +2 -2
  72. package/dist/generate-DYPz-3vT.cjs.map +0 -1
  73. package/dist/generate-DuzUhvOd.js.map +0 -1
  74. package/dist/package-B0p9bKKV.js +0 -6
  75. package/dist/package-B0p9bKKV.js.map +0 -1
  76. package/dist/package-B121qOXj.cjs.map +0 -1
  77. package/dist/run-tnqS6GZS.cjs.map +0 -1
  78. package/dist/run-zuPIKTwa.js.map +0 -1
  79. package/dist/telemetry-DRhd3joO.cjs.map +0 -1
  80. package/dist/telemetry-ne1IOrz1.js.map +0 -1
  81. package/src/loggers/fileSystemLogger.ts +0 -151
@@ -1,10 +1,10 @@
1
1
  import "./chunk-CRm0XQPb.js";
2
2
  import { n as toCause, r as toError } from "./errors-CoxrNXaA.js";
3
- import { a as canUseTTY, i as executeIfOnline, o as isGitHubActions, r as sendTelemetry, t as buildTelemetryEvent } from "./telemetry-ne1IOrz1.js";
3
+ import { a as canUseTTY, i as executeIfOnline, o as isGitHubActions, r as sendTelemetry, t as buildTelemetryEvent } from "./telemetry-ueaMzs_c.js";
4
4
  import { n as tokenize } from "./shell-BrqyJdB7.js";
5
- import { t as version } from "./package-B0p9bKKV.js";
6
- import { a as WATCHER_IGNORED_PATHS, i as SUMMARY_SEPARATOR, t as KUBB_NPM_PACKAGE_URL } from "./constants-Co6NWt3U.js";
7
- import { styleText } from "node:util";
5
+ import { t as version } from "./package-DUwUSFeL.js";
6
+ import { i as WATCHER_IGNORED_PATHS, t as KUBB_NPM_PACKAGE_URL } from "./constants-CYxk4aNm.js";
7
+ import { stripVTControlCharacters, styleText } from "node:util";
8
8
  import { EventEmitter } from "node:events";
9
9
  import { createHash } from "node:crypto";
10
10
  import { spawn } from "node:child_process";
@@ -13,7 +13,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
13
13
  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
- import { createKubb, defineLogger, isInputPath, logLevel } from "@kubb/core";
16
+ import { Diagnostics, createKubb, createReporter, defineLogger, diagnosticCode, isInputPath, isPerformanceDiagnostic, isProblemDiagnostic, isUpdateDiagnostic, logLevel, narrowDiagnostic } from "@kubb/core";
17
17
  import { cosmiconfig } from "cosmiconfig";
18
18
  import { createJiti } from "jiti";
19
19
  import { NonZeroExitError, x } from "tinyexec";
@@ -115,6 +115,24 @@ var AsyncEventEmitter = class {
115
115
  return this.#emitter.listenerCount(eventName);
116
116
  }
117
117
  /**
118
+ * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
119
+ * Set this above the expected listener count when many listeners attach by design.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * emitter.setMaxListeners(40)
124
+ * ```
125
+ */
126
+ setMaxListeners(max) {
127
+ this.#emitter.setMaxListeners(max);
128
+ }
129
+ /**
130
+ * Returns the current per-event listener ceiling.
131
+ */
132
+ getMaxListeners() {
133
+ return this.#emitter.getMaxListeners();
134
+ }
135
+ /**
118
136
  * Removes all listeners from every event channel.
119
137
  *
120
138
  * @example
@@ -488,6 +506,262 @@ async function detectLinter() {
488
506
  return null;
489
507
  }
490
508
  //#endregion
509
+ //#region src/reporters/report.ts
510
+ /**
511
+ * Builds the normalized {@link Report} for one config from its {@link GenerationResult}. Splits the
512
+ * diagnostics into problems and per-plugin timings (slowest first) and derives the plugin and issue
513
+ * counts, so every reporter renders the same data.
514
+ */
515
+ function buildReport(result) {
516
+ const { config, diagnostics, filesCreated, status, hrStart } = result;
517
+ const failed = Diagnostics.failedPlugins(diagnostics);
518
+ const total = config.plugins?.length ?? 0;
519
+ const counts = Diagnostics.count(diagnostics);
520
+ const problems = diagnostics.filter(isProblemDiagnostic);
521
+ const timings = diagnostics.filter(isPerformanceDiagnostic).sort((a, b) => b.duration - a.duration).map((diagnostic) => ({
522
+ plugin: diagnostic.plugin,
523
+ durationMs: diagnostic.duration
524
+ }));
525
+ return {
526
+ name: config.name ?? "",
527
+ status,
528
+ plugins: {
529
+ passed: total - failed.length,
530
+ failed,
531
+ total
532
+ },
533
+ counts,
534
+ filesCreated,
535
+ durationMs: getElapsedMs(hrStart),
536
+ output: resolve(config.root, config.output.path),
537
+ timings,
538
+ diagnostics: problems.map((diagnostic) => Diagnostics.serialize(diagnostic))
539
+ };
540
+ }
541
+ //#endregion
542
+ //#region src/reporters/cliReporter.ts
543
+ /**
544
+ * Builds the vitest/jest-style summary for one {@link Report}: right-aligned dim labels with
545
+ * `N passed (total)` counts, and a per-plugin `Timings` section when `showTimings`.
546
+ */
547
+ function buildSummaryLines(report, { showTimings }) {
548
+ const { status, plugins, counts, filesCreated, durationMs, output, timings } = report;
549
+ const rows = [];
550
+ rows.push(["Plugins", status === "success" ? `${styleText("green", `${plugins.passed} passed`)} (${plugins.total})` : `${styleText("green", `${plugins.passed} passed`)} | ${styleText("red", `${plugins.failed.length} failed`)} (${plugins.total})`]);
551
+ if (status === "failed" && plugins.failed.length > 0) rows.push(["Failed", plugins.failed.map((name) => randomCliColor(name)).join(", ")]);
552
+ if (counts.errors > 0 || counts.warnings > 0) {
553
+ const issues = [counts.errors > 0 ? styleText("red", `${counts.errors} ${counts.errors === 1 ? "error" : "errors"}`) : void 0, counts.warnings > 0 ? styleText("yellow", `${counts.warnings} ${counts.warnings === 1 ? "warning" : "warnings"}`) : void 0].filter(Boolean).join(" | ");
554
+ rows.push(["Issues", issues]);
555
+ }
556
+ rows.push(["Files", `${styleText("green", String(filesCreated))} generated`]);
557
+ rows.push(["Duration", styleText("green", formatMs(durationMs))]);
558
+ rows.push(["Output", output]);
559
+ const labelWidth = Math.max(...rows.map(([label]) => label.length), timings.length > 0 ? 7 : 0);
560
+ const lines = rows.map(([label, value]) => `${styleText("dim", label.padStart(labelWidth))} ${value}`);
561
+ if (showTimings && timings.length > 0) {
562
+ const nameWidth = Math.max(0, ...timings.map((timing) => timing.plugin.length));
563
+ const indent = " ".repeat(labelWidth + 2);
564
+ lines.push(styleText("dim", "Timings".padStart(labelWidth)));
565
+ for (const timing of timings) {
566
+ const timeStr = formatMs(timing.durationMs);
567
+ const barLength = Math.min(Math.ceil(timing.durationMs / 100), 10);
568
+ const bar = styleText("dim", "█".repeat(barLength));
569
+ lines.push(`${indent}${styleText("dim", "•")} ${timing.plugin.padEnd(nameWidth)} ${bar} ${timeStr}`);
570
+ }
571
+ }
572
+ return lines;
573
+ }
574
+ /**
575
+ * Renders the summary as plain `console.log` lines so it works in every CLI (no clack/TTY
576
+ * dependency): a blank line, the config name colored by status, then the summary rows.
577
+ */
578
+ function renderSummary(lines, { title, status }) {
579
+ console.log("");
580
+ if (title) console.log(styleText(status === "failed" ? "red" : "green", title));
581
+ for (const line of lines) console.log(line);
582
+ }
583
+ /**
584
+ * The default `cli` reporter. Renders the {@link Report} for each config as it finishes, independent
585
+ * of the live logger view. Suppressed at `silent`; the `verbose` level adds the per-plugin timings.
586
+ */
587
+ const cliReporter = createReporter({
588
+ name: "cli",
589
+ report(result, { logLevel: logLevel$7 }) {
590
+ if (logLevel$7 <= logLevel.silent) return;
591
+ const report = buildReport(result);
592
+ renderSummary(buildSummaryLines(report, { showTimings: logLevel$7 >= logLevel.verbose }), {
593
+ title: report.name,
594
+ status: report.status
595
+ });
596
+ }
597
+ });
598
+ //#endregion
599
+ //#region src/loggers/diagnostics.ts
600
+ /**
601
+ * Glyph and accent color per severity, matching the miette/oxlint convention
602
+ * (`×` error, `⚠` warning, `ℹ` advice).
603
+ */
604
+ const severityStyle = {
605
+ error: {
606
+ glyph: "×",
607
+ color: "red"
608
+ },
609
+ warning: {
610
+ glyph: "⚠",
611
+ color: "yellow"
612
+ },
613
+ info: {
614
+ glyph: "ℹ",
615
+ color: "blue"
616
+ }
617
+ };
618
+ /**
619
+ * The colored, bold severity glyph (`×`, `⚠`, `ℹ`) on its own. Pass it as clack's
620
+ * `symbol` so clack owns the gutter and adds the bar to the continuation lines,
621
+ * instead of baking the glyph into the message text.
622
+ */
623
+ function diagnosticSymbol(severity) {
624
+ const { glyph, color } = severityStyle[severity];
625
+ return styleText(color, styleText("bold", glyph));
626
+ }
627
+ /**
628
+ * The `plugin(CODE): message` headline, without the leading severity glyph.
629
+ */
630
+ function diagnosticHeadline(diagnostic) {
631
+ const { code, severity, message } = diagnostic;
632
+ const plugin = isProblemDiagnostic(diagnostic) ? diagnostic.plugin : void 0;
633
+ const { color } = severityStyle[severity];
634
+ return `${styleText(color, styleText("bold", plugin ? `${plugin}(${code})` : code))}: ${message}`;
635
+ }
636
+ /**
637
+ * The detail lines below the headline: optional `at <pointer>`, `help:`, and
638
+ * `docs:`. OpenAPI has no line/column, so the location is the JSON pointer the
639
+ * adapter built. Each line keeps a two-space indent so it sits under the headline.
640
+ */
641
+ function diagnosticDetails(diagnostic) {
642
+ const { code } = diagnostic;
643
+ const problem = isProblemDiagnostic(diagnostic) ? diagnostic : void 0;
644
+ const location = problem?.location;
645
+ const help = problem?.help;
646
+ const lines = [];
647
+ if (location && "pointer" in location) lines.push(` ${styleText("dim", "at")} ${styleText("cyan", location.pointer)}`);
648
+ if (help) lines.push(` ${styleText("cyan", "help:")} ${help}`);
649
+ if (code !== diagnosticCode.unknown) lines.push(` ${styleText("dim", "docs:")} ${styleText("cyan", Diagnostics.docsUrl(code))}`);
650
+ return lines;
651
+ }
652
+ /**
653
+ * Renders a {@link Diagnostic} in the oxlint style as a self-contained block: a
654
+ * `× plugin(CODE): message` header followed by the {@link diagnosticDetails}.
655
+ * Use this where clack's gutter is not available (plain, file output); clack
656
+ * loggers pass {@link diagnosticSymbol}, {@link diagnosticHeadline}, and
657
+ * {@link diagnosticDetails} to `clack.log.message` instead.
658
+ *
659
+ * @example
660
+ * ```ts
661
+ * formatDiagnostic({ code: 'KUBB_REF_NOT_FOUND', severity: 'error', message: 'Could not find Pet', help: 'Add Pet under components.schemas.', plugin: '@kubb/plugin-zod', location: { kind: 'schema', pointer: '#/components/schemas/Pet' } })
662
+ * ```
663
+ */
664
+ function formatDiagnostic(diagnostic) {
665
+ return [`${diagnosticSymbol(diagnostic.severity)} ${diagnosticHeadline(diagnostic)}`, ...diagnosticDetails(diagnostic)];
666
+ }
667
+ //#endregion
668
+ //#region src/reporters/fileReporter.ts
669
+ /**
670
+ * Builds the `## Summary` section: the same counts the cli and json reporters expose, as a list of
671
+ * `label value` rows with the labels padded to a common width.
672
+ */
673
+ function buildSummarySection(report) {
674
+ const { status, plugins, counts, filesCreated, durationMs, output } = report;
675
+ const rows = [["Status", status], ["Plugins", status === "success" ? `${plugins.passed} passed (${plugins.total})` : `${plugins.passed} passed | ${plugins.failed.length} failed (${plugins.total})`]];
676
+ if (plugins.failed.length > 0) rows.push(["Failed", plugins.failed.join(", ")]);
677
+ rows.push(["Issues", `${counts.errors} errors | ${counts.warnings} warnings | ${counts.infos} infos`]);
678
+ rows.push(["Files", `${filesCreated} generated`]);
679
+ rows.push(["Duration", formatMs(durationMs)]);
680
+ rows.push(["Output", output]);
681
+ const labelWidth = Math.max(...rows.map(([label]) => label.length));
682
+ return [
683
+ "## Summary",
684
+ "",
685
+ ...rows.map(([label, value]) => ` ${label.padEnd(labelWidth)} ${value}`)
686
+ ];
687
+ }
688
+ /**
689
+ * Builds the `## Problems` section: each problem rendered in the miette block format, blocks
690
+ * separated by a blank line. Returns an empty array when there are no problems, so the caller
691
+ * can drop the heading.
692
+ */
693
+ function buildProblemSection(diagnostics) {
694
+ const problems = diagnostics.filter(isProblemDiagnostic);
695
+ if (problems.length === 0) return [];
696
+ return [
697
+ "## Problems",
698
+ "",
699
+ problems.map((diagnostic) => formatDiagnostic(diagnostic).join("\n")).join("\n\n")
700
+ ];
701
+ }
702
+ /**
703
+ * Builds the `## Timings` section from a {@link Report}: one `plugin duration` row per record,
704
+ * slowest first with the plugin names left-aligned and the durations right-aligned. Returns an
705
+ * empty array when there are no timings.
706
+ */
707
+ function buildTimingSection(report) {
708
+ const { timings } = report;
709
+ if (timings.length === 0) return [];
710
+ const nameWidth = Math.max(...timings.map((timing) => timing.plugin.length));
711
+ const durations = timings.map((timing) => formatMs(timing.durationMs));
712
+ const durationWidth = Math.max(...durations.map((duration) => duration.length));
713
+ return [
714
+ "## Timings",
715
+ "",
716
+ ...timings.map((timing, index) => ` ${timing.plugin.padEnd(nameWidth)} ${durations[index].padStart(durationWidth)}`)
717
+ ];
718
+ }
719
+ /**
720
+ * The `file` reporter. Writes a config's {@link Report} to `.kubb/kubb-<name>-<timestamp>.log` as a
721
+ * plain-text document: a `# <name> — <timestamp>` header, a `## Summary` with the same counts the
722
+ * cli and json reporters expose, a `## Problems` section in the miette block format, and a
723
+ * `## Timings` section. Selected with `--reporter file` (or `reporters: ['file']`), replacing the
724
+ * old `--debug` flag.
725
+ *
726
+ * @note Unlike the streaming logger it replaced, it captures the collected diagnostics once a
727
+ * config finishes, not the live `kubb:info`/`kubb:plugin` event stream. Color is stripped so the
728
+ * file stays plain text even when the run is attached to a TTY.
729
+ */
730
+ const fileReporter = createReporter({
731
+ name: "file",
732
+ async report(result) {
733
+ const { diagnostics, config } = result;
734
+ if (diagnostics.length === 0) return;
735
+ const report = buildReport(result);
736
+ const content = stripVTControlCharacters([config.name ? `# ${config.name} — ${(/* @__PURE__ */ new Date()).toISOString()}` : `# ${(/* @__PURE__ */ new Date()).toISOString()}`, ...[
737
+ buildSummarySection(report),
738
+ buildProblemSection(diagnostics),
739
+ buildTimingSection(report)
740
+ ].filter((section) => section.length > 0).map((section) => section.join("\n"))].join("\n\n"));
741
+ const baseName = `${[
742
+ "kubb",
743
+ config.name,
744
+ Date.now()
745
+ ].filter(Boolean).join("-")}.log`;
746
+ const pathName = resolve(process$1.cwd(), ".kubb", baseName);
747
+ await write(pathName, `${content}\n`);
748
+ console.error(`Debug log written to ${relative(process$1.cwd(), pathName)}`);
749
+ }
750
+ });
751
+ //#endregion
752
+ //#region src/reporters/jsonReporter.ts
753
+ /**
754
+ * The `json` reporter. Writes the {@link Report} for each config to stdout as JSON, for CI tooling.
755
+ * The terminal reporter is suppressed while this is active so stdout stays valid JSON.
756
+ */
757
+ const jsonReporter = createReporter({
758
+ name: "json",
759
+ report(result) {
760
+ const report = buildReport(result);
761
+ process$1.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
762
+ }
763
+ });
764
+ //#endregion
491
765
  //#region src/loggers/clackLogger.ts
492
766
  /**
493
767
  * TTY logger with beautiful UI and progress indicators for local development.
@@ -495,36 +769,45 @@ async function detectLinter() {
495
769
  const clackLogger = defineLogger({
496
770
  name: "clack",
497
771
  install(context, options) {
498
- const logLevel$8 = options?.logLevel ?? logLevel.info;
772
+ const logLevel$6 = options?.logLevel ?? logLevel.info;
499
773
  const state = {
500
774
  ...createProgressCounters(),
501
775
  spinner: clack.spinner(),
502
776
  isSpinning: false,
777
+ runningPlugins: /* @__PURE__ */ new Set(),
503
778
  activeProgress: /* @__PURE__ */ new Map(),
504
779
  activeHookLogs: /* @__PURE__ */ new Map()
505
780
  };
506
- function reset() {
507
- for (const [_key, active] of state.activeProgress) {
781
+ function stopActiveProgress() {
782
+ for (const [, active] of state.activeProgress) {
508
783
  if (active.interval) clearInterval(active.interval);
509
784
  active.progressBar?.stop();
510
785
  }
786
+ state.activeProgress.clear();
787
+ }
788
+ function reset() {
789
+ stopActiveProgress();
511
790
  resetProgressCounters(state);
512
791
  state.spinner = clack.spinner();
513
792
  state.isSpinning = false;
514
- state.activeProgress.clear();
793
+ state.runningPlugins.clear();
515
794
  state.activeHookLogs.clear();
516
795
  }
796
+ function pluginProgressText() {
797
+ const running = [...state.runningPlugins].map((name) => styleText("bold", name));
798
+ return getMessage(running.length > 0 ? `Generating ${running.join(", ")}` : "Generating plugins");
799
+ }
517
800
  function showProgressStep() {
518
- if (logLevel$8 <= logLevel.silent) return;
801
+ if (logLevel$6 <= logLevel.silent) return;
519
802
  const line = buildProgressLine(state);
520
803
  if (line) clack.log.step(getMessage(line));
521
804
  }
522
805
  function getMessage(message) {
523
- return formatMessage(message, logLevel$8);
806
+ return formatMessage(message, logLevel$6);
524
807
  }
525
808
  function onStep(event, message) {
526
809
  context.on(event, () => {
527
- if (logLevel$8 <= logLevel.silent) return;
810
+ if (logLevel$6 <= logLevel.silent) return;
528
811
  clack.log.step(getMessage(message));
529
812
  });
530
813
  }
@@ -538,12 +821,12 @@ const clackLogger = defineLogger({
538
821
  state.isSpinning = false;
539
822
  }
540
823
  context.on("kubb:info", ({ message, info = "" }) => {
541
- if (logLevel$8 <= logLevel.silent) return;
824
+ if (logLevel$6 <= logLevel.silent) return;
542
825
  const text = getMessage([
543
826
  styleText("blue", "ℹ"),
544
827
  message,
545
- styleText("dim", info)
546
- ].join(" "));
828
+ info ? styleText("dim", info) : void 0
829
+ ].filter(Boolean).join(" "));
547
830
  if (state.isSpinning) {
548
831
  state.spinner.message(text);
549
832
  return;
@@ -551,11 +834,11 @@ const clackLogger = defineLogger({
551
834
  clack.log.info(text);
552
835
  });
553
836
  context.on("kubb:success", ({ message, info = "" }) => {
554
- if (logLevel$8 <= logLevel.silent) return;
837
+ if (logLevel$6 <= logLevel.silent) return;
555
838
  const text = getMessage([
556
839
  styleText("blue", "✓"),
557
840
  message,
558
- logLevel$8 >= logLevel.info ? styleText("dim", info) : void 0
841
+ logLevel$6 >= logLevel.info ? styleText("dim", info) : void 0
559
842
  ].filter(Boolean).join(" "));
560
843
  if (state.isSpinning) {
561
844
  stopSpinner(text);
@@ -564,11 +847,11 @@ const clackLogger = defineLogger({
564
847
  clack.log.success(text);
565
848
  });
566
849
  context.on("kubb:warn", ({ message, info }) => {
567
- if (logLevel$8 < logLevel.warn) return;
850
+ if (logLevel$6 < logLevel.warn) return;
568
851
  const text = getMessage([
569
852
  styleText("yellow", "⚠"),
570
853
  message,
571
- logLevel$8 >= logLevel.info && info ? styleText("dim", info) : void 0
854
+ logLevel$6 >= logLevel.info && info ? styleText("dim", info) : void 0
572
855
  ].filter(Boolean).join(" "));
573
856
  clack.log.warn(text);
574
857
  });
@@ -580,7 +863,7 @@ const clackLogger = defineLogger({
580
863
  return;
581
864
  }
582
865
  clack.log.error(getMessage(text));
583
- if (logLevel$8 >= logLevel.debug && error.stack) {
866
+ if (logLevel$6 >= logLevel.verbose && error.stack) {
584
867
  const frames = error.stack.split("\n").slice(1, 4);
585
868
  for (const frame of frames) clack.log.message(getMessage(styleText("dim", frame.trim())));
586
869
  if (caused?.stack) {
@@ -590,10 +873,12 @@ const clackLogger = defineLogger({
590
873
  }
591
874
  }
592
875
  });
593
- context.on("kubb:version:new", ({ currentVersion, latestVersion }) => {
594
- if (logLevel$8 <= logLevel.silent) return;
595
- try {
596
- clack.box(`\`v${currentVersion}\` → \`v${latestVersion}\`
876
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
877
+ if (logLevel$6 <= logLevel.silent && diagnostic.severity !== "error") return;
878
+ stopSpinner();
879
+ stopActiveProgress();
880
+ if (isUpdateDiagnostic(diagnostic)) {
881
+ clack.box(`\`v${diagnostic.currentVersion}\` → \`v${diagnostic.latestVersion}\`
597
882
  Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
598
883
  width: "auto",
599
884
  formatBorder: (s) => styleText("yellow", s),
@@ -602,14 +887,13 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
602
887
  contentAlign: "center",
603
888
  titleAlign: "center"
604
889
  });
605
- } catch {
606
- console.log(`Update available for Kubb: v${currentVersion} → v${latestVersion}`);
607
- console.log("Run `npm install -g @kubb/cli` to update");
890
+ return;
608
891
  }
892
+ clack.log.message([diagnosticHeadline(diagnostic), ...diagnosticDetails(diagnostic)], { symbol: diagnosticSymbol(diagnostic.severity) });
609
893
  });
610
894
  context.on("kubb:lifecycle:start", async ({ version }) => {
611
895
  console.log(`\n${getIntro({
612
- title: "The ultimate toolkit for working with APIs",
896
+ title: "The meta framework for code generation",
613
897
  description: "Ready to start",
614
898
  version,
615
899
  areEyesOpen: true
@@ -617,55 +901,56 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
617
901
  reset();
618
902
  });
619
903
  context.on("kubb:config:start", () => {
620
- if (logLevel$8 <= logLevel.silent) return;
904
+ if (logLevel$6 <= logLevel.silent) return;
621
905
  const text = getMessage("Configuration started");
622
906
  clack.intro(text);
623
907
  startSpinner(getMessage("Configuration loading"));
624
908
  });
625
909
  context.on("kubb:config:end", () => {
626
- if (logLevel$8 <= logLevel.silent) return;
910
+ if (logLevel$6 <= logLevel.silent) return;
627
911
  const text = getMessage("Configuration completed");
628
912
  clack.outro(text);
629
913
  });
630
914
  context.on("kubb:generation:start", ({ config }) => {
631
915
  reset();
632
916
  state.totalPlugins = config.plugins?.length ?? 0;
633
- if (logLevel$8 <= logLevel.silent) return;
917
+ if (logLevel$6 <= logLevel.silent) return;
634
918
  const text = getMessage(["Generation started", config.name ? `for ${styleText("dim", config.name)}` : void 0].filter(Boolean).join(" "));
635
919
  clack.intro(text);
636
920
  });
637
921
  context.on("kubb:plugin:start", ({ plugin }) => {
638
- if (logLevel$8 <= logLevel.silent) return;
922
+ if (logLevel$6 <= logLevel.silent) return;
639
923
  stopSpinner();
924
+ state.runningPlugins.add(plugin.name);
925
+ const active = state.activeProgress.get("plugins");
926
+ if (active) {
927
+ active.progressBar.advance(0, pluginProgressText());
928
+ return;
929
+ }
640
930
  const progressBar = clack.progress({
641
931
  style: "block",
642
- max: 100,
932
+ max: Math.max(state.totalPlugins, 1),
643
933
  size: 30
644
934
  });
645
- const text = getMessage(`Generating ${styleText("bold", plugin.name)}`);
646
- progressBar.start(text);
647
- const interval = setInterval(() => {
648
- progressBar.advance();
649
- }, 100);
650
- state.activeProgress.set(plugin.name, {
651
- progressBar,
652
- interval
653
- });
935
+ progressBar.start(pluginProgressText());
936
+ progressBar.advance(state.completedPlugins + state.failedPlugins, pluginProgressText());
937
+ state.activeProgress.set("plugins", { progressBar });
654
938
  });
655
- context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
939
+ context.on("kubb:plugin:end", ({ plugin, success }) => {
656
940
  stopSpinner();
657
- const active = state.activeProgress.get(plugin.name);
658
- if (!active || logLevel$8 === logLevel.silent) return;
659
- clearInterval(active.interval);
941
+ const active = state.activeProgress.get("plugins");
942
+ if (!active || logLevel$6 === logLevel.silent) return;
943
+ state.runningPlugins.delete(plugin.name);
660
944
  recordPluginResult(state, success);
661
- const durationStr = formatMsWithColor(duration);
662
- const text = getMessage(success ? `${styleText("bold", plugin.name)} completed in ${durationStr}` : `${styleText("bold", plugin.name)} failed in ${styleText("red", formatMs(duration))}`);
663
- active.progressBar.stop(text);
664
- state.activeProgress.delete(plugin.name);
665
- showProgressStep();
945
+ active.progressBar.advance(1, pluginProgressText());
946
+ if (state.runningPlugins.size === 0) {
947
+ active.progressBar.stop(getMessage("Plugins generated"));
948
+ state.activeProgress.delete("plugins");
949
+ showProgressStep();
950
+ }
666
951
  });
667
952
  context.on("kubb:files:processing:start", ({ files }) => {
668
- if (logLevel$8 <= logLevel.silent) return;
953
+ if (logLevel$6 <= logLevel.silent) return;
669
954
  stopSpinner();
670
955
  state.totalFiles = files.length;
671
956
  state.processedFiles = 0;
@@ -680,7 +965,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
680
965
  state.activeProgress.set("files", { progressBar });
681
966
  });
682
967
  context.on("kubb:files:processing:update", ({ files }) => {
683
- if (logLevel$8 <= logLevel.silent) return;
968
+ if (logLevel$6 <= logLevel.silent) return;
684
969
  stopSpinner();
685
970
  const active = state.activeProgress.get("files");
686
971
  for (const { file, config } of files) {
@@ -689,7 +974,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
689
974
  }
690
975
  });
691
976
  context.on("kubb:files:processing:end", () => {
692
- if (logLevel$8 <= logLevel.silent) return;
977
+ if (logLevel$6 <= logLevel.silent) return;
693
978
  stopSpinner();
694
979
  const text = getMessage("Files written successfully");
695
980
  const active = state.activeProgress.get("files");
@@ -707,7 +992,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
707
992
  onStep("kubb:lint:start", "Linting");
708
993
  onStep("kubb:hooks:start", "Running hooks");
709
994
  context.on("kubb:hook:start", ({ id, command, args }) => {
710
- if (logLevel$8 <= logLevel.silent || !id) return;
995
+ if (logLevel$6 <= logLevel.silent || !id) return;
711
996
  stopSpinner();
712
997
  const title = getMessage(`Running ${styleText("dim", formatCommandWithArgs(command, args))}`);
713
998
  const taskLog = clack.taskLog({ title });
@@ -717,7 +1002,7 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
717
1002
  });
718
1003
  });
719
1004
  context.on("kubb:hook:end", ({ id, command, args, success, error }) => {
720
- if (logLevel$8 <= logLevel.silent || !id) return;
1005
+ if (logLevel$6 <= logLevel.silent || !id) return;
721
1006
  const active = state.activeHookLogs.get(id);
722
1007
  if (!active) return;
723
1008
  state.activeHookLogs.delete(id);
@@ -729,37 +1014,11 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
729
1014
  active.taskLog.error(getMessage(`${styleText("dim", commandWithArgs)} failed${reason}`), { showLog: true });
730
1015
  }
731
1016
  });
732
- context.on("kubb:generation:summary", ({ config, pluginTimings, failedPlugins, filesCreated, status, hrStart }) => {
733
- const summary = getSummary({
734
- failedPlugins,
735
- filesCreated,
736
- config,
737
- status,
738
- hrStart,
739
- pluginTimings: logLevel$8 >= logLevel.verbose ? pluginTimings : void 0
740
- });
741
- const title = config.name || "";
742
- summary.unshift("\n");
743
- summary.push("\n");
744
- const borderColor = status === "success" ? "green" : "red";
745
- try {
746
- clack.box(summary.join("\n"), getMessage(title), {
747
- width: "auto",
748
- formatBorder: (s) => styleText(borderColor, s),
749
- rounded: true,
750
- withGuide: false,
751
- contentAlign: "left",
752
- titleAlign: "center"
753
- });
754
- } catch {
755
- console.log(summary.join("\n"));
756
- }
757
- });
758
1017
  context.on("kubb:lifecycle:end", () => {
759
1018
  reset();
760
1019
  });
761
1020
  return (_commandWithArgs, hookId) => {
762
- if (logLevel$8 <= logLevel.silent) return {
1021
+ if (logLevel$6 <= logLevel.silent) return {
763
1022
  onStdout: (s) => console.log(s),
764
1023
  onStderr: (s) => console.error(s)
765
1024
  };
@@ -776,114 +1035,6 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
776
1035
  }
777
1036
  });
778
1037
  //#endregion
779
- //#region src/loggers/fileSystemLogger.ts
780
- /**
781
- * FileSystem logger that captures debug events and writes them to `.kubb` directory files.
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.
784
- */
785
- const fileSystemLogger = defineLogger({
786
- name: "filesystem",
787
- install(context) {
788
- const state = {
789
- cachedLogs: /* @__PURE__ */ new Set(),
790
- startDate: Date.now()
791
- };
792
- function reset() {
793
- state.cachedLogs = /* @__PURE__ */ new Set();
794
- state.startDate = Date.now();
795
- }
796
- async function writeLogs(name) {
797
- if (state.cachedLogs.size === 0) return [];
798
- const files = {};
799
- for (const log of state.cachedLogs) {
800
- const baseName = log.fileName || `${[
801
- "kubb",
802
- name,
803
- state.startDate
804
- ].filter(Boolean).join("-")}.log`;
805
- const pathName = resolve(process$1.cwd(), ".kubb", baseName);
806
- if (!files[pathName]) files[pathName] = [];
807
- if (log.logs.length > 0) {
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"));
812
- }
813
- }
814
- for (const [fileName, logs] of Object.entries(files)) await write(fileName, logs.join("\n"));
815
- return Object.keys(files);
816
- }
817
- context.on("kubb:info", ({ message, info }) => {
818
- state.cachedLogs.add({
819
- date: /* @__PURE__ */ new Date(),
820
- logs: [`ℹ ${[message, info].filter(Boolean).join(" ")}`]
821
- });
822
- });
823
- context.on("kubb:success", ({ message, info }) => {
824
- state.cachedLogs.add({
825
- date: /* @__PURE__ */ new Date(),
826
- logs: [`✓ ${[message, info].filter(Boolean).join(" ")}`]
827
- });
828
- });
829
- context.on("kubb:warn", ({ message, info }) => {
830
- state.cachedLogs.add({
831
- date: /* @__PURE__ */ new Date(),
832
- logs: [`⚠ ${[message, info].filter(Boolean).join(" ")}`]
833
- });
834
- });
835
- context.on("kubb:error", ({ error }) => {
836
- state.cachedLogs.add({
837
- date: /* @__PURE__ */ new Date(),
838
- logs: [`✗ ${error.message}`, error.stack || "unknown stack"]
839
- });
840
- });
841
- context.on("kubb:debug", ({ date, fileName, logs }) => {
842
- state.cachedLogs.add({
843
- date,
844
- fileName,
845
- logs
846
- });
847
- });
848
- context.on("kubb:plugin:start", ({ plugin }) => {
849
- state.cachedLogs.add({
850
- date: /* @__PURE__ */ new Date(),
851
- logs: [`► Generating ${plugin.name}`]
852
- });
853
- });
854
- context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
855
- const durationStr = formatMs(duration);
856
- state.cachedLogs.add({
857
- date: /* @__PURE__ */ new Date(),
858
- logs: [success ? `✓ ${plugin.name} completed in ${durationStr}` : `✗ ${plugin.name} failed in ${durationStr}`]
859
- });
860
- });
861
- context.on("kubb:files:processing:start", ({ files }) => {
862
- state.cachedLogs.add({
863
- date: /* @__PURE__ */ new Date(),
864
- logs: [`► Writing ${files.length} files`, ...files.map((file) => ` ${file.path}`)]
865
- });
866
- });
867
- context.on("kubb:generation:end", async ({ config }) => {
868
- const writtenFilePaths = await writeLogs(config.name);
869
- if (writtenFilePaths.length > 0) {
870
- const files = writtenFilePaths.map((f) => relative(process$1.cwd(), f));
871
- await context.emit("kubb:info", {
872
- message: "Debug files written to:",
873
- info: files.join(", ")
874
- });
875
- }
876
- reset();
877
- });
878
- const exitHandler = () => {
879
- if (state.cachedLogs.size > 0) writeLogs().catch(() => {});
880
- };
881
- process$1.once("exit", exitHandler);
882
- process$1.once("SIGINT", exitHandler);
883
- process$1.once("SIGTERM", exitHandler);
884
- }
885
- });
886
- //#endregion
887
1038
  //#region src/loggers/githubActionsLogger.ts
888
1039
  /**
889
1040
  * GitHub Actions logger using group annotations for collapsible sections in CI.
@@ -891,7 +1042,7 @@ const fileSystemLogger = defineLogger({
891
1042
  const githubActionsLogger = defineLogger({
892
1043
  name: "github-actions",
893
1044
  install(context, options) {
894
- const logLevel$7 = options?.logLevel ?? logLevel.info;
1045
+ const logLevel$5 = options?.logLevel ?? logLevel.info;
895
1046
  const state = {
896
1047
  ...createProgressCounters(),
897
1048
  currentConfigs: [],
@@ -905,12 +1056,12 @@ const githubActionsLogger = defineLogger({
905
1056
  hookTimer.clear();
906
1057
  }
907
1058
  function showProgressStep() {
908
- if (logLevel$7 <= logLevel.silent) return;
1059
+ if (logLevel$5 <= logLevel.silent) return;
909
1060
  const line = buildProgressLine(state);
910
1061
  if (line) console.log(getMessage(line));
911
1062
  }
912
1063
  function getMessage(message) {
913
- return formatMessage(message, logLevel$7);
1064
+ return formatMessage(message, logLevel$5);
914
1065
  }
915
1066
  function openGroup(name) {
916
1067
  console.log(`::group::${name}`);
@@ -928,20 +1079,20 @@ const githubActionsLogger = defineLogger({
928
1079
  }
929
1080
  function onGroupStart(event, message, group) {
930
1081
  context.on(event, () => {
931
- if (logLevel$7 <= logLevel.silent) return;
1082
+ if (logLevel$5 <= logLevel.silent) return;
932
1083
  if (state.currentConfigs.length === 1) openGroup(group);
933
1084
  console.log(getMessage(message));
934
1085
  });
935
1086
  }
936
1087
  function onGroupEnd(event, message, group) {
937
1088
  context.on(event, () => {
938
- if (logLevel$7 <= logLevel.silent) return;
1089
+ if (logLevel$5 <= logLevel.silent) return;
939
1090
  console.log(getMessage(message));
940
1091
  if (state.currentConfigs.length === 1) closeGroup(group);
941
1092
  });
942
1093
  }
943
1094
  context.on("kubb:info", ({ message, info = "" }) => {
944
- if (logLevel$7 <= logLevel.silent) return;
1095
+ if (logLevel$5 <= logLevel.silent) return;
945
1096
  const text = getMessage([
946
1097
  styleText("blue", "ℹ"),
947
1098
  message,
@@ -950,30 +1101,29 @@ const githubActionsLogger = defineLogger({
950
1101
  console.log(text);
951
1102
  });
952
1103
  context.on("kubb:success", ({ message, info = "" }) => {
953
- if (logLevel$7 <= logLevel.silent) return;
1104
+ if (logLevel$5 <= logLevel.silent) return;
954
1105
  const text = getMessage([
955
1106
  styleText("blue", "✓"),
956
1107
  message,
957
- logLevel$7 >= logLevel.info ? styleText("dim", info) : void 0
1108
+ logLevel$5 >= logLevel.info ? styleText("dim", info) : void 0
958
1109
  ].filter(Boolean).join(" "));
959
1110
  console.log(text);
960
1111
  });
961
1112
  context.on("kubb:warn", ({ message, info = "" }) => {
962
- if (logLevel$7 <= logLevel.silent) return;
1113
+ if (logLevel$5 <= logLevel.silent) return;
963
1114
  const text = getMessage([
964
1115
  styleText("yellow", "⚠"),
965
1116
  message,
966
- logLevel$7 >= logLevel.info ? styleText("dim", info) : void 0
1117
+ logLevel$5 >= logLevel.info ? styleText("dim", info) : void 0
967
1118
  ].filter(Boolean).join(" "));
968
1119
  console.warn(`::warning::${text}`);
969
1120
  });
970
1121
  context.on("kubb:error", ({ error }) => {
971
1122
  const caused = toCause(error);
972
1123
  closeAllGroups();
973
- if (logLevel$7 <= logLevel.silent) return;
974
1124
  const message = error.message || String(error);
975
1125
  console.error(`::error::${message}`);
976
- if (logLevel$7 >= logLevel.debug && error.stack) {
1126
+ if (logLevel$5 >= logLevel.verbose && error.stack) {
977
1127
  const frames = error.stack.split("\n").slice(1, 4);
978
1128
  for (const frame of frames) console.log(getMessage(styleText("dim", frame.trim())));
979
1129
  if (caused?.stack) {
@@ -983,22 +1133,33 @@ const githubActionsLogger = defineLogger({
983
1133
  }
984
1134
  }
985
1135
  });
1136
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
1137
+ closeAllGroups();
1138
+ if (logLevel$5 <= logLevel.silent && diagnostic.severity !== "error") return;
1139
+ if (!isProblemDiagnostic(diagnostic)) {
1140
+ console.log(`::notice::${diagnostic.message}`);
1141
+ return;
1142
+ }
1143
+ const parts = [`${diagnostic.code} ${diagnostic.message}`];
1144
+ if (diagnostic.location && "pointer" in diagnostic.location) parts.push(`(at ${diagnostic.location.pointer})`);
1145
+ if (diagnostic.plugin) parts.push(`[plugin: ${diagnostic.plugin}]`);
1146
+ if (diagnostic.help) parts.push(`help: ${diagnostic.help}`);
1147
+ if (diagnostic.code !== diagnosticCode.unknown) parts.push(`docs: ${Diagnostics.docsUrl(diagnostic.code)}`);
1148
+ console.error(`::error::${parts.join(" ")}`);
1149
+ });
986
1150
  context.on("kubb:lifecycle:start", ({ version }) => {
987
1151
  console.log(styleText("yellow", `Kubb ${version} 🧩`));
988
1152
  reset();
989
1153
  });
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
- });
993
1154
  context.on("kubb:config:start", () => {
994
- if (logLevel$7 <= logLevel.silent) return;
1155
+ if (logLevel$5 <= logLevel.silent) return;
995
1156
  const text = getMessage("Configuration started");
996
1157
  openGroup("Configuration");
997
1158
  console.log(text);
998
1159
  });
999
1160
  context.on("kubb:config:end", ({ configs }) => {
1000
1161
  state.currentConfigs = configs;
1001
- if (logLevel$7 <= logLevel.silent) return;
1162
+ if (logLevel$5 <= logLevel.silent) return;
1002
1163
  const text = getMessage("Configuration completed");
1003
1164
  console.log(text);
1004
1165
  closeGroup("Configuration");
@@ -1011,13 +1172,13 @@ const githubActionsLogger = defineLogger({
1011
1172
  if (state.currentConfigs.length === 1) console.log(getMessage(text));
1012
1173
  });
1013
1174
  context.on("kubb:plugin:start", ({ plugin }) => {
1014
- if (logLevel$7 <= logLevel.silent) return;
1175
+ if (logLevel$5 <= logLevel.silent) return;
1015
1176
  const text = getMessage(`Generating ${styleText("bold", plugin.name)}`);
1016
1177
  if (state.currentConfigs.length === 1) openGroup(`Plugin: ${plugin.name}`);
1017
1178
  console.log(text);
1018
1179
  });
1019
1180
  context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
1020
- if (logLevel$7 <= logLevel.silent) return;
1181
+ if (logLevel$5 <= logLevel.silent) return;
1021
1182
  recordPluginResult(state, success);
1022
1183
  const durationStr = formatMsWithColor(duration);
1023
1184
  const text = getMessage(success ? `${styleText("bold", plugin.name)} completed in ${durationStr}` : `${styleText("bold", plugin.name)} failed in ${styleText("red", formatMs(duration))}`);
@@ -1027,7 +1188,7 @@ const githubActionsLogger = defineLogger({
1027
1188
  showProgressStep();
1028
1189
  });
1029
1190
  context.on("kubb:files:processing:start", ({ files }) => {
1030
- if (logLevel$7 <= logLevel.silent) return;
1191
+ if (logLevel$5 <= logLevel.silent) return;
1031
1192
  state.totalFiles = files.length;
1032
1193
  state.processedFiles = 0;
1033
1194
  if (state.currentConfigs.length === 1) openGroup("File Generation");
@@ -1035,19 +1196,20 @@ const githubActionsLogger = defineLogger({
1035
1196
  console.log(text);
1036
1197
  });
1037
1198
  context.on("kubb:files:processing:end", () => {
1038
- if (logLevel$7 <= logLevel.silent) return;
1199
+ if (logLevel$5 <= logLevel.silent) return;
1039
1200
  const text = getMessage("Files written successfully");
1040
1201
  console.log(text);
1041
1202
  if (state.currentConfigs.length === 1) closeGroup("File Generation");
1042
1203
  showProgressStep();
1043
1204
  });
1044
1205
  context.on("kubb:files:processing:update", ({ files }) => {
1045
- if (logLevel$7 <= logLevel.silent) return;
1206
+ if (logLevel$5 <= logLevel.silent) return;
1046
1207
  state.processedFiles += files.length;
1047
1208
  });
1048
1209
  context.on("kubb:generation:end", ({ config }) => {
1049
1210
  const text = getMessage(config.name ? `${styleText("blue", "✓")} Generation completed for ${styleText("dim", config.name)}` : `${styleText("blue", "✓")} Generation completed`);
1050
1211
  console.log(text);
1212
+ if (state.currentConfigs.length > 1) closeGroup(config.name ? `Generation for ${styleText("bold", config.name)}` : "Generation");
1051
1213
  });
1052
1214
  onGroupStart("kubb:format:start", "Format started", "Formatting");
1053
1215
  onGroupEnd("kubb:format:end", "Format completed", "Formatting");
@@ -1056,7 +1218,7 @@ const githubActionsLogger = defineLogger({
1056
1218
  onGroupStart("kubb:hooks:start", "Hooks started", "Hooks");
1057
1219
  onGroupEnd("kubb:hooks:end", "Hooks completed", "Hooks");
1058
1220
  context.on("kubb:hook:start", ({ id, command, args }) => {
1059
- if (logLevel$7 <= logLevel.silent) return;
1221
+ if (logLevel$5 <= logLevel.silent) return;
1060
1222
  if (id) hookTimer.start(id);
1061
1223
  const commandWithArgs = formatCommandWithArgs(command, args);
1062
1224
  const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} started`);
@@ -1064,7 +1226,7 @@ const githubActionsLogger = defineLogger({
1064
1226
  console.log(text);
1065
1227
  });
1066
1228
  context.on("kubb:hook:end", ({ id, command, args, success, error }) => {
1067
- if (logLevel$7 <= logLevel.silent) return;
1229
+ if (logLevel$5 <= logLevel.silent) return;
1068
1230
  const ms = id ? hookTimer.end(id) : void 0;
1069
1231
  const durationStr = ms !== void 0 ? ` in ${formatMsWithColor(ms)}` : "";
1070
1232
  const commandWithArgs = formatCommandWithArgs(command, args);
@@ -1075,20 +1237,12 @@ const githubActionsLogger = defineLogger({
1075
1237
  }
1076
1238
  if (state.currentConfigs.length === 1) closeGroup(`Hook ${commandWithArgs}`);
1077
1239
  });
1078
- context.on("kubb:generation:summary", ({ config, status, hrStart, failedPlugins }) => {
1079
- const pluginsCount = config.plugins?.length ?? 0;
1080
- const successCount = pluginsCount - failedPlugins.size;
1081
- const duration = formatHrtime(hrStart);
1082
- if (state.currentConfigs.length > 1) console.log(" ");
1083
- console.log(status === "success" ? `Kubb Summary: ${styleText("blue", "✓")} ${`${successCount} successful`}, ${pluginsCount} total, ${styleText("green", duration)}` : `Kubb Summary: ${styleText("blue", "✓")} ${`${successCount} successful`}, ✗ ${`${failedPlugins.size} failed`}, ${pluginsCount} total, ${styleText("green", duration)}`);
1084
- if (state.currentConfigs.length > 1) closeGroup(config.name ? `Generation for ${styleText("bold", config.name)}` : "Generation");
1085
- });
1086
1240
  context.on("kubb:lifecycle:end", () => {
1087
1241
  reset();
1088
1242
  });
1089
1243
  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
1244
+ onStdout: logLevel$5 > logLevel.silent ? (s) => console.log(s) : void 0,
1245
+ onStderr: logLevel$5 > logLevel.silent ? (s) => console.error(`::error::${s}`) : void 0
1092
1246
  });
1093
1247
  }
1094
1248
  });
@@ -1100,19 +1254,19 @@ const githubActionsLogger = defineLogger({
1100
1254
  const plainLogger = defineLogger({
1101
1255
  name: "plain",
1102
1256
  install(context, options) {
1103
- const logLevel$6 = options?.logLevel ?? logLevel.info;
1257
+ const logLevel$4 = options?.logLevel ?? logLevel.info;
1104
1258
  const hookTimer = createHookTimer();
1105
1259
  function getMessage(message) {
1106
- return formatMessage(message, logLevel$6);
1260
+ return formatMessage(message, logLevel$4);
1107
1261
  }
1108
1262
  function onStep(event, message) {
1109
1263
  context.on(event, () => {
1110
- if (logLevel$6 <= logLevel.silent) return;
1264
+ if (logLevel$4 <= logLevel.silent) return;
1111
1265
  console.log(getMessage(message));
1112
1266
  });
1113
1267
  }
1114
1268
  context.on("kubb:info", ({ message, info }) => {
1115
- if (logLevel$6 <= logLevel.silent) return;
1269
+ if (logLevel$4 <= logLevel.silent) return;
1116
1270
  const text = getMessage([
1117
1271
  "ℹ",
1118
1272
  message,
@@ -1121,20 +1275,20 @@ const plainLogger = defineLogger({
1121
1275
  console.log(text);
1122
1276
  });
1123
1277
  context.on("kubb:success", ({ message, info = "" }) => {
1124
- if (logLevel$6 <= logLevel.silent) return;
1278
+ if (logLevel$4 <= logLevel.silent) return;
1125
1279
  const text = getMessage([
1126
1280
  "✓",
1127
1281
  message,
1128
- logLevel$6 >= logLevel.info ? info : void 0
1282
+ logLevel$4 >= logLevel.info ? info : void 0
1129
1283
  ].filter(Boolean).join(" "));
1130
1284
  console.log(text);
1131
1285
  });
1132
1286
  context.on("kubb:warn", ({ message, info }) => {
1133
- if (logLevel$6 < logLevel.warn) return;
1287
+ if (logLevel$4 < logLevel.warn) return;
1134
1288
  const text = getMessage([
1135
1289
  "⚠",
1136
1290
  message,
1137
- logLevel$6 >= logLevel.info ? info : void 0
1291
+ logLevel$4 >= logLevel.info ? info : void 0
1138
1292
  ].filter(Boolean).join(" "));
1139
1293
  console.log(text);
1140
1294
  });
@@ -1142,7 +1296,7 @@ const plainLogger = defineLogger({
1142
1296
  const caused = toCause(error);
1143
1297
  const text = getMessage(["✗", error.message].join(" "));
1144
1298
  console.log(text);
1145
- if (logLevel$6 >= logLevel.debug && error.stack) {
1299
+ if (logLevel$4 >= logLevel.verbose && error.stack) {
1146
1300
  const frames = error.stack.split("\n").slice(1, 4);
1147
1301
  for (const frame of frames) console.log(getMessage(frame.trim()));
1148
1302
  if (caused?.stack) {
@@ -1152,13 +1306,13 @@ const plainLogger = defineLogger({
1152
1306
  }
1153
1307
  }
1154
1308
  });
1309
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
1310
+ if (logLevel$4 <= logLevel.silent && diagnostic.severity !== "error") return;
1311
+ console.log(getMessage(formatDiagnostic(diagnostic).join("\n")));
1312
+ });
1155
1313
  context.on("kubb:lifecycle:start", ({ version }) => {
1156
1314
  console.log(`Kubb CLI v${version}`);
1157
1315
  });
1158
- context.on("kubb:version:new", ({ currentVersion, latestVersion }) => {
1159
- if (logLevel$6 <= logLevel.silent) return;
1160
- console.log(getMessage(`Update available: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`));
1161
- });
1162
1316
  onStep("kubb:config:start", "Configuration started");
1163
1317
  onStep("kubb:config:end", "Configuration completed");
1164
1318
  context.on("kubb:generation:start", () => {
@@ -1166,27 +1320,27 @@ const plainLogger = defineLogger({
1166
1320
  console.log(text);
1167
1321
  });
1168
1322
  context.on("kubb:plugin:start", ({ plugin }) => {
1169
- if (logLevel$6 <= logLevel.silent) return;
1323
+ if (logLevel$4 <= logLevel.silent) return;
1170
1324
  const text = getMessage(`Generating ${plugin.name}`);
1171
1325
  console.log(text);
1172
1326
  });
1173
1327
  context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
1174
- if (logLevel$6 <= logLevel.silent) return;
1328
+ if (logLevel$4 <= logLevel.silent) return;
1175
1329
  const durationStr = formatMs(duration);
1176
1330
  const text = getMessage(success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`);
1177
1331
  console.log(text);
1178
1332
  });
1179
1333
  context.on("kubb:files:processing:start", ({ files }) => {
1180
- if (logLevel$6 <= logLevel.silent) return;
1334
+ if (logLevel$4 <= logLevel.silent) return;
1181
1335
  const text = getMessage(`Writing ${files.length} files`);
1182
1336
  console.log(text);
1183
1337
  });
1184
1338
  context.on("kubb:files:processing:update", ({ files }) => {
1185
- if (logLevel$6 <= logLevel.silent) return;
1339
+ if (logLevel$4 <= logLevel.silent) return;
1186
1340
  for (const { file, config } of files) console.log(getMessage(`Writing ${relative(config.root, file.path)}`));
1187
1341
  });
1188
1342
  context.on("kubb:files:processing:end", () => {
1189
- if (logLevel$6 <= logLevel.silent) return;
1343
+ if (logLevel$4 <= logLevel.silent) return;
1190
1344
  const text = getMessage("Files written successfully");
1191
1345
  console.log(text);
1192
1346
  });
@@ -1201,13 +1355,13 @@ const plainLogger = defineLogger({
1201
1355
  onStep("kubb:hooks:start", "Hooks started");
1202
1356
  onStep("kubb:hooks:end", "Hooks completed");
1203
1357
  context.on("kubb:hook:start", ({ id, command, args }) => {
1204
- if (logLevel$6 <= logLevel.silent) return;
1358
+ if (logLevel$4 <= logLevel.silent) return;
1205
1359
  if (id) hookTimer.start(id);
1206
1360
  const commandWithArgs = formatCommandWithArgs(command, args);
1207
1361
  console.log(getMessage(`Hook ${commandWithArgs} started`));
1208
1362
  });
1209
1363
  context.on("kubb:hook:end", ({ id, command, args, success, error }) => {
1210
- if (logLevel$6 <= logLevel.silent) return;
1364
+ if (logLevel$4 <= logLevel.silent) return;
1211
1365
  const ms = id ? hookTimer.end(id) : void 0;
1212
1366
  const durationStr = ms !== void 0 ? ` in ${formatMs(ms)}` : "";
1213
1367
  const commandWithArgs = formatCommandWithArgs(command, args);
@@ -1217,22 +1371,9 @@ const plainLogger = defineLogger({
1217
1371
  console.log(getMessage(`✗ Hook ${commandWithArgs} failed${durationStr}${reason}`));
1218
1372
  }
1219
1373
  });
1220
- context.on("kubb:generation:summary", ({ config, pluginTimings, status, hrStart, failedPlugins, filesCreated }) => {
1221
- const summary = getSummary({
1222
- failedPlugins,
1223
- filesCreated,
1224
- config,
1225
- status,
1226
- hrStart,
1227
- pluginTimings: logLevel$6 >= logLevel.verbose ? pluginTimings : void 0
1228
- });
1229
- console.log(SUMMARY_SEPARATOR);
1230
- console.log(summary.join("\n"));
1231
- console.log(SUMMARY_SEPARATOR);
1232
- });
1233
1374
  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
1375
+ onStdout: logLevel$4 > logLevel.silent ? (s) => console.log(s) : void 0,
1376
+ onStderr: logLevel$4 > logLevel.silent ? (s) => console.error(s) : void 0
1236
1377
  });
1237
1378
  }
1238
1379
  });
@@ -1242,8 +1383,8 @@ const plainLogger = defineLogger({
1242
1383
  * Optionally prefix a message with a [HH:MM:SS] timestamp when logLevel >= verbose.
1243
1384
  * Shared across all logger adapters to avoid duplication.
1244
1385
  */
1245
- function formatMessage(message, logLevel$4) {
1246
- if (logLevel$4 >= logLevel.verbose) return `${styleText("dim", `[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
1386
+ function formatMessage(message, logLevel$3) {
1387
+ if (logLevel$3 >= logLevel.verbose) return `${styleText("dim", `[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
1247
1388
  hour12: false,
1248
1389
  hour: "2-digit",
1249
1390
  minute: "2-digit",
@@ -1335,53 +1476,45 @@ const logMapper = {
1335
1476
  plain: plainLogger,
1336
1477
  "github-actions": githubActionsLogger
1337
1478
  };
1338
- async function setupLogger(context, { logLevel: logLevel$5 }) {
1339
- const type = detectLogger();
1340
- const logger = logMapper[type];
1341
- if (!logger) throw new Error(`Unknown adapter type: ${type}`);
1342
- const makeSink = await logger.install(context, { logLevel: logLevel$5 });
1343
- if (logLevel$5 >= logLevel.debug) await fileSystemLogger.install(context, { logLevel: logLevel$5 });
1344
- return typeof makeSink === "function" ? makeSink : null;
1479
+ /**
1480
+ * Bridges a {@link Reporter} onto the run's event emitter: calls `report` with each config's
1481
+ * {@link GenerationResult} on `kubb:generation:end`. The reporter never touches the emitter.
1482
+ */
1483
+ function installReporter(context, reporter, ctx) {
1484
+ context.on("kubb:generation:end", async ({ config, diagnostics = [], filesCreated = 0, status = "success", hrStart = process$1.hrtime() }) => {
1485
+ await reporter.report({
1486
+ config,
1487
+ diagnostics,
1488
+ filesCreated,
1489
+ status,
1490
+ hrStart
1491
+ }, ctx);
1492
+ });
1345
1493
  }
1346
1494
  /**
1347
- * Builds the generation summary lines rendered in the end-of-run box.
1348
- * Returns an array of styled strings, one per summary row.
1495
+ * Installs the live logger (the TUI view) and the selected reporters (the output), returning the
1496
+ * terminal logger's hook sink when one was installed. Loggers and reporters are independent: the
1497
+ * `cli` selection activates the env logger plus the {@link cliReporter} summary.
1498
+ *
1499
+ * The `json` reporter owns stdout, so the terminal logger and `cli` summary are suppressed whenever
1500
+ * `json` is selected, even if `cli` is also listed.
1349
1501
  */
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}`);
1381
- });
1502
+ async function setupReporters(context, { logLevel, reporters }) {
1503
+ const unique = new Set(reporters.length ? reporters : ["cli"]);
1504
+ const hasJson = unique.has("json");
1505
+ const ctx = { logLevel };
1506
+ let makeSink = null;
1507
+ if (unique.has("cli") && !hasJson) {
1508
+ const type = detectLogger();
1509
+ const logger = logMapper[type];
1510
+ if (!logger) throw new Error(`Unknown adapter type: ${type}`);
1511
+ const sink = await logger.install(context, { logLevel });
1512
+ makeSink = typeof sink === "function" ? sink : null;
1513
+ installReporter(context, cliReporter, ctx);
1382
1514
  }
1383
- summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`);
1384
- return summaryLines;
1515
+ if (hasJson) installReporter(context, jsonReporter, ctx);
1516
+ if (unique.has("file")) installReporter(context, fileReporter, ctx);
1517
+ return makeSink;
1385
1518
  }
1386
1519
  //#endregion
1387
1520
  //#region src/runners/generate/utils.ts
@@ -1480,7 +1613,7 @@ async function executeHooks({ configHooks, hooks, makeSink }) {
1480
1613
  command: cmd,
1481
1614
  args,
1482
1615
  commandWithArgs,
1483
- context: hooks,
1616
+ hooks,
1484
1617
  stream,
1485
1618
  sink: {
1486
1619
  onLine,
@@ -1490,8 +1623,8 @@ async function executeHooks({ configHooks, hooks, makeSink }) {
1490
1623
  });
1491
1624
  }
1492
1625
  }
1493
- async function runHook({ id, command, args, commandWithArgs, context, stream = false, sink }) {
1494
- const emitEnd = (success, error) => context.emit("kubb:hook:end", {
1626
+ async function runHook({ id, command, args, commandWithArgs, hooks, stream = false, sink }) {
1627
+ const emitEnd = (success, error) => hooks.emit("kubb:hook:end", {
1495
1628
  command,
1496
1629
  args,
1497
1630
  id,
@@ -1504,31 +1637,23 @@ async function runHook({ id, command, args, commandWithArgs, context, stream = f
1504
1637
  throwOnError: true
1505
1638
  });
1506
1639
  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` });
1640
+ await proc;
1641
+ await hooks.emit("kubb:success", { message: `${styleText("dim", commandWithArgs)} successfully executed` });
1513
1642
  await emitEnd(true, null);
1514
1643
  } catch (err) {
1515
1644
  if (!(err instanceof NonZeroExitError)) {
1516
1645
  const error = toError(err);
1517
1646
  await emitEnd(false, error);
1518
- await context.emit("kubb:error", { error });
1647
+ await hooks.emit("kubb:error", { error });
1519
1648
  return;
1520
1649
  }
1521
1650
  const stderr = err.output?.stderr ?? "";
1522
1651
  const stdout = err.output?.stdout ?? "";
1523
- await context.emit("kubb:debug", {
1524
- date: /* @__PURE__ */ new Date(),
1525
- logs: [stdout, stderr].filter(Boolean)
1526
- });
1527
1652
  if (stderr) sink?.onStderr?.(stderr);
1528
1653
  if (stdout) sink?.onStdout?.(stdout);
1529
1654
  const error = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
1530
1655
  await emitEnd(false, error);
1531
- await context.emit("kubb:error", { error });
1656
+ await hooks.emit("kubb:error", { error });
1532
1657
  }
1533
1658
  }
1534
1659
  /**
@@ -1614,7 +1739,7 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1614
1739
  command: toolConfig.command,
1615
1740
  args: hookArgs,
1616
1741
  commandWithArgs,
1617
- context: hooks,
1742
+ hooks,
1618
1743
  stream,
1619
1744
  sink: {
1620
1745
  onLine,
@@ -1624,16 +1749,14 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1624
1749
  }).catch(() => {});
1625
1750
  await hookEndPromise;
1626
1751
  } catch (caughtError) {
1627
- const err = toError(caughtError);
1628
- await hooks.emit("kubb:error", { error: err });
1629
- toolError = err;
1752
+ toolError = toError(caughtError);
1630
1753
  }
1631
1754
  }
1632
1755
  await onEnd();
1633
1756
  if (toolError) throw toolError;
1634
1757
  }
1635
1758
  async function generate(options) {
1636
- const { input, hooks, logLevel: logLevel$2, makeSink } = options;
1759
+ const { input, hooks, logLevel, makeSink } = options;
1637
1760
  const hrStart = process$1.hrtime();
1638
1761
  const inputPath = input ?? (options.config.input && "path" in options.config.input ? options.config.input.path : void 0);
1639
1762
  const config = {
@@ -1655,7 +1778,7 @@ async function generate(options) {
1655
1778
  message: config.name ? `Build generation ${styleText("bold", config.name)}` : "Build generation",
1656
1779
  info: inputPath
1657
1780
  });
1658
- const { files, failedPlugins, pluginTimings, error, driver } = await kubb.safeBuild();
1781
+ const { files, diagnostics, driver } = await kubb.safeBuild();
1659
1782
  await hooks.emit("kubb:info", { message: "Load summary" });
1660
1783
  const telemetryPlugins = Array.from(driver.plugins.values(), (p) => ({
1661
1784
  name: p.name,
@@ -1669,34 +1792,28 @@ async function generate(options) {
1669
1792
  filesCreated: files.length,
1670
1793
  status
1671
1794
  }));
1672
- if (failedPlugins.size > 0 || error) {
1673
- const allErrors = [error, ...Array.from(failedPlugins, (it) => it.error)].filter(Boolean);
1674
- for (const err of allErrors) await hooks.emit("kubb:error", { error: err });
1795
+ for (const diagnostic of diagnostics) {
1796
+ if (!isProblemDiagnostic(diagnostic)) continue;
1797
+ const unknown = narrowDiagnostic(diagnostic, diagnosticCode.unknown);
1798
+ if (unknown) await hooks.emit("kubb:error", { error: unknown.cause ?? new Error(unknown.message) });
1799
+ else await Diagnostics.emit(hooks, diagnostic);
1800
+ }
1801
+ if (Diagnostics.hasError(diagnostics)) {
1675
1802
  await hooks.emit("kubb:generation:end", {
1676
1803
  config,
1677
- storage: kubb.storage
1678
- });
1679
- await hooks.emit("kubb:generation:summary", {
1680
- config,
1681
- failedPlugins,
1804
+ storage: kubb.storage,
1805
+ diagnostics,
1682
1806
  filesCreated: files.length,
1683
1807
  status: "failed",
1684
- hrStart,
1685
- pluginTimings: logLevel$2 >= logLevel.verbose ? pluginTimings : void 0
1808
+ hrStart
1686
1809
  });
1687
1810
  await reportTelemetry("failed");
1688
1811
  return false;
1689
1812
  }
1690
- await hooks.emit("kubb:success", {
1691
- message: "Generation succeeded",
1692
- info: inputPath
1693
- });
1694
- await hooks.emit("kubb:generation:end", {
1695
- config,
1696
- storage: kubb.storage
1697
- });
1698
1813
  const outputPath = path.resolve(config.root, config.output.path);
1814
+ const outputDiagnostics = [];
1699
1815
  const toolPasses = [config.output.format && {
1816
+ code: diagnosticCode.formatFailed,
1700
1817
  toolValue: config.output.format,
1701
1818
  detect: detectFormatter,
1702
1819
  toolMap: formatters,
@@ -1706,6 +1823,7 @@ async function generate(options) {
1706
1823
  onStart: () => hooks.emit("kubb:format:start"),
1707
1824
  onEnd: () => hooks.emit("kubb:format:end")
1708
1825
  }, config.output.lint && {
1826
+ code: diagnosticCode.lintFailed,
1709
1827
  toolValue: config.output.lint,
1710
1828
  detect: detectLinter,
1711
1829
  toolMap: linters,
@@ -1715,64 +1833,121 @@ async function generate(options) {
1715
1833
  onStart: () => hooks.emit("kubb:lint:start"),
1716
1834
  onEnd: () => hooks.emit("kubb:lint:end")
1717
1835
  }].filter(Boolean);
1718
- for (const pass of toolPasses) await runToolPass({
1719
- ...pass,
1720
- configName: config.name,
1721
- outputPath,
1722
- logLevel: logLevel$2,
1723
- hooks,
1724
- makeSink
1725
- });
1726
- if (config.hooks) {
1727
- await hooks.emit("kubb:hooks:start");
1728
- await executeHooks({
1729
- configHooks: config.hooks,
1836
+ for (const { code, ...pass } of toolPasses) try {
1837
+ await runToolPass({
1838
+ ...pass,
1839
+ configName: config.name,
1840
+ outputPath,
1841
+ logLevel,
1730
1842
  hooks,
1731
1843
  makeSink
1732
1844
  });
1845
+ } catch (caughtError) {
1846
+ const diagnostic = outputDiagnostic(code, pass.toolLabel, caughtError);
1847
+ outputDiagnostics.push(diagnostic);
1848
+ await Diagnostics.emit(hooks, diagnostic);
1849
+ }
1850
+ if (config.hooks) {
1851
+ await hooks.emit("kubb:hooks:start");
1852
+ const hookFailures = [];
1853
+ const onHookEnd = (ctx) => {
1854
+ if (!ctx.success) hookFailures.push(ctx.error ?? /* @__PURE__ */ new Error("Post-generate hook failed"));
1855
+ };
1856
+ hooks.on("kubb:hook:end", onHookEnd);
1857
+ try {
1858
+ await executeHooks({
1859
+ configHooks: config.hooks,
1860
+ hooks,
1861
+ makeSink
1862
+ });
1863
+ } finally {
1864
+ hooks.off("kubb:hook:end", onHookEnd);
1865
+ }
1866
+ for (const error of hookFailures) {
1867
+ const diagnostic = outputDiagnostic(diagnosticCode.hookFailed, "Post-generate hook", error);
1868
+ outputDiagnostics.push(diagnostic);
1869
+ await Diagnostics.emit(hooks, diagnostic);
1870
+ }
1733
1871
  await hooks.emit("kubb:hooks:end");
1734
1872
  }
1735
- await hooks.emit("kubb:generation:summary", {
1873
+ const finalDiagnostics = [...diagnostics, ...outputDiagnostics];
1874
+ const failed = Diagnostics.hasError(outputDiagnostics);
1875
+ if (!failed) await hooks.emit("kubb:success", {
1876
+ message: "Generation succeeded",
1877
+ info: inputPath
1878
+ });
1879
+ await hooks.emit("kubb:generation:end", {
1736
1880
  config,
1737
- failedPlugins,
1881
+ storage: kubb.storage,
1882
+ diagnostics: finalDiagnostics,
1738
1883
  filesCreated: files.length,
1739
- status: "success",
1740
- hrStart,
1741
- pluginTimings
1884
+ status: failed ? "failed" : "success",
1885
+ hrStart
1742
1886
  });
1743
- await reportTelemetry("success");
1744
- return true;
1887
+ await reportTelemetry(failed ? "failed" : "success");
1888
+ return !failed;
1889
+ }
1890
+ /**
1891
+ * Builds a coded diagnostic for an output-phase failure (formatter, linter, or `done` hook).
1892
+ */
1893
+ function outputDiagnostic(code, label, caughtError) {
1894
+ const error = toError(caughtError);
1895
+ return {
1896
+ code,
1897
+ severity: "error",
1898
+ message: `${label} failed: ${error.message}`,
1899
+ help: "Check that the tool is installed and that the command and its config are correct.",
1900
+ location: { kind: "config" },
1901
+ cause: error
1902
+ };
1745
1903
  }
1746
1904
  async function checkForUpdate(hooks) {
1747
1905
  await executeIfOnline(async () => {
1748
1906
  try {
1749
1907
  const data = await (await fetch(KUBB_NPM_PACKAGE_URL)).json();
1750
- if (data.version && version < data.version) await hooks.emit("kubb:version:new", {
1908
+ if (data.version && version < data.version) await Diagnostics.emit(hooks, Diagnostics.update({
1751
1909
  currentVersion: version,
1752
1910
  latestVersion: data.version
1753
- });
1911
+ }));
1754
1912
  } catch {}
1755
1913
  });
1756
1914
  }
1757
1915
  /**
1758
1916
  * 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.
1917
+ * Loads configs, sets up the selected reporters (CLI `--reporter` overrides `config.reporters`),
1918
+ * checks for a newer version, and calls `generate` for each config entry.
1760
1919
  */
1761
- async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1762
- const logLevel$3 = logLevel[logLevelKey] ?? logLevel.info;
1920
+ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters: cliReporters }) {
1921
+ const logLevel$2 = logLevel[logLevelKey] ?? logLevel.info;
1763
1922
  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);
1923
+ let configs;
1924
+ let resolvedConfigPath;
1767
1925
  try {
1768
- await hooks.emit("kubb:config:start");
1769
- const { configs, configPath: resolvedConfigPath } = await getConfigs({
1926
+ const loaded = await getConfigs({
1770
1927
  configPath,
1771
1928
  input,
1772
1929
  watch,
1773
1930
  logLevel: logLevelKey
1774
1931
  });
1932
+ configs = loaded.configs;
1933
+ resolvedConfigPath = loaded.configPath;
1934
+ } catch (error) {
1935
+ await setupReporters(hooks, {
1936
+ logLevel: logLevel$2,
1937
+ reporters: ["cli"]
1938
+ });
1939
+ await hooks.emit("kubb:error", { error: toError(error) });
1940
+ process$1.exit(1);
1941
+ }
1942
+ const makeSink = await setupReporters(hooks, {
1943
+ logLevel: logLevel$2,
1944
+ reporters: cliReporters?.length ? cliReporters : configs[0]?.reporters ?? ["cli"]
1945
+ });
1946
+ await hooks.emit("kubb:lifecycle:start", { version });
1947
+ await checkForUpdate(hooks);
1948
+ try {
1775
1949
  const relativeConfigPath = path.relative(process$1.cwd(), resolvedConfigPath);
1950
+ await hooks.emit("kubb:config:start");
1776
1951
  await hooks.emit("kubb:info", {
1777
1952
  message: "Config loaded",
1778
1953
  info: relativeConfigPath
@@ -1787,7 +1962,7 @@ async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1787
1962
  await generate({
1788
1963
  input,
1789
1964
  config,
1790
- logLevel: logLevel$3,
1965
+ logLevel: logLevel$2,
1791
1966
  hooks,
1792
1967
  makeSink
1793
1968
  });
@@ -1800,7 +1975,7 @@ async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1800
1975
  if (!await generate({
1801
1976
  input,
1802
1977
  config,
1803
- logLevel: logLevel$3,
1978
+ logLevel: logLevel$2,
1804
1979
  hooks,
1805
1980
  makeSink
1806
1981
  })) anyFailed = true;
@@ -1818,4 +1993,4 @@ async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1818
1993
  //#endregion
1819
1994
  export { run };
1820
1995
 
1821
- //# sourceMappingURL=run-zuPIKTwa.js.map
1996
+ //# sourceMappingURL=run-Ca2h07rN.js.map