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