@kubb/core 5.0.0-beta.1 → 5.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +9 -39
  2. package/dist/{PluginDriver-BXibeQk-.cjs → PluginDriver-C1OsqGBJ.cjs} +106 -56
  3. package/dist/PluginDriver-C1OsqGBJ.cjs.map +1 -0
  4. package/dist/{PluginDriver-DV3p2Hky.js → PluginDriver-CGypdXHg.js} +101 -57
  5. package/dist/PluginDriver-CGypdXHg.js.map +1 -0
  6. package/dist/{types-CuNocrbJ.d.ts → createKubb-BSfMDBwR.d.ts} +1533 -1505
  7. package/dist/index.cjs +249 -209
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +2 -185
  10. package/dist/index.js +249 -209
  11. package/dist/index.js.map +1 -1
  12. package/dist/mocks.cjs +1 -1
  13. package/dist/mocks.cjs.map +1 -1
  14. package/dist/mocks.d.ts +1 -1
  15. package/dist/mocks.js +1 -1
  16. package/dist/mocks.js.map +1 -1
  17. package/package.json +5 -12
  18. package/src/FileManager.ts +8 -0
  19. package/src/FileProcessor.ts +12 -7
  20. package/src/PluginDriver.ts +49 -7
  21. package/src/constants.ts +6 -2
  22. package/src/createAdapter.ts +77 -1
  23. package/src/createKubb.ts +973 -141
  24. package/src/defineGenerator.ts +92 -4
  25. package/src/defineLogger.ts +42 -3
  26. package/src/defineMiddleware.ts +1 -1
  27. package/src/definePlugin.ts +304 -8
  28. package/src/defineResolver.ts +185 -52
  29. package/src/devtools.ts +8 -1
  30. package/src/index.ts +1 -1
  31. package/src/mocks.ts +1 -2
  32. package/src/storages/fsStorage.ts +6 -31
  33. package/src/types.ts +38 -1292
  34. package/dist/PluginDriver-BXibeQk-.cjs.map +0 -1
  35. package/dist/PluginDriver-DV3p2Hky.js.map +0 -1
  36. package/src/Kubb.ts +0 -300
  37. package/src/renderNode.ts +0 -35
  38. package/src/utils/diagnostics.ts +0 -18
  39. package/src/utils/isInputPath.ts +0 -10
  40. package/src/utils/packageJSON.ts +0 -99
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { t as __name } from "./chunk--u3MIqq1.js";
2
- import { a as DEFAULT_BANNER, c as logLevel, i as defineResolver, l as camelCase, n as applyHookResult, o as DEFAULT_EXTENSION, r as FileManager, s as DEFAULT_STUDIO_URL, t as PluginDriver } from "./PluginDriver-DV3p2Hky.js";
2
+ import { a as definePlugin, c as DEFAULT_STUDIO_URL, i as defineResolver, l as logLevel, n as applyHookResult, o as DEFAULT_BANNER, r as FileManager, s as DEFAULT_EXTENSION, t as PluginDriver, u as camelCase } from "./PluginDriver-CGypdXHg.js";
3
3
  import { EventEmitter } from "node:events";
4
4
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
5
  import { dirname, join, resolve } from "node:path";
6
6
  import * as ast from "@kubb/ast";
7
- import { extractStringsFromNodes, transform, walk } from "@kubb/ast";
7
+ import { collectUsedSchemaNames, extractStringsFromNodes, transform, walk } from "@kubb/ast";
8
8
  import { version } from "node:process";
9
9
  //#region ../../internals/utils/src/errors.ts
10
10
  /**
@@ -524,6 +524,46 @@ function createAdapter(build) {
524
524
  return (options) => build(options ?? {});
525
525
  }
526
526
  //#endregion
527
+ //#region package.json
528
+ var version$1 = "5.0.0-beta.11";
529
+ //#endregion
530
+ //#region src/createStorage.ts
531
+ /**
532
+ * Factory for implementing custom storage backends that control where generated files are written.
533
+ *
534
+ * Takes a builder function `(options: TOptions) => Storage` and returns a factory `(options?: TOptions) => Storage`.
535
+ * Kubb provides filesystem and in-memory implementations out of the box.
536
+ *
537
+ * @note Call the returned factory with optional options to instantiate the storage adapter.
538
+ *
539
+ * @example
540
+ * ```ts
541
+ * import { createStorage } from '@kubb/core'
542
+ *
543
+ * export const memoryStorage = createStorage(() => {
544
+ * const store = new Map<string, string>()
545
+ * return {
546
+ * name: 'memory',
547
+ * async hasItem(key) { return store.has(key) },
548
+ * async getItem(key) { return store.get(key) ?? null },
549
+ * async setItem(key, value) { store.set(key, value) },
550
+ * async removeItem(key) { store.delete(key) },
551
+ * async getKeys(base) {
552
+ * const keys = [...store.keys()]
553
+ * return base ? keys.filter((k) => k.startsWith(base)) : keys
554
+ * },
555
+ * async clear(base) { if (!base) store.clear() },
556
+ * }
557
+ * })
558
+ *
559
+ * // Instantiate:
560
+ * const storage = memoryStorage()
561
+ * ```
562
+ */
563
+ function createStorage(build) {
564
+ return (options) => build(options ?? {});
565
+ }
566
+ //#endregion
527
567
  //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
528
568
  var Node$1 = class {
529
569
  static {
@@ -665,7 +705,8 @@ function joinSources(file) {
665
705
  * @internal
666
706
  */
667
707
  var FileProcessor = class {
668
- #limit = pLimit(100);
708
+ events = new AsyncEventEmitter();
709
+ #limit = pLimit(16);
669
710
  async parse(file, { parsers, extension } = {}) {
670
711
  const parseExtName = extension?.[file.extname] || void 0;
671
712
  if (!parsers || !file.extname) return joinSources(file);
@@ -673,8 +714,8 @@ var FileProcessor = class {
673
714
  if (!parser) return joinSources(file);
674
715
  return parser.parse(file, { extname: parseExtName });
675
716
  }
676
- async run(files, { parsers, mode = "sequential", extension, onStart, onEnd, onUpdate } = {}) {
677
- await onStart?.(files);
717
+ async run(files, { parsers, mode = "sequential", extension } = {}) {
718
+ await this.events.emit("start", files);
678
719
  const total = files.length;
679
720
  let processed = 0;
680
721
  const processOne = async (file) => {
@@ -684,7 +725,7 @@ var FileProcessor = class {
684
725
  });
685
726
  const currentProcessed = ++processed;
686
727
  const percentage = currentProcessed / total * 100;
687
- await onUpdate?.({
728
+ await this.events.emit("update", {
688
729
  file,
689
730
  source,
690
731
  processed: currentProcessed,
@@ -694,56 +735,13 @@ var FileProcessor = class {
694
735
  };
695
736
  if (mode === "sequential") for (const file of files) await processOne(file);
696
737
  else await Promise.all(files.map((file) => this.#limit(() => processOne(file))));
697
- await onEnd?.(files);
738
+ await this.events.emit("end", files);
698
739
  return files;
699
740
  }
700
741
  };
701
742
  //#endregion
702
- //#region src/createStorage.ts
703
- /**
704
- * Factory for implementing custom storage backends that control where generated files are written.
705
- *
706
- * Takes a builder function `(options: TOptions) => Storage` and returns a factory `(options?: TOptions) => Storage`.
707
- * Kubb provides filesystem and in-memory implementations out of the box.
708
- *
709
- * @note Call the returned factory with optional options to instantiate the storage adapter.
710
- *
711
- * @example
712
- * ```ts
713
- * import { createStorage } from '@kubb/core'
714
- *
715
- * export const memoryStorage = createStorage(() => {
716
- * const store = new Map<string, string>()
717
- * return {
718
- * name: 'memory',
719
- * async hasItem(key) { return store.has(key) },
720
- * async getItem(key) { return store.get(key) ?? null },
721
- * async setItem(key, value) { store.set(key, value) },
722
- * async removeItem(key) { store.delete(key) },
723
- * async getKeys(base) {
724
- * const keys = [...store.keys()]
725
- * return base ? keys.filter((k) => k.startsWith(base)) : keys
726
- * },
727
- * async clear(base) { if (!base) store.clear() },
728
- * }
729
- * })
730
- *
731
- * // Instantiate:
732
- * const storage = memoryStorage()
733
- * ```
734
- */
735
- function createStorage(build) {
736
- return (options) => build(options ?? {});
737
- }
738
- //#endregion
739
743
  //#region src/storages/fsStorage.ts
740
744
  /**
741
- * Detects the filesystem error used to indicate that a path does not exist.
742
- */
743
- function isMissingPathError(error) {
744
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
745
- }
746
- /**
747
745
  * Built-in filesystem storage driver.
748
746
  *
749
747
  * This is the default storage when no `storage` option is configured in the root config.
@@ -774,17 +772,15 @@ const fsStorage = createStorage(() => ({
774
772
  try {
775
773
  await access(resolve(key));
776
774
  return true;
777
- } catch (error) {
778
- if (isMissingPathError(error)) return false;
779
- throw new Error(`Failed to access storage item "${key}"`, { cause: error });
775
+ } catch (_error) {
776
+ return false;
780
777
  }
781
778
  },
782
779
  async getItem(key) {
783
780
  try {
784
781
  return await readFile(resolve(key), "utf8");
785
- } catch (error) {
786
- if (isMissingPathError(error)) return null;
787
- throw new Error(`Failed to read storage item "${key}"`, { cause: error });
782
+ } catch (_error) {
783
+ return null;
788
784
  }
789
785
  },
790
786
  async setItem(key, value) {
@@ -800,9 +796,8 @@ const fsStorage = createStorage(() => ({
800
796
  let entries;
801
797
  try {
802
798
  entries = await readdir(dir, { withFileTypes: true });
803
- } catch (error) {
804
- if (isMissingPathError(error)) return;
805
- throw new Error(`Failed to list storage keys under "${resolvedBase}"`, { cause: error });
799
+ } catch (_error) {
800
+ return;
806
801
  }
807
802
  for (const entry of entries) {
808
803
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
@@ -819,37 +814,69 @@ const fsStorage = createStorage(() => ({
819
814
  }
820
815
  }));
821
816
  //#endregion
822
- //#region package.json
823
- var version$1 = "5.0.0-beta.1";
824
- //#endregion
825
- //#region src/utils/diagnostics.ts
817
+ //#region src/createKubb.ts
826
818
  /**
827
- * Returns a snapshot of the current runtime environment.
819
+ * Builds a `Storage` view scoped to the file paths produced by the current build.
828
820
  *
829
- * Useful for attaching context to debug logs and error reports so that
830
- * issues can be reproduced without manual information gathering.
821
+ * Reads delegate to the underlying `storage` (typically `fsStorage()`) so source bytes
822
+ * stay where they were written instead of being held in an extra in-memory map.
823
+ * Writing via `setItem` stores the content in the underlying storage and registers the
824
+ * key so subsequent reads and `getKeys` are scoped to this build's output.
831
825
  */
832
- function getDiagnosticInfo() {
833
- return {
834
- nodeVersion: version,
835
- KubbVersion: version$1,
836
- platform: process.platform,
837
- arch: process.arch,
838
- cwd: process.cwd()
839
- };
840
- }
841
- //#endregion
842
- //#region src/utils/isInputPath.ts
843
- function isInputPath(config) {
844
- return typeof config?.input === "object" && config.input !== null && "path" in config.input;
826
+ function createSourcesView(storage) {
827
+ const paths = /* @__PURE__ */ new Set();
828
+ return createStorage(() => ({
829
+ name: `${storage.name}:sources`,
830
+ async hasItem(key) {
831
+ return paths.has(key) && await storage.hasItem(key);
832
+ },
833
+ async getItem(key) {
834
+ return paths.has(key) ? storage.getItem(key) : null;
835
+ },
836
+ async setItem(key, value) {
837
+ paths.add(key);
838
+ await storage.setItem(key, value);
839
+ },
840
+ async removeItem(key) {
841
+ paths.delete(key);
842
+ await storage.removeItem(key);
843
+ },
844
+ async getKeys(base) {
845
+ if (!base) return [...paths];
846
+ const result = [];
847
+ for (const key of paths) if (key.startsWith(base)) result.push(key);
848
+ return result;
849
+ },
850
+ async clear() {
851
+ paths.clear();
852
+ await storage.clear();
853
+ }
854
+ }))();
845
855
  }
846
- //#endregion
847
- //#region src/createKubb.ts
848
856
  async function setup(userConfig, options = {}) {
849
857
  const hooks = options.hooks ?? new AsyncEventEmitter();
850
- const sources = /* @__PURE__ */ new Map();
858
+ const config = {
859
+ ...userConfig,
860
+ root: userConfig.root || process.cwd(),
861
+ parsers: userConfig.parsers ?? [],
862
+ adapter: userConfig.adapter,
863
+ output: {
864
+ format: false,
865
+ lint: false,
866
+ extension: DEFAULT_EXTENSION,
867
+ defaultBanner: DEFAULT_BANNER,
868
+ ...userConfig.output
869
+ },
870
+ storage: userConfig.storage ?? fsStorage(),
871
+ devtools: userConfig.devtools ? {
872
+ studioUrl: DEFAULT_STUDIO_URL,
873
+ ...typeof userConfig.devtools === "boolean" ? {} : userConfig.devtools
874
+ } : void 0,
875
+ plugins: userConfig.plugins ?? []
876
+ };
877
+ const driver = new PluginDriver(config, { hooks });
878
+ const storage = createSourcesView(config.storage);
851
879
  const diagnosticInfo = getDiagnosticInfo();
852
- if (Array.isArray(userConfig.input)) await hooks.emit("kubb:warn", { message: "This feature is still under development — use with caution" });
853
880
  await hooks.emit("kubb:debug", {
854
881
  date: /* @__PURE__ */ new Date(),
855
882
  logs: [
@@ -859,7 +886,7 @@ async function setup(userConfig, options = {}) {
859
886
  ` • Output: ${userConfig.output?.path || "not specified"}`,
860
887
  ` • Plugins: ${userConfig.plugins?.length || 0}`,
861
888
  "Output Settings:",
862
- ` • Storage: ${userConfig.storage ? `custom(${userConfig.storage.name})` : userConfig.output?.write === false ? "disabled" : "filesystem (default)"}`,
889
+ ` • Storage: ${config.storage.name}`,
863
890
  ` • Formatter: ${userConfig.output?.format || "none"}`,
864
891
  ` • Linter: ${userConfig.output?.lint || "none"}`,
865
892
  "Environment:",
@@ -880,73 +907,56 @@ async function setup(userConfig, options = {}) {
880
907
  throw new Error(`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`, { cause: error });
881
908
  }
882
909
  }
883
- if (!userConfig.adapter) throw new Error("Adapter should be defined");
884
- const config = {
885
- ...userConfig,
886
- root: userConfig.root || process.cwd(),
887
- parsers: userConfig.parsers ?? [],
888
- adapter: userConfig.adapter,
889
- output: {
890
- format: false,
891
- lint: false,
892
- write: true,
893
- extension: DEFAULT_EXTENSION,
894
- defaultBanner: DEFAULT_BANNER,
895
- ...userConfig.output
896
- },
897
- devtools: userConfig.devtools ? {
898
- studioUrl: DEFAULT_STUDIO_URL,
899
- ...typeof userConfig.devtools === "boolean" ? {} : userConfig.devtools
900
- } : void 0,
901
- plugins: userConfig.plugins
902
- };
903
- const storage = config.output.write === false ? null : config.storage ?? fsStorage();
904
910
  if (config.output.clean) {
905
911
  await hooks.emit("kubb:debug", {
906
912
  date: /* @__PURE__ */ new Date(),
907
913
  logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
908
914
  });
909
- await storage?.clear(resolve(config.root, config.output.path));
915
+ await config.storage.clear(resolve(config.root, config.output.path));
910
916
  }
911
- const driver = new PluginDriver(config, { hooks });
912
917
  function registerMiddlewareHook(event, middlewareHooks) {
913
918
  const handler = middlewareHooks[event];
914
919
  if (handler) hooks.on(event, handler);
915
920
  }
916
921
  for (const middleware of config.middleware ?? []) for (const event of Object.keys(middleware.hooks)) registerMiddlewareHook(event, middleware.hooks);
917
- const adapter = config.adapter;
918
- if (!adapter) throw new Error("No adapter configured. Please provide an adapter in your kubb.config.ts.");
919
- const source = inputToAdapterSource(config);
920
- await hooks.emit("kubb:debug", {
921
- date: /* @__PURE__ */ new Date(),
922
- logs: [`Running adapter: ${adapter.name}`]
923
- });
924
- driver.adapter = adapter;
925
- driver.inputNode = await adapter.parse(source);
926
- await hooks.emit("kubb:debug", {
927
- date: /* @__PURE__ */ new Date(),
928
- logs: [
929
- `✓ Adapter '${adapter.name}' resolved InputNode`,
930
- ` • Schemas: ${driver.inputNode.schemas.length}`,
931
- ` • Operations: ${driver.inputNode.operations.length}`
932
- ]
933
- });
922
+ if (config.adapter) {
923
+ const source = inputToAdapterSource(config);
924
+ await hooks.emit("kubb:debug", {
925
+ date: /* @__PURE__ */ new Date(),
926
+ logs: [`Running adapter: ${config.adapter.name}`]
927
+ });
928
+ driver.adapter = config.adapter;
929
+ driver.inputNode = await config.adapter.parse(source);
930
+ await hooks.emit("kubb:debug", {
931
+ date: /* @__PURE__ */ new Date(),
932
+ logs: [
933
+ `✓ Adapter '${config.adapter.name}' resolved InputNode`,
934
+ ` • Schemas: ${driver.inputNode.schemas.length}`,
935
+ ` • Operations: ${driver.inputNode.operations.length}`
936
+ ]
937
+ });
938
+ }
934
939
  return {
935
940
  config,
936
941
  hooks,
937
942
  driver,
938
- sources,
939
943
  storage
940
944
  };
941
945
  }
942
946
  /**
943
947
  * Walks the AST and dispatches nodes to a plugin's direct AST hooks
944
948
  * (`schema`, `operation`, `operations`).
949
+ *
950
+ * When `include` contains only operation-scoped filters (`tag`, `operationId`, `path`,
951
+ * `method`, `contentType`) and no `schemaName` filter, the function pre-computes the set
952
+ * of top-level schema names transitively reachable from the included operations and skips
953
+ * schemas that fall outside that set. This ensures that component schemas referenced
954
+ * exclusively by excluded operations are not generated.
945
955
  */
946
956
  async function runPluginAstHooks(plugin, context) {
947
957
  const { adapter, inputNode, resolver, driver } = context;
948
958
  const { exclude, include, override } = plugin.options;
949
- if (!adapter || !inputNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`);
959
+ if (!adapter || !inputNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. adapterOas()) before this plugin in your Kubb config.`);
950
960
  function resolveRenderer(gen) {
951
961
  return gen.renderer === null ? void 0 : gen.renderer ?? plugin.renderer ?? context.config.renderer;
952
962
  }
@@ -956,10 +966,27 @@ async function runPluginAstHooks(plugin, context) {
956
966
  ...context,
957
967
  resolver: driver.getResolver(plugin.name)
958
968
  };
969
+ const operationFilterTypes = new Set([
970
+ "tag",
971
+ "operationId",
972
+ "path",
973
+ "method",
974
+ "contentType"
975
+ ]);
976
+ const hasOperationBasedIncludes = include?.some(({ type }) => operationFilterTypes.has(type)) ?? false;
977
+ const hasSchemaNameIncludes = include?.some(({ type }) => type === "schemaName") ?? false;
978
+ let allowedSchemaNames;
979
+ if (hasOperationBasedIncludes && !hasSchemaNameIncludes) allowedSchemaNames = collectUsedSchemaNames(inputNode.operations.filter((op) => resolver.resolveOptions(op, {
980
+ options: plugin.options,
981
+ exclude,
982
+ include,
983
+ override
984
+ }) !== null), inputNode.schemas);
959
985
  await walk(inputNode, {
960
986
  depth: "shallow",
961
987
  async schema(node) {
962
988
  const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
989
+ if (allowedSchemaNames !== void 0 && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) return;
963
990
  const options = resolver.resolveOptions(transformedNode, {
964
991
  options: plugin.options,
965
992
  exclude,
@@ -1012,10 +1039,49 @@ async function runPluginAstHooks(plugin, context) {
1012
1039
  }
1013
1040
  }
1014
1041
  async function safeBuild(setupResult) {
1015
- const { driver, hooks, sources, storage } = setupResult;
1042
+ const { driver, hooks, storage } = setupResult;
1016
1043
  const failedPlugins = /* @__PURE__ */ new Set();
1017
1044
  const pluginTimings = /* @__PURE__ */ new Map();
1018
1045
  const config = driver.config;
1046
+ const writtenPaths = /* @__PURE__ */ new Set();
1047
+ const parsersMap = /* @__PURE__ */ new Map();
1048
+ for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
1049
+ const fileProcessor = new FileProcessor();
1050
+ fileProcessor.events.on("start", async (processingFiles) => {
1051
+ await hooks.emit("kubb:files:processing:start", { files: processingFiles });
1052
+ });
1053
+ fileProcessor.events.on("update", async ({ file, source, processed, total, percentage }) => {
1054
+ await hooks.emit("kubb:file:processing:update", {
1055
+ file,
1056
+ source,
1057
+ processed,
1058
+ total,
1059
+ percentage,
1060
+ config
1061
+ });
1062
+ if (source) await storage.setItem(file.path, source);
1063
+ });
1064
+ fileProcessor.events.on("end", async (processed) => {
1065
+ await hooks.emit("kubb:files:processing:end", { files: processed });
1066
+ await hooks.emit("kubb:debug", {
1067
+ date: /* @__PURE__ */ new Date(),
1068
+ logs: [`✓ File write process completed for ${processed.length} files`]
1069
+ });
1070
+ });
1071
+ async function flushPendingFiles() {
1072
+ const files = driver.fileManager.files.filter((f) => !writtenPaths.has(f.path));
1073
+ if (files.length === 0) return;
1074
+ await hooks.emit("kubb:debug", {
1075
+ date: /* @__PURE__ */ new Date(),
1076
+ logs: [`Writing ${files.length} files...`]
1077
+ });
1078
+ await fileProcessor.run(files, {
1079
+ parsers: parsersMap,
1080
+ mode: "parallel",
1081
+ extension: config.output.extension
1082
+ });
1083
+ for (const file of files) writtenPaths.add(file.path);
1084
+ }
1019
1085
  try {
1020
1086
  await driver.emitSetupHooks();
1021
1087
  if (driver.adapter && driver.inputNode) await hooks.emit("kubb:build:start", {
@@ -1051,6 +1117,7 @@ async function safeBuild(setupResult) {
1051
1117
  },
1052
1118
  upsertFile: (...files) => driver.fileManager.upsert(...files)
1053
1119
  });
1120
+ await flushPendingFiles();
1054
1121
  await hooks.emit("kubb:debug", {
1055
1122
  date: /* @__PURE__ */ new Date(),
1056
1123
  logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
@@ -1070,6 +1137,7 @@ async function safeBuild(setupResult) {
1070
1137
  },
1071
1138
  upsertFile: (...files) => driver.fileManager.upsert(...files)
1072
1139
  });
1140
+ await flushPendingFiles();
1073
1141
  await hooks.emit("kubb:debug", {
1074
1142
  date: errorTimestamp,
1075
1143
  logs: [
@@ -1093,42 +1161,8 @@ async function safeBuild(setupResult) {
1093
1161
  },
1094
1162
  upsertFile: (...files) => driver.fileManager.upsert(...files)
1095
1163
  });
1164
+ await flushPendingFiles();
1096
1165
  const files = driver.fileManager.files;
1097
- const parsersMap = /* @__PURE__ */ new Map();
1098
- for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
1099
- const fileProcessor = new FileProcessor();
1100
- await hooks.emit("kubb:debug", {
1101
- date: /* @__PURE__ */ new Date(),
1102
- logs: [`Writing ${files.length} files...`]
1103
- });
1104
- await fileProcessor.run(files, {
1105
- parsers: parsersMap,
1106
- extension: config.output.extension,
1107
- onStart: async (processingFiles) => {
1108
- await hooks.emit("kubb:files:processing:start", { files: processingFiles });
1109
- },
1110
- onUpdate: async ({ file, source, processed, total, percentage }) => {
1111
- await hooks.emit("kubb:file:processing:update", {
1112
- file,
1113
- source,
1114
- processed,
1115
- total,
1116
- percentage,
1117
- config
1118
- });
1119
- if (source) {
1120
- await storage?.setItem(file.path, source);
1121
- sources.set(file.path, source);
1122
- }
1123
- },
1124
- onEnd: async (processedFiles) => {
1125
- await hooks.emit("kubb:files:processing:end", { files: processedFiles });
1126
- await hooks.emit("kubb:debug", {
1127
- date: /* @__PURE__ */ new Date(),
1128
- logs: [`✓ File write process completed for ${processedFiles.length} files`]
1129
- });
1130
- }
1131
- });
1132
1166
  await hooks.emit("kubb:build:end", {
1133
1167
  files,
1134
1168
  config,
@@ -1139,7 +1173,7 @@ async function safeBuild(setupResult) {
1139
1173
  files,
1140
1174
  driver,
1141
1175
  pluginTimings,
1142
- sources
1176
+ storage
1143
1177
  };
1144
1178
  } catch (error) {
1145
1179
  return {
@@ -1148,14 +1182,14 @@ async function safeBuild(setupResult) {
1148
1182
  driver,
1149
1183
  pluginTimings,
1150
1184
  error,
1151
- sources
1185
+ storage
1152
1186
  };
1153
1187
  } finally {
1154
1188
  driver.dispose();
1155
1189
  }
1156
1190
  }
1157
1191
  async function build(setupResult) {
1158
- const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult);
1192
+ const { files, driver, failedPlugins, pluginTimings, error, storage } = await safeBuild(setupResult);
1159
1193
  if (error) throw error;
1160
1194
  if (failedPlugins.size > 0) {
1161
1195
  const errors = [...failedPlugins].map(({ error }) => error);
@@ -1167,32 +1201,48 @@ async function build(setupResult) {
1167
1201
  driver,
1168
1202
  pluginTimings,
1169
1203
  error: void 0,
1170
- sources
1204
+ storage
1171
1205
  };
1172
1206
  }
1173
- function inputToAdapterSource(config) {
1174
- if (Array.isArray(config.input)) return {
1175
- type: "paths",
1176
- paths: config.input.map((i) => new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))
1207
+ /**
1208
+ * Returns a snapshot of the current runtime environment.
1209
+ *
1210
+ * Useful for attaching context to debug logs and error reports so that
1211
+ * issues can be reproduced without manual information gathering.
1212
+ */
1213
+ function getDiagnosticInfo() {
1214
+ return {
1215
+ nodeVersion: version,
1216
+ KubbVersion: version$1,
1217
+ platform: process.platform,
1218
+ arch: process.arch,
1219
+ cwd: process.cwd()
1177
1220
  };
1178
- if ("data" in config.input) return {
1221
+ }
1222
+ function isInputPath(config) {
1223
+ return typeof config?.input === "object" && config.input !== null && "path" in config.input;
1224
+ }
1225
+ function inputToAdapterSource(config) {
1226
+ const input = config.input;
1227
+ if (!input) throw new Error("[kubb] input is required when using an adapter. Provide input.path or input.data in your config.");
1228
+ if ("data" in input) return {
1179
1229
  type: "data",
1180
- data: config.input.data
1230
+ data: input.data
1181
1231
  };
1182
- if (new URLPath(config.input.path).isURL) return {
1232
+ if (new URLPath(input.path).isURL) return {
1183
1233
  type: "path",
1184
- path: config.input.path
1234
+ path: input.path
1185
1235
  };
1186
1236
  return {
1187
1237
  type: "path",
1188
- path: resolve(config.root, config.input.path)
1238
+ path: resolve(config.root, input.path)
1189
1239
  };
1190
1240
  }
1191
1241
  /**
1192
1242
  * Creates a Kubb instance bound to a single config entry.
1193
1243
  *
1194
1244
  * Accepts a user-facing config shape and resolves it to a full {@link Config} during
1195
- * `setup()`. The instance then holds shared state (`hooks`, `sources`, `driver`, `config`)
1245
+ * `setup()`. The instance then holds shared state (`hooks`, `storage`, `driver`, `config`)
1196
1246
  * across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
1197
1247
  * calling `setup()` or `build()`.
1198
1248
  *
@@ -1214,14 +1264,17 @@ function createKubb(userConfig, options = {}) {
1214
1264
  get hooks() {
1215
1265
  return hooks;
1216
1266
  },
1217
- get sources() {
1218
- return setupResult?.sources ?? /* @__PURE__ */ new Map();
1267
+ get storage() {
1268
+ if (!setupResult) throw new Error("[kubb] setup() must be called before accessing storage");
1269
+ return setupResult.storage;
1219
1270
  },
1220
1271
  get driver() {
1221
- return setupResult?.driver;
1272
+ if (!setupResult) throw new Error("[kubb] setup() must be called before accessing driver");
1273
+ return setupResult.driver;
1222
1274
  },
1223
1275
  get config() {
1224
- return setupResult?.config;
1276
+ if (!setupResult) throw new Error("[kubb] setup() must be called before accessing config");
1277
+ return setupResult.config;
1225
1278
  },
1226
1279
  async setup() {
1227
1280
  setupResult = await setup(userConfig, { hooks });
@@ -1285,7 +1338,11 @@ function defineGenerator(generator) {
1285
1338
  /**
1286
1339
  * Wraps a logger definition into a typed {@link Logger}.
1287
1340
  *
1288
- * @example
1341
+ * The optional second type parameter `TInstallReturn` allows loggers to return
1342
+ * a value from `install` — for example, a sink factory that the caller can
1343
+ * forward to hook execution.
1344
+ *
1345
+ * @example Basic logger
1289
1346
  * ```ts
1290
1347
  * export const myLogger = defineLogger({
1291
1348
  * name: 'my-logger',
@@ -1295,6 +1352,17 @@ function defineGenerator(generator) {
1295
1352
  * },
1296
1353
  * })
1297
1354
  * ```
1355
+ *
1356
+ * @example Logger that returns a hook sink factory
1357
+ * ```ts
1358
+ * export const myLogger = defineLogger<LoggerOptions, HookSinkFactory>({
1359
+ * name: 'my-logger',
1360
+ * install(context, options) {
1361
+ * // … register event handlers …
1362
+ * return (commandWithArgs) => ({ onStdout: console.log })
1363
+ * },
1364
+ * })
1365
+ * ```
1298
1366
  */
1299
1367
  function defineLogger(logger) {
1300
1368
  return logger;
@@ -1365,34 +1433,6 @@ function defineParser(parser) {
1365
1433
  return parser;
1366
1434
  }
1367
1435
  //#endregion
1368
- //#region src/definePlugin.ts
1369
- /**
1370
- * Wraps a factory function and returns a typed `Plugin` with lifecycle handlers grouped under `hooks`.
1371
- *
1372
- * Handlers live in a single `hooks` object (inspired by Astro integrations).
1373
- * All lifecycle events from `KubbHooks` are available for subscription.
1374
- *
1375
- * @note For real plugins, use a `PluginFactoryOptions` type parameter to get type-safe context in `kubb:plugin:setup`.
1376
- * Plugin names should follow the convention `plugin-<feature>` (e.g., `plugin-react-query`, `plugin-zod`).
1377
- *
1378
- * @example
1379
- * ```ts
1380
- * import { definePlugin } from '@kubb/core'
1381
- *
1382
- * export const pluginTs = definePlugin((options: { prefix?: string } = {}) => ({
1383
- * name: 'plugin-ts',
1384
- * hooks: {
1385
- * 'kubb:plugin:setup'(ctx) {
1386
- * ctx.setResolver(resolverTs)
1387
- * },
1388
- * },
1389
- * }))
1390
- * ```
1391
- */
1392
- function definePlugin(factory) {
1393
- return (options) => factory(options ?? {});
1394
- }
1395
- //#endregion
1396
1436
  //#region src/storages/memoryStorage.ts
1397
1437
  /**
1398
1438
  * In-memory storage driver. Useful for testing and dry-run scenarios where