@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.js CHANGED
@@ -1,15 +1,13 @@
1
- import "./chunk--u3MIqq1.js";
1
+ import { t as __name } from "./chunk--u3MIqq1.js";
2
2
  import { EventEmitter } from "node:events";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
- import path, { basename, dirname, extname, join, posix, relative, resolve } from "node:path";
6
- import { composeTransformers, composeTransformers as composeTransformers$1, definePrinter, isOperationNode, isSchemaNode, transform, walk } from "@kubb/ast";
7
- import { Fabric, createFabric, createReactFabric } from "@kubb/react-fabric";
8
- import { typescriptParser } from "@kubb/react-fabric/parsers";
9
- import { fsPlugin } from "@kubb/react-fabric/plugins";
5
+ import path, { basename, dirname, extname, join, posix, resolve } from "node:path";
6
+ import { composeTransformers, composeTransformers as composeTransformers$1, createExport, createFile, createSource, definePrinter, isOperationNode, isSchemaNode, transform, walk } from "@kubb/ast";
10
7
  import { performance } from "node:perf_hooks";
11
8
  import { deflateSync } from "fflate";
12
9
  import { x } from "tinyexec";
10
+ import { Fabric, createReactFabric } from "@kubb/react-fabric";
13
11
  import { jsx } from "@kubb/react-fabric/jsx-runtime";
14
12
  import { version } from "node:process";
15
13
  import { sortBy } from "remeda";
@@ -334,6 +332,24 @@ async function clean(path) {
334
332
  });
335
333
  }
336
334
  //#endregion
335
+ //#region ../../internals/utils/src/string.ts
336
+ /**
337
+ * Strips the file extension from a path or file name.
338
+ * Only removes the last `.ext` segment when the dot is not part of a directory name.
339
+ *
340
+ * @example
341
+ * trimExtName('petStore.ts') // 'petStore'
342
+ * trimExtName('/src/models/pet.ts') // '/src/models/pet'
343
+ * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
344
+ * trimExtName('noExtension') // 'noExtension'
345
+ */
346
+ function trimExtName$1(text) {
347
+ const dotIndex = text.lastIndexOf(".");
348
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
349
+ return text;
350
+ }
351
+ __name(trimExtName$1, "trimExtName");
352
+ //#endregion
337
353
  //#region ../../internals/utils/src/promise.ts
338
354
  /** Returns `true` when `result` is a rejected `Promise.allSettled` result with a typed `reason`.
339
355
  *
@@ -615,17 +631,6 @@ var URLPath = class {
615
631
  }
616
632
  };
617
633
  //#endregion
618
- //#region src/config.ts
619
- function defineConfig(config) {
620
- return config;
621
- }
622
- /**
623
- * Type guard to check if a given config has an `input.path`.
624
- */
625
- function isInputPath(config) {
626
- return typeof config?.input === "object" && config.input !== null && "path" in config.input;
627
- }
628
- //#endregion
629
634
  //#region src/constants.ts
630
635
  /**
631
636
  * Base URL for the Kubb Studio web app.
@@ -717,50 +722,6 @@ const formatters = {
717
722
  }
718
723
  };
719
724
  //#endregion
720
- //#region src/devtools.ts
721
- /**
722
- * Encodes a `RootNode` as a compressed, URL-safe string.
723
- *
724
- * The JSON representation is deflate-compressed with {@link deflateSync} before
725
- * base64url encoding, which typically reduces payload size by 70–80 % and
726
- * keeps URLs well within browser and server path-length limits.
727
- *
728
- * Use {@link decodeAst} to reverse.
729
- */
730
- function encodeAst(root) {
731
- const compressed = deflateSync(new TextEncoder().encode(JSON.stringify(root)));
732
- return Buffer.from(compressed).toString("base64url");
733
- }
734
- /**
735
- * Constructs the Kubb Studio URL for the given `RootNode`.
736
- * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
737
- * The `root` is encoded and attached as the `?root=` query parameter so Studio
738
- * can decode and render it without a round-trip to any server.
739
- */
740
- function getStudioUrl(root, studioUrl, options = {}) {
741
- return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(root)}`;
742
- }
743
- /**
744
- * Opens the Kubb Studio URL for the given `RootNode` in the default browser —
745
- *
746
- * Falls back to printing the URL if the browser cannot be launched.
747
- */
748
- async function openInStudio(root, studioUrl, options = {}) {
749
- const url = getStudioUrl(root, studioUrl, options);
750
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
751
- const args = process.platform === "win32" ? [
752
- "/c",
753
- "start",
754
- "",
755
- url
756
- ] : [url];
757
- try {
758
- await x(cmd, args);
759
- } catch {
760
- console.log(`\n ${url}\n`);
761
- }
762
- }
763
- //#endregion
764
725
  //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
765
726
  var Node = class {
766
727
  value;
@@ -888,6 +849,194 @@ function validateConcurrency(concurrency) {
888
849
  if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
889
850
  }
890
851
  //#endregion
852
+ //#region src/FileProcessor.ts
853
+ function joinSources(file) {
854
+ return file.sources.map((item) => item.value).filter((value) => value != null).join("\n\n");
855
+ }
856
+ /**
857
+ * Converts a single file to a string using the registered parsers.
858
+ * Falls back to joining source values when no matching parser is found.
859
+ */
860
+ var FileProcessor = class {
861
+ #limit = pLimit(100);
862
+ async parse(file, { parsers, extension } = {}) {
863
+ const parseExtName = extension?.[file.extname] || void 0;
864
+ if (!parsers || !file.extname) return joinSources(file);
865
+ const parser = parsers.get(file.extname);
866
+ if (!parser) return joinSources(file);
867
+ return parser.parse(file, { extname: parseExtName });
868
+ }
869
+ async run(files, { parsers, mode = "sequential", extension, onStart, onEnd, onUpdate } = {}) {
870
+ await onStart?.(files);
871
+ const total = files.length;
872
+ let processed = 0;
873
+ const processOne = async (file) => {
874
+ const source = await this.parse(file, {
875
+ extension,
876
+ parsers
877
+ });
878
+ const currentProcessed = ++processed;
879
+ const percentage = currentProcessed / total * 100;
880
+ await onUpdate?.({
881
+ file,
882
+ source,
883
+ processed: currentProcessed,
884
+ percentage,
885
+ total
886
+ });
887
+ };
888
+ if (mode === "sequential") for (const file of files) await processOne(file);
889
+ else await Promise.all(files.map((file) => this.#limit(() => processOne(file))));
890
+ await onEnd?.(files);
891
+ return files;
892
+ }
893
+ };
894
+ //#endregion
895
+ //#region src/devtools.ts
896
+ /**
897
+ * Encodes an `InputNode` as a compressed, URL-safe string.
898
+ *
899
+ * The JSON representation is deflate-compressed with {@link deflateSync} before
900
+ * base64url encoding, which typically reduces payload size by 70–80 % and
901
+ * keeps URLs well within browser and server path-length limits.
902
+ *
903
+ * Use {@link decodeAst} to reverse.
904
+ */
905
+ function encodeAst(input) {
906
+ const compressed = deflateSync(new TextEncoder().encode(JSON.stringify(input)));
907
+ return Buffer.from(compressed).toString("base64url");
908
+ }
909
+ /**
910
+ * Constructs the Kubb Studio URL for the given `InputNode`.
911
+ * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
912
+ * The `input` is encoded and attached as the `?root=` query parameter so Studio
913
+ * can decode and render it without a round-trip to any server.
914
+ */
915
+ function getStudioUrl(input, studioUrl, options = {}) {
916
+ return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(input)}`;
917
+ }
918
+ /**
919
+ * Opens the Kubb Studio URL for the given `InputNode` in the default browser —
920
+ *
921
+ * Falls back to printing the URL if the browser cannot be launched.
922
+ */
923
+ async function openInStudio(input, studioUrl, options = {}) {
924
+ const url = getStudioUrl(input, studioUrl, options);
925
+ const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
926
+ const args = process.platform === "win32" ? [
927
+ "/c",
928
+ "start",
929
+ "",
930
+ url
931
+ ] : [url];
932
+ try {
933
+ await x(cmd, args);
934
+ } catch {
935
+ console.log(`\n ${url}\n`);
936
+ }
937
+ }
938
+ //#endregion
939
+ //#region src/FileManager.ts
940
+ function mergeFile(a, b) {
941
+ return {
942
+ ...a,
943
+ sources: [...a.sources || [], ...b.sources || []],
944
+ imports: [...a.imports || [], ...b.imports || []],
945
+ exports: [...a.exports || [], ...b.exports || []]
946
+ };
947
+ }
948
+ /**
949
+ * In-memory file store for generated files.
950
+ *
951
+ * Files with the same `path` are merged — sources, imports, and exports are concatenated.
952
+ * The `files` getter returns all stored files sorted by path length (shortest first).
953
+ *
954
+ * @example
955
+ * ```ts
956
+ * import { FileManager } from '@kubb/core'
957
+ *
958
+ * const manager = new FileManager()
959
+ * manager.upsert(myFile)
960
+ * console.log(manager.files) // all stored files
961
+ * ```
962
+ */
963
+ var FileManager = class {
964
+ #cache = /* @__PURE__ */ new Map();
965
+ #filesCache = null;
966
+ /**
967
+ * Adds one or more files. Files with the same path are merged — sources, imports,
968
+ * and exports from all calls with the same path are concatenated together.
969
+ */
970
+ add(...files) {
971
+ const resolvedFiles = [];
972
+ const mergedFiles = /* @__PURE__ */ new Map();
973
+ files.forEach((file) => {
974
+ const existing = mergedFiles.get(file.path);
975
+ if (existing) mergedFiles.set(file.path, mergeFile(existing, file));
976
+ else mergedFiles.set(file.path, file);
977
+ });
978
+ for (const file of mergedFiles.values()) {
979
+ const resolvedFile = createFile(file);
980
+ this.#cache.set(resolvedFile.path, resolvedFile);
981
+ this.#filesCache = null;
982
+ resolvedFiles.push(resolvedFile);
983
+ }
984
+ return resolvedFiles;
985
+ }
986
+ /**
987
+ * Adds or merges one or more files.
988
+ * If a file with the same path already exists, its sources/imports/exports are merged together.
989
+ */
990
+ upsert(...files) {
991
+ const resolvedFiles = [];
992
+ const mergedFiles = /* @__PURE__ */ new Map();
993
+ files.forEach((file) => {
994
+ const existing = mergedFiles.get(file.path);
995
+ if (existing) mergedFiles.set(file.path, mergeFile(existing, file));
996
+ else mergedFiles.set(file.path, file);
997
+ });
998
+ for (const file of mergedFiles.values()) {
999
+ const existing = this.#cache.get(file.path);
1000
+ const resolvedFile = createFile(existing ? mergeFile(existing, file) : file);
1001
+ this.#cache.set(resolvedFile.path, resolvedFile);
1002
+ this.#filesCache = null;
1003
+ resolvedFiles.push(resolvedFile);
1004
+ }
1005
+ return resolvedFiles;
1006
+ }
1007
+ getByPath(path) {
1008
+ return this.#cache.get(path) ?? null;
1009
+ }
1010
+ deleteByPath(path) {
1011
+ this.#cache.delete(path);
1012
+ this.#filesCache = null;
1013
+ }
1014
+ clear() {
1015
+ this.#cache.clear();
1016
+ this.#filesCache = null;
1017
+ }
1018
+ /**
1019
+ * All stored files, sorted by path length (shorter paths first).
1020
+ * Barrel/index files (e.g. index.ts) are sorted last within each length bucket.
1021
+ */
1022
+ get files() {
1023
+ if (this.#filesCache) return this.#filesCache;
1024
+ const keys = [...this.#cache.keys()].sort((a, b) => {
1025
+ if (a.length !== b.length) return a.length - b.length;
1026
+ const aIsIndex = trimExtName$1(a).endsWith("index");
1027
+ if (aIsIndex !== trimExtName$1(b).endsWith("index")) return aIsIndex ? 1 : -1;
1028
+ return 0;
1029
+ });
1030
+ const files = [];
1031
+ for (const key of keys) {
1032
+ const file = this.#cache.get(key);
1033
+ if (file) files.push(file);
1034
+ }
1035
+ this.#filesCache = files;
1036
+ return files;
1037
+ }
1038
+ };
1039
+ //#endregion
891
1040
  //#region src/utils/executeStrategies.ts
892
1041
  /**
893
1042
  * Runs promise functions in sequence, threading each result into the next call.
@@ -954,12 +1103,18 @@ var PluginDriver = class {
954
1103
  config;
955
1104
  options;
956
1105
  /**
957
- * The universal `@kubb/ast` `RootNode` produced by the adapter, set by
1106
+ * The universal `@kubb/ast` `InputNode` produced by the adapter, set by
958
1107
  * the build pipeline after the adapter's `parse()` resolves.
959
1108
  */
960
- rootNode = void 0;
1109
+ inputNode = void 0;
961
1110
  adapter = void 0;
962
1111
  #studioIsOpen = false;
1112
+ /**
1113
+ * Central file store for all generated files.
1114
+ * Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
1115
+ * add files; this property gives direct read/write access when needed.
1116
+ */
1117
+ fileManager = new FileManager();
963
1118
  plugins = /* @__PURE__ */ new Map();
964
1119
  constructor(config, options) {
965
1120
  this.config = config;
@@ -984,7 +1139,6 @@ var PluginDriver = class {
984
1139
  getContext(plugin) {
985
1140
  const driver = this;
986
1141
  const baseContext = {
987
- fabric: driver.options.fabric,
988
1142
  config: driver.config,
989
1143
  get root() {
990
1144
  return resolve(driver.config.root, driver.config.output.path);
@@ -998,13 +1152,13 @@ var PluginDriver = class {
998
1152
  requirePlugin: driver.requirePlugin.bind(driver),
999
1153
  driver,
1000
1154
  addFile: async (...files) => {
1001
- await this.options.fabric.addFile(...files);
1155
+ driver.fileManager.add(...files);
1002
1156
  },
1003
1157
  upsertFile: async (...files) => {
1004
- await this.options.fabric.upsertFile(...files);
1158
+ driver.fileManager.upsert(...files);
1005
1159
  },
1006
- get rootNode() {
1007
- return driver.rootNode;
1160
+ get inputNode() {
1161
+ return driver.inputNode;
1008
1162
  },
1009
1163
  get adapter() {
1010
1164
  return driver.adapter;
@@ -1027,10 +1181,10 @@ var PluginDriver = class {
1027
1181
  openInStudio(options) {
1028
1182
  if (!driver.config.devtools || driver.#studioIsOpen) return;
1029
1183
  if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
1030
- if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1184
+ if (!driver.inputNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1031
1185
  driver.#studioIsOpen = true;
1032
1186
  const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1033
- return openInStudio(driver.rootNode, studioUrl, options);
1187
+ return openInStudio(driver.inputNode, studioUrl, options);
1034
1188
  }
1035
1189
  };
1036
1190
  const mergedExtras = {};
@@ -1059,14 +1213,14 @@ var PluginDriver = class {
1059
1213
  options
1060
1214
  });
1061
1215
  if (!path) throw new Error(`Filepath should be defined for resolvedName "${resolvedName}" and pluginName "${pluginName}"`);
1062
- return {
1216
+ return createFile({
1063
1217
  path,
1064
1218
  baseName: basename(path),
1065
1219
  meta: { pluginName },
1066
1220
  sources: [],
1067
1221
  imports: [],
1068
1222
  exports: []
1069
- };
1223
+ });
1070
1224
  }
1071
1225
  /**
1072
1226
  * @deprecated use resolvers context instead
@@ -1350,19 +1504,19 @@ var PluginDriver = class {
1350
1504
  /**
1351
1505
  * Handles the return value of a plugin AST hook or generator method.
1352
1506
  *
1353
- * - React element → rendered via an isolated react-fabric context, files merged into `fabric`
1354
- * - `Array<FabricFile.File>` → upserted directly into `fabric`
1507
+ * - React element → rendered via an isolated react-fabric context, files stored in `driver.fileManager`
1508
+ * - `Array<FileNode>` → upserted directly into `driver.fileManager`
1355
1509
  * - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
1356
1510
  */
1357
- async function applyHookResult(result, fabric) {
1511
+ async function applyHookResult(result, driver) {
1358
1512
  if (!result) return;
1359
1513
  if (Array.isArray(result)) {
1360
- await fabric.upsertFile(...result);
1514
+ driver.fileManager.upsert(...result);
1361
1515
  return;
1362
1516
  }
1363
1517
  const fabricChild = createReactFabric();
1364
1518
  await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { children: result }));
1365
- fabric.context.fileManager.upsert(...fabricChild.files);
1519
+ driver.fileManager.upsert(...fabricChild.files);
1366
1520
  fabricChild.unmount();
1367
1521
  }
1368
1522
  //#endregion
@@ -1463,7 +1617,7 @@ const fsStorage = createStorage(() => ({
1463
1617
  }));
1464
1618
  //#endregion
1465
1619
  //#region package.json
1466
- var version$1 = "5.0.0-alpha.30";
1620
+ var version$1 = "5.0.0-alpha.32";
1467
1621
  //#endregion
1468
1622
  //#region src/utils/diagnostics.ts
1469
1623
  /**
@@ -1485,7 +1639,7 @@ function getDiagnosticInfo() {
1485
1639
  //#region src/utils/TreeNode.ts
1486
1640
  /**
1487
1641
  * Tree structure used to build per-directory barrel (`index.ts`) files from a
1488
- * flat list of generated {@link FabricFile.File} entries.
1642
+ * flat list of generated {@link FileNode} entries.
1489
1643
  *
1490
1644
  * Each node represents either a directory or a file within the output tree.
1491
1645
  * Use {@link TreeNode.build} to construct a root node from a file list, then
@@ -1646,36 +1800,36 @@ function getBarrelFilesByRoot(root, files) {
1646
1800
  const cachedFiles = /* @__PURE__ */ new Map();
1647
1801
  TreeNode.build(files, root)?.forEach((treeNode) => {
1648
1802
  if (!treeNode?.children || !treeNode.parent?.data.path) return;
1649
- const barrelFile = {
1803
+ const barrelFile = createFile({
1650
1804
  path: join(treeNode.parent?.data.path, "index.ts"),
1651
1805
  baseName: "index.ts",
1652
1806
  exports: [],
1653
1807
  imports: [],
1654
1808
  sources: []
1655
- };
1809
+ });
1656
1810
  const previousBarrelFile = cachedFiles.get(barrelFile.path);
1657
1811
  treeNode.leaves.forEach((item) => {
1658
1812
  if (!item.data.name) return;
1659
1813
  (item.data.file?.sources || []).forEach((source) => {
1660
1814
  if (!item.data.file?.path || !source.isIndexable || !source.name) return;
1661
1815
  if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
1662
- barrelFile.exports.push({
1816
+ barrelFile.exports.push(createExport({
1663
1817
  name: [source.name],
1664
1818
  path: getRelativePath(treeNode.parent?.data.path, item.data.path),
1665
1819
  isTypeOnly: source.isTypeOnly
1666
- });
1667
- barrelFile.sources.push({
1820
+ }));
1821
+ barrelFile.sources.push(createSource({
1668
1822
  name: source.name,
1669
1823
  isTypeOnly: source.isTypeOnly,
1670
1824
  value: "",
1671
1825
  isExportable: false,
1672
1826
  isIndexable: false
1673
- });
1827
+ }));
1674
1828
  });
1675
1829
  });
1676
1830
  if (previousBarrelFile) {
1677
1831
  previousBarrelFile.sources.push(...barrelFile.sources);
1678
- previousBarrelFile.exports?.push(...barrelFile.exports || []);
1832
+ previousBarrelFile.exports.push(...barrelFile.exports);
1679
1833
  } else cachedFiles.set(barrelFile.path, barrelFile);
1680
1834
  });
1681
1835
  return [...cachedFiles.values()];
@@ -1701,7 +1855,7 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1701
1855
  if (type === "all") return barrelFiles.map((file) => {
1702
1856
  return {
1703
1857
  ...file,
1704
- exports: file.exports?.map((exportItem) => {
1858
+ exports: file.exports.map((exportItem) => {
1705
1859
  return {
1706
1860
  ...exportItem,
1707
1861
  name: void 0
@@ -1717,6 +1871,14 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1717
1871
  });
1718
1872
  }
1719
1873
  //#endregion
1874
+ //#region src/utils/isInputPath.ts
1875
+ /**
1876
+ * Type guard to check if a given config has an `input.path`.
1877
+ */
1878
+ function isInputPath(config) {
1879
+ return typeof config?.input === "object" && config.input !== null && "path" in config.input;
1880
+ }
1881
+ //#endregion
1720
1882
  //#region src/build.ts
1721
1883
  /**
1722
1884
  * Initializes all Kubb infrastructure for a build without executing any plugins.
@@ -1724,7 +1886,8 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1724
1886
  * - Validates the input path (when applicable).
1725
1887
  * - Applies config defaults (`root`, `output.*`, `devtools`).
1726
1888
  * - Creates the Fabric instance and wires storage, format, and lint hooks.
1727
- * - Runs the adapter (if configured) to produce the universal `RootNode`.
1889
+ * - Runs the adapter (if configured) to produce the universal `InputNode`.
1890
+ * When no adapter is supplied and `@kubb/adapter-oas` is installed as an
1728
1891
  *
1729
1892
  * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
1730
1893
  * via the `overrides` argument to reuse the same infrastructure across multiple runs.
@@ -1764,9 +1927,12 @@ async function setup(options) {
1764
1927
  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 });
1765
1928
  }
1766
1929
  }
1767
- const definedConfig = {
1930
+ if (!userConfig.adapter) throw new Error("Adapter should be defined");
1931
+ const config = {
1768
1932
  root: userConfig.root || process.cwd(),
1769
1933
  ...userConfig,
1934
+ parsers: userConfig.parsers ?? [],
1935
+ adapter: userConfig.adapter,
1770
1936
  output: {
1771
1937
  write: true,
1772
1938
  barrelType: "named",
@@ -1780,79 +1946,41 @@ async function setup(options) {
1780
1946
  } : void 0,
1781
1947
  plugins: userConfig.plugins
1782
1948
  };
1783
- const storage = definedConfig.output.write === false ? null : definedConfig.output.storage ?? fsStorage();
1784
- if (definedConfig.output.clean) {
1949
+ const storage = config.output.write === false ? null : config.output.storage ?? fsStorage();
1950
+ if (config.output.clean) {
1785
1951
  await events.emit("debug", {
1786
1952
  date: /* @__PURE__ */ new Date(),
1787
- logs: ["Cleaning output directories", ` • Output: ${definedConfig.output.path}`]
1788
- });
1789
- await storage?.clear(resolve(definedConfig.root, definedConfig.output.path));
1790
- }
1791
- const fabric = createFabric();
1792
- fabric.use(fsPlugin);
1793
- fabric.use(typescriptParser);
1794
- fabric.context.on("files:processing:start", (files) => {
1795
- events.emit("files:processing:start", files);
1796
- events.emit("debug", {
1797
- date: /* @__PURE__ */ new Date(),
1798
- logs: [`Writing ${files.length} files...`]
1799
- });
1800
- });
1801
- fabric.context.on("file:processing:update", async (params) => {
1802
- const { file, source } = params;
1803
- await events.emit("file:processing:update", {
1804
- ...params,
1805
- config: definedConfig,
1806
- source
1953
+ logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
1807
1954
  });
1808
- if (source) {
1809
- const key = relative(resolve(definedConfig.root), file.path);
1810
- await storage?.setItem(key, source);
1811
- sources.set(file.path, source);
1812
- }
1955
+ await storage?.clear(resolve(config.root, config.output.path));
1956
+ }
1957
+ const driver = new PluginDriver(config, {
1958
+ events,
1959
+ concurrency: 15
1813
1960
  });
1814
- fabric.context.on("files:processing:end", async (files) => {
1815
- await events.emit("files:processing:end", files);
1816
- await events.emit("debug", {
1817
- date: /* @__PURE__ */ new Date(),
1818
- logs: [`✓ File write process completed for ${files.length} files`]
1819
- });
1961
+ const adapter = config.adapter;
1962
+ if (!adapter) throw new Error("No adapter configured. Please provide an adapter in your kubb.config.ts.");
1963
+ const source = inputToAdapterSource(config);
1964
+ await events.emit("debug", {
1965
+ date: /* @__PURE__ */ new Date(),
1966
+ logs: [`Running adapter: ${adapter.name}`]
1820
1967
  });
1968
+ driver.adapter = adapter;
1969
+ driver.inputNode = await adapter.parse(source);
1821
1970
  await events.emit("debug", {
1822
1971
  date: /* @__PURE__ */ new Date(),
1823
1972
  logs: [
1824
- "✓ Fabric initialized",
1825
- ` • Storage: ${storage ? storage.name : "disabled (dry-run)"}`,
1826
- ` • Barrel type: ${definedConfig.output.barrelType || "none"}`
1973
+ `✓ Adapter '${adapter.name}' resolved InputNode`,
1974
+ ` • Schemas: ${driver.inputNode.schemas.length}`,
1975
+ ` • Operations: ${driver.inputNode.operations.length}`
1827
1976
  ]
1828
1977
  });
1829
- const pluginDriver = new PluginDriver(definedConfig, {
1830
- fabric,
1831
- events,
1832
- concurrency: 15
1833
- });
1834
- if (definedConfig.adapter) {
1835
- const source = inputToAdapterSource(definedConfig);
1836
- await events.emit("debug", {
1837
- date: /* @__PURE__ */ new Date(),
1838
- logs: [`Running adapter: ${definedConfig.adapter.name}`]
1839
- });
1840
- pluginDriver.adapter = definedConfig.adapter;
1841
- pluginDriver.rootNode = await definedConfig.adapter.parse(source);
1842
- await events.emit("debug", {
1843
- date: /* @__PURE__ */ new Date(),
1844
- logs: [
1845
- `✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
1846
- ` • Schemas: ${pluginDriver.rootNode.schemas.length}`,
1847
- ` • Operations: ${pluginDriver.rootNode.operations.length}`
1848
- ]
1849
- });
1850
- }
1851
1978
  return {
1979
+ config,
1852
1980
  events,
1853
- fabric,
1854
- driver: pluginDriver,
1855
- sources
1981
+ driver,
1982
+ sources,
1983
+ storage
1856
1984
  };
1857
1985
  }
1858
1986
  /**
@@ -1862,7 +1990,7 @@ async function setup(options) {
1862
1990
  * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
1863
1991
  */
1864
1992
  async function build(options, overrides) {
1865
- const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1993
+ const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1866
1994
  if (error) throw error;
1867
1995
  if (failedPlugins.size > 0) {
1868
1996
  const errors = [...failedPlugins].map(({ error }) => error);
@@ -1870,7 +1998,6 @@ async function build(options, overrides) {
1870
1998
  }
1871
1999
  return {
1872
2000
  failedPlugins,
1873
- fabric,
1874
2001
  files,
1875
2002
  driver,
1876
2003
  pluginTimings,
@@ -1885,15 +2012,15 @@ async function build(options, overrides) {
1885
2012
  * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
1886
2013
  * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
1887
2014
  * - Return values are handled via `applyHookResult`: React elements are rendered,
1888
- * `FabricFile.File[]` are written via upsert, and `void` is a no-op (manual handling).
2015
+ * `FileNode[]` are written via upsert, and `void` is a no-op (manual handling).
1889
2016
  * - Barrel files are generated automatically when `output.barrelType` is set.
1890
2017
  */
1891
2018
  async function runPluginAstHooks(plugin, context) {
1892
- const { adapter, rootNode, resolver, fabric } = context;
2019
+ const { adapter, inputNode, resolver, driver } = context;
1893
2020
  const { exclude, include, override } = plugin.options;
1894
- 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.`);
2021
+ 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.`);
1895
2022
  const collectedOperations = [];
1896
- await walk(rootNode, {
2023
+ await walk(inputNode, {
1897
2024
  depth: "shallow",
1898
2025
  async schema(node) {
1899
2026
  if (!plugin.schema) return;
@@ -1905,7 +2032,7 @@ async function runPluginAstHooks(plugin, context) {
1905
2032
  override
1906
2033
  });
1907
2034
  if (options === null) return;
1908
- await applyHookResult(await plugin.schema.call(context, transformedNode, options), fabric);
2035
+ await applyHookResult(await plugin.schema.call(context, transformedNode, options), driver);
1909
2036
  },
1910
2037
  async operation(node) {
1911
2038
  const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
@@ -1917,24 +2044,24 @@ async function runPluginAstHooks(plugin, context) {
1917
2044
  });
1918
2045
  if (options !== null) {
1919
2046
  collectedOperations.push(transformedNode);
1920
- if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), fabric);
2047
+ if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), driver);
1921
2048
  }
1922
2049
  }
1923
2050
  });
1924
- if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), fabric);
2051
+ if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), driver);
1925
2052
  }
1926
2053
  /**
1927
2054
  * Runs a full Kubb build and captures errors instead of throwing.
1928
2055
  *
1929
2056
  * - Installs each plugin in order, recording failures in `failedPlugins`.
1930
2057
  * - Generates the root barrel file when `output.barrelType` is set.
1931
- * - Writes all files through Fabric.
2058
+ * - Writes all files through the driver's FileManager and FileProcessor.
1932
2059
  *
1933
2060
  * Returns a {@link BuildOutput} even on failure — inspect `error` and
1934
2061
  * `failedPlugins` to determine whether the build succeeded.
1935
2062
  */
1936
2063
  async function safeBuild(options, overrides) {
1937
- const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
2064
+ const { driver, events, sources, storage } = overrides ? overrides : await setup(options);
1938
2065
  const failedPlugins = /* @__PURE__ */ new Set();
1939
2066
  const pluginTimings = /* @__PURE__ */ new Map();
1940
2067
  const config = driver.config;
@@ -1954,7 +2081,7 @@ async function safeBuild(options, overrides) {
1954
2081
  await plugin.buildStart.call(context);
1955
2082
  if (plugin.schema || plugin.operation || plugin.operations) await runPluginAstHooks(plugin, context);
1956
2083
  if (output) {
1957
- const barrelFiles = await getBarrelFiles(fabric.files, {
2084
+ const barrelFiles = await getBarrelFiles(driver.fileManager.files, {
1958
2085
  type: output.barrelType ?? "named",
1959
2086
  root,
1960
2087
  output,
@@ -2008,15 +2135,15 @@ async function safeBuild(options, overrides) {
2008
2135
  ` • Path: ${rootPath}`
2009
2136
  ]
2010
2137
  });
2011
- const barrelFiles = fabric.files.filter((file) => {
2138
+ const barrelFiles = driver.fileManager.files.filter((file) => {
2012
2139
  return file.sources.some((source) => source.isIndexable);
2013
2140
  });
2014
2141
  await events.emit("debug", {
2015
2142
  date: /* @__PURE__ */ new Date(),
2016
2143
  logs: [`Found ${barrelFiles.length} indexable files for barrel export`]
2017
2144
  });
2018
- const existingBarrel = fabric.files.find((f) => f.path === rootPath);
2019
- const rootFile = {
2145
+ const existingBarrel = driver.fileManager.files.find((f) => f.path === rootPath);
2146
+ const rootFile = createFile({
2020
2147
  path: rootPath,
2021
2148
  baseName: BARREL_FILENAME,
2022
2149
  exports: buildBarrelExports({
@@ -2025,26 +2152,59 @@ async function safeBuild(options, overrides) {
2025
2152
  existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
2026
2153
  config,
2027
2154
  driver
2028
- }),
2155
+ }).map((e) => createExport(e)),
2029
2156
  sources: [],
2030
2157
  imports: [],
2031
2158
  meta: {}
2032
- };
2033
- await fabric.upsertFile(rootFile);
2159
+ });
2160
+ driver.fileManager.upsert(rootFile);
2034
2161
  await events.emit("debug", {
2035
2162
  date: /* @__PURE__ */ new Date(),
2036
2163
  logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`]
2037
2164
  });
2038
2165
  }
2039
- const files = [...fabric.files];
2040
- await fabric.write({ extension: config.output.extension });
2166
+ const files = driver.fileManager.files;
2167
+ const parsersMap = /* @__PURE__ */ new Map();
2168
+ for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
2169
+ const fileProcessor = new FileProcessor();
2170
+ await events.emit("debug", {
2171
+ date: /* @__PURE__ */ new Date(),
2172
+ logs: [`Writing ${files.length} files...`]
2173
+ });
2174
+ await fileProcessor.run(files, {
2175
+ parsers: parsersMap,
2176
+ extension: config.output.extension,
2177
+ onStart: async (processingFiles) => {
2178
+ await events.emit("files:processing:start", processingFiles);
2179
+ },
2180
+ onUpdate: async ({ file, source, processed, total, percentage }) => {
2181
+ await events.emit("file:processing:update", {
2182
+ file,
2183
+ source,
2184
+ processed,
2185
+ total,
2186
+ percentage,
2187
+ config
2188
+ });
2189
+ if (source) {
2190
+ await storage?.setItem(file.path, source);
2191
+ sources.set(file.path, source);
2192
+ }
2193
+ },
2194
+ onEnd: async (processedFiles) => {
2195
+ await events.emit("files:processing:end", processedFiles);
2196
+ await events.emit("debug", {
2197
+ date: /* @__PURE__ */ new Date(),
2198
+ logs: [`✓ File write process completed for ${processedFiles.length} files`]
2199
+ });
2200
+ }
2201
+ });
2041
2202
  for (const plugin of driver.plugins.values()) if (plugin.buildEnd) {
2042
2203
  const context = driver.getContext(plugin);
2043
2204
  await plugin.buildEnd.call(context);
2044
2205
  }
2045
2206
  return {
2046
2207
  failedPlugins,
2047
- fabric,
2048
2208
  files,
2049
2209
  driver,
2050
2210
  pluginTimings,
@@ -2053,7 +2213,6 @@ async function safeBuild(options, overrides) {
2053
2213
  } catch (error) {
2054
2214
  return {
2055
2215
  failedPlugins,
2056
- fabric,
2057
2216
  files: [],
2058
2217
  driver,
2059
2218
  pluginTimings,
@@ -2074,11 +2233,11 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, dri
2074
2233
  if (!pluginOptions || pluginOptions.output?.barrelType === false) return [];
2075
2234
  const exportName = config.output.barrelType === "all" ? void 0 : source.name ? [source.name] : void 0;
2076
2235
  if (exportName?.some((n) => existingExports.has(n))) return [];
2077
- return [{
2236
+ return [createExport({
2078
2237
  name: exportName,
2079
2238
  path: getRelativePath(rootDir, file.path),
2080
2239
  isTypeOnly: config.output.barrelType === "all" ? containsOnlyTypes : source.isTypeOnly
2081
- }];
2240
+ })];
2082
2241
  });
2083
2242
  });
2084
2243
  }
@@ -2148,6 +2307,11 @@ function createPlugin(build) {
2148
2307
  return (options) => build(options ?? {});
2149
2308
  }
2150
2309
  //#endregion
2310
+ //#region src/defineConfig.ts
2311
+ function defineConfig(config) {
2312
+ return config;
2313
+ }
2314
+ //#endregion
2151
2315
  //#region src/defineGenerator.ts
2152
2316
  /**
2153
2317
  * Defines a generator. Returns the object as-is with correct `this` typings.
@@ -2185,19 +2349,19 @@ function mergeGenerators(generators) {
2185
2349
  async schema(node, options) {
2186
2350
  for (const gen of generators) {
2187
2351
  if (!gen.schema) continue;
2188
- await applyHookResult(await gen.schema.call(this, node, options), this.fabric);
2352
+ await applyHookResult(await gen.schema.call(this, node, options), this.driver);
2189
2353
  }
2190
2354
  },
2191
2355
  async operation(node, options) {
2192
2356
  for (const gen of generators) {
2193
2357
  if (!gen.operation) continue;
2194
- await applyHookResult(await gen.operation.call(this, node, options), this.fabric);
2358
+ await applyHookResult(await gen.operation.call(this, node, options), this.driver);
2195
2359
  }
2196
2360
  },
2197
2361
  async operations(nodes, options) {
2198
2362
  for (const gen of generators) {
2199
2363
  if (!gen.operations) continue;
2200
- await applyHookResult(await gen.operations.call(this, nodes, options), this.fabric);
2364
+ await applyHookResult(await gen.operations.call(this, nodes, options), this.driver);
2201
2365
  }
2202
2366
  }
2203
2367
  };
@@ -2220,6 +2384,34 @@ function defineLogger(logger) {
2220
2384
  return logger;
2221
2385
  }
2222
2386
  //#endregion
2387
+ //#region src/defineParser.ts
2388
+ /**
2389
+ * Defines a parser with type safety.
2390
+ *
2391
+ * Use this function to create parsers that transform generated files to strings
2392
+ * based on their extension.
2393
+ *
2394
+ * @example
2395
+ * ```ts
2396
+ * import { defineParser } from '@kubb/core'
2397
+ *
2398
+ * export const jsonParser = defineParser({
2399
+ * name: 'json',
2400
+ * extNames: ['.json'],
2401
+ * parse(file) {
2402
+ * return file.sources.map((s) => s.value).join('\n')
2403
+ * },
2404
+ * })
2405
+ * ```
2406
+ */
2407
+ function defineParser(parser) {
2408
+ return {
2409
+ install() {},
2410
+ type: "parser",
2411
+ ...parser
2412
+ };
2413
+ }
2414
+ //#endregion
2223
2415
  //#region src/definePresets.ts
2224
2416
  /**
2225
2417
  * Creates a typed presets registry object — a named collection of {@link Preset} entries.
@@ -2373,7 +2565,7 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
2373
2565
  /**
2374
2566
  * Default file resolver used by `defineResolver`.
2375
2567
  *
2376
- * Resolves a `FabricFile.File` by combining name resolution (`resolver.default`) with
2568
+ * Resolves a `FileNode` by combining name resolution (`resolver.default`) with
2377
2569
  * path resolution (`resolver.resolvePath`). The resolved file always has empty
2378
2570
  * `sources`, `imports`, and `exports` arrays — consumers populate those separately.
2379
2571
  *
@@ -2406,14 +2598,14 @@ function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
2406
2598
  tag,
2407
2599
  path: groupPath
2408
2600
  }, context);
2409
- return {
2601
+ return createFile({
2410
2602
  path: filePath,
2411
2603
  baseName: path.basename(filePath),
2412
2604
  meta: { pluginName: this.pluginName },
2413
2605
  sources: [],
2414
2606
  imports: [],
2415
2607
  exports: []
2416
- };
2608
+ });
2417
2609
  }
2418
2610
  /**
2419
2611
  * Generates the default "Generated by Kubb" banner from config and optional node metadata.
@@ -2465,13 +2657,13 @@ function buildDefaultBanner({ title, description, version, config }) {
2465
2657
  *
2466
2658
  * @example Function banner with node
2467
2659
  * ```ts
2468
- * defaultResolveBanner(rootNode, { output: { banner: (node) => `// v${node.version}` }, config })
2660
+ * defaultResolveBanner(inputNode, { output: { banner: (node) => `// v${node.version}` }, config })
2469
2661
  * // → '// v3.0.0'
2470
2662
  * ```
2471
2663
  *
2472
2664
  * @example No user banner — Kubb notice with OAS metadata
2473
2665
  * ```ts
2474
- * defaultResolveBanner(rootNode, { config })
2666
+ * defaultResolveBanner(inputNode, { config })
2475
2667
  * // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
2476
2668
  * ```
2477
2669
  *
@@ -2507,7 +2699,7 @@ function defaultResolveBanner(node, { output, config }) {
2507
2699
  *
2508
2700
  * @example Function footer with node
2509
2701
  * ```ts
2510
- * defaultResolveFooter(rootNode, { output: { footer: (node) => `// ${node.title}` }, config })
2702
+ * defaultResolveFooter(inputNode, { output: { footer: (node) => `// ${node.title}` }, config })
2511
2703
  * // → '// Pet Store'
2512
2704
  * ```
2513
2705
  */
@@ -2523,7 +2715,7 @@ function defaultResolveFooter(node, { output }) {
2523
2715
  * - `default` — name casing strategy (camelCase / PascalCase)
2524
2716
  * - `resolveOptions` — include/exclude/override filtering
2525
2717
  * - `resolvePath` — output path computation
2526
- * - `resolveFile` — full `FabricFile.File` construction
2718
+ * - `resolveFile` — full `FileNode` construction
2527
2719
  *
2528
2720
  * Methods in the builder have access to `this` (the full resolver object), so they
2529
2721
  * can call other resolver methods without circular imports.
@@ -2875,6 +3067,6 @@ function satisfiesDependency(dependency, version, cwd) {
2875
3067
  return satisfies(semVer, version);
2876
3068
  }
2877
3069
  //#endregion
2878
- export { AsyncEventEmitter, FunctionParams, PluginDriver, URLPath, build, build as default, buildDefaultBanner, composeTransformers, createAdapter, createPlugin, createStorage, defaultResolveBanner, defaultResolveFile, defaultResolveFooter, defaultResolveOptions, defaultResolvePath, defineConfig, defineGenerator, defineLogger, definePresets, definePrinter, defineResolver, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, getPreset, isInputPath, linters, logLevel, memoryStorage, mergeGenerators, safeBuild, satisfiesDependency, setup };
3070
+ export { AsyncEventEmitter, FunctionParams, PluginDriver, URLPath, build, build as default, buildDefaultBanner, composeTransformers, createAdapter, createPlugin, createStorage, defaultResolveBanner, defaultResolveFile, defaultResolveFooter, defaultResolveOptions, defaultResolvePath, defineConfig, defineGenerator, defineLogger, defineParser, definePresets, definePrinter, defineResolver, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, getPreset, isInputPath, linters, logLevel, memoryStorage, mergeGenerators, safeBuild, satisfiesDependency, setup };
2879
3071
 
2880
3072
  //# sourceMappingURL=index.js.map