@kubb/middleware-barrel 5.0.0-alpha.62 → 5.0.0-alpha.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3,12 +3,11 @@ import * as _$_kubb_core0 from "@kubb/core";
3
3
 
4
4
  //#region src/types.d.ts
5
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.
6
+ * Re-export style used when generating barrel `index.ts` files.
7
+ * - `'all'` emits `export * from '...'` for every file.
8
+ * - `'named'` emits `export { name1, name2 } from '...'` from each file's indexable sources.
9
+ * - `'propagate'` behaves like `'all'` and also generates an intermediate barrel for every
10
+ * sub-directory so consumers can import from any depth.
12
11
  */
13
12
  type BarrelType = 'all' | 'named' | 'propagate';
14
13
  //#endregion
@@ -18,14 +17,13 @@ declare global {
18
17
  interface PluginOptionsRegistry {
19
18
  output: {
20
19
  /**
21
- * Controls which barrel file (index.ts) is generated for this plugin's output directory.
20
+ * Re-export style for this plugin's barrel file.
21
+ * Set to `false` to disable barrel generation for this plugin entirely; doing so also
22
+ * excludes the plugin's files from the root barrel.
22
23
  *
23
- * - `'all'` — `export * from '...'` for every generated file.
24
- * - `'named'` — `export { … } from '...'` using the file's named exports.
25
- * - `'propagate'` — like `'all'` but also generates intermediate barrel files.
26
- * - `false` — disable barrel generation for this plugin.
24
+ * Falls back to `config.output.barrelType` when omitted.
27
25
  *
28
- * When omitted, the root `config.output.barrelType` is used as the default.
26
+ * @default 'named'
29
27
  */
30
28
  barrelType?: BarrelType | false;
31
29
  };
@@ -33,14 +31,11 @@ declare global {
33
31
  interface ConfigOptionsRegistry {
34
32
  output: {
35
33
  /**
36
- * Controls the root barrel file (index.ts) generated at `config.output.path`.
37
- *
38
- * - `'all'` — `export * from '...'` for every plugin's barrel.
39
- * - `'named'` — `export { … } from '...'` using the barrel's named exports.
40
- * - `'propagate'` — like `'all'` but also generates intermediate barrel files.
41
- * - `false` — disable root barrel generation.
34
+ * Re-export style for the root barrel file at `config.output.path/index.ts`.
35
+ * Set to `false` to disable root barrel generation. Individual plugins can override
36
+ * this via their own `output.barrelType`.
42
37
  *
43
- * Individual plugins can override this via their own `output.barrelType`.
38
+ * @default 'named'
44
39
  */
45
40
  barrelType?: BarrelType | false;
46
41
  };
@@ -48,20 +43,16 @@ declare global {
48
43
  }
49
44
  }
50
45
  /**
51
- * Barrel-file generation middleware.
52
- *
53
- * When added to `config.middleware`, generates an `index.ts` barrel file for each
54
- * plugin. When `output.barrelType` is omitted on a plugin it inherits from `config.output.barrelType`,
55
- * and when that is also omitted both default to `'all'`. Set `barrelType: false` to disable
56
- * barrel generation for a specific plugin or for the root entirely.
46
+ * Generates `index.ts` barrel files for each plugin's output directory and one root barrel
47
+ * at `config.output.path/index.ts`.
57
48
  *
58
- * The `barrelType` option controls the re-export style:
59
- * - `'all'` — `export * from '...'` for each generated file
60
- * - `'named'` — `export { name1, name2 } from '...'` using each file's indexable sources
61
- * - `'propagate'` — like `'all'` with intermediate barrel files for sub-directories
49
+ * Plugins inherit `output.barrelType` from `config.output.barrelType` (which itself defaults to `'named'`).
50
+ * Setting `barrelType: false` on a plugin disables its barrel and excludes the plugin's files from the
51
+ * root barrel as well.
62
52
  *
63
53
  * @example
64
54
  * ```ts
55
+ * import { defineConfig } from '@kubb/core'
65
56
  * import { middlewareBarrel } from '@kubb/middleware-barrel'
66
57
  *
67
58
  * export default defineConfig({
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./chunk--u3MIqq1.js";
2
2
  import { defineMiddleware } from "@kubb/core";
3
- import { posix, resolve } from "node:path";
3
+ import { extname, resolve } from "node:path";
4
4
  import { createExport, createFile } from "@kubb/ast";
5
5
  //#region src/constants.ts
6
6
  /**
@@ -8,18 +8,18 @@ import { createExport, createFile } from "@kubb/ast";
8
8
  */
9
9
  const BARREL_FILENAME = "index.ts";
10
10
  //#endregion
11
- //#region src/utils/buildTree.ts
11
+ //#region ../../internals/utils/src/buildTree.ts
12
12
  /**
13
- * Builds a `TreeNode` directory tree from a list of absolute file paths.
13
+ * Builds a directory tree rooted at `rootPath` from a list of absolute file paths.
14
+ * Paths outside `rootPath` are silently ignored. Children are sorted alphabetically
15
+ * by path so consumers (barrel exports, propagated indexes) emit a deterministic order.
14
16
  *
15
- * All `filePaths` must be inside `rootPath`. Paths that are outside
16
- * the root or that equal the root are silently ignored.
17
+ * Both POSIX (`/`) and Windows (`\`) separators are accepted in input paths.
17
18
  *
18
19
  * @example
19
20
  * ```ts
20
- * const tree = buildTree('/src/gen/types', [
21
+ * buildTree('/src/gen/types', [
21
22
  * '/src/gen/types/pet.ts',
22
- * '/src/gen/types/user.ts',
23
23
  * '/src/gen/types/pets/listPets.ts',
24
24
  * ])
25
25
  * ```
@@ -30,28 +30,45 @@ function buildTree(rootPath, filePaths) {
30
30
  children: [],
31
31
  isFile: false
32
32
  };
33
+ const childIndex = /* @__PURE__ */ new Map();
34
+ childIndex.set(root, /* @__PURE__ */ new Map());
35
+ const rootPrefix = `${rootPath.replaceAll("\\", "/")}/`;
33
36
  for (const filePath of filePaths) {
34
- if (!filePath.startsWith(rootPath + posix.sep) && !filePath.startsWith(rootPath + "/")) continue;
35
- const parts = filePath.slice(rootPath.length).replace(/^\//g, "").replace(/^\\/g, "").split(/[/\\]/).filter(Boolean);
37
+ const normalized = filePath.replaceAll("\\", "/");
38
+ if (!normalized.startsWith(rootPrefix)) continue;
39
+ const parts = normalized.slice(rootPrefix.length).split("/");
40
+ if (parts.length === 0) continue;
36
41
  let current = root;
37
- for (let i = 0; i < parts.length; i++) {
38
- const isLast = i === parts.length - 1;
39
- const part = parts[i];
40
- const childPath = `${current.path}/${part}`;
41
- let child = current.children.find((c) => c.path === childPath);
42
+ const lastIndex = parts.length - 1;
43
+ for (const [i, part] of parts.entries()) {
44
+ if (!part) continue;
45
+ const isLast = i === lastIndex;
46
+ const siblings = childIndex.get(current);
47
+ let child = siblings.get(part);
42
48
  if (!child) {
43
49
  child = {
44
- path: childPath,
50
+ path: `${current.path}/${part}`,
45
51
  children: [],
46
52
  isFile: isLast
47
53
  };
48
54
  current.children.push(child);
55
+ siblings.set(part, child);
56
+ if (!isLast) childIndex.set(child, /* @__PURE__ */ new Map());
49
57
  }
50
58
  current = child;
51
59
  }
52
60
  }
61
+ sortTree(root);
53
62
  return root;
54
63
  }
64
+ function sortTree(node) {
65
+ if (node.children.length === 0) return;
66
+ node.children.sort(compareByPath);
67
+ for (const child of node.children) if (!child.isFile) sortTree(child);
68
+ }
69
+ function compareByPath(a, b) {
70
+ return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
71
+ }
55
72
  //#endregion
56
73
  //#region src/utils/getBarrelFiles.ts
57
74
  const SOURCE_EXTENSIONS = new Set([
@@ -60,199 +77,236 @@ const SOURCE_EXTENSIONS = new Set([
60
77
  ".js",
61
78
  ".jsx"
62
79
  ]);
80
+ const BARREL_SUFFIX = `/${BARREL_FILENAME}`;
63
81
  /**
64
- * Derives a relative module specifier from an absolute `filePath` relative to an absolute `fromDir`.
65
- * The source extension is preserved so that `@kubb/parser-ts` can apply the `extNames` mapping
66
- * (e.g. `.ts` → `.js` for ESM output).
82
+ * Derives a relative module specifier from `filePath` relative to `fromDir`.
83
+ * The source extension is preserved so `@kubb/parser-ts` can apply its `extNames` mapping.
67
84
  *
68
85
  * @example
86
+ * ```ts
69
87
  * toRelativeModulePath('/src/gen/types', '/src/gen/types/pet.ts') // './pet.ts'
70
88
  * toRelativeModulePath('/src/gen/types', '/src/gen/types/tags/tag.ts') // './tags/tag.ts'
89
+ * ```
71
90
  */
72
91
  function toRelativeModulePath(fromDir, filePath) {
73
- return `./${filePath.slice(fromDir.length).replace(/^[/\\]/g, "")}`;
92
+ return `./${filePath.slice(fromDir.length + 1)}`;
74
93
  }
75
- /**
76
- * Generates barrel `FileNode[]` for a given directory tree node using the `'all'` strategy:
77
- * each leaf file gets a `export * from './relPath'` in the barrel of its nearest ancestor directory.
78
- *
79
- * Only a single barrel file (at `treeNode.path`) is generated — sub-directory files are referenced
80
- * with their full relative path from `treeNode.path`.
81
- */
82
- function getBarrelFilesAll(treeNode, sourceFiles) {
83
- const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`));
84
- if (leafPaths.length === 0) return [];
85
- const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`;
86
- const exports = [];
87
- for (const filePath of leafPaths) {
88
- const sourceFile = sourceFiles.find((f) => f.path === filePath);
89
- if (sourceFile && sourceFile.sources.length > 0 && sourceFile.sources.every((s) => !s.isIndexable)) continue;
90
- exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }));
91
- }
92
- if (exports.length === 0) return [];
93
- return [createFile({
94
+ function isBarrelPath(path) {
95
+ return path.endsWith(BARREL_SUFFIX);
96
+ }
97
+ function makeBarrel(dirPath, exports) {
98
+ return createFile({
94
99
  baseName: BARREL_FILENAME,
95
- path: barrelPath,
100
+ path: `${dirPath}${BARREL_SUFFIX}`,
96
101
  exports,
97
102
  sources: [],
98
103
  imports: []
99
- })];
104
+ });
105
+ }
106
+ function hasOnlyNonIndexableSources(sources) {
107
+ if (sources.length === 0) return false;
108
+ for (const source of sources) if (source.isIndexable) return false;
109
+ return true;
100
110
  }
111
+ function partitionIndexableNames(sources) {
112
+ const byTypeOnly = new Map([[false, /* @__PURE__ */ new Set()], [true, /* @__PURE__ */ new Set()]]);
113
+ for (const source of sources) {
114
+ if (!source.isIndexable || !source.name) continue;
115
+ byTypeOnly.get(Boolean(source.isTypeOnly)).add(source.name);
116
+ }
117
+ return byTypeOnly;
118
+ }
119
+ const allStrategy = ({ dirPath, leafPath, sourceFile }) => {
120
+ if (sourceFile && hasOnlyNonIndexableSources(sourceFile.sources)) return [];
121
+ return [createExport({ path: toRelativeModulePath(dirPath, leafPath) })];
122
+ };
123
+ const namedStrategy = ({ dirPath, leafPath, sourceFile }) => {
124
+ const modulePath = toRelativeModulePath(dirPath, leafPath);
125
+ if (!sourceFile) return [createExport({ path: modulePath })];
126
+ const namesByTypeOnly = partitionIndexableNames(sourceFile.sources);
127
+ const valueNames = namesByTypeOnly.get(false);
128
+ const typeNames = namesByTypeOnly.get(true);
129
+ if (valueNames.size === 0 && typeNames.size === 0) {
130
+ if (sourceFile.sources.length > 0) return [];
131
+ return [createExport({ path: modulePath })];
132
+ }
133
+ const exports = [];
134
+ if (valueNames.size > 0) exports.push(createExport({
135
+ name: [...valueNames],
136
+ path: modulePath
137
+ }));
138
+ if (typeNames.size > 0) exports.push(createExport({
139
+ name: [...typeNames],
140
+ path: modulePath,
141
+ isTypeOnly: true
142
+ }));
143
+ return exports;
144
+ };
145
+ const LEAF_STRATEGIES = new Map([["all", allStrategy], ["named", namedStrategy]]);
101
146
  /**
102
- * Generates barrel `FileNode[]` for a given directory tree node using the `'named'` strategy:
103
- * each indexable source in each leaf file gets an individual named `export { name } from '...'`.
147
+ * Single-pass post-order traversal that emits a barrel for each visited directory and
148
+ * returns its leaf paths so parents don't have to re-walk the subtree.
104
149
  */
105
- function getBarrelFilesNamed(treeNode, sourceFiles) {
106
- const leafPaths = collectLeafPaths(treeNode).filter((p) => !p.endsWith(`/${BARREL_FILENAME}`));
107
- if (leafPaths.length === 0) return [];
108
- const barrelPath = `${treeNode.path}/${BARREL_FILENAME}`;
109
- const exports = [];
110
- for (const filePath of leafPaths) {
111
- const sourceFile = sourceFiles.find((f) => f.path === filePath);
112
- if (!sourceFile) {
113
- exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }));
150
+ function walkAllOrNamed(node, params, isRoot, out) {
151
+ const subtreeLeaves = [];
152
+ for (const child of node.children) {
153
+ if (child.isFile) {
154
+ if (!isBarrelPath(child.path)) subtreeLeaves.push(child.path);
114
155
  continue;
115
156
  }
116
- const indexableSources = sourceFile.sources.filter((s) => s.isIndexable && s.name);
117
- if (indexableSources.length === 0) {
118
- if (sourceFile.sources.length > 0) continue;
119
- exports.push(createExport({ path: toRelativeModulePath(treeNode.path, filePath) }));
157
+ const childLeaves = walkAllOrNamed(child, params, false, out);
158
+ for (const leaf of childLeaves) subtreeLeaves.push(leaf);
159
+ }
160
+ if (!isRoot && !params.recursive) return subtreeLeaves;
161
+ const exports = subtreeLeaves.flatMap((leafPath) => params.strategy({
162
+ dirPath: node.path,
163
+ leafPath,
164
+ sourceFile: params.sourceFiles.get(leafPath)
165
+ }));
166
+ if (exports.length > 0) out.push(makeBarrel(node.path, exports));
167
+ return subtreeLeaves;
168
+ }
169
+ /**
170
+ * Emits one barrel per directory: every direct child file is re-exported and every
171
+ * sub-directory is re-exported via its own barrel (recursive by design).
172
+ */
173
+ function walkPropagate(node, out) {
174
+ const exports = [];
175
+ for (const child of node.children) {
176
+ if (child.isFile) {
177
+ if (isBarrelPath(child.path)) continue;
178
+ exports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }));
120
179
  continue;
121
180
  }
122
- const valueNames = indexableSources.filter((s) => !s.isTypeOnly).map((s) => s.name);
123
- const typeNames = indexableSources.filter((s) => s.isTypeOnly).map((s) => s.name);
124
- const modulePath = toRelativeModulePath(treeNode.path, filePath);
125
- if (valueNames.length > 0) exports.push(createExport({
126
- name: valueNames,
127
- path: modulePath
128
- }));
129
- if (typeNames.length > 0) exports.push(createExport({
130
- name: typeNames,
131
- path: modulePath,
132
- isTypeOnly: true
133
- }));
181
+ walkPropagate(child, out);
182
+ exports.push(createExport({ path: toRelativeModulePath(node.path, `${child.path}${BARREL_SUFFIX}`) }));
134
183
  }
135
- if (exports.length === 0) return [];
136
- return [createFile({
137
- baseName: BARREL_FILENAME,
138
- path: barrelPath,
139
- exports,
140
- sources: [],
141
- imports: []
142
- })];
184
+ if (exports.length > 0) out.push(makeBarrel(node.path, exports));
185
+ }
186
+ function indexRelevantFiles(files, outputPath) {
187
+ const outputPrefix = `${outputPath.replaceAll("\\", "/")}/`;
188
+ const sourceFiles = /* @__PURE__ */ new Map();
189
+ const paths = [];
190
+ for (const file of files) {
191
+ const normalized = file.path.replaceAll("\\", "/");
192
+ if (!normalized.startsWith(outputPrefix)) continue;
193
+ if (isBarrelPath(normalized)) continue;
194
+ if (!SOURCE_EXTENSIONS.has(extname(normalized))) continue;
195
+ sourceFiles.set(file.path, file);
196
+ paths.push(file.path);
197
+ }
198
+ return {
199
+ sourceFiles,
200
+ paths
201
+ };
143
202
  }
144
203
  /**
145
- * Generates barrel `FileNode[]` for a given directory tree node using the `'propagate'` strategy:
146
- * like `'all'` but also generates intermediate barrel files for every sub-directory, so that
147
- * consumers can import from any depth.
204
+ * Generates barrel `FileNode`s for the directory rooted at `outputPath`.
148
205
  *
149
- * Leaf barrels export directly from their files; parent barrels export from their sub-barrel files.
206
+ * Files outside `outputPath`, existing barrel files, and non-source extensions are filtered out
207
+ * before the tree is built.
150
208
  */
151
- function getBarrelFilesPropagate(treeNode) {
152
- return collectPropagatedBarrels(treeNode);
153
- }
154
- function collectPropagatedBarrels(node) {
209
+ function getBarrelFiles({ outputPath, files, barrelType, recursive = false }) {
210
+ const { sourceFiles, paths } = indexRelevantFiles(files, outputPath);
211
+ if (paths.length === 0) return [];
212
+ const tree = buildTree(outputPath, paths);
155
213
  const result = [];
156
- const barrelExports = [];
157
- for (const child of node.children) if (child.isFile) {
158
- if (!child.path.endsWith(`/index.ts`)) barrelExports.push(createExport({ path: toRelativeModulePath(node.path, child.path) }));
159
- } else {
160
- const subBarrels = collectPropagatedBarrels(child);
161
- result.push(...subBarrels);
162
- const subBarrelPath = `${child.path}/${BARREL_FILENAME}`;
163
- barrelExports.push(createExport({ path: toRelativeModulePath(node.path, subBarrelPath) }));
214
+ if (barrelType === "propagate") {
215
+ walkPropagate(tree, result);
216
+ return result;
164
217
  }
165
- if (barrelExports.length > 0) result.push(createFile({
166
- baseName: BARREL_FILENAME,
167
- path: `${node.path}/${BARREL_FILENAME}`,
168
- exports: barrelExports,
169
- sources: [],
170
- imports: []
171
- }));
218
+ const strategy = LEAF_STRATEGIES.get(barrelType);
219
+ if (!strategy) return result;
220
+ walkAllOrNamed(tree, {
221
+ sourceFiles,
222
+ strategy,
223
+ recursive
224
+ }, true, result);
172
225
  return result;
173
226
  }
227
+ //#endregion
228
+ //#region src/utils/generatePerPluginBarrel.ts
174
229
  /**
175
- * Collects all leaf (file) paths within a tree node recursively.
230
+ * Generates barrel files for a single plugin's output directory.
231
+ *
232
+ * The barrel is placed at `<config.root>/<config.output.path>/<plugin.options.output.path>/index.ts`.
233
+ * When the plugin uses `group`, additional sub-directory barrels are generated so that grouped
234
+ * output (e.g. `petController/index.ts`) gets its own re-export entry point.
176
235
  */
177
- function collectLeafPaths(node) {
178
- if (node.isFile) return [node.path];
179
- return node.children.flatMap((c) => collectLeafPaths(c));
236
+ function generatePerPluginBarrel({ barrelType, plugin, files, config }) {
237
+ return getBarrelFiles({
238
+ outputPath: resolve(config.root, config.output.path, plugin.options.output.path),
239
+ files,
240
+ barrelType,
241
+ recursive: true
242
+ });
180
243
  }
244
+ //#endregion
245
+ //#region src/utils/generateRootBarrel.ts
181
246
  /**
182
- * Generates barrel `FileNode[]` for a directory rooted at `outputPath`, given the full set of
183
- * generated source `files`, using the specified `barrelType` strategy.
247
+ * Generates the root barrel file at `<config.root>/<config.output.path>/index.ts`.
184
248
  *
185
- * Files not located inside `outputPath` are excluded automatically.
186
- *
187
- * @param outputPath Absolute path to the output directory.
188
- * @param files All generated files (across all plugins).
189
- * @param barrelType Barrel generation strategy.
249
+ * Unlike `generatePerPluginBarrel`, this does not recurse into sub-directories each
250
+ * plugin is responsible for its own per-plugin barrels.
190
251
  */
191
- function getBarrelFiles(outputPath, files, barrelType) {
192
- const relevantFiles = files.filter((f) => {
193
- const normalizedFilePath = f.path.replace(/\\/g, "/");
194
- const normalizedOutputPath = outputPath.replace(/\\/g, "/");
195
- if (!normalizedFilePath.startsWith(normalizedOutputPath + "/")) return false;
196
- if (normalizedFilePath.endsWith(`/index.ts`)) return false;
197
- const dotIndex = normalizedFilePath.lastIndexOf(".");
198
- const ext = dotIndex === -1 ? "" : normalizedFilePath.slice(dotIndex);
199
- return SOURCE_EXTENSIONS.has(ext);
252
+ function generateRootBarrel({ barrelType, files, config }) {
253
+ return getBarrelFiles({
254
+ outputPath: resolve(config.root, config.output.path),
255
+ files,
256
+ barrelType
200
257
  });
201
- if (relevantFiles.length === 0) return [];
202
- const tree = buildTree(outputPath, relevantFiles.map((f) => f.path));
203
- switch (barrelType) {
204
- case "all": return getBarrelFilesAll(tree, relevantFiles);
205
- case "named": return getBarrelFilesNamed(tree, relevantFiles);
206
- case "propagate": return getBarrelFilesPropagate(tree);
207
- default: return [];
208
- }
209
258
  }
210
259
  //#endregion
211
- //#region src/utils/generatePerPluginBarrel.ts
260
+ //#region src/utils/excludedPaths.ts
212
261
  /**
213
- * Generates barrel files for a single plugin's output directory.
262
+ * Returns the absolute output directory of `plugin` with a trailing separator,
263
+ * suitable for prefix-based exclusion checks via {@link isExcludedPath}.
214
264
  *
215
- * The barrel file is placed at `resolve(config.root, config.output.path, plugin.options.output.path)/index.ts`
216
- * and re-exports all files generated by that plugin, using the given `barrelType` strategy.
265
+ * The trailing `/` ensures `startsWith` does not match unrelated siblings
266
+ * (e.g. `/foo/bar` vs `/foo/barbaz/x.ts`).
267
+ */
268
+ function getPluginOutputPrefix(plugin, config) {
269
+ return `${resolve(config.root, config.output.path, plugin.options.output.path)}/`;
270
+ }
271
+ /**
272
+ * Returns `true` when `filePath` lies under any of the directory prefixes in `prefixes`.
273
+ * Prefixes must already include a trailing separator (see {@link getPluginOutputPrefix}).
217
274
  *
218
- * Note: `plugin.options.output.path` is relative to `config.output.path`, not to `config.root`.
219
- * Files generated by plugins land at `config.root + config.output.path + plugin.output.path`,
220
- * so the barrel directory must be resolved with all three segments.
275
+ * Uses Node 22 iterator helpers (`Iterator.prototype.some`) to avoid materializing the set.
221
276
  */
222
- function generatePerPluginBarrel({ barrelType, plugin, files, config }) {
223
- return getBarrelFiles(resolve(config.root, config.output.path, plugin.options.output.path), files, barrelType);
277
+ function isExcludedPath(filePath, prefixes) {
278
+ return prefixes.values().some((prefix) => filePath.startsWith(prefix));
224
279
  }
225
280
  //#endregion
226
- //#region src/utils/generateRootBarrel.ts
281
+ //#region src/utils/resolveBarrelType.ts
282
+ const DEFAULT_BARREL_TYPE = "named";
227
283
  /**
228
- * Generates a root barrel file at `resolve(config.root, config.output.path)/index.ts`.
229
- *
230
- * The root barrel re-exports from all files across all plugins that are located
231
- * inside the root output directory, using the given `barrelType` strategy.
232
- *
233
- * In practice this re-exports the per-plugin barrels when `barrelType = 'propagate'`,
234
- * or all individual files when `barrelType = 'all'` or `'named'`.
284
+ * Resolves the effective barrel style for a single plugin: explicit plugin option →
285
+ * root config option → `'named'` default. Returns `false` when barrel generation is disabled.
235
286
  */
236
- function generateRootBarrel({ barrelType, files, config }) {
237
- return getBarrelFiles(resolve(config.root, config.output.path), files, barrelType);
287
+ function resolvePluginBarrelType(plugin, config) {
288
+ return plugin.options.output?.barrelType ?? config.output.barrelType ?? DEFAULT_BARREL_TYPE;
289
+ }
290
+ /**
291
+ * Resolves the effective barrel style for the root `index.ts`: root config option → `'named'` default.
292
+ * Returns `false` when the root barrel is disabled.
293
+ */
294
+ function resolveRootBarrelType(config) {
295
+ return config.output.barrelType ?? DEFAULT_BARREL_TYPE;
238
296
  }
239
297
  //#endregion
240
298
  //#region src/middleware.ts
241
299
  /**
242
- * Barrel-file generation middleware.
243
- *
244
- * When added to `config.middleware`, generates an `index.ts` barrel file for each
245
- * plugin. When `output.barrelType` is omitted on a plugin it inherits from `config.output.barrelType`,
246
- * and when that is also omitted both default to `'all'`. Set `barrelType: false` to disable
247
- * barrel generation for a specific plugin or for the root entirely.
300
+ * Generates `index.ts` barrel files for each plugin's output directory and one root barrel
301
+ * at `config.output.path/index.ts`.
248
302
  *
249
- * The `barrelType` option controls the re-export style:
250
- * - `'all'` — `export * from '...'` for each generated file
251
- * - `'named'` — `export { name1, name2 } from '...'` using each file's indexable sources
252
- * - `'propagate'` — like `'all'` with intermediate barrel files for sub-directories
303
+ * Plugins inherit `output.barrelType` from `config.output.barrelType` (which itself defaults to `'named'`).
304
+ * Setting `barrelType: false` on a plugin disables its barrel and excludes the plugin's files from the
305
+ * root barrel as well.
253
306
  *
254
307
  * @example
255
308
  * ```ts
309
+ * import { defineConfig } from '@kubb/core'
256
310
  * import { middlewareBarrel } from '@kubb/middleware-barrel'
257
311
  *
258
312
  * export default defineConfig({
@@ -269,15 +323,15 @@ const middlewareBarrel = defineMiddleware({
269
323
  name: "middleware-barrel",
270
324
  install(hooks) {
271
325
  let ctx;
272
- const excludedPaths = /* @__PURE__ */ new Set();
326
+ const excludedPrefixes = /* @__PURE__ */ new Set();
273
327
  hooks.on("kubb:build:start", (buildCtx) => {
274
328
  ctx = buildCtx;
275
329
  });
276
330
  hooks.on("kubb:plugin:end", ({ plugin }) => {
277
331
  if (!ctx) return;
278
- const barrelType = plugin.options.output?.barrelType ?? ctx.config.output.barrelType ?? "all";
332
+ const barrelType = resolvePluginBarrelType(plugin, ctx.config);
279
333
  if (!barrelType) {
280
- excludedPaths.add(resolve(ctx.config.root, ctx.config.output.path, plugin.options.output.path));
334
+ excludedPrefixes.add(getPluginOutputPrefix(plugin, ctx.config));
281
335
  return;
282
336
  }
283
337
  const barrelFiles = generatePerPluginBarrel({
@@ -289,11 +343,11 @@ const middlewareBarrel = defineMiddleware({
289
343
  if (barrelFiles.length > 0) ctx.upsertFile(...barrelFiles);
290
344
  });
291
345
  hooks.on("kubb:plugins:end", ({ files, config, upsertFile }) => {
292
- const rootBarrelType = config.output.barrelType ?? "all";
346
+ const rootBarrelType = resolveRootBarrelType(config);
293
347
  if (!rootBarrelType) return;
294
348
  const rootBarrelFiles = generateRootBarrel({
295
349
  barrelType: rootBarrelType,
296
- files: excludedPaths.size > 0 ? files.filter((f) => ![...excludedPaths].some((excluded) => f.path.startsWith(excluded + "/"))) : files,
350
+ files: excludedPrefixes.size === 0 ? files : files.filter((f) => !isExcludedPath(f.path, excludedPrefixes)),
297
351
  config
298
352
  });
299
353
  if (rootBarrelFiles.length > 0) upsertFile(...rootBarrelFiles);