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