@kubb/cli 5.0.0-beta.36 → 5.0.0-beta.38

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-DajReUxm.cjs → agent-Bl8JwjMa.cjs} +4 -4
  3. package/dist/{agent-DajReUxm.cjs.map → agent-Bl8JwjMa.cjs.map} +1 -1
  4. package/dist/{agent-cAalLgeU.js → agent-CfZ_Uqde.js} +4 -4
  5. package/dist/{agent-cAalLgeU.js.map → agent-CfZ_Uqde.js.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-C6oskVzT.cjs → generate-Bgds6Zx3.cjs} +19 -14
  11. package/dist/generate-Bgds6Zx3.cjs.map +1 -0
  12. package/dist/{generate-DmYQJcBv.js → generate-CfxFqNeb.js} +19 -14
  13. package/dist/generate-CfxFqNeb.js.map +1 -0
  14. package/dist/index.cjs +8 -8
  15. package/dist/index.js +8 -8
  16. package/dist/{init-Dbb4U-Xs.cjs → init-C5wnuzeK.cjs} +2 -2
  17. package/dist/{init-Dbb4U-Xs.cjs.map → init-C5wnuzeK.cjs.map} +1 -1
  18. package/dist/{init-iOg_X-uh.js → init-TIec3Dym.js} +2 -2
  19. package/dist/{init-iOg_X-uh.js.map → init-TIec3Dym.js.map} +1 -1
  20. package/dist/{mcp-DxrSTT8i.cjs → mcp-Cr753GW1.cjs} +3 -3
  21. package/dist/{mcp-DxrSTT8i.cjs.map → mcp-Cr753GW1.cjs.map} +1 -1
  22. package/dist/{mcp-Ci2OkdBj.js → mcp-Damue5Mq.js} +3 -3
  23. package/dist/{mcp-Ci2OkdBj.js.map → mcp-Damue5Mq.js.map} +1 -1
  24. package/dist/package-Cnt1K03J.js +6 -0
  25. package/dist/package-Cnt1K03J.js.map +1 -0
  26. package/dist/{package-DbsOo2rT.cjs → package-guApEHiW.cjs} +2 -2
  27. package/dist/package-guApEHiW.cjs.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-GvXhj9XF.cjs → run-BFZtWpcW.cjs} +491 -314
  31. package/dist/run-BFZtWpcW.cjs.map +1 -0
  32. package/dist/{run-CCgNPz0F.cjs → run-BFv6avA_.cjs} +3 -3
  33. package/dist/{run-CCgNPz0F.cjs.map → run-BFv6avA_.cjs.map} +1 -1
  34. package/dist/{run-DpDKN_rb.cjs → run-BQZyg7If.cjs} +2 -2
  35. package/dist/{run-DpDKN_rb.cjs.map → run-BQZyg7If.cjs.map} +1 -1
  36. package/dist/{run-CPimpDgO.js → run-BvXxelGR.js} +2 -2
  37. package/dist/{run-CPimpDgO.js.map → run-BvXxelGR.js.map} +1 -1
  38. package/dist/{run-Lnupy7qb.cjs → run-Bz9IFMWg.cjs} +2 -2
  39. package/dist/{run-Lnupy7qb.cjs.map → run-Bz9IFMWg.cjs.map} +1 -1
  40. package/dist/{run-B9ZkldVt.js → run-C752fag9.js} +557 -380
  41. package/dist/run-C752fag9.js.map +1 -0
  42. package/dist/run-C_NMctua.cjs.map +1 -1
  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-Bh7MgISX.js → validate-CYTKdezO.js} +3 -3
  51. package/dist/{validate-Bh7MgISX.js.map → validate-CYTKdezO.js.map} +1 -1
  52. package/dist/{validate-DVkJx4q8.cjs → validate-DMzjP-hd.cjs} +3 -3
  53. package/dist/{validate-DVkJx4q8.cjs.map → validate-DMzjP-hd.cjs.map} +1 -1
  54. package/package.json +6 -6
  55. package/src/commands/generate.ts +16 -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 +47 -94
  63. package/src/reporters/cliReporter.ts +89 -0
  64. package/src/reporters/fileReporter.ts +103 -0
  65. package/src/reporters/jsonReporter.ts +20 -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-C6oskVzT.cjs.map +0 -1
  73. package/dist/generate-DmYQJcBv.js.map +0 -1
  74. package/dist/package-DbsOo2rT.cjs.map +0 -1
  75. package/dist/package-_R15a7lY.js +0 -6
  76. package/dist/package-_R15a7lY.js.map +0 -1
  77. package/dist/run-B9ZkldVt.js.map +0 -1
  78. package/dist/run-GvXhj9XF.cjs.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-DbsOo2rT.cjs");
6
- const require_constants = require("./constants-FhPsMOdo.cjs");
5
+ const require_package = require("./package-guApEHiW.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,267 @@ 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. `report` returns one config's {@link Report}, which {@link createReporter}
758
+ * buffers, and `flush` writes them as a single pretty-printed JSON array on `kubb:lifecycle:end`.
759
+ * Buffering keeps a multi-config run one valid JSON document on stdout instead of concatenated
760
+ * objects that would break `jq .`. The terminal reporter is suppressed while `json` is active so
761
+ * stdout stays valid JSON.
762
+ */
763
+ const jsonReporter = (0, _kubb_core.createReporter)({
764
+ name: "json",
765
+ report(result) {
766
+ return buildReport(result);
767
+ },
768
+ flush(_context, reports) {
769
+ node_process.default.stdout.write(`${JSON.stringify(reports, null, 2)}\n`);
770
+ }
771
+ });
772
+ //#endregion
494
773
  //#region src/loggers/clackLogger.ts
495
774
  /**
496
775
  * TTY logger with beautiful UI and progress indicators for local development.
@@ -503,20 +782,29 @@ const clackLogger = (0, _kubb_core.defineLogger)({
503
782
  ...createProgressCounters(),
504
783
  spinner: _clack_prompts.spinner(),
505
784
  isSpinning: false,
785
+ runningPlugins: /* @__PURE__ */ new Set(),
506
786
  activeProgress: /* @__PURE__ */ new Map(),
507
787
  activeHookLogs: /* @__PURE__ */ new Map()
508
788
  };
509
- function reset() {
510
- for (const [_key, active] of state.activeProgress) {
789
+ function stopActiveProgress() {
790
+ for (const [, active] of state.activeProgress) {
511
791
  if (active.interval) clearInterval(active.interval);
512
792
  active.progressBar?.stop();
513
793
  }
794
+ state.activeProgress.clear();
795
+ }
796
+ function reset() {
797
+ stopActiveProgress();
514
798
  resetProgressCounters(state);
515
799
  state.spinner = _clack_prompts.spinner();
516
800
  state.isSpinning = false;
517
- state.activeProgress.clear();
801
+ state.runningPlugins.clear();
518
802
  state.activeHookLogs.clear();
519
803
  }
804
+ function pluginProgressText() {
805
+ const running = [...state.runningPlugins].map((name) => (0, node_util.styleText)("bold", name));
806
+ return getMessage(running.length > 0 ? `Generating ${running.join(", ")}` : "Generating plugins");
807
+ }
520
808
  function showProgressStep() {
521
809
  if (logLevel <= _kubb_core.logLevel.silent) return;
522
810
  const line = buildProgressLine(state);
@@ -545,8 +833,8 @@ const clackLogger = (0, _kubb_core.defineLogger)({
545
833
  const text = getMessage([
546
834
  (0, node_util.styleText)("blue", "ℹ"),
547
835
  message,
548
- (0, node_util.styleText)("dim", info)
549
- ].join(" "));
836
+ info ? (0, node_util.styleText)("dim", info) : void 0
837
+ ].filter(Boolean).join(" "));
550
838
  if (state.isSpinning) {
551
839
  state.spinner.message(text);
552
840
  return;
@@ -583,7 +871,7 @@ const clackLogger = (0, _kubb_core.defineLogger)({
583
871
  return;
584
872
  }
585
873
  _clack_prompts.log.error(getMessage(text));
586
- if (logLevel >= _kubb_core.logLevel.debug && error.stack) {
874
+ if (logLevel >= _kubb_core.logLevel.verbose && error.stack) {
587
875
  const frames = error.stack.split("\n").slice(1, 4);
588
876
  for (const frame of frames) _clack_prompts.log.message(getMessage((0, node_util.styleText)("dim", frame.trim())));
589
877
  if (caused?.stack) {
@@ -593,10 +881,12 @@ const clackLogger = (0, _kubb_core.defineLogger)({
593
881
  }
594
882
  }
595
883
  });
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}\`
884
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
885
+ if (logLevel <= _kubb_core.logLevel.silent && diagnostic.severity !== "error") return;
886
+ stopSpinner();
887
+ stopActiveProgress();
888
+ if ((0, _kubb_core.isUpdateDiagnostic)(diagnostic)) {
889
+ _clack_prompts.box(`\`v${diagnostic.currentVersion}\` → \`v${diagnostic.latestVersion}\`
600
890
  Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
601
891
  width: "auto",
602
892
  formatBorder: (s) => (0, node_util.styleText)("yellow", s),
@@ -605,14 +895,13 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
605
895
  contentAlign: "center",
606
896
  titleAlign: "center"
607
897
  });
608
- } catch {
609
- console.log(`Update available for Kubb: v${currentVersion} → v${latestVersion}`);
610
- console.log("Run `npm install -g @kubb/cli` to update");
898
+ return;
611
899
  }
900
+ _clack_prompts.log.message([diagnosticHeadline(diagnostic), ...diagnosticDetails(diagnostic)], { symbol: diagnosticSymbol(diagnostic.severity) });
612
901
  });
613
902
  context.on("kubb:lifecycle:start", async ({ version }) => {
614
903
  console.log(`\n${getIntro({
615
- title: "The ultimate toolkit for working with APIs",
904
+ title: "The meta framework for code generation",
616
905
  description: "Ready to start",
617
906
  version,
618
907
  areEyesOpen: true
@@ -640,32 +929,33 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
640
929
  context.on("kubb:plugin:start", ({ plugin }) => {
641
930
  if (logLevel <= _kubb_core.logLevel.silent) return;
642
931
  stopSpinner();
932
+ state.runningPlugins.add(plugin.name);
933
+ const active = state.activeProgress.get("plugins");
934
+ if (active) {
935
+ active.progressBar.advance(0, pluginProgressText());
936
+ return;
937
+ }
643
938
  const progressBar = _clack_prompts.progress({
644
939
  style: "block",
645
- max: 100,
940
+ max: Math.max(state.totalPlugins, 1),
646
941
  size: 30
647
942
  });
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
- });
943
+ progressBar.start(pluginProgressText());
944
+ progressBar.advance(state.completedPlugins + state.failedPlugins, pluginProgressText());
945
+ state.activeProgress.set("plugins", { progressBar });
657
946
  });
658
- context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
947
+ context.on("kubb:plugin:end", ({ plugin, success }) => {
659
948
  stopSpinner();
660
- const active = state.activeProgress.get(plugin.name);
949
+ const active = state.activeProgress.get("plugins");
661
950
  if (!active || logLevel === _kubb_core.logLevel.silent) return;
662
- clearInterval(active.interval);
951
+ state.runningPlugins.delete(plugin.name);
663
952
  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();
953
+ active.progressBar.advance(1, pluginProgressText());
954
+ if (state.runningPlugins.size === 0) {
955
+ active.progressBar.stop(getMessage("Plugins generated"));
956
+ state.activeProgress.delete("plugins");
957
+ showProgressStep();
958
+ }
669
959
  });
670
960
  context.on("kubb:files:processing:start", ({ files }) => {
671
961
  if (logLevel <= _kubb_core.logLevel.silent) return;
@@ -732,32 +1022,6 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
732
1022
  active.taskLog.error(getMessage(`${(0, node_util.styleText)("dim", commandWithArgs)} failed${reason}`), { showLog: true });
733
1023
  }
734
1024
  });
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
1025
  context.on("kubb:lifecycle:end", () => {
762
1026
  reset();
763
1027
  });
@@ -779,114 +1043,6 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
779
1043
  }
780
1044
  });
781
1045
  //#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
1046
  //#region src/loggers/githubActionsLogger.ts
891
1047
  /**
892
1048
  * GitHub Actions logger using group annotations for collapsible sections in CI.
@@ -973,10 +1129,9 @@ const githubActionsLogger = (0, _kubb_core.defineLogger)({
973
1129
  context.on("kubb:error", ({ error }) => {
974
1130
  const caused = require_errors.toCause(error);
975
1131
  closeAllGroups();
976
- if (logLevel <= _kubb_core.logLevel.silent) return;
977
1132
  const message = error.message || String(error);
978
1133
  console.error(`::error::${message}`);
979
- if (logLevel >= _kubb_core.logLevel.debug && error.stack) {
1134
+ if (logLevel >= _kubb_core.logLevel.verbose && error.stack) {
980
1135
  const frames = error.stack.split("\n").slice(1, 4);
981
1136
  for (const frame of frames) console.log(getMessage((0, node_util.styleText)("dim", frame.trim())));
982
1137
  if (caused?.stack) {
@@ -986,13 +1141,24 @@ const githubActionsLogger = (0, _kubb_core.defineLogger)({
986
1141
  }
987
1142
  }
988
1143
  });
1144
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
1145
+ closeAllGroups();
1146
+ if (logLevel <= _kubb_core.logLevel.silent && diagnostic.severity !== "error") return;
1147
+ if (!(0, _kubb_core.isProblemDiagnostic)(diagnostic)) {
1148
+ console.log(`::notice::${diagnostic.message}`);
1149
+ return;
1150
+ }
1151
+ const parts = [`${diagnostic.code} ${diagnostic.message}`];
1152
+ if (diagnostic.location && "pointer" in diagnostic.location) parts.push(`(at ${diagnostic.location.pointer})`);
1153
+ if (diagnostic.plugin) parts.push(`[plugin: ${diagnostic.plugin}]`);
1154
+ if (diagnostic.help) parts.push(`help: ${diagnostic.help}`);
1155
+ if (diagnostic.code !== _kubb_core.diagnosticCode.unknown) parts.push(`docs: ${_kubb_core.Diagnostics.docsUrl(diagnostic.code)}`);
1156
+ console.error(`::error::${parts.join(" ")}`);
1157
+ });
989
1158
  context.on("kubb:lifecycle:start", ({ version }) => {
990
1159
  console.log((0, node_util.styleText)("yellow", `Kubb ${version} 🧩`));
991
1160
  reset();
992
1161
  });
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
1162
  context.on("kubb:config:start", () => {
997
1163
  if (logLevel <= _kubb_core.logLevel.silent) return;
998
1164
  const text = getMessage("Configuration started");
@@ -1051,6 +1217,7 @@ const githubActionsLogger = (0, _kubb_core.defineLogger)({
1051
1217
  context.on("kubb:generation:end", ({ config }) => {
1052
1218
  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
1219
  console.log(text);
1220
+ if (state.currentConfigs.length > 1) closeGroup(config.name ? `Generation for ${(0, node_util.styleText)("bold", config.name)}` : "Generation");
1054
1221
  });
1055
1222
  onGroupStart("kubb:format:start", "Format started", "Formatting");
1056
1223
  onGroupEnd("kubb:format:end", "Format completed", "Formatting");
@@ -1078,14 +1245,6 @@ const githubActionsLogger = (0, _kubb_core.defineLogger)({
1078
1245
  }
1079
1246
  if (state.currentConfigs.length === 1) closeGroup(`Hook ${commandWithArgs}`);
1080
1247
  });
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
1248
  context.on("kubb:lifecycle:end", () => {
1090
1249
  reset();
1091
1250
  });
@@ -1145,7 +1304,7 @@ const plainLogger = (0, _kubb_core.defineLogger)({
1145
1304
  const caused = require_errors.toCause(error);
1146
1305
  const text = getMessage(["✗", error.message].join(" "));
1147
1306
  console.log(text);
1148
- if (logLevel >= _kubb_core.logLevel.debug && error.stack) {
1307
+ if (logLevel >= _kubb_core.logLevel.verbose && error.stack) {
1149
1308
  const frames = error.stack.split("\n").slice(1, 4);
1150
1309
  for (const frame of frames) console.log(getMessage(frame.trim()));
1151
1310
  if (caused?.stack) {
@@ -1155,13 +1314,13 @@ const plainLogger = (0, _kubb_core.defineLogger)({
1155
1314
  }
1156
1315
  }
1157
1316
  });
1317
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
1318
+ if (logLevel <= _kubb_core.logLevel.silent && diagnostic.severity !== "error") return;
1319
+ console.log(getMessage(formatDiagnostic(diagnostic).join("\n")));
1320
+ });
1158
1321
  context.on("kubb:lifecycle:start", ({ version }) => {
1159
1322
  console.log(`Kubb CLI v${version}`);
1160
1323
  });
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
1324
  onStep("kubb:config:start", "Configuration started");
1166
1325
  onStep("kubb:config:end", "Configuration completed");
1167
1326
  context.on("kubb:generation:start", () => {
@@ -1220,19 +1379,6 @@ const plainLogger = (0, _kubb_core.defineLogger)({
1220
1379
  console.log(getMessage(`✗ Hook ${commandWithArgs} failed${durationStr}${reason}`));
1221
1380
  }
1222
1381
  });
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
1382
  return (_commandWithArgs, _hookId) => ({
1237
1383
  onStdout: logLevel > _kubb_core.logLevel.silent ? (s) => console.log(s) : void 0,
1238
1384
  onStderr: logLevel > _kubb_core.logLevel.silent ? (s) => console.error(s) : void 0
@@ -1338,53 +1484,46 @@ const logMapper = {
1338
1484
  plain: plainLogger,
1339
1485
  "github-actions": githubActionsLogger
1340
1486
  };
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;
1487
+ /**
1488
+ * Bridges a {@link Reporter} onto the run's event emitter: calls `report` with each config's
1489
+ * {@link GenerationResult} on `kubb:generation:end`. The reporter never touches the emitter.
1490
+ */
1491
+ function installReporter(context, reporter, ctx) {
1492
+ context.on("kubb:generation:end", async ({ config, diagnostics = [], filesCreated = 0, status = "success", hrStart = node_process.default.hrtime() }) => {
1493
+ await reporter.report({
1494
+ config,
1495
+ diagnostics,
1496
+ filesCreated,
1497
+ status,
1498
+ hrStart
1499
+ }, ctx);
1500
+ });
1501
+ if (reporter.flush) context.on("kubb:lifecycle:end", () => reporter.flush?.(ctx));
1348
1502
  }
1349
1503
  /**
1350
- * Builds the generation summary lines rendered in the end-of-run box.
1351
- * Returns an array of styled strings, one per summary row.
1504
+ * Installs the live logger (the TUI view) and the selected reporters (the output), returning the
1505
+ * terminal logger's hook sink when one was installed. Loggers and reporters are independent: the
1506
+ * `cli` selection activates the env logger plus the {@link cliReporter} summary.
1507
+ *
1508
+ * The `json` reporter owns stdout, so the terminal logger and `cli` summary are suppressed whenever
1509
+ * `json` is selected, even if `cli` is also listed.
1352
1510
  */
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
- });
1511
+ async function setupReporters(context, { logLevel, reporters }) {
1512
+ const unique = new Set(reporters.length ? reporters : ["cli"]);
1513
+ const hasJson = unique.has("json");
1514
+ const ctx = { logLevel };
1515
+ let makeSink = null;
1516
+ if (unique.has("cli") && !hasJson) {
1517
+ const type = detectLogger();
1518
+ const logger = logMapper[type];
1519
+ if (!logger) throw new Error(`Unknown adapter type: ${type}`);
1520
+ const sink = await logger.install(context, { logLevel });
1521
+ makeSink = typeof sink === "function" ? sink : null;
1522
+ installReporter(context, cliReporter, ctx);
1385
1523
  }
1386
- summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`);
1387
- return summaryLines;
1524
+ if (hasJson) installReporter(context, jsonReporter, ctx);
1525
+ if (unique.has("file")) installReporter(context, fileReporter, ctx);
1526
+ return makeSink;
1388
1527
  }
1389
1528
  //#endregion
1390
1529
  //#region src/runners/generate/utils.ts
@@ -1483,7 +1622,7 @@ async function executeHooks({ configHooks, hooks, makeSink }) {
1483
1622
  command: cmd,
1484
1623
  args,
1485
1624
  commandWithArgs,
1486
- context: hooks,
1625
+ hooks,
1487
1626
  stream,
1488
1627
  sink: {
1489
1628
  onLine,
@@ -1493,8 +1632,8 @@ async function executeHooks({ configHooks, hooks, makeSink }) {
1493
1632
  });
1494
1633
  }
1495
1634
  }
1496
- async function runHook({ id, command, args, commandWithArgs, context, stream = false, sink }) {
1497
- const emitEnd = (success, error) => context.emit("kubb:hook:end", {
1635
+ async function runHook({ id, command, args, commandWithArgs, hooks, stream = false, sink }) {
1636
+ const emitEnd = (success, error) => hooks.emit("kubb:hook:end", {
1498
1637
  command,
1499
1638
  args,
1500
1639
  id,
@@ -1507,31 +1646,19 @@ async function runHook({ id, command, args, commandWithArgs, context, stream = f
1507
1646
  throwOnError: true
1508
1647
  });
1509
1648
  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` });
1649
+ await proc;
1650
+ await hooks.emit("kubb:success", { message: `${(0, node_util.styleText)("dim", commandWithArgs)} successfully executed` });
1516
1651
  await emitEnd(true, null);
1517
1652
  } catch (err) {
1518
1653
  if (!(err instanceof tinyexec.NonZeroExitError)) {
1519
- const error = require_errors.toError(err);
1520
- await emitEnd(false, error);
1521
- await context.emit("kubb:error", { error });
1654
+ await emitEnd(false, require_errors.toError(err));
1522
1655
  return;
1523
1656
  }
1524
1657
  const stderr = err.output?.stderr ?? "";
1525
1658
  const stdout = err.output?.stdout ?? "";
1526
- await context.emit("kubb:debug", {
1527
- date: /* @__PURE__ */ new Date(),
1528
- logs: [stdout, stderr].filter(Boolean)
1529
- });
1530
1659
  if (stderr) sink?.onStderr?.(stderr);
1531
1660
  if (stdout) sink?.onStdout?.(stdout);
1532
- const error = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
1533
- await emitEnd(false, error);
1534
- await context.emit("kubb:error", { error });
1661
+ await emitEnd(false, /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`));
1535
1662
  }
1536
1663
  }
1537
1664
  /**
@@ -1617,7 +1744,7 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1617
1744
  command: toolConfig.command,
1618
1745
  args: hookArgs,
1619
1746
  commandWithArgs,
1620
- context: hooks,
1747
+ hooks,
1621
1748
  stream,
1622
1749
  sink: {
1623
1750
  onLine,
@@ -1627,9 +1754,7 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1627
1754
  }).catch(() => {});
1628
1755
  await hookEndPromise;
1629
1756
  } catch (caughtError) {
1630
- const err = require_errors.toError(caughtError);
1631
- await hooks.emit("kubb:error", { error: err });
1632
- toolError = err;
1757
+ toolError = require_errors.toError(caughtError);
1633
1758
  }
1634
1759
  }
1635
1760
  await onEnd();
@@ -1658,7 +1783,7 @@ async function generate(options) {
1658
1783
  message: config.name ? `Build generation ${(0, node_util.styleText)("bold", config.name)}` : "Build generation",
1659
1784
  info: inputPath
1660
1785
  });
1661
- const { files, failedPlugins, pluginTimings, error, driver } = await kubb.safeBuild();
1786
+ const { files, diagnostics, driver } = await kubb.safeBuild();
1662
1787
  await hooks.emit("kubb:info", { message: "Load summary" });
1663
1788
  const telemetryPlugins = Array.from(driver.plugins.values(), (p) => ({
1664
1789
  name: p.name,
@@ -1672,34 +1797,28 @@ async function generate(options) {
1672
1797
  filesCreated: files.length,
1673
1798
  status
1674
1799
  }));
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 });
1800
+ for (const diagnostic of diagnostics) {
1801
+ if (!(0, _kubb_core.isProblemDiagnostic)(diagnostic)) continue;
1802
+ const unknown = (0, _kubb_core.narrowDiagnostic)(diagnostic, _kubb_core.diagnosticCode.unknown);
1803
+ if (unknown) await hooks.emit("kubb:error", { error: unknown.cause ?? new Error(unknown.message) });
1804
+ else await _kubb_core.Diagnostics.emit(hooks, diagnostic);
1805
+ }
1806
+ if (_kubb_core.Diagnostics.hasError(diagnostics)) {
1678
1807
  await hooks.emit("kubb:generation:end", {
1679
1808
  config,
1680
- storage: kubb.storage
1681
- });
1682
- await hooks.emit("kubb:generation:summary", {
1683
- config,
1684
- failedPlugins,
1809
+ storage: kubb.storage,
1810
+ diagnostics,
1685
1811
  filesCreated: files.length,
1686
1812
  status: "failed",
1687
- hrStart,
1688
- pluginTimings: logLevel >= _kubb_core.logLevel.verbose ? pluginTimings : void 0
1813
+ hrStart
1689
1814
  });
1690
1815
  await reportTelemetry("failed");
1691
1816
  return false;
1692
1817
  }
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
1818
  const outputPath = node_path.default.resolve(config.root, config.output.path);
1819
+ const outputDiagnostics = [];
1702
1820
  const toolPasses = [config.output.format && {
1821
+ code: _kubb_core.diagnosticCode.formatFailed,
1703
1822
  toolValue: config.output.format,
1704
1823
  detect: detectFormatter,
1705
1824
  toolMap: formatters,
@@ -1709,6 +1828,7 @@ async function generate(options) {
1709
1828
  onStart: () => hooks.emit("kubb:format:start"),
1710
1829
  onEnd: () => hooks.emit("kubb:format:end")
1711
1830
  }, config.output.lint && {
1831
+ code: _kubb_core.diagnosticCode.lintFailed,
1712
1832
  toolValue: config.output.lint,
1713
1833
  detect: detectLinter,
1714
1834
  toolMap: linters,
@@ -1718,64 +1838,121 @@ async function generate(options) {
1718
1838
  onStart: () => hooks.emit("kubb:lint:start"),
1719
1839
  onEnd: () => hooks.emit("kubb:lint:end")
1720
1840
  }].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,
1841
+ for (const { code, ...pass } of toolPasses) try {
1842
+ await runToolPass({
1843
+ ...pass,
1844
+ configName: config.name,
1845
+ outputPath,
1846
+ logLevel,
1733
1847
  hooks,
1734
1848
  makeSink
1735
1849
  });
1850
+ } catch (caughtError) {
1851
+ const diagnostic = outputDiagnostic(code, pass.toolLabel, caughtError);
1852
+ outputDiagnostics.push(diagnostic);
1853
+ await _kubb_core.Diagnostics.emit(hooks, diagnostic);
1854
+ }
1855
+ if (config.hooks) {
1856
+ await hooks.emit("kubb:hooks:start");
1857
+ const hookFailures = [];
1858
+ const onHookEnd = (ctx) => {
1859
+ if (!ctx.success) hookFailures.push(ctx.error ?? /* @__PURE__ */ new Error("Post-generate hook failed"));
1860
+ };
1861
+ hooks.on("kubb:hook:end", onHookEnd);
1862
+ try {
1863
+ await executeHooks({
1864
+ configHooks: config.hooks,
1865
+ hooks,
1866
+ makeSink
1867
+ });
1868
+ } finally {
1869
+ hooks.off("kubb:hook:end", onHookEnd);
1870
+ }
1871
+ for (const error of hookFailures) {
1872
+ const diagnostic = outputDiagnostic(_kubb_core.diagnosticCode.hookFailed, "Post-generate hook", error);
1873
+ outputDiagnostics.push(diagnostic);
1874
+ await _kubb_core.Diagnostics.emit(hooks, diagnostic);
1875
+ }
1736
1876
  await hooks.emit("kubb:hooks:end");
1737
1877
  }
1738
- await hooks.emit("kubb:generation:summary", {
1878
+ const finalDiagnostics = [...diagnostics, ...outputDiagnostics];
1879
+ const failed = _kubb_core.Diagnostics.hasError(outputDiagnostics);
1880
+ if (!failed) await hooks.emit("kubb:success", {
1881
+ message: "Generation succeeded",
1882
+ info: inputPath
1883
+ });
1884
+ await hooks.emit("kubb:generation:end", {
1739
1885
  config,
1740
- failedPlugins,
1886
+ storage: kubb.storage,
1887
+ diagnostics: finalDiagnostics,
1741
1888
  filesCreated: files.length,
1742
- status: "success",
1743
- hrStart,
1744
- pluginTimings
1889
+ status: failed ? "failed" : "success",
1890
+ hrStart
1745
1891
  });
1746
- await reportTelemetry("success");
1747
- return true;
1892
+ await reportTelemetry(failed ? "failed" : "success");
1893
+ return !failed;
1894
+ }
1895
+ /**
1896
+ * Builds a coded diagnostic for an output-phase failure (formatter, linter, or `done` hook).
1897
+ */
1898
+ function outputDiagnostic(code, label, caughtError) {
1899
+ const error = require_errors.toError(caughtError);
1900
+ return {
1901
+ code,
1902
+ severity: "error",
1903
+ message: `${label} failed: ${error.message}`,
1904
+ help: "Check that the tool is installed and that the command and its config are correct.",
1905
+ location: { kind: "config" },
1906
+ cause: error
1907
+ };
1748
1908
  }
1749
1909
  async function checkForUpdate(hooks) {
1750
1910
  await require_telemetry.executeIfOnline(async () => {
1751
1911
  try {
1752
1912
  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", {
1913
+ if (data.version && require_package.version < data.version) await _kubb_core.Diagnostics.emit(hooks, _kubb_core.Diagnostics.update({
1754
1914
  currentVersion: require_package.version,
1755
1915
  latestVersion: data.version
1756
- });
1916
+ }));
1757
1917
  } catch {}
1758
1918
  });
1759
1919
  }
1760
1920
  /**
1761
1921
  * 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.
1922
+ * Loads configs, sets up the selected reporters (CLI `--reporter` overrides `config.reporters`),
1923
+ * checks for a newer version, and calls `generate` for each config entry.
1763
1924
  */
1764
- async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1925
+ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters: cliReporters }) {
1765
1926
  const logLevel = _kubb_core.logLevel[logLevelKey] ?? _kubb_core.logLevel.info;
1766
1927
  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);
1928
+ let configs;
1929
+ let resolvedConfigPath;
1770
1930
  try {
1771
- await hooks.emit("kubb:config:start");
1772
- const { configs, configPath: resolvedConfigPath } = await getConfigs({
1931
+ const loaded = await getConfigs({
1773
1932
  configPath,
1774
1933
  input,
1775
1934
  watch,
1776
1935
  logLevel: logLevelKey
1777
1936
  });
1937
+ configs = loaded.configs;
1938
+ resolvedConfigPath = loaded.configPath;
1939
+ } catch (error) {
1940
+ await setupReporters(hooks, {
1941
+ logLevel,
1942
+ reporters: ["cli"]
1943
+ });
1944
+ await hooks.emit("kubb:error", { error: require_errors.toError(error) });
1945
+ node_process.default.exit(1);
1946
+ }
1947
+ const makeSink = await setupReporters(hooks, {
1948
+ logLevel,
1949
+ reporters: cliReporters?.length ? cliReporters : configs[0]?.reporters ?? ["cli"]
1950
+ });
1951
+ await hooks.emit("kubb:lifecycle:start", { version: require_package.version });
1952
+ await checkForUpdate(hooks);
1953
+ try {
1778
1954
  const relativeConfigPath = node_path.default.relative(node_process.default.cwd(), resolvedConfigPath);
1955
+ await hooks.emit("kubb:config:start");
1779
1956
  await hooks.emit("kubb:info", {
1780
1957
  message: "Config loaded",
1781
1958
  info: relativeConfigPath
@@ -1821,4 +1998,4 @@ async function run({ input, configPath, logLevel: logLevelKey, watch }) {
1821
1998
  //#endregion
1822
1999
  exports.run = run;
1823
2000
 
1824
- //# sourceMappingURL=run-GvXhj9XF.cjs.map
2001
+ //# sourceMappingURL=run-BFZtWpcW.cjs.map