@kubb/cli 5.3.8 → 5.3.10

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 (98) hide show
  1. package/dist/{Telemetry-CvHQevSK.cjs → Telemetry-B3kc9gFh.cjs} +3 -2
  2. package/dist/Telemetry-B3kc9gFh.cjs.map +1 -0
  3. package/dist/{Telemetry-CJcQkQOT.js → Telemetry-Dw5s6abr.js} +3 -2
  4. package/dist/Telemetry-Dw5s6abr.js.map +1 -0
  5. package/dist/connect-BAZv-FKo.cjs +11 -0
  6. package/dist/connect-BAZv-FKo.cjs.map +1 -0
  7. package/dist/connect-CJ51y1JP.js +11 -0
  8. package/dist/connect-CJ51y1JP.js.map +1 -0
  9. package/dist/{generate-Zldpj9MI.js → generate-COXHbcDp.js} +5 -18
  10. package/dist/generate-COXHbcDp.js.map +1 -0
  11. package/dist/{generate-DGRNFF3n.cjs → generate-FD-YZ_YE.cjs} +5 -18
  12. package/dist/generate-FD-YZ_YE.cjs.map +1 -0
  13. package/dist/index.cjs +9 -18
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +9 -18
  16. package/dist/index.js.map +1 -1
  17. package/dist/{init-CqKLyf-D.js → init-DTS0VTu2.js} +3 -3
  18. package/dist/{init-CqKLyf-D.js.map → init-DTS0VTu2.js.map} +1 -1
  19. package/dist/{init-C2aRtQsn.cjs → init-D_PY4Uei.cjs} +3 -3
  20. package/dist/{init-C2aRtQsn.cjs.map → init-D_PY4Uei.cjs.map} +1 -1
  21. package/dist/login-B2QH5bsM.js +11 -0
  22. package/dist/login-B2QH5bsM.js.map +1 -0
  23. package/dist/login-CnyE3-OV.cjs +11 -0
  24. package/dist/login-CnyE3-OV.cjs.map +1 -0
  25. package/dist/logout-Bep0igFY.js +14 -0
  26. package/dist/logout-Bep0igFY.js.map +1 -0
  27. package/dist/logout-DWO1gOhR.cjs +14 -0
  28. package/dist/logout-DWO1gOhR.cjs.map +1 -0
  29. package/dist/output-C_Gk4p-8.js +321 -0
  30. package/dist/output-C_Gk4p-8.js.map +1 -0
  31. package/dist/output-D-9bGS7y.cjs +411 -0
  32. package/dist/output-D-9bGS7y.cjs.map +1 -0
  33. package/dist/{package-BzG32I44.cjs → package-B9moNdpw.cjs} +2 -2
  34. package/dist/package-B9moNdpw.cjs.map +1 -0
  35. package/dist/package-BXFAvVfB.js +6 -0
  36. package/dist/package-BXFAvVfB.js.map +1 -0
  37. package/dist/{run-kdxqOL8u.js → run-BEZhsX-8.js} +2 -2
  38. package/dist/{run-kdxqOL8u.js.map → run-BEZhsX-8.js.map} +1 -1
  39. package/dist/{run-VK50TiB7.cjs → run-Bidc3n3b.cjs} +2 -2
  40. package/dist/{run-VK50TiB7.cjs.map → run-Bidc3n3b.cjs.map} +1 -1
  41. package/dist/{run-ByKuuI4i.js → run-C_Sbhgzd.js} +23 -23
  42. package/dist/run-C_Sbhgzd.js.map +1 -0
  43. package/dist/{run-gQD6xSuC.js → run-DC3jip_S.js} +31 -301
  44. package/dist/run-DC3jip_S.js.map +1 -0
  45. package/dist/{run-BP7-azAp.js → run-DLHKj9it.js} +3 -3
  46. package/dist/{run-BP7-azAp.js.map → run-DLHKj9it.js.map} +1 -1
  47. package/dist/{run-De_AsR8i.cjs → run-DgmCyO-A.cjs} +23 -23
  48. package/dist/run-DgmCyO-A.cjs.map +1 -0
  49. package/dist/{run-BZiPrq4E.cjs → run-DoCdUe8x.cjs} +3 -3
  50. package/dist/{run-BZiPrq4E.cjs.map → run-DoCdUe8x.cjs.map} +1 -1
  51. package/dist/{run-BaaFaBBx.cjs → run-htJVnqh_.cjs} +82 -299
  52. package/dist/run-htJVnqh_.cjs.map +1 -0
  53. package/dist/{run-BrnQLk3o.js → run-rwpEYekb.js} +3 -3
  54. package/dist/{run-BrnQLk3o.js.map → run-rwpEYekb.js.map} +1 -1
  55. package/dist/{run-CqLiqWP1.cjs → run-uw3vqD40.cjs} +3 -3
  56. package/dist/{run-CqLiqWP1.cjs.map → run-uw3vqD40.cjs.map} +1 -1
  57. package/dist/shared-C5doIqKD.js +64 -0
  58. package/dist/shared-C5doIqKD.js.map +1 -0
  59. package/dist/shared-CaRlFkWL.cjs +87 -0
  60. package/dist/shared-CaRlFkWL.cjs.map +1 -0
  61. package/dist/snapshot-CZF99fiV.cjs +261 -0
  62. package/dist/snapshot-CZF99fiV.cjs.map +1 -0
  63. package/dist/snapshot-CcUlXFqw.js +260 -0
  64. package/dist/snapshot-CcUlXFqw.js.map +1 -0
  65. package/dist/status-Bx_1keY0.cjs +11 -0
  66. package/dist/status-Bx_1keY0.cjs.map +1 -0
  67. package/dist/status-DiOWD-uz.js +11 -0
  68. package/dist/status-DiOWD-uz.js.map +1 -0
  69. package/dist/studio-BAMOeAwf.js +155 -0
  70. package/dist/studio-BAMOeAwf.js.map +1 -0
  71. package/dist/studio-D9oA87Mh.cjs +155 -0
  72. package/dist/studio-D9oA87Mh.cjs.map +1 -0
  73. package/dist/{utils-CMU1rLfY.cjs → utils-CyfAlK9G.cjs} +348 -508
  74. package/dist/utils-CyfAlK9G.cjs.map +1 -0
  75. package/dist/{utils-s6yetN6W.js → utils-LvEYQf9I.js} +359 -525
  76. package/dist/utils-LvEYQf9I.js.map +1 -0
  77. package/package.json +9 -9
  78. package/dist/Telemetry-CJcQkQOT.js.map +0 -1
  79. package/dist/Telemetry-CvHQevSK.cjs.map +0 -1
  80. package/dist/generate-DGRNFF3n.cjs.map +0 -1
  81. package/dist/generate-Zldpj9MI.js.map +0 -1
  82. package/dist/output-DH2Uoy4x.js +0 -118
  83. package/dist/output-DH2Uoy4x.js.map +0 -1
  84. package/dist/output-kmklyYIv.cjs +0 -172
  85. package/dist/output-kmklyYIv.cjs.map +0 -1
  86. package/dist/package-BzG32I44.cjs.map +0 -1
  87. package/dist/package-CRlfmw6k.js +0 -6
  88. package/dist/package-CRlfmw6k.js.map +0 -1
  89. package/dist/run-BaaFaBBx.cjs.map +0 -1
  90. package/dist/run-ByKuuI4i.js.map +0 -1
  91. package/dist/run-De_AsR8i.cjs.map +0 -1
  92. package/dist/run-gQD6xSuC.js.map +0 -1
  93. package/dist/studio-B33RIU3-.cjs +0 -111
  94. package/dist/studio-B33RIU3-.cjs.map +0 -1
  95. package/dist/studio-Cq_th0uM.js +0 -112
  96. package/dist/studio-Cq_th0uM.js.map +0 -1
  97. package/dist/utils-CMU1rLfY.cjs.map +0 -1
  98. package/dist/utils-s6yetN6W.js.map +0 -1
@@ -1,14 +1,14 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
2
  import { t as toError } from "./errors-C8yNfV7w.js";
3
3
  import { t as getAgentName } from "./agent-3JmkMghp.js";
4
- import { c as URL_WATCHER_TIMEOUT_MS, l as WATCHER_IGNORED_PATHS, s as URL_WATCHER_INTERVAL_MS } from "./Telemetry-CJcQkQOT.js";
5
- import { l as canUseTTY } from "./output-DH2Uoy4x.js";
4
+ import { c as URL_WATCHER_TIMEOUT_MS, l as WATCHER_IGNORED_PATHS, s as URL_WATCHER_INTERVAL_MS } from "./Telemetry-Dw5s6abr.js";
5
+ import { f as formatMsWithColor, h as getElapsedMs, m as formatMs, p as canUseTTY } from "./output-C_Gk4p-8.js";
6
6
  import { styleText } from "node:util";
7
7
  import { randomUUID } from "node:crypto";
8
- import process$1 from "node:process";
8
+ import process from "node:process";
9
9
  import { spawn } from "node:child_process";
10
10
  import { basename, dirname, relative, resolve } from "node:path";
11
- import { Diagnostics, logLevel } from "@kubb/core";
11
+ import { Diagnostics, createCliReporter, logLevel } from "@kubb/core";
12
12
  import * as clack from "@clack/prompts";
13
13
  import { createJiti } from "jiti";
14
14
  import { NonZeroExitError, x } from "tinyexec";
@@ -121,608 +121,444 @@ function tokenize(command) {
121
121
  return (command.match(/[^\s"']+|"([^"]*)"|'([^']*)'/g) ?? []).map((token) => token.replace(/^["']|["']$/g, ""));
122
122
  }
123
123
  //#endregion
124
- //#region ../../internals/utils/src/time.ts
124
+ //#region src/loggers/createLogger.ts
125
125
  /**
126
- * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
127
- * Rounds to 2 decimal places for sub-millisecond precision without noise.
128
- *
129
- * @example
130
- * ```ts
131
- * const start = process.hrtime()
132
- * doWork()
133
- * getElapsedMs(start) // 42.35
134
- * ```
135
- */
136
- function getElapsedMs(hrStart) {
137
- const [seconds, nanoseconds] = process.hrtime(hrStart);
138
- const ms = seconds * 1e3 + nanoseconds / 1e6;
139
- return Math.round(ms * 100) / 100;
140
- }
141
- /**
142
- * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
143
- *
144
- * @example
145
- * ```ts
146
- * formatMs(250) // '250ms'
147
- * formatMs(1500) // '1.50s'
148
- * formatMs(90000) // '1m 30.0s'
149
- * ```
150
- */
151
- function formatMs(ms) {
152
- if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
153
- if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
154
- return `${Math.round(ms)}ms`;
155
- }
156
- //#endregion
157
- //#region src/loggers/banner.ts
158
- /**
159
- * Parses a CSS hex color string (`#RGB`) into its RGB channels.
160
- * Falls back to `255` for any channel that cannot be parsed.
161
- */
162
- function parseHex(color) {
163
- const int = Number.parseInt(color.replace("#", ""), 16);
164
- return Number.isNaN(int) ? {
165
- r: 255,
166
- g: 255,
167
- b: 255
168
- } : {
169
- r: int >> 16 & 255,
170
- g: int >> 8 & 255,
171
- b: int & 255
172
- };
173
- }
174
- /**
175
- * Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
176
- * for the given hex color.
177
- */
178
- function hex(color) {
179
- const { r, g, b } = parseHex(color);
180
- return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
181
- }
182
- function gradient(colorStops, text) {
183
- const chars = text.split("");
184
- return chars.map((char, i) => {
185
- const t = chars.length <= 1 ? 0 : i / (chars.length - 1);
186
- const seg = Math.min(Math.floor(t * (colorStops.length - 1)), colorStops.length - 2);
187
- const lt = t * (colorStops.length - 1) - seg;
188
- const from = parseHex(colorStops[seg]);
189
- const to = parseHex(colorStops[seg + 1]);
190
- return `\x1b[38;2;${Math.round(from.r + (to.r - from.r) * lt)};${Math.round(from.g + (to.g - from.g) * lt)};${Math.round(from.b + (to.b - from.b) * lt)}m${char}\x1b[0m`;
191
- }).join("");
192
- }
193
- /**
194
- * ANSI color functions for each part of the Kubb mascot illustration.
126
+ * The three output phases, in the order the generate runner runs them.
195
127
  */
196
- const palette = {
197
- /**
198
- * Top cap of the skittle.
199
- */
200
- lid: hex("#F55A17"),
201
- /**
202
- * Upper wood body.
203
- */
204
- woodTop: hex("#F5A217"),
205
- /**
206
- * Middle wood body.
207
- */
208
- woodMid: hex("#F58517"),
209
- /**
210
- * Base wood body.
211
- */
212
- woodBase: hex("#B45309"),
213
- /**
214
- * Eye whites.
215
- */
216
- eye: hex("#FFFFFF"),
217
- /**
218
- * Highlight accent.
219
- */
220
- highlight: hex("#adadc6"),
221
- /**
222
- * Cheek blush.
223
- */
224
- blush: hex("#FDA4AF")
128
+ const PHASES = {
129
+ format: {
130
+ running: "Formatting",
131
+ success: "Formatted",
132
+ failure: "Formatting failed"
133
+ },
134
+ lint: {
135
+ running: "Linting",
136
+ success: "Linted",
137
+ failure: "Linting failed"
138
+ },
139
+ hooks: {
140
+ running: "Running post-generate hooks",
141
+ success: "Post-generate hooks completed",
142
+ failure: "Post-generate hooks failed",
143
+ showHookResults: true
144
+ }
225
145
  };
226
146
  /**
227
- * Generates the Kubb mascot welcome banner as an ANSI-colored string.
228
- *
229
- * @example
230
- * ```ts
231
- * console.log(getIntro({ title: 'kubb.config.ts', description: 'generating…', version: '5.0.0', areEyesOpen: true }))
232
- * ```
147
+ * Drops the blank lines a subprocess leaves at either end of its output, so its block does not open
148
+ * or close on an empty row.
233
149
  */
234
- function getIntro({ title, description, version, areEyesOpen }) {
235
- const kubbVersion = gradient([
236
- "#F58517",
237
- "#F5A217",
238
- "#F55A17"
239
- ], `KUBB v${version}`);
240
- const eyeTop = areEyesOpen ? palette.eye("█▀█") : palette.eye("───");
241
- const eyeBottom = areEyesOpen ? palette.eye("▀▀▀") : palette.eye("───");
242
- return `
243
- ${palette.lid("▄▄▄▄▄▄▄▄▄▄▄▄▄")}
244
- ${palette.woodTop("█ ")}${palette.highlight("▄▄")}${palette.woodTop(" ")}${palette.highlight("▄▄")}${palette.woodTop(" █")} ${kubbVersion}
245
- ${palette.woodMid("█ ")}${eyeTop}${palette.woodMid(" ")}${eyeTop}${palette.woodMid(" █")} ${styleText("gray", title)}
246
- ${palette.woodMid("█ ")}${eyeBottom}${palette.woodMid(" ")}${palette.blush("◡")}${palette.woodMid(" ")}${eyeBottom}${palette.woodMid(" █")} ${styleText("yellow", "➜")} ${styleText("white", description)}
247
- ${palette.woodBase("▀▀▀▀▀▀▀▀▀▀▀▀▀")}
248
- `;
150
+ function trimBlankEdges(lines) {
151
+ const start = lines.findIndex((line) => line.trim());
152
+ if (start === -1) return [];
153
+ return lines.slice(start, lines.findLastIndex((line) => line.trim()) + 1);
249
154
  }
250
155
  /**
251
- * Formats a millisecond duration with a threshold-based ANSI color.
252
- * `≤ 500 ms` → green · `≤ 1 000 ms` → yellow · `> 1 000 ms` → red.
156
+ * Wires a {@link LoggerWriter} onto the run's hooks. This owns what a run says and in what order:
157
+ * one group per config, a step over the plugin work, a progress bar for the writes, one step per
158
+ * output phase, then the summary and the group's closing result. The writer only decides how each
159
+ * line looks, so every logger reports the same run the same way.
253
160
  *
254
161
  * @example
255
162
  * ```ts
256
- * formatMsWithColor(200) // '\x1b[32m200ms\x1b[39m'
257
- * formatMsWithColor(800) // '\x1b[33m800ms\x1b[39m'
258
- * formatMsWithColor(1500) // '\x1b[31m1.50s\x1b[39m'
163
+ * export const plainLogger = { name: 'plain', install: (context, options) => createLogger(writer)(context, options) }
259
164
  * ```
260
165
  */
261
- function formatMsWithColor(ms) {
262
- const formatted = formatMs(ms);
263
- if (ms <= 500) return styleText("green", formatted);
264
- if (ms <= 1e3) return styleText("yellow", formatted);
265
- return styleText("red", formatted);
266
- }
267
- //#endregion
268
- //#region src/loggers/clackLogger.ts
269
- /**
270
- * TTY logger for local development, with spinners and progress bars.
271
- */
272
- const clackLogger = {
273
- name: "clack",
274
- install(context, options) {
275
- const logLevel$3 = options?.logLevel ?? logLevel.info;
166
+ function createLogger(writer) {
167
+ return function install(context, options) {
168
+ const logLevel$2 = options?.logLevel ?? logLevel.info;
169
+ const silent = logLevel$2 <= logLevel.silent;
276
170
  const state = {
277
171
  ...createProgressCounters(),
278
- spinner: clack.spinner(),
279
- isSpinning: false,
280
- runningPlugins: /* @__PURE__ */ new Set(),
281
- activeProgress: /* @__PURE__ */ new Map(),
282
- activeHookLogs: /* @__PURE__ */ new Map()
172
+ spinner: null,
173
+ progress: null,
174
+ hooks: /* @__PURE__ */ new Map(),
175
+ /**
176
+ * Plugin results held back while the generation step owns the line.
177
+ */
178
+ pluginLines: [],
179
+ phase: null,
180
+ groupOpen: false
283
181
  };
284
- function stopActiveProgress() {
285
- for (const [, active] of state.activeProgress) {
286
- if (active.interval) clearInterval(active.interval);
287
- active.progressBar?.stop();
182
+ function text(message) {
183
+ return formatMessage(message, logLevel$2);
184
+ }
185
+ function startSpinner(message) {
186
+ if (silent) return;
187
+ state.spinner = writer.spinner();
188
+ state.spinner.start(text(message));
189
+ }
190
+ function stopSpinner(message, status = "success") {
191
+ const spinner = state.spinner;
192
+ if (!spinner) return;
193
+ state.spinner = null;
194
+ if (message === void 0) {
195
+ spinner.clear();
196
+ return;
288
197
  }
289
- state.activeProgress.clear();
198
+ if (status === "failed") {
199
+ spinner.error(text(message));
200
+ return;
201
+ }
202
+ spinner.stop(text(message));
290
203
  }
291
- function reset() {
292
- stopActiveProgress();
293
- resetProgressCounters(state);
294
- state.spinner = clack.spinner();
295
- state.isSpinning = false;
296
- state.runningPlugins.clear();
297
- state.activeHookLogs.clear();
204
+ function stopProgress(message) {
205
+ const progress = state.progress;
206
+ if (!progress) return;
207
+ state.progress = null;
208
+ progress.stop(message === void 0 ? "" : text(message));
298
209
  }
299
- function pluginProgressText() {
300
- const running = [...state.runningPlugins].map((name) => styleText("bold", name));
301
- return getMessage(running.length > 0 ? `Generating ${running.join(", ")}` : "Generating plugins");
210
+ function startPhase({ running, success, failure, showHookResults = false }) {
211
+ state.phase = {
212
+ successLabel: success,
213
+ failureLabel: failure,
214
+ showHookResults,
215
+ failed: false,
216
+ hrStart: process.hrtime(),
217
+ lines: [],
218
+ output: []
219
+ };
220
+ startSpinner(running);
302
221
  }
303
- function showProgressStep() {
304
- if (logLevel$3 <= logLevel.silent) return;
305
- const line = buildProgressLine(state);
306
- if (line) clack.log.step(getMessage(line));
222
+ function endPhase() {
223
+ const phase = state.phase;
224
+ state.phase = null;
225
+ if (!phase || silent) return;
226
+ const duration = formatMsWithColor(getElapsedMs(phase.hrStart));
227
+ stopSpinner(phase.failed ? `${phase.failureLabel} after ${duration}` : `${phase.successLabel} in ${duration}`, phase.failed ? "failed" : "success");
228
+ if (phase.lines.length) writer.block(phase.lines);
229
+ writeOutput(phase.output);
307
230
  }
308
- function getMessage(message) {
309
- return formatMessage(message, logLevel$3);
231
+ function writeOutput(lines) {
232
+ const output = trimBlankEdges(lines);
233
+ if (output.length) writer.raw(output);
310
234
  }
311
- function onStep(hook, message) {
312
- context.hook(hook, () => {
313
- if (logLevel$3 <= logLevel.silent) return;
314
- clack.log.step(getMessage(message));
315
- });
235
+ function closeGroup(status) {
236
+ if (!state.groupOpen) return;
237
+ state.groupOpen = false;
238
+ stopSpinner();
239
+ stopProgress();
240
+ writer.groupEnd(status === "failed" ? styleText("red", "✗ Generation failed") : styleText("green", "✓ Generation succeeded"));
316
241
  }
317
- function stopSpinner(text) {
318
- if (!state.isSpinning) return;
319
- state.spinner.stop(text);
320
- state.isSpinning = false;
242
+ function reset() {
243
+ stopSpinner();
244
+ stopProgress();
245
+ resetProgressCounters(state);
246
+ state.hooks.clear();
247
+ state.pluginLines = [];
248
+ state.phase = null;
249
+ state.groupOpen = false;
321
250
  }
322
- context.hook("kubb:info", ({ message, info = "" }) => {
323
- if (logLevel$3 <= logLevel.silent) return;
324
- const text = getMessage([
251
+ context.hook("kubb:info", ({ message, info }) => {
252
+ if (silent) return;
253
+ const line = text([
325
254
  styleText("blue", "ℹ"),
326
255
  message,
327
256
  info ? styleText("dim", info) : void 0
328
257
  ].filter(Boolean).join(" "));
329
- if (state.isSpinning) {
330
- state.spinner.message(text);
258
+ if (state.phase) {
259
+ state.phase.lines.push(line);
331
260
  return;
332
261
  }
333
- clack.log.info(text);
262
+ if (state.spinner) {
263
+ state.spinner.message(line);
264
+ return;
265
+ }
266
+ writer.info(line);
334
267
  });
335
- context.hook("kubb:success", ({ message, info = "" }) => {
336
- if (logLevel$3 <= logLevel.silent) return;
337
- const text = getMessage([
338
- styleText("blue", "✓"),
268
+ context.hook("kubb:success", ({ message, info }) => {
269
+ if (silent) return;
270
+ const line = text([
271
+ styleText("green", "✓"),
339
272
  message,
340
- logLevel$3 >= logLevel.info ? styleText("dim", info) : void 0
273
+ logLevel$2 >= logLevel.info && info ? styleText("dim", info) : void 0
341
274
  ].filter(Boolean).join(" "));
342
- if (state.isSpinning) {
343
- stopSpinner(text);
275
+ if (state.spinner) {
276
+ state.spinner.message(line);
344
277
  return;
345
278
  }
346
- clack.log.success(text);
279
+ writer.success(line);
347
280
  });
348
281
  context.hook("kubb:warn", ({ message, info }) => {
349
- if (logLevel$3 < logLevel.warn) return;
350
- const text = getMessage([
282
+ if (logLevel$2 < logLevel.warn) return;
283
+ writer.warn(text([
351
284
  styleText("yellow", "⚠"),
352
285
  message,
353
- logLevel$3 >= logLevel.info && info ? styleText("dim", info) : void 0
354
- ].filter(Boolean).join(" "));
355
- clack.log.warn(text);
286
+ logLevel$2 >= logLevel.info && info ? styleText("dim", info) : void 0
287
+ ].filter(Boolean).join(" ")));
356
288
  });
357
289
  context.hook("kubb:error", ({ error }) => {
358
- const text = [styleText("red", "✗"), error.message].join(" ");
359
- if (state.isSpinning) {
360
- stopSpinner(getMessage(text));
361
- return;
362
- }
363
- clack.log.error(getMessage(text));
364
- const frames = logLevel$3 >= logLevel.verbose ? formatErrorFrames(error) : null;
365
- if (frames) {
366
- for (const frame of frames.frames) clack.log.message(getMessage(styleText("dim", frame)));
367
- if (frames.cause) {
368
- clack.log.message(styleText("dim", frames.cause.header));
369
- for (const frame of frames.cause.frames) clack.log.message(getMessage(` ${styleText("dim", frame)}`));
370
- }
371
- }
290
+ if (state.phase) state.phase.failed = true;
291
+ const line = [styleText("red", "✗"), error.message].join(" ");
292
+ if (state.spinner) stopSpinner(line, "failed");
293
+ else writer.error(text(line));
294
+ const frames = logLevel$2 >= logLevel.verbose ? formatErrorFrames(error) : null;
295
+ if (!frames) return;
296
+ writer.block(frames.frames.map((frame) => text(styleText("dim", frame))));
297
+ if (frames.cause) writer.block([styleText("dim", frames.cause.header), ...frames.cause.frames.map((frame) => text(` ${styleText("dim", frame)}`))]);
372
298
  });
373
299
  context.hook("kubb:diagnostic", ({ diagnostic }) => {
374
- if (logLevel$3 <= logLevel.silent && diagnostic.severity !== "error") return;
300
+ if (silent && diagnostic.severity !== "error") return;
375
301
  stopSpinner();
376
- stopActiveProgress();
302
+ stopProgress();
377
303
  if (Diagnostics.isUpdate(diagnostic)) {
378
- clack.box(`\`v${diagnostic.currentVersion}\` → \`v${diagnostic.latestVersion}\`
379
- Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
380
- width: "auto",
381
- formatBorder: (s) => styleText("yellow", s),
382
- rounded: true,
383
- withGuide: false,
384
- contentAlign: "center",
385
- titleAlign: "center"
386
- });
304
+ writer.update([`\`v${diagnostic.currentVersion}\` → \`v${diagnostic.latestVersion}\``, "Run `npm install -g @kubb/cli` to update"], "Update available for `Kubb`");
387
305
  return;
388
306
  }
389
307
  const { headline, details } = Diagnostics.format(diagnostic);
390
- clack.log.message([headline, ...details], {
391
- symbol: "",
392
- secondarySymbol: ""
393
- });
308
+ writer.diagnostic([headline, ...details]);
394
309
  });
395
310
  context.hook("studio:connecting", ({ url }) => {
396
- if (logLevel$3 <= logLevel.silent) return;
397
- state.spinner = clack.spinner();
398
- state.spinner.start(getMessage(`Connecting to ${styleText("cyan", url)}`));
399
- state.isSpinning = true;
311
+ startSpinner(`Connecting to ${styleText("cyan", url)}`);
400
312
  });
401
313
  context.hook("studio:connected", ({ url, versions }) => {
402
- if (logLevel$3 <= logLevel.silent) return;
403
- const text = getMessage(`Connected to ${styleText("cyan", url)} ${styleText("dim", `(${formatVersions(versions)})`)}`);
404
- if (state.isSpinning) {
405
- stopSpinner(text);
314
+ if (silent) return;
315
+ const line = `Connected to ${styleText("cyan", url)} ${styleText("dim", `(${formatVersions(versions)})`)}`;
316
+ if (state.spinner) {
317
+ stopSpinner(line);
406
318
  return;
407
319
  }
408
- clack.log.success(text);
320
+ writer.success(text(`✓ ${line}`));
409
321
  });
410
322
  context.hook("studio:ready", () => {
411
- if (logLevel$3 <= logLevel.silent) return;
412
- clack.log.success(getMessage("Ready to receive jobs"));
323
+ if (silent) return;
324
+ startSpinner("✓ Ready to receive jobs");
413
325
  });
414
326
  context.hook("studio:disconnected", ({ reason }) => {
415
- if (logLevel$3 < logLevel.warn) return;
327
+ if (logLevel$2 < logLevel.warn) return;
416
328
  stopSpinner();
417
- clack.log.warn(getMessage(`Kubb Studio ended the session (${reason})`));
329
+ writer.warn(text(`⚠ Kubb Studio ended the session (${reason})`));
418
330
  });
419
331
  context.hook("studio:command:start", ({ command }) => {
420
- if (logLevel$3 <= logLevel.silent) return;
421
- clack.log.info(getMessage(`Kubb Studio asked to ${styleText("bold", command)}`));
332
+ if (silent) return;
333
+ stopSpinner();
334
+ writer.info(text(`Kubb Studio asked to ${styleText("bold", command)}`));
422
335
  });
423
336
  context.hook("studio:command:end", ({ command, info }) => {
424
- if (logLevel$3 <= logLevel.silent) return;
425
- clack.log.success(getMessage(`Finished ${command}${info ? ` ${styleText("dim", `(${info})`)}` : ""}`));
337
+ if (silent) return;
338
+ writer.success(text(`✓ Finished ${command}${info ? ` ${styleText("dim", `(${info})`)}` : ""}`));
339
+ startSpinner("✓ Ready to receive jobs");
426
340
  });
427
341
  context.hook("studio:warn", ({ message }) => {
428
- if (logLevel$3 < logLevel.warn) return;
429
- clack.log.warn(getMessage(message));
342
+ if (logLevel$2 < logLevel.warn) return;
343
+ writer.warn(text(`⚠ ${message}`));
430
344
  });
431
345
  context.hook("studio:error", ({ error }) => {
432
- const text = getMessage([styleText("red", "✗"), error.message].join(" "));
433
- if (state.isSpinning) {
434
- stopSpinner(text);
346
+ const line = `✗ ${error.message}`;
347
+ if (state.spinner) {
348
+ stopSpinner(line, "failed");
435
349
  return;
436
350
  }
437
- clack.log.error(text);
351
+ writer.error(text(line));
438
352
  });
439
- context.hook("kubb:lifecycle:start", async ({ version }) => {
440
- console.log(`\n${getIntro({
441
- title: "The meta framework for code generation",
442
- description: "Ready to start",
443
- version,
444
- areEyesOpen: true
445
- })}\n`);
353
+ context.hook("kubb:lifecycle:start", () => {
446
354
  reset();
447
355
  });
448
356
  context.hook("kubb:generation:start", ({ config }) => {
357
+ closeGroup("failed");
449
358
  reset();
450
359
  state.totalPlugins = config.plugins?.length ?? 0;
451
- if (logLevel$3 <= logLevel.silent) return;
452
- const text = getMessage([
453
- "Generation started",
454
- config.name ? `for ${styleText("dim", config.name)}` : void 0,
455
- getInputPath(config)
456
- ].filter(Boolean).join(" "));
457
- clack.intro(text);
360
+ if (silent) return;
361
+ const inputPath = getInputPath(config);
362
+ writer.group(text([styleText("bold", config.name ?? "kubb"), inputPath ? styleText("dim", inputPath) : void 0].filter(Boolean).join(" ")));
363
+ state.groupOpen = true;
364
+ startSpinner("Generating");
458
365
  });
459
366
  context.hook("kubb:plugin:start", ({ plugin }) => {
460
- if (logLevel$3 <= logLevel.silent) return;
461
- stopSpinner();
462
- state.runningPlugins.add(plugin.name);
463
- const active = state.activeProgress.get("plugins");
464
- if (active) {
465
- active.progressBar.advance(0, pluginProgressText());
466
- return;
467
- }
468
- const progressBar = clack.progress({
469
- style: "block",
470
- max: Math.max(state.totalPlugins, 1),
471
- size: 30
472
- });
473
- progressBar.start(pluginProgressText());
474
- progressBar.advance(state.completedPlugins + state.failedPlugins, pluginProgressText());
475
- state.activeProgress.set("plugins", { progressBar });
367
+ if (silent) return;
368
+ state.spinner?.message(text(`Generating ${styleText("dim", plugin.name)}`));
476
369
  });
477
- context.hook("kubb:plugin:end", ({ plugin, success }) => {
478
- stopSpinner();
479
- const active = state.activeProgress.get("plugins");
480
- if (!active || logLevel$3 <= logLevel.silent) return;
481
- state.runningPlugins.delete(plugin.name);
370
+ context.hook("kubb:plugin:end", ({ plugin, duration, success }) => {
371
+ if (silent) return;
482
372
  recordPluginResult(state, success);
483
- active.progressBar.advance(1, pluginProgressText());
484
- if (state.runningPlugins.size === 0) {
485
- active.progressBar.stop(getMessage("Plugins generated"));
486
- state.activeProgress.delete("plugins");
487
- showProgressStep();
373
+ state.pluginLines.push(text(`${plugin.name} ${success ? "completed" : "failed"} in ${formatMsWithColor(duration)}`));
374
+ });
375
+ context.hook("kubb:plugins:end", () => {
376
+ if (silent) return;
377
+ const failed = state.failedPlugins > 0;
378
+ stopSpinner(buildProgressLine(state) ?? "No plugins ran", failed ? "failed" : "success");
379
+ if (state.pluginLines.length) {
380
+ writer.block(state.pluginLines);
381
+ state.pluginLines = [];
488
382
  }
489
383
  });
490
384
  context.hook("kubb:files:processing:start", ({ files }) => {
491
- if (logLevel$3 <= logLevel.silent) return;
385
+ if (silent) return;
492
386
  stopSpinner();
493
387
  state.totalFiles = files.length;
494
388
  state.processedFiles = 0;
495
- const text = `Writing ${files.length} files`;
496
- const progressBar = clack.progress({
497
- style: "block",
498
- max: files.length,
499
- size: 30
500
- });
501
- context.callHook("kubb:info", { message: text });
502
- progressBar.start(getMessage(text));
503
- state.activeProgress.set("files", { progressBar });
389
+ state.progress = writer.progress(files.length);
390
+ state.progress.start(text(`Writing ${pluralize(files.length, "file")}`));
504
391
  });
505
392
  context.hook("kubb:files:processing:update", ({ files }) => {
506
- if (logLevel$3 <= logLevel.silent) return;
507
- stopSpinner();
508
- const active = state.activeProgress.get("files");
393
+ if (silent) return;
509
394
  for (const { file, config } of files) {
510
395
  state.processedFiles++;
511
- if (active) active.progressBar.advance(void 0, `Writing ${relative(config.root, file.path)}`);
396
+ state.progress?.advance(`Writing ${relative(config.root, file.path)}`);
512
397
  }
513
398
  });
514
399
  context.hook("kubb:files:processing:end", () => {
515
- if (logLevel$3 <= logLevel.silent) return;
516
- stopSpinner();
517
- const text = getMessage("Files written successfully");
518
- const active = state.activeProgress.get("files");
519
- if (!active) return;
520
- active.progressBar.stop(text);
521
- state.activeProgress.delete("files");
522
- showProgressStep();
400
+ if (silent) return;
401
+ stopProgress(`Wrote ${pluralize(state.processedFiles, "file")}`);
523
402
  });
524
- context.hook("kubb:generation:end", ({ config }) => {
525
- stopSpinner();
526
- const text = getMessage(config.name ? `Generation completed for ${styleText("dim", config.name)}` : "Generation completed");
527
- clack.outro(text);
528
- });
529
- onStep("kubb:format:start", "Formatting");
530
- onStep("kubb:lint:start", "Linting");
531
- onStep("kubb:hooks:start", "Running hooks");
532
- context.hook("kubb:hook:start", ({ id, command, name, args }) => {
533
- if (logLevel$3 <= logLevel.silent || !id) return;
534
- stopSpinner();
535
- const commandWithArgs = formatCommandWithArgs(command, args);
536
- const title = getMessage(`Running ${styleText("dim", name ?? commandWithArgs)}`);
537
- const taskLog = clack.taskLog({ title });
538
- state.activeHookLogs.set(id, {
539
- taskLog,
540
- hrStart: process$1.hrtime()
403
+ context.hook("kubb:format:start", () => startPhase(PHASES.format));
404
+ context.hook("kubb:format:end", endPhase);
405
+ context.hook("kubb:lint:start", () => startPhase(PHASES.lint));
406
+ context.hook("kubb:lint:end", endPhase);
407
+ context.hook("kubb:hooks:start", () => startPhase(PHASES.hooks));
408
+ context.hook("kubb:hooks:end", endPhase);
409
+ context.hook("kubb:hook:start", ({ id }) => {
410
+ if (silent || !id) return;
411
+ state.hooks.set(id, {
412
+ hrStart: process.hrtime(),
413
+ lines: []
541
414
  });
542
415
  });
543
- if (logLevel$3 > logLevel.silent) context.hook("kubb:hook:line", ({ id, line }) => {
544
- state.activeHookLogs.get(id)?.taskLog.message(styleText("dim", line));
416
+ if (!silent) context.hook("kubb:hook:line", ({ id, line }) => {
417
+ const active = state.hooks.get(id);
418
+ if (!active) return;
419
+ if (state.phase) {
420
+ active.lines.push(line);
421
+ return;
422
+ }
423
+ writer.raw([line]);
545
424
  });
546
425
  context.hook("kubb:hook:end", ({ id, command, name, args, success, error, stdout, stderr }) => {
547
426
  if (!id) return;
548
- if (logLevel$3 <= logLevel.silent) {
427
+ if (!success && state.phase) state.phase.failed = true;
428
+ if (silent) {
549
429
  if (!success) {
550
430
  if (stdout) console.log(stdout);
551
431
  if (stderr) console.error(stderr);
552
432
  }
553
433
  return;
554
434
  }
555
- const active = state.activeHookLogs.get(id);
435
+ const active = state.hooks.get(id);
556
436
  if (!active) return;
557
- state.activeHookLogs.delete(id);
558
- const commandWithArgs = formatCommandWithArgs(command, args);
559
- const duration = formatMsWithColor(getElapsedMs(active.hrStart));
560
- if (success) active.taskLog.success(getMessage(`${styleText("dim", name ?? commandWithArgs)} completed in ${duration}`));
561
- else {
562
- const reason = error?.message ? ` (${error.message})` : "";
563
- active.taskLog.error(getMessage(`${styleText("dim", name ?? commandWithArgs)} failed${reason}`), { showLog: true });
437
+ state.hooks.delete(id);
438
+ const label = styleText("dim", name ?? formatCommandWithArgs(command, args));
439
+ const reason = error?.message ? ` (${error.message})` : "";
440
+ const result = success ? `${styleText("green", "✓")} ${label} in ${formatMsWithColor(getElapsedMs(active.hrStart))}` : `${styleText("red", "✗")} ${label} failed${reason}`;
441
+ const phase = state.phase;
442
+ if (phase) {
443
+ if (phase.showHookResults || !success) phase.lines.push(result);
444
+ phase.output.push(...active.lines);
445
+ return;
564
446
  }
447
+ stopSpinner();
448
+ writer.step(text(result));
449
+ writeOutput(active.lines);
450
+ });
451
+ context.hook("kubb:generation:end", () => {
452
+ stopSpinner();
453
+ stopProgress();
565
454
  });
566
455
  context.hook("kubb:lifecycle:end", () => {
456
+ closeGroup("failed");
567
457
  reset();
568
458
  });
569
- }
459
+ return { renderSummary(lines, { status }) {
460
+ if (silent || !state.groupOpen) return;
461
+ closeGroup(status);
462
+ writer.diagnostic([...lines]);
463
+ } };
464
+ };
465
+ }
466
+ /**
467
+ * TTY logger for local development, with animated steps and progress bars.
468
+ */
469
+ const clackLogger = {
470
+ name: "clack",
471
+ install: createLogger({
472
+ group: (title) => clack.intro(title),
473
+ groupEnd: (text) => clack.outro(text),
474
+ step: (text) => clack.log.step(text, { spacing: 0 }),
475
+ info: (text) => clack.log.info(text),
476
+ success: (text) => clack.log.success(text),
477
+ warn: (text) => clack.log.warn(text),
478
+ error: (text) => clack.log.error(text),
479
+ block: (lines) => clack.log.message(lines, { spacing: 0 }),
480
+ raw: (lines) => console.log(styleText("dim", lines.join("\n"))),
481
+ diagnostic: (lines) => clack.log.message(lines, {
482
+ symbol: "",
483
+ secondarySymbol: ""
484
+ }),
485
+ update: (lines, title) => clack.box(lines.join("\n"), title, {
486
+ width: "auto",
487
+ formatBorder: (border) => styleText("yellow", border),
488
+ rounded: true,
489
+ withGuide: false,
490
+ contentAlign: "center",
491
+ titleAlign: "center"
492
+ }),
493
+ spinner() {
494
+ const spinner = clack.spinner();
495
+ return {
496
+ start: (text) => spinner.start(text),
497
+ message: (text) => spinner.message(text),
498
+ clear: () => spinner.clear(),
499
+ stop: (text) => spinner.stop(text),
500
+ error: (text) => spinner.error(text)
501
+ };
502
+ },
503
+ progress(max) {
504
+ const bar = clack.progress({
505
+ style: "block",
506
+ max,
507
+ size: 30
508
+ });
509
+ return {
510
+ start: (text) => bar.start(text),
511
+ advance: (text) => bar.advance(1, text),
512
+ stop: (text) => bar.stop(text)
513
+ };
514
+ }
515
+ })
570
516
  };
571
517
  //#endregion
572
518
  //#region src/loggers/plainLogger.ts
573
519
  /**
574
- * Plain console adapter for non-TTY environments, built on `console.log`.
520
+ * Prefixes that stand in for the symbols clack draws, so plain output reads the same way.
521
+ */
522
+ const SYMBOLS = {
523
+ info: "ℹ",
524
+ warn: "⚠",
525
+ error: "✗",
526
+ step: "◇"
527
+ };
528
+ function write(line) {
529
+ console.log(line);
530
+ }
531
+ /**
532
+ * Console adapter for non-TTY environments, and for an AI coding agent reading the output. Reports
533
+ * the same run in the same order as `clackLogger`, without the animation.
575
534
  */
576
535
  const plainLogger = {
577
536
  name: "plain",
578
- install(context, options) {
579
- const logLevel$2 = options?.logLevel ?? logLevel.info;
580
- const hookTimer = createHookTimer();
581
- function getMessage(message) {
582
- return formatMessage(message, logLevel$2);
583
- }
584
- function onStep(hook, message) {
585
- context.hook(hook, () => {
586
- if (logLevel$2 <= logLevel.silent) return;
587
- console.log(getMessage(message));
588
- });
589
- }
590
- context.hook("kubb:info", ({ message, info }) => {
591
- if (logLevel$2 <= logLevel.silent) return;
592
- const text = getMessage([
593
- "ℹ",
594
- message,
595
- info
596
- ].join(" "));
597
- console.log(text);
598
- });
599
- context.hook("kubb:success", ({ message, info = "" }) => {
600
- if (logLevel$2 <= logLevel.silent) return;
601
- const text = getMessage([
602
- "✓",
603
- message,
604
- logLevel$2 >= logLevel.info ? info : void 0
605
- ].filter(Boolean).join(" "));
606
- console.log(text);
607
- });
608
- context.hook("kubb:warn", ({ message, info }) => {
609
- if (logLevel$2 < logLevel.warn) return;
610
- const text = getMessage([
611
- "⚠",
612
- message,
613
- logLevel$2 >= logLevel.info ? info : void 0
614
- ].filter(Boolean).join(" "));
615
- console.log(text);
616
- });
617
- context.hook("kubb:error", ({ error }) => {
618
- const text = getMessage(["✗", error.message].join(" "));
619
- console.log(text);
620
- const frames = logLevel$2 >= logLevel.verbose ? formatErrorFrames(error) : null;
621
- if (frames) {
622
- for (const frame of frames.frames) console.log(getMessage(frame));
623
- if (frames.cause) {
624
- console.log(frames.cause.header);
625
- for (const frame of frames.cause.frames) console.log(getMessage(` ${frame}`));
626
- }
627
- }
628
- });
629
- context.hook("kubb:diagnostic", ({ diagnostic }) => {
630
- if (logLevel$2 <= logLevel.silent && diagnostic.severity !== "error") return;
631
- console.log(getMessage(Diagnostics.formatLines(diagnostic).join("\n")));
632
- });
633
- context.hook("studio:connecting", ({ url }) => {
634
- if (logLevel$2 <= logLevel.silent) return;
635
- console.log(getMessage(`Connecting to ${url}`));
636
- });
637
- context.hook("studio:connected", ({ url, versions }) => {
638
- if (logLevel$2 <= logLevel.silent) return;
639
- console.log(getMessage(`✓ Connected to ${url} (${formatVersions(versions)})`));
640
- });
641
- context.hook("studio:ready", () => {
642
- if (logLevel$2 <= logLevel.silent) return;
643
- console.log(getMessage("✓ Ready to receive jobs"));
644
- });
645
- context.hook("studio:disconnected", ({ reason }) => {
646
- if (logLevel$2 < logLevel.warn) return;
647
- console.log(getMessage(`⚠ Kubb Studio ended the session (${reason})`));
648
- });
649
- context.hook("studio:command:start", ({ command }) => {
650
- if (logLevel$2 <= logLevel.silent) return;
651
- console.log(getMessage(`Kubb Studio asked to ${command}`));
652
- });
653
- context.hook("studio:command:end", ({ command, info }) => {
654
- if (logLevel$2 <= logLevel.silent) return;
655
- console.log(getMessage(`✓ Finished ${command}${info ? ` (${info})` : ""}`));
656
- });
657
- context.hook("studio:warn", ({ message }) => {
658
- if (logLevel$2 < logLevel.warn) return;
659
- console.log(getMessage(`⚠ ${message}`));
660
- });
661
- context.hook("studio:error", ({ error }) => {
662
- console.log(getMessage(`✗ ${error.message}`));
663
- });
664
- context.hook("kubb:lifecycle:start", ({ version }) => {
665
- console.log(`Kubb CLI v${version}`);
666
- });
667
- context.hook("kubb:generation:start", ({ config }) => {
668
- const text = getMessage(["Generation started", getInputPath(config)].filter(Boolean).join(" "));
669
- console.log(text);
670
- });
671
- context.hook("kubb:plugin:start", ({ plugin }) => {
672
- if (logLevel$2 <= logLevel.silent) return;
673
- const text = getMessage(`Generating ${plugin.name}`);
674
- console.log(text);
675
- });
676
- context.hook("kubb:plugin:end", ({ plugin, duration, success }) => {
677
- if (logLevel$2 <= logLevel.silent) return;
678
- const durationStr = formatMs(duration);
679
- const text = getMessage(success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`);
680
- console.log(text);
681
- });
682
- context.hook("kubb:files:processing:start", ({ files }) => {
683
- if (logLevel$2 <= logLevel.silent) return;
684
- const text = getMessage(`Writing ${files.length} files`);
685
- console.log(text);
686
- });
687
- context.hook("kubb:files:processing:update", ({ files }) => {
688
- if (logLevel$2 <= logLevel.silent) return;
689
- for (const { file, config } of files) console.log(getMessage(`Writing ${relative(config.root, file.path)}`));
690
- });
691
- context.hook("kubb:files:processing:end", () => {
692
- if (logLevel$2 <= logLevel.silent) return;
693
- const text = getMessage("Files written successfully");
694
- console.log(text);
695
- });
696
- context.hook("kubb:generation:end", ({ config }) => {
697
- const text = getMessage(config.name ? `Generation completed for ${config.name}` : "Generation completed");
698
- console.log(text);
699
- });
700
- onStep("kubb:format:start", "Format started");
701
- onStep("kubb:format:end", "Format completed");
702
- onStep("kubb:lint:start", "Lint started");
703
- onStep("kubb:lint:end", "Lint completed");
704
- onStep("kubb:hooks:start", "Hooks started");
705
- onStep("kubb:hooks:end", "Hooks completed");
706
- context.hook("kubb:hook:start", ({ id, command, name, args }) => {
707
- if (logLevel$2 <= logLevel.silent) return;
708
- if (id) hookTimer.start(id);
709
- const commandWithArgs = formatCommandWithArgs(command, args);
710
- console.log(getMessage(`Hook ${name ?? commandWithArgs} started`));
711
- });
712
- context.hook("kubb:hook:end", ({ id, command, name, args, success, error, stdout, stderr }) => {
713
- if (logLevel$2 <= logLevel.silent) return;
714
- const ms = id ? hookTimer.end(id) : void 0;
715
- const durationStr = ms !== void 0 ? ` in ${formatMs(ms)}` : "";
716
- const commandWithArgs = formatCommandWithArgs(command, args);
717
- if (success) console.log(getMessage(`✓ Hook ${name ?? commandWithArgs} completed${durationStr}`));
718
- else {
719
- if (stdout) console.log(stdout);
720
- if (stderr) console.error(stderr);
721
- const reason = error?.message ? ` (${error.message})` : "";
722
- console.log(getMessage(`✗ Hook ${name ?? commandWithArgs} failed${durationStr}${reason}`));
723
- }
724
- });
725
- }
537
+ install: createLogger({
538
+ group: write,
539
+ groupEnd: write,
540
+ step: (text) => write(`${SYMBOLS.step} ${text}`),
541
+ info: write,
542
+ success: write,
543
+ warn: write,
544
+ error: write,
545
+ block: (lines) => write(lines.join("\n")),
546
+ raw: (lines) => write(lines.join("\n")),
547
+ diagnostic: (lines) => write(lines.join("\n")),
548
+ update: (lines, title) => write([title, ...lines].join("\n")),
549
+ spinner: () => ({
550
+ start: write,
551
+ message: write,
552
+ clear: () => {},
553
+ stop: (text) => text && write(text),
554
+ error: (text) => write(`${SYMBOLS.error} ${text}`)
555
+ }),
556
+ progress: () => ({
557
+ start: write,
558
+ advance: write,
559
+ stop: (text) => text && write(text)
560
+ })
561
+ })
726
562
  };
727
563
  //#endregion
728
564
  //#region src/loggers/utils.ts
@@ -736,6 +572,15 @@ function getInputPath(config) {
736
572
  return typeof input?.path === "string" ? input.path : void 0;
737
573
  }
738
574
  /**
575
+ * Counts a noun, so a message never reads `1 files`.
576
+ *
577
+ * @example
578
+ * `pluralize(1, 'config')` returns `'1 config'`
579
+ */
580
+ function pluralize(count, noun) {
581
+ return `${count} ${count === 1 ? noun : `${noun}s`}`;
582
+ }
583
+ /**
739
584
  * Optionally prefix a message with a [HH:MM:SS] timestamp when logLevel >= verbose.
740
585
  * Shared across all logger adapters to avoid duplication.
741
586
  */
@@ -810,7 +655,7 @@ function createProgressCounters() {
810
655
  failedPlugins: 0,
811
656
  totalFiles: 0,
812
657
  processedFiles: 0,
813
- hrStart: process$1.hrtime()
658
+ hrStart: process.hrtime()
814
659
  };
815
660
  }
816
661
  /**
@@ -822,7 +667,7 @@ function resetProgressCounters(state) {
822
667
  state.failedPlugins = 0;
823
668
  state.totalFiles = 0;
824
669
  state.processedFiles = 0;
825
- state.hrStart = process$1.hrtime();
670
+ state.hrStart = process.hrtime();
826
671
  }
827
672
  /**
828
673
  * Records a finished plugin against the progress counters.
@@ -832,23 +677,6 @@ function recordPluginResult(state, success) {
832
677
  else state.failedPlugins++;
833
678
  }
834
679
  /**
835
- * Creates a {@link HookTimer} backed by a private `id → hrtime` map.
836
- */
837
- function createHookTimer() {
838
- const starts = /* @__PURE__ */ new Map();
839
- return {
840
- start(id) {
841
- starts.set(id, process$1.hrtime());
842
- },
843
- end(id) {
844
- const hrStart = starts.get(id);
845
- if (!hrStart) return;
846
- starts.delete(id);
847
- return getElapsedMs(hrStart);
848
- }
849
- };
850
- }
851
- /**
852
680
  * Join a command and its optional args into a single display string.
853
681
  *
854
682
  * @example With args
@@ -870,7 +698,7 @@ function installReporter(context, reporter, ctx) {
870
698
  if (added.length) filesByPlugin.set(plugin.name, /* @__PURE__ */ new Set([...filesByPlugin.get(plugin.name) ?? [], ...added]));
871
699
  pluginFiles.set(config, filesByPlugin);
872
700
  });
873
- context.hook("kubb:generation:end", async ({ config, diagnostics = [], filesCreated = 0, status = "success", hrStart = process$1.hrtime() }) => {
701
+ context.hook("kubb:generation:end", async ({ config, diagnostics = [], filesCreated = 0, status = "success", hrStart = process.hrtime() }) => {
874
702
  const files = pluginFiles?.get(config);
875
703
  const grouped = files && [...files].map(([plugin, paths]) => ({
876
704
  plugin,
@@ -895,20 +723,26 @@ function installReporter(context, reporter, ctx) {
895
723
  * `stdout`/`stderr` on `kubb:hook:end`, so nothing is returned here.
896
724
  *
897
725
  * Loggers and reporters are independent, except for `cli`: it both installs the live logger view
898
- * here and registers as a reporter that prints the per-config summary. The `json` reporter owns
899
- * stdout, so the whole `cli` reporter (live logger and summary) is skipped whenever `json` is
900
- * among the reporters, even if `cli` is also listed.
726
+ * here and registers a reporter that renders the per-config summary through that logger, so the
727
+ * summary lands inside the group the logger opened. The `json` reporter owns stdout, so the whole
728
+ * `cli` reporter (live logger and summary) is skipped whenever `json` is among the reporters, even
729
+ * if `cli` is also listed.
901
730
  */
902
731
  async function setupReporters(context, { logLevel, reporters }) {
732
+ let cliHandle;
903
733
  const hasJson = reporters.some((reporter) => reporter.name === "json");
904
734
  const ctx = { logLevel };
905
735
  for (const reporter of reporters) {
906
- if (reporter.name === "cli") {
907
- if (hasJson) continue;
908
- await (canUseTTY() && !getAgentName() ? clackLogger : plainLogger).install(context, { logLevel });
736
+ if (reporter.name !== "cli") {
737
+ installReporter(context, reporter, ctx);
738
+ continue;
909
739
  }
910
- installReporter(context, reporter, ctx);
740
+ if (hasJson) continue;
741
+ const handle = await (canUseTTY() && !getAgentName() ? clackLogger : plainLogger).install(context, { logLevel });
742
+ cliHandle = handle;
743
+ installReporter(context, createCliReporter({ render: handle?.renderSummary }), ctx);
911
744
  }
745
+ return cliHandle;
912
746
  }
913
747
  /**
914
748
  * Picks the reporters whose `name` matches one of `names`, in the order the names are given.
@@ -992,7 +826,7 @@ async function getConfigs({ configPath, input, watch, logLevel }) {
992
826
  let result;
993
827
  try {
994
828
  result = await loadConfig({
995
- cwd: abs ? dirname(abs) : process$1.cwd(),
829
+ cwd: abs ? dirname(abs) : process.cwd(),
996
830
  sources,
997
831
  merge: false
998
832
  });
@@ -1085,7 +919,7 @@ async function runHook({ id, command, name, args, commandWithArgs, hooks }) {
1085
919
  const stream = hooks.listenerCount("kubb:hook:line") > 0;
1086
920
  try {
1087
921
  const proc = x(command, [...args ?? []], {
1088
- nodeOptions: { detached: process$1.platform !== "win32" },
922
+ nodeOptions: { detached: process.platform !== "win32" },
1089
923
  throwOnError: true
1090
924
  });
1091
925
  if (stream) for await (const line of proc) await hooks.callHook("kubb:hook:line", {
@@ -1164,10 +998,10 @@ async function startWatcher(path, cb, log = {
1164
998
  ignored: WATCHER_IGNORED_PATHS,
1165
999
  ignoreInitial: true
1166
1000
  });
1167
- process$1.once("SIGINT", () => {
1001
+ process.once("SIGINT", () => {
1168
1002
  watcher.close();
1169
1003
  });
1170
- process$1.once("SIGTERM", () => {
1004
+ process.once("SIGTERM", () => {
1171
1005
  watcher.close();
1172
1006
  });
1173
1007
  const runBuild = createSerialRunner({
@@ -1226,8 +1060,8 @@ function startUrlWatcher(url, cb, options = {}) {
1226
1060
  if (timer) clearTimeout(timer);
1227
1061
  controller?.abort();
1228
1062
  };
1229
- process$1.once("SIGINT", stop);
1230
- process$1.once("SIGTERM", stop);
1063
+ process.once("SIGINT", stop);
1064
+ process.once("SIGTERM", stop);
1231
1065
  const runBuild = createSerialRunner({
1232
1066
  run: () => cb([url]),
1233
1067
  onError: () => log.error(styleText("red", "Watcher failed"))
@@ -1259,6 +1093,6 @@ function startUrlWatcher(url, cb, options = {}) {
1259
1093
  return stop;
1260
1094
  }
1261
1095
  //#endregion
1262
- export { runPostGenerate as a, selectReporters as c, LINTER_PREFERENCE as d, detectTool as f, runHook as i, setupReporters as l, linters as m, getConfigs as n, startUrlWatcher as o, formatters as p, isNewerVersion as r, startWatcher as s, fetchUrlBody as t, FORMATTER_PREFERENCE as u };
1096
+ export { runPostGenerate as a, pluralize as c, FORMATTER_PREFERENCE as d, LINTER_PREFERENCE as f, linters as h, runHook as i, selectReporters as l, formatters as m, getConfigs as n, startUrlWatcher as o, detectTool as p, isNewerVersion as r, startWatcher as s, fetchUrlBody as t, setupReporters as u };
1263
1097
 
1264
- //# sourceMappingURL=utils-s6yetN6W.js.map
1098
+ //# sourceMappingURL=utils-LvEYQf9I.js.map