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

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
@@ -15,8 +15,8 @@ let _kubb_react_fabric_plugins = require("@kubb/react-fabric/plugins");
15
15
  let node_perf_hooks = require("node:perf_hooks");
16
16
  let fflate = require("fflate");
17
17
  let tinyexec = require("tinyexec");
18
- let node_process = require("node:process");
19
18
  let _kubb_react_fabric_jsx_runtime = require("@kubb/react-fabric/jsx-runtime");
19
+ let node_process = require("node:process");
20
20
  let remeda = require("remeda");
21
21
  let empathic_package = require("empathic/package");
22
22
  empathic_package = require_chunk.__toESM(empathic_package);
@@ -901,6 +901,7 @@ function validateConcurrency(concurrency) {
901
901
  * - Each function receives the accumulated state from the previous call.
902
902
  * - Skips functions that return a falsy value (acts as a no-op for that step).
903
903
  * - Returns an array of all individual results.
904
+ * @deprecated
904
905
  */
905
906
  function hookSeq(promises) {
906
907
  return promises.filter(Boolean).reduce((promise, func) => {
@@ -917,6 +918,7 @@ function hookSeq(promises) {
917
918
  *
918
919
  * - Stops as soon as `nullCheck` passes for a result (default: `!== null`).
919
920
  * - Subsequent functions are skipped once a match is found.
921
+ * @deprecated
920
922
  */
921
923
  function hookFirst(promises, nullCheck = (state) => state !== null) {
922
924
  let promise = Promise.resolve(null);
@@ -931,6 +933,7 @@ function hookFirst(promises, nullCheck = (state) => state !== null) {
931
933
  *
932
934
  * - Limits simultaneous executions to `concurrency` (default: unlimited).
933
935
  * - Uses `Promise.allSettled` so individual failures do not cancel other tasks.
936
+ * @deprecated
934
937
  */
935
938
  function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
936
939
  const limit = pLimit(concurrency);
@@ -967,7 +970,13 @@ var PluginDriver = class {
967
970
  constructor(config, options) {
968
971
  this.config = config;
969
972
  this.options = options;
970
- config.plugins.map((plugin) => Object.assign({ install() {} }, plugin)).sort((a, b) => {
973
+ config.plugins.map((plugin) => Object.assign({
974
+ buildStart() {},
975
+ buildEnd() {}
976
+ }, plugin)).filter((plugin) => {
977
+ if (typeof plugin.apply === "function") return plugin.apply(config);
978
+ return true;
979
+ }).sort((a, b) => {
971
980
  if (b.pre?.includes(a.name)) return 1;
972
981
  if (b.post?.includes(a.name)) return -1;
973
982
  return 0;
@@ -983,9 +992,16 @@ var PluginDriver = class {
983
992
  const baseContext = {
984
993
  fabric: driver.options.fabric,
985
994
  config: driver.config,
995
+ get root() {
996
+ return (0, node_path.resolve)(driver.config.root, driver.config.output.path);
997
+ },
998
+ getMode(output) {
999
+ return getMode((0, node_path.resolve)(driver.config.root, driver.config.output.path, output.path));
1000
+ },
1001
+ events: driver.options.events,
986
1002
  plugin,
987
1003
  getPlugin: driver.getPlugin.bind(driver),
988
- events: driver.options.events,
1004
+ requirePlugin: driver.requirePlugin.bind(driver),
989
1005
  driver,
990
1006
  addFile: async (...files) => {
991
1007
  await this.options.fabric.addFile(...files);
@@ -1005,6 +1021,15 @@ var PluginDriver = class {
1005
1021
  get transformer() {
1006
1022
  return plugin.transformer;
1007
1023
  },
1024
+ warn(message) {
1025
+ driver.events.emit("warn", message);
1026
+ },
1027
+ error(error) {
1028
+ driver.events.emit("error", typeof error === "string" ? new Error(error) : error);
1029
+ },
1030
+ info(message) {
1031
+ driver.events.emit("info", message);
1032
+ },
1008
1033
  openInStudio(options) {
1009
1034
  if (!driver.config.devtools || driver.#studioIsOpen) return;
1010
1035
  if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
@@ -1015,8 +1040,8 @@ var PluginDriver = class {
1015
1040
  }
1016
1041
  };
1017
1042
  const mergedExtras = {};
1018
- for (const plugin of this.plugins.values()) if (typeof plugin.inject === "function") {
1019
- const result = plugin.inject.call(baseContext, baseContext);
1043
+ for (const p of this.plugins.values()) if (typeof p.inject === "function") {
1044
+ const result = p.inject.call(baseContext);
1020
1045
  if (result !== null && typeof result === "object") Object.assign(mergedExtras, result);
1021
1046
  }
1022
1047
  return {
@@ -1234,6 +1259,11 @@ var PluginDriver = class {
1234
1259
  getPlugin(pluginName) {
1235
1260
  return this.plugins.get(pluginName);
1236
1261
  }
1262
+ requirePlugin(pluginName) {
1263
+ const plugin = this.plugins.get(pluginName);
1264
+ if (!plugin) throw new Error(`[kubb] Plugin "${pluginName}" is required but not found. Make sure it is included in your Kubb config.`);
1265
+ return plugin;
1266
+ }
1237
1267
  /**
1238
1268
  * Run an async plugin hook and return the result.
1239
1269
  * @param hookName Name of the plugin hook. Must be either in `PluginHooks` or `OutputPluginValueHooks`.
@@ -1322,6 +1352,26 @@ var PluginDriver = class {
1322
1352
  }
1323
1353
  };
1324
1354
  //#endregion
1355
+ //#region src/renderNode.tsx
1356
+ /**
1357
+ * Handles the return value of a plugin AST hook or generator method.
1358
+ *
1359
+ * - React element → rendered via an isolated react-fabric context, files merged into `fabric`
1360
+ * - `Array<FabricFile.File>` → upserted directly into `fabric`
1361
+ * - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
1362
+ */
1363
+ async function applyHookResult(result, fabric) {
1364
+ if (!result) return;
1365
+ if (Array.isArray(result)) {
1366
+ await fabric.upsertFile(...result);
1367
+ return;
1368
+ }
1369
+ const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
1370
+ await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, { children: result }));
1371
+ fabric.context.fileManager.upsert(...fabricChild.files);
1372
+ fabricChild.unmount();
1373
+ }
1374
+ //#endregion
1325
1375
  //#region src/createStorage.ts
1326
1376
  /**
1327
1377
  * Creates a storage factory. Call the returned function with optional options to get the storage instance.
@@ -1419,7 +1469,7 @@ const fsStorage = createStorage(() => ({
1419
1469
  }));
1420
1470
  //#endregion
1421
1471
  //#region package.json
1422
- var version = "5.0.0-alpha.29";
1472
+ var version = "5.0.0-alpha.30";
1423
1473
  //#endregion
1424
1474
  //#region src/utils/diagnostics.ts
1425
1475
  /**
@@ -1438,103 +1488,338 @@ function getDiagnosticInfo() {
1438
1488
  };
1439
1489
  }
1440
1490
  //#endregion
1441
- //#region src/build.ts
1491
+ //#region src/utils/TreeNode.ts
1442
1492
  /**
1443
- * Initializes all Kubb infrastructure for a build without executing any plugins.
1444
- *
1445
- * - Validates the input path (when applicable).
1446
- * - Applies config defaults (`root`, `output.*`, `devtools`).
1447
- * - Creates the Fabric instance and wires storage, format, and lint hooks.
1448
- * - Runs the adapter (if configured) to produce the universal `RootNode`.
1493
+ * Tree structure used to build per-directory barrel (`index.ts`) files from a
1494
+ * flat list of generated {@link FabricFile.File} entries.
1449
1495
  *
1450
- * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
1451
- * via the `overrides` argument to reuse the same infrastructure across multiple runs.
1496
+ * Each node represents either a directory or a file within the output tree.
1497
+ * Use {@link TreeNode.build} to construct a root node from a file list, then
1498
+ * traverse with {@link TreeNode.forEach}, {@link TreeNode.leaves}, or the
1499
+ * `*Deep` helpers.
1452
1500
  */
1453
- async function setup(options) {
1454
- const { config: userConfig, events = new AsyncEventEmitter() } = options;
1455
- const sources = /* @__PURE__ */ new Map();
1456
- const diagnosticInfo = getDiagnosticInfo();
1457
- if (Array.isArray(userConfig.input)) await events.emit("warn", "This feature is still under development — use with caution");
1458
- await events.emit("debug", {
1459
- date: /* @__PURE__ */ new Date(),
1460
- logs: [
1461
- "Configuration:",
1462
- ` • Name: ${userConfig.name || "unnamed"}`,
1463
- ` • Root: ${userConfig.root || process.cwd()}`,
1464
- ` • Output: ${userConfig.output?.path || "not specified"}`,
1465
- ` • Plugins: ${userConfig.plugins?.length || 0}`,
1466
- "Output Settings:",
1467
- ` • Storage: ${userConfig.output?.storage ? `custom(${userConfig.output.storage.name})` : userConfig.output?.write === false ? "disabled" : "filesystem (default)"}`,
1468
- ` • Formatter: ${userConfig.output?.format || "none"}`,
1469
- ` • Linter: ${userConfig.output?.lint || "none"}`,
1470
- "Environment:",
1471
- Object.entries(diagnosticInfo).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
1472
- ]
1473
- });
1474
- try {
1475
- if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
1476
- await exists(userConfig.input.path);
1477
- await events.emit("debug", {
1478
- date: /* @__PURE__ */ new Date(),
1479
- logs: [`✓ Input file validated: ${userConfig.input.path}`]
1501
+ var TreeNode = class TreeNode {
1502
+ data;
1503
+ parent;
1504
+ children = [];
1505
+ #cachedLeaves = void 0;
1506
+ constructor(data, parent) {
1507
+ this.data = data;
1508
+ this.parent = parent;
1509
+ }
1510
+ addChild(data) {
1511
+ const child = new TreeNode(data, this);
1512
+ if (!this.children) this.children = [];
1513
+ this.children.push(child);
1514
+ return child;
1515
+ }
1516
+ /**
1517
+ * Returns the root ancestor of this node, walking up via `parent` links.
1518
+ */
1519
+ get root() {
1520
+ if (!this.parent) return this;
1521
+ return this.parent.root;
1522
+ }
1523
+ /**
1524
+ * Returns all leaf descendants (nodes with no children) of this node.
1525
+ *
1526
+ * Results are cached after the first traversal.
1527
+ */
1528
+ get leaves() {
1529
+ if (!this.children || this.children.length === 0) return [this];
1530
+ if (this.#cachedLeaves) return this.#cachedLeaves;
1531
+ const leaves = [];
1532
+ for (const child of this.children) leaves.push(...child.leaves);
1533
+ this.#cachedLeaves = leaves;
1534
+ return leaves;
1535
+ }
1536
+ /**
1537
+ * Visits this node and every descendant in depth-first order.
1538
+ */
1539
+ forEach(callback) {
1540
+ if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
1541
+ callback(this);
1542
+ for (const child of this.children) child.forEach(callback);
1543
+ return this;
1544
+ }
1545
+ /**
1546
+ * Finds the first leaf that satisfies `predicate`, or `undefined` when none match.
1547
+ */
1548
+ findDeep(predicate) {
1549
+ if (typeof predicate !== "function") throw new TypeError("find() predicate must be a function");
1550
+ return this.leaves.find(predicate);
1551
+ }
1552
+ /**
1553
+ * Calls `callback` for every leaf of this node.
1554
+ */
1555
+ forEachDeep(callback) {
1556
+ if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
1557
+ this.leaves.forEach(callback);
1558
+ }
1559
+ /**
1560
+ * Returns all leaves that satisfy `callback`.
1561
+ */
1562
+ filterDeep(callback) {
1563
+ if (typeof callback !== "function") throw new TypeError("filter() callback must be a function");
1564
+ return this.leaves.filter(callback);
1565
+ }
1566
+ /**
1567
+ * Maps every leaf through `callback` and returns the resulting array.
1568
+ */
1569
+ mapDeep(callback) {
1570
+ if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
1571
+ return this.leaves.map(callback);
1572
+ }
1573
+ /**
1574
+ * Builds a {@link TreeNode} tree from a flat list of files.
1575
+ *
1576
+ * - Filters to files under `root` (when provided) and skips `.json` files.
1577
+ * - Returns `null` when no files match.
1578
+ */
1579
+ static build(files, root) {
1580
+ try {
1581
+ const filteredTree = buildDirectoryTree(files, root);
1582
+ if (!filteredTree) return null;
1583
+ const treeNode = new TreeNode({
1584
+ name: filteredTree.name,
1585
+ path: filteredTree.path,
1586
+ file: filteredTree.file,
1587
+ type: getMode(filteredTree.path)
1480
1588
  });
1589
+ const recurse = (node, item) => {
1590
+ const subNode = node.addChild({
1591
+ name: item.name,
1592
+ path: item.path,
1593
+ file: item.file,
1594
+ type: getMode(item.path)
1595
+ });
1596
+ if (item.children?.length) item.children?.forEach((child) => {
1597
+ recurse(subNode, child);
1598
+ });
1599
+ };
1600
+ filteredTree.children?.forEach((child) => {
1601
+ recurse(treeNode, child);
1602
+ });
1603
+ return treeNode;
1604
+ } catch (error) {
1605
+ throw new Error("Something went wrong with creating barrel files with the TreeNode class", { cause: error });
1481
1606
  }
1482
- } catch (caughtError) {
1483
- if (isInputPath(userConfig)) {
1484
- const error = caughtError;
1485
- 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 });
1486
- }
1487
- }
1488
- const definedConfig = {
1489
- root: userConfig.root || process.cwd(),
1490
- ...userConfig,
1491
- output: {
1492
- write: true,
1493
- barrelType: "named",
1494
- extension: DEFAULT_EXTENSION,
1495
- defaultBanner: DEFAULT_BANNER,
1496
- ...userConfig.output
1497
- },
1498
- devtools: userConfig.devtools ? {
1499
- studioUrl: DEFAULT_STUDIO_URL,
1500
- ...typeof userConfig.devtools === "boolean" ? {} : userConfig.devtools
1501
- } : void 0,
1502
- plugins: userConfig.plugins
1503
- };
1504
- const storage = definedConfig.output.write === false ? null : definedConfig.output.storage ?? fsStorage();
1505
- if (definedConfig.output.clean) {
1506
- await events.emit("debug", {
1507
- date: /* @__PURE__ */ new Date(),
1508
- logs: ["Cleaning output directories", ` • Output: ${definedConfig.output.path}`]
1509
- });
1510
- await storage?.clear((0, node_path.resolve)(definedConfig.root, definedConfig.output.path));
1511
1607
  }
1512
- const fabric = (0, _kubb_react_fabric.createFabric)();
1513
- fabric.use(_kubb_react_fabric_plugins.fsPlugin);
1514
- fabric.use(_kubb_react_fabric_parsers.typescriptParser);
1515
- fabric.context.on("files:processing:start", (files) => {
1516
- events.emit("files:processing:start", files);
1517
- events.emit("debug", {
1518
- date: /* @__PURE__ */ new Date(),
1519
- logs: [`Writing ${files.length} files...`]
1520
- });
1608
+ };
1609
+ const normalizePath = (p) => p.replaceAll("\\", "/");
1610
+ function buildDirectoryTree(files, rootFolder = "") {
1611
+ const normalizedRootFolder = normalizePath(rootFolder);
1612
+ const rootPrefix = normalizedRootFolder.endsWith("/") ? normalizedRootFolder : `${normalizedRootFolder}/`;
1613
+ const filteredFiles = files.filter((file) => {
1614
+ const normalizedFilePath = normalizePath(file.path);
1615
+ return rootFolder ? normalizedFilePath.startsWith(rootPrefix) && !normalizedFilePath.endsWith(".json") : !normalizedFilePath.endsWith(".json");
1521
1616
  });
1522
- fabric.context.on("file:processing:update", async (params) => {
1523
- const { file, source } = params;
1524
- await events.emit("file:processing:update", {
1525
- ...params,
1526
- config: definedConfig,
1527
- source
1617
+ if (filteredFiles.length === 0) return null;
1618
+ const root = {
1619
+ name: rootFolder || "",
1620
+ path: rootFolder || "",
1621
+ children: []
1622
+ };
1623
+ filteredFiles.forEach((file) => {
1624
+ const parts = file.path.slice(rootFolder.length).split("/").filter(Boolean);
1625
+ let currentLevel = root.children;
1626
+ let currentPath = normalizePath(rootFolder);
1627
+ parts.forEach((part, index) => {
1628
+ currentPath = node_path.default.posix.join(currentPath, part);
1629
+ let existingNode = currentLevel.find((node) => node.name === part);
1630
+ if (!existingNode) {
1631
+ if (index === parts.length - 1) existingNode = {
1632
+ name: part,
1633
+ file,
1634
+ path: currentPath
1635
+ };
1636
+ else existingNode = {
1637
+ name: part,
1638
+ path: currentPath,
1639
+ children: []
1640
+ };
1641
+ currentLevel.push(existingNode);
1642
+ }
1643
+ if (!existingNode.file) currentLevel = existingNode.children;
1528
1644
  });
1529
- if (source) {
1530
- const key = (0, node_path.relative)((0, node_path.resolve)(definedConfig.root), file.path);
1531
- await storage?.setItem(key, source);
1532
- sources.set(file.path, source);
1533
- }
1534
1645
  });
1535
- fabric.context.on("files:processing:end", async (files) => {
1536
- await events.emit("files:processing:end", files);
1537
- await events.emit("debug", {
1646
+ return root;
1647
+ }
1648
+ //#endregion
1649
+ //#region src/utils/getBarrelFiles.ts
1650
+ /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
1651
+ function getBarrelFilesByRoot(root, files) {
1652
+ const cachedFiles = /* @__PURE__ */ new Map();
1653
+ TreeNode.build(files, root)?.forEach((treeNode) => {
1654
+ if (!treeNode?.children || !treeNode.parent?.data.path) return;
1655
+ const barrelFile = {
1656
+ path: (0, node_path.join)(treeNode.parent?.data.path, "index.ts"),
1657
+ baseName: "index.ts",
1658
+ exports: [],
1659
+ imports: [],
1660
+ sources: []
1661
+ };
1662
+ const previousBarrelFile = cachedFiles.get(barrelFile.path);
1663
+ treeNode.leaves.forEach((item) => {
1664
+ if (!item.data.name) return;
1665
+ (item.data.file?.sources || []).forEach((source) => {
1666
+ if (!item.data.file?.path || !source.isIndexable || !source.name) return;
1667
+ if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
1668
+ barrelFile.exports.push({
1669
+ name: [source.name],
1670
+ path: getRelativePath(treeNode.parent?.data.path, item.data.path),
1671
+ isTypeOnly: source.isTypeOnly
1672
+ });
1673
+ barrelFile.sources.push({
1674
+ name: source.name,
1675
+ isTypeOnly: source.isTypeOnly,
1676
+ value: "",
1677
+ isExportable: false,
1678
+ isIndexable: false
1679
+ });
1680
+ });
1681
+ });
1682
+ if (previousBarrelFile) {
1683
+ previousBarrelFile.sources.push(...barrelFile.sources);
1684
+ previousBarrelFile.exports?.push(...barrelFile.exports || []);
1685
+ } else cachedFiles.set(barrelFile.path, barrelFile);
1686
+ });
1687
+ return [...cachedFiles.values()];
1688
+ }
1689
+ function trimExtName(text) {
1690
+ const dotIndex = text.lastIndexOf(".");
1691
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
1692
+ return text;
1693
+ }
1694
+ /**
1695
+ * Generates `index.ts` barrel files for all directories under `root/output.path`.
1696
+ *
1697
+ * - Returns an empty array when `type` is falsy or `'propagate'`.
1698
+ * - Skips generation when the output path itself ends with `index` (already a barrel).
1699
+ * - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
1700
+ * - Attaches `meta` to each barrel file for downstream plugin identification.
1701
+ */
1702
+ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1703
+ if (!type || type === "propagate") return [];
1704
+ const pathToBuildFrom = (0, node_path.join)(root, output.path);
1705
+ if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
1706
+ const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files);
1707
+ if (type === "all") return barrelFiles.map((file) => {
1708
+ return {
1709
+ ...file,
1710
+ exports: file.exports?.map((exportItem) => {
1711
+ return {
1712
+ ...exportItem,
1713
+ name: void 0
1714
+ };
1715
+ })
1716
+ };
1717
+ });
1718
+ return barrelFiles.map((indexFile) => {
1719
+ return {
1720
+ ...indexFile,
1721
+ meta
1722
+ };
1723
+ });
1724
+ }
1725
+ //#endregion
1726
+ //#region src/build.ts
1727
+ /**
1728
+ * Initializes all Kubb infrastructure for a build without executing any plugins.
1729
+ *
1730
+ * - Validates the input path (when applicable).
1731
+ * - Applies config defaults (`root`, `output.*`, `devtools`).
1732
+ * - Creates the Fabric instance and wires storage, format, and lint hooks.
1733
+ * - Runs the adapter (if configured) to produce the universal `RootNode`.
1734
+ *
1735
+ * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
1736
+ * via the `overrides` argument to reuse the same infrastructure across multiple runs.
1737
+ */
1738
+ async function setup(options) {
1739
+ const { config: userConfig, events = new AsyncEventEmitter() } = options;
1740
+ const sources = /* @__PURE__ */ new Map();
1741
+ const diagnosticInfo = getDiagnosticInfo();
1742
+ if (Array.isArray(userConfig.input)) await events.emit("warn", "This feature is still under development — use with caution");
1743
+ await events.emit("debug", {
1744
+ date: /* @__PURE__ */ new Date(),
1745
+ logs: [
1746
+ "Configuration:",
1747
+ ` • Name: ${userConfig.name || "unnamed"}`,
1748
+ ` • Root: ${userConfig.root || process.cwd()}`,
1749
+ ` • Output: ${userConfig.output?.path || "not specified"}`,
1750
+ ` • Plugins: ${userConfig.plugins?.length || 0}`,
1751
+ "Output Settings:",
1752
+ ` • Storage: ${userConfig.output?.storage ? `custom(${userConfig.output.storage.name})` : userConfig.output?.write === false ? "disabled" : "filesystem (default)"}`,
1753
+ ` • Formatter: ${userConfig.output?.format || "none"}`,
1754
+ ` • Linter: ${userConfig.output?.lint || "none"}`,
1755
+ "Environment:",
1756
+ Object.entries(diagnosticInfo).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
1757
+ ]
1758
+ });
1759
+ try {
1760
+ if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
1761
+ await exists(userConfig.input.path);
1762
+ await events.emit("debug", {
1763
+ date: /* @__PURE__ */ new Date(),
1764
+ logs: [`✓ Input file validated: ${userConfig.input.path}`]
1765
+ });
1766
+ }
1767
+ } catch (caughtError) {
1768
+ if (isInputPath(userConfig)) {
1769
+ const error = caughtError;
1770
+ 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
+ }
1772
+ }
1773
+ const definedConfig = {
1774
+ root: userConfig.root || process.cwd(),
1775
+ ...userConfig,
1776
+ output: {
1777
+ write: true,
1778
+ barrelType: "named",
1779
+ extension: DEFAULT_EXTENSION,
1780
+ defaultBanner: DEFAULT_BANNER,
1781
+ ...userConfig.output
1782
+ },
1783
+ devtools: userConfig.devtools ? {
1784
+ studioUrl: DEFAULT_STUDIO_URL,
1785
+ ...typeof userConfig.devtools === "boolean" ? {} : userConfig.devtools
1786
+ } : void 0,
1787
+ plugins: userConfig.plugins
1788
+ };
1789
+ const storage = definedConfig.output.write === false ? null : definedConfig.output.storage ?? fsStorage();
1790
+ if (definedConfig.output.clean) {
1791
+ await events.emit("debug", {
1792
+ date: /* @__PURE__ */ new Date(),
1793
+ logs: ["Cleaning output directories", ` • Output: ${definedConfig.output.path}`]
1794
+ });
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
+ }
1819
+ });
1820
+ fabric.context.on("files:processing:end", async (files) => {
1821
+ await events.emit("files:processing:end", files);
1822
+ await events.emit("debug", {
1538
1823
  date: /* @__PURE__ */ new Date(),
1539
1824
  logs: [`✓ File write process completed for ${files.length} files`]
1540
1825
  });
@@ -1600,6 +1885,51 @@ async function build(options, overrides) {
1600
1885
  };
1601
1886
  }
1602
1887
  /**
1888
+ * Walks the AST and dispatches nodes to a plugin's direct AST hooks
1889
+ * (`schema`, `operation`, `operations`).
1890
+ *
1891
+ * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
1892
+ * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
1893
+ * - 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).
1895
+ * - Barrel files are generated automatically when `output.barrelType` is set.
1896
+ */
1897
+ async function runPluginAstHooks(plugin, context) {
1898
+ const { adapter, rootNode, resolver, fabric } = context;
1899
+ 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.`);
1901
+ const collectedOperations = [];
1902
+ await (0, _kubb_ast.walk)(rootNode, {
1903
+ depth: "shallow",
1904
+ async schema(node) {
1905
+ if (!plugin.schema) return;
1906
+ const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
1907
+ const options = resolver.resolveOptions(transformedNode, {
1908
+ options: plugin.options,
1909
+ exclude,
1910
+ include,
1911
+ override
1912
+ });
1913
+ if (options === null) return;
1914
+ await applyHookResult(await plugin.schema.call(context, transformedNode, options), fabric);
1915
+ },
1916
+ async operation(node) {
1917
+ const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
1918
+ const options = resolver.resolveOptions(transformedNode, {
1919
+ options: plugin.options,
1920
+ exclude,
1921
+ include,
1922
+ override
1923
+ });
1924
+ if (options !== null) {
1925
+ collectedOperations.push(transformedNode);
1926
+ if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), fabric);
1927
+ }
1928
+ }
1929
+ });
1930
+ if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), fabric);
1931
+ }
1932
+ /**
1603
1933
  * Runs a full Kubb build and captures errors instead of throwing.
1604
1934
  *
1605
1935
  * - Installs each plugin in order, recording failures in `failedPlugins`.
@@ -1618,15 +1948,26 @@ async function safeBuild(options, overrides) {
1618
1948
  for (const plugin of driver.plugins.values()) {
1619
1949
  const context = driver.getContext(plugin);
1620
1950
  const hrStart = process.hrtime();
1621
- const installer = plugin.install.bind(context);
1951
+ const { output } = plugin.options ?? {};
1952
+ const root = (0, node_path.resolve)(config.root, config.output.path);
1622
1953
  try {
1623
1954
  const timestamp = /* @__PURE__ */ new Date();
1624
1955
  await events.emit("plugin:start", plugin);
1625
1956
  await events.emit("debug", {
1626
1957
  date: timestamp,
1627
- logs: ["Installing plugin...", ` • Plugin Name: ${plugin.name}`]
1958
+ logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1628
1959
  });
1629
- await installer(context);
1960
+ await plugin.buildStart.call(context);
1961
+ if (plugin.schema || plugin.operation || plugin.operations) await runPluginAstHooks(plugin, context);
1962
+ if (output) {
1963
+ const barrelFiles = await getBarrelFiles(fabric.files, {
1964
+ type: output.barrelType ?? "named",
1965
+ root,
1966
+ output,
1967
+ meta: { pluginName: plugin.name }
1968
+ });
1969
+ await context.upsertFile(...barrelFiles);
1970
+ }
1630
1971
  const duration = getElapsedMs(hrStart);
1631
1972
  pluginTimings.set(plugin.name, duration);
1632
1973
  await events.emit("plugin:end", plugin, {
@@ -1635,7 +1976,7 @@ async function safeBuild(options, overrides) {
1635
1976
  });
1636
1977
  await events.emit("debug", {
1637
1978
  date: /* @__PURE__ */ new Date(),
1638
- logs: [`✓ Plugin installed successfully (${formatMs(duration)})`]
1979
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1639
1980
  });
1640
1981
  } catch (caughtError) {
1641
1982
  const error = caughtError;
@@ -1649,7 +1990,7 @@ async function safeBuild(options, overrides) {
1649
1990
  await events.emit("debug", {
1650
1991
  date: errorTimestamp,
1651
1992
  logs: [
1652
- "✗ Plugin installation failed",
1993
+ "✗ Plugin start failed",
1653
1994
  ` • Plugin Name: ${plugin.name}`,
1654
1995
  ` • Error: ${error.constructor.name} - ${error.message}`,
1655
1996
  " • Stack Trace:",
@@ -1703,6 +2044,10 @@ async function safeBuild(options, overrides) {
1703
2044
  }
1704
2045
  const files = [...fabric.files];
1705
2046
  await fabric.write({ extension: config.output.extension });
2047
+ for (const plugin of driver.plugins.values()) if (plugin.buildEnd) {
2048
+ const context = driver.getContext(plugin);
2049
+ await plugin.buildEnd.call(context);
2050
+ }
1706
2051
  return {
1707
2052
  failedPlugins,
1708
2053
  fabric,
@@ -1791,10 +2136,11 @@ function createAdapter(build) {
1791
2136
  * Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
1792
2137
  *
1793
2138
  * @example
2139
+ * ```ts
1794
2140
  * export const myPlugin = createPlugin<MyPlugin>((options) => {
1795
2141
  * return {
1796
2142
  * name: 'my-plugin',
1797
- * options,
2143
+ * get options() { return options },
1798
2144
  * resolvePath(baseName) { ... },
1799
2145
  * resolveName(name, type) { ... },
1800
2146
  * }
@@ -1802,38 +2148,64 @@ function createAdapter(build) {
1802
2148
  *
1803
2149
  * // instantiate
1804
2150
  * const plugin = myPlugin({ output: { path: 'src/gen' } })
2151
+ * ```
1805
2152
  */
1806
2153
  function createPlugin(build) {
1807
2154
  return (options) => build(options ?? {});
1808
2155
  }
1809
2156
  //#endregion
1810
2157
  //#region src/defineGenerator.ts
2158
+ /**
2159
+ * Defines a generator. Returns the object as-is with correct `this` typings.
2160
+ * No type discrimination (`type: 'react' | 'core'`) needed — `applyHookResult`
2161
+ * handles React elements and `File[]` uniformly.
2162
+ */
1811
2163
  function defineGenerator(generator) {
1812
- if (generator.type === "react") return {
1813
- version: "2",
1814
- Operations() {
1815
- return null;
1816
- },
1817
- Operation() {
1818
- return null;
1819
- },
1820
- Schema() {
1821
- return null;
1822
- },
1823
- ...generator
1824
- };
2164
+ return generator;
2165
+ }
2166
+ /**
2167
+ * Merges an array of generators into a single generator.
2168
+ *
2169
+ * The merged generator's `schema`, `operation`, and `operations` methods run
2170
+ * the corresponding method from each input generator in sequence, applying each
2171
+ * result via `applyHookResult`. This eliminates the need to write the loop
2172
+ * manually in each plugin.
2173
+ *
2174
+ * @param generators - Array of generators to merge into a single generator.
2175
+ *
2176
+ * @example
2177
+ * ```ts
2178
+ * const merged = mergeGenerators(generators)
2179
+ *
2180
+ * return {
2181
+ * name: pluginName,
2182
+ * schema: merged.schema,
2183
+ * operation: merged.operation,
2184
+ * operations: merged.operations,
2185
+ * }
2186
+ * ```
2187
+ */
2188
+ function mergeGenerators(generators) {
1825
2189
  return {
1826
- version: "2",
1827
- async operations() {
1828
- return [];
1829
- },
1830
- async operation() {
1831
- return [];
2190
+ name: generators.length > 0 ? generators.map((g) => g.name).join("+") : "merged",
2191
+ async schema(node, options) {
2192
+ for (const gen of generators) {
2193
+ if (!gen.schema) continue;
2194
+ await applyHookResult(await gen.schema.call(this, node, options), this.fabric);
2195
+ }
1832
2196
  },
1833
- async schema() {
1834
- return [];
2197
+ async operation(node, options) {
2198
+ for (const gen of generators) {
2199
+ if (!gen.operation) continue;
2200
+ await applyHookResult(await gen.operation.call(this, node, options), this.fabric);
2201
+ }
1835
2202
  },
1836
- ...generator
2203
+ async operations(nodes, options) {
2204
+ for (const gen of generators) {
2205
+ if (!gen.operations) continue;
2206
+ await applyHookResult(await gen.operations.call(this, nodes, options), this.fabric);
2207
+ }
2208
+ }
1837
2209
  };
1838
2210
  }
1839
2211
  //#endregion
@@ -2207,172 +2579,6 @@ function defineResolver(build) {
2207
2579
  };
2208
2580
  }
2209
2581
  //#endregion
2210
- //#region src/renderNode.tsx
2211
- /**
2212
- * Renders a React component for a list of operation nodes (V2 generators).
2213
- */
2214
- async function renderOperations(nodes, options) {
2215
- const { config, fabric, plugin, Component, driver, adapter } = options;
2216
- if (!Component) return;
2217
- const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
2218
- await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, { children: /* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(Component, {
2219
- config,
2220
- plugin,
2221
- driver,
2222
- adapter,
2223
- nodes,
2224
- options: options.options,
2225
- resolver: options.resolver
2226
- }) }));
2227
- fabric.context.fileManager.upsert(...fabricChild.files);
2228
- fabricChild.unmount();
2229
- }
2230
- /**
2231
- * Renders a React component for a single operation node (V2 generators).
2232
- */
2233
- async function renderOperation(node, options) {
2234
- const { config, fabric, plugin, Component, adapter, driver } = options;
2235
- if (!Component) return;
2236
- const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
2237
- await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, { children: /* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(Component, {
2238
- config,
2239
- plugin,
2240
- driver,
2241
- adapter,
2242
- node,
2243
- options: options.options,
2244
- resolver: options.resolver
2245
- }) }));
2246
- fabric.context.fileManager.upsert(...fabricChild.files);
2247
- fabricChild.unmount();
2248
- }
2249
- /**
2250
- * Renders a React component for a single schema node (V2 generators).
2251
- */
2252
- async function renderSchema(node, options) {
2253
- const { config, fabric, plugin, Component, adapter, driver } = options;
2254
- if (!Component) return;
2255
- const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
2256
- await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, { children: /* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(Component, {
2257
- config,
2258
- plugin,
2259
- driver,
2260
- adapter,
2261
- node,
2262
- options: options.options,
2263
- resolver: options.resolver
2264
- }) }));
2265
- fabric.context.fileManager.upsert(...fabricChild.files);
2266
- fabricChild.unmount();
2267
- }
2268
- /**
2269
- * Dispatches a single schema node to all generators (react + core).
2270
- * Resolves options per generator and skips excluded nodes.
2271
- */
2272
- async function runGeneratorSchema(node, ctx) {
2273
- const { generators, plugin, resolver, exclude, include, override, fabric, adapter, config, driver } = ctx;
2274
- for (const generator of generators) {
2275
- const options = resolver.resolveOptions(node, {
2276
- options: plugin.options,
2277
- exclude,
2278
- include,
2279
- override
2280
- });
2281
- if (options === null) continue;
2282
- if (generator.type === "react" && generator.version === "2") await renderSchema(node, {
2283
- options,
2284
- resolver,
2285
- adapter,
2286
- config,
2287
- fabric,
2288
- Component: generator.Schema,
2289
- plugin,
2290
- driver
2291
- });
2292
- if (generator.type === "core" && generator.version === "2") {
2293
- const files = await generator.schema?.({
2294
- node,
2295
- options,
2296
- resolver,
2297
- adapter,
2298
- config,
2299
- plugin,
2300
- driver
2301
- }) ?? [];
2302
- await fabric.upsertFile(...files);
2303
- }
2304
- }
2305
- }
2306
- /**
2307
- * Dispatches a single operation node to all generators (react + core).
2308
- * Resolves options per generator and skips excluded nodes.
2309
- */
2310
- async function runGeneratorOperation(node, ctx) {
2311
- const { generators, plugin, resolver, exclude, include, override, fabric, adapter, config, driver } = ctx;
2312
- for (const generator of generators) {
2313
- const options = resolver.resolveOptions(node, {
2314
- options: plugin.options,
2315
- exclude,
2316
- include,
2317
- override
2318
- });
2319
- if (options === null) continue;
2320
- if (generator.type === "react" && generator.version === "2") await renderOperation(node, {
2321
- options,
2322
- resolver,
2323
- adapter,
2324
- config,
2325
- fabric,
2326
- Component: generator.Operation,
2327
- plugin,
2328
- driver
2329
- });
2330
- if (generator.type === "core" && generator.version === "2") {
2331
- const files = await generator.operation?.({
2332
- node,
2333
- options,
2334
- resolver,
2335
- adapter,
2336
- config,
2337
- plugin,
2338
- driver
2339
- }) ?? [];
2340
- await fabric.upsertFile(...files);
2341
- }
2342
- }
2343
- }
2344
- /**
2345
- * Batch-dispatches all collected operation nodes to every generator (react + core).
2346
- * Uses `plugin.options` directly — no per-node option resolution.
2347
- */
2348
- async function runGeneratorOperations(nodes, ctx) {
2349
- const { generators, plugin, resolver, fabric, adapter, config, driver } = ctx;
2350
- for (const generator of generators) {
2351
- if (generator.type === "react" && generator.version === "2") await renderOperations(nodes, {
2352
- options: plugin.options,
2353
- resolver,
2354
- adapter,
2355
- config,
2356
- fabric,
2357
- Component: generator.Operations,
2358
- plugin,
2359
- driver
2360
- });
2361
- if (generator.type === "core" && generator.version === "2") {
2362
- const files = await generator.operations?.({
2363
- nodes,
2364
- options: plugin.options,
2365
- resolver,
2366
- adapter,
2367
- config,
2368
- plugin,
2369
- driver
2370
- }) ?? [];
2371
- await fabric.upsertFile(...files);
2372
- }
2373
- }
2374
- }
2375
- //#endregion
2376
2582
  //#region src/storages/memoryStorage.ts
2377
2583
  /**
2378
2584
  * In-memory storage driver. Useful for testing and dry-run scenarios where
@@ -2536,241 +2742,6 @@ async function detectFormatter() {
2536
2742
  return null;
2537
2743
  }
2538
2744
  //#endregion
2539
- //#region src/utils/TreeNode.ts
2540
- /**
2541
- * Tree structure used to build per-directory barrel (`index.ts`) files from a
2542
- * flat list of generated {@link FabricFile.File} entries.
2543
- *
2544
- * Each node represents either a directory or a file within the output tree.
2545
- * Use {@link TreeNode.build} to construct a root node from a file list, then
2546
- * traverse with {@link TreeNode.forEach}, {@link TreeNode.leaves}, or the
2547
- * `*Deep` helpers.
2548
- */
2549
- var TreeNode = class TreeNode {
2550
- data;
2551
- parent;
2552
- children = [];
2553
- #cachedLeaves = void 0;
2554
- constructor(data, parent) {
2555
- this.data = data;
2556
- this.parent = parent;
2557
- }
2558
- addChild(data) {
2559
- const child = new TreeNode(data, this);
2560
- if (!this.children) this.children = [];
2561
- this.children.push(child);
2562
- return child;
2563
- }
2564
- /**
2565
- * Returns the root ancestor of this node, walking up via `parent` links.
2566
- */
2567
- get root() {
2568
- if (!this.parent) return this;
2569
- return this.parent.root;
2570
- }
2571
- /**
2572
- * Returns all leaf descendants (nodes with no children) of this node.
2573
- *
2574
- * Results are cached after the first traversal.
2575
- */
2576
- get leaves() {
2577
- if (!this.children || this.children.length === 0) return [this];
2578
- if (this.#cachedLeaves) return this.#cachedLeaves;
2579
- const leaves = [];
2580
- for (const child of this.children) leaves.push(...child.leaves);
2581
- this.#cachedLeaves = leaves;
2582
- return leaves;
2583
- }
2584
- /**
2585
- * Visits this node and every descendant in depth-first order.
2586
- */
2587
- forEach(callback) {
2588
- if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
2589
- callback(this);
2590
- for (const child of this.children) child.forEach(callback);
2591
- return this;
2592
- }
2593
- /**
2594
- * Finds the first leaf that satisfies `predicate`, or `undefined` when none match.
2595
- */
2596
- findDeep(predicate) {
2597
- if (typeof predicate !== "function") throw new TypeError("find() predicate must be a function");
2598
- return this.leaves.find(predicate);
2599
- }
2600
- /**
2601
- * Calls `callback` for every leaf of this node.
2602
- */
2603
- forEachDeep(callback) {
2604
- if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
2605
- this.leaves.forEach(callback);
2606
- }
2607
- /**
2608
- * Returns all leaves that satisfy `callback`.
2609
- */
2610
- filterDeep(callback) {
2611
- if (typeof callback !== "function") throw new TypeError("filter() callback must be a function");
2612
- return this.leaves.filter(callback);
2613
- }
2614
- /**
2615
- * Maps every leaf through `callback` and returns the resulting array.
2616
- */
2617
- mapDeep(callback) {
2618
- if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
2619
- return this.leaves.map(callback);
2620
- }
2621
- /**
2622
- * Builds a {@link TreeNode} tree from a flat list of files.
2623
- *
2624
- * - Filters to files under `root` (when provided) and skips `.json` files.
2625
- * - Returns `null` when no files match.
2626
- */
2627
- static build(files, root) {
2628
- try {
2629
- const filteredTree = buildDirectoryTree(files, root);
2630
- if (!filteredTree) return null;
2631
- const treeNode = new TreeNode({
2632
- name: filteredTree.name,
2633
- path: filteredTree.path,
2634
- file: filteredTree.file,
2635
- type: getMode(filteredTree.path)
2636
- });
2637
- const recurse = (node, item) => {
2638
- const subNode = node.addChild({
2639
- name: item.name,
2640
- path: item.path,
2641
- file: item.file,
2642
- type: getMode(item.path)
2643
- });
2644
- if (item.children?.length) item.children?.forEach((child) => {
2645
- recurse(subNode, child);
2646
- });
2647
- };
2648
- filteredTree.children?.forEach((child) => {
2649
- recurse(treeNode, child);
2650
- });
2651
- return treeNode;
2652
- } catch (error) {
2653
- throw new Error("Something went wrong with creating barrel files with the TreeNode class", { cause: error });
2654
- }
2655
- }
2656
- };
2657
- const normalizePath = (p) => p.replaceAll("\\", "/");
2658
- function buildDirectoryTree(files, rootFolder = "") {
2659
- const normalizedRootFolder = normalizePath(rootFolder);
2660
- const rootPrefix = normalizedRootFolder.endsWith("/") ? normalizedRootFolder : `${normalizedRootFolder}/`;
2661
- const filteredFiles = files.filter((file) => {
2662
- const normalizedFilePath = normalizePath(file.path);
2663
- return rootFolder ? normalizedFilePath.startsWith(rootPrefix) && !normalizedFilePath.endsWith(".json") : !normalizedFilePath.endsWith(".json");
2664
- });
2665
- if (filteredFiles.length === 0) return null;
2666
- const root = {
2667
- name: rootFolder || "",
2668
- path: rootFolder || "",
2669
- children: []
2670
- };
2671
- filteredFiles.forEach((file) => {
2672
- const parts = file.path.slice(rootFolder.length).split("/").filter(Boolean);
2673
- let currentLevel = root.children;
2674
- let currentPath = normalizePath(rootFolder);
2675
- parts.forEach((part, index) => {
2676
- currentPath = node_path.default.posix.join(currentPath, part);
2677
- let existingNode = currentLevel.find((node) => node.name === part);
2678
- if (!existingNode) {
2679
- if (index === parts.length - 1) existingNode = {
2680
- name: part,
2681
- file,
2682
- path: currentPath
2683
- };
2684
- else existingNode = {
2685
- name: part,
2686
- path: currentPath,
2687
- children: []
2688
- };
2689
- currentLevel.push(existingNode);
2690
- }
2691
- if (!existingNode.file) currentLevel = existingNode.children;
2692
- });
2693
- });
2694
- return root;
2695
- }
2696
- //#endregion
2697
- //#region src/utils/getBarrelFiles.ts
2698
- /** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
2699
- function getBarrelFilesByRoot(root, files) {
2700
- const cachedFiles = /* @__PURE__ */ new Map();
2701
- TreeNode.build(files, root)?.forEach((treeNode) => {
2702
- if (!treeNode?.children || !treeNode.parent?.data.path) return;
2703
- const barrelFile = {
2704
- path: (0, node_path.join)(treeNode.parent?.data.path, "index.ts"),
2705
- baseName: "index.ts",
2706
- exports: [],
2707
- imports: [],
2708
- sources: []
2709
- };
2710
- const previousBarrelFile = cachedFiles.get(barrelFile.path);
2711
- treeNode.leaves.forEach((item) => {
2712
- if (!item.data.name) return;
2713
- (item.data.file?.sources || []).forEach((source) => {
2714
- if (!item.data.file?.path || !source.isIndexable || !source.name) return;
2715
- if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
2716
- barrelFile.exports.push({
2717
- name: [source.name],
2718
- path: getRelativePath(treeNode.parent?.data.path, item.data.path),
2719
- isTypeOnly: source.isTypeOnly
2720
- });
2721
- barrelFile.sources.push({
2722
- name: source.name,
2723
- isTypeOnly: source.isTypeOnly,
2724
- value: "",
2725
- isExportable: false,
2726
- isIndexable: false
2727
- });
2728
- });
2729
- });
2730
- if (previousBarrelFile) {
2731
- previousBarrelFile.sources.push(...barrelFile.sources);
2732
- previousBarrelFile.exports?.push(...barrelFile.exports || []);
2733
- } else cachedFiles.set(barrelFile.path, barrelFile);
2734
- });
2735
- return [...cachedFiles.values()];
2736
- }
2737
- function trimExtName(text) {
2738
- const dotIndex = text.lastIndexOf(".");
2739
- if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
2740
- return text;
2741
- }
2742
- /**
2743
- * Generates `index.ts` barrel files for all directories under `root/output.path`.
2744
- *
2745
- * - Returns an empty array when `type` is falsy or `'propagate'`.
2746
- * - Skips generation when the output path itself ends with `index` (already a barrel).
2747
- * - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
2748
- * - Attaches `meta` to each barrel file for downstream plugin identification.
2749
- */
2750
- async function getBarrelFiles(files, { type, meta = {}, root, output }) {
2751
- if (!type || type === "propagate") return [];
2752
- const pathToBuildFrom = (0, node_path.join)(root, output.path);
2753
- if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
2754
- const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files);
2755
- if (type === "all") return barrelFiles.map((file) => {
2756
- return {
2757
- ...file,
2758
- exports: file.exports?.map((exportItem) => {
2759
- return {
2760
- ...exportItem,
2761
- name: void 0
2762
- };
2763
- })
2764
- };
2765
- });
2766
- return barrelFiles.map((indexFile) => {
2767
- return {
2768
- ...indexFile,
2769
- meta
2770
- };
2771
- });
2772
- }
2773
- //#endregion
2774
2745
  //#region src/utils/getConfigs.ts
2775
2746
  /**
2776
2747
  * Resolves a {@link ConfigInput} into a normalized array of {@link Config} objects.
@@ -2954,12 +2925,7 @@ exports.isInputPath = isInputPath;
2954
2925
  exports.linters = linters;
2955
2926
  exports.logLevel = logLevel;
2956
2927
  exports.memoryStorage = memoryStorage;
2957
- exports.renderOperation = renderOperation;
2958
- exports.renderOperations = renderOperations;
2959
- exports.renderSchema = renderSchema;
2960
- exports.runGeneratorOperation = runGeneratorOperation;
2961
- exports.runGeneratorOperations = runGeneratorOperations;
2962
- exports.runGeneratorSchema = runGeneratorSchema;
2928
+ exports.mergeGenerators = mergeGenerators;
2963
2929
  exports.safeBuild = safeBuild;
2964
2930
  exports.satisfiesDependency = satisfiesDependency;
2965
2931
  exports.setup = setup;