@kubb/core 5.10.1 → 5.10.3

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.
@@ -1,12 +1,19 @@
1
- import "./rolldown-runtime-CRm0XQPb.js";
1
+ import "node:module";
2
2
  import { satisfies } from "verkit";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
- import { styleText } from "node:util";
5
4
  import { mkdir, readFile, writeFile } from "node:fs/promises";
6
5
  import path, { dirname, isAbsolute, relative, resolve } from "node:path";
6
+ import { stripVTControlCharacters, styleText } from "node:util";
7
7
  import { ast, collectImportedRefNames, collectUsedSchemaNames, extractStringsFromNodes } from "@kubb/ast";
8
+ //#region \0rolldown/runtime.js
9
+ var __defProp = Object.defineProperty;
10
+ var __name = (target, value) => __defProp(target, "name", {
11
+ value,
12
+ configurable: true
13
+ });
14
+ //#endregion
8
15
  //#region package.json
9
- var version = "5.10.1";
16
+ var version = "5.10.3";
10
17
  //#endregion
11
18
  //#region ../../internals/utils/src/casing.ts
12
19
  /**
@@ -62,6 +69,35 @@ function toError(value) {
62
69
  function getErrorMessage(value) {
63
70
  return value instanceof Error ? value.message : String(value);
64
71
  }
72
+ /**
73
+ * Returns `error` followed by every error in its `cause` chain, outermost first. Stops at a
74
+ * non-`Error` cause or a cycle.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * getErrorChain(new Error('Config failed loading', { cause: new Error('boom') })).map((error) => error.message)
79
+ * // ['Config failed loading', 'boom']
80
+ * ```
81
+ */
82
+ function getErrorChain(error) {
83
+ const chain = [];
84
+ for (let current = error; current instanceof Error && !chain.includes(current); current = current.cause) chain.push(current);
85
+ return chain;
86
+ }
87
+ //#endregion
88
+ //#region ../../internals/utils/src/jsonPointer.ts
89
+ /**
90
+ * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
91
+ * name holding those characters maps to a distinct pointer.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * escapePointerToken('/pets/{id}') // '~1pets~1{id}'
96
+ * ```
97
+ */
98
+ function escapePointerToken(token) {
99
+ return token.replaceAll("~", "~0").replaceAll("/", "~1");
100
+ }
65
101
  //#endregion
66
102
  //#region ../../internals/utils/src/runtime.ts
67
103
  const isBun = typeof Bun !== "undefined";
@@ -262,375 +298,388 @@ function formatMs(ms) {
262
298
  return `${Math.round(ms)}ms`;
263
299
  }
264
300
  //#endregion
265
- //#region src/constants.ts
266
- /**
267
- * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
268
- * and stays stable so it can be referenced in tooling and (later) docs. Reference
269
- * these instead of inlining the string at a throw site.
270
- */
271
- const diagnosticCode = {
272
- /**
273
- * Fallback for an unstructured error with no specific code.
274
- */
275
- unknown: "KUBB_UNKNOWN",
276
- /**
277
- * The file or URL set as `input` could not be read.
278
- */
279
- inputNotFound: "KUBB_INPUT_NOT_FOUND",
280
- /**
281
- * A URL set as `input` (or referenced by a `$ref`) answered with a 4xx or 5xx status
282
- * instead of the document.
283
- */
284
- inputRequestFailed: "KUBB_INPUT_REQUEST_FAILED",
285
- /**
286
- * A URL set as `input` (or referenced by a `$ref`) never answered, so the request failed
287
- * before a status was returned.
288
- */
289
- inputUnreachable: "KUBB_INPUT_UNREACHABLE",
290
- /**
291
- * An adapter was configured without an `input`.
292
- */
293
- inputRequired: "KUBB_INPUT_REQUIRED",
294
- /**
295
- * `input` uses the v4 `{ path }` / `{ data }` wrapper, which v5 reads as a parsed
296
- * document instead of a pointer to one.
297
- */
298
- legacyInput: "KUBB_LEGACY_INPUT",
299
- /**
300
- * The parsed `input` carries no `openapi` or `swagger` version, so it is not a
301
- * document the adapter can read.
302
- */
303
- invalidDocument: "KUBB_INVALID_DOCUMENT",
304
- /** The document fails OpenAPI schema validation. Reported as a warning. */
305
- invalidSpec: "KUBB_INVALID_SPEC",
306
- /**
307
- * A `$ref` (or equivalent reference) could not be resolved in the source document.
308
- */
309
- refNotFound: "KUBB_REF_NOT_FOUND",
310
- /**
311
- * A server variable value is not allowed by its `enum`.
312
- */
313
- invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE",
314
- /**
315
- * A required plugin is missing from the config.
316
- */
317
- pluginNotFound: "KUBB_PLUGIN_NOT_FOUND",
318
- /**
319
- * The running Kubb or Node.js version is outside a plugin's `meta.compatibility`.
320
- */
321
- incompatibleExtension: "KUBB_INCOMPATIBLE_EXTENSION",
322
- /**
323
- * A required entry in a plugin's `dependencies` record is missing from the config.
324
- */
325
- missingDependency: "KUBB_MISSING_DEPENDENCY",
326
- /**
327
- * A dependency's `meta.version` is outside the range a plugin's `dependencies` record declares.
328
- */
329
- incompatibleDependency: "KUBB_INCOMPATIBLE_DEPENDENCY",
330
- /**
331
- * A plugin threw while generating.
332
- */
333
- pluginFailed: "KUBB_PLUGIN_FAILED",
334
- /** An adapter threw while parsing the input. */
335
- adapterFailed: "KUBB_ADAPTER_FAILED",
336
- /** A parser threw while printing a generated file. */
337
- parserFailed: "KUBB_PARSER_FAILED",
338
- /**
339
- * A plugin reported a non-fatal warning through `ctx.warn`.
340
- */
341
- pluginWarning: "KUBB_PLUGIN_WARNING",
342
- /**
343
- * A plugin reported an informational message through `ctx.info`.
344
- */
345
- pluginInfo: "KUBB_PLUGIN_INFO",
346
- /**
347
- * A `kubb/kit` composable was called outside a plugin's `setup` and `kubb:plugin:setup`.
348
- */
349
- noContext: "KUBB_NO_CONTEXT",
350
- /**
351
- * A schema uses a `format` Kubb does not map to a specific type. Reserved for
352
- * adapters to emit as a `warning`.
353
- */
354
- unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT",
355
- /**
356
- * A referenced schema or operation is marked `deprecated`. Reserved for adapters
357
- * to emit as an `info`.
358
- */
359
- deprecated: "KUBB_DEPRECATED",
360
- /**
361
- * An adapter is required but the config has none. The build cannot read the input
362
- * without one.
363
- */
364
- adapterRequired: "KUBB_ADAPTER_REQUIRED",
365
- /**
366
- * A resolved output path escapes the output directory, which can stem from a path
367
- * traversal in the spec or a misconfigured `group.name`.
368
- */
369
- pathTraversal: "KUBB_PATH_TRAVERSAL",
370
- /**
371
- * `output.clean` is enabled but `output.path` resolves to the project root or a parent of it,
372
- * so cleaning would delete kubb.config and every source file.
373
- */
374
- cleanRoot: "KUBB_CLEAN_ROOT",
375
- /**
376
- * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.
377
- */
378
- invalidPluginOptions: "KUBB_INVALID_PLUGIN_OPTIONS",
379
- /**
380
- * A post-generate command (`output.postGenerate`) exited with a failure.
381
- */
382
- postGenerateFailed: "KUBB_POST_GENERATE_FAILED",
383
- /**
384
- * The formatter pass over the generated files failed.
385
- */
386
- formatFailed: "KUBB_FORMAT_FAILED",
387
- /**
388
- * The linter pass over the generated files failed.
389
- */
390
- lintFailed: "KUBB_LINT_FAILED",
391
- /**
392
- * A barrel tries to re-export the same name from more than one module.
393
- */
394
- barrelDuplicateExport: "KUBB_BARREL_DUPLICATE_EXPORT",
395
- /**
396
- * Not a failure. Carries a plugin's elapsed time, summed into the run total.
397
- */
398
- performance: "KUBB_PERFORMANCE",
399
- /**
400
- * Not a failure. A newer Kubb version is available on npm.
401
- */
402
- updateAvailable: "KUBB_UPDATE_AVAILABLE"
403
- };
404
- //#endregion
405
- //#region src/Diagnostics.ts
406
- /**
407
- * Docs major version, derived from the package version so the link tracks the published major.
408
- */
409
- const docsMajor = version.split(".")[0] ?? "5";
410
- /**
411
- * Builds a type guard that narrows a {@link Diagnostic} to the variant for `kind`. A diagnostic
412
- * with no `kind` is treated as a `problem`.
413
- */
414
- function isKind(kind) {
415
- return (diagnostic) => (diagnostic.kind ?? "problem") === kind;
416
- }
417
- /**
418
- * Returns `true` when the diagnostic is a build {@link ProblemDiagnostic}.
419
- *
420
- * @example
421
- * ```ts
422
- * if (isProblem(diagnostic)) {
423
- * console.log(diagnostic.location)
424
- * }
425
- * ```
426
- */
427
- const isProblem = isKind("problem");
428
- /**
429
- * Returns `true` when the diagnostic is a per-plugin {@link PerformanceDiagnostic}.
430
- *
431
- * @example
432
- * ```ts
433
- * const timings = diagnostics.filter(isPerformance)
434
- * ```
435
- */
436
- const isPerformance = isKind("performance");
437
- /**
438
- * Returns `true` when the diagnostic is a version-update {@link UpdateDiagnostic}.
439
- *
440
- * @example
441
- * ```ts
442
- * if (isUpdate(diagnostic)) {
443
- * console.log(diagnostic.latestVersion)
444
- * }
445
- * ```
446
- */
447
- const isUpdate = isKind("update");
301
+ //#region src/diagnosticCatalog.ts
448
302
  /**
449
- * Accent color per severity. The color tints the `[CODE]` tag (red error, yellow warning,
450
- * blue info).
451
- */
452
- const severityStyle = {
453
- error: "red",
454
- warning: "yellow",
455
- info: "blue"
456
- };
457
- const failedCode = {
458
- plugin: diagnosticCode.pluginFailed,
459
- adapter: diagnosticCode.adapterFailed,
460
- parser: diagnosticCode.parserFailed
461
- };
462
- const countKey = {
463
- error: "errors",
464
- warning: "warnings",
465
- info: "infos"
466
- };
467
- /**
468
- * Explanation for every {@link diagnosticCode}. Use {@link Diagnostics.explain} to look one up
469
- * and `Diagnostics.docsUrl` for the matching kubb.dev page.
303
+ * Every diagnostic code with its docs entry: a short title, what triggers it, and how to resolve
304
+ * it. The source of truth for {@link diagnosticCode}, and the kubb.dev `/reference/diagnostics`
305
+ * sections mirror it.
470
306
  */
471
307
  const diagnosticCatalog = {
472
- [diagnosticCode.unknown]: {
308
+ unknown: {
309
+ code: "KUBB_UNKNOWN",
473
310
  title: "Unknown error",
474
311
  cause: "An error was thrown without a stable Kubb code, so it is reported as-is.",
475
312
  fix: "Read the underlying message and stack. If it comes from a plugin or adapter, check its configuration; otherwise report it as a possible Kubb bug."
476
313
  },
477
- [diagnosticCode.inputNotFound]: {
314
+ inputNotFound: {
315
+ code: "KUBB_INPUT_NOT_FOUND",
478
316
  title: "Input not found",
479
317
  cause: "The file set as `input` (or passed as `kubb generate PATH`) could not be read. A URL reports `KUBB_INPUT_REQUEST_FAILED` or `KUBB_INPUT_UNREACHABLE` instead.",
480
318
  fix: "Check that the path exists and is readable, then set it as `input` or pass it on the CLI."
481
319
  },
482
- [diagnosticCode.inputRequestFailed]: {
320
+ inputRequestFailed: {
321
+ code: "KUBB_INPUT_REQUEST_FAILED",
483
322
  title: "Input request failed",
484
323
  cause: "A URL set as `input` (or reached through a `$ref`) answered with a 4xx or 5xx status instead of the document.",
485
324
  fix: "Open the URL to see what the server returns. A 401 or 403 needs credentials Kubb does not send, so download the document and point `input` at the local file. A 404 means the path is wrong, and a 5xx means the server itself failed."
486
325
  },
487
- [diagnosticCode.inputUnreachable]: {
326
+ inputUnreachable: {
327
+ code: "KUBB_INPUT_UNREACHABLE",
488
328
  title: "Input unreachable",
489
329
  cause: "A URL set as `input` (or reached through a `$ref`) never answered, so the request failed before a status came back. A refused connection, an unknown host, an expired certificate, and a timeout all land here.",
490
330
  fix: "Check that the host is running and reachable from this machine. For a local server, start it and confirm the port matches the one in `input`."
491
331
  },
492
- [diagnosticCode.inputRequired]: {
332
+ inputRequired: {
333
+ code: "KUBB_INPUT_REQUIRED",
493
334
  title: "Input required",
494
335
  cause: "An adapter is configured but no `input` was provided.",
495
336
  fix: "Set `input` to a file path, a URL, an inline spec (JSON/YAML string), or a parsed object in your Kubb config."
496
337
  },
497
- [diagnosticCode.legacyInput]: {
338
+ legacyInput: {
339
+ code: "KUBB_LEGACY_INPUT",
498
340
  title: "Legacy input shape",
499
341
  cause: "`input` is a `{ path }` or `{ data }` wrapper, which v4 used to point at a document and v5 reads as the document itself.",
500
342
  fix: "Unwrap it: `input: { path: \"./petStore.yaml\" }` becomes `input: \"./petStore.yaml\"`, and `input: { data: spec }` becomes `input: spec`."
501
343
  },
502
- [diagnosticCode.invalidDocument]: {
344
+ invalidDocument: {
345
+ code: "KUBB_INVALID_DOCUMENT",
503
346
  title: "Invalid document",
504
- cause: "The parsed `input` has no `openapi` or `swagger` version field, so it is not an OpenAPI or Swagger document.",
505
- fix: "Point `input` at a document that declares `openapi` or `swagger`, and check that a passed object is the spec itself rather than a wrapper around it."
347
+ cause: "The `input` cannot be read as an OpenAPI or Swagger document: its JSON or YAML has a syntax error, it declares no `openapi` or `swagger` version field, or its version cannot be upgraded to OpenAPI 3.1.",
348
+ fix: "Fix the syntax at the reported line, and check that `input` points at the spec and not an HTML page. Point `input` at a Swagger 2.0, OpenAPI 3.0, or OpenAPI 3.1 document, and check that a passed object is the spec itself rather than a wrapper around it."
506
349
  },
507
- [diagnosticCode.invalidSpec]: {
350
+ invalidSpec: {
351
+ code: "KUBB_INVALID_SPEC",
508
352
  title: "Invalid spec",
509
353
  cause: "The document does not pass OpenAPI schema validation. Kubb still generates from it, but the output can be incomplete or wrong.",
510
354
  fix: "Fix the reported problem in the spec. Run `kubb validate` to list every error, or set `validate: false` on the adapter to skip the check."
511
355
  },
512
- [diagnosticCode.refNotFound]: {
356
+ refNotFound: {
357
+ code: "KUBB_REF_NOT_FOUND",
513
358
  title: "Reference not found",
514
359
  cause: "A `$ref` could not be resolved in the source document.",
515
360
  fix: "Add the missing definition (for example under `components.schemas`) or fix the `$ref`. Run `kubb validate` to check the spec."
516
361
  },
517
- [diagnosticCode.invalidServerVariable]: {
362
+ invalidServerVariable: {
363
+ code: "KUBB_INVALID_SERVER_VARIABLE",
518
364
  title: "Invalid server variable",
519
365
  cause: "A server variable value is not allowed by its `enum`.",
520
366
  fix: "Use one of the values listed in the server variable `enum`, or update the spec."
521
367
  },
522
- [diagnosticCode.pluginNotFound]: {
368
+ pluginNotFound: {
369
+ code: "KUBB_PLUGIN_NOT_FOUND",
523
370
  title: "Plugin not found",
524
371
  cause: "A plugin that another plugin depends on is missing from the config.",
525
372
  fix: "Add the required plugin to the `plugins` array in kubb.config.ts, or remove the dependency on it."
526
373
  },
527
- [diagnosticCode.incompatibleExtension]: {
374
+ incompatibleExtension: {
375
+ code: "KUBB_INCOMPATIBLE_EXTENSION",
528
376
  title: "Incompatible extension",
529
377
  cause: "A plugin sets `meta.compatibility.kubb` or `meta.compatibility.node` to a range the running Kubb or Node.js version does not satisfy.",
530
378
  fix: "Upgrade Kubb or Node.js to a version in the range, or install a version of the plugin that supports your current Kubb and Node.js."
531
379
  },
532
- [diagnosticCode.missingDependency]: {
380
+ missingDependency: {
381
+ code: "KUBB_MISSING_DEPENDENCY",
533
382
  title: "Missing dependency",
534
383
  cause: "A plugin lists another plugin in its `dependencies` record without `optional: true`, and the config does not include it.",
535
384
  fix: "Add the missing plugin to the `plugins` array in kubb.config.ts."
536
385
  },
537
- [diagnosticCode.incompatibleDependency]: {
386
+ incompatibleDependency: {
387
+ code: "KUBB_INCOMPATIBLE_DEPENDENCY",
538
388
  title: "Incompatible dependency",
539
389
  cause: "A plugin's `dependencies` record sets a `version` range, and the configured plugin's `meta.version` falls outside it.",
540
390
  fix: "Install a version of the dependency inside the range, or a version of the dependent plugin that supports the installed dependency."
541
391
  },
542
- [diagnosticCode.pluginFailed]: {
392
+ pluginFailed: {
393
+ code: "KUBB_PLUGIN_FAILED",
543
394
  title: "Plugin failed",
544
395
  cause: "A plugin threw while generating, or reported an error through `ctx.error`.",
545
- fix: "Read the underlying error and check the plugin options and the schema or operation it failed on. If the plugin crashed, rerun with `--verbose` for the stack trace and report it to the plugin maintainers, at the `report:` link when one is shown."
396
+ fix: "Check the plugin options and the node at `at:`. If it is a bug, report it with the stack at `report:`."
546
397
  },
547
- [diagnosticCode.adapterFailed]: {
398
+ adapterFailed: {
399
+ code: "KUBB_ADAPTER_FAILED",
548
400
  title: "Adapter failed",
549
401
  cause: "The adapter threw an unexpected error while parsing the input.",
550
- fix: "This is likely a bug in the adapter. Rerun with `--verbose` for the stack trace and report it to the adapter maintainers, at the `report:` link when one is shown."
402
+ fix: "Check the input and the adapter options. If it is a bug, report it with the stack at `report:`."
551
403
  },
552
- [diagnosticCode.parserFailed]: {
404
+ parserFailed: {
405
+ code: "KUBB_PARSER_FAILED",
553
406
  title: "Parser failed",
554
407
  cause: "A parser threw an unexpected error while printing a generated file.",
555
- fix: "This is likely a bug in the parser. Rerun with `--verbose` for the stack trace and report it to the parser maintainers, at the `report:` link when one is shown."
408
+ fix: "Check the parser options. If it is a bug, report it with the stack at `report:`."
556
409
  },
557
- [diagnosticCode.pluginWarning]: {
410
+ pluginWarning: {
411
+ code: "KUBB_PLUGIN_WARNING",
558
412
  title: "Plugin warning",
559
413
  cause: "A plugin reported a non-fatal warning through `ctx.warn`.",
560
414
  fix: "Review the message. It does not fail the build; adjust the plugin options or input if the warning is unwanted."
561
415
  },
562
- [diagnosticCode.pluginInfo]: {
416
+ pluginInfo: {
417
+ code: "KUBB_PLUGIN_INFO",
563
418
  title: "Plugin info",
564
419
  cause: "A plugin reported an informational message through `ctx.info`.",
565
420
  fix: "Informational only. No action is required."
566
421
  },
567
- [diagnosticCode.noContext]: {
422
+ noContext: {
423
+ code: "KUBB_NO_CONTEXT",
568
424
  title: "No plugin context",
569
425
  cause: "A `kubb/kit` composable such as `useKubb()` or `addGenerator()` ran outside the `setup` and `kubb:plugin:setup` of a plugin, so there is no plugin to apply it to.",
570
426
  fix: "Call the composable inside `setup` or `kubb:plugin:setup`, including in code that setup awaits."
571
427
  },
572
- [diagnosticCode.unsupportedFormat]: {
428
+ unsupportedFormat: {
429
+ code: "KUBB_UNSUPPORTED_FORMAT",
573
430
  title: "Unsupported format",
574
431
  cause: "A schema uses a `format` Kubb does not map to a specific type, so it falls back to the base type.",
575
432
  fix: "Use a format Kubb supports, or handle the custom format with a parser or plugin."
576
433
  },
577
- [diagnosticCode.deprecated]: {
434
+ deprecated: {
435
+ code: "KUBB_DEPRECATED",
578
436
  title: "Deprecated",
579
437
  cause: "A referenced schema or operation is marked `deprecated`.",
580
438
  fix: "Migrate off the deprecated definition if the warning is unwanted."
581
439
  },
582
- [diagnosticCode.adapterRequired]: {
440
+ adapterRequired: {
441
+ code: "KUBB_ADAPTER_REQUIRED",
583
442
  title: "Adapter required",
584
443
  cause: "An action needs an adapter but none is configured.",
585
444
  fix: "Set `adapter` in kubb.config.ts, for example `adapterOas()`."
586
445
  },
587
- [diagnosticCode.pathTraversal]: {
446
+ pathTraversal: {
447
+ code: "KUBB_PATH_TRAVERSAL",
588
448
  title: "Path traversal",
589
449
  cause: "A resolved output path escaped the output directory, which can stem from a path traversal in the spec or a misconfigured `group.name`.",
590
450
  fix: "Keep generated paths within the output directory. Review the `group.name` function and the names coming from the spec."
591
451
  },
592
- [diagnosticCode.cleanRoot]: {
452
+ cleanRoot: {
453
+ code: "KUBB_CLEAN_ROOT",
593
454
  title: "Clean targets the project root",
594
455
  cause: "`output.clean` is enabled and `output.path` resolves to the project root or a parent of it, so cleaning would delete `kubb.config` and every source file.",
595
456
  fix: "Point `output.path` at a subdirectory such as `./src/gen` so clean only removes generated code, or disable `output.clean`."
596
457
  },
597
- [diagnosticCode.invalidPluginOptions]: {
458
+ invalidPluginOptions: {
459
+ code: "KUBB_INVALID_PLUGIN_OPTIONS",
598
460
  title: "Invalid plugin options",
599
461
  cause: "A plugin was configured with options that cannot be honored, for example `output.mode: 'file'` paired with a `group` option.",
600
462
  fix: "Fix the plugin options. A single-file output has nothing to group, so remove the `group` option or use `output.mode: 'directory'`."
601
463
  },
602
- [diagnosticCode.postGenerateFailed]: {
464
+ postGenerateFailed: {
465
+ code: "KUBB_POST_GENERATE_FAILED",
603
466
  title: "Post-generate command failed",
604
467
  cause: "A post-generate command (`output.postGenerate`) exited with a non-zero status.",
605
468
  fix: "Check the command is installed and correct, and run it manually to see the error."
606
469
  },
607
- [diagnosticCode.formatFailed]: {
470
+ formatFailed: {
471
+ code: "KUBB_FORMAT_FAILED",
608
472
  title: "Format failed",
609
473
  cause: "The formatter pass over the generated files failed.",
610
474
  fix: "Check the formatter (oxfmt, biome, or prettier) is installed and its config is valid, then run it manually on the output."
611
475
  },
612
- [diagnosticCode.lintFailed]: {
476
+ lintFailed: {
477
+ code: "KUBB_LINT_FAILED",
613
478
  title: "Lint failed",
614
479
  cause: "The linter pass over the generated files failed.",
615
480
  fix: "Check the linter (oxlint, biome, or eslint) is installed and its config is valid, then run it manually on the output."
616
481
  },
617
- [diagnosticCode.barrelDuplicateExport]: {
482
+ barrelDuplicateExport: {
483
+ code: "KUBB_BARREL_DUPLICATE_EXPORT",
618
484
  title: "Duplicate barrel export",
619
485
  cause: "Two generated modules export the same name, so the barrel cannot re-export both.",
620
486
  fix: "Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names."
621
487
  },
622
- [diagnosticCode.performance]: {
488
+ internalError: {
489
+ code: "KUBB_INTERNAL_ERROR",
490
+ title: "Internal error",
491
+ cause: "Kubb hit a state it does not expect. This is a bug in Kubb, not in your config.",
492
+ fix: "Rerun with `--reporter json` or `--verbose` to get the stack. Then report it at https://github.com/kubb-labs/kubb/issues with the spec and config that trigger it."
493
+ },
494
+ performance: {
495
+ code: "KUBB_PERFORMANCE",
623
496
  title: "Performance",
624
497
  cause: "Not a failure. Records a plugin’s elapsed time, summed into the run total.",
625
498
  fix: "No action. This is an informational metric."
626
499
  },
627
- [diagnosticCode.updateAvailable]: {
500
+ updateAvailable: {
501
+ code: "KUBB_UPDATE_AVAILABLE",
628
502
  title: "Update available",
629
503
  cause: "A newer Kubb version is published on npm than the one running.",
630
504
  fix: "Update the `@kubb/*` packages, for example `npm install -g @kubb/cli`, to get the latest fixes."
631
505
  }
632
506
  };
633
507
  /**
508
+ * Stable codes Kubb attaches to a `Diagnostic`, derived from {@link diagnosticCatalog}. Reference
509
+ * these instead of inlining the string at a throw site.
510
+ */
511
+ const diagnosticCode = Object.fromEntries(Object.entries(diagnosticCatalog).map(([key, { code }]) => [key, code]));
512
+ //#endregion
513
+ //#region src/formatDiagnostic.ts
514
+ /** A problem's `help`, or the catalog fix. `info` gets none: its catalog fix says no action is needed. */
515
+ function getHelp(problem) {
516
+ if (problem.help || problem.severity === "info") return problem.help;
517
+ return Diagnostics.explain(problem.code).fix;
518
+ }
519
+ /** The `cause` chain's messages, outermost first, minus those `message` already contains. */
520
+ function getCauses(problem) {
521
+ return getErrorChain(problem.cause).filter((error) => !problem.message.includes(error.message)).map((error) => error.name === "Error" ? error.message : `${error.name}: ${error.message}`);
522
+ }
523
+ /**
524
+ * Accent color per severity, which paints the icon in the `human` layout (red error, yellow
525
+ * warning, blue info). The code itself stays dim.
526
+ */
527
+ const severityStyle = {
528
+ error: "red",
529
+ warning: "yellow",
530
+ info: "blue"
531
+ };
532
+ const severityIcon = {
533
+ error: "✖",
534
+ warning: "⚠",
535
+ info: "ℹ"
536
+ };
537
+ /**
538
+ * Codes for a crash, which carry the root cause's stack.
539
+ */
540
+ const crashCodes = /* @__PURE__ */ new Set([
541
+ diagnosticCode.unknown,
542
+ diagnosticCode.internalError,
543
+ diagnosticCode.pluginFailed,
544
+ diagnosticCode.adapterFailed,
545
+ diagnosticCode.parserFailed
546
+ ]);
547
+ /**
548
+ * Reduces a diagnostic to its JSON-safe fields plus a `docsUrl`, for machine-readable
549
+ * consumers, with the catalog `title`, `cause`, and fix, and the `cause` error as `causes` and
550
+ * `stack`. Absent optional fields are omitted.
551
+ */
552
+ function serialize(diagnostic) {
553
+ const problem = Diagnostics.isProblem(diagnostic) ? diagnostic : void 0;
554
+ const doc = problem ? Diagnostics.explain(problem.code) : void 0;
555
+ const help = problem ? getHelp(problem) : void 0;
556
+ const causes = problem ? getCauses(problem) : [];
557
+ const stack = problem && crashCodes.has(problem.code) ? getErrorChain(problem.cause).at(-1)?.stack : void 0;
558
+ return {
559
+ code: diagnostic.code,
560
+ severity: diagnostic.severity,
561
+ message: diagnostic.message,
562
+ ...doc ? {
563
+ title: doc.title,
564
+ cause: doc.cause
565
+ } : {},
566
+ ...problem?.location ? { location: problem.location } : {},
567
+ ...help ? { help } : {},
568
+ ...problem?.plugin ? { plugin: problem.plugin } : {},
569
+ ...problem?.issues ? { issues: problem.issues } : {},
570
+ ...causes.length ? { causes } : {},
571
+ ...stack ? { stack } : {},
572
+ ...diagnostic.code === diagnosticCode.unknown ? {} : { docsUrl: Diagnostics.docsUrl(diagnostic.code) }
573
+ };
574
+ }
575
+ /**
576
+ * Renders a {@link Diagnostic} as lines: a headline, then indented detail rows. `agent` (the default)
577
+ * prints uncolored labeled rows (`at:`, `why:`, `fix:`, `caused by:`, `stack:` for a crash,
578
+ * `docs:`, `report:`). `human` prints an icon, the bold message, and the code aligned to `width`,
579
+ * with `at`, `fix`, the first cause, and `Learn more` for an error, and one row for a warning.
580
+ * `verbose` adds the full stack.
581
+ */
582
+ function format(diagnostic, { audience = "agent", verbose = false, width = 0 } = {}) {
583
+ const { code, severity, message } = diagnostic;
584
+ const problem = Diagnostics.isProblem(diagnostic) ? diagnostic : void 0;
585
+ const human = audience === "human";
586
+ const paint = human ? styleText : (_format, text) => text;
587
+ const [firstLine = "", ...rest] = message.split("\n");
588
+ const pointer = problem?.location && "pointer" in problem.location ? problem.location.pointer : void 0;
589
+ const help = problem ? getHelp(problem) : void 0;
590
+ const causes = problem ? getCauses(problem) : [];
591
+ const crash = problem ? crashCodes.has(problem.code) : false;
592
+ const frames = (problem && (verbose || !human && crash) ? getErrorChain(problem.cause).at(-1)?.stack : void 0)?.split("\n").slice(1, verbose ? void 0 : 4).map((frame) => ` ${paint("dim", frame.trim())}`);
593
+ if (!human) {
594
+ const details = rest.filter((line) => line.trim()).map((line) => ` ${line}`);
595
+ const row = (label, value) => {
596
+ if (value) details.push(` ${label}: ${value}`);
597
+ };
598
+ row("at", pointer);
599
+ if (problem && severity !== "info") row("why", Diagnostics.explain(problem.code).cause);
600
+ row("fix", help);
601
+ for (const cause of causes) row("caused by", cause);
602
+ if (frames?.length) details.push(" stack:", ...frames);
603
+ if (code !== diagnosticCode.unknown) row("docs", Diagnostics.docsUrl(code));
604
+ row("report", problem?.issues);
605
+ return [`${severity} ${code}${problem?.plugin ? ` [${problem.plugin}]` : ""}: ${firstLine}`, ...details];
606
+ }
607
+ const left = `${paint(severityStyle[severity], severityIcon[severity])} ${paint("bold", firstLine)}`;
608
+ const gap = Math.max(2, width - stripVTControlCharacters(left).length - code.length);
609
+ const headline = `${left}${" ".repeat(gap)}${paint("dim", code)}`;
610
+ const frame = rest.filter((line) => line.trim()).map((line) => ` ${paint("dim", line)}`);
611
+ const label = (name) => paint("dim", name.length < 4 ? name.padEnd(4) : `${name} `);
612
+ const rows = [["at", pointer && paint("cyan", pointer)], ["fix", help]];
613
+ if (severity !== "error") {
614
+ const first = rows.find(([, value]) => value);
615
+ return [
616
+ headline,
617
+ ...frame,
618
+ ...first ? [` ${label(first[0])} ${first[1]}`] : []
619
+ ];
620
+ }
621
+ rows.push(["caused by", causes[0]], ["report", problem?.issues && paint("cyan", problem.issues)]);
622
+ const details = [...frame, ...rows.flatMap(([name, value]) => value ? [` ${label(name)} ${value}`] : [])];
623
+ if (frames?.length) details.push(` ${paint("dim", "stack")}`, ...frames);
624
+ if (code !== diagnosticCode.unknown) details.push(` ${paint("dim", "↳ Learn more")} ${paint("cyan", Diagnostics.docsUrl(code).replace(/^https:\/\//, ""))}`);
625
+ return [headline, ...details];
626
+ }
627
+ //#endregion
628
+ //#region src/Diagnostics.ts
629
+ /**
630
+ * Docs major version, derived from the package version so the link tracks the published major.
631
+ */
632
+ const docsMajor = version.split(".")[0] ?? "5";
633
+ /**
634
+ * Builds a type guard that narrows a {@link Diagnostic} to the variant for `kind`. A diagnostic
635
+ * with no `kind` is treated as a `problem`.
636
+ */
637
+ function isKind(kind) {
638
+ return (diagnostic) => (diagnostic.kind ?? "problem") === kind;
639
+ }
640
+ /**
641
+ * Returns `true` when the diagnostic is a build {@link ProblemDiagnostic}.
642
+ *
643
+ * @example
644
+ * ```ts
645
+ * if (isProblem(diagnostic)) {
646
+ * console.log(diagnostic.location)
647
+ * }
648
+ * ```
649
+ */
650
+ const isProblem = isKind("problem");
651
+ /**
652
+ * Returns `true` when the diagnostic is a per-plugin {@link PerformanceDiagnostic}.
653
+ *
654
+ * @example
655
+ * ```ts
656
+ * const timings = diagnostics.filter(isPerformance)
657
+ * ```
658
+ */
659
+ const isPerformance = isKind("performance");
660
+ /**
661
+ * Returns `true` when the diagnostic is a version-update {@link UpdateDiagnostic}.
662
+ *
663
+ * @example
664
+ * ```ts
665
+ * if (isUpdate(diagnostic)) {
666
+ * console.log(diagnostic.latestVersion)
667
+ * }
668
+ * ```
669
+ */
670
+ const isUpdate = isKind("update");
671
+ const failedCode = {
672
+ plugin: diagnosticCode.pluginFailed,
673
+ adapter: diagnosticCode.adapterFailed,
674
+ parser: diagnosticCode.parserFailed
675
+ };
676
+ const docsByCode = new Map(Object.values(diagnosticCatalog).map((doc) => [doc.code, doc]));
677
+ const countKey = {
678
+ error: "errors",
679
+ warning: "warnings",
680
+ info: "infos"
681
+ };
682
+ /**
634
683
  * Static helpers for working with {@link Diagnostic}s, plus the run-scoped sink
635
684
  * that lets deep code report a diagnostic without threading a callback.
636
685
  *
@@ -705,27 +754,37 @@ var Diagnostics = class Diagnostics {
705
754
  return true;
706
755
  }
707
756
  /**
708
- * Emits a diagnostic on the run's `kubb:diagnostic` hook so the loggers render it live.
709
- * Use it instead of calling `hooks.callHook('kubb:diagnostic', ...)` directly. To collect a
710
- * diagnostic into the build result from deep in a run, use {@link Diagnostics.report} instead.
757
+ * Emits a diagnostic, or any thrown value, on the run's hooks so the loggers render it live.
758
+ * A coded problem or an update goes to `kubb:diagnostic`. An uncoded one goes to `kubb:error`
759
+ * whole, so every wrapper's message, its help, and the stack survive. To collect a diagnostic into
760
+ * the build result from deep in a run, use {@link Diagnostics.report} instead.
711
761
  */
712
- static async emit(hooks, diagnostic) {
713
- await hooks.callHook("kubb:diagnostic", { diagnostic });
762
+ static async emit(hooks, value) {
763
+ const thrown = value instanceof Error || typeof value?.code !== "string";
764
+ const diagnostic = thrown ? Diagnostics.from(value) : value;
765
+ if (!isProblem(diagnostic) || diagnostic.code !== diagnosticCode.unknown) {
766
+ await hooks.callHook("kubb:diagnostic", { diagnostic });
767
+ return;
768
+ }
769
+ const { cause, help, message } = diagnostic;
770
+ const bare = cause && !help && cause.message === message;
771
+ await hooks.callHook("kubb:error", { error: thrown ? toError(value) : bare ? cause : new Diagnostics.Error(diagnostic) });
714
772
  }
715
773
  /**
716
774
  * Coerces any thrown value into a {@link ProblemDiagnostic}. A {@link Diagnostics.Error}
717
775
  * keeps its structured data, and anything else becomes a `KUBB_UNKNOWN` error.
718
776
  */
719
777
  static from(error) {
720
- const seen = /* @__PURE__ */ new Set();
721
- let current = error;
722
- let root;
723
- while (current instanceof Error && !seen.has(current)) {
724
- if (Diagnostics.isError(current)) return current.diagnostic;
725
- seen.add(current);
726
- root = current;
727
- current = current.cause;
778
+ const chain = getErrorChain(error);
779
+ const coded = chain.findLast((current) => Diagnostics.isError(current));
780
+ if (coded) {
781
+ const { diagnostic } = coded;
782
+ return diagnostic.cause || !crashCodes.has(diagnostic.code) ? diagnostic : {
783
+ ...diagnostic,
784
+ cause: coded
785
+ };
728
786
  }
787
+ const root = chain.at(-1);
729
788
  return {
730
789
  code: diagnosticCode.unknown,
731
790
  severity: "error",
@@ -743,7 +802,9 @@ var Diagnostics = class Diagnostics {
743
802
  const diagnostic = Diagnostics.from(error);
744
803
  const name = extension.meta?.name ?? extension.name;
745
804
  const owner = kind === "plugin" ? { plugin: diagnostic.plugin ?? name } : {};
746
- if (diagnostic.code !== diagnosticCode.unknown || diagnostic.cause?.name === "AbortError") return {
805
+ const crashed = diagnostic.code === diagnosticCode.unknown && diagnostic.cause?.name !== "AbortError";
806
+ const failed = Object.values(failedCode).includes(diagnostic.code);
807
+ if (!crashed && !failed) return {
747
808
  ...diagnostic,
748
809
  ...owner
749
810
  };
@@ -751,8 +812,8 @@ var Diagnostics = class Diagnostics {
751
812
  return {
752
813
  ...diagnostic,
753
814
  ...owner,
754
- code: failedCode[kind],
755
- message: kind === "plugin" ? diagnostic.message : `${kind} "${name}" crashed: ${diagnostic.message}`,
815
+ code: crashed ? failedCode[kind] : diagnostic.code,
816
+ message: crashed && kind !== "plugin" ? `${kind} "${name}" crashed: ${diagnostic.message}` : diagnostic.message,
756
817
  ...issues ? { issues } : {}
757
818
  };
758
819
  }
@@ -843,71 +904,33 @@ var Diagnostics = class Diagnostics {
843
904
  * `/reference/diagnostics#<slug>` section.
844
905
  */
845
906
  static explain(code) {
846
- return diagnosticCatalog[code];
907
+ return docsByCode.get(code);
847
908
  }
848
909
  /**
849
910
  * Reduces a diagnostic to its JSON-safe fields plus a `docsUrl`, for machine-readable
850
- * consumers. The `cause`, `kind`, and `duration` are dropped, and absent optional
851
- * fields are omitted rather than set to `undefined`.
911
+ * consumers, with the catalog `title`, `cause`, and fix, and the `cause` error as `causes` and
912
+ * `stack`. Absent optional fields are omitted.
852
913
  */
853
- static serialize(diagnostic) {
854
- const problem = isProblem(diagnostic) ? diagnostic : void 0;
855
- return {
856
- code: diagnostic.code,
857
- severity: diagnostic.severity,
858
- message: diagnostic.message,
859
- ...problem?.location ? { location: problem.location } : {},
860
- ...problem?.help ? { help: problem.help } : {},
861
- ...problem?.plugin ? { plugin: problem.plugin } : {},
862
- ...problem?.issues ? { issues: problem.issues } : {},
863
- ...diagnostic.code === diagnosticCode.unknown ? {} : { docsUrl: Diagnostics.docsUrl(diagnostic.code) }
864
- };
865
- }
866
- /**
867
- * Renders a {@link Diagnostic} for terminal output as its parts: the `headline`
868
- * (`[CODE] plugin: message`, with the code in the severity color) and the indented `details`
869
- * rows (`at:` pointer, `fix:` help, `see:` docs link, `report:` issue tracker).
870
- *
871
- * Hosts compose these to fit their gutter: a clack logger passes `[headline, ...details]` as the
872
- * message with no gutter symbol, while plain text outputs use {@link Diagnostics.formatLines}.
873
- */
874
- static format(diagnostic) {
875
- const { code, severity, message } = diagnostic;
876
- const color = severityStyle[severity];
877
- const problem = isProblem(diagnostic) ? diagnostic : void 0;
878
- const tag = styleText(color, styleText("bold", `[${code}]`));
879
- const headline = problem?.plugin ? `${tag} ${problem.plugin}: ${message}` : `${tag}: ${message}`;
880
- const details = [];
881
- if (problem?.location && "pointer" in problem.location) details.push(` ${styleText("dim", "at:")} ${styleText("cyan", problem.location.pointer)}`);
882
- if (problem?.help) details.push(` ${styleText("cyan", "fix:")} ${problem.help}`);
883
- if (code !== diagnosticCode.unknown) details.push(` ${styleText("dim", "see:")} ${styleText("cyan", Diagnostics.docsUrl(code))}`);
884
- if (problem?.issues) details.push(` ${styleText("dim", "report:")} ${styleText("cyan", problem.issues)}`);
885
- return {
886
- headline,
887
- details
888
- };
889
- }
914
+ static serialize = serialize;
890
915
  /**
891
- * The self-contained block form of {@link Diagnostics.format}: the `headline` followed by the
892
- * indented detail rows. Used where there is no gutter (plain and file output).
916
+ * Renders a diagnostic as lines for an `agent` (the default) or a `human`. See {@link format}.
893
917
  */
894
- static formatLines(diagnostic) {
895
- const { headline, details } = Diagnostics.format(diagnostic);
896
- return [headline, ...details];
897
- }
918
+ static format = format;
898
919
  };
899
920
  //#endregion
900
921
  //#region src/defineExtension.ts
901
922
  /**
902
923
  * Returns `meta`, or `{ name }` for an extension that only sets the deprecated `name`. Throws `KUBB_INVALID_PLUGIN_OPTIONS` when it has neither.
903
924
  */
904
- function resolveMeta({ name, meta }) {
925
+ function resolveMeta(extension) {
926
+ const { name, meta } = extension;
905
927
  if (meta) return meta;
906
928
  if (name) return { name };
929
+ const keys = Object.keys(extension);
907
930
  throw new Diagnostics.Error({
908
931
  code: Diagnostics.code.invalidPluginOptions,
909
932
  severity: "error",
910
- message: "An extension has neither `meta.name` nor `name`.",
933
+ message: `An extension has neither \`meta.name\` nor \`name\`. Received keys: ${keys.length ? keys.join(", ") : "none"}.`,
911
934
  help: "Set `meta: { name }` on the plugin or parser.",
912
935
  location: { kind: "config" }
913
936
  });
@@ -1744,7 +1767,13 @@ async function parseCopy(file, parser) {
1744
1767
  try {
1745
1768
  content = await readFile(file.copy, "utf8");
1746
1769
  } catch (err) {
1747
- throw new Error(`[kubb] Could not copy file into output: ${file.copy}`, { cause: err });
1770
+ throw new Diagnostics.Error({
1771
+ code: Diagnostics.code.pluginFailed,
1772
+ severity: "error",
1773
+ message: `Could not copy file into output: ${file.copy}`,
1774
+ help: "Check that the file a plugin copies into the output exists and is readable.",
1775
+ cause: toError(err)
1776
+ });
1748
1777
  }
1749
1778
  if (parser?.copy) return runParser(parser, () => parser.parse(ast.factory.createFile(parser.copy(file, content))));
1750
1779
  return [
@@ -1986,6 +2015,50 @@ function normalizePluginOptions(rawOptions, pluginName) {
1986
2015
  });
1987
2016
  return options;
1988
2017
  }
2018
+ /**
2019
+ * Throws `KUBB_INVALID_PLUGIN_OPTIONS` when a `plugins` entry is not a plugin object, such as a
2020
+ * factory passed without calling it.
2021
+ */
2022
+ function assertPluginEntry(entry, index) {
2023
+ if (typeof entry === "function") {
2024
+ const name = entry.name || "pluginName";
2025
+ throw new Diagnostics.Error({
2026
+ code: Diagnostics.code.invalidPluginOptions,
2027
+ severity: "error",
2028
+ message: `plugins[${index}] is a function ("${name}"), not a plugin.`,
2029
+ help: `Call it: ${name}()`,
2030
+ location: { kind: "config" }
2031
+ });
2032
+ }
2033
+ if (typeof entry !== "object" || entry === null) throw new Diagnostics.Error({
2034
+ code: Diagnostics.code.invalidPluginOptions,
2035
+ severity: "error",
2036
+ message: `plugins[${index}] is ${entry === null ? "null" : typeof entry}, not a plugin.`,
2037
+ help: "Pass the result of a plugin factory, for example `pluginTs()`, or remove the entry.",
2038
+ location: { kind: "config" }
2039
+ });
2040
+ }
2041
+ /**
2042
+ * Where a generator crashed: a label naming the node and, when the node has one, a JSON pointer
2043
+ * to it. Built from the node fields so core stays adapter-agnostic.
2044
+ */
2045
+ function getCrashSite(node) {
2046
+ if (node.kind === "Schema") return node.name ? {
2047
+ label: `schema "${node.name}"`,
2048
+ location: {
2049
+ kind: "schema",
2050
+ pointer: `#/components/schemas/${escapePointerToken(node.name)}`
2051
+ }
2052
+ } : { label: "an inline schema" };
2053
+ if (!node.method || !node.path) return { label: `operation "${node.operationId}"` };
2054
+ return {
2055
+ label: `operation "${node.operationId}" (${node.method.toUpperCase()} ${node.path})`,
2056
+ location: {
2057
+ kind: "operation",
2058
+ pointer: `#/paths/${escapePointerToken(node.path)}/${node.method.toLowerCase()}`
2059
+ }
2060
+ };
2061
+ }
1989
2062
  var KubbDriver = class {
1990
2063
  config;
1991
2064
  options;
@@ -2025,9 +2098,19 @@ var KubbDriver = class {
2025
2098
  async setup() {
2026
2099
  this.options.signal?.throwIfAborted();
2027
2100
  for (const extension of [this.config.adapter, ...this.config.parsers]) if (extension?.meta) checkCompatibility(extension.meta);
2028
- const normalized = this.#sortPlugins(this.config.plugins.map((rawPlugin) => {
2101
+ const names = /* @__PURE__ */ new Set();
2102
+ const normalized = this.#sortPlugins(this.config.plugins.map((rawPlugin, index) => {
2103
+ assertPluginEntry(rawPlugin, index);
2029
2104
  if (rawPlugin.meta) checkCompatibility(rawPlugin.meta);
2030
2105
  const meta = resolveMeta(rawPlugin);
2106
+ if (names.has(meta.name)) throw new Diagnostics.Error({
2107
+ code: Diagnostics.code.invalidPluginOptions,
2108
+ severity: "error",
2109
+ message: `Plugin "${meta.name}" appears twice in \`plugins\`.`,
2110
+ help: "Remove the duplicate, or merge its options.",
2111
+ location: { kind: "config" }
2112
+ });
2113
+ names.add(meta.name);
2031
2114
  return {
2032
2115
  name: meta.name,
2033
2116
  meta,
@@ -2353,7 +2436,8 @@ var KubbDriver = class {
2353
2436
  operationGenerators: generators.filter((generator) => generator.operation),
2354
2437
  operationsGenerators: generators.filter((generator) => generator.operations),
2355
2438
  pluginOperations: [],
2356
- error: null
2439
+ error: null,
2440
+ crashSite: void 0
2357
2441
  };
2358
2442
  });
2359
2443
  const resolveForPlugin = (state, node) => {
@@ -2397,6 +2481,7 @@ var KubbDriver = class {
2397
2481
  await this.hooks.callHook("kubb:generate:schema", transformedNode, ctx);
2398
2482
  } catch (caughtError) {
2399
2483
  state.error = toError(caughtError);
2484
+ state.crashSite = getCrashSite(node);
2400
2485
  }
2401
2486
  }
2402
2487
  }
@@ -2425,6 +2510,7 @@ var KubbDriver = class {
2425
2510
  }
2426
2511
  } catch (caughtError) {
2427
2512
  state.error = toError(caughtError);
2513
+ state.crashSite = getCrashSite(node);
2428
2514
  }
2429
2515
  }
2430
2516
  }
@@ -2453,10 +2539,18 @@ var KubbDriver = class {
2453
2539
  success: !state.error,
2454
2540
  error: state.error ?? void 0
2455
2541
  });
2456
- if (state.error) diagnostics.push(Diagnostics.fromExtension(state.error, {
2457
- kind: "plugin",
2458
- extension: state.plugin
2459
- }));
2542
+ if (state.error) {
2543
+ const diagnostic = Diagnostics.fromExtension(state.error, {
2544
+ kind: "plugin",
2545
+ extension: state.plugin
2546
+ });
2547
+ const site = diagnostic.code === Diagnostics.code.pluginFailed && !diagnostic.location ? state.crashSite : void 0;
2548
+ diagnostics.push(site ? {
2549
+ ...diagnostic,
2550
+ message: `While generating ${site.label}: ${diagnostic.message}`,
2551
+ ...site.location ? { location: site.location } : {}
2552
+ } : diagnostic);
2553
+ }
2460
2554
  diagnostics.push(Diagnostics.performance({
2461
2555
  plugin: state.plugin.name,
2462
2556
  duration
@@ -2531,27 +2625,37 @@ var KubbDriver = class {
2531
2625
  get resolver() {
2532
2626
  return driver.getResolver(plugin.name);
2533
2627
  },
2534
- warn(message) {
2628
+ warn(message, options) {
2535
2629
  report({
2536
2630
  code: Diagnostics.code.pluginWarning,
2537
2631
  severity: "warning",
2538
- message
2632
+ message,
2633
+ ...options
2539
2634
  });
2540
2635
  },
2541
- error(error) {
2636
+ error(error, options) {
2637
+ if (Diagnostics.isError(error)) {
2638
+ report({
2639
+ ...options,
2640
+ ...error.diagnostic
2641
+ });
2642
+ return;
2643
+ }
2542
2644
  const cause = typeof error === "string" ? void 0 : error;
2543
2645
  report({
2544
2646
  code: Diagnostics.code.pluginFailed,
2545
2647
  severity: "error",
2546
2648
  message: typeof error === "string" ? error : error.message,
2547
- cause
2649
+ cause,
2650
+ ...options
2548
2651
  });
2549
2652
  },
2550
- info(message) {
2653
+ info(message, options) {
2551
2654
  report({
2552
2655
  code: Diagnostics.code.pluginInfo,
2553
2656
  severity: "info",
2554
- message
2657
+ message,
2658
+ ...options
2555
2659
  });
2556
2660
  }
2557
2661
  };
@@ -2689,6 +2793,6 @@ var Hookable = class {
2689
2793
  }
2690
2794
  };
2691
2795
  //#endregion
2692
- export { inParallel as A, useOptions as C, Diagnostics as D, resolveMeta as E, toError as F, camelCase as I, read as M, toPosixPath as N, formatMs as O, write as P, useKubb as S, defineExtension as T, setMacros as _, FileManager as a, tryUseKubb as b, Resolver as c, definePlugin as d, addGenerator as f, requirePlugin as g, kubbContext as h, createNodeCache as i, isPathInside as j, getElapsedMs as k, getInputKind as l, injectFile as m, KubbDriver as n, _usingCtx as o, addMacro as p, pluginNotFoundError as r, createResolver as s, Hookable as t, assertPluginOption as u, setOptions as v, checkCompatibility as w, useConfig as x, setResolver as y };
2796
+ export { inParallel as A, useOptions as C, Diagnostics as D, resolveMeta as E, toError as F, camelCase as I, __name as L, read as M, toPosixPath as N, formatMs as O, write as P, useKubb as S, defineExtension as T, setMacros as _, FileManager as a, tryUseKubb as b, Resolver as c, definePlugin as d, addGenerator as f, requirePlugin as g, kubbContext as h, createNodeCache as i, isPathInside as j, getElapsedMs as k, getInputKind as l, injectFile as m, KubbDriver as n, _usingCtx as o, addMacro as p, pluginNotFoundError as r, createResolver as s, Hookable as t, assertPluginOption as u, setOptions as v, checkCompatibility as w, useConfig as x, setResolver as y };
2693
2797
 
2694
- //# sourceMappingURL=Hookable-ZX2OqaPh.js.map
2798
+ //# sourceMappingURL=Hookable-BsWZ8pHO.js.map