@kubb/core 5.0.0-alpha.30 → 5.0.0-alpha.32

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.
package/dist/index.cjs CHANGED
@@ -9,12 +9,10 @@ let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
10
10
  node_path = require_chunk.__toESM(node_path);
11
11
  let _kubb_ast = require("@kubb/ast");
12
- let _kubb_react_fabric = require("@kubb/react-fabric");
13
- let _kubb_react_fabric_parsers = require("@kubb/react-fabric/parsers");
14
- let _kubb_react_fabric_plugins = require("@kubb/react-fabric/plugins");
15
12
  let node_perf_hooks = require("node:perf_hooks");
16
13
  let fflate = require("fflate");
17
14
  let tinyexec = require("tinyexec");
15
+ let _kubb_react_fabric = require("@kubb/react-fabric");
18
16
  let _kubb_react_fabric_jsx_runtime = require("@kubb/react-fabric/jsx-runtime");
19
17
  let node_process = require("node:process");
20
18
  let remeda = require("remeda");
@@ -340,6 +338,24 @@ async function clean(path) {
340
338
  });
341
339
  }
342
340
  //#endregion
341
+ //#region ../../internals/utils/src/string.ts
342
+ /**
343
+ * Strips the file extension from a path or file name.
344
+ * Only removes the last `.ext` segment when the dot is not part of a directory name.
345
+ *
346
+ * @example
347
+ * trimExtName('petStore.ts') // 'petStore'
348
+ * trimExtName('/src/models/pet.ts') // '/src/models/pet'
349
+ * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
350
+ * trimExtName('noExtension') // 'noExtension'
351
+ */
352
+ function trimExtName$1(text) {
353
+ const dotIndex = text.lastIndexOf(".");
354
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
355
+ return text;
356
+ }
357
+ require_chunk.__name(trimExtName$1, "trimExtName");
358
+ //#endregion
343
359
  //#region ../../internals/utils/src/promise.ts
344
360
  /** Returns `true` when `result` is a rejected `Promise.allSettled` result with a typed `reason`.
345
361
  *
@@ -621,17 +637,6 @@ var URLPath = class {
621
637
  }
622
638
  };
623
639
  //#endregion
624
- //#region src/config.ts
625
- function defineConfig(config) {
626
- return config;
627
- }
628
- /**
629
- * Type guard to check if a given config has an `input.path`.
630
- */
631
- function isInputPath(config) {
632
- return typeof config?.input === "object" && config.input !== null && "path" in config.input;
633
- }
634
- //#endregion
635
640
  //#region src/constants.ts
636
641
  /**
637
642
  * Base URL for the Kubb Studio web app.
@@ -723,50 +728,6 @@ const formatters = {
723
728
  }
724
729
  };
725
730
  //#endregion
726
- //#region src/devtools.ts
727
- /**
728
- * Encodes a `RootNode` as a compressed, URL-safe string.
729
- *
730
- * The JSON representation is deflate-compressed with {@link deflateSync} before
731
- * base64url encoding, which typically reduces payload size by 70–80 % and
732
- * keeps URLs well within browser and server path-length limits.
733
- *
734
- * Use {@link decodeAst} to reverse.
735
- */
736
- function encodeAst(root) {
737
- const compressed = (0, fflate.deflateSync)(new TextEncoder().encode(JSON.stringify(root)));
738
- return Buffer.from(compressed).toString("base64url");
739
- }
740
- /**
741
- * Constructs the Kubb Studio URL for the given `RootNode`.
742
- * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
743
- * The `root` is encoded and attached as the `?root=` query parameter so Studio
744
- * can decode and render it without a round-trip to any server.
745
- */
746
- function getStudioUrl(root, studioUrl, options = {}) {
747
- return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(root)}`;
748
- }
749
- /**
750
- * Opens the Kubb Studio URL for the given `RootNode` in the default browser —
751
- *
752
- * Falls back to printing the URL if the browser cannot be launched.
753
- */
754
- async function openInStudio(root, studioUrl, options = {}) {
755
- const url = getStudioUrl(root, studioUrl, options);
756
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
757
- const args = process.platform === "win32" ? [
758
- "/c",
759
- "start",
760
- "",
761
- url
762
- ] : [url];
763
- try {
764
- await (0, tinyexec.x)(cmd, args);
765
- } catch {
766
- console.log(`\n ${url}\n`);
767
- }
768
- }
769
- //#endregion
770
731
  //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
771
732
  var Node = class {
772
733
  value;
@@ -894,6 +855,194 @@ function validateConcurrency(concurrency) {
894
855
  if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
895
856
  }
896
857
  //#endregion
858
+ //#region src/FileProcessor.ts
859
+ function joinSources(file) {
860
+ return file.sources.map((item) => item.value).filter((value) => value != null).join("\n\n");
861
+ }
862
+ /**
863
+ * Converts a single file to a string using the registered parsers.
864
+ * Falls back to joining source values when no matching parser is found.
865
+ */
866
+ var FileProcessor = class {
867
+ #limit = pLimit(100);
868
+ async parse(file, { parsers, extension } = {}) {
869
+ const parseExtName = extension?.[file.extname] || void 0;
870
+ if (!parsers || !file.extname) return joinSources(file);
871
+ const parser = parsers.get(file.extname);
872
+ if (!parser) return joinSources(file);
873
+ return parser.parse(file, { extname: parseExtName });
874
+ }
875
+ async run(files, { parsers, mode = "sequential", extension, onStart, onEnd, onUpdate } = {}) {
876
+ await onStart?.(files);
877
+ const total = files.length;
878
+ let processed = 0;
879
+ const processOne = async (file) => {
880
+ const source = await this.parse(file, {
881
+ extension,
882
+ parsers
883
+ });
884
+ const currentProcessed = ++processed;
885
+ const percentage = currentProcessed / total * 100;
886
+ await onUpdate?.({
887
+ file,
888
+ source,
889
+ processed: currentProcessed,
890
+ percentage,
891
+ total
892
+ });
893
+ };
894
+ if (mode === "sequential") for (const file of files) await processOne(file);
895
+ else await Promise.all(files.map((file) => this.#limit(() => processOne(file))));
896
+ await onEnd?.(files);
897
+ return files;
898
+ }
899
+ };
900
+ //#endregion
901
+ //#region src/devtools.ts
902
+ /**
903
+ * Encodes an `InputNode` as a compressed, URL-safe string.
904
+ *
905
+ * The JSON representation is deflate-compressed with {@link deflateSync} before
906
+ * base64url encoding, which typically reduces payload size by 70–80 % and
907
+ * keeps URLs well within browser and server path-length limits.
908
+ *
909
+ * Use {@link decodeAst} to reverse.
910
+ */
911
+ function encodeAst(input) {
912
+ const compressed = (0, fflate.deflateSync)(new TextEncoder().encode(JSON.stringify(input)));
913
+ return Buffer.from(compressed).toString("base64url");
914
+ }
915
+ /**
916
+ * Constructs the Kubb Studio URL for the given `InputNode`.
917
+ * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
918
+ * The `input` is encoded and attached as the `?root=` query parameter so Studio
919
+ * can decode and render it without a round-trip to any server.
920
+ */
921
+ function getStudioUrl(input, studioUrl, options = {}) {
922
+ return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(input)}`;
923
+ }
924
+ /**
925
+ * Opens the Kubb Studio URL for the given `InputNode` in the default browser —
926
+ *
927
+ * Falls back to printing the URL if the browser cannot be launched.
928
+ */
929
+ async function openInStudio(input, studioUrl, options = {}) {
930
+ const url = getStudioUrl(input, studioUrl, options);
931
+ const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
932
+ const args = process.platform === "win32" ? [
933
+ "/c",
934
+ "start",
935
+ "",
936
+ url
937
+ ] : [url];
938
+ try {
939
+ await (0, tinyexec.x)(cmd, args);
940
+ } catch {
941
+ console.log(`\n ${url}\n`);
942
+ }
943
+ }
944
+ //#endregion
945
+ //#region src/FileManager.ts
946
+ function mergeFile(a, b) {
947
+ return {
948
+ ...a,
949
+ sources: [...a.sources || [], ...b.sources || []],
950
+ imports: [...a.imports || [], ...b.imports || []],
951
+ exports: [...a.exports || [], ...b.exports || []]
952
+ };
953
+ }
954
+ /**
955
+ * In-memory file store for generated files.
956
+ *
957
+ * Files with the same `path` are merged — sources, imports, and exports are concatenated.
958
+ * The `files` getter returns all stored files sorted by path length (shortest first).
959
+ *
960
+ * @example
961
+ * ```ts
962
+ * import { FileManager } from '@kubb/core'
963
+ *
964
+ * const manager = new FileManager()
965
+ * manager.upsert(myFile)
966
+ * console.log(manager.files) // all stored files
967
+ * ```
968
+ */
969
+ var FileManager = class {
970
+ #cache = /* @__PURE__ */ new Map();
971
+ #filesCache = null;
972
+ /**
973
+ * Adds one or more files. Files with the same path are merged — sources, imports,
974
+ * and exports from all calls with the same path are concatenated together.
975
+ */
976
+ add(...files) {
977
+ const resolvedFiles = [];
978
+ const mergedFiles = /* @__PURE__ */ new Map();
979
+ files.forEach((file) => {
980
+ const existing = mergedFiles.get(file.path);
981
+ if (existing) mergedFiles.set(file.path, mergeFile(existing, file));
982
+ else mergedFiles.set(file.path, file);
983
+ });
984
+ for (const file of mergedFiles.values()) {
985
+ const resolvedFile = (0, _kubb_ast.createFile)(file);
986
+ this.#cache.set(resolvedFile.path, resolvedFile);
987
+ this.#filesCache = null;
988
+ resolvedFiles.push(resolvedFile);
989
+ }
990
+ return resolvedFiles;
991
+ }
992
+ /**
993
+ * Adds or merges one or more files.
994
+ * If a file with the same path already exists, its sources/imports/exports are merged together.
995
+ */
996
+ upsert(...files) {
997
+ const resolvedFiles = [];
998
+ const mergedFiles = /* @__PURE__ */ new Map();
999
+ files.forEach((file) => {
1000
+ const existing = mergedFiles.get(file.path);
1001
+ if (existing) mergedFiles.set(file.path, mergeFile(existing, file));
1002
+ else mergedFiles.set(file.path, file);
1003
+ });
1004
+ for (const file of mergedFiles.values()) {
1005
+ const existing = this.#cache.get(file.path);
1006
+ const resolvedFile = (0, _kubb_ast.createFile)(existing ? mergeFile(existing, file) : file);
1007
+ this.#cache.set(resolvedFile.path, resolvedFile);
1008
+ this.#filesCache = null;
1009
+ resolvedFiles.push(resolvedFile);
1010
+ }
1011
+ return resolvedFiles;
1012
+ }
1013
+ getByPath(path) {
1014
+ return this.#cache.get(path) ?? null;
1015
+ }
1016
+ deleteByPath(path) {
1017
+ this.#cache.delete(path);
1018
+ this.#filesCache = null;
1019
+ }
1020
+ clear() {
1021
+ this.#cache.clear();
1022
+ this.#filesCache = null;
1023
+ }
1024
+ /**
1025
+ * All stored files, sorted by path length (shorter paths first).
1026
+ * Barrel/index files (e.g. index.ts) are sorted last within each length bucket.
1027
+ */
1028
+ get files() {
1029
+ if (this.#filesCache) return this.#filesCache;
1030
+ const keys = [...this.#cache.keys()].sort((a, b) => {
1031
+ if (a.length !== b.length) return a.length - b.length;
1032
+ const aIsIndex = trimExtName$1(a).endsWith("index");
1033
+ if (aIsIndex !== trimExtName$1(b).endsWith("index")) return aIsIndex ? 1 : -1;
1034
+ return 0;
1035
+ });
1036
+ const files = [];
1037
+ for (const key of keys) {
1038
+ const file = this.#cache.get(key);
1039
+ if (file) files.push(file);
1040
+ }
1041
+ this.#filesCache = files;
1042
+ return files;
1043
+ }
1044
+ };
1045
+ //#endregion
897
1046
  //#region src/utils/executeStrategies.ts
898
1047
  /**
899
1048
  * Runs promise functions in sequence, threading each result into the next call.
@@ -960,12 +1109,18 @@ var PluginDriver = class {
960
1109
  config;
961
1110
  options;
962
1111
  /**
963
- * The universal `@kubb/ast` `RootNode` produced by the adapter, set by
1112
+ * The universal `@kubb/ast` `InputNode` produced by the adapter, set by
964
1113
  * the build pipeline after the adapter's `parse()` resolves.
965
1114
  */
966
- rootNode = void 0;
1115
+ inputNode = void 0;
967
1116
  adapter = void 0;
968
1117
  #studioIsOpen = false;
1118
+ /**
1119
+ * Central file store for all generated files.
1120
+ * Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
1121
+ * add files; this property gives direct read/write access when needed.
1122
+ */
1123
+ fileManager = new FileManager();
969
1124
  plugins = /* @__PURE__ */ new Map();
970
1125
  constructor(config, options) {
971
1126
  this.config = config;
@@ -990,7 +1145,6 @@ var PluginDriver = class {
990
1145
  getContext(plugin) {
991
1146
  const driver = this;
992
1147
  const baseContext = {
993
- fabric: driver.options.fabric,
994
1148
  config: driver.config,
995
1149
  get root() {
996
1150
  return (0, node_path.resolve)(driver.config.root, driver.config.output.path);
@@ -1004,13 +1158,13 @@ var PluginDriver = class {
1004
1158
  requirePlugin: driver.requirePlugin.bind(driver),
1005
1159
  driver,
1006
1160
  addFile: async (...files) => {
1007
- await this.options.fabric.addFile(...files);
1161
+ driver.fileManager.add(...files);
1008
1162
  },
1009
1163
  upsertFile: async (...files) => {
1010
- await this.options.fabric.upsertFile(...files);
1164
+ driver.fileManager.upsert(...files);
1011
1165
  },
1012
- get rootNode() {
1013
- return driver.rootNode;
1166
+ get inputNode() {
1167
+ return driver.inputNode;
1014
1168
  },
1015
1169
  get adapter() {
1016
1170
  return driver.adapter;
@@ -1033,10 +1187,10 @@ var PluginDriver = class {
1033
1187
  openInStudio(options) {
1034
1188
  if (!driver.config.devtools || driver.#studioIsOpen) return;
1035
1189
  if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
1036
- if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1190
+ if (!driver.inputNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1037
1191
  driver.#studioIsOpen = true;
1038
1192
  const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1039
- return openInStudio(driver.rootNode, studioUrl, options);
1193
+ return openInStudio(driver.inputNode, studioUrl, options);
1040
1194
  }
1041
1195
  };
1042
1196
  const mergedExtras = {};
@@ -1065,14 +1219,14 @@ var PluginDriver = class {
1065
1219
  options
1066
1220
  });
1067
1221
  if (!path) throw new Error(`Filepath should be defined for resolvedName "${resolvedName}" and pluginName "${pluginName}"`);
1068
- return {
1222
+ return (0, _kubb_ast.createFile)({
1069
1223
  path,
1070
1224
  baseName: (0, node_path.basename)(path),
1071
1225
  meta: { pluginName },
1072
1226
  sources: [],
1073
1227
  imports: [],
1074
1228
  exports: []
1075
- };
1229
+ });
1076
1230
  }
1077
1231
  /**
1078
1232
  * @deprecated use resolvers context instead
@@ -1356,19 +1510,19 @@ var PluginDriver = class {
1356
1510
  /**
1357
1511
  * Handles the return value of a plugin AST hook or generator method.
1358
1512
  *
1359
- * - React element → rendered via an isolated react-fabric context, files merged into `fabric`
1360
- * - `Array<FabricFile.File>` → upserted directly into `fabric`
1513
+ * - React element → rendered via an isolated react-fabric context, files stored in `driver.fileManager`
1514
+ * - `Array<FileNode>` → upserted directly into `driver.fileManager`
1361
1515
  * - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
1362
1516
  */
1363
- async function applyHookResult(result, fabric) {
1517
+ async function applyHookResult(result, driver) {
1364
1518
  if (!result) return;
1365
1519
  if (Array.isArray(result)) {
1366
- await fabric.upsertFile(...result);
1520
+ driver.fileManager.upsert(...result);
1367
1521
  return;
1368
1522
  }
1369
1523
  const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
1370
1524
  await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, { children: result }));
1371
- fabric.context.fileManager.upsert(...fabricChild.files);
1525
+ driver.fileManager.upsert(...fabricChild.files);
1372
1526
  fabricChild.unmount();
1373
1527
  }
1374
1528
  //#endregion
@@ -1469,7 +1623,7 @@ const fsStorage = createStorage(() => ({
1469
1623
  }));
1470
1624
  //#endregion
1471
1625
  //#region package.json
1472
- var version = "5.0.0-alpha.30";
1626
+ var version = "5.0.0-alpha.32";
1473
1627
  //#endregion
1474
1628
  //#region src/utils/diagnostics.ts
1475
1629
  /**
@@ -1491,7 +1645,7 @@ function getDiagnosticInfo() {
1491
1645
  //#region src/utils/TreeNode.ts
1492
1646
  /**
1493
1647
  * Tree structure used to build per-directory barrel (`index.ts`) files from a
1494
- * flat list of generated {@link FabricFile.File} entries.
1648
+ * flat list of generated {@link FileNode} entries.
1495
1649
  *
1496
1650
  * Each node represents either a directory or a file within the output tree.
1497
1651
  * Use {@link TreeNode.build} to construct a root node from a file list, then
@@ -1652,36 +1806,36 @@ function getBarrelFilesByRoot(root, files) {
1652
1806
  const cachedFiles = /* @__PURE__ */ new Map();
1653
1807
  TreeNode.build(files, root)?.forEach((treeNode) => {
1654
1808
  if (!treeNode?.children || !treeNode.parent?.data.path) return;
1655
- const barrelFile = {
1809
+ const barrelFile = (0, _kubb_ast.createFile)({
1656
1810
  path: (0, node_path.join)(treeNode.parent?.data.path, "index.ts"),
1657
1811
  baseName: "index.ts",
1658
1812
  exports: [],
1659
1813
  imports: [],
1660
1814
  sources: []
1661
- };
1815
+ });
1662
1816
  const previousBarrelFile = cachedFiles.get(barrelFile.path);
1663
1817
  treeNode.leaves.forEach((item) => {
1664
1818
  if (!item.data.name) return;
1665
1819
  (item.data.file?.sources || []).forEach((source) => {
1666
1820
  if (!item.data.file?.path || !source.isIndexable || !source.name) return;
1667
1821
  if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
1668
- barrelFile.exports.push({
1822
+ barrelFile.exports.push((0, _kubb_ast.createExport)({
1669
1823
  name: [source.name],
1670
1824
  path: getRelativePath(treeNode.parent?.data.path, item.data.path),
1671
1825
  isTypeOnly: source.isTypeOnly
1672
- });
1673
- barrelFile.sources.push({
1826
+ }));
1827
+ barrelFile.sources.push((0, _kubb_ast.createSource)({
1674
1828
  name: source.name,
1675
1829
  isTypeOnly: source.isTypeOnly,
1676
1830
  value: "",
1677
1831
  isExportable: false,
1678
1832
  isIndexable: false
1679
- });
1833
+ }));
1680
1834
  });
1681
1835
  });
1682
1836
  if (previousBarrelFile) {
1683
1837
  previousBarrelFile.sources.push(...barrelFile.sources);
1684
- previousBarrelFile.exports?.push(...barrelFile.exports || []);
1838
+ previousBarrelFile.exports.push(...barrelFile.exports);
1685
1839
  } else cachedFiles.set(barrelFile.path, barrelFile);
1686
1840
  });
1687
1841
  return [...cachedFiles.values()];
@@ -1707,7 +1861,7 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1707
1861
  if (type === "all") return barrelFiles.map((file) => {
1708
1862
  return {
1709
1863
  ...file,
1710
- exports: file.exports?.map((exportItem) => {
1864
+ exports: file.exports.map((exportItem) => {
1711
1865
  return {
1712
1866
  ...exportItem,
1713
1867
  name: void 0
@@ -1723,6 +1877,14 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1723
1877
  });
1724
1878
  }
1725
1879
  //#endregion
1880
+ //#region src/utils/isInputPath.ts
1881
+ /**
1882
+ * Type guard to check if a given config has an `input.path`.
1883
+ */
1884
+ function isInputPath(config) {
1885
+ return typeof config?.input === "object" && config.input !== null && "path" in config.input;
1886
+ }
1887
+ //#endregion
1726
1888
  //#region src/build.ts
1727
1889
  /**
1728
1890
  * Initializes all Kubb infrastructure for a build without executing any plugins.
@@ -1730,7 +1892,8 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1730
1892
  * - Validates the input path (when applicable).
1731
1893
  * - Applies config defaults (`root`, `output.*`, `devtools`).
1732
1894
  * - Creates the Fabric instance and wires storage, format, and lint hooks.
1733
- * - Runs the adapter (if configured) to produce the universal `RootNode`.
1895
+ * - Runs the adapter (if configured) to produce the universal `InputNode`.
1896
+ * When no adapter is supplied and `@kubb/adapter-oas` is installed as an
1734
1897
  *
1735
1898
  * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
1736
1899
  * via the `overrides` argument to reuse the same infrastructure across multiple runs.
@@ -1770,9 +1933,12 @@ async function setup(options) {
1770
1933
  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 });
1771
1934
  }
1772
1935
  }
1773
- const definedConfig = {
1936
+ if (!userConfig.adapter) throw new Error("Adapter should be defined");
1937
+ const config = {
1774
1938
  root: userConfig.root || process.cwd(),
1775
1939
  ...userConfig,
1940
+ parsers: userConfig.parsers ?? [],
1941
+ adapter: userConfig.adapter,
1776
1942
  output: {
1777
1943
  write: true,
1778
1944
  barrelType: "named",
@@ -1786,79 +1952,41 @@ async function setup(options) {
1786
1952
  } : void 0,
1787
1953
  plugins: userConfig.plugins
1788
1954
  };
1789
- const storage = definedConfig.output.write === false ? null : definedConfig.output.storage ?? fsStorage();
1790
- if (definedConfig.output.clean) {
1955
+ const storage = config.output.write === false ? null : config.output.storage ?? fsStorage();
1956
+ if (config.output.clean) {
1791
1957
  await events.emit("debug", {
1792
1958
  date: /* @__PURE__ */ new Date(),
1793
- logs: ["Cleaning output directories", ` • Output: ${definedConfig.output.path}`]
1959
+ logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
1794
1960
  });
1795
- await storage?.clear((0, node_path.resolve)(definedConfig.root, definedConfig.output.path));
1796
- }
1797
- const fabric = (0, _kubb_react_fabric.createFabric)();
1798
- fabric.use(_kubb_react_fabric_plugins.fsPlugin);
1799
- fabric.use(_kubb_react_fabric_parsers.typescriptParser);
1800
- fabric.context.on("files:processing:start", (files) => {
1801
- events.emit("files:processing:start", files);
1802
- events.emit("debug", {
1803
- date: /* @__PURE__ */ new Date(),
1804
- logs: [`Writing ${files.length} files...`]
1805
- });
1806
- });
1807
- fabric.context.on("file:processing:update", async (params) => {
1808
- const { file, source } = params;
1809
- await events.emit("file:processing:update", {
1810
- ...params,
1811
- config: definedConfig,
1812
- source
1813
- });
1814
- if (source) {
1815
- const key = (0, node_path.relative)((0, node_path.resolve)(definedConfig.root), file.path);
1816
- await storage?.setItem(key, source);
1817
- sources.set(file.path, source);
1818
- }
1961
+ await storage?.clear((0, node_path.resolve)(config.root, config.output.path));
1962
+ }
1963
+ const driver = new PluginDriver(config, {
1964
+ events,
1965
+ concurrency: 15
1819
1966
  });
1820
- fabric.context.on("files:processing:end", async (files) => {
1821
- await events.emit("files:processing:end", files);
1822
- await events.emit("debug", {
1823
- date: /* @__PURE__ */ new Date(),
1824
- logs: [`✓ File write process completed for ${files.length} files`]
1825
- });
1967
+ const adapter = config.adapter;
1968
+ if (!adapter) throw new Error("No adapter configured. Please provide an adapter in your kubb.config.ts.");
1969
+ const source = inputToAdapterSource(config);
1970
+ await events.emit("debug", {
1971
+ date: /* @__PURE__ */ new Date(),
1972
+ logs: [`Running adapter: ${adapter.name}`]
1826
1973
  });
1974
+ driver.adapter = adapter;
1975
+ driver.inputNode = await adapter.parse(source);
1827
1976
  await events.emit("debug", {
1828
1977
  date: /* @__PURE__ */ new Date(),
1829
1978
  logs: [
1830
- "✓ Fabric initialized",
1831
- ` • Storage: ${storage ? storage.name : "disabled (dry-run)"}`,
1832
- ` • Barrel type: ${definedConfig.output.barrelType || "none"}`
1979
+ `✓ Adapter '${adapter.name}' resolved InputNode`,
1980
+ ` • Schemas: ${driver.inputNode.schemas.length}`,
1981
+ ` • Operations: ${driver.inputNode.operations.length}`
1833
1982
  ]
1834
1983
  });
1835
- const pluginDriver = new PluginDriver(definedConfig, {
1836
- fabric,
1837
- events,
1838
- concurrency: 15
1839
- });
1840
- if (definedConfig.adapter) {
1841
- const source = inputToAdapterSource(definedConfig);
1842
- await events.emit("debug", {
1843
- date: /* @__PURE__ */ new Date(),
1844
- logs: [`Running adapter: ${definedConfig.adapter.name}`]
1845
- });
1846
- pluginDriver.adapter = definedConfig.adapter;
1847
- pluginDriver.rootNode = await definedConfig.adapter.parse(source);
1848
- await events.emit("debug", {
1849
- date: /* @__PURE__ */ new Date(),
1850
- logs: [
1851
- `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
1852
- ` • Schemas: ${pluginDriver.rootNode.schemas.length}`,
1853
- ` • Operations: ${pluginDriver.rootNode.operations.length}`
1854
- ]
1855
- });
1856
- }
1857
1984
  return {
1985
+ config,
1858
1986
  events,
1859
- fabric,
1860
- driver: pluginDriver,
1861
- sources
1987
+ driver,
1988
+ sources,
1989
+ storage
1862
1990
  };
1863
1991
  }
1864
1992
  /**
@@ -1868,7 +1996,7 @@ async function setup(options) {
1868
1996
  * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
1869
1997
  */
1870
1998
  async function build(options, overrides) {
1871
- const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1999
+ const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1872
2000
  if (error) throw error;
1873
2001
  if (failedPlugins.size > 0) {
1874
2002
  const errors = [...failedPlugins].map(({ error }) => error);
@@ -1876,7 +2004,6 @@ async function build(options, overrides) {
1876
2004
  }
1877
2005
  return {
1878
2006
  failedPlugins,
1879
- fabric,
1880
2007
  files,
1881
2008
  driver,
1882
2009
  pluginTimings,
@@ -1891,15 +2018,15 @@ async function build(options, overrides) {
1891
2018
  * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
1892
2019
  * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
1893
2020
  * - Return values are handled via `applyHookResult`: React elements are rendered,
1894
- * `FabricFile.File[]` are written via upsert, and `void` is a no-op (manual handling).
2021
+ * `FileNode[]` are written via upsert, and `void` is a no-op (manual handling).
1895
2022
  * - Barrel files are generated automatically when `output.barrelType` is set.
1896
2023
  */
1897
2024
  async function runPluginAstHooks(plugin, context) {
1898
- const { adapter, rootNode, resolver, fabric } = context;
2025
+ const { adapter, inputNode, resolver, driver } = context;
1899
2026
  const { exclude, include, override } = plugin.options;
1900
- if (!adapter || !rootNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`);
2027
+ 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.`);
1901
2028
  const collectedOperations = [];
1902
- await (0, _kubb_ast.walk)(rootNode, {
2029
+ await (0, _kubb_ast.walk)(inputNode, {
1903
2030
  depth: "shallow",
1904
2031
  async schema(node) {
1905
2032
  if (!plugin.schema) return;
@@ -1911,7 +2038,7 @@ async function runPluginAstHooks(plugin, context) {
1911
2038
  override
1912
2039
  });
1913
2040
  if (options === null) return;
1914
- await applyHookResult(await plugin.schema.call(context, transformedNode, options), fabric);
2041
+ await applyHookResult(await plugin.schema.call(context, transformedNode, options), driver);
1915
2042
  },
1916
2043
  async operation(node) {
1917
2044
  const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
@@ -1923,24 +2050,24 @@ async function runPluginAstHooks(plugin, context) {
1923
2050
  });
1924
2051
  if (options !== null) {
1925
2052
  collectedOperations.push(transformedNode);
1926
- if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), fabric);
2053
+ if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), driver);
1927
2054
  }
1928
2055
  }
1929
2056
  });
1930
- if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), fabric);
2057
+ if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), driver);
1931
2058
  }
1932
2059
  /**
1933
2060
  * Runs a full Kubb build and captures errors instead of throwing.
1934
2061
  *
1935
2062
  * - Installs each plugin in order, recording failures in `failedPlugins`.
1936
2063
  * - Generates the root barrel file when `output.barrelType` is set.
1937
- * - Writes all files through Fabric.
2064
+ * - Writes all files through the driver's FileManager and FileProcessor.
1938
2065
  *
1939
2066
  * Returns a {@link BuildOutput} even on failure — inspect `error` and
1940
2067
  * `failedPlugins` to determine whether the build succeeded.
1941
2068
  */
1942
2069
  async function safeBuild(options, overrides) {
1943
- const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
2070
+ const { driver, events, sources, storage } = overrides ? overrides : await setup(options);
1944
2071
  const failedPlugins = /* @__PURE__ */ new Set();
1945
2072
  const pluginTimings = /* @__PURE__ */ new Map();
1946
2073
  const config = driver.config;
@@ -1960,7 +2087,7 @@ async function safeBuild(options, overrides) {
1960
2087
  await plugin.buildStart.call(context);
1961
2088
  if (plugin.schema || plugin.operation || plugin.operations) await runPluginAstHooks(plugin, context);
1962
2089
  if (output) {
1963
- const barrelFiles = await getBarrelFiles(fabric.files, {
2090
+ const barrelFiles = await getBarrelFiles(driver.fileManager.files, {
1964
2091
  type: output.barrelType ?? "named",
1965
2092
  root,
1966
2093
  output,
@@ -2014,15 +2141,15 @@ async function safeBuild(options, overrides) {
2014
2141
  ` • Path: ${rootPath}`
2015
2142
  ]
2016
2143
  });
2017
- const barrelFiles = fabric.files.filter((file) => {
2144
+ const barrelFiles = driver.fileManager.files.filter((file) => {
2018
2145
  return file.sources.some((source) => source.isIndexable);
2019
2146
  });
2020
2147
  await events.emit("debug", {
2021
2148
  date: /* @__PURE__ */ new Date(),
2022
2149
  logs: [`Found ${barrelFiles.length} indexable files for barrel export`]
2023
2150
  });
2024
- const existingBarrel = fabric.files.find((f) => f.path === rootPath);
2025
- const rootFile = {
2151
+ const existingBarrel = driver.fileManager.files.find((f) => f.path === rootPath);
2152
+ const rootFile = (0, _kubb_ast.createFile)({
2026
2153
  path: rootPath,
2027
2154
  baseName: BARREL_FILENAME,
2028
2155
  exports: buildBarrelExports({
@@ -2031,26 +2158,59 @@ async function safeBuild(options, overrides) {
2031
2158
  existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
2032
2159
  config,
2033
2160
  driver
2034
- }),
2161
+ }).map((e) => (0, _kubb_ast.createExport)(e)),
2035
2162
  sources: [],
2036
2163
  imports: [],
2037
2164
  meta: {}
2038
- };
2039
- await fabric.upsertFile(rootFile);
2165
+ });
2166
+ driver.fileManager.upsert(rootFile);
2040
2167
  await events.emit("debug", {
2041
2168
  date: /* @__PURE__ */ new Date(),
2042
2169
  logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`]
2043
2170
  });
2044
2171
  }
2045
- const files = [...fabric.files];
2046
- await fabric.write({ extension: config.output.extension });
2172
+ const files = driver.fileManager.files;
2173
+ const parsersMap = /* @__PURE__ */ new Map();
2174
+ for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
2175
+ const fileProcessor = new FileProcessor();
2176
+ await events.emit("debug", {
2177
+ date: /* @__PURE__ */ new Date(),
2178
+ logs: [`Writing ${files.length} files...`]
2179
+ });
2180
+ await fileProcessor.run(files, {
2181
+ parsers: parsersMap,
2182
+ extension: config.output.extension,
2183
+ onStart: async (processingFiles) => {
2184
+ await events.emit("files:processing:start", processingFiles);
2185
+ },
2186
+ onUpdate: async ({ file, source, processed, total, percentage }) => {
2187
+ await events.emit("file:processing:update", {
2188
+ file,
2189
+ source,
2190
+ processed,
2191
+ total,
2192
+ percentage,
2193
+ config
2194
+ });
2195
+ if (source) {
2196
+ await storage?.setItem(file.path, source);
2197
+ sources.set(file.path, source);
2198
+ }
2199
+ },
2200
+ onEnd: async (processedFiles) => {
2201
+ await events.emit("files:processing:end", processedFiles);
2202
+ await events.emit("debug", {
2203
+ date: /* @__PURE__ */ new Date(),
2204
+ logs: [`✓ File write process completed for ${processedFiles.length} files`]
2205
+ });
2206
+ }
2207
+ });
2047
2208
  for (const plugin of driver.plugins.values()) if (plugin.buildEnd) {
2048
2209
  const context = driver.getContext(plugin);
2049
2210
  await plugin.buildEnd.call(context);
2050
2211
  }
2051
2212
  return {
2052
2213
  failedPlugins,
2053
- fabric,
2054
2214
  files,
2055
2215
  driver,
2056
2216
  pluginTimings,
@@ -2059,7 +2219,6 @@ async function safeBuild(options, overrides) {
2059
2219
  } catch (error) {
2060
2220
  return {
2061
2221
  failedPlugins,
2062
- fabric,
2063
2222
  files: [],
2064
2223
  driver,
2065
2224
  pluginTimings,
@@ -2080,11 +2239,11 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, dri
2080
2239
  if (!pluginOptions || pluginOptions.output?.barrelType === false) return [];
2081
2240
  const exportName = config.output.barrelType === "all" ? void 0 : source.name ? [source.name] : void 0;
2082
2241
  if (exportName?.some((n) => existingExports.has(n))) return [];
2083
- return [{
2242
+ return [(0, _kubb_ast.createExport)({
2084
2243
  name: exportName,
2085
2244
  path: getRelativePath(rootDir, file.path),
2086
2245
  isTypeOnly: config.output.barrelType === "all" ? containsOnlyTypes : source.isTypeOnly
2087
- }];
2246
+ })];
2088
2247
  });
2089
2248
  });
2090
2249
  }
@@ -2154,6 +2313,11 @@ function createPlugin(build) {
2154
2313
  return (options) => build(options ?? {});
2155
2314
  }
2156
2315
  //#endregion
2316
+ //#region src/defineConfig.ts
2317
+ function defineConfig(config) {
2318
+ return config;
2319
+ }
2320
+ //#endregion
2157
2321
  //#region src/defineGenerator.ts
2158
2322
  /**
2159
2323
  * Defines a generator. Returns the object as-is with correct `this` typings.
@@ -2191,19 +2355,19 @@ function mergeGenerators(generators) {
2191
2355
  async schema(node, options) {
2192
2356
  for (const gen of generators) {
2193
2357
  if (!gen.schema) continue;
2194
- await applyHookResult(await gen.schema.call(this, node, options), this.fabric);
2358
+ await applyHookResult(await gen.schema.call(this, node, options), this.driver);
2195
2359
  }
2196
2360
  },
2197
2361
  async operation(node, options) {
2198
2362
  for (const gen of generators) {
2199
2363
  if (!gen.operation) continue;
2200
- await applyHookResult(await gen.operation.call(this, node, options), this.fabric);
2364
+ await applyHookResult(await gen.operation.call(this, node, options), this.driver);
2201
2365
  }
2202
2366
  },
2203
2367
  async operations(nodes, options) {
2204
2368
  for (const gen of generators) {
2205
2369
  if (!gen.operations) continue;
2206
- await applyHookResult(await gen.operations.call(this, nodes, options), this.fabric);
2370
+ await applyHookResult(await gen.operations.call(this, nodes, options), this.driver);
2207
2371
  }
2208
2372
  }
2209
2373
  };
@@ -2226,6 +2390,34 @@ function defineLogger(logger) {
2226
2390
  return logger;
2227
2391
  }
2228
2392
  //#endregion
2393
+ //#region src/defineParser.ts
2394
+ /**
2395
+ * Defines a parser with type safety.
2396
+ *
2397
+ * Use this function to create parsers that transform generated files to strings
2398
+ * based on their extension.
2399
+ *
2400
+ * @example
2401
+ * ```ts
2402
+ * import { defineParser } from '@kubb/core'
2403
+ *
2404
+ * export const jsonParser = defineParser({
2405
+ * name: 'json',
2406
+ * extNames: ['.json'],
2407
+ * parse(file) {
2408
+ * return file.sources.map((s) => s.value).join('\n')
2409
+ * },
2410
+ * })
2411
+ * ```
2412
+ */
2413
+ function defineParser(parser) {
2414
+ return {
2415
+ install() {},
2416
+ type: "parser",
2417
+ ...parser
2418
+ };
2419
+ }
2420
+ //#endregion
2229
2421
  //#region src/definePresets.ts
2230
2422
  /**
2231
2423
  * Creates a typed presets registry object — a named collection of {@link Preset} entries.
@@ -2379,7 +2571,7 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
2379
2571
  /**
2380
2572
  * Default file resolver used by `defineResolver`.
2381
2573
  *
2382
- * Resolves a `FabricFile.File` by combining name resolution (`resolver.default`) with
2574
+ * Resolves a `FileNode` by combining name resolution (`resolver.default`) with
2383
2575
  * path resolution (`resolver.resolvePath`). The resolved file always has empty
2384
2576
  * `sources`, `imports`, and `exports` arrays — consumers populate those separately.
2385
2577
  *
@@ -2412,14 +2604,14 @@ function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
2412
2604
  tag,
2413
2605
  path: groupPath
2414
2606
  }, context);
2415
- return {
2607
+ return (0, _kubb_ast.createFile)({
2416
2608
  path: filePath,
2417
2609
  baseName: node_path.default.basename(filePath),
2418
2610
  meta: { pluginName: this.pluginName },
2419
2611
  sources: [],
2420
2612
  imports: [],
2421
2613
  exports: []
2422
- };
2614
+ });
2423
2615
  }
2424
2616
  /**
2425
2617
  * Generates the default "Generated by Kubb" banner from config and optional node metadata.
@@ -2471,13 +2663,13 @@ function buildDefaultBanner({ title, description, version, config }) {
2471
2663
  *
2472
2664
  * @example Function banner with node
2473
2665
  * ```ts
2474
- * defaultResolveBanner(rootNode, { output: { banner: (node) => `// v${node.version}` }, config })
2666
+ * defaultResolveBanner(inputNode, { output: { banner: (node) => `// v${node.version}` }, config })
2475
2667
  * // → '// v3.0.0'
2476
2668
  * ```
2477
2669
  *
2478
2670
  * @example No user banner — Kubb notice with OAS metadata
2479
2671
  * ```ts
2480
- * defaultResolveBanner(rootNode, { config })
2672
+ * defaultResolveBanner(inputNode, { config })
2481
2673
  * // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
2482
2674
  * ```
2483
2675
  *
@@ -2513,7 +2705,7 @@ function defaultResolveBanner(node, { output, config }) {
2513
2705
  *
2514
2706
  * @example Function footer with node
2515
2707
  * ```ts
2516
- * defaultResolveFooter(rootNode, { output: { footer: (node) => `// ${node.title}` }, config })
2708
+ * defaultResolveFooter(inputNode, { output: { footer: (node) => `// ${node.title}` }, config })
2517
2709
  * // → '// Pet Store'
2518
2710
  * ```
2519
2711
  */
@@ -2529,7 +2721,7 @@ function defaultResolveFooter(node, { output }) {
2529
2721
  * - `default` — name casing strategy (camelCase / PascalCase)
2530
2722
  * - `resolveOptions` — include/exclude/override filtering
2531
2723
  * - `resolvePath` — output path computation
2532
- * - `resolveFile` — full `FabricFile.File` construction
2724
+ * - `resolveFile` — full `FileNode` construction
2533
2725
  *
2534
2726
  * Methods in the builder have access to `this` (the full resolver object), so they
2535
2727
  * can call other resolver methods without circular imports.
@@ -2905,6 +3097,7 @@ exports.defaultResolvePath = defaultResolvePath;
2905
3097
  exports.defineConfig = defineConfig;
2906
3098
  exports.defineGenerator = defineGenerator;
2907
3099
  exports.defineLogger = defineLogger;
3100
+ exports.defineParser = defineParser;
2908
3101
  exports.definePresets = definePresets;
2909
3102
  Object.defineProperty(exports, "definePrinter", {
2910
3103
  enumerable: true,