@kubb/core 5.10.0 → 5.10.2

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,12 @@
1
1
  import "./rolldown-runtime-CRm0XQPb.js";
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
8
  //#region package.json
9
- var version = "5.10.0";
9
+ var version = "5.10.2";
10
10
  //#endregion
11
11
  //#region ../../internals/utils/src/casing.ts
12
12
  /**
@@ -62,6 +62,35 @@ function toError(value) {
62
62
  function getErrorMessage(value) {
63
63
  return value instanceof Error ? value.message : String(value);
64
64
  }
65
+ /**
66
+ * Returns `error` followed by every error in its `cause` chain, outermost first. Stops at a
67
+ * non-`Error` cause or a cycle.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * getErrorChain(new Error('Config failed loading', { cause: new Error('boom') })).map((error) => error.message)
72
+ * // ['Config failed loading', 'boom']
73
+ * ```
74
+ */
75
+ function getErrorChain(error) {
76
+ const chain = [];
77
+ for (let current = error; current instanceof Error && !chain.includes(current); current = current.cause) chain.push(current);
78
+ return chain;
79
+ }
80
+ //#endregion
81
+ //#region ../../internals/utils/src/jsonPointer.ts
82
+ /**
83
+ * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
84
+ * name holding those characters maps to a distinct pointer.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * escapePointerToken('/pets/{id}') // '~1pets~1{id}'
89
+ * ```
90
+ */
91
+ function escapePointerToken(token) {
92
+ return token.replaceAll("~", "~0").replaceAll("/", "~1");
93
+ }
65
94
  //#endregion
66
95
  //#region ../../internals/utils/src/runtime.ts
67
96
  const isBun = typeof Bun !== "undefined";
@@ -262,375 +291,388 @@ function formatMs(ms) {
262
291
  return `${Math.round(ms)}ms`;
263
292
  }
264
293
  //#endregion
265
- //#region src/constants.ts
294
+ //#region src/diagnosticCatalog.ts
266
295
  /**
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");
448
- /**
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.
296
+ * Every diagnostic code with its docs entry: a short title, what triggers it, and how to resolve
297
+ * it. The source of truth for {@link diagnosticCode}, and the kubb.dev `/reference/diagnostics`
298
+ * sections mirror it.
470
299
  */
471
300
  const diagnosticCatalog = {
472
- [diagnosticCode.unknown]: {
301
+ unknown: {
302
+ code: "KUBB_UNKNOWN",
473
303
  title: "Unknown error",
474
304
  cause: "An error was thrown without a stable Kubb code, so it is reported as-is.",
475
305
  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
306
  },
477
- [diagnosticCode.inputNotFound]: {
307
+ inputNotFound: {
308
+ code: "KUBB_INPUT_NOT_FOUND",
478
309
  title: "Input not found",
479
310
  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
311
  fix: "Check that the path exists and is readable, then set it as `input` or pass it on the CLI."
481
312
  },
482
- [diagnosticCode.inputRequestFailed]: {
313
+ inputRequestFailed: {
314
+ code: "KUBB_INPUT_REQUEST_FAILED",
483
315
  title: "Input request failed",
484
316
  cause: "A URL set as `input` (or reached through a `$ref`) answered with a 4xx or 5xx status instead of the document.",
485
317
  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
318
  },
487
- [diagnosticCode.inputUnreachable]: {
319
+ inputUnreachable: {
320
+ code: "KUBB_INPUT_UNREACHABLE",
488
321
  title: "Input unreachable",
489
322
  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
323
  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
324
  },
492
- [diagnosticCode.inputRequired]: {
325
+ inputRequired: {
326
+ code: "KUBB_INPUT_REQUIRED",
493
327
  title: "Input required",
494
328
  cause: "An adapter is configured but no `input` was provided.",
495
329
  fix: "Set `input` to a file path, a URL, an inline spec (JSON/YAML string), or a parsed object in your Kubb config."
496
330
  },
497
- [diagnosticCode.legacyInput]: {
331
+ legacyInput: {
332
+ code: "KUBB_LEGACY_INPUT",
498
333
  title: "Legacy input shape",
499
334
  cause: "`input` is a `{ path }` or `{ data }` wrapper, which v4 used to point at a document and v5 reads as the document itself.",
500
335
  fix: "Unwrap it: `input: { path: \"./petStore.yaml\" }` becomes `input: \"./petStore.yaml\"`, and `input: { data: spec }` becomes `input: spec`."
501
336
  },
502
- [diagnosticCode.invalidDocument]: {
337
+ invalidDocument: {
338
+ code: "KUBB_INVALID_DOCUMENT",
503
339
  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."
340
+ 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.",
341
+ 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
342
  },
507
- [diagnosticCode.invalidSpec]: {
343
+ invalidSpec: {
344
+ code: "KUBB_INVALID_SPEC",
508
345
  title: "Invalid spec",
509
346
  cause: "The document does not pass OpenAPI schema validation. Kubb still generates from it, but the output can be incomplete or wrong.",
510
347
  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
348
  },
512
- [diagnosticCode.refNotFound]: {
349
+ refNotFound: {
350
+ code: "KUBB_REF_NOT_FOUND",
513
351
  title: "Reference not found",
514
352
  cause: "A `$ref` could not be resolved in the source document.",
515
353
  fix: "Add the missing definition (for example under `components.schemas`) or fix the `$ref`. Run `kubb validate` to check the spec."
516
354
  },
517
- [diagnosticCode.invalidServerVariable]: {
355
+ invalidServerVariable: {
356
+ code: "KUBB_INVALID_SERVER_VARIABLE",
518
357
  title: "Invalid server variable",
519
358
  cause: "A server variable value is not allowed by its `enum`.",
520
359
  fix: "Use one of the values listed in the server variable `enum`, or update the spec."
521
360
  },
522
- [diagnosticCode.pluginNotFound]: {
361
+ pluginNotFound: {
362
+ code: "KUBB_PLUGIN_NOT_FOUND",
523
363
  title: "Plugin not found",
524
364
  cause: "A plugin that another plugin depends on is missing from the config.",
525
365
  fix: "Add the required plugin to the `plugins` array in kubb.config.ts, or remove the dependency on it."
526
366
  },
527
- [diagnosticCode.incompatibleExtension]: {
367
+ incompatibleExtension: {
368
+ code: "KUBB_INCOMPATIBLE_EXTENSION",
528
369
  title: "Incompatible extension",
529
370
  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
371
  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
372
  },
532
- [diagnosticCode.missingDependency]: {
373
+ missingDependency: {
374
+ code: "KUBB_MISSING_DEPENDENCY",
533
375
  title: "Missing dependency",
534
376
  cause: "A plugin lists another plugin in its `dependencies` record without `optional: true`, and the config does not include it.",
535
377
  fix: "Add the missing plugin to the `plugins` array in kubb.config.ts."
536
378
  },
537
- [diagnosticCode.incompatibleDependency]: {
379
+ incompatibleDependency: {
380
+ code: "KUBB_INCOMPATIBLE_DEPENDENCY",
538
381
  title: "Incompatible dependency",
539
382
  cause: "A plugin's `dependencies` record sets a `version` range, and the configured plugin's `meta.version` falls outside it.",
540
383
  fix: "Install a version of the dependency inside the range, or a version of the dependent plugin that supports the installed dependency."
541
384
  },
542
- [diagnosticCode.pluginFailed]: {
385
+ pluginFailed: {
386
+ code: "KUBB_PLUGIN_FAILED",
543
387
  title: "Plugin failed",
544
388
  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."
389
+ fix: "Check the plugin options and the node at `at:`. If it is a bug, report it with the stack at `report:`."
546
390
  },
547
- [diagnosticCode.adapterFailed]: {
391
+ adapterFailed: {
392
+ code: "KUBB_ADAPTER_FAILED",
548
393
  title: "Adapter failed",
549
394
  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."
395
+ fix: "Check the input and the adapter options. If it is a bug, report it with the stack at `report:`."
551
396
  },
552
- [diagnosticCode.parserFailed]: {
397
+ parserFailed: {
398
+ code: "KUBB_PARSER_FAILED",
553
399
  title: "Parser failed",
554
400
  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."
401
+ fix: "Check the parser options. If it is a bug, report it with the stack at `report:`."
556
402
  },
557
- [diagnosticCode.pluginWarning]: {
403
+ pluginWarning: {
404
+ code: "KUBB_PLUGIN_WARNING",
558
405
  title: "Plugin warning",
559
406
  cause: "A plugin reported a non-fatal warning through `ctx.warn`.",
560
407
  fix: "Review the message. It does not fail the build; adjust the plugin options or input if the warning is unwanted."
561
408
  },
562
- [diagnosticCode.pluginInfo]: {
409
+ pluginInfo: {
410
+ code: "KUBB_PLUGIN_INFO",
563
411
  title: "Plugin info",
564
412
  cause: "A plugin reported an informational message through `ctx.info`.",
565
413
  fix: "Informational only. No action is required."
566
414
  },
567
- [diagnosticCode.noContext]: {
415
+ noContext: {
416
+ code: "KUBB_NO_CONTEXT",
568
417
  title: "No plugin context",
569
418
  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
419
  fix: "Call the composable inside `setup` or `kubb:plugin:setup`, including in code that setup awaits."
571
420
  },
572
- [diagnosticCode.unsupportedFormat]: {
421
+ unsupportedFormat: {
422
+ code: "KUBB_UNSUPPORTED_FORMAT",
573
423
  title: "Unsupported format",
574
424
  cause: "A schema uses a `format` Kubb does not map to a specific type, so it falls back to the base type.",
575
425
  fix: "Use a format Kubb supports, or handle the custom format with a parser or plugin."
576
426
  },
577
- [diagnosticCode.deprecated]: {
427
+ deprecated: {
428
+ code: "KUBB_DEPRECATED",
578
429
  title: "Deprecated",
579
430
  cause: "A referenced schema or operation is marked `deprecated`.",
580
431
  fix: "Migrate off the deprecated definition if the warning is unwanted."
581
432
  },
582
- [diagnosticCode.adapterRequired]: {
433
+ adapterRequired: {
434
+ code: "KUBB_ADAPTER_REQUIRED",
583
435
  title: "Adapter required",
584
436
  cause: "An action needs an adapter but none is configured.",
585
437
  fix: "Set `adapter` in kubb.config.ts, for example `adapterOas()`."
586
438
  },
587
- [diagnosticCode.pathTraversal]: {
439
+ pathTraversal: {
440
+ code: "KUBB_PATH_TRAVERSAL",
588
441
  title: "Path traversal",
589
442
  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
443
  fix: "Keep generated paths within the output directory. Review the `group.name` function and the names coming from the spec."
591
444
  },
592
- [diagnosticCode.cleanRoot]: {
445
+ cleanRoot: {
446
+ code: "KUBB_CLEAN_ROOT",
593
447
  title: "Clean targets the project root",
594
448
  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
449
  fix: "Point `output.path` at a subdirectory such as `./src/gen` so clean only removes generated code, or disable `output.clean`."
596
450
  },
597
- [diagnosticCode.invalidPluginOptions]: {
451
+ invalidPluginOptions: {
452
+ code: "KUBB_INVALID_PLUGIN_OPTIONS",
598
453
  title: "Invalid plugin options",
599
454
  cause: "A plugin was configured with options that cannot be honored, for example `output.mode: 'file'` paired with a `group` option.",
600
455
  fix: "Fix the plugin options. A single-file output has nothing to group, so remove the `group` option or use `output.mode: 'directory'`."
601
456
  },
602
- [diagnosticCode.postGenerateFailed]: {
457
+ postGenerateFailed: {
458
+ code: "KUBB_POST_GENERATE_FAILED",
603
459
  title: "Post-generate command failed",
604
460
  cause: "A post-generate command (`output.postGenerate`) exited with a non-zero status.",
605
461
  fix: "Check the command is installed and correct, and run it manually to see the error."
606
462
  },
607
- [diagnosticCode.formatFailed]: {
463
+ formatFailed: {
464
+ code: "KUBB_FORMAT_FAILED",
608
465
  title: "Format failed",
609
466
  cause: "The formatter pass over the generated files failed.",
610
467
  fix: "Check the formatter (oxfmt, biome, or prettier) is installed and its config is valid, then run it manually on the output."
611
468
  },
612
- [diagnosticCode.lintFailed]: {
469
+ lintFailed: {
470
+ code: "KUBB_LINT_FAILED",
613
471
  title: "Lint failed",
614
472
  cause: "The linter pass over the generated files failed.",
615
473
  fix: "Check the linter (oxlint, biome, or eslint) is installed and its config is valid, then run it manually on the output."
616
474
  },
617
- [diagnosticCode.barrelDuplicateExport]: {
475
+ barrelDuplicateExport: {
476
+ code: "KUBB_BARREL_DUPLICATE_EXPORT",
618
477
  title: "Duplicate barrel export",
619
478
  cause: "Two generated modules export the same name, so the barrel cannot re-export both.",
620
479
  fix: "Rename one of the colliding declarations, or configure the plugin resolver to produce distinct names."
621
480
  },
622
- [diagnosticCode.performance]: {
481
+ internalError: {
482
+ code: "KUBB_INTERNAL_ERROR",
483
+ title: "Internal error",
484
+ cause: "Kubb hit a state it does not expect. This is a bug in Kubb, not in your config.",
485
+ 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."
486
+ },
487
+ performance: {
488
+ code: "KUBB_PERFORMANCE",
623
489
  title: "Performance",
624
490
  cause: "Not a failure. Records a plugin’s elapsed time, summed into the run total.",
625
491
  fix: "No action. This is an informational metric."
626
492
  },
627
- [diagnosticCode.updateAvailable]: {
493
+ updateAvailable: {
494
+ code: "KUBB_UPDATE_AVAILABLE",
628
495
  title: "Update available",
629
496
  cause: "A newer Kubb version is published on npm than the one running.",
630
497
  fix: "Update the `@kubb/*` packages, for example `npm install -g @kubb/cli`, to get the latest fixes."
631
498
  }
632
499
  };
633
500
  /**
501
+ * Stable codes Kubb attaches to a `Diagnostic`, derived from {@link diagnosticCatalog}. Reference
502
+ * these instead of inlining the string at a throw site.
503
+ */
504
+ const diagnosticCode = Object.fromEntries(Object.entries(diagnosticCatalog).map(([key, { code }]) => [key, code]));
505
+ //#endregion
506
+ //#region src/formatDiagnostic.ts
507
+ /** A problem's `help`, or the catalog fix. `info` gets none: its catalog fix says no action is needed. */
508
+ function getHelp(problem) {
509
+ if (problem.help || problem.severity === "info") return problem.help;
510
+ return Diagnostics.explain(problem.code).fix;
511
+ }
512
+ /** The `cause` chain's messages, outermost first, minus those `message` already contains. */
513
+ function getCauses(problem) {
514
+ return getErrorChain(problem.cause).filter((error) => !problem.message.includes(error.message)).map((error) => error.name === "Error" ? error.message : `${error.name}: ${error.message}`);
515
+ }
516
+ /**
517
+ * Accent color per severity, which paints the icon in the `human` layout (red error, yellow
518
+ * warning, blue info). The code itself stays dim.
519
+ */
520
+ const severityStyle = {
521
+ error: "red",
522
+ warning: "yellow",
523
+ info: "blue"
524
+ };
525
+ const severityIcon = {
526
+ error: "✖",
527
+ warning: "⚠",
528
+ info: "ℹ"
529
+ };
530
+ /**
531
+ * Codes for a crash, which carry the root cause's stack.
532
+ */
533
+ const crashCodes = /* @__PURE__ */ new Set([
534
+ diagnosticCode.unknown,
535
+ diagnosticCode.internalError,
536
+ diagnosticCode.pluginFailed,
537
+ diagnosticCode.adapterFailed,
538
+ diagnosticCode.parserFailed
539
+ ]);
540
+ /**
541
+ * Reduces a diagnostic to its JSON-safe fields plus a `docsUrl`, for machine-readable
542
+ * consumers, with the catalog `title`, `cause`, and fix, and the `cause` error as `causes` and
543
+ * `stack`. Absent optional fields are omitted.
544
+ */
545
+ function serialize(diagnostic) {
546
+ const problem = Diagnostics.isProblem(diagnostic) ? diagnostic : void 0;
547
+ const doc = problem ? Diagnostics.explain(problem.code) : void 0;
548
+ const help = problem ? getHelp(problem) : void 0;
549
+ const causes = problem ? getCauses(problem) : [];
550
+ const stack = problem && crashCodes.has(problem.code) ? getErrorChain(problem.cause).at(-1)?.stack : void 0;
551
+ return {
552
+ code: diagnostic.code,
553
+ severity: diagnostic.severity,
554
+ message: diagnostic.message,
555
+ ...doc ? {
556
+ title: doc.title,
557
+ cause: doc.cause
558
+ } : {},
559
+ ...problem?.location ? { location: problem.location } : {},
560
+ ...help ? { help } : {},
561
+ ...problem?.plugin ? { plugin: problem.plugin } : {},
562
+ ...problem?.issues ? { issues: problem.issues } : {},
563
+ ...causes.length ? { causes } : {},
564
+ ...stack ? { stack } : {},
565
+ ...diagnostic.code === diagnosticCode.unknown ? {} : { docsUrl: Diagnostics.docsUrl(diagnostic.code) }
566
+ };
567
+ }
568
+ /**
569
+ * Renders a {@link Diagnostic} as lines: a headline, then indented detail rows. `agent` (the default)
570
+ * prints uncolored labeled rows (`at:`, `why:`, `fix:`, `caused by:`, `stack:` for a crash,
571
+ * `docs:`, `report:`). `human` prints an icon, the bold message, and the code aligned to `width`,
572
+ * with `at`, `fix`, the first cause, and `Learn more` for an error, and one row for a warning.
573
+ * `verbose` adds the full stack.
574
+ */
575
+ function format(diagnostic, { audience = "agent", verbose = false, width = 0 } = {}) {
576
+ const { code, severity, message } = diagnostic;
577
+ const problem = Diagnostics.isProblem(diagnostic) ? diagnostic : void 0;
578
+ const human = audience === "human";
579
+ const paint = human ? styleText : (_format, text) => text;
580
+ const [firstLine = "", ...rest] = message.split("\n");
581
+ const pointer = problem?.location && "pointer" in problem.location ? problem.location.pointer : void 0;
582
+ const help = problem ? getHelp(problem) : void 0;
583
+ const causes = problem ? getCauses(problem) : [];
584
+ const crash = problem ? crashCodes.has(problem.code) : false;
585
+ 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())}`);
586
+ if (!human) {
587
+ const details = rest.filter((line) => line.trim()).map((line) => ` ${line}`);
588
+ const row = (label, value) => {
589
+ if (value) details.push(` ${label}: ${value}`);
590
+ };
591
+ row("at", pointer);
592
+ if (problem && severity !== "info") row("why", Diagnostics.explain(problem.code).cause);
593
+ row("fix", help);
594
+ for (const cause of causes) row("caused by", cause);
595
+ if (frames?.length) details.push(" stack:", ...frames);
596
+ if (code !== diagnosticCode.unknown) row("docs", Diagnostics.docsUrl(code));
597
+ row("report", problem?.issues);
598
+ return [`${severity} ${code}${problem?.plugin ? ` [${problem.plugin}]` : ""}: ${firstLine}`, ...details];
599
+ }
600
+ const left = `${paint(severityStyle[severity], severityIcon[severity])} ${paint("bold", firstLine)}`;
601
+ const gap = Math.max(2, width - stripVTControlCharacters(left).length - code.length);
602
+ const headline = `${left}${" ".repeat(gap)}${paint("dim", code)}`;
603
+ const frame = rest.filter((line) => line.trim()).map((line) => ` ${paint("dim", line)}`);
604
+ const label = (name) => paint("dim", name.length < 4 ? name.padEnd(4) : `${name} `);
605
+ const rows = [["at", pointer && paint("cyan", pointer)], ["fix", help]];
606
+ if (severity !== "error") {
607
+ const first = rows.find(([, value]) => value);
608
+ return [
609
+ headline,
610
+ ...frame,
611
+ ...first ? [` ${label(first[0])} ${first[1]}`] : []
612
+ ];
613
+ }
614
+ rows.push(["caused by", causes[0]], ["report", problem?.issues && paint("cyan", problem.issues)]);
615
+ const details = [...frame, ...rows.flatMap(([name, value]) => value ? [` ${label(name)} ${value}`] : [])];
616
+ if (frames?.length) details.push(` ${paint("dim", "stack")}`, ...frames);
617
+ if (code !== diagnosticCode.unknown) details.push(` ${paint("dim", "↳ Learn more")} ${paint("cyan", Diagnostics.docsUrl(code).replace(/^https:\/\//, ""))}`);
618
+ return [headline, ...details];
619
+ }
620
+ //#endregion
621
+ //#region src/Diagnostics.ts
622
+ /**
623
+ * Docs major version, derived from the package version so the link tracks the published major.
624
+ */
625
+ const docsMajor = version.split(".")[0] ?? "5";
626
+ /**
627
+ * Builds a type guard that narrows a {@link Diagnostic} to the variant for `kind`. A diagnostic
628
+ * with no `kind` is treated as a `problem`.
629
+ */
630
+ function isKind(kind) {
631
+ return (diagnostic) => (diagnostic.kind ?? "problem") === kind;
632
+ }
633
+ /**
634
+ * Returns `true` when the diagnostic is a build {@link ProblemDiagnostic}.
635
+ *
636
+ * @example
637
+ * ```ts
638
+ * if (isProblem(diagnostic)) {
639
+ * console.log(diagnostic.location)
640
+ * }
641
+ * ```
642
+ */
643
+ const isProblem = isKind("problem");
644
+ /**
645
+ * Returns `true` when the diagnostic is a per-plugin {@link PerformanceDiagnostic}.
646
+ *
647
+ * @example
648
+ * ```ts
649
+ * const timings = diagnostics.filter(isPerformance)
650
+ * ```
651
+ */
652
+ const isPerformance = isKind("performance");
653
+ /**
654
+ * Returns `true` when the diagnostic is a version-update {@link UpdateDiagnostic}.
655
+ *
656
+ * @example
657
+ * ```ts
658
+ * if (isUpdate(diagnostic)) {
659
+ * console.log(diagnostic.latestVersion)
660
+ * }
661
+ * ```
662
+ */
663
+ const isUpdate = isKind("update");
664
+ const failedCode = {
665
+ plugin: diagnosticCode.pluginFailed,
666
+ adapter: diagnosticCode.adapterFailed,
667
+ parser: diagnosticCode.parserFailed
668
+ };
669
+ const docsByCode = new Map(Object.values(diagnosticCatalog).map((doc) => [doc.code, doc]));
670
+ const countKey = {
671
+ error: "errors",
672
+ warning: "warnings",
673
+ info: "infos"
674
+ };
675
+ /**
634
676
  * Static helpers for working with {@link Diagnostic}s, plus the run-scoped sink
635
677
  * that lets deep code report a diagnostic without threading a callback.
636
678
  *
@@ -705,27 +747,37 @@ var Diagnostics = class Diagnostics {
705
747
  return true;
706
748
  }
707
749
  /**
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.
750
+ * Emits a diagnostic, or any thrown value, on the run's hooks so the loggers render it live.
751
+ * A coded problem or an update goes to `kubb:diagnostic`. An uncoded one goes to `kubb:error`
752
+ * whole, so every wrapper's message, its help, and the stack survive. To collect a diagnostic into
753
+ * the build result from deep in a run, use {@link Diagnostics.report} instead.
711
754
  */
712
- static async emit(hooks, diagnostic) {
713
- await hooks.callHook("kubb:diagnostic", { diagnostic });
755
+ static async emit(hooks, value) {
756
+ const thrown = value instanceof Error || typeof value?.code !== "string";
757
+ const diagnostic = thrown ? Diagnostics.from(value) : value;
758
+ if (!isProblem(diagnostic) || diagnostic.code !== diagnosticCode.unknown) {
759
+ await hooks.callHook("kubb:diagnostic", { diagnostic });
760
+ return;
761
+ }
762
+ const { cause, help, message } = diagnostic;
763
+ const bare = cause && !help && cause.message === message;
764
+ await hooks.callHook("kubb:error", { error: thrown ? toError(value) : bare ? cause : new Diagnostics.Error(diagnostic) });
714
765
  }
715
766
  /**
716
767
  * Coerces any thrown value into a {@link ProblemDiagnostic}. A {@link Diagnostics.Error}
717
768
  * keeps its structured data, and anything else becomes a `KUBB_UNKNOWN` error.
718
769
  */
719
770
  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;
771
+ const chain = getErrorChain(error);
772
+ const coded = chain.findLast((current) => Diagnostics.isError(current));
773
+ if (coded) {
774
+ const { diagnostic } = coded;
775
+ return diagnostic.cause || !crashCodes.has(diagnostic.code) ? diagnostic : {
776
+ ...diagnostic,
777
+ cause: coded
778
+ };
728
779
  }
780
+ const root = chain.at(-1);
729
781
  return {
730
782
  code: diagnosticCode.unknown,
731
783
  severity: "error",
@@ -743,7 +795,9 @@ var Diagnostics = class Diagnostics {
743
795
  const diagnostic = Diagnostics.from(error);
744
796
  const name = extension.meta?.name ?? extension.name;
745
797
  const owner = kind === "plugin" ? { plugin: diagnostic.plugin ?? name } : {};
746
- if (diagnostic.code !== diagnosticCode.unknown || diagnostic.cause?.name === "AbortError") return {
798
+ const crashed = diagnostic.code === diagnosticCode.unknown && diagnostic.cause?.name !== "AbortError";
799
+ const failed = Object.values(failedCode).includes(diagnostic.code);
800
+ if (!crashed && !failed) return {
747
801
  ...diagnostic,
748
802
  ...owner
749
803
  };
@@ -751,8 +805,8 @@ var Diagnostics = class Diagnostics {
751
805
  return {
752
806
  ...diagnostic,
753
807
  ...owner,
754
- code: failedCode[kind],
755
- message: kind === "plugin" ? diagnostic.message : `${kind} "${name}" crashed: ${diagnostic.message}`,
808
+ code: crashed ? failedCode[kind] : diagnostic.code,
809
+ message: crashed && kind !== "plugin" ? `${kind} "${name}" crashed: ${diagnostic.message}` : diagnostic.message,
756
810
  ...issues ? { issues } : {}
757
811
  };
758
812
  }
@@ -843,71 +897,33 @@ var Diagnostics = class Diagnostics {
843
897
  * `/reference/diagnostics#<slug>` section.
844
898
  */
845
899
  static explain(code) {
846
- return diagnosticCatalog[code];
900
+ return docsByCode.get(code);
847
901
  }
848
902
  /**
849
903
  * 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`.
904
+ * consumers, with the catalog `title`, `cause`, and fix, and the `cause` error as `causes` and
905
+ * `stack`. Absent optional fields are omitted.
852
906
  */
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
- }
907
+ static serialize = serialize;
890
908
  /**
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).
909
+ * Renders a diagnostic as lines for an `agent` (the default) or a `human`. See {@link format}.
893
910
  */
894
- static formatLines(diagnostic) {
895
- const { headline, details } = Diagnostics.format(diagnostic);
896
- return [headline, ...details];
897
- }
911
+ static format = format;
898
912
  };
899
913
  //#endregion
900
914
  //#region src/defineExtension.ts
901
915
  /**
902
916
  * Returns `meta`, or `{ name }` for an extension that only sets the deprecated `name`. Throws `KUBB_INVALID_PLUGIN_OPTIONS` when it has neither.
903
917
  */
904
- function resolveMeta({ name, meta }) {
918
+ function resolveMeta(extension) {
919
+ const { name, meta } = extension;
905
920
  if (meta) return meta;
906
921
  if (name) return { name };
922
+ const keys = Object.keys(extension);
907
923
  throw new Diagnostics.Error({
908
924
  code: Diagnostics.code.invalidPluginOptions,
909
925
  severity: "error",
910
- message: "An extension has neither `meta.name` nor `name`.",
926
+ message: `An extension has neither \`meta.name\` nor \`name\`. Received keys: ${keys.length ? keys.join(", ") : "none"}.`,
911
927
  help: "Set `meta: { name }` on the plugin or parser.",
912
928
  location: { kind: "config" }
913
929
  });
@@ -1744,7 +1760,13 @@ async function parseCopy(file, parser) {
1744
1760
  try {
1745
1761
  content = await readFile(file.copy, "utf8");
1746
1762
  } catch (err) {
1747
- throw new Error(`[kubb] Could not copy file into output: ${file.copy}`, { cause: err });
1763
+ throw new Diagnostics.Error({
1764
+ code: Diagnostics.code.pluginFailed,
1765
+ severity: "error",
1766
+ message: `Could not copy file into output: ${file.copy}`,
1767
+ help: "Check that the file a plugin copies into the output exists and is readable.",
1768
+ cause: toError(err)
1769
+ });
1748
1770
  }
1749
1771
  if (parser?.copy) return runParser(parser, () => parser.parse(ast.factory.createFile(parser.copy(file, content))));
1750
1772
  return [
@@ -1986,6 +2008,50 @@ function normalizePluginOptions(rawOptions, pluginName) {
1986
2008
  });
1987
2009
  return options;
1988
2010
  }
2011
+ /**
2012
+ * Throws `KUBB_INVALID_PLUGIN_OPTIONS` when a `plugins` entry is not a plugin object, such as a
2013
+ * factory passed without calling it.
2014
+ */
2015
+ function assertPluginEntry(entry, index) {
2016
+ if (typeof entry === "function") {
2017
+ const name = entry.name || "pluginName";
2018
+ throw new Diagnostics.Error({
2019
+ code: Diagnostics.code.invalidPluginOptions,
2020
+ severity: "error",
2021
+ message: `plugins[${index}] is a function ("${name}"), not a plugin.`,
2022
+ help: `Call it: ${name}()`,
2023
+ location: { kind: "config" }
2024
+ });
2025
+ }
2026
+ if (typeof entry !== "object" || entry === null) throw new Diagnostics.Error({
2027
+ code: Diagnostics.code.invalidPluginOptions,
2028
+ severity: "error",
2029
+ message: `plugins[${index}] is ${entry === null ? "null" : typeof entry}, not a plugin.`,
2030
+ help: "Pass the result of a plugin factory, for example `pluginTs()`, or remove the entry.",
2031
+ location: { kind: "config" }
2032
+ });
2033
+ }
2034
+ /**
2035
+ * Where a generator crashed: a label naming the node and, when the node has one, a JSON pointer
2036
+ * to it. Built from the node fields so core stays adapter-agnostic.
2037
+ */
2038
+ function getCrashSite(node) {
2039
+ if (node.kind === "Schema") return node.name ? {
2040
+ label: `schema "${node.name}"`,
2041
+ location: {
2042
+ kind: "schema",
2043
+ pointer: `#/components/schemas/${escapePointerToken(node.name)}`
2044
+ }
2045
+ } : { label: "an inline schema" };
2046
+ if (!node.method || !node.path) return { label: `operation "${node.operationId}"` };
2047
+ return {
2048
+ label: `operation "${node.operationId}" (${node.method.toUpperCase()} ${node.path})`,
2049
+ location: {
2050
+ kind: "operation",
2051
+ pointer: `#/paths/${escapePointerToken(node.path)}/${node.method.toLowerCase()}`
2052
+ }
2053
+ };
2054
+ }
1989
2055
  var KubbDriver = class {
1990
2056
  config;
1991
2057
  options;
@@ -2025,9 +2091,19 @@ var KubbDriver = class {
2025
2091
  async setup() {
2026
2092
  this.options.signal?.throwIfAborted();
2027
2093
  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) => {
2094
+ const names = /* @__PURE__ */ new Set();
2095
+ const normalized = this.#sortPlugins(this.config.plugins.map((rawPlugin, index) => {
2096
+ assertPluginEntry(rawPlugin, index);
2029
2097
  if (rawPlugin.meta) checkCompatibility(rawPlugin.meta);
2030
2098
  const meta = resolveMeta(rawPlugin);
2099
+ if (names.has(meta.name)) throw new Diagnostics.Error({
2100
+ code: Diagnostics.code.invalidPluginOptions,
2101
+ severity: "error",
2102
+ message: `Plugin "${meta.name}" appears twice in \`plugins\`.`,
2103
+ help: "Remove the duplicate, or merge its options.",
2104
+ location: { kind: "config" }
2105
+ });
2106
+ names.add(meta.name);
2031
2107
  return {
2032
2108
  name: meta.name,
2033
2109
  meta,
@@ -2353,7 +2429,8 @@ var KubbDriver = class {
2353
2429
  operationGenerators: generators.filter((generator) => generator.operation),
2354
2430
  operationsGenerators: generators.filter((generator) => generator.operations),
2355
2431
  pluginOperations: [],
2356
- error: null
2432
+ error: null,
2433
+ crashSite: void 0
2357
2434
  };
2358
2435
  });
2359
2436
  const resolveForPlugin = (state, node) => {
@@ -2397,6 +2474,7 @@ var KubbDriver = class {
2397
2474
  await this.hooks.callHook("kubb:generate:schema", transformedNode, ctx);
2398
2475
  } catch (caughtError) {
2399
2476
  state.error = toError(caughtError);
2477
+ state.crashSite = getCrashSite(node);
2400
2478
  }
2401
2479
  }
2402
2480
  }
@@ -2425,6 +2503,7 @@ var KubbDriver = class {
2425
2503
  }
2426
2504
  } catch (caughtError) {
2427
2505
  state.error = toError(caughtError);
2506
+ state.crashSite = getCrashSite(node);
2428
2507
  }
2429
2508
  }
2430
2509
  }
@@ -2453,10 +2532,18 @@ var KubbDriver = class {
2453
2532
  success: !state.error,
2454
2533
  error: state.error ?? void 0
2455
2534
  });
2456
- if (state.error) diagnostics.push(Diagnostics.fromExtension(state.error, {
2457
- kind: "plugin",
2458
- extension: state.plugin
2459
- }));
2535
+ if (state.error) {
2536
+ const diagnostic = Diagnostics.fromExtension(state.error, {
2537
+ kind: "plugin",
2538
+ extension: state.plugin
2539
+ });
2540
+ const site = diagnostic.code === Diagnostics.code.pluginFailed && !diagnostic.location ? state.crashSite : void 0;
2541
+ diagnostics.push(site ? {
2542
+ ...diagnostic,
2543
+ message: `While generating ${site.label}: ${diagnostic.message}`,
2544
+ ...site.location ? { location: site.location } : {}
2545
+ } : diagnostic);
2546
+ }
2460
2547
  diagnostics.push(Diagnostics.performance({
2461
2548
  plugin: state.plugin.name,
2462
2549
  duration
@@ -2531,27 +2618,37 @@ var KubbDriver = class {
2531
2618
  get resolver() {
2532
2619
  return driver.getResolver(plugin.name);
2533
2620
  },
2534
- warn(message) {
2621
+ warn(message, options) {
2535
2622
  report({
2536
2623
  code: Diagnostics.code.pluginWarning,
2537
2624
  severity: "warning",
2538
- message
2625
+ message,
2626
+ ...options
2539
2627
  });
2540
2628
  },
2541
- error(error) {
2629
+ error(error, options) {
2630
+ if (Diagnostics.isError(error)) {
2631
+ report({
2632
+ ...options,
2633
+ ...error.diagnostic
2634
+ });
2635
+ return;
2636
+ }
2542
2637
  const cause = typeof error === "string" ? void 0 : error;
2543
2638
  report({
2544
2639
  code: Diagnostics.code.pluginFailed,
2545
2640
  severity: "error",
2546
2641
  message: typeof error === "string" ? error : error.message,
2547
- cause
2642
+ cause,
2643
+ ...options
2548
2644
  });
2549
2645
  },
2550
- info(message) {
2646
+ info(message, options) {
2551
2647
  report({
2552
2648
  code: Diagnostics.code.pluginInfo,
2553
2649
  severity: "info",
2554
- message
2650
+ message,
2651
+ ...options
2555
2652
  });
2556
2653
  }
2557
2654
  };
@@ -2691,4 +2788,4 @@ var Hookable = class {
2691
2788
  //#endregion
2692
2789
  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 };
2693
2790
 
2694
- //# sourceMappingURL=Hookable-DBB42ADP.js.map
2791
+ //# sourceMappingURL=Hookable-Bjfoua7Y.js.map