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