@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/{PluginDriver-C6VX0skO.d.ts → PluginDriver-D110FoJ-.d.ts} +265 -108
- package/dist/hooks.d.ts +1 -1
- package/dist/index.cjs +495 -529
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -80
- package/dist/index.js +496 -525
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/PluginDriver.ts +44 -10
- package/src/build.ts +87 -8
- package/src/createPlugin.ts +3 -1
- package/src/defineGenerator.ts +101 -134
- package/src/index.ts +1 -2
- package/src/renderNode.tsx +14 -186
- package/src/types.ts +213 -11
- package/src/utils/executeStrategies.ts +7 -1
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({
|
|
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
|
-
|
|
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
|
|
1013
|
-
const result =
|
|
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.
|
|
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/
|
|
1485
|
+
//#region src/utils/TreeNode.ts
|
|
1436
1486
|
/**
|
|
1437
|
-
*
|
|
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
|
-
*
|
|
1445
|
-
*
|
|
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
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
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
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
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
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
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
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
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
|
|
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: ["
|
|
1952
|
+
logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
|
|
1622
1953
|
});
|
|
1623
|
-
await
|
|
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
|
|
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
|
|
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
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
-
|
|
1821
|
-
async
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
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
|
|
1828
|
-
|
|
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
|
-
|
|
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,
|
|
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
|