@kubb/middleware-barrel 5.0.0-alpha.55
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/LICENSE +14 -0
- package/dist/chunk--u3MIqq1.js +8 -0
- package/dist/index.cjs +279 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +278 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
- package/src/constants.ts +9 -0
- package/src/index.ts +1 -0
- package/src/middleware.ts +87 -0
- package/src/types.ts +44 -0
- package/src/utils/TreeNode.ts +67 -0
- package/src/utils/generatePerPluginBarrel.ts +23 -0
- package/src/utils/generateRootBarrel.ts +25 -0
- package/src/utils/getBarrelFiles.ts +188 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Copyright (c) 2026 Stijn Van Hulle
|
|
2
|
+
|
|
3
|
+
This repository contains software under two licenses:
|
|
4
|
+
|
|
5
|
+
1. Most of the code in this repository is licensed under the
|
|
6
|
+
MIT License — see licenses/LICENSE-MIT for the full license text.
|
|
7
|
+
|
|
8
|
+
2. The following components are licensed under the
|
|
9
|
+
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)
|
|
10
|
+
— see licenses/LICENSE-AGPL-3.0 for the full license text:
|
|
11
|
+
|
|
12
|
+
- packages/agent (published as @kubb/agent)
|
|
13
|
+
|
|
14
|
+
Each package's own LICENSE file or package.json specifies its applicable license.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#endregion
|
|
3
|
+
let _kubb_core = require("@kubb/core");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
let _kubb_ast = require("@kubb/ast");
|
|
6
|
+
//#region src/constants.ts
|
|
7
|
+
/**
|
|
8
|
+
* Base file name for barrel files (without extension).
|
|
9
|
+
*/
|
|
10
|
+
const BARREL_BASENAME = "index";
|
|
11
|
+
/**
|
|
12
|
+
* Full file name for barrel files (with extension).
|
|
13
|
+
*/
|
|
14
|
+
const BARREL_FILENAME = "index.ts";
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/utils/TreeNode.ts
|
|
17
|
+
/**
|
|
18
|
+
* Builds a `TreeNode` directory tree from a list of absolute file paths.
|
|
19
|
+
*
|
|
20
|
+
* All `filePaths` must be inside `rootPath`. Paths that are outside
|
|
21
|
+
* the root or that equal the root are silently ignored.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const tree = buildTree('/src/gen/types', [
|
|
26
|
+
* '/src/gen/types/pet.ts',
|
|
27
|
+
* '/src/gen/types/user.ts',
|
|
28
|
+
* '/src/gen/types/pets/listPets.ts',
|
|
29
|
+
* ])
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function buildTree(rootPath, filePaths) {
|
|
33
|
+
const root = {
|
|
34
|
+
path: rootPath,
|
|
35
|
+
children: [],
|
|
36
|
+
isFile: false
|
|
37
|
+
};
|
|
38
|
+
for (const filePath of filePaths) {
|
|
39
|
+
if (!filePath.startsWith(rootPath + node_path.posix.sep) && !filePath.startsWith(rootPath + "/")) continue;
|
|
40
|
+
const parts = filePath.slice(rootPath.length).replace(/^\//g, "").replace(/^\\/g, "").split(/[/\\]/).filter(Boolean);
|
|
41
|
+
let current = root;
|
|
42
|
+
for (let i = 0; i < parts.length; i++) {
|
|
43
|
+
const isLast = i === parts.length - 1;
|
|
44
|
+
const part = parts[i];
|
|
45
|
+
const childPath = `${current.path}/${part}`;
|
|
46
|
+
let child = current.children.find((c) => c.path === childPath);
|
|
47
|
+
if (!child) {
|
|
48
|
+
child = {
|
|
49
|
+
path: childPath,
|
|
50
|
+
children: [],
|
|
51
|
+
isFile: isLast
|
|
52
|
+
};
|
|
53
|
+
current.children.push(child);
|
|
54
|
+
}
|
|
55
|
+
current = child;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return root;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/utils/getBarrelFiles.ts
|
|
62
|
+
/**
|
|
63
|
+
* Derives a relative module specifier (no extension) from an absolute `filePath`
|
|
64
|
+
* relative to an absolute `fromDir`.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* toRelativeModulePath('/src/gen/types', '/src/gen/types/pet.ts') // './pet'
|
|
68
|
+
* toRelativeModulePath('/src/gen/types', '/src/gen/types/tags/tag.ts') // './tags/tag'
|
|
69
|
+
*/
|
|
70
|
+
function toRelativeModulePath(fromDir, filePath) {
|
|
71
|
+
return `./${filePath.slice(fromDir.length).replace(/^[/\\]/g, "").replace(/\.[^/.]+$/, "")}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generates barrel `FileNode[]` for a given directory tree node using the `'all'` strategy:
|
|
75
|
+
* each leaf file gets a `export * from './relPath'` in the barrel of its nearest ancestor directory.
|
|
76
|
+
*
|
|
77
|
+
* Only a single barrel file (at `treeNode.path`) is generated — sub-directory files are referenced
|
|
78
|
+
* with their full relative path from `treeNode.path`.
|
|
79
|
+
*/
|
|
80
|
+
function getBarrelFilesAll(treeNode, _sourceFiles) {
|
|
81
|
+
const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`));
|
|
82
|
+
if (leafPaths.length === 0) return [];
|
|
83
|
+
return [(0, _kubb_ast.createFile)({
|
|
84
|
+
baseName: BARREL_FILENAME,
|
|
85
|
+
path: `${treeNode.path}/${BARREL_FILENAME}`,
|
|
86
|
+
exports: leafPaths.map((filePath) => (0, _kubb_ast.createExport)({ path: toRelativeModulePath(treeNode.path, filePath) })),
|
|
87
|
+
sources: [],
|
|
88
|
+
imports: []
|
|
89
|
+
})];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Generates barrel `FileNode[]` for a given directory tree node using the `'named'` strategy:
|
|
93
|
+
* each indexable source in each leaf file gets an individual named `export { name } from '...'`.
|
|
94
|
+
*/
|
|
95
|
+
function getBarrelFilesNamed(treeNode, sourceFiles) {
|
|
96
|
+
const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`));
|
|
97
|
+
if (leafPaths.length === 0) return [];
|
|
98
|
+
const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`;
|
|
99
|
+
const exports = [];
|
|
100
|
+
for (const filePath of leafPaths) {
|
|
101
|
+
const sourceFile = sourceFiles.find((f) => f.path === filePath);
|
|
102
|
+
if (!sourceFile) {
|
|
103
|
+
exports.push((0, _kubb_ast.createExport)({ path: toRelativeModulePath(treeNode.path, filePath) }));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const indexableSources = sourceFile.sources.filter((s) => s.isIndexable && s.name);
|
|
107
|
+
if (indexableSources.length === 0) {
|
|
108
|
+
exports.push((0, _kubb_ast.createExport)({ path: toRelativeModulePath(treeNode.path, filePath) }));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const names = indexableSources.map((s) => s.name);
|
|
112
|
+
exports.push((0, _kubb_ast.createExport)({
|
|
113
|
+
name: names,
|
|
114
|
+
path: toRelativeModulePath(treeNode.path, filePath)
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
if (exports.length === 0) return [];
|
|
118
|
+
return [(0, _kubb_ast.createFile)({
|
|
119
|
+
baseName: BARREL_FILENAME,
|
|
120
|
+
path: barrelPath,
|
|
121
|
+
exports,
|
|
122
|
+
sources: [],
|
|
123
|
+
imports: []
|
|
124
|
+
})];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Generates barrel `FileNode[]` for a given directory tree node using the `'propagate'` strategy:
|
|
128
|
+
* like `'all'` but also generates intermediate barrel files for every sub-directory, so that
|
|
129
|
+
* consumers can import from any depth.
|
|
130
|
+
*
|
|
131
|
+
* Leaf barrels export directly from their files; parent barrels export from their sub-barrel files.
|
|
132
|
+
*/
|
|
133
|
+
function getBarrelFilesPropagate(treeNode) {
|
|
134
|
+
return collectPropagatedBarrels(treeNode);
|
|
135
|
+
}
|
|
136
|
+
function collectPropagatedBarrels(node) {
|
|
137
|
+
const result = [];
|
|
138
|
+
const barrelExports = [];
|
|
139
|
+
for (const child of node.children) if (child.isFile) {
|
|
140
|
+
if (!child.path.endsWith(`/index.ts`)) barrelExports.push((0, _kubb_ast.createExport)({ path: toRelativeModulePath(node.path, child.path) }));
|
|
141
|
+
} else {
|
|
142
|
+
const subBarrels = collectPropagatedBarrels(child);
|
|
143
|
+
result.push(...subBarrels);
|
|
144
|
+
const subBarrelPath = `${child.path}/${BARREL_BASENAME}`;
|
|
145
|
+
barrelExports.push((0, _kubb_ast.createExport)({ path: toRelativeModulePath(node.path, subBarrelPath) }));
|
|
146
|
+
}
|
|
147
|
+
if (barrelExports.length > 0) result.push((0, _kubb_ast.createFile)({
|
|
148
|
+
baseName: BARREL_FILENAME,
|
|
149
|
+
path: `${node.path}/${BARREL_FILENAME}`,
|
|
150
|
+
exports: barrelExports,
|
|
151
|
+
sources: [],
|
|
152
|
+
imports: []
|
|
153
|
+
}));
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Collects all leaf (file) paths within a tree node recursively.
|
|
158
|
+
*/
|
|
159
|
+
function collectLeafPaths(node) {
|
|
160
|
+
if (node.isFile) return [node.path];
|
|
161
|
+
return node.children.flatMap((c) => collectLeafPaths(c));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Generates barrel `FileNode[]` for a directory rooted at `outputPath`, given the full set of
|
|
165
|
+
* generated source `files`, using the specified `barrelType` strategy.
|
|
166
|
+
*
|
|
167
|
+
* Files not located inside `outputPath` are excluded automatically.
|
|
168
|
+
*
|
|
169
|
+
* @param outputPath Absolute path to the output directory.
|
|
170
|
+
* @param files All generated files (across all plugins).
|
|
171
|
+
* @param barrelType Barrel generation strategy.
|
|
172
|
+
*/
|
|
173
|
+
function getBarrelFiles(outputPath, files, barrelType) {
|
|
174
|
+
const relevantFiles = files.filter((f) => {
|
|
175
|
+
const normalizedFilePath = f.path.replace(/\\/g, "/");
|
|
176
|
+
const normalizedOutputPath = outputPath.replace(/\\/g, "/");
|
|
177
|
+
return normalizedFilePath.startsWith(normalizedOutputPath + "/") && !normalizedFilePath.endsWith(`/index.ts`);
|
|
178
|
+
});
|
|
179
|
+
if (relevantFiles.length === 0) return [];
|
|
180
|
+
const tree = buildTree(outputPath, relevantFiles.map((f) => f.path));
|
|
181
|
+
switch (barrelType) {
|
|
182
|
+
case "all": return getBarrelFilesAll(tree, relevantFiles);
|
|
183
|
+
case "named": return getBarrelFilesNamed(tree, relevantFiles);
|
|
184
|
+
case "propagate": return getBarrelFilesPropagate(tree);
|
|
185
|
+
default: return [];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/utils/generatePerPluginBarrel.ts
|
|
190
|
+
/**
|
|
191
|
+
* Generates barrel files for a single plugin's output directory.
|
|
192
|
+
*
|
|
193
|
+
* The barrel file is placed at `resolve(config.root, plugin.options.output.path)/index.ts`
|
|
194
|
+
* and re-exports all files generated by that plugin, using the given `barrelType` strategy.
|
|
195
|
+
*/
|
|
196
|
+
function generatePerPluginBarrel({ barrelType, plugin, files, config }) {
|
|
197
|
+
return getBarrelFiles((0, node_path.resolve)(config.root, plugin.options.output.path), files, barrelType);
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/utils/generateRootBarrel.ts
|
|
201
|
+
/**
|
|
202
|
+
* Generates a root barrel file at `resolve(config.root, config.output.path)/index.ts`.
|
|
203
|
+
*
|
|
204
|
+
* The root barrel re-exports from all files across all plugins that are located
|
|
205
|
+
* inside the root output directory, using the given `barrelType` strategy.
|
|
206
|
+
*
|
|
207
|
+
* In practice this re-exports the per-plugin barrels when `barrelType = 'propagate'`,
|
|
208
|
+
* or all individual files when `barrelType = 'all'` or `'named'`.
|
|
209
|
+
*/
|
|
210
|
+
function generateRootBarrel({ barrelType, files, config }) {
|
|
211
|
+
return getBarrelFiles((0, node_path.resolve)(config.root, config.output.path), files, barrelType);
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/middleware.ts
|
|
215
|
+
/**
|
|
216
|
+
* Barrel-file generation middleware.
|
|
217
|
+
*
|
|
218
|
+
* When added to `config.middleware`, generates an `index.ts` barrel file for each
|
|
219
|
+
* plugin that has `output.barrelType` set (or inherits it from `config.output.barrelType`),
|
|
220
|
+
* as well as a root `index.ts` at `config.output.path` when `config.output.barrelType` is set.
|
|
221
|
+
*
|
|
222
|
+
* The `barrelType` option controls the re-export style:
|
|
223
|
+
* - `'all'` — `export * from '...'` for each generated file
|
|
224
|
+
* - `'named'` — `export { name1, name2 } from '...'` using each file's indexable sources
|
|
225
|
+
* - `'propagate'` — like `'all'` with intermediate barrel files for sub-directories
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```ts
|
|
229
|
+
* import { middlewareBarrel } from '@kubb/middleware-barrel'
|
|
230
|
+
*
|
|
231
|
+
* export default defineConfig({
|
|
232
|
+
* output: { path: 'src/gen', barrelType: 'named' },
|
|
233
|
+
* plugins: [
|
|
234
|
+
* pluginTs({ output: { path: 'types', barrelType: 'all' } }),
|
|
235
|
+
* pluginZod({ output: { path: 'schemas' } }),
|
|
236
|
+
* ],
|
|
237
|
+
* middleware: [middlewareBarrel],
|
|
238
|
+
* })
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
const middlewareBarrel = (0, _kubb_core.defineMiddleware)({
|
|
242
|
+
name: "middleware-barrel",
|
|
243
|
+
install(hooks) {
|
|
244
|
+
let ctx;
|
|
245
|
+
hooks.on("kubb:build:start", (buildCtx) => {
|
|
246
|
+
ctx = buildCtx;
|
|
247
|
+
});
|
|
248
|
+
hooks.on("kubb:plugin:end", ({ plugin }) => {
|
|
249
|
+
if (!ctx) return;
|
|
250
|
+
const normalizedPlugin = plugin;
|
|
251
|
+
const pluginOutput = normalizedPlugin.options.output;
|
|
252
|
+
const rootOutput = ctx.config.output;
|
|
253
|
+
const barrelType = pluginOutput?.barrelType !== void 0 ? pluginOutput.barrelType : rootOutput.barrelType;
|
|
254
|
+
if (!barrelType) return;
|
|
255
|
+
const barrelFiles = generatePerPluginBarrel({
|
|
256
|
+
barrelType,
|
|
257
|
+
plugin: normalizedPlugin,
|
|
258
|
+
files: ctx.files,
|
|
259
|
+
config: ctx.config
|
|
260
|
+
});
|
|
261
|
+
if (barrelFiles.length > 0) ctx.upsertFile(...barrelFiles);
|
|
262
|
+
});
|
|
263
|
+
hooks.on("kubb:build:end", () => {
|
|
264
|
+
if (!ctx) return;
|
|
265
|
+
const rootBarrelType = ctx.config.output.barrelType;
|
|
266
|
+
if (!rootBarrelType) return;
|
|
267
|
+
const rootBarrelFiles = generateRootBarrel({
|
|
268
|
+
barrelType: rootBarrelType,
|
|
269
|
+
files: ctx.files,
|
|
270
|
+
config: ctx.config
|
|
271
|
+
});
|
|
272
|
+
if (rootBarrelFiles.length > 0) ctx.upsertFile(...rootBarrelFiles);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
//#endregion
|
|
277
|
+
exports.middlewareBarrel = middlewareBarrel;
|
|
278
|
+
|
|
279
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["posix"],"sources":["../src/constants.ts","../src/utils/TreeNode.ts","../src/utils/getBarrelFiles.ts","../src/utils/generatePerPluginBarrel.ts","../src/utils/generateRootBarrel.ts","../src/middleware.ts"],"sourcesContent":["/**\n * Base file name for barrel files (without extension).\n */\nexport const BARREL_BASENAME = 'index' as const\n\n/**\n * Full file name for barrel files (with extension).\n */\nexport const BARREL_FILENAME = 'index.ts' as const\n","import { posix } from 'node:path'\n\n/**\n * A node in a directory tree used to compute barrel file exports.\n *\n * Each `TreeNode` represents either a directory or a file entry.\n * Directory nodes have `children`; file nodes have an empty `children` array.\n */\nexport type TreeNode = {\n /**\n * Absolute path of the directory (root of this subtree) or file.\n */\n path: string\n /**\n * Child nodes (sub-directories and files) within this directory.\n */\n children: Array<TreeNode>\n /**\n * `true` when this node represents a file (leaf node).\n */\n isFile: boolean\n}\n\n/**\n * Builds a `TreeNode` directory tree from a list of absolute file paths.\n *\n * All `filePaths` must be inside `rootPath`. Paths that are outside\n * the root or that equal the root are silently ignored.\n *\n * @example\n * ```ts\n * const tree = buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/user.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): TreeNode {\n const root: TreeNode = { path: rootPath, children: [], isFile: false }\n\n for (const filePath of filePaths) {\n // Only include files inside rootPath\n if (!filePath.startsWith(rootPath + posix.sep) && !filePath.startsWith(rootPath + '/')) {\n continue\n }\n\n const relative = filePath.slice(rootPath.length).replace(/^\\//g, '').replace(/^\\\\/g, '')\n const parts = relative.split(/[/\\\\]/).filter(Boolean)\n\n let current = root\n for (let i = 0; i < parts.length; i++) {\n const isLast = i === parts.length - 1\n const part = parts[i]!\n const childPath = `${current.path}/${part}`\n\n let child = current.children.find((c) => c.path === childPath)\n if (!child) {\n child = { path: childPath, children: [], isFile: isLast }\n current.children.push(child)\n }\n current = child\n }\n }\n\n return root\n}\n","import { createExport, createFile } from '@kubb/ast'\nimport type { FileNode } from '@kubb/ast'\nimport { BARREL_BASENAME, BARREL_FILENAME } from '../constants.ts'\nimport type { BarrelType } from '../types.ts'\nimport { buildTree, type TreeNode } from './TreeNode.ts'\n\n/**\n * Derives a relative module specifier (no extension) from an absolute `filePath`\n * relative to an absolute `fromDir`.\n *\n * @example\n * toRelativeModulePath('/src/gen/types', '/src/gen/types/pet.ts') // './pet'\n * toRelativeModulePath('/src/gen/types', '/src/gen/types/tags/tag.ts') // './tags/tag'\n */\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n const relative = filePath.slice(fromDir.length).replace(/^[/\\\\]/g, '')\n // Strip extension\n const withoutExt = relative.replace(/\\.[^/.]+$/, '')\n return `./${withoutExt}`\n}\n\n/**\n * Generates barrel `FileNode[]` for a given directory tree node using the `'all'` strategy:\n * each leaf file gets a `export * from './relPath'` in the barrel of its nearest ancestor directory.\n *\n * Only a single barrel file (at `treeNode.path`) is generated — sub-directory files are referenced\n * with their full relative path from `treeNode.path`.\n */\nfunction getBarrelFilesAll(treeNode: TreeNode, _sourceFiles: ReadonlyArray<FileNode>): Array<FileNode> {\n // Collect all source file paths under this node (excluding barrel files themselves)\n const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))\n\n if (leafPaths.length === 0) return []\n\n const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`\n const exports = leafPaths.map((filePath) =>\n createExport({\n path: toRelativeModulePath(treeNode.path, filePath),\n }),\n )\n\n return [\n createFile({\n baseName: BARREL_FILENAME,\n path: barrelPath,\n exports,\n sources: [],\n imports: [],\n }),\n ]\n}\n\n/**\n * Generates barrel `FileNode[]` for a given directory tree node using the `'named'` strategy:\n * each indexable source in each leaf file gets an individual named `export { name } from '...'`.\n */\nfunction getBarrelFilesNamed(treeNode: TreeNode, sourceFiles: ReadonlyArray<FileNode>): Array<FileNode> {\n const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))\n\n if (leafPaths.length === 0) return []\n\n const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`\n const exports: ReturnType<typeof createExport>[] = []\n\n for (const filePath of leafPaths) {\n const sourceFile = sourceFiles.find((f) => f.path === filePath)\n if (!sourceFile) {\n // Fall back to wildcard if the source file is not in our set\n exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))\n continue\n }\n\n const indexableSources = sourceFile.sources.filter((s) => s.isIndexable && s.name)\n if (indexableSources.length === 0) {\n // No named exports: fall back to wildcard\n exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))\n continue\n }\n\n const names = indexableSources.map((s) => s.name as string)\n exports.push(\n createExport({\n name: names,\n path: toRelativeModulePath(treeNode.path, filePath),\n }),\n )\n }\n\n if (exports.length === 0) return []\n\n return [\n createFile({\n baseName: BARREL_FILENAME,\n path: barrelPath,\n exports,\n sources: [],\n imports: [],\n }),\n ]\n}\n\n/**\n * Generates barrel `FileNode[]` for a given directory tree node using the `'propagate'` strategy:\n * like `'all'` but also generates intermediate barrel files for every sub-directory, so that\n * consumers can import from any depth.\n *\n * Leaf barrels export directly from their files; parent barrels export from their sub-barrel files.\n */\nfunction getBarrelFilesPropagate(treeNode: TreeNode): Array<FileNode> {\n return collectPropagatedBarrels(treeNode)\n}\n\nfunction collectPropagatedBarrels(node: TreeNode): Array<FileNode> {\n const result: Array<FileNode> = []\n const barrelExports: ReturnType<typeof createExport>[] = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (!child.path.endsWith(`/${BARREL_FILENAME}`)) {\n barrelExports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }))\n }\n } else {\n // Recurse into sub-directory\n const subBarrels = collectPropagatedBarrels(child)\n result.push(...subBarrels)\n\n // Export the sub-directory's barrel (not individual files)\n const subBarrelPath = `${child.path}/${BARREL_BASENAME}`\n barrelExports.push(createExport({ path: toRelativeModulePath(node.path, subBarrelPath) }))\n }\n }\n\n if (barrelExports.length > 0) {\n result.push(\n createFile({\n baseName: BARREL_FILENAME,\n path: `${node.path}/${BARREL_FILENAME}`,\n exports: barrelExports,\n sources: [],\n imports: [],\n }),\n )\n }\n\n return result\n}\n\n/**\n * Collects all leaf (file) paths within a tree node recursively.\n */\nfunction collectLeafPaths(node: TreeNode): Array<string> {\n if (node.isFile) return [node.path]\n return node.children.flatMap((c) => collectLeafPaths(c))\n}\n\n/**\n * Generates barrel `FileNode[]` for a directory rooted at `outputPath`, given the full set of\n * generated source `files`, using the specified `barrelType` strategy.\n *\n * Files not located inside `outputPath` are excluded automatically.\n *\n * @param outputPath Absolute path to the output directory.\n * @param files All generated files (across all plugins).\n * @param barrelType Barrel generation strategy.\n */\nexport function getBarrelFiles(outputPath: string, files: ReadonlyArray<FileNode>, barrelType: BarrelType): Array<FileNode> {\n // Only include files that live inside this outputPath\n const relevantFiles = files.filter((f) => {\n const normalizedFilePath = f.path.replace(/\\\\/g, '/')\n const normalizedOutputPath = outputPath.replace(/\\\\/g, '/')\n return normalizedFilePath.startsWith(normalizedOutputPath + '/') && !normalizedFilePath.endsWith(`/${BARREL_FILENAME}`)\n })\n\n if (relevantFiles.length === 0) return []\n\n const tree = buildTree(outputPath, relevantFiles.map((f) => f.path))\n\n switch (barrelType) {\n case 'all':\n return getBarrelFilesAll(tree, relevantFiles)\n case 'named':\n return getBarrelFilesNamed(tree, relevantFiles)\n case 'propagate':\n return getBarrelFilesPropagate(tree)\n default:\n return []\n }\n}\n","import { resolve } from 'node:path'\nimport type { FileNode } from '@kubb/ast'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport type { BarrelType } from '../types.ts'\nimport { getBarrelFiles } from './getBarrelFiles.ts'\n\nexport type GeneratePerPluginBarrelParams = {\n barrelType: BarrelType\n plugin: NormalizedPlugin\n files: ReadonlyArray<FileNode>\n config: Config\n}\n\n/**\n * Generates barrel files for a single plugin's output directory.\n *\n * The barrel file is placed at `resolve(config.root, plugin.options.output.path)/index.ts`\n * and re-exports all files generated by that plugin, using the given `barrelType` strategy.\n */\nexport function generatePerPluginBarrel({ barrelType, plugin, files, config }: GeneratePerPluginBarrelParams): Array<FileNode> {\n const outputPath = resolve(config.root, plugin.options.output.path)\n return getBarrelFiles(outputPath, files, barrelType)\n}\n","import { resolve } from 'node:path'\nimport type { FileNode } from '@kubb/ast'\nimport type { Config } from '@kubb/core'\nimport type { BarrelType } from '../types.ts'\nimport { getBarrelFiles } from './getBarrelFiles.ts'\n\nexport type GenerateRootBarrelParams = {\n barrelType: BarrelType\n files: ReadonlyArray<FileNode>\n config: Config\n}\n\n/**\n * Generates a root barrel file at `resolve(config.root, config.output.path)/index.ts`.\n *\n * The root barrel re-exports from all files across all plugins that are located\n * inside the root output directory, using the given `barrelType` strategy.\n *\n * In practice this re-exports the per-plugin barrels when `barrelType = 'propagate'`,\n * or all individual files when `barrelType = 'all'` or `'named'`.\n */\nexport function generateRootBarrel({ barrelType, files, config }: GenerateRootBarrelParams): Array<FileNode> {\n const outputPath = resolve(config.root, config.output.path)\n return getBarrelFiles(outputPath, files, barrelType)\n}\n","import { defineMiddleware } from '@kubb/core'\nimport type { KubbBuildStartContext, NormalizedPlugin } from '@kubb/core'\nimport './types.ts'\nimport type { BarrelType } from './types.ts'\nimport { generatePerPluginBarrel } from './utils/generatePerPluginBarrel.ts'\nimport { generateRootBarrel } from './utils/generateRootBarrel.ts'\n\n/**\n * Barrel-file generation middleware.\n *\n * When added to `config.middleware`, generates an `index.ts` barrel file for each\n * plugin that has `output.barrelType` set (or inherits it from `config.output.barrelType`),\n * as well as a root `index.ts` at `config.output.path` when `config.output.barrelType` is set.\n *\n * The `barrelType` option controls the re-export style:\n * - `'all'` — `export * from '...'` for each generated file\n * - `'named'` — `export { name1, name2 } from '...'` using each file's indexable sources\n * - `'propagate'` — like `'all'` with intermediate barrel files for sub-directories\n *\n * @example\n * ```ts\n * import { middlewareBarrel } from '@kubb/middleware-barrel'\n *\n * export default defineConfig({\n * output: { path: 'src/gen', barrelType: 'named' },\n * plugins: [\n * pluginTs({ output: { path: 'types', barrelType: 'all' } }),\n * pluginZod({ output: { path: 'schemas' } }),\n * ],\n * middleware: [middlewareBarrel],\n * })\n * ```\n */\nexport const middlewareBarrel = defineMiddleware({\n name: 'middleware-barrel',\n\n install(hooks) {\n let ctx: KubbBuildStartContext\n\n hooks.on('kubb:build:start', (buildCtx) => {\n ctx = buildCtx\n })\n\n hooks.on('kubb:plugin:end', ({ plugin }) => {\n if (!ctx) return\n\n // At runtime the plugin in kubb:plugin:end is always a NormalizedPlugin;\n // KubbPluginEndContext types it as Plugin for public API simplicity.\n const normalizedPlugin = plugin as NormalizedPlugin\n const pluginOutput = normalizedPlugin.options.output as { barrelType?: BarrelType | false } | undefined\n const rootOutput = ctx.config.output as { barrelType?: BarrelType | false }\n const barrelType = pluginOutput?.barrelType !== undefined ? pluginOutput.barrelType : rootOutput.barrelType\n\n if (!barrelType) return\n\n const barrelFiles = generatePerPluginBarrel({\n barrelType,\n plugin: normalizedPlugin,\n files: ctx.files,\n config: ctx.config,\n })\n\n if (barrelFiles.length > 0) {\n ctx.upsertFile(...barrelFiles)\n }\n })\n\n hooks.on('kubb:build:end', () => {\n if (!ctx) return\n\n const rootOutput = ctx.config.output as { barrelType?: BarrelType | false }\n const rootBarrelType = rootOutput.barrelType\n\n if (!rootBarrelType) return\n\n const rootBarrelFiles = generateRootBarrel({\n barrelType: rootBarrelType,\n files: ctx.files,\n config: ctx.config,\n })\n\n if (rootBarrelFiles.length > 0) {\n ctx.upsertFile(...rootBarrelFiles)\n }\n })\n },\n})\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { t as __name } from "./chunk--u3MIqq1.js";
|
|
2
|
+
import * as _$_kubb_core0 from "@kubb/core";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The barrel type controls the style of re-exports generated in barrel files.
|
|
7
|
+
*
|
|
8
|
+
* - `'all'` — `export * from '...'` (re-export everything)
|
|
9
|
+
* - `'named'` — `export { name1, name2 } from '...'` (named re-exports only)
|
|
10
|
+
* - `'propagate'` — like `'all'` but also generates intermediate barrel files for
|
|
11
|
+
* every sub-directory so that consumers can import from any depth.
|
|
12
|
+
*/
|
|
13
|
+
type BarrelType = 'all' | 'named' | 'propagate';
|
|
14
|
+
declare global {
|
|
15
|
+
namespace Kubb {
|
|
16
|
+
interface PluginOptionsRegistry {
|
|
17
|
+
output: {
|
|
18
|
+
/**
|
|
19
|
+
* Controls which barrel file (index.ts) is generated for this plugin's output directory.
|
|
20
|
+
*
|
|
21
|
+
* - `'all'` — `export * from '...'` for every generated file.
|
|
22
|
+
* - `'named'` — `export { … } from '...'` using the file's named exports.
|
|
23
|
+
* - `'propagate'` — like `'all'` but also generates intermediate barrel files.
|
|
24
|
+
* - `false` — disable barrel generation for this plugin.
|
|
25
|
+
*
|
|
26
|
+
* When omitted, the root `config.output.barrelType` is used as the default.
|
|
27
|
+
*/
|
|
28
|
+
barrelType?: BarrelType | false;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
interface ConfigOptionsRegistry {
|
|
32
|
+
output: {
|
|
33
|
+
/**
|
|
34
|
+
* Controls the root barrel file (index.ts) generated at `config.output.path`.
|
|
35
|
+
*
|
|
36
|
+
* - `'all'` — `export * from '...'` for every plugin's barrel.
|
|
37
|
+
* - `'named'` — `export { … } from '...'` using the barrel's named exports.
|
|
38
|
+
* - `'propagate'` — like `'all'` but also generates intermediate barrel files.
|
|
39
|
+
* - `false` — disable root barrel generation.
|
|
40
|
+
*
|
|
41
|
+
* Individual plugins can override this via their own `output.barrelType`.
|
|
42
|
+
*/
|
|
43
|
+
barrelType?: BarrelType | false;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/middleware.d.ts
|
|
50
|
+
/**
|
|
51
|
+
* Barrel-file generation middleware.
|
|
52
|
+
*
|
|
53
|
+
* When added to `config.middleware`, generates an `index.ts` barrel file for each
|
|
54
|
+
* plugin that has `output.barrelType` set (or inherits it from `config.output.barrelType`),
|
|
55
|
+
* as well as a root `index.ts` at `config.output.path` when `config.output.barrelType` is set.
|
|
56
|
+
*
|
|
57
|
+
* The `barrelType` option controls the re-export style:
|
|
58
|
+
* - `'all'` — `export * from '...'` for each generated file
|
|
59
|
+
* - `'named'` — `export { name1, name2 } from '...'` using each file's indexable sources
|
|
60
|
+
* - `'propagate'` — like `'all'` with intermediate barrel files for sub-directories
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { middlewareBarrel } from '@kubb/middleware-barrel'
|
|
65
|
+
*
|
|
66
|
+
* export default defineConfig({
|
|
67
|
+
* output: { path: 'src/gen', barrelType: 'named' },
|
|
68
|
+
* plugins: [
|
|
69
|
+
* pluginTs({ output: { path: 'types', barrelType: 'all' } }),
|
|
70
|
+
* pluginZod({ output: { path: 'schemas' } }),
|
|
71
|
+
* ],
|
|
72
|
+
* middleware: [middlewareBarrel],
|
|
73
|
+
* })
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
declare const middlewareBarrel: _$_kubb_core0.Middleware;
|
|
77
|
+
//#endregion
|
|
78
|
+
export { middlewareBarrel };
|
|
79
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import "./chunk--u3MIqq1.js";
|
|
2
|
+
import { defineMiddleware } from "@kubb/core";
|
|
3
|
+
import { posix, resolve } from "node:path";
|
|
4
|
+
import { createExport, createFile } from "@kubb/ast";
|
|
5
|
+
//#region src/constants.ts
|
|
6
|
+
/**
|
|
7
|
+
* Base file name for barrel files (without extension).
|
|
8
|
+
*/
|
|
9
|
+
const BARREL_BASENAME = "index";
|
|
10
|
+
/**
|
|
11
|
+
* Full file name for barrel files (with extension).
|
|
12
|
+
*/
|
|
13
|
+
const BARREL_FILENAME = "index.ts";
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/utils/TreeNode.ts
|
|
16
|
+
/**
|
|
17
|
+
* Builds a `TreeNode` directory tree from a list of absolute file paths.
|
|
18
|
+
*
|
|
19
|
+
* All `filePaths` must be inside `rootPath`. Paths that are outside
|
|
20
|
+
* the root or that equal the root are silently ignored.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const tree = buildTree('/src/gen/types', [
|
|
25
|
+
* '/src/gen/types/pet.ts',
|
|
26
|
+
* '/src/gen/types/user.ts',
|
|
27
|
+
* '/src/gen/types/pets/listPets.ts',
|
|
28
|
+
* ])
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function buildTree(rootPath, filePaths) {
|
|
32
|
+
const root = {
|
|
33
|
+
path: rootPath,
|
|
34
|
+
children: [],
|
|
35
|
+
isFile: false
|
|
36
|
+
};
|
|
37
|
+
for (const filePath of filePaths) {
|
|
38
|
+
if (!filePath.startsWith(rootPath + posix.sep) && !filePath.startsWith(rootPath + "/")) continue;
|
|
39
|
+
const parts = filePath.slice(rootPath.length).replace(/^\//g, "").replace(/^\\/g, "").split(/[/\\]/).filter(Boolean);
|
|
40
|
+
let current = root;
|
|
41
|
+
for (let i = 0; i < parts.length; i++) {
|
|
42
|
+
const isLast = i === parts.length - 1;
|
|
43
|
+
const part = parts[i];
|
|
44
|
+
const childPath = `${current.path}/${part}`;
|
|
45
|
+
let child = current.children.find((c) => c.path === childPath);
|
|
46
|
+
if (!child) {
|
|
47
|
+
child = {
|
|
48
|
+
path: childPath,
|
|
49
|
+
children: [],
|
|
50
|
+
isFile: isLast
|
|
51
|
+
};
|
|
52
|
+
current.children.push(child);
|
|
53
|
+
}
|
|
54
|
+
current = child;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return root;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/utils/getBarrelFiles.ts
|
|
61
|
+
/**
|
|
62
|
+
* Derives a relative module specifier (no extension) from an absolute `filePath`
|
|
63
|
+
* relative to an absolute `fromDir`.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* toRelativeModulePath('/src/gen/types', '/src/gen/types/pet.ts') // './pet'
|
|
67
|
+
* toRelativeModulePath('/src/gen/types', '/src/gen/types/tags/tag.ts') // './tags/tag'
|
|
68
|
+
*/
|
|
69
|
+
function toRelativeModulePath(fromDir, filePath) {
|
|
70
|
+
return `./${filePath.slice(fromDir.length).replace(/^[/\\]/g, "").replace(/\.[^/.]+$/, "")}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Generates barrel `FileNode[]` for a given directory tree node using the `'all'` strategy:
|
|
74
|
+
* each leaf file gets a `export * from './relPath'` in the barrel of its nearest ancestor directory.
|
|
75
|
+
*
|
|
76
|
+
* Only a single barrel file (at `treeNode.path`) is generated — sub-directory files are referenced
|
|
77
|
+
* with their full relative path from `treeNode.path`.
|
|
78
|
+
*/
|
|
79
|
+
function getBarrelFilesAll(treeNode, _sourceFiles) {
|
|
80
|
+
const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`));
|
|
81
|
+
if (leafPaths.length === 0) return [];
|
|
82
|
+
return [createFile({
|
|
83
|
+
baseName: BARREL_FILENAME,
|
|
84
|
+
path: `${treeNode.path}/${BARREL_FILENAME}`,
|
|
85
|
+
exports: leafPaths.map((filePath) => createExport({ path: toRelativeModulePath(treeNode.path, filePath) })),
|
|
86
|
+
sources: [],
|
|
87
|
+
imports: []
|
|
88
|
+
})];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Generates barrel `FileNode[]` for a given directory tree node using the `'named'` strategy:
|
|
92
|
+
* each indexable source in each leaf file gets an individual named `export { name } from '...'`.
|
|
93
|
+
*/
|
|
94
|
+
function getBarrelFilesNamed(treeNode, sourceFiles) {
|
|
95
|
+
const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`));
|
|
96
|
+
if (leafPaths.length === 0) return [];
|
|
97
|
+
const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`;
|
|
98
|
+
const exports = [];
|
|
99
|
+
for (const filePath of leafPaths) {
|
|
100
|
+
const sourceFile = sourceFiles.find((f) => f.path === filePath);
|
|
101
|
+
if (!sourceFile) {
|
|
102
|
+
exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }));
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const indexableSources = sourceFile.sources.filter((s) => s.isIndexable && s.name);
|
|
106
|
+
if (indexableSources.length === 0) {
|
|
107
|
+
exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const names = indexableSources.map((s) => s.name);
|
|
111
|
+
exports.push(createExport({
|
|
112
|
+
name: names,
|
|
113
|
+
path: toRelativeModulePath(treeNode.path, filePath)
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
if (exports.length === 0) return [];
|
|
117
|
+
return [createFile({
|
|
118
|
+
baseName: BARREL_FILENAME,
|
|
119
|
+
path: barrelPath,
|
|
120
|
+
exports,
|
|
121
|
+
sources: [],
|
|
122
|
+
imports: []
|
|
123
|
+
})];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Generates barrel `FileNode[]` for a given directory tree node using the `'propagate'` strategy:
|
|
127
|
+
* like `'all'` but also generates intermediate barrel files for every sub-directory, so that
|
|
128
|
+
* consumers can import from any depth.
|
|
129
|
+
*
|
|
130
|
+
* Leaf barrels export directly from their files; parent barrels export from their sub-barrel files.
|
|
131
|
+
*/
|
|
132
|
+
function getBarrelFilesPropagate(treeNode) {
|
|
133
|
+
return collectPropagatedBarrels(treeNode);
|
|
134
|
+
}
|
|
135
|
+
function collectPropagatedBarrels(node) {
|
|
136
|
+
const result = [];
|
|
137
|
+
const barrelExports = [];
|
|
138
|
+
for (const child of node.children) if (child.isFile) {
|
|
139
|
+
if (!child.path.endsWith(`/index.ts`)) barrelExports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }));
|
|
140
|
+
} else {
|
|
141
|
+
const subBarrels = collectPropagatedBarrels(child);
|
|
142
|
+
result.push(...subBarrels);
|
|
143
|
+
const subBarrelPath = `${child.path}/${BARREL_BASENAME}`;
|
|
144
|
+
barrelExports.push(createExport({ path: toRelativeModulePath(node.path, subBarrelPath) }));
|
|
145
|
+
}
|
|
146
|
+
if (barrelExports.length > 0) result.push(createFile({
|
|
147
|
+
baseName: BARREL_FILENAME,
|
|
148
|
+
path: `${node.path}/${BARREL_FILENAME}`,
|
|
149
|
+
exports: barrelExports,
|
|
150
|
+
sources: [],
|
|
151
|
+
imports: []
|
|
152
|
+
}));
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Collects all leaf (file) paths within a tree node recursively.
|
|
157
|
+
*/
|
|
158
|
+
function collectLeafPaths(node) {
|
|
159
|
+
if (node.isFile) return [node.path];
|
|
160
|
+
return node.children.flatMap((c) => collectLeafPaths(c));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Generates barrel `FileNode[]` for a directory rooted at `outputPath`, given the full set of
|
|
164
|
+
* generated source `files`, using the specified `barrelType` strategy.
|
|
165
|
+
*
|
|
166
|
+
* Files not located inside `outputPath` are excluded automatically.
|
|
167
|
+
*
|
|
168
|
+
* @param outputPath Absolute path to the output directory.
|
|
169
|
+
* @param files All generated files (across all plugins).
|
|
170
|
+
* @param barrelType Barrel generation strategy.
|
|
171
|
+
*/
|
|
172
|
+
function getBarrelFiles(outputPath, files, barrelType) {
|
|
173
|
+
const relevantFiles = files.filter((f) => {
|
|
174
|
+
const normalizedFilePath = f.path.replace(/\\/g, "/");
|
|
175
|
+
const normalizedOutputPath = outputPath.replace(/\\/g, "/");
|
|
176
|
+
return normalizedFilePath.startsWith(normalizedOutputPath + "/") && !normalizedFilePath.endsWith(`/index.ts`);
|
|
177
|
+
});
|
|
178
|
+
if (relevantFiles.length === 0) return [];
|
|
179
|
+
const tree = buildTree(outputPath, relevantFiles.map((f) => f.path));
|
|
180
|
+
switch (barrelType) {
|
|
181
|
+
case "all": return getBarrelFilesAll(tree, relevantFiles);
|
|
182
|
+
case "named": return getBarrelFilesNamed(tree, relevantFiles);
|
|
183
|
+
case "propagate": return getBarrelFilesPropagate(tree);
|
|
184
|
+
default: return [];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/utils/generatePerPluginBarrel.ts
|
|
189
|
+
/**
|
|
190
|
+
* Generates barrel files for a single plugin's output directory.
|
|
191
|
+
*
|
|
192
|
+
* The barrel file is placed at `resolve(config.root, plugin.options.output.path)/index.ts`
|
|
193
|
+
* and re-exports all files generated by that plugin, using the given `barrelType` strategy.
|
|
194
|
+
*/
|
|
195
|
+
function generatePerPluginBarrel({ barrelType, plugin, files, config }) {
|
|
196
|
+
return getBarrelFiles(resolve(config.root, plugin.options.output.path), files, barrelType);
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/utils/generateRootBarrel.ts
|
|
200
|
+
/**
|
|
201
|
+
* Generates a root barrel file at `resolve(config.root, config.output.path)/index.ts`.
|
|
202
|
+
*
|
|
203
|
+
* The root barrel re-exports from all files across all plugins that are located
|
|
204
|
+
* inside the root output directory, using the given `barrelType` strategy.
|
|
205
|
+
*
|
|
206
|
+
* In practice this re-exports the per-plugin barrels when `barrelType = 'propagate'`,
|
|
207
|
+
* or all individual files when `barrelType = 'all'` or `'named'`.
|
|
208
|
+
*/
|
|
209
|
+
function generateRootBarrel({ barrelType, files, config }) {
|
|
210
|
+
return getBarrelFiles(resolve(config.root, config.output.path), files, barrelType);
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/middleware.ts
|
|
214
|
+
/**
|
|
215
|
+
* Barrel-file generation middleware.
|
|
216
|
+
*
|
|
217
|
+
* When added to `config.middleware`, generates an `index.ts` barrel file for each
|
|
218
|
+
* plugin that has `output.barrelType` set (or inherits it from `config.output.barrelType`),
|
|
219
|
+
* as well as a root `index.ts` at `config.output.path` when `config.output.barrelType` is set.
|
|
220
|
+
*
|
|
221
|
+
* The `barrelType` option controls the re-export style:
|
|
222
|
+
* - `'all'` — `export * from '...'` for each generated file
|
|
223
|
+
* - `'named'` — `export { name1, name2 } from '...'` using each file's indexable sources
|
|
224
|
+
* - `'propagate'` — like `'all'` with intermediate barrel files for sub-directories
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```ts
|
|
228
|
+
* import { middlewareBarrel } from '@kubb/middleware-barrel'
|
|
229
|
+
*
|
|
230
|
+
* export default defineConfig({
|
|
231
|
+
* output: { path: 'src/gen', barrelType: 'named' },
|
|
232
|
+
* plugins: [
|
|
233
|
+
* pluginTs({ output: { path: 'types', barrelType: 'all' } }),
|
|
234
|
+
* pluginZod({ output: { path: 'schemas' } }),
|
|
235
|
+
* ],
|
|
236
|
+
* middleware: [middlewareBarrel],
|
|
237
|
+
* })
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
const middlewareBarrel = defineMiddleware({
|
|
241
|
+
name: "middleware-barrel",
|
|
242
|
+
install(hooks) {
|
|
243
|
+
let ctx;
|
|
244
|
+
hooks.on("kubb:build:start", (buildCtx) => {
|
|
245
|
+
ctx = buildCtx;
|
|
246
|
+
});
|
|
247
|
+
hooks.on("kubb:plugin:end", ({ plugin }) => {
|
|
248
|
+
if (!ctx) return;
|
|
249
|
+
const normalizedPlugin = plugin;
|
|
250
|
+
const pluginOutput = normalizedPlugin.options.output;
|
|
251
|
+
const rootOutput = ctx.config.output;
|
|
252
|
+
const barrelType = pluginOutput?.barrelType !== void 0 ? pluginOutput.barrelType : rootOutput.barrelType;
|
|
253
|
+
if (!barrelType) return;
|
|
254
|
+
const barrelFiles = generatePerPluginBarrel({
|
|
255
|
+
barrelType,
|
|
256
|
+
plugin: normalizedPlugin,
|
|
257
|
+
files: ctx.files,
|
|
258
|
+
config: ctx.config
|
|
259
|
+
});
|
|
260
|
+
if (barrelFiles.length > 0) ctx.upsertFile(...barrelFiles);
|
|
261
|
+
});
|
|
262
|
+
hooks.on("kubb:build:end", () => {
|
|
263
|
+
if (!ctx) return;
|
|
264
|
+
const rootBarrelType = ctx.config.output.barrelType;
|
|
265
|
+
if (!rootBarrelType) return;
|
|
266
|
+
const rootBarrelFiles = generateRootBarrel({
|
|
267
|
+
barrelType: rootBarrelType,
|
|
268
|
+
files: ctx.files,
|
|
269
|
+
config: ctx.config
|
|
270
|
+
});
|
|
271
|
+
if (rootBarrelFiles.length > 0) ctx.upsertFile(...rootBarrelFiles);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
//#endregion
|
|
276
|
+
export { middlewareBarrel };
|
|
277
|
+
|
|
278
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/constants.ts","../src/utils/TreeNode.ts","../src/utils/getBarrelFiles.ts","../src/utils/generatePerPluginBarrel.ts","../src/utils/generateRootBarrel.ts","../src/middleware.ts"],"sourcesContent":["/**\n * Base file name for barrel files (without extension).\n */\nexport const BARREL_BASENAME = 'index' as const\n\n/**\n * Full file name for barrel files (with extension).\n */\nexport const BARREL_FILENAME = 'index.ts' as const\n","import { posix } from 'node:path'\n\n/**\n * A node in a directory tree used to compute barrel file exports.\n *\n * Each `TreeNode` represents either a directory or a file entry.\n * Directory nodes have `children`; file nodes have an empty `children` array.\n */\nexport type TreeNode = {\n /**\n * Absolute path of the directory (root of this subtree) or file.\n */\n path: string\n /**\n * Child nodes (sub-directories and files) within this directory.\n */\n children: Array<TreeNode>\n /**\n * `true` when this node represents a file (leaf node).\n */\n isFile: boolean\n}\n\n/**\n * Builds a `TreeNode` directory tree from a list of absolute file paths.\n *\n * All `filePaths` must be inside `rootPath`. Paths that are outside\n * the root or that equal the root are silently ignored.\n *\n * @example\n * ```ts\n * const tree = buildTree('/src/gen/types', [\n * '/src/gen/types/pet.ts',\n * '/src/gen/types/user.ts',\n * '/src/gen/types/pets/listPets.ts',\n * ])\n * ```\n */\nexport function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): TreeNode {\n const root: TreeNode = { path: rootPath, children: [], isFile: false }\n\n for (const filePath of filePaths) {\n // Only include files inside rootPath\n if (!filePath.startsWith(rootPath + posix.sep) && !filePath.startsWith(rootPath + '/')) {\n continue\n }\n\n const relative = filePath.slice(rootPath.length).replace(/^\\//g, '').replace(/^\\\\/g, '')\n const parts = relative.split(/[/\\\\]/).filter(Boolean)\n\n let current = root\n for (let i = 0; i < parts.length; i++) {\n const isLast = i === parts.length - 1\n const part = parts[i]!\n const childPath = `${current.path}/${part}`\n\n let child = current.children.find((c) => c.path === childPath)\n if (!child) {\n child = { path: childPath, children: [], isFile: isLast }\n current.children.push(child)\n }\n current = child\n }\n }\n\n return root\n}\n","import { createExport, createFile } from '@kubb/ast'\nimport type { FileNode } from '@kubb/ast'\nimport { BARREL_BASENAME, BARREL_FILENAME } from '../constants.ts'\nimport type { BarrelType } from '../types.ts'\nimport { buildTree, type TreeNode } from './TreeNode.ts'\n\n/**\n * Derives a relative module specifier (no extension) from an absolute `filePath`\n * relative to an absolute `fromDir`.\n *\n * @example\n * toRelativeModulePath('/src/gen/types', '/src/gen/types/pet.ts') // './pet'\n * toRelativeModulePath('/src/gen/types', '/src/gen/types/tags/tag.ts') // './tags/tag'\n */\nfunction toRelativeModulePath(fromDir: string, filePath: string): string {\n const relative = filePath.slice(fromDir.length).replace(/^[/\\\\]/g, '')\n // Strip extension\n const withoutExt = relative.replace(/\\.[^/.]+$/, '')\n return `./${withoutExt}`\n}\n\n/**\n * Generates barrel `FileNode[]` for a given directory tree node using the `'all'` strategy:\n * each leaf file gets a `export * from './relPath'` in the barrel of its nearest ancestor directory.\n *\n * Only a single barrel file (at `treeNode.path`) is generated — sub-directory files are referenced\n * with their full relative path from `treeNode.path`.\n */\nfunction getBarrelFilesAll(treeNode: TreeNode, _sourceFiles: ReadonlyArray<FileNode>): Array<FileNode> {\n // Collect all source file paths under this node (excluding barrel files themselves)\n const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))\n\n if (leafPaths.length === 0) return []\n\n const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`\n const exports = leafPaths.map((filePath) =>\n createExport({\n path: toRelativeModulePath(treeNode.path, filePath),\n }),\n )\n\n return [\n createFile({\n baseName: BARREL_FILENAME,\n path: barrelPath,\n exports,\n sources: [],\n imports: [],\n }),\n ]\n}\n\n/**\n * Generates barrel `FileNode[]` for a given directory tree node using the `'named'` strategy:\n * each indexable source in each leaf file gets an individual named `export { name } from '...'`.\n */\nfunction getBarrelFilesNamed(treeNode: TreeNode, sourceFiles: ReadonlyArray<FileNode>): Array<FileNode> {\n const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))\n\n if (leafPaths.length === 0) return []\n\n const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`\n const exports: ReturnType<typeof createExport>[] = []\n\n for (const filePath of leafPaths) {\n const sourceFile = sourceFiles.find((f) => f.path === filePath)\n if (!sourceFile) {\n // Fall back to wildcard if the source file is not in our set\n exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))\n continue\n }\n\n const indexableSources = sourceFile.sources.filter((s) => s.isIndexable && s.name)\n if (indexableSources.length === 0) {\n // No named exports: fall back to wildcard\n exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))\n continue\n }\n\n const names = indexableSources.map((s) => s.name as string)\n exports.push(\n createExport({\n name: names,\n path: toRelativeModulePath(treeNode.path, filePath),\n }),\n )\n }\n\n if (exports.length === 0) return []\n\n return [\n createFile({\n baseName: BARREL_FILENAME,\n path: barrelPath,\n exports,\n sources: [],\n imports: [],\n }),\n ]\n}\n\n/**\n * Generates barrel `FileNode[]` for a given directory tree node using the `'propagate'` strategy:\n * like `'all'` but also generates intermediate barrel files for every sub-directory, so that\n * consumers can import from any depth.\n *\n * Leaf barrels export directly from their files; parent barrels export from their sub-barrel files.\n */\nfunction getBarrelFilesPropagate(treeNode: TreeNode): Array<FileNode> {\n return collectPropagatedBarrels(treeNode)\n}\n\nfunction collectPropagatedBarrels(node: TreeNode): Array<FileNode> {\n const result: Array<FileNode> = []\n const barrelExports: ReturnType<typeof createExport>[] = []\n\n for (const child of node.children) {\n if (child.isFile) {\n if (!child.path.endsWith(`/${BARREL_FILENAME}`)) {\n barrelExports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }))\n }\n } else {\n // Recurse into sub-directory\n const subBarrels = collectPropagatedBarrels(child)\n result.push(...subBarrels)\n\n // Export the sub-directory's barrel (not individual files)\n const subBarrelPath = `${child.path}/${BARREL_BASENAME}`\n barrelExports.push(createExport({ path: toRelativeModulePath(node.path, subBarrelPath) }))\n }\n }\n\n if (barrelExports.length > 0) {\n result.push(\n createFile({\n baseName: BARREL_FILENAME,\n path: `${node.path}/${BARREL_FILENAME}`,\n exports: barrelExports,\n sources: [],\n imports: [],\n }),\n )\n }\n\n return result\n}\n\n/**\n * Collects all leaf (file) paths within a tree node recursively.\n */\nfunction collectLeafPaths(node: TreeNode): Array<string> {\n if (node.isFile) return [node.path]\n return node.children.flatMap((c) => collectLeafPaths(c))\n}\n\n/**\n * Generates barrel `FileNode[]` for a directory rooted at `outputPath`, given the full set of\n * generated source `files`, using the specified `barrelType` strategy.\n *\n * Files not located inside `outputPath` are excluded automatically.\n *\n * @param outputPath Absolute path to the output directory.\n * @param files All generated files (across all plugins).\n * @param barrelType Barrel generation strategy.\n */\nexport function getBarrelFiles(outputPath: string, files: ReadonlyArray<FileNode>, barrelType: BarrelType): Array<FileNode> {\n // Only include files that live inside this outputPath\n const relevantFiles = files.filter((f) => {\n const normalizedFilePath = f.path.replace(/\\\\/g, '/')\n const normalizedOutputPath = outputPath.replace(/\\\\/g, '/')\n return normalizedFilePath.startsWith(normalizedOutputPath + '/') && !normalizedFilePath.endsWith(`/${BARREL_FILENAME}`)\n })\n\n if (relevantFiles.length === 0) return []\n\n const tree = buildTree(outputPath, relevantFiles.map((f) => f.path))\n\n switch (barrelType) {\n case 'all':\n return getBarrelFilesAll(tree, relevantFiles)\n case 'named':\n return getBarrelFilesNamed(tree, relevantFiles)\n case 'propagate':\n return getBarrelFilesPropagate(tree)\n default:\n return []\n }\n}\n","import { resolve } from 'node:path'\nimport type { FileNode } from '@kubb/ast'\nimport type { Config, NormalizedPlugin } from '@kubb/core'\nimport type { BarrelType } from '../types.ts'\nimport { getBarrelFiles } from './getBarrelFiles.ts'\n\nexport type GeneratePerPluginBarrelParams = {\n barrelType: BarrelType\n plugin: NormalizedPlugin\n files: ReadonlyArray<FileNode>\n config: Config\n}\n\n/**\n * Generates barrel files for a single plugin's output directory.\n *\n * The barrel file is placed at `resolve(config.root, plugin.options.output.path)/index.ts`\n * and re-exports all files generated by that plugin, using the given `barrelType` strategy.\n */\nexport function generatePerPluginBarrel({ barrelType, plugin, files, config }: GeneratePerPluginBarrelParams): Array<FileNode> {\n const outputPath = resolve(config.root, plugin.options.output.path)\n return getBarrelFiles(outputPath, files, barrelType)\n}\n","import { resolve } from 'node:path'\nimport type { FileNode } from '@kubb/ast'\nimport type { Config } from '@kubb/core'\nimport type { BarrelType } from '../types.ts'\nimport { getBarrelFiles } from './getBarrelFiles.ts'\n\nexport type GenerateRootBarrelParams = {\n barrelType: BarrelType\n files: ReadonlyArray<FileNode>\n config: Config\n}\n\n/**\n * Generates a root barrel file at `resolve(config.root, config.output.path)/index.ts`.\n *\n * The root barrel re-exports from all files across all plugins that are located\n * inside the root output directory, using the given `barrelType` strategy.\n *\n * In practice this re-exports the per-plugin barrels when `barrelType = 'propagate'`,\n * or all individual files when `barrelType = 'all'` or `'named'`.\n */\nexport function generateRootBarrel({ barrelType, files, config }: GenerateRootBarrelParams): Array<FileNode> {\n const outputPath = resolve(config.root, config.output.path)\n return getBarrelFiles(outputPath, files, barrelType)\n}\n","import { defineMiddleware } from '@kubb/core'\nimport type { KubbBuildStartContext, NormalizedPlugin } from '@kubb/core'\nimport './types.ts'\nimport type { BarrelType } from './types.ts'\nimport { generatePerPluginBarrel } from './utils/generatePerPluginBarrel.ts'\nimport { generateRootBarrel } from './utils/generateRootBarrel.ts'\n\n/**\n * Barrel-file generation middleware.\n *\n * When added to `config.middleware`, generates an `index.ts` barrel file for each\n * plugin that has `output.barrelType` set (or inherits it from `config.output.barrelType`),\n * as well as a root `index.ts` at `config.output.path` when `config.output.barrelType` is set.\n *\n * The `barrelType` option controls the re-export style:\n * - `'all'` — `export * from '...'` for each generated file\n * - `'named'` — `export { name1, name2 } from '...'` using each file's indexable sources\n * - `'propagate'` — like `'all'` with intermediate barrel files for sub-directories\n *\n * @example\n * ```ts\n * import { middlewareBarrel } from '@kubb/middleware-barrel'\n *\n * export default defineConfig({\n * output: { path: 'src/gen', barrelType: 'named' },\n * plugins: [\n * pluginTs({ output: { path: 'types', barrelType: 'all' } }),\n * pluginZod({ output: { path: 'schemas' } }),\n * ],\n * middleware: [middlewareBarrel],\n * })\n * ```\n */\nexport const middlewareBarrel = defineMiddleware({\n name: 'middleware-barrel',\n\n install(hooks) {\n let ctx: KubbBuildStartContext\n\n hooks.on('kubb:build:start', (buildCtx) => {\n ctx = buildCtx\n })\n\n hooks.on('kubb:plugin:end', ({ plugin }) => {\n if (!ctx) return\n\n // At runtime the plugin in kubb:plugin:end is always a NormalizedPlugin;\n // KubbPluginEndContext types it as Plugin for public API simplicity.\n const normalizedPlugin = plugin as NormalizedPlugin\n const pluginOutput = normalizedPlugin.options.output as { barrelType?: BarrelType | false } | undefined\n const rootOutput = ctx.config.output as { barrelType?: BarrelType | false }\n const barrelType = pluginOutput?.barrelType !== undefined ? pluginOutput.barrelType : rootOutput.barrelType\n\n if (!barrelType) return\n\n const barrelFiles = generatePerPluginBarrel({\n barrelType,\n plugin: normalizedPlugin,\n files: ctx.files,\n config: ctx.config,\n })\n\n if (barrelFiles.length > 0) {\n ctx.upsertFile(...barrelFiles)\n }\n })\n\n hooks.on('kubb:build:end', () => {\n if (!ctx) return\n\n const rootOutput = ctx.config.output as { barrelType?: BarrelType | false }\n const rootBarrelType = rootOutput.barrelType\n\n if (!rootBarrelType) return\n\n const rootBarrelFiles = generateRootBarrel({\n barrelType: rootBarrelType,\n files: ctx.files,\n config: ctx.config,\n })\n\n if (rootBarrelFiles.length > 0) {\n ctx.upsertFile(...rootBarrelFiles)\n }\n })\n },\n})\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kubb/middleware-barrel",
|
|
3
|
+
"version": "5.0.0-alpha.55",
|
|
4
|
+
"description": "Barrel-file generation middleware for Kubb. Generates index.ts barrel files for each plugin's output directory and a root index.ts after all plugins have run.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"barrel",
|
|
7
|
+
"code-generator",
|
|
8
|
+
"codegen",
|
|
9
|
+
"kubb",
|
|
10
|
+
"middleware",
|
|
11
|
+
"openapi",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "stijnvanhulle",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/kubb-labs/kubb.git",
|
|
19
|
+
"directory": "packages/middleware-barrel"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src",
|
|
23
|
+
"dist",
|
|
24
|
+
"*.d.ts",
|
|
25
|
+
"*.d.cts",
|
|
26
|
+
"!/**/**.test.**",
|
|
27
|
+
"!/**/__tests__/**",
|
|
28
|
+
"!/**/__snapshots__/**"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"main": "./dist/index.cjs",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"import": "./dist/index.js",
|
|
38
|
+
"require": "./dist/index.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public",
|
|
44
|
+
"registry": "https://registry.npmjs.org/"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@kubb/core": "5.0.0-alpha.55"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsdown",
|
|
54
|
+
"clean": "npx rimraf ./dist",
|
|
55
|
+
"lint": "oxlint .",
|
|
56
|
+
"lint:fix": "oxlint --fix .",
|
|
57
|
+
"release": "pnpm publish --no-git-check",
|
|
58
|
+
"start": "tsdown --watch",
|
|
59
|
+
"test": "vitest --passWithNoTests",
|
|
60
|
+
"typecheck": "tsc -p ./tsconfig.json --noEmit --emitDeclarationOnly false"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/constants.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { middlewareBarrel } from './middleware.ts'
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defineMiddleware } from '@kubb/core'
|
|
2
|
+
import type { KubbBuildStartContext, NormalizedPlugin } from '@kubb/core'
|
|
3
|
+
import './types.ts'
|
|
4
|
+
import type { BarrelType } from './types.ts'
|
|
5
|
+
import { generatePerPluginBarrel } from './utils/generatePerPluginBarrel.ts'
|
|
6
|
+
import { generateRootBarrel } from './utils/generateRootBarrel.ts'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Barrel-file generation middleware.
|
|
10
|
+
*
|
|
11
|
+
* When added to `config.middleware`, generates an `index.ts` barrel file for each
|
|
12
|
+
* plugin that has `output.barrelType` set (or inherits it from `config.output.barrelType`),
|
|
13
|
+
* as well as a root `index.ts` at `config.output.path` when `config.output.barrelType` is set.
|
|
14
|
+
*
|
|
15
|
+
* The `barrelType` option controls the re-export style:
|
|
16
|
+
* - `'all'` — `export * from '...'` for each generated file
|
|
17
|
+
* - `'named'` — `export { name1, name2 } from '...'` using each file's indexable sources
|
|
18
|
+
* - `'propagate'` — like `'all'` with intermediate barrel files for sub-directories
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { middlewareBarrel } from '@kubb/middleware-barrel'
|
|
23
|
+
*
|
|
24
|
+
* export default defineConfig({
|
|
25
|
+
* output: { path: 'src/gen', barrelType: 'named' },
|
|
26
|
+
* plugins: [
|
|
27
|
+
* pluginTs({ output: { path: 'types', barrelType: 'all' } }),
|
|
28
|
+
* pluginZod({ output: { path: 'schemas' } }),
|
|
29
|
+
* ],
|
|
30
|
+
* middleware: [middlewareBarrel],
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export const middlewareBarrel = defineMiddleware({
|
|
35
|
+
name: 'middleware-barrel',
|
|
36
|
+
|
|
37
|
+
install(hooks) {
|
|
38
|
+
let ctx: KubbBuildStartContext
|
|
39
|
+
|
|
40
|
+
hooks.on('kubb:build:start', (buildCtx) => {
|
|
41
|
+
ctx = buildCtx
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
hooks.on('kubb:plugin:end', ({ plugin }) => {
|
|
45
|
+
if (!ctx) return
|
|
46
|
+
|
|
47
|
+
// At runtime the plugin in kubb:plugin:end is always a NormalizedPlugin;
|
|
48
|
+
// KubbPluginEndContext types it as Plugin for public API simplicity.
|
|
49
|
+
const normalizedPlugin = plugin as NormalizedPlugin
|
|
50
|
+
const pluginOutput = normalizedPlugin.options.output as { barrelType?: BarrelType | false } | undefined
|
|
51
|
+
const rootOutput = ctx.config.output as { barrelType?: BarrelType | false }
|
|
52
|
+
const barrelType = pluginOutput?.barrelType !== undefined ? pluginOutput.barrelType : rootOutput.barrelType
|
|
53
|
+
|
|
54
|
+
if (!barrelType) return
|
|
55
|
+
|
|
56
|
+
const barrelFiles = generatePerPluginBarrel({
|
|
57
|
+
barrelType,
|
|
58
|
+
plugin: normalizedPlugin,
|
|
59
|
+
files: ctx.files,
|
|
60
|
+
config: ctx.config,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if (barrelFiles.length > 0) {
|
|
64
|
+
ctx.upsertFile(...barrelFiles)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
hooks.on('kubb:build:end', () => {
|
|
69
|
+
if (!ctx) return
|
|
70
|
+
|
|
71
|
+
const rootOutput = ctx.config.output as { barrelType?: BarrelType | false }
|
|
72
|
+
const rootBarrelType = rootOutput.barrelType
|
|
73
|
+
|
|
74
|
+
if (!rootBarrelType) return
|
|
75
|
+
|
|
76
|
+
const rootBarrelFiles = generateRootBarrel({
|
|
77
|
+
barrelType: rootBarrelType,
|
|
78
|
+
files: ctx.files,
|
|
79
|
+
config: ctx.config,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
if (rootBarrelFiles.length > 0) {
|
|
83
|
+
ctx.upsertFile(...rootBarrelFiles)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
},
|
|
87
|
+
})
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The barrel type controls the style of re-exports generated in barrel files.
|
|
3
|
+
*
|
|
4
|
+
* - `'all'` — `export * from '...'` (re-export everything)
|
|
5
|
+
* - `'named'` — `export { name1, name2 } from '...'` (named re-exports only)
|
|
6
|
+
* - `'propagate'` — like `'all'` but also generates intermediate barrel files for
|
|
7
|
+
* every sub-directory so that consumers can import from any depth.
|
|
8
|
+
*/
|
|
9
|
+
export type BarrelType = 'all' | 'named' | 'propagate'
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
namespace Kubb {
|
|
13
|
+
interface PluginOptionsRegistry {
|
|
14
|
+
output: {
|
|
15
|
+
/**
|
|
16
|
+
* Controls which barrel file (index.ts) is generated for this plugin's output directory.
|
|
17
|
+
*
|
|
18
|
+
* - `'all'` — `export * from '...'` for every generated file.
|
|
19
|
+
* - `'named'` — `export { … } from '...'` using the file's named exports.
|
|
20
|
+
* - `'propagate'` — like `'all'` but also generates intermediate barrel files.
|
|
21
|
+
* - `false` — disable barrel generation for this plugin.
|
|
22
|
+
*
|
|
23
|
+
* When omitted, the root `config.output.barrelType` is used as the default.
|
|
24
|
+
*/
|
|
25
|
+
barrelType?: BarrelType | false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
interface ConfigOptionsRegistry {
|
|
29
|
+
output: {
|
|
30
|
+
/**
|
|
31
|
+
* Controls the root barrel file (index.ts) generated at `config.output.path`.
|
|
32
|
+
*
|
|
33
|
+
* - `'all'` — `export * from '...'` for every plugin's barrel.
|
|
34
|
+
* - `'named'` — `export { … } from '...'` using the barrel's named exports.
|
|
35
|
+
* - `'propagate'` — like `'all'` but also generates intermediate barrel files.
|
|
36
|
+
* - `false` — disable root barrel generation.
|
|
37
|
+
*
|
|
38
|
+
* Individual plugins can override this via their own `output.barrelType`.
|
|
39
|
+
*/
|
|
40
|
+
barrelType?: BarrelType | false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { posix } from 'node:path'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A node in a directory tree used to compute barrel file exports.
|
|
5
|
+
*
|
|
6
|
+
* Each `TreeNode` represents either a directory or a file entry.
|
|
7
|
+
* Directory nodes have `children`; file nodes have an empty `children` array.
|
|
8
|
+
*/
|
|
9
|
+
export type TreeNode = {
|
|
10
|
+
/**
|
|
11
|
+
* Absolute path of the directory (root of this subtree) or file.
|
|
12
|
+
*/
|
|
13
|
+
path: string
|
|
14
|
+
/**
|
|
15
|
+
* Child nodes (sub-directories and files) within this directory.
|
|
16
|
+
*/
|
|
17
|
+
children: Array<TreeNode>
|
|
18
|
+
/**
|
|
19
|
+
* `true` when this node represents a file (leaf node).
|
|
20
|
+
*/
|
|
21
|
+
isFile: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Builds a `TreeNode` directory tree from a list of absolute file paths.
|
|
26
|
+
*
|
|
27
|
+
* All `filePaths` must be inside `rootPath`. Paths that are outside
|
|
28
|
+
* the root or that equal the root are silently ignored.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const tree = buildTree('/src/gen/types', [
|
|
33
|
+
* '/src/gen/types/pet.ts',
|
|
34
|
+
* '/src/gen/types/user.ts',
|
|
35
|
+
* '/src/gen/types/pets/listPets.ts',
|
|
36
|
+
* ])
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function buildTree(rootPath: string, filePaths: ReadonlyArray<string>): TreeNode {
|
|
40
|
+
const root: TreeNode = { path: rootPath, children: [], isFile: false }
|
|
41
|
+
|
|
42
|
+
for (const filePath of filePaths) {
|
|
43
|
+
// Only include files inside rootPath
|
|
44
|
+
if (!filePath.startsWith(rootPath + posix.sep) && !filePath.startsWith(rootPath + '/')) {
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const relative = filePath.slice(rootPath.length).replace(/^\//g, '').replace(/^\\/g, '')
|
|
49
|
+
const parts = relative.split(/[/\\]/).filter(Boolean)
|
|
50
|
+
|
|
51
|
+
let current = root
|
|
52
|
+
for (let i = 0; i < parts.length; i++) {
|
|
53
|
+
const isLast = i === parts.length - 1
|
|
54
|
+
const part = parts[i]!
|
|
55
|
+
const childPath = `${current.path}/${part}`
|
|
56
|
+
|
|
57
|
+
let child = current.children.find((c) => c.path === childPath)
|
|
58
|
+
if (!child) {
|
|
59
|
+
child = { path: childPath, children: [], isFile: isLast }
|
|
60
|
+
current.children.push(child)
|
|
61
|
+
}
|
|
62
|
+
current = child
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return root
|
|
67
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import type { FileNode } from '@kubb/ast'
|
|
3
|
+
import type { Config, NormalizedPlugin } from '@kubb/core'
|
|
4
|
+
import type { BarrelType } from '../types.ts'
|
|
5
|
+
import { getBarrelFiles } from './getBarrelFiles.ts'
|
|
6
|
+
|
|
7
|
+
export type GeneratePerPluginBarrelParams = {
|
|
8
|
+
barrelType: BarrelType
|
|
9
|
+
plugin: NormalizedPlugin
|
|
10
|
+
files: ReadonlyArray<FileNode>
|
|
11
|
+
config: Config
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generates barrel files for a single plugin's output directory.
|
|
16
|
+
*
|
|
17
|
+
* The barrel file is placed at `resolve(config.root, plugin.options.output.path)/index.ts`
|
|
18
|
+
* and re-exports all files generated by that plugin, using the given `barrelType` strategy.
|
|
19
|
+
*/
|
|
20
|
+
export function generatePerPluginBarrel({ barrelType, plugin, files, config }: GeneratePerPluginBarrelParams): Array<FileNode> {
|
|
21
|
+
const outputPath = resolve(config.root, plugin.options.output.path)
|
|
22
|
+
return getBarrelFiles(outputPath, files, barrelType)
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import type { FileNode } from '@kubb/ast'
|
|
3
|
+
import type { Config } from '@kubb/core'
|
|
4
|
+
import type { BarrelType } from '../types.ts'
|
|
5
|
+
import { getBarrelFiles } from './getBarrelFiles.ts'
|
|
6
|
+
|
|
7
|
+
export type GenerateRootBarrelParams = {
|
|
8
|
+
barrelType: BarrelType
|
|
9
|
+
files: ReadonlyArray<FileNode>
|
|
10
|
+
config: Config
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generates a root barrel file at `resolve(config.root, config.output.path)/index.ts`.
|
|
15
|
+
*
|
|
16
|
+
* The root barrel re-exports from all files across all plugins that are located
|
|
17
|
+
* inside the root output directory, using the given `barrelType` strategy.
|
|
18
|
+
*
|
|
19
|
+
* In practice this re-exports the per-plugin barrels when `barrelType = 'propagate'`,
|
|
20
|
+
* or all individual files when `barrelType = 'all'` or `'named'`.
|
|
21
|
+
*/
|
|
22
|
+
export function generateRootBarrel({ barrelType, files, config }: GenerateRootBarrelParams): Array<FileNode> {
|
|
23
|
+
const outputPath = resolve(config.root, config.output.path)
|
|
24
|
+
return getBarrelFiles(outputPath, files, barrelType)
|
|
25
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { createExport, createFile } from '@kubb/ast'
|
|
2
|
+
import type { FileNode } from '@kubb/ast'
|
|
3
|
+
import { BARREL_BASENAME, BARREL_FILENAME } from '../constants.ts'
|
|
4
|
+
import type { BarrelType } from '../types.ts'
|
|
5
|
+
import { buildTree, type TreeNode } from './TreeNode.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Derives a relative module specifier (no extension) from an absolute `filePath`
|
|
9
|
+
* relative to an absolute `fromDir`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* toRelativeModulePath('/src/gen/types', '/src/gen/types/pet.ts') // './pet'
|
|
13
|
+
* toRelativeModulePath('/src/gen/types', '/src/gen/types/tags/tag.ts') // './tags/tag'
|
|
14
|
+
*/
|
|
15
|
+
function toRelativeModulePath(fromDir: string, filePath: string): string {
|
|
16
|
+
const relative = filePath.slice(fromDir.length).replace(/^[/\\]/g, '')
|
|
17
|
+
// Strip extension
|
|
18
|
+
const withoutExt = relative.replace(/\.[^/.]+$/, '')
|
|
19
|
+
return `./${withoutExt}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generates barrel `FileNode[]` for a given directory tree node using the `'all'` strategy:
|
|
24
|
+
* each leaf file gets a `export * from './relPath'` in the barrel of its nearest ancestor directory.
|
|
25
|
+
*
|
|
26
|
+
* Only a single barrel file (at `treeNode.path`) is generated — sub-directory files are referenced
|
|
27
|
+
* with their full relative path from `treeNode.path`.
|
|
28
|
+
*/
|
|
29
|
+
function getBarrelFilesAll(treeNode: TreeNode, _sourceFiles: ReadonlyArray<FileNode>): Array<FileNode> {
|
|
30
|
+
// Collect all source file paths under this node (excluding barrel files themselves)
|
|
31
|
+
const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))
|
|
32
|
+
|
|
33
|
+
if (leafPaths.length === 0) return []
|
|
34
|
+
|
|
35
|
+
const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`
|
|
36
|
+
const exports = leafPaths.map((filePath) =>
|
|
37
|
+
createExport({
|
|
38
|
+
path: toRelativeModulePath(treeNode.path, filePath),
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return [
|
|
43
|
+
createFile({
|
|
44
|
+
baseName: BARREL_FILENAME,
|
|
45
|
+
path: barrelPath,
|
|
46
|
+
exports,
|
|
47
|
+
sources: [],
|
|
48
|
+
imports: [],
|
|
49
|
+
}),
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generates barrel `FileNode[]` for a given directory tree node using the `'named'` strategy:
|
|
55
|
+
* each indexable source in each leaf file gets an individual named `export { name } from '...'`.
|
|
56
|
+
*/
|
|
57
|
+
function getBarrelFilesNamed(treeNode: TreeNode, sourceFiles: ReadonlyArray<FileNode>): Array<FileNode> {
|
|
58
|
+
const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`))
|
|
59
|
+
|
|
60
|
+
if (leafPaths.length === 0) return []
|
|
61
|
+
|
|
62
|
+
const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`
|
|
63
|
+
const exports: ReturnType<typeof createExport>[] = []
|
|
64
|
+
|
|
65
|
+
for (const filePath of leafPaths) {
|
|
66
|
+
const sourceFile = sourceFiles.find((f) => f.path === filePath)
|
|
67
|
+
if (!sourceFile) {
|
|
68
|
+
// Fall back to wildcard if the source file is not in our set
|
|
69
|
+
exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const indexableSources = sourceFile.sources.filter((s) => s.isIndexable && s.name)
|
|
74
|
+
if (indexableSources.length === 0) {
|
|
75
|
+
// No named exports: fall back to wildcard
|
|
76
|
+
exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }))
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const names = indexableSources.map((s) => s.name as string)
|
|
81
|
+
exports.push(
|
|
82
|
+
createExport({
|
|
83
|
+
name: names,
|
|
84
|
+
path: toRelativeModulePath(treeNode.path, filePath),
|
|
85
|
+
}),
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (exports.length === 0) return []
|
|
90
|
+
|
|
91
|
+
return [
|
|
92
|
+
createFile({
|
|
93
|
+
baseName: BARREL_FILENAME,
|
|
94
|
+
path: barrelPath,
|
|
95
|
+
exports,
|
|
96
|
+
sources: [],
|
|
97
|
+
imports: [],
|
|
98
|
+
}),
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generates barrel `FileNode[]` for a given directory tree node using the `'propagate'` strategy:
|
|
104
|
+
* like `'all'` but also generates intermediate barrel files for every sub-directory, so that
|
|
105
|
+
* consumers can import from any depth.
|
|
106
|
+
*
|
|
107
|
+
* Leaf barrels export directly from their files; parent barrels export from their sub-barrel files.
|
|
108
|
+
*/
|
|
109
|
+
function getBarrelFilesPropagate(treeNode: TreeNode): Array<FileNode> {
|
|
110
|
+
return collectPropagatedBarrels(treeNode)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function collectPropagatedBarrels(node: TreeNode): Array<FileNode> {
|
|
114
|
+
const result: Array<FileNode> = []
|
|
115
|
+
const barrelExports: ReturnType<typeof createExport>[] = []
|
|
116
|
+
|
|
117
|
+
for (const child of node.children) {
|
|
118
|
+
if (child.isFile) {
|
|
119
|
+
if (!child.path.endsWith(`/${BARREL_FILENAME}`)) {
|
|
120
|
+
barrelExports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }))
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
// Recurse into sub-directory
|
|
124
|
+
const subBarrels = collectPropagatedBarrels(child)
|
|
125
|
+
result.push(...subBarrels)
|
|
126
|
+
|
|
127
|
+
// Export the sub-directory's barrel (not individual files)
|
|
128
|
+
const subBarrelPath = `${child.path}/${BARREL_BASENAME}`
|
|
129
|
+
barrelExports.push(createExport({ path: toRelativeModulePath(node.path, subBarrelPath) }))
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (barrelExports.length > 0) {
|
|
134
|
+
result.push(
|
|
135
|
+
createFile({
|
|
136
|
+
baseName: BARREL_FILENAME,
|
|
137
|
+
path: `${node.path}/${BARREL_FILENAME}`,
|
|
138
|
+
exports: barrelExports,
|
|
139
|
+
sources: [],
|
|
140
|
+
imports: [],
|
|
141
|
+
}),
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return result
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Collects all leaf (file) paths within a tree node recursively.
|
|
150
|
+
*/
|
|
151
|
+
function collectLeafPaths(node: TreeNode): Array<string> {
|
|
152
|
+
if (node.isFile) return [node.path]
|
|
153
|
+
return node.children.flatMap((c) => collectLeafPaths(c))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Generates barrel `FileNode[]` for a directory rooted at `outputPath`, given the full set of
|
|
158
|
+
* generated source `files`, using the specified `barrelType` strategy.
|
|
159
|
+
*
|
|
160
|
+
* Files not located inside `outputPath` are excluded automatically.
|
|
161
|
+
*
|
|
162
|
+
* @param outputPath Absolute path to the output directory.
|
|
163
|
+
* @param files All generated files (across all plugins).
|
|
164
|
+
* @param barrelType Barrel generation strategy.
|
|
165
|
+
*/
|
|
166
|
+
export function getBarrelFiles(outputPath: string, files: ReadonlyArray<FileNode>, barrelType: BarrelType): Array<FileNode> {
|
|
167
|
+
// Only include files that live inside this outputPath
|
|
168
|
+
const relevantFiles = files.filter((f) => {
|
|
169
|
+
const normalizedFilePath = f.path.replace(/\\/g, '/')
|
|
170
|
+
const normalizedOutputPath = outputPath.replace(/\\/g, '/')
|
|
171
|
+
return normalizedFilePath.startsWith(normalizedOutputPath + '/') && !normalizedFilePath.endsWith(`/${BARREL_FILENAME}`)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
if (relevantFiles.length === 0) return []
|
|
175
|
+
|
|
176
|
+
const tree = buildTree(outputPath, relevantFiles.map((f) => f.path))
|
|
177
|
+
|
|
178
|
+
switch (barrelType) {
|
|
179
|
+
case 'all':
|
|
180
|
+
return getBarrelFilesAll(tree, relevantFiles)
|
|
181
|
+
case 'named':
|
|
182
|
+
return getBarrelFilesNamed(tree, relevantFiles)
|
|
183
|
+
case 'propagate':
|
|
184
|
+
return getBarrelFilesPropagate(tree)
|
|
185
|
+
default:
|
|
186
|
+
return []
|
|
187
|
+
}
|
|
188
|
+
}
|