@kubb/core 5.0.0-beta.38 → 5.0.0-beta.39

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,10 +1,9 @@
1
1
  import "./chunk-C0LytTxp.js";
2
2
  import { EventEmitter } from "node:events";
3
+ import { styleText } from "node:util";
3
4
  import path, { extname, resolve } from "node:path";
4
5
  import { collectUsedSchemaNames, createFile, createStreamInput, extractStringsFromNodes, isOperationNode, isSchemaNode, transform } from "@kubb/ast";
5
6
  import { AsyncLocalStorage } from "node:async_hooks";
6
- import { deflateSync } from "fflate";
7
- import { x } from "tinyexec";
8
7
  //#region ../../internals/utils/src/errors.ts
9
8
  /**
10
9
  * Thrown when one or more errors occur during a Kubb build.
@@ -259,6 +258,21 @@ function getElapsedMs(hrStart) {
259
258
  const ms = seconds * 1e3 + nanoseconds / 1e6;
260
259
  return Math.round(ms * 100) / 100;
261
260
  }
261
+ /**
262
+ * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * formatMs(250) // '250ms'
267
+ * formatMs(1500) // '1.50s'
268
+ * formatMs(90000) // '1m 30.0s'
269
+ * ```
270
+ */
271
+ function formatMs(ms) {
272
+ if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
273
+ if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
274
+ return `${Math.round(ms)}ms`;
275
+ }
262
276
  //#endregion
263
277
  //#region ../../internals/utils/src/promise.ts
264
278
  function* chunks(arr, size) {
@@ -620,11 +634,14 @@ var URLPath = class {
620
634
  }
621
635
  };
622
636
  //#endregion
637
+ //#region package.json
638
+ var version = "5.0.0-beta.39";
639
+ //#endregion
623
640
  //#region src/constants.ts
624
641
  /**
625
- * Base URL for the Kubb Studio web app.
642
+ * OpenTelemetry ingestion endpoint for anonymous usage telemetry.
626
643
  */
627
- const DEFAULT_STUDIO_URL = "https://kubb.studio";
644
+ const OTLP_ENDPOINT = "https://otlp.kubb.dev";
628
645
  /**
629
646
  * Plugin `include` filter types that select operations directly. When one of these is set
630
647
  * without a `schemaName` include, the generate phase pre-scans operations to compute the set
@@ -638,18 +655,6 @@ const OPERATION_FILTER_TYPES = new Set([
638
655
  "contentType"
639
656
  ]);
640
657
  /**
641
- * Numeric log-level thresholds used internally to compare verbosity.
642
- *
643
- * Higher numbers are more verbose.
644
- */
645
- const logLevel = {
646
- silent: Number.NEGATIVE_INFINITY,
647
- error: 0,
648
- warn: 1,
649
- info: 3,
650
- verbose: 4
651
- };
652
- /**
653
658
  * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
654
659
  * and stays stable so it can be referenced in tooling and (later) docs. Reference
655
660
  * these instead of inlining the string at a throw site.
@@ -707,10 +712,6 @@ const diagnosticCode = {
707
712
  */
708
713
  adapterRequired: "KUBB_ADAPTER_REQUIRED",
709
714
  /**
710
- * The `devtools` config is set to something other than an object.
711
- */
712
- devtoolsInvalid: "KUBB_DEVTOOLS_INVALID",
713
- /**
714
715
  * A resolved output path escapes the output directory, which can stem from a path
715
716
  * traversal in the spec or a misconfigured `group.name`.
716
717
  */
@@ -741,19 +742,19 @@ const diagnosticCode = {
741
742
  /**
742
743
  * Docs major, derived from the package version so the link tracks the published major.
743
744
  */
744
- const docsMajor = "5.0.0-beta.38".split(".")[0] ?? "5";
745
+ const docsMajor = version.split(".")[0] ?? "5";
745
746
  /**
746
747
  * Narrows a {@link Diagnostic} to the variant for `code`, or `null` when it does not match.
747
748
  *
748
749
  * @example
749
750
  * ```ts
750
- * const update = narrowDiagnostic(diagnostic, diagnosticCode.updateAvailable)
751
+ * const update = narrow(diagnostic, diagnosticCode.updateAvailable)
751
752
  * if (update) {
752
753
  * console.log(update.latestVersion)
753
754
  * }
754
755
  * ```
755
756
  */
756
- function narrowDiagnostic(diagnostic, code) {
757
+ function narrow(diagnostic, code) {
757
758
  return diagnostic.code === code ? diagnostic : null;
758
759
  }
759
760
  /**
@@ -768,59 +769,51 @@ function isKind(kind) {
768
769
  *
769
770
  * @example
770
771
  * ```ts
771
- * if (isProblemDiagnostic(diagnostic)) {
772
+ * if (isProblem(diagnostic)) {
772
773
  * console.log(diagnostic.location)
773
774
  * }
774
775
  * ```
775
776
  */
776
- const isProblemDiagnostic = isKind("problem");
777
+ const isProblem = isKind("problem");
777
778
  /**
778
779
  * Returns `true` when the diagnostic is a per-plugin {@link PerformanceDiagnostic}.
779
780
  *
780
781
  * @example
781
782
  * ```ts
782
- * const timings = diagnostics.filter(isPerformanceDiagnostic)
783
+ * const timings = diagnostics.filter(isPerformance)
783
784
  * ```
784
785
  */
785
- const isPerformanceDiagnostic = isKind("performance");
786
+ const isPerformance = isKind("performance");
786
787
  /**
787
788
  * Returns `true` when the diagnostic is a version-update {@link UpdateDiagnostic}.
788
789
  *
789
790
  * @example
790
791
  * ```ts
791
- * if (isUpdateDiagnostic(diagnostic)) {
792
+ * if (isUpdate(diagnostic)) {
792
793
  * console.log(diagnostic.latestVersion)
793
794
  * }
794
795
  * ```
795
796
  */
796
- const isUpdateDiagnostic = isKind("update");
797
+ const isUpdate = isKind("update");
797
798
  /**
798
- * An `Error` that carries a {@link Diagnostic}, so structured problems can flow
799
- * through the existing throw/catch paths while keeping their code and location.
800
- *
801
- * @example
802
- * ```ts
803
- * throw new DiagnosticError({ code: diagnosticCode.refNotFound, severity: 'error', message: `Could not find ${ref}`, location: { kind: 'schema', pointer: ref, ref } })
804
- * ```
799
+ * Glyph and accent color per severity, matching the miette/oxlint convention
800
+ * (`×` error, `⚠` warning, `ℹ` advice).
805
801
  */
806
- var DiagnosticError = class extends Error {
807
- diagnostic;
808
- constructor(diagnostic) {
809
- super(diagnostic.message, { cause: diagnostic.cause });
810
- this.name = "DiagnosticError";
811
- this.diagnostic = diagnostic;
802
+ const severityStyle = {
803
+ error: {
804
+ glyph: "×",
805
+ color: "red"
806
+ },
807
+ warning: {
808
+ glyph: "⚠",
809
+ color: "yellow"
810
+ },
811
+ info: {
812
+ glyph: "ℹ",
813
+ color: "blue"
812
814
  }
813
815
  };
814
816
  /**
815
- * Structural check for a {@link DiagnosticError}, including one thrown from a duplicated
816
- * `@kubb/core` copy where `instanceof` fails. Matches on the `name` and a `diagnostic`
817
- * that carries a `code`.
818
- */
819
- function isDiagnosticError(error) {
820
- if (error instanceof DiagnosticError) return true;
821
- return error instanceof Error && error.name === "DiagnosticError" && "diagnostic" in error && typeof error.diagnostic === "object" && error.diagnostic !== null && typeof error.diagnostic?.code === "string";
822
- }
823
- /**
824
817
  * Explanation for every {@link diagnosticCode}. Use {@link Diagnostics.explain} to look one up
825
818
  * and `Diagnostics.docsUrl` for the matching kubb.dev page.
826
819
  */
@@ -882,14 +875,9 @@ const diagnosticCatalog = {
882
875
  },
883
876
  [diagnosticCode.adapterRequired]: {
884
877
  title: "Adapter required",
885
- cause: "An action needs an adapter (for example opening Kubb Studio) but none is configured.",
878
+ cause: "An action needs an adapter but none is configured.",
886
879
  fix: "Set `adapter` in kubb.config.ts, for example `adapterOas()`."
887
880
  },
888
- [diagnosticCode.devtoolsInvalid]: {
889
- title: "Invalid devtools config",
890
- cause: "The `devtools` config is set to something other than an object.",
891
- fix: "Set `devtools` to an options object, or remove it to disable Kubb Studio."
892
- },
893
881
  [diagnosticCode.pathTraversal]: {
894
882
  title: "Path traversal",
895
883
  cause: "A resolved output path escaped the output directory, which can stem from a path traversal in the spec or a misconfigured `group.name`.",
@@ -933,6 +921,52 @@ const diagnosticCatalog = {
933
921
  var Diagnostics = class Diagnostics {
934
922
  static #reporterStorage = new AsyncLocalStorage();
935
923
  /**
924
+ * The diagnostic code catalog, exposed as `Diagnostics.code` (e.g. `Diagnostics.code.refNotFound`).
925
+ */
926
+ static code = diagnosticCode;
927
+ /**
928
+ * Type guard for a build {@link ProblemDiagnostic}.
929
+ */
930
+ static isProblem = isProblem;
931
+ /**
932
+ * Type guard for a version-update {@link UpdateDiagnostic}.
933
+ */
934
+ static isUpdate = isUpdate;
935
+ /**
936
+ * Type guard for a per-plugin {@link PerformanceDiagnostic}.
937
+ */
938
+ static isPerformance = isPerformance;
939
+ /**
940
+ * Narrows a {@link Diagnostic} to the variant for `code`, or `null` when it does not match.
941
+ */
942
+ static narrow = narrow;
943
+ /**
944
+ * An `Error` that carries a {@link Diagnostic}, so structured problems can flow
945
+ * through the existing throw/catch paths while keeping their code and location.
946
+ *
947
+ * @example
948
+ * ```ts
949
+ * throw new Diagnostics.Error({ code: diagnosticCode.refNotFound, severity: 'error', message: `Could not find ${ref}`, location: { kind: 'schema', pointer: ref, ref } })
950
+ * ```
951
+ */
952
+ static Error = class DiagnosticError extends Error {
953
+ diagnostic;
954
+ constructor(diagnostic) {
955
+ super(diagnostic.message, { cause: diagnostic.cause });
956
+ this.name = "DiagnosticError";
957
+ this.diagnostic = diagnostic;
958
+ }
959
+ };
960
+ /**
961
+ * Structural check for a {@link Diagnostics.Error}, including one thrown from a duplicated
962
+ * `@kubb/core` copy where `instanceof` fails. Matches on the `name` and a `diagnostic`
963
+ * that carries a `code`.
964
+ */
965
+ static isError(error) {
966
+ if (error instanceof Diagnostics.Error) return true;
967
+ return error instanceof Error && error.name === "DiagnosticError" && "diagnostic" in error && typeof error.diagnostic === "object" && error.diagnostic !== null && typeof error.diagnostic?.code === "string";
968
+ }
969
+ /**
936
970
  * Runs `fn` with `sink` as the active diagnostic sink for the whole async
937
971
  * subtree, so {@link Diagnostics.report} reaches it from anywhere inside.
938
972
  */
@@ -960,7 +994,7 @@ var Diagnostics = class Diagnostics {
960
994
  await hooks.emit("kubb:diagnostic", { diagnostic });
961
995
  }
962
996
  /**
963
- * Coerces any thrown value into a {@link ProblemDiagnostic}. A {@link DiagnosticError}
997
+ * Coerces any thrown value into a {@link ProblemDiagnostic}. A {@link Diagnostics.Error}
964
998
  * keeps its structured data, and anything else becomes a `KUBB_UNKNOWN` error.
965
999
  */
966
1000
  static from(error) {
@@ -968,7 +1002,7 @@ var Diagnostics = class Diagnostics {
968
1002
  let current = error;
969
1003
  let root;
970
1004
  while (current instanceof Error && !seen.has(current)) {
971
- if (isDiagnosticError(current)) return current.diagnostic;
1005
+ if (Diagnostics.isError(current)) return current.diagnostic;
972
1006
  seen.add(current);
973
1007
  root = current;
974
1008
  current = current.cause;
@@ -1031,7 +1065,7 @@ var Diagnostics = class Diagnostics {
1031
1065
  let warnings = 0;
1032
1066
  let infos = 0;
1033
1067
  for (const diagnostic of diagnostics) {
1034
- if (!isProblemDiagnostic(diagnostic)) continue;
1068
+ if (!isProblem(diagnostic)) continue;
1035
1069
  if (diagnostic.severity === "error") errors += 1;
1036
1070
  else if (diagnostic.severity === "warning") warnings += 1;
1037
1071
  else infos += 1;
@@ -1051,7 +1085,7 @@ var Diagnostics = class Diagnostics {
1051
1085
  const seen = /* @__PURE__ */ new Set();
1052
1086
  const result = [];
1053
1087
  for (const diagnostic of diagnostics) {
1054
- if (!isProblemDiagnostic(diagnostic)) {
1088
+ if (!isProblem(diagnostic)) {
1055
1089
  result.push(diagnostic);
1056
1090
  continue;
1057
1091
  }
@@ -1083,7 +1117,7 @@ var Diagnostics = class Diagnostics {
1083
1117
  * fields are omitted rather than set to `undefined`.
1084
1118
  */
1085
1119
  static serialize(diagnostic) {
1086
- const problem = isProblemDiagnostic(diagnostic) ? diagnostic : void 0;
1120
+ const problem = isProblem(diagnostic) ? diagnostic : void 0;
1087
1121
  return {
1088
1122
  code: diagnostic.code,
1089
1123
  severity: diagnostic.severity,
@@ -1094,8 +1128,82 @@ var Diagnostics = class Diagnostics {
1094
1128
  ...diagnostic.code === diagnosticCode.unknown ? {} : { docsUrl: Diagnostics.docsUrl(diagnostic.code) }
1095
1129
  };
1096
1130
  }
1131
+ /**
1132
+ * Renders a {@link Diagnostic} for terminal output as its parts: the colored severity `symbol`
1133
+ * (the gutter glyph), the `plugin(CODE): message` `headline`, and the `details` lines (optional
1134
+ * `at <pointer>`, `help:`, and `docs:`).
1135
+ *
1136
+ * Hosts compose these to fit their gutter: a clack logger passes `symbol` as its own gutter and
1137
+ * `[headline, ...details]` as the message, while plain text outputs use {@link Diagnostics.formatLines}.
1138
+ */
1139
+ static format(diagnostic) {
1140
+ const { code, severity, message } = diagnostic;
1141
+ const { glyph, color } = severityStyle[severity];
1142
+ const problem = isProblem(diagnostic) ? diagnostic : void 0;
1143
+ const rule = styleText(color, styleText("bold", problem?.plugin ? `${problem.plugin}(${code})` : code));
1144
+ const details = [];
1145
+ if (problem?.location && "pointer" in problem.location) details.push(` ${styleText("dim", "at")} ${styleText("cyan", problem.location.pointer)}`);
1146
+ if (problem?.help) details.push(` ${styleText("cyan", "help:")} ${problem.help}`);
1147
+ if (code !== diagnosticCode.unknown) details.push(` ${styleText("dim", "docs:")} ${styleText("cyan", Diagnostics.docsUrl(code))}`);
1148
+ return {
1149
+ symbol: styleText(color, styleText("bold", glyph)),
1150
+ headline: `${rule}: ${message}`,
1151
+ details
1152
+ };
1153
+ }
1154
+ /**
1155
+ * The self-contained block form of {@link Diagnostics.format}: `${symbol} ${headline}` followed by
1156
+ * the detail lines. Used where there is no gutter to own the symbol (plain and file output).
1157
+ */
1158
+ static formatLines(diagnostic) {
1159
+ const { symbol, headline, details } = Diagnostics.format(diagnostic);
1160
+ return [`${symbol} ${headline}`, ...details];
1161
+ }
1097
1162
  };
1098
1163
  //#endregion
1164
+ //#region src/createStorage.ts
1165
+ /**
1166
+ * Defines a custom storage backend. The builder receives user options and
1167
+ * returns a `Storage` implementation. Kubb ships with filesystem and
1168
+ * in-memory storages, reach for this when you need to write generated files
1169
+ * elsewhere (cloud storage, a database, a remote API).
1170
+ *
1171
+ * @example In-memory storage (the built-in implementation)
1172
+ * ```ts
1173
+ * import { createStorage } from '@kubb/core'
1174
+ *
1175
+ * export const memoryStorage = createStorage(() => {
1176
+ * const store = new Map<string, string>()
1177
+ *
1178
+ * return {
1179
+ * name: 'memory',
1180
+ * async hasItem(key) {
1181
+ * return store.has(key)
1182
+ * },
1183
+ * async getItem(key) {
1184
+ * return store.get(key) ?? null
1185
+ * },
1186
+ * async setItem(key, value) {
1187
+ * store.set(key, value)
1188
+ * },
1189
+ * async removeItem(key) {
1190
+ * store.delete(key)
1191
+ * },
1192
+ * async getKeys(base) {
1193
+ * const keys = [...store.keys()]
1194
+ * return base ? keys.filter((k) => k.startsWith(base)) : keys
1195
+ * },
1196
+ * async clear(base) {
1197
+ * if (!base) store.clear()
1198
+ * },
1199
+ * }
1200
+ * })
1201
+ * ```
1202
+ */
1203
+ function createStorage(build) {
1204
+ return (options) => build(options ?? {});
1205
+ }
1206
+ //#endregion
1099
1207
  //#region src/definePlugin.ts
1100
1208
  /**
1101
1209
  * Wraps a plugin factory and returns a function that accepts user options and
@@ -1323,8 +1431,8 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
1323
1431
  })();
1324
1432
  const outputDir = path.resolve(root, output.path);
1325
1433
  const outputDirWithSep = outputDir.endsWith(path.sep) ? outputDir : `${outputDir}${path.sep}`;
1326
- if (result !== outputDir && !result.startsWith(outputDirWithSep)) throw new DiagnosticError({
1327
- code: diagnosticCode.pathTraversal,
1434
+ if (result !== outputDir && !result.startsWith(outputDirWithSep)) throw new Diagnostics.Error({
1435
+ code: Diagnostics.code.pathTraversal,
1328
1436
  severity: "error",
1329
1437
  message: `Resolved path "${result}" is outside the output directory "${outputDir}".`,
1330
1438
  help: "This can stem from a path traversal in the OpenAPI specification or a misconfigured `group.name` function. Keep generated paths within the output directory.",
@@ -1548,47 +1656,6 @@ function defineResolver(build) {
1548
1656
  return resolver;
1549
1657
  }
1550
1658
  //#endregion
1551
- //#region src/devtools.ts
1552
- /**
1553
- * Encodes an `InputNode` as a compressed, URL-safe string.
1554
- *
1555
- * The JSON representation is deflate-compressed with {@link deflateSync} before
1556
- * base64url encoding, which typically reduces payload size by 70, 80 % and
1557
- * keeps URLs well within browser and server path-length limits.
1558
- */
1559
- function encodeAst(input) {
1560
- const compressed = deflateSync(new TextEncoder().encode(JSON.stringify(input)));
1561
- return Buffer.from(compressed).toString("base64url");
1562
- }
1563
- /**
1564
- * Constructs the Kubb Studio URL for the given `InputNode`.
1565
- * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
1566
- * The `input` is encoded and attached as the `?root=` query parameter so Studio
1567
- * can decode and render it without a round-trip to any server.
1568
- */
1569
- function getStudioUrl(input, studioUrl, options = {}) {
1570
- return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(input)}`;
1571
- }
1572
- /**
1573
- * Opens the Kubb Studio URL for the given `InputNode` in the default browser, *
1574
- * Falls back to printing the URL if the browser cannot be launched.
1575
- */
1576
- async function openInStudio(input, studioUrl, options = {}) {
1577
- const url = getStudioUrl(input, studioUrl, options);
1578
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
1579
- const args = process.platform === "win32" ? [
1580
- "/c",
1581
- "start",
1582
- "",
1583
- url
1584
- ] : [url];
1585
- try {
1586
- await x(cmd, args);
1587
- } catch {
1588
- console.log(`\n ${url}\n`);
1589
- }
1590
- }
1591
- //#endregion
1592
1659
  //#region src/FileManager.ts
1593
1660
  function mergeFile(a, b) {
1594
1661
  return {
@@ -1994,19 +2061,10 @@ var KubbDriver = class KubbDriver {
1994
2061
  inputNode = null;
1995
2062
  adapter = null;
1996
2063
  /**
1997
- * Studio session state, kept together so `dispose()` can reset it atomically.
1998
- *
1999
- * - `source` holds the raw adapter source so `adapter.parse()` can be called lazily.
2000
- * Intentionally outlives the build, cleared by `dispose()`.
2001
- * - `isOpen` prevents opening the studio more than once per build.
2002
- * - `inputNode` caches the parse promise so `adapter.parse()` is called at most once
2003
- * per studio session, even when `openInStudio()` is called multiple times.
2004
- */
2005
- #studio = {
2006
- source: null,
2007
- isOpen: false,
2008
- inputNode: null
2009
- };
2064
+ * Raw adapter source so `adapter.parse()` / `adapter.stream()` can run lazily.
2065
+ * Intentionally outlives the build, cleared by `dispose()`.
2066
+ */
2067
+ #adapterSource = null;
2010
2068
  /**
2011
2069
  * Central file store for all generated files.
2012
2070
  * Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
@@ -2051,7 +2109,7 @@ var KubbDriver = class KubbDriver {
2051
2109
  this.plugins.set(plugin.name, plugin);
2052
2110
  }
2053
2111
  if (this.config.middleware) for (const middleware of this.config.middleware) for (const event of Object.keys(middleware.hooks)) this.#registerMiddleware(event, middleware.hooks);
2054
- if (this.config.adapter) this.#studio.source = inputToAdapterSource(this.config);
2112
+ if (this.config.adapter) this.#adapterSource = inputToAdapterSource(this.config);
2055
2113
  }
2056
2114
  get hooks() {
2057
2115
  return this.options.hooks;
@@ -2077,14 +2135,14 @@ var KubbDriver = class KubbDriver {
2077
2135
  }
2078
2136
  /**
2079
2137
  * Parses the adapter source into `this.inputNode`. Idempotent, so repeated calls from
2080
- * `run` or the studio path do not re-parse. Adapters with `stream()` are used directly.
2138
+ * `run` do not re-parse. Adapters with `stream()` are used directly.
2081
2139
  * Adapters with only `parse()` are wrapped via `createStreamInput` so the dispatch loop
2082
2140
  * stays stream-only.
2083
2141
  */
2084
2142
  async #parseInput() {
2085
- if (this.inputNode || !this.adapter || !this.#studio.source) return;
2143
+ if (this.inputNode || !this.adapter || !this.#adapterSource) return;
2086
2144
  const adapter = this.adapter;
2087
- const source = this.#studio.source;
2145
+ const source = this.#adapterSource;
2088
2146
  if (adapter.stream) {
2089
2147
  this.inputNode = await adapter.stream(source);
2090
2148
  return;
@@ -2607,11 +2665,7 @@ var KubbDriver = class KubbDriver {
2607
2665
  this.#defaultResolvers.clear();
2608
2666
  this.fileManager.dispose();
2609
2667
  this.inputNode = null;
2610
- this.#studio = {
2611
- source: null,
2612
- isOpen: false,
2613
- inputNode: null
2614
- };
2668
+ this.#adapterSource = null;
2615
2669
  }
2616
2670
  [Symbol.dispose]() {
2617
2671
  this.dispose();
@@ -2682,7 +2736,7 @@ var KubbDriver = class KubbDriver {
2682
2736
  },
2683
2737
  warn(message) {
2684
2738
  report({
2685
- code: diagnosticCode.pluginWarning,
2739
+ code: Diagnostics.code.pluginWarning,
2686
2740
  severity: "warning",
2687
2741
  message
2688
2742
  });
@@ -2690,7 +2744,7 @@ var KubbDriver = class KubbDriver {
2690
2744
  error(error) {
2691
2745
  const cause = typeof error === "string" ? void 0 : error;
2692
2746
  report({
2693
- code: diagnosticCode.pluginFailed,
2747
+ code: Diagnostics.code.pluginFailed,
2694
2748
  severity: "error",
2695
2749
  message: typeof error === "string" ? error : error.message,
2696
2750
  cause
@@ -2698,31 +2752,10 @@ var KubbDriver = class KubbDriver {
2698
2752
  },
2699
2753
  info(message) {
2700
2754
  report({
2701
- code: diagnosticCode.pluginInfo,
2755
+ code: Diagnostics.code.pluginInfo,
2702
2756
  severity: "info",
2703
2757
  message
2704
2758
  });
2705
- },
2706
- async openInStudio(options) {
2707
- if (!driver.config.devtools || driver.#studio.isOpen) return;
2708
- if (typeof driver.config.devtools !== "object") throw new DiagnosticError({
2709
- code: diagnosticCode.devtoolsInvalid,
2710
- severity: "error",
2711
- message: "The `devtools` config must be an object.",
2712
- help: "Set `devtools` to an options object, or remove it to disable Kubb Studio.",
2713
- location: { kind: "config" }
2714
- });
2715
- if (!driver.adapter || !driver.#studio.source) throw new DiagnosticError({
2716
- code: diagnosticCode.adapterRequired,
2717
- severity: "error",
2718
- message: "An adapter is required to open Kubb Studio, but none is configured.",
2719
- help: "Set `adapter` in kubb.config.ts (for example `adapterOas()`).",
2720
- location: { kind: "config" }
2721
- });
2722
- driver.#studio.isOpen = true;
2723
- const studioUrl = driver.config.devtools?.studioUrl ?? "https://kubb.studio";
2724
- driver.#studio.inputNode ??= Promise.resolve(driver.adapter.parse(driver.#studio.source));
2725
- return openInStudio(await driver.#studio.inputNode, studioUrl, options);
2726
2759
  }
2727
2760
  };
2728
2761
  }
@@ -2731,8 +2764,8 @@ var KubbDriver = class KubbDriver {
2731
2764
  }
2732
2765
  requirePlugin(pluginName) {
2733
2766
  const plugin = this.plugins.get(pluginName);
2734
- if (!plugin) throw new DiagnosticError({
2735
- code: diagnosticCode.pluginNotFound,
2767
+ if (!plugin) throw new Diagnostics.Error({
2768
+ code: Diagnostics.code.pluginNotFound,
2736
2769
  severity: "error",
2737
2770
  message: `Plugin "${pluginName}" is required but not found. Make sure it is included in your Kubb config.`,
2738
2771
  help: `Add "${pluginName}" to the \`plugins\` array in kubb.config.ts, or remove the dependency on it.`,
@@ -2743,8 +2776,8 @@ var KubbDriver = class KubbDriver {
2743
2776
  };
2744
2777
  function inputToAdapterSource(config) {
2745
2778
  const input = config.input;
2746
- if (!input) throw new DiagnosticError({
2747
- code: diagnosticCode.inputRequired,
2779
+ if (!input) throw new Diagnostics.Error({
2780
+ code: Diagnostics.code.inputRequired,
2748
2781
  severity: "error",
2749
2782
  message: "An adapter is configured without an input.",
2750
2783
  help: "Provide `input.path` (a file or URL) or `input.data` (an inline spec) in your Kubb config.",
@@ -2764,6 +2797,56 @@ function inputToAdapterSource(config) {
2764
2797
  };
2765
2798
  }
2766
2799
  //#endregion
2767
- export { URLPath as _, defineResolver as a, Diagnostics as c, isProblemDiagnostic as d, isUpdateDiagnostic as f, logLevel as g, diagnosticCode as h, FileManager as i, diagnosticCatalog as l, DEFAULT_STUDIO_URL as m, _usingCtx as n, definePlugin as o, narrowDiagnostic as p, FileProcessor as r, DiagnosticError as s, KubbDriver as t, isPerformanceDiagnostic as u, AsyncEventEmitter as v, BuildError as y };
2800
+ //#region src/storages/memoryStorage.ts
2801
+ /**
2802
+ * In-memory storage driver. Useful for testing and dry-run scenarios where
2803
+ * generated output should be captured without touching the filesystem.
2804
+ *
2805
+ * All data lives in a `Map` scoped to the storage instance and is discarded
2806
+ * when the instance is garbage-collected.
2807
+ *
2808
+ * @example
2809
+ * ```ts
2810
+ * import { memoryStorage } from '@kubb/core'
2811
+ * import { defineConfig } from 'kubb'
2812
+ *
2813
+ * export default defineConfig({
2814
+ * input: { path: './petStore.yaml' },
2815
+ * output: { path: './src/gen' },
2816
+ * storage: memoryStorage(),
2817
+ * })
2818
+ * ```
2819
+ */
2820
+ const memoryStorage = createStorage(() => {
2821
+ const store = /* @__PURE__ */ new Map();
2822
+ return {
2823
+ name: "memory",
2824
+ async hasItem(key) {
2825
+ return store.has(key);
2826
+ },
2827
+ async getItem(key) {
2828
+ return store.get(key) ?? null;
2829
+ },
2830
+ async setItem(key, value) {
2831
+ store.set(key, value);
2832
+ },
2833
+ async removeItem(key) {
2834
+ store.delete(key);
2835
+ },
2836
+ async getKeys(base) {
2837
+ const keys = [...store.keys()];
2838
+ return base ? keys.filter((k) => k.startsWith(base)) : keys;
2839
+ },
2840
+ async clear(base) {
2841
+ if (!base) {
2842
+ store.clear();
2843
+ return;
2844
+ }
2845
+ for (const key of store.keys()) if (key.startsWith(base)) store.delete(key);
2846
+ }
2847
+ };
2848
+ });
2849
+ //#endregion
2850
+ export { FileManager as a, createStorage as c, formatMs as d, getElapsedMs as f, BuildError as h, FileProcessor as i, Diagnostics as l, AsyncEventEmitter as m, KubbDriver as n, defineResolver as o, camelCase as p, _usingCtx as r, definePlugin as s, memoryStorage as t, OTLP_ENDPOINT as u };
2768
2851
 
2769
- //# sourceMappingURL=KubbDriver-CyNF-NIb.js.map
2852
+ //# sourceMappingURL=memoryStorage-CNQTs-YG.js.map