@kubb/cli 5.0.0-beta.4 → 5.0.0-beta.40

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 (155) hide show
  1. package/README.md +198 -36
  2. package/dist/agent-DQerNyd8.cjs +70 -0
  3. package/dist/agent-DQerNyd8.cjs.map +1 -0
  4. package/dist/agent-ZKJkTTGN.js +68 -0
  5. package/dist/agent-ZKJkTTGN.js.map +1 -0
  6. package/dist/{chunk--u3MIqq1.js → chunk-CRm0XQPb.js} +1 -0
  7. package/dist/constants-84a47qA-.js +35 -0
  8. package/dist/constants-84a47qA-.js.map +1 -0
  9. package/dist/constants-AHhyFH15.cjs +139 -0
  10. package/dist/constants-AHhyFH15.cjs.map +1 -0
  11. package/dist/constants-BtmponZ3.cjs +58 -0
  12. package/dist/constants-BtmponZ3.cjs.map +1 -0
  13. package/dist/constants-C94RKp3A.js +116 -0
  14. package/dist/constants-C94RKp3A.js.map +1 -0
  15. package/dist/{define-Bdn8j5VM.cjs → define-C4AB3POr.cjs} +2 -2
  16. package/dist/{define-Bdn8j5VM.cjs.map → define-C4AB3POr.cjs.map} +1 -1
  17. package/dist/{define-Ctii4bel.js → define-DNG1U8ha.js} +2 -2
  18. package/dist/{define-Ctii4bel.js.map → define-DNG1U8ha.js.map} +1 -1
  19. package/dist/{errors-CjPmyZHy.js → errors-CoxrNXaA.js} +2 -2
  20. package/dist/{errors-CjPmyZHy.js.map → errors-CoxrNXaA.js.map} +1 -1
  21. package/dist/{errors-CLCjoSg0.cjs → errors-DykI11xo.cjs} +2 -2
  22. package/dist/{errors-CLCjoSg0.cjs.map → errors-DykI11xo.cjs.map} +1 -1
  23. package/dist/generate-40x9PP4o.js +77 -0
  24. package/dist/generate-40x9PP4o.js.map +1 -0
  25. package/dist/generate-DQLvFw4z.cjs +76 -0
  26. package/dist/generate-DQLvFw4z.cjs.map +1 -0
  27. package/dist/index.cjs +23 -14
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.js +23 -14
  31. package/dist/index.js.map +1 -1
  32. package/dist/init-CT9RChdK.js +53 -0
  33. package/dist/init-CT9RChdK.js.map +1 -0
  34. package/dist/init-sEaUN7Dj.cjs +53 -0
  35. package/dist/init-sEaUN7Dj.cjs.map +1 -0
  36. package/dist/mcp-Siyb6fTT.cjs +39 -0
  37. package/dist/mcp-Siyb6fTT.cjs.map +1 -0
  38. package/dist/mcp-cjPrOeot.js +39 -0
  39. package/dist/mcp-cjPrOeot.js.map +1 -0
  40. package/dist/{package-BapVyQ-w.cjs → package-BJ6qam2Y.cjs} +2 -2
  41. package/dist/package-BJ6qam2Y.cjs.map +1 -0
  42. package/dist/package-CR5vEK4K.js +6 -0
  43. package/dist/package-CR5vEK4K.js.map +1 -0
  44. package/dist/{agent-sdYBBgrd.js → run-BQ3Qj0xB.js} +46 -43
  45. package/dist/run-BQ3Qj0xB.js.map +1 -0
  46. package/dist/run-BQzoaxjR.js +32 -0
  47. package/dist/run-BQzoaxjR.js.map +1 -0
  48. package/dist/run-CGf0KEts.js +51 -0
  49. package/dist/run-CGf0KEts.js.map +1 -0
  50. package/dist/{generate-CNrRLY4n.js → run-CHZKHTv0.js} +832 -828
  51. package/dist/run-CHZKHTv0.js.map +1 -0
  52. package/dist/{init-CZ5Xq2Hd.cjs → run-C_NMctua.cjs} +107 -149
  53. package/dist/run-C_NMctua.cjs.map +1 -0
  54. package/dist/{generate-B1Pa2ho-.cjs → run-CcQawFNK.cjs} +784 -780
  55. package/dist/run-CcQawFNK.cjs.map +1 -0
  56. package/dist/run-CkTpemme.cjs +52 -0
  57. package/dist/run-CkTpemme.cjs.map +1 -0
  58. package/dist/run-Cl4SrSob.cjs +33 -0
  59. package/dist/run-Cl4SrSob.cjs.map +1 -0
  60. package/dist/{agent-B4cAAab2.cjs → run-D-s2LdlW.cjs} +46 -43
  61. package/dist/run-D-s2LdlW.cjs.map +1 -0
  62. package/dist/{init-eNRlotJK.js → run-D8dCWepS.js} +107 -149
  63. package/dist/run-D8dCWepS.js.map +1 -0
  64. package/dist/{shell-DLzN4fRo.js → shell-BrqyJdB7.js} +2 -2
  65. package/dist/{shell-DLzN4fRo.js.map → shell-BrqyJdB7.js.map} +1 -1
  66. package/dist/{shell-475fQKaX.cjs → shell-Lh-vLWwH.cjs} +2 -2
  67. package/dist/{shell-475fQKaX.cjs.map → shell-Lh-vLWwH.cjs.map} +1 -1
  68. package/dist/validate-8pgfxUTy.js +26 -0
  69. package/dist/validate-8pgfxUTy.js.map +1 -0
  70. package/dist/validate-CstV7Pc2.cjs +26 -0
  71. package/dist/validate-CstV7Pc2.cjs.map +1 -0
  72. package/package.json +16 -15
  73. package/src/commands/agent/start.ts +10 -7
  74. package/src/commands/agent.ts +3 -1
  75. package/src/commands/generate.ts +21 -13
  76. package/src/commands/init.ts +34 -3
  77. package/src/commands/mcp.ts +28 -4
  78. package/src/commands/validate.ts +6 -4
  79. package/src/constants.ts +3 -74
  80. package/src/index.ts +6 -4
  81. package/src/loggers/clackLogger.ts +143 -181
  82. package/src/loggers/githubActionsLogger.ts +119 -121
  83. package/src/loggers/plainLogger.ts +50 -100
  84. package/src/loggers/types.ts +6 -0
  85. package/src/loggers/utils.ts +190 -18
  86. package/src/runners/agent/run.ts +113 -0
  87. package/src/runners/agent/utils.ts +98 -0
  88. package/src/runners/generate/run.ts +411 -0
  89. package/src/runners/generate/utils.ts +222 -0
  90. package/src/runners/init/run.ts +212 -0
  91. package/src/{utils/packageManager.ts → runners/init/utils.ts} +12 -2
  92. package/src/runners/mcp/run.ts +37 -0
  93. package/src/runners/validate/run.ts +63 -0
  94. package/dist/agent-B4cAAab2.cjs.map +0 -1
  95. package/dist/agent-CR6Z96og.js +0 -56
  96. package/dist/agent-CR6Z96og.js.map +0 -1
  97. package/dist/agent-Dmxzqg4d.cjs +0 -58
  98. package/dist/agent-Dmxzqg4d.cjs.map +0 -1
  99. package/dist/agent-sdYBBgrd.js.map +0 -1
  100. package/dist/constants-CnDXa1R6.cjs +0 -148
  101. package/dist/constants-CnDXa1R6.cjs.map +0 -1
  102. package/dist/constants-aL3CP_Wq.js +0 -95
  103. package/dist/constants-aL3CP_Wq.js.map +0 -1
  104. package/dist/generate-B1Pa2ho-.cjs.map +0 -1
  105. package/dist/generate-BDGOOsBM.cjs +0 -65
  106. package/dist/generate-BDGOOsBM.cjs.map +0 -1
  107. package/dist/generate-CNrRLY4n.js.map +0 -1
  108. package/dist/generate-DuhxPLGr.js +0 -66
  109. package/dist/generate-DuhxPLGr.js.map +0 -1
  110. package/dist/init-CZ5Xq2Hd.cjs.map +0 -1
  111. package/dist/init-CnZXHrbq.js +0 -25
  112. package/dist/init-CnZXHrbq.js.map +0 -1
  113. package/dist/init-NYJSZJSb.cjs +0 -25
  114. package/dist/init-NYJSZJSb.cjs.map +0 -1
  115. package/dist/init-eNRlotJK.js.map +0 -1
  116. package/dist/mcp-CYOgxB82.cjs +0 -47
  117. package/dist/mcp-CYOgxB82.cjs.map +0 -1
  118. package/dist/mcp-CdFWyrwi.cjs +0 -16
  119. package/dist/mcp-CdFWyrwi.cjs.map +0 -1
  120. package/dist/mcp-DhSxuDMD.js +0 -16
  121. package/dist/mcp-DhSxuDMD.js.map +0 -1
  122. package/dist/mcp-DmJm3TrU.js +0 -46
  123. package/dist/mcp-DmJm3TrU.js.map +0 -1
  124. package/dist/package-BapVyQ-w.cjs.map +0 -1
  125. package/dist/package-DyJE-qNq.js +0 -6
  126. package/dist/package-DyJE-qNq.js.map +0 -1
  127. package/dist/telemetry-DN95_2pF.cjs +0 -282
  128. package/dist/telemetry-DN95_2pF.cjs.map +0 -1
  129. package/dist/telemetry-LgT_sdPe.js +0 -245
  130. package/dist/telemetry-LgT_sdPe.js.map +0 -1
  131. package/dist/validate-C6npXzel.cjs +0 -25
  132. package/dist/validate-C6npXzel.cjs.map +0 -1
  133. package/dist/validate-kLJoT_hi.js +0 -33
  134. package/dist/validate-kLJoT_hi.js.map +0 -1
  135. package/dist/validate-n38Rh-Y7.js +0 -25
  136. package/dist/validate-n38Rh-Y7.js.map +0 -1
  137. package/dist/validate-yKKzqEZ5.cjs +0 -34
  138. package/dist/validate-yKKzqEZ5.cjs.map +0 -1
  139. package/src/loggers/fileSystemLogger.ts +0 -138
  140. package/src/runners/agent.ts +0 -155
  141. package/src/runners/generate.ts +0 -333
  142. package/src/runners/init.ts +0 -296
  143. package/src/runners/mcp.ts +0 -51
  144. package/src/runners/validate.ts +0 -39
  145. package/src/types.ts +0 -11
  146. package/src/utils/Writables.ts +0 -17
  147. package/src/utils/executeHooks.ts +0 -45
  148. package/src/utils/flags.ts +0 -9
  149. package/src/utils/getConfig.ts +0 -10
  150. package/src/utils/getCosmiConfig.ts +0 -80
  151. package/src/utils/getSummary.ts +0 -68
  152. package/src/utils/runHook.ts +0 -91
  153. package/src/utils/telemetry.ts +0 -273
  154. package/src/utils/watcher.ts +0 -19
  155. /package/dist/{chunk-ByKO4r7w.cjs → chunk-Bx3C2hgW.cjs} +0 -0
@@ -1,23 +1,21 @@
1
- import "./chunk--u3MIqq1.js";
2
- import { n as toCause, r as toError } from "./errors-CjPmyZHy.js";
3
- import { a as canUseTTY, i as executeIfOnline, o as isGitHubActions, r as sendTelemetry, t as buildTelemetryEvent } from "./telemetry-LgT_sdPe.js";
4
- import { n as tokenize } from "./shell-DLzN4fRo.js";
5
- import { t as version } from "./package-DyJE-qNq.js";
6
- import { a as SUMMARY_SEPARATOR, n as KUBB_NPM_PACKAGE_URL, o as WATCHER_IGNORED_PATHS } from "./constants-aL3CP_Wq.js";
1
+ import "./chunk-CRm0XQPb.js";
2
+ import { n as toCause, r as toError } from "./errors-CoxrNXaA.js";
3
+ import { n as tokenize } from "./shell-BrqyJdB7.js";
4
+ import { t as version } from "./package-CR5vEK4K.js";
5
+ import { r as WATCHER_IGNORED_PATHS, t as KUBB_NPM_PACKAGE_URL } from "./constants-84a47qA-.js";
7
6
  import { styleText } from "node:util";
8
7
  import { EventEmitter } from "node:events";
9
8
  import { createHash } from "node:crypto";
10
9
  import { spawn } from "node:child_process";
11
- import { readdirSync } from "node:fs";
12
- import { mkdir, readFile, writeFile } from "node:fs/promises";
13
- import path, { dirname, relative, resolve } from "node:path";
10
+ import { existsSync } from "node:fs";
11
+ import path, { relative } from "node:path";
12
+ import { promises } from "node:dns";
13
+ import { Diagnostics, Telemetry, cliReporter, createKubb, defineLogger, logLevel, selectReporters } from "@kubb/core";
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";
17
- import { NonZeroExitError, x } from "tinyexec";
18
- import { Writable } from "node:stream";
19
16
  import { cosmiconfig } from "cosmiconfig";
20
- import { unrun } from "unrun";
17
+ import { createJiti } from "jiti";
18
+ import { NonZeroExitError, x } from "tinyexec";
21
19
  //#region ../../internals/utils/src/asyncEventEmitter.ts
22
20
  /**
23
21
  * Typed `EventEmitter` that awaits all async listeners before resolving.
@@ -48,9 +46,12 @@ var AsyncEventEmitter = class {
48
46
  * await emitter.emit('build', 'petstore')
49
47
  * ```
50
48
  */
51
- async emit(eventName, ...eventArgs) {
49
+ emit(eventName, ...eventArgs) {
52
50
  const listeners = this.#emitter.listeners(eventName);
53
51
  if (listeners.length === 0) return;
52
+ return this.#emitAll(eventName, listeners, eventArgs);
53
+ }
54
+ async #emitAll(eventName, listeners, eventArgs) {
54
55
  for (const listener of listeners) try {
55
56
  await listener(...eventArgs);
56
57
  } catch (err) {
@@ -113,6 +114,24 @@ var AsyncEventEmitter = class {
113
114
  return this.#emitter.listenerCount(eventName);
114
115
  }
115
116
  /**
117
+ * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
118
+ * Set this above the expected listener count when many listeners attach by design.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * emitter.setMaxListeners(40)
123
+ * ```
124
+ */
125
+ setMaxListeners(max) {
126
+ this.#emitter.setMaxListeners(max);
127
+ }
128
+ /**
129
+ * Returns the current per-event listener ceiling.
130
+ */
131
+ getMaxListeners() {
132
+ return this.#emitter.getMaxListeners();
133
+ }
134
+ /**
116
135
  * Removes all listeners from every event channel.
117
136
  *
118
137
  * @example
@@ -265,32 +284,6 @@ function getIntro({ title, description, version, areEyesOpen }) {
265
284
  `;
266
285
  }
267
286
  /**
268
- * ANSI color names used by {@link randomCliColor} for deterministic terminal coloring.
269
- */
270
- const randomColors = [
271
- "black",
272
- "red",
273
- "green",
274
- "yellow",
275
- "blue",
276
- "white",
277
- "magenta",
278
- "cyan",
279
- "gray"
280
- ];
281
- /**
282
- * Wraps `text` in a deterministic ANSI color derived from the text's SHA-256 hash.
283
- *
284
- * @example
285
- * ```ts
286
- * randomCliColor('petstore') // '\x1b[33m' + 'petstore' + '\x1b[39m' (always the same color for 'petstore')
287
- * ```
288
- */
289
- function randomCliColor(text) {
290
- if (!text) return "";
291
- return styleText(randomColors[createHash("sha256").update(text).digest().readUInt32BE(0) % randomColors.length] ?? "white", text);
292
- }
293
- /**
294
287
  * Formats a millisecond duration with a threshold-based ANSI color.
295
288
  * `≤ 500 ms` → green · `≤ 1 000 ms` → yellow · `> 1 000 ms` → red.
296
289
  *
@@ -308,6 +301,55 @@ function formatMsWithColor(ms) {
308
301
  return styleText("red", formatted);
309
302
  }
310
303
  //#endregion
304
+ //#region ../../internals/utils/src/env.ts
305
+ /**
306
+ * Returns `true` when running inside a GitHub Actions workflow.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * if (isGitHubActions()) {
311
+ * core.setOutput('result', 'ok')
312
+ * }
313
+ * ```
314
+ */
315
+ function isGitHubActions() {
316
+ return !!process.env.GITHUB_ACTIONS;
317
+ }
318
+ /**
319
+ * Returns `true` when the process is running in a CI environment.
320
+ * Covers GitHub Actions, GitLab CI, CircleCI, Travis CI, Jenkins, Bitbucket,
321
+ * TeamCity, Buildkite, and Azure Pipelines.
322
+ *
323
+ * @example
324
+ * ```ts
325
+ * if (isCIEnvironment()) {
326
+ * logger.level = 'error'
327
+ * }
328
+ * ```
329
+ */
330
+ function isCIEnvironment() {
331
+ return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.BITBUCKET_BUILD_NUMBER || process.env.JENKINS_URL || process.env.CIRCLECI || process.env.TRAVIS || process.env.TEAMCITY_VERSION || process.env.BUILDKITE || process.env.TF_BUILD);
332
+ }
333
+ /**
334
+ * Returns `true` when the process has an interactive TTY with a valid terminal
335
+ * width and is not running in CI.
336
+ *
337
+ * Some IDE-embedded terminals report `isTTY = true` but set `columns` to `0`,
338
+ * which breaks clack's box-drawing helpers (they call `String.prototype.repeat`
339
+ * with a negative count and throw a `RangeError`). We therefore require a
340
+ * positive column count before declaring the TTY usable.
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * if (canUseTTY()) {
345
+ * renderProgressBar()
346
+ * }
347
+ * ```
348
+ */
349
+ function canUseTTY() {
350
+ return !!process.stdout.isTTY && (process.stdout.columns ?? 0) > 0 && !isCIEnvironment();
351
+ }
352
+ //#endregion
311
353
  //#region ../../internals/utils/src/formatters.ts
312
354
  /**
313
355
  * CLI command descriptors for each supported code formatter.
@@ -372,62 +414,8 @@ async function detectFormatter() {
372
414
  return null;
373
415
  }
374
416
  //#endregion
375
- //#region ../../internals/utils/src/fs.ts
376
- /**
377
- * Writes `data` to `path`, trimming leading/trailing whitespace before saving.
378
- * Skips the write when the trimmed content is empty or identical to what is already on disk.
379
- * Creates any missing parent directories automatically.
380
- * When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.
381
- *
382
- * @example
383
- * ```ts
384
- * await write('./src/Pet.ts', source) // writes and returns trimmed content
385
- * await write('./src/Pet.ts', source) // null — file unchanged
386
- * await write('./src/Pet.ts', ' ') // null — empty content skipped
387
- * ```
388
- */
389
- async function write(path, data, options = {}) {
390
- const trimmed = data.trim();
391
- if (trimmed === "") return null;
392
- const resolved = resolve(path);
393
- if (typeof Bun !== "undefined") {
394
- const file = Bun.file(resolved);
395
- if ((await file.exists() ? await file.text() : null) === trimmed) return null;
396
- await Bun.write(resolved, trimmed);
397
- return trimmed;
398
- }
399
- try {
400
- if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return null;
401
- } catch {}
402
- await mkdir(dirname(resolved), { recursive: true });
403
- await writeFile(resolved, trimmed, { encoding: "utf-8" });
404
- if (options.sanity) {
405
- const savedData = await readFile(resolved, { encoding: "utf-8" });
406
- if (savedData !== trimmed) throw new Error(`Sanity check failed for ${path}\n\nData[${data.length}]:\n${data}\n\nSaved[${savedData.length}]:\n${savedData}\n`);
407
- return savedData;
408
- }
409
- return trimmed;
410
- }
411
- //#endregion
412
417
  //#region ../../internals/utils/src/linters.ts
413
418
  /**
414
- * Collects all files under `dir` recursively using Node's built-in fs APIs.
415
- *
416
- * Passing explicit file paths to oxlint (instead of a directory) bypasses
417
- * oxlint's `.gitignore`-aware directory traversal, which would otherwise skip
418
- * files that are listed in `.gitignore` (e.g. generated output directories).
419
- */
420
- function findLintableFiles(dir) {
421
- try {
422
- return readdirSync(dir, {
423
- withFileTypes: true,
424
- recursive: true
425
- }).filter((d) => d.isFile()).map((d) => `${d.parentPath}/${d.name}`);
426
- } catch {
427
- return [];
428
- }
429
- }
430
- /**
431
419
  * CLI command descriptors for each supported linter.
432
420
  *
433
421
  * Each entry contains the executable `command`, an `args` factory that maps an
@@ -451,7 +439,11 @@ const linters = {
451
439
  },
452
440
  oxlint: {
453
441
  command: "oxlint",
454
- args: (outputPath) => ["--fix", ...findLintableFiles(outputPath)],
442
+ args: (outputPath) => [
443
+ "--fix",
444
+ "--no-ignore",
445
+ outputPath
446
+ ],
455
447
  errorMessage: "Oxlint not found"
456
448
  }
457
449
  };
@@ -486,112 +478,51 @@ async function detectLinter() {
486
478
  return null;
487
479
  }
488
480
  //#endregion
489
- //#region src/utils/getSummary.ts
490
- function getSummary({ failedPlugins, filesCreated, status, hrStart, config, pluginTimings }) {
491
- const duration = formatHrtime(hrStart);
492
- const pluginsCount = config.plugins?.length ?? 0;
493
- const successCount = pluginsCount - failedPlugins.size;
494
- const meta = {
495
- plugins: status === "success" ? `${styleText("green", `${successCount} successful`)}, ${pluginsCount} total` : `${styleText("green", `${successCount} successful`)}, ${styleText("red", `${failedPlugins.size} failed`)}, ${pluginsCount} total`,
496
- pluginsFailed: status === "failed" ? [...failedPlugins].map(({ plugin }) => randomCliColor(plugin.name)).join(", ") : void 0,
497
- filesCreated,
498
- time: styleText("green", duration),
499
- output: path.resolve(config.root, config.output.path)
500
- };
501
- const labels = {
502
- plugins: "Plugins:",
503
- failed: "Failed:",
504
- generated: "Generated:",
505
- pluginTimings: "Plugin Timings:",
506
- output: "Output:"
507
- };
508
- const maxLength = Math.max(0, ...[...Object.values(labels), ...pluginTimings ? Array.from(pluginTimings.keys()) : []].map((s) => s.length));
509
- const summaryLines = [];
510
- summaryLines.push(`${labels.plugins.padEnd(maxLength + 2)} ${meta.plugins}`);
511
- if (meta.pluginsFailed) summaryLines.push(`${labels.failed.padEnd(maxLength + 2)} ${meta.pluginsFailed}`);
512
- summaryLines.push(`${labels.generated.padEnd(maxLength + 2)} ${meta.filesCreated} files in ${meta.time}`);
513
- if (pluginTimings && pluginTimings.size > 0) {
514
- const sortedTimings = Array.from(pluginTimings.entries()).sort((a, b) => b[1] - a[1]);
515
- summaryLines.push(`${labels.pluginTimings}`);
516
- sortedTimings.forEach(([name, time]) => {
517
- const timeStr = time >= 1e3 ? `${(time / 1e3).toFixed(2)}s` : `${Math.round(time)}ms`;
518
- const barLength = Math.min(Math.ceil(time / 100), 10);
519
- const bar = styleText("dim", "█".repeat(barLength));
520
- summaryLines.push(`${styleText("dim", "•")} ${name.padEnd(maxLength + 1)}${bar} ${timeStr}`);
521
- });
522
- }
523
- summaryLines.push(`${labels.output.padEnd(maxLength + 2)} ${meta.output}`);
524
- return summaryLines;
481
+ //#region ../../internals/utils/src/network.ts
482
+ /**
483
+ * Well-known stable domains used as DNS probes to check internet connectivity.
484
+ */
485
+ const TEST_DOMAINS = [
486
+ "dns.google.com",
487
+ "cloudflare.com",
488
+ "one.one.one.one"
489
+ ];
490
+ /**
491
+ * Returns `true` when the system has internet connectivity.
492
+ * Probes DNS resolution against well-known stable domains.
493
+ *
494
+ * @example
495
+ * ```ts
496
+ * if (await isOnline()) {
497
+ * await fetchLatestVersion()
498
+ * }
499
+ * ```
500
+ */
501
+ async function isOnline() {
502
+ for (const domain of TEST_DOMAINS) try {
503
+ await promises.resolve(domain);
504
+ return true;
505
+ } catch {}
506
+ return false;
525
507
  }
526
- //#endregion
527
- //#region src/utils/runHook.ts
528
508
  /**
529
- * Executes a hook command, emits debug and completion events, and forwards output to an optional sink.
509
+ * Executes `fn` only when the system is online. Returns `null` when offline or on error.
510
+ *
511
+ * @example
512
+ * ```ts
513
+ * const version = await executeIfOnline(() => fetchLatestVersion('kubb'))
514
+ * // null when offline
515
+ * ```
530
516
  */
531
- async function runHook({ id, command, args, commandWithArgs, context, stream = false, sink }) {
517
+ async function executeIfOnline(fn) {
518
+ if (!await isOnline()) return null;
532
519
  try {
533
- const proc = x(command, [...args ?? []], {
534
- nodeOptions: { detached: process.platform !== "win32" },
535
- throwOnError: true
536
- });
537
- if (stream && sink?.onLine) for await (const line of proc) sink.onLine(line);
538
- const result = await proc;
539
- await context.emit("kubb:debug", {
540
- date: /* @__PURE__ */ new Date(),
541
- logs: [result.stdout.trimEnd()]
542
- });
543
- await context.emit("kubb:hook:end", {
544
- command,
545
- args,
546
- id,
547
- success: true,
548
- error: null
549
- });
550
- } catch (err) {
551
- if (!(err instanceof NonZeroExitError)) {
552
- await context.emit("kubb:hook:end", {
553
- command,
554
- args,
555
- id,
556
- success: false,
557
- error: toError(err)
558
- });
559
- await context.emit("kubb:error", { error: toError(err) });
560
- return;
561
- }
562
- const stderr = err.output?.stderr ?? "";
563
- const stdout = err.output?.stdout ?? "";
564
- await context.emit("kubb:debug", {
565
- date: /* @__PURE__ */ new Date(),
566
- logs: [stdout, stderr].filter(Boolean)
567
- });
568
- if (stderr) sink?.onStderr?.(stderr);
569
- if (stdout) sink?.onStdout?.(stdout);
570
- const errorMessage = /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`);
571
- await context.emit("kubb:hook:end", {
572
- command,
573
- args,
574
- id,
575
- success: false,
576
- error: errorMessage
577
- });
578
- await context.emit("kubb:error", { error: errorMessage });
520
+ return await fn();
521
+ } catch {
522
+ return null;
579
523
  }
580
524
  }
581
525
  //#endregion
582
- //#region src/utils/Writables.ts
583
- var ClackWritable = class extends Writable {
584
- taskLog;
585
- constructor(taskLog, opts) {
586
- super(opts);
587
- this.taskLog = taskLog;
588
- }
589
- _write(chunk, _encoding, callback) {
590
- this.taskLog.message(`${styleText("dim", chunk.toString())}`);
591
- callback();
592
- }
593
- };
594
- //#endregion
595
526
  //#region src/loggers/clackLogger.ts
596
527
  /**
597
528
  * TTY logger with beautiful UI and progress indicators for local development.
@@ -599,84 +530,101 @@ var ClackWritable = class extends Writable {
599
530
  const clackLogger = defineLogger({
600
531
  name: "clack",
601
532
  install(context, options) {
602
- const logLevel$8 = options?.logLevel ?? logLevel.info;
533
+ const logLevel$6 = options?.logLevel ?? logLevel.info;
603
534
  const state = {
604
- totalPlugins: 0,
605
- completedPlugins: 0,
606
- failedPlugins: 0,
607
- totalFiles: 0,
608
- processedFiles: 0,
609
- hrStart: process$1.hrtime(),
535
+ ...createProgressCounters(),
610
536
  spinner: clack.spinner(),
611
537
  isSpinning: false,
612
- activeProgress: /* @__PURE__ */ new Map()
538
+ runningPlugins: /* @__PURE__ */ new Set(),
539
+ activeProgress: /* @__PURE__ */ new Map(),
540
+ activeHookLogs: /* @__PURE__ */ new Map()
613
541
  };
614
- function reset() {
615
- for (const [_key, active] of state.activeProgress) {
542
+ function stopActiveProgress() {
543
+ for (const [, active] of state.activeProgress) {
616
544
  if (active.interval) clearInterval(active.interval);
617
545
  active.progressBar?.stop();
618
546
  }
619
- state.totalPlugins = 0;
620
- state.completedPlugins = 0;
621
- state.failedPlugins = 0;
622
- state.totalFiles = 0;
623
- state.processedFiles = 0;
624
- state.hrStart = process$1.hrtime();
547
+ state.activeProgress.clear();
548
+ }
549
+ function reset() {
550
+ stopActiveProgress();
551
+ resetProgressCounters(state);
625
552
  state.spinner = clack.spinner();
626
553
  state.isSpinning = false;
627
- state.activeProgress.clear();
554
+ state.runningPlugins.clear();
555
+ state.activeHookLogs.clear();
556
+ }
557
+ function pluginProgressText() {
558
+ const running = [...state.runningPlugins].map((name) => styleText("bold", name));
559
+ return getMessage(running.length > 0 ? `Generating ${running.join(", ")}` : "Generating plugins");
628
560
  }
629
561
  function showProgressStep() {
630
- if (logLevel$8 <= logLevel.silent) return;
562
+ if (logLevel$6 <= logLevel.silent) return;
631
563
  const line = buildProgressLine(state);
632
564
  if (line) clack.log.step(getMessage(line));
633
565
  }
634
566
  function getMessage(message) {
635
- return formatMessage(message, logLevel$8);
567
+ return formatMessage(message, logLevel$6);
568
+ }
569
+ function onStep(event, message) {
570
+ context.on(event, () => {
571
+ if (logLevel$6 <= logLevel.silent) return;
572
+ clack.log.step(getMessage(message));
573
+ });
636
574
  }
637
575
  function startSpinner(text) {
638
576
  state.spinner.start(text);
639
577
  state.isSpinning = true;
640
578
  }
641
579
  function stopSpinner(text) {
580
+ if (!state.isSpinning) return;
642
581
  state.spinner.stop(text);
643
582
  state.isSpinning = false;
644
583
  }
645
584
  context.on("kubb:info", ({ message, info = "" }) => {
646
- if (logLevel$8 <= logLevel.silent) return;
585
+ if (logLevel$6 <= logLevel.silent) return;
647
586
  const text = getMessage([
648
587
  styleText("blue", "ℹ"),
649
588
  message,
650
- styleText("dim", info)
651
- ].join(" "));
652
- if (state.isSpinning) state.spinner.message(text);
653
- else clack.log.info(text);
589
+ info ? styleText("dim", info) : void 0
590
+ ].filter(Boolean).join(" "));
591
+ if (state.isSpinning) {
592
+ state.spinner.message(text);
593
+ return;
594
+ }
595
+ clack.log.info(text);
654
596
  });
655
597
  context.on("kubb:success", ({ message, info = "" }) => {
656
- if (logLevel$8 <= logLevel.silent) return;
598
+ if (logLevel$6 <= logLevel.silent) return;
657
599
  const text = getMessage([
658
600
  styleText("blue", "✓"),
659
601
  message,
660
- logLevel$8 >= logLevel.info ? styleText("dim", info) : void 0
602
+ logLevel$6 >= logLevel.info ? styleText("dim", info) : void 0
661
603
  ].filter(Boolean).join(" "));
662
- if (state.isSpinning) stopSpinner(text);
663
- else clack.log.success(text);
604
+ if (state.isSpinning) {
605
+ stopSpinner(text);
606
+ return;
607
+ }
608
+ clack.log.success(text);
664
609
  });
665
610
  context.on("kubb:warn", ({ message, info }) => {
666
- if (logLevel$8 < logLevel.warn) return;
611
+ if (logLevel$6 < logLevel.warn) return;
667
612
  const text = getMessage([
668
613
  styleText("yellow", "⚠"),
669
614
  message,
670
- logLevel$8 >= logLevel.info && info ? styleText("dim", info) : void 0
615
+ logLevel$6 >= logLevel.info && info ? styleText("dim", info) : void 0
671
616
  ].filter(Boolean).join(" "));
672
617
  clack.log.warn(text);
673
618
  });
674
619
  context.on("kubb:error", ({ error }) => {
675
620
  const caused = toCause(error);
676
621
  const text = [styleText("red", "✗"), error.message].join(" ");
677
- if (state.isSpinning) stopSpinner(getMessage(text));
678
- else clack.log.error(getMessage(text));
679
- if (logLevel$8 >= logLevel.debug && error.stack) {
622
+ if (state.isSpinning) {
623
+ stopSpinner(getMessage(text));
624
+ return;
625
+ }
626
+ clack.log.error(getMessage(text));
627
+ if (logLevel$6 >= logLevel.verbose && error.stack) {
680
628
  const frames = error.stack.split("\n").slice(1, 4);
681
629
  for (const frame of frames) clack.log.message(getMessage(styleText("dim", frame.trim())));
682
630
  if (caused?.stack) {
@@ -686,10 +634,12 @@ const clackLogger = defineLogger({
686
634
  }
687
635
  }
688
636
  });
689
- context.on("kubb:version:new", ({ currentVersion, latestVersion }) => {
690
- if (logLevel$8 <= logLevel.silent) return;
691
- try {
692
- clack.box(`\`v${currentVersion}\` → \`v${latestVersion}\`
637
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
638
+ if (logLevel$6 <= logLevel.silent && diagnostic.severity !== "error") return;
639
+ stopSpinner();
640
+ stopActiveProgress();
641
+ if (Diagnostics.isUpdate(diagnostic)) {
642
+ clack.box(`\`v${diagnostic.currentVersion}\` → \`v${diagnostic.latestVersion}\`
693
643
  Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
694
644
  width: "auto",
695
645
  formatBorder: (s) => styleText("yellow", s),
@@ -698,14 +648,14 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
698
648
  contentAlign: "center",
699
649
  titleAlign: "center"
700
650
  });
701
- } catch {
702
- console.log(`Update available for Kubb: v${currentVersion} → v${latestVersion}`);
703
- console.log("Run `npm install -g @kubb/cli` to update");
651
+ return;
704
652
  }
653
+ const { symbol, headline, details } = Diagnostics.format(diagnostic);
654
+ clack.log.message([headline, ...details], { symbol });
705
655
  });
706
656
  context.on("kubb:lifecycle:start", async ({ version }) => {
707
657
  console.log(`\n${getIntro({
708
- title: "The ultimate toolkit for working with APIs",
658
+ title: "The meta framework for code generation",
709
659
  description: "Ready to start",
710
660
  version,
711
661
  areEyesOpen: true
@@ -713,55 +663,56 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
713
663
  reset();
714
664
  });
715
665
  context.on("kubb:config:start", () => {
716
- if (logLevel$8 <= logLevel.silent) return;
666
+ if (logLevel$6 <= logLevel.silent) return;
717
667
  const text = getMessage("Configuration started");
718
668
  clack.intro(text);
719
669
  startSpinner(getMessage("Configuration loading"));
720
670
  });
721
671
  context.on("kubb:config:end", () => {
722
- if (logLevel$8 <= logLevel.silent) return;
672
+ if (logLevel$6 <= logLevel.silent) return;
723
673
  const text = getMessage("Configuration completed");
724
674
  clack.outro(text);
725
675
  });
726
676
  context.on("kubb:generation:start", ({ config }) => {
727
677
  reset();
728
678
  state.totalPlugins = config.plugins?.length ?? 0;
679
+ if (logLevel$6 <= logLevel.silent) return;
729
680
  const text = getMessage(["Generation started", config.name ? `for ${styleText("dim", config.name)}` : void 0].filter(Boolean).join(" "));
730
681
  clack.intro(text);
731
682
  });
732
683
  context.on("kubb:plugin:start", ({ plugin }) => {
733
- if (logLevel$8 <= logLevel.silent) return;
684
+ if (logLevel$6 <= logLevel.silent) return;
734
685
  stopSpinner();
686
+ state.runningPlugins.add(plugin.name);
687
+ const active = state.activeProgress.get("plugins");
688
+ if (active) {
689
+ active.progressBar.advance(0, pluginProgressText());
690
+ return;
691
+ }
735
692
  const progressBar = clack.progress({
736
693
  style: "block",
737
- max: 100,
694
+ max: Math.max(state.totalPlugins, 1),
738
695
  size: 30
739
696
  });
740
- const text = getMessage(`Generating ${styleText("bold", plugin.name)}`);
741
- progressBar.start(text);
742
- const interval = setInterval(() => {
743
- progressBar.advance();
744
- }, 100);
745
- state.activeProgress.set(plugin.name, {
746
- progressBar,
747
- interval
748
- });
697
+ progressBar.start(pluginProgressText());
698
+ progressBar.advance(state.completedPlugins + state.failedPlugins, pluginProgressText());
699
+ state.activeProgress.set("plugins", { progressBar });
749
700
  });
750
- context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
701
+ context.on("kubb:plugin:end", ({ plugin, success }) => {
751
702
  stopSpinner();
752
- const active = state.activeProgress.get(plugin.name);
753
- if (!active || logLevel$8 === logLevel.silent) return;
754
- clearInterval(active.interval);
755
- if (success) state.completedPlugins++;
756
- else state.failedPlugins++;
757
- const durationStr = formatMsWithColor(duration);
758
- const text = getMessage(success ? `${styleText("bold", plugin.name)} completed in ${durationStr}` : `${styleText("bold", plugin.name)} failed in ${styleText("red", formatMs(duration))}`);
759
- active.progressBar.stop(text);
760
- state.activeProgress.delete(plugin.name);
761
- showProgressStep();
703
+ const active = state.activeProgress.get("plugins");
704
+ if (!active || logLevel$6 === logLevel.silent) return;
705
+ state.runningPlugins.delete(plugin.name);
706
+ recordPluginResult(state, success);
707
+ active.progressBar.advance(1, pluginProgressText());
708
+ if (state.runningPlugins.size === 0) {
709
+ active.progressBar.stop(getMessage("Plugins generated"));
710
+ state.activeProgress.delete("plugins");
711
+ showProgressStep();
712
+ }
762
713
  });
763
714
  context.on("kubb:files:processing:start", ({ files }) => {
764
- if (logLevel$8 <= logLevel.silent) return;
715
+ if (logLevel$6 <= logLevel.silent) return;
765
716
  stopSpinner();
766
717
  state.totalFiles = files.length;
767
718
  state.processedFiles = 0;
@@ -775,17 +726,17 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
775
726
  progressBar.start(getMessage(text));
776
727
  state.activeProgress.set("files", { progressBar });
777
728
  });
778
- context.on("kubb:file:processing:update", ({ file, config }) => {
779
- if (logLevel$8 <= logLevel.silent) return;
729
+ context.on("kubb:files:processing:update", ({ files }) => {
730
+ if (logLevel$6 <= logLevel.silent) return;
780
731
  stopSpinner();
781
- state.processedFiles++;
782
- const text = `Writing ${relative(config.root, file.path)}`;
783
732
  const active = state.activeProgress.get("files");
784
- if (!active) return;
785
- active.progressBar.advance(void 0, text);
733
+ for (const { file, config } of files) {
734
+ state.processedFiles++;
735
+ if (active) active.progressBar.advance(void 0, `Writing ${relative(config.root, file.path)}`);
736
+ }
786
737
  });
787
738
  context.on("kubb:files:processing:end", () => {
788
- if (logLevel$8 <= logLevel.silent) return;
739
+ if (logLevel$6 <= logLevel.silent) return;
789
740
  stopSpinner();
790
741
  const text = getMessage("Files written successfully");
791
742
  const active = state.activeProgress.get("files");
@@ -795,202 +746,54 @@ Run \`npm install -g @kubb/cli\` to update`, "Update available for `Kubb`", {
795
746
  showProgressStep();
796
747
  });
797
748
  context.on("kubb:generation:end", ({ config }) => {
749
+ stopSpinner();
798
750
  const text = getMessage(config.name ? `Generation completed for ${styleText("dim", config.name)}` : "Generation completed");
799
751
  clack.outro(text);
800
752
  });
801
- context.on("kubb:format:start", () => {
802
- if (logLevel$8 <= logLevel.silent) return;
803
- const text = getMessage("Format started");
804
- clack.intro(text);
805
- });
806
- context.on("kubb:format:end", () => {
807
- if (logLevel$8 <= logLevel.silent) return;
808
- const text = getMessage("Format completed");
809
- clack.outro(text);
810
- });
811
- context.on("kubb:lint:start", () => {
812
- if (logLevel$8 <= logLevel.silent) return;
813
- const text = getMessage("Lint started");
814
- clack.intro(text);
815
- });
816
- context.on("kubb:lint:end", () => {
817
- if (logLevel$8 <= logLevel.silent) return;
818
- const text = getMessage("Lint completed");
819
- clack.outro(text);
820
- });
821
- context.on("kubb:hook:start", async ({ id, command, args }) => {
822
- const commandWithArgs = formatCommandWithArgs(command, args);
823
- const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} started`);
824
- if (!id) return;
825
- if (logLevel$8 <= logLevel.silent) {
826
- await runHook({
827
- id,
828
- command,
829
- args,
830
- commandWithArgs,
831
- context,
832
- sink: {
833
- onStderr: (s) => console.error(s),
834
- onStdout: (s) => console.log(s)
835
- }
836
- });
837
- return;
838
- }
839
- clack.intro(text);
840
- const logger = clack.taskLog({ title: getMessage(["Executing hook", logLevel$8 >= logLevel.info ? styleText("dim", commandWithArgs) : void 0].filter(Boolean).join(" ")) });
841
- const writable = new ClackWritable(logger);
842
- await runHook({
843
- id,
844
- command,
845
- args,
846
- commandWithArgs,
847
- context,
848
- stream: true,
849
- sink: {
850
- onLine: (line) => writable.write(line),
851
- onStderr: (s) => logger.error(s),
852
- onStdout: (s) => logger.message(s)
853
- }
753
+ onStep("kubb:format:start", "Formatting");
754
+ onStep("kubb:lint:start", "Linting");
755
+ onStep("kubb:hooks:start", "Running hooks");
756
+ context.on("kubb:hook:start", ({ id, command, args }) => {
757
+ if (logLevel$6 <= logLevel.silent || !id) return;
758
+ stopSpinner();
759
+ const title = getMessage(`Running ${styleText("dim", formatCommandWithArgs(command, args))}`);
760
+ const taskLog = clack.taskLog({ title });
761
+ state.activeHookLogs.set(id, {
762
+ taskLog,
763
+ hrStart: process$1.hrtime()
854
764
  });
855
765
  });
856
- context.on("kubb:hook:end", ({ command, args }) => {
857
- if (logLevel$8 <= logLevel.silent) return;
858
- const text = getMessage(`Hook ${styleText("dim", formatCommandWithArgs(command, args))} successfully executed`);
859
- clack.outro(text);
860
- });
861
- context.on("kubb:generation:summary", ({ config, pluginTimings, failedPlugins, filesCreated, status, hrStart }) => {
862
- const summary = getSummary({
863
- failedPlugins,
864
- filesCreated,
865
- config,
866
- status,
867
- hrStart,
868
- pluginTimings: logLevel$8 >= logLevel.verbose ? pluginTimings : void 0
869
- });
870
- const title = config.name || "";
871
- summary.unshift("\n");
872
- summary.push("\n");
873
- const borderColor = status === "success" ? "green" : "red";
874
- try {
875
- clack.box(summary.join("\n"), getMessage(title), {
876
- width: "auto",
877
- formatBorder: (s) => styleText(borderColor, s),
878
- rounded: true,
879
- withGuide: false,
880
- contentAlign: "left",
881
- titleAlign: "center"
882
- });
883
- } catch {
884
- console.log(summary.join("\n"));
766
+ context.on("kubb:hook:end", ({ id, command, args, success, error }) => {
767
+ if (logLevel$6 <= logLevel.silent || !id) return;
768
+ const active = state.activeHookLogs.get(id);
769
+ if (!active) return;
770
+ state.activeHookLogs.delete(id);
771
+ const commandWithArgs = formatCommandWithArgs(command, args);
772
+ const duration = formatMsWithColor(getElapsedMs(active.hrStart));
773
+ if (success) active.taskLog.success(getMessage(`${styleText("dim", commandWithArgs)} completed in ${duration}`));
774
+ else {
775
+ const reason = error?.message ? ` (${error.message})` : "";
776
+ active.taskLog.error(getMessage(`${styleText("dim", commandWithArgs)} failed${reason}`), { showLog: true });
885
777
  }
886
778
  });
887
779
  context.on("kubb:lifecycle:end", () => {
888
780
  reset();
889
781
  });
890
- }
891
- });
892
- //#endregion
893
- //#region src/loggers/fileSystemLogger.ts
894
- /**
895
- * FileSystem logger that captures debug events and writes them to `.kubb` directory files.
896
- * Note: Logs write on `lifecycle:end` or process exit. Cached logs may be lost if the process crashes before these events.
897
- */
898
- const fileSystemLogger = defineLogger({
899
- name: "filesystem",
900
- install(context) {
901
- const state = {
902
- cachedLogs: /* @__PURE__ */ new Set(),
903
- startDate: Date.now()
904
- };
905
- function reset() {
906
- state.cachedLogs = /* @__PURE__ */ new Set();
907
- state.startDate = Date.now();
908
- }
909
- async function writeLogs(name) {
910
- if (state.cachedLogs.size === 0) return [];
911
- const files = {};
912
- for (const log of state.cachedLogs) {
913
- const baseName = log.fileName || `${[
914
- "kubb",
915
- name,
916
- state.startDate
917
- ].filter(Boolean).join("-")}.log`;
918
- const pathName = resolve(process$1.cwd(), ".kubb", baseName);
919
- if (!files[pathName]) files[pathName] = [];
920
- if (log.logs.length > 0) {
921
- const timestamp = log.date.toLocaleString();
922
- files[pathName].push(`[${timestamp}]\n${log.logs.join("\n")}`);
923
- }
924
- }
925
- for (const [fileName, logs] of Object.entries(files)) await write(fileName, logs.join("\n\n"));
926
- return Object.keys(files);
927
- }
928
- context.on("kubb:info", ({ message, info }) => {
929
- state.cachedLogs.add({
930
- date: /* @__PURE__ */ new Date(),
931
- logs: [`ℹ ${message} ${info}`]
932
- });
933
- });
934
- context.on("kubb:success", ({ message, info }) => {
935
- state.cachedLogs.add({
936
- date: /* @__PURE__ */ new Date(),
937
- logs: [`✓ ${message} ${info}`]
938
- });
939
- });
940
- context.on("kubb:warn", ({ message, info }) => {
941
- state.cachedLogs.add({
942
- date: /* @__PURE__ */ new Date(),
943
- logs: [`⚠ ${message} ${info}`]
944
- });
945
- });
946
- context.on("kubb:error", ({ error }) => {
947
- state.cachedLogs.add({
948
- date: /* @__PURE__ */ new Date(),
949
- logs: [`✗ ${error.message}`, error.stack || "unknown stack"]
950
- });
951
- });
952
- context.on("kubb:debug", (message) => {
953
- state.cachedLogs.add({
954
- date: /* @__PURE__ */ new Date(),
955
- logs: message.logs
956
- });
957
- });
958
- context.on("kubb:plugin:start", ({ plugin }) => {
959
- state.cachedLogs.add({
960
- date: /* @__PURE__ */ new Date(),
961
- logs: [`Generating ${plugin.name}`]
962
- });
963
- });
964
- context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
965
- const durationStr = formatMs(duration);
966
- state.cachedLogs.add({
967
- date: /* @__PURE__ */ new Date(),
968
- logs: [success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`]
969
- });
970
- });
971
- context.on("kubb:files:processing:start", ({ files }) => {
972
- state.cachedLogs.add({
973
- date: /* @__PURE__ */ new Date(),
974
- logs: [`Start ${files.length} writing:`, ...files.map((file) => file.path)]
975
- });
976
- });
977
- context.on("kubb:generation:end", async ({ config }) => {
978
- const writtenFilePaths = await writeLogs(config.name);
979
- if (writtenFilePaths.length > 0) {
980
- const files = writtenFilePaths.map((f) => relative(process$1.cwd(), f));
981
- await context.emit("kubb:info", {
982
- message: "Debug files written to:",
983
- info: files.join(", ")
984
- });
985
- }
986
- reset();
987
- });
988
- const exitHandler = () => {
989
- if (state.cachedLogs.size > 0) writeLogs().catch(() => {});
782
+ return (_commandWithArgs, hookId) => {
783
+ if (logLevel$6 <= logLevel.silent) return {
784
+ onStdout: (s) => console.log(s),
785
+ onStderr: (s) => console.error(s)
786
+ };
787
+ const active = state.activeHookLogs.get(hookId);
788
+ if (!active) return null;
789
+ const { taskLog } = active;
790
+ return {
791
+ stream: true,
792
+ onLine: (line) => taskLog.message(styleText("dim", line)),
793
+ onStdout: (s) => taskLog.message(s),
794
+ onStderr: (s) => taskLog.message(styleText("red", s))
795
+ };
990
796
  };
991
- process$1.once("exit", exitHandler);
992
- process$1.once("SIGINT", exitHandler);
993
- process$1.once("SIGTERM", exitHandler);
994
797
  }
995
798
  });
996
799
  //#endregion
@@ -1001,41 +804,57 @@ const fileSystemLogger = defineLogger({
1001
804
  const githubActionsLogger = defineLogger({
1002
805
  name: "github-actions",
1003
806
  install(context, options) {
1004
- const logLevel$7 = options?.logLevel ?? logLevel.info;
807
+ const logLevel$5 = options?.logLevel ?? logLevel.info;
1005
808
  const state = {
1006
- totalPlugins: 0,
1007
- completedPlugins: 0,
1008
- failedPlugins: 0,
1009
- totalFiles: 0,
1010
- processedFiles: 0,
1011
- hrStart: process.hrtime(),
1012
- currentConfigs: []
809
+ ...createProgressCounters(),
810
+ currentConfigs: [],
811
+ openGroupDepth: 0
1013
812
  };
813
+ const hookTimer = createHookTimer();
1014
814
  function reset() {
1015
- state.totalPlugins = 0;
1016
- state.completedPlugins = 0;
1017
- state.failedPlugins = 0;
1018
- state.totalFiles = 0;
1019
- state.processedFiles = 0;
1020
- state.hrStart = process.hrtime();
815
+ closeAllGroups();
816
+ resetProgressCounters(state);
1021
817
  state.currentConfigs = [];
818
+ hookTimer.clear();
1022
819
  }
1023
820
  function showProgressStep() {
1024
- if (logLevel$7 <= logLevel.silent) return;
821
+ if (logLevel$5 <= logLevel.silent) return;
1025
822
  const line = buildProgressLine(state);
1026
823
  if (line) console.log(getMessage(line));
1027
824
  }
1028
825
  function getMessage(message) {
1029
- return formatMessage(message, logLevel$7);
826
+ return formatMessage(message, logLevel$5);
1030
827
  }
1031
828
  function openGroup(name) {
1032
829
  console.log(`::group::${name}`);
830
+ state.openGroupDepth++;
1033
831
  }
1034
832
  function closeGroup(_name) {
1035
833
  console.log("::endgroup::");
834
+ if (state.openGroupDepth > 0) state.openGroupDepth--;
835
+ }
836
+ function closeAllGroups() {
837
+ while (state.openGroupDepth > 0) {
838
+ console.log("::endgroup::");
839
+ state.openGroupDepth--;
840
+ }
841
+ }
842
+ function onGroupStart(event, message, group) {
843
+ context.on(event, () => {
844
+ if (logLevel$5 <= logLevel.silent) return;
845
+ if (state.currentConfigs.length === 1) openGroup(group);
846
+ console.log(getMessage(message));
847
+ });
848
+ }
849
+ function onGroupEnd(event, message, group) {
850
+ context.on(event, () => {
851
+ if (logLevel$5 <= logLevel.silent) return;
852
+ console.log(getMessage(message));
853
+ if (state.currentConfigs.length === 1) closeGroup(group);
854
+ });
1036
855
  }
1037
856
  context.on("kubb:info", ({ message, info = "" }) => {
1038
- if (logLevel$7 <= logLevel.silent) return;
857
+ if (logLevel$5 <= logLevel.silent) return;
1039
858
  const text = getMessage([
1040
859
  styleText("blue", "ℹ"),
1041
860
  message,
@@ -1044,29 +863,29 @@ const githubActionsLogger = defineLogger({
1044
863
  console.log(text);
1045
864
  });
1046
865
  context.on("kubb:success", ({ message, info = "" }) => {
1047
- if (logLevel$7 <= logLevel.silent) return;
866
+ if (logLevel$5 <= logLevel.silent) return;
1048
867
  const text = getMessage([
1049
868
  styleText("blue", "✓"),
1050
869
  message,
1051
- logLevel$7 >= logLevel.info ? styleText("dim", info) : void 0
870
+ logLevel$5 >= logLevel.info ? styleText("dim", info) : void 0
1052
871
  ].filter(Boolean).join(" "));
1053
872
  console.log(text);
1054
873
  });
1055
874
  context.on("kubb:warn", ({ message, info = "" }) => {
1056
- if (logLevel$7 <= logLevel.silent) return;
875
+ if (logLevel$5 <= logLevel.silent) return;
1057
876
  const text = getMessage([
1058
877
  styleText("yellow", "⚠"),
1059
878
  message,
1060
- logLevel$7 >= logLevel.info ? styleText("dim", info) : void 0
879
+ logLevel$5 >= logLevel.info ? styleText("dim", info) : void 0
1061
880
  ].filter(Boolean).join(" "));
1062
881
  console.warn(`::warning::${text}`);
1063
882
  });
1064
883
  context.on("kubb:error", ({ error }) => {
1065
884
  const caused = toCause(error);
1066
- if (logLevel$7 <= logLevel.silent) return;
885
+ closeAllGroups();
1067
886
  const message = error.message || String(error);
1068
887
  console.error(`::error::${message}`);
1069
- if (logLevel$7 >= logLevel.debug && error.stack) {
888
+ if (logLevel$5 >= logLevel.verbose && error.stack) {
1070
889
  const frames = error.stack.split("\n").slice(1, 4);
1071
890
  for (const frame of frames) console.log(getMessage(styleText("dim", frame.trim())));
1072
891
  if (caused?.stack) {
@@ -1076,19 +895,33 @@ const githubActionsLogger = defineLogger({
1076
895
  }
1077
896
  }
1078
897
  });
898
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
899
+ closeAllGroups();
900
+ if (logLevel$5 <= logLevel.silent && diagnostic.severity !== "error") return;
901
+ if (!Diagnostics.isProblem(diagnostic)) {
902
+ console.log(`::notice::${diagnostic.message}`);
903
+ return;
904
+ }
905
+ const parts = [`${diagnostic.code} ${diagnostic.message}`];
906
+ if (diagnostic.location && "pointer" in diagnostic.location) parts.push(`(at ${diagnostic.location.pointer})`);
907
+ if (diagnostic.plugin) parts.push(`[plugin: ${diagnostic.plugin}]`);
908
+ if (diagnostic.help) parts.push(`help: ${diagnostic.help}`);
909
+ if (diagnostic.code !== Diagnostics.code.unknown) parts.push(`docs: ${Diagnostics.docsUrl(diagnostic.code)}`);
910
+ console.error(`::error::${parts.join(" ")}`);
911
+ });
1079
912
  context.on("kubb:lifecycle:start", ({ version }) => {
1080
913
  console.log(styleText("yellow", `Kubb ${version} 🧩`));
1081
914
  reset();
1082
915
  });
1083
916
  context.on("kubb:config:start", () => {
1084
- if (logLevel$7 <= logLevel.silent) return;
917
+ if (logLevel$5 <= logLevel.silent) return;
1085
918
  const text = getMessage("Configuration started");
1086
919
  openGroup("Configuration");
1087
920
  console.log(text);
1088
921
  });
1089
922
  context.on("kubb:config:end", ({ configs }) => {
1090
923
  state.currentConfigs = configs;
1091
- if (logLevel$7 <= logLevel.silent) return;
924
+ if (logLevel$5 <= logLevel.silent) return;
1092
925
  const text = getMessage("Configuration completed");
1093
926
  console.log(text);
1094
927
  closeGroup("Configuration");
@@ -1101,15 +934,14 @@ const githubActionsLogger = defineLogger({
1101
934
  if (state.currentConfigs.length === 1) console.log(getMessage(text));
1102
935
  });
1103
936
  context.on("kubb:plugin:start", ({ plugin }) => {
1104
- if (logLevel$7 <= logLevel.silent) return;
937
+ if (logLevel$5 <= logLevel.silent) return;
1105
938
  const text = getMessage(`Generating ${styleText("bold", plugin.name)}`);
1106
939
  if (state.currentConfigs.length === 1) openGroup(`Plugin: ${plugin.name}`);
1107
940
  console.log(text);
1108
941
  });
1109
942
  context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
1110
- if (logLevel$7 <= logLevel.silent) return;
1111
- if (success) state.completedPlugins++;
1112
- else state.failedPlugins++;
943
+ if (logLevel$5 <= logLevel.silent) return;
944
+ recordPluginResult(state, success);
1113
945
  const durationStr = formatMsWithColor(duration);
1114
946
  const text = getMessage(success ? `${styleText("bold", plugin.name)} completed in ${durationStr}` : `${styleText("bold", plugin.name)} failed in ${styleText("red", formatMs(duration))}`);
1115
947
  console.log(text);
@@ -1118,7 +950,7 @@ const githubActionsLogger = defineLogger({
1118
950
  showProgressStep();
1119
951
  });
1120
952
  context.on("kubb:files:processing:start", ({ files }) => {
1121
- if (logLevel$7 <= logLevel.silent) return;
953
+ if (logLevel$5 <= logLevel.silent) return;
1122
954
  state.totalFiles = files.length;
1123
955
  state.processedFiles = 0;
1124
956
  if (state.currentConfigs.length === 1) openGroup("File Generation");
@@ -1126,82 +958,54 @@ const githubActionsLogger = defineLogger({
1126
958
  console.log(text);
1127
959
  });
1128
960
  context.on("kubb:files:processing:end", () => {
1129
- if (logLevel$7 <= logLevel.silent) return;
961
+ if (logLevel$5 <= logLevel.silent) return;
1130
962
  const text = getMessage("Files written successfully");
1131
963
  console.log(text);
1132
964
  if (state.currentConfigs.length === 1) closeGroup("File Generation");
1133
965
  showProgressStep();
1134
966
  });
1135
- context.on("kubb:file:processing:update", () => {
1136
- if (logLevel$7 <= logLevel.silent) return;
1137
- state.processedFiles++;
967
+ context.on("kubb:files:processing:update", ({ files }) => {
968
+ if (logLevel$5 <= logLevel.silent) return;
969
+ state.processedFiles += files.length;
1138
970
  });
1139
971
  context.on("kubb:generation:end", ({ config }) => {
1140
972
  const text = getMessage(config.name ? `${styleText("blue", "✓")} Generation completed for ${styleText("dim", config.name)}` : `${styleText("blue", "✓")} Generation completed`);
1141
973
  console.log(text);
974
+ if (state.currentConfigs.length > 1) closeGroup(config.name ? `Generation for ${styleText("bold", config.name)}` : "Generation");
1142
975
  });
1143
- context.on("kubb:format:start", () => {
1144
- if (logLevel$7 <= logLevel.silent) return;
1145
- const text = getMessage("Format started");
1146
- if (state.currentConfigs.length === 1) openGroup("Formatting");
1147
- console.log(text);
1148
- });
1149
- context.on("kubb:format:end", () => {
1150
- if (logLevel$7 <= logLevel.silent) return;
1151
- const text = getMessage("Format completed");
1152
- console.log(text);
1153
- if (state.currentConfigs.length === 1) closeGroup("Formatting");
1154
- });
1155
- context.on("kubb:lint:start", () => {
1156
- if (logLevel$7 <= logLevel.silent) return;
1157
- const text = getMessage("Lint started");
1158
- if (state.currentConfigs.length === 1) openGroup("Linting");
1159
- console.log(text);
1160
- });
1161
- context.on("kubb:lint:end", () => {
1162
- if (logLevel$7 <= logLevel.silent) return;
1163
- const text = getMessage("Lint completed");
1164
- console.log(text);
1165
- if (state.currentConfigs.length === 1) closeGroup("Linting");
1166
- });
1167
- context.on("kubb:hook:start", async ({ id, command, args }) => {
976
+ onGroupStart("kubb:format:start", "Format started", "Formatting");
977
+ onGroupEnd("kubb:format:end", "Format completed", "Formatting");
978
+ onGroupStart("kubb:lint:start", "Lint started", "Linting");
979
+ onGroupEnd("kubb:lint:end", "Lint completed", "Linting");
980
+ onGroupStart("kubb:hooks:start", "Hooks started", "Hooks");
981
+ onGroupEnd("kubb:hooks:end", "Hooks completed", "Hooks");
982
+ context.on("kubb:hook:start", ({ id, command, args }) => {
983
+ if (logLevel$5 <= logLevel.silent) return;
984
+ if (id) hookTimer.start(id);
1168
985
  const commandWithArgs = formatCommandWithArgs(command, args);
1169
986
  const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} started`);
1170
- if (logLevel$7 > logLevel.silent) {
1171
- if (state.currentConfigs.length === 1) openGroup(`Hook ${commandWithArgs}`);
1172
- console.log(text);
1173
- }
1174
- if (!id) return;
1175
- await runHook({
1176
- id,
1177
- command,
1178
- args,
1179
- commandWithArgs,
1180
- context,
1181
- sink: {
1182
- onStdout: logLevel$7 > logLevel.silent ? (s) => console.log(s) : void 0,
1183
- onStderr: logLevel$7 > logLevel.silent ? (s) => console.error(`::error::${s}`) : void 0
1184
- }
1185
- });
987
+ if (state.currentConfigs.length === 1) openGroup(`Hook ${commandWithArgs}`);
988
+ console.log(text);
1186
989
  });
1187
- context.on("kubb:hook:end", ({ command, args }) => {
1188
- if (logLevel$7 <= logLevel.silent) return;
990
+ context.on("kubb:hook:end", ({ id, command, args, success, error }) => {
991
+ if (logLevel$5 <= logLevel.silent) return;
992
+ const ms = id ? hookTimer.end(id) : void 0;
993
+ const durationStr = ms !== void 0 ? ` in ${formatMsWithColor(ms)}` : "";
1189
994
  const commandWithArgs = formatCommandWithArgs(command, args);
1190
- const text = getMessage(`Hook ${styleText("dim", commandWithArgs)} completed`);
1191
- console.log(text);
995
+ if (success) console.log(getMessage(`${styleText("green", "✓")} Hook ${styleText("dim", commandWithArgs)} completed${durationStr}`));
996
+ else {
997
+ const reason = error?.message ? ` (${error.message})` : "";
998
+ console.log(`::error::Hook ${commandWithArgs} failed${durationStr}${reason}`);
999
+ }
1192
1000
  if (state.currentConfigs.length === 1) closeGroup(`Hook ${commandWithArgs}`);
1193
1001
  });
1194
- context.on("kubb:generation:summary", ({ config, status, hrStart, failedPlugins }) => {
1195
- const pluginsCount = config.plugins?.length ?? 0;
1196
- const successCount = pluginsCount - failedPlugins.size;
1197
- const duration = formatHrtime(hrStart);
1198
- if (state.currentConfigs.length > 1) console.log(" ");
1199
- 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)}`);
1200
- if (state.currentConfigs.length > 1) closeGroup(config.name ? `Generation for ${styleText("bold", config.name)}` : "Generation");
1201
- });
1202
1002
  context.on("kubb:lifecycle:end", () => {
1203
1003
  reset();
1204
1004
  });
1005
+ return (_commandWithArgs, _hookId) => ({
1006
+ onStdout: logLevel$5 > logLevel.silent ? (s) => console.log(s) : void 0,
1007
+ onStderr: logLevel$5 > logLevel.silent ? (s) => console.error(`::error::${s}`) : void 0
1008
+ });
1205
1009
  }
1206
1010
  });
1207
1011
  //#endregion
@@ -1212,12 +1016,19 @@ const githubActionsLogger = defineLogger({
1212
1016
  const plainLogger = defineLogger({
1213
1017
  name: "plain",
1214
1018
  install(context, options) {
1215
- const logLevel$6 = options?.logLevel ?? logLevel.info;
1019
+ const logLevel$4 = options?.logLevel ?? logLevel.info;
1020
+ const hookTimer = createHookTimer();
1216
1021
  function getMessage(message) {
1217
- return formatMessage(message, logLevel$6);
1022
+ return formatMessage(message, logLevel$4);
1023
+ }
1024
+ function onStep(event, message) {
1025
+ context.on(event, () => {
1026
+ if (logLevel$4 <= logLevel.silent) return;
1027
+ console.log(getMessage(message));
1028
+ });
1218
1029
  }
1219
1030
  context.on("kubb:info", ({ message, info }) => {
1220
- if (logLevel$6 <= logLevel.silent) return;
1031
+ if (logLevel$4 <= logLevel.silent) return;
1221
1032
  const text = getMessage([
1222
1033
  "ℹ",
1223
1034
  message,
@@ -1226,20 +1037,20 @@ const plainLogger = defineLogger({
1226
1037
  console.log(text);
1227
1038
  });
1228
1039
  context.on("kubb:success", ({ message, info = "" }) => {
1229
- if (logLevel$6 <= logLevel.silent) return;
1040
+ if (logLevel$4 <= logLevel.silent) return;
1230
1041
  const text = getMessage([
1231
1042
  "✓",
1232
1043
  message,
1233
- logLevel$6 >= logLevel.info ? info : void 0
1044
+ logLevel$4 >= logLevel.info ? info : void 0
1234
1045
  ].filter(Boolean).join(" "));
1235
1046
  console.log(text);
1236
1047
  });
1237
1048
  context.on("kubb:warn", ({ message, info }) => {
1238
- if (logLevel$6 < logLevel.warn) return;
1049
+ if (logLevel$4 < logLevel.warn) return;
1239
1050
  const text = getMessage([
1240
1051
  "⚠",
1241
1052
  message,
1242
- logLevel$6 >= logLevel.info ? info : void 0
1053
+ logLevel$4 >= logLevel.info ? info : void 0
1243
1054
  ].filter(Boolean).join(" "));
1244
1055
  console.log(text);
1245
1056
  });
@@ -1247,7 +1058,7 @@ const plainLogger = defineLogger({
1247
1058
  const caused = toCause(error);
1248
1059
  const text = getMessage(["✗", error.message].join(" "));
1249
1060
  console.log(text);
1250
- if (logLevel$6 >= logLevel.debug && error.stack) {
1061
+ if (logLevel$4 >= logLevel.verbose && error.stack) {
1251
1062
  const frames = error.stack.split("\n").slice(1, 4);
1252
1063
  for (const frame of frames) console.log(getMessage(frame.trim()));
1253
1064
  if (caused?.stack) {
@@ -1257,46 +1068,41 @@ const plainLogger = defineLogger({
1257
1068
  }
1258
1069
  }
1259
1070
  });
1260
- context.on("kubb:lifecycle:start", () => {
1261
- console.log("Kubb CLI 🧩");
1262
- });
1263
- context.on("kubb:config:start", () => {
1264
- if (logLevel$6 <= logLevel.silent) return;
1265
- const text = getMessage("Configuration started");
1266
- console.log(text);
1071
+ context.on("kubb:diagnostic", ({ diagnostic }) => {
1072
+ if (logLevel$4 <= logLevel.silent && diagnostic.severity !== "error") return;
1073
+ console.log(getMessage(Diagnostics.formatLines(diagnostic).join("\n")));
1267
1074
  });
1268
- context.on("kubb:config:end", () => {
1269
- if (logLevel$6 <= logLevel.silent) return;
1270
- const text = getMessage("Configuration completed");
1271
- console.log(text);
1075
+ context.on("kubb:lifecycle:start", ({ version }) => {
1076
+ console.log(`Kubb CLI v${version}`);
1272
1077
  });
1078
+ onStep("kubb:config:start", "Configuration started");
1079
+ onStep("kubb:config:end", "Configuration completed");
1273
1080
  context.on("kubb:generation:start", () => {
1274
1081
  const text = getMessage("Generation started");
1275
1082
  console.log(text);
1276
1083
  });
1277
1084
  context.on("kubb:plugin:start", ({ plugin }) => {
1278
- if (logLevel$6 <= logLevel.silent) return;
1085
+ if (logLevel$4 <= logLevel.silent) return;
1279
1086
  const text = getMessage(`Generating ${plugin.name}`);
1280
1087
  console.log(text);
1281
1088
  });
1282
1089
  context.on("kubb:plugin:end", ({ plugin, duration, success }) => {
1283
- if (logLevel$6 <= logLevel.silent) return;
1090
+ if (logLevel$4 <= logLevel.silent) return;
1284
1091
  const durationStr = formatMs(duration);
1285
1092
  const text = getMessage(success ? `${plugin.name} completed in ${durationStr}` : `${plugin.name} failed in ${durationStr}`);
1286
1093
  console.log(text);
1287
1094
  });
1288
1095
  context.on("kubb:files:processing:start", ({ files }) => {
1289
- if (logLevel$6 <= logLevel.silent) return;
1096
+ if (logLevel$4 <= logLevel.silent) return;
1290
1097
  const text = getMessage(`Writing ${files.length} files`);
1291
1098
  console.log(text);
1292
1099
  });
1293
- context.on("kubb:file:processing:update", ({ file, config }) => {
1294
- if (logLevel$6 <= logLevel.silent) return;
1295
- const text = getMessage(`Writing ${relative(config.root, file.path)}`);
1296
- console.log(text);
1100
+ context.on("kubb:files:processing:update", ({ files }) => {
1101
+ if (logLevel$4 <= logLevel.silent) return;
1102
+ for (const { file, config } of files) console.log(getMessage(`Writing ${relative(config.root, file.path)}`));
1297
1103
  });
1298
1104
  context.on("kubb:files:processing:end", () => {
1299
- if (logLevel$6 <= logLevel.silent) return;
1105
+ if (logLevel$4 <= logLevel.silent) return;
1300
1106
  const text = getMessage("Files written successfully");
1301
1107
  console.log(text);
1302
1108
  });
@@ -1304,60 +1110,32 @@ const plainLogger = defineLogger({
1304
1110
  const text = getMessage(config.name ? `Generation completed for ${config.name}` : "Generation completed");
1305
1111
  console.log(text);
1306
1112
  });
1307
- context.on("kubb:format:start", () => {
1308
- if (logLevel$6 <= logLevel.silent) return;
1309
- const text = getMessage("Format started");
1310
- console.log(text);
1311
- });
1312
- context.on("kubb:format:end", () => {
1313
- if (logLevel$6 <= logLevel.silent) return;
1314
- const text = getMessage("Format completed");
1315
- console.log(text);
1316
- });
1317
- context.on("kubb:lint:start", () => {
1318
- if (logLevel$6 <= logLevel.silent) return;
1319
- const text = getMessage("Lint started");
1320
- console.log(text);
1321
- });
1322
- context.on("kubb:lint:end", () => {
1323
- if (logLevel$6 <= logLevel.silent) return;
1324
- const text = getMessage("Lint completed");
1325
- console.log(text);
1326
- });
1327
- context.on("kubb:hook:start", async ({ id, command, args }) => {
1113
+ onStep("kubb:format:start", "Format started");
1114
+ onStep("kubb:format:end", "Format completed");
1115
+ onStep("kubb:lint:start", "Lint started");
1116
+ onStep("kubb:lint:end", "Lint completed");
1117
+ onStep("kubb:hooks:start", "Hooks started");
1118
+ onStep("kubb:hooks:end", "Hooks completed");
1119
+ context.on("kubb:hook:start", ({ id, command, args }) => {
1120
+ if (logLevel$4 <= logLevel.silent) return;
1121
+ if (id) hookTimer.start(id);
1328
1122
  const commandWithArgs = formatCommandWithArgs(command, args);
1329
- const text = getMessage(`Hook ${commandWithArgs} started`);
1330
- if (logLevel$6 > logLevel.silent) console.log(text);
1331
- if (!id) return;
1332
- await runHook({
1333
- id,
1334
- command,
1335
- args,
1336
- commandWithArgs,
1337
- context,
1338
- sink: {
1339
- onStdout: logLevel$6 > logLevel.silent ? (s) => console.log(s) : void 0,
1340
- onStderr: logLevel$6 > logLevel.silent ? (s) => console.error(s) : void 0
1341
- }
1342
- });
1123
+ console.log(getMessage(`Hook ${commandWithArgs} started`));
1343
1124
  });
1344
- context.on("kubb:hook:end", ({ command, args }) => {
1345
- if (logLevel$6 <= logLevel.silent) return;
1346
- const text = getMessage(`Hook ${formatCommandWithArgs(command, args)} completed`);
1347
- console.log(text);
1125
+ context.on("kubb:hook:end", ({ id, command, args, success, error }) => {
1126
+ if (logLevel$4 <= logLevel.silent) return;
1127
+ const ms = id ? hookTimer.end(id) : void 0;
1128
+ const durationStr = ms !== void 0 ? ` in ${formatMs(ms)}` : "";
1129
+ const commandWithArgs = formatCommandWithArgs(command, args);
1130
+ if (success) console.log(getMessage(`✓ Hook ${commandWithArgs} completed${durationStr}`));
1131
+ else {
1132
+ const reason = error?.message ? ` (${error.message})` : "";
1133
+ console.log(getMessage(`✗ Hook ${commandWithArgs} failed${durationStr}${reason}`));
1134
+ }
1348
1135
  });
1349
- context.on("kubb:generation:summary", ({ config, pluginTimings, status, hrStart, failedPlugins, filesCreated }) => {
1350
- const summary = getSummary({
1351
- failedPlugins,
1352
- filesCreated,
1353
- config,
1354
- status,
1355
- hrStart,
1356
- pluginTimings: logLevel$6 >= logLevel.verbose ? pluginTimings : void 0
1357
- });
1358
- console.log(SUMMARY_SEPARATOR);
1359
- console.log(summary.join("\n"));
1360
- console.log(SUMMARY_SEPARATOR);
1136
+ return (_commandWithArgs, _hookId) => ({
1137
+ onStdout: logLevel$4 > logLevel.silent ? (s) => console.log(s) : void 0,
1138
+ onStderr: logLevel$4 > logLevel.silent ? (s) => console.error(s) : void 0
1361
1139
  });
1362
1140
  }
1363
1141
  });
@@ -1367,8 +1145,8 @@ const plainLogger = defineLogger({
1367
1145
  * Optionally prefix a message with a [HH:MM:SS] timestamp when logLevel >= verbose.
1368
1146
  * Shared across all logger adapters to avoid duplication.
1369
1147
  */
1370
- function formatMessage(message, logLevel$4) {
1371
- if (logLevel$4 >= logLevel.verbose) return `${styleText("dim", `[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
1148
+ function formatMessage(message, logLevel$3) {
1149
+ if (logLevel$3 >= logLevel.verbose) return `${styleText("dim", `[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
1372
1150
  hour12: false,
1373
1151
  hour: "2-digit",
1374
1152
  minute: "2-digit",
@@ -1393,6 +1171,57 @@ function buildProgressLine(state) {
1393
1171
  return parts.join(styleText("dim", " | "));
1394
1172
  }
1395
1173
  /**
1174
+ * Creates the per-run progress counters shared by the clack and GitHub Actions loggers.
1175
+ */
1176
+ function createProgressCounters() {
1177
+ return {
1178
+ totalPlugins: 0,
1179
+ completedPlugins: 0,
1180
+ failedPlugins: 0,
1181
+ totalFiles: 0,
1182
+ processedFiles: 0,
1183
+ hrStart: process$1.hrtime()
1184
+ };
1185
+ }
1186
+ /**
1187
+ * Resets the progress counters in place at the start/end of a generation run.
1188
+ */
1189
+ function resetProgressCounters(state) {
1190
+ state.totalPlugins = 0;
1191
+ state.completedPlugins = 0;
1192
+ state.failedPlugins = 0;
1193
+ state.totalFiles = 0;
1194
+ state.processedFiles = 0;
1195
+ state.hrStart = process$1.hrtime();
1196
+ }
1197
+ /**
1198
+ * Records a finished plugin against the progress counters.
1199
+ */
1200
+ function recordPluginResult(state, success) {
1201
+ if (success) state.completedPlugins++;
1202
+ else state.failedPlugins++;
1203
+ }
1204
+ /**
1205
+ * Creates a {@link HookTimer} backed by a private `id → hrtime` map.
1206
+ */
1207
+ function createHookTimer() {
1208
+ const starts = /* @__PURE__ */ new Map();
1209
+ return {
1210
+ start(id) {
1211
+ starts.set(id, process$1.hrtime());
1212
+ },
1213
+ end(id) {
1214
+ const hrStart = starts.get(id);
1215
+ if (!hrStart) return;
1216
+ starts.delete(id);
1217
+ return getElapsedMs(hrStart);
1218
+ },
1219
+ clear() {
1220
+ starts.clear();
1221
+ }
1222
+ };
1223
+ }
1224
+ /**
1396
1225
  * Join a command and its optional args into a single display string.
1397
1226
  * e.g. ("prettier", ["--write", "."]) → "prettier --write ."
1398
1227
  */
@@ -1409,129 +1238,233 @@ const logMapper = {
1409
1238
  plain: plainLogger,
1410
1239
  "github-actions": githubActionsLogger
1411
1240
  };
1412
- async function setupLogger(context, { logLevel: logLevel$5 }) {
1413
- const type = detectLogger();
1414
- const logger = logMapper[type];
1415
- if (!logger) throw new Error(`Unknown adapter type: ${type}`);
1416
- const cleanup = await logger.install(context, { logLevel: logLevel$5 });
1417
- if (logLevel$5 >= logLevel.debug) await fileSystemLogger.install(context, { logLevel: logLevel$5 });
1418
- return cleanup;
1241
+ /**
1242
+ * Bridges a {@link Reporter} onto the run's event emitter: calls `report` with each config's
1243
+ * {@link GenerationResult} on `kubb:generation:end`. The reporter never touches the emitter.
1244
+ */
1245
+ function installReporter(context, reporter, ctx) {
1246
+ context.on("kubb:generation:end", async ({ config, diagnostics = [], filesCreated = 0, status = "success", hrStart = process$1.hrtime() }) => {
1247
+ await reporter.report({
1248
+ config,
1249
+ diagnostics,
1250
+ filesCreated,
1251
+ status,
1252
+ hrStart
1253
+ }, ctx);
1254
+ });
1255
+ if (reporter.drain) context.on("kubb:lifecycle:end", () => reporter.drain?.(ctx));
1256
+ }
1257
+ /**
1258
+ * Installs the live logger (the TUI view) and the given reporters (the output), returning the
1259
+ * terminal logger's hook sink when one was installed. The reporters are already selected by the
1260
+ * caller (the CLI maps `--reporter` to names via `selectReporters`); this only wires them.
1261
+ *
1262
+ * Loggers and reporters are independent: the `cli` reporter also activates the env logger summary.
1263
+ * The `json` reporter owns stdout, so the live logger and the `cli` summary are suppressed whenever
1264
+ * `json` is among the reporters, even if `cli` is also listed.
1265
+ */
1266
+ async function setupReporters(context, { logLevel, reporters }) {
1267
+ const hasJson = reporters.some((reporter) => reporter.name === "json");
1268
+ const ctx = { logLevel };
1269
+ let makeSink = null;
1270
+ for (const reporter of reporters) {
1271
+ if (reporter.name === "cli") {
1272
+ if (hasJson) continue;
1273
+ const type = detectLogger();
1274
+ const logger = logMapper[type];
1275
+ if (!logger) throw new Error(`Unknown adapter type: ${type}`);
1276
+ const sink = await logger.install(context, { logLevel });
1277
+ makeSink = typeof sink === "function" ? sink : null;
1278
+ }
1279
+ installReporter(context, reporter, ctx);
1280
+ }
1281
+ return makeSink;
1419
1282
  }
1420
1283
  //#endregion
1421
- //#region src/utils/executeHooks.ts
1422
- async function executeHooks({ configHooks, hooks }) {
1284
+ //#region src/runners/generate/utils.ts
1285
+ const jiti = createJiti(import.meta.url, {
1286
+ jsx: {
1287
+ runtime: "automatic",
1288
+ importSource: "@kubb/renderer-jsx"
1289
+ },
1290
+ moduleCache: false
1291
+ });
1292
+ const tsLoader = (configFile) => jiti.import(configFile, { default: true });
1293
+ const MODULE_NAME = "kubb";
1294
+ const BASE_SEARCH_PLACES = [
1295
+ "package.json",
1296
+ `.${MODULE_NAME}rc`,
1297
+ `.${MODULE_NAME}rc.json`,
1298
+ `.${MODULE_NAME}rc.yaml`,
1299
+ `.${MODULE_NAME}rc.yml`,
1300
+ `.${MODULE_NAME}rc.ts`,
1301
+ `.${MODULE_NAME}rc.mts`,
1302
+ `.${MODULE_NAME}rc.cts`,
1303
+ `.${MODULE_NAME}rc.js`,
1304
+ `.${MODULE_NAME}rc.mjs`,
1305
+ `.${MODULE_NAME}rc.cjs`,
1306
+ `${MODULE_NAME}.config.ts`,
1307
+ `${MODULE_NAME}.config.mts`,
1308
+ `${MODULE_NAME}.config.cts`,
1309
+ `${MODULE_NAME}.config.js`,
1310
+ `${MODULE_NAME}.config.mjs`,
1311
+ `${MODULE_NAME}.config.cjs`
1312
+ ];
1313
+ const SEARCH_PLACES = [
1314
+ "",
1315
+ ".config/",
1316
+ "configs/"
1317
+ ].flatMap((prefix) => BASE_SEARCH_PLACES.map((p) => `${prefix}${p}`));
1318
+ async function getCosmiConfig(configFile) {
1319
+ const explorer = cosmiconfig(MODULE_NAME, {
1320
+ cache: false,
1321
+ searchPlaces: SEARCH_PLACES,
1322
+ loaders: {
1323
+ ".ts": tsLoader,
1324
+ ".mts": tsLoader,
1325
+ ".cts": tsLoader
1326
+ }
1327
+ });
1328
+ let result;
1329
+ try {
1330
+ result = configFile ? await explorer.load(configFile) : await explorer.search();
1331
+ } catch (error) {
1332
+ throw new Error("Config failed loading", { cause: error });
1333
+ }
1334
+ if (!result?.config || result.isEmpty) throw new Error("Config not defined, create a kubb.config.js or pass through your config with the option --config");
1335
+ return result;
1336
+ }
1337
+ /**
1338
+ * Discovers the Kubb config via cosmiconfig and resolves it into a normalized array of configs.
1339
+ * Every config in the result is guaranteed to have a `plugins` array.
1340
+ */
1341
+ async function getConfigs({ configPath, input, watch, logLevel }) {
1342
+ const result = await getCosmiConfig(configPath);
1343
+ const cli = {
1344
+ config: configPath,
1345
+ input,
1346
+ watch,
1347
+ logLevel
1348
+ };
1349
+ const resolved = await (typeof result.config === "function" ? result.config(cli) : result.config);
1350
+ const userConfigs = Array.isArray(resolved) ? resolved : [resolved];
1351
+ return {
1352
+ configPath: result.filepath,
1353
+ configs: userConfigs.map((item) => ({
1354
+ ...item,
1355
+ plugins: item.plugins ?? []
1356
+ }))
1357
+ };
1358
+ }
1359
+ /**
1360
+ * Runs the `done` hooks defined in a Kubb config in sequence.
1361
+ */
1362
+ async function executeHooks({ configHooks, hooks, makeSink }) {
1423
1363
  const commands = Array.isArray(configHooks.done) ? configHooks.done : [configHooks.done].filter(Boolean);
1424
1364
  for (const command of commands) {
1425
1365
  const [cmd, ...args] = tokenize(command);
1426
1366
  if (!cmd) continue;
1427
1367
  const hookId = createHash("sha256").update(command).digest("hex");
1428
- const hookEndPromise = new Promise((resolve, reject) => {
1429
- const handler = (ctx) => {
1430
- if (ctx.id !== hookId) return;
1431
- hooks.off("kubb:hook:end", handler);
1432
- if (!ctx.success) {
1433
- reject(ctx.error ?? /* @__PURE__ */ new Error(`Hook failed: ${command}`));
1434
- return;
1435
- }
1436
- hooks.emit("kubb:success", { message: `${styleText("dim", command)} successfully executed` }).then(resolve).catch(reject);
1437
- };
1438
- hooks.on("kubb:hook:end", handler);
1439
- });
1368
+ const commandWithArgs = [cmd, ...args].join(" ");
1440
1369
  await hooks.emit("kubb:hook:start", {
1441
1370
  id: hookId,
1442
1371
  command: cmd,
1443
1372
  args
1444
1373
  });
1445
- await hookEndPromise;
1374
+ const { stream = false, onLine, onStdout, onStderr } = makeSink?.(commandWithArgs, hookId) ?? {};
1375
+ await runHook({
1376
+ id: hookId,
1377
+ command: cmd,
1378
+ args,
1379
+ commandWithArgs,
1380
+ hooks,
1381
+ stream,
1382
+ sink: {
1383
+ onLine,
1384
+ onStdout,
1385
+ onStderr
1386
+ }
1387
+ });
1446
1388
  }
1447
1389
  }
1448
- //#endregion
1449
- //#region src/utils/getConfig.ts
1450
- async function getConfigs(config, args) {
1451
- const resolved = await (typeof config === "function" ? config(args) : config);
1452
- return (Array.isArray(resolved) ? resolved : [resolved]).map((item) => ({
1453
- ...item,
1454
- plugins: item.plugins ?? []
1455
- }));
1456
- }
1457
- //#endregion
1458
- //#region src/utils/getCosmiConfig.ts
1459
- const unrunInputOptions = { transform: { jsx: {
1460
- runtime: "automatic",
1461
- importSource: "@kubb/renderer-jsx"
1462
- } } };
1463
- const tsLoader = async (configFile) => {
1464
- const { module } = await unrun({
1465
- path: configFile,
1466
- inputOptions: unrunInputOptions
1467
- });
1468
- return module;
1469
- };
1470
- async function getCosmiConfig(moduleName, config) {
1471
- let result;
1472
- const searchPlaces = [
1473
- "package.json",
1474
- `.${moduleName}rc`,
1475
- `.${moduleName}rc.json`,
1476
- `.${moduleName}rc.yaml`,
1477
- `.${moduleName}rc.yml`,
1478
- `.${moduleName}rc.ts`,
1479
- `.${moduleName}rc.mts`,
1480
- `.${moduleName}rc.cts`,
1481
- `.${moduleName}rc.js`,
1482
- `.${moduleName}rc.mjs`,
1483
- `.${moduleName}rc.cjs`,
1484
- `${moduleName}.config.ts`,
1485
- `${moduleName}.config.mts`,
1486
- `${moduleName}.config.cts`,
1487
- `${moduleName}.config.js`,
1488
- `${moduleName}.config.mjs`,
1489
- `${moduleName}.config.cjs`
1490
- ];
1491
- const explorer = cosmiconfig(moduleName, {
1492
- cache: false,
1493
- searchPlaces: [
1494
- ...searchPlaces.map((searchPlace) => {
1495
- return `.config/${searchPlace}`;
1496
- }),
1497
- ...searchPlaces.map((searchPlace) => {
1498
- return `configs/${searchPlace}`;
1499
- }),
1500
- ...searchPlaces
1501
- ],
1502
- loaders: {
1503
- ".ts": tsLoader,
1504
- ".mts": tsLoader,
1505
- ".cts": tsLoader
1506
- }
1390
+ async function runHook({ id, command, args, commandWithArgs, hooks, stream = false, sink }) {
1391
+ const emitEnd = (success, error) => hooks.emit("kubb:hook:end", {
1392
+ command,
1393
+ args,
1394
+ id,
1395
+ success,
1396
+ error
1507
1397
  });
1508
1398
  try {
1509
- result = config ? await explorer.load(config) : await explorer.search();
1510
- } catch (error) {
1511
- throw new Error("Config failed loading", { cause: error });
1399
+ const proc = x(command, [...args ?? []], {
1400
+ nodeOptions: { detached: process.platform !== "win32" },
1401
+ throwOnError: true
1402
+ });
1403
+ if (stream && sink?.onLine) for await (const line of proc) sink.onLine(line);
1404
+ await proc;
1405
+ await hooks.emit("kubb:success", { message: `${styleText("dim", commandWithArgs)} successfully executed` });
1406
+ await emitEnd(true, null);
1407
+ } catch (err) {
1408
+ if (!(err instanceof NonZeroExitError)) {
1409
+ await emitEnd(false, toError(err));
1410
+ return;
1411
+ }
1412
+ const stderr = err.output?.stderr ?? "";
1413
+ const stdout = err.output?.stdout ?? "";
1414
+ if (stderr) sink?.onStderr?.(stderr);
1415
+ if (stdout) sink?.onStdout?.(stdout);
1416
+ await emitEnd(false, /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`));
1512
1417
  }
1513
- if (result?.isEmpty || !result || !result.config) throw new Error("Config not defined, create a kubb.config.js or pass through your config with the option --config");
1514
- return result;
1515
1418
  }
1516
- //#endregion
1517
- //#region src/utils/watcher.ts
1518
- async function startWatcher(path, cb) {
1419
+ /**
1420
+ * Starts a file watcher on the given paths and calls `cb` on any change.
1421
+ * Ignores `.git` and `node_modules` directories.
1422
+ */
1423
+ async function startWatcher(path, cb, log = {
1424
+ info: console.log,
1425
+ error: console.log
1426
+ }) {
1519
1427
  const { watch } = await import("chokidar");
1520
- watch(path, {
1428
+ const watcher = watch(path, {
1521
1429
  ignorePermissionErrors: true,
1522
1430
  ignored: WATCHER_IGNORED_PATHS
1523
- }).on("all", async (type, file) => {
1524
- console.log(styleText("yellow", styleText("bold", `Change detected: ${type} ${file}`)));
1431
+ });
1432
+ process.once("SIGINT", () => {
1433
+ watcher.close();
1434
+ });
1435
+ process.once("SIGTERM", () => {
1436
+ watcher.close();
1437
+ });
1438
+ watcher.on("all", async (type, file) => {
1439
+ log.info(styleText("yellow", styleText("bold", `Change detected: ${type} ${file}`)));
1525
1440
  try {
1526
1441
  await cb(path);
1527
1442
  } catch (_e) {
1528
- console.log(styleText("red", "Watcher failed"));
1443
+ log.error(styleText("red", "Watcher failed"));
1529
1444
  }
1530
1445
  });
1531
1446
  }
1532
1447
  //#endregion
1533
- //#region src/runners/generate.ts
1534
- async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, configName, outputPath, logLevel: logLevel$1, hooks, onStart, onEnd }) {
1448
+ //#region src/runners/generate/run.ts
1449
+ /**
1450
+ * Registers a one-shot `kubb:hook:end` listener for `hookId` BEFORE the caller emits `kubb:hook:start`,
1451
+ * avoiding the race where a synchronous emitter fires end before the listener is attached.
1452
+ */
1453
+ function waitForHookEnd(hooks, hookId, onSuccess, fallbackErrorMessage) {
1454
+ return new Promise((resolve, reject) => {
1455
+ const handler = (ctx) => {
1456
+ if (ctx.id !== hookId) return;
1457
+ hooks.off("kubb:hook:end", handler);
1458
+ if (!ctx.success) {
1459
+ reject(ctx.error ?? new Error(fallbackErrorMessage));
1460
+ return;
1461
+ }
1462
+ Promise.resolve(onSuccess()).then(resolve).catch(reject);
1463
+ };
1464
+ hooks.on("kubb:hook:end", handler);
1465
+ });
1466
+ }
1467
+ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefix, noToolMessage, configName, outputPath, logLevel: logLevel$1, hooks, makeSink, onStart, onEnd }) {
1535
1468
  await onStart();
1536
1469
  let resolvedTool = toolValue;
1537
1470
  if (resolvedTool === "auto") {
@@ -1543,43 +1476,47 @@ async function runToolPass({ toolValue, detect, toolMap, toolLabel, successPrefi
1543
1476
  }
1544
1477
  }
1545
1478
  let toolError;
1546
- if (resolvedTool && resolvedTool !== "auto" && resolvedTool in toolMap) {
1479
+ if (resolvedTool && resolvedTool !== "auto" && resolvedTool in toolMap && existsSync(outputPath)) {
1547
1480
  const toolConfig = toolMap[resolvedTool];
1481
+ const hookId = createHash("sha256").update([configName, resolvedTool].filter(Boolean).join("-")).digest("hex");
1482
+ const successMessage = [
1483
+ `${successPrefix} with ${styleText("dim", resolvedTool)}`,
1484
+ logLevel$1 >= logLevel.info ? `on ${styleText("dim", outputPath)}` : void 0,
1485
+ "successfully"
1486
+ ].filter(Boolean).join(" ");
1548
1487
  try {
1549
- const hookId = createHash("sha256").update([configName, resolvedTool].filter(Boolean).join("-")).digest("hex");
1550
- const hookEndPromise = new Promise((resolve, reject) => {
1551
- const handler = (ctx) => {
1552
- if (ctx.id !== hookId) return;
1553
- hooks.off("kubb:hook:end", handler);
1554
- if (!ctx.success) {
1555
- reject(ctx.error ?? /* @__PURE__ */ new Error(`${toolConfig.errorMessage}`));
1556
- return;
1557
- }
1558
- hooks.emit("kubb:success", { message: [
1559
- `${successPrefix} with ${styleText("dim", resolvedTool)}`,
1560
- logLevel$1 >= logLevel.info ? `on ${styleText("dim", outputPath)}` : void 0,
1561
- "successfully"
1562
- ].filter(Boolean).join(" ") }).then(resolve).catch(reject);
1563
- };
1564
- hooks.on("kubb:hook:end", handler);
1565
- });
1488
+ const hookArgs = toolConfig.args(outputPath);
1489
+ const commandWithArgs = [toolConfig.command, ...hookArgs].join(" ");
1490
+ const hookEndPromise = waitForHookEnd(hooks, hookId, () => hooks.emit("kubb:success", { message: successMessage }), toolConfig.errorMessage);
1566
1491
  await hooks.emit("kubb:hook:start", {
1567
1492
  id: hookId,
1568
1493
  command: toolConfig.command,
1569
- args: toolConfig.args(outputPath)
1494
+ args: hookArgs
1570
1495
  });
1496
+ const { stream = false, onLine, onStdout, onStderr } = makeSink?.(commandWithArgs, hookId) ?? {};
1497
+ runHook({
1498
+ id: hookId,
1499
+ command: toolConfig.command,
1500
+ args: hookArgs,
1501
+ commandWithArgs,
1502
+ hooks,
1503
+ stream,
1504
+ sink: {
1505
+ onLine,
1506
+ onStdout,
1507
+ onStderr
1508
+ }
1509
+ }).catch(() => {});
1571
1510
  await hookEndPromise;
1572
1511
  } catch (caughtError) {
1573
- const err = toError(caughtError);
1574
- await hooks.emit("kubb:error", { error: err });
1575
- toolError = err;
1512
+ toolError = toError(caughtError);
1576
1513
  }
1577
1514
  }
1578
1515
  await onEnd();
1579
1516
  if (toolError) throw toolError;
1580
1517
  }
1581
1518
  async function generate(options) {
1582
- const { input, hooks, logLevel: logLevel$2 } = options;
1519
+ const { input, hooks, logLevel, makeSink } = options;
1583
1520
  const hrStart = process$1.hrtime();
1584
1521
  const inputPath = input ?? (options.config.input && "path" in options.config.input ? options.config.input.path : void 0);
1585
1522
  const config = {
@@ -1591,163 +1528,230 @@ async function generate(options) {
1591
1528
  ...options.config.output
1592
1529
  };
1593
1530
  const kubb = createKubb(config, { hooks });
1594
- await kubb.setup();
1595
1531
  await hooks.emit("kubb:generation:start", { config });
1596
1532
  await hooks.emit("kubb:info", {
1597
1533
  message: config.name ? `Setup generation ${styleText("bold", config.name)}` : "Setup generation",
1598
1534
  info: inputPath
1599
1535
  });
1536
+ await kubb.setup();
1600
1537
  await hooks.emit("kubb:info", {
1601
1538
  message: config.name ? `Build generation ${styleText("bold", config.name)}` : "Build generation",
1602
1539
  info: inputPath
1603
1540
  });
1604
- const { files, failedPlugins, pluginTimings, error, driver } = await kubb.safeBuild();
1541
+ const { files, diagnostics, driver } = await kubb.safeBuild();
1605
1542
  await hooks.emit("kubb:info", { message: "Load summary" });
1606
- if (failedPlugins.size > 0 || error) {
1607
- const allErrors = [error, ...Array.from(failedPlugins).filter((it) => it.error).map((it) => it.error)].filter(Boolean);
1608
- for (const err of allErrors) await hooks.emit("kubb:error", { error: err });
1543
+ const telemetryPlugins = Array.from(driver.plugins.values(), (p) => ({
1544
+ name: p.name,
1545
+ options: p.options
1546
+ }));
1547
+ const reportTelemetry = (status) => Telemetry.send(Telemetry.build({
1548
+ command: "generate",
1549
+ kubbVersion: version,
1550
+ plugins: telemetryPlugins,
1551
+ hrStart,
1552
+ filesCreated: files.length,
1553
+ status
1554
+ }));
1555
+ for (const diagnostic of diagnostics) {
1556
+ if (!Diagnostics.isProblem(diagnostic)) continue;
1557
+ const unknown = Diagnostics.narrow(diagnostic, Diagnostics.code.unknown);
1558
+ if (unknown) await hooks.emit("kubb:error", { error: unknown.cause ?? new Error(unknown.message) });
1559
+ else await Diagnostics.emit(hooks, diagnostic);
1560
+ }
1561
+ if (Diagnostics.hasError(diagnostics)) {
1609
1562
  await hooks.emit("kubb:generation:end", {
1610
1563
  config,
1611
- files,
1612
- sources: kubb.sources
1613
- });
1614
- await hooks.emit("kubb:generation:summary", {
1615
- config,
1616
- failedPlugins,
1564
+ storage: kubb.storage,
1565
+ diagnostics,
1617
1566
  filesCreated: files.length,
1618
1567
  status: "failed",
1619
- hrStart,
1620
- pluginTimings: logLevel$2 >= logLevel.verbose ? pluginTimings : void 0
1568
+ hrStart
1621
1569
  });
1622
- await sendTelemetry(buildTelemetryEvent({
1623
- command: "generate",
1624
- kubbVersion: version,
1625
- plugins: Array.from(driver.plugins.values(), (p) => ({
1626
- name: p.name,
1627
- options: p.options
1628
- })),
1629
- hrStart,
1630
- filesCreated: files.length,
1631
- status: "failed"
1632
- }));
1633
- process$1.exit(1);
1570
+ await reportTelemetry("failed");
1571
+ return false;
1634
1572
  }
1635
- await hooks.emit("kubb:success", {
1636
- message: "Generation successfully",
1637
- info: inputPath
1638
- });
1639
- await hooks.emit("kubb:generation:end", {
1640
- config,
1641
- files,
1642
- sources: kubb.sources
1643
- });
1644
1573
  const outputPath = path.resolve(config.root, config.output.path);
1645
- if (config.output.format) await runToolPass({
1574
+ const outputDiagnostics = [];
1575
+ const toolPasses = [config.output.format && {
1576
+ code: Diagnostics.code.formatFailed,
1646
1577
  toolValue: config.output.format,
1647
1578
  detect: detectFormatter,
1648
1579
  toolMap: formatters,
1649
1580
  toolLabel: "formatter",
1650
1581
  successPrefix: "Formatting",
1651
1582
  noToolMessage: "No formatter found (oxfmt, biome, or prettier). Skipping formatting.",
1652
- configName: config.name,
1653
- outputPath,
1654
- logLevel: logLevel$2,
1655
- hooks,
1656
1583
  onStart: () => hooks.emit("kubb:format:start"),
1657
1584
  onEnd: () => hooks.emit("kubb:format:end")
1658
- });
1659
- if (config.output.lint) await runToolPass({
1585
+ }, config.output.lint && {
1586
+ code: Diagnostics.code.lintFailed,
1660
1587
  toolValue: config.output.lint,
1661
1588
  detect: detectLinter,
1662
1589
  toolMap: linters,
1663
1590
  toolLabel: "linter",
1664
1591
  successPrefix: "Linting",
1665
1592
  noToolMessage: "No linter found (oxlint, biome, or eslint). Skipping linting.",
1666
- configName: config.name,
1667
- outputPath,
1668
- logLevel: logLevel$2,
1669
- hooks,
1670
1593
  onStart: () => hooks.emit("kubb:lint:start"),
1671
1594
  onEnd: () => hooks.emit("kubb:lint:end")
1672
- });
1595
+ }].filter(Boolean);
1596
+ for (const { code, ...pass } of toolPasses) try {
1597
+ await runToolPass({
1598
+ ...pass,
1599
+ configName: config.name,
1600
+ outputPath,
1601
+ logLevel,
1602
+ hooks,
1603
+ makeSink
1604
+ });
1605
+ } catch (caughtError) {
1606
+ const diagnostic = outputDiagnostic(code, pass.toolLabel, caughtError);
1607
+ outputDiagnostics.push(diagnostic);
1608
+ await Diagnostics.emit(hooks, diagnostic);
1609
+ }
1673
1610
  if (config.hooks) {
1674
1611
  await hooks.emit("kubb:hooks:start");
1675
- await executeHooks({
1676
- configHooks: config.hooks,
1677
- hooks
1678
- });
1612
+ const hookFailures = [];
1613
+ const onHookEnd = (ctx) => {
1614
+ if (!ctx.success) hookFailures.push(ctx.error ?? /* @__PURE__ */ new Error("Post-generate hook failed"));
1615
+ };
1616
+ hooks.on("kubb:hook:end", onHookEnd);
1617
+ try {
1618
+ await executeHooks({
1619
+ configHooks: config.hooks,
1620
+ hooks,
1621
+ makeSink
1622
+ });
1623
+ } finally {
1624
+ hooks.off("kubb:hook:end", onHookEnd);
1625
+ }
1626
+ for (const error of hookFailures) {
1627
+ const diagnostic = outputDiagnostic(Diagnostics.code.hookFailed, "Post-generate hook", error);
1628
+ outputDiagnostics.push(diagnostic);
1629
+ await Diagnostics.emit(hooks, diagnostic);
1630
+ }
1679
1631
  await hooks.emit("kubb:hooks:end");
1680
1632
  }
1681
- await hooks.emit("kubb:generation:summary", {
1633
+ const finalDiagnostics = [...diagnostics, ...outputDiagnostics];
1634
+ const failed = Diagnostics.hasError(outputDiagnostics);
1635
+ if (!failed) await hooks.emit("kubb:success", {
1636
+ message: "Generation succeeded",
1637
+ info: inputPath
1638
+ });
1639
+ await hooks.emit("kubb:generation:end", {
1682
1640
  config,
1683
- failedPlugins,
1641
+ storage: kubb.storage,
1642
+ diagnostics: finalDiagnostics,
1684
1643
  filesCreated: files.length,
1685
- status: "success",
1686
- hrStart,
1687
- pluginTimings
1644
+ status: failed ? "failed" : "success",
1645
+ hrStart
1688
1646
  });
1689
- await sendTelemetry(buildTelemetryEvent({
1690
- command: "generate",
1691
- kubbVersion: version,
1692
- plugins: Array.from(driver.plugins.values(), (p) => ({
1693
- name: p.name,
1694
- options: p.options
1695
- })),
1696
- hrStart,
1697
- filesCreated: files.length,
1698
- status: "success"
1699
- }));
1647
+ await reportTelemetry(failed ? "failed" : "success");
1648
+ return !failed;
1700
1649
  }
1701
- async function runGenerateCommand({ input, configPath, logLevel: logLevelKey, watch }) {
1702
- const logLevel$3 = logLevel[logLevelKey] ?? logLevel.info;
1703
- const hooks = new AsyncEventEmitter();
1704
- await setupLogger(hooks, { logLevel: logLevel$3 });
1650
+ /**
1651
+ * Builds a coded diagnostic for an output-phase failure (formatter, linter, or `done` hook).
1652
+ */
1653
+ function outputDiagnostic(code, label, caughtError) {
1654
+ const error = toError(caughtError);
1655
+ return {
1656
+ code,
1657
+ severity: "error",
1658
+ message: `${label} failed: ${error.message}`,
1659
+ help: "Check that the tool is installed and that the command and its config are correct.",
1660
+ location: { kind: "config" },
1661
+ cause: error
1662
+ };
1663
+ }
1664
+ async function checkForUpdate(hooks) {
1705
1665
  await executeIfOnline(async () => {
1706
1666
  try {
1707
- const latestVersion = (await (await fetch(KUBB_NPM_PACKAGE_URL)).json()).version;
1708
- if (latestVersion && version < latestVersion) await hooks.emit("kubb:version:new", {
1667
+ const data = await (await fetch(KUBB_NPM_PACKAGE_URL)).json();
1668
+ if (data.version && version < data.version) await Diagnostics.emit(hooks, Diagnostics.update({
1709
1669
  currentVersion: version,
1710
- latestVersion
1711
- });
1670
+ latestVersion: data.version
1671
+ }));
1712
1672
  } catch {}
1713
1673
  });
1674
+ }
1675
+ /**
1676
+ * Runs the full Kubb generation lifecycle for the given CLI options.
1677
+ * Loads configs, sets up the selected reporters (CLI `--reporter` overrides `config.reporters`),
1678
+ * checks for a newer version, and calls `generate` for each config entry.
1679
+ */
1680
+ async function run({ input, configPath, logLevel: logLevelKey, watch, reporters: cliReporters }) {
1681
+ const logLevel$2 = logLevel[logLevelKey] ?? logLevel.info;
1682
+ const hooks = new AsyncEventEmitter();
1683
+ let configs;
1684
+ let resolvedConfigPath;
1685
+ try {
1686
+ const loaded = await getConfigs({
1687
+ configPath,
1688
+ input,
1689
+ watch,
1690
+ logLevel: logLevelKey
1691
+ });
1692
+ configs = loaded.configs;
1693
+ resolvedConfigPath = loaded.configPath;
1694
+ } catch (error) {
1695
+ await setupReporters(hooks, {
1696
+ logLevel: logLevel$2,
1697
+ reporters: [cliReporter]
1698
+ });
1699
+ await hooks.emit("kubb:error", { error: toError(error) });
1700
+ process$1.exit(1);
1701
+ }
1702
+ const requestedNames = cliReporters?.length ? cliReporters : ["cli"];
1703
+ const makeSink = await setupReporters(hooks, {
1704
+ logLevel: logLevel$2,
1705
+ reporters: selectReporters(configs[0]?.reporters ?? [], requestedNames)
1706
+ });
1707
+ await hooks.emit("kubb:lifecycle:start", { version });
1708
+ await checkForUpdate(hooks);
1714
1709
  try {
1715
- const result = await getCosmiConfig("kubb", configPath);
1716
- const configs = await getConfigs(result.config, { input });
1710
+ const relativeConfigPath = path.relative(process$1.cwd(), resolvedConfigPath);
1717
1711
  await hooks.emit("kubb:config:start");
1718
1712
  await hooks.emit("kubb:info", {
1719
1713
  message: "Config loaded",
1720
- info: path.relative(process$1.cwd(), result.filepath)
1714
+ info: relativeConfigPath
1721
1715
  });
1722
1716
  await hooks.emit("kubb:success", {
1723
1717
  message: "Config loaded successfully",
1724
- info: path.relative(process$1.cwd(), result.filepath)
1718
+ info: relativeConfigPath
1725
1719
  });
1726
1720
  await hooks.emit("kubb:config:end", { configs });
1727
- await hooks.emit("kubb:lifecycle:start", { version });
1728
- for (const config of configs) if (isInputPath(config) && watch) await startWatcher([input || config.input.path], async (paths) => {
1729
- hooks.removeAll();
1721
+ let anyFailed = false;
1722
+ for (const config of configs) if (config.input && "path" in config.input && watch) await startWatcher([input || config.input.path], async (paths) => {
1730
1723
  await generate({
1731
1724
  input,
1732
1725
  config,
1733
- logLevel: logLevel$3,
1734
- hooks
1726
+ logLevel: logLevel$2,
1727
+ hooks,
1728
+ makeSink
1735
1729
  });
1736
1730
  clack.log.step(styleText("yellow", `Watching for changes in ${paths.join(" and ")}`));
1731
+ }, {
1732
+ info: (msg) => clack.log.info(msg),
1733
+ error: (msg) => clack.log.error(msg)
1737
1734
  });
1738
- else await generate({
1739
- input,
1740
- config,
1741
- logLevel: logLevel$3,
1742
- hooks
1743
- });
1735
+ else try {
1736
+ if (!await generate({
1737
+ input,
1738
+ config,
1739
+ logLevel: logLevel$2,
1740
+ hooks,
1741
+ makeSink
1742
+ })) anyFailed = true;
1743
+ } catch (configError) {
1744
+ await hooks.emit("kubb:error", { error: toError(configError) });
1745
+ anyFailed = true;
1746
+ }
1744
1747
  await hooks.emit("kubb:lifecycle:end");
1748
+ if (anyFailed) process$1.exit(1);
1745
1749
  } catch (error) {
1746
1750
  await hooks.emit("kubb:error", { error: toError(error) });
1747
1751
  process$1.exit(1);
1748
1752
  }
1749
1753
  }
1750
1754
  //#endregion
1751
- export { runGenerateCommand };
1755
+ export { run };
1752
1756
 
1753
- //# sourceMappingURL=generate-CNrRLY4n.js.map
1757
+ //# sourceMappingURL=run-CHZKHTv0.js.map