@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,9 +1,9 @@
1
1
  const require_chunk = require("./chunk-Bx3C2hgW.cjs");
2
2
  const require_errors = require("./errors-DykI11xo.cjs");
3
- const require_telemetry = require("./telemetry-DRhd3joO.cjs");
3
+ const require_telemetry = require("./telemetry-B80oJfxR.cjs");
4
4
  const require_shell = require("./shell-Lh-vLWwH.cjs");
5
- const require_package = require("./package-B121qOXj.cjs");
6
- const require_constants = require("./constants-FhPsMOdo.cjs");
5
+ const require_package = require("./package-DQFf9DB2.cjs");
6
+ const require_constants = require("./constants-CAKUpLcQ.cjs");
7
7
  let node_util = require("node:util");
8
8
  let node_events = require("node:events");
9
9
  let node_crypto = require("node:crypto");
@@ -118,6 +118,24 @@ var AsyncEventEmitter = class {
118
118
  return this.#emitter.listenerCount(eventName);
119
119
  }
120
120
  /**
121
+ * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
122
+ * Set this above the expected listener count when many listeners attach by design.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * emitter.setMaxListeners(40)
127
+ * ```
128
+ */
129
+ setMaxListeners(max) {
130
+ this.#emitter.setMaxListeners(max);
131
+ }
132
+ /**
133
+ * Returns the current per-event listener ceiling.
134
+ */
135
+ getMaxListeners() {
136
+ return this.#emitter.getMaxListeners();
137
+ }
138
+ /**
121
139
  * Removes all listeners from every event channel.
122
140
  *
123
141
  * @example
@@ -491,6 +509,262 @@ async function detectLinter() {
491
509
  return null;
492
510
  }
493
511
  //#endregion
512
+ //#region src/reporters/report.ts
513
+ /**
514
+ * Builds the normalized {@link Report} for one config from its {@link GenerationResult}. Splits the
515
+ * diagnostics into problems and per-plugin timings (slowest first) and derives the plugin and issue
516
+ * counts, so every reporter renders the same data.
517
+ */
518
+ function buildReport(result) {
519
+ const { config, diagnostics, filesCreated, status, hrStart } = result;
520
+ const failed = _kubb_core.Diagnostics.failedPlugins(diagnostics);
521
+ const total = config.plugins?.length ?? 0;
522
+ const counts = _kubb_core.Diagnostics.count(diagnostics);
523
+ const problems = diagnostics.filter(_kubb_core.isProblemDiagnostic);
524
+ const timings = diagnostics.filter(_kubb_core.isPerformanceDiagnostic).sort((a, b) => b.duration - a.duration).map((diagnostic) => ({
525
+ plugin: diagnostic.plugin,
526
+ durationMs: diagnostic.duration
527
+ }));
528
+ return {
529
+ name: config.name ?? "",
530
+ status,
531
+ plugins: {
532
+ passed: total - failed.length,
533
+ failed,
534
+ total
535
+ },
536
+ counts,
537
+ filesCreated,
538
+ durationMs: getElapsedMs(hrStart),
539
+ output: (0, node_path.resolve)(config.root, config.output.path),
540
+ timings,
541
+ diagnostics: problems.map((diagnostic) => _kubb_core.Diagnostics.serialize(diagnostic))
542
+ };
543
+ }
544
+ //#endregion
545
+ //#region src/reporters/cliReporter.ts
546
+ /**
547
+ * Builds the vitest/jest-style summary for one {@link Report}: right-aligned dim labels with
548
+ * `N passed (total)` counts, and a per-plugin `Timings` section when `showTimings`.
549
+ */
550
+ function buildSummaryLines(report, { showTimings }) {
551
+ const { status, plugins, counts, filesCreated, durationMs, output, timings } = report;
552
+ const rows = [];
553
+ rows.push(["Plugins", status === "success" ? `${(0, node_util.styleText)("green", `${plugins.passed} passed`)} (${plugins.total})` : `${(0, node_util.styleText)("green", `${plugins.passed} passed`)} | ${(0, node_util.styleText)("red", `${plugins.failed.length} failed`)} (${plugins.total})`]);
554
+ if (status === "failed" && plugins.failed.length > 0) rows.push(["Failed", plugins.failed.map((name) => randomCliColor(name)).join(", ")]);
555
+ if (counts.errors > 0 || counts.warnings > 0) {
556
+ const issues = [counts.errors > 0 ? (0, node_util.styleText)("red", `${counts.errors} ${counts.errors === 1 ? "error" : "errors"}`) : void 0, counts.warnings > 0 ? (0, node_util.styleText)("yellow", `${counts.warnings} ${counts.warnings === 1 ? "warning" : "warnings"}`) : void 0].filter(Boolean).join(" | ");
557
+ rows.push(["Issues", issues]);
558
+ }
559
+ rows.push(["Files", `${(0, node_util.styleText)("green", String(filesCreated))} generated`]);
560
+ rows.push(["Duration", (0, node_util.styleText)("green", formatMs(durationMs))]);
561
+ rows.push(["Output", output]);
562
+ const labelWidth = Math.max(...rows.map(([label]) => label.length), timings.length > 0 ? 7 : 0);
563
+ const lines = rows.map(([label, value]) => `${(0, node_util.styleText)("dim", label.padStart(labelWidth))} ${value}`);
564
+ if (showTimings && timings.length > 0) {
565
+ const nameWidth = Math.max(0, ...timings.map((timing) => timing.plugin.length));
566
+ const indent = " ".repeat(labelWidth + 2);
567
+ lines.push((0, node_util.styleText)("dim", "Timings".padStart(labelWidth)));
568
+ for (const timing of timings) {
569
+ const timeStr = formatMs(timing.durationMs);
570
+ const barLength = Math.min(Math.ceil(timing.durationMs / 100), 10);
571
+ const bar = (0, node_util.styleText)("dim", "█".repeat(barLength));
572
+ lines.push(`${indent}${(0, node_util.styleText)("dim", "•")} ${timing.plugin.padEnd(nameWidth)} ${bar} ${timeStr}`);
573
+ }
574
+ }
575
+ return lines;
576
+ }
577
+ /**
578
+ * Renders the summary as plain `console.log` lines so it works in every CLI (no clack/TTY
579
+ * dependency): a blank line, the config name colored by status, then the summary rows.
580
+ */
581
+ function renderSummary(lines, { title, status }) {
582
+ console.log("");
583
+ if (title) console.log((0, node_util.styleText)(status === "failed" ? "red" : "green", title));
584
+ for (const line of lines) console.log(line);
585
+ }
586
+ /**
587
+ * The default `cli` reporter. Renders the {@link Report} for each config as it finishes, independent
588
+ * of the live logger view. Suppressed at `silent`; the `verbose` level adds the per-plugin timings.
589
+ */
590
+ const cliReporter = (0, _kubb_core.createReporter)({
591
+ name: "cli",
592
+ report(result, { logLevel }) {
593
+ if (logLevel <= _kubb_core.logLevel.silent) return;
594
+ const report = buildReport(result);
595
+ renderSummary(buildSummaryLines(report, { showTimings: logLevel >= _kubb_core.logLevel.verbose }), {
596
+ title: report.name,
597
+ status: report.status
598
+ });
599
+ }
600
+ });
601
+ //#endregion
602
+ //#region src/loggers/diagnostics.ts
603
+ /**
604
+ * Glyph and accent color per severity, matching the miette/oxlint convention
605
+ * (`×` error, `⚠` warning, `ℹ` advice).
606
+ */
607
+ const severityStyle = {
608
+ error: {
609
+ glyph: "×",
610
+ color: "red"
611
+ },
612
+ warning: {
613
+ glyph: "⚠",
614
+ color: "yellow"
615
+ },
616
+ info: {
617
+ glyph: "ℹ",
618
+ color: "blue"
619
+ }
620
+ };
621
+ /**
622
+ * The colored, bold severity glyph (`×`, `⚠`, `ℹ`) on its own. Pass it as clack's
623
+ * `symbol` so clack owns the gutter and adds the bar to the continuation lines,
624
+ * instead of baking the glyph into the message text.
625
+ */
626
+ function diagnosticSymbol(severity) {
627
+ const { glyph, color } = severityStyle[severity];
628
+ return (0, node_util.styleText)(color, (0, node_util.styleText)("bold", glyph));
629
+ }
630
+ /**
631
+ * The `plugin(CODE): message` headline, without the leading severity glyph.
632
+ */
633
+ function diagnosticHeadline(diagnostic) {
634
+ const { code, severity, message } = diagnostic;
635
+ const plugin = (0, _kubb_core.isProblemDiagnostic)(diagnostic) ? diagnostic.plugin : void 0;
636
+ const { color } = severityStyle[severity];
637
+ return `${(0, node_util.styleText)(color, (0, node_util.styleText)("bold", plugin ? `${plugin}(${code})` : code))}: ${message}`;
638
+ }
639
+ /**
640
+ * The detail lines below the headline: optional `at <pointer>`, `help:`, and
641
+ * `docs:`. OpenAPI has no line/column, so the location is the JSON pointer the
642
+ * adapter built. Each line keeps a two-space indent so it sits under the headline.
643
+ */
644
+ function diagnosticDetails(diagnostic) {
645
+ const { code } = diagnostic;
646
+ const problem = (0, _kubb_core.isProblemDiagnostic)(diagnostic) ? diagnostic : void 0;
647
+ const location = problem?.location;
648
+ const help = problem?.help;
649
+ const lines = [];
650
+ if (location && "pointer" in location) lines.push(` ${(0, node_util.styleText)("dim", "at")} ${(0, node_util.styleText)("cyan", location.pointer)}`);
651
+ if (help) lines.push(` ${(0, node_util.styleText)("cyan", "help:")} ${help}`);
652
+ if (code !== _kubb_core.diagnosticCode.unknown) lines.push(` ${(0, node_util.styleText)("dim", "docs:")} ${(0, node_util.styleText)("cyan", _kubb_core.Diagnostics.docsUrl(code))}`);
653
+ return lines;
654
+ }
655
+ /**
656
+ * Renders a {@link Diagnostic} in the oxlint style as a self-contained block: a
657
+ * `× plugin(CODE): message` header followed by the {@link diagnosticDetails}.
658
+ * Use this where clack's gutter is not available (plain, file output); clack
659
+ * loggers pass {@link diagnosticSymbol}, {@link diagnosticHeadline}, and
660
+ * {@link diagnosticDetails} to `clack.log.message` instead.
661
+ *
662
+ * @example
663
+ * ```ts
664
+ * 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' } })
665
+ * ```
666
+ */
667
+ function formatDiagnostic(diagnostic) {
668
+ return [`${diagnosticSymbol(diagnostic.severity)} ${diagnosticHeadline(diagnostic)}`, ...diagnosticDetails(diagnostic)];
669
+ }
670
+ //#endregion
671
+ //#region src/reporters/fileReporter.ts
672
+ /**
673
+ * Builds the `## Summary` section: the same counts the cli and json reporters expose, as a list of
674
+ * `label value` rows with the labels padded to a common width.
675
+ */
676
+ function buildSummarySection(report) {
677
+ const { status, plugins, counts, filesCreated, durationMs, output } = report;
678
+ const rows = [["Status", status], ["Plugins", status === "success" ? `${plugins.passed} passed (${plugins.total})` : `${plugins.passed} passed | ${plugins.failed.length} failed (${plugins.total})`]];
679
+ if (plugins.failed.length > 0) rows.push(["Failed", plugins.failed.join(", ")]);
680
+ rows.push(["Issues", `${counts.errors} errors | ${counts.warnings} warnings | ${counts.infos} infos`]);
681
+ rows.push(["Files", `${filesCreated} generated`]);
682
+ rows.push(["Duration", formatMs(durationMs)]);
683
+ rows.push(["Output", output]);
684
+ const labelWidth = Math.max(...rows.map(([label]) => label.length));
685
+ return [
686
+ "## Summary",
687
+ "",
688
+ ...rows.map(([label, value]) => ` ${label.padEnd(labelWidth)} ${value}`)
689
+ ];
690
+ }
691
+ /**
692
+ * Builds the `## Problems` section: each problem rendered in the miette block format, blocks
693
+ * separated by a blank line. Returns an empty array when there are no problems, so the caller
694
+ * can drop the heading.
695
+ */
696
+ function buildProblemSection(diagnostics) {
697
+ const problems = diagnostics.filter(_kubb_core.isProblemDiagnostic);
698
+ if (problems.length === 0) return [];
699
+ return [
700
+ "## Problems",
701
+ "",
702
+ problems.map((diagnostic) => formatDiagnostic(diagnostic).join("\n")).join("\n\n")
703
+ ];
704
+ }
705
+ /**
706
+ * Builds the `## Timings` section from a {@link Report}: one `plugin duration` row per record,
707
+ * slowest first with the plugin names left-aligned and the durations right-aligned. Returns an
708
+ * empty array when there are no timings.
709
+ */
710
+ function buildTimingSection(report) {
711
+ const { timings } = report;
712
+ if (timings.length === 0) return [];
713
+ const nameWidth = Math.max(...timings.map((timing) => timing.plugin.length));
714
+ const durations = timings.map((timing) => formatMs(timing.durationMs));
715
+ const durationWidth = Math.max(...durations.map((duration) => duration.length));
716
+ return [
717
+ "## Timings",
718
+ "",
719
+ ...timings.map((timing, index) => ` ${timing.plugin.padEnd(nameWidth)} ${durations[index].padStart(durationWidth)}`)
720
+ ];
721
+ }
722
+ /**
723
+ * The `file` reporter. Writes a config's {@link Report} to `.kubb/kubb-<name>-<timestamp>.log` as a
724
+ * plain-text document: a `# <name> — <timestamp>` header, a `## Summary` with the same counts the
725
+ * cli and json reporters expose, a `## Problems` section in the miette block format, and a
726
+ * `## Timings` section. Selected with `--reporter file` (or `reporters: ['file']`), replacing the
727
+ * old `--debug` flag.
728
+ *
729
+ * @note Unlike the streaming logger it replaced, it captures the collected diagnostics once a
730
+ * config finishes, not the live `kubb:info`/`kubb:plugin` event stream. Color is stripped so the
731
+ * file stays plain text even when the run is attached to a TTY.
732
+ */
733
+ const fileReporter = (0, _kubb_core.createReporter)({
734
+ name: "file",
735
+ async report(result) {
736
+ const { diagnostics, config } = result;
737
+ if (diagnostics.length === 0) return;
738
+ const report = buildReport(result);
739
+ const content = (0, node_util.stripVTControlCharacters)([config.name ? `# ${config.name} — ${(/* @__PURE__ */ new Date()).toISOString()}` : `# ${(/* @__PURE__ */ new Date()).toISOString()}`, ...[
740
+ buildSummarySection(report),
741
+ buildProblemSection(diagnostics),
742
+ buildTimingSection(report)
743
+ ].filter((section) => section.length > 0).map((section) => section.join("\n"))].join("\n\n"));
744
+ const baseName = `${[
745
+ "kubb",
746
+ config.name,
747
+ Date.now()
748
+ ].filter(Boolean).join("-")}.log`;
749
+ const pathName = (0, node_path.resolve)(node_process.default.cwd(), ".kubb", baseName);
750
+ await write(pathName, `${content}\n`);
751
+ console.error(`Debug log written to ${(0, node_path.relative)(node_process.default.cwd(), pathName)}`);
752
+ }
753
+ });
754
+ //#endregion
755
+ //#region src/reporters/jsonReporter.ts
756
+ /**
757
+ * The `json` reporter. Writes the {@link Report} for each config to stdout as JSON, for CI tooling.
758
+ * The terminal reporter is suppressed while this is active so stdout stays valid JSON.
759
+ */
760
+ const jsonReporter = (0, _kubb_core.createReporter)({
761
+ name: "json",
762
+ report(result) {
763
+ const report = buildReport(result);
764
+ node_process.default.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
765
+ }
766
+ });
767
+ //#endregion
494
768
  //#region src/loggers/clackLogger.ts
495
769
  /**
496
770
  * TTY logger with beautiful UI and progress indicators for local development.
@@ -503,20 +777,29 @@ const clackLogger = (0, _kubb_core.defineLogger)({
503
777
  ...createProgressCounters(),
504
778
  spinner: _clack_prompts.spinner(),
505
779
  isSpinning: false,
780
+ runningPlugins: /* @__PURE__ */ new Set(),
506
781
  activeProgress: /* @__PURE__ */ new Map(),
507
782
  activeHookLogs: /* @__PURE__ */ new Map()
508
783
  };
509
- function reset() {
510
- for (const [_key, active] of state.activeProgress) {
784
+ function stopActiveProgress() {
785
+ for (const [, active] of state.activeProgress) {
511
786
  if (active.interval) clearInterval(active.interval);
512
787
  active.progressBar?.stop();
513
788
  }
789
+ state.activeProgress.clear();
790
+ }
791
+ function reset() {
792
+ stopActiveProgress();
514
793
  resetProgressCounters(state);
515
794
  state.spinner = _clack_prompts.spinner();
516
795
  state.isSpinning = false;
517
- state.activeProgress.clear();
796
+ state.runningPlugins.clear();
518
797
  state.activeHookLogs.clear();
519
798
  }
799
+ function pluginProgressText() {
800
+ const running = [...state.runningPlugins].map((name) => (0, node_util.styleText)("bold", name));
801
+ return getMessage(running.length > 0 ? `Generating ${running.join(", ")}` : "Generating plugins");
802
+ }
520
803
  function showProgressStep() {
521
804
  if (logLevel <= _kubb_core.logLevel.silent) return;
522
805
  const line = buildProgressLine(state);
@@ -545,8 +828,8 @@ const clackLogger = (0, _kubb_core.defineLogger)({
545
828
  const text = getMessage([
546
829
  (0, node_util.styleText)("blue", "ℹ"),
547
830
  message,
548
- (0, node_util.styleText)("dim", info)
549
- ].join(" "));
831
+ info ? (0, node_util.styleText)("dim", info) : void 0
832
+ ].filter(Boolean).join(" "));
550
833
  if (state.isSpinning) {
551
834
  state.spinner.message(text);
552
835
  return;
@@ -583,7 +866,7 @@ const clackLogger = (0, _kubb_core.defineLogger)({
583
866
  return;
584
867
  }
585
868
  _clack_prompts.log.error(getMessage(text));
586
- if (logLevel >= _kubb_core.logLevel.debug && error.stack) {
869
+ if (logLevel >= _kubb_core.logLevel.verbose && error.stack) {
587
870
  const frames = error.stack.split("\n").slice(1, 4);
588
871
  for (const frame of frames) _clack_prompts.log.message(getMessage((0, node_util.styleText)("dim", frame.trim())));
589
872
  if (caused?.stack) {
@@ -593,10 +876,12 @@ const clackLogger = (0, _kubb_core.defineLogger)({
593
876
  }
594
877
  }
595
878
  });
596
- context.on("kubb:version:new", ({ currentVersion, latestVersion }) => {
597
- if (logLevel <= _kubb_core.logLevel.silent) return;
598
- try {
599
- _clack_prompts.box(`\`v${currentVersion}\` → \`v${latestVersion}\`
879
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
880
+ if (logLevel <= _kubb_core.logLevel.silent && diagnostic.severity !== "error") return;
881
+ stopSpinner();
882
+ stopActiveProgress();
883
+ if ((0, _kubb_core.isUpdateDiagnostic)(diagnostic)) {
884
+ _clack_prompts.box(`\`v${diagnostic.currentVersion}\` → \`v${diagnostic.latestVersion}\`
600
885
  Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
601
886
  width: "auto",
602
887
  formatBorder: (s) => (0, node_util.styleText)("yellow", s),
@@ -605,14 +890,13 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
605
890
  contentAlign: "center",
606
891
  titleAlign: "center"
607
892
  });
608
- } catch {
609
- console.log(`Update available for Kubb: v${currentVersion} → v${latestVersion}`);
610
- console.log("Run `npm install -g @kubb/cli` to update");
893
+ return;
611
894
  }
895
+ _clack_prompts.log.message([diagnosticHeadline(diagnostic), ...diagnosticDetails(diagnostic)], { symbol: diagnosticSymbol(diagnostic.severity) });
612
896
  });
613
897
  context.on("kubb:lifecycle:start", async ({ version }) => {
614
898
  console.log(`\n${getIntro({
615
- title: "The ultimate toolkit for working with APIs",
899
+ title: "The meta framework for code generation",
616
900
  description: "Ready to start",
617
901
  version,
618
902
  areEyesOpen: true
@@ -640,32 +924,33 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
640
924
  context.on("kubb:plugin:start", ({ plugin }) => {
641
925
  if (logLevel <= _kubb_core.logLevel.silent) return;
642
926
  stopSpinner();
927
+ state.runningPlugins.add(plugin.name);
928
+ const active = state.activeProgress.get("plugins");
929
+ if (active) {
930
+ active.progressBar.advance(0, pluginProgressText());
931
+ return;
932
+ }
643
933
  const progressBar = _clack_prompts.progress({
644
934
  style: "block",
645
- max: 100,
935
+ max: Math.max(state.totalPlugins, 1),
646
936
  size: 30
647
937
  });
648
- const text = getMessage(`Generating ${(0, node_util.styleText)("bold", plugin.name)}`);
649
- progressBar.start(text);
650
- const interval = setInterval(() => {
651
- progressBar.advance();
652
- }, 100);
653
- state.activeProgress.set(plugin.name, {
654
- progressBar,
655
- interval
656
- });
938
+ progressBar.start(pluginProgressText());
939
+ progressBar.advance(state.completedPlugins + state.failedPlugins, pluginProgressText());
940
+ state.activeProgress.set("plugins", { progressBar });
657
941
  });
658
- context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
942
+ context.on("kubb:plugin:end", ({ plugin, success }) => {
659
943
  stopSpinner();
660
- const active = state.activeProgress.get(plugin.name);
944
+ const active = state.activeProgress.get("plugins");
661
945
  if (!active || logLevel === _kubb_core.logLevel.silent) return;
662
- clearInterval(active.interval);
946
+ state.runningPlugins.delete(plugin.name);
663
947
  recordPluginResult(state, success);
664
- const durationStr = formatMsWithColor(duration);
665
- const text = getMessage(success ? `${(0, node_util.styleText)("bold", plugin.name)} completed in ${durationStr}` : `${(0, node_util.styleText)("bold", plugin.name)} failed in ${(0, node_util.styleText)("red", formatMs(duration))}`);
666
- active.progressBar.stop(text);
667
- state.activeProgress.delete(plugin.name);
668
- showProgressStep();
948
+ active.progressBar.advance(1, pluginProgressText());
949
+ if (state.runningPlugins.size === 0) {
950
+ active.progressBar.stop(getMessage("Plugins generated"));
951
+ state.activeProgress.delete("plugins");
952
+ showProgressStep();
953
+ }
669
954
  });
670
955
  context.on("kubb:files:processing:start", ({ files }) => {
671
956
  if (logLevel <= _kubb_core.logLevel.silent) return;
@@ -732,32 +1017,6 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
732
1017
  active.taskLog.error(getMessage(`${(0, node_util.styleText)("dim", commandWithArgs)} failed${reason}`), { showLog: true });
733
1018
  }
734
1019
  });
735
- context.on("kubb:generation:summary", ({ config, pluginTimings, failedPlugins, filesCreated, status, hrStart }) => {
736
- const summary = getSummary({
737
- failedPlugins,
738
- filesCreated,
739
- config,
740
- status,
741
- hrStart,
742
- pluginTimings: logLevel >= _kubb_core.logLevel.verbose ? pluginTimings : void 0
743
- });
744
- const title = config.name || "";
745
- summary.unshift("\n");
746
- summary.push("\n");
747
- const borderColor = status === "success" ? "green" : "red";
748
- try {
749
- _clack_prompts.box(summary.join("\n"), getMessage(title), {
750
- width: "auto",
751
- formatBorder: (s) => (0, node_util.styleText)(borderColor, s),
752
- rounded: true,
753
- withGuide: false,
754
- contentAlign: "left",
755
- titleAlign: "center"
756
- });
757
- } catch {
758
- console.log(summary.join("\n"));
759
- }
760
- });
761
1020
  context.on("kubb:lifecycle:end", () => {
762
1021
  reset();
763
1022
  });
@@ -779,114 +1038,6 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
779
1038
  }
780
1039
  });
781
1040
  //#endregion
782
- //#region src/loggers/fileSystemLogger.ts
783
- /**
784
- * FileSystem logger that captures debug events and writes them to `.kubb` directory files.
785
- *
786
- * @note Logs are written on `kubb:lifecycle:end` or process exit. Cached logs may be lost if the process crashes before either event.
787
- */
788
- const fileSystemLogger = (0, _kubb_core.defineLogger)({
789
- name: "filesystem",
790
- install(context) {
791
- const state = {
792
- cachedLogs: /* @__PURE__ */ new Set(),
793
- startDate: Date.now()
794
- };
795
- function reset() {
796
- state.cachedLogs = /* @__PURE__ */ new Set();
797
- state.startDate = Date.now();
798
- }
799
- async function writeLogs(name) {
800
- if (state.cachedLogs.size === 0) return [];
801
- const files = {};
802
- for (const log of state.cachedLogs) {
803
- const baseName = log.fileName || `${[
804
- "kubb",
805
- name,
806
- state.startDate
807
- ].filter(Boolean).join("-")}.log`;
808
- const pathName = (0, node_path.resolve)(node_process.default.cwd(), ".kubb", baseName);
809
- if (!files[pathName]) files[pathName] = [];
810
- if (log.logs.length > 0) {
811
- const prefix = `[${log.date.toLocaleString()}] `;
812
- const indent = " ".repeat(prefix.length);
813
- const [first, ...rest] = log.logs;
814
- files[pathName].push([prefix + first, ...rest.map((line) => indent + line)].join("\n"));
815
- }
816
- }
817
- for (const [fileName, logs] of Object.entries(files)) await write(fileName, logs.join("\n"));
818
- return Object.keys(files);
819
- }
820
- context.on("kubb:info", ({ message, info }) => {
821
- state.cachedLogs.add({
822
- date: /* @__PURE__ */ new Date(),
823
- logs: [`ℹ ${[message, info].filter(Boolean).join(" ")}`]
824
- });
825
- });
826
- context.on("kubb:success", ({ message, info }) => {
827
- state.cachedLogs.add({
828
- date: /* @__PURE__ */ new Date(),
829
- logs: [`✓ ${[message, info].filter(Boolean).join(" ")}`]
830
- });
831
- });
832
- context.on("kubb:warn", ({ message, info }) => {
833
- state.cachedLogs.add({
834
- date: /* @__PURE__ */ new Date(),
835
- logs: [`⚠ ${[message, info].filter(Boolean).join(" ")}`]
836
- });
837
- });
838
- context.on("kubb:error", ({ error }) => {
839
- state.cachedLogs.add({
840
- date: /* @__PURE__ */ new Date(),
841
- logs: [`✗ ${error.message}`, error.stack || "unknown stack"]
842
- });
843
- });
844
- context.on("kubb:debug", ({ date, fileName, logs }) => {
845
- state.cachedLogs.add({
846
- date,
847
- fileName,
848
- logs
849
- });
850
- });
851
- context.on("kubb:plugin:start", ({ plugin }) => {
852
- state.cachedLogs.add({
853
- date: /* @__PURE__ */ new Date(),
854
- logs: [`► Generating ${plugin.name}`]
855
- });
856
- });
857
- context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
858
- const durationStr = formatMs(duration);
859
- state.cachedLogs.add({
860
- date: /* @__PURE__ */ new Date(),
861
- logs: [success ? `✓ ${plugin.name} completed in ${durationStr}` : `✗ ${plugin.name} failed in ${durationStr}`]
862
- });
863
- });
864
- context.on("kubb:files:processing:start", ({ files }) => {
865
- state.cachedLogs.add({
866
- date: /* @__PURE__ */ new Date(),
867
- logs: [`► Writing ${files.length} files`, ...files.map((file) => ` ${file.path}`)]
868
- });
869
- });
870
- context.on("kubb:generation:end", async ({ config }) => {
871
- const writtenFilePaths = await writeLogs(config.name);
872
- if (writtenFilePaths.length > 0) {
873
- const files = writtenFilePaths.map((f) => (0, node_path.relative)(node_process.default.cwd(), f));
874
- await context.emit("kubb:info", {
875
- message: "Debug files written to:",
876
- info: files.join(", ")
877
- });
878
- }
879
- reset();
880
- });
881
- const exitHandler = () => {
882
- if (state.cachedLogs.size > 0) writeLogs().catch(() => {});
883
- };
884
- node_process.default.once("exit", exitHandler);
885
- node_process.default.once("SIGINT", exitHandler);
886
- node_process.default.once("SIGTERM", exitHandler);
887
- }
888
- });
889
- //#endregion
890
1041
  //#region src/loggers/githubActionsLogger.ts
891
1042
  /**
892
1043
  * GitHub Actions logger using group annotations for collapsible sections in CI.
@@ -973,10 +1124,9 @@ const githubActionsLogger = (0, _kubb_core.defineLogger)({
973
1124
  context.on("kubb:error", ({ error }) => {
974
1125
  const caused = require_errors.toCause(error);
975
1126
  closeAllGroups();
976
- if (logLevel <= _kubb_core.logLevel.silent) return;
977
1127
  const message = error.message || String(error);
978
1128
  console.error(`::error::${message}`);
979
- if (logLevel >= _kubb_core.logLevel.debug && error.stack) {
1129
+ if (logLevel >= _kubb_core.logLevel.verbose && error.stack) {
980
1130
  const frames = error.stack.split("\n").slice(1, 4);
981
1131
  for (const frame of frames) console.log(getMessage((0, node_util.styleText)("dim", frame.trim())));
982
1132
  if (caused?.stack) {
@@ -986,13 +1136,24 @@ const githubActionsLogger = (0, _kubb_core.defineLogger)({
986
1136
  }
987
1137
  }
988
1138
  });
1139
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
1140
+ closeAllGroups();
1141
+ if (logLevel <= _kubb_core.logLevel.silent && diagnostic.severity !== "error") return;
1142
+ if (!(0, _kubb_core.isProblemDiagnostic)(diagnostic)) {
1143
+ console.log(`::notice::${diagnostic.message}`);
1144
+ return;
1145
+ }
1146
+ const parts = [`${diagnostic.code} ${diagnostic.message}`];
1147
+ if (diagnostic.location && "pointer" in diagnostic.location) parts.push(`(at ${diagnostic.location.pointer})`);
1148
+ if (diagnostic.plugin) parts.push(`[plugin: ${diagnostic.plugin}]`);
1149
+ if (diagnostic.help) parts.push(`help: ${diagnostic.help}`);
1150
+ if (diagnostic.code !== _kubb_core.diagnosticCode.unknown) parts.push(`docs: ${_kubb_core.Diagnostics.docsUrl(diagnostic.code)}`);
1151
+ console.error(`::error::${parts.join(" ")}`);
1152
+ });
989
1153
  context.on("kubb:lifecycle:start", ({ version }) => {
990
1154
  console.log((0, node_util.styleText)("yellow", `Kubb ${version} 🧩`));
991
1155
  reset();
992
1156
  });
993
- context.on("kubb:version:new", ({ currentVersion, latestVersion }) => {
994
- console.log(`::notice::Update available for Kubb: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`);
995
- });
996
1157
  context.on("kubb:config:start", () => {
997
1158
  if (logLevel <= _kubb_core.logLevel.silent) return;
998
1159
  const text = getMessage("Configuration started");
@@ -1051,6 +1212,7 @@ const githubActionsLogger = (0, _kubb_core.defineLogger)({
1051
1212
  context.on("kubb:generation:end", ({ config }) => {
1052
1213
  const text = getMessage(config.name ? `${(0, node_util.styleText)("blue", "✓")} Generation completed for ${(0, node_util.styleText)("dim", config.name)}` : `${(0, node_util.styleText)("blue", "✓")} Generation completed`);
1053
1214
  console.log(text);
1215
+ if (state.currentConfigs.length > 1) closeGroup(config.name ? `Generation for ${(0, node_util.styleText)("bold", config.name)}` : "Generation");
1054
1216
  });
1055
1217
  onGroupStart("kubb:format:start", "Format started", "Formatting");
1056
1218
  onGroupEnd("kubb:format:end", "Format completed", "Formatting");
@@ -1078,14 +1240,6 @@ const githubActionsLogger = (0, _kubb_core.defineLogger)({
1078
1240
  }
1079
1241
  if (state.currentConfigs.length === 1) closeGroup(`Hook ${commandWithArgs}`);
1080
1242
  });
1081
- context.on("kubb:generation:summary", ({ config, status, hrStart, failedPlugins }) => {
1082
- const pluginsCount = config.plugins?.length ?? 0;
1083
- const successCount = pluginsCount - failedPlugins.size;
1084
- const duration = formatHrtime(hrStart);
1085
- if (state.currentConfigs.length > 1) console.log(" ");
1086
- console.log(status === "success" ? `Kubb Summary: ${(0, node_util.styleText)("blue", "✓")} ${`${successCount} successful`}, ${pluginsCount} total, ${(0, node_util.styleText)("green", duration)}` : `Kubb Summary: ${(0, node_util.styleText)("blue", "✓")} ${`${successCount} successful`}, ✗ ${`${failedPlugins.size} failed`}, ${pluginsCount} total, ${(0, node_util.styleText)("green", duration)}`);
1087
- if (state.currentConfigs.length > 1) closeGroup(config.name ? `Generation for ${(0, node_util.styleText)("bold", config.name)}` : "Generation");
1088
- });
1089
1243
  context.on("kubb:lifecycle:end", () => {
1090
1244
  reset();
1091
1245
  });
@@ -1145,7 +1299,7 @@ const plainLogger = (0, _kubb_core.defineLogger)({
1145
1299
  const caused = require_errors.toCause(error);
1146
1300
  const text = getMessage(["✗", error.message].join(" "));
1147
1301
  console.log(text);
1148
- if (logLevel >= _kubb_core.logLevel.debug && error.stack) {
1302
+ if (logLevel >= _kubb_core.logLevel.verbose && error.stack) {
1149
1303
  const frames = error.stack.split("\n").slice(1, 4);
1150
1304
  for (const frame of frames) console.log(getMessage(frame.trim()));
1151
1305
  if (caused?.stack) {
@@ -1155,13 +1309,13 @@ const plainLogger = (0, _kubb_core.defineLogger)({
1155
1309
  }
1156
1310
  }
1157
1311
  });
1312
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
1313
+ if (logLevel <= _kubb_core.logLevel.silent && diagnostic.severity !== "error") return;
1314
+ console.log(getMessage(formatDiagnostic(diagnostic).join("\n")));
1315
+ });
1158
1316
  context.on("kubb:lifecycle:start", ({ version }) => {
1159
1317
  console.log(`Kubb CLI v${version}`);
1160
1318
  });
1161
- context.on("kubb:version:new", ({ currentVersion, latestVersion }) => {
1162
- if (logLevel <= _kubb_core.logLevel.silent) return;
1163
- console.log(getMessage(`Update available: v${currentVersion} → v${latestVersion}. Run \`npm install -g @kubb/cli\` to update.`));
1164
- });
1165
1319
  onStep("kubb:config:start", "Configuration started");
1166
1320
  onStep("kubb:config:end", "Configuration completed");
1167
1321
  context.on("kubb:generation:start", () => {
@@ -1220,19 +1374,6 @@ const plainLogger = (0, _kubb_core.defineLogger)({
1220
1374
  console.log(getMessage(`✗ Hook ${commandWithArgs} failed${durationStr}${reason}`));
1221
1375
  }
1222
1376
  });
1223
- context.on("kubb:generation:summary", ({ config, pluginTimings, status, hrStart, failedPlugins, filesCreated }) => {
1224
- const summary = getSummary({
1225
- failedPlugins,
1226
- filesCreated,
1227
- config,
1228
- status,
1229
- hrStart,
1230
- pluginTimings: logLevel >= _kubb_core.logLevel.verbose ? pluginTimings : void 0
1231
- });
1232
- console.log(require_constants.SUMMARY_SEPARATOR);
1233
- console.log(summary.join("\n"));
1234
- console.log(require_constants.SUMMARY_SEPARATOR);
1235
- });
1236
1377
  return (_commandWithArgs, _hookId) => ({
1237
1378
  onStdout: logLevel > _kubb_core.logLevel.silent ? (s) => console.log(s) : void 0,
1238
1379
  onStderr: logLevel > _kubb_core.logLevel.silent ? (s) => console.error(s) : void 0
@@ -1338,53 +1479,45 @@ const logMapper = {
1338
1479
  plain: plainLogger,
1339
1480
  "github-actions": githubActionsLogger
1340
1481
  };
1341
- async function setupLogger(context, { logLevel }) {
1342
- const type = detectLogger();
1343
- const logger = logMapper[type];
1344
- if (!logger) throw new Error(`Unknown adapter type: ${type}`);
1345
- const makeSink = await logger.install(context, { logLevel });
1346
- if (logLevel >= _kubb_core.logLevel.debug) await fileSystemLogger.install(context, { logLevel });
1347
- return typeof makeSink === "function" ? makeSink : null;
1482
+ /**
1483
+ * Bridges a {@link Reporter} onto the run's event emitter: calls `report` with each config's
1484
+ * {@link GenerationResult} on `kubb:generation:end`. The reporter never touches the emitter.
1485
+ */
1486
+ function installReporter(context, reporter, ctx) {
1487
+ context.on("kubb:generation:end", async ({ config, diagnostics = [], filesCreated = 0, status = "success", hrStart = node_process.default.hrtime() }) => {
1488
+ await reporter.report({
1489
+ config,
1490
+ diagnostics,
1491
+ filesCreated,
1492
+ status,
1493
+ hrStart
1494
+ }, ctx);
1495
+ });
1348
1496
  }
1349
1497
  /**
1350
- * Builds the generation summary lines rendered in the end-of-run box.
1351
- * Returns an array of styled strings, one per summary row.
1498
+ * Installs the live logger (the TUI view) and the selected reporters (the output), returning the
1499
+ * terminal logger's hook sink when one was installed. Loggers and reporters are independent: the
1500
+ * `cli` selection activates the env logger plus the {@link cliReporter} summary.
1501
+ *
1502
+ * The `json` reporter owns stdout, so the terminal logger and `cli` summary are suppressed whenever
1503
+ * `json` is selected, even if `cli` is also listed.
1352
1504
  */
1353
- function getSummary({ failedPlugins, filesCreated, status, hrStart, config, pluginTimings }) {
1354
- const duration = formatHrtime(hrStart);
1355
- const pluginsCount = config.plugins?.length ?? 0;
1356
- const successCount = pluginsCount - failedPlugins.size;
1357
- const meta = {
1358
- plugins: status === "success" ? `${(0, node_util.styleText)("green", `${successCount} successful`)}, ${pluginsCount} total` : `${(0, node_util.styleText)("green", `${successCount} successful`)}, ${(0, node_util.styleText)("red", `${failedPlugins.size} failed`)}, ${pluginsCount} total`,
1359
- pluginsFailed: status === "failed" ? [...failedPlugins].map(({ plugin }) => randomCliColor(plugin.name)).join(", ") : void 0,
1360
- filesCreated,
1361
- time: (0, node_util.styleText)("green", duration),
1362
- output: node_path.default.resolve(config.root, config.output.path)
1363
- };
1364
- const labels = {
1365
- plugins: "Plugins:",
1366
- failed: "Failed:",
1367
- generated: "Generated:",
1368
- pluginTimings: "Plugin Timings:",
1369
- output: "Output:"
1370
- };
1371
- const maxLength = Math.max(0, ...[...Object.values(labels), ...pluginTimings ? Array.from(pluginTimings.keys()) : []].map((s) => s.length));
1372
- const summaryLines = [];
1373
- summaryLines.push(`${labels.plugins.padEnd(maxLength + 2)} ${meta.plugins}`);
1374
- if (meta.pluginsFailed) summaryLines.push(`${labels.failed.padEnd(maxLength + 2)} ${meta.pluginsFailed}`);
1375
- summaryLines.push(`${labels.generated.padEnd(maxLength + 2)} ${meta.filesCreated} files in ${meta.time}`);
1376
- if (pluginTimings && pluginTimings.size > 0) {
1377
- const sortedTimings = Array.from(pluginTimings.entries()).sort((a, b) => b[1] - a[1]);
1378
- summaryLines.push(`${labels.pluginTimings}`);
1379
- sortedTimings.forEach(([name, time]) => {
1380
- const timeStr = time >= 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
1381
- const barLength = Math.min(Math.ceil(time / 100), 10);
1382
- const bar = (0, node_util.styleText)("dim", "█".repeat(barLength));
1383
- summaryLines.push(`${(0, node_util.styleText)("dim", "•")} ${name.padEnd(maxLength + 1)}${bar} ${timeStr}`);
1384
- });
1505
+ async function setupReporters(context, { logLevel, reporters }) {
1506
+ const unique = new Set(reporters.length ? reporters : ["cli"]);
1507
+ const hasJson = unique.has("json");
1508
+ const ctx = { logLevel };
1509
+ let makeSink = null;
1510
+ if (unique.has("cli") && !hasJson) {
1511
+ const type = detectLogger();
1512
+ const logger = logMapper[type];
1513
+ if (!logger) throw new Error(`Unknown adapter type: ${type}`);
1514
+ const sink = await logger.install(context, { logLevel });
1515
+ makeSink = typeof sink === "function" ? sink : null;
1516
+ installReporter(context, cliReporter, ctx);
1385
1517
  }
1386
- summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`);
1387
- return summaryLines;
1518
+ if (hasJson) installReporter(context, jsonReporter, ctx);
1519
+ if (unique.has("file")) installReporter(context, fileReporter, ctx);
1520
+ return makeSink;
1388
1521
  }
1389
1522
  //#endregion
1390
1523
  //#region src/runners/generate/utils.ts
@@ -1483,7 +1616,7 @@ async function executeHooks({ configHooks, hooks, makeSink }) {
1483
1616
  command: cmd,
1484
1617
  args,
1485
1618
  commandWithArgs,
1486
- context: hooks,
1619
+ hooks,
1487
1620
  stream,
1488
1621
  sink: {
1489
1622
  onLine,
@@ -1493,8 +1626,8 @@ async function executeHooks({ configHooks, hooks, makeSink }) {
1493
1626
  });
1494
1627
  }
1495
1628
  }
1496
- async function runHook({ id, command, args, commandWithArgs, context, stream = false, sink }) {
1497
- const emitEnd = (success, error) => context.emit("kubb:hook:end", {
1629
+ async function runHook({ id, command, args, commandWithArgs, hooks, stream = false, sink }) {
1630
+ const emitEnd = (success, error) => hooks.emit("kubb:hook:end", {
1498
1631
  command,
1499
1632
  args,
1500
1633
  id,
@@ -1507,31 +1640,23 @@ async function runHook({ id, command, args, commandWithArgs, context, stream = f
1507
1640
  throwOnError: true
1508
1641
  });
1509
1642
  if (stream && sink?.onLine) for await (const line of proc) sink.onLine(line);
1510
- const result = await proc;
1511
- await context.emit("kubb:debug", {
1512
- date: /* @__PURE__ */ new Date(),
1513
- logs: [result.stdout.trimEnd()]
1514
- });
1515
- await context.emit("kubb:success", { message: `${(0, node_util.styleText)("dim", commandWithArgs)} successfully executed` });
1643
+ await proc;
1644
+ await hooks.emit("kubb:success", { message: `${(0, node_util.styleText)("dim", commandWithArgs)} successfully executed` });
1516
1645
  await emitEnd(true, null);
1517
1646
  } catch (err) {
1518
1647
  if (!(err instanceof tinyexec.NonZeroExitError)) {
1519
1648
  const error = require_errors.toError(err);
1520
1649
  await emitEnd(false, error);
1521
- await context.emit("kubb:error", { error });
1650
+ await hooks.emit("kubb:error", { error });
1522
1651
  return;
1523
1652
  }
1524
1653
  const stderr = err.output?.stderr ?? "";
1525
1654
  const stdout = err.output?.stdout ?? "";
1526
- await context.emit("kubb:debug", {
1527
- date: /* @__PURE__ */ new Date(),
1528
- logs: [stdout, stderr].filter(Boolean)
1529
- });
1530
1655
  if (stderr) sink?.onStderr?.(stderr);
1531
1656
  if (stdout) sink?.onStdout?.(stdout);
1532
1657
  const error = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
1533
1658
  await emitEnd(false, error);
1534
- await context.emit("kubb:error", { error });
1659
+ await hooks.emit("kubb:error", { error });
1535
1660
  }
1536
1661
  }
1537
1662
  /**
@@ -1617,7 +1742,7 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1617
1742
  command: toolConfig.command,
1618
1743
  args: hookArgs,
1619
1744
  commandWithArgs,
1620
- context: hooks,
1745
+ hooks,
1621
1746
  stream,
1622
1747
  sink: {
1623
1748
  onLine,
@@ -1627,9 +1752,7 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1627
1752
  }).catch(() => {});
1628
1753
  await hookEndPromise;
1629
1754
  } catch (caughtError) {
1630
- const err = require_errors.toError(caughtError);
1631
- await hooks.emit("kubb:error", { error: err });
1632
- toolError = err;
1755
+ toolError = require_errors.toError(caughtError);
1633
1756
  }
1634
1757
  }
1635
1758
  await onEnd();
@@ -1658,7 +1781,7 @@ async function generate(options) {
1658
1781
  message: config.name ? `Build generation ${(0, node_util.styleText)("bold", config.name)}` : "Build generation",
1659
1782
  info: inputPath
1660
1783
  });
1661
- const { files, failedPlugins, pluginTimings, error, driver } = await kubb.safeBuild();
1784
+ const { files, diagnostics, driver } = await kubb.safeBuild();
1662
1785
  await hooks.emit("kubb:info", { message: "Load summary" });
1663
1786
  const telemetryPlugins = Array.from(driver.plugins.values(), (p) => ({
1664
1787
  name: p.name,
@@ -1672,34 +1795,28 @@ async function generate(options) {
1672
1795
  filesCreated: files.length,
1673
1796
  status
1674
1797
  }));
1675
- if (failedPlugins.size > 0 || error) {
1676
- const allErrors = [error, ...Array.from(failedPlugins, (it) => it.error)].filter(Boolean);
1677
- for (const err of allErrors) await hooks.emit("kubb:error", { error: err });
1798
+ for (const diagnostic of diagnostics) {
1799
+ if (!(0, _kubb_core.isProblemDiagnostic)(diagnostic)) continue;
1800
+ const unknown = (0, _kubb_core.narrowDiagnostic)(diagnostic, _kubb_core.diagnosticCode.unknown);
1801
+ if (unknown) await hooks.emit("kubb:error", { error: unknown.cause ?? new Error(unknown.message) });
1802
+ else await _kubb_core.Diagnostics.emit(hooks, diagnostic);
1803
+ }
1804
+ if (_kubb_core.Diagnostics.hasError(diagnostics)) {
1678
1805
  await hooks.emit("kubb:generation:end", {
1679
1806
  config,
1680
- storage: kubb.storage
1681
- });
1682
- await hooks.emit("kubb:generation:summary", {
1683
- config,
1684
- failedPlugins,
1807
+ storage: kubb.storage,
1808
+ diagnostics,
1685
1809
  filesCreated: files.length,
1686
1810
  status: "failed",
1687
- hrStart,
1688
- pluginTimings: logLevel >= _kubb_core.logLevel.verbose ? pluginTimings : void 0
1811
+ hrStart
1689
1812
  });
1690
1813
  await reportTelemetry("failed");
1691
1814
  return false;
1692
1815
  }
1693
- await hooks.emit("kubb:success", {
1694
- message: "Generation succeeded",
1695
- info: inputPath
1696
- });
1697
- await hooks.emit("kubb:generation:end", {
1698
- config,
1699
- storage: kubb.storage
1700
- });
1701
1816
  const outputPath = node_path.default.resolve(config.root, config.output.path);
1817
+ const outputDiagnostics = [];
1702
1818
  const toolPasses = [config.output.format && {
1819
+ code: _kubb_core.diagnosticCode.formatFailed,
1703
1820
  toolValue: config.output.format,
1704
1821
  detect: detectFormatter,
1705
1822
  toolMap: formatters,
@@ -1709,6 +1826,7 @@ async function generate(options) {
1709
1826
  onStart: () => hooks.emit("kubb:format:start"),
1710
1827
  onEnd: () => hooks.emit("kubb:format:end")
1711
1828
  }, config.output.lint && {
1829
+ code: _kubb_core.diagnosticCode.lintFailed,
1712
1830
  toolValue: config.output.lint,
1713
1831
  detect: detectLinter,
1714
1832
  toolMap: linters,
@@ -1718,64 +1836,121 @@ async function generate(options) {
1718
1836
  onStart: () => hooks.emit("kubb:lint:start"),
1719
1837
  onEnd: () => hooks.emit("kubb:lint:end")
1720
1838
  }].filter(Boolean);
1721
- for (const pass of toolPasses) await runToolPass({
1722
- ...pass,
1723
- configName: config.name,
1724
- outputPath,
1725
- logLevel,
1726
- hooks,
1727
- makeSink
1728
- });
1729
- if (config.hooks) {
1730
- await hooks.emit("kubb:hooks:start");
1731
- await executeHooks({
1732
- configHooks: config.hooks,
1839
+ for (const { code, ...pass } of toolPasses) try {
1840
+ await runToolPass({
1841
+ ...pass,
1842
+ configName: config.name,
1843
+ outputPath,
1844
+ logLevel,
1733
1845
  hooks,
1734
1846
  makeSink
1735
1847
  });
1848
+ } catch (caughtError) {
1849
+ const diagnostic = outputDiagnostic(code, pass.toolLabel, caughtError);
1850
+ outputDiagnostics.push(diagnostic);
1851
+ await _kubb_core.Diagnostics.emit(hooks, diagnostic);
1852
+ }
1853
+ if (config.hooks) {
1854
+ await hooks.emit("kubb:hooks:start");
1855
+ const hookFailures = [];
1856
+ const onHookEnd = (ctx) => {
1857
+ if (!ctx.success) hookFailures.push(ctx.error ?? /* @__PURE__ */ new Error("Post-generate hook failed"));
1858
+ };
1859
+ hooks.on("kubb:hook:end", onHookEnd);
1860
+ try {
1861
+ await executeHooks({
1862
+ configHooks: config.hooks,
1863
+ hooks,
1864
+ makeSink
1865
+ });
1866
+ } finally {
1867
+ hooks.off("kubb:hook:end", onHookEnd);
1868
+ }
1869
+ for (const error of hookFailures) {
1870
+ const diagnostic = outputDiagnostic(_kubb_core.diagnosticCode.hookFailed, "Post-generate hook", error);
1871
+ outputDiagnostics.push(diagnostic);
1872
+ await _kubb_core.Diagnostics.emit(hooks, diagnostic);
1873
+ }
1736
1874
  await hooks.emit("kubb:hooks:end");
1737
1875
  }
1738
- await hooks.emit("kubb:generation:summary", {
1876
+ const finalDiagnostics = [...diagnostics, ...outputDiagnostics];
1877
+ const failed = _kubb_core.Diagnostics.hasError(outputDiagnostics);
1878
+ if (!failed) await hooks.emit("kubb:success", {
1879
+ message: "Generation succeeded",
1880
+ info: inputPath
1881
+ });
1882
+ await hooks.emit("kubb:generation:end", {
1739
1883
  config,
1740
- failedPlugins,
1884
+ storage: kubb.storage,
1885
+ diagnostics: finalDiagnostics,
1741
1886
  filesCreated: files.length,
1742
- status: "success",
1743
- hrStart,
1744
- pluginTimings
1887
+ status: failed ? "failed" : "success",
1888
+ hrStart
1745
1889
  });
1746
- await reportTelemetry("success");
1747
- return true;
1890
+ await reportTelemetry(failed ? "failed" : "success");
1891
+ return !failed;
1892
+ }
1893
+ /**
1894
+ * Builds a coded diagnostic for an output-phase failure (formatter, linter, or `done` hook).
1895
+ */
1896
+ function outputDiagnostic(code, label, caughtError) {
1897
+ const error = require_errors.toError(caughtError);
1898
+ return {
1899
+ code,
1900
+ severity: "error",
1901
+ message: `${label} failed: ${error.message}`,
1902
+ help: "Check that the tool is installed and that the command and its config are correct.",
1903
+ location: { kind: "config" },
1904
+ cause: error
1905
+ };
1748
1906
  }
1749
1907
  async function checkForUpdate(hooks) {
1750
1908
  await require_telemetry.executeIfOnline(async () => {
1751
1909
  try {
1752
1910
  const data = await (await fetch(require_constants.KUBB_NPM_PACKAGE_URL)).json();
1753
- if (data.version && require_package.version < data.version) await hooks.emit("kubb:version:new", {
1911
+ if (data.version && require_package.version < data.version) await _kubb_core.Diagnostics.emit(hooks, _kubb_core.Diagnostics.update({
1754
1912
  currentVersion: require_package.version,
1755
1913
  latestVersion: data.version
1756
- });
1914
+ }));
1757
1915
  } catch {}
1758
1916
  });
1759
1917
  }
1760
1918
  /**
1761
1919
  * Runs the full Kubb generation lifecycle for the given CLI options.
1762
- * Sets up the logger, checks for a newer version, loads configs, and calls `generate` for each config entry.
1920
+ * Loads configs, sets up the selected reporters (CLI `--reporter` overrides `config.reporters`),
1921
+ * checks for a newer version, and calls `generate` for each config entry.
1763
1922
  */
1764
- async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1923
+ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters: cliReporters }) {
1765
1924
  const logLevel = _kubb_core.logLevel[logLevelKey] ?? _kubb_core.logLevel.info;
1766
1925
  const hooks = new AsyncEventEmitter();
1767
- const makeSink = await setupLogger(hooks, { logLevel });
1768
- await hooks.emit("kubb:lifecycle:start", { version: require_package.version });
1769
- await checkForUpdate(hooks);
1926
+ let configs;
1927
+ let resolvedConfigPath;
1770
1928
  try {
1771
- await hooks.emit("kubb:config:start");
1772
- const { configs, configPath: resolvedConfigPath } = await getConfigs({
1929
+ const loaded = await getConfigs({
1773
1930
  configPath,
1774
1931
  input,
1775
1932
  watch,
1776
1933
  logLevel: logLevelKey
1777
1934
  });
1935
+ configs = loaded.configs;
1936
+ resolvedConfigPath = loaded.configPath;
1937
+ } catch (error) {
1938
+ await setupReporters(hooks, {
1939
+ logLevel,
1940
+ reporters: ["cli"]
1941
+ });
1942
+ await hooks.emit("kubb:error", { error: require_errors.toError(error) });
1943
+ node_process.default.exit(1);
1944
+ }
1945
+ const makeSink = await setupReporters(hooks, {
1946
+ logLevel,
1947
+ reporters: cliReporters?.length ? cliReporters : configs[0]?.reporters ?? ["cli"]
1948
+ });
1949
+ await hooks.emit("kubb:lifecycle:start", { version: require_package.version });
1950
+ await checkForUpdate(hooks);
1951
+ try {
1778
1952
  const relativeConfigPath = node_path.default.relative(node_process.default.cwd(), resolvedConfigPath);
1953
+ await hooks.emit("kubb:config:start");
1779
1954
  await hooks.emit("kubb:info", {
1780
1955
  message: "Config loaded",
1781
1956
  info: relativeConfigPath
@@ -1821,4 +1996,4 @@ async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1821
1996
  //#endregion
1822
1997
  exports.run = run;
1823
1998
 
1824
- //# sourceMappingURL=run-tnqS6GZS.cjs.map
1999
+ //# sourceMappingURL=run-CK8Cvq6n.cjs.map